191 lines
7.6 KiB
PL/PgSQL
191 lines
7.6 KiB
PL/PgSQL
-- Caterium v17.5.30 - granular administrator permissions and correct last-admin checks.
|
||
|
||
create or replace function public.sun_has_permission(p_workspace uuid, p_permission text)
|
||
returns boolean
|
||
language plpgsql
|
||
stable
|
||
security definer
|
||
set search_path='public'
|
||
as $$
|
||
declare
|
||
v_role text;
|
||
v_permissions jsonb;
|
||
v_active boolean;
|
||
begin
|
||
select role,permissions,is_active
|
||
into v_role,v_permissions,v_active
|
||
from public.sun_workspace_members
|
||
where workspace_id=p_workspace and user_id=auth.uid()
|
||
limit 1;
|
||
|
||
if not coalesce(v_active,false) then return false; end if;
|
||
|
||
-- Explicit member permissions override the role template for every role,
|
||
-- including administrators. Missing keys fall back to the role defaults.
|
||
if coalesce(v_permissions,'{}'::jsonb) ? p_permission then
|
||
return coalesce((v_permissions->>p_permission)::boolean,false);
|
||
end if;
|
||
return coalesce((public.sun_role_default_permissions(v_role)->>p_permission)::boolean,false);
|
||
end;
|
||
$$;
|
||
|
||
create or replace function public.sun_admin_update_member(
|
||
p_workspace uuid,
|
||
p_user uuid,
|
||
p_display_name text,
|
||
p_role text,
|
||
p_is_active boolean,
|
||
p_permissions jsonb
|
||
)
|
||
returns void
|
||
language plpgsql
|
||
security definer
|
||
set search_path='public'
|
||
as $$
|
||
declare
|
||
v_old_role text;
|
||
v_old_active boolean;
|
||
v_role text:=lower(coalesce(p_role,''));
|
||
v_other_admins integer:=0;
|
||
v_other_managing_admins integer:=0;
|
||
v_target_will_manage boolean:=false;
|
||
v_max integer;
|
||
v_active_count integer;
|
||
begin
|
||
-- Serialize membership administration inside one workspace so two admins
|
||
-- cannot simultaneously remove/demote the last administrators.
|
||
perform pg_advisory_xact_lock(hashtext(p_workspace::text));
|
||
|
||
if public.sun_subscription_access_mode(p_workspace)<>'full' then raise exception 'Подписка не позволяет изменять пользователей'; end if;
|
||
if not public.sun_workspace_has_feature(p_workspace,'users_manage') then raise exception 'Управление сотрудниками недоступно на текущем тарифе'; end if;
|
||
if not public.sun_has_permission(p_workspace,'users.manage') then raise exception 'Недостаточно прав для управления сотрудниками'; end if;
|
||
if v_role not in ('admin','manager','kitchen','courier','viewer') then raise exception 'Некорректная роль'; end if;
|
||
if p_permissions is null or jsonb_typeof(p_permissions)<>'object' then raise exception 'Некорректные права пользователя'; end if;
|
||
|
||
select role,is_active into v_old_role,v_old_active
|
||
from public.sun_workspace_members
|
||
where workspace_id=p_workspace and user_id=p_user
|
||
for update;
|
||
if not found then raise exception 'Пользователь не найден'; end if;
|
||
|
||
select count(*)::int into v_other_admins
|
||
from public.sun_workspace_members
|
||
where workspace_id=p_workspace
|
||
and user_id<>p_user
|
||
and role='admin'
|
||
and is_active=true;
|
||
|
||
if v_old_role='admin' and coalesce(v_old_active,false)
|
||
and (v_role<>'admin' or not coalesce(p_is_active,false))
|
||
and v_other_admins=0 then
|
||
raise exception 'Нельзя отключить или понизить последнего активного администратора';
|
||
end if;
|
||
|
||
select count(*)::int into v_other_managing_admins
|
||
from public.sun_workspace_members
|
||
where workspace_id=p_workspace
|
||
and user_id<>p_user
|
||
and role='admin'
|
||
and is_active=true
|
||
and coalesce(
|
||
case when coalesce(permissions,'{}'::jsonb) ? 'users.manage'
|
||
then (permissions->>'users.manage')::boolean
|
||
else null end,
|
||
true
|
||
)=true;
|
||
|
||
v_target_will_manage := v_role='admin'
|
||
and coalesce(p_is_active,false)
|
||
and coalesce(
|
||
case when p_permissions ? 'users.manage'
|
||
then (p_permissions->>'users.manage')::boolean
|
||
else null end,
|
||
true
|
||
);
|
||
|
||
if not v_target_will_manage and v_other_managing_admins=0 then
|
||
raise exception 'У хотя бы одного активного администратора должно оставаться право «Пользователи и права»';
|
||
end if;
|
||
|
||
if coalesce(p_is_active,false) and not coalesce(v_old_active,false) then
|
||
select p.max_members into v_max
|
||
from public.sun_workspace_subscriptions s
|
||
join public.sun_plans p on p.id=s.plan_id
|
||
where s.workspace_id=p_workspace;
|
||
select count(*)::int into v_active_count
|
||
from public.sun_workspace_members
|
||
where workspace_id=p_workspace and is_active=true;
|
||
if v_max is not null and v_active_count>=v_max then
|
||
raise exception 'Достигнут лимит сотрудников тарифа (%).',v_max;
|
||
end if;
|
||
end if;
|
||
|
||
update public.sun_workspace_members
|
||
set display_name=nullif(trim(coalesce(p_display_name,'')),''),
|
||
role=v_role,
|
||
is_active=coalesce(p_is_active,false),
|
||
permissions=p_permissions,
|
||
updated_at=now()
|
||
where workspace_id=p_workspace and user_id=p_user;
|
||
end;
|
||
$$;
|
||
|
||
create or replace function public.sun_admin_remove_member(p_workspace uuid,p_user uuid)
|
||
returns void
|
||
language plpgsql
|
||
security definer
|
||
set search_path='public'
|
||
as $$
|
||
declare
|
||
v_role text;
|
||
v_active boolean;
|
||
v_other_admins integer:=0;
|
||
v_other_managing_admins integer:=0;
|
||
begin
|
||
perform pg_advisory_xact_lock(hashtext(p_workspace::text));
|
||
|
||
if public.sun_subscription_access_mode(p_workspace)<>'full' then raise exception 'Подписка не позволяет изменять пользователей'; end if;
|
||
if not public.sun_workspace_has_feature(p_workspace,'users_manage') then raise exception 'Управление сотрудниками недоступно на текущем тарифе'; end if;
|
||
if not public.sun_has_permission(p_workspace,'users.manage') then raise exception 'Недостаточно прав для управления сотрудниками'; end if;
|
||
|
||
select role,is_active into v_role,v_active
|
||
from public.sun_workspace_members
|
||
where workspace_id=p_workspace and user_id=p_user
|
||
for update;
|
||
if not found then return; end if;
|
||
|
||
if v_role='admin' and coalesce(v_active,false) then
|
||
select count(*)::int into v_other_admins
|
||
from public.sun_workspace_members
|
||
where workspace_id=p_workspace and user_id<>p_user and role='admin' and is_active=true;
|
||
if v_other_admins=0 then raise exception 'Нельзя удалить последнего активного администратора'; end if;
|
||
|
||
select count(*)::int into v_other_managing_admins
|
||
from public.sun_workspace_members
|
||
where workspace_id=p_workspace
|
||
and user_id<>p_user
|
||
and role='admin'
|
||
and is_active=true
|
||
and coalesce(
|
||
case when coalesce(permissions,'{}'::jsonb) ? 'users.manage'
|
||
then (permissions->>'users.manage')::boolean
|
||
else null end,
|
||
true
|
||
)=true;
|
||
if v_other_managing_admins=0 then
|
||
raise exception 'Нельзя удалить администратора: после удаления никто не сможет управлять пользователями';
|
||
end if;
|
||
end if;
|
||
|
||
delete from public.sun_workspace_members
|
||
where workspace_id=p_workspace and user_id=p_user;
|
||
end;
|
||
$$;
|
||
|
||
revoke all on function public.sun_has_permission(uuid,text) from public,anon;
|
||
revoke all on function public.sun_admin_update_member(uuid,uuid,text,text,boolean,jsonb) from public,anon;
|
||
revoke all on function public.sun_admin_remove_member(uuid,uuid) from public,anon;
|
||
grant execute on function public.sun_has_permission(uuid,text) to authenticated;
|
||
grant execute on function public.sun_admin_update_member(uuid,uuid,text,text,boolean,jsonb) to authenticated;
|
||
grant execute on function public.sun_admin_remove_member(uuid,uuid) to authenticated;
|