Skip to content

Commit

Permalink
SAK-40881 - Add OIDC flow to Sakai's LTI Advantage (sakaiproject#6238)
Browse files Browse the repository at this point in the history
* SAK-40881 - Add OIDC flow to Sakai's LTI Advantage

* SAK-40881 - Change to target_link_uri

* SAK-40881 - Address code review issues
  • Loading branch information
csev authored Nov 10, 2018
1 parent 5613872 commit ee74319
Show file tree
Hide file tree
Showing 9 changed files with 168 additions and 56 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,8 @@ public interface LTIService extends LTISubstitutionsFilter {
"lti13_tool_keyset:textarea:hidden=true:label=bl_lti13_tool_keyset:maxlength=1M:role=admin",
// The tool kid is internal (comes through on launch and we store it and cache the public key)
"lti13_tool_kid:text:hidden=true:label=bl_lti13_tool_kid:maxlength=1024:role=admin",
"lti13_oidc_endpoint:text:label=bl_lti13_oidc_endpoint:maxlength=1024:role=admin",
"lti13_oidc_redirect:text:label=bl_lti13_oidc_redirect:maxlength=1024:role=admin",

// SHA256 Support (See SAK-33898)
"sha256:radio:label=bl_sha256:hidden=true:role=admin:choices=off,on,content",
Expand Down Expand Up @@ -311,6 +313,8 @@ public interface LTIService extends LTISubstitutionsFilter {
String LTI13_TOOL_PRIVATE = "lti13_tool_private";
String LTI13_PLATFORM_PUBLIC = "lti13_platform_public";
String LTI13_PLATFORM_PRIVATE = "lti13_platform_private";
String LTI13_OIDC_ENDPOINT = "lti13_oidc_endpoint";
String LTI13_OIDC_REDIRECT = "lti13_oidc_redirect";

// For Instructors, this model is filtered down dynamically based on
// Tool settings
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,12 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response) t
return;
}

// /imsblis/lti13/oidc_auth?state=42&login_hint=/access/basiclti/site/92e..e8e67/content:6
if (parts.length == 4 && "oidc_auth".equals(parts[3]) ) {
handleOIDCAuthorization(request, response);
return;
}

log.error("Unrecognized GET request parts={} request={}", parts.length, uri);

LTI13Util.return400(response, "Unrecognized GET request parts="+parts.length+" request="+uri);
Expand Down Expand Up @@ -1530,4 +1536,44 @@ private void handleLineItemsDelete(String signed_placement, String lineItem, Htt
LTI13Util.return400(response, "Could not delete assignment "+assignment_id);
log.error("Could delete assignment={}", assignment_id);
}

/**
* Process the returned OIDC Authorization request
* @param signed_placement
* @param lineItem - Can be null
* @param results
* @param request
* @param response
*/
private void handleOIDCAuthorization(HttpServletRequest request, HttpServletResponse response) throws IOException {

String state = (String) request.getParameter("state");
if ( state != null && state.trim().length() < 1 ) state = null;

String login_hint = (String) request.getParameter("login_hint");
if ( login_hint != null && login_hint.trim().length() < 1 ) state = null;

if ( state == null || login_hint == null ) {
LTI13Util.return400(response, "Missing login_hint or state parameter");
log.error("Missing login_hint or state parameter");
return;
}

if ( login_hint.contains("&") || login_hint.contains("?") || ! login_hint.startsWith("/access/basiclti/site/") ) {
LTI13Util.return400(response, "Bad format for login_hint");
log.error("Bad format for login_hint");
return;
}

String redirect = login_hint + "?state=" + java.net.URLEncoder.encode(state);
log.debug("redirect={}", redirect);

try {
response.sendRedirect(redirect);
} catch (IOException unlikely) {
log.error("failed redirect {}", unlikely.getMessage());
}

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -854,7 +854,7 @@ public static String[] postLaunchHTML(String descriptor, String contextId, Strin

// This must return an HTML message as the [0] in the array
// If things are successful - the launch URL is in [1]
public static String[] postLaunchHTML(Map<String, Object> content, Map<String, Object> tool, LTIService ltiService, ResourceLoader rb) {
public static String[] postLaunchHTML(Map<String, Object> content, Map<String, Object> tool, String state, LTIService ltiService, ResourceLoader rb) {
if (content == null) {
return postError("<p>" + getRB(rb, "error.content.missing", "Content item is missing or improperly configured.") + "</p>");
}
Expand Down Expand Up @@ -1001,6 +1001,7 @@ public static String[] postLaunchHTML(Map<String, Object> content, Map<String, O
setProperty(lti2subst, "ResourceLink.id", resource_link_id);

setProperty(toolProps, "launch_url", launch_url);
setProperty(toolProps, "state", state); // So far LTI 1.3 only

setProperty(toolProps, LTIService.LTI_SECRET, secret);
setProperty(toolProps, "key", key);
Expand Down Expand Up @@ -1426,7 +1427,7 @@ public static DeepLinkResponse getDeepLinkFromToken(Map<String, Object> tool, St
* successful - the launch URL is in [1]
*/
public static String[] postContentItemSelectionRequest(Long toolKey, Map<String, Object> tool,
ResourceLoader rb, String contentReturn, Properties dataProps) {
String state, ResourceLoader rb, String contentReturn, Properties dataProps) {
if (tool == null) {
return postError("<p>" + getRB(rb, "error.tool.missing", "Tool is missing or improperly configured.") + "</p>");
}
Expand Down Expand Up @@ -1553,6 +1554,7 @@ public static String[] postContentItemSelectionRequest(Long toolKey, Map<String,
if ( isLTI13 ) {
Properties toolProps = new Properties();
toolProps.put("launch_url", launch_url);
setProperty(toolProps, "state", state); // So far LTI 1.3 only
toolProps.put(LTIService.LTI_DEBUG, dodebug ? "1" : "0");

Map<String, Object> content = null;
Expand Down Expand Up @@ -1788,10 +1790,11 @@ public static String[] postLaunchJWT(Properties toolProps, Properties ltiProps,
lj.message_type = LaunchJWT.MESSAGE_TYPE_DEEP_LINK;
deepLink = true;
}
lj.launch_url = launch_url; // The actual launch URL
lj.launch_presentation.css_url = ltiProps.getProperty("launch_presentation_css_url");
lj.locale = ltiProps.getProperty("launch_presentation_locale");
lj.launch_presentation.return_url = ltiProps.getProperty("launch_presentation_return_url");
lj.issuer = "https://www.sakaiproject.org/";
lj.issuer = getOurServerUrl();
lj.audience = client_id;
lj.deployment_id = org_guid;
lj.subject = ltiProps.getProperty("user_id");
Expand Down Expand Up @@ -1957,14 +1960,26 @@ public static String[] postLaunchJWT(Properties toolProps, Properties ltiProps,
dodebug = true;
}

String state = toolProps.getProperty("state");
if ( state != null && state.trim().length() < 1 ) state = null;

String lti13_oidc_redirect = toNull((String) tool.get(LTIService.LTI13_OIDC_REDIRECT));
if ( lti13_oidc_redirect != null ) launch_url = lti13_oidc_redirect;

String html = "<form action=\"" + launch_url + "\" method=\"POST\">\n"
+ " <input type=\"hidden\" name=\"id_token\" value=\"" + BasicLTIUtil.htmlspecialchars(jws) + "\" />\n"
+ " <input type=\"submit\" value=\"Go!\" />\n"
+ "</form>\n";
+ " <input type=\"hidden\" name=\"id_token\" value=\"" + BasicLTIUtil.htmlspecialchars(jws) + "\" />\n";

if ( state != null ) {
html += " <input type=\"hidden\" name=\"state\" value=\"" + BasicLTIUtil.htmlspecialchars(state) + "\" />\n";
}

html += " <input type=\"submit\" value=\"Go!\" />\n</form>\n";

if (dodebug) {
html += "<p>\n--- Unencoded JWT:<br/>"
+ BasicLTIUtil.htmlspecialchars(ljs)
+ "</p>\n<p>\n--- State:<br/>"
+ BasicLTIUtil.htmlspecialchars(state)
+ "</p>\n<p>\n--- Encoded JWT:<br/>"
+ BasicLTIUtil.htmlspecialchars(jws)
+ "</p>\n";
Expand Down
2 changes: 2 additions & 0 deletions basiclti/basiclti-impl/src/bundle/ltiservice.properties
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@ bl_lti13_client_id=LTI 1.3 Client ID
bl_lti13_tool_private=LTI 1.3 Private Key (for messages from the tool)
bl_lti13_tool_keyset=LTI 1.3 Keyset URL (for messages from the tool)
bl_lti13_tool_kid=LTI 1.3 Latest KID value (for messages from the tool)
bl_lti13_oidc_endpoint=LTI 1.3 OpenID Connect Endpoint (from the tool)
bl_lti13_oidc_redirect=LTI 1.3 Tool Redirect Endpoint(s)

bl_lti13_platform_header=These fields are also required for LTI 1.3, but these must not be shared with the tool.
bl_lti13_tool_public=LTI 1.3 Public Key (for messages from the tool)
Expand Down
Loading

0 comments on commit ee74319

Please sign in to comment.