Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
abee512866 | ||
|
|
af0dee05ec | ||
|
|
83d4892acb | ||
|
|
011bc918a8 |
@@ -0,0 +1,66 @@
|
|||||||
|
"""V1R3C0 User Portal RBAC Ops core services.
|
||||||
|
|
||||||
|
Pure application layer: no secrets, no external credentials.
|
||||||
|
"""
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
|
||||||
|
ROLES = {"user", "group_admin", "owner"}
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class PortalContext:
|
||||||
|
user_id: str
|
||||||
|
group_id: str | None
|
||||||
|
role: str
|
||||||
|
|
||||||
|
def scope(self):
|
||||||
|
if self.role == "owner":
|
||||||
|
return "global"
|
||||||
|
if self.role == "group_admin":
|
||||||
|
return "group"
|
||||||
|
return "self"
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Approval:
|
||||||
|
request_id: str
|
||||||
|
request_type: str
|
||||||
|
state: str = "pending"
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Alert:
|
||||||
|
alert_id: str
|
||||||
|
level: str
|
||||||
|
state: str = "open"
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class AuditEvent:
|
||||||
|
action: str
|
||||||
|
actor: str
|
||||||
|
created_at: str = field(default_factory=lambda: datetime.now(timezone.utc).isoformat())
|
||||||
|
|
||||||
|
class PortalRBACService:
|
||||||
|
def authorize(self, ctx: PortalContext, target_scope: str):
|
||||||
|
if ctx.role not in ROLES:
|
||||||
|
return False
|
||||||
|
if ctx.scope() == "global":
|
||||||
|
return True
|
||||||
|
return ctx.scope() == target_scope
|
||||||
|
|
||||||
|
def navigation(self, role: str):
|
||||||
|
common = ["/", "/me", "/model", "/usage", "/quota", "/tasks", "/alerts", "/approvals", "/audit"]
|
||||||
|
if role == "owner":
|
||||||
|
return common + ["/owner/system", "/owner/gitops", "/owner/runtime", "/owner/security"]
|
||||||
|
if role == "group_admin":
|
||||||
|
return common + ["/group", "/group/users", "/group/quota", "/group/audit"]
|
||||||
|
return common
|
||||||
|
|
||||||
|
class PortalAuditService:
|
||||||
|
def append(self, action: str, actor: str):
|
||||||
|
return AuditEvent(action=action, actor=actor)
|
||||||
|
|
||||||
|
class SecretPolicy:
|
||||||
|
@staticmethod
|
||||||
|
def redact(value: str):
|
||||||
|
return "***REDACTED***" if value else value
|
||||||
|
|
||||||
|
__all__ = ["PortalContext", "PortalRBACService", "PortalAuditService", "SecretPolicy", "Approval", "Alert"]
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS portal_approvals (
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
request_type TEXT NOT NULL,
|
||||||
|
state TEXT NOT NULL DEFAULT 'pending',
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS portal_alerts (
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
level TEXT NOT NULL,
|
||||||
|
state TEXT NOT NULL DEFAULT 'open',
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS portal_audit_logs (
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
actor TEXT NOT NULL,
|
||||||
|
action TEXT NOT NULL,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS portal_notifications (
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
user_id TEXT NOT NULL,
|
||||||
|
message TEXT NOT NULL,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"type": "branch",
|
||||||
|
"branch": "patch/v1r3c0-user-portal-rbac-ops-20260708",
|
||||||
|
"base": "main",
|
||||||
|
"created_by": "chatgpt-gitops-bot",
|
||||||
|
"created_at": "2026-07-07T18:32:29Z",
|
||||||
|
"direct_main_push": false,
|
||||||
|
"owner_approval_required": true
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
from active_runtime.app.portal_rbac_ops import PortalContext, PortalRBACService, PortalAuditService, SecretPolicy
|
||||||
|
|
||||||
|
|
||||||
|
def test_user_scope():
|
||||||
|
ctx = PortalContext('u1', 'g1', 'user')
|
||||||
|
assert PortalRBACService().authorize(ctx, 'self')
|
||||||
|
|
||||||
|
|
||||||
|
def test_owner_scope():
|
||||||
|
ctx = PortalContext('o1', None, 'owner')
|
||||||
|
assert PortalRBACService().authorize(ctx, 'global')
|
||||||
|
|
||||||
|
|
||||||
|
def test_navigation():
|
||||||
|
assert '/owner/system' in PortalRBACService().navigation('owner')
|
||||||
|
|
||||||
|
|
||||||
|
def test_audit_append():
|
||||||
|
event = PortalAuditService().append('login', 'u1')
|
||||||
|
assert event.action == 'login'
|
||||||
|
|
||||||
|
|
||||||
|
def test_secret_redaction():
|
||||||
|
assert SecretPolicy.redact('secret') == '***REDACTED***'
|
||||||
Reference in New Issue
Block a user