openwebwork / webwork2

Course management front end for WeBWorK
http://webwork.maa.org/wiki/Main_Page
Other
143 stars 165 forks source link

Add content item selection for LTI. #2322

Closed drgrice1 closed 5 months ago

drgrice1 commented 6 months ago

See https://www.imsglobal.org/specs/lticiv1p0/specification and https://www.imsglobal.org/spec/lti-dl/v2p0.

This will be needed for Moodle 4.03 (and beyond?) but might be nice for other LMS's as well. If the new content selection endpoints (https://your.webwork2.server.domain/ltiadvanced/content_selection for LTI 1.1 and https://your.webwork2.server.domain/ltiadvantage/content_selection for LTI 1.3) are added to the LMS external tool configuration, then instructors can select from a list of homework assignments in the webwork2 course, and it will add the correct link(s) to the LMS. For Moodle (at least for 4.1 and up) you can add links to all assignments with a few clicks of the mouse. Canvas is not quite as nice as it only allows selection one assignment at a time (at least as far as I have been able to figure out at this point). As usual, I am not sure what D2L can do.

In order for this to work each webwork2 course must set the new LTI configuration variable $LMSCourseName in its course.conf file. This is the name of the LMS course that is associated with the webwork2 course. This must be done because these content item requests do not have the webwork2 course name in the link like the usual webwork2 course or assignment links.

In order to support multiple LMS's with a single webwork2 server there is a little more that must be done for LTI 1.1, particularly if there is the possibility that courses from different LMS's might have the same LMS course name. In this case each LMS must use a different consumer key for its external tool for the webwork2 server, and the $LTI{v1p1}{ConsumerKey} must be set in each courses course.conf file. For LTI 1.3 no additional configuration is needed because the authentication parameters already uniquely identify an LMS external tool.

Note that none of this new configuration is needed in the case that the webwork2 server administrator does not want instructors to be able to utilize the new content item selection routes. The $LTICourseName does not need to be set in the course.conf files, the $LTI{v1p1}{ConsumerKey} does not need to be set, and both versions of LTI will continue to work as they did before.

Now for implementation details.

For LTI 1.1 nothing is changed in the way that authentication works unless the LMS sends the user to the new ltiadvanced_content_selection route. In that case the correct course is identified by iterating through the webwork2 courses on the server, loading the course environment for each one, and finding the one with the correct LMS course name (and ConsumerKey if there is more than one with the same LMS course name). This is probably rather inefficient, but fortunately this is only done for content item selection requests.

For LTI 1.3 things needed to be reworked quite a bit more. All of the hackery that was previously implemented to pass the course id, state, and nonce from the login request to the launch request using the key table had to go. Instead there is now a non-native table (the lti_launch_data table) that this information is stored in, identified by the state generated from the parameters provided by the LMS in the login request which are unique to the LMS user and request. This table can be accessed without a webwork2 course id since it is non-native. This allows for the state to be retrieved from the database in the launch request, and the JWT to be decoded earlier in that request (previously this was done in the authentication step, and now it is done before that in the webwork2 dispatch step). It needs to be done at dispatch time because the JWT contains the information that determines if it is a deep linking request or not. For both the login and launch requests a webwork2 course is identified by iterating through courses, loading course environments, and finding an LTI 1.3 tool configuration that matches that in the request. Again this is probably rather inefficient, but again this is only done for content item selection requests.

Note that the new non-native table will be automatically created the first time that you sign in to the admin course. This is how it has always worked for non-native tables.

There is a new hook that can be utilized by content generator modules to modify parameters, route captures, and do other things early in the dispatch phase before a course id has been determined and a course environment and database instance is initialized. The hook can be utilized by the content generator module providing an initializeRoute method. This is just clean up really. A lot of route specific special cases were starting to build up in the dispatch method in lib/WeBWork.pm, and this makes it possible for that code to be moved out of there.

drgrice1 commented 5 months ago

We may need to check with @dlglin. When we tested this with his D2L and my local server, it was sending the context id.

dlglin commented 5 months ago

In the deployment settings they probably need to check the "Org Unit Information" box. Screenshot 2024-03-26 at 2 35 41 PM Screenshot 2024-03-26 at 2 35 32 PM

Alex-Jordan commented 5 months ago

It's feeling like one step forward, two steps backwards. @dlglin's screenshots helped our D2L team toggle things to get the org unit information in there, getting me past the previous screenshot. However now when I activate the tool from the LMS, it's just this blank iframe:

Screenshot 2024-03-26 at 3 10 34 PM

A very long time later (I would say more than five minutes...I only noticed this after walking away for something and coming back) a page appears in the iframe with:

Screenshot 2024-03-26 at 3 19 41 PM
drgrice1 commented 5 months ago

It is odd that it took that long, but it sounds like either cookies aren't getting set (have you allowed cross site tracking in your browser?) or something else is blocking the requests.

drgrice1 commented 5 months ago

Also, what do you see if you enable both LTI debugging and the general debugging in webwork2.mojolicious.yml when you attempt this?

dlglin commented 5 months ago

Just double-checking that you're not using Safari with "prevent cross-site tracking" enabled? In that case I also get the blank iframe, but that's not unique to WeBWorK. It happens with all LTI advantage content item selectors in D2L. See for example https://help.gradescope.com/article/1jn2lnwc5b-brightspace-instructor#troubleshooting_posting_grades

Alex-Jordan commented 5 months ago

I disabled the blocking of cross-site tracking in Firefox, and set both types of debugging, and at first there is no change; it is just a blank iframe. I went over to Chrome, and I instantly get:

Screenshot 2024-03-26 at 3 38 03 PM Screenshot 2024-03-26 at 3 39 22 PM

It has filled the iframe with the login screen for the course I am attempting to access. Except the title is "content selection".

I returned to FF and finally the blank screen finished, but now it has loaded the same thing we see with Chrome.

Alex-Jordan commented 5 months ago

Yeah, the blocking of cross-site tracking in FF seems to be the reason for the "secure connection failed", but even without that I still get the blank iframe for a long time. And then I get the WW course's log-in screen. Or with Chrome, I just immediately get the course's login screen.

drgrice1 commented 5 months ago

Can you inspect the cookies in the developer tools, and confirm that they have SameSite: None set?

Also, have you tested with session_management_via = "key" to see if that works?

Alex-Jordan commented 5 months ago

Changing to key has not changed anything with Firefox.

In Chrome, changing to key is now causing the iframe to take its time like Firefox does before it ultimately lands on the course login screen. Although in Chrome, it's some sort of generic loading display as opposed to an empty iframe) up until the login screen loads.

With session management back to cookies, this process is just not creating any cookies. I can clear cookies, then create a cookie for the WW site by logging in with a password. But triggering this tool does not create a cookie for webwork.pcc.edu. I guess that is the problem, but I don't know what is preventing the cookie.

drgrice1 commented 5 months ago

If changing to key doesn't work, then there must be something else going on beside cookies. What does enabling the debugging mechanisms show?

Alex-Jordan commented 5 months ago

Here is some log lines following a click to the LMS tool, with all long alphanumeric strings "REDACTED". I'll be on radio silence after this for several hours. See you at the meeting tomorrow?

[2024-03-26 16:47:10.46428] [329512] [warn] [k1mVcVWFlyEk] [/webwork2/ltiadvantage/login] The LTI Advantage login route was accessed with the appropriate parameters.
[2024-03-26 16:47:10.80353] [329512] [info] [rs-Oe9-HUHcl] ====== JWT PARAMETERS RECEIVED ======
[2024-03-26 16:47:10.80392] [329512] [info] [rs-Oe9-HUHcl] {
  "aud" => "REDACTED",
  "email" => "alex.jordan\@pcc.edu",
  "exp" => 1711498630,
  "family_name" => "Jordan",
  "given_name" => "Alexander",
  "http://www.brightspace.com" => {
    "Context.id.history" => "",
    "link_id" => 333875,
    "org_defined_id" => "G03518416",
    "tenant_id" => "REDACTED",
    "user_id" => 337,
    "username" => "alex.jordan"
  },
  "https://purl.imsglobal.org/spec/lti-ags/claim/endpoint" => {
    "lineitems" => "https://online.pcc.edu/d2l/api/lti/ags/2.0/deployment/REDACTED/orgunit/522938/lineitems",
    "scope" => [
      "https://purl.imsglobal.org/spec/lti-ags/scope/lineitem",
      "https://purl.imsglobal.org/spec/lti-ags/scope/lineitem.readonly",
      "https://purl.imsglobal.org/spec/lti-ags/scope/result.readonly",
      "https://purl.imsglobal.org/spec/lti-ags/scope/score"
    ]
  },
  "https://purl.imsglobal.org/spec/lti-dl/claim/deep_linking_settings" => {
    "accept_media_types" => "*/*",
    "accept_multiple" => bless( do{\(my $o = 1)}, 'JSON::PP::Boolean' ),
    "accept_presentation_document_targets" => [
      "iframe",
      "window"
    ],
    "accept_types" => [
      "link",
      "file",
      "ltiResourceLink",
      "image"
    ],
    "auto_create" => bless( do{\(my $o = 0)}, 'JSON::PP::Boolean' ),
    "data" => "REDACTED",
    "deep_link_return_url" => "https://online.pcc.edu/d2l/lti/dl/content/orgUnitId/522938/linkId/333875/parentModuleId/11312241/REDACTED"
  },
  "https://purl.imsglobal.org/spec/lti-nrps/claim/namesroleservice" => {
    "context_memberships_url" => "https://online.pcc.edu/d2l/api/lti/nrps/2.0/deployment/REDACTED/orgunit/522938/memberships",
    "service_versions" => [
      "2.0"
    ]
  },
  "https://purl.imsglobal.org/spec/lti/claim/context" => {
    "id" => 522938,
    "label" => "25414.202402.MTH253",
    "title" => "MTH-253-25414 - Calculus III",
    "type" => [
      "http://purl.imsglobal.org/vocab/lis/v2/course#CourseOffering"
    ]
  },
  "https://purl.imsglobal.org/spec/lti/claim/deployment_id" => "REDACTED",
  "https://purl.imsglobal.org/spec/lti/claim/launch_presentation" => {
    "locale" => "en-us"
  },
  "https://purl.imsglobal.org/spec/lti/claim/lis" => {
    "course_offering_sourcedid" => "online.pcc.edu:25414.202402.MTH253",
    "course_section_sourcedid" => "online.pcc.edu:25414.202402",
    "person_sourcedid" => "G03518416"
  },
  "https://purl.imsglobal.org/spec/lti/claim/message_type" => "LtiDeepLinkingRequest",
  "https://purl.imsglobal.org/spec/lti/claim/roles" => [
    "http://purl.imsglobal.org/vocab/lis/v2/membership#Instructor",
    "http://purl.imsglobal.org/vocab/lis/v2/institution/person#Student",
    "http://purl.imsglobal.org/vocab/lis/v2/institution/person#Learner"
  ],
  "https://purl.imsglobal.org/spec/lti/claim/target_link_uri" => "https://webwork.pcc.edu/webwork2/ltiadvantage/content_selection",
  "https://purl.imsglobal.org/spec/lti/claim/tool_platform" => {
    "guid" => "REDACTED",
    "product_family_code" => "desire2learn"
  },
  "https://purl.imsglobal.org/spec/lti/claim/version" => "1.3.0",
  "iat" => 1711496830,
  "iss" => "https://online.pcc.edu",
  "name" => "Alexander Jordan",
  "nbf" => 1711496830,
  "nonce" => "REDACTED",
  "sub" => "PCCD_337"
}

[2024-03-26 16:47:10.83740] [329512] [warn] [rs-Oe9-HUHcl] [/webwork2/ltiadvantage/launch] =========== SUMMARY ============
[2024-03-26 16:47:10.83746] [329512] [warn] [rs-Oe9-HUHcl] [/webwork2/ltiadvantage/launch] User id is |alex.jordan| (obtained from email which was preferred_source_of_username)
[2024-03-26 16:47:10.83750] [329512] [warn] [rs-Oe9-HUHcl] [/webwork2/ltiadvantage/launch] User email address is |alex.jordan@pcc.edu|
[2024-03-26 16:47:10.83753] [329512] [warn] [rs-Oe9-HUHcl] [/webwork2/ltiadvantage/launch] strip_domain_from_email is |1|
[2024-03-26 16:47:10.83756] [329512] [warn] [rs-Oe9-HUHcl] [/webwork2/ltiadvantage/launch] Student id is ||
[2024-03-26 16:47:10.83758] [329512] [warn] [rs-Oe9-HUHcl] [/webwork2/ltiadvantage/launch] preferred_source_of_username is |email|
[2024-03-26 16:47:10.83760] [329512] [warn] [rs-Oe9-HUHcl] [/webwork2/ltiadvantage/launch] fallback_source_of_username is ||
[2024-03-26 16:47:10.83764] [329512] [warn] [rs-Oe9-HUHcl] [/webwork2/ltiadvantage/launch] preferred_source_of_student_id is |ext_d2l_orgdefinedid|
[2024-03-26 16:47:10.83766] [329512] [warn] [rs-Oe9-HUHcl] [/webwork2/ltiadvantage/launch] ================================
drgrice1 commented 5 months ago

Okay. See you tomorrow.

Alex-Jordan commented 5 months ago

Still working for me in production with the latest commit.

I saw this yesterday:

https://www.youtube.com/watch?v=oDrMoFCtE-c