Merge and resolve conflit

This commit is contained in:
Bob Sun 2017-03-21 13:34:26 -07:00
commit c21502a10f
No known key found for this signature in database
GPG key ID: 1F86BA2052FED3B4
5 changed files with 139 additions and 111 deletions

View file

@ -13,26 +13,25 @@ import SVProgressHUD
class PasswordDetailTableViewController: UITableViewController, UIGestureRecognizerDelegate { class PasswordDetailTableViewController: UITableViewController, UIGestureRecognizerDelegate {
var passwordEntity: PasswordEntity? var passwordEntity: PasswordEntity?
var passwordCategoryText = "" private var password: Password?
var password: Password? private var passwordCategoryText = ""
var passwordImage: UIImage? private var passwordImage: UIImage?
var oneTimePasswordIndexPath : IndexPath? private var oneTimePasswordIndexPath : IndexPath?
var shouldPopCurrentView = false private var shouldPopCurrentView = false
let passwordStore = PasswordStore.shared private let passwordStore = PasswordStore.shared
let indicator: UIActivityIndicatorView = { private let indicator: UIActivityIndicatorView = {
let indicator = UIActivityIndicatorView(activityIndicatorStyle: .gray) let indicator = UIActivityIndicatorView(activityIndicatorStyle: .gray)
indicator.center = CGPoint(x: UIScreen.main.bounds.width / 2, y: UIScreen.main.bounds.height * 0.382) indicator.center = CGPoint(x: UIScreen.main.bounds.width / 2, y: UIScreen.main.bounds.height * 0.382)
return indicator return indicator
}() }()
lazy var editUIBarButtonItem: UIBarButtonItem = { private lazy var editUIBarButtonItem: UIBarButtonItem = {
let uiBarButtonItem = UIBarButtonItem(barButtonSystemItem: .edit, target: self, action: #selector(pressEdit(_:))) let uiBarButtonItem = UIBarButtonItem(barButtonSystemItem: .edit, target: self, action: #selector(pressEdit(_:)))
return uiBarButtonItem return uiBarButtonItem
}() }()
private struct TableCell {
struct TableCell {
var title: String var title: String
var content: String var content: String
init() { init() {
@ -46,12 +45,12 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
} }
} }
struct TableSection { private struct TableSection {
var title: String var title: String
var item: Array<TableCell> var item: Array<TableCell>
} }
var tableData = Array<TableSection>() private var tableData = Array<TableSection>()
override func viewDidLoad() { override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
@ -68,7 +67,6 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
tableView.rowHeight = UITableViewAutomaticDimension tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 52 tableView.estimatedRowHeight = 52
indicator.startAnimating() indicator.startAnimating()
tableView.addSubview(indicator) tableView.addSubview(indicator)
editUIBarButtonItem.isEnabled = false editUIBarButtonItem.isEnabled = false
@ -96,16 +94,29 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
self.present(alert, animated: true, completion: nil) self.present(alert, animated: true, completion: nil)
} }
self.setupUpdateOneTimePassword() self.setupOneTimePasswordAutoRefresh()
self.addNotificationObservers()
NotificationCenter.default.addObserver(self, selector: #selector(setShouldPopCurrentView), name: .passwordStoreChangeDiscarded, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(showPassword), name: .passwordStoreUpdated, object: nil)
} }
func decryptThenShowPassword(passphrase: String) { override func viewDidAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if self.shouldPopCurrentView {
let alert = UIAlertController(title: "Notice", message: "All previous local changes have been discarded. Your current Password Store will be shown.", preferredStyle: UIAlertControllerStyle.alert)
alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: {_ in
_ = self.navigationController?.popViewController(animated: true)
}))
self.present(alert, animated: true, completion: nil)
}
}
private func decryptThenShowPassword(passphrase: String) {
if Defaults[.isRememberPassphraseOn] { if Defaults[.isRememberPassphraseOn] {
self.passwordStore.pgpKeyPassphrase = passphrase self.passwordStore.pgpKeyPassphrase = passphrase
} }
DispatchQueue.global(qos: .userInitiated).async { DispatchQueue.global(qos: .userInitiated).async {
// decrypt password
do { do {
self.password = try self.passwordEntity!.decrypt(passphrase: passphrase)! self.password = try self.passwordEntity!.decrypt(passphrase: passphrase)!
} catch { } catch {
@ -118,27 +129,30 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
} }
return return
} }
// display password
self.showPassword()
}
}
let password = self.password! @objc private func showPassword() {
DispatchQueue.main.async { [weak self] in DispatchQueue.main.async { [weak self] in
self?.showPassword(password: password) self?.indicator.stopAnimating()
self?.setTableData()
UIView.performWithoutAnimation {
self?.tableView.reloadData()
// add layoutIfNeeded solves the "flickering problem" during refresh
self?.tableView.layoutIfNeeded()
}
self?.editUIBarButtonItem.isEnabled = true
if let urlString = self?.password?.getURLString() {
if self?.passwordEntity?.image == nil {
self?.updatePasswordImage(urlString: urlString)
}
} }
} }
} }
func showPassword(password: Password) { private func setupOneTimePasswordAutoRefresh() {
setTableData()
self.tableView.reloadData()
indicator.stopAnimating()
editUIBarButtonItem.isEnabled = true
if let urlString = password.getURLString() {
if self.passwordEntity?.image == nil{
self.updatePasswordImage(urlString: urlString)
}
}
}
func setupUpdateOneTimePassword() {
Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { Timer.scheduledTimer(withTimeInterval: 1, repeats: true) {
[weak self] timer in [weak self] timer in
// bail out of the timer code if the object has been freed // bail out of the timer code if the object has been freed
@ -163,15 +177,19 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
} }
} }
func pressEdit(_ sender: Any?) { @objc private func pressEdit(_ sender: Any?) {
performSegue(withIdentifier: "editPasswordSegue", sender: self) performSegue(withIdentifier: "editPasswordSegue", sender: self)
} }
@IBAction func cancelEditPassword(segue: UIStoryboardSegue) { @objc private func setShouldPopCurrentView() {
self.shouldPopCurrentView = true
}
@IBAction private func cancelEditPassword(segue: UIStoryboardSegue) {
} }
@IBAction func saveEditPassword(segue: UIStoryboardSegue) { @IBAction private func saveEditPassword(segue: UIStoryboardSegue) {
if self.password!.changed { if self.password!.changed {
SVProgressHUD.show(withStatus: "Saving") SVProgressHUD.show(withStatus: "Saving")
DispatchQueue.global(qos: .userInitiated).async { DispatchQueue.global(qos: .userInitiated).async {
@ -192,13 +210,13 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
} }
} }
@IBAction func deletePassword(segue: UIStoryboardSegue) { @IBAction private func deletePassword(segue: UIStoryboardSegue) {
print("delete") print("delete")
passwordStore.delete(passwordEntity: passwordEntity!) passwordStore.delete(passwordEntity: passwordEntity!)
navigationController?.popViewController(animated: true) navigationController?.popViewController(animated: true)
} }
func setTableData() { private func setTableData() {
self.tableData = Array<TableSection>() self.tableData = Array<TableSection>()
tableData.append(TableSection(title: "", item: [])) tableData.append(TableSection(title: "", item: []))
tableData[0].item.append(TableCell()) tableData[0].item.append(TableCell())
@ -260,7 +278,7 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
} }
} }
func updatePasswordImage(urlString: String) { private func updatePasswordImage(urlString: String) {
var newUrlString = urlString var newUrlString = urlString
if urlString.lowercased().hasPrefix("http://") { if urlString.lowercased().hasPrefix("http://") {
// try to replace http url to https url // try to replace http url to https url
@ -299,7 +317,7 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
} }
} }
func tapMenu(recognizer: UITapGestureRecognizer) { @objc private func tapMenu(recognizer: UITapGestureRecognizer) {
if recognizer.state == UIGestureRecognizerState.ended { if recognizer.state == UIGestureRecognizerState.ended {
let tapLocation = recognizer.location(in: self.tableView) let tapLocation = recognizer.location(in: self.tableView)
if let tapIndexPath = self.tableView.indexPathForRow(at: tapLocation) { if let tapIndexPath = self.tableView.indexPathForRow(at: tapLocation) {
@ -308,9 +326,9 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
let menuController = UIMenuController.shared let menuController = UIMenuController.shared
let revealItem = UIMenuItem(title: "Reveal", action: #selector(LabelTableViewCell.revealPassword(_:))) let revealItem = UIMenuItem(title: "Reveal", action: #selector(LabelTableViewCell.revealPassword(_:)))
let concealItem = UIMenuItem(title: "Conceal", action: #selector(LabelTableViewCell.concealPassword(_:))) let concealItem = UIMenuItem(title: "Conceal", action: #selector(LabelTableViewCell.concealPassword(_:)))
let nextPasswordItem = UIMenuItem(title: "Next Password", action: #selector(LabelTableViewCell.nextPassword(_:))) let nextHOTPItem = UIMenuItem(title: "Next Password", action: #selector(LabelTableViewCell.getNextHOTP(_:)))
let openURLItem = UIMenuItem(title: "Copy Password & Open Link", action: #selector(LabelTableViewCell.openLink(_:))) let openURLItem = UIMenuItem(title: "Copy Password & Open Link", action: #selector(LabelTableViewCell.openLink(_:)))
menuController.menuItems = [revealItem, concealItem, nextPasswordItem, openURLItem] menuController.menuItems = [revealItem, concealItem, nextHOTPItem, openURLItem]
menuController.setTargetRect(tappedCell.contentLabel.frame, in: tappedCell.contentLabel.superview!) menuController.setTargetRect(tappedCell.contentLabel.frame, in: tappedCell.contentLabel.superview!)
menuController.setMenuVisible(true, animated: true) menuController.setMenuVisible(true, animated: true)
} }
@ -318,6 +336,46 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
} }
} }
func getNextHOTP() {
guard password != nil, passwordEntity != nil, password?.otpType == .hotp else {
DispatchQueue.main.async {
Utils.alert(title: "Error", message: "Get next password of a non-HOTP entry.", controller: self, completion: nil)
}
return;
}
// increase HOTP counter
password!.increaseHotpCounter()
// copy HOTP to pasteboard
if let plainPassword = password!.otpToken?.currentPassword {
Utils.copyToPasteboard(textToCopy: plainPassword)
}
// commit the change of HOTP counter
if password!.changed {
DispatchQueue.global(qos: .userInitiated).async {
self.passwordStore.update(passwordEntity: self.passwordEntity!, password: self.password!, progressBlock: {_ in })
DispatchQueue.main.async {
self.passwordEntity!.synced = false
self.passwordStore.saveUpdated(passwordEntity: self.passwordEntity!)
SVProgressHUD.showSuccess(withStatus: "Password Copied\nCounter Updated")
SVProgressHUD.dismiss(withDelay: 1)
}
}
}
}
func openLink() {
guard let urlString = self.password?.getURLString(), let url = URL(string: urlString) else {
DispatchQueue.main.async {
Utils.alert(title: "Error", message: "Cannot find a valid URL", controller: self, completion: nil)
}
return;
}
Utils.copyToPasteboard(textToCopy: password?.password)
UIApplication.shared.open(url, options: [:], completionHandler: nil)
}
override func numberOfSections(in tableView: UITableView) -> Int { override func numberOfSections(in tableView: UITableView) -> Int {
return tableData.count return tableData.count
@ -345,7 +403,7 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
let cell = tableView.dequeueReusableCell(withIdentifier: "labelCell", for: indexPath) as! LabelTableViewCell let cell = tableView.dequeueReusableCell(withIdentifier: "labelCell", for: indexPath) as! LabelTableViewCell
let titleData = tableData[sectionIndex].item[rowIndex].title let titleData = tableData[sectionIndex].item[rowIndex].title
let contentData = tableData[sectionIndex].item[rowIndex].content let contentData = tableData[sectionIndex].item[rowIndex].content
cell.passwordTableView = self cell.delegatePasswordTableView = self
cell.isPasswordCell = (titleData.lowercased() == "password" ? true : false) cell.isPasswordCell = (titleData.lowercased() == "password" ? true : false)
cell.isURLCell = (titleData.lowercased() == "url" ? true : false) cell.isURLCell = (titleData.lowercased() == "url" ? true : false)
cell.isHOTPCell = (titleData == "HMAC-based" ? true : false) cell.isHOTPCell = (titleData == "HMAC-based" ? true : false)
@ -386,23 +444,4 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
override func tableView(_ tableView: UITableView, shouldShowMenuForRowAt indexPath: IndexPath) -> Bool { override func tableView(_ tableView: UITableView, shouldShowMenuForRowAt indexPath: IndexPath) -> Bool {
return true return true
} }
private func addNotificationObservers() {
NotificationCenter.default.addObserver(self, selector: #selector(setShouldPopCurrentView), name: .passwordStoreChangeDiscarded, object: nil)
}
func setShouldPopCurrentView() {
self.shouldPopCurrentView = true
}
override func viewDidAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if self.shouldPopCurrentView {
let alert = UIAlertController(title: "Notice", message: "All previous local changes have been discarded. Your current Password Store will be shown.", preferredStyle: UIAlertControllerStyle.alert)
alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: {_ in
_ = self.navigationController?.popViewController(animated: true)
}))
self.present(alert, animated: true, completion: nil)
}
}
} }

View file

@ -28,6 +28,9 @@ class Globals {
static let passwordMaximumLength = 24 static let passwordMaximumLength = 24
static let passwordDefaultLength = 16 static let passwordDefaultLength = 16
static let passwordDots = "••••••••••••"
static let passwordFonts = "Menlo"
private init() { } private init() { }
} }

View file

@ -28,6 +28,24 @@ class Password {
var firstLineIsOTPField = false var firstLineIsOTPField = false
var otpToken: Token? var otpToken: Token?
enum OtpType {
case totp, hotp, none
}
var otpType: OtpType {
get {
guard let token = self.otpToken else {
return OtpType.none
}
switch token.generator.factor {
case .counter:
return OtpType.hotp
case .timer:
return OtpType.totp
}
}
}
init(name: String, plainText: String) { init(name: String, plainText: String) {
self.initEverything(name: name, plainText: plainText) self.initEverything(name: name, plainText: plainText)
} }

View file

@ -20,6 +20,7 @@ class FillPasswordTableViewCell: ContentTableViewCell {
override func awakeFromNib() { override func awakeFromNib() {
super.awakeFromNib() super.awakeFromNib()
// Initialization code // Initialization code
contentTextField.font = UIFont(name: Globals.passwordFonts, size: (contentTextField.font?.pointSize)!)
} }
override func setSelected(_ selected: Bool, animated: Bool) { override func setSelected(_ selected: Bool, animated: Bool) {
@ -30,7 +31,7 @@ class FillPasswordTableViewCell: ContentTableViewCell {
@IBAction func generatePassword(_ sender: UIButton) { @IBAction func generatePassword(_ sender: UIButton) {
let plainPassword = self.delegate?.generatePassword() ?? Utils.generatePassword(length: 16) let plainPassword = self.delegate?.generatePassword() ?? Utils.generatePassword(length: 16)
contentTextField.attributedText = Utils.attributedPassword(plainPassword: plainPassword) self.setContent(content: plainPassword)
Utils.copyToPasteboard(textToCopy: plainPassword) Utils.copyToPasteboard(textToCopy: plainPassword)
} }

View file

@ -19,15 +19,13 @@ class LabelTableViewCell: UITableViewCell {
@IBOutlet weak var contentLabel: UILabel! @IBOutlet weak var contentLabel: UILabel!
@IBOutlet weak var titleLabel: UILabel! @IBOutlet weak var titleLabel: UILabel!
let passwordStore = PasswordStore.shared
var isPasswordCell = false var isPasswordCell = false
var isURLCell = false var isURLCell = false
var isReveal = false var isReveal = false
var isHOTPCell = false var isHOTPCell = false
let passwordDots = "••••••••••••"
weak var passwordTableView : PasswordDetailTableViewController? weak var delegatePasswordTableView : PasswordDetailTableViewController?
var cellData: LabelTableViewCellData? { var cellData: LabelTableViewCellData? {
didSet { didSet {
@ -36,14 +34,14 @@ class LabelTableViewCell: UITableViewCell {
if isReveal { if isReveal {
contentLabel.attributedText = Utils.attributedPassword(plainPassword: cellData?.content ?? "") contentLabel.attributedText = Utils.attributedPassword(plainPassword: cellData?.content ?? "")
} else { } else {
contentLabel.text = passwordDots contentLabel.text = Globals.passwordDots
} }
contentLabel.font = UIFont(name: "Menlo", size: contentLabel.font.pointSize) contentLabel.font = UIFont(name: Globals.passwordFonts, size: contentLabel.font.pointSize)
} else if isHOTPCell { } else if isHOTPCell {
if isReveal { if isReveal {
contentLabel.text = cellData?.content ?? "" contentLabel.text = cellData?.content ?? ""
} else { } else {
contentLabel.text = passwordDots contentLabel.text = Globals.passwordDots
} }
} else { } else {
contentLabel.text = cellData?.content contentLabel.text = cellData?.content
@ -78,9 +76,9 @@ class LabelTableViewCell: UITableViewCell {
} }
if isHOTPCell { if isHOTPCell {
if isReveal { if isReveal {
return action == #selector(copy(_:)) || action == #selector(LabelTableViewCell.concealPassword(_:)) || action == #selector(LabelTableViewCell.nextPassword(_:)) return action == #selector(copy(_:)) || action == #selector(LabelTableViewCell.concealPassword(_:)) || action == #selector(LabelTableViewCell.getNextHOTP(_:))
} else { } else {
return action == #selector(copy(_:)) || action == #selector(LabelTableViewCell.revealPassword(_:)) || action == #selector(LabelTableViewCell.nextPassword(_:)) return action == #selector(copy(_:)) || action == #selector(LabelTableViewCell.revealPassword(_:)) || action == #selector(LabelTableViewCell.getNextHOTP(_:))
} }
} }
return action == #selector(copy(_:)) return action == #selector(copy(_:))
@ -104,48 +102,17 @@ class LabelTableViewCell: UITableViewCell {
} }
func concealPassword(_ sender: Any?) { func concealPassword(_ sender: Any?) {
contentLabel.text = passwordDots contentLabel.text = Globals.passwordDots
isReveal = false isReveal = false
} }
func nextPassword(_ sender: Any?) {
guard let password = passwordTableView?.password,
let passwordEntity = passwordTableView?.passwordEntity else {
print("Cannot find password/passwordEntity of a cell")
return;
}
// increase HOTP counter
password.increaseHotpCounter()
// only the HOTP password needs update
if let plainPassword = password.otpToken?.currentPassword {
cellData?.content = plainPassword
// contentLabel will be updated automatically
}
// commit
if password.changed {
DispatchQueue.global(qos: .userInitiated).async {
self.passwordStore.update(passwordEntity: passwordEntity, password: password, progressBlock: {_ in })
DispatchQueue.main.async {
passwordEntity.synced = false
self.passwordStore.saveUpdated(passwordEntity: passwordEntity)
// reload so that the "unsynced" symbol could be added
self.passwordTableView?.tableView.reloadRows(at: [IndexPath(row: 0, section: 0)], with: UITableViewRowAnimation.automatic)
SVProgressHUD.showSuccess(withStatus: "Password Copied\nCounter Updated")
SVProgressHUD.dismiss(withDelay: 1)
}
}
}
}
func openLink(_ sender: Any?) { func openLink(_ sender: Any?) {
guard let password = passwordTableView?.password else { // if isURLCell, passwordTableView should not be nil
print("Cannot find password of a cell") delegatePasswordTableView!.openLink()
return;
} }
Utils.copyToPasteboard(textToCopy: password.password)
UIApplication.shared.open(URL(string: cellData!.content)!, options: [:], completionHandler: nil) func getNextHOTP(_ sender: Any?) {
// if isHOTPCell, passwordTableView should not be nil
delegatePasswordTableView!.getNextHOTP()
} }
} }