Airlock manager can use user resources (#2649)

Co-authored-by: JaimieWi <92854957+JaimieWi@users.noreply.github.com>
This commit is contained in:
tanya-borisova 2022-09-26 18:36:26 +01:00 коммит произвёл GitHub
Родитель 9dc7b55810
Коммит 5067730259
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
13 изменённых файлов: 110 добавлений и 44 удалений

1
.gitignore поставляемый
Просмотреть файл

@ -209,3 +209,4 @@ pytest_api_unit_failed
validation.txt validation.txt
/index.html /index.html
.DS_Store

Просмотреть файл

@ -1 +1 @@
__version__ = "0.4.40" __version__ = "0.4.41"

Просмотреть файл

@ -24,7 +24,10 @@ from models.schemas.resource_template import ResourceTemplateInformationInList
from resources import strings from resources import strings
from services.access_service import AuthConfigValidationError from services.access_service import AuthConfigValidationError
from services.authentication import get_current_admin_user, \ from services.authentication import get_current_admin_user, \
get_access_service, get_current_workspace_owner_user, get_current_workspace_owner_or_researcher_user, get_current_tre_user_or_tre_admin, get_current_workspace_owner_or_researcher_user_or_tre_admin, get_current_workspace_owner_or_tre_admin get_access_service, get_current_workspace_owner_user, get_current_workspace_owner_or_researcher_user, get_current_tre_user_or_tre_admin, \
get_current_workspace_owner_or_tre_admin, \
get_current_workspace_owner_or_researcher_user_or_airlock_manager, \
get_current_workspace_owner_or_researcher_user_or_airlock_manager_or_tre_admin
from services.authentication import extract_auth_information from services.authentication import extract_auth_information
from services.azure_resource_status import get_azure_resource_status from services.azure_resource_status import get_azure_resource_status
from azure.cosmos.exceptions import CosmosAccessConditionFailedError from azure.cosmos.exceptions import CosmosAccessConditionFailedError
@ -33,13 +36,13 @@ from .resource_helpers import get_identity_role_assignments, save_and_deploy_res
from models.domain.request_action import RequestAction from models.domain.request_action import RequestAction
workspaces_core_router = APIRouter(dependencies=[Depends(get_current_tre_user_or_tre_admin)]) workspaces_core_router = APIRouter(dependencies=[Depends(get_current_tre_user_or_tre_admin)])
workspaces_shared_router = APIRouter(dependencies=[Depends(get_current_workspace_owner_or_researcher_user_or_tre_admin)]) workspaces_shared_router = APIRouter(dependencies=[Depends(get_current_workspace_owner_or_researcher_user_or_airlock_manager_or_tre_admin)])
workspace_services_workspace_router = APIRouter(dependencies=[Depends(get_current_workspace_owner_or_researcher_user)]) workspace_services_workspace_router = APIRouter(dependencies=[Depends(get_current_workspace_owner_or_researcher_user_or_airlock_manager)])
user_resources_workspace_router = APIRouter(dependencies=[Depends(get_current_workspace_owner_or_researcher_user)]) user_resources_workspace_router = APIRouter(dependencies=[Depends(get_current_workspace_owner_or_researcher_user_or_airlock_manager)])
def validate_user_is_workspace_owner_or_resource_owner(user, user_resource): def validate_user_has_valid_role_for_user_resource(user, user_resource):
if "WorkspaceOwner" in user.roles: if "WorkspaceOwner" in user.roles or "AirlockManager" in user.roles:
return return
if "WorkspaceResearcher" in user.roles and user_resource.ownerId == user.id: if "WorkspaceResearcher" in user.roles and user_resource.ownerId == user.id:
@ -170,14 +173,21 @@ async def invoke_action_on_workspace(response: Response, action: str, user=Depen
# workspace operations # workspace operations
# This method only returns templates that the authenticated user is authorized to use # This method only returns templates that the authenticated user is authorized to use
@workspaces_shared_router.get("/workspace/{workspace_id}/workspace-service-templates", response_model=ResourceTemplateInformationInList, name=strings.API_GET_WORKSPACE_SERVICE_TEMPLATES_IN_WORKSPACE) @workspaces_shared_router.get("/workspace/{workspace_id}/workspace-service-templates", response_model=ResourceTemplateInformationInList, name=strings.API_GET_WORKSPACE_SERVICE_TEMPLATES_IN_WORKSPACE)
async def get_workspace_service_templates(workspace=Depends(get_workspace_by_id_from_path), template_repo=Depends(get_repository(ResourceTemplateRepository)), user=Depends(get_current_workspace_owner_or_researcher_user_or_tre_admin)) -> ResourceTemplateInformationInList: async def get_workspace_service_templates(
workspace=Depends(get_workspace_by_id_from_path),
template_repo=Depends(get_repository(ResourceTemplateRepository)),
user=Depends(get_current_workspace_owner_or_researcher_user_or_airlock_manager_or_tre_admin)) -> ResourceTemplateInformationInList:
template_infos = template_repo.get_templates_information(ResourceType.WorkspaceService, user.roles) template_infos = template_repo.get_templates_information(ResourceType.WorkspaceService, user.roles)
return ResourceTemplateInformationInList(templates=template_infos) return ResourceTemplateInformationInList(templates=template_infos)
# This method only returns templates that the authenticated user is authorized to use # This method only returns templates that the authenticated user is authorized to use
@workspaces_shared_router.get("/workspace/{workspace_id}/workspace-service-templates/{service_template_name}/user-resource-templates", response_model=ResourceTemplateInformationInList, name=strings.API_GET_USER_RESOURCE_TEMPLATES_IN_WORKSPACE) @workspaces_shared_router.get("/workspace/{workspace_id}/workspace-service-templates/{service_template_name}/user-resource-templates", response_model=ResourceTemplateInformationInList, name=strings.API_GET_USER_RESOURCE_TEMPLATES_IN_WORKSPACE)
async def get_user_resource_templates(service_template_name: str, workspace=Depends(get_workspace_by_id_from_path), template_repo=Depends(get_repository(ResourceTemplateRepository)), user=Depends(get_current_workspace_owner_or_researcher_user_or_tre_admin)) -> ResourceTemplateInformationInList: async def get_user_resource_templates(
service_template_name: str,
workspace=Depends(get_workspace_by_id_from_path),
template_repo=Depends(get_repository(ResourceTemplateRepository)),
user=Depends(get_current_workspace_owner_or_researcher_user_or_airlock_manager_or_tre_admin)) -> ResourceTemplateInformationInList:
template_infos = template_repo.get_templates_information(ResourceType.UserResource, user.roles, service_template_name) template_infos = template_repo.get_templates_information(ResourceType.UserResource, user.roles, service_template_name)
return ResourceTemplateInformationInList(templates=template_infos) return ResourceTemplateInformationInList(templates=template_infos)
@ -193,13 +203,13 @@ async def retrieve_workspace_operation_by_workspace_id_and_operation_id(workspac
# WORKSPACE SERVICES ROUTES # WORKSPACE SERVICES ROUTES
@workspace_services_workspace_router.get("/workspaces/{workspace_id}/workspace-services", response_model=WorkspaceServicesInList, name=strings.API_GET_ALL_WORKSPACE_SERVICES, dependencies=[Depends(get_current_workspace_owner_or_researcher_user)]) @workspace_services_workspace_router.get("/workspaces/{workspace_id}/workspace-services", response_model=WorkspaceServicesInList, name=strings.API_GET_ALL_WORKSPACE_SERVICES, dependencies=[Depends(get_current_workspace_owner_or_researcher_user_or_airlock_manager)])
async def retrieve_users_active_workspace_services(workspace=Depends(get_workspace_by_id_from_path), workspace_services_repo=Depends(get_repository(WorkspaceServiceRepository))) -> WorkspaceServicesInList: async def retrieve_users_active_workspace_services(workspace=Depends(get_workspace_by_id_from_path), workspace_services_repo=Depends(get_repository(WorkspaceServiceRepository))) -> WorkspaceServicesInList:
workspace_services = workspace_services_repo.get_active_workspace_services_for_workspace(workspace.id) workspace_services = workspace_services_repo.get_active_workspace_services_for_workspace(workspace.id)
return WorkspaceServicesInList(workspaceServices=workspace_services) return WorkspaceServicesInList(workspaceServices=workspace_services)
@workspace_services_workspace_router.get("/workspaces/{workspace_id}/workspace-services/{service_id}", response_model=WorkspaceServiceInResponse, name=strings.API_GET_WORKSPACE_SERVICE_BY_ID, dependencies=[Depends(get_current_workspace_owner_or_researcher_user), Depends(get_workspace_by_id_from_path)]) @workspace_services_workspace_router.get("/workspaces/{workspace_id}/workspace-services/{service_id}", response_model=WorkspaceServiceInResponse, name=strings.API_GET_WORKSPACE_SERVICE_BY_ID, dependencies=[Depends(get_current_workspace_owner_or_researcher_user_or_airlock_manager), Depends(get_workspace_by_id_from_path)])
async def retrieve_workspace_service_by_id(workspace_service=Depends(get_workspace_service_by_id_from_path)) -> WorkspaceServiceInResponse: async def retrieve_workspace_service_by_id(workspace_service=Depends(get_workspace_service_by_id_from_path)) -> WorkspaceServiceInResponse:
return WorkspaceServiceInResponse(workspaceService=workspace_service) return WorkspaceServiceInResponse(workspaceService=workspace_service)
@ -290,19 +300,23 @@ async def invoke_action_on_workspace_service(response: Response, action: str, us
# workspace service operations # workspace service operations
@workspace_services_workspace_router.get("/workspaces/{workspace_id}/workspace-services/{service_id}/operations", response_model=OperationInList, name=strings.API_GET_RESOURCE_OPERATIONS, dependencies=[Depends(get_current_workspace_owner_or_researcher_user), Depends(get_workspace_by_id_from_path)]) @workspace_services_workspace_router.get("/workspaces/{workspace_id}/workspace-services/{service_id}/operations", response_model=OperationInList, name=strings.API_GET_RESOURCE_OPERATIONS, dependencies=[Depends(get_current_workspace_owner_or_researcher_user_or_airlock_manager), Depends(get_workspace_by_id_from_path)])
async def retrieve_workspace_service_operations_by_workspace_service_id(workspace_service=Depends(get_workspace_service_by_id_from_path), operations_repo=Depends(get_repository(OperationRepository))) -> OperationInList: async def retrieve_workspace_service_operations_by_workspace_service_id(workspace_service=Depends(get_workspace_service_by_id_from_path), operations_repo=Depends(get_repository(OperationRepository))) -> OperationInList:
return OperationInList(operations=operations_repo.get_operations_by_resource_id(resource_id=workspace_service.id)) return OperationInList(operations=operations_repo.get_operations_by_resource_id(resource_id=workspace_service.id))
@workspace_services_workspace_router.get("/workspaces/{workspace_id}/workspace-services/{service_id}/operations/{operation_id}", response_model=OperationInResponse, name=strings.API_GET_RESOURCE_OPERATION_BY_ID, dependencies=[Depends(get_current_workspace_owner_or_researcher_user), Depends(get_workspace_by_id_from_path)]) @workspace_services_workspace_router.get("/workspaces/{workspace_id}/workspace-services/{service_id}/operations/{operation_id}", response_model=OperationInResponse, name=strings.API_GET_RESOURCE_OPERATION_BY_ID, dependencies=[Depends(get_current_workspace_owner_or_researcher_user_or_airlock_manager), Depends(get_workspace_by_id_from_path)])
async def retrieve_workspace_service_operation_by_workspace_service_id_and_operation_id(workspace_service=Depends(get_workspace_service_by_id_from_path), operation=Depends(get_operation_by_id_from_path)) -> OperationInList: async def retrieve_workspace_service_operation_by_workspace_service_id_and_operation_id(workspace_service=Depends(get_workspace_service_by_id_from_path), operation=Depends(get_operation_by_id_from_path)) -> OperationInList:
return OperationInResponse(operation=operation) return OperationInResponse(operation=operation)
# USER RESOURCE ROUTES # USER RESOURCE ROUTES
@user_resources_workspace_router.get("/workspaces/{workspace_id}/workspace-services/{service_id}/user-resources", response_model=UserResourcesInList, name=strings.API_GET_MY_USER_RESOURCES, dependencies=[Depends(get_workspace_by_id_from_path)]) @user_resources_workspace_router.get("/workspaces/{workspace_id}/workspace-services/{service_id}/user-resources", response_model=UserResourcesInList, name=strings.API_GET_MY_USER_RESOURCES, dependencies=[Depends(get_workspace_by_id_from_path)])
async def retrieve_user_resources_for_workspace_service(workspace_id: str, service_id: str, user=Depends(get_current_workspace_owner_or_researcher_user), user_resource_repo=Depends(get_repository(UserResourceRepository))) -> UserResourcesInList: async def retrieve_user_resources_for_workspace_service(
workspace_id: str,
service_id: str,
user=Depends(get_current_workspace_owner_or_researcher_user_or_airlock_manager),
user_resource_repo=Depends(get_repository(UserResourceRepository))) -> UserResourcesInList:
user_resources = user_resource_repo.get_user_resources_for_workspace_service(workspace_id, service_id) user_resources = user_resource_repo.get_user_resources_for_workspace_service(workspace_id, service_id)
# filter only to the user - for researchers # filter only to the user - for researchers
@ -317,8 +331,10 @@ async def retrieve_user_resources_for_workspace_service(workspace_id: str, servi
@user_resources_workspace_router.get("/workspaces/{workspace_id}/workspace-services/{service_id}/user-resources/{resource_id}", response_model=UserResourceInResponse, name=strings.API_GET_USER_RESOURCE, dependencies=[Depends(get_workspace_by_id_from_path)]) @user_resources_workspace_router.get("/workspaces/{workspace_id}/workspace-services/{service_id}/user-resources/{resource_id}", response_model=UserResourceInResponse, name=strings.API_GET_USER_RESOURCE, dependencies=[Depends(get_workspace_by_id_from_path)])
async def retrieve_user_resource_by_id(user_resource=Depends(get_user_resource_by_id_from_path), user=Depends(get_current_workspace_owner_or_researcher_user)) -> UserResourceInResponse: async def retrieve_user_resource_by_id(
validate_user_is_workspace_owner_or_resource_owner(user, user_resource) user_resource=Depends(get_user_resource_by_id_from_path),
user=Depends(get_current_workspace_owner_or_researcher_user_or_airlock_manager)) -> UserResourceInResponse:
validate_user_has_valid_role_for_user_resource(user, user_resource)
if 'azure_resource_id' in user_resource.properties: if 'azure_resource_id' in user_resource.properties:
user_resource.azureStatus = get_azure_resource_status(user_resource.properties['azure_resource_id']) user_resource.azureStatus = get_azure_resource_status(user_resource.properties['azure_resource_id'])
@ -333,7 +349,7 @@ async def create_user_resource(
user_resource_repo=Depends(get_repository(UserResourceRepository)), user_resource_repo=Depends(get_repository(UserResourceRepository)),
resource_template_repo=Depends(get_repository(ResourceTemplateRepository)), resource_template_repo=Depends(get_repository(ResourceTemplateRepository)),
operations_repo=Depends(get_repository(OperationRepository)), operations_repo=Depends(get_repository(OperationRepository)),
user=Depends(get_current_workspace_owner_or_researcher_user), user=Depends(get_current_workspace_owner_or_researcher_user_or_airlock_manager),
workspace=Depends(get_deployed_workspace_by_id_from_path), workspace=Depends(get_deployed_workspace_by_id_from_path),
workspace_service=Depends(get_deployed_workspace_service_by_id_from_path)) -> OperationInResponse: workspace_service=Depends(get_deployed_workspace_service_by_id_from_path)) -> OperationInResponse:
@ -359,8 +375,15 @@ async def create_user_resource(
@user_resources_workspace_router.delete("/workspaces/{workspace_id}/workspace-services/{service_id}/user-resources/{resource_id}", response_model=OperationInResponse, name=strings.API_DELETE_USER_RESOURCE) @user_resources_workspace_router.delete("/workspaces/{workspace_id}/workspace-services/{service_id}/user-resources/{resource_id}", response_model=OperationInResponse, name=strings.API_DELETE_USER_RESOURCE)
async def delete_user_resource(response: Response, user=Depends(get_current_workspace_owner_or_researcher_user), user_resource=Depends(get_user_resource_by_id_from_path), workspace_service=Depends(get_workspace_service_by_id_from_path), user_resource_repo=Depends(get_repository(UserResourceRepository)), operations_repo=Depends(get_repository(OperationRepository)), resource_template_repo=Depends(get_repository(ResourceTemplateRepository))) -> OperationInResponse: async def delete_user_resource(
validate_user_is_workspace_owner_or_resource_owner(user, user_resource) response: Response,
user=Depends(get_current_workspace_owner_or_researcher_user_or_airlock_manager),
user_resource=Depends(get_user_resource_by_id_from_path),
workspace_service=Depends(get_workspace_service_by_id_from_path),
user_resource_repo=Depends(get_repository(UserResourceRepository)),
operations_repo=Depends(get_repository(OperationRepository)),
resource_template_repo=Depends(get_repository(ResourceTemplateRepository))) -> OperationInResponse:
validate_user_has_valid_role_for_user_resource(user, user_resource)
if user_resource.isEnabled: if user_resource.isEnabled:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=strings.USER_RESOURCE_NEEDS_TO_BE_DISABLED_BEFORE_DELETION) raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=strings.USER_RESOURCE_NEEDS_TO_BE_DISABLED_BEFORE_DELETION)
@ -382,8 +405,17 @@ async def delete_user_resource(response: Response, user=Depends(get_current_work
@user_resources_workspace_router.patch("/workspaces/{workspace_id}/workspace-services/{service_id}/user-resources/{resource_id}", status_code=status.HTTP_202_ACCEPTED, response_model=OperationInResponse, name=strings.API_UPDATE_USER_RESOURCE, dependencies=[Depends(get_workspace_by_id_from_path), Depends(get_workspace_service_by_id_from_path)]) @user_resources_workspace_router.patch("/workspaces/{workspace_id}/workspace-services/{service_id}/user-resources/{resource_id}", status_code=status.HTTP_202_ACCEPTED, response_model=OperationInResponse, name=strings.API_UPDATE_USER_RESOURCE, dependencies=[Depends(get_workspace_by_id_from_path), Depends(get_workspace_service_by_id_from_path)])
async def patch_user_resource(user_resource_patch: ResourcePatch, response: Response, user=Depends(get_current_workspace_owner_or_researcher_user), user_resource=Depends(get_user_resource_by_id_from_path), workspace_service=Depends(get_workspace_service_by_id_from_path), user_resource_repo=Depends(get_repository(UserResourceRepository)), resource_template_repo=Depends(get_repository(ResourceTemplateRepository)), operations_repo=Depends(get_repository(OperationRepository)), etag: str = Header(...)) -> OperationInResponse: async def patch_user_resource(
validate_user_is_workspace_owner_or_resource_owner(user, user_resource) user_resource_patch: ResourcePatch,
response: Response,
user=Depends(get_current_workspace_owner_or_researcher_user_or_airlock_manager),
user_resource=Depends(get_user_resource_by_id_from_path),
workspace_service=Depends(get_workspace_service_by_id_from_path),
user_resource_repo=Depends(get_repository(UserResourceRepository)),
resource_template_repo=Depends(get_repository(ResourceTemplateRepository)),
operations_repo=Depends(get_repository(OperationRepository)),
etag: str = Header(...)) -> OperationInResponse:
validate_user_has_valid_role_for_user_resource(user, user_resource)
try: try:
patched_user_resource, resource_template = user_resource_repo.patch_user_resource(user_resource, user_resource_patch, etag, resource_template_repo, workspace_service.templateName, user) patched_user_resource, resource_template = user_resource_repo.patch_user_resource(user_resource, user_resource_patch, etag, resource_template_repo, workspace_service.templateName, user)
@ -406,8 +438,16 @@ async def patch_user_resource(user_resource_patch: ResourcePatch, response: Resp
# user resource actions # user resource actions
@user_resources_workspace_router.post("/workspaces/{workspace_id}/workspace-services/{service_id}/user-resources/{resource_id}/invoke-action", status_code=status.HTTP_202_ACCEPTED, response_model=OperationInResponse, name=strings.API_INVOKE_ACTION_ON_USER_RESOURCE) @user_resources_workspace_router.post("/workspaces/{workspace_id}/workspace-services/{service_id}/user-resources/{resource_id}/invoke-action", status_code=status.HTTP_202_ACCEPTED, response_model=OperationInResponse, name=strings.API_INVOKE_ACTION_ON_USER_RESOURCE)
async def invoke_action_on_user_resource(response: Response, action: str, user_resource=Depends(get_user_resource_by_id_from_path), workspace_service=Depends(get_workspace_service_by_id_from_path), resource_template_repo=Depends(get_repository(ResourceTemplateRepository)), user_resource_repo=Depends(get_repository(UserResourceRepository)), operations_repo=Depends(get_repository(OperationRepository)), user=Depends(get_current_workspace_owner_or_researcher_user)) -> OperationInResponse: async def invoke_action_on_user_resource(
validate_user_is_workspace_owner_or_resource_owner(user, user_resource) response: Response,
action: str,
user_resource=Depends(get_user_resource_by_id_from_path),
workspace_service=Depends(get_workspace_service_by_id_from_path),
resource_template_repo=Depends(get_repository(ResourceTemplateRepository)),
user_resource_repo=Depends(get_repository(UserResourceRepository)),
operations_repo=Depends(get_repository(OperationRepository)),
user=Depends(get_current_workspace_owner_or_researcher_user_or_airlock_manager)) -> OperationInResponse:
validate_user_has_valid_role_for_user_resource(user, user_resource)
operation = await send_custom_action_message( operation = await send_custom_action_message(
resource=user_resource, resource=user_resource,
resource_repo=user_resource_repo, resource_repo=user_resource_repo,
@ -425,12 +465,18 @@ async def invoke_action_on_user_resource(response: Response, action: str, user_r
# user resource operations # user resource operations
@user_resources_workspace_router.get("/workspaces/{workspace_id}/workspace-services/{service_id}/user-resources/{resource_id}/operations", response_model=OperationInList, name=strings.API_GET_RESOURCE_OPERATIONS, dependencies=[Depends(get_workspace_by_id_from_path)]) @user_resources_workspace_router.get("/workspaces/{workspace_id}/workspace-services/{service_id}/user-resources/{resource_id}/operations", response_model=OperationInList, name=strings.API_GET_RESOURCE_OPERATIONS, dependencies=[Depends(get_workspace_by_id_from_path)])
async def retrieve_user_resource_operations_by_user_resource_id(user_resource=Depends(get_user_resource_by_id_from_path), user=Depends(get_current_workspace_owner_or_researcher_user), operations_repo=Depends(get_repository(OperationRepository))) -> OperationInList: async def retrieve_user_resource_operations_by_user_resource_id(
validate_user_is_workspace_owner_or_resource_owner(user, user_resource) user_resource=Depends(get_user_resource_by_id_from_path),
user=Depends(get_current_workspace_owner_or_researcher_user_or_airlock_manager),
operations_repo=Depends(get_repository(OperationRepository))) -> OperationInList:
validate_user_has_valid_role_for_user_resource(user, user_resource)
return OperationInList(operations=operations_repo.get_operations_by_resource_id(resource_id=user_resource.id)) return OperationInList(operations=operations_repo.get_operations_by_resource_id(resource_id=user_resource.id))
@user_resources_workspace_router.get("/workspaces/{workspace_id}/workspace-services/{service_id}/user-resources/{resource_id}/operations/{operation_id}", response_model=OperationInResponse, name=strings.API_GET_RESOURCE_OPERATION_BY_ID, dependencies=[Depends(get_workspace_by_id_from_path)]) @user_resources_workspace_router.get("/workspaces/{workspace_id}/workspace-services/{service_id}/user-resources/{resource_id}/operations/{operation_id}", response_model=OperationInResponse, name=strings.API_GET_RESOURCE_OPERATION_BY_ID, dependencies=[Depends(get_workspace_by_id_from_path)])
async def retrieve_user_resource_operations_by_user_resource_id_and_operation_id(user_resource=Depends(get_user_resource_by_id_from_path), user=Depends(get_current_workspace_owner_or_researcher_user), operation=Depends(get_operation_by_id_from_path)) -> OperationInList: async def retrieve_user_resource_operations_by_user_resource_id_and_operation_id(
validate_user_is_workspace_owner_or_resource_owner(user, user_resource) user_resource=Depends(get_user_resource_by_id_from_path),
user=Depends(get_current_workspace_owner_or_researcher_user_or_airlock_manager),
operation=Depends(get_operation_by_id_from_path)) -> OperationInList:
validate_user_has_valid_role_for_user_resource(user, user_resource)
return OperationInResponse(operation=operation) return OperationInResponse(operation=operation)

Просмотреть файл

@ -48,4 +48,7 @@ get_current_workspace_owner_or_researcher_user_or_airlock_manager = AzureADAutho
get_current_workspace_owner_or_researcher_user_or_tre_admin = AzureADAuthorization(require_one_of_roles=["TREAdmin", "WorkspaceOwner", "WorkspaceResearcher"]) get_current_workspace_owner_or_researcher_user_or_tre_admin = AzureADAuthorization(require_one_of_roles=["TREAdmin", "WorkspaceOwner", "WorkspaceResearcher"])
get_current_workspace_owner_or_researcher_user_or_airlock_manager_or_tre_admin = AzureADAuthorization(require_one_of_roles=["TREAdmin", "WorkspaceOwner", "WorkspaceResearcher", "AirlockManager"])
get_current_workspace_owner_or_tre_admin = AzureADAuthorization(require_one_of_roles=["TREAdmin", "WorkspaceOwner"]) get_current_workspace_owner_or_tre_admin = AzureADAuthorization(require_one_of_roles=["TREAdmin", "WorkspaceOwner"])

Просмотреть файл

@ -20,7 +20,11 @@ from models.domain.workspace import Workspace, WorkspaceRole
from models.domain.workspace_service import WorkspaceService from models.domain.workspace_service import WorkspaceService
from models.schemas.resource_template import ResourceTemplateInformation from models.schemas.resource_template import ResourceTemplateInformation
from resources import strings from resources import strings
from services.authentication import get_current_admin_user, get_current_tre_user_or_tre_admin, get_current_workspace_owner_user, get_current_workspace_owner_or_researcher_user, get_current_workspace_owner_or_researcher_user_or_tre_admin from services.authentication import get_current_admin_user, \
get_current_tre_user_or_tre_admin, get_current_workspace_owner_user, \
get_current_workspace_owner_or_researcher_user, \
get_current_workspace_owner_or_researcher_user_or_airlock_manager, \
get_current_workspace_owner_or_researcher_user_or_airlock_manager_or_tre_admin
from azure.cosmos.exceptions import CosmosAccessConditionFailedError from azure.cosmos.exceptions import CosmosAccessConditionFailedError
@ -215,7 +219,8 @@ class TestWorkspaceRoutesThatDontRequireAdminRights:
def log_in_with_non_admin_user(self, app, non_admin_user): def log_in_with_non_admin_user(self, app, non_admin_user):
with patch('services.aad_authentication.AzureADAuthorization._get_user_from_token', return_value=non_admin_user()): with patch('services.aad_authentication.AzureADAuthorization._get_user_from_token', return_value=non_admin_user()):
app.dependency_overrides[get_current_tre_user_or_tre_admin] = non_admin_user app.dependency_overrides[get_current_tre_user_or_tre_admin] = non_admin_user
app.dependency_overrides[get_current_workspace_owner_or_researcher_user_or_tre_admin] = non_admin_user app.dependency_overrides[get_current_workspace_owner_or_researcher_user_or_airlock_manager] = non_admin_user
app.dependency_overrides[get_current_workspace_owner_or_researcher_user_or_airlock_manager_or_tre_admin] = non_admin_user
yield yield
app.dependency_overrides = {} app.dependency_overrides = {}
@ -298,6 +303,7 @@ class TestWorkspaceRoutesThatRequireAdminRights:
def _prepare(self, app, admin_user): def _prepare(self, app, admin_user):
with patch('services.aad_authentication.AzureADAuthorization._get_user_from_token', return_value=admin_user()): with patch('services.aad_authentication.AzureADAuthorization._get_user_from_token', return_value=admin_user()):
app.dependency_overrides[get_current_tre_user_or_tre_admin] = admin_user app.dependency_overrides[get_current_tre_user_or_tre_admin] = admin_user
app.dependency_overrides[get_current_workspace_owner_or_researcher_user_or_airlock_manager] = admin_user
app.dependency_overrides[get_current_admin_user] = admin_user app.dependency_overrides[get_current_admin_user] = admin_user
yield yield
app.dependency_overrides = {} app.dependency_overrides = {}
@ -503,6 +509,7 @@ class TestWorkspaceServiceRoutesThatRequireOwnerRights:
def log_in_with_owner_user(self, app, owner_user): def log_in_with_owner_user(self, app, owner_user):
# The following ws services requires the WS app registration # The following ws services requires the WS app registration
app.dependency_overrides[get_current_workspace_owner_user] = owner_user app.dependency_overrides[get_current_workspace_owner_user] = owner_user
app.dependency_overrides[get_current_workspace_owner_or_researcher_user_or_airlock_manager] = owner_user
app.dependency_overrides[get_current_workspace_owner_or_researcher_user] = owner_user app.dependency_overrides[get_current_workspace_owner_or_researcher_user] = owner_user
yield yield
app.dependency_overrides = {} app.dependency_overrides = {}
@ -661,7 +668,7 @@ class TestWorkspaceServiceRoutesThatRequireOwnerRights:
# [PATCH] /workspaces/{workspace_id}/workspace-services/{service_id}/user-resources/{resource_id} # [PATCH] /workspaces/{workspace_id}/workspace-services/{service_id}/user-resources/{resource_id}
@ patch("api.routes.workspaces.send_resource_request_message", return_value=sample_resource_operation(resource_id=USER_RESOURCE_ID, operation_id=OPERATION_ID)) @ patch("api.routes.workspaces.send_resource_request_message", return_value=sample_resource_operation(resource_id=USER_RESOURCE_ID, operation_id=OPERATION_ID))
@ patch("api.routes.workspaces.ResourceTemplateRepository.get_template_by_name_and_version", return_value=None) @ patch("api.routes.workspaces.ResourceTemplateRepository.get_template_by_name_and_version", return_value=None)
@ patch("api.routes.workspaces.validate_user_is_workspace_owner_or_resource_owner") @ patch("api.routes.workspaces.validate_user_has_valid_role_for_user_resource")
@ patch("api.dependencies.workspaces.WorkspaceServiceRepository.get_workspace_service_by_id", return_value=sample_workspace_service()) @ patch("api.dependencies.workspaces.WorkspaceServiceRepository.get_workspace_service_by_id", return_value=sample_workspace_service())
@ patch("api.dependencies.workspaces.WorkspaceRepository.get_workspace_by_id", return_value=sample_workspace()) @ patch("api.dependencies.workspaces.WorkspaceRepository.get_workspace_by_id", return_value=sample_workspace())
@ patch("api.dependencies.workspaces.UserResourceRepository.get_user_resource_by_id", return_value=sample_user_resource_object()) @ patch("api.dependencies.workspaces.UserResourceRepository.get_user_resource_by_id", return_value=sample_user_resource_object())
@ -726,7 +733,7 @@ class TestWorkspaceServiceRoutesThatRequireOwnerOrResearcherRights:
@pytest.fixture(autouse=True, scope='class') @pytest.fixture(autouse=True, scope='class')
def log_in_with_researcher_user(self, app, researcher_user): def log_in_with_researcher_user(self, app, researcher_user):
# The following ws services requires the WS app registration # The following ws services requires the WS app registration
app.dependency_overrides[get_current_workspace_owner_or_researcher_user_or_tre_admin] = researcher_user app.dependency_overrides[get_current_workspace_owner_or_researcher_user_or_airlock_manager] = researcher_user
app.dependency_overrides[get_current_workspace_owner_or_researcher_user] = researcher_user app.dependency_overrides[get_current_workspace_owner_or_researcher_user] = researcher_user
yield yield
app.dependency_overrides = {} app.dependency_overrides = {}
@ -951,7 +958,7 @@ class TestWorkspaceServiceRoutesThatRequireOwnerOrResearcherRights:
assert response.text == strings.WORKSPACE_SERVICE_IS_NOT_DEPLOYED assert response.text == strings.WORKSPACE_SERVICE_IS_NOT_DEPLOYED
# [PATCH] /workspaces/{workspace_id}/workspace-services/{service_id}/user-resources/{resource_id} # [PATCH] /workspaces/{workspace_id}/workspace-services/{service_id}/user-resources/{resource_id}
@ patch("api.routes.workspaces.validate_user_is_workspace_owner_or_resource_owner") @ patch("api.routes.workspaces.validate_user_has_valid_role_for_user_resource")
@ patch("api.dependencies.workspaces.UserResourceRepository.get_user_resource_by_id", return_value=sample_user_resource_object()) @ patch("api.dependencies.workspaces.UserResourceRepository.get_user_resource_by_id", return_value=sample_user_resource_object())
@ patch("api.dependencies.workspaces.WorkspaceServiceRepository.get_workspace_service_by_id", return_value=sample_workspace_service()) @ patch("api.dependencies.workspaces.WorkspaceServiceRepository.get_workspace_service_by_id", return_value=sample_workspace_service())
@ patch("api.dependencies.workspaces.WorkspaceRepository.get_workspace_by_id", return_value=sample_workspace()) @ patch("api.dependencies.workspaces.WorkspaceRepository.get_workspace_by_id", return_value=sample_workspace())
@ -998,7 +1005,7 @@ class TestWorkspaceServiceRoutesThatRequireOwnerOrResearcherRights:
# [PATCH] /workspaces/{workspace_id}/workspace-services/{service_id}/user-resources/{resource_id} # [PATCH] /workspaces/{workspace_id}/workspace-services/{service_id}/user-resources/{resource_id}
@ patch("api.routes.workspaces.send_resource_request_message", return_value=sample_resource_operation(resource_id=USER_RESOURCE_ID, operation_id=OPERATION_ID)) @ patch("api.routes.workspaces.send_resource_request_message", return_value=sample_resource_operation(resource_id=USER_RESOURCE_ID, operation_id=OPERATION_ID))
@ patch("api.routes.workspaces.ResourceTemplateRepository.get_template_by_name_and_version", return_value=None) @ patch("api.routes.workspaces.ResourceTemplateRepository.get_template_by_name_and_version", return_value=None)
@ patch("api.routes.workspaces.validate_user_is_workspace_owner_or_resource_owner") @ patch("api.routes.workspaces.validate_user_has_valid_role_for_user_resource")
@ patch("api.dependencies.workspaces.WorkspaceServiceRepository.get_workspace_service_by_id", return_value=sample_workspace_service()) @ patch("api.dependencies.workspaces.WorkspaceServiceRepository.get_workspace_service_by_id", return_value=sample_workspace_service())
@ patch("api.dependencies.workspaces.WorkspaceRepository.get_workspace_by_id", return_value=sample_workspace()) @ patch("api.dependencies.workspaces.WorkspaceRepository.get_workspace_by_id", return_value=sample_workspace())
@ patch("api.dependencies.workspaces.UserResourceRepository.get_user_resource_by_id", return_value=sample_user_resource_object()) @ patch("api.dependencies.workspaces.UserResourceRepository.get_user_resource_by_id", return_value=sample_user_resource_object())
@ -1026,7 +1033,7 @@ class TestWorkspaceServiceRoutesThatRequireOwnerOrResearcherRights:
@patch("api.dependencies.workspaces.WorkspaceServiceRepository.get_workspace_service_by_id") @patch("api.dependencies.workspaces.WorkspaceServiceRepository.get_workspace_service_by_id")
@patch("api.dependencies.workspaces.WorkspaceRepository.get_workspace_by_id") @patch("api.dependencies.workspaces.WorkspaceRepository.get_workspace_by_id")
@patch("api.dependencies.workspaces.UserResourceRepository.get_user_resource_by_id") @patch("api.dependencies.workspaces.UserResourceRepository.get_user_resource_by_id")
@patch("api.routes.workspaces.validate_user_is_workspace_owner_or_resource_owner") @patch("api.routes.workspaces.validate_user_has_valid_role_for_user_resource")
async def test_delete_user_resource_raises_400_if_user_resource_is_enabled(self, _, get_user_resource_mock, ___, ____, resource_template_repo, app, client, basic_user_resource_template): async def test_delete_user_resource_raises_400_if_user_resource_is_enabled(self, _, get_user_resource_mock, ___, ____, resource_template_repo, app, client, basic_user_resource_template):
user_resource = sample_user_resource_object() user_resource = sample_user_resource_object()
get_user_resource_mock.return_value = user_resource get_user_resource_mock.return_value = user_resource
@ -1041,7 +1048,7 @@ class TestWorkspaceServiceRoutesThatRequireOwnerOrResearcherRights:
@patch("api.dependencies.workspaces.WorkspaceServiceRepository.get_workspace_service_by_id") @patch("api.dependencies.workspaces.WorkspaceServiceRepository.get_workspace_service_by_id")
@patch("api.dependencies.workspaces.WorkspaceRepository.get_workspace_by_id") @patch("api.dependencies.workspaces.WorkspaceRepository.get_workspace_by_id")
@patch("api.dependencies.workspaces.UserResourceRepository.get_user_resource_by_id", return_value=disabled_user_resource()) @patch("api.dependencies.workspaces.UserResourceRepository.get_user_resource_by_id", return_value=disabled_user_resource())
@patch("api.routes.workspaces.validate_user_is_workspace_owner_or_resource_owner") @patch("api.routes.workspaces.validate_user_has_valid_role_for_user_resource")
@patch("api.routes.workspaces.send_uninstall_message", return_value=sample_resource_operation(resource_id=USER_RESOURCE_ID, operation_id=OPERATION_ID)) @patch("api.routes.workspaces.send_uninstall_message", return_value=sample_resource_operation(resource_id=USER_RESOURCE_ID, operation_id=OPERATION_ID))
async def test_delete_user_resource_sends_uninstall_message(self, send_uninstall_mock, __, ___, ____, _____, resource_template_repo, app, client, basic_user_resource_template): async def test_delete_user_resource_sends_uninstall_message(self, send_uninstall_mock, __, ___, ____, _____, resource_template_repo, app, client, basic_user_resource_template):
resource_template_repo.return_value = basic_user_resource_template resource_template_repo.return_value = basic_user_resource_template
@ -1052,7 +1059,7 @@ class TestWorkspaceServiceRoutesThatRequireOwnerOrResearcherRights:
@patch("api.dependencies.workspaces.WorkspaceServiceRepository.get_workspace_service_by_id") @patch("api.dependencies.workspaces.WorkspaceServiceRepository.get_workspace_service_by_id")
@patch("api.dependencies.workspaces.WorkspaceRepository.get_workspace_by_id") @patch("api.dependencies.workspaces.WorkspaceRepository.get_workspace_by_id")
@patch("api.dependencies.workspaces.UserResourceRepository.get_user_resource_by_id") @patch("api.dependencies.workspaces.UserResourceRepository.get_user_resource_by_id")
@patch("api.routes.workspaces.validate_user_is_workspace_owner_or_resource_owner") @patch("api.routes.workspaces.validate_user_has_valid_role_for_user_resource")
@patch("api.routes.workspaces.send_uninstall_message", return_value=sample_resource_operation(resource_id=USER_RESOURCE_ID, operation_id=OPERATION_ID)) @patch("api.routes.workspaces.send_uninstall_message", return_value=sample_resource_operation(resource_id=USER_RESOURCE_ID, operation_id=OPERATION_ID))
async def test_delete_user_resource_returns_resource_id(self, __, ___, get_user_resource_mock, ____, _____, resource_template_repo, app, client, basic_user_resource_template): async def test_delete_user_resource_returns_resource_id(self, __, ___, get_user_resource_mock, ____, _____, resource_template_repo, app, client, basic_user_resource_template):
user_resource = disabled_user_resource() user_resource = disabled_user_resource()

Просмотреть файл

@ -1 +1 @@
__version__ = "0.3.7" __version__ = "0.3.8"

Просмотреть файл

@ -67,9 +67,11 @@ public class AuthenticationProviderService {
List<String> rolesList = roles.asList(String.class); List<String> rolesList = roles.asList(String.class);
if (rolesList.stream().noneMatch(x -> x.equalsIgnoreCase("WorkspaceOwner") if (rolesList.stream().noneMatch(x -> x.equalsIgnoreCase("WorkspaceOwner")
|| x.equalsIgnoreCase("WorkspaceResearcher"))) { || x.equalsIgnoreCase("WorkspaceResearcher")
|| x.equalsIgnoreCase("AirlockManager"))) {
throw new GuacamoleInvalidCredentialsException( throw new GuacamoleInvalidCredentialsException(
"User must have a workspace owner or workspace researcher role", CredentialsInfo.USERNAME_PASSWORD); "User must have a workspace owner or workspace researcher or Airlock Manager role",
CredentialsInfo.USERNAME_PASSWORD);
} }
} catch (final Exception ex) { } catch (final Exception ex) {
LOGGER.error("Could not validate token", ex); LOGGER.error("Could not validate token", ex);

Просмотреть файл

@ -1,6 +1,6 @@
--- ---
name: tre-service-guacamole name: tre-service-guacamole
version: 0.4.5 version: 0.4.6
description: "An Azure TRE service for Guacamole" description: "An Azure TRE service for Guacamole"
dockerfile: Dockerfile.tmpl dockerfile: Dockerfile.tmpl
registry: azuretre registry: azuretre

Просмотреть файл

@ -6,6 +6,9 @@
"description": "Linux virtual machine.", "description": "Linux virtual machine.",
"required": [ "required": [
], ],
"authorizedRoles": [
"WorkspaceOwner", "WorkspaceResearcher"
],
"properties": { "properties": {
"os_image": { "os_image": {
"$id": "#/properties/os_image", "$id": "#/properties/os_image",

Просмотреть файл

@ -6,6 +6,9 @@
"description": "Windows virtual machine.", "description": "Windows virtual machine.",
"required": [ "required": [
], ],
"authorizedRoles": [
"WorkspaceOwner", "WorkspaceResearcher"
],
"properties": { "properties": {
"os_image": { "os_image": {
"$id": "#/properties/os_image", "$id": "#/properties/os_image",

Просмотреть файл

@ -74,7 +74,7 @@ export const ResourceContextMenu: React.FunctionComponent<ResourceContextMenuPro
wsAuth = true; wsAuth = true;
break; break;
case ResourceType.UserResource: case ResourceType.UserResource:
r = [WorkspaceRoleName.WorkspaceOwner, WorkspaceRoleName.WorkspaceResearcher]; r = [WorkspaceRoleName.WorkspaceOwner, WorkspaceRoleName.WorkspaceResearcher, WorkspaceRoleName.AirlockManager];
wsAuth = true; wsAuth = true;
break; break;
case ResourceType.Workspace: case ResourceType.Workspace:

Просмотреть файл

@ -112,7 +112,7 @@ export const WorkspaceServiceItem: React.FunctionComponent<WorkspaceServiceItemP
<Stack.Item> <Stack.Item>
<Stack horizontal horizontalAlign="space-between"> <Stack horizontal horizontalAlign="space-between">
<h1>Resources</h1> <h1>Resources</h1>
<SecuredByRole allowedRoles={[WorkspaceRoleName.WorkspaceOwner, WorkspaceRoleName.WorkspaceResearcher]} workspaceAuth={true} element={ <SecuredByRole allowedRoles={[WorkspaceRoleName.WorkspaceOwner, WorkspaceRoleName.WorkspaceResearcher, WorkspaceRoleName.AirlockManager]} workspaceAuth={true} element={
<PrimaryButton iconProps={{ iconName: 'Add' }} text="Create new" <PrimaryButton iconProps={{ iconName: 'Add' }} text="Create new"
disabled={!workspaceService.isEnabled || latestUpdate.componentAction === ComponentAction.Lock || successStates.indexOf(workspaceService.deploymentStatus) === -1} disabled={!workspaceService.isEnabled || latestUpdate.componentAction === ComponentAction.Lock || successStates.indexOf(workspaceService.deploymentStatus) === -1}
title={(!workspaceService.isEnabled || latestUpdate.componentAction === ComponentAction.Lock || successStates.indexOf(workspaceService.deploymentStatus) === -1) ? 'Service must be enabled, successfully deployed, and not locked' : 'Create a User Resource'} title={(!workspaceService.isEnabled || latestUpdate.componentAction === ComponentAction.Lock || successStates.indexOf(workspaceService.deploymentStatus) === -1) ? 'Service must be enabled, successfully deployed, and not locked' : 'Create a User Resource'}

Просмотреть файл

@ -5,5 +5,6 @@ export enum RoleName {
export enum WorkspaceRoleName { export enum WorkspaceRoleName {
WorkspaceOwner = "WorkspaceOwner", WorkspaceOwner = "WorkspaceOwner",
WorkspaceResearcher = "WorkspaceResearcher" WorkspaceResearcher = "WorkspaceResearcher",
} AirlockManager = "AirlockManager"
}