This gem is aimed at providing a Ruby client for Desire2Learn's Valence Learning Framework APIs primarily used for integration with D2L Brightspace
Add this line to your application's Gemfile:
gem 'd2l-valence'
And then execute:
$ bundle
Or install it yourself as:
$ gem install d2l-valence
The first step is to get an API Key generated by your D2L Brightspace Admin. They will require you to provide a
Trusted URL
. This is the URL in your application that processes the authorisation response from the D2L Brightspace
Server. If you do not have a hosted server for you application at the time of development then you can use the AP Test
Tool provided by D2L as the Trusted URL
: https://apitesttool.desire2learnvalence.com/index.php
Once generated the key will include an Application ID
and Application Key
which is used in the authentication request.
Methods that expect a Brightspace host argument are expecting an instance of the D2L::Valence::Host class, eg:
brightspace_host = D2L::Valence::Host.new(scheme: 'https', host: 'partners.brightspace.com')
To start the process of authentication you will need to generate an Authentication URL.
app_context = D2L::Valence::AppContext.new(
brightspace_host: brightspace_host,
app_id: 'l3LcL9Lvpyg-ViYNbK',
app_key: 'mz5HRx6ER6jLMqKRv6Fw',
api_version: '1.15' # optional defaults to 1.0
)
auth_url = app_context.auth_url('https://apitesttool.desire2learnvalence.com/index.php')
Once you have generated your authentication URL you can do a redirect to it in your application if you have your
application hosted. If you application is not hosted as yet and you're using the D2L API Test Tool then just paste the
URL into your browser and the API Test Tool will appear. From there you'll be able to get the User ID
and User Key
which will be used in all subsequent authenticated requests.
NB: You will need to be authenticated on your instance of D2L Brightspace in order for the above function as expected. The Valence API uses that account for all subsequent authenticated requests for authorisation so it's important that you are authenticated with the right account.
The D2L Valence API requires that all requests are signed with authentication details. This is based on your App ID
and App Key
along with the User ID
and User Key
that you harvest from the authentication response from your D2L
Server.
In order to make requests against the D2L Valence API you will need to create a D2L::Valence::UserContext
instance. This
will then be used for all of your subsequent requests against the various D2L Valence APIs. Following is an example of
the user context creation when processing the D2L Authentication response.
app_context = D2L::Valence::AppContext.new(
brightspace_host: brightspace_host,
app_id: 'l3LcL9Lvpyg-ViYNbK',
app_key: 'mz5HRx6ER6jLMqKRv6Fw',
api_version: '1.15' # optional defaults to 1.0
)
user_id = params['x_a'] # query parameters from the response
user_key = params['x_b']
user_context = D2L::Valence::UserContext.new(
app_context: app_context,
user_id: user_id,
user_key: user_key
)
Please note if you're using a application framework that supports sessions (e.g. Rails, Sinatra, etc) then it's probably
best store the User ID
and User Key
in the session to allow for multiple API requests.
When creating your request you will need to specify a route
. The format of the route
provided when doing a request
allows you to specify parameters that are replaced based on the route_params
hash that you provide in the request
creation. The only parameter that's pre-populated is the version
which is based on the api_version you supply when
creating your application context. It can, however be overridden when you supply your route_params
. Following are
some examples of route
and route_params
# NB: For the following example the api_version in the app_context has been set to 1.0
route = '/d2l/api/lp/:version/:orgUnitId/groupcategories/:groupCategoryId'
route_params = {orgUnitId: 1, groupCategoryId: 23} # => /d2l/api/lp/1.0/1/groupcategories/23
route = '/d2l/api/lp/:version/users/whoami'
route_params = {version: '1.15'} # => /d2l/api/lp/1.15/users/whoami
GET /d2l/api/lp/(version)/users/whoami
response = D2L::Valence::Request.new(
user_context: user_context,
http_method: 'GET',
route: '/d2l/api/lp/:version/users/whoami'
).execute
response.code # => :HTTP_200
response.to_hash # => will yield the following hash
{
"Identifier" => "1",
"FirstName" => "Jack",
"LastName" => "User",
"UniqueName" => "jack.user",
"ProfileIdentifier" => "OqW9594ZXT"
}
POST /d2l/api/le/(version)/lti/link/(orgUnitId)¶
response = D2L::Valence::Request.new(
user_context: user_context,
http_method: 'POST',
route: '/d2l/api/le/:version/lti/link/:orgUnitId',
route_params: { orgUnitId: 123 },
query_params: {
Title: 'LTI Link',
Url: 'http://myapplication.com/tool/launch',
Description: 'Link for external tool',
Key: '2015141297208',
PlainSecret: 'a30be7c3550149b7a7daac3065f0e5e5',
IsVisible: false,
SignMessage: true,
SignWithTc: true,
SendTcInfo: true,
SendContextInfo: true,
SendUserId: true,
SendUserName: true,
SendUserEmail: true,
SendLinkTitle: true,
SendLinkDescription: true,
SendD2LUserName: true,
SendD2LOrgDefinedId: true,
SendD2LOrgRoleId: true,
UseToolProviderSecuritySettings: true,
CustomParameters: nil
}
).execute
response.code # => :HTTP_200
response.to_hash # => full details of the created LTI Link with OrgUnitId and LtiLinkId included
PUT /d2l/api/le/(version)/lti/link/(ltiLinkId)
response = D2L::Valence::Request.new(
user_context: user_context,
http_method: 'PUT',
route: '/d2l/api/le/:version/lti/link/:ltiLinkId',
route_params: { ltiLinkId: '123' },
query_params: { Title: 'New LTI Link Title' }
).execute
response.code # => :HTTP_200
response.to_hash # => full details of the updated LTI Link with OrgUnitId and LtiLinkId included
DELETE /d2l/api/le/(version)/lti/link/(ltiLinkId)
response = D2L::Valence::Request.new(
user_context: user_context,
http_method: 'DELETE',
route: '/d2l/api/le/:version/lti/link/:ltiLinkId',
route_params: { ltiLinkId: '123' }
).execute
response.code # => :HTTP_200
response.to_hash # => {}
Other than the normal HTTP response codes for failures there are a number of D2L Valence API specific response codes that will appear with the right conditions.
This response code identifies that there is enough of a difference between your local server time and the D2L Server time to be a problem. For developers this is hard to have changed so we provided a mechanism where by the failure can be rectified at the application level. In the design of the gem there is detection of time skew between the client and server. This is compensated for automatically. Should your request get this failure then all you need do is execute your request a second time and the gem will take the skew into consideration when creating the necessary authentication tokens. Following is an example of handling the response:
request = D2L::Valence::Request.new(
user_context: user_context,
http_method: 'GET',
route: '/d2l/api/lp/:version/users/whoami'
)
response = request.execute
response = request.execute if response.code == :INVALID_TIMESTAMP
response.code # => :HTTP_200
This response code occurs, as it suggests when you have provided an invalid set of authentication tokens. This could be a combination of incorrect App ID/Key and/or User ID/Key.
- Fork it ( https://github.com/michael-harrison/d2l-valence/fork )
- Create your feature branch (
git checkout -b my-new-feature
) - Commit your changes (
git commit -am 'Add some feature'
) - Push to the branch (
git push origin my-new-feature
) - Create a new Pull Request
Given the use of timestamps in the the URL it does mean that any tests that have recorded HTTP traffic via VCR need to use Timecop. In some cases the calls will need to wrapped with a Timecop block:
Timecop.freeze Time.at(1491780043) do
# Your HTTP Calls
end
But for the majority of the tests it just a matter of adding a before
and after
:
before { Timecop.freeze Time.at(1491547536) }
after { Timecop.return }
In order to do PRs with passing tests you'll need to use your own temporary App ID, App Key, User ID and User Key as I've done with these tests. For convenience the tests uses environment variables:
let(:app_id) { ENV['D2L_API_ID'] }
let(:app_key) { ENV['D2L_API_KEY'] }
let(:user_id) { ENV['D2L_USER_ID'] }
let(:user_key) { ENV['D2L_USER_KEY'] }
It is a merry dance but the only reliable way to have real testing against a real API.