This commit is contained in:
Markos Gogoulos
2025-12-29 14:13:45 +02:00
parent 9667e6b0ad
commit 0585513439
6 changed files with 43 additions and 230 deletions

View File

@@ -4,18 +4,19 @@ LTI Names and Role Provisioning Service (NRPS) Client
Fetches course membership from Moodle via NRPS and syncs to MediaCMS RBAC groups
"""
import logging
import hashlib
from allauth.account.models import EmailAddress
from django.utils import timezone
from pylti1p3.names_roles import NamesRolesProvisioningService
from rbac.models import RBACMembership
from users.models import User
from .adapters import DjangoToolConfig
from .handlers import apply_lti_roles, generate_username_from_lti
from .models import LTIUserMapping
logger = logging.getLogger(__name__)
class LTINRPSClient:
"""Client for Names and Role Provisioning Service"""
@@ -37,16 +38,13 @@ class LTINRPSClient:
def can_sync(self):
"""Check if NRPS sync is available"""
if not self.platform.enable_nrps:
logger.warning(f"NRPS disabled for platform {self.platform.name}")
return False
if not self.nrps_claim:
logger.warning("NRPS claim missing in launch data")
return False
service_url = self.nrps_claim.get('context_memberships_url')
if not service_url:
logger.warning("NRPS context_memberships_url missing")
return False
return True
@@ -62,28 +60,19 @@ class LTINRPSClient:
return []
try:
print(f"NRPS claim data: {self.nrps_claim}", flush=True)
# Use PyLTI1p3's NRPS service
# Note: This requires proper configuration in the tool config
from .adapters import DjangoToolConfig
tool_config = DjangoToolConfig.from_platform(self.platform)
# Pass the entire NRPS claim as service_data, not just the URL
nrps = NamesRolesProvisioningService(tool_config, self.nrps_claim)
# Fetch members
print("Calling nrps.get_members()...", flush=True)
members = nrps.get_members()
print(f"Fetched {len(members)} members from NRPS", flush=True)
logger.info(f"Fetched {len(members)} members from NRPS for platform {self.platform.name}")
return members
except Exception as e:
print(f"NRPS fetch error: {str(e)}", flush=True)
logger.error(f"NRPS fetch error: {str(e)}", exc_info=True)
except Exception:
return []
def sync_members_to_rbac_group(self, rbac_group):
@@ -99,7 +88,6 @@ class LTINRPSClient:
members = self.fetch_members()
if not members:
logger.warning("No members fetched from NRPS")
return {'synced': 0, 'removed': 0, 'synced_at': timezone.now().isoformat()}
processed_users = set()
@@ -121,26 +109,19 @@ class LTINRPSClient:
synced_count += 1
except Exception as e:
logger.error(f"Error syncing NRPS member {member.get('user_id')}: {str(e)}")
except Exception:
continue
# Remove unenrolled users if configured
removed_count = 0
if self.platform.remove_from_groups_on_unenroll:
from rbac.models import RBACMembership
removed = RBACMembership.objects.filter(rbac_group=rbac_group).exclude(user_id__in=processed_users)
removed_count = removed.count()
removed.delete()
logger.info(f"Removed {removed_count} unenrolled users from RBAC group {rbac_group.name}")
result = {'synced': synced_count, 'removed': removed_count, 'synced_at': timezone.now().isoformat()}
logger.info(f"NRPS sync complete for {rbac_group.name}: {result}")
return result
def _get_or_create_user_from_nrps(self, member):
@@ -155,7 +136,6 @@ class LTINRPSClient:
"""
user_id = member.get('user_id')
if not user_id:
logger.warning("NRPS member missing user_id")
return None
# Get user details from NRPS data
@@ -176,27 +156,22 @@ class LTINRPSClient:
if email and user.email != email:
user.email = email
update_fields.append('email')
print(f"Updating email for {user.username}: {user.email} -> {email}", flush=True)
# Update name fields if changed
if given_name and user.first_name != given_name:
user.first_name = given_name
update_fields.append('first_name')
print(f"Updating first_name for {user.username}: {user.first_name} -> {given_name}", flush=True)
if family_name and user.last_name != family_name:
user.last_name = family_name
update_fields.append('last_name')
print(f"Updating last_name for {user.username}: {user.last_name} -> {family_name}", flush=True)
if name and user.name != name:
user.name = name
update_fields.append('name')
print(f"Updating name for {user.username}: {user.name} -> {name}", flush=True)
if update_fields:
user.save(update_fields=update_fields)
logger.info(f"Updated user details for {user.username} via NRPS sync")
# Update mapping cache
mapping_update_fields = []
@@ -225,8 +200,6 @@ class LTINRPSClient:
# Check if username exists
if User.objects.filter(username=username).exists():
import hashlib
username = f"{username}_{hashlib.md5(user_id.encode()).hexdigest()[:6]}"
# Create user
@@ -238,12 +211,8 @@ class LTINRPSClient:
# Mark email as verified
if email:
try:
from allauth.account.models import EmailAddress
EmailAddress.objects.create(user=user, email=email, verified=True, primary=True)
except Exception as e:
logger.warning(f"Could not create EmailAddress for NRPS user: {e}")
logger.info(f"Created user {username} from NRPS data")
except Exception:
pass
return user