mirror of
https://github.com/mediacms-io/mediacms.git
synced 2026-01-20 15:22:58 -05:00
this
This commit is contained in:
18
lti/admin.py
18
lti/admin.py
@@ -56,18 +56,16 @@ class LTIPlatformAdmin(admin.ModelAdmin):
|
||||
class LTIResourceLinkAdmin(admin.ModelAdmin):
|
||||
"""Admin for LTI Resource Links"""
|
||||
|
||||
list_display = ['context_title', 'platform', 'category_link', 'rbac_group_link', 'launch_count', 'last_launch']
|
||||
list_filter = ['platform', 'created_at', 'last_launch']
|
||||
list_display = ['context_title', 'platform', 'category_link', 'rbac_group_link']
|
||||
list_filter = ['platform']
|
||||
search_fields = ['context_id', 'context_title', 'resource_link_id']
|
||||
readonly_fields = ['created_at', 'last_launch', 'launch_count']
|
||||
actions = ['sync_course_members']
|
||||
|
||||
fieldsets = (
|
||||
('Platform', {'fields': ('platform',)}),
|
||||
('Context (Course)', {'fields': ('context_id', 'context_title', 'context_label')}),
|
||||
('Resource Link', {'fields': ('resource_link_id', 'resource_link_title')}),
|
||||
('MediaCMS Mappings', {'fields': ('category', 'rbac_group', 'media')}),
|
||||
('Metrics', {'fields': ('launch_count', 'last_launch', 'created_at'), 'classes': ('collapse',)}),
|
||||
('MediaCMS Mappings', {'fields': ('category', 'rbac_group')}),
|
||||
)
|
||||
|
||||
def category_link(self, obj):
|
||||
@@ -134,14 +132,13 @@ class LTIResourceLinkAdmin(admin.ModelAdmin):
|
||||
class LTIUserMappingAdmin(admin.ModelAdmin):
|
||||
"""Admin for LTI User Mappings"""
|
||||
|
||||
list_display = ['user_link', 'lti_user_id', 'platform', 'email', 'last_login']
|
||||
list_display = ['user_link', 'lti_user_id', 'platform', 'user_email', 'last_login']
|
||||
list_filter = ['platform', 'created_at', 'last_login']
|
||||
search_fields = ['lti_user_id', 'user__username', 'user__email', 'email']
|
||||
search_fields = ['lti_user_id', 'user__username', 'user__email']
|
||||
readonly_fields = ['created_at', 'last_login']
|
||||
|
||||
fieldsets = (
|
||||
('Mapping', {'fields': ('platform', 'lti_user_id', 'user')}),
|
||||
('User Info (Cached)', {'fields': ('email', 'given_name', 'family_name', 'name')}),
|
||||
('Timestamps', {'fields': ('created_at', 'last_login')}),
|
||||
)
|
||||
|
||||
@@ -150,6 +147,11 @@ class LTIUserMappingAdmin(admin.ModelAdmin):
|
||||
|
||||
user_link.short_description = 'MediaCMS User'
|
||||
|
||||
def user_email(self, obj):
|
||||
return obj.user.email
|
||||
|
||||
user_email.short_description = 'User Email'
|
||||
|
||||
|
||||
@admin.register(LTIRoleMapping)
|
||||
class LTIRoleMappingAdmin(admin.ModelAdmin):
|
||||
|
||||
@@ -23,12 +23,12 @@ from .models import LTIResourceLink, LTIRoleMapping, LTIUserMapping
|
||||
|
||||
# Default LTI role mappings
|
||||
DEFAULT_LTI_ROLE_MAPPINGS = {
|
||||
'Instructor': {'global_role': 'advancedUser', 'group_role': 'manager'},
|
||||
'TeachingAssistant': {'global_role': 'user', 'group_role': 'contributor'},
|
||||
'Learner': {'global_role': 'user', 'group_role': 'member'},
|
||||
'Student': {'global_role': 'user', 'group_role': 'member'},
|
||||
'Administrator': {'global_role': 'manager', 'group_role': 'manager'},
|
||||
'Faculty': {'global_role': 'advancedUser', 'group_role': 'manager'},
|
||||
'Instructor': {'global_role': '', 'group_role': 'manager'},
|
||||
'TeachingAssistant': {'global_role': '', 'group_role': 'contributor'},
|
||||
'Learner': {'global_role': '', 'group_role': 'member'},
|
||||
'Student': {'global_role': '', 'group_role': 'member'},
|
||||
'Administrator': {'global_role': '', 'group_role': 'manager'},
|
||||
'Faculty': {'global_role': '', 'group_role': 'manager'},
|
||||
}
|
||||
|
||||
|
||||
@@ -107,7 +107,7 @@ def provision_lti_user(platform, claims):
|
||||
pass
|
||||
|
||||
# Create mapping
|
||||
LTIUserMapping.objects.create(platform=platform, lti_user_id=lti_user_id, user=user, email=email, given_name=given_name, family_name=family_name, name=name)
|
||||
LTIUserMapping.objects.create(platform=platform, lti_user_id=lti_user_id, user=user)
|
||||
|
||||
return user
|
||||
|
||||
@@ -208,11 +208,6 @@ def provision_lti_context(platform, claims, resource_link_id):
|
||||
},
|
||||
)
|
||||
|
||||
# Update launch metrics
|
||||
resource_link.launch_count += 1
|
||||
resource_link.last_launch = timezone.now()
|
||||
resource_link.save(update_fields=['launch_count', 'last_launch'])
|
||||
|
||||
if not created:
|
||||
# Update relationships if needed
|
||||
if resource_link.category != category:
|
||||
|
||||
@@ -53,21 +53,12 @@ class Migration(migrations.Migration):
|
||||
('context_label', models.CharField(blank=True, help_text='Course short name/code', max_length=100)),
|
||||
('resource_link_id', models.CharField(db_index=True, help_text='LTI resource link ID', max_length=255)),
|
||||
('resource_link_title', models.CharField(blank=True, help_text='Resource link title', max_length=255)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('last_launch', models.DateTimeField(auto_now=True)),
|
||||
('launch_count', models.IntegerField(default=0, help_text='Number of times this resource has been launched')),
|
||||
(
|
||||
'category',
|
||||
models.ForeignKey(
|
||||
blank=True, help_text='Mapped MediaCMS category', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='lti_resource_links', to='files.category'
|
||||
),
|
||||
),
|
||||
(
|
||||
'media',
|
||||
models.ForeignKey(
|
||||
blank=True, help_text='Specific media for embedded links', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='lti_resource_links', to='files.media'
|
||||
),
|
||||
),
|
||||
('platform', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='resource_links', to='lti.ltiplatform')),
|
||||
(
|
||||
'rbac_group',
|
||||
@@ -147,10 +138,6 @@ class Migration(migrations.Migration):
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('lti_user_id', models.CharField(db_index=True, help_text="LTI 'sub' claim (unique user identifier from platform)", max_length=255)),
|
||||
('email', models.EmailField(blank=True, max_length=254)),
|
||||
('given_name', models.CharField(blank=True, max_length=100)),
|
||||
('family_name', models.CharField(blank=True, max_length=100)),
|
||||
('name', models.CharField(blank=True, max_length=255)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('last_login', models.DateTimeField(auto_now=True)),
|
||||
('platform', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='user_mappings', to='lti.ltiplatform')),
|
||||
|
||||
@@ -4,27 +4,22 @@ from django.db import models
|
||||
class LTIPlatform(models.Model):
|
||||
"""LTI 1.3 Platform (Moodle instance) configuration"""
|
||||
|
||||
# Basic identification
|
||||
name = models.CharField(max_length=255, unique=True, help_text="Platform name (e.g., 'Moodle Production')")
|
||||
platform_id = models.URLField(help_text="Platform's issuer URL (iss claim, e.g., https://moodle.example.com)")
|
||||
client_id = models.CharField(max_length=255, help_text="Client ID provided by the platform")
|
||||
|
||||
# OIDC endpoints
|
||||
auth_login_url = models.URLField(help_text="OIDC authentication endpoint URL")
|
||||
auth_token_url = models.URLField(help_text="OAuth2 token endpoint URL")
|
||||
auth_audience = models.URLField(blank=True, null=True, help_text="OAuth2 audience (optional)")
|
||||
|
||||
# JWK configuration
|
||||
key_set_url = models.URLField(help_text="Platform's public JWK Set URL")
|
||||
key_set = models.JSONField(blank=True, null=True, help_text="Cached JWK Set (auto-fetched)")
|
||||
key_set_updated = models.DateTimeField(null=True, blank=True, help_text="Last time JWK Set was fetched")
|
||||
|
||||
# Deployment & features
|
||||
deployment_ids = models.JSONField(default=list, help_text="List of deployment IDs for this platform")
|
||||
enable_nrps = models.BooleanField(default=True, help_text="Enable Names and Role Provisioning Service")
|
||||
enable_deep_linking = models.BooleanField(default=True, help_text="Enable Deep Linking 2.0")
|
||||
|
||||
# Auto-provisioning settings
|
||||
auto_create_categories = models.BooleanField(default=True, help_text="Automatically create categories for courses")
|
||||
auto_create_users = models.BooleanField(default=True, help_text="Automatically create users on first launch")
|
||||
auto_sync_roles = models.BooleanField(default=True, help_text="Automatically sync user roles from LTI")
|
||||
@@ -73,14 +68,8 @@ class LTIResourceLink(models.Model):
|
||||
|
||||
# MediaCMS mappings
|
||||
category = models.ForeignKey('files.Category', on_delete=models.SET_NULL, null=True, blank=True, related_name='lti_resource_links', help_text="Mapped MediaCMS category")
|
||||
media = models.ForeignKey('files.Media', on_delete=models.SET_NULL, null=True, blank=True, related_name='lti_resource_links', help_text="Specific media for embedded links")
|
||||
rbac_group = models.ForeignKey('rbac.RBACGroup', on_delete=models.SET_NULL, null=True, blank=True, related_name='lti_resource_links', help_text="RBAC group for course members")
|
||||
|
||||
# Metrics
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
last_launch = models.DateTimeField(auto_now=True)
|
||||
launch_count = models.IntegerField(default=0, help_text="Number of times this resource has been launched")
|
||||
|
||||
class Meta:
|
||||
verbose_name = 'LTI Resource Link'
|
||||
verbose_name_plural = 'LTI Resource Links'
|
||||
@@ -101,13 +90,6 @@ class LTIUserMapping(models.Model):
|
||||
lti_user_id = models.CharField(max_length=255, db_index=True, help_text="LTI 'sub' claim (unique user identifier from platform)")
|
||||
user = models.ForeignKey('users.User', on_delete=models.CASCADE, related_name='lti_mappings')
|
||||
|
||||
# User info from LTI (cached)
|
||||
email = models.EmailField(blank=True)
|
||||
given_name = models.CharField(max_length=100, blank=True)
|
||||
family_name = models.CharField(max_length=100, blank=True)
|
||||
name = models.CharField(max_length=255, blank=True)
|
||||
|
||||
# Timestamps
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
last_login = models.DateTimeField(auto_now=True)
|
||||
|
||||
@@ -127,7 +109,7 @@ class LTIUserMapping(models.Model):
|
||||
class LTIRoleMapping(models.Model):
|
||||
"""Maps LTI institutional roles to MediaCMS roles"""
|
||||
|
||||
GLOBAL_ROLE_CHOICES = [('user', 'Authenticated User'), ('advancedUser', 'Advanced User'), ('editor', 'MediaCMS Editor'), ('manager', 'MediaCMS Manager'), ('admin', 'MediaCMS Administrator')]
|
||||
GLOBAL_ROLE_CHOICES = [('advancedUser', 'Advanced User'), ('editor', 'MediaCMS Editor'), ('manager', 'MediaCMS Manager'), ('admin', 'MediaCMS Administrator')]
|
||||
|
||||
GROUP_ROLE_CHOICES = [('member', 'Member'), ('contributor', 'Contributor'), ('manager', 'Manager')]
|
||||
|
||||
|
||||
103
lti/views.py
103
lti/views.py
@@ -10,7 +10,8 @@ Implements the LTI 1.3 / LTI Advantage flow:
|
||||
- Manual NRPS Sync
|
||||
"""
|
||||
|
||||
import json
|
||||
import logging
|
||||
import traceback
|
||||
import uuid
|
||||
from urllib.parse import urlencode
|
||||
|
||||
@@ -41,9 +42,11 @@ from .handlers import (
|
||||
provision_lti_user,
|
||||
validate_lti_session,
|
||||
)
|
||||
from .models import LTILaunchLog, LTIPlatform, LTIResourceLink, LTIUserMapping
|
||||
from .models import LTILaunchLog, LTIPlatform, LTIResourceLink
|
||||
from .services import LTINRPSClient
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def get_client_ip(request):
|
||||
"""Get client IP address from request"""
|
||||
@@ -71,7 +74,13 @@ class OIDCLoginView(View):
|
||||
|
||||
def handle_oidc_login(self, request):
|
||||
"""Handle OIDC login initiation"""
|
||||
print("=== OIDC Login Started ===", flush=True)
|
||||
logger.info("=== OIDC Login Started ===")
|
||||
try:
|
||||
# Get all request parameters for debugging
|
||||
all_params = dict(request.GET.items()) if request.method == 'GET' else dict(request.POST.items())
|
||||
print(f"All OIDC request params: {all_params}", flush=True)
|
||||
|
||||
# Get target_link_uri and other OIDC params
|
||||
target_link_uri = request.GET.get('target_link_uri') or request.POST.get('target_link_uri')
|
||||
iss = request.GET.get('iss') or request.POST.get('iss')
|
||||
@@ -79,14 +88,23 @@ class OIDCLoginView(View):
|
||||
login_hint = request.GET.get('login_hint') or request.POST.get('login_hint')
|
||||
lti_message_hint = request.GET.get('lti_message_hint') or request.POST.get('lti_message_hint')
|
||||
|
||||
print(f"OIDC params - iss: {iss}, client_id: {client_id}, target: {target_link_uri}", flush=True)
|
||||
print(f"login_hint: {login_hint}, lti_message_hint: {lti_message_hint}", flush=True)
|
||||
logger.info(f"OIDC params - iss: {iss}, client_id: {client_id}, target: {target_link_uri}")
|
||||
|
||||
if not all([target_link_uri, iss, client_id]):
|
||||
print("ERROR: Missing OIDC parameters", flush=True)
|
||||
logger.error("Missing OIDC parameters")
|
||||
return JsonResponse({'error': 'Missing required OIDC parameters'}, status=400)
|
||||
|
||||
# Get platform configuration
|
||||
platform = get_object_or_404(LTIPlatform, platform_id=iss, client_id=client_id, active=True)
|
||||
print(f"Found platform: {platform.name}", flush=True)
|
||||
logger.info(f"Found platform: {platform.name}")
|
||||
|
||||
# Create tool config for this platform
|
||||
tool_config = DjangoToolConfig.from_platform(platform)
|
||||
print(f"Tool config: {tool_config._config}", flush=True)
|
||||
|
||||
# Wrap Django request for PyLTI1p3
|
||||
lti_request = DjangoRequest(request)
|
||||
@@ -95,15 +113,28 @@ class OIDCLoginView(View):
|
||||
session_service = DjangoSessionService(request)
|
||||
cookie_service = DjangoSessionService(request) # Using same service for cookies
|
||||
|
||||
print("Creating OIDCLogin...", flush=True)
|
||||
oidc_login = OIDCLogin(lti_request, tool_config, session_service=session_service, cookie_service=cookie_service)
|
||||
print("OIDCLogin created successfully", flush=True)
|
||||
|
||||
# Redirect to platform's authorization endpoint
|
||||
print(f"Target link URI: {target_link_uri}", flush=True)
|
||||
print(f"Auth login URL: {platform.auth_login_url}", flush=True)
|
||||
|
||||
try:
|
||||
print("Calling enable_check_cookies()...", flush=True)
|
||||
oidc_with_cookies = oidc_login.enable_check_cookies()
|
||||
print(f"Calling redirect({target_link_uri})...", flush=True)
|
||||
redirect_url = oidc_with_cookies.redirect(target_link_uri)
|
||||
print(f"Redirect returned: '{redirect_url}'", flush=True)
|
||||
print(f"OIDC redirect URL type: {type(redirect_url)}", flush=True)
|
||||
print(f"OIDC redirecting to: {redirect_url}", flush=True)
|
||||
logger.info(f"OIDC redirecting to: {redirect_url}")
|
||||
|
||||
if not redirect_url:
|
||||
print("PyLTI1p3 redirect failed, building URL manually...", flush=True)
|
||||
# Manual OIDC redirect construction with all required OAuth 2.0 parameters
|
||||
|
||||
state = str(uuid.uuid4())
|
||||
nonce = str(uuid.uuid4())
|
||||
|
||||
@@ -128,14 +159,20 @@ class OIDCLoginView(View):
|
||||
params['lti_message_hint'] = lti_message_hint
|
||||
|
||||
redirect_url = f"{platform.auth_login_url}?{urlencode(params)}"
|
||||
print(f"Manually built redirect URL: {redirect_url}", flush=True)
|
||||
|
||||
return HttpResponseRedirect(redirect_url)
|
||||
except Exception:
|
||||
except Exception as e:
|
||||
print(f"ERROR in OIDC redirect: {str(e)}", flush=True)
|
||||
|
||||
traceback.print_exc()
|
||||
raise
|
||||
|
||||
except LtiException as e:
|
||||
logger.error(f"LTI OIDC Login Error: {str(e)}")
|
||||
return render(request, 'lti/launch_error.html', {'error': 'OIDC Login Failed', 'message': str(e)}, status=400)
|
||||
except Exception:
|
||||
except Exception as e:
|
||||
logger.error(f"OIDC Login Error: {str(e)}", exc_info=True)
|
||||
return JsonResponse({'error': 'Internal server error during OIDC login'}, status=500)
|
||||
|
||||
|
||||
@@ -150,6 +187,8 @@ class LaunchView(View):
|
||||
|
||||
def post(self, request):
|
||||
"""Handle LTI launch with JWT validation"""
|
||||
print("=== LTI Launch Started ===", flush=True)
|
||||
logger.info("=== LTI Launch Started ===")
|
||||
platform = None
|
||||
user = None
|
||||
error_message = ''
|
||||
@@ -162,12 +201,15 @@ class LaunchView(View):
|
||||
raise ValueError("Missing id_token in launch request")
|
||||
|
||||
# Decode JWT to get issuer (without validation first)
|
||||
|
||||
unverified = jwt.decode(id_token, options={"verify_signature": False})
|
||||
iss = unverified.get('iss')
|
||||
aud = unverified.get('aud')
|
||||
|
||||
# Get platform
|
||||
platform = get_object_or_404(LTIPlatform, platform_id=iss, client_id=aud, active=True)
|
||||
print(f"Launch from platform: {platform.name}", flush=True)
|
||||
logger.info(f"Launch from platform: {platform.name}")
|
||||
|
||||
# Create tool config
|
||||
tool_config = DjangoToolConfig.from_platform(platform)
|
||||
@@ -191,28 +233,6 @@ class LaunchView(View):
|
||||
launch_data = message_launch.get_launch_data()
|
||||
claims = self.sanitize_claims(launch_data)
|
||||
|
||||
# Print all JWT information for debugging
|
||||
print("\n" + "=" * 80)
|
||||
print("LTI JWT DECRYPTED - ALL CLAIMS:")
|
||||
print("=" * 80)
|
||||
print(f"Issuer (iss): {launch_data.get('iss')}")
|
||||
print(f"Subject (sub): {launch_data.get('sub')}")
|
||||
print(f"Email: {launch_data.get('email')}")
|
||||
print(f"Given Name: {launch_data.get('given_name')}")
|
||||
print(f"Family Name: {launch_data.get('family_name')}")
|
||||
print(f"Full Name: {launch_data.get('name')}")
|
||||
print(f"Roles: {launch_data.get('https://purl.imsglobal.org/spec/lti/claim/roles')}")
|
||||
context = launch_data.get('https://purl.imsglobal.org/spec/lti/claim/context', {})
|
||||
print(f"Context ID: {context.get('id')}")
|
||||
print(f"Context Title: {context.get('title')}")
|
||||
print(f"Context Label: {context.get('label')}")
|
||||
resource_link = launch_data.get('https://purl.imsglobal.org/spec/lti/claim/resource_link', {})
|
||||
print(f"Resource Link ID: {resource_link.get('id')}")
|
||||
print(f"Resource Link Title: {resource_link.get('title')}")
|
||||
print("\nFull Launch Data:")
|
||||
print(json.dumps(launch_data, indent=2))
|
||||
print("=" * 80 + "\n")
|
||||
|
||||
# Extract key claims
|
||||
sub = launch_data.get('sub')
|
||||
resource_link = launch_data.get('https://purl.imsglobal.org/spec/lti/claim/resource_link', {})
|
||||
@@ -227,10 +247,16 @@ class LaunchView(View):
|
||||
return self.handle_deep_linking_launch(request, message_launch, platform, launch_data)
|
||||
|
||||
# Provision user
|
||||
print(f"Provisioning user, sub: {sub}", flush=True)
|
||||
logger.info(f"Provisioning user, sub: {sub}")
|
||||
if platform.auto_create_users:
|
||||
user = provision_lti_user(platform, launch_data)
|
||||
print(f"User provisioned: {user.username}", flush=True)
|
||||
logger.info(f"User provisioned: {user.username}")
|
||||
else:
|
||||
# Must find existing user
|
||||
from .models import LTIUserMapping
|
||||
|
||||
mapping = LTIUserMapping.objects.filter(platform=platform, lti_user_id=sub).first()
|
||||
if not mapping:
|
||||
raise ValueError("User auto-creation disabled and no existing mapping found")
|
||||
@@ -238,29 +264,38 @@ class LaunchView(View):
|
||||
|
||||
# Provision context (category + RBAC group)
|
||||
if 'https://purl.imsglobal.org/spec/lti/claim/context' in launch_data:
|
||||
logger.info("Provisioning context...")
|
||||
category, rbac_group, resource_link_obj = provision_lti_context(platform, launch_data, resource_link_id)
|
||||
logger.info(f"Context provisioned: category={category.title if category else None}")
|
||||
|
||||
# Apply roles
|
||||
apply_lti_roles(user, platform, roles, rbac_group)
|
||||
logger.info(f"Roles applied: {roles}")
|
||||
else:
|
||||
# No context - might be a direct media embed
|
||||
resource_link_obj = None
|
||||
|
||||
# Create session
|
||||
create_lti_session(request, user, message_launch, platform)
|
||||
logger.info("LTI session created")
|
||||
|
||||
# Log successful launch
|
||||
LTILaunchLog.objects.create(platform=platform, user=user, resource_link=resource_link_obj, launch_type='resource_link', success=True, claims=claims, ip_address=get_client_ip(request))
|
||||
logger.info("Launch logged")
|
||||
|
||||
# Determine where to redirect
|
||||
redirect_url = self.determine_redirect(launch_data, resource_link_obj)
|
||||
print(f"=== Launch Success - Redirecting to: {redirect_url} ===", flush=True)
|
||||
logger.info(f"=== Launch Success - Redirecting to: {redirect_url} ===")
|
||||
|
||||
return HttpResponseRedirect(redirect_url)
|
||||
|
||||
except LtiException as e:
|
||||
error_message = f"LTI Launch Error: {str(e)}"
|
||||
logger.error(error_message)
|
||||
except Exception as e:
|
||||
error_message = f"Launch Error: {str(e)}"
|
||||
logger.error(error_message, exc_info=True)
|
||||
|
||||
# Log failed launch
|
||||
if platform:
|
||||
@@ -337,15 +372,24 @@ class MyMediaLTIView(View):
|
||||
|
||||
def get(self, request):
|
||||
"""Display my media page"""
|
||||
print(f"=== My Media LTI View - User: {request.user} ===", flush=True)
|
||||
logger.info(f"=== My Media LTI View - User: {request.user} ===")
|
||||
|
||||
# Validate LTI session
|
||||
lti_session = validate_lti_session(request)
|
||||
print(f"LTI session valid: {bool(lti_session)}", flush=True)
|
||||
logger.info(f"LTI session valid: {bool(lti_session)}")
|
||||
|
||||
if not lti_session:
|
||||
print("ERROR: LTI session validation failed", flush=True)
|
||||
logger.error("LTI session validation failed")
|
||||
return JsonResponse({'error': 'Not authenticated via LTI'}, status=403)
|
||||
|
||||
# Redirect to user's profile page
|
||||
# The existing user profile page is already iframe-compatible
|
||||
profile_url = f"/user/{request.user.username}"
|
||||
print(f"Redirecting to profile: {profile_url}", flush=True)
|
||||
logger.info(f"Redirecting to profile: {profile_url}")
|
||||
return HttpResponseRedirect(profile_url)
|
||||
|
||||
|
||||
@@ -370,9 +414,9 @@ class EmbedMediaLTIView(View):
|
||||
can_view = True
|
||||
else:
|
||||
can_view = False
|
||||
else:
|
||||
# Fall back to public state check
|
||||
can_view = media.state == 'public'
|
||||
|
||||
if media.state in ["public", "unlisted"]:
|
||||
can_view = True
|
||||
|
||||
if not can_view:
|
||||
return JsonResponse({'error': 'Access denied', 'message': 'You do not have permission to view this media'}, status=403)
|
||||
@@ -440,4 +484,5 @@ class ManualSyncView(APIView):
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"NRPS sync error: {str(e)}", exc_info=True)
|
||||
return Response({'error': 'Sync failed', 'message': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||
|
||||
Reference in New Issue
Block a user