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 +4 -4
- data/app/controllers/active_admin/oidc/devise/omniauth_callbacks_controller.rb +22 -17
- data/app/views/active_admin/devise/sessions/new.html.erb +8 -6
- data/lib/activeadmin/oidc/user_provisioner.rb +33 -0
- data/lib/activeadmin/oidc/version.rb +1 -1
- data/lib/activeadmin-oidc.rb +17 -0
- data/lib/generators/active_admin/oidc/install/templates/sessions_new.html.erb +7 -5
- data/lib/generators/active_admin/oidc/install/templates/sessions_new_v4.html.erb +1 -1
- metadata +41 -23
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8afe2cffbac6a3491a7bcef10a33b8e87385b2574237826c4932bf0dda809536
|
|
4
|
+
data.tar.gz: 3984564073d074906197fe8bc8101935cfe293a88d515db0f6b228b389bb144d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
|
82
|
-
#
|
|
83
|
-
#
|
|
84
|
-
#
|
|
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
|
-
|
|
88
|
-
|
|
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
|
-
"/
|
|
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
|
-
<%=
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
data/lib/activeadmin-oidc.rb
CHANGED
|
@@ -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
|
-
<%%=
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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>
|
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.
|
|
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: |
|