forked from wangziqi/gongxue-base
278 lines
9.0 KiB
SQL
278 lines
9.0 KiB
SQL
do $$
|
|
declare
|
|
runtime_role name;
|
|
role_state record;
|
|
begin
|
|
foreach runtime_role in array array['tiku_api'::name, 'tiku_worker'::name]
|
|
loop
|
|
select role_row.*,
|
|
exists (
|
|
select 1 from pg_auth_members membership
|
|
where membership.member = role_row.oid
|
|
) as has_parent_roles
|
|
into role_state
|
|
from pg_roles role_row
|
|
where role_row.rolname = runtime_role;
|
|
|
|
if not found then
|
|
raise exception
|
|
'Runtime role % is missing. A PostgreSQL superuser must run scripts/bootstrap-backend-runtime-roles.js before migrations.',
|
|
runtime_role;
|
|
end if;
|
|
|
|
if role_state.rolsuper
|
|
or role_state.rolinherit
|
|
or role_state.rolcreatedb
|
|
or role_state.rolcreaterole
|
|
or not role_state.rolcanlogin
|
|
or role_state.rolreplication
|
|
or not role_state.rolbypassrls
|
|
or role_state.has_parent_roles
|
|
or not coalesce(role_state.rolconfig, '{}'::text[])
|
|
@> array['search_path=pg_catalog, public, extensions']::text[] then
|
|
raise exception
|
|
'Runtime role % attributes are unsafe or incomplete. Re-run the privileged backend runtime role bootstrap.',
|
|
runtime_role;
|
|
end if;
|
|
end loop;
|
|
end
|
|
$$;
|
|
|
|
-- Persistent DDL stays with the migration owner. Revoke PUBLIC first so an
|
|
-- effective CREATE privilege cannot leak back through the implicit role.
|
|
revoke create on schema public, app, app_private, extensions from public;
|
|
revoke all privileges on schema public, app, app_private, extensions from tiku_api, tiku_worker;
|
|
grant usage on schema public, app_private, extensions to tiku_api, tiku_worker;
|
|
grant usage on schema app to tiku_api;
|
|
|
|
revoke all privileges
|
|
on all tables in schema public, app_private
|
|
from tiku_api, tiku_worker;
|
|
|
|
grant select, insert, update, delete
|
|
on all tables in schema public
|
|
to tiku_api, tiku_worker;
|
|
|
|
grant select
|
|
on all tables in schema app_private
|
|
to tiku_api, tiku_worker;
|
|
|
|
grant insert, update
|
|
on app_private.auth_sessions,
|
|
app_private.tenant_secrets,
|
|
app_private.platform_secrets
|
|
to tiku_api;
|
|
|
|
grant insert, update, delete
|
|
on app_private.sms_send_rate_limits
|
|
to tiku_api;
|
|
|
|
revoke all privileges
|
|
on all sequences in schema public, app_private
|
|
from tiku_api, tiku_worker;
|
|
|
|
grant usage, select
|
|
on all sequences in schema public
|
|
to tiku_api, tiku_worker;
|
|
|
|
-- Normalize UUID defaults to the PostgreSQL 13+ core function. Supabase may
|
|
-- install pgcrypto in extensions while older self-hosted databases may have a
|
|
-- public wrapper, so runtime correctness must not depend on either layout.
|
|
do $$
|
|
declare
|
|
default_column record;
|
|
begin
|
|
if to_regprocedure('pg_catalog.gen_random_uuid()') is null then
|
|
raise exception 'PostgreSQL 13 or newer is required: pg_catalog.gen_random_uuid() is unavailable';
|
|
end if;
|
|
|
|
for default_column in
|
|
select namespace.nspname as schema_name,
|
|
relation.relname as table_name,
|
|
attribute.attname as column_name
|
|
from pg_attrdef default_value
|
|
join pg_class relation on relation.oid = default_value.adrelid
|
|
join pg_namespace namespace on namespace.oid = relation.relnamespace
|
|
join pg_attribute attribute
|
|
on attribute.attrelid = relation.oid
|
|
and attribute.attnum = default_value.adnum
|
|
where namespace.nspname in ('public', 'app_private')
|
|
and relation.relkind in ('r', 'p')
|
|
and attribute.atttypid = 'uuid'::regtype
|
|
and pg_get_expr(default_value.adbin, default_value.adrelid)
|
|
~ '(^|[.])gen_random_uuid[(][)]$'
|
|
loop
|
|
execute format(
|
|
'alter table %I.%I alter column %I set default pg_catalog.gen_random_uuid()',
|
|
default_column.schema_name,
|
|
default_column.table_name,
|
|
default_column.column_name
|
|
);
|
|
end loop;
|
|
end
|
|
$$;
|
|
|
|
-- Function execution is reviewed explicitly. The API directly calls only the
|
|
-- public-bank helpers; pg_catalog functions retain PostgreSQL's built-in ACL.
|
|
revoke execute on all functions in schema app from public;
|
|
revoke all privileges
|
|
on all functions in schema public, app, app_private
|
|
from tiku_api, tiku_worker;
|
|
|
|
-- RLS policies resolve these functions by OID. Client roles still need
|
|
-- EXECUTE even though the app schema is not exposed for direct RPC access.
|
|
grant execute on function app.jwt_text(text)
|
|
to anon, authenticated, service_role;
|
|
|
|
grant execute on function app.current_tenant_id()
|
|
to anon, authenticated, service_role;
|
|
|
|
grant execute on function app.current_role()
|
|
to anon, authenticated, service_role;
|
|
|
|
grant execute on function app.is_platform_admin()
|
|
to anon, authenticated, service_role;
|
|
|
|
grant execute on function app.uuid_array_from_jsonb(jsonb)
|
|
to anon, authenticated, service_role;
|
|
|
|
grant execute on function app.public_question_bank_grant_allows(uuid[], uuid[], uuid, uuid[])
|
|
to anon, authenticated, service_role;
|
|
|
|
grant execute on function app.public_question_bank_subscription_allows(jsonb, jsonb, uuid, uuid, uuid[])
|
|
to anon, authenticated, service_role;
|
|
|
|
grant execute on function app.uuid_array_from_jsonb(jsonb)
|
|
to tiku_api;
|
|
|
|
grant execute on function app.public_question_bank_grant_allows(uuid[], uuid[], uuid, uuid[])
|
|
to tiku_api;
|
|
|
|
grant execute on function app.public_question_bank_subscription_allows(jsonb, jsonb, uuid, uuid, uuid[])
|
|
to tiku_api;
|
|
|
|
-- Keep future migration-owned objects on the same privilege matrix. New
|
|
-- functions intentionally receive no runtime grant until reviewed.
|
|
do $$
|
|
declare
|
|
schema_name text;
|
|
owner_name name;
|
|
begin
|
|
foreach schema_name in array array['public', 'app_private']
|
|
loop
|
|
for owner_name in
|
|
select distinct owner_role.rolname
|
|
from (
|
|
select c.relowner as owner_oid
|
|
from pg_class c
|
|
join pg_namespace n on n.oid = c.relnamespace
|
|
where n.nspname = schema_name
|
|
union
|
|
select p.proowner as owner_oid
|
|
from pg_proc p
|
|
join pg_namespace n on n.oid = p.pronamespace
|
|
where n.nspname = schema_name
|
|
) owners
|
|
join pg_roles owner_role on owner_role.oid = owners.owner_oid
|
|
where owner_role.rolname = current_user
|
|
or pg_has_role(current_user, owner_role.oid, 'MEMBER')
|
|
loop
|
|
execute format(
|
|
'alter default privileges for role %I in schema %I revoke all privileges on tables from tiku_api, tiku_worker',
|
|
owner_name,
|
|
schema_name
|
|
);
|
|
execute format(
|
|
'alter default privileges for role %I in schema %I revoke all privileges on sequences from tiku_api, tiku_worker',
|
|
owner_name,
|
|
schema_name
|
|
);
|
|
|
|
if schema_name = 'public' then
|
|
execute format(
|
|
'alter default privileges for role %I in schema public grant select, insert, update, delete on tables to tiku_api, tiku_worker',
|
|
owner_name
|
|
);
|
|
execute format(
|
|
'alter default privileges for role %I in schema public grant usage, select on sequences to tiku_api, tiku_worker',
|
|
owner_name
|
|
);
|
|
else
|
|
execute format(
|
|
'alter default privileges for role %I in schema app_private grant select on tables to tiku_api, tiku_worker',
|
|
owner_name
|
|
);
|
|
end if;
|
|
end loop;
|
|
end loop;
|
|
end
|
|
$$;
|
|
|
|
-- PostgreSQL's built-in function default is PUBLIC EXECUTE. Revoke it at the
|
|
-- owner-global level because a schema-local default ACL cannot subtract that
|
|
-- global default for future app functions.
|
|
do $$
|
|
declare
|
|
owner_name name;
|
|
begin
|
|
for owner_name in
|
|
select distinct owner_role.rolname
|
|
from pg_proc p
|
|
join pg_namespace n on n.oid = p.pronamespace
|
|
join pg_roles owner_role on owner_role.oid = p.proowner
|
|
where n.nspname = 'app'
|
|
and (
|
|
owner_role.rolname = current_user
|
|
or pg_has_role(current_user, owner_role.oid, 'MEMBER')
|
|
)
|
|
loop
|
|
execute format(
|
|
'alter default privileges for role %I revoke execute on functions from public',
|
|
owner_name
|
|
);
|
|
end loop;
|
|
end
|
|
$$;
|
|
|
|
-- Fail closed if an earlier manual setup made a runtime role an owner. Owners
|
|
-- can always ALTER/DROP their objects regardless of grants.
|
|
do $$
|
|
declare
|
|
owned_object_count integer;
|
|
begin
|
|
select count(*)
|
|
into owned_object_count
|
|
from (
|
|
select c.relowner as owner_oid
|
|
from pg_class c
|
|
join pg_namespace n on n.oid = c.relnamespace
|
|
where n.nspname in ('public', 'app', 'app_private')
|
|
union all
|
|
select p.proowner
|
|
from pg_proc p
|
|
join pg_namespace n on n.oid = p.pronamespace
|
|
where n.nspname in ('public', 'app', 'app_private')
|
|
union all
|
|
select t.typowner
|
|
from pg_type t
|
|
join pg_namespace n on n.oid = t.typnamespace
|
|
where n.nspname in ('public', 'app', 'app_private')
|
|
union all
|
|
select n.nspowner
|
|
from pg_namespace n
|
|
where n.nspname in ('public', 'app', 'app_private')
|
|
union all
|
|
select d.datdba
|
|
from pg_database d
|
|
where d.datname = current_database()
|
|
) owners
|
|
join pg_roles owner_role on owner_role.oid = owners.owner_oid
|
|
where owner_role.rolname in ('tiku_api', 'tiku_worker');
|
|
|
|
if owned_object_count > 0 then
|
|
raise exception
|
|
'tiku_api/tiku_worker must not own database objects; reassign ownership to the migration role before applying this migration';
|
|
end if;
|
|
end
|
|
$$;
|