2017-01-19 21:15:47 +08:00
//
// P a s s w o r d S t o r e . s w i f t
// p a s s
//
// C r e a t e d b y M i n g s h e n S u n o n 1 9 / 1 / 2 0 1 7 .
// C o p y r i g h t © 2 0 1 7 B o b S u n . A l l r i g h t s r e s e r v e d .
//
import Foundation
import CoreData
import UIKit
2017-01-22 01:42:36 +08:00
import SwiftyUserDefaults
2017-01-23 16:29:36 +08:00
import ObjectiveGit
2017-02-17 20:14:01 +08:00
import SVProgressHUD
2017-01-19 21:15:47 +08:00
2017-01-24 01:49:55 +08:00
struct GitCredential {
enum Credential {
case http ( userName : String , password : String )
2017-02-23 16:34:22 +03:00
case ssh ( userName : String , password : String , publicKeyFile : URL , privateKeyFile : URL , passwordNotSetCallback : ( ( ) -> String ) ? )
2017-01-24 01:49:55 +08:00
}
var credential : Credential
2017-02-23 16:34:22 +03:00
2017-01-24 01:49:55 +08:00
func credentialProvider ( ) throws -> GTCredentialProvider {
2017-02-17 20:14:01 +08:00
return GTCredentialProvider { ( _ , _ , _ ) -> ( GTCredential ? ) in
var credential : GTCredential ? = nil
2017-01-24 01:49:55 +08:00
switch self . credential {
case let . http ( userName , password ) :
2017-04-02 11:21:24 -07:00
print ( Defaults [ . gitPasswordAttempts ] )
2017-02-17 20:14:01 +08:00
var newPassword : String = password
2017-04-02 11:21:24 -07:00
if Defaults [ . gitPasswordAttempts ] != 0 {
2017-02-17 20:14:01 +08:00
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 !
2017-04-02 11:21:24 -07:00
PasswordStore . shared . gitPassword = newPassword
2017-02-17 20:14:01 +08:00
sem . signal ( )
} ) )
2017-02-26 21:12:17 +08:00
alert . addAction ( UIAlertAction ( title : " Cancel " , style : . cancel ) { _ in
2017-04-02 11:21:24 -07:00
Defaults [ . gitPasswordAttempts ] = - 1
2017-02-26 21:12:17 +08:00
sem . signal ( )
} )
2017-02-17 20:14:01 +08:00
alert . addTextField ( configurationHandler : { ( textField : UITextField ! ) in
2017-04-02 11:21:24 -07:00
textField . text = PasswordStore . shared . gitPassword
2017-02-17 20:14:01 +08:00
textField . isSecureTextEntry = true
} )
topController . present ( alert , animated : true , completion : nil )
}
}
let _ = sem . wait ( timeout : DispatchTime . distantFuture )
}
2017-04-02 11:21:24 -07:00
if Defaults [ . gitPasswordAttempts ] = = - 1 {
Defaults [ . gitPasswordAttempts ] = 0
2017-02-17 20:14:01 +08:00
return nil
}
2017-04-02 11:21:24 -07:00
Defaults [ . gitPasswordAttempts ] += 1
PasswordStore . shared . gitPassword = newPassword
2017-02-17 20:14:01 +08:00
credential = try ? GTCredential ( userName : userName , password : newPassword )
2017-02-23 16:34:22 +03:00
case let . ssh ( userName , password , publicKeyFile , privateKeyFile , passwordNotSetCallback ) :
var newPassword : String ? = password
// C h e c k i f t h e p r i v a t e k e y i s e n c r y p t e d
let encrypted = try ? String ( contentsOf : privateKeyFile ) . contains ( " ENCRYPTED " )
// R e q u e s t p a s s w o r d i f n o t a l r e a d y s e t
2017-04-23 10:16:50 -07:00
if encrypted = = nil && password = = " " {
2017-02-23 16:34:22 +03:00
newPassword = passwordNotSetCallback ! ( )
}
2017-02-23 17:34:55 +03:00
// S a v e p a s s w o r d f o r t h e f u t u r e
2017-04-02 11:21:24 -07:00
Utils . addPasswordToKeychain ( name : " gitSSHPrivateKeyPassphrase " , password : newPassword ! )
2017-02-23 17:34:55 +03:00
2017-02-23 16:34:22 +03:00
// n i l i s e x p e c t e d i n c a s e o f e m p t y p a s s w o r d
if newPassword = = " " {
newPassword = nil
}
credential = try ? GTCredential ( userName : userName , publicKeyURL : publicKeyFile , privateKeyURL : privateKeyFile , passphrase : newPassword )
2017-01-24 01:49:55 +08:00
}
2017-02-17 20:14:01 +08:00
return credential
2017-01-24 01:49:55 +08:00
}
}
}
2017-01-19 21:15:47 +08:00
class PasswordStore {
static let shared = PasswordStore ( )
2017-02-24 20:16:11 +03:00
let storeURL = URL ( fileURLWithPath : " \( Globals . repositoryPath ) " )
let tempStoreURL = URL ( fileURLWithPath : " \( Globals . repositoryPath ) -temp " )
2017-03-16 22:06:39 -07:00
2017-01-23 16:29:36 +08:00
var storeRepository : GTRepository ?
2017-02-02 15:02:57 +08:00
var gitCredential : GitCredential ?
2017-03-16 22:06:39 -07:00
var pgpKeyID : String ?
var publicKey : PGPKey ? {
didSet {
if publicKey != nil {
pgpKeyID = publicKey ! . keyID ! . shortKeyString
} else {
pgpKeyID = nil
}
}
}
var privateKey : PGPKey ?
2017-03-16 00:38:38 +08:00
var gitSignatureForNow : GTSignature {
get {
2017-04-23 10:16:50 -07:00
let name = Defaults [ . gitName ] ? ? Defaults [ . gitUsername ] ? ? " "
let email = Defaults [ . gitEmail ] ? ? ( Defaults [ . gitUsername ] ? ? " " + " @passforios " )
2017-04-10 23:02:42 +08:00
return GTSignature ( name : name , email : email , time : Date ( ) ) !
2017-03-16 00:38:38 +08:00
}
}
2017-01-23 16:29:36 +08:00
2017-01-22 01:42:36 +08:00
let pgp : ObjectivePGP = ObjectivePGP ( )
2017-01-19 21:15:47 +08:00
2017-02-19 22:10:36 +08:00
var pgpKeyPassphrase : String ? {
2017-02-20 11:48:39 +08:00
set {
2017-03-06 12:49:00 -08:00
Utils . addPasswordToKeychain ( name : " pgpKeyPassphrase " , password : newValue )
2017-02-20 11:48:39 +08:00
}
get {
return Utils . getPasswordFromKeychain ( name : " pgpKeyPassphrase " )
2017-02-19 22:10:36 +08:00
}
}
2017-04-02 11:21:24 -07:00
var gitPassword : String ? {
2017-02-20 11:48:39 +08:00
set {
2017-04-02 11:21:24 -07:00
Utils . addPasswordToKeychain ( name : " gitPassword " , password : newValue )
2017-02-20 11:48:39 +08:00
}
get {
2017-04-02 11:21:24 -07:00
return Utils . getPasswordFromKeychain ( name : " gitPassword " )
}
}
var gitSSHPrivateKeyPassphrase : String ? {
set {
Utils . addPasswordToKeychain ( name : " gitSSHPrivateKeyPassphrase " , password : newValue )
}
get {
return Utils . getPasswordFromKeychain ( name : " gitSSHPrivateKeyPassphrase " ) ? ? " "
2017-02-19 22:10:36 +08:00
}
}
2017-01-19 21:15:47 +08:00
let context = ( UIApplication . shared . delegate as ! AppDelegate ) . persistentContainer . viewContext
2017-03-24 22:05:09 +08:00
var numberOfPasswords : Int {
return self . fetchPasswordEntityCoreData ( withDir : false ) . count
}
var sizeOfRepositoryByteCount : UInt64 {
let fm = FileManager . default
var size = UInt64 ( 0 )
do {
if fm . fileExists ( atPath : self . storeURL . path ) {
size = try fm . allocatedSizeOfDirectoryAtURL ( directoryURL : self . storeURL )
}
} catch {
print ( error )
}
return size
}
2017-01-19 21:15:47 +08:00
private init ( ) {
2017-01-23 16:29:36 +08:00
do {
2017-02-06 11:17:56 +08:00
if FileManager . default . fileExists ( atPath : storeURL . path ) {
try storeRepository = GTRepository . init ( url : storeURL )
}
2017-01-23 16:29:36 +08:00
} catch {
print ( error )
2017-01-19 21:15:47 +08:00
}
2017-03-16 22:06:39 -07:00
initPGPKeys ( )
2017-04-02 11:21:24 -07:00
initGitCredential ( )
}
enum SSHKeyType {
case ` public ` , secret
}
public func initGitCredential ( ) {
if Defaults [ . gitAuthenticationMethod ] = = " Password " {
2017-04-23 10:16:50 -07:00
gitCredential = GitCredential ( credential : GitCredential . Credential . http ( userName : Defaults [ . gitUsername ] ? ? " " , password : Utils . getPasswordFromKeychain ( name : " gitPassword " ) ? ? " " ) )
2017-04-02 11:21:24 -07:00
} else if Defaults [ . gitAuthenticationMethod ] = = " SSH Key " {
2017-02-23 16:34:22 +03:00
gitCredential = GitCredential (
credential : GitCredential . Credential . ssh (
2017-04-23 10:16:50 -07:00
userName : Defaults [ . gitUsername ] ? ? " " ,
2017-04-02 11:21:24 -07:00
password : gitSSHPrivateKeyPassphrase ? ? " " ,
publicKeyFile : Globals . gitSSHPublicKeyURL ,
privateKeyFile : Globals . gitSSHPrivateKeyURL ,
2017-02-23 16:34:22 +03:00
passwordNotSetCallback : nil
)
)
2017-02-02 15:02:57 +08:00
} else {
gitCredential = nil
2017-01-31 22:00:50 +08:00
}
2017-04-02 11:21:24 -07:00
}
public func initGitSSHKey ( with armorKey : String , _ keyType : SSHKeyType ) throws {
var keyPath = " "
switch keyType {
case . public :
keyPath = Globals . gitSSHPublicKeyPath
case . secret :
keyPath = Globals . gitSSHPrivateKeyPath
}
2017-01-24 01:49:55 +08:00
2017-04-02 11:21:24 -07:00
try armorKey . write ( toFile : keyPath , atomically : true , encoding : . ascii )
2017-01-19 21:15:47 +08:00
}
2017-02-19 22:10:36 +08:00
2017-03-16 22:06:39 -07:00
public func initPGPKeys ( ) {
do {
try initPGPKey ( . public )
try initPGPKey ( . secret )
} catch {
print ( error )
2017-02-23 16:53:50 +08:00
}
2017-03-16 22:06:39 -07:00
}
public func initPGPKey ( _ keyType : PGPKeyType ) throws {
switch keyType {
case . public :
2017-04-09 00:25:04 +08:00
let keyPath = Globals . pgpPublicKeyPath
self . publicKey = importKey ( from : keyPath )
if self . publicKey = = nil {
throw NSError ( domain : " me.mssun.pass.error " , code : 2 , userInfo : [ NSLocalizedDescriptionKey : " Cannot import the public PGP key. " ] )
}
2017-03-16 22:06:39 -07:00
case . secret :
2017-04-09 00:25:04 +08:00
let keyPath = Globals . pgpPrivateKeyPath
self . privateKey = importKey ( from : keyPath )
if self . privateKey = = nil {
throw NSError ( domain : " me.mssun.pass.error " , code : 2 , userInfo : [ NSLocalizedDescriptionKey : " Cannot import the private PGP key. " ] )
2017-03-16 22:06:39 -07:00
}
2017-04-09 00:25:04 +08:00
default :
throw NSError ( domain : " me.mssun.pass.error " , code : 2 , userInfo : [ NSLocalizedDescriptionKey : " Cannot import key: unknown PGP key type. " ] )
2017-02-23 16:53:50 +08:00
}
2017-03-16 22:06:39 -07:00
}
public func initPGPKey ( from url : URL , keyType : PGPKeyType ) throws {
var pgpKeyLocalPath = " "
if keyType = = . public {
pgpKeyLocalPath = Globals . pgpPublicKeyPath
} else {
pgpKeyLocalPath = Globals . pgpPrivateKeyPath
2017-02-13 15:01:04 +08:00
}
2017-03-16 22:06:39 -07:00
let pgpKeyData = try Data ( contentsOf : url )
try pgpKeyData . write ( to : URL ( fileURLWithPath : pgpKeyLocalPath ) , options : . atomic )
try initPGPKey ( keyType )
}
public func initPGPKey ( with armorKey : String , keyType : PGPKeyType ) throws {
var pgpKeyLocalPath = " "
if keyType = = . public {
pgpKeyLocalPath = Globals . pgpPublicKeyPath
} else {
pgpKeyLocalPath = Globals . pgpPrivateKeyPath
2017-01-22 01:42:36 +08:00
}
2017-03-16 22:06:39 -07:00
try armorKey . write ( toFile : pgpKeyLocalPath , atomically : true , encoding : . ascii )
try initPGPKey ( keyType )
}
private func importKey ( from keyPath : String ) -> PGPKey ? {
let fm = FileManager . default
if fm . fileExists ( atPath : keyPath ) {
if let keys = pgp . importKeys ( fromFile : keyPath , allowDuplicates : false ) as ? [ PGPKey ] {
2017-04-09 00:25:04 +08:00
return keys . first
2017-03-16 22:06:39 -07:00
}
}
return nil
2017-01-22 01:42:36 +08:00
}
2017-02-24 17:59:04 +03:00
func getPgpPrivateKey ( ) -> PGPKey {
return pgp . getKeysOf ( . secret ) [ 0 ]
}
2017-01-22 01:42:36 +08:00
2017-03-02 17:26:46 +08:00
func repositoryExisted ( ) -> Bool {
2017-02-27 17:30:33 +08:00
let fm = FileManager ( )
return fm . fileExists ( atPath : Globals . repositoryPath )
}
2017-03-02 17:26:12 +08:00
func passwordExisted ( password : Password ) -> Bool {
let passwordEntityFetchRequest = NSFetchRequest < NSFetchRequestResult > ( entityName : " PasswordEntity " )
do {
2017-04-23 10:03:09 -07:00
passwordEntityFetchRequest . predicate = NSPredicate ( format : " name = %@ and path = %@ " , password . name , password . url ! . path )
2017-03-02 17:26:12 +08:00
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
}
2017-04-23 10:03:09 -07:00
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 ) -> PasswordEntity ? {
let passwordEntityFetchRequest = NSFetchRequest < NSFetchRequestResult > ( entityName : " PasswordEntity " )
do {
passwordEntityFetchRequest . predicate = NSPredicate ( format : " path = %@ " , path )
return try context . fetch ( passwordEntityFetchRequest ) . first as ? PasswordEntity
} catch {
fatalError ( " Failed to fetch password entities: \( error ) " )
}
}
2017-01-23 17:36:10 +08:00
func cloneRepository ( remoteRepoURL : URL ,
2017-01-24 01:49:55 +08:00
credential : GitCredential ,
2017-01-23 17:36:10 +08:00
transferProgressBlock : @ escaping ( UnsafePointer < git_transfer_progress > , UnsafeMutablePointer < ObjCBool > ) -> Void ,
2017-02-04 14:24:59 +08:00
checkoutProgressBlock : @ escaping ( String ? , UInt , UInt ) -> Void ) throws {
2017-02-15 22:51:26 +08:00
Utils . removeFileIfExists ( at : storeURL )
Utils . removeFileIfExists ( at : tempStoreURL )
2017-02-04 14:24:59 +08:00
let credentialProvider = try credential . credentialProvider ( )
let options : [ String : Any ] = [
GTRepositoryCloneOptionsCredentialProvider : credentialProvider ,
]
2017-03-03 00:12:28 +08:00
storeRepository = try GTRepository . clone ( from : remoteRepoURL , toWorkingDirectory : tempStoreURL , options : options , transferProgressBlock : transferProgressBlock )
2017-02-04 14:59:55 +08:00
let fm = FileManager . default
do {
if fm . fileExists ( atPath : storeURL . path ) {
try fm . removeItem ( at : storeURL )
}
try fm . copyItem ( at : tempStoreURL , to : storeURL )
try fm . removeItem ( at : tempStoreURL )
} catch {
print ( error )
}
storeRepository = try GTRepository ( url : storeURL )
2017-02-04 14:24:59 +08:00
gitCredential = credential
2017-03-29 00:56:07 +08:00
Defaults [ . lastSyncedTime ] = Date ( )
2017-03-29 22:59:30 -07:00
DispatchQueue . main . async {
self . updatePasswordEntityCoreData ( )
2017-03-30 23:19:04 +08:00
NotificationCenter . default . post ( name : . passwordStoreUpdated , object : nil )
2017-03-29 22:59:30 -07:00
}
2017-01-23 16:29:36 +08:00
}
2017-01-24 16:57:16 +08:00
2017-02-04 14:24:59 +08:00
func pullRepository ( transferProgressBlock : @ escaping ( UnsafePointer < git_transfer_progress > , UnsafeMutablePointer < ObjCBool > ) -> Void ) throws {
2017-02-09 19:44:12 +08:00
if gitCredential = = nil {
throw NSError ( domain : " me.mssun.pass.error " , code : 1 , userInfo : [ NSLocalizedDescriptionKey : " Git Repository is not set. " ] )
}
2017-02-04 14:24:59 +08:00
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 )
2017-03-29 00:56:07 +08:00
Defaults [ . lastSyncedTime ] = Date ( )
2017-03-29 22:59:30 -07:00
DispatchQueue . main . async {
self . setAllSynced ( )
self . updatePasswordEntityCoreData ( )
NotificationCenter . default . post ( name : . passwordStoreUpdated , object : nil )
}
2017-01-19 21:15:47 +08:00
}
2017-03-24 21:53:07 +08:00
private func updatePasswordEntityCoreData ( ) {
2017-02-07 16:45:14 +08:00
deleteCoreData ( entityName : " PasswordEntity " )
2017-01-19 21:15:47 +08:00
let fm = FileManager . default
2017-03-02 14:51:40 +08:00
do {
var q = try fm . contentsOfDirectory ( atPath : self . storeURL . path ) . filter {
! $0 . hasPrefix ( " . " )
} . map { ( filename ) -> PasswordEntity in
let passwordEntity = NSEntityDescription . insertNewObject ( forEntityName : " PasswordEntity " , into : context ) as ! PasswordEntity
if filename . hasSuffix ( " .gpg " ) {
passwordEntity . name = filename . substring ( to : filename . index ( filename . endIndex , offsetBy : - 4 ) )
} else {
passwordEntity . name = filename
}
passwordEntity . path = filename
passwordEntity . parent = nil
return passwordEntity
}
while q . count > 0 {
let e = q . first !
q . remove ( at : 0 )
guard ! e . name ! . hasPrefix ( " . " ) else {
continue
}
var isDirectory : ObjCBool = false
let filePath = storeURL . appendingPathComponent ( e . path ! ) . path
if fm . fileExists ( atPath : filePath , isDirectory : & isDirectory ) {
if isDirectory . boolValue {
e . isDir = true
let files = try fm . contentsOfDirectory ( atPath : filePath ) . map { ( filename ) -> PasswordEntity in
let passwordEntity = NSEntityDescription . insertNewObject ( forEntityName : " PasswordEntity " , into : context ) as ! PasswordEntity
if filename . hasSuffix ( " .gpg " ) {
passwordEntity . name = filename . substring ( to : filename . index ( filename . endIndex , offsetBy : - 4 ) )
} else {
passwordEntity . name = filename
}
passwordEntity . path = " \( e . path ! ) / \( filename ) "
passwordEntity . parent = e
return passwordEntity
}
q += files
} else {
e . isDir = false
2017-02-06 21:53:54 +08:00
}
2017-01-19 21:15:47 +08:00
}
}
2017-03-02 14:51:40 +08:00
} catch {
print ( error )
}
2017-01-19 21:15:47 +08:00
do {
try context . save ( )
} catch {
print ( " Error with save: \( error ) " )
}
}
2017-02-22 18:43:19 +08:00
func getRecentCommits ( count : Int ) -> [ GTCommit ] {
2017-03-19 10:36:59 -07:00
guard storeRepository != nil else {
return [ ]
}
2017-02-22 18:43:19 +08:00
var commits = [ GTCommit ] ( )
do {
let enumerator = try GTEnumerator ( repository : storeRepository ! )
try enumerator . pushSHA ( storeRepository ! . headReference ( ) . targetOID . sha ! )
for _ in 0 . . < count {
let commit = try enumerator . nextObject ( withSuccess : nil )
commits . append ( commit )
}
} catch {
print ( error )
return commits
}
return commits
}
2017-03-02 14:51:40 +08:00
func fetchPasswordEntityCoreData ( parent : PasswordEntity ? ) -> [ PasswordEntity ] {
2017-01-19 21:15:47 +08:00
let passwordEntityFetch = NSFetchRequest < NSFetchRequestResult > ( entityName : " PasswordEntity " )
do {
2017-03-02 14:51:40 +08:00
passwordEntityFetch . predicate = NSPredicate ( format : " parent = %@ " , parent ? ? 0 )
2017-01-19 21:15:47 +08:00
let fetchedPasswordEntities = try context . fetch ( passwordEntityFetch ) as ! [ PasswordEntity ]
2017-02-09 13:38:42 +08:00
return fetchedPasswordEntities . sorted { $0 . name ! . caseInsensitiveCompare ( $1 . name ! ) = = . orderedAscending }
2017-01-19 21:15:47 +08:00
} catch {
2017-02-12 01:59:40 +08:00
fatalError ( " Failed to fetch passwords: \( error ) " )
2017-01-19 21:15:47 +08:00
}
}
2017-03-02 14:51:40 +08:00
func fetchPasswordEntityCoreData ( withDir : Bool ) -> [ PasswordEntity ] {
let passwordEntityFetch = NSFetchRequest < NSFetchRequestResult > ( entityName : " PasswordEntity " )
2017-02-06 21:53:54 +08:00
do {
2017-03-02 14:51:40 +08:00
if ! withDir {
passwordEntityFetch . predicate = NSPredicate ( format : " isDir = false " )
}
let fetchedPasswordEntities = try context . fetch ( passwordEntityFetch ) as ! [ PasswordEntity ]
return fetchedPasswordEntities . sorted { $0 . name ! . caseInsensitiveCompare ( $1 . name ! ) = = . orderedAscending }
2017-02-12 01:59:40 +08:00
} catch {
2017-03-02 14:51:40 +08:00
fatalError ( " Failed to fetch passwords: \( error ) " )
2017-02-12 01:59:40 +08:00
}
}
2017-03-02 14:51:40 +08:00
2017-02-12 01:59:40 +08:00
func fetchUnsyncedPasswords ( ) -> [ PasswordEntity ] {
let passwordEntityFetchRequest = NSFetchRequest < NSFetchRequestResult > ( entityName : " PasswordEntity " )
passwordEntityFetchRequest . predicate = NSPredicate ( format : " synced = %i " , 0 )
do {
let passwordEntities = try context . fetch ( passwordEntityFetchRequest ) as ! [ PasswordEntity ]
return passwordEntities
} catch {
fatalError ( " Failed to fetch passwords: \( error ) " )
}
}
func setAllSynced ( ) {
let passwordEntities = fetchUnsyncedPasswords ( )
for passwordEntity in passwordEntities {
passwordEntity . synced = true
}
do {
2017-02-15 21:48:06 +08:00
if context . hasChanges {
try context . save ( )
}
2017-02-12 01:59:40 +08:00
} catch {
fatalError ( " Failed to save: \( error ) " )
}
}
func getNumberOfUnsyncedPasswords ( ) -> Int {
let passwordEntityFetchRequest = NSFetchRequest < NSFetchRequestResult > ( entityName : " PasswordEntity " )
do {
passwordEntityFetchRequest . predicate = NSPredicate ( format : " synced = %i " , 0 )
return try context . count ( for : passwordEntityFetchRequest )
2017-02-06 21:53:54 +08:00
} catch {
2017-03-02 17:26:12 +08:00
fatalError ( " Failed to fetch unsynced passwords: \( error ) " )
2017-02-06 21:53:54 +08:00
}
}
2017-03-02 17:26:12 +08:00
2017-03-02 00:06:27 +08:00
func getLatestUpdateInfo ( filename : String ) -> String {
2017-02-27 15:21:25 +08:00
guard let blameHunks = try ? storeRepository ? . blame ( withFile : filename , options : nil ) . hunks ,
let latestCommitTime = blameHunks ? . map ( {
$0 . finalSignature ? . time ? . timeIntervalSince1970 ? ? 0
} ) . max ( ) else {
2017-03-02 00:06:27 +08:00
return " unknown "
2017-02-27 15:21:25 +08:00
}
2017-03-02 00:06:27 +08:00
let lastCommitDate = Date ( timeIntervalSince1970 : latestCommitTime )
let currentDate = Date ( )
var autoFormattedDifference : String
if currentDate . timeIntervalSince ( lastCommitDate ) <= 60 {
2017-03-03 20:16:06 +08:00
autoFormattedDifference = " Just now "
2017-03-02 00:06:27 +08:00
} else {
let diffDate = Calendar . current . dateComponents ( [ . year , . month , . day , . hour , . minute ] , from : lastCommitDate , to : currentDate )
let dateComponentsFormatter = DateComponentsFormatter ( )
dateComponentsFormatter . unitsStyle = . full
dateComponentsFormatter . maximumUnitCount = 2
dateComponentsFormatter . includesApproximationPhrase = true
autoFormattedDifference = ( dateComponentsFormatter . string ( from : diffDate ) ? . appending ( " ago " ) ) !
}
return autoFormattedDifference
2017-02-27 15:21:25 +08:00
}
2017-01-19 21:15:47 +08:00
func updateRemoteRepo ( ) {
}
2017-02-07 16:45:14 +08:00
2017-04-23 10:03:09 -07:00
func createAddCommitInRepository ( message : String , path : String ) -> GTCommit ? {
2017-02-13 01:15:42 +08:00
do {
2017-04-23 10:03:09 -07:00
try storeRepository ? . index ( ) . addFile ( path )
2017-03-16 00:01:17 +08:00
try storeRepository ? . index ( ) . write ( )
let newTree = try storeRepository ! . index ( ) . writeTree ( )
2017-02-13 01:15:42 +08:00
let headReference = try storeRepository ! . headReference ( )
let commitEnum = try GTEnumerator ( repository : storeRepository ! )
2017-02-17 20:14:01 +08:00
try commitEnum . pushSHA ( headReference . targetOID . sha ! )
2017-02-13 01:15:42 +08:00
let parent = commitEnum . nextObject ( ) as ! GTCommit
2017-03-16 00:38:38 +08:00
let signature = gitSignatureForNow
let commit = try storeRepository ! . createCommit ( with : newTree , message : message , author : signature , committer : signature , parents : [ parent ] , updatingReferenceNamed : headReference . name )
2017-02-13 01:15:42 +08:00
return commit
} catch {
print ( error )
}
return nil
}
2017-03-21 13:16:25 -07:00
func createRemoveCommitInRepository ( message : String , path : String ) -> GTCommit ? {
2017-02-13 01:15:42 +08:00
do {
2017-03-21 13:16:25 -07:00
try storeRepository ? . index ( ) . removeFile ( path )
2017-03-16 00:01:17 +08:00
try storeRepository ? . index ( ) . write ( )
let newTree = try storeRepository ! . index ( ) . writeTree ( )
2017-02-11 01:50:39 +08:00
let headReference = try storeRepository ! . headReference ( )
let commitEnum = try GTEnumerator ( repository : storeRepository ! )
2017-02-17 20:14:01 +08:00
try commitEnum . pushSHA ( headReference . targetOID . sha ! )
2017-02-11 01:50:39 +08:00
let parent = commitEnum . nextObject ( ) as ! GTCommit
2017-03-16 00:38:38 +08:00
let signature = gitSignatureForNow
let commit = try storeRepository ! . createCommit ( with : newTree , message : message , author : signature , committer : signature , parents : [ parent ] , updatingReferenceNamed : headReference . name )
2017-02-11 01:50:39 +08:00
return commit
} catch {
print ( error )
}
return nil
}
2017-02-12 01:59:40 +08:00
2017-02-11 01:50:39 +08:00
private func getLocalBranch ( withName branchName : String ) -> GTBranch ? {
do {
let reference = GTBranch . localNamePrefix ( ) . appending ( branchName )
let branches = try storeRepository ! . branches ( withPrefix : reference )
return branches [ 0 ]
} catch {
print ( error )
}
return nil
}
2017-02-11 19:48:47 +08:00
func pushRepository ( transferProgressBlock : @ escaping ( UInt32 , UInt32 , Int , UnsafeMutablePointer < ObjCBool > ) -> Void ) throws {
2017-02-11 01:50:39 +08:00
let credentialProvider = try gitCredential ! . credentialProvider ( )
let options : [ String : Any ] = [
GTRepositoryRemoteOptionsCredentialProvider : credentialProvider ,
]
let masterBranch = getLocalBranch ( withName : " master " ) !
let remote = try GTRemote ( name : " origin " , in : storeRepository ! )
2017-02-11 19:48:47 +08:00
try storeRepository ? . push ( masterBranch , to : remote , withOptions : options , progress : transferProgressBlock )
2017-02-10 22:15:01 +08:00
}
2017-04-23 10:03:09 -07:00
private func addPasswordEntities ( password : Password ) -> PasswordEntity ? {
var passwordURL = password . url !
var paths : [ String ] = [ ]
while passwordURL . path != " . " {
paths . append ( passwordURL . path )
passwordURL = passwordURL . deletingLastPathComponent ( )
2017-03-02 17:26:12 +08:00
}
2017-04-23 10:03:09 -07:00
paths . reverse ( )
var parentPasswordEntity : PasswordEntity ? = nil
for path in paths {
if let passwordEntity = getPasswordEntity ( by : path ) {
parentPasswordEntity = passwordEntity
} else {
if path . hasSuffix ( " .gpg " ) {
return insertPasswordEntity ( name : URL ( string : path ) ! . deletingPathExtension ( ) . lastPathComponent , path : path , parent : parentPasswordEntity , synced : false , isDir : false )
} else {
parentPasswordEntity = insertPasswordEntity ( name : URL ( string : path ) ! . lastPathComponent , path : path , parent : parentPasswordEntity , synced : false , isDir : true )
let fm = FileManager . default
let saveURL = storeURL . appendingPathComponent ( path )
do {
try fm . createDirectory ( at : saveURL , withIntermediateDirectories : false , attributes : nil )
} catch {
print ( error )
}
}
}
2017-02-10 22:15:01 +08:00
}
2017-04-23 10:03:09 -07:00
return nil
2017-02-10 22:15:01 +08:00
}
2017-04-23 10:03:09 -07:00
private func insertPasswordEntity ( name : String , path : String , parent : PasswordEntity ? , synced : Bool = false , isDir : Bool = false ) -> PasswordEntity ? {
var ret : PasswordEntity ? = nil
DispatchQueue . main . sync {
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 ) " )
}
}
2017-02-15 20:01:17 +08:00
}
2017-04-23 10:03:09 -07:00
return ret
2017-02-15 20:01:17 +08:00
}
2017-04-23 10:03:09 -07:00
func add ( 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. " ] )
2017-03-21 13:16:25 -07:00
}
2017-04-23 10:03:09 -07:00
let newPasswordEntity = addPasswordEntities ( password : password )
print ( " new: \( newPasswordEntity ! . path ! ) " )
let saveURL = storeURL . appendingPathComponent ( password . url ! . path )
try self . encrypt ( password : password ) . write ( to : saveURL )
let _ = createAddCommitInRepository ( message : " Add password for \( password . url ! . deletingPathExtension ( ) . path ) to store using Pass for iOS. " , path : password . url ! . path )
2017-03-21 13:16:25 -07:00
NotificationCenter . default . post ( name : . passwordStoreUpdated , object : nil )
2017-04-23 10:03:09 -07:00
return newPasswordEntity
}
func update ( passwordEntity : PasswordEntity , password : Password ) throws -> PasswordEntity ? {
delete ( passwordEntity : passwordEntity )
return try add ( password : password )
}
public func delete ( passwordEntity : PasswordEntity ) {
DispatchQueue . main . async {
let _ = self . createRemoveCommitInRepository ( message : " Remove \( passwordEntity . nameWithCategory ) from store using Pass for iOS " , path : passwordEntity . path ! )
var current : PasswordEntity ? = passwordEntity
while current != nil && ( current ! . children ! . count = = 0 || ! current ! . isDir ) {
Utils . removeFileIfExists ( at : self . storeURL . appendingPathComponent ( current ! . path ! ) )
let parent = current ! . parent
self . context . delete ( current ! )
current = parent
do {
try self . context . save ( )
} catch {
fatalError ( " Failed to delete a PasswordEntity: \( error ) " )
}
}
NotificationCenter . default . post ( name : . passwordStoreUpdated , object : nil )
}
2017-03-21 13:16:25 -07:00
}
2017-02-15 20:01:17 +08:00
func saveUpdated ( passwordEntity : PasswordEntity ) {
do {
2017-02-13 01:15:42 +08:00
try context . save ( )
} catch {
2017-02-15 20:01:17 +08:00
fatalError ( " Failed to save a PasswordEntity: \( error ) " )
2017-02-13 01:15:42 +08:00
}
}
2017-02-07 16:45:14 +08:00
func deleteCoreData ( entityName : String ) {
let deleteFetchRequest = NSFetchRequest < NSFetchRequestResult > ( entityName : entityName )
let deleteRequest = NSBatchDeleteRequest ( fetchRequest : deleteFetchRequest )
do {
try context . execute ( deleteRequest )
2017-02-15 16:51:12 +08:00
try context . save ( )
2017-02-15 21:37:02 +08:00
context . reset ( )
2017-02-07 16:45:14 +08:00
} catch let error as NSError {
print ( error )
}
}
2017-02-16 13:24:41 +08:00
func updateImage ( passwordEntity : PasswordEntity , image : Data ? ) {
if image = = nil {
return
}
let privateMOC = NSManagedObjectContext ( concurrencyType : . privateQueueConcurrencyType )
privateMOC . parent = context
privateMOC . perform {
passwordEntity . image = NSData ( data : image ! )
do {
try privateMOC . save ( )
self . context . performAndWait {
do {
try self . context . save ( )
} catch {
fatalError ( " Failure to save context: \( error ) " )
}
}
} catch {
fatalError ( " Failure to save context: \( error ) " )
}
}
}
2017-02-07 16:45:14 +08:00
func erase ( ) {
2017-03-16 22:06:39 -07:00
publicKey = nil
privateKey = nil
2017-02-08 19:29:44 +08:00
Utils . removeFileIfExists ( at : storeURL )
2017-02-15 11:15:11 +08:00
Utils . removeFileIfExists ( at : tempStoreURL )
2017-02-10 22:15:01 +08:00
Utils . removeFileIfExists ( atPath : Globals . pgpPublicKeyPath )
Utils . removeFileIfExists ( atPath : Globals . pgpPrivateKeyPath )
2017-04-02 11:21:24 -07:00
Utils . removeFileIfExists ( atPath : Globals . gitSSHPublicKeyPath )
Utils . removeFileIfExists ( atPath : Globals . gitSSHPrivateKeyPath )
2017-02-07 16:45:14 +08:00
2017-02-19 22:10:36 +08:00
Utils . removeAllKeychain ( )
2017-02-20 11:48:39 +08:00
2017-02-19 22:10:36 +08:00
2017-02-07 16:45:14 +08:00
deleteCoreData ( entityName : " PasswordEntity " )
2017-02-13 14:30:38 +08:00
Defaults . removeAll ( )
storeRepository = nil
2017-03-18 00:18:55 +08:00
NotificationCenter . default . post ( name : . passwordStoreUpdated , object : nil )
NotificationCenter . default . post ( name : . passwordStoreErased , object : nil )
2017-02-07 16:45:14 +08:00
}
2017-03-04 01:15:28 +08:00
2017-03-07 01:43:23 +08:00
// r e t u r n t h e n u m b e r o f d i s c a r d e d c o m m i t s
func reset ( ) throws -> Int {
2017-03-04 01:15:28 +08:00
// g e t a l i s t o f l o c a l c o m m i t s
2017-03-22 19:07:41 -07:00
if let localCommits = try getLocalCommits ( ) ,
2017-03-04 01:15:28 +08:00
localCommits . count > 0 {
2017-03-07 01:43:23 +08:00
// g e t t h e o l d e s t l o c a l c o m m i t
guard let firstLocalCommit = localCommits . last ,
2017-03-04 01:15:28 +08:00
firstLocalCommit . parents . count = = 1 ,
let newHead = firstLocalCommit . parents . first else {
throw NSError ( domain : " me.mssun.pass.error " , code : 1 , userInfo : [ NSLocalizedDescriptionKey : " Cannot decide how to reset. " ] )
}
try self . storeRepository ? . reset ( to : newHead , resetType : GTRepositoryResetType . hard )
2017-03-24 21:53:07 +08:00
self . setAllSynced ( )
2017-03-04 01:15:28 +08:00
self . updatePasswordEntityCoreData ( )
2017-03-29 00:56:07 +08:00
Defaults [ . lastSyncedTime ] = nil
2017-03-18 00:18:55 +08:00
NotificationCenter . default . post ( name : . passwordStoreUpdated , object : nil )
NotificationCenter . default . post ( name : . passwordStoreChangeDiscarded , object : nil )
2017-03-07 01:43:23 +08:00
return localCommits . count
2017-03-04 01:15:28 +08:00
} else {
2017-03-07 01:43:23 +08:00
return 0 // n o n e w c o m m i t
2017-03-04 01:15:28 +08:00
}
}
2017-03-22 19:07:41 -07:00
func numberOfLocalCommits ( ) -> Int {
do {
if let localCommits = try getLocalCommits ( ) {
return localCommits . count
} else {
return 0
}
} catch {
print ( error )
}
return 0
}
private func getLocalCommits ( ) throws -> [ GTCommit ] ? {
// g e t t h e r e m o t e o r i g i n / m a s t e r b r a n c h
guard let remoteBranches = try storeRepository ? . remoteBranches ( ) ,
let index = remoteBranches . index ( where : { $0 . shortName = = " master " } )
else {
throw NSError ( domain : " me.mssun.pass.error " , code : 1 , userInfo : [ NSLocalizedDescriptionKey : " Cannot find remote branch origin/master. " ] )
}
let remoteMasterBranch = remoteBranches [ index ]
// p r i n t ( " r e m o t e M a s t e r B r a n c h \ ( r e m o t e M a s t e r B r a n c h ) " )
// g e t a l i s t o f l o c a l c o m m i t s
return try storeRepository ? . localCommitsRelative ( toRemoteBranch : remoteMasterBranch )
}
2017-04-23 10:03:09 -07:00
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 ) ? ? " "
password = Password ( name : passwordEntity . name ! , url : URL ( string : passwordEntity . path ! ) , 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
}
2017-01-19 21:15:47 +08:00
}