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:

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.

If you just want to get the big 4 data (Pass, Completion, Score, Time), for those things we provide an extension to the Tin Can API to allow you to pull that aggregate data directly from the SCORM Engine. You can read more about this in the Tin Can /results API documentation

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

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. The SCORM Engine makes this easy to do. The SCORM 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 External configuration object. This value can then be used in all other integration methods where tenancy will be important. Internally, the SCORM Engine will use the concept of "AppId" as a way to track tenancy amongst the various Tin Can tables. Externally, you'll continue to use the External Configuration object to represent the data your system uses to determine individual tenants.

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:

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.

Key Integration Methods

    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;
    }
    public TCAPIContext TinCanPostProcessContext(TCAPIContext ctx, Map requestParameters, String extraEndpointInfo) throws Exception{
        String tenantID = extraEndpointInfo;
        MyExternalConfiguration myConfig = (MyExternalConfiguration)ctx.getExternalConfiguration();
        myConfig.TenantID = tenantID;
        ctx.setAppId(myConfig.TenantID);
        ctx.setSandbox(false);
        ctx.setOriginalAppId(myConfig.TenantID);
        return ctx;
    }


Tin Can->SCORM Parity Reports

SCORM Parity Reports are an extension of the Tin Can API created by Rustici Software.

All Calls will return 40X if not authorized

Following are GET requests. Only GET will be supported at this time.

TCAPI/Report/results

This call requires Actor, Activity, or both. Sending neither will return nothing.

Returns 200 ok if successful returns code for not found if not found

Parameter Required Format Description
agent Optional JSON Represents the Actor object we're requesting data for. This will be passed to the integration layers GetPerson call first
activity Optional String Represents a particular Activity ID you'd like results for
Example

Endpoint: http://example.com/TCAPI/Report/results

Request Body:

agent:{
    "name": "Generic Example",
    "account": {
        "homePage": "http://example.com/ScormEngineInterface/",
        "name": "gexample"
    },
    "objectType": "Agent"
}

Response JSON:

{
    "results": [
        {
            "date": "2013-11-11T16:05:06.000Z",
            "result": {
                "score": {
                    "raw": 0.93
                },
                "success": true,
                "completion": true,
                "duration": "PT55S"
            },
            "actor": {
                "name": "Generic Example",
                "account": {
                    "homePage": "http://example.com/ScormEngineInterface/",
                    "name": "gexample"
                },
                "objectType": "Agent"
            },
            "activity": {
                "id": "http://example.com/ScormEngineInterface/3",
                "definition": {
                    "name": {
                        "en-US": "Golf Explained - Run-time Basic Calls"
                    }
                },
                "objectType": "Activity"
            }
        }
    ],
    "more": "/TCAPI/results?continueToken=6FD79067CD90FBD9453E2D9F16F988EC8BBB2F11tincan/default/activities/1.0.x/6B1B0FB819C861F744536967883DCA7D79EA387C"
}

TCAPI/Report/activities

Returns 200 ok if successful

If No Actor is Sent all Activities will be returned

Parameter Required Format Description
agent Optional JSON Represents the Actor object we're requesting data for. This will be passed to the integration layers GetPerson call first
Example

Endpoint: http://example.com/TCAPI/Report/activities

Response JSON:

{
    "results": [
        {
            "objectType": "Activity",
            "id": "http://example.org/GolfBasics",
            "definition": {
                "name": {
                    "en-US": "Golf Explained - Basics"
                }
            }
        },
        {
            "objectType": "Activity",
            "id": "http://example.org/GolfAdvanced",
            "definition": {
                "name": {
                    "en-US": "Golf Explained - Advanced Techniques
                }
            }
        }
    ]
}

TCAPI/Report/actors

Returns 200 ok if successful returns code for not found if not found

If no Activity is sent all Actors will be returned. This does not include child activities or sub activities part of a group.

Parameter Required Format Description
activity Optional String Represents a particular Actor ID you'd like results for
Example

Endpoint: http://example.com/TCAPI/Report/actors

Response JSON:

{
    "results": [
        {
            "objectType": "Agent",
            "name": "Learner One",
            "account": {
                "homePage": "http://example.org/",
                "name": "lone"
            }
        },
        {
            "objectType": "Agent",
            "name": "Learner Two",
            "account": {
                "homePage": "http://example.org/",
                "name": "ltwo"
            }
        },
        {
            "objectType": "Agent",
            "name": "Learner Three",
            "account": {
                "homePage": "http://example.org/",
                "name": "lthree"
            }
        }
    ]
}

Tin Can Statement Forwarding

Statement Forwarding provides the means to automatically forward statements sent from one LRS to another. Applications include:

SCORMEngineSettings

Properties relevant to Statement Forwarding:

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().