Use an https callback for OIDC once again. (#3461)

* Use the new WAS callback type and return back to the https callback for OIDC.

* Simplify OIDCAuthenticationPresenter now it doesn't need to handle universal links.

* Remove old unit tests.
This commit is contained in:
Doug
2024-10-29 11:28:05 +00:00
committed by GitHub
parent a583892e01
commit 589df7d76e
10 changed files with 34 additions and 143 deletions
@@ -14,16 +14,6 @@ class OIDCAuthenticationPresenter: NSObject {
private let oidcRedirectURL: URL
private let presentationAnchor: UIWindow
/// The data required to complete a request.
struct Request {
let session: ASWebAuthenticationSession
let oidcData: OIDCAuthorizationDataProxy
let continuation: CheckedContinuation<Result<UserSessionProtocol, AuthenticationServiceError>, Never>
}
/// The current request in progress. This is a single use value and will be moved on access.
@Consumable private var request: Request?
init(authenticationService: AuthenticationServiceProtocol, oidcRedirectURL: URL, presentationAnchor: UIWindow) {
self.authenticationService = authenticationService
self.oidcRedirectURL = oidcRedirectURL
@@ -33,74 +23,35 @@ class OIDCAuthenticationPresenter: NSObject {
/// Presents a web authentication session for the supplied data.
func authenticate(using oidcData: OIDCAuthorizationDataProxy) async -> Result<UserSessionProtocol, AuthenticationServiceError> {
await withCheckedContinuation { continuation in
let session = ASWebAuthenticationSession(url: oidcData.url,
callbackURLScheme: oidcRedirectURL.scheme) { [weak self] url, error in
// This closure won't be called if the scheme is https, see handleUniversalLinkCallback for more info.
guard let self else { return }
guard let url else {
// Check for user cancellation to avoid showing an alert in that instance.
if let nsError = error as? NSError,
nsError.domain == ASWebAuthenticationSessionErrorDomain,
nsError.code == ASWebAuthenticationSessionError.canceledLogin.rawValue {
self.completeAuthentication(throwing: .oidcError(.userCancellation))
return
}
self.completeAuthentication(throwing: .oidcError(.unknown))
return
}
completeAuthentication(callbackURL: url)
let (url, error) = await withCheckedContinuation { continuation in
let session = ASWebAuthenticationSession(url: oidcData.url, callback: .oidcRedirectURL(oidcRedirectURL)) { url, error in
continuation.resume(returning: (url, error))
}
session.prefersEphemeralWebBrowserSession = false
session.presentationContextProvider = self
request = Request(session: session, oidcData: oidcData, continuation: continuation)
session.start()
}
}
/// This method will be used if the `appSettings.oidcRedirectURL`'s scheme is `https`.
/// When using a custom scheme, the redirect will be handled by the web auth session's closure.
func handleUniversalLinkCallback(_ url: URL) {
completeAuthentication(callbackURL: url)
}
/// Completes the authentication by exchanging the callback URL for a user session.
private func completeAuthentication(callbackURL: URL) {
guard let request else {
MXLog.error("Failed to complete authentication. Missing request.")
return
}
if callbackURL.scheme?.starts(with: "http") == true {
request.session.cancel()
}
Task {
switch await authenticationService.loginWithOIDCCallback(callbackURL, data: request.oidcData) {
case .success(let userSession):
request.continuation.resume(returning: .success(userSession))
case .failure(let error):
request.continuation.resume(returning: .failure(error))
guard let url else {
// Check for user cancellation to avoid showing an alert in that instance.
if let nsError = error as? NSError,
nsError.domain == ASWebAuthenticationSessionErrorDomain,
nsError.code == ASWebAuthenticationSessionError.canceledLogin.rawValue {
await authenticationService.abortOIDCLogin(data: oidcData)
return .failure(.oidcError(.userCancellation))
}
}
}
/// Aborts the authentication with the supplied error.
private func completeAuthentication(throwing error: AuthenticationServiceError) {
guard let request else {
MXLog.error("Failed to throw authentication error. Missing request.")
return
await authenticationService.abortOIDCLogin(data: oidcData)
return .failure(.oidcError(.unknown))
}
Task {
await authenticationService.abortOIDCLogin(data: request.oidcData)
request.continuation.resume(returning: .failure(error))
switch await authenticationService.loginWithOIDCCallback(url, data: oidcData) {
case .success(let userSession):
return .success(userSession)
case .failure(let error):
return .failure(error)
}
}
}
@@ -110,3 +61,15 @@ class OIDCAuthenticationPresenter: NSObject {
extension OIDCAuthenticationPresenter: ASWebAuthenticationPresentationContextProviding {
func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor { presentationAnchor }
}
extension ASWebAuthenticationSession.Callback {
static func oidcRedirectURL(_ url: URL) -> Self {
if url.scheme == "https", let host = url.host() {
.https(host: host, path: url.path())
} else if let scheme = url.scheme {
.customScheme(scheme)
} else {
fatalError("Invalid OIDC redirect URL: \(url)")
}
}
}
@@ -85,15 +85,6 @@ final class SoftLogoutScreenCoordinator: CoordinatorProtocol {
AnyView(SoftLogoutScreen(context: viewModel.context))
}
func handleOIDCRedirectURL(_ url: URL) {
guard let oidcPresenter else {
MXLog.error("Failed to find an OIDC request in progress.")
return
}
oidcPresenter.handleUniversalLinkCallback(url)
}
// MARK: - Private
private static let loadingIndicatorIdentifier = "\(SoftLogoutScreenCoordinator.self)-Loading"