panda_pal 5.0.0.beta.4 → 5.2.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +208 -90
  3. data/app/controllers/panda_pal/lti_controller.rb +0 -18
  4. data/app/controllers/panda_pal/lti_v1_p0_controller.rb +34 -0
  5. data/app/controllers/panda_pal/lti_v1_p3_controller.rb +98 -0
  6. data/app/lib/lti_xml/base_platform.rb +4 -4
  7. data/app/lib/panda_pal/launch_url_helpers.rb +69 -0
  8. data/app/lib/panda_pal/lti_jwt_validator.rb +88 -0
  9. data/app/lib/panda_pal/misc_helper.rb +13 -0
  10. data/app/models/panda_pal/organization.rb +21 -47
  11. data/app/models/panda_pal/organization_concerns/settings_validation.rb +127 -0
  12. data/app/models/panda_pal/organization_concerns/task_scheduling.rb +204 -0
  13. data/app/models/panda_pal/platform.rb +40 -0
  14. data/app/views/panda_pal/lti_v1_p3/login.html.erb +1 -0
  15. data/app/views/panda_pal/partials/_auto_submit_form.html.erb +9 -0
  16. data/config/dev_lti_key.key +27 -0
  17. data/config/routes.rb +12 -2
  18. data/db/migrate/20160412205931_create_panda_pal_organizations.rb +1 -1
  19. data/db/migrate/20160413135653_create_panda_pal_sessions.rb +1 -1
  20. data/db/migrate/20160425130344_add_panda_pal_organization_to_session.rb +1 -1
  21. data/db/migrate/20170106165533_add_salesforce_id_to_organizations.rb +1 -1
  22. data/db/migrate/20171205183457_encrypt_organization_settings.rb +1 -1
  23. data/db/migrate/20171205194657_remove_old_organization_settings.rb +8 -3
  24. data/lib/panda_pal.rb +28 -15
  25. data/lib/panda_pal/engine.rb +8 -39
  26. data/lib/panda_pal/helpers.rb +1 -0
  27. data/lib/panda_pal/helpers/controller_helper.rb +139 -44
  28. data/lib/panda_pal/helpers/route_helper.rb +8 -8
  29. data/lib/panda_pal/helpers/secure_headers.rb +79 -0
  30. data/lib/panda_pal/version.rb +1 -1
  31. data/panda_pal.gemspec +6 -2
  32. data/spec/dummy/config/application.rb +7 -1
  33. data/spec/dummy/config/environments/development.rb +0 -14
  34. data/spec/dummy/config/environments/production.rb +0 -11
  35. data/spec/models/panda_pal/organization/settings_validation_spec.rb +175 -0
  36. data/spec/models/panda_pal/organization/task_scheduling_spec.rb +144 -0
  37. data/spec/models/panda_pal/organization_spec.rb +0 -89
  38. data/spec/spec_helper.rb +4 -0
  39. metadata +66 -10
  40. data/spec/dummy/config/initializers/assets.rb +0 -11
@@ -1,6 +1,12 @@
1
1
  require File.expand_path('../boot', __FILE__)
2
2
 
3
- require 'rails/all'
3
+ require "active_model/railtie"
4
+ require "active_job/railtie"
5
+ require "active_record/railtie"
6
+ # require "active_storage/engine"
7
+ require "action_controller/railtie"
8
+ require "action_mailer/railtie"
9
+ require "action_view/railtie"
4
10
 
5
11
  Bundler.require(*Rails.groups)
6
12
  require "panda_pal"
@@ -22,20 +22,6 @@ Rails.application.configure do
22
22
  # Raise an error on page load if there are pending migrations.
23
23
  config.active_record.migration_error = :page_load
24
24
 
25
- # Debug mode disables concatenation and preprocessing of assets.
26
- # This option may cause significant delays in view rendering with a large
27
- # number of complex assets.
28
- config.assets.debug = true
29
-
30
- # Asset digests allow you to set far-future HTTP expiration dates on all assets,
31
- # yet still be able to expire them through the digest params.
32
- config.assets.digest = true
33
-
34
- # Adds additional error checking when serving assets at runtime.
35
- # Checks for improperly declared sprockets dependencies.
36
- # Raises helpful error messages.
37
- config.assets.raise_runtime_errors = true
38
-
39
25
  # Raises error for missing translations
40
26
  # config.action_view.raise_on_missing_translations = true
41
27
  end
@@ -24,17 +24,6 @@ Rails.application.configure do
24
24
  # Apache or NGINX already handles this.
25
25
  config.serve_static_files = ENV['RAILS_SERVE_STATIC_FILES'].present?
26
26
 
27
- # Compress JavaScripts and CSS.
28
- config.assets.js_compressor = :uglifier
29
- # config.assets.css_compressor = :sass
30
-
31
- # Do not fallback to assets pipeline if a precompiled asset is missed.
32
- config.assets.compile = false
33
-
34
- # Asset digests allow you to set far-future HTTP expiration dates on all assets,
35
- # yet still be able to expire them through the digest params.
36
- config.assets.digest = true
37
-
38
27
  # `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb
39
28
 
40
29
  # Specifies the header that your server uses for sending files.
@@ -0,0 +1,175 @@
1
+ require 'rails_helper'
2
+
3
+ module PandaPal
4
+ PandaPal::Organization
5
+ RSpec.describe OrganizationConcerns::SettingsValidation, type: :model do
6
+ let!(:org) { create :panda_pal_organization }
7
+
8
+ def set_test_settings_structure
9
+ PandaPal.lti_options = {
10
+ title: 'Test App',
11
+ settings_structure: structure,
12
+ }
13
+ end
14
+
15
+ RSpec.shared_examples "shared stuff" do
16
+ it 'does not perform any validations if settings is not defined' do
17
+ PandaPal.lti_options = {}
18
+ expect(org.valid?).to be_truthy
19
+ end
20
+
21
+ it 'does not perform any validations if options is not defined' do
22
+ PandaPal.lti_options = nil
23
+ expect(org.valid?).to be_truthy
24
+ end
25
+
26
+ it 'does perform validations if settings_structure is defined' do
27
+ set_test_settings_structure
28
+ org.valid?
29
+ expect(org.valid?).to be_falsey
30
+ end
31
+
32
+ it 'will fail if a required setting is not present' do
33
+ set_test_settings_structure
34
+ expect(org.valid?).to be_falsey
35
+ errors = org.errors.messages[:settings]
36
+ expect(errors[0]).to eq("Entry [:canvas] is required")
37
+ end
38
+
39
+ it 'will fail if a setting is supplied but data_type is wrong' do
40
+ set_test_settings_structure
41
+ org.settings = {canvas: "Dog", reports: {}}
42
+ expect(org.valid?).to be_falsey
43
+ errors = org.errors.messages[:settings]
44
+ expect(errors[0]).to eq("Expected [:canvas] to be a Hash. Was a String")
45
+ end
46
+
47
+ it 'will fail if a required subsetting is missing' do
48
+ set_test_settings_structure
49
+ org.settings = {canvas: {base_url: 'http://'}, reports: {}}
50
+ expect(org.valid?).to be_falsey
51
+ errors = org.errors.messages[:settings]
52
+ expect(errors[0]).to eq("Entry [:canvas][:api_token] is required")
53
+ end
54
+
55
+ it 'will fail if extra options are specified' do
56
+ set_test_settings_structure
57
+ org.settings = {canvas: {base_url: 'http://', api_token: 'TEST'}, reports: {}, unknown_option: "WHAT IS THIS?"}
58
+ expect(org.valid?).to be_falsey
59
+ errors = org.errors.messages[:settings]
60
+ expect(errors[0]).to eq("Did not expect [:] to contain [unknown_option]")
61
+ end
62
+
63
+ it 'will pass if all structure is maintained' do
64
+ set_test_settings_structure
65
+ org.settings = {canvas: {base_url: 'http://', api_token: 'TEST'}, reports: {submissions_report_time_length: 30.minutes, max_recheck_time: 10.hours}}
66
+ expect(org.valid?).to be_truthy
67
+ end
68
+ end
69
+
70
+ context 'new settings_structure' do
71
+ let!(:properties) do
72
+ {
73
+ canvas: {
74
+ required: true,
75
+ type: 'Hash',
76
+ properties: {
77
+ api_token: { type: 'String', required: true },
78
+ base_url: { type: 'String', required: true },
79
+ },
80
+ },
81
+ reports: {
82
+ required: true,
83
+ type: 'Hash',
84
+ allow_additional: true,
85
+ }
86
+ }
87
+ end
88
+
89
+ let!(:structure) do
90
+ {
91
+ properties: properties,
92
+ }
93
+ end
94
+
95
+ it_behaves_like("shared stuff")
96
+
97
+ describe ':allow_additional' do
98
+ it 'passes extra properties if allow_additional is a matching Hash' do
99
+ structure[:allow_additional] = { type: 'String' }
100
+ set_test_settings_structure
101
+ org.settings = {canvas: {base_url: 'http://', api_token: 'TEST'}, reports: {}, unknown_option: "WHAT IS THIS?"}
102
+ expect(org).to be_valid
103
+ end
104
+
105
+ it 'fails extra properties if allow_additional is an unmatching Hash' do
106
+ structure[:allow_additional] = { type: 'Hash' }
107
+ set_test_settings_structure
108
+ org.settings = {canvas: {base_url: 'http://', api_token: 'TEST'}, reports: {}, unknown_option: "WHAT IS THIS?"}
109
+ expect(org).not_to be_valid
110
+ end
111
+
112
+ it 'passes extra properties if allow_additional is a blank Hash' do
113
+ structure[:allow_additional] = { }
114
+ set_test_settings_structure
115
+ org.settings = {canvas: {base_url: 'http://', api_token: 'TEST'}, reports: {}, unknown_option: "WHAT IS THIS?"}
116
+ expect(org).to be_valid
117
+ end
118
+ end
119
+
120
+ describe ':validate' do
121
+ let!(:properties) do
122
+ {
123
+ blah: {
124
+ type: 'String',
125
+ validate: -> (v, *args) { v == 'blah' ? nil : '<path> failed validation' },
126
+ }
127
+ }
128
+ end
129
+
130
+ it 'supports a custom validation proc' do
131
+ set_test_settings_structure
132
+ org.settings = { blah: 'blah' }
133
+ expect(org).to be_valid
134
+ end
135
+
136
+ it 'replaces <path> with the human readable path' do
137
+ set_test_settings_structure
138
+ org.settings = { blah: '' }
139
+ expect(org.valid?).to be_falsey
140
+ errors = org.errors.messages[:settings]
141
+ expect(errors[0]).to eq("[:blah] failed validation")
142
+ end
143
+ end
144
+ end
145
+
146
+ context 'old settings_structure' do
147
+ let!(:structure) do
148
+ YAML.load("
149
+ canvas:
150
+ is_required: true
151
+ data_type: Hash
152
+ api_token:
153
+ is_required: true
154
+ data_type: String
155
+ base_url:
156
+ is_required: true
157
+ data_type: String
158
+ reports:
159
+ is_required: true
160
+ data_type: Hash
161
+ active_term_allowance:
162
+ submissions_report_time_length:
163
+ is_required: false
164
+ data_type: ActiveSupport::Duration
165
+ recheck_wait:
166
+ data_type: ActiveSupport::Duration
167
+ max_recheck_time:
168
+ is_required: false
169
+ ").deep_symbolize_keys
170
+ end
171
+
172
+ it_behaves_like("shared stuff")
173
+ end
174
+ end
175
+ end
@@ -0,0 +1,144 @@
1
+ require 'rails_helper'
2
+
3
+ module PandaPal
4
+ PandaPal::Organization
5
+
6
+ RSpec.describe OrganizationConcerns::TaskScheduling, type: :model do
7
+ let!(:organization) { create(:panda_pal_organization) }
8
+ let(:schedules) { {} }
9
+
10
+ before :each do
11
+ Organization.instance_variable_set(:@_schedule_descriptors, nil)
12
+ Organization.scheduled_task('0 0 0 * * *', :ident) { }
13
+
14
+ allow(Sidekiq).to receive(:remove_schedule)
15
+ allow(Sidekiq).to receive(:get_schedule) { schedules }
16
+ end
17
+
18
+ def descriptors
19
+ Organization.instance_variable_get(:@_schedule_descriptors)
20
+ end
21
+
22
+ def descriptor
23
+ descriptors[descriptors.keys[0]]
24
+ end
25
+
26
+ describe '.scheduled_task' do
27
+ it 'adds to the set of descriptors' do
28
+ expect(descriptors).to match(
29
+ 'ident' => {
30
+ :key=>"ident",
31
+ :schedule=>"0 0 0 * * *",
32
+ :worker=>Proc,
33
+ :queue=>"default",
34
+ }
35
+ )
36
+ end
37
+ end
38
+
39
+ describe '.sync_schedules' do
40
+ it 'adds new schedules' do
41
+ expect(Sidekiq).to receive(:set_schedule).with(/org:\w+-ident/, anything)
42
+ Organization.sync_schedules
43
+ end
44
+
45
+ it 'removes schedules for deleted Orgs' do
46
+ schedules['org:deleted_org-schedule'] = {}
47
+ expect(Sidekiq).to receive(:remove_schedule).with('org:deleted_org-schedule')
48
+ Organization.sync_schedules
49
+ end
50
+
51
+ it 'keeps schedules created from other sources' do
52
+ schedules['other-schedule'] = {}
53
+ expect(Sidekiq).not_to receive(:remove_schedule).with('other-schedule')
54
+ Organization.sync_schedules
55
+ end
56
+ end
57
+
58
+ describe '#generate_schedule' do
59
+ it 'generates the expected schedule' do
60
+ expect(organization.generate_schedule).to eq({
61
+ "org:#{organization.name}-ident" => {
62
+ "cron"=>"0 0 0 * * *",
63
+ "queue"=>"default",
64
+ "class"=>"PandaPal::OrganizationConcerns::TaskScheduling::ScheduledTaskExecutor",
65
+ "args"=>[organization.name, "ident"],
66
+ }
67
+ })
68
+ end
69
+ end
70
+
71
+ describe '#schedule_task_cron_time' do
72
+ before :each do
73
+ organization.settings = { timezone: 'America/Denver' }
74
+ end
75
+
76
+ it 'includes timezone if in settings' do
77
+ expect(organization.send(:schedule_task_cron_time, descriptor)).to eq '0 0 0 * * * America/Denver'
78
+ end
79
+
80
+ it 'does not re-append timezone if already present' do
81
+ descriptor[:schedule] = '1 1 1 * * * America/Chicago'
82
+ expect(organization.send(:schedule_task_cron_time, descriptor)).to eq '1 1 1 * * * America/Chicago'
83
+ end
84
+ end
85
+
86
+ describe '#sync_schedule' do
87
+ it 'adds new schedules' do
88
+ expect(Sidekiq).to receive(:set_schedule).with(/org:\w+-ident/, anything)
89
+ organization.sync_schedule
90
+ end
91
+
92
+ it 'removes old jobs' do
93
+ schedules["org:#{organization.name}-old_schedule"] = {}
94
+ expect(Sidekiq).to receive(:remove_schedule).with("org:#{organization.name}-old_schedule")
95
+ organization.sync_schedule
96
+ end
97
+ end
98
+
99
+ describe 'SettingsValidation' do
100
+ before :each do
101
+ organization.settings = {}
102
+ end
103
+
104
+ it 'passes a valid timezone' do
105
+ organization.settings[:timezone] = 'America/Denver'
106
+ expect(organization).to be_valid
107
+ end
108
+
109
+ it 'fails an invalid timezone' do
110
+ organization.settings[:timezone] = 'Timezone/Blorg'
111
+ expect(organization).not_to be_valid
112
+ end
113
+
114
+ it 'allows [:task_schedules] entry to be missing' do
115
+ expect(organization).to be_valid
116
+ end
117
+
118
+ it 'does not require [:task_schedules] sub-entries' do
119
+ organization.settings[:task_schedules] = {}
120
+ expect(organization).to be_valid
121
+ end
122
+
123
+ it 'allows task entry to be false' do
124
+ organization.settings[:task_schedules] = { ident: false }
125
+ expect(organization).to be_valid
126
+ end
127
+
128
+ it 'allows task entry to be a valid cron string' do
129
+ organization.settings[:task_schedules] = { ident: '0 0 0 * * * America/Denver' }
130
+ expect(organization).to be_valid
131
+ end
132
+
133
+ it 'does not allow task entry to be an invalid cron string' do
134
+ organization.settings[:task_schedules] = { ident: 'blort' }
135
+ expect(organization).not_to be_valid
136
+ end
137
+
138
+ it 'does not allow sub-entries for unknown tasks' do
139
+ organization.settings[:task_schedules] = { missing_ident: '0 0 0 * * * America/Denver' }
140
+ expect(organization).not_to be_valid
141
+ end
142
+ end
143
+ end
144
+ end
@@ -2,35 +2,6 @@ require 'rails_helper'
2
2
 
3
3
  module PandaPal
4
4
  RSpec.describe Organization, type: :model do
5
-
6
- def set_test_settings_structure
7
- PandaPal.lti_options = {
8
- title: 'Test App',
9
- settings_structure: YAML.load("
10
- canvas:
11
- is_required: true
12
- data_type: Hash
13
- api_token:
14
- is_required: true
15
- data_type: String
16
- base_url:
17
- is_required: true
18
- data_type: String
19
- reports:
20
- is_required: true
21
- data_type: Hash
22
- active_term_allowance:
23
- submissions_report_time_length:
24
- is_required: true
25
- data_type: ActiveSupport::Duration
26
- recheck_wait:
27
- data_type: ActiveSupport::Duration
28
- max_recheck_time:
29
- is_required: true
30
- ").deep_symbolize_keys
31
- }
32
- end
33
-
34
5
  it 'creates a schema upon creation' do
35
6
  expect(Apartment::Tenant).to receive(:create)
36
7
  create :panda_pal_organization
@@ -59,65 +30,5 @@ module PandaPal
59
30
  org2 = build :panda_pal_organization, salesforce_id: 'salesforce'
60
31
  expect(org2.valid?).to be_falsey
61
32
  end
62
-
63
- context 'settings validation' do
64
- let!(:org) { create :panda_pal_organization }
65
-
66
- it 'does not perform any validations if settings is not defined' do
67
- PandaPal.lti_options = {}
68
- expect_any_instance_of(PandaPal::Organization).not_to receive(:validate_level)
69
- expect(org.valid?).to be_truthy
70
- end
71
-
72
- it 'does not perform any validations if options is not defined' do
73
- PandaPal.lti_options = nil
74
- expect_any_instance_of(PandaPal::Organization).not_to receive(:validate_level)
75
- expect(org.valid?).to be_truthy
76
- end
77
-
78
- it 'does perform validations if settings_structure is defined' do
79
- set_test_settings_structure
80
- org.valid?
81
- expect(org.valid?).to be_falsey
82
- end
83
-
84
- it 'will fail if a required setting is not present' do
85
- set_test_settings_structure
86
- expect(org.valid?).to be_falsey
87
- errors = org.errors.messages[:settings]
88
- expect(errors[0]).to eq("PandaPal::Organization.settings requires key [:canvas]. It was not found.")
89
- end
90
-
91
- it 'will fail if a setting is supplied but data_type is wrong' do
92
- set_test_settings_structure
93
- org.settings = {canvas: "Dog"}
94
- expect(org.valid?).to be_falsey
95
- errors = org.errors.messages[:settings]
96
- expect(errors[0]).to eq("PandaPal::Organization.settings expected key [:canvas] to be Hash but it was instead String.")
97
- end
98
-
99
- it 'will fail if a required subsetting is missing' do
100
- set_test_settings_structure
101
- org.settings = {canvas: {base_url: 'http://'}, reports: {}}
102
- expect(org.valid?).to be_falsey
103
- errors = org.errors.messages[:settings]
104
- expect(errors[0]).to eq("PandaPal::Organization.settings requires key [:canvas][:api_token]. It was not found.")
105
- end
106
-
107
- it 'will fail if extra options are specified' do
108
- set_test_settings_structure
109
- org.settings = {canvas: {base_url: 'http://'}, reports: {}, unknown_option: "WHAT IS THIS?"}
110
- expect(org.valid?).to be_falsey
111
- errors = org.errors.messages[:settings]
112
- expect(errors[0]).to eq("PandaPal::Organization.settings had unexpected key: unknown_option. If settings have expanded please update your lti_options accordingly.")
113
- end
114
-
115
- it 'will pass if all structure is maintained' do
116
- set_test_settings_structure
117
- org.settings = {canvas: {base_url: 'http://', api_token: 'TEST'}, reports: {submissions_report_time_length: 30.minutes, max_recheck_time: 10.hours}}
118
- expect(org.valid?).to be_truthy
119
- end
120
-
121
- end
122
33
  end
123
34
  end