Merge branch 'appservice/feature/warp'
commit
4a83e36195
|
@ -94,14 +94,25 @@ jobs:
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
name:
|
name:
|
||||||
- linux / appservice / stable
|
- linux / appservice / stable / actix
|
||||||
- macOS / appservice / stable
|
- macOS / appservice / stable / actix
|
||||||
|
- linux / appservice / stable / warp
|
||||||
|
- macOS / appservice / stable / warp
|
||||||
|
|
||||||
include:
|
include:
|
||||||
- name: linux / appservice / stable
|
- name: linux / appservice / stable / actix
|
||||||
|
cargo_args: --features actix
|
||||||
|
|
||||||
- name: macOS / appservice / stable
|
- name: macOS / appservice / stable / actix
|
||||||
os: macOS-latest
|
os: macOS-latest
|
||||||
|
cargo_args: --features actix
|
||||||
|
|
||||||
|
- name: linux / appservice / stable / warp
|
||||||
|
cargo_args: --features warp
|
||||||
|
|
||||||
|
- name: macOS / appservice / stable / warp
|
||||||
|
os: macOS-latest
|
||||||
|
cargo_args: --features warp
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
|
@ -119,19 +130,19 @@ jobs:
|
||||||
uses: actions-rs/cargo@v1
|
uses: actions-rs/cargo@v1
|
||||||
with:
|
with:
|
||||||
command: clippy
|
command: clippy
|
||||||
args: --manifest-path matrix_sdk_appservice/Cargo.toml -- -D warnings
|
args: --manifest-path matrix_sdk_appservice/Cargo.toml ${{ matrix.cargo_args }} -- -D warnings
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
uses: actions-rs/cargo@v1
|
uses: actions-rs/cargo@v1
|
||||||
with:
|
with:
|
||||||
command: build
|
command: build
|
||||||
args: --manifest-path matrix_sdk_appservice/Cargo.toml
|
args: --manifest-path matrix_sdk_appservice/Cargo.toml ${{ matrix.cargo_args }}
|
||||||
|
|
||||||
- name: Test
|
- name: Test
|
||||||
uses: actions-rs/cargo@v1
|
uses: actions-rs/cargo@v1
|
||||||
with:
|
with:
|
||||||
command: test
|
command: test
|
||||||
args: --manifest-path matrix_sdk_appservice/Cargo.toml
|
args: --manifest-path matrix_sdk_appservice/Cargo.toml ${{ matrix.cargo_args }}
|
||||||
|
|
||||||
test-features:
|
test-features:
|
||||||
name: ${{ matrix.name }}
|
name: ${{ matrix.name }}
|
||||||
|
|
|
@ -480,8 +480,8 @@ impl RequestConfig {
|
||||||
|
|
||||||
/// Force sending authorization even if the endpoint does not require it.
|
/// Force sending authorization even if the endpoint does not require it.
|
||||||
/// Default is only sending authorization if it is required
|
/// Default is only sending authorization if it is required
|
||||||
#[cfg(feature = "require_auth_for_profile_requests")]
|
#[cfg(any(feature = "require_auth_for_profile_requests", feature = "appservice"))]
|
||||||
#[cfg_attr(feature = "docs", doc(cfg(require_auth_for_profile_requests)))]
|
#[cfg_attr(feature = "docs", doc(cfg(any(require_auth_for_profile_requests, appservice))))]
|
||||||
pub(crate) fn force_auth(mut self) -> Self {
|
pub(crate) fn force_auth(mut self) -> Self {
|
||||||
self.force_auth = true;
|
self.force_auth = true;
|
||||||
self
|
self
|
||||||
|
@ -1363,8 +1363,14 @@ impl Client {
|
||||||
) -> Result<register::Response> {
|
) -> Result<register::Response> {
|
||||||
info!("Registering to {}", self.homeserver().await);
|
info!("Registering to {}", self.homeserver().await);
|
||||||
|
|
||||||
|
#[cfg(not(feature = "appservice"))]
|
||||||
|
let config = None;
|
||||||
|
|
||||||
|
#[cfg(feature = "appservice")]
|
||||||
|
let config = Some(self.http_client.request_config.force_auth());
|
||||||
|
|
||||||
let request = registration.into();
|
let request = registration.into();
|
||||||
self.send(request, None).await
|
self.send(request, config).await
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get or upload a sync filter.
|
/// Get or upload a sync filter.
|
||||||
|
|
|
@ -8,10 +8,10 @@ name = "matrix-sdk-appservice"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["actix"]
|
default = []
|
||||||
actix = ["actix-rt", "actix-web"]
|
actix = ["actix-rt", "actix-web"]
|
||||||
|
|
||||||
docs = []
|
docs = ["actix", "warp"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix-rt = { version = "2", optional = true }
|
actix-rt = { version = "2", optional = true }
|
||||||
|
@ -21,11 +21,13 @@ futures = "0.3"
|
||||||
futures-util = "0.3"
|
futures-util = "0.3"
|
||||||
http = "0.2"
|
http = "0.2"
|
||||||
regex = "1"
|
regex = "1"
|
||||||
serde = "1.0.126"
|
serde = "1"
|
||||||
|
serde_json = "1"
|
||||||
serde_yaml = "0.8"
|
serde_yaml = "0.8"
|
||||||
thiserror = "1.0"
|
thiserror = "1.0"
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
url = "2"
|
url = "2"
|
||||||
|
warp = { git = "https://github.com/seanmonstar/warp.git", rev = "629405", optional = true, default-features = false, features = ["multipart", "websocket"] }
|
||||||
|
|
||||||
matrix-sdk = { version = "0.2", path = "../matrix_sdk", default-features = false, features = ["appservice", "native-tls"] }
|
matrix-sdk = { version = "0.2", path = "../matrix_sdk", default-features = false, features = ["appservice", "native-tls"] }
|
||||||
|
|
||||||
|
@ -36,12 +38,11 @@ features = ["client-api-c", "appservice-api-s", "unstable-pre-spec"]
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
env_logger = "0.8"
|
env_logger = "0.8"
|
||||||
mockito = "0.30"
|
mockito = "0.30"
|
||||||
serde_json = "1"
|
|
||||||
tokio = { version = "1", default-features = false, features = ["rt-multi-thread", "macros"] }
|
tokio = { version = "1", default-features = false, features = ["rt-multi-thread", "macros"] }
|
||||||
tracing-subscriber = "0.2"
|
tracing-subscriber = "0.2"
|
||||||
|
|
||||||
matrix-sdk-test = { version = "0.2", path = "../matrix_sdk_test" }
|
matrix-sdk-test = { version = "0.2", path = "../matrix_sdk_test", features = ["appservice"] }
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "actix_autojoin"
|
name = "autojoin"
|
||||||
required-features = ["actix"]
|
required-features = ["warp"]
|
||||||
|
|
|
@ -1,67 +0,0 @@
|
||||||
use std::{convert::TryFrom, env};
|
|
||||||
|
|
||||||
use actix_web::{App, HttpServer};
|
|
||||||
use matrix_sdk::{
|
|
||||||
async_trait,
|
|
||||||
events::{
|
|
||||||
room::member::{MemberEventContent, MembershipState},
|
|
||||||
SyncStateEvent,
|
|
||||||
},
|
|
||||||
identifiers::UserId,
|
|
||||||
room::Room,
|
|
||||||
EventHandler,
|
|
||||||
};
|
|
||||||
use matrix_sdk_appservice::{Appservice, AppserviceRegistration};
|
|
||||||
|
|
||||||
struct AppserviceEventHandler {
|
|
||||||
appservice: Appservice,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AppserviceEventHandler {
|
|
||||||
pub fn new(appservice: Appservice) -> Self {
|
|
||||||
Self { appservice }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
impl EventHandler for AppserviceEventHandler {
|
|
||||||
async fn on_room_member(&self, room: Room, event: &SyncStateEvent<MemberEventContent>) {
|
|
||||||
if !self.appservice.user_id_is_in_namespace(&event.state_key).unwrap() {
|
|
||||||
dbg!("not an appservice user");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let MembershipState::Invite = event.content.membership {
|
|
||||||
let user_id = UserId::try_from(event.state_key.clone()).unwrap();
|
|
||||||
|
|
||||||
let mut appservice = self.appservice.clone();
|
|
||||||
appservice.register(user_id.localpart()).await.unwrap();
|
|
||||||
|
|
||||||
let client = appservice.virtual_user(user_id.localpart()).await.unwrap();
|
|
||||||
|
|
||||||
client.join_room_by_id(room.room_id()).await.unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[actix_web::main]
|
|
||||||
pub async fn main() -> std::io::Result<()> {
|
|
||||||
env::set_var("RUST_LOG", "actix_web=debug,actix_server=info,matrix_sdk=debug");
|
|
||||||
tracing_subscriber::fmt::init();
|
|
||||||
|
|
||||||
let homeserver_url = "http://localhost:8008";
|
|
||||||
let server_name = "localhost";
|
|
||||||
let registration =
|
|
||||||
AppserviceRegistration::try_from_yaml_file("./tests/registration.yaml").unwrap();
|
|
||||||
|
|
||||||
let mut appservice = Appservice::new(homeserver_url, server_name, registration).await.unwrap();
|
|
||||||
|
|
||||||
let event_handler = AppserviceEventHandler::new(appservice.clone());
|
|
||||||
|
|
||||||
appservice.set_event_handler(Box::new(event_handler)).await.unwrap();
|
|
||||||
|
|
||||||
HttpServer::new(move || App::new().service(appservice.actix_service()))
|
|
||||||
.bind(("0.0.0.0", 8090))?
|
|
||||||
.run()
|
|
||||||
.await
|
|
||||||
}
|
|
|
@ -0,0 +1,75 @@
|
||||||
|
use std::{convert::TryFrom, env};
|
||||||
|
|
||||||
|
use matrix_sdk_appservice::{
|
||||||
|
matrix_sdk::{
|
||||||
|
async_trait,
|
||||||
|
events::{
|
||||||
|
room::member::{MemberEventContent, MembershipState},
|
||||||
|
SyncStateEvent,
|
||||||
|
},
|
||||||
|
identifiers::UserId,
|
||||||
|
room::Room,
|
||||||
|
EventHandler,
|
||||||
|
},
|
||||||
|
Appservice, AppserviceRegistration,
|
||||||
|
};
|
||||||
|
use tracing::{error, trace};
|
||||||
|
|
||||||
|
struct AppserviceEventHandler {
|
||||||
|
appservice: Appservice,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AppserviceEventHandler {
|
||||||
|
pub fn new(appservice: Appservice) -> Self {
|
||||||
|
Self { appservice }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn handle_room_member(
|
||||||
|
&self,
|
||||||
|
room: Room,
|
||||||
|
event: &SyncStateEvent<MemberEventContent>,
|
||||||
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
if !self.appservice.user_id_is_in_namespace(&event.state_key)? {
|
||||||
|
trace!("not an appservice user: {}", event.state_key);
|
||||||
|
} else if let MembershipState::Invite = event.content.membership {
|
||||||
|
let user_id = UserId::try_from(event.state_key.clone())?;
|
||||||
|
|
||||||
|
let appservice = self.appservice.clone();
|
||||||
|
appservice.register_virtual_user(user_id.localpart()).await?;
|
||||||
|
|
||||||
|
let client = appservice.virtual_user_client(user_id.localpart()).await?;
|
||||||
|
|
||||||
|
client.join_room_by_id(room.room_id()).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl EventHandler for AppserviceEventHandler {
|
||||||
|
async fn on_room_member(&self, room: Room, event: &SyncStateEvent<MemberEventContent>) {
|
||||||
|
match self.handle_room_member(room, event).await {
|
||||||
|
Ok(_) => (),
|
||||||
|
Err(error) => error!("{:?}", error),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
pub async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
env::set_var("RUST_LOG", "matrix_sdk=debug,matrix_sdk_appservice=debug");
|
||||||
|
tracing_subscriber::fmt::init();
|
||||||
|
|
||||||
|
let homeserver_url = "http://localhost:8008";
|
||||||
|
let server_name = "localhost";
|
||||||
|
let registration = AppserviceRegistration::try_from_yaml_file("./tests/registration.yaml")?;
|
||||||
|
|
||||||
|
let mut appservice = Appservice::new(homeserver_url, server_name, registration).await?;
|
||||||
|
appservice.set_event_handler(Box::new(AppserviceEventHandler::new(appservice.clone()))).await?;
|
||||||
|
|
||||||
|
let (host, port) = appservice.registration().get_host_and_port()?;
|
||||||
|
appservice.run(host, port).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
|
@ -31,6 +31,9 @@ pub enum Error {
|
||||||
#[error("no client for localpart found")]
|
#[error("no client for localpart found")]
|
||||||
NoClientForLocalpart,
|
NoClientForLocalpart,
|
||||||
|
|
||||||
|
#[error("could not convert host:port to socket addr")]
|
||||||
|
HostPortToSocketAddrs,
|
||||||
|
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
HttpRequest(#[from] ruma::api::error::FromHttpRequestError),
|
HttpRequest(#[from] ruma::api::error::FromHttpRequestError),
|
||||||
|
|
||||||
|
@ -61,6 +64,13 @@ pub enum Error {
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
SerdeYaml(#[from] serde_yaml::Error),
|
SerdeYaml(#[from] serde_yaml::Error),
|
||||||
|
|
||||||
|
#[error(transparent)]
|
||||||
|
SerdeJson(#[from] serde_json::Error),
|
||||||
|
|
||||||
|
#[cfg(feature = "warp")]
|
||||||
|
#[error("warp rejection: {0}")]
|
||||||
|
WarpRejection(String),
|
||||||
|
|
||||||
#[cfg(feature = "actix")]
|
#[cfg(feature = "actix")]
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
Actix(#[from] actix_web::Error),
|
Actix(#[from] actix_web::Error),
|
||||||
|
@ -72,3 +82,13 @@ pub enum Error {
|
||||||
|
|
||||||
#[cfg(feature = "actix")]
|
#[cfg(feature = "actix")]
|
||||||
impl actix_web::error::ResponseError for Error {}
|
impl actix_web::error::ResponseError for Error {}
|
||||||
|
|
||||||
|
#[cfg(feature = "warp")]
|
||||||
|
impl warp::reject::Reject for Error {}
|
||||||
|
|
||||||
|
#[cfg(feature = "warp")]
|
||||||
|
impl From<warp::Rejection> for Error {
|
||||||
|
fn from(rejection: warp::Rejection) -> Self {
|
||||||
|
Self::WarpRejection(format!("{:?}", rejection))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -36,10 +36,10 @@
|
||||||
//! #
|
//! #
|
||||||
//! # use matrix_sdk::{async_trait, EventHandler};
|
//! # use matrix_sdk::{async_trait, EventHandler};
|
||||||
//! #
|
//! #
|
||||||
//! # struct AppserviceEventHandler;
|
//! # struct MyEventHandler;
|
||||||
//! #
|
//! #
|
||||||
//! # #[async_trait]
|
//! # #[async_trait]
|
||||||
//! # impl EventHandler for AppserviceEventHandler {}
|
//! # impl EventHandler for MyEventHandler {}
|
||||||
//! #
|
//! #
|
||||||
//! use matrix_sdk_appservice::{Appservice, AppserviceRegistration};
|
//! use matrix_sdk_appservice::{Appservice, AppserviceRegistration};
|
||||||
//!
|
//!
|
||||||
|
@ -59,7 +59,7 @@
|
||||||
//! ")?;
|
//! ")?;
|
||||||
//!
|
//!
|
||||||
//! let mut appservice = Appservice::new(homeserver_url, server_name, registration).await?;
|
//! let mut appservice = Appservice::new(homeserver_url, server_name, registration).await?;
|
||||||
//! appservice.set_event_handler(Box::new(AppserviceEventHandler)).await?;
|
//! appservice.set_event_handler(Box::new(MyEventHandler)).await?;
|
||||||
//!
|
//!
|
||||||
//! let (host, port) = appservice.registration().get_host_and_port()?;
|
//! let (host, port) = appservice.registration().get_host_and_port()?;
|
||||||
//! appservice.run(host, port).await?;
|
//! appservice.run(host, port).await?;
|
||||||
|
@ -74,8 +74,8 @@
|
||||||
//! [matrix-org/matrix-rust-sdk#228]: https://github.com/matrix-org/matrix-rust-sdk/issues/228
|
//! [matrix-org/matrix-rust-sdk#228]: https://github.com/matrix-org/matrix-rust-sdk/issues/228
|
||||||
//! [examples directory]: https://github.com/matrix-org/matrix-rust-sdk/tree/master/matrix_sdk_appservice/examples
|
//! [examples directory]: https://github.com/matrix-org/matrix-rust-sdk/tree/master/matrix_sdk_appservice/examples
|
||||||
|
|
||||||
#[cfg(not(any(feature = "actix",)))]
|
#[cfg(not(any(feature = "actix", feature = "warp")))]
|
||||||
compile_error!("one webserver feature must be enabled. available ones: `actix`");
|
compile_error!("one webserver feature must be enabled. available ones: `actix`, `warp`");
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
convert::{TryFrom, TryInto},
|
convert::{TryFrom, TryInto},
|
||||||
|
@ -86,32 +86,28 @@ use std::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use dashmap::DashMap;
|
use dashmap::DashMap;
|
||||||
use http::Uri;
|
pub use error::Error;
|
||||||
use matrix_sdk::{reqwest::Url, Client, ClientConfig, EventHandler, HttpError, Session};
|
use http::{uri::PathAndQuery, Uri};
|
||||||
|
pub use matrix_sdk;
|
||||||
|
use matrix_sdk::{reqwest::Url, Bytes, Client, ClientConfig, EventHandler, HttpError, Session};
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
pub use ruma::api::appservice as api;
|
pub use ruma::api::{appservice as api, appservice::Registration};
|
||||||
use ruma::{
|
use ruma::{
|
||||||
api::{
|
api::{
|
||||||
appservice::Registration,
|
|
||||||
client::{
|
client::{
|
||||||
error::ErrorKind,
|
error::ErrorKind,
|
||||||
r0::{
|
r0::{account::register, uiaa::UiaaResponse},
|
||||||
account::register::{LoginType, Request as RegistrationRequest},
|
|
||||||
uiaa::UiaaResponse,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
error::{FromHttpResponseError, ServerError},
|
error::{FromHttpResponseError, ServerError},
|
||||||
},
|
},
|
||||||
assign, identifiers, DeviceId, ServerNameBox, UserId,
|
assign, identifiers, DeviceId, ServerNameBox, UserId,
|
||||||
};
|
};
|
||||||
use tracing::warn;
|
use tracing::{info, warn};
|
||||||
|
|
||||||
#[cfg(feature = "actix")]
|
|
||||||
mod actix;
|
|
||||||
mod error;
|
mod error;
|
||||||
|
mod webserver;
|
||||||
|
|
||||||
pub use error::Error;
|
|
||||||
pub type Result<T> = std::result::Result<T, Error>;
|
pub type Result<T> = std::result::Result<T, Error>;
|
||||||
pub type Host = String;
|
pub type Host = String;
|
||||||
pub type Port = u16;
|
pub type Port = u16;
|
||||||
|
@ -250,8 +246,8 @@ impl Appservice {
|
||||||
|
|
||||||
let appservice = Appservice { homeserver_url, server_name, registration, clients };
|
let appservice = Appservice { homeserver_url, server_name, registration, clients };
|
||||||
|
|
||||||
// we cache the [`MainUser`] by default
|
// we create and cache the [`MainUser`] by default
|
||||||
appservice.virtual_user_with_config(sender_localpart, client_config).await?;
|
appservice.create_and_cache_client(&sender_localpart, client_config).await?;
|
||||||
|
|
||||||
Ok(appservice)
|
Ok(appservice)
|
||||||
}
|
}
|
||||||
|
@ -267,24 +263,29 @@ impl Appservice {
|
||||||
/// by calling this method again or by calling [`Self::get_cached_client()`]
|
/// by calling this method again or by calling [`Self::get_cached_client()`]
|
||||||
/// which is non-async convenience wrapper.
|
/// which is non-async convenience wrapper.
|
||||||
///
|
///
|
||||||
|
/// Note that if you want to do actions like joining rooms with a virtual
|
||||||
|
/// user it needs to be registered first. `Self::register_virtual_user()`
|
||||||
|
/// can be used for that purpose.
|
||||||
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
///
|
///
|
||||||
/// * `localpart` - The localpart of the user we want assert our identity to
|
/// * `localpart` - The localpart of the user we want assert our identity to
|
||||||
///
|
///
|
||||||
/// [registration]: https://matrix.org/docs/spec/application_service/r0.1.2#registration
|
/// [registration]: https://matrix.org/docs/spec/application_service/r0.1.2#registration
|
||||||
/// [assert the identity]: https://matrix.org/docs/spec/application_service/r0.1.2#identity-assertion
|
/// [assert the identity]: https://matrix.org/docs/spec/application_service/r0.1.2#identity-assertion
|
||||||
pub async fn virtual_user(&self, localpart: impl AsRef<str>) -> Result<Client> {
|
pub async fn virtual_user_client(&self, localpart: impl AsRef<str>) -> Result<Client> {
|
||||||
let client = self.virtual_user_with_config(localpart, ClientConfig::default()).await?;
|
let client =
|
||||||
|
self.virtual_user_client_with_config(localpart, ClientConfig::default()).await?;
|
||||||
|
|
||||||
Ok(client)
|
Ok(client)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Same as [`Self::virtual_user()`] but with the ability to pass in a
|
/// Same as [`Self::virtual_user_client()`] but with the ability to pass in
|
||||||
/// [`ClientConfig`]
|
/// a [`ClientConfig`]
|
||||||
///
|
///
|
||||||
/// Since this method is a singleton follow-up calls with different
|
/// Since this method is a singleton follow-up calls with different
|
||||||
/// [`ClientConfig`]s will be ignored.
|
/// [`ClientConfig`]s will be ignored.
|
||||||
pub async fn virtual_user_with_config(
|
pub async fn virtual_user_client_with_config(
|
||||||
&self,
|
&self,
|
||||||
localpart: impl AsRef<str>,
|
localpart: impl AsRef<str>,
|
||||||
config: ClientConfig,
|
config: ClientConfig,
|
||||||
|
@ -295,14 +296,28 @@ impl Appservice {
|
||||||
let client = if let Some(client) = self.clients.get(localpart) {
|
let client = if let Some(client) = self.clients.get(localpart) {
|
||||||
client.clone()
|
client.clone()
|
||||||
} else {
|
} else {
|
||||||
|
self.create_and_cache_client(localpart, config).await?
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(client)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn create_and_cache_client(
|
||||||
|
&self,
|
||||||
|
localpart: &str,
|
||||||
|
config: ClientConfig,
|
||||||
|
) -> Result<Client> {
|
||||||
let user_id = UserId::parse_with_server_name(localpart, &self.server_name)?;
|
let user_id = UserId::parse_with_server_name(localpart, &self.server_name)?;
|
||||||
|
|
||||||
// The `as_token` in the `Session` maps to the [`MainUser`]
|
// The `as_token` in the `Session` maps to the [`MainUser`]
|
||||||
// (`sender_localpart`) by default, so we don't need to assert identity
|
// (`sender_localpart`) by default, so we don't need to assert identity
|
||||||
// in that case
|
// in that case
|
||||||
if localpart != self.registration.sender_localpart {
|
let config = if localpart != self.registration.sender_localpart {
|
||||||
config.get_request_config().assert_identity();
|
let request_config = config.get_request_config().assert_identity();
|
||||||
}
|
config.request_config(request_config)
|
||||||
|
} else {
|
||||||
|
config
|
||||||
|
};
|
||||||
|
|
||||||
let client = Client::new_with_config(self.homeserver_url.clone(), config)?;
|
let client = Client::new_with_config(self.homeserver_url.clone(), config)?;
|
||||||
|
|
||||||
|
@ -316,17 +331,14 @@ impl Appservice {
|
||||||
client.restore_login(session).await?;
|
client.restore_login(session).await?;
|
||||||
self.clients.insert(localpart.to_owned(), client.clone());
|
self.clients.insert(localpart.to_owned(), client.clone());
|
||||||
|
|
||||||
client
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(client)
|
Ok(client)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get cached [`Client`]
|
/// Get cached [`Client`]
|
||||||
///
|
///
|
||||||
/// Will return the client for the given `localpart` if previously
|
/// Will return the client for the given `localpart` if previously
|
||||||
/// constructed with [`Self::virtual_user()`] or
|
/// constructed with [`Self::virtual_user_client()`] or
|
||||||
/// [`Self::virtual_user_with_config()`].
|
/// [`Self::virtual_user_client_with_config()`].
|
||||||
///
|
///
|
||||||
/// If no `localpart` is given it assumes the [`MainUser`]'s `localpart`. If
|
/// If no `localpart` is given it assumes the [`MainUser`]'s `localpart`. If
|
||||||
/// no client for `localpart` is found it will return an Error.
|
/// no client for `localpart` is found it will return an Error.
|
||||||
|
@ -338,9 +350,22 @@ impl Appservice {
|
||||||
Ok(entry.value().clone())
|
Ok(entry.value().clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convenience wrapper around [`Client::set_event_handler()`]
|
/// Convenience wrapper around [`Client::set_event_handler()`] that attaches
|
||||||
|
/// the event handler to the [`MainUser`]'s [`Client`]
|
||||||
///
|
///
|
||||||
/// Attaches the event handler to the [`MainUser`]'s [`Client`]
|
/// Note that the event handler in the [`Appservice`] context only triggers
|
||||||
|
/// [`join` room `timeline` events], so no state events or events from the
|
||||||
|
/// `invite`, `knock` or `leave` scope. The rationale behind that is
|
||||||
|
/// that incoming Appservice transactions from the homeserver are not
|
||||||
|
/// necessarily bound to a specific user but can cover a multitude of
|
||||||
|
/// namespaces, and as such the Appservice basically only "observes
|
||||||
|
/// joined rooms". Also currently homeservers only push PDUs to appservices,
|
||||||
|
/// no EDUs. There's the open [MSC2409] regarding supporting EDUs in the
|
||||||
|
/// future, though it seems to be planned to put EDUs into a different
|
||||||
|
/// JSON key than `events` to stay backwards compatible.
|
||||||
|
///
|
||||||
|
/// [`join` room `timeline` events]: https://spec.matrix.org/unstable/client-server-api/#get_matrixclientr0sync
|
||||||
|
/// [MSC2409]: https://github.com/matrix-org/matrix-doc/pull/2409
|
||||||
pub async fn set_event_handler(&mut self, handler: Box<dyn EventHandler>) -> Result<()> {
|
pub async fn set_event_handler(&mut self, handler: Box<dyn EventHandler>) -> Result<()> {
|
||||||
let client = self.get_cached_client(None)?;
|
let client = self.get_cached_client(None)?;
|
||||||
|
|
||||||
|
@ -349,17 +374,17 @@ impl Appservice {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Register a virtual user by sending a [`RegistrationRequest`] to the
|
/// Register a virtual user by sending a [`register::Request`] to the
|
||||||
/// homeserver
|
/// homeserver
|
||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
///
|
///
|
||||||
/// * `localpart` - The localpart of the user to register. Must be covered
|
/// * `localpart` - The localpart of the user to register. Must be covered
|
||||||
/// by the namespaces in the [`Registration`] in order to succeed.
|
/// by the namespaces in the [`Registration`] in order to succeed.
|
||||||
pub async fn register(&mut self, localpart: impl AsRef<str>) -> Result<()> {
|
pub async fn register_virtual_user(&self, localpart: impl AsRef<str>) -> Result<()> {
|
||||||
let request = assign!(RegistrationRequest::new(), {
|
let request = assign!(register::Request::new(), {
|
||||||
username: Some(localpart.as_ref()),
|
username: Some(localpart.as_ref()),
|
||||||
login_type: Some(&LoginType::ApplicationService),
|
login_type: Some(®ister::LoginType::ApplicationService),
|
||||||
});
|
});
|
||||||
|
|
||||||
let client = self.get_cached_client(None)?;
|
let client = self.get_cached_client(None)?;
|
||||||
|
@ -412,11 +437,35 @@ impl Appservice {
|
||||||
Ok(false)
|
Ok(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Service to register on an Actix `App`
|
/// Returns a closure to be used with [`actix_web::App::configure()`]
|
||||||
|
///
|
||||||
|
/// Note that if you handle any of the [application-service-specific
|
||||||
|
/// routes], including the legacy routes, you will break the appservice
|
||||||
|
/// functionality.
|
||||||
|
///
|
||||||
|
/// [application-service-specific routes]: https://spec.matrix.org/unstable/application-service-api/#legacy-routes
|
||||||
#[cfg(feature = "actix")]
|
#[cfg(feature = "actix")]
|
||||||
#[cfg_attr(docs, doc(cfg(feature = "actix")))]
|
#[cfg_attr(docs, doc(cfg(feature = "actix")))]
|
||||||
pub fn actix_service(&self) -> actix::Scope {
|
pub fn actix_configure(&self) -> impl FnOnce(&mut actix_web::web::ServiceConfig) {
|
||||||
actix::get_scope().data(self.clone())
|
let appservice = self.clone();
|
||||||
|
|
||||||
|
move |config| {
|
||||||
|
config.data(appservice);
|
||||||
|
webserver::actix::configure(config);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a [`warp::Filter`] to be used as [`warp::serve()`] route
|
||||||
|
///
|
||||||
|
/// Note that if you handle any of the [application-service-specific
|
||||||
|
/// routes], including the legacy routes, you will break the appservice
|
||||||
|
/// functionality.
|
||||||
|
///
|
||||||
|
/// [application-service-specific routes]: https://spec.matrix.org/unstable/application-service-api/#legacy-routes
|
||||||
|
#[cfg(feature = "warp")]
|
||||||
|
#[cfg_attr(docs, doc(cfg(feature = "warp")))]
|
||||||
|
pub fn warp_filter(&self) -> warp::filters::BoxedFilter<(impl warp::Reply,)> {
|
||||||
|
webserver::warp::warp_filter(self.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convenience method that runs an http server depending on the selected
|
/// Convenience method that runs an http server depending on the selected
|
||||||
|
@ -424,14 +473,50 @@ impl Appservice {
|
||||||
///
|
///
|
||||||
/// This is a blocking call that tries to listen on the provided host and
|
/// This is a blocking call that tries to listen on the provided host and
|
||||||
/// port
|
/// port
|
||||||
pub async fn run(&self, host: impl AsRef<str>, port: impl Into<u16>) -> Result<()> {
|
pub async fn run(&self, host: impl Into<String>, port: impl Into<u16>) -> Result<()> {
|
||||||
|
let host = host.into();
|
||||||
|
let port = port.into();
|
||||||
|
info!("Starting Appservice on {}:{}", &host, &port);
|
||||||
|
|
||||||
#[cfg(feature = "actix")]
|
#[cfg(feature = "actix")]
|
||||||
{
|
{
|
||||||
actix::run_server(self.clone(), host, port).await?;
|
webserver::actix::run_server(self.clone(), host, port).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(any(feature = "actix",)))]
|
#[cfg(feature = "warp")]
|
||||||
|
{
|
||||||
|
webserver::warp::run_server(self.clone(), host, port).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(any(feature = "actix", feature = "warp",)))]
|
||||||
unreachable!()
|
unreachable!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Transforms [legacy routes] to the correct route so ruma can parse them
|
||||||
|
/// properly
|
||||||
|
///
|
||||||
|
/// [legacy routes]: https://matrix.org/docs/spec/application_service/r0.1.2#legacy-routes
|
||||||
|
pub(crate) fn transform_legacy_route(
|
||||||
|
mut request: http::Request<Bytes>,
|
||||||
|
) -> Result<http::Request<Bytes>> {
|
||||||
|
let uri = request.uri().to_owned();
|
||||||
|
|
||||||
|
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(),
|
||||||
|
};
|
||||||
|
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)?;
|
||||||
|
|
||||||
|
*request.uri_mut() = uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(request)
|
||||||
|
}
|
||||||
|
|
|
@ -12,47 +12,43 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
use std::{
|
use std::pin::Pin;
|
||||||
convert::{TryFrom, TryInto},
|
|
||||||
pin::Pin,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub use actix_web::Scope;
|
pub use actix_web::Scope;
|
||||||
use actix_web::{
|
use actix_web::{
|
||||||
dev::Payload,
|
dev::Payload,
|
||||||
error::PayloadError,
|
error::PayloadError,
|
||||||
get,
|
get, put,
|
||||||
http::PathAndQuery,
|
|
||||||
put,
|
|
||||||
web::{self, BytesMut, Data},
|
web::{self, BytesMut, Data},
|
||||||
App, FromRequest, HttpRequest, HttpResponse, HttpServer,
|
App, FromRequest, HttpRequest, HttpResponse, HttpServer,
|
||||||
};
|
};
|
||||||
use futures::Future;
|
use futures::Future;
|
||||||
use futures_util::{TryFutureExt, TryStreamExt};
|
use futures_util::TryStreamExt;
|
||||||
use ruma::api::appservice as api;
|
use ruma::api::appservice as api;
|
||||||
|
|
||||||
use crate::{error::Error, Appservice};
|
use crate::{error::Error, Appservice};
|
||||||
|
|
||||||
pub async fn run_server(
|
pub async fn run_server(
|
||||||
appservice: Appservice,
|
appservice: Appservice,
|
||||||
host: impl AsRef<str>,
|
host: impl Into<String>,
|
||||||
port: impl Into<u16>,
|
port: impl Into<u16>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
HttpServer::new(move || App::new().service(appservice.actix_service()))
|
HttpServer::new(move || App::new().configure(appservice.actix_configure()))
|
||||||
.bind((host.as_ref(), port.into()))?
|
.bind((host.into(), port.into()))?
|
||||||
.run()
|
.run()
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_scope() -> Scope {
|
pub fn configure(config: &mut actix_web::web::ServiceConfig) {
|
||||||
gen_scope("/"). // handle legacy routes
|
// also handles legacy routes
|
||||||
service(gen_scope("/_matrix/app/v1"))
|
config.service(push_transactions).service(query_user_id).service(query_room_alias).service(
|
||||||
}
|
web::scope("/_matrix/app/v1")
|
||||||
|
.service(push_transactions)
|
||||||
fn gen_scope(scope: &str) -> Scope {
|
.service(query_user_id)
|
||||||
web::scope(scope).service(push_transactions).service(query_user_id).service(query_room_alias)
|
.service(query_room_alias),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument]
|
#[tracing::instrument]
|
||||||
|
@ -112,23 +108,8 @@ impl<T: ruma::api::IncomingRequest> FromRequest for IncomingRequest<T> {
|
||||||
let payload = payload.take();
|
let payload = payload.take();
|
||||||
|
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
let uri = request.uri().to_owned();
|
let mut builder =
|
||||||
|
http::request::Builder::new().method(request.method()).uri(request.uri());
|
||||||
let uri = 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(),
|
|
||||||
};
|
|
||||||
parts.path_and_query =
|
|
||||||
Some(PathAndQuery::try_from(path_and_query).map_err(http::Error::from)?);
|
|
||||||
parts.try_into().map_err(http::Error::from)?
|
|
||||||
} else {
|
|
||||||
uri
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut builder = http::request::Builder::new().method(request.method()).uri(uri);
|
|
||||||
|
|
||||||
let headers = builder.headers_mut().ok_or(Error::UnknownHttpRequestBuilder)?;
|
let headers = builder.headers_mut().ok_or(Error::UnknownHttpRequestBuilder)?;
|
||||||
for (key, value) in request.headers().iter() {
|
for (key, value) in request.headers().iter() {
|
||||||
|
@ -140,8 +121,8 @@ impl<T: ruma::api::IncomingRequest> FromRequest for IncomingRequest<T> {
|
||||||
body.extend_from_slice(&chunk);
|
body.extend_from_slice(&chunk);
|
||||||
Ok::<_, PayloadError>(body)
|
Ok::<_, PayloadError>(body)
|
||||||
})
|
})
|
||||||
.and_then(|bytes| async move { Ok::<Vec<u8>, _>(bytes.into_iter().collect()) })
|
.await?
|
||||||
.await?;
|
.into();
|
||||||
|
|
||||||
let access_token = match request.uri().query() {
|
let access_token = match request.uri().query() {
|
||||||
Some(query) => {
|
Some(query) => {
|
||||||
|
@ -157,6 +138,7 @@ impl<T: ruma::api::IncomingRequest> FromRequest for IncomingRequest<T> {
|
||||||
};
|
};
|
||||||
|
|
||||||
let request = builder.body(bytes)?;
|
let request = builder.body(bytes)?;
|
||||||
|
let request = crate::transform_legacy_route(request)?;
|
||||||
|
|
||||||
Ok(IncomingRequest {
|
Ok(IncomingRequest {
|
||||||
access_token,
|
access_token,
|
|
@ -0,0 +1,4 @@
|
||||||
|
#[cfg(feature = "actix")]
|
||||||
|
pub mod actix;
|
||||||
|
#[cfg(feature = "warp")]
|
||||||
|
pub mod warp;
|
|
@ -0,0 +1,210 @@
|
||||||
|
// Copyright 2021 Famedly GmbH
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
use std::{net::ToSocketAddrs, result::Result as StdResult};
|
||||||
|
|
||||||
|
use futures::TryFutureExt;
|
||||||
|
use matrix_sdk::Bytes;
|
||||||
|
use serde::Serialize;
|
||||||
|
use warp::{filters::BoxedFilter, path::FullPath, Filter, Rejection, Reply};
|
||||||
|
|
||||||
|
use crate::{Appservice, Error, Result};
|
||||||
|
|
||||||
|
pub async fn run_server(
|
||||||
|
appservice: Appservice,
|
||||||
|
host: impl Into<String>,
|
||||||
|
port: impl Into<u16>,
|
||||||
|
) -> Result<()> {
|
||||||
|
let routes = warp_filter(appservice);
|
||||||
|
|
||||||
|
let mut addr = format!("{}:{}", host.into(), port.into()).to_socket_addrs()?;
|
||||||
|
if let Some(addr) = addr.next() {
|
||||||
|
warp::serve(routes).run(addr).await;
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(Error::HostPortToSocketAddrs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn warp_filter(appservice: Appservice) -> BoxedFilter<(impl Reply,)> {
|
||||||
|
// TODO: try to use a struct instead of needlessly cloning appservice multiple
|
||||||
|
// times on every request
|
||||||
|
warp::any()
|
||||||
|
.and(filters::transactions(appservice.clone()))
|
||||||
|
.or(filters::users(appservice.clone()))
|
||||||
|
.or(filters::rooms(appservice))
|
||||||
|
.recover(handle_rejection)
|
||||||
|
.boxed()
|
||||||
|
}
|
||||||
|
|
||||||
|
mod filters {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
pub fn users(appservice: Appservice) -> BoxedFilter<(impl Reply,)> {
|
||||||
|
warp::get()
|
||||||
|
.and(
|
||||||
|
warp::path!("_matrix" / "app" / "v1" / "users" / String)
|
||||||
|
// legacy route
|
||||||
|
.or(warp::path!("users" / String))
|
||||||
|
.unify(),
|
||||||
|
)
|
||||||
|
.and(warp::path::end())
|
||||||
|
.and(common(appservice))
|
||||||
|
.and_then(handlers::user)
|
||||||
|
.boxed()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn rooms(appservice: Appservice) -> BoxedFilter<(impl Reply,)> {
|
||||||
|
warp::get()
|
||||||
|
.and(
|
||||||
|
warp::path!("_matrix" / "app" / "v1" / "rooms" / String)
|
||||||
|
// legacy route
|
||||||
|
.or(warp::path!("rooms" / String))
|
||||||
|
.unify(),
|
||||||
|
)
|
||||||
|
.and(warp::path::end())
|
||||||
|
.and(common(appservice))
|
||||||
|
.and_then(handlers::room)
|
||||||
|
.boxed()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn transactions(appservice: Appservice) -> BoxedFilter<(impl Reply,)> {
|
||||||
|
warp::put()
|
||||||
|
.and(
|
||||||
|
warp::path!("_matrix" / "app" / "v1" / "transactions" / String)
|
||||||
|
// legacy route
|
||||||
|
.or(warp::path!("transactions" / String))
|
||||||
|
.unify(),
|
||||||
|
)
|
||||||
|
.and(warp::path::end())
|
||||||
|
.and(common(appservice))
|
||||||
|
.and_then(handlers::transaction)
|
||||||
|
.boxed()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn common(appservice: Appservice) -> BoxedFilter<(Appservice, http::Request<Bytes>)> {
|
||||||
|
warp::any()
|
||||||
|
.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)?;
|
||||||
|
Ok::<http::Request<Bytes>, Rejection>(request)
|
||||||
|
}))
|
||||||
|
.boxed()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn valid_access_token(token: String) -> BoxedFilter<()> {
|
||||||
|
warp::any()
|
||||||
|
.map(move || token.clone())
|
||||||
|
.and(warp::query::raw())
|
||||||
|
.and_then(|token: String, query: String| async move {
|
||||||
|
let query: Vec<(String, String)> =
|
||||||
|
matrix_sdk::urlencoded::from_str(&query).map_err(Error::from)?;
|
||||||
|
|
||||||
|
if query.into_iter().any(|(key, value)| key == "access_token" && value == token) {
|
||||||
|
Ok::<(), Rejection>(())
|
||||||
|
} else {
|
||||||
|
Err(warp::reject::custom(Unauthorized))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.untuple_one()
|
||||||
|
.boxed()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn http_request() -> impl Filter<Extract = (http::Request<Bytes>,), Error = Rejection> + Copy
|
||||||
|
{
|
||||||
|
// TODO: extract `hyper::Request` instead
|
||||||
|
// blocked by https://github.com/seanmonstar/warp/issues/139
|
||||||
|
warp::any()
|
||||||
|
.and(warp::method())
|
||||||
|
.and(warp::filters::path::full())
|
||||||
|
.and(warp::filters::query::raw())
|
||||||
|
.and(warp::header::headers_cloned())
|
||||||
|
.and(warp::body::bytes())
|
||||||
|
.and_then(|method, path: FullPath, query, headers, bytes| async move {
|
||||||
|
let uri = http::uri::Builder::new()
|
||||||
|
.path_and_query(format!("{}?{}", path.as_str(), query))
|
||||||
|
.build()
|
||||||
|
.map_err(Error::from)?;
|
||||||
|
|
||||||
|
let mut request = http::Request::builder()
|
||||||
|
.method(method)
|
||||||
|
.uri(uri)
|
||||||
|
.body(bytes)
|
||||||
|
.map_err(Error::from)?;
|
||||||
|
|
||||||
|
*request.headers_mut() = headers;
|
||||||
|
|
||||||
|
Ok::<http::Request<Bytes>, Rejection>(request)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod handlers {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
pub async fn user(
|
||||||
|
_user_id: String,
|
||||||
|
_appservice: Appservice,
|
||||||
|
_request: http::Request<Bytes>,
|
||||||
|
) -> StdResult<impl warp::Reply, Rejection> {
|
||||||
|
Ok(warp::reply::json(&String::from("{}")))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn room(
|
||||||
|
_room_id: String,
|
||||||
|
_appservice: Appservice,
|
||||||
|
_request: http::Request<Bytes>,
|
||||||
|
) -> StdResult<impl warp::Reply, Rejection> {
|
||||||
|
Ok(warp::reply::json(&String::from("{}")))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn transaction(
|
||||||
|
_txn_id: String,
|
||||||
|
appservice: Appservice,
|
||||||
|
request: http::Request<Bytes>,
|
||||||
|
) -> StdResult<impl warp::Reply, Rejection> {
|
||||||
|
let incoming_transaction: matrix_sdk::api_appservice::event::push_events::v1::IncomingRequest =
|
||||||
|
matrix_sdk::IncomingRequest::try_from_http_request(request).map_err(Error::from)?;
|
||||||
|
|
||||||
|
let client = appservice.get_cached_client(None)?;
|
||||||
|
client.receive_transaction(incoming_transaction).map_err(Error::from).await?;
|
||||||
|
|
||||||
|
Ok(warp::reply::json(&String::from("{}")))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct Unauthorized;
|
||||||
|
|
||||||
|
impl warp::reject::Reject for Unauthorized {}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
struct ErrorMessage {
|
||||||
|
code: u16,
|
||||||
|
message: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn handle_rejection(err: Rejection) -> std::result::Result<impl Reply, Rejection> {
|
||||||
|
if err.find::<Unauthorized>().is_some() || err.find::<warp::reject::InvalidQuery>().is_some() {
|
||||||
|
let code = http::StatusCode::UNAUTHORIZED;
|
||||||
|
let message = "UNAUTHORIZED";
|
||||||
|
|
||||||
|
let json =
|
||||||
|
warp::reply::json(&ErrorMessage { code: code.as_u16(), message: message.into() });
|
||||||
|
Ok(warp::reply::with_status(json, code))
|
||||||
|
} else {
|
||||||
|
Err(err)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,114 +0,0 @@
|
||||||
#[cfg(feature = "actix")]
|
|
||||||
mod actix {
|
|
||||||
use std::env;
|
|
||||||
|
|
||||||
use actix_web::{test, App};
|
|
||||||
use matrix_sdk_appservice::*;
|
|
||||||
|
|
||||||
async fn appservice() -> Appservice {
|
|
||||||
env::set_var("RUST_LOG", "mockito=debug,matrix_sdk=debug,ruma=debug,actix_web=debug");
|
|
||||||
let _ = tracing_subscriber::fmt::try_init();
|
|
||||||
|
|
||||||
Appservice::new(
|
|
||||||
mockito::server_url().as_ref(),
|
|
||||||
"test.local",
|
|
||||||
AppserviceRegistration::try_from_yaml_str(include_str!("./registration.yaml")).unwrap(),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[actix_rt::test]
|
|
||||||
async fn test_transactions() {
|
|
||||||
let appservice = appservice().await;
|
|
||||||
let app = test::init_service(App::new().service(appservice.actix_service())).await;
|
|
||||||
|
|
||||||
let transactions = r#"{
|
|
||||||
"events": [
|
|
||||||
{
|
|
||||||
"content": {},
|
|
||||||
"type": "m.dummy"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}"#;
|
|
||||||
|
|
||||||
let transactions: serde_json::Value = serde_json::from_str(transactions).unwrap();
|
|
||||||
|
|
||||||
let req = test::TestRequest::put()
|
|
||||||
.uri("/_matrix/app/v1/transactions/1?access_token=hs_token")
|
|
||||||
.set_json(&transactions)
|
|
||||||
.to_request();
|
|
||||||
|
|
||||||
let resp = test::call_service(&app, req).await;
|
|
||||||
|
|
||||||
assert_eq!(resp.status(), 200);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[actix_rt::test]
|
|
||||||
async fn test_users() {
|
|
||||||
let appservice = appservice().await;
|
|
||||||
let app = test::init_service(App::new().service(appservice.actix_service())).await;
|
|
||||||
|
|
||||||
let req = test::TestRequest::get()
|
|
||||||
.uri("/_matrix/app/v1/users/%40_botty_1%3Adev.famedly.local?access_token=hs_token")
|
|
||||||
.to_request();
|
|
||||||
|
|
||||||
let resp = test::call_service(&app, req).await;
|
|
||||||
|
|
||||||
assert_eq!(resp.status(), 200);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[actix_rt::test]
|
|
||||||
async fn test_invalid_access_token() {
|
|
||||||
let appservice = appservice().await;
|
|
||||||
let app = test::init_service(App::new().service(appservice.actix_service())).await;
|
|
||||||
|
|
||||||
let transactions = r#"{
|
|
||||||
"events": [
|
|
||||||
{
|
|
||||||
"content": {},
|
|
||||||
"type": "m.dummy"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}"#;
|
|
||||||
|
|
||||||
let transactions: serde_json::Value = serde_json::from_str(transactions).unwrap();
|
|
||||||
|
|
||||||
let req = test::TestRequest::put()
|
|
||||||
.uri("/_matrix/app/v1/transactions/1?access_token=invalid_token")
|
|
||||||
.set_json(&transactions)
|
|
||||||
.to_request();
|
|
||||||
|
|
||||||
let resp = test::call_service(&app, req).await;
|
|
||||||
|
|
||||||
assert_eq!(resp.status(), 401);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[actix_rt::test]
|
|
||||||
async fn test_no_access_token() {
|
|
||||||
let appservice = appservice().await;
|
|
||||||
let app = test::init_service(App::new().service(appservice.actix_service())).await;
|
|
||||||
|
|
||||||
let transactions = r#"{
|
|
||||||
"events": [
|
|
||||||
{
|
|
||||||
"content": {},
|
|
||||||
"type": "m.dummy"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}"#;
|
|
||||||
|
|
||||||
let transactions: serde_json::Value = serde_json::from_str(transactions).unwrap();
|
|
||||||
|
|
||||||
let req = test::TestRequest::put()
|
|
||||||
.uri("/_matrix/app/v1/transactions/1")
|
|
||||||
.set_json(&transactions)
|
|
||||||
.to_request();
|
|
||||||
|
|
||||||
let resp = test::call_service(&app, req).await;
|
|
||||||
|
|
||||||
// TODO: this should actually return a 401 but is 500 because something in the
|
|
||||||
// extractor fails
|
|
||||||
assert_eq!(resp.status(), 500);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,23 +1,29 @@
|
||||||
use std::env;
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
|
#[cfg(feature = "actix")]
|
||||||
|
use actix_web::{test as actix_test, App as ActixApp, HttpResponse};
|
||||||
use matrix_sdk::{
|
use matrix_sdk::{
|
||||||
api_appservice,
|
|
||||||
api_appservice::Registration,
|
api_appservice::Registration,
|
||||||
async_trait,
|
async_trait,
|
||||||
events::{room::member::MemberEventContent, AnyRoomEvent, AnyStateEvent, SyncStateEvent},
|
events::{room::member::MemberEventContent, SyncStateEvent},
|
||||||
room::Room,
|
room::Room,
|
||||||
EventHandler, Raw,
|
ClientConfig, EventHandler, RequestConfig,
|
||||||
};
|
};
|
||||||
use matrix_sdk_appservice::*;
|
use matrix_sdk_appservice::*;
|
||||||
use matrix_sdk_test::async_test;
|
use matrix_sdk_test::{appservice::TransactionBuilder, async_test, EventsJson};
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
#[cfg(feature = "warp")]
|
||||||
|
use warp::{Filter, Reply};
|
||||||
|
|
||||||
fn registration_string() -> String {
|
fn registration_string() -> String {
|
||||||
include_str!("../tests/registration.yaml").to_owned()
|
include_str!("../tests/registration.yaml").to_owned()
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn appservice(registration: Option<Registration>) -> Result<Appservice> {
|
async fn appservice(registration: Option<Registration>) -> Result<Appservice> {
|
||||||
env::set_var("RUST_LOG", "mockito=debug,matrix_sdk=debug");
|
// env::set_var(
|
||||||
|
// "RUST_LOG",
|
||||||
|
// "mockito=debug,matrix_sdk=debug,ruma=debug,actix_web=debug,warp=debug",
|
||||||
|
// );
|
||||||
let _ = tracing_subscriber::fmt::try_init();
|
let _ = tracing_subscriber::fmt::try_init();
|
||||||
|
|
||||||
let registration = match registration {
|
let registration = match registration {
|
||||||
|
@ -28,95 +34,317 @@ async fn appservice(registration: Option<Registration>) -> Result<Appservice> {
|
||||||
let homeserver_url = mockito::server_url();
|
let homeserver_url = mockito::server_url();
|
||||||
let server_name = "localhost";
|
let server_name = "localhost";
|
||||||
|
|
||||||
Ok(Appservice::new(homeserver_url.as_ref(), server_name, registration).await?)
|
let client_config =
|
||||||
|
ClientConfig::default().request_config(RequestConfig::default().disable_retry());
|
||||||
|
|
||||||
|
Ok(Appservice::new_with_config(
|
||||||
|
homeserver_url.as_ref(),
|
||||||
|
server_name,
|
||||||
|
registration,
|
||||||
|
client_config,
|
||||||
|
)
|
||||||
|
.await?)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn member_json() -> serde_json::Value {
|
#[async_test]
|
||||||
json!({
|
async fn test_register_virtual_user() -> Result<()> {
|
||||||
"content": {
|
let appservice = appservice(None).await?;
|
||||||
"avatar_url": null,
|
|
||||||
"displayname": "example",
|
let localpart = "someone";
|
||||||
"membership": "join"
|
let _mock = mockito::mock("POST", "/_matrix/client/r0/register")
|
||||||
},
|
.match_query(mockito::Matcher::Missing)
|
||||||
"event_id": "$151800140517rfvjc:localhost",
|
.match_header(
|
||||||
"membership": "join",
|
"authorization",
|
||||||
"origin_server_ts": 151800140,
|
mockito::Matcher::Exact(format!("Bearer {}", appservice.registration().as_token)),
|
||||||
"room_id": "!ahpSDaDUPCCqktjUEF:localhost",
|
)
|
||||||
"sender": "@example:localhost",
|
.match_body(mockito::Matcher::Json(json!({
|
||||||
"state_key": "@example:localhost",
|
"username": localpart.to_owned(),
|
||||||
"type": "m.room.member",
|
"type": "m.login.application_service"
|
||||||
"prev_content": {
|
})))
|
||||||
"avatar_url": null,
|
.with_body(format!(
|
||||||
"displayname": "example",
|
r#"{{
|
||||||
"membership": "invite"
|
"access_token": "abc123",
|
||||||
},
|
"device_id": "GHTYAJCE",
|
||||||
"unsigned": {
|
"user_id": "@{localpart}:localhost"
|
||||||
"age": 297036,
|
}}"#,
|
||||||
"replaces_state": "$151800111315tsynI:localhost"
|
localpart = localpart
|
||||||
|
))
|
||||||
|
.create();
|
||||||
|
|
||||||
|
appservice.register_virtual_user(localpart).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_test]
|
||||||
|
async fn test_put_transaction() -> Result<()> {
|
||||||
|
let uri = "/_matrix/app/v1/transactions/1?access_token=hs_token";
|
||||||
|
|
||||||
|
let mut transaction_builder = TransactionBuilder::new();
|
||||||
|
transaction_builder.add_room_event(EventsJson::Member);
|
||||||
|
let transaction = transaction_builder.build_json_transaction();
|
||||||
|
|
||||||
|
let appservice = appservice(None).await?;
|
||||||
|
|
||||||
|
#[cfg(feature = "warp")]
|
||||||
|
let status = warp::test::request()
|
||||||
|
.method("PUT")
|
||||||
|
.path(uri)
|
||||||
|
.json(&transaction)
|
||||||
|
.filter(&appservice.warp_filter())
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.into_response()
|
||||||
|
.status();
|
||||||
|
|
||||||
|
#[cfg(feature = "actix")]
|
||||||
|
let status = {
|
||||||
|
let app =
|
||||||
|
actix_test::init_service(ActixApp::new().configure(appservice.actix_configure())).await;
|
||||||
|
|
||||||
|
let req = actix_test::TestRequest::put().uri(uri).set_json(&transaction).to_request();
|
||||||
|
|
||||||
|
actix_test::call_service(&app, req).await.status()
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(status, 200);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_test]
|
||||||
|
async fn test_get_user() -> Result<()> {
|
||||||
|
let appservice = appservice(None).await?;
|
||||||
|
|
||||||
|
let uri = "/_matrix/app/v1/users/%40_botty_1%3Adev.famedly.local?access_token=hs_token";
|
||||||
|
|
||||||
|
#[cfg(feature = "warp")]
|
||||||
|
let status = warp::test::request()
|
||||||
|
.method("GET")
|
||||||
|
.path(uri)
|
||||||
|
.filter(&appservice.warp_filter())
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.into_response()
|
||||||
|
.status();
|
||||||
|
|
||||||
|
#[cfg(feature = "actix")]
|
||||||
|
let status = {
|
||||||
|
let app =
|
||||||
|
actix_test::init_service(ActixApp::new().configure(appservice.actix_configure())).await;
|
||||||
|
|
||||||
|
let req = actix_test::TestRequest::get().uri(uri).to_request();
|
||||||
|
|
||||||
|
actix_test::call_service(&app, req).await.status()
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(status, 200);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_test]
|
||||||
|
async fn test_get_room() -> Result<()> {
|
||||||
|
let appservice = appservice(None).await?;
|
||||||
|
|
||||||
|
let uri = "/_matrix/app/v1/rooms/%23magicforest%3Aexample.com?access_token=hs_token";
|
||||||
|
|
||||||
|
#[cfg(feature = "warp")]
|
||||||
|
let status = warp::test::request()
|
||||||
|
.method("GET")
|
||||||
|
.path(uri)
|
||||||
|
.filter(&appservice.warp_filter())
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.into_response()
|
||||||
|
.status();
|
||||||
|
|
||||||
|
#[cfg(feature = "actix")]
|
||||||
|
let status = {
|
||||||
|
let app =
|
||||||
|
actix_test::init_service(ActixApp::new().configure(appservice.actix_configure())).await;
|
||||||
|
|
||||||
|
let req = actix_test::TestRequest::get().uri(uri).to_request();
|
||||||
|
|
||||||
|
actix_test::call_service(&app, req).await.status()
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(status, 200);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_test]
|
||||||
|
async fn test_invalid_access_token() -> Result<()> {
|
||||||
|
let uri = "/_matrix/app/v1/transactions/1?access_token=invalid_token";
|
||||||
|
|
||||||
|
let mut transaction_builder = TransactionBuilder::new();
|
||||||
|
let transaction =
|
||||||
|
transaction_builder.add_room_event(EventsJson::Member).build_json_transaction();
|
||||||
|
|
||||||
|
let appservice = appservice(None).await?;
|
||||||
|
|
||||||
|
#[cfg(feature = "warp")]
|
||||||
|
let status = warp::test::request()
|
||||||
|
.method("PUT")
|
||||||
|
.path(uri)
|
||||||
|
.json(&transaction)
|
||||||
|
.filter(&appservice.warp_filter())
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.into_response()
|
||||||
|
.status();
|
||||||
|
|
||||||
|
#[cfg(feature = "actix")]
|
||||||
|
let status = {
|
||||||
|
let app =
|
||||||
|
actix_test::init_service(ActixApp::new().configure(appservice.actix_configure())).await;
|
||||||
|
|
||||||
|
let req = actix_test::TestRequest::put().uri(uri).set_json(&transaction).to_request();
|
||||||
|
|
||||||
|
actix_test::call_service(&app, req).await.status()
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(status, 401);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_test]
|
||||||
|
async fn test_no_access_token() -> Result<()> {
|
||||||
|
let uri = "/_matrix/app/v1/transactions/1";
|
||||||
|
|
||||||
|
let mut transaction_builder = TransactionBuilder::new();
|
||||||
|
transaction_builder.add_room_event(EventsJson::Member);
|
||||||
|
let transaction = transaction_builder.build_json_transaction();
|
||||||
|
|
||||||
|
let appservice = appservice(None).await?;
|
||||||
|
|
||||||
|
#[cfg(feature = "warp")]
|
||||||
|
{
|
||||||
|
let status = warp::test::request()
|
||||||
|
.method("PUT")
|
||||||
|
.path(uri)
|
||||||
|
.json(&transaction)
|
||||||
|
.filter(&appservice.warp_filter())
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.into_response()
|
||||||
|
.status();
|
||||||
|
|
||||||
|
assert_eq!(status, 401);
|
||||||
}
|
}
|
||||||
})
|
|
||||||
|
#[cfg(feature = "actix")]
|
||||||
|
{
|
||||||
|
let app =
|
||||||
|
actix_test::init_service(ActixApp::new().configure(appservice.actix_configure())).await;
|
||||||
|
|
||||||
|
let req = actix_test::TestRequest::put().uri(uri).set_json(&transaction).to_request();
|
||||||
|
|
||||||
|
let resp = actix_test::call_service(&app, req).await;
|
||||||
|
|
||||||
|
// TODO: this should actually return a 401 but is 500 because something in the
|
||||||
|
// extractor fails
|
||||||
|
assert_eq!(resp.status(), 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_test]
|
#[async_test]
|
||||||
async fn test_event_handler() -> Result<()> {
|
async fn test_event_handler() -> Result<()> {
|
||||||
let mut appservice = appservice(None).await?;
|
let mut appservice = appservice(None).await?;
|
||||||
|
|
||||||
struct Example {}
|
#[derive(Clone)]
|
||||||
|
struct Example {
|
||||||
|
pub on_state_member: Arc<Mutex<bool>>,
|
||||||
|
}
|
||||||
|
|
||||||
impl Example {
|
impl Example {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {}
|
#[allow(clippy::mutex_atomic)]
|
||||||
|
Self { on_state_member: Arc::new(Mutex::new(false)) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl EventHandler for Example {
|
impl EventHandler for Example {
|
||||||
async fn on_state_member(&self, room: Room, event: &SyncStateEvent<MemberEventContent>) {
|
async fn on_room_member(&self, _: Room, _: &SyncStateEvent<MemberEventContent>) {
|
||||||
dbg!(room, event);
|
let on_state_member = self.on_state_member.clone();
|
||||||
|
*on_state_member.lock().unwrap() = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
appservice.set_event_handler(Box::new(Example::new())).await?;
|
let example = Example::new();
|
||||||
|
appservice.set_event_handler(Box::new(example.clone())).await?;
|
||||||
|
|
||||||
let event = serde_json::from_value::<AnyStateEvent>(member_json()).unwrap();
|
let uri = "/_matrix/app/v1/transactions/1?access_token=hs_token";
|
||||||
let event: Raw<AnyRoomEvent> = AnyRoomEvent::State(event).into();
|
|
||||||
let events = vec![event];
|
|
||||||
|
|
||||||
let incoming = api_appservice::event::push_events::v1::IncomingRequest::new(
|
let mut transaction_builder = TransactionBuilder::new();
|
||||||
"any_txn_id".to_owned(),
|
transaction_builder.add_room_event(EventsJson::Member);
|
||||||
events,
|
let transaction = transaction_builder.build_json_transaction();
|
||||||
);
|
|
||||||
|
|
||||||
appservice.get_cached_client(None)?.receive_transaction(incoming).await?;
|
#[cfg(feature = "warp")]
|
||||||
|
warp::test::request()
|
||||||
|
.method("PUT")
|
||||||
|
.path(uri)
|
||||||
|
.json(&transaction)
|
||||||
|
.filter(&appservice.warp_filter())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
#[cfg(feature = "actix")]
|
||||||
|
{
|
||||||
|
let app =
|
||||||
|
actix_test::init_service(ActixApp::new().configure(appservice.actix_configure())).await;
|
||||||
|
|
||||||
|
let req = actix_test::TestRequest::put().uri(uri).set_json(&transaction).to_request();
|
||||||
|
|
||||||
|
actix_test::call_service(&app, req).await;
|
||||||
|
};
|
||||||
|
|
||||||
|
let on_room_member_called = *example.on_state_member.lock().unwrap();
|
||||||
|
assert!(on_room_member_called);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_test]
|
#[async_test]
|
||||||
async fn test_transaction() -> Result<()> {
|
async fn test_unrelated_path() -> Result<()> {
|
||||||
let appservice = appservice(None).await?;
|
let appservice = appservice(None).await?;
|
||||||
|
|
||||||
let event = serde_json::from_value::<AnyStateEvent>(member_json()).unwrap();
|
#[cfg(feature = "warp")]
|
||||||
let event: Raw<AnyRoomEvent> = AnyRoomEvent::State(event).into();
|
let status = {
|
||||||
let events = vec![event];
|
let consumer_filter = warp::any()
|
||||||
|
.and(appservice.warp_filter())
|
||||||
|
.or(warp::get().and(warp::path("unrelated").map(warp::reply)));
|
||||||
|
|
||||||
let incoming = api_appservice::event::push_events::v1::IncomingRequest::new(
|
let response = warp::test::request()
|
||||||
"any_txn_id".to_owned(),
|
.method("GET")
|
||||||
events,
|
.path("/unrelated")
|
||||||
);
|
.filter(&consumer_filter)
|
||||||
|
.await?
|
||||||
|
.into_response();
|
||||||
|
|
||||||
appservice.get_cached_client(None)?.receive_transaction(incoming).await?;
|
response.status()
|
||||||
|
};
|
||||||
|
|
||||||
Ok(())
|
#[cfg(feature = "actix")]
|
||||||
}
|
let status = {
|
||||||
|
let app = actix_test::init_service(
|
||||||
|
ActixApp::new()
|
||||||
|
.configure(appservice.actix_configure())
|
||||||
|
.route("/unrelated", actix_web::web::get().to(HttpResponse::Ok)),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
#[async_test]
|
let req = actix_test::TestRequest::get().uri("/unrelated").to_request();
|
||||||
async fn test_verify_hs_token() -> Result<()> {
|
|
||||||
let appservice = appservice(None).await?;
|
|
||||||
|
|
||||||
let registration = appservice.registration();
|
actix_test::call_service(&app, req).await.status()
|
||||||
|
};
|
||||||
|
|
||||||
assert!(appservice.compare_hs_token(®istration.hs_token));
|
assert_eq!(status, 200);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,9 @@ readme = "README.md"
|
||||||
repository = "https://github.com/matrix-org/matrix-rust-sdk"
|
repository = "https://github.com/matrix-org/matrix-rust-sdk"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
appservice = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
http = "0.2.3"
|
http = "0.2.3"
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
|
|
|
@ -0,0 +1,69 @@
|
||||||
|
use std::convert::TryFrom;
|
||||||
|
|
||||||
|
use ruma::{events::AnyRoomEvent, identifiers::room_id};
|
||||||
|
use serde_json::Value;
|
||||||
|
|
||||||
|
use crate::{test_json, EventsJson};
|
||||||
|
|
||||||
|
/// Clones the given [`Value`] and adds a `room_id` to it
|
||||||
|
///
|
||||||
|
/// Adding the `room_id` conditionally with `cfg` directly to the lazy_static
|
||||||
|
/// test_json values is blocked by "experimental attributes on expressions, see
|
||||||
|
/// issue #15701 <https://github.com/rust-lang/rust/issues/15701> for more information"
|
||||||
|
pub fn value_with_room_id(value: &Value) -> Value {
|
||||||
|
let mut val = value.clone();
|
||||||
|
let room_id =
|
||||||
|
Value::try_from(room_id!("!SVkFJHzfwvuaIEawgC:localhost").to_string()).expect("room_id");
|
||||||
|
val.as_object_mut().expect("mutable test_json").insert("room_id".to_owned(), room_id);
|
||||||
|
|
||||||
|
val
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The `TransactionBuilder` struct can be used to easily generate valid
|
||||||
|
/// incoming appservice transactions in json value format for testing.
|
||||||
|
///
|
||||||
|
/// Usage is similar to [`super::EventBuilder`]
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct TransactionBuilder {
|
||||||
|
events: Vec<AnyRoomEvent>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TransactionBuilder {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Default::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a room event.
|
||||||
|
pub fn add_room_event(&mut self, json: EventsJson) -> &mut Self {
|
||||||
|
let val: &Value = match json {
|
||||||
|
EventsJson::Member => &test_json::MEMBER,
|
||||||
|
EventsJson::MemberNameChange => &test_json::MEMBER_NAME_CHANGE,
|
||||||
|
EventsJson::PowerLevels => &test_json::POWER_LEVELS,
|
||||||
|
_ => panic!("unknown event json {:?}", json),
|
||||||
|
};
|
||||||
|
|
||||||
|
let val = value_with_room_id(val);
|
||||||
|
|
||||||
|
let event = serde_json::from_value::<AnyRoomEvent>(val).unwrap();
|
||||||
|
|
||||||
|
self.events.push(event);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build the transaction
|
||||||
|
#[cfg(feature = "appservice")]
|
||||||
|
#[cfg_attr(feature = "docs", doc(cfg(appservice)))]
|
||||||
|
pub fn build_json_transaction(&self) -> Value {
|
||||||
|
let body = serde_json::json! {
|
||||||
|
{
|
||||||
|
"events": self.events
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
body
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clear(&mut self) {
|
||||||
|
self.events.clear();
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,6 +12,8 @@ use ruma::{
|
||||||
};
|
};
|
||||||
use serde_json::Value as JsonValue;
|
use serde_json::Value as JsonValue;
|
||||||
|
|
||||||
|
#[cfg(feature = "appservice")]
|
||||||
|
pub mod appservice;
|
||||||
pub mod test_json;
|
pub mod test_json;
|
||||||
|
|
||||||
/// Embedded event files
|
/// Embedded event files
|
||||||
|
|
Loading…
Reference in New Issue