Index: 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/exception/BadToolProviderConfigurationException.java =================================================================== diff -u --- 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/exception/BadToolProviderConfigurationException.java (revision 0) +++ 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/exception/BadToolProviderConfigurationException.java (revision 22d20e45f4a7cbb2a9477c66f75e2a162b0d3f06) @@ -0,0 +1,14 @@ +package edu.uoc.elc.lti.exception; + +/** + * @author xaracil@uoc.edu + */ +public class BadToolProviderConfigurationException extends RuntimeException { + public BadToolProviderConfigurationException(String message) { + super(message); + } + + public BadToolProviderConfigurationException(Throwable cause) { + super(cause); + } +} Index: 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/exception/InvalidLTICallException.java =================================================================== diff -u --- 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/exception/InvalidLTICallException.java (revision 0) +++ 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/exception/InvalidLTICallException.java (revision 22d20e45f4a7cbb2a9477c66f75e2a162b0d3f06) @@ -0,0 +1,10 @@ +package edu.uoc.elc.lti.exception; + +/** + * @author xaracil@uoc.edu + */ +public class InvalidLTICallException extends RuntimeException { + public InvalidLTICallException(String message) { + super(message); + } +} Index: 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/exception/InvalidTokenException.java =================================================================== diff -u --- 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/exception/InvalidTokenException.java (revision 0) +++ 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/exception/InvalidTokenException.java (revision 22d20e45f4a7cbb2a9477c66f75e2a162b0d3f06) @@ -0,0 +1,10 @@ +package edu.uoc.elc.lti.exception; + +/** + * @author xaracil@uoc.edu + */ +public class InvalidTokenException extends RuntimeException { + public InvalidTokenException(String message) { + super(message); + } +} Index: 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/exception/LTISignatureException.java =================================================================== diff -u --- 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/exception/LTISignatureException.java (revision 0) +++ 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/exception/LTISignatureException.java (revision 22d20e45f4a7cbb2a9477c66f75e2a162b0d3f06) @@ -0,0 +1,14 @@ +package edu.uoc.elc.lti.exception; + +/** + * @author xaracil@uoc.edu + */ +public class LTISignatureException extends RuntimeException { + public LTISignatureException(String message) { + super(message); + } + + public LTISignatureException(Throwable cause) { + super(cause); + } +} Index: 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/exception/UnauthorizedAgsCallException.java =================================================================== diff -u --- 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/exception/UnauthorizedAgsCallException.java (revision 0) +++ 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/exception/UnauthorizedAgsCallException.java (revision 22d20e45f4a7cbb2a9477c66f75e2a162b0d3f06) @@ -0,0 +1,13 @@ +package edu.uoc.elc.lti.exception; + +/** + * @author xaracil@uoc.edu + */ +public class UnauthorizedAgsCallException extends RuntimeException { + public UnauthorizedAgsCallException() { + } + + public UnauthorizedAgsCallException(String message) { + super(message); + } +} Index: 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/platform/Member.java =================================================================== diff -u --- 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/platform/Member.java (revision 0) +++ 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/platform/Member.java (revision 22d20e45f4a7cbb2a9477c66f75e2a162b0d3f06) @@ -0,0 +1,41 @@ +package edu.uoc.elc.lti.platform; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Getter; +import lombok.Setter; + +import java.util.List; + +/** + * @author xaracil@uoc.edu + */ +@Getter +@Setter +@JsonIgnoreProperties(ignoreUnknown = true) +public class Member { + @JsonProperty("context_id") + private String contextId; + @JsonProperty("context_label") + private String contextLabel; + @JsonProperty("context_title") + private String contextTitle; + @JsonProperty("name") + private String name; + @JsonProperty("picture") + private String picture; + @JsonProperty("given_name") + private String givenName; + @JsonProperty("family_name") + private String familyName; + @JsonProperty("middle_name") + private String middleName; + @JsonProperty("email") + private String email; + @JsonProperty("user_id") + private String userId; + @JsonProperty("lis_person_sourcedid") + private String lisPersonSourceid; + @JsonProperty("roles") + private List roles; +} Index: 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/platform/NamesRoleServiceResponse.java =================================================================== diff -u --- 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/platform/NamesRoleServiceResponse.java (revision 0) +++ 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/platform/NamesRoleServiceResponse.java (revision 22d20e45f4a7cbb2a9477c66f75e2a162b0d3f06) @@ -0,0 +1,16 @@ +package edu.uoc.elc.lti.platform; + +import lombok.Getter; +import lombok.Setter; + +import java.util.List; + +/** + * @author xaracil@uoc.edu + */ +@Getter +@Setter +public class NamesRoleServiceResponse { + private String id; + private List members; +} Index: 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/platform/PlatformClient.java =================================================================== diff -u --- 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/platform/PlatformClient.java (revision 0) +++ 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/platform/PlatformClient.java (revision 22d20e45f4a7cbb2a9477c66f75e2a162b0d3f06) @@ -0,0 +1,64 @@ +package edu.uoc.elc.lti.platform; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.commons.io.IOUtils; + +import java.io.IOException; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.nio.charset.StandardCharsets; + +/** + * @author xaracil@uoc.edu + */ +public class PlatformClient { + private final static String CHARSET = StandardCharsets.UTF_8.toString(); + + public T post(URL url, String body, String contentType, Class type) throws IOException { + final HttpURLConnection connection = createConnection(url, contentType); + sendData(connection, body); + String response = getResponse(connection); + return formatResponse(response, type); + } + + private HttpURLConnection createConnection(URL url, String contentType) throws IOException { + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + connection = initConnection(connection); + connection = setContentType(connection, contentType); + return connection; + } + + private HttpURLConnection initConnection(HttpURLConnection connection) throws IOException { + connection.setRequestMethod("POST"); + connection.setDoOutput(true); + connection.setRequestProperty("Accept-Charset", CHARSET); + return connection; + } + + private HttpURLConnection setContentType(HttpURLConnection connection, String contentType) { + if (contentType != null) { + connection.setRequestProperty("Content-Type", contentType); + } + return connection; + } + + private void sendData(HttpURLConnection connection, String body) throws IOException { + try (OutputStream output = connection.getOutputStream()) { + output.write(body.getBytes(CHARSET)); + } + } + + private String getResponse(HttpURLConnection connection) throws IOException { + return IOUtils.toString(connection.getInputStream(), CHARSET); + } + + private T formatResponse(String response, Class type) throws IOException { + if (type.getSimpleName().equals("String")) { + return (T) response; + } + + ObjectMapper objectMapper = new ObjectMapper(); + return objectMapper.readValue(response, type); + } +} Index: 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/platform/accesstoken/AccessTokenRequestHandler.java =================================================================== diff -u --- 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/platform/accesstoken/AccessTokenRequestHandler.java (revision 0) +++ 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/platform/accesstoken/AccessTokenRequestHandler.java (revision 22d20e45f4a7cbb2a9477c66f75e2a162b0d3f06) @@ -0,0 +1,60 @@ +package edu.uoc.elc.lti.platform.accesstoken; + +import edu.uoc.elc.lti.platform.PlatformClient; +import edu.uoc.elc.lti.tool.ScopeEnum; +import edu.uoc.elc.lti.tool.ToolDefinition; +import edu.uoc.lti.accesstoken.AccessTokenRequest; +import edu.uoc.lti.accesstoken.AccessTokenRequestBuilder; +import edu.uoc.lti.clientcredentials.ClientCredentialsRequest; +import edu.uoc.lti.clientcredentials.ClientCredentialsTokenBuilder; +import lombok.RequiredArgsConstructor; + +import java.io.IOException; +import java.net.URL; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +/** + * @author xaracil@uoc.edu + */ +@RequiredArgsConstructor +public class AccessTokenRequestHandler { + private final String kid; + private final ToolDefinition toolDefinition; + private final ClientCredentialsTokenBuilder clientCredentialsTokenBuilder; + private final AccessTokenRequestBuilder accessTokenRequestBuilder; + + public AccessTokenResponse getAccessToken() throws IOException { + AccessTokenRequest request = AccessTokenRequest.builder() + .grant_type("client_credentials") + .client_assertion_type("urn:ietf:params:oauth:client-assertion-type:jwt-bearer") + .scope(scopes()) + .client_assertion(getClientAssertion()) + .build(); + + // do a post to the service + return postToService(new URL(toolDefinition.getAccessTokenUrl()), request); + } + + private String getClientAssertion() { + ClientCredentialsRequest clientCredentialsRequest = new ClientCredentialsRequest(kid, + toolDefinition.getName(), + toolDefinition.getClientId(), + toolDefinition.getAccessTokenUrl()); + return clientCredentialsTokenBuilder.build(clientCredentialsRequest); + } + + private String scopes() { + List scopeEnums = Arrays.asList(ScopeEnum.AGS_SCOPE_LINE_ITEM, ScopeEnum.AGS_SCOPE_RESULT, ScopeEnum.NAMES_AND_ROLES_SCOPE, ScopeEnum.AGS_SCOPE_SCORE); + return scopeEnums.stream().map(ScopeEnum::getScope).collect(Collectors.joining(" ")); + } + + private AccessTokenResponse postToService(URL url, AccessTokenRequest request) throws IOException { + final String bodyAsString = accessTokenRequestBuilder.build(request); + final String contentType = accessTokenRequestBuilder.getContentType(); + + PlatformClient platformClient = new PlatformClient(); + return platformClient.post(url, bodyAsString, contentType, AccessTokenResponse.class); + } +} Index: 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/platform/accesstoken/AccessTokenResponse.java =================================================================== diff -u --- 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/platform/accesstoken/AccessTokenResponse.java (revision 0) +++ 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/platform/accesstoken/AccessTokenResponse.java (revision 22d20e45f4a7cbb2a9477c66f75e2a162b0d3f06) @@ -0,0 +1,21 @@ +package edu.uoc.elc.lti.platform.accesstoken; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Getter; + +/** + * @author xaracil@uoc.edu + */ +@Getter +public class AccessTokenResponse { + private String scope; + + @JsonProperty("access_token") + private String accessToken; + + @JsonProperty("token_type") + private String tokenType; + + @JsonProperty("expires_in") + private int expiresIn; +} Index: 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/platform/ags/AgsClientFactory.java =================================================================== diff -u --- 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/platform/ags/AgsClientFactory.java (revision 0) +++ 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/platform/ags/AgsClientFactory.java (revision 22d20e45f4a7cbb2a9477c66f75e2a162b0d3f06) @@ -0,0 +1,68 @@ +package edu.uoc.elc.lti.platform.ags; + +import edu.uoc.elc.lti.exception.UnauthorizedAgsCallException; +import edu.uoc.elc.lti.platform.ags.empty.EmptyResultServiceClient; +import edu.uoc.elc.lti.platform.ags.empty.EmptyScoreServiceClient; +import edu.uoc.elc.lti.platform.ags.empty.EmptyToolLineItemServiceClient; +import edu.uoc.elc.lti.tool.AssignmentGradeService; +import edu.uoc.elc.lti.tool.ResourceLink; +import edu.uoc.lti.ags.LineItemServiceClient; +import edu.uoc.lti.ags.ResultServiceClient; +import edu.uoc.lti.ags.ScoreServiceClient; +import lombok.RequiredArgsConstructor; + +import java.net.URI; +import java.net.URISyntaxException; + +/** + * @author xaracil@uoc.edu + */ +@RequiredArgsConstructor +public class AgsClientFactory { + private final AssignmentGradeService assignmentGradeService; + private final ResourceLink resourceLink; + + public GenericResultServiceClient getResultServiceClient(ResultServiceClient resultServiceClient) { + if (!hasAgsService()) { + return new EmptyResultServiceClient(); + } + return new GenericResultServiceClient(assignmentGradeService.canReadResults(), resultServiceClient); + } + + public GenericScoreServiceClient getScoreServiceClient(ScoreServiceClient scoreServiceClient) { + if (!hasAgsService()) { + return new EmptyScoreServiceClient(); + } + return new GenericScoreServiceClient(assignmentGradeService.canScore(), scoreServiceClient); + } + + public ToolLineItemServiceClient getLineItemServiceClient(LineItemServiceClient lineItemServiceClient) { + if (!hasAgsService()) { + return new EmptyToolLineItemServiceClient(); + } + return new ToolLineItemServiceClient(getServerUri(), + getResourceLinkId(), + assignmentGradeService.canReadLineItems(), + assignmentGradeService.canManageLineItems(), + lineItemServiceClient); + } + + public boolean hasAgsService() { + return assignmentGradeService != null; + } + + private URI getServerUri() { + try { + return new URI(assignmentGradeService.getLineitems()); + } catch (URISyntaxException e) { + throw new UnauthorizedAgsCallException("Lineitems URI is invalid"); + } + } + + private String getResourceLinkId() { + if (resourceLink != null) { + return resourceLink.getId(); + } + return null; + } +} Index: 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/platform/ags/GenericResultServiceClient.java =================================================================== diff -u --- 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/platform/ags/GenericResultServiceClient.java (revision 0) +++ 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/platform/ags/GenericResultServiceClient.java (revision 22d20e45f4a7cbb2a9477c66f75e2a162b0d3f06) @@ -0,0 +1,24 @@ +package edu.uoc.elc.lti.platform.ags; + +import edu.uoc.elc.lti.exception.UnauthorizedAgsCallException; +import edu.uoc.lti.ags.Result; +import edu.uoc.lti.ags.ResultServiceClient; +import lombok.RequiredArgsConstructor; + +import java.util.List; + +/** + * @author xaracil@uoc.edu + */ +@RequiredArgsConstructor +public class GenericResultServiceClient { + private final boolean canRead; + private final ResultServiceClient resultServiceClient; + + public List getLineItemResults(String id, Integer limit, Integer page, String userId) { + if (!canRead) { + throw new UnauthorizedAgsCallException("getLineItemResults"); + } + return resultServiceClient.getLineItemResults(id, limit, page, userId); + } +} Index: 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/platform/ags/GenericScoreServiceClient.java =================================================================== diff -u --- 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/platform/ags/GenericScoreServiceClient.java (revision 0) +++ 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/platform/ags/GenericScoreServiceClient.java (revision 22d20e45f4a7cbb2a9477c66f75e2a162b0d3f06) @@ -0,0 +1,22 @@ +package edu.uoc.elc.lti.platform.ags; + +import edu.uoc.elc.lti.exception.UnauthorizedAgsCallException; +import edu.uoc.lti.ags.Score; +import edu.uoc.lti.ags.ScoreServiceClient; +import lombok.RequiredArgsConstructor; + +/** + * @author xaracil@uoc.edu + */ +@RequiredArgsConstructor +public class GenericScoreServiceClient { + private final boolean canScore; + private final ScoreServiceClient scoreServiceClient; + + public boolean score(String lineItemId, Score score) { + if (!canScore) { + throw new UnauthorizedAgsCallException("score"); + } + return scoreServiceClient.score(lineItemId, score); + } +} Index: 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/platform/ags/ToolLineItemServiceClient.java =================================================================== diff -u --- 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/platform/ags/ToolLineItemServiceClient.java (revision 0) +++ 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/platform/ags/ToolLineItemServiceClient.java (revision 22d20e45f4a7cbb2a9477c66f75e2a162b0d3f06) @@ -0,0 +1,61 @@ +package edu.uoc.elc.lti.platform.ags; + +import edu.uoc.elc.lti.exception.UnauthorizedAgsCallException; +import edu.uoc.lti.ags.LineItem; +import edu.uoc.lti.ags.LineItemServiceClient; +import lombok.RequiredArgsConstructor; + +import java.net.URI; +import java.util.List; + +/** + * @author xaracil@uoc.edu + */ +@RequiredArgsConstructor +public class ToolLineItemServiceClient { + private final URI lineItemsUri; + private final String resourceLinkId; + private final boolean canRead; + private final boolean canManage; + private final LineItemServiceClient lineItemServiceClient; + + public List getLineItems(Integer limit, Integer page, String tag, String resourceId) { + if (!canRead) { + throw new UnauthorizedAgsCallException("getLineItems"); + } + lineItemServiceClient.setServiceUri(lineItemsUri); + return lineItemServiceClient.getLineItems(limit, page, resourceLinkId, tag, resourceId); + } + + public LineItem createLineItem(LineItem lineItem) { + if (!canManage) { + throw new UnauthorizedAgsCallException("createLineItem"); + } + lineItemServiceClient.setServiceUri(lineItemsUri); + return lineItemServiceClient.createLineItem(lineItem); + } + + public LineItem getLineItem(String id) { + if (!canRead) { + throw new UnauthorizedAgsCallException("getLineItems"); + } + lineItemServiceClient.setServiceUri(lineItemsUri); + return lineItemServiceClient.getLineItem(id); + } + + public LineItem updateLineItem(String id, LineItem lineItem) { + if (!canManage) { + throw new UnauthorizedAgsCallException("updateLineItem"); + } + lineItemServiceClient.setServiceUri(lineItemsUri); + return lineItemServiceClient.updateLineItem(id, lineItem); + } + + public void deleteLineItem(String id) { + if (!canManage) { + throw new UnauthorizedAgsCallException("deleteLineItem"); + } + lineItemServiceClient.setServiceUri(lineItemsUri); + lineItemServiceClient.deleteLineItem(id); + } +} Index: 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/platform/ags/empty/EmptyResultServiceClient.java =================================================================== diff -u --- 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/platform/ags/empty/EmptyResultServiceClient.java (revision 0) +++ 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/platform/ags/empty/EmptyResultServiceClient.java (revision 22d20e45f4a7cbb2a9477c66f75e2a162b0d3f06) @@ -0,0 +1,12 @@ +package edu.uoc.elc.lti.platform.ags.empty; + +import edu.uoc.elc.lti.platform.ags.GenericResultServiceClient; + +/** + * @author xaracil@uoc.edu + */ +public class EmptyResultServiceClient extends GenericResultServiceClient { + public EmptyResultServiceClient() { + super(false, null); + } +} Index: 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/platform/ags/empty/EmptyScoreServiceClient.java =================================================================== diff -u --- 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/platform/ags/empty/EmptyScoreServiceClient.java (revision 0) +++ 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/platform/ags/empty/EmptyScoreServiceClient.java (revision 22d20e45f4a7cbb2a9477c66f75e2a162b0d3f06) @@ -0,0 +1,13 @@ +package edu.uoc.elc.lti.platform.ags.empty; + +import edu.uoc.elc.lti.platform.ags.GenericScoreServiceClient; + +/** + * @author xaracil@uoc.edu + */ +public class EmptyScoreServiceClient extends GenericScoreServiceClient { + + public EmptyScoreServiceClient() { + super(false, null); + } +} Index: 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/platform/ags/empty/EmptyToolLineItemServiceClient.java =================================================================== diff -u --- 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/platform/ags/empty/EmptyToolLineItemServiceClient.java (revision 0) +++ 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/platform/ags/empty/EmptyToolLineItemServiceClient.java (revision 22d20e45f4a7cbb2a9477c66f75e2a162b0d3f06) @@ -0,0 +1,12 @@ +package edu.uoc.elc.lti.platform.ags.empty; + +import edu.uoc.elc.lti.platform.ags.ToolLineItemServiceClient; + +/** + * @author xaracil@uoc.edu + */ +public class EmptyToolLineItemServiceClient extends ToolLineItemServiceClient { + public EmptyToolLineItemServiceClient() { + super(null, null, false, false, null); + } +} Index: 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/platform/deeplinking/DeepLinkingClient.java =================================================================== diff -u --- 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/platform/deeplinking/DeepLinkingClient.java (revision 0) +++ 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/platform/deeplinking/DeepLinkingClient.java (revision 22d20e45f4a7cbb2a9477c66f75e2a162b0d3f06) @@ -0,0 +1,66 @@ +package edu.uoc.elc.lti.platform.deeplinking; + +import edu.uoc.elc.lti.exception.InvalidLTICallException; +import edu.uoc.elc.lti.tool.deeplinking.Settings; +import edu.uoc.lti.deeplink.DeepLinkingResponse; +import edu.uoc.lti.deeplink.DeepLinkingTokenBuilder; +import edu.uoc.lti.deeplink.content.Item; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.net.MalformedURLException; +import java.net.URL; +import java.util.ArrayList; +import java.util.List; + +/** + * @author xaracil@uoc.edu + */ +@RequiredArgsConstructor +public class DeepLinkingClient { + + private final DeepLinkingTokenBuilder deepLinkingTokenBuilder; + + private final String platformName; + private final String clientId; + private final String azp; + + private final String deploymentId; + private final String nonce; + private final Settings settings; + + @Getter + private List itemList = new ArrayList<>(); + + public boolean canAddItem() { + return settings.isAccept_multiple() || itemList.size() == 0; + } + + public void addItem(Item item) { + // check for multiple content item + if (!canAddItem()) { + throw new InvalidLTICallException("Platform doesn't allow multiple content items"); + } + + ItemValidator itemValidator = ItemValidatorFactory.itemValidatorFor(item, settings); + if (!itemValidator.isValid(item)) { + throw new InvalidLTICallException(itemValidator.getMessage()); + } + + itemList.add(item); + } + + public URL getReturnUrl() { + try { + return new URL(settings.getDeep_link_return_url()); + } catch (MalformedURLException e) { + throw new InvalidLTICallException(e.getMessage()); + } + } + + public String buildJWT() { + DeepLinkingResponse deepLinkingResponse = new DeepLinkingResponse(platformName, + clientId, azp, deploymentId, nonce, settings.getData(), itemList); + return deepLinkingTokenBuilder.build(deepLinkingResponse); + } +} Index: 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/platform/deeplinking/FileItemValidator.java =================================================================== diff -u --- 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/platform/deeplinking/FileItemValidator.java (revision 0) +++ 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/platform/deeplinking/FileItemValidator.java (revision 22d20e45f4a7cbb2a9477c66f75e2a162b0d3f06) @@ -0,0 +1,38 @@ +package edu.uoc.elc.lti.platform.deeplinking; + +import edu.uoc.elc.lti.tool.deeplinking.Settings; +import edu.uoc.lti.deeplink.content.FileItem; +import edu.uoc.lti.deeplink.content.Item; + +import java.util.List; + +/** + * @author xaracil@uoc.edu + */ +public class FileItemValidator extends ItemValidator { + public FileItemValidator(Settings settings) { + super(settings); + } + + @Override + public boolean isValid(Item item) { + boolean valid = super.isValid(item); + valid = valid && fileItemIsValid((FileItem) item); + return valid; + } + + private boolean fileItemIsValid(FileItem item) { + if (!mediaTypeIsValid(item.getMediaType())) { + message = "Platform doesn't allow file content items of media type " + item.getMediaType(); + return false; + } + + return true; + } + + private boolean mediaTypeIsValid(String type) { + final List acceptMediaTypes = settings.getAccept_media_types(); + return (acceptMediaTypes != null && acceptMediaTypes.size() > 0 ? acceptMediaTypes.contains(type) : true); + } + +} Index: 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/platform/deeplinking/ItemValidator.java =================================================================== diff -u --- 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/platform/deeplinking/ItemValidator.java (revision 0) +++ 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/platform/deeplinking/ItemValidator.java (revision 22d20e45f4a7cbb2a9477c66f75e2a162b0d3f06) @@ -0,0 +1,37 @@ +package edu.uoc.elc.lti.platform.deeplinking; + +import edu.uoc.elc.lti.tool.deeplinking.Settings; +import edu.uoc.lti.deeplink.content.Item; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.util.List; + +/** + * @author xaracil@uoc.edu + */ +@RequiredArgsConstructor +public class ItemValidator { + final Settings settings; + + @Getter + String message; + + public boolean isValid(Item item) { + if (!accessTypeIsValid(item.getType())) { + message = "Platform doesn't allow content items of type " + item.getType(); + return false; + } + return true; + } + + private boolean accessTypeIsValid(String type) { + return settings.getAccept_types().contains(type); + } + + protected boolean documentTargetIsValid(String presentation) { + final List acceptPresentationDocumentTargets = settings.getAccept_presentation_document_targets(); + return (acceptPresentationDocumentTargets != null && acceptPresentationDocumentTargets.size() > 0 ? acceptPresentationDocumentTargets.contains(presentation) : true); + } + +} Index: 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/platform/deeplinking/ItemValidatorFactory.java =================================================================== diff -u --- 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/platform/deeplinking/ItemValidatorFactory.java (revision 0) +++ 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/platform/deeplinking/ItemValidatorFactory.java (revision 22d20e45f4a7cbb2a9477c66f75e2a162b0d3f06) @@ -0,0 +1,26 @@ +package edu.uoc.elc.lti.platform.deeplinking; + +import edu.uoc.elc.lti.tool.deeplinking.Settings; +import edu.uoc.lti.deeplink.content.FileItem; +import edu.uoc.lti.deeplink.content.Item; +import edu.uoc.lti.deeplink.content.LinkItem; +import edu.uoc.lti.deeplink.content.LtiResourceItem; + +/** + * @author xaracil@uoc.edu + */ +public class ItemValidatorFactory { + public static ItemValidator itemValidatorFor(Item item, Settings settings) { + if (item instanceof FileItem) { + return new FileItemValidator(settings); + } + if (item instanceof LinkItem) { + return new LinkItemValidator(settings); + } + if (item instanceof LtiResourceItem) { + return new LtiResourceItemValidator(settings); + } + + return new ItemValidator(settings); + } +} Index: 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/platform/deeplinking/LinkItemValidator.java =================================================================== diff -u --- 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/platform/deeplinking/LinkItemValidator.java (revision 0) +++ 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/platform/deeplinking/LinkItemValidator.java (revision 22d20e45f4a7cbb2a9477c66f75e2a162b0d3f06) @@ -0,0 +1,44 @@ +package edu.uoc.elc.lti.platform.deeplinking; + +import edu.uoc.elc.lti.tool.deeplinking.Settings; +import edu.uoc.lti.deeplink.content.Item; +import edu.uoc.lti.deeplink.content.LinkItem; + +/** + * @author xaracil@uoc.edu + */ +public class LinkItemValidator extends ItemValidator { + public LinkItemValidator(Settings settings) { + super(settings); + } + + @Override + public boolean isValid(Item item) { + boolean valid = super.isValid(item); + valid = valid && linkItemIsValid((LinkItem) item); + return valid; + } + + private boolean linkItemIsValid(LinkItem item) { + if (item.getEmbed() != null) { + if (!documentTargetIsValid("embed")) { + message = "Platform doesn't allow content items with document target embed"; + return false; + } + } + if (item.getIframe() != null) { + if (!documentTargetIsValid("iframe")) { + message = "Platform doesn't allow content items with document target iframe"; + return false; + } + } + if (item.getWindow() != null) { + if (!documentTargetIsValid("window")) { + message = "Platform doesn't allow content items with document target window"; + return false; + } + } + + return true; + } +} Index: 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/platform/deeplinking/LtiResourceItemValidator.java =================================================================== diff -u --- 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/platform/deeplinking/LtiResourceItemValidator.java (revision 0) +++ 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/platform/deeplinking/LtiResourceItemValidator.java (revision 22d20e45f4a7cbb2a9477c66f75e2a162b0d3f06) @@ -0,0 +1,37 @@ +package edu.uoc.elc.lti.platform.deeplinking; + +import edu.uoc.elc.lti.tool.deeplinking.Settings; +import edu.uoc.lti.deeplink.content.Item; +import edu.uoc.lti.deeplink.content.LtiResourceItem; + +/** + * @author xaracil@uoc.edu + */ +public class LtiResourceItemValidator extends ItemValidator { + public LtiResourceItemValidator(Settings settings) { + super(settings); + } + + @Override + public boolean isValid(Item item) { + boolean valid = super.isValid(item); + valid = valid && ltiItemIsValid((LtiResourceItem) item); + return valid; + } + + private boolean ltiItemIsValid(LtiResourceItem item) { + if (item.getIFrame() != null) { + if (!documentTargetIsValid("iframe")) { + message = "Platform doesn't allow content items with document target iframe"; + return false; + } + } + if (item.getWindow() != null) { + if (!documentTargetIsValid("window")) { + message = "Platform doesn't allow content items with document target window"; + return false; + } + } + return true; + } +} Index: 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/tool/AssignmentGradeService.java =================================================================== diff -u --- 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/tool/AssignmentGradeService.java (revision 0) +++ 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/tool/AssignmentGradeService.java (revision 22d20e45f4a7cbb2a9477c66f75e2a162b0d3f06) @@ -0,0 +1,33 @@ +package edu.uoc.elc.lti.tool; + +import lombok.Getter; +import lombok.Setter; + +import java.util.List; + +/** + * @author xaracil@uoc.edu + */ +@Getter +@Setter +public class AssignmentGradeService { + private List scope; + private String lineitems; + private String lineitem; + + public boolean canReadResults() { + return scope != null && scope.contains(ScopeEnum.AGS_SCOPE_RESULT.getScope()); + } + + public boolean canReadLineItems() { + return canManageLineItems() || (scope != null && scope.contains(ScopeEnum.AGS_SCOPE_LINE_ITEM_READONLY.getScope())); + } + + public boolean canManageLineItems() { + return scope != null && scope.contains(ScopeEnum.AGS_SCOPE_LINE_ITEM.getScope()); + } + + public boolean canScore() { + return scope != null && scope.contains(ScopeEnum.AGS_SCOPE_SCORE.getScope()); + } +} Index: 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/tool/AuthenticationResponseValidator.java =================================================================== diff -u --- 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/tool/AuthenticationResponseValidator.java (revision 0) +++ 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/tool/AuthenticationResponseValidator.java (revision 22d20e45f4a7cbb2a9477c66f75e2a162b0d3f06) @@ -0,0 +1,123 @@ +package edu.uoc.elc.lti.tool; + +import edu.uoc.lti.claims.ClaimAccessor; +import edu.uoc.lti.claims.ClaimsEnum; +import edu.uoc.lti.oidc.OIDCLaunchSession; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.util.Arrays; +import java.util.Date; +import java.util.List; + +/** + * @author xaracil@uoc.edu + */ +@RequiredArgsConstructor +public class AuthenticationResponseValidator { + private final ToolDefinition toolDefinition; + private final ClaimAccessor claimAccessor; + + @Getter + private String reason; + + + /** + * Validates token, using rules from https://www.imsglobal.org/spec/security/v1p0/#authentication-response-validation + * @param token token to validate + * @return true if token is valid, false otherwise + */ + public boolean validate(String token) { + /** + * 1. The Tool MUST Validate the signature of the ID Token according to JSON Web Signature [RFC7515], + * Section 5.2 using the Public Key from the Platform; + */ + try { + this.claimAccessor.decode(token); + + } catch (Throwable ex) { + reason = "Invalid token"; + return false; + } + + /** + * 2. The Issuer Identifier for the Platform MUST exactly match the value of the iss (Issuer) Claim + * (therefore the Tool MUST previously have been made aware of this identifier); + */ + if (!this.toolDefinition.getPlatform().equals(this.claimAccessor.getIssuer())) { + reason = "Issuer invalid"; + return false; + } + + /** 3. The Tool MUST validate that the aud (audience) Claim contains its client_id value registered as an + * audience with the Issuer identified by the iss (Issuer) Claim. The aud (audience) Claim MAY contain an array + * with more than one element. The Tool MUST reject the ID Token if it does not list the client_id as a valid + * audience, or if it contains additional audiences not trusted by the Tool. The request message will be + * rejected with a HTTP code of 401; + */ + final String audienceClaim = this.claimAccessor.getAudience(); + final List audiences = Arrays.asList(audienceClaim.split(",")); + if (!audiences.contains(this.toolDefinition.getClientId())) { + reason = "Audience invalid"; + return false; + } + + /** + * 4. If the ID Token contains multiple audiences, the Tool SHOULD verify that an azp Claim is present; + */ + if (audiences.size() > 1) { + if (this.claimAccessor.getAzp() == null) { + reason = "Azp claim is not present"; + return false; + } + } + + /** + * 5. If an azp (authorized party) Claim is present, the Tool SHOULD verify that its client_id is the Claim's value + */ + if (this.claimAccessor.getAzp() != null) { + if (!this.toolDefinition.getClientId().equals(this.claimAccessor.getAzp())) { + reason = "Azp invalid"; + return false; + } + } + + + /** + * 6. The alg value SHOULD be the default of RS256 or the algorithm sent by the Tool in + * the id_token_signed_response_alg parameter during its registration. Use of algorithms other that RS256 will + * limit interoperability + * + * Developer's note: Not doing this + */ + + /** + * 7. The current time MUST be before the time represented by the exp Claim; + */ + if (this.claimAccessor.getExpiration().before(new Date())) { + reason = "Expired"; + return false; + } + + /** + * 8. The Tool MAY use the iat Claim to reject tokens that were issued too far away from the current time, + * limiting the amount of time that it needs to store nonces used to prevent attacks. + * The Tool MAY define its own acceptable time range; + * + * Developer's note: Already done in ClaimAccessor + */ + + + /** + * 9. The ID Token MUST contain a nonce Claim. The Tool SHOULD verify that it has not yet received this nonce + * value (within a Tool-defined time window), in order to help prevent replay attacks. The Tool MAY define its own + * precise method for detecting replay attacks. + */ + if (this.claimAccessor.get(ClaimsEnum.NONCE) == null) { + reason = "Nonce not present"; + return false; + } + + return true; + } +} Index: 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/tool/Context.java =================================================================== diff -u --- 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/tool/Context.java (revision 0) +++ 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/tool/Context.java (revision 22d20e45f4a7cbb2a9477c66f75e2a162b0d3f06) @@ -0,0 +1,19 @@ +package edu.uoc.elc.lti.tool; + +import lombok.Getter; +import lombok.Setter; + +import java.util.List; + +/** + * @author xaracil@uoc.edu + */ +@Getter +@Setter +public class Context { + private String id; + private String label; + private String title; + private List type; + +} Index: 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/tool/LaunchValidator.java =================================================================== diff -u --- 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/tool/LaunchValidator.java (revision 0) +++ 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/tool/LaunchValidator.java (revision 22d20e45f4a7cbb2a9477c66f75e2a162b0d3f06) @@ -0,0 +1,64 @@ +package edu.uoc.elc.lti.tool; + +import edu.uoc.elc.lti.tool.validator.LaunchValidatable; +import edu.uoc.elc.lti.tool.validator.MessageTypesValidatorEnum; +import edu.uoc.lti.MessageTypesEnum; +import edu.uoc.lti.claims.ClaimAccessor; +import edu.uoc.lti.claims.ClaimsEnum; +import edu.uoc.lti.deeplink.content.DocumentTargetEnum; +import edu.uoc.lti.deeplink.content.Presentation; +import edu.uoc.lti.oidc.OIDCLaunchSession; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +/** + * @author xaracil@uoc.edu + */ +@RequiredArgsConstructor +public class LaunchValidator { + private final ToolDefinition toolDefinition; + private final ClaimAccessor claimAccessor; + private final OIDCLaunchSession oidcLaunchSession; + + @Getter + private String reason; + + /** + * Validates a LTI 1.3 launch + * @param token JWT token to validate + * @param state saved state, if present + * @return true if token is a valid LTI 1.3 launch, false otherwise + */ + public boolean validate(String token, String state) { + AuthenticationResponseValidator authenticationResponseValidator = new AuthenticationResponseValidator(toolDefinition, claimAccessor); + + // IMS Security validations + if (!authenticationResponseValidator.validate(token)) { + this.reason = authenticationResponseValidator.getReason(); + return false; + } + + final String messageTypeClaim = this.claimAccessor.get(ClaimsEnum.MESSAGE_TYPE); + final LaunchValidatable validator = MessageTypesValidatorEnum.getValidator(messageTypeClaim); + if (validator == null) { + setReasonToInvalidClaim(ClaimsEnum.MESSAGE_TYPE); + return false; + } + + if (!validator.validate(state, toolDefinition, claimAccessor, oidcLaunchSession)) { + this.reason = validator.getReason(); + return false; + } + + return true; + } + + private void setReasonToInvalidClaim(ClaimsEnum claim) { + reason = "Claim " + claim.getName() + " is invalid"; + } + +} Index: 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/tool/NamesRoleService.java =================================================================== diff -u --- 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/tool/NamesRoleService.java (revision 0) +++ 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/tool/NamesRoleService.java (revision 22d20e45f4a7cbb2a9477c66f75e2a162b0d3f06) @@ -0,0 +1,16 @@ +package edu.uoc.elc.lti.tool; + +import lombok.Getter; +import lombok.Setter; + +import java.util.List; + +/** + * @author xaracil@uoc.edu + */ +@Getter +@Setter +public class NamesRoleService { + private String context_memberships_url; + private List service_versions; +} Index: 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/tool/Platform.java =================================================================== diff -u --- 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/tool/Platform.java (revision 0) +++ 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/tool/Platform.java (revision 22d20e45f4a7cbb2a9477c66f75e2a162b0d3f06) @@ -0,0 +1,19 @@ +package edu.uoc.elc.lti.tool; + +import lombok.Getter; +import lombok.Setter; + +/** + * @author xaracil@uoc.edu + */ +@Getter +@Setter +public class Platform { + private String guid; + private String name; + private String contact_email; + private String description; + private String url; + private String product_family_code; + private String version; +} Index: 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/tool/ResourceLink.java =================================================================== diff -u --- 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/tool/ResourceLink.java (revision 0) +++ 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/tool/ResourceLink.java (revision 22d20e45f4a7cbb2a9477c66f75e2a162b0d3f06) @@ -0,0 +1,15 @@ +package edu.uoc.elc.lti.tool; + +import lombok.Getter; +import lombok.Setter; + +/** + * @author xaracil@uoc.edu + */ +@Getter +@Setter +public class ResourceLink { + private String id; + private String title; + private String description; +} Index: 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/tool/RolesEnum.java =================================================================== diff -u --- 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/tool/RolesEnum.java (revision 0) +++ 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/tool/RolesEnum.java (revision 22d20e45f4a7cbb2a9477c66f75e2a162b0d3f06) @@ -0,0 +1,66 @@ +package edu.uoc.elc.lti.tool; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * @author xaracil@uoc.edu + */ +@AllArgsConstructor +@Getter +public enum RolesEnum { + // System roles + ACCOUNT_ADMIN("http://purl.imsglobal.org/vocab/lis/v2/system/person#AccountAdmin"), + CREATOR("http://purl.imsglobal.org/vocab/lis/v2/system/person#Creator"), + SYS_ADMIN("http://purl.imsglobal.org/vocab/lis/v2/system/person#SysAdmin"), + SYS_SUPPORT("http://purl.imsglobal.org/vocab/lis/v2/system/person#SysSupport"), + USER("http://purl.imsglobal.org/vocab/lis/v2/system/person#User"), + + // Institution roles + INSTITUTION_ADMINISTRATOR("http://purl.imsglobal.org/vocab/lis/v2/institution/person#Administrator"), + FACULTY("http://purl.imsglobal.org/vocab/lis/v2/institution/person#Faculty"), + GUEST("http://purl.imsglobal.org/vocab/lis/v2/institution/person#Guest"), + NONE("http://purl.imsglobal.org/vocab/lis/v2/institution/person#None"), + OTHER("http://purl.imsglobal.org/vocab/lis/v2/institution/person#Other"), + STAFF("http://purl.imsglobal.org/vocab/lis/v2/institution/person#Staff"), + STUDENT("http://purl.imsglobal.org/vocab/lis/v2/institution/person#Student"), + ALUMNI("http://purl.imsglobal.org/vocab/lis/v2/institution/person#Alumni"), + INSTITUTION_INSTRUCTOR("http://purl.imsglobal.org/vocab/lis/v2/institution/person#Instructor"), + INSTITUTION_LEARNER("http://purl.imsglobal.org/vocab/lis/v2/institution/person#Learner"), + INSTITUTION_MEMBER("http://purl.imsglobal.org/vocab/lis/v2/institution/person#Member"), + INSTITUTION_MENTOR("http://purl.imsglobal.org/vocab/lis/v2/institution/person#Mentor"), + OBSERVER("http://purl.imsglobal.org/vocab/lis/v2/institution/person#Observer"), + PROSPECTIVE_STUDENT("http://purl.imsglobal.org/vocab/lis/v2/institution/person#ProspectiveStudent"), + + // Context roles + ADMINISTRATOR("http://purl.imsglobal.org/vocab/lis/v2/membership#Administrator"), + CONTENT_DEVELOPER("http://purl.imsglobal.org/vocab/lis/v2/membership#ContentDeveloper"), + INSTRUCTOR("http://purl.imsglobal.org/vocab/lis/v2/membership#Instructor"), + LEARNER("http://purl.imsglobal.org/vocab/lis/v2/membership#Learner"), + MENTOR("http://purl.imsglobal.org/vocab/lis/v2/membership#Mentor"), + MANAGER("http://purl.imsglobal.org/vocab/lis/v2/membership#Manager"), + MEMBER("http://purl.imsglobal.org/vocab/lis/v2/membership#Member"), + OFFICER("http://purl.imsglobal.org/vocab/lis/v2/membership#Officer"), + + // Soon to deprecate context roles + OLD_ADMINISTRATOR("Administrator"), + OLD_CONTENT_DEVELOPER("ContentDeveloper"), + OLD_INSTRUCTOR("Instructor"), + OLD_LEARNER("Learner"), + OLD_MENTOR("Mentor"), + OLD_MANAGER("Manager"), + OLD_MEMBER("Member"), + OLD_OFFICER("Officer") + ; + + private String name; + + public static RolesEnum from(String name) { + for (RolesEnum rolesEnum: values()) { + if (rolesEnum.getName().equals(name)) { + return rolesEnum; + } + } + return null; + } +} Index: 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/tool/ScopeEnum.java =================================================================== diff -u --- 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/tool/ScopeEnum.java (revision 0) +++ 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/tool/ScopeEnum.java (revision 22d20e45f4a7cbb2a9477c66f75e2a162b0d3f06) @@ -0,0 +1,22 @@ +package edu.uoc.elc.lti.tool; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * @author xaracil@uoc.edu + */ +@Getter +@AllArgsConstructor +public enum ScopeEnum { + + AGS_SCOPE_LINE_ITEM ("https://purl.imsglobal.org/spec/lti-ags/scope/lineitem"), + AGS_SCOPE_LINE_ITEM_READONLY ("https://purl.imsglobal.org/spec/lti-ags/scope/lineitem.readonly"), + AGS_SCOPE_RESULT ("https://purl.imsglobal.org/spec/lti-ags/scope/result.readonly"), + AGS_SCOPE_SCORE ("https://purl.imsglobal.org/spec/lti-ags/scope/score"), + NAMES_AND_ROLES_SCOPE ("https://purl.imsglobal.org/spec/lti-nrps/scope/contextmembership.readonly"), + CALIPER_SCOPE ("https://purl.imsglobal.org/spec/lti-ces/v1p0/scope/send") + ; + + private String scope; +} Index: 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/tool/Tool.java =================================================================== diff -u --- 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/tool/Tool.java (revision 0) +++ 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/tool/Tool.java (revision 22d20e45f4a7cbb2a9477c66f75e2a162b0d3f06) @@ -0,0 +1,234 @@ +package edu.uoc.elc.lti.tool; + +import edu.uoc.elc.lti.exception.BadToolProviderConfigurationException; +import edu.uoc.elc.lti.platform.accesstoken.AccessTokenRequestHandler; +import edu.uoc.elc.lti.platform.accesstoken.AccessTokenResponse; +import edu.uoc.elc.lti.platform.ags.AgsClientFactory; +import edu.uoc.elc.lti.platform.deeplinking.DeepLinkingClient; +import edu.uoc.elc.lti.tool.deeplinking.Settings; +import edu.uoc.elc.lti.tool.oidc.AuthRequestUrlBuilder; +import edu.uoc.elc.lti.tool.oidc.LoginRequest; +import edu.uoc.elc.lti.tool.oidc.LoginResponse; +import edu.uoc.lti.MessageTypesEnum; +import edu.uoc.lti.claims.ClaimAccessor; +import edu.uoc.lti.claims.ClaimsEnum; +import edu.uoc.lti.oidc.OIDCLaunchSession; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.io.IOException; +import java.math.BigInteger; +import java.net.URI; +import java.net.URISyntaxException; +import java.security.SecureRandom; +import java.util.Date; +import java.util.List; +import java.util.Map; + +/** + * @author xaracil@uoc.edu + */ +@RequiredArgsConstructor +public class Tool { + @Getter + String issuer; + @Getter + String audience; + + @Getter + String kid; + + @Getter + Date issuedAt; + + @Getter + Date expiresAt; + + @Getter + private User user; + + @Getter + private String locale; + + @Getter + private boolean valid; + + @Getter + private String reason; + + private AccessTokenResponse accessTokenResponse; + + private final ToolDefinition toolDefinition; + private final ClaimAccessor claimAccessor; + private final OIDCLaunchSession oidcLaunchSession; + private final ToolBuilders toolBuilders; + + public boolean validate(String token, String state) { + LaunchValidator launchValidator = new LaunchValidator(toolDefinition, claimAccessor, oidcLaunchSession); + this.valid = launchValidator.validate(token, state); + if (!this.valid) { + this.reason = launchValidator.getReason(); + return false; + } + + // get the standard JWT payload claims + this.issuer = this.claimAccessor.getIssuer(); + this.audience = this.claimAccessor.getAudience(); + this.issuedAt = this.claimAccessor.getIssuedAt(); + this.expiresAt = this.claimAccessor.getExpiration(); + + // create the user attribute + createUser(this.claimAccessor.getSubject()); + + // update locale attribute + this.locale = this.claimAccessor.get(ClaimsEnum.LOCALE); + + return this.valid; + } + + public AccessTokenResponse getAccessToken() throws IOException, BadToolProviderConfigurationException { + if (!this.isValid()) { + return null; + } + + if (accessTokenResponse == null) { + AccessTokenRequestHandler accessTokenRequestHandler = new AccessTokenRequestHandler(kid, + toolDefinition, + toolBuilders.getClientCredentialsTokenBuilder(), + toolBuilders.getAccessTokenRequestBuilder()); + accessTokenResponse = accessTokenRequestHandler.getAccessToken(); + } + + return accessTokenResponse; + } + + private void createUser(String subject) { + this.user = User.builder() + .id(subject) + .givenName(this.claimAccessor.get(ClaimsEnum.GIVEN_NAME)) + .familyName(this.claimAccessor.get(ClaimsEnum.FAMILY_NAME)) + .middleName(this.claimAccessor.get(ClaimsEnum.MIDDLE_NAME)) + .picture(this.claimAccessor.get(ClaimsEnum.PICTURE)) + .email(this.claimAccessor.get(ClaimsEnum.EMAIL)) + .name(this.claimAccessor.get(ClaimsEnum.NAME)) + .build(); + } + + + // general claims getters + public Platform getPlatform() { + return this.claimAccessor.get(ClaimsEnum.TOOL_PLATFORM, Platform.class); + } + + public Context getContext() { + return this.claimAccessor.get(ClaimsEnum.CONTEXT, Context.class); + } + + public ResourceLink getResourceLink() { + return this.claimAccessor.get(ClaimsEnum.RESOURCE_LINK, ResourceLink.class); + } + + public NamesRoleService getNameRoleService() { + return this.claimAccessor.get(ClaimsEnum.NAMES_ROLE_SERVICE, NamesRoleService.class); + } + + public AssignmentGradeService getAssignmentGradeService() { + return this.claimAccessor.get(ClaimsEnum.ASSIGNMENT_GRADE_SERVICE, AssignmentGradeService.class); + } + + public String getDeploymentId() { + if (!isDeepLinkingRequest()) { + return null; + } + return this.claimAccessor.get(ClaimsEnum.DEPLOYMENT_ID); + } + + public Settings getDeepLinkingSettings() { + if (!isDeepLinkingRequest()) { + return null; + } + return this.claimAccessor.get(ClaimsEnum.DEEP_LINKING_SETTINGS, Settings.class); + } + + + public List getRoles() { + Class> rolesClass = (Class) List.class; + return this.claimAccessor.get(ClaimsEnum.ROLES, rolesClass); + } + + public String getCustomParameter(String name) { + Class> customClass = (Class) Map.class; + final Map claim = this.claimAccessor.get(ClaimsEnum.CUSTOM, customClass); + if (claim != null) { + return claim.get(name); + } + return null; + } + + public MessageTypesEnum getMessageType() { + try { + return MessageTypesEnum.valueOf(this.claimAccessor.get(ClaimsEnum.MESSAGE_TYPE)); + } catch (IllegalArgumentException ignored) { + return null; + } + } + + public boolean isDeepLinkingRequest() { + return MessageTypesEnum.LtiDeepLinkingRequest == getMessageType(); + } + + public boolean isResourceLinkLaunch() { + return MessageTypesEnum.LtiResourceLinkRequest == getMessageType(); + } + + public DeepLinkingClient getDeepLinkingClient() { + if (!isDeepLinkingRequest()) { + return null; + } + + return new DeepLinkingClient( + toolBuilders.getDeepLinkingTokenBuilder(), + getIssuer(), + toolDefinition.getClientId(), + this.claimAccessor.getAzp(), + getDeploymentId(), + this.claimAccessor.get(ClaimsEnum.NONCE), + getDeepLinkingSettings()); + } + + public AgsClientFactory getAssignmentGradeServiceClientFactory() { + return new AgsClientFactory(getAssignmentGradeService(), + getResourceLink()); + } + + // roles commodity methods + public boolean isLearner() { + return getRoles() != null && getRoles().contains(RolesEnum.LEARNER.getName()); + } + + public boolean isInstructor() { + return getRoles() != null && getRoles().contains(RolesEnum.INSTRUCTOR.getName()); + } + + // openid methods + public String getOidcAuthUrl(LoginRequest loginRequest) throws URISyntaxException { + final LoginResponse loginResponse = LoginResponse.builder() + .client_id(loginRequest.getClient_id() != null ? loginRequest.getClient_id() : toolDefinition.getClientId()) + .redirect_uri(loginRequest.getTarget_link_uri()) + .login_hint(loginRequest.getLogin_hint()) + .state(new BigInteger(50, new SecureRandom()).toString(16)) + .nonce(new BigInteger(50, new SecureRandom()).toString(16)) + .lti_message_hint(loginRequest.getLti_message_hint()) + .build(); + + final URI uri = new URI(loginRequest.getTarget_link_uri()); + + // save in session + this.oidcLaunchSession.setState(loginResponse.getState()); + this.oidcLaunchSession.setNonce(loginResponse.getNonce()); + this.oidcLaunchSession.setTargetLinkUri(loginResponse.getRedirect_uri()); + + // return url + return AuthRequestUrlBuilder.build(toolDefinition.getOidcAuthUrl(), loginResponse); + } +} Index: 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/tool/ToolBuilders.java =================================================================== diff -u --- 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/tool/ToolBuilders.java (revision 0) +++ 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/tool/ToolBuilders.java (revision 22d20e45f4a7cbb2a9477c66f75e2a162b0d3f06) @@ -0,0 +1,18 @@ +package edu.uoc.elc.lti.tool; + +import edu.uoc.lti.accesstoken.AccessTokenRequestBuilder; +import edu.uoc.lti.clientcredentials.ClientCredentialsTokenBuilder; +import edu.uoc.lti.deeplink.DeepLinkingTokenBuilder; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +/** + * @author xaracil@uoc.edu + */ +@RequiredArgsConstructor +@Getter +public class ToolBuilders { + private final ClientCredentialsTokenBuilder clientCredentialsTokenBuilder; + private final AccessTokenRequestBuilder accessTokenRequestBuilder; + private final DeepLinkingTokenBuilder deepLinkingTokenBuilder; +} Index: 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/tool/ToolDefinition.java =================================================================== diff -u --- 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/tool/ToolDefinition.java (revision 0) +++ 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/tool/ToolDefinition.java (revision 22d20e45f4a7cbb2a9477c66f75e2a162b0d3f06) @@ -0,0 +1,21 @@ +package edu.uoc.elc.lti.tool; + +import lombok.Builder; +import lombok.Getter; + +/** + * @author xaracil@uoc.edu + */ +@Getter +@Builder +public class ToolDefinition { + private String clientId; + private String name; + private String platform; + private String keySetUrl; + private String accessTokenUrl; + private String oidcAuthUrl; + private String privateKey; + private String publicKey; + private String deploymentId; +} Index: 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/tool/User.java =================================================================== diff -u --- 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/tool/User.java (revision 0) +++ 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/tool/User.java (revision 22d20e45f4a7cbb2a9477c66f75e2a162b0d3f06) @@ -0,0 +1,21 @@ +package edu.uoc.elc.lti.tool; + +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; + +/** + * @author xaracil@uoc.edu + */ +@Getter +@Setter +@Builder +public class User { + private String id; + private String givenName; + private String familyName; + private String middleName; + private String picture; + private String email; + private String name; +} Index: 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/tool/deeplinking/Settings.java =================================================================== diff -u --- 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/tool/deeplinking/Settings.java (revision 0) +++ 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/tool/deeplinking/Settings.java (revision 22d20e45f4a7cbb2a9477c66f75e2a162b0d3f06) @@ -0,0 +1,24 @@ +package edu.uoc.elc.lti.tool.deeplinking; + +import lombok.Getter; +import lombok.Setter; + +import java.util.Arrays; +import java.util.List; + +/** + * @author xaracil@uoc.edu + */ +@Getter +@Setter +public class Settings { + private List accept_types; + private List accept_media_types; + private List accept_presentation_document_targets; + private boolean accept_multiple; + private boolean auto_create; + private String title; + private String text; + private String data; + private String deep_link_return_url; +} Index: 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/tool/oidc/AuthRequestUrlBuilder.java =================================================================== diff -u --- 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/tool/oidc/AuthRequestUrlBuilder.java (revision 0) +++ 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/tool/oidc/AuthRequestUrlBuilder.java (revision 22d20e45f4a7cbb2a9477c66f75e2a162b0d3f06) @@ -0,0 +1,30 @@ +package edu.uoc.elc.lti.tool.oidc; + +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; + +/** + * @author xaracil@uoc.edu + */ +public class AuthRequestUrlBuilder { + public static String build(String url, LoginResponse params) { + StringBuilder authUrl = new StringBuilder(url + "?"); + authUrl.append("scope=" + params.getScope()); + authUrl.append("&response_type=" + params.getResponse_type()); + authUrl.append("&client_id=" + params.getClient_id()); + try { + authUrl.append("&redirect_uri=" + URLEncoder.encode(params.getRedirect_uri(), "UTF-8")); + } catch (UnsupportedEncodingException e) { + authUrl.append("&redirect_uri=" + params.getRedirect_uri()); + } + authUrl.append("&login_hint=" + params.getLogin_hint()); + authUrl.append("&state=" + params.getState()); + authUrl.append("&response_mode=" + params.getResponse_mode()); + authUrl.append("&nonce=" + params.getNonce()); + authUrl.append("&prompt=" + params.getPrompt()); + if (params.getLti_message_hint() != null) { + authUrl.append("<i_message_hint=" + params.getLti_message_hint()); + } + return authUrl.toString(); + } +} Index: 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/tool/oidc/InMemoryOIDCLaunchSession.java =================================================================== diff -u --- 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/tool/oidc/InMemoryOIDCLaunchSession.java (revision 0) +++ 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/tool/oidc/InMemoryOIDCLaunchSession.java (revision 22d20e45f4a7cbb2a9477c66f75e2a162b0d3f06) @@ -0,0 +1,16 @@ +package edu.uoc.elc.lti.tool.oidc; + +import edu.uoc.lti.oidc.OIDCLaunchSession; +import lombok.Getter; +import lombok.Setter; + +/** + * @author xaracil@uoc.edu + */ +@Getter +@Setter +public class InMemoryOIDCLaunchSession implements OIDCLaunchSession { + private String state; + private String nonce; + private String targetLinkUri; +} Index: 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/tool/oidc/LoginRequest.java =================================================================== diff -u --- 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/tool/oidc/LoginRequest.java (revision 0) +++ 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/tool/oidc/LoginRequest.java (revision 22d20e45f4a7cbb2a9477c66f75e2a162b0d3f06) @@ -0,0 +1,51 @@ +package edu.uoc.elc.lti.tool.oidc; + +import lombok.Builder; +import lombok.Getter; +import lombok.ToString; + +/** + * @author xaracil@uoc.edu + */ +@Builder +@Getter +@ToString +public class LoginRequest { + /** REQUIRED. The issuer identifier identifying the learning platform. */ + private final String iss; + + /** REQUIRED. Hint to the Authorization Server about the login identifier the End-User might use to log in. + * The permitted values will be defined in the host specification.*/ + private final String login_hint; + + /** + * REQUIRED. The actual end-point that should be executed at the end of the OpenID Connect authentication flow. + */ + private final String target_link_uri; + + /** + * The new optional parameter lti_message_hint may be used alongside the login_hint to carry information about + * the actual LTI message that is being launched. + * + * Similarly to the login_hint parameter, lti_message_hint value is opaque to the tool. + * If present in the login initiation request, the tool MUST include it back in the authentication request unaltered. + */ + private String lti_message_hint; + + /** + * The new optional parameter lti_deployment_id that if included, MUST contain the same deployment id that would be + * passed in the https://purl.imsglobal.org/spec/lti/claim/deployment_id claim for the subsequent LTI message launch. + * + * This parameter may be used by the tool to perform actions that are dependant on a specific deployment. + * An example of this would be, using the deployment id to identify the region in which a tenant linked to the + * deployment lives. Subsequently changing the redirect_url the final launch will be directed to. + */ + private String lti_deployment_id; + + /** + * The new optional parameter client_id specifies the client id for the authorization server that should be used to + * authorize the subsequent LTI message request. This allows for a platform to support multiple registrations + * from a single issuer, without relying on the initiate_login_uri as a key + */ + private String client_id; +} Index: 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/tool/oidc/LoginResponse.java =================================================================== diff -u --- 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/tool/oidc/LoginResponse.java (revision 0) +++ 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/tool/oidc/LoginResponse.java (revision 22d20e45f4a7cbb2a9477c66f75e2a162b0d3f06) @@ -0,0 +1,35 @@ +package edu.uoc.elc.lti.tool.oidc; + +import lombok.Builder; +import lombok.Getter; +import lombok.ToString; + +/** + * @author xaracil@uoc.edu + */ +@Getter +@Builder +@ToString +public class LoginResponse { + private final String scope = "openid"; + private final String response_type = "id_token"; + /** The Tool’s Client ID for this issuer. */ + private final String client_id; + /** One of the registered redirect URIs. **/ + private final String redirect_uri; + /** As passed in the initiate login request.*/ + private final String login_hint; + /** Opaque value for the platform to maintain state between the request and callback and provide Cross-Site Request Forgery (CSRF) mitigation.**/ + private String state; + /** The Token can be lengthy and thus should be passed over as a form POST. */ + private final String response_mode = "form_post"; + /** String value used to associate a Client session with an ID Token, and to mitigate replay attacks. + * The value is passed through unmodified from the Authentication Request to the ID Token.*/ + private final String nonce; + /** + * Since the message launch is meant to be sent from a platform where the user is already logged in. + * If the user has no session, a platform must just fail the flow rather than ask the user to log in. + */ + private final String prompt = "none"; + private String lti_message_hint; +} Index: 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/tool/validator/DeepLinkingLaunchValidatable.java =================================================================== diff -u --- 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/tool/validator/DeepLinkingLaunchValidatable.java (revision 0) +++ 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/tool/validator/DeepLinkingLaunchValidatable.java (revision 22d20e45f4a7cbb2a9477c66f75e2a162b0d3f06) @@ -0,0 +1,70 @@ +package edu.uoc.elc.lti.tool.validator; + +import edu.uoc.elc.lti.tool.ToolDefinition; +import edu.uoc.elc.lti.tool.deeplinking.Settings; +import edu.uoc.lti.MessageTypesEnum; +import edu.uoc.lti.claims.ClaimAccessor; +import edu.uoc.lti.claims.ClaimsEnum; +import edu.uoc.lti.oidc.OIDCLaunchSession; + +import java.util.List; + +/** + * @author xaracil@uoc.edu + */ +public class DeepLinkingLaunchValidatable extends LTICoreValidator { + /** + * Validates request following https://www.imsglobal.org/spec/lti-dl/v2p0#deep-linking-request-message + * @param state saved state, if present + * @param toolDefinition {@link ToolDefinition} with the tool's data + * @param claimAccessor {@link ClaimAccessor} for accessing the claims + * @param oidcLaunchSession {@link OIDCLaunchSession} with the OIDC session + * @return true if the launch is valid, false otherwise + */ + @Override + public boolean validate(String state, ToolDefinition toolDefinition, ClaimAccessor claimAccessor, OIDCLaunchSession oidcLaunchSession) { + // Core validation + if (!super.validate(state, toolDefinition, claimAccessor, oidcLaunchSession)) { + return false; + } + + // 3.4.1 Deep linking settings + final Settings settings = claimAccessor.get(ClaimsEnum.DEEP_LINKING_SETTINGS, Settings.class); + if (settings == null) { + setReasonToMissingRequiredClaim(ClaimsEnum.DEEP_LINKING_SETTINGS); + return false; + } + if (isEmpty(settings.getDeep_link_return_url())) { + setReasonToInvalidClaim(ClaimsEnum.DEEP_LINKING_SETTINGS); + return false; + } + final List acceptTypes = settings.getAccept_types(); + if (acceptTypes == null || acceptTypes.size() == 0) { + setReasonToInvalidClaim(ClaimsEnum.DEEP_LINKING_SETTINGS); + return false; + } + final List acceptPresentationDocumentTargets = settings.getAccept_presentation_document_targets(); + if (acceptPresentationDocumentTargets == null || acceptPresentationDocumentTargets.size() == 0) { + setReasonToInvalidClaim(ClaimsEnum.DEEP_LINKING_SETTINGS); + return false; + } + + // 3.4.2 Message type + final String messageTypeClaim = claimAccessor.get(ClaimsEnum.MESSAGE_TYPE); + final MessageTypesEnum messageType = MessageTypesEnum.valueOf(messageTypeClaim); + if (messageType != MessageTypesEnum.LtiDeepLinkingRequest) { + setReasonToInvalidClaim(ClaimsEnum.MESSAGE_TYPE); + return false; + } + + // 3.4.3 LTI version (already in core) + // 3.4.4 Deployment ID (already in core) + // 3.4.5 User (already in core) + // 3.4.6 Launch Presentation (already in core) + // 3.4.7 Platform (already in core) + // 3.4.8 Context (already in core) + // 3.4.10 Role-scope mentor (already in core) + + return true; + } +} Index: 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/tool/validator/LTICoreValidator.java =================================================================== diff -u --- 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/tool/validator/LTICoreValidator.java (revision 0) +++ 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/tool/validator/LTICoreValidator.java (revision 22d20e45f4a7cbb2a9477c66f75e2a162b0d3f06) @@ -0,0 +1,176 @@ +package edu.uoc.elc.lti.tool.validator; + +import edu.uoc.elc.lti.tool.Context; +import edu.uoc.elc.lti.tool.Platform; +import edu.uoc.elc.lti.tool.RolesEnum; +import edu.uoc.elc.lti.tool.ToolDefinition; +import edu.uoc.lti.MessageTypesEnum; +import edu.uoc.lti.claims.ClaimAccessor; +import edu.uoc.lti.claims.ClaimsEnum; +import edu.uoc.lti.deeplink.content.DocumentTargetEnum; +import edu.uoc.lti.deeplink.content.Presentation; +import edu.uoc.lti.oidc.OIDCLaunchSession; +import lombok.Getter; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +/** + * Core validator, validades only core requirements (common to all launches) + * Intented to expand by subclasses + * @author xaracil@uoc.edu + */ +public class LTICoreValidator implements LaunchValidatable { + static final int ID_MAX_LENGTH = 255; + final static String VERSION = "1.3.0"; + + @Getter + String reason; + + @Override + public boolean validate(String state, ToolDefinition toolDefinition, ClaimAccessor claimAccessor, OIDCLaunchSession oidcLaunchSession) { + if (!this.validateRequiredFields(toolDefinition, claimAccessor)) { + return false; + } + + if (!this.validateOptionalFields(claimAccessor)) { + return false; + } + + return true; + } + + private boolean validateRequiredFields(ToolDefinition toolDefinition, ClaimAccessor claimAccessor) { + // Message type + final String messageTypeClaim = claimAccessor.get(ClaimsEnum.MESSAGE_TYPE); + if (messageTypeClaim == null) { + setReasonToMissingRequiredClaim(ClaimsEnum.MESSAGE_TYPE); + return false; + } + + try { + MessageTypesEnum.valueOf(messageTypeClaim); + } catch (IllegalArgumentException e) { + setReasonToInvalidClaim(ClaimsEnum.MESSAGE_TYPE); + return false; + } + + // LTI version + final String versionClaim = claimAccessor.get(ClaimsEnum.VERSION); + if (versionClaim == null || !VERSION.equals(versionClaim)) { + setReasonToInvalidClaim(ClaimsEnum.VERSION); + return false; + } + + // Deployment ID + final String deploymentId = claimAccessor.get(ClaimsEnum.DEPLOYMENT_ID); + if (isEmpty(deploymentId)) { + setReasonToMissingRequiredClaim(ClaimsEnum.DEPLOYMENT_ID); + return false; + } + if (deploymentId.trim().length() > ID_MAX_LENGTH) { + setReasonToInvalidClaim(ClaimsEnum.DEPLOYMENT_ID); + return false; + } + if (!toolDefinition.getDeploymentId().equals(deploymentId)) { + setReasonToInvalidClaim(ClaimsEnum.DEPLOYMENT_ID); + return false; + } + + // User + final String subject = claimAccessor.getSubject(); + if (isEmpty(subject)) { + // check other user identity claims are empty as well + List identityClaims = Arrays.asList(ClaimsEnum.GIVEN_NAME, ClaimsEnum.FAMILY_NAME, ClaimsEnum.NAME, ClaimsEnum.EMAIL); + if (identityClaims.stream().anyMatch(claim -> !isEmpty(claimAccessor.get(claim)))) { + this.reason = "Subject is required"; + return false; + } + } else { + if (!isIdStringValid(subject)) { + this.reason = "Subject is invalid"; + return false; + } + } + + // Roles + Class> rolesClass = (Class) List.class; + final List roles = claimAccessor.get(ClaimsEnum.ROLES, rolesClass); + if (roles == null) { + setReasonToMissingRequiredClaim(ClaimsEnum.ROLES); + return false; + } + // check it contains, at least one valid role + final List filteredRoles = roles.stream().filter(s -> !s.isEmpty()).collect(Collectors.toList()); + if (filteredRoles.size() > 0 && !filteredRoles.stream().anyMatch(role -> RolesEnum.from(role) != null)) { + setReasonToInvalidClaim(ClaimsEnum.ROLES); + return false; + } + + return true; + } + + private boolean validateOptionalFields(ClaimAccessor claimAccessor) { + // Context + final Context contextClaim = claimAccessor.get(ClaimsEnum.CONTEXT, Context.class); + if (contextClaim != null) { + if (!isIdStringValid(contextClaim.getId())) { + setReasonToInvalidClaim(ClaimsEnum.CONTEXT); + return false; + } + } + + // Platform + final Platform platform = claimAccessor.get(ClaimsEnum.TOOL_PLATFORM, Platform.class); + if (platform != null) { + if (!isIdStringValid(platform.getGuid())) { + setReasonToInvalidClaim(ClaimsEnum.TOOL_PLATFORM); + return false; + } + } + + // Launch presentation claim + final Presentation presentation = claimAccessor.get(ClaimsEnum.PRESENTATION, Presentation.class); + if (presentation != null) { + if (!isEmpty(presentation.getDocumentTarget())) { + try { + DocumentTargetEnum.valueOf(presentation.getDocumentTarget()); + } catch (IllegalArgumentException e) { + setReasonToInvalidClaim(ClaimsEnum.PRESENTATION); + return false; + } + } + } + + // Role-scope mentor + Class> mentorRolesClass = (Class) List.class; + final List mentorRoles = claimAccessor.get(ClaimsEnum.ROLE_SCOPE_MENTOR, mentorRolesClass); + if (mentorRoles != null && mentorRoles.size() > 0) { + Class> rolesClass = (Class) List.class; + final List roles = claimAccessor.get(ClaimsEnum.ROLES, rolesClass); + if (!roles.contains(RolesEnum.MENTOR.getName())) { + setReasonToInvalidClaim(ClaimsEnum.ROLE_SCOPE_MENTOR); + return false; + } + } + + return true; + } + + boolean isEmpty(String value) { + return value == null || "".equals(value.trim()); + } + boolean isIdStringValid(String value) { + return !isEmpty(value) && value.trim().length() <= ID_MAX_LENGTH; + } + + void setReasonToMissingRequiredClaim(ClaimsEnum claim) { + reason = "Required claim " + claim.getName() + " not found"; + } + + void setReasonToInvalidClaim(ClaimsEnum claim) { + reason = "Claim " + claim.getName() + " is invalid"; + } + +} Index: 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/tool/validator/LTIResourceLinkLaunchValidatable.java =================================================================== diff -u --- 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/tool/validator/LTIResourceLinkLaunchValidatable.java (revision 0) +++ 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/tool/validator/LTIResourceLinkLaunchValidatable.java (revision 22d20e45f4a7cbb2a9477c66f75e2a162b0d3f06) @@ -0,0 +1,115 @@ +package edu.uoc.elc.lti.tool.validator; + +import edu.uoc.elc.lti.tool.*; +import edu.uoc.lti.MessageTypesEnum; +import edu.uoc.lti.claims.ClaimAccessor; +import edu.uoc.lti.claims.ClaimsEnum; +import edu.uoc.lti.oidc.OIDCLaunchSession; +import lombok.RequiredArgsConstructor; + +/** + * @author xaracil@uoc.edu + */ +@RequiredArgsConstructor +public class LTIResourceLinkLaunchValidatable extends LTICoreValidator { + private static final int ID_MAX_LENGTH = 255; + + @Override + public boolean validate(String state, ToolDefinition toolDefinition, ClaimAccessor claimAccessor, OIDCLaunchSession oidcLaunchSession) { + // Core validation + if (!super.validate(state, toolDefinition, claimAccessor, oidcLaunchSession)) { + return false; + } + + // LTI required claims + if (!validateRequiredClaims(state, toolDefinition, claimAccessor, oidcLaunchSession)) { + return false; + } + + // LTI optional claims + if (!validateOptionalClaims()) { + return false; + } + + // state + if (state != null) { + if (!state.equals(oidcLaunchSession.getState())) { + reason = "Invalid state"; + return false; + } + if (claimAccessor.get(ClaimsEnum.NONCE) != null) { + if (!claimAccessor.get(ClaimsEnum.NONCE).equals(oidcLaunchSession.getNonce())) { + setReasonToInvalidClaim(ClaimsEnum.NONCE); + return false; + } + } + } + return true; + } + + /** + * Validates the required claims of the LTI launch following https://www.imsglobal.org/spec/lti/v1p3/#required-message-claims + * @param state saved state, if present + * @return true if the required claims of the LTI launch are valid, false otherwise + */ + private boolean validateRequiredClaims(String state, ToolDefinition toolDefinition, ClaimAccessor claimAccessor, OIDCLaunchSession oidcLaunchSession) { + // 5.3.1 message type claim + final String messageTypeClaim = claimAccessor.get(ClaimsEnum.MESSAGE_TYPE); + final MessageTypesEnum messageType = MessageTypesEnum.valueOf(messageTypeClaim); + if (messageType != MessageTypesEnum.LtiResourceLinkRequest) { + setReasonToInvalidClaim(ClaimsEnum.MESSAGE_TYPE); + return false; + } + + // 5.3.2 version (already in core) + // 5.3.3 LTI Deployment ID claim (already in core) + + // 5.3.4 Target Link URI + if (state != null) { + final String targetLinkUri = claimAccessor.get(ClaimsEnum.TARGET_LINK_URI); + if (isEmpty(targetLinkUri)) { + setReasonToMissingRequiredClaim(ClaimsEnum.TARGET_LINK_URI); + return false; + } + final String targetLinkUriFromOidcSession = oidcLaunchSession.getTargetLinkUri(); + if (!targetLinkUri.equals(targetLinkUriFromOidcSession)) { + setReasonToInvalidClaim(ClaimsEnum.TARGET_LINK_URI); + return false; + } + } + + // 5.3.5 Resource link claim + final ResourceLink resourceLink = claimAccessor.get(ClaimsEnum.RESOURCE_LINK, ResourceLink.class); + if (resourceLink == null) { + setReasonToMissingRequiredClaim(ClaimsEnum.RESOURCE_LINK); + return false; + } + + if (!isIdStringValid(resourceLink.getId())) { + setReasonToInvalidClaim(ClaimsEnum.RESOURCE_LINK); + return false; + } + + // 5.3.6 User Identity claims (already in core) + // 5.3.7 Roles claim (already in core) + + return true; + } + + /** + * Validates the optional claims of the LTI launch following https://www.imsglobal.org/spec/lti/v1p3/#optional-message-claims + * @return true if the optional claims of the LTI launch are valid, false otherwise + */ + private boolean validateOptionalClaims() { + // 5.4.1 Context claim (already in core) + // 5.4.2 Platform instance claim (already in core) + // 5.4.3 Role-scope mentor claims (already in core) + // 5.4.4 Launch presentation claim (already in core) + + // 5.4.5 Learning Information Services LIS claim: Nothing to do here + // 5.4.6 Custom properties and variable substitution: Nothing to do here (values are gotten as string in Tool) + // 5.4.7 Vendor-specific extension claims: Nothing to do here + return true; + } + +} Index: 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/tool/validator/LaunchValidatable.java =================================================================== diff -u --- 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/tool/validator/LaunchValidatable.java (revision 0) +++ 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/tool/validator/LaunchValidatable.java (revision 22d20e45f4a7cbb2a9477c66f75e2a162b0d3f06) @@ -0,0 +1,14 @@ +package edu.uoc.elc.lti.tool.validator; + +import edu.uoc.elc.lti.tool.ToolDefinition; +import edu.uoc.lti.claims.ClaimAccessor; +import edu.uoc.lti.oidc.OIDCLaunchSession; + +/** + * @author xaracil@uoc.edu + */ +public interface LaunchValidatable { + boolean validate(String state, ToolDefinition toolDefinition, ClaimAccessor claimAccessor, OIDCLaunchSession oidcLaunchSession); + + String getReason(); +} Index: 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/tool/validator/MessageTypesValidatorEnum.java =================================================================== diff -u --- 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/tool/validator/MessageTypesValidatorEnum.java (revision 0) +++ 3rdParty_sources/uoc-lti-advantage/edu/uoc/elc/lti/tool/validator/MessageTypesValidatorEnum.java (revision 22d20e45f4a7cbb2a9477c66f75e2a162b0d3f06) @@ -0,0 +1,29 @@ +package edu.uoc.elc.lti.tool.validator; + +import edu.uoc.lti.MessageTypesEnum; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +/** + * @author xaracil@uoc.edu + */ +@RequiredArgsConstructor +public enum MessageTypesValidatorEnum { + LTI_RESOURCE_LINK(MessageTypesEnum.LtiResourceLinkRequest.name(), new LTIResourceLinkLaunchValidatable()), + DEEP_LINKING(MessageTypesEnum.LtiDeepLinkingRequest.name(), new DeepLinkingLaunchValidatable()); + + @Getter + private final String messageType; + + @Getter + private final LaunchValidatable validator; + + public static LaunchValidatable getValidator(String name) { + for (MessageTypesValidatorEnum messageTypesValidator : values()) { + if (messageTypesValidator.getMessageType().equals(name)) { + return messageTypesValidator.getValidator(); + } + } + return null; + } +} Index: 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/MessageTypesEnum.java =================================================================== diff -u --- 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/MessageTypesEnum.java (revision 0) +++ 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/MessageTypesEnum.java (revision 22d20e45f4a7cbb2a9477c66f75e2a162b0d3f06) @@ -0,0 +1,9 @@ +package edu.uoc.lti; + +/** + * @author Created by xaracil@uoc.edu + */ +public enum MessageTypesEnum { + LtiResourceLinkRequest, + LtiDeepLinkingRequest +} Index: 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/ResponseMessageTypeEnum.java =================================================================== diff -u --- 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/ResponseMessageTypeEnum.java (revision 0) +++ 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/ResponseMessageTypeEnum.java (revision 22d20e45f4a7cbb2a9477c66f75e2a162b0d3f06) @@ -0,0 +1,5 @@ +package edu.uoc.lti; + +public enum ResponseMessageTypeEnum { + LtiDeepLinkingResponse +} \ No newline at end of file Index: 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/accesstoken/AccessTokenRequest.java =================================================================== diff -u --- 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/accesstoken/AccessTokenRequest.java (revision 0) +++ 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/accesstoken/AccessTokenRequest.java (revision 22d20e45f4a7cbb2a9477c66f75e2a162b0d3f06) @@ -0,0 +1,16 @@ +package edu.uoc.lti.accesstoken; + +import lombok.Builder; +import lombok.Getter; + +/** + * @author Created by xaracil@uoc.edu + */ +@Getter +@Builder +public class AccessTokenRequest { + private String grant_type; + private String client_assertion_type; + private String scope; + private String client_assertion; +} Index: 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/accesstoken/AccessTokenRequestBuilder.java =================================================================== diff -u --- 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/accesstoken/AccessTokenRequestBuilder.java (revision 0) +++ 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/accesstoken/AccessTokenRequestBuilder.java (revision 22d20e45f4a7cbb2a9477c66f75e2a162b0d3f06) @@ -0,0 +1,19 @@ +package edu.uoc.lti.accesstoken; + +import java.io.IOException; + +public interface AccessTokenRequestBuilder { + /** + * Returns the request for obtaing the access token + * @param request request to build + * @return string with the request to pass to the authorization server + * @throws IOException if something went wrong getting the access token + */ + String build(AccessTokenRequest request) throws IOException; + + /** + * Gets the content type of the request to the authorization server + * @return the content type of the request to the authorization server + */ + String getContentType(); +} \ No newline at end of file Index: 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/accesstoken/JSONAccessTokenRequestBuilderImpl.java =================================================================== diff -u --- 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/accesstoken/JSONAccessTokenRequestBuilderImpl.java (revision 0) +++ 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/accesstoken/JSONAccessTokenRequestBuilderImpl.java (revision 22d20e45f4a7cbb2a9477c66f75e2a162b0d3f06) @@ -0,0 +1,20 @@ +package edu.uoc.lti.accesstoken; + +import java.io.IOException; + +import com.fasterxml.jackson.databind.ObjectMapper; + +public class JSONAccessTokenRequestBuilderImpl implements AccessTokenRequestBuilder { + + @Override + public String build(AccessTokenRequest request) throws IOException { + ObjectMapper objectMapper = new ObjectMapper(); + return objectMapper.writeValueAsString(request); + } + + @Override + public String getContentType() { + return "application/json"; + } + +} \ No newline at end of file Index: 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/accesstoken/UrlEncodedFormAccessTokenRequestBuilderImpl.java =================================================================== diff -u --- 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/accesstoken/UrlEncodedFormAccessTokenRequestBuilderImpl.java (revision 0) +++ 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/accesstoken/UrlEncodedFormAccessTokenRequestBuilderImpl.java (revision 22d20e45f4a7cbb2a9477c66f75e2a162b0d3f06) @@ -0,0 +1,50 @@ +package edu.uoc.lti.accesstoken; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.stream.Collectors; + +public class UrlEncodedFormAccessTokenRequestBuilderImpl implements AccessTokenRequestBuilder { + + @Override + public String build(final AccessTokenRequest request) throws IOException { + final Method[] methods = request.getClass().getDeclaredMethods(); + + return Arrays.stream(methods) + .map(method -> { + try { + if (!method.getName().startsWith("get")) { + return null; + } + final Object value = method.invoke(request, null); + String valueAsString = null; + if (method.getReturnType().getSimpleName().equals("String")) { + valueAsString = (String) value; + } + else if (method.getReturnType().isPrimitive()) { + valueAsString = String.valueOf(value); + } + return valueAsString != null && valueAsString.length() > 0 + ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) + "=" + URLEncoder.encode(valueAsString, StandardCharsets.UTF_8.toString()) + : null; + } catch (IllegalArgumentException | InvocationTargetException | UnsupportedEncodingException + | IllegalAccessException e) { + e.printStackTrace(); + } + return null; + }) + .filter(value -> value != null) + .collect(Collectors.joining("&")); + } + + @Override + public String getContentType() { + return "application/x-www-form-urlencoded"; + } + +} \ No newline at end of file Index: 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/ags/ActivityProgressEnum.java =================================================================== diff -u --- 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/ags/ActivityProgressEnum.java (revision 0) +++ 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/ags/ActivityProgressEnum.java (revision 22d20e45f4a7cbb2a9477c66f75e2a162b0d3f06) @@ -0,0 +1,20 @@ +package edu.uoc.lti.ags; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +/** + * @author Created by xaracil@uoc.edu + */ +@RequiredArgsConstructor +public enum ActivityProgressEnum { + INITIALIZED("Initialized"), + STARTED("Started"), + IN_PROGRESS("InProgress"), + SUBMITTED("Submitted"), + COMPLETED("Completed") + ; + + @Getter + private final String value; +} Index: 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/ags/ContentTypes.java =================================================================== diff -u --- 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/ags/ContentTypes.java (revision 0) +++ 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/ags/ContentTypes.java (revision 22d20e45f4a7cbb2a9477c66f75e2a162b0d3f06) @@ -0,0 +1,21 @@ +package edu.uoc.lti.ags; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +/** + * @author Created by xaracil@uoc.edu + */ +@RequiredArgsConstructor +public enum ContentTypes { + LINE_ITEM("application", "vnd.ims.lis.v2.lineitem+json"), + LINE_ITEM_CONTAINER("application", "vnd.ims.lis.v2.lineitemcontainer+json"), + RESULT("application", "vnd.ims.lis.v2.resultcontainer+json"), + SCORE("application", "vnd.ims.lis.v1.score+json") + ; + + @Getter + private final String type; + @Getter + private final String subtype; +} Index: 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/ags/GradingProgressEnum.java =================================================================== diff -u --- 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/ags/GradingProgressEnum.java (revision 0) +++ 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/ags/GradingProgressEnum.java (revision 22d20e45f4a7cbb2a9477c66f75e2a162b0d3f06) @@ -0,0 +1,20 @@ +package edu.uoc.lti.ags; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +/** + * @author Created by xaracil@uoc.edu + */ +@Getter +@RequiredArgsConstructor +public enum GradingProgressEnum { + NOT_READY("NotReady"), + FAILED("Failed"), + PENDING("Pending"), + PENDING_MANUAL("PendingManual"), + FULLY_GRADED("FullyGraded") + ; + + private final String value; +} Index: 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/ags/LineItem.java =================================================================== diff -u --- 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/ags/LineItem.java (revision 0) +++ 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/ags/LineItem.java (revision 22d20e45f4a7cbb2a9477c66f75e2a162b0d3f06) @@ -0,0 +1,51 @@ +package edu.uoc.lti.ags; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.NonNull; + +/** + * @author Created by xaracil@uoc.edu + */ +@Data +@NoArgsConstructor +@JsonInclude(JsonInclude.Include.NON_EMPTY) +@JsonIgnoreProperties(ignoreUnknown = true) +public class LineItem { + /** + * URL end point for the resource. It must be present on all responses containing the resource and may be used for subsequent operations on that resource. + */ + private String id; + + /** + * + */ + private double scoreMaximum; + + /** + * label to use in the Tool Consumer UI (Gradebook) + */ + @NonNull private String label; + + /** + * Additional information about the line item; may be used by the tool to identify line items attached to the same resource or resource link (example: grade, originality, participation) + */ + private String tag; + + /** + * Tool resource identifier for which this line item is receiving scores from + */ + private String resourceId; + + /** + * Id of the tool platform's resource link to which this line item is attached to + */ + private String resourceLinkId; + + /** + * Submission information + */ + private Submission submission; +} Index: 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/ags/LineItemServiceClient.java =================================================================== diff -u --- 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/ags/LineItemServiceClient.java (revision 0) +++ 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/ags/LineItemServiceClient.java (revision 22d20e45f4a7cbb2a9477c66f75e2a162b0d3f06) @@ -0,0 +1,50 @@ +package edu.uoc.lti.ags; + +import java.net.URI; +import java.util.List; + +/** + * @author Created by xaracil@uoc.edu + */ +public interface LineItemServiceClient { + void setServiceUri(URI uri); + + /** + * Get the line items of the platform + * @param limit restricts the maximum number of items to be returned. If null doesn't apply this restriction. + * @param page indicates the offset for which this page should start containing items. Can be null. + * @param resourceLinkId limit the line items returned to only those which have been associated with the specified tool platform's resource link. Can be null. + * @param tag limit the line items returned to only those which have been associated with the specified tag. Can be null. + * @param resourceId limit the line items returned to only those which have been associated with the specified tool resource ID. Can be null. + * @return all the line items associated to tool in the specified learning context. Results may be broken in multiple pages in particular if a limit parameter is present. + */ + List getLineItems(Integer limit, Integer page, String resourceLinkId, String tag, String resourceId); + + /** + * Add a new line item; the created line item is returned with the url, scores and results endpoints (assuming the client has the enabled capabilities for those) + * @param lineItem the LineItem to create + * @return the LineItem created + */ + LineItem createLineItem(LineItem lineItem); + + /** + * Returns the current value for the line item. + * @param id the id of the line item. + * @return the line item of the given id + */ + LineItem getLineItem(String id); + + /** + * Updates the line item + * @param id Id of the line item. + * @param lineItem the updated line item. + * @return the updated line item. + */ + LineItem updateLineItem(String id, LineItem lineItem); + + /** + * Removes the line item. While no more associated with the tool, the tool platform may decide to keep the line item around (unassociated) + * @param id Id of the line item. + */ + void deleteLineItem(String id); +} Index: 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/ags/Result.java =================================================================== diff -u --- 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/ags/Result.java (revision 0) +++ 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/ags/Result.java (revision 22d20e45f4a7cbb2a9477c66f75e2a162b0d3f06) @@ -0,0 +1,45 @@ +package edu.uoc.lti.ags; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.Getter; +import lombok.Setter; + +/** + * @author Created by xaracil@uoc.edu + */ +@Getter +@Setter +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_EMPTY) +public class Result { + /** + * URL end point for the resource. It must be present on all responses containing the resource and may be used for subsequent operations on that resource. + */ + private String id; + + /** + * Recipient of the result, usually a student + */ + private String userId; + + /** + * Current score for this line item and user, in scale with resultMaximum + */ + private double resultScore; + + /** + * Maximum possible score for this result; 1 is the default value and will be assumed if not specified otherwise. + */ + private double resultMaximum; + + /** + * Comment visible to the student about the Result. + */ + private String comment; + + /** + * URL of the line item this result belongs to; must be the same as the url property of the line item. + */ + private String scoreOf; +} Index: 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/ags/ResultServiceClient.java =================================================================== diff -u --- 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/ags/ResultServiceClient.java (revision 0) +++ 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/ags/ResultServiceClient.java (revision 22d20e45f4a7cbb2a9477c66f75e2a162b0d3f06) @@ -0,0 +1,18 @@ +package edu.uoc.lti.ags; + +import java.util.List; + +/** + * @author Created by xaracil@uoc.edu + */ +public interface ResultServiceClient { + /** + * Returns all the results for the line item (results for all the students in the current context for this line item) Results may be broken in multiple pages in particular if a limit parameter is present. + * @param id Id of the line item. + * @param limit restricts the maximum number of items to be returned. The response may be further constrained. If null doesn't apply this restriction. + * @param page indicates the offset for which this page should start containing items. Used exclusively by the nextPage URL. Can be null. + * @param userId limit the line items returned to only those which have been associated with this user. Results must contain at most one result. + * @return all the results for the line item + */ + List getLineItemResults(String id, Integer limit, Integer page, String userId); +} Index: 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/ags/Score.java =================================================================== diff -u --- 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/ags/Score.java (revision 0) +++ 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/ags/Score.java (revision 22d20e45f4a7cbb2a9477c66f75e2a162b0d3f06) @@ -0,0 +1,71 @@ +package edu.uoc.lti.ags; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; + +import java.time.Instant; + +/** + * @author Created by xaracil@uoc.edu + */ +@Setter +@Builder +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_EMPTY) +public class Score { + /** + * Recipient of the score, usually a student. Must be present when publishing a score update through Scores + */ + @Getter + private String userId; + + /** + * Current score received in the tool for this line item and user, in scale with scoreMaximum + */ + @Getter + private double scoreGiven; + + /** + * Maximum possible score for this result; It must be present if scoreGiven is present. + */ + @Getter + private double scoreMaximum; + + /** + * Comment visible to the student about this score. + */ + @Getter + private String comment; + + /** + * Date and time when the score was modified in the tool. Should use subsecond precision. + */ + private Instant timeStamp; + + public String getTimeStamp() { + return timeStamp != null ? timeStamp.toString() : null; + } + + /** + * Indicate to the tool platform the status of the user towards the activity's completion. + * [ Initialized, Started, InProgress, Submitted, Completed ] + */ + private ActivityProgressEnum activityProgress; + + public String getActivityProgress() { + return activityProgress.getValue(); + } + + /** + * Indicate to the platform the status of the grading process, including allowing to inform when human intervention is needed. A value other than FullyGraded may cause the tool platform to ignore the scoreGiven value if present. + * [ NotReady, Failed, Pending, PendingManual, FullyGraded ] + */ + private GradingProgressEnum gradingProgress; + + public String getGradingProgress() { + return gradingProgress.getValue(); + } +} Index: 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/ags/ScoreServiceClient.java =================================================================== diff -u --- 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/ags/ScoreServiceClient.java (revision 0) +++ 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/ags/ScoreServiceClient.java (revision 22d20e45f4a7cbb2a9477c66f75e2a162b0d3f06) @@ -0,0 +1,14 @@ +package edu.uoc.lti.ags; + +/** + * @author Created by xaracil@uoc.edu + */ +public interface ScoreServiceClient { + /** + * Publishes a score update. Tool platform may decide to change the result value based on the updated score. + * @param lineItemId Id of the line item + * @param score the score to update + * @return true if the score was updated. false otherwise + */ + boolean score(String lineItemId, Score score); +} Index: 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/ags/Submission.java =================================================================== diff -u --- 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/ags/Submission.java (revision 0) +++ 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/ags/Submission.java (revision 22d20e45f4a7cbb2a9477c66f75e2a162b0d3f06) @@ -0,0 +1,33 @@ +package edu.uoc.lti.ags; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.Setter; + +import java.time.Instant; + +/** + * @author Created by xaracil@uoc.edu + */ +@Setter +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_EMPTY) +public class Submission { + /** + * Date and time in ISO 8601 format when a submission can start being submitted by learner + */ + private Instant startDateTime; + + public String getStartDateTime() { + return startDateTime != null ? startDateTime.toString() : null; + } + + /** + * Date and time in ISO 8601 format when a submission can last be submitted by learner + */ + private Instant endDateTime; + + public String getEndDateTime() { + return endDateTime != null ? endDateTime.toString() : null; + } +} Index: 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/claims/ClaimAccessor.java =================================================================== diff -u --- 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/claims/ClaimAccessor.java (revision 0) +++ 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/claims/ClaimAccessor.java (revision 22d20e45f4a7cbb2a9477c66f75e2a162b0d3f06) @@ -0,0 +1,70 @@ +package edu.uoc.lti.claims; + +import java.util.Date; + +/** + * @author Created by xaracil@uoc.edu + */ +public interface ClaimAccessor { + + void decode(String token); + + /** + * Get the kid + * @return claim content identifying the kid + */ + String getKId(); + + /** + * Get the issuer + * @return claim content identifying the issuer + */ + String getIssuer(); + + /** + * Get the subject + * @return claim content identifying the subject + */ + String getSubject(); + + /** + * Get the audience + * @return claim content identifying the audience + */ + String getAudience(); + + + /** + * Get the authorized part + * @return claim content identifying the azp + */ + String getAzp(); + + /** + * Get the issued date + * @return claim content identifying the issuetAt + */ + Date getIssuedAt(); + + /** + * Get the expiration date + * @return claim content identifying the expiration date + */ + Date getExpiration(); + + /** + * Get claim as String object + * @param name claim to get + * @return String with claim's content or null if not found + */ + String get(ClaimsEnum name); + + /** + * Get claim content as a given type + * @param name claim to get + * @param returnClass type to return claim as + * @param type to return claim as + * @return object of type T with claim's content or null if not found + */ + T get(ClaimsEnum name, Class returnClass); +} Index: 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/claims/ClaimsEnum.java =================================================================== diff -u --- 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/claims/ClaimsEnum.java (revision 0) +++ 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/claims/ClaimsEnum.java (revision 22d20e45f4a7cbb2a9477c66f75e2a162b0d3f06) @@ -0,0 +1,51 @@ +package edu.uoc.lti.claims; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +/** + * @author Created by xaracil@uoc.edu + */ +@RequiredArgsConstructor +public enum ClaimsEnum { + // header claims + KID("kid"), + + // general claims + MESSAGE_TYPE("https://purl.imsglobal.org/spec/lti/claim/message_type"), + GIVEN_NAME("given_name"), + FAMILY_NAME("family_name"), + MIDDLE_NAME("middle_name"), + PICTURE("picture"), + EMAIL("email"), + NAME("name"), + NONCE("nonce"), + VERSION("https://purl.imsglobal.org/spec/lti/claim/version"), + LOCALE("locale"), + RESOURCE_LINK("https://purl.imsglobal.org/spec/lti/claim/resource_link"), + CONTEXT("https://purl.imsglobal.org/spec/lti/claim/context"), + ROLES("https://purl.imsglobal.org/spec/lti/claim/roles"), + TOOL_PLATFORM("https://purl.imsglobal.org/spec/lti/claim/tool_platform"), + ASSIGNMENT_GRADE_SERVICE("https://purl.imsglobal.org/spec/lti-ags/claim/endpoint"), + NAMES_ROLE_SERVICE("https://purl.imsglobal.org/spec/lti-nrps/claim/namesroleservice"), + CALIPER_SERVICE("https://purl.imsglobal.org/spec/lti-ces/claim/caliper-endpoint-service"), + PRESENTATION("https://purl.imsglobal.org/spec/lti/claim/launch_presentation"), + CUSTOM("https://purl.imsglobal.org/spec/lti/claim/custom"), + TARGET_LINK_URI("https://purl.imsglobal.org/spec/lti/claim/target_link_uri"), + ROLE_SCOPE_MENTOR("https://purlimsglobal.org/spec/lti/claim/role_scope_mentor"), + + // deep linking claims + DEPLOYMENT_ID("https://purl.imsglobal.org/spec/lti/claim/deployment_id"), + AUTHORIZED_PART("azp"), + DEEP_LINKING_SETTINGS("https://purl.imsglobal.org/spec/lti-dl/claim/deep_linking_settings"), + DEEP_LINKING_CONTENT_ITEMS("https://purl.imsglobal.org/spec/lti-dl/claim/content_items"), + DEEP_LINKING_MESSAGE("https://purl.imsglobal.org/spec/lti-dl/claim/msg"), + DEEP_LINKING_LOG("https://purl.imsglobal.org/spec/lti-dl/claim/log"), + DEEP_LINKING_ERROR_MESSAGE("https://purl.imsglobal.org/spec/lti-dl/claim/errormsg"), + DEEP_LINKING_ERROR_LOG("https://purl.imsglobal.org/spec/lti-dl/claim/errorlog"), + DEEP_LINKING_DATA("https://purl.imsglobal.org/spec/lti-dl/claim/data") + ; + + @Getter + private final String name; +} Index: 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/clientcredentials/ClientCredentialsRequest.java =================================================================== diff -u --- 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/clientcredentials/ClientCredentialsRequest.java (revision 0) +++ 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/clientcredentials/ClientCredentialsRequest.java (revision 22d20e45f4a7cbb2a9477c66f75e2a162b0d3f06) @@ -0,0 +1,16 @@ +package edu.uoc.lti.clientcredentials; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +/** + * @author Created by xaracil@uoc.edu + */ +@RequiredArgsConstructor +@Getter +public class ClientCredentialsRequest { + private final String kid; + private final String toolName; + private final String clientId; + private final String oauth2Url; +} Index: 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/clientcredentials/ClientCredentialsTokenBuilder.java =================================================================== diff -u --- 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/clientcredentials/ClientCredentialsTokenBuilder.java (revision 0) +++ 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/clientcredentials/ClientCredentialsTokenBuilder.java (revision 22d20e45f4a7cbb2a9477c66f75e2a162b0d3f06) @@ -0,0 +1,13 @@ +package edu.uoc.lti.clientcredentials; + +/** + * @author Created by xaracil@uoc.edu + */ +public interface ClientCredentialsTokenBuilder { + /** + * Builds a JWT token for the Client Credentials call + * @param request params of the Client Credentials call + * @return JWT token + */ + String build(ClientCredentialsRequest request); +} Index: 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/deeplink/DeepLinkingResponse.java =================================================================== diff -u --- 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/deeplink/DeepLinkingResponse.java (revision 0) +++ 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/deeplink/DeepLinkingResponse.java (revision 22d20e45f4a7cbb2a9477c66f75e2a162b0d3f06) @@ -0,0 +1,34 @@ +package edu.uoc.lti.deeplink; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.Setter; + +import java.util.List; + +import edu.uoc.lti.deeplink.content.Item; + +/** + * @author Created by xaracil@uoc.edu + */ +@RequiredArgsConstructor +@Getter +public class DeepLinkingResponse { + private final String platformName; + private final String clientId; + private final String azp; + + private final String deploymentId; + private final String nonce; + private final String data; + private final List itemList; + + @Setter + private String message; + @Setter + private String log; + @Setter + private String errorMessage; + @Setter + private String errorLog; +} Index: 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/deeplink/DeepLinkingTokenBuilder.java =================================================================== diff -u --- 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/deeplink/DeepLinkingTokenBuilder.java (revision 0) +++ 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/deeplink/DeepLinkingTokenBuilder.java (revision 22d20e45f4a7cbb2a9477c66f75e2a162b0d3f06) @@ -0,0 +1,13 @@ +package edu.uoc.lti.deeplink; + +/** + * @author Created by xaracil@uoc.edu + */ +public interface DeepLinkingTokenBuilder { + /** + * Builds a JWT token for the DeepLink call + * @param response params of the DeepLink call + * @return JWT token + */ + String build(DeepLinkingResponse response); +} Index: 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/deeplink/content/Custom.java =================================================================== diff -u --- 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/deeplink/content/Custom.java (revision 0) +++ 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/deeplink/content/Custom.java (revision 22d20e45f4a7cbb2a9477c66f75e2a162b0d3f06) @@ -0,0 +1,18 @@ +package edu.uoc.lti.deeplink.content; + +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +/** + * @author Created by xaracil@uoc.edu + */ +@Getter +@Setter +@Builder +@ToString +public class Custom { + private String quiz_id; + private String duedate; +} Index: 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/deeplink/content/DataItem.java =================================================================== diff -u --- 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/deeplink/content/DataItem.java (revision 0) +++ 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/deeplink/content/DataItem.java (revision 22d20e45f4a7cbb2a9477c66f75e2a162b0d3f06) @@ -0,0 +1,24 @@ +package edu.uoc.lti.deeplink.content; + +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +/** + * @author Created by xaracil@uoc.edu + */ +@Getter +@Setter +@JsonInclude(JsonInclude.Include.NON_EMPTY) +@ToString(callSuper = true) +public class DataItem extends Item { + private String data; + + @Builder + public DataItem(String type, String data) { + super(type); + this.data = data; + } +} Index: 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/deeplink/content/DocumentTargetEnum.java =================================================================== diff -u --- 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/deeplink/content/DocumentTargetEnum.java (revision 0) +++ 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/deeplink/content/DocumentTargetEnum.java (revision 22d20e45f4a7cbb2a9477c66f75e2a162b0d3f06) @@ -0,0 +1,13 @@ +package edu.uoc.lti.deeplink.content; + +import lombok.AllArgsConstructor; + +/** + * @author Created by xaracil@uoc.edu + */ +@AllArgsConstructor +public enum DocumentTargetEnum { + frame, + iframe, + window; +} Index: 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/deeplink/content/Duration.java =================================================================== diff -u --- 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/deeplink/content/Duration.java (revision 0) +++ 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/deeplink/content/Duration.java (revision 22d20e45f4a7cbb2a9477c66f75e2a162b0d3f06) @@ -0,0 +1,28 @@ +package edu.uoc.lti.deeplink.content; + +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.Builder; +import lombok.Setter; +import lombok.ToString; + +import java.time.Instant; + +/** + * @author Created by xaracil@uoc.edu + */ +@Setter +@Builder +@JsonInclude(JsonInclude.Include.NON_EMPTY) +@ToString +public class Duration { + private Instant startDate; + private Instant endDate; + + public String getStartDateTime() { + return startDate != null ? startDate.toString() : null; + } + + public String getEndDateTime() { + return endDate != null ? endDate.toString() : null; + } +} Index: 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/deeplink/content/Embed.java =================================================================== diff -u --- 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/deeplink/content/Embed.java (revision 0) +++ 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/deeplink/content/Embed.java (revision 22d20e45f4a7cbb2a9477c66f75e2a162b0d3f06) @@ -0,0 +1,17 @@ +package edu.uoc.lti.deeplink.content; + +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +/** + * @author Created by xaracil@uoc.edu + */ +@Getter +@Setter +@ToString +@Builder +public class Embed { + private final String html; +} Index: 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/deeplink/content/FileItem.java =================================================================== diff -u --- 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/deeplink/content/FileItem.java (revision 0) +++ 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/deeplink/content/FileItem.java (revision 22d20e45f4a7cbb2a9477c66f75e2a162b0d3f06) @@ -0,0 +1,78 @@ +package edu.uoc.lti.deeplink.content; + +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +import java.time.Instant; + +/** + * @author Created by xaracil@uoc.edu + */ +@Setter +@ToString(callSuper = true) +public class FileItem extends Item { + private static String TYPE = "file"; + + /** + * String, plain text to use as the title or heading for content. + */ + @Getter + private String title; + + /** + * Fully qualified URL of the resource. This link may be short-lived or expire after 1st use. + */ + @Getter + private final String url; + + /** + * String, plain text to use as the title or heading for content. + */ + @Getter + private String text; + + /** + * Fully qualified URL of an icon image to be placed with the file. + * A platform may not support the display of icons, but where it does, it may choose to use a local copy of the icon + * rather than linking to the URL provided (which would also allow it to resize the image to suit its needs). + */ + @Getter + private Image icon; + + /** + * Fully qualified URL of a thumbnail image to be made a hyperlink. + * This allows the hyperlink to be opened within the platform from text or an image, or from both. + */ + @Getter + private Image thumbnail; + + /** + * + */ + @Getter + private String mediaType; + + /** + * ISO 8601 Date and time [ISO8601]. The URL will be available until this time. No guarantees after that. (e.g. 2014-03-05T12:34:56Z). + */ + private Instant expiresAt; + + public String getExpiresAt() { + return expiresAt != null ? expiresAt.toString() : null; + } + + public FileItem(String url) { + super(TYPE); + this.url = url; + } + + @Builder + public FileItem(String title, String url, String mediaType, Instant expiresAt) { + this(url); + this.title = title; + this.mediaType = mediaType; + this.expiresAt = expiresAt; + } +} Index: 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/deeplink/content/HtmlItem.java =================================================================== diff -u --- 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/deeplink/content/HtmlItem.java (revision 0) +++ 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/deeplink/content/HtmlItem.java (revision 22d20e45f4a7cbb2a9477c66f75e2a162b0d3f06) @@ -0,0 +1,45 @@ +package edu.uoc.lti.deeplink.content; + +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +/** + * @author Created by xaracil@uoc.edu + */ +@Getter +@Setter +@JsonInclude(JsonInclude.Include.NON_EMPTY) +@ToString(callSuper = true) +public class HtmlItem extends Item { + private static String TYPE = "html"; + + /** + * HTML fragment to be embedded. The platform is expected to sanitize it against cross-site scripting attacks. + */ + private final String html; + + /** + * String, plain text to use as the title or heading for content. + */ + private String title; + + /** + * String, plain text description of the content item intended to be displayed to all users who can access the item. + */ + private String text; + + public HtmlItem(String html) { + super(TYPE); + this.html = html; + } + + @Builder + public HtmlItem(String html, String title, String text) { + this(html); + this.title = title; + this.text = text; + } +} Index: 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/deeplink/content/IFrame.java =================================================================== diff -u --- 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/deeplink/content/IFrame.java (revision 0) +++ 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/deeplink/content/IFrame.java (revision 22d20e45f4a7cbb2a9477c66f75e2a162b0d3f06) @@ -0,0 +1,22 @@ +package edu.uoc.lti.deeplink.content; + +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +/** + * @author Created by xaracil@uoc.edu + */ +@Getter +@Setter +@Builder +@JsonInclude(JsonInclude.Include.NON_EMPTY) +@ToString +public class IFrame { + private final String url; + private int width; + private int height; + +} Index: 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/deeplink/content/Image.java =================================================================== diff -u --- 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/deeplink/content/Image.java (revision 0) +++ 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/deeplink/content/Image.java (revision 22d20e45f4a7cbb2a9477c66f75e2a162b0d3f06) @@ -0,0 +1,21 @@ +package edu.uoc.lti.deeplink.content; + +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +/** + * @author Created by xaracil@uoc.edu + */ +@Getter +@Setter +@Builder +@JsonInclude(JsonInclude.Include.NON_EMPTY) +@ToString +public class Image { + private final String url; + private int width; + private int height; +} Index: 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/deeplink/content/ImageItem.java =================================================================== diff -u --- 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/deeplink/content/ImageItem.java (revision 0) +++ 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/deeplink/content/ImageItem.java (revision 22d20e45f4a7cbb2a9477c66f75e2a162b0d3f06) @@ -0,0 +1,73 @@ +package edu.uoc.lti.deeplink.content; + +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +/** + * @author Created by xaracil@uoc.edu + */ +@Getter +@Setter +@JsonInclude(JsonInclude.Include.NON_EMPTY) +@ToString(callSuper = true) +public class ImageItem extends Item { + private static String TYPE = "image"; + + /** + * Fully qualified URL of the image. + */ + private final String url; + + /** + * String, plain text to use as the title or heading for content. + */ + private String title; + + /** + * String, plain text description of the content item intended to be displayed to all users who can access the item. + */ + private String text; + + /** + * Fully qualified URL of an icon image to be placed with the file. A platform may not support the display of icons, + * but where it does, it may choose to use a local copy of the icon rather than linking to the URL provided + * (which would also allow it to resize the image to suit its needs). + */ + private Image icon; + + /** + * Fully qualified URL of a thumbnail image to be made a hyperlink. This allows the hyperlink to be opened within the + * platform from text or an image, or from both. + */ + private Image thumbnail; + + /** + * Integer representing the width in pixels of the image. + */ + private int width; + + /** + * Integer representing the height in pixels of the image. + */ + private int height; + + + public ImageItem(String url) { + super(TYPE); + this.url = url; + } + + @Builder + public ImageItem(String url, String title, String text, Image icon, Image thumbnail, int width, int height) { + this(url); + this.title = title; + this.text = text; + this.icon = icon; + this.thumbnail = thumbnail; + this.width = width; + this.height = height; + } +} Index: 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/deeplink/content/Item.java =================================================================== diff -u --- 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/deeplink/content/Item.java (revision 0) +++ 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/deeplink/content/Item.java (revision 22d20e45f4a7cbb2a9477c66f75e2a162b0d3f06) @@ -0,0 +1,41 @@ +package edu.uoc.lti.deeplink.content; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.ToString; + +import java.util.HashMap; +import java.util.Map; + +/** + * @author Created by xaracil@uoc.edu + */ +@RequiredArgsConstructor +@JsonInclude(JsonInclude.Include.NON_EMPTY) +@ToString +public abstract class Item { + @Getter + private final String type; + + + // extending a type https://www.imsglobal.org/spec/lti-dl/v2p0#extending-a-type + // gotten from http://tutorials.jenkov.com/java-json/jackson-annotations.html + private Map properties = new HashMap<>(); + + @JsonAnyGetter + public Map getProperties() { + return properties; + } + + @JsonAnySetter + public void set(String fieldName, Object value){ + this.properties.put(fieldName, value); + } + + public Object get(String fieldName){ + return this.properties.get(fieldName); + } +} Index: 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/deeplink/content/LineItem.java =================================================================== diff -u --- 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/deeplink/content/LineItem.java (revision 0) +++ 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/deeplink/content/LineItem.java (revision 22d20e45f4a7cbb2a9477c66f75e2a162b0d3f06) @@ -0,0 +1,38 @@ +package edu.uoc.lti.deeplink.content; + +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +/** + * @author Created by xaracil@uoc.edu + */ +@Getter +@Setter +@Builder +@JsonInclude(JsonInclude.Include.NON_EMPTY) +@ToString(callSuper = true) +public class LineItem { + /** + * Positive decimal value indicating the maximum score possible for this activity + */ + private final double scoreMaximum; + + /** + * label for the line item. If not present, the title of the content item must be used. + */ + private String label; + + /** + * String, tool provided ID for the resource. + */ + private String resourceId; + + /** + * String, additional information about the line item; may be used by the tool to identify line items attached + * to the same resource or resource link (example: grade, originality, participation). + */ + private String tag; +} Index: 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/deeplink/content/LinkItem.java =================================================================== diff -u --- 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/deeplink/content/LinkItem.java (revision 0) +++ 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/deeplink/content/LinkItem.java (revision 22d20e45f4a7cbb2a9477c66f75e2a162b0d3f06) @@ -0,0 +1,39 @@ +package edu.uoc.lti.deeplink.content; + +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.*; + +/** + * @author Created by xaracil@uoc.edu + */ +@Getter +@Setter +@JsonInclude(JsonInclude.Include.NON_EMPTY) +@ToString(callSuper = true) +public class LinkItem extends Item { + private static String TYPE = "link"; + + private String title; + private String text; + private final String url; + private Image icon; + private Image thumbnail; + + private Window window; + private IFrame iframe; + private Embed embed; + + public LinkItem(String url) { + super(TYPE); + this.url = url; + } + + @Builder + public LinkItem(String title, String url, String text, Image icon, Image thumbnail) { + this(url); + this.title = title; + this.text = text; + this.icon = icon; + this.thumbnail = thumbnail; + } +} Index: 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/deeplink/content/LtiResourceItem.java =================================================================== diff -u --- 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/deeplink/content/LtiResourceItem.java (revision 0) +++ 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/deeplink/content/LtiResourceItem.java (revision 22d20e45f4a7cbb2a9477c66f75e2a162b0d3f06) @@ -0,0 +1,110 @@ +package edu.uoc.lti.deeplink.content; + +import com.fasterxml.jackson.annotation.JsonInclude; + +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +import java.util.HashMap; +import java.util.Map; + +/** + * @author Created by xaracil@uoc.edu + */ +@Setter +@Getter +@JsonInclude(JsonInclude.Include.NON_EMPTY) +@ToString(callSuper = true) +public class LtiResourceItem extends Item { + private static String TYPE = "ltiResourceLink"; + + /** + * String, plain text to use as the title or heading for content. + */ + private String title; + + /** + * Fully qualified url of the resource. If absent, the base LTI URL of the tool must be used for launch. + */ + private String url; + + private Presentation presentation; + + /** + * Fully qualified URL of an icon image to be placed with the file. A platform may not support the display of icons, + * but where it does, it may choose to use a local copy of the icon rather than linking to the URL provided (which + * would also allow it to resize the image to suit its needs). + */ + private Image icon; + + /** + * Fully qualified URL of a thumbnail image to be made a hyperlink. This allows the hyperlink to be opened within + * the platform from text or an image, or from both. + */ + private Image thumbnail; + + /** + * A map of key/value custom parameters. Those parameters must be included in the LtiResourceLinkRequest payload. + * Value may include substitution parameters as defined in the LTI Core Specification [LTI-13]. + */ + private Map custom = new HashMap<>(); + + private Window window; + + /** + * The iframe property indicates the resource can be embedded using an IFrame + */ + private IFrame iFrame; + + /** + * A lineItem object that indicates this activity is expected to receive scores; the platform may automatically + * create a corresponding line item when the resource link is created, using the maximum score as the default maximum points. + */ + private LineItem lineItem; + + + /** + * Indicates the initial start and end time this activity should be made available to learners. A platform may choose + * to make an item not accessible by hiding it, or by disabling the link, or some other method which prevents the link + * from being opened by a learner. The initial value may subsequently be changed within the platform and the tool may use + * the ResourceLink.available.startDateTime and ResourceLink.available.endDateTime substitution parameters defined in + * LTI Core specification [LTI-13] within custom parameters to get the actual values at launch time. + */ + private Duration available; + + /** + * Indicates the initial start and end time submissions for this activity can be made by learners. The initial value + * may subsequently be changed within the platform and the tool may use the ResourceLink.submission.startDateTime and + * ResourceLink.submission.endDateTime substitution parameters defined in LTI Core specification [LTI-13] within + * custom parameters to get the actual values at launch time. + */ + private Duration submission; + + public LtiResourceItem() { + super(TYPE); + } + + @Builder + public LtiResourceItem(String title, String url, Presentation presentation, Image icon, Image thumbnail, Window window, IFrame iFrame, Duration available, Duration submission) { + this(); + this.title = title; + this.url = url; + this.presentation = presentation; + this.icon = icon; + this.thumbnail = thumbnail; + this.window = window; + this.iFrame = iFrame; + this.available = available; + this.submission = submission; + } + + public void setCustom(String key, Object value) { + this.custom.put(key, value); + } + + public Object getCustom(String key) { + return this.custom.get(key); + } +} Index: 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/deeplink/content/Presentation.java =================================================================== diff -u --- 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/deeplink/content/Presentation.java (revision 0) +++ 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/deeplink/content/Presentation.java (revision 22d20e45f4a7cbb2a9477c66f75e2a162b0d3f06) @@ -0,0 +1,32 @@ +package edu.uoc.lti.deeplink.content; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; + +/** + * @author Created by xaracil@uoc.edu + */ +@Getter +@Setter +@Builder +@NoArgsConstructor +@AllArgsConstructor +@JsonInclude(JsonInclude.Include.NON_EMPTY) +@ToString +public class Presentation { + @JsonProperty("document_target") + private String documentTarget; + private int width; + private int height; + @JsonProperty("return_url") + private String returnUrl; + @JsonProperty("locale") + private String locale; +} Index: 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/deeplink/content/Window.java =================================================================== diff -u --- 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/deeplink/content/Window.java (revision 0) +++ 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/deeplink/content/Window.java (revision 22d20e45f4a7cbb2a9477c66f75e2a162b0d3f06) @@ -0,0 +1,27 @@ +package edu.uoc.lti.deeplink.content; + +import com.fasterxml.jackson.annotation.JsonInclude; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; + +/** + * @author Created by xaracil@uoc.edu + */ +@Getter +@Setter +@Builder +@NoArgsConstructor +@AllArgsConstructor +@JsonInclude(JsonInclude.Include.NON_EMPTY) +@ToString +public class Window { + private String targetName; + private int with; + private int height; + private String windowFeatures; +} Index: 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/jwt/AlgorithmFactory.java =================================================================== diff -u --- 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/jwt/AlgorithmFactory.java (revision 0) +++ 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/jwt/AlgorithmFactory.java (revision 22d20e45f4a7cbb2a9477c66f75e2a162b0d3f06) @@ -0,0 +1,59 @@ +package edu.uoc.lti.jwt; + +import lombok.Getter; +import sun.security.util.DerInputStream; +import sun.security.util.DerValue; + +import java.io.IOException; +import java.math.BigInteger; +import java.security.GeneralSecurityException; +import java.security.KeyFactory; +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.RSAPublicKey; +import java.security.spec.RSAPrivateCrtKeySpec; +import java.security.spec.X509EncodedKeySpec; +import java.util.Base64; + +/** + * @author xaracil@uoc.edu + */ +public class AlgorithmFactory { + private final RSAPublicKey publicKey; + @Getter + private final RSAPrivateKey privateKey; + + public AlgorithmFactory(String publicKey, String privateKey) { + KeyFactory kf; + try { + kf = KeyFactory.getInstance("RSA"); + byte[] encodedPb = Base64.getDecoder().decode(publicKey); + X509EncodedKeySpec keySpecPb = new X509EncodedKeySpec(encodedPb); + this.publicKey = (RSAPublicKey) kf.generatePublic(keySpecPb); + + DerInputStream derReader = new DerInputStream(Base64.getDecoder().decode(privateKey)); + + DerValue[] seq = derReader.getSequence(0); + + if (seq.length < 9) { + throw new GeneralSecurityException("Could not parse a PKCS1 private key."); + } + + // skip version seq[0]; + BigInteger modulus = seq[1].getBigInteger(); + BigInteger publicExp = seq[2].getBigInteger(); + BigInteger privateExp = seq[3].getBigInteger(); + BigInteger prime1 = seq[4].getBigInteger(); + BigInteger prime2 = seq[5].getBigInteger(); + BigInteger exp1 = seq[6].getBigInteger(); + BigInteger exp2 = seq[7].getBigInteger(); + BigInteger crtCoef = seq[8].getBigInteger(); + + RSAPrivateCrtKeySpec keySpecPv = new RSAPrivateCrtKeySpec(modulus, publicExp, privateExp, prime1, prime2, exp1, exp2, crtCoef); + + this.privateKey = (RSAPrivateKey) kf.generatePrivate(keySpecPv); + + } catch (GeneralSecurityException | IOException e) { + throw new BadToolProviderConfigurationException(e); + } + } +} Index: 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/jwt/BadToolProviderConfigurationException.java =================================================================== diff -u --- 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/jwt/BadToolProviderConfigurationException.java (revision 0) +++ 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/jwt/BadToolProviderConfigurationException.java (revision 22d20e45f4a7cbb2a9477c66f75e2a162b0d3f06) @@ -0,0 +1,14 @@ +package edu.uoc.lti.jwt; + +/** + * @author xaracil@uoc.edu + */ +public class BadToolProviderConfigurationException extends RuntimeException { + public BadToolProviderConfigurationException(String message) { + super(message); + } + + public BadToolProviderConfigurationException(Throwable cause) { + super(cause); + } +} Index: 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/jwt/LtiSigningKeyResolver.java =================================================================== diff -u --- 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/jwt/LtiSigningKeyResolver.java (revision 0) +++ 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/jwt/LtiSigningKeyResolver.java (revision 22d20e45f4a7cbb2a9477c66f75e2a162b0d3f06) @@ -0,0 +1,43 @@ +package edu.uoc.lti.jwt; + +import com.auth0.jwk.Jwk; +import com.auth0.jwk.JwkException; +import com.auth0.jwk.JwkProvider; +import com.auth0.jwk.UrlJwkProvider; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.JwsHeader; +import io.jsonwebtoken.SigningKeyResolverAdapter; +import lombok.RequiredArgsConstructor; + +import java.net.MalformedURLException; +import java.net.URL; +import java.security.Key; + +/** + * @author xaracil@uoc.edu + */ +@RequiredArgsConstructor +public class LtiSigningKeyResolver extends SigningKeyResolverAdapter { + private final String keysetUrl; + + @Override + public Key resolveSigningKey(JwsHeader header, Claims claims) { + String keyId = header.getKeyId(); + + if (keyId == null) { + return null; + } + + Key key = null; + try { + JwkProvider provider = new UrlJwkProvider(new URL(keysetUrl)); + Jwk jwk = provider.get(keyId); + key = jwk.getPublicKey(); + } catch (MalformedURLException e) { + e.printStackTrace(); + } catch (JwkException e) { + e.printStackTrace(); + } + return key; + } +} Index: 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/jwt/claims/JWSClaimAccessor.java =================================================================== diff -u --- 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/jwt/claims/JWSClaimAccessor.java (revision 0) +++ 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/jwt/claims/JWSClaimAccessor.java (revision 22d20e45f4a7cbb2a9477c66f75e2a162b0d3f06) @@ -0,0 +1,118 @@ +package edu.uoc.lti.jwt.claims; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import edu.uoc.lti.claims.ClaimAccessor; +import edu.uoc.lti.claims.ClaimsEnum; +import edu.uoc.lti.jwt.LtiSigningKeyResolver; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jws; +import io.jsonwebtoken.Jwts; + +import java.util.Date; + +/** + * @author xaracil@uoc.edu + */ +public class JWSClaimAccessor implements ClaimAccessor { + private final static long _5_MINUTES = 5 * 60; + + private ObjectMapper objectMapper = new ObjectMapper(); + + private LtiSigningKeyResolver ltiSigningKeyResolver; + + private Jws jws; + + long allowedClockSkewSeconds = _5_MINUTES; + + + public JWSClaimAccessor(String keySetUrl) { + this.ltiSigningKeyResolver = new LtiSigningKeyResolver(keySetUrl); + } + + @Override + public void decode(String token) { + this.jws = Jwts.parser() + .setSigningKeyResolver(ltiSigningKeyResolver) + .setAllowedClockSkewSeconds(allowedClockSkewSeconds) + .parseClaimsJws(token); + } + + @Override + public String getKId() { + if (this.jws == null) { + return null; + } + + return jws.getHeader().getKeyId(); + } + + @Override + public String getIssuer() { + if (this.jws == null) { + return null; + } + + return jws.getBody().getIssuer(); + } + + @Override + public String getAudience() { + if (this.jws == null) { + return null; + } + + return jws.getBody().getAudience(); + } + + @Override + public String getSubject() { + if (this.jws == null) { + return null; + } + + return jws.getBody().getSubject(); + } + + @Override + public String getAzp() { + return get(ClaimsEnum.AUTHORIZED_PART); + } + + @Override + public Date getIssuedAt() { + if (this.jws == null) { + return null; + } + + return jws.getBody().getIssuedAt(); + } + + @Override + public Date getExpiration() { + if (this.jws == null) { + return null; + } + + return jws.getBody().getExpiration(); + } + + @Override + public String get(ClaimsEnum claim) { + if (this.jws == null) { + return null; + } + + return jws.getBody().get(claim.getName(), String.class); + } + + @Override + public T get(ClaimsEnum claim, Class returnClass) { + if (jws == null ||jws.getBody() == null || !jws.getBody().containsKey(claim.getName())) { + return null; + } + final Object o = jws.getBody().get(claim.getName()); + // doing this way because Jwts deserialize json classes as LinkedHashMap + return objectMapper.convertValue(o, returnClass); + } +} Index: 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/jwt/client/JWSClientCredentialsTokenBuilder.java =================================================================== diff -u --- 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/jwt/client/JWSClientCredentialsTokenBuilder.java (revision 0) +++ 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/jwt/client/JWSClientCredentialsTokenBuilder.java (revision 22d20e45f4a7cbb2a9477c66f75e2a162b0d3f06) @@ -0,0 +1,39 @@ +package edu.uoc.lti.jwt.client; + +import io.jsonwebtoken.Jwts; +import lombok.RequiredArgsConstructor; + +import java.util.Date; +import java.util.UUID; + +import edu.uoc.lti.clientcredentials.ClientCredentialsRequest; +import edu.uoc.lti.clientcredentials.ClientCredentialsTokenBuilder; +import edu.uoc.lti.jwt.AlgorithmFactory; + +/** + * @author xaracil@uoc.edu + */ +@RequiredArgsConstructor +public class JWSClientCredentialsTokenBuilder implements ClientCredentialsTokenBuilder { + + private final static long _5_MINUTES = 5 * 30 * 1000; + private final String publicKey; + private final String privateKey; + + @Override + public String build(ClientCredentialsRequest request) { + AlgorithmFactory algorithmFactory = new AlgorithmFactory(publicKey, privateKey); + + return Jwts.builder() + //.setHeaderParam("kid", request.getKid()) + .setHeaderParam("typ", "JWT") + .setIssuer(request.getClientId()) + .setSubject(request.getClientId()) + .setAudience(request.getOauth2Url()) + .setIssuedAt(new Date()) + .setExpiration(new Date(System.currentTimeMillis() + _5_MINUTES)) + .signWith(algorithmFactory.getPrivateKey()) + .setId(UUID.randomUUID().toString()) + .compact(); + } +} Index: 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/jwt/deeplink/JWSTokenBuilder.java =================================================================== diff -u --- 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/jwt/deeplink/JWSTokenBuilder.java (revision 0) +++ 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/jwt/deeplink/JWSTokenBuilder.java (revision 22d20e45f4a7cbb2a9477c66f75e2a162b0d3f06) @@ -0,0 +1,67 @@ +package edu.uoc.lti.jwt.deeplink; + +import edu.uoc.lti.claims.ClaimsEnum; +import edu.uoc.lti.deeplink.DeepLinkingResponse; +import edu.uoc.lti.deeplink.DeepLinkingTokenBuilder; +import edu.uoc.lti.jwt.AlgorithmFactory; +import edu.uoc.lti.ResponseMessageTypeEnum; +import io.jsonwebtoken.JwtBuilder; +import io.jsonwebtoken.Jwts; +import lombok.RequiredArgsConstructor; + +import java.util.Date; + +/** + * @author xaracil@uoc.edu + */ +@RequiredArgsConstructor +public class JWSTokenBuilder implements DeepLinkingTokenBuilder { + private final static long _5_MINUTES = 5 * 30 * 1000; + private final static String AUTHORIZED_PART = "azp"; + + private final String publicKey; + private final String privateKey; + + @Override + public String build(DeepLinkingResponse deepLinkingResponse) { + AlgorithmFactory algorithmFactory = new AlgorithmFactory(publicKey, privateKey); + + final JwtBuilder builder = Jwts.builder() + .setIssuer(deepLinkingResponse.getClientId()) + .setAudience(deepLinkingResponse.getPlatformName()) + .setIssuedAt(new Date()) + .setExpiration(new Date(System.currentTimeMillis() + _5_MINUTES)) + .signWith(algorithmFactory.getPrivateKey()) + .claim(ClaimsEnum.MESSAGE_TYPE.getName(), ResponseMessageTypeEnum.LtiDeepLinkingResponse.name()) + .claim(ClaimsEnum.VERSION.getName(), "1.3.0") + .claim(ClaimsEnum.DEPLOYMENT_ID.getName(), deepLinkingResponse.getDeploymentId()) + .claim(ClaimsEnum.NONCE.getName(), deepLinkingResponse.getNonce()) + .claim(ClaimsEnum.DEEP_LINKING_CONTENT_ITEMS.getName(), deepLinkingResponse.getItemList()); + + if (deepLinkingResponse.getAzp() != null) { + builder.claim(AUTHORIZED_PART, deepLinkingResponse.getAzp()); + } + + if (deepLinkingResponse.getData() != null) { + builder.claim(ClaimsEnum.DEEP_LINKING_DATA.getName(), deepLinkingResponse.getData()); + } + + if (deepLinkingResponse.getMessage() != null) { + builder.claim(ClaimsEnum.DEEP_LINKING_MESSAGE.getName(), deepLinkingResponse.getMessage()); + } + + if (deepLinkingResponse.getLog() != null) { + builder.claim(ClaimsEnum.DEEP_LINKING_LOG.getName(), deepLinkingResponse.getLog()); + } + + if (deepLinkingResponse.getErrorMessage() != null) { + builder.claim(ClaimsEnum.DEEP_LINKING_ERROR_MESSAGE.getName(), deepLinkingResponse.getErrorMessage()); + } + + if (deepLinkingResponse.getErrorLog() != null) { + builder.claim(ClaimsEnum.DEEP_LINKING_ERROR_LOG.getName(), deepLinkingResponse.getErrorLog()); + } + + return builder.compact(); + } +} Index: 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/namesrole/ContentTypes.java =================================================================== diff -u --- 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/namesrole/ContentTypes.java (revision 0) +++ 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/namesrole/ContentTypes.java (revision 22d20e45f4a7cbb2a9477c66f75e2a162b0d3f06) @@ -0,0 +1,19 @@ +package edu.uoc.lti.namesrole; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +/** + * @author Created by xaracil@uoc.edu + */ +@RequiredArgsConstructor +public enum ContentTypes { + REQUEST("application", "vnd.ims.lti-nrps.v2.membershipcontainer+json"), + RESPONSE("application", "vnd.ims-nrps.v2.membershipcontainer+json") + ; + + @Getter + private final String type; + @Getter + private final String subtype; +} Index: 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/oidc/OIDCLaunchSession.java =================================================================== diff -u --- 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/oidc/OIDCLaunchSession.java (revision 0) +++ 3rdParty_sources/uoc-lti-advantage/edu/uoc/lti/oidc/OIDCLaunchSession.java (revision 22d20e45f4a7cbb2a9477c66f75e2a162b0d3f06) @@ -0,0 +1,44 @@ +package edu.uoc.lti.oidc; + +/** + * Represents a session for storing OIDC launch params (currently state and nonce) + * A tool rely on this to allow OIDC launches. So, a conformant class must be passed to the tool as a constructor + * parameter + * @author Created by xaracil@uoc.edu + */ +public interface OIDCLaunchSession { + /** + * Save state in session + * @param state state to save + */ + void setState(String state); + /** + * Save nonce in session + * @param nonce nonce to save + */ + void setNonce(String nonce); + + /** + * Save target_link_uri in session + * @param targetLinkUri target_link_uri to save + */ + void setTargetLinkUri(String targetLinkUri); + + /** + * Get state from session + * @return saved state + */ + String getState(); + + /** + * Get nonce from session + * @return saved nonce + */ + String getNonce(); + + /** + * Get target_link_uri from session + * @return target_link_uri + */ + String getTargetLinkUri(); +} Index: 3rdParty_sources/versions.txt =================================================================== diff -u -r87a030f47f2efc26e0ce486ae2cb6295dc0bed63 -r22d20e45f4a7cbb2a9477c66f75e2a162b0d3f06 --- 3rdParty_sources/versions.txt (.../versions.txt) (revision 87a030f47f2efc26e0ce486ae2cb6295dc0bed63) +++ 3rdParty_sources/versions.txt (.../versions.txt) (revision 22d20e45f4a7cbb2a9477c66f75e2a162b0d3f06) @@ -67,6 +67,8 @@ Undertow servlet 2.0.13 +UOC LTI Advantage integration 0.0.3 + xmltooling 1.4.0 XStream 1.4.11