Skip to content

Commit fee832a

Browse files
author
Matthew Fortunka
committed
Moved to use ID instead of email
1 parent 081055c commit fee832a

File tree

6 files changed

+187
-165
lines changed

6 files changed

+187
-165
lines changed

api_app/api/routes/workspace_users.py

Lines changed: 17 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,67 +1,60 @@
11
from fastapi import APIRouter, Depends, Response, status
22
from api.dependencies.workspaces import get_workspace_by_id_from_path
3+
from models.domain.authentication import AssignmentType
34
from resources import strings
45
from services.authentication import get_access_service
56
from models.schemas.users import UsersInResponse, AssignableUsersInResponse
67
from models.schemas.roles import RolesInResponse
78
from services.authentication import get_current_admin_user, get_current_workspace_owner_or_researcher_user_or_airlock_manager_or_tre_admin
9+
from services.logging import logger
810

911
workspaces_users_admin_router = APIRouter(dependencies=[Depends(get_current_admin_user)])
1012
workspaces_users_shared_router = APIRouter(dependencies=[Depends(get_current_workspace_owner_or_researcher_user_or_airlock_manager_or_tre_admin)])
1113

1214

1315
@workspaces_users_shared_router.get("/workspaces/{workspace_id}/users", response_model=UsersInResponse, name=strings.API_GET_WORKSPACE_USERS)
14-
async def get_workspace_users(workspace=Depends(get_workspace_by_id_from_path)) -> UsersInResponse:
15-
access_service = get_access_service()
16+
async def get_workspace_users(workspace=Depends(get_workspace_by_id_from_path), access_service=Depends(get_access_service)) -> UsersInResponse:
1617
users = access_service.get_workspace_users(workspace)
1718
return UsersInResponse(users=users)
1819

1920

2021
@workspaces_users_admin_router.get("/workspaces/{workspace_id}/assignable-users", response_model=AssignableUsersInResponse, name=strings.API_GET_ASSIGNABLE_USERS)
21-
async def get_assignable_users(filter: str = "", maxResultCount: int = 5, workspace=Depends(get_workspace_by_id_from_path)) -> AssignableUsersInResponse:
22-
access_service = get_access_service()
22+
async def get_assignable_users(filter: str = "", maxResultCount: int = 5, access_service=Depends(get_access_service)) -> AssignableUsersInResponse:
2323
assignable_users = access_service.get_assignable_users(filter, maxResultCount)
2424
return AssignableUsersInResponse(assignable_users=assignable_users)
2525

2626

2727
@workspaces_users_admin_router.get("/workspaces/{workspace_id}/roles", response_model=RolesInResponse, name=strings.API_GET_WORKSPACE_ROLES)
28-
async def get_workspace_roles(workspace=Depends(get_workspace_by_id_from_path)) -> RolesInResponse:
29-
access_service = get_access_service()
28+
async def get_workspace_roles(workspace=Depends(get_workspace_by_id_from_path), access_service=Depends(get_access_service)) -> RolesInResponse:
3029
roles = access_service.get_workspace_roles(workspace)
3130
return RolesInResponse(roles=roles)
3231

3332

3433
@workspaces_users_admin_router.post("/workspaces/{workspace_id}/users/assign", status_code=status.HTTP_202_ACCEPTED, name=strings.API_ASSIGN_WORKSPACE_USER)
35-
async def assign_workspace_user(response: Response, user_email: str, role_name: str, workspace=Depends(get_workspace_by_id_from_path)) -> UsersInResponse:
36-
access_service = get_access_service()
37-
38-
user = access_service.get_user_by_email(user_email)
39-
role = access_service.get_workspace_role_by_name(role_name, workspace)
34+
async def assign_workspace_user(response: Response, user_id: str, role_id: str, workspace=Depends(get_workspace_by_id_from_path), access_service=Depends(get_access_service)) -> UsersInResponse:
4035

4136
access_service.assign_workspace_user(
42-
user,
37+
user_id,
4338
workspace,
44-
role
39+
role_id
4540
)
4641

4742
users = access_service.get_workspace_users(workspace)
4843
return UsersInResponse(users=users)
4944

5045

5146
@workspaces_users_admin_router.delete("/workspaces/{workspace_id}/users/assign", status_code=status.HTTP_202_ACCEPTED, name=strings.API_REMOVE_WORKSPACE_USER_ASSIGNMENT)
52-
async def remove_workspace_user_assignment(user_email: str,
53-
role_name: str,
54-
workspace=Depends(get_workspace_by_id_from_path)) -> UsersInResponse:
55-
56-
access_service = get_access_service()
57-
58-
user = access_service.get_user_by_email(user_email)
59-
role = access_service.get_workspace_role_by_name(role_name, workspace)
47+
async def remove_workspace_user_assignment(user_id: str,
48+
role_id: str,
49+
assignmentType: AssignmentType = AssignmentType.APP_ROLE,
50+
workspace=Depends(get_workspace_by_id_from_path),
51+
access_service=Depends(get_access_service)) -> UsersInResponse:
6052

6153
access_service.remove_workspace_role_user_assignment(
62-
user,
63-
role,
64-
workspace
54+
user_id,
55+
role_id,
56+
workspace,
57+
assignmentType
6558
)
6659

6760
users = access_service.get_workspace_users(workspace)
Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,17 @@
11
from collections import namedtuple
22
from typing import List
3-
43
from pydantic import BaseModel, Field
5-
4+
from enum import Enum
65

76
RoleAssignment = namedtuple("RoleAssignment", "resource_id, role_id")
87

9-
108
class User(BaseModel):
119
id: str
1210
name: str
1311
email: str = Field(None)
1412
roles: List[str] = Field([])
1513
roleAssignments: List[RoleAssignment] = Field([])
1614

17-
18-
class AssignableUser(BaseModel):
19-
name: str
20-
email: str
21-
22-
2315
class Role(BaseModel):
2416
id: str
2517
value: str
@@ -30,3 +22,32 @@ class Role(BaseModel):
3022
displayName: str
3123
origin: str
3224
roleAssignments: List[RoleAssignment] = Field([])
25+
26+
class AssignableUser(BaseModel):
27+
id: str
28+
displayName: str
29+
userPrincipalName: str
30+
31+
class AssignmentType(Enum):
32+
APP_ROLE = "ApplicationRole"
33+
GROUP = "Group"
34+
35+
class AssignedRole(BaseModel):
36+
id: str
37+
displayName: str
38+
type: AssignmentType
39+
40+
def __eq__(self, other):
41+
return self.id == other.id
42+
43+
def __hash__(self):
44+
return hash(self.id)
45+
46+
class AssignedUser(BaseModel):
47+
id: str
48+
displayName: str
49+
userPrincipalName: str
50+
roles: List[AssignedRole] = Field([])
51+
52+
53+

api_app/models/schemas/users.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
from pydantic import BaseModel, Field
22
from typing import List
33

4-
from models.domain.authentication import User, AssignableUser
4+
from models.domain.authentication import AssignedUser, AssignableUser
55

66

77
class UsersInResponse(BaseModel):
8-
users: List[User] = Field(..., title="Users", description="List of users assigned to the workspace")
8+
users: List[AssignedUser] = Field(..., title="Users", description="List of users assigned to the workspace")
99

1010
class Config:
1111
schema_extra = {

api_app/services/aad_authentication.py

Lines changed: 49 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from services.access_service import AccessService, AuthConfigValidationError, UserRoleAssignmentError
1313
from core import config
1414
from db.errors import EntityDoesNotExist
15-
from models.domain.authentication import User, AssignableUser, Role, RoleAssignment
15+
from models.domain.authentication import AssignedRole, AssignedUser, AssignmentType, User, AssignableUser, Role, RoleAssignment
1616
from models.domain.workspace import Workspace, WorkspaceRole
1717
from resources import strings
1818
from db.repositories.workspaces import WorkspaceRepository
@@ -21,6 +21,7 @@
2121
from cryptography.hazmat.primitives.asymmetric import rsa
2222
from cryptography.hazmat.backends import default_backend
2323
from cryptography.hazmat.primitives import serialization
24+
import json
2425

2526

2627
MICROSOFT_GRAPH_URL = config.MICROSOFT_GRAPH_URL.strip("/")
@@ -263,14 +264,14 @@ def _get_user_details(self, roles_graph_data, msgraph_token):
263264

264265
return users_graph_data
265266

266-
def _get_roles_for_principal(self, user_id, roles_graph_data, app_id_to_role_name):
267+
def _get_roles_for_principal(self, user_id, roles_graph_data, app_id_to_role_name, assignmentType: AssignmentType = AssignmentType.APP_ROLE) -> List[AssignedRole]:
267268
roles = []
268269
for role_assignment in roles_graph_data["value"]:
269270
if role_assignment["principalId"] == user_id:
270-
roles.append(app_id_to_role_name[role_assignment["appRoleId"]])
271+
roles.append(AssignedRole(id=role_assignment["appRoleId"], displayName=app_id_to_role_name[role_assignment["appRoleId"]], type=assignmentType))
271272
return roles
272273

273-
def _get_users_inc_groups_from_response(self, users_graph_data, roles_graph_data, app_id_to_role_name) -> List[User]:
274+
def _get_users_inc_groups_from_response(self, users_graph_data, roles_graph_data, app_id_to_role_name) -> List[AssignedUser]:
274275
users = []
275276
for user_data in users_graph_data["responses"]:
276277
if "users" in user_data["body"]["@odata.context"]:
@@ -284,7 +285,7 @@ def _get_users_inc_groups_from_response(self, users_graph_data, roles_graph_data
284285
user_roles = self._get_roles_for_principal(user_id, roles_graph_data, app_id_to_role_name)
285286

286287
if not any(user.id == user_id for user in users):
287-
users.append(User(id=user_id, name=user_name, email=user_email, roles=user_roles))
288+
users.append(AssignedUser(id=user_id, displayName=user_name, userPrincipalName=user_email, roles=user_roles))
288289
else:
289290
user = next((user for user in users if user.id == user_id), None)
290291
user.roles = list(set(user.roles + user_roles))
@@ -297,20 +298,21 @@ def _get_users_inc_groups_from_response(self, users_graph_data, roles_graph_data
297298
user_name = group_member["displayName"]
298299
user_email = group_member["userPrincipalName"]
299300

300-
group_roles = self._get_roles_for_principal(group_id, roles_graph_data, app_id_to_role_name)
301+
group_roles = self._get_roles_for_principal(group_id, roles_graph_data, app_id_to_role_name, AssignmentType.GROUP)
301302

302303
if not any(user.id == user_id for user in users):
303-
users.append(User(id=user_id, name=user_name, email=user_email, roles=group_roles))
304+
users.append(AssignedUser(id=user_id, displayName=user_name, userPrincipalName=user_email, roles=group_roles))
304305
else:
305306
user = next((user for user in users if user.id == user_id), None)
306307
user.roles = list(set(user.roles + group_roles))
307308

308309
return users
309310

310-
def get_workspace_users(self, workspace: Workspace) -> List[User]:
311+
def get_workspace_users(self, workspace: Workspace) -> List[AssignedUser]:
311312
msgraph_token = self._get_msgraph_token()
313+
312314
sp_graph_data = self._get_app_sp_graph_data(workspace.properties["client_id"])
313-
app_id_to_role_name = {app_role["id"]: app_role["value"] for app_role in sp_graph_data["value"][0]["appRoles"]}
315+
app_id_to_role_name = {app_role["id"]: (app_role["displayName"]) for app_role in sp_graph_data["value"][0]["appRoles"]}
314316
roles_graph_data = self._get_user_role_assignments(workspace.properties["sp_id"], msgraph_token)
315317
users_graph_data = self._get_user_details(roles_graph_data, msgraph_token)
316318
users_inc_groups = self._get_users_inc_groups_from_response(users_graph_data, roles_graph_data, app_id_to_role_name)
@@ -327,7 +329,7 @@ def get_assignable_users(self, filter: str = "", maxResultCount: int = 5) -> Lis
327329

328330
for user_data in graph_data["value"]:
329331
result.append(
330-
AssignableUser(name=user_data["displayName"], email=user_data["userPrincipalName"])
332+
AssignableUser(id=user_data["id"], displayName=user_data["displayName"], userPrincipalName=user_data["userPrincipalName"])
331333
)
332334

333335
return result
@@ -374,72 +376,67 @@ def get_workspace_role_by_name(self, name: str, workspace: Workspace) -> Role:
374376

375377
return None
376378

377-
def get_user_by_email(self, user_email: str) -> User:
378-
encoded_email = urllib.parse.quote(user_email)
379-
user_by_email_endpoint = f"{MICROSOFT_GRAPH_URL}/v1.0/users/{encoded_email}"
380-
user_data = self._ms_graph_query(user_by_email_endpoint, "GET")
381-
382-
return User(id=user_data["id"][0], name=user_data["displayName"][0], email=user_email, roles=[])
383-
384-
def assign_workspace_user(self, user: User, workspace: Workspace, role: Role) -> None:
379+
def assign_workspace_user(self, user_id: str, workspace: Workspace, role_id: str) -> None:
385380
# User already has the role, do nothing
386-
if self._is_user_in_role(user, role):
381+
if self._is_user_in_role(user_id, role_id):
387382
return
388383
if self._is_workspace_role_group_in_use(workspace):
389-
return self._assign_workspace_user_to_application_group(user, workspace, role)
384+
return self._assign_workspace_user_to_application_group(user_id, workspace, role_id)
390385
else:
391-
return self._assign_workspace_user_to_application(user, workspace, role)
386+
return self._assign_workspace_user_to_application(user_id, workspace, role_id)
392387

393-
def _is_user_in_role(self, user: User, role: Role) -> bool:
394-
return any(r for r in user.roles if r == role.value)
388+
def _is_user_in_role(self, user_id: User, role_id: Role) -> bool:
389+
user_app_role_query = f"{MICROSOFT_GRAPH_URL}/v1.0/users/{user_id}/appRoleAssignments"
390+
user_app_roles = self._ms_graph_query(user_app_role_query, "GET")
391+
return any(r for r in user_app_roles["value"] if r["appRoleId"] == role_id)
395392

396393
def _is_workspace_role_group_in_use(self, workspace: Workspace) -> bool:
397394
aad_groups_in_user = workspace.properties["create_aad_groups"]
398395
return aad_groups_in_user
399396

400-
def _get_workspace_group_name(self, workspace: Workspace, role: Role) -> tuple:
397+
def _get_workspace_group_name(self, workspace: Workspace, role_id: str) -> tuple:
401398
tre_id = workspace.properties["tre_id"]
402399
workspace_id = workspace.properties["workspace_id"]
403400
group_name = ""
404401
app_role_id_suffix = ""
405-
if role.value == "WorkspaceResearcher":
402+
if workspace.properties["app_role_id_workspace_researcher"] == role_id:
406403
group_name = "Workspace Researchers"
407404
app_role_id_suffix = "workspace_researcher"
408-
elif role.value == "WorkspaceOwner":
405+
elif workspace.properties["app_role_id_workspace_owner"] == role_id:
409406
group_name = "Workspace Owners"
410407
app_role_id_suffix = "workspace_owner"
411-
elif role.value == "AirlockManager":
408+
elif workspace.properties["app_role_id_workspace_airlock_manager"] == role_id:
412409
group_name = "Airlock Managers"
413410
app_role_id_suffix = "workspace_airlock_manager"
414411
else:
415-
raise UserRoleAssignmentError(f"Unknown role: {role.value}")
412+
raise UserRoleAssignmentError(f"Unknown role: {role_id}")
416413

417414
return (f"{tre_id}-ws-{workspace_id} {group_name}", f"app_role_id_{app_role_id_suffix}")
418415

419-
def _assign_workspace_user_to_application_group(self, user: User, workspace: Workspace, role: Role):
416+
def _assign_workspace_user_to_application_group(self, user_id: str, workspace: Workspace, role_id: str):
420417
msgraph_token = self._get_msgraph_token()
421418
roles_graph_data = self._get_user_role_assignments(workspace.properties["sp_id"], msgraph_token)
422-
group_details = self._get_workspace_group_name(workspace, role)
419+
group_details = self._get_workspace_group_name(workspace, role_id)
423420
group_name = group_details[0]
424421
workspace_app_role_field = group_details[1]
425422

426423
for group in [item for item in roles_graph_data["value"] if item["principalType"] == PrincipalType.Group.value]:
427424
if group.get("principalDisplayName") == group_name and group.get("appRoleId") == workspace.properties[workspace_app_role_field]:
428-
self._add_user_to_group(user.id, group["principalId"])
425+
self._add_user_to_group(user_id, group["principalId"])
429426
return
430427

431-
raise UserRoleAssignmentError(f"Unable to assign user to group with role: {role.value}")
428+
raise UserRoleAssignmentError(f"Unable to assign user to group with role: {role_id}")
432429

433-
def _remove_workspace_user_from_application_group(self, user: User, workspace: Workspace, role: Role):
430+
def _remove_workspace_user_from_application_group(self, user_id: str, workspace: Workspace, role_id: str):
434431
msgraph_token = self._get_msgraph_token()
435432
roles_graph_data = self._get_user_role_assignments(workspace.properties["sp_id"], msgraph_token)
436-
group_details = self._get_workspace_group_name(workspace, role)
433+
group_details = self._get_workspace_group_name(workspace, role_id)
437434
group_name = group_details[0]
438435
workspace_app_role_field = group_details[1]
439436

440437
for group in [item for item in roles_graph_data["value"] if item["principalType"] == PrincipalType.Group.value]:
441438
if group.get("principalDisplayName") == group_name and group.get("appRoleId") == workspace.properties[workspace_app_role_field]:
442-
self._remove_user_from_group(user.id, group["principalId"])
439+
self._remove_user_from_group(user_id, group["principalId"])
443440
return
444441
raise UserRoleAssignmentError(f"Unable to assign user to group with role: {role.value}")
445442

@@ -459,15 +456,15 @@ def _remove_user_from_group(self, user_id: str, group_id: str):
459456
response = requests.delete(url, headers=self._get_auth_header(msgraph_token))
460457
return response
461458

462-
def _assign_workspace_user_to_application(self, user: User, workspace: Workspace, role: Role):
459+
def _assign_workspace_user_to_application(self, user_id: str, workspace: Workspace, role_id: str):
463460
msgraph_token = self._get_msgraph_token()
464-
url = f"{MICROSOFT_GRAPH_URL}/v1.0/users/{user.id}/appRoleAssignments"
461+
url = f"{MICROSOFT_GRAPH_URL}/v1.0/users/{user_id}/appRoleAssignments"
465462
application_id = workspace.properties["sp_id"]
466463

467464
body = {
468-
"principalId": user.id,
465+
"principalId": user_id,
469466
"resourceId": application_id,
470-
"appRoleId": role.id,
467+
"appRoleId": role_id,
471468
}
472469

473470
response = requests.post(url, json=body, headers=self._get_auth_header(msgraph_token))
@@ -479,20 +476,22 @@ def _get_role_assignment_for_user(self, user_id: str, role_id: str) -> dict:
479476
if role["appRoleId"] == role_id:
480477
return role
481478

482-
def remove_workspace_role_user_assignment(self, user: User,
483-
role: Role,
484-
workspace: Workspace) -> None:
485-
# Get the role assignment id for the role we want to remove
486-
role_assignment = self._get_role_assignment_for_user(user.id, role.id)
487-
488-
if self._is_workspace_role_group_in_use(workspace):
489-
self._remove_workspace_user_from_application_group(user, workspace, role)
479+
def remove_workspace_role_user_assignment(self,
480+
user_id: str,
481+
role_id: str,
482+
workspace: Workspace,
483+
assignmentType: AssignmentType = AssignmentType.APP_ROLE
484+
) -> None:
485+
if assignmentType == AssignmentType.APP_ROLE:
486+
self._remove_workspace_user_from_application(user_id, role_id)
490487
else:
491-
self._remove_workspace_user_from_application(user, role_assignment)
488+
self._remove_workspace_user_from_application_group(user_id, workspace, role_id)
489+
490+
def _remove_workspace_user_from_application(self, user_id: str, role_id: str) -> requests.Response:
491+
role_assignment = self._get_role_assignment_for_user(user_id, role_id)
492492

493-
def _remove_workspace_user_from_application(self, user: User, role_assignment: dict) -> requests.Response:
494493
msgraph_token = self._get_msgraph_token()
495-
url = f"{MICROSOFT_GRAPH_URL}/v1.0/users/{user.id}/appRoleAssignments/{role_assignment['id']}"
494+
url = f"{MICROSOFT_GRAPH_URL}/v1.0/users/{user_id}/appRoleAssignments/{role_assignment['id']}"
496495
response = requests.delete(url, headers=self._get_auth_header(msgraph_token))
497496
return response
498497

0 commit comments

Comments
 (0)