Single Sign On

This commit is contained in:
Christophe Deschamps 2024-05-31 13:53:11 +02:00
parent bd29389a40
commit 2d0f50d11a
11 changed files with 423 additions and 6 deletions

View file

@ -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 = "<group>"; };
66E56BCD2BA9A1F8006CE56F /* MeetingModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeetingModel.swift; sourceTree = "<group>"; };
66F626B12BCEBB86003E2DEC /* AddParticipantsFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddParticipantsFragment.swift; sourceTree = "<group>"; };
C60E8F182C0F649200A06DB8 /* UIApplicationExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIApplicationExtension.swift; sourceTree = "<group>"; };
C67586AD2C09F23C002E77BF /* URLExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = URLExtension.swift; sourceTree = "<group>"; };
C67586AF2C09F247002E77BF /* URIHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = URIHandler.swift; sourceTree = "<group>"; };
C67586B22C09F617002E77BF /* SingleSignOnManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SingleSignOnManager.swift; sourceTree = "<group>"; };
C6A5A9402C10B5D50070FEA4 /* EncodableExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncodableExtension.swift; sourceTree = "<group>"; };
C6A5A9422C10B5ED0070FEA4 /* DecodableExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DecodableExtension.swift; sourceTree = "<group>"; };
C6A5A9442C10B6270070FEA4 /* OIDAuthStateExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OIDAuthStateExtension.swift; sourceTree = "<group>"; };
C6A5A9472C10B6A30070FEA4 /* AuthState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthState.swift; sourceTree = "<group>"; };
D706BA812ADD72D100278F45 /* DeviceRotationViewModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceRotationViewModifier.swift; sourceTree = "<group>"; };
D70959F02B8DF3EC0014AC0B /* ConversationModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationModel.swift; sourceTree = "<group>"; };
D70A26ED2B7CF60B006CC8FC /* ConversationsListBottomSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationsListBottomSheet.swift; sourceTree = "<group>"; };
@ -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 = "<group>";
@ -406,6 +421,16 @@
path = Pods;
sourceTree = "<group>";
};
C6A5A9462C10B64A0070FEA4 /* SingleSignOn */ = {
isa = PBXGroup;
children = (
C6A5A9442C10B6270070FEA4 /* OIDAuthStateExtension.swift */,
C67586B22C09F617002E77BF /* SingleSignOnManager.swift */,
C6A5A9472C10B6A30070FEA4 /* AuthState.swift */,
);
path = SingleSignOn;
sourceTree = "<group>";
};
D70959EF2B8DF33B0014AC0B /* Model */ = {
isa = PBXGroup;
children = (
@ -432,6 +457,7 @@
D7B99E9A2B29F7C200BE7BF2 /* ActivityIndicator.swift */,
D7173EBD2B7A5C0A00BCC481 /* LinphoneUtils.swift */,
C67586AF2C09F247002E77BF /* URIHandler.swift */,
C6A5A9462C10B64A0070FEA4 /* SingleSignOn */,
);
path = Utils;
sourceTree = "<group>";
@ -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 */,

View file

@ -45,6 +45,8 @@ final class CoreContext: ObservableObject {
private var mIterateSuscription: AnyCancellable?
private var mCoreSuscriptions = Set<AnyCancellable?>()
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)

View file

@ -84,6 +84,16 @@
<string>tel</string>
</array>
</dict>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLName</key>
<string>org.linphone.phone</string>
<key>CFBundleURLSchemes</key>
<array>
<string>org.linphone</string>
</array>
</dict>
</array>
<key>ITSAppUsesNonExemptEncryption</key>
<true/>

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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]
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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")
}
}
}
}

View file

@ -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

View file

@ -26,6 +26,7 @@ target 'Linphone' do
# Pods for Linphone
pod 'SwiftLint'
pod 'AppAuth'
basic_pods
end