panda_pal 4.0.0 → 4.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d06b29a94018b002f596dd6182ae6e99b2095d77
4
- data.tar.gz: 333749f144fa69a081204fc36664f1250d2667b0
3
+ metadata.gz: 3210543886deb93ce6dcd14691b3dd2d3e55134a
4
+ data.tar.gz: cb0fda8a8d40dca28a56e33923c705c9c3907ea2
5
5
  SHA512:
6
- metadata.gz: 31d73e9e8d25d4d5ba6930e7582ddb83a41c43c7c088c2bd283cfa26e9f87ae03f426e940e2502b38ba11debc06c8e99715c4229b8fd3b11861e7f10d332cd3d
7
- data.tar.gz: 5ffd1dcf5a23a191b3d3ad8acfaf38d0bfbad58ca172c610559f49b8281ec047381eea8500d237d34695b098c6b15261a078b990111581dcba6595dd8d4b2029
6
+ metadata.gz: 940ce47d4589a481f7ade6b68b45fe3a76d4be36ea189c064b2a5beb1a83cf0bf011aab85e7706a64047d416533833b905c847289dce9524b25b7815b74bb451
7
+ data.tar.gz: 638b6c937c9e18d6ac74297171a7a0a55747e705a6b98243acdad4e268bb409147f9a9630365b957c4301b2d04a007a85f6d425f1d3a688406bbeefcfee1bd58
data/README.md ADDED
@@ -0,0 +1,282 @@
1
+ # PandaPal
2
+
3
+ ## LTI Configuration
4
+ A standard Canvas LTI may have a configuration similar to the one below
5
+
6
+ ```ruby
7
+ # config/initializers/lti.rb
8
+ PandaPal.lti_options = { title: 'Teacher Reports' }
9
+ PandaPal.lti_properties = {oauth_compliant: 'true'}
10
+ # Environments reflect those used by Canvas
11
+ PandaPal.lti_environments = { domain: ENV['PROD_DOMAIN'], beta_domain: ENV['BETA_DOMAIN'], test_domain: ENV['TEST_DOMAIN'] }
12
+ PandaPal.lti_custom_params = { custom_canvas_role: '$Canvas.membership.roles' }
13
+ # :user_navigation, :course_navigation for user context and course context endpoints respectively
14
+ PandaPal::stage_navigation(:account_navigation, { enabled: true, text: 'Teacher Reports', visibility: 'admins' })
15
+ ```
16
+
17
+ Configuration data for an installation can be set by creating a `PandaPal::Organization` record. Due to the nature of the data segregation, once created, the name of the organization should not be changed (and will raise a validation error).
18
+
19
+ ### Launch URL property
20
+ LTI Spec: `The launch_url contains the URL to which the LTI Launch is to be sent. The secure_launch_url is the URL to use if secure http is required. One of either the launch_url or the secure_launch_url must be specified.`
21
+
22
+ Bridge is now validating that this launch URL exists and requires it per the LTI spec. As of PandaPal 3.1.0, you now have 6 options to use this.
23
+ Use one of these 6 options in `PandaPal.lti_options` hash.
24
+ 1. Use option `secure_launch_url: 'http://domain.com/path'` when a full static secure domain is needed.
25
+ 2. Use option `secure_launch_path: '/path'` when you need the secure options and want the host to be dynamic and just need to provide a path.
26
+ 3. Use option `launch_url: 'http://domain.com/path'` when a full static domain is needed.
27
+ 4. Use option `launch_path: '/path'` when you don't need the secure options and want the host to be dynamic and just need to provide a path.
28
+ 5. Leave this property off, and you will get the dynamic host with the root path ('http://appdomain.com/') by default.
29
+ 6. If you really do not want this property use the option `launch_url: false` for it to be left off.
30
+
31
+ # Organization Attributes
32
+ id: Primary Key
33
+ name: Name of the organization. Used to on requests to select the tenant
34
+ key: Key field from CanvasLMS
35
+ secret: Secret field from CanvasLMS
36
+ canvas_account_id: ID of the corresponding Canvas account.
37
+ settings: Hash of settings for this Organization
38
+ salesforce_id: ID of this organization in Salesforce
39
+
40
+ XML for an installation can be generated by visiting /lti/config in your application.
41
+
42
+ ### Routing
43
+
44
+ The following routes should be added to the routes.rb file of the implementing LTI. (substituting `account_navigation` with the other staged navigation routes, if necessary)
45
+
46
+ ```ruby
47
+ # config/routes.rb
48
+ mount PandaPal::Engine, at: '/lti'
49
+ lti_nav account_navigation: 'accounts#launch'
50
+ root to: 'panda_pal/lti#launch'
51
+ ```
52
+
53
+ ## Implementating data segregation
54
+ This engine uses Apartment to keep data segregated between installations of the implementing LTI tool.
55
+ By default, it does this by inspecting the path of the request, and matching URLs containing `orgs` or `organizations`,
56
+ followed by the numeric ID of the organization (path such as `/organizations/123/accounts`).
57
+ As such, all URLs should be built on top of a routing scope, such as the example below.
58
+
59
+ ```ruby
60
+ scope '/organizations/:organization_id' do
61
+ resources :accounts, only: :index do
62
+ collection do
63
+ get 'teachers' => 'accounts#teachers'
64
+ end
65
+ end
66
+ end
67
+ ```
68
+
69
+ This can be overriden by creating an initializer, and adding a custom elevator
70
+
71
+ ```ruby
72
+ Rails.application.config.middleware.delete 'Apartment::Elevators::Generic'
73
+ Rails.application.config.middleware.use 'Apartment::Elevators::Generic', lambda { |request|
74
+ if match = request.path.match(/\/(?:orgs|organizations)\/(\d+)/)
75
+ PandaPal::Organization.find_by(id: match[1]).try(:name)
76
+ end
77
+ }
78
+ ```
79
+
80
+ It is also possible to switch tenants by implementing `around_action :switch_tenant` in the controller.
81
+ However, it should be noted that calling `switch_tenant` must be used in an `around_action` hook (or given a block), and is only intended to be used for LTI launches, and not subsequent requests.
82
+
83
+ ### Rake tasks and jobs
84
+ # Delayed Job Support has been removed. This allows each project to assess it's need to handle background jobs.
85
+ The Apartment Gem makes it so background jobs need to be run within the current tenant. If using sidekiq, there is a gem that
86
+ does this automatically called apartment-sidkiq. If Delayed Jobs, see how it's done in version 1 of PandaPal.
87
+
88
+ For rake tasks, the current tenant will be set to `public`, and thus special handling must be taken when running the task or queueing jobs as part of it.
89
+
90
+ One potential solution is to create a method with switches between tenants, and invoke the job for each individual organization as necessary.
91
+
92
+ ```ruby
93
+ # config/initializers/global_methods.rb
94
+ def switch_tenant(tenant, &block)
95
+ if block_given?
96
+ Apartment::Tenant.switch(tenant, &block)
97
+ else
98
+ Apartment::Tenant.switch! tenant
99
+ end
100
+ end
101
+ ```
102
+
103
+ ```ruby
104
+ # lib/tasks/lti_tasks.rake
105
+ namespace :lti_tasks do
106
+ desc 'some rake task'
107
+ task some_rake_task: :environment do
108
+ PandaPal::Organization.all.each do |org|
109
+ switch_tenant(org.name) do
110
+ SomeJob.perform_later
111
+ end
112
+ end
113
+ end
114
+ end
115
+ ```
116
+
117
+ ## Controller helper methods
118
+ Controllers will automatically have access to a number of methods that can be helpful to implement. A list with descriptions is provided below.
119
+
120
+ * `validate_launch!` - should be used in any tool launch points, and will verify that the launch request received from Canvas is legitimate based on the saved key and secret.
121
+ * `current_session` - PandaPal provides support to use database persisted sessions, rather than the standard rails variety, as these play better when the tool is launched in an `iframe`. The `session_key` attribute from the returned object needs to be served to the client at some point during the launch and during subsequent requests. The benefit from this is that sessions will exist entirely in the frame used to launch the tool, and will not be visible should the user be accessing the tool in other contexts simultaneously. See section below for more information about this method.
122
+ * `current_session_data` - Shortcut method to access the `data` hash given by the above method.
123
+ * `current_organization` - Used to return the organization related to the currently selected tenant. See section below for more information about this method.
124
+ * `session_changed?` - returns true if the session given by `current_session` has been modified during the request.
125
+ * `save_session` - Saves the session given by `current_session` to the database.
126
+
127
+ ## Sessions and `current_session`
128
+ A session_key returned by `current_session` is essentially an access token, and while we can't prevent users from sharing if they're so inclined, it should still be kept hidden as much as possible (namely by not including it as a URL param at any time).
129
+ A good way to pass the `session_key` if using ajax or something similar, is to define it as a header in `$.ajaxSetup`. This is particularly useful if a javascript framework such as React is being utilized.
130
+
131
+ PandaPal provides a number of ways to find a session, shown in the example below
132
+
133
+ ```ruby
134
+ def session_key
135
+ params[:session_key] || session_key_header || flash[:session_key] || session[:session_key]
136
+ end
137
+
138
+ def session_key_header
139
+ if match = request.headers['Authorization'].try(:match, /token=(.+)/)
140
+ match[1]
141
+ end
142
+ end
143
+ ```
144
+
145
+ ### Persisting the session
146
+ In order to reduce the number of sessions saved to the database, a session should only be saved if data has been added to it. A good way to accomplish this is to add `append_after_action :save_session, if: proc { session_changed? }` to the application_controller.rb
147
+
148
+ ### Session cleanup
149
+ Over time, the sessions table will become bloated with sessions that are no longer active.
150
+ As such, `rake panda_pal:clean_sessions` should be run periodically to clean up sessions that haven't been updated in over a week.
151
+
152
+ ## Organizations and `current_organization`
153
+ Similar to `current_session`, `current_organization` can be returned with a number of methods, shown below
154
+ ```ruby
155
+ def organization_key
156
+ params[:oauth_consumer_key] || params[:organization_id] || current_session_data[:organization_key] || session[:organization_key]
157
+ end
158
+ ```
159
+
160
+ ## Security
161
+
162
+ ### Tool Launches
163
+ Any time a tool launch request is received, we should **always** validate that it originated from within Canvas and that it wasn't spoofed.
164
+ The easiest way to do this is to call `validate_launch!` in any controller action that is expecting to receive launch requests.
165
+
166
+ ### Validate active session is present
167
+ It is easier to implement this if a purely javascript frontend is used (such as React).
168
+ Any controller actions that are not designated as launch points, or externally available API endpoints (such as in the case where an endpoint should be exposed outside the context of a tool launch, and has its own authentication mechanisms in place) should implement a line similar to the one below in `application_controller.rb`
169
+
170
+ ```ruby
171
+ # app/controllers/application_controller.rb
172
+ prepend_before_action :forbid_access_if_lacking_session
173
+ ```
174
+
175
+ This will render an unauthorized message any time a request is received and a valid session is not present.
176
+ It can be overridden on a action-by-action basis by using `skip_before_action`.
177
+
178
+ ```ruby
179
+ skip_before_action :forbid_access_if_lacking_session, only: :launch
180
+ ```
181
+
182
+ ### Upgrading to version 3
183
+
184
+ Before upgrading save existing settings somewhere safe in case you need to
185
+ restore them for whatever reason.
186
+
187
+ panda_pal v3 introduces an encrypted settings hash. This should provide more security in the case where a
188
+ customer may have gained access to Organization information in the database. Settings cannot be decrypted
189
+ without knowing the secret decryption key. For panda_pal, we are relying on the secret_key_base to be set
190
+ (typically in config/secrets.yml), if that is not available we are falling back to directly use
191
+ ENV['SECRET_KEY_BASE']. For production environments, config/secrets.yml should not have a plain-text secret,
192
+ it should be referencing an ENV variable. Make sure your secret is not plain-text committed to a repository!
193
+
194
+ The secret key is used to encrypt / decrypt the settings hash as necessary.
195
+
196
+ Before upgrading to version 3, you should confirm that the secret key is set to a consistent value (if the
197
+ value is lost your settings will be hosed).
198
+
199
+ You should also rollback any local encryption you have done on the settings hash. The settings hash
200
+ should just be plainly visible when you upgrade to V3. Otherwise the panda_pal migrations will
201
+ probably fail.
202
+
203
+ Once you have upgraded your gem, you will need to run migrations. Before doing that, I would store off your
204
+ unencrypted settings (just in case).
205
+
206
+ `rake db:migrate`
207
+
208
+ If all goes well, you should be set! Log into console, and verify that you can still access settings:
209
+
210
+ `PandaPal::Organization.first.settings`
211
+
212
+ should show unencrypted settings in your console.
213
+
214
+ If anything goes wrong, you should be able to rollback the change:
215
+
216
+ `rake db:rollback STEP=2`
217
+
218
+ If you need to give up on the change, just make sure to change your gem version for panda_pal to be < 3.
219
+
220
+ ### Validating settings in your LTI.
221
+
222
+ You can specify a structure that you would like to have enforced for your settings.
223
+
224
+ In your panda_pal initializer (i.e. config/initializers/panda_pal.rb or config/initializers/lti.rb)
225
+
226
+ You can specify options that can include a structure for your settings. If specified, PandaPal will
227
+ enforce this structure on any new / updated organizations.
228
+
229
+ Here is an example options specification:
230
+
231
+ ```
232
+ PandaPal.lti_options = {
233
+ title: 'LBS Gradebook',
234
+ settings_structure: YAML.load("
235
+ canvas:
236
+ is_required: true
237
+ data_type: Hash
238
+ api_token:
239
+ is_required: true
240
+ data_type: String
241
+ base_url:
242
+ is_required: true
243
+ data_type: String
244
+ reports:
245
+ is_required: true
246
+ data_type: Hash
247
+ active_term_allowance:
248
+ submissions_report_time_length:
249
+ is_required: true
250
+ data_type: ActiveSupport::Duration
251
+ recheck_wait:
252
+ data_type: ActiveSupport::Duration
253
+ max_recheck_time:
254
+ is_required: true
255
+ ").deep_symbolize_keys
256
+ }
257
+ ```
258
+ (This loads the structure in from YAML, but you can specify a hash directly if you prefer.)
259
+
260
+ Each data attribute can have two children attributes to describe desired structure:
261
+
262
+ is_required: If specified, and specified as true, the parent attribute is required in the settings hash.
263
+
264
+ data_type: If specified, and a settings hash contains this attribute, attribute data type will be compared
265
+ to the specified data type. If you are not sure how to derive your class data type, you can
266
+ use `class` to determine data type. For example: 30.minutes.class.to_s => "ActiveSupport::Duration"
267
+
268
+ ## Bridge vs Canvas
269
+ As of 3.2.0, the LTI XML config can have subtle differences based on whether your platform is bridge, or canvas.
270
+ This is determined by `PandaPal.lti_options[:platform]`.
271
+ Set this to `platform: 'canvas.instructure.com'` (default)
272
+ OR `platform: 'bridgeapp.com'`
273
+
274
+ ### Safari Support
275
+
276
+ Safari is weird, and you'll potentially run into issues getting `POST` requests to properly validate CSRF if you don't do the following:
277
+
278
+ - Make sure you have both a `launch` controller and your normal controllers. The `launch` controller should call `before_action :validate_launch!` and then redirect
279
+ to your other controller.
280
+ - Make sure your other controller calls `before_action :forbid_access_if_lacking_session`
281
+
282
+ This will allow `PandaPal` to apply an iframe cookie fix that will allow CSRF validation to work.
@@ -1,3 +1,3 @@
1
1
  module PandaPal
2
- VERSION = "4.0.0"
2
+ VERSION = "4.0.1"
3
3
  end
data/panda_pal.gemspec CHANGED
@@ -12,7 +12,7 @@ Gem::Specification.new do |s|
12
12
  s.summary = "LTI mountable engine"
13
13
  s.license = "MIT"
14
14
 
15
- s.files = Dir["{app,config,db,lib}/**/*", "MIT-LICENSE", "Rakefile", "README.rdoc", "panda_pal.gemspec"]
15
+ s.files = Dir["{app,config,db,lib}/**/*", "MIT-LICENSE", "Rakefile", "README.md", "panda_pal.gemspec"]
16
16
  s.test_files = Dir["spec/**/*"]
17
17
 
18
18
  s.add_dependency "rails", ">= 5.1.0"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: panda_pal
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.0.0
4
+ version: 4.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Instructure ProServe
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-10-24 00:00:00.000000000 Z
11
+ date: 2019-01-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -150,6 +150,7 @@ extensions: []
150
150
  extra_rdoc_files: []
151
151
  files:
152
152
  - MIT-LICENSE
153
+ - README.md
153
154
  - Rakefile
154
155
  - app/assets/javascripts/panda_pal/application.js
155
156
  - app/assets/javascripts/panda_pal/lti.js
@@ -257,48 +258,48 @@ signing_key:
257
258
  specification_version: 4
258
259
  summary: LTI mountable engine
259
260
  test_files:
261
+ - spec/spec_helper.rb
262
+ - spec/dummy/app/controllers/application_controller.rb
263
+ - spec/dummy/app/views/layouts/application.html.erb
260
264
  - spec/dummy/app/assets/javascripts/application.js
261
265
  - spec/dummy/app/assets/stylesheets/application.css
262
- - spec/dummy/app/controllers/application_controller.rb
263
266
  - spec/dummy/app/helpers/application_helper.rb
264
- - spec/dummy/app/views/layouts/application.html.erb
265
- - spec/dummy/bin/bundle
266
- - spec/dummy/bin/rails
267
267
  - spec/dummy/bin/rake
268
268
  - spec/dummy/bin/setup
269
- - spec/dummy/config/application.rb
270
- - spec/dummy/config/boot.rb
271
- - spec/dummy/config/database.yml
272
- - spec/dummy/config/environment.rb
273
- - spec/dummy/config/environments/development.rb
269
+ - spec/dummy/bin/bundle
270
+ - spec/dummy/bin/rails
271
+ - spec/dummy/config/secrets.yml
272
+ - spec/dummy/config/routes.rb
273
+ - spec/dummy/config/locales/en.yml
274
274
  - spec/dummy/config/environments/production.rb
275
+ - spec/dummy/config/environments/development.rb
275
276
  - spec/dummy/config/environments/test.rb
276
- - spec/dummy/config/initializers/assets.rb
277
+ - spec/dummy/config/environment.rb
278
+ - spec/dummy/config/application.rb
279
+ - spec/dummy/config/database.yml
280
+ - spec/dummy/config/boot.rb
277
281
  - spec/dummy/config/initializers/backtrace_silencers.rb
278
- - spec/dummy/config/initializers/cookies_serializer.rb
279
- - spec/dummy/config/initializers/filter_parameter_logging.rb
280
- - spec/dummy/config/initializers/inflections.rb
281
282
  - spec/dummy/config/initializers/mime_types.rb
283
+ - spec/dummy/config/initializers/filter_parameter_logging.rb
282
284
  - spec/dummy/config/initializers/session_store.rb
283
285
  - spec/dummy/config/initializers/wrap_parameters.rb
284
- - spec/dummy/config/locales/en.yml
285
- - spec/dummy/config/routes.rb
286
- - spec/dummy/config/secrets.yml
286
+ - spec/dummy/config/initializers/assets.rb
287
+ - spec/dummy/config/initializers/cookies_serializer.rb
288
+ - spec/dummy/config/initializers/inflections.rb
287
289
  - spec/dummy/config.ru
288
- - spec/dummy/db/development.sqlite3
290
+ - spec/dummy/Rakefile
291
+ - spec/dummy/public/favicon.ico
292
+ - spec/dummy/public/422.html
293
+ - spec/dummy/public/500.html
294
+ - spec/dummy/public/404.html
289
295
  - spec/dummy/db/schema.rb
290
296
  - spec/dummy/db/test.sqlite3
291
- - spec/dummy/log/development.log
297
+ - spec/dummy/db/development.sqlite3
292
298
  - spec/dummy/log/test.log
293
- - spec/dummy/public/404.html
294
- - spec/dummy/public/422.html
295
- - spec/dummy/public/500.html
296
- - spec/dummy/public/favicon.ico
297
- - spec/dummy/Rakefile
299
+ - spec/dummy/log/development.log
298
300
  - spec/dummy/README.rdoc
299
- - spec/factories/panda_pal_organizations.rb
300
- - spec/factories/panda_pal_sessions.rb
301
- - spec/models/panda_pal/organization_spec.rb
302
301
  - spec/models/panda_pal/session_spec.rb
302
+ - spec/models/panda_pal/organization_spec.rb
303
+ - spec/factories/panda_pal_sessions.rb
304
+ - spec/factories/panda_pal_organizations.rb
303
305
  - spec/rails_helper.rb
304
- - spec/spec_helper.rb