Files
microdao-daarion/services/messaging-service/pep_middleware.py
Apple fca48b3eb0 feat(node2): Complete NODE2 setup - guardian, agents, swapper models
- Node-guardian running on MacBook and updating metrics
- NODE2 agents (Atlas, Greeter, Oracle, Builder Bot) assigned to node-2-macbook-m4max
- Swapper models displaying correctly (8 models)
- DAGI Router agents showing with correct status (3 active, 1 stale)
- Router health check using node_cache for remote nodes
2025-12-02 07:07:58 -08:00

182 lines
5.9 KiB
Python

"""
PEP Middleware for messaging-service
Policy Enforcement Point - enforces access control via PDP
"""
import httpx
import os
from fastapi import HTTPException, Header
from typing import Optional
PDP_SERVICE_URL = os.getenv("PDP_SERVICE_URL", "http://pdp-service:7012")
AUTH_SERVICE_URL = os.getenv("AUTH_SERVICE_URL", "http://auth-service:7011")
class PEPClient:
"""Client for Policy Enforcement Point"""
def __init__(self):
self.pdp_url = PDP_SERVICE_URL
self.auth_url = AUTH_SERVICE_URL
async def get_actor_identity(self, authorization: Optional[str] = None, x_api_key: Optional[str] = None):
"""
Get actor identity from auth-service
Returns ActorIdentity or raises HTTPException
"""
if not authorization and not x_api_key:
raise HTTPException(401, "Authorization required")
headers = {}
if authorization:
headers["Authorization"] = authorization
if x_api_key:
headers["X-API-Key"] = x_api_key
try:
async with httpx.AsyncClient(timeout=5.0) as client:
response = await client.get(
f"{self.auth_url}/auth/me",
headers=headers
)
response.raise_for_status()
return response.json()
except httpx.HTTPStatusError as e:
if e.response.status_code == 401:
raise HTTPException(401, "Invalid credentials")
raise HTTPException(503, f"Auth service error: {e.response.status_code}")
except Exception as e:
print(f"⚠️ Auth service unavailable: {e}")
# Fallback: extract user ID from header (Phase 4 stub)
return self._fallback_actor(authorization, x_api_key)
def _fallback_actor(self, authorization: Optional[str], x_api_key: Optional[str]):
"""Fallback actor identity when auth-service is unavailable"""
# Extract from X-User-Id header (Phase 2/3 compatibility)
actor_id = "user:admin" # Default for Phase 4 testing
return {
"actor_id": actor_id,
"actor_type": "human",
"microdao_ids": ["microdao:daarion", "microdao:7"],
"roles": ["member", "admin"]
}
async def check_permission(self, actor, action: str, resource_type: str, resource_id: str, context: dict = None):
"""
Check permission via PDP
Raises HTTPException(403) if denied
"""
try:
async with httpx.AsyncClient(timeout=5.0) as client:
response = await client.post(
f"{self.pdp_url}/internal/pdp/evaluate",
json={
"actor": actor,
"action": action,
"resource": {
"type": resource_type,
"id": resource_id
},
"context": context or {}
}
)
response.raise_for_status()
decision = response.json()
if decision["effect"] == "deny":
reason = decision.get("reason", "access_denied")
raise HTTPException(403, f"Access denied: {reason}")
return decision
except httpx.HTTPStatusError as e:
if e.response.status_code == 403:
raise HTTPException(403, "Access denied")
print(f"⚠️ PDP service error: {e.response.status_code}")
# Fallback: allow (Phase 4 testing)
return {"effect": "permit", "reason": "pdp_unavailable_fallback"}
except Exception as e:
print(f"⚠️ PDP service unavailable: {e}")
# Fallback: allow (Phase 4 testing)
return {"effect": "permit", "reason": "pdp_unavailable_fallback"}
# Global PEP client instance
pep_client = PEPClient()
# ============================================================================
# Dependency Functions (for FastAPI endpoints)
# ============================================================================
async def require_actor(
authorization: Optional[str] = Header(None),
x_api_key: Optional[str] = Header(None),
x_user_id: Optional[str] = Header(None) # Phase 2/3 compatibility
):
"""
FastAPI dependency: Get current actor identity
Usage:
@app.post("/api/messaging/channels/{channel_id}/messages")
async def send_message(actor = Depends(require_actor)):
...
"""
# Phase 2/3 compatibility: use X-User-Id if present
if x_user_id and not authorization and not x_api_key:
return {
"actor_id": x_user_id,
"actor_type": "agent" if x_user_id.startswith("agent:") else "human",
"microdao_ids": ["microdao:daarion"],
"roles": ["member"]
}
return await pep_client.get_actor_identity(authorization, x_api_key)
async def require_channel_permission(
channel_id: str,
action: str = "send_message",
actor = None,
context: dict = None
):
"""
Check permission for channel action
Raises HTTPException(403) if denied
"""
if not actor:
raise HTTPException(401, "Authentication required")
await pep_client.check_permission(
actor=actor,
action=action,
resource_type="channel",
resource_id=channel_id,
context=context
)
async def require_microdao_permission(
microdao_id: str,
action: str = "read",
actor = None
):
"""
Check permission for microDAO action
Raises HTTPException(403) if denied
"""
if not actor:
raise HTTPException(401, "Authentication required")
await pep_client.check_permission(
actor=actor,
action=action,
resource_type="microdao",
resource_id=microdao_id
)