matrix-sdk: Add support to delete devices.

master
Damir Jelić 2020-10-16 17:27:00 +02:00
parent 425a07d670
commit b1c8c64205
2 changed files with 157 additions and 4 deletions

View File

@ -65,7 +65,7 @@ pub enum LoopCtrl {
use matrix_sdk_common::{ use matrix_sdk_common::{
api::r0::{ api::r0::{
account::register, account::register,
device::get_devices, device::{delete_devices, get_devices},
directory::{get_public_rooms, get_public_rooms_filtered}, directory::{get_public_rooms, get_public_rooms_filtered},
media::create_content, media::create_content,
membership::{ membership::{
@ -82,6 +82,7 @@ use matrix_sdk_common::{
typing::create_typing_event::{ typing::create_typing_event::{
Request as TypingRequest, Response as TypingResponse, Typing, Request as TypingRequest, Response as TypingResponse, Typing,
}, },
uiaa::AuthData,
}, },
assign, assign,
events::{ events::{
@ -1383,6 +1384,71 @@ impl Client {
self.send(request).await self.send(request).await
} }
/// Delete the given devices from the server.
///
/// # Arguments
///
/// * `devices` - The list of devices that should be deleted from the
/// server.
///
/// * `auth_data` - This request requires user interactive auth, the first
/// request needs to set this to `None` and will always fail with an
/// `UiaaResponse`. The response will contain information for the
/// interactive auth and the same request needs to be made but this time
/// with some `auth_data` provided.
///
/// ```no_run
/// # use matrix_sdk::{
/// # api::r0::uiaa::{UiaaResponse, AuthData},
/// # Client, SyncSettings, Error, FromHttpResponseError, ServerError,
/// # };
/// # use futures::executor::block_on;
/// # use serde_json::json;
/// # use url::Url;
/// # use std::{collections::BTreeMap, convert::TryFrom};
/// # block_on(async {
/// # let homeserver = Url::parse("http://localhost:8080").unwrap();
/// # let mut client = Client::new(homeserver).unwrap();
/// let devices = &["DEVICEID".into()];
///
/// if let Err(e) = client.delete_devices(devices, None).await {
/// if let Some(info) = e.uiaa_response() {
/// let mut auth_parameters = BTreeMap::new();
///
/// let identifier = json!({
/// "type": "m.id.user",
/// "user": "example",
/// });
/// auth_parameters.insert("identifier".to_owned(), identifier);
/// auth_parameters.insert("password".to_owned(), "wordpass".into());
///
/// // This is needed because of https://github.com/matrix-org/synapse/issues/5665
/// auth_parameters.insert("user".to_owned(), "@example:localhost".into());
///
/// let auth_data = AuthData::DirectRequest {
/// kind: "m.login.password",
/// auth_parameters,
/// session: info.session.as_deref(),
/// };
///
/// client
/// .delete_devices(devices, Some(auth_data))
/// .await
/// .expect("Can't delete devices");
/// }
/// }
/// # });
pub async fn delete_devices(
&self,
devices: &[DeviceIdBox],
auth_data: Option<AuthData<'_>>,
) -> Result<delete_devices::Response> {
let mut request = delete_devices::Request::new(devices);
request.auth = auth_data;
self.send(request).await
}
/// Synchronize the client's state with the latest state on the server. /// Synchronize the client's state with the latest state on the server.
/// ///
/// **Note**: You should not use this method to repeatedly sync if encryption /// **Note**: You should not use this method to repeatedly sync if encryption
@ -1956,7 +2022,10 @@ mod test {
use serde_json::json; use serde_json::json;
use tempfile::tempdir; use tempfile::tempdir;
use std::{convert::TryInto, io::Cursor, path::Path, str::FromStr, time::Duration}; use std::{
collections::BTreeMap, convert::TryInto, io::Cursor, path::Path, str::FromStr,
time::Duration,
};
async fn logged_in_client() -> Client { async fn logged_in_client() -> Client {
let session = Session { let session = Session {
@ -2755,4 +2824,61 @@ mod test {
assert_eq!("tutorial".to_string(), room.read().await.display_name()); assert_eq!("tutorial".to_string(), room.read().await.display_name());
} }
#[tokio::test]
async fn delete_devices() {
let homeserver = Url::from_str(&mockito::server_url()).unwrap();
let client = Client::new(homeserver).unwrap();
let _m = mock("POST", "/_matrix/client/r0/delete_devices")
.with_status(401)
.with_body(
json!({
"flows": [
{
"stages": [
"m.login.password"
]
}
],
"params": {},
"session": "vBslorikviAjxzYBASOBGfPp"
})
.to_string(),
)
.create();
let _m = mock("POST", "/_matrix/client/r0/delete_devices")
.with_status(401)
// empty response
// TODO rename that response type.
.with_body(test_json::LOGOUT.to_string())
.create();
let devices = &["DEVICEID".into()];
if let Err(e) = client.delete_devices(devices, None).await {
if let Some(info) = e.uiaa_response() {
let mut auth_parameters = BTreeMap::new();
let identifier = json!({
"type": "m.id.user",
"user": "example",
});
auth_parameters.insert("identifier".to_owned(), identifier);
auth_parameters.insert("password".to_owned(), "wordpass".into());
let auth_data = AuthData::DirectRequest {
kind: "m.login.password",
auth_parameters,
session: info.session.as_deref(),
};
client
.delete_devices(devices, Some(auth_data))
.await
.unwrap();
}
}
}
} }

View File

@ -16,8 +16,11 @@
use matrix_sdk_base::Error as MatrixError; use matrix_sdk_base::Error as MatrixError;
use matrix_sdk_common::{ use matrix_sdk_common::{
api::{r0::uiaa::UiaaResponse as UiaaError, Error as RumaClientError}, api::{
FromHttpResponseError as RumaResponseError, IntoHttpError as RumaIntoHttpError, r0::uiaa::{UiaaInfo, UiaaResponse as UiaaError},
Error as RumaClientError,
},
FromHttpResponseError as RumaResponseError, IntoHttpError as RumaIntoHttpError, ServerError,
}; };
use reqwest::Error as ReqwestError; use reqwest::Error as ReqwestError;
use serde_json::Error as JsonError; use serde_json::Error as JsonError;
@ -79,6 +82,30 @@ pub enum Error {
UiaaError(RumaResponseError<UiaaError>), UiaaError(RumaResponseError<UiaaError>),
} }
impl Error {
/// Try to destructure the error into an universal interactive auth info.
///
/// Some requests require universal interactive auth, doing such a request
/// will always fail the first time with a 401 status code, the response
/// body will contain info how the client can authenticate.
///
/// The request will need to be retried, this time containing additional
/// authentication data.
///
/// This method is an convenience method to get to the info the server
/// returned on the first, failed request.
pub fn uiaa_response(&self) -> Option<&UiaaInfo> {
if let Error::UiaaError(RumaResponseError::Http(ServerError::Known(
UiaaError::AuthResponse(i),
))) = self
{
Some(i)
} else {
None
}
}
}
impl From<RumaResponseError<UiaaError>> for Error { impl From<RumaResponseError<UiaaError>> for Error {
fn from(error: RumaResponseError<UiaaError>) -> Self { fn from(error: RumaResponseError<UiaaError>) -> Self {
Self::UiaaError(error) Self::UiaaError(error)