summaryrefslogtreecommitdiff
path: root/opendc-web
diff options
context:
space:
mode:
authorFabian Mastenbroek <mail.fabianm@gmail.com>2022-04-05 21:15:57 +0200
committerFabian Mastenbroek <mail.fabianm@gmail.com>2022-04-06 13:39:32 +0200
commit68d9003f8d8d2adcba43cad6366eca5365110e48 (patch)
tree8e9287ae4c738229e82ace3e9b39d33a2953f490 /opendc-web
parentf2ff40b5170260289e99e0506525f0905f380907 (diff)
feat(web/ui): Add support for unauthenticated user access
This change updates the web UI and API to support unauthenticated user access. Such functionality is helpful when there is just a single user that wants to try OpenDC.
Diffstat (limited to 'opendc-web')
-rw-r--r--opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/util/DevSecurityOverrideFilter.kt51
-rw-r--r--opendc-web/opendc-web-api/src/main/resources/application-dev.properties12
-rw-r--r--opendc-web/opendc-web-ui/public/img/avatar.svg18
-rw-r--r--opendc-web/opendc-web-ui/src/api/index.js17
-rw-r--r--opendc-web/opendc-web-ui/src/auth.js42
-rw-r--r--opendc-web/opendc-web-ui/src/components/AppHeaderTools.js16
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/MachineComponent.js7
-rw-r--r--opendc-web/opendc-web-ui/src/config.js41
-rw-r--r--opendc-web/opendc-web-ui/src/data/project.js2
-rw-r--r--opendc-web/opendc-web-ui/src/pages/_app.js18
-rw-r--r--opendc-web/opendc-web-ui/src/pages/projects/index.js17
-rw-r--r--opendc-web/opendc-web-ui/src/redux/index.js3
12 files changed, 185 insertions, 59 deletions
diff --git a/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/util/DevSecurityOverrideFilter.kt b/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/util/DevSecurityOverrideFilter.kt
new file mode 100644
index 00000000..ba2cf2ae
--- /dev/null
+++ b/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/util/DevSecurityOverrideFilter.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2022 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.opendc.web.api.util
+
+import io.quarkus.arc.properties.IfBuildProperty
+import java.security.Principal
+import javax.ws.rs.container.ContainerRequestContext
+import javax.ws.rs.container.ContainerRequestFilter
+import javax.ws.rs.container.PreMatching
+import javax.ws.rs.core.SecurityContext
+import javax.ws.rs.ext.Provider
+
+/**
+ * Helper class to disable security for the OpenDC web API when in development mode.
+ */
+@Provider
+@PreMatching
+@IfBuildProperty(name = "opendc.security.enabled", stringValue = "false")
+class DevSecurityOverrideFilter : ContainerRequestFilter {
+ override fun filter(requestContext: ContainerRequestContext) {
+ requestContext.securityContext = object : SecurityContext {
+ override fun getUserPrincipal(): Principal = Principal { "anon" }
+
+ override fun isSecure(): Boolean = false
+
+ override fun isUserInRole(role: String): Boolean = true
+
+ override fun getAuthenticationScheme(): String = "basic"
+ }
+ }
+}
diff --git a/opendc-web/opendc-web-api/src/main/resources/application-dev.properties b/opendc-web/opendc-web-api/src/main/resources/application-dev.properties
index 1c1c6950..08d11609 100644
--- a/opendc-web/opendc-web-api/src/main/resources/application-dev.properties
+++ b/opendc-web/opendc-web-api/src/main/resources/application-dev.properties
@@ -27,10 +27,14 @@ quarkus.hibernate-orm.dialect=org.hibernate.dialect.H2Dialect
quarkus.hibernate-orm.database.generation=drop-and-create
quarkus.hibernate-orm.sql-load-script=init-dev.sql
+# OpenID
+quarkus.oidc.enabled=false
+quarkus.oidc.auth-server-url=
+quarkus.oidc.client-id=
+
# OpenDC web UI
quarkus.opendc-ui.path=/
-quarkus.opendc-ui.auth.domain=${OPENDC_AUTH0_DOMAIN}
-quarkus.opendc-ui.auth.client-id=${OPENDC_AUTH0_CLIENT_ID}
-quarkus.opendc-ui.auth.audience=${OPENDC_AUTH0_AUDIENCE}
-
quarkus.resteasy.path=/api
+
+opendc.security.enabled=false
+
diff --git a/opendc-web/opendc-web-ui/public/img/avatar.svg b/opendc-web/opendc-web-ui/public/img/avatar.svg
new file mode 100644
index 00000000..73726f9b
--- /dev/null
+++ b/opendc-web/opendc-web-ui/public/img/avatar.svg
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 24.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ viewBox="0 0 36 36" style="enable-background:new 0 0 36 36;" xml:space="preserve">
+<style type="text/css">
+ .st0{fill-rule:evenodd;clip-rule:evenodd;fill:#F0F0F0;}
+ .st1{fill-rule:evenodd;clip-rule:evenodd;fill:#D2D2D2;}
+ .st2{fill:#B8BBBE;}
+ .st3{fill:#D2D2D2;}
+</style>
+<rect class="st0" width="36" height="36"/>
+<path class="st1" d="M17.7,20.1c-3.5,0-6.4-2.9-6.4-6.4s2.9-6.4,6.4-6.4s6.4,2.9,6.4,6.4S21.3,20.1,17.7,20.1z"/>
+<path class="st2" d="M13.3,36l0-6.7c-2,0.4-2.9,1.4-3.1,3.5L10.1,36H13.3z"/>
+<path class="st3" d="M10.1,36l0.1-3.2c0.2-2.1,1.1-3.1,3.1-3.5l0,6.7h9.4l0-6.7c2,0.4,2.9,1.4,3.1,3.5l0.1,3.2h4.7
+ c-0.4-3.9-1.3-9-2.9-11c-1.1-1.4-2.3-2.2-3.5-2.6s-1.8-0.6-6.3-0.6s-6.1,0.7-6.1,0.7c-1.2,0.4-2.4,1.2-3.4,2.6
+ C6.7,27,5.8,32.2,5.4,36H10.1z"/>
+<path class="st2" d="M25.9,36l-0.1-3.2c-0.2-2.1-1.1-3.1-3.1-3.5l0,6.7H25.9z"/>
+</svg>
diff --git a/opendc-web/opendc-web-ui/src/api/index.js b/opendc-web/opendc-web-ui/src/api/index.js
index 1a9877d0..75751658 100644
--- a/opendc-web/opendc-web-ui/src/api/index.js
+++ b/opendc-web/opendc-web-ui/src/api/index.js
@@ -20,7 +20,7 @@
* SOFTWARE.
*/
-const apiUrl = process.env.NEXT_PUBLIC_API_BASE_URL
+import { apiUrl } from '../config'
/**
* Send the specified request to the OpenDC API.
@@ -31,14 +31,19 @@ const apiUrl = process.env.NEXT_PUBLIC_API_BASE_URL
* @param body The body of the request.
*/
export async function request(auth, path, method = 'GET', body) {
+ const headers = {
+ 'Content-Type': 'application/json',
+ }
+
const { getAccessTokenSilently } = auth
- const token = await getAccessTokenSilently()
+ if (getAccessTokenSilently) {
+ const token = await getAccessTokenSilently()
+ headers['Authorization'] = `Bearer ${token}`
+ }
+
const response = await fetch(`${apiUrl}/${path}`, {
method: method,
- headers: {
- Authorization: `Bearer ${token}`,
- 'Content-Type': 'application/json',
- },
+ headers: headers,
body: body && JSON.stringify(body),
})
const json = await response.json()
diff --git a/opendc-web/opendc-web-ui/src/auth.js b/opendc-web/opendc-web-ui/src/auth.js
index e670476c..3d6cf87c 100644
--- a/opendc-web/opendc-web-ui/src/auth.js
+++ b/opendc-web/opendc-web-ui/src/auth.js
@@ -23,15 +23,27 @@
import PropTypes from 'prop-types'
import { Auth0Provider, useAuth0 } from '@auth0/auth0-react'
import { useEffect } from 'react'
+import { auth } from './config'
/**
- * Obtain the authentication context.
+ * Helper function to provide the authentication context in case Auth0 is not
+ * configured.
*/
-export function useAuth() {
- return useAuth0()
+function useAuthDev() {
+ return {
+ isAuthenticated: false,
+ isLoading: false,
+ logout: () => {},
+ loginWithRedirect: () => {},
+ }
}
/**
+ * Obtain the authentication context.
+ */
+export const useAuth = auth.domain ? useAuth0 : useAuthDev
+
+/**
* Force the user to be authenticated or redirect to the homepage.
*/
export function useRequireAuth() {
@@ -51,16 +63,20 @@ export function useRequireAuth() {
* AuthProvider which provides an authentication context.
*/
export function AuthProvider({ children }) {
- return (
- <Auth0Provider
- domain={process.env.NEXT_PUBLIC_AUTH0_DOMAIN}
- clientId={process.env.NEXT_PUBLIC_AUTH0_CLIENT_ID}
- redirectUri={global.window && global.window.location.origin}
- audience={process.env.NEXT_PUBLIC_AUTH0_AUDIENCE}
- >
- {children}
- </Auth0Provider>
- )
+ if (auth.domain) {
+ return (
+ <Auth0Provider
+ domain={auth.domain}
+ clientId={auth.clientId}
+ redirectUri={auth.redirectUri}
+ audience={auth.audience}
+ >
+ {children}
+ </Auth0Provider>
+ )
+ }
+
+ return children
}
AuthProvider.propTypes = {
diff --git a/opendc-web/opendc-web-ui/src/components/AppHeaderTools.js b/opendc-web/opendc-web-ui/src/components/AppHeaderTools.js
index 02e5d265..3e58b209 100644
--- a/opendc-web/opendc-web-ui/src/components/AppHeaderTools.js
+++ b/opendc-web/opendc-web-ui/src/components/AppHeaderTools.js
@@ -39,7 +39,9 @@ import { useAuth } from '../auth'
import { GithubIcon, HelpIcon } from '@patternfly/react-icons'
function AppHeaderTools() {
- const auth = useAuth()
+ const { logout, user, isAuthenticated, isLoading } = useAuth()
+ const username = isAuthenticated || isLoading ? user?.name : 'Anonymous'
+ const avatar = isAuthenticated || isLoading ? user?.picture : '/img/avatar.svg'
const [isKebabDropdownOpen, setKebabDropdownOpen] = useState(false)
const kebabDropdownItems = [
@@ -56,7 +58,11 @@ function AppHeaderTools() {
const [isDropdownOpen, setDropdownOpen] = useState(false)
const userDropdownItems = [
<DropdownGroup key="group 2">
- <DropdownItem key="group 2 logout" onClick={() => auth.logout({ returnTo: window.location.origin })}>
+ <DropdownItem
+ key="group 2 logout"
+ isDisabled={!isAuthenticated}
+ onClick={() => logout({ returnTo: window.location.origin })}
+ >
Logout
</DropdownItem>
</DropdownGroup>,
@@ -105,7 +111,7 @@ function AppHeaderTools() {
isOpen={isDropdownOpen}
toggle={
<DropdownToggle onToggle={() => setDropdownOpen(!isDropdownOpen)}>
- {auth?.user?.name ?? (
+ {username ?? (
<Skeleton
fontSize="xs"
width="150px"
@@ -119,8 +125,8 @@ function AppHeaderTools() {
/>
</PageHeaderToolsItem>
</PageHeaderToolsGroup>
- {auth?.user?.picture ? (
- <Avatar src={auth.user.picture} alt="Avatar image" />
+ {avatar ? (
+ <Avatar src={avatar} alt="Avatar image" />
) : (
<Skeleton className="pf-c-avatar" shape="circle" width="2.25rem" screenreaderText="Loading avatar" />
)}
diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/MachineComponent.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/MachineComponent.js
index 8897f2d0..18c3db3c 100644
--- a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/MachineComponent.js
+++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/MachineComponent.js
@@ -5,12 +5,7 @@ import { Machine } from '../../../../shapes'
const UnitIcon = ({ id, type }) => (
// eslint-disable-next-line @next/next/no-img-element
- <img
- src={'/img/topology/' + id + '-icon.png'}
- alt={'Machine contains ' + type + ' units'}
- height={24}
- width={24}
- />
+ <img src={'/img/topology/' + id + '-icon.png'} alt={'Machine contains ' + type + ' units'} height={24} width={24} />
)
UnitIcon.propTypes = {
diff --git a/opendc-web/opendc-web-ui/src/config.js b/opendc-web/opendc-web-ui/src/config.js
new file mode 100644
index 00000000..52952d69
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/config.js
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2022 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+/**
+ * URL to OpenDC API.
+ */
+export const apiUrl = process.env.NEXT_PUBLIC_API_BASE_URL
+
+/**
+ * Authentication configuration.
+ */
+export const auth = {
+ domain: process.env.NEXT_PUBLIC_AUTH0_DOMAIN,
+ clientId: process.env.NEXT_PUBLIC_AUTH0_CLIENT_ID,
+ audience: process.env.NEXT_PUBLIC_AUTH0_AUDIENCE,
+ redirectUri: global.window && global.window.location.origin,
+}
+
+/**
+ * Sentry DSN for web frontend.
+ */
+export const sentryDsn = process.env.NEXT_PUBLIC_SENTRY_DSN
diff --git a/opendc-web/opendc-web-ui/src/data/project.js b/opendc-web/opendc-web-ui/src/data/project.js
index b1db3da5..60a8fab6 100644
--- a/opendc-web/opendc-web-ui/src/data/project.js
+++ b/opendc-web/opendc-web-ui/src/data/project.js
@@ -23,7 +23,7 @@
import { useQuery, useMutation } from 'react-query'
import { addProject, deleteProject, fetchProject, fetchProjects } from '../api/projects'
import { addPortfolio, deletePortfolio, fetchPortfolio, fetchPortfolios } from '../api/portfolios'
-import { addScenario, deleteScenario, fetchScenario, fetchScenariosOfPortfolio } from '../api/scenarios'
+import { addScenario, deleteScenario, fetchScenario } from '../api/scenarios'
/**
* Configure the query defaults for the project endpoints.
diff --git a/opendc-web/opendc-web-ui/src/pages/_app.js b/opendc-web/opendc-web-ui/src/pages/_app.js
index 4861f5c1..bac9a5af 100644
--- a/opendc-web/opendc-web-ui/src/pages/_app.js
+++ b/opendc-web/opendc-web-ui/src/pages/_app.js
@@ -30,6 +30,7 @@ import { AuthProvider, useRequireAuth } from '../auth'
import * as Sentry from '@sentry/react'
import { Integrations } from '@sentry/tracing'
import { QueryClientProvider } from 'react-query'
+import { sentryDsn } from '../config'
import '@patternfly/react-core/dist/styles/base.css'
import '@patternfly/react-styles/css/utilities/Alignment/alignment.css'
@@ -67,17 +68,14 @@ Inner.propTypes = {
}).isRequired,
}
-const dsn = process.env.NEXT_PUBLIC_SENTRY_DSN
// Initialize Sentry if the user has configured a DSN
-if (process.browser && dsn) {
- if (dsn) {
- Sentry.init({
- environment: process.env.NODE_ENV,
- dsn: dsn,
- integrations: [new Integrations.BrowserTracing()],
- tracesSampleRate: 0.1,
- })
- }
+if (process.browser && sentryDsn) {
+ Sentry.init({
+ environment: process.env.NODE_ENV,
+ dsn: sentryDsn,
+ integrations: [new Integrations.BrowserTracing()],
+ tracesSampleRate: 0.1,
+ })
}
export default function App(props) {
diff --git a/opendc-web/opendc-web-ui/src/pages/projects/index.js b/opendc-web/opendc-web-ui/src/pages/projects/index.js
index bb1fbd69..40792275 100644
--- a/opendc-web/opendc-web-ui/src/pages/projects/index.js
+++ b/opendc-web/opendc-web-ui/src/pages/projects/index.js
@@ -23,38 +23,29 @@
import React, { useMemo, useState } from 'react'
import Head from 'next/head'
import ProjectFilterPanel from '../../components/projects/FilterPanel'
-import { useAuth } from '../../auth'
import { AppPage } from '../../components/AppPage'
import { PageSection, PageSectionVariants, Text, TextContent } from '@patternfly/react-core'
import { useProjects, useDeleteProject } from '../../data/project'
import ProjectTable from '../../components/projects/ProjectTable'
import NewProject from '../../components/projects/NewProject'
-const getVisibleProjects = (projects, filter, userId) => {
+const getVisibleProjects = (projects, filter) => {
switch (filter) {
case 'SHOW_ALL':
return projects
case 'SHOW_OWN':
- return projects.filter((project) =>
- project.authorizations.some((a) => a.userId === userId && a.level === 'OWN')
- )
+ return projects.filter((project) => project.role === 'OWNER')
case 'SHOW_SHARED':
- return projects.filter((project) =>
- project.authorizations.some((a) => a.userId === userId && a.level !== 'OWN')
- )
+ return projects.filter((project) => project.role !== 'OWNER')
default:
return projects
}
}
function Projects() {
- const { user } = useAuth()
const { status, data: projects } = useProjects()
const [filter, setFilter] = useState('SHOW_ALL')
- const visibleProjects = useMemo(
- () => getVisibleProjects(projects ?? [], filter, user?.sub),
- [projects, filter, user?.sub]
- )
+ const visibleProjects = useMemo(() => getVisibleProjects(projects ?? [], filter), [projects, filter])
const { mutate: deleteProject } = useDeleteProject()
diff --git a/opendc-web/opendc-web-ui/src/redux/index.js b/opendc-web/opendc-web-ui/src/redux/index.js
index fa0c9d23..53cd9144 100644
--- a/opendc-web/opendc-web-ui/src/redux/index.js
+++ b/opendc-web/opendc-web-ui/src/redux/index.js
@@ -6,6 +6,7 @@ import thunk from 'redux-thunk'
import rootReducer from './reducers'
import rootSaga from './sagas'
import { createReduxEnhancer } from '@sentry/react'
+import { sentryDsn } from '../config'
let store
@@ -20,7 +21,7 @@ function initStore(initialState, ctx) {
let middleware = applyMiddleware(...middlewares)
- if (process.env.NEXT_PUBLIC_SENTRY_DSN) {
+ if (sentryDsn) {
middleware = compose(middleware, createReduxEnhancer())
}