From 515cc729d04f1af16f6cc5ff314571c05842c3d5 Mon Sep 17 00:00:00 2001 From: Wolfgang Bumiller Date: Wed, 25 Jan 2023 10:06:11 +0100 Subject: [PATCH] rest-server: drop ServerAdapter, move AuthError Instead of a ServerAdapter for the index page and authentication checking (which don't relate to each other), provide a `.with_auth_handler` and `.with_index_handler` builder for ApiConfig separately. Both are optional. Without an index handler, it'll produce a 404. Without an auth handler, an `AuthError::NoData` is returned. Signed-off-by: Wolfgang Bumiller --- proxmox-rest-server/src/api_config.rs | 114 ++++++++++++++++++++------ proxmox-rest-server/src/lib.rs | 49 +---------- 2 files changed, 90 insertions(+), 73 deletions(-) diff --git a/proxmox-rest-server/src/api_config.rs b/proxmox-rest-server/src/api_config.rs index 8d81a7e2..7f5af954 100644 --- a/proxmox-rest-server/src/api_config.rs +++ b/proxmox-rest-server/src/api_config.rs @@ -5,6 +5,7 @@ use std::pin::Pin; use std::sync::{Arc, Mutex}; use anyhow::{format_err, Error}; +use http::{HeaderMap, Method}; use hyper::http::request::Parts; use hyper::{Body, Response}; @@ -12,7 +13,7 @@ use proxmox_router::{Router, RpcEnvironmentType, UserInformation}; use proxmox_sys::fs::{create_path, CreateOptions}; use crate::rest::Handler; -use crate::{AuthError, CommandSocket, FileLogOptions, FileLogger, RestEnvironment, ServerAdapter}; +use crate::{CommandSocket, FileLogOptions, FileLogger, RestEnvironment}; /// REST server configuration pub struct ApiConfig { @@ -21,8 +22,9 @@ pub struct ApiConfig { env_type: RpcEnvironmentType, request_log: Option>>, auth_log: Option>>, - adapter: Pin>, handlers: Vec, + auth_handler: Option, + index_handler: Option, #[cfg(feature = "templates")] templates: templates::Templates, @@ -48,28 +50,47 @@ impl ApiConfig { env_type, request_log: None, auth_log: None, - adapter: Box::pin(DummyAdapter), handlers: Vec::new(), + auth_handler: None, + index_handler: None, #[cfg(feature = "templates")] templates: Default::default(), } } + /// Set the authentication handler. + pub fn with_auth_handler(mut self, auth_handler: AuthHandler) -> Self { + self.auth_handler = Some(auth_handler); + self + } + + /// Set the index handler. + pub fn with_index_handler(mut self, index_handler: IndexHandler) -> Self { + self.index_handler = Some(index_handler); + self + } + pub(crate) async fn get_index( &self, rest_env: RestEnvironment, parts: Parts, ) -> Response { - self.adapter.get_index(rest_env, parts).await + match self.index_handler.as_ref() { + Some(handler) => (handler.func)(rest_env, parts).await, + None => Response::builder().status(404).body("".into()).unwrap(), + } } pub(crate) async fn check_auth( &self, - headers: &http::HeaderMap, - method: &hyper::Method, + headers: &HeaderMap, + method: &Method, ) -> Result<(String, Box), AuthError> { - self.adapter.check_auth(headers, method).await + match self.auth_handler.as_ref() { + Some(handler) => (handler.func)(headers, method).await, + None => Err(AuthError::NoData), + } } pub(crate) fn find_alias(&self, mut components: &[&str]) -> PathBuf { @@ -334,27 +355,70 @@ mod templates { } } -pub struct DummyAdapter; +pub type IndexFuture = Pin> + Send>>; +pub type IndexFunc = Box IndexFuture + Send + Sync>; -impl ServerAdapter for DummyAdapter { - fn get_index( - &self, - _rest_env: RestEnvironment, - _parts: Parts, - ) -> Pin> + Send>> { - Box::pin(async move { - Response::builder() - .status(400) - .body("no index defined".into()) - .unwrap() +pub struct IndexHandler { + func: IndexFunc, +} + +impl From for IndexHandler { + fn from(func: IndexFunc) -> Self { + Self { func } + } +} + +impl IndexHandler { + pub fn new_static_body(body: B) -> Self + where + B: Clone + Send + Sync + Into + 'static, + { + Self::from_response_fn(move |_, _| { + let body = body.clone().into(); + Box::pin(async move { Response::builder().status(200).body(body).unwrap() }) }) } - fn check_auth<'a>( - &'a self, - _headers: &'a http::HeaderMap, - _method: &'a http::Method, - ) -> crate::ServerAdapterCheckAuth<'a> { - Box::pin(async move { Err(crate::AuthError::NoData) }) + pub fn from_response_fn(func: Func) -> Self + where + Func: Fn(RestEnvironment, Parts) -> IndexFuture + Send + Sync + 'static, + { + Self::from(Box::new(func) as IndexFunc) + } +} + +pub type CheckAuthOutput = Result<(String, Box), AuthError>; +pub type CheckAuthFuture<'a> = Pin + Send + 'a>>; +pub type CheckAuthFunc = + Box Fn(&'a HeaderMap, &'a Method) -> CheckAuthFuture<'a> + Send + Sync>; + +pub struct AuthHandler { + func: CheckAuthFunc, +} + +impl From for AuthHandler { + fn from(func: CheckAuthFunc) -> Self { + Self { func } + } +} + +impl AuthHandler { + pub fn from_fn(func: Func) -> Self + where + Func: for<'a> Fn(&'a HeaderMap, &'a Method) -> CheckAuthFuture<'a> + Send + Sync + 'static, + { + Self::from(Box::new(func) as CheckAuthFunc) + } +} + +/// Authentication Error +pub enum AuthError { + Generic(Error), + NoData, +} + +impl From for AuthError { + fn from(err: Error) -> Self { + AuthError::Generic(err) } } diff --git a/proxmox-rest-server/src/lib.rs b/proxmox-rest-server/src/lib.rs index a5cce380..72d19293 100644 --- a/proxmox-rest-server/src/lib.rs +++ b/proxmox-rest-server/src/lib.rs @@ -16,18 +16,12 @@ //! * generic interface to authenticate user use std::fmt; -use std::future::Future; use std::os::unix::io::{FromRawFd, OwnedFd}; -use std::pin::Pin; use std::sync::atomic::{AtomicBool, Ordering}; use anyhow::{bail, format_err, Error}; -use http::request::Parts; -use http::HeaderMap; -use hyper::{Body, Method, Response}; use nix::unistd::Pid; -use proxmox_router::UserInformation; use proxmox_sys::fs::CreateOptions; use proxmox_sys::linux::procfs::PidStat; @@ -51,7 +45,7 @@ mod file_logger; pub use file_logger::{FileLogOptions, FileLogger}; mod api_config; -pub use api_config::ApiConfig; +pub use api_config::{ApiConfig, AuthError, AuthHandler, IndexHandler}; mod rest; pub use rest::RestServer; @@ -62,47 +56,6 @@ pub use worker_task::*; mod h2service; pub use h2service::*; -/// Authentication Error -pub enum AuthError { - Generic(Error), - NoData, -} - -impl From for AuthError { - fn from(err: Error) -> Self { - AuthError::Generic(err) - } -} - -/// Result of [`ServerAdapter::check_auth`]. -pub type ServerAdapterCheckAuth<'a> = Pin< - Box< - dyn Future), AuthError>> - + Send - + 'a, - >, ->; - -/// User Authentication and index/root page generation methods -pub trait ServerAdapter: Send + Sync { - /// Returns the index/root page - fn get_index( - &self, - rest_env: RestEnvironment, - parts: Parts, - ) -> Pin> + Send>>; - - /// Extract user credentials from headers and check them. - /// - /// If credenthials are valid, returns the username and a - /// [UserInformation] object to query additional user data. - fn check_auth<'a>( - &'a self, - headers: &'a HeaderMap, - method: &'a Method, - ) -> ServerAdapterCheckAuth<'a>; -} - lazy_static::lazy_static! { static ref PID: i32 = unsafe { libc::getpid() }; static ref PSTART: u64 = PidStat::read_from_pid(Pid::from_raw(*PID)).unwrap().starttime;