masks-server 0.11.0 → 0.13.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/app/controllers/concerns/masks/server/scim_endpoint.rb +2 -2
- data/app/controllers/masks/server/account_controller.rb +3 -0
- data/app/controllers/masks/server/account_exports_controller.rb +2 -2
- data/app/controllers/masks/server/addresses_controller.rb +2 -3
- data/app/controllers/masks/server/app_passwords_controller.rb +41 -0
- data/app/controllers/masks/server/authenticator_apps_controller.rb +2 -3
- data/app/controllers/masks/server/authorize_controller.rb +33 -2
- data/app/controllers/masks/server/device_authorizations_controller.rb +2 -2
- data/app/controllers/masks/server/device_verifications_controller.rb +2 -2
- data/app/controllers/masks/server/enrollments_controller.rb +4 -4
- data/app/controllers/masks/server/forward_auth_controller.rb +106 -14
- data/app/controllers/masks/server/introspections_controller.rb +2 -2
- data/app/controllers/masks/server/logins_controller.rb +14 -15
- data/app/controllers/masks/server/organization_access_controller.rb +39 -0
- data/app/controllers/masks/server/organization_members_controller.rb +2 -2
- data/app/controllers/masks/server/passwords_controller.rb +2 -3
- data/app/controllers/masks/server/pushed_authorizations_controller.rb +2 -2
- data/app/controllers/masks/server/received_signals_controller.rb +2 -2
- data/app/controllers/masks/server/registrations_controller.rb +2 -2
- data/app/controllers/masks/server/revocations_controller.rb +2 -2
- data/app/controllers/masks/server/saml_controller.rb +2 -2
- data/app/controllers/masks/server/sign_in_approvals_controller.rb +2 -3
- data/app/controllers/masks/server/tokens_controller.rb +2 -2
- data/app/controllers/masks/server/verifications_controller.rb +2 -3
- data/app/graphql/masks/server/manage/mutations/base_mutation.rb +7 -0
- data/app/graphql/masks/server/manage/mutations/create_group.rb +6 -4
- data/app/graphql/masks/server/manage/mutations/decline_access.rb +27 -0
- data/app/graphql/masks/server/manage/mutations/grant_access.rb +29 -0
- data/app/graphql/masks/server/manage/mutations/group_source.rb +34 -0
- data/app/graphql/masks/server/manage/mutations/issue_ldap_credential.rb +5 -2
- data/app/graphql/masks/server/manage/mutations/refresh_group.rb +21 -0
- data/app/graphql/masks/server/manage/mutations/remove_group_member.rb +2 -0
- data/app/graphql/masks/server/manage/mutations/run_health_checks.rb +19 -0
- data/app/graphql/masks/server/manage/mutations/update_client.rb +6 -0
- data/app/graphql/masks/server/manage/mutations/update_group.rb +7 -4
- data/app/graphql/masks/server/manage/mutations/update_tenant.rb +14 -1
- data/app/graphql/masks/server/manage/types/access_request_type.rb +21 -0
- data/app/graphql/masks/server/manage/types/actor_type.rb +6 -0
- data/app/graphql/masks/server/manage/types/client_type.rb +12 -0
- data/app/graphql/masks/server/manage/types/group_condition_input.rb +16 -0
- data/app/graphql/masks/server/manage/types/group_condition_type.rb +29 -0
- data/app/graphql/masks/server/manage/types/group_member_type.rb +16 -0
- data/app/graphql/masks/server/manage/types/group_type.rb +17 -1
- data/app/graphql/masks/server/manage/types/health_check_type.rb +18 -0
- data/app/graphql/masks/server/manage/types/ldap_credential_type.rb +1 -0
- data/app/graphql/masks/server/manage/types/mutation_type.rb +4 -0
- data/app/graphql/masks/server/manage/types/query_type.rb +24 -0
- data/app/graphql/masks/server/manage/types/release_type.rb +16 -0
- data/app/graphql/masks/server/manage/types/tenant_type.rb +6 -1
- data/app/helpers/masks/server/account_helper.rb +4 -0
- data/app/jobs/masks/server/cleanup_job.rb +1 -0
- data/app/jobs/masks/server/health_check_job.rb +21 -0
- data/app/jobs/masks/server/refresh_groups_job.rb +18 -0
- data/app/jobs/masks/server/release_check_job.rb +21 -0
- data/app/ldap/masks/server/ldap/connection.rb +3 -2
- data/app/ldap/masks/server/ldap/gate.rb +44 -19
- data/app/logins/masks/server/login_states/second_factor.rb +1 -1
- data/app/mailers/masks/server/actor_mailer.rb +31 -0
- data/app/models/masks/server/access_request.rb +103 -0
- data/app/models/masks/server/actor.rb +6 -0
- data/app/models/masks/server/app_password.rb +91 -0
- data/app/models/masks/server/app_passwords.rb +76 -0
- data/app/models/masks/server/client.rb +15 -0
- data/app/models/masks/server/client_metadata_document.rb +4 -8
- data/app/models/masks/server/confirmation_code.rb +2 -2
- data/app/models/masks/server/connection.rb +7 -0
- data/app/models/masks/server/declared.rb +13 -3
- data/app/models/masks/server/email_verification.rb +2 -2
- data/app/models/masks/server/event.rb +13 -3
- data/app/models/masks/server/federation/protocol.rb +1 -1
- data/app/models/masks/server/federation/saml.rb +1 -1
- data/app/models/masks/server/forward_auth_rules.rb +162 -0
- data/app/models/masks/server/group.rb +207 -4
- data/app/models/masks/server/group_member.rb +7 -0
- data/app/models/masks/server/group_rule.rb +63 -0
- data/app/models/masks/server/health.rb +310 -0
- data/app/models/masks/server/health_check.rb +27 -0
- data/app/models/masks/server/invitation.rb +2 -2
- data/app/models/masks/server/ldap_credential.rb +2 -2
- data/app/models/masks/server/login.rb +28 -2
- data/app/models/masks/server/mail_previews.rb +5 -0
- data/app/models/masks/server/mail_template.rb +2 -0
- data/app/models/masks/server/membership.rb +10 -3
- data/app/models/masks/server/notifications.rb +3 -1
- data/app/models/masks/server/password_reset.rb +2 -2
- data/app/models/masks/server/provider.rb +1 -0
- data/app/models/masks/server/queue.rb +60 -0
- data/app/models/masks/server/release.rb +74 -0
- data/app/models/masks/server/saml_identity/farewells.rb +1 -0
- data/app/models/masks/server/session.rb +8 -0
- data/app/models/masks/server/tenant.rb +87 -6
- data/app/models/masks/server/throttle.rb +165 -0
- data/app/models/masks/server/token.rb +2 -1
- data/app/views/masks/server/account/_app_passwords.html.erb +83 -0
- data/app/views/masks/server/account/_organizations.html.erb +33 -0
- data/app/views/masks/server/account/index.html.erb +5 -0
- data/app/views/masks/server/actor_mailer/access_declined.html.erb +5 -0
- data/app/views/masks/server/actor_mailer/access_declined.text.erb +5 -0
- data/app/views/masks/server/actor_mailer/access_granted.html.erb +7 -0
- data/app/views/masks/server/actor_mailer/access_granted.text.erb +7 -0
- data/app/views/masks/server/authorize/error.html.erb +25 -0
- data/config/locales/de/access_requests.yml +43 -0
- data/config/locales/de/account/app_passwords.yml +42 -0
- data/config/locales/de/events.yml +5 -0
- data/config/locales/de/flashes.yml +1 -0
- data/config/locales/de/group_sources.yml +5 -0
- data/config/locales/de/manage/access_requests.yml +12 -0
- data/config/locales/de/manage/app_passwords.yml +16 -0
- data/config/locales/de/manage/forward_auth_rules.yml +29 -0
- data/config/locales/de/manage/group_sources.yml +44 -0
- data/config/locales/de/manage/health.yml +85 -0
- data/config/locales/de/manage/links.yml +20 -0
- data/config/locales/en/access_requests.yml +43 -0
- data/config/locales/en/account/app_passwords.yml +42 -0
- data/config/locales/en/events.yml +5 -0
- data/config/locales/en/flashes.yml +1 -0
- data/config/locales/en/group_sources.yml +5 -0
- data/config/locales/en/manage/access_requests.yml +12 -0
- data/config/locales/en/manage/app_passwords.yml +16 -0
- data/config/locales/en/manage/forward_auth_rules.yml +29 -0
- data/config/locales/en/manage/group_sources.yml +44 -0
- data/config/locales/en/manage/health.yml +85 -0
- data/config/locales/en/manage/links.yml +20 -0
- data/config/locales/es/access_requests.yml +43 -0
- data/config/locales/es/account/app_passwords.yml +42 -0
- data/config/locales/es/events.yml +5 -0
- data/config/locales/es/flashes.yml +1 -0
- data/config/locales/es/group_sources.yml +5 -0
- data/config/locales/es/manage/access_requests.yml +12 -0
- data/config/locales/es/manage/app_passwords.yml +16 -0
- data/config/locales/es/manage/forward_auth_rules.yml +29 -0
- data/config/locales/es/manage/group_sources.yml +44 -0
- data/config/locales/es/manage/health.yml +85 -0
- data/config/locales/es/manage/links.yml +20 -0
- data/config/locales/fr/access_requests.yml +43 -0
- data/config/locales/fr/account/app_passwords.yml +42 -0
- data/config/locales/fr/events.yml +5 -0
- data/config/locales/fr/flashes.yml +1 -0
- data/config/locales/fr/group_sources.yml +5 -0
- data/config/locales/fr/manage/access_requests.yml +12 -0
- data/config/locales/fr/manage/app_passwords.yml +16 -0
- data/config/locales/fr/manage/forward_auth_rules.yml +29 -0
- data/config/locales/fr/manage/group_sources.yml +44 -0
- data/config/locales/fr/manage/health.yml +85 -0
- data/config/locales/fr/manage/links.yml +20 -0
- data/config/routes.rb +5 -0
- data/db/migrate/20261020100000_create_access_requests.rb +28 -0
- data/db/migrate/20261020110000_add_app_passwords_to_clients.rb +5 -0
- data/db/migrate/20261020120000_add_forward_auth_rules_to_clients.rb +6 -0
- data/db/migrate/20261020130000_add_sources_to_groups.rb +15 -0
- data/db/migrate/20261020140000_create_health_checks.rb +25 -0
- data/db/migrate/20261020150000_add_link_lifetimes_to_tenants.rb +8 -0
- data/db/migrate/20261021100000_add_pinned_origin_to_tenants.rb +5 -0
- data/db/migrate/20261031100000_default_key_rotation_to_ninety_days.rb +5 -0
- data/lib/generators/masks/server/install/install_generator.rb +22 -2
- data/lib/masks/server/configuration.rb +16 -19
- data/lib/masks/server/engine.rb +2 -1
- data/lib/masks/server/forwarding.rb +26 -3
- data/lib/masks/server/schema_reference.rb +12 -0
- data/lib/masks/server/tenancy/middleware.rb +21 -13
- data/lib/masks/server/tenant_isolation.rb +12 -0
- data/public/masks-assets/.vite/manifest.json +13 -13
- data/public/masks-assets/assets/{account-D46qDQCo.js → account-h7I6VUPR.js} +2 -2
- data/public/masks-assets/assets/{account-D46qDQCo.js.map → account-h7I6VUPR.js.map} +1 -1
- data/public/masks-assets/assets/{app-D7W0gAyT.css → app-BXOScMjL.css} +1 -1
- data/public/masks-assets/assets/{handshake-DIFZPSAK.js → handshake-YQpRNMIs.js} +2 -2
- data/public/masks-assets/assets/{handshake-DIFZPSAK.js.map → handshake-YQpRNMIs.js.map} +1 -1
- data/public/masks-assets/assets/initial-Cn-UuAsy.js +3 -0
- data/public/masks-assets/assets/{initial-C1GpCiEh.js.map → initial-Cn-UuAsy.js.map} +1 -1
- data/public/masks-assets/assets/{login-C_6igRZ3.js → login-C21_jb6L.js} +3 -3
- data/public/masks-assets/assets/{login-C_6igRZ3.js.map → login-C21_jb6L.js.map} +1 -1
- data/public/masks-assets/assets/{manage-D4MzaDvM.js → manage-DpVxe5sv.js} +80 -51
- data/public/masks-assets/assets/manage-DpVxe5sv.js.map +1 -0
- data/public/masks-assets/assets/passkey-5UYPVD3c.js +2 -0
- data/public/masks-assets/assets/{passkey-DIj_5LCY.js.map → passkey-5UYPVD3c.js.map} +1 -1
- data/version.txt +1 -1
- metadata +89 -14
- data/public/masks-assets/assets/initial-C1GpCiEh.js +0 -3
- data/public/masks-assets/assets/manage-D4MzaDvM.js.map +0 -1
- data/public/masks-assets/assets/passkey-DIj_5LCY.js +0 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c268c36afc7aa404d7cb904ef65cb1f67db51d6b84bdb3a5f83d5aae7d84bf48
|
|
4
|
+
data.tar.gz: '09285b0606e241c323c82f16555328830421c7d97338d9aa32f38055e6972353'
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a253ac4a9e52258b001f1a7424a189728979a5363255d1d9c4d306e70385ea2eecdd8f93e2859603f6959e43786f90be43921d5ed7bc523fa81477f06384f507
|
|
7
|
+
data.tar.gz: 844b3eb9927070dfddbea67dbbb17992772e84348c35ffdc9feba33fd1b079f718e9d986b8f9da591f77498a54a8404c7240a6dd5ad22d320a04500ec57641f6
|
|
@@ -8,9 +8,9 @@ module Masks
|
|
|
8
8
|
|
|
9
9
|
skip_forgery_protection
|
|
10
10
|
|
|
11
|
-
rate_limit
|
|
11
|
+
rate_limit **Throttle.limits(:scim),
|
|
12
12
|
by: -> { [ current_tenant.id, request.remote_ip ].join(":") },
|
|
13
|
-
with:
|
|
13
|
+
with: Throttle.refused(:scim) { scim_error(Scim::Error.new(:too_many_requests, "too many provisioning requests from this address")) }
|
|
14
14
|
|
|
15
15
|
rescue_from Scim::Error, with: :scim_error
|
|
16
16
|
before_action :require_provisioner
|
|
@@ -9,6 +9,9 @@ module Masks
|
|
|
9
9
|
return redirect_to login_path if @actor.nil?
|
|
10
10
|
|
|
11
11
|
@apps = Apps.held_by(@actor)
|
|
12
|
+
@app_passwords = AppPassword.live.where(actor: @actor).includes(:client).newest_first.to_a
|
|
13
|
+
@app_password_clients = AppPassword.offered(@actor)
|
|
14
|
+
@app_password = flash[:app_password]
|
|
12
15
|
@launchable = Apps.launchable(@actor)
|
|
13
16
|
@invitations = @actor.memberships.outstanding.joins(:organization).merge(Organization.active)
|
|
14
17
|
.includes(:organization, :invited_by).order("organizations.name").to_a
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
module Masks
|
|
2
2
|
module Server
|
|
3
3
|
class AccountExportsController < ApplicationController
|
|
4
|
-
rate_limit
|
|
4
|
+
rate_limit **Throttle.limits(:export), only: :create,
|
|
5
5
|
by: -> { [ current_tenant.id, current_actor&.id || request.remote_ip ].join(":") },
|
|
6
|
-
with:
|
|
6
|
+
with: Throttle.refused(:export) { redirect_to root_path(anchor: "export"), alert: t("account_export.throttled") }
|
|
7
7
|
|
|
8
8
|
def create
|
|
9
9
|
actor = current_actor
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
module Masks
|
|
2
2
|
module Server
|
|
3
3
|
class AddressesController < ApplicationController
|
|
4
|
-
rate_limit
|
|
5
|
-
within: 3.minutes,
|
|
4
|
+
rate_limit **Throttle.limits(:account_attempt),
|
|
6
5
|
by: -> { [ current_tenant.id, current_actor&.id ].join(":") },
|
|
7
|
-
with:
|
|
6
|
+
with: Throttle.refused(:account_attempt) { refuse(t("addresses.too_many_attempts")) },
|
|
8
7
|
only: :update
|
|
9
8
|
|
|
10
9
|
before_action :require_actor
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
module Masks
|
|
2
|
+
module Server
|
|
3
|
+
class AppPasswordsController < ApplicationController
|
|
4
|
+
ANCHOR = "app-passwords".freeze
|
|
5
|
+
|
|
6
|
+
before_action :require_actor
|
|
7
|
+
|
|
8
|
+
def create
|
|
9
|
+
return unless reauthenticated!(ANCHOR, t("app_passwords.again"))
|
|
10
|
+
|
|
11
|
+
client = Client.active.find_by(client_id: params[:client_id].to_s)
|
|
12
|
+
held = AppPassword.issue!(actor: current_actor, client: client, name: params[:name])
|
|
13
|
+
|
|
14
|
+
Event.record!(Event::APP_PASSWORD_CREATED, actor: current_actor, client: client, name: held.name)
|
|
15
|
+
|
|
16
|
+
flash[:app_password] = { "name" => held.name, "app" => client.name, "secret" => held.secret }
|
|
17
|
+
redirect_to root_path(anchor: ANCHOR)
|
|
18
|
+
rescue AppPassword::Refused => e
|
|
19
|
+
redirect_to root_path(anchor: ANCHOR), alert: t("app_passwords.refused.#{e.message}", most: AppPassword::MOST)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def destroy
|
|
23
|
+
held = AppPassword.live.find_by(id: params[:id].to_s[/\A\d+\z/], actor: current_actor)
|
|
24
|
+
|
|
25
|
+
return redirect_to root_path(anchor: ANCHOR), alert: t("app_passwords.unknown") if held.nil?
|
|
26
|
+
|
|
27
|
+
held.revoke!
|
|
28
|
+
|
|
29
|
+
Event.record!(Event::APP_PASSWORD_REVOKED, actor: current_actor, client: held.client, name: held.name)
|
|
30
|
+
|
|
31
|
+
redirect_to root_path(anchor: ANCHOR), notice: t("app_passwords.revoked", name: held.name)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def require_actor
|
|
37
|
+
redirect_to login_path unless current_actor
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
module Masks
|
|
2
2
|
module Server
|
|
3
3
|
class AuthenticatorAppsController < ApplicationController
|
|
4
|
-
rate_limit
|
|
5
|
-
within: 3.minutes,
|
|
4
|
+
rate_limit **Throttle.limits(:account_attempt),
|
|
6
5
|
by: -> { [ current_tenant.id, current_actor&.id ].join(":") },
|
|
7
|
-
with:
|
|
6
|
+
with: Throttle.refused(:account_attempt) { refuse(t("authenticator_apps.too_many_attempts")) },
|
|
8
7
|
only: :create
|
|
9
8
|
|
|
10
9
|
before_action :require_actor
|
|
@@ -9,6 +9,10 @@ module Masks
|
|
|
9
9
|
refuse(bad_request(:invalid_request_object, refusal.message))
|
|
10
10
|
end
|
|
11
11
|
|
|
12
|
+
rate_limit **Throttle.limits(:access_request), only: :ask,
|
|
13
|
+
by: -> { [ current_tenant.id, current_actor&.id, refused_request(params[:rid])&.client_id ].join(":") },
|
|
14
|
+
with: Throttle.refused(:access_request) { redirect_to authorize_refused_path(rid: params[:rid]), alert: t("authorize.error.slow_down") }
|
|
15
|
+
|
|
12
16
|
def show
|
|
13
17
|
authorization = Authorization.from_request(request)
|
|
14
18
|
|
|
@@ -45,7 +49,7 @@ module Masks
|
|
|
45
49
|
|
|
46
50
|
login = advance(pending)
|
|
47
51
|
|
|
48
|
-
return redirect_to(refuse_on_masks(pending, login.refusal)) if login.refused? && !answerable?(pending)
|
|
52
|
+
return redirect_to(refuse_on_masks(pending, login.refusal)) if login.refused? && (!answerable?(pending) || askable?(pending, login))
|
|
49
53
|
return answer(deny(pending, login.refusal.error, login.refusal.description)) if login.refused?
|
|
50
54
|
return interaction_required(pending) if login.prompted? && pending.silent?
|
|
51
55
|
return redirect_to(login.redirect_to, allow_other_host: true) if login.redirect_to.present?
|
|
@@ -55,14 +59,31 @@ module Masks
|
|
|
55
59
|
end
|
|
56
60
|
|
|
57
61
|
def refused
|
|
58
|
-
|
|
62
|
+
pending = refused_request(params[:rid])
|
|
63
|
+
refusal = pending&.refusal
|
|
59
64
|
|
|
60
65
|
return show_error("invalid_request", "that sign-in request has expired", :bad_request) if refusal.nil?
|
|
61
66
|
|
|
62
67
|
@refused = true
|
|
68
|
+
@asking = asking_for(pending)
|
|
69
|
+
@waiting = @asking && AccessRequest.open.find_by(actor: current_actor, client: @asking)
|
|
70
|
+
@return_to = answerable?(pending) && pending.authorization.redirect_with(
|
|
71
|
+
issuer: issuer, error: refusal["error"], error_description: refusal["description"]
|
|
72
|
+
)
|
|
63
73
|
show_error(refusal["error"], refusal["description"], :forbidden)
|
|
64
74
|
end
|
|
65
75
|
|
|
76
|
+
def ask
|
|
77
|
+
pending = refused_request(params[:rid])
|
|
78
|
+
asking = pending && asking_for(pending)
|
|
79
|
+
|
|
80
|
+
return show_error("invalid_request", "that sign-in request has expired", :bad_request) if asking.nil?
|
|
81
|
+
|
|
82
|
+
AccessRequest.ask!(actor: current_actor, client: asking)
|
|
83
|
+
|
|
84
|
+
redirect_to authorize_refused_path(rid: params[:rid]), notice: t("authorize.error.asked")
|
|
85
|
+
end
|
|
86
|
+
|
|
66
87
|
private
|
|
67
88
|
|
|
68
89
|
def validate(authorization)
|
|
@@ -149,6 +170,16 @@ module Masks
|
|
|
149
170
|
end
|
|
150
171
|
end
|
|
151
172
|
|
|
173
|
+
def askable?(pending, login)
|
|
174
|
+
!pending.silent? && login.actor.present? && asking_for(pending, login.actor).present?
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def asking_for(pending, actor = current_actor)
|
|
178
|
+
client = pending.client
|
|
179
|
+
|
|
180
|
+
client if actor && client && client.allowed_groups.any? && !client.admits?(actor)
|
|
181
|
+
end
|
|
182
|
+
|
|
152
183
|
def admitted?(authorization)
|
|
153
184
|
tracked(REQUESTS).key?(authorization.fingerprint)
|
|
154
185
|
end
|
|
@@ -4,9 +4,9 @@ module Masks
|
|
|
4
4
|
include RackOAuth2Endpoint
|
|
5
5
|
include TokenPresented
|
|
6
6
|
|
|
7
|
-
rate_limit
|
|
7
|
+
rate_limit **Throttle.limits(:device_authorization),
|
|
8
8
|
by: -> { [ current_tenant.id, request.remote_ip ].join(":") },
|
|
9
|
-
with:
|
|
9
|
+
with: Throttle.refused(:device_authorization) { slow_down }
|
|
10
10
|
|
|
11
11
|
def create
|
|
12
12
|
client = authenticate_client!
|
|
@@ -6,10 +6,10 @@ module Masks
|
|
|
6
6
|
|
|
7
7
|
before_action :establish_device
|
|
8
8
|
|
|
9
|
-
rate_limit
|
|
9
|
+
rate_limit **Throttle.limits(:device_code),
|
|
10
10
|
if: -> { params[:user_code].present? },
|
|
11
11
|
by: -> { [ current_tenant.id, request.remote_ip ].join(":") },
|
|
12
|
-
with:
|
|
12
|
+
with: Throttle.refused(:device_code) { asking(UNKNOWN, status: :too_many_requests) }
|
|
13
13
|
|
|
14
14
|
def show
|
|
15
15
|
return asking(params[:refused].presence) if params[:user_code].blank?
|
|
@@ -3,18 +3,18 @@ module Masks
|
|
|
3
3
|
class EnrollmentsController < ApplicationController
|
|
4
4
|
include TokenPresented
|
|
5
5
|
|
|
6
|
-
rate_limit
|
|
6
|
+
rate_limit **Throttle.limits(:enrollment),
|
|
7
7
|
by: -> { [ current_tenant.id, request.remote_ip ].join(":") },
|
|
8
|
-
with:
|
|
8
|
+
with: Throttle.refused(:enrollment) {
|
|
9
9
|
render json: {
|
|
10
10
|
"error" => "too_many_requests",
|
|
11
11
|
"error_description" => "too many enrollment tokens asked for from this address"
|
|
12
12
|
}, status: :too_many_requests
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
rate_limit
|
|
15
|
+
rate_limit **Throttle.limits(:app_enrollment), only: :create, name: "app",
|
|
16
16
|
by: -> { [ current_tenant.id, claimed_client_id ].join(":") },
|
|
17
|
-
with:
|
|
17
|
+
with: Throttle.refused(:app_enrollment) {
|
|
18
18
|
render json: {
|
|
19
19
|
"error" => "too_many_requests",
|
|
20
20
|
"error_description" => "too many enrollment tokens asked for by this app"
|
|
@@ -4,9 +4,9 @@ module Masks
|
|
|
4
4
|
skip_forgery_protection
|
|
5
5
|
before_action -> { request.session_options[:skip] = true }, only: %i[check callback]
|
|
6
6
|
|
|
7
|
-
rate_limit
|
|
7
|
+
rate_limit **Throttle.limits(:forward_auth), only: %i[start callback],
|
|
8
8
|
by: -> { [ current_tenant.id, request.remote_ip ].join(":") },
|
|
9
|
-
with:
|
|
9
|
+
with: Throttle.refused(:forward_auth) { head :too_many_requests }
|
|
10
10
|
|
|
11
11
|
def check
|
|
12
12
|
target = Forwarded.target(request)
|
|
@@ -15,13 +15,21 @@ module Masks
|
|
|
15
15
|
|
|
16
16
|
client = Client.forwarding_to(target)
|
|
17
17
|
return head(:forbidden) if client.nil?
|
|
18
|
+
return basic(client, target) if ActionController::HttpAuthentication::Basic.has_basic_credentials?(request)
|
|
18
19
|
|
|
19
20
|
held = ForwardSession.held(cookies[ForwardSession::COOKIE], client)
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
21
|
+
decided = client.forward_rules.decide(path: target.path, method: forwarded_method, address: request.remote_ip,
|
|
22
|
+
actor: held&.actor)
|
|
23
|
+
|
|
24
|
+
case decided.outcome
|
|
25
|
+
when ForwardAuthRules::OPEN then admit(held&.actor)
|
|
26
|
+
when ForwardAuthRules::DENY then head(:forbidden)
|
|
27
|
+
when ForwardAuthRules::ONE_FACTOR then held ? admit(held.actor) : ask_to_sign_in(target)
|
|
28
|
+
when ForwardAuthRules::TWO_FACTOR then held&.session&.multi_factored? ? admit(held.actor) : ask_to_sign_in(target)
|
|
29
|
+
else
|
|
30
|
+
complete = held if held&.session&.complete?
|
|
31
|
+
decided.outcome == ForwardAuthRules::TOKEN ? admit_presented(client, complete) : answer(complete, target)
|
|
32
|
+
end
|
|
25
33
|
end
|
|
26
34
|
|
|
27
35
|
def start
|
|
@@ -29,8 +37,20 @@ module Masks
|
|
|
29
37
|
client = Client.forwarding_to(target)
|
|
30
38
|
|
|
31
39
|
return render(plain: t("forward_auth.unknown"), status: :not_found) if client.nil?
|
|
32
|
-
|
|
33
|
-
|
|
40
|
+
|
|
41
|
+
decided = client.forward_rules.decide(path: target.path, method: "GET", address: request.remote_ip, actor: current_actor)
|
|
42
|
+
|
|
43
|
+
return render(plain: t("forward_auth.denied", app: client.name), status: :forbidden) if decided.outcome == ForwardAuthRules::DENY
|
|
44
|
+
return send_to_login(decided) if current_actor.nil?
|
|
45
|
+
|
|
46
|
+
unless client.admits?(current_actor)
|
|
47
|
+
return redirect_to(target.to_s, allow_other_host: true) if decided.outcome == ForwardAuthRules::OPEN
|
|
48
|
+
|
|
49
|
+
return render(plain: t("forward_auth.not_admitted", app: client.name), status: :forbidden)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
return step_up if decided.outcome == ForwardAuthRules::TWO_FACTOR && !current_session.multi_factored?
|
|
53
|
+
return step_up if [ ForwardAuthRules::ONE_FACTOR, ForwardAuthRules::OPEN ].exclude?(decided.outcome) && !current_session.complete?
|
|
34
54
|
|
|
35
55
|
code = ForwardCode.mint!(actor: current_actor, client: client, session: current_session,
|
|
36
56
|
payload: { "return_to" => target.to_s })
|
|
@@ -67,15 +87,87 @@ module Masks
|
|
|
67
87
|
redirect_to code.return_to, allow_other_host: true
|
|
68
88
|
end
|
|
69
89
|
|
|
70
|
-
def
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
90
|
+
def basic(client, target)
|
|
91
|
+
identifier, secret = ActionController::HttpAuthentication::Basic.user_name_and_password(request)
|
|
92
|
+
outcome = AppPasswords.authenticate(client: client, identifier: identifier, secret: secret, via: "forward_auth")
|
|
93
|
+
|
|
94
|
+
return head(:too_many_requests) if outcome.throttled?
|
|
95
|
+
return head(:unauthorized) if outcome.actor.nil?
|
|
96
|
+
|
|
97
|
+
decided = client.forward_rules.decide(path: target.path, method: forwarded_method, address: request.remote_ip,
|
|
98
|
+
actor: outcome.actor)
|
|
99
|
+
return head(:forbidden) if decided.outcome == ForwardAuthRules::DENY
|
|
100
|
+
|
|
101
|
+
admit(outcome.actor)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def answer(held, target)
|
|
105
|
+
held ? admit(held.actor) : ask_to_sign_in(target)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def ask_to_sign_in(target)
|
|
109
|
+
return head(:unauthorized) if params[:status] == "401"
|
|
110
|
+
|
|
111
|
+
redirect_to forward_auth_start_url(rd: target.to_s), allow_other_host: true
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def send_to_login(decided)
|
|
115
|
+
Login.new(store: login_store).relax! if decided.outcome == ForwardAuthRules::ONE_FACTOR
|
|
116
|
+
|
|
117
|
+
redirect_to login_path(return_to: request.fullpath)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def step_up
|
|
121
|
+
Login.new(store: login_store).step_up!
|
|
122
|
+
|
|
123
|
+
redirect_to login_path(return_to: request.fullpath)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def admit_presented(client, held)
|
|
127
|
+
return admit(held.actor) if held
|
|
128
|
+
|
|
129
|
+
token = presented_token(client)
|
|
130
|
+
return admit(token.actor, machine: token.client) if token
|
|
131
|
+
|
|
132
|
+
response.headers["WWW-Authenticate"] = %(Bearer realm="#{client.forward_origin}")
|
|
133
|
+
head :unauthorized
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def presented_token(client)
|
|
137
|
+
scheme, secret = request.authorization.to_s.split(" ", 2)
|
|
138
|
+
return nil unless scheme.to_s.casecmp?("Bearer") && secret.present?
|
|
139
|
+
|
|
140
|
+
claims = AccessToken.decode(secret.strip, issuer: issuer)
|
|
141
|
+
token = AccessToken.live.includes(:actor, :client).find_by(digest: claims["jti"])
|
|
142
|
+
|
|
143
|
+
return nil if token.nil? || token.bound? || token.client.nil?
|
|
144
|
+
return nil unless token.client_id == client.id || Array(token.audience).include?(client.forward_origin)
|
|
145
|
+
return nil if token.actor && (token.actor.suspended? || !client.admits?(token.actor))
|
|
146
|
+
|
|
147
|
+
token
|
|
148
|
+
rescue JWT::DecodeError
|
|
149
|
+
nil
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def admit(actor, machine: nil)
|
|
153
|
+
if actor
|
|
154
|
+
response.headers["Remote-User"] = actor.nickname.presence || actor.identifier
|
|
155
|
+
response.headers["Remote-Email"] = actor.email if actor.email_verified_at.present?
|
|
156
|
+
response.headers["Remote-Name"] = actor.name if actor.name.present?
|
|
157
|
+
response.headers["Remote-Groups"] = actor.group_labels.join(",")
|
|
158
|
+
elsif machine
|
|
159
|
+
response.headers["Remote-User"] = machine.client_id
|
|
160
|
+
response.headers["Remote-Name"] = machine.name
|
|
161
|
+
response.headers["Remote-Groups"] = ""
|
|
162
|
+
end
|
|
75
163
|
|
|
76
164
|
head :ok
|
|
77
165
|
end
|
|
78
166
|
|
|
167
|
+
def forwarded_method
|
|
168
|
+
request.headers["X-Forwarded-Method"].presence || request.headers["X-Original-Method"].presence || "GET"
|
|
169
|
+
end
|
|
170
|
+
|
|
79
171
|
def return_to
|
|
80
172
|
raw = request.query_string[/(?:\A|&)rd=(.*)\z/m, 1].to_s
|
|
81
173
|
|
|
@@ -5,9 +5,9 @@ module Masks
|
|
|
5
5
|
|
|
6
6
|
INACTIVE = { "active" => false }.freeze
|
|
7
7
|
|
|
8
|
-
rate_limit
|
|
8
|
+
rate_limit **Throttle.limits(:introspection),
|
|
9
9
|
by: -> { [ current_tenant.id, request.remote_ip ].join(":") },
|
|
10
|
-
with:
|
|
10
|
+
with: Throttle.refused(:introspection) { render json: INACTIVE, status: :too_many_requests }
|
|
11
11
|
|
|
12
12
|
def create
|
|
13
13
|
client = authenticate_client!
|
|
@@ -8,26 +8,22 @@ module Masks
|
|
|
8
8
|
|
|
9
9
|
skip_forgery_protection
|
|
10
10
|
|
|
11
|
-
rate_limit
|
|
12
|
-
within: 3.minutes, only: :update, if: -> { verifying? },
|
|
11
|
+
rate_limit **Throttle.limits(:attempt), only: :update, if: -> { verifying? },
|
|
13
12
|
by: -> { [ current_tenant.id, request.remote_ip ].join(":") },
|
|
14
|
-
with:
|
|
13
|
+
with: Throttle.refused(:attempt) { too_many("too-many-attempts") }
|
|
15
14
|
|
|
16
|
-
rate_limit
|
|
17
|
-
within: 3.minutes, only: :update, name: "identifier", if: -> { verifying? },
|
|
15
|
+
rate_limit **Throttle.limits(:account_attempt), only: :update, name: "identifier", if: -> { verifying? },
|
|
18
16
|
by: -> { [ current_tenant.id, attempted_account ].join(":") },
|
|
19
|
-
with:
|
|
17
|
+
with: Throttle.refused(:account_attempt) { too_many("too-many-attempts-for-account") }
|
|
20
18
|
|
|
21
|
-
rate_limit
|
|
22
|
-
within: 15.minutes, only: :update, name: "recovery",
|
|
19
|
+
rate_limit **Throttle.limits(:recovery), only: :update, name: "recovery",
|
|
23
20
|
if: -> { Login.limit_for(params[:event]) == :sending },
|
|
24
21
|
by: -> { [ current_tenant.id, request.remote_ip ].join(":") },
|
|
25
|
-
with:
|
|
22
|
+
with: Throttle.refused(:recovery) { too_many("too-many-attempts") }
|
|
26
23
|
|
|
27
|
-
rate_limit
|
|
28
|
-
within: 3.minutes, only: %i[provider posted_provider], name: "provider",
|
|
24
|
+
rate_limit **Throttle.limits(:attempt), only: %i[provider posted_provider], name: "provider",
|
|
29
25
|
by: -> { [ current_tenant.id, request.remote_ip ].join(":") },
|
|
30
|
-
with:
|
|
26
|
+
with: Throttle.refused(:attempt) { too_many("too-many-attempts") }
|
|
31
27
|
|
|
32
28
|
before_action :refuse_blocked_agent
|
|
33
29
|
before_action :verify_authenticity_token, except: :posted_provider
|
|
@@ -35,9 +31,9 @@ module Masks
|
|
|
35
31
|
|
|
36
32
|
def show
|
|
37
33
|
hold_return_to
|
|
38
|
-
return redirect_to after_login_path if current_actor && pending.nil?
|
|
39
|
-
|
|
40
34
|
@login = run
|
|
35
|
+
return redirect_to after_login_path if current_actor && pending.nil? && !@login.stepping_up?
|
|
36
|
+
|
|
41
37
|
@login.carry!(flash[:warnings]) if flash[:warnings].present?
|
|
42
38
|
|
|
43
39
|
redirect_to after_login_path if @login.settled?
|
|
@@ -108,6 +104,8 @@ module Masks
|
|
|
108
104
|
end
|
|
109
105
|
|
|
110
106
|
def take_posted(handle)
|
|
107
|
+
return {} unless Replay.first?("provider-callback", handle, expires_in: POSTED_WINDOW)
|
|
108
|
+
|
|
111
109
|
key = posted_key(handle)
|
|
112
110
|
held = ::Rails.cache.read(key)
|
|
113
111
|
::Rails.cache.delete(key)
|
|
@@ -153,8 +151,9 @@ module Masks
|
|
|
153
151
|
if login.touched?(:first_factor)
|
|
154
152
|
sign_in(login.actor, amr: login.amr, policy: login.policy, first_factor: login.first_factored_by,
|
|
155
153
|
authenticated_at: login.authenticated_at)
|
|
154
|
+
current_session.update!(second_factor_skipped: true) if login.skipped_second_factor?
|
|
156
155
|
elsif login.signed_in? && login.touched?(:second_factor)
|
|
157
|
-
current_session.update!(amr: Array(current_session.amr) | login.amr)
|
|
156
|
+
current_session.update!(amr: Array(current_session.amr) | login.amr, second_factor_skipped: false)
|
|
158
157
|
end
|
|
159
158
|
|
|
160
159
|
forget_login
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
module Masks
|
|
2
|
+
module Server
|
|
3
|
+
class OrganizationAccessController < OrganizationMembersController
|
|
4
|
+
def create
|
|
5
|
+
own!
|
|
6
|
+
|
|
7
|
+
asked = asked!
|
|
8
|
+
role = params[:role].to_s
|
|
9
|
+
|
|
10
|
+
asked.grant!(label: "#{@organization.key}:#{role}", by: current_actor, journey: Journey.account(current_actor))
|
|
11
|
+
|
|
12
|
+
back(notice: t("organization_access.granted", identifier: asked.actor.identifier, app: asked.client.name,
|
|
13
|
+
role: role, organization: @organization.name))
|
|
14
|
+
rescue AccessRequest::Refused => e
|
|
15
|
+
back(alert: e.message)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def destroy
|
|
19
|
+
own!
|
|
20
|
+
|
|
21
|
+
asked = asked!
|
|
22
|
+
asked.decline!(by: current_actor, journey: Journey.account(current_actor))
|
|
23
|
+
|
|
24
|
+
back(notice: t("organization_access.declined", identifier: asked.actor.identifier, app: asked.client.name))
|
|
25
|
+
rescue AccessRequest::Refused => e
|
|
26
|
+
back(alert: e.message)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def asked!
|
|
32
|
+
id = params[:id].to_s[/\A\d+\z/]
|
|
33
|
+
|
|
34
|
+
(id && AccessRequest.for_organization(@organization).includes(:actor, :client).find_by(id: id)) ||
|
|
35
|
+
raise(AccessRequest::Refused, t("organization_access.unknown", organization: @organization.name))
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -7,9 +7,9 @@ module Masks
|
|
|
7
7
|
back(alert: t("organization_members.owners_only", organization: @organization.name))
|
|
8
8
|
end
|
|
9
9
|
|
|
10
|
-
rate_limit
|
|
10
|
+
rate_limit **Throttle.limits(:invitation), only: %i[create resend],
|
|
11
11
|
by: -> { [ current_tenant.id, current_actor&.id || request.remote_ip ].join(":") },
|
|
12
|
-
with:
|
|
12
|
+
with: Throttle.refused(:invitation) { back(alert: t("organization_members.slow_down")) }
|
|
13
13
|
|
|
14
14
|
before_action :require_actor
|
|
15
15
|
before_action :require_membership
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
module Masks
|
|
2
2
|
module Server
|
|
3
3
|
class PasswordsController < ApplicationController
|
|
4
|
-
rate_limit
|
|
5
|
-
within: 3.minutes,
|
|
4
|
+
rate_limit **Throttle.limits(:account_attempt),
|
|
6
5
|
by: -> { [ current_tenant.id, current_actor&.id ].join(":") },
|
|
7
|
-
with:
|
|
6
|
+
with: Throttle.refused(:account_attempt) { refuse(t("passwords.too_many_attempts")) }
|
|
8
7
|
|
|
9
8
|
before_action :require_actor
|
|
10
9
|
|
|
@@ -3,9 +3,9 @@ module Masks
|
|
|
3
3
|
class PushedAuthorizationsController < ApplicationController
|
|
4
4
|
include TokenPresented
|
|
5
5
|
|
|
6
|
-
rate_limit
|
|
6
|
+
rate_limit **Throttle.limits(:pushed_authorization),
|
|
7
7
|
by: -> { [ current_tenant.id, request.remote_ip ].join(":") },
|
|
8
|
-
with:
|
|
8
|
+
with: Throttle.refused(:pushed_authorization) { slow_down }
|
|
9
9
|
|
|
10
10
|
def create
|
|
11
11
|
client = authenticate_client!
|
|
@@ -3,9 +3,9 @@ module Masks
|
|
|
3
3
|
class ReceivedSignalsController < ApplicationController
|
|
4
4
|
skip_forgery_protection
|
|
5
5
|
|
|
6
|
-
rate_limit
|
|
6
|
+
rate_limit **Throttle.limits(:signal), only: :create,
|
|
7
7
|
by: -> { [ current_tenant.id, request.remote_ip ].join(":") },
|
|
8
|
-
with:
|
|
8
|
+
with: Throttle.refused(:signal) { render json: { "err" => "invalid_request", "description" => "too many events from this address" }, status: :too_many_requests }
|
|
9
9
|
|
|
10
10
|
def create
|
|
11
11
|
ReceivedSignal.new(request.raw_post, issuer: issuer).receive!
|
|
@@ -3,9 +3,9 @@ module Masks
|
|
|
3
3
|
class RegistrationsController < ApplicationController
|
|
4
4
|
skip_forgery_protection
|
|
5
5
|
|
|
6
|
-
rate_limit
|
|
6
|
+
rate_limit **Throttle.limits(:registration), only: :create,
|
|
7
7
|
by: -> { [ current_tenant.id, request.remote_ip ].join(":") },
|
|
8
|
-
with:
|
|
8
|
+
with: Throttle.refused(:registration) {
|
|
9
9
|
render json: {
|
|
10
10
|
"error" => "too_many_requests",
|
|
11
11
|
"error_description" => "too many registrations from this address"
|
|
@@ -3,9 +3,9 @@ module Masks
|
|
|
3
3
|
class RevocationsController < ApplicationController
|
|
4
4
|
include TokenPresented
|
|
5
5
|
|
|
6
|
-
rate_limit
|
|
6
|
+
rate_limit **Throttle.limits(:revocation),
|
|
7
7
|
by: -> { [ current_tenant.id, request.remote_ip ].join(":") },
|
|
8
|
-
with:
|
|
8
|
+
with: Throttle.refused(:revocation) {
|
|
9
9
|
render json: {
|
|
10
10
|
"error" => "slow_down",
|
|
11
11
|
"error_description" => "too many revocation requests from this address"
|
|
@@ -5,9 +5,9 @@ module Masks
|
|
|
5
5
|
|
|
6
6
|
skip_forgery_protection only: %i[sso slo]
|
|
7
7
|
|
|
8
|
-
rate_limit
|
|
8
|
+
rate_limit **Throttle.limits(:saml), only: %i[sso initiate slo],
|
|
9
9
|
by: -> { [ current_tenant.id, request.remote_ip ].join(":") },
|
|
10
|
-
with:
|
|
10
|
+
with: Throttle.refused(:saml) { refuse("too many sign-in requests from this address", status: :too_many_requests) }
|
|
11
11
|
|
|
12
12
|
rescue_from SamlIdentity::Refused do |refusal|
|
|
13
13
|
refuse(refusal.message)
|
|
@@ -5,11 +5,10 @@ module Masks
|
|
|
5
5
|
|
|
6
6
|
before_action :require_trusted_device
|
|
7
7
|
|
|
8
|
-
rate_limit
|
|
9
|
-
within: 3.minutes,
|
|
8
|
+
rate_limit **Throttle.limits(:account_attempt),
|
|
10
9
|
only: :create,
|
|
11
10
|
by: -> { [ current_tenant.id, current_actor&.id ].join(":") },
|
|
12
|
-
with:
|
|
11
|
+
with: Throttle.refused(:account_attempt) { refuse(t("sign_in_approvals.too_many_attempts")) }
|
|
13
12
|
|
|
14
13
|
def create
|
|
15
14
|
approval = SignInApproval.matching(actor: current_actor, code: params[:code], from: current_device)
|
|
@@ -9,9 +9,9 @@ module Masks
|
|
|
9
9
|
|
|
10
10
|
skip_forgery_protection
|
|
11
11
|
|
|
12
|
-
rate_limit
|
|
12
|
+
rate_limit **Throttle.limits(:token),
|
|
13
13
|
by: -> { [ current_tenant.id, request.remote_ip ].join(":") },
|
|
14
|
-
with:
|
|
14
|
+
with: Throttle.refused(:token) { slow_down }
|
|
15
15
|
|
|
16
16
|
def create
|
|
17
17
|
return unsupported_grant_type unless Client::GRANT_TYPES.include?(params[:grant_type].to_s)
|