|
|
|
@ -15,6 +15,12 @@
|
|
|
|
|
|
|
|
|
|
#[cfg(feature = "encryption")]
|
|
|
|
|
use std::{collections::BTreeMap, io::Write, path::PathBuf};
|
|
|
|
|
#[cfg(feature = "sso_login")]
|
|
|
|
|
use std::{
|
|
|
|
|
collections::HashMap,
|
|
|
|
|
io::{Error as IoError, ErrorKind as IoErrorKind},
|
|
|
|
|
ops::Range,
|
|
|
|
|
};
|
|
|
|
|
use std::{
|
|
|
|
|
convert::TryInto,
|
|
|
|
|
fmt::{self, Debug},
|
|
|
|
@ -29,9 +35,19 @@ use std::{
|
|
|
|
|
use dashmap::DashMap;
|
|
|
|
|
use futures_timer::Delay as sleep;
|
|
|
|
|
use http::HeaderValue;
|
|
|
|
|
#[cfg(feature = "sso_login")]
|
|
|
|
|
use http::Response;
|
|
|
|
|
use mime::{self, Mime};
|
|
|
|
|
#[cfg(feature = "sso_login")]
|
|
|
|
|
use rand::{thread_rng, Rng};
|
|
|
|
|
use reqwest::header::InvalidHeaderValue;
|
|
|
|
|
#[cfg(feature = "sso_login")]
|
|
|
|
|
use tokio::{net::TcpListener, sync::oneshot};
|
|
|
|
|
#[cfg(feature = "sso_login")]
|
|
|
|
|
use tokio_stream::wrappers::TcpListenerStream;
|
|
|
|
|
use url::Url;
|
|
|
|
|
#[cfg(feature = "sso_login")]
|
|
|
|
|
use warp::Filter;
|
|
|
|
|
#[cfg(feature = "encryption")]
|
|
|
|
|
use zeroize::Zeroizing;
|
|
|
|
|
|
|
|
|
@ -75,7 +91,7 @@ use matrix_sdk_common::{
|
|
|
|
|
message::send_message_event,
|
|
|
|
|
profile::{get_avatar_url, get_display_name, set_avatar_url, set_display_name},
|
|
|
|
|
room::create_room,
|
|
|
|
|
session::login,
|
|
|
|
|
session::{get_login_types, login, sso_login},
|
|
|
|
|
sync::sync_events,
|
|
|
|
|
uiaa::AuthData,
|
|
|
|
|
},
|
|
|
|
@ -121,6 +137,12 @@ const SYNC_REQUEST_TIMEOUT: Duration = Duration::from_secs(15);
|
|
|
|
|
const DEFAULT_UPLOAD_SPEED: u64 = 125_000;
|
|
|
|
|
/// 5 min minimal upload request timeout, used to clamp the request timeout.
|
|
|
|
|
const MIN_UPLOAD_REQUEST_TIMEOUT: Duration = Duration::from_secs(60 * 5);
|
|
|
|
|
/// The range of ports the SSO server will try to bind to randomly
|
|
|
|
|
#[cfg(feature = "sso_login")]
|
|
|
|
|
const SSO_SERVER_BIND_RANGE: Range<u16> = 20000..30000;
|
|
|
|
|
/// The number of timesthe SSO server will try to bind to a random port
|
|
|
|
|
#[cfg(feature = "sso_login")]
|
|
|
|
|
const SSO_SERVER_BIND_TRIES: u8 = 10;
|
|
|
|
|
|
|
|
|
|
/// An async/await enabled Matrix client.
|
|
|
|
|
///
|
|
|
|
@ -637,6 +659,39 @@ impl Client {
|
|
|
|
|
.and_then(|room| room::Left::new(self.clone(), room))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Gets the homeserver’s supported login types.
|
|
|
|
|
///
|
|
|
|
|
/// This should be the first step when trying to login so you can call the
|
|
|
|
|
/// appropriate method for the next step.
|
|
|
|
|
pub async fn get_login_types(&self) -> Result<get_login_types::Response> {
|
|
|
|
|
let request = get_login_types::Request::new();
|
|
|
|
|
self.send(request, None).await
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Get the URL to use to login via Single Sign-On.
|
|
|
|
|
///
|
|
|
|
|
/// Returns a URL that should be opened in a web browser to let the user
|
|
|
|
|
/// login.
|
|
|
|
|
///
|
|
|
|
|
/// After a successful login, the loginToken received at the redirect URL should
|
|
|
|
|
/// be used to login with [`login_with_token`].
|
|
|
|
|
///
|
|
|
|
|
/// # Arguments
|
|
|
|
|
///
|
|
|
|
|
/// * `redirect_url` - The URL that will receive a `loginToken` after a
|
|
|
|
|
/// successful SSO login.
|
|
|
|
|
///
|
|
|
|
|
/// [`login_with_token`]: #method.login_with_token
|
|
|
|
|
pub fn get_sso_login_url(&self, redirect_url: &str) -> Result<String> {
|
|
|
|
|
let homeserver = self.homeserver();
|
|
|
|
|
let request =
|
|
|
|
|
sso_login::Request::new(redirect_url).try_into_http_request(homeserver.as_str(), None);
|
|
|
|
|
match request {
|
|
|
|
|
Ok(req) => Ok(req.uri().to_string()),
|
|
|
|
|
Err(err) => Err(Error::from(HttpError::from(err))),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Login to the server.
|
|
|
|
|
///
|
|
|
|
|
/// This can be used for the first login as well as for subsequent logins,
|
|
|
|
@ -693,8 +748,281 @@ impl Client {
|
|
|
|
|
|
|
|
|
|
let request = assign!(
|
|
|
|
|
login::Request::new(
|
|
|
|
|
login::UserInfo::MatrixId(user),
|
|
|
|
|
login::LoginInfo::Password { password },
|
|
|
|
|
login::LoginInfo::Password { identifier: login::UserIdentifier::MatrixId(user), password },
|
|
|
|
|
), {
|
|
|
|
|
device_id: device_id.map(|d| d.into()),
|
|
|
|
|
initial_device_display_name,
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
let response = self.send(request, None).await?;
|
|
|
|
|
self.base_client.receive_login_response(&response).await?;
|
|
|
|
|
|
|
|
|
|
Ok(response)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Login to the server via Single Sign-On.
|
|
|
|
|
///
|
|
|
|
|
/// This takes care of the whole SSO flow:
|
|
|
|
|
/// * Spawn a local http server
|
|
|
|
|
/// * Provide a callback to open the SSO login URL in a web browser
|
|
|
|
|
/// * Wait for the local http server to get the loginToken
|
|
|
|
|
/// * Call [`login_with_token`]
|
|
|
|
|
///
|
|
|
|
|
/// If cancellation is needed the method should be wrapped in a cancellable
|
|
|
|
|
/// task. **Note** that users with root access to the system have the ability
|
|
|
|
|
/// to snoop in on the data/token that is passed to the local HTTP server
|
|
|
|
|
/// that will be spawned.
|
|
|
|
|
///
|
|
|
|
|
/// If you need more control over the SSO login process, you should use
|
|
|
|
|
/// [`get_sso_login_url`] and [`login_with_token`] directly.
|
|
|
|
|
///
|
|
|
|
|
/// This should only be used for the first login.
|
|
|
|
|
///
|
|
|
|
|
/// The [`restore_login`] method should be used to restore a
|
|
|
|
|
/// logged in client after the first login.
|
|
|
|
|
///
|
|
|
|
|
/// A device id should be provided to restore the correct stores, if the
|
|
|
|
|
/// device id isn't provided a new device will be created.
|
|
|
|
|
///
|
|
|
|
|
/// # Arguments
|
|
|
|
|
///
|
|
|
|
|
/// * `use_sso_login_url` - A callback that will receive the SSO Login URL. It
|
|
|
|
|
/// should usually be used to open the SSO URL in a browser and must return
|
|
|
|
|
/// `Ok(())` if the URL was successfully opened. If it returns `Err`, the
|
|
|
|
|
/// error will be forwarded.
|
|
|
|
|
///
|
|
|
|
|
/// * `server_url` - The local URL the server is going to try to bind to, e.g.
|
|
|
|
|
/// `http://localhost:3030`. If `None`, the server will try to open a random
|
|
|
|
|
/// port on localhost.
|
|
|
|
|
///
|
|
|
|
|
/// * `server_response` - The text that will be shown on the webpage at the end
|
|
|
|
|
/// of the login process. This can be an HTML page. If `None`, a default
|
|
|
|
|
/// text will be displayed.
|
|
|
|
|
///
|
|
|
|
|
/// * `device_id` - A unique id that will be associated with this session. If
|
|
|
|
|
/// not given the homeserver will create one. Can be an existing device_id
|
|
|
|
|
/// from a previous login call. Note that this should be provided only
|
|
|
|
|
/// if the client also holds the encryption keys for this device.
|
|
|
|
|
///
|
|
|
|
|
/// * `initial_device_display_name` - A public display name that will be
|
|
|
|
|
/// associated with the device_id. Only necessary the first time you
|
|
|
|
|
/// login with this device_id. It can be changed later.
|
|
|
|
|
///
|
|
|
|
|
/// # Example
|
|
|
|
|
/// ```no_run
|
|
|
|
|
/// # use matrix_sdk::Client;
|
|
|
|
|
/// # use futures::executor::block_on;
|
|
|
|
|
/// # use url::Url;
|
|
|
|
|
/// # let homeserver = Url::parse("https://example.com").unwrap();
|
|
|
|
|
/// # block_on(async {
|
|
|
|
|
/// let client = Client::new(homeserver).unwrap();
|
|
|
|
|
///
|
|
|
|
|
/// let response = client
|
|
|
|
|
/// .login_with_sso(
|
|
|
|
|
/// |sso_url| async move {
|
|
|
|
|
/// // Open sso_url
|
|
|
|
|
/// Ok(())
|
|
|
|
|
/// },
|
|
|
|
|
/// None,
|
|
|
|
|
/// None,
|
|
|
|
|
/// None,
|
|
|
|
|
/// Some("My app")
|
|
|
|
|
/// )
|
|
|
|
|
/// .await
|
|
|
|
|
/// .unwrap();
|
|
|
|
|
///
|
|
|
|
|
/// println!("Logged in as {}, got device_id {} and access_token {}",
|
|
|
|
|
/// response.user_id, response.device_id, response.access_token);
|
|
|
|
|
/// # })
|
|
|
|
|
/// ```
|
|
|
|
|
///
|
|
|
|
|
/// [`get_sso_login_url`]: #method.get_sso_login_url
|
|
|
|
|
/// [`login_with_token`]: #method.login_with_token
|
|
|
|
|
/// [`restore_login`]: #method.restore_login
|
|
|
|
|
#[cfg(all(feature = "sso_login", not(target_arch = "wasm32")))]
|
|
|
|
|
#[cfg_attr(
|
|
|
|
|
feature = "docs",
|
|
|
|
|
doc(cfg(all(sso_login, not(target_arch = "wasm32"))))
|
|
|
|
|
)]
|
|
|
|
|
pub async fn login_with_sso<C>(
|
|
|
|
|
&self,
|
|
|
|
|
use_sso_login_url: impl Fn(String) -> C,
|
|
|
|
|
server_url: Option<&str>,
|
|
|
|
|
server_response: Option<&str>,
|
|
|
|
|
device_id: Option<&str>,
|
|
|
|
|
initial_device_display_name: Option<&str>,
|
|
|
|
|
) -> Result<login::Response>
|
|
|
|
|
where
|
|
|
|
|
C: Future<Output = Result<()>>,
|
|
|
|
|
{
|
|
|
|
|
info!("Logging in to {}", self.homeserver);
|
|
|
|
|
let (signal_tx, signal_rx) = oneshot::channel();
|
|
|
|
|
let (data_tx, data_rx) = oneshot::channel();
|
|
|
|
|
let data_tx_mutex = Arc::new(std::sync::Mutex::new(Some(data_tx)));
|
|
|
|
|
|
|
|
|
|
let mut redirect_url = match server_url {
|
|
|
|
|
Some(s) => match Url::parse(s) {
|
|
|
|
|
Ok(url) => url,
|
|
|
|
|
Err(err) => return Err(IoError::new(IoErrorKind::InvalidData, err).into()),
|
|
|
|
|
},
|
|
|
|
|
None => Url::parse("http://localhost:0/").unwrap(),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let response = match server_response {
|
|
|
|
|
Some(s) => s.to_string(),
|
|
|
|
|
None => String::from(
|
|
|
|
|
"The Single Sign-On login process is complete. You can close this page now.",
|
|
|
|
|
),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let route = warp::get()
|
|
|
|
|
.and(warp::query::<HashMap<String, String>>())
|
|
|
|
|
.map(move |p: HashMap<String, String>| {
|
|
|
|
|
if let Some(data_tx) = data_tx_mutex.lock().unwrap().take() {
|
|
|
|
|
if let Some(token) = p.get("loginToken") {
|
|
|
|
|
data_tx.send(Some(token.to_owned())).unwrap();
|
|
|
|
|
} else {
|
|
|
|
|
data_tx.send(None).unwrap();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Response::builder().body(response.clone())
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
let listener = {
|
|
|
|
|
if redirect_url
|
|
|
|
|
.port()
|
|
|
|
|
.expect("The redirect URL doesn't include a port")
|
|
|
|
|
== 0
|
|
|
|
|
{
|
|
|
|
|
let host = redirect_url
|
|
|
|
|
.host_str()
|
|
|
|
|
.expect("The redirect URL doesn't have a host");
|
|
|
|
|
let mut n = 0u8;
|
|
|
|
|
let mut port = 0u16;
|
|
|
|
|
let mut res = Err(IoError::new(IoErrorKind::Other, ""));
|
|
|
|
|
let mut rng = thread_rng();
|
|
|
|
|
|
|
|
|
|
while res.is_err() && n < SSO_SERVER_BIND_TRIES {
|
|
|
|
|
port = rng.gen_range(SSO_SERVER_BIND_RANGE);
|
|
|
|
|
res = TcpListener::bind((host, port)).await;
|
|
|
|
|
n += 1;
|
|
|
|
|
}
|
|
|
|
|
match res {
|
|
|
|
|
Ok(s) => {
|
|
|
|
|
redirect_url
|
|
|
|
|
.set_port(Some(port))
|
|
|
|
|
.expect("Could not set new port on redirect URL");
|
|
|
|
|
s
|
|
|
|
|
}
|
|
|
|
|
Err(err) => return Err(err.into()),
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
match TcpListener::bind(redirect_url.as_str()).await {
|
|
|
|
|
Ok(s) => s,
|
|
|
|
|
Err(err) => return Err(err.into()),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let server = warp::serve(route).serve_incoming_with_graceful_shutdown(
|
|
|
|
|
TcpListenerStream::new(listener),
|
|
|
|
|
async {
|
|
|
|
|
signal_rx.await.ok();
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
tokio::spawn(server);
|
|
|
|
|
|
|
|
|
|
let sso_url = self.get_sso_login_url(redirect_url.as_str()).unwrap();
|
|
|
|
|
|
|
|
|
|
match use_sso_login_url(sso_url).await {
|
|
|
|
|
Ok(t) => t,
|
|
|
|
|
Err(err) => return Err(err),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let token = match data_rx.await {
|
|
|
|
|
Ok(Some(t)) => t,
|
|
|
|
|
Ok(None) => {
|
|
|
|
|
return Err(IoError::new(IoErrorKind::Other, "Could not get the loginToken").into())
|
|
|
|
|
}
|
|
|
|
|
Err(err) => return Err(IoError::new(IoErrorKind::Other, format!("{}", err)).into()),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let _ = signal_tx.send(());
|
|
|
|
|
|
|
|
|
|
self.login_with_token(token.as_str(), device_id, initial_device_display_name)
|
|
|
|
|
.await
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Login to the server with a token.
|
|
|
|
|
///
|
|
|
|
|
/// This token is usually received in the SSO flow after following the URL
|
|
|
|
|
/// provided by [`get_sso_login_url`], note that this is not the access token
|
|
|
|
|
/// of a session.
|
|
|
|
|
///
|
|
|
|
|
/// This should only be used for the first login.
|
|
|
|
|
///
|
|
|
|
|
/// The [`restore_login`] method should be used to restore a
|
|
|
|
|
/// logged in client after the first login.
|
|
|
|
|
///
|
|
|
|
|
/// A device id should be provided to restore the correct stores, if the
|
|
|
|
|
/// device id isn't provided a new device will be created.
|
|
|
|
|
///
|
|
|
|
|
/// # Arguments
|
|
|
|
|
///
|
|
|
|
|
/// * `token` - A login token.
|
|
|
|
|
///
|
|
|
|
|
/// * `device_id` - A unique id that will be associated with this session. If
|
|
|
|
|
/// not given the homeserver will create one. Can be an existing device_id
|
|
|
|
|
/// from a previous login call. Note that this should be provided only
|
|
|
|
|
/// if the client also holds the encryption keys for this device.
|
|
|
|
|
///
|
|
|
|
|
/// * `initial_device_display_name` - A public display name that will be
|
|
|
|
|
/// associated with the device_id. Only necessary the first time you
|
|
|
|
|
/// login with this device_id. It can be changed later.
|
|
|
|
|
///
|
|
|
|
|
/// # Example
|
|
|
|
|
/// ```no_run
|
|
|
|
|
/// # use std::convert::TryFrom;
|
|
|
|
|
/// # use matrix_sdk::Client;
|
|
|
|
|
/// # use matrix_sdk::identifiers::DeviceId;
|
|
|
|
|
/// # use matrix_sdk_common::assign;
|
|
|
|
|
/// # use futures::executor::block_on;
|
|
|
|
|
/// # use url::Url;
|
|
|
|
|
/// # let homeserver = Url::parse("https://example.com").unwrap();
|
|
|
|
|
/// # let redirect_url = "http://localhost:1234";
|
|
|
|
|
/// # let login_token = "token";
|
|
|
|
|
/// # block_on(async {
|
|
|
|
|
/// let client = Client::new(homeserver).unwrap();
|
|
|
|
|
/// let sso_url = client.get_sso_login_url(redirect_url);
|
|
|
|
|
///
|
|
|
|
|
/// // Let the user authenticate at the SSO URL
|
|
|
|
|
/// // Receive the loginToken param at redirect_url
|
|
|
|
|
///
|
|
|
|
|
/// let response = client
|
|
|
|
|
/// .login_with_token(login_token, None, Some("My app")).await
|
|
|
|
|
/// .unwrap();
|
|
|
|
|
///
|
|
|
|
|
/// println!("Logged in as {}, got device_id {} and access_token {}",
|
|
|
|
|
/// response.user_id, response.device_id, response.access_token);
|
|
|
|
|
/// # })
|
|
|
|
|
/// ```
|
|
|
|
|
///
|
|
|
|
|
/// [`get_sso_login_url`]: #method.get_sso_login_url
|
|
|
|
|
/// [`restore_login`]: #method.restore_login
|
|
|
|
|
#[instrument(skip(token))]
|
|
|
|
|
pub async fn login_with_token(
|
|
|
|
|
&self,
|
|
|
|
|
token: &str,
|
|
|
|
|
device_id: Option<&str>,
|
|
|
|
|
initial_device_display_name: Option<&str>,
|
|
|
|
|
) -> Result<login::Response> {
|
|
|
|
|
info!("Logging in to {}", self.homeserver);
|
|
|
|
|
|
|
|
|
|
let request = assign!(
|
|
|
|
|
login::Request::new(
|
|
|
|
|
login::LoginInfo::Token { token },
|
|
|
|
|
), {
|
|
|
|
|
device_id: device_id.map(|d| d.into()),
|
|
|
|
|
initial_device_display_name,
|
|
|
|
@ -1884,7 +2212,7 @@ mod test {
|
|
|
|
|
api::r0::{
|
|
|
|
|
account::register::Request as RegistrationRequest,
|
|
|
|
|
directory::get_public_rooms_filtered::Request as PublicRoomsFilterRequest,
|
|
|
|
|
membership::Invite3pid, uiaa::AuthData,
|
|
|
|
|
membership::Invite3pid, session::get_login_types::LoginType, uiaa::AuthData,
|
|
|
|
|
},
|
|
|
|
|
assign,
|
|
|
|
|
directory::Filter,
|
|
|
|
@ -1915,15 +2243,106 @@ mod test {
|
|
|
|
|
async fn login() {
|
|
|
|
|
let homeserver = Url::from_str(&mockito::server_url()).unwrap();
|
|
|
|
|
|
|
|
|
|
let client = Client::new(homeserver).unwrap();
|
|
|
|
|
|
|
|
|
|
let _m_types = mock("GET", "/_matrix/client/r0/login")
|
|
|
|
|
.with_status(200)
|
|
|
|
|
.with_body(test_json::LOGIN_TYPES.to_string())
|
|
|
|
|
.create();
|
|
|
|
|
|
|
|
|
|
let can_password = client
|
|
|
|
|
.get_login_types()
|
|
|
|
|
.await
|
|
|
|
|
.unwrap()
|
|
|
|
|
.flows
|
|
|
|
|
.iter()
|
|
|
|
|
.any(|flow| flow == &LoginType::Password);
|
|
|
|
|
assert!(can_password);
|
|
|
|
|
|
|
|
|
|
let _m_login = mock("POST", "/_matrix/client/r0/login")
|
|
|
|
|
.with_status(200)
|
|
|
|
|
.with_body(test_json::LOGIN.to_string())
|
|
|
|
|
.create();
|
|
|
|
|
|
|
|
|
|
client
|
|
|
|
|
.login("example", "wordpass", None, None)
|
|
|
|
|
.await
|
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
|
|
let logged_in = client.logged_in().await;
|
|
|
|
|
assert!(logged_in, "Client should be logged in");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(feature = "sso_login")]
|
|
|
|
|
#[tokio::test]
|
|
|
|
|
async fn login_with_sso() {
|
|
|
|
|
let _m_login = mock("POST", "/_matrix/client/r0/login")
|
|
|
|
|
.with_status(200)
|
|
|
|
|
.with_body(test_json::LOGIN.to_string())
|
|
|
|
|
.create();
|
|
|
|
|
|
|
|
|
|
let homeserver = Url::from_str(&mockito::server_url()).unwrap();
|
|
|
|
|
let client = Client::new(homeserver).unwrap();
|
|
|
|
|
|
|
|
|
|
client
|
|
|
|
|
.login_with_sso(
|
|
|
|
|
|sso_url| async move {
|
|
|
|
|
let sso_url = Url::parse(sso_url.as_str()).unwrap();
|
|
|
|
|
|
|
|
|
|
let (_, redirect) = sso_url
|
|
|
|
|
.query_pairs()
|
|
|
|
|
.find(|(key, _)| key == "redirectUrl")
|
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
|
|
let mut redirect_url = Url::parse(redirect.into_owned().as_str()).unwrap();
|
|
|
|
|
redirect_url.set_query(Some("loginToken=tinytoken"));
|
|
|
|
|
|
|
|
|
|
reqwest::get(redirect_url.to_string()).await.unwrap();
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
},
|
|
|
|
|
None,
|
|
|
|
|
None,
|
|
|
|
|
None,
|
|
|
|
|
None,
|
|
|
|
|
)
|
|
|
|
|
.await
|
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
|
|
let logged_in = client.logged_in().await;
|
|
|
|
|
assert!(logged_in, "Client should be logged in");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
|
async fn login_with_sso_token() {
|
|
|
|
|
let homeserver = Url::from_str(&mockito::server_url()).unwrap();
|
|
|
|
|
|
|
|
|
|
let client = Client::new(homeserver).unwrap();
|
|
|
|
|
|
|
|
|
|
let _m = mock("GET", "/_matrix/client/r0/login")
|
|
|
|
|
.with_status(200)
|
|
|
|
|
.with_body(test_json::LOGIN_TYPES.to_string())
|
|
|
|
|
.create();
|
|
|
|
|
|
|
|
|
|
let can_sso = client
|
|
|
|
|
.get_login_types()
|
|
|
|
|
.await
|
|
|
|
|
.unwrap()
|
|
|
|
|
.flows
|
|
|
|
|
.iter()
|
|
|
|
|
.any(|flow| flow == &LoginType::Sso);
|
|
|
|
|
assert!(can_sso);
|
|
|
|
|
|
|
|
|
|
let sso_url = client.get_sso_login_url("http://127.0.0.1:3030");
|
|
|
|
|
assert!(sso_url.is_ok());
|
|
|
|
|
|
|
|
|
|
let _m = mock("POST", "/_matrix/client/r0/login")
|
|
|
|
|
.with_status(200)
|
|
|
|
|
.with_body(test_json::LOGIN.to_string())
|
|
|
|
|
.create();
|
|
|
|
|
|
|
|
|
|
let client = Client::new(homeserver).unwrap();
|
|
|
|
|
|
|
|
|
|
client
|
|
|
|
|
.login("example", "wordpass", None, None)
|
|
|
|
|
.login_with_token("averysmalltoken", None, None)
|
|
|
|
|
.await
|
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
|
|