Use OIDC username

This commit is contained in:
Simon Goller 2024-06-05 18:00:05 +02:00
parent 9888ac4062
commit 3c670d50d3
10 changed files with 154 additions and 43 deletions

View file

@ -5,9 +5,10 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features]
default = ["mock_auth"]
#default = ["oidc"]
default = []
oidc = ["dep:axum-oidc"]
mock_auth = []
[dependencies]
axum = "0.7.5"

View file

@ -4,12 +4,12 @@ use axum::body::Body;
use axum::extract::Path;
use axum::routing::{delete, get, post};
use axum::{extract::State, response::Response};
use axum::{Json, Router};
use axum::{Extension, Json, Router};
use serde::{Deserialize, Serialize};
use time::PrimitiveDateTime;
use uuid::Uuid;
use crate::{error_handler, RestStateDef};
use crate::{error_handler, Context, RestStateDef};
use service::booking::{Booking, BookingService};
#[derive(Serialize, Deserialize, Clone, Debug)]
@ -65,12 +65,15 @@ pub fn generate_route<RestState: RestStateDef>() -> Router<RestState> {
.route("/:id", delete(delete_booking::<RestState>))
}
pub async fn get_all_bookings<RestState: RestStateDef>(rest_state: State<RestState>) -> Response {
pub async fn get_all_bookings<RestState: RestStateDef>(
rest_state: State<RestState>,
Extension(context): Extension<Context>,
) -> Response {
error_handler(
(async {
let bookings: Arc<[BookingTO]> = rest_state
.booking_service()
.get_all(().into())
.get_all(context.into())
.await?
.iter()
.map(BookingTO::from)
@ -86,13 +89,14 @@ pub async fn get_all_bookings<RestState: RestStateDef>(rest_state: State<RestSta
pub async fn get_booking<RestState: RestStateDef>(
rest_state: State<RestState>,
Extension(context): Extension<Context>,
Path(booking_id): Path<Uuid>,
) -> Response {
error_handler(
(async {
let booking = rest_state
.booking_service()
.get(booking_id, ().into())
.get(booking_id, context.into())
.await?;
Ok(Response::builder()
.status(200)
@ -107,13 +111,14 @@ pub async fn get_booking<RestState: RestStateDef>(
pub async fn create_booking<RestState: RestStateDef>(
rest_state: State<RestState>,
Extension(context): Extension<Context>,
Json(booking): Json<BookingTO>,
) -> Response {
error_handler(
(async {
let booking = rest_state
.booking_service()
.create(&Booking::from(&booking), ().into())
.create(&Booking::from(&booking), context.into())
.await?;
Ok(Response::builder()
.status(200)
@ -128,13 +133,14 @@ pub async fn create_booking<RestState: RestStateDef>(
pub async fn delete_booking<RestState: RestStateDef>(
rest_state: State<RestState>,
Extension(context): Extension<Context>,
Path(booking_id): Path<Uuid>,
) -> Response {
error_handler(
(async {
rest_state
.booking_service()
.delete(booking_id, ().into())
.delete(booking_id, context.into())
.await?;
Ok(Response::builder().status(200).body(Body::empty()).unwrap())
})

View file

@ -5,7 +5,9 @@ mod permission;
mod sales_person;
mod slot;
use axum::extract::Request;
use axum::http::Uri;
use axum::middleware::{self, Next};
use axum::response::{IntoResponse, Redirect};
use axum::routing::get;
use axum::{body::Body, error_handling::HandleErrorLayer, response::Response, Router};
@ -19,7 +21,34 @@ use tower_sessions::{cookie::SameSite, Expiry, MemoryStore, SessionManagerLayer}
use uuid::Uuid;
// TODO: In prod, it must be a different type than in dev mode.
#[cfg(feature = "mock_auth")]
type Context = ();
#[cfg(feature = "oidc")]
type Context = Option<Arc<str>>;
#[cfg(feature = "oidc")]
pub async fn context_extractor(
claims: Option<OidcClaims<EmptyAdditionalClaims>>,
mut request: Request,
next: Next,
) -> Response {
let context: Context = if let Some(oidc_claims) = claims {
let username = oidc_claims
.preferred_username()
.map(|s| s.as_str().to_string())
.unwrap_or_else(|| "NoUsername".to_string());
Some(username.into())
} else {
None
};
request.extensions_mut().insert(context);
next.run(request).await
}
#[cfg(feature = "mock_auth")]
pub async fn context_extractor(mut request: Request, next: Next) -> Response {
request.extensions_mut().insert(());
next.run(request).await
}
pub struct RoString(Arc<str>, bool);
impl http_body::Body for RoString {
@ -77,6 +106,9 @@ fn error_handler(result: Result<Response, RestError>) -> Response {
Err(RestError::ServiceError(service::ServiceError::Forbidden)) => {
Response::builder().status(403).body(Body::empty()).unwrap()
}
Err(RestError::ServiceError(service::ServiceError::Unauthorized)) => {
Response::builder().status(401).body(Body::empty()).unwrap()
}
Err(RestError::ServiceError(service::ServiceError::DatabaseQueryError(e))) => {
Response::builder()
.status(500)
@ -245,7 +277,8 @@ pub async fn start_server<RestState: RestStateDef>(rest_state: RestState) {
.nest("/slot", slot::generate_route())
.nest("/sales-person", sales_person::generate_route())
.nest("/booking", booking::generate_route())
.with_state(rest_state);
.with_state(rest_state)
.layer(middleware::from_fn(context_extractor));
#[cfg(feature = "oidc")]
let app = {

View file

@ -6,10 +6,10 @@ use axum::{
extract::State,
response::Response,
routing::{delete, get, post},
Json, Router,
Extension, Json, Router,
};
use crate::{error_handler, RestStateDef};
use crate::{error_handler, Context, RestStateDef};
use service::PermissionService;
#[derive(Debug, Serialize, Deserialize)]
@ -80,6 +80,7 @@ pub fn generate_route<RestState: RestStateDef>() -> Router<RestState> {
pub async fn add_user<RestState: RestStateDef>(
rest_state: State<RestState>,
Extension(context): Extension<Context>,
Json(user): Json<UserTO>,
) -> Response {
println!("Adding user: {:?}", user);
@ -87,7 +88,7 @@ pub async fn add_user<RestState: RestStateDef>(
(async {
rest_state
.permission_service()
.create_user(user.name.as_str(), ().into())
.create_user(user.name.as_str(), context.into())
.await?;
Ok(Response::builder()
.status(201)
@ -100,6 +101,7 @@ pub async fn add_user<RestState: RestStateDef>(
pub async fn remove_user<RestState: RestStateDef>(
rest_state: State<RestState>,
Extension(context): Extension<Context>,
Json(user): Json<String>,
) -> Response {
println!("Removing user: {:?}", user);
@ -107,7 +109,7 @@ pub async fn remove_user<RestState: RestStateDef>(
(async {
rest_state
.permission_service()
.delete_user(&user, ().into())
.delete_user(&user, context.into())
.await?;
Ok(Response::builder()
.status(200)
@ -120,13 +122,14 @@ pub async fn remove_user<RestState: RestStateDef>(
pub async fn add_role<RestState: RestStateDef>(
rest_state: State<RestState>,
Extension(context): Extension<Context>,
Json(role): Json<RoleTO>,
) -> Response {
error_handler(
(async {
rest_state
.permission_service()
.create_role(role.name.as_str(), ().into())
.create_role(role.name.as_str(), context.into())
.await?;
Ok(Response::builder()
.status(200)
@ -139,13 +142,14 @@ pub async fn add_role<RestState: RestStateDef>(
pub async fn delete_role<RestState: RestStateDef>(
rest_state: State<RestState>,
Extension(context): Extension<Context>,
Json(role): Json<String>,
) -> Response {
error_handler(
(async {
rest_state
.permission_service()
.delete_role(role.as_str(), ().into())
.delete_role(role.as_str(), context.into())
.await?;
Ok(Response::builder()
.status(200)
@ -158,13 +162,18 @@ pub async fn delete_role<RestState: RestStateDef>(
pub async fn add_user_role<RestState: RestStateDef>(
rest_state: State<RestState>,
Extension(context): Extension<Context>,
Json(user_role): Json<UserRole>,
) -> Response {
error_handler(
(async {
rest_state
.permission_service()
.add_user_role(user_role.user.as_str(), user_role.role.as_str(), ().into())
.add_user_role(
user_role.user.as_str(),
user_role.role.as_str(),
context.into(),
)
.await?;
Ok(Response::builder()
.status(201)
@ -177,13 +186,18 @@ pub async fn add_user_role<RestState: RestStateDef>(
pub async fn remove_user_role<RestState: RestStateDef>(
rest_state: State<RestState>,
Extension(context): Extension<Context>,
Json(user_role): Json<UserRole>,
) -> Response {
error_handler(
(async {
rest_state
.permission_service()
.delete_user_role(user_role.user.as_str(), user_role.role.as_str(), ().into())
.delete_user_role(
user_role.user.as_str(),
user_role.role.as_str(),
context.into(),
)
.await?;
Ok(Response::builder()
.status(200)
@ -196,6 +210,7 @@ pub async fn remove_user_role<RestState: RestStateDef>(
pub async fn add_role_privilege<RestState: RestStateDef>(
rest_state: State<RestState>,
Extension(context): Extension<Context>,
Json(role_privilege): Json<RolePrivilege>,
) -> Response {
error_handler(
@ -205,7 +220,7 @@ pub async fn add_role_privilege<RestState: RestStateDef>(
.add_role_privilege(
role_privilege.role.as_str(),
role_privilege.privilege.as_str(),
().into(),
context.into(),
)
.await?;
Ok(Response::builder()
@ -219,6 +234,7 @@ pub async fn add_role_privilege<RestState: RestStateDef>(
pub async fn remove_role_privilege<RestState: RestStateDef>(
rest_state: State<RestState>,
Extension(context): Extension<Context>,
Json(role_privilege): Json<RolePrivilege>,
) -> Response {
error_handler(
@ -228,7 +244,7 @@ pub async fn remove_role_privilege<RestState: RestStateDef>(
.delete_role_privilege(
role_privilege.role.as_str(),
role_privilege.privilege.as_str(),
().into(),
context.into(),
)
.await?;
Ok(Response::builder()
@ -240,12 +256,15 @@ pub async fn remove_role_privilege<RestState: RestStateDef>(
)
}
pub async fn get_all_users<RestState: RestStateDef>(rest_state: State<RestState>) -> Response {
pub async fn get_all_users<RestState: RestStateDef>(
rest_state: State<RestState>,
Extension(context): Extension<Context>,
) -> Response {
error_handler(
(async {
let users: Arc<[UserTO]> = rest_state
.permission_service()
.get_all_users(().into())
.get_all_users(context.into())
.await?
.iter()
.map(UserTO::from)
@ -259,12 +278,15 @@ pub async fn get_all_users<RestState: RestStateDef>(rest_state: State<RestState>
)
}
pub async fn get_all_roles<RestState: RestStateDef>(rest_state: State<RestState>) -> Response {
pub async fn get_all_roles<RestState: RestStateDef>(
rest_state: State<RestState>,
Extension(context): Extension<Context>,
) -> Response {
error_handler(
(async {
let roles: Arc<[RoleTO]> = rest_state
.permission_service()
.get_all_roles(().into())
.get_all_roles(context.into())
.await?
.iter()
.map(RoleTO::from)
@ -278,12 +300,15 @@ pub async fn get_all_roles<RestState: RestStateDef>(rest_state: State<RestState>
)
}
pub async fn get_all_privileges<RestState: RestStateDef>(rest_state: State<RestState>) -> Response {
pub async fn get_all_privileges<RestState: RestStateDef>(
rest_state: State<RestState>,
Extension(context): Extension<Context>,
) -> Response {
error_handler(
(async {
let privileges: Arc<[PrivilegeTO]> = rest_state
.permission_service()
.get_all_privileges(().into())
.get_all_privileges(context.into())
.await?
.iter()
.map(PrivilegeTO::from)

View file

@ -4,13 +4,13 @@ use axum::body::Body;
use axum::extract::Path;
use axum::routing::{delete, get, post, put};
use axum::{extract::State, response::Response};
use axum::{Json, Router};
use axum::{Extension, Json, Router};
use serde::{Deserialize, Serialize};
use service::sales_person::SalesPerson;
use service::sales_person::SalesPersonService;
use uuid::Uuid;
use crate::{error_handler, RestError, RestStateDef};
use crate::{error_handler, Context, RestError, RestStateDef};
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct SalesPersonTO {
@ -62,12 +62,13 @@ pub fn generate_route<RestState: RestStateDef>() -> Router<RestState> {
pub async fn get_all_sales_persons<RestState: RestStateDef>(
rest_state: State<RestState>,
Extension(context): Extension<Context>,
) -> Response {
error_handler(
(async {
let sales_persons: Arc<[SalesPersonTO]> = rest_state
.sales_person_service()
.get_all(().into())
.get_all(context.into())
.await?
.iter()
.map(SalesPersonTO::from)
@ -83,6 +84,7 @@ pub async fn get_all_sales_persons<RestState: RestStateDef>(
pub async fn get_sales_person<RestState: RestStateDef>(
rest_state: State<RestState>,
Extension(context): Extension<Context>,
Path(sales_person_id): Path<Uuid>,
) -> Response {
error_handler(
@ -90,7 +92,7 @@ pub async fn get_sales_person<RestState: RestStateDef>(
let sales_person = SalesPersonTO::from(
&rest_state
.sales_person_service()
.get(sales_person_id, ().into())
.get(sales_person_id, context.into())
.await?,
);
Ok(Response::builder()
@ -104,6 +106,7 @@ pub async fn get_sales_person<RestState: RestStateDef>(
pub async fn create_sales_person<RestState: RestStateDef>(
rest_state: State<RestState>,
Extension(context): Extension<Context>,
Json(sales_person): Json<SalesPersonTO>,
) -> Response {
error_handler(
@ -111,7 +114,7 @@ pub async fn create_sales_person<RestState: RestStateDef>(
let sales_person = SalesPersonTO::from(
&rest_state
.sales_person_service()
.create(&(&sales_person).into(), ().into())
.create(&(&sales_person).into(), context.into())
.await?,
);
Ok(Response::builder()
@ -125,6 +128,7 @@ pub async fn create_sales_person<RestState: RestStateDef>(
pub async fn update_sales_person<RestState: RestStateDef>(
rest_state: State<RestState>,
Extension(context): Extension<Context>,
Path(sales_person_id): Path<Uuid>,
Json(sales_person): Json<SalesPersonTO>,
) -> Response {
@ -135,7 +139,7 @@ pub async fn update_sales_person<RestState: RestStateDef>(
}
rest_state
.sales_person_service()
.update(&(&sales_person).into(), ().into())
.update(&(&sales_person).into(), context.into())
.await?;
Ok(Response::builder()
.status(200)
@ -148,13 +152,14 @@ pub async fn update_sales_person<RestState: RestStateDef>(
pub async fn delete_sales_person<RestState: RestStateDef>(
rest_state: State<RestState>,
Extension(context): Extension<Context>,
Path(sales_person_id): Path<Uuid>,
) -> Response {
error_handler(
(async {
rest_state
.sales_person_service()
.delete(sales_person_id, ().into())
.delete(sales_person_id, context.into())
.await?;
Ok(Response::builder().status(204).body(Body::empty()).unwrap())
})
@ -164,13 +169,14 @@ pub async fn delete_sales_person<RestState: RestStateDef>(
pub async fn get_sales_person_user<RestState: RestStateDef>(
rest_state: State<RestState>,
Extension(context): Extension<Context>,
Path(sales_person_id): Path<Uuid>,
) -> Response {
error_handler(
(async {
let user = rest_state
.sales_person_service()
.get_assigned_user(sales_person_id, ().into())
.get_assigned_user(sales_person_id, context.into())
.await?;
Ok(Response::builder()
.status(200)
@ -183,6 +189,7 @@ pub async fn get_sales_person_user<RestState: RestStateDef>(
pub async fn set_sales_person_user<RestState: RestStateDef>(
rest_state: State<RestState>,
Extension(context): Extension<Context>,
Path(sales_person_id): Path<Uuid>,
Json(user): Json<Arc<str>>,
) -> Response {
@ -190,7 +197,7 @@ pub async fn set_sales_person_user<RestState: RestStateDef>(
(async {
rest_state
.sales_person_service()
.set_user(sales_person_id, user.into(), ().into())
.set_user(sales_person_id, user.into(), context.into())
.await?;
Ok(Response::builder().status(204).body(Body::empty()).unwrap())
})
@ -200,13 +207,14 @@ pub async fn set_sales_person_user<RestState: RestStateDef>(
pub async fn delete_sales_person_user<RestState: RestStateDef>(
rest_state: State<RestState>,
Extension(context): Extension<Context>,
Path(sales_person_id): Path<Uuid>,
) -> Response {
error_handler(
(async {
rest_state
.sales_person_service()
.set_user(sales_person_id, None, ().into())
.set_user(sales_person_id, None, context.into())
.await?;
Ok(Response::builder().status(204).body(Body::empty()).unwrap())
})

View file

@ -5,13 +5,13 @@ use axum::{
extract::{Path, State},
response::Response,
routing::{get, post, put},
Json, Router,
Extension, Json, Router,
};
use serde::{Deserialize, Serialize};
use service::slot::SlotService;
use uuid::Uuid;
use crate::{error_handler, RestError, RestStateDef};
use crate::{error_handler, Context, RestError, RestStateDef};
#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
pub enum DayOfWeek {
@ -102,12 +102,15 @@ pub fn generate_route<RestState: RestStateDef>() -> Router<RestState> {
.route("/:id", put(update_slot::<RestState>))
}
pub async fn get_all_slots<RestState: RestStateDef>(rest_state: State<RestState>) -> Response {
pub async fn get_all_slots<RestState: RestStateDef>(
rest_state: State<RestState>,
Extension(context): Extension<Context>,
) -> Response {
error_handler(
(async {
let slots: Arc<[SlotTO]> = rest_state
.slot_service()
.get_slots(().into())
.get_slots(context.into())
.await?
.iter()
.map(SlotTO::from)
@ -123,6 +126,7 @@ pub async fn get_all_slots<RestState: RestStateDef>(rest_state: State<RestState>
pub async fn get_slot<RestState: RestStateDef>(
rest_state: State<RestState>,
Extension(context): Extension<Context>,
Path(slot_id): Path<Uuid>,
) -> Response {
error_handler(
@ -130,7 +134,7 @@ pub async fn get_slot<RestState: RestStateDef>(
let slot = SlotTO::from(
&rest_state
.slot_service()
.get_slot(&slot_id, ().into())
.get_slot(&slot_id, context.into())
.await?,
);
Ok(Response::builder()
@ -144,6 +148,7 @@ pub async fn get_slot<RestState: RestStateDef>(
pub async fn create_slot<RestState: RestStateDef>(
rest_state: State<RestState>,
Extension(context): Extension<Context>,
Json(slot): Json<SlotTO>,
) -> Response {
error_handler(
@ -151,7 +156,7 @@ pub async fn create_slot<RestState: RestStateDef>(
let slot = SlotTO::from(
&rest_state
.slot_service()
.create_slot(&(&slot).into(), ().into())
.create_slot(&(&slot).into(), context.into())
.await?,
);
Ok(Response::builder()
@ -165,6 +170,7 @@ pub async fn create_slot<RestState: RestStateDef>(
pub async fn update_slot<RestState: RestStateDef>(
rest_state: State<RestState>,
Extension(context): Extension<Context>,
Path(slot_id): Path<Uuid>,
Json(slot): Json<SlotTO>,
) -> Response {
@ -175,7 +181,7 @@ pub async fn update_slot<RestState: RestStateDef>(
}
rest_state
.slot_service()
.update_slot(&(&slot).into(), ().into())
.update_slot(&(&slot).into(), context.into())
.await?;
Ok(Response::builder()
.status(200)