diff --git a/CHANGELOG.md b/CHANGELOG.md index b5a55050..b713b591 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,15 @@ -# Version 4.0.0 +# Version 4.1.0 + +## [4.1.0] +#### Added +- Interceptor support for the Authenticator module [SDKS-2545] +- Deep link support for `mfauth` scheme in Authenticator sample app [SDKS-2524] +- Interface for access_token refresh [SDKS-2563] +- Ability to process new JSON format of IG policy advice [SDKS-2239] + +#### Fixed +- Fixed an issue on parsing `issuer` from combined MFA registration uri [SDKS-2542] +- Added error message about duplicated accounts while performing combined MFA registration [SDKS-2627] ## [4.0.0] #### Added diff --git a/FRAuth.podspec b/FRAuth.podspec index a7be356b..edf85483 100644 --- a/FRAuth.podspec +++ b/FRAuth.podspec @@ -8,7 +8,7 @@ Pod::Spec.new do |s| s.name = 'FRAuth' - s.version = '4.0.0' + s.version = '4.1.0' s.summary = 'ForgeRock Auth SDK for iOS' s.description = <<-DESC FRAuth is a SDK that allows you easily and quickly develop an application with ForgeRock Platform or ForgeRock Identity Cloud. FRAuth SDK provides interfaces and functionalities of user authentication, registration, and identity and access management against ForgeRock solutions. @@ -29,5 +29,5 @@ Pod::Spec.new do |s| base_dir = "FRAuth/FRAuth" s.source_files = base_dir + '/**/*.swift', base_dir + '/**/*.c', base_dir + '/**/*.h' - s.ios.dependency 'FRCore', '~> 4.0.0' + s.ios.dependency 'FRCore', '~> 4.1.0' end diff --git a/FRAuth/FRAuth.xcodeproj/project.pbxproj b/FRAuth/FRAuth.xcodeproj/project.pbxproj index 05616c95..bac6543a 100644 --- a/FRAuth/FRAuth.xcodeproj/project.pbxproj +++ b/FRAuth/FRAuth.xcodeproj/project.pbxproj @@ -9,8 +9,13 @@ /* Begin PBXBuildFile section */ 3A1B43D0284510B700EAFC9D /* AtomicDictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A1B43CF284510B700EAFC9D /* AtomicDictionary.swift */; }; 3A1B43D3284524B900EAFC9D /* AtomicDictionaryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A1B43D2284524B900EAFC9D /* AtomicDictionaryTests.swift */; }; + + 3A53E4DA2A153AF200E17DDF /* PolicyAdviceCreatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A53E4D92A153AF200E17DDF /* PolicyAdviceCreatorTests.swift */; }; + 3A6D26672A1345400099D877 /* PolicyAdviceCreator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A6D26662A1345400099D877 /* PolicyAdviceCreator.swift */; }; + 959D7D98290B4B9200A1F22F /* AA-05-DeviceBindingCallbackTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 959D7D97290B4B9200A1F22F /* AA-05-DeviceBindingCallbackTest.swift */; }; 95E180B42992A6F20087457D /* AA-06-DeviceSigningVerifierCallbackTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95E180B32992A6F20087457D /* AA-06-DeviceSigningVerifierCallbackTest.swift */; }; + A5950A2A27EA205B00EDEFE4 /* SSLPinningTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5950A2927EA205B00EDEFE4 /* SSLPinningTests.swift */; }; D512CD41240DC41E00AF520E /* FRRestClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = D512CD40240DC41E00AF520E /* FRRestClient.swift */; }; D513F17524DA6B490042228B /* AuthApiError.swift in Sources */ = {isa = PBXBuildFile; fileRef = D513F17424DA6B490042228B /* AuthApiError.swift */; }; @@ -323,8 +328,13 @@ /* Begin PBXFileReference section */ 3A1B43CF284510B700EAFC9D /* AtomicDictionary.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AtomicDictionary.swift; sourceTree = ""; }; 3A1B43D2284524B900EAFC9D /* AtomicDictionaryTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AtomicDictionaryTests.swift; sourceTree = ""; }; + + 3A53E4D92A153AF200E17DDF /* PolicyAdviceCreatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PolicyAdviceCreatorTests.swift; sourceTree = ""; }; + 3A6D26662A1345400099D877 /* PolicyAdviceCreator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PolicyAdviceCreator.swift; sourceTree = ""; }; + 959D7D97290B4B9200A1F22F /* AA-05-DeviceBindingCallbackTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AA-05-DeviceBindingCallbackTest.swift"; sourceTree = ""; }; 95E180B32992A6F20087457D /* AA-06-DeviceSigningVerifierCallbackTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AA-06-DeviceSigningVerifierCallbackTest.swift"; sourceTree = ""; }; + A5950A2927EA205B00EDEFE4 /* SSLPinningTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSLPinningTests.swift; sourceTree = ""; }; D5015FEE24996AE50025FEB6 /* MetadataCallbackTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetadataCallbackTests.swift; sourceTree = ""; }; D5054B4D244680C3007FBA92 /* DeviceProfileCallbackTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceProfileCallbackTests.swift; sourceTree = ""; }; @@ -951,6 +961,7 @@ isa = PBXGroup; children = ( D5723C3E23F4C66A00557AA8 /* PolicyAdvice.swift */, + 3A6D26662A1345400099D877 /* PolicyAdviceCreator.swift */, ); path = Authorization; sourceTree = ""; @@ -1433,6 +1444,7 @@ isa = PBXGroup; children = ( D5DE4BB02488AFD000FE2654 /* PolicyAdviceTests.swift */, + 3A53E4D92A153AF200E17DDF /* PolicyAdviceCreatorTests.swift */, ); path = Authorization; sourceTree = ""; @@ -1697,6 +1709,7 @@ D586CFA223358EE0007A2194 /* StringAttributeInputCallback.swift in Sources */, D53A8041262789BD0093B1CA /* PlatformAuthenticatorMakeCredentialSession.swift in Sources */, D53A804E262789BD0093B1CA /* WAKTypes.swift in Sources */, + 3A6D26672A1345400099D877 /* PolicyAdviceCreator.swift in Sources */, D53A804A262789BD0093B1CA /* AuthenticatorData.swift in Sources */, D53A803E262789BD0093B1CA /* Bytes.swift in Sources */, D53A8044262789BD0093B1CA /* PlatformAuthenticatorConfig.swift in Sources */, @@ -1884,7 +1897,11 @@ D5791BDD25F87DE8004B487A /* TokenTests.swift in Sources */, D5791BDE25F87DE8004B487A /* PKCETests.swift in Sources */, D58BC39F2602FB7700254654 /* IdPValueTests.swift in Sources */, + + 3A53E4DA2A153AF200E17DDF /* PolicyAdviceCreatorTests.swift in Sources */, + 959D7D98290B4B9200A1F22F /* AA-05-DeviceBindingCallbackTest.swift in Sources */, + ECDF5F4C296851DD007BB721 /* FRWebAuthnTests.swift in Sources */, D5791BDF25F87DE8004B487A /* AccessTokenTests.swift in Sources */, D5791BE025F87DE8004B487A /* FRUserTests.swift in Sources */, @@ -2058,7 +2075,7 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - MARKETING_VERSION = "4.0.0"; + MARKETING_VERSION = 4.1.0; MODULEMAP_FILE = ""; OTHER_CFLAGS = "-DXCODE_FRAMEWORK=1"; PRODUCT_BUNDLE_IDENTIFIER = com.forgerock.ios.FRAuth; @@ -2095,7 +2112,7 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - MARKETING_VERSION = "4.0.0"; + MARKETING_VERSION = 4.1.0; MODULEMAP_FILE = ""; OTHER_CFLAGS = "-DXCODE_FRAMEWORK=1"; PRODUCT_BUNDLE_IDENTIFIER = com.forgerock.ios.FRAuth; diff --git a/FRAuth/FRAuth/Authorization/PolicyAdvice.swift b/FRAuth/FRAuth/Authorization/PolicyAdvice.swift index 16e5bebc..445a8133 100644 --- a/FRAuth/FRAuth/Authorization/PolicyAdvice.swift +++ b/FRAuth/FRAuth/Authorization/PolicyAdvice.swift @@ -2,7 +2,7 @@ // PolicyAdvice.swift // FRAuth // -// Copyright (c) 2020 ForgeRock. All rights reserved. +// Copyright (c) 2020 - 2023 ForgeRock. All rights reserved. // // This software may be modified and distributed under the terms // of the MIT license. See the LICENSE file for details. @@ -47,7 +47,7 @@ public enum AdviceType: String { guard let url = URL(string: redirectUrl), let xmlstring = url.valueOf("authIndexValue"), let authIndexType = url.valueOf("authIndexType") else { return nil } - + self.authIndexType = authIndexType self.authIndexValue = xmlstring @@ -91,12 +91,15 @@ public enum AdviceType: String { if let advicesJSON = json["advices"] as? [String: Any], advicesJSON.keys.count > 0 { advices = advicesJSON + } else { + advices = json } + if let advices = advices, let adviceKey = advices.keys.first, let adviceValues = advices[adviceKey] as? [String], let adviceValue = adviceValues.first, let adviceType = AdviceType(rawValue: adviceKey) { - authIndexType = OpenAM.compositeAdvice - authIndexValue = "\(adviceValue)" + self.authIndexType = OpenAM.compositeAdvice + self.authIndexValue = "\(adviceValue)" type = adviceType value = adviceValue @@ -109,7 +112,6 @@ public enum AdviceType: String { } } - /** Initializes PolicyAdvice object with authorization policy type, and value. With example JSON payload shown below, 'TransactionConditionAdvice' is type of PolicyAdvice, and '9dae2c80-fe7a-4a36-b57b-4fb1271b0687' is value of PolicyAdvice ```` @@ -128,15 +130,18 @@ public enum AdviceType: String { - Parameter type: Type of authorization policy in string; 'TransactionConditionAdvice' or 'AuthenticateToServiceConditionAdvice' - Parameter value: String value of authorization policy; (i.e. transactionId, or Authentication Tree name) **/ - @objc public init?(type: String, value: String) { + @objc public init?(type: String, + value: String, + authIndexType: String? = nil, + authIndexValue: String? = nil) { guard let adviceType = AdviceType(rawValue: type) else { FRLog.w("Failed to parse AdviceType string value") return nil } - authIndexType = OpenAM.compositeAdvice - authIndexValue = "\(value)" + self.authIndexType = authIndexType ?? OpenAM.compositeAdvice + self.authIndexValue = authIndexValue ?? "\(value)" self.type = adviceType self.value = value diff --git a/FRAuth/FRAuth/Authorization/PolicyAdviceCreator.swift b/FRAuth/FRAuth/Authorization/PolicyAdviceCreator.swift new file mode 100644 index 00000000..ef614801 --- /dev/null +++ b/FRAuth/FRAuth/Authorization/PolicyAdviceCreator.swift @@ -0,0 +1,80 @@ +// +// PolicyAdviceCreator.swift +// FRAuth +// +// Copyright (c) 2023 ForgeRock. All rights reserved. +// +// This software may be modified and distributed under the terms +// of the MIT license. See the LICENSE file for details. +// + + +import Foundation + +/// PolicyAdviceCreator helps create a Authorization PolicyAdvice based on different response types (xml, base64XML, json) that receive from AM's policy engine +class PolicyAdviceCreator { + + private let adviceKey = "advices" + private let valueKey = "authIndexValue" + private let typeKey = "authIndexType" + + /// Parse the advice json + /// - Parameters: + /// - advice: The Advice in key value form + /// - Returns: The parsed PolicyAdvice + func parseAsBase64(advice: String) -> PolicyAdvice? { + var dict: [String: String] = [:] + let regex = try? NSRegularExpression(pattern: "^\"|\"$", options: []) + advice.components(separatedBy: ",").forEach { value in + let componenets = value.components(separatedBy: "=") + if(componenets.count >= 2 ) { + dict[componenets[0]] = regex?.stringByReplacingMatches(in: componenets[1], range: NSMakeRange(0, componenets[1].count), withTemplate: "") + } + } + guard let advices = dict[adviceKey], let decodedAdvices = advices.decodeURL(), + let adviceDict = try? JSONSerialization.jsonObject(with: decodedAdvices, options: []) as? [String: Any] else { + return nil + } + return PolicyAdvice(json: adviceDict) + } + + /// Parse the advice json + /// - Parameters: + /// - advice: The Advice in XML or base64 encoded form + /// - Returns: The parsed PolicyAdvice + func parse(advice: String) -> PolicyAdvice? { + guard let urlString = URL(string: advice), let authIndexValue = urlString.valueOf(valueKey), let authIndexType = urlString.valueOf(typeKey) else { + return nil + } + + // Try Parse the XML. + guard let result = parseXML(advice: authIndexValue) else { + //On Failure Decode and parse the XML + return decodeAndParseXML(authIndexType: authIndexType, + authIndexValue: authIndexValue) + } + return PolicyAdvice(type: result.0, + value: result.1, + authIndexType: authIndexType, + authIndexValue: authIndexValue) + } + + private func decodeAndParseXML(authIndexType: String, + authIndexValue: String) -> PolicyAdvice? { + guard let data = authIndexValue.decodeURL(), + let decodeXML = String(data: data, encoding: .utf8), + let result = parseXML(advice: decodeXML) else { return nil } + + return PolicyAdvice(type: result.0, + value: result.1, + authIndexType: authIndexType, + authIndexValue: decodeXML) + } + + private func parseXML(advice: String) -> (String, String)? { + guard let valueRange = advice.range(of: #"(?<=\).*?(?=\<\/Value\>)"#, options: .regularExpression), let nameRange = advice.range(of: #"(?<=\ PolicyAdvice? { FRLog.v("[AuthorizationPolicy] Evaluating Authorization Policy started") - if response.statusCode == 307, let redirectUrl = response.allHeaderFields["Location"] as? String, let policyAdvise = PolicyAdvice(redirectUrl: redirectUrl) { + if response.statusCode == 307, let redirectUrl = response.allHeaderFields["Location"] as? String, let policyAdvise = PolicyAdviceCreator().parse(advice: redirectUrl) { FRLog.i("[AuthorizationPolicy] IG request redirect (307) for Authorization Policy found; constructed PolicyAdvice based on IG redirection") return policyAdvise } @@ -131,6 +131,13 @@ import Foundation /// - Returns: PolicyAdvice if given redirect response matches with any of conditions func evaluateAuthorizationPolicy(responseData: Data?, response: URLResponse?, error: Error?) -> PolicyAdvice? { FRLog.v("[AuthorizationPolicy] Evaluating Authorization Policy started") + + if let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 401, let json = httpResponse.allHeaderFields["Www-Authenticate"] as? String, + let policyAdvice = PolicyAdviceCreator().parseAsBase64(advice: json) { + FRLog.i("[AuthorizationPolicy] PolicyAdvice JSON object found from response JSON payload; returning PolicyAdvice") + return policyAdvice + } + if let data = responseData, let json = try? JSONSerialization.jsonObject(with: data, options: []) as? [[String: Any]], let evalResult = json.first, let policyAdvice = PolicyAdvice(json: evalResult) { FRLog.i("[AuthorizationPolicy] PolicyAdvice JSON object found from response JSON payload; returning PolicyAdvice") return policyAdvice diff --git a/FRAuth/FRAuth/User/FRUser.swift b/FRAuth/FRAuth/User/FRUser.swift index 3a66a088..dcd24a3a 100644 --- a/FRAuth/FRAuth/User/FRUser.swift +++ b/FRAuth/FRAuth/User/FRUser.swift @@ -297,6 +297,51 @@ public class FRUser: NSObject, NSSecureCoding { } } + /// Refreshes user's session with refresh_token regardless of validity of current access_token + /// + /// - Parameter completion: Completion callback that notifies the result of operation + @objc + public func refresh(completion:@escaping UserCallback) { + if let frAuth = FRAuth.shared, let tokenManager = frAuth.tokenManager { + let oldToken = self.token + tokenManager.refresh{ (token, error) in + if let token = token { + self.revokeGivenAccessToken(oldToken) + self.token = token + self.save() + completion(self, nil) + } + else { + completion(nil, error) + } + } + } + else { + FRLog.w("Invalid SDK state") + completion(nil, ConfigError.invalidSDKState) + } + } + + + /// Refreshes user's session with refresh_token synchronously regardless of validity of current access_token + /// - Throws: TokenError + /// - Returns: FRUser object with newly renewed OAuth2 token + @objc + public func refreshSync() throws -> FRUser { + let oldToken = self.token + if let frAuth = FRAuth.shared, let tokenManager = frAuth.tokenManager { + let token = try tokenManager.refreshSync() + self.revokeGivenAccessToken(oldToken) + self.token = token + self.save() + return self + } + else { + FRLog.w("Invalid SDK state") + throw ConfigError.invalidSDKState + } + } + // MARK: - UserInfo /// Retrieves currently authenticated user's UserInfo from /userinfo endpoint @@ -377,47 +422,6 @@ public class FRUser: NSObject, NSSecureCoding { } // MARK: - Private methods - - /// Refreshes user's session with refresh_token regardless of validity of current access_token - /// - /// - Parameter completion: Completion callback that notifies the result of operation - func refresh(completion:@escaping UserCallback) { - if let frAuth = FRAuth.shared, let tokenManager = frAuth.tokenManager { - tokenManager.refresh{ (token, error) in - if let token = token { - self.token = token - self.save() - completion(self, nil) - } - else { - completion(nil, error) - } - } - } - else { - FRLog.w("Invalid SDK state") - completion(nil, ConfigError.invalidSDKState) - } - } - - - /// Refreshes user's session with refresh_token synchronously regardless of validity of current access_token - /// - Throws: TokenError - /// - Returns: FRUser object with newly renewed OAuth2 token - func refreshSync() throws -> FRUser { - if let frAuth = FRAuth.shared, let tokenManager = frAuth.tokenManager { - let token = try tokenManager.refreshSync() - self.token = token - self.save() - return self - } - else { - FRLog.w("Invalid SDK state") - throw ConfigError.invalidSDKState - } - } - - /// Saves current FRUser instance to Keychain func save() { } @@ -454,4 +458,17 @@ public class FRUser: NSObject, NSSecureCoding { /// - Parameter aCoder: NSCoder public func encode(with aCoder: NSCoder) { } + + + /// Revoke given Access Token without using the Refresh Token + func revokeGivenAccessToken(_ token: AccessToken?) { + if let frAuth = FRAuth.shared, let tokenManager = frAuth.tokenManager, let token = token { + tokenManager.revokeToken(token) { + error in + if let error = error { + FRLog.e("Error revoking the old AccessToken: \(error.localizedDescription)") + } + } + } + } } diff --git a/FRAuth/FRAuthTests/FRAuthSwiftTests/FRAuth/Authorization/PolicyAdviceCreatorTests.swift b/FRAuth/FRAuthTests/FRAuthSwiftTests/FRAuth/Authorization/PolicyAdviceCreatorTests.swift new file mode 100644 index 00000000..f65c8828 --- /dev/null +++ b/FRAuth/FRAuthTests/FRAuthSwiftTests/FRAuth/Authorization/PolicyAdviceCreatorTests.swift @@ -0,0 +1,86 @@ +// +// PolicyAdviceCreatorTests.swift +// FRAuthTests +// +// Copyright (c) 2023 ForgeRock. All rights reserved. +// +// This software may be modified and distributed under the terms +// of the MIT license. See the LICENSE file for details. +// + + +import XCTest +@testable import FRAuth + +final class PolicyAdviceCreatorTests: XCTestCase { + + func test_legacy_IG_response() { + + // Given + let adviceStr = """ + https://default.forgeops.petrov.ca/am/?goto=http://openig.petrov.ca/products?_txid%3Dcbac1559-ec42-4bdb-9d30-61c3abaf5a6b&realm=/&authIndexType=composite_advice&authIndexValue=%3CAdvices%3E%3CAttributeValuePair%3E%3CAttribute%20name%3D%22TransactionConditionAdvice%22/%3E%3CValue%3Ecbac1559-ec42-4bdb-9d30-61c3abaf5a6b%3C/Value%3E%3C/AttributeValuePair%3E%3C/Advices%3E + """ + + let policyAdviceCreator = PolicyAdviceCreator() + let result = policyAdviceCreator.parse(advice: adviceStr) + XCTAssertEqual(result?.authIndexType, "composite_advice") + XCTAssertEqual(result?.authIndexValue, "cbac1559-ec42-4bdb-9d30-61c3abaf5a6b") + XCTAssertEqual(result?.type, AdviceType.transactionCondition) + XCTAssertEqual(result?.value, "cbac1559-ec42-4bdb-9d30-61c3abaf5a6b") + + } + + func test_encoded_legacy_IG_response() { + + // Given + let adviceStr = """ + https://default.forgeops.petrov.ca/am/?goto=http://openig.petrov.ca/products?_txid%3Debfbbd31-36d7-486f-89fd-7bf7694d3e7e&realm=/&authIndexType=composite_advice&authIndexValue=PEFkdmljZXM-PEF0dHJpYnV0ZVZhbHVlUGFpcj48QXR0cmlidXRlIG5hbWU9IlRyYW5zYWN0aW9uQ29uZGl0aW9uQWR2aWNlIi8-PFZhbHVlPmViZmJiZDMxLTM2ZDctNDg2Zi04OWZkLTdiZjc2OTRkM2U3ZTwvVmFsdWU-PC9BdHRyaWJ1dGVWYWx1ZVBhaXI-PC9BZHZpY2VzPg + """ + + let policyAdviceCreator = PolicyAdviceCreator() + let result = policyAdviceCreator.parse(advice: adviceStr) + XCTAssertEqual(result?.authIndexType, "composite_advice") + XCTAssertEqual(result?.authIndexValue, "ebfbbd31-36d7-486f-89fd-7bf7694d3e7e") + XCTAssertEqual(result?.type, AdviceType.transactionCondition) + XCTAssertEqual(result?.value, "ebfbbd31-36d7-486f-89fd-7bf7694d3e7e") + + } + + + func test_invalid_legacy_IG_response() { + + // Given + let adviceStr = """ + https://default.forgeops.petrov.ca/am/?goto=http://openig.petrov.ca/products?_txid%3Dcbac1559-ec42-4bdb-9d30-61c3abaf5a6b&realm=/&authIndexType=composite_advice&authIndexValue=%3CAdvices%3E%3CAttributeValuePair%3E%3CAttribute%20name%3D%/Value%3E%3C/AttributeValuePair%3E%3C/Advices%3E + """ + + let policyAdviceCreator = PolicyAdviceCreator() + let result = policyAdviceCreator.parse(advice: adviceStr) + XCTAssertEqual(result, nil) + } + + func test_new_JSON_IG_response() { + + // Given + let adviceStr = "SSOADVICE realm=\"/\",advices=\"eyJUcmFuc2FjdGlvbkNvbmRpdGlvbkFkdmljZSI6WyI1ODY2OWUxOS00MjVhLTQzMzMtOTFkOC03MDk5NWFmMDY5MjciXX0=\",am_uri=\"https://default.forgeops.petrov.ca/am/\"" + + let policyAdviceCreator = PolicyAdviceCreator() + let result = policyAdviceCreator.parseAsBase64(advice: adviceStr) + XCTAssertEqual(result?.authIndexType, "composite_advice") + XCTAssertEqual(result?.authIndexValue, "58669e19-425a-4333-91d8-70995af06927") + XCTAssertEqual(result?.type, AdviceType.transactionCondition) + XCTAssertEqual(result?.value, "58669e19-425a-4333-91d8-70995af06927") + + } + + func test_invalid_JSON_IG_response() { + + // Given + let adviceStr = "SSOADVICE realm=\"/\",advices=\"hLTQzMzMtOTFkOC03MDk5NWFmMDY5MjciXX0=\",am_uri=\"https://default.forgeops.petrov.ca/am/\"" + + let policyAdviceCreator = PolicyAdviceCreator() + let result = policyAdviceCreator.parseAsBase64(advice: adviceStr) + XCTAssertEqual(result, nil) + } + +} diff --git a/FRAuth/FRAuthTests/FRAuthSwiftTests/FRAuth/Authorization/PolicyAdviceTests.swift b/FRAuth/FRAuthTests/FRAuthSwiftTests/FRAuth/Authorization/PolicyAdviceTests.swift index 44524488..c5e561a4 100644 --- a/FRAuth/FRAuthTests/FRAuthSwiftTests/FRAuth/Authorization/PolicyAdviceTests.swift +++ b/FRAuth/FRAuthTests/FRAuthSwiftTests/FRAuth/Authorization/PolicyAdviceTests.swift @@ -2,7 +2,7 @@ // PolicyAdviceTests.swift // FRAuthTests // -// Copyright (c) 2020 ForgeRock. All rights reserved. +// Copyright (c) 2020 - 2023 ForgeRock. All rights reserved. // // This software may be modified and distributed under the terms // of the MIT license. See the LICENSE file for details. @@ -32,6 +32,18 @@ class PolicyAdviceTests: FRAuthBaseTest { XCTAssertNil(advice) } + func test_01_policy_advice_init_with_passed_dict() { + + // Given + + var adviceJSON: [String: Any] = [:] + adviceJSON["TransactionConditionAdvice"] = ["562fd2ef-c494-4d5d-9375-7f17aef817d3"] + + // Then + let advice = PolicyAdvice(json: adviceJSON) + XCTAssertNotNil(advice) + } + func test_02_policy_advice_init_with_json_authenticate_to_service() { // Given @@ -98,6 +110,20 @@ class PolicyAdviceTests: FRAuthBaseTest { XCTAssertEqual(advice?.type, AdviceType.transactionCondition) XCTAssertEqual(advice?.authIndexType, OpenAM.compositeAdvice) XCTAssertEqual(advice?.authIndexValue, "5afff42a-2715-40c8-98e7-919abc1b2dfc") + } + + func test_04_policy_advice_init_with_params_with_indexTypes() { + // Given + let indexValue = "5afff42a-2715-40c8-98e7-919abc1b2dfc" + var advice = PolicyAdvice(type: "TransactionConditionAdvice", value: "5afff42a-2715-40c8-98e7-919abc1b2dfc", authIndexType: "other_advice", authIndexValue: indexValue) + // Then + XCTAssertNotNil(advice) + XCTAssertNotNil(advice?.txId) + XCTAssertEqual(advice?.txId, "5afff42a-2715-40c8-98e7-919abc1b2dfc") + XCTAssertEqual(advice?.value, "5afff42a-2715-40c8-98e7-919abc1b2dfc") + XCTAssertEqual(advice?.type, AdviceType.transactionCondition) + XCTAssertEqual(advice?.authIndexType, "other_advice") + XCTAssertEqual(advice?.authIndexValue, indexValue) // Given diff --git a/FRAuth/FRAuthTests/FRAuthSwiftTests/FRAuth/Model/User/FRUserTests.swift b/FRAuth/FRAuthTests/FRAuthSwiftTests/FRAuth/Model/User/FRUserTests.swift index 79956db7..0390948f 100644 --- a/FRAuth/FRAuthTests/FRAuthSwiftTests/FRAuth/Model/User/FRUserTests.swift +++ b/FRAuth/FRAuthTests/FRAuthSwiftTests/FRAuth/Model/User/FRUserTests.swift @@ -2,7 +2,7 @@ // FRUserTests.swift // FRAuthTests // -// Copyright (c) 2019-2021 ForgeRock. All rights reserved. +// Copyright (c) 2019-2023 ForgeRock. All rights reserved. // // This software may be modified and distributed under the terms // of the MIT license. See the LICENSE file for details. @@ -710,4 +710,89 @@ class FRUserTests: FRAuthBaseTest { } waitForExpectations(timeout: 60, handler: nil) } + + func test_06_01_ForceRefreshAccessTokenAsync() { + // Perform login first + self.performLogin() + + guard let user = FRUser.currentUser else { + XCTFail("Failed to perform user login") + return + } + + // Load mock responses for refresh token + self.loadMockResponses(["OAuth2_Token_Refresh_Success"]) + + // Validate if FRUser.currentUser is not nil + XCTAssertNotNil(FRUser.currentUser) + XCTAssertNotNil(user.token) + let oldAccessToken = user.token?.value + XCTAssertNotNil(oldAccessToken) + + let ex = self.expectation(description: "Refresh AccessToken failure") + user.refresh { updatedUser, error in + XCTAssertNil(error) + XCTAssertNotNil(updatedUser) + XCTAssertNotNil(updatedUser?.token) + XCTAssertFalse(oldAccessToken == updatedUser?.token?.value) + ex.fulfill() + } + + waitForExpectations(timeout: 60, handler: nil) + } + + func test_06_02_ForceRefreshAccessTokenSync() { + // Perform login first + self.performLogin() + + guard let user = FRUser.currentUser else { + XCTFail("Failed to perform user login") + return + } + + // Load mock responses for refresh token + self.loadMockResponses(["OAuth2_Token_Refresh_Success"]) + + // Validate if FRUser.currentUser is not nil + XCTAssertNotNil(FRUser.currentUser) + XCTAssertNotNil(user.token) + let oldAccessToken = user.token?.value + XCTAssertNotNil(oldAccessToken) + let updatedUser: FRUser? + do { + updatedUser = try user.refreshSync() + XCTAssertNotNil(updatedUser) + XCTAssertNotNil(updatedUser?.token) + XCTAssertFalse(oldAccessToken == updatedUser?.token?.value) + } catch { + XCTFail("Refresh AccessToken failure") + } + } + + func test_07_01_RevokeGivenAccessToken() { + // Perform login first + self.performLogin() + + guard let user = FRUser.currentUser else { + XCTFail("Failed to perform user login") + return + } + + // Load mock responses for refresh token + self.loadMockResponses(["OAuth2_Token_Revoke_Success"]) + + // Validate if FRUser.currentUser is not nil + XCTAssertNotNil(FRUser.currentUser) + XCTAssertNotNil(user.token) + + let ex = self.expectation(description: "Revoke AccessToken failure") + if let frAuth = FRAuth.shared, let tokenManager = frAuth.tokenManager, let token = user.token { + tokenManager.revokeToken(token) { + error in + XCTAssertNil(error) + ex.fulfill() + } + } + waitForExpectations(timeout: 60, handler: nil) + } } diff --git a/FRAuth/FRAuthTests/FRAuthSwiftTests/FRAuth/Policy/AuthorizationPolicyTests.swift b/FRAuth/FRAuthTests/FRAuthSwiftTests/FRAuth/Policy/AuthorizationPolicyTests.swift index cedcbcbd..e9f75167 100644 --- a/FRAuth/FRAuthTests/FRAuthSwiftTests/FRAuth/Policy/AuthorizationPolicyTests.swift +++ b/FRAuth/FRAuthTests/FRAuthSwiftTests/FRAuth/Policy/AuthorizationPolicyTests.swift @@ -2,7 +2,7 @@ // AuthorizationPolicyTests.swift // FRAuthTests // -// Copyright (c) 2020 ForgeRock. All rights reserved. +// Copyright (c) 2020 - 2023 ForgeRock. All rights reserved. // // This software may be modified and distributed under the terms // of the MIT license. See the LICENSE file for details. @@ -285,6 +285,33 @@ class AuthorizationPolicyTests: FRAuthBaseTest { XCTAssertEqual(self.list.count, 1) XCTAssertEqual(self.list.first, "AuthorizationPolicyTests.evaluateAuthorizationPolicy") } + + func test_16_redirect_evaluation_with_307_status_with_base64_encoded() { + let policy = AuthorizationPolicy(validatingURL: [URL(string: "https://openam.example.com/anything")!], delegate: self) + let header: [String: String] = ["Location": "https://default.forgeops.petrov.ca/am/?goto=http://openig.petrov.ca/products?_txid%3Debfbbd31-36d7-486f-89fd-7bf7694d3e7e&realm=/&authIndexType=composite_advice&authIndexValue=PEFkdmljZXM-PEF0dHJpYnV0ZVZhbHVlUGFpcj48QXR0cmlidXRlIG5hbWU9IlRyYW5zYWN0aW9uQ29uZGl0aW9uQWR2aWNlIi8-PFZhbHVlPmViZmJiZDMxLTM2ZDctNDg2Zi04OWZkLTdiZjc2OTRkM2U3ZTwvVmFsdWU-PC9BdHRyaWJ1dGVWYWx1ZVBhaXI-PC9BZHZpY2VzPg"] + let response = HTTPURLResponse(url: URL(string: "https://openam.example.com/anything")!, statusCode: 307, httpVersion: nil, headerFields: header)! + let advice = policy.evaluateAuthorizationPolicyWithRedirect(responseData: nil, session: URLSession(), task: URLSessionTask(), willPerformHTTPRedirection: response, newRequest: URLRequest(url: URL(string: "https://www.forgerock.com")!)) + XCTAssertNotNil(advice) + XCTAssertEqual(self.list.count, 0) + } + + func test_17_redirect_evaluation_with_307_status_with_JSON() { + let policy = AuthorizationPolicy(validatingURL: [URL(string: "https://openam.example.com/anything")!], delegate: self) + let header: [String: String] = ["Location": "https://default.forgeops.petrov.ca/am/?goto=http://openig.petrov.ca/products?_txid%3Debfbbd31-36d7-486f-89fd-7bf7694d3e7e&realm=/&authIndexType=composite_advice&authIndexValue=PEFkdmljZXM-PEF0dHJpYnV0ZVZhbHVlUGFpcj48QXR0cmlidXRlIG5hbWU9IlRyYW5zYWN0aW9uQ29uZGl0aW9uQWR2aWNlIi8-PFZhbHVlPmViZmJiZDMxLTM2ZDctNDg2Zi04OWZkLTdiZjc2OTRkM2U3ZTwvVmFsdWU-PC9BdHRyaWJ1dGVWYWx1ZVBhaXI-PC9BZHZpY2VzPg"] + let response = HTTPURLResponse(url: URL(string: "https://openam.example.com/anything")!, statusCode: 307, httpVersion: nil, headerFields: header)! + let advice = policy.evaluateAuthorizationPolicyWithRedirect(responseData: nil, session: URLSession(), task: URLSessionTask(), willPerformHTTPRedirection: response, newRequest: URLRequest(url: URL(string: "https://www.forgerock.com")!)) + XCTAssertNotNil(advice) + XCTAssertEqual(self.list.count, 0) + } + + func test_18_redirect_evaluation_with_307_status_with_base64_encoded() { + let policy = AuthorizationPolicy(validatingURL: [URL(string: "https://openam.example.com/anything")!], delegate: self) + let header: [String: String] = ["Www-Authenticate": "SSOADVICE realm=\"/\",advices=\"eyJUcmFuc2FjdGlvbkNvbmRpdGlvbkFkdmljZSI6WyI1ODY2OWUxOS00MjVhLTQzMzMtOTFkOC03MDk5NWFmMDY5MjciXX0=\",am_uri=\"https://default.forgeops.petrov.ca/am/\""] + let response = HTTPURLResponse(url: URL(string: "https://openam.example.com/anything")!, statusCode: 401, httpVersion: nil, headerFields: header)! + let advice = policy.evaluateAuthorizationPolicy(responseData: nil, response: response, error: nil) + XCTAssertNotNil(advice) + XCTAssertEqual(self.list.count, 0) + } } diff --git a/FRAuth/FRAuthTests/FRAuthSwiftTests/FRAuth/RequestInterceptor/FRRequestInterceptorTests.swift b/FRAuth/FRAuthTests/FRAuthSwiftTests/FRAuth/RequestInterceptor/FRRequestInterceptorTests.swift index 47ac5a2c..f6a0d64d 100644 --- a/FRAuth/FRAuthTests/FRAuthSwiftTests/FRAuth/RequestInterceptor/FRRequestInterceptorTests.swift +++ b/FRAuth/FRAuthTests/FRAuthSwiftTests/FRAuth/RequestInterceptor/FRRequestInterceptorTests.swift @@ -2,7 +2,7 @@ // FRRequestInterceptorTests.swift // FRAuthTests // -// Copyright (c) 2020-2022 ForgeRock. All rights reserved. +// Copyright (c) 2020-2023 ForgeRock. All rights reserved. // // This software may be modified and distributed under the terms // of the MIT license. See the LICENSE file for details. @@ -57,8 +57,8 @@ class FRRequestInterceptorTests: FRAuthBaseTest { waitForExpectations(timeout: 60, handler: nil) XCTAssertEqual(FRRequestInterceptorTests.payload.count, 0) - XCTAssertEqual(FRRequestInterceptorTests.intercepted.count, 1) - let interceptorsInOrder: [String] = ["REFRESH_TOKEN"] + XCTAssertEqual(FRRequestInterceptorTests.intercepted.count, 2) + let interceptorsInOrder: [String] = ["REFRESH_TOKEN", "REVOKE_TOKEN"] for (index, intercepted) in FRRequestInterceptorTests.intercepted.enumerated() { XCTAssertEqual(interceptorsInOrder[index], intercepted) } diff --git a/FRAuthenticator.podspec b/FRAuthenticator.podspec index cb9bef06..a6118b07 100644 --- a/FRAuthenticator.podspec +++ b/FRAuthenticator.podspec @@ -8,7 +8,7 @@ Pod::Spec.new do |s| s.name = 'FRAuthenticator' - s.version = '4.0.0' + s.version = '4.1.0' s.summary = 'ForgeRock OTP/Push Authentication SDK for iOS' s.description = <<-DESC FRAuthenticator is a SDK that allows you easily and quickly develop an application with ForgeRock Platform for OATH and Push Authentication with AM. FRAuthenticator SDK provides interfaces and functionalities of HMAC-based OTP, Time-based OTP, Push Registration and Authentication with AM. @@ -29,5 +29,5 @@ Pod::Spec.new do |s| base_dir = "FRAuthenticator/FRAuthenticator" s.source_files = base_dir + '/**/*.swift', base_dir + '/**/*.c', base_dir + '/**/*.h' - s.ios.dependency 'FRCore', '~> 4.0.0' + s.ios.dependency 'FRCore', '~> 4.1.0' end diff --git a/FRAuthenticator/FRAuthenticator.xcodeproj/project.pbxproj b/FRAuthenticator/FRAuthenticator.xcodeproj/project.pbxproj index 57b6a2bd..c87d1532 100644 --- a/FRAuthenticator/FRAuthenticator.xcodeproj/project.pbxproj +++ b/FRAuthenticator/FRAuthenticator.xcodeproj/project.pbxproj @@ -18,6 +18,7 @@ 1BB34B44299704A300729300 /* AccountError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1BB34B43299704A300729300 /* AccountError.swift */; }; 1BB34B46299AD30100729300 /* InvalidFakePolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1BB34B45299AD30100729300 /* InvalidFakePolicy.swift */; }; 1BB34B48299AE08300729300 /* URIType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1BB34B47299AE08300729300 /* URIType.swift */; }; + A56A46EF2A4A5601000C1055 /* RequestInterceptorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A56A46EE2A4A5601000C1055 /* RequestInterceptorTests.swift */; }; A5EAD7D228356B5F000637F0 /* BiometricAuthentication.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5EAD7D128356B5F000637F0 /* BiometricAuthentication.swift */; }; D5230BE02457868B004AB6E9 /* FRAConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5230BDF2457868B004AB6E9 /* FRAConstants.swift */; }; D525BE01256765190012CE33 /* FRCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D525BDFC2567650B0012CE33 /* FRCore.framework */; }; @@ -201,6 +202,7 @@ 1BB34B43299704A300729300 /* AccountError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountError.swift; sourceTree = ""; }; 1BB34B45299AD30100729300 /* InvalidFakePolicy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InvalidFakePolicy.swift; sourceTree = ""; }; 1BB34B47299AE08300729300 /* URIType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URIType.swift; sourceTree = ""; }; + A56A46EE2A4A5601000C1055 /* RequestInterceptorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestInterceptorTests.swift; sourceTree = ""; }; A5EAD7D128356B5F000637F0 /* BiometricAuthentication.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BiometricAuthentication.swift; sourceTree = ""; }; D5230BDF2457868B004AB6E9 /* FRAConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FRAConstants.swift; sourceTree = ""; }; D525BDF62567650B0012CE33 /* FRCore.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = FRCore.xcodeproj; path = ../FRCore/FRCore.xcodeproj; sourceTree = ""; }; @@ -357,6 +359,14 @@ path = Policy; sourceTree = ""; }; + A56A46EC2A4A55EB000C1055 /* RequestInterceptor */ = { + isa = PBXGroup; + children = ( + A56A46EE2A4A5601000C1055 /* RequestInterceptorTests.swift */, + ); + path = RequestInterceptor; + sourceTree = ""; + }; A5EAD7D428367D12000637F0 /* Biometric */ = { isa = PBXGroup; children = ( @@ -442,6 +452,7 @@ D52D5C67241333D500835035 /* Push */, D542AA572416F9F800ECBFC1 /* Storage */, D52D5C66241333D500835035 /* Util */, + A56A46EC2A4A55EB000C1055 /* RequestInterceptor */, ); path = UnitTests; sourceTree = ""; @@ -1146,6 +1157,7 @@ D5791C0225F8829F004B487A /* OathMechanismTests.swift in Sources */, D5791C0325F8829F004B487A /* HOTPCodeGenerationTests.swift in Sources */, D5791C0425F8829F004B487A /* TOTPCodeGenerationTests.swift in Sources */, + A56A46EF2A4A5601000C1055 /* RequestInterceptorTests.swift in Sources */, D5791C0525F8829F004B487A /* OathAlgorithmTests.swift in Sources */, D5791C0625F8829F004B487A /* OathTokenCodeTests.swift in Sources */, D5791C0725F8829F004B487A /* FRAPushHandlerTests.swift in Sources */, @@ -1320,7 +1332,7 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - MARKETING_VERSION = "4.0.0"; + MARKETING_VERSION = 4.1.0; MODULEMAP_FILE = "${PROJECT_DIR}/FRAuthenticator/SharedC/FRAuthenticator.modulemap"; OTHER_CFLAGS = "-DXCODE_FRAMEWORK=1"; PRODUCT_BUNDLE_IDENTIFIER = com.forgerock.ios.FRAuthenticator; @@ -1352,7 +1364,7 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - MARKETING_VERSION = "4.0.0"; + MARKETING_VERSION = 4.1.0; MODULEMAP_FILE = "${PROJECT_DIR}/FRAuthenticator/SharedC/FRAuthenticator.modulemap"; OTHER_CFLAGS = "-DXCODE_FRAMEWORK=1"; PRODUCT_BUNDLE_IDENTIFIER = com.forgerock.ios.FRAuthenticator; diff --git a/FRAuthenticator/FRAuthenticator/Manager/AuthenticatorManager.swift b/FRAuthenticator/FRAuthenticator/Manager/AuthenticatorManager.swift index 71e45965..63f7fd24 100644 --- a/FRAuthenticator/FRAuthenticator/Manager/AuthenticatorManager.swift +++ b/FRAuthenticator/FRAuthenticator/Manager/AuthenticatorManager.swift @@ -46,6 +46,20 @@ struct AuthenticatorManager { let uriType = uri.getURIType() let authType = uri.getAuthType() if uriType == .mfauth { + FRALog.v("Validating stored Mechanisms for duplication") + do { + let parser = try PushQRCodeParser(url: uri) + let account = Account(issuer: parser.issuer, accountName: parser.label, imageUrl: parser.image, backgroundColor: parser.backgroundColor, policies: parser.policies) + if let thisMechanism = self.storageClient.getMechanismsForAccount(account: account).first { + FRALog.e("Found a Mechanism under the same account") + onError(MechanismError.alreadyExists(thisMechanism.identifier)) + return + } + } + catch { + onError(error) + } + FRALog.v("Evaluating policies for the new Account") let result = self.policyEvaluator.evaluate(uri: uri) if let policy = result.nonCompliancePolicy, !result.comply { diff --git a/FRAuthenticator/FRAuthenticator/Model/Mechanism/OATH/OathQRCodeParser.swift b/FRAuthenticator/FRAuthenticator/Model/Mechanism/OATH/OathQRCodeParser.swift index 1487485e..7f1ff55a 100644 --- a/FRAuthenticator/FRAuthenticator/Model/Mechanism/OATH/OathQRCodeParser.swift +++ b/FRAuthenticator/FRAuthenticator/Model/Mechanism/OATH/OathQRCodeParser.swift @@ -120,7 +120,11 @@ struct OathQRCodeParser { self.backgroundColor = item.value } if item.name == "issuer", let strVal = item.value { - self.issuer = strVal + if self.scheme == URIType.mfauth.rawValue, let decodedVal = strVal.base64Decoded() { + self.issuer = decodedVal + } else { + self.issuer = strVal + } } if item.name == "policies", let strVal = item.value { self.policies = strVal.base64Decoded() diff --git a/FRAuthenticator/FRAuthenticator/Model/Mechanism/Push/PushMechanism.swift b/FRAuthenticator/FRAuthenticator/Model/Mechanism/Push/PushMechanism.swift index 5341e95b..e9b82dae 100644 --- a/FRAuthenticator/FRAuthenticator/Model/Mechanism/Push/PushMechanism.swift +++ b/FRAuthenticator/FRAuthenticator/Model/Mechanism/Push/PushMechanism.swift @@ -2,7 +2,7 @@ // PushMechanism.swift // FRAuthenticator // -// Copyright (c) 2020-2021 ForgeRock. All rights reserved. +// Copyright (c) 2020-2023 ForgeRock. All rights reserved. // // This software may be modified and distributed under the terms // of the MIT license. See the LICENSE file for details. @@ -182,7 +182,7 @@ public class PushMechanism: Mechanism { func register(onSuccess: @escaping SuccessCallback, onFailure: @escaping ErrorCallback) { do { let request = try buildPushRegistrationRequest() - RestClient.shared.invoke(request: request) { (result) in + RestClient.shared.invoke(request: request, action: Action(type: .PUSH_REGISTER)) { (result) in switch result { case .success(let result, let httpResponse): FRALog.v("Push registration request was successful: \n\nResponse:\(result)\n\nHTTPResponse:\(String(describing: httpResponse))") diff --git a/FRAuthenticator/FRAuthenticator/Model/Notification/PushNotification.swift b/FRAuthenticator/FRAuthenticator/Model/Notification/PushNotification.swift index 83fa100c..ea65ebf1 100644 --- a/FRAuthenticator/FRAuthenticator/Model/Notification/PushNotification.swift +++ b/FRAuthenticator/FRAuthenticator/Model/Notification/PushNotification.swift @@ -369,7 +369,7 @@ public class PushNotification: NSObject, NSSecureCoding, Codable { do { let request = try buildPushAuthenticationRequest(challengeResponse: challengeResponse, approved: approved, mechanism: mechanism) - RestClient.shared.invoke(request: request) { (result) in + RestClient.shared.invoke(request: request, action: Action(type: .PUSH_AUTHENTICATE)) { (result) in switch result { case .success(_, _): self.approved = approved diff --git a/FRAuthenticator/FRAuthenticatorTests/E2ETests/FRAClient/FRAClientTests.swift b/FRAuthenticator/FRAuthenticatorTests/E2ETests/FRAClient/FRAClientTests.swift index 839b5855..2afd0fe0 100644 --- a/FRAuthenticator/FRAuthenticatorTests/E2ETests/FRAClient/FRAClientTests.swift +++ b/FRAuthenticator/FRAuthenticatorTests/E2ETests/FRAClient/FRAClientTests.swift @@ -153,7 +153,6 @@ class FRAClientTests: FRABaseTests { } } - func test_04_account_removal_from_previous_test() { self.shouldCleanup = true @@ -1061,9 +1060,158 @@ class FRAClientTests: FRABaseTests { } } + func test_13_store_combined_mechanisms_with_same_oath_account_and_fail() { + + self.shouldCleanup = true + + // Given + let totp = URL(string: "otpauth://totp/ForgeRock:demo1?secret=T7SIIEPTZJQQDSCB&issuer=ForgeRock&digits=6&period=30")! + + do { + self.loadMockResponses(["AM_Push_Registration_Successful", "AM_Push_Authentication_Successful", "AM_Push_Authentication_Successful", "AM_Push_Authentication_Successful"]) + // Set DeviceToken before PushMechnaism registration + let deviceTokenStr = "PJ6d7k8uM2AvK+T1jJTMBYD5so+SrHnvVLoGz2Mte3A=" + guard let deviceToken = deviceTokenStr.decodeBase64() else { + XCTFail("Failed to parse device token data") + return + } + FRAPushHandler.shared.application(UIApplication.shared, didRegisterForRemoteNotificationsWithDeviceToken: deviceToken) + try FRAClient.setStorage(storage: DummyStorageClient()) + FRAClient.start() + + // Store OATH Mechanism + var ex = self.expectation(description: "FRAClient.createMechanismFromUri") + FRAClient.shared?.createMechanismFromUri(uri: totp, onSuccess: { (mechanism) in + ex.fulfill() + }, onError: { (error) in + XCTFail("FRAClient.createMechanismFromUri failed with unexpected reason: \(error.localizedDescription)") + ex.fulfill() + }) + waitForExpectations(timeout: 60, handler: nil) + + XCTAssertEqual(FRAClient.shared?.getAllAccounts().count, 1) + XCTAssertNotNil(FRAClient.shared?.getAccount(identifier: "ForgeRock-demo1")) + XCTAssertEqual(FRAClient.shared?.getAccount(identifier: "ForgeRock-demo1")?.mechanisms.count, 1) + + // Store Combined Mechanism under same Account + let diff = URL(string: "mfauth://totp/ForgeRock:demo1?" + + "a=aHR0cHM6Ly9mb3JnZXJvY2suZXhhbXBsZS5jb20vb3BlbmFtL2pzb24vcHVzaC9zbnMvbWVzc2FnZT9fYWN0aW9uPWF1dGhlbnRpY2F0ZQ&" + + "image=aHR0cDovL3NlYXR0bGV3cml0ZXIuY29tL3dwLWNvbnRlbnQvdXBsb2Fkcy8yMDEzLzAxL3dlaWdodC13YXRjaGVycy1zbWFsbC5naWY&" + + "b=ff00ff&" + + "r=aHR0cHM6Ly9mb3JnZXJvY2suZXhhbXBsZS5jb20vb3BlbmFtL2pzb24vcHVzaC9zbnMvbWVzc2FnZT9fYWN0aW9uPXJlZ2lzdGVy&" + + "s=ryJkqNRjXYd_nX523672AX_oKdVXrKExq-VjVeRKKTc&" + + "c=Daf8vrc8onKu-dcptwCRS9UHmdui5u16vAdG2HMU4w0&" + + "l=YW1sYmNvb2tpZT0wMQ==&" + + "m=9326d19c-4d08-4538-8151-f8558e71475f1464361288472&" + + "digits=6&" + + "secret=R2PYFZRISXA5L25NVSSYK2RQ6E======&" + + "period=30&" + + "issuer=Rm9yZ2VSb2Nr")! + + ex = self.expectation(description: "FRAClient.createMechanismFromUri") + FRAClient.shared?.createMechanismFromUri(uri: diff, onSuccess: { (mechanism) in + XCTFail("FRAClient.createMechanismFromUri was expected to fail for duplication; but somehow passed") + ex.fulfill() + }, onError: { (error) in + switch error { + case MechanismError.alreadyExists(let message): + XCTAssertEqual(message, "ForgeRock-demo1-totp") + break + default: + XCTFail("FRAClient.createMechanismFromUri failed with unexpected reason: \(error.localizedDescription)") + break + } + ex.fulfill() + }) + waitForExpectations(timeout: 60, handler: nil) + + XCTAssertEqual(FRAClient.shared?.getAllAccounts().count, 1) + XCTAssertNotNil(FRAClient.shared?.getAccount(identifier: "ForgeRock-demo1")) + XCTAssertEqual(FRAClient.shared?.getAccount(identifier: "ForgeRock-demo1")?.mechanisms.count, 1) + } + catch { + XCTFail("Unexpected failure on SDK init: \(error.localizedDescription)") + } + } + + func test_14_store_combined_mechanisms_with_same_push_account_and_fail() { + self.shouldCleanup = true + + // Given + let push = URL(string: "pushauth://push/ForgeRockSandbox:pushtestuser?a=aHR0cDovL29wZW5hbS5leGFtcGxlLmNvbTo4MDgxL29wZW5hbS9qc29uL3B1c2gvc25zL21lc3NhZ2U_X2FjdGlvbj1hdXRoZW50aWNhdGU&b=519387&r=aHR0cDovL29wZW5hbS5leGFtcGxlLmNvbTo4MDgxL29wZW5hbS9qc29uL3B1c2gvc25zL21lc3NhZ2U_X2FjdGlvbj1yZWdpc3Rlcg&s=-3xGWaKjfls_ZHFRnGeIvFHn--GxzjQyg1RVG_Pak1s&c=esDK4G8eYce0_Gdf4p9XGGg2cIYYoxf6CTlL_O_1aF8&l=YW1sYmNvb2tpZT0wMQ&m=REGISTER:593b6a92-f5c1-4ac0-a94a-a63e05451dd51589138620791&issuer=Rm9yZ2VSb2NrU2FuZGJveA")! + + do { + self.loadMockResponses(["AM_Push_Registration_Successful", "AM_Push_Authentication_Successful"]) + // Set DeviceToken before PushMechnaism registration + let deviceTokenStr = "PJ6d7k8uM2AvK+T1jJTMBYD5so+SrHnvVLoGz2Mte3A=" + guard let deviceToken = deviceTokenStr.decodeBase64() else { + XCTFail("Failed to parse device token data") + return + } + FRAPushHandler.shared.application(UIApplication.shared, didRegisterForRemoteNotificationsWithDeviceToken: deviceToken) + try FRAClient.setStorage(storage: DummyStorageClient()) + FRAClient.start() + + // Store first Mechanism + var ex = self.expectation(description: "FRAClient.createMechanismFromUri") + FRAClient.shared?.createMechanismFromUri(uri: push, onSuccess: { (mechanism) in + ex.fulfill() + }, onError: { (error) in + XCTFail("FRAClient.createMechanismFromUri failed with unexpected reason: \(error.localizedDescription)") + ex.fulfill() + }) + waitForExpectations(timeout: 60, handler: nil) + + XCTAssertEqual(FRAClient.shared?.getAllAccounts().count, 1) + XCTAssertNotNil(FRAClient.shared?.getAccount(identifier: "ForgeRockSandbox-pushtestuser")) + XCTAssertEqual(FRAClient.shared?.getAccount(identifier: "ForgeRockSandbox-pushtestuser")?.mechanisms.count, 1) + + // Store Combined Mechanism under same Account + let diff = URL(string: "mfauth://totp/ForgeRockSandbox:pushtestuser?" + + "a=aHR0cHM6Ly9mb3JnZXJvY2suZXhhbXBsZS5jb20vb3BlbmFtL2pzb24vcHVzaC9zbnMvbWVzc2FnZT9fYWN0aW9uPWF1dGhlbnRpY2F0ZQ&" + + "image=aHR0cDovL3NlYXR0bGV3cml0ZXIuY29tL3dwLWNvbnRlbnQvdXBsb2Fkcy8yMDEzLzAxL3dlaWdodC13YXRjaGVycy1zbWFsbC5naWY&" + + "b=ff00ff&" + + "r=aHR0cHM6Ly9mb3JnZXJvY2suZXhhbXBsZS5jb20vb3BlbmFtL2pzb24vcHVzaC9zbnMvbWVzc2FnZT9fYWN0aW9uPXJlZ2lzdGVy&" + + "s=ryJkqNRjXYd_nX523672AX_oKdVXrKExq-VjVeRKKTc&" + + "c=Daf8vrc8onKu-dcptwCRS9UHmdui5u16vAdG2HMU4w0&" + + "l=YW1sYmNvb2tpZT0wMQ==&" + + "m=9326d19c-4d08-4538-8151-f8558e71475f1464361288472&" + + "digits=6&" + + "secret=R2PYFZRISXA5L25NVSSYK2RQ6E======&" + + "period=30&" + + "issuer=Rm9yZ2VSb2NrU2FuZGJveA")! + + ex = self.expectation(description: "FRAClient.createMechanismFromUri") + FRAClient.shared?.createMechanismFromUri(uri: diff, onSuccess: { (mechanism) in + XCTFail("FRAClient.createMechanismFromUri was expected to fail for duplication; but somehow passed") + ex.fulfill() + }, onError: { (error) in + switch error { + case MechanismError.alreadyExists(let message): + XCTAssertEqual(message, "ForgeRockSandbox-pushtestuser-push") + break + default: + XCTFail("FRAClient.createMechanismFromUri failed with unexpected reason: \(error.localizedDescription)") + break + } + ex.fulfill() + }) + waitForExpectations(timeout: 60, handler: nil) + + XCTAssertEqual(FRAClient.shared?.getAllAccounts().count, 1) + XCTAssertNotNil(FRAClient.shared?.getAccount(identifier: "ForgeRockSandbox-pushtestuser")) + XCTAssertEqual(FRAClient.shared?.getAccount(identifier: "ForgeRockSandbox-pushtestuser")?.mechanisms.count, 1) + + } + catch { + XCTFail("Unexpected failure on SDK init: \(error.localizedDescription)") + } + } } + + // MARK: - PolicyEvaluator func test_11_policy_evaluator_changed() { diff --git a/FRAuthenticator/FRAuthenticatorTests/UnitTests/Model/HOTPMechanismTests.swift b/FRAuthenticator/FRAuthenticatorTests/UnitTests/Model/HOTPMechanismTests.swift index 17f6fdfc..5992ff2c 100644 --- a/FRAuthenticator/FRAuthenticatorTests/UnitTests/Model/HOTPMechanismTests.swift +++ b/FRAuthenticator/FRAuthenticatorTests/UnitTests/Model/HOTPMechanismTests.swift @@ -297,7 +297,8 @@ class HOTPMechanismTests: FRABaseTests { "policies=eyJiaW9tZXRyaWNBdmFpbGFibGUiOiB7IH0sImRldmljZVRhbXBlcmluZyI6IHsic2NvcmUiOiAwLjh9fQ&" + "digits=6&" + "secret=R2PYFZRISXA5L25NVSSYK2RQ6E======&" + - "counter=10&")! + "counter=10&" + + "issuer=Rm9yZ2Vyb2Nr")! do { let parser = try OathQRCodeParser(url: qrCode) diff --git a/FRAuthenticator/FRAuthenticatorTests/UnitTests/Model/OathMechanismTests.swift b/FRAuthenticator/FRAuthenticatorTests/UnitTests/Model/OathMechanismTests.swift index 279c56ad..aca38c28 100644 --- a/FRAuthenticator/FRAuthenticatorTests/UnitTests/Model/OathMechanismTests.swift +++ b/FRAuthenticator/FRAuthenticatorTests/UnitTests/Model/OathMechanismTests.swift @@ -98,7 +98,8 @@ class OathMechanismTests: FRABaseTests { "policies=eyJiaW9tZXRyaWNBdmFpbGFibGUiOiB7IH0sImRldmljZVRhbXBlcmluZyI6IHsic2NvcmUiOiAwLjh9fQ&" + "digits=6&" + "secret=R2PYFZRISXA5L25NVSSYK2RQ6E======&" + - "period=30&")! + "period=30&" + + "issuer=Rm9yZ2Vyb2Nr")! do { let parser = try OathQRCodeParser(url: qrCode) diff --git a/FRAuthenticator/FRAuthenticatorTests/UnitTests/Model/PushMechanismTests.swift b/FRAuthenticator/FRAuthenticatorTests/UnitTests/Model/PushMechanismTests.swift index fd247e39..bb4eb7d0 100644 --- a/FRAuthenticator/FRAuthenticatorTests/UnitTests/Model/PushMechanismTests.swift +++ b/FRAuthenticator/FRAuthenticatorTests/UnitTests/Model/PushMechanismTests.swift @@ -174,7 +174,8 @@ class PushMechanismTests: FRABaseTests { "policies=eyJiaW9tZXRyaWNBdmFpbGFibGUiOiB7IH0sImRldmljZVRhbXBlcmluZyI6IHsic2NvcmUiOiAwLjh9fQ&" + "digits=6&" + "secret=R2PYFZRISXA5L25NVSSYK2RQ6E======&" + - "period=30&")! + "period=30&" + + "issuer=Rm9yZ2Vyb2Nr")! do { let parser = try PushQRCodeParser(url: qrCode) diff --git a/FRAuthenticator/FRAuthenticatorTests/UnitTests/Model/TOTPMechanismTests.swift b/FRAuthenticator/FRAuthenticatorTests/UnitTests/Model/TOTPMechanismTests.swift index e2892eb9..e0c0f833 100644 --- a/FRAuthenticator/FRAuthenticatorTests/UnitTests/Model/TOTPMechanismTests.swift +++ b/FRAuthenticator/FRAuthenticatorTests/UnitTests/Model/TOTPMechanismTests.swift @@ -301,7 +301,8 @@ class TOTPMechanismTests: FRABaseTests { "policies=eyJiaW9tZXRyaWNBdmFpbGFibGUiOiB7IH0sImRldmljZVRhbXBlcmluZyI6IHsic2NvcmUiOiAwLjh9fQ&" + "digits=6&" + "secret=R2PYFZRISXA5L25NVSSYK2RQ6E======&" + - "period=30&")! + "period=30&" + + "issuer=Rm9yZ2Vyb2Nr")! do { let parser = try OathQRCodeParser(url: qrCode) diff --git a/FRAuthenticator/FRAuthenticatorTests/UnitTests/RequestInterceptor/RequestInterceptorTests.swift b/FRAuthenticator/FRAuthenticatorTests/UnitTests/RequestInterceptor/RequestInterceptorTests.swift new file mode 100644 index 00000000..d49f8090 --- /dev/null +++ b/FRAuthenticator/FRAuthenticatorTests/UnitTests/RequestInterceptor/RequestInterceptorTests.swift @@ -0,0 +1,158 @@ +// +// RequestInterceptorTests.swift +// FRAuthenticatorTests +// +// Copyright (c) 2023 ForgeRock. All rights reserved. +// +// This software may be modified and distributed under the terms +// of the MIT license. See the LICENSE file for details. +// + + +import XCTest +@testable import FRCore +@testable import FRAuthenticator + +class RequestInterceptorTests: FRABaseTests { + + override func setUp() { + super.setUp() + RequestInterceptorTests.intercepted = [] + } + + override func tearDown() { + RequestInterceptorTests.intercepted = [] + super.tearDown() + } + + static var intercepted: [String] = [] + + + func test_01_push_registration() { + // Register RequestInterceptors + RequestInterceptorRegistry.shared.registerInterceptors(interceptors: [PushRequestInterceptor()]) + + self.loadMockResponses(["AM_Push_Registration_Successful"]) + + // Set DeviceToken before PushMechnaism registration + let deviceTokenStr = "PJ6d7k8uM2AvK+T1jJTMBYD5so+SrHnvVLoGz2Mte3A=" + guard let deviceToken = deviceTokenStr.decodeBase64() else { + XCTFail("Failed to parse device token data") + return + } + FRAPushHandler.shared.application(UIApplication.shared, didRegisterForRemoteNotificationsWithDeviceToken: deviceToken) + + // Given + let qrCode = URL(string: "pushauth://push/forgerock:pushreg3?a=aHR0cDovL29wZW5hbS5leGFtcGxlLmNvbTo4MDgxL29wZW5hbS9qc29uL3B1c2gvc25zL21lc3NhZ2U_X2FjdGlvbj1hdXRoZW50aWNhdGU&b=519387&r=aHR0cDovL29wZW5hbS5leGFtcGxlLmNvbTo4MDgxL29wZW5hbS9qc29uL3B1c2gvc25zL21lc3NhZ2U_X2FjdGlvbj1yZWdpc3Rlcg&s=5GuioYhLlh-xER3n5I8vrx0uuYQo3yD86aJi6KuWDsg&c=KP0XQfZ21N_jsXP_xfVQMmsmoUiWvdDPWecHdb5_INQ&l=YW1sYmNvb2tpZT0wMQ&m=REGISTER:a8970dea-3257-4be1-a37a-23eed2b692131588282723889&issuer=Rm9yZ2VSb2NrU2FuZGJveA")! + + do { + // Then + let parser = try PushQRCodeParser(url: qrCode) + let mechanism = PushMechanism(issuer: parser.issuer, accountName: parser.label, secret: parser.secret, authEndpoint: parser.authenticationEndpoint, regEndpoint: parser.registrationEndpoint, messageId: parser.messageId, challenge: parser.challenge, loadBalancer: parser.loadBalancer) + let ex = self.expectation(description: "Register PushMechanism") + mechanism.register(onSuccess: { + ex.fulfill() + }) { (error) in + XCTFail("Failed to register PushMechanism with following error: \(error.localizedDescription)") + ex.fulfill() + } + waitForExpectations(timeout: 60, handler: nil) + + XCTAssertEqual(RequestInterceptorTests.intercepted.count, 1) + let interceptorsInOrder: [String] = ["PUSH_REGISTER"] + for (index, intercepted) in RequestInterceptorTests.intercepted.enumerated() { + XCTAssertEqual(interceptorsInOrder[index], intercepted) + } + + guard let urlRequest = FRTestNetworkStubProtocol.requestHistory.last else { + XCTFail("Failed to retrieve URLRequest from URLRequestProtocol") + return + } + + guard let requestHeader = urlRequest.value(forHTTPHeaderField: "testHeader") else { + XCTFail("Failed to parse URL from URLRequest") + return + } + XCTAssertEqual(requestHeader, "PUSH_REGISTER") + } + catch { + XCTFail("Fail to create PushMechanism with given QRCode: \(qrCode.absoluteString)") + } + } + + func test_02_push_authentication() { + // Register RequestInterceptors + RequestInterceptorRegistry.shared.registerInterceptors(interceptors: [PushRequestInterceptor()]) + + self.loadMockResponses(["AM_Push_Authentication_Successful"]) + + let qrCode = URL(string: "pushauth://push/forgerock:pushdemouser1?a=aHR0cDovL29wZW5hbS5leGFtcGxlLmNvbTo4MDgxL29wZW5hbS9qc29uL3B1c2gvc25zL21lc3NhZ2U_X2FjdGlvbj1hdXRoZW50aWNhdGU&b=519387&r=aHR0cDovL29wZW5hbS5leGFtcGxlLmNvbTo4MDgxL29wZW5hbS9qc29uL3B1c2gvc25zL21lc3NhZ2U_X2FjdGlvbj1yZWdpc3Rlcg&s=O9JHEGfOsaZqc5JT0DHM5hYFA8jofohw5vAP0EpG4JU&c=75OQ3FXmzV99TPf0ihevFfB0s43XsxQ747sY6BopgME&l=YW1sYmNvb2tpZT0wMQ&m=REGISTER:fe6311ab-013e-4599-9c0e-4c4e2525199b1588721418483&issuer=Rm9yZ2VSb2NrU2FuZGJveA")! + + do { + let parser = try PushQRCodeParser(url: qrCode) + let mechanism = PushMechanism(issuer: parser.issuer, accountName: parser.label, secret: parser.secret, authEndpoint: parser.authenticationEndpoint, regEndpoint: parser.registrationEndpoint, messageId: parser.messageId, challenge: parser.challenge, loadBalancer: parser.loadBalancer) + mechanism.mechanismUUID = "32E28B44-153C-4BDE-9FDB-38069BC23D9C" + FRAClient.storage.setMechanism(mechanism: mechanism) + + let messageId = "AUTHENTICATE:8af40ee6-8fa0-4bdd-949c-1dd29d5e55931588721432364" + var notificationPayload: [String: String] = [:] + notificationPayload["c"] = "6ggPLysKJ6wSwBsQFtPclHQKebpOTMNwHP53kZxIGE4=" + notificationPayload["t"] = "120" + notificationPayload["u"] = "32E28B44-153C-4BDE-9FDB-38069BC23D9C" + notificationPayload["l"] = "YW1sYmNvb2tpZT0wMQ==" + notificationPayload["k"] = "challenge" + notificationPayload["n"] = "34,56,82" + + let ex = self.expectation(description: "PushNotification Authentication") + let notification = try PushNotification(messageId: messageId, payload: notificationPayload) + notification.handleNotification(challengeResponse: "56", approved: true, onSuccess: { + // Expected to be successful + ex.fulfill() + }) { (error) in + XCTFail("Push authentication failed while expecting to be successful") + ex.fulfill() + } + waitForExpectations(timeout: 60, handler: nil) + + XCTAssertEqual(RequestInterceptorTests.intercepted.count, 1) + let interceptorsInOrder: [String] = ["PUSH_AUTHENTICATE"] + for (index, intercepted) in RequestInterceptorTests.intercepted.enumerated() { + XCTAssertEqual(interceptorsInOrder[index], intercepted) + } + + guard let urlRequest = FRTestNetworkStubProtocol.requestHistory.last else { + XCTFail("Failed to retrieve URLRequest from URLRequestProtocol") + return + } + + guard let requestHeader = urlRequest.value(forHTTPHeaderField: "testHeader") else { + XCTFail("Failed to parse URL from URLRequest") + return + } + XCTAssertEqual(requestHeader, "PUSH_AUTHENTICATE") + } + catch { + XCTFail("Push authentication failed to prepare auth request") + } + } +} + +class PushRequestInterceptor: RequestInterceptor { + func intercept(request: Request, action: Action) -> Request { + var headers = request.headers + + if action.type == "PUSH_REGISTER" { + RequestInterceptorTests.intercepted.append("PUSH_REGISTER") + headers["testHeader"] = "PUSH_REGISTER" + } + else if action.type == "PUSH_AUTHENTICATE" { + RequestInterceptorTests.intercepted.append("PUSH_AUTHENTICATE") + headers["testHeader"] = "PUSH_AUTHENTICATE" + } + + let newRequest = Request(url: request.url, method: request.method, headers: headers, bodyParams: request.bodyParams, urlParams: request.urlParams, requestType: request.requestType, responseType: request.responseType, timeoutInterval: request.timeoutInterval) + + return newRequest + } +} + diff --git a/FRAuthenticator/FRAuthenticatorTests/UnitTests/Util/OathQRCodeParserTests.swift b/FRAuthenticator/FRAuthenticatorTests/UnitTests/Util/OathQRCodeParserTests.swift index 847d98ad..d72a596c 100644 --- a/FRAuthenticator/FRAuthenticatorTests/UnitTests/Util/OathQRCodeParserTests.swift +++ b/FRAuthenticator/FRAuthenticatorTests/UnitTests/Util/OathQRCodeParserTests.swift @@ -394,7 +394,8 @@ class OathQRCodeParserTests: FRABaseTests { "policies=eyJiaW9tZXRyaWNBdmFpbGFibGUiOiB7IH0sImRldmljZVRhbXBlcmluZyI6IHsic2NvcmUiOiAwLjh9fQ&" + "digits=6&" + "secret=R2PYFZRISXA5L25NVSSYK2RQ6E======&" + - "period=30&")! + "period=30&" + + "issuer=Rm9yZ2Vyb2Nr")! let jsonPolicies = """ {"biometricAvailable": { },"deviceTampering": {"score": 0.8}} diff --git a/FRAuthenticator/FRAuthenticatorTests/UnitTests/Util/PushQRCodeParserTests.swift b/FRAuthenticator/FRAuthenticatorTests/UnitTests/Util/PushQRCodeParserTests.swift index c845ac7e..d6cfe6c9 100644 --- a/FRAuthenticator/FRAuthenticatorTests/UnitTests/Util/PushQRCodeParserTests.swift +++ b/FRAuthenticator/FRAuthenticatorTests/UnitTests/Util/PushQRCodeParserTests.swift @@ -137,7 +137,8 @@ class PushQRCodeParserTests: FRABaseTests { "policies=eyJiaW9tZXRyaWNBdmFpbGFibGUiOiB7IH0sImRldmljZVRhbXBlcmluZyI6IHsic2NvcmUiOiAwLjh9fQ&" + "digits=6&" + "secret=R2PYFZRISXA5L25NVSSYK2RQ6E======&" + - "period=30&")! + "period=30&" + + "issuer=Rm9yZ2Vyb2Nr")! let jsonPolicies = """ {"biometricAvailable": { },"deviceTampering": {"score": 0.8}} diff --git a/FRCore.podspec b/FRCore.podspec index 7df69eec..0485adb1 100644 --- a/FRCore.podspec +++ b/FRCore.podspec @@ -8,7 +8,7 @@ Pod::Spec.new do |s| s.name = 'FRCore' - s.version = '4.0.0' + s.version = '4.1.0' s.summary = 'ForgeRock Core SDK for iOS' s.description = <<-DESC FRCore is a SDK that allows you to consume some of core functionalities and security features built for FRAuth SDK. diff --git a/FRCore/FRCore.xcodeproj/project.pbxproj b/FRCore/FRCore.xcodeproj/project.pbxproj index e0d1c886..ca3a3f66 100644 --- a/FRCore/FRCore.xcodeproj/project.pbxproj +++ b/FRCore/FRCore.xcodeproj/project.pbxproj @@ -921,7 +921,7 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - MARKETING_VERSION = "4.0.0"; + MARKETING_VERSION = 4.1.0; MODULEMAP_FILE = "${PROJECT_DIR}/FRCore/SharedC/FRCore.modulemap"; OTHER_CFLAGS = "-DXCODE_FRAMEWORK=1"; PRODUCT_BUNDLE_IDENTIFIER = com.forgerock.ios.FRCore; @@ -955,7 +955,7 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - MARKETING_VERSION = "4.0.0"; + MARKETING_VERSION = 4.1.0; MODULEMAP_FILE = "${PROJECT_DIR}/FRCore/SharedC/FRCore.modulemap"; OTHER_CFLAGS = "-DXCODE_FRAMEWORK=1"; PRODUCT_BUNDLE_IDENTIFIER = com.forgerock.ios.FRCore; diff --git a/FRCore/FRCore/Log/Log.swift b/FRCore/FRCore/Log/Log.swift index 7f34e56c..df231b62 100644 --- a/FRCore/FRCore/Log/Log.swift +++ b/FRCore/FRCore/Log/Log.swift @@ -129,7 +129,7 @@ public class Log: NSObject { // MARK: - Property /// Current SDK version. We hard code it here as currently there is no other way to get it dinamically when used with SPM - public static let sdkVersion = "4.0.0" + public static let sdkVersion = "4.1.0" /// Current LogLevel static var logLevel: LogLevel = .none /// Current Loggers to handle log entries diff --git a/FRDeviceBinding.podspec b/FRDeviceBinding.podspec index c1c39598..ef89f21f 100644 --- a/FRDeviceBinding.podspec +++ b/FRDeviceBinding.podspec @@ -8,7 +8,7 @@ Pod::Spec.new do |s| s.name = 'FRDeviceBinding' - s.version = '4.0.0' + s.version = '4.1.0' s.summary = 'ForgeRock Device Binding SDK for iOS' s.description = <<-DESC FRDeviceBinding is a SDK that adds support for the Device Binding feature. FRDeviceBinding depends on JOSESwift. @@ -29,6 +29,6 @@ Pod::Spec.new do |s| base_dir = "FRDeviceBinding/FRDeviceBinding" s.source_files = base_dir + '/**/*.swift', base_dir + '/**/*.c', base_dir + '/**/*.h' - s.ios.dependency 'FRAuth', '~> 4.0.0' + s.ios.dependency 'FRAuth', '~> 4.1.0' s.ios.dependency 'JOSESwift', '~> 2.4.0' end diff --git a/FRDeviceBinding/FRDeviceBinding.xcodeproj/project.pbxproj b/FRDeviceBinding/FRDeviceBinding.xcodeproj/project.pbxproj index 18e399fb..5b5ce23f 100644 --- a/FRDeviceBinding/FRDeviceBinding.xcodeproj/project.pbxproj +++ b/FRDeviceBinding/FRDeviceBinding.xcodeproj/project.pbxproj @@ -815,7 +815,7 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - MARKETING_VERSION = 4.0.0; + MARKETING_VERSION = 4.1.0; MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++20"; PRODUCT_BUNDLE_IDENTIFIER = com.forgerock.ios.FRDeviceBinding; @@ -848,7 +848,7 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - MARKETING_VERSION = 4.0.0; + MARKETING_VERSION = 4.1.0; MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++20"; PRODUCT_BUNDLE_IDENTIFIER = com.forgerock.ios.FRDeviceBinding; diff --git a/FRFacebookSignIn.podspec b/FRFacebookSignIn.podspec index b4b7dc4b..8e567c23 100644 --- a/FRFacebookSignIn.podspec +++ b/FRFacebookSignIn.podspec @@ -8,7 +8,7 @@ Pod::Spec.new do |s| s.name = 'FRFacebookSignIn' - s.version = '4.0.0' + s.version = '4.1.0' s.summary = 'ForgeRock Auth Facebook Sign-in SDK for iOS' s.description = <<-DESC FRFacebookSignIn is a SDK that allows a user to sign-in through Facebook. FRFacebookSignIn depends on FBSDKLoginKit, and uses Facebook's SDK to perform authorization following Facebook's protocol. @@ -29,6 +29,6 @@ Pod::Spec.new do |s| base_dir = "FRFacebookSignIn/FRFacebookSignIn" s.source_files = base_dir + '/**/*.swift', base_dir + '/**/*.c', base_dir + '/**/*.h' - s.ios.dependency 'FRAuth', '~> 4.0.0' + s.ios.dependency 'FRAuth', '~> 4.1.0' s.ios.dependency 'FBSDKLoginKit', '~> 16.0.1' end diff --git a/FRFacebookSignIn/FRFacebookSignIn.xcodeproj/project.pbxproj b/FRFacebookSignIn/FRFacebookSignIn.xcodeproj/project.pbxproj index 355fd748..c134361d 100644 --- a/FRFacebookSignIn/FRFacebookSignIn.xcodeproj/project.pbxproj +++ b/FRFacebookSignIn/FRFacebookSignIn.xcodeproj/project.pbxproj @@ -440,7 +440,7 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - MARKETING_VERSION = "4.0.0"; + MARKETING_VERSION = 4.1.0; PRODUCT_BUNDLE_IDENTIFIER = com.forgerock.ios.FRFacebookSignIn; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; @@ -470,7 +470,7 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - MARKETING_VERSION = "4.0.0"; + MARKETING_VERSION = 4.1.0; PRODUCT_BUNDLE_IDENTIFIER = com.forgerock.ios.FRFacebookSignIn; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; diff --git a/FRGoogleSignIn.podspec b/FRGoogleSignIn.podspec index f4a9980e..b23e7e0b 100644 --- a/FRGoogleSignIn.podspec +++ b/FRGoogleSignIn.podspec @@ -8,7 +8,7 @@ Pod::Spec.new do |s| s.name = 'FRGoogleSignIn' - s.version = '4.0.0' + s.version = '4.1.0' s.summary = 'ForgeRock Auth Google Sign-in SDK for iOS' s.description = <<-DESC FRGoogleSignIn is a SDK that allows a user to sign-in through Google. FRGoogleSignIn depends on GoogleSignIn, and uses Google's SDK to perform authorization following Google's protocol. @@ -31,7 +31,7 @@ Pod::Spec.new do |s| base_dir = "FRGoogleSignIn/FRGoogleSignIn" s.source_files = base_dir + '/**/*.swift', base_dir + '/**/*.c', base_dir + '/**/*.h' - s.ios.dependency 'FRAuth', '~> 4.0.0' + s.ios.dependency 'FRAuth', '~> 4.1.0' s.ios.dependency 'GoogleSignIn', '~> 7.0.0' s.pod_target_xcconfig = { 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'arm64' } s.user_target_xcconfig = { 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'arm64' } diff --git a/FRGoogleSignIn/FRGoogleSignIn.xcodeproj/project.pbxproj b/FRGoogleSignIn/FRGoogleSignIn.xcodeproj/project.pbxproj index 6cd7e23d..6f738819 100644 --- a/FRGoogleSignIn/FRGoogleSignIn.xcodeproj/project.pbxproj +++ b/FRGoogleSignIn/FRGoogleSignIn.xcodeproj/project.pbxproj @@ -451,7 +451,7 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - MARKETING_VERSION = "4.0.0"; + MARKETING_VERSION = 4.1.0; OTHER_LDFLAGS = "-ObjC"; PRODUCT_BUNDLE_IDENTIFIER = com.forgerock.ios.FRGoogleSignIn; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; @@ -484,7 +484,7 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - MARKETING_VERSION = "4.0.0"; + MARKETING_VERSION = 4.1.0; ONLY_ACTIVE_ARCH = YES; OTHER_LDFLAGS = "-ObjC"; PRODUCT_BUNDLE_IDENTIFIER = com.forgerock.ios.FRGoogleSignIn; diff --git a/FRProximity.podspec b/FRProximity.podspec index 039374dd..209272f8 100644 --- a/FRProximity.podspec +++ b/FRProximity.podspec @@ -8,7 +8,7 @@ Pod::Spec.new do |s| s.name = 'FRProximity' - s.version = '4.0.0' + s.version = '4.1.0' s.summary = 'ForgeRock Auth Proximity SDK for iOS' s.description = <<-DESC FRProximity is a SDK that allows you to additionally collect device information with FRDeviceCollector in FRAuth. FRProximity SDK leverages functionalities in iOS that requires user's consent. You must properly set privacy consent in the application's Info.plist. @@ -29,5 +29,5 @@ Pod::Spec.new do |s| base_dir = "FRProximity/FRProximity" s.source_files = base_dir + '/**/*.swift', base_dir + '/**/*.c', base_dir + '/**/*.h' - s.ios.dependency 'FRAuth', '~> 4.0.0' + s.ios.dependency 'FRAuth', '~> 4.1.0' end diff --git a/FRProximity/FRProximity.xcodeproj/project.pbxproj b/FRProximity/FRProximity.xcodeproj/project.pbxproj index 9ac44b6f..90d9e77f 100644 --- a/FRProximity/FRProximity.xcodeproj/project.pbxproj +++ b/FRProximity/FRProximity.xcodeproj/project.pbxproj @@ -895,7 +895,7 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - MARKETING_VERSION = "4.0.0"; + MARKETING_VERSION = 4.1.0; PRODUCT_BUNDLE_IDENTIFIER = com.forgerock.ios.FRProximity; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -930,7 +930,7 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - MARKETING_VERSION = "4.0.0"; + MARKETING_VERSION = 4.1.0; PRODUCT_BUNDLE_IDENTIFIER = com.forgerock.ios.FRProximity; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; PROVISIONING_PROFILE_SPECIFIER = ""; diff --git a/FRTestHost/FRTestHost/Info.plist b/FRTestHost/FRTestHost/Info.plist index 349caf8c..afb42011 100644 --- a/FRTestHost/FRTestHost/Info.plist +++ b/FRTestHost/FRTestHost/Info.plist @@ -2,10 +2,8 @@ - NSLocationAlwaysAndWhenInUseUsageDescription - FRTestHost app uses location information for some test case - NSLocationWhenInUseUsageDescription - FRTestHost app uses location information for some test case + NSLocationAlwaysUsageDescription + FRTestHost app uses location information for some test case NSBluetoothAlwaysUsageDescription FRTestHost app uses BLE to collect device information NSFaceIDUsageDescription diff --git a/FRUI.podspec b/FRUI.podspec index c178bbf1..830cf605 100644 --- a/FRUI.podspec +++ b/FRUI.podspec @@ -8,7 +8,7 @@ Pod::Spec.new do |s| s.name = 'FRUI' - s.version = '4.0.0' + s.version = '4.1.0' s.summary = 'ForgeRock UI SDK for FRAuth iOS' s.description = <<-DESC FRUI is a SDK that allows you easily and quickly develop an application with ForgeRock Platform or ForgeRock Identity Cloud, and FRAuth SDK with pre-built UI components. FRUI SDK demonstrates most of functionalities available in FRAuth SDK which includes user authentication, registration, and identity and access management against ForgeRock solutions. @@ -30,5 +30,5 @@ Pod::Spec.new do |s| base_dir = "FRUI/FRUI" s.source_files = base_dir + '/**/*.swift', base_dir + '/**/*.c', base_dir + '/**/*.h' s.resources = [base_dir + '/**/*.xib', base_dir + '/Assets/*'] - s.ios.dependency 'FRDeviceBinding', '~> 4.0.0' + s.ios.dependency 'FRDeviceBinding', '~> 4.1.0' end diff --git a/FRUI/FRUI.xcodeproj/project.pbxproj b/FRUI/FRUI.xcodeproj/project.pbxproj index 06419357..eeff9b38 100644 --- a/FRUI/FRUI.xcodeproj/project.pbxproj +++ b/FRUI/FRUI.xcodeproj/project.pbxproj @@ -8,7 +8,6 @@ /* Begin PBXBuildFile section */ A5C38A2329E5AFB20016B565 /* FRDeviceBinding.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A5C38A2229E5AFB20016B565 /* FRDeviceBinding.framework */; }; - D51F44DB22B1B8CF00FC8F0D /* FRAuth.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D51F44D722B1B8C900FC8F0D /* FRAuth.framework */; }; D527D03124BCDB4E00C21663 /* FRUITextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D527D03024BCDB4E00C21663 /* FRUITextView.swift */; }; D539C8E822DE930800BE2B3C /* TermsAndConditionsTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D539C8E622DE930800BE2B3C /* TermsAndConditionsTableViewCell.swift */; }; D539C8E922DE930800BE2B3C /* TermsAndConditionsTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D539C8E722DE930800BE2B3C /* TermsAndConditionsTableViewCell.xib */; }; @@ -58,26 +57,19 @@ /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ - D51F44D622B1B8C900FC8F0D /* PBXContainerItemProxy */ = { + 953E5D372A424347003BC7B3 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; - containerPortal = D51F44D122B1B8C900FC8F0D /* FRAuth.xcodeproj */; + containerPortal = 953E5D312A424347003BC7B3 /* FRDeviceBinding.xcodeproj */; proxyType = 2; - remoteGlobalIDString = D55462F822A6DF720096C7A4; - remoteInfo = FRAuth; + remoteGlobalIDString = A5D1C37429E59FC20096039E; + remoteInfo = FRDeviceBinding; }; - D51F44D822B1B8C900FC8F0D /* PBXContainerItemProxy */ = { + 953E5D392A424347003BC7B3 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; - containerPortal = D51F44D122B1B8C900FC8F0D /* FRAuth.xcodeproj */; + containerPortal = 953E5D312A424347003BC7B3 /* FRDeviceBinding.xcodeproj */; proxyType = 2; - remoteGlobalIDString = D554630122A6DF720096C7A4; - remoteInfo = FRAuthTests; - }; - D51F44DC22B1B8D400FC8F0D /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = D51F44D122B1B8C900FC8F0D /* FRAuth.xcodeproj */; - proxyType = 1; - remoteGlobalIDString = D55462F722A6DF720096C7A4; - remoteInfo = FRAuth; + remoteGlobalIDString = A5D1C37C29E59FC30096039E; + remoteInfo = FRDeviceBindingTests; }; D53A91EC22B1815B003DF94E /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; @@ -89,8 +81,8 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 953E5D312A424347003BC7B3 /* FRDeviceBinding.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = FRDeviceBinding.xcodeproj; path = ../FRDeviceBinding/FRDeviceBinding.xcodeproj; sourceTree = ""; }; A5C38A2229E5AFB20016B565 /* FRDeviceBinding.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = FRDeviceBinding.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - D51F44D122B1B8C900FC8F0D /* FRAuth.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = FRAuth.xcodeproj; path = ../FRAuth/FRAuth.xcodeproj; sourceTree = ""; }; D527D03024BCDB4E00C21663 /* FRUITextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FRUITextView.swift; sourceTree = ""; }; D539C8E622DE930800BE2B3C /* TermsAndConditionsTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TermsAndConditionsTableViewCell.swift; sourceTree = ""; }; D539C8E722DE930800BE2B3C /* TermsAndConditionsTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = TermsAndConditionsTableViewCell.xib; sourceTree = ""; }; @@ -148,7 +140,6 @@ buildActionMask = 2147483647; files = ( A5C38A2329E5AFB20016B565 /* FRDeviceBinding.framework in Frameworks */, - D51F44DB22B1B8CF00FC8F0D /* FRAuth.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -163,11 +154,11 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - D51F44D222B1B8C900FC8F0D /* Products */ = { + 953E5D322A424347003BC7B3 /* Products */ = { isa = PBXGroup; children = ( - D51F44D722B1B8C900FC8F0D /* FRAuth.framework */, - D51F44D922B1B8C900FC8F0D /* FRAuthTests.xctest */, + 953E5D382A424347003BC7B3 /* FRDeviceBinding.framework */, + 953E5D3A2A424347003BC7B3 /* FRDeviceBindingTests.xctest */, ); name = Products; sourceTree = ""; @@ -183,7 +174,7 @@ D53A91D722B1815B003DF94E = { isa = PBXGroup; children = ( - D51F44D122B1B8C900FC8F0D /* FRAuth.xcodeproj */, + 953E5D312A424347003BC7B3 /* FRDeviceBinding.xcodeproj */, D53A91E322B1815B003DF94E /* FRUI */, D53A91EE22B1815B003DF94E /* FRUITests */, D53A91E222B1815B003DF94E /* Products */, @@ -367,7 +358,6 @@ buildRules = ( ); dependencies = ( - D51F44DD22B1B8D400FC8F0D /* PBXTargetDependency */, ); name = FRUI; productName = FRUI; @@ -423,8 +413,8 @@ projectDirPath = ""; projectReferences = ( { - ProductGroup = D51F44D222B1B8C900FC8F0D /* Products */; - ProjectRef = D51F44D122B1B8C900FC8F0D /* FRAuth.xcodeproj */; + ProductGroup = 953E5D322A424347003BC7B3 /* Products */; + ProjectRef = 953E5D312A424347003BC7B3 /* FRDeviceBinding.xcodeproj */; }, ); projectRoot = ""; @@ -436,18 +426,18 @@ /* End PBXProject section */ /* Begin PBXReferenceProxy section */ - D51F44D722B1B8C900FC8F0D /* FRAuth.framework */ = { + 953E5D382A424347003BC7B3 /* FRDeviceBinding.framework */ = { isa = PBXReferenceProxy; fileType = wrapper.framework; - path = FRAuth.framework; - remoteRef = D51F44D622B1B8C900FC8F0D /* PBXContainerItemProxy */; + path = FRDeviceBinding.framework; + remoteRef = 953E5D372A424347003BC7B3 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; - D51F44D922B1B8C900FC8F0D /* FRAuthTests.xctest */ = { + 953E5D3A2A424347003BC7B3 /* FRDeviceBindingTests.xctest */ = { isa = PBXReferenceProxy; fileType = wrapper.cfbundle; - path = FRAuthTests.xctest; - remoteRef = D51F44D822B1B8C900FC8F0D /* PBXContainerItemProxy */; + path = FRDeviceBindingTests.xctest; + remoteRef = 953E5D392A424347003BC7B3 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXReferenceProxy section */ @@ -531,11 +521,6 @@ /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ - D51F44DD22B1B8D400FC8F0D /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - name = FRAuth; - targetProxy = D51F44DC22B1B8D400FC8F0D /* PBXContainerItemProxy */; - }; D53A91ED22B1815B003DF94E /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = D53A91E022B1815B003DF94E /* FRUI */; @@ -688,7 +673,7 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - MARKETING_VERSION = "4.0.0"; + MARKETING_VERSION = 4.1.0; PRODUCT_BUNDLE_IDENTIFIER = com.forgerock.ios.FRUI; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -725,7 +710,7 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - MARKETING_VERSION = "4.0.0"; + MARKETING_VERSION = 4.1.0; PRODUCT_BUNDLE_IDENTIFIER = com.forgerock.ios.FRUI; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; PROVISIONING_PROFILE_SPECIFIER = ""; diff --git a/SampleApps/FRAuthenticatorExample/FRAuthenticatorExample/AppDelegate.swift b/SampleApps/FRAuthenticatorExample/FRAuthenticatorExample/AppDelegate.swift index 0056ee64..a7164aaa 100644 --- a/SampleApps/FRAuthenticatorExample/FRAuthenticatorExample/AppDelegate.swift +++ b/SampleApps/FRAuthenticatorExample/FRAuthenticatorExample/AppDelegate.swift @@ -2,7 +2,7 @@ // AppDelegate.swift // FRAuthenticatorExample // -// Copyright (c) 2020-2022 ForgeRock. All rights reserved. +// Copyright (c) 2020-2023 ForgeRock. All rights reserved. // // This software may be modified and distributed under the terms // of the MIT license. See the LICENSE file for details. @@ -21,6 +21,12 @@ class AppDelegate: UIResponder, UIApplicationDelegate { let center = UNUserNotificationCenter.current() center.requestAuthorization(options: [.alert, .sound, .badge]) { (granted, error) in } application.registerForRemoteNotifications() + + if let url = launchOptions?[UIApplication.LaunchOptionsKey.url] as? URL { + NSLog("App launched with deeplink: \(url.absoluteString)") + createMechanismFromUri(uri: url) + } + return true } @@ -74,6 +80,15 @@ class AppDelegate: UIResponder, UIApplicationDelegate { } } + + func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool { + + NSLog("App opened with deeplink: \(url.absoluteString)") + createMechanismFromUri(uri: url) + + return true + } + // MARK: - Helper @@ -103,5 +118,20 @@ class AppDelegate: UIResponder, UIApplicationDelegate { return UIImage(ciImage: outputImage) } + + + private func createMechanismFromUri(uri: URL) { + FRAClient.shared?.createMechanismFromUri(uri: uri, onSuccess: { mechanism in + NSLog("Successfully created a mechanism with deeplink: \(uri.absoluteString)"); + DispatchQueue.main.async { + if let window = self.window, + let rootViewController = window.rootViewController as? UINavigationController, + let mainListViewCointroller = rootViewController.viewControllers.first as? MainListViewController { + mainListViewCointroller.reload() + } + } + }, onError: { error in + NSLog("Error creating a mechanism with deeplink: \(uri.absoluteString)"); + }) + } } - diff --git a/SampleApps/FRAuthenticatorExample/FRAuthenticatorExample/Controller/MainListViewController.swift b/SampleApps/FRAuthenticatorExample/FRAuthenticatorExample/Controller/MainListViewController.swift index e028c3ca..ae615585 100644 --- a/SampleApps/FRAuthenticatorExample/FRAuthenticatorExample/Controller/MainListViewController.swift +++ b/SampleApps/FRAuthenticatorExample/FRAuthenticatorExample/Controller/MainListViewController.swift @@ -9,6 +9,7 @@ // import UIKit +import FRCore import FRAuthenticator class MainListViewController: BaseTableViewController { @@ -30,6 +31,10 @@ class MainListViewController: BaseTableViewController { DispatchQueue.main.async { self.reload() } + + // - MARK: PushRequestInterceptor example + /// Uncomment the next line to test the http request interceptor... + //RequestInterceptorRegistry.shared.registerInterceptors(interceptors: [PushRequestInterceptor()]) } @@ -233,3 +238,25 @@ extension MainListViewController: QRCodeScannerDelegate { self.displayAlert(title: "Error", message: error.localizedDescription) } } + + +/// This is an example http interceptor for testing purposes (SDKS-2545) +class PushRequestInterceptor: RequestInterceptor { + func intercept(request: Request, action: Action) -> Request { + var headers = request.headers + + if action.type == "PUSH_REGISTER" { + NotificationRequestViewController.intercepted.append("PUSH_REGISTER") + headers["testHeader"] = "PUSH_REGISTER" + } + else if action.type == "PUSH_AUTHENTICATE" { + NotificationRequestViewController.intercepted.append("PUSH_AUTHENTICATE") + headers["testHeader"] = "PUSH_AUTHENTICATE" + } + + let newRequest = Request(url: request.url, method: request.method, headers: headers, bodyParams: request.bodyParams, urlParams: request.urlParams, requestType: request.requestType, responseType: request.responseType, timeoutInterval: request.timeoutInterval) + + return newRequest + } +} + diff --git a/SampleApps/FRAuthenticatorExample/FRAuthenticatorExample/Controller/NotificationRequestViewController.swift b/SampleApps/FRAuthenticatorExample/FRAuthenticatorExample/Controller/NotificationRequestViewController.swift index 3f28f536..239ad6ad 100644 --- a/SampleApps/FRAuthenticatorExample/FRAuthenticatorExample/Controller/NotificationRequestViewController.swift +++ b/SampleApps/FRAuthenticatorExample/FRAuthenticatorExample/Controller/NotificationRequestViewController.swift @@ -9,6 +9,7 @@ // import UIKit +import FRCore import FRAuthenticator import CoreLocation @@ -29,6 +30,7 @@ class NotificationRequestViewController: BaseViewController { @IBOutlet weak var numbersChallengeStackView: UIStackView! var notification: PushNotification? + static var intercepted: [String] = [] override func viewDidLoad() { super.viewDidLoad() diff --git a/SampleApps/FRAuthenticatorExample/FRAuthenticatorExample/Info.plist b/SampleApps/FRAuthenticatorExample/FRAuthenticatorExample/Info.plist index eeda2c3a..d54c53f3 100644 --- a/SampleApps/FRAuthenticatorExample/FRAuthenticatorExample/Info.plist +++ b/SampleApps/FRAuthenticatorExample/FRAuthenticatorExample/Info.plist @@ -1,49 +1,82 @@ - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - $(PRODUCT_BUNDLE_PACKAGE_TYPE) - CFBundleShortVersionString - 1.0 - CFBundleVersion - 1 - LSRequiresIPhoneOS - - NSCameraUsageDescription - Cameara access is required to scan QR Code - UIBackgroundModes - - remote-notification - - UILaunchStoryboardName - LaunchScreen - UIMainStoryboardFile - Main - UIRequiredDeviceCapabilities - - armv7 - - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - - UISupportedInterfaceOrientations~ipad - - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleURLTypes + + + CFBundleTypeRole + Editor + CFBundleURLName + com.forgerock.authenticator.otpauth + CFBundleURLSchemes + + otpauth + + + + CFBundleTypeRole + Editor + CFBundleURLName + com.forgerock.authenticator.pushauth + CFBundleURLSchemes + + pushauth + + + + CFBundleTypeRole + Editor + CFBundleURLName + com.forgerock.authenticator.mfauth + CFBundleURLSchemes + + mfauth + + + + CFBundleVersion + 1 + LSRequiresIPhoneOS + + NSCameraUsageDescription + Cameara access is required to scan QR Code + UIBackgroundModes + + remote-notification + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + diff --git a/SampleApps/FRExample.xcworkspace/xcshareddata/swiftpm/Package.resolved b/SampleApps/FRExample.xcworkspace/xcshareddata/swiftpm/Package.resolved index ed9a906a..c89b0fbf 100644 --- a/SampleApps/FRExample.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/SampleApps/FRExample.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -41,8 +41,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/google/GTMAppAuth.git", "state" : { - "revision" : "cee3c709307912d040bd1e06ca919875a92339c6", - "version" : "2.0.0" + "revision" : "6dee0cde8a1b223737a5159e55e6b4ec16bbbdd9", + "version" : "1.3.1" } }, { diff --git a/SampleApps/FRExample/FRExample.xcodeproj/project.pbxproj b/SampleApps/FRExample/FRExample.xcodeproj/project.pbxproj index 2c2695ed..8f2a39b1 100644 --- a/SampleApps/FRExample/FRExample.xcodeproj/project.pbxproj +++ b/SampleApps/FRExample/FRExample.xcodeproj/project.pbxproj @@ -780,7 +780,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = "4.0.0"; + MARKETING_VERSION = 4.1.0; PRODUCT_BUNDLE_IDENTIFIER = com.forgerock.ios.sdk.example.sso; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -803,7 +803,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = "4.0.0"; + MARKETING_VERSION = 4.1.0; PRODUCT_BUNDLE_IDENTIFIER = com.forgerock.ios.sdk.example.sso; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -946,7 +946,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = "4.0.0"; + MARKETING_VERSION = 4.1.0; PRODUCT_BUNDLE_IDENTIFIER = com.forgerock.ios.sdk.example; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -973,7 +973,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = "4.0.0"; + MARKETING_VERSION = 4.1.0; PRODUCT_BUNDLE_IDENTIFIER = com.forgerock.ios.sdk.example; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; diff --git a/SampleApps/FRExample/FRExample/Base.lproj/Main.storyboard b/SampleApps/FRExample/FRExample/Base.lproj/Main.storyboard index 5a2459ee..a5326f58 100644 --- a/SampleApps/FRExample/FRExample/Base.lproj/Main.storyboard +++ b/SampleApps/FRExample/FRExample/Base.lproj/Main.storyboard @@ -1,9 +1,9 @@ - + - + @@ -17,14 +17,14 @@ - + - + - - - - - + + - + @@ -92,7 +93,6 @@ - @@ -113,7 +113,7 @@ - + @@ -125,4 +125,9 @@ + + + + + diff --git a/SampleApps/FRExample/FRExample/ViewController.swift b/SampleApps/FRExample/FRExample/ViewController.swift index d663d019..faa8b350 100644 --- a/SampleApps/FRExample/FRExample/ViewController.swift +++ b/SampleApps/FRExample/FRExample/ViewController.swift @@ -142,6 +142,7 @@ class ViewController: UIViewController { "Collect Device Information", "JailbreakDetector.analyze()", "FRUser.getAccessToken()", + "FRUser.refresh()", "Login with UI (Accesstoken)", "FRSession.authenticate with UI (Token)", "FRSession.logout()", @@ -571,7 +572,23 @@ class ViewController: UIViewController { } } } - + + func refreshAccessToken() { + guard let user = FRUser.currentUser else { + // If no currently authenticated user is found, log error + self.displayLog("FRUser.currentUser does not exist") + return + } + + user.refresh(completion: { (user, error) in + if let tokenError = error { + self.displayLog(tokenError.localizedDescription) + } else { + self.displayLog("Access token refreshed (forcefully)!") + self.displayLog("\(String(describing: user))") + } + }) + } // MARK: - Helper: Logout / UserInfo / JailbreakDetector / Device Collector / Invoke API @@ -758,6 +775,8 @@ class ViewController: UIViewController { var request = URLRequest(url: url) + request.setValue("header", forHTTPHeaderField: "x-authenticate-response") + // TODO: - Change following code as needed for authorization policy, and PEP // Setting SSO Token in the request cookie is expected for Identity Gateway set-up, and where IG is acting as Policy Enforcement Points (PEP) request.setValue("\(cookieName)="+(FRSession.currentSession?.sessionToken?.value ?? ""), forHTTPHeaderField: "Cookie") @@ -829,26 +848,30 @@ class ViewController: UIViewController { self.getAccessTokenFromUser() break case 9: + // Force Refresh AccessToken + self.refreshAccessToken() + break + case 10: // Login for AccessToken self.performActionHelperWithUI(auth: frAuth, flowType: .authentication, expectedType: AccessToken.self) break - case 10: + case 11: // FRSession.authenticate with UI (Token) self.performSessionAuthenticate(handleWithUI: true) break - case 11: + case 12: // FRSession.logout FRSession.currentSession?.logout() break - case 12: + case 13: // Register a user for FRUser self.performActionHelperWithUI(auth: frAuth, flowType: .registration, expectedType: FRUser.self) break - case 13: + case 14: // Register a user for AccessToken self.performActionHelperWithUI(auth: frAuth, flowType: .registration, expectedType: AccessToken.self) break - case 14: + case 15: // Login for FRUser without UI self.performActionHelper(auth: frAuth, flowType: .authentication, expectedType: FRUser.self) break @@ -856,23 +879,23 @@ class ViewController: UIViewController { // Login for AccessToken without UI self.performActionHelper(auth: frAuth, flowType: .authentication, expectedType: AccessToken.self) break - case 16: + case 17: // FRSession.authenticate without UI (Token) self.performSessionAuthenticate(handleWithUI: false) break - case 17: + case 18: // Display current Configuration self.displayCurrentConfig() break - case 18: + case 19: // Revoke Access Token self.revokeAccessToken() break - case 19: + case 20: // List WebAuthn Credentials by rpId self.listWebAuthnCredentialsByRpId() break - case 20: + case 21: // List device binding user keys self.listUserKeys() break