diff --git a/CHANGELOG.md b/CHANGELOG.md index 0dba277510..cdd486fbf3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ ENHANCEMENTS: BUG FIXES: * Upgrade unresticted and airlock base template versions due to diagnostic settings retention period being depreciated ([#3704](https://github.com/microsoft/AzureTRE/pull/3704)) -* Fix TRE Admin not being able to view operations log when workspace has no auth provisioned ([#2363](https://github.com/microsoft/AzureTRE/issues/2363)) +* Enable TRE Admins to view workspace details when don't have a workspace role ([#2363](https://github.com/microsoft/AzureTRE/issues/2363)) * Fix grey box appearing on resource card when costs are not available. ([#3254](https://github.com/microsoft/AzureTRE/issues/3254)) ## 0.14.1 (September 1, 2023) diff --git a/ui/app/src/components/workspaces/WorkspaceProvider.tsx b/ui/app/src/components/workspaces/WorkspaceProvider.tsx index 27cdfc1959..f0a8504a52 100644 --- a/ui/app/src/components/workspaces/WorkspaceProvider.tsx +++ b/ui/app/src/components/workspaces/WorkspaceProvider.tsx @@ -1,4 +1,4 @@ -import { Spinner, SpinnerSize, Stack } from '@fluentui/react'; +import { FontIcon, Icon, Label, Spinner, SpinnerSize, Stack, getTheme, mergeStyles } from '@fluentui/react'; import React, { useContext, useEffect, useRef, useState } from 'react'; import { Route, Routes, useParams } from 'react-router-dom'; import { ApiEndpoint } from '../../models/apiEndpoints'; @@ -32,7 +32,7 @@ export const WorkspaceProvider: React.FunctionComponent = () => { const { workspaceId } = useParams(); const appRoles = useContext(AppRolesContext); - const authProvisionedRef = useRef(false); + const refIsTREAdminUser = useRef(false); // set workspace context from url useEffect(() => { @@ -41,42 +41,62 @@ export const WorkspaceProvider: React.FunctionComponent = () => { // get the workspace - first we get the scope_id so we can auth against the right aad app let scopeId = (await apiCall(`${ApiEndpoint.Workspaces}/${workspaceId}/scopeid`, HttpMethod.Get)).workspaceAuth.scopeId; - authProvisionedRef.current = scopeId !== ""; + const authProvisioned = scopeId !== ""; - if (authProvisionedRef.current) { - const ws = (await apiCall(`${ApiEndpoint.Workspaces}/${workspaceId}`, HttpMethod.Get, scopeId)).workspace; - workspaceCtx.current.setWorkspace(ws); - const ws_application_id_uri = ws.properties.scope_id; + let wsRoles: Array = []; + let ws: Workspace = {} as Workspace; + + if (authProvisioned) { + try { + ws = (await apiCall(`${ApiEndpoint.Workspaces}/${workspaceId}`, HttpMethod.Get, scopeId)).workspace; + workspaceCtx.current.setWorkspace(ws); + // use the client ID to get a token against the workspace (tokenOnly), and set the workspace roles in the context + + await apiCall(`${ApiEndpoint.Workspaces}/${workspaceId}`, HttpMethod.Get, ws.properties.scope_id, undefined, ResultType.JSON, (roles: Array) => { + workspaceCtx.current.setRoles(roles); + wsRoles = roles; + }, true); + } catch (e: any) { + // do nothing, we need to try as TRE Admin + } + } - // use the client ID to get a token against the workspace (tokenOnly), and set the workspace roles in the context - let wsRoles: Array = []; - await apiCall(`${ApiEndpoint.Workspaces}/${workspaceId}`, HttpMethod.Get, ws_application_id_uri, undefined, ResultType.JSON, (roles: Array) => { - workspaceCtx.current.setRoles(roles); - wsRoles = roles; - }, true); + if (wsRoles.length > 0) { // get workspace services to pass to nav + ws services page - const workspaceServices = await apiCall(`${ApiEndpoint.Workspaces}/${ws.id}/${ApiEndpoint.WorkspaceServices}`, HttpMethod.Get, ws_application_id_uri); + const workspaceServices = await apiCall(`${ApiEndpoint.Workspaces}/${ws.id}/${ApiEndpoint.WorkspaceServices}`, HttpMethod.Get, ws.properties.scope_id); setWorkspaceServices(workspaceServices.workspaceServices); - setLoadingState(wsRoles && wsRoles.length > 0 ? LoadingState.Ok : LoadingState.AccessDenied); - + setLoadingState(LoadingState.Ok); // get shared services to pass to nav shared services pages const sharedServices = await apiCall(ApiEndpoint.SharedServices, HttpMethod.Get); setSharedServices(sharedServices.sharedServices); + } else if (appRoles.roles.includes(RoleName.TREAdmin)) { + + ws = (await apiCall(`${ApiEndpoint.Workspaces}/${workspaceId}`, HttpMethod.Get)).workspace; + workspaceCtx.current.setWorkspace(ws); + setLoadingState(LoadingState.Ok); + refIsTREAdminUser.current = true; } else { - if (appRoles.roles.includes(RoleName.TREAdmin)) { - const ws = (await apiCall(`${ApiEndpoint.Workspaces}/${workspaceId}`, HttpMethod.Get)).workspace; - workspaceCtx.current.setWorkspace(ws); - setLoadingState(LoadingState.Ok); - } else { - setLoadingState(LoadingState.AccessDenied); - } + let e = new APIError(); + e.status = 403; + e.userMessage = "User does not have a role assigned in the workspace or the TRE Admin role assigned"; + e.endpoint = `${ApiEndpoint.Workspaces}/${workspaceId}`; + throw e; } + } catch (e: any) { - e.userMessage = 'Error retrieving workspace'; - setApiError(e); - setLoadingState(LoadingState.Error); + if (e.status === 401 || e.status === 403) { + setApiError(e); + setLoadingState(LoadingState.AccessDenied) + } else { + e.userMessage = 'Error retrieving workspace'; + setApiError(e); + setLoadingState(LoadingState.Error); + } } + + console.log('loading state: ' + loadingState); + }; getWorkspace(); @@ -87,7 +107,7 @@ export const WorkspaceProvider: React.FunctionComponent = () => { ctx.setRoles([]); ctx.setWorkspace({} as Workspace); }); - }, [apiCall, workspaceId, appRoles.roles]); + }, [apiCall, workspaceId, appRoles.roles, loadingState]); const addWorkspaceService = (w: WorkspaceService) => { let ws = [...workspaceServices] @@ -105,7 +125,6 @@ export const WorkspaceProvider: React.FunctionComponent = () => { const removeWorkspaceService = (w: WorkspaceService) => { let i = workspaceServices.findIndex((f: WorkspaceService) => f.id === w.id); let ws = [...workspaceServices]; - console.log("removing WS...", ws[i]); ws.splice(i, 1); setWorkspaceServices(ws); } @@ -116,28 +135,40 @@ export const WorkspaceProvider: React.FunctionComponent = () => { <> - - setSelectedWorkspaceService(ws)} - addWorkspaceService={(ws: WorkspaceService) => addWorkspaceService(ws)} /> - + {!refIsTREAdminUser.current && ( + + setSelectedWorkspaceService(ws)} + addWorkspaceService={(ws: WorkspaceService) => addWorkspaceService(ws)} /> + + )} - {authProvisionedRef.current && ( + {!refIsTREAdminUser.current ? ( setSelectedWorkspaceService(ws)} addWorkspaceService={(ws: WorkspaceService) => addWorkspaceService(ws)} updateWorkspaceService={(ws: WorkspaceService) => updateWorkspaceService(ws)} removeWorkspaceService={(ws: WorkspaceService) => removeWorkspaceService(ws)} /> + ) : ( + + + + You are currently accessing this workspace using a TRE Admin role. Additional funcitonality requires a workspace role, such as Workspace Owner. + + )} - } /> - {authProvisionedRef.current && ( + } + /> + {!refIsTREAdminUser.current && ( <> { updateWorkspaceService={(ws: WorkspaceService) => updateWorkspaceService(ws)} removeWorkspaceService={(ws: WorkspaceService) => removeWorkspaceService(ws)} /> } /> + } /> @@ -172,6 +204,7 @@ export const WorkspaceProvider: React.FunctionComponent = () => { ); case LoadingState.Error: + case LoadingState.AccessDenied: return ( ) @@ -183,3 +216,10 @@ export const WorkspaceProvider: React.FunctionComponent = () => { ) } }; + +const { palette } = getTheme(); +const warningIcon = mergeStyles({ + color: palette.orangeLight, + fontSize: 18, + marginRight: 8 +});