open-loam 0.2.0 → 0.3.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.
Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +116 -0
  3. data/README.md +4 -2
  4. data/app/jobs/open_loam/event_log_prune_job.rb +15 -0
  5. data/app/jobs/open_loam/webhook_delivery_job.rb +10 -1
  6. data/app/models/open_loam/api_token.rb +27 -4
  7. data/app/models/open_loam/event_record.rb +42 -0
  8. data/app/models/open_loam/pending_action.rb +47 -3
  9. data/app/models/open_loam/sso_provider.rb +31 -0
  10. data/app/models/open_loam/webhook_endpoint.rb +14 -1
  11. data/app/views/open_loam/custom_fields/_fields.html.erb +4 -0
  12. data/lib/generators/open_loam/entity/templates/api_controller.rb +1 -0
  13. data/lib/generators/open_loam/entity/templates/controller.rb +11 -1
  14. data/lib/generators/open_loam/entity/templates/views/_form.html.erb +4 -1
  15. data/lib/generators/open_loam/entity/templates/views/index.html.erb +6 -2
  16. data/lib/generators/open_loam/entity/templates/views/show.html.erb +2 -0
  17. data/lib/generators/open_loam/install/install_generator.rb +11 -0
  18. data/lib/generators/open_loam/install/templates/AGENTS.md +5 -1
  19. data/lib/generators/open_loam/install/templates/admin/api_tokens_controller.rb +25 -7
  20. data/lib/generators/open_loam/install/templates/admin/api_tokens_index.html.erb +23 -0
  21. data/lib/generators/open_loam/install/templates/admin/base_controller.rb +63 -0
  22. data/lib/generators/open_loam/install/templates/admin/dashboard_controller.rb +2 -0
  23. data/lib/generators/open_loam/install/templates/admin/events_controller.rb +2 -0
  24. data/lib/generators/open_loam/install/templates/admin/history_controller.rb +26 -0
  25. data/lib/generators/open_loam/install/templates/admin/history_index.html.erb +1 -1
  26. data/lib/generators/open_loam/install/templates/admin/imports_controller.rb +13 -1
  27. data/lib/generators/open_loam/install/templates/admin/inbound_webhook_sources_controller.rb +1 -1
  28. data/lib/generators/open_loam/install/templates/admin/inbound_webhook_sources_new.html.erb +0 -2
  29. data/lib/generators/open_loam/install/templates/admin/mfa_controller.rb +5 -0
  30. data/lib/generators/open_loam/install/templates/admin/notifications_controller.rb +2 -0
  31. data/lib/generators/open_loam/install/templates/admin/perspectives_controller.rb +6 -0
  32. data/lib/generators/open_loam/install/templates/admin/progress_jobs_controller.rb +4 -0
  33. data/lib/generators/open_loam/install/templates/admin/search_controller.rb +2 -0
  34. data/lib/generators/open_loam/install/templates/admin/sessions_controller.rb +3 -4
  35. data/lib/generators/open_loam/install/templates/admin/sso_providers_index.html.erb +8 -1
  36. data/lib/generators/open_loam/install/templates/admin/sudo_controller.rb +3 -1
  37. data/lib/generators/open_loam/install/templates/api_base_controller.rb +43 -6
  38. data/lib/generators/open_loam/install/templates/import_job.rb +14 -1
  39. data/lib/generators/open_loam/install/templates/initializer.rb +14 -2
  40. data/lib/generators/open_loam/install/templates/migrations/create_open_loam_api_tokens.rb +2 -2
  41. data/lib/generators/open_loam/install/templates/migrations/create_open_loam_event_records.rb +13 -0
  42. data/lib/generators/open_loam/install/templates/migrations/create_open_loam_inbound_webhooks.rb +0 -1
  43. data/lib/generators/open_loam/install/templates/migrations/create_open_loam_sso_providers.rb +3 -0
  44. data/lib/open_loam/auth_throttle.rb +11 -3
  45. data/lib/open_loam/custom_field_index.rb +11 -8
  46. data/lib/open_loam/encryptable.rb +8 -3
  47. data/lib/open_loam/encryption/key_provider.rb +17 -1
  48. data/lib/open_loam/encryption.rb +47 -4
  49. data/lib/open_loam/engine.rb +8 -0
  50. data/lib/open_loam/errors.rb +10 -0
  51. data/lib/open_loam/event_log.rb +77 -0
  52. data/lib/open_loam/export.rb +6 -6
  53. data/lib/open_loam/import.rb +1 -4
  54. data/lib/open_loam/inbound_webhooks.rb +12 -11
  55. data/lib/open_loam/lifecycle.rb +29 -0
  56. data/lib/open_loam/mcp.rb +10 -1
  57. data/lib/open_loam/outbound_url.rb +100 -0
  58. data/lib/open_loam/policy.rb +16 -3
  59. data/lib/open_loam/search/token_driver.rb +1 -1
  60. data/lib/open_loam/sso/http_client.rb +15 -6
  61. data/lib/open_loam/sso.rb +23 -2
  62. data/lib/open_loam/version.rb +1 -1
  63. data/lib/open_loam.rb +2 -0
  64. data/lib/tasks/open_loam.rake +32 -0
  65. metadata +6 -1
data/lib/open_loam/sso.rb CHANGED
@@ -32,6 +32,11 @@ module OpenLoam
32
32
  # (cross-domain account takeover), so the flow refuses it.
33
33
  class DomainMismatchError < Error; end
34
34
 
35
+ # The provider has not proven it owns its `domain`, so it may not adopt an
36
+ # existing account. Ownership is stamped out of band by an operator
37
+ # (rake open_loam:sso:verify_domain).
38
+ class UnverifiedDomainError < Error; end
39
+
35
40
  class << self
36
41
  attr_writer :builder
37
42
 
@@ -60,11 +65,14 @@ module OpenLoam
60
65
  # OpenLoam::Membership.tenants_for): "which tenant's IdP owns this email
61
66
  # domain?" is asked at the sign-in page, before any tenant is chosen, so it
62
67
  # reaches across tenants via `unscoped` — something host code must never do.
68
+ #
69
+ # Verified providers only: the domain is manager-typed, so an unverified
70
+ # claim would capture every sign-in on a domain the tenant does not own.
63
71
  def provider_for(email:)
64
72
  domain = email.to_s.split("@").last.to_s.strip.downcase
65
73
  return nil if domain.blank?
66
74
 
67
- OpenLoam::SsoProvider.unscoped.where(domain: domain, active: true).first
75
+ OpenLoam::SsoProvider.unscoped.domain_verified.where(domain: domain, active: true).first
68
76
  end
69
77
 
70
78
  # Resolve verified IdP claims to a signed-in User, in the provider's tenant.
@@ -87,7 +95,7 @@ module OpenLoam
87
95
  # Resolve the user: the durable (provider, sub) identity first, then an
88
96
  # existing User by verified email (link), else just-in-time create.
89
97
  identity = OpenLoam::SsoIdentity.find_by(sso_provider_id: provider.id, sub: claims.sub)
90
- user = identity&.user || User.find_by(email: claims.email) || jit_create_user(claims)
98
+ user = identity&.user || link_or_create_user(provider, claims)
91
99
 
92
100
  # Re-map claims -> role on EVERY login (including a returning identity),
93
101
  # so an IdP role change takes effect.
@@ -106,6 +114,19 @@ module OpenLoam
106
114
 
107
115
  private
108
116
 
117
+ # `User` is global, so linking adopts an account this tenant did not create
118
+ # — allowed only once the provider proved it owns the domain. JIT-creating
119
+ # needs no proof: the new user lands in the claimant's own tenant.
120
+ def link_or_create_user(provider, claims)
121
+ existing = User.find_by(email: claims.email)
122
+ return jit_create_user(claims) if existing.nil?
123
+ return existing if provider.domain_verified?
124
+
125
+ raise UnverifiedDomainError,
126
+ "provider for #{provider.domain.inspect} has not proven it owns that domain — " \
127
+ "refusing to link the existing account #{claims.email.inspect}"
128
+ end
129
+
109
130
  def jit_create_user(claims)
110
131
  # SSO users authenticate at the IdP; the random password satisfies
111
132
  # has_secure_password and is unguessable, never used to log in.
@@ -1,3 +1,3 @@
1
1
  module OpenLoam
2
- VERSION = "0.2.0"
2
+ VERSION = "0.3.0"
3
3
  end
data/lib/open_loam.rb CHANGED
@@ -7,6 +7,7 @@ require "open_loam/lifecycle"
7
7
  require "open_loam/configs"
8
8
  require "open_loam/features"
9
9
  require "open_loam/auth_throttle"
10
+ require "open_loam/outbound_url"
10
11
  require "open_loam/dictionaries"
11
12
  require "open_loam/custom_field_index"
12
13
  require "open_loam/undo"
@@ -44,6 +45,7 @@ require "open_loam/business_rules"
44
45
  require "open_loam/webhooks"
45
46
  require "open_loam/inbound_webhooks"
46
47
  require "open_loam/durable_events"
48
+ require "open_loam/event_log"
47
49
  require "open_loam/engine" if defined?(Rails::Engine)
48
50
 
49
51
  module OpenLoam
@@ -103,6 +103,38 @@ namespace :open_loam do
103
103
  end
104
104
  end
105
105
 
106
+ namespace :sso do
107
+ # Domain ownership can only be granted from OUTSIDE the tenant: the manager
108
+ # who typed the domain is the party this check exists to constrain. Confirm
109
+ # ownership out of band (DNS TXT, a signed request from the domain's
110
+ # operator, an existing contract) BEFORE running this.
111
+ #
112
+ # bin/rails open_loam:sso:verify_domain[42]
113
+ desc "Mark an SSO provider's domain as proven-owned (operator only)"
114
+ task :verify_domain, %i[provider_id] => :environment do |_task, args|
115
+ provider = OpenLoam::SsoProvider.unscoped.find(args[:provider_id])
116
+ tenant = OpenLoam::Tenant.find(provider.tenant_id)
117
+ OpenLoam.as_tenant(tenant) { provider.verify_domain! }
118
+ puts "open_loam:sso:verify_domain — #{provider.domain} verified for tenant #{tenant.slug} (provider #{provider.id})."
119
+ end
120
+
121
+ desc "Revoke an SSO provider's domain verification"
122
+ task :unverify_domain, %i[provider_id] => :environment do |_task, args|
123
+ provider = OpenLoam::SsoProvider.unscoped.find(args[:provider_id])
124
+ tenant = OpenLoam::Tenant.find(provider.tenant_id)
125
+ OpenLoam.as_tenant(tenant) { provider.update!(domain_verified_at: nil) }
126
+ puts "open_loam:sso:unverify_domain — #{provider.domain} is no longer verified."
127
+ end
128
+
129
+ desc "List SSO providers and their domain-verification state"
130
+ task providers: :environment do
131
+ OpenLoam::SsoProvider.unscoped.order(:domain).each do |provider|
132
+ state = provider.domain_verified? ? "verified #{provider.domain_verified_at.to_fs(:db)}" : "UNVERIFIED"
133
+ puts format("%-6s %-30s tenant=%-6s %s", provider.id, provider.domain, provider.tenant_id, state)
134
+ end
135
+ end
136
+ end
137
+
106
138
  namespace :encryption do
107
139
  # Rotate a tenant's encrypted data under the current key: read each record's
108
140
  # encrypted fields (old key) and re-seal them (new key), one record at a
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: open-loam
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Grzegorz Smajdor
@@ -64,6 +64,7 @@ files:
64
64
  - README.md
65
65
  - app/jobs/open_loam/custom_field_reindex_job.rb
66
66
  - app/jobs/open_loam/event_delivery_job.rb
67
+ - app/jobs/open_loam/event_log_prune_job.rb
67
68
  - app/jobs/open_loam/event_redelivery_sweep_job.rb
68
69
  - app/jobs/open_loam/webhook_delivery_job.rb
69
70
  - app/models/open_loam/api_token.rb
@@ -78,6 +79,7 @@ files:
78
79
  - app/models/open_loam/dictionary.rb
79
80
  - app/models/open_loam/dictionary_entry.rb
80
81
  - app/models/open_loam/event_delivery.rb
82
+ - app/models/open_loam/event_record.rb
81
83
  - app/models/open_loam/field_definition.rb
82
84
  - app/models/open_loam/inbound_webhook_delivery.rb
83
85
  - app/models/open_loam/inbound_webhook_source.rb
@@ -210,6 +212,7 @@ files:
210
212
  - lib/generators/open_loam/install/templates/migrations/create_open_loam_dictionaries.rb
211
213
  - lib/generators/open_loam/install/templates/migrations/create_open_loam_dictionary_entries.rb
212
214
  - lib/generators/open_loam/install/templates/migrations/create_open_loam_event_deliveries.rb
215
+ - lib/generators/open_loam/install/templates/migrations/create_open_loam_event_records.rb
213
216
  - lib/generators/open_loam/install/templates/migrations/create_open_loam_field_definitions.rb
214
217
  - lib/generators/open_loam/install/templates/migrations/create_open_loam_inbound_webhooks.rb
215
218
  - lib/generators/open_loam/install/templates/migrations/create_open_loam_memberships.rb
@@ -256,6 +259,7 @@ files:
256
259
  - lib/open_loam/enrichers.rb
257
260
  - lib/open_loam/errors.rb
258
261
  - lib/open_loam/eval.rb
262
+ - lib/open_loam/event_log.rb
259
263
  - lib/open_loam/event_stream.rb
260
264
  - lib/open_loam/eventful.rb
261
265
  - lib/open_loam/events.rb
@@ -270,6 +274,7 @@ files:
270
274
  - lib/open_loam/mcp/server.rb
271
275
  - lib/open_loam/notifications.rb
272
276
  - lib/open_loam/open_api.rb
277
+ - lib/open_loam/outbound_url.rb
273
278
  - lib/open_loam/overrides.rb
274
279
  - lib/open_loam/pending_actions.rb
275
280
  - lib/open_loam/permissions.rb