Files
microdao-daarion/services/pdp-service/main.py
Apple 3de3c8cb36 feat: Add presence heartbeat for Matrix online status
- matrix-gateway: POST /internal/matrix/presence/online endpoint
- usePresenceHeartbeat hook with activity tracking
- Auto away after 5 min inactivity
- Offline on page close/visibility change
- Integrated in MatrixChatRoom component
2025-11-27 00:19:40 -08:00

161 lines
4.7 KiB
Python

"""
DAARION PDP Service (Policy Decision Point)
Port: 7012
Centralized access control decisions
"""
import os
import asyncpg
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from contextlib import asynccontextmanager
from models import PolicyRequest, PolicyDecision
from engine import evaluate
from policy_store import PolicyStore
# ============================================================================
# Configuration
# ============================================================================
DATABASE_URL = os.getenv("DATABASE_URL", "postgresql://postgres:postgres@localhost:5432/daarion")
# ============================================================================
# Global State
# ============================================================================
policy_store: PolicyStore | None = None
db_pool: asyncpg.Pool | None = None
# ============================================================================
# App Setup
# ============================================================================
@asynccontextmanager
async def lifespan(app: FastAPI):
"""Startup and shutdown"""
global policy_store, db_pool
print("🚀 Starting PDP Service...")
# Load policy store
policy_store = PolicyStore()
print("✅ Policy store loaded")
# Database connection (for audit logging)
db_pool = await asyncpg.create_pool(DATABASE_URL, min_size=2, max_size=10)
print("✅ Database pool created")
print("✅ PDP Service ready")
yield
# Shutdown
print("🛑 Shutting down PDP Service...")
if db_pool:
await db_pool.close()
app = FastAPI(
title="DAARION PDP Service",
version="1.0.0",
description="Policy Decision Point for access control",
lifespan=lifespan
)
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# ============================================================================
# API Endpoints
# ============================================================================
@app.post("/internal/pdp/evaluate", response_model=PolicyDecision)
async def evaluate_policy(request: PolicyRequest):
"""
Evaluate access control policy
Returns permit/deny decision with reason
"""
# Evaluate policy
decision = evaluate(request, policy_store)
# Log to audit (async, best-effort)
if db_pool:
try:
await log_audit(request, decision)
except Exception as e:
print(f"⚠️ Audit log failed: {e}")
# Don't fail the request if audit fails
print(f"📋 PDP: {request.actor.actor_id}{request.action} on {request.resource.type}:{request.resource.id} = {decision.effect} ({decision.reason})")
return decision
async def log_audit(request: PolicyRequest, decision: PolicyDecision):
"""Write audit log entry"""
import json
async with db_pool.acquire() as conn:
# Check if table exists
table_exists = await conn.fetchval("""
SELECT EXISTS (
SELECT FROM information_schema.tables
WHERE table_name = 'security_audit'
)
""")
if not table_exists:
print("⚠️ security_audit table not found, skipping audit log")
return
await conn.execute("""
INSERT INTO security_audit
(actor_id, actor_type, action, resource_type, resource_id, decision, reason, context)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
""",
request.actor.actor_id,
request.actor.actor_type.value,
request.action.value,
request.resource.type.value,
request.resource.id,
decision.effect,
decision.reason,
json.dumps(request.context or {})
)
@app.get("/internal/pdp/policies")
async def list_policies():
"""List loaded policies (for debugging)"""
return {
"microdao_policies": len(policy_store.microdao_policies),
"channel_policies": len(policy_store.channel_policies),
"tool_policies": len(policy_store.tool_policies),
"agent_policies": len(policy_store.agent_policies)
}
@app.get("/health")
async def health():
"""Health check"""
return {
"status": "ok",
"service": "pdp-service",
"policies_loaded": policy_store is not None
}
# ============================================================================
# Run
# ============================================================================
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=7012)