dce_lti 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (78) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +154 -0
  4. data/Rakefile +19 -0
  5. data/app/assets/javascripts/dce_lti/application.js +13 -0
  6. data/app/assets/stylesheets/dce_lti/application.css +15 -0
  7. data/app/concerns/dce_lti/session_helpers.rb +55 -0
  8. data/app/controllers/dce_lti/application_controller.rb +5 -0
  9. data/app/controllers/dce_lti/configs_controller.rb +26 -0
  10. data/app/controllers/dce_lti/sessions_controller.rb +20 -0
  11. data/app/helpers/dce_lti/application_helper.rb +4 -0
  12. data/app/models/dce_lti/nonce.rb +17 -0
  13. data/app/models/dce_lti/user.rb +16 -0
  14. data/app/services/dce_lti/timestamp_validator.rb +7 -0
  15. data/app/services/dce_lti/user_initializer.rb +22 -0
  16. data/app/views/dce_lti/sessions/invalid.html.erb +4 -0
  17. data/app/views/layouts/dce_lti/application.html.erb +14 -0
  18. data/config/routes.rb +9 -0
  19. data/db/migrate/20141003180140_create_dce_lti_users.rb +16 -0
  20. data/db/migrate/20141008172001_create_dce_lti_nonces.rb +10 -0
  21. data/lib/dce_lti/controller_methods.rb +18 -0
  22. data/lib/dce_lti/engine.rb +30 -0
  23. data/lib/dce_lti/version.rb +3 -0
  24. data/lib/dce_lti.rb +5 -0
  25. data/lib/tasks/dce_lti_tasks.rake +34 -0
  26. data/spec/controllers/dce_lti/configs_controller_spec.rb +71 -0
  27. data/spec/controllers/dce_lti/sessions_controller_spec.rb +201 -0
  28. data/spec/controllers/embedding_headers_are_correct_spec.rb +8 -0
  29. data/spec/controllers/posts_controller_spec.rb +22 -0
  30. data/spec/dummy/README.rdoc +28 -0
  31. data/spec/dummy/Rakefile +6 -0
  32. data/spec/dummy/app/assets/javascripts/application.js +13 -0
  33. data/spec/dummy/app/assets/stylesheets/application.css +15 -0
  34. data/spec/dummy/app/controllers/application_controller.rb +5 -0
  35. data/spec/dummy/app/controllers/posts_controller.rb +6 -0
  36. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  37. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  38. data/spec/dummy/bin/bundle +3 -0
  39. data/spec/dummy/bin/rails +4 -0
  40. data/spec/dummy/bin/rake +4 -0
  41. data/spec/dummy/config/application.rb +29 -0
  42. data/spec/dummy/config/boot.rb +5 -0
  43. data/spec/dummy/config/database.yml +19 -0
  44. data/spec/dummy/config/environment.rb +5 -0
  45. data/spec/dummy/config/environments/development.rb +37 -0
  46. data/spec/dummy/config/environments/production.rb +82 -0
  47. data/spec/dummy/config/environments/test.rb +39 -0
  48. data/spec/dummy/config/initializers/assets.rb +8 -0
  49. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  50. data/spec/dummy/config/initializers/cookies_serializer.rb +3 -0
  51. data/spec/dummy/config/initializers/dce_lti_config.rb +40 -0
  52. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  53. data/spec/dummy/config/initializers/inflections.rb +16 -0
  54. data/spec/dummy/config/initializers/mime_types.rb +4 -0
  55. data/spec/dummy/config/initializers/session_store.rb +3 -0
  56. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  57. data/spec/dummy/config/initializers/x_frame_options.rb +9 -0
  58. data/spec/dummy/config/locales/en.yml +23 -0
  59. data/spec/dummy/config/routes.rb +7 -0
  60. data/spec/dummy/config/secrets.yml +22 -0
  61. data/spec/dummy/config.ru +4 -0
  62. data/spec/dummy/db/schema.rb +40 -0
  63. data/spec/dummy/log/development.log +2875 -0
  64. data/spec/dummy/log/test.log +108620 -0
  65. data/spec/dummy/public/404.html +67 -0
  66. data/spec/dummy/public/422.html +67 -0
  67. data/spec/dummy/public/500.html +66 -0
  68. data/spec/dummy/public/favicon.ico +0 -0
  69. data/spec/factories.rb +7 -0
  70. data/spec/models/dce_lti/nonce_spec.rb +42 -0
  71. data/spec/models/dce_lti/user_spec.rb +71 -0
  72. data/spec/services/dce_lti/timestamp_validator_spec.rb +15 -0
  73. data/spec/services/dce_lti/user_initializer_spec.rb +58 -0
  74. data/spec/spec_helper.rb +25 -0
  75. data/spec/support/database_cleaner.rb +21 -0
  76. data/spec/support/dce_lti/configuration_helpers.rb +36 -0
  77. data/spec/support/factory_girl.rb +3 -0
  78. metadata +327 -0
@@ -0,0 +1,67 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>The page you were looking for doesn't exist (404)</title>
5
+ <meta name="viewport" content="width=device-width,initial-scale=1">
6
+ <style>
7
+ body {
8
+ background-color: #EFEFEF;
9
+ color: #2E2F30;
10
+ text-align: center;
11
+ font-family: arial, sans-serif;
12
+ margin: 0;
13
+ }
14
+
15
+ div.dialog {
16
+ width: 95%;
17
+ max-width: 33em;
18
+ margin: 4em auto 0;
19
+ }
20
+
21
+ div.dialog > div {
22
+ border: 1px solid #CCC;
23
+ border-right-color: #999;
24
+ border-left-color: #999;
25
+ border-bottom-color: #BBB;
26
+ border-top: #B00100 solid 4px;
27
+ border-top-left-radius: 9px;
28
+ border-top-right-radius: 9px;
29
+ background-color: white;
30
+ padding: 7px 12% 0;
31
+ box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
32
+ }
33
+
34
+ h1 {
35
+ font-size: 100%;
36
+ color: #730E15;
37
+ line-height: 1.5em;
38
+ }
39
+
40
+ div.dialog > p {
41
+ margin: 0 0 1em;
42
+ padding: 1em;
43
+ background-color: #F7F7F7;
44
+ border: 1px solid #CCC;
45
+ border-right-color: #999;
46
+ border-left-color: #999;
47
+ border-bottom-color: #999;
48
+ border-bottom-left-radius: 4px;
49
+ border-bottom-right-radius: 4px;
50
+ border-top-color: #DADADA;
51
+ color: #666;
52
+ box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
53
+ }
54
+ </style>
55
+ </head>
56
+
57
+ <body>
58
+ <!-- This file lives in public/404.html -->
59
+ <div class="dialog">
60
+ <div>
61
+ <h1>The page you were looking for doesn't exist.</h1>
62
+ <p>You may have mistyped the address or the page may have moved.</p>
63
+ </div>
64
+ <p>If you are the application owner check the logs for more information.</p>
65
+ </div>
66
+ </body>
67
+ </html>
@@ -0,0 +1,67 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>The change you wanted was rejected (422)</title>
5
+ <meta name="viewport" content="width=device-width,initial-scale=1">
6
+ <style>
7
+ body {
8
+ background-color: #EFEFEF;
9
+ color: #2E2F30;
10
+ text-align: center;
11
+ font-family: arial, sans-serif;
12
+ margin: 0;
13
+ }
14
+
15
+ div.dialog {
16
+ width: 95%;
17
+ max-width: 33em;
18
+ margin: 4em auto 0;
19
+ }
20
+
21
+ div.dialog > div {
22
+ border: 1px solid #CCC;
23
+ border-right-color: #999;
24
+ border-left-color: #999;
25
+ border-bottom-color: #BBB;
26
+ border-top: #B00100 solid 4px;
27
+ border-top-left-radius: 9px;
28
+ border-top-right-radius: 9px;
29
+ background-color: white;
30
+ padding: 7px 12% 0;
31
+ box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
32
+ }
33
+
34
+ h1 {
35
+ font-size: 100%;
36
+ color: #730E15;
37
+ line-height: 1.5em;
38
+ }
39
+
40
+ div.dialog > p {
41
+ margin: 0 0 1em;
42
+ padding: 1em;
43
+ background-color: #F7F7F7;
44
+ border: 1px solid #CCC;
45
+ border-right-color: #999;
46
+ border-left-color: #999;
47
+ border-bottom-color: #999;
48
+ border-bottom-left-radius: 4px;
49
+ border-bottom-right-radius: 4px;
50
+ border-top-color: #DADADA;
51
+ color: #666;
52
+ box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
53
+ }
54
+ </style>
55
+ </head>
56
+
57
+ <body>
58
+ <!-- This file lives in public/422.html -->
59
+ <div class="dialog">
60
+ <div>
61
+ <h1>The change you wanted was rejected.</h1>
62
+ <p>Maybe you tried to change something you didn't have access to.</p>
63
+ </div>
64
+ <p>If you are the application owner check the logs for more information.</p>
65
+ </div>
66
+ </body>
67
+ </html>
@@ -0,0 +1,66 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>We're sorry, but something went wrong (500)</title>
5
+ <meta name="viewport" content="width=device-width,initial-scale=1">
6
+ <style>
7
+ body {
8
+ background-color: #EFEFEF;
9
+ color: #2E2F30;
10
+ text-align: center;
11
+ font-family: arial, sans-serif;
12
+ margin: 0;
13
+ }
14
+
15
+ div.dialog {
16
+ width: 95%;
17
+ max-width: 33em;
18
+ margin: 4em auto 0;
19
+ }
20
+
21
+ div.dialog > div {
22
+ border: 1px solid #CCC;
23
+ border-right-color: #999;
24
+ border-left-color: #999;
25
+ border-bottom-color: #BBB;
26
+ border-top: #B00100 solid 4px;
27
+ border-top-left-radius: 9px;
28
+ border-top-right-radius: 9px;
29
+ background-color: white;
30
+ padding: 7px 12% 0;
31
+ box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
32
+ }
33
+
34
+ h1 {
35
+ font-size: 100%;
36
+ color: #730E15;
37
+ line-height: 1.5em;
38
+ }
39
+
40
+ div.dialog > p {
41
+ margin: 0 0 1em;
42
+ padding: 1em;
43
+ background-color: #F7F7F7;
44
+ border: 1px solid #CCC;
45
+ border-right-color: #999;
46
+ border-left-color: #999;
47
+ border-bottom-color: #999;
48
+ border-bottom-left-radius: 4px;
49
+ border-bottom-right-radius: 4px;
50
+ border-top-color: #DADADA;
51
+ color: #666;
52
+ box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
53
+ }
54
+ </style>
55
+ </head>
56
+
57
+ <body>
58
+ <!-- This file lives in public/500.html -->
59
+ <div class="dialog">
60
+ <div>
61
+ <h1>We're sorry, but something went wrong.</h1>
62
+ </div>
63
+ <p>If you are the application owner check the logs for more information.</p>
64
+ </div>
65
+ </body>
66
+ </html>
File without changes
data/spec/factories.rb ADDED
@@ -0,0 +1,7 @@
1
+ module DceLti
2
+ FactoryGirl.define do
3
+ factory :user, class: User do
4
+ sequence(:lti_user_id) { |n| "user_id-#{n}" }
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,42 @@
1
+ module DceLti
2
+ describe Nonce do
3
+ it { should have_db_index(:nonce).unique(:true) }
4
+
5
+ context '.valid?' do
6
+ it 'returns true when a value is unique' do
7
+ expect(described_class.valid?('100')).to be true
8
+ end
9
+
10
+ it 'returns false when a value already exists' do
11
+ described_class.create!(nonce: '100')
12
+
13
+ expect(described_class.valid?('100')).to be false
14
+ end
15
+ end
16
+
17
+ context 'logging' do
18
+ it 'logs about it when a nonce is invalid' do
19
+ allow(Rails.logger).to receive(:warn)
20
+ allow(described_class).to receive(:create!).and_raise
21
+
22
+ described_class.valid?('100')
23
+
24
+ expect(Rails.logger).to have_received(:warn).with(/Creating nonce failed: "100"/)
25
+ end
26
+ end
27
+
28
+ context '.clean' do
29
+ it 'deletes based on the time limit in seconds set in config' do
30
+ Time.freeze do
31
+ allow(described_class).to receive(:delete_all)
32
+
33
+ described_class.clean
34
+
35
+ expect(described_class).to have_received(:delete_all).with(
36
+ ['created_at < ?', Time.now - 6.hours]
37
+ )
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,71 @@
1
+ module DceLti
2
+ describe User do
3
+ context '#lti_user_id' do
4
+ it { should validate_presence_of(:lti_user_id) }
5
+ it { should validate_uniqueness_of(:lti_user_id) }
6
+ it { should ensure_length_of(:lti_user_id).is_at_most(255) }
7
+ end
8
+
9
+ context '#roles' do
10
+ it 'keeps an array of roles' do
11
+ roles = %w|foo bar|
12
+
13
+ user = build(:user, roles: roles)
14
+ user.save && user.reload
15
+
16
+ expect(user.roles).to match_array(roles)
17
+ end
18
+
19
+ it 'are case downcased before storing' do
20
+ roles = %w|foo bar BAZ|
21
+
22
+ user = build(:user, roles: roles)
23
+ user.save && user.reload
24
+
25
+ expect(user.roles).to match_array(['foo','bar', 'baz'])
26
+ end
27
+
28
+ end
29
+
30
+ context 'has_role?' do
31
+ it 'handles nil elegantly' do
32
+ user = described_class.new
33
+
34
+ expect(user).not_to have_role(nil)
35
+ end
36
+
37
+ it 'can be queried via predicate methods' do
38
+ user = create(:user, roles: ['foo', 'blee'])
39
+
40
+ expect(user).to have_role(:foo)
41
+ end
42
+
43
+ it 'downcases a role before querying' do
44
+ user = create(:user, roles: ['foo', 'BLEE'])
45
+
46
+ expect(user).to have_role('blee')
47
+ end
48
+
49
+ it 'does not care if a role is a string or a symbol' do
50
+ user = create(:user, roles: ['foo', 'BLEE'])
51
+
52
+ expect(user).to have_role('blee')
53
+ expect(user).to have_role(:blee)
54
+ end
55
+ end
56
+
57
+ it 'stores additional attributes from the consumer' do
58
+ user = described_class.new
59
+ %i|
60
+ lis_person_contact_email_primary
61
+ lis_person_name_family
62
+ lis_person_name_full
63
+ lis_person_name_given
64
+ lis_person_sourcedid
65
+ user_image
66
+ |.each do |attribute|
67
+ expect(user).to respond_to(attribute)
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,15 @@
1
+ module DceLti
2
+ describe TimestampValidator do
3
+ it 'returns true for a timestamp that is in range' do
4
+ timestamp = (Time.now - 1.hour).to_i
5
+
6
+ expect(described_class.valid?(timestamp)).to be true
7
+ end
8
+
9
+ it 'returns false for a timestamp that is not in range' do
10
+ timestamp = (Time.now - 6.hours).to_i
11
+
12
+ expect(described_class.valid?(timestamp)).to be false
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,58 @@
1
+ module DceLti
2
+ describe UserInitializer do
3
+ it 'finds or creates a user based on the tool provider user_id' do
4
+ user_double = double('user').as_null_object
5
+ fake_oauth_id = 'an_oauth_id'
6
+ allow(tool_provider_double).to receive(:user_id).
7
+ and_return(fake_oauth_id)
8
+ allow(User).to receive(:find_or_create_by).and_return(user_double)
9
+
10
+ described_class.find_from(tool_provider_double)
11
+
12
+ expect(User).to have_received(:find_or_create_by).
13
+ with(lti_user_id: fake_oauth_id)
14
+ expect(tool_provider_double).to have_received(:user_id)
15
+ end
16
+
17
+ it 'adds additional attributes to a user from the tool_provider' do
18
+ user = User.new
19
+ allow(user).to receive_messages(tool_provider_methods)
20
+ allow(User).to receive(:find_or_create_by).and_return(user)
21
+
22
+ described_class.find_from(tool_provider_double)
23
+
24
+ tool_provider_methods.each do |tool_provider_attribute|
25
+ send_to_tool_provider = tool_provider_attribute.to_s.gsub(/=\Z/,'').to_sym
26
+ expect(user).to have_received(tool_provider_attribute).with(
27
+ tool_provider_attributes[send_to_tool_provider]
28
+ )
29
+ end
30
+ end
31
+
32
+ def tool_provider_methods
33
+ tool_provider_attributes.keys.reject do |att|
34
+ att == :user_id
35
+ end.map{|att| "#{att}=".to_sym}
36
+ end
37
+
38
+ def tool_provider_attributes
39
+ {
40
+ user_id: 'an_oauth_id',
41
+ roles: [ 'instructor' ],
42
+ lis_person_contact_email_primary: 'instructor@example.com',
43
+ lis_person_name_family: 'Last Name',
44
+ lis_person_name_full: 'Last Name, First Name',
45
+ lis_person_name_given: 'First Name',
46
+ lis_person_sourcedid: 'sourcedid',
47
+ user_image: 'http://example.com/image.png'
48
+ }
49
+ end
50
+
51
+ def tool_provider_double
52
+ @tool_provider_double ||= double(
53
+ 'Tool Provider',
54
+ tool_provider_attributes
55
+ )
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,25 @@
1
+ ENV['RAILS_ENV'] = 'test'
2
+
3
+ require File.expand_path('../dummy/config/environment.rb', __FILE__)
4
+
5
+ require 'rspec/rails'
6
+ require 'shoulda/matchers'
7
+ require 'factory_girl_rails'
8
+ require 'pry-byebug'
9
+ require 'database_cleaner'
10
+
11
+ Rails.backtrace_cleaner.remove_silencers!
12
+
13
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
14
+
15
+ RSpec.configure do |config|
16
+ config.expect_with :rspec do |c|
17
+ c.syntax = :expect
18
+ end
19
+
20
+ # config.include Features, type: :feature
21
+ config.infer_base_class_for_anonymous_controllers = false
22
+ config.infer_spec_type_from_file_location!
23
+ config.order = 'random'
24
+ config.use_transactional_fixtures = false
25
+ end
@@ -0,0 +1,21 @@
1
+ RSpec.configure do |config|
2
+ config.before(:suite) do
3
+ DatabaseCleaner.clean_with(:deletion)
4
+ end
5
+
6
+ config.before(:each) do
7
+ DatabaseCleaner.strategy = :transaction
8
+ end
9
+
10
+ config.before(:each, :js => true) do
11
+ DatabaseCleaner.strategy = :deletion
12
+ end
13
+
14
+ config.before(:each) do
15
+ DatabaseCleaner.start
16
+ end
17
+
18
+ config.after(:each) do
19
+ DatabaseCleaner.clean
20
+ end
21
+ end
@@ -0,0 +1,36 @@
1
+ module DceLti
2
+ module ConfigurationHelpers
3
+ def with_overridden_lti_config_of(new_config)
4
+ original_config = get_original_config(new_config)
5
+
6
+ begin
7
+ new_config.keys.each do |config_att|
8
+ DceLti::Engine.config.send("#{config_att}=", new_config[config_att])
9
+ end
10
+ yield DceLti::Engine.config
11
+ ensure
12
+ new_config.keys.each do |config_att|
13
+ DceLti::Engine.config.send("#{config_att}=", original_config[config_att])
14
+ end
15
+ end
16
+ end
17
+
18
+ def get_original_config(new_config)
19
+ original_config = {}
20
+ new_config.keys.each do |config_att|
21
+ if ! DceLti::Engine.config.respond_to?(config_att)
22
+ DceLti::Engine.config.send("#{config_att}=", nil)
23
+ end
24
+ original_config[config_att] = DceLti::Engine.config.send(config_att.to_sym)
25
+ end
26
+ original_config
27
+ end
28
+
29
+ def lti_config
30
+ {
31
+ provider_title: 'An awesome title',
32
+ provider_description: 'A cool description',
33
+ }
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,3 @@
1
+ RSpec.configure do |config|
2
+ config.include FactoryGirl::Syntax::Methods
3
+ end