diff --git a/config/configuration/bundles/src/bundle/org/sakaiproject/config/bundle/default.sakai.properties b/config/configuration/bundles/src/bundle/org/sakaiproject/config/bundle/default.sakai.properties index 46f2060901ea..6ec5b869cb8c 100644 --- a/config/configuration/bundles/src/bundle/org/sakaiproject/config/bundle/default.sakai.properties +++ b/config/configuration/bundles/src/bundle/org/sakaiproject/config/bundle/default.sakai.properties @@ -5932,3 +5932,17 @@ # lti.encryption.key=some-string-that-you-keep-secret # DEFAULT: none +# Configure a 1EdTech Personal Needs & Preferences (PNP) Server +# The @ sign is a place holder in the PNP URL for either the user_id or email (see below) +# lti.pnp.baseurl=https://pnp.amp-up.io/ims/afapnp/v1p0/users/@/afapnprecords +# DEFAULT: none + +# Normally for PNP we include the user_id value from the LTI launch. How the user_id is +# set in Sakai and in the PNP server is a bit of a complex issue but using the user_id +# from the launch if the right default. However, sometimes for testing it is simpler to +# user the email so a tester can reliable re-create a user on an empty system to test +# PNP. Setting this to true is fine on dev or test servers but probably is not suitable +# on a production system. If there is no PNP base URL, this value has no effect. +# lti.pnp.use_email=false +# DEFAULT: false + diff --git a/lti/lti-common/src/java/org/sakaiproject/lti/util/SakaiLTIUtil.java b/lti/lti-common/src/java/org/sakaiproject/lti/util/SakaiLTIUtil.java index 71a0f634c652..bc19dac84d3a 100644 --- a/lti/lti-common/src/java/org/sakaiproject/lti/util/SakaiLTIUtil.java +++ b/lti/lti-common/src/java/org/sakaiproject/lti/util/SakaiLTIUtil.java @@ -112,6 +112,7 @@ import org.tsugi.lti13.objects.LaunchLIS; import org.tsugi.lti13.objects.NamesAndRoles; import org.tsugi.lti13.objects.GroupService; +import org.tsugi.lti13.objects.PNPService; import org.tsugi.lti13.objects.ResourceLink; import org.tsugi.lti13.objects.ToolPlatform; import org.tsugi.lti13.objects.ForUser; @@ -2048,6 +2049,22 @@ public static String[] postLaunchJWT(Properties toolProps, Properties ltiProps, lj.group_service = gs; } + // SAK-50682 - Add support for PNPService + String pnpBaseUrl = ServerConfigurationService.getString("lti.pnp.baseurl", null); + Boolean pnpUseEmail = ServerConfigurationService.getBoolean("lti.pnp.use_email", false); + String user_email = ltiProps.getProperty(LTIConstants.LIS_PERSON_CONTACT_EMAIL_PRIMARY); + if ( StringUtils.isNotEmpty(user_id) && StringUtils.isNotEmpty(pnpBaseUrl) ) { + PNPService ps = new PNPService(); + String pnp_settings_service_url = pnpBaseUrl; + if ( pnpUseEmail && StringUtils.isNotEmpty(user_email) ) { + pnp_settings_service_url = pnp_settings_service_url.replace("@", user_email); + } else { + pnp_settings_service_url = pnp_settings_service_url.replace("@", user_id); + } + ps.pnp_settings_service_url = pnp_settings_service_url; + lj.pnp_service = ps; + } + // Add Sakai Extensions from ltiProps SakaiExtension se = new SakaiExtension(); se.copyFromPost(ltiProps); diff --git a/lti/tsugi-util/src/java/org/tsugi/lti13/objects/LaunchJWT.java b/lti/tsugi-util/src/java/org/tsugi/lti13/objects/LaunchJWT.java index 02cd316d2497..58f44392cbe3 100644 --- a/lti/tsugi-util/src/java/org/tsugi/lti13/objects/LaunchJWT.java +++ b/lti/tsugi-util/src/java/org/tsugi/lti13/objects/LaunchJWT.java @@ -92,6 +92,9 @@ public class LaunchJWT extends BaseJWT { @JsonProperty("https://purl.imsglobal.org/spec/lti-gs/claim/groupsservice") public GroupService group_service; + @JsonProperty("https://purl.imsglobal.org/spec/lti-pnp/claim/pnpservice") + public PNPService pnp_service; + // This is in LaunchJWTs @JsonProperty("nonce") public String nonce; diff --git a/lti/tsugi-util/src/java/org/tsugi/lti13/objects/PNPService.java b/lti/tsugi-util/src/java/org/tsugi/lti13/objects/PNPService.java new file mode 100644 index 000000000000..caa4baf04327 --- /dev/null +++ b/lti/tsugi-util/src/java/org/tsugi/lti13/objects/PNPService.java @@ -0,0 +1,42 @@ +package org.tsugi.lti13.objects; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +import java.util.List; +import java.util.ArrayList; + +@JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL) + +/* Spec in Draft - This is from Paul G + + $pnpHost = 'https://pnp.amp-up.io'; + $pnpBase = '/ims/afapnp/v1p0'; + $requestparams['launch_pnp_settings_service_url'] = $pnpHost . $pnpBase . '/users/' . $USER->id . '/afapnprecords'; + + $pnp_launch_settings = array( + 'pnp_settings_service_url' => $parms['launch_pnp_settings_service_url'], + 'scope' => array('https://purl.imsglobal.org/spec/lti-pnp/scope/pnpsettings.readonly'), + 'pnp_supported_versions' => array('http://purl.imsglobal.org/spec/afapnp/v1p0/schema/openapi/afapnpv1p0service_openapi3_v1p0') + ); + $payload['https://purl.imsglobal.org/spec/lti-pnp/claim/pnpservice'] = $pnp_launch_settings; +*/ + +public class PNPService extends org.tsugi.jackson.objects.JacksonBase { + public static String SCOPE_PNP_READONLY = "https://purl.imsglobal.org/spec/lti-pnp/scope/pnpsettings.readonly"; + public static String VERSION_1_0 = "http://purl.imsglobal.org/spec/afapnp/v1p0/schema/openapi/afapnpv1p0service_openapi3_v1p0"; + + @JsonProperty("scope") + public List scope = new ArrayList(); + @JsonProperty("pnp_settings_service_url") + public String pnp_settings_service_url; + @JsonProperty("pnp_supported_versions") + public List pnp_supported_versions = new ArrayList(); + + public PNPService() { + this.scope.add(SCOPE_PNP_READONLY); + this.pnp_supported_versions.add(VERSION_1_0); + } + +} diff --git a/lti/tsugi-util/src/test/org/tsugi/lti13/PNPServiceTest.java b/lti/tsugi-util/src/test/org/tsugi/lti13/PNPServiceTest.java new file mode 100644 index 000000000000..228a6ac103da --- /dev/null +++ b/lti/tsugi-util/src/test/org/tsugi/lti13/PNPServiceTest.java @@ -0,0 +1,50 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package org.tsugi.lti13; + +import static org.junit.Assert.*; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.Map; +import java.util.TreeMap; + +import org.tsugi.lti13.objects.PNPService; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.core.JsonProcessingException; + +/** + * + * @author csev + */ +public class PNPServiceTest { + + @Test + public void testConstructor() throws com.fasterxml.jackson.core.JsonProcessingException { + + PNPService ps = new PNPService(); + assertNotNull(ps); + assertEquals(ps.pnp_supported_versions.size(),1); + assertEquals(ps.pnp_supported_versions.get(0), ps.VERSION_1_0); + + assertEquals(ps.scope.size(),1); + assertEquals(ps.scope.get(0), ps.SCOPE_PNP_READONLY); + ps.pnp_settings_service_url = "https://www.myuniv.example.com/2344/groups"; + + ObjectMapper mapper = new ObjectMapper(); + String jsonString = mapper.writeValueAsString(ps); + String jsonTest = "{\"scope\":[\"https://purl.imsglobal.org/spec/lti-pnp/scope/pnpsettings.readonly\"],\"pnp_settings_service_url\":\"https://www.myuniv.example.com/2344/groups\",\"pnp_supported_versions\":[\"http://purl.imsglobal.org/spec/afapnp/v1p0/schema/openapi/afapnpv1p0service_openapi3_v1p0\"]}"; + assertEquals(jsonString, jsonTest); + } + + @Test + public void testAddServiceVersion() { + PNPService groupService = new PNPService(); + groupService.pnp_supported_versions.add("1.1"); + assertTrue(groupService.pnp_supported_versions.contains("1.1")); + } + +}