Skip to content

Commit 5cbc720

Browse files
committed
add RBAC documentation, update OpenAPI security, hide reviewed status from unauthenticated users
1 parent e3ebc31 commit 5cbc720

File tree

6 files changed

+157
-6
lines changed

6 files changed

+157
-6
lines changed

docs/auth/rbac.md

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
# Role-Based Access Control (RBAC)
2+
3+
## Overview
4+
5+
The Status Dashboard implements RBAC for maintenance event management. Roles are extracted from the JWT `groups` claim and mapped to application permissions.
6+
7+
## Roles
8+
9+
Three roles are supported, with highest privilege taking precedence when a user has multiple roles:
10+
11+
| Role | Priority | Description |
12+
|------|----------|-------------|
13+
| **sd_admins** | Highest | Full access to all operations |
14+
| **sd_operators** | Medium | Review and approve maintenance events |
15+
| **sd_creators** | Lowest | Create and manage own maintenance events |
16+
17+
## Configuration
18+
19+
Roles are mapped from IdP group names via environment variables:
20+
21+
| Environment Variable | Default | Description |
22+
|---------------------|---------|-------------|
23+
| `SD_ADMINS_GROUP` | `admin-group` | IdP group for admin role |
24+
| `SD_OPERATORS_GROUP` | (none) | IdP group for operator role |
25+
| `SD_CREATORS_GROUP` | (none) | IdP group for creator role |
26+
27+
## Permissions by Role
28+
29+
### sd_admins
30+
31+
- Unrestricted access to all maintenance operations
32+
- Bypass all status-based restrictions
33+
- Can transition events to any status
34+
- Events created with status `planned` (bypass review)
35+
36+
### sd_operators
37+
38+
- View all maintenance events regardless of status
39+
- Approve events: `pending_review``reviewed`
40+
- Create events with status `planned` (bypass review)
41+
- Cancel events in `pending_review` status
42+
- Update events in `pending_review` status
43+
44+
### sd_creators
45+
46+
- Create maintenance events (status: `pending_review`)
47+
- Modify **own** events only when status is `pending_review`
48+
- Cancel **own** events only when status is `pending_review`
49+
- Cannot modify events after approval (`reviewed`, `planned`, etc.)
50+
51+
## Maintenance Status Workflow
52+
53+
```
54+
Creator creates event
55+
56+
57+
┌─────────────┐
58+
│pending_review│ ◄── sd_creators can modify/cancel here
59+
└──────┬──────┘
60+
│ Operator approves
61+
62+
┌─────────────┐
63+
│ reviewed │
64+
└──────┬──────┘
65+
│ Checker auto-transitions
66+
67+
┌─────────────┐
68+
│ planned │ ◄── sd_operators/sd_admins start here
69+
└──────┬──────┘
70+
71+
72+
[active → completed]
73+
```
74+
75+
## Field Visibility
76+
77+
Some fields are only visible to authenticated users:
78+
79+
| Field | Visibility |
80+
|-------|------------|
81+
| `creator` | Authenticated only |
82+
| `contact_email` | Authenticated only |
83+
| `version` | Authenticated only |
84+
85+
Events with status `pending_review` or `reviewed` are hidden from unauthenticated users.
86+
87+
## Error Responses
88+
89+
| HTTP Code | Condition |
90+
|-----------|-----------|
91+
| `401 Unauthorized` | Missing or invalid JWT token |
92+
| `403 Forbidden` | Insufficient role permissions |
93+
| `403 Forbidden` | Attempting to modify event you don't own (sd_creators) |
94+
| `403 Forbidden` | Attempting to modify event not in `pending_review` (sd_creators) |
95+
| `409 Conflict` | Version mismatch (concurrent modification) |
96+
| `409 Conflict` | Event no longer in expected status |
97+
98+
## JWT Token Structure
99+
100+
The API expects JWT tokens with the following claims:
101+
102+
```json
103+
{
104+
"preferred_username": "user@example.com",
105+
"groups": ["sd_creators", "other-group"]
106+
}
107+
```
108+
109+
- `preferred_username` → stored as event creator
110+
- `groups` → matched against configured role environment variables

docs/readme.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@
77
- [Incident creation for API V2](./v2/v2_incident_creation.md)
88
- [Components availability V2](./v2/v2_components_availability.md)
99
- [Authentication for FE part](./auth/authentication.md)
10+
- [Role-Based Access Control (RBAC)](./auth/rbac.md)

internal/api/v2/v2.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -300,8 +300,9 @@ func GetIncidentHandler(dbInst *db.DB, logger *zap.Logger, svc *rbac.Service) gi
300300
}
301301

302302
isAuth := hasExtendedView(c, svc)
303-
// Hide pending review maintenance from non-authenticated users
304-
if !isAuth && r.Type == event.TypeMaintenance && r.Status == event.MaintenancePendingReview {
303+
// Hide pending review and reviewed maintenance from non-authenticated users
304+
if !isAuth && r.Type == event.TypeMaintenance &&
305+
(r.Status == event.MaintenancePendingReview || r.Status == event.MaintenanceReviewed) {
305306
apiErrors.RaiseStatusNotFoundErr(c, apiErrors.ErrIncidentDSNotExist)
306307
return
307308
}

internal/db/db.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -124,8 +124,8 @@ func applyEventsFilters(base *gorm.DB, params *IncidentsParams, isAuth bool) (*g
124124

125125
if !isAuth {
126126
base = base.Where(
127-
"NOT (incident.type = ? AND incident.status = ?)",
128-
event.TypeMaintenance, event.MaintenancePendingReview,
127+
"NOT (incident.type = ? AND incident.status IN (?, ?))",
128+
event.TypeMaintenance, event.MaintenancePendingReview, event.MaintenanceReviewed,
129129
)
130130
}
131131

openapi.yaml

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ info:
1212
- `contact_email` - Contact email for maintenance events
1313
- `version` - Version number for optimistic locking (conflict detection)
1414
15-
Maintenance events with status `pending_review` are only visible to authenticated users.
15+
Maintenance events with status `pending_review` or `reviewed` are only visible to authenticated users.
1616
1717
## Optimistic Locking
1818
@@ -21,6 +21,25 @@ info:
2121
- PATCH requests must include the current `version` number
2222
- If the version doesn't match (event was modified by another user), a 409 Conflict is returned
2323
- On successful update, the version is automatically incremented
24+
25+
## Role-Based Access Control (RBAC)
26+
27+
The API implements RBAC with three roles (highest to lowest privilege):
28+
29+
| Role | Permissions |
30+
|------|-------------|
31+
| **sd_admins** | Unrestricted access to all operations, bypass status restrictions |
32+
| **sd_operators** | View all events, approve pending_review → reviewed, create events (status: planned) |
33+
| **sd_creators** | Create maintenance (status: pending_review), modify/cancel own pending_review events |
34+
35+
Roles are determined from the JWT `groups` claim and mapped via environment variables:
36+
- `SD_ADMINS_GROUP` (default: "admin-group")
37+
- `SD_OPERATORS_GROUP`
38+
- `SD_CREATORS_GROUP`
39+
40+
### Error Responses
41+
- `401 Unauthorized` - Missing or invalid JWT token
42+
- `403 Forbidden` - Insufficient role permissions or not event owner
2443
servers:
2544
- url: http://localhost:8000
2645
tags:
@@ -34,6 +53,8 @@ tags:
3453
description: Operations about components
3554
- name: v1
3655
description: Deprecated API schema for backward compatibility
56+
security:
57+
- BearerAuth: []
3758
paths:
3859
/auth/login:
3960
get:
@@ -331,6 +352,10 @@ paths:
331352
application/json:
332353
schema:
333354
$ref: '#/components/schemas/IncidentPostResponse'
355+
'401':
356+
description: Unauthorized - missing or invalid JWT token
357+
'403':
358+
description: Forbidden - insufficient permissions for this operation
334359

335360
/v2/events/{event_id}:
336361
get:
@@ -386,6 +411,10 @@ paths:
386411
description: Invalid ID supplied
387412
'404':
388413
description: Event not found.
414+
'401':
415+
description: Unauthorized - missing or invalid JWT token
416+
'403':
417+
description: Forbidden - insufficient permissions or not event owner
389418
'409':
390419
description: Version conflict - event has been modified by another user. Fetch the latest version and retry.
391420
content:
@@ -519,6 +548,10 @@ paths:
519548
description: Invalid ID supplied
520549
'404':
521550
description: Incident not found.
551+
'401':
552+
description: Unauthorized - missing or invalid JWT token
553+
'403':
554+
description: Forbidden - insufficient permissions or not event owner
522555
'409':
523556
description: Version conflict - incident has been modified by another user. Fetch the latest version and retry.
524557
content:
@@ -1214,3 +1247,9 @@ components:
12141247
schema:
12151248
type: integer
12161249
example: 0
1250+
securitySchemes:
1251+
BearerAuth:
1252+
type: http
1253+
scheme: bearer
1254+
bearerFormat: JWT
1255+
description: JWT token from Keycloak. Include in Authorization header as "Bearer <token>"

specs/001-maintenance-rbac/spec.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ The system enforces role-based permissions throughout the maintenance lifecycle.
168168
#### Status Workflow
169169

170170
- **FR-022**: System MUST support the following status flow for sd_creators: pending review → reviewed → planned → [existing statuses]
171-
- **FR-022-1**: System MUST NOT include maintenance events with "pending review" status in API responses for unauthenticated users
171+
- **FR-022-1**: System MUST NOT include maintenance events with "pending review" or "reviewed" status in API responses for unauthenticated users
172172
- **FR-022a**: System MUST support direct "planned" status for events created by sd_operators and sd_admins users (bypassing pending review and reviewed statuses)
173173
- **FR-022b**: System MUST support "cancelled" as a terminal status reachable from any other status, representing event removal/cancellation
174174
- **FR-023**: The internal checker goroutine in the existing "checker" module MUST automatically change status from "reviewed" to "planned" without performing additional validation

0 commit comments

Comments
 (0)