d2l-valence 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +7 -0
  2. data/.codeclimate.yml +22 -0
  3. data/.gitignore +17 -0
  4. data/.rubocop.yml +13 -0
  5. data/Gemfile +4 -0
  6. data/Gemfile.lock +71 -0
  7. data/LICENSE.txt +22 -0
  8. data/README.md +266 -0
  9. data/Rakefile +6 -0
  10. data/d2l-valence.gemspec +30 -0
  11. data/lib/d2l/valence.rb +15 -0
  12. data/lib/d2l/valence/app_context.rb +49 -0
  13. data/lib/d2l/valence/auth_tokens.rb +60 -0
  14. data/lib/d2l/valence/encrypt.rb +34 -0
  15. data/lib/d2l/valence/host.rb +43 -0
  16. data/lib/d2l/valence/request.rb +104 -0
  17. data/lib/d2l/valence/response.rb +66 -0
  18. data/lib/d2l/valence/timestamp_error.rb +42 -0
  19. data/lib/d2l/valence/user_context.rb +58 -0
  20. data/lib/d2l/valence/version.rb +5 -0
  21. data/spec/d2l/valence/app_context_spec.rb +14 -0
  22. data/spec/d2l/valence/auth_tokens_spec.rb +30 -0
  23. data/spec/d2l/valence/host_spec.rb +64 -0
  24. data/spec/d2l/valence/request/authenticated_uri_spec.rb +75 -0
  25. data/spec/d2l/valence/request/execute/invalid_time_stamp_spec.rb +40 -0
  26. data/spec/d2l/valence/request/execute/lti_links/create_spec.rb +66 -0
  27. data/spec/d2l/valence/request/execute/lti_links/delete_spec.rb +39 -0
  28. data/spec/d2l/valence/request/execute/lti_links/list_spec.rb +41 -0
  29. data/spec/d2l/valence/request/execute/lti_links/update_spec.rb +73 -0
  30. data/spec/d2l/valence/request/execute/lti_links/view_spec.rb +41 -0
  31. data/spec/d2l/valence/request/execute/version_spec.rb +41 -0
  32. data/spec/d2l/valence/request/execute/whoami_spec.rb +40 -0
  33. data/spec/d2l/valence/response/code_spec.rb +34 -0
  34. data/spec/d2l/valence/response/server_skew_spec.rb +33 -0
  35. data/spec/d2l/valence/timestamp_error_spec.rb +21 -0
  36. data/spec/d2l/valence/user_context_spec.rb +6 -0
  37. data/spec/fixtures/vcr_cassettes/request/execute/create_lti_link.yml +71 -0
  38. data/spec/fixtures/vcr_cassettes/request/execute/delete_lti_link.yml +48 -0
  39. data/spec/fixtures/vcr_cassettes/request/execute/get_a_lti_link.yml +66 -0
  40. data/spec/fixtures/vcr_cassettes/request/execute/get_lti_links.yml +70 -0
  41. data/spec/fixtures/vcr_cassettes/request/execute/get_version.yml +66 -0
  42. data/spec/fixtures/vcr_cassettes/request/execute/get_whoami.yml +61 -0
  43. data/spec/fixtures/vcr_cassettes/request/execute/invalid_timestamp.yml +112 -0
  44. data/spec/fixtures/vcr_cassettes/request/execute/put_lti_link.yml +139 -0
  45. data/spec/spec_helper.rb +20 -0
  46. data/spec/support/common_context.rb +37 -0
  47. metadata +228 -0
@@ -0,0 +1,5 @@
1
+ module D2L
2
+ module Valence
3
+ VERSION = '0.0.1'.freeze
4
+ end
5
+ end
@@ -0,0 +1,14 @@
1
+ require 'spec_helper'
2
+
3
+ describe D2L::Valence::AppContext, type: :service do
4
+ include_context :common_context
5
+
6
+ context '.auth_url' do
7
+ subject { described_class.new(brightspace_host: auth_host, app_id: app_id, app_key: app_key) }
8
+ let(:expected_url) { "https://partners.brightspace.com/d2l/auth/api/token?x_a=#{app_id}&x_b=#{auth_key}&x_target=#{CGI.escape(callback_uri.to_s)}" }
9
+
10
+ it('will generate an authentication URL') do
11
+ expect(subject.auth_url(callback_uri)).to eq expected_url
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,30 @@
1
+ require 'spec_helper'
2
+
3
+ describe D2L::Valence::AuthTokens, type: :service do
4
+ include_context :common_context
5
+
6
+ context '.generate' do
7
+ subject { described_class.new(request: request) }
8
+ let(:request) do
9
+ D2L::Valence::Request.new(
10
+ user_context: user_context,
11
+ http_method: 'GET',
12
+ route: '/d2l/api/lp/:version/users/whoami'
13
+ )
14
+ end
15
+
16
+ let(:signature) { "#{request.http_method}&#{CGI.unescape(request.path)}&#{subject.adjusted_timestamp}" }
17
+ let(:signature_by_app_key) { D2L::Valence::Encrypt.generate_from(app_key, signature) }
18
+ let(:signature_by_user_key) { D2L::Valence::Encrypt.generate_from(user_key, signature) }
19
+ let(:tokens) { subject.generate }
20
+
21
+ it 'will generate the right token values' do
22
+ expect(tokens[described_class::APP_ID_PARAM]).to eq app_id
23
+ expect(tokens[described_class::SIGNATURE_BY_APP_KEY_PARAM]).to eq signature_by_app_key
24
+ expect(tokens[described_class::USER_ID_PARAM]).to eq user_id
25
+ expect(tokens[described_class::SIGNATURE_BY_USER_KEY_PARAM]).to eq signature_by_user_key
26
+ expect(tokens[described_class::TIMESTAMP_PARAM]).to eq subject.adjusted_timestamp
27
+ end
28
+ end
29
+
30
+ end
@@ -0,0 +1,64 @@
1
+ require 'spec_helper'
2
+
3
+ describe D2L::Valence::Host do
4
+ context '.initialize' do
5
+ let(:host) { 'www.somehost.com' }
6
+
7
+ context 'for scheme string' do
8
+ subject { described_class.new(scheme: 'HTTPS', host: host) }
9
+
10
+ its(:scheme) { is_expected.to eq :https }
11
+ its(:host) { is_expected.to eq host }
12
+ its(:port) { is_expected.to be_nil}
13
+ end
14
+
15
+ context 'for scheme symbol' do
16
+ subject { described_class.new(scheme: :http, host: host) }
17
+
18
+ its(:scheme) { is_expected.to eq :http }
19
+ its(:host) { is_expected.to eq host }
20
+ its(:port) { is_expected.to be_nil}
21
+ end
22
+
23
+ context 'for unsupported scheme' do
24
+ it('will raise an exception') do
25
+ expect { described_class.new(scheme: :sftp, host: host) }.to raise_error RuntimeError, 'sftp is an unsupported scheme. Please use either HTTP or HTTPS'
26
+ end
27
+ end
28
+ end
29
+
30
+ context '.to_uri' do
31
+ let(:host) { 'www.somehost.com' }
32
+
33
+ context 'for HTTP' do
34
+ subject { described_class.new(scheme: :http, host: host, port: port) }
35
+
36
+ context 'with no port' do
37
+ let(:port) { nil }
38
+
39
+ its('to_uri.to_s') { is_expected.to eq 'http://www.somehost.com' }
40
+ end
41
+ context 'with specific port' do
42
+ let(:port) { 8080 }
43
+
44
+ its('to_uri.to_s') { is_expected.to eq 'http://www.somehost.com:8080' }
45
+ end
46
+ end
47
+
48
+ context 'for HTTPS' do
49
+ subject { described_class.new(scheme: :https, host: host, port: port) }
50
+
51
+ context 'with no port' do
52
+ let(:port) { nil }
53
+
54
+ its('to_uri.to_s') { is_expected.to eq 'https://www.somehost.com' }
55
+ end
56
+ context 'with specific port' do
57
+ let(:port) { 4040 }
58
+
59
+ its('to_uri.to_s') { is_expected.to eq 'https://www.somehost.com:4040' }
60
+ end
61
+ end
62
+ end
63
+
64
+ end
@@ -0,0 +1,75 @@
1
+ require 'spec_helper'
2
+
3
+ describe D2L::Valence::Request, type: :service do
4
+ include_context :common_context
5
+
6
+ subject do
7
+ described_class.new(
8
+ user_context: user_context,
9
+ http_method: http_method,
10
+ route: route,
11
+ route_params: route_params,
12
+ query_params: query_params
13
+ )
14
+ end
15
+
16
+ context '.authenticated_uri' do
17
+ context 'with no parameters' do
18
+ let(:http_method) { 'GET' }
19
+ let(:route) { '/d2l/api/lp/:version/users/whoami' }
20
+ let(:route_params) { {} }
21
+ let(:query_params) { {} }
22
+ let(:expected_path) { '/d2l/api/lp/1.0/users/whoami' }
23
+
24
+ its('authenticated_uri.path') { is_expected.to eq expected_path }
25
+ it('will include the authentication query parameters') do
26
+ auth_token_parameters.each { |t| expect(subject.authenticated_uri.query).to include "#{t}=" }
27
+ end
28
+ end
29
+
30
+ context 'with a specific API version' do
31
+ let(:api_version) { '1.1' }
32
+ let(:http_method) { 'GET' }
33
+ let(:route) { '/d2l/api/lp/:version/users/whoami' }
34
+ let(:route_params) { {} }
35
+ let(:query_params) { {} }
36
+ let(:expected_path) { "/d2l/api/lp/#{api_version}/users/whoami" }
37
+
38
+ its('authenticated_uri.path') { is_expected.to eq expected_path }
39
+ end
40
+
41
+ context 'with route parameters' do
42
+ let(:http_method) { 'GET' }
43
+ let(:route) { '/d2l/api/lp/:version/:org_unit_id/groupcategories/:groupCategoryId' }
44
+ let(:route_params) do
45
+ {
46
+ org_unit_id: 4,
47
+ groupCategoryId: 23,
48
+ }
49
+ end
50
+ let(:query_params) { {} }
51
+ let(:expected_path) { "/d2l/api/lp/#{api_version}/#{route_params[:org_unit_id]}/groupcategories/#{route_params[:groupCategoryId]}" }
52
+
53
+ its('authenticated_uri.path') { is_expected.to eq expected_path }
54
+ it('will include the authentication query parameters') do
55
+ auth_token_parameters.each { |t| expect(subject.authenticated_uri.query).to include "#{t}=" }
56
+ end
57
+ end
58
+
59
+ context 'with query parameters' do
60
+ let(:http_method) { 'GET' }
61
+ let(:route) { '/d2l/api/lp/:version/users/' }
62
+ let(:query_params) { {userName: 'student123'} }
63
+ let(:route_params) { {} }
64
+ let(:expected_path) { "/d2l/api/lp/1.0/users/" }
65
+
66
+ its('authenticated_uri.path') { is_expected.to eq expected_path }
67
+ it('will include the query params') do
68
+ query_params.each { |k,v| expect(subject.authenticated_uri.query).to include "#{k}=#{v}" }
69
+ end
70
+ it('will include the authentication query parameters') do
71
+ auth_token_parameters.each { |t| expect(subject.authenticated_uri.query).to include "#{t}=" }
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,40 @@
1
+ require 'spec_helper'
2
+
3
+ describe D2L::Valence::Request, type: :service do
4
+ include_context :common_context
5
+
6
+ subject do
7
+ described_class.new(
8
+ user_context: user_context,
9
+ http_method: http_method,
10
+ route: route,
11
+ route_params: route_params,
12
+ query_params: query_params
13
+ )
14
+ end
15
+
16
+ context '.execute' do
17
+ let(:user_id) { ENV['D2L_USER_ID'] }
18
+ let(:user_key) { ENV['D2L_USER_KEY'] }
19
+
20
+ context 'with the timestamp is invalid' do
21
+ let(:http_method) { 'GET' }
22
+ let(:route) { '/d2l/api/lp/:version/users/whoami' }
23
+ let(:route_params) { {} }
24
+ let(:query_params) { {} }
25
+ let(:api_version) { '1.15' }
26
+ let(:skewed_start_time) { Time.at(1491940559) }
27
+ # NB: The commented line below is the needed for the regeneration of VCR cassettes
28
+ # let(:skewed_start_time) { Time.at(Time.now.to_i - 20000) }
29
+
30
+ context 'on second try', vcr: {cassette_name: 'request/execute/invalid_timestamp'} do
31
+ it 'will succeed' do
32
+ Timecop.travel skewed_start_time do
33
+ expect(subject.execute.code).to eq :INVALID_TIMESTAMP
34
+ expect(subject.execute.code).to eq :HTTP_200
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,66 @@
1
+ require 'spec_helper'
2
+
3
+ describe D2L::Valence::Request, type: :service do
4
+ include_context :common_context
5
+
6
+ subject do
7
+ described_class.new(
8
+ user_context: user_context,
9
+ http_method: http_method,
10
+ route: route,
11
+ route_params: route_params,
12
+ query_params: query_params
13
+ )
14
+ end
15
+
16
+ context '.execute' do
17
+ let(:user_id) { ENV['D2L_USER_ID'] }
18
+ let(:user_key) { ENV['D2L_USER_KEY'] }
19
+
20
+ context 'for POST', vcr: {cassette_name: 'request/execute/create_lti_link'} do
21
+ let(:http_method) { 'POST' }
22
+ let(:route) { '/d2l/api/le/:version/lti/link/:orgUnitId' }
23
+ let(:route_params) { {orgUnitId: 8041} }
24
+ let(:query_params) do
25
+ {
26
+ Title: 'LTI Link',
27
+ Url: 'http://myapplication.com/tool/launch',
28
+ Description: 'Link for external tool',
29
+ Key: '2015141297208',
30
+ PlainSecret: 'a30be7c3550149b7a7daac3065f0e5e5',
31
+ IsVisible: false,
32
+ SignMessage: true,
33
+ SignWithTc: true,
34
+ SendTcInfo: true,
35
+ SendContextInfo: true,
36
+ SendUserId: true,
37
+ SendUserName: true,
38
+ SendUserEmail: true,
39
+ SendLinkTitle: true,
40
+ SendLinkDescription: true,
41
+ SendD2LUserName: true,
42
+ SendD2LOrgDefinedId: true,
43
+ SendD2LOrgRoleId: true,
44
+ UseToolProviderSecuritySettings: false,
45
+ CustomParameters: nil
46
+ }
47
+ end
48
+ let(:api_version) { '1.15' }
49
+ let(:response) { subject.execute }
50
+
51
+ before do
52
+ Timecop.freeze Time.at(1491960645)
53
+ end
54
+
55
+ after { Timecop.return }
56
+
57
+ it 'will return the version information' do
58
+ expect(response.to_hash['LtiLinkId']).to_not be_nil
59
+ query_params.each do |k, v|
60
+ expect("#{k}: #{response.to_hash[k.to_s]}").to eq "#{k}: #{v}" if not [:PlainSecret, :CustomParameters].include? k
61
+ end
62
+ expect(response.code).to eq :HTTP_200
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,39 @@
1
+ require 'spec_helper'
2
+
3
+ describe D2L::Valence::Request, type: :service do
4
+ include_context :common_context
5
+
6
+ subject do
7
+ described_class.new(
8
+ user_context: user_context,
9
+ http_method: http_method,
10
+ route: route,
11
+ route_params: route_params,
12
+ query_params: query_params
13
+ )
14
+ end
15
+
16
+ context '.execute' do
17
+ let(:user_id) { ENV['D2L_USER_ID'] }
18
+ let(:user_key) { ENV['D2L_USER_KEY'] }
19
+
20
+ context 'for DELETE an lti link', vcr: {cassette_name: 'request/execute/delete_lti_link'} do
21
+ let(:http_method) { 'DELETE' }
22
+ let(:route) { '/d2l/api/le/:version/lti/link/:ltiLinkId' }
23
+ let(:route_params) { {ltiLinkId: 144931} }
24
+ let(:query_params) { {} }
25
+ let(:api_version) { '1.15' }
26
+ let(:response) { subject.execute }
27
+
28
+ before do
29
+ Timecop.freeze Time.at(1491960880)
30
+ end
31
+
32
+ after { Timecop.return }
33
+
34
+ it 'will delete the LTI Link' do
35
+ expect(response.code).to eq :HTTP_200
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,41 @@
1
+ require 'spec_helper'
2
+
3
+ describe D2L::Valence::Request, type: :service do
4
+ include_context :common_context
5
+
6
+ subject do
7
+ described_class.new(
8
+ user_context: user_context,
9
+ http_method: http_method,
10
+ route: route,
11
+ route_params: route_params,
12
+ query_params: query_params
13
+ )
14
+ end
15
+
16
+ context '.execute' do
17
+ let(:user_id) { ENV['D2L_USER_ID'] }
18
+ let(:user_key) { ENV['D2L_USER_KEY'] }
19
+
20
+ context 'for GET all lti links', vcr: {cassette_name: 'request/execute/get_lti_links'} do
21
+ let(:http_method) { 'GET' }
22
+ let(:route) { '/d2l/api/le/:version/lti/link/:orgUnitId/' }
23
+ let(:route_params) { {orgUnitId: 8041} }
24
+ let(:query_params) { {} }
25
+ let(:api_version) { '1.15' }
26
+ let(:response) { subject.execute }
27
+
28
+ before do
29
+ Timecop.freeze Time.at(1491960964)
30
+ end
31
+
32
+ after { Timecop.return }
33
+
34
+ its(:execute) { is_expected.to be_a D2L::Valence::Response }
35
+ it 'will return all lti links for the associated unit' do
36
+ expect(response.code).to eq :HTTP_200
37
+ response.to_hash.each { |lti_record| expect(lti_record['LtiLinkId']).to_not be_nil }
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,73 @@
1
+ require 'spec_helper'
2
+
3
+ describe D2L::Valence::Request, type: :service do
4
+ include_context :common_context
5
+
6
+ subject do
7
+ described_class.new(
8
+ user_context: user_context,
9
+ http_method: http_method,
10
+ route: route,
11
+ route_params: route_params,
12
+ query_params: query_params
13
+ )
14
+ end
15
+
16
+ context '.execute' do
17
+ let(:user_id) { ENV['D2L_USER_ID'] }
18
+ let(:user_key) { ENV['D2L_USER_KEY'] }
19
+
20
+ context 'for PUT with an lti link', vcr: {cassette_name: 'request/execute/put_lti_link'} do
21
+ let(:incorrect_details) { query_params.dup.merge(Title: 'The wrong title') }
22
+ let!(:existing_lit_link) do
23
+ Timecop.freeze Time.at(1491961066) do
24
+ D2L::Valence::Request.new(
25
+ user_context: user_context,
26
+ http_method: 'POST',
27
+ route: '/d2l/api/le/:version/lti/link/:orgUnitId',
28
+ route_params: {orgUnitId: 8041},
29
+ query_params: incorrect_details
30
+ ).execute.to_hash
31
+ end
32
+ end
33
+ let(:http_method) { 'PUT' }
34
+ let(:route) { '/d2l/api/le/:version/lti/link/:ltiLinkId' }
35
+ let(:route_params) { {ltiLinkId: existing_lit_link['LtiLinkId']} }
36
+ let(:query_params) do
37
+ {
38
+ Title: 'LTI Link',
39
+ Url: 'http://myapplication.com/tool/launch',
40
+ Description: 'Link for external tool',
41
+ Key: '2015141297208',
42
+ PlainSecret: 'a30be7c3550149b7a7daac3065f0e5e5',
43
+ IsVisible: false,
44
+ SignMessage: true,
45
+ SignWithTc: true,
46
+ SendTcInfo: true,
47
+ SendContextInfo: true,
48
+ SendUserId: true,
49
+ SendUserName: true,
50
+ SendUserEmail: true,
51
+ SendLinkTitle: true,
52
+ SendLinkDescription: true,
53
+ SendD2LUserName: true,
54
+ SendD2LOrgDefinedId: true,
55
+ SendD2LOrgRoleId: true,
56
+ UseToolProviderSecuritySettings: true,
57
+ CustomParameters: nil
58
+ }
59
+ end
60
+ let(:api_version) { '1.15' }
61
+ let(:response) do
62
+ Timecop.freeze Time.at(1491961067) do
63
+ subject.execute
64
+ end
65
+ end
66
+
67
+ it 'will update the LTI Link' do
68
+ expect(response.code).to eq :HTTP_200
69
+ expect(response.to_hash['Title']).to eq query_params[:Title]
70
+ end
71
+ end
72
+ end
73
+ end