diff --git a/Jenkinsfile b/Jenkinsfile index ca7aba7..c0f0c3f 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -6,8 +6,7 @@ buildMvn { doDocker = { buildJavaDocker { publishMaster = 'yes' - healthChk = 'yes' - healthChkCmd = 'wget --no-verbose --tries=1 --spider http://localhost:8081/admin/health || exit 1' + //healthChk for /admin/health in MainVerticleTest.java } } } diff --git a/README.md b/README.md index b6d4cf6..9652246 100644 --- a/README.md +++ b/README.md @@ -7,18 +7,24 @@ Version 2.0. See the file "[LICENSE](LICENSE)" for more information. ## Overview -The purpose of this edge module is to connect LMS such as Sakai and Blackboard (LTI Platforms) to Folio via the [LTI Advantage](https://www.imsglobal.org/lti-advantage-overview) protocol for the purposes of sharing the course reserves stored in Folio. +The purpose of this edge module is to connect LMS such as Sakai and Blackboard (LTI Platforms) to Folio via +the [LTI Advantage](https://www.imsglobal.org/lti-advantage-overview) protocol for the purposes of sharing the course reserves stored in Folio. -To accomplish this, this module acts as an LTI Tool Provider. It adheres to two parts of the LTI Advantage spec to accomplish this. First, security is enabled via a [third-party-initiated OIDC Authentication flow](https://www.imsglobal.org/spec/security/v1p0/#platform-originating-messages) (this is a generic ). Second, the actual course reserves are requested and shown via messages using [the Resource Link spec](https://www.imsglobal.org/spec/lti/v1p3/#resource-link-launch-request-message). +To accomplish this, this module acts as an LTI Tool Provider. It adheres to two parts of the LTI Advantage spec to +accomplish this. First, security is enabled via a [third-party-initiated OIDC Authentication flow](https://www.imsglobal.org/spec/security/v1p0/#platform-originating-messages) ( +this is a generic ). Second, the actual course reserves are requested and shown via messages using [the Resource Link spec](https://www.imsglobal.org/spec/lti/v1p3/#resource-link-launch-request-message). The general use flow is as follows: -1. Upon navigation to the tool's page on an LMS such as Sakai (the LTI platform), the platform sends an OIDC login initiation request to the tool at a predefined URL. -1. This request is handled in `LtiCoursesHandler::handleOidcLoginInit`. The request is parsed and validated. -1. If the request is valid, it responds with an HTTP 302 Redirect to a preconfigured URL for that platform. -1. The platform handles that redirect and bounces a request back to the tool to the tool launch endpoint with the LTI request itself. -1. The tool parses the LTI request which contains info about which course is being viewed in the LMS. Based on that, it fetches the course and its reserves from mod-courses, renders them in HTML, and sends back the HTML. -1. The platform takes the response and displays it in an iframe. +1. Upon navigation to the tool's page on an LMS such as Sakai (the LTI platform), the platform sends an OIDC login + initiation request to the tool at a predefined URL. +2. This request is handled in `LtiCoursesHandler::handleOidcLoginInit`. The request is parsed and validated. +3. If the request is valid, it responds with an HTTP 302 Redirect to a preconfigured URL for that platform. +4. The platform handles that redirect and bounces a request back to the tool to the tool launch endpoint with the LTI + request itself. +5. The tool parses the LTI request which contains info about which course is being viewed in the LMS. Based on that, it + fetches the course and its reserves from mod-courses, renders them in HTML, and sends back the HTML. +6. The platform takes the response and displays it in an iframe. ## Configuration @@ -26,24 +32,64 @@ Configuration is done in two ways: system properties/args/secure stores, and LTI ### Arguments / System Properties -Values common to Folio Edge modules are configured using the methods made available in [edge-common](https://github.com/folio-org/edge-common). These handle things like the `port`, `okapi_url`, or `secure_store_props`. E.g., you can run the module as `java -jar edge-lti-courses-fat.jar -Dokapi_url=https://okapi.folio.my-university.com ...`. Besides the common values defined in `edge-common`, this module also uses the following system properties. - -| Name | Description | Default Value | -| ---- | ----------- | ------------- | -| `lti_tool_private_key_file` | File path to a PEM file containing an RSA-encoded (PKCS8) private key. The key will be used by the Edge module when signing responses. This is needed if your LTI platform creates a private key for each tool. This edge module will generate its own keys if this property is not defined. | | -| `lti_tool_public_key_file` | File path to a PEM file containing an RSA-encoded (X509) private key. The key will be used by the Edge module when signing responses. This is needed if your LTI platform creates a private key for each tool. This edge module will generate its own keys if this property is not defined. | | -| `oidc_ttl` | TTL in ms of the OIDC `state` cache. I.e., how long the OIDC auth handshake can take. | 10000 | -| `ignore_oidc_state` | Never use this in production!!! This allows you to debug requests by sending requests to the `/launches` endpoints directly rather than negotiating an OIDC handshake. This is inherently unsafe. | false | -| `box_api_app_token` | A [box.com API App Token](https://developer.box.com/guides/applications/custom-apps/app-token-setup/) that allows the edge module to rewrite links to Box.com files (eg, https://my-uni.box.com/file/12345678) to an URL that is handled by the edge module (eg, `/lti-courses/download-file/f00b4r-h4sh`) that are downloaded directly by the client. | | -| `download_url_ttl` | TTL in ms of the download URLs generated by the edge module when rewriting Box.com links | 600000 | +Values common to Folio Edge modules are configured using the methods made available in [edge-common](https://github.com/folio-org/edge-common). +These handle things like the `port`, `okapi_url`, or `secure_store_props`. E.g., you can run the module +as `java -jar edge-lti-courses-fat.jar -Dokapi_url=https://okapi.folio.my-university.com ...`. Besides the common values +defined in `edge-common`, this module also uses the following system properties. + +| Name | Description | Default Value | +|-----------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------| +| `lti_tool_private_key_file` | File path to a PEM file containing an RSA-encoded (PKCS8) private key. The key will be used by the Edge module when signing responses. This is needed if your LTI platform creates a private key for each tool. This edge module will generate its own keys if this property is not defined. | | +| `lti_tool_public_key_file` | File path to a PEM file containing an RSA-encoded (X509) private key. The key will be used by the Edge module when signing responses. This is needed if your LTI platform creates a private key for each tool. This edge module will generate its own keys if this property is not defined. | | +| `oidc_ttl` | TTL in ms of the OIDC `state` cache. I.e., how long the OIDC auth handshake can take. | `10000` | +| `ignore_oidc_state` | Never use this in production!!! This allows you to debug requests by sending requests to the `/launches` endpoints directly rather than negotiating an OIDC handshake. This is inherently unsafe. | `false` | +| `box_api_app_token` | A [box.com API App Token](https://developer.box.com/guides/applications/custom-apps/app-token-setup/) that allows the edge module to rewrite links to Box.com files (eg, https://my-uni.box.com/file/12345678) to an URL that is handled by the edge module (eg, `/lti-courses/download-file/f00b4r-h4sh`) that are downloaded directly by the client. | | +| `download_url_ttl` | TTL in ms of the download URLs generated by the edge module when rewriting Box.com links | `300000` | +| `port` | `8081` Server port to listen on | `8081` | +| `okapi_url` | Where to find Okapi (URL) | *required* | +| `request_timeout_ms` | Request Timeout | `30000` | +| `log_level` | Log4j Log Level | `INFO` | +| `token_cache_capacity` | Max token cache size | `100` | +| `token_cache_ttl_ms` | How long to cache JWTs, in milliseconds (ms) | `100` | +| `secure_store` | Type of secure store to use. Valid: `Ephemeral`, `AwsSsm`, `Vault` | `Ephemeral` | +| `secure_store_props` | Path to a properties file specifying secure store configuration | `NA` | + +### Env variables for TLS configuration for Http server + +To configure Transport Layer Security (TLS) for the HTTP server in an edge module, the following configuration parameters should be used. +Parameters marked as Required are required only in case when TLS for the server should be enabled. + +| Property | Default | Description | +|-----------------------------------------------------|------------------|---------------------------------------------------------------------------------------------| +| `SPRING_SSL_BUNDLE_JKS_WEBSERVER_KEYSTORE_TYPE` | `NA` | (Required). Set the type of the keystore. Common types include `JKS`, `PKCS12`, and `BCFKS` | +| `SPRING_SSL_BUNDLE_JKS_WEBSERVER_KEYSTORE_LOCATION` | `NA` | (Required). Set the location of the keystore file in the local file system | +| `SPRING_SSL_BUNDLE_JKS_WEBSERVER_KEYSTORE_PASSWORD` | `NA` | (Required). Set the password for the keystore | +| `SPRING_SSL_BUNDLE_JKS_WEBSERVER_KEY_ALIAS` | `NA` | Set the alias of the key within the keystore. | +| `SPRING_SSL_BUNDLE_JKS_WEBSERVER_KEY_PASSWORD` | `NA` | Optional param that points to a password of `KEY_ALIAS` if it protected | + +### Env variables for TLS configuration for Web Client + +To configure Transport Layer Security (TLS) for Web clients in the edge module, you can use the following configuration parameters. +Truststore parameters for configuring Web clients are optional even when `FOLIO_CLIENT_TLS_ENABLED = true`. +If truststore parameters need to be populated, `FOLIO_CLIENT_TLS_TRUSTSTORETYPE`, `FOLIO_CLIENT_TLS_TRUSTSTOREPATH` and `FOLIO_CLIENT_TLS_TRUSTSTOREPASSWORD` are required. + +| Property | Default | Description | +|-----------------------------------------|-------------------|----------------------------------------------------------------------------------| +| `FOLIO_CLIENT_TLS_ENABLED` | `false` | Set whether SSL/TLS is enabled for Vertx Http Server | +| `FOLIO_CLIENT_TLS_TRUSTSTORETYPE` | `NA` | Set the type of the keystore. Common types include `JKS`, `PKCS12`, and `BCFKS` | +| `FOLIO_CLIENT_TLS_TRUSTSTOREPATH` | `NA` | Set the location of the keystore file in the local file system | +| `FOLIO_CLIENT_TLS_TRUSTSTOREPASSWORD` | `NA` | Set the password for the keystore | ### LTI Platforms -LTI Advantage requires that Platforms and Tools have knowledge of one other before they can safely and effectively communicate. This Tool is configured with the platforms it should respond to via the [`ui-lti-courses`](https://github.com/doytch/ui-lti-courses) module. More information about what is configurable can be found there, but at a minimum, this Tool needs to know the following about a platform: +LTI Advantage requires that Platforms and Tools have knowledge of one other before they can safely and effectively +communicate. This Tool is configured with the platforms it should respond to via the [`ui-lti-courses`](https://github.com/doytch/ui-lti-courses) module. +More information about what is configurable can be found there, but at a minimum, this Tool needs to know the following about a platform: - Client ID: The ID that was generated _for the Tool_ by the Platform. - Issuer: Corresponds to the `iss` field in a JWT sent by the Platform. -- JWKS URL: A location where the Tool can fetch the Platform's JWKS so that it can decode the JWTs sent and signed by the Platform. +- JWKS URL: A location where the Tool can fetch the Platform's JWKS so that it can decode the JWTs sent and signed by + the Platform. - OIDC Authorization URL: The URL that the Tool should redirect the Platform to after handling an OIDC Login Initiation. - Search URL: A templated URL that the Tool will use when rendering HTML links to the course reserve items. @@ -53,11 +99,14 @@ LTI Advantage requires that Platforms and Tools have knowledge of one other befo ## Security -[edge-common](https://github.com/folio-org/edge-common) contains a description of the security model as it relates to the `apiKey`. Additionally, further security is added due to LTI's use of an OIDC login flow. +[edge-common](https://github.com/folio-org/edge-common) contains a description of the security model as it relates to the `apiKey`. +Additionally, further security is added due to LTI's use of an OIDC login flow. ### apiKey -This module requires passing the `apiKey` as a path param, e.g., `https://my-server.edu/lti-courses/launches/myApiKey`. The reason for this is that many LMS expect to have exclusive use of the query params and insert a second `?` in the URL, so this just ensures things are a bit less flaky. +This module requires passing the `apiKey` as a path param, e.g., `https://my-server.edu/lti-courses/launches/myApiKey`. +The reason for this is that many LMS expect to have exclusive use of the query params and insert a second `?` in the +URL, so this just ensures things are a bit less flaky. #### Platform RSA Keys @@ -66,19 +115,31 @@ The Platform's public key is fetched via a JWKS. The Platform's JWKS URL is conf ## Requires Permissions Institutional users should be granted the following permission in order to use this edge module: + - `course-reserves-storage.courselistings.reserves.collection.get` - `course-reserves-storage.courses.collection.get` - `configuration.entries.collection.get` ## Box.com API integration -Folio inventory items can have their "Electronic access" defined with an URL. If an item's electronic access URL is a link to a file stored on box.com (eg, https://my-uni.box.com/file/12345678), the link rendered by the edge module would normally link the user to that same box.com URL. If the user viewing the link doesn't have permissions to view the page, then the edge module can be configured so that the link rendered can instead result in the user downloading the file directly regardless of their permissions. +Folio inventory items can have their "Electronic access" defined with an URL. If an item's electronic access URL is a +link to a file stored on box.com (eg, https://my-uni.box.com/file/12345678), the link rendered by the edge module would +normally link the user to that same box.com URL. If the user viewing the link doesn't have permissions to view the page, +then the edge module can be configured so that the link rendered can instead result in the user downloading the file +directly regardless of their permissions. -To enable this functionality, [you need to pass in a Box.com API App Token](https://developer.box.com/guides/applications/custom-apps/app-token-setup) via the `box_api_app_token` system property as described above. Afterwards, when responding to a LTI Request the edge module will rewrite box.com links into generated links to _itself_ that contain a hash of the file ID. The generated link will be valid for 10 minutes by default. When a user clicks the link, the edge module will download the file directly using it's box.com App Token and route the file contents to the user as a file download. +To enable this functionality, [you need to pass in a Box.com API App Token](https://developer.box.com/guides/applications/custom-apps/app-token-setup) +via the `box_api_app_token` system property as described above. Afterwards, when responding to a LTI Request the edge +module will rewrite box.com links into generated links to _itself_ that contain a hash of the file ID. The generated +link will be valid for 10 minutes by default. When a user clicks the link, the edge module will download the file +directly using its box.com App Token and route the file contents to the user as a file download. ## Additional Docs -- [OAuth 2.0 and OIDC (in plain English)](https://www.youtube.com/watch?v=996OiexHze0): I love this video as a primer on the oft-confusing OAuth/OIDC flow. -- [Adding the reference implementation Tool to Sakai](https://github.com/sakaiproject/sakai/blob/master/basiclti/docs/IMS_RI.md): This is useful if you need something to pattern adding this Tool into your instance of Sakai (or other LMS). +- [OAuth 2.0 and OIDC (in plain English)](https://www.youtube.com/watch?v=996OiexHze0): I love this video as a primer on + the oft-confusing OAuth/OIDC flow. +- [Adding the reference implementation Tool to Sakai](https://github.com/sakaiproject/sakai/blob/master/basiclti/docs/IMS_RI.md): + This is useful if you need something to pattern adding this Tool into your instance of Sakai (or other LMS). - [LTI Advantage Reference Implementations](https://lti-ri.imsglobal.org/): Useful for debugging platform/tool issues. -- [ui-lti-courses](https://github.com/doytch/ui-lti-courses): Folio settings module that allows you to register LTI Platforms for this tool +- [ui-lti-courses](https://github.com/doytch/ui-lti-courses): Folio settings module that allows you to register LTI + Platforms for this tool diff --git a/pom.xml b/pom.xml index 326ef84..34c2cb8 100644 --- a/pom.xml +++ b/pom.xml @@ -72,7 +72,7 @@ org.folio edge-common - 4.5.2 + 4.7.0 diff --git a/src/main/java/org/folio/edge/ltiCourses/LtiCoursesHandler.java b/src/main/java/org/folio/edge/ltiCourses/LtiCoursesHandler.java index 6839067..2d2826a 100644 --- a/src/main/java/org/folio/edge/ltiCourses/LtiCoursesHandler.java +++ b/src/main/java/org/folio/edge/ltiCourses/LtiCoursesHandler.java @@ -12,12 +12,12 @@ import org.folio.edge.core.ApiKeyHelper; import org.folio.edge.core.security.SecureStore; +import org.folio.edge.core.utils.OkapiClientFactory; import org.folio.edge.ltiCourses.cache.OidcStateCache; import org.folio.edge.ltiCourses.model.Course; import org.folio.edge.ltiCourses.model.LtiPlatform; import org.folio.edge.ltiCourses.utils.LtiContextClaim; import org.folio.edge.ltiCourses.utils.LtiCoursesOkapiClient; -import org.folio.edge.ltiCourses.utils.LtiCoursesOkapiClientFactory; import static org.folio.edge.ltiCourses.Constants.LTI_MESSAGE_TYPE_RESOURCE_LINK_REQUEST; @@ -45,7 +45,7 @@ public class LtiCoursesHandler extends org.folio.edge.core.Handler { public LtiCoursesHandler( SecureStore secureStore, - LtiCoursesOkapiClientFactory ocf, + OkapiClientFactory ocf, ApiKeyHelper apiKeyHelper, RSAPrivateKey privateKey, JadeTemplateEngine jadeTemplateEngine, @@ -75,7 +75,7 @@ protected void handleCommonLTI( return; } - LtiCoursesOkapiClient coursesOkapiClient = (LtiCoursesOkapiClient) client; + LtiCoursesOkapiClient coursesOkapiClient = new LtiCoursesOkapiClient(client); coursesOkapiClient.getPlatform( issuer, response -> { diff --git a/src/main/java/org/folio/edge/ltiCourses/MainVerticle.java b/src/main/java/org/folio/edge/ltiCourses/MainVerticle.java index 81a6e84..0211742 100644 --- a/src/main/java/org/folio/edge/ltiCourses/MainVerticle.java +++ b/src/main/java/org/folio/edge/ltiCourses/MainVerticle.java @@ -20,9 +20,10 @@ import org.folio.edge.core.ApiKeyHelper; import org.folio.edge.core.EdgeVerticleHttp; +import org.folio.edge.core.utils.OkapiClientFactory; +import org.folio.edge.core.utils.OkapiClientFactoryInitializer; import org.folio.edge.ltiCourses.cache.BoxFileCache; import org.folio.edge.ltiCourses.cache.OidcStateCache; -import org.folio.edge.ltiCourses.utils.LtiCoursesOkapiClientFactory; import io.vertx.core.http.HttpMethod; import io.vertx.ext.web.Router; @@ -95,11 +96,7 @@ public Router defineRoutes() { JadeTemplateEngine jadeTemplateEngine = JadeTemplateEngine.create(vertx); // Next, set up the common Edge module stuff. - final LtiCoursesOkapiClientFactory ocf = new LtiCoursesOkapiClientFactory( - vertx, - config().getString(org.folio.edge.core.Constants.SYS_OKAPI_URL), - config().getInteger(org.folio.edge.core.Constants.SYS_REQUEST_TIMEOUT_MS) - ); + final OkapiClientFactory ocf = OkapiClientFactoryInitializer.createInstance(vertx, config()); final ApiKeyHelper apiKeyHelper = new ApiKeyHelper("PATH"); diff --git a/src/main/java/org/folio/edge/ltiCourses/utils/LtiCoursesOkapiClientFactory.java b/src/main/java/org/folio/edge/ltiCourses/utils/LtiCoursesOkapiClientFactory.java deleted file mode 100644 index f279ea5..0000000 --- a/src/main/java/org/folio/edge/ltiCourses/utils/LtiCoursesOkapiClientFactory.java +++ /dev/null @@ -1,16 +0,0 @@ -package org.folio.edge.ltiCourses.utils; - -import org.folio.edge.core.utils.OkapiClientFactory; - -import io.vertx.core.Vertx; - -public class LtiCoursesOkapiClientFactory extends OkapiClientFactory { - - public LtiCoursesOkapiClientFactory(Vertx vertx, String okapiURL, int reqTimeoutMs) { - super(vertx, okapiURL, reqTimeoutMs); - } - - public LtiCoursesOkapiClient getOkapiClient(String tenant) { - return new LtiCoursesOkapiClient(vertx, okapiURL, tenant, reqTimeoutMs); - } -}