appservice: Support appservice located on sub path
This commit is contained in:
parent
0bd438e617
commit
937d0aca79
4 changed files with 104 additions and 15 deletions
|
@ -34,6 +34,12 @@ pub enum Error {
|
||||||
#[error("could not convert host:port to socket addr")]
|
#[error("could not convert host:port to socket addr")]
|
||||||
HostPortToSocketAddrs,
|
HostPortToSocketAddrs,
|
||||||
|
|
||||||
|
#[error("uri has empty path")]
|
||||||
|
UriEmptyPath,
|
||||||
|
|
||||||
|
#[error("uri path is unknown")]
|
||||||
|
UriPathUnknown,
|
||||||
|
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
HttpRequest(#[from] ruma::api::error::FromHttpRequestError),
|
HttpRequest(#[from] ruma::api::error::FromHttpRequestError),
|
||||||
|
|
||||||
|
|
|
@ -87,7 +87,7 @@ use std::{
|
||||||
|
|
||||||
use dashmap::DashMap;
|
use dashmap::DashMap;
|
||||||
pub use error::Error;
|
pub use error::Error;
|
||||||
use http::{uri::PathAndQuery, Uri};
|
use http::Uri;
|
||||||
pub use matrix_sdk;
|
pub use matrix_sdk;
|
||||||
#[doc(no_inline)]
|
#[doc(no_inline)]
|
||||||
pub use matrix_sdk::ruma;
|
pub use matrix_sdk::ruma;
|
||||||
|
@ -475,26 +475,63 @@ impl AppService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Transforms [legacy routes] to the correct route so ruma can parse them
|
/// Ruma always expects the path to start with `/_matrix`, so we transform
|
||||||
/// properly
|
/// accordingly. Handles [legacy routes] and appservice being located on a sub
|
||||||
|
/// path.
|
||||||
///
|
///
|
||||||
/// [legacy routes]: https://matrix.org/docs/spec/application_service/r0.1.2#legacy-routes
|
/// [legacy routes]: https://matrix.org/docs/spec/application_service/r0.1.2#legacy-routes
|
||||||
pub(crate) fn transform_legacy_route(
|
// TODO: consider ruma PR
|
||||||
|
pub(crate) fn transform_request_path(
|
||||||
mut request: http::Request<Bytes>,
|
mut request: http::Request<Bytes>,
|
||||||
) -> Result<http::Request<Bytes>> {
|
) -> Result<http::Request<Bytes>> {
|
||||||
let uri = request.uri().to_owned();
|
let uri = request.uri();
|
||||||
|
// remove trailing slash from path
|
||||||
|
let path = uri.path().trim_end_matches('/').to_string();
|
||||||
|
|
||||||
if !uri.path().starts_with("/_matrix/app/v1") {
|
if !path.starts_with("/_matrix/app/v1/") {
|
||||||
// rename legacy routes
|
let path = match path {
|
||||||
let mut parts = uri.into_parts();
|
// special-case paths without value at the end
|
||||||
let path_and_query = match parts.path_and_query {
|
_ if path.ends_with("/_matrix/app/unstable/thirdparty/user") => {
|
||||||
Some(path_and_query) => format!("/_matrix/app/v1{}", path_and_query),
|
"/_matrix/app/v1/thirdparty/user".to_owned()
|
||||||
None => "/_matrix/app/v1".to_owned(),
|
}
|
||||||
|
_ if path.ends_with("/_matrix/app/unstable/thirdparty/location") => {
|
||||||
|
"/_matrix/app/v1/thirdparty/location".to_owned()
|
||||||
|
}
|
||||||
|
// regular paths with values at the end
|
||||||
|
_ => {
|
||||||
|
let mut path = path.split('/').into_iter().rev();
|
||||||
|
let value = match path.next() {
|
||||||
|
Some(value) => value,
|
||||||
|
None => return Err(Error::UriEmptyPath),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut path = match path.next() {
|
||||||
|
Some(path_segment)
|
||||||
|
if ["transactions", "users", "rooms"].contains(&path_segment) =>
|
||||||
|
{
|
||||||
|
format!("/_matrix/app/v1/{}/{}", path_segment, value)
|
||||||
|
}
|
||||||
|
Some(path_segment) => match path.next() {
|
||||||
|
Some(path_segment2) if path_segment2 == "thirdparty" => {
|
||||||
|
format!("/_matrix/app/v1/thirdparty/{}/{}", path_segment, value)
|
||||||
|
}
|
||||||
|
_ => return Err(Error::UriPathUnknown),
|
||||||
|
},
|
||||||
|
None => return Err(Error::UriEmptyPath),
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(query) = uri.query() {
|
||||||
|
path.push_str(&format!("?{}", query));
|
||||||
|
}
|
||||||
|
|
||||||
|
path
|
||||||
|
}
|
||||||
};
|
};
|
||||||
parts.path_and_query =
|
|
||||||
Some(PathAndQuery::try_from(path_and_query).map_err(http::Error::from)?);
|
|
||||||
let uri = parts.try_into().map_err(http::Error::from)?;
|
|
||||||
|
|
||||||
|
let mut parts = uri.clone().into_parts();
|
||||||
|
parts.path_and_query = Some(path.parse()?);
|
||||||
|
|
||||||
|
let uri = parts.try_into().map_err(http::Error::from)?;
|
||||||
*request.uri_mut() = uri;
|
*request.uri_mut() = uri;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -98,7 +98,7 @@ mod filters {
|
||||||
.and(filters::valid_access_token(appservice.registration().hs_token.clone()))
|
.and(filters::valid_access_token(appservice.registration().hs_token.clone()))
|
||||||
.map(move || appservice.clone())
|
.map(move || appservice.clone())
|
||||||
.and(http_request().and_then(|request| async move {
|
.and(http_request().and_then(|request| async move {
|
||||||
let request = crate::transform_legacy_route(request).map_err(Error::from)?;
|
let request = crate::transform_request_path(request).map_err(Error::from)?;
|
||||||
Ok::<http::Request<Bytes>, Rejection>(request)
|
Ok::<http::Request<Bytes>, Rejection>(request)
|
||||||
}))
|
}))
|
||||||
.boxed()
|
.boxed()
|
||||||
|
|
|
@ -11,6 +11,7 @@ use matrix_sdk::{
|
||||||
};
|
};
|
||||||
use matrix_sdk_appservice::*;
|
use matrix_sdk_appservice::*;
|
||||||
use matrix_sdk_test::{appservice::TransactionBuilder, async_test, EventsJson};
|
use matrix_sdk_test::{appservice::TransactionBuilder, async_test, EventsJson};
|
||||||
|
use ruma::room_id;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
#[cfg(feature = "warp")]
|
#[cfg(feature = "warp")]
|
||||||
use warp::{Filter, Reply};
|
use warp::{Filter, Reply};
|
||||||
|
@ -271,6 +272,51 @@ async fn test_unrelated_path() -> Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[async_test]
|
||||||
|
async fn test_appservice_on_sub_path() -> Result<()> {
|
||||||
|
let room_id = room_id!("!SVkFJHzfwvuaIEawgC:localhost");
|
||||||
|
let uri_1 = "/sub_path/_matrix/app/v1/transactions/1?access_token=hs_token";
|
||||||
|
let uri_2 = "/sub_path/_matrix/app/v1/transactions/2?access_token=hs_token";
|
||||||
|
|
||||||
|
let mut transaction_builder = TransactionBuilder::new();
|
||||||
|
transaction_builder.add_room_event(EventsJson::Member);
|
||||||
|
let transaction_1 = transaction_builder.build_json_transaction();
|
||||||
|
|
||||||
|
let mut transaction_builder = TransactionBuilder::new();
|
||||||
|
transaction_builder.add_room_event(EventsJson::MemberNameChange);
|
||||||
|
let transaction_2 = transaction_builder.build_json_transaction();
|
||||||
|
|
||||||
|
let appservice = appservice(None).await?;
|
||||||
|
|
||||||
|
#[cfg(feature = "warp")]
|
||||||
|
{
|
||||||
|
warp::test::request()
|
||||||
|
.method("PUT")
|
||||||
|
.path(uri_1)
|
||||||
|
.json(&transaction_1)
|
||||||
|
.filter(&warp::path("sub_path").and(appservice.warp_filter()))
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
warp::test::request()
|
||||||
|
.method("PUT")
|
||||||
|
.path(uri_2)
|
||||||
|
.json(&transaction_2)
|
||||||
|
.filter(&warp::path("sub_path").and(appservice.warp_filter()))
|
||||||
|
.await?;
|
||||||
|
};
|
||||||
|
|
||||||
|
let members = appservice
|
||||||
|
.get_cached_client(None)?
|
||||||
|
.get_room(&room_id)
|
||||||
|
.expect("Expected room to be availabe")
|
||||||
|
.members_no_sync()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
assert_eq!(members[0].display_name().unwrap(), "changed");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
mod registration {
|
mod registration {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue