Merge branch 'release/0.2.5'
|
|
@ -10,8 +10,8 @@ Pass is an iOS client compatible with [ZX2C4's Pass command line
|
|||
application](http://www.passwordstore.org/). It is a password manager using
|
||||
GPG for encryption and Git for version control.
|
||||
|
||||
Pass for iOS is under *TestFlight external testing*. Drop me an email for
|
||||
testing. Thank you.
|
||||
Pass for iOS is under *TestFlight external testing*. Drop an email to
|
||||
`developer@passforios.mssun.me` for testing. Thank you.
|
||||
|
||||
## Features
|
||||
|
||||
|
|
@ -20,7 +20,7 @@ testing. Thank you.
|
|||
- Encrypt and decrypt password entries by PGP keys
|
||||
- Synchronize with your password Git repository
|
||||
- User-friendly interface: search, long press to copy, copy and open link, etc.
|
||||
- Support one-time password (OTP) tokens
|
||||
- Support one-time password (OTP) tokens (QR code and otpauth URI)
|
||||
- Written in Swift
|
||||
- No need to jailbreak your devices
|
||||
- Get from App Store (stay tuned)
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
/* Begin PBXBuildFile section */
|
||||
94BA784B85E071D25EE89B59 /* libPods-pass.a in Frameworks */ = {isa = PBXBuildFile; fileRef = ADCE7A5C3CCC67D7D21BB3C4 /* libPods-pass.a */; };
|
||||
A217ACE21E9AB17C00A1A6CF /* OTPScannerController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A217ACE11E9AB17C00A1A6CF /* OTPScannerController.swift */; };
|
||||
A217ACE41E9BBBBD00A1A6CF /* GitConfigSettingTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A217ACE31E9BBBBD00A1A6CF /* GitConfigSettingTableViewController.swift */; };
|
||||
A262A58D1E68749C006B0890 /* Base32.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A262A58C1E68749C006B0890 /* Base32.framework */; };
|
||||
A27424D91E7C35960093F436 /* NotificationNames.swift in Sources */ = {isa = PBXBuildFile; fileRef = A27424D81E7C35960093F436 /* NotificationNames.swift */; };
|
||||
A2802BF91E70813A00879216 /* SliderTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = A2802BF71E70813A00879216 /* SliderTableViewCell.swift */; };
|
||||
|
|
@ -84,6 +85,7 @@
|
|||
/* Begin PBXFileReference section */
|
||||
274CCFCF32444A2FF46BE7F4 /* 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 = "<group>"; };
|
||||
A217ACE11E9AB17C00A1A6CF /* OTPScannerController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OTPScannerController.swift; sourceTree = "<group>"; };
|
||||
A217ACE31E9BBBBD00A1A6CF /* GitConfigSettingTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = GitConfigSettingTableViewController.swift; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.swift; };
|
||||
A262A58C1E68749C006B0890 /* Base32.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Base32.framework; path = Carthage/Build/iOS/Base32.framework; sourceTree = "<group>"; };
|
||||
A27424D81E7C35960093F436 /* NotificationNames.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationNames.swift; sourceTree = "<group>"; };
|
||||
A2802BF71E70813A00879216 /* SliderTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SliderTableViewCell.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -107,7 +109,7 @@
|
|||
DC13B14E1E8640810097803F /* passTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = passTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
DC13B1501E8640810097803F /* passTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = passTests.swift; sourceTree = "<group>"; };
|
||||
DC13B1521E8640810097803F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
DC193FF91E49B4430077E0A3 /* AdvancedSettingsTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AdvancedSettingsTableViewController.swift; sourceTree = "<group>"; };
|
||||
DC193FF91E49B4430077E0A3 /* AdvancedSettingsTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = AdvancedSettingsTableViewController.swift; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.swift; };
|
||||
DC193FFB1E49E0340077E0A3 /* PasscodeLock.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = PasscodeLock.framework; path = Carthage/Build/iOS/PasscodeLock.framework; sourceTree = "<group>"; };
|
||||
DC193FFD1E49E0760077E0A3 /* PasscodeLockRepository.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PasscodeLockRepository.swift; sourceTree = "<group>"; };
|
||||
DC193FFF1E49E1A60077E0A3 /* PasscodeLockConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PasscodeLockConfiguration.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -128,14 +130,14 @@
|
|||
DC917BE21E2E8231000FDF54 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
DC962CDE1E4B62C10033B5D8 /* AboutTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AboutTableViewController.swift; sourceTree = "<group>"; };
|
||||
DCA049951E3357E000522E8F /* SwiftyUserDefaults.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftyUserDefaults.framework; path = Carthage/Build/iOS/SwiftyUserDefaults.framework; sourceTree = "<group>"; };
|
||||
DCA049971E33586A00522E8F /* DefaultsKeys.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DefaultsKeys.swift; sourceTree = "<group>"; };
|
||||
DCA049971E33586A00522E8F /* DefaultsKeys.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = DefaultsKeys.swift; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.swift; };
|
||||
DCA049991E335CC800522E8F /* GitServerSettingTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GitServerSettingTableViewController.swift; sourceTree = "<group>"; };
|
||||
DCA0499B1E3362F400522E8F /* PGPKeySettingTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PGPKeySettingTableViewController.swift; sourceTree = "<group>"; };
|
||||
DCA0499D1E33BAC100522E8F /* Globals.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Globals.swift; sourceTree = "<group>"; };
|
||||
DCA671DE1E7A73B100D3ABE1 /* OneTimePassword.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OneTimePassword.framework; path = Carthage/Build/iOS/OneTimePassword.framework; sourceTree = "<group>"; };
|
||||
DCA742D91E599ED400D54E16 /* KeychainAccess.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = KeychainAccess.framework; path = Carthage/Build/iOS/KeychainAccess.framework; sourceTree = "<group>"; };
|
||||
DCAAF7441E2FA66800AB94BC /* SettingsTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsTableViewController.swift; sourceTree = "<group>"; };
|
||||
DCC408A31E2FCC9E00F29B0E /* PasswordStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PasswordStore.swift; sourceTree = "<group>"; };
|
||||
DCC408A31E2FCC9E00F29B0E /* PasswordStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = PasswordStore.swift; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.swift; };
|
||||
DCC408C61E307DBB00F29B0E /* SVProgressHUD.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SVProgressHUD.framework; path = Carthage/Build/iOS/SVProgressHUD.framework; sourceTree = "<group>"; };
|
||||
DCC408C91E30BA1300F29B0E /* pass.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = pass.xcdatamodel; sourceTree = "<group>"; };
|
||||
DCC441511E8F6C06008A90C4 /* RawPasswordViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RawPasswordViewController.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -223,6 +225,7 @@
|
|||
DCC441531E916382008A90C4 /* GitSSHKeyArmorSettingTableViewController.swift */,
|
||||
A2A7813E1E97DBD9001311F5 /* QRScannerController.swift */,
|
||||
A217ACE11E9AB17C00A1A6CF /* OTPScannerController.swift */,
|
||||
A217ACE31E9BBBBD00A1A6CF /* GitConfigSettingTableViewController.swift */,
|
||||
);
|
||||
path = Controllers;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -549,6 +552,7 @@
|
|||
DCFB779E1E4F40C7008DE471 /* FillPasswordTableViewCell.swift in Sources */,
|
||||
A2802BF91E70813A00879216 /* SliderTableViewCell.swift in Sources */,
|
||||
DC037CB21E4CAB1700609409 /* AboutRepositoryTableViewController.swift in Sources */,
|
||||
A217ACE41E9BBBBD00A1A6CF /* GitConfigSettingTableViewController.swift in Sources */,
|
||||
DC037CB01E4CA51F00609409 /* GeneralSettingsTableViewController.swift in Sources */,
|
||||
DC037CB81E4DD1A500609409 /* AddPasswordTableViewController.swift in Sources */,
|
||||
DCC441521E8F6C06008A90C4 /* RawPasswordViewController.swift in Sources */,
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12118" systemVersion="16E195" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="YoR-iB-XAd">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12120" systemVersion="16F60a" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="YoR-iB-XAd">
|
||||
<device id="retina5_5" orientation="portrait">
|
||||
<adaptation id="fullscreen"/>
|
||||
</device>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12086"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12088"/>
|
||||
<capability name="Constraints to layout margins" minToolsVersion="6.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
|
|
@ -89,11 +89,11 @@
|
|||
<rect key="frame" x="0.0" y="35" width="414" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="55g-T3-9ak" id="dKn-cO-EJa">
|
||||
<rect key="frame" x="0.0" y="0.0" width="381" height="43.666666666666664"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="376" height="43.666666666666664"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="General" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="dOt-Rj-vWD">
|
||||
<rect key="frame" x="15" y="0.0" width="364" height="43.666666666666664"/>
|
||||
<rect key="frame" x="20" y="0.0" width="356" height="43.666666666666664"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
|
|
@ -109,18 +109,18 @@
|
|||
<rect key="frame" x="0.0" y="79" width="414" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="2rc-ZW-XKd" id="CpT-zb-QEP">
|
||||
<rect key="frame" x="0.0" y="0.0" width="381" height="43.666666666666664"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="376" height="43.666666666666664"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Password Repository" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="gWn-ib-STb">
|
||||
<rect key="frame" x="15" y="11.999999999999998" width="160.33333333333334" height="20.333333333333332"/>
|
||||
<rect key="frame" x="20" y="11.999999999999998" width="160.33333333333334" height="20.333333333333332"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Not Set" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="Myq-fV-riz">
|
||||
<rect key="frame" x="321.33333333333331" y="11.999999999999998" width="57.666666666666664" height="20.333333333333332"/>
|
||||
<rect key="frame" x="318.33333333333331" y="11.999999999999998" width="57.666666666666664" height="20.333333333333332"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" red="0.5568627451" green="0.5568627451" blue="0.57647058819999997" alpha="1" colorSpace="calibratedRGB"/>
|
||||
|
|
@ -136,18 +136,18 @@
|
|||
<rect key="frame" x="0.0" y="123" width="414" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="1ze-MS-Xbj" id="W7U-oL-hOh">
|
||||
<rect key="frame" x="0.0" y="0.0" width="381" height="43.666666666666664"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="376" height="43.666666666666664"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="PGP Key" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="RR9-xr-9ko">
|
||||
<rect key="frame" x="15" y="11.999999999999998" width="66" height="20.333333333333332"/>
|
||||
<rect key="frame" x="20" y="11.999999999999998" width="66" height="20.333333333333332"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Not Set" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="7lc-Vh-G9W">
|
||||
<rect key="frame" x="321.33333333333331" y="11.999999999999998" width="57.666666666666664" height="20.333333333333332"/>
|
||||
<rect key="frame" x="318.33333333333331" y="11.999999999999998" width="57.666666666666664" height="20.333333333333332"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" red="0.55686274509803924" green="0.55686274509803924" blue="0.57647058823529407" alpha="1" colorSpace="calibratedRGB"/>
|
||||
|
|
@ -164,18 +164,18 @@
|
|||
<rect key="frame" x="0.0" y="223" width="414" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="6Y0-mj-qhA" id="qlv-tQ-Xmc">
|
||||
<rect key="frame" x="0.0" y="0.0" width="381" height="43.666666666666664"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="376" height="43.666666666666664"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Passcode Lock" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="RaZ-6t-0CU">
|
||||
<rect key="frame" x="15" y="11.999999999999998" width="115" height="20.333333333333332"/>
|
||||
<rect key="frame" x="20" y="11.999999999999998" width="115" height="20.333333333333332"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Off" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="HXb-ZX-HUv">
|
||||
<rect key="frame" x="354.66666666666669" y="11.999999999999998" width="24.333333333333332" height="20.333333333333332"/>
|
||||
<rect key="frame" x="351.66666666666669" y="11.999999999999998" width="24.333333333333332" height="20.333333333333332"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" red="0.5568627451" green="0.5568627451" blue="0.57647058819999997" alpha="1" colorSpace="calibratedRGB"/>
|
||||
|
|
@ -192,7 +192,7 @@
|
|||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Touch ID" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="H2E-hP-Gyf">
|
||||
<rect key="frame" x="15" y="0.0" width="384" height="43.666666666666664"/>
|
||||
<rect key="frame" x="20" y="0.0" width="379" height="43.666666666666664"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
|
|
@ -209,11 +209,11 @@
|
|||
<rect key="frame" x="0.0" y="347" 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="381" height="43.666666666666664"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="376" height="43.666666666666664"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Advanced" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="MKj-d0-8q3">
|
||||
<rect key="frame" x="15" y="0.0" width="364" height="43.666666666666664"/>
|
||||
<rect key="frame" x="20" y="0.0" width="356" height="43.666666666666664"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
|
||||
|
|
@ -233,11 +233,11 @@
|
|||
<rect key="frame" x="0.0" y="427" 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="381" height="43.666666666666664"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="376" height="43.666666666666664"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="About" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="oqz-Hr-RAl">
|
||||
<rect key="frame" x="15" y="0.0" width="364" height="43.666666666666664"/>
|
||||
<rect key="frame" x="20" y="0.0" width="356" height="43.666666666666664"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
|
||||
|
|
@ -288,22 +288,22 @@
|
|||
<rect key="frame" x="0.0" y="28" width="414" height="54"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" tableViewCell="OfB-1N-1Am" id="fh0-au-C6q">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="54"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="53.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" tag="101" contentMode="left" verticalHuggingPriority="251" preservesSuperviewLayoutMargins="YES" text="2017/04/04" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="GLC-qL-55P">
|
||||
<label opaque="NO" userInteractionEnabled="NO" tag="201" contentMode="left" verticalHuggingPriority="251" preservesSuperviewLayoutMargins="YES" text="2017/04/04" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="GLC-qL-55P">
|
||||
<rect key="frame" x="87.333333333333343" y="10.999999999999998" width="311.66666666666663" height="14.33333333333333"/>
|
||||
<fontDescription key="fontDescription" name=".AppleSystemUIFont" family=".AppleSystemUIFont" pointSize="12"/>
|
||||
<color key="textColor" white="0.33333333333333331" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" tag="102" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" preservesSuperviewLayoutMargins="YES" text="Edit password for baidu.com using Pass for iOS." textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="L1p-Dm-Mnh">
|
||||
<label opaque="NO" userInteractionEnabled="NO" tag="202" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" preservesSuperviewLayoutMargins="YES" text="Edit password for baidu.com using Pass for iOS." textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="L1p-Dm-Mnh">
|
||||
<rect key="frame" x="15" y="28.666666666666671" width="384" height="14.333333333333329"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="14"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" tag="100" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" preservesSuperviewLayoutMargins="YES" text="Bob Sun" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="8Dc-U9-AVf">
|
||||
<label opaque="NO" userInteractionEnabled="NO" tag="200" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" preservesSuperviewLayoutMargins="YES" text="Bob Sun" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="8Dc-U9-AVf">
|
||||
<rect key="frame" x="15" y="11.000000000000002" width="62.333333333333329" height="14.66666666666667"/>
|
||||
<fontDescription key="fontDescription" type="system" weight="medium" pointSize="16"/>
|
||||
<nil key="textColor"/>
|
||||
|
|
@ -348,10 +348,10 @@
|
|||
<tableViewSection headerTitle="Git Repository URL" id="pbe-W6-w4V">
|
||||
<cells>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" reuseIdentifier="gitRepositoryURLTabelViewCell" rowHeight="52" id="FRr-pf-aPO">
|
||||
<rect key="frame" x="0.0" y="55" width="414" height="52"/>
|
||||
<rect key="frame" x="0.0" y="55.5" width="414" height="52"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" tableViewCell="FRr-pf-aPO" id="60A-PS-qGe">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="51"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="52"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" contentHorizontalAlignment="left" contentVerticalAlignment="center" placeholder="Git Repository URL" textAlignment="natural" minimumFontSize="17" clearButtonMode="whileEditing" translatesAutoresizingMaskIntoConstraints="NO" id="EVT-VU-sCi">
|
||||
|
|
@ -378,10 +378,10 @@
|
|||
<tableViewSection headerTitle="Username" id="fRu-A2-SCk">
|
||||
<cells>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" reuseIdentifier="usernameTableVIewCell" rowHeight="52" id="tnj-5U-kMB">
|
||||
<rect key="frame" x="0.0" y="163" width="414" height="52"/>
|
||||
<rect key="frame" x="0.0" y="163.5" width="414" height="52"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" tableViewCell="tnj-5U-kMB" id="f0c-pI-MSJ">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="51"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="52"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" contentHorizontalAlignment="left" contentVerticalAlignment="center" placeholder="Username" textAlignment="natural" minimumFontSize="17" clearButtonMode="whileEditing" translatesAutoresizingMaskIntoConstraints="NO" id="TMg-Gk-7nG">
|
||||
|
|
@ -407,10 +407,10 @@
|
|||
<tableViewSection headerTitle="Authentication Method" id="h0N-tI-shZ">
|
||||
<cells>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="blue" hidesAccessoryWhenEditing="NO" indentationLevel="2" indentationWidth="0.0" shouldIndentWhileEditing="NO" id="KrP-nb-haa">
|
||||
<rect key="frame" x="0.0" y="271" width="414" height="44"/>
|
||||
<rect key="frame" x="0.0" y="271.5" width="414" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="KrP-nb-haa" id="1uB-oE-kfI">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="43"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Password" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="LfQ-Af-j2O">
|
||||
|
|
@ -438,10 +438,10 @@
|
|||
<inset key="separatorInset" minX="62" minY="0.0" maxX="0.0" maxY="0.0"/>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="blue" accessoryType="detailButton" hidesAccessoryWhenEditing="NO" indentationLevel="2" indentationWidth="0.0" shouldIndentWhileEditing="NO" id="Qmt-bo-CuJ">
|
||||
<rect key="frame" x="0.0" y="315" width="414" height="44"/>
|
||||
<rect key="frame" x="0.0" y="315.5" width="414" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="Qmt-bo-CuJ" id="p3u-8b-h3U">
|
||||
<rect key="frame" x="0.0" y="0.0" width="367" height="43"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="367" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="SSH Key" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Ezz-76-a53">
|
||||
|
|
@ -482,7 +482,7 @@
|
|||
<segue destination="7K9-cE-9qq" kind="unwind" unwindAction="cancelGitServerSettingWithSegue:" id="SGr-tc-vDL"/>
|
||||
</connections>
|
||||
</barButtonItem>
|
||||
<barButtonItem key="rightBarButtonItem" style="done" systemItem="save" id="sgQ-zB-rxv">
|
||||
<barButtonItem key="rightBarButtonItem" title="Clone" style="done" id="sgQ-zB-rxv">
|
||||
<connections>
|
||||
<action selector="save:" destination="ynQ-64-MfA" id="HNL-Da-fXT"/>
|
||||
</connections>
|
||||
|
|
@ -519,7 +519,7 @@
|
|||
<rect key="frame" x="0.0" y="35" width="414" height="52"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="BYZ-9g-xZy" id="Zfn-rK-sN1">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="52"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="51.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Public Key URL" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="dWi-eh-7Eq">
|
||||
|
|
@ -553,7 +553,7 @@
|
|||
<rect key="frame" x="0.0" y="87" width="414" height="52"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="vpk-J8-j7t" id="1td-qT-6ts">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="52"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="51.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Private Key URL" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Qht-RC-Yeg">
|
||||
|
|
@ -640,7 +640,7 @@
|
|||
</barButtonItem>
|
||||
</navigationItem>
|
||||
<connections>
|
||||
<segue destination="lY3-ru-aoM" kind="show" identifier="showQRScannerSegue" id="mPU-tp-J0X"/>
|
||||
<segue destination="A9p-Qb-WmU" kind="show" identifier="showQRScannerSegue" id="yyD-4H-pLE"/>
|
||||
</connections>
|
||||
</tableViewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="6R0-BP-wo8" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
|
|
@ -684,11 +684,6 @@
|
|||
<segue destination="0gD-ix-2NF" kind="unwind" unwindAction="cancelOTPScannerWithSegue:" id="nZe-B6-MNt"/>
|
||||
</connections>
|
||||
</barButtonItem>
|
||||
<barButtonItem key="rightBarButtonItem" systemItem="save" id="4kF-IA-Obv">
|
||||
<connections>
|
||||
<segue destination="0gD-ix-2NF" kind="unwind" unwindAction="saveScannedOTPWithSegue:" id="CWK-sE-Ic4"/>
|
||||
</connections>
|
||||
</barButtonItem>
|
||||
</navigationItem>
|
||||
<connections>
|
||||
<outlet property="scannerOutput" destination="lOI-p4-BGb" id="LZa-eC-1Lc"/>
|
||||
|
|
@ -697,7 +692,7 @@
|
|||
<placeholder placeholderIdentifier="IBFirstResponder" id="rqh-SR-bIq" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
<exit id="0gD-ix-2NF" userLabel="Exit" sceneMemberID="exit"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="7994.202898550725" y="-1008.4239130434784"/>
|
||||
<point key="canvasLocation" x="7059" y="-1012"/>
|
||||
</scene>
|
||||
<!--Password Detail Table View Controller-->
|
||||
<scene sceneID="9wY-d0-fB1">
|
||||
|
|
@ -807,7 +802,7 @@
|
|||
</navigationItem>
|
||||
<connections>
|
||||
<segue destination="HB6-Yu-Y3J" kind="unwind" identifier="deletePasswordSegue" unwindAction="deletePasswordWithSegue:" id="L1Z-64-EZh"/>
|
||||
<segue destination="lY3-ru-aoM" kind="show" identifier="showQRScannerSegue" id="Cpl-XA-cZ9"/>
|
||||
<segue destination="A9p-Qb-WmU" kind="show" identifier="showQRScannerSegue" id="UfP-k3-XeR"/>
|
||||
</connections>
|
||||
</tableViewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="HlX-6r-eOU" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
|
|
@ -921,7 +916,7 @@ Phone Support PIN #: 84719</string>
|
|||
<rect key="frame" x="0.0" y="35" width="414" height="52"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="Jy1-4S-Lvf" id="tJE-ww-okf">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="52"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="51.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Public Key URL" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Oys-xP-ZrB">
|
||||
|
|
@ -955,7 +950,7 @@ Phone Support PIN #: 84719</string>
|
|||
<rect key="frame" x="0.0" y="87" width="414" height="52"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="MA5-lE-8dT" id="pTv-Wj-psC">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="52"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="51.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Private Key URL" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="C2w-dd-roS">
|
||||
|
|
@ -1058,17 +1053,17 @@ Phone Support PIN #: 84719</string>
|
|||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" cocoaTouchSystemColor="groupTableViewBackgroundColor"/>
|
||||
<sections>
|
||||
<tableViewSection id="ugP-R2-9M7">
|
||||
<tableViewSection headerTitle="GPG Configuration" id="ugP-R2-9M7">
|
||||
<cells>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" textLabel="Jwg-mt-woS" style="IBUITableViewCellStyleDefault" id="tHt-Ro-0HF" userLabel="Discard Changes Table View Cell">
|
||||
<rect key="frame" x="0.0" y="35" width="414" height="44"/>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" textLabel="Jwg-mt-woS" style="IBUITableViewCellStyleDefault" id="tHt-Ro-0HF" userLabel="Encrypt in ASCII-Armored">
|
||||
<rect key="frame" x="0.0" y="55.5" width="414" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="tHt-Ro-0HF" id="Epj-ei-NtS">
|
||||
<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="Encrypt in ASCII-Armored" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="Jwg-mt-woS">
|
||||
<rect key="frame" x="15" y="0.0" width="384" height="43.666666666666664"/>
|
||||
<rect key="frame" x="20" y="0.0" width="379" height="43.666666666666664"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
|
|
@ -1079,17 +1074,48 @@ Phone Support PIN #: 84719</string>
|
|||
</tableViewCell>
|
||||
</cells>
|
||||
</tableViewSection>
|
||||
<tableViewSection id="aVR-FE-jMg">
|
||||
<tableViewSection headerTitle="Git Configuration" id="ihT-OG-HTv">
|
||||
<cells>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" textLabel="87a-xY-AbR" detailTextLabel="2qr-d7-0SK" style="IBUITableViewCellStyleValue1" id="SVj-jD-qPT" userLabel="Git Signature">
|
||||
<rect key="frame" x="0.0" y="155.5" width="414" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="SVj-jD-qPT" id="HaO-5w-qZt">
|
||||
<rect key="frame" x="0.0" y="0.0" width="376" height="43.666666666666664"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Git Signature" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="87a-xY-AbR">
|
||||
<rect key="frame" x="20.000000000000007" y="11.999999999999998" width="99.666666666666671" height="20.333333333333332"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Not Set" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="2qr-d7-0SK">
|
||||
<rect key="frame" x="318.33333333333331" y="11.999999999999998" width="57.666666666666664" height="20.333333333333332"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" red="0.5568627451" green="0.5568627451" blue="0.57647058819999997" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
</tableViewCellContentView>
|
||||
<connections>
|
||||
<segue destination="RHo-mb-ayc" kind="show" id="YZO-oX-Umt"/>
|
||||
</connections>
|
||||
</tableViewCell>
|
||||
</cells>
|
||||
</tableViewSection>
|
||||
<tableViewSection headerTitle="PasswordStore Data" id="aVR-FE-jMg">
|
||||
<cells>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" textLabel="zrl-v3-fxg" style="IBUITableViewCellStyleDefault" id="Jm8-B5-wKx" userLabel="Discard Changes Table View Cell">
|
||||
<rect key="frame" x="0.0" y="115" width="414" height="44"/>
|
||||
<rect key="frame" x="0.0" y="255.5" width="414" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="Jm8-B5-wKx" id="tjS-Q6-y2M">
|
||||
<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="Discard All Local Changes" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="zrl-v3-fxg">
|
||||
<rect key="frame" x="15" y="0.0" width="384" height="43.666666666666664"/>
|
||||
<rect key="frame" x="20" y="0.0" width="379" height="43.666666666666664"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" red="0.50196081400000003" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
|
||||
|
|
@ -1098,19 +1124,15 @@ Phone Support PIN #: 84719</string>
|
|||
</subviews>
|
||||
</tableViewCellContentView>
|
||||
</tableViewCell>
|
||||
</cells>
|
||||
</tableViewSection>
|
||||
<tableViewSection id="ujw-Wl-vs1">
|
||||
<cells>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" textLabel="K2K-Bx-g7Z" style="IBUITableViewCellStyleDefault" id="NI1-Kd-hyH">
|
||||
<rect key="frame" x="0.0" y="195" width="414" height="44"/>
|
||||
<rect key="frame" x="0.0" y="299.5" width="414" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="NI1-Kd-hyH" id="yLe-T2-TWF">
|
||||
<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="Erase All Password Store Data" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="K2K-Bx-g7Z">
|
||||
<rect key="frame" x="15" y="0.0" width="384" height="43.666666666666664"/>
|
||||
<rect key="frame" x="20" y="0.0" width="379" height="43.666666666666664"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" red="0.50196081400000003" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
|
||||
|
|
@ -1132,6 +1154,7 @@ Phone Support PIN #: 84719</string>
|
|||
<outlet property="discardChangesTableViewCell" destination="Jm8-B5-wKx" id="rfA-G3-2OE"/>
|
||||
<outlet property="encryptInASCIIArmoredTableViewCell" destination="tHt-Ro-0HF" id="tOi-Sj-mLJ"/>
|
||||
<outlet property="eraseDataTableViewCell" destination="NI1-Kd-hyH" id="NtJ-f4-oxb"/>
|
||||
<outlet property="gitSignatureTableViewCell" destination="SVj-jD-qPT" id="4PE-oy-KPR"/>
|
||||
</connections>
|
||||
</tableViewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="adh-5o-YYB" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
|
|
@ -1257,14 +1280,14 @@ Phone Support PIN #: 84719</string>
|
|||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="ASCII-Armor Keys" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="M32-yr-IfE">
|
||||
<rect key="frame" x="15.000000000000007" y="17" width="121.66666666666667" height="17"/>
|
||||
<rect key="frame" x="20.000000000000007" y="17" width="121.66666666666667" height="17"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" name="HelveticaNeue-Bold" family="Helvetica Neue" pointSize="14"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" usesAttributedText="YES" lineBreakMode="wordWrap" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="sMx-qd-MTJ">
|
||||
<rect key="frame" x="15" y="34" width="384" height="119"/>
|
||||
<rect key="frame" x="20" y="34" width="363.66666666666669" height="119"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<attributedString key="attributedText">
|
||||
<fragment>
|
||||
|
|
@ -1317,7 +1340,7 @@ Cgo
|
|||
<rect key="frame" x="0.0" y="244" width="414" height="160"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="Lom-iT-l16" id="eya-Tv-r0q">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="160"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="159.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="oyB-oI-1fS">
|
||||
|
|
@ -1347,7 +1370,7 @@ Cgo
|
|||
<rect key="frame" x="0.0" y="443" width="414" height="160"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="J8U-ev-FRQ" id="eb0-vb-Fcc">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="160"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="159.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="lrQ-Ln-ZOv">
|
||||
|
|
@ -1455,14 +1478,14 @@ Cgo
|
|||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="ASCII-Armor Keys" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="i78-t7-fP9">
|
||||
<rect key="frame" x="15.000000000000007" y="37" width="121.66666666666667" height="17"/>
|
||||
<rect key="frame" x="20.000000000000007" y="37" width="121.66666666666667" height="17"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" name="HelveticaNeue-Bold" family="Helvetica Neue" pointSize="14"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" usesAttributedText="YES" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="Oit-sd-wa5">
|
||||
<rect key="frame" x="15" y="53.999999999999993" width="344" height="79.333333333333329"/>
|
||||
<rect key="frame" x="20" y="53.999999999999993" width="344" height="79.333333333333329"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<attributedString key="attributedText">
|
||||
<fragment>
|
||||
|
|
@ -1498,7 +1521,7 @@ Cgo
|
|||
<rect key="frame" x="0.0" y="244" width="414" height="160"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="3po-uS-Nch" id="pet-if-EHU">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="160"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="159.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Y9t-DD-6uH">
|
||||
|
|
@ -1525,7 +1548,7 @@ Cgo
|
|||
<rect key="frame" x="0.0" y="443" width="414" height="160"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="nmc-vy-Ab5" id="TQD-GC-YOY">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="160"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="159.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="23o-MP-wQY">
|
||||
|
|
@ -1568,23 +1591,130 @@ Cgo
|
|||
</objects>
|
||||
<point key="canvasLocation" x="6083" y="2895"/>
|
||||
</scene>
|
||||
<!--Navigation Controller-->
|
||||
<scene sceneID="E7j-qe-O6d">
|
||||
<!--Git Signature-->
|
||||
<scene sceneID="eTh-B3-0rv">
|
||||
<objects>
|
||||
<navigationController automaticallyAdjustsScrollViewInsets="NO" id="lY3-ru-aoM" sceneMemberID="viewController">
|
||||
<tableViewController id="zDo-XK-ymp" customClass="GitConfigSettingTableViewController" customModule="pass" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="static" style="grouped" separatorStyle="default" allowsSelection="NO" rowHeight="44" sectionHeaderHeight="18" sectionFooterHeight="18" id="9r8-pk-GBf">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="736"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" cocoaTouchSystemColor="groupTableViewBackgroundColor"/>
|
||||
<sections>
|
||||
<tableViewSection id="TCt-zE-c6t">
|
||||
<cells>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="none" indentationWidth="10" reuseIdentifier="pgpKeyURLTableViewCell" rowHeight="52" id="4Bh-1D-w5T">
|
||||
<rect key="frame" x="0.0" y="35" width="414" height="52"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="4Bh-1D-w5T" id="mIL-ig-tbp">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="51.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Name" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Hlb-Zh-ega">
|
||||
<rect key="frame" x="15" y="8" width="391" height="15"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleCaption1"/>
|
||||
<color key="textColor" white="0.33333333333333331" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" placeholder="name" minimumFontSize="17" clearButtonMode="whileEditing" translatesAutoresizingMaskIntoConstraints="NO" id="fa8-Vc-w8F">
|
||||
<rect key="frame" x="15" y="23" width="391" height="21"/>
|
||||
<nil key="textColor"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<textInputTraits key="textInputTraits" autocorrectionType="no" spellCheckingType="no" returnKeyType="next"/>
|
||||
<connections>
|
||||
<outlet property="delegate" destination="zDo-XK-ymp" id="KRG-7d-AfM"/>
|
||||
</connections>
|
||||
</textField>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="fa8-Vc-w8F" firstAttribute="top" secondItem="Hlb-Zh-ega" secondAttribute="bottom" id="7dX-q0-fio"/>
|
||||
<constraint firstItem="fa8-Vc-w8F" firstAttribute="leading" secondItem="Hlb-Zh-ega" secondAttribute="leading" id="N0c-Yi-SOw"/>
|
||||
<constraint firstAttribute="trailingMargin" secondItem="Hlb-Zh-ega" secondAttribute="trailing" id="QuW-Id-nJ3"/>
|
||||
<constraint firstAttribute="topMargin" secondItem="Hlb-Zh-ega" secondAttribute="top" id="Wk8-N9-29h"/>
|
||||
<constraint firstAttribute="trailingMargin" secondItem="fa8-Vc-w8F" secondAttribute="trailing" id="dWA-Zk-8Xs"/>
|
||||
<constraint firstItem="Hlb-Zh-ega" firstAttribute="leading" secondItem="mIL-ig-tbp" secondAttribute="leadingMargin" constant="7" id="rEw-IA-Jqp"/>
|
||||
</constraints>
|
||||
</tableViewCellContentView>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="none" indentationWidth="10" reuseIdentifier="pgpKeyURLTableViewCell" rowHeight="52" id="JRY-pY-XtS">
|
||||
<rect key="frame" x="0.0" y="87" width="414" height="52"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="JRY-pY-XtS" id="KrQ-Ih-dk8">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="51.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Email" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="m25-Qy-XwU">
|
||||
<rect key="frame" x="15" y="8" width="391" height="15"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleCaption1"/>
|
||||
<color key="textColor" white="0.33333333333333331" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" placeholder="email" minimumFontSize="17" clearButtonMode="whileEditing" translatesAutoresizingMaskIntoConstraints="NO" id="YY9-za-MNV">
|
||||
<rect key="frame" x="15" y="23" width="391" height="21"/>
|
||||
<nil key="textColor"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<textInputTraits key="textInputTraits" autocorrectionType="no" spellCheckingType="no" keyboardType="emailAddress" returnKeyType="done"/>
|
||||
<connections>
|
||||
<outlet property="delegate" destination="zDo-XK-ymp" id="HX8-SE-uSw"/>
|
||||
</connections>
|
||||
</textField>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstAttribute="topMargin" secondItem="m25-Qy-XwU" secondAttribute="top" id="2Ht-xl-rR3"/>
|
||||
<constraint firstItem="YY9-za-MNV" firstAttribute="top" secondItem="m25-Qy-XwU" secondAttribute="bottom" id="EL6-fb-eg3"/>
|
||||
<constraint firstAttribute="trailingMargin" secondItem="YY9-za-MNV" secondAttribute="trailing" id="KpQ-1j-faY"/>
|
||||
<constraint firstItem="YY9-za-MNV" firstAttribute="leading" secondItem="m25-Qy-XwU" secondAttribute="leading" id="MJG-cs-oIQ"/>
|
||||
<constraint firstAttribute="trailingMargin" secondItem="m25-Qy-XwU" secondAttribute="trailing" id="brX-cL-blr"/>
|
||||
<constraint firstItem="m25-Qy-XwU" firstAttribute="leading" secondItem="KrQ-Ih-dk8" secondAttribute="leadingMargin" constant="7" id="dNf-iF-vSC"/>
|
||||
</constraints>
|
||||
</tableViewCellContentView>
|
||||
</tableViewCell>
|
||||
</cells>
|
||||
</tableViewSection>
|
||||
</sections>
|
||||
<connections>
|
||||
<outlet property="dataSource" destination="zDo-XK-ymp" id="Q18-Fj-bhC"/>
|
||||
<outlet property="delegate" destination="zDo-XK-ymp" id="uMh-l3-IeX"/>
|
||||
</connections>
|
||||
</tableView>
|
||||
<navigationItem key="navigationItem" title="Git Signature" id="pPi-jd-x5U">
|
||||
<barButtonItem key="leftBarButtonItem" systemItem="cancel" id="fQD-Bc-Mcd">
|
||||
<connections>
|
||||
<segue destination="M5P-Ab-cIc" kind="unwind" identifier="cancelGitConfigSettingSegue" unwindAction="cancelGitConfigSettingWithSegue:" id="Z2N-nJ-04S"/>
|
||||
</connections>
|
||||
</barButtonItem>
|
||||
<barButtonItem key="rightBarButtonItem" systemItem="save" id="kMD-4J-wBb">
|
||||
<connections>
|
||||
<segue destination="M5P-Ab-cIc" kind="unwind" identifier="saveGitConfigSettingSegue" unwindAction="saveGitConfigSettingWithSegue:" id="RGN-ff-tuP"/>
|
||||
</connections>
|
||||
</barButtonItem>
|
||||
</navigationItem>
|
||||
<connections>
|
||||
<outlet property="emailTextField" destination="YY9-za-MNV" id="bpt-uh-1iB"/>
|
||||
<outlet property="nameTextField" destination="fa8-Vc-w8F" id="UQ7-gG-Qe4"/>
|
||||
</connections>
|
||||
</tableViewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="FmV-2I-aov" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
<exit id="M5P-Ab-cIc" userLabel="Exit" sceneMemberID="exit"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="6109" y="5333"/>
|
||||
</scene>
|
||||
<!--Navigation Controller-->
|
||||
<scene sceneID="9zM-kx-OZU">
|
||||
<objects>
|
||||
<navigationController automaticallyAdjustsScrollViewInsets="NO" id="RHo-mb-ayc" sceneMemberID="viewController">
|
||||
<toolbarItems/>
|
||||
<navigationBar key="navigationBar" contentMode="scaleToFill" id="6YB-px-rnU">
|
||||
<navigationBar key="navigationBar" contentMode="scaleToFill" id="ch6-Ko-kuU">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</navigationBar>
|
||||
<nil name="viewControllers"/>
|
||||
<connections>
|
||||
<segue destination="A9p-Qb-WmU" kind="relationship" relationship="rootViewController" id="und-S5-p1v"/>
|
||||
<segue destination="zDo-XK-ymp" kind="relationship" relationship="rootViewController" id="vT1-I7-q3N"/>
|
||||
</connections>
|
||||
</navigationController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="D45-tZ-3fu" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="6nC-pe-hyF" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="7086.9565217391309" y="-1008.4239130434784"/>
|
||||
<point key="canvasLocation" x="4967" y="5333"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
<resources>
|
||||
|
|
@ -1592,6 +1722,6 @@ Cgo
|
|||
<image name="Settings" width="25" height="25"/>
|
||||
</resources>
|
||||
<inferredMetricsTieBreakers>
|
||||
<segue reference="Cpl-XA-cZ9"/>
|
||||
<segue reference="UfP-k3-XeR"/>
|
||||
</inferredMetricsTieBreakers>
|
||||
</document>
|
||||
|
|
|
|||
|
|
@ -15,10 +15,10 @@ class AddPasswordTableViewController: PasswordEditorTableViewController {
|
|||
|
||||
override func viewDidLoad() {
|
||||
tableData = [
|
||||
[[.type: PasswordEditorCellType.textFieldCell, .title: "name"]],
|
||||
[[.type: PasswordEditorCellType.nameCell, .title: "name"]],
|
||||
[[.type: PasswordEditorCellType.fillPasswordCell, .title: "password"],
|
||||
[.type: PasswordEditorCellType.passwordLengthCell, .title: "passwordlength"]],
|
||||
[[.type: PasswordEditorCellType.textViewCell, .title: "additions"]],
|
||||
[[.type: PasswordEditorCellType.additionsCell, .title: "additions"]],
|
||||
[[.type: PasswordEditorCellType.scanQRCodeCell]]
|
||||
]
|
||||
super.viewDidLoad()
|
||||
|
|
@ -35,21 +35,12 @@ class AddPasswordTableViewController: PasswordEditorTableViewController {
|
|||
}
|
||||
|
||||
// check name
|
||||
let nameCell = tableView.cellForRow(at: IndexPath(row: 0, section: 0)) as! TextFieldTableViewCell
|
||||
guard nameCell.getContent()!.isEmpty == false else {
|
||||
guard nameCell?.getContent()?.isEmpty == false else {
|
||||
let alertTitle = "Cannot Add Password"
|
||||
let alertMessage = "Please fill in the name."
|
||||
Utils.alert(title: alertTitle, message: alertMessage, controller: self, completion: nil)
|
||||
return false
|
||||
}
|
||||
|
||||
// check "/"
|
||||
guard nameCell.getContent()!.contains("/") == false else {
|
||||
let alertTitle = "Cannot Add Password"
|
||||
let alertMessage = "Illegal character."
|
||||
Utils.alert(title: alertTitle, message: alertMessage, controller: self, completion: nil)
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
|
@ -57,23 +48,15 @@ class AddPasswordTableViewController: PasswordEditorTableViewController {
|
|||
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
|
||||
super.prepare(for: segue, sender: sender)
|
||||
if segue.identifier == "saveAddPasswordSegue" {
|
||||
let cells = tableView.visibleCells
|
||||
var cellContents = [String: String]()
|
||||
for cell in cells {
|
||||
if let indexPath = tableView.indexPath(for: cell),
|
||||
let contentCell = cell as? ContentTableViewCell,
|
||||
let cellTitle = tableData[indexPath.section][indexPath.row][.title] as? String,
|
||||
let cellContent = contentCell.getContent() {
|
||||
cellContents[cellTitle] = cellContent
|
||||
}
|
||||
var plainText = (fillPasswordCell?.getContent())!
|
||||
if let additionsString = additionsCell?.getContent(), additionsString.isEmpty == false {
|
||||
plainText.append("\n")
|
||||
plainText.append(additionsString)
|
||||
}
|
||||
var plainText = ""
|
||||
if cellContents["additions"]! != "" {
|
||||
plainText = "\(cellContents["password"]!)\n\(cellContents["additions"]!)"
|
||||
} else {
|
||||
plainText = "\(cellContents["password"]!)"
|
||||
}
|
||||
password = Password(name: cellContents["name"]!, plainText: plainText)
|
||||
let encodedName = (nameCell?.getContent()?.stringByAddingPercentEncodingForRFC3986())!
|
||||
let name = URL(string: encodedName)!.lastPathComponent
|
||||
let url = URL(string: encodedName)!.appendingPathExtension("gpg")
|
||||
password = Password(name: name, url: url, plainText: plainText)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import SwiftyUserDefaults
|
|||
class AdvancedSettingsTableViewController: UITableViewController {
|
||||
|
||||
@IBOutlet weak var encryptInASCIIArmoredTableViewCell: UITableViewCell!
|
||||
@IBOutlet weak var gitSignatureTableViewCell: UITableViewCell!
|
||||
@IBOutlet weak var eraseDataTableViewCell: UITableViewCell!
|
||||
@IBOutlet weak var discardChangesTableViewCell: UITableViewCell!
|
||||
let passwordStore = PasswordStore.shared
|
||||
|
|
@ -30,6 +31,18 @@ class AdvancedSettingsTableViewController: UITableViewController {
|
|||
encryptInASCIIArmoredSwitch.isOn = Defaults[.encryptInArmored]
|
||||
encryptInASCIIArmoredTableViewCell.accessoryView = encryptInASCIIArmoredSwitch
|
||||
encryptInASCIIArmoredTableViewCell.selectionStyle = .none
|
||||
setGitSignatureText()
|
||||
}
|
||||
|
||||
private func setGitSignatureText() {
|
||||
let gitSignatureName = passwordStore.gitSignatureForNow.name!
|
||||
let gitSignatureEmail = passwordStore.gitSignatureForNow.email!
|
||||
self.gitSignatureTableViewCell.detailTextLabel?.font = UIFont.systemFont(ofSize: 14)
|
||||
self.gitSignatureTableViewCell.detailTextLabel?.text = "\(gitSignatureName) <\(gitSignatureEmail)>"
|
||||
if Defaults[.gitSignatureName] == nil && Defaults[.gitSignatureEmail] == nil {
|
||||
self.gitSignatureTableViewCell.detailTextLabel?.font = UIFont.systemFont(ofSize: 17)
|
||||
gitSignatureTableViewCell.detailTextLabel?.text = "Not Set"
|
||||
}
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
|
|
@ -48,27 +61,23 @@ class AdvancedSettingsTableViewController: UITableViewController {
|
|||
} else if tableView.cellForRow(at: indexPath) == discardChangesTableViewCell {
|
||||
let alert = UIAlertController(title: "Discard All Changes?", message: "Do you want to permanently discard all changes to the local copy of your password data? You cannot undo this action.", preferredStyle: UIAlertControllerStyle.alert)
|
||||
alert.addAction(UIAlertAction(title: "Discard All Changes", style: UIAlertActionStyle.destructive, handler: {[unowned self] (action) -> Void in
|
||||
DispatchQueue.global(qos: .userInitiated).async {
|
||||
SVProgressHUD.show(withStatus: "Resetting ...")
|
||||
DispatchQueue.main.async {
|
||||
do {
|
||||
let numberDiscarded = try self.passwordStore.reset()
|
||||
self.navigationController!.popViewController(animated: true)
|
||||
switch numberDiscarded {
|
||||
case 0:
|
||||
SVProgressHUD.showSuccess(withStatus: "No local commits")
|
||||
case 1:
|
||||
SVProgressHUD.showSuccess(withStatus: "Discarded 1 commit")
|
||||
default:
|
||||
SVProgressHUD.showSuccess(withStatus: "Discarded \(numberDiscarded) commits")
|
||||
}
|
||||
SVProgressHUD.dismiss(withDelay: 1)
|
||||
} catch {
|
||||
Utils.alert(title: "Error", message: error.localizedDescription, controller: self, completion: nil)
|
||||
}
|
||||
SVProgressHUD.show(withStatus: "Resetting ...")
|
||||
do {
|
||||
let numberDiscarded = try self.passwordStore.reset()
|
||||
self.navigationController!.popViewController(animated: true)
|
||||
switch numberDiscarded {
|
||||
case 0:
|
||||
SVProgressHUD.showSuccess(withStatus: "No local commits")
|
||||
case 1:
|
||||
SVProgressHUD.showSuccess(withStatus: "Discarded 1 commit")
|
||||
default:
|
||||
SVProgressHUD.showSuccess(withStatus: "Discarded \(numberDiscarded) commits")
|
||||
}
|
||||
SVProgressHUD.dismiss(withDelay: 1)
|
||||
} catch {
|
||||
Utils.alert(title: "Error", message: error.localizedDescription, controller: self, completion: nil)
|
||||
}
|
||||
|
||||
|
||||
}))
|
||||
alert.addAction(UIAlertAction(title: "Dismiss", style: UIAlertActionStyle.cancel, handler:nil))
|
||||
self.present(alert, animated: true, completion: nil)
|
||||
|
|
@ -78,5 +87,19 @@ class AdvancedSettingsTableViewController: UITableViewController {
|
|||
func encryptInASCIIArmoredAction(_ sender: Any?) {
|
||||
Defaults[.encryptInArmored] = encryptInASCIIArmoredSwitch.isOn
|
||||
}
|
||||
|
||||
@IBAction func cancelGitConfigSetting(segue: UIStoryboardSegue) {
|
||||
}
|
||||
|
||||
@IBAction func saveGitConfigSetting(segue: UIStoryboardSegue) {
|
||||
if let controller = segue.source as? GitConfigSettingTableViewController {
|
||||
if let gitSignatureName = controller.nameTextField.text,
|
||||
let gitSignatureEmail = controller.emailTextField.text {
|
||||
Defaults[.gitSignatureName] = gitSignatureName.isEmpty ? nil : gitSignatureName
|
||||
Defaults[.gitSignatureEmail] = gitSignatureEmail.isEmpty ? nil : gitSignatureEmail
|
||||
}
|
||||
setGitSignatureText()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ class CommitLogsTableViewController: UITableViewController {
|
|||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(updateCommitLogs), name: .passwordStoreUpdated, object: nil)
|
||||
commits = passwordStore.getRecentCommits(count: 20)
|
||||
self.tableView.estimatedRowHeight = 50
|
||||
self.tableView.rowHeight = UITableViewAutomaticDimension
|
||||
|
|
@ -31,12 +32,17 @@ class CommitLogsTableViewController: UITableViewController {
|
|||
formatter.timeStyle = .medium
|
||||
let dateString = formatter.string(from: commits[indexPath.row].commitDate)
|
||||
|
||||
let author = cell.contentView.viewWithTag(100) as? UILabel
|
||||
let dateLabel = cell.contentView.viewWithTag(101) as? UILabel
|
||||
let messageLabel = cell.contentView.viewWithTag(102) as? UILabel
|
||||
let author = cell.contentView.viewWithTag(200) as? UILabel
|
||||
let dateLabel = cell.contentView.viewWithTag(201) as? UILabel
|
||||
let messageLabel = cell.contentView.viewWithTag(202) as? UILabel
|
||||
author?.text = commits[indexPath.row].author?.name
|
||||
dateLabel?.text = dateString
|
||||
messageLabel?.text = commits[indexPath.row].message?.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
return cell
|
||||
}
|
||||
|
||||
func updateCommitLogs () {
|
||||
commits = passwordStore.getRecentCommits(count: 20)
|
||||
tableView.reloadData()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,10 +11,10 @@ import UIKit
|
|||
class EditPasswordTableViewController: PasswordEditorTableViewController {
|
||||
override func viewDidLoad() {
|
||||
tableData = [
|
||||
[[.type: PasswordEditorCellType.textFieldCell, .title: "name", .content: password!.name]],
|
||||
[[.type: PasswordEditorCellType.nameCell, .title: "name", .content: password!.namePath]],
|
||||
[[.type: PasswordEditorCellType.fillPasswordCell, .title: "password", .content: password!.password],
|
||||
[.type: PasswordEditorCellType.passwordLengthCell, .title: "passwordlength"]],
|
||||
[[.type: PasswordEditorCellType.textViewCell, .title: "additions", .content: password!.getAdditionsPlainText()]],
|
||||
[[.type: PasswordEditorCellType.additionsCell, .title: "additions", .content: password!.getAdditionsPlainText()]],
|
||||
[[.type: PasswordEditorCellType.scanQRCodeCell],
|
||||
[.type: PasswordEditorCellType.deletePasswordCell]]
|
||||
]
|
||||
|
|
@ -23,15 +23,13 @@ class EditPasswordTableViewController: PasswordEditorTableViewController {
|
|||
|
||||
override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool {
|
||||
if identifier == "saveEditPasswordSegue" {
|
||||
if let nameCell = tableView.cellForRow(at: IndexPath(row: 0, section: 0)) as? ContentTableViewCell {
|
||||
if nameCell.getContent() != password?.name {
|
||||
let alertTitle = "Cannot Save Edit"
|
||||
let alertMessage = "Editing name is not supported."
|
||||
Utils.alert(title: alertTitle, message: alertMessage, controller: self) {
|
||||
nameCell.setContent(content: self.password!.name)
|
||||
}
|
||||
return false
|
||||
}
|
||||
if let name = nameCell?.getContent(),
|
||||
let path = name.stringByAddingPercentEncodingForRFC3986(),
|
||||
let _ = URL(string: path) {
|
||||
return true
|
||||
} else {
|
||||
Utils.alert(title: "Cannot Save", message: "Password name is invalid.", controller: self, completion: nil)
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
|
|
@ -40,23 +38,17 @@ class EditPasswordTableViewController: PasswordEditorTableViewController {
|
|||
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
|
||||
super.prepare(for: segue, sender: sender)
|
||||
if segue.identifier == "saveEditPasswordSegue" {
|
||||
let cells = tableView.visibleCells
|
||||
var cellContents = [String: String]()
|
||||
for cell in cells {
|
||||
if let indexPath = tableView.indexPath(for: cell),
|
||||
let contentCell = cell as? ContentTableViewCell,
|
||||
let cellTitle = tableData[indexPath.section][indexPath.row][.title] as? String,
|
||||
let cellContent = contentCell.getContent() {
|
||||
cellContents[cellTitle] = cellContent
|
||||
}
|
||||
var plainText = (fillPasswordCell?.getContent())!
|
||||
if let additionsString = additionsCell?.getContent(), additionsString.isEmpty == false {
|
||||
plainText.append("\n")
|
||||
plainText.append(additionsString)
|
||||
}
|
||||
var plainText = ""
|
||||
if cellContents["additions"]! != "" {
|
||||
plainText = "\(cellContents["password"]!)\n\(cellContents["additions"]!)"
|
||||
} else {
|
||||
plainText = "\(cellContents["password"]!)"
|
||||
let encodedName = (nameCell?.getContent()?.stringByAddingPercentEncodingForRFC3986())!
|
||||
let name = URL(string: encodedName)!.lastPathComponent
|
||||
let url = URL(string: encodedName)!.appendingPathExtension("gpg")
|
||||
if password!.plainText != plainText || password!.url!.path != url.path {
|
||||
password!.updatePassword(name: name, url: url, plainText: plainText)
|
||||
}
|
||||
password!.updatePassword(name: cellContents["name"]!, plainText: plainText)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
41
pass/Controllers/GitConfigSettingTableViewController.swift
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
//
|
||||
// GitConfigSettingTableViewController.swift
|
||||
// pass
|
||||
//
|
||||
// Created by Yishi Lin on 10/4/17.
|
||||
// Copyright © 2017 Yishi Lin. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import SwiftyUserDefaults
|
||||
|
||||
class GitConfigSettingTableViewController: UITableViewController {
|
||||
let passwordStore = PasswordStore.shared
|
||||
|
||||
@IBOutlet weak var nameTextField: UITextField!
|
||||
@IBOutlet weak var emailTextField: UITextField!
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
tableView.rowHeight = UITableViewAutomaticDimension
|
||||
|
||||
let signature = passwordStore.gitSignatureForNow
|
||||
nameTextField.placeholder = signature.name
|
||||
emailTextField.placeholder = signature.email
|
||||
nameTextField.text = Defaults[.gitSignatureName]
|
||||
emailTextField.text = Defaults[.gitSignatureEmail]
|
||||
}
|
||||
|
||||
override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool {
|
||||
if identifier == "saveGitConfigSettingSegue" {
|
||||
let name = nameTextField.text!.isEmpty ? Globals.gitSignatureDefaultName : nameTextField.text!
|
||||
let email = emailTextField.text!.isEmpty ? Globals.gitSignatureDefaultEmail : nameTextField.text!
|
||||
guard GTSignature(name: name, email: email, time: nil) != nil else {
|
||||
Utils.alert(title: "Error", message: "Invalid name or email.", controller: self, completion: nil)
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -21,28 +21,10 @@ class GitSSHKeyArmorSettingTableViewController: UITableViewController, UITextVie
|
|||
super.viewDidLoad()
|
||||
armorPublicKeyTextView.text = Defaults[.gitSSHPublicKeyArmor]
|
||||
armorPrivateKeyTextView.text = Defaults[.gitSSHPrivateKeyArmor]
|
||||
gitSSHPrivateKeyPassphrase = passwordStore.gitSSHPrivateKeyPassphrase
|
||||
|
||||
armorPublicKeyTextView.delegate = self
|
||||
armorPrivateKeyTextView.delegate = self
|
||||
}
|
||||
|
||||
private func createSavePassphraseAlert() -> UIAlertController {
|
||||
let savePassphraseAlert = UIAlertController(title: "Passphrase", message: "Do you want to save the passphrase for later sync?", preferredStyle: UIAlertControllerStyle.alert)
|
||||
savePassphraseAlert.addAction(UIAlertAction(title: "No", style: UIAlertActionStyle.default) { _ in
|
||||
Defaults[.isRememberPassphraseOn] = false
|
||||
Defaults[.gitSSHKeySource] = "armor"
|
||||
self.navigationController!.popViewController(animated: true)
|
||||
})
|
||||
savePassphraseAlert.addAction(UIAlertAction(title: "Save", style: UIAlertActionStyle.destructive) {_ in
|
||||
Defaults[.isRememberPassphraseOn] = true
|
||||
self.passwordStore.gitSSHPrivateKeyPassphrase = self.gitSSHPrivateKeyPassphrase
|
||||
Defaults[.gitSSHKeySource] = "armor"
|
||||
self.navigationController!.popViewController(animated: true)
|
||||
})
|
||||
return savePassphraseAlert
|
||||
}
|
||||
|
||||
@IBAction func doneButtonTapped(_ sender: Any) {
|
||||
Defaults[.gitSSHPublicKeyArmor] = armorPublicKeyTextView.text
|
||||
Defaults[.gitSSHPrivateKeyArmor] = armorPrivateKeyTextView.text
|
||||
|
|
@ -52,22 +34,13 @@ class GitSSHKeyArmorSettingTableViewController: UITableViewController, UITextVie
|
|||
} catch {
|
||||
Utils.alert(title: "Cannot Save", message: "Cannot Save SSH Key", controller: self, completion: nil)
|
||||
}
|
||||
let alert = UIAlertController(title: "Passphrase", message: "Please fill in the passphrase of your SSH secret key.", preferredStyle: UIAlertControllerStyle.alert)
|
||||
alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: {_ in
|
||||
self.gitSSHPrivateKeyPassphrase = alert.textFields?.first?.text
|
||||
let savePassphraseAlert = self.createSavePassphraseAlert()
|
||||
self.present(savePassphraseAlert, animated: true, completion: nil)
|
||||
}))
|
||||
alert.addTextField(configurationHandler: {(textField: UITextField!) in
|
||||
textField.text = self.gitSSHPrivateKeyPassphrase
|
||||
textField.isSecureTextEntry = true
|
||||
})
|
||||
self.present(alert, animated: true, completion: nil)
|
||||
Defaults[.gitSSHKeySource] = "armor"
|
||||
self.navigationController!.popViewController(animated: true)
|
||||
}
|
||||
|
||||
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
|
||||
if text == UIPasteboard.general.string {
|
||||
// user pastes somethint, get ready to clear in 10s
|
||||
// user pastes something, get ready to clear in 10s
|
||||
recentPastedText = text
|
||||
DispatchQueue.global(qos: .background).asyncAfter(deadline: DispatchTime.now() + 10) { [weak weakSelf = self] in
|
||||
if let pasteboardString = UIPasteboard.general.string,
|
||||
|
|
|
|||
|
|
@ -17,8 +17,8 @@ class GitServerSettingTableViewController: UITableViewController {
|
|||
@IBOutlet weak var authSSHKeyCell: UITableViewCell!
|
||||
@IBOutlet weak var authPasswordCell: UITableViewCell!
|
||||
let passwordStore = PasswordStore.shared
|
||||
var password: String?
|
||||
|
||||
var sshLabel: UILabel? = nil
|
||||
|
||||
var authenticationMethod = Defaults[.gitAuthenticationMethod]
|
||||
|
||||
private func checkAuthenticationMethod(method: String) {
|
||||
|
|
@ -37,24 +37,22 @@ class GitServerSettingTableViewController: UITableViewController {
|
|||
sshKeyCheckView.isHidden = true
|
||||
}
|
||||
}
|
||||
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
super.viewWillAppear(animated)
|
||||
// Grey out ssh option if ssh_key and ssh_key.pub are not present
|
||||
sshLabel = authSSHKeyCell.subviews[0].subviews[0] as? UILabel
|
||||
sshLabel!.isEnabled = gitSSHKeyExists()
|
||||
|
||||
}
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
if let url = Defaults[.gitURL] {
|
||||
gitURLTextField.text = url.absoluteString
|
||||
}
|
||||
usernameTextField.text = Defaults[.gitUsername]
|
||||
password = passwordStore.gitPassword
|
||||
authenticationMethod = Defaults[.gitAuthenticationMethod]
|
||||
|
||||
// Grey out ssh option if ssh_key and ssh_key.pub are not present
|
||||
let sshLabel = authSSHKeyCell.subviews[0].subviews[0] as! UILabel
|
||||
|
||||
sshLabel.isEnabled = gitSSHKeyExists()
|
||||
|
||||
if authenticationMethod == nil || !sshLabel.isEnabled {
|
||||
authenticationMethod = "Password"
|
||||
}
|
||||
|
||||
checkAuthenticationMethod(method: authenticationMethod!)
|
||||
authSSHKeyCell.accessoryType = .detailButton
|
||||
}
|
||||
|
|
@ -107,24 +105,22 @@ class GitServerSettingTableViewController: UITableViewController {
|
|||
tableView.deselectRow(at: indexPath, animated: true)
|
||||
}
|
||||
|
||||
private func doClone() {
|
||||
if self.shouldPerformSegue(withIdentifier: "saveGitServerSettingSegue", sender: self) {
|
||||
self.performSegue(withIdentifier: "saveGitServerSettingSegue", sender: self)
|
||||
}
|
||||
}
|
||||
|
||||
@IBAction func save(_ sender: Any) {
|
||||
if authenticationMethod == "Password" {
|
||||
let alert = UIAlertController(title: "Password", message: "Please fill in the password of your Git account.", preferredStyle: UIAlertControllerStyle.alert)
|
||||
alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: {_ in
|
||||
self.password = alert.textFields!.first!.text
|
||||
if self.shouldPerformSegue(withIdentifier: "saveGitServerSettingSegue", sender: self) {
|
||||
self.performSegue(withIdentifier: "saveGitServerSettingSegue", sender: self)
|
||||
}
|
||||
if passwordStore.repositoryExisted() {
|
||||
let alert = UIAlertController(title: "Erase Current Password Store Data?", message: "A cloned password store exists. This operation will erase all local data. Data on your remote server will not be affected.", preferredStyle: UIAlertControllerStyle.alert)
|
||||
alert.addAction(UIAlertAction(title: "Erase", style: UIAlertActionStyle.destructive, handler: { _ in
|
||||
self.doClone()
|
||||
}))
|
||||
alert.addTextField(configurationHandler: {(textField: UITextField!) in
|
||||
textField.text = self.password
|
||||
textField.isSecureTextEntry = true
|
||||
})
|
||||
alert.addAction(UIAlertAction(title: "Cancel", style: UIAlertActionStyle.cancel, handler: nil))
|
||||
self.present(alert, animated: true, completion: nil)
|
||||
} else {
|
||||
if self.shouldPerformSegue(withIdentifier: "saveGitServerSettingSegue", sender: self) {
|
||||
self.performSegue(withIdentifier: "saveGitServerSettingSegue", sender: self)
|
||||
}
|
||||
doClone()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -158,30 +154,8 @@ class GitServerSettingTableViewController: UITableViewController {
|
|||
|
||||
if (gitSSHKeyExists()) {
|
||||
let fileAction = UIAlertAction(title: fileActionTitle, style: .default) { _ in
|
||||
let alert = UIAlertController(
|
||||
title: "SSH Key Passphrase",
|
||||
message: "Please fill in the passphrase for your Git Repository SSH key.",
|
||||
preferredStyle: UIAlertControllerStyle.alert
|
||||
)
|
||||
|
||||
alert.addAction(
|
||||
UIAlertAction(
|
||||
title: "OK",
|
||||
style: UIAlertActionStyle.default,
|
||||
handler: {_ in
|
||||
self.passwordStore.gitSSHPrivateKeyPassphrase = alert.textFields!.first!.text!
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
alert.addTextField(
|
||||
configurationHandler: {(textField: UITextField!) in
|
||||
textField.text = self.passwordStore.gitSSHPrivateKeyPassphrase
|
||||
textField.isSecureTextEntry = true
|
||||
}
|
||||
)
|
||||
Defaults[.gitSSHKeySource] = "file"
|
||||
}
|
||||
Defaults[.gitSSHKeySource] = "file"
|
||||
optionMenu.addAction(fileAction)
|
||||
}
|
||||
|
||||
|
|
@ -189,6 +163,7 @@ class GitServerSettingTableViewController: UITableViewController {
|
|||
let deleteAction = UIAlertAction(title: "Remove Git SSH Keys", style: .destructive) { _ in
|
||||
Utils.removeGitSSHKeys()
|
||||
Defaults[.gitSSHKeySource] = nil
|
||||
self.sshLabel!.isEnabled = false
|
||||
}
|
||||
optionMenu.addAction(deleteAction)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ import AVFoundation
|
|||
|
||||
class OTPScannerController: QRScannerController {
|
||||
|
||||
var tempPassword: Password?
|
||||
var scannedOTP: String?
|
||||
|
||||
// MARK: - AVCaptureMetadataOutputObjectsDelegate Methods
|
||||
|
|
@ -27,14 +26,11 @@ class OTPScannerController: QRScannerController {
|
|||
// check whether it is a valid result
|
||||
if let scannedString = metadataObj.stringValue {
|
||||
if let (accept, message) = delegate?.checkScannedOutput(line: scannedString) {
|
||||
scannerOutput.text = message
|
||||
if accept == true {
|
||||
captureSession?.stopRunning()
|
||||
scannedOTP = scannedString
|
||||
tempPassword = Password(name: "empty", plainText: scannedString)
|
||||
// set scannerOutput
|
||||
setupOneTimePasswordMessage()
|
||||
} else {
|
||||
scannerOutput.text = message
|
||||
presentSaveAlert()
|
||||
}
|
||||
} else {
|
||||
// no delegate, show the scanned result
|
||||
|
|
@ -50,34 +46,28 @@ class OTPScannerController: QRScannerController {
|
|||
}
|
||||
}
|
||||
|
||||
private func setupOneTimePasswordMessage() {
|
||||
if let password = tempPassword {
|
||||
if password.otpType == .hotp {
|
||||
// hotp, no need to refresh
|
||||
let (title, content) = password.getOtpStrings()!
|
||||
scannerOutput.text = "\(title):\(content)"
|
||||
} else if password.otpType == .totp {
|
||||
// totp, refresh
|
||||
Timer.scheduledTimer(withTimeInterval: 1, repeats: true) {
|
||||
[weak weakSelf = self] timer in
|
||||
private func presentSaveAlert() {
|
||||
// initialize alert
|
||||
let password = Password(name: "empty", url: nil, plainText: scannedOTP!)
|
||||
let (title, content) = password.getOtpStrings()!
|
||||
let alert = UIAlertController(title: "Success", message: "\(title): \(content)", preferredStyle: UIAlertControllerStyle.alert)
|
||||
alert.addAction(UIAlertAction(title: "Save", style: UIAlertActionStyle.default, handler: {[unowned self] (action) -> Void in
|
||||
self.delegate?.handleScannedOutput(line: self.scannedOTP!)
|
||||
self.navigationController?.popViewController(animated: true)
|
||||
}))
|
||||
|
||||
if password.otpType == .hotp {
|
||||
// hotp, no need to refresh
|
||||
self.present(alert, animated: true, completion: nil)
|
||||
} else if password.otpType == .totp {
|
||||
// totp, refresh otp
|
||||
self.present(alert, animated: true) {
|
||||
let alertController = self.presentedViewController as! UIAlertController
|
||||
Timer.scheduledTimer(withTimeInterval: 1, repeats: true) {_ in
|
||||
let (title, content) = password.getOtpStrings()!
|
||||
weakSelf?.scannerOutput.text = "\(title):\(content)"
|
||||
alertController.message = "\(title): \(content)"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool {
|
||||
if identifier == "saveAddScannedOTPSegue" {
|
||||
return tempPassword != nil
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
|
||||
// super.prepare(for: segue, sender: sender)
|
||||
// if segue.identifier == "saveAddScannedOTPSegue" {
|
||||
// delegate?.handleScannedOutput(line: scannedOTP)
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ class PGPKeyArmorSettingTableViewController: UITableViewController, UITextViewDe
|
|||
let savePassphraseAlert = UIAlertController(title: "Passphrase", message: "Do you want to save the passphrase for later decryption?", preferredStyle: UIAlertControllerStyle.alert)
|
||||
savePassphraseAlert.addAction(UIAlertAction(title: "No", style: UIAlertActionStyle.default) { _ in
|
||||
Defaults[.isRememberPassphraseOn] = false
|
||||
self.pgpPassphrase = nil
|
||||
self.performSegue(withIdentifier: "savePGPKeySegue", sender: self)
|
||||
})
|
||||
savePassphraseAlert.addAction(UIAlertAction(title: "Save", style: UIAlertActionStyle.destructive) {_ in
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ import SVProgressHUD
|
|||
class PasswordDetailTableViewController: UITableViewController, UIGestureRecognizerDelegate {
|
||||
var passwordEntity: PasswordEntity?
|
||||
private var password: Password?
|
||||
private var passwordCategoryText = ""
|
||||
private var passwordImage: UIImage?
|
||||
private var oneTimePasswordIndexPath : IndexPath?
|
||||
private var shouldPopCurrentView = false
|
||||
|
|
@ -77,7 +76,6 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
|
|||
tableView.register(UINib(nibName: "LabelTableViewCell", bundle: nil), forCellReuseIdentifier: "labelCell")
|
||||
tableView.register(UINib(nibName: "PasswordDetailTitleTableViewCell", bundle: nil), forCellReuseIdentifier: "passwordDetailTitleTableViewCell")
|
||||
|
||||
passwordCategoryText = passwordEntity!.getCategoryText()
|
||||
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(PasswordDetailTableViewController.tapMenu(recognizer:)))
|
||||
tapGesture.cancelsTouchesInView = false
|
||||
tableView.addGestureRecognizer(tapGesture)
|
||||
|
|
@ -96,24 +94,7 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
|
|||
let image = UIImage(data: imageData as Data)
|
||||
passwordImage = image
|
||||
}
|
||||
|
||||
var passphrase = ""
|
||||
if Defaults[.isRememberPassphraseOn] && self.passwordStore.pgpKeyPassphrase != nil {
|
||||
passphrase = self.passwordStore.pgpKeyPassphrase!
|
||||
self.decryptThenShowPassword(passphrase: passphrase)
|
||||
} else {
|
||||
let alert = UIAlertController(title: "Passphrase", message: "Please fill in the passphrase of your PGP secret key.", preferredStyle: UIAlertControllerStyle.alert)
|
||||
alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: {_ in
|
||||
passphrase = alert.textFields!.first!.text!
|
||||
self.decryptThenShowPassword(passphrase: passphrase)
|
||||
}))
|
||||
alert.addTextField(configurationHandler: {(textField: UITextField!) in
|
||||
textField.text = ""
|
||||
textField.isSecureTextEntry = true
|
||||
})
|
||||
self.present(alert, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
self.decryptThenShowPassword()
|
||||
self.setupOneTimePasswordAutoRefresh()
|
||||
|
||||
// pop the current view because this password might be "discarded"
|
||||
|
|
@ -137,14 +118,33 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
|
|||
}
|
||||
}
|
||||
|
||||
private func decryptThenShowPassword(passphrase: String) {
|
||||
private func requestPGPKeyPassphrase() -> String {
|
||||
let sem = DispatchSemaphore(value: 0)
|
||||
var passphrase = ""
|
||||
DispatchQueue.main.async {
|
||||
let alert = UIAlertController(title: "Passphrase", message: "Please fill in the passphrase of your PGP secret key.", preferredStyle: UIAlertControllerStyle.alert)
|
||||
alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: {_ in
|
||||
passphrase = alert.textFields!.first!.text!
|
||||
sem.signal()
|
||||
}))
|
||||
alert.addTextField(configurationHandler: {(textField: UITextField!) in
|
||||
textField.text = ""
|
||||
textField.isSecureTextEntry = true
|
||||
})
|
||||
self.present(alert, animated: true, completion: nil)
|
||||
}
|
||||
let _ = sem.wait(timeout: DispatchTime.distantFuture)
|
||||
if Defaults[.isRememberPassphraseOn] {
|
||||
self.passwordStore.pgpKeyPassphrase = passphrase
|
||||
}
|
||||
return passphrase
|
||||
}
|
||||
|
||||
private func decryptThenShowPassword() {
|
||||
DispatchQueue.global(qos: .userInitiated).async {
|
||||
// decrypt password
|
||||
do {
|
||||
self.password = try self.passwordEntity!.decrypt(passphrase: passphrase)!
|
||||
self.password = try self.passwordStore.decrypt(passwordEntity: self.passwordEntity!, requestPGPKeyPassphrase: self.requestPGPKeyPassphrase)
|
||||
} catch {
|
||||
DispatchQueue.main.async {
|
||||
let alert = UIAlertController(title: "Cannot Show Password", message: error.localizedDescription, preferredStyle: UIAlertControllerStyle.alert)
|
||||
|
|
@ -164,11 +164,7 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
|
|||
DispatchQueue.main.async { [weak self] in
|
||||
self?.indicator.stopAnimating()
|
||||
self?.setTableData()
|
||||
UIView.performWithoutAnimation {
|
||||
self?.tableView.reloadData()
|
||||
// add layoutIfNeeded solves the "flickering problem" during refresh
|
||||
self?.tableView.layoutIfNeeded()
|
||||
}
|
||||
self?.tableView.reloadData()
|
||||
self?.editUIBarButtonItem.isEnabled = true
|
||||
if let urlString = self?.password?.getURLString() {
|
||||
if self?.passwordEntity?.image == nil {
|
||||
|
|
@ -216,29 +212,26 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
|
|||
}
|
||||
|
||||
@IBAction private func saveEditPassword(segue: UIStoryboardSegue) {
|
||||
if self.password!.changed {
|
||||
if self.password!.changed != 0 {
|
||||
SVProgressHUD.show(withStatus: "Saving")
|
||||
DispatchQueue.global(qos: .userInitiated).async {
|
||||
self.passwordStore.update(passwordEntity: self.passwordEntity!, password: self.password!, progressBlock: { progress in
|
||||
DispatchQueue.main.async {
|
||||
SVProgressHUD.showProgress(progress, status: "Encrypting")
|
||||
}
|
||||
})
|
||||
DispatchQueue.main.async {
|
||||
self.passwordEntity!.synced = false
|
||||
self.passwordStore.saveUpdated(passwordEntity: self.passwordEntity!)
|
||||
self.setTableData()
|
||||
self.tableView.reloadData()
|
||||
SVProgressHUD.showSuccess(withStatus: "Success")
|
||||
SVProgressHUD.dismiss(withDelay: 1)
|
||||
}
|
||||
do {
|
||||
self.passwordEntity = try self.passwordStore.edit(passwordEntity: self.passwordEntity!, password: self.password!)
|
||||
} catch {
|
||||
Utils.alert(title: "Error", message: error.localizedDescription, controller: self, completion: nil)
|
||||
}
|
||||
self.setTableData()
|
||||
self.tableView.reloadData()
|
||||
SVProgressHUD.showSuccess(withStatus: "Success")
|
||||
SVProgressHUD.dismiss(withDelay: 1)
|
||||
}
|
||||
}
|
||||
|
||||
@IBAction private func deletePassword(segue: UIStoryboardSegue) {
|
||||
print("delete")
|
||||
passwordStore.delete(passwordEntity: passwordEntity!)
|
||||
do {
|
||||
try passwordStore.delete(passwordEntity: passwordEntity!)
|
||||
} catch {
|
||||
Utils.alert(title: "Error", message: error.localizedDescription, controller: self, completion: nil)
|
||||
}
|
||||
let _ = navigationController?.popViewController(animated: true)
|
||||
}
|
||||
|
||||
|
|
@ -392,16 +385,14 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
|
|||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
if password!.changed != 0 {
|
||||
do {
|
||||
self.passwordEntity = try self.passwordStore.edit(passwordEntity: self.passwordEntity!, password: self.password!)
|
||||
} catch {
|
||||
Utils.alert(title: "Error", message: error.localizedDescription, controller: self, completion: nil)
|
||||
}
|
||||
SVProgressHUD.showSuccess(withStatus: "Password Copied\nCounter Updated")
|
||||
SVProgressHUD.dismiss(withDelay: 1)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -432,12 +423,14 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
|
|||
case .name:
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: "passwordDetailTitleTableViewCell", for: indexPath) as! PasswordDetailTitleTableViewCell
|
||||
cell.passwordImageImageView.image = passwordImage ?? #imageLiteral(resourceName: "PasswordImagePlaceHolder")
|
||||
var passwordName = passwordEntity!.name!
|
||||
if passwordEntity!.synced == false {
|
||||
passwordName = "\(passwordName) ↻"
|
||||
if let passwordName = passwordEntity!.name {
|
||||
if passwordEntity!.synced == false {
|
||||
cell.nameLabel.text = "\(passwordName) ↻"
|
||||
} else {
|
||||
cell.nameLabel.text = passwordName
|
||||
}
|
||||
}
|
||||
cell.nameLabel.text = passwordName
|
||||
cell.categoryLabel.text = passwordCategoryText
|
||||
cell.categoryLabel.text = passwordEntity!.getCategoryText()
|
||||
cell.selectionStyle = .none
|
||||
return cell
|
||||
case .main, .addition:
|
||||
|
|
@ -467,7 +460,7 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
|
|||
footerLabel.numberOfLines = 0
|
||||
footerLabel.font = UIFont.preferredFont(forTextStyle: .footnote)
|
||||
footerLabel.textColor = UIColor.gray
|
||||
let dateString = self.passwordStore.getLatestUpdateInfo(filename: (passwordEntity?.path)!)
|
||||
let dateString = self.passwordStore.getLatestUpdateInfo(filename: password!.url!.path)
|
||||
footerLabel.text = "Last Updated: \(dateString)"
|
||||
view.addSubview(footerLabel)
|
||||
return view
|
||||
|
|
|
|||
|
|
@ -11,14 +11,14 @@ import SwiftyUserDefaults
|
|||
import OneTimePassword
|
||||
|
||||
enum PasswordEditorCellType {
|
||||
case textFieldCell, textViewCell, fillPasswordCell, passwordLengthCell, deletePasswordCell, scanQRCodeCell
|
||||
case nameCell, fillPasswordCell, passwordLengthCell, additionsCell, deletePasswordCell, scanQRCodeCell
|
||||
}
|
||||
|
||||
enum PasswordEditorCellKey {
|
||||
case type, title, content, placeholders
|
||||
}
|
||||
|
||||
class PasswordEditorTableViewController: UITableViewController, FillPasswordTableViewCellDelegate, PasswordSettingSliderTableViewCellDelegate, QRScannerControllerDelegate {
|
||||
class PasswordEditorTableViewController: UITableViewController, FillPasswordTableViewCellDelegate, PasswordSettingSliderTableViewCellDelegate, QRScannerControllerDelegate, UITextFieldDelegate, UITextViewDelegate {
|
||||
|
||||
var tableData = [
|
||||
[Dictionary<PasswordEditorCellKey, Any>]
|
||||
|
|
@ -29,12 +29,15 @@ class PasswordEditorTableViewController: UITableViewController, FillPasswordTabl
|
|||
|
||||
private var sectionHeaderTitles = ["name", "password", "additions",""].map {$0.uppercased()}
|
||||
private var sectionFooterTitles = ["", "", "Use \"key: value\" format for additional fields.", ""]
|
||||
private let nameSection = 0
|
||||
private let passwordSection = 1
|
||||
private let additionsSection = 2
|
||||
private var hidePasswordSettings = true
|
||||
|
||||
private var fillPasswordCell: FillPasswordTableViewCell?
|
||||
var nameCell: TextFieldTableViewCell?
|
||||
var fillPasswordCell: FillPasswordTableViewCell?
|
||||
private var passwordLengthCell: SliderTableViewCell?
|
||||
var additionsCell: TextViewTableViewCell?
|
||||
private var deletePasswordCell: UITableViewCell?
|
||||
private var scanQRCodeCell: UITableViewCell?
|
||||
|
||||
|
|
@ -50,6 +53,7 @@ class PasswordEditorTableViewController: UITableViewController, FillPasswordTabl
|
|||
scanQRCodeCell?.textLabel?.text = "Add One-Time Password"
|
||||
scanQRCodeCell?.textLabel?.textColor = Globals.blue
|
||||
scanQRCodeCell?.selectionStyle = .default
|
||||
scanQRCodeCell?.accessoryType = .disclosureIndicator
|
||||
// scanQRCodeCell?.imageView?.image = #imageLiteral(resourceName: "Camera").withRenderingMode(.alwaysTemplate)
|
||||
// scanQRCodeCell?.imageView?.tintColor = Globals.blue
|
||||
// scanQRCodeCell?.imageView?.contentMode = .scaleAspectFit
|
||||
|
|
@ -76,10 +80,11 @@ class PasswordEditorTableViewController: UITableViewController, FillPasswordTabl
|
|||
let cellData = tableData[indexPath.section][indexPath.row]
|
||||
|
||||
switch cellData[PasswordEditorCellKey.type] as! PasswordEditorCellType {
|
||||
case .textViewCell:
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: "textViewCell", for: indexPath) as! ContentTableViewCell
|
||||
cell.setContent(content: cellData[PasswordEditorCellKey.content] as? String)
|
||||
return cell
|
||||
case .nameCell:
|
||||
nameCell = tableView.dequeueReusableCell(withIdentifier: "textFieldCell", for: indexPath) as? TextFieldTableViewCell
|
||||
nameCell?.contentTextField.delegate = self
|
||||
nameCell?.setContent(content: cellData[PasswordEditorCellKey.content] as? String)
|
||||
return nameCell!
|
||||
case .fillPasswordCell:
|
||||
fillPasswordCell = tableView.dequeueReusableCell(withIdentifier: "fillPasswordCell", for: indexPath) as? FillPasswordTableViewCell
|
||||
fillPasswordCell?.delegate = self
|
||||
|
|
@ -95,14 +100,15 @@ class PasswordEditorTableViewController: UITableViewController, FillPasswordTabl
|
|||
defaultValue: lengthSetting?.def ?? 0)
|
||||
passwordLengthCell?.delegate = self
|
||||
return passwordLengthCell!
|
||||
case .additionsCell:
|
||||
additionsCell = tableView.dequeueReusableCell(withIdentifier: "textViewCell", for: indexPath) as?TextViewTableViewCell
|
||||
additionsCell?.contentTextView.delegate = self
|
||||
additionsCell?.setContent(content: cellData[PasswordEditorCellKey.content] as? String)
|
||||
return additionsCell!
|
||||
case .deletePasswordCell:
|
||||
return deletePasswordCell!
|
||||
case .scanQRCodeCell:
|
||||
return scanQRCodeCell!
|
||||
default:
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: "textFieldCell", for: indexPath) as! ContentTableViewCell
|
||||
cell.setContent(content: cellData[PasswordEditorCellKey.content] as? String)
|
||||
return cell
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -187,13 +193,16 @@ class PasswordEditorTableViewController: UITableViewController, FillPasswordTabl
|
|||
|
||||
func insertScannedOTPFields(_ otpauth: String) {
|
||||
// update tableData
|
||||
var additionsString = ""
|
||||
if let additionsPlainText = (tableData[additionsSection][0][PasswordEditorCellKey.content] as? String)?.trimmingCharacters(in: .whitespacesAndNewlines), additionsPlainText != "" {
|
||||
tableData[additionsSection][0][PasswordEditorCellKey.content] = additionsPlainText + "\n" + otpauth
|
||||
additionsString = additionsPlainText + "\n" + otpauth
|
||||
} else {
|
||||
tableData[additionsSection][0][PasswordEditorCellKey.content] = otpauth
|
||||
additionsString = otpauth
|
||||
}
|
||||
// reload
|
||||
tableView.reloadSections([additionsSection], with: .none)
|
||||
tableData[additionsSection][0][PasswordEditorCellKey.content] = additionsString
|
||||
|
||||
// reload the additions cell
|
||||
additionsCell?.setContent(content: additionsString)
|
||||
}
|
||||
|
||||
// MARK: - QRScannerControllerDelegate Methods
|
||||
|
|
@ -226,11 +235,17 @@ class PasswordEditorTableViewController: UITableViewController, FillPasswordTabl
|
|||
|
||||
}
|
||||
|
||||
@IBAction func saveScannedOTP(segue: UIStoryboardSegue) {
|
||||
if let controller = segue.source as? OTPScannerController {
|
||||
if let scannedOTP = controller.scannedOTP {
|
||||
insertScannedOTPFields(scannedOTP)
|
||||
}
|
||||
// update the data table after editing
|
||||
func textFieldDidEndEditing(_ textField: UITextField) {
|
||||
if textField == nameCell?.contentTextField {
|
||||
tableData[nameSection][0][PasswordEditorCellKey.content] = nameCell?.getContent()
|
||||
}
|
||||
}
|
||||
|
||||
// update the data table after editing
|
||||
func textViewDidEndEditing(_ textView: UITextView) {
|
||||
if textView == additionsCell?.contentTextView {
|
||||
tableData[additionsSection][0][PasswordEditorCellKey.content] = additionsCell?.getContent()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -112,11 +112,7 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
|
|||
SVProgressHUD.show(withStatus: "Saving")
|
||||
DispatchQueue.global(qos: .userInitiated).async {
|
||||
do {
|
||||
try self.passwordStore.add(password: controller.password!, progressBlock: { progress in
|
||||
DispatchQueue.main.async {
|
||||
SVProgressHUD.showProgress(progress, status: "Encrypting")
|
||||
}
|
||||
})
|
||||
let _ = try self.passwordStore.add(password: controller.password!)
|
||||
DispatchQueue.main.async {
|
||||
// will trigger reloadTableView() by a notification
|
||||
SVProgressHUD.showSuccess(withStatus: "Done")
|
||||
|
|
@ -132,19 +128,38 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
|
|||
}
|
||||
|
||||
private func syncPasswords() {
|
||||
guard passwordStore.repositoryExisted() else {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(800)) {
|
||||
Utils.alert(title: "Error", message: "There is no password store right now.", controller: self, completion: nil)
|
||||
}
|
||||
return
|
||||
}
|
||||
SVProgressHUD.setDefaultMaskType(.black)
|
||||
SVProgressHUD.setDefaultStyle(.light)
|
||||
SVProgressHUD.show(withStatus: "Sync Password Store")
|
||||
let numberOfLocalCommits = self.passwordStore.numberOfLocalCommits()
|
||||
var gitCredential: GitCredential
|
||||
if Defaults[.gitAuthenticationMethod] == "Password" {
|
||||
gitCredential = GitCredential(credential: GitCredential.Credential.http(userName: Defaults[.gitUsername]!, controller: self))
|
||||
} else {
|
||||
gitCredential = GitCredential(
|
||||
credential: GitCredential.Credential.ssh(
|
||||
userName: Defaults[.gitUsername]!,
|
||||
publicKeyFile: Globals.gitSSHPublicKeyURL,
|
||||
privateKeyFile: Globals.gitSSHPrivateKeyURL,
|
||||
controller: self
|
||||
)
|
||||
)
|
||||
}
|
||||
DispatchQueue.global(qos: .userInitiated).async { [unowned self] in
|
||||
do {
|
||||
try self.passwordStore.pullRepository(transferProgressBlock: {(git_transfer_progress, stop) in
|
||||
try self.passwordStore.pullRepository(credential: gitCredential, transferProgressBlock: {(git_transfer_progress, stop) in
|
||||
DispatchQueue.main.async {
|
||||
SVProgressHUD.showProgress(Float(git_transfer_progress.pointee.received_objects)/Float(git_transfer_progress.pointee.total_objects), status: "Pull Remote Repository")
|
||||
}
|
||||
})
|
||||
if numberOfLocalCommits > 0 {
|
||||
try self.passwordStore.pushRepository(transferProgressBlock: {(current, total, bytes, stop) in
|
||||
try self.passwordStore.pushRepository(credential: gitCredential, transferProgressBlock: {(current, total, bytes, stop) in
|
||||
DispatchQueue.main.async {
|
||||
SVProgressHUD.showProgress(Float(current)/Float(total), status: "Push Remote Repository")
|
||||
}
|
||||
|
|
@ -152,7 +167,6 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
|
|||
}
|
||||
DispatchQueue.main.async {
|
||||
self.reloadTableView(parent: nil)
|
||||
Defaults[.gitPasswordAttempts] = 0
|
||||
SVProgressHUD.showSuccess(withStatus: "Done")
|
||||
SVProgressHUD.dismiss(withDelay: 1)
|
||||
}
|
||||
|
|
@ -323,15 +337,19 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
|
|||
}
|
||||
let password = getPasswordEntry(by: indexPath).passwordEntity!
|
||||
UIImpactFeedbackGenerator(style: .medium).impactOccurred()
|
||||
decryptThenCopyPassword(passwordEntity: password)
|
||||
}
|
||||
|
||||
|
||||
|
||||
private func requestPGPKeyPassphrase() -> String {
|
||||
let sem = DispatchSemaphore(value: 0)
|
||||
var passphrase = ""
|
||||
if Defaults[.isRememberPassphraseOn] && self.passwordStore.pgpKeyPassphrase != nil {
|
||||
passphrase = self.passwordStore.pgpKeyPassphrase!
|
||||
self.decryptThenCopyPassword(passwordEntity: password, passphrase: passphrase)
|
||||
} else {
|
||||
DispatchQueue.main.async {
|
||||
let alert = UIAlertController(title: "Passphrase", message: "Please fill in the passphrase of your PGP secret key.", preferredStyle: UIAlertControllerStyle.alert)
|
||||
alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: {_ in
|
||||
passphrase = alert.textFields!.first!.text!
|
||||
self.decryptThenCopyPassword(passwordEntity: password, passphrase: passphrase)
|
||||
sem.signal()
|
||||
}))
|
||||
alert.addTextField(configurationHandler: {(textField: UITextField!) in
|
||||
textField.text = ""
|
||||
|
|
@ -339,17 +357,21 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
|
|||
})
|
||||
self.present(alert, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
let _ = sem.wait(timeout: DispatchTime.distantFuture)
|
||||
if Defaults[.isRememberPassphraseOn] {
|
||||
self.passwordStore.pgpKeyPassphrase = passphrase
|
||||
}
|
||||
return passphrase
|
||||
}
|
||||
|
||||
private func decryptThenCopyPassword(passwordEntity: PasswordEntity, passphrase: String) {
|
||||
private func decryptThenCopyPassword(passwordEntity: PasswordEntity) {
|
||||
SVProgressHUD.setDefaultMaskType(.black)
|
||||
SVProgressHUD.setDefaultStyle(.dark)
|
||||
SVProgressHUD.show(withStatus: "Decrypting")
|
||||
DispatchQueue.global(qos: .userInteractive).async {
|
||||
var decryptedPassword: Password?
|
||||
do {
|
||||
decryptedPassword = try passwordEntity.decrypt(passphrase: passphrase)!
|
||||
decryptedPassword = try self.passwordStore.decrypt(passwordEntity: passwordEntity, requestPGPKeyPassphrase: self.requestPGPKeyPassphrase)
|
||||
DispatchQueue.main.async {
|
||||
Utils.copyToPasteboard(textToCopy: decryptedPassword?.password)
|
||||
SVProgressHUD.showSuccess(withStatus: "Password copied, and will be cleared in 45 seconds.")
|
||||
|
|
@ -486,9 +508,9 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
|
|||
}
|
||||
|
||||
func actOnReloadTableViewRelatedNotification() {
|
||||
initPasswordsTableEntries(parent: nil)
|
||||
DispatchQueue.main.async { [weak weakSelf = self] in
|
||||
guard let strongSelf = weakSelf else { return }
|
||||
strongSelf.initPasswordsTableEntries(parent: nil)
|
||||
strongSelf.reloadTableView(data: strongSelf.passwordsTableEntries)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,32 +43,7 @@ class SSHKeySettingTableViewController: UITableViewController {
|
|||
Utils.alert(title: "Error", message: error.localizedDescription, controller: self, completion: nil)
|
||||
}
|
||||
Defaults[.gitSSHKeySource] = "url"
|
||||
let alert = UIAlertController(
|
||||
title: "PGP Passphrase",
|
||||
message: "Please fill in the passphrase for your Git Repository SSH key.",
|
||||
preferredStyle: UIAlertControllerStyle.alert
|
||||
)
|
||||
|
||||
alert.addAction(
|
||||
UIAlertAction(
|
||||
title: "OK",
|
||||
style: UIAlertActionStyle.default,
|
||||
handler: {_ in
|
||||
Utils.addPasswordToKeychain(
|
||||
name: "gitSSHPrivateKeyPassphrase",
|
||||
password: alert.textFields!.first!.text!
|
||||
)
|
||||
self.navigationController!.popViewController(animated: true)
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
alert.addTextField(
|
||||
configurationHandler: {(textField: UITextField!) in
|
||||
textField.text = self.passwordStore.gitSSHPrivateKeyPassphrase
|
||||
textField.isSecureTextEntry = true
|
||||
})
|
||||
self.present(alert, animated: true, completion: nil)
|
||||
self.navigationController!.popViewController(animated: true)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,7 +34,9 @@ class SettingsTableViewController: UITableViewController {
|
|||
if let controller = segue.source as? PGPKeySettingTableViewController {
|
||||
Defaults[.pgpPrivateKeyURL] = URL(string: controller.pgpPrivateKeyURLTextField.text!)
|
||||
Defaults[.pgpPublicKeyURL] = URL(string: controller.pgpPublicKeyURLTextField.text!)
|
||||
self.passwordStore.pgpKeyPassphrase = controller.pgpPassphrase
|
||||
if Defaults[.isRememberPassphraseOn] {
|
||||
self.passwordStore.pgpKeyPassphrase = controller.pgpPassphrase
|
||||
}
|
||||
Defaults[.pgpKeySource] = "url"
|
||||
|
||||
SVProgressHUD.setDefaultMaskType(.black)
|
||||
|
|
@ -48,7 +50,7 @@ class SettingsTableViewController: UITableViewController {
|
|||
self.pgpKeyTableViewCell.detailTextLabel?.text = self.passwordStore.pgpKeyID
|
||||
SVProgressHUD.showSuccess(withStatus: "Success")
|
||||
SVProgressHUD.dismiss(withDelay: 1)
|
||||
Utils.alert(title: "Rememver to Remove the Key", message: "Remember to remove the key from the server.", controller: self, completion: nil)
|
||||
Utils.alert(title: "Remember to Remove the Key", message: "Remember to remove the key from the server.", controller: self, completion: nil)
|
||||
}
|
||||
} catch {
|
||||
DispatchQueue.main.async {
|
||||
|
|
@ -60,9 +62,8 @@ class SettingsTableViewController: UITableViewController {
|
|||
|
||||
} else if let controller = segue.source as? PGPKeyArmorSettingTableViewController {
|
||||
Defaults[.pgpKeySource] = "armor"
|
||||
self.passwordStore.pgpKeyPassphrase = controller.pgpPassphrase
|
||||
if Defaults[.isRememberPassphraseOn] {
|
||||
Utils.addPasswordToKeychain(name: "pgpKeyPassphrase", password: controller.pgpPassphrase!)
|
||||
self.passwordStore.pgpKeyPassphrase = controller.pgpPassphrase
|
||||
}
|
||||
|
||||
Defaults[.pgpPublicKeyArmor] = controller.armorPublicKeyTextView.text!
|
||||
|
|
@ -97,64 +98,53 @@ class SettingsTableViewController: UITableViewController {
|
|||
if let controller = segue.source as? GitServerSettingTableViewController {
|
||||
let gitRepostiroyURL = controller.gitURLTextField.text!
|
||||
let username = controller.usernameTextField.text!
|
||||
let password = controller.password
|
||||
let auth = controller.authenticationMethod
|
||||
|
||||
if Defaults[.gitURL] == nil ||
|
||||
Defaults[.gitURL]!.absoluteString != gitRepostiroyURL ||
|
||||
auth != Defaults[.gitAuthenticationMethod] ||
|
||||
username != Defaults[.gitUsername] ||
|
||||
password != self.passwordStore.gitPassword ||
|
||||
self.passwordStore.repositoryExisted() == false {
|
||||
|
||||
SVProgressHUD.setDefaultMaskType(.black)
|
||||
SVProgressHUD.setDefaultStyle(.light)
|
||||
SVProgressHUD.show(withStatus: "Prepare Repository")
|
||||
var gitCredential: GitCredential
|
||||
if auth == "Password" {
|
||||
gitCredential = GitCredential(credential: GitCredential.Credential.http(userName: username, password: password!))
|
||||
} else {
|
||||
gitCredential = GitCredential(
|
||||
credential: GitCredential.Credential.ssh(
|
||||
userName: username,
|
||||
password: Utils.getPasswordFromKeychain(name: "gitSSHPrivateKeyPassphrase") ?? "",
|
||||
publicKeyFile: Globals.gitSSHPublicKeyURL,
|
||||
privateKeyFile: Globals.gitSSHPrivateKeyURL,
|
||||
passwordNotSetCallback: self.requestSshKeyPassword
|
||||
)
|
||||
SVProgressHUD.setDefaultMaskType(.black)
|
||||
SVProgressHUD.setDefaultStyle(.light)
|
||||
SVProgressHUD.show(withStatus: "Prepare Repository")
|
||||
var gitCredential: GitCredential
|
||||
if auth == "Password" {
|
||||
gitCredential = GitCredential(credential: GitCredential.Credential.http(userName: username, controller: self))
|
||||
} else {
|
||||
gitCredential = GitCredential(
|
||||
credential: GitCredential.Credential.ssh(
|
||||
userName: username,
|
||||
publicKeyFile: Globals.gitSSHPublicKeyURL,
|
||||
privateKeyFile: Globals.gitSSHPrivateKeyURL,
|
||||
controller: self
|
||||
)
|
||||
}
|
||||
let dispatchQueue = DispatchQueue.global(qos: .userInitiated)
|
||||
dispatchQueue.async {
|
||||
do {
|
||||
try self.passwordStore.cloneRepository(remoteRepoURL: URL(string: gitRepostiroyURL)!,
|
||||
credential: gitCredential,
|
||||
transferProgressBlock:{ (git_transfer_progress, stop) in
|
||||
DispatchQueue.main.async {
|
||||
SVProgressHUD.showProgress(Float(git_transfer_progress.pointee.received_objects)/Float(git_transfer_progress.pointee.total_objects), status: "Clone Remote Repository")
|
||||
}
|
||||
},
|
||||
checkoutProgressBlock: { (path, completedSteps, totalSteps) in
|
||||
DispatchQueue.main.async {
|
||||
SVProgressHUD.showProgress(Float(completedSteps)/Float(totalSteps), status: "Checkout Master Branch")
|
||||
}
|
||||
})
|
||||
DispatchQueue.main.async {
|
||||
Defaults[.gitURL] = URL(string: gitRepostiroyURL)
|
||||
Defaults[.gitUsername] = username
|
||||
Defaults[.gitAuthenticationMethod] = auth
|
||||
Defaults[.gitPasswordAttempts] = 0
|
||||
self.passwordRepositoryTableViewCell.detailTextLabel?.text = Defaults[.gitURL]?.host
|
||||
SVProgressHUD.showSuccess(withStatus: "Done")
|
||||
SVProgressHUD.dismiss(withDelay: 1)
|
||||
}
|
||||
} catch {
|
||||
DispatchQueue.main.async {
|
||||
Utils.alert(title: "Error", message: error.localizedDescription, controller: self, completion: nil)
|
||||
}
|
||||
)
|
||||
}
|
||||
let dispatchQueue = DispatchQueue.global(qos: .userInitiated)
|
||||
dispatchQueue.async {
|
||||
do {
|
||||
try self.passwordStore.cloneRepository(remoteRepoURL: URL(string: gitRepostiroyURL)!,
|
||||
credential: gitCredential,
|
||||
transferProgressBlock:{ (git_transfer_progress, stop) in
|
||||
DispatchQueue.main.async {
|
||||
SVProgressHUD.showProgress(Float(git_transfer_progress.pointee.received_objects)/Float(git_transfer_progress.pointee.total_objects), status: "Clone Remote Repository")
|
||||
}
|
||||
},
|
||||
checkoutProgressBlock: { (path, completedSteps, totalSteps) in
|
||||
DispatchQueue.main.async {
|
||||
SVProgressHUD.showProgress(Float(completedSteps)/Float(totalSteps), status: "Checkout Master Branch")
|
||||
}
|
||||
})
|
||||
DispatchQueue.main.async {
|
||||
Defaults[.gitURL] = URL(string: gitRepostiroyURL)
|
||||
Defaults[.gitUsername] = username
|
||||
Defaults[.gitAuthenticationMethod] = auth
|
||||
self.passwordRepositoryTableViewCell.detailTextLabel?.text = Defaults[.gitURL]?.host
|
||||
SVProgressHUD.showSuccess(withStatus: "Done")
|
||||
SVProgressHUD.dismiss(withDelay: 1)
|
||||
}
|
||||
} catch {
|
||||
DispatchQueue.main.async {
|
||||
Utils.alert(title: "Error", message: error.localizedDescription, controller: self, completion: nil)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -203,32 +193,7 @@ class SettingsTableViewController: UITableViewController {
|
|||
touchIDSwitch.isOn = false
|
||||
}
|
||||
}
|
||||
|
||||
func requestSshKeyPassword() -> String {
|
||||
let sem = DispatchSemaphore(value: 0)
|
||||
var newPassword = ""
|
||||
|
||||
DispatchQueue.main.async {
|
||||
SVProgressHUD.dismiss()
|
||||
let alert = UIAlertController(title: "Password", message: "Please fill in the password of your SSH key.", preferredStyle: UIAlertControllerStyle.alert)
|
||||
|
||||
alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: {_ in
|
||||
newPassword = alert.textFields!.first!.text!
|
||||
sem.signal()
|
||||
}))
|
||||
|
||||
alert.addTextField(configurationHandler: {(textField: UITextField!) in
|
||||
textField.text = self.passwordStore.gitPassword
|
||||
textField.isSecureTextEntry = true
|
||||
})
|
||||
|
||||
self.present(alert, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
let _ = sem.wait(timeout: DispatchTime.distantFuture)
|
||||
return newPassword
|
||||
}
|
||||
|
||||
|
||||
func actOnPasswordStoreErasedNotification() {
|
||||
setPGPKeyTableViewCellDetailText()
|
||||
setPasswordRepositoryTableViewCellDetailText()
|
||||
|
|
|
|||
|
|
@ -20,14 +20,13 @@ extension DefaultsKeys {
|
|||
static let gitURL = DefaultsKey<URL?>("gitURL")
|
||||
static let gitAuthenticationMethod = DefaultsKey<String?>("gitAuthenticationMethod")
|
||||
static let gitUsername = DefaultsKey<String?>("gitUsername")
|
||||
static let gitPasswordAttempts = DefaultsKey<Int>("gitPasswordAttempts")
|
||||
static let gitSSHPublicKeyURL = DefaultsKey<URL?>("gitSSHPublicKeyURL")
|
||||
static let gitSSHPrivateKeyURL = DefaultsKey<URL?>("gitSSHPrivateKeyURL")
|
||||
static let gitSSHKeySource = DefaultsKey<String?>("gitSSHKeySource")
|
||||
|
||||
static let gitSSHPublicKeyArmor = DefaultsKey<String?>("gitSSHPublicKeyArmor")
|
||||
static let gitSSHPrivateKeyArmor = DefaultsKey<String?>("gitSSHPrivateKeyArmor")
|
||||
|
||||
static let gitSignatureName = DefaultsKey<String?>("gitSignatureName")
|
||||
static let gitSignatureEmail = DefaultsKey<String?>("gitSignatureEmail")
|
||||
|
||||
static let lastSyncedTime = DefaultsKey<Date?>("lastSyncedTime")
|
||||
|
||||
|
|
|
|||
|
|
@ -26,6 +26,9 @@ class Globals {
|
|||
static let passwordDefaultLength = ["Random": (min: 6, max: 24, def: 16),
|
||||
"Apple": (min: 15, max: 15, def: 15)]
|
||||
|
||||
static let gitSignatureDefaultName = "Pass for iOS"
|
||||
static let gitSignatureDefaultEmail = "user@passforios"
|
||||
|
||||
static let passwordDots = "••••••••••••"
|
||||
static let oneTimePasswordDots = "••••••"
|
||||
static let passwordFonts = "Menlo"
|
||||
|
|
|
|||
|
|
@ -238,3 +238,12 @@ extension FileManager {
|
|||
return accumulatedSize
|
||||
}
|
||||
}
|
||||
|
||||
extension String {
|
||||
func stringByAddingPercentEncodingForRFC3986() -> String? {
|
||||
let unreserved = "-._~/?"
|
||||
var allowed = CharacterSet.alphanumerics
|
||||
allowed.insert(charactersIn: unreserved)
|
||||
return addingPercentEncoding(withAllowedCharacters: allowed)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>0.2.4</string>
|
||||
<string>0.2.5</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>ITSAppUsesNonExemptEncryption</key>
|
||||
|
|
|
|||
|
|
@ -16,14 +16,29 @@ struct AdditionField {
|
|||
var content: String
|
||||
}
|
||||
|
||||
enum PasswordChange: Int {
|
||||
case path = 0x01
|
||||
case content = 0x02
|
||||
case none = 0x00
|
||||
}
|
||||
|
||||
class Password {
|
||||
static let otpKeywords = ["otp_secret", "otp_type", "otp_algorithm", "otp_period", "otp_digits", "otp_counter", "otpauth"]
|
||||
|
||||
|
||||
var name = ""
|
||||
var url: URL?
|
||||
var namePath: String {
|
||||
get {
|
||||
if url == nil {
|
||||
return ""
|
||||
}
|
||||
return url!.deletingPathExtension().path
|
||||
}
|
||||
}
|
||||
var password = ""
|
||||
var additions = [String: String]()
|
||||
var additionKeys = [String]()
|
||||
var changed = false
|
||||
var changed: Int = 0
|
||||
var plainText = ""
|
||||
|
||||
private var firstLineIsOTPField = false
|
||||
|
|
@ -47,19 +62,25 @@ class Password {
|
|||
}
|
||||
}
|
||||
|
||||
init(name: String, plainText: String) {
|
||||
self.initEverything(name: name, plainText: plainText)
|
||||
init(name: String, url: URL?, plainText: String) {
|
||||
self.initEverything(name: name, url: url, plainText: plainText)
|
||||
}
|
||||
|
||||
func updatePassword(name: String, plainText: String) {
|
||||
if self.plainText != plainText {
|
||||
self.initEverything(name: name, plainText: plainText)
|
||||
changed = true
|
||||
func updatePassword(name: String, url: URL?, plainText: String) {
|
||||
if self.plainText != plainText || self.url != url {
|
||||
if self.plainText != plainText {
|
||||
changed = changed|PasswordChange.content.rawValue
|
||||
}
|
||||
if self.url != url {
|
||||
changed = changed|PasswordChange.path.rawValue
|
||||
}
|
||||
self.initEverything(name: name, url: url, plainText: plainText)
|
||||
}
|
||||
}
|
||||
|
||||
private func initEverything(name: String, plainText: String) {
|
||||
private func initEverything(name: String, url: URL?, plainText: String) {
|
||||
self.name = name
|
||||
self.url = url
|
||||
self.plainText = plainText
|
||||
self.additions.removeAll()
|
||||
self.additionKeys.removeAll()
|
||||
|
|
@ -322,7 +343,7 @@ class Password {
|
|||
if newOtpauth != nil {
|
||||
lines.append(newOtpauth!)
|
||||
}
|
||||
self.updatePassword(name: self.name, plainText: lines.joined(separator: "\n"))
|
||||
self.updatePassword(name: self.name, url: self.url, plainText: lines.joined(separator: "\n"))
|
||||
|
||||
// get and return the password
|
||||
return self.otpToken?.currentPassword
|
||||
|
|
|
|||
|
|
@ -21,24 +21,6 @@ extension PasswordEntity {
|
|||
}
|
||||
}
|
||||
|
||||
func decrypt(passphrase: String) throws -> Password? {
|
||||
var password: Password?
|
||||
let encryptedDataPath = URL(fileURLWithPath: "\(Globals.repositoryPath)/\(path!)")
|
||||
let encryptedData = try Data(contentsOf: encryptedDataPath)
|
||||
let decryptedData = try PasswordStore.shared.pgp.decryptData(encryptedData, passphrase: passphrase)
|
||||
let plainText = String(data: decryptedData, encoding: .utf8) ?? ""
|
||||
password = Password(name: name!, plainText: plainText)
|
||||
return password
|
||||
}
|
||||
|
||||
func encrypt(password: Password) throws -> Data {
|
||||
name = password.name
|
||||
let plainData = password.getPlainData()
|
||||
let pgp = PasswordStore.shared.pgp
|
||||
let encryptedData = try pgp.encryptData(plainData, usingPublicKey: pgp.getKeysOf(.public)[0], armored: Defaults[.encryptInArmored])
|
||||
return encryptedData
|
||||
}
|
||||
|
||||
func getCategoryText() -> String {
|
||||
var parentEntity = parent
|
||||
var passwordCategoryArray: [String] = []
|
||||
|
|
@ -49,4 +31,11 @@ extension PasswordEntity {
|
|||
passwordCategoryArray.reverse()
|
||||
return passwordCategoryArray.joined(separator: " > ")
|
||||
}
|
||||
|
||||
func getURL() -> URL? {
|
||||
if let p = path {
|
||||
return URL(string: p.stringByAddingPercentEncodingForRFC3986()!)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,81 +14,96 @@ import ObjectiveGit
|
|||
import SVProgressHUD
|
||||
|
||||
struct GitCredential {
|
||||
var credential: Credential
|
||||
|
||||
enum Credential {
|
||||
case http(userName: String, password: String)
|
||||
case ssh(userName: String, password: String, publicKeyFile: URL, privateKeyFile: URL, passwordNotSetCallback: (() -> String)? )
|
||||
case http(userName: String, controller: UIViewController)
|
||||
case ssh(userName: String, publicKeyFile: URL, privateKeyFile: URL, controller: UIViewController)
|
||||
}
|
||||
|
||||
init(credential: Credential) {
|
||||
self.credential = credential
|
||||
}
|
||||
|
||||
var credential: Credential
|
||||
|
||||
func credentialProvider() throws -> GTCredentialProvider {
|
||||
var attempts = 0
|
||||
var lastPassword: String? = nil
|
||||
return GTCredentialProvider { (_, _, _) -> (GTCredential?) in
|
||||
var credential: GTCredential? = nil
|
||||
|
||||
switch self.credential {
|
||||
case let .http(userName, password):
|
||||
print(Defaults[.gitPasswordAttempts])
|
||||
var newPassword: String = password
|
||||
if Defaults[.gitPasswordAttempts] != 0 {
|
||||
let sem = DispatchSemaphore(value: 0)
|
||||
DispatchQueue.main.async {
|
||||
SVProgressHUD.dismiss()
|
||||
if var topController = UIApplication.shared.keyWindow?.rootViewController {
|
||||
while let presentedViewController = topController.presentedViewController {
|
||||
topController = presentedViewController
|
||||
}
|
||||
let alert = UIAlertController(title: "Password", message: "Please fill in the password of your Git account.", preferredStyle: UIAlertControllerStyle.alert)
|
||||
alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: {_ in
|
||||
newPassword = alert.textFields!.first!.text!
|
||||
PasswordStore.shared.gitPassword = newPassword
|
||||
sem.signal()
|
||||
}))
|
||||
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel) { _ in
|
||||
Defaults[.gitPasswordAttempts] = -1
|
||||
sem.signal()
|
||||
})
|
||||
alert.addTextField(configurationHandler: {(textField: UITextField!) in
|
||||
textField.text = PasswordStore.shared.gitPassword
|
||||
textField.isSecureTextEntry = true
|
||||
})
|
||||
topController.present(alert, animated: true, completion: nil)
|
||||
}
|
||||
case let .http(userName, controller):
|
||||
var newPassword = Utils.getPasswordFromKeychain(name: "gitPassword")
|
||||
if newPassword == nil || attempts != 0 {
|
||||
if let requestedPassword = self.requestGitPassword(controller, lastPassword) {
|
||||
newPassword = requestedPassword
|
||||
Utils.addPasswordToKeychain(name: "gitPassword", password: newPassword)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
let _ = sem.wait(timeout: DispatchTime.distantFuture)
|
||||
}
|
||||
if Defaults[.gitPasswordAttempts] == -1 {
|
||||
Defaults[.gitPasswordAttempts] = 0
|
||||
return nil
|
||||
attempts += 1
|
||||
lastPassword = newPassword
|
||||
credential = try? GTCredential(userName: userName, password: newPassword!)
|
||||
case let .ssh(userName, publicKeyFile, privateKeyFile, controller):
|
||||
var newPassword = Utils.getPasswordFromKeychain(name: "gitSSHKeyPassphrase")
|
||||
if newPassword == nil || attempts != 0 {
|
||||
if let requestedPassword = self.requestGitPassword(controller, lastPassword) {
|
||||
newPassword = requestedPassword
|
||||
Utils.addPasswordToKeychain(name: "gitSSHKeyPassphrase", password: newPassword)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
Defaults[.gitPasswordAttempts] += 1
|
||||
PasswordStore.shared.gitPassword = newPassword
|
||||
credential = try? GTCredential(userName: userName, password: newPassword)
|
||||
case let .ssh(userName, password, publicKeyFile, privateKeyFile, passwordNotSetCallback):
|
||||
|
||||
var newPassword:String? = password
|
||||
|
||||
// Check if the private key is encrypted
|
||||
let encrypted = try? String(contentsOf: privateKeyFile).contains("ENCRYPTED")
|
||||
|
||||
// Request password if not already set
|
||||
if encrypted! && password == "" {
|
||||
newPassword = passwordNotSetCallback!()
|
||||
}
|
||||
|
||||
// Save password for the future
|
||||
Utils.addPasswordToKeychain(name: "gitSSHPrivateKeyPassphrase", password: newPassword!)
|
||||
|
||||
// nil is expected in case of empty password
|
||||
if newPassword == "" {
|
||||
newPassword = nil
|
||||
}
|
||||
|
||||
|
||||
credential = try? GTCredential(userName: userName, publicKeyURL: publicKeyFile, privateKeyURL: privateKeyFile, passphrase: newPassword)
|
||||
attempts += 1
|
||||
lastPassword = newPassword
|
||||
credential = try? GTCredential(userName: userName, publicKeyURL: publicKeyFile, privateKeyURL: privateKeyFile, passphrase: newPassword!)
|
||||
}
|
||||
return credential
|
||||
}
|
||||
}
|
||||
|
||||
func delete() {
|
||||
switch credential {
|
||||
case .http:
|
||||
Utils.removeKeychain(name: "gitPassword")
|
||||
case .ssh:
|
||||
Utils.removeKeychain(name: "gitSSHKeyPassphrase")
|
||||
}
|
||||
}
|
||||
|
||||
private func requestGitPassword(_ controller: UIViewController, _ lastPassword: String?) -> String? {
|
||||
let sem = DispatchSemaphore(value: 0)
|
||||
var password: String?
|
||||
var message = ""
|
||||
switch credential {
|
||||
case .http:
|
||||
message = "Please fill in the password of your Git account."
|
||||
case .ssh:
|
||||
message = "Please fill in the password of your SSH key."
|
||||
}
|
||||
|
||||
DispatchQueue.main.async {
|
||||
SVProgressHUD.dismiss()
|
||||
let alert = UIAlertController(title: "Password", message: message, preferredStyle: UIAlertControllerStyle.alert)
|
||||
alert.addTextField(configurationHandler: {(textField: UITextField!) in
|
||||
textField.text = lastPassword ?? ""
|
||||
textField.isSecureTextEntry = true
|
||||
})
|
||||
alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: {_ in
|
||||
password = alert.textFields!.first!.text
|
||||
sem.signal()
|
||||
}))
|
||||
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel) { _ in
|
||||
password = nil
|
||||
sem.signal()
|
||||
})
|
||||
controller.present(alert, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
let _ = sem.wait(timeout: .distantFuture)
|
||||
return password
|
||||
}
|
||||
}
|
||||
|
||||
class PasswordStore {
|
||||
|
|
@ -97,7 +112,6 @@ class PasswordStore {
|
|||
let tempStoreURL = URL(fileURLWithPath: "\(Globals.repositoryPath)-temp")
|
||||
|
||||
var storeRepository: GTRepository?
|
||||
var gitCredential: GitCredential?
|
||||
var pgpKeyID: String?
|
||||
var publicKey: PGPKey? {
|
||||
didSet {
|
||||
|
|
@ -112,7 +126,9 @@ class PasswordStore {
|
|||
|
||||
var gitSignatureForNow: GTSignature {
|
||||
get {
|
||||
return GTSignature(name: Defaults[.gitUsername]!, email: Defaults[.gitUsername]!+"@passforios", time: Date())!
|
||||
let gitSignatureName = Defaults[.gitSignatureName] ?? Globals.gitSignatureDefaultName
|
||||
let gitSignatureEmail = Defaults[.gitSignatureEmail] ?? Globals.gitSignatureDefaultEmail
|
||||
return GTSignature(name: gitSignatureName, email: gitSignatureEmail, time: Date())!
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -126,6 +142,7 @@ class PasswordStore {
|
|||
return Utils.getPasswordFromKeychain(name: "pgpKeyPassphrase")
|
||||
}
|
||||
}
|
||||
|
||||
var gitPassword: String? {
|
||||
set {
|
||||
Utils.addPasswordToKeychain(name: "gitPassword", password: newValue)
|
||||
|
|
@ -140,7 +157,7 @@ class PasswordStore {
|
|||
Utils.addPasswordToKeychain(name: "gitSSHPrivateKeyPassphrase", password: newValue)
|
||||
}
|
||||
get {
|
||||
return Utils.getPasswordFromKeychain(name: "gitSSHPrivateKeyPassphrase") ?? ""
|
||||
return Utils.getPasswordFromKeychain(name: "gitSSHPrivateKeyPassphrase")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -173,31 +190,12 @@ class PasswordStore {
|
|||
print(error)
|
||||
}
|
||||
initPGPKeys()
|
||||
initGitCredential()
|
||||
}
|
||||
|
||||
enum SSHKeyType {
|
||||
case `public`, secret
|
||||
}
|
||||
|
||||
public func initGitCredential() {
|
||||
if Defaults[.gitAuthenticationMethod] == "Password" {
|
||||
gitCredential = GitCredential(credential: GitCredential.Credential.http(userName: Defaults[.gitUsername]!, password: Utils.getPasswordFromKeychain(name: "gitPassword") ?? ""))
|
||||
} else if Defaults[.gitAuthenticationMethod] == "SSH Key"{
|
||||
gitCredential = GitCredential(
|
||||
credential: GitCredential.Credential.ssh(
|
||||
userName: Defaults[.gitUsername]!,
|
||||
password: gitSSHPrivateKeyPassphrase ?? "",
|
||||
publicKeyFile: Globals.gitSSHPublicKeyURL,
|
||||
privateKeyFile: Globals.gitSSHPrivateKeyURL,
|
||||
passwordNotSetCallback: nil
|
||||
)
|
||||
)
|
||||
} else {
|
||||
gitCredential = nil
|
||||
}
|
||||
}
|
||||
|
||||
public func initGitSSHKey(with armorKey: String, _ keyType: SSHKeyType) throws {
|
||||
var keyPath = ""
|
||||
switch keyType {
|
||||
|
|
@ -282,10 +280,9 @@ class PasswordStore {
|
|||
}
|
||||
|
||||
func passwordExisted(password: Password) -> Bool {
|
||||
print(password.name)
|
||||
let passwordEntityFetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "PasswordEntity")
|
||||
do {
|
||||
passwordEntityFetchRequest.predicate = NSPredicate(format: "name = %@", password.name)
|
||||
passwordEntityFetchRequest.predicate = NSPredicate(format: "name = %@ and path = %@", password.name, password.url!.path)
|
||||
let count = try context.count(for: passwordEntityFetchRequest)
|
||||
if count > 0 {
|
||||
return true
|
||||
|
|
@ -298,49 +295,79 @@ class PasswordStore {
|
|||
return true
|
||||
}
|
||||
|
||||
func passwordEntityExisted(path: String) -> Bool {
|
||||
let passwordEntityFetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "PasswordEntity")
|
||||
do {
|
||||
passwordEntityFetchRequest.predicate = NSPredicate(format: "path = %@", path)
|
||||
let count = try context.count(for: passwordEntityFetchRequest)
|
||||
if count > 0 {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
} catch {
|
||||
fatalError("Failed to fetch password entities: \(error)")
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func getPasswordEntity(by path: String, isDir: Bool) -> PasswordEntity? {
|
||||
let passwordEntityFetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "PasswordEntity")
|
||||
do {
|
||||
passwordEntityFetchRequest.predicate = NSPredicate(format: "path = %@ and isDir = %@", path, isDir.description)
|
||||
return try context.fetch(passwordEntityFetchRequest).first as? PasswordEntity
|
||||
} catch {
|
||||
fatalError("Failed to fetch password entities: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
func cloneRepository(remoteRepoURL: URL,
|
||||
credential: GitCredential,
|
||||
transferProgressBlock: @escaping (UnsafePointer<git_transfer_progress>, UnsafeMutablePointer<ObjCBool>) -> Void,
|
||||
checkoutProgressBlock: @escaping (String?, UInt, UInt) -> Void) throws {
|
||||
Utils.removeFileIfExists(at: storeURL)
|
||||
Utils.removeFileIfExists(at: tempStoreURL)
|
||||
|
||||
let credentialProvider = try credential.credentialProvider()
|
||||
let options: [String: Any] = [
|
||||
GTRepositoryCloneOptionsCredentialProvider: credentialProvider,
|
||||
]
|
||||
storeRepository = try GTRepository.clone(from: remoteRepoURL, toWorkingDirectory: tempStoreURL, options: options, transferProgressBlock:transferProgressBlock)
|
||||
let fm = FileManager.default
|
||||
do {
|
||||
let credentialProvider = try credential.credentialProvider()
|
||||
let options: [String: Any] = [
|
||||
GTRepositoryCloneOptionsCredentialProvider: credentialProvider,
|
||||
]
|
||||
storeRepository = try GTRepository.clone(from: remoteRepoURL, toWorkingDirectory: tempStoreURL, options: options, transferProgressBlock:transferProgressBlock)
|
||||
let fm = FileManager.default
|
||||
if fm.fileExists(atPath: storeURL.path) {
|
||||
try fm.removeItem(at: storeURL)
|
||||
}
|
||||
try fm.copyItem(at: tempStoreURL, to: storeURL)
|
||||
try fm.removeItem(at: tempStoreURL)
|
||||
storeRepository = try GTRepository(url: storeURL)
|
||||
} catch {
|
||||
print(error)
|
||||
credential.delete()
|
||||
throw(error)
|
||||
}
|
||||
storeRepository = try GTRepository(url: storeURL)
|
||||
gitCredential = credential
|
||||
Defaults[.lastSyncedTime] = Date()
|
||||
DispatchQueue.main.async {
|
||||
Defaults[.lastSyncedTime] = Date()
|
||||
self.updatePasswordEntityCoreData()
|
||||
NotificationCenter.default.post(name: .passwordStoreUpdated, object: nil)
|
||||
}
|
||||
}
|
||||
|
||||
func pullRepository(transferProgressBlock: @escaping (UnsafePointer<git_transfer_progress>, UnsafeMutablePointer<ObjCBool>) -> Void) throws {
|
||||
if gitCredential == nil {
|
||||
func pullRepository(credential: GitCredential, transferProgressBlock: @escaping (UnsafePointer<git_transfer_progress>, UnsafeMutablePointer<ObjCBool>) -> Void) throws {
|
||||
if storeRepository == nil {
|
||||
throw NSError(domain: "me.mssun.pass.error", code: 1, userInfo: [NSLocalizedDescriptionKey: "Git Repository is not set."])
|
||||
}
|
||||
let credentialProvider = try gitCredential!.credentialProvider()
|
||||
let options: [String: Any] = [
|
||||
GTRepositoryRemoteOptionsCredentialProvider: credentialProvider
|
||||
]
|
||||
let remote = try GTRemote(name: "origin", in: storeRepository!)
|
||||
try storeRepository?.pull((storeRepository?.currentBranch())!, from: remote, withOptions: options, progress: transferProgressBlock)
|
||||
Defaults[.lastSyncedTime] = Date()
|
||||
do {
|
||||
let credentialProvider = try credential.credentialProvider()
|
||||
let options: [String: Any] = [
|
||||
GTRepositoryRemoteOptionsCredentialProvider: credentialProvider
|
||||
]
|
||||
let remote = try GTRemote(name: "origin", in: storeRepository!)
|
||||
try storeRepository!.pull((storeRepository?.currentBranch())!, from: remote, withOptions: options, progress: transferProgressBlock)
|
||||
} catch {
|
||||
credential.delete()
|
||||
throw(error)
|
||||
}
|
||||
DispatchQueue.main.async {
|
||||
Defaults[.lastSyncedTime] = Date()
|
||||
self.setAllSynced()
|
||||
self.updatePasswordEntityCoreData()
|
||||
NotificationCenter.default.post(name: .passwordStoreUpdated, object: nil)
|
||||
|
|
@ -509,45 +536,69 @@ class PasswordStore {
|
|||
func updateRemoteRepo() {
|
||||
}
|
||||
|
||||
func createAddCommitInRepository(message: String, fileData: Data, filename: String, progressBlock: (_ progress: Float) -> Void) -> GTCommit? {
|
||||
private func gitAdd(path: String) throws {
|
||||
if let repo = storeRepository {
|
||||
try repo.index().addFile(path)
|
||||
try repo.index().write()
|
||||
}
|
||||
}
|
||||
|
||||
private func gitRm(path: String) throws {
|
||||
if let repo = storeRepository {
|
||||
if FileManager.default.fileExists(atPath: storeURL.appendingPathComponent(path).path) {
|
||||
try FileManager.default.removeItem(at: storeURL.appendingPathComponent(path))
|
||||
}
|
||||
try repo.index().removeFile(path)
|
||||
try repo.index().write()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private func deleteDirectoryTree(at url: URL) throws {
|
||||
var tempURL = storeURL.appendingPathComponent(url.deletingLastPathComponent().path)
|
||||
let fm = FileManager.default
|
||||
var count = try fm.contentsOfDirectory(atPath: tempURL.path).count
|
||||
while count == 0 {
|
||||
try fm.removeItem(at: tempURL)
|
||||
tempURL.deleteLastPathComponent()
|
||||
count = try fm.contentsOfDirectory(atPath: tempURL.path).count
|
||||
}
|
||||
}
|
||||
|
||||
private func createDirectoryTree(at url: URL) throws {
|
||||
let tempURL = storeURL.appendingPathComponent(url.deletingLastPathComponent().path)
|
||||
try FileManager.default.createDirectory(at: tempURL, withIntermediateDirectories: true, attributes: nil)
|
||||
}
|
||||
|
||||
private func gitMv(from: String, to: String) throws {
|
||||
let fm = FileManager.default
|
||||
do {
|
||||
try storeRepository?.index().add(fileData, withPath: filename)
|
||||
try storeRepository?.index().write()
|
||||
let newTree = try storeRepository!.index().writeTree()
|
||||
let headReference = try storeRepository!.headReference()
|
||||
let commitEnum = try GTEnumerator(repository: storeRepository!)
|
||||
try commitEnum.pushSHA(headReference.targetOID.sha!)
|
||||
let parent = commitEnum.nextObject() as! GTCommit
|
||||
progressBlock(0.5)
|
||||
let signature = gitSignatureForNow
|
||||
let commit = try storeRepository!.createCommit(with: newTree, message: message, author: signature, committer: signature, parents: [parent], updatingReferenceNamed: headReference.name)
|
||||
progressBlock(0.7)
|
||||
return commit
|
||||
guard fm.fileExists(atPath: storeURL.appendingPathComponent(from).path) else {
|
||||
print("\(from) not exist")
|
||||
return
|
||||
}
|
||||
try fm.moveItem(at: storeURL.appendingPathComponent(from), to: storeURL.appendingPathComponent(to))
|
||||
} catch {
|
||||
print(error)
|
||||
}
|
||||
return nil
|
||||
try gitAdd(path: to)
|
||||
try gitRm(path: from)
|
||||
}
|
||||
|
||||
func createRemoveCommitInRepository(message: String, path: String) -> GTCommit? {
|
||||
do {
|
||||
try storeRepository?.index().removeFile(path)
|
||||
try storeRepository?.index().write()
|
||||
let newTree = try storeRepository!.index().writeTree()
|
||||
let headReference = try storeRepository!.headReference()
|
||||
let commitEnum = try GTEnumerator(repository: storeRepository!)
|
||||
private func gitCommit(message: String) throws -> GTCommit? {
|
||||
if let repo = storeRepository {
|
||||
let newTree = try repo.index().writeTree()
|
||||
let headReference = try repo.headReference()
|
||||
let commitEnum = try GTEnumerator(repository: repo)
|
||||
try commitEnum.pushSHA(headReference.targetOID.sha!)
|
||||
let parent = commitEnum.nextObject() as! GTCommit
|
||||
let signature = gitSignatureForNow
|
||||
let commit = try storeRepository!.createCommit(with: newTree, message: message, author: signature, committer: signature, parents: [parent], updatingReferenceNamed: headReference.name)
|
||||
let commit = try repo.createCommit(with: newTree, message: message, author: signature, committer: signature, parents: [parent], updatingReferenceNamed: headReference.name)
|
||||
return commit
|
||||
} catch {
|
||||
print(error)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
private func getLocalBranch(withName branchName: String) -> GTBranch? {
|
||||
do {
|
||||
let reference = GTBranch.localNamePrefix().appending(branchName)
|
||||
|
|
@ -559,67 +610,133 @@ class PasswordStore {
|
|||
return nil
|
||||
}
|
||||
|
||||
func pushRepository(transferProgressBlock: @escaping (UInt32, UInt32, Int, UnsafeMutablePointer<ObjCBool>) -> Void) throws {
|
||||
let credentialProvider = try gitCredential!.credentialProvider()
|
||||
let options: [String: Any] = [
|
||||
GTRepositoryRemoteOptionsCredentialProvider: credentialProvider,
|
||||
]
|
||||
let masterBranch = getLocalBranch(withName: "master")!
|
||||
let remote = try GTRemote(name: "origin", in: storeRepository!)
|
||||
try storeRepository?.push(masterBranch, to: remote, withOptions: options, progress: transferProgressBlock)
|
||||
func pushRepository(credential: GitCredential, transferProgressBlock: @escaping (UInt32, UInt32, Int, UnsafeMutablePointer<ObjCBool>) -> Void) throws {
|
||||
do {
|
||||
let credentialProvider = try credential.credentialProvider()
|
||||
let options: [String: Any] = [
|
||||
GTRepositoryRemoteOptionsCredentialProvider: credentialProvider,
|
||||
]
|
||||
let masterBranch = getLocalBranch(withName: "master")!
|
||||
let remote = try GTRemote(name: "origin", in: storeRepository!)
|
||||
try storeRepository?.push(masterBranch, to: remote, withOptions: options, progress: transferProgressBlock)
|
||||
} catch {
|
||||
credential.delete()
|
||||
throw(error)
|
||||
}
|
||||
}
|
||||
|
||||
func add(password: Password, progressBlock: (_ progress: Float) -> Void) throws {
|
||||
progressBlock(0.0)
|
||||
private func addPasswordEntities(password: Password) throws -> PasswordEntity? {
|
||||
guard !passwordExisted(password: password) else {
|
||||
throw NSError(domain: "me.mssun.pass.error", code: 2, userInfo: [NSLocalizedDescriptionKey: "Cannot add password: password duplicated."])
|
||||
}
|
||||
let passwordEntity = NSEntityDescription.insertNewObject(forEntityName: "PasswordEntity", into: context) as! PasswordEntity
|
||||
do {
|
||||
let encryptedData = try passwordEntity.encrypt(password: password)
|
||||
progressBlock(0.3)
|
||||
let saveURL = storeURL.appendingPathComponent("\(password.name).gpg")
|
||||
try encryptedData.write(to: saveURL)
|
||||
passwordEntity.name = password.name
|
||||
passwordEntity.path = "\(password.name).gpg"
|
||||
passwordEntity.parent = nil
|
||||
passwordEntity.synced = false
|
||||
passwordEntity.isDir = false
|
||||
try context.save()
|
||||
print(saveURL.path)
|
||||
let _ = createAddCommitInRepository(message: "Add password for \(passwordEntity.nameWithCategory) to store using Pass for iOS.", fileData: encryptedData, filename: saveURL.lastPathComponent, progressBlock: progressBlock)
|
||||
progressBlock(1.0)
|
||||
NotificationCenter.default.post(name: .passwordStoreUpdated, object: nil)
|
||||
} catch {
|
||||
print(error)
|
||||
|
||||
var passwordURL = password.url!
|
||||
var paths: [String] = []
|
||||
while passwordURL.path != "." {
|
||||
paths.append(passwordURL.path)
|
||||
passwordURL = passwordURL.deletingLastPathComponent()
|
||||
}
|
||||
paths.reverse()
|
||||
var parentPasswordEntity: PasswordEntity? = nil
|
||||
for path in paths {
|
||||
let isDir = !path.hasSuffix(".gpg")
|
||||
if let passwordEntity = getPasswordEntity(by: path, isDir: isDir) {
|
||||
print(passwordEntity.path!)
|
||||
parentPasswordEntity = passwordEntity
|
||||
} else {
|
||||
if !isDir {
|
||||
return insertPasswordEntity(name: URL(string: path.stringByAddingPercentEncodingForRFC3986()!)!.deletingPathExtension().lastPathComponent, path: path, parent: parentPasswordEntity, synced: false, isDir: false)
|
||||
} else {
|
||||
parentPasswordEntity = insertPasswordEntity(name: URL(string: path.stringByAddingPercentEncodingForRFC3986()!)!.lastPathComponent, path: path, parent: parentPasswordEntity, synced: false, isDir: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func update(passwordEntity: PasswordEntity, password: Password, progressBlock: (_ progress: Float) -> Void) {
|
||||
progressBlock(0.0)
|
||||
do {
|
||||
let encryptedData = try passwordEntity.encrypt(password: password)
|
||||
let saveURL = storeURL.appendingPathComponent(passwordEntity.path!)
|
||||
try encryptedData.write(to: saveURL)
|
||||
progressBlock(0.3)
|
||||
let _ = createAddCommitInRepository(message: "Edit password for \(passwordEntity.nameWithCategory) using Pass for iOS.", fileData: encryptedData, filename: saveURL.lastPathComponent, progressBlock: progressBlock)
|
||||
progressBlock(1.0)
|
||||
NotificationCenter.default.post(name: .passwordStoreUpdated, object: nil)
|
||||
} catch {
|
||||
print(error)
|
||||
private func insertPasswordEntity(name: String, path: String, parent: PasswordEntity?, synced: Bool = false, isDir: Bool = false) -> PasswordEntity? {
|
||||
var ret: PasswordEntity? = nil
|
||||
if let passwordEntity = NSEntityDescription.insertNewObject(forEntityName: "PasswordEntity", into: self.context) as? PasswordEntity {
|
||||
passwordEntity.name = name
|
||||
passwordEntity.path = path
|
||||
passwordEntity.parent = parent
|
||||
passwordEntity.synced = synced
|
||||
passwordEntity.isDir = isDir
|
||||
do {
|
||||
try self.context.save()
|
||||
ret = passwordEntity
|
||||
} catch {
|
||||
fatalError("Failed to insert a PasswordEntity: \(error)")
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
public func delete(passwordEntity: PasswordEntity) {
|
||||
Utils.removeFileIfExists(at: storeURL.appendingPathComponent(passwordEntity.path!))
|
||||
let _ = createRemoveCommitInRepository(message: "Remove \(passwordEntity.nameWithCategory) from store using Pass for iOS", path: passwordEntity.path!)
|
||||
context.delete(passwordEntity)
|
||||
do {
|
||||
try context.save()
|
||||
} catch {
|
||||
fatalError("Failed to delete a PasswordEntity: \(error)")
|
||||
func add(password: Password) throws -> PasswordEntity? {
|
||||
try createDirectoryTree(at: password.url!)
|
||||
let newPasswordEntity = try addPasswordEntities(password: password)
|
||||
let saveURL = storeURL.appendingPathComponent(password.url!.path)
|
||||
try self.encrypt(password: password).write(to: saveURL)
|
||||
try gitAdd(path: password.url!.path)
|
||||
let _ = try gitCommit(message: "Add password for \(password.url!.deletingPathExtension().path) to store using Pass for iOS.")
|
||||
NotificationCenter.default.post(name: .passwordStoreUpdated, object: nil)
|
||||
return newPasswordEntity
|
||||
}
|
||||
|
||||
public func delete(passwordEntity: PasswordEntity) throws {
|
||||
let deletedFileURL = passwordEntity.getURL()!
|
||||
try deleteDirectoryTree(at: passwordEntity.getURL()!)
|
||||
try deletePasswordEntities(passwordEntity: passwordEntity)
|
||||
try gitRm(path: deletedFileURL.path)
|
||||
let _ = try gitCommit(message: "Remove \(deletedFileURL.deletingPathExtension().path.removingPercentEncoding!) from store using Pass for iOS.")
|
||||
NotificationCenter.default.post(name: .passwordStoreUpdated, object: nil)
|
||||
}
|
||||
|
||||
func edit(passwordEntity: PasswordEntity, password: Password) throws -> PasswordEntity? {
|
||||
var newPasswordEntity: PasswordEntity? = passwordEntity
|
||||
|
||||
if password.changed&PasswordChange.content.rawValue != 0 {
|
||||
print("chagne content")
|
||||
let saveURL = storeURL.appendingPathComponent(passwordEntity.getURL()!.path)
|
||||
try self.encrypt(password: password).write(to: saveURL)
|
||||
try gitAdd(path: passwordEntity.getURL()!.path)
|
||||
let _ = try gitCommit(message: "Edit password for \(passwordEntity.getURL()!.deletingPathExtension().path.removingPercentEncoding!) to store using Pass for iOS.")
|
||||
newPasswordEntity = passwordEntity
|
||||
}
|
||||
|
||||
if password.changed&PasswordChange.path.rawValue != 0 {
|
||||
print("change path")
|
||||
let deletedFileURL = passwordEntity.getURL()!
|
||||
// add
|
||||
try createDirectoryTree(at: password.url!)
|
||||
newPasswordEntity = try addPasswordEntities(password: password)
|
||||
|
||||
// mv
|
||||
try gitMv(from: deletedFileURL.path, to: password.url!.path)
|
||||
|
||||
// delete
|
||||
try deleteDirectoryTree(at: deletedFileURL)
|
||||
try deletePasswordEntities(passwordEntity: passwordEntity)
|
||||
let _ = try gitCommit(message: "Rename \(deletedFileURL.deletingPathExtension().path.removingPercentEncoding!) to \(password.url!.deletingPathExtension().path.removingPercentEncoding!) using Pass for iOS.")
|
||||
|
||||
}
|
||||
NotificationCenter.default.post(name: .passwordStoreUpdated, object: nil)
|
||||
return newPasswordEntity
|
||||
}
|
||||
|
||||
private func deletePasswordEntities(passwordEntity: PasswordEntity) throws {
|
||||
var current: PasswordEntity? = passwordEntity
|
||||
print(passwordEntity.path!)
|
||||
while current != nil && (current!.children!.count == 0 || !current!.isDir) {
|
||||
let parent = current!.parent
|
||||
self.context.delete(current!)
|
||||
current = parent
|
||||
do {
|
||||
try self.context.save()
|
||||
} catch {
|
||||
fatalError("Failed to delete a PasswordEntity: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func saveUpdated(passwordEntity: PasswordEntity) {
|
||||
|
|
@ -703,7 +820,6 @@ class PasswordStore {
|
|||
try self.storeRepository?.reset(to: newHead, resetType: GTRepositoryResetType.hard)
|
||||
self.setAllSynced()
|
||||
self.updatePasswordEntityCoreData()
|
||||
Defaults[.lastSyncedTime] = nil
|
||||
|
||||
NotificationCenter.default.post(name: .passwordStoreUpdated, object: nil)
|
||||
NotificationCenter.default.post(name: .passwordStoreChangeDiscarded, object: nil)
|
||||
|
|
@ -739,4 +855,28 @@ class PasswordStore {
|
|||
// get a list of local commits
|
||||
return try storeRepository?.localCommitsRelative(toRemoteBranch: remoteMasterBranch)
|
||||
}
|
||||
|
||||
|
||||
|
||||
func decrypt(passwordEntity: PasswordEntity, requestPGPKeyPassphrase: () -> String) throws -> Password? {
|
||||
var password: Password?
|
||||
let encryptedDataPath = URL(fileURLWithPath: "\(Globals.repositoryPath)/\(passwordEntity.path!)")
|
||||
let encryptedData = try Data(contentsOf: encryptedDataPath)
|
||||
var passphrase = self.pgpKeyPassphrase
|
||||
if passphrase == nil {
|
||||
passphrase = requestPGPKeyPassphrase()
|
||||
}
|
||||
let decryptedData = try PasswordStore.shared.pgp.decryptData(encryptedData, passphrase: passphrase)
|
||||
let plainText = String(data: decryptedData, encoding: .utf8) ?? ""
|
||||
let escapedPath = passwordEntity.path!.stringByAddingPercentEncodingForRFC3986() ?? ""
|
||||
password = Password(name: passwordEntity.name!, url: URL(string: escapedPath), plainText: plainText)
|
||||
return password
|
||||
}
|
||||
|
||||
func encrypt(password: Password) throws -> Data {
|
||||
let plainData = password.getPlainData()
|
||||
let pgp = PasswordStore.shared.pgp
|
||||
let encryptedData = try pgp.encryptData(plainData, usingPublicKey: pgp.getKeysOf(.public)[0], armored: Defaults[.encryptInArmored])
|
||||
return encryptedData
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 126 KiB After Width: | Height: | Size: 150 KiB |
|
Before Width: | Height: | Size: 182 KiB After Width: | Height: | Size: 206 KiB |
|
Before Width: | Height: | Size: 99 KiB After Width: | Height: | Size: 137 KiB |
|
Before Width: | Height: | Size: 170 KiB After Width: | Height: | Size: 210 KiB |
|
Before Width: | Height: | Size: 144 KiB After Width: | Height: | Size: 152 KiB |