RusticiSoftware / TinCanPHP

PHP library for the Experience API (Tin Can API)
http://rusticisoftware.github.io/TinCanPHP/
Apache License 2.0
87 stars 78 forks source link

use TinCanPHP with cmi5 #84

Closed knowledgeplaces closed 7 years ago

knowledgeplaces commented 7 years ago

Hi,

First, thanks for providing the TinCanPHP library and Cloud SCORM.

I use TinCanPHP to send XAPI Statements from a WordPress based LCMS I develop, and I use Cloud Scorm as a test LRS for these XAPI Statements, and everything works perfectly fine.

I am now investigating cmi5 as a way to package content produced with my LCMS, so these contents could be launched and tracked from any cmi5 compatible LMS.

As stated here, there is a specific launch mechanism with cmi5: https://github.com/AICC/CMI-5_Spec_Current/blob/quartz/cmi5_spec.md#launch_method

In this launch mechanism, the AU, i.e. the content produced with my LCMS, uses a fetch URL to obtain an authorization token created and managed by the LMS. The AU must then place the authorization token in the Authorization headers of all HTTP requests made to the endpoint using the xAPI.

With TinCanPHP, XAPI Statements are sent using HTTP Authentification, as stated here: https://github.com/adlnet/xAPI-Spec/blob/master/xAPI-Communication.md#authentication

With the TinCanPHP library, I can set the endpoint LRS using its URL and proper credentials, i.e. "userid" and "password".

How can I use the TinCanPHP library to set in HTTP headers of all requests sent to the LRS the token the AU has received from the fetch URL?

If this is not supported, do you plan to do so? When?

If you do not plan to do it, is this something I could do on my own?

Regards.

garemoko commented 7 years ago

There's three different ways to get files in PHP. Your approach would be to get the file url from the querystring and then get read the file using one of the methods I linked. You could then use the contents of that file in configuring TinCanPHP.

It's worth noting though that this method is nowhere near as secure as having the LRS configured within your application. You might decide it's better just to ignore the fetch parameter and encourage your users to configure the LRS credentials in your application rather than in the LMS. If you do go down that path, you might consider using a different mechanism for passing user details too, such as SSO.

brianjmiller commented 7 years ago

@garemoko that wouldn't be cmi5 conformant, and at least in the Rustici Cloud implementation you wouldn't get cmi5 handling as you must use the credential for the session for it to pay attention to that session. Otherwise it just looks like any other random xAPI statement. Additionally (though we don't do this currently) it would be possible for an LRS to prevent access to the LMS launch data (in the State) needed based on credential. Ignoring the fetch URL value should absolutely not be done.

@knowledgeplaces though it is poorly documented, the TinCan.RemoteLRS constructor can take different forms of arguments, so you have a few options:

  1. You can pass an (object) array where one of the keys is auth and the value is that returned by the fetchUrl. You would pass the endpoint at the same time. This is likely the easiest.
  2. You can pass three arguments to the constructor where the three arguments are the endpoint, the version of xAPI to use, and the auth value from the fetch URL.
  3. You can pass no arguments and then set the values on the object directly using the available methods (setEndpoint, setAuth, etc.) after construction.

Have a look at the constructor source: https://github.com/RusticiSoftware/TinCanPHP/blob/master/src/RemoteLRS.php#L39

garemoko commented 7 years ago

@brianjmiller good to know. I guess serverside applications have to weigh up the risks of exposing the LRS credentials fetch url vs. the benefits of cmi5. If there's plenty of other client side content being launched from the LMS anyway, then it's presumably less of an issue.

I wonder if a future version of cmi5 will include serverside learning record providers as one of the other launch environments.

knowledgeplaces commented 7 years ago

@brianjmiller thanks for feedback, it sounds quite clear now. I think I will go this way in my LCMS PHP code:

  1. get the fetch url from the fetch url parameter of the cmi5 launch url generated by the LMS
  2. then get the token using file_get_contents on this fetch url
  3. then set LRS using TinCanPHP with 3 parameters: endpoint URL, version and token (this was the point I was really missing)

Please confirm this is correct and secure.

In my LMS, I think I will go this way:

  1. generate the cmi5 launch URL with fetch parameter set to a url with a sessionId parameter, sessionId being managed by the LMS and being unique for each launch of a cmi5 content by a user
  2. generate a token associated to a sessionId, stored in the LMS database
  3. the fetch url returns the token associated to the sessionId, and as mentioned in the cmi5 spec, updates the LMS database to invalidate this token so the fech url cannot be reused
  4. All http requests are https

Please confirm this is also correct and secure.

brianjmiller commented 7 years ago

@garemoko I'm not sure I follow what you are saying. You mean having the cmi5 credential passed to a client side app being exposed by the launched server side app? cmi5 already supports server side LRPs as the AU itself, which on the server is the thing communicating with the LRS so the credential is never exposed to the end user.

@knowledgeplaces that sounds correct.

knowledgeplaces commented 7 years ago

@brianjmiller I have completed the 3 steps in my CMS:

  1. get the fetch url from the fetch url parameter of the cmi5 launch url generated by the LMS
  2. then get the token using file_get_contents on this fetch url
  3. then set LRS using TinCanPHP with 3 parameters: endpoint URL, version and token (this was the point I was really missing)

Unfortunately, when I want to save statements, I get an error: TinCan\LRSResponse Object {success: false, content: "Credentials invalid for this endpoint.", httpResponse: Object}

I use the auth token I got, such as: OjMzNTMwMTY4LWY5YzktNDlkNS04MjFjLTg3YWFkZGY3OTY1OQ== and the endpoint I also got from the launch URL: https://cloud.scorm.com:443/tc/XD18NMIQ10/

Do I need to do something else, maybe in the Cloud Scorm admin area?

Is it because I have not yet send the "initialized" cmi5 statement and directly send a "experienced" statement?

brianjmiller commented 7 years ago

@knowledgeplaces I suspect you need to prepend the auth token with Basic (note the space) to indicate to the server that it is a Basic Auth token in the header.

knowledgeplaces commented 7 years ago

Thanks @brianjmiller , adding Basic made the trick.

I am now deep diving into cmi5 spec and TinCanPhp code and expect to send my first statements to cloud.scorm in a few days!