Skip to content

Commit 774dabe

Browse files
committed
Enforce sso checks for both snapshots (gql) and auth_roles (postgrest)
1 parent 3fbc8d2 commit 774dabe

7 files changed

+372
-3
lines changed

.sqlx/query-59b5d98e5cc0ce441ddefa0b32eef6820b1db95156c6206e773362e4477fc2dd.json

Lines changed: 94 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.sqlx/query-a5697a9097f21c69f3da2850fd08cd9aa7102b964071bad56e9b82bb9e82bce1.json

Lines changed: 94 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.sqlx/query-bf114fcbbb3b0c8bd057869c92b7c6ceee073f467105083ec751a75c11478adf.json renamed to .sqlx/query-e60eb5d6aec7b22576ff88a5862784cae60b21aa971908b01eb16ff4770bf29b.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
---
2+
source: crates/control-plane-api/src/server/public/graphql/invite_links.rs
3+
assertion_line: 882
4+
expression: unauthorized_list
5+
---
6+
{
7+
"data": null,
8+
"errors": [
9+
{
10+
"locations": [
11+
{
12+
"column": 25,
13+
"line": 3
14+
}
15+
],
16+
"message": "PermissionDenied: bob@example.test is not authorized to access prefix or name 'aliceCo/' with required capability admin",
17+
"path": [
18+
"inviteLinks"
19+
]
20+
}
21+
]
22+
}

crates/control-plane-api/src/server/snapshot.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -496,11 +496,17 @@ pub async fn try_fetch(
496496
g.object_role AS "object_role: models::Prefix",
497497
g.capability AS "capability: models::Capability"
498498
FROM user_grants g
499+
WHERE NOT EXISTS (
500+
SELECT 1 FROM tenants t
501+
WHERE t.tenant ^@ g.object_role
502+
AND t.enforce_sso
503+
AND NOT coalesce((SELECT au.is_sso_user FROM auth.users au WHERE au.id = g.user_id), false)
504+
)
499505
"#,
500506
)
501507
.fetch_all(pg_pool)
502508
.await
503-
.context("failed to fetch role_grants")?;
509+
.context("failed to fetch user_grants")?;
504510

505511
let tasks = sqlx::query_as!(
506512
SnapshotTask,
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
-- Phase 4: Enforce SSO at the authorization layer.
2+
--
3+
-- When a tenant has enforce_sso = true, grants on that tenant are excluded
4+
-- unless the user has an SSO identity matching the tenant's configured provider.
5+
-- This is enforced in the base case of internal.user_roles(), so transitive
6+
-- grants through SSO-enforced tenants are also excluded.
7+
8+
begin;
9+
10+
-- Add SSO columns to tenants.
11+
alter table public.tenants
12+
add column sso_provider_id uuid references auth.sso_providers(id),
13+
add column enforce_sso boolean not null default false;
14+
15+
comment on column public.tenants.sso_provider_id is
16+
'The SSO provider configured for this tenant, if any';
17+
comment on column public.tenants.enforce_sso is
18+
'When true, only SSO-authenticated users may access this tenant''s resources';
19+
20+
-- Replace internal.user_roles to exclude grants on SSO-enforced tenants unless
21+
-- the user authenticated via the tenant's specific SSO provider.
22+
create or replace function internal.user_roles(
23+
target_user_id uuid,
24+
min_capability public.grant_capability default 'x_00'::public.grant_capability
25+
)
26+
returns table(role_prefix public.catalog_prefix, capability public.grant_capability)
27+
language sql stable
28+
as $$
29+
with recursive
30+
all_roles(role_prefix, capability) as (
31+
select object_role, capability from user_grants
32+
where user_id = target_user_id
33+
and capability >= min_capability
34+
-- Exclude grants on SSO-enforced tenants unless the user has an
35+
-- identity linked to that tenant's specific SSO provider.
36+
and not exists (
37+
select 1 from tenants t
38+
where t.tenant ^@ user_grants.object_role
39+
and t.enforce_sso
40+
and not exists (
41+
select 1 from auth.identities ai
42+
where ai.user_id = target_user_id
43+
and ai.provider = 'sso'
44+
and ai.provider_id = t.sso_provider_id::text
45+
)
46+
)
47+
union
48+
-- Recursive case: for each object_role granted as 'admin',
49+
-- project through grants where object_role acts as the subject_role.
50+
select role_grants.object_role, role_grants.capability
51+
from role_grants, all_roles
52+
where role_grants.subject_role ^@ all_roles.role_prefix
53+
and role_grants.capability >= min_capability
54+
and all_roles.capability = 'admin'
55+
)
56+
select role_prefix, max(capability) from all_roles
57+
group by role_prefix
58+
order by role_prefix;
59+
$$;
60+
61+
commit;

0 commit comments

Comments
 (0)