Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add an authentication service to the FFI #820

Merged
merged 7 commits into from
Jul 8, 2022
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,6 @@ emsdk-*
## User settings
xcuserdata/
.vscode/

## OS garbage
.DS_Store
27 changes: 27 additions & 0 deletions bindings/matrix-sdk-ffi/src/api.udl
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ interface Client {
[Throws=ClientError]
void restore_login(string restore_token);

string homeserver();
pixlwave marked this conversation as resolved.
Show resolved Hide resolved

void start_sync();

[Throws=ClientError]
Expand Down Expand Up @@ -152,6 +154,31 @@ interface MediaSource {
string url();
};

[Error]
enum AuthenticationError {
"ClientMissing",
"Generic",
};

interface AuthenticationService {
constructor(string base_path);

[Throws=AuthenticationError]
string homeserver();

[Throws=AuthenticationError]
string? authentication_issuer();

[Throws=AuthenticationError]
boolean supports_password_login();

[Throws=AuthenticationError]
void use_server(string server_name);

[Throws=AuthenticationError]
Client login(string username, string password);
};

interface SessionVerificationEmoji {
string symbol();
string description();
Expand Down
103 changes: 103 additions & 0 deletions bindings/matrix-sdk-ffi/src/authentication_service.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
use std::sync::Arc;

use parking_lot::RwLock;
pixlwave marked this conversation as resolved.
Show resolved Hide resolved

use super::{client::Client, client_builder::ClientBuilder};

pub struct AuthenticationService {
base_path: String,
client_container: RwLock<ClientContainer>,
}

struct ClientContainer {
pixlwave marked this conversation as resolved.
Show resolved Hide resolved
client: Option<Arc<Client>>,
}

#[derive(Debug, thiserror::Error)]
pub enum AuthenticationError {
#[error("A successfull call to use_server must be made first.")]
ClientMissing,
#[error("An error occurred: {message}")]
Generic { message: String },
}

impl From<anyhow::Error> for AuthenticationError {
fn from(e: anyhow::Error) -> AuthenticationError {
AuthenticationError::Generic { message: e.to_string() }
}
}

impl AuthenticationService {
/// Creates a new service to authenticate a user with.
pub fn new(base_path: String) -> Self {
AuthenticationService {
base_path,
client_container: RwLock::new(ClientContainer { client: None }),
}
}

/// The currently configured homeserver.
pub fn homeserver(&self) -> Result<String, AuthenticationError> {
self.client_container
.read()
.client
.as_ref()
.ok_or(AuthenticationError::ClientMissing)
.and_then(|client| Ok(client.homeserver()))
}

/// The OIDC Provider that is trusted by the homeserver. `nil` when
/// not configured.
pub fn authentication_issuer(&self) -> Result<Option<String>, AuthenticationError> {
self.client_container
.read()
.client
.as_ref()
.ok_or(AuthenticationError::ClientMissing)
.and_then(|client| Ok(client.authentication_issuer()))
}

/// Whether the current homeserver supports the password login flow.
pub fn supports_password_login(&self) -> Result<bool, AuthenticationError> {
self.client_container
.read()
.client
.as_ref()
.ok_or(AuthenticationError::ClientMissing)
.and_then(|client| client.supports_password_login().map_err(AuthenticationError::from))
}

/// Updates the server to authenticate with the specified homeserver.
pub fn use_server(&self, server_name: String) -> Result<(), AuthenticationError> {
// Construct a username as the builder currently requires one.
let username = format!("@auth:{}", server_name);
let client = Arc::new(ClientBuilder::new())
.base_path(self.base_path.clone())
.username(username)
.build();

match client {
Ok(client) => {
let mut client_containter = self.client_container.write();
client_containter.client = Some(client);
Ok(())
}
Err(error) => Err(AuthenticationError::Generic { message: error.to_string() }),
}
}

/// Performs a password login using the current homeserver.
pub fn login(
&self,
username: String,
password: String,
) -> Result<Arc<Client>, AuthenticationError> {
match self.client_container.read().client.as_ref() {
Some(client) => client
.login(username, password)
.and_then(|_| Ok(client.clone()))
.map_err(AuthenticationError::from),
None => Err(AuthenticationError::ClientMissing),
}
}
}
26 changes: 26 additions & 0 deletions bindings/matrix-sdk-ffi/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use matrix_sdk::{
ruma::{
api::client::{
filter::{FilterDefinition, LazyLoadOptions, RoomEventFilter, RoomFilter},
session::get_login_types,
sync::sync_events::v3::Filter,
},
events::room::MediaSource,
Expand Down Expand Up @@ -71,6 +72,31 @@ impl Client {
*self.delegate.write() = delegate;
}

/// The homeserver this client is configured to use.
pub fn homeserver(&self) -> String {
RUNTIME.block_on(async move { self.client.homeserver().await.to_string() })
}

/// The OIDC Provider that is trusted by the homeserver. `nil` when
pixlwave marked this conversation as resolved.
Show resolved Hide resolved
/// not configured.
pub fn authentication_issuer(&self) -> Option<String> {
RUNTIME.block_on(async move {
self.client.authentication_issuer().await.map(|server| server.to_string())
})
}

/// Whether or not the client's homeserver supports the password login flow.
pub fn supports_password_login(&self) -> anyhow::Result<bool> {
RUNTIME.block_on(async move {
let login_types = self.client.get_login_types().await?;
let supports_password = login_types.flows.iter().any(|login_type| match login_type {
get_login_types::v3::LoginType::Password(_) => true,
_ => false,
});
Ok(supports_password)
})
}

pub fn start_sync(&self) {
let client = self.client.clone();
let state = self.state.clone();
Expand Down
6 changes: 5 additions & 1 deletion bindings/matrix-sdk-ffi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

#![allow(unused_qualifications)]

pub mod authentication_service;
pub mod backward_stream;
pub mod client;
pub mod client_builder;
Expand All @@ -23,7 +24,10 @@ pub static RUNTIME: Lazy<Runtime> =

pub use matrix_sdk::ruma::{api::client::account::register, UserId};

pub use self::{backward_stream::*, client::*, messages::*, room::*, session_verification::*};
pub use self::{
authentication_service::*, backward_stream::*, client::*, messages::*, room::*,
session_verification::*,
};

#[derive(Default, Debug)]
pub struct ClientState {
Expand Down
5 changes: 5 additions & 0 deletions crates/matrix-sdk/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,11 @@ git = "https://github.com/ruma/ruma"
rev = "96155915f"
features = ["client-api-c", "compat", "rand", "unstable-msc2448"]

[dependencies.ruma-client-api]
git = "https://github.com/ruma/ruma"
rev = "96155915f"
features = ["compat", "unstable-msc2965"]
pixlwave marked this conversation as resolved.
Show resolved Hide resolved

[dependencies.tokio-stream]
version = "0.1.8"
features = ["net"]
Expand Down
7 changes: 7 additions & 0 deletions crates/matrix-sdk/src/client/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,7 @@ impl ClientBuilder {
let base_client = BaseClient::with_store_config(self.store_config);
let http_client = HttpClient::new(inner_http_client.clone(), self.request_config);

let mut authentication_issuer: Option<Url> = None;
let homeserver = match homeserver_cfg {
HomeserverConfig::Url(url) => url,
HomeserverConfig::ServerName(server_name) => {
Expand All @@ -313,14 +314,20 @@ impl ClientBuilder {
err => ClientBuildError::Http(err),
})?;

if let Some(issuer) = well_known.authentication.map(|auth| auth.issuer) {
authentication_issuer = Url::parse(&issuer).ok();
};

well_known.homeserver.base_url
}
};

let homeserver = RwLock::new(Url::parse(&homeserver)?);
let authentication_issuer = authentication_issuer.map(|server| RwLock::new(server));

let inner = Arc::new(ClientInner {
homeserver,
authentication_issuer,
http_client,
base_client,
server_versions: OnceCell::new_with(self.server_versions),
Expand Down
10 changes: 10 additions & 0 deletions crates/matrix-sdk/src/client/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,8 @@ pub struct Client {
pub(crate) struct ClientInner {
/// The URL of the homeserver to connect to.
homeserver: RwLock<Url>,
/// The OIDC Provider that is trusted by the homeserver.
authentication_issuer: Option<RwLock<Url>>,
/// The underlying HTTP client.
http_client: HttpClient,
/// User session data.
Expand Down Expand Up @@ -292,6 +294,14 @@ impl Client {
self.inner.homeserver.read().await.clone()
}

/// The OIDC Provider that is trusted by the homeserver.
pub async fn authentication_issuer(&self) -> Option<Url> {
if let Some(server) = &self.inner.authentication_issuer {
pixlwave marked this conversation as resolved.
Show resolved Hide resolved
return Some(server.read().await.clone());
}
return None;
}

/// Get the user id of the current owner of the client.
pub fn user_id(&self) -> Option<&UserId> {
self.session().map(|s| s.user_id.as_ref())
Expand Down