diff --git a/.sqlx/query-88c56af867c22de3d86839e86a2f1d7b75f2527d59ee4ea594c0cab0018bcd48.json b/.sqlx/query-88c56af867c22de3d86839e86a2f1d7b75f2527d59ee4ea594c0cab0018bcd48.json new file mode 100644 index 0000000..cdd6d3f --- /dev/null +++ b/.sqlx/query-88c56af867c22de3d86839e86a2f1d7b75f2527d59ee4ea594c0cab0018bcd48.json @@ -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" +} diff --git a/app/src/main.rs b/app/src/main.rs index 5d62c71..f56dbe2 100644 --- a/app/src/main.rs +++ b/app/src/main.rs @@ -33,17 +33,22 @@ type BookingService = service_impl::booking::BookingServiceImpl< #[derive(Clone)] pub struct RestStateImpl { + user_service: Arc, permission_service: Arc, slot_service: Arc, sales_person_service: Arc, booking_service: Arc, } impl rest::RestStateDef for RestStateImpl { + type UserService = UserService; type PermissionService = PermissionService; type SlotService = SlotService; type SalesPersonService = SalesPersonService; type BookingService = BookingService; + fn user_service(&self) -> Arc { + self.user_service.clone() + } fn permission_service(&self) -> Arc { self.permission_service.clone() } @@ -74,9 +79,10 @@ impl RestStateImpl { let user_service = service_impl::UserServiceDev; #[cfg(feature = "oidc")] let user_service = service_impl::UserServiceImpl; + let user_service = Arc::new(user_service); let permission_service = Arc::new(service_impl::PermissionServiceImpl::new( permission_dao.into(), - user_service.into(), + user_service.clone(), )); let clock_service = Arc::new(service_impl::clock::ClockServiceImpl); let uuid_service = Arc::new(service_impl::uuid_service::UuidServiceImpl); @@ -102,6 +108,7 @@ impl RestStateImpl { slot_service.clone(), )); Self { + user_service, permission_service, slot_service, sales_person_service, diff --git a/dao/src/permission.rs b/dao/src/permission.rs index 391a351..a5b1d15 100644 --- a/dao/src/permission.rs +++ b/dao/src/permission.rs @@ -50,4 +50,6 @@ pub trait PermissionDao { ) -> 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 privileges_for_user(&self, user: &str) -> Result, DaoError>; } diff --git a/dao_impl/src/lib.rs b/dao_impl/src/lib.rs index de6211e..f49c154 100644 --- a/dao_impl/src/lib.rs +++ b/dao_impl/src/lib.rs @@ -1,7 +1,7 @@ use std::sync::Arc; use async_trait::async_trait; -use dao::DaoError; +use dao::{DaoError, PrivilegeEntity}; use sqlx::{query, query_as, SqlitePool}; pub mod booking; @@ -211,4 +211,21 @@ impl dao::PermissionDao for PermissionDaoImpl { .map_db_error()?; Ok(()) } + + async fn privileges_for_user(&self, user: &str) -> Result, 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()?, + ) + } } diff --git a/rest/src/lib.rs b/rest/src/lib.rs index f9e8709..29e87f9 100644 --- a/rest/src/lib.rs +++ b/rest/src/lib.rs @@ -5,14 +5,18 @@ mod permission; mod sales_person; mod slot; -use axum::extract::Request; +use axum::extract::{Request, State}; use axum::http::Uri; use axum::middleware::{self, Next}; use axum::response::{IntoResponse, Redirect}; use axum::routing::get; +use axum::Extension; use axum::{body::Body, error_handling::HandleErrorLayer, response::Response, Router}; #[cfg(feature = "oidc")] use axum_oidc::{EmptyAdditionalClaims, OidcClaims}; +use serde::{Deserialize, Serialize}; +use service::user_service::UserService; +use service::PermissionService; use service::ServiceError; use thiserror::Error; use time::Duration; @@ -177,6 +181,7 @@ fn error_handler(result: Result) -> Response { } pub trait RestStateDef: Clone + Send + Sync + 'static { + type UserService: service::user_service::UserService + Send + Sync + 'static; type PermissionService: service::PermissionService + Send + Sync + 'static; type SlotService: service::slot::SlotService + Send + Sync + 'static; type SalesPersonService: service::sales_person::SalesPersonService @@ -185,6 +190,7 @@ pub trait RestStateDef: Clone + Send + Sync + 'static { + 'static; type BookingService: service::booking::BookingService + Send + Sync + 'static; + fn user_service(&self) -> Arc; fn permission_service(&self) -> Arc; fn slot_service(&self) -> Arc; fn sales_person_service(&self) -> Arc; @@ -220,37 +226,36 @@ pub async fn login() -> Redirect { Redirect::to("/") } -#[cfg(feature = "oidc")] -pub async fn auth_info(claims: Option>) -> Response { - 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()); - let email = oidc_claims - .email() - .map(|s| s.as_str().to_string()) - .unwrap_or_else(|| "MailNotSet".to_string()); - let name = oidc_claims - .name() - .map(|s| { - s.iter() - .next() - .map(|s| s.1.as_str().to_string()) - .unwrap_or_else(|| "NoLocalizedName".to_string()) - }) - .unwrap_or_else(|| "NameNotSet".to_string()); - let body = format!( - "Hello, {}! Your email is {} and your username is {}", - name, email, username - ); - Response::builder() - .status(200) - .body(Body::new(body)) - .unwrap() - } else { - Response::builder().status(401).body(Body::empty()).unwrap() - } +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct AuthInfoTO { + pub user: Arc, + pub privileges: Arc<[Arc]>, +} + +pub async fn auth_info( + rest_state: State, + Extension(context): Extension, +) -> Response { + let user = rest_state + .user_service() + .current_user(context.clone().into()) + .await + .unwrap_or_else(|_| "NoUser".into()); + let privileges: Arc<[Arc]> = rest_state + .permission_service() + .get_privileges_for_current_user(context.into()) + .await + .unwrap_or_else(|_| Arc::new([])) + .into_iter() + .map(|privilege| privilege.name.clone()) + .collect(); + let auth_info = AuthInfoTO { user, privileges }; + + let response = serde_json::to_string(&AuthInfoTO::from(auth_info)).unwrap(); + Response::builder() + .status(200) + .body(Body::new(response)) + .unwrap() } pub async fn start_server(rest_state: RestState) { @@ -269,10 +274,10 @@ pub async fn start_server(rest_state: RestState) { app.route("/authenticate", get(login)) .layer(oidc_login_service) - .route("/auth-info", get(auth_info)) }; let app = app + .route("/auth-info", get(auth_info::)) .nest("/permission", permission::generate_route()) .nest("/slot", slot::generate_route()) .nest("/sales-person", sales_person::generate_route()) diff --git a/service/src/permission.rs b/service/src/permission.rs index f09d894..46907e9 100644 --- a/service/src/permission.rs +++ b/service/src/permission.rs @@ -65,6 +65,10 @@ pub trait PermissionService { privilege: &str, context: Authentication, ) -> Result<(), ServiceError>; + async fn get_privileges_for_current_user( + &self, + context: Authentication, + ) -> Result, ServiceError>; async fn create_user( &self, diff --git a/service_impl/src/permission.rs b/service_impl/src/permission.rs index 5c81e38..a4c0a52 100644 --- a/service_impl/src/permission.rs +++ b/service_impl/src/permission.rs @@ -2,7 +2,7 @@ use std::sync::Arc; use async_trait::async_trait; use service::permission::Authentication; -use service::ServiceError; +use service::{Privilege, ServiceError}; pub struct PermissionServiceImpl where @@ -58,6 +58,27 @@ where } } + async fn get_privileges_for_current_user( + &self, + context: Authentication, + ) -> Result, 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( &self, user: &str,