feat(rules): add preemptive warning when creating conflicting rules
This commit is contained in:
@@ -1,6 +1,16 @@
|
||||
-- =============================================================================
|
||||
-- VALIDATION FUNCTIONS
|
||||
-- =============================================================================
|
||||
--
|
||||
-- Rule conflict detection (from Multi-OrBAC paper):
|
||||
-- Conflicts exist when two rules share the same (org, role, activity, view, context)
|
||||
-- but their modalities create a situation where one permanently overrides the other:
|
||||
-- - prohibition + permission -> permission is dead (always overridden)
|
||||
-- - prohibition + obligation -> obligation is always voided
|
||||
-- - prohibition + recommendation -> recommendation is always voided
|
||||
-- - obligation + recommendation -> recommendation is always voided
|
||||
--
|
||||
-- Note: hierarchy-based overlaps are intentional design, not flagged here.
|
||||
|
||||
-- Check if role assignment would violate Separation of Duty
|
||||
CREATE OR REPLACE FUNCTION morbac.check_sod_violation(
|
||||
@@ -80,3 +90,85 @@ $$;
|
||||
|
||||
COMMENT ON FUNCTION morbac.check_cardinality_violation(UUID, BOOLEAN) IS
|
||||
'Returns error message if cardinality constraint would be violated, NULL otherwise';
|
||||
|
||||
-- Detect direct modality conflicts for a candidate rule
|
||||
CREATE OR REPLACE FUNCTION morbac.detect_rule_conflicts(
|
||||
p_org_id UUID,
|
||||
p_role_id UUID,
|
||||
p_activity TEXT,
|
||||
p_view TEXT,
|
||||
p_context_id UUID,
|
||||
p_modality morbac.modality,
|
||||
p_exclude_id UUID DEFAULT NULL -- exclude self on UPDATE
|
||||
)
|
||||
RETURNS TABLE(
|
||||
conflicting_rule_id UUID,
|
||||
conflicting_modality morbac.modality,
|
||||
description TEXT
|
||||
)
|
||||
LANGUAGE plpgsql
|
||||
STABLE
|
||||
AS $$
|
||||
BEGIN
|
||||
RETURN QUERY
|
||||
SELECT
|
||||
r.id,
|
||||
r.modality,
|
||||
CASE
|
||||
WHEN p_modality = 'prohibition'
|
||||
THEN 'prohibition voids existing ' || r.modality::text
|
||||
WHEN r.modality = 'prohibition'
|
||||
THEN p_modality::text || ' will always be overridden by existing prohibition'
|
||||
WHEN p_modality = 'obligation' AND r.modality = 'recommendation'
|
||||
THEN 'obligation voids existing recommendation'
|
||||
ELSE
|
||||
'recommendation will always be overridden by existing obligation'
|
||||
END
|
||||
FROM morbac.rules r
|
||||
WHERE r.org_id = p_org_id
|
||||
AND r.role_id = p_role_id
|
||||
AND r.activity = p_activity
|
||||
AND r.view = p_view
|
||||
AND r.context_id = p_context_id
|
||||
AND r.modality != p_modality
|
||||
AND (p_exclude_id IS NULL OR r.id != p_exclude_id)
|
||||
AND (
|
||||
(p_modality = 'prohibition' AND r.modality IN ('permission', 'obligation', 'recommendation'))
|
||||
OR (r.modality = 'prohibition' AND p_modality IN ('permission', 'obligation', 'recommendation'))
|
||||
OR (p_modality = 'obligation' AND r.modality = 'recommendation')
|
||||
OR (p_modality = 'recommendation' AND r.modality = 'obligation')
|
||||
);
|
||||
END;
|
||||
$$;
|
||||
|
||||
COMMENT ON FUNCTION morbac.detect_rule_conflicts(UUID, UUID, TEXT, TEXT, UUID, morbac.modality, UUID) IS
|
||||
'Returns rules that directly conflict with the given tuple due to modality precedence (prohibition > obligation > recommendation > permission).';
|
||||
|
||||
-- Trigger: warn (non-blocking) when a new/updated rule conflicts with an existing one
|
||||
CREATE OR REPLACE FUNCTION morbac.trg_warn_rule_conflicts()
|
||||
RETURNS TRIGGER
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
v_conflict RECORD;
|
||||
BEGIN
|
||||
FOR v_conflict IN
|
||||
SELECT * FROM morbac.detect_rule_conflicts(
|
||||
NEW.org_id, NEW.role_id, NEW.activity, NEW.view, NEW.context_id, NEW.modality,
|
||||
CASE WHEN TG_OP = 'UPDATE' THEN NEW.id ELSE NULL END
|
||||
)
|
||||
LOOP
|
||||
RAISE WARNING 'Rule conflict: % (conflicts with rule %)',
|
||||
v_conflict.description, v_conflict.conflicting_rule_id;
|
||||
END LOOP;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$;
|
||||
|
||||
DROP TRIGGER IF EXISTS trg_rules_conflict_check ON morbac.rules;
|
||||
CREATE TRIGGER trg_rules_conflict_check
|
||||
BEFORE INSERT OR UPDATE ON morbac.rules
|
||||
FOR EACH ROW EXECUTE FUNCTION morbac.trg_warn_rule_conflicts();
|
||||
|
||||
COMMENT ON FUNCTION morbac.trg_warn_rule_conflicts() IS
|
||||
'Trigger function: emits a WARNING (non-blocking) when a rule conflicts with an existing rule due to modality precedence.';
|
||||
|
||||
Reference in New Issue
Block a user