masks 0.10.0 → 0.12.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/CHANGELOG.md +63 -0
- data/README.md +28 -341
- data/app/controllers/masks/rails/handshakes_controller.rb +2 -2
- data/app/controllers/masks/rails/logouts_controller.rb +11 -8
- data/app/controllers/masks/rails/sessions_controller.rb +48 -6
- data/config/routes.rb +1 -0
- data/lib/masks/client/device.rb +47 -0
- data/lib/masks/client/embedded.rb +125 -0
- data/lib/masks/client/enrollment.rb +33 -0
- data/lib/masks/client/errors.rb +6 -0
- data/lib/masks/client/handshake.rb +12 -4
- data/lib/masks/client/http.rb +87 -24
- data/lib/masks/client/issuer.rb +2 -1
- data/lib/masks/client/key.rb +100 -0
- data/lib/masks/client/logout.rb +5 -1
- data/lib/masks/client/nonces.rb +31 -0
- data/lib/masks/client/proof.rb +7 -1
- data/lib/masks/client/rack.rb +8 -0
- data/lib/masks/client/registration.rb +32 -15
- data/lib/masks/client/resource.rb +1 -1
- data/lib/masks/client/session.rb +120 -11
- data/lib/masks/client/tokens.rb +4 -0
- data/lib/masks/client/transport.rb +114 -0
- data/lib/masks/client/verifier.rb +10 -2
- data/lib/masks/client.rb +21 -4
- data/lib/masks/rails/authentication.rb +115 -35
- data/lib/masks/rails/cache.rb +173 -0
- data/lib/masks/rails/configuration.rb +27 -12
- data/lib/masks/rails/protected_resource.rb +13 -0
- data/lib/masks/rails/renewal.rb +54 -0
- data/lib/masks/rails/session_cookies.rb +145 -0
- data/lib/masks/rails.rb +5 -4
- data/lib/masks/version.rb +1 -1
- data/lib/masks.rb +0 -1
- metadata +10 -1
|
@@ -7,12 +7,17 @@ module Masks
|
|
|
7
7
|
|
|
8
8
|
included do
|
|
9
9
|
if respond_to?(:helper_method)
|
|
10
|
-
helper_method :masked, :masks_login_url
|
|
10
|
+
helper_method :masked, :masks_login_url
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
if respond_to?(:rescue_from) && method_defined?(:render)
|
|
14
|
+
rescue_from Masks::Rails::Cache::Unavailable, with: :masks_cache_unavailable
|
|
11
15
|
end
|
|
12
16
|
end
|
|
13
17
|
|
|
14
18
|
REQUESTS = "masks_requests".freeze
|
|
15
19
|
HANDSHAKES = "masks_handshakes".freeze
|
|
20
|
+
FAREWELLS = "masks_farewells".freeze
|
|
16
21
|
STEPPED_UP = "masks_stepped_up".freeze
|
|
17
22
|
STEP_UP_WITHIN = 300
|
|
18
23
|
|
|
@@ -28,6 +33,10 @@ module Masks
|
|
|
28
33
|
@masks_handshakes ||= masks_tracker(HANDSHAKES)
|
|
29
34
|
end
|
|
30
35
|
|
|
36
|
+
def masks_farewells
|
|
37
|
+
masks_tracker(FAREWELLS)
|
|
38
|
+
end
|
|
39
|
+
|
|
31
40
|
def masks_tracker(key)
|
|
32
41
|
Masks::Client::Tracker.new(Masks::Client::Stores::Session.new(session, key))
|
|
33
42
|
end
|
|
@@ -35,24 +44,42 @@ module Masks
|
|
|
35
44
|
def masks_tokens
|
|
36
45
|
return @masks_tokens if defined?(@masks_tokens)
|
|
37
46
|
|
|
38
|
-
|
|
47
|
+
held = masks_held
|
|
48
|
+
|
|
49
|
+
if held.present? && masks_departed?(held)
|
|
50
|
+
masks_forget
|
|
51
|
+
return nil
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
@masks_tokens = Masks::Client::Tokens.from_h(held)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def masks_cache
|
|
58
|
+
@masks_cache ||= Masks::Rails::Cache.new(masks_config.cache)
|
|
39
59
|
end
|
|
40
60
|
|
|
41
61
|
def masks_store(tokens, identity: nil)
|
|
42
62
|
held = tokens.to_h.except("id_token")
|
|
63
|
+
held.delete("token_type") if held["token_type"] == "Bearer"
|
|
43
64
|
held["identity"] = identity&.slice(*IDENTITY) || masks_held["identity"]
|
|
65
|
+
held["signed_in_at"] = masks_held["signed_in_at"] || Masks::Rails::Cache.now
|
|
44
66
|
|
|
45
|
-
|
|
67
|
+
masks_session_cookies.write(held.compact)
|
|
68
|
+
@masks_held = held.compact
|
|
46
69
|
@masks_tokens = tokens
|
|
47
70
|
@masked = nil
|
|
48
71
|
end
|
|
49
72
|
|
|
50
73
|
def masks_held
|
|
51
|
-
|
|
74
|
+
@masks_held ||= masks_session_cookies.read || {}
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def masks_session_cookies
|
|
78
|
+
Masks::Rails::SessionCookies.new(request, masks_config.session_key)
|
|
52
79
|
end
|
|
53
80
|
|
|
54
81
|
IDENTITY = [
|
|
55
|
-
"sub", "name", "preferred_username", "email", "email_verified", "tenant",
|
|
82
|
+
"sub", "sid", "name", "preferred_username", "email", "email_verified", "tenant",
|
|
56
83
|
"picture", Masks::Client::Claims::AVATARS, Masks::Client::Claims::ORGANIZATION,
|
|
57
84
|
Masks::Client::Claims::ORGANIZATIONS
|
|
58
85
|
].freeze
|
|
@@ -64,7 +91,8 @@ module Masks
|
|
|
64
91
|
end
|
|
65
92
|
|
|
66
93
|
def masks_forget
|
|
67
|
-
|
|
94
|
+
masks_session_cookies.clear
|
|
95
|
+
@masks_held = {}
|
|
68
96
|
@masks_tokens = nil
|
|
69
97
|
@masked = nil
|
|
70
98
|
end
|
|
@@ -124,22 +152,34 @@ module Masks
|
|
|
124
152
|
end
|
|
125
153
|
|
|
126
154
|
def masks_refresh!
|
|
127
|
-
return false unless masks_configured?
|
|
128
|
-
return false if masks_tokens&.refresh_token.nil?
|
|
155
|
+
return false unless masks_configured? && masks_tokens
|
|
129
156
|
|
|
130
|
-
|
|
131
|
-
masks_tokens.refresh_token,
|
|
132
|
-
resource: masks_config.resource_for(request)
|
|
133
|
-
)
|
|
157
|
+
spent = masks_tokens.refresh_token
|
|
134
158
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
159
|
+
if spent.nil?
|
|
160
|
+
masks_forget
|
|
161
|
+
return false
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
renewal = begin
|
|
165
|
+
masks_cache.renew(masks_config.issuer_for(request).to_s, spent) { masks_renewal(spent) }
|
|
166
|
+
rescue Masks::Rails::Cache::Unavailable
|
|
167
|
+
Masks::Rails::Renewal.unreachable
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
case renewal.outcome
|
|
171
|
+
when :renewed
|
|
172
|
+
masks_store(renewal.tokens, identity: renewal.identity)
|
|
173
|
+
true
|
|
174
|
+
when :unregistered
|
|
175
|
+
masks_disconnect!
|
|
176
|
+
false
|
|
177
|
+
when :refused
|
|
178
|
+
masks_forget
|
|
179
|
+
false
|
|
180
|
+
else
|
|
181
|
+
false
|
|
182
|
+
end
|
|
143
183
|
end
|
|
144
184
|
|
|
145
185
|
def masks_refreshed_identity(tokens)
|
|
@@ -149,22 +189,9 @@ module Masks
|
|
|
149
189
|
|
|
150
190
|
profile = masks_session.profile(tokens)
|
|
151
191
|
|
|
152
|
-
profile.empty?
|
|
153
|
-
end
|
|
154
|
-
|
|
155
|
-
def masks_logout_url(return_to: nil)
|
|
156
|
-
return nil unless masks_configured?
|
|
192
|
+
return nil if profile.empty? || profile["sub"].to_s != held["sub"].to_s
|
|
157
193
|
|
|
158
|
-
|
|
159
|
-
post_logout_redirect_uri: masks_post_logout_redirect_uri,
|
|
160
|
-
state: SecureRandom.urlsafe_base64(16)
|
|
161
|
-
)
|
|
162
|
-
rescue Masks::Client::Error
|
|
163
|
-
nil
|
|
164
|
-
end
|
|
165
|
-
|
|
166
|
-
def masks_post_logout_redirect_uri
|
|
167
|
-
"#{request.base_url}#{masks_local_path(masks_config.after_sign_out) || '/'}"
|
|
194
|
+
held.merge(profile.slice(*IDENTITY))
|
|
168
195
|
end
|
|
169
196
|
|
|
170
197
|
def masks_login_url(return_to: nil, organization: nil, privilege: nil)
|
|
@@ -193,6 +220,47 @@ module Masks
|
|
|
193
220
|
|
|
194
221
|
private
|
|
195
222
|
|
|
223
|
+
def masks_departed?(held)
|
|
224
|
+
identity = held["identity"]
|
|
225
|
+
|
|
226
|
+
return false unless identity.is_a?(Hash)
|
|
227
|
+
|
|
228
|
+
masks_cache.departed?(masks_config.issuer_for(request).to_s, identity, since: held["signed_in_at"].to_i)
|
|
229
|
+
rescue Masks::Rails::Configuration::Unconfigured
|
|
230
|
+
false
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
def masks_renewal(spent)
|
|
234
|
+
refreshed = masks_session.refresh(spent, resource: masks_config.resource_for(request))
|
|
235
|
+
refreshed = Masks::Client::Tokens.from_h(refreshed.to_h.merge("refresh_token" => spent)) if refreshed.refresh_token.nil?
|
|
236
|
+
|
|
237
|
+
Masks::Rails::Renewal.renewed(refreshed, masks_refreshed_identity(refreshed))
|
|
238
|
+
rescue Masks::Client::Unregistered
|
|
239
|
+
Masks::Rails::Renewal.unregistered
|
|
240
|
+
rescue Masks::Client::Rejected => e
|
|
241
|
+
e.status.to_i >= 500 || e.status.to_i == 429 ? Masks::Rails::Renewal.unreachable : Masks::Rails::Renewal.refused
|
|
242
|
+
rescue Masks::Client::Error
|
|
243
|
+
Masks::Rails::Renewal.unreachable
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
def masks_sign_out_upstream
|
|
247
|
+
return nil unless masks_configured?
|
|
248
|
+
|
|
249
|
+
farewell = masks_farewells.open
|
|
250
|
+
|
|
251
|
+
masks_session.end_session_url(
|
|
252
|
+
post_logout_redirect_uri: masks_config.post_logout_redirect_uri_for(request),
|
|
253
|
+
state: farewell.id
|
|
254
|
+
)
|
|
255
|
+
rescue Masks::Client::Error
|
|
256
|
+
masks_farewells.clear!
|
|
257
|
+
nil
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
def masks_after_sign_out_path
|
|
261
|
+
masks_local_path(masks_config.after_sign_out) || "/"
|
|
262
|
+
end
|
|
263
|
+
|
|
196
264
|
def masks_authorize_session!(wanted)
|
|
197
265
|
return false unless authenticate_masks!
|
|
198
266
|
|
|
@@ -253,6 +321,18 @@ module Masks
|
|
|
253
321
|
redirect_to Masks::Rails::Engine.routes.url_helpers.start_path
|
|
254
322
|
end
|
|
255
323
|
|
|
324
|
+
def masks_cache_unavailable(error)
|
|
325
|
+
response.headers["Cache-Control"] = "no-store"
|
|
326
|
+
response.headers["Retry-After"] = "5"
|
|
327
|
+
|
|
328
|
+
if masks_wants_json?
|
|
329
|
+
render json: { "error" => "temporarily_unavailable", "error_description" => error.message },
|
|
330
|
+
status: :service_unavailable
|
|
331
|
+
else
|
|
332
|
+
render plain: "Signing in is unavailable right now. Try again in a moment.", status: :service_unavailable
|
|
333
|
+
end
|
|
334
|
+
end
|
|
335
|
+
|
|
256
336
|
def masks_refuse_json
|
|
257
337
|
response.headers["Cache-Control"] = "no-store"
|
|
258
338
|
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
module Masks
|
|
2
|
+
module Rails
|
|
3
|
+
class Cache
|
|
4
|
+
class Unavailable < Masks::Client::Error; end
|
|
5
|
+
|
|
6
|
+
CLAIM = 30
|
|
7
|
+
RESULT = 60
|
|
8
|
+
WAIT = 0.05
|
|
9
|
+
PATIENCE = 30
|
|
10
|
+
LOGOUT = 600
|
|
11
|
+
LOGOUT_LEEWAY = 120
|
|
12
|
+
DEPARTURE = 30 * 24 * 3600
|
|
13
|
+
CIPHER = "aes-256-gcm".freeze
|
|
14
|
+
NONCE = 12
|
|
15
|
+
TAG = 16
|
|
16
|
+
|
|
17
|
+
def self.key(*parts)
|
|
18
|
+
"masks:#{OpenSSL::Digest::SHA256.hexdigest(parts.map(&:to_s).join("\0"))}"
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def self.now
|
|
22
|
+
(Time.now.to_f * 1000).to_i
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
attr_reader :store
|
|
26
|
+
|
|
27
|
+
def initialize(store)
|
|
28
|
+
@store = store
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def first?(key, expires_in:)
|
|
32
|
+
add(self.class.key("proof", key), expires_in)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def first_logout?(issuer, logout)
|
|
36
|
+
add(self.class.key("logout", issuer, logout.jti), logout_memory(logout))
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def depart!(issuer, logout)
|
|
40
|
+
kind, value = logout.sid.to_s.empty? ? [ "sub", logout.subject ] : [ "sid", logout.sid ]
|
|
41
|
+
|
|
42
|
+
kept = begin
|
|
43
|
+
write(self.class.key("departed", issuer, kind, value), self.class.now, expires_in: DEPARTURE)
|
|
44
|
+
rescue Unavailable
|
|
45
|
+
false
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
return if kept
|
|
49
|
+
|
|
50
|
+
forgotten { delete(self.class.key("logout", issuer, logout.jti)) }
|
|
51
|
+
|
|
52
|
+
raise Unavailable, "the cache did not keep that logout"
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def departed?(issuer, identity, since:)
|
|
56
|
+
[ [ "sid", identity["sid"] ], [ "sub", identity["sub"] ] ].any? do |kind, value|
|
|
57
|
+
next false if value.to_s.empty?
|
|
58
|
+
|
|
59
|
+
at = read(self.class.key("departed", issuer, kind, value))
|
|
60
|
+
|
|
61
|
+
at.is_a?(Integer) && at >= since
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def renew(issuer, refresh_token)
|
|
66
|
+
result = self.class.key("refreshed", issuer, refresh_token)
|
|
67
|
+
claim = self.class.key("refreshing", issuer, refresh_token)
|
|
68
|
+
found = opened(result, refresh_token)
|
|
69
|
+
|
|
70
|
+
return found if found
|
|
71
|
+
return awaited(result, claim, refresh_token) unless add(claim, CLAIM)
|
|
72
|
+
|
|
73
|
+
begin
|
|
74
|
+
held = yield
|
|
75
|
+
|
|
76
|
+
forgotten { write(result, sealed(refresh_token, held), expires_in: RESULT) } unless held.outcome == :unreachable
|
|
77
|
+
|
|
78
|
+
held
|
|
79
|
+
ensure
|
|
80
|
+
forgotten { delete(claim) }
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
private
|
|
85
|
+
|
|
86
|
+
def add(key, expires_in)
|
|
87
|
+
return true if write(key, 1, unless_exist: true, expires_in: expires_in)
|
|
88
|
+
return false unless read(key).nil?
|
|
89
|
+
|
|
90
|
+
raise Unavailable, "the cache neither kept that key nor holds it, so it is not answering"
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def read(key)
|
|
94
|
+
reached { store.read(key) }
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def write(key, value, **options)
|
|
98
|
+
reached { store.write(key, value, **options) }
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def delete(key)
|
|
102
|
+
reached { store.delete(key) }
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def reached
|
|
106
|
+
yield
|
|
107
|
+
rescue Unavailable
|
|
108
|
+
raise
|
|
109
|
+
rescue StandardError => e
|
|
110
|
+
raise Unavailable, "the cache could not be reached (#{e.class})"
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def forgotten
|
|
114
|
+
yield
|
|
115
|
+
rescue Unavailable
|
|
116
|
+
nil
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def logout_memory(logout)
|
|
120
|
+
[ LOGOUT, logout.expires_at.to_i - Time.now.to_i + LOGOUT_LEEWAY ].max
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def awaited(result, claim, refresh_token)
|
|
124
|
+
deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + PATIENCE
|
|
125
|
+
|
|
126
|
+
while Process.clock_gettime(Process::CLOCK_MONOTONIC) < deadline
|
|
127
|
+
sleep WAIT
|
|
128
|
+
|
|
129
|
+
found = opened(result, refresh_token)
|
|
130
|
+
|
|
131
|
+
return found if found
|
|
132
|
+
return opened(result, refresh_token) || Renewal.unreachable if read(claim).nil?
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
Renewal.unreachable
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def cipher(mode, refresh_token)
|
|
139
|
+
held = OpenSSL::Cipher.new(CIPHER).public_send(mode)
|
|
140
|
+
held.key = OpenSSL::KDF.hkdf(refresh_token.to_s, salt: "masks", info: "masks/rails shared refresh",
|
|
141
|
+
length: 32, hash: "SHA256")
|
|
142
|
+
held
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def sealed(refresh_token, held)
|
|
146
|
+
plain = JSON.generate(held.to_h)
|
|
147
|
+
sealing = cipher(:encrypt, refresh_token)
|
|
148
|
+
nonce = SecureRandom.random_bytes(NONCE)
|
|
149
|
+
sealing.iv = nonce
|
|
150
|
+
sealing.auth_data = ""
|
|
151
|
+
body = sealing.update(plain) + sealing.final
|
|
152
|
+
|
|
153
|
+
Base64.strict_encode64(nonce + sealing.auth_tag(TAG) + body)
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def opened(result, refresh_token)
|
|
157
|
+
held = read(result)
|
|
158
|
+
|
|
159
|
+
return nil if held.nil?
|
|
160
|
+
|
|
161
|
+
raw = Base64.strict_decode64(held.to_s)
|
|
162
|
+
opening = cipher(:decrypt, refresh_token)
|
|
163
|
+
opening.iv = raw.byteslice(0, NONCE)
|
|
164
|
+
opening.auth_tag = raw.byteslice(NONCE, TAG)
|
|
165
|
+
opening.auth_data = ""
|
|
166
|
+
|
|
167
|
+
Renewal.from_h(JSON.parse(opening.update(raw.byteslice((NONCE + TAG)..).to_s) + opening.final))
|
|
168
|
+
rescue ArgumentError, TypeError, OpenSSL::Cipher::CipherError, JSON::ParserError
|
|
169
|
+
Renewal.unreachable
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
end
|
|
@@ -11,7 +11,7 @@ module Masks
|
|
|
11
11
|
:after_sign_out, :session_key, :sign_out_of_issuer, :parent_controller,
|
|
12
12
|
:credentials_path, :authenticate_everything, :delegates
|
|
13
13
|
attr_writer :issuer, :redirect_uri, :name, :credentials, :store, :forget, :logged_out,
|
|
14
|
-
:delegation_redirect_uri, :organization, :manages
|
|
14
|
+
:delegation_redirect_uri, :organization, :manages, :cache
|
|
15
15
|
|
|
16
16
|
def initialize
|
|
17
17
|
@scope = Masks::Client::Session::DEFAULT_SCOPE
|
|
@@ -80,18 +80,13 @@ module Masks
|
|
|
80
80
|
end
|
|
81
81
|
|
|
82
82
|
def backchannel_logout_uri_for(request)
|
|
83
|
-
return nil unless @logged_out.respond_to?(:call)
|
|
84
|
-
return nil unless Masks::Client::Issuer.resolve(issuer_for(request)).backchannel_logout?
|
|
85
|
-
|
|
86
83
|
"#{request.base_url}#{routes.backchannel_logout_path}"
|
|
87
84
|
end
|
|
88
85
|
|
|
89
86
|
def logged_out!(request, logout)
|
|
90
|
-
return
|
|
87
|
+
return unless @logged_out.respond_to?(:call)
|
|
91
88
|
|
|
92
89
|
@logged_out.arity == 1 ? @logged_out.call(logout) : @logged_out.call(request, logout)
|
|
93
|
-
|
|
94
|
-
true
|
|
95
90
|
end
|
|
96
91
|
|
|
97
92
|
def manages?(request, masked = nil)
|
|
@@ -139,10 +134,21 @@ module Masks
|
|
|
139
134
|
Masks::Client::Resource.new(
|
|
140
135
|
issuer: issuer_for(request),
|
|
141
136
|
url: url,
|
|
142
|
-
scopes: resource_scopes
|
|
137
|
+
scopes: resource_scopes,
|
|
138
|
+
replay: Cache.new(cache)
|
|
143
139
|
)
|
|
144
140
|
end
|
|
145
141
|
|
|
142
|
+
def cache
|
|
143
|
+
return @cache if @cache
|
|
144
|
+
|
|
145
|
+
held = ::Rails.cache if ::Rails.respond_to?(:cache)
|
|
146
|
+
|
|
147
|
+
return held unless held.nil? || held.is_a?(ActiveSupport::Cache::NullStore)
|
|
148
|
+
|
|
149
|
+
@process_cache ||= ActiveSupport::Cache::MemoryStore.new
|
|
150
|
+
end
|
|
151
|
+
|
|
146
152
|
def handshake_for(request)
|
|
147
153
|
Masks::Client::Handshake.new(
|
|
148
154
|
issuer_for(request),
|
|
@@ -152,7 +158,8 @@ module Masks
|
|
|
152
158
|
redirect_uris: [ redirect_uri_for(request), delegation_redirect_uri_for(request) ].compact.uniq,
|
|
153
159
|
return_to: return_to_for(request),
|
|
154
160
|
scope: approved_scope,
|
|
155
|
-
backchannel_logout_uri: backchannel_logout_uri_for(request)
|
|
161
|
+
backchannel_logout_uri: backchannel_logout_uri_for(request),
|
|
162
|
+
post_logout_redirect_uri: post_logout_redirect_uri_for(request)
|
|
156
163
|
)
|
|
157
164
|
end
|
|
158
165
|
|
|
@@ -189,14 +196,22 @@ module Masks
|
|
|
189
196
|
end
|
|
190
197
|
|
|
191
198
|
def return_to_for(request)
|
|
192
|
-
|
|
193
|
-
|
|
199
|
+
"#{origin_for(request)}#{routes.handshake_callback_path}"
|
|
200
|
+
end
|
|
194
201
|
|
|
195
|
-
|
|
202
|
+
def post_logout_redirect_uri_for(request)
|
|
203
|
+
"#{origin_for(request)}#{routes.logout_return_path}"
|
|
196
204
|
end
|
|
197
205
|
|
|
198
206
|
private
|
|
199
207
|
|
|
208
|
+
def origin_for(request)
|
|
209
|
+
uri = URI.parse(redirect_uri_for(request))
|
|
210
|
+
port = ":#{uri.port}" unless uri.port == uri.default_port
|
|
211
|
+
|
|
212
|
+
"#{uri.scheme}://#{uri.host}#{port}"
|
|
213
|
+
end
|
|
214
|
+
|
|
200
215
|
def routes
|
|
201
216
|
Masks::Rails::Engine.routes.url_helpers
|
|
202
217
|
end
|
|
@@ -5,6 +5,10 @@ module Masks
|
|
|
5
5
|
include Masks::Rails::Configurable
|
|
6
6
|
include Masks::Rails::Privileged
|
|
7
7
|
|
|
8
|
+
included do
|
|
9
|
+
rescue_from Masks::Client::ReplayUnavailable, with: :masks_unavailable if respond_to?(:rescue_from)
|
|
10
|
+
end
|
|
11
|
+
|
|
8
12
|
def masks_resource
|
|
9
13
|
@masks_resource ||= masks_config.resource_server_for(request)
|
|
10
14
|
end
|
|
@@ -41,6 +45,15 @@ module Masks
|
|
|
41
45
|
}, status: error.status
|
|
42
46
|
end
|
|
43
47
|
|
|
48
|
+
def masks_unavailable
|
|
49
|
+
response.headers["Cache-Control"] = "no-store"
|
|
50
|
+
|
|
51
|
+
render json: {
|
|
52
|
+
"error" => "temporarily_unavailable",
|
|
53
|
+
"error_description" => "the token could not be checked"
|
|
54
|
+
}, status: :service_unavailable
|
|
55
|
+
end
|
|
56
|
+
|
|
44
57
|
def masks_resource_metadata
|
|
45
58
|
masks_resource.metadata
|
|
46
59
|
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
module Masks
|
|
2
|
+
module Rails
|
|
3
|
+
class Renewal
|
|
4
|
+
OUTCOMES = %i[renewed refused unregistered unreachable].freeze
|
|
5
|
+
|
|
6
|
+
attr_reader :outcome, :tokens, :identity
|
|
7
|
+
|
|
8
|
+
class << self
|
|
9
|
+
def renewed(tokens, identity)
|
|
10
|
+
new(:renewed, tokens: tokens, identity: identity)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def refused
|
|
14
|
+
new(:refused)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def unregistered
|
|
18
|
+
new(:unregistered)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def unreachable
|
|
22
|
+
new(:unreachable)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def initialize(outcome, tokens: nil, identity: nil)
|
|
27
|
+
raise ArgumentError, "#{outcome.inspect} is not one of #{OUTCOMES.join(', ')}" unless OUTCOMES.include?(outcome)
|
|
28
|
+
|
|
29
|
+
@outcome = outcome
|
|
30
|
+
@tokens = tokens
|
|
31
|
+
@identity = identity
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def to_h
|
|
35
|
+
{ "outcome" => outcome.to_s, "tokens" => tokens&.to_h&.except("id_token"), "identity" => identity }
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def self.from_h(body)
|
|
39
|
+
return unreachable unless body.is_a?(Hash)
|
|
40
|
+
|
|
41
|
+
outcome = OUTCOMES.find { |known| known.to_s == body["outcome"] }
|
|
42
|
+
|
|
43
|
+
return unreachable if outcome.nil? || outcome == :unreachable
|
|
44
|
+
return new(outcome) unless outcome == :renewed
|
|
45
|
+
|
|
46
|
+
tokens = Masks::Client::Tokens.from_h(body["tokens"])
|
|
47
|
+
|
|
48
|
+
return unreachable if tokens.nil? || tokens.access_token.to_s.empty?
|
|
49
|
+
|
|
50
|
+
renewed(tokens, body["identity"].is_a?(Hash) ? body["identity"] : nil)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|