use std::sync::Arc;
use std::task::{Context, Poll};
-use failure::*;
+use anyhow::{bail, format_err, Error};
use futures::future::{self, FutureExt, TryFutureExt};
use futures::stream::TryStreamExt;
use hyper::header;
use tokio::fs::File;
use tokio::time::Instant;
use url::form_urlencoded;
+use handlebars::Handlebars;
-use proxmox::api::http_err;
+use proxmox::http_err;
use proxmox::api::{ApiHandler, ApiMethod, HttpError};
-use proxmox::api::{RpcEnvironment, RpcEnvironmentType};
+use proxmox::api::{RpcEnvironment, RpcEnvironmentType, check_api_permission};
use proxmox::api::schema::{ObjectSchema, parse_simple_value, verify_json_object, parse_parameter_strings};
use super::environment::RestEnvironment;
use crate::auth_helpers::*;
use crate::tools;
+use crate::config::cached_user_info::CachedUserInfo;
extern "C" { fn tzset(); }
Ok(resp)
}
-fn get_index(username: Option<String>, token: Option<String>) -> Response<Body> {
+fn get_index(username: Option<String>, token: Option<String>, template: &Handlebars, parts: Parts) -> Response<Body> {
let nodename = proxmox::tools::nodename();
let username = username.unwrap_or_else(|| String::from(""));
let token = token.unwrap_or_else(|| String::from(""));
- let setup = json!({
- "Setup": { "auth_cookie_name": "PBSAuthCookie" },
+ let mut debug = false;
+
+ if let Some(query_str) = parts.uri.query() {
+ for (k, v) in form_urlencoded::parse(query_str.as_bytes()).into_owned() {
+ if k == "debug" && v == "1" || v == "true" {
+ debug = true;
+ }
+ }
+ }
+
+ let data = json!({
"NodeName": nodename,
"UserName": username,
"CSRFPreventionToken": token,
+ "debug": debug,
});
- let index = format!(r###"
-<!DOCTYPE html>
-<html>
- <head>
- <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
- <meta http-equiv="X-UA-Compatible" content="IE=edge">
- <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
- <title>Proxmox Backup Server</title>
- <link rel="icon" sizes="128x128" href="/images/logo-128.png" />
- <link rel="apple-touch-icon" sizes="128x128" href="/pve2/images/logo-128.png" />
- <link rel="stylesheet" type="text/css" href="/extjs/theme-crisp/resources/theme-crisp-all.css" />
- <link rel="stylesheet" type="text/css" href="/extjs/crisp/resources/charts-all.css" />
- <link rel="stylesheet" type="text/css" href="/fontawesome/css/font-awesome.css" />
- <script type='text/javascript'> function gettext(buf) {{ return buf; }} </script>
- <script type="text/javascript" src="/extjs/ext-all-debug.js"></script>
- <script type="text/javascript" src="/extjs/charts-debug.js"></script>
- <script type="text/javascript">
- Proxmox = {};
- </script>
- <script type="text/javascript" src="/widgettoolkit/proxmoxlib.js"></script>
- <script type="text/javascript" src="/extjs/locale/locale-en.js"></script>
- <script type="text/javascript">
- Ext.History.fieldid = 'x-history-field';
- </script>
- <script type="text/javascript" src="/js/proxmox-backup-gui.js"></script>
- </head>
- <body>
- <!-- Fields required for history management -->
- <form id="history-form" class="x-hidden">
- <input type="hidden" id="x-history-field"/>
- </form>
- </body>
-</html>
-"###, setup.to_string());
+ let mut ct = "text/html";
+
+ let index = match template.render("index", &data) {
+ Ok(index) => index,
+ Err(err) => {
+ ct = "text/plain";
+ format!("Error rendering template: {}", err.desc)
+ },
+ };
Response::builder()
.status(StatusCode::OK)
- .header(header::CONTENT_TYPE, "text/html")
+ .header(header::CONTENT_TYPE, ct)
.body(index.into())
.unwrap()
}
async fn handle_static_file_download(filename: PathBuf) -> Result<Response<Body>, Error> {
- tokio::fs::metadata(filename.clone())
+ let metadata = tokio::fs::metadata(filename.clone())
.map_err(|err| http_err!(BAD_REQUEST, format!("File access problems: {}", err)))
- .and_then(|metadata| async move {
- if metadata.len() < 1024*32 {
- simple_static_file_download(filename).await
- } else {
- chuncked_static_file_download(filename).await
- }
- })
- .await
+ .await?;
+
+ if metadata.len() < 1024*32 {
+ simple_static_file_download(filename).await
+ } else {
+ chuncked_static_file_download(filename).await
+ }
}
fn extract_auth_data(headers: &http::HeaderMap) -> (Option<String>, Option<String>) {
(ticket, token)
}
-fn check_auth(method: &hyper::Method, ticket: &Option<String>, token: &Option<String>) -> Result<String, Error> {
+fn check_auth(
+ method: &hyper::Method,
+ ticket: &Option<String>,
+ token: &Option<String>,
+ user_info: &CachedUserInfo,
+) -> Result<String, Error> {
let ticket_lifetime = tools::ticket::TICKET_LIFETIME;
None => bail!("missing ticket"),
};
+ if !user_info.is_active_user(&username) {
+ bail!("user account disabled or expired.");
+ }
+
if method != hyper::Method::GET {
if let Some(token) = token {
println!("CSRF prevention token: {:?}", token);
let env_type = api.env_type();
let mut rpcenv = RestEnvironment::new(env_type);
+ let user_info = CachedUserInfo::new()?;
+
let delay_unauth_time = std::time::Instant::now() + std::time::Duration::from_millis(3000);
+ let access_forbidden_time = std::time::Instant::now() + std::time::Duration::from_millis(500);
if comp_len >= 1 && components[0] == "api2" {
let mut uri_param = HashMap::new();
- if comp_len == 4 && components[2] == "access" && components[3] == "ticket" {
+ if comp_len == 4 && components[2] == "access" && (
+ (components[3] == "ticket" && method == hyper::Method::POST) ||
+ (components[3] == "domains" && method == hyper::Method::GET)
+ ) {
// explicitly allow those calls without auth
} else {
let (ticket, token) = extract_auth_data(&parts.headers);
- match check_auth(&method, &ticket, &token) {
- Ok(username) => {
-
- // fixme: check permissions
-
- rpcenv.set_user(Some(username));
- }
+ match check_auth(&method, &ticket, &token, &user_info) {
+ Ok(username) => rpcenv.set_user(Some(username)),
Err(err) => {
// always delay unauthorized calls by 3 seconds (from start of request)
- let err = http_err!(UNAUTHORIZED, format!("permission check failed - {}", err));
+ let err = http_err!(UNAUTHORIZED, format!("authentication failed - {}", err));
tokio::time::delay_until(Instant::from_std(delay_unauth_time)).await;
return Ok((formatter.format_error)(err));
}
return Ok((formatter.format_error)(err));
}
Some(api_method) => {
- if api_method.protected && env_type == RpcEnvironmentType::PUBLIC {
- return proxy_protected_request(api_method, parts, body).await;
+ let user = rpcenv.get_user();
+ if !check_api_permission(api_method.access.permission, user.as_deref(), &uri_param, user_info.as_ref()) {
+ let err = http_err!(FORBIDDEN, format!("permission check failed"));
+ tokio::time::delay_until(Instant::from_std(access_forbidden_time)).await;
+ return Ok((formatter.format_error)(err));
+ }
+
+ let result = if api_method.protected && env_type == RpcEnvironmentType::PUBLIC {
+ proxy_protected_request(api_method, parts, body).await
} else {
- return handle_api_request(rpcenv, api_method, formatter, parts, body, uri_param).await;
+ handle_api_request(rpcenv, api_method, formatter, parts, body, uri_param).await
+ };
+
+ if let Err(err) = result {
+ return Ok((formatter.format_error)(err));
}
+ return result;
}
}
+
}
} else {
// not Auth required for accessing files!
if comp_len == 0 {
let (ticket, token) = extract_auth_data(&parts.headers);
if ticket != None {
- match check_auth(&method, &ticket, &token) {
+ match check_auth(&method, &ticket, &token, &user_info) {
Ok(username) => {
let new_token = assemble_csrf_prevention_token(csrf_secret(), &username);
- return Ok(get_index(Some(username), Some(new_token)));
+ return Ok(get_index(Some(username), Some(new_token), &api.templates, parts));
}
_ => {
tokio::time::delay_until(Instant::from_std(delay_unauth_time)).await;
- return Ok(get_index(None, None));
+ return Ok(get_index(None, None, &api.templates, parts));
}
}
} else {
- return Ok(get_index(None, None));
+ return Ok(get_index(None, None, &api.templates, parts));
}
} else {
let filename = api.find_alias(&components);