Add migration, handle sync for mentors and supervisors
This commit is contained in:
parent
cc3b6bbf0d
commit
8af955f794
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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(
|
||||||
|
|
|
||||||
|
|
@ -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=[
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue