diff --git a/pass.xcodeproj/project.pbxproj b/pass.xcodeproj/project.pbxproj index 162e70e..a0fb477 100644 --- a/pass.xcodeproj/project.pbxproj +++ b/pass.xcodeproj/project.pbxproj @@ -12,6 +12,7 @@ 2C58F31EECC494C7A7F00A98 /* libPods-passKitTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = FB6C63FA1652F925B5C9F0B5 /* libPods-passKitTests.a */; }; 398A8F69C2230A8117820BB7 /* libPods-passKit.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 45BAA15189E80AA544EAF7AD /* libPods-passKit.a */; }; 6930A9D26085DE7CA1A7AACC /* libPods-passExtension.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1B240CA444AC9172F3053651 /* libPods-passExtension.a */; }; + A20691F41F2A3D0E0096483D /* SecurePasteboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = A20691F31F2A3D0E0096483D /* SecurePasteboard.swift */; }; A2168A7F1EFD40D5005EA873 /* OnePasswordExtensionConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = A2168A7E1EFD40D5005EA873 /* OnePasswordExtensionConstants.swift */; }; A217ACE21E9AB17C00A1A6CF /* OTPScannerController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A217ACE11E9AB17C00A1A6CF /* OTPScannerController.swift */; }; A217ACE41E9BBBBD00A1A6CF /* GitConfigSettingTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A217ACE31E9BBBBD00A1A6CF /* GitConfigSettingTableViewController.swift */; }; @@ -167,6 +168,7 @@ 7592A214C22CEBBEF4596CC1 /* libPods-pass.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-pass.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 7E088A9255B6CB576EF757C1 /* Pods-passKit.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-passKit.debug.xcconfig"; path = "Pods/Target Support Files/Pods-passKit/Pods-passKit.debug.xcconfig"; sourceTree = ""; }; A02ACA4077630047EA669D05 /* Pods-pass.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-pass.debug.xcconfig"; path = "Pods/Target Support Files/Pods-pass/Pods-pass.debug.xcconfig"; sourceTree = ""; }; + A20691F31F2A3D0E0096483D /* SecurePasteboard.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecurePasteboard.swift; sourceTree = ""; }; A2168A7E1EFD40D5005EA873 /* OnePasswordExtensionConstants.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnePasswordExtensionConstants.swift; sourceTree = ""; }; A217ACE11E9AB17C00A1A6CF /* OTPScannerController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OTPScannerController.swift; sourceTree = ""; }; A217ACE31E9BBBBD00A1A6CF /* GitConfigSettingTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = GitConfigSettingTableViewController.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; @@ -476,6 +478,7 @@ children = ( A2BC54C71EEE5669001FAFBD /* Objective-CBridgingHeader.h */, A2A61C1F1EEFABAD00CFE063 /* UtilsExtension.swift */, + A20691F31F2A3D0E0096483D /* SecurePasteboard.swift */, ); path = Helpers; sourceTree = ""; @@ -1066,6 +1069,7 @@ DC962CDF1E4B62C10033B5D8 /* AboutTableViewController.swift in Sources */, DC5734AE1E439AD400D09270 /* PasswordsViewController.swift in Sources */, DCD3C65E1EFB9BB400CBE842 /* SettingsSplitViewController.swift in Sources */, + A20691F41F2A3D0E0096483D /* SecurePasteboard.swift in Sources */, DC3E64E61E656F11009A83DE /* CommitLogsTableViewController.swift in Sources */, DC037CAA1E4B8EAE00609409 /* SpecialThanksTableViewController.swift in Sources */, DC037CA61E4B883900609409 /* OpenSourceComponentsTableViewController.swift in Sources */, diff --git a/pass/Controllers/PasswordDetailTableViewController.swift b/pass/Controllers/PasswordDetailTableViewController.swift index e402159..14d9a4a 100644 --- a/pass/Controllers/PasswordDetailTableViewController.swift +++ b/pass/Controllers/PasswordDetailTableViewController.swift @@ -391,7 +391,7 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni // copy HOTP to pasteboard (will update counter) if let plainPassword = password!.getNextHotp() { - Utils.copyToPasteboard(textToCopy: plainPassword) + SecurePasteboard.shared.copy(textToCopy: plainPassword) } // commit the change of HOTP counter @@ -413,7 +413,7 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni } return; } - Utils.copyToPasteboard(textToCopy: password?.password) + SecurePasteboard.shared.copy(textToCopy: password?.password) UIApplication.shared.open(url, options: [:], completionHandler: nil) } @@ -480,7 +480,7 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni override func tableView(_ tableView: UITableView, performAction action: Selector, forRowAt indexPath: IndexPath, withSender sender: Any?) { if action == #selector(copy(_:)) { - Utils.copyToPasteboard(textToCopy: tableData[indexPath.section].item[indexPath.row].content) + SecurePasteboard.shared.copy(textToCopy: tableData[indexPath.section].item[indexPath.row].content) } } diff --git a/pass/Controllers/PasswordEditorTableViewController.swift b/pass/Controllers/PasswordEditorTableViewController.swift index b320c14..71defd2 100644 --- a/pass/Controllers/PasswordEditorTableViewController.swift +++ b/pass/Controllers/PasswordEditorTableViewController.swift @@ -189,7 +189,7 @@ class PasswordEditorTableViewController: UITableViewController, FillPasswordTabl } let length = passwordLengthCell?.roundedValue ?? 0 let plainPassword = Utils.generatePassword(length: length) - Utils.copyToPasteboard(textToCopy: plainPassword) + SecurePasteboard.shared.copy(textToCopy: plainPassword) // update tableData so to make sure reloadData() works correctly tableData[passwordSection][0][PasswordEditorCellKey.content] = plainPassword diff --git a/pass/Controllers/PasswordsViewController.swift b/pass/Controllers/PasswordsViewController.swift index 6b35047..cf5035e 100644 --- a/pass/Controllers/PasswordsViewController.swift +++ b/pass/Controllers/PasswordsViewController.swift @@ -370,7 +370,7 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV do { decryptedPassword = try self.passwordStore.decrypt(passwordEntity: passwordEntity, requestPGPKeyPassphrase: self.requestPGPKeyPassphrase) DispatchQueue.main.async { - Utils.copyToPasteboard(textToCopy: decryptedPassword?.password) + SecurePasteboard.shared.copy(textToCopy: decryptedPassword?.password) SVProgressHUD.showSuccess(withStatus: "Password copied, and will be cleared in 45 seconds.") SVProgressHUD.dismiss(withDelay: 0.6) } diff --git a/pass/Helpers/SecurePasteboard.swift b/pass/Helpers/SecurePasteboard.swift new file mode 100644 index 0000000..04541b6 --- /dev/null +++ b/pass/Helpers/SecurePasteboard.swift @@ -0,0 +1,55 @@ +// +// SecurePasteboard.swift +// pass +// +// Created by Yishi Lin on 2017/7/27. +// Copyright © 2017 Bob Sun. All rights reserved. +// + +import Foundation +import UIKit + +class SecurePasteboard { + public static let shared = SecurePasteboard() + private var backgroundTaskID: UIBackgroundTaskIdentifier? = nil + + func copy(textToCopy: String?, expirationTime: Double = 45) { + // copy to the pasteboard + UIPasteboard.general.string = textToCopy ?? "" + + // clean the pasteboard after expirationTime + guard expirationTime > 0 else { + return + } + + // exit the existing background task, if any + if let backgroundTaskID = backgroundTaskID { + UIApplication.shared.endBackgroundTask(backgroundTaskID) + self.backgroundTaskID = nil + } + + backgroundTaskID = UIApplication.shared.beginBackgroundTask(expirationHandler: { [weak self] in + guard let taskID = self?.backgroundTaskID else { + return + } + if textToCopy == UIPasteboard.general.string { + UIPasteboard.general.string = "" + } + UIApplication.shared.endBackgroundTask(taskID) + }) + + DispatchQueue.global(qos: .utility).asyncAfter(deadline: .now() + expirationTime) { [weak self] in + guard let strongSelf = self else { + return + } + if textToCopy == UIPasteboard.general.string { + UIPasteboard.general.string = "" + } + if let backgroundTaskID = strongSelf.backgroundTaskID { + UIApplication.shared.endBackgroundTask(backgroundTaskID) + strongSelf.backgroundTaskID = nil + } + } + } + +} diff --git a/pass/Views/LabelTableViewCell.swift b/pass/Views/LabelTableViewCell.swift index 12ac599..c937e9d 100644 --- a/pass/Views/LabelTableViewCell.swift +++ b/pass/Views/LabelTableViewCell.swift @@ -101,7 +101,7 @@ class LabelTableViewCell: UITableViewCell { } override func copy(_ sender: Any?) { - Utils.copyToPasteboard(textToCopy: cellData?.content) + SecurePasteboard.shared.copy(textToCopy: cellData?.content) } func revealPassword(_ sender: Any?) { diff --git a/passKit/Helpers/Utils.swift b/passKit/Helpers/Utils.swift index 9ce518e..bc7c625 100644 --- a/passKit/Helpers/Utils.swift +++ b/passKit/Helpers/Utils.swift @@ -94,17 +94,11 @@ public class Utils { print(error) } } - public static func copyToPasteboard(textToCopy: String?, expirationTime: Double = 45) { + public static func copyToPasteboard(textToCopy: String?) { guard textToCopy != nil else { return } UIPasteboard.general.string = textToCopy - DispatchQueue.global(qos: .background).asyncAfter(deadline: DispatchTime.now() + expirationTime) { - let pasteboardString: String? = UIPasteboard.general.string - if textToCopy == pasteboardString { - UIPasteboard.general.string = "" - } - } } public static func attributedPassword(plainPassword: String) -> NSAttributedString{ let attributedPassword = NSMutableAttributedString.init(string: plainPassword)