diff --git a/server/vbv_lernwelt/core/models.py b/server/vbv_lernwelt/core/models.py index d0baf2c5..8fa757bd 100644 --- a/server/vbv_lernwelt/core/models.py +++ b/server/vbv_lernwelt/core/models.py @@ -147,10 +147,10 @@ class User(AbstractUser): self.additional_json_data = ( self.additional_json_data | sanitize_json_data_input( - { - **data, - } - ) + { + **data, + } + ) ) @property diff --git a/server/vbv_lernwelt/sso/admin.py b/server/vbv_lernwelt/sso/admin.py index f843d6a6..afd9b3aa 100644 --- a/server/vbv_lernwelt/sso/admin.py +++ b/server/vbv_lernwelt/sso/admin.py @@ -4,6 +4,8 @@ from django.utils.translation import gettext_lazy as _ from keycloak.exceptions import KeycloakDeleteError, KeycloakPostError from vbv_lernwelt.course.models import CourseSessionUser +from vbv_lernwelt.course_session_group.models import CourseSessionGroup +from vbv_lernwelt.learning_mentor.models import LearningMentor from vbv_lernwelt.sso.models import SsoSyncError, SsoUser from vbv_lernwelt.sso.role_sync.services import ( create_and_update_user, @@ -33,6 +35,16 @@ def sync_sso_roles_from_admin(user: User, request): (csu.course_session.course.slug, csu.role) for csu in CourseSessionUser.objects.filter(user=user) ] + + course_roles += [ + (lm.course_session.course.slug, "LEARNING_MENTOR") + for lm in LearningMentor.objects.filter(mentor=user) + ] + + for csg in CourseSessionGroup.objects.filter(supervisor=user): + for course_session in csg.course_session.all(): + course_roles.append((course_session.course.slug, "SUPERVISOR")) + try: sync_roles_for_user(user, course_roles) messages.add_message( diff --git a/server/vbv_lernwelt/sso/migrations/0001_initial.py b/server/vbv_lernwelt/sso/migrations/0001_initial.py index 687c6a17..7267c8b3 100644 --- a/server/vbv_lernwelt/sso/migrations/0001_initial.py +++ b/server/vbv_lernwelt/sso/migrations/0001_initial.py @@ -1,18 +1,34 @@ -# Generated by Django 3.2.25 on 2024-06-20 05:24 +# Generated by Django 3.2.25 on 2024-06-26 15:34 +import django.contrib.auth.models import django.db.models.deletion from django.conf import settings from django.db import migrations, models class Migration(migrations.Migration): + initial = True dependencies = [ + ("core", "0007_auto_20240220_1058"), migrations.swappable_dependency(settings.AUTH_USER_MODEL), ] operations = [ + migrations.CreateModel( + name="SsoUser", + fields=[], + options={ + "proxy": True, + "indexes": [], + "constraints": [], + }, + bases=("core.user",), + managers=[ + ("objects", django.contrib.auth.models.UserManager()), + ], + ), migrations.CreateModel( name="SsoSyncError", fields=[ diff --git a/server/vbv_lernwelt/sso/role_sync/services.py b/server/vbv_lernwelt/sso/role_sync/services.py index 23df6078..e88c25f0 100644 --- a/server/vbv_lernwelt/sso/role_sync/services.py +++ b/server/vbv_lernwelt/sso/role_sync/services.py @@ -24,7 +24,7 @@ if settings.OAUTH_SYNC_ROLES: user_realm_name=settings.OAUTH_SIGNIN_REALM, client_id=settings.OAUTH_SIGNIN_ADMIN_CLIENT_ID, client_secret_key=settings.OAUTH_SIGNIN_ADMIN_CLIENT_SECRET, - verify=False, + verify=True, ) keycloak_admin = KeycloakAdmin(connection=keycloak_connection) @@ -48,7 +48,7 @@ def _handle_add_remove_action( action: SsoSyncError.Action, ): user_id = user.additional_json_data.get("intermediate_sso_id", "") - if settings.OAUTH_SYNC_ROLES and user_id: + if keycloak_admin and user_id: request_roles = _get_role_request_data(course_roles) if not request_roles: return False @@ -65,7 +65,7 @@ def _handle_add_remove_action( def update_roles_for_user( user: User, add_course_roles: CourseRolesType, remove_course_roles: CourseRolesType ): - if settings.OAUTH_SYNC_ROLES: + if keycloak_admin: remove_ret_value = remove_roles_from_user(user, remove_course_roles) add_ret_value = add_roles_to_user(user, add_course_roles) return remove_ret_value and add_ret_value @@ -73,10 +73,9 @@ def update_roles_for_user( def sync_roles_for_user(user: User, course_roles: CourseRolesType): - if settings.OAUTH_SYNC_ROLES: + if keycloak_admin: user_id = user.additional_json_data.get("intermediate_sso_id", "") if user_id: - # ignore for the moment, just in the admin assigned_roles = _filter_non_myvbv_roles( keycloak_admin.get_realm_roles_of_user(user_id=user_id) ) @@ -91,7 +90,7 @@ def sync_roles_for_user(user: User, course_roles: CourseRolesType): def create_user(user: User): - if settings.OAUTH_SYNC_ROLES: + if keycloak_admin: return _kc_create_user(user) return "" @@ -104,7 +103,7 @@ def create_and_update_user(user: User, save=False): def get_roles_for_user(user_id: str): - if settings.OAUTH_SYNC_ROLES: + if keycloak_admin: return keycloak_admin.get_realm_roles_of_user( user_id=user_id, ) diff --git a/server/vbv_lernwelt/sso/signals.py b/server/vbv_lernwelt/sso/signals.py index 31c5dc59..8e189586 100644 --- a/server/vbv_lernwelt/sso/signals.py +++ b/server/vbv_lernwelt/sso/signals.py @@ -34,7 +34,10 @@ def update_sso_roles_in_cs(sender, instance: CourseSessionUser, **kwargs): _add_sso_role(instance.user, instance.course_session.course.slug, instance.role) else: old_csu = CourseSessionUser.objects.get(pk=instance.pk) - if old_csu.role != instance.role: + if ( + old_csu.role != instance.role + or old_csu.course_session.course != instance.course_session.course + ): try: update_roles_for_user( instance.user, @@ -42,7 +45,7 @@ def update_sso_roles_in_cs(sender, instance: CourseSessionUser, **kwargs): (instance.course_session.course.slug, instance.role) ], remove_course_roles=[ - (instance.course_session.course.slug, old_csu.role) + (old_csu.course_session.course.slug, old_csu.role) ], ) except KeycloakError: diff --git a/server/vbv_lernwelt/sso/tests/test_signals.py b/server/vbv_lernwelt/sso/tests/test_signals.py index 7e96b2f5..70697ee7 100644 --- a/server/vbv_lernwelt/sso/tests/test_signals.py +++ b/server/vbv_lernwelt/sso/tests/test_signals.py @@ -92,7 +92,9 @@ class CourseSessionUserTests(TestCase): ) @patch("vbv_lernwelt.sso.signals.update_roles_for_user") - def test_update_role_for_user_on_creation(self, mock_update_roles_for_user): + def test_update_role_for_user_on_update_with_role_change( + self, mock_update_roles_for_user + ): mock_update_roles_for_user.return_value = None self.csu1_student1.role = "TRAINER" @@ -113,7 +115,32 @@ class CourseSessionUserTests(TestCase): ) @patch("vbv_lernwelt.sso.signals.update_roles_for_user") - def test_dont_update_role_for_user_on_creation(self, mock_update_roles_for_user): + def test_update_role_for_user_on_update_with_course_change( + self, mock_update_roles_for_user + ): + mock_update_roles_for_user.return_value = None + course, self.course_page = create_course("Test Course") + course_session = create_course_session(course=course, title="Test VV") + old_course_slug = self.csu1_student1.course_session.course.slug + + self.csu1_student1.course_session = course_session + + self.mock_pre_save_signal.send( + sender=CourseSessionUser, instance=self.csu1_student1 + ) + + self.assertEqual(mock_update_roles_for_user.call_count, 1) + + mock_update_roles_for_user.assert_called_with( + self.student1, + add_course_roles=[ + (self.csu1_student1.course_session.course.slug, "MEMBER") + ], + remove_course_roles=[(old_course_slug, "MEMBER")], + ) + + @patch("vbv_lernwelt.sso.signals.update_roles_for_user") + def test_dont_update_role_for_user_on_update(self, mock_update_roles_for_user): mock_update_roles_for_user.return_value = None self.csu1_student1.role = "MEMBER" @@ -138,12 +165,6 @@ class CourseSessionGroupTests(TestCase): self.trainer = User.objects.get(id=TEST_TRAINER1_USER_ID) self.supervisor = User.objects.get(id=TEST_SUPERVISOR1_USER_ID) - # m2m_changed.disconnect(receiver=update_sso_roles_in_csg, sender=CourseSessionGroup.supervisor.through) - # - # # Connect a mock signal handler - # self.mock_signal = Signal() - # self.mock_signal.connect(receiver=update_sso_roles_in_csg, sender=CourseSessionGroup.supervisor.through) - @patch("vbv_lernwelt.sso.signals.remove_roles_from_user") def test_remove_roles_for_csg_supervisors(self, mock_remove_roles_from_user): mock_remove_roles_from_user.return_value = None