masks 0.5.0 → 0.7.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 +52 -0
- data/LICENSE +1 -1
- data/README.md +110 -32
- data/app/controllers/masks/rails/avatars_controller.rb +51 -0
- data/app/controllers/masks/rails/handshakes_controller.rb +18 -18
- data/app/controllers/masks/rails/logouts_controller.rb +32 -0
- data/app/controllers/masks/rails/sessions_controller.rb +5 -0
- data/app/views/masks/rails/handshakes/show.html.erb +17 -33
- data/config/routes.rb +2 -0
- data/lib/masks/client/claims.rb +41 -0
- data/lib/masks/client/delegations/fake.rb +91 -0
- data/lib/masks/client/delegations.rb +117 -0
- data/lib/masks/client/errors.rb +8 -0
- data/lib/masks/client/handshake.rb +7 -3
- data/lib/masks/client/http.rb +16 -1
- data/lib/masks/client/introspection.rb +1 -1
- data/lib/masks/client/issuer.rb +20 -0
- data/lib/masks/client/logout.rb +60 -0
- data/lib/masks/client/registration.rb +29 -6
- data/lib/masks/client/session.rb +10 -2
- data/lib/masks/client/tokens.rb +3 -1
- data/lib/masks/client.rb +46 -0
- data/lib/masks/rails/authentication.rb +45 -2
- data/lib/masks/rails/configuration.rb +40 -16
- data/lib/masks/rails/credentials.rb +0 -4
- data/lib/masks/rails/engine.rb +0 -5
- data/lib/masks/rails.rb +26 -0
- data/lib/masks/version.rb +1 -1
- data/lib/masks.rb +17 -0
- metadata +7 -2
|
@@ -6,7 +6,8 @@ module Masks
|
|
|
6
6
|
|
|
7
7
|
included do
|
|
8
8
|
if respond_to?(:helper_method)
|
|
9
|
-
helper_method :masks_signed_in?, :masks_identity, :masks_tenant, :masks_scopes
|
|
9
|
+
helper_method :masks_signed_in?, :masks_identity, :masks_tenant, :masks_scopes,
|
|
10
|
+
:masks_claims
|
|
10
11
|
end
|
|
11
12
|
end
|
|
12
13
|
|
|
@@ -48,7 +49,10 @@ module Masks
|
|
|
48
49
|
session[masks_config.session_key] || {}
|
|
49
50
|
end
|
|
50
51
|
|
|
51
|
-
IDENTITY =
|
|
52
|
+
IDENTITY = [
|
|
53
|
+
"sub", "name", "preferred_username", "email", "email_verified", "tenant",
|
|
54
|
+
"picture", Masks::Client::Claims::AVATARS
|
|
55
|
+
].freeze
|
|
52
56
|
|
|
53
57
|
def masks_identity_from(tokens)
|
|
54
58
|
return nil if tokens.id_token.nil?
|
|
@@ -70,6 +74,36 @@ module Masks
|
|
|
70
74
|
masks_config.configured?(request)
|
|
71
75
|
end
|
|
72
76
|
|
|
77
|
+
def masks_registration
|
|
78
|
+
Masks::Client::Registration.held(
|
|
79
|
+
masks_config.issuer_for(request),
|
|
80
|
+
masks_config.credentials_for(request)
|
|
81
|
+
)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def masks_registered?
|
|
85
|
+
held = masks_registration
|
|
86
|
+
|
|
87
|
+
held.nil? || held.known?
|
|
88
|
+
rescue Masks::Client::Error
|
|
89
|
+
true
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def masks_disconnect!
|
|
93
|
+
return false unless masks_configured? && masks_config.can_forget?
|
|
94
|
+
|
|
95
|
+
masks_config.forget!(request)
|
|
96
|
+
masks_forget
|
|
97
|
+
|
|
98
|
+
true
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def masks_reconnect!
|
|
102
|
+
return false if masks_registered?
|
|
103
|
+
|
|
104
|
+
masks_disconnect!
|
|
105
|
+
end
|
|
106
|
+
|
|
73
107
|
def masks_handshake_path
|
|
74
108
|
Masks::Rails::Engine.routes.url_helpers.handshake_path
|
|
75
109
|
end
|
|
@@ -84,6 +118,10 @@ module Masks
|
|
|
84
118
|
masks_identity&.dig("tenant")
|
|
85
119
|
end
|
|
86
120
|
|
|
121
|
+
def masks_claims
|
|
122
|
+
@masks_claims ||= Masks::Client::Claims.new(masks_identity || {})
|
|
123
|
+
end
|
|
124
|
+
|
|
87
125
|
def masks_scopes
|
|
88
126
|
masks_tokens&.scopes || []
|
|
89
127
|
end
|
|
@@ -107,6 +145,9 @@ module Masks
|
|
|
107
145
|
)
|
|
108
146
|
)
|
|
109
147
|
true
|
|
148
|
+
rescue Masks::Client::Unregistered
|
|
149
|
+
masks_disconnect!
|
|
150
|
+
false
|
|
110
151
|
rescue Masks::Client::Rejected
|
|
111
152
|
masks_forget
|
|
112
153
|
false
|
|
@@ -144,6 +185,8 @@ module Masks
|
|
|
144
185
|
"nickname" => identity["preferred_username"],
|
|
145
186
|
"email" => identity["email"],
|
|
146
187
|
"email_verified" => identity["email_verified"],
|
|
188
|
+
"picture" => masks_claims.picture,
|
|
189
|
+
"avatars" => masks_claims.avatars.to_h.presence,
|
|
147
190
|
"tenant" => masks_tenant,
|
|
148
191
|
"scopes" => masks_scopes,
|
|
149
192
|
"expires_at" => masks_tokens&.expires_at
|
|
@@ -3,10 +3,11 @@ module Masks
|
|
|
3
3
|
class Configuration
|
|
4
4
|
class Unconfigured < Masks::Client::Error; end
|
|
5
5
|
|
|
6
|
-
attr_accessor :scope, :resource, :resource_scopes, :after_sign_in,
|
|
7
|
-
:session_key, :sign_out_of_issuer, :parent_controller,
|
|
8
|
-
:credentials_path, :authenticate_everything
|
|
9
|
-
attr_writer :issuer, :redirect_uri, :name, :credentials, :store, :forget
|
|
6
|
+
attr_accessor :scope, :namespace, :resource, :resource_scopes, :after_sign_in,
|
|
7
|
+
:after_sign_out, :session_key, :sign_out_of_issuer, :parent_controller,
|
|
8
|
+
:credentials_path, :authenticate_everything, :delegates
|
|
9
|
+
attr_writer :issuer, :redirect_uri, :name, :credentials, :store, :forget, :logged_out,
|
|
10
|
+
:delegation_redirect_uri
|
|
10
11
|
|
|
11
12
|
def initialize
|
|
12
13
|
@scope = Masks::Client::Session::DEFAULT_SCOPE
|
|
@@ -17,12 +18,9 @@ module Masks
|
|
|
17
18
|
@sign_out_of_issuer = false
|
|
18
19
|
@parent_controller = "ActionController::Base"
|
|
19
20
|
@authenticate_everything = false
|
|
21
|
+
@delegates = false
|
|
20
22
|
end
|
|
21
23
|
|
|
22
|
-
# An app that has not said where to keep its credentials gets one file
|
|
23
|
-
# under the Rails root, so the handshake works before anybody writes a
|
|
24
|
-
# `store` lambda. `things` overrides both because it is multi-tenant,
|
|
25
|
-
# which is the interesting case rather than the common one.
|
|
26
24
|
def default_credentials
|
|
27
25
|
@default_credentials ||= Credentials.new(
|
|
28
26
|
credentials_path || ::Rails.root.join("config", "masks.json")
|
|
@@ -43,6 +41,12 @@ module Masks
|
|
|
43
41
|
"#{request.base_url}#{routes.callback_path}"
|
|
44
42
|
end
|
|
45
43
|
|
|
44
|
+
def delegation_redirect_uri_for(request)
|
|
45
|
+
return nil unless delegates
|
|
46
|
+
|
|
47
|
+
resolve(@delegation_redirect_uri, request)
|
|
48
|
+
end
|
|
49
|
+
|
|
46
50
|
def resource_for(request)
|
|
47
51
|
resolve(@resource, request)
|
|
48
52
|
end
|
|
@@ -66,8 +70,21 @@ module Masks
|
|
|
66
70
|
client_id_for(request).present?
|
|
67
71
|
end
|
|
68
72
|
|
|
69
|
-
|
|
70
|
-
|
|
73
|
+
def backchannel_logout_uri_for(request)
|
|
74
|
+
return nil unless @logged_out.respond_to?(:call)
|
|
75
|
+
return nil unless Masks::Client::Issuer.resolve(issuer_for(request)).backchannel_logout?
|
|
76
|
+
|
|
77
|
+
"#{request.base_url}#{routes.backchannel_logout_path}"
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def logged_out!(request, logout)
|
|
81
|
+
return false unless @logged_out.respond_to?(:call)
|
|
82
|
+
|
|
83
|
+
@logged_out.arity == 1 ? @logged_out.call(logout) : @logged_out.call(request, logout)
|
|
84
|
+
|
|
85
|
+
true
|
|
86
|
+
end
|
|
87
|
+
|
|
71
88
|
def can_forget?
|
|
72
89
|
@forget.respond_to?(:call) || !@store.respond_to?(:call)
|
|
73
90
|
end
|
|
@@ -85,10 +102,6 @@ module Masks
|
|
|
85
102
|
@store.call(request, registration)
|
|
86
103
|
end
|
|
87
104
|
|
|
88
|
-
# The engine depends on `handshake_endpoint` being in the discovery
|
|
89
|
-
# document, and on the approval flow behind it. An issuer that predates
|
|
90
|
-
# both should say so here rather than at the one screen that exists to
|
|
91
|
-
# be the first thing anybody touches.
|
|
92
105
|
MINIMUM_ISSUER = 1
|
|
93
106
|
|
|
94
107
|
def issuer_speaks!(request)
|
|
@@ -132,12 +145,23 @@ module Masks
|
|
|
132
145
|
name: name_for(request),
|
|
133
146
|
resource: Array(resource_for(request)).first ||
|
|
134
147
|
raise(Unconfigured, "Masks::Rails.config.resource is not set"),
|
|
135
|
-
redirect_uris: [ redirect_uri_for(request) ],
|
|
148
|
+
redirect_uris: [ redirect_uri_for(request), delegation_redirect_uri_for(request) ].compact.uniq,
|
|
136
149
|
return_to: return_to_for(request),
|
|
137
|
-
scope:
|
|
150
|
+
scope: approved_scope,
|
|
151
|
+
backchannel_logout_uri: backchannel_logout_uri_for(request)
|
|
138
152
|
)
|
|
139
153
|
end
|
|
140
154
|
|
|
155
|
+
def approved_scope
|
|
156
|
+
delegated = delegates ? [ Masks::Client::Delegations::SCOPE ] : []
|
|
157
|
+
|
|
158
|
+
return Array(scope) + delegated if namespace.blank?
|
|
159
|
+
|
|
160
|
+
outside = Array(scope).reject { |name| name.to_s.start_with?(namespace) }
|
|
161
|
+
|
|
162
|
+
outside + [ namespace ] + delegated
|
|
163
|
+
end
|
|
164
|
+
|
|
141
165
|
def return_to_for(request)
|
|
142
166
|
uri = URI.parse(redirect_uri_for(request))
|
|
143
167
|
port = ":#{uri.port}" unless uri.port == uri.default_port
|
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
module Masks
|
|
2
2
|
module Rails
|
|
3
|
-
# The default place a single-tenant app keeps what the handshake gives it,
|
|
4
|
-
# so `config.store` has something to be before a consumer writes one. An
|
|
5
|
-
# app with more than one issuer wants its own — `things` keeps these on
|
|
6
|
-
# `tenants`, because a handshake is per tenant there.
|
|
7
3
|
class Credentials
|
|
8
4
|
KEYS = %w[client_id client_secret registration_access_token registration_client_uri].freeze
|
|
9
5
|
|
data/lib/masks/rails/engine.rb
CHANGED
|
@@ -5,11 +5,6 @@ module Masks
|
|
|
5
5
|
|
|
6
6
|
config.masks = Masks::Rails.config
|
|
7
7
|
|
|
8
|
-
# Opt-in, not blanket. This used to `include Masks::Rails::Authentication`
|
|
9
|
-
# into every controller in the host app, which lands twenty-five `masks_*`
|
|
10
|
-
# methods and four helper_methods on code the consumer owns, with no way
|
|
11
|
-
# to say no — and a name collision found that way is the kind of thing
|
|
12
|
-
# `grants:` versus `scope:` already cost this project once.
|
|
13
8
|
initializer "masks.authentication" do
|
|
14
9
|
ActiveSupport.on_load(:action_controller) do
|
|
15
10
|
include Masks::Rails::Authentication if Masks::Rails.config.authenticate_everything
|
data/lib/masks/rails.rb
CHANGED
|
@@ -1,5 +1,31 @@
|
|
|
1
1
|
require "rails"
|
|
2
2
|
|
|
3
|
+
module Masks
|
|
4
|
+
# = Masks::Rails
|
|
5
|
+
#
|
|
6
|
+
# The consumer half: a \Rails engine that mounts the code flow into an
|
|
7
|
+
# application, so signing in against a masks issuer is configuration rather
|
|
8
|
+
# than a controller you write.
|
|
9
|
+
#
|
|
10
|
+
# It loads only when +Rails::Engine+ is already defined. Requiring the gem
|
|
11
|
+
# from a plain Ruby process gets Masks::Client and nothing else.
|
|
12
|
+
#
|
|
13
|
+
# Three pieces do the work:
|
|
14
|
+
#
|
|
15
|
+
# [Configuration] the issuer, credentials and routes, set once in an
|
|
16
|
+
# initializer and validated on boot rather than on the
|
|
17
|
+
# first request that needs them
|
|
18
|
+
# [Authentication] +masks_login_url+, the callback, and the session the
|
|
19
|
+
# app reads +current_actor+ from
|
|
20
|
+
# [ProtectedResource] the other direction — checking a bearer this app was
|
|
21
|
+
# handed, for an API rather than a browser
|
|
22
|
+
#
|
|
23
|
+
# This engine is the client. It never runs in the same process as the
|
|
24
|
+
# provider, which is a standalone deployable holding its own database.
|
|
25
|
+
module Rails
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
3
29
|
require_relative "version"
|
|
4
30
|
require_relative "client"
|
|
5
31
|
require_relative "rails/credentials"
|
data/lib/masks/version.rb
CHANGED
data/lib/masks.rb
CHANGED
|
@@ -1,3 +1,20 @@
|
|
|
1
|
+
# = \Masks
|
|
2
|
+
#
|
|
3
|
+
# Signs an application in against a masks issuer.
|
|
4
|
+
#
|
|
5
|
+
# The gem is two halves. Masks::Client is plain Ruby and speaks the protocol —
|
|
6
|
+
# discovery, PKCE, the code exchange, token verification. Masks::Rails mounts
|
|
7
|
+
# that flow into a \Rails app, and loads only when +Rails::Engine+ is already
|
|
8
|
+
# defined, so requiring this gem outside \Rails costs nothing.
|
|
9
|
+
#
|
|
10
|
+
# issuer = Masks::Client.issuer("https://auth.example")
|
|
11
|
+
# issuer.authorization_url(client_id: id, redirect_uri: uri)
|
|
12
|
+
#
|
|
13
|
+
# The provider itself is not in here. It is a deployable that holds a database
|
|
14
|
+
# and the per-tenant signing keys, and it stays standalone.
|
|
15
|
+
module Masks
|
|
16
|
+
end
|
|
17
|
+
|
|
1
18
|
require_relative "masks/version"
|
|
2
19
|
require_relative "masks/client"
|
|
3
20
|
|
metadata
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: masks
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.7.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
|
-
-
|
|
7
|
+
- the masks authors
|
|
8
8
|
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
@@ -33,8 +33,10 @@ files:
|
|
|
33
33
|
- CHANGELOG.md
|
|
34
34
|
- LICENSE
|
|
35
35
|
- README.md
|
|
36
|
+
- app/controllers/masks/rails/avatars_controller.rb
|
|
36
37
|
- app/controllers/masks/rails/base_controller.rb
|
|
37
38
|
- app/controllers/masks/rails/handshakes_controller.rb
|
|
39
|
+
- app/controllers/masks/rails/logouts_controller.rb
|
|
38
40
|
- app/controllers/masks/rails/sessions_controller.rb
|
|
39
41
|
- app/views/layouts/masks/rails/plain.html.erb
|
|
40
42
|
- app/views/masks/rails/handshakes/refused.html.erb
|
|
@@ -45,11 +47,14 @@ files:
|
|
|
45
47
|
- lib/masks.rb
|
|
46
48
|
- lib/masks/client.rb
|
|
47
49
|
- lib/masks/client/claims.rb
|
|
50
|
+
- lib/masks/client/delegations.rb
|
|
51
|
+
- lib/masks/client/delegations/fake.rb
|
|
48
52
|
- lib/masks/client/errors.rb
|
|
49
53
|
- lib/masks/client/handshake.rb
|
|
50
54
|
- lib/masks/client/http.rb
|
|
51
55
|
- lib/masks/client/introspection.rb
|
|
52
56
|
- lib/masks/client/issuer.rb
|
|
57
|
+
- lib/masks/client/logout.rb
|
|
53
58
|
- lib/masks/client/pkce.rb
|
|
54
59
|
- lib/masks/client/rack.rb
|
|
55
60
|
- lib/masks/client/registration.rb
|