Add migration, handle sync for mentors and supervisors

This commit is contained in:
Christian Cueni 2024-06-27 09:43:34 +02:00
parent cc3b6bbf0d
commit 8af955f794
6 changed files with 73 additions and 22 deletions

View File

@ -147,10 +147,10 @@ class User(AbstractUser):
self.additional_json_data = ( self.additional_json_data = (
self.additional_json_data self.additional_json_data
| sanitize_json_data_input( | sanitize_json_data_input(
{ {
**data, **data,
} }
) )
) )
@property @property

View File

@ -4,6 +4,8 @@ from django.utils.translation import gettext_lazy as _
from keycloak.exceptions import KeycloakDeleteError, KeycloakPostError from keycloak.exceptions import KeycloakDeleteError, KeycloakPostError
from vbv_lernwelt.course.models import CourseSessionUser 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.models import SsoSyncError, SsoUser
from vbv_lernwelt.sso.role_sync.services import ( from vbv_lernwelt.sso.role_sync.services import (
create_and_update_user, create_and_update_user,
@ -33,6 +35,16 @@ def sync_sso_roles_from_admin(user: User, request):
(csu.course_session.course.slug, csu.role) (csu.course_session.course.slug, csu.role)
for csu in CourseSessionUser.objects.filter(user=user) 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: try:
sync_roles_for_user(user, course_roles) sync_roles_for_user(user, course_roles)
messages.add_message( messages.add_message(

View File

@ -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 import django.db.models.deletion
from django.conf import settings from django.conf import settings
from django.db import migrations, models from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
initial = True initial = True
dependencies = [ dependencies = [
("core", "0007_auto_20240220_1058"),
migrations.swappable_dependency(settings.AUTH_USER_MODEL), migrations.swappable_dependency(settings.AUTH_USER_MODEL),
] ]
operations = [ operations = [
migrations.CreateModel(
name="SsoUser",
fields=[],
options={
"proxy": True,
"indexes": [],
"constraints": [],
},
bases=("core.user",),
managers=[
("objects", django.contrib.auth.models.UserManager()),
],
),
migrations.CreateModel( migrations.CreateModel(
name="SsoSyncError", name="SsoSyncError",
fields=[ fields=[

View File

@ -24,7 +24,7 @@ if settings.OAUTH_SYNC_ROLES:
user_realm_name=settings.OAUTH_SIGNIN_REALM, user_realm_name=settings.OAUTH_SIGNIN_REALM,
client_id=settings.OAUTH_SIGNIN_ADMIN_CLIENT_ID, client_id=settings.OAUTH_SIGNIN_ADMIN_CLIENT_ID,
client_secret_key=settings.OAUTH_SIGNIN_ADMIN_CLIENT_SECRET, client_secret_key=settings.OAUTH_SIGNIN_ADMIN_CLIENT_SECRET,
verify=False, verify=True,
) )
keycloak_admin = KeycloakAdmin(connection=keycloak_connection) keycloak_admin = KeycloakAdmin(connection=keycloak_connection)
@ -48,7 +48,7 @@ def _handle_add_remove_action(
action: SsoSyncError.Action, action: SsoSyncError.Action,
): ):
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 keycloak_admin and user_id:
request_roles = _get_role_request_data(course_roles) request_roles = _get_role_request_data(course_roles)
if not request_roles: if not request_roles:
return False return False
@ -65,7 +65,7 @@ def _handle_add_remove_action(
def update_roles_for_user( 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 keycloak_admin:
remove_ret_value = remove_roles_from_user(user, remove_course_roles) remove_ret_value = remove_roles_from_user(user, remove_course_roles)
add_ret_value = add_roles_to_user(user, add_course_roles) add_ret_value = add_roles_to_user(user, add_course_roles)
return remove_ret_value and add_ret_value 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): 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", "") user_id = user.additional_json_data.get("intermediate_sso_id", "")
if user_id: if user_id:
# ignore for the moment, just in the admin
assigned_roles = _filter_non_myvbv_roles( assigned_roles = _filter_non_myvbv_roles(
keycloak_admin.get_realm_roles_of_user(user_id=user_id) 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): def create_user(user: User):
if settings.OAUTH_SYNC_ROLES: if keycloak_admin:
return _kc_create_user(user) return _kc_create_user(user)
return "" return ""
@ -104,7 +103,7 @@ def create_and_update_user(user: User, save=False):
def get_roles_for_user(user_id: str): def get_roles_for_user(user_id: str):
if settings.OAUTH_SYNC_ROLES: if keycloak_admin:
return keycloak_admin.get_realm_roles_of_user( return keycloak_admin.get_realm_roles_of_user(
user_id=user_id, user_id=user_id,
) )

View File

@ -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) _add_sso_role(instance.user, instance.course_session.course.slug, instance.role)
else: else:
old_csu = CourseSessionUser.objects.get(pk=instance.pk) 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: try:
update_roles_for_user( update_roles_for_user(
instance.user, instance.user,
@ -42,7 +45,7 @@ def update_sso_roles_in_cs(sender, instance: CourseSessionUser, **kwargs):
(instance.course_session.course.slug, instance.role) (instance.course_session.course.slug, instance.role)
], ],
remove_course_roles=[ remove_course_roles=[
(instance.course_session.course.slug, old_csu.role) (old_csu.course_session.course.slug, old_csu.role)
], ],
) )
except KeycloakError: except KeycloakError:

View File

@ -92,7 +92,9 @@ class CourseSessionUserTests(TestCase):
) )
@patch("vbv_lernwelt.sso.signals.update_roles_for_user") @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 mock_update_roles_for_user.return_value = None
self.csu1_student1.role = "TRAINER" self.csu1_student1.role = "TRAINER"
@ -113,7 +115,32 @@ class CourseSessionUserTests(TestCase):
) )
@patch("vbv_lernwelt.sso.signals.update_roles_for_user") @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 mock_update_roles_for_user.return_value = None
self.csu1_student1.role = "MEMBER" self.csu1_student1.role = "MEMBER"
@ -138,12 +165,6 @@ class CourseSessionGroupTests(TestCase):
self.trainer = User.objects.get(id=TEST_TRAINER1_USER_ID) self.trainer = User.objects.get(id=TEST_TRAINER1_USER_ID)
self.supervisor = User.objects.get(id=TEST_SUPERVISOR1_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") @patch("vbv_lernwelt.sso.signals.remove_roles_from_user")
def test_remove_roles_for_csg_supervisors(self, mock_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 mock_remove_roles_from_user.return_value = None