Skip to content

Commit

Permalink
SAK-40955 - Add code so FireFox works with OIDC flow (sakaiproject#6264)
Browse files Browse the repository at this point in the history
* SAK-40955 - Initial commit - there will be more to come

* SAK-40955 - Use javascript since redirect does not work

* SAK-40955 - Clean up formatting.

* SAK-40955 - Fix Codasy complaint
  • Loading branch information
csev authored Nov 19, 2018
1 parent 2996cbc commit 4b08daf
Show file tree
Hide file tree
Showing 5 changed files with 68 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1970,6 +1970,11 @@ public static String[] postLaunchJWT(Properties toolProps, Properties ltiProps,

Key privateKey = LTI13Util.string2PrivateKey(platform_private);
Key publicKey = LTI13Util.string2PublicKey(platform_public);

if ( privateKey == null | publicKey == null ) {
return postError("<p>" + getRB(rb, "error.no.pki", "Public and/or Private Key(s) not configured.") + "</p>");
}

String kid = LTI13KeySetUtil.getPublicKID(publicKey);

String jws = Jwts.builder().setHeaderParam("kid", kid).
Expand Down
1 change: 1 addition & 0 deletions basiclti/basiclti-impl/src/bundle/basicltisvc.properties
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ error.missing = Error, non-existent placementId.
error.load = Error, cannot load placement=
error.props = Error, cannot load Sakai information for placement=
error.sign = Error signing message.
error.no.pki = Public and/or Private Key(s) not configured.

error.content.missing=External tool content item is missing or improperly configured.
error.tool.missing=External tool item is missing or improperly configured.
Expand Down
3 changes: 1 addition & 2 deletions basiclti/basiclti-oidc/src/bundle/oidc.properties
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
# TBD

# outcomes.invalid=Not a valid outcomes request
oidc.continue=Continue To Launch
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@
/* This does not use the request filter because it needs full control of response headers.
But this also means that no work that should be in a session should be done in this servlet.
In particular, never use ThreadLocal in this servlet.
*/

*/
import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
Expand All @@ -31,11 +31,13 @@
import javax.servlet.http.HttpServletResponse;

import lombok.extern.slf4j.Slf4j;
import org.sakaiproject.component.cover.ComponentManager;
import org.sakaiproject.lti.api.LTIService;

import org.tsugi.lti13.LTI13Util;
import org.apache.commons.lang.StringUtils;
import org.tsugi.http.HttpUtil;

import org.sakaiproject.util.ResourceLoader;

/**
*
Expand All @@ -47,6 +49,11 @@ public class OIDCServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
protected static LTIService ltiService = null;

private static ResourceLoader rb = new ResourceLoader("oidc");

// TODO: Come up with a better way to handle this in RequestFilter
protected String cookieName = "JSESSIONID";

@Override
public void init(ServletConfig config) throws ServletException {
super.init(config);
Expand All @@ -59,14 +66,14 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response) t
String[] parts = uri.split("/");

// /imsoidc/lti13/oidc_auth?state=42&login_hint=/access/basiclti/site/92e..e8e67/content:6
if (parts.length == 4 && "oidc_auth".equals(parts[3]) ) {
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);
LTI13Util.return400(response, "Unrecognized GET request parts=" + parts.length + " request=" + uri);
}

protected void doPost(HttpServletRequest request, HttpServletResponse response)
Expand All @@ -77,6 +84,7 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response)

/**
* Process the returned OIDC Authorization request
*
* @param signed_placement
* @param lineItem - Can be null
* @param results
Expand All @@ -89,33 +97,58 @@ private void handleOIDCAuthorization(HttpServletRequest request, HttpServletResp
state = StringUtils.trimToNull(state);

String login_hint = (String) request.getParameter("login_hint");
if ( StringUtils.isEmpty(login_hint) ) state = null;
if (StringUtils.isEmpty(login_hint)) {
state = null;
}

String nonce = (String) request.getParameter("nonce");
nonce = StringUtils.trimToNull(nonce);

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

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

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

// Check if we need to generate a page to re-grab the cookie
String sessionCookie = HttpUtil.getCookie(request, cookieName);
if (StringUtils.isEmpty(sessionCookie)) {
PrintWriter out = null;
try {
out = response.getWriter();
out.println("<script>window.location.href=\"" + redirect + "\";</script>");
out.println("<p>...</p>");
out.print("<p><a href=\"" + redirect + "\" style=\"display: none;\" id=\"linker\">");
out.print(rb.getString("oidc.continue"));
out.println("</a></p>");
out.println("<script>setTimeout(function(){ document.getElementById('linker').style.display = 'inline'}, 1000);</script>");
} catch (IOException e) {
log.error(e.getMessage(), e);
}
return;
}

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

}
}

Expand Down
19 changes: 19 additions & 0 deletions basiclti/tsugi-util/src/java/org/tsugi/http/HttpUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@

import java.util.Enumeration;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.Cookie;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;

/**
* Some Tsugi Utility code for to make using Jackson easier to use.
Expand Down Expand Up @@ -47,4 +49,21 @@ public static void printParameters(HttpServletRequest request) {
}
}

public static String getCookie(HttpServletRequest request, String lookup) {
if ( request == null || lookup == null ) return null;

// https://stackoverflow.com/questions/11047548/getting-cookie-in-servlet
Cookie[] cookies = request.getCookies();
if ( cookies == null ) return null;
for (int i = 0; i < cookies.length; i++) {
Cookie cookie=cookies[i];
String cookieName = cookie.getName();
String cookieValue = cookie.getValue();
if ( StringUtils.isEmpty(cookieName) ) continue;
if ( cookieName.equalsIgnoreCase(lookup) ) {
return cookieValue;
}
}
return null;
}
}

0 comments on commit 4b08daf

Please sign in to comment.