panda_pal 5.6.12 → 5.7.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 29034766a62fed97c43d0e3ef2754e1f04a4d925b8a467bd78dda5a2f5d87f1e
4
- data.tar.gz: d4c3c29aa255a8311bbe00d1dbe0358f247425fa8ed1f7eddfca1ec7315897d5
3
+ metadata.gz: c17abc690e56e5bedffa292170eee88041e9d3214200a9bcf75571fc18b10a69
4
+ data.tar.gz: 40c41edfe6b016d6f9c8db8316fa44f1d7c9e7cff03ab9eb88fda74e6d695712
5
5
  SHA512:
6
- metadata.gz: e4ca709472f506cb84cfc9020b254e3ddafdf756f6dd83325f314910914878adfc1ea3363c8bca938f3933a27852205ccb9e1832df7a67a501bc442d4e3e3b80
7
- data.tar.gz: 72fe59902312a2da78350ab1a095808e8c1e902ca02e208474413fa06989f6bbb0f8c1174e929555b501674754b21f4ebdbfce7d73404f7ffe0d27595b7546ad
6
+ metadata.gz: 48ea51186371b9941434937ed23cfcf8b44f0726fd8d89cf280e7e65edc132702ccaec6e98917194c0c29d320f77f1218287906d9a3489b4efcf8fc8546d0332
7
+ data.tar.gz: 2005bc3182ffdf509da90ae5e29363a6bc79964e71a558386fb8c9ab0de8bf16a33f071ea0e40d7ab44bf5c9eb1f74549491b37eb12010bf6d0820b1acffe4eb
data/README.md CHANGED
@@ -29,7 +29,7 @@ org.install_lti(
29
29
  context: "account/self", # (Optional) Or "account/3", "course/1", etc
30
30
  exists: :error, # (Optional) Action to take if an LTI with the same Key already exists. Options are :error, :replace, :duplicate
31
31
  version: "v1p3", # (Optional, default `v1p3`) LTI Version. Accepts `v1p0` or `v1p3`.
32
- dedicated_deployment: false, # (Optional) If true, the Organization will be updated to link to a single deployment rather then to the general LTI Key. (experimental)
32
+ dedicated_deployment: false, # (Optional) If true, the Organization will be updated to link to a single deployment rather then to the general LTI Key. (experimental, LTI 1.3 only)
33
33
  )
34
34
  ```
35
35
 
@@ -1,7 +1,7 @@
1
1
  require 'jwt'
2
2
 
3
3
  module PandaPal
4
- class ApiCall < ActiveRecord::Base
4
+ class ApiCall < PandaPalRecord
5
5
  # TODO Add Rate Limiting?
6
6
 
7
7
  def self.decode_jwt(jwt)
@@ -1,7 +1,7 @@
1
1
  module PandaPal
2
2
  module OrganizationConcerns; end
3
3
 
4
- # Nwer versions of SymmetricEncryption introduced it's own version of attr_encrypted
4
+ # Newer versions of SymmetricEncryption introduced it's own version of attr_encrypted
5
5
  # that is completely incompatible with the attr_encrypted Gem that PandaPal uses.
6
6
  if defined?(::SymmetricEncryption::ActiveRecord::AttrEncrypted)
7
7
  module SkipSymmetricEncAttrEncrypted
@@ -21,16 +21,34 @@ module PandaPal
21
21
  end
22
22
  end
23
23
 
24
- class Organization < ActiveRecord::Base
24
+ class SettingsMarshaler
25
+ def self.load(data)
26
+ return nil unless data.present?
27
+ loaded = Marshal.load(data)
28
+ loaded = loaded.with_indifferent_access if loaded.is_a?(Hash) && !loaded.is_a?(HashWithIndifferentAccess)
29
+ loaded
30
+ end
31
+
32
+ def self.dump(obj)
33
+ Marshal.dump(obj)
34
+ end
35
+ end
36
+
37
+ class Organization < PandaPalRecord
25
38
  include SkipSymmetricEncAttrEncrypted if defined?(SkipSymmetricEncAttrEncrypted)
26
39
 
27
40
  include OrganizationConcerns::SettingsValidation
28
41
  include OrganizationConcerns::TaskScheduling if defined?(Sidekiq.schedule)
29
42
 
30
- serialize :settings, Hash
31
- attr_encrypted :settings, marshal: true, key: :encryption_key
43
+ attr_encrypted :settings, marshal: true, key: :encryption_key, marshaler: SettingsMarshaler
32
44
  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
33
45
 
46
+ alias_method "settings_panda_pal_super=", "settings="
47
+ def settings=(settings)
48
+ settings = settings.with_indifferent_access if settings.is_a?(Hash) && !settings.is_a?(HashWithIndifferentAccess)
49
+ self.settings_panda_pal_super = settings
50
+ end
51
+
34
52
  validates :key, uniqueness: { case_sensitive: false }, presence: true
35
53
  validates :secret, presence: true
36
54
  validates :name, uniqueness: { case_sensitive: false }, presence: true, format: { with: /\A[a-z0-9_]+\z/i }
@@ -84,10 +102,6 @@ module PandaPal
84
102
  end
85
103
  end
86
104
 
87
- def self.resolve_platform(hint)
88
- iss = hint["iss"]
89
- end
90
-
91
105
  def rename!(new_name)
92
106
  do_switch = Apartment::Tenant.current == name
93
107
  ActiveRecord::Base.connection.execute(
@@ -98,23 +112,56 @@ module PandaPal
98
112
  switch_tenant if do_switch
99
113
  end
100
114
 
101
- def platform_extensions(platform_type)
102
- return self unless defined?(platform_type::OrgExtension)
103
- return self if self.class < platform_type::OrgExtension
104
-
105
- scl = platform_type.org_subclass
106
- scl ? becomes(scl) : self
115
+ if !PandaPal.lti_options[:platform].present? || PandaPal.lti_options[:platform].is_a?(String)
116
+ CONST_PLATFORM_TYPE = Platform.resolve_platform_class(nil) rescue nil
117
+ else
118
+ CONST_PLATFORM_TYPE = nil
107
119
  end
108
120
 
109
- if !PandaPal.lti_options[:platform].present? || PandaPal.lti_options[:platform].is_a?(String)
110
- platform_cls = Platform.resolve_platform_class(nil) rescue nil
111
- if platform_cls && defined?(platform_cls::OrgExtension)
112
- include platform_cls::OrgExtension
121
+ # Include the Platform API...
122
+ if CONST_PLATFORM_TYPE
123
+ # ... directly if this is a single-platform tool
124
+ include CONST_PLATFORM_TYPE::OrgExtension if defined?(CONST_PLATFORM_TYPE::OrgExtension)
125
+ else
126
+ # ... via method_missing/delegation if this is a multi-platform tool
127
+ def respond_to_missing?(name, include_private = false)
128
+ if (self.class == PandaPal::Organization) && (plat_api = platform_api).present?
129
+ plat_api.respond_to?(name, include_private)
130
+ else
131
+ super
132
+ end
133
+ end
134
+
135
+ def method_missing(method, *args, **kwargs, &block)
136
+ if (self.class == PandaPal::Organization) && (plat_api = platform_api).present?
137
+ plat_api.send(method, *args, **kwargs, &block)
138
+ else
139
+ super
140
+ end
113
141
  end
114
142
  end
115
143
 
116
- PandaPal.resolved_extensions_for(self).each do |ext|
117
- include ext
144
+ # Extend a particular type of Platform API.
145
+ def self.extend_platform_api(platform_type = CONST_PLATFORM_TYPE, &blk)
146
+ scl = platform_type.organization_api
147
+ scl.class_eval(&blk)
148
+ end
149
+
150
+ # Retrieve the specified Platform API for this Organization.
151
+ # If only a single Platform is used (when lti_options[:platform] is a constant string or nil), passing the platform_type is unnecessary
152
+ #
153
+ # The API is currently an Organization subclass (using `becomes()`), but such may change.
154
+ def platform_api(platform_type = primary_platform)
155
+ scl = platform_type.organization_api
156
+ return self if scl == self.class
157
+ becomes(scl)
158
+ end
159
+
160
+ protected
161
+
162
+ # OrgExtension-Overridable method to allow multi-platform tool Orgs to implicitly include a Platform API
163
+ def primary_platform
164
+ CONST_PLATFORM_TYPE
118
165
  end
119
166
 
120
167
  private
@@ -126,5 +173,11 @@ module PandaPal
126
173
  def destroy_schema
127
174
  Apartment::Tenant.drop name
128
175
  end
176
+
177
+ public
178
+
179
+ PandaPal.resolved_extensions_for(self).each do |ext|
180
+ include ext
181
+ end
129
182
  end
130
183
  end
@@ -155,7 +155,12 @@ module PandaPal
155
155
  end
156
156
 
157
157
  if spec[:properties] != nil || spec[:allow_additional] != nil
158
- extra_keys = settings.keys - (spec[:properties]&.keys || [])
158
+ set_keys = settings.keys
159
+ expected_keys = spec[:properties]&.keys || []
160
+ expected_keys = expected_keys.map(&:to_s) if settings.is_a?(HashWithIndifferentAccess)
161
+
162
+ extra_keys = set_keys - expected_keys
163
+
159
164
  if extra_keys.present?
160
165
  if spec[:allow_additional].is_a?(Hash)
161
166
  extra_keys.each do |key|
@@ -42,7 +42,7 @@ module PandaPal
42
42
 
43
43
  hash.tap do |hash|
44
44
  kl = ' ' * (k.to_s.length - 4)
45
- hash[k.to_sym] = hash[k.to_s] = PandaPal::OrganizationConcerns::TaskScheduling.build_settings_entry(desc)
45
+ hash[k.to_sym] = PandaPal::OrganizationConcerns::TaskScheduling.build_settings_entry(desc)
46
46
  end
47
47
  end,
48
48
  }
@@ -0,0 +1,5 @@
1
+ module PandaPal
2
+ class PandaPalRecord < ActiveRecord::Base
3
+ self.abstract_class = true
4
+ end
5
+ end
@@ -44,13 +44,11 @@ module PandaPal
44
44
  def install_lti(host: nil, context: :root_account, version: 'v1p3', exists: :error, dedicated_deployment: false)
45
45
  raise "Automatically installing this LTI requires Bearcat." unless defined?(Bearcat)
46
46
 
47
- version = version.to_s
48
-
49
47
  run_callbacks :lti_install do
50
48
  ctype, cid = _parse_lti_context(context)
51
49
 
52
50
  if version == 'v1p0'
53
- existing_installs = _find_existing_installs(context, exists: exists) do |lti|
51
+ existing_installs = _find_existing_installs(exists: exists) do |lti|
54
52
  lti[:consumer_key] == self.key
55
53
  end
56
54
 
@@ -112,9 +110,6 @@ module PandaPal
112
110
  end
113
111
 
114
112
  ekey = existing_keys[0]
115
- # if ekey && exists == :error
116
- # raise "Tool with key #{self.key} already installed"
117
- # end
118
113
 
119
114
  lti_json_url = PandaPal::LaunchUrlHelpers.resolve_route(:v1p3_config_url, host: host)
120
115
  lti_json = JSON.parse(HTTParty.get(lti_json_url, format: :plain).body)
@@ -268,6 +263,7 @@ module PandaPal
268
263
 
269
264
  if defined?(Bearcat)
270
265
  def bearcat_client
266
+ # Less than ideal, but `canvas_sync_client` has been the long-adopted tradition so we check for it so that we can continue to drop-in new versions of PandaPal
271
267
  return canvas_sync_client if defined?(canvas_sync_client)
272
268
 
273
269
  Bearcat::Client.new(
@@ -72,11 +72,13 @@ module PandaPal
72
72
  raise "Unknown platform '#{platform}'"
73
73
  end
74
74
 
75
- def self.org_subclass
76
- return nil unless defined?(self::OrgExtension)
75
+ def self.organization_api
76
+ return PandaPal::Organization unless defined?(self::OrgExtension)
77
+ return PandaPal::Organization if PandaPal::Organization < self::OrgExtension
78
+
77
79
  oext = self::OrgExtension
78
80
 
79
- @org_subclass ||= Class.new(PandaPal::Organization) do
81
+ @organization_api ||= self::OrganizationApi ||= Class.new(PandaPal::Organization) do
80
82
  include oext
81
83
  end
82
84
  end
@@ -1,5 +1,5 @@
1
1
  module PandaPal
2
- class Session < ActiveRecord::Base
2
+ class Session < PandaPalRecord
3
3
  belongs_to :panda_pal_organization, class_name: 'PandaPal::Organization', optional: true
4
4
 
5
5
  after_initialize do
@@ -69,41 +69,105 @@ module PandaPal
69
69
  end
70
70
  end
71
71
 
72
- initializer "panda_pal.autoswitch" do |app|
72
+ initializer "panda_pal.prompts" do |app|
73
73
  class ::Object
74
- def _panda_pal_console_autoswitch_tenant
75
- org = nil
76
- if Rails.env.development?
77
- org = PandaPal::Organization.find_by(name: 'local') || PandaPal::Organization.first
78
- elsif PandaPal::Organization.count == 1
79
- org = PandaPal::Organization.first
74
+ def _panda_pal_console_app_name
75
+ app_class = Rails.application.class
76
+ app_name = app_class.respond_to?(:parent) ? app_class.parent : app_class.module_parent
77
+ end
78
+ end
79
+
80
+ if defined? IRB
81
+ module PandaPalIrbTimePrompt
82
+ def prompt(prompt, ltype, indent, line_no)
83
+ formatted = super(prompt, ltype, indent, line_no)
84
+ app_bit = Pry::Helpers::Text.cyan("#{_panda_pal_console_app_name}-#{Apartment::Tenant.current}")
85
+ "[#{app_bit}] #{formatted}"
80
86
  end
87
+ end
81
88
 
82
- if org
83
- org.switch_tenant
84
- puts "PandaPal: Auto Switched to tenant '#{org.name}'"
89
+ module ::IRB
90
+ class Irb
91
+ prepend PandaPalIrbTimePrompt
85
92
  end
86
- rescue => err
87
- puts "PadaPal: Error occurred auto-switching tenant: #{err}"
88
93
  end
89
94
  end
90
95
 
91
- module Rails::ConsoleMethods
92
- def self.included(base)
93
- _panda_pal_console_autoswitch_tenant
96
+ if defined?(Pry)
97
+ default_prompt = Pry::Prompt[:default]
98
+ env = Pry::Helpers::Text.red(Rails.env.upcase)
99
+
100
+ app_name = _panda_pal_console_app_name
101
+
102
+ Pry.config.prompt = Pry::Prompt.new(
103
+ 'custom',
104
+ 'my custom prompt',
105
+ [
106
+ ->(*args) {
107
+ app_bit = Pry::Helpers::Text.cyan("#{app_name}-#{Apartment::Tenant.current}")
108
+ "#{app_bit}#{default_prompt.wait_proc.call(*args)}"
109
+ },
110
+ ->(*args) {
111
+ app_bit = Pry::Helpers::Text.cyan("#{app_name}-#{Apartment::Tenant.current}")
112
+ "#{app_bit}#{default_prompt.incomplete_proc.call(*args)}"
113
+ },
114
+ ],
115
+ )
116
+ end
117
+
118
+ if defined? Rails::Console
119
+ module PandaPal::ConsoleExtPrefix
120
+ extend ActiveSupport::Concern
121
+
122
+ def start(*args)
123
+ print "\033];#{_panda_pal_console_app_name} Rails Console\007"
124
+ super
125
+ end
94
126
  end
127
+
128
+ Rails::Console.prepend(PandaPal::ConsoleExtPrefix)
95
129
  end
130
+ end
96
131
 
97
- if defined?(Pry)
98
- Pry.hooks.add_hook(:before_session, "switch PandaPal tenant") do |output, binding, pry|
99
- _panda_pal_console_autoswitch_tenant
132
+ initializer "panda_pal.autoswitch" do |app|
133
+ if defined? Rails::Console
134
+ module PandaPal::ConsoleExtAutoSwitch
135
+ extend ActiveSupport::Concern
136
+
137
+ def start(*args)
138
+ begin
139
+ org = nil
140
+ if Rails.env.development?
141
+ org = PandaPal::Organization.find_by(name: 'local') || PandaPal::Organization.first
142
+ elsif PandaPal::Organization.count == 1
143
+ org = PandaPal::Organization.first
144
+ end
145
+
146
+ if org
147
+ org.switch_tenant
148
+ puts "PandaPal: Auto Switched to tenant '#{org.name}'"
149
+ end
150
+ rescue => err
151
+ puts "PandaPal: Error occurred auto-switching tenant: #{err}"
152
+ end
153
+
154
+ super
155
+ end
100
156
  end
157
+
158
+ Rails::Console.prepend(PandaPal::ConsoleExtAutoSwitch)
101
159
  end
102
160
  end
103
161
 
104
162
  initializer "panda_pal.serialze_symbols" do |app|
105
163
  app.config.active_record.yaml_column_permitted_classes ||= []
106
- app.config.active_record.yaml_column_permitted_classes |= [Symbol, ActiveSupport::Duration]
164
+ app.config.active_record.yaml_column_permitted_classes |= [
165
+ Symbol,
166
+ ActiveSupport::Duration,
167
+ ActiveSupport::TimeWithZone,
168
+ ActiveSupport::TimeZone,
169
+ Time,
170
+ ]
107
171
  rescue
108
172
  end
109
173
 
@@ -1,3 +1,3 @@
1
1
  module PandaPal
2
- VERSION = "5.6.12"
2
+ VERSION = "5.7.0.beta1"
3
3
  end
@@ -30,5 +30,21 @@ module PandaPal
30
30
  org2 = build :panda_pal_organization, salesforce_id: 'salesforce'
31
31
  expect(org2.valid?).to be_falsey
32
32
  end
33
+
34
+ it 'stores setting as an indifferent Hash' do
35
+ org = create :panda_pal_organization
36
+ org.update!(settings: { a: 1 })
37
+
38
+ expect(Organization.last.settings).to be_a (ActiveSupport::HashWithIndifferentAccess)
39
+
40
+ org.settings = {}
41
+ expect(org.settings).to be_a (ActiveSupport::HashWithIndifferentAccess)
42
+ end
43
+
44
+ it 'stores settings' do
45
+ org = create :panda_pal_organization
46
+ org.update!(settings: { a: 1 })
47
+ expect(Organization.last.settings[:a]).to eql(1)
48
+ end
33
49
  end
34
50
  end
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: 5.6.12
4
+ version: 5.7.0.beta1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Instructure ProServe
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-05-02 00:00:00.000000000 Z
11
+ date: 2023-04-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -313,6 +313,7 @@ files:
313
313
  - app/models/panda_pal/organization.rb
314
314
  - app/models/panda_pal/organization_concerns/settings_validation.rb
315
315
  - app/models/panda_pal/organization_concerns/task_scheduling.rb
316
+ - app/models/panda_pal/panda_pal_record.rb
316
317
  - app/models/panda_pal/platform.rb
317
318
  - app/models/panda_pal/platform/canvas.rb
318
319
  - app/models/panda_pal/session.rb
@@ -403,9 +404,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
403
404
  version: '0'
404
405
  required_rubygems_version: !ruby/object:Gem::Requirement
405
406
  requirements:
406
- - - ">="
407
+ - - ">"
407
408
  - !ruby/object:Gem::Version
408
- version: '0'
409
+ version: 1.3.1
409
410
  requirements: []
410
411
  rubygems_version: 3.1.6
411
412
  signing_key: