From 5444c37db7c05bdcdcd833bb55ee9a1aa1e5c091 Mon Sep 17 00:00:00 2001 From: Rodrigo Reis Date: Tue, 20 Feb 2024 10:37:01 -0800 Subject: [PATCH 1/3] SDKS-546 TextInputCallback Support --- FRAuth/FRAuth.xcodeproj/project.pbxproj | 12 +- FRAuth/FRAuth/Callbacks/CallbackFactory.swift | 5 +- .../FRAuth/Callbacks/TextInputCallback.swift | 51 +++++ .../FRAuth/Constants/CallbackConstants.swift | 3 +- .../Callback/CallbackConstantsTests.swift | 6 +- .../Callback/TextInputCallbackTests.swift | 205 ++++++++++++++++++ .../Model/CallbackTableViewCellFactory.swift | 4 +- .../FRExample/FRExample/ViewController.swift | 11 +- .../FRexampleObjC/ViewController.m | 3 +- 9 files changed, 292 insertions(+), 8 deletions(-) create mode 100644 FRAuth/FRAuth/Callbacks/TextInputCallback.swift create mode 100644 FRAuth/FRAuthTests/FRAuthSwiftTests/FRAuth/Callback/TextInputCallbackTests.swift diff --git a/FRAuth/FRAuth.xcodeproj/project.pbxproj b/FRAuth/FRAuth.xcodeproj/project.pbxproj index 4201de17..df84d07b 100644 --- a/FRAuth/FRAuth.xcodeproj/project.pbxproj +++ b/FRAuth/FRAuth.xcodeproj/project.pbxproj @@ -7,6 +7,8 @@ objects = { /* Begin PBXBuildFile section */ + 1B6037E62B505924009090DF /* TextInputCallback.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B6037E52B505924009090DF /* TextInputCallback.swift */; }; + 1B85DA132B85208C0023E953 /* TextInputCallbackTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B85DA122B85208C0023E953 /* TextInputCallbackTests.swift */; }; 3A1B43D0284510B700EAFC9D /* AtomicDictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A1B43CF284510B700EAFC9D /* AtomicDictionary.swift */; }; 3A1B43D3284524B900EAFC9D /* AtomicDictionaryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A1B43D2284524B900EAFC9D /* AtomicDictionaryTests.swift */; }; 3A3E785B2AAF6BE8007962B7 /* FRAppIntegrityCallback.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A3E785A2AAF6BE8007962B7 /* FRAppIntegrityCallback.swift */; }; @@ -19,10 +21,10 @@ 3A53E4DA2A153AF200E17DDF /* PolicyAdviceCreatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A53E4D92A153AF200E17DDF /* PolicyAdviceCreatorTests.swift */; }; 3A67B8832AD83946003331C5 /* FRAppIntegrityKeys.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A67B8822AD83946003331C5 /* FRAppIntegrityKeys.swift */; }; 3A6D26672A1345400099D877 /* PolicyAdviceCreator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A6D26662A1345400099D877 /* PolicyAdviceCreator.swift */; }; + 3AB062FA2AE6224D00C4B47C /* FRAppIntegrityKeysTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AB062F92AE6224D00C4B47C /* FRAppIntegrityKeysTests.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 */; }; A53723D12AE80A5D0047B809 /* TelephonyCollectorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A53723D02AE80A5D0047B809 /* TelephonyCollectorTests.swift */; }; - 3AB062FA2AE6224D00C4B47C /* FRAppIntegrityKeysTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AB062F92AE6224D00C4B47C /* FRAppIntegrityKeysTests.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 */; }; @@ -333,6 +335,8 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 1B6037E52B505924009090DF /* TextInputCallback.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextInputCallback.swift; sourceTree = ""; }; + 1B85DA122B85208C0023E953 /* TextInputCallbackTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextInputCallbackTests.swift; sourceTree = ""; }; 3A1B43CF284510B700EAFC9D /* AtomicDictionary.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AtomicDictionary.swift; sourceTree = ""; }; 3A1B43D2284524B900EAFC9D /* AtomicDictionaryTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AtomicDictionaryTests.swift; sourceTree = ""; }; 3A3E785A2AAF6BE8007962B7 /* FRAppIntegrityCallback.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FRAppIntegrityCallback.swift; sourceTree = ""; }; @@ -345,10 +349,10 @@ 3A53E4D92A153AF200E17DDF /* PolicyAdviceCreatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PolicyAdviceCreatorTests.swift; sourceTree = ""; }; 3A67B8822AD83946003331C5 /* FRAppIntegrityKeys.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FRAppIntegrityKeys.swift; sourceTree = ""; }; 3A6D26662A1345400099D877 /* PolicyAdviceCreator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PolicyAdviceCreator.swift; sourceTree = ""; }; + 3AB062F92AE6224D00C4B47C /* FRAppIntegrityKeysTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FRAppIntegrityKeysTests.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 = ""; }; A53723D02AE80A5D0047B809 /* TelephonyCollectorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TelephonyCollectorTests.swift; sourceTree = ""; }; - 3AB062F92AE6224D00C4B47C /* FRAppIntegrityKeysTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FRAppIntegrityKeysTests.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 = ""; }; @@ -816,6 +820,7 @@ D58BC38E2602A47600254654 /* IdPCallbackTests.swift */, D588649126312D2000EAFE4E /* CallbackConstantsTests.swift */, 3A4B46DD2AB38BA6009E7171 /* FRAppIntegrityCallbackTests.swift */, + 1B85DA122B85208C0023E953 /* TextInputCallbackTests.swift */, ); path = Callback; sourceTree = ""; @@ -1049,6 +1054,7 @@ D53A8055262789E40093B1CA /* WebAuthnRegistrationCallback.swift */, D586CF4123358EDF007A2194 /* TermsAndConditionsCallback.swift */, D586CF4223358EDF007A2194 /* NameCallback.swift */, + 1B6037E52B505924009090DF /* TextInputCallback.swift */, D586CF4323358EDF007A2194 /* PasswordCallback.swift */, D586CF4423358EDF007A2194 /* MultipleValuesCallback.swift */, D586CF4523358EDF007A2194 /* PollingWaitCallback.swift */, @@ -1734,6 +1740,7 @@ D586CF9223358EE0007A2194 /* TermsAndConditionsCallback.swift in Sources */, D53A804D262789BD0093B1CA /* Client.swift in Sources */, D586CFB023358EE0007A2194 /* OIDC.swift in Sources */, + 1B6037E62B505924009090DF /* TextInputCallback.swift in Sources */, D53A8057262789E40093B1CA /* WebAuthnCallback.swift in Sources */, D513F17524DA6B490042228B /* AuthApiError.swift in Sources */, D5C8641A252E880D00268F06 /* OAuth2Error.swift in Sources */, @@ -1939,6 +1946,7 @@ D5791BD525F87DE8004B487A /* AuthApiErrorTests.swift in Sources */, D5791BD625F87DE8004B487A /* BrowserErrorTests.swift in Sources */, D5791BD725F87DE8004B487A /* OAuth2ErrorTests.swift in Sources */, + 1B85DA132B85208C0023E953 /* TextInputCallbackTests.swift in Sources */, D5791BD825F87DE8004B487A /* FRAuthTests.swift in Sources */, D5791BD925F87DE8004B487A /* FRSessionTests.swift in Sources */, D5791BDA25F87DE8004B487A /* SessionManagerTests.swift in Sources */, diff --git a/FRAuth/FRAuth/Callbacks/CallbackFactory.swift b/FRAuth/FRAuth/Callbacks/CallbackFactory.swift index 2f497326..4158acb2 100644 --- a/FRAuth/FRAuth/Callbacks/CallbackFactory.swift +++ b/FRAuth/FRAuth/Callbacks/CallbackFactory.swift @@ -2,7 +2,7 @@ // CallbackFactory.swift // FRAuth // -// Copyright (c) 2019-2023 ForgeRock. All rights reserved. +// Copyright (c) 2019-2024 ForgeRock. All rights reserved. // // This software may be modified and distributed under the terms // of the MIT license. See the LICENSE file for details. @@ -38,6 +38,8 @@ import Foundation 19. WebAuthnAuthenticationCallback 20. IdPCallback 21. SelectIdPCallback + 22. FRAppIntegrityCallback + 23. TextInputCallback */ @objc(FRCallbackFactory) public class CallbackFactory: NSObject { @@ -80,6 +82,7 @@ public class CallbackFactory: NSObject { CallbackType.IdPCallback.rawValue: IdPCallback.self, CallbackType.SelectIdPCallback.rawValue: SelectIdPCallback.self, CallbackType.FRAppIntegrityCallback.rawValue: FRAppIntegrityCallback.self, + CallbackType.TextInputCallback.rawValue: TextInputCallback.self, ] } diff --git a/FRAuth/FRAuth/Callbacks/TextInputCallback.swift b/FRAuth/FRAuth/Callbacks/TextInputCallback.swift new file mode 100644 index 00000000..20ef54cb --- /dev/null +++ b/FRAuth/FRAuth/Callbacks/TextInputCallback.swift @@ -0,0 +1,51 @@ +// +// TextInputCallback.swift +// FRAuth +// +// Copyright (c) 2024 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 + +/** + TextInputCallback is a representation of OpenAM's TextInputCallback to collect single user input; It is typically used to collect any text input for the authentication flow. + */ +@objc(FRTextInputCallback) +public class TextInputCallback: SingleValueCallback { + + private var defaultText: String? + + // MARK: - Init + + /// Designated initialization method for TextInputCallback + /// + /// - Parameter json: JSON object of TextInputCallback + /// - Throws: AuthError.invalidCallbackResponse for invalid callback response + required init(json: [String : Any]) throws { + try super.init(json: json) + + // Validate prompt value for the callback + guard let _ = self.prompt else { + throw AuthError.invalidCallbackResponse(String(describing: json)) + } + + if let outputs = json[CBConstants.output] as? [[String: Any]] { + for output in outputs { + if let outputName = output[CBConstants.name] as? String, let outputValue = output[CBConstants.value] as? String, outputName == "defaultText" { + self.defaultText = outputValue + } + } + } + } + + public func getDefaultText() -> String? { + return self.defaultText + } + + public func setValue(_ val: String) { + self._value = val + } +} diff --git a/FRAuth/FRAuth/Constants/CallbackConstants.swift b/FRAuth/FRAuth/Constants/CallbackConstants.swift index e5cc2537..608a0126 100644 --- a/FRAuth/FRAuth/Constants/CallbackConstants.swift +++ b/FRAuth/FRAuth/Constants/CallbackConstants.swift @@ -2,7 +2,7 @@ // CallbackConstants.swift // FRAuth // -// Copyright (c) 2019-2023 ForgeRock. All rights reserved. +// Copyright (c) 2019-2024 ForgeRock. All rights reserved. // // This software may be modified and distributed under the terms // of the MIT license. See the LICENSE file for details. @@ -94,6 +94,7 @@ enum CallbackType: String { case DeviceBindingCallback = "DeviceBindingCallback" case DeviceSigningVerifierCallback = "DeviceSigningVerifierCallback" case FRAppIntegrityCallback = "AppIntegrityCallback" + case TextInputCallback = "TextInputCallback" } /// CBConstants is mainly responsible to maintain all constant values related to Callback implementation diff --git a/FRAuth/FRAuthTests/FRAuthSwiftTests/FRAuth/Callback/CallbackConstantsTests.swift b/FRAuth/FRAuthTests/FRAuthSwiftTests/FRAuth/Callback/CallbackConstantsTests.swift index 936a2487..5fc86671 100644 --- a/FRAuth/FRAuthTests/FRAuthSwiftTests/FRAuth/Callback/CallbackConstantsTests.swift +++ b/FRAuth/FRAuthTests/FRAuthSwiftTests/FRAuth/Callback/CallbackConstantsTests.swift @@ -2,7 +2,7 @@ // CallbackConstantsTests.swift // FRAuthTests // -// Copyright (c) 2021 ForgeRock. All rights reserved. +// Copyright (c) 2021-2024 ForgeRock. All rights reserved. // // This software may be modified and distributed under the terms // of the MIT license. See the LICENSE file for details. @@ -59,6 +59,10 @@ class CallbackConstantsTests: FRBaseTestCase { XCTAssertEqual(callbackType.rawValue, "IdPCallback") callbackType = .SelectIdPCallback XCTAssertEqual(callbackType.rawValue, "SelectIdPCallback") + callbackType = .FRAppIntegrityCallback + XCTAssertEqual(callbackType.rawValue, "AppIntegrityCallback") + callbackType = .TextInputCallback + XCTAssertEqual(callbackType.rawValue, "TextInputCallback") } diff --git a/FRAuth/FRAuthTests/FRAuthSwiftTests/FRAuth/Callback/TextInputCallbackTests.swift b/FRAuth/FRAuthTests/FRAuthSwiftTests/FRAuth/Callback/TextInputCallbackTests.swift new file mode 100644 index 00000000..b5041bad --- /dev/null +++ b/FRAuth/FRAuthTests/FRAuthSwiftTests/FRAuth/Callback/TextInputCallbackTests.swift @@ -0,0 +1,205 @@ +// +// TextInputCallbackTests.swift +// FRAuthTests +// +// Copyright (c) 2024 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 + +class TextInputCallbackTests: FRAuthBaseTest { + + func testTextInputCallbackWithEmptyJSON() { + + // Try + do { + let callback = try TextInputCallback(json: [:]) + // If not fail, should fail + XCTFail("Passed while expecting failure with empty Dictionary, and callback obj: \(callback)") + } catch let error as AuthError { + // Catch AuthError + switch error { + // Should only fail with this error + case .invalidCallbackResponse: + break + default: + XCTFail("Failed with unexpected error: \(error)") + break + } + } catch { + XCTFail("Failed with unexpected error: \(error)") + } + } + + func testTextInputCallbackInit() { + + // Given + let jsonStr = """ + { + "type": "TextInputCallback", + "output": [{ + "name": "prompt", + "value": "One Time Pin" + }, + { + "name": "defaultText", + "value": "default" + }], + "input": [{ + "name": "IDToken1", "value": "" + }], + "_id": 1 + } + """ + let callbackResponse = self.parseStringToDictionary(jsonStr) + + // Try + do { + let callback = try TextInputCallback(json: callbackResponse) + + // Then + XCTAssertEqual(callback._id, 1) + XCTAssertEqual(callback.type, "TextInputCallback") + XCTAssertEqual(callback.prompt, "One Time Pin") + XCTAssertEqual(callback.getDefaultText(), "default") + XCTAssertEqual(callback.inputName, "IDToken1") + + // Sets value + callback.setValue("339709") + + // Builds new response + let jsonStr2 = """ + { + "type": "TextInputCallback", + "output": [{ + "name": "prompt", + "value": "One Time Pin" + }, + { + "name": "defaultText", + "value": "default" + }], + "input": [{ + "name": "IDToken1", "value": "339709" + }], + "_id": 1 + } + """ + let response = self.parseStringToDictionary(jsonStr2) + + // Should equal when built + XCTAssertTrue(NSDictionary(dictionary: response).isEqual(to: callback.buildResponse())) + + } catch let error { + XCTFail("Failed while expecting success: \(error)") + } + } + + func testTextInputCallbackWithMultipleOutputs() { + // Given + let jsonStr = """ + { + "type": "TextInputCallback", + "output": [{ + "name": "prompt","value": "User Name" + },{ + "name": "testing", "value": "what" + }], + "input": [{ + "name": "IDToken2", "value": "" + }], + "_id": 1 + } + """ + let callbackResponse = self.parseStringToDictionary(jsonStr) + + // Try + do { + let callback = try TextInputCallback(json: callbackResponse) + + // Then + XCTAssertEqual(callback._id, 1) + XCTAssertEqual(callback.type, "TextInputCallback") + XCTAssertEqual(callback.prompt, "User Name") + XCTAssertEqual(callback.inputName, "IDToken2") + } catch let error { + XCTFail("Failed while expecting success: \(error)") + } + } + + func testTextInputCallbackMissingPrompt() { + // Given + let jsonStr = """ + { + "type": "TextInputCallback", + "output": [{ + "name": "noPrompt","value": "User Name" + }], + "input": [{ + "name": "IDToken2", "value": "" + }], + "_id": 1 + } + """ + let callbackResponse = self.parseStringToDictionary(jsonStr) + + // Try + do { + let callback = try TextInputCallback(json: callbackResponse) + // If not fail, should fail + XCTFail("Passed while expecting failure with \(callbackResponse), and callback obj: \(callback)") + } catch let error as AuthError { + // Catch AuthError + switch error { + // Should only fail with this error + case .invalidCallbackResponse: + break + default: + XCTFail("Failed with unexpected error: \(error)") + break + } + } catch { + XCTFail("Failed with unexpected error: \(error)") + } + } + + func testTextInputCallbackMissingInput() { + + // Given + let jsonStr = """ + { + "type": "TextInputCallback", + "output": [{ + "name": "prompt","value": "User Name" + }], + "_id": 1 + } + """ + let callbackResponse = self.parseStringToDictionary(jsonStr) + + // Try + do { + let callback = try TextInputCallback(json: callbackResponse) + // If not fail, should fail + XCTFail("Passed while expecting failure with \(callbackResponse), and callback obj: \(callback)") + } catch let error as AuthError { + // Catch AuthError + switch error { + // Should only fail with this error + case .invalidCallbackResponse: + break + default: + XCTFail("Failed with unexpected error: \(error)") + break + } + } catch { + XCTFail("Failed with unexpected error: \(error)") + } + } +} + diff --git a/FRUI/FRUI/Model/CallbackTableViewCellFactory.swift b/FRUI/FRUI/Model/CallbackTableViewCellFactory.swift index 82f1ed1f..63de7014 100644 --- a/FRUI/FRUI/Model/CallbackTableViewCellFactory.swift +++ b/FRUI/FRUI/Model/CallbackTableViewCellFactory.swift @@ -2,7 +2,7 @@ // CallbackTableViewCellFactory.swift // FRUI // -// Copyright (c) 2020-2021 ForgeRock. All rights reserved. +// Copyright (c) 2020-2024 ForgeRock. All rights reserved. // // This software may be modified and distributed under the terms // of the MIT license. See the LICENSE file for details. @@ -43,6 +43,7 @@ public class CallbackTableViewCellFactory: NSObject { self.talbeViewCellForCallbacks = [ "NameCallback": NameCallbackTableViewCell.self, "StringAttributeInputCallback": NameCallbackTableViewCell.self, + "TextInputCallback": NameCallbackTableViewCell.self, "ValidatedCreateUsernameCallback": NameCallbackTableViewCell.self, "PasswordCallback": PasswordCallbackTableViewCell.self, "ValidatedCreatePasswordCallback": PasswordCallbackTableViewCell.self, @@ -64,6 +65,7 @@ public class CallbackTableViewCellFactory: NSObject { self.tableViewCellNibForCallbacks = [ "NameCallback": "NameCallbackTableViewCell", "StringAttributeInputCallback": "NameCallbackTableViewCell", + "TextInputCallback": "NameCallbackTableViewCell", "ValidatedCreateUsernameCallback": "NameCallbackTableViewCell", "PasswordCallback": "PasswordCallbackTableViewCell", "ValidatedCreatePasswordCallback": "PasswordCallbackTableViewCell", diff --git a/SampleApps/FRExample/FRExample/ViewController.swift b/SampleApps/FRExample/FRExample/ViewController.swift index e75d1564..77216264 100644 --- a/SampleApps/FRExample/FRExample/ViewController.swift +++ b/SampleApps/FRExample/FRExample/ViewController.swift @@ -2,7 +2,7 @@ // ViewController.swift // FRExample // -// Copyright (c) 2019-2023 ForgeRock. All rights reserved. +// Copyright (c) 2019-2024 ForgeRock. All rights reserved. // // This software may be modified and distributed under the terms // of the MIT license. See the LICENSE file for details. @@ -251,6 +251,15 @@ class ViewController: UIViewController, ErrorAlertShowing { textField.autocapitalizationType = .none }) } + else if callback.type == "TextInputCallback", let textInputCallback = callback as? TextInputCallback { + + alert.addTextField(configurationHandler: { (textField) in + textField.placeholder = textInputCallback.prompt + textField.autocorrectionType = .no + textField.autocapitalizationType = .none + textField.text = textInputCallback.getDefaultText() + }) + } else if callback.type == "PasswordCallback", let passwordCallback = callback as? PasswordCallback { alert.addTextField(configurationHandler: { (textField) in textField.placeholder = passwordCallback.prompt diff --git a/SampleApps/FRexampleObjC/FRexampleObjC/ViewController.m b/SampleApps/FRexampleObjC/FRexampleObjC/ViewController.m index a32523da..68256fb4 100644 --- a/SampleApps/FRexampleObjC/FRexampleObjC/ViewController.m +++ b/SampleApps/FRexampleObjC/FRexampleObjC/ViewController.m @@ -2,7 +2,7 @@ // ViewController.m // FRexampleObjC // -// Copyright (c) 2019 ForgeRock. All rights reserved. +// Copyright (c) 2019-2024 ForgeRock. All rights reserved. // // This software may be modified and distributed under the terms // of the MIT license. See the LICENSE file for details. @@ -356,6 +356,7 @@ - (void)handleNodeWithObj:(id)resultObj expectedResult:(Class)expectedResult nod for (id callback in node.callbacks) { if ([callback isKindOfClass:[FRNameCallback class]] || + [callback isKindOfClass:[FRTextInputCallback class]] || [callback isKindOfClass:[FRValidatedCreateUsernameCallback class]] || [callback isKindOfClass:[FRPasswordCallback class]] || [callback isKindOfClass:[FRValidatedCreatePasswordCallback class]]) { From 840430677be38e3369497da7fc43a7df274a6037 Mon Sep 17 00:00:00 2001 From: Stoyan Petrov Date: Wed, 21 Feb 2024 09:22:41 -0800 Subject: [PATCH 2/3] Added e2e test for TextInputCallback. --- FRAuth/FRAuth.xcodeproj/project.pbxproj | 4 + .../AA_08_TextInputCallbackTest.swift | 86 +++++++++++++++++++ .../Views/NameCallbackTableViewCell.swift | 4 + 3 files changed, 94 insertions(+) create mode 100644 FRAuth/FRAuthTests/FRAuthSwiftTests/E2ETests/Callback-Live/AA_08_TextInputCallbackTest.swift diff --git a/FRAuth/FRAuth.xcodeproj/project.pbxproj b/FRAuth/FRAuth.xcodeproj/project.pbxproj index df84d07b..29ddce14 100644 --- a/FRAuth/FRAuth.xcodeproj/project.pbxproj +++ b/FRAuth/FRAuth.xcodeproj/project.pbxproj @@ -22,6 +22,7 @@ 3A67B8832AD83946003331C5 /* FRAppIntegrityKeys.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A67B8822AD83946003331C5 /* FRAppIntegrityKeys.swift */; }; 3A6D26672A1345400099D877 /* PolicyAdviceCreator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A6D26662A1345400099D877 /* PolicyAdviceCreator.swift */; }; 3AB062FA2AE6224D00C4B47C /* FRAppIntegrityKeysTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AB062F92AE6224D00C4B47C /* FRAppIntegrityKeysTests.swift */; }; + 9536C56C2B865DD600B2DFDD /* AA_08_TextInputCallbackTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9536C56B2B865DD600B2DFDD /* AA_08_TextInputCallbackTest.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 */; }; A53723D12AE80A5D0047B809 /* TelephonyCollectorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A53723D02AE80A5D0047B809 /* TelephonyCollectorTests.swift */; }; @@ -350,6 +351,7 @@ 3A67B8822AD83946003331C5 /* FRAppIntegrityKeys.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FRAppIntegrityKeys.swift; sourceTree = ""; }; 3A6D26662A1345400099D877 /* PolicyAdviceCreator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PolicyAdviceCreator.swift; sourceTree = ""; }; 3AB062F92AE6224D00C4B47C /* FRAppIntegrityKeysTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FRAppIntegrityKeysTests.swift; sourceTree = ""; }; + 9536C56B2B865DD600B2DFDD /* AA_08_TextInputCallbackTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AA_08_TextInputCallbackTest.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 = ""; }; A53723D02AE80A5D0047B809 /* TelephonyCollectorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TelephonyCollectorTests.swift; sourceTree = ""; }; @@ -1196,6 +1198,7 @@ 959D7D97290B4B9200A1F22F /* AA-05-DeviceBindingCallbackTest.swift */, 95E180B32992A6F20087457D /* AA-06-DeviceSigningVerifierCallbackTest.swift */, 3A4B46E32AB95B3D009E7171 /* AA_07_AppIntegrityTest.swift */, + 9536C56B2B865DD600B2DFDD /* AA_08_TextInputCallbackTest.swift */, ); path = "Callback-Live"; sourceTree = ""; @@ -1945,6 +1948,7 @@ D5791BD425F87DE8004B487A /* TokenErrorTests.swift in Sources */, D5791BD525F87DE8004B487A /* AuthApiErrorTests.swift in Sources */, D5791BD625F87DE8004B487A /* BrowserErrorTests.swift in Sources */, + 9536C56C2B865DD600B2DFDD /* AA_08_TextInputCallbackTest.swift in Sources */, D5791BD725F87DE8004B487A /* OAuth2ErrorTests.swift in Sources */, 1B85DA132B85208C0023E953 /* TextInputCallbackTests.swift in Sources */, D5791BD825F87DE8004B487A /* FRAuthTests.swift in Sources */, diff --git a/FRAuth/FRAuthTests/FRAuthSwiftTests/E2ETests/Callback-Live/AA_08_TextInputCallbackTest.swift b/FRAuth/FRAuthTests/FRAuthSwiftTests/E2ETests/Callback-Live/AA_08_TextInputCallbackTest.swift new file mode 100644 index 00000000..a35b2e4a --- /dev/null +++ b/FRAuth/FRAuthTests/FRAuthSwiftTests/E2ETests/Callback-Live/AA_08_TextInputCallbackTest.swift @@ -0,0 +1,86 @@ +// +// AA_08_TextInputCallbackTest.swift +// FRAuthTests +// +// Copyright (c) 2024 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 + +class AA_08_TextInputCallbackTest: CallbackBaseTest { + + override func setUp() { + super.setUp() + self.config.authServiceName = "TextInputCallbackTest" + } + + func test_01_text_input_callback() { + var currentNode: Node? + + do { + try currentNode = fulfillUsernamePasswordNodes() + } catch AuthError.invalidCallbackResponse { + XCTFail("Expected at least one node after username/password nodes, but got nothing!") + return + } catch { + XCTFail("Unexpected error occured!") + return + } + + // We expect TextInputCallback here. Assert its properties. . . + for callback in currentNode!.callbacks { + if callback is TextInputCallback, let textInputCallback = callback as? TextInputCallback { + XCTAssertEqual(textInputCallback.prompt, "What is your username?") + XCTAssertEqual(textInputCallback.getDefaultText(), "ForgerRocker") + + textInputCallback.setValue(config.username) + } + else { + XCTFail("Received unexpected callback \(callback)") + } + } + + var ex = self.expectation(description: "Submit the text input callback and continue...") + currentNode?.next { (token: AccessToken?, node, error) in + // Validate result + XCTAssertNotNil(node) // We expect Message node with "Success" + XCTAssertNil(error) + XCTAssertNil(token) + currentNode = node + ex.fulfill() + } + waitForExpectations(timeout: 60, handler: nil) + + guard let node = currentNode else { + XCTFail("Test Failed: Expected TextOutputCallback and ConfirmationCallback (returned by Message node), but got nothing...") + return + } + + for callback in node.callbacks { + if callback is TextOutputCallback, let textOutputCallback = callback as? TextOutputCallback { + XCTAssertEqual(textOutputCallback.message, "Success") + } + else if callback is ConfirmationCallback, let confirmationCallback = callback as? ConfirmationCallback { + confirmationCallback.value = 0 + } + else { + XCTFail("Received unexpected callback \(callback)") + } + } + + ex = self.expectation(description: "Submit value for the ConfirmationCallback and continue...") + currentNode?.next { (token: AccessToken?, node, error) in + XCTAssertNil(node) + XCTAssertNil(error) + XCTAssertNotNil(token) + ex.fulfill() + } + waitForExpectations(timeout: 60, handler: nil) + + XCTAssertNotNil(FRUser.currentUser) + } +} diff --git a/FRUI/FRUI/Views/NameCallbackTableViewCell.swift b/FRUI/FRUI/Views/NameCallbackTableViewCell.swift index 48b21c1d..18992412 100644 --- a/FRUI/FRUI/Views/NameCallbackTableViewCell.swift +++ b/FRUI/FRUI/Views/NameCallbackTableViewCell.swift @@ -36,6 +36,10 @@ class NameCallbackTableViewCell: UITableViewCell, FRUICallbackTableViewCell { self.callback = callback as? SingleValueCallback self.textField?.placeholder = self.callback?.prompt + if callback is TextInputCallback, let textInputCallback = callback as? TextInputCallback { + self.textField?.text = textInputCallback.getDefaultText() + } + if callback is AbstractValidatedCallback, let validatedCallback = callback as? AbstractValidatedCallback { self.textField?.text = validatedCallback.getValue() as? String if let failedPolicies = validatedCallback.failedPolicies { From 399f79112c7642443418a37b0a69024a5472d763 Mon Sep 17 00:00:00 2001 From: Rodrigo Reis Date: Wed, 21 Feb 2024 11:44:52 -0800 Subject: [PATCH 3/3] Move "defaultText" in TextInputCallback to CBConstants --- FRAuth/FRAuth/Callbacks/TextInputCallback.swift | 2 +- FRAuth/FRAuth/Constants/CallbackConstants.swift | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/FRAuth/FRAuth/Callbacks/TextInputCallback.swift b/FRAuth/FRAuth/Callbacks/TextInputCallback.swift index 20ef54cb..ac81ad40 100644 --- a/FRAuth/FRAuth/Callbacks/TextInputCallback.swift +++ b/FRAuth/FRAuth/Callbacks/TextInputCallback.swift @@ -34,7 +34,7 @@ public class TextInputCallback: SingleValueCallback { if let outputs = json[CBConstants.output] as? [[String: Any]] { for output in outputs { - if let outputName = output[CBConstants.name] as? String, let outputValue = output[CBConstants.value] as? String, outputName == "defaultText" { + if let outputName = output[CBConstants.name] as? String, let outputValue = output[CBConstants.value] as? String, outputName == CBConstants.defaultText { self.defaultText = outputValue } } diff --git a/FRAuth/FRAuth/Constants/CallbackConstants.swift b/FRAuth/FRAuth/Constants/CallbackConstants.swift index 608a0126..2c828c0e 100644 --- a/FRAuth/FRAuth/Constants/CallbackConstants.swift +++ b/FRAuth/FRAuth/Constants/CallbackConstants.swift @@ -253,3 +253,8 @@ extension CBConstants { static let keyId = "keyId" static let clientData = "clientData" } + +// MARK: - TextInputCallback +extension CBConstants { + static let defaultText: String = "defaultText" +}