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:
parent
bb4cade9fd
commit
61fd9166f6
5 changed files with 90 additions and 26 deletions
|
@ -47,9 +47,6 @@ registration_token = ""
|
||||||
allow_check_for_updates = true
|
allow_check_for_updates = true
|
||||||
allow_federation = true
|
allow_federation = true
|
||||||
|
|
||||||
# Allows clients to request a URL preview
|
|
||||||
allow_url_preview = false
|
|
||||||
|
|
||||||
# Enable the display name lightning bolt on registration.
|
# Enable the display name lightning bolt on registration.
|
||||||
enable_lightning_bolt = true
|
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 = "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.
|
#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]
|
[global.well_known]
|
||||||
# Conduit handles the /.well-known/matrix/* endpoints, making both clients and servers try to access conduit with the host
|
# 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.
|
# server_name and port 443 by default.
|
||||||
|
|
10
debian/postinst
vendored
10
debian/postinst
vendored
|
@ -84,9 +84,6 @@ allow_check_for_updates = true
|
||||||
# Enable the display name lightning bolt on registration.
|
# Enable the display name lightning bolt on registration.
|
||||||
enable_lightning_bolt = true
|
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.
|
# Servers listed here will be used to gather public keys of other servers.
|
||||||
# Generally, copying this exactly should be enough. (Currently, Conduit doesn't
|
# Generally, copying this exactly should be enough. (Currently, Conduit doesn't
|
||||||
# support batched key requests, so this list should only contain Synapse
|
# 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
|
# [0]: https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html#directives
|
||||||
#log = "..."
|
#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
|
EOF
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
|
|
|
@ -11,9 +11,11 @@ use ruma::api::client::{
|
||||||
|
|
||||||
#[cfg(feature = "url_preview")]
|
#[cfg(feature = "url_preview")]
|
||||||
use {
|
use {
|
||||||
|
crate::config::UrlPreviewMode,
|
||||||
crate::service::media::UrlPreviewData,
|
crate::service::media::UrlPreviewData,
|
||||||
webpage::HTML,
|
webpage::HTML,
|
||||||
std::{io::Cursor, net::IpAddr, sync::Arc, time::Duration},
|
reqwest::Url,
|
||||||
|
std::{io::Cursor, net::IpAddr, sync::Arc},
|
||||||
tokio::sync::Notify,
|
tokio::sync::Notify,
|
||||||
image::io::Reader as ImgReader,
|
image::io::Reader as ImgReader,
|
||||||
};
|
};
|
||||||
|
@ -123,9 +125,9 @@ fn url_request_allowed(addr: &IpAddr) -> bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "url_preview")]
|
#[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 client = services().globals.default_client();
|
||||||
let response = client.head(&url).send().await?;
|
let response = client.head(url).send().await?;
|
||||||
|
|
||||||
if !response
|
if !response
|
||||||
.remote_addr()
|
.remote_addr()
|
||||||
|
@ -151,8 +153,8 @@ async fn request_url_preview(url: String) -> Result<UrlPreviewData> {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let data = match content_type {
|
let data = match content_type {
|
||||||
html if html.starts_with("text/html") => download_html(&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?,
|
img if img.starts_with("image/") => download_image(&client, url).await?,
|
||||||
_ => {
|
_ => {
|
||||||
return Err(Error::BadRequest(
|
return Err(Error::BadRequest(
|
||||||
ErrorKind::Unknown,
|
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)
|
Ok(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "url_preview")]
|
#[cfg(feature = "url_preview")]
|
||||||
async fn get_url_preview(url: String) -> Result<UrlPreviewData> {
|
async fn get_url_preview(url: &str) -> Result<UrlPreviewData> {
|
||||||
if let Some(preview) = services().media.get_url_preview(&url).await {
|
if let Some(preview) = services().media.get_url_preview(url).await {
|
||||||
return Ok(preview);
|
return Ok(preview);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -177,7 +179,7 @@ async fn get_url_preview(url: String) -> Result<UrlPreviewData> {
|
||||||
.url_preview_requests
|
.url_preview_requests
|
||||||
.read()
|
.read()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.get(&url)
|
.get(url)
|
||||||
.cloned();
|
.cloned();
|
||||||
|
|
||||||
match notif_opt {
|
match notif_opt {
|
||||||
|
@ -188,15 +190,15 @@ async fn get_url_preview(url: String) -> Result<UrlPreviewData> {
|
||||||
.url_preview_requests
|
.url_preview_requests
|
||||||
.write()
|
.write()
|
||||||
.unwrap()
|
.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();
|
notifier.notify_waiters();
|
||||||
|
|
||||||
{
|
{
|
||||||
services().media.url_preview_requests.write().unwrap().remove(&url);
|
services().media.url_preview_requests.write().unwrap().remove(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
data
|
data
|
||||||
|
@ -208,7 +210,7 @@ async fn get_url_preview(url: String) -> Result<UrlPreviewData> {
|
||||||
notifier.await;
|
notifier.await;
|
||||||
|
|
||||||
services().media
|
services().media
|
||||||
.get_url_preview(&url)
|
.get_url_preview(url)
|
||||||
.await
|
.await
|
||||||
.ok_or(Error::BadRequest(
|
.ok_or(Error::BadRequest(
|
||||||
ErrorKind::Unknown,
|
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`
|
/// # `GET /_matrix/media/r0/preview_url`
|
||||||
///
|
///
|
||||||
/// Returns URL preview.
|
/// Returns URL preview.
|
||||||
|
@ -225,14 +250,15 @@ async fn get_url_preview(url: String) -> Result<UrlPreviewData> {
|
||||||
pub async fn get_media_preview_route(
|
pub async fn get_media_preview_route(
|
||||||
body: Ruma<get_media_preview::v3::Request>,
|
body: Ruma<get_media_preview::v3::Request>,
|
||||||
) -> Result<get_media_preview::v3::Response> {
|
) -> 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(
|
return Err(Error::BadRequest(
|
||||||
ErrorKind::Unknown,
|
ErrorKind::Unknown,
|
||||||
"Previewing URL not allowed",
|
"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");
|
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));
|
return Ok(get_media_preview::v3::Response::from_raw_value(res));
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,23 @@ mod proxy;
|
||||||
|
|
||||||
use self::proxy::ProxyConfig;
|
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)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
#[serde(default = "default_address")]
|
#[serde(default = "default_address")]
|
||||||
|
@ -53,8 +70,6 @@ pub struct Config {
|
||||||
pub allow_encryption: bool,
|
pub allow_encryption: bool,
|
||||||
#[serde(default = "false_fn")]
|
#[serde(default = "false_fn")]
|
||||||
pub allow_federation: bool,
|
pub allow_federation: bool,
|
||||||
#[serde(default = "false_fn")]
|
|
||||||
pub allow_url_preview: bool,
|
|
||||||
#[serde(default = "true_fn")]
|
#[serde(default = "true_fn")]
|
||||||
pub allow_room_creation: bool,
|
pub allow_room_creation: bool,
|
||||||
#[serde(default = "true_fn")]
|
#[serde(default = "true_fn")]
|
||||||
|
@ -87,6 +102,11 @@ pub struct Config {
|
||||||
|
|
||||||
pub emergency_password: Option<String>,
|
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)]
|
#[serde(flatten)]
|
||||||
pub catchall: BTreeMap<String, IgnoredAny>,
|
pub catchall: BTreeMap<String, IgnoredAny>,
|
||||||
}
|
}
|
||||||
|
@ -186,7 +206,6 @@ impl fmt::Display for Config {
|
||||||
),
|
),
|
||||||
("Allow encryption", &self.allow_encryption.to_string()),
|
("Allow encryption", &self.allow_encryption.to_string()),
|
||||||
("Allow federation", &self.allow_federation.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()),
|
("Allow room creation", &self.allow_room_creation.to_string()),
|
||||||
(
|
(
|
||||||
"JWT secret",
|
"JWT secret",
|
||||||
|
@ -235,6 +254,8 @@ impl fmt::Display for Config {
|
||||||
}),
|
}),
|
||||||
("Well-known server name", well_known_server.as_str()),
|
("Well-known server name", well_known_server.as_str()),
|
||||||
("Well-known client URL", &self.well_known_client()),
|
("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();
|
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 {
|
pub fn default_default_room_version() -> RoomVersionId {
|
||||||
RoomVersionId::V10
|
RoomVersionId::V10
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn default_url_preview_mode() -> UrlPreviewMode {
|
||||||
|
UrlPreviewMode::None
|
||||||
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ use futures_util::FutureExt;
|
||||||
use hickory_resolver::TokioAsyncResolver;
|
use hickory_resolver::TokioAsyncResolver;
|
||||||
use hyper_util::client::legacy::connect::dns::{GaiResolver, Name as HyperName};
|
use hyper_util::client::legacy::connect::dns::{GaiResolver, Name as HyperName};
|
||||||
use reqwest::dns::{Addrs, Name, Resolve, Resolving};
|
use reqwest::dns::{Addrs, Name, Resolve, Resolving};
|
||||||
|
use crate::config::UrlPreviewMode;
|
||||||
use ruma::{
|
use ruma::{
|
||||||
api::{client::sync::sync_events, federation::discovery::ServerSigningKeys},
|
api::{client::sync::sync_events, federation::discovery::ServerSigningKeys},
|
||||||
DeviceId, RoomVersionId, ServerName, UserId,
|
DeviceId, RoomVersionId, ServerName, UserId,
|
||||||
|
@ -324,8 +325,12 @@ impl Service {
|
||||||
self.config.allow_federation
|
self.config.allow_federation
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn allow_url_preview(&self) -> bool {
|
pub fn url_preview_mode(&self) -> UrlPreviewMode {
|
||||||
self.config.allow_url_preview
|
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 {
|
pub fn allow_room_creation(&self) -> bool {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue