panda_pal 5.10.1 → 5.12.0
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/MIT-LICENSE +0 -0
- data/README.md +31 -21
- data/Rakefile +0 -0
- data/app/assets/config/panda_pal_manifest.js +0 -0
- data/app/assets/javascripts/panda_pal/application.js +0 -0
- data/app/assets/javascripts/panda_pal/lti.js +0 -0
- data/app/assets/stylesheets/panda_pal/application.css +0 -0
- data/app/assets/stylesheets/panda_pal/lti.css +0 -0
- data/app/controllers/panda_pal/api_call_controller.rb +0 -0
- data/app/controllers/panda_pal/application_controller.rb +0 -0
- data/app/controllers/panda_pal/lti_controller.rb +0 -0
- data/app/controllers/panda_pal/lti_v1_p0_controller.rb +0 -0
- data/app/controllers/panda_pal/lti_v1_p3_controller.rb +14 -0
- data/app/helpers/panda_pal/application_helper.rb +0 -0
- data/app/jobs/panda_pal/jobs/grade_passback_job.rb +0 -0
- data/app/lib/lti_xml/base_platform.rb +0 -0
- data/app/lib/lti_xml/bridge_platform.rb +0 -0
- data/app/lib/lti_xml/canvas_platform.rb +0 -0
- data/app/lib/panda_pal/launch_url_helpers.rb +0 -0
- data/app/lib/panda_pal/lti_jwt_validator.rb +0 -0
- data/app/models/panda_pal/api_call.rb +0 -0
- data/app/models/panda_pal/organization.rb +3 -39
- data/app/models/panda_pal/organization_concerns/multi_database_sharding.rb +22 -0
- data/app/models/panda_pal/organization_concerns/organization_builder.rb +0 -0
- data/app/models/panda_pal/organization_concerns/settings_validation.rb +0 -0
- data/app/models/panda_pal/organization_concerns/task_scheduling.rb +0 -0
- data/app/models/panda_pal/organization_concerns/tenant_handling.rb +54 -0
- data/app/models/panda_pal/panda_pal_record.rb +0 -0
- data/app/models/panda_pal/platform/canvas.rb +0 -0
- data/app/models/panda_pal/platform.rb +0 -0
- data/app/models/panda_pal/session.rb +28 -13
- data/app/views/layouts/panda_pal/application.html.erb +0 -0
- data/app/views/panda_pal/lti/launch.html.erb +0 -0
- data/app/views/panda_pal/lti_v1_p3/login.html.erb +0 -0
- data/app/views/panda_pal/partials/_auto_submit_form.html.erb +0 -0
- data/config/dev_lti_key.key +0 -0
- data/config/initializers/apartment.rb +202 -3
- data/config/routes.rb +0 -0
- data/db/migrate/20160412205931_create_panda_pal_organizations.rb +0 -0
- data/db/migrate/20160413135653_create_panda_pal_sessions.rb +0 -0
- data/db/migrate/20160425130344_add_panda_pal_organization_to_session.rb +0 -0
- data/db/migrate/20170106165533_add_salesforce_id_to_organizations.rb +0 -0
- data/db/migrate/20171205183457_encrypt_organization_settings.rb +0 -0
- data/db/migrate/20171205194657_remove_old_organization_settings.rb +0 -0
- data/db/migrate/20220721095653_create_panda_pal_api_calls.rb +0 -0
- data/lib/panda_pal/concerns/ability_helper.rb +0 -0
- data/lib/panda_pal/engine.rb +0 -2
- data/lib/panda_pal/helpers/console_helpers.rb +0 -0
- data/lib/panda_pal/helpers/controller_helper.rb +0 -0
- data/lib/panda_pal/helpers/misc_helper.rb +0 -0
- data/lib/panda_pal/helpers/route_helper.rb +0 -0
- data/lib/panda_pal/helpers/secure_headers.rb +0 -0
- data/lib/panda_pal/helpers/session_replacement.rb +0 -0
- data/lib/panda_pal/helpers.rb +0 -0
- data/lib/panda_pal/plugins.rb +0 -0
- data/lib/panda_pal/spec_helper.rb +82 -0
- data/lib/panda_pal/version.rb +1 -1
- data/lib/panda_pal.rb +1 -1
- data/lib/tasks/panda_pal_tasks.rake +0 -0
- data/panda_pal.gemspec +3 -3
- data/spec/controllers/panda_pal/api_call_controller_spec.rb +0 -0
- data/spec/core/apartment_multidb_spec.rb +48 -0
- data/spec/dummy/README.rdoc +0 -0
- data/spec/dummy/Rakefile +0 -0
- data/spec/dummy/app/assets/javascripts/application.js +0 -0
- data/spec/dummy/app/assets/stylesheets/application.css +0 -0
- data/spec/dummy/app/controllers/application_controller.rb +0 -0
- data/spec/dummy/app/helpers/application_helper.rb +0 -0
- data/spec/dummy/app/views/layouts/application.html.erb +0 -0
- data/spec/dummy/config/application.rb +0 -0
- data/spec/dummy/config/boot.rb +0 -0
- data/spec/dummy/config/database.yml +6 -2
- data/spec/dummy/config/environment.rb +0 -0
- data/spec/dummy/config/environments/development.rb +0 -0
- data/spec/dummy/config/environments/production.rb +0 -0
- data/spec/dummy/config/environments/test.rb +5 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +0 -0
- data/spec/dummy/config/initializers/cookies_serializer.rb +0 -0
- data/spec/dummy/config/initializers/filter_parameter_logging.rb +0 -0
- data/spec/dummy/config/initializers/inflections.rb +0 -0
- data/spec/dummy/config/initializers/mime_types.rb +0 -0
- data/spec/dummy/config/initializers/session_store.rb +0 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +0 -0
- data/spec/dummy/config/locales/en.yml +0 -0
- data/spec/dummy/config/routes.rb +0 -0
- data/spec/dummy/config/secrets.yml +0 -0
- data/spec/dummy/config.ru +0 -0
- data/spec/dummy/db/schema.rb +0 -0
- data/spec/dummy/db/test2_schema.rb +18 -0
- data/spec/dummy/public/404.html +0 -0
- data/spec/dummy/public/422.html +0 -0
- data/spec/dummy/public/500.html +0 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/factories/panda_pal_organizations.rb +0 -0
- data/spec/factories/panda_pal_sessions.rb +0 -0
- data/spec/models/panda_pal/api_call_spec.rb +1 -1
- data/spec/models/panda_pal/organization/settings_validation_spec.rb +4 -0
- data/spec/models/panda_pal/organization_spec.rb +0 -0
- data/spec/models/panda_pal/session_spec.rb +0 -0
- data/spec/rails_helper.rb +4 -0
- data/spec/spec_helper.rb +0 -1
- metadata +57 -50
- /data/spec/dummy/{bin → app/bin}/bundle +0 -0
- /data/spec/dummy/{bin → app/bin}/rails +0 -0
- /data/spec/dummy/{bin → app/bin}/rake +0 -0
- /data/spec/dummy/{bin → app/bin}/setup +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ab899936461dc59208ae6afef28264feda7a75927798e158ca1ee8381b2ea463
|
|
4
|
+
data.tar.gz: 20424ac6bef9bede8af9e58e8a1393267b0966f96cf56dd8ae8ed0e5b486420a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 85041955aabe8255fcf0a4e3cbcfce3c55c690e0d66d74ac511d6b4d70825d796a62a18fe3ac3e0b5dc66e9ddc865c5b45f672940d92c15a1cc38a7ac350ad91
|
|
7
|
+
data.tar.gz: bcbe2513a4b4916af671f5a913036e0154dd474d510351f6325a2f264d03c1dac10adc4c48bc060c40faafdc798268664c48e2c32943dd4ed3d0dcda076b7ff3
|
data/MIT-LICENSE
CHANGED
|
File without changes
|
data/README.md
CHANGED
|
@@ -57,34 +57,32 @@ Use one of these 6 options in `PandaPal.lti_options` hash.
|
|
|
57
57
|
### Task Scheduling
|
|
58
58
|
`PandaPal` includes an integration with `sidekiq-scheduler`. You can define tasks on an Organization class Stub like so:
|
|
59
59
|
```ruby
|
|
60
|
-
# <your_app>/app/models/
|
|
61
|
-
|
|
60
|
+
# <your_app>/app/models/organization_extension.rb
|
|
61
|
+
module OrganizationExtension
|
|
62
|
+
extend ActiveSupport::Concern
|
|
62
63
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
# Will invoke CanvasSyncStarterWorker.perform_async() according to the cron schedule
|
|
66
|
-
scheduled_task '0 15 05 * * *', :identifier, worker: CanvasSyncStarterWorker
|
|
64
|
+
# Will invoke CanvasSyncStarterWorker.perform_async() according to the cron schedule
|
|
65
|
+
scheduled_task '0 15 05 * * *', :identifier, worker: CanvasSyncStarterWorker
|
|
67
66
|
|
|
68
|
-
|
|
69
|
-
|
|
67
|
+
# Will invoke the method 'organization_method' on the Organization
|
|
68
|
+
scheduled_task '0 15 05 * * *', :organization_method_and_identifier
|
|
70
69
|
|
|
71
|
-
|
|
72
|
-
|
|
70
|
+
# If you need to invoke the same method on multiple schedules
|
|
71
|
+
scheduled_task '0 15 05 * * *', :identifier, worker: :organization_method
|
|
73
72
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
73
|
+
# You can also use a block
|
|
74
|
+
scheduled_task '0 15 05 * * *', :identifier do
|
|
75
|
+
# Do Stuff
|
|
76
|
+
end
|
|
78
77
|
|
|
79
|
-
|
|
80
|
-
|
|
78
|
+
# You can use a Proc (called in the context of the Organization) to determine the schedule
|
|
79
|
+
scheduled_task -> { settings[:cron] }, :identifier
|
|
81
80
|
|
|
82
|
-
|
|
83
|
-
|
|
81
|
+
# You can specify a timezone. If a TZ is not coded and settings[:timezone] is present, it will be appended automatically
|
|
82
|
+
scheduled_task '0 15 05 * * * America/Denver', :identifier, worker: :organization_method
|
|
84
83
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
end
|
|
84
|
+
# Setting settings[:task_schedules][:identifier] will override the code cron schedule. Setting it to false will disable the Task
|
|
85
|
+
# :identifer values _must_ be unique, but can be nil, in which case they will be determined by where (lineno etc) scheduled_task is called
|
|
88
86
|
end
|
|
89
87
|
```
|
|
90
88
|
|
|
@@ -181,6 +179,18 @@ Rails.application.config.middleware.use 'Apartment::Elevators::Generic', lambda
|
|
|
181
179
|
It is also possible to switch tenants by implementing `around_action :switch_tenant` in the controller.
|
|
182
180
|
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.
|
|
183
181
|
|
|
182
|
+
### Sharding
|
|
183
|
+
PandaPal 5.12 added support for multi-DB sharding. If you wish to use this, add a `shard` column to the `PandaPal::Organization` model. The implementation should be fairly transparent and should behave as normal PandaPal/Apartment - the only difference is that tenants can be created on different DB shards.
|
|
184
|
+
|
|
185
|
+
The list of available shards is computed from the Environment variables. On startup, the application looks for variables with the following formats:
|
|
186
|
+
- `SHARD_DB_XYZ_URL`
|
|
187
|
+
- `HEROKU_POSTGRESQL_XYZ_URL`
|
|
188
|
+
(where `XYZ` is the shard identifier)
|
|
189
|
+
|
|
190
|
+
When an `Organization` is created, it is assigned to a random shard (or if no shards were discovered, it is placed in the default database as normal). Alternatively, a specific shard can be specified: `PandaPal::Organization.new(shard: "shard_a")`. An `Organization` can be added to the primary shard with `shard: "default"`
|
|
191
|
+
|
|
192
|
+
The `PandaPal::Organization` record is still created in the `public` schema of the default database, as per usual.
|
|
193
|
+
|
|
184
194
|
### Rake tasks and jobs
|
|
185
195
|
**Delayed Job Support has been removed. This allows each project to assess it's need to handle background jobs.**
|
|
186
196
|
|
data/Rakefile
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -10,6 +10,7 @@ module PandaPal
|
|
|
10
10
|
|
|
11
11
|
before_action :validate_launch!, only: [:resource_link_request]
|
|
12
12
|
around_action :switch_tenant, only: [:resource_link_request]
|
|
13
|
+
before_action :enforce_environment!, only: [:resource_link_request]
|
|
13
14
|
|
|
14
15
|
# Redirect to beta/test as necessary
|
|
15
16
|
before_action :forward_to_env_and_region, only: [:login]
|
|
@@ -19,6 +20,8 @@ module PandaPal
|
|
|
19
20
|
|
|
20
21
|
current_session_data[:lti_platform] = @current_lti_platform&.serialize
|
|
21
22
|
current_session_data[:lti_oauth_nonce] = SecureRandom.uuid
|
|
23
|
+
current_session_data[:canvas_environment] = params['canvas_environment']
|
|
24
|
+
current_session_data[:canvas_region] = params['canvas_region']
|
|
22
25
|
current_session.panda_pal_organization_id = -1
|
|
23
26
|
|
|
24
27
|
@form_action = current_lti_platform.authentication_redirect_url
|
|
@@ -136,6 +139,17 @@ module PandaPal
|
|
|
136
139
|
url
|
|
137
140
|
end
|
|
138
141
|
|
|
142
|
+
def enforce_environment!
|
|
143
|
+
canvas_env = current_session_data[:canvas_environment]
|
|
144
|
+
return unless canvas_env.present?
|
|
145
|
+
|
|
146
|
+
org_canvas_url = current_organization.canvas_url
|
|
147
|
+
|
|
148
|
+
if (canvas_env == 'beta' || canvas_env == 'test') && org_canvas_url.present && !org_canvas_url.include?(".#{canvas_env}.")
|
|
149
|
+
render plain: "This tool is not properly configured for use in #{canvas_env}", status: 400
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
139
153
|
private
|
|
140
154
|
|
|
141
155
|
def auth_redirect_query
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -41,6 +41,9 @@ module PandaPal
|
|
|
41
41
|
include OrganizationConcerns::OrganizationBuilder if $stdin&.tty?
|
|
42
42
|
include OrganizationConcerns::TaskScheduling if defined?(Sidekiq.schedule)
|
|
43
43
|
|
|
44
|
+
include OrganizationConcerns::TenantHandling
|
|
45
|
+
include OrganizationConcerns::MultiDatabaseSharding if (column_names rescue []).include?('shard')
|
|
46
|
+
|
|
44
47
|
attr_encrypted :settings, marshal: true, key: :encryption_key, marshaler: SettingsMarshaler
|
|
45
48
|
before_save {|a| a.settings = a.settings} # this is a hacky work-around to a bug where attr_encrypted is not saving settings in place
|
|
46
49
|
|
|
@@ -56,9 +59,6 @@ module PandaPal
|
|
|
56
59
|
validates :canvas_account_id, presence: true
|
|
57
60
|
validates :salesforce_id, presence: true, uniqueness: true
|
|
58
61
|
|
|
59
|
-
after_create :create_schema
|
|
60
|
-
after_commit :destroy_schema, on: :destroy
|
|
61
|
-
|
|
62
62
|
define_setting("lti", {
|
|
63
63
|
type: 'Hash',
|
|
64
64
|
required: false,
|
|
@@ -71,10 +71,6 @@ module PandaPal
|
|
|
71
71
|
end
|
|
72
72
|
end
|
|
73
73
|
|
|
74
|
-
before_validation on: [:update] do
|
|
75
|
-
errors.add(:name, 'should not be changed after creation') if name_changed?
|
|
76
|
-
end
|
|
77
|
-
|
|
78
74
|
def encryption_key
|
|
79
75
|
# production environment might not have loaded secret_key_base yet.
|
|
80
76
|
# In that case, just read it from env.
|
|
@@ -85,18 +81,6 @@ module PandaPal
|
|
|
85
81
|
end
|
|
86
82
|
end
|
|
87
83
|
|
|
88
|
-
def switch_tenant(&block)
|
|
89
|
-
if block_given?
|
|
90
|
-
Apartment::Tenant.switch(name, &block)
|
|
91
|
-
else
|
|
92
|
-
Apartment::Tenant.switch!(name)
|
|
93
|
-
end
|
|
94
|
-
end
|
|
95
|
-
|
|
96
|
-
def self.current
|
|
97
|
-
find_by_name(Apartment::Tenant.current)
|
|
98
|
-
end
|
|
99
|
-
|
|
100
84
|
def create_api(logic, expiration: nil, uses: nil, host: nil)
|
|
101
85
|
switch_tenant do
|
|
102
86
|
logic = "current_organization.#{logic}" if logic.is_a?(Symbol)
|
|
@@ -109,16 +93,6 @@ module PandaPal
|
|
|
109
93
|
end
|
|
110
94
|
end
|
|
111
95
|
|
|
112
|
-
def rename!(new_name)
|
|
113
|
-
do_switch = Apartment::Tenant.current == name
|
|
114
|
-
ActiveRecord::Base.connection.execute(
|
|
115
|
-
"ALTER SCHEMA \"#{name}\" RENAME TO \"#{new_name}\";"
|
|
116
|
-
)
|
|
117
|
-
self.class.where(id: id).update_all(name: new_name)
|
|
118
|
-
reload
|
|
119
|
-
switch_tenant if do_switch
|
|
120
|
-
end
|
|
121
|
-
|
|
122
96
|
if !PandaPal.lti_options[:platform].present? || PandaPal.lti_options[:platform].is_a?(String)
|
|
123
97
|
CONST_PLATFORM_TYPE = Platform.resolve_platform_class(nil) rescue nil
|
|
124
98
|
else
|
|
@@ -192,16 +166,6 @@ module PandaPal
|
|
|
192
166
|
CONST_PLATFORM_TYPE
|
|
193
167
|
end
|
|
194
168
|
|
|
195
|
-
private
|
|
196
|
-
|
|
197
|
-
def create_schema
|
|
198
|
-
Apartment::Tenant.create name
|
|
199
|
-
end
|
|
200
|
-
|
|
201
|
-
def destroy_schema
|
|
202
|
-
Apartment::Tenant.drop name
|
|
203
|
-
end
|
|
204
|
-
|
|
205
169
|
public
|
|
206
170
|
|
|
207
171
|
PandaPal.resolved_extensions_for(self).each do |ext|
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
module PandaPal
|
|
2
|
+
module OrganizationConcerns
|
|
3
|
+
module MultiDatabaseSharding
|
|
4
|
+
extend ActiveSupport::Concern
|
|
5
|
+
|
|
6
|
+
included do
|
|
7
|
+
if column_names.include?('shard')
|
|
8
|
+
validates :shard, format: { with: /\A[a-z0-9_]+\z/i }, allow_blank: true
|
|
9
|
+
|
|
10
|
+
before_validation on: [:update] do
|
|
11
|
+
errors.add(:shard, 'should not be changed after creation') if shard_changed?
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def tenant_name
|
|
17
|
+
return "#{shard}:#{name}" if shard.present?
|
|
18
|
+
super
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
module PandaPal
|
|
2
|
+
module OrganizationConcerns
|
|
3
|
+
module TenantHandling
|
|
4
|
+
extend ActiveSupport::Concern
|
|
5
|
+
|
|
6
|
+
class_methods do
|
|
7
|
+
def current
|
|
8
|
+
find_by_name(Apartment::Tenant.current)
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
included do
|
|
13
|
+
after_create :create_schema
|
|
14
|
+
after_commit :destroy_schema, on: :destroy
|
|
15
|
+
|
|
16
|
+
before_validation on: [:update] do
|
|
17
|
+
errors.add(:name, 'should not be changed after creation') if name_changed?
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def tenant_name
|
|
22
|
+
name
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def switch_tenant(&block)
|
|
26
|
+
if block_given?
|
|
27
|
+
Apartment::Tenant.switch(tenant_name, &block)
|
|
28
|
+
else
|
|
29
|
+
Apartment::Tenant.switch!(tenant_name)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def rename!(new_name)
|
|
34
|
+
do_switch = Apartment::Tenant.current == name
|
|
35
|
+
ActiveRecord::Base.connection.execute(
|
|
36
|
+
"ALTER SCHEMA \"#{name}\" RENAME TO \"#{new_name}\";"
|
|
37
|
+
)
|
|
38
|
+
self.class.where(id: id).update_all(name: new_name)
|
|
39
|
+
reload
|
|
40
|
+
switch_tenant if do_switch
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
def create_schema
|
|
46
|
+
Apartment::Tenant.create tenant_name
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def destroy_schema
|
|
50
|
+
Apartment::Tenant.drop tenant_name
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -42,24 +42,39 @@ module PandaPal
|
|
|
42
42
|
end
|
|
43
43
|
|
|
44
44
|
def custom_lti_params
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
45
|
+
@custom_lti_params ||= begin
|
|
46
|
+
# LT 1.3
|
|
47
|
+
custom_params = launch_params["https://purl.imsglobal.org/spec/lti/claim/custom"]
|
|
48
|
+
return custom_params if custom_params.present?
|
|
49
|
+
|
|
50
|
+
# LTI 1.0/1.1
|
|
51
|
+
custom_params = {}
|
|
52
|
+
launch_params.each do |k, v|
|
|
53
|
+
next unless k.start_with?("custom_")
|
|
54
|
+
custom_params[k[7..-1]] = v
|
|
55
|
+
end
|
|
55
56
|
|
|
56
|
-
|
|
57
|
+
custom_params.with_indifferent_access
|
|
58
|
+
end
|
|
57
59
|
end
|
|
58
60
|
|
|
59
|
-
def get_lti_cust_param(key)
|
|
61
|
+
def get_lti_cust_param(key, default: :if_not_var)
|
|
60
62
|
nkey = key.to_s.gsub(/^custom_/, '')
|
|
63
|
+
default_value = ->() { PandaPal.lti_custom_params[nkey] || PandaPal.lti_custom_params["custom_#{nkey}"] }
|
|
64
|
+
|
|
65
|
+
val = launch_params.dig("https://purl.imsglobal.org/spec/lti/claim/custom", nkey) || launch_params[nkey] || launch_params["custom_#{nkey}"]
|
|
66
|
+
|
|
67
|
+
if default == :if_not_var
|
|
68
|
+
if val.is_a?(String) && /\$[\.\w]+/.match?(val) && val == default_value[]
|
|
69
|
+
return nil
|
|
70
|
+
end
|
|
71
|
+
elsif default && !val.present?
|
|
72
|
+
return default_value[]
|
|
73
|
+
elsif !default && val == default_value[]
|
|
74
|
+
return nil
|
|
75
|
+
end
|
|
61
76
|
|
|
62
|
-
|
|
77
|
+
val
|
|
63
78
|
end
|
|
64
79
|
|
|
65
80
|
def canvas_role_labels
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
data/config/dev_lti_key.key
CHANGED
|
File without changes
|
|
@@ -5,13 +5,212 @@ begin
|
|
|
5
5
|
rescue LoadError
|
|
6
6
|
end
|
|
7
7
|
|
|
8
|
+
require "apartment/adapters/postgresql_adapter"
|
|
9
|
+
|
|
10
|
+
module Apartment
|
|
11
|
+
SHARD_PREFIXES = ["SHARD_DB", "HEROKU_POSTGRESQL"]
|
|
12
|
+
|
|
13
|
+
def self.shard_configurations
|
|
14
|
+
$shard_configurations ||= begin
|
|
15
|
+
shard_to_env = {}
|
|
16
|
+
|
|
17
|
+
ENV.keys.each do |k|
|
|
18
|
+
m = /^(#{SHARD_PREFIXES.join("|")})_(\w+)_URL$/.match(k)
|
|
19
|
+
next unless m
|
|
20
|
+
|
|
21
|
+
url = ENV[k]
|
|
22
|
+
shard_to_env[m[2].downcase] = ActiveRecord::Base.configurations.resolve(url).configuration_hash
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
shard_to_env.freeze unless Rails.env.test?
|
|
26
|
+
|
|
27
|
+
shard_to_env
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
module Tenant
|
|
32
|
+
self.singleton_class.send(:alias_method, :original_postgresql_adapter, :postgresql_adapter)
|
|
33
|
+
|
|
34
|
+
def self.postgresql_adapter(config)
|
|
35
|
+
if Apartment.with_multi_server_setup
|
|
36
|
+
adapter = Adapters::PostgresMultiDBSchemaAdapter
|
|
37
|
+
adapter.new(config)
|
|
38
|
+
else
|
|
39
|
+
original_postgresql_adapter(config)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def self.split_tenant(tenant)
|
|
44
|
+
bits = tenant.split(":", 2)
|
|
45
|
+
bits.unshift(nil) if bits.length == 1
|
|
46
|
+
bits[0] = "default" if bits[0].nil? || bits[0].empty?
|
|
47
|
+
bits
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
module Adapters
|
|
52
|
+
class PostgresMultiDBSchemaAdapter < Apartment::Adapters::PostgresqlSchemaAdapter
|
|
53
|
+
def initialize(*args, **kwargs)
|
|
54
|
+
super
|
|
55
|
+
@excluded_model_set = Set.new(Apartment.excluded_models)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def process_excluded_model(excluded_model)
|
|
59
|
+
@excluded_model_set << excluded_model
|
|
60
|
+
super
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def is_excluded_model?(model)
|
|
64
|
+
@excluded_model_set.include?(model.to_s)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def db_connection_config(tenant)
|
|
68
|
+
shard, schema = Tenant.split_tenant(tenant)
|
|
69
|
+
if shard == "default"
|
|
70
|
+
@config
|
|
71
|
+
else
|
|
72
|
+
Apartment.shard_configurations[shard.downcase]
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def drop_command(conn, tenant)
|
|
77
|
+
shard, schema = Tenant.split_tenant(tenant)
|
|
78
|
+
conn.execute(%(DROP SCHEMA "#{schema}" CASCADE))
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def tenant_exists?(tenant)
|
|
82
|
+
return true unless Apartment.tenant_presence_check
|
|
83
|
+
shard, schema = Tenant.split_tenant(tenant)
|
|
84
|
+
|
|
85
|
+
Apartment.connection.schema_exists?(schema)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def create_tenant_command(conn, tenant)
|
|
89
|
+
shard, schema = Tenant.split_tenant(tenant)
|
|
90
|
+
# NOTE: This was causing some tests to fail because of the database strategy for rspec
|
|
91
|
+
if conn.open_transactions.positive?
|
|
92
|
+
conn.execute(%(CREATE SCHEMA "#{schema}")).inspect
|
|
93
|
+
else
|
|
94
|
+
schema = %(BEGIN;
|
|
95
|
+
CREATE SCHEMA "#{schema}";
|
|
96
|
+
COMMIT;)
|
|
97
|
+
|
|
98
|
+
conn.execute(schema)
|
|
99
|
+
end
|
|
100
|
+
rescue *rescuable_exceptions => e
|
|
101
|
+
rollback_transaction(conn)
|
|
102
|
+
raise e
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def connect_to_new(tenant = nil)
|
|
106
|
+
return reset if tenant.nil?
|
|
107
|
+
|
|
108
|
+
current_tenant = @current
|
|
109
|
+
tenants_array = tenant.is_a?(Array) ? tenant.map(&:to_s) : tenant.to_s
|
|
110
|
+
tenant_schemas = map_to_schemas(Array(tenants_array))
|
|
111
|
+
query_cache_enabled = ActiveRecord::Base.connection.query_cache_enabled
|
|
112
|
+
|
|
113
|
+
@current = tenants_array
|
|
114
|
+
|
|
115
|
+
raise ActiveRecord::StatementInvalid, "PandaPal/Apartment Mutli-DB support does not currently support DB roles" if ActiveRecord::Base.current_role != ActiveRecord::Base.default_role
|
|
116
|
+
|
|
117
|
+
unless ActiveRecord::Base.connected?
|
|
118
|
+
Apartment.establish_connection multi_tenantify(tenant, false)
|
|
119
|
+
Apartment.connection.verify!
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
Apartment.connection.enable_query_cache! if query_cache_enabled
|
|
123
|
+
|
|
124
|
+
raise ActiveRecord::StatementInvalid, "Could not find schema for tenant #{tenant} (#{tenant_schemas.inspect})" unless schema_exists?(tenant_schemas)
|
|
125
|
+
|
|
126
|
+
Apartment.connection.schema_search_path = full_search_path
|
|
127
|
+
rescue *rescuable_exceptions => e
|
|
128
|
+
@current = current_tenant
|
|
129
|
+
raise_schema_connect_to_new(tenant, e)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
protected
|
|
133
|
+
|
|
134
|
+
def persistent_schemas
|
|
135
|
+
map_to_schemas(super)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def map_to_schemas(tenants)
|
|
139
|
+
all_shard = nil
|
|
140
|
+
tenants.map do |schema|
|
|
141
|
+
shard, schema = Tenant.split_tenant(schema)
|
|
142
|
+
all_shard ||= shard
|
|
143
|
+
raise "Cannot mix shards in persistent_schemas" if shard != all_shard
|
|
144
|
+
schema
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
module CorePatches
|
|
151
|
+
module ActiveRecord
|
|
152
|
+
module FutureResult
|
|
153
|
+
# called in the original thread that knows the tenant
|
|
154
|
+
def initialize(_pool, *_args, **_kwargs)
|
|
155
|
+
@tenant = Apartment::Tenant.current
|
|
156
|
+
super
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# called in the new thread with a connection that needs switching
|
|
160
|
+
def exec_query(_conn, *_args, **_kwargs)
|
|
161
|
+
Apartment::Tenant.switch!(@tenant) unless Apartment::Tenant.current == @tenant
|
|
162
|
+
super
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
module Base
|
|
167
|
+
extend ActiveSupport::Concern
|
|
168
|
+
|
|
169
|
+
included do
|
|
170
|
+
self.singleton_class.send(:alias_method, :pre_apartment_current_shard, :current_shard)
|
|
171
|
+
|
|
172
|
+
def self.current_shard
|
|
173
|
+
# This implementation is definitely a hack, but it should be fairly compatible. If you need to leverage
|
|
174
|
+
# Rails' sharding natively - you should just need to add models to the excluded_models list in Apartment
|
|
175
|
+
# to effectively disable this patch for that model.
|
|
176
|
+
if (adapter = Thread.current[:apartment_adapter]) && adapter.is_a?(Apartment::Adapters::PostgresMultiDBSchemaAdapter) && !adapter.is_excluded_model?(self)
|
|
177
|
+
shard, schema = Apartment::Tenant.split_tenant(adapter.current)
|
|
178
|
+
return "apt:#{shard}" unless shard == "default"
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
pre_apartment_current_shard
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# Fix Apartment's lack of support for load_async
|
|
189
|
+
::ActiveRecord::FutureResult.prepend CorePatches::ActiveRecord::FutureResult
|
|
190
|
+
|
|
191
|
+
# Hack ActiveRecord shards/connection_pooling to support our multi-DB approach
|
|
192
|
+
::ActiveRecord::Base.include CorePatches::ActiveRecord::Base
|
|
193
|
+
end
|
|
194
|
+
|
|
8
195
|
Apartment.configure do |config|
|
|
9
196
|
config.excluded_models ||= []
|
|
10
197
|
config.excluded_models |= ['PandaPal::Organization', 'PandaPal::Session']
|
|
11
198
|
|
|
12
|
-
config.
|
|
13
|
-
|
|
14
|
-
|
|
199
|
+
config.with_multi_server_setup = true unless Rails.env.test?
|
|
200
|
+
|
|
201
|
+
config.tenant_names = lambda do
|
|
202
|
+
if PandaPal::Organization < PandaPal::OrganizationConcerns::MultiDatabaseSharding
|
|
203
|
+
base_config = Apartment.connection_config
|
|
204
|
+
shard_configurations = Apartment.shard_configurations
|
|
205
|
+
|
|
206
|
+
PandaPal::Organization.all.to_a.each_with_object({}) do |org, hash|
|
|
207
|
+
shard = org.shard || "default"
|
|
208
|
+
hash[org.tenant_name] = shard == "default" ? base_config : shard_configurations[shard.downcase]
|
|
209
|
+
end
|
|
210
|
+
else
|
|
211
|
+
PandaPal::Organization.pluck(:name)
|
|
212
|
+
end
|
|
213
|
+
end
|
|
15
214
|
end
|
|
16
215
|
|
|
17
216
|
Rails.application.config.middleware.use Apartment::Elevators::Generic, lambda { |request|
|
data/config/routes.rb
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
data/lib/panda_pal/engine.rb
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|