Permission Functions Reference¶
This reference provides comprehensive documentation for all permission-related functions in the PostgreSQL Permissions Model. Functions are organized by schema and security level, with detailed examples and usage patterns.
Function Categories by Schema¶
Core Permission Functions (auth schema)¶
These functions include full permission validation and audit logging.
Permission Checking Functions¶
auth.has_permission()
- Single permission check with caching
-- Check if user has specific permission
SELECT auth.has_permission(
_tenant_id := 'tenant-uuid',
_user_id := 'user-uuid',
_perm_code := 'users.create',
_throw_err := true -- Default: true (throws error if denied)
) -> boolean;
-- Silent permission check (no exception)
SELECT auth.has_permission(
'tenant-uuid', 'user-uuid', 'admin.settings', false
) as can_access_settings;
auth.has_permissions()
- Multiple permission check
-- Check multiple permissions at once
SELECT auth.has_permissions(
_tenant_id := 'tenant-uuid',
_user_id := 'user-uuid',
_perm_codes := ARRAY['users.read', 'users.update', 'users.delete'],
_throw_err := true
) -> boolean; -- Returns true if ALL permissions granted
-- Alternative: Check with partial matching
SELECT auth.has_any_permissions(
'tenant-uuid', 'user-uuid',
ARRAY['admin.users', 'admin.groups', 'admin.system']
) -> boolean; -- Returns true if ANY permission granted
auth.has_permission_level()
- Level-based permission check
-- Check if user has permissions at specific level or higher
SELECT auth.has_permission_level(
_tenant_id := 'tenant-uuid',
_user_id := 'user-uuid',
_required_level := 'admin'::auth.permission_level
) -> boolean;
-- Usage in conditional logic
IF auth.has_permission_level('tenant-uuid', 'user-uuid', 'system') THEN
-- User has system-level permissions
PERFORM execute_system_function();
END IF;
Permission Assignment Functions¶
auth.assign_permission()
- Core assignment function
-- Assign individual permission to user
SELECT auth.assign_permission(
_tenant_id := 'tenant-uuid',
_user_id := 'user-uuid',
_user_group_id := NULL,
_perm_set_code := NULL,
_perm_code := 'reports.financial',
_granted := true,
_expires_at := now() + interval '30 days'
) -> uuid; -- Returns assignment_id
-- Assign permission set to group
SELECT auth.assign_permission(
'tenant-uuid', NULL, 'managers-group-uuid', 'MANAGER_PERMISSIONS', NULL
) -> uuid;
-- Explicit denial (overrides group permissions)
SELECT auth.assign_permission(
'tenant-uuid', 'user-uuid', NULL, NULL, 'data.delete', false
) -> uuid;
auth.assign_permissions_bulk()
- Bulk assignment
-- Assign multiple permissions at once
SELECT auth.assign_permissions_bulk(
_tenant_id := 'tenant-uuid',
_user_id := 'user-uuid',
_user_group_id := NULL,
_assignments := '[
{"perm_code": "users.view", "granted": true},
{"perm_code": "reports.generate", "expires_at": "2024-12-31T23:59:59"},
{"perm_set_code": "BASIC_USER", "granted": true}
]'::jsonb
) -> integer; -- Returns count of assignments created
auth.revoke_permission()
- Permission revocation
-- Revoke specific permission
SELECT auth.revoke_permission(
_tenant_id := 'tenant-uuid',
_user_id := 'user-uuid',
_user_group_id := NULL,
_perm_code := 'admin.users.delete',
_reason := 'Security policy change'
) -> boolean;
-- Revoke all permissions for user
SELECT auth.revoke_all_user_permissions(
_tenant_id := 'tenant-uuid',
_user_id := 'user-uuid',
_reason := 'User deactivation'
) -> integer; -- Returns count of revoked permissions
Permission Set Management¶
auth.create_permission_set()
- Create new permission set
-- Create tenant-specific permission set
SELECT auth.create_permission_set(
_tenant_id := 'tenant-uuid',
_perm_set_code := 'CUSTOM_ROLE',
_name := 'Custom Role Permissions',
_description := 'Permissions for custom business role',
_is_system_set := false
) -> boolean;
auth.add_permission_to_set()
- Add permission to set
-- Add individual permission to set
SELECT auth.add_permission_to_set(
_tenant_id := 'tenant-uuid',
_perm_set_code := 'CUSTOM_ROLE',
_perm_code := 'reports.financial',
_granted := true
) -> boolean;
-- Add another permission set to this set (inheritance)
SELECT auth.add_permission_to_set(
'tenant-uuid', 'MANAGER_ROLE', 'BASIC_USER', true
) -> boolean;
auth.remove_permission_from_set()
- Remove permission from set
-- Remove specific permission
SELECT auth.remove_permission_from_set(
_tenant_id := 'tenant-uuid',
_perm_set_code := 'CUSTOM_ROLE',
_perm_code := 'admin.users.delete'
) -> boolean;
Permission Analysis Functions¶
auth.get_user_permissions()
- Get all user permissions
-- Get comprehensive permission list for user
SELECT * FROM auth.get_user_permissions(
_tenant_id := 'tenant-uuid',
_user_id := 'user-uuid',
_include_inherited := true,
_include_denied := false
) -> TABLE (
perm_code text,
permission_name text,
source_type text,
source_name text,
granted boolean,
assigned_at timestamptz,
expires_at timestamptz
);
auth.get_effective_permissions()
- Get resolved permissions
-- Get final effective permissions after resolution
SELECT * FROM auth.get_effective_permissions(
_tenant_id := 'tenant-uuid',
_user_id := 'user-uuid',
_permission_filter := 'admin%' -- Optional filter pattern
) -> TABLE (
perm_code text,
has_permission boolean,
source_priority integer,
resolution_reason text
);
auth.analyze_permission_conflicts()
- Detect conflicts
-- Find permission assignment conflicts
SELECT * FROM auth.analyze_permission_conflicts(
_tenant_id := 'tenant-uuid',
_user_id := 'user-uuid' -- NULL for all users
) -> TABLE (
conflict_type text,
perm_code text,
conflicting_sources text[],
recommendation text
);
Hierarchical Permission Functions¶
auth.get_permission_ancestors()
- Find parent permissions
-- Get all permissions that grant this permission
SELECT * FROM auth.get_permission_ancestors('admin.users.create')
-> TABLE (perm_code text, name text, level integer);
-- Example result:
-- perm_code | name | level
-- admin.users | User Administration | 1
-- admin | Administration | 2
auth.get_permission_descendants()
- Find child permissions
-- Get all permissions granted by this permission
SELECT * FROM auth.get_permission_descendants('admin.users')
-> TABLE (perm_code text, name text, level integer);
-- Example result:
-- perm_code | name | level
-- admin.users.create | Create Users | 1
-- admin.users.update | Update Users | 1
-- admin.users.delete | Delete Users | 1
auth.has_hierarchical_permission()
- Hierarchical check
-- Check permission with hierarchy consideration
SELECT auth.has_hierarchical_permission(
_tenant_id := 'tenant-uuid',
_user_id := 'user-uuid',
_perm_code := 'admin.users.create',
_check_ancestors := true -- Check if user has 'admin' or 'admin.users'
) -> boolean;
Helper Functions (helpers schema)¶
Utility functions without permission checks or sensitive data access.
helpers.validate_permission_code()
- Validate permission format
-- Validate permission code format
SELECT helpers.validate_permission_code('admin.users.create') -> boolean; -- true
SELECT helpers.validate_permission_code('invalid..code') -> boolean; -- false
helpers.generate_permission_tree()
- Build permission hierarchy tree
-- Generate hierarchical permission structure
SELECT helpers.generate_permission_tree(
_root_permission := 'admin',
_max_depth := 3
) -> jsonb; -- Returns nested JSON tree structure
helpers.permission_code_depth()
- Get permission hierarchy depth
-- Get depth of permission in hierarchy
SELECT helpers.permission_code_depth('admin.users.create.bulk') -> integer; -- 4
Internal Functions (internal schema)¶
Business logic functions without permission checks - use with caution.
internal.resolve_user_permissions_raw()
- Raw permission resolution
-- Get raw permission data without security checks
SELECT * FROM internal.resolve_user_permissions_raw(
_tenant_id := 'tenant-uuid',
_user_id := 'user-uuid'
) -> TABLE (
perm_code text,
source_type text,
source_id uuid,
granted boolean,
priority integer
);
-- WARNING: Use only in trusted application contexts
internal.bulk_assign_permissions_unsafe()
- Bulk assignment without validation
-- High-performance bulk assignment (bypasses validation)
SELECT internal.bulk_assign_permissions_unsafe(
_assignments := '[
{"tenant_id": "...", "user_id": "...", "perm_code": "users.view"},
{"tenant_id": "...", "user_id": "...", "perm_code": "reports.view"}
]'::jsonb
) -> integer;
-- WARNING: Only use in migration or trusted batch operations
Unsecure Functions (unsecure schema)¶
Security and system administration functions - DBA/Admin only.
unsecure.clear_permission_cache()
- Clear user permission cache
-- Clear cached permissions for user (after permission changes)
SELECT unsecure.clear_permission_cache(
_user_id := 'user-uuid',
_reason := 'Permission assignment changed'
) -> integer; -- Returns count of cleared cache entries
unsecure.rebuild_permission_cache()
- Rebuild permission cache
-- Rebuild permission cache for all users
SELECT unsecure.rebuild_permission_cache(
_tenant_id := 'tenant-uuid', -- NULL for all tenants
_force_refresh := true
) -> integer; -- Returns count of cache entries created
unsecure.audit_permission_assignments()
- Security audit
-- Comprehensive permission assignment audit
SELECT * FROM unsecure.audit_permission_assignments(
_tenant_id := 'tenant-uuid',
_include_system_permissions := false,
_days_back := 90
) -> TABLE (
audit_type text,
user_id uuid,
username text,
perm_code text,
issue_description text,
risk_level text,
recommendation text
);
unsecure.emergency_revoke_permissions()
- Emergency security action
-- Emergency revocation of permissions (security incident)
SELECT unsecure.emergency_revoke_permissions(
_permission_pattern := 'admin.system%',
_reason := 'Security incident response',
_except_users := ARRAY['emergency-admin-uuid']
) -> integer; -- Returns count of revoked permissions
Common Usage Patterns¶
Application Permission Checks¶
-- Standard application permission check pattern
CREATE OR REPLACE FUNCTION app.secure_business_function(
_tenant_id uuid,
_user_id uuid,
_operation_data jsonb
) RETURNS jsonb AS $$
DECLARE
v_result jsonb;
BEGIN
-- 1. Check basic permission
PERFORM auth.has_permission(_tenant_id, _user_id, 'business.function.execute');
-- 2. Check additional permissions based on data
IF _operation_data ? 'financial_data' THEN
PERFORM auth.has_permission(_tenant_id, _user_id, 'finance.data.access');
END IF;
-- 3. Execute business logic
v_result := jsonb_build_object(
'status', 'success',
'data', 'business function result'
);
-- 4. Log the operation
PERFORM auth.create_auth_event(
_tenant_id, _user_id, '50100', 'Business function executed',
jsonb_build_object('operation', 'secure_business_function')
);
RETURN v_result;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
Conditional Permission Assignment¶
-- Smart permission assignment based on user attributes
CREATE OR REPLACE FUNCTION auth.assign_role_based_permissions(
_tenant_id uuid,
_user_id uuid,
_role_data jsonb
) RETURNS integer AS $$
DECLARE
v_assignments jsonb := '[]'::jsonb;
v_count integer;
BEGIN
-- Build role-based assignments
CASE _role_data->>'department'
WHEN 'Engineering' THEN
v_assignments := v_assignments || '[
{"perm_set_code": "DEVELOPER_PERMISSIONS"},
{"perm_code": "api.development"},
{"perm_code": "data.read"}
]'::jsonb;
WHEN 'Finance' THEN
v_assignments := v_assignments || '[
{"perm_set_code": "FINANCE_PERMISSIONS"},
{"perm_code": "reports.financial"},
{"perm_code": "budgets.manage"}
]'::jsonb;
END CASE;
-- Add level-based permissions
IF (_role_data->>'level')::integer >= 8 THEN
v_assignments := v_assignments || '[
{"perm_code": "admin.users.view"},
{"perm_code": "reports.confidential"}
]'::jsonb;
END IF;
-- Apply assignments
SELECT auth.assign_permissions_bulk(_tenant_id, _user_id, NULL, v_assignments)
INTO v_count;
RETURN v_count;
END;
$$ LANGUAGE plpgsql;
Permission Validation Middleware¶
-- Middleware pattern for API permission validation
CREATE OR REPLACE FUNCTION auth.validate_api_permission(
_tenant_id uuid,
_user_id uuid,
_http_method text,
_endpoint_path text
) RETURNS boolean AS $$
DECLARE
v_required_permission text;
v_has_permission boolean := false;
BEGIN
-- Map HTTP method and path to permission
v_required_permission := CASE
WHEN _endpoint_path ~ '^/api/v1/users' AND _http_method = 'GET' THEN 'api.users.read'
WHEN _endpoint_path ~ '^/api/v1/users' AND _http_method = 'POST' THEN 'api.users.create'
WHEN _endpoint_path ~ '^/api/v1/users' AND _http_method = 'PUT' THEN 'api.users.update'
WHEN _endpoint_path ~ '^/api/v1/users' AND _http_method = 'DELETE' THEN 'api.users.delete'
WHEN _endpoint_path ~ '^/api/v1/reports' THEN 'api.reports.access'
WHEN _endpoint_path ~ '^/api/v1/admin' THEN 'api.admin.access'
ELSE 'api.basic.access'
END;
-- Check permission
v_has_permission := auth.has_permission(_tenant_id, _user_id, v_required_permission, false);
-- Log API access attempt
PERFORM auth.create_auth_event(
_tenant_id, _user_id,
CASE WHEN v_has_permission THEN '50200' ELSE '50201' END, -- API_ACCESS_GRANTED/DENIED
'API access: ' || _http_method || ' ' || _endpoint_path,
jsonb_build_object(
'method', _http_method,
'path', _endpoint_path,
'required_permission', v_required_permission,
'granted', v_has_permission
)
);
RETURN v_has_permission;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
Bulk Permission Operations¶
-- Efficient bulk permission checks for UI components
CREATE OR REPLACE FUNCTION auth.check_ui_permissions(
_tenant_id uuid,
_user_id uuid,
_ui_components text[]
) RETURNS TABLE (
component text,
can_view boolean,
can_edit boolean,
can_delete boolean
) AS $$
DECLARE
v_component text;
v_permissions_to_check text[] := ARRAY[]::text[];
v_permission_results jsonb;
BEGIN
-- Build list of all permissions to check
FOREACH v_component IN ARRAY _ui_components
LOOP
v_permissions_to_check := v_permissions_to_check || ARRAY[
'ui.' || v_component || '.view',
'ui.' || v_component || '.edit',
'ui.' || v_component || '.delete'
];
END LOOP;
-- Bulk check all permissions
SELECT jsonb_object_agg(perm_code, has_permission)
INTO v_permission_results
FROM (
SELECT
perm,
auth.has_permission(_tenant_id, _user_id, perm, false) as has_permission
FROM unnest(v_permissions_to_check) AS perm
) AS perm_checks(perm_code, has_permission);
-- Return organized results
FOREACH v_component IN ARRAY _ui_components
LOOP
RETURN QUERY
SELECT
v_component,
COALESCE((v_permission_results->('ui.' || v_component || '.view'))::boolean, false),
COALESCE((v_permission_results->('ui.' || v_component || '.edit'))::boolean, false),
COALESCE((v_permission_results->('ui.' || v_component || '.delete'))::boolean, false);
END LOOP;
END;
$$ LANGUAGE plpgsql;
Error Handling and Debugging¶
Permission Error Functions¶
-- Custom permission error handling
CREATE OR REPLACE FUNCTION auth.throw_permission_denied(
_tenant_id uuid,
_user_id uuid,
_perm_code text,
_context text DEFAULT NULL
) RETURNS void AS $$
DECLARE
v_error_code text := '42501'; -- Insufficient privilege
v_error_message text;
v_user_info record;
BEGIN
-- Get user context for error message
SELECT username, display_name INTO v_user_info
FROM auth.user_info
WHERE user_id = _user_id;
v_error_message := format(
'User %s (%s) denied access to permission "%s" in tenant %s',
COALESCE(v_user_info.display_name, 'Unknown'),
COALESCE(v_user_info.username, _user_id::text),
_perm_code,
_tenant_id
);
IF _context IS NOT NULL THEN
v_error_message := v_error_message || '. Context: ' || _context;
END IF;
-- Log security event
PERFORM auth.create_auth_event(
_tenant_id, _user_id, '52001', -- PERMISSION_DENIED
'Permission denied: ' || _perm_code,
jsonb_build_object(
'perm_code', _perm_code,
'context', _context,
'error_code', v_error_code
)
);
RAISE EXCEPTION USING
ERRCODE = v_error_code,
MESSAGE = v_error_message,
DETAIL = 'Required permission: ' || _perm_code;
END;
$$ LANGUAGE plpgsql;
Debug Functions¶
-- Debug permission resolution
CREATE OR REPLACE FUNCTION auth.debug_permission_resolution(
_tenant_id uuid,
_user_id uuid,
_perm_code text
) RETURNS jsonb AS $$
DECLARE
v_debug_info jsonb := '{}';
v_user_perms jsonb;
v_group_perms jsonb;
v_hierarchy_check jsonb;
v_final_result boolean;
BEGIN
-- Get user direct permissions
SELECT jsonb_agg(
jsonb_build_object(
'perm_code', up.perm_code,
'perm_set_code', up.perm_set_code,
'granted', up.granted,
'expires_at', up.expires_at,
'source', 'direct_assignment'
)
)
INTO v_user_perms
FROM auth.user_permission up
WHERE up.tenant_id = _tenant_id
AND up.user_id = _user_id;
-- Get group permissions
SELECT jsonb_agg(
jsonb_build_object(
'group_name', ug.name,
'perm_code', up.perm_code,
'perm_set_code', up.perm_set_code,
'granted', up.granted,
'source', 'group_assignment'
)
)
INTO v_group_perms
FROM auth.user_group_member ugm
JOIN auth.user_group ug ON ugm.user_group_id = ug.user_group_id
JOIN auth.user_permission up ON ug.user_group_id = up.user_group_id
WHERE ugm.user_id = _user_id
AND ugm.is_active = true
AND ug.tenant_id = _tenant_id
AND up.tenant_id = _tenant_id;
-- Check hierarchy
SELECT jsonb_build_object(
'requested_permission', _perm_code,
'hierarchy_matches', jsonb_agg(
jsonb_build_object(
'parent_perm', p.perm_code,
'covers_requested', (_perm_code::ltree <@ p.perm_path)
)
)
)
INTO v_hierarchy_check
FROM auth.permission p
WHERE _perm_code::ltree <@ p.perm_path
AND p.is_active = true;
-- Get final result
v_final_result := auth.has_permission(_tenant_id, _user_id, _perm_code, false);
RETURN jsonb_build_object(
'user_id', _user_id,
'tenant_id', _tenant_id,
'requested_permission', _perm_code,
'final_result', v_final_result,
'user_direct_permissions', COALESCE(v_user_perms, '[]'),
'group_permissions', COALESCE(v_group_perms, '[]'),
'hierarchy_analysis', COALESCE(v_hierarchy_check, '{}'),
'debug_timestamp', now()
);
END;
$$ LANGUAGE plpgsql;
Function Security Notes¶
Usage Guidelines by Schema¶
auth.*
functions: Always use these for application logichelpers.*
functions: Safe utility functions, no sensitive accessinternal.*
functions: Use only in trusted contexts (migrations, internal tools)unsecure.*
functions: DBA/system administration only
Performance Considerations¶
- Use Caching: Prefer
auth.has_permission_cached()
for repeated checks - Bulk Operations: Use bulk functions for multiple permissions
- Materialized Views: Use pre-computed views for read-heavy workloads
- Index Usage: Ensure proper indexes on permission tables
Security Best Practices¶
- Always Validate Context: Check tenant access before permission checks
- Use SECURITY DEFINER: For functions that need elevated privileges
- Log Security Events: Use
auth.create_auth_event()
for audit trails - Handle Errors Gracefully: Use appropriate error codes and messages
What's Next¶
- Review Permission Model for architectural overview
- Explore Permission Assignment patterns
- Learn about Permission Caching optimization
- See Permission Check Examples for practical usage