Skip to content

Commit

Permalink
refactored to removed unused classes
Browse files Browse the repository at this point in the history
Added support for multi assignment
  • Loading branch information
Matthew Fortunka committed Feb 19, 2025
1 parent fee832a commit d18a392
Show file tree
Hide file tree
Showing 8 changed files with 78 additions and 83 deletions.
18 changes: 10 additions & 8 deletions api_app/api/routes/workspace_users.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from fastapi import APIRouter, Depends, Response, status
from api.dependencies.workspaces import get_workspace_by_id_from_path
from models.domain.authentication import AssignmentType
from models.schemas.workspace_users import UserRoleAssignmentRequest
from models.domain.workspace_users import AssignmentType
from resources import strings
from services.authentication import get_access_service
from models.schemas.users import UsersInResponse, AssignableUsersInResponse
Expand Down Expand Up @@ -31,13 +32,14 @@ async def get_workspace_roles(workspace=Depends(get_workspace_by_id_from_path),


@workspaces_users_admin_router.post("/workspaces/{workspace_id}/users/assign", status_code=status.HTTP_202_ACCEPTED, name=strings.API_ASSIGN_WORKSPACE_USER)
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:

access_service.assign_workspace_user(
user_id,
workspace,
role_id
)
async def assign_workspace_user(response: Response, userRoleAssignmentRequest: UserRoleAssignmentRequest, workspace=Depends(get_workspace_by_id_from_path), access_service=Depends(get_access_service)) -> UsersInResponse:

for user_id in userRoleAssignmentRequest.user_ids:
access_service.assign_workspace_user(
user_id,
workspace,
userRoleAssignmentRequest.role_id
)

users = access_service.get_workspace_users(workspace)
return UsersInResponse(users=users)
Expand Down
38 changes: 0 additions & 38 deletions api_app/models/domain/authentication.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
from collections import namedtuple
from typing import List
from pydantic import BaseModel, Field
from enum import Enum

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

Expand All @@ -11,43 +10,6 @@ class User(BaseModel):
email: str = Field(None)
roles: List[str] = Field([])
roleAssignments: List[RoleAssignment] = Field([])

class Role(BaseModel):
id: str
value: str
isEnabled: bool
email: str = Field(None)
allowedMemberTypes: List[str] = Field([])
description: str
displayName: str
origin: str
roleAssignments: List[RoleAssignment] = Field([])

class AssignableUser(BaseModel):
id: str
displayName: str
userPrincipalName: str

class AssignmentType(Enum):
APP_ROLE = "ApplicationRole"
GROUP = "Group"

class AssignedRole(BaseModel):
id: str
displayName: str
type: AssignmentType

def __eq__(self, other):
return self.id == other.id

def __hash__(self):
return hash(self.id)

class AssignedUser(BaseModel):
id: str
displayName: str
userPrincipalName: str
roles: List[AssignedRole] = Field([])



29 changes: 29 additions & 0 deletions api_app/models/domain/workspace_users.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from typing import List
from pydantic import BaseModel, Field
from enum import Enum

class AssignableUser(BaseModel):
id: str
displayName: str
userPrincipalName: str

class AssignmentType(Enum):
APP_ROLE = "ApplicationRole"
GROUP = "Group"

class Role(BaseModel):
id: str
displayName: str
type: AssignmentType

def __eq__(self, other):
return self.id == other.id

def __hash__(self):
return hash(self.id)

class AssignedUser(BaseModel):
id: str
displayName: str
userPrincipalName: str
roles: List[Role] = Field([])
2 changes: 1 addition & 1 deletion api_app/models/schemas/roles.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from pydantic import BaseModel, Field
from typing import List
from models.domain.authentication import Role
from models.domain.workspace_users import Role


class RolesInResponse(BaseModel):
Expand Down
2 changes: 1 addition & 1 deletion api_app/models/schemas/users.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from pydantic import BaseModel, Field
from typing import List

from models.domain.authentication import AssignedUser, AssignableUser
from models.domain.workspace_users import AssignedUser, AssignableUser


class UsersInResponse(BaseModel):
Expand Down
15 changes: 15 additions & 0 deletions api_app/models/schemas/workspace_users.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from typing import List
from pydantic import BaseModel, Field


class UserRoleAssignmentRequest(BaseModel):
role_id: str = Field(title="Role Id", description="Role to assign users to")
user_ids: List[str] = Field([], title="List of User Ids", description="List of User Ids to assign the role to")

class Config:
schema_extra = {
"example": {
"role_id": "1234",
"user_ids": ["1", "2"]
}
}
35 changes: 11 additions & 24 deletions api_app/services/aad_authentication.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
from services.access_service import AccessService, AuthConfigValidationError, UserRoleAssignmentError
from core import config
from db.errors import EntityDoesNotExist
from models.domain.authentication import AssignedRole, AssignedUser, AssignmentType, User, AssignableUser, Role, RoleAssignment
from models.domain.authentication import User, RoleAssignment
from models.domain.workspace_users import AssignedUser, AssignmentType, AssignableUser, Role
from models.domain.workspace import Workspace, WorkspaceRole
from resources import strings
from db.repositories.workspaces import WorkspaceRepository
Expand Down Expand Up @@ -264,11 +265,11 @@ def _get_user_details(self, roles_graph_data, msgraph_token):

return users_graph_data

def _get_roles_for_principal(self, user_id, roles_graph_data, app_id_to_role_name, assignmentType: AssignmentType = AssignmentType.APP_ROLE) -> List[AssignedRole]:
def _get_roles_for_principal(self, user_id, roles_graph_data, app_id_to_role_name, assignmentType: AssignmentType = AssignmentType.APP_ROLE) -> List[Role]:
roles = []
for role_assignment in roles_graph_data["value"]:
if role_assignment["principalId"] == user_id:
roles.append(AssignedRole(id=role_assignment["appRoleId"], displayName=app_id_to_role_name[role_assignment["appRoleId"]], type=assignmentType))
roles.append(Role(id=role_assignment["appRoleId"], displayName=app_id_to_role_name[role_assignment["appRoleId"]], type=assignmentType))
return roles

def _get_users_inc_groups_from_response(self, users_graph_data, roles_graph_data, app_id_to_role_name) -> List[AssignedUser]:
Expand Down Expand Up @@ -340,13 +341,14 @@ def get_workspace_roles(self, workspace: Workspace) -> List[Role]:

roles = []

roleAssignmentType = AssignmentType.APP_ROLE
if self._is_workspace_role_group_in_use(workspace):
roleAssignmentType = AssignmentType.GROUP

for role in graph_data["value"]:
roles.append(Role(id=role["id"], value=role["value"],
isEnabled=role["isEnabled"],
description=role["description"],
roles.append(Role(id=role["id"],
displayName=role["displayName"],
origin=role["origin"],
allowedMemberTypes=role["allowedMemberTypes"]))
type=roleAssignmentType))

return roles

Expand All @@ -361,21 +363,6 @@ def get_workspace_user_emails_by_role_assignment(self, workspace: Workspace):
workspace_role_assignments_details[role].append(user.email)
return workspace_role_assignments_details

def get_workspace_role_by_name(self, name: str, workspace: Workspace) -> Role:
app_roles_endpoint = f"{MICROSOFT_GRAPH_URL}/v1.0/servicePrincipals/{workspace.properties['sp_id']}/appRoles"
graph_data = self._ms_graph_query(app_roles_endpoint, "GET")

for role in graph_data["value"]:
if role["value"] == name:
return Role(id=role["id"], value=role["value"],
isEnabled=role["isEnabled"],
description=role["description"],
displayName=role["displayName"],
origin=role["origin"],
allowedMemberTypes=role["allowedMemberTypes"])

return None

def assign_workspace_user(self, user_id: str, workspace: Workspace, role_id: str) -> None:
# User already has the role, do nothing
if self._is_user_in_role(user_id, role_id):
Expand All @@ -385,7 +372,7 @@ def assign_workspace_user(self, user_id: str, workspace: Workspace, role_id: str
else:
return self._assign_workspace_user_to_application(user_id, workspace, role_id)

def _is_user_in_role(self, user_id: User, role_id: Role) -> bool:
def _is_user_in_role(self, user_id: str, role_id: str) -> bool:
user_app_role_query = f"{MICROSOFT_GRAPH_URL}/v1.0/users/{user_id}/appRoleAssignments"
user_app_roles = self._ms_graph_query(user_app_role_query, "GET")
return any(r for r in user_app_roles["value"] if r["appRoleId"] == role_id)
Expand Down
22 changes: 11 additions & 11 deletions ui/app/src/components/workspaces/WorkspaceUsersAssignNew.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,15 +75,15 @@ export const WorkSpaceUsersAssignNew: React.FunctionComponent<WorkspaceUsersAssi

const onChange = (items?: IPersonaProps[] | undefined): void => {
if (items && items.length > 0) {
setSelectedUser(items[0].key as string);
setSelectedUsers(items.map(item => item.key as string));
}
else {
setSelectedUser(null);
setSelectedUsers(null);
}
};

const [roleOptions, setRoleOptions] = useState<IDropdownOption[]>([]);
const [selectedUser, setSelectedUser] = useState<string | null>(null);
const [selectedUsers, setSelectedUsers] = useState<string[] | null>(null);
const [selectedRole, setSelectedRole] = useState<string | null>(null);
const [assigning, setAssigning] = useState(false);
const [hasAssignmentError, setHasAssignmentError] = useState(false);
Expand Down Expand Up @@ -120,11 +120,12 @@ export const WorkSpaceUsersAssignNew: React.FunctionComponent<WorkspaceUsersAssi
const assign = useCallback(async () => {
setAssigning(true);

const encodedUser = selectedUser?.replaceAll('#', '%23');

const scopeId = "";
try {
const response = await apiCall(`${ApiEndpoint.Workspaces}/${workspace.id}/${ApiEndpoint.Users}/assign?user_id=${selectedUser}&role_id=${selectedRole}`, HttpMethod.Post, scopeId);
const response = await apiCall(`${ApiEndpoint.Workspaces}/${workspace.id}/${ApiEndpoint.Users}/assign`, HttpMethod.Post, scopeId, {
role_id: selectedRole,
user_ids: selectedUsers
});
props.onAssignUser(response);
}
catch (err: any) {
Expand All @@ -134,21 +135,21 @@ export const WorkSpaceUsersAssignNew: React.FunctionComponent<WorkspaceUsersAssi
}
setAssigning(false);

}, [selectedUser, apiCall, workspace.id, selectedRole, props]);
}, [selectedUsers, apiCall, workspace.id, selectedRole, props]);

const renderFooter = useCallback(() => {
let footer = <></>
footer = <>
<div style={{ textAlign: 'end' }}>
<PrimaryButton onClick={() => assign()} disabled={assigning || (!selectedUser || !selectedRole)}>Assign</PrimaryButton>
<PrimaryButton onClick={() => assign()} disabled={assigning || (!selectedUsers || !selectedRole)}>Assign</PrimaryButton>
</div>
</>
return footer;
}, [selectedUser, selectedRole, assign, assigning]);
}, [selectedUsers, selectedRole, assign, assigning]);

return (
<Panel
headerText="Assign user to a role"
headerText="Assign users to a role"
isOpen={true}
isLightDismiss={true}
onDismiss={dismissPanel}
Expand All @@ -174,7 +175,6 @@ export const WorkSpaceUsersAssignNew: React.FunctionComponent<WorkspaceUsersAssi
componentRef={picker}
resolveDelay={300}
required={true}
itemLimit={1}
onChange={onChange}
/>
</Stack>
Expand Down

0 comments on commit d18a392

Please sign in to comment.