Authorization Guide¶
Comprehensive authorization system for controlling access to resources and operations in Anya Enterprise.
Overview¶
The authorization system provides fine-grained access control based on user roles, permissions, and contextual factors. It integrates with the authentication system to ensure users can only access resources they're authorized to use.
Core Concepts¶
Role-Based Access Control (RBAC)¶
interface Role {
id: string;
name: string;
description: string;
permissions: Permission[];
inherits?: string[]; // Role inheritance
}
interface Permission {
id: string;
resource: string;
action: string;
conditions?: PermissionCondition[];
}
interface PermissionCondition {
field: string;
operator: 'eq' | 'ne' | 'in' | 'contains' | 'gt' | 'lt';
value: any;
}
Attribute-Based Access Control (ABAC)¶
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
pub struct AuthorizationRequest {
pub subject: Subject,
pub resource: Resource,
pub action: Action,
pub context: Context,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Subject {
pub user_id: String,
pub roles: Vec<String>,
pub attributes: HashMap<String, Value>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Resource {
pub id: String,
pub type_: String,
pub owner: Option<String>,
pub attributes: HashMap<String, Value>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Context {
pub time: SystemTime,
pub ip_address: IpAddr,
pub location: Option<String>,
pub attributes: HashMap<String, Value>,
}
Authorization Engine¶
Policy Evaluation¶
pub struct AuthorizationEngine {
policies: Vec<Policy>,
rbac_engine: RBACEngine,
abac_engine: ABACEngine,
}
impl AuthorizationEngine {
pub async fn authorize(&self, request: &AuthorizationRequest) -> AuthorizationResult {
// First check RBAC permissions
if let Some(rbac_result) = self.rbac_engine.evaluate(request).await? {
if rbac_result.is_allowed() {
return Ok(AuthorizationResult::Allow);
}
}
// Then evaluate ABAC policies
for policy in &self.policies {
let result = self.abac_engine.evaluate_policy(policy, request).await?;
match result {
PolicyResult::Allow => return Ok(AuthorizationResult::Allow),
PolicyResult::Deny => return Ok(AuthorizationResult::Deny),
PolicyResult::NotApplicable => continue,
}
}
// Default deny
Ok(AuthorizationResult::Deny)
}
}
Policy Language¶
# Example authorization policy
policies:
- name: "wallet_access"
description: "Control access to wallet operations"
rules:
- effect: "allow"
subjects:
- role: "wallet_owner"
resources:
- type: "wallet"
attributes:
owner: "{{ subject.user_id }}"
actions: ["read", "send"]
conditions:
- field: "context.ip_address"
operator: "in"
value: ["trusted_networks"]
- name: "admin_operations"
description: "Administrative operations"
rules:
- effect: "allow"
subjects:
- role: "admin"
resources:
- type: "*"
actions: ["*"]
conditions:
- field: "context.time"
operator: "between"
value: ["09:00", "17:00"] # Business hours only
Implementation Examples¶
API Authorization Middleware¶
import { Request, Response, NextFunction } from 'express';
interface AuthorizedRequest extends Request {
user?: User;
authorization?: AuthorizationResult;
}
export function authorize(resource: string, action: string) {
return async (req: AuthorizedRequest, res: Response, next: NextFunction) => {
if (!req.user) {
return res.status(401).json({ error: 'Authentication required' });
}
const authRequest: AuthorizationRequest = {
subject: {
user_id: req.user.id,
roles: req.user.roles,
attributes: req.user.attributes
},
resource: {
id: req.params.id || resource,
type_: resource,
owner: req.params.owner,
attributes: {}
},
action: { name: action },
context: {
time: new Date(),
ip_address: req.ip,
attributes: {
user_agent: req.get('User-Agent'),
method: req.method
}
}
};
const result = await authorizationEngine.authorize(authRequest);
if (result.is_allowed()) {
req.authorization = result;
next();
} else {
res.status(403).json({
error: 'Insufficient permissions',
required_permissions: result.required_permissions
});
}
};
}
// Usage
app.get('/api/wallets/:id',
authenticate,
authorize('wallet', 'read'),
getWallet
);
Database-Level Authorization¶
-- Row-level security policies
CREATE POLICY wallet_owner_policy ON wallets
FOR ALL TO authenticated_users
USING (owner_id = current_user_id());
CREATE POLICY admin_access_policy ON wallets
FOR ALL TO authenticated_users
USING (has_role('admin'));
-- Function to check permissions
CREATE OR REPLACE FUNCTION check_permission(
user_id UUID,
resource_type TEXT,
resource_id UUID,
action TEXT
) RETURNS BOOLEAN AS $$
BEGIN
-- Check if user has required permissions
RETURN EXISTS (
SELECT 1 FROM user_permissions up
JOIN permissions p ON up.permission_id = p.id
WHERE up.user_id = $1
AND p.resource_type = $2
AND p.action = $4
AND (p.resource_id IS NULL OR p.resource_id = $3)
);
END;
$$ LANGUAGE plpgsql;
Advanced Features¶
Dynamic Permissions¶
use async_trait::async_trait;
#[async_trait]
pub trait DynamicPermissionProvider {
async fn get_permissions(
&self,
user_id: &str,
resource: &Resource,
context: &Context,
) -> Result<Vec<Permission>, AuthorizationError>;
}
pub struct OwnershipPermissionProvider;
#[async_trait]
impl DynamicPermissionProvider for OwnershipPermissionProvider {
async fn get_permissions(
&self,
user_id: &str,
resource: &Resource,
context: &Context,
) -> Result<Vec<Permission>, AuthorizationError> {
let mut permissions = Vec::new();
// Grant full permissions to resource owner
if resource.owner.as_ref() == Some(&user_id.to_string()) {
permissions.push(Permission {
id: "owner_all".to_string(),
resource: resource.type_.clone(),
action: "*".to_string(),
conditions: None,
});
}
Ok(permissions)
}
}
Hierarchical Resources¶
class HierarchicalAuthorization {
async checkPermission(
userId: string,
resourcePath: string,
action: string
): Promise<boolean> {
// Check permission at current level
if (await this.hasDirectPermission(userId, resourcePath, action)) {
return true;
}
// Check inherited permissions from parent resources
const parentPath = this.getParentPath(resourcePath);
if (parentPath) {
return this.checkPermission(userId, parentPath, action);
}
return false;
}
private getParentPath(path: string): string | null {
const parts = path.split('/');
if (parts.length <= 1) return null;
parts.pop();
return parts.join('/');
}
}
Time-Based Permissions¶
#[derive(Debug, Serialize, Deserialize)]
pub struct TemporalPermission {
pub permission: Permission,
pub valid_from: Option<SystemTime>,
pub valid_until: Option<SystemTime>,
pub schedule: Option<Schedule>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Schedule {
pub days_of_week: Vec<u8>, // 1-7 (Monday-Sunday)
pub hours: Option<(u8, u8)>, // (start_hour, end_hour)
pub timezone: String,
}
impl TemporalPermission {
pub fn is_valid_at(&self, time: SystemTime) -> bool {
if let Some(valid_from) = self.valid_from {
if time < valid_from {
return false;
}
}
if let Some(valid_until) = self.valid_until {
if time > valid_until {
return false;
}
}
if let Some(schedule) = &self.schedule {
return self.matches_schedule(schedule, time);
}
true
}
}
Performance Optimization¶
Permission Caching¶
class PermissionCache {
private cache = new Map<string, CacheEntry>();
private ttl = 5 * 60 * 1000; // 5 minutes
async getPermissions(userId: string, resourceId: string): Promise<Permission[]> {
const key = `${userId}:${resourceId}`;
const cached = this.cache.get(key);
if (cached && cached.expires > Date.now()) {
return cached.permissions;
}
const permissions = await this.fetchPermissions(userId, resourceId);
this.cache.set(key, {
permissions,
expires: Date.now() + this.ttl
});
return permissions;
}
invalidate(userId?: string, resourceId?: string) {
if (userId && resourceId) {
this.cache.delete(`${userId}:${resourceId}`);
} else if (userId) {
// Clear all permissions for user
for (const key of this.cache.keys()) {
if (key.startsWith(`${userId}:`)) {
this.cache.delete(key);
}
}
} else {
// Clear all cache
this.cache.clear();
}
}
}
Bulk Authorization¶
impl AuthorizationEngine {
pub async fn authorize_bulk(
&self,
requests: &[AuthorizationRequest],
) -> Result<Vec<AuthorizationResult>, AuthorizationError> {
// Group requests by user for efficient permission lookup
let mut user_groups: HashMap<String, Vec<&AuthorizationRequest>> = HashMap::new();
for request in requests {
user_groups
.entry(request.subject.user_id.clone())
.or_default()
.push(request);
}
let mut results = Vec::with_capacity(requests.len());
for (user_id, user_requests) in user_groups {
// Fetch all permissions for user once
let permissions = self.get_user_permissions(&user_id).await?;
for request in user_requests {
let result = self.evaluate_with_permissions(request, &permissions).await?;
results.push(result);
}
}
Ok(results)
}
}
Monitoring and Auditing¶
Authorization Events¶
interface AuthorizationEvent {
id: string;
timestamp: Date;
user_id: string;
resource: string;
action: string;
result: 'allow' | 'deny';
reason?: string;
ip_address: string;
user_agent: string;
}
class AuthorizationAuditor {
async logEvent(event: AuthorizationEvent): Promise<void> {
// Store in audit log
await this.auditDb.insert('authorization_events', event);
// Real-time monitoring
if (event.result === 'deny') {
await this.alertOnUnauthorizedAccess(event);
}
// Metrics collection
this.metrics.increment(`authorization.${event.result}`, {
resource: event.resource,
action: event.action
});
}
}
Security Analytics¶
-- Find users with unusual access patterns
SELECT
user_id,
COUNT(*) as access_attempts,
COUNT(CASE WHEN result = 'deny' THEN 1 END) as denied_attempts,
ARRAY_AGG(DISTINCT resource) as accessed_resources
FROM authorization_events
WHERE timestamp > NOW() - INTERVAL '1 hour'
GROUP BY user_id
HAVING COUNT(CASE WHEN result = 'deny' THEN 1 END) > 10;
-- Resource access frequency
SELECT
resource,
action,
COUNT(*) as access_count,
COUNT(DISTINCT user_id) as unique_users
FROM authorization_events
WHERE timestamp > NOW() - INTERVAL '1 day'
GROUP BY resource, action
ORDER BY access_count DESC;
Configuration¶
Authorization Configuration¶
authorization:
engine: "hybrid" # rbac, abac, or hybrid
rbac:
role_hierarchy: true
role_inheritance: true
abac:
policy_language: "yaml"
policy_directory: "/etc/anya/policies"
caching:
enabled: true
ttl: 300 # seconds
max_entries: 10000
audit:
enabled: true
log_all_events: true
alert_on_denials: true
performance:
bulk_evaluation: true
parallel_processing: true
max_concurrent: 100
Testing¶
Authorization Test Framework¶
describe('Authorization System', () => {
it('should allow wallet owner to read their wallet', async () => {
const request = {
subject: { user_id: 'user123', roles: ['user'] },
resource: { id: 'wallet456', type_: 'wallet', owner: 'user123' },
action: { name: 'read' },
context: { time: new Date(), ip_address: '192.168.1.1' }
};
const result = await authEngine.authorize(request);
expect(result.is_allowed()).toBe(true);
});
it('should deny access to other users wallets', async () => {
const request = {
subject: { user_id: 'user123', roles: ['user'] },
resource: { id: 'wallet456', type_: 'wallet', owner: 'user999' },
action: { name: 'read' },
context: { time: new Date(), ip_address: '192.168.1.1' }
};
const result = await authEngine.authorize(request);
expect(result.is_allowed()).toBe(false);
});
});
See Also¶
This documentation is part of the Anya Enterprise Security suite.