masks-server 0.7.0 → 0.9.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 (208) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/concerns/masks/server/client_authentication.rb +1 -1
  3. data/app/controllers/concerns/masks/server/resource_token.rb +12 -3
  4. data/app/controllers/masks/server/application_controller.rb +8 -6
  5. data/app/controllers/masks/server/authorize_controller.rb +1 -1
  6. data/app/controllers/masks/server/enrollments_controller.rb +66 -0
  7. data/app/controllers/masks/server/introspections_controller.rb +2 -2
  8. data/app/controllers/masks/server/logins_controller.rb +2 -1
  9. data/app/controllers/masks/server/registrations_controller.rb +40 -2
  10. data/app/controllers/masks/server/scim/metadata_controller.rb +1 -1
  11. data/app/controllers/masks/server/tokens_controller.rb +1 -1
  12. data/app/graphql/masks/server/manage/mutations/add_app.rb +1 -1
  13. data/app/graphql/masks/server/manage/types/client_type.rb +8 -0
  14. data/app/graphql/masks/server/manage/types/query_type.rb +6 -1
  15. data/app/helpers/masks/server/account_helper.rb +1 -1
  16. data/app/helpers/masks/server/application_helper.rb +0 -1
  17. data/app/helpers/masks/server/scopes_helper.rb +28 -0
  18. data/app/jobs/masks/server/cleanup_job.rb +1 -0
  19. data/app/models/masks/server/actor.rb +3 -3
  20. data/app/models/masks/server/client.rb +127 -7
  21. data/app/models/masks/server/client_keys.rb +6 -1
  22. data/app/models/masks/server/enrollment_token.rb +54 -0
  23. data/app/models/masks/server/event.rb +3 -0
  24. data/app/models/masks/server/handshake.rb +12 -6
  25. data/app/models/masks/server/issuer.rb +1 -0
  26. data/app/models/masks/server/login.rb +3 -6
  27. data/app/models/masks/server/namespace.rb +6 -3
  28. data/app/models/masks/server/pending_handshake.rb +2 -1
  29. data/app/models/masks/server/replay.rb +22 -4
  30. data/app/models/masks/server/session.rb +7 -5
  31. data/app/models/masks/server/token.rb +1 -0
  32. data/app/policies/masks/server/exchange_policy.rb +1 -1
  33. data/app/policies/masks/server/grant_checks.rb +1 -1
  34. data/app/views/layouts/masks/server/manage.html.erb +2 -2
  35. data/app/views/masks/server/account/_info.html.erb +1 -1
  36. data/app/views/masks/server/account/_organizations.html.erb +4 -4
  37. data/app/views/masks/server/application/_actor.html.erb +27 -0
  38. data/app/views/masks/server/handshakes/refused.html.erb +1 -1
  39. data/app/views/masks/server/handshakes/show.html.erb +129 -59
  40. data/app/views/masks/server/logins/_choose_organization.html.erb +1 -1
  41. data/app/views/masks/server/logins/_consent.html.erb +1 -1
  42. data/app/views/masks/server/sessions/confirm.html.erb +1 -1
  43. data/config/locales/de/account.yml +3 -3
  44. data/config/locales/de/application.yml +1 -1
  45. data/config/locales/de/events.yml +10 -7
  46. data/config/locales/de/flashes.yml +2 -2
  47. data/config/locales/de/handshakes.yml +33 -14
  48. data/config/locales/de/mailers.yml +1 -1
  49. data/config/locales/de/manage/activity.yml +2 -2
  50. data/config/locales/de/manage/actor.yml +41 -23
  51. data/config/locales/de/manage/actors.yml +16 -15
  52. data/config/locales/de/manage/api.yml +1 -1
  53. data/config/locales/de/manage/app.yml +6 -6
  54. data/config/locales/de/manage/client.yml +45 -45
  55. data/config/locales/de/manage/clients.yml +28 -32
  56. data/config/locales/de/manage/connections.yml +2 -2
  57. data/config/locales/de/manage/consents.yml +2 -2
  58. data/config/locales/de/manage/device.yml +7 -7
  59. data/config/locales/de/manage/devices.yml +18 -18
  60. data/config/locales/de/manage/directory.yml +1 -1
  61. data/config/locales/de/manage/domains.yml +6 -6
  62. data/config/locales/de/manage/emails.yml +4 -4
  63. data/config/locales/de/manage/events.yml +3 -0
  64. data/config/locales/de/manage/groups.yml +7 -7
  65. data/config/locales/de/manage/ldap.yml +8 -8
  66. data/config/locales/de/manage/namespaces.yml +1 -1
  67. data/config/locales/de/manage/organization.yml +15 -8
  68. data/config/locales/de/manage/organizations.yml +3 -3
  69. data/config/locales/de/manage/policies.yml +6 -6
  70. data/config/locales/de/manage/policy_rules.yml +1 -1
  71. data/config/locales/de/manage/presence.yml +6 -6
  72. data/config/locales/de/manage/providers.yml +70 -48
  73. data/config/locales/de/manage/provisioning.yml +4 -4
  74. data/config/locales/de/manage/senders.yml +1 -1
  75. data/config/locales/de/manage/settings.yml +37 -26
  76. data/config/locales/de/manage/settings_shell.yml +2 -2
  77. data/config/locales/de/refusals.yml +5 -5
  78. data/config/locales/en/account.yml +5 -5
  79. data/config/locales/en/application.yml +1 -1
  80. data/config/locales/en/events.yml +9 -6
  81. data/config/locales/en/flashes.yml +3 -3
  82. data/config/locales/en/handshakes.yml +33 -14
  83. data/config/locales/en/logins.yml +2 -2
  84. data/config/locales/en/manage/actor.yml +32 -14
  85. data/config/locales/en/manage/actors.yml +16 -15
  86. data/config/locales/en/manage/app.yml +5 -5
  87. data/config/locales/en/manage/client.yml +9 -9
  88. data/config/locales/en/manage/clients.yml +8 -12
  89. data/config/locales/en/manage/connections.yml +1 -1
  90. data/config/locales/en/manage/consents.yml +1 -1
  91. data/config/locales/en/manage/device.yml +1 -1
  92. data/config/locales/en/manage/devices.yml +1 -1
  93. data/config/locales/en/manage/directory.yml +1 -1
  94. data/config/locales/en/manage/domains.yml +6 -6
  95. data/config/locales/en/manage/emails.yml +4 -4
  96. data/config/locales/en/manage/events.yml +3 -0
  97. data/config/locales/en/manage/groups.yml +7 -7
  98. data/config/locales/en/manage/ldap.yml +8 -8
  99. data/config/locales/en/manage/messages.yml +1 -1
  100. data/config/locales/en/manage/namespaces.yml +1 -1
  101. data/config/locales/en/manage/organization.yml +16 -9
  102. data/config/locales/en/manage/organizations.yml +3 -3
  103. data/config/locales/en/manage/policies.yml +6 -6
  104. data/config/locales/en/manage/policy_rules.yml +2 -2
  105. data/config/locales/en/manage/presence.yml +1 -1
  106. data/config/locales/en/manage/providers.yml +70 -48
  107. data/config/locales/en/manage/provisioning.yml +4 -4
  108. data/config/locales/en/manage/senders.yml +2 -2
  109. data/config/locales/en/manage/settings.yml +34 -23
  110. data/config/locales/es/account.yml +3 -3
  111. data/config/locales/es/application.yml +2 -2
  112. data/config/locales/es/events.yml +9 -6
  113. data/config/locales/es/flashes.yml +4 -4
  114. data/config/locales/es/handshakes.yml +33 -14
  115. data/config/locales/es/logins.yml +12 -12
  116. data/config/locales/es/mailers.yml +7 -7
  117. data/config/locales/es/manage/activity.yml +2 -2
  118. data/config/locales/es/manage/actor.yml +33 -15
  119. data/config/locales/es/manage/actors.yml +17 -16
  120. data/config/locales/es/manage/api.yml +1 -1
  121. data/config/locales/es/manage/app.yml +6 -6
  122. data/config/locales/es/manage/client.yml +30 -30
  123. data/config/locales/es/manage/clients.yml +22 -26
  124. data/config/locales/es/manage/connections.yml +1 -1
  125. data/config/locales/es/manage/consents.yml +1 -1
  126. data/config/locales/es/manage/device.yml +1 -1
  127. data/config/locales/es/manage/directory.yml +1 -1
  128. data/config/locales/es/manage/domains.yml +5 -5
  129. data/config/locales/es/manage/emails.yml +7 -7
  130. data/config/locales/es/manage/events.yml +3 -0
  131. data/config/locales/es/manage/groups.yml +7 -7
  132. data/config/locales/es/manage/ldap.yml +8 -8
  133. data/config/locales/es/manage/namespaces.yml +1 -1
  134. data/config/locales/es/manage/organization.yml +16 -9
  135. data/config/locales/es/manage/organizations.yml +3 -3
  136. data/config/locales/es/manage/policies.yml +8 -8
  137. data/config/locales/es/manage/policy_rules.yml +2 -2
  138. data/config/locales/es/manage/providers.yml +70 -48
  139. data/config/locales/es/manage/provisioning.yml +4 -4
  140. data/config/locales/es/manage/senders.yml +1 -1
  141. data/config/locales/es/manage/settings.yml +38 -27
  142. data/config/locales/es/manage/settings_shell.yml +2 -2
  143. data/config/locales/es/refusals.yml +3 -3
  144. data/config/locales/fr/account.yml +2 -2
  145. data/config/locales/fr/application.yml +1 -1
  146. data/config/locales/fr/events.yml +8 -5
  147. data/config/locales/fr/flashes.yml +1 -1
  148. data/config/locales/fr/handshakes.yml +33 -14
  149. data/config/locales/fr/manage/activity.yml +4 -4
  150. data/config/locales/fr/manage/actor.yml +43 -25
  151. data/config/locales/fr/manage/actors.yml +22 -21
  152. data/config/locales/fr/manage/api.yml +1 -1
  153. data/config/locales/fr/manage/app.yml +7 -7
  154. data/config/locales/fr/manage/client.yml +50 -50
  155. data/config/locales/fr/manage/clients.yml +28 -32
  156. data/config/locales/fr/manage/connections.yml +2 -2
  157. data/config/locales/fr/manage/consents.yml +1 -1
  158. data/config/locales/fr/manage/device.yml +1 -1
  159. data/config/locales/fr/manage/directory.yml +1 -1
  160. data/config/locales/fr/manage/domains.yml +6 -6
  161. data/config/locales/fr/manage/emails.yml +5 -5
  162. data/config/locales/fr/manage/events.yml +3 -0
  163. data/config/locales/fr/manage/groups.yml +7 -7
  164. data/config/locales/fr/manage/ldap.yml +8 -8
  165. data/config/locales/fr/manage/namespaces.yml +1 -1
  166. data/config/locales/fr/manage/organization.yml +15 -8
  167. data/config/locales/fr/manage/organizations.yml +3 -3
  168. data/config/locales/fr/manage/policies.yml +6 -6
  169. data/config/locales/fr/manage/policy_rules.yml +1 -1
  170. data/config/locales/fr/manage/providers.yml +70 -48
  171. data/config/locales/fr/manage/provisioning.yml +4 -4
  172. data/config/locales/fr/manage/senders.yml +1 -1
  173. data/config/locales/fr/manage/settings.yml +38 -27
  174. data/config/locales/fr/manage/settings_shell.yml +2 -2
  175. data/config/routes.rb +2 -0
  176. data/config/vite.json +1 -0
  177. data/db/migrate/20261011190000_add_enrolled_by_to_clients.rb +5 -0
  178. data/db/migrate/20261012100000_create_replays.rb +23 -0
  179. data/db/migrate/20261013010000_add_handshake_return_to_to_clients.rb +5 -0
  180. data/lib/generators/masks/server/install/install_generator.rb +2 -2
  181. data/lib/masks/server/browser_access.rb +35 -0
  182. data/lib/masks/server/configuration.rb +3 -3
  183. data/lib/masks/server/engine.rb +1 -0
  184. data/lib/masks/server/schema_reference.rb +17 -4
  185. data/lib/masks/server.rb +1 -0
  186. data/public/masks-assets/.vite/manifest-assets.json +1 -0
  187. data/public/masks-assets/.vite/manifest.json +62 -0
  188. data/public/masks-assets/assets/app-B27oPUll.css +2 -0
  189. data/public/masks-assets/assets/console-DoyNbIw3.css +1 -0
  190. data/public/masks-assets/assets/handshake-DIFZPSAK.js +2 -0
  191. data/public/masks-assets/assets/handshake-DIFZPSAK.js.map +1 -0
  192. data/public/masks-assets/assets/{initial-D99AzFOd.js → initial-C1GpCiEh.js} +2 -2
  193. data/public/masks-assets/assets/{initial-D99AzFOd.js.map → initial-C1GpCiEh.js.map} +1 -1
  194. data/public/masks-assets/assets/login-C_6igRZ3.js +4 -0
  195. data/public/masks-assets/assets/login-C_6igRZ3.js.map +1 -0
  196. data/public/masks-assets/assets/manage-Dbfiy3Mj.js +567 -0
  197. data/public/masks-assets/assets/manage-Dbfiy3Mj.js.map +1 -0
  198. data/version.txt +1 -1
  199. metadata +21 -12
  200. data/app/views/masks/server/application/_person.html.erb +0 -27
  201. data/public/masks-assets/assets/app-olx-5PkA.css +0 -2
  202. data/public/masks-assets/assets/console-CiuQ3qj0.css +0 -1
  203. data/public/masks-assets/assets/handshake-BYhhQ_X3.js +0 -2
  204. data/public/masks-assets/assets/handshake-BYhhQ_X3.js.map +0 -1
  205. data/public/masks-assets/assets/login-CNs1UpxJ.js +0 -4
  206. data/public/masks-assets/assets/login-CNs1UpxJ.js.map +0 -1
  207. data/public/masks-assets/assets/manage-Z3GmPDNQ.js +0 -560
  208. data/public/masks-assets/assets/manage-Z3GmPDNQ.js.map +0 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 670bf543e143915a6be6c9ca44965b2a66315bf479d60ec190b2cbc53fe939bb
4
- data.tar.gz: 8b1fabb6b7597f23802f81e07b966992a69cd9ba8bd03f070812729f246b66a6
3
+ metadata.gz: 36e33f34e755bbbb4470a5c55b920624f2042678b87c810ce614e86f9638a4df
4
+ data.tar.gz: 2bf178807ba578015922b44145ad0eb28454698adb877ac51b5059be57a35ca3
5
5
  SHA512:
6
- metadata.gz: 96e59cfb63394b1fe6be72fcb2320c1ed401951ce6eb681600f405221eda392040f4d6f84a918641e9de5de236f563479e532c155c1fe015d5f0d53301d9f4d1
7
- data.tar.gz: a5733b84f8c14b7bc86e9b3ca2099b471fad489ac53486a71386f92e9fb10076d45c977387672236d1db039dc06218547fd3e214e65a533d322b3efc80f19bdb
6
+ metadata.gz: 3d32dd6592c56c0132971c1f322ca90b2a31df6832c0b9fe33d5c41c7d5d7aab443b4c6b2fec0a9d3ac0635b22b6d86bbda1c451bd791fd3794e4776c1edd1c8
7
+ data.tar.gz: 59f5658c277590947cf74f3b8e57fe2d9d7b01f1a160bdc3bdbc3593f4aba9b9395b08255b866e54029f48aff6d9b7b1384daf9ab1b21b95f7468678a800fbcb
@@ -44,7 +44,7 @@ module Masks
44
44
  end
45
45
 
46
46
  def assertion_audiences
47
- [ issuer.url, "#{issuer.url}/token", "#{issuer.url}#{request.path}" ].uniq
47
+ [ issuer.url, "#{issuer.url}/token", "#{issuer.url}#{request.path_info}" ].uniq
48
48
  end
49
49
 
50
50
  def presented_credentials
@@ -19,7 +19,7 @@ module Masks
19
19
  answer = handler.call(request.env)
20
20
 
21
21
  return render_rack(answer) unless answer.first == 200
22
- return refuse_token("a bearer token is required") if token.nil?
22
+ return ask_for_token if token.nil?
23
23
 
24
24
  yield token
25
25
  end
@@ -50,8 +50,11 @@ module Masks
50
50
 
51
51
  def decode(bearer)
52
52
  claims_in_token(bearer.access_token)
53
+ rescue JWT::ExpiredSignature
54
+ bearer.invalid_token!("that token has expired")
53
55
  rescue JWT::DecodeError => e
54
- bearer.invalid_token!(e.message)
56
+ ::Rails.logger.info("masks: an access token was refused: #{e.message}")
57
+ bearer.invalid_token!("that token is not one this server signed")
55
58
  end
56
59
 
57
60
  def claims_in_token(secret)
@@ -96,7 +99,7 @@ module Masks
96
99
  end
97
100
 
98
101
  def proof_for(secret = nil)
99
- Proof.read!(request, url: "#{issuer.url}#{request.path}", access_token: secret)
102
+ Proof.read!(request, url: "#{issuer.url}#{request.path_info}", access_token: secret)
100
103
  end
101
104
 
102
105
  def held_token(secret)
@@ -118,6 +121,12 @@ module Masks
118
121
  nil
119
122
  end
120
123
 
124
+ def ask_for_token
125
+ response.headers["WWW-Authenticate"] = %(Bearer realm="#{issuer.url}")
126
+
127
+ head :unauthorized
128
+ end
129
+
121
130
  def refuse_token(description)
122
131
  render_rack(
123
132
  Rack::OAuth2::Server::Resource::Bearer::Unauthorized.new(
@@ -143,7 +143,7 @@ module Masks
143
143
  Current.device = device
144
144
  end
145
145
 
146
- def sign_in(actor, amr: [], policy: nil, first_factor: {})
146
+ def sign_in(actor, amr: [], policy: nil, first_factor: {}, authenticated_at: nil)
147
147
  carried = session.to_hash.slice(REQUESTS, HANDSHAKES, "login", RETURN_TO)
148
148
  reset_session
149
149
  carried.each { |key, value| session[key] = value }
@@ -157,7 +157,8 @@ module Masks
157
157
  ip_address: request.remote_ip,
158
158
  amr: amr,
159
159
  policy: policy,
160
- first_factor: first_factor
160
+ first_factor: first_factor,
161
+ authenticated_at: authenticated_at
161
162
  )
162
163
 
163
164
  cookies.encrypted[:masks_session] = {
@@ -301,17 +302,18 @@ module Masks
301
302
  end
302
303
 
303
304
  def settle!(login)
304
- hold(login.actor, amr: login.amr, policy: login.policy, first_factor: login.first_factored_by)
305
+ hold(login.actor, amr: login.amr, policy: login.policy, first_factor: login.first_factored_by,
306
+ authenticated_at: login.factored_at(:first_factor))
305
307
  forget_login
306
308
  end
307
309
 
308
- def hold(actor, amr: [], policy: nil, first_factor: {})
310
+ def hold(actor, amr: [], policy: nil, first_factor: {}, authenticated_at: nil)
309
311
  return if actor.nil?
310
- return current_session.first_factored!(first_factor) if current_session&.actor_id == actor.id
312
+ return current_session.first_factored!(first_factor, at: authenticated_at) if current_session&.actor_id == actor.id
311
313
 
312
314
  sign_out if current_session
313
315
 
314
- sign_in(actor, amr: amr, policy: policy, first_factor: first_factor)
316
+ sign_in(actor, amr: amr, policy: policy, first_factor: first_factor, authenticated_at: authenticated_at)
315
317
  end
316
318
 
317
319
  def policy_denied(denial)
@@ -63,7 +63,7 @@ module Masks
63
63
  req.bad_request!(:invalid_client, unknown_client(req.client_id)) if client.nil?
64
64
  req.invalid_request!("redirect_uri is required") if req.redirect_uri.blank?
65
65
 
66
- req.verify_redirect_uri!(client.redirect_uris)
66
+ req.verify_redirect_uri!(client.redirect_uri?(req.redirect_uri) ? [ req.redirect_uri.to_s ] : client.redirect_uris)
67
67
  req.verified_redirect_uri = with_issuer(req.verified_redirect_uri)
68
68
  res.redirect_uri = req.verified_redirect_uri
69
69
 
@@ -0,0 +1,66 @@
1
+ module Masks
2
+ module Server
3
+ class EnrollmentsController < ApplicationController
4
+ include TokenPresented
5
+
6
+ rate_limit to: 30, within: 1.minute,
7
+ by: -> { [ current_tenant.id, request.remote_ip ].join(":") },
8
+ with: -> {
9
+ render json: {
10
+ "error" => "too_many_requests",
11
+ "error_description" => "too many enrollment tokens asked for from this address"
12
+ }, status: :too_many_requests
13
+ }
14
+
15
+ before_action :no_store!
16
+
17
+ def create
18
+ client = authenticate_app!
19
+ token = EnrollmentToken.issue!(client, scope: params[:scope], name: params[:client_name],
20
+ expires_in: params[:expires_in])
21
+
22
+ Event.record!(Event::CLIENT_ENROLLMENT_ISSUED, client: client, scopes: token.scopes,
23
+ name: token.held("name"), expires_at: token.expires_at.iso8601)
24
+
25
+ render json: {
26
+ "initial_access_token" => token.secret,
27
+ "expires_in" => token.expires_in,
28
+ "scope" => token.scopes
29
+ }, status: :created
30
+ rescue Client::EnrollmentRefused => e
31
+ render json: { "error" => "invalid_request", "error_description" => e.message }, status: :bad_request
32
+ end
33
+
34
+ def destroy
35
+ app = authenticate_app!
36
+ machine = app.enrolled.find_by(client_id: params[:machine].to_s)
37
+
38
+ return not_enrolled if machine.nil?
39
+
40
+ revoked = machine.unenroll!
41
+
42
+ Event.record!(Event::CLIENT_UNENROLLED, client: machine, name: machine.name, enrolled_by: app.client_id,
43
+ revoked: revoked)
44
+
45
+ head :no_content
46
+ end
47
+
48
+ private
49
+
50
+ def authenticate_app!
51
+ client = authenticate_client!
52
+
53
+ client_refused!("a public client proves nothing about itself, so it enrolls and unenrolls nothing") if client.public?
54
+
55
+ client
56
+ end
57
+
58
+ def not_enrolled
59
+ render json: {
60
+ "error" => "invalid_request",
61
+ "error_description" => "that client was not enrolled by this app"
62
+ }, status: :not_found
63
+ end
64
+ end
65
+ end
66
+ end
@@ -13,7 +13,7 @@ module Masks
13
13
  client = authenticate_client!
14
14
  token = presented_token
15
15
 
16
- return render json: INACTIVE unless token&.live? && entitled?(client, token)
16
+ return render json: INACTIVE unless token&.live? && !token.client&.archived? && entitled?(client, token)
17
17
 
18
18
  render json: described(token)
19
19
  end
@@ -22,7 +22,7 @@ module Masks
22
22
 
23
23
  def entitled?(client, token)
24
24
  return true if token.client_id == client.id
25
- return false unless client.approved?
25
+ return false if client.enrolled? || !client.approved?
26
26
 
27
27
  (Array(token.audience) & Array(client.resources)).any?
28
28
  end
@@ -151,7 +151,8 @@ module Masks
151
151
  return unless login.actor
152
152
 
153
153
  if login.touched?(:first_factor)
154
- sign_in(login.actor, amr: login.amr, policy: login.policy, first_factor: login.first_factored_by)
154
+ sign_in(login.actor, amr: login.amr, policy: login.policy, first_factor: login.first_factored_by,
155
+ authenticated_at: login.factored_at(:first_factor))
155
156
  elsif login.signed_in? && login.touched?(:second_factor)
156
157
  current_session.update!(amr: Array(current_session.amr) | login.amr)
157
158
  end
@@ -14,6 +14,7 @@ module Masks
14
14
 
15
15
  before_action :no_store!
16
16
  before_action :require_registration_token, except: :create
17
+ before_action :refuse_enrolled, only: :update
17
18
 
18
19
  def create
19
20
  return redeem if bearer.present?
@@ -26,7 +27,7 @@ module Masks
26
27
  render json: issued(client), status: :created
27
28
  rescue ActiveRecord::RecordInvalid => e
28
29
  invalid_metadata(e.record.errors.full_messages.join("; "))
29
- rescue Client::ScopesUnavailable => e
30
+ rescue Client::ScopesUnavailable, Client::EnrollmentRefused => e
30
31
  invalid_metadata(e.message)
31
32
  end
32
33
 
@@ -47,6 +48,8 @@ module Masks
47
48
  end
48
49
 
49
50
  def destroy
51
+ return unenroll if @client.enrolled?
52
+
50
53
  @client.update!(archived_at: Time.current)
51
54
 
52
55
  Event.record!(Event::CLIENT_ARCHIVED, client: @client, name: @client.name)
@@ -56,7 +59,27 @@ module Masks
56
59
 
57
60
  private
58
61
 
62
+ def unenroll
63
+ revoked = @client.unenroll!
64
+
65
+ Event.record!(Event::CLIENT_UNENROLLED, client: @client, name: @client.name,
66
+ enrolled_by: @client.enrolled_by&.client_id, revoked: revoked)
67
+
68
+ head :no_content
69
+ end
70
+
71
+ def refuse_enrolled
72
+ return unless @client.enrolled?
73
+
74
+ render json: {
75
+ "error" => "access_denied",
76
+ "error_description" => "an enrolled machine is changed only by the app that enrolled it"
77
+ }, status: :forbidden
78
+ end
79
+
59
80
  def redeem
81
+ return enrolled(@enrolled) if (@enrolled = EnrollmentToken.redeem!(bearer, body))
82
+
60
83
  token = InitialAccessToken.claim(bearer)
61
84
 
62
85
  return unauthorized("that initial access token is not valid or has expired") if token&.client.nil?
@@ -64,6 +87,13 @@ module Masks
64
87
  render json: issued(token.issue!), status: :created
65
88
  end
66
89
 
90
+ def enrolled(client)
91
+ Event.record!(Event::CLIENT_ENROLLED, client: client, name: client.name,
92
+ enrolled_by: client.enrolled_by.client_id)
93
+
94
+ render json: issued(client), status: :created
95
+ end
96
+
67
97
  def bearer
68
98
  request.authorization.to_s[/\ABearer (\S+)\z/, 1]
69
99
  end
@@ -103,6 +133,12 @@ module Masks
103
133
  }, status: :bad_request
104
134
  end
105
135
 
136
+ def ask_for_token
137
+ response.headers["WWW-Authenticate"] = %(Bearer realm="#{issuer.url}")
138
+
139
+ head :unauthorized
140
+ end
141
+
106
142
  def issued(client)
107
143
  client.metadata.merge(
108
144
  "client_secret" => client.secret,
@@ -125,7 +161,9 @@ module Masks
125
161
  end
126
162
 
127
163
  def unauthorized(description)
128
- response.headers["WWW-Authenticate"] = %(Bearer error="invalid_token")
164
+ return ask_for_token if bearer.blank?
165
+
166
+ response.headers["WWW-Authenticate"] = %(Bearer realm="#{issuer.url}", error="invalid_token")
129
167
 
130
168
  render json: {
131
169
  "error" => "invalid_token",
@@ -7,7 +7,7 @@ module Masks
7
7
  def service_provider_config
8
8
  scim({
9
9
  "schemas" => [ Scim::SERVICE_PROVIDER_CONFIG ],
10
- "documentationUri" => "#{::Rails.configuration.masks.docs_url}/concepts/provisioning/",
10
+ "documentationUri" => "#{::Rails.configuration.masks.docs_url}/guides/sso-saml/#provisioning",
11
11
  "patch" => { "supported" => true },
12
12
  "bulk" => { "supported" => false, "maxOperations" => 0, "maxPayloadSize" => 0 },
13
13
  "filter" => { "supported" => true, "maxResults" => Scim::MAX_RESULTS },
@@ -42,7 +42,7 @@ module Masks
42
42
  return @proof if defined?(@proof)
43
43
 
44
44
  @proof = if Proof.presented?(request)
45
- Proof.read!(request, url: "#{issuer.url}#{request.path}")
45
+ Proof.read!(request, url: "#{issuer.url}#{request.path_info}")
46
46
  end
47
47
  end
48
48
 
@@ -7,7 +7,7 @@ module Masks
7
7
 
8
8
  argument :preset, ID
9
9
  argument :base_url, String, description: "Where the app is served, such as https://grafana.example.com."
10
- argument :name, String, required: false, description: "What people see. The preset's name when left out."
10
+ argument :name, String, required: false, description: "What people see."
11
11
  argument :slug, String, required: false,
12
12
  description: "The name some apps put in their redirect URI, such as Gitea's authentication source."
13
13
 
@@ -57,6 +57,10 @@ module Masks
57
57
  field :dynamic, Boolean, null: false
58
58
  field :approved_at, GraphQL::Types::ISO8601DateTime
59
59
  field :approved_by, ActorType
60
+ field :enrolled_by, ClientType,
61
+ description: "The app that enrolled this client as one of its machines. Archiving that app archives this client."
62
+ field :enrolled, [ ClientType ], null: false,
63
+ description: "The machines this app enrolled that are not archived."
60
64
  field :archived_at, GraphQL::Types::ISO8601DateTime
61
65
  field :secret_expires_at, GraphQL::Types::ISO8601DateTime
62
66
  field :created_at, GraphQL::Types::ISO8601DateTime, null: false
@@ -75,6 +79,10 @@ module Masks
75
79
  argument :limit, Integer, required: false
76
80
  end
77
81
 
82
+ def enrolled
83
+ object.enrolled.active.order(:name)
84
+ end
85
+
78
86
  def tokens(limit: nil)
79
87
  Masks::Server::Token.where(kind: TokenType::GRANTS, client_id: object.id)
80
88
  .live
@@ -169,6 +169,8 @@ module Masks
169
169
  argument :action, String, required: false
170
170
  argument :grave, Boolean, required: false
171
171
  argument :organization, ID, required: false
172
+ argument :since, GraphQL::Types::ISO8601DateTime, required: false,
173
+ description: "Only events recorded at or after this time."
172
174
  argument :after_id, ID, required: false
173
175
  argument :limit, Integer, required: false
174
176
  end
@@ -180,6 +182,8 @@ module Masks
180
182
  argument :action, String, required: false
181
183
  argument :grave, Boolean, required: false
182
184
  argument :organization, ID, required: false
185
+ argument :since, GraphQL::Types::ISO8601DateTime, required: false,
186
+ description: "Only events recorded at or after this time."
183
187
  end
184
188
 
185
189
  field :event_actions, [ EventActionType ], null: false
@@ -484,7 +488,7 @@ module Masks
484
488
  scope.where("name ILIKE :term OR client_id = :exact", term: term, exact: search.strip)
485
489
  end
486
490
 
487
- def event_scope(actor: nil, client: nil, device: nil, action: nil, grave: false, organization: nil)
491
+ def event_scope(actor: nil, client: nil, device: nil, action: nil, grave: false, organization: nil, since: nil)
488
492
  subject = actor.present? ? Actor.find_by(uuid: actor) : nil
489
493
  group = organization.present? ? Masks::Server::Organization.find_by(key: organization) : nil
490
494
 
@@ -504,6 +508,7 @@ module Masks
504
508
  scope = scope.where(organization: group) if group
505
509
  scope = scope.where(action: action) if action.present?
506
510
  scope = scope.where(action: Masks::Server::Event::GRAVE) if grave
511
+ scope = scope.where(created_at: since..) if since
507
512
 
508
513
  scope
509
514
  end
@@ -15,7 +15,7 @@ module Masks
15
15
  close: '<path d="M6.5 6.5l7 7M13.5 6.5l-7 7"/>',
16
16
  check: '<path d="m5.5 10.5 3 3 6-7"/>',
17
17
  organization: '<rect x="3.5" y="7" width="13" height="9.5" rx="1.5"/><path d="M7.5 7V4.5h5V7"/><path d="M3.5 11h13"/>',
18
- person: '<circle cx="10" cy="7" r="3.25"/><path d="M4 16.5c.8-3 3.2-4.5 6-4.5s5.2 1.5 6 4.5"/>',
18
+ actor: '<circle cx="10" cy="7" r="3.25"/><path d="M4 16.5c.8-3 3.2-4.5 6-4.5s5.2 1.5 6 4.5"/>',
19
19
  authenticator: '<rect x="3" y="5.5" width="14" height="9" rx="2"/><path d="M6.5 10h.01M10 10h.01M13.5 10h.01"/>',
20
20
  backup_codes: '<path d="M5.5 3.5h9v13h-9z"/><path d="M8 7h4M8 10h4M8 13h4"/>',
21
21
  manage: '<path d="M10 2.5 3.5 5v5c0 3.4 2.7 6.2 6.5 7.5 3.8-1.3 6.5-4.1 6.5-7.5V5L10 2.5Z"/>'
@@ -15,7 +15,6 @@ module Masks
15
15
  .auth-page {
16
16
  --signal: #{accent};
17
17
  --signal-ink: #{tenant.accent_ink};
18
- --came: #{accent};
19
18
  --brass: #{accent};
20
19
  --ring: color-mix(in oklab, #{accent} 30%, transparent);
21
20
  }
@@ -3,13 +3,41 @@ module Masks
3
3
  module ScopesHelper
4
4
  CONSEQUENTIAL = /(:write|:command|:exec|:delete|:admin)\z|\Aoffline_access\z|\Amasks:manage(:[a-z]+)?\z|\Amasks:delegate:./
5
5
 
6
+ SEEN = [
7
+ Scopes::OPENID, Scopes::PROFILE, Scopes::EMAIL,
8
+ Scopes::IDENTITIES, Scopes::ORGANIZATION, Scopes::GROUPS
9
+ ].freeze
10
+
6
11
  def consequential_scope?(scope)
7
12
  scope.to_s.match?(CONSEQUENTIAL)
8
13
  end
9
14
 
15
+ def heeded_scope?(scope)
16
+ scope == Scopes::DELEGATE || consequential_scope?(scope)
17
+ end
18
+
10
19
  def ranked_scopes(described)
11
20
  described.to_a.partition { |scope, _| consequential_scope?(scope) }
12
21
  end
22
+
23
+ def plain_scopes(described, areas: [])
24
+ held = { sees: [], heeds: [], also: [] }
25
+
26
+ described.to_a.each do |scope, description|
27
+ next if areas.include?(scope)
28
+
29
+ group =
30
+ if heeded_scope?(scope) then :heeds
31
+ elsif SEEN.include?(scope) then :sees
32
+ else :also
33
+ end
34
+
35
+ held[group] << [ scope, description ]
36
+ end
37
+
38
+ held[:sees].sort_by! { |scope, _| SEEN.index(scope) }
39
+ held
40
+ end
13
41
  end
14
42
  end
15
43
  end
@@ -32,6 +32,7 @@ module Masks
32
32
  SigningKey.where.not(retired_at: nil).where(retired_at: ...cutoff).delete_all
33
33
  Event.where(created_at: ...tenant.event_retention.ago).in_batches(of: BATCH).delete_all
34
34
  Members.purge_lapsed!
35
+ Replay.where(expires_at: ...Time.current).in_batches(of: BATCH).delete_all
35
36
  end
36
37
 
37
38
  def sweep_tokens(cutoff)
@@ -190,14 +190,14 @@ module Masks
190
190
  holds?(Scopes::MANAGE)
191
191
  end
192
192
 
193
- def person_json
193
+ def card_json
194
194
  style = Avatars.held?(self) ? Avatars::PHOTO : Avatars::FALLBACK
195
195
 
196
196
  {
197
197
  "name" => display_name,
198
198
  "details" => display_details,
199
- "note" => (I18n.t("application.person.unconfirmed") if email_unconfirmed?),
200
- "role" => (I18n.t("application.person.manager") if manages?),
199
+ "note" => (I18n.t("application.actor.unconfirmed") if email_unconfirmed?),
200
+ "role" => (I18n.t("application.actor.manager") if manages?),
201
201
  "avatar" => Avatars.url(self, style, subject: uuid, size: 128)
202
202
  }
203
203
  end