Allow the app to be configured to bypass the server selection screen. (#4131)

* Make account provider configuration more flexible.

- Change defaultHomeserverAddress to an array of providers (needs UI).
- Add allowOtherAccountProviders to prevent the user from manually entering a provider.

* Refactor QR code scan failures into a common type.

* Validate scanned QR codes against the allowed account providers.

* Hide the login flow on the QR code screen when restricted.
This commit is contained in:
Doug
2025-05-20 11:09:50 +01:00
committed by GitHub
parent 57007f9eea
commit 33fcb8e667
26 changed files with 326 additions and 111 deletions

View File

@@ -18,6 +18,16 @@ class AuthenticationStartScreenViewModelTests: XCTestCase {
var viewModel: AuthenticationStartScreenViewModel!
var context: AuthenticationStartScreenViewModel.Context { viewModel.context }
override func setUp() {
AppSettings.resetAllSettings()
let appSettings = AppSettings()
ServiceLocator.shared.register(appSettings: appSettings)
}
override func tearDown() {
AppSettings.resetAllSettings()
}
func testInitialState() async throws {
// Given a view model that has no provisioning parameters.
setupViewModel()
@@ -80,6 +90,44 @@ class AuthenticationStartScreenViewModelTests: XCTestCase {
XCTAssertEqual(authenticationService.homeserver.value.loginMode, .password)
}
func testSingleProviderOIDCState() async throws {
// Given a view model that for an app that only allows the use of a single provider that supports OIDC.
setAllowedAccountProviders(["company.com"])
setupViewModel()
XCTAssertEqual(authenticationService.homeserver.value.loginMode, .unknown)
XCTAssertEqual(client.urlForOidcOidcConfigurationPromptLoginHintCallsCount, 0)
// When tapping the login button the authentication service should be used and the screen
// should request to continue the flow without any server selection needed.
let deferred = deferFulfillment(viewModel.actions) { $0.isLoginDirectlyWithOIDC }
context.send(viewAction: .login)
try await deferred.fulfill()
XCTAssertEqual(clientBuilderFactory.makeBuilderSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksCallsCount, 1)
XCTAssertEqual(client.urlForOidcOidcConfigurationPromptLoginHintCallsCount, 1)
XCTAssertEqual(client.urlForOidcOidcConfigurationPromptLoginHintReceivedArguments?.prompt, .consent)
XCTAssertEqual(client.urlForOidcOidcConfigurationPromptLoginHintReceivedArguments?.loginHint, nil)
XCTAssertEqual(authenticationService.homeserver.value.loginMode, .oidc(supportsCreatePrompt: false))
}
func testSingleProviderPasswordState() async throws {
// Given a view model that for an app that only allows the use of a single provider that does not support OIDC.
setAllowedAccountProviders(["company.com"])
setupViewModel(supportsOIDC: false)
XCTAssertEqual(authenticationService.homeserver.value.loginMode, .unknown)
XCTAssertEqual(client.urlForOidcOidcConfigurationPromptLoginHintCallsCount, 0)
// When tapping the login button the authentication service should be used and the screen
// should request to continue the flow without any server selection needed.
let deferred = deferFulfillment(viewModel.actions) { $0.isLoginDirectlyWithPassword }
context.send(viewAction: .login)
try await deferred.fulfill()
// Then a call to configure service should be made.
XCTAssertEqual(clientBuilderFactory.makeBuilderSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksCallsCount, 1)
XCTAssertEqual(authenticationService.homeserver.value.loginMode, .password)
}
// MARK: - Helpers
private func setupViewModel(provisioningParameters: AccountProvisioningParameters? = nil, supportsOIDC: Bool = true) {
@@ -106,6 +154,28 @@ class AuthenticationStartScreenViewModelTests: XCTestCase {
// Add a fake window in order for the OIDC flow to continue
viewModel.context.send(viewAction: .updateWindow(UIWindow()))
}
private func setAllowedAccountProviders(_ providers: [String]) {
let appSettings: AppSettings! = ServiceLocator.shared.settings
appSettings.override(accountProviders: providers,
allowOtherAccountProviders: false,
pushGatewayBaseURL: appSettings.pushGatewayBaseURL,
oidcRedirectURL: appSettings.oidcRedirectURL,
websiteURL: appSettings.websiteURL,
logoURL: appSettings.logoURL,
copyrightURL: appSettings.copyrightURL,
acceptableUseURL: appSettings.acceptableUseURL,
privacyURL: appSettings.privacyURL,
encryptionURL: appSettings.encryptionURL,
deviceVerificationURL: appSettings.deviceVerificationURL,
chatBackupDetailsURL: appSettings.chatBackupDetailsURL,
identityPinningViolationDetailsURL: appSettings.identityPinningViolationDetailsURL,
elementWebHosts: appSettings.elementWebHosts,
accountProvisioningHost: appSettings.accountProvisioningHost,
bugReportApplicationID: appSettings.bugReportApplicationID,
analyticsTermsURL: appSettings.analyticsTermsURL,
mapTilerConfiguration: appSettings.mapTilerConfiguration)
}
}
extension AuthenticationStartScreenViewModelAction {