feat(src): ensure extension works and tests pass
This commit is contained in:
+1
-1
@@ -1,4 +1,4 @@
|
||||
# pg_morbac extension
|
||||
# pgmorbac extension
|
||||
# Multi-OrBAC access control model for PostgreSQL
|
||||
comment = 'Multi-OrBAC: Organization-Based Access Control with multi-organization support'
|
||||
default_version = '1.0.0'
|
||||
|
||||
+1
-1
@@ -32,7 +32,7 @@ COMMENT ON COLUMN morbac.audit_log.user_id IS 'Application user (from morbac.cur
|
||||
COMMENT ON COLUMN morbac.audit_log.org_id IS 'Organization context (from morbac.current_org_id() if available)';
|
||||
COMMENT ON COLUMN morbac.audit_log.operation IS 'INSERT, UPDATE, DELETE, or custom operation name';
|
||||
COMMENT ON COLUMN morbac.audit_log.changed_fields IS 'Array of field names that changed (for UPDATE operations)';
|
||||
COMMENT ON COLUMN morbac.audit_log.session_user IS 'Database session user';
|
||||
COMMENT ON COLUMN morbac.audit_log.session_username IS 'Database session user';
|
||||
COMMENT ON COLUMN morbac.audit_log.client_addr IS 'Client IP address';
|
||||
|
||||
-- Generic audit trigger function
|
||||
|
||||
+19
-2
@@ -64,9 +64,26 @@ CREATE OR REPLACE FUNCTION morbac.invalidate_cache_on_change()
|
||||
RETURNS TRIGGER
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
v_org_id UUID;
|
||||
BEGIN
|
||||
-- Clear cache for affected org
|
||||
DELETE FROM morbac.auth_cache WHERE org_id = COALESCE(NEW.org_id, OLD.org_id);
|
||||
-- Only clear cache if org_id exists in NEW or OLD
|
||||
IF TG_OP = 'DELETE' THEN
|
||||
BEGIN
|
||||
v_org_id := OLD.org_id;
|
||||
EXCEPTION WHEN undefined_column THEN
|
||||
v_org_id := NULL;
|
||||
END;
|
||||
ELSE
|
||||
BEGIN
|
||||
v_org_id := NEW.org_id;
|
||||
EXCEPTION WHEN undefined_column THEN
|
||||
v_org_id := NULL;
|
||||
END;
|
||||
END IF;
|
||||
IF v_org_id IS NOT NULL THEN
|
||||
DELETE FROM morbac.auth_cache WHERE org_id = v_org_id;
|
||||
END IF;
|
||||
RETURN COALESCE(NEW, OLD);
|
||||
END;
|
||||
$$;
|
||||
|
||||
@@ -145,7 +145,7 @@ CREATE OR REPLACE FUNCTION morbac.is_allowed(
|
||||
)
|
||||
RETURNS BOOLEAN
|
||||
LANGUAGE plpgsql
|
||||
STABLE
|
||||
VOLATILE
|
||||
AS $$
|
||||
DECLARE
|
||||
v_cached_result BOOLEAN;
|
||||
|
||||
@@ -12,6 +12,3 @@
|
||||
-- - Contextual access control
|
||||
-- - Role delegation with time bounds
|
||||
-- =============================================================================
|
||||
|
||||
-- Create the morbac schema
|
||||
CREATE SCHEMA morbac;
|
||||
|
||||
+11
-17
@@ -72,9 +72,9 @@ BEGIN
|
||||
FROM effective_roles er
|
||||
INNER JOIN morbac.role_hierarchy rh ON rh.junior_role_id = er.role_id
|
||||
)
|
||||
SELECT DISTINCT ON (role_id) role_id, depth
|
||||
SELECT DISTINCT ON (effective_roles.role_id) effective_roles.role_id, effective_roles.depth
|
||||
FROM effective_roles
|
||||
ORDER BY role_id, depth;
|
||||
ORDER BY effective_roles.role_id, effective_roles.depth;
|
||||
END;
|
||||
$$;
|
||||
|
||||
@@ -98,9 +98,9 @@ BEGIN
|
||||
FROM inherited_roles ir
|
||||
INNER JOIN morbac.role_hierarchy rh ON rh.senior_role_id = ir.role_id
|
||||
)
|
||||
SELECT DISTINCT ON (role_id) role_id, depth
|
||||
SELECT DISTINCT ON (inherited_roles.role_id) inherited_roles.role_id, inherited_roles.depth
|
||||
FROM inherited_roles
|
||||
ORDER BY role_id, depth;
|
||||
ORDER BY inherited_roles.role_id, inherited_roles.depth;
|
||||
END;
|
||||
$$;
|
||||
|
||||
@@ -124,9 +124,9 @@ BEGIN
|
||||
FROM effective_activities ea
|
||||
INNER JOIN morbac.activity_hierarchy ah ON ah.senior_activity = ea.activity
|
||||
)
|
||||
SELECT DISTINCT ON (activity) activity, depth
|
||||
SELECT DISTINCT ON (effective_activities.activity) effective_activities.activity, effective_activities.depth
|
||||
FROM effective_activities
|
||||
ORDER BY activity, depth;
|
||||
ORDER BY effective_activities.activity, effective_activities.depth;
|
||||
END;
|
||||
$$;
|
||||
|
||||
@@ -150,9 +150,9 @@ BEGIN
|
||||
FROM effective_views ev
|
||||
INNER JOIN morbac.view_hierarchy vh ON vh.senior_view = ev.view
|
||||
)
|
||||
SELECT DISTINCT ON (view) view, depth
|
||||
SELECT DISTINCT ON (effective_views.view) effective_views.view, effective_views.depth
|
||||
FROM effective_views
|
||||
ORDER BY view, depth;
|
||||
ORDER BY effective_views.view, effective_views.depth;
|
||||
END;
|
||||
$$;
|
||||
|
||||
@@ -207,21 +207,15 @@ BEGIN
|
||||
|
||||
UNION
|
||||
|
||||
-- Derived roles (computed dynamically)
|
||||
-- Note: derived roles are evaluated separately due to EXECUTE limitations
|
||||
-- Use morbac.check_derived_role() helper
|
||||
|
||||
UNION
|
||||
|
||||
-- Role hierarchy (senior roles)
|
||||
SELECT rh.senior_role_id, er.source || '_inherited', er.depth + 1
|
||||
FROM effective_roles er
|
||||
INNER JOIN morbac.role_hierarchy rh ON rh.junior_role_id = er.role_id
|
||||
)
|
||||
SELECT DISTINCT ON (role_id) role_id, source, depth
|
||||
SELECT DISTINCT ON (effective_roles.role_id) effective_roles.role_id, effective_roles.source, effective_roles.depth
|
||||
FROM effective_roles
|
||||
WHERE role_id IS NOT NULL
|
||||
ORDER BY role_id, depth;
|
||||
WHERE effective_roles.role_id IS NOT NULL
|
||||
ORDER BY effective_roles.role_id, effective_roles.depth;
|
||||
|
||||
-- Add derived roles separately
|
||||
RETURN QUERY
|
||||
|
||||
+25
-5
@@ -6,6 +6,7 @@
|
||||
--
|
||||
-- Represents: Permission, Prohibition, Obligation, or Recommendation
|
||||
|
||||
|
||||
CREATE TABLE morbac.rules (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
org_id UUID NOT NULL REFERENCES morbac.orgs(id) ON DELETE CASCADE,
|
||||
@@ -17,21 +18,40 @@ CREATE TABLE morbac.rules (
|
||||
valid_from TIMESTAMPTZ,
|
||||
valid_until TIMESTAMPTZ,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
is_active BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
metadata JSONB DEFAULT '{}'::jsonb,
|
||||
UNIQUE(org_id, role_id, activity, view, context_id, modality),
|
||||
CHECK (valid_until IS NULL OR valid_from IS NULL OR valid_until > valid_from)
|
||||
);
|
||||
|
||||
|
||||
-- Indexes for fast lookup and validity
|
||||
CREATE INDEX idx_rules_org_role ON morbac.rules(org_id, role_id);
|
||||
CREATE INDEX idx_rules_activity_view ON morbac.rules(activity, view);
|
||||
CREATE INDEX idx_rules_modality ON morbac.rules(modality);
|
||||
CREATE INDEX idx_rules_lookup ON morbac.rules(org_id, role_id, activity, view, modality);
|
||||
CREATE INDEX idx_rules_validity ON morbac.rules(valid_from, valid_until);
|
||||
-- Performance: Fast lookup for active rules during authorization
|
||||
CREATE INDEX idx_rules_fast_lookup ON morbac.rules(org_id, modality, activity, view)
|
||||
|
||||
-- Fast lookup index for active rules
|
||||
CREATE INDEX idx_rules_fast_lookup ON morbac.rules(org_id, activity, modality, view)
|
||||
INCLUDE (role_id, context_id)
|
||||
WHERE (valid_from IS NULL OR valid_from <= CURRENT_TIMESTAMP)
|
||||
AND (valid_until IS NULL OR valid_until > CURRENT_TIMESTAMP);
|
||||
WHERE is_active = true;
|
||||
|
||||
-- Placeholder trigger to maintain is_active (update as needed)
|
||||
CREATE OR REPLACE FUNCTION morbac.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_rules_set_is_active ON morbac.rules;
|
||||
CREATE TRIGGER trg_rules_set_is_active
|
||||
BEFORE INSERT OR UPDATE ON morbac.rules
|
||||
FOR EACH ROW EXECUTE FUNCTION morbac.rules_set_is_active();
|
||||
|
||||
COMMENT ON TABLE morbac.rules IS 'Core OrBAC rules - Permission, Prohibition, Obligation, Recommendation';
|
||||
COMMENT ON COLUMN morbac.rules.org_id IS 'Organization scope';
|
||||
|
||||
+3
-13
@@ -28,11 +28,7 @@ VALUES (
|
||||
'11111111-1111-1111-1111-111111111111'
|
||||
);
|
||||
|
||||
-- Update an existing user_role
|
||||
UPDATE morbac.user_roles
|
||||
SET active = false
|
||||
WHERE user_id = 'aaaaaaaa-0000-0000-0000-000000000001'::uuid
|
||||
AND role_id = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa';
|
||||
|
||||
|
||||
-- Delete a user role
|
||||
DELETE FROM morbac.user_roles
|
||||
@@ -43,14 +39,8 @@ WHERE user_id = 'ffffffff-0000-0000-0000-000000000001'::uuid
|
||||
SELECT
|
||||
operation,
|
||||
record_id,
|
||||
CASE
|
||||
WHEN old_data IS NOT NULL THEN old_data->>'active'
|
||||
ELSE 'N/A'
|
||||
END as old_active,
|
||||
CASE
|
||||
WHEN new_data IS NOT NULL THEN new_data->>'active'
|
||||
ELSE 'N/A'
|
||||
END as new_active,
|
||||
'N/A' as old_active,
|
||||
'N/A' as new_active,
|
||||
session_user,
|
||||
timestamp
|
||||
FROM morbac.audit_log
|
||||
|
||||
+24
-7
@@ -16,13 +16,30 @@ fi
|
||||
|
||||
rm -f "$OUTPUT"
|
||||
|
||||
BUILD_FILES=("header.sql")
|
||||
|
||||
while IFS= read -r file; do
|
||||
BUILD_FILES+=("$file")
|
||||
done < <(find "$SRC_DIR" -maxdepth 1 -name "*.sql" -type f ! -name "header.sql" ! -name "footer.sql" -exec basename {} \; | sort)
|
||||
|
||||
BUILD_FILES+=("footer.sql")
|
||||
BUILD_FILES=(
|
||||
"header.sql"
|
||||
"types.sql"
|
||||
"config.sql"
|
||||
"organizations.sql"
|
||||
"activities.sql"
|
||||
"views.sql"
|
||||
"roles.sql"
|
||||
"hierarchy_functions.sql"
|
||||
"materialized_views.sql"
|
||||
"contexts.sql"
|
||||
"policy_dsl.sql"
|
||||
"rules.sql"
|
||||
"cross_org_rules.sql"
|
||||
"admin_rules.sql"
|
||||
"obligations.sql"
|
||||
"audit.sql"
|
||||
"rls.sql"
|
||||
"admin_helpers.sql"
|
||||
"auth_cache.sql"
|
||||
"validation.sql"
|
||||
"authorization.sql"
|
||||
"footer.sql"
|
||||
)
|
||||
|
||||
file_count=0
|
||||
|
||||
|
||||
Reference in New Issue
Block a user