appservice: Support appservice located on sub path
parent
0bd438e617
commit
937d0aca79
|
@ -34,6 +34,12 @@ pub enum Error {
|
|||
#[error("could not convert host:port to socket addr")]
|
||||
HostPortToSocketAddrs,
|
||||
|
||||
#[error("uri has empty path")]
|
||||
UriEmptyPath,
|
||||
|
||||
#[error("uri path is unknown")]
|
||||
UriPathUnknown,
|
||||
|
||||
#[error(transparent)]
|
||||
HttpRequest(#[from] ruma::api::error::FromHttpRequestError),
|
||||
|
||||
|
|
|
@ -87,7 +87,7 @@ use std::{
|
|||
|
||||
use dashmap::DashMap;
|
||||
pub use error::Error;
|
||||
use http::{uri::PathAndQuery, Uri};
|
||||
use http::Uri;
|
||||
pub use matrix_sdk;
|
||||
#[doc(no_inline)]
|
||||
pub use matrix_sdk::ruma;
|
||||
|
@ -475,26 +475,63 @@ impl AppService {
|
|||
}
|
||||
}
|
||||
|
||||
/// Transforms [legacy routes] to the correct route so ruma can parse them
|
||||
/// properly
|
||||
/// Ruma always expects the path to start with `/_matrix`, so we transform
|
||||
/// 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
|
||||
pub(crate) fn transform_legacy_route(
|
||||
// TODO: consider ruma PR
|
||||
pub(crate) fn transform_request_path(
|
||||
mut request: 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") {
|
||||
// rename legacy routes
|
||||
let mut parts = uri.into_parts();
|
||||
let path_and_query = match parts.path_and_query {
|
||||
Some(path_and_query) => format!("/_matrix/app/v1{}", path_and_query),
|
||||
None => "/_matrix/app/v1".to_owned(),
|
||||
if !path.starts_with("/_matrix/app/v1/") {
|
||||
let path = match path {
|
||||
// special-case paths without value at the end
|
||||
_ if path.ends_with("/_matrix/app/unstable/thirdparty/user") => {
|
||||
"/_matrix/app/v1/thirdparty/user".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;
|
||||
}
|
||||
|
||||
|
|
|
@ -98,7 +98,7 @@ mod filters {
|
|||
.and(filters::valid_access_token(appservice.registration().hs_token.clone()))
|
||||
.map(move || appservice.clone())
|
||||
.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)
|
||||
}))
|
||||
.boxed()
|
||||
|
|
|
@ -11,6 +11,7 @@ use matrix_sdk::{
|
|||
};
|
||||
use matrix_sdk_appservice::*;
|
||||
use matrix_sdk_test::{appservice::TransactionBuilder, async_test, EventsJson};
|
||||
use ruma::room_id;
|
||||
use serde_json::json;
|
||||
#[cfg(feature = "warp")]
|
||||
use warp::{Filter, Reply};
|
||||
|
@ -271,6 +272,51 @@ async fn test_unrelated_path() -> Result<()> {
|
|||
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 {
|
||||
use super::*;
|
||||
|
||||
|
|
Loading…
Reference in New Issue