Compare commits

..

2 Commits

Author SHA1 Message Date
blek 589ff7b040
include changes as requested 2023-12-14 19:35:13 +10:00
blek 5fab110513
properly implement delete method according to config 2023-12-13 20:49:33 +10:00
5 changed files with 80 additions and 14 deletions

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

@ -2,7 +2,6 @@
use std::error::Error; use std::error::Error;
use redis::{Client, Commands, AsyncCommands, Connection}; use redis::{Client, Commands, AsyncCommands, Connection};
use tokio::task::JoinSet;
use crate::env::Env; use crate::env::Env;

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,57 @@
use std::collections::HashMap; use std::{collections::HashMap, net::IpAddr};
use warp::{reply::{Reply, json}, reject::Rejection, Filter, http::StatusCode}; use serde_json::json;
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 +61,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 +77,46 @@ pub async fn delete(state: SharedState, body: DeleteFunctionPayload) -> Result<B
) )
} }
Ok(Box::new(json(&HashMap::<(), ()>::new()))) 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(&json!({}))))
} }
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 =
@ -48,7 +48,6 @@ pub async fn get_all(state: SharedState, ip: Option<IpAddr>) -> Result<Box<dyn R
|x| { |x| {
if let Some(owner) = x.uploader_ip { if let Some(owner) = x.uploader_ip {
if let Some(caller) = ip { if let Some(caller) = ip {
println!("{owner} {caller}");
return owner == caller return owner == caller
} }
} }