2019-08-26 11:33:38 +00:00
|
|
|
use std::collections::HashMap;
|
2019-12-12 14:27:07 +00:00
|
|
|
use std::future::Future;
|
2019-10-26 09:36:01 +00:00
|
|
|
use std::hash::BuildHasher;
|
2018-12-02 10:00:52 +00:00
|
|
|
use std::path::{Path, PathBuf};
|
2019-08-26 11:33:38 +00:00
|
|
|
use std::pin::Pin;
|
2018-11-15 07:18:48 +00:00
|
|
|
use std::sync::Arc;
|
2019-08-26 11:33:38 +00:00
|
|
|
use std::task::{Context, Poll};
|
2018-11-15 07:18:48 +00:00
|
|
|
|
2020-04-17 12:11:25 +00:00
|
|
|
use anyhow::{bail, format_err, Error};
|
2019-11-22 12:02:05 +00:00
|
|
|
use futures::future::{self, FutureExt, TryFutureExt};
|
2019-08-26 11:33:38 +00:00
|
|
|
use futures::stream::TryStreamExt;
|
2020-10-15 15:43:42 +00:00
|
|
|
use hyper::header::{self, HeaderMap};
|
2019-08-26 11:33:38 +00:00
|
|
|
use hyper::http::request::Parts;
|
|
|
|
use hyper::{Body, Request, Response, StatusCode};
|
2020-10-15 15:43:42 +00:00
|
|
|
use lazy_static::lazy_static;
|
2018-12-01 12:37:49 +00:00
|
|
|
use serde_json::{json, Value};
|
2018-11-15 07:18:48 +00:00
|
|
|
use tokio::fs::File;
|
2019-12-12 14:27:07 +00:00
|
|
|
use tokio::time::Instant;
|
2019-08-26 11:33:38 +00:00
|
|
|
use url::form_urlencoded;
|
2020-10-15 15:43:42 +00:00
|
|
|
use regex::Regex;
|
2018-11-15 07:18:48 +00:00
|
|
|
|
2020-01-21 11:28:01 +00:00
|
|
|
use proxmox::http_err;
|
2020-10-02 11:04:08 +00:00
|
|
|
use proxmox::api::{
|
|
|
|
ApiHandler,
|
|
|
|
ApiMethod,
|
|
|
|
HttpError,
|
2020-10-02 11:17:12 +00:00
|
|
|
Permission,
|
2020-10-02 11:04:08 +00:00
|
|
|
RpcEnvironment,
|
|
|
|
RpcEnvironmentType,
|
|
|
|
check_api_permission,
|
|
|
|
};
|
|
|
|
use proxmox::api::schema::{
|
|
|
|
ObjectSchema,
|
|
|
|
parse_parameter_strings,
|
|
|
|
parse_simple_value,
|
|
|
|
verify_json_object,
|
|
|
|
};
|
2019-11-21 13:36:28 +00:00
|
|
|
|
2019-08-26 11:33:38 +00:00
|
|
|
use super::environment::RestEnvironment;
|
|
|
|
use super::formatter::*;
|
2019-11-22 08:23:03 +00:00
|
|
|
use super::ApiConfig;
|
|
|
|
|
2019-08-26 11:33:38 +00:00
|
|
|
use crate::auth_helpers::*;
|
2020-08-06 13:46:01 +00:00
|
|
|
use crate::api2::types::Userid;
|
2019-08-26 11:33:38 +00:00
|
|
|
use crate::tools;
|
2020-08-12 10:05:52 +00:00
|
|
|
use crate::tools::ticket::Ticket;
|
2020-04-16 08:01:59 +00:00
|
|
|
use crate::config::cached_user_info::CachedUserInfo;
|
2018-11-15 07:18:48 +00:00
|
|
|
|
2019-02-01 08:54:56 +00:00
|
|
|
extern "C" { fn tzset(); }
|
|
|
|
|
2018-11-15 09:18:01 +00:00
|
|
|
pub struct RestServer {
|
|
|
|
pub api_config: Arc<ApiConfig>,
|
|
|
|
}
|
|
|
|
|
server: rest: implement max URI path and query length request limits
Add a generous limit now and return the correct error (414 URI Too
Long). Otherwise we could to pretty larger GET requests, 64 KiB and
possible bigger (at 64 KiB my simple curl test failed due to
shell/curl limitations).
For now allow a 3072 characters as combined length of URI path and
query.
This is conform with the HTTP/1.1 RFCs (e.g., RFC 7231, 6.5.12 and
RFC 2616, 3.2.1) which do not specify any limits, upper or lower, but
require that all server accessible resources mus be reachable without
getting 414, which is normally fulfilled as we have various length
limits for stuff which could be in an URI, in place, e.g.:
* user id: max. 64 chars
* datastore: max. 32 chars
The only known problematic API endpoint is the catalog one, used in
the GUI's pxar file browser:
GET /api2/json/admin/datastore/<id>/catalog?..&filepath=<path>
The <path> is the encoded archive path, and can be arbitrary long.
But, this is a flawed design, as even without this new limit one can
easily generate archives which cannot be browsed anymore, as hyper
only accepts requests with max. 64 KiB in the URI.
So rather, we should move that to a GET-as-POST call, which has no
such limitations (and would not need to base32 encode the path).
Note: This change was inspired by adding a request access log, which
profits from such limits as we can then rely on certain atomicity
guarantees when writing requests to the log.
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2020-10-15 15:49:16 +00:00
|
|
|
const MAX_URI_QUERY_LENGTH: usize = 3072;
|
|
|
|
|
2018-11-15 09:18:01 +00:00
|
|
|
impl RestServer {
|
|
|
|
|
|
|
|
pub fn new(api_config: ApiConfig) -> Self {
|
|
|
|
Self { api_config: Arc::new(api_config) }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-26 11:33:38 +00:00
|
|
|
impl tower_service::Service<&tokio_openssl::SslStream<tokio::net::TcpStream>> for RestServer {
|
|
|
|
type Response = ApiService;
|
2019-07-03 09:54:35 +00:00
|
|
|
type Error = Error;
|
2019-08-26 11:33:38 +00:00
|
|
|
type Future = Pin<Box<dyn Future<Output = Result<ApiService, Error>> + Send>>;
|
|
|
|
|
|
|
|
fn poll_ready(&mut self, _cx: &mut Context) -> Poll<Result<(), Self::Error>> {
|
|
|
|
Poll::Ready(Ok(()))
|
|
|
|
}
|
|
|
|
|
|
|
|
fn call(&mut self, ctx: &tokio_openssl::SslStream<tokio::net::TcpStream>) -> Self::Future {
|
|
|
|
match ctx.get_ref().peer_addr() {
|
2019-07-03 10:00:43 +00:00
|
|
|
Err(err) => {
|
2019-08-26 11:33:38 +00:00
|
|
|
future::err(format_err!("unable to get peer address - {}", err)).boxed()
|
2019-07-03 10:00:43 +00:00
|
|
|
}
|
|
|
|
Ok(peer) => {
|
2019-08-26 11:33:38 +00:00
|
|
|
future::ok(ApiService { peer, api_config: self.api_config.clone() }).boxed()
|
2019-07-03 10:00:43 +00:00
|
|
|
}
|
|
|
|
}
|
2018-11-15 09:18:01 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-26 11:33:38 +00:00
|
|
|
impl tower_service::Service<&tokio::net::TcpStream> for RestServer {
|
|
|
|
type Response = ApiService;
|
2019-07-03 09:54:35 +00:00
|
|
|
type Error = Error;
|
2019-08-26 11:33:38 +00:00
|
|
|
type Future = Pin<Box<dyn Future<Output = Result<ApiService, Error>> + Send>>;
|
|
|
|
|
|
|
|
fn poll_ready(&mut self, _cx: &mut Context) -> Poll<Result<(), Self::Error>> {
|
|
|
|
Poll::Ready(Ok(()))
|
|
|
|
}
|
|
|
|
|
|
|
|
fn call(&mut self, ctx: &tokio::net::TcpStream) -> Self::Future {
|
2019-07-03 10:00:43 +00:00
|
|
|
match ctx.peer_addr() {
|
|
|
|
Err(err) => {
|
2019-08-26 11:33:38 +00:00
|
|
|
future::err(format_err!("unable to get peer address - {}", err)).boxed()
|
2019-07-03 10:00:43 +00:00
|
|
|
}
|
|
|
|
Ok(peer) => {
|
2019-08-26 11:33:38 +00:00
|
|
|
future::ok(ApiService { peer, api_config: self.api_config.clone() }).boxed()
|
2019-07-03 10:00:43 +00:00
|
|
|
}
|
|
|
|
}
|
2019-07-03 09:54:35 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-11-15 09:18:01 +00:00
|
|
|
pub struct ApiService {
|
2019-07-03 09:54:35 +00:00
|
|
|
pub peer: std::net::SocketAddr,
|
2018-11-15 09:18:01 +00:00
|
|
|
pub api_config: Arc<ApiConfig>,
|
|
|
|
}
|
|
|
|
|
2019-07-03 09:54:35 +00:00
|
|
|
fn log_response(
|
|
|
|
peer: &std::net::SocketAddr,
|
|
|
|
method: hyper::Method,
|
|
|
|
path: &str,
|
|
|
|
resp: &Response<Body>,
|
|
|
|
) {
|
2019-02-14 12:28:41 +00:00
|
|
|
|
2019-02-15 09:16:12 +00:00
|
|
|
if resp.extensions().get::<NoLogExtension>().is_some() { return; };
|
2019-02-14 12:28:41 +00:00
|
|
|
|
server: rest: implement max URI path and query length request limits
Add a generous limit now and return the correct error (414 URI Too
Long). Otherwise we could to pretty larger GET requests, 64 KiB and
possible bigger (at 64 KiB my simple curl test failed due to
shell/curl limitations).
For now allow a 3072 characters as combined length of URI path and
query.
This is conform with the HTTP/1.1 RFCs (e.g., RFC 7231, 6.5.12 and
RFC 2616, 3.2.1) which do not specify any limits, upper or lower, but
require that all server accessible resources mus be reachable without
getting 414, which is normally fulfilled as we have various length
limits for stuff which could be in an URI, in place, e.g.:
* user id: max. 64 chars
* datastore: max. 32 chars
The only known problematic API endpoint is the catalog one, used in
the GUI's pxar file browser:
GET /api2/json/admin/datastore/<id>/catalog?..&filepath=<path>
The <path> is the encoded archive path, and can be arbitrary long.
But, this is a flawed design, as even without this new limit one can
easily generate archives which cannot be browsed anymore, as hyper
only accepts requests with max. 64 KiB in the URI.
So rather, we should move that to a GET-as-POST call, which has no
such limitations (and would not need to base32 encode the path).
Note: This change was inspired by adding a request access log, which
profits from such limits as we can then rely on certain atomicity
guarantees when writing requests to the log.
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2020-10-15 15:49:16 +00:00
|
|
|
// we also log URL-to-long requests, so avoid message bigger than PIPE_BUF (4k on Linux)
|
|
|
|
// to profit from atomicty guarantees for O_APPEND opened logfiles
|
|
|
|
let path = &path[..MAX_URI_QUERY_LENGTH.min(path.len())];
|
|
|
|
|
2019-02-15 09:16:12 +00:00
|
|
|
let status = resp.status();
|
2019-02-14 12:28:41 +00:00
|
|
|
|
2019-05-14 04:23:22 +00:00
|
|
|
if !(status.is_success() || status.is_informational()) {
|
2019-02-15 09:16:12 +00:00
|
|
|
let reason = status.canonical_reason().unwrap_or("unknown reason");
|
2019-02-15 08:55:12 +00:00
|
|
|
|
2019-02-15 09:16:12 +00:00
|
|
|
let mut message = "request failed";
|
|
|
|
if let Some(data) = resp.extensions().get::<ErrorMessageExtension>() {
|
|
|
|
message = &data.0;
|
2019-02-14 12:07:34 +00:00
|
|
|
}
|
2019-02-15 09:16:12 +00:00
|
|
|
|
2019-07-03 09:54:35 +00:00
|
|
|
log::error!("{} {}: {} {}: [client {}] {}", method.as_str(), path, status.as_str(), reason, peer, message);
|
2019-02-14 12:07:34 +00:00
|
|
|
}
|
|
|
|
}
|
2018-11-15 09:18:01 +00:00
|
|
|
|
2020-10-15 15:43:42 +00:00
|
|
|
fn get_proxied_peer(headers: &HeaderMap) -> Option<std::net::SocketAddr> {
|
|
|
|
lazy_static! {
|
|
|
|
static ref RE: Regex = Regex::new(r#"for="([^"]+)""#).unwrap();
|
|
|
|
}
|
|
|
|
let forwarded = headers.get(header::FORWARDED)?.to_str().ok()?;
|
|
|
|
let capture = RE.captures(&forwarded)?;
|
|
|
|
let rhost = capture.get(1)?.as_str();
|
|
|
|
|
|
|
|
rhost.parse().ok()
|
|
|
|
}
|
|
|
|
|
2019-08-26 11:33:38 +00:00
|
|
|
impl tower_service::Service<Request<Body>> for ApiService {
|
|
|
|
type Response = Response<Body>;
|
2019-07-03 09:54:35 +00:00
|
|
|
type Error = Error;
|
2019-08-26 11:33:38 +00:00
|
|
|
type Future = Pin<Box<dyn Future<Output = Result<Response<Body>, Self::Error>> + Send>>;
|
|
|
|
|
|
|
|
fn poll_ready(&mut self, _cx: &mut Context) -> Poll<Result<(), Self::Error>> {
|
|
|
|
Poll::Ready(Ok(()))
|
|
|
|
}
|
2018-11-15 09:18:01 +00:00
|
|
|
|
2019-08-26 11:33:38 +00:00
|
|
|
fn call(&mut self, req: Request<Body>) -> Self::Future {
|
2019-02-14 12:07:34 +00:00
|
|
|
let path = req.uri().path().to_owned();
|
2019-02-15 09:16:12 +00:00
|
|
|
let method = req.method().clone();
|
|
|
|
|
2020-10-12 08:36:32 +00:00
|
|
|
let config = Arc::clone(&self.api_config);
|
2020-10-15 15:43:42 +00:00
|
|
|
let peer = match get_proxied_peer(req.headers()) {
|
|
|
|
Some(proxied_peer) => proxied_peer,
|
|
|
|
None => self.peer,
|
|
|
|
};
|
2020-10-12 08:36:32 +00:00
|
|
|
async move {
|
2020-10-15 15:43:42 +00:00
|
|
|
let response = match handle_request(config, req, &peer).await {
|
2020-10-15 07:03:54 +00:00
|
|
|
Ok(response) => response,
|
2018-11-15 09:18:01 +00:00
|
|
|
Err(err) => {
|
2020-10-15 07:03:54 +00:00
|
|
|
let (err, code) = match err.downcast_ref::<HttpError>() {
|
|
|
|
Some(apierr) => (apierr.message.clone(), apierr.code),
|
|
|
|
_ => (err.to_string(), StatusCode::BAD_REQUEST),
|
|
|
|
};
|
|
|
|
Response::builder().status(code).body(err.into())?
|
2018-11-15 09:18:01 +00:00
|
|
|
}
|
2020-10-15 07:03:54 +00:00
|
|
|
};
|
|
|
|
log_response(&peer, method, &path, &response);
|
|
|
|
Ok(response)
|
2020-10-12 08:36:32 +00:00
|
|
|
}
|
|
|
|
.boxed()
|
2018-11-15 09:18:01 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-11-22 17:44:14 +00:00
|
|
|
fn parse_query_parameters<S: 'static + BuildHasher + Send>(
|
|
|
|
param_schema: &ObjectSchema,
|
|
|
|
form: &str, // x-www-form-urlencoded body data
|
|
|
|
parts: &Parts,
|
|
|
|
uri_param: &HashMap<String, String, S>,
|
|
|
|
) -> Result<Value, Error> {
|
|
|
|
|
|
|
|
let mut param_list: Vec<(String, String)> = vec![];
|
|
|
|
|
|
|
|
if !form.is_empty() {
|
|
|
|
for (k, v) in form_urlencoded::parse(form.as_bytes()).into_owned() {
|
|
|
|
param_list.push((k, v));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(query_str) = parts.uri.query() {
|
|
|
|
for (k, v) in form_urlencoded::parse(query_str.as_bytes()).into_owned() {
|
|
|
|
if k == "_dc" { continue; } // skip extjs "disable cache" parameter
|
|
|
|
param_list.push((k, v));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (k, v) in uri_param {
|
|
|
|
param_list.push((k.clone(), v.clone()));
|
|
|
|
}
|
|
|
|
|
|
|
|
let params = parse_parameter_strings(¶m_list, param_schema, true)?;
|
|
|
|
|
|
|
|
Ok(params)
|
|
|
|
}
|
|
|
|
|
2019-11-22 16:24:16 +00:00
|
|
|
async fn get_request_parameters<S: 'static + BuildHasher + Send>(
|
2019-11-22 16:22:07 +00:00
|
|
|
param_schema: &ObjectSchema,
|
2018-11-15 07:18:48 +00:00
|
|
|
parts: Parts,
|
|
|
|
req_body: Body,
|
2019-10-26 09:36:01 +00:00
|
|
|
uri_param: HashMap<String, String, S>,
|
2019-11-22 12:02:05 +00:00
|
|
|
) -> Result<Value, Error> {
|
|
|
|
|
2019-02-27 11:12:00 +00:00
|
|
|
let mut is_json = false;
|
|
|
|
|
|
|
|
if let Some(value) = parts.headers.get(header::CONTENT_TYPE) {
|
2019-03-19 11:50:15 +00:00
|
|
|
match value.to_str().map(|v| v.split(';').next()) {
|
|
|
|
Ok(Some("application/x-www-form-urlencoded")) => {
|
|
|
|
is_json = false;
|
|
|
|
}
|
|
|
|
Ok(Some("application/json")) => {
|
|
|
|
is_json = true;
|
|
|
|
}
|
2019-11-22 12:02:05 +00:00
|
|
|
_ => bail!("unsupported content type {:?}", value.to_str()),
|
2019-02-27 11:12:00 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-11-22 12:02:05 +00:00
|
|
|
let body = req_body
|
2020-07-29 07:38:11 +00:00
|
|
|
.map_err(|err| http_err!(BAD_REQUEST, "Promlems reading request body: {}", err))
|
2019-08-26 11:33:38 +00:00
|
|
|
.try_fold(Vec::new(), |mut acc, chunk| async move {
|
2018-11-15 07:18:48 +00:00
|
|
|
if acc.len() + chunk.len() < 64*1024 { //fimxe: max request body size?
|
|
|
|
acc.extend_from_slice(&*chunk);
|
|
|
|
Ok(acc)
|
2019-08-26 11:33:38 +00:00
|
|
|
} else {
|
2020-07-29 07:38:11 +00:00
|
|
|
Err(http_err!(BAD_REQUEST, "Request body too large"))
|
2018-11-15 07:18:48 +00:00
|
|
|
}
|
2019-11-22 12:02:05 +00:00
|
|
|
}).await?;
|
2018-11-15 07:18:48 +00:00
|
|
|
|
2019-11-22 17:44:14 +00:00
|
|
|
let utf8_data = std::str::from_utf8(&body)
|
2019-11-22 12:02:05 +00:00
|
|
|
.map_err(|err| format_err!("Request body not uft8: {}", err))?;
|
2019-02-27 11:12:00 +00:00
|
|
|
|
2019-11-22 12:02:05 +00:00
|
|
|
if is_json {
|
2019-11-22 17:44:14 +00:00
|
|
|
let mut params: Value = serde_json::from_str(utf8_data)?;
|
2019-11-22 12:02:05 +00:00
|
|
|
for (k, v) in uri_param {
|
2019-11-22 16:22:07 +00:00
|
|
|
if let Some((_optional, prop_schema)) = param_schema.lookup(&k) {
|
2019-11-22 12:02:05 +00:00
|
|
|
params[&k] = parse_simple_value(&v, prop_schema)?;
|
2018-11-15 07:18:48 +00:00
|
|
|
}
|
2019-11-22 12:02:05 +00:00
|
|
|
}
|
2019-11-22 16:22:07 +00:00
|
|
|
verify_json_object(¶ms, param_schema)?;
|
2019-11-22 12:02:05 +00:00
|
|
|
return Ok(params);
|
2019-11-22 17:44:14 +00:00
|
|
|
} else {
|
|
|
|
parse_query_parameters(param_schema, utf8_data, &parts, &uri_param)
|
2019-11-22 12:02:05 +00:00
|
|
|
}
|
2018-11-15 07:18:48 +00:00
|
|
|
}
|
|
|
|
|
2019-02-14 15:04:24 +00:00
|
|
|
struct NoLogExtension();
|
|
|
|
|
2019-11-22 12:02:05 +00:00
|
|
|
async fn proxy_protected_request(
|
2019-02-01 08:54:56 +00:00
|
|
|
info: &'static ApiMethod,
|
2019-01-28 17:06:42 +00:00
|
|
|
mut parts: Parts,
|
2019-01-28 16:30:39 +00:00
|
|
|
req_body: Body,
|
2020-10-15 15:43:42 +00:00
|
|
|
peer: &std::net::SocketAddr,
|
2019-11-22 12:02:05 +00:00
|
|
|
) -> Result<Response<Body>, Error> {
|
2019-01-28 16:30:39 +00:00
|
|
|
|
2019-01-28 17:06:42 +00:00
|
|
|
let mut uri_parts = parts.uri.clone().into_parts();
|
|
|
|
|
|
|
|
uri_parts.scheme = Some(http::uri::Scheme::HTTP);
|
|
|
|
uri_parts.authority = Some(http::uri::Authority::from_static("127.0.0.1:82"));
|
|
|
|
let new_uri = http::Uri::from_parts(uri_parts).unwrap();
|
|
|
|
|
|
|
|
parts.uri = new_uri;
|
|
|
|
|
2020-10-15 15:43:42 +00:00
|
|
|
let mut request = Request::from_parts(parts, req_body);
|
|
|
|
request
|
|
|
|
.headers_mut()
|
|
|
|
.insert(header::FORWARDED, format!("for=\"{}\";", peer).parse().unwrap());
|
2019-01-28 17:06:42 +00:00
|
|
|
|
2019-11-22 12:02:05 +00:00
|
|
|
let reload_timezone = info.reload_timezone;
|
|
|
|
|
2019-01-28 17:06:42 +00:00
|
|
|
let resp = hyper::client::Client::new()
|
|
|
|
.request(request)
|
2019-02-18 12:21:25 +00:00
|
|
|
.map_err(Error::from)
|
2019-08-26 11:33:38 +00:00
|
|
|
.map_ok(|mut resp| {
|
2019-02-18 05:54:12 +00:00
|
|
|
resp.extensions_mut().insert(NoLogExtension());
|
2019-02-14 12:28:41 +00:00
|
|
|
resp
|
2019-11-22 12:02:05 +00:00
|
|
|
})
|
|
|
|
.await?;
|
2019-01-28 17:06:42 +00:00
|
|
|
|
2019-11-22 12:02:05 +00:00
|
|
|
if reload_timezone { unsafe { tzset(); } }
|
2019-02-18 05:54:12 +00:00
|
|
|
|
2019-11-22 12:02:05 +00:00
|
|
|
Ok(resp)
|
2019-01-28 16:30:39 +00:00
|
|
|
}
|
|
|
|
|
2019-11-22 17:44:14 +00:00
|
|
|
pub async fn handle_api_request<Env: RpcEnvironment, S: 'static + BuildHasher + Send>(
|
2019-05-08 09:09:01 +00:00
|
|
|
mut rpcenv: Env,
|
2018-11-15 09:25:59 +00:00
|
|
|
info: &'static ApiMethod,
|
2018-12-05 11:42:25 +00:00
|
|
|
formatter: &'static OutputFormatter,
|
2018-11-15 07:18:48 +00:00
|
|
|
parts: Parts,
|
|
|
|
req_body: Body,
|
2019-10-26 09:36:01 +00:00
|
|
|
uri_param: HashMap<String, String, S>,
|
2019-11-22 12:02:05 +00:00
|
|
|
) -> Result<Response<Body>, Error> {
|
|
|
|
|
2019-01-31 13:34:21 +00:00
|
|
|
let delay_unauth_time = std::time::Instant::now() + std::time::Duration::from_millis(3000);
|
|
|
|
|
2019-11-22 17:44:14 +00:00
|
|
|
let result = match info.handler {
|
2019-11-23 08:03:21 +00:00
|
|
|
ApiHandler::AsyncHttp(handler) => {
|
2019-11-22 17:44:14 +00:00
|
|
|
let params = parse_query_parameters(info.parameters, "", &parts, &uri_param)?;
|
|
|
|
(handler)(parts, req_body, params, info, Box::new(rpcenv)).await
|
|
|
|
}
|
|
|
|
ApiHandler::Sync(handler) => {
|
|
|
|
let params = get_request_parameters(info.parameters, parts, req_body, uri_param).await?;
|
|
|
|
(handler)(params, info, &mut rpcenv)
|
|
|
|
.map(|data| (formatter.format_data)(data, &rpcenv))
|
|
|
|
}
|
2019-12-16 08:59:45 +00:00
|
|
|
ApiHandler::Async(handler) => {
|
|
|
|
let params = get_request_parameters(info.parameters, parts, req_body, uri_param).await?;
|
|
|
|
(handler)(params, info, &mut rpcenv)
|
|
|
|
.await
|
|
|
|
.map(|data| (formatter.format_data)(data, &rpcenv))
|
|
|
|
}
|
2019-11-22 17:44:14 +00:00
|
|
|
};
|
2019-01-31 13:34:21 +00:00
|
|
|
|
2019-11-22 17:44:14 +00:00
|
|
|
let resp = match result {
|
|
|
|
Ok(resp) => resp,
|
2019-11-22 12:02:05 +00:00
|
|
|
Err(err) => {
|
|
|
|
if let Some(httperr) = err.downcast_ref::<HttpError>() {
|
|
|
|
if httperr.code == StatusCode::UNAUTHORIZED {
|
2019-12-12 14:27:07 +00:00
|
|
|
tokio::time::delay_until(Instant::from_std(delay_unauth_time)).await;
|
2019-11-22 12:02:05 +00:00
|
|
|
}
|
2019-02-01 08:54:56 +00:00
|
|
|
}
|
2019-11-22 12:02:05 +00:00
|
|
|
(formatter.format_error)(err)
|
|
|
|
}
|
|
|
|
};
|
2019-02-01 08:54:56 +00:00
|
|
|
|
2019-11-22 12:02:05 +00:00
|
|
|
if info.reload_timezone { unsafe { tzset(); } }
|
|
|
|
|
|
|
|
Ok(resp)
|
2019-01-14 11:26:04 +00:00
|
|
|
}
|
|
|
|
|
2020-09-07 12:33:05 +00:00
|
|
|
fn get_index(
|
|
|
|
userid: Option<Userid>,
|
2020-10-12 16:39:45 +00:00
|
|
|
csrf_token: Option<String>,
|
2020-09-07 12:33:05 +00:00
|
|
|
language: Option<String>,
|
|
|
|
api: &Arc<ApiConfig>,
|
|
|
|
parts: Parts,
|
|
|
|
) -> Response<Body> {
|
2018-12-01 12:37:49 +00:00
|
|
|
|
2019-08-03 15:06:23 +00:00
|
|
|
let nodename = proxmox::tools::nodename();
|
2020-10-12 16:39:45 +00:00
|
|
|
let user = userid.as_ref().map(|u| u.as_str()).unwrap_or("");
|
2019-02-17 17:50:40 +00:00
|
|
|
|
2020-10-12 16:39:45 +00:00
|
|
|
let csrf_token = csrf_token.unwrap_or_else(|| String::from(""));
|
2018-12-01 12:37:49 +00:00
|
|
|
|
2020-04-29 09:59:31 +00:00
|
|
|
let mut debug = false;
|
2020-07-21 09:10:39 +00:00
|
|
|
let mut template_file = "index";
|
2020-04-29 09:59:31 +00:00
|
|
|
|
|
|
|
if let Some(query_str) = parts.uri.query() {
|
|
|
|
for (k, v) in form_urlencoded::parse(query_str.as_bytes()).into_owned() {
|
2020-06-25 08:45:49 +00:00
|
|
|
if k == "debug" && v != "0" && v != "false" {
|
2020-04-29 09:59:31 +00:00
|
|
|
debug = true;
|
2020-07-21 09:10:39 +00:00
|
|
|
} else if k == "console" {
|
|
|
|
template_file = "console";
|
2020-04-29 09:59:31 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-07 12:33:05 +00:00
|
|
|
let mut lang = String::from("");
|
|
|
|
if let Some(language) = language {
|
|
|
|
if Path::new(&format!("/usr/share/pbs-i18n/pbs-lang-{}.js", language)).exists() {
|
|
|
|
lang = language;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-29 09:59:31 +00:00
|
|
|
let data = json!({
|
2018-12-01 12:37:49 +00:00
|
|
|
"NodeName": nodename,
|
2020-10-12 16:39:45 +00:00
|
|
|
"UserName": user,
|
|
|
|
"CSRFPreventionToken": csrf_token,
|
2020-09-07 12:33:05 +00:00
|
|
|
"language": lang,
|
2020-04-29 09:59:31 +00:00
|
|
|
"debug": debug,
|
2018-12-01 12:37:49 +00:00
|
|
|
});
|
|
|
|
|
2020-10-12 08:38:13 +00:00
|
|
|
let (ct, index) = match api.render_template(template_file, &data) {
|
|
|
|
Ok(index) => ("text/html", index),
|
2020-04-29 09:59:31 +00:00
|
|
|
Err(err) => {
|
2020-10-12 08:38:13 +00:00
|
|
|
("text/plain", format!("Error rendering template: {}", err))
|
2020-07-21 09:10:39 +00:00
|
|
|
}
|
2020-04-29 09:59:31 +00:00
|
|
|
};
|
2018-12-01 12:37:49 +00:00
|
|
|
|
2019-02-17 17:50:40 +00:00
|
|
|
Response::builder()
|
2019-01-23 11:49:10 +00:00
|
|
|
.status(StatusCode::OK)
|
2020-04-29 09:59:31 +00:00
|
|
|
.header(header::CONTENT_TYPE, ct)
|
2019-01-23 11:49:10 +00:00
|
|
|
.body(index.into())
|
2019-02-17 17:50:40 +00:00
|
|
|
.unwrap()
|
2018-12-01 12:37:49 +00:00
|
|
|
}
|
|
|
|
|
2018-12-02 10:00:52 +00:00
|
|
|
fn extension_to_content_type(filename: &Path) -> (&'static str, bool) {
|
|
|
|
|
|
|
|
if let Some(ext) = filename.extension().and_then(|osstr| osstr.to_str()) {
|
|
|
|
return match ext {
|
|
|
|
"css" => ("text/css", false),
|
|
|
|
"html" => ("text/html", false),
|
|
|
|
"js" => ("application/javascript", false),
|
|
|
|
"json" => ("application/json", false),
|
|
|
|
"map" => ("application/json", false),
|
|
|
|
"png" => ("image/png", true),
|
|
|
|
"ico" => ("image/x-icon", true),
|
|
|
|
"gif" => ("image/gif", true),
|
|
|
|
"svg" => ("image/svg+xml", false),
|
|
|
|
"jar" => ("application/java-archive", true),
|
|
|
|
"woff" => ("application/font-woff", true),
|
|
|
|
"woff2" => ("application/font-woff2", true),
|
|
|
|
"ttf" => ("application/font-snft", true),
|
|
|
|
"pdf" => ("application/pdf", true),
|
|
|
|
"epub" => ("application/epub+zip", true),
|
|
|
|
"mp3" => ("audio/mpeg", true),
|
|
|
|
"oga" => ("audio/ogg", true),
|
|
|
|
"tgz" => ("application/x-compressed-tar", true),
|
|
|
|
_ => ("application/octet-stream", false),
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
("application/octet-stream", false)
|
|
|
|
}
|
|
|
|
|
2019-08-26 11:33:38 +00:00
|
|
|
async fn simple_static_file_download(filename: PathBuf) -> Result<Response<Body>, Error> {
|
2018-11-15 07:18:48 +00:00
|
|
|
|
2018-12-02 10:00:52 +00:00
|
|
|
let (content_type, _nocomp) = extension_to_content_type(&filename);
|
|
|
|
|
2019-08-26 11:33:38 +00:00
|
|
|
use tokio::io::AsyncReadExt;
|
2018-11-15 07:18:48 +00:00
|
|
|
|
2019-08-26 11:33:38 +00:00
|
|
|
let mut file = File::open(filename)
|
|
|
|
.await
|
2020-07-29 07:38:11 +00:00
|
|
|
.map_err(|err| http_err!(BAD_REQUEST, "File open failed: {}", err))?;
|
2018-11-15 07:18:48 +00:00
|
|
|
|
2019-08-26 11:33:38 +00:00
|
|
|
let mut data: Vec<u8> = Vec::new();
|
|
|
|
file.read_to_end(&mut data)
|
|
|
|
.await
|
2020-07-29 07:38:11 +00:00
|
|
|
.map_err(|err| http_err!(BAD_REQUEST, "File read failed: {}", err))?;
|
2019-08-26 11:33:38 +00:00
|
|
|
|
|
|
|
let mut response = Response::new(data.into());
|
|
|
|
response.headers_mut().insert(
|
|
|
|
header::CONTENT_TYPE,
|
|
|
|
header::HeaderValue::from_static(content_type));
|
|
|
|
Ok(response)
|
|
|
|
}
|
|
|
|
|
|
|
|
async fn chuncked_static_file_download(filename: PathBuf) -> Result<Response<Body>, Error> {
|
2018-12-02 10:00:52 +00:00
|
|
|
let (content_type, _nocomp) = extension_to_content_type(&filename);
|
|
|
|
|
2019-08-26 11:33:38 +00:00
|
|
|
let file = File::open(filename)
|
|
|
|
.await
|
2020-07-29 07:38:11 +00:00
|
|
|
.map_err(|err| http_err!(BAD_REQUEST, "File open failed: {}", err))?;
|
2019-08-26 11:33:38 +00:00
|
|
|
|
2019-12-12 14:27:07 +00:00
|
|
|
let payload = tokio_util::codec::FramedRead::new(file, tokio_util::codec::BytesCodec::new())
|
|
|
|
.map_ok(|bytes| hyper::body::Bytes::from(bytes.freeze()));
|
2019-08-26 11:33:38 +00:00
|
|
|
let body = Body::wrap_stream(payload);
|
|
|
|
|
|
|
|
// fixme: set other headers ?
|
|
|
|
Ok(Response::builder()
|
|
|
|
.status(StatusCode::OK)
|
|
|
|
.header(header::CONTENT_TYPE, content_type)
|
|
|
|
.body(body)
|
|
|
|
.unwrap()
|
|
|
|
)
|
2018-11-15 07:18:48 +00:00
|
|
|
}
|
|
|
|
|
2019-11-22 12:02:05 +00:00
|
|
|
async fn handle_static_file_download(filename: PathBuf) -> Result<Response<Body>, Error> {
|
2018-11-15 07:18:48 +00:00
|
|
|
|
2019-12-17 07:56:52 +00:00
|
|
|
let metadata = tokio::fs::metadata(filename.clone())
|
2020-07-29 07:38:11 +00:00
|
|
|
.map_err(|err| http_err!(BAD_REQUEST, "File access problems: {}", err))
|
2019-12-17 07:56:52 +00:00
|
|
|
.await?;
|
|
|
|
|
|
|
|
if metadata.len() < 1024*32 {
|
|
|
|
simple_static_file_download(filename).await
|
|
|
|
} else {
|
|
|
|
chuncked_static_file_download(filename).await
|
|
|
|
}
|
2018-11-15 07:18:48 +00:00
|
|
|
}
|
|
|
|
|
2020-09-07 12:33:05 +00:00
|
|
|
fn extract_auth_data(headers: &http::HeaderMap) -> (Option<String>, Option<String>, Option<String>) {
|
2019-02-16 14:52:55 +00:00
|
|
|
|
|
|
|
let mut ticket = None;
|
2020-09-07 12:33:05 +00:00
|
|
|
let mut language = None;
|
2019-02-16 14:52:55 +00:00
|
|
|
if let Some(raw_cookie) = headers.get("COOKIE") {
|
|
|
|
if let Ok(cookie) = raw_cookie.to_str() {
|
2020-09-07 12:30:44 +00:00
|
|
|
ticket = tools::extract_cookie(cookie, "PBSAuthCookie");
|
2020-09-07 12:33:05 +00:00
|
|
|
language = tools::extract_cookie(cookie, "PBSLangCookie");
|
2019-02-16 14:52:55 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let token = match headers.get("CSRFPreventionToken").map(|v| v.to_str()) {
|
|
|
|
Some(Ok(v)) => Some(v.to_owned()),
|
|
|
|
_ => None,
|
|
|
|
};
|
|
|
|
|
2020-09-07 12:33:05 +00:00
|
|
|
(ticket, token, language)
|
2019-02-16 14:52:55 +00:00
|
|
|
}
|
|
|
|
|
2020-04-16 08:01:59 +00:00
|
|
|
fn check_auth(
|
|
|
|
method: &hyper::Method,
|
|
|
|
ticket: &Option<String>,
|
|
|
|
token: &Option<String>,
|
|
|
|
user_info: &CachedUserInfo,
|
2020-08-06 13:46:01 +00:00
|
|
|
) -> Result<Userid, Error> {
|
2019-03-05 11:52:39 +00:00
|
|
|
let ticket_lifetime = tools::ticket::TICKET_LIFETIME;
|
2019-02-16 14:52:55 +00:00
|
|
|
|
2020-08-12 10:05:52 +00:00
|
|
|
let ticket = ticket.as_ref().map(String::as_str);
|
|
|
|
let userid: Userid = Ticket::parse(&ticket.ok_or_else(|| format_err!("missing ticket"))?)?
|
|
|
|
.verify_with_time_frame(public_auth_key(), "PBS", None, -300..ticket_lifetime)?;
|
2019-02-16 14:52:55 +00:00
|
|
|
|
2020-08-06 13:46:01 +00:00
|
|
|
if !user_info.is_active_user(&userid) {
|
2020-04-16 08:01:59 +00:00
|
|
|
bail!("user account disabled or expired.");
|
|
|
|
}
|
|
|
|
|
2019-02-16 14:52:55 +00:00
|
|
|
if method != hyper::Method::GET {
|
|
|
|
if let Some(token) = token {
|
2020-08-06 13:46:01 +00:00
|
|
|
verify_csrf_prevention_token(csrf_secret(), &userid, &token, -300, ticket_lifetime)?;
|
2019-02-16 14:52:55 +00:00
|
|
|
} else {
|
2019-02-17 16:18:44 +00:00
|
|
|
bail!("missing CSRF prevention token");
|
2019-02-16 14:52:55 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-06 13:46:01 +00:00
|
|
|
Ok(userid)
|
2019-02-16 14:52:55 +00:00
|
|
|
}
|
|
|
|
|
2020-10-15 15:43:42 +00:00
|
|
|
async fn handle_request(
|
|
|
|
api: Arc<ApiConfig>,
|
|
|
|
req: Request<Body>,
|
|
|
|
peer: &std::net::SocketAddr,
|
|
|
|
) -> Result<Response<Body>, Error> {
|
2019-02-17 16:31:53 +00:00
|
|
|
|
|
|
|
let (parts, body) = req.into_parts();
|
|
|
|
let method = parts.method.clone();
|
2020-07-15 07:11:16 +00:00
|
|
|
let (path, components) = tools::normalize_uri_path(parts.uri.path())?;
|
2019-02-17 16:31:53 +00:00
|
|
|
|
2018-11-15 07:18:48 +00:00
|
|
|
let comp_len = components.len();
|
|
|
|
|
server: rest: implement max URI path and query length request limits
Add a generous limit now and return the correct error (414 URI Too
Long). Otherwise we could to pretty larger GET requests, 64 KiB and
possible bigger (at 64 KiB my simple curl test failed due to
shell/curl limitations).
For now allow a 3072 characters as combined length of URI path and
query.
This is conform with the HTTP/1.1 RFCs (e.g., RFC 7231, 6.5.12 and
RFC 2616, 3.2.1) which do not specify any limits, upper or lower, but
require that all server accessible resources mus be reachable without
getting 414, which is normally fulfilled as we have various length
limits for stuff which could be in an URI, in place, e.g.:
* user id: max. 64 chars
* datastore: max. 32 chars
The only known problematic API endpoint is the catalog one, used in
the GUI's pxar file browser:
GET /api2/json/admin/datastore/<id>/catalog?..&filepath=<path>
The <path> is the encoded archive path, and can be arbitrary long.
But, this is a flawed design, as even without this new limit one can
easily generate archives which cannot be browsed anymore, as hyper
only accepts requests with max. 64 KiB in the URI.
So rather, we should move that to a GET-as-POST call, which has no
such limitations (and would not need to base32 encode the path).
Note: This change was inspired by adding a request access log, which
profits from such limits as we can then rely on certain atomicity
guarantees when writing requests to the log.
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2020-10-15 15:49:16 +00:00
|
|
|
let query = parts.uri.query().unwrap_or_default();
|
|
|
|
if path.len() + query.len() > MAX_URI_QUERY_LENGTH {
|
|
|
|
return Ok(Response::builder()
|
|
|
|
.status(StatusCode::URI_TOO_LONG)
|
|
|
|
.body("".into())
|
|
|
|
.unwrap());
|
|
|
|
}
|
|
|
|
|
2019-01-28 16:30:39 +00:00
|
|
|
let env_type = api.env_type();
|
|
|
|
let mut rpcenv = RestEnvironment::new(env_type);
|
2019-01-27 09:18:52 +00:00
|
|
|
|
2020-10-15 15:43:42 +00:00
|
|
|
rpcenv.set_client_ip(Some(*peer));
|
|
|
|
|
2020-04-16 08:01:59 +00:00
|
|
|
let user_info = CachedUserInfo::new()?;
|
|
|
|
|
2019-01-31 11:22:00 +00:00
|
|
|
let delay_unauth_time = std::time::Instant::now() + std::time::Duration::from_millis(3000);
|
2020-04-16 10:56:34 +00:00
|
|
|
let access_forbidden_time = std::time::Instant::now() + std::time::Duration::from_millis(500);
|
2019-01-31 11:22:00 +00:00
|
|
|
|
2019-01-22 11:10:38 +00:00
|
|
|
if comp_len >= 1 && components[0] == "api2" {
|
2019-02-16 14:52:55 +00:00
|
|
|
|
2018-11-15 07:18:48 +00:00
|
|
|
if comp_len >= 2 {
|
2019-11-22 12:02:05 +00:00
|
|
|
|
2018-11-15 07:18:48 +00:00
|
|
|
let format = components[1];
|
2019-11-22 12:02:05 +00:00
|
|
|
|
2018-12-05 11:42:25 +00:00
|
|
|
let formatter = match format {
|
|
|
|
"json" => &JSON_FORMATTER,
|
|
|
|
"extjs" => &EXTJS_FORMATTER,
|
2019-11-22 12:02:05 +00:00
|
|
|
_ => bail!("Unsupported output format '{}'.", format),
|
2018-12-05 11:42:25 +00:00
|
|
|
};
|
2018-11-15 07:18:48 +00:00
|
|
|
|
2018-11-16 08:15:33 +00:00
|
|
|
let mut uri_param = HashMap::new();
|
2020-10-02 11:17:12 +00:00
|
|
|
let api_method = api.find_method(&components[2..], method.clone(), &mut uri_param);
|
2018-11-16 08:15:33 +00:00
|
|
|
|
2020-10-02 11:17:12 +00:00
|
|
|
let mut auth_required = true;
|
|
|
|
if let Some(api_method) = api_method {
|
|
|
|
if let Permission::World = *api_method.access.permission {
|
|
|
|
auth_required = false; // no auth for endpoints with World permission
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if auth_required {
|
2020-09-07 12:33:05 +00:00
|
|
|
let (ticket, token, _) = extract_auth_data(&parts.headers);
|
2020-04-16 08:01:59 +00:00
|
|
|
match check_auth(&method, &ticket, &token, &user_info) {
|
2020-08-06 13:46:01 +00:00
|
|
|
Ok(userid) => rpcenv.set_user(Some(userid.to_string())),
|
2019-02-16 14:52:55 +00:00
|
|
|
Err(err) => {
|
|
|
|
// always delay unauthorized calls by 3 seconds (from start of request)
|
2020-07-29 07:38:11 +00:00
|
|
|
let err = http_err!(UNAUTHORIZED, "authentication failed - {}", err);
|
2019-12-12 14:27:07 +00:00
|
|
|
tokio::time::delay_until(Instant::from_std(delay_unauth_time)).await;
|
2019-11-22 12:02:05 +00:00
|
|
|
return Ok((formatter.format_error)(err));
|
2019-02-16 14:52:55 +00:00
|
|
|
}
|
2019-01-31 11:22:00 +00:00
|
|
|
}
|
|
|
|
}
|
2019-01-27 09:42:45 +00:00
|
|
|
|
2020-10-02 11:17:12 +00:00
|
|
|
match api_method {
|
2019-11-21 08:36:41 +00:00
|
|
|
None => {
|
2020-07-29 07:38:11 +00:00
|
|
|
let err = http_err!(NOT_FOUND, "Path '{}' not found.", path);
|
2019-11-22 12:02:05 +00:00
|
|
|
return Ok((formatter.format_error)(err));
|
2019-04-01 06:04:12 +00:00
|
|
|
}
|
2019-11-21 08:36:41 +00:00
|
|
|
Some(api_method) => {
|
2020-04-16 08:01:59 +00:00
|
|
|
let user = rpcenv.get_user();
|
2020-04-18 06:49:20 +00:00
|
|
|
if !check_api_permission(api_method.access.permission, user.as_deref(), &uri_param, user_info.as_ref()) {
|
2020-07-29 07:38:11 +00:00
|
|
|
let err = http_err!(FORBIDDEN, "permission check failed");
|
2020-04-16 10:56:34 +00:00
|
|
|
tokio::time::delay_until(Instant::from_std(access_forbidden_time)).await;
|
2020-04-16 08:01:59 +00:00
|
|
|
return Ok((formatter.format_error)(err));
|
|
|
|
}
|
|
|
|
|
2020-03-26 11:54:20 +00:00
|
|
|
let result = if api_method.protected && env_type == RpcEnvironmentType::PUBLIC {
|
2020-10-15 15:43:42 +00:00
|
|
|
proxy_protected_request(api_method, parts, body, peer).await
|
2019-01-28 16:30:39 +00:00
|
|
|
} else {
|
2020-03-26 11:54:20 +00:00
|
|
|
handle_api_request(rpcenv, api_method, formatter, parts, body, uri_param).await
|
|
|
|
};
|
|
|
|
|
|
|
|
if let Err(err) = result {
|
|
|
|
return Ok((formatter.format_error)(err));
|
2019-01-28 16:30:39 +00:00
|
|
|
}
|
2020-03-26 11:54:20 +00:00
|
|
|
return result;
|
2019-01-14 11:26:04 +00:00
|
|
|
}
|
2018-11-15 07:18:48 +00:00
|
|
|
}
|
2020-03-26 11:54:20 +00:00
|
|
|
|
2018-11-15 07:18:48 +00:00
|
|
|
}
|
2019-11-22 12:02:05 +00:00
|
|
|
} else {
|
2019-02-17 17:50:40 +00:00
|
|
|
// not Auth required for accessing files!
|
2018-11-15 07:18:48 +00:00
|
|
|
|
2019-04-01 05:52:30 +00:00
|
|
|
if method != hyper::Method::GET {
|
2019-11-22 12:02:05 +00:00
|
|
|
bail!("Unsupported HTTP method {}", method);
|
2019-04-01 05:52:30 +00:00
|
|
|
}
|
|
|
|
|
2018-12-01 12:37:49 +00:00
|
|
|
if comp_len == 0 {
|
2020-09-07 12:33:05 +00:00
|
|
|
let (ticket, token, language) = extract_auth_data(&parts.headers);
|
2019-02-17 17:50:40 +00:00
|
|
|
if ticket != None {
|
2020-04-16 08:01:59 +00:00
|
|
|
match check_auth(&method, &ticket, &token, &user_info) {
|
2020-08-06 13:46:01 +00:00
|
|
|
Ok(userid) => {
|
|
|
|
let new_token = assemble_csrf_prevention_token(csrf_secret(), &userid);
|
2020-09-07 12:33:05 +00:00
|
|
|
return Ok(get_index(Some(userid), Some(new_token), language, &api, parts));
|
2019-04-01 05:52:30 +00:00
|
|
|
}
|
2019-08-26 11:33:38 +00:00
|
|
|
_ => {
|
2019-12-12 14:27:07 +00:00
|
|
|
tokio::time::delay_until(Instant::from_std(delay_unauth_time)).await;
|
2020-09-07 12:33:05 +00:00
|
|
|
return Ok(get_index(None, None, language, &api, parts));
|
2019-08-26 11:33:38 +00:00
|
|
|
}
|
2019-02-17 17:50:40 +00:00
|
|
|
}
|
|
|
|
} else {
|
2020-09-07 12:33:05 +00:00
|
|
|
return Ok(get_index(None, None, language, &api, parts));
|
2019-02-17 17:50:40 +00:00
|
|
|
}
|
2018-12-01 12:37:49 +00:00
|
|
|
} else {
|
|
|
|
let filename = api.find_alias(&components);
|
2019-11-22 12:02:05 +00:00
|
|
|
return handle_static_file_download(filename).await;
|
2018-12-01 12:37:49 +00:00
|
|
|
}
|
2018-11-15 07:18:48 +00:00
|
|
|
}
|
|
|
|
|
2020-07-29 07:38:11 +00:00
|
|
|
Err(http_err!(NOT_FOUND, "Path '{}' not found.", path))
|
2018-11-15 07:18:48 +00:00
|
|
|
}
|