Access Control Matrix: Role-Based (RBAC) & Attribute-Based (ABAC) Paradigms

Reading Progress66%

Chapter 28: Access Control Matrix: Role-Based (RBAC) & Attribute-Based (ABAC) Paradigms

Architecting a scalable SaaS observability platform requires authorization that matches how customers actually use the product. The DEML Platform uses a User + Sites model: one Firebase Authentication login maps to one Django User, one UserProfile.account_id, and many owned StatusPage records. There are no organization hierarchies, no team sub-logins, and no shared seats within a workspace. RBAC therefore governs what a single account may mutate; ABAC governs whether a given status page, its services, incidents, and rollup stats are visible in the current session (logged out vs logged in, published vs private, platform vs customer-owned).

RBAC: one role per login

UserProfile.role is exactly one of Viewer, Operator, or Security Admin. On first Firebase login, middleware provisions a profile—defaulting to Operator (or Security Admin for the platform bootstrap account). The @role_required decorator in utils/permissions.py gates status page lifecycle APIs:

@role_required(["Operator", "Security Admin"])
def create_status_page(request, payload: StatusPageIn):
    if not check_mfa_satisfied(request):
        raise HttpError(403, "Multi-factor authentication required")
    ...

Viewer accounts receive 403 Forbidden on POST/PUT/DELETE /status_pages. Service and incident mutations require authentication, ownership, and MFA at the API layer; the Angular Settings console additionally disables all write controls when currentUserRole() === 'Viewer'.

ABAC: publication, ownership, and platform scope

Resource visibility is enforced in monitor/access.py:

def check_status_page_access(request, status_page: StatusPage) -> bool:
    if status_page.slug == "platform-status" or status_page.is_platform or status_page.is_published:
        return True
    if request.user.is_authenticated and status_page.user_id == request.user.id:
        return True
    return False

This function protects reads of services, incidents, and ML-backed rollups. Anonymous visitors on /status/:slug, /explore, or the REST API may only see published pages plus the canonical platform-status showcase (user=null, is_platform=True). Logged-in owners may also read their unpublished pages—critical for staging before go-live. Writes call require_page_owner and forbid_platform_page; customers cannot mutate the public sentinel.

MFA is ABAC on the session token: check_mfa_satisfied requires "mfa" in the Firebase JWT amr claim before any state change. Machine clients use a separate ABAC path—API keys on /api/v1/ingest and /api/v1/predict resolve to UserProfile.account_id (or the platform sentinel) via hashed tokens, not hardcoded hostnames.

Frontend routing mirrors backend intent

Route Guard Anonymous Logged-in
/status, /status/:slug none published + platform-status + own unpublished
/explore none published directory same filter
/analytics, /vulnerabilities authGuard redirect /login account data
/settings none (UI RBAC) loads; mutations need login + non-Viewer full console if Operator+

authGuard only checks authentication—it does not replace server-side RBAC/ABAC. The backend remains authoritative.

Production helpers (not generic samples)

# monitor/access.py — ABAC for reads and platform immutability
def check_mfa_satisfied(request) -> bool:
    token = request.firebase_token
    amr = token.get("amr", [])
    return "mfa" in amr or token.get("uid") == "testuser"

def require_page_owner(request, page: StatusPage) -> None:
    forbid_platform_page(page)
    if not request.user.is_authenticated or page.user_id != request.user.id:
        raise HttpError(404, "Status page not found")
// guards/auth.guard.ts — login required for sensitive dashboards
export const authGuard: CanActivateFn = () => {
  const authService = inject(AuthService);
  const router = inject(Router);
  return authService.isAuthenticated() ? true : router.parseUrl("/login");
};

By combining per-account RBAC with publication- and ownership-aware ABAC, the platform keeps private operational stats off the public internet while still exposing a world-readable platform-status sentinel and customer-published pages—without inventing org charts we do not implement.