From ced9225ee5187ddd77e62932887134ec7f23123e Mon Sep 17 00:00:00 2001 From: Andrew Scholer Date: Wed, 5 Aug 2026 09:53:51 -0700 Subject: [PATCH] Remove w2py session info from lti logins --- .../rsptx/admin_server_api/routers/lti1p1.py | 9 +-- .../rsptx/admin_server_api/routers/lti1p3.py | 73 +------------------ 2 files changed, 6 insertions(+), 76 deletions(-) diff --git a/bases/rsptx/admin_server_api/routers/lti1p1.py b/bases/rsptx/admin_server_api/routers/lti1p1.py index d20f55871..f6bd1d637 100644 --- a/bases/rsptx/admin_server_api/routers/lti1p1.py +++ b/bases/rsptx/admin_server_api/routers/lti1p1.py @@ -55,7 +55,7 @@ from rsptx.response_helpers.core import canonical_utcnow from rsptx.templates import get_shared_templates -from .lti1p3 import add_w2py_session_cookie, get_domain, get_web2py_session_cookie +from .lti1p3 import get_domain # Routing # ======= @@ -315,18 +315,13 @@ async def _login_or_create_user( async def _finalize_login(user: AuthUserValidator, redirect_to: str) -> Response: """ - Set the Runestone JWT cookie (and the web2py session cookie for pages still - served by web2py) on a redirect response. + Set the Runestone JWT cookie on a redirect response. """ response = RedirectResponse(redirect_to, status_code=status.HTTP_303_SEE_OTHER) access_token = auth_manager.create_access_token( data={"sub": user.username}, expires=datetime.timedelta(hours=12) ) auth_manager.set_cookie(response, access_token) - reg_id = user.registration_id or user.username - web2py_session_cookie = await get_web2py_session_cookie(reg_id) - if web2py_session_cookie: - add_w2py_session_cookie(response, web2py_session_cookie) rslogger.debug(f"LTI1.1 - sending user to {redirect_to}") return response diff --git a/bases/rsptx/admin_server_api/routers/lti1p3.py b/bases/rsptx/admin_server_api/routers/lti1p3.py index aa679ce66..70ac6fe03 100644 --- a/bases/rsptx/admin_server_api/routers/lti1p3.py +++ b/bases/rsptx/admin_server_api/routers/lti1p3.py @@ -22,7 +22,6 @@ import uuid import os import tldextract -from typing import Optional from zoneinfo import ZoneInfo # Third-party imports @@ -31,13 +30,11 @@ from aiohttp import ClientResponseError from fastapi import APIRouter, Depends, Request, HTTPException, status from fastapi.responses import ( - Response, JSONResponse, RedirectResponse, HTMLResponse, ) from pydantic import ValidationError -import jwt # Local imports # ------------------- @@ -81,7 +78,6 @@ ) from rsptx.db.crud.assignment import is_assignment_visible_to_students -from rsptx.configuration import settings from rsptx.logging import rslogger from rsptx.auth.session import auth_manager from rsptx.response_helpers.core import canonical_utcnow @@ -107,7 +103,6 @@ from rsptx.lti1p3.pylti1p3.exception import LtiException, LtiServiceException from rsptx.lti1p3.pylti1p3.deep_link import DeepLinkResource - # Routing # ======= router = APIRouter( @@ -149,7 +144,7 @@ def get_session_service(): async def login_or_create_user( launch: FastAPIMessageLaunch, lti_course: Lti1p3Course, course: CoursesValidator -) -> tuple[Lti1p3User, str]: +) -> Lti1p3User: """ Helper function for routes that bring an LMS user to Runestone. """ @@ -226,60 +221,7 @@ async def login_or_create_user( f"LTI1p3 - Marked {user.username} ({user.id}) as instructor in {course.id}" ) - # Finally, make sure that w2p server knows they are logged in - # need registration id. If user.registration_id is set, trust it. - # But if user was generated without a registration_id, - # fall back on the one we decided based on launch data - web2py_session_cookie = await get_web2py_session_cookie(reg_id_for_user) - - return lti_user, web2py_session_cookie - - -async def get_web2py_session_cookie(reg_id_for_user: str) -> Optional[str]: - """ - Log the user in to the legacy web2py server so pages still served by web2py - recognize the session, and return the ``session_id_runestone`` cookie to - forward to the browser. Shared by both the LTI 1.1 and 1.3 launch flows. - """ - to_encode = dict(registration_id=reg_id_for_user) - jwt_secret = settings.jwt_secret - encoded_jwt = jwt.encode(to_encode, jwt_secret, "HS256") - # Assuming we are running in docker, we need to connect out to the machine - # that is running the container. If we try to use localhost, runestone, - # or the domain name, they will resolve to an internal IP that makes sense on - # the host, not the container. - domain = get_domain() - web2py_session_cookie = None - url = f"https://{domain}/default/w2py_login?token={encoded_jwt}" - async with aiohttp.ClientSession() as session: - try: - resp = await session.get(url) - resp.raise_for_status() - cookies = resp._headers.getall("Set-Cookie") - for cookie in cookies: - if "session_id_runestone" in cookie: - rslogger.debug(f"LTI - session_id_runestone cookie set: {cookie}") - web2py_session_cookie = cookie.split(";")[ - 0 - ] # Get the cookie name=value part - except Exception as e: - rslogger.error(f"LTI - Error logging in to w2p server: {e}") - raise HTTPException( - status_code=500, - detail=f"Error logging in to w2p server '{e}'", - ) - - return web2py_session_cookie - - -def add_w2py_session_cookie(response: Response, cookie: str): - """ - Add the w2p session cookie to the response. - """ - cookie_parts = cookie.split("=") - response.set_cookie( - cookie_parts[0], cookie_parts[1], httponly=True, samesite="None", secure=True - ) + return lti_user def check_launch_data( @@ -424,9 +366,7 @@ async def launch(request: Request): course = lti_course.rs_course # identify and create/login the user - lti_user, web2py_session_cookie = await login_or_create_user( - message_launch, lti_course, course - ) + lti_user = await login_or_create_user(message_launch, lti_course, course) rs_user = lti_user.rs_user in_course = await user_in_course(rs_user.id, course.id) if not in_course: @@ -493,8 +433,6 @@ async def launch(request: Request): ) auth_manager.set_cookie(response, access_token) - add_w2py_session_cookie(response, web2py_session_cookie) - rslogger.debug(f"LTI1p3 - launch sending user to {redirect_to}") return response @@ -1040,9 +978,7 @@ async def assign_select(launch_id: str, request: Request, course=None): # Make sure we have an LTI mapping for the user. They should already exist and # be in the course, but this will make sure we have an LTI1p3User record - user_confirmation, web2py_session_cookie = await login_or_create_user( - message_launch, lti_course, course - ) + await login_or_create_user(message_launch, lti_course, course) # Now start building the response domain = get_domain() @@ -1165,7 +1101,6 @@ async def assign_select(launch_id: str, request: Request, course=None): deep_link = message_launch.get_deep_link() response_html = deep_link.output_response_form(response_list) response = HTMLResponse(content=response_html, status_code=200) - add_w2py_session_cookie(response, web2py_session_cookie) return response