refacto: add build and cleanup file structure
This commit is contained in:
@@ -0,0 +1,191 @@
|
||||
-- =============================================================================
|
||||
-- AUTHORIZATION DECISION FUNCTION (CRITICAL)
|
||||
-- =============================================================================
|
||||
-- Implements the canonical OrBAC authorization decision
|
||||
--
|
||||
-- Semantics (from Multi-OrBAC paper):
|
||||
-- - Access is allowed if and only if:
|
||||
-- 1. At least one applicable permission exists
|
||||
-- 2. AND no applicable prohibition exists
|
||||
-- - Prohibitions have precedence over permissions
|
||||
-- - Contexts must be evaluated
|
||||
-- - Default deny (no permission = deny)
|
||||
-- - Obligations and recommendations do NOT affect authorization
|
||||
|
||||
CREATE OR REPLACE FUNCTION morbac.is_allowed_nocache(
|
||||
p_user_id UUID,
|
||||
p_org_id UUID,
|
||||
p_activity TEXT,
|
||||
p_view TEXT
|
||||
)
|
||||
RETURNS BOOLEAN
|
||||
LANGUAGE plpgsql
|
||||
STABLE
|
||||
AS $$
|
||||
DECLARE
|
||||
v_rule RECORD;
|
||||
BEGIN
|
||||
-- STEP 1: Check for prohibitions first (prohibition precedence)
|
||||
-- If any applicable prohibition exists, deny immediately
|
||||
-- Checks:
|
||||
-- - Direct rules for (activity, view) combinations
|
||||
-- - Activity hierarchy (senior activities imply junior)
|
||||
-- - View hierarchy (senior views imply junior)
|
||||
-- - Comprehensive roles (direct, delegated, derived, inherited)
|
||||
-- - Temporal validity (if rule has time constraints)
|
||||
|
||||
FOR v_rule IN
|
||||
SELECT r.context_id
|
||||
FROM morbac.rules r
|
||||
WHERE r.org_id = p_org_id
|
||||
AND r.modality = 'prohibition'
|
||||
-- Match activity or any senior activity in hierarchy
|
||||
AND r.activity IN (
|
||||
SELECT activity FROM morbac.get_effective_activities(p_activity)
|
||||
)
|
||||
-- Match view or any senior view in hierarchy
|
||||
AND r.view IN (
|
||||
SELECT view FROM morbac.get_effective_views(p_view)
|
||||
)
|
||||
-- Match comprehensive roles (direct, delegated, derived, inherited)
|
||||
AND r.role_id IN (
|
||||
SELECT role_id FROM morbac.get_comprehensive_roles(p_user_id, p_org_id)
|
||||
)
|
||||
-- Check temporal validity
|
||||
AND morbac.is_rule_valid(r.valid_from, r.valid_until)
|
||||
LOOP
|
||||
-- Evaluate context
|
||||
IF morbac.eval_context(v_rule.context_id) THEN
|
||||
-- Prohibition found - immediate deny (prohibition precedence)
|
||||
RETURN FALSE;
|
||||
END IF;
|
||||
END LOOP;
|
||||
|
||||
-- STEP 2: Check cross-organizational prohibitions
|
||||
FOR v_rule IN
|
||||
SELECT cr.context_id
|
||||
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))
|
||||
-- User has role in source org
|
||||
AND cr.role_id IN (
|
||||
SELECT role_id FROM morbac.get_comprehensive_roles(p_user_id, cr.source_org_id)
|
||||
)
|
||||
-- Check temporal validity
|
||||
AND morbac.is_rule_valid(cr.valid_from, cr.valid_until)
|
||||
LOOP
|
||||
IF morbac.eval_context(v_rule.context_id) THEN
|
||||
RETURN FALSE;
|
||||
END IF;
|
||||
END LOOP;
|
||||
|
||||
-- STEP 3: No prohibitions found, check for permissions
|
||||
FOR v_rule IN
|
||||
SELECT r.context_id
|
||||
FROM morbac.rules r
|
||||
WHERE r.org_id = p_org_id
|
||||
AND r.modality = 'permission'
|
||||
-- Match activity or any senior activity in hierarchy
|
||||
AND r.activity IN (
|
||||
SELECT activity FROM morbac.get_effective_activities(p_activity)
|
||||
)
|
||||
-- Match view or any senior view in hierarchy
|
||||
AND r.view IN (
|
||||
SELECT view FROM morbac.get_effective_views(p_view)
|
||||
)
|
||||
-- Match comprehensive roles
|
||||
AND r.role_id IN (
|
||||
SELECT role_id FROM morbac.get_comprehensive_roles(p_user_id, p_org_id)
|
||||
)
|
||||
-- Check temporal validity
|
||||
AND morbac.is_rule_valid(r.valid_from, r.valid_until)
|
||||
LOOP
|
||||
-- Evaluate context
|
||||
IF morbac.eval_context(v_rule.context_id) THEN
|
||||
-- Permission found and no prohibition - allow
|
||||
RETURN TRUE;
|
||||
END IF;
|
||||
END LOOP;
|
||||
|
||||
-- STEP 4: Check cross-organizational permissions
|
||||
FOR v_rule IN
|
||||
SELECT cr.context_id
|
||||
FROM morbac.cross_org_rules cr
|
||||
WHERE cr.target_org_id = p_org_id
|
||||
AND cr.modality = 'permission'
|
||||
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)
|
||||
)
|
||||
-- Check temporal validity
|
||||
AND morbac.is_rule_valid(cr.valid_from, cr.valid_until)
|
||||
LOOP
|
||||
IF morbac.eval_context(v_rule.context_id) THEN
|
||||
RETURN TRUE;
|
||||
END IF;
|
||||
END LOOP;
|
||||
|
||||
-- No permission found - deny (default deny)
|
||||
RETURN FALSE;
|
||||
END;
|
||||
$$;
|
||||
|
||||
COMMENT ON FUNCTION morbac.is_allowed_nocache(UUID, UUID, TEXT, TEXT) IS
|
||||
'Authorization without cache - use for debugging or when cache must be bypassed';
|
||||
|
||||
-- Cached authorization check (default, recommended for production use)
|
||||
CREATE OR REPLACE FUNCTION morbac.is_allowed(
|
||||
p_user_id UUID,
|
||||
p_org_id UUID,
|
||||
p_activity TEXT,
|
||||
p_view TEXT
|
||||
)
|
||||
RETURNS BOOLEAN
|
||||
LANGUAGE plpgsql
|
||||
STABLE
|
||||
AS $$
|
||||
DECLARE
|
||||
v_cached_result BOOLEAN;
|
||||
v_computed_result BOOLEAN;
|
||||
v_expires_at TIMESTAMPTZ;
|
||||
BEGIN
|
||||
-- Try cache first
|
||||
SELECT allowed, expires_at INTO v_cached_result, v_expires_at
|
||||
FROM morbac.auth_cache
|
||||
WHERE user_id = p_user_id
|
||||
AND org_id = p_org_id
|
||||
AND activity = p_activity
|
||||
AND view = p_view
|
||||
AND expires_at > CURRENT_TIMESTAMP;
|
||||
|
||||
IF FOUND THEN
|
||||
RETURN v_cached_result;
|
||||
END IF;
|
||||
|
||||
-- Cache miss - compute authorization
|
||||
v_computed_result := morbac.is_allowed_nocache(p_user_id, p_org_id, p_activity, p_view);
|
||||
|
||||
-- Store in cache (TTL from config)
|
||||
INSERT INTO morbac.auth_cache (user_id, org_id, activity, view, allowed, expires_at)
|
||||
VALUES (
|
||||
p_user_id,
|
||||
p_org_id,
|
||||
p_activity,
|
||||
p_view,
|
||||
v_computed_result,
|
||||
CURRENT_TIMESTAMP + make_interval(secs => morbac.get_config('cache_ttl_seconds')::integer)
|
||||
)
|
||||
ON CONFLICT (user_id, org_id, activity, view) DO UPDATE
|
||||
SET allowed = v_computed_result,
|
||||
computed_at = CURRENT_TIMESTAMP,
|
||||
expires_at = CURRENT_TIMESTAMP + make_interval(secs => morbac.get_config('cache_ttl_seconds')::integer);
|
||||
|
||||
RETURN v_computed_result;
|
||||
END;
|
||||
$$;
|
||||
|
||||
COMMENT ON FUNCTION morbac.is_allowed(UUID, UUID, TEXT, TEXT) IS
|
||||
'Complete OrBAC authorization with caching (default) - use is_allowed_nocache() for debugging';
|
||||
Reference in New Issue
Block a user