panda_pal 5.2.5 → 5.3.6.beta1
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 +23 -1
- data/app/controllers/panda_pal/lti_v1_p0_controller.rb +2 -0
- data/app/controllers/panda_pal/lti_v1_p3_controller.rb +7 -2
- data/app/lib/panda_pal/batch_processor.rb +41 -0
- data/app/lib/panda_pal/misc_helper.rb +2 -2
- data/app/models/panda_pal/organization_concerns/task_scheduling.rb +46 -35
- data/db/migrate/20171205194657_remove_old_organization_settings.rb +0 -1
- data/lib/panda_pal/helpers/controller_helper.rb +101 -188
- data/lib/panda_pal/helpers/session_replacement.rb +192 -0
- data/lib/panda_pal/version.rb +1 -1
- data/spec/dummy/log/development.log +40 -15042
- data/spec/dummy/log/test.log +83348 -0
- metadata +7 -13
- data/db/618eef7c0380ba654ad16f867a919e72.sqlite3 +0 -0
- data/db/9ff93d4f7e0e9dc80a43f68997caf4a1.sqlite3 +0 -0
- data/db/a3fda4044a7215bc2c9eb01a4b9e517a.sqlite3 +0 -0
- data/db/daa0e6378a5ec76fcce83b7070dad219.sqlite3 +0 -0
- data/spec/dummy/db/development.sqlite3 +0 -0
- data/spec/dummy/db/test.sqlite3 +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 89cb05a4553807543825da2a80fa692689cbbc594a1ed7d7087aaf91b4967039
|
4
|
+
data.tar.gz: 0603fbf7137c94639f1c43252e2ddb5510d8d620a72042587840fed8b18b3d08
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c0b5747018b1a1f0fcb6193038e26905a54f4afcc9f4e698954f2ac9924082826d1ed29be76740c4ea389a2c2ccbb3d4d3d303bd0d18b3d4ba6709f7ad79b625
|
7
|
+
data.tar.gz: 0deda4bedafe6583167610e83a86b8d13bd4256094df6158971811c097c3a085345ea18ecaaedcf84cc9b0d890ca6ebf8ee6685ee6e70ada9b71370d4b4c9834
|
data/README.md
CHANGED
@@ -369,8 +369,30 @@ You will want to watch out for a few scenarios:
|
|
369
369
|
3) If you use `link_to` and navigate in your LTI (apps that are not single page)
|
370
370
|
make sure you include the `link_nonce` like so:
|
371
371
|
```ruby
|
372
|
-
link_to "Link Name", somewhere_else_path(session_token: link_nonce)
|
372
|
+
link_to "Link Name", somewhere_else_path(arg, session_token: link_nonce)
|
373
373
|
```
|
374
|
+
NB: As of PandaPal 5.2.6, you can instead use
|
375
|
+
```ruby
|
376
|
+
link_to "Name", url_with_session(:somewhere_else_path, arg, kwarg: 1)
|
377
|
+
```
|
378
|
+
|
379
|
+
Persistent sessions have session_tokens as a way to safely communicate a session key in a way that is hopefully not too persistent in case it is logged somewhere.
|
380
|
+
Options for communicating session_token -
|
381
|
+
:nonce (default) - each nonce is good for exactly one communication with the backend server. Once the nonce is used, it is no longer valid.
|
382
|
+
:fixed_ip - each session_token is good until it expires. It must be used from the same ip the LTI launched from.
|
383
|
+
:expiring - this is the least secure. Each token is good until it expires.
|
384
|
+
|
385
|
+
For :fixed_ip and :expiring tokens you can override the default expiration period of 15 minutes.
|
386
|
+
|
387
|
+
See the following example of how to override the link_nonce_type and token expiration length.
|
388
|
+
|
389
|
+
class ApplicationController < ActionController::Base
|
390
|
+
link_nonce_type :fixed_ip
|
391
|
+
def session_expiration_period_minutes
|
392
|
+
120
|
393
|
+
end
|
394
|
+
...
|
395
|
+
end
|
374
396
|
|
375
397
|
### Previous Safari Instructions
|
376
398
|
Safari is weird and you'll potentially run into issues getting `POST` requests to properly validate CSRF if you don't do the following:
|
@@ -2,8 +2,7 @@ require_dependency "panda_pal/application_controller"
|
|
2
2
|
|
3
3
|
module PandaPal
|
4
4
|
class LtiV1P3Controller < ApplicationController
|
5
|
-
|
6
|
-
|
5
|
+
skip_forgery_protection
|
7
6
|
before_action :validate_launch!, only: [:resource_link_request]
|
8
7
|
around_action :switch_tenant, only: [:resource_link_request]
|
9
8
|
|
@@ -37,6 +36,12 @@ module PandaPal
|
|
37
36
|
|
38
37
|
redirect_with_session_to(:"#{LaunchUrlHelpers.launch_route(params[:launch_type])}_url", route_context: main_app)
|
39
38
|
end
|
39
|
+
# render json: {
|
40
|
+
# launch_type: params[:launch_type],
|
41
|
+
# final_url: LaunchUrlHelpers.launch_url(params[:launch_type]),
|
42
|
+
# final_route: LaunchUrlHelpers.launch_route(params[:launch_type]),
|
43
|
+
# decoded_jwt: @decoded_lti_jwt,
|
44
|
+
# }
|
40
45
|
end
|
41
46
|
|
42
47
|
def tool_config
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module PandaPal
|
2
|
+
# An array that "processes" after so many items are added.
|
3
|
+
#
|
4
|
+
# Example Usage:
|
5
|
+
# batches = BatchProcessor.new(of: 1000) do |batch|
|
6
|
+
# # Process the batch somehow
|
7
|
+
# end
|
8
|
+
# enumerator_of_some_kind.each { |item| batches << item }
|
9
|
+
# batches.flush
|
10
|
+
class BatchProcessor
|
11
|
+
attr_reader :batch_size
|
12
|
+
|
13
|
+
def initialize(of: 1000, &blk)
|
14
|
+
@batch_size = of
|
15
|
+
@block = blk
|
16
|
+
@current_batch = []
|
17
|
+
end
|
18
|
+
|
19
|
+
def <<(item)
|
20
|
+
@current_batch << item
|
21
|
+
process_batch if @current_batch.count >= batch_size
|
22
|
+
end
|
23
|
+
|
24
|
+
def add_all(items)
|
25
|
+
items.each do |i|
|
26
|
+
self << i
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def flush
|
31
|
+
process_batch if @current_batch.present?
|
32
|
+
end
|
33
|
+
|
34
|
+
protected
|
35
|
+
|
36
|
+
def process_batch
|
37
|
+
@block.call(@current_batch)
|
38
|
+
@current_batch = []
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -4,9 +4,9 @@ module PandaPal
|
|
4
4
|
|
5
5
|
def self.to_boolean(v)
|
6
6
|
if Rails.version < '5.0'
|
7
|
-
ActiveRecord::Type::Boolean.new.type_cast_from_user(
|
7
|
+
ActiveRecord::Type::Boolean.new.type_cast_from_user(v)
|
8
8
|
else
|
9
|
-
ActiveRecord::Type::Boolean.new.deserialize(
|
9
|
+
ActiveRecord::Type::Boolean.new.deserialize(v)
|
10
10
|
end
|
11
11
|
end
|
12
12
|
end
|
@@ -40,41 +40,7 @@ module PandaPal
|
|
40
40
|
|
41
41
|
hash.tap do |hash|
|
42
42
|
kl = ' ' * (k.to_s.length - 4)
|
43
|
-
hash[k.to_sym] = hash[k.to_s] =
|
44
|
-
required: false,
|
45
|
-
description: <<~MARKDOWN,
|
46
|
-
Override schedule for '#{k.to_s}' task.
|
47
|
-
|
48
|
-
**Default**: #{desc[:schedule].is_a?(String) ? desc[:schedule] : '<Computed>'}
|
49
|
-
|
50
|
-
Set to `false` to disable or supply a Cron string:
|
51
|
-
```yaml
|
52
|
-
#{k.to_s}: 0 0 0 * * * America/Denver
|
53
|
-
##{kl} │ │ │ │ │ │ └── Timezone (Optional)
|
54
|
-
##{kl} │ │ │ │ │ └── Day of Week
|
55
|
-
##{kl} │ │ │ │ └── Month
|
56
|
-
##{kl} │ │ │ └── Day of Month
|
57
|
-
##{kl} │ │ └── Hour
|
58
|
-
##{kl} │ └── Minute
|
59
|
-
##{kl} └── Second (Optional)
|
60
|
-
````
|
61
|
-
MARKDOWN
|
62
|
-
json_schema: {
|
63
|
-
oneOf: [
|
64
|
-
{ type: 'string', pattern: '^((((\d+,)+\d+|(\d+(\/|-)\d+)|\d+|\*) ?){5,6})(\w+\/\w+)?$' },
|
65
|
-
{ enum: [false] },
|
66
|
-
],
|
67
|
-
default: desc[:schedule].is_a?(String) ? desc[:schedule] : '0 0 3 * * * America/Denver',
|
68
|
-
},
|
69
|
-
validate: ->(value, *args, errors:, **kwargs) {
|
70
|
-
begin
|
71
|
-
Rufus::Scheduler.parse(value) if value
|
72
|
-
nil
|
73
|
-
rescue ArgumentError
|
74
|
-
errors << "<path> must be false or a Crontab string"
|
75
|
-
end
|
76
|
-
}
|
77
|
-
}
|
43
|
+
hash[k.to_sym] = hash[k.to_s] = PandaPal::OrganizationConcerns::TaskScheduling.build_settings_entry(desc)
|
78
44
|
end
|
79
45
|
end,
|
80
46
|
}
|
@@ -137,6 +103,51 @@ module PandaPal
|
|
137
103
|
end
|
138
104
|
end
|
139
105
|
|
106
|
+
def self.build_settings_entry(desc)
|
107
|
+
k = desc[:key]
|
108
|
+
kl = ' ' * (k.to_s.length - 4)
|
109
|
+
|
110
|
+
default_schedule = '<Computed>'
|
111
|
+
default_schedule = desc[:schedule] if desc[:schedule].is_a?(String)
|
112
|
+
default_schedule = '<Disabled>' unless desc[:schedule].present?
|
113
|
+
|
114
|
+
{
|
115
|
+
required: false,
|
116
|
+
description: <<~MARKDOWN,
|
117
|
+
Override schedule for '#{k.to_s}' task.
|
118
|
+
|
119
|
+
**Default**: #{default_schedule}
|
120
|
+
|
121
|
+
Set to `false` to disable or supply a Cron string:
|
122
|
+
```yaml
|
123
|
+
#{k.to_s}: 0 0 0 * * * America/Denver
|
124
|
+
##{kl} │ │ │ │ │ │ └── Timezone (Optional)
|
125
|
+
##{kl} │ │ │ │ │ └── Day of Week
|
126
|
+
##{kl} │ │ │ │ └── Month
|
127
|
+
##{kl} │ │ │ └── Day of Month
|
128
|
+
##{kl} │ │ └── Hour
|
129
|
+
##{kl} │ └── Minute
|
130
|
+
##{kl} └── Second (Optional)
|
131
|
+
````
|
132
|
+
MARKDOWN
|
133
|
+
json_schema: {
|
134
|
+
oneOf: [
|
135
|
+
{ type: 'string', pattern: '^((((\d+,)+\d+|(\d+(\/|-)\d+)|\d+|\*) ?){5,6})(\w+\/\w+)?$' },
|
136
|
+
{ enum: [false] },
|
137
|
+
],
|
138
|
+
default: desc[:schedule].is_a?(String) ? desc[:schedule] : '0 0 3 * * * America/Denver',
|
139
|
+
},
|
140
|
+
validate: ->(value, *args, errors:, **kwargs) {
|
141
|
+
begin
|
142
|
+
Rufus::Scheduler.parse(value) if value
|
143
|
+
nil
|
144
|
+
rescue ArgumentError
|
145
|
+
errors << "<path> must be false or a Crontab string"
|
146
|
+
end
|
147
|
+
}
|
148
|
+
}
|
149
|
+
end
|
150
|
+
|
140
151
|
private
|
141
152
|
|
142
153
|
def unschedule_tasks(new_task_keys = nil)
|
@@ -19,7 +19,6 @@ class RemoveOldOrganizationSettings < PandaPal::MiscHelper::MigrationClass
|
|
19
19
|
#PandaPal::Organization.reset_column_information
|
20
20
|
PandaPal::Organization.find_each do |o|
|
21
21
|
# Would like to just be able to do this:
|
22
|
-
# PandaPal::Organization.reset_column_information
|
23
22
|
# o.settings = YAML.load(o.old_settings)
|
24
23
|
# o.save!
|
25
24
|
# but for some reason that is always making the settings null. Instead we will encrypt the settings manually.
|
@@ -1,226 +1,139 @@
|
|
1
1
|
require 'browser'
|
2
|
+
require_relative 'session_replacement'
|
2
3
|
|
3
|
-
module PandaPal::Helpers
|
4
|
-
|
4
|
+
module PandaPal::Helpers
|
5
|
+
module ControllerHelper
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
include SessionReplacement
|
5
8
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
after_action :auto_save_session
|
12
|
-
end
|
13
|
-
|
14
|
-
def save_session
|
15
|
-
current_session.try(:save)
|
16
|
-
end
|
17
|
-
|
18
|
-
def current_session
|
19
|
-
return @current_session if @current_session.present?
|
20
|
-
|
21
|
-
if params[:session_token]
|
22
|
-
payload = JSON.parse(panda_pal_cryptor.decrypt_and_verify(params[:session_token])).with_indifferent_access
|
23
|
-
matched_session = PandaPal::Session.find_by(session_key: payload[:session_key])
|
9
|
+
def current_organization
|
10
|
+
@organization ||= PandaPal::Organization.find_by!(key: organization_key) if organization_key
|
11
|
+
@organization ||= PandaPal::Organization.find_by(id: organization_id) if organization_id
|
12
|
+
@organization ||= PandaPal::Organization.find_by_name(Apartment::Tenant.current)
|
13
|
+
end
|
24
14
|
|
25
|
-
|
26
|
-
|
27
|
-
|
15
|
+
def current_lti_platform
|
16
|
+
return @current_lti_platform if @current_lti_platform.present?
|
17
|
+
# TODO: (Future) This could be expanded more to take better advantage of the LTI 1.3 Multi-Tenancy model.
|
18
|
+
if (canvas_url = current_organization&.settings&.dig(:canvas, :base_url)).present?
|
19
|
+
@current_lti_platform ||= PandaPal::Platform::Canvas.new(canvas_url)
|
28
20
|
end
|
29
|
-
|
30
|
-
|
31
|
-
@
|
21
|
+
@current_lti_platform ||= PandaPal::Platform::Canvas.new('http://localhost:3000') if Rails.env.development?
|
22
|
+
@current_lti_platform ||= PandaPal::Platform::CANVAS
|
23
|
+
@current_lti_platform
|
32
24
|
end
|
33
25
|
|
34
|
-
|
35
|
-
|
36
|
-
@current_session
|
37
|
-
end
|
38
|
-
|
39
|
-
def current_organization
|
40
|
-
@organization ||= PandaPal::Organization.find_by!(key: organization_key) if organization_key
|
41
|
-
@organization ||= PandaPal::Organization.find_by(id: organization_id) if organization_id
|
42
|
-
@organization ||= PandaPal::Organization.find_by_name(Apartment::Tenant.current)
|
43
|
-
end
|
44
|
-
|
45
|
-
def current_lti_platform
|
46
|
-
return @current_lti_platform if @current_lti_platform.present?
|
47
|
-
# TODO: (Future) This could be expanded more to take better advantage of the LTI 1.3 Multi-Tenancy model.
|
48
|
-
if (canvas_url = current_organization&.settings&.dig(:canvas, :base_url)).present?
|
49
|
-
@current_lti_platform ||= PandaPal::Platform::Canvas.new(canvas_url)
|
26
|
+
def lti_launch_params
|
27
|
+
current_session_data[:launch_params]
|
50
28
|
end
|
51
|
-
@current_lti_platform ||= PandaPal::Platform::Canvas.new('http://localhost:3000') if Rails.env.development?
|
52
|
-
@current_lti_platform ||= PandaPal::Platform::CANVAS
|
53
|
-
@current_lti_platform
|
54
|
-
end
|
55
|
-
|
56
|
-
def current_session_data
|
57
|
-
current_session.data
|
58
|
-
end
|
59
|
-
|
60
|
-
def lti_launch_params
|
61
|
-
current_session_data[:launch_params]
|
62
|
-
end
|
63
|
-
|
64
|
-
def session_changed?
|
65
|
-
current_session.changed? && current_session.changes[:data].present?
|
66
|
-
end
|
67
29
|
|
68
|
-
|
69
|
-
|
30
|
+
def validate_launch!
|
31
|
+
safari_override
|
70
32
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
end
|
76
|
-
end
|
77
|
-
|
78
|
-
def validate_v1p0_launch
|
79
|
-
authorized = false
|
80
|
-
if @organization = params['oauth_consumer_key'] && PandaPal::Organization.find_by_key(params['oauth_consumer_key'])
|
81
|
-
sanitized_params = request.request_parameters
|
82
|
-
# These params come over with a safari-workaround launch. The authenticator doesn't like them, so clean them out.
|
83
|
-
safe_unexpected_params = ["full_win_launch_requested", "platform_redirect_url", "dummy_param"]
|
84
|
-
safe_unexpected_params.each do |p|
|
85
|
-
sanitized_params.delete(p)
|
33
|
+
if params[:id_token].present?
|
34
|
+
validate_v1p3_launch
|
35
|
+
elsif params[:oauth_consumer_key].present?
|
36
|
+
validate_v1p0_launch
|
86
37
|
end
|
87
|
-
authenticator = IMS::LTI::Services::MessageAuthenticator.new(request.original_url, sanitized_params, @organization.secret)
|
88
|
-
authorized = authenticator.valid_signature?
|
89
38
|
end
|
90
39
|
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
40
|
+
def validate_v1p0_launch
|
41
|
+
authorized = false
|
42
|
+
if @organization = params['oauth_consumer_key'] && PandaPal::Organization.find_by_key(params['oauth_consumer_key'])
|
43
|
+
sanitized_params = request.request_parameters
|
44
|
+
# These params come over with a safari-workaround launch. The authenticator doesn't like them, so clean them out.
|
45
|
+
safe_unexpected_params = ["full_win_launch_requested", "platform_redirect_url", "dummy_param"]
|
46
|
+
safe_unexpected_params.each do |p|
|
47
|
+
sanitized_params.delete(p)
|
48
|
+
end
|
49
|
+
authenticator = IMS::LTI::Services::MessageAuthenticator.new(request.original_url, sanitized_params, @organization.secret)
|
50
|
+
authorized = authenticator.valid_signature?
|
51
|
+
end
|
97
52
|
|
98
|
-
|
99
|
-
|
100
|
-
|
53
|
+
if !authorized
|
54
|
+
render plain: 'Invalid Credentials, please contact your Administrator.', :status => :unauthorized unless authorized
|
55
|
+
end
|
101
56
|
|
102
|
-
|
103
|
-
|
104
|
-
raise JSON::JWT::VerificationFailed, 'Unrecognized Organization' unless @organization.present?
|
57
|
+
authorized
|
58
|
+
end
|
105
59
|
|
106
|
-
|
60
|
+
def validate_v1p3_launch
|
61
|
+
decoded_jwt = JSON::JWT.decode(params.require(:id_token), :skip_verification)
|
62
|
+
raise JSON::JWT::VerificationFailed, 'error decoding id_token' if decoded_jwt.blank?
|
107
63
|
|
108
|
-
|
109
|
-
|
64
|
+
client_id = decoded_jwt['aud']
|
65
|
+
@organization = PandaPal::Organization.find_by!(key: client_id)
|
66
|
+
raise JSON::JWT::VerificationFailed, 'Unrecognized Organization' unless @organization.present?
|
110
67
|
|
111
|
-
|
112
|
-
raise JSON::JWT::VerificationFailed, jwt_verifier.errors unless jwt_verifier.valid?
|
68
|
+
decoded_jwt.verify!(current_lti_platform.public_jwks)
|
113
69
|
|
114
|
-
|
115
|
-
|
116
|
-
payload = Array(e.message)
|
70
|
+
params[:session_key] = params[:state]
|
71
|
+
raise JSON::JWT::VerificationFailed, 'State is invalid' unless current_session_data[:lti_oauth_nonce] == decoded_jwt['nonce']
|
117
72
|
|
118
|
-
|
119
|
-
|
120
|
-
{ errors: payload },
|
121
|
-
{ id_token: params.require(:id_token) },
|
122
|
-
],
|
123
|
-
}, status: :unauthorized
|
73
|
+
jwt_verifier = PandaPal::LtiJwtValidator.new(decoded_jwt, client_id)
|
74
|
+
raise JSON::JWT::VerificationFailed, jwt_verifier.errors unless jwt_verifier.valid?
|
124
75
|
|
125
|
-
|
126
|
-
|
76
|
+
@decoded_lti_jwt = decoded_jwt
|
77
|
+
rescue JSON::JWT::VerificationFailed => e
|
78
|
+
payload = Array(e.message)
|
127
79
|
|
128
|
-
|
129
|
-
|
130
|
-
|
80
|
+
render json: {
|
81
|
+
message: [
|
82
|
+
{ errors: payload },
|
83
|
+
{ id_token: params.require(:id_token) },
|
84
|
+
],
|
85
|
+
}, status: :unauthorized
|
131
86
|
|
132
|
-
|
133
|
-
yield
|
87
|
+
false
|
134
88
|
end
|
135
|
-
end
|
136
89
|
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
end
|
90
|
+
def switch_tenant(organization = current_organization, &block)
|
91
|
+
return unless organization
|
92
|
+
raise 'This method should be called in an around_action callback' unless block_given?
|
141
93
|
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
return unless request.cookies.keys.length > 0
|
146
|
-
super
|
147
|
-
end
|
148
|
-
|
149
|
-
def valid_session?
|
150
|
-
[
|
151
|
-
current_session.persisted?,
|
152
|
-
current_organization,
|
153
|
-
current_session.panda_pal_organization_id == current_organization.id,
|
154
|
-
Apartment::Tenant.current == current_organization.name
|
155
|
-
].all?
|
156
|
-
rescue SessionNonceMismatch
|
157
|
-
false
|
158
|
-
end
|
159
|
-
|
160
|
-
def safari_override
|
161
|
-
use_secure_headers_override(:safari_override) if browser.safari?
|
162
|
-
end
|
163
|
-
|
164
|
-
# Redirect with the session key intact. In production,
|
165
|
-
# handle this by adding a one-time use encrypted token to the URL.
|
166
|
-
# Keeping it in the URL in development means that it plays
|
167
|
-
# nicely with webpack-dev-server live reloading (otherwise
|
168
|
-
# you get an access error everytime it tries to live reload).
|
169
|
-
|
170
|
-
def redirect_with_session_to(location, params = {}, route_context: self, **rest)
|
171
|
-
params.merge!(rest)
|
172
|
-
if Rails.env.development?
|
173
|
-
redirect_to route_context.send(location, {
|
174
|
-
session_key: current_session.session_key,
|
175
|
-
organization_id: current_organization.id,
|
176
|
-
}.merge(params))
|
177
|
-
else
|
178
|
-
redirect_to route_context.send(location, {
|
179
|
-
session_token: link_nonce,
|
180
|
-
organization_id: current_organization.id,
|
181
|
-
}.merge(params))
|
94
|
+
Apartment::Tenant.switch(organization.name) do
|
95
|
+
yield
|
96
|
+
end
|
182
97
|
end
|
183
|
-
end
|
184
|
-
|
185
|
-
def link_nonce
|
186
|
-
@link_nonce ||= begin
|
187
|
-
current_session_data[:link_nonce] = SecureRandom.hex
|
188
98
|
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
}
|
99
|
+
def forbid_access_if_lacking_session
|
100
|
+
super
|
101
|
+
safari_override
|
102
|
+
end
|
194
103
|
|
195
|
-
|
104
|
+
def valid_session?
|
105
|
+
return false unless current_session(create_missing: false)&.persisted?
|
106
|
+
return false unless current_organization
|
107
|
+
return false unless current_session.panda_pal_organization_id == current_organization.id
|
108
|
+
return false unless Apartment::Tenant.current == current_organization.name
|
109
|
+
true
|
110
|
+
rescue SessionNonceMismatch
|
111
|
+
false
|
196
112
|
end
|
197
|
-
end
|
198
113
|
|
199
|
-
|
114
|
+
def safari_override
|
115
|
+
use_secure_headers_override(:safari_override) if browser.safari?
|
116
|
+
end
|
200
117
|
|
201
|
-
|
202
|
-
org_key ||= params[:oauth_consumer_key]
|
203
|
-
org_key ||= "#{params[:client_id]}/#{params[:deployment_id]}" if params[:client_id].present?
|
204
|
-
org_key ||= session[:organization_key]
|
205
|
-
org_key
|
206
|
-
end
|
118
|
+
private
|
207
119
|
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
match[1]
|
120
|
+
def find_or_create_session(key:)
|
121
|
+
if key == :create
|
122
|
+
PandaPal::Session.new(panda_pal_organization_id: current_organization.id)
|
123
|
+
else
|
124
|
+
PandaPal::Session.find_by(session_key: key)
|
125
|
+
end
|
215
126
|
end
|
216
|
-
end
|
217
127
|
|
218
|
-
|
219
|
-
|
220
|
-
|
128
|
+
def organization_key
|
129
|
+
org_key ||= params[:oauth_consumer_key]
|
130
|
+
org_key ||= "#{params[:client_id]}/#{params[:deployment_id]}" if params[:client_id].present?
|
131
|
+
org_key ||= session[:organization_key]
|
132
|
+
org_key
|
133
|
+
end
|
221
134
|
|
222
|
-
|
223
|
-
|
224
|
-
|
135
|
+
def organization_id
|
136
|
+
params[:organization_id]
|
137
|
+
end
|
225
138
|
end
|
226
139
|
end
|