213 lines
7.9 KiB
Swift
213 lines
7.9 KiB
Swift
//
|
|
// Copyright 2022-2024 New Vector Ltd.
|
|
//
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
// Please see LICENSE in the repository root for full details.
|
|
//
|
|
|
|
import Foundation
|
|
|
|
@testable import Element
|
|
|
|
/// A mock REST client that can be used for authentication.
|
|
class MockAuthenticationRestClient: AuthenticationRestClient {
|
|
|
|
enum MockError: Error {
|
|
/// The fixture is missing.
|
|
case fixture
|
|
/// The method isn't implemented.
|
|
case unhandled
|
|
/// Login attempted with incorrect credentials.
|
|
case invalidCredentials
|
|
/// The homeserver doesn't allow for registration.
|
|
case registrationDisabled
|
|
/// A registration stage was attempted without first registering a username and password.
|
|
case createAccountNotCalled
|
|
/// The request is invalid.
|
|
case invalidRequest
|
|
}
|
|
|
|
/// An account to test password based login with.
|
|
static let registeredAccount = (username: "alice", email: "alice@example.com", phone: "+447777777777", password: "password")
|
|
/// A token to test token based login with.
|
|
static var pendingLoginToken = "000000"
|
|
|
|
// MARK: - Configuration
|
|
|
|
/// The client's internal configuration.
|
|
var config: Config
|
|
/// The homeserver's URL string.
|
|
var homeserver: String! { homeserverURL.absoluteString }
|
|
/// Unused: The identity server.
|
|
var identityServer: String!
|
|
/// Unused: The credentials.
|
|
var credentials: MXCredentials!
|
|
/// Unused: The type of content to accept for responses.
|
|
var acceptableContentTypes: Set<String>!
|
|
|
|
// MARK: - Private
|
|
|
|
/// The URL used to create the client with.
|
|
private var homeserverURL: URL
|
|
/// Unused: A callback for handling an unrecognized certificate.
|
|
private var unrecognizedCertificateHandler: MXHTTPClientOnUnrecognizedCertificate?
|
|
|
|
/// The credentials for a pending account creation.
|
|
private var newAccount: (username: String, password: String)?
|
|
/// The stages completed in the registration flow.
|
|
private var completedStages: Set<String> = []
|
|
|
|
// MARK: - Setup
|
|
|
|
/// Creates a new mock client.
|
|
/// - Parameters:
|
|
/// - homeServer: See `MockAuthenticationRestClient.Config` for various URLs that can be used.
|
|
/// - handler: Unused.
|
|
required init(homeServer: URL, unrecognizedCertificateHandler handler: MXHTTPClientOnUnrecognizedCertificate?) {
|
|
self.config = Config(url: homeServer)
|
|
|
|
self.homeserverURL = homeServer
|
|
self.unrecognizedCertificateHandler = handler
|
|
}
|
|
|
|
// MARK: - Login
|
|
|
|
var loginFallbackURL: URL {
|
|
homeserverURL.appendingPathComponent("_matrix/static/client/login")
|
|
}
|
|
|
|
func wellKnown() async throws -> MXWellKnown {
|
|
try MXWellKnown(fromJSON: config.wellKnownJSON())
|
|
}
|
|
|
|
func getLoginSession() async throws -> MXAuthenticationSession {
|
|
try MXAuthenticationSession(fromJSON: config.loginSessionJSON())
|
|
}
|
|
|
|
func login(parameters: LoginParameters) async throws -> MXCredentials {
|
|
if let passwordParameters = parameters as? LoginPasswordParameters {
|
|
return try login(passwordParameters: passwordParameters)
|
|
} else if let tokenParameters = parameters as? LoginTokenParameters {
|
|
return try login(tokenParameters: tokenParameters)
|
|
} else {
|
|
throw MockError.unhandled
|
|
}
|
|
}
|
|
|
|
/// Checks login against the `registeredAccount` and returns credentials if valid.
|
|
private func login(passwordParameters: LoginPasswordParameters) throws -> MXCredentials {
|
|
switch passwordParameters.id {
|
|
case .user(let username):
|
|
guard username == Self.registeredAccount.username else { throw MockError.invalidCredentials }
|
|
case .thirdParty(medium: let medium, address: let address):
|
|
guard medium == .email, address == Self.registeredAccount.email else { throw MockError.invalidCredentials }
|
|
case .phone(country: let country, phone: let phone):
|
|
guard "+\(country)\(phone)" == Self.registeredAccount.phone else { throw MockError.invalidCredentials }
|
|
}
|
|
|
|
guard passwordParameters.password == Self.registeredAccount.password else { throw MockError.invalidCredentials }
|
|
|
|
return makeCredentials()
|
|
}
|
|
|
|
/// Checks login against the `pendingLoginToken` and returns credentials if valid.
|
|
private func login(tokenParameters: LoginTokenParameters) throws -> MXCredentials {
|
|
guard tokenParameters.token == Self.pendingLoginToken else { throw MockError.invalidCredentials }
|
|
return makeCredentials()
|
|
}
|
|
|
|
/// Mock credentials for the registered account.
|
|
private func makeCredentials() -> MXCredentials {
|
|
MXCredentials(homeServer: homeserver,
|
|
userId: "@\(Self.registeredAccount.username):\(config.baseURL)",
|
|
accessToken: "1234")
|
|
}
|
|
|
|
func login(parameters: [String : Any]) async throws -> MXCredentials {
|
|
throw MockError.unhandled
|
|
}
|
|
|
|
func generateLoginToken() async throws -> MXLoginToken {
|
|
throw MockError.unhandled
|
|
}
|
|
|
|
// MARK: - Registration
|
|
|
|
var registerFallbackURL: URL {
|
|
homeserverURL.appendingPathComponent("_matrix/static/client/register")
|
|
}
|
|
|
|
func getRegisterSession() async throws -> MXAuthenticationSession {
|
|
try MXAuthenticationSession(fromJSON: config.registerSessionJSON())
|
|
}
|
|
|
|
func isUsernameAvailable(_ username: String) async throws -> Bool {
|
|
username != Self.registeredAccount.username
|
|
}
|
|
|
|
func register(parameters: RegistrationParameters) async throws -> MXLoginResponse {
|
|
guard let supportedStages = config.supportedStages else { throw MockError.registrationDisabled }
|
|
|
|
let success = attemptStage(with: parameters)
|
|
|
|
guard success, completedStages == supportedStages, let newAccount = newAccount else {
|
|
var errorResponse = try config.registerSessionJSON()
|
|
errorResponse["completed"] = Array(completedStages)
|
|
let nsError = NSError(domain: "mock",
|
|
code: 401,
|
|
userInfo: [MXHTTPClientErrorResponseDataKey: errorResponse])
|
|
throw nsError
|
|
}
|
|
|
|
let response = MXLoginResponse()
|
|
response.accessToken = "1234"
|
|
response.homeserver = homeserver
|
|
response.userId = "@\(newAccount.username):\(config.baseURL)"
|
|
|
|
return response
|
|
}
|
|
|
|
/// Returns a boolean indicating whether the stage was completed or not.
|
|
private func attemptStage(with parameters: RegistrationParameters) -> Bool {
|
|
if let username = parameters.username, let password = parameters.password {
|
|
newAccount = (username: username, password: password)
|
|
return true
|
|
}
|
|
|
|
guard newAccount != nil else { return false }
|
|
guard let auth = parameters.auth else { return false }
|
|
|
|
completedStages.insert(auth.type)
|
|
|
|
return true
|
|
}
|
|
|
|
func register(parameters: [String : Any]) async throws -> MXLoginResponse {
|
|
throw MockError.unhandled
|
|
}
|
|
|
|
func requestTokenDuringRegistration(for threePID: RegisterThreePID, clientSecret: String, sendAttempt: UInt) async throws -> RegistrationThreePIDTokenResponse {
|
|
throw MockError.unhandled
|
|
}
|
|
|
|
// MARK: - Forgot password
|
|
|
|
func forgetPassword(for email: String, clientSecret: String, sendAttempt: UInt) async throws -> String {
|
|
throw MockError.unhandled
|
|
}
|
|
|
|
func resetPassword(parameters: CheckResetPasswordParameters) async throws {
|
|
throw MockError.unhandled
|
|
}
|
|
|
|
func resetPassword(parameters: [String : Any]) async throws {
|
|
throw MockError.unhandled
|
|
}
|
|
|
|
// MARK: Versions
|
|
|
|
func supportedMatrixVersions() async throws -> MXMatrixVersions {
|
|
return MXMatrixVersions()
|
|
}
|
|
}
|