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 +4 -4
- data/README.md +282 -0
- data/lib/panda_pal/version.rb +1 -1
- data/panda_pal.gemspec +1 -1
- metadata +30 -29
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3210543886deb93ce6dcd14691b3dd2d3e55134a
|
4
|
+
data.tar.gz: cb0fda8a8d40dca28a56e33923c705c9c3907ea2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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.
|
data/lib/panda_pal/version.rb
CHANGED
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.
|
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.
|
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:
|
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/
|
270
|
-
- spec/dummy/
|
271
|
-
- spec/dummy/config/
|
272
|
-
- spec/dummy/config/
|
273
|
-
- spec/dummy/config/
|
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/
|
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/
|
285
|
-
- spec/dummy/config/
|
286
|
-
- spec/dummy/config/
|
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/
|
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/
|
297
|
+
- spec/dummy/db/development.sqlite3
|
292
298
|
- spec/dummy/log/test.log
|
293
|
-
- spec/dummy/
|
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
|