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
This commit is contained in:
160
services/pdp-service/main.py
Normal file
160
services/pdp-service/main.py
Normal file
@@ -0,0 +1,160 @@
|
||||
"""
|
||||
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)
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user