Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 0 additions & 11 deletions epictrack-api/src/api/services/keycloak.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,6 @@ def get_user_by_email(email: str):
users = response.json()
if not users:
raise ValueError(f"No user found with email: {email}")
print(users)
return users

@staticmethod
Expand All @@ -63,16 +62,6 @@ def get_group_members(group_id):
response = KeycloakService._request_keycloak(f'groups/{group_id}/members')
return response.json()

@staticmethod
def update_user_group(user_id, group_id):
"""Update the group of user"""
return KeycloakService._request_keycloak(f'users/{user_id}/groups/{group_id}', HttpMethod.PUT)

@staticmethod
def delete_user_group(user_id, group_id):
"""Delete user-group mapping"""
return KeycloakService._request_keycloak(f'users/{user_id}/groups/{group_id}', HttpMethod.DELETE)

@staticmethod
def get_user_groups(user_id):
"""Get groups of a user by user ID"""
Expand Down
66 changes: 0 additions & 66 deletions epictrack-api/src/api/services/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,7 @@
from flask import current_app
import sys

from api.exceptions import BusinessError, PermissionDeniedError
from api.services import authorisation
from api.utils import TokenInfo
from api.utils.roles import Role as KeycloakRole
from .keycloak import KeycloakService

Expand Down Expand Up @@ -80,70 +78,6 @@ def get_groups(cls):
current_app.logger.debug(f"filtered_groups: {filtered_groups}")
return filtered_groups

@classmethod
def update_user_group(cls, user_id, user_group_request):
"""
Updates the user's group based on the provided user group request.

Args:
cls: The class instance.
user_id (str): The ID of the user to update.
user_group_request (dict): A dictionary containing the group update request details.
Expected keys:
- "group_id_to_update" (str): The ID of the group to update.
Raises:
PermissionDeniedError: If the requester does not have permission to update the group.
Returns:
dict: The result of the group update operation from KeycloakService.
"""
cls._check_auth()
token_groups = TokenInfo.get_user_data()["groups"]
groups = cls.get_groups()
requesters_group = next(
(
group
for group in groups
if group["name"] in token_groups
),
None,
)
updating_group = next(
(
group
for group in groups
if group["id"] == user_group_request.get("group_id_to_update")
),
None,
)
if (
not requesters_group
and not updating_group
and int(UserService._get_level(requesters_group))
< int(UserService._get_level(updating_group))
):
raise PermissionDeniedError("Permission denied")

UserService._delete_from_all_epictrack_subgroups(user_id)

result = KeycloakService.update_user_group(
user_id, user_group_request["group_id_to_update"]
)
return result

@staticmethod
def _delete_from_all_epictrack_subgroups(user_id):
"""Delete all subgroups of 'epictrack' for a user"""
groups = KeycloakService.get_user_groups(user_id)

# Find the main group 'epictrack' and get its subgroups
track_subgroups = [group for group in groups if 'track/' in group['path'].lower()]

for subgroup in track_subgroups:
result = KeycloakService.delete_user_group(user_id, subgroup['id'])

if result.status_code != 204:
raise BusinessError("Error removing group", 500)

@classmethod
def _get_level(cls, group):
"""
Expand Down
81 changes: 1 addition & 80 deletions epictrack-api/tests/unit/services/test_user.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
"""Unit tests for User Service."""
import sys
from unittest.mock import MagicMock, patch
from unittest.mock import patch

import pytest

from api.services.user import UserService
from api.exceptions import BusinessError


class TestGetAllUsers:
Expand Down Expand Up @@ -132,84 +131,6 @@ def test_handles_empty_subgroups(self, mock_app, mock_keycloak, mock_check_auth)
assert len(result) == 0


class TestUpdateUserGroup:
"""Tests for update_user_group method."""

@patch("api.services.user.UserService._delete_from_all_epictrack_subgroups")
@patch("api.services.user.UserService.get_groups")
@patch("api.services.user.TokenInfo")
@patch("api.services.user.KeycloakService")
@patch("api.services.user.UserService._check_auth")
def test_updates_user_group(
self, mock_check_auth, mock_keycloak, mock_token, mock_get_groups, mock_delete
):
"""Test updating user's group."""
user_id = "user123"
user_group_request = {"group_id_to_update": "new-group-id"}

mock_token.get_user_data.return_value = {"groups": ["Admin"]}
mock_groups = [
{"id": "new-group-id", "name": "NewGroup", "attributes": {"level": ["5"]}},
{"id": "old-group-id", "name": "Admin", "attributes": {"level": ["10"]}},
]
mock_get_groups.return_value = mock_groups
mock_keycloak.update_user_group.return_value = {"success": True}

UserService.update_user_group(user_id, user_group_request)

mock_delete.assert_called_once_with(user_id)
mock_keycloak.update_user_group.assert_called_once_with(user_id, "new-group-id")


class TestDeleteFromAllEpictrackSubgroups:
"""Tests for _delete_from_all_epictrack_subgroups method."""

@patch("api.services.user.KeycloakService")
def test_deletes_all_track_subgroups(self, mock_keycloak):
"""Test deleting user from all TRACK subgroups."""
user_id = "user123"
mock_groups = [
{"id": "group1", "path": "/TRACK/Admin"},
{"id": "group2", "path": "/TRACK/User"},
{"id": "group3", "path": "/OTHER/Group"},
]
mock_keycloak.get_user_groups.return_value = mock_groups

mock_response = MagicMock()
mock_response.status_code = 204
mock_keycloak.delete_user_group.return_value = mock_response

UserService._delete_from_all_epictrack_subgroups(user_id)

# Should only delete from TRACK groups (2 calls)
assert mock_keycloak.delete_user_group.call_count == 2

@patch("api.services.user.KeycloakService")
def test_raises_error_on_delete_failure(self, mock_keycloak):
"""Test raises error when delete fails."""
user_id = "user123"
mock_groups = [{"id": "group1", "path": "/TRACK/Admin"}]
mock_keycloak.get_user_groups.return_value = mock_groups

mock_response = MagicMock()
mock_response.status_code = 500
mock_keycloak.delete_user_group.return_value = mock_response

with pytest.raises(BusinessError):
UserService._delete_from_all_epictrack_subgroups(user_id)

@patch("api.services.user.KeycloakService")
def test_handles_no_track_groups(self, mock_keycloak):
"""Test handling user with no TRACK groups."""
user_id = "user123"
mock_groups = [{"id": "group1", "path": "/OTHER/Group"}]
mock_keycloak.get_user_groups.return_value = mock_groups

UserService._delete_from_all_epictrack_subgroups(user_id)

mock_keycloak.delete_user_group.assert_not_called()


class TestGetLevel:
"""Tests for _get_level method."""

Expand Down
4 changes: 0 additions & 4 deletions epictrack-web/src/components/AppHelpButton/HelpPageMap.json
Original file line number Diff line number Diff line change
Expand Up @@ -157,10 +157,6 @@
{
"epicTrackPath": "/list-management/projects",
"helpPage": "https://intranet.gov.bc.ca/intranet/content?id=165BCE74CDFB458F894E6826D7664B12"
},
{
"epicTrackPath": "/admin/users",
"helpPage": "https://intranet.gov.bc.ca/intranet/content?id=D198F12C1FF743E3A7BE8A0A8A5E2AA9"
}
]
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,12 +122,6 @@ export const Routes: RouteType[] = [
icon: "GearIcon",
group: "Group5",
routes: [
{
name: "Users",
path: "/admin/users",
allowedRoles: [ROLES.MANAGE_USERS],
isAuthenticated: true,
},
{
name: "Settings",
path: "/admin/settings",
Expand Down
Loading