Skip to content

Commit

Permalink
SAK-49345 LTI switch from Base62 to a variant of Base64 (#11976)
Browse files Browse the repository at this point in the history
(cherry picked from commit 2ea87c8)
  • Loading branch information
csev authored and ern committed Oct 19, 2023
1 parent 701b875 commit e150f91
Show file tree
Hide file tree
Showing 6 changed files with 191 additions and 279 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
import org.tsugi.lti13.LTI13Util;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.StringEscapeUtils;
import org.tsugi.util.Base62;
import org.tsugi.util.Base64DoubleUrlEncodeSafe;
import org.tsugi.http.HttpUtil;

import org.sakaiproject.util.RequestFilter;
Expand Down Expand Up @@ -253,7 +253,7 @@ private void fancyRedirect(HttpServletRequest request, HttpServletResponse respo
* @param response
*/
private void handleResignContentItemResponse(HttpServletRequest request, HttpServletResponse response) throws IOException {
String forward = Base62.decode((String) request.getParameter("forward"));
String forward = Base64DoubleUrlEncodeSafe.decode((String) request.getParameter("forward"));
forward = StringUtils.trimToNull(forward);

Long toolKey = SakaiBLTIUtil.getLongKey((String) request.getParameter("tool_id"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@
import org.json.simple.JSONObject;
import org.json.simple.JSONArray;

import org.tsugi.util.Base62;
import org.tsugi.util.Base64DoubleUrlEncodeSafe;
import static org.tsugi.basiclti.BasicLTIUtil.getObject;
import static org.tsugi.basiclti.BasicLTIUtil.getString;

Expand Down Expand Up @@ -1673,7 +1673,7 @@ public void doSingleContentItemResponse(RunData data, Context context) {
}

// Sanity check our (within Sakai) returnUrl
String returnUrl = Base62.decode(data.getParameters().getString("returnUrl"));
String returnUrl = Base64DoubleUrlEncodeSafe.decode(data.getParameters().getString("returnUrl"));
if (returnUrl == null) {
addAlert(state, rb.getString("error.contentitem.missing.returnurl"));
switchPanel(state, errorPanel);
Expand Down Expand Up @@ -2528,7 +2528,7 @@ public String buildContentConfigPanelContext(VelocityPortlet portlet, Context co
+ "?eventSubmit_doSingleContentItemResponse=Save"
+ "&" + FLOW_PARAMETER + "=" + flow
+ "&" + RequestFilter.ATTR_SESSION + "=" + URLEncoder.encode(sessionid + "." + suffix)
+ "&returnUrl=" + Base62.encode(returnUrl)
+ "&returnUrl=" + Base64DoubleUrlEncodeSafe.encode(returnUrl)
+ "&panel=PostContentItem"
+ "&tool_id=" + tool.get(LTIService.LTI_ID);

Expand Down Expand Up @@ -2558,7 +2558,7 @@ public String buildContentConfigPanelContext(VelocityPortlet portlet, Context co

// Run the contentreturn through the forward servlet
contentReturn = serverConfigurationService.getServerUrl() + "/imsoidc/lti13/resigncontentitem?forward=" +
Base62.encode(contentReturn) + "&tool_id=" + tool.get(LTIService.LTI_ID);
Base64DoubleUrlEncodeSafe.encode(contentReturn) + "&tool_id=" + tool.get(LTIService.LTI_ID);

contentLaunch = ContentItem.buildLaunch(contentLaunch, contentReturn, contentData);

Expand Down Expand Up @@ -2796,7 +2796,7 @@ private String buildContentItemGenericMainPanelContext(VelocityPortlet portlet,
+ "/sakai.lti.admin.helper.helper"
+ "?panel=ContentConfig"
+ "&" + FLOW_PARAMETER + "=" + flow
+ "&returnUrl=" + Base62.encode(returnUrl)
+ "&returnUrl=" + Base64DoubleUrlEncodeSafe.encode(returnUrl)
+ "&tool_id=" + tool.get(LTIService.LTI_ID)
+ "&" + RequestFilter.ATTR_SESSION + "=" + URLEncoder.encode(sessionid + "." + suffix);
context.put("forwardUrl", configUrl);
Expand Down Expand Up @@ -2840,7 +2840,7 @@ private String buildContentItemGenericMainPanelContext(VelocityPortlet portlet,

// Run the contentreturn through the forward servlet
contentReturn = serverConfigurationService.getServerUrl() + "/imsoidc/lti13/resigncontentitem?forward=" +
Base62.encode(contentReturn) + "&tool_id=" + tool.get(LTIService.LTI_ID);
Base64DoubleUrlEncodeSafe.encode(contentReturn) + "&tool_id=" + tool.get(LTIService.LTI_ID);

// This will forward to AccessServlet / BasicLTISecurityServiceImpl with a tool: url
// AccessServlet will detect if this is a CI or DL and handle it accordingly using
Expand Down
129 changes: 0 additions & 129 deletions basiclti/tsugi-util/src/java/org/tsugi/util/Base62.java

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/**
* When complex strings with URL sensitive characters are passed as URL parameters we could use
* URLEncoding, but sometimes we end up double URL Encoding and that is generally seen as a security
* problem and some firewalls remove double URL Encoding.
*
* And Base64 has a URL safe encoding - but it uses '=' for padding which does not survive double URL
* Encoding.
*
* So our solution is to manually map '=' to '.' which produces strings which do not change when URL
* Encoded - so double URL Encoding can be avoided. The use of '.' as one of the "encoded" characters
* is common in JWT encoders. Sadly, we cannot use JWT encoder and decoder because it is specialized
* for JSON so we cannot use it here. Also, since the only compatibility that JWT encoders need to work
* with are JWT decoders, so JWT dispenses with padding completely - hence you will never see an '='
* in an encoded JWT (https://jwt.io/)
*/

package org.tsugi.util;

public class Base64DoubleUrlEncodeSafe {

// https://datatracker.ietf.org/doc/html/rfc4648#section-3.2
public static final char CHARACTER_TO_AVOID = '=';
public static final char REPLACEMENT_CHARACTER = '.';

/**
* Encodes a given string into a Base64-Double-UrlEncode-Safe string.
*
* @param data The input string to be encoded.
* @return The Base64-Double-UrlEncode-Safe encoded string.
*/
public static String encode(String data) {
if ( data == null ) return null;
try {
String encoded = java.util.Base64.getUrlEncoder().encodeToString(data.getBytes("UTF-8"));
return encoded.replace(CHARACTER_TO_AVOID, REPLACEMENT_CHARACTER);
} catch (java.io.UnsupportedEncodingException e ) {
// Unlikely
e.printStackTrace();
return null;
}
}

/**
* Decodes a Base64-Double-UrlEncode-Safe string into its original string representation.
*
* @param encoded The Base64-Double-UrlEncode-Safe string to be decoded.
* @return The decoded original string.
* @throws java.io.UnsupportedEncodingException If the input string contains invalid characters.
*/
public static String decode(String encoded) {
if ( encoded == null ) return null;
return new String(java.util.Base64.getUrlDecoder().decode(encoded.replace(REPLACEMENT_CHARACTER, CHARACTER_TO_AVOID)));
}

}
Loading

0 comments on commit e150f91

Please sign in to comment.