Remove AppAuth and prepare for Rust OIDC. (#870)

This commit is contained in:
Doug
2023-05-11 16:46:36 +01:00
committed by GitHub
parent a28e686c8d
commit 185710adf4
22 changed files with 249 additions and 264 deletions
@@ -64,8 +64,7 @@ extension LoginHomeserver {
/// A mock homeserver that supports only supports authentication via a single SSO provider.
static var mockOIDC: LoginHomeserver {
let issuerURL = URL(staticString: "https://auth.company.com")
return LoginHomeserver(address: "company.com", loginMode: .oidc(issuerURL))
LoginHomeserver(address: "company.com", loginMode: .oidc)
}
/// A mock homeserver that only with no supported login flows.
@@ -20,8 +20,8 @@ import Foundation
enum LoginMode: Equatable {
/// The login mode hasn't been determined yet.
case unknown
/// The homeserver supports login via OpenID Connect at the associated URL.
case oidc(URL)
/// The homeserver supports login via OpenID Connect.
case oidc
/// The homeserver supports login with a password.
case password
/// The homeserver only allows login with unsupported mechanisms. Use fallback instead.
@@ -14,7 +14,7 @@
// limitations under the License.
//
import AppAuth
import Combine
import SwiftUI
struct LoginScreenCoordinatorParameters {
@@ -32,16 +32,10 @@ enum LoginScreenCoordinatorAction {
final class LoginScreenCoordinator: CoordinatorProtocol {
private let parameters: LoginScreenCoordinatorParameters
private var viewModel: LoginScreenViewModelProtocol
private let hostingController: UIViewController
/// Passed to the OIDC service to provide a view controller from which to present the authentication session.
private let oidcUserAgent: OIDExternalUserAgentIOS?
private var currentTask: Task<Void, Error>? {
willSet {
currentTask?.cancel()
}
}
@CancellableTask private var currentTask: Task<Void, Error>?
private let oidcAuthenticationPresenter: OIDCAuthenticationPresenter
private var authenticationService: AuthenticationServiceProxyProtocol { parameters.authenticationService }
private var navigationStackCoordinator: NavigationStackCoordinator { parameters.navigationStackCoordinator }
@@ -54,8 +48,7 @@ final class LoginScreenCoordinator: CoordinatorProtocol {
viewModel = LoginScreenViewModel(homeserver: parameters.authenticationService.homeserver)
hostingController = UIHostingController(rootView: LoginScreen(context: viewModel.context))
oidcUserAgent = OIDExternalUserAgentIOS(presenting: hostingController)
oidcAuthenticationPresenter = OIDCAuthenticationPresenter(authenticationService: parameters.authenticationService)
}
// MARK: - Public
@@ -74,7 +67,7 @@ final class LoginScreenCoordinator: CoordinatorProtocol {
case .login(let username, let password):
self.login(username: username, password: password)
case .continueWithOIDC:
self.loginWithOIDC()
self.continueWithOIDC()
}
}
}
@@ -126,27 +119,34 @@ final class LoginScreenCoordinator: CoordinatorProtocol {
viewModel.displayError(.alert(L10n.screenLoginErrorDeactivatedAccount))
case .slidingSyncNotAvailable:
viewModel.displayError(.slidingSyncAlert)
case .oidcError(.notSupported):
// Temporary alert hijacking the use of .notSupported, can be removed when OIDC support is in the SDK.
viewModel.displayError(.alert(L10n.commonServerNotSupported))
case .oidcError(.userCancellation):
// No need to show an error, the user cancelled authentication.
break
default:
viewModel.displayError(.alert(L10n.errorUnknown))
}
}
private func loginWithOIDC() {
guard let oidcUserAgent else {
handleError(AuthenticationServiceError.oidcError(.notSupported))
return
}
private func continueWithOIDC() {
startLoading(isInteractionBlocking: true)
Task {
switch await authenticationService.loginWithOIDC(userAgent: oidcUserAgent) {
case .success(let userSession):
callback?(.signedIn(userSession))
stopLoading()
switch await authenticationService.urlForOIDCLogin() {
case .failure(let error):
stopLoading()
handleError(error)
case .success(let oidcData):
stopLoading()
switch await oidcAuthenticationPresenter.authenticate(using: oidcData) {
case .success(let userSession):
callback?(.signedIn(userSession))
case .failure(let error):
handleError(error)
}
}
}
}
@@ -0,0 +1,77 @@
//
// Copyright 2023 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 AuthenticationServices
/// Presents a web authentication session for an OIDC request.
class OIDCAuthenticationPresenter {
private let authenticationService: AuthenticationServiceProxyProtocol
private let oidcPresentationContent = OIDCPresentationContext()
init(authenticationService: AuthenticationServiceProxyProtocol) {
self.authenticationService = authenticationService
}
/// Presents a web authentication session for the supplied data.
func authenticate(using oidcData: OIDCAuthenticationDataProxy) async -> Result<UserSessionProtocol, AuthenticationServiceError> {
await withCheckedContinuation { continuation in
let session = ASWebAuthenticationSession(url: oidcData.url,
callbackURLScheme: ServiceLocator.shared.settings.oidcRedirectURL.scheme) { [weak self] url, error in
guard let self else { return }
guard let url else {
if let nsError = error as? NSError,
nsError.domain == ASWebAuthenticationSessionErrorDomain,
nsError.code == ASWebAuthenticationSessionError.canceledLogin.rawValue {
continuation.resume(returning: .failure(AuthenticationServiceError.oidcError(.userCancellation)))
return
}
continuation.resume(returning: .failure(AuthenticationServiceError.oidcError(.unknown)))
return
}
completeAuthentication(callbackURL: url, data: oidcData, continuation: continuation)
}
session.prefersEphemeralWebBrowserSession = false
session.presentationContextProvider = oidcPresentationContent
session.start()
}
}
private func completeAuthentication(callbackURL: URL,
data: OIDCAuthenticationDataProxy,
continuation: CheckedContinuation<Result<UserSessionProtocol, AuthenticationServiceError>, Never>) {
Task {
switch await authenticationService.loginWithOIDCCallback(callbackURL, data: data) {
case .success(let userSession):
continuation.resume(returning: .success(userSession))
case .failure(let error):
continuation.resume(returning: .failure(error))
}
}
}
}
class OIDCPresentationContext: NSObject, ASWebAuthenticationPresentationContextProviding {
func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor {
guard let window = UIApplication.shared.connectedScenes.compactMap({ $0 as? UIWindowScene }).first?.keyWindow else {
fatalError("Failed to find the main window.")
}
return window
}
}
@@ -14,7 +14,6 @@
// limitations under the License.
//
import AppAuth
import SwiftUI
struct SoftLogoutScreenCoordinatorParameters {
@@ -43,11 +42,8 @@ enum SoftLogoutScreenCoordinatorResult: CustomStringConvertible {
final class SoftLogoutScreenCoordinator: CoordinatorProtocol {
private let parameters: SoftLogoutScreenCoordinatorParameters
private var viewModel: SoftLogoutScreenViewModelProtocol
private let hostingController: UIViewController
/// Passed to the OIDC service to provide a view controller from which to present the authentication session.
private let oidcUserAgent: OIDExternalUserAgentIOS?
/// The wizard used to handle the registration flow.
private let oidcAuthenticationPresenter: OIDCAuthenticationPresenter
private var authenticationService: AuthenticationServiceProxyProtocol { parameters.authenticationService }
var callback: (@MainActor (SoftLogoutScreenCoordinatorResult) -> Void)?
@@ -56,13 +52,11 @@ final class SoftLogoutScreenCoordinator: CoordinatorProtocol {
self.parameters = parameters
let homeserver = parameters.authenticationService.homeserver
viewModel = SoftLogoutScreenViewModel(credentials: parameters.credentials,
homeserver: homeserver,
keyBackupNeeded: parameters.keyBackupNeeded)
hostingController = UIHostingController(rootView: SoftLogoutScreen(context: viewModel.context))
oidcUserAgent = OIDExternalUserAgentIOS(presenting: hostingController)
oidcAuthenticationPresenter = OIDCAuthenticationPresenter(authenticationService: parameters.authenticationService)
}
// MARK: - Public
@@ -80,7 +74,7 @@ final class SoftLogoutScreenCoordinator: CoordinatorProtocol {
case .clearAllData:
self.callback?(.clearAllData)
case .continueWithOIDC:
self.loginWithOIDC()
self.continueWithOIDC()
}
}
}
@@ -136,22 +130,23 @@ final class SoftLogoutScreenCoordinator: CoordinatorProtocol {
}
}
private func loginWithOIDC() {
guard let oidcUserAgent else {
handleError(AuthenticationServiceError.oidcError(.notSupported))
return
}
private func continueWithOIDC() {
startLoading()
Task {
switch await authenticationService.loginWithOIDC(userAgent: oidcUserAgent) {
case .success(let userSession):
callback?(.signedIn(userSession))
stopLoading()
switch await authenticationService.urlForOIDCLogin() {
case .failure(let error):
stopLoading()
handleError(error)
case .success(let oidcData):
stopLoading()
switch await oidcAuthenticationPresenter.authenticate(using: oidcData) {
case .success(let userSession):
callback?(.signedIn(userSession))
case .failure(let error):
handleError(error)
}
}
}
}
@@ -163,6 +158,12 @@ final class SoftLogoutScreenCoordinator: CoordinatorProtocol {
viewModel.displayError(.alert(L10n.screenLoginErrorInvalidCredentials))
case .accountDeactivated:
viewModel.displayError(.alert(L10n.screenLoginErrorDeactivatedAccount))
case .oidcError(.notSupported):
// Temporary alert hijacking the use of .notSupported, can be removed when OIDC support is in the SDK.
viewModel.displayError(.alert(L10n.commonServerNotSupported))
case .oidcError(.userCancellation):
// No need to show an error, the user cancelled authentication.
break
default:
viewModel.displayError(.alert(L10n.errorUnknown))
}