Use a Client in AuthenticationService now that the Rust one has been removed. (#2954)

* Replace Rust's Authentication service by building a Client.

* Drop the Proxy from AuthenticationServiceProxy[Protocol].

* Make sure to call abortOIDCLogin.

* Centralise common ClientBuilder code.

* Update the SDK.
This commit is contained in:
Doug
2024-06-24 15:05:00 +01:00
committed by GitHub
parent fc0573a41d
commit 3465ea4cc1
23 changed files with 489 additions and 564 deletions
@@ -18,12 +18,14 @@ import Combine
import Foundation
import MatrixRustSDK
class AuthenticationServiceProxy: AuthenticationServiceProxyProtocol {
private let authenticationService: AuthenticationService
private let userSessionStore: UserSessionStoreProtocol
class AuthenticationService: AuthenticationServiceProtocol {
private var client: Client?
private let sessionDirectory: URL
private let passphrase: String
private let userSessionStore: UserSessionStoreProtocol
private let appSettings: AppSettings
private let homeserverSubject: CurrentValueSubject<LoginHomeserver, Never>
var homeserver: CurrentValuePublisher<LoginHomeserver, Never> { homeserverSubject.asCurrentValuePublisher() }
@@ -31,19 +33,10 @@ class AuthenticationServiceProxy: AuthenticationServiceProxyProtocol {
sessionDirectory = .sessionsBaseDirectory.appending(component: UUID().uuidString)
passphrase = encryptionKeyProvider.generateKey().base64EncodedString()
self.userSessionStore = userSessionStore
self.appSettings = appSettings
homeserverSubject = .init(LoginHomeserver(address: appSettings.defaultHomeserverAddress,
loginMode: .unknown))
authenticationService = AuthenticationService(sessionPath: sessionDirectory.path(percentEncoded: false),
passphrase: passphrase,
userAgent: UserAgentBuilder.makeASCIIUserAgent(),
additionalRootCertificates: [],
proxy: appSettings.websiteURL.globalProxy,
oidcConfiguration: appSettings.oidcConfiguration.rustValue,
customSlidingSyncProxy: appSettings.slidingSyncProxyURL?.absoluteString,
sessionDelegate: userSessionStore.clientSessionDelegate,
crossProcessRefreshLockId: InfoPlistReader.main.bundleIdentifier)
}
// MARK: - Public
@@ -52,24 +45,24 @@ class AuthenticationServiceProxy: AuthenticationServiceProxyProtocol {
do {
var homeserver = LoginHomeserver(address: homeserverAddress, loginMode: .unknown)
try await authenticationService.configureHomeserver(serverNameOrHomeserverUrl: homeserverAddress)
let client = try await makeClientBuilder().serverNameOrHomeserverUrl(serverNameOrUrl: homeserverAddress).build()
let loginDetails = await client.homeserverLoginDetails()
if let details = authenticationService.homeserverDetails() {
if details.supportsOidcLogin() {
homeserver.loginMode = .oidc
} else if details.supportsPasswordLogin() {
homeserver.loginMode = .password
} else {
homeserver.loginMode = .unsupported
}
if loginDetails.supportsOidcLogin() {
homeserver.loginMode = .oidc
} else if loginDetails.supportsPasswordLogin() {
homeserver.loginMode = .password
} else {
homeserver.loginMode = .unsupported
}
self.client = client
homeserverSubject.send(homeserver)
return .success(())
} catch AuthenticationError.WellKnownDeserializationError(let error) {
} catch ClientBuildError.WellKnownDeserializationError(let error) {
MXLog.error("The user entered a server with an invalid well-known file: \(error)")
return .failure(.invalidWellKnown(error))
} catch AuthenticationError.SlidingSyncNotAvailable {
} catch ClientBuildError.SlidingSyncNotAvailable {
MXLog.info("User entered a homeserver that isn't configured for sliding sync.")
return .failure(.slidingSyncNotAvailable)
} catch {
@@ -78,21 +71,29 @@ class AuthenticationServiceProxy: AuthenticationServiceProxyProtocol {
}
}
func urlForOIDCLogin() async -> Result<OIDCAuthenticationDataProxy, AuthenticationServiceError> {
func urlForOIDCLogin() async -> Result<OIDCAuthorizationDataProxy, AuthenticationServiceError> {
guard let client else { return .failure(.oidcError(.urlFailure)) }
do {
let oidcData = try await authenticationService.urlForOidcLogin()
return .success(OIDCAuthenticationDataProxy(underlyingData: oidcData))
let oidcData = try await client.urlForOidcLogin(oidcConfiguration: appSettings.oidcConfiguration.rustValue)
return .success(OIDCAuthorizationDataProxy(underlyingData: oidcData))
} catch {
MXLog.error("Failed to get URL for OIDC login: \(error)")
return .failure(.oidcError(.urlFailure))
}
}
func loginWithOIDCCallback(_ callbackURL: URL, data: OIDCAuthenticationDataProxy) async -> Result<UserSessionProtocol, AuthenticationServiceError> {
func abortOIDCLogin(data: OIDCAuthorizationDataProxy) async {
guard let client else { return }
MXLog.info("Aborting OIDC login.")
await client.abortOidcLogin(authorizationData: data.underlyingData)
}
func loginWithOIDCCallback(_ callbackURL: URL, data: OIDCAuthorizationDataProxy) async -> Result<UserSessionProtocol, AuthenticationServiceError> {
guard let client else { return .failure(.failedLoggingIn) }
do {
let client = try await authenticationService.loginWithOidcCallback(authenticationData: data.underlyingData, callbackUrl: callbackURL.absoluteString)
try await client.loginWithOidcCallback(authorizationData: data.underlyingData, callbackUrl: callbackURL.absoluteString)
return await userSession(for: client)
} catch AuthenticationError.OidcCancelled {
} catch OidcError.Cancelled {
return .failure(.oidcError(.userCancellation))
} catch {
MXLog.error("Login with OIDC failed: \(error)")
@@ -101,14 +102,11 @@ class AuthenticationServiceProxy: AuthenticationServiceProxyProtocol {
}
func login(username: String, password: String, initialDeviceName: String?, deviceID: String?) async -> Result<UserSessionProtocol, AuthenticationServiceError> {
guard let client else { return .failure(.failedLoggingIn) }
do {
let client = try await authenticationService.login(username: username,
password: password,
initialDeviceName: initialDeviceName,
deviceId: deviceID)
try await client.login(username: username, password: password, initialDeviceName: initialDeviceName, deviceId: deviceID)
let refreshToken = try? client.session().refreshToken
if refreshToken != nil {
MXLog.warning("Refresh token found for a non oidc session, can't restore session, logging out")
_ = try? await client.logout()
@@ -118,7 +116,8 @@ class AuthenticationServiceProxy: AuthenticationServiceProxyProtocol {
return await userSession(for: client)
} catch {
MXLog.error("Failed logging in with error: \(error)")
guard let error = error as? AuthenticationError else { return .failure(.failedLoggingIn) }
// FIXME: How about we make a proper type in the FFI? 😅
guard let error = error as? ClientError else { return .failure(.failedLoggingIn) }
if error.isElementWaitlist {
return .failure(.isOnWaitlist)
@@ -137,6 +136,16 @@ class AuthenticationServiceProxy: AuthenticationServiceProxyProtocol {
// MARK: - Private
private func makeClientBuilder() -> ClientBuilder {
ClientBuilder
.baseBuilder(httpProxy: appSettings.websiteURL.globalProxy,
slidingSyncProxy: appSettings.slidingSyncProxyURL,
sessionDelegate: userSessionStore.clientSessionDelegate)
.sessionPath(path: sessionDirectory.path(percentEncoded: false))
.passphrase(passphrase: passphrase)
.requiresSlidingSync()
}
private func userSession(for client: Client) async -> Result<UserSessionProtocol, AuthenticationServiceError> {
switch await userSessionStore.userSession(for: client, sessionDirectory: sessionDirectory, passphrase: passphrase) {
case .success(let clientProxy):
@@ -39,16 +39,18 @@ enum AuthenticationServiceError: Error {
case sessionTokenRefreshNotSupported
}
protocol AuthenticationServiceProxyProtocol {
protocol AuthenticationServiceProtocol {
/// The currently configured homeserver.
var homeserver: CurrentValuePublisher<LoginHomeserver, Never> { get }
/// Sets up the service for login on the specified homeserver address.
func configure(for homeserverAddress: String) async -> Result<Void, AuthenticationServiceError>
/// Performs login using OIDC for the current homeserver.
func urlForOIDCLogin() async -> Result<OIDCAuthenticationDataProxy, AuthenticationServiceError>
/// Add docs.
func loginWithOIDCCallback(_ callbackURL: URL, data: OIDCAuthenticationDataProxy) async -> Result<UserSessionProtocol, AuthenticationServiceError>
func urlForOIDCLogin() async -> Result<OIDCAuthorizationDataProxy, AuthenticationServiceError>
/// Asks the SDK to abort an ongoing OIDC login if we didn't get a callback to complete the request with.
func abortOIDCLogin(data: OIDCAuthorizationDataProxy) async
/// Completes an OIDC login that was started using ``urlForOIDCLogin``.
func loginWithOIDCCallback(_ callbackURL: URL, data: OIDCAuthorizationDataProxy) async -> Result<UserSessionProtocol, AuthenticationServiceError>
/// Performs a password login using the current homeserver.
func login(username: String, password: String, initialDeviceName: String?, deviceID: String?) async -> Result<UserSessionProtocol, AuthenticationServiceError>
}
@@ -66,8 +68,8 @@ enum OIDCError: Error {
case unknown
}
struct OIDCAuthenticationDataProxy: Equatable {
let underlyingData: OidcAuthenticationData
struct OIDCAuthorizationDataProxy: Equatable {
let underlyingData: OidcAuthorizationData
var url: URL {
guard let url = URL(string: underlyingData.loginUrl()) else {
@@ -77,8 +79,8 @@ struct OIDCAuthenticationDataProxy: Equatable {
}
}
extension OidcAuthenticationData: Equatable {
public static func == (lhs: MatrixRustSDK.OidcAuthenticationData, rhs: MatrixRustSDK.OidcAuthenticationData) -> Bool {
extension OidcAuthorizationData: Equatable {
public static func == (lhs: MatrixRustSDK.OidcAuthorizationData, rhs: MatrixRustSDK.OidcAuthorizationData) -> Bool {
lhs.loginUrl() == rhs.loginUrl()
}
}
@@ -18,7 +18,7 @@ import Combine
import Foundation
import MatrixRustSDK
class MockAuthenticationServiceProxy: AuthenticationServiceProxyProtocol {
class MockAuthenticationServiceProxy: AuthenticationServiceProtocol {
let validCredentials = (username: "alice", password: "12345678")
private let homeserverSubject: CurrentValueSubject<LoginHomeserver, Never>
@@ -48,11 +48,13 @@ class MockAuthenticationServiceProxy: AuthenticationServiceProxyProtocol {
}
}
func urlForOIDCLogin() async -> Result<OIDCAuthenticationDataProxy, AuthenticationServiceError> {
func urlForOIDCLogin() async -> Result<OIDCAuthorizationDataProxy, AuthenticationServiceError> {
.failure(.oidcError(.notSupported))
}
func loginWithOIDCCallback(_ callbackURL: URL, data: OIDCAuthenticationDataProxy) async -> Result<UserSessionProtocol, AuthenticationServiceError> {
func abortOIDCLogin(data: OIDCAuthorizationDataProxy) async { }
func loginWithOIDCCallback(_ callbackURL: URL, data: OIDCAuthorizationDataProxy) async -> Result<UserSessionProtocol, AuthenticationServiceError> {
.failure(.oidcError(.notSupported))
}
@@ -34,18 +34,6 @@ extension ClientError {
return first
}
}
extension AuthenticationError {
var code: MatrixErrorCode {
guard case let .Generic(message) = self else { return .unknown }
guard let first = MatrixErrorCode.allCases.first(where: { message.contains($0.rawValue) }) else {
return .unknown
}
return first
}
/// Whether or not the error is related to the sliding sync proxy being full.
///
@@ -20,21 +20,21 @@ import Foundation
import MatrixRustSDK
final class QRCodeLoginService: QRCodeLoginServiceProtocol {
private let oidcConfiguration: OidcConfiguration
private let sessionDirectory: URL
private let passphrase: String
private let userSessionStore: UserSessionStoreProtocol
private let appSettings: AppSettings
private let qrLoginProgressSubject = PassthroughSubject<QrLoginProgress, Never>()
var qrLoginProgressPublisher: AnyPublisher<QrLoginProgress, Never> {
qrLoginProgressSubject.eraseToAnyPublisher()
}
init(oidcConfiguration: OidcConfiguration,
encryptionKeyProvider: EncryptionKeyProviderProtocol,
userSessionStore: UserSessionStoreProtocol) {
self.oidcConfiguration = oidcConfiguration
init(encryptionKeyProvider: EncryptionKeyProviderProtocol,
userSessionStore: UserSessionStoreProtocol,
appSettings: AppSettings) {
self.userSessionStore = userSessionStore
self.appSettings = appSettings
sessionDirectory = .sessionsBaseDirectory.appending(component: UUID().uuidString)
passphrase = encryptionKeyProvider.generateKey().base64EncodedString()
}
@@ -53,14 +53,14 @@ final class QRCodeLoginService: QRCodeLoginServiceProtocol {
}
do {
let client = try await ClientBuilder()
let client = try await ClientBuilder
.baseBuilder(httpProxy: appSettings.websiteURL.globalProxy,
slidingSyncProxy: appSettings.slidingSyncProxyURL,
sessionDelegate: userSessionStore.clientSessionDelegate)
.sessionPath(path: sessionDirectory.path(percentEncoded: false))
.passphrase(passphrase: passphrase)
.userAgent(userAgent: UserAgentBuilder.makeASCIIUserAgent())
.enableCrossProcessRefreshLock(processId: InfoPlistReader.main.bundleIdentifier,
sessionDelegate: userSessionStore.clientSessionDelegate)
.serverVersions(versions: ["v1.0", "v1.1", "v1.2", "v1.3", "v1.4", "v1.5"]) // FIXME: Quick and dirty fix for stopping version requests on startup https://github.com/matrix-org/matrix-rust-sdk/pull/1376
.buildWithQrCode(qrCodeData: qrData, oidcConfiguration: oidcConfiguration, progressListener: listener)
.requiresSlidingSync()
.buildWithQrCode(qrCodeData: qrData, oidcConfiguration: appSettings.oidcConfiguration.rustValue, progressListener: listener)
return await login(client: client)
} catch let error as HumanQrLoginError {
MXLog.error("QRCode login error: \(error)")
@@ -118,24 +118,16 @@ class UserSessionStore: UserSessionStoreProtocol {
let homeserverURL = credentials.restorationToken.session.homeserverUrl
var builder = ClientBuilder()
let builder = ClientBuilder
.baseBuilder(httpProxy: URL(string: homeserverURL)?.globalProxy,
sessionDelegate: keychainController)
.sessionPath(path: credentials.restorationToken.sessionDirectory.path(percentEncoded: false))
.username(username: credentials.userID)
.homeserverUrl(url: homeserverURL)
.passphrase(passphrase: credentials.restorationToken.passphrase)
.userAgent(userAgent: UserAgentBuilder.makeASCIIUserAgent())
.enableCrossProcessRefreshLock(processId: InfoPlistReader.main.bundleIdentifier,
sessionDelegate: keychainController)
.serverVersions(versions: ["v1.0", "v1.1", "v1.2", "v1.3", "v1.4", "v1.5"]) // FIXME: Quick and dirty fix for stopping version requests on startup https://github.com/matrix-org/matrix-rust-sdk/pull/1376
if let homeserverURL = URL(string: homeserverURL),
let proxy = homeserverURL.globalProxy {
builder = builder.proxy(url: proxy)
}
let completeBuilder = builder
do {
let client = try await completeBuilder.build()
let client = try await builder.build()
try await client.restoreSession(session: credentials.restorationToken.session)