diff --git a/Riot/Modules/Authentication/SSO/LegacySSOAuthentificationSession.swift b/Riot/Modules/Authentication/SSO/LegacySSOAuthentificationSession.swift new file mode 100644 index 000000000..7c6a70742 --- /dev/null +++ b/Riot/Modules/Authentication/SSO/LegacySSOAuthentificationSession.swift @@ -0,0 +1,59 @@ +// +// Copyright 2020 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 +import SafariServices + +/// LegacySSOAuthentificationSession is session used to authenticate a user through a web service on iOS 11 and earlier. It uses SFAuthenticationSession. +final class LegacySSOAuthentificationSession: SSOAuthentificationSessionProtocol { + + // MARK: - Constants + + // MARK: - Properties + + private var authentificationSession: SFAuthenticationSession? + + // MARK: - Public + + func setContextProvider(_ contextProvider: SSOAuthenticationSessionContextProviding) { + } + + func authenticate(with url: URL, callbackURLScheme: String?, completionHandler: @escaping SSOAuthentificationSessionCompletionHandler) { + + let authentificationSession = SFAuthenticationSession(url: url, callbackURLScheme: callbackURLScheme) { (callbackURL, error) in + + var finalError: Error? + + if let error = error as? SFAuthenticationError { + switch error.code { + case .canceledLogin: + finalError = SSOAuthentificationSessionError.userCanceled + default: + finalError = error + } + } + + completionHandler(callbackURL, finalError) + } + + self.authentificationSession = authentificationSession + authentificationSession.start() + } + + func cancel() { + self.authentificationSession?.cancel() + } +} diff --git a/Riot/Modules/Authentication/SSO/SSOAuthentificationSession.swift b/Riot/Modules/Authentication/SSO/SSOAuthentificationSession.swift new file mode 100644 index 000000000..f068f8943 --- /dev/null +++ b/Riot/Modules/Authentication/SSO/SSOAuthentificationSession.swift @@ -0,0 +1,89 @@ +// +// Copyright 2020 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 +import AuthenticationServices + +/// Provides context to target where in an application's UI the authorization view should be shown. +@available(iOS 12.0, *) +class SSOAuthenticationSessionContextProvider: NSObject, SSOAuthenticationSessionContextProviding, ASWebAuthenticationPresentationContextProviding { + let window: UIWindow + + init(window: UIWindow) { + self.window = window + } + + func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor { + return window + } +} + +/// SSOAuthentificationSession is session used to authenticate a user through a web service on iOS 12+. It uses ASWebAuthenticationSession. +/// More information: https://developer.apple.com/documentation/authenticationservices/authenticating_a_user_through_a_web_service +@available(iOS 12.0, *) +final class SSOAuthentificationSession: SSOAuthentificationSessionProtocol { + + // MARK: - Constants + + // MARK: - Properties + + private var authentificationSession: ASWebAuthenticationSession? + private var contextProvider: SSOAuthenticationSessionContextProviding? + + // MARK: - Public + + func setContextProvider(_ contextProvider: SSOAuthenticationSessionContextProviding) { + self.contextProvider = contextProvider + } + + func authenticate(with url: URL, callbackURLScheme: String?, completionHandler: @escaping SSOAuthentificationSessionCompletionHandler) { + + let authentificationSession = ASWebAuthenticationSession(url: url, callbackURLScheme: callbackURLScheme) { (callbackURL, error) in + + var finalError: Error? + + if let error = error as? ASWebAuthenticationSessionError { + switch error.code { + case .canceledLogin: + finalError = SSOAuthentificationSessionError.userCanceled + default: + finalError = error + } + } + + completionHandler(callbackURL, finalError) + } + + // Ask the browser for a private authentication session + if #available(iOS 13.0, *) { + authentificationSession.prefersEphemeralWebBrowserSession = true + } + + self.authentificationSession = authentificationSession + + if #available(iOS 13.0, *) { + if let asWebContextProvider = contextProvider as? ASWebAuthenticationPresentationContextProviding { + authentificationSession.presentationContextProvider = asWebContextProvider + } + } + + authentificationSession.start() + } + + func cancel() { + self.authentificationSession?.cancel() + } +} diff --git a/Riot/Modules/Authentication/SSO/SSOAuthentificationSessionProtocol.swift b/Riot/Modules/Authentication/SSO/SSOAuthentificationSessionProtocol.swift new file mode 100644 index 000000000..899574e08 --- /dev/null +++ b/Riot/Modules/Authentication/SSO/SSOAuthentificationSessionProtocol.swift @@ -0,0 +1,46 @@ +// +// Copyright 2020 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 + +enum SSOAuthentificationSessionError: Error { + case userCanceled +} + +/// A completion handler the session calls when it completes successfully, or when the user cancels the session. +public typealias SSOAuthentificationSessionCompletionHandler = (URL?, Error?) -> Void + +/// An interface the session uses to ask a delegate for a presentation context. +protocol SSOAuthenticationSessionContextProviding { + var window: UIWindow { get } +} + +/// SSOAuthentificationSessionProtocol abstract a session that an app uses to authenticate a user through a web service (SFAuthenticationSession or ASWebAuthenticationSession). +protocol SSOAuthentificationSessionProtocol { + + /// Cancels the authentication session. Dismiss displayed authentication screen. + func cancel() + + /// Provides context to target where in an application's UI the authorization view should be shown. + func setContextProvider(_ contextProvider: SSOAuthenticationSessionContextProviding) + + /// Starts a web authentication session. + /// - Parameters: + /// - url: A URL with the http or https scheme pointing to the authentication webpage. + /// - callbackURLScheme: The custom URL scheme that the app expects in the callback URL. + /// - completionHandler: A completion handler the session calls when it completes successfully, or when the user cancels the session. + func authenticate(with url: URL, callbackURLScheme: String?, completionHandler: @escaping SSOAuthentificationSessionCompletionHandler) +}