1 use std
::collections
::HashMap
;
2 use std
::path
::PathBuf
;
3 use std
::time
::SystemTime
;
5 use std
::sync
::{Arc, Mutex, RwLock}
;
8 use anyhow
::{bail, Error, format_err}
;
9 use hyper
::{Method, Body, Response}
;
10 use hyper
::http
::request
::Parts
;
12 use handlebars
::Handlebars
;
15 use proxmox
::api
::{ApiMethod, Router, RpcEnvironmentType, UserInformation}
;
16 use proxmox
::tools
::fs
::{create_path, CreateOptions}
;
18 use crate::{ServerAdapter, AuthError, FileLogger, FileLogOptions, CommandSocket, RestEnvironment}
;
21 /// REST server configuration
22 pub struct ApiConfig
{
24 router
: &'
static Router
,
25 aliases
: HashMap
<String
, PathBuf
>,
26 env_type
: RpcEnvironmentType
,
27 templates
: RwLock
<Handlebars
<'
static>>,
28 template_files
: RwLock
<HashMap
<String
, (SystemTime
, PathBuf
)>>,
29 request_log
: Option
<Arc
<Mutex
<FileLogger
>>>,
30 auth_log
: Option
<Arc
<Mutex
<FileLogger
>>>,
31 adapter
: Pin
<Box
<dyn ServerAdapter
+ Send
+ Sync
>>,
35 /// Creates a new instance
37 /// `basedir` - File lookups are relative to this directory.
39 /// `router` - The REST API definition.
41 /// `env_type` - The environment type.
43 /// `api_auth` - The Authentication handler
45 /// `get_index_fn` - callback to generate the root page
46 /// (index). Please note that this fuctions gets a reference to
47 /// the [ApiConfig], so it can use [Handlebars] templates
48 /// ([render_template](Self::render_template) to generate pages.
49 pub fn new
<B
: Into
<PathBuf
>>(
51 router
: &'
static Router
,
52 env_type
: RpcEnvironmentType
,
53 adapter
: impl ServerAdapter
+ '
static,
54 ) -> Result
<Self, Error
> {
56 basedir
: basedir
.into(),
58 aliases
: HashMap
::new(),
60 templates
: RwLock
::new(Handlebars
::new()),
61 template_files
: RwLock
::new(HashMap
::new()),
64 adapter
: Box
::pin(adapter
),
68 pub(crate) async
fn get_index(
70 rest_env
: RestEnvironment
,
73 self.adapter
.get_index(rest_env
, parts
).await
76 pub(crate) async
fn check_auth(
78 headers
: &http
::HeaderMap
,
79 method
: &hyper
::Method
,
80 ) -> Result
<(String
, Box
<dyn UserInformation
+ Sync
+ Send
>), AuthError
> {
81 self.adapter
.check_auth(headers
, method
).await
84 pub(crate) fn find_method(
88 uri_param
: &mut HashMap
<String
, String
>,
89 ) -> Option
<&'
static ApiMethod
> {
91 self.router
.find_method(components
, method
, uri_param
)
94 pub(crate) fn find_alias(&self, components
: &[&str]) -> PathBuf
{
96 let mut prefix
= String
::new();
97 let mut filename
= self.basedir
.clone();
98 let comp_len
= components
.len();
100 prefix
.push_str(components
[0]);
101 if let Some(subdir
) = self.aliases
.get(&prefix
) {
102 filename
.push(subdir
);
103 components
.iter().skip(1).for_each(|comp
| filename
.push(comp
));
105 components
.iter().for_each(|comp
| filename
.push(comp
));
111 /// Register a path alias
113 /// This can be used to redirect file lookups to a specific
117 /// use proxmox_rest_server::ApiConfig;
118 /// // let mut config = ApiConfig::new(...);
119 /// # fn fake(config: &mut ApiConfig) {
120 /// config.add_alias("extjs", "/usr/share/javascript/extjs");
123 pub fn add_alias
<S
, P
>(&mut self, alias
: S
, path
: P
)
124 where S
: Into
<String
>,
127 self.aliases
.insert(alias
.into(), path
.into());
130 pub(crate) fn env_type(&self) -> RpcEnvironmentType
{
134 /// Register a [Handlebars] template file
136 /// Those templates cane be use with [render_template](Self::render_template) to generate pages.
137 pub fn register_template
<P
>(&self, name
: &str, path
: P
) -> Result
<(), Error
>
141 if self.template_files
.read().unwrap().contains_key(name
) {
142 bail
!("template already registered");
145 let path
: PathBuf
= path
.into();
146 let metadata
= metadata(&path
)?
;
147 let mtime
= metadata
.modified()?
;
149 self.templates
.write().unwrap().register_template_file(name
, &path
)?
;
150 self.template_files
.write().unwrap().insert(name
.to_string(), (mtime
, path
));
155 /// Checks if the template was modified since the last rendering
156 /// if yes, it loads a the new version of the template
157 pub fn render_template
<T
>(&self, name
: &str, data
: &T
) -> Result
<String
, Error
>
164 let template_files
= self.template_files
.read().unwrap();
165 let (old_mtime
, old_path
) = template_files
.get(name
).ok_or_else(|| format_err
!("template not found"))?
;
167 mtime
= metadata(old_path
)?
.modified()?
;
168 if mtime
<= *old_mtime
{
169 return self.templates
.read().unwrap().render(name
, data
).map_err(|err
| format_err
!("{}", err
));
171 path
= old_path
.to_path_buf();
175 let mut template_files
= self.template_files
.write().unwrap();
176 let mut templates
= self.templates
.write().unwrap();
178 templates
.register_template_file(name
, &path
)?
;
179 template_files
.insert(name
.to_string(), (mtime
, path
));
181 templates
.render(name
, data
).map_err(|err
| format_err
!("{}", err
))
185 /// Enable the access log feature
187 /// When enabled, all requests are logged to the specified file.
188 /// This function also registers a `api-access-log-reopen`
189 /// command one the [CommandSocket].
190 pub fn enable_access_log
<P
>(
193 dir_opts
: Option
<CreateOptions
>,
194 file_opts
: Option
<CreateOptions
>,
195 commando_sock
: &mut CommandSocket
,
196 ) -> Result
<(), Error
>
200 let path
: PathBuf
= path
.into();
201 if let Some(base
) = path
.parent() {
203 create_path(base
, None
, dir_opts
).map_err(|err
| format_err
!("{}", err
))?
;
207 let logger_options
= FileLogOptions
{
209 file_opts
: file_opts
.unwrap_or(CreateOptions
::default()),
212 let request_log
= Arc
::new(Mutex
::new(FileLogger
::new(&path
, logger_options
)?
));
213 self.request_log
= Some(Arc
::clone(&request_log
));
215 commando_sock
.register_command("api-access-log-reopen".into(), move |_args
| {
216 println
!("re-opening access-log file");
217 request_log
.lock().unwrap().reopen()?
;
218 Ok(serde_json
::Value
::Null
)
224 /// Enable the authentication log feature
226 /// When enabled, all authentication requests are logged to the
227 /// specified file. This function also registers a
228 /// `api-auth-log-reopen` command one the [CommandSocket].
229 pub fn enable_auth_log
<P
>(
232 dir_opts
: Option
<CreateOptions
>,
233 file_opts
: Option
<CreateOptions
>,
234 commando_sock
: &mut CommandSocket
,
235 ) -> Result
<(), Error
>
239 let path
: PathBuf
= path
.into();
240 if let Some(base
) = path
.parent() {
242 create_path(base
, None
, dir_opts
).map_err(|err
| format_err
!("{}", err
))?
;
246 let logger_options
= FileLogOptions
{
249 file_opts
: file_opts
.unwrap_or(CreateOptions
::default()),
252 let auth_log
= Arc
::new(Mutex
::new(FileLogger
::new(&path
, logger_options
)?
));
253 self.auth_log
= Some(Arc
::clone(&auth_log
));
255 commando_sock
.register_command("api-auth-log-reopen".into(), move |_args
| {
256 println
!("re-opening auth-log file");
257 auth_log
.lock().unwrap().reopen()?
;
258 Ok(serde_json
::Value
::Null
)
264 pub(crate) fn get_access_log(&self) -> Option
<&Arc
<Mutex
<FileLogger
>>> {
265 self.request_log
.as_ref()
268 pub(crate) fn get_auth_log(&self) -> Option
<&Arc
<Mutex
<FileLogger
>>> {
269 self.auth_log
.as_ref()