allow only whitelisted folders to be used in ffpapi
This commit is contained in:
parent
8bb2e96edb
commit
3729d37a78
9
Cargo.lock
generated
9
Cargo.lock
generated
@ -1292,7 +1292,7 @@ checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a"
|
||||
|
||||
[[package]]
|
||||
name = "ffplayout"
|
||||
version = "0.22.3"
|
||||
version = "0.23.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"clap",
|
||||
@ -1314,7 +1314,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ffplayout-api"
|
||||
version = "0.22.3"
|
||||
version = "0.23.0"
|
||||
dependencies = [
|
||||
"actix-files",
|
||||
"actix-multipart",
|
||||
@ -1330,6 +1330,7 @@ dependencies = [
|
||||
"faccess",
|
||||
"ffplayout-lib",
|
||||
"futures-util",
|
||||
"home",
|
||||
"jsonwebtoken",
|
||||
"lazy_static",
|
||||
"lexical-sort",
|
||||
@ -1357,7 +1358,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ffplayout-lib"
|
||||
version = "0.22.3"
|
||||
version = "0.23.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"crossbeam-channel",
|
||||
@ -3587,7 +3588,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tests"
|
||||
version = "0.22.3"
|
||||
version = "0.23.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"crossbeam-channel",
|
||||
|
@ -4,7 +4,7 @@ default-members = ["ffplayout-api", "ffplayout-engine", "tests"]
|
||||
resolver = "2"
|
||||
|
||||
[workspace.package]
|
||||
version = "0.22.3"
|
||||
version = "0.23.0"
|
||||
license = "GPL-3.0"
|
||||
repository = "https://github.com/ffplayout/ffplayout"
|
||||
authors = ["Jonathan Baecker <jonbae77@gmail.com>"]
|
||||
|
@ -27,6 +27,7 @@ clap = { version = "4.3", features = ["derive"] }
|
||||
derive_more = "0.99"
|
||||
faccess = "0.2"
|
||||
futures-util = { version = "0.3", default-features = false, features = ["std"] }
|
||||
home = "0.5"
|
||||
jsonwebtoken = "9"
|
||||
lazy_static = "1.4"
|
||||
lexical-sort = "0.3"
|
||||
|
@ -855,7 +855,7 @@ pub async fn gen_playlist(
|
||||
let mut path_list = vec![];
|
||||
|
||||
for path in paths {
|
||||
let (p, _, _) = norm_abs_path(&config.storage.path, path);
|
||||
let (p, _, _) = norm_abs_path(&config.storage.path, path)?;
|
||||
|
||||
path_list.push(p);
|
||||
}
|
||||
@ -1024,7 +1024,7 @@ async fn get_file(
|
||||
let (config, _) = playout_config(&pool.into_inner(), &id).await?;
|
||||
let storage_path = config.storage.path;
|
||||
let file_path = req.match_info().query("filename");
|
||||
let (path, _, _) = norm_abs_path(&storage_path, file_path);
|
||||
let (path, _, _) = norm_abs_path(&storage_path, file_path)?;
|
||||
let file = actix_files::NamedFile::open(path)?;
|
||||
|
||||
Ok(file
|
||||
|
@ -12,6 +12,9 @@ pub enum ServiceError {
|
||||
#[display(fmt = "Conflict: {_0}")]
|
||||
Conflict(String),
|
||||
|
||||
#[display(fmt = "Forbidden: {_0}")]
|
||||
Forbidden(String),
|
||||
|
||||
#[display(fmt = "Unauthorized: {_0}")]
|
||||
Unauthorized(String),
|
||||
|
||||
@ -31,6 +34,7 @@ impl ResponseError for ServiceError {
|
||||
}
|
||||
ServiceError::BadRequest(ref message) => HttpResponse::BadRequest().json(message),
|
||||
ServiceError::Conflict(ref message) => HttpResponse::Conflict().json(message),
|
||||
ServiceError::Forbidden(ref message) => HttpResponse::Forbidden().json(message),
|
||||
ServiceError::Unauthorized(ref message) => HttpResponse::Unauthorized().json(message),
|
||||
ServiceError::NoContent(ref message) => HttpResponse::NoContent().json(message),
|
||||
ServiceError::ServiceUnavailable(ref message) => {
|
||||
|
@ -6,6 +6,7 @@ use std::{
|
||||
use actix_multipart::Multipart;
|
||||
use actix_web::{web, HttpResponse};
|
||||
use futures_util::TryStreamExt as _;
|
||||
use lazy_static::lazy_static;
|
||||
use lexical_sort::{natural_lexical_cmp, PathSort};
|
||||
use rand::{distributions::Alphanumeric, Rng};
|
||||
use relative_path::RelativePath;
|
||||
@ -54,11 +55,30 @@ pub struct VideoFile {
|
||||
duration: f64,
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
pub static ref HOME_DIR: String = home::home_dir()
|
||||
.unwrap_or("/home/h1wl3n2og".into()) // any random not existing folder
|
||||
.as_os_str()
|
||||
.to_string_lossy()
|
||||
.to_string();
|
||||
}
|
||||
|
||||
const FOLDER_WHITELIST: &[&str; 6] = &[
|
||||
"/media",
|
||||
"/mnt",
|
||||
"/playlists",
|
||||
"/tv-media",
|
||||
"/usr/share/ffplayout",
|
||||
"/var/lib/ffplayout",
|
||||
];
|
||||
|
||||
/// Normalize absolut path
|
||||
///
|
||||
/// This function takes care, that it is not possible to break out from root_path.
|
||||
/// It also gives always a relative path back.
|
||||
pub fn norm_abs_path(root_path: &Path, input_path: &str) -> (PathBuf, String, String) {
|
||||
pub fn norm_abs_path(
|
||||
root_path: &Path,
|
||||
input_path: &str,
|
||||
) -> Result<(PathBuf, String, String), ServiceError> {
|
||||
let path_relative = RelativePath::new(&root_path.to_string_lossy())
|
||||
.normalize()
|
||||
.to_string()
|
||||
@ -91,7 +111,15 @@ pub fn norm_abs_path(root_path: &Path, input_path: &str) -> (PathBuf, String, St
|
||||
|
||||
let path = &root_path.join(&source_relative);
|
||||
|
||||
(path.to_path_buf(), path_suffix, source_relative)
|
||||
if !FOLDER_WHITELIST.iter().any(|f| path.starts_with(f))
|
||||
&& !path.starts_with(&HOME_DIR.to_string())
|
||||
{
|
||||
return Err(ServiceError::Forbidden(
|
||||
"Access forbidden: Folder cannot be opened.".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
Ok((path.to_path_buf(), path_suffix, source_relative))
|
||||
}
|
||||
|
||||
/// File Browser
|
||||
@ -114,7 +142,7 @@ pub async fn browser(
|
||||
let mut extensions = config.storage.extensions;
|
||||
extensions.append(&mut channel_extensions);
|
||||
|
||||
let (path, parent, path_component) = norm_abs_path(&config.storage.path, &path_obj.source);
|
||||
let (path, parent, path_component) = norm_abs_path(&config.storage.path, &path_obj.source)?;
|
||||
|
||||
let parent_path = if !path_component.is_empty() {
|
||||
path.parent().unwrap()
|
||||
@ -212,7 +240,7 @@ pub async fn create_directory(
|
||||
path_obj: &PathObject,
|
||||
) -> Result<HttpResponse, ServiceError> {
|
||||
let (config, _) = playout_config(conn, &id).await?;
|
||||
let (path, _, _) = norm_abs_path(&config.storage.path, &path_obj.source);
|
||||
let (path, _, _) = norm_abs_path(&config.storage.path, &path_obj.source)?;
|
||||
|
||||
if let Err(e) = fs::create_dir_all(&path).await {
|
||||
return Err(ServiceError::BadRequest(e.to_string()));
|
||||
@ -283,8 +311,8 @@ pub async fn rename_file(
|
||||
move_object: &MoveObject,
|
||||
) -> Result<MoveObject, ServiceError> {
|
||||
let (config, _) = playout_config(conn, &id).await?;
|
||||
let (source_path, _, _) = norm_abs_path(&config.storage.path, &move_object.source);
|
||||
let (mut target_path, _, _) = norm_abs_path(&config.storage.path, &move_object.target);
|
||||
let (source_path, _, _) = norm_abs_path(&config.storage.path, &move_object.source)?;
|
||||
let (mut target_path, _, _) = norm_abs_path(&config.storage.path, &move_object.target)?;
|
||||
|
||||
if !source_path.exists() {
|
||||
return Err(ServiceError::BadRequest("Source file not exist!".into()));
|
||||
@ -318,7 +346,7 @@ pub async fn remove_file_or_folder(
|
||||
source_path: &str,
|
||||
) -> Result<(), ServiceError> {
|
||||
let (config, _) = playout_config(conn, &id).await?;
|
||||
let (source, _, _) = norm_abs_path(&config.storage.path, source_path);
|
||||
let (source, _, _) = norm_abs_path(&config.storage.path, source_path)?;
|
||||
|
||||
if !source.exists() {
|
||||
return Err(ServiceError::BadRequest("Source does not exists!".into()));
|
||||
@ -351,7 +379,7 @@ pub async fn remove_file_or_folder(
|
||||
|
||||
async fn valid_path(conn: &Pool<Sqlite>, id: i32, path: &str) -> Result<PathBuf, ServiceError> {
|
||||
let (config, _) = playout_config(conn, &id).await?;
|
||||
let (test_path, _, _) = norm_abs_path(&config.storage.path, path);
|
||||
let (test_path, _, _) = norm_abs_path(&config.storage.path, path)?;
|
||||
|
||||
if !test_path.is_dir() {
|
||||
return Err(ServiceError::BadRequest("Target folder not exists!".into()));
|
||||
|
@ -14,7 +14,8 @@ pub async fn read_playlist(
|
||||
date: String,
|
||||
) -> Result<JsonPlaylist, ServiceError> {
|
||||
let (config, _) = playout_config(conn, &id).await?;
|
||||
let mut playlist_path = PathBuf::from(&config.playlist.path);
|
||||
let (path, _, _) = norm_abs_path(&config.playlist.path, "")?;
|
||||
let mut playlist_path = path;
|
||||
let d: Vec<&str> = date.split('-').collect();
|
||||
playlist_path = playlist_path
|
||||
.join(d[0])
|
||||
@ -96,7 +97,7 @@ pub async fn generate_playlist(
|
||||
|
||||
for path in &source.paths {
|
||||
let (safe_path, _, _) =
|
||||
norm_abs_path(&config.storage.path, &path.to_string_lossy());
|
||||
norm_abs_path(&config.storage.path, &path.to_string_lossy())?;
|
||||
paths.push(safe_path);
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user