Add option to disable image upload (fixes #1118)

This commit is contained in:
Felix Ableitner 2024-12-17 16:10:55 +01:00
parent 8ae4b405d0
commit b0d4bdb8ff
9 changed files with 29 additions and 14 deletions

View file

@ -320,7 +320,7 @@ pub async fn purge_image_from_pictrs(image_url: &Url, context: &LemmyContext) ->
.next_back() .next_back()
.ok_or(LemmyErrorType::ImageUrlMissingLastPathSegment)?; .ok_or(LemmyErrorType::ImageUrlMissingLastPathSegment)?;
let pictrs_config = context.settings().pictrs_config()?; let pictrs_config = context.settings().pictrs()?;
let purge_url = format!("{}internal/purge?alias={}", pictrs_config.url, alias); let purge_url = format!("{}internal/purge?alias={}", pictrs_config.url, alias);
let pictrs_api_key = pictrs_config let pictrs_api_key = pictrs_config
@ -348,7 +348,7 @@ pub async fn delete_image_from_pictrs(
delete_token: &str, delete_token: &str,
context: &LemmyContext, context: &LemmyContext,
) -> LemmyResult<()> { ) -> LemmyResult<()> {
let pictrs_config = context.settings().pictrs_config()?; let pictrs_config = context.settings().pictrs()?;
let url = format!( let url = format!(
"{}image/delete/{}/{}", "{}image/delete/{}/{}",
pictrs_config.url, &delete_token, &alias pictrs_config.url, &delete_token, &alias
@ -366,7 +366,7 @@ pub async fn delete_image_from_pictrs(
/// Retrieves the image with local pict-rs and generates a thumbnail. Returns the thumbnail url. /// Retrieves the image with local pict-rs and generates a thumbnail. Returns the thumbnail url.
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn generate_pictrs_thumbnail(image_url: &Url, context: &LemmyContext) -> LemmyResult<Url> { async fn generate_pictrs_thumbnail(image_url: &Url, context: &LemmyContext) -> LemmyResult<Url> {
let pictrs_config = context.settings().pictrs_config()?; let pictrs_config = context.settings().pictrs()?;
match pictrs_config.image_mode() { match pictrs_config.image_mode() {
PictrsImageMode::None => return Ok(image_url.clone()), PictrsImageMode::None => return Ok(image_url.clone()),
@ -382,7 +382,7 @@ async fn generate_pictrs_thumbnail(image_url: &Url, context: &LemmyContext) -> L
"{}image/download?url={}&resize={}", "{}image/download?url={}&resize={}",
pictrs_config.url, pictrs_config.url,
encode(image_url.as_str()), encode(image_url.as_str()),
context.settings().pictrs_config()?.max_thumbnail_size context.settings().pictrs()?.max_thumbnail_size
); );
let res = context let res = context
@ -425,7 +425,7 @@ pub async fn fetch_pictrs_proxied_image_details(
image_url: &Url, image_url: &Url,
context: &LemmyContext, context: &LemmyContext,
) -> LemmyResult<PictrsFileDetails> { ) -> LemmyResult<PictrsFileDetails> {
let pictrs_url = context.settings().pictrs_config()?.url; let pictrs_url = context.settings().pictrs()?.url;
let encoded_image_url = encode(image_url.as_str()); let encoded_image_url = encode(image_url.as_str());
// Pictrs needs you to fetch the proxied image before you can fetch the details // Pictrs needs you to fetch the proxied image before you can fetch the details

View file

@ -455,6 +455,9 @@ pub struct GetSiteResponse {
#[cfg_attr(feature = "full", ts(optional))] #[cfg_attr(feature = "full", ts(optional))]
pub admin_oauth_providers: Option<Vec<OAuthProvider>>, pub admin_oauth_providers: Option<Vec<OAuthProvider>>,
pub blocked_urls: Vec<LocalSiteUrlBlocklist>, pub blocked_urls: Vec<LocalSiteUrlBlocklist>,
// If true then uploads for post images or markdown images are disabled. Only avatars, icons and
// banners can be set.
pub image_upload_disabled: bool,
} }
#[skip_serializing_none] #[skip_serializing_none]

View file

@ -1060,7 +1060,7 @@ pub async fn process_markdown(
markdown_check_for_blocked_urls(&text, url_blocklist)?; markdown_check_for_blocked_urls(&text, url_blocklist)?;
if context.settings().pictrs_config()?.image_mode() == PictrsImageMode::ProxyAllImages { if context.settings().pictrs()?.image_mode() == PictrsImageMode::ProxyAllImages {
let (text, links) = markdown_rewrite_image_links(text); let (text, links) = markdown_rewrite_image_links(text);
RemoteImage::create(&mut context.pool(), links.clone()).await?; RemoteImage::create(&mut context.pool(), links.clone()).await?;
@ -1130,7 +1130,7 @@ async fn proxy_image_link_internal(
pub async fn proxy_image_link(link: Url, context: &LemmyContext) -> LemmyResult<DbUrl> { pub async fn proxy_image_link(link: Url, context: &LemmyContext) -> LemmyResult<DbUrl> {
proxy_image_link_internal( proxy_image_link_internal(
link, link,
context.settings().pictrs_config()?.image_mode(), context.settings().pictrs()?.image_mode(),
context, context,
) )
.await .await

View file

@ -69,5 +69,6 @@ async fn read_site(context: &LemmyContext) -> LemmyResult<GetSiteResponse> {
tagline, tagline,
oauth_providers: Some(oauth_providers), oauth_providers: Some(oauth_providers),
admin_oauth_providers: Some(admin_oauth_providers), admin_oauth_providers: Some(admin_oauth_providers),
image_upload_disabled: context.settings().pictrs()?.disable_image_upload,
}) })
} }

View file

@ -2,6 +2,7 @@ use actix_web::{body::BoxBody, web::*, HttpRequest, HttpResponse, Responder};
use lemmy_api_common::{ use lemmy_api_common::{
context::LemmyContext, context::LemmyContext,
image::{DeleteImageParams, ImageGetParams, ImageProxyParams, UploadImageResponse}, image::{DeleteImageParams, ImageGetParams, ImageProxyParams, UploadImageResponse},
LemmyErrorType,
SuccessResponse, SuccessResponse,
}; };
use lemmy_db_schema::source::{ use lemmy_db_schema::source::{
@ -22,6 +23,10 @@ pub async fn upload_image(
local_user_view: LocalUserView, local_user_view: LocalUserView,
context: Data<LemmyContext>, context: Data<LemmyContext>,
) -> LemmyResult<Json<UploadImageResponse>> { ) -> LemmyResult<Json<UploadImageResponse>> {
if context.settings().pictrs()?.disable_image_upload {
return Err(LemmyErrorType::ImageUploadDisabled.into());
}
let image = do_upload_image(req, body, UploadType::Other, &local_user_view, &context).await?; let image = do_upload_image(req, body, UploadType::Other, &local_user_view, &context).await?;
let image_url = image.image_url(&context.settings().get_protocol_and_hostname())?; let image_url = image.image_url(&context.settings().get_protocol_and_hostname())?;
@ -47,7 +52,7 @@ pub async fn get_image(
let name = &filename.into_inner(); let name = &filename.into_inner();
// If there are no query params, the URL is original // If there are no query params, the URL is original
let pictrs_url = context.settings().pictrs_config()?.url; let pictrs_url = context.settings().pictrs()?.url;
let processed_url = if params.file_type.is_none() && params.max_size.is_none() { let processed_url = if params.file_type.is_none() && params.max_size.is_none() {
format!("{}image/original/{}", pictrs_url, name) format!("{}image/original/{}", pictrs_url, name)
} else { } else {
@ -69,7 +74,7 @@ pub async fn delete_image(
// require login // require login
_local_user_view: LocalUserView, _local_user_view: LocalUserView,
) -> LemmyResult<Json<SuccessResponse>> { ) -> LemmyResult<Json<SuccessResponse>> {
let pictrs_config = context.settings().pictrs_config()?; let pictrs_config = context.settings().pictrs()?;
let url = format!( let url = format!(
"{}image/delete/{}/{}", "{}image/delete/{}/{}",
pictrs_config.url, &data.token, &data.filename pictrs_config.url, &data.token, &data.filename
@ -83,7 +88,7 @@ pub async fn delete_image(
} }
pub async fn pictrs_health(context: Data<LemmyContext>) -> LemmyResult<Json<SuccessResponse>> { pub async fn pictrs_health(context: Data<LemmyContext>) -> LemmyResult<Json<SuccessResponse>> {
let pictrs_config = context.settings().pictrs_config()?; let pictrs_config = context.settings().pictrs()?;
let url = format!("{}healthz", pictrs_config.url); let url = format!("{}healthz", pictrs_config.url);
PICTRS_CLIENT.get(url).send().await?.error_for_status()?; PICTRS_CLIENT.get(url).send().await?.error_for_status()?;
@ -102,7 +107,7 @@ pub async fn image_proxy(
// for arbitrary purposes. // for arbitrary purposes.
RemoteImage::validate(&mut context.pool(), url.clone().into()).await?; RemoteImage::validate(&mut context.pool(), url.clone().into()).await?;
let pictrs_config = context.settings().pictrs_config()?; let pictrs_config = context.settings().pictrs()?;
let processed_url = if params.file_type.is_none() && params.max_size.is_none() { let processed_url = if params.file_type.is_none() && params.max_size.is_none() {
format!("{}image/original?proxy={}", pictrs_config.url, params.url) format!("{}image/original?proxy={}", pictrs_config.url, params.url)
} else { } else {

View file

@ -125,7 +125,7 @@ pub(super) async fn do_upload_image(
local_user_view: &LocalUserView, local_user_view: &LocalUserView,
context: &Data<LemmyContext>, context: &Data<LemmyContext>,
) -> LemmyResult<PictrsFile> { ) -> LemmyResult<PictrsFile> {
let pictrs_config = context.settings().pictrs_config()?; let pictrs_config = context.settings().pictrs()?;
let image_url = format!("{}image", pictrs_config.url); let image_url = format!("{}image", pictrs_config.url);
let mut client_req = adapt_request(&req, image_url); let mut client_req = adapt_request(&req, image_url);
@ -134,7 +134,7 @@ pub(super) async fn do_upload_image(
UploadType::Avatar => { UploadType::Avatar => {
let max_size = context let max_size = context
.settings() .settings()
.pictrs_config()? .pictrs()?
.max_avatar_size .max_avatar_size
.to_string(); .to_string();
client_req.query(&[ client_req.query(&[

View file

@ -31,6 +31,7 @@ pub enum LemmyErrorType {
NoContentTypeHeader, NoContentTypeHeader,
NotAnImageType, NotAnImageType,
InvalidImageUpload, InvalidImageUpload,
ImageUploadDisabled,
NotAModOrAdmin, NotAModOrAdmin,
NotTopMod, NotTopMod,
NotLoggedIn, NotLoggedIn,

View file

@ -97,7 +97,7 @@ impl Settings {
WEBFINGER_REGEX.clone() WEBFINGER_REGEX.clone()
} }
pub fn pictrs_config(&self) -> LemmyResult<PictrsConfig> { pub fn pictrs(&self) -> LemmyResult<PictrsConfig> {
self self
.pictrs .pictrs
.clone() .clone()

View file

@ -116,6 +116,11 @@ pub struct PictrsConfig {
/// if image is larger. /// if image is larger.
#[default(512)] #[default(512)]
pub max_banner_size: u32, pub max_banner_size: u32,
/// Prevent users from uploading images for posts or embedding in markdown. Avatars, icons and
/// banners can still be uploaded.
#[default(false)]
pub disable_image_upload: bool,
} }
#[derive(Debug, Deserialize, Serialize, Clone, SmartDefault, Document, PartialEq)] #[derive(Debug, Deserialize, Serialize, Clone, SmartDefault, Document, PartialEq)]