|
@@ -0,0 +1,100 @@
|
|
|
+package riomhaire.lti.core.business.commands;
|
|
|
+
|
|
|
+import com.auth0.jwt.JWT;
|
|
|
+import lombok.AllArgsConstructor;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.springframework.web.servlet.ModelAndView;
|
|
|
+import riomhaire.lti.core.adapters.token.JWKBasedJwtToMapAdapter;
|
|
|
+import riomhaire.lti.core.model.ClientConfiguration;
|
|
|
+import riomhaire.lti.core.model.LTIMessage;
|
|
|
+import riomhaire.lti.core.model.LTIMessageType;
|
|
|
+import riomhaire.lti.core.model.interfaces.ApplicationRegistry;
|
|
|
+import riomhaire.lti.core.model.interfaces.Command;
|
|
|
+import riomhaire.lti.core.model.interfaces.DecodeException;
|
|
|
+
|
|
|
+import java.util.HashMap;
|
|
|
+import java.util.Map;
|
|
|
+
|
|
|
+/**
|
|
|
+ * Processes lti launch content and create deep link messages - IE claims within a JWT
|
|
|
+ * Delegates to the actual handler
|
|
|
+ */
|
|
|
+@AllArgsConstructor
|
|
|
+@Slf4j
|
|
|
+public class LTILaunchCommand implements Command<ModelAndView, ApplicationRegistry, LTIMessage> {
|
|
|
+ public static final String CLAIM_MESSAGE_TYPE = "https://purl.imsglobal.org/spec/lti/claim/message_type";
|
|
|
+ public static final String LTI_RESOURCE_LINK_REQUEST = "LtiResourceLinkRequest";
|
|
|
+ public static final String LTI_DEEP_LINKING_REQUEST = "LtiDeepLinkingRequest";
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public ModelAndView execute(ApplicationRegistry registry, LTIMessage message) {
|
|
|
+ var idToken = message.getQueryParams().get("id_token");
|
|
|
+ var jwtToken = message.getQueryParams().get("JWT");
|
|
|
+ // Crack open so we can find the config
|
|
|
+ String token = (idToken != null) ? idToken : jwtToken;
|
|
|
+ var jwt = JWT.decode(token);
|
|
|
+ // Lookup client id and issuer
|
|
|
+ var issuer = jwt.getClaim("iss").asString();
|
|
|
+ var clientId = jwt.getClaim("aud").asString();
|
|
|
+
|
|
|
+ var toolRegistration = registry.clientRegistrationResolver().lookupClient(issuer, clientId);
|
|
|
+ var claims = new HashMap<String,Object>();
|
|
|
+ if( toolRegistration.isPresent()) {
|
|
|
+ claims.putAll(decodeToken(token, issuer, clientId, toolRegistration.get()));
|
|
|
+ }
|
|
|
+
|
|
|
+ // OK based off of the message delegate to the right sub-action
|
|
|
+ var messageType = String.valueOf(claims.get(CLAIM_MESSAGE_TYPE));
|
|
|
+ return switch (messageType) {
|
|
|
+ case LTI_RESOURCE_LINK_REQUEST:
|
|
|
+ // Build Message
|
|
|
+ var delegateLaunchMessage = LTIMessage.builder()
|
|
|
+ .messageType(LTIMessageType.LaunchContent.name())
|
|
|
+ .metadata(message.getMetadata())
|
|
|
+ .queryParams(message.getQueryParams())
|
|
|
+ .claims(claims)
|
|
|
+ .build();
|
|
|
+ yield registry.commandDispatcher().dispatch(registry, delegateLaunchMessage);
|
|
|
+ case LTI_DEEP_LINKING_REQUEST:
|
|
|
+ // Build Message
|
|
|
+ var delegateDeepLinkMessage = LTIMessage.builder()
|
|
|
+ .messageType(LTIMessageType.SelectDeepLinkContent.name())
|
|
|
+ .metadata(message.getMetadata())
|
|
|
+ .queryParams(message.getQueryParams())
|
|
|
+ .claims(claims)
|
|
|
+ .build();
|
|
|
+ yield registry.commandDispatcher().dispatch(registry, delegateDeepLinkMessage);
|
|
|
+ default:
|
|
|
+ throw new IllegalStateException("Unexpected value: " + messageType);
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * This method using info tool configuration for jwks etc
|
|
|
+ *
|
|
|
+ * @param token token to decode
|
|
|
+ * @param issuer who the issuer was
|
|
|
+ * @param clientId the client id
|
|
|
+ * @param clientConfiguration the tool configuration
|
|
|
+ * @return Model and View to Return the code
|
|
|
+ */
|
|
|
+ private Map<String, Object> decodeToken(String token, String issuer, String clientId, ClientConfiguration clientConfiguration) {
|
|
|
+ var claims = new HashMap<String, Object>();
|
|
|
+
|
|
|
+ if (clientConfiguration != null) {
|
|
|
+ // Validate JWT to verify its by who they say they are
|
|
|
+ var adapter = new JWKBasedJwtToMapAdapter(clientConfiguration.getJwksUrl(), clientConfiguration.isSkipVerification());
|
|
|
+ try {
|
|
|
+ claims.putAll(adapter.decode(token));
|
|
|
+ } catch (DecodeException e) {
|
|
|
+ // OK not valid
|
|
|
+ claims.put("error", "cannot verify token because of: " + e.toString());
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ claims.put("error", "cannot find client for " + issuer + " client-id " + clientId);
|
|
|
+ }
|
|
|
+ return claims;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+}
|