Merge branch 'release/0.3.1'

This commit is contained in:
Bob Sun 2018-04-29 11:55:06 -07:00
commit 620e11be46
27 changed files with 584 additions and 260 deletions

View file

@ -1,8 +1,7 @@
github "SVProgressHUD/SVProgressHUD"
github "radex/SwiftyUserDefaults"
github "libgit2/objective-git"
# github "zahlz/SwiftPasscodeLock" "master"
github "yishilin14/SwiftPasscodeLock" "app-extension-support"
github "bitserf/FavIcon"
github "leonbreedt/FavIcon"
github "kishikawakatsumi/KeychainAccess"
github "mattrubin/OneTimePassword"
github "jpsim/Yams"

View file

@ -2,7 +2,7 @@ platform :ios, '10.2'
use_frameworks!
target 'passKit' do
pod 'ObjectivePGP', :git => 'https://github.com/krzyzanowskim/ObjectivePGP.git', :tag => '0.10.0-beta2'
pod 'ObjectivePGP', :git => 'https://github.com/krzyzanowskim/ObjectivePGP.git', :tag => '0.10.0'
target 'pass' do
inherit! :search_paths
end

View file

@ -1,7 +1,7 @@
<img src="icon/icon_round.png" width="76"/>
# Pass
[![GitHub release](https://img.shields.io/github/release/mssun/pass-ios.svg)](https://github.com/mssun/pass-ios/releases)
[![GitHub release](https://img.shields.io/github/release/mssun/passforios.svg)](https://github.com/mssun/passforios/releases)
![Swift 3.1](https://img.shields.io/badge/Swift-4.0-orange.svg)
[![Gitter](https://img.shields.io/gitter/room/nwjs/nw.js.svg)](https://gitter.im/passforios/passforios)
[![Build Status](https://travis-ci.org/mssun/passforios.svg?branch=develop)](https://travis-ci.org/mssun/passforios)

View file

@ -62,7 +62,7 @@ platform :ios do
)
# ensure_git_status_clean
increment_build_number(
build_number: latest_testflight_build_number(version: get_version_number, initial_build_number: 0) + 1,
build_number: latest_testflight_build_number(version: get_version_number(target: "pass"), initial_build_number: 0) + 1,
xcodeproj: "pass.xcodeproj"
)
# commit_version_bump(xcodeproj: "pass.xcodeproj")

View file

@ -9,6 +9,7 @@
/* Begin PBXBuildFile section */
18F19A67B0C07F13C17169E0 /* Pods_pass.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3A5620D17DF5E86B61761D0E /* Pods_pass.framework */; };
23B82F0228254275DBA609E7 /* Pods_passExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B975797E0F0B7476CADD6A7D /* Pods_passExtension.framework */; };
3012B06D2039D6E400BE1793 /* Yams.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3012B06C2039D6E400BE1793 /* Yams.framework */; };
61326CDA7A73757FB68DCB04 /* Pods_passKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DAB3F5541E51ADC8C6B56642 /* Pods_passKit.framework */; };
A20691F41F2A3D0E0096483D /* SecurePasteboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = A20691F31F2A3D0E0096483D /* SecurePasteboard.swift */; };
A2168A7F1EFD40D5005EA873 /* OnePasswordExtensionConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = A2168A7E1EFD40D5005EA873 /* OnePasswordExtensionConstants.swift */; };
@ -29,8 +30,6 @@
A26700371EEC475600176B8A /* passProcessor.js in Resources */ = {isa = PBXBuildFile; fileRef = A26700351EEC475600176B8A /* passProcessor.js */; };
A2802BF91E70813A00879216 /* SliderTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = A2802BF71E70813A00879216 /* SliderTableViewCell.swift */; };
A2802BFA1E70813A00879216 /* SliderTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = A2802BF81E70813A00879216 /* SliderTableViewCell.xib */; };
A28C66651EF109D600A398A1 /* PasscodeLockConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = A28C66631EF109D600A398A1 /* PasscodeLockConfiguration.swift */; };
A28C66661EF109D600A398A1 /* PasscodeLockRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = A28C66641EF109D600A398A1 /* PasscodeLockRepository.swift */; };
A28C66681EF10EC900A398A1 /* PasscodeExtensionDisplay.swift in Sources */ = {isa = PBXBuildFile; fileRef = A28C66671EF10EC900A398A1 /* PasscodeExtensionDisplay.swift */; };
A2A61C131EEF90CB00CFE063 /* Base32.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A262A58C1E68749C006B0890 /* Base32.framework */; };
A2A61C151EEF90CB00CFE063 /* KeychainAccess.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DCA742D91E599ED400D54E16 /* KeychainAccess.framework */; };
@ -39,6 +38,10 @@
A2A61C201EEFABAD00CFE063 /* UtilsExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = A2A61C1F1EEFABAD00CFE063 /* UtilsExtension.swift */; };
A2A61C2C1EEFDF3300CFE063 /* ExtensionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A2A61C2B1EEFDF3300CFE063 /* ExtensionViewController.swift */; };
A2A7813F1E97DBD9001311F5 /* QRScannerController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A2A7813E1E97DBD9001311F5 /* QRScannerController.swift */; };
A2BEC1BB207D2EFE00F3051C /* UIViewExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = A2BEC1BA207D2EFE00F3051C /* UIViewExtension.swift */; };
A2C532BB201E5A9600DB9F53 /* PasscodeLock.swift in Sources */ = {isa = PBXBuildFile; fileRef = A2C532BA201E5A9600DB9F53 /* PasscodeLock.swift */; };
A2C532BE201E5AA100DB9F53 /* PasscodeLockViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A2C532BC201E5AA000DB9F53 /* PasscodeLockViewController.swift */; };
A2C532BF201E5AA100DB9F53 /* PasscodeLockPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = A2C532BD201E5AA100DB9F53 /* PasscodeLockPresenter.swift */; };
A2F4E2141EED800F0011986E /* GitCredential.swift in Sources */ = {isa = PBXBuildFile; fileRef = A2F4E2101EED800F0011986E /* GitCredential.swift */; };
A2F4E2151EED800F0011986E /* Password.swift in Sources */ = {isa = PBXBuildFile; fileRef = A2F4E2111EED800F0011986E /* Password.swift */; };
A2F4E2161EED800F0011986E /* PasswordEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = A2F4E2121EED800F0011986E /* PasswordEntity.swift */; };
@ -61,7 +64,6 @@
DC037CC01E4ED4E100609409 /* TextViewTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = DC037CBE1E4ED4E100609409 /* TextViewTableViewCell.xib */; };
DC13B1511E8640810097803F /* passTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC13B1501E8640810097803F /* passTests.swift */; };
DC193FFA1E49B4430077E0A3 /* AdvancedSettingsTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC193FF91E49B4430077E0A3 /* AdvancedSettingsTableViewController.swift */; };
DC193FFC1E49E0340077E0A3 /* PasscodeLock.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DC193FFB1E49E0340077E0A3 /* PasscodeLock.framework */; };
DC3E64E61E656F11009A83DE /* CommitLogsTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC3E64E51E656F11009A83DE /* CommitLogsTableViewController.swift */; };
DC4914961E434301007FF592 /* LabelTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC4914941E434301007FF592 /* LabelTableViewCell.swift */; };
DC4914991E434600007FF592 /* PasswordDetailTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC4914981E434600007FF592 /* PasswordDetailTableViewController.swift */; };
@ -157,6 +159,7 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
3012B06C2039D6E400BE1793 /* Yams.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Yams.framework; path = Carthage/Build/iOS/Yams.framework; sourceTree = "<group>"; };
31C3033E8868D05B2C55C8B1 /* Pods-passExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-passExtension.debug.xcconfig"; path = "Pods/Target Support Files/Pods-passExtension/Pods-passExtension.debug.xcconfig"; sourceTree = "<group>"; };
3A5620D17DF5E86B61761D0E /* Pods_pass.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_pass.framework; sourceTree = BUILT_PRODUCTS_DIR; };
666769E0B255666D02945C15 /* Pods-passKitTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-passKitTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-passKitTests/Pods-passKitTests.release.xcconfig"; sourceTree = "<group>"; };
@ -191,8 +194,6 @@
A26700351EEC475600176B8A /* passProcessor.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = passProcessor.js; sourceTree = "<group>"; };
A2802BF71E70813A00879216 /* SliderTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SliderTableViewCell.swift; sourceTree = "<group>"; };
A2802BF81E70813A00879216 /* SliderTableViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = SliderTableViewCell.xib; sourceTree = "<group>"; };
A28C66631EF109D600A398A1 /* PasscodeLockConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = PasscodeLockConfiguration.swift; path = Models/PasscodeLockConfiguration.swift; sourceTree = "<group>"; };
A28C66641EF109D600A398A1 /* PasscodeLockRepository.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = PasscodeLockRepository.swift; path = Models/PasscodeLockRepository.swift; sourceTree = "<group>"; };
A28C66671EF10EC900A398A1 /* PasscodeExtensionDisplay.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PasscodeExtensionDisplay.swift; sourceTree = "<group>"; };
A2A61C0C1EEF8DFE00CFE063 /* libPods-passExtension.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = "libPods-passExtension.a"; path = "../../Library/Developer/Xcode/DerivedData/pass-fwlmfsjroyvbfhdyqmglrwfhvjli/Build/Products/Debug-iphonesimulator/libPods-passExtension.a"; sourceTree = "<group>"; };
A2A61C101EEF8E3500CFE063 /* libPods-passKit.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = "libPods-passKit.a"; path = "Pods/../build/Debug-iphoneos/libPods-passKit.a"; sourceTree = "<group>"; };
@ -200,6 +201,10 @@
A2A61C2B1EEFDF3300CFE063 /* ExtensionViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExtensionViewController.swift; sourceTree = "<group>"; };
A2A7813E1E97DBD9001311F5 /* QRScannerController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QRScannerController.swift; sourceTree = "<group>"; };
A2BC54C71EEE5669001FAFBD /* Objective-CBridgingHeader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "Objective-CBridgingHeader.h"; sourceTree = "<group>"; };
A2BEC1BA207D2EFE00F3051C /* UIViewExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = UIViewExtension.swift; path = Helpers/UIViewExtension.swift; sourceTree = "<group>"; };
A2C532BA201E5A9600DB9F53 /* PasscodeLock.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = PasscodeLock.swift; path = Models/PasscodeLock.swift; sourceTree = "<group>"; };
A2C532BC201E5AA000DB9F53 /* PasscodeLockViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = PasscodeLockViewController.swift; path = Controllers/PasscodeLockViewController.swift; sourceTree = "<group>"; };
A2C532BD201E5AA100DB9F53 /* PasscodeLockPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = PasscodeLockPresenter.swift; path = Controllers/PasscodeLockPresenter.swift; sourceTree = "<group>"; };
A2F4E2101EED800F0011986E /* GitCredential.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = GitCredential.swift; path = Models/GitCredential.swift; sourceTree = "<group>"; };
A2F4E2111EED800F0011986E /* Password.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Password.swift; path = Models/Password.swift; sourceTree = "<group>"; };
A2F4E2121EED800F0011986E /* PasswordEntity.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = PasswordEntity.swift; path = Models/PasswordEntity.swift; sourceTree = "<group>"; };
@ -315,8 +320,8 @@
files = (
A260758D1EEC6F34005DB03E /* passKit.framework in Frameworks */,
DCC408C71E307DBB00F29B0E /* SVProgressHUD.framework in Frameworks */,
DC193FFC1E49E0340077E0A3 /* PasscodeLock.framework in Frameworks */,
18F19A67B0C07F13C17169E0 /* Pods_pass.framework in Frameworks */,
3012B06D2039D6E400BE1793 /* Yams.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -344,11 +349,12 @@
A26075791EEC6F34005DB03E /* passKit */ = {
isa = PBXGroup;
children = (
A2C532B9201DD07500DB9F53 /* Controllers */,
A2F4E20F1EED7F0A0011986E /* Helpers */,
A260757B1EEC6F34005DB03E /* Info.plist */,
A2F4E20E1EED7F040011986E /* Models */,
A26075A51EEC7125005DB03E /* pass.xcdatamodeld */,
A2F4E20F1EED7F0A0011986E /* Helpers */,
A260757A1EEC6F34005DB03E /* passKit.h */,
A260757B1EEC6F34005DB03E /* Info.plist */,
);
path = passKit;
sourceTree = "<group>";
@ -376,11 +382,19 @@
path = passExtension;
sourceTree = "<group>";
};
A2C532B9201DD07500DB9F53 /* Controllers */ = {
isa = PBXGroup;
children = (
A2C532BD201E5AA100DB9F53 /* PasscodeLockPresenter.swift */,
A2C532BC201E5AA000DB9F53 /* PasscodeLockViewController.swift */,
);
name = Controllers;
sourceTree = "<group>";
};
A2F4E20E1EED7F040011986E /* Models */ = {
isa = PBXGroup;
children = (
A28C66631EF109D600A398A1 /* PasscodeLockConfiguration.swift */,
A28C66641EF109D600A398A1 /* PasscodeLockRepository.swift */,
A2C532BA201E5A9600DB9F53 /* PasscodeLock.swift */,
A2F4E2101EED800F0011986E /* GitCredential.swift */,
A2F4E2111EED800F0011986E /* Password.swift */,
A2F4E2121EED800F0011986E /* PasswordEntity.swift */,
@ -398,6 +412,7 @@
A2F4E21B1EED80160011986E /* NotificationNames.swift */,
A2F4E21C1EED80160011986E /* UITextFieldExtension.swift */,
A2F4E21D1EED80160011986E /* Utils.swift */,
A2BEC1BA207D2EFE00F3051C /* UIViewExtension.swift */,
);
name = Helpers;
sourceTree = "<group>";
@ -429,30 +444,30 @@
DC19400C1E4B39400077E0A3 /* Controllers */ = {
isa = PBXGroup;
children = (
DCD3C65D1EFB9BB400CBE842 /* SettingsSplitViewController.swift */,
DC3E64E51E656F11009A83DE /* CommitLogsTableViewController.swift */,
DC5F385A1E56AADB00C69ACA /* PGPKeyArmorSettingTableViewController.swift */,
DC037CB11E4CAB1700609409 /* AboutRepositoryTableViewController.swift */,
DC962CDE1E4B62C10033B5D8 /* AboutTableViewController.swift */,
DC037CB71E4DD1A500609409 /* AddPasswordTableViewController.swift */,
DC193FF91E49B4430077E0A3 /* AdvancedSettingsTableViewController.swift */,
DC037CA71E4B898100609409 /* BasicStaticTableViewController.swift */,
DC3E64E51E656F11009A83DE /* CommitLogsTableViewController.swift */,
DCFB77A61E502DF9008DE471 /* EditPasswordTableViewController.swift */,
DC037CAF1E4CA51F00609409 /* GeneralSettingsTableViewController.swift */,
A217ACE31E9BBBBD00A1A6CF /* GitConfigSettingTableViewController.swift */,
DCA049991E335CC800522E8F /* GitServerSettingTableViewController.swift */,
DCC441531E916382008A90C4 /* GitSSHKeyArmorSettingTableViewController.swift */,
DC037CA51E4B883900609409 /* OpenSourceComponentsTableViewController.swift */,
A217ACE11E9AB17C00A1A6CF /* OTPScannerController.swift */,
DC4914981E434600007FF592 /* PasswordDetailTableViewController.swift */,
DCC441511E8F6C06008A90C4 /* RawPasswordViewController.swift */,
DCFB77A81E502FF6008DE471 /* PasswordEditorTableViewController.swift */,
DC5734AD1E439AD400D09270 /* PasswordsViewController.swift */,
DC5F385A1E56AADB00C69ACA /* PGPKeyArmorSettingTableViewController.swift */,
DCA0499B1E3362F400522E8F /* PGPKeySettingTableViewController.swift */,
A2A7813E1E97DBD9001311F5 /* QRScannerController.swift */,
DCC441511E8F6C06008A90C4 /* RawPasswordViewController.swift */,
DCD3C65D1EFB9BB400CBE842 /* SettingsSplitViewController.swift */,
DCAAF7441E2FA66800AB94BC /* SettingsTableViewController.swift */,
DC037CA91E4B8EAE00609409 /* SpecialThanksTableViewController.swift */,
DC8963BF1E38EEB900828B09 /* SSHKeySettingTableViewController.swift */,
DCC441531E916382008A90C4 /* GitSSHKeyArmorSettingTableViewController.swift */,
A2A7813E1E97DBD9001311F5 /* QRScannerController.swift */,
A217ACE11E9AB17C00A1A6CF /* OTPScannerController.swift */,
A217ACE31E9BBBBD00A1A6CF /* GitConfigSettingTableViewController.swift */,
);
path = Controllers;
sourceTree = "<group>";
@ -477,21 +492,21 @@
DC19400F1E4B3A9E0077E0A3 /* Views */ = {
isa = PBXGroup;
children = (
A2802BF71E70813A00879216 /* SliderTableViewCell.swift */,
A2802BF81E70813A00879216 /* SliderTableViewCell.xib */,
DCFB77AA1E503729008DE471 /* ContentTableViewCell.swift */,
DCFB779C1E4F40C7008DE471 /* FillPasswordTableViewCell.swift */,
DCFB779D1E4F40C7008DE471 /* FillPasswordTableViewCell.xib */,
DCFB77981E4F3BCF008DE471 /* TitleTextFieldTableViewCell.swift */,
DCFB77991E4F3BCF008DE471 /* TitleTextFieldTableViewCell.xib */,
DC4914941E434301007FF592 /* LabelTableViewCell.swift */,
DCDDEAAF1E4639F300F68193 /* LabelTableViewCell.xib */,
DC037CB91E4DD47B00609409 /* TextFieldTableViewCell.swift */,
DC037CBA1E4DD47B00609409 /* TextFieldTableViewCell.xib */,
DCDDEAB11E4896BF00F68193 /* PasswordDetailTitleTableViewCell.swift */,
DCFB77A21E500D9C008DE471 /* PasswordDetailTitleTableViewCell.xib */,
A2802BF71E70813A00879216 /* SliderTableViewCell.swift */,
A2802BF81E70813A00879216 /* SliderTableViewCell.xib */,
DC037CB91E4DD47B00609409 /* TextFieldTableViewCell.swift */,
DC037CBA1E4DD47B00609409 /* TextFieldTableViewCell.xib */,
DC037CBD1E4ED4E100609409 /* TextViewTableViewCell.swift */,
DC037CBE1E4ED4E100609409 /* TextViewTableViewCell.xib */,
DCFB77AA1E503729008DE471 /* ContentTableViewCell.swift */,
DCFB77981E4F3BCF008DE471 /* TitleTextFieldTableViewCell.swift */,
DCFB77991E4F3BCF008DE471 /* TitleTextFieldTableViewCell.xib */,
);
path = Views;
sourceTree = "<group>";
@ -542,6 +557,7 @@
DC917BED1E2F38C4000FDF54 /* Frameworks */ = {
isa = PBXGroup;
children = (
3012B06C2039D6E400BE1793 /* Yams.framework */,
A2A61C101EEF8E3500CFE063 /* libPods-passKit.a */,
A2A61C0C1EEF8DFE00CFE063 /* libPods-passExtension.a */,
A2227D541EEE5E78002A69A9 /* libObjectivePGP.a */,
@ -693,7 +709,7 @@
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 0830;
LastUpgradeCheck = 0900;
LastUpgradeCheck = 0930;
ORGANIZATIONNAME = "Bob Sun";
TargetAttributes = {
A26075771EEC6F34005DB03E = {
@ -984,11 +1000,11 @@
"$(SRCROOT)/Carthage/Build/iOS/SVProgressHUD.framework",
"$(SRCROOT)/Carthage/Build/iOS/SwiftyUserDefaults.framework",
"$(SRCROOT)/Carthage/Build/iOS/ObjectiveGit.framework",
"$(SRCROOT)/Carthage/Build/iOS/PasscodeLock.framework",
"$(SRCROOT)/Carthage/Build/iOS/FavIcon.framework",
"$(SRCROOT)/Carthage/Build/iOS/KeychainAccess.framework",
"$(SRCROOT)/Carthage/Build/iOS/OneTimePassword.framework",
"$(SRCROOT)/Carthage/Build/iOS/Base32.framework",
"$(SRCROOT)/Carthage/Build/iOS/Yams.framework",
);
name = "Run Script";
outputPaths = (
@ -1019,14 +1035,16 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
A28C66661EF109D600A398A1 /* PasscodeLockRepository.swift in Sources */,
A2BEC1BB207D2EFE00F3051C /* UIViewExtension.swift in Sources */,
A2C532BB201E5A9600DB9F53 /* PasscodeLock.swift in Sources */,
A2F4E2151EED800F0011986E /* Password.swift in Sources */,
A28C66651EF109D600A398A1 /* PasscodeLockConfiguration.swift in Sources */,
A26075AD1EEC7125005DB03E /* pass.xcdatamodeld in Sources */,
A2F4E21E1EED80160011986E /* AppError.swift in Sources */,
A2F4E2171EED800F0011986E /* PasswordStore.swift in Sources */,
A2F4E2211EED80160011986E /* NotificationNames.swift in Sources */,
A2F4E2221EED80160011986E /* UITextFieldExtension.swift in Sources */,
A2C532BF201E5AA100DB9F53 /* PasscodeLockPresenter.swift in Sources */,
A2C532BE201E5AA100DB9F53 /* PasscodeLockViewController.swift in Sources */,
A2F4E2201EED80160011986E /* Globals.swift in Sources */,
A2F4E2231EED80160011986E /* Utils.swift in Sources */,
A2F4E21F1EED80160011986E /* DefaultsKeys.swift in Sources */,
@ -1325,6 +1343,7 @@
SKIP_INSTALL = YES;
SWIFT_SWIFT3_OBJC_INFERENCE = Default;
SWIFT_VERSION = 4.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
@ -1358,6 +1377,7 @@
SKIP_INSTALL = YES;
SWIFT_SWIFT3_OBJC_INFERENCE = Default;
SWIFT_VERSION = 4.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
};
@ -1408,6 +1428,7 @@
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
@ -1415,6 +1436,7 @@
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
@ -1441,6 +1463,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
HEADER_SEARCH_PATHS = "$(SDKROOT)/usr/include/libxml2";
IPHONEOS_DEPLOYMENT_TARGET = 10.2;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
@ -1465,6 +1488,7 @@
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
@ -1472,6 +1496,7 @@
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
@ -1492,6 +1517,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
HEADER_SEARCH_PATHS = "$(SDKROOT)/usr/include/libxml2";
IPHONEOS_DEPLOYMENT_TARGET = 10.2;
MTL_ENABLE_DEBUG_INFO = NO;
PRODUCT_BUNDLE_IDENTIFIER = me.mssun.passforios;
@ -1530,7 +1556,7 @@
OTHER_LDFLAGS = "${inherited}";
PRODUCT_BUNDLE_IDENTIFIER = "$(PRODUCT_BUNDLE_IDENTIFIER)";
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE = "2e72f4af-b935-4970-9cd3-44d4cc24b646";
PROVISIONING_PROFILE = "3c4f599a-ce77-4184-b4c4-edebf09cba3b";
PROVISIONING_PROFILE_SPECIFIER = "match Development me.mssun.passforios";
SWIFT_OBJC_BRIDGING_HEADER = "pass/Helpers/Objective-CBridgingHeader.h";
SWIFT_SWIFT3_OBJC_INFERENCE = Default;

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0910"
LastUpgradeVersion = "0930"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
@ -26,7 +26,6 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
language = ""
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
<TestableReference
@ -66,7 +65,6 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
language = ""
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"

View file

@ -8,7 +8,6 @@
import UIKit
import CoreData
import PasscodeLock
import SVProgressHUD
import passKit
@ -21,7 +20,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
}
lazy var passcodeLockPresenter: PasscodeLockPresenter = {
let presenter = PasscodeLockPresenter(mainWindow: self.window, configuration: PasscodeLockConfiguration.shared)
let presenter = PasscodeLockPresenter(mainWindow: self.window)
return presenter
}()
@ -92,7 +91,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
func applicationWillEnterForeground(_ application: UIApplication) {
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
passcodeLockPresenter = PasscodeLockPresenter(mainWindow: self.window, configuration: PasscodeLockConfiguration.shared)
passcodeLockPresenter.present(windowLevel: UIApplication.shared.windows.last?.windowLevel)
}

View file

@ -195,29 +195,12 @@
</subviews>
</tableViewCellContentView>
</tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="none" indentationWidth="10" reuseIdentifier="touchIDTableViewCell" textLabel="H2E-hP-Gyf" style="IBUITableViewCellStyleDefault" id="wB7-Km-Oel">
<rect key="frame" x="0.0" y="267" width="414" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="wB7-Km-Oel" id="m90-X7-DbI">
<rect key="frame" x="0.0" y="0.0" width="414" height="43.666666666666664"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Touch ID" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" id="H2E-hP-Gyf">
<rect key="frame" x="20" y="0.0" width="374" height="43.666666666666664"/>
<autoresizingMask key="autoresizingMask"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
</tableViewCellContentView>
</tableViewCell>
</cells>
</tableViewSection>
<tableViewSection id="6U8-ue-MhL">
<cells>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" reuseIdentifier="advancedTableViewCell" textLabel="MKj-d0-8q3" style="IBUITableViewCellStyleDefault" id="tQN-gu-iRe">
<rect key="frame" x="0.0" y="347" width="414" height="44"/>
<rect key="frame" x="0.0" y="303" width="414" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="tQN-gu-iRe" id="Xs0-LN-r43">
<rect key="frame" x="0.0" y="0.0" width="376" height="43.666666666666664"/>
@ -241,7 +224,7 @@
<tableViewSection id="T7L-rR-R9W">
<cells>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" reuseIdentifier="aboutTableViewCell" textLabel="oqz-Hr-RAl" style="IBUITableViewCellStyleDefault" id="osS-xk-WRP">
<rect key="frame" x="0.0" y="427" width="414" height="44"/>
<rect key="frame" x="0.0" y="383" width="414" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="osS-xk-WRP" id="G6j-ij-rNr">
<rect key="frame" x="0.0" y="0.0" width="376" height="43.666666666666664"/>
@ -273,7 +256,6 @@
<outlet property="passcodeTableViewCell" destination="6Y0-mj-qhA" id="vkI-h5-GRo"/>
<outlet property="passwordRepositoryTableViewCell" destination="2rc-ZW-XKd" id="aFq-K7-eIj"/>
<outlet property="pgpKeyTableViewCell" destination="1ze-MS-Xbj" id="hXe-eD-0R4"/>
<outlet property="touchIDTableViewCell" destination="wB7-Km-Oel" id="0fi-Sb-qMa"/>
<segue destination="ZUt-x1-TJu" kind="showDetail" identifier="setPGPKeyByURLSegue" id="qRF-S1-bqF"/>
<segue destination="ffY-rC-jhq" kind="showDetail" identifier="setPGPKeyByASCIISegue" id="mgi-Oe-i2X"/>
</connections>

View file

@ -17,9 +17,6 @@ class OpenSourceComponentsTableViewController: BasicStaticTableViewController {
["KeychainAccess",
"https://github.com/kishikawakatsumi/KeychainAccess",
"https://github.com/kishikawakatsumi/KeychainAccess/blob/master/LICENSE"],
["PasscodeLock",
"https://github.com/zahlz/SwiftPasscodeLock",
"https://github.com/zahlz/SwiftPasscodeLock/blob/master/LICENSE.txt"],
["ObjectiveGit",
"https://github.com/libgit2/objective-git",
"https://github.com/libgit2/objective-git/blob/master/LICENSE"],
@ -35,6 +32,9 @@ class OpenSourceComponentsTableViewController: BasicStaticTableViewController {
["SVProgressHUD",
"https://github.com/SVProgressHUD/SVProgressHUD",
"https://github.com/SVProgressHUD/SVProgressHUD/blob/master/LICENSE.txt"],
["Yams",
"https://github.com/jpsim/Yams",
"https://github.com/jpsim/Yams/blob/master/LICENSE"],
]
override func viewDidLoad() {

View file

@ -28,7 +28,7 @@ class PasswordEditorTableViewController: UITableViewController, FillPasswordTabl
private var navigationItemTitle: String?
private var sectionHeaderTitles = ["name", "password", "additions",""].map {$0.uppercased()}
private var sectionFooterTitles = ["", "", "Use \"key: value\" format for additional fields.", ""]
private var sectionFooterTitles = ["", "", "Use YAML format for additional fields.", ""]
private let nameSection = 0
private let passwordSection = 1
private let additionsSection = 2

View file

@ -229,6 +229,11 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
// reset the data table if the disaply settings have been changed
NotificationCenter.default.addObserver(self, selector: #selector(actOnReloadTableViewRelatedNotification), name: .passwordDisplaySettingChanged, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(actOnSearchNotification), name: .passwordSearch, object: nil)
// listen to the swipe back guesture
let swipeRight = UISwipeGestureRecognizer(target: self, action: #selector(self.respondToSwipeGesture))
swipeRight.direction = UISwipeGestureRecognizerDirection.right
self.view.addGestureRecognizer(swipeRight)
}
override func viewWillAppear(_ animated: Bool) {
@ -314,6 +319,15 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
}
}
@objc func respondToSwipeGesture(gesture: UIGestureRecognizer) {
if let swipeGesture = gesture as? UISwipeGestureRecognizer {
// swipe right -> swipe back
if swipeGesture.direction == .right && parentPasswordEntity != nil {
self.backAction(nil)
}
}
}
@objc func backAction(_ sender: Any?) {
guard SharedDefaults[.isShowFolderOn] else { return }
var anim: CATransition? = transitionFromLeft

View file

@ -9,25 +9,16 @@
import UIKit
import SVProgressHUD
import CoreData
import PasscodeLock
import LocalAuthentication
import passKit
class SettingsTableViewController: UITableViewController, UITabBarControllerDelegate {
lazy var touchIDSwitch: UISwitch = {
let uiSwitch = UISwitch(frame: CGRect.zero)
uiSwitch.onTintColor = Globals.blue
uiSwitch.addTarget(self, action: #selector(touchIDSwitchAction), for: UIControlEvents.valueChanged)
return uiSwitch
}()
@IBOutlet weak var pgpKeyTableViewCell: UITableViewCell!
@IBOutlet weak var touchIDTableViewCell: UITableViewCell!
@IBOutlet weak var passcodeTableViewCell: UITableViewCell!
@IBOutlet weak var passwordRepositoryTableViewCell: UITableViewCell!
var setPasscodeLockAlert: UIAlertController?
let passwordStore = PasswordStore.shared
var passcodeLockConfig = PasscodeLockConfiguration.shared
var passcodeLock = PasscodeLock.shared
func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
navigationController?.popViewController(animated: true)
@ -77,8 +68,8 @@ class SettingsTableViewController: UITableViewController, UITabBarControllerDele
SVProgressHUD.show(withStatus: "Fetching PGP Key")
DispatchQueue.global(qos: .userInitiated).async { [unowned self] in
do {
try self.passwordStore.initPGPKey(with: controller.armorPublicKeyTextView.text, keyType: .public)
try self.passwordStore.initPGPKey(with: controller.armorPrivateKeyTextView.text, keyType: .secret)
try self.passwordStore.initPGPKey(with: SharedDefaults[.pgpPublicKeyArmor] ?? "", keyType: .public)
try self.passwordStore.initPGPKey(with: SharedDefaults[.pgpPrivateKeyArmor] ?? "", keyType: .secret)
DispatchQueue.main.async {
self.pgpKeyTableViewCell.detailTextLabel?.text = self.passwordStore.pgpKeyID
SVProgressHUD.showSuccess(withStatus: "Success")
@ -123,14 +114,6 @@ class SettingsTableViewController: UITableViewController, UITabBarControllerDele
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// Security section, hide TouchID if the device doesn't support
if section == 1 {
if hasTouchID() {
return 2
} else {
return 1
}
}
return super.tableView(tableView, numberOfRowsInSection: section)
}
@ -138,10 +121,9 @@ class SettingsTableViewController: UITableViewController, UITabBarControllerDele
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(SettingsTableViewController.actOnPasswordStoreErasedNotification), name: .passwordStoreErased, object: nil)
self.passwordRepositoryTableViewCell.detailTextLabel?.text = SharedDefaults[.gitURL]?.host
touchIDTableViewCell.accessoryView = touchIDSwitch
setPGPKeyTableViewCellDetailText()
setPasswordRepositoryTableViewCellDetailText()
setPasscodeLockTouchIDCells()
setPasscodeLockCell()
}
override func viewWillAppear(_ animated: Bool) {
@ -149,38 +131,11 @@ class SettingsTableViewController: UITableViewController, UITabBarControllerDele
tabBarController!.delegate = self
}
private func hasTouchID() -> Bool {
let context = LAContext()
var error: NSError?
if context.canEvaluatePolicy(LAPolicy.deviceOwnerAuthenticationWithBiometrics, error: &error) {
return true
} else {
switch error!.code {
case LAError.Code.touchIDNotEnrolled.rawValue:
return true
case LAError.Code.passcodeNotSet.rawValue:
return true
default:
return false
}
}
}
private func isTouchIDEnabled() -> Bool {
let context = LAContext()
return context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: nil)
}
private func setPasscodeLockTouchIDCells() {
if passcodeLockConfig.repository.hasPasscode {
private func setPasscodeLockCell() {
if passcodeLock.hasPasscode {
self.passcodeTableViewCell.detailTextLabel?.text = "On"
passcodeLockConfig.isTouchIDAllowed = SharedDefaults[.isTouchIDOn]
touchIDSwitch.isOn = SharedDefaults[.isTouchIDOn]
} else {
self.passcodeTableViewCell.detailTextLabel?.text = "Off"
SharedDefaults[.isTouchIDOn] = false
passcodeLockConfig.isTouchIDAllowed = SharedDefaults[.isTouchIDOn]
touchIDSwitch.isOn = SharedDefaults[.isTouchIDOn]
}
}
@ -203,10 +158,7 @@ class SettingsTableViewController: UITableViewController, UITabBarControllerDele
@objc func actOnPasswordStoreErasedNotification() {
setPGPKeyTableViewCellDetailText()
setPasswordRepositoryTableViewCellDetailText()
setPasscodeLockTouchIDCells()
let appDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.passcodeLockPresenter = PasscodeLockPresenter(mainWindow: appDelegate.window, configuration: passcodeLockConfig)
setPasscodeLockCell()
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
@ -222,21 +174,6 @@ class SettingsTableViewController: UITableViewController, UITabBarControllerDele
tableView.deselectRow(at: indexPath, animated: true)
}
@objc func touchIDSwitchAction(uiSwitch: UISwitch) {
if !passcodeLockConfig.repository.hasPasscode || !isTouchIDEnabled() {
// switch off
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(500)) {
uiSwitch.isOn = SharedDefaults[.isTouchIDOn] // SharedDefaults[.isTouchIDOn] should be false
Utils.alert(title: "Notice", message: "Please enable Touch ID of your phone and setup the passcode lock for Pass.", controller: self, completion: nil)
}
} else {
SharedDefaults[.isTouchIDOn] = uiSwitch.isOn
passcodeLockConfig.isTouchIDAllowed = SharedDefaults[.isTouchIDOn]
}
let appDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.passcodeLockPresenter = PasscodeLockPresenter(mainWindow: appDelegate.window, configuration: passcodeLockConfig)
}
func showPGPKeyActionSheet() {
let optionMenu = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
var urlActionTitle = "Download from URL"
@ -314,21 +251,20 @@ class SettingsTableViewController: UITableViewController, UITabBarControllerDele
}
func showPasscodeActionSheet() {
let passcodeChangeViewController = PasscodeLockViewController(state: .change, configuration: passcodeLockConfig)
let passcodeRemoveViewController = PasscodeLockViewController(state: .remove, configuration: passcodeLockConfig)
let optionMenu = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
let passcodeRemoveViewController = PasscodeLockViewController()
let removePasscodeAction = UIAlertAction(title: "Remove Passcode", style: .destructive) { [weak self] _ in
passcodeRemoveViewController.successCallback = { _ in
self?.setPasscodeLockTouchIDCells()
let appDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.passcodeLockPresenter = PasscodeLockPresenter(mainWindow: appDelegate.window, configuration: (self?.passcodeLockConfig)!)
passcodeRemoveViewController.successCallback = {
self?.passcodeLock.delete()
self?.setPasscodeLockCell()
}
self?.present(passcodeRemoveViewController, animated: true, completion: nil)
}
let changePasscodeAction = UIAlertAction(title: "Change Passcode", style: .default) { [weak self] _ in
self?.present(passcodeChangeViewController, animated: true, completion: nil)
self?.setPasscodeLock()
}
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
@ -340,11 +276,49 @@ class SettingsTableViewController: UITableViewController, UITabBarControllerDele
self.present(optionMenu, animated: true, completion: nil)
}
@objc func alertTextFieldDidChange(_ sender: UITextField) {
// check whether we should enable the Save button in setPasscodeLockAlert
if let setPasscodeLockAlert = self.setPasscodeLockAlert,
let setPasscodeLockAlertTextFields0 = setPasscodeLockAlert.textFields?[0],
let setPasscodeLockAlertTextFields1 = setPasscodeLockAlert.textFields?[1] {
if sender == setPasscodeLockAlertTextFields0 || sender == setPasscodeLockAlertTextFields1 {
// two passwords should be the same, and length >= 4
let passcodeText = setPasscodeLockAlertTextFields0.text!
let passcodeConfirmationText = setPasscodeLockAlertTextFields1.text!
setPasscodeLockAlert.actions[0].isEnabled = passcodeText == passcodeConfirmationText && passcodeText.count >= 4
}
}
}
func setPasscodeLock() {
let passcodeSetViewController = PasscodeLockViewController(state: .set, configuration: passcodeLockConfig)
passcodeSetViewController.successCallback = { _ in
self.setPasscodeLockTouchIDCells()
// prepare the alert for setting the passcode
setPasscodeLockAlert = UIAlertController(title: "Set passcode", message: "Fill in your passcode for Pass (at least 4 characters)", preferredStyle: .alert)
setPasscodeLockAlert?.addTextField(configurationHandler: {(_ textField: UITextField) -> Void in
textField.placeholder = "Password"
textField.isSecureTextEntry = true
textField.addTarget(self, action: #selector(self.alertTextFieldDidChange(_:)), for: UIControlEvents.editingChanged)
})
setPasscodeLockAlert?.addTextField(configurationHandler: {(_ textField: UITextField) -> Void in
textField.placeholder = "Password Confirmation"
textField.isSecureTextEntry = true
textField.addTarget(self, action: #selector(self.alertTextFieldDidChange(_:)), for: UIControlEvents.editingChanged)
})
// save action
let saveAction = UIAlertAction(title: "Save", style: .default) { (action:UIAlertAction) -> Void in
let passcode: String = self.setPasscodeLockAlert!.textFields![0].text!
self.passcodeLock.save(passcode: passcode)
// refresh the passcode lock cell ("On")
self.setPasscodeLockCell()
}
present(passcodeSetViewController, animated: true, completion: nil)
saveAction.isEnabled = false // disable the Save button by default
// cancel action
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
// present
setPasscodeLockAlert?.addAction(saveAction)
setPasscodeLockAlert?.addAction(cancelAction)
self.present(setPasscodeLockAlert!, animated: true, completion: nil)
}
}

View file

@ -17,7 +17,7 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>0.3.0</string>
<string>0.3.1</string>
<key>CFBundleURLTypes</key>
<array>
<dict>

View file

@ -1,11 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="13196" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14109" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13173"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14088"/>
<capability name="Constraints to layout margins" minToolsVersion="6.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
@ -20,19 +20,19 @@
<autoresizingMask key="autoresizingMask"/>
<subviews>
<stackView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="19l-R0-gP1">
<rect key="frame" x="8" y="11" width="304" height="68"/>
<rect key="frame" x="16" y="11" width="288" height="68"/>
<subviews>
<textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" horizontalHuggingPriority="249" contentHorizontalAlignment="left" contentVerticalAlignment="center" placeholder="password" textAlignment="natural" minimumFontSize="10" translatesAutoresizingMaskIntoConstraints="NO" id="k0U-2N-YaX" userLabel="Password">
<rect key="frame" x="0.0" y="0.0" width="244" height="68"/>
<rect key="frame" x="0.0" y="0.0" width="228" height="68"/>
<nil key="textColor"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
<textInputTraits key="textInputTraits" autocorrectionType="no" spellCheckingType="no" keyboardType="alphabet"/>
<textInputTraits key="textInputTraits" autocorrectionType="no" spellCheckingType="no" keyboardType="alphabet" textContentType="password"/>
<connections>
<action selector="textFieldDidChange:" destination="KGk-i7-Jjw" eventType="editingChanged" id="U0t-2B-JxY"/>
</connections>
</textField>
<button opaque="NO" contentMode="scaleAspectFit" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="hTh-ek-Xam" userLabel="Generate">
<rect key="frame" x="244" y="0.0" width="30" height="68"/>
<rect key="frame" x="228" y="0.0" width="30" height="68"/>
<constraints>
<constraint firstAttribute="width" constant="30" id="l0l-7B-Tws"/>
</constraints>
@ -43,7 +43,7 @@
</connections>
</button>
<button opaque="NO" contentMode="scaleAspectFit" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="SZJ-aY-45Y" userLabel="Setting">
<rect key="frame" x="274" y="0.0" width="30" height="68"/>
<rect key="frame" x="258" y="0.0" width="30" height="68"/>
<constraints>
<constraint firstAttribute="width" constant="30" id="D9D-FC-ANz"/>
</constraints>

View file

@ -39,8 +39,7 @@ class LabelTableViewCell: UITableViewCell {
return
}
titleLabel.text = title
switch title.lowercased() {
case "password":
if title.caseInsensitiveCompare("password") == .orderedSame {
type = .password
if isReveal {
contentLabel.attributedText = Utils.attributedPassword(plainPassword: content)
@ -52,7 +51,7 @@ class LabelTableViewCell: UITableViewCell {
}
}
contentLabel.font = Globals.passwordFont
case "hmac-based":
} else if title.caseInsensitiveCompare("hmac-based") == .orderedSame {
type = .HOTP
if isReveal {
contentLabel.text = content
@ -60,11 +59,12 @@ class LabelTableViewCell: UITableViewCell {
contentLabel.text = Globals.oneTimePasswordDots
}
contentLabel.font = Globals.passwordFont
case "url":
} else if title.lowercased().range(of: "url") != nil || verifyUrl(content) {
type = .URL
contentLabel.text = content
contentLabel.font = UIFont.systemFont(ofSize: contentLabel.font.pointSize)
default:
} else {
// default
type = .other
contentLabel.text = content
contentLabel.font = UIFont.systemFont(ofSize: contentLabel.font.pointSize)
@ -198,4 +198,12 @@ class LabelTableViewCell: UITableViewCell {
}
self.accessoryView = buttons
}
private func verifyUrl(_ urlString: String?) -> Bool {
guard let urlString = urlString,
let _ = URL(string: urlString) else {
return false
}
return true
}
}

View file

@ -17,7 +17,7 @@
<key>CFBundlePackageType</key>
<string>XPC!</string>
<key>CFBundleShortVersionString</key>
<string>0.3.0</string>
<string>0.3.1</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>NSExtension</key>
@ -25,14 +25,17 @@
<key>NSExtensionAttributes</key>
<dict>
<key>NSExtensionActivationRule</key>
<dict>
<key>NSExtensionActivationSupportsText</key>
<true/>
<key>NSExtensionActivationSupportsWebPageWithMaxCount</key>
<integer>1</integer>
<key>NSExtensionActivationSupportsWebURLWithMaxCount</key>
<integer>1</integer>
</dict>
<string>SUBQUERY (
extensionItems,
$extensionItem,
SUBQUERY (
$extensionItem.attachments,
$attachment,
ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "org.appextension.find-login-action" ||
ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.url" ||
ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.plain-text"
).@count == $extensionItem.attachments.@count
).@count == 1</string>
<key>NSExtensionJavaScriptPreprocessingFile</key>
<string>passProcessor</string>
</dict>

View file

@ -7,27 +7,13 @@
//
import Foundation
import PasscodeLock
import passKit
// add a cancel button in the passcode lock view
struct CancelableEnterPasscodeState: PasscodeLockStateType {
let title: String = "Enter passcode"
let description: String = "Enter passcode"
let isCancellableAction = true
var isTouchIDAllowed = true
mutating func accept(passcode: String, from lock: PasscodeLockType) {
if lock.repository.check(passcode: passcode) {
lock.delegate?.passcodeLockDidSucceed(lock)
}
}
}
// cancel means cancel the extension
class PasscodeLockViewControllerForExtension: PasscodeLockViewController {
var originalExtensionContest: NSExtensionContext?
public convenience init(extensionContext: NSExtensionContext?, state: PasscodeLockStateType, configuration: PasscodeLockConfigurationType, animateOnDismiss: Bool = true) {
self.init(state: state, configuration: configuration, animateOnDismiss: animateOnDismiss)
public convenience init(extensionContext: NSExtensionContext?) {
self.init()
originalExtensionContest = extensionContext
}
override func viewDidLoad() {
@ -43,18 +29,20 @@ class PasscodeLockViewControllerForExtension: PasscodeLockViewController {
class PasscodeExtensionDisplay {
private var isPasscodePresented = false
private let passcodeLockVC: PasscodeLockViewControllerForExtension
private let extensionContext: NSExtensionContext?
init(extensionContext: NSExtensionContext?) {
let cancelableEnter = CancelableEnterPasscodeState()
passcodeLockVC = PasscodeLockViewControllerForExtension(extensionContext: extensionContext, state: cancelableEnter, configuration: PasscodeLockConfiguration.shared)
self.extensionContext = extensionContext
passcodeLockVC = PasscodeLockViewControllerForExtension(extensionContext: extensionContext)
passcodeLockVC.dismissCompletionCallback = { [weak self] in
self?.dismiss()
}
passcodeLockVC.setCancellable(true)
}
// present the passcode lock view if passcode is set and the view controller is not presented
func presentPasscodeLockIfNeeded(_ extensionVC: ExtensionViewController) {
guard PasscodeLockConfiguration.shared.repository.hasPasscode && !isPasscodePresented == true else {
guard PasscodeLock.shared.hasPasscode && !isPasscodePresented == true else {
return
}
isPasscodePresented = true

View file

@ -0,0 +1,54 @@
//
// PasscodeLockPresenter.swift
// PasscodeLock
//
// Created by Yishi Lin on 10/04/2018.
// Copyright © 2018 Yishi Lin. All rights reserved.
//
// Inspired by SwiftPasscodeLock created by Yanko Dimitrov.
//
import UIKit
open class PasscodeLockPresenter {
fileprivate var mainWindow: UIWindow?
fileprivate var passcodeLockWindow: UIWindow?
public init(mainWindow window: UIWindow?) {
self.mainWindow = window
}
open func present(windowLevel: CGFloat?) {
guard PasscodeLock.shared.hasPasscode else { return }
// dismiss the original window
dismiss()
// new window
mainWindow?.endEditing(true)
passcodeLockWindow = UIWindow(frame: self.mainWindow!.frame)
moveWindowsToFront(windowLevel: windowLevel)
passcodeLockWindow?.isHidden = false
// new vc
let passcodeLockVC = PasscodeLockViewController()
let userDismissCompletionCallback = passcodeLockVC.dismissCompletionCallback
passcodeLockVC.dismissCompletionCallback = { [weak self] in
userDismissCompletionCallback?()
self?.dismiss()
}
passcodeLockWindow?.rootViewController = passcodeLockVC
}
open func dismiss() {
passcodeLockWindow?.isHidden = true
passcodeLockWindow?.rootViewController = nil
}
fileprivate func moveWindowsToFront(windowLevel: CGFloat?) {
let windowLevel = windowLevel ?? UIWindowLevelNormal
let maxWinLevel = max(windowLevel, UIWindowLevelNormal)
passcodeLockWindow?.windowLevel = maxWinLevel + 1
}
}

View file

@ -0,0 +1,212 @@
//
// PasscodeLockPresenter.swift
// PasscodeLock
//
// Created by Yishi Lin on 10/04/2018.
// Copyright © 2018 Yishi Lin. All rights reserved.
//
// Inspired by SwiftPasscodeLock created by Yanko Dimitrov.
//
import UIKit
import LocalAuthentication
open class PasscodeLockViewController: UIViewController, UITextFieldDelegate {
open var dismissCompletionCallback: (()->Void)?
open var successCallback: (()->Void)?
open var cancelCallback: (()->Void)?
weak var passcodeLabel: UILabel?
weak var passcodeWrongAttemptsLabel: UILabel?
weak var passcodeTextField: UITextField?
weak var biometryAuthButton: UIButton?
open weak var cancelButton: UIButton?
var passcodeFailedAttempts = 0
var isCancellable: Bool = false
open override func loadView() {
super.loadView()
let passcodeLabel = UILabel(frame: CGRect(x: 0, y: 0, width: 300, height: 40))
passcodeLabel.text = "Enter passcode for Pass"
passcodeLabel.font = UIFont.boldSystemFont(ofSize: 18)
passcodeLabel.textColor = UIColor.black
passcodeLabel.textAlignment = .center
passcodeLabel.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(passcodeLabel)
self.passcodeLabel = passcodeLabel
let passcodeWrongAttemptsLabel = UILabel(frame: CGRect(x: 0, y: 0, width: 300, height: 40))
passcodeWrongAttemptsLabel.text = ""
passcodeWrongAttemptsLabel.textColor = UIColor.red
passcodeWrongAttemptsLabel.textAlignment = .center
passcodeWrongAttemptsLabel.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(passcodeWrongAttemptsLabel)
self.passcodeWrongAttemptsLabel = passcodeWrongAttemptsLabel
let passcodeTextField = UITextField(frame: CGRect(x: 0, y: 0, width: 300, height: 40))
passcodeTextField.borderStyle = UITextBorderStyle.roundedRect
passcodeTextField.placeholder = "passcode"
passcodeTextField.isSecureTextEntry = true
passcodeTextField.clearButtonMode = UITextFieldViewMode.whileEditing
passcodeTextField.delegate = self
passcodeTextField.addTarget(self, action: #selector(self.passcodeTextFieldDidChange(_:)), for: UIControlEvents.editingChanged)
self.view.backgroundColor = UIColor.white
passcodeTextField.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(passcodeTextField)
self.passcodeTextField = passcodeTextField
let biometryAuthButton = UIButton(type: .custom)
biometryAuthButton.setTitle("", for: .normal)
biometryAuthButton.setTitleColor(Globals.blue, for: .normal)
biometryAuthButton.addTarget(self, action: #selector(bioButtonPressedAction(_:)), for: .touchUpInside)
biometryAuthButton.isHidden = true
biometryAuthButton.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(biometryAuthButton)
self.biometryAuthButton = biometryAuthButton
let myContext = LAContext()
var authError: NSError?
if #available(iOS 8.0, macOS 10.12.1, *) {
if myContext.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &authError) {
var biometryType = "Touch ID"
if #available(iOS 11.0, *) {
if myContext.biometryType == LABiometryType.faceID {
biometryType = "Face ID"
}
}
biometryAuthButton.setTitle(biometryType, for: .normal)
biometryAuthButton.isHidden = false
}
}
let cancelButton = UIButton(type: .custom)
cancelButton.setTitle("Cancel", for: .normal)
cancelButton.setTitleColor(Globals.blue, for: .normal)
cancelButton.addTarget(self, action: #selector(passcodeLockDidCancel), for: .touchUpInside)
cancelButton.isHidden = !self.isCancellable
cancelButton.translatesAutoresizingMaskIntoConstraints = false
cancelButton.contentHorizontalAlignment = UIControlContentHorizontalAlignment.left
self.view.addSubview(cancelButton)
self.cancelButton = cancelButton
NSLayoutConstraint.activate([
passcodeTextField.widthAnchor.constraint(equalToConstant: 300),
passcodeTextField.heightAnchor.constraint(equalToConstant: 40),
passcodeTextField.centerXAnchor.constraint(equalTo: self.view.centerXAnchor),
passcodeTextField.centerYAnchor.constraint(equalTo: self.view.centerYAnchor, constant: -20),
// above passocde
passcodeLabel.widthAnchor.constraint(equalToConstant: 300),
passcodeLabel.heightAnchor.constraint(equalToConstant: 40),
passcodeLabel.centerXAnchor.constraint(equalTo: self.view.centerXAnchor),
passcodeLabel.bottomAnchor.constraint(equalTo: passcodeTextField.topAnchor),
// below passcode
passcodeWrongAttemptsLabel.widthAnchor.constraint(equalToConstant: 300),
passcodeWrongAttemptsLabel.heightAnchor.constraint(equalToConstant: 40),
passcodeWrongAttemptsLabel.centerXAnchor.constraint(equalTo: self.view.centerXAnchor),
passcodeWrongAttemptsLabel.topAnchor.constraint(equalTo: passcodeTextField.bottomAnchor),
// bottom of the screen
biometryAuthButton.widthAnchor.constraint(equalToConstant: 150),
biometryAuthButton.heightAnchor.constraint(equalToConstant: 40),
biometryAuthButton.centerXAnchor.constraint(equalTo: self.view.centerXAnchor),
biometryAuthButton.bottomAnchor.constraint(equalTo: self.view.safeBottomAnchor, constant: -40),
// cancel (top-left of the screen)
cancelButton.widthAnchor.constraint(equalToConstant: 150),
cancelButton.heightAnchor.constraint(equalToConstant: 40),
cancelButton.topAnchor.constraint(equalTo: self.view.safeTopAnchor),
cancelButton.leftAnchor.constraint(equalTo: self.view.safeLeftAnchor, constant: 20)
])
}
open override func viewDidLoad() {
super.viewDidLoad()
}
open override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if let biometryAuthButton = biometryAuthButton {
self.bioButtonPressedAction(biometryAuthButton)
}
}
internal func dismissPasscodeLock(completionHandler: (() -> Void)? = nil) {
// clean up the textfield
DispatchQueue.main.async {
self.passcodeTextField?.text = ""
}
// pop
if presentingViewController?.presentedViewController == self {
// if presented as modal
dismiss(animated: true, completion: { [weak self] in
self?.dismissCompletionCallback?()
completionHandler?()
})
} else {
// if pushed in a navigation controller
_ = navigationController?.popViewController(animated: true)
dismissCompletionCallback?()
completionHandler?()
}
}
// MARK: - PasscodeLockDelegate
open func passcodeLockDidSucceed() {
passcodeFailedAttempts = 0
passcodeWrongAttemptsLabel?.text = ""
dismissPasscodeLock(completionHandler: successCallback)
}
@objc func passcodeLockDidCancel() {
dismissPasscodeLock(completionHandler: cancelCallback)
}
@objc func bioButtonPressedAction(_ uiButton: UIButton) {
let myContext = LAContext()
let myLocalizedReasonString = "Authentication is needed to access Pass."
var authError: NSError?
if #available(iOS 8.0, *) {
if myContext.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &authError) {
myContext.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: myLocalizedReasonString) { success, evaluateError in
if success {
DispatchQueue.main.async {
// user authenticated successfully, take appropriate action
self.passcodeLockDidSucceed()
}
}
}
}
}
}
public override func textFieldShouldReturn(_ textField: UITextField) -> Bool {
if textField == passcodeTextField {
if !PasscodeLock.shared.check(passcode: textField.text ?? "") {
passcodeFailedAttempts = passcodeFailedAttempts + 1
if passcodeFailedAttempts == 1 {
passcodeWrongAttemptsLabel?.text = "1 wrong attempt"
} else {
passcodeWrongAttemptsLabel?.text = "\(passcodeFailedAttempts) wrong attempts"
}
}
}
textField.resignFirstResponder()
return true
}
@objc func passcodeTextFieldDidChange(_ textField: UITextField) {
if PasscodeLock.shared.check(passcode: textField.text ?? "") {
self.passcodeLockDidSucceed()
}
}
public func setCancellable(_ isCancellable: Bool) {
self.isCancellable = isCancellable
cancelButton?.isHidden = !isCancellable
}
}

View file

@ -16,6 +16,7 @@ public enum AppError: Error {
case GitResetError
case PGPPublicKeyNotExistError
case WrongPasswordFilename
case YamlLoadError
case UnknownError
}
@ -36,6 +37,8 @@ extension AppError: LocalizedError {
return "PGP public key doesn't exist."
case .WrongPasswordFilename:
return "Cannot write to the password file."
case .YamlLoadError:
return "Cannot be parsed as a YAML file."
case .UnknownError:
return "Unknown error."
}

View file

@ -23,7 +23,7 @@ extension UITextField {
}
extension UIViewController {
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
@objc public func textFieldShouldReturn(_ textField: UITextField) -> Bool {
if textField.nextField != nil {
textField.nextField?.becomeFirstResponder()
} else {

View file

@ -0,0 +1,45 @@
//
// UIViewExtension.swift
// passKit
//
// Created by Yishi Lin on 2018/4/11.
// Copyright © 2018 Yishi Lin. All rights reserved.
//
import Foundation
extension UIView {
// Save anchors: https://stackoverflow.com/questions/46317061/use-safe-area-layout-programmatically
var safeTopAnchor: NSLayoutYAxisAnchor {
if #available(iOS 11.0, *) {
return self.safeAreaLayoutGuide.topAnchor
} else {
return self.topAnchor
}
}
var safeLeftAnchor: NSLayoutXAxisAnchor {
if #available(iOS 11.0, *){
return self.safeAreaLayoutGuide.leftAnchor
} else {
return self.leftAnchor
}
}
var safeRightAnchor: NSLayoutXAxisAnchor {
if #available(iOS 11.0, *){
return self.safeAreaLayoutGuide.rightAnchor
} else {
return self.rightAnchor
}
}
var safeBottomAnchor: NSLayoutYAxisAnchor {
if #available(iOS 11.0, *) {
return self.safeAreaLayoutGuide.bottomAnchor
} else {
return self.bottomAnchor
}
}
}

View file

@ -17,7 +17,7 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>0.3.0</string>
<string>0.3.1</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>NSPrincipalClass</key>

View file

@ -1,28 +1,24 @@
//
// PasscodeRepository.swift
// pass
// PasscodeLock.swift
// PassKit
//
// Created by Mingshen Sun on 7/2/2017.
// Copyright © 2017 Bob Sun. All rights reserved.
// Created by Yishi Lin on 28/1/2018.
// Copyright © 2017 Yishi Lin. All rights reserved.
//
import Foundation
import PasscodeLock
import LocalAuthentication
public class PasscodeLockRepository: PasscodeRepositoryType {
private let passcodeKey = "passcode.lock.passcode"
open class PasscodeLock {
public static let shared = PasscodeLock()
fileprivate let passcodeKey = "passcode.lock.passcode"
fileprivate var passcode: String? {
return SharedDefaults[.passcodeKey]
}
public var hasPasscode: Bool {
if passcode != nil {
return true
}
return false
}
private var passcode: String? {
return SharedDefaults[.passcodeKey]
return passcode != nil
}
public func save(passcode: String) {

View file

@ -1,26 +0,0 @@
//
// PasscodeLockConfiguration.swift
// pass
//
// Created by Mingshen Sun on 7/2/2017.
// Copyright © 2017 Bob Sun. All rights reserved.
//
import Foundation
import PasscodeLock
public class PasscodeLockConfiguration: PasscodeLockConfigurationType {
public static let shared = PasscodeLockConfiguration()
public let repository: PasscodeRepositoryType
public let passcodeLength = 4
public var isTouchIDAllowed = SharedDefaults[.isTouchIDOn]
public let shouldRequestTouchIDImmediately = true
public let maximumInccorectPasscodeAttempts = 3
init() {
self.repository = PasscodeLockRepository()
}
}

View file

@ -10,6 +10,7 @@ import Foundation
import SwiftyUserDefaults
import OneTimePassword
import Base32
import Yams
struct AdditionField {
var title: String
@ -24,6 +25,8 @@ enum PasswordChange: Int {
public class Password {
public static let otpKeywords = ["otp_secret", "otp_type", "otp_algorithm", "otp_period", "otp_digits", "otp_counter", "otpauth"]
private static let OTPAUTH = "otpauth"
private static let OTPAUTH_URL_START = "\(OTPAUTH)://"
public var name = ""
public var url: URL?
@ -84,29 +87,34 @@ public class Password {
additions.removeAll()
// split the plain text
let plainTextSplit = plainText.split(maxSplits: 1, omittingEmptySubsequences: false) {
let plainTextSplit = self.plainText.split(omittingEmptySubsequences: false) {
$0 == "\n" || $0 == "\r\n"
}.map(String.init)
// get password
password = plainTextSplit.first ?? ""
// get additonal fields
if plainTextSplit.count == 2 {
var unknownIndex = 0
plainTextSplit[1].enumerateLines() { line, _ in
if !line.isEmpty {
var (key, value) = Password.getKeyValuePair(from: line)
if key == nil {
unknownIndex += 1
key = "unknown \(unknownIndex)"
}
self.additions.append((key!, value))
}
// get remaining lines (filter out empty lines)
let additionalLines = plainTextSplit[1...].filter { !$0.isEmpty }
// separate normal lines (no otp tokens)
let normalAdditionalLines = additionalLines.filter {
!$0.hasPrefix(Password.OTPAUTH_URL_START)
}.joined(separator: "\n")
// try to interpret the text format as YAML first
do {
try getAdditionalFields(fromYaml: normalAdditionalLines)
}
catch {
getAdditionalFields(fromPlainText: normalAdditionalLines)
}
// check whether the first line of the plainText looks like an otp entry
// get and append otp tokens
let otpAdditionalLines = additionalLines.filter { $0.hasPrefix(Password.OTPAUTH_URL_START) }
otpAdditionalLines.forEach { self.additions.append((Password.OTPAUTH, $0)) }
// check whether the first line looks like an otp entry
let (key, value) = Password.getKeyValuePair(from: self.password)
if Password.otpKeywords.contains(key ?? "") {
firstLineIsOTPField = true
@ -119,6 +127,48 @@ public class Password {
self.updateOtpToken()
}
// check whether the file has lines with duplicated field names
private func checkDuplicatedFields(lines: String) -> Bool {
var keys = Set<String>()
var hasDuplicatedFields = false
lines.enumerateLines { (line, stop) -> () in
let (key, _) = Password.getKeyValuePair(from: line)
if let key = key {
if keys.contains(key) {
hasDuplicatedFields = true
stop = true
}
keys.insert(key)
}
}
return hasDuplicatedFields
}
private func getAdditionalFields(fromYaml: String) throws {
guard !fromYaml.isEmpty else { return }
if checkDuplicatedFields(lines: fromYaml) {
throw AppError.YamlLoadError
}
guard let yamlFile = try Yams.load(yaml: fromYaml) as? [String: Any] else {
throw AppError.YamlLoadError
}
additions.append(contentsOf: yamlFile.map { ($0, String(describing: $1)) })
}
private func getAdditionalFields(fromPlainText: String) {
var unknownIndex = 0
fromPlainText.enumerateLines() { line, _ in
if !line.isEmpty {
var (key, value) = Password.getKeyValuePair(from: line)
if key == nil {
unknownIndex += 1
key = "unknown \(unknownIndex)"
}
self.additions.append((key!, value))
}
}
}
public func getFilteredAdditions() -> [(String, String)] {
var filteredAdditions = [(String, String)]()
additions.forEach { (key: String, value: String) in
@ -153,8 +203,8 @@ public class Password {
// no ": " found, or empty on both sides of ": "
value = line
// otpauth special case
if value.hasPrefix("otpauth://") {
key = "otpauth"
if value.hasPrefix(Password.OTPAUTH_URL_START) {
key = Password.OTPAUTH
}
} else {
if !items[0].isEmpty {
@ -221,9 +271,9 @@ public class Password {
self.otpToken = nil
// get otpauth, if we are able to generate a token, return
if var otpauthString = getAdditionValue(withKey: "otpauth") {
if !otpauthString.hasPrefix("otpauth:") {
otpauthString = "otpauth:\(otpauthString)"
if var otpauthString = getAdditionValue(withKey: Password.OTPAUTH) {
if !otpauthString.hasPrefix("\(Password.OTPAUTH):") {
otpauthString = "\(Password.OTPAUTH):\(otpauthString)"
}
if let otpauthUrl = URL(string: otpauthString),
let token = Token(url: otpauthUrl) {
@ -347,7 +397,7 @@ public class Password {
let (key, _) = Password.getKeyValuePair(from: line)
if !Password.otpKeywords.contains(key ?? "") {
lines.append(line)
} else if key == "otpauth" && newOtpauth != nil {
} else if key == Password.OTPAUTH && newOtpauth != nil {
lines.append(newOtpauth!)
// set to nil to prevent duplication
newOtpauth = nil

View file

@ -654,9 +654,9 @@ public class PasswordStore {
public func delete(passwordEntity: PasswordEntity) throws {
let deletedFileURL = passwordEntity.getURL()!
try deleteDirectoryTree(at: passwordEntity.getURL()!)
try deletePasswordEntities(passwordEntity: passwordEntity)
try gitRm(path: deletedFileURL.path)
try deletePasswordEntities(passwordEntity: passwordEntity)
try deleteDirectoryTree(at: deletedFileURL)
let _ = try gitCommit(message: "Remove \(deletedFileURL.deletingPathExtension().path.removingPercentEncoding!) from store using Pass for iOS.")
NotificationCenter.default.post(name: .passwordStoreUpdated, object: nil)
}