Extend your application with import abilities

You can extend your application to support automated adding of content or creating instances through the itslearning Organisation API. It allows to create extension instances and inserting their contents in batch. In order to do that, you can implement two web services: FileStreamservice and an InstanceService. First should contain a logic to upload files through Organisation API into the extension. The second one will be invoked be Organisation API to create an extension instance and insert new content to an extension. In an example scenario, a privileged user runs Organisation API's Create.Extension.Instance. The request invoking the method contains information about extension instance's identity and content. Then, another request is sent from itslearning to the extension's web services to call the logic responsible for creating the extension's instance. The logic is enclosed within the methods that have to be written by the extension's author. More about the Organisation API and implementation of the additional services for can be found here. All of the services belonging to your extension should be properly secured. You need to ensure, that both FileStreamservice and InstanceService will work only with authorized input coming from itslearning Organisation API:

  • All incoming messages will have a header added to the message containing a signature and a timestamp of the message.

  • All incoming messages should be checked for a valid signature and the timestamp of the message is checked against the lifetime.

The signature is calculated from the concatenation of the message body, shared secret and timestamp, separated by pipelines. Then, the input is hashed by SHA1 algorithm. The signature can be used to verify authenticity of the message. On top of that, a timestamp with arbitrary set request expiration time can be used to prevent reply attacks.

Authorization process step-by-step

First, Organisation API's Create.Extension.Instance method is invoked.

Then a request comes from itslearning to the extension:

POST http://www.myitslearningextension.com:3001/Services/InstanceService.svc HTTP/1.1
Content-Type: text/xml; charset=utf-8
SOAPAction: "http://tempuri.org/IInstanceService/CreateInstance"
Host: www.myitslearningextension.com:3001
...
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"><s:Header><Authentication xmlns="http://www.itslearning.com/Authentication" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"><Signature>NTCmZDatRXKIzTj0VX4oE2Zrw7E=</Signature><Timestamp>2014-01-07T09:05:46.1086945Z</Timestamp></Authentication></s:Header><s:Body><CreateInstanceRequest xmlns="http://tempuri.org/"><Context xmlns:a="http://schemas.datacontract.org/2004/07/Itslearning.Platform.RestApi.Sdk.Common.Services" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"><a:AllowedHtmlCodeLevel>HighlyRestricted</a:AllowedHtmlCodeLevel><a:ApiSessionId>bepsggjny3wiwu45gpl4eu55</a:ApiSessionId><a:CustomerId>1</a:CustomerId><a:LearningObjectId>249</a:LearningObjectId><a:LearningObjectInstanceId>1247</a:LearningObjectInstanceId><a:Role>Guest</a:Role><a:UserId>2</a:UserId></Context></CreateInstanceRequest></s:Body></s:Envelope>

The request contains Authentication element, that consists of the UTC-based timestamp and the signature. To verify the signature, first we have to concatenate content of <Body> element, extension's shared secret and the attached timestamp and separate them by "|" pipelines. We assume, that the shared secret is equal to "43ff2a46-ca3e-450e-8f35-4a30d0ab4953". The timestamp must be presented in yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'fff'Z' format. Therefore, 2014-01-07T09:05:46.1086945Z must be converted to 2014-01-07T09:05:46.108Z:

<CreateInstanceRequest xmlns="http://tempuri.org/"><Context xmlns:a="http://schemas.datacontract.org/2004/07/Itslearning.Platform.RestApi.Sdk.Common.Services" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"><a:AllowedHtmlCodeLevel>HighlyRestricted</a:AllowedHtmlCodeLevel><a:ApiSessionId>bepsggjny3wiwu45gpl4eu55</a:ApiSessionId><a:CustomerId>1</a:CustomerId><a:LearningObjectId>249</a:LearningObjectId><a:LearningObjectInstanceId>1247</a:LearningObjectInstanceId><a:Role>Guest</a:Role><a:UserId>2</a:UserId></Context></CreateInstanceRequest>|43ff2a46-ca3e-450e-8f35-4a30d0ab4953|2014-01-07T09:05:46.108Z

To create the hash, SHA-1 function is used. It is assumed that input string comes in UTF-8 format. The calculated SHA-1 sum is in hexadecimal format, equal to:

3530a66436ad457288cd38f4557e2813666bc3b1

Then, the sum is converted to Base64, which gives is the signature:

NTCmZDatRXKIzTj0VX4oE2Zrw7E=

We can compare it to the signature's value attached to the incoming request in 'Authorization' header. To ensure that the request has not expired or it has not been repeated, we can check the difference between the current time and the timestamp from the authorization header and compare it against chosen expiration time. In most scenarios, the request for creating the extension instance is followed by another one, used to create the content of the newly added extension instance:

POST http://www.myitslearningextension.com:3001/Services/InstanceService.svc HTTP/1.1
Content-Type: text/xml; charset=utf-8
VsDebuggerCausalityData: uIDPo7hXV5XJVFZJgCkTLC5Y9NwAAAAA99f5DOc9xEuaKNMgVcclmidW5RpUPwdCs9lECQp2GtcACQAA
SOAPAction: "http://tempuri.org/IInstanceService/AddContent"
Host: www.myitslearningextension.com:3001
Content-Length: 1025
Expect: 100-continue
Accept-Encoding: gzip, deflate
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"><s:Header><Authentication xmlns="http://www.itslearning.com/Authentication" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"><Signature>Zg9ZQPTNkYv8UYXrlFWFR+xeSFo=</Signature><Timestamp>2014-01-07T09:15:03.0473828Z</Timestamp></Authentication></s:Header><s:Body><AddContentRequest xmlns="http://tempuri.org/"><Content>&lt;PageContent&gt;&lt;ContentBlockSets&gt;&lt;/ContentBlockSets&gt;&lt;/PageContent&gt;</Content><Context xmlns:a="http://schemas.datacontract.org/2004/07/Itslearning.Platform.RestApi.Sdk.Common.Services" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"><a:AllowedHtmlCodeLevel>HighlyRestricted</a:AllowedHtmlCodeLevel><a:ApiSessionId>bepsggjny3wiwu45gpl4eu55</a:ApiSessionId><a:CustomerId>1</a:CustomerId><a:LearningObjectId>249</a:LearningObjectId><a:LearningObjectInstanceId>1247</a:LearningObjectInstanceId><a:Role>Guest</a:Role><a:UserId>2</a:UserId></Context><InstanceId>195</InstanceId></AddContentRequest></s:Body></s:Envelope>

This request should be also verified in the similar way. For more information, refer to InstanceService and Organisation API documentation.

Implementation examples

The itslearning SDK contains examples of .NET service contracts that can help with implementing the web services. Please refer to IInstanceService and IFileStreamService interfaces included in the SDK. There is also AuthenticationInspector class, that contains logic for signing requests and verifying signatures. It can be specified in web service's web.config file as a behavior element:

<system.serviceModel>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true"/>
<services>
<service behaviorConfiguration="FileStreamServiceBehavior" name="Itslearning.LearningTool.Pages.Web.Services.FileStreamService">
<endpoint address="" behaviorConfiguration="AsyncBehavior" binding="basicHttpBinding" contract="Itslearning.Platform.RestApi.Sdk.Common.Services.IFileStreamService" bindingConfiguration="FileStreamServiceBinding"/>
<endpoint address="" behaviorConfiguration="AsyncBehavior" binding="basicHttpBinding" contract="Itslearning.Platform.RestApi.Sdk.Common.Services.IFileStreamService" bindingConfiguration="FileStreamServiceBindingSecure"/>
</service>
<service behaviorConfiguration="AuthenticatedServiceBehavior" name="Itslearning.LearningTool.Pages.Web.Services.InstanceService">
<endpoint address="" binding="basicHttpBinding" contract="Itslearning.Platform.RestApi.Sdk.Common.Services.IInstanceService" bindingConfiguration="InstanceServiceBinding"/>
<endpoint address="" binding="basicHttpBinding" contract="Itslearning.Platform.RestApi.Sdk.Common.Services.IInstanceService" bindingConfiguration="InstanceServiceBindingSecure"/>
</service>
</services>
<bindings>
<basicHttpBinding>
<!-- Set max message size to 50 MB -->
<binding name="FileStreamServiceBinding"
transferMode="Streamed"
messageEncoding="Mtom"
maxReceivedMessageSize="52428800"
maxBufferSize="52428800"
closeTimeout="00:30:00"
openTimeout="00:30:00"
receiveTimeout="00:30:00"
sendTimeout="00:30:00">
</binding>
<binding name="FileStreamServiceBindingSecure"
transferMode="Streamed"
messageEncoding="Mtom"
maxReceivedMessageSize="52428800"
maxBufferSize="52428800"
closeTimeout="00:30:00"
openTimeout="00:30:00"
receiveTimeout="00:30:00"
sendTimeout="00:30:00">
<security mode="Transport"/>
</binding>
<binding name="InstanceServiceBinding"
maxReceivedMessageSize="52428800"
maxBufferSize="52428800"
closeTimeout="00:10:00"
openTimeout="00:10:00"
receiveTimeout="00:10:00"
sendTimeout="00:10:00">
</binding>
<binding name="InstanceServiceBindingSecure"
maxReceivedMessageSize="52428800"
maxBufferSize="52428800"
closeTimeout="00:10:00"
openTimeout="00:10:00"
receiveTimeout="00:10:00"
sendTimeout="00:10:00">
<security mode="Transport"/>
</binding>
</basicHttpBinding>
<webHttpBinding>
<binding name="largeRequestBindingConfig" maxReceivedMessageSize="1024000" >
<readerQuotas maxStringContentLength="1024000" />
</binding>
</webHttpBinding>
</bindings>
<extensions>
<behaviorExtensions>
<add name="restApiAuthenticationBehavior" type="Itslearning.Platform.RestApi.Sdk.Common.Services.AuthenticationBehaviorElement, Itslearning.Platform.RestApi.Sdk.Common"/>
</behaviorExtensions>
</extensions>
<behaviors>
<endpointBehaviors>
<behavior name="webHttp">
<webHttp/>
</behavior>
<behavior name="AsyncBehavior">
<dispatcherSynchronization asynchronousSendEnabled="true" />
</behavior>
</endpointBehaviors>
<serviceBehaviors>
<behavior name="AuthenticatedServiceBehavior">
<serviceMetadata httpGetEnabled="True" httpsGetEnabled="True"/>
<serviceDebug includeExceptionDetailInFaults="False"/>
<restApiAuthenticationBehavior SharedSecret="958h5645-7845-78gd-648j-v14746k743t4" TimeToLive="10"/>
</behavior>
<behavior name="FileStreamServiceBehavior">
<serviceMetadata httpGetEnabled="True" httpsGetEnabled="True"/>
<serviceDebug includeExceptionDetailInFaults="False"/>
<restApiAuthenticationBehavior SharedSecret="958h5645-7845-78gd-648j-v14746k743t4" TimeToLive="10" SignBody="False"/>
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>