Add services test

This commit is contained in:
Christian Cueni 2024-06-20 10:36:43 +02:00
parent ade89c3c5b
commit e436c5ddbd
4 changed files with 203 additions and 23 deletions

View File

@ -53,7 +53,7 @@ class CreateOrUpdateStudentTestCase(TestCase):
"Tel. Privat": "079 593 83 43", "Tel. Privat": "079 593 83 43",
"Geburtsdatum": "01.01.2000", "Geburtsdatum": "01.01.2000",
"email_notification_categories": ["INFORMATION"], "email_notification_categories": ["INFORMATION"],
'intermediate_sso_id': '' "intermediate_sso_id": "",
} }
def test_create_student(self): def test_create_student(self):

View File

@ -1,14 +1,17 @@
import unicodedata import unicodedata
from typing import Dict, List, Tuple from typing import Dict, List, Tuple
import structlog
from django.conf import settings from django.conf import settings
from keycloak import KeycloakAdmin, KeycloakOpenIDConnection from keycloak import KeycloakAdmin, KeycloakOpenIDConnection
from keycloak.exceptions import KeycloakDeleteError, KeycloakPostError from keycloak.exceptions import KeycloakDeleteError, KeycloakError, KeycloakPostError
from vbv_lernwelt.core.models import User from vbv_lernwelt.core.models import User
from vbv_lernwelt.sso.exceptions import MyVbvKeycloakDeleteError, MyVbvKeycloakPostError from vbv_lernwelt.sso.exceptions import MyVbvKeycloakDeleteError, MyVbvKeycloakPostError
from vbv_lernwelt.sso.role_sync.roles import ROLE_IDS, SSO_ROLES from vbv_lernwelt.sso.role_sync.roles import ROLE_IDS, SSO_ROLES
logger = structlog.get_logger(__name__)
CourseRolesType = List[Tuple[str, str]] CourseRolesType = List[Tuple[str, str]]
if settings.OAUTH_SYNC_ROLES: if settings.OAUTH_SYNC_ROLES:
@ -25,31 +28,44 @@ if settings.OAUTH_SYNC_ROLES:
def add_roles_to_user(user: User, course_roles: CourseRolesType): def add_roles_to_user(user: User, course_roles: CourseRolesType):
user_id = user.additional_json_data.get("intermediate_sso_id", "") return _handle_add_remove_action(
if settings.OAUTH_SYNC_ROLES and user_id: user=user,
request_roles = _get_role_request_data(course_roles) course_roles=course_roles,
try: func=keycloak_admin.assign_realm_roles,
keycloak_admin.assign_realm_roles( kc_exception=KeycloakPostError,
user_id=user_id, myvbv_exception=MyVbvKeycloakPostError,
roles=request_roles, )
)
except KeycloakPostError as e:
raise MyVbvKeycloakPostError(e, request_roles)
return True
return False
def remove_roles_from_user(user: User, course_roles: CourseRolesType): def remove_roles_from_user(user: User, course_roles: CourseRolesType):
return _handle_add_remove_action(
user=user,
course_roles=course_roles,
func=keycloak_admin.delete_realm_roles_of_user,
kc_exception=KeycloakDeleteError,
myvbv_exception=MyVbvKeycloakDeleteError,
)
def _handle_add_remove_action(
user: User,
course_roles: CourseRolesType,
func: callable,
kc_exception: KeycloakError,
myvbv_exception: KeycloakPostError or KeycloakDeleteError,
):
user_id = user.additional_json_data.get("intermediate_sso_id", "") user_id = user.additional_json_data.get("intermediate_sso_id", "")
if settings.OAUTH_SYNC_ROLES and user_id: if settings.OAUTH_SYNC_ROLES and user_id:
request_roles = _get_role_request_data(course_roles) request_roles = _get_role_request_data(course_roles)
if not request_roles:
return False
try: try:
keycloak_admin.delete_realm_roles_of_user( func(
user_id=user_id, user_id=user_id,
roles=request_roles, roles=request_roles,
) )
except KeycloakDeleteError as e: except kc_exception as e:
raise MyVbvKeycloakDeleteError(e, request_roles) raise myvbv_exception(e, request_roles)
return True return True
return False return False
@ -58,9 +74,9 @@ def update_roles_for_user(
user: User, add_course_roles: CourseRolesType, remove_course_roles: CourseRolesType user: User, add_course_roles: CourseRolesType, remove_course_roles: CourseRolesType
): ):
if settings.OAUTH_SYNC_ROLES: if settings.OAUTH_SYNC_ROLES:
remove_roles_from_user(user, remove_course_roles) remove_ret_value = remove_roles_from_user(user, remove_course_roles)
add_roles_to_user(user, add_course_roles) add_ret_value = add_roles_to_user(user, add_course_roles)
return True return remove_ret_value and add_ret_value
return False return False
@ -110,8 +126,15 @@ def _get_role_request_data(course_roles: CourseRolesType) -> List[Dict[str, str]
for item in course_roles: for item in course_roles:
course_slug, role = item course_slug, role = item
sanitized_course_slug = _remove_accents(course_slug) sanitized_course_slug = _remove_accents(course_slug)
oauth_role = _create_role_name(sanitized_course_slug, role) try:
request_roles.append({"id": ROLE_IDS[oauth_role], "name": oauth_role}) oauth_role = _create_role_name(sanitized_course_slug, role)
request_roles.append({"id": ROLE_IDS[oauth_role], "name": oauth_role})
except KeyError:
logger.warning(
"Role or course not found in SSO_ROLES",
course_slug=course_slug,
role=role,
)
return request_roles return request_roles

View File

@ -15,7 +15,7 @@ from vbv_lernwelt.sso.role_sync.services import (
@receiver(post_delete, sender=CourseSessionUser, dispatch_uid="delete_sso_roles") @receiver(post_delete, sender=CourseSessionUser, dispatch_uid="delete_sso_roles")
def delete_sso_roles(sender, instance, **kwargs): def remove_sso_roles(sender, instance, **kwargs):
try: try:
remove_roles_from_user( remove_roles_from_user(
instance.user, [(instance.course_session.course.slug, instance.role)] instance.user, [(instance.course_session.course.slug, instance.role)]

View File

@ -0,0 +1,157 @@
from unittest.mock import patch
from django.test import override_settings, TestCase
from keycloak.exceptions import KeycloakDeleteError, KeycloakPostError
from vbv_lernwelt.core.models import User
from vbv_lernwelt.sso.exceptions import MyVbvKeycloakDeleteError, MyVbvKeycloakPostError
from vbv_lernwelt.sso.role_sync.services import (
_remove_accents,
add_roles_to_user,
create_user,
remove_roles_from_user,
sync_roles_for_user,
update_roles_for_user,
)
class ApiTestCase(TestCase):
def setUp(self):
self.user = User(email="test@example.com", first_name="Test", last_name="User")
self.user.additional_json_data = {"intermediate_sso_id": "1234"}
self.course_roles = [
("überbetriebliche-kurse", "EXPERT"),
("versicherungsvermittler-in", "MEMBER"),
]
self.expected_roles = [
{
"name": "myvbv-uberbetriebliche-kurse-expert",
"id": "c7e33cb6-d227-4764-9b8e-d42af79fb46d",
},
{
"name": "myvbv-versicherungsvermittler-in-member",
"id": "3ab4eab2-7d7c-43bb-a927-4cf54f24ccc2",
},
]
@override_settings(OAUTH_SYNC_ROLES=True)
@patch("vbv_lernwelt.sso.role_sync.services.keycloak_admin")
def test_add_roles_to_user_success(self, mock_keycloak_admin):
mock_keycloak_admin.assign_realm_roles.return_value = None
result = add_roles_to_user(self.user, self.course_roles)
self.assertTrue(result)
mock_keycloak_admin.assign_realm_roles.assert_called_once_with(
user_id="1234", roles=self.expected_roles
)
@override_settings(OAUTH_SYNC_ROLES=True)
@patch("vbv_lernwelt.sso.role_sync.services.keycloak_admin")
def test_add_roles_to_user_keycloak_post_error(self, mock_keycloak_admin):
mock_keycloak_admin.assign_realm_roles.side_effect = KeycloakPostError
with self.assertRaises(MyVbvKeycloakPostError) as cm:
add_roles_to_user(self.user, self.course_roles)
exception = cm.exception
self.assertIsInstance(exception, MyVbvKeycloakPostError)
self.assertEqual(exception.additional_data, self.expected_roles)
@override_settings(OAUTH_SYNC_ROLES=True)
@patch("vbv_lernwelt.sso.role_sync.services.keycloak_admin")
def test_remove_roles_to_user_success(self, mock_keycloak_admin):
mock_keycloak_admin.delete_realm_roles_of_user.return_value = None
result = remove_roles_from_user(self.user, self.course_roles)
self.assertTrue(result)
mock_keycloak_admin.delete_realm_roles_of_user.assert_called_once_with(
user_id="1234", roles=self.expected_roles
)
@override_settings(OAUTH_SYNC_ROLES=True)
@patch("vbv_lernwelt.sso.role_sync.services.keycloak_admin")
def test_remove_roles_to_user_keycloak_delete_error(self, mock_keycloak_admin):
mock_keycloak_admin.delete_realm_roles_of_user.side_effect = KeycloakDeleteError
with self.assertRaises(MyVbvKeycloakDeleteError) as cm:
remove_roles_from_user(self.user, self.course_roles)
exception = cm.exception
self.assertIsInstance(exception, MyVbvKeycloakDeleteError)
self.assertEqual(exception.additional_data, self.expected_roles)
@override_settings(OAUTH_SYNC_ROLES=True)
@patch("vbv_lernwelt.sso.role_sync.services.remove_roles_from_user")
@patch("vbv_lernwelt.sso.role_sync.services.add_roles_to_user")
def test_update_roles_to_user(
self, mock_add_roles_to_user, mock_remove_roles_from_user
):
mock_add_roles_to_user.return_value = True
mock_remove_roles_from_user.return_value = True
update_roles_for_user(self.user, self.course_roles, self.course_roles)
mock_add_roles_to_user.assert_called_once()
mock_remove_roles_from_user.assert_called_once()
@override_settings(OAUTH_SYNC_ROLES=True)
@patch("vbv_lernwelt.sso.role_sync.services.keycloak_admin")
def test_sync_roles_to_user(self, mock_keycloak_admin):
mock_keycloak_admin.get_realm_roles_of_user.return_value = (
self.expected_roles
) # just use them here as well
mock_keycloak_admin.delete_realm_roles_of_user.return_value = True
mock_keycloak_admin.assign_realm_roles.return_value = None
sync_roles_for_user(self.user, self.course_roles)
mock_keycloak_admin.get_realm_roles_of_user.assert_called_once_with(
user_id="1234"
)
mock_keycloak_admin.delete_realm_roles_of_user.assert_called_once_with(
user_id="1234", roles=self.expected_roles
)
mock_keycloak_admin.assign_realm_roles.assert_called_once_with(
user_id="1234", roles=self.expected_roles
)
@override_settings(OAUTH_SYNC_ROLES=True)
@patch("vbv_lernwelt.sso.role_sync.services.keycloak_admin")
def test_create_user(self, mock_keycloak_admin):
mock_keycloak_admin.create_user.return_value = "im-an-uuid-1234"
user_data = {
"username": self.user.email,
"email": self.user.email,
"enabled": True,
"firstName": self.user.first_name,
"lastName": self.user.last_name,
}
create_user(self.user)
mock_keycloak_admin.create_user.assert_called_once_with(
user_data, exist_ok=True
)
@override_settings(OAUTH_SYNC_ROLES=True)
@patch("vbv_lernwelt.sso.role_sync.services.keycloak_admin")
def test_ignore_missing_course(self, mock_keycloak_admin):
mock_keycloak_admin.assign_realm_roles.return_value = None
course_roles = [
("blabla-kurse", "EXPERT"),
]
result = add_roles_to_user(self.user, course_roles)
self.assertFalse(result)
mock_keycloak_admin.assign_realm_roles.assert_not_called()
class HelpersTestCase(TestCase):
def test_remove_accents(self):
no_accents = _remove_accents("äüöéèà")
self.assertEqual(no_accents, "auoeea")
# test helpers
# test wrong key