feat: isolate administrators from task data plane
This commit is contained in:
213
control-plane/migrations/021_admin_task_data_plane_isolation.sql
Normal file
213
control-plane/migrations/021_admin_task_data_plane_isolation.sql
Normal file
@@ -0,0 +1,213 @@
|
||||
-- Administrators are management-plane identities only. Remove any legacy
|
||||
-- execution bindings and reject every future database write that would make
|
||||
-- an administrator a task creator, assignee, channel owner, browser worker,
|
||||
-- or business-route grantee.
|
||||
|
||||
UPDATE tasks task
|
||||
SET assigned_user_id = NULL,
|
||||
lease_owner = NULL,
|
||||
lease_expires_at = NULL,
|
||||
updated_at = now()
|
||||
FROM users account
|
||||
WHERE account.organization_id = task.organization_id
|
||||
AND account.id = task.assigned_user_id
|
||||
AND account.role = 'admin';
|
||||
|
||||
UPDATE user_channels channel
|
||||
SET owner_user_id = NULL,
|
||||
enabled = false,
|
||||
status = 'disabled',
|
||||
last_error = '管理员账号已与任务执行面隔离,渠道绑定已解除。',
|
||||
updated_at = now()
|
||||
FROM users account
|
||||
WHERE account.organization_id = channel.organization_id
|
||||
AND account.id = channel.owner_user_id
|
||||
AND account.role = 'admin';
|
||||
|
||||
UPDATE browser_connections connection
|
||||
SET status = 'superseded',
|
||||
erp_account_verified = false
|
||||
FROM users account
|
||||
WHERE account.organization_id = connection.organization_id
|
||||
AND account.id = connection.user_id
|
||||
AND account.role = 'admin'
|
||||
AND connection.status = 'connected';
|
||||
|
||||
DELETE FROM user_business_route_authorizations route_grant
|
||||
USING users account
|
||||
WHERE account.organization_id = route_grant.organization_id
|
||||
AND account.id = route_grant.user_id
|
||||
AND account.role = 'admin';
|
||||
|
||||
UPDATE leader_task_summary_subscriptions subscription
|
||||
SET enabled = false,
|
||||
target_verified_at = NULL,
|
||||
revision = revision + 1,
|
||||
updated_at = now()
|
||||
FROM users account
|
||||
WHERE account.organization_id = subscription.organization_id
|
||||
AND account.id = subscription.leader_user_id
|
||||
AND account.role = 'admin';
|
||||
|
||||
UPDATE leader_task_summary_deliveries delivery
|
||||
SET delivery_status = 'cancelled',
|
||||
last_error = '管理员账号已与任务数据面隔离,组长任务摘要投递已取消。',
|
||||
updated_at = now()
|
||||
FROM users account
|
||||
WHERE account.organization_id = delivery.organization_id
|
||||
AND account.id = delivery.leader_user_id
|
||||
AND account.role = 'admin'
|
||||
AND delivery.delivery_status IN ('pending', 'sending', 'failed');
|
||||
|
||||
CREATE OR REPLACE FUNCTION reject_admin_task_principal()
|
||||
RETURNS trigger
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
principal_id uuid;
|
||||
principal_role text;
|
||||
BEGIN
|
||||
principal_id := NULLIF(to_jsonb(NEW) ->> TG_ARGV[0], '')::uuid;
|
||||
IF principal_id IS NULL THEN
|
||||
RETURN NEW;
|
||||
END IF;
|
||||
|
||||
SELECT role
|
||||
INTO principal_role
|
||||
FROM users
|
||||
WHERE organization_id = NEW.organization_id
|
||||
AND id = principal_id
|
||||
FOR SHARE;
|
||||
|
||||
IF principal_role = 'admin' THEN
|
||||
RAISE EXCEPTION USING
|
||||
ERRCODE = '23514',
|
||||
CONSTRAINT = TG_ARGV[1],
|
||||
MESSAGE = 'administrator identities cannot enter the task data plane';
|
||||
END IF;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$;
|
||||
|
||||
DROP TRIGGER IF EXISTS tasks_admin_assignee_forbidden_trigger ON tasks;
|
||||
CREATE TRIGGER tasks_admin_assignee_forbidden_trigger
|
||||
BEFORE INSERT OR UPDATE OF organization_id, assigned_user_id ON tasks
|
||||
FOR EACH ROW EXECUTE FUNCTION reject_admin_task_principal(
|
||||
'assigned_user_id',
|
||||
'tasks_admin_assignee_forbidden'
|
||||
);
|
||||
|
||||
DROP TRIGGER IF EXISTS tasks_admin_creator_forbidden_trigger ON tasks;
|
||||
CREATE TRIGGER tasks_admin_creator_forbidden_trigger
|
||||
BEFORE INSERT OR UPDATE OF organization_id, created_by ON tasks
|
||||
FOR EACH ROW EXECUTE FUNCTION reject_admin_task_principal(
|
||||
'created_by',
|
||||
'tasks_admin_creator_forbidden'
|
||||
);
|
||||
|
||||
DROP TRIGGER IF EXISTS user_channels_admin_owner_forbidden_trigger ON user_channels;
|
||||
CREATE TRIGGER user_channels_admin_owner_forbidden_trigger
|
||||
BEFORE INSERT OR UPDATE OF organization_id, owner_user_id ON user_channels
|
||||
FOR EACH ROW EXECUTE FUNCTION reject_admin_task_principal(
|
||||
'owner_user_id',
|
||||
'user_channels_admin_owner_forbidden'
|
||||
);
|
||||
|
||||
DROP TRIGGER IF EXISTS browser_connections_admin_worker_forbidden_trigger ON browser_connections;
|
||||
CREATE TRIGGER browser_connections_admin_worker_forbidden_trigger
|
||||
BEFORE INSERT OR UPDATE OF organization_id, user_id ON browser_connections
|
||||
FOR EACH ROW EXECUTE FUNCTION reject_admin_task_principal(
|
||||
'user_id',
|
||||
'browser_connections_admin_worker_forbidden'
|
||||
);
|
||||
|
||||
DROP TRIGGER IF EXISTS user_business_routes_admin_grantee_forbidden_trigger
|
||||
ON user_business_route_authorizations;
|
||||
CREATE TRIGGER user_business_routes_admin_grantee_forbidden_trigger
|
||||
BEFORE INSERT OR UPDATE OF organization_id, user_id ON user_business_route_authorizations
|
||||
FOR EACH ROW EXECUTE FUNCTION reject_admin_task_principal(
|
||||
'user_id',
|
||||
'user_business_routes_admin_grantee_forbidden'
|
||||
);
|
||||
|
||||
DROP TRIGGER IF EXISTS leader_task_summary_admin_subscriber_forbidden_trigger
|
||||
ON leader_task_summary_subscriptions;
|
||||
CREATE TRIGGER leader_task_summary_admin_subscriber_forbidden_trigger
|
||||
BEFORE INSERT OR UPDATE OF organization_id, leader_user_id ON leader_task_summary_subscriptions
|
||||
FOR EACH ROW EXECUTE FUNCTION reject_admin_task_principal(
|
||||
'leader_user_id',
|
||||
'leader_task_summary_admin_subscriber_forbidden'
|
||||
);
|
||||
|
||||
DROP TRIGGER IF EXISTS leader_task_summary_admin_recipient_forbidden_trigger
|
||||
ON leader_task_summary_deliveries;
|
||||
CREATE TRIGGER leader_task_summary_admin_recipient_forbidden_trigger
|
||||
BEFORE INSERT OR UPDATE OF organization_id, leader_user_id ON leader_task_summary_deliveries
|
||||
FOR EACH ROW EXECUTE FUNCTION reject_admin_task_principal(
|
||||
'leader_user_id',
|
||||
'leader_task_summary_admin_recipient_forbidden'
|
||||
);
|
||||
|
||||
CREATE OR REPLACE FUNCTION isolate_administrator_from_task_runtime()
|
||||
RETURNS trigger
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
BEGIN
|
||||
IF NEW.role <> 'admin' THEN
|
||||
RETURN NEW;
|
||||
END IF;
|
||||
|
||||
UPDATE tasks
|
||||
SET assigned_user_id = NULL,
|
||||
lease_owner = NULL,
|
||||
lease_expires_at = NULL,
|
||||
updated_at = now()
|
||||
WHERE organization_id = NEW.organization_id
|
||||
AND assigned_user_id = NEW.id;
|
||||
|
||||
UPDATE user_channels
|
||||
SET owner_user_id = NULL,
|
||||
enabled = false,
|
||||
status = 'disabled',
|
||||
last_error = '绑定账号已变更为管理员,渠道已解除绑定。',
|
||||
updated_at = now()
|
||||
WHERE organization_id = NEW.organization_id
|
||||
AND owner_user_id = NEW.id;
|
||||
|
||||
UPDATE browser_connections
|
||||
SET status = 'superseded',
|
||||
erp_account_verified = false
|
||||
WHERE organization_id = NEW.organization_id
|
||||
AND user_id = NEW.id
|
||||
AND status = 'connected';
|
||||
|
||||
DELETE FROM user_business_route_authorizations
|
||||
WHERE organization_id = NEW.organization_id
|
||||
AND user_id = NEW.id;
|
||||
|
||||
UPDATE leader_task_summary_subscriptions
|
||||
SET enabled = false,
|
||||
target_verified_at = NULL,
|
||||
revision = revision + 1,
|
||||
updated_at = now()
|
||||
WHERE organization_id = NEW.organization_id
|
||||
AND leader_user_id = NEW.id;
|
||||
|
||||
UPDATE leader_task_summary_deliveries
|
||||
SET delivery_status = 'cancelled',
|
||||
last_error = '账号已变更为管理员,组长任务摘要投递已取消。',
|
||||
updated_at = now()
|
||||
WHERE organization_id = NEW.organization_id
|
||||
AND leader_user_id = NEW.id
|
||||
AND delivery_status IN ('pending', 'sending', 'failed');
|
||||
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$;
|
||||
|
||||
DROP TRIGGER IF EXISTS users_admin_task_runtime_isolation_trigger ON users;
|
||||
CREATE TRIGGER users_admin_task_runtime_isolation_trigger
|
||||
AFTER UPDATE OF role ON users
|
||||
FOR EACH ROW
|
||||
WHEN (NEW.role = 'admin' AND OLD.role IS DISTINCT FROM NEW.role)
|
||||
EXECUTE FUNCTION isolate_administrator_from_task_runtime();
|
||||
Reference in New Issue
Block a user