Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 2 additions & 7 deletions bases/rsptx/admin_server_api/routers/lti1p1.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
# =======
Expand Down Expand Up @@ -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

Expand Down
73 changes: 4 additions & 69 deletions bases/rsptx/admin_server_api/routers/lti1p3.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
import uuid
import os
import tldextract
from typing import Optional
from zoneinfo import ZoneInfo

# Third-party imports
Expand All @@ -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
# -------------------
Expand Down Expand Up @@ -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
Expand All @@ -107,7 +103,6 @@
from rsptx.lti1p3.pylti1p3.exception import LtiException, LtiServiceException
from rsptx.lti1p3.pylti1p3.deep_link import DeepLinkResource


# Routing
# =======
router = APIRouter(
Expand Down Expand Up @@ -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:
Comment on lines 145 to +147
"""
Helper function for routes that bring an LMS user to Runestone.
"""
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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


Expand Down
Loading