1
0
Fork 0
mirror of https://gitlab.com/famedly/conduit.git synced 2025-06-27 16:35:59 +00:00

Change URL preview setting from bool to a mode, and add support for an allowlist

This commit is contained in:
Reiner Herrmann 2023-07-29 00:38:58 +02:00
parent bb4cade9fd
commit 61fd9166f6
5 changed files with 90 additions and 26 deletions

View file

@ -47,9 +47,6 @@ registration_token = ""
allow_check_for_updates = true
allow_federation = true
# Allows clients to request a URL preview
allow_url_preview = false
# Enable the display name lightning bolt on registration.
enable_lightning_bolt = true
@ -69,6 +66,13 @@ trusted_servers = ["matrix.org"]
address = "127.0.0.1" # This makes sure Conduit can only be reached using the reverse proxy
#address = "0.0.0.0" # If Conduit is running in a container, make sure the reverse proxy (ie. Traefik) can reach it.
# possible URL preview modes:
# None: previews disabled
# All: previews for any URL allowed
# Allowlist: only domains in `url_preview_allowlist` are allowed
url_preview_mode = "None"
url_preview_allowlist = ["google.com", "youtube.com", "www.youtube.com"]
[global.well_known]
# Conduit handles the /.well-known/matrix/* endpoints, making both clients and servers try to access conduit with the host
# server_name and port 443 by default.

10
debian/postinst vendored
View file

@ -84,9 +84,6 @@ allow_check_for_updates = true
# Enable the display name lightning bolt on registration.
enable_lightning_bolt = true
# Allows clients to request a URL preview
allow_url_preview = false
# Servers listed here will be used to gather public keys of other servers.
# Generally, copying this exactly should be enough. (Currently, Conduit doesn't
# support batched key requests, so this list should only contain Synapse
@ -99,6 +96,13 @@ trusted_servers = ["matrix.org"]
#
# [0]: https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html#directives
#log = "..."
# possible URL preview modes:
# None: previews disabled
# All: previews for any URL allowed
# Allowlist: only domains in \`url_preview_allowlist\` are allowed
url_preview_mode = "None"
url_preview_allowlist = ["google.com", "youtube.com", "www.youtube.com"]
EOF
fi
;;

View file

@ -11,9 +11,11 @@ use ruma::api::client::{
#[cfg(feature = "url_preview")]
use {
crate::config::UrlPreviewMode,
crate::service::media::UrlPreviewData,
webpage::HTML,
std::{io::Cursor, net::IpAddr, sync::Arc, time::Duration},
reqwest::Url,
std::{io::Cursor, net::IpAddr, sync::Arc},
tokio::sync::Notify,
image::io::Reader as ImgReader,
};
@ -123,9 +125,9 @@ fn url_request_allowed(addr: &IpAddr) -> bool {
}
#[cfg(feature = "url_preview")]
async fn request_url_preview(url: String) -> Result<UrlPreviewData> {
async fn request_url_preview(url: &str) -> Result<UrlPreviewData> {
let client = services().globals.default_client();
let response = client.head(&url).send().await?;
let response = client.head(url).send().await?;
if !response
.remote_addr()
@ -151,8 +153,8 @@ async fn request_url_preview(url: String) -> Result<UrlPreviewData> {
}
};
let data = match content_type {
html if html.starts_with("text/html") => download_html(&client, &url).await?,
img if img.starts_with("image/") => download_image(&client, &url).await?,
html if html.starts_with("text/html") => download_html(&client, url).await?,
img if img.starts_with("image/") => download_image(&client, url).await?,
_ => {
return Err(Error::BadRequest(
ErrorKind::Unknown,
@ -161,14 +163,14 @@ async fn request_url_preview(url: String) -> Result<UrlPreviewData> {
}
};
services().media.set_url_preview(&url, &data).await?;
services().media.set_url_preview(url, &data).await?;
Ok(data)
}
#[cfg(feature = "url_preview")]
async fn get_url_preview(url: String) -> Result<UrlPreviewData> {
if let Some(preview) = services().media.get_url_preview(&url).await {
async fn get_url_preview(url: &str) -> Result<UrlPreviewData> {
if let Some(preview) = services().media.get_url_preview(url).await {
return Ok(preview);
}
@ -177,7 +179,7 @@ async fn get_url_preview(url: String) -> Result<UrlPreviewData> {
.url_preview_requests
.read()
.unwrap()
.get(&url)
.get(url)
.cloned();
match notif_opt {
@ -188,15 +190,15 @@ async fn get_url_preview(url: String) -> Result<UrlPreviewData> {
.url_preview_requests
.write()
.unwrap()
.insert(url.clone(), notifier.clone());
.insert(url.to_string(), notifier.clone());
}
let data = request_url_preview(url.clone()).await;
let data = request_url_preview(url).await;
notifier.notify_waiters();
{
services().media.url_preview_requests.write().unwrap().remove(&url);
services().media.url_preview_requests.write().unwrap().remove(url);
}
data
@ -208,7 +210,7 @@ async fn get_url_preview(url: String) -> Result<UrlPreviewData> {
notifier.await;
services().media
.get_url_preview(&url)
.get_url_preview(url)
.await
.ok_or(Error::BadRequest(
ErrorKind::Unknown,
@ -218,6 +220,29 @@ async fn get_url_preview(url: String) -> Result<UrlPreviewData> {
}
}
#[cfg(feature = "url_preview")]
fn url_preview_allowed(url_str: &str) -> bool {
let url = match Url::parse(url_str) {
Ok(u) => u,
Err(_) => return false,
};
if ["http", "https"].iter().all(|&scheme| scheme != url.scheme().to_lowercase()) {
return false;
}
match services().globals.url_preview_mode() {
UrlPreviewMode::All => true,
UrlPreviewMode::None => false,
UrlPreviewMode::Allowlist => {
match url.host_str() {
None => false,
Some(host) => {
services().globals.url_preview_allowlist().contains(&host.to_string())
}
}
}
}
}
/// # `GET /_matrix/media/r0/preview_url`
///
/// Returns URL preview.
@ -225,14 +250,15 @@ async fn get_url_preview(url: String) -> Result<UrlPreviewData> {
pub async fn get_media_preview_route(
body: Ruma<get_media_preview::v3::Request>,
) -> Result<get_media_preview::v3::Response> {
if !services().globals.allow_url_preview() {
let url = &body.url;
if !url_preview_allowed(url) {
return Err(Error::BadRequest(
ErrorKind::Unknown,
"Previewing URL not allowed",
));
}
if let Ok(preview) = get_url_preview(body.url.clone()).await {
if let Ok(preview) = get_url_preview(url).await {
let res = serde_json::value::to_raw_value(&preview).expect("Converting to JSON failed");
return Ok(get_media_preview::v3::Response::from_raw_value(res));
}

View file

@ -13,6 +13,23 @@ mod proxy;
use self::proxy::ProxyConfig;
#[derive(Clone, Copy, Debug, Deserialize)]
pub enum UrlPreviewMode {
All,
None,
Allowlist,
}
impl ToString for UrlPreviewMode {
fn to_string(&self) -> String {
match *self {
UrlPreviewMode::All => "All".to_string(),
UrlPreviewMode::None => "None".to_string(),
UrlPreviewMode::Allowlist => "Allowlist".to_string(),
}
}
}
#[derive(Clone, Debug, Deserialize)]
pub struct Config {
#[serde(default = "default_address")]
@ -53,8 +70,6 @@ pub struct Config {
pub allow_encryption: bool,
#[serde(default = "false_fn")]
pub allow_federation: bool,
#[serde(default = "false_fn")]
pub allow_url_preview: bool,
#[serde(default = "true_fn")]
pub allow_room_creation: bool,
#[serde(default = "true_fn")]
@ -87,6 +102,11 @@ pub struct Config {
pub emergency_password: Option<String>,
#[serde(default = "default_url_preview_mode")]
pub url_preview_mode: UrlPreviewMode,
#[serde(default = "Vec::new")]
pub url_preview_allowlist: Vec<String>,
#[serde(flatten)]
pub catchall: BTreeMap<String, IgnoredAny>,
}
@ -186,7 +206,6 @@ impl fmt::Display for Config {
),
("Allow encryption", &self.allow_encryption.to_string()),
("Allow federation", &self.allow_federation.to_string()),
("Allow URL preview", &self.allow_url_preview.to_string()),
("Allow room creation", &self.allow_room_creation.to_string()),
(
"JWT secret",
@ -235,6 +254,8 @@ impl fmt::Display for Config {
}),
("Well-known server name", well_known_server.as_str()),
("Well-known client URL", &self.well_known_client()),
("URL preview mode", &self.url_preview_mode.to_string()),
("URL preview allowlist", &self.url_preview_allowlist.join(", ")),
];
let mut msg: String = "Active config values:\n\n".to_owned();
@ -315,3 +336,7 @@ fn default_openid_token_ttl() -> u64 {
pub fn default_default_room_version() -> RoomVersionId {
RoomVersionId::V10
}
pub fn default_url_preview_mode() -> UrlPreviewMode {
UrlPreviewMode::None
}

View file

@ -12,6 +12,7 @@ use futures_util::FutureExt;
use hickory_resolver::TokioAsyncResolver;
use hyper_util::client::legacy::connect::dns::{GaiResolver, Name as HyperName};
use reqwest::dns::{Addrs, Name, Resolve, Resolving};
use crate::config::UrlPreviewMode;
use ruma::{
api::{client::sync::sync_events, federation::discovery::ServerSigningKeys},
DeviceId, RoomVersionId, ServerName, UserId,
@ -324,8 +325,12 @@ impl Service {
self.config.allow_federation
}
pub fn allow_url_preview(&self) -> bool {
self.config.allow_url_preview
pub fn url_preview_mode(&self) -> UrlPreviewMode {
self.config.url_preview_mode
}
pub fn url_preview_allowlist(&self) -> &Vec<String> {
&self.config.url_preview_allowlist
}
pub fn allow_room_creation(&self) -> bool {