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
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
module Masks
|
|
2
2
|
module Server
|
|
3
3
|
class VerificationsController < ApplicationController
|
|
4
|
-
rate_limit
|
|
5
|
-
within: 15.minutes,
|
|
4
|
+
rate_limit **Throttle.limits(:recovery),
|
|
6
5
|
by: -> { [ current_tenant.id, current_actor&.id ].join(":") },
|
|
7
|
-
with:
|
|
6
|
+
with: Throttle.refused(:recovery) { redirect_to root_path, alert: t("verifications.too_many_requests") }
|
|
8
7
|
|
|
9
8
|
before_action :require_actor
|
|
10
9
|
|
|
@@ -102,6 +102,13 @@ module Masks
|
|
|
102
102
|
Masks::Server::Group.find_by(key: key) || refuse!("no group keyed #{key}")
|
|
103
103
|
end
|
|
104
104
|
|
|
105
|
+
def access_request!(id)
|
|
106
|
+
held = id.to_s[/\A\d+\z/] && Masks::Server::AccessRequest.open.find_by(id: id)
|
|
107
|
+
|
|
108
|
+
managed!(held.actor) && held if held
|
|
109
|
+
held || refuse!("no access request with that id is waiting")
|
|
110
|
+
end
|
|
111
|
+
|
|
105
112
|
def invited!(actor)
|
|
106
113
|
actor_created!(actor, invited: true)
|
|
107
114
|
|
|
@@ -3,6 +3,8 @@ module Masks
|
|
|
3
3
|
module Manage
|
|
4
4
|
module Mutations
|
|
5
5
|
class CreateGroup < BaseMutation
|
|
6
|
+
include GroupSource
|
|
7
|
+
|
|
6
8
|
requires :security
|
|
7
9
|
|
|
8
10
|
argument :key, String, description: "Lowercase letters, digits, dots, dashes, and underscores. Apps see it in the groups claim."
|
|
@@ -10,13 +12,13 @@ module Masks
|
|
|
10
12
|
|
|
11
13
|
field :group, Types::GroupType, null: false
|
|
12
14
|
|
|
13
|
-
def resolve(key:, name
|
|
14
|
-
group = Masks::Server::Group.new(key: key, name: name)
|
|
15
|
+
def resolve(key:, name:, **source)
|
|
16
|
+
group = fill_source(Masks::Server::Group.new(key: key, name: name), **source)
|
|
15
17
|
save!(group)
|
|
16
18
|
|
|
17
|
-
audit!(Masks::Server::Event::GROUP_CREATED, group: group.key)
|
|
19
|
+
audit!(Masks::Server::Event::GROUP_CREATED, group: group.key, source: group.source)
|
|
18
20
|
|
|
19
|
-
{ group: group }
|
|
21
|
+
{ group: group.reload }
|
|
20
22
|
end
|
|
21
23
|
end
|
|
22
24
|
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
module Masks
|
|
2
|
+
module Server
|
|
3
|
+
module Manage
|
|
4
|
+
module Mutations
|
|
5
|
+
class DeclineAccess < BaseMutation
|
|
6
|
+
requires :security
|
|
7
|
+
|
|
8
|
+
description "Declines a waiting access request. The actor is told by email and can ask again in an hour."
|
|
9
|
+
|
|
10
|
+
argument :id, ID
|
|
11
|
+
|
|
12
|
+
field :access_request, Types::AccessRequestType, null: false
|
|
13
|
+
|
|
14
|
+
def resolve(id:)
|
|
15
|
+
asked = access_request!(id)
|
|
16
|
+
|
|
17
|
+
asked.decline!(by: viewer, journey: Masks::Server::Journey.manage(viewer))
|
|
18
|
+
|
|
19
|
+
{ access_request: asked }
|
|
20
|
+
rescue Masks::Server::AccessRequest::Refused => e
|
|
21
|
+
refuse!(e.message)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
module Masks
|
|
2
|
+
module Server
|
|
3
|
+
module Manage
|
|
4
|
+
module Mutations
|
|
5
|
+
class GrantAccess < BaseMutation
|
|
6
|
+
requires :security
|
|
7
|
+
|
|
8
|
+
description "Grants a waiting access request by putting the actor in one of the groups the client allows. " \
|
|
9
|
+
"Name the group with label, or leave it out for the first one the client allows."
|
|
10
|
+
|
|
11
|
+
argument :id, ID
|
|
12
|
+
argument :label, String, required: false
|
|
13
|
+
|
|
14
|
+
field :access_request, Types::AccessRequestType, null: false
|
|
15
|
+
|
|
16
|
+
def resolve(id:, label: nil)
|
|
17
|
+
asked = access_request!(id)
|
|
18
|
+
|
|
19
|
+
asked.grant!(label: label, by: viewer, journey: Masks::Server::Journey.manage(viewer))
|
|
20
|
+
|
|
21
|
+
{ access_request: asked }
|
|
22
|
+
rescue Masks::Server::AccessRequest::Refused => e
|
|
23
|
+
refuse!(e.message)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
module Masks
|
|
2
|
+
module Server
|
|
3
|
+
module Manage
|
|
4
|
+
module Mutations
|
|
5
|
+
module GroupSource
|
|
6
|
+
extend ActiveSupport::Concern
|
|
7
|
+
|
|
8
|
+
included do
|
|
9
|
+
argument :source, String, required: false,
|
|
10
|
+
description: "manual, rule, provider, or group. A sourced group fills itself and keeps anyone added by hand."
|
|
11
|
+
argument :rule, [ Types::GroupConditionInput ], required: false,
|
|
12
|
+
description: "For rule: up to five conditions, all of which must hold."
|
|
13
|
+
argument :provider_key, String, required: false, description: "For provider: the provider whose sign-ins list groups."
|
|
14
|
+
argument :upstream_group, String, required: false, description: "For provider: the group name the provider lists."
|
|
15
|
+
argument :parent_labels, [ String ], required: false,
|
|
16
|
+
description: "For group: group keys and organization roles, as key:role, the group holds everyone from."
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def fill_source(group, source: nil, rule: nil, provider_key: nil, upstream_group: nil, parent_labels: nil)
|
|
22
|
+
group.source = source unless source.nil?
|
|
23
|
+
group.rule = rule.map(&:to_h) unless rule.nil?
|
|
24
|
+
group.provider = provider_key.blank? ? nil : provider!(provider_key) unless provider_key.nil?
|
|
25
|
+
group.upstream_group = upstream_group unless upstream_group.nil?
|
|
26
|
+
group.parent_labels = parent_labels unless parent_labels.nil?
|
|
27
|
+
|
|
28
|
+
group
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -8,12 +8,15 @@ module Masks
|
|
|
8
8
|
description "Issues a bind DN and secret an app uses to search this tenant's LDAP directory. The secret is returned once."
|
|
9
9
|
|
|
10
10
|
argument :label, String, description: "Lowercase letters, digits, dots, dashes, and underscores. It becomes the cn of the bind DN."
|
|
11
|
+
argument :client_id, ID, required: false,
|
|
12
|
+
description: "The client this app is registered as. People who make an app password for that client can bind with it on a connection that bound with this credential first."
|
|
11
13
|
|
|
12
14
|
field :ldap_credential, Types::LdapCredentialType, null: false
|
|
13
15
|
field :secret, String, null: false
|
|
14
16
|
|
|
15
|
-
def resolve(label:)
|
|
16
|
-
|
|
17
|
+
def resolve(label:, client_id: nil)
|
|
18
|
+
client = client_id.present? ? client!(client_id) : nil
|
|
19
|
+
credential = Masks::Server::LdapCredential.issue!(label: label, by: viewer, client: client)
|
|
17
20
|
|
|
18
21
|
audit!(Masks::Server::Event::LDAP_CREDENTIAL_ISSUED, label: credential.label)
|
|
19
22
|
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
module Masks
|
|
2
|
+
module Server
|
|
3
|
+
module Manage
|
|
4
|
+
module Mutations
|
|
5
|
+
class RefreshGroup < BaseMutation
|
|
6
|
+
requires :security
|
|
7
|
+
|
|
8
|
+
description "Fills a sourced group now instead of waiting for the hourly run."
|
|
9
|
+
|
|
10
|
+
argument :key, String
|
|
11
|
+
|
|
12
|
+
field :group, Types::GroupType, null: false
|
|
13
|
+
|
|
14
|
+
def resolve(key:)
|
|
15
|
+
{ group: group!(key).refresh!(by: viewer) }
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module Masks
|
|
2
|
+
module Server
|
|
3
|
+
module Manage
|
|
4
|
+
module Mutations
|
|
5
|
+
class RunHealthChecks < BaseMutation
|
|
6
|
+
requires :support
|
|
7
|
+
|
|
8
|
+
description "Runs every health check now, instead of waiting for the next scheduled run."
|
|
9
|
+
|
|
10
|
+
field :health, [ Types::HealthCheckType ], null: false
|
|
11
|
+
|
|
12
|
+
def resolve
|
|
13
|
+
{ health: Masks::Server::Health.check!.sort_by(&:name) }
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -32,6 +32,8 @@ module Masks
|
|
|
32
32
|
argument :saml_slo_url, String, required: false,
|
|
33
33
|
description: "The application's single logout URL, which receives masks' logout messages over HTTP-POST. Empty stops single logout."
|
|
34
34
|
argument :consent_required, Boolean, required: false
|
|
35
|
+
argument :app_passwords, Boolean, required: false,
|
|
36
|
+
description: "Whether people may make an app password for this client. An app password signs in over LDAP and forward auth without a second factor, for this client alone."
|
|
35
37
|
argument :access_token_lifetime, Integer, required: false,
|
|
36
38
|
description: "Seconds an access token lasts, from 60 to a day. Zero goes back to an hour."
|
|
37
39
|
argument :refresh_token_lifetime, Integer, required: false,
|
|
@@ -41,6 +43,10 @@ module Masks
|
|
|
41
43
|
"from 300 to 400 days. Zero goes back to 90 days."
|
|
42
44
|
argument :allowed_groups, [ String ], required: false,
|
|
43
45
|
description: "Group keys, or organization roles as key:role, whose people may sign in to this client. Empty lets everyone in."
|
|
46
|
+
argument :forward_auth_rules, GraphQL::Types::JSON, required: false,
|
|
47
|
+
description: "The ordered rules forward auth follows, for an app behind a proxy only. Each rule has `paths` " \
|
|
48
|
+
"(absolute, with * for one segment and ** for any depth), `methods`, `networks` (addresses or ranges), " \
|
|
49
|
+
"and `groups` it covers, and an `outcome` of open, one_factor, two_factor, token, or deny. Empty removes them all."
|
|
44
50
|
argument :sign_in_policy, ID, required: false
|
|
45
51
|
argument :client_uri, String, required: false
|
|
46
52
|
argument :logo_uri, String, required: false
|
|
@@ -3,19 +3,22 @@ module Masks
|
|
|
3
3
|
module Manage
|
|
4
4
|
module Mutations
|
|
5
5
|
class UpdateGroup < BaseMutation
|
|
6
|
+
include GroupSource
|
|
7
|
+
|
|
6
8
|
requires :security
|
|
7
9
|
|
|
8
10
|
argument :key, String
|
|
9
|
-
argument :name, String
|
|
11
|
+
argument :name, String, required: false
|
|
10
12
|
|
|
11
13
|
field :group, Types::GroupType, null: false
|
|
12
14
|
|
|
13
|
-
def resolve(key:, name:)
|
|
15
|
+
def resolve(key:, name: nil, **source)
|
|
14
16
|
group = group!(key)
|
|
15
|
-
group.name = name
|
|
17
|
+
group.name = name unless name.nil?
|
|
18
|
+
fill_source(group, **source)
|
|
16
19
|
save!(group)
|
|
17
20
|
|
|
18
|
-
{ group: group }
|
|
21
|
+
{ group: group.reload }
|
|
19
22
|
end
|
|
20
23
|
end
|
|
21
24
|
end
|
|
@@ -30,14 +30,27 @@ module Masks
|
|
|
30
30
|
argument :key_rotation_days, Integer, required: false,
|
|
31
31
|
description: "Days between scheduled signing key rotations, from 7 to 365. Zero leaves rotation to a manager."
|
|
32
32
|
|
|
33
|
+
argument :invitation_lifetime, Integer, required: false,
|
|
34
|
+
description: "Seconds an invitation to an account stays valid, from an hour to 30 days. Zero goes back to 7 days."
|
|
35
|
+
argument :organization_invitation_lifetime, Integer, required: false,
|
|
36
|
+
description: "Seconds an invitation to an organization stays open after it was last sent, from an hour to 30 days. Zero goes back to 14 days."
|
|
37
|
+
argument :password_reset_lifetime, Integer, required: false,
|
|
38
|
+
description: "Seconds a password reset link stays valid, from 5 minutes to a day. Zero goes back to 30 minutes."
|
|
39
|
+
argument :email_verification_lifetime, Integer, required: false,
|
|
40
|
+
description: "Seconds an email confirmation link stays valid, from an hour to 14 days. Zero goes back to 2 days."
|
|
41
|
+
|
|
33
42
|
field :tenant, Types::TenantType, null: false
|
|
34
43
|
|
|
35
44
|
def resolve(name: nil, dynamic_client_scopes: nil, dynamic_registration: nil,
|
|
36
45
|
named_by: nil, browsers_only: nil, blocked_agents: nil, sign_in_policy: nil,
|
|
37
46
|
suspend_after: nil, delete_after: nil, event_retention_days: nil, risky_networks: nil,
|
|
38
|
-
accent_color: nil, key_rotation_days: nil, ldap_enabled: nil, ldap_password_only: nil)
|
|
47
|
+
accent_color: nil, key_rotation_days: nil, ldap_enabled: nil, ldap_password_only: nil, **link_lifetimes)
|
|
39
48
|
tenant = Current.tenant
|
|
40
49
|
|
|
50
|
+
link_lifetimes.compact.each do |name, seconds|
|
|
51
|
+
tenant[name] = seconds.zero? ? Masks::Server::Tenant.default_link_lifetime(name) : seconds
|
|
52
|
+
end
|
|
53
|
+
|
|
41
54
|
tenant.name = name unless name.nil?
|
|
42
55
|
tenant.accent_color = accent_color unless accent_color.nil?
|
|
43
56
|
tenant.key_rotation_days = key_rotation_days.zero? ? nil : key_rotation_days unless key_rotation_days.nil?
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
module Masks
|
|
2
|
+
module Server
|
|
3
|
+
module Manage
|
|
4
|
+
module Types
|
|
5
|
+
class AccessRequestType < BaseObject
|
|
6
|
+
description "An actor turned away from a client who asked for access. Granting it puts them in one of the groups the client allows."
|
|
7
|
+
|
|
8
|
+
field :id, ID, null: false
|
|
9
|
+
field :actor, ActorType, null: false
|
|
10
|
+
field :client, ClientType, null: false
|
|
11
|
+
field :label, String, description: "The group key, or organization role as key:role, the actor was put in. Null until granted."
|
|
12
|
+
field :decided_by, ActorType
|
|
13
|
+
field :granted_at, GraphQL::Types::ISO8601DateTime
|
|
14
|
+
field :declined_at, GraphQL::Types::ISO8601DateTime
|
|
15
|
+
field :open, Boolean, null: false, method: :open?, description: "True while nobody has granted or declined it."
|
|
16
|
+
field :created_at, GraphQL::Types::ISO8601DateTime, null: false
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -32,6 +32,8 @@ module Masks
|
|
|
32
32
|
field :tokens, [ "Masks::Server::Manage::Types::TokenType" ], null: false
|
|
33
33
|
field :memberships, [ "Masks::Server::Manage::Types::MembershipType" ], null: false
|
|
34
34
|
field :groups, [ "Masks::Server::Manage::Types::GroupType" ], null: false
|
|
35
|
+
field :group_memberships, [ "Masks::Server::Manage::Types::GroupMemberType" ], null: false,
|
|
36
|
+
description: "Each group the person is in, with what put them there."
|
|
35
37
|
field :group_labels, [ String ], null: false,
|
|
36
38
|
description: "What the groups claim carries: each group's key, and each organization role as key:role."
|
|
37
39
|
|
|
@@ -130,6 +132,10 @@ module Masks
|
|
|
130
132
|
object.memberships.includes(:organization).joins(:organization).order("organizations.name")
|
|
131
133
|
end
|
|
132
134
|
|
|
135
|
+
def group_memberships
|
|
136
|
+
object.group_members.includes(:group).joins(:group).order("groups.name")
|
|
137
|
+
end
|
|
138
|
+
|
|
133
139
|
def tokens
|
|
134
140
|
Masks::Server::Token.where(kind: TokenType::GRANTS, actor_id: object.id)
|
|
135
141
|
.live
|
|
@@ -20,6 +20,10 @@ module Masks
|
|
|
20
20
|
description: "Seconds from a sign-in until its refresh tokens stop, however often they are used. Null is 90 days."
|
|
21
21
|
field :allowed_groups, [ String ], null: false,
|
|
22
22
|
description: "Group keys, or organization roles as key:role, whose people may sign in. Empty lets everyone in."
|
|
23
|
+
field :forward_auth_rules, GraphQL::Types::JSON, null: false,
|
|
24
|
+
description: "The ordered rules forward auth follows for an app behind a proxy. Each has `paths`, `methods`, " \
|
|
25
|
+
"`networks`, and `groups` it covers, and an `outcome` of open, one_factor, two_factor, token, or deny. " \
|
|
26
|
+
"The first rule that covers a request decides it. With no rule, a signed-in person who may sign in is let through."
|
|
23
27
|
field :namespaces, [ "Masks::Server::Manage::Types::NamespaceType" ], null: false
|
|
24
28
|
field :token_endpoint_auth_method, String, null: false
|
|
25
29
|
field :subject_type, String, null: false
|
|
@@ -55,6 +59,8 @@ module Masks
|
|
|
55
59
|
description: "Where masks posts a signed LogoutRequest when a person who signed in to this SAML application signs out, and its LogoutResponse when the application asks."
|
|
56
60
|
field :require_signed_request_object, Boolean, null: false
|
|
57
61
|
field :consent_required, Boolean, null: false
|
|
62
|
+
field :app_passwords, Boolean, null: false,
|
|
63
|
+
description: "Whether people may make an app password for this client, which LDAP and forward auth accept in place of their password and second factor."
|
|
58
64
|
field :sign_in_policy, SignInPolicyType
|
|
59
65
|
field :dynamic, Boolean, null: false
|
|
60
66
|
field :approved_at, GraphQL::Types::ISO8601DateTime
|
|
@@ -65,6 +71,8 @@ module Masks
|
|
|
65
71
|
description: "The machines this app enrolled that are not archived."
|
|
66
72
|
field :last_seen_at, GraphQL::Types::ISO8601DateTime,
|
|
67
73
|
description: "When this client last got a token. Null if it never has."
|
|
74
|
+
field :access_requests, [ "Masks::Server::Manage::Types::AccessRequestType" ], null: false,
|
|
75
|
+
description: "Actors turned away from this client who asked for access and are waiting."
|
|
68
76
|
field :archived_at, GraphQL::Types::ISO8601DateTime
|
|
69
77
|
field :secret_expires_at, GraphQL::Types::ISO8601DateTime
|
|
70
78
|
field :created_at, GraphQL::Types::ISO8601DateTime, null: false
|
|
@@ -87,6 +95,10 @@ module Masks
|
|
|
87
95
|
object.enrolled.active.order(:name)
|
|
88
96
|
end
|
|
89
97
|
|
|
98
|
+
def access_requests
|
|
99
|
+
object.access_requests.open.includes(:actor).order(:created_at)
|
|
100
|
+
end
|
|
101
|
+
|
|
90
102
|
def last_seen_at
|
|
91
103
|
Masks::Server::Token.where(kind: TokenType::GRANTS, client_id: object.id).maximum(:created_at)
|
|
92
104
|
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
module Masks
|
|
2
|
+
module Server
|
|
3
|
+
module Manage
|
|
4
|
+
module Types
|
|
5
|
+
class GroupConditionInput < GraphQL::Schema::InputObject
|
|
6
|
+
description "One condition of a group's rule."
|
|
7
|
+
|
|
8
|
+
argument :kind, String, description: "email_domain or profile."
|
|
9
|
+
argument :field, String, required: false,
|
|
10
|
+
description: "For profile: name, given_name, family_name, middle_name, nickname, gender, zoneinfo, or locale."
|
|
11
|
+
argument :value, String, description: "The domain a verified email address ends with, or the value the profile field equals."
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
module Masks
|
|
2
|
+
module Server
|
|
3
|
+
module Manage
|
|
4
|
+
module Types
|
|
5
|
+
class GroupConditionType < BaseObject
|
|
6
|
+
description "One condition of a group's rule. Every condition must hold for a person to be in the group."
|
|
7
|
+
|
|
8
|
+
field :kind, String, null: false, description: "email_domain or profile."
|
|
9
|
+
field :field, String, description: "The profile field a profile condition reads."
|
|
10
|
+
field :value, String, null: false,
|
|
11
|
+
description: "The domain a verified email address ends with, or the value the profile field equals. " \
|
|
12
|
+
"A profile condition matches only people whose profile a directory sets."
|
|
13
|
+
|
|
14
|
+
def kind
|
|
15
|
+
object["kind"]
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def field
|
|
19
|
+
object["field"]
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def value
|
|
23
|
+
object["value"]
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
module Masks
|
|
2
|
+
module Server
|
|
3
|
+
module Manage
|
|
4
|
+
module Types
|
|
5
|
+
class GroupMemberType < BaseObject
|
|
6
|
+
description "A person's place in a group, and what put them there."
|
|
7
|
+
|
|
8
|
+
field :group, GroupType, null: false
|
|
9
|
+
field :actor, ActorType, null: false
|
|
10
|
+
field :sourced_by, String, description: "rule, provider, or group when the group's source put them in. Null when a manager added them by hand."
|
|
11
|
+
field :created_at, GraphQL::Types::ISO8601DateTime, null: false
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -3,14 +3,26 @@ module Masks
|
|
|
3
3
|
module Manage
|
|
4
4
|
module Types
|
|
5
5
|
class GroupType < BaseObject
|
|
6
|
-
description "A named set of people in the tenant. The groups claim lists the key of each group a person is in."
|
|
6
|
+
description "A named set of people in the tenant. The groups claim lists the key of each group a person is in. " \
|
|
7
|
+
"A group fills by hand, from a rule, from a provider, or from other groups."
|
|
7
8
|
|
|
8
9
|
field :key, String, null: false
|
|
9
10
|
field :name, String, null: false
|
|
11
|
+
field :source, String, null: false, description: "manual, rule, provider, or group."
|
|
12
|
+
field :rule, [ GroupConditionType ], null: false, description: "The conditions a rule group checks. Empty unless the source is rule."
|
|
13
|
+
field :provider_key, String, description: "The provider a provider group reads at sign-in."
|
|
14
|
+
field :upstream_group, String, description: "The group name the provider lists for people who belong."
|
|
15
|
+
field :parent_labels, [ String ], null: false, description: "The group keys and organization roles, as key:role, a nested group holds everyone from."
|
|
16
|
+
field :refreshed_at, GraphQL::Types::ISO8601DateTime, description: "When the source last filled the group."
|
|
10
17
|
field :member_count, Integer, null: false
|
|
11
18
|
field :members, [ ActorType ], null: false
|
|
19
|
+
field :memberships, [ GroupMemberType ], null: false, description: "Each member with what put them in the group."
|
|
12
20
|
field :created_at, GraphQL::Types::ISO8601DateTime, null: false
|
|
13
21
|
|
|
22
|
+
def provider_key
|
|
23
|
+
object.provider&.key
|
|
24
|
+
end
|
|
25
|
+
|
|
14
26
|
def member_count
|
|
15
27
|
object.members.count
|
|
16
28
|
end
|
|
@@ -18,6 +30,10 @@ module Masks
|
|
|
18
30
|
def members
|
|
19
31
|
object.actors.order(:nickname)
|
|
20
32
|
end
|
|
33
|
+
|
|
34
|
+
def memberships
|
|
35
|
+
object.members.includes(:actor).sort_by { |member| member.actor.nickname.to_s }
|
|
36
|
+
end
|
|
21
37
|
end
|
|
22
38
|
end
|
|
23
39
|
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module Masks
|
|
2
|
+
module Server
|
|
3
|
+
module Manage
|
|
4
|
+
module Types
|
|
5
|
+
class HealthCheckType < BaseObject
|
|
6
|
+
description "The latest result of one health check. Checks run every 10 minutes."
|
|
7
|
+
|
|
8
|
+
field :name, ID, null: false, description: "jobs, worker, queue, ldap, metrics, mail, origin, domains, keys, or proxy."
|
|
9
|
+
field :status, String, null: false, description: "ok, warning, or failing."
|
|
10
|
+
field :summary, String, null: false, description: "A short word for what the check found, such as none, stale, or silent."
|
|
11
|
+
field :details, GraphQL::Types::JSON, null: false, description: "The figures and names behind the summary."
|
|
12
|
+
field :checked_at, GraphQL::Types::ISO8601DateTime, null: false
|
|
13
|
+
field :changed_at, GraphQL::Types::ISO8601DateTime, null: false, description: "When the status last changed."
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -7,6 +7,7 @@ module Masks
|
|
|
7
7
|
field :label, String, null: false
|
|
8
8
|
field :bind_dn, String, null: false, description: "The DN an app binds as, under ou=services in this tenant's directory."
|
|
9
9
|
field :issued_by, ActorType
|
|
10
|
+
field :client, ClientType, description: "The client whose app passwords bind through this credential, when one was chosen."
|
|
10
11
|
field :used_at, GraphQL::Types::ISO8601DateTime, description: "When an app last bound with it, to within five minutes."
|
|
11
12
|
field :created_at, GraphQL::Types::ISO8601DateTime, null: false
|
|
12
13
|
|
|
@@ -28,6 +28,9 @@ module Masks
|
|
|
28
28
|
field :delete_group, mutation: Mutations::DeleteGroup
|
|
29
29
|
field :add_group_member, mutation: Mutations::AddGroupMember
|
|
30
30
|
field :remove_group_member, mutation: Mutations::RemoveGroupMember
|
|
31
|
+
field :grant_access, mutation: Mutations::GrantAccess
|
|
32
|
+
field :decline_access, mutation: Mutations::DeclineAccess
|
|
33
|
+
field :refresh_group, mutation: Mutations::RefreshGroup
|
|
31
34
|
field :disable_code_factor, mutation: Mutations::DisableCodeFactor
|
|
32
35
|
field :revoke_passkey, mutation: Mutations::RevokePasskey
|
|
33
36
|
field :upload_avatar, mutation: Mutations::UploadAvatar
|
|
@@ -97,6 +100,7 @@ module Masks
|
|
|
97
100
|
field :activate_signing_key, mutation: Mutations::ActivateSigningKey
|
|
98
101
|
field :discard_signing_key, mutation: Mutations::DiscardSigningKey
|
|
99
102
|
field :rotate_signing_key, mutation: Mutations::RotateSigningKey
|
|
103
|
+
field :run_health_checks, mutation: Mutations::RunHealthChecks
|
|
100
104
|
end
|
|
101
105
|
end
|
|
102
106
|
end
|
|
@@ -191,6 +191,13 @@ module Masks
|
|
|
191
191
|
|
|
192
192
|
field :event_actions, [ EventActionType ], null: false
|
|
193
193
|
|
|
194
|
+
field :health, [ HealthCheckType ], null: false,
|
|
195
|
+
description: "The latest result of each health check for this tenant and the server it runs on. " \
|
|
196
|
+
"Empty until the first scheduled run."
|
|
197
|
+
field :release, ReleaseType,
|
|
198
|
+
description: "The newest release when it is newer than this server. Null while the server is current, " \
|
|
199
|
+
"or while MASKS_RELEASE_CHECK is off."
|
|
200
|
+
|
|
194
201
|
LIMIT = 50
|
|
195
202
|
CEILING = 200
|
|
196
203
|
SPAN = 30
|
|
@@ -464,6 +471,23 @@ module Masks
|
|
|
464
471
|
Masks::Server::Event::ACTIONS.sort
|
|
465
472
|
end
|
|
466
473
|
|
|
474
|
+
def health
|
|
475
|
+
Masks::Server::HealthCheck.by_name
|
|
476
|
+
end
|
|
477
|
+
|
|
478
|
+
def release
|
|
479
|
+
found = Masks::Server::Release.available
|
|
480
|
+
|
|
481
|
+
return nil if found.nil?
|
|
482
|
+
|
|
483
|
+
{
|
|
484
|
+
version: found["version"],
|
|
485
|
+
url: found["url"],
|
|
486
|
+
operator_step: found["operator_step"] == true,
|
|
487
|
+
checked_at: Time.iso8601(found["checked_at"])
|
|
488
|
+
}
|
|
489
|
+
end
|
|
490
|
+
|
|
467
491
|
private
|
|
468
492
|
|
|
469
493
|
def actor_scope(search: nil, activated: nil, holds: nil, pending_approval: nil, recovery_requested: nil,
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
module Masks
|
|
2
|
+
module Server
|
|
3
|
+
module Manage
|
|
4
|
+
module Types
|
|
5
|
+
class ReleaseType < BaseObject
|
|
6
|
+
description "A newer release of masks than the one this server runs."
|
|
7
|
+
|
|
8
|
+
field :version, String, null: false
|
|
9
|
+
field :url, String, null: false, description: "The release notes."
|
|
10
|
+
field :operator_step, Boolean, null: false, description: "Whether the release notes carry a line that starts with Operator:."
|
|
11
|
+
field :checked_at, GraphQL::Types::ISO8601DateTime, null: false
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -9,7 +9,7 @@ module Masks
|
|
|
9
9
|
field :origins, [ String ], null: false, description: "Every origin this tenant answers on, each its own issuer."
|
|
10
10
|
|
|
11
11
|
def origins
|
|
12
|
-
[ object.templated_origin, object.custom_origin ].compact
|
|
12
|
+
[ object.templated_origin || object.held_origin, object.custom_origin ].compact
|
|
13
13
|
end
|
|
14
14
|
field :name, String, null: false
|
|
15
15
|
field :named_by, String, null: false
|
|
@@ -31,6 +31,11 @@ module Masks
|
|
|
31
31
|
def event_retention_days
|
|
32
32
|
object.event_retention.in_days.round
|
|
33
33
|
end
|
|
34
|
+
field :invitation_lifetime, Integer, null: false, description: "Seconds an invitation to an account stays valid."
|
|
35
|
+
field :organization_invitation_lifetime, Integer, null: false,
|
|
36
|
+
description: "Seconds an invitation to an organization stays open after it was last sent."
|
|
37
|
+
field :password_reset_lifetime, Integer, null: false, description: "Seconds a password reset link stays valid."
|
|
38
|
+
field :email_verification_lifetime, Integer, null: false, description: "Seconds an email confirmation link stays valid."
|
|
34
39
|
field :ldap_enabled, Boolean, null: false, description: "Whether the LDAP directory answers for this tenant.", method: :ldap?
|
|
35
40
|
field :ldap_password_only, Boolean, null: false,
|
|
36
41
|
description: "Whether a password alone binds to LDAP for an account that would otherwise need a second factor.",
|
|
@@ -29,6 +29,10 @@ module Masks
|
|
|
29
29
|
t("account.index.#{key}", **options)
|
|
30
30
|
end
|
|
31
31
|
|
|
32
|
+
def account_access_requests(organization)
|
|
33
|
+
AccessRequest.for_organization(organization).includes(:actor, :client).order(:created_at)
|
|
34
|
+
end
|
|
35
|
+
|
|
32
36
|
def account_roster(organization)
|
|
33
37
|
organization.memberships.sort_by do |member|
|
|
34
38
|
[ member.pending? ? 1 : 0, member.owner? ? 0 : 1, Members.label(member).to_s.downcase ]
|
|
@@ -34,6 +34,7 @@ module Masks
|
|
|
34
34
|
Members.purge_lapsed!
|
|
35
35
|
Replay.where(expires_at: ...Time.current).in_batches(of: BATCH).delete_all
|
|
36
36
|
ReleasedNickname.lapsed.in_batches(of: BATCH).delete_all
|
|
37
|
+
AccessRequest.lapsed.in_batches(of: BATCH).delete_all
|
|
37
38
|
end
|
|
38
39
|
|
|
39
40
|
def sweep_tokens(cutoff)
|