mirror of
https://gitlab.opencode.de/bwi/bundesmessenger/clients/bundesmessenger-ios.git
synced 2026-04-17 23:18:27 +02:00
Add initial tests on the authentication service. (#6229)
This commit is contained in:
@@ -18,22 +18,33 @@ import XCTest
|
||||
|
||||
@testable import Riot
|
||||
|
||||
class AuthenticationServiceTests: XCTestCase {
|
||||
func testRegistrationWizardWhenStartingLoginFlow() async throws {
|
||||
@MainActor class AuthenticationServiceTests: XCTestCase {
|
||||
var service: AuthenticationService!
|
||||
|
||||
/// Makes a new service configured for testing.
|
||||
@MainActor override func setUp() {
|
||||
service = AuthenticationService(sessionCreator: MockSessionCreator())
|
||||
service.clientType = MockAuthenticationRestClient.self
|
||||
}
|
||||
|
||||
// MARK: - Service State
|
||||
|
||||
func testWizardsWhenStartingLoginFlow() async throws {
|
||||
// Given a fresh service.
|
||||
let service = AuthenticationService()
|
||||
XCTAssertNil(service.loginWizard, "A new service shouldn't have a login wizard.")
|
||||
XCTAssertNil(service.registrationWizard, "A new service shouldn't have a registration wizard.")
|
||||
|
||||
// When starting a new login flow.
|
||||
try await service.startFlow(.login, for: "https://matrix.org")
|
||||
|
||||
// Then a registration wizard shouldn't have been created.
|
||||
XCTAssertNotNil(service.loginWizard, "The login wizard should exist after starting a login flow.")
|
||||
XCTAssertNil(service.registrationWizard, "The registration wizard should not exist if startFlow was called for login.")
|
||||
}
|
||||
|
||||
func testRegistrationWizard() async throws {
|
||||
func testWizardsWhenStartingRegistrationFlow() async throws {
|
||||
// Given a fresh service.
|
||||
let service = AuthenticationService()
|
||||
XCTAssertNil(service.loginWizard, "A new service shouldn't have a login wizard.")
|
||||
XCTAssertNil(service.registrationWizard, "A new service shouldn't provide a registration wizard.")
|
||||
XCTAssertNil(service.state.homeserver.registrationFlow, "A new service shouldn't provide a registration flow for the homeserver.")
|
||||
|
||||
@@ -41,13 +52,13 @@ class AuthenticationServiceTests: XCTestCase {
|
||||
try await service.startFlow(.register, for: "https://matrix.org")
|
||||
|
||||
// Then a registration wizard should be available for use.
|
||||
XCTAssertNotNil(service.loginWizard, "The login wizard should exist after starting a registration flow.")
|
||||
XCTAssertNotNil(service.registrationWizard, "The registration wizard should exist after starting a registration flow.")
|
||||
XCTAssertNotNil(service.state.homeserver.registrationFlow, "The supported registration flow should be stored after starting a registration flow.")
|
||||
}
|
||||
|
||||
func testReset() async throws {
|
||||
// Given a service that has begun registration.
|
||||
let service = AuthenticationService()
|
||||
try await service.startFlow(.register, for: "https://matrix.org")
|
||||
_ = try await service.registrationWizard?.createAccount(username: UUID().uuidString, password: UUID().uuidString, initialDeviceDisplayName: "Test")
|
||||
XCTAssertNotNil(service.loginWizard, "The login wizard should exist after starting a registration flow.")
|
||||
@@ -55,6 +66,8 @@ class AuthenticationServiceTests: XCTestCase {
|
||||
XCTAssertNotNil(service.state.homeserver.registrationFlow, "The supported registration flow should be stored after starting a registration flow.")
|
||||
XCTAssertTrue(service.isRegistrationStarted, "The service should show as having started registration.")
|
||||
XCTAssertEqual(service.state.flow, .register, "The service should show as using a registration flow.")
|
||||
XCTAssertEqual(service.state.homeserver.address, "https://matrix-client.matrix.org", "The actual homeserver address should be discovered.")
|
||||
XCTAssertEqual(service.state.homeserver.addressFromUser, "https://matrix.org", "The address from the startFlow call should be stored.")
|
||||
|
||||
// When resetting the service.
|
||||
service.reset()
|
||||
@@ -65,14 +78,14 @@ class AuthenticationServiceTests: XCTestCase {
|
||||
XCTAssertNil(service.state.homeserver.registrationFlow, "The supported registration flow should be cleared when calling reset.")
|
||||
XCTAssertFalse(service.isRegistrationStarted, "The service should not indicate it has started registration after calling reset.")
|
||||
XCTAssertEqual(service.state.flow, .login, "The flow should have been set back to login when calling reset.")
|
||||
XCTAssertEqual(service.state.homeserver.address, "https://matrix.org", "The address should reset to the value entered by the user.")
|
||||
}
|
||||
|
||||
func testHomeserverState() async throws {
|
||||
// Given a service that has begun login for one homeserver.
|
||||
let service = AuthenticationService()
|
||||
try await service.startFlow(.login, for: "https://glasgow.social")
|
||||
XCTAssertEqual(service.state.homeserver.addressFromUser, "https://glasgow.social", "The initial address entered by the user should be stored.")
|
||||
XCTAssertEqual(service.state.homeserver.address, "https://matrix.glasgow.social", "The initial address discovered from the well-known should be stored.")
|
||||
try await service.startFlow(.login, for: "https://example.com")
|
||||
XCTAssertEqual(service.state.homeserver.addressFromUser, "https://example.com", "The initial address entered by the user should be stored.")
|
||||
XCTAssertEqual(service.state.homeserver.address, "https://matrix.example.com", "The initial address discovered from the well-known should be stored.")
|
||||
|
||||
// When switching to a different homeserver
|
||||
try await service.startFlow(.login, for: "https://matrix.org")
|
||||
@@ -82,6 +95,142 @@ class AuthenticationServiceTests: XCTestCase {
|
||||
XCTAssertEqual(service.state.homeserver.address, "https://matrix-client.matrix.org", "The new address discovered from the well-known should be stored.")
|
||||
}
|
||||
|
||||
func testStartingLoginWithInvalidURL() async throws {
|
||||
// Given a service that has started the register flow for one homeserver.
|
||||
try await service.startFlow(.login, for: "https://example.com")
|
||||
XCTAssertEqual(service.client.homeserver, "https://matrix.example.com", "The client should be set up for the homeserver")
|
||||
XCTAssertEqual(service.state.flow, .login, "The flow should be set as login.")
|
||||
XCTAssertEqual(service.state.homeserver.addressFromUser, "https://example.com", "The initial address entered by the user should be stored.")
|
||||
XCTAssertEqual(service.state.homeserver.address, "https://matrix.example.com", "The initial address discovered from the well-known should be stored.")
|
||||
|
||||
// When failing to start login by entering an invalid address.
|
||||
do {
|
||||
try await service.startFlow(.login, for: "https://google.com")
|
||||
XCTFail("The registration flow should fail for an incorrect homeserver address.")
|
||||
} catch {
|
||||
XCTAssertNotNil(error, "The client should throw an error for an incorrect address.")
|
||||
}
|
||||
|
||||
// Then the service's state and client should be unchanged.
|
||||
XCTAssertEqual(service.client.homeserver, "https://matrix.example.com", "The client should be set up for the homeserver")
|
||||
XCTAssertEqual(service.state.flow, .login, "The flow should still be set as login.")
|
||||
XCTAssertEqual(service.state.homeserver.addressFromUser, "https://example.com", "The initial address entered by the user should be stored.")
|
||||
XCTAssertEqual(service.state.homeserver.address, "https://matrix.example.com", "The initial address discovered from the well-known should be stored.")
|
||||
}
|
||||
|
||||
func testStartingRegistrationForLoginOnlyServer() async throws {
|
||||
// Given a service that has started the register flow for one homeserver.
|
||||
try await service.startFlow(.register, for: "https://example.com")
|
||||
XCTAssertEqual(service.client.homeserver, "https://matrix.example.com", "The client should be set up for the homeserver")
|
||||
XCTAssertEqual(service.state.flow, .register, "The flow should be set as registration.")
|
||||
XCTAssertEqual(service.state.homeserver.addressFromUser, "https://example.com", "The initial address entered by the user should be stored.")
|
||||
XCTAssertEqual(service.state.homeserver.address, "https://matrix.example.com", "The initial address discovered from the well-known should be stored.")
|
||||
|
||||
// When failing to start registration for another homeserver that only supports login.
|
||||
do {
|
||||
try await service.startFlow(.register, for: "https://private.com")
|
||||
XCTFail("The registration flow should fail for a server that doesn't support registration")
|
||||
} catch {
|
||||
XCTAssertEqual(error as? MockAuthenticationRestClient.MockError, MockAuthenticationRestClient.MockError.registrationDisabled,
|
||||
"The client should throw with disabled registration.")
|
||||
}
|
||||
|
||||
// The the service's state and client should be unchanged.
|
||||
XCTAssertEqual(service.client.homeserver, "https://matrix.example.com", "The client should still be set up for the homeserver")
|
||||
XCTAssertEqual(service.state.flow, .register, "The flow should still be set as registration.")
|
||||
XCTAssertEqual(service.state.homeserver.addressFromUser, "https://example.com", "The initial address entered by the user should still be stored.")
|
||||
XCTAssertEqual(service.state.homeserver.address, "https://matrix.example.com", "The initial address discovered from the well-known should still be stored.")
|
||||
}
|
||||
|
||||
func testPasswordLogin() async throws {
|
||||
// Given a server ready for login.
|
||||
try await service.startFlow(.login, for: "https://matrix.org")
|
||||
guard let loginWizard = service.loginWizard else {
|
||||
XCTFail("The login wizard should exist after starting a login flow.")
|
||||
return
|
||||
}
|
||||
|
||||
// When logging in with valid credentials.
|
||||
let account = MockAuthenticationRestClient.registeredAccount
|
||||
let session = try await loginWizard.login(login: account.username,
|
||||
password: account.password,
|
||||
initialDeviceName: UIDevice.current.initialDisplayName)
|
||||
|
||||
// Then the MXSession should be created for the user ID.
|
||||
XCTAssertEqual(session.myUserId, "@alice:matrix.org")
|
||||
}
|
||||
|
||||
func testBasicRegistration() async throws {
|
||||
// Given a basic server ready for registration (only has a dummy stage).
|
||||
try await service.startFlow(.register, for: "https://example.com")
|
||||
guard let registrationWizard = service.registrationWizard else {
|
||||
XCTFail("The registration wizard should exist after starting a registration flow.")
|
||||
return
|
||||
}
|
||||
|
||||
// When registering with a username and password.
|
||||
let result = try await registrationWizard.createAccount(username: "bob",
|
||||
password: "password",
|
||||
initialDeviceDisplayName: "whatever")
|
||||
|
||||
// Then an MXSession should be created for the new account.
|
||||
guard case let .success(session) = result else {
|
||||
XCTFail("The dummy stage should be performed and registration should be successful.")
|
||||
return
|
||||
}
|
||||
XCTAssertEqual(session.myUserId, "@bob:example.com")
|
||||
}
|
||||
|
||||
func testInteractiveRegistration() async throws {
|
||||
// Given a server ready for registration with multiple mandatory stages.
|
||||
try await service.startFlow(.register, for: "https://matrix.org")
|
||||
guard let registrationWizard = service.registrationWizard else {
|
||||
XCTFail("The registration wizard should exist after starting a registration flow.")
|
||||
return
|
||||
}
|
||||
XCTAssertFalse(registrationWizard.state.isRegistrationStarted, "Registration should not be started yet.")
|
||||
|
||||
// When registering with a username and password.
|
||||
let createAccountResult = try await registrationWizard.createAccount(username: "bob",
|
||||
password: "password",
|
||||
initialDeviceDisplayName: "whatever")
|
||||
|
||||
// Then the registration should be started and be waiting for all of the stages to be completed.
|
||||
guard case let .flowResponse(flowResult) = createAccountResult else {
|
||||
XCTFail("The registration should not have completed.")
|
||||
return
|
||||
}
|
||||
XCTAssertEqual(flowResult.completedStages.count, 0)
|
||||
XCTAssertEqual(flowResult.missingStages.count, 3)
|
||||
XCTAssertTrue(registrationWizard.state.isRegistrationStarted, "Registration should be started after calling create account.")
|
||||
|
||||
// TODO: Email step
|
||||
|
||||
// When performing the terms stage.
|
||||
let termsResult = try await registrationWizard.acceptTerms()
|
||||
|
||||
// Then the completed and missing stages should be updated accordingly.
|
||||
guard case let .flowResponse(termsFlowResult) = termsResult else {
|
||||
XCTFail("The registration should not have completed.")
|
||||
return
|
||||
}
|
||||
XCTAssertEqual(termsFlowResult.completedStages.count, 1)
|
||||
XCTAssertEqual(termsFlowResult.missingStages.count, 2)
|
||||
|
||||
// When performing the ReCaptcha stage.
|
||||
let reCaptchaResult = try await registrationWizard.performReCaptcha(response: "trafficlights")
|
||||
|
||||
// Then the completed and missing stages should be updated accordingly.
|
||||
guard case let .flowResponse(reCaptchaFlowResult) = reCaptchaResult else {
|
||||
XCTFail("The registration should not have completed.")
|
||||
return
|
||||
}
|
||||
XCTAssertEqual(reCaptchaFlowResult.completedStages.count, 2)
|
||||
XCTAssertEqual(reCaptchaFlowResult.missingStages.count, 1)
|
||||
}
|
||||
|
||||
// MARK: - Homeserver View Data
|
||||
|
||||
func testHomeserverViewDataForMatrixDotOrg() {
|
||||
// Given a homeserver such as matrix.org.
|
||||
let address = "https://matrix-client.matrix.org"
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"flows": [
|
||||
{
|
||||
"type": "m.login.password"
|
||||
},
|
||||
{
|
||||
"type": "m.login.application_service"
|
||||
},
|
||||
{
|
||||
"type": "uk.half-shot.msc2778.login.application_service"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"session": "123456",
|
||||
"flows": [
|
||||
{
|
||||
"stages": [
|
||||
"m.login.dummy"
|
||||
]
|
||||
}
|
||||
],
|
||||
"params": {}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"m.homeserver": {
|
||||
"base_url": "https://matrix.example.com"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"flows": [
|
||||
{
|
||||
"type": "m.login.password"
|
||||
},
|
||||
{
|
||||
"type": "m.login.application_service"
|
||||
},
|
||||
{
|
||||
"type": "uk.half-shot.msc2778.login.application_service"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
{
|
||||
"flows": [
|
||||
{
|
||||
"type": "m.login.sso",
|
||||
"identity_providers": [
|
||||
{
|
||||
"id": "oidc-github",
|
||||
"name": "GitHub",
|
||||
"icon": "mxc://matrix.org/sVesTtrFDTpXRbYfpahuJsKP",
|
||||
"brand": "github"
|
||||
},
|
||||
{
|
||||
"id": "oidc-google",
|
||||
"name": "Google",
|
||||
"icon": "mxc://matrix.org/ZlnaaZNPxtUuQemvgQzlOlkz",
|
||||
"brand": "google"
|
||||
},
|
||||
{
|
||||
"id": "oidc-gitlab",
|
||||
"name": "GitLab",
|
||||
"icon": "mxc://matrix.org/MCVOEmFgVieKFshPxmnejWOq",
|
||||
"brand": "gitlab"
|
||||
},
|
||||
{
|
||||
"id": "oidc-facebook",
|
||||
"name": "Facebook",
|
||||
"icon": "mxc://matrix.org/nsyeLIgzxazZmJadflMAsAWG",
|
||||
"brand": "facebook"
|
||||
},
|
||||
{
|
||||
"id": "oidc-apple",
|
||||
"name": "Apple",
|
||||
"icon": "mxc://matrix.org/QQKNSOdLiMHtJhzeAObmkFiU",
|
||||
"brand": "apple"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "m.login.token"
|
||||
},
|
||||
{
|
||||
"type": "m.login.password"
|
||||
},
|
||||
{
|
||||
"type": "m.login.application_service"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"session": "123456",
|
||||
"flows": [
|
||||
{
|
||||
"stages": [
|
||||
"m.login.recaptcha",
|
||||
"m.login.terms",
|
||||
"m.login.email.identity"
|
||||
]
|
||||
}
|
||||
],
|
||||
"params": {
|
||||
"m.login.recaptcha": {
|
||||
"public_key": "1234"
|
||||
},
|
||||
"m.login.terms": {
|
||||
"policies": {
|
||||
"privacy_policy": {
|
||||
"version": "1.0",
|
||||
"en": {
|
||||
"name": "Terms and Conditions",
|
||||
"url": "https://matrix-client.matrix.org/_matrix/consent?v=1.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"m.homeserver": {
|
||||
"base_url": "https://matrix-client.matrix.org"
|
||||
},
|
||||
"m.identity_server": {
|
||||
"base_url": "https://vector.im"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"flows": [
|
||||
{
|
||||
"type": "m.login.sso",
|
||||
"identity_providers": [
|
||||
{
|
||||
"id": "saml",
|
||||
"name": "SAML"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "m.login.token"
|
||||
},
|
||||
{
|
||||
"type": "m.login.application_service"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"m.homeserver": {
|
||||
"base_url": "https://matrix.company.com"
|
||||
},
|
||||
"m.identity_server": {
|
||||
"base_url": "https://identity.company.com"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,211 @@
|
||||
//
|
||||
// Copyright 2022 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
@testable import Riot
|
||||
|
||||
/// 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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
//
|
||||
// Copyright 2022 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/// Represents a homeserver configuration used for the mock authentication client.
|
||||
extension MockAuthenticationRestClient {
|
||||
enum Config: String {
|
||||
/// A homeserver that mimics matrix.org with both passwords and SSO.
|
||||
/// Create the client using https://matrix.org for this configuration.
|
||||
case matrix
|
||||
|
||||
/// A homeserver that supports login and registration using a password.
|
||||
/// Create the client using https://example.com for this configuration.
|
||||
case basic
|
||||
|
||||
/// A homeserver that only supports login using a password and has registration disabled.
|
||||
/// This configuration doesn't returns a well-known response.
|
||||
/// Create the client using https://private.com for this configuration.
|
||||
case loginOnly
|
||||
|
||||
/// A homeserver the only supports login via SSO and has registration disabled.
|
||||
/// This configuration has a custom identity server configured.
|
||||
/// Create the client using https://company.com for this configuration.
|
||||
case ssoOnly
|
||||
|
||||
/// The client if configured to use an unknown address.
|
||||
/// Create the client using any other address for this configuration.
|
||||
case unknown
|
||||
|
||||
init(url: URL) {
|
||||
switch url.absoluteString {
|
||||
case "https://matrix.org", "https://matrix-client.matrix.org":
|
||||
self = .matrix
|
||||
case "https://example.com", "https://matrix.example.com":
|
||||
self = .basic
|
||||
case "https://private.com":
|
||||
self = .loginOnly
|
||||
case "https://company.com", "https://matrix.company.com":
|
||||
self = .ssoOnly
|
||||
default:
|
||||
self = .unknown
|
||||
}
|
||||
}
|
||||
|
||||
/// The baseURL for the homeserver.
|
||||
var baseURL: String {
|
||||
switch self {
|
||||
case .matrix:
|
||||
return "matrix.org"
|
||||
case .basic:
|
||||
return "example.com"
|
||||
case .loginOnly:
|
||||
return "private.com"
|
||||
case .ssoOnly:
|
||||
return "company.com"
|
||||
case .unknown:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
/// The supported stages when performing interactive registration.
|
||||
var supportedStages: Set<String>? {
|
||||
switch self {
|
||||
case .matrix:
|
||||
return [kMXLoginFlowTypeRecaptcha, kMXLoginFlowTypeTerms, kMXLoginFlowTypeEmailIdentity]
|
||||
case .basic:
|
||||
return [kMXLoginFlowTypeDummy]
|
||||
case .loginOnly, .ssoOnly, .unknown:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the well-known JSON for this configuration
|
||||
func wellKnownJSON() throws -> [AnyHashable: Any] {
|
||||
try fixtureJSON(named: "wellknown")
|
||||
}
|
||||
|
||||
/// Returns the login session JSON for this configuration
|
||||
func loginSessionJSON() throws -> [AnyHashable: Any] {
|
||||
try fixtureJSON(named: "loginsession")
|
||||
}
|
||||
|
||||
/// Returns the register session JSON for this configuration
|
||||
func registerSessionJSON() throws -> [AnyHashable: Any] {
|
||||
switch self {
|
||||
case .matrix, .basic:
|
||||
return try fixtureJSON(named: "registersession")
|
||||
case .loginOnly, .ssoOnly:
|
||||
throw MockError.registrationDisabled
|
||||
case .unknown:
|
||||
throw MockError.unhandled
|
||||
}
|
||||
}
|
||||
|
||||
/// Loads a JSON fixture for this configuration.
|
||||
/// - Parameter fileName: The file name of the fixture without the configuration prefix.
|
||||
private func fixtureJSON(named fileName: String) throws -> [AnyHashable: Any] {
|
||||
let fileName = "\(rawValue)-\(fileName)"
|
||||
let data = try fixtureData(named: fileName)
|
||||
guard let jsonDictionary = try JSONSerialization.jsonObject(with: data) as? [AnyHashable: Any] else { throw MockError.fixture }
|
||||
return jsonDictionary
|
||||
}
|
||||
|
||||
/// Loads the raw data for a fixture from disk.
|
||||
/// - Parameter fileName: The file name of the fixture as stored in the bundle.
|
||||
private func fixtureData(named fileName: String) throws -> Data {
|
||||
let bundle = Bundle(for: MockAuthenticationRestClient.self)
|
||||
|
||||
guard let url = bundle.url(forResource: fileName, withExtension: "json") else { throw MockError.fixture }
|
||||
return try Data(contentsOf: url)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
//
|
||||
// Copyright 2022 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
@testable import Riot
|
||||
|
||||
struct MockSessionCreator: SessionCreatorProtocol {
|
||||
/// Returns a basic session created from the supplied credentials. This prevents the app from setting up the account during tests.
|
||||
func createSession(credentials: MXCredentials, client: AuthenticationRestClient) -> MXSession {
|
||||
let client = MXRestClient(credentials: credentials,
|
||||
unauthenticatedHandler: { _,_,_,_ in }) // The handler is expected if credentials are set.
|
||||
return MXSession(matrixRestClient: client)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user