From bf7cb33ba2619d52b3d40b6866a0cf76286333ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Czekan=CC=81ski?= Date: Thu, 22 Aug 2019 13:51:52 +0200 Subject: [PATCH 1/6] Update project to Swift 5 --- .gitignore | 3 ++ Noti.xcodeproj/project.pbxproj | 12 +++---- Noti/AppDelegate.swift | 32 ++++++++--------- Noti/Ephemerals.swift | 6 ++-- Noti/Info.plist | 13 +++++++ Noti/PreferencesViewController.swift | 2 +- Noti/PushManager.swift | 4 +-- Noti/StatusMenuController.swift | 4 +-- Podfile | 16 ++++----- Podfile.lock | 51 ++++++++++++++-------------- 10 files changed, 78 insertions(+), 65 deletions(-) diff --git a/.gitignore b/.gitignore index 8898cb9..2d6d1cd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,5 @@ Pods/ build/ + +xcuserdata/ +.idea/ \ No newline at end of file diff --git a/Noti.xcodeproj/project.pbxproj b/Noti.xcodeproj/project.pbxproj index 697176c..d71bd3a 100644 --- a/Noti.xcodeproj/project.pbxproj +++ b/Noti.xcodeproj/project.pbxproj @@ -194,7 +194,7 @@ files = ( ); inputPaths = ( - "${SRCROOT}/Pods/Target Support Files/Pods-Noti/Pods-Noti-frameworks.sh", + "${PODS_ROOT}/Target Support Files/Pods-Noti/Pods-Noti-frameworks.sh", "${BUILT_PRODUCTS_DIR}/Alamofire/Alamofire.framework", "${BUILT_PRODUCTS_DIR}/CryptoSwift/CryptoSwift.framework", "${BUILT_PRODUCTS_DIR}/EMCLoginItem/EMCLoginItem.framework", @@ -219,7 +219,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Noti/Pods-Noti-frameworks.sh\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Noti/Pods-Noti-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; C46969CE10D5230F33E29F6C /* [CP] Check Pods Manifest.lock */ = { @@ -390,11 +390,11 @@ "$(inherited)", "$(PROJECT_DIR)/SwCrypt/build/Debug", ); - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.12; PRODUCT_BUNDLE_IDENTIFIER = io.jari.Noti; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_SWIFT3_OBJC_INFERENCE = On; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 5.0; }; name = Debug; }; @@ -416,11 +416,11 @@ "$(inherited)", "$(PROJECT_DIR)/SwCrypt/build/Debug", ); - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.12; PRODUCT_BUNDLE_IDENTIFIER = io.jari.Noti; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_SWIFT3_OBJC_INFERENCE = On; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 5.0; }; name = Release; }; diff --git a/Noti/AppDelegate.swift b/Noti/AppDelegate.swift index 3097fb2..2b69a09 100644 --- a/Noti/AppDelegate.swift +++ b/Noti/AppDelegate.swift @@ -13,36 +13,36 @@ let log = SwiftyBeaver.self @NSApplicationMain class AppDelegate: NSObject, NSApplicationDelegate { - + var pushManager: PushManager? let userDefaults: UserDefaults = UserDefaults.standard var iwc:NSWindowController?; var pwc:NSWindowController?; - + func setPassword(password: String) { pushManager?.setPassword(password: password) } - + func loadPushManager() { let token = userDefaults.string(forKey: "token") - + if(token != nil) { pushManager = PushManager(token: token!) } else { print("WARN: PushManager not initialized because of missing token. Displaying Intro") - + if(pushManager != nil) { pushManager!.disconnect() } - - let storyboard = NSStoryboard(name: NSStoryboard.Name(rawValue: "Main"), bundle: nil) - iwc = storyboard.instantiateController(withIdentifier: NSStoryboard.SceneIdentifier(rawValue: "IntroWindowController")) as? NSWindowController + + let storyboard = NSStoryboard(name: "Main", bundle: nil) + iwc = storyboard.instantiateController(withIdentifier: "IntroWindowController") as? NSWindowController NSApplication.shared.activate(ignoringOtherApps: true) iwc!.showWindow(self) iwc!.window?.makeKeyAndOrderFront(self) } } - + func displayPreferencesWindow() { if(pushManager == nil) { let alert = NSAlert() @@ -50,14 +50,14 @@ class AppDelegate: NSObject, NSApplicationDelegate { alert.runModal() return; } - - let storyboard = NSStoryboard(name: NSStoryboard.Name(rawValue: "Main"), bundle: nil) - pwc = storyboard.instantiateController(withIdentifier: NSStoryboard.SceneIdentifier(rawValue: "PreferencesWindowController")) as? NSWindowController + + let storyboard = NSStoryboard(name: "Main", bundle: nil) + pwc = storyboard.instantiateController(withIdentifier: "PreferencesWindowController") as? NSWindowController NSApplication.shared.activate(ignoringOtherApps: true) pwc!.showWindow(self) pwc!.window?.makeKeyAndOrderFront(self) } - + func applicationDidFinishLaunching(_ aNotification: Notification) { let file = FileDestination() // Adding file destination of log output let console = ConsoleDestination() // log to Xcode Console @@ -73,11 +73,11 @@ class AppDelegate: NSObject, NSApplicationDelegate { log.addDestination(console) loadPushManager() } - + func applicationWillTerminate(_ aNotification: Notification) { // Insert code here to tear down your application log.info("Going to terminate Noti.") } - - + + } diff --git a/Noti/Ephemerals.swift b/Noti/Ephemerals.swift index f3f9591..f9b2ec3 100644 --- a/Noti/Ephemerals.swift +++ b/Noti/Ephemerals.swift @@ -46,7 +46,7 @@ class Ephemerals: NSObject { Alamofire.request("https://api.pushbullet.com/v2/ephemerals", method: .post, parameters: body.dictionaryObject!, encoding: JSONEncoding.default, headers: headers) .responseString { response in - var result = JSON.parse(response.result.value!) + var result = JSON(parseJSON: response.result.value!) if(response.response?.statusCode != 200) { let alert = NSAlert() @@ -93,11 +93,11 @@ class Ephemerals: NSObject { Alamofire.request("https://api.pushbullet.com/v3/get-permanent", method: .post, parameters: body.dictionaryObject!, encoding: JSONEncoding.default, headers: headers) .responseString { response in debugPrint(response) - var parsed = JSON.parse(response.result.value!) + var parsed = JSON(parseJSON: response.result.value!) //decrypt if needed.... if self.crypt != nil && parsed["data"]["encrypted"].exists() { - parsed["data"] = JSON.parse((self.crypt!.decryptMessage(parsed["data"]["ciphertext"].string!))!) + parsed["data"] = JSON(parseJSON: (self.crypt!.decryptMessage(parsed["data"]["ciphertext"].string!))!) } if let threads = parsed["data"]["threads"].array { diff --git a/Noti/Info.plist b/Noti/Info.plist index 08718c0..f671321 100644 --- a/Noti/Info.plist +++ b/Noti/Info.plist @@ -20,6 +20,19 @@ 0.3.2 CFBundleSignature ???? + CFBundleURLTypes + + + CFBundleTypeRole + Viewer + CFBundleURLName + Noti + CFBundleURLSchemes + + noti + + + CFBundleVersion 11 LSApplicationCategoryType diff --git a/Noti/PreferencesViewController.swift b/Noti/PreferencesViewController.swift index d099cb6..eb2f41a 100644 --- a/Noti/PreferencesViewController.swift +++ b/Noti/PreferencesViewController.swift @@ -52,7 +52,7 @@ open class PreferencesViewController: NSViewController { } } - open override func controlTextDidChange(_ obj: Notification) { + open func controlTextDidChange(_ obj: Notification) { //gets called every time password changes if(encryptionField.stringValue == FAKE_PASSWORD) { return; diff --git a/Noti/PushManager.swift b/Noti/PushManager.swift index cdfff24..3427a0a 100644 --- a/Noti/PushManager.swift +++ b/Noti/PushManager.swift @@ -227,8 +227,8 @@ class PushManager: NSObject, WebSocketDelegate, NSUserNotificationCenterDelegate } //determine if we replied to a sms or a normal notification - if (notification.identifier?.characters.count)! > 4 { - let index = notification.identifier?.characters.index((notification.identifier?.startIndex)!, offsetBy: 4) + if (notification.identifier?.count)! > 4 { + let index = notification.identifier?.index((notification.identifier?.startIndex)!, offsetBy: 4) if notification.identifier?.substring(to: index!) == "sms_" { var indexToBeRemoved = -1, i = -1; for item in pushHistory { diff --git a/Noti/StatusMenuController.swift b/Noti/StatusMenuController.swift index 6bc00fd..8fd14ef 100644 --- a/Noti/StatusMenuController.swift +++ b/Noti/StatusMenuController.swift @@ -22,7 +22,7 @@ class StatusMenuController: NSObject, NSUserNotificationCenterDelegate { appDelegate = NSApplication.shared.delegate as? AppDelegate if let button = statusItem.button { - button.image = NSImage(named: NSImage.Name(rawValue: "StatusBarButtonImageFail")) + button.image = NSImage(named: "StatusBarButtonImageFail") statusItem.menu = menu; button.appearsDisabled = true } @@ -39,7 +39,7 @@ class StatusMenuController: NSObject, NSUserNotificationCenterDelegate { } if let disabled = info["disabled"] as? Bool { - statusItem.button?.image = NSImage(named: NSImage.Name(rawValue: disabled ? "StatusBarButtonImageFail" : "StatusBarButtonImage")) + statusItem.button?.image = NSImage(named: disabled ? "StatusBarButtonImageFail" : "StatusBarButtonImage") statusItem.button?.appearsDisabled = disabled } diff --git a/Podfile b/Podfile index 6da1403..cd4efe5 100644 --- a/Podfile +++ b/Podfile @@ -1,14 +1,12 @@ use_frameworks! -source 'https://github.com/CocoaPods/Specs.git' -source 'https://github.com/jariz/Noti-Spec.git' target 'Noti' do - pod 'Starscream', '~> 3.0.2' - pod 'SwiftyJSON', '~> 3.1.1' - pod 'Alamofire', '~> 4.0.1' - pod 'Sparkle', '~> 1.14.0' - pod 'CryptoSwift', '~> 0.8.0' - pod 'SwCrypt', '4.0.0' + pod 'Starscream', '~> 3.1.0' + pod 'SwiftyJSON', '~> 5.0.0' + pod 'Alamofire', '~> 4.8.2' + pod 'Sparkle', '~> 1.21.3' + pod 'CryptoSwift', '~> 1.0.0' + pod 'SwCrypt', '~> 5.1.3' pod 'EMCLoginItem', '~> 1.0.1' - pod 'SwiftyBeaver' + pod 'SwiftyBeaver', '~> 1.7.0' end diff --git a/Podfile.lock b/Podfile.lock index 125cf7c..396a8d9 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -1,22 +1,22 @@ PODS: - - Alamofire (4.0.1) - - CryptoSwift (0.8.0) + - Alamofire (4.8.2) + - CryptoSwift (1.0.0) - EMCLoginItem (1.0.1) - - Sparkle (1.14.0) - - Starscream (3.0.3) - - SwCrypt (4.0.0) - - SwiftyBeaver (1.5.0) - - SwiftyJSON (3.1.4) + - Sparkle (1.21.3) + - Starscream (3.1.0) + - SwCrypt (5.1.3) + - SwiftyBeaver (1.7.0) + - SwiftyJSON (5.0.0) DEPENDENCIES: - - Alamofire (~> 4.0.1) - - CryptoSwift (~> 0.8.0) + - Alamofire (~> 4.8.2) + - CryptoSwift (~> 1.0.0) - EMCLoginItem (~> 1.0.1) - - Sparkle (~> 1.14.0) - - Starscream (~> 3.0.2) - - SwCrypt (= 4.0.0) - - SwiftyBeaver - - SwiftyJSON (~> 3.1.1) + - Sparkle (~> 1.21.3) + - Starscream (~> 3.1.0) + - SwCrypt (~> 5.1.3) + - SwiftyBeaver (~> 1.7.0) + - SwiftyJSON (~> 5.0.0) SPEC REPOS: https://github.com/cocoapods/specs.git: @@ -25,21 +25,20 @@ SPEC REPOS: - EMCLoginItem - Sparkle - Starscream + - SwCrypt - SwiftyBeaver - SwiftyJSON - https://github.com/jariz/Noti-Spec.git: - - SwCrypt SPEC CHECKSUMS: - Alamofire: 7682d43245de14874acd142ec137b144aa1dd335 - CryptoSwift: f81907430ebbe38b8404b4f6fb40f3c576a8755f - EMCLoginItem: ac4a198cda3f4a8dc72797d2e89ad6c640624835 - Sparkle: ccd95233b12a3e3d4eeb55ff01dd4c8bb8188b07 - Starscream: dfb1b3f39506717ee52b67fb48de0c8269504cbc - SwCrypt: 107e7a971662bd10212a696354f943b127de371d - SwiftyBeaver: 384235c59d9c67b4a5e1810cffa713babba34efd - SwiftyJSON: c2842d878f95482ffceec5709abc3d05680c0220 + Alamofire: ceb123cfeef758ac73979feab6d37669a989d95a + CryptoSwift: 75445c957b8560194609f7e3a5fb5e8db2c9858d + EMCLoginItem: 3ee01265e84238567ed7e37a79d2edfe5cabe626 + Sparkle: 05f0ffe7098dd917ce4afc437731d48f54148347 + Starscream: e34cec5e0923e66ed357380dcb79235e054cb2b0 + SwCrypt: a95efc9b8d8ded58b8ef94ab167e50856476767c + SwiftyBeaver: faff13c9b69e467c2d21425a0f8e5efaed8e6a35 + SwiftyJSON: 58f5e2faedded0d4d222e85f7fed37bb6ee58ff1 -PODFILE CHECKSUM: 7481dc9ca87f0ae2c2cf3cd704c80fb346debf7e +PODFILE CHECKSUM: 5d0d055acb3bdedb382b063b336f1a2f70a36535 -COCOAPODS: 1.5.3 +COCOAPODS: 1.7.5 From 1992aedf86ab92ec29fc529f433d0c11e84fcf8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Czekan=CC=81ski?= Date: Thu, 22 Aug 2019 13:59:41 +0200 Subject: [PATCH 2/6] Moved login flow to external browser access token is received using deep link to application --- Noti.xcodeproj/project.pbxproj | 5 +-- Noti/AppDelegate.swift | 14 ++++++ Noti/AuthViewController.swift | 55 ----------------------- Noti/Base.lproj/Main.storyboard | 80 +++++++++------------------------ Noti/Info.plist | 2 +- Noti/IntroViewController.swift | 9 ++-- 6 files changed, 41 insertions(+), 124 deletions(-) delete mode 100644 Noti/AuthViewController.swift diff --git a/Noti.xcodeproj/project.pbxproj b/Noti.xcodeproj/project.pbxproj index d71bd3a..f0e5cbe 100644 --- a/Noti.xcodeproj/project.pbxproj +++ b/Noti.xcodeproj/project.pbxproj @@ -17,7 +17,6 @@ 499DD0F81D2D7BBF00BB759D /* Ephemerals.swift in Sources */ = {isa = PBXBuildFile; fileRef = 499DD0F71D2D7BBF00BB759D /* Ephemerals.swift */; }; 499DD0FC1D2D8FAC00BB759D /* RoundedImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 499DD0FB1D2D8FAC00BB759D /* RoundedImage.swift */; }; 49B43B0C1D1ADFB200A24585 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49B43B0B1D1ADFB200A24585 /* AppDelegate.swift */; }; - 49B43B0E1D1ADFB200A24585 /* AuthViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49B43B0D1D1ADFB200A24585 /* AuthViewController.swift */; }; 49B43B101D1ADFB200A24585 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 49B43B0F1D1ADFB200A24585 /* Assets.xcassets */; }; 49B43B131D1ADFB200A24585 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 49B43B111D1ADFB200A24585 /* Main.storyboard */; }; 49B43B1B1D1AE0F200A24585 /* Podfile in Resources */ = {isa = PBXBuildFile; fileRef = 49B43B1A1D1AE0F200A24585 /* Podfile */; }; @@ -37,7 +36,6 @@ 49AE29531DB4D86E00B61843 /* SwCrypt.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwCrypt.framework; path = SwCrypt/build/Debug/SwCrypt.framework; sourceTree = ""; }; 49B43B081D1ADFB200A24585 /* Noti.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Noti.app; sourceTree = BUILT_PRODUCTS_DIR; }; 49B43B0B1D1ADFB200A24585 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - 49B43B0D1D1ADFB200A24585 /* AuthViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthViewController.swift; sourceTree = ""; }; 49B43B0F1D1ADFB200A24585 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 49B43B121D1ADFB200A24585 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 49B43B141D1ADFB200A24585 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -82,7 +80,6 @@ isa = PBXGroup; children = ( 49B43B0B1D1ADFB200A24585 /* AppDelegate.swift */, - 49B43B0D1D1ADFB200A24585 /* AuthViewController.swift */, 49B43B0F1D1ADFB200A24585 /* Assets.xcassets */, 49B43B111D1ADFB200A24585 /* Main.storyboard */, 4901D3D21D23E6AB005F1641 /* IntroViewController.swift */, @@ -160,6 +157,7 @@ developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( + English, en, Base, ); @@ -251,7 +249,6 @@ 499DD0FC1D2D8FAC00BB759D /* RoundedImage.swift in Sources */, 491E887B1FC588F70048215F /* PreferencesViewController.swift in Sources */, 498499FE1D1BE46F007FC963 /* PushManager.swift in Sources */, - 49B43B0E1D1ADFB200A24585 /* AuthViewController.swift in Sources */, 4901D3CF1D1C121D005F1641 /* StatusMenuController.swift in Sources */, 49B43B0C1D1ADFB200A24585 /* AppDelegate.swift in Sources */, 499DD0F81D2D7BBF00BB759D /* Ephemerals.swift in Sources */, diff --git a/Noti/AppDelegate.swift b/Noti/AppDelegate.swift index 2b69a09..aa841e5 100644 --- a/Noti/AppDelegate.swift +++ b/Noti/AppDelegate.swift @@ -79,5 +79,19 @@ class AppDelegate: NSObject, NSApplicationDelegate { log.info("Going to terminate Noti.") } + func application(_ application: NSApplication, open urls: [URL]) { + // Extract access token from redirect uri + guard let fragment = urls.first?.fragment else { return } + let parts = fragment.split(separator: "=") + if parts.count != 2 { + return + } + + let token = parts[1] + userDefaults.setValue(token, forKeyPath: "token") + loadPushManager() + + NotificationCenter.default.post(name: Notification.Name(rawValue: "AuthSuccess"), object: nil) + } } diff --git a/Noti/AuthViewController.swift b/Noti/AuthViewController.swift deleted file mode 100644 index ee10c33..0000000 --- a/Noti/AuthViewController.swift +++ /dev/null @@ -1,55 +0,0 @@ -// -// ViewController.swift -// Noti -// -// Created by Jari on 22/06/16. -// Copyright © 2016 Jari Zwarts. All rights reserved. -// - -import Cocoa -import WebKit - -class AuthViewController: NSViewController, WebFrameLoadDelegate { - - @IBOutlet weak var webView: WebView! - let userDefaults: UserDefaults = UserDefaults.standard - - override func viewDidLoad() { - super.viewDidLoad() - - let req = URLRequest(url:URL(string:"https://www.pushbullet.com/authorize?client_id=QTVK7zATuEcu4sME8TrwLBMuoW7vC7Wr&redirect_uri=about:blank&response_type=token&scope=everything")!) - webView.frameLoadDelegate = self - webView.mainFrame.load(req) - } - - override func viewDidAppear() { - if (self.view.window != nil) { - self.view.window!.appearance = NSAppearance(named: NSAppearance.Name.vibrantDark) - self.view.window!.invalidateShadow() - } - } - - func webView(_ sender: WebView!, didFinishLoadFor frame: WebFrame!) { - //remove fugly loader that is stuck on page, even after loading (pushbullet get your shit together pls ty) - sender.stringByEvaluatingJavaScript(from: "var uglyDivs = document.querySelectorAll(\"#onecup .agree-page div:not(#header), #onecup .agree-page #header\"); if(uglyDivs.length > 0) for (var i = 0; i < uglyDivs.length; i++) uglyDivs[i].remove()") - - if let ds = frame.dataSource { - if let url = ds.response.url { - if url.absoluteString.hasPrefix("about:blank") { - let token = (url.absoluteString as NSString).substring(from: 27) - let appDelegate = NSApplication.shared.delegate as! AppDelegate - - print("Got token!", token, "saving and restarting PushManager") - - userDefaults.setValue(token, forKeyPath: "token") - appDelegate.loadPushManager() - - self.view.window?.close() - NotificationCenter.default.post(name: Notification.Name(rawValue: "AuthSuccess"), object: nil) - } - } - } - } - -} - diff --git a/Noti/Base.lproj/Main.storyboard b/Noti/Base.lproj/Main.storyboard index f856840..d4067d7 100644 --- a/Noti/Base.lproj/Main.storyboard +++ b/Noti/Base.lproj/Main.storyboard @@ -1,11 +1,9 @@ - + - - + - @@ -692,10 +690,13 @@ - + + + + @@ -716,7 +717,7 @@ - + @@ -730,7 +731,7 @@ - + @@ -741,7 +742,7 @@ Whenever your devices have something to say, I'll let you know! - + @@ -789,10 +790,13 @@ Whenever your devices have something to say, I'll let you know! - + + + + @@ -835,7 +839,7 @@ Whenever your devices have something to say, I'll let you know! - + @@ -844,7 +848,7 @@ Whenever your devices have something to say, I'll let you know! - + @@ -886,7 +890,7 @@ Whenever your devices have something to say, I'll let you know! - + @@ -904,7 +908,7 @@ Whenever your devices have something to say, I'll let you know! - + @@ -922,12 +926,12 @@ Whenever your devices have something to say, I'll let you know! - + - + NSAllRomanInputSourcesLocaleIdentifier @@ -937,7 +941,7 @@ Whenever your devices have something to say, I'll let you know! - + @@ -983,51 +987,9 @@ Whenever your devices have something to say, I'll let you know! - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + diff --git a/Noti/Info.plist b/Noti/Info.plist index f671321..176d798 100644 --- a/Noti/Info.plist +++ b/Noti/Info.plist @@ -17,7 +17,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 0.3.2 + 0.3.3 CFBundleSignature ???? CFBundleURLTypes diff --git a/Noti/IntroViewController.swift b/Noti/IntroViewController.swift index 531ef28..b1e5cc1 100644 --- a/Noti/IntroViewController.swift +++ b/Noti/IntroViewController.swift @@ -9,6 +9,8 @@ import Cocoa class IntroViewController: NSViewController { + let clientId = "lIdYYNaWmj7ZJaCaycRXevhQz9yhdeJS" + let redirectUri = "noti://redirect" var awc:NSWindowController?; override func viewDidAppear() { @@ -55,10 +57,7 @@ class IntroViewController: NSViewController { } @IBAction func startAuth(_ sender: AnyObject) { - let storyboard = NSStoryboard(name: NSStoryboard.Name(rawValue: "Main"), bundle: nil) - - awc = storyboard.instantiateController(withIdentifier: NSStoryboard.SceneIdentifier(rawValue: "AuthWindowController")) as? NSWindowController - print("showWindow") - awc!.showWindow(self) + let url = URL(string: "https://www.pushbullet.com/authorize?client_id=\(clientId)&redirect_uri=\(redirectUri)&response_type=token&scope=everything") + NSWorkspace.shared.open(url!) } } From f529883a6e4ea43ace8fee5d7ef99880f4c247c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Czekan=CC=81ski?= Date: Thu, 22 Aug 2019 14:26:27 +0200 Subject: [PATCH 3/6] Use Xcode11 image for Travis builds --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 21e0f1b..e20d2cf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,4 @@ -osx_image: xcode9.1 +osx_image: xcode11 language: objective-c xcode_workspace: Noti.xcworkspace xcode_scheme: Noti From a263a2d34c9d6b2f6d39b9ab3e6a60e44905034a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Czekan=CC=81ski?= Date: Thu, 22 Aug 2019 17:17:38 +0200 Subject: [PATCH 4/6] Prevent replay attack by adding random nonce to redirect_uri --- Noti/AppDelegate.swift | 16 +++++++---- Noti/IntroViewController.swift | 52 ++++++++++++++++++++++++++-------- 2 files changed, 50 insertions(+), 18 deletions(-) diff --git a/Noti/AppDelegate.swift b/Noti/AppDelegate.swift index aa841e5..bd47790 100644 --- a/Noti/AppDelegate.swift +++ b/Noti/AppDelegate.swift @@ -81,17 +81,21 @@ class AppDelegate: NSObject, NSApplicationDelegate { func application(_ application: NSApplication, open urls: [URL]) { // Extract access token from redirect uri - guard let fragment = urls.first?.fragment else { return } + guard let url = URLComponents(url: urls.first!, resolvingAgainstBaseURL: true) else { return } + guard let token = url.fragment?.split(separator: "=")[1] else { return } + guard let receivedNonce = url.queryItems?.first(where: { $0.name == "nonce" })?.value else { return } + guard let storedNonce = userDefaults.string(forKey: "nonce") else { return } - let parts = fragment.split(separator: "=") - if parts.count != 2 { + // Verify nonce + if receivedNonce != storedNonce { return } - - let token = parts[1] + + // Dispose to prevent replays + userDefaults.removeObject(forKey: "nonce") + userDefaults.setValue(token, forKeyPath: "token") loadPushManager() - NotificationCenter.default.post(name: Notification.Name(rawValue: "AuthSuccess"), object: nil) } } diff --git a/Noti/IntroViewController.swift b/Noti/IntroViewController.swift index b1e5cc1..19f039d 100644 --- a/Noti/IntroViewController.swift +++ b/Noti/IntroViewController.swift @@ -9,10 +9,11 @@ import Cocoa class IntroViewController: NSViewController { + let authUrl = "https://www.pushbullet.com/authorize" let clientId = "lIdYYNaWmj7ZJaCaycRXevhQz9yhdeJS" let redirectUri = "noti://redirect" var awc:NSWindowController?; - + override func viewDidAppear() { if (self.view.window != nil) { self.view.window!.appearance = NSAppearance(named: NSAppearance.Name.vibrantDark) @@ -20,44 +21,71 @@ class IntroViewController: NSViewController { self.view.window!.isMovableByWindowBackground = true self.view.window!.invalidateShadow() } - + NotificationCenter.default.addObserver(self, selector: #selector(IntroViewController.authSuccess(_:)), name:NSNotification.Name(rawValue: "AuthSuccess"), object: nil) } - + @IBOutlet weak var authBtn:NSButton!; @IBOutlet weak var authTxt:NSTextField!; @IBOutlet weak var authImg:NSImageView!; - + @objc func authSuccess(_ notification: Notification) { authBtn.isEnabled = false self.authTxt.alphaValue = 1 self.authTxt.alphaValue = self.authBtn.alphaValue //self.view.window!.styleMask.subtract(NSClosableWindowMask) - + NSAnimationContext.runAnimationGroup({ (context) -> Void in context.duration = 0.50 self.authTxt.animator().alphaValue = 0 self.authBtn.animator().alphaValue = 0 - + }, completionHandler: { () -> Void in self.authTxt.isHidden = true self.authBtn.isHidden = true self.authImg.isHidden = false self.authImg.alphaValue = 0 - + NSAnimationContext.runAnimationGroup({ (context) -> Void in context.duration = 0.50 self.authImg.animator().alphaValue = 1 }, completionHandler: nil) - + }) - + Timer.scheduledTimer(timeInterval: 3, target: BlockOperation(block: self.view.window!.close), selector: #selector(Operation.main), userInfo: nil, repeats: false) } - + + private func generateNonce() -> String? { + var bytes = [UInt8](repeating: 0, count: 16) + if SecRandomCopyBytes(kSecRandomDefault, bytes.count, &bytes) != errSecSuccess { + return nil + } + return bytes.toHexString() + } + + private func prepareRedirectUri(nonce: String) -> String { + var url = URLComponents(string: redirectUri)! + url.queryItems = [ + URLQueryItem(name: "nonce", value: nonce) + ] + + return url.string! + } + @IBAction func startAuth(_ sender: AnyObject) { - let url = URL(string: "https://www.pushbullet.com/authorize?client_id=\(clientId)&redirect_uri=\(redirectUri)&response_type=token&scope=everything") - NSWorkspace.shared.open(url!) + guard let nonce = generateNonce() else { return } + UserDefaults.standard.set(nonce, forKey: "nonce") + + var url = URLComponents(string: authUrl)! + url.queryItems = [ + "client_id": clientId, + "response_type": "token", + "scope": "everything", + "redirect_uri": prepareRedirectUri(nonce: nonce) + ].map { URLQueryItem(name: $0, value: $1) } + + try! NSWorkspace.shared.open(url.asURL()) } } From 534980decec2ebbb8d0d9258a5b66329f384e973 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Czekan=CC=81ski?= Date: Sun, 29 Sep 2019 13:11:48 +0200 Subject: [PATCH 5/6] Store nonce in AppDelegate --- Noti/AppDelegate.swift | 6 +++--- Noti/IntroViewController.swift | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Noti/AppDelegate.swift b/Noti/AppDelegate.swift index bd47790..7438267 100644 --- a/Noti/AppDelegate.swift +++ b/Noti/AppDelegate.swift @@ -18,6 +18,7 @@ class AppDelegate: NSObject, NSApplicationDelegate { let userDefaults: UserDefaults = UserDefaults.standard var iwc:NSWindowController?; var pwc:NSWindowController?; + var nonce: String? = nil; func setPassword(password: String) { pushManager?.setPassword(password: password) @@ -84,15 +85,14 @@ class AppDelegate: NSObject, NSApplicationDelegate { guard let url = URLComponents(url: urls.first!, resolvingAgainstBaseURL: true) else { return } guard let token = url.fragment?.split(separator: "=")[1] else { return } guard let receivedNonce = url.queryItems?.first(where: { $0.name == "nonce" })?.value else { return } - guard let storedNonce = userDefaults.string(forKey: "nonce") else { return } // Verify nonce - if receivedNonce != storedNonce { + if receivedNonce != nonce { return } // Dispose to prevent replays - userDefaults.removeObject(forKey: "nonce") + nonce = nil userDefaults.setValue(token, forKeyPath: "token") loadPushManager() diff --git a/Noti/IntroViewController.swift b/Noti/IntroViewController.swift index 19f039d..afc98dd 100644 --- a/Noti/IntroViewController.swift +++ b/Noti/IntroViewController.swift @@ -9,6 +9,7 @@ import Cocoa class IntroViewController: NSViewController { + let appDelegate = NSApp.delegate as! AppDelegate let authUrl = "https://www.pushbullet.com/authorize" let clientId = "lIdYYNaWmj7ZJaCaycRXevhQz9yhdeJS" let redirectUri = "noti://redirect" @@ -76,7 +77,7 @@ class IntroViewController: NSViewController { @IBAction func startAuth(_ sender: AnyObject) { guard let nonce = generateNonce() else { return } - UserDefaults.standard.set(nonce, forKey: "nonce") + appDelegate.nonce = nonce var url = URLComponents(string: authUrl)! url.queryItems = [ From 0fb8a33bdc049011d8c195704767185376f61a13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Czekan=CC=81ski?= Date: Sun, 29 Sep 2019 14:08:18 +0200 Subject: [PATCH 6/6] Make notification_tag an optional parameter --- Noti/Ephemerals.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Noti/Ephemerals.swift b/Noti/Ephemerals.swift index f9b2ec3..3b81626 100644 --- a/Noti/Ephemerals.swift +++ b/Noti/Ephemerals.swift @@ -144,7 +144,7 @@ class Ephemerals: NSObject { "type": "push", "push": [ "notification_id": push["notification_id"].string!, - "notification_tag": push["notification_tag"].string!, + "notification_tag": push["notification_tag"].string, "package_name": push["package_name"].string!, "source_user_iden": push["source_user_iden"].string!, "type": "dismissal"