Files
pgmorbac/src/obligations.sql
T

179 lines
5.5 KiB
PL/PgSQL

-- =============================================================================
-- OBLIGATIONS AND RECOMMENDATIONS
-- =============================================================================
-- Obligations and recommendations do NOT affect authorization
-- They are queryable for informational purposes
--
-- Conflict resolution (from Multi-OrBAC paper):
-- - A prohibition voids any applicable obligation for the same (activity, view)
-- - A prohibition or obligation voids any applicable recommendation for the same (activity, view)
-- - Both functions use comprehensive roles (direct, delegated, derived, inherited)
-- Pending obligations for a user in an organization
-- Returns only obligations not voided by an applicable prohibition
CREATE OR REPLACE FUNCTION morbac.pending_obligations(
p_user_id UUID,
p_org_id UUID
)
RETURNS TABLE(
rule_id UUID,
role_name TEXT,
activity TEXT,
view TEXT,
context_name TEXT,
created_at TIMESTAMPTZ
)
LANGUAGE plpgsql
STABLE
AS $$
BEGIN
RETURN QUERY
SELECT
r.id,
ro.name,
r.activity,
r.view,
c.name,
r.created_at
FROM morbac.rules r
INNER JOIN morbac.roles ro ON ro.id = r.role_id
INNER JOIN morbac.contexts c ON c.id = r.context_id
WHERE r.org_id = p_org_id
AND r.modality = 'obligation'
AND r.role_id IN (
SELECT role_id FROM morbac.get_comprehensive_roles(p_user_id, p_org_id)
)
AND morbac.is_rule_valid(r.valid_from, r.valid_until)
AND morbac.eval_context(r.context_id)
-- Prohibition voids obligation: exclude if an applicable prohibition exists
AND NOT EXISTS (
SELECT 1
FROM morbac.rules p
WHERE p.org_id = p_org_id
AND p.modality = 'prohibition'
AND p.activity IN (SELECT activity FROM morbac.get_effective_activities(r.activity))
AND p.view IN (SELECT view FROM morbac.get_effective_views(r.view))
AND p.role_id IN (
SELECT role_id FROM morbac.get_comprehensive_roles(p_user_id, p_org_id)
)
AND morbac.is_rule_valid(p.valid_from, p.valid_until)
AND morbac.eval_context(p.context_id)
)
ORDER BY r.created_at;
END;
$$;
COMMENT ON FUNCTION morbac.pending_obligations(UUID, UUID) IS
'Returns pending obligations for a user in an organization (informational only). Prohibitions void applicable obligations per Multi-OrBAC conflict resolution.';
-- Recommendations for a user in an organization
-- Returns only recommendations not voided by an applicable prohibition or obligation
CREATE OR REPLACE FUNCTION morbac.pending_recommendations(
p_user_id UUID,
p_org_id UUID
)
RETURNS TABLE(
rule_id UUID,
role_name TEXT,
activity TEXT,
view TEXT,
context_name TEXT,
created_at TIMESTAMPTZ
)
LANGUAGE plpgsql
STABLE
AS $$
BEGIN
RETURN QUERY
SELECT
r.id,
ro.name,
r.activity,
r.view,
c.name,
r.created_at
FROM morbac.rules r
INNER JOIN morbac.roles ro ON ro.id = r.role_id
INNER JOIN morbac.contexts c ON c.id = r.context_id
WHERE r.org_id = p_org_id
AND r.modality = 'recommendation'
AND r.role_id IN (
SELECT role_id FROM morbac.get_comprehensive_roles(p_user_id, p_org_id)
)
AND morbac.is_rule_valid(r.valid_from, r.valid_until)
AND morbac.eval_context(r.context_id)
-- Prohibition or obligation voids recommendation
AND NOT EXISTS (
SELECT 1
FROM morbac.rules p
WHERE p.org_id = p_org_id
AND p.modality IN ('prohibition', 'obligation')
AND p.activity IN (SELECT activity FROM morbac.get_effective_activities(r.activity))
AND p.view IN (SELECT view FROM morbac.get_effective_views(r.view))
AND p.role_id IN (
SELECT role_id FROM morbac.get_comprehensive_roles(p_user_id, p_org_id)
)
AND morbac.is_rule_valid(p.valid_from, p.valid_until)
AND morbac.eval_context(p.context_id)
)
ORDER BY r.created_at;
END;
$$;
COMMENT ON FUNCTION morbac.pending_recommendations(UUID, UUID) IS
'Returns recommendations for a user in an organization (informational only). Prohibitions and obligations void applicable recommendations per Multi-OrBAC conflict resolution.';
-- Get all roles for a user in an organization
CREATE OR REPLACE FUNCTION morbac.user_roles_in_org(
p_user_id UUID,
p_org_id UUID
)
RETURNS TABLE(
role_id UUID,
role_name TEXT
)
LANGUAGE plpgsql
STABLE
AS $$
BEGIN
RETURN QUERY
SELECT r.id, r.name
FROM morbac.roles r
INNER JOIN morbac.user_roles ur ON ur.role_id = r.id
WHERE ur.user_id = p_user_id
AND ur.org_id = p_org_id
AND r.org_id = p_org_id;
END;
$$;
COMMENT ON FUNCTION morbac.user_roles_in_org(UUID, UUID) IS
'Returns all roles for a user in an organization';
-- Check if user has specific role in organization
CREATE OR REPLACE FUNCTION morbac.user_has_role(
p_user_id UUID,
p_org_id UUID,
p_role_name TEXT
)
RETURNS BOOLEAN
LANGUAGE plpgsql
STABLE
AS $$
DECLARE
v_count INTEGER;
BEGIN
SELECT COUNT(*) INTO v_count
FROM morbac.roles r
INNER JOIN morbac.user_roles ur ON ur.role_id = r.id
WHERE ur.user_id = p_user_id
AND ur.org_id = p_org_id
AND r.org_id = p_org_id
AND r.name = p_role_name;
RETURN v_count > 0;
END;
$$;
COMMENT ON FUNCTION morbac.user_has_role(UUID, UUID, TEXT) IS
'Returns true if user has specific role in organization';