panda_pal 5.15.1.beta4 → 5.16.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +50 -2
- data/app/controllers/panda_pal/lti_v1_p3_controller.rb +28 -3
- data/app/lib/panda_pal/deep_linking_helpers.rb +142 -0
- data/app/lib/panda_pal/launch_url_helpers.rb +38 -1
- data/app/models/panda_pal/session.rb +1 -1
- data/config/initializers/apartment.rb +21 -31
- data/config/routes.rb +1 -0
- data/db/migrate/20250401214421_rename_session_session_key.rb +1 -1
- data/lib/panda_pal/lti_constants.rb +48 -0
- data/lib/panda_pal/version.rb +1 -1
- data/lib/panda_pal.rb +18 -4
- metadata +3 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2b33c2970defb1a7405a8482186720ab63a05b10db522e4c39ce56b9aa38c1b9
|
4
|
+
data.tar.gz: 6a44dc5db6d135882e4c2aefe8b7872ea87ccd066fe329b0764e2f8564694800
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 589b2581d892bca49ba635d01a70b9db1f6044b9c885758fdbfb6d483ca3c91861bc22515fdbde2086499c7d380b96f199f8dd26e44e1cd900f2a3c3c72fc53a
|
7
|
+
data.tar.gz: 330d2c17013313a47f361f12709acd99291fe918cdea00405b7f76d55925557000c66b0eee08ea68049ece4acd77e86db1fd3acec73ccacaa3165e235cadcafa
|
data/README.md
CHANGED
@@ -149,6 +149,54 @@ end
|
|
149
149
|
the defined action. Setting it to `false` indicates that the defined action handles launch validation and setup itself (this has been the legacy approach).
|
150
150
|
Because `auto_launch: false` is most similar to the previous behavior, it is the default for LTI 1.0/1.1 LTIs. For LTI 1.3 LTIs, `auto_launch: true` is the default. If not specified and `:organization_id` is detected in the Route Path, `auto_launch` will be set to `true`
|
151
151
|
|
152
|
+
## Deep Linking (LTI 1.3)
|
153
|
+
|
154
|
+
PandaPal provides comprehensive support for LTI 1.3 Deep Linking, allowing tools to be configured for content selection that can be placed directly into Canvas courses.
|
155
|
+
|
156
|
+
### Quick Start
|
157
|
+
|
158
|
+
1. **Configure placements** in your LTI initializer:
|
159
|
+
```ruby
|
160
|
+
# config/initializers/lti.rb
|
161
|
+
PandaPal.stage_placement(:content_selection, {
|
162
|
+
deep_linking: true,
|
163
|
+
deep_link_redirect_url: :content_selection_url,
|
164
|
+
text: 'Select Content',
|
165
|
+
enabled: true
|
166
|
+
})
|
167
|
+
```
|
168
|
+
|
169
|
+
2. **Include helpers** in your controller and build content items with **required `launch_type`**:
|
170
|
+
```ruby
|
171
|
+
class ContentSelectionController < ApplicationController
|
172
|
+
include PandaPal::DeepLinkingHelpers
|
173
|
+
|
174
|
+
def create
|
175
|
+
content_items = [
|
176
|
+
build_link_content_item(
|
177
|
+
url: panda_pal.v1p3_resource_link_request_url,
|
178
|
+
title: 'Selected Content',
|
179
|
+
custom: {
|
180
|
+
content_id: params[:content_id],
|
181
|
+
launch_type: :content_launch # REQUIRED for proper routing
|
182
|
+
}
|
183
|
+
)
|
184
|
+
]
|
185
|
+
render_deep_linking_response(content_items)
|
186
|
+
end
|
187
|
+
end
|
188
|
+
```
|
189
|
+
|
190
|
+
3. **Add corresponding launch routes**:
|
191
|
+
```ruby
|
192
|
+
# config/routes.rb
|
193
|
+
lti_nav content_launch: 'content#launch'
|
194
|
+
```
|
195
|
+
|
196
|
+
**⚠️ IMPORTANT**: All content items **MUST** include `launch_type` in the `custom` hash. This is used by the `resource_link_request` action to determine which route to redirect to when Canvas launches your content.
|
197
|
+
|
198
|
+
**For complete configuration options, implementation examples, helper method documentation, and troubleshooting, see [DEEP_LINKING.md](DEEP_LINKING.md).**
|
199
|
+
|
152
200
|
## Implementating data segregation
|
153
201
|
This engine uses Apartment to keep data segregated between installations of the implementing LTI tool.
|
154
202
|
By default, it does this by inspecting the path of the request, and matching URLs containing `orgs` or `organizations`,
|
@@ -457,7 +505,7 @@ data_type: If specified, and a settings hash contains this attribute, attribute
|
|
457
505
|
use `class` to determine data type. For example: 30.minutes.class.to_s => "ActiveSupport::Duration"
|
458
506
|
|
459
507
|
## Bridge vs Canvas
|
460
|
-
As of 3.2.0, the LTI XML config can have subtle differences based on whether your platform is bridge, or canvas.
|
508
|
+
As of 3.2.0, the LTI XML config can have subtle differences based on whether your platform is bridge, or canvas.
|
461
509
|
This is determined by `PandaPal.lti_options[:platform]`.
|
462
510
|
Set this to `platform: 'canvas.instructure.com'` (default)
|
463
511
|
OR `platform: 'bridgeapp.com'`
|
@@ -490,7 +538,7 @@ You will want to watch out for a few scenarios:
|
|
490
538
|
2) Use the `Authorization` header with `token={session_key}` to send your
|
491
539
|
PandaPal session info into api calls.
|
492
540
|
3) If you use `link_to` and navigate in your LTI (apps that are not single page)
|
493
|
-
make sure you include the `link_nonce` like so:
|
541
|
+
make sure you include the `link_nonce` like so:
|
494
542
|
```ruby
|
495
543
|
link_to "Link Name", somewhere_else_path(arg, session_token: link_nonce)
|
496
544
|
```
|
@@ -6,7 +6,7 @@ module PandaPal
|
|
6
6
|
skip_before_action :verify_authenticity_token, raise: false
|
7
7
|
end
|
8
8
|
|
9
|
-
before_action ->{ validate_launch!(version: :v1p3) }, only: [:resource_link_request]
|
9
|
+
before_action ->{ validate_launch!(version: :v1p3) }, only: [:resource_link_request, :deep_link_request]
|
10
10
|
before_action :enforce_environment!, only: [:resource_link_request]
|
11
11
|
|
12
12
|
# Redirect to beta/test as necessary
|
@@ -40,7 +40,7 @@ module PandaPal
|
|
40
40
|
end
|
41
41
|
|
42
42
|
def resource_link_request
|
43
|
-
ltype = @decoded_lti_jwt[
|
43
|
+
ltype = @decoded_lti_jwt[LtiConstants::Canvas::PLACEMENT] || @decoded_lti_jwt[LtiConstants::Claims::CUSTOM]&.dig("launch_type")
|
44
44
|
|
45
45
|
if ltype
|
46
46
|
current_session.data.merge!({
|
@@ -54,6 +54,32 @@ module PandaPal
|
|
54
54
|
end
|
55
55
|
end
|
56
56
|
|
57
|
+
def deep_link_request
|
58
|
+
message_type = @decoded_lti_jwt[LtiConstants::Claims::MESSAGE_TYPE]
|
59
|
+
|
60
|
+
unless message_type == LtiConstants::MessageTypes::DEEP_LINKING_REQUEST
|
61
|
+
render plain: 'Not a Deep Linking request', status: :bad_request
|
62
|
+
return
|
63
|
+
end
|
64
|
+
|
65
|
+
# Store deep linking session data
|
66
|
+
current_session.data.merge!({
|
67
|
+
lti_version: 'v1p3',
|
68
|
+
lti_launch_placement: 'deep_linking',
|
69
|
+
launch_url_params: request.query_parameters.to_h,
|
70
|
+
launch_params: @decoded_lti_jwt,
|
71
|
+
deep_link_return_url: @decoded_lti_jwt[LtiConstants::DeepLinking::DEEP_LINKING_SETTINGS][LtiConstants::DeepLinkingSettings::DEEP_LINK_RETURN_URL],
|
72
|
+
deep_link_data: @decoded_lti_jwt[LtiConstants::DeepLinking::DATA]
|
73
|
+
})
|
74
|
+
|
75
|
+
redirect_url = LaunchUrlHelpers.deep_link_redirect_url(request)
|
76
|
+
if redirect_url
|
77
|
+
redirect_with_session_to(redirect_url, route_context: main_app)
|
78
|
+
else
|
79
|
+
render plain: 'Deep linking redirect URL not configured. Please configure deep_link_redirect_url in your placement or global LTI options.', status: :internal_server_error
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
57
83
|
def tool_config
|
58
84
|
if PandaPal.lti_environments.empty?
|
59
85
|
render plain: 'Domains must be set in lti_environments'
|
@@ -71,7 +97,6 @@ module PandaPal
|
|
71
97
|
target_link_uri: LaunchUrlHelpers.absolute_launch_url(
|
72
98
|
k.to_sym,
|
73
99
|
host: parsed_request_url,
|
74
|
-
launch_handler: v1p3_resource_link_request_path,
|
75
100
|
default_auto_launch: true
|
76
101
|
),
|
77
102
|
})
|
@@ -0,0 +1,142 @@
|
|
1
|
+
module PandaPal
|
2
|
+
module DeepLinkingHelpers
|
3
|
+
# Build a link content item
|
4
|
+
# @param url [String] The launch URL for the content
|
5
|
+
# @param title [String] The title of the content item
|
6
|
+
# @param text [String] Optional description text
|
7
|
+
# @param icon [String] Optional icon URL
|
8
|
+
# @param thumbnail [String] Optional thumbnail URL
|
9
|
+
# @param custom [Hash] Custom parameters - MUST include launch_type for proper routing
|
10
|
+
# @param options [Hash] Additional options to merge into the content item
|
11
|
+
#
|
12
|
+
# Example:
|
13
|
+
# build_link_content_item(
|
14
|
+
# url: panda_pal.v1p3_resource_link_request_url,
|
15
|
+
# title: 'My Quiz',
|
16
|
+
# custom: {
|
17
|
+
# quiz_id: 123,
|
18
|
+
# launch_type: :quiz_launch # REQUIRED for proper routing
|
19
|
+
# }
|
20
|
+
# )
|
21
|
+
def build_link_content_item(url:, title: nil, text: nil, icon: nil, thumbnail: nil, custom: {}, **options)
|
22
|
+
validate_custom_launch_type!(custom)
|
23
|
+
|
24
|
+
content_item = {
|
25
|
+
type: 'link',
|
26
|
+
url: url,
|
27
|
+
title: title
|
28
|
+
}
|
29
|
+
content_item[:text] = text if text.present?
|
30
|
+
content_item[:icon] = icon if icon.present?
|
31
|
+
content_item[:thumbnail] = thumbnail if thumbnail.present?
|
32
|
+
content_item[:custom] = custom if custom.present?
|
33
|
+
content_item.merge(options)
|
34
|
+
end
|
35
|
+
|
36
|
+
# Build a file content item
|
37
|
+
# @param url [String] The file URL
|
38
|
+
# @param title [String] The title of the content item
|
39
|
+
# @param text [String] Optional description text
|
40
|
+
# @param media_type [String] Optional media type
|
41
|
+
# @param icon [String] Optional icon URL
|
42
|
+
# @param custom [Hash] Custom parameters - MUST include launch_type for proper routing
|
43
|
+
# @param options [Hash] Additional options to merge into the content item
|
44
|
+
def build_file_content_item(url:, title: nil, text: nil, media_type: nil, icon: nil, custom: {}, **options)
|
45
|
+
validate_custom_launch_type!(custom)
|
46
|
+
|
47
|
+
content_item = {
|
48
|
+
type: 'file',
|
49
|
+
url: url,
|
50
|
+
title: title
|
51
|
+
}
|
52
|
+
content_item[:text] = text if text.present?
|
53
|
+
content_item[:mediaType] = media_type if media_type.present?
|
54
|
+
content_item[:icon] = icon if icon.present?
|
55
|
+
content_item[:custom] = custom if custom.present?
|
56
|
+
content_item.merge(options)
|
57
|
+
end
|
58
|
+
|
59
|
+
# Build an HTML content item
|
60
|
+
# @param html [String] The HTML content
|
61
|
+
# @param title [String] The title of the content item
|
62
|
+
# @param text [String] Optional description text
|
63
|
+
# @param custom [Hash] Custom parameters - MUST include launch_type for proper routing
|
64
|
+
# @param options [Hash] Additional options to merge into the content item
|
65
|
+
def build_html_content_item(html:, title: nil, text: nil, custom: {}, **options)
|
66
|
+
validate_custom_launch_type!(custom)
|
67
|
+
|
68
|
+
content_item = {
|
69
|
+
type: 'html',
|
70
|
+
html: html,
|
71
|
+
title: title
|
72
|
+
}
|
73
|
+
content_item[:text] = text if text.present?
|
74
|
+
content_item[:custom] = custom if custom.present?
|
75
|
+
content_item.merge(options)
|
76
|
+
end
|
77
|
+
|
78
|
+
# Render the deep linking response
|
79
|
+
# This builds the JWT and renders the auto-submit form
|
80
|
+
def render_deep_linking_response(content_items, custom_data: {})
|
81
|
+
deep_link_return_url = current_session[:deep_link_return_url]
|
82
|
+
deep_link_data = current_session[:deep_link_data]
|
83
|
+
|
84
|
+
unless deep_link_return_url.present?
|
85
|
+
render plain: 'No deep linking session found. Please ensure you accessed this through a proper deep linking flow.', status: :bad_request
|
86
|
+
return
|
87
|
+
end
|
88
|
+
|
89
|
+
# Build custom deep link data - merge Canvas data with custom data
|
90
|
+
launch_params = current_session[:launch_params]
|
91
|
+
merged_custom_data = (deep_link_data || {}).merge({
|
92
|
+
selection_time: Time.now.iso8601,
|
93
|
+
selected_by: launch_params&.dig('sub') || 'unknown'
|
94
|
+
}).merge(custom_data)
|
95
|
+
|
96
|
+
# Build the JWT response
|
97
|
+
jwt = build_deep_link_jwt(content_items, merged_custom_data)
|
98
|
+
|
99
|
+
# Set up the auto-submit form
|
100
|
+
@form_action = deep_link_return_url
|
101
|
+
@method = :post
|
102
|
+
@form_data = { JWT: jwt }
|
103
|
+
|
104
|
+
render partial: 'panda_pal/partials/auto_submit_form'
|
105
|
+
end
|
106
|
+
|
107
|
+
private
|
108
|
+
|
109
|
+
def validate_custom_launch_type!(custom)
|
110
|
+
unless custom.is_a?(Hash) && custom.key?(:launch_type)
|
111
|
+
raise ArgumentError, "custom parameter must include :launch_type for proper routing. Example: custom: { launch_type: :my_content_type }"
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def build_deep_link_jwt(content_items, custom_deep_link_data)
|
116
|
+
# Get issuer (client_id) and audience from session
|
117
|
+
launch_params = current_session[:launch_params]
|
118
|
+
issuer = launch_params&.dig('aud') || current_organization.key
|
119
|
+
audience = launch_params&.dig('iss')
|
120
|
+
|
121
|
+
unless audience.present?
|
122
|
+
raise StandardError, 'Unable to determine audience for deep linking response'
|
123
|
+
end
|
124
|
+
|
125
|
+
deep_link_jwt_payload = {
|
126
|
+
iss: issuer,
|
127
|
+
aud: audience,
|
128
|
+
iat: Time.now.to_i,
|
129
|
+
exp: Time.now.to_i + 600,
|
130
|
+
nonce: SecureRandom.uuid,
|
131
|
+
PandaPal::LtiConstants::Claims::MESSAGE_TYPE => PandaPal::LtiConstants::MessageTypes::DEEP_LINKING_RESPONSE,
|
132
|
+
PandaPal::LtiConstants::Claims::VERSION => PandaPal::LtiConstants::Versions::V1_3,
|
133
|
+
PandaPal::LtiConstants::DeepLinking::CONTENT_ITEMS => content_items,
|
134
|
+
PandaPal::LtiConstants::DeepLinking::DATA => custom_deep_link_data,
|
135
|
+
}
|
136
|
+
|
137
|
+
require 'json/jwt'
|
138
|
+
jwk = JSON::JWK.new(PandaPal.lti_private_key)
|
139
|
+
JSON::JWT.new(deep_link_jwt_payload).sign(jwk, :RS256).to_s
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
@@ -2,6 +2,13 @@ module PandaPal
|
|
2
2
|
module LaunchUrlHelpers
|
3
3
|
def self.absolute_launch_url(launch_type, host: uri_host, launch_handler: nil, default_auto_launch: false)
|
4
4
|
opts = PandaPal.lti_paths[launch_type]
|
5
|
+
|
6
|
+
if launch_handler.nil? && opts.present?
|
7
|
+
launch_handler = opts[:message_type] == PandaPal::LtiConstants::MessageTypes::DEEP_LINKING_REQUEST ?
|
8
|
+
:v1p3_deep_link_request_path :
|
9
|
+
:v1p3_resource_link_request_path
|
10
|
+
end
|
11
|
+
|
5
12
|
auto_launch = opts[:auto_launch] != nil ? opts[:auto_launch] : default_auto_launch
|
6
13
|
auto_launch = auto_launch && launch_handler.present?
|
7
14
|
|
@@ -45,7 +52,7 @@ module PandaPal
|
|
45
52
|
def self.launch_route(opts, launch_type: nil)
|
46
53
|
if opts.is_a?(Symbol) || opts.is_a?(String)
|
47
54
|
launch_type = opts.to_sym
|
48
|
-
opts = PandaPal.lti_paths[launch_type]
|
55
|
+
opts = PandaPal.lti_paths[launch_type] || {}
|
49
56
|
end
|
50
57
|
|
51
58
|
if opts[:route_helper_key]
|
@@ -100,5 +107,35 @@ module PandaPal
|
|
100
107
|
engine = key_bits[0] == 'MainApp' ? Rails.application : (key_bits[0].constantize)::Engine
|
101
108
|
engine.routes.url_helpers.send(key_bits[1], *arguments, **kwargs)
|
102
109
|
end
|
110
|
+
|
111
|
+
def self.deep_link_redirect_url(request)
|
112
|
+
# Try placement-specific configuration first
|
113
|
+
launch_type = request.query_parameters['launch_type']&.to_sym
|
114
|
+
return global_configuration_url unless launch_type
|
115
|
+
|
116
|
+
placement_config = PandaPal.lti_paths[launch_type]
|
117
|
+
return global_configuration_url unless deep_linking_placement?(placement_config)
|
118
|
+
|
119
|
+
# Check for explicit deep_link_redirect_url configuration
|
120
|
+
redirect_url = placement_config[:deep_link_redirect_url]
|
121
|
+
return normalize_redirect_url(redirect_url) if redirect_url.present?
|
122
|
+
|
123
|
+
# Fall back to global configuration
|
124
|
+
global_configuration_url
|
125
|
+
end
|
126
|
+
|
127
|
+
def self.global_configuration_url
|
128
|
+
redirect_url = PandaPal.lti_options[:deep_link_redirect_url]
|
129
|
+
normalize_redirect_url(redirect_url) if redirect_url.present?
|
130
|
+
end
|
131
|
+
|
132
|
+
def self.deep_linking_placement?(placement_config)
|
133
|
+
placement_config&.dig(:message_type) == PandaPal::LtiConstants::MessageTypes::DEEP_LINKING_REQUEST
|
134
|
+
end
|
135
|
+
|
136
|
+
def self.normalize_redirect_url(url)
|
137
|
+
return nil unless url.present?
|
138
|
+
url.is_a?(Symbol) ? url : url.to_sym
|
139
|
+
end
|
103
140
|
end
|
104
141
|
end
|
@@ -199,7 +199,7 @@ module PandaPal
|
|
199
199
|
def custom_lti_params
|
200
200
|
@custom_lti_params ||= begin
|
201
201
|
# LT 1.3
|
202
|
-
custom_params = launch_params[
|
202
|
+
custom_params = launch_params[PandaPal::LtiConstants::Claims::CUSTOM]
|
203
203
|
return custom_params if custom_params.present?
|
204
204
|
|
205
205
|
# LTI 1.0/1.1
|
@@ -10,29 +10,25 @@ require "apartment/adapters/postgresql_adapter"
|
|
10
10
|
module Apartment
|
11
11
|
SHARD_PREFIXES = ["SHARD_DB", "HEROKU_POSTGRESQL"]
|
12
12
|
|
13
|
-
|
14
|
-
|
13
|
+
def self.shard_configurations
|
14
|
+
$shard_configurations ||= begin
|
15
|
+
shard_to_env = {}
|
15
16
|
|
16
|
-
|
17
|
-
$shard_configurations ||= begin
|
18
|
-
shard_to_env = {}
|
17
|
+
base_config = ActiveRecord::Base.configurations.find_db_config(Rails.env).configuration_hash
|
19
18
|
|
20
|
-
|
19
|
+
ENV.keys.each do |k|
|
20
|
+
m = /^(#{SHARD_PREFIXES.join("|")})_(\w+)_URL$/.match(k)
|
21
|
+
next unless m
|
21
22
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
shardcfg = ActiveRecord::Base.configurations.resolve(url).configuration_hash
|
28
|
-
shardcfg = base_config.merge(shardcfg || {})
|
29
|
-
shard_to_env[m[2].downcase] = shardcfg
|
30
|
-
end
|
23
|
+
url = ENV[k]
|
24
|
+
shardcfg = ActiveRecord::Base.configurations.resolve(url).configuration_hash
|
25
|
+
shardcfg = base_config.merge(shardcfg || {})
|
26
|
+
shard_to_env[m[2].downcase] = shardcfg
|
27
|
+
end
|
31
28
|
|
32
|
-
|
29
|
+
shard_to_env.freeze unless Rails.env.test?
|
33
30
|
|
34
|
-
|
35
|
-
end
|
31
|
+
shard_to_env
|
36
32
|
end
|
37
33
|
end
|
38
34
|
|
@@ -124,12 +120,14 @@ module Apartment
|
|
124
120
|
|
125
121
|
raise ActiveRecord::StatementInvalid, "PandaPal/Apartment Mutli-DB support does not currently support DB roles" if ActiveRecord::Base.current_role != ActiveRecord::Base.default_role
|
126
122
|
|
127
|
-
|
128
|
-
|
129
|
-
|
123
|
+
unless ActiveRecord::Base.connected?
|
124
|
+
Apartment.establish_connection multi_tenantify(tenant, false)
|
125
|
+
Apartment.connection.verify!
|
126
|
+
end
|
127
|
+
|
130
128
|
Apartment.connection.enable_query_cache! if query_cache_enabled
|
131
129
|
|
132
|
-
raise ActiveRecord::StatementInvalid, "Could not find schema for tenant
|
130
|
+
raise ActiveRecord::StatementInvalid, "Could not find schema for tenant #{tenant} (#{tenant_schemas.inspect})" unless schema_exists?(tenant_schemas)
|
133
131
|
|
134
132
|
Apartment.connection.schema_search_path = full_search_path
|
135
133
|
rescue *rescuable_exceptions => e
|
@@ -152,14 +150,6 @@ module Apartment
|
|
152
150
|
schema
|
153
151
|
end
|
154
152
|
end
|
155
|
-
|
156
|
-
def raise_schema_connect_to_new(tenant, exception)
|
157
|
-
shard, schema = Tenant.split_tenant(tenant)
|
158
|
-
raise TenantNotFound, <<~EXCEPTION_MESSAGE
|
159
|
-
Could not set search path to schemas, they may be invalid: "#{schema}" #{full_search_path}.
|
160
|
-
Original error: #{exception.class}: #{exception}
|
161
|
-
EXCEPTION_MESSAGE
|
162
|
-
end
|
163
153
|
end
|
164
154
|
end
|
165
155
|
|
@@ -220,7 +210,7 @@ Apartment.configure do |config|
|
|
220
210
|
shard_configurations = Apartment.shard_configurations
|
221
211
|
|
222
212
|
PandaPal::Organization.all.to_a.each_with_object({}) do |org, hash|
|
223
|
-
shard = org.shard
|
213
|
+
shard = org.shard || "default"
|
224
214
|
hash[org.tenant_name] = shard == "default" ? base_config : shard_configurations[shard.downcase]
|
225
215
|
end
|
226
216
|
else
|
data/config/routes.rb
CHANGED
@@ -14,6 +14,7 @@ PandaPal::Engine.routes.draw do
|
|
14
14
|
post '/oidc_login' => 'lti_v1_p3#login'
|
15
15
|
get '/oidc_login' => 'lti_v1_p3#login'
|
16
16
|
post '/resource_link_request' => 'lti_v1_p3#resource_link_request'
|
17
|
+
post '/deep_link_request' => 'lti_v1_p3#deep_link_request'
|
17
18
|
get '/public_jwks' => 'lti_v1_p3#public_jwks'
|
18
19
|
end
|
19
20
|
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module PandaPal
|
2
|
+
module LtiConstants
|
3
|
+
# LTI 1.3 Standard Claims
|
4
|
+
module Claims
|
5
|
+
MESSAGE_TYPE = 'https://purl.imsglobal.org/spec/lti/claim/message_type'.freeze
|
6
|
+
VERSION = 'https://purl.imsglobal.org/spec/lti/claim/version'.freeze
|
7
|
+
DEPLOYMENT_ID = 'https://purl.imsglobal.org/spec/lti/claim/deployment_id'.freeze
|
8
|
+
TARGET_LINK_URI = 'https://purl.imsglobal.org/spec/lti/claim/target_link_uri'.freeze
|
9
|
+
CONTEXT = 'https://purl.imsglobal.org/spec/lti/claim/context'.freeze
|
10
|
+
RESOURCE_LINK = 'https://purl.imsglobal.org/spec/lti/claim/resource_link'.freeze
|
11
|
+
ROLES = 'https://purl.imsglobal.org/spec/lti/claim/roles'.freeze
|
12
|
+
CUSTOM = 'https://purl.imsglobal.org/spec/lti/claim/custom'.freeze
|
13
|
+
end
|
14
|
+
|
15
|
+
# LTI Deep Linking Claims
|
16
|
+
module DeepLinking
|
17
|
+
CONTENT_ITEMS = 'https://purl.imsglobal.org/spec/lti-dl/claim/content_items'.freeze
|
18
|
+
DATA = 'https://purl.imsglobal.org/spec/lti-dl/claim/data'.freeze
|
19
|
+
DEEP_LINKING_SETTINGS = 'https://purl.imsglobal.org/spec/lti-dl/claim/deep_linking_settings'.freeze
|
20
|
+
end
|
21
|
+
|
22
|
+
# Canvas-specific Claims
|
23
|
+
module Canvas
|
24
|
+
PLACEMENT = 'https://www.instructure.com/placement'.freeze
|
25
|
+
end
|
26
|
+
|
27
|
+
# LTI Message Types
|
28
|
+
module MessageTypes
|
29
|
+
DEEP_LINKING_REQUEST = 'LtiDeepLinkingRequest'.freeze
|
30
|
+
DEEP_LINKING_RESPONSE = 'LtiDeepLinkingResponse'.freeze
|
31
|
+
RESOURCE_LINK_REQUEST = 'LtiResourceLinkRequest'.freeze
|
32
|
+
end
|
33
|
+
|
34
|
+
# LTI Versions
|
35
|
+
module Versions
|
36
|
+
V1_3 = '1.3.0'.freeze
|
37
|
+
end
|
38
|
+
|
39
|
+
# Deep Linking Settings Keys
|
40
|
+
module DeepLinkingSettings
|
41
|
+
DEEP_LINK_RETURN_URL = 'deep_link_return_url'.freeze
|
42
|
+
ACCEPT_TYPES = 'accept_types'.freeze
|
43
|
+
ACCEPT_PRESENTATION_DOCUMENT_TARGETS = 'accept_presentation_document_targets'.freeze
|
44
|
+
ACCEPT_MEDIA_TYPES = 'accept_media_types'.freeze
|
45
|
+
AUTO_CREATE = 'auto_create'.freeze
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
data/lib/panda_pal/version.rb
CHANGED
data/lib/panda_pal.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require "panda_pal/engine"
|
2
2
|
require 'panda_pal/plugins'
|
3
3
|
require 'panda_pal/helpers'
|
4
|
+
require 'panda_pal/lti_constants'
|
4
5
|
require 'oauth/request_proxy/rack_request'
|
5
6
|
require 'oauth/request_proxy/action_dispatch_request'
|
6
7
|
|
@@ -50,9 +51,22 @@ module PandaPal
|
|
50
51
|
@@lti_custom_params.deep_dup
|
51
52
|
end
|
52
53
|
|
53
|
-
def self.stage_navigation(navigation, options)
|
54
|
-
|
55
|
-
|
54
|
+
def self.stage_navigation(navigation, options = {})
|
55
|
+
warn "[DEPRECATION] `stage_navigation` is deprecated. Please use `stage_placement` instead."
|
56
|
+
stage_placement(navigation, options)
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.stage_placement(placement, options = {})
|
60
|
+
@@lti_navigation[placement] ||= {}
|
61
|
+
|
62
|
+
if options.delete(:deep_linking)
|
63
|
+
options = options.merge({
|
64
|
+
message_type: LtiConstants::MessageTypes::DEEP_LINKING_REQUEST,
|
65
|
+
route_helper_key: :deep_link_request
|
66
|
+
})
|
67
|
+
end
|
68
|
+
|
69
|
+
@@lti_navigation[placement].merge!(options)
|
56
70
|
end
|
57
71
|
|
58
72
|
def self.lti_paths
|
@@ -70,7 +84,7 @@ module PandaPal
|
|
70
84
|
end
|
71
85
|
|
72
86
|
def self.lti_private_key=(v)
|
73
|
-
@@lti_private_key =
|
87
|
+
@@lti_private_key = v
|
74
88
|
end
|
75
89
|
|
76
90
|
def self.register_extension(type, modul)
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: panda_pal
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 5.
|
4
|
+
version: 5.16.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Instructure CustomDev
|
@@ -159,6 +159,7 @@ files:
|
|
159
159
|
- app/lib/lti_xml/base_platform.rb
|
160
160
|
- app/lib/lti_xml/bridge_platform.rb
|
161
161
|
- app/lib/lti_xml/canvas_platform.rb
|
162
|
+
- app/lib/panda_pal/deep_linking_helpers.rb
|
162
163
|
- app/lib/panda_pal/launch_url_helpers.rb
|
163
164
|
- app/lib/panda_pal/lti_jwt_validator.rb
|
164
165
|
- app/models/panda_pal/api_call.rb
|
@@ -197,6 +198,7 @@ files:
|
|
197
198
|
- lib/panda_pal/helpers/route_helper.rb
|
198
199
|
- lib/panda_pal/helpers/secure_headers.rb
|
199
200
|
- lib/panda_pal/helpers/session_replacement.rb
|
201
|
+
- lib/panda_pal/lti_constants.rb
|
200
202
|
- lib/panda_pal/plugins.rb
|
201
203
|
- lib/panda_pal/spec_helper.rb
|
202
204
|
- lib/panda_pal/version.rb
|