mirror of
https://github.com/mediacms-io/mediacms.git
synced 2026-02-04 06:22:59 -05:00
xif
This commit is contained in:
61
lti/views.py
61
lti/views.py
@@ -298,7 +298,11 @@ class LaunchView(View):
|
|||||||
|
|
||||||
# Clear retry counter on successful launch
|
# Clear retry counter on successful launch
|
||||||
if 'lti_retry_count' in request.session:
|
if 'lti_retry_count' in request.session:
|
||||||
|
retry_attempts = request.session['lti_retry_count']
|
||||||
del request.session['lti_retry_count']
|
del request.session['lti_retry_count']
|
||||||
|
logger.error(f"========== [LTI RETRY SUCCESS] Launch succeeded after {retry_attempts} retry attempt(s) ==========")
|
||||||
|
else:
|
||||||
|
logger.error("[LTI LAUNCH SUCCESS] Launch succeeded on first attempt (no retry needed)")
|
||||||
|
|
||||||
LTILaunchLog.objects.create(platform=platform, user=user, resource_link=resource_link_obj, launch_type='resource_link', success=True, claims=claims)
|
LTILaunchLog.objects.create(platform=platform, user=user, resource_link=resource_link_obj, launch_type='resource_link', success=True, claims=claims)
|
||||||
|
|
||||||
@@ -311,16 +315,10 @@ class LaunchView(View):
|
|||||||
error_message = str(e)
|
error_message = str(e)
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
|
|
||||||
# Log state errors but don't retry - retry causes Moodle launch data expiration issues
|
# Attempt automatic retry for state errors (handles concurrent launches and session issues)
|
||||||
if "State not found" in error_message or "state not found" in error_message.lower():
|
if "State not found" in error_message or "state not found" in error_message.lower():
|
||||||
logger.error("[LTI LAUNCH] State not found - this indicates session persistence issues")
|
logger.warning("[LTI LAUNCH] State not found error detected, attempting recovery")
|
||||||
error_message = (
|
return self.handle_state_not_found(request, platform)
|
||||||
"Session authentication failed. This usually resolves by refreshing the page. "
|
|
||||||
"If the issue persists, try:\n"
|
|
||||||
"1. Clearing browser cookies\n"
|
|
||||||
"2. Disabling browser tracking protection for this site\n"
|
|
||||||
"3. Using a different browser"
|
|
||||||
)
|
|
||||||
except Exception as e: # noqa
|
except Exception as e: # noqa
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
|
|
||||||
@@ -370,18 +368,20 @@ class LaunchView(View):
|
|||||||
try:
|
try:
|
||||||
# Check retry count to prevent infinite loops
|
# Check retry count to prevent infinite loops
|
||||||
retry_count = request.session.get('lti_retry_count', 0)
|
retry_count = request.session.get('lti_retry_count', 0)
|
||||||
MAX_RETRIES = 2
|
MAX_RETRIES = 5 # Increased for concurrent launches (e.g., multiple videos on same page)
|
||||||
|
|
||||||
|
logger.error(f"========== [LTI RETRY START] Attempt #{retry_count + 1} of {MAX_RETRIES} ==========")
|
||||||
|
|
||||||
if retry_count >= MAX_RETRIES:
|
if retry_count >= MAX_RETRIES:
|
||||||
logger.error(f"[LTI RETRY] Max retries ({MAX_RETRIES}) exceeded for state recovery")
|
logger.error(f"========== [LTI RETRY FAILED] Max retries ({MAX_RETRIES}) exceeded ==========")
|
||||||
return render(
|
return render(
|
||||||
request,
|
request,
|
||||||
'lti/launch_error.html',
|
'lti/launch_error.html',
|
||||||
{
|
{
|
||||||
'error': 'Authentication Failed',
|
'error': 'Authentication Failed',
|
||||||
'message': (
|
'message': (
|
||||||
'Unable to establish a secure session. This may be due to browser '
|
'Unable to establish a secure session after multiple attempts. '
|
||||||
'cookie settings or privacy features. Please try:\n\n'
|
'This may be due to browser cookie settings or privacy features. Please try:\n\n'
|
||||||
'1. Enabling cookies for this site\n'
|
'1. Enabling cookies for this site\n'
|
||||||
'2. Disabling tracking protection for this site\n'
|
'2. Disabling tracking protection for this site\n'
|
||||||
'3. Using a different browser\n'
|
'3. Using a different browser\n'
|
||||||
@@ -396,14 +396,19 @@ class LaunchView(View):
|
|||||||
id_token = request.POST.get('id_token')
|
id_token = request.POST.get('id_token')
|
||||||
state = request.POST.get('state')
|
state = request.POST.get('state')
|
||||||
|
|
||||||
|
logger.error("[LTI RETRY] Extracting parameters from failed launch request")
|
||||||
|
logger.error(f"[LTI RETRY] Has id_token: {bool(id_token)}")
|
||||||
|
logger.error(f"[LTI RETRY] State value: {state[:50]}..." if state else "[LTI RETRY] No state")
|
||||||
|
|
||||||
if not id_token:
|
if not id_token:
|
||||||
raise ValueError("No id_token available for retry")
|
raise ValueError("No id_token available for retry")
|
||||||
|
|
||||||
# Decode state to extract lti_message_hint (encoded during OIDC login)
|
# Decode state to extract lti_message_hint and media_token (encoded during OIDC login)
|
||||||
import base64
|
import base64
|
||||||
import json as json_module
|
import json as json_module
|
||||||
|
|
||||||
lti_message_hint_from_state = None
|
lti_message_hint_from_state = None
|
||||||
|
media_token_from_retry = None
|
||||||
try:
|
try:
|
||||||
# Add padding if needed for base64 decode
|
# Add padding if needed for base64 decode
|
||||||
padding = 4 - (len(state) % 4)
|
padding = 4 - (len(state) % 4)
|
||||||
@@ -415,12 +420,15 @@ class LaunchView(View):
|
|||||||
state_decoded = base64.urlsafe_b64decode(state_padded.encode()).decode()
|
state_decoded = base64.urlsafe_b64decode(state_padded.encode()).decode()
|
||||||
state_data = json_module.loads(state_decoded)
|
state_data = json_module.loads(state_decoded)
|
||||||
lti_message_hint_from_state = state_data.get('hint')
|
lti_message_hint_from_state = state_data.get('hint')
|
||||||
|
media_token_from_retry = state_data.get('media_token')
|
||||||
logger.error(f"[LTI RETRY] Decoded state, found hint: {bool(lti_message_hint_from_state)}")
|
logger.error(f"[LTI RETRY] Decoded state, found hint: {bool(lti_message_hint_from_state)}")
|
||||||
|
logger.error(f"[LTI RETRY] Decoded state, found media_token: {bool(media_token_from_retry)}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"[LTI RETRY] Could not decode state (might be plain UUID): {e}")
|
logger.error(f"[LTI RETRY] Could not decode state (might be plain UUID): {e}")
|
||||||
# State might be a plain UUID from older code, that's OK
|
# State might be a plain UUID from older code, that's OK
|
||||||
|
|
||||||
# Decode JWT to extract issuer and target info (no verification needed for this)
|
# Decode JWT to extract issuer and target info (no verification needed for this)
|
||||||
|
logger.error("[LTI RETRY] Decoding JWT to extract launch parameters")
|
||||||
unverified = jwt.decode(id_token, options={"verify_signature": False})
|
unverified = jwt.decode(id_token, options={"verify_signature": False})
|
||||||
|
|
||||||
iss = unverified.get('iss')
|
iss = unverified.get('iss')
|
||||||
@@ -430,6 +438,8 @@ class LaunchView(View):
|
|||||||
# Get login_hint and lti_message_hint if available
|
# Get login_hint and lti_message_hint if available
|
||||||
login_hint = request.POST.get('login_hint') or unverified.get('sub')
|
login_hint = request.POST.get('login_hint') or unverified.get('sub')
|
||||||
|
|
||||||
|
logger.error(f"[LTI RETRY] Extracted: iss={iss}, aud={aud}, target={target_link_uri}, login_hint={login_hint}")
|
||||||
|
|
||||||
if not all([iss, aud, target_link_uri]):
|
if not all([iss, aud, target_link_uri]):
|
||||||
raise ValueError("Missing required parameters for OIDC retry")
|
raise ValueError("Missing required parameters for OIDC retry")
|
||||||
|
|
||||||
@@ -444,7 +454,10 @@ class LaunchView(View):
|
|||||||
request.session['lti_retry_count'] = retry_count + 1
|
request.session['lti_retry_count'] = retry_count + 1
|
||||||
request.session.modified = True
|
request.session.modified = True
|
||||||
|
|
||||||
logger.warning(f"[LTI RETRY] State not found, attempting retry #{retry_count + 1}. " f"Platform: {platform.name}, State: {state}, Target: {target_link_uri}")
|
logger.error(f"========== [LTI RETRY] Attempting retry #{retry_count + 1} of {MAX_RETRIES} ==========")
|
||||||
|
logger.error(f"[LTI RETRY] Platform: {platform.name}")
|
||||||
|
logger.error(f"[LTI RETRY] Original State: {state[:50]}...")
|
||||||
|
logger.error(f"[LTI RETRY] Target: {target_link_uri}")
|
||||||
|
|
||||||
# Build OIDC login URL with all parameters
|
# Build OIDC login URL with all parameters
|
||||||
oidc_login_url = request.build_absolute_uri(reverse('lti:oidc_login'))
|
oidc_login_url = request.build_absolute_uri(reverse('lti:oidc_login'))
|
||||||
@@ -456,19 +469,27 @@ class LaunchView(View):
|
|||||||
'login_hint': login_hint,
|
'login_hint': login_hint,
|
||||||
}
|
}
|
||||||
|
|
||||||
# Use lti_message_hint decoded from state parameter
|
# DON'T pass lti_message_hint in retry - it's single-use and causes Moodle 404
|
||||||
|
# The launchid in lti_message_hint is only valid for one authentication flow
|
||||||
|
# Moodle will handle the retry without the hint
|
||||||
if lti_message_hint_from_state:
|
if lti_message_hint_from_state:
|
||||||
params['lti_message_hint'] = lti_message_hint_from_state
|
logger.error(f"[LTI RETRY] NOT passing stale lti_message_hint: {lti_message_hint_from_state}")
|
||||||
logger.error(f"[LTI RETRY] Using lti_message_hint from state: {lti_message_hint_from_state}")
|
|
||||||
else:
|
else:
|
||||||
logger.error("[LTI RETRY] No lti_message_hint available - Moodle may reject retry")
|
logger.error("[LTI RETRY] No lti_message_hint in state")
|
||||||
|
|
||||||
|
# Pass media_token in retry for filter launches (our custom parameter, not Moodle's)
|
||||||
|
if media_token_from_retry:
|
||||||
|
params['media_token'] = media_token_from_retry
|
||||||
|
logger.error(f"[LTI RETRY] Using media_token from state: {media_token_from_retry}")
|
||||||
|
|
||||||
# Add retry indicator
|
# Add retry indicator
|
||||||
params['retry'] = retry_count + 1
|
params['retry'] = retry_count + 1
|
||||||
|
|
||||||
redirect_url = f"{oidc_login_url}?{urlencode(params)}"
|
redirect_url = f"{oidc_login_url}?{urlencode(params)}"
|
||||||
|
|
||||||
logger.error(f"[LTI RETRY] Redirecting to OIDC login: {redirect_url}")
|
logger.error("[LTI RETRY] Building retry OIDC login URL")
|
||||||
|
logger.error(f"[LTI RETRY] Retry params: {params}")
|
||||||
|
logger.error(f"========== [LTI RETRY REDIRECT] Redirecting to: {redirect_url} ==========")
|
||||||
|
|
||||||
return HttpResponseRedirect(redirect_url)
|
return HttpResponseRedirect(redirect_url)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user