diff --git a/.gitignore b/.gitignore index 071f2dc..534403e 100644 --- a/.gitignore +++ b/.gitignore @@ -51,8 +51,10 @@ Library/ # Carthage # # Add this line if you want to avoid checking in source code from Carthage dependencies. -#Carthage/Checkouts +Carthage/Checkouts Carthage +#ignoring gitmodules to disable dependency propagation of the carthage +.gitmodules *.jar diff --git a/Cartfile b/Cartfile index fc5ba05..f69b3a0 100644 --- a/Cartfile +++ b/Cartfile @@ -1 +1 @@ -github "wultra/networking-apple" "release/1.1.x" +github "wultra/networking-apple" "1.1.4" \ No newline at end of file diff --git a/Cartfile.resolved b/Cartfile.resolved index 3be1e51..1d6fefd 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1,3 +1,3 @@ -binary "https://raw.githubusercontent.com/wultra/powerauth-mobile-sdk-spm/develop/PowerAuth2.json" "1.7.1" -binary "https://raw.githubusercontent.com/wultra/powerauth-mobile-sdk-spm/develop/PowerAuthCore.json" "1.7.1" -github "wultra/networking-apple" "eaf413b0ef0fcf2c5cef9c3ff0735b2643450e8c" +binary "https://raw.githubusercontent.com/wultra/powerauth-mobile-sdk-spm/develop/PowerAuth2.json" "1.7.2" +binary "https://raw.githubusercontent.com/wultra/powerauth-mobile-sdk-spm/develop/PowerAuthCore.json" "1.7.2" +github "wultra/networking-apple" "1.1.4" diff --git a/WultraMobileTokenSDK/Operations/Model/UserOperation/WMTUserOperation.swift b/WultraMobileTokenSDK/Operations/Model/UserOperation/WMTUserOperation.swift index a608dd1..cfd9b89 100644 --- a/WultraMobileTokenSDK/Operations/Model/UserOperation/WMTUserOperation.swift +++ b/WultraMobileTokenSDK/Operations/Model/UserOperation/WMTUserOperation.swift @@ -19,7 +19,7 @@ import Foundation /// `WMTUserOperation` is object returned from the backend that can be either approved or rejected. /// It is usually visually presented to the user as a non-editable form with information, about /// the real-world operation (for example login or payment). -public class WMTUserOperation: WMTOperation, Codable { +open class WMTUserOperation: WMTOperation, Codable { /// Unique operation identifier public let id: String diff --git a/WultraMobileTokenSDK/Operations/QR/WMTQROperation.swift b/WultraMobileTokenSDK/Operations/QR/WMTQROperation.swift index 4a8bfb3..2a11093 100644 --- a/WultraMobileTokenSDK/Operations/QR/WMTQROperation.swift +++ b/WultraMobileTokenSDK/Operations/QR/WMTQROperation.swift @@ -77,6 +77,15 @@ public struct QROperationFlags { /// If true, then 2FA signature with biometry factor can be used for operation confirmation. public let allowBiometryFactor: Bool + + /// If confirm/reject buttons should be flipped in the UI. This can be useful to test users attention. + public let flipButtons: Bool + + /// When the operation is considered a "potential fraud" on the server, a warning UI should be displayed to the user. + public let fraudWarning: Bool + + /// Block confirmation when call is active. + public let blockWhenOnCall: Bool } /// The `WMTQROperationData` structure defines operation data in QR operation. diff --git a/WultraMobileTokenSDK/Operations/QR/WMTQROperationParser.swift b/WultraMobileTokenSDK/Operations/QR/WMTQROperationParser.swift index ae117a7..b779b56 100644 --- a/WultraMobileTokenSDK/Operations/QR/WMTQROperationParser.swift +++ b/WultraMobileTokenSDK/Operations/QR/WMTQROperationParser.swift @@ -235,7 +235,11 @@ public class WMTQROperationParser { /// Parses given string into QROperationFlags structure private func parseOperationFlags(string: String) -> QROperationFlags { return QROperationFlags( - allowBiometryFactor: string.contains("B")) + allowBiometryFactor: string.contains("B"), + flipButtons: string.contains("X"), + fraudWarning: string.contains("F"), + blockWhenOnCall: string.contains("C") + ) } /// Returns true if provided string is in Base64 format and encoded data's length diff --git a/WultraMobileTokenSDK/Operations/Service/WMTOperationEndpoints.swift b/WultraMobileTokenSDK/Operations/Service/WMTOperationEndpoints.swift index 142a446..620b162 100644 --- a/WultraMobileTokenSDK/Operations/Service/WMTOperationEndpoints.swift +++ b/WultraMobileTokenSDK/Operations/Service/WMTOperationEndpoints.swift @@ -19,9 +19,9 @@ import WultraPowerAuthNetworking enum WMTOperationEndpoints { - enum List { - typealias EndpointType = WPNEndpointSignedWithToken> - static let endpoint: EndpointType = WPNEndpointSignedWithToken(endpointURLPath: "/api/auth/token/app/operation/list", tokenName: "possession_universal") + enum List { + typealias EndpointType = WPNEndpointSignedWithToken> + static var endpoint: EndpointType { WPNEndpointSignedWithToken(endpointURLPath: "/api/auth/token/app/operation/list", tokenName: "possession_universal") } } enum History { diff --git a/WultraMobileTokenSDK/Operations/Service/WMTOperationsImpl.swift b/WultraMobileTokenSDK/Operations/Service/WMTOperationsImpl.swift index 708e9c8..a8aefda 100644 --- a/WultraMobileTokenSDK/Operations/Service/WMTOperationsImpl.swift +++ b/WultraMobileTokenSDK/Operations/Service/WMTOperationsImpl.swift @@ -29,7 +29,21 @@ public extension PowerAuthSDK { /// - pollingOptions: Polling feature configuration /// - Returns: Operations service func createWMTOperations(networkingConfig: WPNConfig, pollingOptions: WMTOperationsPollingOptions = []) -> WMTOperations { - return WMTOperationsImpl(networking: WPNNetworkingService(powerAuth: self, config: networkingConfig, serviceName: "WMTOperations"), pollingOptions: pollingOptions) + return createWMTOperations(networkingConfig: networkingConfig, pollingOptions: pollingOptions, customUserOperationType: WMTUserOperation.self) + } + + /// Creates instance of the `WMTOperations` on top of the PowerAuth instance. + /// - Parameters: + /// - networkingConfig: Networking service config + /// - pollingOptions: Polling feature configuration + /// - customUserOperationType: All user operations fetched from the server will be decoded as the given type. Make sure such type properly conforms to the Codable protocol. + /// - Returns: Operations service + func createWMTOperations( + networkingConfig: WPNConfig, + pollingOptions: WMTOperationsPollingOptions = [], + customUserOperationType: T.Type + ) -> WMTOperations { + return WMTOperationsImpl(networking: WPNNetworkingService(powerAuth: self, config: networkingConfig, serviceName: "WMTOperations"), pollingOptions: pollingOptions) } } @@ -40,7 +54,16 @@ public extension WPNNetworkingService { /// - pollingOptions: Polling feature configuration /// - Returns: Operations service func createWMTOperations(pollingOptions: WMTOperationsPollingOptions = []) -> WMTOperations { - return WMTOperationsImpl(networking: self, pollingOptions: pollingOptions) + return createWMTOperations(pollingOptions: pollingOptions, customUserOperationType: WMTUserOperation.self) + } + + /// Creates instance of the `WMTOperations` on top of the WPNNetworkingService/PowerAuth instance. + /// - Parameters: + /// - pollingOptions: Polling feature configuration + /// - customUserOperationType: All user operations fetched from the server will be decoded as the given type. Make sure such type properly conforms to the Codable protocol. + /// - Returns: Operations service + func createWMTOperations(pollingOptions: WMTOperationsPollingOptions = [], customUserOperationType: T.Type) -> WMTOperations { + return WMTOperationsImpl(networking: self, pollingOptions: pollingOptions) } } @@ -64,7 +87,7 @@ public extension WMTErrorReason { static let operations_QROperationFailed = WMTErrorReason(rawValue: "operations_QRFailed") } -class WMTOperationsImpl: WMTOperations { +class WMTOperationsImpl: WMTOperations { // Dependencies private lazy var powerAuth = networking.powerAuth @@ -211,6 +234,7 @@ class WMTOperationsImpl: WMTOperations { } } + // DEPRECATED, REMOVE IN THE FUTURE func authorize(operation: WMTOperation, authentication: PowerAuthAuthentication, completion: @escaping(WMTError?) -> Void) -> Operation? { return authorize(operation: operation, with: authentication) { result in switch result { @@ -244,6 +268,7 @@ class WMTOperationsImpl: WMTOperations { } } + // DEPRECATED, REMOVE IN THE FUTURE func reject(operation: WMTOperation, reason: WMTRejectionReason, completion: @escaping(WMTError?) -> Void) -> Operation? { return reject(operation: operation, with: reason) { result in switch result { diff --git a/WultraMobileTokenSDKTests/QROperationParserTests.swift b/WultraMobileTokenSDKTests/QROperationParserTests.swift index 2247090..d685e8e 100644 --- a/WultraMobileTokenSDKTests/QROperationParserTests.swift +++ b/WultraMobileTokenSDKTests/QROperationParserTests.swift @@ -24,7 +24,7 @@ class QROperationParserTests: XCTestCase { title: String = "Payment", message: String = "Please confirm this payment", operationData: String = "A1*A100CZK*ICZ2730300000001165254011*D20180425*Thello world", - flags: String = "B", + flags: String = "BCFX", otherAttrs: [String]? = nil, nonce: String = "AD8bOO0Df73kNaIGb3Vmpg==", signingKey: String = "0", @@ -44,7 +44,7 @@ class QROperationParserTests: XCTestCase { "Payment\n" + "Please confirm this payment\n" + "A1*A100CZK*ICZ2730300000001165254011*D20180425*Thello world\n" + - "B\n" + + "BCFX\n" + "AD8bOO0Df73kNaIGb3Vmpg==\n" + "0").data(using: .utf8) @@ -57,6 +57,9 @@ class QROperationParserTests: XCTestCase { XCTAssertTrue(operation.title == "Payment") XCTAssertTrue(operation.message == "Please confirm this payment") XCTAssertTrue(operation.flags.allowBiometryFactor == true) + XCTAssertTrue(operation.flags.flipButtons == true) + XCTAssertTrue(operation.flags.fraudWarning == true) + XCTAssertTrue(operation.flags.blockWhenOnCall == true) XCTAssertTrue(operation.nonce == "AD8bOO0Df73kNaIGb3Vmpg==") XCTAssertTrue(operation.signature.signature == "MEYCIQDby1Uq+MaxiAAGzKmE/McHzNOUrvAP2qqGBvSgcdtyjgIhAMo1sgqNa1pPZTFBhhKvCKFLGDuHuTTYexdmHFjUUIJW") XCTAssertTrue(operation.signature.signingKey == .master) @@ -107,7 +110,7 @@ class QROperationParserTests: XCTestCase { "Payment\n" + "Please confirm this payment\n" + "B2*Xtest\n" + - "B\n" + + "BCFX\n" + "Some Additional Information\n" + "AD8bOO0Df73kNaIGb3Vmpg==\n" + "0").data(using: .utf8) @@ -167,6 +170,22 @@ class QROperationParserTests: XCTestCase { return } XCTAssertFalse(operation.flags.allowBiometryFactor) + XCTAssertFalse(operation.flags.blockWhenOnCall) + XCTAssertFalse(operation.flags.flipButtons) + XCTAssertFalse(operation.flags.fraudWarning) + } + + func testSomeMissingFlags() { + let parser = WMTQROperationParser() + let qrcode = makeCode(flags: "FX") + guard case .success(let operation) = parser.parse(string: qrcode) else { + XCTFail("This should be parsed") + return + } + XCTAssertFalse(operation.flags.allowBiometryFactor) + XCTAssertFalse(operation.flags.blockWhenOnCall) + XCTAssertTrue(operation.flags.flipButtons) + XCTAssertTrue(operation.flags.fraudWarning) } func testMissingOrBadNonce() { @@ -347,5 +366,4 @@ class QROperationParserTests: XCTestCase { return } } - } diff --git a/docs/Using-Operations-Service.md b/docs/Using-Operations-Service.md index 06dec5a..5e9e238 100644 --- a/docs/Using-Operations-Service.md +++ b/docs/Using-Operations-Service.md @@ -9,6 +9,7 @@ - [Reject an Operation](#reject-an-operation) - [Off-line Authorization](#off-line-authorization) - [Operations API Reference](#operations-api-reference) +- [WMTUserOperation](#WMTUserOperation) - [Creating a Custom Operation](#creating-a-custom-operation) - [Error handling](#error-handling) @@ -53,6 +54,17 @@ The `pollingOptions` parameter is used for polling feature configuration. The de - `WMTOperationsPollingOptions.pauseWhenOnBackground` +### With custom WMTUserOperation objects + +To retreive custom user operations, both `createWMTOperations` methods offer optional parameter `customUserOperationType` where you can setup requested type. + +```swift +// networkingService is instance of WPNNetworkingService +let opsService = networkingService.createWMTOperations(customUserOperationType: CustomUserOperation.self). +``` + +When [custom operation type](#subclassing-WMTUserOperation) is set, all `WMTUserOperation` objects from such service can be explicitly unboxed to this type. + ## Retrieve Pending Operations To fetch the list with pending operations, can call the `WMTOperations` API: @@ -447,6 +459,47 @@ Attributes types: - `HEADING` single highlighted text, written in a larger font, used as a section heading - `PARTY_INFO` providing structured information about third-party data (for example known e-shop) +### Subclassing WMTUserOperation + +`WMTUserOperation` class is `open` and can be subclassed. This is useful when your backend adds additional properties to operations retrieved via the `getOperations` API. + +Example of such class: + +```swift +class CustomUserOperation: WMTUserOperation { + + enum CodingKeys: CodingKey { + case playSound + } + + /// Should we play a sound when the operation is displayed? + let playSound: Bool + + required init(from decoder: Decoder) throws { + /// Decode the playSound property + playSound = try decoder.container(keyedBy: CodingKeys.self).decode(Bool, forKey: . playSound) + /// Decode the rest of the properties by the super class + try super.init(from: decoder) +} +``` + +To set up the Operation Service to receive such objects, you need to create it with a [`customUserOperationType` parameter](#with-custom-WMTUserOperation-objects). After that, all `WMTUserOperation` objects can be unboxed into your custom objects. + +Example of the unboxing: + +```swift +opsService.getOperations { result in + switch result { + case .success(let ops): + // unbox operations into the [CustomUserOperation] + let unboxed = ops.map { $0 as! CustomUserOperation } + case .failure(let error): + // do something with the error + break + } +} +``` + ## Creating a Custom Operation In some specific scenarios, you might need to approve or reject an operation that you received through a different channel than `getOperations`. In such cases, you can implement the `WMTOperation` protocol in your custom class and then feed created objects to both `authorize` and `reject` methods. diff --git a/scripts/cart-update.sh b/scripts/cart-update.sh index d9f4407..9e84fdd 100755 --- a/scripts/cart-update.sh +++ b/scripts/cart-update.sh @@ -5,5 +5,5 @@ set -e # stop sript when error occures SCRIPT_FOLDER=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) pushd "${SCRIPT_FOLDER}/.." -carthage update --use-xcframeworks --platform ios +carthage update --use-xcframeworks --use-submodules --platform ios popd \ No newline at end of file