feat(system): introduce global rules and system principals for easier system account handling
This commit is contained in:
@@ -125,6 +125,41 @@ CREATE TRIGGER trg_invalidate_cache_user_rules
|
||||
AFTER INSERT OR UPDATE OR DELETE ON morbac.user_rules
|
||||
FOR EACH ROW EXECUTE FUNCTION morbac.invalidate_cache_on_user_rule_change();
|
||||
|
||||
-- Global rules have no org scope — any change invalidates the entire cache
|
||||
CREATE OR REPLACE FUNCTION morbac.invalidate_cache_on_global_rule_change()
|
||||
RETURNS TRIGGER
|
||||
LANGUAGE plpgsql
|
||||
SECURITY DEFINER
|
||||
AS $$
|
||||
BEGIN
|
||||
DELETE FROM morbac.auth_cache;
|
||||
RETURN COALESCE(NEW, OLD);
|
||||
END;
|
||||
$$;
|
||||
|
||||
CREATE TRIGGER trg_invalidate_cache_global_rules
|
||||
AFTER INSERT OR UPDATE OR DELETE ON morbac.global_rules
|
||||
FOR EACH ROW EXECUTE FUNCTION morbac.invalidate_cache_on_global_rule_change();
|
||||
|
||||
-- system_principals changes affect prohibition bypass — invalidate per user
|
||||
CREATE OR REPLACE FUNCTION morbac.invalidate_cache_on_system_principal_change()
|
||||
RETURNS TRIGGER
|
||||
LANGUAGE plpgsql
|
||||
SECURITY DEFINER
|
||||
AS $$
|
||||
DECLARE
|
||||
v_user_id UUID;
|
||||
BEGIN
|
||||
v_user_id := CASE WHEN TG_OP = 'DELETE' THEN OLD.user_id ELSE NEW.user_id END;
|
||||
DELETE FROM morbac.auth_cache WHERE user_id = v_user_id;
|
||||
RETURN COALESCE(NEW, OLD);
|
||||
END;
|
||||
$$;
|
||||
|
||||
CREATE TRIGGER trg_invalidate_cache_system_principals
|
||||
AFTER INSERT OR UPDATE OR DELETE ON morbac.system_principals
|
||||
FOR EACH ROW EXECUTE FUNCTION morbac.invalidate_cache_on_system_principal_change();
|
||||
|
||||
-- Refresh materialized hierarchy views when hierarchies change
|
||||
|
||||
CREATE OR REPLACE FUNCTION morbac.refresh_on_hierarchy_change()
|
||||
|
||||
+100
-53
@@ -32,64 +32,92 @@ DECLARE
|
||||
v_rule RECORD;
|
||||
v_max_prohibition_priority INTEGER := NULL;
|
||||
v_max_permission_priority INTEGER := NULL;
|
||||
v_is_system_principal BOOLEAN := FALSE;
|
||||
BEGIN
|
||||
-- STEP 1: Local prohibitions — find the highest-priority applicable one
|
||||
FOR v_rule IN
|
||||
SELECT r.context_id, COALESCE(r.priority, 0) AS prio
|
||||
FROM morbac.rules r
|
||||
WHERE morbac.org_in_scope(p_org_id, r.org_id, r.scope)
|
||||
AND r.modality = 'prohibition'
|
||||
AND r.activity IN (SELECT activity FROM morbac.get_effective_activities(p_activity))
|
||||
AND r.view IN (SELECT view FROM morbac.get_effective_views(p_view))
|
||||
AND r.role_id IN (SELECT role_id FROM morbac.get_comprehensive_roles(p_user_id, r.org_id))
|
||||
AND morbac.is_rule_valid(r.valid_from, r.valid_until)
|
||||
ORDER BY COALESCE(r.priority, 0) DESC
|
||||
LOOP
|
||||
IF morbac.eval_context(v_rule.context_id) THEN
|
||||
v_max_prohibition_priority := v_rule.prio;
|
||||
EXIT;
|
||||
END IF;
|
||||
END LOOP;
|
||||
SELECT TRUE INTO v_is_system_principal
|
||||
FROM morbac.system_principals WHERE user_id = p_user_id;
|
||||
|
||||
-- STEP 2: Cross-org prohibitions — update max if a higher priority is found
|
||||
FOR v_rule IN
|
||||
SELECT cr.context_id, COALESCE(cr.priority, 0) AS prio
|
||||
FROM morbac.cross_org_rules cr
|
||||
WHERE cr.target_org_id = p_org_id
|
||||
AND cr.modality = 'prohibition'
|
||||
AND cr.activity IN (SELECT activity FROM morbac.get_effective_activities(p_activity))
|
||||
AND cr.view IN (SELECT view FROM morbac.get_effective_views(p_view))
|
||||
AND cr.role_id IN (SELECT role_id FROM morbac.get_comprehensive_roles(p_user_id, cr.source_org_id))
|
||||
AND morbac.is_rule_valid(cr.valid_from, cr.valid_until)
|
||||
ORDER BY COALESCE(cr.priority, 0) DESC
|
||||
LOOP
|
||||
IF morbac.eval_context(v_rule.context_id) THEN
|
||||
IF v_max_prohibition_priority IS NULL OR v_rule.prio > v_max_prohibition_priority THEN
|
||||
v_max_prohibition_priority := v_rule.prio;
|
||||
END IF;
|
||||
EXIT;
|
||||
END IF;
|
||||
END LOOP;
|
||||
-- STEPS 1-3.5: Prohibitions — skipped entirely for system principals
|
||||
IF NOT v_is_system_principal THEN
|
||||
|
||||
-- STEP 3: User-level prohibitions — direct user rules, update max if higher
|
||||
FOR v_rule IN
|
||||
SELECT ur.context_id, COALESCE(ur.priority, 0) AS prio
|
||||
FROM morbac.user_rules ur
|
||||
WHERE ur.user_id = p_user_id
|
||||
AND ur.org_id = p_org_id
|
||||
AND ur.modality = 'prohibition'
|
||||
AND ur.activity IN (SELECT activity FROM morbac.get_effective_activities(p_activity))
|
||||
AND ur.view IN (SELECT view FROM morbac.get_effective_views(p_view))
|
||||
AND morbac.is_rule_valid(ur.valid_from, ur.valid_until)
|
||||
ORDER BY COALESCE(ur.priority, 0) DESC
|
||||
LOOP
|
||||
IF morbac.eval_context(v_rule.context_id) THEN
|
||||
IF v_max_prohibition_priority IS NULL OR v_rule.prio > v_max_prohibition_priority THEN
|
||||
-- STEP 1: Local prohibitions — find the highest-priority applicable one
|
||||
FOR v_rule IN
|
||||
SELECT r.context_id, COALESCE(r.priority, 0) AS prio
|
||||
FROM morbac.rules r
|
||||
WHERE morbac.org_in_scope(p_org_id, r.org_id, r.scope)
|
||||
AND r.modality = 'prohibition'
|
||||
AND r.activity IN (SELECT activity FROM morbac.get_effective_activities(p_activity))
|
||||
AND r.view IN (SELECT view FROM morbac.get_effective_views(p_view))
|
||||
AND r.role_id IN (SELECT role_id FROM morbac.get_comprehensive_roles(p_user_id, r.org_id))
|
||||
AND morbac.is_rule_valid(r.valid_from, r.valid_until)
|
||||
ORDER BY COALESCE(r.priority, 0) DESC
|
||||
LOOP
|
||||
IF morbac.eval_context(v_rule.context_id) THEN
|
||||
v_max_prohibition_priority := v_rule.prio;
|
||||
EXIT;
|
||||
END IF;
|
||||
EXIT;
|
||||
END IF;
|
||||
END LOOP;
|
||||
END LOOP;
|
||||
|
||||
-- STEP 2: Cross-org prohibitions — update max if a higher priority is found
|
||||
FOR v_rule IN
|
||||
SELECT cr.context_id, COALESCE(cr.priority, 0) AS prio
|
||||
FROM morbac.cross_org_rules cr
|
||||
WHERE cr.target_org_id = p_org_id
|
||||
AND cr.modality = 'prohibition'
|
||||
AND cr.activity IN (SELECT activity FROM morbac.get_effective_activities(p_activity))
|
||||
AND cr.view IN (SELECT view FROM morbac.get_effective_views(p_view))
|
||||
AND cr.role_id IN (SELECT role_id FROM morbac.get_comprehensive_roles(p_user_id, cr.source_org_id))
|
||||
AND morbac.is_rule_valid(cr.valid_from, cr.valid_until)
|
||||
ORDER BY COALESCE(cr.priority, 0) DESC
|
||||
LOOP
|
||||
IF morbac.eval_context(v_rule.context_id) THEN
|
||||
IF v_max_prohibition_priority IS NULL OR v_rule.prio > v_max_prohibition_priority THEN
|
||||
v_max_prohibition_priority := v_rule.prio;
|
||||
END IF;
|
||||
EXIT;
|
||||
END IF;
|
||||
END LOOP;
|
||||
|
||||
-- STEP 3: User-level prohibitions — direct user rules, update max if higher
|
||||
FOR v_rule IN
|
||||
SELECT ur.context_id, COALESCE(ur.priority, 0) AS prio
|
||||
FROM morbac.user_rules ur
|
||||
WHERE ur.user_id = p_user_id
|
||||
AND ur.org_id = p_org_id
|
||||
AND ur.modality = 'prohibition'
|
||||
AND ur.activity IN (SELECT activity FROM morbac.get_effective_activities(p_activity))
|
||||
AND ur.view IN (SELECT view FROM morbac.get_effective_views(p_view))
|
||||
AND morbac.is_rule_valid(ur.valid_from, ur.valid_until)
|
||||
ORDER BY COALESCE(ur.priority, 0) DESC
|
||||
LOOP
|
||||
IF morbac.eval_context(v_rule.context_id) THEN
|
||||
IF v_max_prohibition_priority IS NULL OR v_rule.prio > v_max_prohibition_priority THEN
|
||||
v_max_prohibition_priority := v_rule.prio;
|
||||
END IF;
|
||||
EXIT;
|
||||
END IF;
|
||||
END LOOP;
|
||||
|
||||
-- STEP 3.5: Global prohibitions
|
||||
FOR v_rule IN
|
||||
SELECT gr.context_id, COALESCE(gr.priority, 0) AS prio
|
||||
FROM morbac.global_rules gr
|
||||
WHERE (gr.user_id IS NULL OR gr.user_id = p_user_id)
|
||||
AND gr.modality = 'prohibition'
|
||||
AND (gr.activity IS NULL OR gr.activity IN (SELECT activity FROM morbac.get_effective_activities(p_activity)))
|
||||
AND (gr.view IS NULL OR gr.view IN (SELECT view FROM morbac.get_effective_views(p_view)))
|
||||
AND morbac.is_rule_valid(gr.valid_from, gr.valid_until)
|
||||
ORDER BY COALESCE(gr.priority, 0) DESC
|
||||
LOOP
|
||||
IF morbac.eval_context(v_rule.context_id) THEN
|
||||
IF v_max_prohibition_priority IS NULL OR v_rule.prio > v_max_prohibition_priority THEN
|
||||
v_max_prohibition_priority := v_rule.prio;
|
||||
END IF;
|
||||
EXIT;
|
||||
END IF;
|
||||
END LOOP;
|
||||
|
||||
END IF; -- v_is_system_principal
|
||||
|
||||
-- STEP 4: Local permissions — find the highest-priority applicable one
|
||||
FOR v_rule IN
|
||||
@@ -149,6 +177,25 @@ BEGIN
|
||||
END IF;
|
||||
END LOOP;
|
||||
|
||||
-- STEP 6.5: Global permissions
|
||||
FOR v_rule IN
|
||||
SELECT gr.context_id, COALESCE(gr.priority, 0) AS prio
|
||||
FROM morbac.global_rules gr
|
||||
WHERE (gr.user_id IS NULL OR gr.user_id = p_user_id)
|
||||
AND gr.modality = 'permission'
|
||||
AND (gr.activity IS NULL OR gr.activity IN (SELECT activity FROM morbac.get_effective_activities(p_activity)))
|
||||
AND (gr.view IS NULL OR gr.view IN (SELECT view FROM morbac.get_effective_views(p_view)))
|
||||
AND morbac.is_rule_valid(gr.valid_from, gr.valid_until)
|
||||
ORDER BY COALESCE(gr.priority, 0) DESC
|
||||
LOOP
|
||||
IF morbac.eval_context(v_rule.context_id) THEN
|
||||
IF v_max_permission_priority IS NULL OR v_rule.prio > v_max_permission_priority THEN
|
||||
v_max_permission_priority := v_rule.prio;
|
||||
END IF;
|
||||
EXIT;
|
||||
END IF;
|
||||
END LOOP;
|
||||
|
||||
-- STEP 7: Priority resolution
|
||||
IF v_max_prohibition_priority IS NULL THEN
|
||||
RETURN v_max_permission_priority IS NOT NULL;
|
||||
|
||||
+3
-1
@@ -23,7 +23,9 @@ INSERT INTO morbac.config (key, value, description) VALUES
|
||||
('system_view.views', 'views', 'View name for morbac.views table access control'),
|
||||
('system_view.delegations', 'delegations', 'View name for morbac.delegations table access control'),
|
||||
('system_view.cross_org_rules', 'cross_org_rules', 'View name for morbac.cross_org_rules table access control'),
|
||||
('system_view.user_rules', 'user_rules', 'View name for morbac.user_rules table access control');
|
||||
('system_view.user_rules', 'user_rules', 'View name for morbac.user_rules table access control'),
|
||||
('system_view.global_rules', 'global_rules', 'View name for morbac.global_rules table access control'),
|
||||
('system_view.system_principals', 'system_principals', 'View name for morbac.system_principals table access control');
|
||||
|
||||
CREATE OR REPLACE FUNCTION morbac.get_config(p_key TEXT)
|
||||
RETURNS TEXT
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
-- Global rules: Rule(user_id, activity, view, context, modality)
|
||||
--
|
||||
-- No org_id or role_id — applies system-wide regardless of org membership or roles.
|
||||
-- user_id NULL = every user; non-NULL = specific user only.
|
||||
-- activity NULL = any activity; view NULL = any view.
|
||||
--
|
||||
-- Evaluated at steps 3.5 (prohibitions) and 6.5 (permissions) in is_allowed_nocache().
|
||||
-- When activity/view are set, hierarchy resolution applies normally.
|
||||
|
||||
CREATE TABLE morbac.global_rules (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
user_id UUID,
|
||||
activity TEXT REFERENCES morbac.activities(name) ON DELETE CASCADE,
|
||||
view TEXT REFERENCES morbac.views(name) ON DELETE CASCADE,
|
||||
context_id UUID NOT NULL REFERENCES morbac.contexts(id) ON DELETE CASCADE,
|
||||
modality morbac.modality NOT NULL,
|
||||
priority INTEGER,
|
||||
valid_from TIMESTAMPTZ,
|
||||
valid_until TIMESTAMPTZ,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
is_active BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
metadata JSONB DEFAULT '{}'::jsonb,
|
||||
CHECK (valid_until IS NULL OR valid_from IS NULL OR valid_until > valid_from),
|
||||
UNIQUE(user_id, activity, view, context_id, modality)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_global_rules_user_id ON morbac.global_rules(user_id);
|
||||
CREATE INDEX idx_global_rules_activity_view ON morbac.global_rules(activity, view);
|
||||
CREATE INDEX idx_global_rules_modality ON morbac.global_rules(modality);
|
||||
CREATE INDEX idx_global_rules_fast_lookup ON morbac.global_rules(modality)
|
||||
INCLUDE (user_id, activity, view, context_id)
|
||||
WHERE is_active = true;
|
||||
|
||||
COMMENT ON TABLE morbac.global_rules IS
|
||||
'System-wide rules with no org or role binding. user_id NULL = all users; activity/view NULL = any activity/view.';
|
||||
COMMENT ON COLUMN morbac.global_rules.user_id IS
|
||||
'NULL = all users; non-NULL = this specific user only';
|
||||
COMMENT ON COLUMN morbac.global_rules.activity IS
|
||||
'NULL = any activity; non-NULL = specific activity (hierarchy applies)';
|
||||
COMMENT ON COLUMN morbac.global_rules.view IS
|
||||
'NULL = any view; non-NULL = specific view (hierarchy applies)';
|
||||
|
||||
CREATE OR REPLACE FUNCTION morbac.global_rules_set_is_active()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
NEW.is_active := (
|
||||
(NEW.valid_from IS NULL OR NEW.valid_from <= CURRENT_TIMESTAMP)
|
||||
AND (NEW.valid_until IS NULL OR NEW.valid_until > CURRENT_TIMESTAMP)
|
||||
);
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
DROP TRIGGER IF EXISTS trg_global_rules_set_is_active ON morbac.global_rules;
|
||||
CREATE TRIGGER trg_global_rules_set_is_active
|
||||
BEFORE INSERT OR UPDATE ON morbac.global_rules
|
||||
FOR EACH ROW EXECUTE FUNCTION morbac.global_rules_set_is_active();
|
||||
@@ -0,0 +1,100 @@
|
||||
-- System principals: backend service accounts defined at deploy time.
|
||||
--
|
||||
-- Registered user_ids are fully protected at the trigger level:
|
||||
-- - No role assignments, revocations, or negative assignments
|
||||
-- - No user_rules (permissions or prohibitions)
|
||||
-- - No delegations involving them
|
||||
-- - No targeted global_rules prohibitions
|
||||
-- - All prohibitions are skipped in is_allowed_nocache() (see authorization.sql)
|
||||
--
|
||||
-- This table has no INSERT/UPDATE/DELETE RLS policies — only the database owner
|
||||
-- can register or remove system principals (done in SQL at deploy time).
|
||||
-- SELECT is gated by is_allowed() like all other system tables.
|
||||
|
||||
CREATE TABLE morbac.system_principals (
|
||||
user_id UUID PRIMARY KEY,
|
||||
description TEXT
|
||||
);
|
||||
|
||||
COMMENT ON TABLE morbac.system_principals IS
|
||||
'Registry of backend service accounts. Immutable at the trigger level — no policy can touch them.';
|
||||
COMMENT ON COLUMN morbac.system_principals.user_id IS
|
||||
'External user UUID of the service account';
|
||||
|
||||
-- Shared guard for tables with a single user_id column
|
||||
|
||||
CREATE OR REPLACE FUNCTION morbac.raise_if_system_principal()
|
||||
RETURNS TRIGGER
|
||||
LANGUAGE plpgsql
|
||||
SECURITY DEFINER
|
||||
AS $$
|
||||
DECLARE
|
||||
v_user_id UUID;
|
||||
BEGIN
|
||||
v_user_id := CASE WHEN TG_OP = 'DELETE' THEN OLD.user_id ELSE NEW.user_id END;
|
||||
IF EXISTS (SELECT 1 FROM morbac.system_principals WHERE user_id = v_user_id) THEN
|
||||
RAISE EXCEPTION 'operation blocked: % is a system principal', v_user_id;
|
||||
END IF;
|
||||
RETURN COALESCE(NEW, OLD);
|
||||
END;
|
||||
$$;
|
||||
|
||||
CREATE TRIGGER trg_system_principal_user_roles
|
||||
BEFORE INSERT OR UPDATE OR DELETE ON morbac.user_roles
|
||||
FOR EACH ROW EXECUTE FUNCTION morbac.raise_if_system_principal();
|
||||
|
||||
CREATE TRIGGER trg_system_principal_user_rules
|
||||
BEFORE INSERT OR UPDATE OR DELETE ON morbac.user_rules
|
||||
FOR EACH ROW EXECUTE FUNCTION morbac.raise_if_system_principal();
|
||||
|
||||
CREATE TRIGGER trg_system_principal_negative_roles
|
||||
BEFORE INSERT OR UPDATE OR DELETE ON morbac.negative_role_assignments
|
||||
FOR EACH ROW EXECUTE FUNCTION morbac.raise_if_system_principal();
|
||||
|
||||
-- Delegations have two user_id columns
|
||||
|
||||
CREATE OR REPLACE FUNCTION morbac.raise_if_system_principal_delegation()
|
||||
RETURNS TRIGGER
|
||||
LANGUAGE plpgsql
|
||||
SECURITY DEFINER
|
||||
AS $$
|
||||
BEGIN
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM morbac.system_principals
|
||||
WHERE user_id IN (NEW.delegator_id, NEW.delegatee_id)
|
||||
) THEN
|
||||
RAISE EXCEPTION 'operation blocked: delegations cannot involve system principals';
|
||||
END IF;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$;
|
||||
|
||||
CREATE TRIGGER trg_system_principal_delegations
|
||||
BEFORE INSERT OR UPDATE ON morbac.delegations
|
||||
FOR EACH ROW EXECUTE FUNCTION morbac.raise_if_system_principal_delegation();
|
||||
|
||||
-- Global rules: all operations on rows belonging to a system principal are blocked.
|
||||
-- This protects both the permission rules defined for them and prevents prohibitions
|
||||
-- from being added against them. Define their rules at deploy time as the DB owner.
|
||||
|
||||
CREATE OR REPLACE FUNCTION morbac.raise_if_system_principal_global_rule()
|
||||
RETURNS TRIGGER
|
||||
LANGUAGE plpgsql
|
||||
SECURITY DEFINER
|
||||
AS $$
|
||||
DECLARE
|
||||
v_user_id UUID;
|
||||
BEGIN
|
||||
v_user_id := CASE WHEN TG_OP = 'DELETE' THEN OLD.user_id ELSE NEW.user_id END;
|
||||
IF v_user_id IS NOT NULL
|
||||
AND EXISTS (SELECT 1 FROM morbac.system_principals WHERE user_id = v_user_id)
|
||||
THEN
|
||||
RAISE EXCEPTION 'operation blocked: global rules for system principal % are immutable', v_user_id;
|
||||
END IF;
|
||||
RETURN COALESCE(NEW, OLD);
|
||||
END;
|
||||
$$;
|
||||
|
||||
CREATE TRIGGER trg_system_principal_global_rules
|
||||
BEFORE INSERT OR UPDATE OR DELETE ON morbac.global_rules
|
||||
FOR EACH ROW EXECUTE FUNCTION morbac.raise_if_system_principal_global_rule();
|
||||
@@ -180,6 +180,32 @@ CREATE POLICY user_rules_delete ON morbac.user_rules FOR DELETE
|
||||
USING (morbac.is_allowed(morbac.current_user_id(), org_id, 'delete',
|
||||
morbac.get_config('system_view.user_rules')));
|
||||
|
||||
-- morbac.system_principals (no org_id — SELECT only; INSERT/UPDATE/DELETE reserved for DB owner)
|
||||
ALTER TABLE morbac.system_principals ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
CREATE POLICY system_principals_select ON morbac.system_principals FOR SELECT
|
||||
USING (morbac.is_allowed(morbac.current_user_id(), morbac.current_org_id(), 'read',
|
||||
morbac.get_config('system_view.system_principals')));
|
||||
|
||||
-- morbac.global_rules (no org_id — use current session org for write checks)
|
||||
ALTER TABLE morbac.global_rules ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
CREATE POLICY global_rules_select ON morbac.global_rules FOR SELECT
|
||||
USING (morbac.is_allowed(morbac.current_user_id(), morbac.current_org_id(), 'read',
|
||||
morbac.get_config('system_view.global_rules')));
|
||||
|
||||
CREATE POLICY global_rules_insert ON morbac.global_rules FOR INSERT
|
||||
WITH CHECK (morbac.is_allowed(morbac.current_user_id(), morbac.current_org_id(), 'create',
|
||||
morbac.get_config('system_view.global_rules')));
|
||||
|
||||
CREATE POLICY global_rules_update ON morbac.global_rules FOR UPDATE
|
||||
USING (morbac.is_allowed(morbac.current_user_id(), morbac.current_org_id(), 'update',
|
||||
morbac.get_config('system_view.global_rules')));
|
||||
|
||||
CREATE POLICY global_rules_delete ON morbac.global_rules FOR DELETE
|
||||
USING (morbac.is_allowed(morbac.current_user_id(), morbac.current_org_id(), 'delete',
|
||||
morbac.get_config('system_view.global_rules')));
|
||||
|
||||
-- morbac.cross_org_rules
|
||||
ALTER TABLE morbac.cross_org_rules ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user