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("/api/permissions/:permission_id", get(get_permission).delete(delete_permission))
125 .route("/api/user-roles", post(assign_role).get(list_user_roles))
127 .route("/api/user-roles/:user_id/:role_id", delete(revoke_role))
128 .route("/api/audit/permissions", get(query_permission_audit))
130 .with_state(Arc::new(state))
131}
132
133async fn create_role(
140 State(state): State<Arc<RbacManagementState>>,
141 Json(payload): Json<CreateRoleRequest>,
142) -> impl IntoResponse {
143 match state
145 .db
146 .create_role(
147 &payload.name,
148 payload.description.as_deref(),
149 payload.permissions,
150 None, )
152 .await
153 {
154 Ok(role) => (StatusCode::CREATED, Json(role)).into_response(),
155 Err(_) => (StatusCode::CONFLICT, Json(serde_json::json!({"error": "role_duplicate"})))
156 .into_response(),
157 }
158}
159
160async fn list_roles(State(state): State<Arc<RbacManagementState>>) -> impl IntoResponse {
163 match state.db.list_roles(None, 100, 0).await {
165 Ok(roles) => (StatusCode::OK, Json(roles)).into_response(),
166 Err(_) => (
167 StatusCode::INTERNAL_SERVER_ERROR,
168 Json(serde_json::json!({"error": "database_error"})),
169 )
170 .into_response(),
171 }
172}
173
174async fn get_role(
177 State(state): State<Arc<RbacManagementState>>,
178 Path(role_id): Path<String>,
179) -> impl IntoResponse {
180 match state.db.get_role(&role_id).await {
181 Ok(role) => (StatusCode::OK, Json(role)).into_response(),
182 Err(_) => (StatusCode::NOT_FOUND, Json(serde_json::json!({"error": "role_not_found"})))
183 .into_response(),
184 }
185}
186
187async fn update_role(
190 State(state): State<Arc<RbacManagementState>>,
191 Path(role_id): Path<String>,
192 Json(payload): Json<CreateRoleRequest>,
193) -> impl IntoResponse {
194 match state
195 .db
196 .update_role(&role_id, &payload.name, payload.description.as_deref(), payload.permissions)
197 .await
198 {
199 Ok(role) => (StatusCode::OK, Json(role)).into_response(),
200 Err(db_backend::RbacDbError::RoleNotFound) => {
201 (StatusCode::NOT_FOUND, Json(serde_json::json!({"error": "role_not_found"})))
202 .into_response()
203 },
204 Err(_) => (StatusCode::CONFLICT, Json(serde_json::json!({"error": "update_failed"})))
205 .into_response(),
206 }
207}
208
209async fn delete_role(
212 State(state): State<Arc<RbacManagementState>>,
213 Path(role_id): Path<String>,
214) -> impl IntoResponse {
215 match state.db.delete_role(&role_id).await {
216 Ok(()) => StatusCode::NO_CONTENT,
217 Err(db_backend::RbacDbError::RoleNotFound) => StatusCode::NOT_FOUND,
218 Err(_) => StatusCode::CONFLICT,
219 }
220}
221
222async fn create_permission(
229 State(state): State<Arc<RbacManagementState>>,
230 Json(payload): Json<CreatePermissionRequest>,
231) -> impl IntoResponse {
232 match state
233 .db
234 .create_permission(&payload.resource, &payload.action, payload.description.as_deref())
235 .await
236 {
237 Ok(perm) => (StatusCode::CREATED, Json(perm)).into_response(),
238 Err(_) => {
239 (StatusCode::CONFLICT, Json(serde_json::json!({"error": "permission_duplicate"})))
240 .into_response()
241 },
242 }
243}
244
245async fn list_permissions(State(state): State<Arc<RbacManagementState>>) -> impl IntoResponse {
248 match state.db.list_permissions().await {
249 Ok(perms) => (StatusCode::OK, Json(perms)).into_response(),
250 Err(_) => (
251 StatusCode::INTERNAL_SERVER_ERROR,
252 Json(serde_json::json!({"error": "database_error"})),
253 )
254 .into_response(),
255 }
256}
257
258async fn get_permission(
261 State(state): State<Arc<RbacManagementState>>,
262 Path(permission_id): Path<String>,
263) -> impl IntoResponse {
264 match state.db.get_permission(&permission_id).await {
265 Ok(perm) => (StatusCode::OK, Json(perm)).into_response(),
266 Err(_) => (
267 StatusCode::NOT_FOUND,
268 Json(serde_json::json!({"error": "permission_not_found"})),
269 )
270 .into_response(),
271 }
272}
273
274async fn delete_permission(
277 State(state): State<Arc<RbacManagementState>>,
278 Path(permission_id): Path<String>,
279) -> impl IntoResponse {
280 match state.db.delete_permission(&permission_id).await {
281 Ok(()) => StatusCode::NO_CONTENT,
282 Err(db_backend::RbacDbError::PermissionInUse) => StatusCode::CONFLICT,
283 Err(db_backend::RbacDbError::PermissionNotFound) => StatusCode::NOT_FOUND,
284 Err(_) => StatusCode::INTERNAL_SERVER_ERROR,
285 }
286}
287
288async fn assign_role(
295 State(state): State<Arc<RbacManagementState>>,
296 Json(payload): Json<AssignRoleRequest>,
297) -> impl IntoResponse {
298 match state.db.assign_role_to_user(&payload.user_id, &payload.role_id, None).await {
299 Ok(assignment) => (StatusCode::CREATED, Json(assignment)).into_response(),
300 Err(_) => {
301 (StatusCode::CONFLICT, Json(serde_json::json!({"error": "assignment_duplicate"})))
302 .into_response()
303 },
304 }
305}
306
307async fn list_user_roles(
310 State(state): State<Arc<RbacManagementState>>,
311 axum::extract::Query(params): axum::extract::Query<std::collections::HashMap<String, String>>,
312) -> axum::response::Response {
313 let user_id = params.get("user_id").map_or("", String::as_str);
314 if user_id.is_empty() {
315 return (StatusCode::OK, Json(serde_json::json!([]))).into_response();
316 }
317 match state.db.list_user_roles(user_id).await {
318 Ok(assignments) => (StatusCode::OK, Json(assignments)).into_response(),
319 Err(_) => (
320 StatusCode::INTERNAL_SERVER_ERROR,
321 Json(serde_json::json!({"error": "database_error"})),
322 )
323 .into_response(),
324 }
325}
326
327async fn revoke_role(
330 State(state): State<Arc<RbacManagementState>>,
331 Path((user_id, role_id)): Path<(String, String)>,
332) -> impl IntoResponse {
333 match state.db.revoke_role_from_user(&user_id, &role_id).await {
334 Ok(()) => StatusCode::NO_CONTENT,
335 Err(_) => StatusCode::NOT_FOUND,
336 }
337}
338
339async fn query_permission_audit(
346 State(_state): State<Arc<RbacManagementState>>,
347) -> impl IntoResponse {
348 Json(Vec::<serde_json::Value>::new())
349}
350
351pub mod db_backend;
353
354#[cfg(test)]
355mod tests;
356
357#[cfg(test)]
358mod db_backend_tests;
359
360#[cfg(test)]
361mod integration_tests;
362
363#[cfg(test)]
364mod schema_tests;