Tin Can Integration Walkthrough
##Concepts##
Security and the Tin Can API
Each statement that comes into the Tin Can web service is evaluated for access rights before proceeding. The first thing that's determined is the "Asserter". The Asserter is essentially a combination of an Actor and a set of permissions. The Actor here is the person/system that is acting as the authority for the Tin Can statement being processed. When statements are being written, this Actor actually shows up as the authoritative source in the statement.
Tin Can security is fully customizable through new SCORM Engine Integration methods. If using basic authentication you will likely want to implement:
Actor TinCanGetAuthorityFromBasicAuth(TCAPIContext context, String username, String password);
The default implementation will only accept one username/password which has full authority. This username/password is defined by your SCORMEngineSettings.config entry named "TinCanRootAccount". This config entry has both the name and password separated by a colon. Ex: "joeadmin:mypass".
If using OAuth we already have a good default implementation so you probably won’t override this, at least initially..
What a particular user can do is defined by the Integration method TinCanGetPermissions().
We have defaults for the root user, a person(actor) and an application(actor). However, by overriding this integration method you can have fine-grained control to all permissions.
###Auth###
Authentication can be handled in Tin Can via Basic Auth or OAuth and is a necessity if you wish to handle Tin Can Statements from an activity not launched by your LMS. Going forward it's expected LMSs will want to track learning happening in places they were never able to track before. An example might be a learner's interactions with a forum inside the LMS. In order to record those interactions as statements in the LRS you will need to have some authentication set up. Integration methods are provided to allow you to tie both of these concepts into your existing system. An example scenario would be allowing a user's Single Sign On credentials to authenticate against your system, through the integration method. This will allow them to use those same credentials for LRS access as they use for other parts of the system. Additionally, you may want to provide special accounts to Activity Providers (like the forum software) so that you can always verify who is making the statement.
###Permissions###
You'll want to consider what permission level each individual account should have. The default permissions level allows customization across two primary areas:
- Statement Writing: By default a user is able to send any statement (About any Actor) and that user will be recorded as the Authority for that statement. The Authority is the person making the claim.
- Statement Reading: By default a user is able to read any statement in which they appear. This includes as the Actor, Authority, Group member, instructor, etc.
Additionally, there is a Root permissions level. This level is what you'll want to use for third party applications you want to have the ability to both read and write any statements to or from the LMS. While it's unlikely you'll apply this permissions level to many, if any, third parties we've made the option available for your own systems.
###Reporting###
Understanding how you want to handle reporting on Tin Can data requires you to understand how you want to use Tin Can. For our customers that will be only using Tin Can content created as traditional courses launched by an LMS you don't need to do anything special. We will aggregate the data from those statements and send it to you via the same RollupRegistration Method we always have. The caveat with this approach is that this approach will focus entirely on the result object within the statement. This means we'll report on completion, success, score, and time as reflected in the result object and we will not depend on the verb for any meaning.
For people looking to record results data for Tin Can content not launched by the LMS you'll want to copy the statements out to a separate location via our TinCanStatementsStored Integration Method for in depth analysis.
To facilitate querying for Tin Can data, we have included some Recipes within Engine to help you pull data for some common data sets including Video, Results Summary and CMI Interactions.
###Content Auth###
When we talk about Content Authorization and its relationship to Tin Can we're actually talking about the Tin Can addendum launch spec. The short version is that some Activity Providers may be launched, or initiated, by an LMS but end up taking place outside of the browser where handling content authorization for the content's resources becomes tricky. The easiest example would be a course that launched in a browser, detected it was on a mobile device, and then redirected to a mobile app. The mobile app needs to download the various resources inside the package such as images and videos. For most LMSs these resources are protected by a cookie, or some other authorization scheme, and the authorization mechanic is available to the browser. Once we've left the confines of the browser we have no way to pass cookies to something like a mobile application, and we wouldn't want to.
What we do pass, however, is the endpoint and credentials for accessing the LRS. With that data, and the full implementation of the spec referenced above, we're able to use the Tin Can endpoint as a way to proxy content files to the mobile application. This means that your content is still protected by your authentication scheme but can be made available to people with valid LRS credentials while they take your course.
###Multi-Tenant###
There are 4 integration methods that need to be considered and possibly overriden in order to make multi-tenant setups work correctly, and they are also listed in the Key Integration Methods section: TinCanUseMultipleEndpoints
, TinCanBuildContext
,TinCanPostProcessContext
, TinCanGetMultiTenantContexts
.
Internally, Engine uses an AppId
property on the TCAPIContext
object as a way to track tenancy amongst the various Tin Can tables. Externally and in the integration layer, you'll use your implementation of the ubiquitous ExternalConfiguration
object to represent the data your system uses to determine individual tenants. When launching content from Engine, the external configuration is passed in on the query string. However, this functionality is not part of the Tin Can spec, so we cannot expect activity providers to pass the tenant info in a query string when communicating with the LRS. Therefore, in order to make multi-tenant setups work correctly with Tin Can you'll want to use a different Tin Can API endpoint for each of your tenants. Engine is prepared to parse out any value that appears between the Tin Can API and the method name being called and present that to the Integration layer as a way to distinguish which tenant is accessing the system and creating the correct external configuration object.
An example might be:
http://example.com/ScormEngine/TCAPI/examplecompany/statements
When accessing the resource above, "examplecompany" will be passed to TinCanPostProcessContext
where the Integration layer should convert the string "examplecompany" to both a unique value that can be used internally to fill the appId variable in the context object as well as a valid ExternalConfiguration
object. This value can then be used in all other integration methods where tenancy will be important.
A note on the use of TinCanBuildContext
and TinCanPostProcessContext
:
Both methods are called when a Tin Can request is first received, and both are meant to instantiate or complete the instantiation of a set of the objects necessary for the request to be completed (e.g. Data Store(s)). However, TinCanBuildContext
method takes an ExternalConfiguration
parameter, while TinCanPostProcessContext
method attempts to get any external information from the string parameter passed under the name "extraEndpointInfo". As such, in a single-tenant scenario, the TinCanBuildContext
method will likely be responsible for most of the instantiation, while TinCanPostProcessContext
will perform some cleanup and validation. However, when we are dealing with multiple tenants, both methods will need to be able to create the full context, since the tenant information can come in either or both of the methods. In fact, there are later times when TinCanBuildContext
is called, and for it to function properly, it must assume that the ExternalConfiguration
being passed in was correctly populated. The only place to do this when endpoints represent tenants is in the TinCanPostProcessContext
method.
Some optional Tin Can extensions, such as the /results API, will require implementation of the integration method TinCanGetMultiTenantContexts
. TinCanPostProcessContext
can be used to construct this list of contexts, using the list of Tin Can API endpoints. This will be used to perform background processing using all of the possible ExternalConfiguration Obejcts your system depends on to inspect and transform the data for each of your customers/connection strings.
##Config##
In order to use Tin Can successfully in Engine, you’ll need to do a few things:
SystemHomepageUrl: Set a value for SystemHomepageUrl in SCORMEngineSettings.config. This is basically a way for actors created implicitly through LMS-style launches of Tin Can content to have a base URL. Typically, it will be your core service URL.
TinCanBasicAccounts: Set a value for TinCanBasicAccounts in SCORMEngineSettings.config. A non-public LRS must have some minimum level of authentication. Tin Can requires basic HTTP authentication and OAuth implementations in the LRS. Engine supports both. Engine also gives you a very lightweight mechanism to configure credentials for common HTTP auth accounts. An example use case is the built in Statement Viewer in the console dashboard. This tool requires at least one read only account be defined to authenticate with the endpoint. Similarly, the OAuth management page requires a root account be defined.
You can read the lengthy comment for this value in the SCORMEngineSettings.config that came with your Engine release for more information. More than likely, you can copy the default from the new config into your existing config and uncomment it, and it should work. That should be enough to get you up and running with Tin Can in console. If your setup is multi-tenant and you want an endpoint per, say, ClientId, then we might have a little more work to do.
ConvertTinCanFromScormRealtime: If you’d like to enable SCORM to Tin Can conversion for all registrations as they happen, set ConvertTinCanFromScormRealtime to true in your config. These statements will be piped through the relevant Tin Can Integration methods and can be interacted with from there.
ConvertTinCanFromScormHistorical: If you’d like to retroactively convert old SCORM registrations to Tin Can statements, set ConvertTinCanFromScormHistorical to true in your config.
Another interesting behavior to note is the way Engine creates Tin Can Activity IDs while generating statements for courses created in another learning standard like SCORM. Starting in version 2014.1, the generated ID will contain a SHA-1 hash of either the package contents or just the manifest file when all of the contents cannot be obtained through directory browsing (e.g. online content). By default, this hash will be transformed into a urn of the following format: urn:uuid:6e8bc430-9c3a-11d9-9669-0800200c9a66
. However, by setting the following config to true
, your activity ID can be formatted as a url, with the base of the url coming from the SystemHomepageUrl
setting:
- ScormPackageActivityIdIsUrl
So if this setting is included in SCORMEngineSettings.config and set to
true
, the output might look like:http://BelovedRusticiCustomer/6e8bc430-9c3a-11d9-9669-0800200c9a66
.
Note that in the past, the generated Activity ID would be different each time a package was imported into Engine (even if the same package was imported multiple times). With this new approach, each package is likely to have only one corresponding ActivityID regardless of how many times it's imported. This is more in the spirit of what it means to be a Tin Can Activity.
##Key Integration Methods
- TinCanBuildContext //multi tenant set appID
public TCAPIContext TinCanBuildContext(TinCanVersion version, ExternalConfiguration cfg, boolean isLaunchLink) throws Exception { MyExternalConfiguration myConfig = (MyExternalConfiguration)cfg); TCAPIContext ctx = super.TinCanBuildContext(version, cfg, isLaunchLink); ctx.setAppId(myConfig.TenantID); ctx.setSandbox(false); ctx.setOriginalAppId(myConfig.TenantID); return ctx; }
- TinCanPostProcessContext //multi tenant
public TCAPIContext TinCanPostProcessContext(TCAPIContext ctx, Map requestParameters, String extraEndpointInfo) throws Exception{ String tenantID = extraEndpointInfo; MyExternalConfiguration myConfig = (MyExternalConfiguration)ctx.getExternalConfiguration(); myConfig.TenantID = tenantID; //For multi-tenant, a changed Tenant ID means we should probably re-create all of the objects necessary to complete this request (e.g. Data Store) ctx.TinCanImplementation = TCAPI.CreateImplementation(ctx.RequestVersion, myConfig); ctx.setAppId(myConfig.TenantID); ctx.setSandbox(false); ctx.setOriginalAppId(myConfig.TenantID); return ctx; }
- TinCanStatementsStored
- TinCanAllowStatements
- TinCanGetAuthorityFromBasicAuth
- TinCanGetAuthorityFromOAuthCredentials
- TinCanGetPermissions
- TinCanHandleContentRequest
- CustomizeConvertedScormStatements
- TinCanGetMultiTenantContexts
Recipes
Recipes offer a methodology for doing relational reporting on Tin Can statements stored in your Engine LRS.
First, there must be a well described collection of statements, such as those that exist in Profiles in our Registry. (Alternatively, there could be a well described implicit scenario, such as results summary data from SCORM.)
Second, there must be a listener in Engine with associated tables.
Engine ships with three Recipes implemented: results summary, interactions, and video.
Read more about the concept of Recipes here.
Read more about the Recipes API in Engine here.
##Settings
In order to use Recipes, you must set the following:
TinCanDataCabinetEnabled
- must be set to true in order for statement processing (listening) to occurTinCanRecipesConfigurationFilePath
- must be set to a valid JSON file that describes the Recipes (Engine ships with a recipes.json file that can be used or extended)
Tin Can Statement Forwarding
Statement Forwarding provides the means to automatically forward statements sent from one LRS to another. Applications include:
- Forwarding for users who wish to have their statements stored in their own Personal Data Lockers
- Replicating production data in a sandbox LRS
##Settings
Properties relevant to Statement Forwarding:
TinCanStatementForwardEnable
- Set this totrue
to enable statement forwarding. The default isfalse
.TinCanStatementForwardSleepTime
- In milliseconds, specifies how long the statement forwarding thread should sleep before polling the source LRSs for forwarding.TinCanStatementForwardMaxBackoffHours
- Upon failing to forward statements to a destination LRS, the statement forwarding thread will wait for an exponentially increasing "backoff" duration before attempting a retry. This sets the maximum threshold for that duration.TinCanStatementForwardStmtBatchSize
- Limit for gathering batches of statements for fowarding.TinCanStatementForwardWebTimeout
- Timeout (in milliseconds) for the HTTP requests gathering and posting the Tin Can statements.
##Integration
For security, generate unique credentials for source and target Learning Record Stores
In order to configure statement forwarding, call ScormEngineManager::AddTinCanForwardingPath(ExternalConfiguration, StatementForwardingPair)
to add paths for forwarding between a source and destination LRS. StatementForwardingPair is a construct containing the following members
Member | Type | Description |
---|---|---|
SourceUrl | string | Contains the endpoint url/query for the source LRS. |
SourceCredentials | Credentials | Contains the credentials for accessing the source LRS |
DestinationUrl | string | Contains the endpoint url for the destination LRS. |
DestinationCredentials | Credentials | Credentials for the destination LRS. |
Id | string | Unique ID for this statement forwarding pair. |
The call will return the unique ID for the statement forwarding. Save this ID for future management of this statement forwarding pair and passing to ScormEngineManager::UpdateTinCanForwardingPath()
and ScormEngineManager::DeleteTinCanForwardingPath()
.