1use std::sync::Arc;
6
7use axum::{
8 Json, Router,
9 extract::{Path, State},
10 http::StatusCode,
11 response::IntoResponse,
12 routing::{delete, get, post},
13};
14use serde::{Deserialize, Serialize};
15
16#[derive(Debug, Clone, Serialize, Deserialize)]
18pub struct RoleDto {
19 pub id: String,
21 pub name: String,
23 pub description: Option<String>,
25 pub permissions: Vec<String>,
27 pub tenant_id: Option<String>,
29 pub created_at: String,
31 pub updated_at: String,
33}
34
35#[derive(Debug, Clone, Serialize, Deserialize)]
37pub struct PermissionDto {
38 pub id: String,
40 pub resource: String,
42 pub action: String,
44 pub description: Option<String>,
46 pub created_at: String,
48}
49
50#[derive(Debug, Clone, Serialize, Deserialize)]
52pub struct UserRoleDto {
53 pub user_id: String,
55 pub role_id: String,
57 pub tenant_id: Option<String>,
59 pub assigned_at: String,
61}
62
63#[derive(Debug, Clone, Serialize, Deserialize)]
65pub struct CreateRoleRequest {
66 pub name: String,
68 pub description: Option<String>,
70 pub permissions: Vec<String>,
72}
73
74#[derive(Debug, Clone, Serialize, Deserialize)]
76pub struct CreatePermissionRequest {
77 pub resource: String,
79 pub action: String,
81 pub description: Option<String>,
83}
84
85#[derive(Debug, Clone, Serialize, Deserialize)]
87pub struct AssignRoleRequest {
88 pub user_id: String,
90 pub role_id: String,
92}
93
94#[derive(Clone)]
96pub struct RbacManagementState {
97 pub db: Arc<db_backend::RbacDbBackend>,
99}
100
101pub fn rbac_management_router(state: RbacManagementState) -> Router {
118 Router::new()
119 .route("/api/roles", post(create_role).get(list_roles))
121 .route("/api/roles/{role_id}", get(get_role).put(update_role).delete(delete_role))
122 .route("/api/permissions", post(create_permission).get(list_permissions))
124 .route(
125 "/api/permissions/{permission_id}",
126 get(get_permission).delete(delete_permission),
127 )
128 .route("/api/user-roles", post(assign_role).get(list_user_roles))
130 .route("/api/user-roles/{user_id}/{role_id}", delete(revoke_role))
131 .route("/api/audit/permissions", get(query_permission_audit))
133 .with_state(Arc::new(state))
134}
135
136async fn create_role(
143 State(state): State<Arc<RbacManagementState>>,
144 Json(payload): Json<CreateRoleRequest>,
145) -> impl IntoResponse {
146 match state
148 .db
149 .create_role(
150 &payload.name,
151 payload.description.as_deref(),
152 payload.permissions,
153 None, )
155 .await
156 {
157 Ok(role) => (StatusCode::CREATED, Json(role)).into_response(),
158 Err(_) => (StatusCode::CONFLICT, Json(serde_json::json!({"error": "role_duplicate"})))
159 .into_response(),
160 }
161}
162
163async fn list_roles(State(state): State<Arc<RbacManagementState>>) -> impl IntoResponse {
166 match state.db.list_roles(None, 100, 0).await {
168 Ok(roles) => (StatusCode::OK, Json(roles)).into_response(),
169 Err(_) => (
170 StatusCode::INTERNAL_SERVER_ERROR,
171 Json(serde_json::json!({"error": "database_error"})),
172 )
173 .into_response(),
174 }
175}
176
177async fn get_role(
180 State(state): State<Arc<RbacManagementState>>,
181 Path(role_id): Path<String>,
182) -> impl IntoResponse {
183 match state.db.get_role(&role_id).await {
184 Ok(role) => (StatusCode::OK, Json(role)).into_response(),
185 Err(_) => (StatusCode::NOT_FOUND, Json(serde_json::json!({"error": "role_not_found"})))
186 .into_response(),
187 }
188}
189
190async fn update_role(
193 State(state): State<Arc<RbacManagementState>>,
194 Path(role_id): Path<String>,
195 Json(payload): Json<CreateRoleRequest>,
196) -> impl IntoResponse {
197 match state
198 .db
199 .update_role(&role_id, &payload.name, payload.description.as_deref(), payload.permissions)
200 .await
201 {
202 Ok(role) => (StatusCode::OK, Json(role)).into_response(),
203 Err(db_backend::RbacDbError::RoleNotFound) => {
204 (StatusCode::NOT_FOUND, Json(serde_json::json!({"error": "role_not_found"})))
205 .into_response()
206 },
207 Err(_) => (StatusCode::CONFLICT, Json(serde_json::json!({"error": "update_failed"})))
208 .into_response(),
209 }
210}
211
212async fn delete_role(
215 State(state): State<Arc<RbacManagementState>>,
216 Path(role_id): Path<String>,
217) -> impl IntoResponse {
218 match state.db.delete_role(&role_id).await {
219 Ok(()) => StatusCode::NO_CONTENT,
220 Err(db_backend::RbacDbError::RoleNotFound) => StatusCode::NOT_FOUND,
221 Err(_) => StatusCode::CONFLICT,
222 }
223}
224
225async fn create_permission(
232 State(state): State<Arc<RbacManagementState>>,
233 Json(payload): Json<CreatePermissionRequest>,
234) -> impl IntoResponse {
235 match state
236 .db
237 .create_permission(&payload.resource, &payload.action, payload.description.as_deref())
238 .await
239 {
240 Ok(perm) => (StatusCode::CREATED, Json(perm)).into_response(),
241 Err(_) => {
242 (StatusCode::CONFLICT, Json(serde_json::json!({"error": "permission_duplicate"})))
243 .into_response()
244 },
245 }
246}
247
248async fn list_permissions(State(state): State<Arc<RbacManagementState>>) -> impl IntoResponse {
251 match state.db.list_permissions().await {
252 Ok(perms) => (StatusCode::OK, Json(perms)).into_response(),
253 Err(_) => (
254 StatusCode::INTERNAL_SERVER_ERROR,
255 Json(serde_json::json!({"error": "database_error"})),
256 )
257 .into_response(),
258 }
259}
260
261async fn get_permission(
264 State(state): State<Arc<RbacManagementState>>,
265 Path(permission_id): Path<String>,
266) -> impl IntoResponse {
267 match state.db.get_permission(&permission_id).await {
268 Ok(perm) => (StatusCode::OK, Json(perm)).into_response(),
269 Err(_) => (
270 StatusCode::NOT_FOUND,
271 Json(serde_json::json!({"error": "permission_not_found"})),
272 )
273 .into_response(),
274 }
275}
276
277async fn delete_permission(
280 State(state): State<Arc<RbacManagementState>>,
281 Path(permission_id): Path<String>,
282) -> impl IntoResponse {
283 match state.db.delete_permission(&permission_id).await {
284 Ok(()) => StatusCode::NO_CONTENT,
285 Err(db_backend::RbacDbError::PermissionInUse) => StatusCode::CONFLICT,
286 Err(db_backend::RbacDbError::PermissionNotFound) => StatusCode::NOT_FOUND,
287 Err(_) => StatusCode::INTERNAL_SERVER_ERROR,
288 }
289}
290
291async fn assign_role(
298 State(state): State<Arc<RbacManagementState>>,
299 Json(payload): Json<AssignRoleRequest>,
300) -> impl IntoResponse {
301 match state.db.assign_role_to_user(&payload.user_id, &payload.role_id, None).await {
302 Ok(assignment) => (StatusCode::CREATED, Json(assignment)).into_response(),
303 Err(_) => {
304 (StatusCode::CONFLICT, Json(serde_json::json!({"error": "assignment_duplicate"})))
305 .into_response()
306 },
307 }
308}
309
310async fn list_user_roles(
313 State(state): State<Arc<RbacManagementState>>,
314 axum::extract::Query(params): axum::extract::Query<std::collections::HashMap<String, String>>,
315) -> axum::response::Response {
316 let user_id = params.get("user_id").map_or("", String::as_str);
317 if user_id.is_empty() {
318 return (StatusCode::OK, Json(serde_json::json!([]))).into_response();
319 }
320 match state.db.list_user_roles(user_id).await {
321 Ok(assignments) => (StatusCode::OK, Json(assignments)).into_response(),
322 Err(_) => (
323 StatusCode::INTERNAL_SERVER_ERROR,
324 Json(serde_json::json!({"error": "database_error"})),
325 )
326 .into_response(),
327 }
328}
329
330async fn revoke_role(
333 State(state): State<Arc<RbacManagementState>>,
334 Path((user_id, role_id)): Path<(String, String)>,
335) -> impl IntoResponse {
336 match state.db.revoke_role_from_user(&user_id, &role_id).await {
337 Ok(()) => StatusCode::NO_CONTENT,
338 Err(_) => StatusCode::NOT_FOUND,
339 }
340}
341
342async fn query_permission_audit(
349 State(_state): State<Arc<RbacManagementState>>,
350) -> impl IntoResponse {
351 Json(Vec::<serde_json::Value>::new())
352}
353
354pub mod db_backend;
356
357#[cfg(test)]
358mod tests;
359
360#[cfg(test)]
361mod db_backend_tests;
362
363#[cfg(test)]
364mod integration_tests;
365
366#[cfg(test)]
367mod schema_tests;