Implement all API according to swagger spec #27

Merged
blek merged 16 commits from delete-upload-api-methods into 0.2-dev 2023-12-14 11:56:46 +01:00
4 changed files with 78 additions and 11 deletions
Showing only changes of commit 5fab110513 - Show all commits

View File

@ -74,7 +74,7 @@ paths:
example: {} example: {}
401: 401:
description: |- description: |-
This error code is returned if one of the two conditions are met: This error code is returned if one of these conditions are met:
1. The instance does not allow deleting files via API. 1. The instance does not allow deleting files via API.
2. The file has been uploaded from another IP, which is not this one, and the API was not authorized via an API key. 2. The file has been uploaded from another IP, which is not this one, and the API was not authorized via an API key.
@ -90,6 +90,9 @@ paths:
fid: fid:
type: string type: string
example: ID or name of the file. It is the NAME in file.blek.codes/uploads/NAME example: ID or name of the file. It is the NAME in file.blek.codes/uploads/NAME
api_key:
type: string
example: '123'
/api/files/upload: /api/files/upload:
post: post:
summary: Upload a file summary: Upload a file

View File

@ -49,10 +49,10 @@ fn check_api_pass(state: &SharedState, key: String) -> Result<(), WithStatus<Jso
} }
} }
fn function_disabled_err() -> WithStatus<Json> { fn function_disabled_err(status: StatusCode) -> WithStatus<Json> {
warp::reply::with_status( warp::reply::with_status(
json(&ErrorMessage::new(Error::APIFunctionDisabled)), json(&ErrorMessage::new(Error::APIFunctionDisabled)),
StatusCode::SERVICE_UNAVAILABLE status
) )
} }

View File

@ -1,26 +1,56 @@
use std::collections::HashMap; use std::{collections::HashMap, net::IpAddr};
use warp::{reply::{Reply, json}, reject::Rejection, Filter, http::StatusCode}; use warp::{reply::{Reply, json, with_status}, reject::Rejection, Filter, http::StatusCode};
use serde::{Serialize, Deserialize}; use serde::{Serialize, Deserialize};
use warp_real_ip::real_ip;
use crate::web::{state::SharedState, rejection::HttpReject, api::types::{ErrorMessage, Error}}; use crate::{web::{state::SharedState, rejection::HttpReject, api::types::{ErrorMessage, Error}}, files::File};
use super::{function_disabled_err, check_api_enabled}; use super::{function_disabled_err, check_api_enabled};
#[derive(Serialize, Deserialize, Clone, Debug)] #[derive(Serialize, Deserialize, Clone, Debug)]
pub struct DeleteFunctionPayload { pub struct DeleteFunctionPayload {
pub fid: String pub fid: String,
pub api_key: Option<String>
} }
pub async fn delete(state: SharedState, body: DeleteFunctionPayload) -> Result<Box<dyn Reply>, Rejection> { pub async fn delete(state: SharedState, body: DeleteFunctionPayload, ip: Option<IpAddr>) -> Result<Box<dyn Reply>, Rejection> {
if let Err(res) = check_api_enabled(&state) { if let Err(res) = check_api_enabled(&state) {
return Ok(Box::new(res)); return Ok(Box::new(res));
} }
if (!state.config.api.delete) || (!state.config.api.enabled) { if (!state.config.api.delete) || (!state.config.api.enabled) {
return Ok(Box::new(function_disabled_err())) return Ok(Box::new(function_disabled_err(StatusCode::UNAUTHORIZED)))
} }
let mut sudo_authorized = false;
let mut blocked = false;
if let Some(keys) = state.config.api.apikeys.clone() {
if let Some(key) = body.api_key {
if keys.contains(&key) {
sudo_authorized = true;
blocked = false;
} else {
sudo_authorized = false;
blocked = true;
}
} else {
sudo_authorized = false;
blocked = true
}
}
if ! sudo_authorized {
if ip.is_none() { // need the ip if sudo is not authorized
blocked = true // to check if the file is the own file
}
}
let ip = ip.unwrap();
let id = body.fid; let id = body.fid;
let mut file = state.file_mgr.find_by_hash(id.clone()) let mut file = state.file_mgr.find_by_hash(id.clone())
.map_err(|x| HttpReject::StringError(x.to_string()))?; .map_err(|x| HttpReject::StringError(x.to_string()))?;
@ -30,7 +60,7 @@ pub async fn delete(state: SharedState, body: DeleteFunctionPayload) -> Result<B
.map_err(|x| HttpReject::StringError(x.to_string()))?; .map_err(|x| HttpReject::StringError(x.to_string()))?;
} }
if let None = file { if let None = file.clone() {
return Ok( return Ok(
Box::new( Box::new(
warp::reply::with_status( warp::reply::with_status(
@ -46,12 +76,46 @@ pub async fn delete(state: SharedState, body: DeleteFunctionPayload) -> Result<B
) )
} }
let file: File = file.unwrap();
if let Some(uploader) = file.uploader_ip {
if uploader != ip && (!sudo_authorized) {
blocked = true;
}
} else {
blocked = true;
}
if blocked {
return Ok(
Box::new(
with_status(
json(
&ErrorMessage {
error: Error::APIPasswordDenied,
details: Some(
"Request has been denied for one of the following reasons: password auth did not pass, file was uploaded by someone else, the instance does not allow deleting files via the API".into()
)
}
),
StatusCode::UNAUTHORIZED
)
)
)
}
file.delete(state).await;
Ok(Box::new(json(&HashMap::<(), ()>::new()))) Ok(Box::new(json(&HashMap::<(), ()>::new())))
} }
pub fn delete_f(state: SharedState) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone { pub fn delete_f(state: SharedState) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
let proxy_ip = state.env.proxy_addr;
warp::path!("api" / "files" / "delete") warp::path!("api" / "files" / "delete")
.map(move || state.clone()) .map(move || state.clone())
.and(warp::body::json()) .and(warp::body::json())
.and(real_ip(vec![proxy_ip]))
.and_then(delete) .and_then(delete)
} }

View File

@ -13,7 +13,7 @@ pub async fn get_all(state: SharedState, ip: Option<IpAddr>) -> Result<Box<dyn R
} }
if (!state.config.api.get_all) || (!state.config.api.enabled) { if (!state.config.api.get_all) || (!state.config.api.enabled) {
return Ok(Box::new(function_disabled_err())) return Ok(Box::new(function_disabled_err(StatusCode::UNAUTHORIZED)))
} }
let found = let found =