atomic_tenant 1.4.1 → 1.5.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/lib/atomic_tenant/current_application_instance_middleware.rb +7 -1
- data/lib/atomic_tenant/deployment_manager/abstract_auto_create_platform_guid_strategy.rb +124 -0
- data/lib/atomic_tenant/exceptions.rb +1 -0
- data/lib/atomic_tenant/version.rb +1 -1
- data/lib/atomic_tenant.rb +4 -0
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1040533dd529e7e1f40ddeb45dd404ded2fa9fa493ecf0befc9580b118698c89
|
4
|
+
data.tar.gz: c614b1b32f09c29987d88a77929b9e0f90dc8f9fc9fc1b29f6cb8aaa6097a932
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2d7de5baefd5dd1a04ccc52323db67706ec7548b1aad81a0695e968adad9139f0419d68247000584d9c1339d7bb98de56ed6aa5ad721df51dfb0addab373b40b
|
7
|
+
data.tar.gz: 3ef28df192ac02f32d0b72dfd441d91864b036ea11014317a14d927de545eb20664788509460b4775c5158c293eba154175997938a116bc54b89368c1ed16a2e
|
@@ -21,12 +21,18 @@ module AtomicTenant
|
|
21
21
|
elsif env['atomic.validated.id_token'].present?
|
22
22
|
|
23
23
|
custom_strategies = AtomicTenant.custom_strategies || []
|
24
|
+
custom_fallback_strategies = AtomicTenant.custom_fallback_strategies || []
|
24
25
|
default_strategies = [
|
25
26
|
AtomicTenant::DeploymentManager::PlatformGuidStrategy.new,
|
26
27
|
AtomicTenant::DeploymentManager::ClientIdStrategy.new
|
27
28
|
]
|
28
29
|
|
29
|
-
deployment_manager = AtomicTenant::DeploymentManager::DeploymentManager.new(
|
30
|
+
deployment_manager = AtomicTenant::DeploymentManager::DeploymentManager.new([
|
31
|
+
*custom_strategies,
|
32
|
+
*default_strategies,
|
33
|
+
*custom_fallback_strategies
|
34
|
+
])
|
35
|
+
|
30
36
|
decoded_token = env['atomic.validated.decoded_id_token']
|
31
37
|
iss = env['atomic.validated.decoded_id_token']['iss']
|
32
38
|
deployment_id = env['atomic.validated.decoded_id_token'][AtomicLti::Definitions::DEPLOYMENT_ID]
|
@@ -0,0 +1,124 @@
|
|
1
|
+
require 'uri'
|
2
|
+
|
3
|
+
module AtomicTenant
|
4
|
+
module DeploymentManager
|
5
|
+
class AbstractAutoCreatePlatformGuidStrategy < DeploymentManagerStrategy
|
6
|
+
TRUSTED_ISSUERS = [
|
7
|
+
%r|^https://canvas\.instructure\.com$|,
|
8
|
+
%r|^https://[a-z0-9.-]+\.brightspace\.com$|,
|
9
|
+
%r|^https://blackboard\.com$|,
|
10
|
+
].freeze
|
11
|
+
|
12
|
+
def name
|
13
|
+
raise NotImplementedError, "Subclasses must implement #name"
|
14
|
+
end
|
15
|
+
|
16
|
+
def call(decoded_id_token:)
|
17
|
+
issuer = decoded_id_token["iss"]
|
18
|
+
platform_guid = decoded_id_token.dig(AtomicLti::Definitions::TOOL_PLATFORM_CLAIM, "guid")
|
19
|
+
target_link_uri = decoded_id_token[AtomicLti::Definitions::TARGET_LINK_URI_CLAIM]
|
20
|
+
|
21
|
+
if !platform_guid.present? || !target_link_uri.present?
|
22
|
+
return AtomicTenant::DeploymentManager::DeploymentStrategyResult.new()
|
23
|
+
end
|
24
|
+
|
25
|
+
uri = URI.parse(target_link_uri)
|
26
|
+
application_key = uri.host&.split('.')&.first
|
27
|
+
return AtomicTenant::DeploymentManager::DeploymentStrategyResult.new() if !application_key.present?
|
28
|
+
|
29
|
+
app = Application.find_by(key: application_key)
|
30
|
+
return AtomicTenant::DeploymentManager::DeploymentStrategyResult.new() if app.nil?
|
31
|
+
|
32
|
+
if !TRUSTED_ISSUERS.any? { |pattern| issuer.match?(pattern) }
|
33
|
+
existing_app_instance_count = AtomicTenant::LtiDeployment
|
34
|
+
.joins(:application_instance)
|
35
|
+
.where(
|
36
|
+
iss: issuer,
|
37
|
+
application_instances: { application_id: app.id },
|
38
|
+
).distinct.count(:application_instance_id)
|
39
|
+
|
40
|
+
if existing_app_instance_count >= AtomicTenant.untrusted_iss_tenant_limit
|
41
|
+
raise AtomicTenant::Exceptions::OnboardingException, "The issuer #{issuer} has reached the limit of #{AtomicTenant.untrusted_iss_tenant_limit} unique tenants for the application #{application_key}."
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
site_url = extract_site_url(decoded_id_token)
|
46
|
+
|
47
|
+
app_inst = find_application_instance(app, site_url, issuer, platform_guid)
|
48
|
+
app_inst ||= maybe_create_application_instance(app, site_url, issuer, platform_guid)
|
49
|
+
pin = pin_platform_guid(issuer, platform_guid, app.id, app_inst.id)
|
50
|
+
AtomicTenant::DeploymentManager::DeploymentStrategyResult.new(application_instance_id: pin.application_instance_id)
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def find_application_instance(current_application, site_url, issuer, platform_guid)
|
56
|
+
raise NotImplementedError, "Subclasses must implement #find_application_instance"
|
57
|
+
end
|
58
|
+
|
59
|
+
def create_application_instance(app, site_url, issuer, platform_guid)
|
60
|
+
raise NotImplementedError, "Subclasses must implement #create_application_instance"
|
61
|
+
end
|
62
|
+
|
63
|
+
def maybe_create_application_instance(app, site_url, issuer, platform_guid)
|
64
|
+
ActiveRecord::Base.transaction do
|
65
|
+
create_application_instance(app, site_url, issuer, platform_guid)
|
66
|
+
rescue ActiveRecord::RecordNotUnique
|
67
|
+
# If we get a RecordNotUnique error, it means another process created the instance concurrently.
|
68
|
+
find_application_instance(app, site_url, issuer, platform_guid)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# Pin platform guid, handling concurrent launches both trying to pin the same
|
73
|
+
# platform guid at the same time.
|
74
|
+
def pin_platform_guid(iss, platform_guid, application_id, application_instance_id)
|
75
|
+
begin
|
76
|
+
AtomicTenant::PinnedPlatformGuid.create!(
|
77
|
+
iss:,
|
78
|
+
platform_guid:,
|
79
|
+
application_id:,
|
80
|
+
application_instance_id:,
|
81
|
+
)
|
82
|
+
rescue ActiveRecord::RecordNotUnique
|
83
|
+
AtomicTenant::PinnedPlatformGuid.find_by!(
|
84
|
+
iss:,
|
85
|
+
platform_guid:,
|
86
|
+
application_id:,
|
87
|
+
application_instance_id:,
|
88
|
+
)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def extract_site_url(decoded_id_token)
|
93
|
+
platform_claim = decoded_id_token[AtomicLti::Definitions::TOOL_PLATFORM_CLAIM]
|
94
|
+
product_family_code = platform_claim["product_family_code"]
|
95
|
+
|
96
|
+
if product_family_code == "canvas"
|
97
|
+
canvas_domain = decoded_id_token.dig(AtomicLti::Definitions::CUSTOM_CLAIM, "canvas_api_domain")
|
98
|
+
if canvas_domain.blank?
|
99
|
+
raise AtomicTenant::Exceptions::OnboardingException, "Missing canvas_api_domain claim from canvas launch"
|
100
|
+
end
|
101
|
+
|
102
|
+
ensure_https(canvas_domain)
|
103
|
+
elsif product_family_code == "BlackboardLearn"
|
104
|
+
blackboard_url = platform_claim["url"]
|
105
|
+
|
106
|
+
if blackboard_url.blank?
|
107
|
+
raise AtomicTenant::Exceptions::OnboardingException, "Missing url in platform claim from blackboard launch"
|
108
|
+
end
|
109
|
+
|
110
|
+
ensure_https(blackboard_url)
|
111
|
+
else
|
112
|
+
decoded_id_token["iss"]
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def ensure_https(url)
|
117
|
+
return nil if url.blank?
|
118
|
+
|
119
|
+
url = "https://#{url}" unless url.start_with?("http")
|
120
|
+
url.gsub("http://", "https://")
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
data/lib/atomic_tenant.rb
CHANGED
@@ -3,6 +3,7 @@ require 'atomic_tenant/deployment_manager/deployment_manager'
|
|
3
3
|
require 'atomic_tenant/deployment_manager/platform_guid_strategy'
|
4
4
|
require 'atomic_tenant/deployment_manager/client_id_strategy'
|
5
5
|
require 'atomic_tenant/deployment_manager/deployment_manager_strategy'
|
6
|
+
require 'atomic_tenant/deployment_manager/abstract_auto_create_platform_guid_strategy'
|
6
7
|
require 'atomic_tenant/engine'
|
7
8
|
require 'atomic_tenant/current_application_instance_middleware'
|
8
9
|
require 'atomic_tenant/tenant_switching'
|
@@ -12,6 +13,9 @@ require 'atomic_tenant/active_job'
|
|
12
13
|
|
13
14
|
module AtomicTenant
|
14
15
|
mattr_accessor :custom_strategies
|
16
|
+
mattr_accessor :custom_fallback_strategies
|
17
|
+
|
18
|
+
mattr_accessor :untrusted_iss_tenant_limit, default: 100
|
15
19
|
|
16
20
|
mattr_accessor :jwt_secret
|
17
21
|
mattr_accessor :jwt_aud
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: atomic_tenant
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nick Benoit
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-
|
11
|
+
date: 2025-10-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: atomic_lti
|
@@ -87,6 +87,7 @@ files:
|
|
87
87
|
- lib/atomic_tenant/active_job.rb
|
88
88
|
- lib/atomic_tenant/canvas_content_migration.rb
|
89
89
|
- lib/atomic_tenant/current_application_instance_middleware.rb
|
90
|
+
- lib/atomic_tenant/deployment_manager/abstract_auto_create_platform_guid_strategy.rb
|
90
91
|
- lib/atomic_tenant/deployment_manager/client_id_strategy.rb
|
91
92
|
- lib/atomic_tenant/deployment_manager/deployment_manager.rb
|
92
93
|
- lib/atomic_tenant/deployment_manager/deployment_manager_strategy.rb
|
@@ -118,7 +119,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
118
119
|
- !ruby/object:Gem::Version
|
119
120
|
version: '0'
|
120
121
|
requirements: []
|
121
|
-
rubygems_version: 3.5.
|
122
|
+
rubygems_version: 3.5.11
|
122
123
|
signing_key:
|
123
124
|
specification_version: 4
|
124
125
|
summary: Summary of AtomicTenant.
|