]>
Commit | Line | Data |
---|---|---|
16b48b81 | 1 | use std::collections::HashMap; |
2ab5acac DC |
2 | use std::path::PathBuf; |
3 | use std::time::SystemTime; | |
4 | use std::fs::metadata; | |
fe4cc5b1 | 5 | use std::sync::{Arc, Mutex, RwLock}; |
16b48b81 | 6 | |
2ab5acac | 7 | use anyhow::{bail, Error, format_err}; |
7fa9a37c DM |
8 | use hyper::{Method, Body, Response}; |
9 | use hyper::http::request::Parts; | |
10 | ||
f9e3b110 | 11 | use handlebars::Handlebars; |
2ab5acac | 12 | use serde::Serialize; |
16b48b81 | 13 | |
a2479cfa | 14 | use proxmox::api::{ApiMethod, Router, RpcEnvironmentType}; |
8e7e2223 TL |
15 | use proxmox::tools::fs::{create_path, CreateOptions}; |
16 | ||
49e25688 | 17 | use crate::{ApiAuth, FileLogger, FileLogOptions, CommandSocket}; |
a2479cfa | 18 | |
7fa9a37c DM |
19 | pub type GetIndexFn = fn(Option<String>, Option<String>, &ApiConfig, Parts) -> Response<Body>; |
20 | ||
0d5d15c9 | 21 | /// REST server configuration |
16b48b81 DM |
22 | pub struct ApiConfig { |
23 | basedir: PathBuf, | |
e63e99d6 | 24 | router: &'static Router, |
16b48b81 | 25 | aliases: HashMap<String, PathBuf>, |
02c7a755 | 26 | env_type: RpcEnvironmentType, |
2ab5acac DC |
27 | templates: RwLock<Handlebars<'static>>, |
28 | template_files: RwLock<HashMap<String, (SystemTime, PathBuf)>>, | |
fe4cc5b1 | 29 | request_log: Option<Arc<Mutex<FileLogger>>>, |
36b7085e | 30 | auth_log: Option<Arc<Mutex<FileLogger>>>, |
0d5d15c9 | 31 | pub(crate) api_auth: Arc<dyn ApiAuth + Send + Sync>, |
7fa9a37c | 32 | get_index_fn: GetIndexFn, |
16b48b81 DM |
33 | } |
34 | ||
35 | impl ApiConfig { | |
0d5d15c9 DM |
36 | /// Creates a new instance |
37 | /// | |
38 | /// `basedir` - File lookups are relative to this directory. | |
39 | /// | |
40 | /// `router` - The REST API definition. | |
41 | /// | |
42 | /// `env_type` - The environment type. | |
43 | /// | |
44 | /// `api_auth` - The Authentification handler | |
45 | /// | |
46 | /// `get_index_fn` - callback to generate the root page | |
47 | /// (index). Please note that this fuctions gets a reference to | |
48 | /// the [ApiConfig], so it can use [Handlebars] templates | |
49 | /// ([render_template](Self::render_template) to generate pages. | |
26858dba SR |
50 | pub fn new<B: Into<PathBuf>>( |
51 | basedir: B, | |
52 | router: &'static Router, | |
53 | env_type: RpcEnvironmentType, | |
54 | api_auth: Arc<dyn ApiAuth + Send + Sync>, | |
7fa9a37c | 55 | get_index_fn: GetIndexFn, |
26858dba | 56 | ) -> Result<Self, Error> { |
f9e3b110 | 57 | Ok(Self { |
2ab5acac | 58 | basedir: basedir.into(), |
653b1ca1 | 59 | router, |
16b48b81 | 60 | aliases: HashMap::new(), |
02c7a755 | 61 | env_type, |
2ab5acac DC |
62 | templates: RwLock::new(Handlebars::new()), |
63 | template_files: RwLock::new(HashMap::new()), | |
8e7e2223 | 64 | request_log: None, |
36b7085e | 65 | auth_log: None, |
26858dba | 66 | api_auth, |
7fa9a37c | 67 | get_index_fn, |
26858dba | 68 | }) |
16b48b81 DM |
69 | } |
70 | ||
0d5d15c9 | 71 | pub(crate) fn get_index( |
7fa9a37c DM |
72 | &self, |
73 | auth_id: Option<String>, | |
74 | language: Option<String>, | |
75 | parts: Parts, | |
76 | ) -> Response<Body> { | |
77 | (self.get_index_fn)(auth_id, language, self, parts) | |
78 | } | |
79 | ||
0d5d15c9 | 80 | pub(crate) fn find_method( |
255f378a DM |
81 | &self, |
82 | components: &[&str], | |
83 | method: Method, | |
84 | uri_param: &mut HashMap<String, String>, | |
85 | ) -> Option<&'static ApiMethod> { | |
16b48b81 | 86 | |
01bf3b7b | 87 | self.router.find_method(components, method, uri_param) |
16b48b81 DM |
88 | } |
89 | ||
0d5d15c9 | 90 | pub(crate) fn find_alias(&self, components: &[&str]) -> PathBuf { |
16b48b81 DM |
91 | |
92 | let mut prefix = String::new(); | |
93 | let mut filename = self.basedir.clone(); | |
94 | let comp_len = components.len(); | |
95 | if comp_len >= 1 { | |
96 | prefix.push_str(components[0]); | |
97 | if let Some(subdir) = self.aliases.get(&prefix) { | |
98 | filename.push(subdir); | |
382f10a0 | 99 | components.iter().skip(1).for_each(|comp| filename.push(comp)); |
8adbdb0a | 100 | } else { |
382f10a0 | 101 | components.iter().for_each(|comp| filename.push(comp)); |
16b48b81 DM |
102 | } |
103 | } | |
104 | filename | |
105 | } | |
106 | ||
0d5d15c9 DM |
107 | /// Register a path alias |
108 | /// | |
109 | /// This can be used to redirect file lookups to a specific | |
110 | /// directory, e.g.: | |
111 | /// | |
112 | /// ``` | |
113 | /// use proxmox_rest_server::ApiConfig; | |
114 | /// // let mut config = ApiConfig::new(...); | |
115 | /// # fn fake(config: &mut ApiConfig) { | |
116 | /// config.add_alias("extjs", "/usr/share/javascript/extjs"); | |
117 | /// # } | |
118 | /// ``` | |
16b48b81 DM |
119 | pub fn add_alias<S, P>(&mut self, alias: S, path: P) |
120 | where S: Into<String>, | |
121 | P: Into<PathBuf>, | |
122 | { | |
123 | self.aliases.insert(alias.into(), path.into()); | |
124 | } | |
02c7a755 | 125 | |
0d5d15c9 | 126 | pub(crate) fn env_type(&self) -> RpcEnvironmentType { |
02c7a755 DM |
127 | self.env_type |
128 | } | |
2ab5acac | 129 | |
0d5d15c9 DM |
130 | /// Register a [Handlebars] template file |
131 | /// | |
132 | /// Those templates cane be use with [render_template](Self::render_template) to generate pages. | |
2ab5acac DC |
133 | pub fn register_template<P>(&self, name: &str, path: P) -> Result<(), Error> |
134 | where | |
135 | P: Into<PathBuf> | |
136 | { | |
137 | if self.template_files.read().unwrap().contains_key(name) { | |
138 | bail!("template already registered"); | |
139 | } | |
140 | ||
141 | let path: PathBuf = path.into(); | |
142 | let metadata = metadata(&path)?; | |
143 | let mtime = metadata.modified()?; | |
144 | ||
145 | self.templates.write().unwrap().register_template_file(name, &path)?; | |
146 | self.template_files.write().unwrap().insert(name.to_string(), (mtime, path)); | |
147 | ||
148 | Ok(()) | |
149 | } | |
150 | ||
151 | /// Checks if the template was modified since the last rendering | |
152 | /// if yes, it loads a the new version of the template | |
153 | pub fn render_template<T>(&self, name: &str, data: &T) -> Result<String, Error> | |
154 | where | |
155 | T: Serialize, | |
156 | { | |
157 | let path; | |
158 | let mtime; | |
159 | { | |
160 | let template_files = self.template_files.read().unwrap(); | |
161 | let (old_mtime, old_path) = template_files.get(name).ok_or_else(|| format_err!("template not found"))?; | |
162 | ||
163 | mtime = metadata(old_path)?.modified()?; | |
164 | if mtime <= *old_mtime { | |
165 | return self.templates.read().unwrap().render(name, data).map_err(|err| format_err!("{}", err)); | |
166 | } | |
167 | path = old_path.to_path_buf(); | |
168 | } | |
169 | ||
170 | { | |
171 | let mut template_files = self.template_files.write().unwrap(); | |
172 | let mut templates = self.templates.write().unwrap(); | |
173 | ||
174 | templates.register_template_file(name, &path)?; | |
175 | template_files.insert(name.to_string(), (mtime, path)); | |
176 | ||
177 | templates.render(name, data).map_err(|err| format_err!("{}", err)) | |
178 | } | |
179 | } | |
8e7e2223 | 180 | |
0d5d15c9 DM |
181 | /// Enable the access log feature |
182 | /// | |
183 | /// When enabled, all requests are logged to the specified file. | |
184 | /// This function also registers a `api-access-log-reopen` | |
49e25688 | 185 | /// command one the [CommandSocket]. |
0d5d15c9 | 186 | pub fn enable_access_log<P>( |
fe4cc5b1 TL |
187 | &mut self, |
188 | path: P, | |
fd6d2438 DM |
189 | dir_opts: Option<CreateOptions>, |
190 | file_opts: Option<CreateOptions>, | |
49e25688 | 191 | commando_sock: &mut CommandSocket, |
fe4cc5b1 | 192 | ) -> Result<(), Error> |
8e7e2223 TL |
193 | where |
194 | P: Into<PathBuf> | |
195 | { | |
196 | let path: PathBuf = path.into(); | |
197 | if let Some(base) = path.parent() { | |
198 | if !base.exists() { | |
fd6d2438 | 199 | create_path(base, None, dir_opts).map_err(|err| format_err!("{}", err))?; |
8e7e2223 TL |
200 | } |
201 | } | |
202 | ||
203 | let logger_options = FileLogOptions { | |
204 | append: true, | |
fd6d2438 | 205 | file_opts: file_opts.unwrap_or(CreateOptions::default()), |
8e7e2223 TL |
206 | ..Default::default() |
207 | }; | |
fe4cc5b1 TL |
208 | let request_log = Arc::new(Mutex::new(FileLogger::new(&path, logger_options)?)); |
209 | self.request_log = Some(Arc::clone(&request_log)); | |
210 | ||
211 | commando_sock.register_command("api-access-log-reopen".into(), move |_args| { | |
36b7085e | 212 | println!("re-opening access-log file"); |
fe4cc5b1 TL |
213 | request_log.lock().unwrap().reopen()?; |
214 | Ok(serde_json::Value::Null) | |
215 | })?; | |
8e7e2223 TL |
216 | |
217 | Ok(()) | |
218 | } | |
fe4cc5b1 | 219 | |
0d5d15c9 DM |
220 | /// Enable the authentification log feature |
221 | /// | |
222 | /// When enabled, all authentification requests are logged to the | |
223 | /// specified file. This function also registers a | |
49e25688 | 224 | /// `api-auth-log-reopen` command one the [CommandSocket]. |
36b7085e DM |
225 | pub fn enable_auth_log<P>( |
226 | &mut self, | |
227 | path: P, | |
228 | dir_opts: Option<CreateOptions>, | |
229 | file_opts: Option<CreateOptions>, | |
49e25688 | 230 | commando_sock: &mut CommandSocket, |
36b7085e DM |
231 | ) -> Result<(), Error> |
232 | where | |
233 | P: Into<PathBuf> | |
234 | { | |
235 | let path: PathBuf = path.into(); | |
236 | if let Some(base) = path.parent() { | |
237 | if !base.exists() { | |
238 | create_path(base, None, dir_opts).map_err(|err| format_err!("{}", err))?; | |
239 | } | |
240 | } | |
241 | ||
242 | let logger_options = FileLogOptions { | |
243 | append: true, | |
244 | prefix_time: true, | |
245 | file_opts: file_opts.unwrap_or(CreateOptions::default()), | |
246 | ..Default::default() | |
247 | }; | |
248 | let auth_log = Arc::new(Mutex::new(FileLogger::new(&path, logger_options)?)); | |
249 | self.auth_log = Some(Arc::clone(&auth_log)); | |
250 | ||
251 | commando_sock.register_command("api-auth-log-reopen".into(), move |_args| { | |
252 | println!("re-opening auth-log file"); | |
253 | auth_log.lock().unwrap().reopen()?; | |
254 | Ok(serde_json::Value::Null) | |
255 | })?; | |
256 | ||
257 | Ok(()) | |
258 | } | |
259 | ||
0d5d15c9 | 260 | pub(crate) fn get_access_log(&self) -> Option<&Arc<Mutex<FileLogger>>> { |
8e7e2223 TL |
261 | self.request_log.as_ref() |
262 | } | |
36b7085e | 263 | |
0d5d15c9 | 264 | pub(crate) fn get_auth_log(&self) -> Option<&Arc<Mutex<FileLogger>>> { |
36b7085e DM |
265 | self.auth_log.as_ref() |
266 | } | |
16b48b81 | 267 | } |