use crate::auth::rebac::{
CheckRequest, CheckResponse, RebacError, RebacEvaluator, RelationshipCondition,
RelationshipTuple,
};
use crate::server::AppState;
use axum::{
extract::{Query, State},
http::StatusCode,
response::{IntoResponse, Response},
Json,
};
use serde::{Deserialize, Serialize};
use std::sync::Arc;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CheckPermissionRequest {
pub subject: String,
pub relation: String,
pub object: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CheckPermissionResponse {
pub allowed: bool,
pub reason: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AddTupleRequest {
pub subject: String,
pub relation: String,
pub object: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub condition: Option<RelationshipCondition>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RemoveTupleRequest {
pub subject: String,
pub relation: String,
pub object: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BatchCheckRequest {
pub checks: Vec<CheckPermissionRequest>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BatchCheckResponse {
pub results: Vec<CheckPermissionResponse>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ListTuplesQuery {
pub subject: Option<String>,
pub object: Option<String>,
pub relation: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ListTuplesResponse {
pub tuples: Vec<RelationshipTuple>,
pub count: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TupleOperationResponse {
pub success: bool,
pub message: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ErrorResponse {
pub error: String,
pub details: Option<String>,
}
impl IntoResponse for ErrorResponse {
fn into_response(self) -> Response {
let status = StatusCode::BAD_REQUEST;
(status, Json(self)).into_response()
}
}
impl From<RebacError> for ErrorResponse {
fn from(err: RebacError) -> Self {
Self {
error: "ReBAC Error".to_string(),
details: Some(err.to_string()),
}
}
}
pub async fn check_permission(
State(state): State<Arc<AppState>>,
Json(req): Json<CheckPermissionRequest>,
) -> Result<Json<CheckPermissionResponse>, ErrorResponse> {
let rebac = state.rebac_manager.as_ref().ok_or_else(|| ErrorResponse {
error: "ReBAC not enabled".to_string(),
details: Some("ReBAC manager is not configured".to_string()),
})?;
let check_req = CheckRequest::new(&req.subject, &req.relation, &req.object);
let response = rebac.check(&check_req).await.map_err(ErrorResponse::from)?;
Ok(Json(CheckPermissionResponse {
allowed: response.allowed,
reason: response.reason,
}))
}
pub async fn batch_check_permissions(
State(state): State<Arc<AppState>>,
Json(req): Json<BatchCheckRequest>,
) -> Result<Json<BatchCheckResponse>, ErrorResponse> {
let rebac = state.rebac_manager.as_ref().ok_or_else(|| ErrorResponse {
error: "ReBAC not enabled".to_string(),
details: Some("ReBAC manager is not configured".to_string()),
})?;
let check_requests: Vec<CheckRequest> = req
.checks
.iter()
.map(|c| CheckRequest::new(&c.subject, &c.relation, &c.object))
.collect();
let responses = rebac
.batch_check(&check_requests)
.await
.map_err(ErrorResponse::from)?;
let results = responses
.into_iter()
.map(|r| CheckPermissionResponse {
allowed: r.allowed,
reason: r.reason,
})
.collect();
Ok(Json(BatchCheckResponse { results }))
}
pub async fn add_tuple(
State(state): State<Arc<AppState>>,
Json(req): Json<AddTupleRequest>,
) -> Result<Json<TupleOperationResponse>, ErrorResponse> {
let rebac = state.rebac_manager.as_ref().ok_or_else(|| ErrorResponse {
error: "ReBAC not enabled".to_string(),
details: Some("ReBAC manager is not configured".to_string()),
})?;
let tuple = if let Some(condition) = req.condition {
RelationshipTuple::with_condition(&req.subject, &req.relation, &req.object, condition)
} else {
RelationshipTuple::new(&req.subject, &req.relation, &req.object)
};
rebac.add_tuple(tuple).await.map_err(ErrorResponse::from)?;
Ok(Json(TupleOperationResponse {
success: true,
message: "Relationship tuple added successfully".to_string(),
}))
}
pub async fn remove_tuple(
State(state): State<Arc<AppState>>,
Json(req): Json<RemoveTupleRequest>,
) -> Result<Json<TupleOperationResponse>, ErrorResponse> {
let rebac = state.rebac_manager.as_ref().ok_or_else(|| ErrorResponse {
error: "ReBAC not enabled".to_string(),
details: Some("ReBAC manager is not configured".to_string()),
})?;
let tuple = RelationshipTuple::new(&req.subject, &req.relation, &req.object);
rebac
.remove_tuple(&tuple)
.await
.map_err(ErrorResponse::from)?;
Ok(Json(TupleOperationResponse {
success: true,
message: "Relationship tuple removed successfully".to_string(),
}))
}
pub async fn list_tuples(
State(state): State<Arc<AppState>>,
Query(query): Query<ListTuplesQuery>,
) -> Result<Json<ListTuplesResponse>, ErrorResponse> {
let rebac = state.rebac_manager.as_ref().ok_or_else(|| ErrorResponse {
error: "ReBAC not enabled".to_string(),
details: Some("ReBAC manager is not configured".to_string()),
})?;
let tuples = if let Some(subject) = &query.subject {
rebac
.list_subject_tuples(subject)
.await
.map_err(ErrorResponse::from)?
} else if let Some(object) = &query.object {
rebac
.list_object_tuples(object)
.await
.map_err(ErrorResponse::from)?
} else {
vec![]
};
let filtered_tuples: Vec<RelationshipTuple> = tuples
.into_iter()
.filter(|t| {
if let Some(rel) = &query.relation {
&t.relation == rel
} else {
true
}
})
.collect();
let count = filtered_tuples.len();
Ok(Json(ListTuplesResponse {
tuples: filtered_tuples,
count,
}))
}