diff --git a/Linphone.xcodeproj/project.pbxproj b/Linphone.xcodeproj/project.pbxproj index eaffc2479..560c3787a 100644 --- a/Linphone.xcodeproj/project.pbxproj +++ b/Linphone.xcodeproj/project.pbxproj @@ -35,8 +35,14 @@ 66FBFC492B83BD2400BC6AB1 /* ConfigExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66C491F82B24D25A00CEA16D /* ConfigExtension.swift */; }; 66FBFC4A2B83BD3300BC6AB1 /* FileUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66C491FE2B24D4AC00CEA16D /* FileUtils.swift */; }; 66FBFC4B2B83BD7B00BC6AB1 /* CoreExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66C491FA2B24D32600CEA16D /* CoreExtension.swift */; }; + C60E8F192C0F649200A06DB8 /* UIApplicationExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C60E8F182C0F649200A06DB8 /* UIApplicationExtension.swift */; }; C67586AE2C09F23C002E77BF /* URLExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C67586AD2C09F23C002E77BF /* URLExtension.swift */; }; C67586B02C09F247002E77BF /* URIHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = C67586AF2C09F247002E77BF /* URIHandler.swift */; }; + C67586B52C09F617002E77BF /* SingleSignOnManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C67586B22C09F617002E77BF /* SingleSignOnManager.swift */; }; + C6A5A9412C10B5D50070FEA4 /* EncodableExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6A5A9402C10B5D50070FEA4 /* EncodableExtension.swift */; }; + C6A5A9432C10B5ED0070FEA4 /* DecodableExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6A5A9422C10B5ED0070FEA4 /* DecodableExtension.swift */; }; + C6A5A9452C10B6270070FEA4 /* OIDAuthStateExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6A5A9442C10B6270070FEA4 /* OIDAuthStateExtension.swift */; }; + C6A5A9482C10B6A30070FEA4 /* AuthState.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6A5A9472C10B6A30070FEA4 /* AuthState.swift */; }; D706BA822ADD72D100278F45 /* DeviceRotationViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = D706BA812ADD72D100278F45 /* DeviceRotationViewModifier.swift */; }; D70959F12B8DF3EC0014AC0B /* ConversationModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D70959F02B8DF3EC0014AC0B /* ConversationModel.swift */; }; D70A26EE2B7CF60B006CC8FC /* ConversationsListBottomSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = D70A26ED2B7CF60B006CC8FC /* ConversationsListBottomSheet.swift */; }; @@ -194,8 +200,14 @@ 66E56BCB2BA9A1E0006CE56F /* MeetingsListItemModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeetingsListItemModel.swift; sourceTree = ""; }; 66E56BCD2BA9A1F8006CE56F /* MeetingModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeetingModel.swift; sourceTree = ""; }; 66F626B12BCEBB86003E2DEC /* AddParticipantsFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddParticipantsFragment.swift; sourceTree = ""; }; + C60E8F182C0F649200A06DB8 /* UIApplicationExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIApplicationExtension.swift; sourceTree = ""; }; C67586AD2C09F23C002E77BF /* URLExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = URLExtension.swift; sourceTree = ""; }; C67586AF2C09F247002E77BF /* URIHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = URIHandler.swift; sourceTree = ""; }; + C67586B22C09F617002E77BF /* SingleSignOnManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SingleSignOnManager.swift; sourceTree = ""; }; + C6A5A9402C10B5D50070FEA4 /* EncodableExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncodableExtension.swift; sourceTree = ""; }; + C6A5A9422C10B5ED0070FEA4 /* DecodableExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DecodableExtension.swift; sourceTree = ""; }; + C6A5A9442C10B6270070FEA4 /* OIDAuthStateExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OIDAuthStateExtension.swift; sourceTree = ""; }; + C6A5A9472C10B6A30070FEA4 /* AuthState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthState.swift; sourceTree = ""; }; D706BA812ADD72D100278F45 /* DeviceRotationViewModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceRotationViewModifier.swift; sourceTree = ""; }; D70959F02B8DF3EC0014AC0B /* ConversationModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationModel.swift; sourceTree = ""; }; D70A26ED2B7CF60B006CC8FC /* ConversationsListBottomSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationsListBottomSheet.swift; sourceTree = ""; }; @@ -353,6 +365,9 @@ D76005F52B0798B00054B79A /* IntExtension.swift */, D717071F2AC5989C0037746F /* TextExtension.swift */, D71A0E182B485ADF0002C6CD /* ViewExtension.swift */, + C60E8F182C0F649200A06DB8 /* UIApplicationExtension.swift */, + C6A5A9402C10B5D50070FEA4 /* EncodableExtension.swift */, + C6A5A9422C10B5ED0070FEA4 /* DecodableExtension.swift */, ); path = Extensions; sourceTree = ""; @@ -406,6 +421,16 @@ path = Pods; sourceTree = ""; }; + C6A5A9462C10B64A0070FEA4 /* SingleSignOn */ = { + isa = PBXGroup; + children = ( + C6A5A9442C10B6270070FEA4 /* OIDAuthStateExtension.swift */, + C67586B22C09F617002E77BF /* SingleSignOnManager.swift */, + C6A5A9472C10B6A30070FEA4 /* AuthState.swift */, + ); + path = SingleSignOn; + sourceTree = ""; + }; D70959EF2B8DF33B0014AC0B /* Model */ = { isa = PBXGroup; children = ( @@ -432,6 +457,7 @@ D7B99E9A2B29F7C200BE7BF2 /* ActivityIndicator.swift */, D7173EBD2B7A5C0A00BCC481 /* LinphoneUtils.swift */, C67586AF2C09F247002E77BF /* URIHandler.swift */, + C6A5A9462C10B64A0070FEA4 /* SingleSignOn */, ); path = Utils; sourceTree = ""; @@ -997,11 +1023,15 @@ 66E50A492BD12B2300AD61CA /* MeetingsView.swift in Sources */, D719ABCF2ABC779A00B41C10 /* AccountLoginViewModel.swift in Sources */, D717630D2BD7BD0E00464097 /* ParticipantsListFragment.swift in Sources */, + C6A5A9452C10B6270070FEA4 /* OIDAuthStateExtension.swift in Sources */, D732A90F2B04C3B400DB42BA /* HistoryFragment.swift in Sources */, D79622342B1DFE600037EACD /* DialerBottomSheet.swift in Sources */, C67586B02C09F247002E77BF /* URIHandler.swift in Sources */, + C60E8F192C0F649200A06DB8 /* UIApplicationExtension.swift in Sources */, D78290BB2ADD40B2004AA85C /* ContactViewModel.swift in Sources */, + C67586B52C09F617002E77BF /* SingleSignOnManager.swift in Sources */, D7F4D9CB2B5FD27200CDCD76 /* CallsListFragment.swift in Sources */, + C6A5A9482C10B6A30070FEA4 /* AuthState.swift in Sources */, D72992392ADD7F68003AF125 /* HistoryContactFragment.swift in Sources */, D70A26F02B7D02E6006CC8FC /* ConversationViewModel.swift in Sources */, 6613A0AE2BAEB7DF008923A4 /* MeetingFragment.swift in Sources */, @@ -1041,6 +1071,7 @@ 6613A0B42BAEBE3F008923A4 /* MeetingViewModel.swift in Sources */, D7173EBE2B7A5C0A00BCC481 /* LinphoneUtils.swift in Sources */, 66C492012B24DB6900CEA16D /* Log.swift in Sources */, + C6A5A9432C10B5ED0070FEA4 /* DecodableExtension.swift in Sources */, D714035B2BE11E00004BD8CA /* CallMediaEncryptionModel.swift in Sources */, 6613A0B62BAEBE5C008923A4 /* ScheduleMeetingViewModel.swift in Sources */, D748BF2C2ACD82D2004844EB /* ThirdPartySipAccountLoginFragment.swift in Sources */, @@ -1075,6 +1106,7 @@ D7DA67642ACCB31700E95002 /* ProfileModeFragment.swift in Sources */, D7CEE03D2B7A23B200FD79B7 /* ConversationsListFragment.swift in Sources */, D74C9CFC2ACACF370021626A /* WelcomePage3Fragment.swift in Sources */, + C6A5A9412C10B5D50070FEA4 /* EncodableExtension.swift in Sources */, D719ABCC2ABC769C00B41C10 /* AssistantView.swift in Sources */, D78E062C2BEA69BC00CE3783 /* CallStatisticsSheetBottomSheet.swift in Sources */, D7C365082AEFAB7F00FE6142 /* ContactListBottomSheet.swift in Sources */, diff --git a/Linphone/Core/CoreContext.swift b/Linphone/Core/CoreContext.swift index a5ecf668e..92b801974 100644 --- a/Linphone/Core/CoreContext.swift +++ b/Linphone/Core/CoreContext.swift @@ -45,6 +45,8 @@ final class CoreContext: ObservableObject { private var mIterateSuscription: AnyCancellable? private var mCoreSuscriptions = Set() + var bearerAuthInfoPendingPasswordUpdate: AuthInfo? = nil + let monitor = NWPathMonitor() private var mCorePushIncomingDelegate: CoreDelegate! @@ -250,6 +252,19 @@ final class CoreContext: ObservableObject { } }) + self.mCoreSuscriptions.insert(self.mCore.publisher?.onAuthenticationRequested?.postOnCoreQueue { (cbValue: (_: Core, authInfo: AuthInfo, method: AuthMethod)) in + let authInfo = cbValue.authInfo + guard let username = authInfo.username, let server = authInfo.authorizationServer, !server.isEmpty else { + Log.error("Authentication requested but either username [\(String(describing: authInfo.username))], domain [\(String(describing: authInfo.domain))] or server [\(String(describing: authInfo.authorizationServer))] is nil or empty!") + return + } + if cbValue.method == .Bearer { + Log.info("Authentication requested method is Bearer, starting Single Sign On activity with server URL \(server) and username \(username)") + self.bearerAuthInfoPendingPasswordUpdate = cbValue.authInfo + SingleSignOnManager.shared.setUp(ssoUrl: server, user: username) + } + }) + self.mIterateSuscription = Timer.publish(every: 0.02, on: .main, in: .common) .autoconnect() .receive(on: coreQueue) diff --git a/Linphone/Info.plist b/Linphone/Info.plist index c683a3dcf..e32fc036e 100644 --- a/Linphone/Info.plist +++ b/Linphone/Info.plist @@ -84,6 +84,16 @@ tel + + CFBundleTypeRole + Editor + CFBundleURLName + org.linphone.phone + CFBundleURLSchemes + + org.linphone + + ITSAppUsesNonExemptEncryption diff --git a/Linphone/Utils/Extensions/DecodableExtension.swift b/Linphone/Utils/Extensions/DecodableExtension.swift new file mode 100644 index 000000000..71ccb81c5 --- /dev/null +++ b/Linphone/Utils/Extensions/DecodableExtension.swift @@ -0,0 +1,28 @@ +/* +* Copyright (c) 2010-2020 Belledonne Communications SARL. +* +* This file is part of linhome +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +*/ + +import Foundation + +extension Decodable { + init?(dictionary: [String: Any]) { + guard let data = try? JSONSerialization.data(withJSONObject: dictionary) else { return nil } + guard let object = try? JSONDecoder().decode(Self.self, from: data) else { return nil } + self = object + } +} diff --git a/Linphone/Utils/Extensions/EncodableExtension.swift b/Linphone/Utils/Extensions/EncodableExtension.swift new file mode 100644 index 000000000..8c0e75710 --- /dev/null +++ b/Linphone/Utils/Extensions/EncodableExtension.swift @@ -0,0 +1,27 @@ +/* +* Copyright (c) 2010-2020 Belledonne Communications SARL. +* +* This file is part of linhome +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +*/ + +import Foundation + +extension Encodable { + var asDictionary: [String: Any]? { + guard let data = try? JSONEncoder().encode(self) else { return nil } + return try? JSONSerialization.jsonObject(with: data) as? [String: Any] + } +} diff --git a/Linphone/Utils/Extensions/UIApplicationExtension.swift b/Linphone/Utils/Extensions/UIApplicationExtension.swift new file mode 100644 index 000000000..09f1d5d54 --- /dev/null +++ b/Linphone/Utils/Extensions/UIApplicationExtension.swift @@ -0,0 +1,38 @@ +/* +* Copyright (c) 2010-2020 Belledonne Communications SARL. +* +* This file is part of linhome +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +*/ + +import Foundation +import UIKit + +extension UIApplication { + class func getTopMostViewController() -> UIViewController? { + if let scenes = UIApplication.shared.connectedScenes.first as? UIWindowScene { + let keyWindow = scenes.windows.filter {$0.isKeyWindow}.first + if var topController = keyWindow?.rootViewController { + while let presentedViewController = topController.presentedViewController { + topController = presentedViewController + } + return topController + } else { + return nil + } + } + return nil + } +} diff --git a/Linphone/Utils/SingleSignOn/AuthState.swift b/Linphone/Utils/SingleSignOn/AuthState.swift new file mode 100644 index 000000000..849415089 --- /dev/null +++ b/Linphone/Utils/SingleSignOn/AuthState.swift @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2010-2023 Belledonne Communications SARL. + * + * This file is part of linphone-iphone + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import Foundation +import AppAuth + +class AuthState: Encodable, Decodable { + var accessToken: String? + var refreshToken: String? + var tokenEndpointUri: String? + var accessTokenExpirationTime: Date? + var isAuthorized: Bool + + init(oidAuthState: OIDAuthState) { + accessToken = oidAuthState.lastTokenResponse?.accessToken + refreshToken = oidAuthState.refreshToken + tokenEndpointUri = oidAuthState.lastTokenResponse?.request.configuration.tokenEndpoint.absoluteString + accessTokenExpirationTime = oidAuthState.getAccessTokenExpirationTime() + isAuthorized = oidAuthState.isAuthorized + } + + func update(tokenResponse: OIDTokenResponse) { + accessToken = tokenResponse.accessToken + refreshToken = tokenResponse.refreshToken + tokenEndpointUri = tokenResponse.request.configuration.tokenEndpoint.absoluteString + accessTokenExpirationTime = tokenResponse.accessTokenExpirationDate + } +} diff --git a/Linphone/Utils/SingleSignOn/OIDAuthStateExtension.swift b/Linphone/Utils/SingleSignOn/OIDAuthStateExtension.swift new file mode 100644 index 000000000..f9f541529 --- /dev/null +++ b/Linphone/Utils/SingleSignOn/OIDAuthStateExtension.swift @@ -0,0 +1,36 @@ +/* +* Copyright (c) 2010-2020 Belledonne Communications SARL. +* +* This file is part of linhome +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +*/ + +import Foundation +import AppAuth + +extension OIDAuthState { + public func getAccessTokenExpirationTime() -> Date? { + if authorizationError != nil { + return nil + } + if lastTokenResponse?.accessToken != nil { + return lastTokenResponse?.accessTokenExpirationDate + } + if lastAuthorizationResponse.accessToken != nil { + return lastAuthorizationResponse.accessTokenExpirationDate + } + return nil + } +} diff --git a/Linphone/Utils/SingleSignOn/SingleSignOnManager.swift b/Linphone/Utils/SingleSignOn/SingleSignOnManager.swift new file mode 100644 index 000000000..4d3afe523 --- /dev/null +++ b/Linphone/Utils/SingleSignOn/SingleSignOnManager.swift @@ -0,0 +1,178 @@ +/* + * Copyright (c) 2010-2023 Belledonne Communications SARL. + * + * This file is part of linphone-iphone + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import Foundation +import linphonesw +import AppAuth + +class SingleSignOnManager { + + static let shared = SingleSignOnManager() + + private let TAG = "[SSO]" + private let clientId = "linphone" + private let userDefaultSSOKey = "sso-authstate" + let ssoRedirectUri = URL(string: "org.linphone:/openidcallback")! + private var singleSignOnUrl = "" + private var username: String = "" + private var authState: AuthState? + private var authService: OIDAuthorizationService? + var currentAuthorizationFlow: OIDExternalUserAgentSession? + + func persistedAuthState() -> AuthState? { + if let persistentAuthState = UserDefaults.standard.object(forKey: userDefaultSSOKey), let fromDictionary = persistentAuthState as? [String: Any] { + return AuthState(dictionary: fromDictionary) + } else { + return nil + } + } + + func persistAuthState() { + if let authState = authState { + UserDefaults.standard.set(authState.asDictionary, forKey: userDefaultSSOKey) + } + } + + func setUp(ssoUrl: String, user: String = "") { + singleSignOnUrl = ssoUrl + username = user + Log.info("\(TAG) Setting up SSO environment for username \(username) and URL \(singleSignOnUrl)") + authState = persistedAuthState() + updateTokenInfo() + } + + private func updateTokenInfo() { + Log.info("\(TAG) Updating token info") + if authState?.isAuthorized == true { + Log.info("\(TAG) User is already authenticated!") + if let expiration = authState?.accessTokenExpirationTime { + if expiration < Date() { + Log.warn("\(TAG) Access token is expired") + performRefreshToken() + } else { + Log.info("\(TAG) Access token valid, expires \(expiration)") + storeTokensInAuthInfo() + } + } else { + Log.warn("\(TAG) Access token expiration info not available") + singleSignOn() + } + } else { + Log.warn("\(TAG) User isn't authenticated yet") + singleSignOn() + } + } + + private func performRefreshToken() { + Log.info("\(TAG) Refreshing token") + if let issuer = URL(string: singleSignOnUrl) { + OIDAuthorizationService.discoverConfiguration(forIssuer: issuer) { configuration, error in + guard let configuration = configuration, let refreshToken = self.authState?.refreshToken else { + Log.error("\(self.TAG) Error retrieving discovery document: \(error?.localizedDescription ?? "Unknown error")") + return + } + let request = OIDTokenRequest( + configuration: configuration, + grantType: OIDGrantTypeRefreshToken, + authorizationCode: nil, + redirectURL: nil, + clientID: self.clientId, + clientSecret: nil, + scope: nil, + refreshToken: refreshToken, + codeVerifier: nil, + additionalParameters: nil) + + OIDAuthorizationService.perform(request) { tokenResponse, error in + if error != nil { + Log.error("\(self.TAG) Error occured refreshing token \(String(describing: error))") + self.authState = nil + self.singleSignOn() + return + } + if let tokenResponse = tokenResponse, tokenResponse.accessToken != nil { + Log.info("\(self.TAG) Refreshed token \(String(describing: tokenResponse.accessToken))") + self.authState?.update(tokenResponse: tokenResponse) + self.storeTokensInAuthInfo() + } else { + Log.info("\(self.TAG) refresh token response or access token is empty") + self.authState = nil + self.singleSignOn() + } + } + } + } + } + + private func singleSignOn() { + if let issuer = URL(string: singleSignOnUrl) { + OIDAuthorizationService.discoverConfiguration(forIssuer: issuer) { configuration, error in + guard let configuration = configuration else { + Log.error("\(self.TAG) Error retrieving discovery document: \(error?.localizedDescription ?? "Unknown error")") + return + } + + let request = OIDAuthorizationRequest(configuration: configuration, + clientId: self.clientId, + scopes: ["offline_access"], + redirectURL: self.ssoRedirectUri, + responseType: OIDResponseTypeCode, + additionalParameters: ["login_hint": self.username]) + + Log.info("\(self.TAG) Initiating authorization request with scope: \(request.scope ?? "nil")") + if let viewController = UIApplication.getTopMostViewController() { + self.currentAuthorizationFlow = + OIDAuthState.authState(byPresenting: request, presenting: viewController) { authState, error in + if let authState = authState { + self.authState = AuthState(oidAuthState: authState) + self.persistAuthState() + Log.info("\(self.TAG) Got authorization tokens. Access token: " + + "\(authState.lastTokenResponse?.accessToken ?? "nil")") + self.storeTokensInAuthInfo() + } else { + Log.info("\(self.TAG) Authorization error: \(error?.localizedDescription ?? "Unknown error")") + self.authState = nil + } + } + } + } + } + } + + private func storeTokensInAuthInfo() { + CoreContext.shared.doOnCoreQueue { core in + if let expire = self.authState?.accessTokenExpirationTime?.timeIntervalSince1970, + let accessToken = self.authState?.accessToken, + let lAccessToken = try?Factory.Instance.createBearerToken(token: accessToken, expirationTime: Int(expire)), + let refreshToken = self.authState?.refreshToken, + let lRefreshToken = try?Factory.Instance.createBearerToken(token: refreshToken, expirationTime: Int(expire)), + let authInfo = CoreContext.shared.bearerAuthInfoPendingPasswordUpdate { + authInfo.accessToken = lAccessToken + authInfo.refreshToken = lRefreshToken + authInfo.tokenEndpointUri = self.authState?.tokenEndpointUri + authInfo.clientId = self.clientId + core.addAuthInfo(info: authInfo) + Log.info("\(self.TAG) Auth info added username=\(self.username) access token=\(accessToken) refresh token=\(refreshToken) expire=\(expire)") + core.refreshRegisters() + } else { + Log.warn("\(self.TAG) Unable to store SSO details in auth info") + } + } + } +} diff --git a/Linphone/Utils/URIHandler.swift b/Linphone/Utils/URIHandler.swift index 8643c7101..54dfeab49 100644 --- a/Linphone/Utils/URIHandler.swift +++ b/Linphone/Utils/URIHandler.swift @@ -27,12 +27,12 @@ class URIHandler { private static let callSchemes = ["sip", "sip-linphone", "linphone-sip", "tel"] private static let secureCallSchemes = ["sips", "sips-linphone", "linphone-sips"] private static let configurationSchemes = ["linphone-config"] - - private static var uriHandlerCoreDelegate: CoreDelegateStub? = nil + + private static var uriHandlerCoreDelegate: CoreDelegateStub? static func addCoreDelegate() { uriHandlerCoreDelegate = CoreDelegateStub( - onCallStateChanged: { (core: Core, call: Call, state: Call.State, message: String) in + onCallStateChanged: { (_: Core, _: Call, state: Call.State, _: String) in if state == .Error { toast("Failed_uri_handler_call_failed") CoreContext.shared.removeCoreDelegateStub(delegate: uriHandlerCoreDelegate!) @@ -41,7 +41,7 @@ class URIHandler { CoreContext.shared.removeCoreDelegateStub(delegate: uriHandlerCoreDelegate!) } }, - onConfiguringStatus: { (core:Core, state:ConfiguringState, status: String) in + onConfiguringStatus: { (_: Core, state: ConfiguringState, _: String) in if state == .Failed { toast("Failed_uri_handler_config_failed") CoreContext.shared.removeCoreDelegateStub(delegate: uriHandlerCoreDelegate!) @@ -54,7 +54,6 @@ class URIHandler { CoreContext.shared.addCoreDelegateStub(delegate: uriHandlerCoreDelegate!) } - static func handleURL(url: URL) { Log.info("[URIHandler] handleURL: \(url)") if let scheme = url.scheme { @@ -64,6 +63,8 @@ class URIHandler { initiateCall(url: url, withScheme: "sip") } else if configurationSchemes.contains(scheme) { initiateConfiguration(url: url) + } else if scheme == SingleSignOnManager.shared.ssoRedirectUri.scheme { + continueSSO(url: url) } else { Log.error("[URIHandler] unhandled URL \(url) (check Info.plist)") } @@ -107,10 +108,17 @@ class URIHandler { } } + private static func continueSSO(url: URL) { + if let authorizationFlow = SingleSignOnManager.shared.currentAuthorizationFlow, + authorizationFlow.resumeExternalUserAgentFlow(with: url) { + SingleSignOnManager.shared.currentAuthorizationFlow = nil + } + } + private static func autoRemoteProvisioningOnConfigUriHandler() -> Bool { return Config.get().getBool(section: "app", key: "auto_apply_provisioning_config_uri_handler", defaultValue: true) } - + private static func toast(_ message: String) { DispatchQueue.main.async { ToastViewModel.shared.toastMessage = message diff --git a/Podfile b/Podfile index cc53ba162..196e3f81f 100644 --- a/Podfile +++ b/Podfile @@ -26,6 +26,7 @@ target 'Linphone' do # Pods for Linphone pod 'SwiftLint' + pod 'AppAuth' basic_pods end