Make auth-info endpoint machine readable

This commit is contained in:
Simon Goller 2024-06-05 22:30:09 +02:00
parent e2f5b04ff1
commit 506791fa6a
7 changed files with 112 additions and 36 deletions

View file

@ -0,0 +1,20 @@
{
"db_name": "SQLite",
"query": "SELECT privilege.name FROM user \n INNER JOIN user_role ON user.name = user_role.user_name \n INNER JOIN role ON user_role.role_name = role.name \n INNER JOIN role_privilege ON role.name = role_privilege.role_name \n INNER JOIN privilege ON role_privilege.privilege_name = privilege.name \n WHERE user.name = ?",
"describe": {
"columns": [
{
"name": "name",
"ordinal": 0,
"type_info": "Text"
}
],
"parameters": {
"Right": 1
},
"nullable": [
false
]
},
"hash": "88c56af867c22de3d86839e86a2f1d7b75f2527d59ee4ea594c0cab0018bcd48"
}

View file

@ -33,17 +33,22 @@ type BookingService = service_impl::booking::BookingServiceImpl<
#[derive(Clone)] #[derive(Clone)]
pub struct RestStateImpl { pub struct RestStateImpl {
user_service: Arc<UserService>,
permission_service: Arc<PermissionService>, permission_service: Arc<PermissionService>,
slot_service: Arc<SlotService>, slot_service: Arc<SlotService>,
sales_person_service: Arc<SalesPersonService>, sales_person_service: Arc<SalesPersonService>,
booking_service: Arc<BookingService>, booking_service: Arc<BookingService>,
} }
impl rest::RestStateDef for RestStateImpl { impl rest::RestStateDef for RestStateImpl {
type UserService = UserService;
type PermissionService = PermissionService; type PermissionService = PermissionService;
type SlotService = SlotService; type SlotService = SlotService;
type SalesPersonService = SalesPersonService; type SalesPersonService = SalesPersonService;
type BookingService = BookingService; type BookingService = BookingService;
fn user_service(&self) -> Arc<Self::UserService> {
self.user_service.clone()
}
fn permission_service(&self) -> Arc<Self::PermissionService> { fn permission_service(&self) -> Arc<Self::PermissionService> {
self.permission_service.clone() self.permission_service.clone()
} }
@ -74,9 +79,10 @@ impl RestStateImpl {
let user_service = service_impl::UserServiceDev; let user_service = service_impl::UserServiceDev;
#[cfg(feature = "oidc")] #[cfg(feature = "oidc")]
let user_service = service_impl::UserServiceImpl; let user_service = service_impl::UserServiceImpl;
let user_service = Arc::new(user_service);
let permission_service = Arc::new(service_impl::PermissionServiceImpl::new( let permission_service = Arc::new(service_impl::PermissionServiceImpl::new(
permission_dao.into(), permission_dao.into(),
user_service.into(), user_service.clone(),
)); ));
let clock_service = Arc::new(service_impl::clock::ClockServiceImpl); let clock_service = Arc::new(service_impl::clock::ClockServiceImpl);
let uuid_service = Arc::new(service_impl::uuid_service::UuidServiceImpl); let uuid_service = Arc::new(service_impl::uuid_service::UuidServiceImpl);
@ -102,6 +108,7 @@ impl RestStateImpl {
slot_service.clone(), slot_service.clone(),
)); ));
Self { Self {
user_service,
permission_service, permission_service,
slot_service, slot_service,
sales_person_service, sales_person_service,

View file

@ -50,4 +50,6 @@ pub trait PermissionDao {
) -> Result<(), DaoError>; ) -> Result<(), DaoError>;
async fn delete_role_privilege(&self, role: &str, privilege: &str) -> Result<(), DaoError>; async fn delete_role_privilege(&self, role: &str, privilege: &str) -> Result<(), DaoError>;
async fn delete_user_role(&self, user: &str, role: &str) -> Result<(), DaoError>; async fn delete_user_role(&self, user: &str, role: &str) -> Result<(), DaoError>;
async fn privileges_for_user(&self, user: &str) -> Result<Arc<[PrivilegeEntity]>, DaoError>;
} }

View file

@ -1,7 +1,7 @@
use std::sync::Arc; use std::sync::Arc;
use async_trait::async_trait; use async_trait::async_trait;
use dao::DaoError; use dao::{DaoError, PrivilegeEntity};
use sqlx::{query, query_as, SqlitePool}; use sqlx::{query, query_as, SqlitePool};
pub mod booking; pub mod booking;
@ -211,4 +211,21 @@ impl dao::PermissionDao for PermissionDaoImpl {
.map_db_error()?; .map_db_error()?;
Ok(()) Ok(())
} }
async fn privileges_for_user(&self, user: &str) -> Result<Arc<[PrivilegeEntity]>, DaoError> {
Ok(
query_as!(PrivilegeEntity, r"SELECT privilege.name FROM user
INNER JOIN user_role ON user.name = user_role.user_name
INNER JOIN role ON user_role.role_name = role.name
INNER JOIN role_privilege ON role.name = role_privilege.role_name
INNER JOIN privilege ON role_privilege.privilege_name = privilege.name
WHERE user.name = ?",
user
)
.fetch_all(self.pool.as_ref())
.await
.map(Arc::<[PrivilegeEntity]>::from)
.map_db_error()?,
)
}
} }

View file

@ -5,14 +5,18 @@ mod permission;
mod sales_person; mod sales_person;
mod slot; mod slot;
use axum::extract::Request; use axum::extract::{Request, State};
use axum::http::Uri; use axum::http::Uri;
use axum::middleware::{self, Next}; use axum::middleware::{self, Next};
use axum::response::{IntoResponse, Redirect}; use axum::response::{IntoResponse, Redirect};
use axum::routing::get; use axum::routing::get;
use axum::Extension;
use axum::{body::Body, error_handling::HandleErrorLayer, response::Response, Router}; use axum::{body::Body, error_handling::HandleErrorLayer, response::Response, Router};
#[cfg(feature = "oidc")] #[cfg(feature = "oidc")]
use axum_oidc::{EmptyAdditionalClaims, OidcClaims}; use axum_oidc::{EmptyAdditionalClaims, OidcClaims};
use serde::{Deserialize, Serialize};
use service::user_service::UserService;
use service::PermissionService;
use service::ServiceError; use service::ServiceError;
use thiserror::Error; use thiserror::Error;
use time::Duration; use time::Duration;
@ -177,6 +181,7 @@ fn error_handler(result: Result<Response, RestError>) -> Response {
} }
pub trait RestStateDef: Clone + Send + Sync + 'static { pub trait RestStateDef: Clone + Send + Sync + 'static {
type UserService: service::user_service::UserService<Context = Context> + Send + Sync + 'static;
type PermissionService: service::PermissionService<Context = Context> + Send + Sync + 'static; type PermissionService: service::PermissionService<Context = Context> + Send + Sync + 'static;
type SlotService: service::slot::SlotService<Context = Context> + Send + Sync + 'static; type SlotService: service::slot::SlotService<Context = Context> + Send + Sync + 'static;
type SalesPersonService: service::sales_person::SalesPersonService<Context = Context> type SalesPersonService: service::sales_person::SalesPersonService<Context = Context>
@ -185,6 +190,7 @@ pub trait RestStateDef: Clone + Send + Sync + 'static {
+ 'static; + 'static;
type BookingService: service::booking::BookingService<Context = Context> + Send + Sync + 'static; type BookingService: service::booking::BookingService<Context = Context> + Send + Sync + 'static;
fn user_service(&self) -> Arc<Self::UserService>;
fn permission_service(&self) -> Arc<Self::PermissionService>; fn permission_service(&self) -> Arc<Self::PermissionService>;
fn slot_service(&self) -> Arc<Self::SlotService>; fn slot_service(&self) -> Arc<Self::SlotService>;
fn sales_person_service(&self) -> Arc<Self::SalesPersonService>; fn sales_person_service(&self) -> Arc<Self::SalesPersonService>;
@ -220,37 +226,36 @@ pub async fn login() -> Redirect {
Redirect::to("/") Redirect::to("/")
} }
#[cfg(feature = "oidc")] #[derive(Serialize, Deserialize, Clone, Debug)]
pub async fn auth_info(claims: Option<OidcClaims<EmptyAdditionalClaims>>) -> Response { pub struct AuthInfoTO {
if let Some(oidc_claims) = claims { pub user: Arc<str>,
let username = oidc_claims pub privileges: Arc<[Arc<str>]>,
.preferred_username() }
.map(|s| s.as_str().to_string())
.unwrap_or_else(|| "NoUsername".to_string()); pub async fn auth_info<RestState: RestStateDef>(
let email = oidc_claims rest_state: State<RestState>,
.email() Extension(context): Extension<Context>,
.map(|s| s.as_str().to_string()) ) -> Response {
.unwrap_or_else(|| "MailNotSet".to_string()); let user = rest_state
let name = oidc_claims .user_service()
.name() .current_user(context.clone().into())
.map(|s| { .await
s.iter() .unwrap_or_else(|_| "NoUser".into());
.next() let privileges: Arc<[Arc<str>]> = rest_state
.map(|s| s.1.as_str().to_string()) .permission_service()
.unwrap_or_else(|| "NoLocalizedName".to_string()) .get_privileges_for_current_user(context.into())
}) .await
.unwrap_or_else(|| "NameNotSet".to_string()); .unwrap_or_else(|_| Arc::new([]))
let body = format!( .into_iter()
"Hello, {}! Your email is {} and your username is {}", .map(|privilege| privilege.name.clone())
name, email, username .collect();
); let auth_info = AuthInfoTO { user, privileges };
Response::builder()
.status(200) let response = serde_json::to_string(&AuthInfoTO::from(auth_info)).unwrap();
.body(Body::new(body)) Response::builder()
.unwrap() .status(200)
} else { .body(Body::new(response))
Response::builder().status(401).body(Body::empty()).unwrap() .unwrap()
}
} }
pub async fn start_server<RestState: RestStateDef>(rest_state: RestState) { pub async fn start_server<RestState: RestStateDef>(rest_state: RestState) {
@ -269,10 +274,10 @@ pub async fn start_server<RestState: RestStateDef>(rest_state: RestState) {
app.route("/authenticate", get(login)) app.route("/authenticate", get(login))
.layer(oidc_login_service) .layer(oidc_login_service)
.route("/auth-info", get(auth_info))
}; };
let app = app let app = app
.route("/auth-info", get(auth_info::<RestState>))
.nest("/permission", permission::generate_route()) .nest("/permission", permission::generate_route())
.nest("/slot", slot::generate_route()) .nest("/slot", slot::generate_route())
.nest("/sales-person", sales_person::generate_route()) .nest("/sales-person", sales_person::generate_route())

View file

@ -65,6 +65,10 @@ pub trait PermissionService {
privilege: &str, privilege: &str,
context: Authentication<Self::Context>, context: Authentication<Self::Context>,
) -> Result<(), ServiceError>; ) -> Result<(), ServiceError>;
async fn get_privileges_for_current_user(
&self,
context: Authentication<Self::Context>,
) -> Result<Arc<[Privilege]>, ServiceError>;
async fn create_user( async fn create_user(
&self, &self,

View file

@ -2,7 +2,7 @@ use std::sync::Arc;
use async_trait::async_trait; use async_trait::async_trait;
use service::permission::Authentication; use service::permission::Authentication;
use service::ServiceError; use service::{Privilege, ServiceError};
pub struct PermissionServiceImpl<PermissionDao, UserService> pub struct PermissionServiceImpl<PermissionDao, UserService>
where where
@ -58,6 +58,27 @@ where
} }
} }
async fn get_privileges_for_current_user(
&self,
context: Authentication<Self::Context>,
) -> Result<Arc<[Privilege]>, ServiceError> {
match context {
Authentication::Full => Ok(Arc::new([Privilege {
name: "god-mode".into(),
}])),
Authentication::Context(context) => {
let current_user = self.user_service.current_user(context).await?;
Ok(self
.permission_dao
.privileges_for_user(current_user.as_ref())
.await?
.iter()
.map(service::Privilege::from)
.collect())
}
}
}
async fn create_user( async fn create_user(
&self, &self,
user: &str, user: &str,