masks-server 0.10.0 → 0.11.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/masks/server/discovery_controller.rb +36 -0
- data/app/controllers/masks/server/enrollments_controller.rb +17 -0
- data/app/controllers/masks/server/organization_members_controller.rb +3 -1
- data/app/graphql/masks/server/manage/mutations/unenroll_client.rb +28 -0
- data/app/graphql/masks/server/manage/types/client_type.rb +6 -0
- data/app/graphql/masks/server/manage/types/mutation_type.rb +1 -0
- data/app/jobs/masks/server/application_job.rb +28 -0
- data/app/jobs/masks/server/check_domain_claims_job.rb +1 -3
- data/app/jobs/masks/server/cleanup_job.rb +4 -6
- data/app/jobs/masks/server/idle_accounts_job.rb +1 -3
- data/app/jobs/masks/server/prune_queue_job.rb +19 -0
- data/app/jobs/masks/server/refresh_saml_metadata_job.rb +1 -3
- data/app/jobs/masks/server/rotate_signing_keys_job.rb +1 -3
- data/app/jobs/masks/server/text_job.rb +12 -1
- data/app/models/masks/server/app_preset.rb +102 -0
- data/app/models/masks/server/boot_task.rb +41 -0
- data/app/models/masks/server/enrollment_token.rb +1 -1
- data/app/models/masks/server/event.rb +5 -1
- data/app/models/masks/server/job_failures.rb +25 -0
- data/config/locales/de/events.yml +4 -0
- data/config/locales/de/manage/client.yml +1 -1
- data/config/locales/de/manage/machines.yml +13 -0
- data/config/locales/de/manage/namespaces.yml +1 -1
- data/config/locales/en/events.yml +4 -0
- data/config/locales/en/manage/machines.yml +13 -0
- data/config/locales/es/events.yml +4 -0
- data/config/locales/es/handshakes.yml +1 -1
- data/config/locales/es/manage/machines.yml +13 -0
- data/config/locales/fr/events.yml +4 -0
- data/config/locales/fr/handshakes.yml +1 -1
- data/config/locales/fr/manage/client.yml +1 -1
- data/config/locales/fr/manage/clients.yml +1 -1
- data/config/locales/fr/manage/machines.yml +13 -0
- data/config/locales/fr/manage/providers.yml +1 -1
- data/config/routes.rb +1 -0
- data/db/migrate/20261019140000_add_indexes_for_cleanup_and_metrics.rb +10 -0
- data/lib/masks/server/configuration.rb +4 -0
- data/lib/tasks/masks.rake +31 -7
- data/public/masks-assets/.vite/manifest.json +1 -1
- data/public/masks-assets/assets/{manage-DR9qQiBU.js → manage-D4MzaDvM.js} +33 -26
- data/public/masks-assets/assets/manage-D4MzaDvM.js.map +1 -0
- data/version.txt +1 -1
- metadata +12 -3
- data/public/masks-assets/assets/manage-DR9qQiBU.js.map +0 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 608910b5ec386933423f04a3596c4b5d454b38e956449ca66d762d2cd79e3e82
|
|
4
|
+
data.tar.gz: f70b574939800fdcb3407025402e39663e15f8a9c71e2dff473685147e7cf693
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a061d6d81ac514d825d3963c466e99272d224e0250e964e8a057a8e794a7cd1f26955cd8e1cb5753eb418f8c91e5dd2d9b31b98c8bba4eb97d11d7112ce0a40b
|
|
7
|
+
data.tar.gz: c537d6afb5fc139d5e83fd92143f0eb28588f9ec1992e607209f89c5c9f59dfff3502dc0f1196a39c2a19fcec67910458494324a70e739ced310ea73f69168cb
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
module Masks
|
|
2
2
|
module Server
|
|
3
3
|
class DiscoveryController < ApplicationController
|
|
4
|
+
OPENID_ISSUER = "http://openid.net/specs/connect/1.0/issuer".freeze
|
|
5
|
+
LONGEST_RESOURCE = 512
|
|
6
|
+
|
|
4
7
|
def openid
|
|
5
8
|
render json: issuer.discovery
|
|
6
9
|
end
|
|
@@ -13,6 +16,39 @@ module Masks
|
|
|
13
16
|
def resource
|
|
14
17
|
render json: issuer.protected_resource
|
|
15
18
|
end
|
|
19
|
+
|
|
20
|
+
def webfinger
|
|
21
|
+
resource = params[:resource].to_s
|
|
22
|
+
host = resource_host(resource)
|
|
23
|
+
|
|
24
|
+
return head(:bad_request) if host.nil?
|
|
25
|
+
return head(:not_found) unless host.casecmp?(URI.parse(issuer.url).host)
|
|
26
|
+
|
|
27
|
+
asked = Array(params[:rel]).map(&:to_s)
|
|
28
|
+
links = [ { "rel" => OPENID_ISSUER, "href" => issuer.url } ]
|
|
29
|
+
links = links.select { |link| asked.include?(link["rel"]) } if asked.any?
|
|
30
|
+
|
|
31
|
+
response.headers["Access-Control-Allow-Origin"] = "*"
|
|
32
|
+
render json: { "subject" => resource, "links" => links }, content_type: "application/jrd+json"
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def resource_host(resource)
|
|
38
|
+
return nil if resource.blank? || resource.length > LONGEST_RESOURCE
|
|
39
|
+
|
|
40
|
+
if resource.start_with?("acct:")
|
|
41
|
+
account, separator, host = resource.delete_prefix("acct:").rpartition("@")
|
|
42
|
+
|
|
43
|
+
return nil if separator.empty? || account.blank? || host.blank? || host.match?(%r{[/\\\s?#]})
|
|
44
|
+
|
|
45
|
+
host.sub(/:\d+\z/, "")
|
|
46
|
+
elsif resource.match?(%r{\Ahttps?://})
|
|
47
|
+
URI.parse(resource).host
|
|
48
|
+
end
|
|
49
|
+
rescue URI::InvalidURIError
|
|
50
|
+
nil
|
|
51
|
+
end
|
|
16
52
|
end
|
|
17
53
|
end
|
|
18
54
|
end
|
|
@@ -12,6 +12,15 @@ module Masks
|
|
|
12
12
|
}, status: :too_many_requests
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
rate_limit to: 60, within: 1.hour, only: :create, name: "app",
|
|
16
|
+
by: -> { [ current_tenant.id, claimed_client_id ].join(":") },
|
|
17
|
+
with: -> {
|
|
18
|
+
render json: {
|
|
19
|
+
"error" => "too_many_requests",
|
|
20
|
+
"error_description" => "too many enrollment tokens asked for by this app"
|
|
21
|
+
}, status: :too_many_requests
|
|
22
|
+
}
|
|
23
|
+
|
|
15
24
|
before_action :no_store!
|
|
16
25
|
|
|
17
26
|
def create
|
|
@@ -55,6 +64,14 @@ module Masks
|
|
|
55
64
|
client
|
|
56
65
|
end
|
|
57
66
|
|
|
67
|
+
def claimed_client_id
|
|
68
|
+
basic = ActionController::HttpAuthentication::Basic
|
|
69
|
+
|
|
70
|
+
named, = basic.user_name_and_password(request) if basic.has_basic_credentials?(request)
|
|
71
|
+
|
|
72
|
+
(named.presence || params[:client_id]).to_s
|
|
73
|
+
end
|
|
74
|
+
|
|
58
75
|
def not_enrolled
|
|
59
76
|
render json: {
|
|
60
77
|
"error" => "invalid_request",
|
|
@@ -99,7 +99,9 @@ module Masks
|
|
|
99
99
|
end
|
|
100
100
|
|
|
101
101
|
def member!
|
|
102
|
-
|
|
102
|
+
membership_id = params[:id].to_s[/\A\d+\z/]
|
|
103
|
+
|
|
104
|
+
(membership_id && @organization.memberships.includes(:actor).find_by(id: membership_id)) ||
|
|
103
105
|
raise(Members::Refused, t("organization_members.not_a_member"))
|
|
104
106
|
end
|
|
105
107
|
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
module Masks
|
|
2
|
+
module Server
|
|
3
|
+
module Manage
|
|
4
|
+
module Mutations
|
|
5
|
+
class UnenrollClient < BaseMutation
|
|
6
|
+
requires :security
|
|
7
|
+
|
|
8
|
+
argument :client_id, ID
|
|
9
|
+
|
|
10
|
+
field :client, Types::ClientType, null: false
|
|
11
|
+
|
|
12
|
+
def resolve(client_id:)
|
|
13
|
+
machine = client!(client_id)
|
|
14
|
+
|
|
15
|
+
refuse!("that client is not a machine an app enrolled, so archive it instead") unless machine.enrolled?
|
|
16
|
+
|
|
17
|
+
revoked = machine.unenroll!
|
|
18
|
+
|
|
19
|
+
audit!(Masks::Server::Event::CLIENT_UNENROLLED, client: machine, name: machine.name,
|
|
20
|
+
enrolled_by: machine.enrolled_by.client_id, revoked: revoked)
|
|
21
|
+
|
|
22
|
+
{ client: machine }
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -63,6 +63,8 @@ module Masks
|
|
|
63
63
|
description: "The app that enrolled this client as one of its machines. Archiving that app archives this client."
|
|
64
64
|
field :enrolled, [ ClientType ], null: false,
|
|
65
65
|
description: "The machines this app enrolled that are not archived."
|
|
66
|
+
field :last_seen_at, GraphQL::Types::ISO8601DateTime,
|
|
67
|
+
description: "When this client last got a token. Null if it never has."
|
|
66
68
|
field :archived_at, GraphQL::Types::ISO8601DateTime
|
|
67
69
|
field :secret_expires_at, GraphQL::Types::ISO8601DateTime
|
|
68
70
|
field :created_at, GraphQL::Types::ISO8601DateTime, null: false
|
|
@@ -85,6 +87,10 @@ module Masks
|
|
|
85
87
|
object.enrolled.active.order(:name)
|
|
86
88
|
end
|
|
87
89
|
|
|
90
|
+
def last_seen_at
|
|
91
|
+
Masks::Server::Token.where(kind: TokenType::GRANTS, client_id: object.id).maximum(:created_at)
|
|
92
|
+
end
|
|
93
|
+
|
|
88
94
|
def tokens(limit: nil)
|
|
89
95
|
Masks::Server::Token.where(kind: TokenType::GRANTS, client_id: object.id)
|
|
90
96
|
.live
|
|
@@ -41,6 +41,7 @@ module Masks
|
|
|
41
41
|
field :update_client, mutation: Mutations::UpdateClient
|
|
42
42
|
field :rotate_client_secret, mutation: Mutations::RotateClientSecret
|
|
43
43
|
field :archive_client, mutation: Mutations::ArchiveClient
|
|
44
|
+
field :unenroll_client, mutation: Mutations::UnenrollClient
|
|
44
45
|
field :restore_client, mutation: Mutations::RestoreClient
|
|
45
46
|
field :release_namespace, mutation: Mutations::ReleaseNamespace
|
|
46
47
|
field :discover_provider, mutation: Mutations::DiscoverProvider
|
|
@@ -2,6 +2,34 @@ module Masks
|
|
|
2
2
|
module Server
|
|
3
3
|
class ApplicationJob < ActiveJob::Base
|
|
4
4
|
include Tenancy::Job
|
|
5
|
+
|
|
6
|
+
class Incomplete < StandardError; end
|
|
7
|
+
|
|
8
|
+
private
|
|
9
|
+
|
|
10
|
+
def each_tenant(scope = Tenant.active)
|
|
11
|
+
failed = scope.find_each.filter_map do |tenant|
|
|
12
|
+
Tenant.switch(tenant) { yield tenant }
|
|
13
|
+
nil
|
|
14
|
+
rescue StandardError => e
|
|
15
|
+
report_tenant_failure(tenant, e)
|
|
16
|
+
tenant.subdomain
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
JobFailures.record(self.class.name, failed)
|
|
20
|
+
|
|
21
|
+
raise Incomplete, "#{self.class.name.demodulize} failed for #{failed.join(', ')}" if failed.any?
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def report_tenant_failure(tenant, error)
|
|
25
|
+
::Rails.error.report(error, context: { tenant: tenant.subdomain, job: self.class.name })
|
|
26
|
+
|
|
27
|
+
Tenant.switch(tenant) do
|
|
28
|
+
Event.record!(Event::JOB_FAILED, by: nil, job: self.class.name.demodulize, error: error.class.name, message: error.message.to_s[0, 200])
|
|
29
|
+
end
|
|
30
|
+
rescue StandardError => e
|
|
31
|
+
::Rails.logger.warn("#{self.class.name} could not record its failure in #{tenant.subdomain}: #{e.message}")
|
|
32
|
+
end
|
|
5
33
|
end
|
|
6
34
|
end
|
|
7
35
|
end
|
|
@@ -37,13 +37,11 @@ module Masks
|
|
|
37
37
|
end
|
|
38
38
|
|
|
39
39
|
def sweep_tokens(cutoff)
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
.limit(BATCH).pluck(Arel.sql("COALESCE(root_id, id)"))
|
|
44
|
-
break if roots.empty?
|
|
40
|
+
Token.where(root_id: nil).ended_before(cutoff).in_batches(of: BATCH) do |roots|
|
|
41
|
+
ids = roots.pluck(:id)
|
|
42
|
+
kept = Token.where(root_id: ids).where("LEAST(consumed_at, expires_at) >= ?", cutoff).distinct.pluck(:root_id)
|
|
45
43
|
|
|
46
|
-
Token.families(
|
|
44
|
+
Token.families(ids - kept).delete_all
|
|
47
45
|
end
|
|
48
46
|
|
|
49
47
|
Token.where.not(kind: RefreshToken.sti_name).ended_before(cutoff)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module Masks
|
|
2
|
+
module Server
|
|
3
|
+
class PruneQueueJob < ApplicationJob
|
|
4
|
+
queue_as :maintenance
|
|
5
|
+
across_tenants!
|
|
6
|
+
|
|
7
|
+
BATCH = 500
|
|
8
|
+
|
|
9
|
+
def perform
|
|
10
|
+
return unless defined?(::SolidQueue::Job)
|
|
11
|
+
|
|
12
|
+
settings = ::Rails.configuration.masks
|
|
13
|
+
|
|
14
|
+
::SolidQueue::Job.clear_finished_in_batches(batch_size: BATCH, finished_before: settings.finished_job_retention.ago)
|
|
15
|
+
::SolidQueue::FailedExecution.where(created_at: ...settings.failed_job_retention.ago).discard_all_in_batches(batch_size: BATCH)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -7,9 +7,7 @@ module Masks
|
|
|
7
7
|
PUBLISHED_FOR = 1.day
|
|
8
8
|
|
|
9
9
|
def perform
|
|
10
|
-
Tenant.active.where.not(key_rotation_days: nil)
|
|
11
|
-
Tenant.switch(tenant) { advance(tenant) }
|
|
12
|
-
end
|
|
10
|
+
each_tenant(Tenant.active.where.not(key_rotation_days: nil)) { |tenant| advance(tenant) }
|
|
13
11
|
end
|
|
14
12
|
|
|
15
13
|
private
|
|
@@ -3,7 +3,11 @@ module Masks
|
|
|
3
3
|
class TextJob < ApplicationJob
|
|
4
4
|
queue_as :mailers
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
ATTEMPTS = 3
|
|
7
|
+
|
|
8
|
+
retry_on Sender::Failed, wait: :polynomially_longer, attempts: ATTEMPTS do |job, error|
|
|
9
|
+
job.gave_up!(error)
|
|
10
|
+
end
|
|
7
11
|
|
|
8
12
|
def perform(to, body)
|
|
9
13
|
sender = Current.tenant&.sms_sender
|
|
@@ -12,6 +16,13 @@ module Masks
|
|
|
12
16
|
|
|
13
17
|
sender.deliver(to: to, body: body)
|
|
14
18
|
end
|
|
19
|
+
|
|
20
|
+
def gave_up!(error)
|
|
21
|
+
Tenant.switch(held_tenant) do
|
|
22
|
+
Event.record!(Event::TEXT_UNDELIVERED, actor: nil, by: nil, device: nil, ip_address: nil, user_agent: nil,
|
|
23
|
+
said: error.message)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
15
26
|
end
|
|
16
27
|
end
|
|
17
28
|
end
|
|
@@ -182,6 +182,108 @@ module Masks
|
|
|
182
182
|
[ "OAUTH_PROVIDER_NAME", "{tenant}" ], [ "ENABLE_OAUTH_SIGNUP", "true" ],
|
|
183
183
|
[ "ENABLE_OAUTH_ROLE_MANAGEMENT", "true" ], [ "OAUTH_ROLES_CLAIM", "groups" ], [ "OAUTH_ADMIN_ROLES", "admins" ]
|
|
184
184
|
]
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
key: "tailscale", name: "Tailscale",
|
|
188
|
+
redirects: [ "https://login.tailscale.com/a/oauth_response" ],
|
|
189
|
+
scopes: %w[openid profile email],
|
|
190
|
+
guide: "https://tailscale.com/kb/1240/sso-custom-oidc",
|
|
191
|
+
settings: [
|
|
192
|
+
[ "WebFinger domain", "{masks_host}" ], [ "Issuer URL", "{issuer}" ],
|
|
193
|
+
[ "Client ID", "{client_id}" ], [ "Client secret", "{client_secret}" ]
|
|
194
|
+
]
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
key: "headscale", name: "Headscale",
|
|
198
|
+
redirects: [ "{base}/oidc/callback" ],
|
|
199
|
+
scopes: %w[openid profile email groups],
|
|
200
|
+
guide: "https://headscale.net/stable/ref/oidc/",
|
|
201
|
+
settings: [
|
|
202
|
+
[ "oidc.issuer", "{issuer}" ], [ "oidc.client_id", "{client_id}" ], [ "oidc.client_secret", "{client_secret}" ],
|
|
203
|
+
[ "oidc.scope", "[openid, profile, email, groups]" ], [ "oidc.allowed_groups", "[admins]" ]
|
|
204
|
+
]
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
key: "home-assistant", name: "Home Assistant (OIDC integration)",
|
|
208
|
+
redirects: [ "{base}/auth/oidc/callback" ],
|
|
209
|
+
scopes: %w[openid profile email groups],
|
|
210
|
+
guide: "https://github.com/christiaangoossens/hass-oidc-auth",
|
|
211
|
+
settings: [
|
|
212
|
+
[ "auth_oidc.discovery_url", "{discovery}" ], [ "auth_oidc.client_id", "{client_id}" ],
|
|
213
|
+
[ "auth_oidc.client_secret", "{client_secret}" ], [ "auth_oidc.display_name", "{tenant}" ],
|
|
214
|
+
[ "auth_oidc.roles.admin", "admins" ]
|
|
215
|
+
]
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
key: "synapse", name: "Matrix (Synapse)", asks: { "name" => "masks" },
|
|
219
|
+
redirects: [ "{base}/_synapse/client/oidc/callback" ],
|
|
220
|
+
scopes: %w[openid profile email],
|
|
221
|
+
guide: "https://element-hq.github.io/synapse/latest/openid.html",
|
|
222
|
+
settings: [
|
|
223
|
+
[ "oidc_providers", "- idp_id: {name}\n idp_name: {tenant}\n issuer: {issuer}\n client_id: {client_id}\n" \
|
|
224
|
+
" client_secret: {client_secret}\n scopes: [openid, profile, email]\n" \
|
|
225
|
+
" user_mapping_provider:\n config:\n localpart_template: \"{{ user.preferred_username }}\"\n" \
|
|
226
|
+
" display_name_template: \"{{ user.name }}\"\n email_template: \"{{ user.email }}\"" ]
|
|
227
|
+
]
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
key: "bookstack", name: "BookStack",
|
|
231
|
+
redirects: [ "{base}/oidc/callback" ],
|
|
232
|
+
scopes: %w[openid profile email groups],
|
|
233
|
+
guide: "https://www.bookstackapp.com/docs/admin/oidc-auth/",
|
|
234
|
+
settings: [
|
|
235
|
+
[ "AUTH_METHOD", "oidc" ], [ "OIDC_NAME", "{tenant}" ], [ "OIDC_ISSUER", "{issuer}" ], [ "OIDC_ISSUER_DISCOVER", "true" ],
|
|
236
|
+
[ "OIDC_CLIENT_ID", "{client_id}" ], [ "OIDC_CLIENT_SECRET", "{client_secret}" ],
|
|
237
|
+
[ "OIDC_DISPLAY_NAME_CLAIMS", "name" ], [ "OIDC_ADDITIONAL_SCOPES", "groups" ],
|
|
238
|
+
[ "OIDC_USER_TO_GROUPS", "true" ], [ "OIDC_GROUPS_CLAIM", "groups" ], [ "OIDC_REMOVE_FROM_GROUPS", "true" ]
|
|
239
|
+
]
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
key: "wikijs", name: "Wiki.js", asks: { "name" => "masks" },
|
|
243
|
+
redirects: [ "{base}/login/{name}/callback" ],
|
|
244
|
+
scopes: %w[openid profile email groups],
|
|
245
|
+
guide: "https://docs.requarks.io/auth/oidc",
|
|
246
|
+
settings: [
|
|
247
|
+
[ "Client ID", "{client_id}" ], [ "Client Secret", "{client_secret}" ],
|
|
248
|
+
[ "Authorization Endpoint URL", "{authorize}" ], [ "Token Endpoint URL", "{token}" ],
|
|
249
|
+
[ "User Info Endpoint URL", "{userinfo}" ], [ "Issuer", "{issuer}" ], [ "Logout URL", "{logout}" ],
|
|
250
|
+
[ "Email Claim", "email" ], [ "Display Name Claim", "name" ], [ "Groups Claim", "groups" ],
|
|
251
|
+
[ "Callback URL", "{base}/login/{name}/callback" ]
|
|
252
|
+
]
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
key: "minio", name: "MinIO",
|
|
256
|
+
redirects: [ "{base}/oauth_callback" ],
|
|
257
|
+
scopes: %w[openid profile email groups],
|
|
258
|
+
guide: "https://min.io/docs/minio/linux/operations/external-iam/configure-openid-external-identity-management.html",
|
|
259
|
+
settings: [
|
|
260
|
+
[ "MINIO_IDENTITY_OPENID_CONFIG_URL", "{discovery}" ], [ "MINIO_IDENTITY_OPENID_CLIENT_ID", "{client_id}" ],
|
|
261
|
+
[ "MINIO_IDENTITY_OPENID_CLIENT_SECRET", "{client_secret}" ], [ "MINIO_IDENTITY_OPENID_SCOPES", "openid,profile,email,groups" ],
|
|
262
|
+
[ "MINIO_IDENTITY_OPENID_CLAIM_NAME", "groups" ], [ "MINIO_IDENTITY_OPENID_REDIRECT_URI", "{base}/oauth_callback" ],
|
|
263
|
+
[ "MINIO_IDENTITY_OPENID_DISPLAY_NAME", "{tenant}" ]
|
|
264
|
+
]
|
|
265
|
+
},
|
|
266
|
+
{
|
|
267
|
+
key: "argocd", name: "Argo CD",
|
|
268
|
+
redirects: [ "{base}/auth/callback", "{base}/api/dex/callback" ],
|
|
269
|
+
scopes: %w[openid profile email groups],
|
|
270
|
+
guide: "https://argo-cd.readthedocs.io/en/stable/operator-manual/user-management/#existing-oidc-provider",
|
|
271
|
+
settings: [
|
|
272
|
+
[ "argocd-cm oidc.config", "name: {tenant}\nissuer: {issuer}\nclientID: {client_id}\nclientSecret: {client_secret}\n" \
|
|
273
|
+
"requestedScopes: [openid, profile, email, groups]" ],
|
|
274
|
+
[ "argocd-rbac-cm policy.csv", "g, admins, role:admin" ], [ "argocd-rbac-cm scopes", "[groups]" ]
|
|
275
|
+
]
|
|
276
|
+
},
|
|
277
|
+
{
|
|
278
|
+
key: "synology", name: "Synology DSM",
|
|
279
|
+
redirects: [ "{base}", "{base}/" ],
|
|
280
|
+
scopes: %w[openid profile email],
|
|
281
|
+
guide: "https://kb.synology.com/en-global/DSM/help/DSM/AdminCenter/connection_sso_client",
|
|
282
|
+
settings: [
|
|
283
|
+
[ "Profile", "OIDC" ], [ "Name", "{tenant}" ], [ "Well Known URL", "{discovery}" ],
|
|
284
|
+
[ "Client ID", "{client_id}" ], [ "Client secret", "{client_secret}" ],
|
|
285
|
+
[ "Scope", "openid profile email" ], [ "Username claim", "preferred_username" ], [ "Redirect URI", "{base}" ]
|
|
286
|
+
]
|
|
185
287
|
}
|
|
186
288
|
].map(&:freeze).freeze
|
|
187
289
|
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
require "socket"
|
|
2
|
+
require "zlib"
|
|
3
|
+
|
|
4
|
+
module Masks
|
|
5
|
+
module Server
|
|
6
|
+
class BootTask
|
|
7
|
+
WINDOW = 10.minutes
|
|
8
|
+
|
|
9
|
+
Outcome = Struct.new(:name, :host, :value, :done) do
|
|
10
|
+
def done? = done
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
class << self
|
|
14
|
+
def once(name, fingerprint: nil)
|
|
15
|
+
key = "masks:boot:#{name}:#{Digest::SHA256.hexdigest(fingerprint.to_s)[0, 16]}"
|
|
16
|
+
host = Socket.gethostname
|
|
17
|
+
outcome = nil
|
|
18
|
+
|
|
19
|
+
ActiveRecord::Base.transaction do
|
|
20
|
+
ActiveRecord::Base.connection.execute("SELECT pg_advisory_xact_lock(#{lock_id(name)})")
|
|
21
|
+
done_by = ::Rails.cache.read(key)
|
|
22
|
+
|
|
23
|
+
if done_by
|
|
24
|
+
outcome = Outcome.new(name, done_by, nil, false)
|
|
25
|
+
else
|
|
26
|
+
value = yield
|
|
27
|
+
::Rails.cache.write(key, host, expires_in: WINDOW)
|
|
28
|
+
outcome = Outcome.new(name, host, value, true)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
outcome
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def lock_id(name)
|
|
36
|
+
Zlib.crc32("masks:boot:#{name}")
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -146,6 +146,7 @@ module Masks
|
|
|
146
146
|
SIGNAL_STREAM_UPDATED = "signal_stream.updated".freeze
|
|
147
147
|
SIGNAL_STREAM_DELETED = "signal_stream.deleted".freeze
|
|
148
148
|
SIGNAL_UNDELIVERED = "signal.undelivered".freeze
|
|
149
|
+
TEXT_UNDELIVERED = "text.undelivered".freeze
|
|
149
150
|
SIGNAL_RECEIVED = "signal.received".freeze
|
|
150
151
|
|
|
151
152
|
ORGANIZATION_CREATED = "organization.created".freeze
|
|
@@ -167,10 +168,13 @@ module Masks
|
|
|
167
168
|
CUSTOM_DOMAIN_SERVED = "custom_domain.served".freeze
|
|
168
169
|
CUSTOM_DOMAIN_STOPPED = "custom_domain.stopped".freeze
|
|
169
170
|
|
|
171
|
+
JOB_FAILED = "job.failed".freeze
|
|
172
|
+
|
|
170
173
|
GRAVE = [
|
|
171
174
|
LOGIN_REFUSED, LOGIN_THROTTLED, LOGOUT_UNDELIVERED, REFRESH_REUSED, STREAM_FAILED,
|
|
172
175
|
DEVICE_BLOCKED, ACTOR_DELETED, AUTHENTICATOR_DISABLED, CONNECTION_REFUSED, DELEGATION_REFUSED,
|
|
173
|
-
EXCHANGE_REFUSED, SIGN_IN_RISKY, SIGNAL_UNDELIVERED, SIGNAL_RECEIVED, MEMBERSHIP_ROLE_KEPT,
|
|
176
|
+
EXCHANGE_REFUSED, SIGN_IN_RISKY, SIGNAL_UNDELIVERED, TEXT_UNDELIVERED, SIGNAL_RECEIVED, MEMBERSHIP_ROLE_KEPT,
|
|
177
|
+
ORGANIZATION_OWNERLESS, JOB_FAILED
|
|
174
178
|
].freeze
|
|
175
179
|
|
|
176
180
|
ACTIONS = constants(false).filter_map do |name|
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module Masks
|
|
2
|
+
module Server
|
|
3
|
+
module JobFailures
|
|
4
|
+
KEEP = 2.days
|
|
5
|
+
PREFIX = "masks:jobs:failed_tenants:".freeze
|
|
6
|
+
|
|
7
|
+
module_function
|
|
8
|
+
|
|
9
|
+
def record(job, subdomains)
|
|
10
|
+
::Rails.cache.write("#{PREFIX}#{job}", subdomains.sort, expires_in: KEEP)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def last(job)
|
|
14
|
+
::Rails.cache.read("#{PREFIX}#{job}") || []
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def named(jobs)
|
|
18
|
+
jobs.map do |job|
|
|
19
|
+
tenants = last(job)
|
|
20
|
+
tenants.empty? ? job : "#{job} (#{tenants.join(', ')})"
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -157,6 +157,8 @@ de:
|
|
|
157
157
|
exported: "Aktivität exportiert"
|
|
158
158
|
mail_template:
|
|
159
159
|
updated: "E-Mail-Vorlage geändert"
|
|
160
|
+
job:
|
|
161
|
+
failed: "Hintergrundjob für diesen Mandanten fehlgeschlagen"
|
|
160
162
|
stream:
|
|
161
163
|
created: "Event-Stream hinzugefügt"
|
|
162
164
|
updated: "Event-Stream geändert"
|
|
@@ -171,6 +173,8 @@ de:
|
|
|
171
173
|
signal:
|
|
172
174
|
undelivered: "Shared Signal nicht zugestellt"
|
|
173
175
|
received: "Anbieter hat Abmeldung signalisiert"
|
|
176
|
+
text:
|
|
177
|
+
undelivered: "SMS nicht zugestellt"
|
|
174
178
|
organization:
|
|
175
179
|
created: "Organisation hinzugefügt"
|
|
176
180
|
updated: "Organisation geändert"
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
de:
|
|
2
|
+
manage:
|
|
3
|
+
machines:
|
|
4
|
+
title: "Maschinen"
|
|
5
|
+
lede: "Clients, die diese App mit einem Registrierungstoken angemeldet hat. Jeder meldet sich mit einem eigenen Schlüssel an."
|
|
6
|
+
empty: "Diese App hat noch keine Maschine angemeldet."
|
|
7
|
+
last_seen: "Zuletzt angemeldet %{day}"
|
|
8
|
+
never: "Nie angemeldet"
|
|
9
|
+
enrolled: "Angemeldet %{day}"
|
|
10
|
+
enrolled_by: "Eine Maschine, angemeldet von"
|
|
11
|
+
revoke: "Widerrufen"
|
|
12
|
+
revoke_confirm: "%{name} widerrufen? Sie wird archiviert, jedes Token endet, und sie kann sich nicht mehr anmelden."
|
|
13
|
+
revoked: "%{name} ist widerrufen und kann sich nicht mehr anmelden."
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
de:
|
|
2
2
|
manage:
|
|
3
3
|
namespaces:
|
|
4
|
-
title: "
|
|
4
|
+
title: "Namensräume"
|
|
5
5
|
release_confirm: "%{name} freigeben? Der nächste Client, der ihn anfordert, erhält ihn, und die Scopes darunter bedeuten nicht mehr, was sie jetzt bedeuten."
|
|
6
6
|
released: "%{name} kann wieder beansprucht werden."
|
|
7
7
|
columns:
|
|
@@ -157,6 +157,8 @@ en:
|
|
|
157
157
|
exported: "Activity exported"
|
|
158
158
|
mail_template:
|
|
159
159
|
updated: "Email template changed"
|
|
160
|
+
job:
|
|
161
|
+
failed: "Background job failed for this tenant"
|
|
160
162
|
stream:
|
|
161
163
|
created: "Event stream added"
|
|
162
164
|
updated: "Event stream changed"
|
|
@@ -171,6 +173,8 @@ en:
|
|
|
171
173
|
signal:
|
|
172
174
|
undelivered: "Shared signal not delivered"
|
|
173
175
|
received: "Provider signaled sign-out"
|
|
176
|
+
text:
|
|
177
|
+
undelivered: "Text message not delivered"
|
|
174
178
|
organization:
|
|
175
179
|
created: "Organization added"
|
|
176
180
|
updated: "Organization changed"
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
en:
|
|
2
|
+
manage:
|
|
3
|
+
machines:
|
|
4
|
+
title: "Machines"
|
|
5
|
+
lede: "Clients this app enrolled with an enrollment token. Each one signs in with its own key."
|
|
6
|
+
empty: "This app has not enrolled a machine yet."
|
|
7
|
+
last_seen: "Last signed in %{day}"
|
|
8
|
+
never: "Never signed in"
|
|
9
|
+
enrolled: "Enrolled %{day}"
|
|
10
|
+
enrolled_by: "A machine enrolled by"
|
|
11
|
+
revoke: "Revoke"
|
|
12
|
+
revoke_confirm: "Revoke %{name}? It is archived, every token it holds ends, and it can no longer sign in."
|
|
13
|
+
revoked: "%{name} is revoked and can no longer sign in."
|
|
@@ -157,6 +157,8 @@ es:
|
|
|
157
157
|
exported: "Actividad exportada"
|
|
158
158
|
mail_template:
|
|
159
159
|
updated: "Plantilla de correo cambiada"
|
|
160
|
+
job:
|
|
161
|
+
failed: "Tarea en segundo plano fallida para este inquilino"
|
|
160
162
|
stream:
|
|
161
163
|
created: "Flujo de eventos añadido"
|
|
162
164
|
updated: "Flujo de eventos cambiado"
|
|
@@ -171,6 +173,8 @@ es:
|
|
|
171
173
|
signal:
|
|
172
174
|
undelivered: "Señal compartida no entregada"
|
|
173
175
|
received: "El proveedor señaló un cierre de sesión"
|
|
176
|
+
text:
|
|
177
|
+
undelivered: "Mensaje de texto no entregado"
|
|
174
178
|
organization:
|
|
175
179
|
created: "Organización añadida"
|
|
176
180
|
updated: "Organización cambiada"
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
es:
|
|
2
|
+
manage:
|
|
3
|
+
machines:
|
|
4
|
+
title: "Máquinas"
|
|
5
|
+
lede: "Clientes que esta aplicación inscribió con un token de inscripción. Cada uno inicia sesión con su propia clave."
|
|
6
|
+
empty: "Esta aplicación aún no ha inscrito ninguna máquina."
|
|
7
|
+
last_seen: "Último inicio de sesión %{day}"
|
|
8
|
+
never: "Nunca ha iniciado sesión"
|
|
9
|
+
enrolled: "Inscrita %{day}"
|
|
10
|
+
enrolled_by: "Una máquina inscrita por"
|
|
11
|
+
revoke: "Revocar"
|
|
12
|
+
revoke_confirm: "¿Revocar %{name}? Se archiva, todos sus tokens terminan y ya no puede iniciar sesión."
|
|
13
|
+
revoked: "%{name} está revocada y ya no puede iniciar sesión."
|