From 051838b03f5e6db55c46125d96a95f8b6df0e47f Mon Sep 17 00:00:00 2001 From: Andrey Scherbovich Date: Wed, 25 Nov 2020 17:30:20 +0100 Subject: [PATCH] gh-716 handle CONFIRMATION_REQUEST pushes; add unit tests --- Multisig.xcodeproj/project.pbxproj | 24 ++++++++++++++ .../RegisterNotificationTokenRequest.swift | 12 +++++-- Multisig/Logic/Models/Signer.swift | 2 +- .../UI/App/RemoteNotificationHandler.swift | 6 ++++ .../AppSettingsViewController.swift | 1 + .../SelectOwnerAddressViewModel.swift | 1 + ...egisterNotificationTokenRequestTests.swift | 33 +++++++++++++++++++ MultisigTests/Logic/Models/SignerTests.swift | 16 +++++---- .../NotificationService.swift | 3 +- .../ConfirmationRequestNotification.swift | 33 +++++++++++++++++++ .../Notifications/MultisigNotification.swift | 1 + 11 files changed, 122 insertions(+), 10 deletions(-) create mode 100644 MultisigTests/Data/Services/Safe Transaction Service/RegisterNotificationTokenRequestTests.swift create mode 100644 NotificationServiceExtension/Notifications/ConfirmationRequestNotification.swift diff --git a/Multisig.xcodeproj/project.pbxproj b/Multisig.xcodeproj/project.pbxproj index b45953f61..7a2bcd8bd 100644 --- a/Multisig.xcodeproj/project.pbxproj +++ b/Multisig.xcodeproj/project.pbxproj @@ -365,6 +365,8 @@ 559670FF256E88FC0097D3A2 /* Signer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 559670FE256E88FC0097D3A2 /* Signer.swift */; }; 55967105256E92470097D3A2 /* SignerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55967104256E92470097D3A2 /* SignerTests.swift */; }; 5596710D256E935F0097D3A2 /* MockSecureStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5596710C256E935F0097D3A2 /* MockSecureStore.swift */; }; + 55967119256EB2120097D3A2 /* RegisterNotificationTokenRequestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55967118256EB2120097D3A2 /* RegisterNotificationTokenRequestTests.swift */; }; + 55967123256EB7690097D3A2 /* ConfirmationRequestNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55967122256EB7690097D3A2 /* ConfirmationRequestNotification.swift */; }; 5596C03825507EAE00EF23A5 /* CollectibleTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5596C03725507EAE00EF23A5 /* CollectibleTableViewCell.xib */; }; 5596C03E25507F3C00EF23A5 /* CollectibleTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5596C03D25507F3C00EF23A5 /* CollectibleTableViewCell.swift */; }; 5596C0482550813B00EF23A5 /* CollectiblesHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5596C0472550813B00EF23A5 /* CollectiblesHeaderView.swift */; }; @@ -873,6 +875,8 @@ 559670FE256E88FC0097D3A2 /* Signer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Signer.swift; sourceTree = ""; }; 55967104256E92470097D3A2 /* SignerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignerTests.swift; sourceTree = ""; }; 5596710C256E935F0097D3A2 /* MockSecureStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockSecureStore.swift; sourceTree = ""; }; + 55967118256EB2120097D3A2 /* RegisterNotificationTokenRequestTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegisterNotificationTokenRequestTests.swift; sourceTree = ""; }; + 55967122256EB7690097D3A2 /* ConfirmationRequestNotification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfirmationRequestNotification.swift; sourceTree = ""; }; 5596C03725507EAE00EF23A5 /* CollectibleTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = CollectibleTableViewCell.xib; sourceTree = ""; }; 5596C03D25507F3C00EF23A5 /* CollectibleTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectibleTableViewCell.swift; sourceTree = ""; }; 5596C0472550813B00EF23A5 /* CollectiblesHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectiblesHeaderView.swift; sourceTree = ""; }; @@ -1214,6 +1218,7 @@ 0A0EFCA1246EADD100D3D8BF /* Data */ = { isa = PBXGroup; children = ( + 55967116256EB1E50097D3A2 /* Services */, 5556A03924489F5D003EC861 /* Persistence */, ); path = Data; @@ -1814,6 +1819,7 @@ 0AF2737024DC57FA007E4012 /* IncomingEtherNotification.swift */, 0AF2737224DC580D007E4012 /* IncomingTokenNotification.swift */, 0AF2736E24DC57E8007E4012 /* MultisigNotification.swift */, + 55967122256EB7690097D3A2 /* ConfirmationRequestNotification.swift */, ); path = Notifications; sourceTree = ""; @@ -2111,6 +2117,22 @@ path = Mocks; sourceTree = ""; }; + 55967116256EB1E50097D3A2 /* Services */ = { + isa = PBXGroup; + children = ( + 55967117256EB1F10097D3A2 /* Safe Transaction Service */, + ); + path = Services; + sourceTree = ""; + }; + 55967117256EB1F10097D3A2 /* Safe Transaction Service */ = { + isa = PBXGroup; + children = ( + 55967118256EB2120097D3A2 /* RegisterNotificationTokenRequestTests.swift */, + ); + path = "Safe Transaction Service"; + sourceTree = ""; + }; 55A585352501417E005E778B /* PrivateKey */ = { isa = PBXGroup; children = ( @@ -3005,6 +3027,7 @@ 55967105256E92470097D3A2 /* SignerTests.swift in Sources */, 5532D4DE2449A34A0067505A /* TestUtils.swift in Sources */, 5556A03C24489F5D003EC861 /* CoreDataStackTests.swift in Sources */, + 55967119256EB2120097D3A2 /* RegisterNotificationTokenRequestTests.swift in Sources */, 551DDEF0244F1A0A00C719D3 /* SafeTests.swift in Sources */, 5532D4CC2449A1980067505A /* LogFormatterTests.swift in Sources */, 0ACC4C4E247534EC00ADF201 /* GnosisSafeTests.swift in Sources */, @@ -3039,6 +3062,7 @@ 0AF2736B24DC57C6007E4012 /* NotificationPayload.swift in Sources */, 0AF2737524DC581A007E4012 /* ExecutedMultisigTransactionNotification.swift in Sources */, 0AF2736F24DC57E8007E4012 /* MultisigNotification.swift in Sources */, + 55967123256EB7690097D3A2 /* ConfirmationRequestNotification.swift in Sources */, 0AF2736D24DC57D5007E4012 /* PlainAddress.swift in Sources */, 0AF2737124DC57FA007E4012 /* IncomingEtherNotification.swift in Sources */, ); diff --git a/Multisig/Data/Services/Notification Service/RegisterNotificationTokenRequest.swift b/Multisig/Data/Services/Notification Service/RegisterNotificationTokenRequest.swift index 955d476f3..fcc8e8450 100644 --- a/Multisig/Data/Services/Notification Service/RegisterNotificationTokenRequest.swift +++ b/Multisig/Data/Services/Notification Service/RegisterNotificationTokenRequest.swift @@ -17,7 +17,7 @@ struct RegisterNotificationTokenRequest: JSONRequest { let version: String let deviceType: String = "IOS" let buildNumber: String - let timestamp: String = String(Int(Date().timeIntervalSince1970 * 1_000)) + let timestamp: String let signatures: [String]? var httpMethod: String { return "POST" } @@ -25,13 +25,21 @@ struct RegisterNotificationTokenRequest: JSONRequest { typealias ResponseType = Response - init(deviceID: UUID, safes: [Address], token: String, bundle: String, version: String, buildNumber: String) throws { + init(deviceID: UUID, + safes: [Address], + token: String, + bundle: String, + version: String, + buildNumber: String, + timestamp: String = String(Int(Date().timeIntervalSince1970 * 1_000))) throws { + self.uuid = deviceID.uuidString.lowercased() self.safes = safes.map { $0.checksummed } self.cloudMessagingToken = token self.bundle = bundle self.version = version self.buildNumber = buildNumber + self.timestamp = timestamp let string = [ "gnosis-safe", diff --git a/Multisig/Logic/Models/Signer.swift b/Multisig/Logic/Models/Signer.swift index eda0650ee..287bdd53a 100644 --- a/Multisig/Logic/Models/Signer.swift +++ b/Multisig/Logic/Models/Signer.swift @@ -10,7 +10,7 @@ import Foundation import Web3 class Signer { - struct Signature { + struct Signature: Equatable { var value: String var signer: String } diff --git a/Multisig/UI/App/RemoteNotificationHandler.swift b/Multisig/UI/App/RemoteNotificationHandler.swift index 56a754202..684ba809b 100644 --- a/Multisig/UI/App/RemoteNotificationHandler.swift +++ b/Multisig/UI/App/RemoteNotificationHandler.swift @@ -76,6 +76,12 @@ class RemoteNotificationHandler { unregister(address: address) } + /// For add / remove signing key + func signingKeyUpdated() { + logDebug("Signing key updated") + registerAll() + } + func received(notification userInfo: [AnyHashable: Any]) { assert(Thread.isMainThread) logDebug("Received notification: \(userInfo)") diff --git a/Multisig/UI/App/UIKit/AppSettingsViewController/AppSettingsViewController.swift b/Multisig/UI/App/UIKit/AppSettingsViewController/AppSettingsViewController.swift index c2bcb1388..6a8c72127 100644 --- a/Multisig/UI/App/UIKit/AppSettingsViewController/AppSettingsViewController.swift +++ b/Multisig/UI/App/UIKit/AppSettingsViewController/AppSettingsViewController.swift @@ -165,6 +165,7 @@ class AppSettingsViewController: UITableViewController { try App.shared.keychainService.removeData( forKey: KeychainKey.ownerPrivateKey.rawValue) AppSettings.setSigningKeyAddress(nil) + App.shared.notificationHandler.signingKeyUpdated() App.shared.snackbar.show(message: "Owner key removed from this app") Tracker.shared.setUserProperty("0", for: TrackingUserProperty.numKeysImported) self.reload() diff --git a/Multisig/UI/Settings/App Settings/Owner Wallet Management/SelectOwnerAddressViewModel.swift b/Multisig/UI/Settings/App Settings/Owner Wallet Management/SelectOwnerAddressViewModel.swift index 3952a1d82..b6ccf8dc0 100644 --- a/Multisig/UI/Settings/App Settings/Owner Wallet Management/SelectOwnerAddressViewModel.swift +++ b/Multisig/UI/Settings/App Settings/Owner Wallet Management/SelectOwnerAddressViewModel.swift @@ -57,6 +57,7 @@ class SelectOwnerAddressViewModel: ObservableObject { try App.shared.keychainService.removeData(forKey: KeychainKey.ownerPrivateKey.rawValue) try App.shared.keychainService.save(data: pkData, forKey: KeychainKey.ownerPrivateKey.rawValue) AppSettings.setSigningKeyAddress(addresses[selectedIndex].checksummed) + App.shared.notificationHandler.signingKeyUpdated() App.shared.snackbar.show(message: "Owner key successfully imported") Tracker.shared.setUserProperty("1", for: TrackingUserProperty.numKeysImported) return true diff --git a/MultisigTests/Data/Services/Safe Transaction Service/RegisterNotificationTokenRequestTests.swift b/MultisigTests/Data/Services/Safe Transaction Service/RegisterNotificationTokenRequestTests.swift new file mode 100644 index 000000000..401b152b6 --- /dev/null +++ b/MultisigTests/Data/Services/Safe Transaction Service/RegisterNotificationTokenRequestTests.swift @@ -0,0 +1,33 @@ +// +// RegisterNotificationTokenRequestTests.swift +// MultisigTests +// +// Created by Andrey Scherbovich on 25.11.20. +// Copyright © 2020 Gnosis Ltd. All rights reserved. +// + +import XCTest +@testable import Multisig + +class RegisterNotificationTokenRequestTests: XCTestCase { + let mockStore = MockSecureStore() + + override func setUpWithError() throws { + App.shared.keychainService = mockStore + try! mockStore.save(data: Data(hex: "0xe7979e5f2ceb1d4ef76019d1fdba88b50ceefe0575bbfdf94969837c50a5d895"), + forKey: KeychainKey.ownerPrivateKey.rawValue) + } + + + func testRequestInitCalculatesProperSignature() throws { + let request = try RegisterNotificationTokenRequest( + deviceID: UUID(uuidString: "bb30cd3e-e0ad-4e9a-b726-44db67a0820b")!, + safes: [Address("0xEefFcdEAB4AC6005E90566B08EAda3994A573C1E")], + token: "erXBYb-CxU1jtSvwfZrxqW:APA91bH0IWkMWOGizlbNAwxV6OVjEmNR1feRs2WBT7BE6aVMm2C-x1COKqNYq19t5YNjIzVBKDyVVEqFojlkvEtiSaJA0lCZL0LfuEwfc8p9jfBuM6HG82pczVbnMev1J0gXlB3bIlAP", + bundle: "io.gnosis.multisig.dev.rinkeby", + version: "2.6.0", + buildNumber: "1", + timestamp: "1606319110027") + XCTAssertEqual(request.signatures, ["460ab62322407376576be061a6bfaaaa78cd1be4e0421d88cd635d0568ff2d473280d0edfd898bf1fd73f3fea8206ef91d6fbf6d9dc63b5d7d1378b8e4059f691c"]) + } +} diff --git a/MultisigTests/Logic/Models/SignerTests.swift b/MultisigTests/Logic/Models/SignerTests.swift index dbe726a52..38d1d7ecf 100644 --- a/MultisigTests/Logic/Models/SignerTests.swift +++ b/MultisigTests/Logic/Models/SignerTests.swift @@ -10,15 +10,19 @@ import XCTest @testable import Multisig class SignerTests: XCTestCase { - override class func setUp() { + let mockStore = MockSecureStore() + + override func setUpWithError() throws { super.setUp() - App.shared.keychainService = MockSecureStore() + App.shared.keychainService = mockStore + try! mockStore.save(data: Data(hex: "0xe7979e5f2ceb1d4ef76019d1fdba88b50ceefe0575bbfdf94969837c50a5d895"), + forKey: KeychainKey.ownerPrivateKey.rawValue) } func testSigner() throws { -// let string = "gnosis-safe33971c4e-fb98-4e18-a08d-13c881ae292a0x4dEBDD6CEe25b2F931D2FE265D70e1a533B024530x72ac1760daF52986421b1552BdCa04707E78950edSh5Se1XgEiTiY-4cv1ixY:APA91bG3vYjy9VgB3X3u5EsBphJABchb8Xgg2cOSSekPsxDsfE5xyBeu6gKY0wNhbJHgQUQQGocrHx0Shbx6JMFx2VOyhJx079AduN01NWD1-WjQerY5s3l-cLnHoNNn8fJfARqSUb3Gio.gnosis.multisig.prod.mainnet2.7.0IOS1991605186645155" -// let expectedSignature = Signer.Signature(value: <#T##String#>, signer: <#T##String#>) -// XCTAssertEqual(Signer.sign(string), <#T##expression2: Equatable##Equatable#>) + let string = "gnosis-safe" + let expected = Signer.Signature(value: "99a7a03e9597e85a0cc4188d270b72b1df2de943de804f144976f4c1e23116ff274d2dec4ee7201b88bdadf08259a5dc8e7e2bbf372347de3470beeab904e5d01b", + signer: "0x728cafe9fB8CC2218Fb12a9A2D9335193caa07e0") + XCTAssertEqual(try Signer.sign(string), expected) } - } diff --git a/NotificationServiceExtension/NotificationService.swift b/NotificationServiceExtension/NotificationService.swift index 3e14f5399..aa9e09b13 100644 --- a/NotificationServiceExtension/NotificationService.swift +++ b/NotificationServiceExtension/NotificationService.swift @@ -24,7 +24,8 @@ class NotificationService: UNNotificationServiceExtension { NewConfirmationNotification.self, ExecutedMultisigTransactionNotification.self, IncomingTokenNotification.self, - IncomingEtherNotification.self + IncomingEtherNotification.self, + ConfirmationRequestNotification.self ] as [MultisigNotification.Type]) .compactMap({ $0.init(payload: payload) }) .first diff --git a/NotificationServiceExtension/Notifications/ConfirmationRequestNotification.swift b/NotificationServiceExtension/Notifications/ConfirmationRequestNotification.swift new file mode 100644 index 000000000..880e1e6fd --- /dev/null +++ b/NotificationServiceExtension/Notifications/ConfirmationRequestNotification.swift @@ -0,0 +1,33 @@ +// +// ConfirmationRequestNotification.swift +// NotificationServiceExtension +// +// Created by Andrey Scherbovich on 25.11.20. +// Copyright © 2020 Gnosis Ltd. All rights reserved. +// + +import Foundation + +struct ConfirmationRequestNotification: MultisigNotification { + let address: PlainAddress + + init?(payload: NotificationPayload) { + guard + let rawType = payload.type, + let type = NotificationType(rawValue: rawType), + type == .confirmationRequest, + let address = PlainAddress(payload.address) + else { + return nil + } + self.address = address + } + + var localizedTitle: String { + "Confirmation required" + } + + var localizedBody: String { + "\(address.truncatedInMiddle): A transaction requires your confirmation!" + } +} diff --git a/NotificationServiceExtension/Notifications/MultisigNotification.swift b/NotificationServiceExtension/Notifications/MultisigNotification.swift index 4e8d41e87..df906f8a4 100644 --- a/NotificationServiceExtension/Notifications/MultisigNotification.swift +++ b/NotificationServiceExtension/Notifications/MultisigNotification.swift @@ -13,6 +13,7 @@ enum NotificationType: String { case incomingToken = "INCOMING_TOKEN" case executedMultisigTx = "EXECUTED_MULTISIG_TRANSACTION" case newConfirmation = "NEW_CONFIRMATION" + case confirmationRequest = "CONFIRMATION_REQUEST" } protocol MultisigNotification {