Merge branch 'master' of github.com:Lyndir/MasterPassword
This commit is contained in:
commit
634ef062f3
2
External/Pearl
vendored
2
External/Pearl
vendored
@ -1 +1 @@
|
||||
Subproject commit 08c42ba0f96a95703ec67cbf5846bfe6680dd0a6
|
||||
Subproject commit 65b4e3d9984d077f66e6ab15f2ffcc4965d07825
|
61
MasterPassword/C/bashcomplib
Normal file
61
MasterPassword/C/bashcomplib
Normal file
@ -0,0 +1,61 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# FIXME
|
||||
# partials are currently readline words, but these can't be reliably compared against literal data. We need to make them literal first.. in a safe way. Currently using xargs' quote parser as a hack.
|
||||
|
||||
# Process literal completion options in COMPREPLY
|
||||
#
|
||||
# 1. Filter COMPREPLY by excluding the options that do not match the word that is being completed.
|
||||
# 2. Shell-escape the COMPREPLY words so they remain syntactical words when injected into the completed command.
|
||||
# 3. Add a space after the words so successful completions advance to the next word
|
||||
# (we disabled this default behavior with -o nospace so we can do completions that don't want this, eg. directory names)
|
||||
_comp_finish_completions() {
|
||||
local partial=$(xargs <<< "${COMP_WORDS[COMP_CWORD]}") # FIXME
|
||||
local word words=( "${COMPREPLY[@]}" )
|
||||
|
||||
COMPREPLY=()
|
||||
for word in "${words[@]}"; do
|
||||
( shopt -s nocasematch; [[ $word = $partial* ]] ) && COMPREPLY+=( "$(printf '%q ' "$word")" )
|
||||
done
|
||||
|
||||
if (( ${#COMPREPLY[@]} > 1 )) && [[ $_comp_title ]]; then
|
||||
printf '\n%s:' "$_comp_title"
|
||||
unset _comp_title
|
||||
fi
|
||||
}
|
||||
|
||||
# Perform pathname completion.
|
||||
#
|
||||
# 1. Populate COMPREPLY with pathnames.
|
||||
# 2. Shell-escape the COMPREPLY words so they remain syntactical words when injected into the completed command.
|
||||
# 3. Add a space after file names so successful completions advance to the next word.
|
||||
# Directory names are suffixed with a / instead so we can keep completing the files inside.
|
||||
_comp_complete_path() {
|
||||
local partial=$(xargs <<< "${COMP_WORDS[COMP_CWORD]}")
|
||||
local path
|
||||
|
||||
COMPREPLY=()
|
||||
for path in "$partial"*; do
|
||||
if [[ -d $path ]]; then
|
||||
COMPREPLY+=( "$(printf '%q/' "$path")" )
|
||||
|
||||
elif [[ -e $path ]]; then
|
||||
COMPREPLY+=( "$(printf '%q ' "$path")" )
|
||||
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
_show_args() {
|
||||
echo
|
||||
local i=0
|
||||
for arg; do
|
||||
printf "arg %d: %s\n" "$((i++))" "$arg"
|
||||
done
|
||||
|
||||
i=0
|
||||
for word in "${COMP_WORDS[@]}"; do
|
||||
printf "word %d: %s -> %s %s\n" "$i" "$word" "$(xargs <<< "$word")" "$( ((i == $COMP_CWORD)) && echo '<CWORD>' )"
|
||||
let i++
|
||||
done
|
||||
}
|
@ -6,6 +6,7 @@
|
||||
// Copyright (c) 2014 Lyndir. All rights reserved.
|
||||
//
|
||||
|
||||
// NOTE: mpw is currently NOT thread-safe.
|
||||
#include "mpw-types.h"
|
||||
|
||||
typedef enum(unsigned int, MPAlgorithmVersion) {
|
||||
|
@ -80,7 +80,7 @@ uint8_t const *mpw_scrypt(const size_t keySize, const char *secret, const uint8_
|
||||
|
||||
uint8_t const *mpw_hmac_sha256(const uint8_t *key, const size_t keySize, const uint8_t *salt, const size_t saltSize) {
|
||||
|
||||
uint8_t *const buffer = malloc(32);
|
||||
uint8_t *const buffer = malloc( 32 );
|
||||
if (!buffer)
|
||||
return NULL;
|
||||
|
||||
@ -98,10 +98,12 @@ const char *mpw_idForBuf(const void *buf, size_t length) {
|
||||
|
||||
static char **mpw_hex_buf = NULL;
|
||||
static unsigned int mpw_hex_buf_i = 0;
|
||||
|
||||
const char *mpw_hex(const void *buf, size_t length) {
|
||||
|
||||
// FIXME
|
||||
if (!mpw_hex_buf) {
|
||||
mpw_hex_buf = malloc( 10 * sizeof( char* ) );
|
||||
mpw_hex_buf = malloc( 10 * sizeof( char * ) );
|
||||
for (uint8_t i = 0; i < 10; ++i)
|
||||
mpw_hex_buf[i] = NULL;
|
||||
}
|
||||
@ -113,7 +115,9 @@ const char *mpw_hex(const void *buf, size_t length) {
|
||||
|
||||
return mpw_hex_buf[mpw_hex_buf_i];
|
||||
}
|
||||
|
||||
const char *mpw_hex_l(uint32_t number) {
|
||||
|
||||
return mpw_hex( &number, sizeof( number ) );
|
||||
}
|
||||
|
||||
@ -144,7 +148,8 @@ const char *mpw_identicon(const char *fullName, const char *masterPassword) {
|
||||
const char *accessory[] = {
|
||||
"◈", "◎", "◐", "◑", "◒", "◓", "☀", "☁", "☂", "☃", "☄", "★", "☆", "☎", "☏", "⎈", "⌂", "☘", "☢", "☣",
|
||||
"☕", "⌚", "⌛", "⏰", "⚡", "⛄", "⛅", "☔", "♔", "♕", "♖", "♗", "♘", "♙", "♚", "♛", "♜", "♝", "♞", "♟",
|
||||
"♨", "♩", "♪", "♫", "⚐", "⚑", "⚔", "⚖", "⚙", "⚠", "⌘", "⏎", "✄", "✆", "✈", "✉", "✌" };
|
||||
"♨", "♩", "♪", "♫", "⚐", "⚑", "⚔", "⚖", "⚙", "⚠", "⌘", "⏎", "✄", "✆", "✈", "✉", "✌"
|
||||
};
|
||||
|
||||
uint8_t identiconSeed[32];
|
||||
HMAC_SHA256_Buf( masterPassword, strlen( masterPassword ), fullName, strlen( fullName ), identiconSeed );
|
||||
@ -206,8 +211,8 @@ const size_t mpw_charlen(const char *utf8String) {
|
||||
|
||||
size_t charlen = 0;
|
||||
char *remainingString = (char *)utf8String;
|
||||
for (int charByteSize; (charByteSize = mpw_charByteSize( *remainingString )); remainingString += charByteSize)
|
||||
for (int charByteSize; (charByteSize = mpw_charByteSize( (unsigned char)*remainingString )); remainingString += charByteSize)
|
||||
++charlen;
|
||||
|
||||
return charlen;
|
||||
return charlen;
|
||||
}
|
||||
|
56
MasterPassword/C/mpw.completion.bash
Normal file
56
MasterPassword/C/mpw.completion.bash
Normal file
@ -0,0 +1,56 @@
|
||||
#!/usr/bin/env bash
|
||||
source bashcomplib
|
||||
|
||||
# completing the 'mpw' command.
|
||||
_comp_mpw() {
|
||||
local optarg= cword=${COMP_WORDS[COMP_CWORD]} pcword
|
||||
|
||||
if (( COMP_CWORD > 0 )); then
|
||||
pcword=${COMP_WORDS[COMP_CWORD - 1]}
|
||||
|
||||
case $pcword in
|
||||
-u) optarg=user ;;
|
||||
-t) optarg=type ;;
|
||||
-c) optarg=counter ;;
|
||||
-V) optarg=version ;;
|
||||
-v) optarg=variant ;;
|
||||
-C) optarg=context ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
case $optarg in
|
||||
user) # complete full names.
|
||||
COMPREPLY=( ~/.mpw.d/*.mpsites ) COMPREPLY=( "${COMPREPLY[@]##*/}" ) COMPREPLY=( "${COMPREPLY[@]%.mpsites}" )
|
||||
;;
|
||||
type) # complete types.
|
||||
COMPREPLY=( maximum long medium basic short pin name phrase )
|
||||
;;
|
||||
counter) # complete counter.
|
||||
COMPREPLY=( 1 )
|
||||
;;
|
||||
version) # complete versions.
|
||||
COMPREPLY=( 0 1 2 3 )
|
||||
;;
|
||||
variant) # complete variants.
|
||||
COMPREPLY=( password login answer )
|
||||
;;
|
||||
context) # complete context.
|
||||
;;
|
||||
*)
|
||||
# previous word is not an option we can complete, complete site name (or option if leading -)
|
||||
if [[ $cword = -* ]]; then
|
||||
COMPREPLY=( -u -t -c -V -v -C )
|
||||
else
|
||||
local w fullName=$MP_FULLNAME
|
||||
for (( w = 0; w < ${#COMP_WORDS[@]}; ++w )); do
|
||||
[[ ${COMP_WORDS[w]} = -u ]] && fullName=$(xargs <<< "${COMP_WORDS[w + 1]}") && break
|
||||
done
|
||||
IFS=$'\n' read -d '' -ra COMPREPLY < <(awk -F$'\t' '!/^ *#/{sub(/^ */, "", $2); print $2}' ~/.mpw.d/"$fullName.mpsites")
|
||||
printf -v _comp_title 'Sites for %s' "$fullName"
|
||||
fi ;;
|
||||
esac
|
||||
_comp_finish_completions
|
||||
}
|
||||
|
||||
#complete -F _show_args mpw
|
||||
complete -o nospace -F _comp_mpw mpw
|
@ -28,8 +28,10 @@
|
||||
#define CRACKING_PER_SECOND 2495000000UL
|
||||
#define CRACKING_PRICE 350
|
||||
|
||||
NSOperationQueue *_mpwQueue = nil;
|
||||
|
||||
@implementation MPAlgorithmV0 {
|
||||
BN_CTX *ctx;
|
||||
BN_CTX *_ctx;
|
||||
}
|
||||
|
||||
- (id)init {
|
||||
@ -37,15 +39,22 @@
|
||||
if (!(self = [super init]))
|
||||
return nil;
|
||||
|
||||
ctx = BN_CTX_new();
|
||||
_ctx = BN_CTX_new();
|
||||
|
||||
static dispatch_once_t once = 0;
|
||||
dispatch_once( &once, ^{
|
||||
_mpwQueue = [NSOperationQueue new];
|
||||
_mpwQueue.maxConcurrentOperationCount = 1;
|
||||
_mpwQueue.name = @"mpw queue";
|
||||
} );
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
|
||||
BN_CTX_free( ctx );
|
||||
ctx = NULL;
|
||||
BN_CTX_free( _ctx );
|
||||
_ctx = NULL;
|
||||
}
|
||||
|
||||
- (MPAlgorithmVersion)version {
|
||||
@ -68,6 +77,19 @@
|
||||
return [(id<MPAlgorithm>)other version] == [self version];
|
||||
}
|
||||
|
||||
- (void)mpw_perform:(void ( ^ )(void))operationBlock {
|
||||
|
||||
if ([NSOperationQueue currentQueue] == _mpwQueue) {
|
||||
operationBlock();
|
||||
return;
|
||||
}
|
||||
|
||||
NSOperation *operation = [NSBlockOperation blockOperationWithBlock:operationBlock];
|
||||
if ([operation respondsToSelector:@selector( qualityOfService )])
|
||||
operation.qualityOfService = NSQualityOfServiceUserInitiated;
|
||||
[_mpwQueue addOperations:@[ operation ] waitUntilFinished:YES];
|
||||
}
|
||||
|
||||
- (BOOL)tryMigrateUser:(MPUserEntity *)user inContext:(NSManagedObjectContext *)moc {
|
||||
|
||||
NSError *error = nil;
|
||||
@ -107,12 +129,16 @@
|
||||
|
||||
- (NSData *)keyDataForFullName:(NSString *)fullName withMasterPassword:(NSString *)masterPassword {
|
||||
|
||||
NSDate *start = [NSDate date];
|
||||
uint8_t const *masterKeyBytes = mpw_masterKeyForUser( fullName.UTF8String, masterPassword.UTF8String, [self version] );
|
||||
NSData *keyData = [NSData dataWithBytes:masterKeyBytes length:MP_dkLen];
|
||||
trc( @"User: %@, password: %@ derives to key ID: %@ (took %0.2fs)", //
|
||||
fullName, masterPassword, [self keyIDForKeyData:keyData], -[start timeIntervalSinceNow] );
|
||||
mpw_free( masterKeyBytes, MP_dkLen );
|
||||
__block NSData *keyData;
|
||||
[self mpw_perform:^{
|
||||
NSDate *start = [NSDate date];
|
||||
uint8_t const *masterKeyBytes = mpw_masterKeyForUser( fullName.UTF8String, masterPassword.UTF8String, [self version] );
|
||||
keyData = [NSData dataWithBytes:masterKeyBytes length:MP_dkLen];
|
||||
trc( @"User: %@, password: %@ derives to key ID: %@ (took %0.2fs)", //
|
||||
fullName, masterPassword, [self keyIDForKeyData:keyData], -[start timeIntervalSinceNow] );
|
||||
mpw_free( masterKeyBytes, MP_dkLen );
|
||||
}];
|
||||
|
||||
return keyData;
|
||||
}
|
||||
|
||||
@ -317,10 +343,13 @@
|
||||
- (NSString *)generateContentForSiteNamed:(NSString *)name ofType:(MPSiteType)type withCounter:(NSUInteger)counter
|
||||
variant:(MPSiteVariant)variant context:(NSString *)context usingKey:(MPKey *)key {
|
||||
|
||||
char const *contentBytes = mpw_passwordForSite( [key keyDataForAlgorithm:self].bytes,
|
||||
name.UTF8String, type, (uint32_t)counter, variant, context.UTF8String, [self version] );
|
||||
NSString *content = [NSString stringWithCString:contentBytes encoding:NSUTF8StringEncoding];
|
||||
mpw_freeString( contentBytes );
|
||||
__block NSString *content;
|
||||
[self mpw_perform:^{
|
||||
char const *contentBytes = mpw_passwordForSite( [key keyDataForAlgorithm:self].bytes,
|
||||
name.UTF8String, type, (uint32_t)counter, variant, context.UTF8String, [self version] );
|
||||
content = [NSString stringWithCString:contentBytes encoding:NSUTF8StringEncoding];
|
||||
mpw_freeString( contentBytes );
|
||||
}];
|
||||
|
||||
return content;
|
||||
}
|
||||
@ -382,7 +411,7 @@
|
||||
[PearlKeyChain deleteItemForQuery:siteQuery];
|
||||
else
|
||||
[PearlKeyChain addOrUpdateItemForQuery:siteQuery withAttributes:@{
|
||||
(__bridge id)kSecValueData : encryptedContent,
|
||||
(__bridge id)kSecValueData : encryptedContent,
|
||||
#if TARGET_OS_IPHONE
|
||||
(__bridge id)kSecAttrAccessible : (__bridge id)kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
|
||||
#endif
|
||||
@ -562,7 +591,8 @@
|
||||
- (void)resolveAnswerForQuestion:(MPSiteQuestionEntity *)question usingKey:(MPKey *)siteKey
|
||||
result:(void ( ^ )(NSString *result))resultBlock {
|
||||
|
||||
NSAssert( [[siteKey keyIDForAlgorithm:question.site.user.algorithm] isEqualToData:question.site.user.keyID], @"Site does not belong to current user." );
|
||||
NSAssert( [[siteKey keyIDForAlgorithm:question.site.user.algorithm] isEqualToData:question.site.user.keyID],
|
||||
@"Site does not belong to current user." );
|
||||
NSString *name = question.site.name;
|
||||
NSString *keyword = question.keyword;
|
||||
id<MPAlgorithm> algorithm = nil;
|
||||
@ -748,7 +778,7 @@
|
||||
|
||||
if (strchr( charactersForClass, passwordCharacter )) {
|
||||
// Found class for password character.
|
||||
characterEntropy = (BN_ULONG)strlen(charactersForClass);
|
||||
characterEntropy = (BN_ULONG)strlen( charactersForClass );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -190,14 +190,14 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
|
||||
// When privateManagedObjectContext is saved, import the changes into mainManagedObjectContext.
|
||||
PearlAddNotificationObserverTo( self.mainManagedObjectContext, NSManagedObjectContextDidSaveNotification,
|
||||
self.privateManagedObjectContext, nil, ^(NSManagedObjectContext *mainManagedObjectContext, NSNotification *note) {
|
||||
[mainManagedObjectContext performBlock:^{
|
||||
@try {
|
||||
[mainManagedObjectContext mergeChangesFromContextDidSaveNotification:note];
|
||||
}
|
||||
@catch (NSException *exception) {
|
||||
err( @"While merging changes:\n%@",[exception fullDescription] );
|
||||
}
|
||||
}];
|
||||
[mainManagedObjectContext performBlock:^{
|
||||
@try {
|
||||
[mainManagedObjectContext mergeChangesFromContextDidSaveNotification:note];
|
||||
}
|
||||
@catch (NSException *exception) {
|
||||
err( @"While merging changes:\n%@", [exception fullDescription] );
|
||||
}
|
||||
}];
|
||||
} );
|
||||
|
||||
|
||||
@ -821,10 +821,17 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
|
||||
content = [site.algorithm exportPasswordForSite:site usingKey:self.key];
|
||||
}
|
||||
|
||||
[export appendFormat:@"%@ %8ld %8s %25s\t%25s\t%@\n",
|
||||
[[NSDateFormatter rfc3339DateFormatter] stringFromDate:lastUsed], (long)uses,
|
||||
[strf( @"%lu:%lu:%lu", (long)type, (long)[algorithm version], (long)counter ) UTF8String],
|
||||
[(loginName?: @"") UTF8String], [siteName UTF8String], content?: @""];
|
||||
NSString *lastUsedExport = [[NSDateFormatter rfc3339DateFormatter] stringFromDate:lastUsed];
|
||||
long usesExport = (long)uses;
|
||||
NSString *typeExport = strf( @"%lu:%lu:%lu", (long)type, (long)[algorithm version], (long)counter );
|
||||
NSString *loginNameExport = loginName?: @"";
|
||||
NSString *contentExport = content?: @"";
|
||||
[export appendFormat:@"%@ %8ld %8S %25S\t%25S\t%@\n",
|
||||
lastUsedExport, usesExport,
|
||||
(const unichar *)[typeExport cStringUsingEncoding:NSUTF16StringEncoding],
|
||||
(const unichar *)[loginNameExport cStringUsingEncoding:NSUTF16StringEncoding],
|
||||
(const unichar *)[siteName cStringUsingEncoding:NSUTF16StringEncoding],
|
||||
contentExport];
|
||||
}
|
||||
|
||||
return export;
|
||||
|
@ -17,6 +17,7 @@
|
||||
//
|
||||
|
||||
#import <QuartzCore/QuartzCore.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "MPPasswordWindowController.h"
|
||||
#import "MPMacAppDelegate.h"
|
||||
#import "MPAppDelegate_Store.h"
|
||||
@ -463,26 +464,26 @@
|
||||
- (void)useSite {
|
||||
|
||||
MPSiteModel *selectedSite = [self selectedSite];
|
||||
if (selectedSite) {
|
||||
// Performing action while content is available. Copy it.
|
||||
[self copyContent:selectedSite.content];
|
||||
if (!selectedSite)
|
||||
return;
|
||||
|
||||
[self fadeOut];
|
||||
if (selectedSite.transient) {
|
||||
[self createNewSite:selectedSite.name];
|
||||
return;
|
||||
}
|
||||
|
||||
NSUserNotification *notification = [NSUserNotification new];
|
||||
notification.title = @"Password Copied";
|
||||
if (selectedSite.loginName.length)
|
||||
notification.subtitle = strf( @"%@ at %@", selectedSite.loginName, selectedSite.name );
|
||||
else
|
||||
notification.subtitle = selectedSite.name;
|
||||
[[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:notification];
|
||||
}
|
||||
else {
|
||||
NSString *siteName = [self.siteField stringValue];
|
||||
if ([siteName length])
|
||||
// Performing action without content but a site name is written.
|
||||
[self createNewSite:siteName];
|
||||
}
|
||||
// Performing action while content is available. Copy it.
|
||||
[self copyContent:selectedSite.content];
|
||||
|
||||
[self fadeOut];
|
||||
|
||||
NSUserNotification *notification = [NSUserNotification new];
|
||||
notification.title = @"Password Copied";
|
||||
if (selectedSite.loginName.length)
|
||||
notification.subtitle = strf( @"%@ at %@", selectedSite.loginName, selectedSite.name );
|
||||
else
|
||||
notification.subtitle = selectedSite.name;
|
||||
[[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:notification];
|
||||
}
|
||||
|
||||
- (void)updateUser {
|
||||
@ -518,6 +519,7 @@
|
||||
|
||||
- (void)updateSites {
|
||||
|
||||
NSAssert( [NSOperationQueue currentQueue] == [NSOperationQueue mainQueue], @"updateSites should be called on the main queue." );
|
||||
if (![MPMacAppDelegate get].key) {
|
||||
self.sites = nil;
|
||||
return;
|
||||
@ -530,13 +532,18 @@
|
||||
} );
|
||||
|
||||
NSString *queryString = self.siteField.stringValue;
|
||||
NSString *queryPattern = [queryString stringByReplacingMatchesOfExpression:fuzzyRE withTemplate:@"*$1*"];
|
||||
NSString *queryPattern;
|
||||
if ([queryString length] < 13)
|
||||
queryPattern = [queryString stringByReplacingMatchesOfExpression:fuzzyRE withTemplate:@"*$1*"];
|
||||
else
|
||||
// If query is too long, a wildcard per character makes the CoreData fetch take excessively long.
|
||||
queryPattern = strf( @"*%@*", queryString );
|
||||
NSMutableArray *fuzzyGroups = [NSMutableArray new];
|
||||
[fuzzyRE enumerateMatchesInString:queryString options:0 range:NSMakeRange( 0, queryString.length )
|
||||
usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
|
||||
[fuzzyGroups addObject:[queryString substringWithRange:result.range]];
|
||||
}];
|
||||
[MPMacAppDelegate managedObjectContextPerformBlockAndWait:^(NSManagedObjectContext *context) {
|
||||
[MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
||||
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPSiteEntity class] )];
|
||||
fetchRequest.sortDescriptors = @[ [[NSSortDescriptor alloc] initWithKey:@"lastUsed" ascending:NO] ];
|
||||
fetchRequest.predicate = [NSPredicate predicateWithFormat:@"(%@ == '' OR name LIKE[cd] %@) AND user == %@",
|
||||
@ -549,10 +556,22 @@
|
||||
return;
|
||||
}
|
||||
|
||||
BOOL exact = NO;
|
||||
NSMutableArray *newSites = [NSMutableArray arrayWithCapacity:[siteResults count]];
|
||||
for (MPSiteEntity *site in siteResults)
|
||||
for (MPSiteEntity *site in siteResults) {
|
||||
[newSites addObject:[[MPSiteModel alloc] initWithEntity:site fuzzyGroups:fuzzyGroups]];
|
||||
self.sites = newSites;
|
||||
exact |= [site.name isEqualToString:queryString];
|
||||
}
|
||||
if (!exact && [queryString length]) {
|
||||
MPUserEntity *activeUser = [[MPAppDelegate_Shared get] activeUserInContext:context];
|
||||
[newSites addObject:[[MPSiteModel alloc] initWithName:queryString forUser:activeUser]];
|
||||
}
|
||||
|
||||
dbg( @"newSites: %@", newSites );
|
||||
if (![newSites isEqualToArray:self.sites])
|
||||
PearlMainQueue( ^{
|
||||
self.sites = newSites;
|
||||
} );
|
||||
}];
|
||||
}
|
||||
|
||||
|
@ -19,6 +19,7 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "MPSiteEntity.h"
|
||||
#import "MPAlgorithm.h"
|
||||
#import "MPUserEntity.h"
|
||||
|
||||
@class MPSiteEntity;
|
||||
|
||||
@ -35,10 +36,12 @@
|
||||
@property (nonatomic) NSUInteger counter;
|
||||
@property (nonatomic) NSDate *lastUsed;
|
||||
@property (nonatomic) id<MPAlgorithm> algorithm;
|
||||
@property (nonatomic) BOOL generated;
|
||||
@property (nonatomic) BOOL stored;
|
||||
@property (nonatomic, readonly) BOOL generated;
|
||||
@property (nonatomic, readonly) BOOL stored;
|
||||
@property (nonatomic, readonly) BOOL transient;
|
||||
|
||||
- (instancetype)initWithEntity:(MPSiteEntity *)entity fuzzyGroups:(NSArray *)fuzzyGroups;
|
||||
- (instancetype)initWithName:(NSString *)siteName forUser:(MPUserEntity *)user;
|
||||
- (MPSiteEntity *)entityInContext:(NSManagedObjectContext *)moc;
|
||||
|
||||
- (void)updateContent;
|
||||
|
@ -28,7 +28,7 @@
|
||||
BOOL _initialized;
|
||||
}
|
||||
|
||||
- (id)initWithEntity:(MPSiteEntity *)entity fuzzyGroups:(NSArray *)fuzzyGroups {
|
||||
- (instancetype)initWithEntity:(MPSiteEntity *)entity fuzzyGroups:(NSArray *)fuzzyGroups {
|
||||
|
||||
if (!(self = [super init]))
|
||||
return nil;
|
||||
@ -39,6 +39,17 @@
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithName:(NSString *)siteName forUser:(MPUserEntity *)user {
|
||||
|
||||
if (!(self = [super init]))
|
||||
return nil;
|
||||
|
||||
[self setTransientSiteName:siteName forUser:user];
|
||||
_initialized = YES;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setEntity:(MPSiteEntity *)entity fuzzyGroups:(NSArray *)fuzzyGroups {
|
||||
|
||||
if ([_entityOID isEqual:entity.objectID])
|
||||
@ -59,7 +70,7 @@
|
||||
NSMutableParagraphStyle *paragraphStyle = [NSMutableParagraphStyle new];
|
||||
paragraphStyle.alignment = NSCenterTextAlignment;
|
||||
[attributedSiteName addAttribute:NSParagraphStyleAttributeName value:paragraphStyle range:NSMakeRange( 0, [siteName length] )];
|
||||
|
||||
|
||||
self.displayedName = attributedSiteName;
|
||||
self.name = siteName;
|
||||
self.algorithm = entity.algorithm;
|
||||
@ -73,6 +84,28 @@
|
||||
[self updateContent:entity];
|
||||
}
|
||||
|
||||
- (void)setTransientSiteName:(NSString *)siteName forUser:(MPUserEntity *)user {
|
||||
|
||||
_entityOID = nil;
|
||||
|
||||
NSMutableParagraphStyle *paragraphStyle = [NSMutableParagraphStyle new];
|
||||
paragraphStyle.alignment = NSCenterTextAlignment;
|
||||
self.displayedName = stra( siteName, @{
|
||||
NSBackgroundColorAttributeName : [NSColor alternateSelectedControlColor],
|
||||
NSParagraphStyleAttributeName : paragraphStyle,
|
||||
} );
|
||||
self.name = siteName;
|
||||
self.algorithm = MPAlgorithmDefault;
|
||||
self.lastUsed = nil;
|
||||
self.type = user.defaultType;
|
||||
self.typeName = [self.algorithm nameOfType:self.type];
|
||||
self.uses = @0;
|
||||
self.counter = 1;
|
||||
|
||||
// Find all password types and the index of the current type amongst them.
|
||||
[self updateContent];
|
||||
}
|
||||
|
||||
- (MPSiteEntity *)entityInContext:(NSManagedObjectContext *)moc {
|
||||
|
||||
if (!_entityOID)
|
||||
@ -96,15 +129,18 @@
|
||||
// This wasn't a change to the entity.
|
||||
return;
|
||||
|
||||
[MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
||||
MPSiteEntity *entity = [self entityInContext:context];
|
||||
if ([entity isKindOfClass:[MPGeneratedSiteEntity class]]) {
|
||||
((MPGeneratedSiteEntity *)entity).counter = counter;
|
||||
[context saveToStore];
|
||||
if (_entityOID)
|
||||
[MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
||||
MPSiteEntity *entity = [self entityInContext:context];
|
||||
if ([entity isKindOfClass:[MPGeneratedSiteEntity class]]) {
|
||||
((MPGeneratedSiteEntity *)entity).counter = counter;
|
||||
[context saveToStore];
|
||||
|
||||
[self updateContent:entity];
|
||||
}
|
||||
}];
|
||||
[self updateContent:entity];
|
||||
}
|
||||
}];
|
||||
else
|
||||
[self updateContent];
|
||||
}
|
||||
|
||||
- (BOOL)generated {
|
||||
@ -117,36 +153,60 @@
|
||||
return self.type & MPSiteTypeClassStored;
|
||||
}
|
||||
|
||||
- (BOOL)transient {
|
||||
|
||||
return _entityOID == nil;
|
||||
}
|
||||
|
||||
- (void)updateContent {
|
||||
|
||||
[MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
||||
[self updateContent:[MPSiteEntity existingObjectWithID:_entityOID inContext:context]];
|
||||
}];
|
||||
if (_entityOID)
|
||||
[MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
||||
[self updateContent:[MPSiteEntity existingObjectWithID:_entityOID inContext:context]];
|
||||
}];
|
||||
else
|
||||
PearlNotMainQueue( ^{
|
||||
NSString *password = [self.algorithm generatePasswordForSiteNamed:self.name ofType:self.type withCounter:self.counter
|
||||
usingKey:[MPAppDelegate_Shared get].key];
|
||||
NSString *loginName = [self.algorithm generateLoginForSiteNamed:self.name usingKey:[MPAppDelegate_Shared get].key];
|
||||
[self updatePasswordWithResult:password];
|
||||
[self updateLoginNameWithResult:loginName];
|
||||
} );
|
||||
}
|
||||
|
||||
- (void)updateContent:(MPSiteEntity *)entity {
|
||||
|
||||
[entity resolvePasswordUsingKey:[MPAppDelegate_Shared get].key result:^(NSString *result) {
|
||||
[self updatePasswordWithResult:result];
|
||||
}];
|
||||
[entity resolveLoginUsingKey:[MPAppDelegate_Shared get].key result:^(NSString *result) {
|
||||
[self updateLoginNameWithResult:result];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)updatePasswordWithResult:(NSString *)result {
|
||||
|
||||
static NSRegularExpression *re_anyChar;
|
||||
static dispatch_once_t once = 0;
|
||||
dispatch_once( &once, ^{
|
||||
re_anyChar = [NSRegularExpression regularExpressionWithPattern:@"." options:0 error:nil];
|
||||
} );
|
||||
|
||||
[entity resolvePasswordUsingKey:[MPAppDelegate_Shared get].key result:^(NSString *result) {
|
||||
NSString *displayResult = result;
|
||||
if ([[MPConfig get].hidePasswords boolValue] && !([NSEvent modifierFlags] & NSAlternateKeyMask))
|
||||
displayResult = [displayResult stringByReplacingMatchesOfExpression:re_anyChar withTemplate:@"●"];
|
||||
NSString *displayResult = result;
|
||||
if ([[MPConfig get].hidePasswords boolValue] && !([NSEvent modifierFlags] & NSAlternateKeyMask))
|
||||
displayResult = [displayResult stringByReplacingMatchesOfExpression:re_anyChar withTemplate:@"●"];
|
||||
|
||||
PearlMainQueue( ^{
|
||||
self.content = result;
|
||||
self.displayedContent = displayResult;
|
||||
} );
|
||||
}];
|
||||
[entity resolveLoginUsingKey:[MPAppDelegate_Shared get].key result:^(NSString *result) {
|
||||
PearlMainQueue( ^{
|
||||
self.loginName = result;
|
||||
} );
|
||||
}];
|
||||
PearlMainQueue( ^{
|
||||
self.content = result;
|
||||
self.displayedContent = displayResult;
|
||||
} );
|
||||
}
|
||||
|
||||
- (void)updateLoginNameWithResult:(NSString *)loginName {
|
||||
|
||||
PearlMainQueue( ^{
|
||||
self.loginName = loginName;
|
||||
} );
|
||||
}
|
||||
|
||||
@end
|
||||
|
@ -392,7 +392,12 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
|
||||
} );
|
||||
|
||||
NSString *queryString = self.query;
|
||||
NSString *queryPattern = [queryString stringByReplacingMatchesOfExpression:fuzzyRE withTemplate:@"*$1*"];
|
||||
NSString *queryPattern;
|
||||
if ([queryString length] < 13)
|
||||
queryPattern = [queryString stringByReplacingMatchesOfExpression:fuzzyRE withTemplate:@"*$1*"];
|
||||
else
|
||||
// If query is too long, a wildcard per character makes the CoreData fetch take excessively long.
|
||||
queryPattern = strf( @"*%@*", queryString );
|
||||
NSMutableArray *fuzzyGroups = [NSMutableArray arrayWithCapacity:[queryString length]];
|
||||
[fuzzyRE enumerateMatchesInString:queryString options:0 range:NSMakeRange( 0, queryString.length )
|
||||
usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
|
||||
|
@ -1 +1 @@
|
||||
Subproject commit 442e41896998e06e850b42f9f8ea4b33bb237bf1
|
||||
Subproject commit 28a988ebe6d1b8052ecc5190b5f9a1fd658b6cf8
|
Loading…
Reference in New Issue
Block a user