Merge pull request #857 from matrix-org/doug/homeserver-details

Add a `HomeserverLoginDetails` to the FFI's auth service.
This commit is contained in:
Benjamin Kampmann
2022-07-20 10:27:09 +02:00
committed by GitHub
3 changed files with 94 additions and 58 deletions

View File

@@ -156,24 +156,23 @@ interface MediaSource {
[Error]
enum AuthenticationError {
"ClientMissing",
"Generic",
"ClientMissing",
"Generic",
};
interface HomeserverLoginDetails {
string url();
string? authentication_issuer();
boolean supports_password_login();
};
interface AuthenticationService {
constructor(string base_path);
[Throws=AuthenticationError]
string homeserver();
HomeserverLoginDetails? homeserver_details();
[Throws=AuthenticationError]
string? authentication_issuer();
[Throws=AuthenticationError]
boolean supports_password_login();
[Throws=AuthenticationError]
void use_server(string server_name);
void configure_homeserver(string server_name);
[Throws=AuthenticationError]
Client login(string username, string password);

View File

@@ -1,12 +1,14 @@
use std::sync::Arc;
use futures_util::future::join3;
use parking_lot::RwLock;
use super::{client::Client, client_builder::ClientBuilder};
use super::{client::Client, client_builder::ClientBuilder, RUNTIME};
pub struct AuthenticationService {
base_path: String,
client: RwLock<Option<Arc<Client>>>,
homeserver_details: RwLock<Option<Arc<HomeserverLoginDetails>>>,
}
#[derive(Debug, thiserror::Error)]
@@ -23,52 +25,67 @@ impl From<anyhow::Error> for AuthenticationError {
}
}
impl AuthenticationService {
/// Creates a new service to authenticate a user with.
pub fn new(base_path: String) -> Self {
AuthenticationService { base_path, client: RwLock::new(None) }
}
pub struct HomeserverLoginDetails {
url: String,
authentication_issuer: Option<String>,
supports_password_login: bool,
}
/// The currently configured homeserver.
pub fn homeserver(&self) -> Result<String, AuthenticationError> {
self.client
.read()
.as_ref()
.ok_or(AuthenticationError::ClientMissing)
.map(|client| client.homeserver())
impl HomeserverLoginDetails {
/// The URL of the currently configured homeserver.
pub fn url(&self) -> String {
self.url.clone()
}
/// The OIDC Provider that is trusted by the homeserver. `None` when
/// not configured.
pub fn authentication_issuer(&self) -> Result<Option<String>, AuthenticationError> {
self.client
.read()
.as_ref()
.ok_or(AuthenticationError::ClientMissing)
.map(|client| client.authentication_issuer())
pub fn authentication_issuer(&self) -> Option<String> {
self.authentication_issuer.clone()
}
/// Whether the current homeserver supports the password login flow.
pub fn supports_password_login(&self) -> Result<bool, AuthenticationError> {
self.client
.read()
.as_ref()
.ok_or(AuthenticationError::ClientMissing)
.and_then(|client| client.supports_password_login().map_err(AuthenticationError::from))
pub fn supports_password_login(&self) -> bool {
self.supports_password_login
}
}
impl AuthenticationService {
/// Creates a new service to authenticate a user with.
pub fn new(base_path: String) -> Self {
AuthenticationService {
base_path,
client: RwLock::new(None),
homeserver_details: RwLock::new(None),
}
}
/// Updates the server to authenticate with the specified homeserver.
pub fn use_server(&self, server_name: String) -> Result<(), AuthenticationError> {
pub fn homeserver_details(&self) -> Option<Arc<HomeserverLoginDetails>> {
self.homeserver_details.read().clone()
}
/// Updates the service to authenticate with the homeserver for the
/// specified address.
pub fn configure_homeserver(&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()
.map_err(AuthenticationError::from)?;
*self.client.write() = Some(client);
Ok(())
let mut builder =
Arc::new(ClientBuilder::new()).base_path(self.base_path.clone()).username(username);
if server_name.starts_with("http://") || server_name.starts_with("https://") {
builder = builder.homeserver_url(server_name)
}
let client = builder.build().map_err(AuthenticationError::from)?;
RUNTIME.block_on(async move {
let details = Arc::new(self.details_from_client(&client).await?);
*self.client.write() = Some(client);
*self.homeserver_details.write() = Some(details);
Ok(())
})
}
/// Performs a password login using the current homeserver.
@@ -97,4 +114,23 @@ impl AuthenticationService {
None => Err(AuthenticationError::ClientMissing),
}
}
/// Get the homeserver login details from a client.
async fn details_from_client(
&self,
client: &Arc<Client>,
) -> Result<HomeserverLoginDetails, AuthenticationError> {
let login_details = join3(
client.async_homeserver(),
client.authentication_issuer(),
client.supports_password_login(),
)
.await;
let url = login_details.0;
let authentication_issuer = login_details.1;
let supports_password_login = login_details.2.map_err(AuthenticationError::from)?;
Ok(HomeserverLoginDetails { url, authentication_issuer, supports_password_login })
}
}

View File

@@ -74,26 +74,27 @@ impl Client {
/// The homeserver this client is configured to use.
pub fn homeserver(&self) -> String {
RUNTIME.block_on(async move { self.client.homeserver().await.to_string() })
RUNTIME.block_on(async move { self.async_homeserver().await })
}
pub async fn async_homeserver(&self) -> String {
self.client.homeserver().await.to_string()
}
/// The OIDC Provider that is trusted by the homeserver. `None` when
/// not configured.
pub fn authentication_issuer(&self) -> Option<String> {
RUNTIME.block_on(async move {
self.client.authentication_issuer().await.map(|server| server.to_string())
})
pub async fn authentication_issuer(&self) -> Option<String> {
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| {
matches!(login_type, get_login_types::v3::LoginType::Password(_))
});
Ok(supports_password)
})
pub async fn supports_password_login(&self) -> anyhow::Result<bool> {
let login_types = self.client.get_login_types().await?;
let supports_password = login_types
.flows
.iter()
.any(|login_type| matches!(login_type, get_login_types::v3::LoginType::Password(_)));
Ok(supports_password)
}
pub fn start_sync(&self) {