activeadmin-oidc 2.0.1 → 2.1.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1a4d32669000d3de07114d54dc1c5d408899c1c8cf40e86b154e328ab08ed385
4
- data.tar.gz: d1f002c56666e32b794bc1024a37b8bb06e275642c625b198664febd7caf6805
3
+ metadata.gz: 8afe2cffbac6a3491a7bcef10a33b8e87385b2574237826c4932bf0dda809536
4
+ data.tar.gz: 3984564073d074906197fe8bc8101935cfe293a88d515db0f6b228b389bb144d
5
5
  SHA512:
6
- metadata.gz: 79d676e27f836c5d271925d067cb5a072450e237394b07a590ac988b779ff5200a39c79208170ec3ef930837b8ad46dbf0f97e8403b9899fb73c1a6983092ab9
7
- data.tar.gz: 13ac29d12fda8a44ba24aa992ac61d65f63c607894e576d66276cf18f2fa478f26b881b335bf93f767296c65c2c35a9d43b1bf76ddcb48d19078dd75ce94bb8f
6
+ metadata.gz: 9186f4de83c070356b1b980d42247ebdada9abbbbee0c8894a037d84487791bff7791617d0c31c19c36b7c95a7789640cded88a345da9d07720d4046869c2c65
7
+ data.tar.gz: e500884e14d9f88e8d08e3d082487410091557ea83a6fc3c7bedd17678e9a38bff0eb8627c16fd54c5cf961dfafe618ec5456451aa8c887fae682c07ee1d5436
@@ -38,18 +38,19 @@ module ActiveAdmin
38
38
  provider: ActiveAdmin::Oidc::Engine::PROVIDER_NAME.to_s
39
39
  ).call
40
40
 
41
- # Devise checks active_for_authentication? on session
42
- # deserialization but NOT on initial OmniAuth sign-in.
43
- # Guard here so disabled/locked users are rejected immediately.
44
- unless admin_user.active_for_authentication?
45
- message = admin_user.inactive_message
46
- flash[:alert] = I18n.t("devise.failure.#{message}", default: message.to_s)
47
- redirect_to after_omniauth_failure_path_for(resource_name)
48
- return
49
- end
50
-
51
41
  sign_in_and_redirect admin_user, event: :authentication
52
42
  set_flash_message(:notice, :success, kind: 'OIDC') if is_navigational_format?
43
+ rescue ActiveAdmin::Oidc::InactiveError => e
44
+ Rails.logger.warn("[activeadmin-oidc] inactive: #{e.inactive_message_key}")
45
+ # Fall back to the standard `inactive` translation rather
46
+ # than the raw symbol — custom keys like :locked_by_admin
47
+ # would otherwise leak host-internal state into a flash
48
+ # visible to unauthenticated visitors.
49
+ flash[:alert] = I18n.t(
50
+ "devise.failure.#{e.inactive_message_key}",
51
+ default: I18n.t("devise.failure.inactive")
52
+ )
53
+ redirect_to after_omniauth_failure_path_for(resource_name)
53
54
  rescue ActiveAdmin::Oidc::ProvisioningError => e
54
55
  Rails.logger.warn("[activeadmin-oidc] denial: #{e.message}")
55
56
  flash[:alert] = ActiveAdmin::Oidc.config.access_denied_message
@@ -78,14 +79,18 @@ module ActiveAdmin
78
79
  # `:database_authenticatable` is in the mapping's `used_helpers`,
79
80
  # so an OIDC-only model never gets it. The engine mounts
80
81
  # `new_<scope>_session_path` itself, but the helper lives on
81
- # whichever route set the mapping's `router_name` points at
82
- # main_app by default, or `<engine_name>` for hosts that mount
83
- # Devise inside a Rails engine. Replicate Devise's own dispatch
84
- # so the right context is asked.
82
+ # whichever route set Devise's URL helper dispatcher points at:
83
+ # the per-mapping `router_name` (set by
84
+ # `devise_for :scope, router_name: :engine`) when present,
85
+ # otherwise the global `Devise.available_router_name`
86
+ # (set by `Devise.router_name = :engine`), which defaults to
87
+ # `:main_app`. Replicate that dispatcher here so the helper is
88
+ # resolved on the right context (Rails.application proxy or
89
+ # mounted engine proxy).
85
90
  def after_omniauth_failure_path_for(scope)
86
- router_name = ::Devise.mappings[scope].router_name
87
- context = router_name ? send(router_name) : self
88
- context.public_send(:"new_#{scope}_session_path")
91
+ router_name = ::Devise.mappings[scope].router_name ||
92
+ ::Devise.available_router_name
93
+ send(router_name).public_send(:"new_#{scope}_session_path")
89
94
  end
90
95
  end
91
96
  end
@@ -5,7 +5,7 @@
5
5
  </h2>
6
6
 
7
7
  <%= button_to ActiveAdmin::Oidc.config.login_button_label,
8
- "/admin/auth/oidc",
8
+ "#{OmniAuth.config.path_prefix}/oidc",
9
9
  method: :post,
10
10
  class: "activeadmin-oidc-login-button w-full",
11
11
  form_class: 'formtastic',
@@ -15,10 +15,12 @@
15
15
  <div id="login">
16
16
  <h2><%= active_admin_application.site_title(self) %></h2>
17
17
 
18
- <%= button_to ActiveAdmin::Oidc.config.login_button_label,
19
- "/admin/auth/oidc",
20
- method: :post,
21
- class: "activeadmin-oidc-login-button",
22
- data: { turbo: false } %>
18
+ <%= form_tag "#{OmniAuth.config.path_prefix}/oidc",
19
+ method: :post,
20
+ class: "activeadmin-oidc-login-form formtastic",
21
+ data: { turbo: false } do %>
22
+ <%= submit_tag ActiveAdmin::Oidc.config.login_button_label,
23
+ class: "activeadmin-oidc-login-button" %>
24
+ <% end %>
23
25
  </div>
24
26
  <% end %>
@@ -35,11 +35,31 @@ module ActiveAdmin
35
35
  validate_claims!
36
36
 
37
37
  admin_user = find_or_adopt_or_build
38
+
39
+ # Retry path: a concurrent first sign-in inserted between our
40
+ # initial miss-and-build and our failed save. Return the
41
+ # winner's row verbatim — on_login already ran on our (now
42
+ # discarded) in-memory build, and re-firing it would double
43
+ # any host-side side effects (audit log, webhook, email).
44
+ return admin_user if @retried
45
+
38
46
  assign_base_attributes(admin_user)
39
47
 
40
48
  allowed = invoke_on_login(admin_user)
41
49
  raise ProvisioningError, denial_message unless allowed
42
50
 
51
+ # Devise's `active_for_authentication?` guard runs in the
52
+ # controller post-sign-in, but by then we've already saved
53
+ # the record. Hostile attempts where on_login flips an
54
+ # inactivity flag (e.g. enabled=false) would otherwise leave
55
+ # provisional rows in the DB on every try. Refuse before
56
+ # persisting. Raise the dedicated InactiveError so the
57
+ # controller can surface the model's I18n inactive_message
58
+ # instead of the generic denial flash.
59
+ unless admin_user.active_for_authentication?
60
+ raise InactiveError, admin_user.inactive_message
61
+ end
62
+
43
63
  save!(admin_user)
44
64
  admin_user
45
65
  rescue RetryProvisioning
@@ -84,6 +104,19 @@ module ActiveAdmin
84
104
  "Identity #{identity_value.inspect} is already linked to a different account (takeover guard)"
85
105
  end
86
106
 
107
+ # Adoption of a pre-existing row (provider/uid still nil) by
108
+ # an IdP-supplied identity value is a privilege-escalation
109
+ # vector when the IdP allows external / unverified emails:
110
+ # an attacker registers `ceo@example.com` at the IdP and
111
+ # adopts the seeded admin row. Refuse when the IdP explicitly
112
+ # marks the claim as unverified. (Absent claim → unchanged
113
+ # behaviour, since many IdPs don't ship `email_verified` at
114
+ # all.)
115
+ if @claims["email_verified"] == false
116
+ raise ProvisioningError,
117
+ "Identity #{identity_value.inspect} is not verified by the IdP — refusing adoption"
118
+ end
119
+
87
120
  identity_match.provider = @provider
88
121
  identity_match.uid = uid
89
122
  return identity_match
@@ -2,6 +2,6 @@
2
2
 
3
3
  module ActiveAdmin
4
4
  module Oidc
5
- VERSION = "2.0.1"
5
+ VERSION = "2.1.1"
6
6
  end
7
7
  end
@@ -28,6 +28,23 @@ module ActiveAdmin
28
28
  class ProvisioningError < Error; end
29
29
  class RetryProvisioning < Error; end
30
30
 
31
+ # Raised when active_for_authentication? rejects the user. Carries
32
+ # the model's inactive_message symbol so the controller can
33
+ # translate it via I18n (devise.failure.<symbol>) instead of
34
+ # falling back to the generic denial flash.
35
+ class InactiveError < ProvisioningError
36
+ attr_reader :inactive_message_key
37
+
38
+ # Devise's default inactive_message is :inactive. Hosts can
39
+ # override the method and legitimately return nil on some
40
+ # branches; fall back to :inactive so the controller never
41
+ # ends up with an empty flash.
42
+ def initialize(inactive_message_key)
43
+ @inactive_message_key = inactive_message_key.presence || :inactive
44
+ super(@inactive_message_key.to_s)
45
+ end
46
+ end
47
+
31
48
  class << self
32
49
  def config
33
50
  @config ||= Configuration.new
@@ -1,9 +1,11 @@
1
1
  <div id="login">
2
2
  <h2><%%= active_admin_application.site_title(self) %></h2>
3
3
 
4
- <%%= button_to ActiveAdmin::Oidc.config.login_button_label,
5
- "/admin/auth/oidc",
6
- method: :post,
7
- class: "activeadmin-oidc-login-button",
8
- data: { turbo: false } %>
4
+ <%%= form_tag "#{OmniAuth.config.path_prefix}/oidc",
5
+ method: :post,
6
+ class: "activeadmin-oidc-login-form formtastic",
7
+ data: { turbo: false } do %>
8
+ <%%= submit_tag ActiveAdmin::Oidc.config.login_button_label,
9
+ class: "activeadmin-oidc-login-button" %>
10
+ <%% end %>
9
11
  </div>
@@ -4,7 +4,7 @@
4
4
  </h2>
5
5
 
6
6
  <%%= button_to ActiveAdmin::Oidc.config.login_button_label,
7
- "/admin/auth/oidc",
7
+ "#{OmniAuth.config.path_prefix}/oidc",
8
8
  method: :post,
9
9
  class: "activeadmin-oidc-login-button w-full",
10
10
  form_class: 'formtastic',
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activeadmin-oidc
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.1
4
+ version: 2.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Igor Fedoronchuk
@@ -33,42 +33,42 @@ dependencies:
33
33
  name: devise
34
34
  requirement: !ruby/object:Gem::Requirement
35
35
  requirements:
36
- - - ">="
36
+ - - "~>"
37
37
  - !ruby/object:Gem::Version
38
38
  version: '5.0'
39
39
  type: :runtime
40
40
  prerelease: false
41
41
  version_requirements: !ruby/object:Gem::Requirement
42
42
  requirements:
43
- - - ">="
43
+ - - "~>"
44
44
  - !ruby/object:Gem::Version
45
45
  version: '5.0'
46
46
  - !ruby/object:Gem::Dependency
47
47
  name: omniauth
48
48
  requirement: !ruby/object:Gem::Requirement
49
49
  requirements:
50
- - - ">="
50
+ - - "~>"
51
51
  - !ruby/object:Gem::Version
52
52
  version: '2.1'
53
53
  type: :runtime
54
54
  prerelease: false
55
55
  version_requirements: !ruby/object:Gem::Requirement
56
56
  requirements:
57
- - - ">="
57
+ - - "~>"
58
58
  - !ruby/object:Gem::Version
59
59
  version: '2.1'
60
60
  - !ruby/object:Gem::Dependency
61
61
  name: omniauth-rails_csrf_protection
62
62
  requirement: !ruby/object:Gem::Requirement
63
63
  requirements:
64
- - - ">="
64
+ - - "~>"
65
65
  - !ruby/object:Gem::Version
66
66
  version: '1.0'
67
67
  type: :runtime
68
68
  prerelease: false
69
69
  version_requirements: !ruby/object:Gem::Requirement
70
70
  requirements:
71
- - - ">="
71
+ - - "~>"
72
72
  - !ruby/object:Gem::Version
73
73
  version: '1.0'
74
74
  - !ruby/object:Gem::Dependency
@@ -78,6 +78,9 @@ dependencies:
78
78
  - - ">="
79
79
  - !ruby/object:Gem::Version
80
80
  version: '0.6'
81
+ - - "<"
82
+ - !ruby/object:Gem::Version
83
+ version: '1'
81
84
  type: :runtime
82
85
  prerelease: false
83
86
  version_requirements: !ruby/object:Gem::Requirement
@@ -85,6 +88,9 @@ dependencies:
85
88
  - - ">="
86
89
  - !ruby/object:Gem::Version
87
90
  version: '0.6'
91
+ - - "<"
92
+ - !ruby/object:Gem::Version
93
+ version: '1'
88
94
  - !ruby/object:Gem::Dependency
89
95
  name: rails
90
96
  requirement: !ruby/object:Gem::Requirement
@@ -92,6 +98,9 @@ dependencies:
92
98
  - - ">="
93
99
  - !ruby/object:Gem::Version
94
100
  version: '7.2'
101
+ - - "<"
102
+ - !ruby/object:Gem::Version
103
+ version: '9'
95
104
  type: :runtime
96
105
  prerelease: false
97
106
  version_requirements: !ruby/object:Gem::Requirement
@@ -99,60 +108,63 @@ dependencies:
99
108
  - - ">="
100
109
  - !ruby/object:Gem::Version
101
110
  version: '7.2'
111
+ - - "<"
112
+ - !ruby/object:Gem::Version
113
+ version: '9'
102
114
  - !ruby/object:Gem::Dependency
103
115
  name: rspec-rails
104
116
  requirement: !ruby/object:Gem::Requirement
105
117
  requirements:
106
- - - ">="
118
+ - - "~>"
107
119
  - !ruby/object:Gem::Version
108
120
  version: '6.0'
109
121
  type: :development
110
122
  prerelease: false
111
123
  version_requirements: !ruby/object:Gem::Requirement
112
124
  requirements:
113
- - - ">="
125
+ - - "~>"
114
126
  - !ruby/object:Gem::Version
115
127
  version: '6.0'
116
128
  - !ruby/object:Gem::Dependency
117
129
  name: capybara
118
130
  requirement: !ruby/object:Gem::Requirement
119
131
  requirements:
120
- - - ">="
132
+ - - "~>"
121
133
  - !ruby/object:Gem::Version
122
134
  version: '3.40'
123
135
  type: :development
124
136
  prerelease: false
125
137
  version_requirements: !ruby/object:Gem::Requirement
126
138
  requirements:
127
- - - ">="
139
+ - - "~>"
128
140
  - !ruby/object:Gem::Version
129
141
  version: '3.40'
130
142
  - !ruby/object:Gem::Dependency
131
143
  name: webmock
132
144
  requirement: !ruby/object:Gem::Requirement
133
145
  requirements:
134
- - - ">="
146
+ - - "~>"
135
147
  - !ruby/object:Gem::Version
136
148
  version: '3.19'
137
149
  type: :development
138
150
  prerelease: false
139
151
  version_requirements: !ruby/object:Gem::Requirement
140
152
  requirements:
141
- - - ">="
153
+ - - "~>"
142
154
  - !ruby/object:Gem::Version
143
155
  version: '3.19'
144
156
  - !ruby/object:Gem::Dependency
145
157
  name: jwt
146
158
  requirement: !ruby/object:Gem::Requirement
147
159
  requirements:
148
- - - ">="
160
+ - - "~>"
149
161
  - !ruby/object:Gem::Version
150
162
  version: '2.7'
151
163
  type: :development
152
164
  prerelease: false
153
165
  version_requirements: !ruby/object:Gem::Requirement
154
166
  requirements:
155
- - - ">="
167
+ - - "~>"
156
168
  - !ruby/object:Gem::Version
157
169
  version: '2.7'
158
170
  - !ruby/object:Gem::Dependency
@@ -162,6 +174,9 @@ dependencies:
162
174
  - - ">="
163
175
  - !ruby/object:Gem::Version
164
176
  version: '1.7'
177
+ - - "<"
178
+ - !ruby/object:Gem::Version
179
+ version: '3'
165
180
  type: :development
166
181
  prerelease: false
167
182
  version_requirements: !ruby/object:Gem::Requirement
@@ -169,60 +184,63 @@ dependencies:
169
184
  - - ">="
170
185
  - !ruby/object:Gem::Version
171
186
  version: '1.7'
187
+ - - "<"
188
+ - !ruby/object:Gem::Version
189
+ version: '3'
172
190
  - !ruby/object:Gem::Dependency
173
191
  name: rake
174
192
  requirement: !ruby/object:Gem::Requirement
175
193
  requirements:
176
- - - ">="
194
+ - - "~>"
177
195
  - !ruby/object:Gem::Version
178
196
  version: '13.0'
179
197
  type: :development
180
198
  prerelease: false
181
199
  version_requirements: !ruby/object:Gem::Requirement
182
200
  requirements:
183
- - - ">="
201
+ - - "~>"
184
202
  - !ruby/object:Gem::Version
185
203
  version: '13.0'
186
204
  - !ruby/object:Gem::Dependency
187
205
  name: rubocop
188
206
  requirement: !ruby/object:Gem::Requirement
189
207
  requirements:
190
- - - ">="
208
+ - - "~>"
191
209
  - !ruby/object:Gem::Version
192
210
  version: '1.60'
193
211
  type: :development
194
212
  prerelease: false
195
213
  version_requirements: !ruby/object:Gem::Requirement
196
214
  requirements:
197
- - - ">="
215
+ - - "~>"
198
216
  - !ruby/object:Gem::Version
199
217
  version: '1.60'
200
218
  - !ruby/object:Gem::Dependency
201
219
  name: rubocop-rails
202
220
  requirement: !ruby/object:Gem::Requirement
203
221
  requirements:
204
- - - ">="
222
+ - - "~>"
205
223
  - !ruby/object:Gem::Version
206
224
  version: '2.20'
207
225
  type: :development
208
226
  prerelease: false
209
227
  version_requirements: !ruby/object:Gem::Requirement
210
228
  requirements:
211
- - - ">="
229
+ - - "~>"
212
230
  - !ruby/object:Gem::Version
213
231
  version: '2.20'
214
232
  - !ruby/object:Gem::Dependency
215
233
  name: rubocop-rspec
216
234
  requirement: !ruby/object:Gem::Requirement
217
235
  requirements:
218
- - - ">="
236
+ - - "~>"
219
237
  - !ruby/object:Gem::Version
220
238
  version: '2.25'
221
239
  type: :development
222
240
  prerelease: false
223
241
  version_requirements: !ruby/object:Gem::Requirement
224
242
  requirements:
225
- - - ">="
243
+ - - "~>"
226
244
  - !ruby/object:Gem::Version
227
245
  version: '2.25'
228
246
  description: |