Compare commits
51 Commits
2.2-androi
...
2.3-releas
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7f5bb9e114 | ||
|
|
4c4aaac08b | ||
|
|
5320e079ce | ||
|
|
636c337c78 | ||
|
|
c2b08678df | ||
|
|
7117d82aa4 | ||
|
|
4fa6436de2 | ||
|
|
3e30087856 | ||
|
|
6bd2d70270 | ||
|
|
cf11b01ed6 | ||
|
|
7e11f01331 | ||
|
|
aeca5e18fe | ||
|
|
615e7c98a8 | ||
|
|
e713776123 | ||
|
|
7d5b7e53d4 | ||
|
|
950c68437a | ||
|
|
300a04f5c7 | ||
|
|
8faf6b48dd | ||
|
|
5f0367ad29 | ||
|
|
e126a55912 | ||
|
|
d05c5eedd8 | ||
|
|
6819a2ace5 | ||
|
|
645b6c5f54 | ||
|
|
d3c09fd979 | ||
|
|
a41ae1814a | ||
|
|
70f7fa1345 | ||
|
|
ea9d8cc275 | ||
|
|
20d1811b5c | ||
|
|
634ef062f3 | ||
|
|
a424531a8a | ||
|
|
9cffe53993 | ||
|
|
fd35fea8cf | ||
|
|
01c21e95bb | ||
|
|
5af3ffa178 | ||
|
|
651d07f982 | ||
|
|
a383d0eee7 | ||
|
|
ca8f14fd3e | ||
|
|
fd855bb025 | ||
|
|
af340806af | ||
|
|
cdeee2576d | ||
|
|
779d2776a0 | ||
|
|
563aab9a81 | ||
|
|
73de98c5e2 | ||
|
|
c330728ac3 | ||
|
|
2db601475f | ||
|
|
d898646097 | ||
|
|
afaa17948f | ||
|
|
8514a58e64 | ||
|
|
f80bbff46e | ||
|
|
f7d595b0e7 | ||
|
|
422066ad4a |
1
.gitignore
vendored
@@ -1,5 +1,6 @@
|
||||
# OS-Specific junk.
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# IntelliJ
|
||||
/MasterPassword/Java/.idea
|
||||
|
||||
8
.idea/encodings.xml
generated
Normal file → Executable file
@@ -1,5 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Encoding" useUTFGuessing="true" native2AsciiForPropertiesFiles="false" />
|
||||
<component name="Encoding" useUTFGuessing="true" native2AsciiForPropertiesFiles="false">
|
||||
<file url="file://$PROJECT_DIR$/MasterPassword/Java" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/MasterPassword/Java/masterpassword-algorithm" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/MasterPassword/Java/masterpassword-cli" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/MasterPassword/Java/masterpassword-gui" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/MasterPassword/Java/masterpassword-model" charset="UTF-8" />
|
||||
</component>
|
||||
</project>
|
||||
|
||||
|
||||
2
External/Pearl
vendored
@@ -218,3 +218,8 @@ OBJC_EXTERN void CLSNSLogv(NSString *format, va_list args) NS_FORMAT_FUNCTION(1,
|
||||
- (void)crashlytics:(Crashlytics *)crashlytics didDetectCrashDuringPreviousExecution:(id <CLSCrashReport>)crash;
|
||||
|
||||
@end
|
||||
|
||||
/**
|
||||
* `CrashlyticsKit` can be used as a parameter to `[Fabric with:@[CrashlyticsKit]];` in Objective-C. In Swift, simply use `Crashlytics()`
|
||||
*/
|
||||
#define CrashlyticsKit [Crashlytics sharedInstance]
|
||||
|
||||
@@ -15,13 +15,13 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>2.2.5</string>
|
||||
<string>2.2.9</string>
|
||||
<key>CFBundleSupportedPlatforms</key>
|
||||
<array>
|
||||
<string>iPhoneOS</string>
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>40</string>
|
||||
<string>44</string>
|
||||
<key>DTPlatformName</key>
|
||||
<string>iphoneos</string>
|
||||
<key>MinimumOSVersion</key>
|
||||
|
||||
BIN
External/iOS/Crashlytics.framework/run
vendored
BIN
External/iOS/Crashlytics.framework/submit
vendored
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
|
||||
}
|
||||
@@ -39,7 +39,7 @@ else
|
||||
fi
|
||||
|
||||
# Optional features.
|
||||
mpw_color=0 # Colorized Identicon, requires libncurses-dev
|
||||
mpw_color=1 # Colorized Identicon, requires libncurses-dev
|
||||
|
||||
|
||||
### DEPENDENCIES
|
||||
@@ -204,11 +204,11 @@ mpw() {
|
||||
|
||||
echo
|
||||
echo "Building target: $target..."
|
||||
CFLAGS=(
|
||||
local CFLAGS=(
|
||||
# include paths
|
||||
-I"lib/include"
|
||||
)
|
||||
LDFLAGS=(
|
||||
local LDFLAGS=(
|
||||
# scrypt
|
||||
"lib/scrypt/scrypt-crypto_aesctr.o"
|
||||
"lib/scrypt/scrypt-sha256.o"
|
||||
@@ -240,11 +240,11 @@ mpw-bench() {
|
||||
|
||||
echo
|
||||
echo "Building target: $target..."
|
||||
CFLAGS=(
|
||||
local CFLAGS=(
|
||||
# include paths
|
||||
-I"lib/include"
|
||||
)
|
||||
LDFLAGS=(
|
||||
local LDFLAGS=(
|
||||
# scrypt
|
||||
"lib/scrypt/scrypt-crypto_aesctr.o"
|
||||
"lib/scrypt/scrypt-sha256.o"
|
||||
@@ -264,6 +264,9 @@ mpw-bench() {
|
||||
-l"crypto"
|
||||
)
|
||||
|
||||
cc "${CFLAGS[@]}" "$@" -c mpw-algorithm.c -o mpw-algorithm.o
|
||||
cc "${CFLAGS[@]}" "$@" -c mpw-types.c -o mpw-types.o
|
||||
cc "${CFLAGS[@]}" "$@" -c mpw-util.c -o mpw-util.o
|
||||
cc "${CFLAGS[@]}" "$@" "mpw-algorithm.o" "mpw-types.o" "mpw-util.o" \
|
||||
"${LDFLAGS[@]}" "mpw-bench.c" -o "mpw-bench"
|
||||
echo "done! Now use ./mpw-bench"
|
||||
@@ -276,13 +279,13 @@ mpw-tests() {
|
||||
|
||||
echo
|
||||
echo "Building target: $target..."
|
||||
CFLAGS=(
|
||||
local CFLAGS=(
|
||||
# include paths
|
||||
-I"lib/include"
|
||||
-I"/usr/include/libxml2"
|
||||
-I"/usr/local/include/libxml2"
|
||||
)
|
||||
LDFLAGS=(
|
||||
local LDFLAGS=(
|
||||
# scrypt
|
||||
"lib/scrypt/scrypt-crypto_aesctr.o"
|
||||
"lib/scrypt/scrypt-sha256.o"
|
||||
@@ -296,7 +299,10 @@ mpw-tests() {
|
||||
-l"crypto" -l"xml2"
|
||||
)
|
||||
|
||||
cc "${CFLAGS[@]}" "$@" -c mpw-tests-util.c -o mpw-tests-util.o
|
||||
cc "${CFLAGS[@]}" "$@" -c mpw-algorithm.c -o mpw-algorithm.o
|
||||
cc "${CFLAGS[@]}" "$@" -c mpw-types.c -o mpw-types.o
|
||||
cc "${CFLAGS[@]}" "$@" -c mpw-util.c -o mpw-util.o
|
||||
cc "${CFLAGS[@]}" "$@" -c mpw-tests-util.c -o mpw-tests-util.o
|
||||
cc "${CFLAGS[@]}" "$@" "mpw-algorithm.o" "mpw-types.o" "mpw-util.o" "mpw-tests-util.o" \
|
||||
"${LDFLAGS[@]}" "mpw-tests.c" -o "mpw-tests"
|
||||
echo "done! Now use ./mpw-tests"
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <errno.h>
|
||||
#include <arpa/inet.h>
|
||||
|
||||
#include "mpw-types.h"
|
||||
#include "mpw-util.h"
|
||||
|
||||
#define MP_N 32768
|
||||
@@ -37,7 +38,8 @@ static const char mpw_characterFromClass_v0(char characterClass, uint16_t seedBy
|
||||
static const uint8_t *mpw_masterKeyForUser_v0(const char *fullName, const char *masterPassword) {
|
||||
|
||||
const char *mpKeyScope = mpw_scopeForVariant( MPSiteVariantPassword );
|
||||
trc( "fullName: %s\n", fullName );
|
||||
trc( "algorithm: v%d\n", 0 );
|
||||
trc( "fullName: %s (%zu)\n", fullName, mpw_charlen( fullName ) );
|
||||
trc( "masterPassword: %s\n", masterPassword );
|
||||
trc( "key scope: %s\n", mpKeyScope );
|
||||
|
||||
@@ -71,11 +73,16 @@ static const char *mpw_passwordForSite_v0(const uint8_t *masterKey, const char *
|
||||
const MPSiteVariant siteVariant, const char *siteContext) {
|
||||
|
||||
const char *siteScope = mpw_scopeForVariant( siteVariant );
|
||||
trc( "algorithm: v%d\n", 0 );
|
||||
trc( "siteName: %s\n", siteName );
|
||||
trc( "siteCounter: %d\n", siteCounter );
|
||||
trc( "siteVariant: %d\n", siteVariant );
|
||||
trc( "siteType: %d\n", siteType );
|
||||
trc( "site scope: %s, context: %s\n", siteScope, siteContext == NULL? "<empty>": siteContext );
|
||||
trc( "site scope: %s, context: %s\n", siteScope, siteContext? "<empty>": siteContext );
|
||||
trc( "seed from: hmac-sha256(masterKey, %s | %s | %s | %s | %s | %s)\n",
|
||||
siteScope, mpw_hex_l( htonl( strlen( siteName ) ) ), siteName,
|
||||
mpw_hex_l( htonl( siteCounter ) ),
|
||||
mpw_hex_l( htonl( siteContext? strlen( siteContext ): 0 ) ), siteContext? "(null)": siteContext );
|
||||
|
||||
// Calculate the site seed.
|
||||
// sitePasswordSeed = hmac-sha256( masterKey, siteScope . #siteName . siteName . siteCounter . #siteContext . siteContext )
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <errno.h>
|
||||
#include <arpa/inet.h>
|
||||
|
||||
#include "mpw-types.h"
|
||||
#include "mpw-util.h"
|
||||
|
||||
#define MP_N 32768
|
||||
@@ -21,7 +22,8 @@
|
||||
static const uint8_t *mpw_masterKeyForUser_v1(const char *fullName, const char *masterPassword) {
|
||||
|
||||
const char *mpKeyScope = mpw_scopeForVariant( MPSiteVariantPassword );
|
||||
trc( "fullName: %s\n", fullName );
|
||||
trc( "algorithm: v%d\n", 1 );
|
||||
trc( "fullName: %s (%zu)\n", fullName, mpw_charlen( fullName ) );
|
||||
trc( "masterPassword: %s\n", masterPassword );
|
||||
trc( "key scope: %s\n", mpKeyScope );
|
||||
|
||||
@@ -55,11 +57,16 @@ static const char *mpw_passwordForSite_v1(const uint8_t *masterKey, const char *
|
||||
const MPSiteVariant siteVariant, const char *siteContext) {
|
||||
|
||||
const char *siteScope = mpw_scopeForVariant( siteVariant );
|
||||
trc( "algorithm: v%d\n", 1 );
|
||||
trc( "siteName: %s\n", siteName );
|
||||
trc( "siteCounter: %d\n", siteCounter );
|
||||
trc( "siteVariant: %d\n", siteVariant );
|
||||
trc( "siteType: %d\n", siteType );
|
||||
trc( "site scope: %s, context: %s\n", siteScope, siteContext == NULL? "<empty>": siteContext );
|
||||
trc( "site scope: %s, context: %s\n", siteScope, siteContext? "<empty>": siteContext );
|
||||
trc( "seed from: hmac-sha256(masterKey, %s | %s | %s | %s | %s | %s)\n",
|
||||
siteScope, mpw_hex_l( htonl( strlen( siteName ) ) ), siteName,
|
||||
mpw_hex_l( htonl( siteCounter ) ),
|
||||
mpw_hex_l( htonl( siteContext? strlen( siteContext ): 0 ) ), siteContext? "(null)": siteContext );
|
||||
|
||||
// Calculate the site seed.
|
||||
// sitePasswordSeed = hmac-sha256( masterKey, siteScope . #siteName . siteName . siteCounter . #siteContext . siteContext )
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <errno.h>
|
||||
#include <arpa/inet.h>
|
||||
|
||||
#include "mpw-types.h"
|
||||
#include "mpw-util.h"
|
||||
|
||||
#define MP_N 32768
|
||||
@@ -21,7 +22,8 @@
|
||||
static const uint8_t *mpw_masterKeyForUser_v2(const char *fullName, const char *masterPassword) {
|
||||
|
||||
const char *mpKeyScope = mpw_scopeForVariant( MPSiteVariantPassword );
|
||||
trc( "fullName: %s\n", fullName );
|
||||
trc( "algorithm: v%d\n", 2 );
|
||||
trc( "fullName: %s (%zu)\n", fullName, mpw_charlen( fullName ) );
|
||||
trc( "masterPassword: %s\n", masterPassword );
|
||||
trc( "key scope: %s\n", mpKeyScope );
|
||||
|
||||
@@ -55,11 +57,16 @@ static const char *mpw_passwordForSite_v2(const uint8_t *masterKey, const char *
|
||||
const MPSiteVariant siteVariant, const char *siteContext) {
|
||||
|
||||
const char *siteScope = mpw_scopeForVariant( siteVariant );
|
||||
trc( "algorithm: v%d\n", 2 );
|
||||
trc( "siteName: %s\n", siteName );
|
||||
trc( "siteCounter: %d\n", siteCounter );
|
||||
trc( "siteVariant: %d\n", siteVariant );
|
||||
trc( "siteType: %d\n", siteType );
|
||||
trc( "site scope: %s, context: %s\n", siteScope, siteContext == NULL? "<empty>": siteContext );
|
||||
trc( "site scope: %s, context: %s\n", siteScope, siteContext? "<empty>": siteContext );
|
||||
trc( "seed from: hmac-sha256(masterKey, %s | %s | %s | %s | %s | %s)\n",
|
||||
siteScope, mpw_hex_l( htonl( strlen( siteName ) ) ), siteName,
|
||||
mpw_hex_l( htonl( siteCounter ) ),
|
||||
mpw_hex_l( htonl( siteContext? strlen( siteContext ): 0 ) ), siteContext? "(null)": siteContext );
|
||||
|
||||
// Calculate the site seed.
|
||||
// sitePasswordSeed = hmac-sha256( masterKey, siteScope . #siteName . siteName . siteCounter . #siteContext . siteContext )
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <errno.h>
|
||||
#include <arpa/inet.h>
|
||||
|
||||
#include "mpw-types.h"
|
||||
#include "mpw-util.h"
|
||||
|
||||
#define MP_N 32768
|
||||
@@ -21,7 +22,8 @@
|
||||
static const uint8_t *mpw_masterKeyForUser_v3(const char *fullName, const char *masterPassword) {
|
||||
|
||||
const char *mpKeyScope = mpw_scopeForVariant( MPSiteVariantPassword );
|
||||
trc( "fullName: %s\n", fullName );
|
||||
trc( "algorithm: v%d\n", 3 );
|
||||
trc( "fullName: %s (%zu)\n", fullName, strlen( fullName ) );
|
||||
trc( "masterPassword: %s\n", masterPassword );
|
||||
trc( "key scope: %s\n", mpKeyScope );
|
||||
|
||||
@@ -55,11 +57,16 @@ static const char *mpw_passwordForSite_v3(const uint8_t *masterKey, const char *
|
||||
const MPSiteVariant siteVariant, const char *siteContext) {
|
||||
|
||||
const char *siteScope = mpw_scopeForVariant( siteVariant );
|
||||
trc( "algorithm: v%d\n", 3 );
|
||||
trc( "siteName: %s\n", siteName );
|
||||
trc( "siteCounter: %d\n", siteCounter );
|
||||
trc( "siteVariant: %d\n", siteVariant );
|
||||
trc( "siteType: %d\n", siteType );
|
||||
trc( "site scope: %s, context: %s\n", siteScope, siteContext == NULL? "<empty>": siteContext );
|
||||
trc( "site scope: %s, context: %s\n", siteScope, siteContext? "<empty>": siteContext );
|
||||
trc( "seed from: hmac-sha256(masterKey, %s | %s | %s | %s | %s | %s)\n",
|
||||
siteScope, mpw_hex_l( htonl( strlen( siteName ) ) ), siteName,
|
||||
mpw_hex_l( htonl( siteCounter ) ),
|
||||
mpw_hex_l( htonl( siteContext? strlen( siteContext ): 0 ) ), siteContext? "(null)": siteContext );
|
||||
|
||||
// Calculate the site seed.
|
||||
// sitePasswordSeed = hmac-sha256( masterKey, siteScope . #siteName . siteName . siteCounter . #siteContext . siteContext )
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
|
||||
static void usage() {
|
||||
|
||||
fprintf( stderr, "Usage: mpw [-u name] [-t type] [-c counter] site\n\n" );
|
||||
fprintf( stderr, "Usage: mpw [-u name] [-t type] [-c counter] [-V version] [-v variant] [-C context] [-h] site\n\n" );
|
||||
fprintf( stderr, " -u name Specify the full name of the user.\n"
|
||||
" Defaults to %s in env.\n\n", MP_env_fullname );
|
||||
fprintf( stderr, " -t type Specify the password's template.\n"
|
||||
|
||||
@@ -21,6 +21,7 @@ int main(int argc, char *const argv[]) {
|
||||
|
||||
// Read in the test case.
|
||||
xmlChar *id = mpw_xmlTestCaseString( testCase, "id" );
|
||||
uint32_t algorithm = mpw_xmlTestCaseInteger( testCase, "algorithm" );
|
||||
xmlChar *fullName = mpw_xmlTestCaseString( testCase, "fullName" );
|
||||
xmlChar *masterPassword = mpw_xmlTestCaseString( testCase, "masterPassword" );
|
||||
xmlChar *keyID = mpw_xmlTestCaseString( testCase, "keyID" );
|
||||
@@ -36,16 +37,20 @@ int main(int argc, char *const argv[]) {
|
||||
|
||||
// Run the test case.
|
||||
fprintf( stdout, "test case %s... ", id );
|
||||
if (!xmlStrlen( result )) {
|
||||
fprintf( stdout, "abstract.\n" );
|
||||
continue;
|
||||
}
|
||||
|
||||
// 1. calculate the master key.
|
||||
const uint8_t *masterKey = mpw_masterKeyForUser(
|
||||
(char *)fullName, (char *)masterPassword, MPAlgorithmVersionCurrent );
|
||||
(char *)fullName, (char *)masterPassword, algorithm );
|
||||
if (!masterKey)
|
||||
ftl( "Couldn't derive master key." );
|
||||
|
||||
// 2. calculate the site password.
|
||||
const char *sitePassword = mpw_passwordForSite(
|
||||
masterKey, (char *)siteName, siteType, siteCounter, siteVariant, (char *)siteContext, MPAlgorithmVersionCurrent );
|
||||
masterKey, (char *)siteName, siteType, siteCounter, siteVariant, (char *)siteContext, algorithm );
|
||||
mpw_free( masterKey, MP_dkLen );
|
||||
if (!sitePassword)
|
||||
ftl( "Couldn't derive site password." );
|
||||
@@ -56,7 +61,7 @@ int main(int argc, char *const argv[]) {
|
||||
|
||||
else {
|
||||
++failedTests;
|
||||
fprintf( stdout, "FAILED! (result %s != expected %s)\n", result, sitePassword );
|
||||
fprintf( stdout, "FAILED! (got %s != expected %s)\n", sitePassword, result );
|
||||
}
|
||||
|
||||
// Free test case.
|
||||
|
||||
@@ -21,15 +21,17 @@
|
||||
|
||||
const MPSiteType mpw_typeWithName(const char *typeName) {
|
||||
|
||||
// Lower-case and trim optionally leading "Generated" string from typeName to standardize it.
|
||||
size_t stdTypeNameOffset = 0;
|
||||
size_t stdTypeNameSize = strlen( typeName );
|
||||
char stdTypeName[strlen( typeName )];
|
||||
if (stdTypeNameSize > strlen( "generated" ))
|
||||
strcpy( stdTypeName, typeName + strlen( "generated" ) );
|
||||
else
|
||||
strcpy( stdTypeName, typeName );
|
||||
for (char *tN = stdTypeName; *tN; ++tN)
|
||||
*tN = (char)tolower( *tN );
|
||||
if (strstr(typeName, "Generated" ) == typeName)
|
||||
stdTypeNameSize -= (stdTypeNameOffset = strlen( "Generated" ));
|
||||
char stdTypeName[stdTypeNameSize + 1];
|
||||
for (size_t c = 0; c < stdTypeNameSize; ++c)
|
||||
stdTypeName[c] = (char)tolower( typeName[c + stdTypeNameOffset] );
|
||||
stdTypeName[stdTypeNameSize] = '\0';
|
||||
|
||||
// Find what site type is represented by the type name.
|
||||
if (0 == strcmp( stdTypeName, "x" ) || 0 == strcmp( stdTypeName, "max" ) || 0 == strcmp( stdTypeName, "maximum" ))
|
||||
return MPSiteTypeGeneratedMaximum;
|
||||
if (0 == strcmp( stdTypeName, "l" ) || 0 == strcmp( stdTypeName, "long" ))
|
||||
@@ -47,11 +49,10 @@ const MPSiteType mpw_typeWithName(const char *typeName) {
|
||||
if (0 == strcmp( stdTypeName, "p" ) || 0 == strcmp( stdTypeName, "phrase" ))
|
||||
return MPSiteTypeGeneratedPhrase;
|
||||
|
||||
fprintf( stderr, "Not a generated type name: %s", stdTypeName );
|
||||
abort();
|
||||
ftl( "Not a generated type name: %s", stdTypeName );
|
||||
}
|
||||
|
||||
inline const char **mpw_templatesForType(MPSiteType type, size_t *count) {
|
||||
const char **mpw_templatesForType(MPSiteType type, size_t *count) {
|
||||
|
||||
if (!(type & MPSiteTypeClassGenerated)) {
|
||||
ftl( "Not a generated type: %d", type );
|
||||
@@ -61,42 +62,42 @@ inline const char **mpw_templatesForType(MPSiteType type, size_t *count) {
|
||||
|
||||
switch (type) {
|
||||
case MPSiteTypeGeneratedMaximum: {
|
||||
*count = 2;
|
||||
return (const char *[]){ "anoxxxxxxxxxxxxxxxxx", "axxxxxxxxxxxxxxxxxno" };
|
||||
return alloc_array( *count, const char *,
|
||||
"anoxxxxxxxxxxxxxxxxx", "axxxxxxxxxxxxxxxxxno" );
|
||||
}
|
||||
case MPSiteTypeGeneratedLong: {
|
||||
*count = 21;
|
||||
return (const char *[]){ "CvcvnoCvcvCvcv", "CvcvCvcvnoCvcv", "CvcvCvcvCvcvno",
|
||||
"CvccnoCvcvCvcv", "CvccCvcvnoCvcv", "CvccCvcvCvcvno",
|
||||
"CvcvnoCvccCvcv", "CvcvCvccnoCvcv", "CvcvCvccCvcvno",
|
||||
"CvcvnoCvcvCvcc", "CvcvCvcvnoCvcc", "CvcvCvcvCvccno",
|
||||
"CvccnoCvccCvcv", "CvccCvccnoCvcv", "CvccCvccCvcvno",
|
||||
"CvcvnoCvccCvcc", "CvcvCvccnoCvcc", "CvcvCvccCvccno",
|
||||
"CvccnoCvcvCvcc", "CvccCvcvnoCvcc", "CvccCvcvCvccno" };
|
||||
return alloc_array( *count, const char *,
|
||||
"CvcvnoCvcvCvcv", "CvcvCvcvnoCvcv", "CvcvCvcvCvcvno",
|
||||
"CvccnoCvcvCvcv", "CvccCvcvnoCvcv", "CvccCvcvCvcvno",
|
||||
"CvcvnoCvccCvcv", "CvcvCvccnoCvcv", "CvcvCvccCvcvno",
|
||||
"CvcvnoCvcvCvcc", "CvcvCvcvnoCvcc", "CvcvCvcvCvccno",
|
||||
"CvccnoCvccCvcv", "CvccCvccnoCvcv", "CvccCvccCvcvno",
|
||||
"CvcvnoCvccCvcc", "CvcvCvccnoCvcc", "CvcvCvccCvccno",
|
||||
"CvccnoCvcvCvcc", "CvccCvcvnoCvcc", "CvccCvcvCvccno" );
|
||||
}
|
||||
case MPSiteTypeGeneratedMedium: {
|
||||
*count = 2;
|
||||
return (const char *[]){ "CvcnoCvc", "CvcCvcno" };
|
||||
return alloc_array( *count, const char *,
|
||||
"CvcnoCvc", "CvcCvcno" );
|
||||
}
|
||||
case MPSiteTypeGeneratedBasic: {
|
||||
*count = 3;
|
||||
return (const char *[]){ "aaanaaan", "aannaaan", "aaannaaa" };
|
||||
return alloc_array( *count, const char *,
|
||||
"aaanaaan", "aannaaan", "aaannaaa" );
|
||||
}
|
||||
case MPSiteTypeGeneratedShort: {
|
||||
*count = 1;
|
||||
return (const char *[]){"Cvcn"};
|
||||
return alloc_array( *count, const char *,
|
||||
"Cvcn" );
|
||||
}
|
||||
case MPSiteTypeGeneratedPIN: {
|
||||
*count = 1;
|
||||
return (const char *[]){ "nnnn" };
|
||||
return alloc_array( *count, const char *,
|
||||
"nnnn" );
|
||||
}
|
||||
case MPSiteTypeGeneratedName: {
|
||||
*count = 1;
|
||||
return (const char *[]) {"cvccvcvcv"};
|
||||
return alloc_array( *count, const char *,
|
||||
"cvccvcvcv" );
|
||||
}
|
||||
case MPSiteTypeGeneratedPhrase: {
|
||||
*count = 3;
|
||||
return (const char *[]){ "cvcc cvc cvccvcv cvc", "cvc cvccvcvcv cvcv", "cv cvccv cvc cvcvccv" };
|
||||
return alloc_array( *count, const char *,
|
||||
"cvcc cvc cvccvcv cvc", "cvc cvccvcvcv cvcv", "cv cvccv cvc cvcvccv" );
|
||||
}
|
||||
default: {
|
||||
ftl( "Unknown generated type: %d", type );
|
||||
@@ -113,15 +114,19 @@ const char *mpw_templateForType(MPSiteType type, uint8_t seedByte) {
|
||||
if (!count)
|
||||
return NULL;
|
||||
|
||||
return templates[seedByte % count];
|
||||
char const *template = templates[seedByte % count];
|
||||
free( templates );
|
||||
return template;
|
||||
}
|
||||
|
||||
const MPSiteVariant mpw_variantWithName(const char *variantName) {
|
||||
|
||||
char stdVariantName[strlen( variantName )];
|
||||
strcpy( stdVariantName, variantName );
|
||||
for (char *vN = stdVariantName; *vN; ++vN)
|
||||
*vN = (char)tolower( *vN );
|
||||
// Lower-case and trim optionally leading "generated" string from typeName to standardize it.
|
||||
size_t stdVariantNameSize = strlen( variantName );
|
||||
char stdVariantName[stdVariantNameSize + 1];
|
||||
for (size_t c = 0; c < stdVariantNameSize; ++c)
|
||||
stdVariantName[c] = (char)tolower( variantName[c] );
|
||||
stdVariantName[stdVariantNameSize] = '\0';
|
||||
|
||||
if (0 == strcmp( stdVariantName, "p" ) || 0 == strcmp( stdVariantName, "password" ))
|
||||
return MPSiteVariantPassword;
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
// Copyright (c) 2014 Lyndir. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef _MPW_TYPES_H
|
||||
#define _MPW_TYPES_H
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
|
||||
@@ -73,9 +75,10 @@ const char *mpw_scopeForVariant(MPSiteVariant variant);
|
||||
const MPSiteType mpw_typeWithName(const char *typeName);
|
||||
|
||||
/**
|
||||
* @return An array of internal strings that express the templates to use for the given type.
|
||||
* @return A newly allocated array of internal strings that express the templates to use for the given type.
|
||||
* The amount of elements in the array is stored in count.
|
||||
* If an unsupported type is given, count will be 0 and will return NULL.
|
||||
* The array needs to be free'ed, the strings themselves must not be free'ed or modified.
|
||||
*/
|
||||
const char **mpw_templatesForType(MPSiteType type, size_t *count);
|
||||
/**
|
||||
@@ -93,3 +96,4 @@ const char *mpw_charactersInClass(char characterClass);
|
||||
*/
|
||||
const char mpw_characterFromClass(char characterClass, uint8_t seedByte);
|
||||
|
||||
#endif // _MPW_TYPES_H
|
||||
|
||||
@@ -9,7 +9,12 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <locale.h>
|
||||
|
||||
#ifdef COLOR
|
||||
#include <unistd.h>
|
||||
#include <curses.h>
|
||||
#include <term.h>
|
||||
#endif
|
||||
|
||||
#include <scrypt/sha256.h>
|
||||
#include <scrypt/crypto_scrypt.h>
|
||||
@@ -75,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;
|
||||
|
||||
@@ -91,20 +96,35 @@ const char *mpw_idForBuf(const void *buf, size_t length) {
|
||||
return mpw_hex( hash, 32 );
|
||||
}
|
||||
|
||||
static char *mpw_hex_buf = NULL;
|
||||
static char **mpw_hex_buf = NULL;
|
||||
static unsigned int mpw_hex_buf_i = 0;
|
||||
|
||||
const char *mpw_hex(const void *buf, size_t length) {
|
||||
|
||||
mpw_hex_buf = realloc( mpw_hex_buf, length * 2 + 1 );
|
||||
for (size_t kH = 0; kH < length; kH++)
|
||||
sprintf( &(mpw_hex_buf[kH * 2]), "%02X", ((const uint8_t *)buf)[kH] );
|
||||
// FIXME
|
||||
if (!mpw_hex_buf) {
|
||||
mpw_hex_buf = malloc( 10 * sizeof( char * ) );
|
||||
for (uint8_t i = 0; i < 10; ++i)
|
||||
mpw_hex_buf[i] = NULL;
|
||||
}
|
||||
mpw_hex_buf_i = (mpw_hex_buf_i + 1) % 10;
|
||||
|
||||
return mpw_hex_buf;
|
||||
mpw_hex_buf[mpw_hex_buf_i] = realloc( mpw_hex_buf[mpw_hex_buf_i], length * 2 + 1 );
|
||||
for (size_t kH = 0; kH < length; kH++)
|
||||
sprintf( &(mpw_hex_buf[mpw_hex_buf_i][kH * 2]), "%02X", ((const uint8_t *)buf)[kH] );
|
||||
|
||||
return mpw_hex_buf[mpw_hex_buf_i];
|
||||
}
|
||||
|
||||
const char *mpw_hex_l(uint32_t number) {
|
||||
|
||||
return mpw_hex( &number, sizeof( number ) );
|
||||
}
|
||||
|
||||
#ifdef COLOR
|
||||
static int putvari;
|
||||
static char *putvarc = NULL;
|
||||
static bool istermsetup = false;
|
||||
static int istermsetup = 0;
|
||||
static void initputvar() {
|
||||
if (putvarc)
|
||||
free(putvarc);
|
||||
@@ -128,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 );
|
||||
@@ -165,8 +186,33 @@ const char *mpw_identicon(const char *fullName, const char *masterPassword) {
|
||||
return identicon;
|
||||
}
|
||||
|
||||
const size_t mpw_charlen(const char *string) {
|
||||
/**
|
||||
* @return the amount of bytes used by UTF-8 to encode a single character that starts with the given byte.
|
||||
*/
|
||||
static int mpw_charByteSize(unsigned char utf8Byte) {
|
||||
|
||||
setlocale( LC_ALL, "en_US.UTF-8" );
|
||||
return mbstowcs( NULL, string, strlen( string ) );
|
||||
if (!utf8Byte)
|
||||
return 0;
|
||||
if ((utf8Byte & 0x80) == 0)
|
||||
return 1;
|
||||
if ((utf8Byte & 0xC0) != 0xC0)
|
||||
return 0;
|
||||
if ((utf8Byte & 0xE0) == 0xC0)
|
||||
return 2;
|
||||
if ((utf8Byte & 0xF0) == 0xE0)
|
||||
return 3;
|
||||
if ((utf8Byte & 0xF8) == 0xF0)
|
||||
return 4;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
const size_t mpw_charlen(const char *utf8String) {
|
||||
|
||||
size_t charlen = 0;
|
||||
char *remainingString = (char *)utf8String;
|
||||
for (int charByteSize; (charByteSize = mpw_charByteSize( (unsigned char)*remainingString )); remainingString += charByteSize)
|
||||
++charlen;
|
||||
|
||||
return charlen;
|
||||
}
|
||||
|
||||
@@ -25,6 +25,14 @@
|
||||
|
||||
//// Buffers and memory.
|
||||
|
||||
#define alloc_array(_count, _type, ...) ({ \
|
||||
_type stackElements[] = { __VA_ARGS__ }; \
|
||||
_count = sizeof( stackElements ) / sizeof( _type ); \
|
||||
_type *allocElements = malloc( sizeof( stackElements ) ); \
|
||||
memcpy( allocElements, stackElements, sizeof( stackElements ) ); \
|
||||
allocElements; \
|
||||
})
|
||||
|
||||
/** Push a buffer onto a buffer. reallocs the given buffer and appends the given buffer. */
|
||||
void mpw_pushBuf(
|
||||
uint8_t **const buffer, size_t *const bufferSize, const void *pushBuffer, const size_t pushSize);
|
||||
@@ -58,6 +66,7 @@ uint8_t const *mpw_hmac_sha256(
|
||||
/** Encode a buffer as a string of hexadecimal characters.
|
||||
* @return A C-string in a reused buffer, do not free or store it. */
|
||||
const char *mpw_hex(const void *buf, size_t length);
|
||||
const char *mpw_hex_l(uint32_t number);
|
||||
/** Encode a fingerprint for a buffer.
|
||||
* @return A C-string in a reused buffer, do not free or store it. */
|
||||
const char *mpw_idForBuf(const void *buf, size_t length);
|
||||
@@ -67,4 +76,5 @@ const char *mpw_identicon(const char *fullName, const char *masterPassword);
|
||||
|
||||
//// String utilities.
|
||||
|
||||
const size_t mpw_charlen(const char *string);
|
||||
/** @return The amount of display characters in the given UTF-8 string. */
|
||||
const size_t mpw_charlen(const char *utf8String);
|
||||
|
||||
69
MasterPassword/C/mpw.completion.bash
Normal file
@@ -0,0 +1,69 @@
|
||||
#!/usr/bin/env bash
|
||||
source bashcomplib
|
||||
|
||||
# completing the 'mpw' command.
|
||||
_comp_mpw() {
|
||||
local optarg= cword=${COMP_WORDS[COMP_CWORD]} pcword=
|
||||
(( COMP_CWORD )) && pcword=${COMP_WORDS[COMP_CWORD - 1]}
|
||||
|
||||
case $pcword in
|
||||
-u) # complete full names.
|
||||
COMPREPLY=( ~/.mpw.d/*.mpsites )
|
||||
[[ -e $COMPREPLY ]] || COMPREPLY=()
|
||||
COMPREPLY=( "${COMPREPLY[@]##*/}" ) COMPREPLY=( "${COMPREPLY[@]%.mpsites}" )
|
||||
;;
|
||||
-t) # complete types.
|
||||
COMPREPLY=( maximum long medium basic short pin name phrase )
|
||||
;;
|
||||
-c) # complete counter.
|
||||
COMPREPLY=( 1 )
|
||||
;;
|
||||
-V) # complete versions.
|
||||
COMPREPLY=( 0 1 2 3 )
|
||||
;;
|
||||
-v) # complete variants.
|
||||
COMPREPLY=( password login answer )
|
||||
;;
|
||||
-C) # 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
|
||||
if [[ -e ~/.mpw.d/"$fullName.mpsites" ]]; then
|
||||
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"
|
||||
else
|
||||
# Default list from the Alexa Top 500
|
||||
COMPREPLY=(
|
||||
163.com 360.cn 9gag.com adobe.com alibaba.com aliexpress.com amazon.com
|
||||
apple.com archive.org ask.com baidu.com battle.net booking.com buzzfeed.com
|
||||
chase.com cnn.com comcast.net craigslist.org dailymotion.com dell.com
|
||||
deviantart.com diply.com disqus.com dropbox.com ebay.com engadget.com
|
||||
espn.go.com evernote.com facebook.com fedex.com feedly.com flickr.com
|
||||
flipkart.com github.com gizmodo.com go.com goodreads.com google.com
|
||||
huffingtonpost.com hulu.com ign.com ikea.com imdb.com imgur.com
|
||||
indiatimes.com instagram.com jd.com kickass.to kickstarter.com linkedin.com
|
||||
live.com livedoor.com mail.ru mozilla.org naver.com netflix.com newegg.com
|
||||
nicovideo.jp nytimes.com pandora.com paypal.com pinterest.com pornhub.com
|
||||
qq.com rakuten.co.jp reddit.com redtube.com shutterstock.com skype.com
|
||||
soso.com spiegel.de spotify.com stackexchange.com steampowered.com
|
||||
stumbleupon.com taobao.com target.com thepiratebay.se tmall.com
|
||||
torrentz.eu tripadvisor.com tube8.com tubecup.com tudou.com tumblr.com
|
||||
twitter.com uol.com.br vimeo.com vk.com walmart.com weibo.com whatsapp.com
|
||||
wikia.com wikipedia.org wired.com wordpress.com xhamster.com xinhuanet.com
|
||||
xvideos.com yahoo.com yandex.ru yelp.com youku.com youporn.com ziddu.com
|
||||
)
|
||||
fi
|
||||
fi ;;
|
||||
esac
|
||||
_comp_finish_completions
|
||||
}
|
||||
|
||||
#complete -F _show_args mpw
|
||||
complete -o nospace -F _comp_mpw mpw
|
||||
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>com.lyndir.masterpassword</groupId>
|
||||
<artifactId>masterpassword</artifactId>
|
||||
<version>2.2</version>
|
||||
<version>2.3</version>
|
||||
</parent>
|
||||
|
||||
<name>Master Password Algorithm Implementation</name>
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
package com.lyndir.masterpassword;
|
||||
|
||||
import static com.lyndir.lhunath.opal.system.util.StringUtils.strf;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.lyndir.lhunath.opal.system.MessageAuthenticationDigests;
|
||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||
import java.awt.*;
|
||||
import java.nio.*;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 15-03-29
|
||||
*/
|
||||
public class MPIdenticon {
|
||||
|
||||
@SuppressWarnings("UnusedDeclaration")
|
||||
private static final Logger logger = Logger.get( MPIdenticon.class );
|
||||
|
||||
private static final Charset charset = StandardCharsets.UTF_8;
|
||||
private static final Color[] colors = new Color[]{
|
||||
Color.RED, Color.GREEN, Color.YELLOW, Color.BLUE, Color.MAGENTA, Color.CYAN, Color.MONO };
|
||||
private static final char[] leftArm = new char[]{ '╔', '╚', '╰', '═' };
|
||||
private static final char[] rightArm = new char[]{ '╗', '╝', '╯', '═' };
|
||||
private static final char[] body = new char[]{ '█', '░', '▒', '▓', '☺', '☻' };
|
||||
private static final char[] accessory = new char[]{
|
||||
'◈', '◎', '◐', '◑', '◒', '◓', '☀', '☁', '☂', '☃', '☄', '★', '☆', '☎', '☏', '⎈', '⌂', '☘', '☢', '☣', '☕', '⌚', '⌛', '⏰', '⚡',
|
||||
'⛄', '⛅', '☔', '♔', '♕', '♖', '♗', '♘', '♙', '♚', '♛', '♜', '♝', '♞', '♟', '♨', '♩', '♪', '♫', '⚐', '⚑', '⚔', '⚖', '⚙', '⚠',
|
||||
'⌘', '⏎', '✄', '✆', '✈', '✉', '✌' };
|
||||
|
||||
private final String fullName;
|
||||
private final Color color;
|
||||
private final String text;
|
||||
|
||||
public MPIdenticon(String fullName, String masterPassword) {
|
||||
this( fullName, masterPassword.toCharArray() );
|
||||
}
|
||||
|
||||
public MPIdenticon(String fullName, char[] masterPassword) {
|
||||
this.fullName = fullName;
|
||||
|
||||
byte[] masterPasswordBytes = charset.encode( CharBuffer.wrap( masterPassword ) ).array();
|
||||
ByteBuffer identiconSeedBytes = ByteBuffer.wrap(
|
||||
MessageAuthenticationDigests.HmacSHA256.of( masterPasswordBytes, fullName.getBytes( charset ) ) );
|
||||
Arrays.fill( masterPasswordBytes, (byte) 0 );
|
||||
|
||||
IntBuffer identiconSeedBuffer = IntBuffer.allocate( identiconSeedBytes.capacity() );
|
||||
while (identiconSeedBytes.hasRemaining())
|
||||
identiconSeedBuffer.put( identiconSeedBytes.get() & 0xFF );
|
||||
int[] identiconSeed = identiconSeedBuffer.array();
|
||||
|
||||
color = colors[identiconSeed[4] % colors.length];
|
||||
text = strf( "%c%c%c%c", leftArm[identiconSeed[0] % leftArm.length], body[identiconSeed[1] % body.length],
|
||||
rightArm[identiconSeed[2] % rightArm.length], accessory[identiconSeed[3] % accessory.length] );
|
||||
}
|
||||
|
||||
public String getFullName() {
|
||||
return fullName;
|
||||
}
|
||||
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
|
||||
public Color getColor() {
|
||||
return color;
|
||||
}
|
||||
|
||||
public enum BackgroundMode {
|
||||
DARK, LIGHT
|
||||
}
|
||||
|
||||
|
||||
public enum Color {
|
||||
RED( "#dc322f", "#dc322f" ),
|
||||
GREEN( "#859900", "#859900" ),
|
||||
YELLOW( "#b58900", "#b58900" ),
|
||||
BLUE( "#268bd2", "#268bd2" ),
|
||||
MAGENTA( "#d33682", "#d33682" ),
|
||||
CYAN( "#2aa198", "#2aa198" ),
|
||||
MONO( "#93a1a1", "#586e75" );
|
||||
|
||||
private final String rgbDark;
|
||||
private final String rgbLight;
|
||||
|
||||
Color(final String rgbDark, final String rgbLight) {
|
||||
this.rgbDark = rgbDark;
|
||||
this.rgbLight = rgbLight;
|
||||
}
|
||||
|
||||
public java.awt.Color getAWTColor(BackgroundMode backgroundMode) {
|
||||
switch (backgroundMode) {
|
||||
case DARK:
|
||||
return new java.awt.Color( Integer.decode( rgbDark ) );
|
||||
case LIGHT:
|
||||
return new java.awt.Color( Integer.decode( rgbLight ) );
|
||||
}
|
||||
|
||||
throw new UnsupportedOperationException( "Unsupported background mode: " + backgroundMode );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import javax.annotation.Nullable;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
|
||||
|
||||
/**
|
||||
@@ -147,6 +148,7 @@ public enum MPSiteType {
|
||||
*
|
||||
* @return The type registered with the given name.
|
||||
*/
|
||||
@Contract("!null -> !null, null -> null")
|
||||
public static MPSiteType forName(@Nullable final String name) {
|
||||
|
||||
if (name == null)
|
||||
|
||||
@@ -4,6 +4,7 @@ import com.google.common.collect.ImmutableList;
|
||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||
import java.util.List;
|
||||
import javax.annotation.Nullable;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
|
||||
|
||||
/**
|
||||
@@ -66,6 +67,7 @@ public enum MPSiteVariant {
|
||||
*
|
||||
* @return The variant registered with the given name.
|
||||
*/
|
||||
@Contract("!null -> !null, null -> null")
|
||||
public static MPSiteVariant forName(@Nullable final String name) {
|
||||
|
||||
if (name == null)
|
||||
|
||||
@@ -65,6 +65,7 @@ public abstract class MasterKey {
|
||||
@Nonnull
|
||||
protected byte[] getKey() {
|
||||
|
||||
Preconditions.checkState( isValid() );
|
||||
return Preconditions.checkNotNull( masterKey );
|
||||
}
|
||||
|
||||
|
||||
@@ -58,8 +58,11 @@ public class MasterKeyV0 extends MasterKey {
|
||||
logger.trc( "key scope: %s", mpKeyScope );
|
||||
logger.trc( "masterKeySalt ID: %s", CodeUtils.encodeHex( idForBytes( masterKeySalt ) ) );
|
||||
|
||||
CharBuffer mpChars = CharBuffer.wrap( masterPassword );
|
||||
byte[] mpBytes = MP_charset.encode( mpChars ).array();
|
||||
ByteBuffer mpBytesBuf = MP_charset.encode( CharBuffer.wrap( masterPassword ) );
|
||||
byte[] mpBytes = new byte[mpBytesBuf.remaining()];
|
||||
mpBytesBuf.get( mpBytes, 0, mpBytes.length );
|
||||
Arrays.fill( mpBytesBuf.array(), (byte) 0 );
|
||||
|
||||
try {
|
||||
return SCrypt.scrypt( mpBytes, masterKeySalt, MP_N, MP_r, MP_p, MP_dkLen );
|
||||
}
|
||||
@@ -89,29 +92,37 @@ public class MasterKeyV0 extends MasterKey {
|
||||
byte[] siteNameBytes = siteName.getBytes( MP_charset );
|
||||
byte[] siteNameLengthBytes = bytesForInt( siteName.length() );
|
||||
byte[] siteCounterBytes = bytesForInt( siteCounter );
|
||||
byte[] siteContextBytes = siteContext == null? null: siteContext.getBytes( MP_charset );
|
||||
byte[] siteContextBytes = siteContext == null || siteContext.isEmpty()? null: siteContext.getBytes( MP_charset );
|
||||
byte[] siteContextLengthBytes = bytesForInt( siteContextBytes == null? 0: siteContextBytes.length );
|
||||
logger.trc( "site scope: %s, context: %s", siteScope, siteContext == null? "<empty>": siteContext );
|
||||
logger.trc( "site scope: %s, context: %s", siteScope, siteContextBytes == null? "<empty>": siteContext );
|
||||
logger.trc( "seed from: hmac-sha256(masterKey, %s | %s | %s | %s | %s | %s)", siteScope, CodeUtils.encodeHex( siteNameLengthBytes ),
|
||||
siteName, CodeUtils.encodeHex( siteCounterBytes ), CodeUtils.encodeHex( siteContextLengthBytes ),
|
||||
siteContext == null? "(null)": siteContext );
|
||||
siteContextBytes == null? "(null)": siteContext );
|
||||
|
||||
byte[] sitePasswordInfo = Bytes.concat( siteScope.getBytes( MP_charset ), siteNameLengthBytes, siteNameBytes, siteCounterBytes );
|
||||
if (siteContextBytes != null)
|
||||
sitePasswordInfo = Bytes.concat( sitePasswordInfo, siteContextLengthBytes, siteContextBytes );
|
||||
logger.trc( "sitePasswordInfo ID: %s", CodeUtils.encodeHex( idForBytes( sitePasswordInfo ) ) );
|
||||
|
||||
byte[] sitePasswordSeed = MP_mac.of( getKey(), sitePasswordInfo );
|
||||
logger.trc( "sitePasswordSeed ID: %s", CodeUtils.encodeHex( idForBytes( sitePasswordSeed ) ) );
|
||||
byte[] sitePasswordSeedBytes = MP_mac.of( getKey(), sitePasswordInfo );
|
||||
int[] sitePasswordSeed = new int[sitePasswordSeedBytes.length];
|
||||
for (int i = 0; i < sitePasswordSeedBytes.length; ++i) {
|
||||
ByteBuffer buf = ByteBuffer.allocate( Integer.SIZE / Byte.SIZE ).order( ByteOrder.BIG_ENDIAN );
|
||||
Arrays.fill( buf.array(), sitePasswordSeedBytes[i] > 0? (byte)0x00: (byte) 0xFF );
|
||||
buf.position( 2 );
|
||||
buf.put( sitePasswordSeedBytes[i] ).rewind();
|
||||
sitePasswordSeed[i] = buf.getInt() & 0xFFFF;
|
||||
}
|
||||
logger.trc( "sitePasswordSeed ID: %s", CodeUtils.encodeHex( idForBytes( sitePasswordSeedBytes ) ) );
|
||||
|
||||
Preconditions.checkState( sitePasswordSeed.length > 0 );
|
||||
int templateIndex = sitePasswordSeed[0] & 0xFFFF;
|
||||
int templateIndex = sitePasswordSeed[0];
|
||||
MPTemplate template = siteType.getTemplateAtRollingIndex( templateIndex );
|
||||
logger.trc( "type %s, template: %s", siteType, template.getTemplateString() );
|
||||
|
||||
StringBuilder password = new StringBuilder( template.length() );
|
||||
for (int i = 0; i < template.length(); ++i) {
|
||||
int characterIndex = sitePasswordSeed[i + 1] & 0xFFFF;
|
||||
int characterIndex = sitePasswordSeed[i + 1];
|
||||
MPTemplateCharacterClass characterClass = template.getCharacterClassAtIndex( i );
|
||||
char passwordCharacter = characterClass.getCharacterAtRollingIndex( characterIndex );
|
||||
logger.trc( "class %c, index %d (0x%02X) -> character: %c", characterClass.getIdentifier(), characterIndex,
|
||||
|
||||
@@ -46,12 +46,12 @@ public class MasterKeyV1 extends MasterKeyV0 {
|
||||
byte[] siteNameBytes = siteName.getBytes( MP_charset );
|
||||
byte[] siteNameLengthBytes = bytesForInt( siteName.length() );
|
||||
byte[] siteCounterBytes = bytesForInt( siteCounter );
|
||||
byte[] siteContextBytes = siteContext == null? null: siteContext.getBytes( MP_charset );
|
||||
byte[] siteContextBytes = siteContext == null || siteContext.isEmpty()? null: siteContext.getBytes( MP_charset );
|
||||
byte[] siteContextLengthBytes = bytesForInt( siteContextBytes == null? 0: siteContextBytes.length );
|
||||
logger.trc( "site scope: %s, context: %s", siteScope, siteContext == null? "<empty>": siteContext );
|
||||
logger.trc( "site scope: %s, context: %s", siteScope, siteContextBytes == null? "<empty>": siteContext );
|
||||
logger.trc( "seed from: hmac-sha256(masterKey, %s | %s | %s | %s | %s | %s)", siteScope, CodeUtils.encodeHex( siteNameLengthBytes ),
|
||||
siteName, CodeUtils.encodeHex( siteCounterBytes ), CodeUtils.encodeHex( siteContextLengthBytes ),
|
||||
siteContext == null? "(null)": siteContext );
|
||||
siteContextBytes == null? "(null)": siteContext );
|
||||
|
||||
byte[] sitePasswordInfo = Bytes.concat( siteScope.getBytes( MP_charset ), siteNameLengthBytes, siteNameBytes, siteCounterBytes );
|
||||
if (siteContextBytes != null)
|
||||
|
||||
@@ -45,12 +45,12 @@ public class MasterKeyV2 extends MasterKeyV1 {
|
||||
byte[] siteNameBytes = siteName.getBytes( MP_charset );
|
||||
byte[] siteNameLengthBytes = bytesForInt( siteNameBytes.length );
|
||||
byte[] siteCounterBytes = bytesForInt( siteCounter );
|
||||
byte[] siteContextBytes = siteContext == null? null: siteContext.getBytes( MP_charset );
|
||||
byte[] siteContextBytes = siteContext == null || siteContext.isEmpty()? null: siteContext.getBytes( MP_charset );
|
||||
byte[] siteContextLengthBytes = bytesForInt( siteContextBytes == null? 0: siteContextBytes.length );
|
||||
logger.trc( "site scope: %s, context: %s", siteScope, siteContext == null? "<empty>": siteContext );
|
||||
logger.trc( "site scope: %s, context: %s", siteScope, siteContextBytes == null? "<empty>": siteContext );
|
||||
logger.trc( "seed from: hmac-sha256(masterKey, %s | %s | %s | %s | %s | %s)", siteScope, CodeUtils.encodeHex( siteNameLengthBytes ),
|
||||
siteName, CodeUtils.encodeHex( siteCounterBytes ), CodeUtils.encodeHex( siteContextLengthBytes ),
|
||||
siteContext == null? "(null)": siteContext );
|
||||
siteContextBytes == null? "(null)": siteContext );
|
||||
|
||||
byte[] sitePasswordInfo = Bytes.concat( siteScope.getBytes( MP_charset ), siteNameLengthBytes, siteNameBytes, siteCounterBytes );
|
||||
if (siteContextBytes != null)
|
||||
|
||||
@@ -4,6 +4,7 @@ import com.google.common.primitives.Bytes;
|
||||
import com.lambdaworks.crypto.SCrypt;
|
||||
import com.lyndir.lhunath.opal.system.CodeUtils;
|
||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.CharBuffer;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.util.Arrays;
|
||||
@@ -42,8 +43,11 @@ public class MasterKeyV3 extends MasterKeyV2 {
|
||||
logger.trc( "key scope: %s", mpKeyScope );
|
||||
logger.trc( "masterKeySalt ID: %s", CodeUtils.encodeHex( idForBytes( masterKeySalt ) ) );
|
||||
|
||||
CharBuffer mpChars = CharBuffer.wrap( masterPassword );
|
||||
byte[] mpBytes = MP_charset.encode( mpChars ).array();
|
||||
ByteBuffer mpBytesBuf = MP_charset.encode( CharBuffer.wrap( masterPassword ) );
|
||||
byte[] mpBytes = new byte[mpBytesBuf.remaining()];
|
||||
mpBytesBuf.get( mpBytes, 0, mpBytes.length );
|
||||
Arrays.fill( mpBytesBuf.array(), (byte)0 );
|
||||
|
||||
try {
|
||||
return SCrypt.scrypt( mpBytes, masterKeySalt, MP_N, MP_r, MP_p, MP_dkLen );
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package com.lyndir.masterpassword;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.*;
|
||||
|
||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||
import com.lyndir.lhunath.opal.system.util.NNSupplier;
|
||||
import com.lyndir.lhunath.opal.system.util.NSupplier;
|
||||
import com.lyndir.lhunath.opal.system.util.*;
|
||||
import java.util.List;
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
@@ -25,8 +25,9 @@ public class MPWTests {
|
||||
@XmlElement(name = "case")
|
||||
private List<Case> cases;
|
||||
|
||||
@Nonnull
|
||||
public List<Case> getCases() {
|
||||
return cases;
|
||||
return checkNotNull( cases );
|
||||
}
|
||||
|
||||
public Case getCase(String identifier) {
|
||||
@@ -45,6 +46,8 @@ public class MPWTests {
|
||||
@XmlAttribute
|
||||
private String parent;
|
||||
@XmlElement
|
||||
private String algorithm;
|
||||
@XmlElement
|
||||
private String fullName;
|
||||
@XmlElement
|
||||
private String masterPassword;
|
||||
@@ -65,76 +68,86 @@ public class MPWTests {
|
||||
|
||||
private transient Case parentCase;
|
||||
|
||||
public void setTests(MPWTests tests) {
|
||||
public void initializeParentHierarchy(MPWTests tests) {
|
||||
|
||||
if (parent != null) {
|
||||
parentCase = tests.getCase( parent );
|
||||
fullName = ifNotNullElse( fullName, new NNSupplier<String>() {
|
||||
@Nonnull
|
||||
@Override
|
||||
public String get() {
|
||||
return parentCase.getFullName();
|
||||
}
|
||||
} );
|
||||
masterPassword = ifNotNullElse( masterPassword, new NNSupplier<String>() {
|
||||
@Nonnull
|
||||
@Override
|
||||
public String get() {
|
||||
return new String( parentCase.getMasterPassword() );
|
||||
}
|
||||
} );
|
||||
keyID = ifNotNullElse( keyID, new NNSupplier<String>() {
|
||||
@Nonnull
|
||||
@Override
|
||||
public String get() {
|
||||
return parentCase.getKeyID();
|
||||
}
|
||||
} );
|
||||
siteName = ifNotNullElse( siteName, new NNSupplier<String>() {
|
||||
@Nonnull
|
||||
@Override
|
||||
public String get() {
|
||||
return parentCase.getSiteName();
|
||||
}
|
||||
} );
|
||||
siteCounter = ifNotNullElse( siteCounter, new NNSupplier<Integer>() {
|
||||
@Nonnull
|
||||
@Override
|
||||
public Integer get() {
|
||||
return parentCase.getSiteCounter();
|
||||
}
|
||||
} );
|
||||
siteType = ifNotNullElse( siteType, new NNSupplier<String>() {
|
||||
@Nonnull
|
||||
@Override
|
||||
public String get() {
|
||||
return parentCase.getSiteType().name();
|
||||
}
|
||||
} );
|
||||
siteVariant = ifNotNullElse( siteVariant, new NNSupplier<String>() {
|
||||
@Nonnull
|
||||
@Override
|
||||
public String get() {
|
||||
return parentCase.getSiteVariant().name();
|
||||
}
|
||||
} );
|
||||
siteContext = ifNotNullElseNullable( siteContext, new NSupplier<String>() {
|
||||
@Nonnull
|
||||
@Override
|
||||
public String get() {
|
||||
return parentCase.getSiteContext();
|
||||
}
|
||||
} );
|
||||
result = ifNotNullElse( result, new NNSupplier<String>() {
|
||||
@Nonnull
|
||||
@Override
|
||||
public String get() {
|
||||
return parentCase.getResult();
|
||||
}
|
||||
} );
|
||||
parentCase.initializeParentHierarchy( tests );
|
||||
}
|
||||
|
||||
algorithm = ifNotNullElse( algorithm, new NNSupplier<String>() {
|
||||
@Nonnull
|
||||
@Override
|
||||
public String get() {
|
||||
return checkNotNull( parentCase.algorithm );
|
||||
}
|
||||
} );
|
||||
fullName = ifNotNullElse( fullName, new NNSupplier<String>() {
|
||||
@Nonnull
|
||||
@Override
|
||||
public String get() {
|
||||
return checkNotNull( parentCase.fullName );
|
||||
}
|
||||
} );
|
||||
masterPassword = ifNotNullElse( masterPassword, new NNSupplier<String>() {
|
||||
@Nonnull
|
||||
@Override
|
||||
public String get() {
|
||||
return checkNotNull( parentCase.masterPassword );
|
||||
}
|
||||
} );
|
||||
keyID = ifNotNullElse( keyID, new NNSupplier<String>() {
|
||||
@Nonnull
|
||||
@Override
|
||||
public String get() {
|
||||
return checkNotNull( parentCase.keyID );
|
||||
}
|
||||
} );
|
||||
siteName = ifNotNullElse( siteName, new NNSupplier<String>() {
|
||||
@Nonnull
|
||||
@Override
|
||||
public String get() {
|
||||
return checkNotNull( parentCase.siteName );
|
||||
}
|
||||
} );
|
||||
siteCounter = ifNotNullElse( siteCounter, new NNSupplier<Integer>() {
|
||||
@Nonnull
|
||||
@Override
|
||||
public Integer get() {
|
||||
return checkNotNull( parentCase.siteCounter );
|
||||
}
|
||||
} );
|
||||
siteType = ifNotNullElse( siteType, new NNSupplier<String>() {
|
||||
@Nonnull
|
||||
@Override
|
||||
public String get() {
|
||||
return checkNotNull( parentCase.siteType );
|
||||
}
|
||||
} );
|
||||
siteVariant = ifNotNullElse( siteVariant, new NNSupplier<String>() {
|
||||
@Nonnull
|
||||
@Override
|
||||
public String get() {
|
||||
return checkNotNull( parentCase.siteVariant );
|
||||
}
|
||||
} );
|
||||
siteContext = ifNotNullElse( siteContext, new NNSupplier<String>() {
|
||||
@Nonnull
|
||||
@Override
|
||||
public String get() {
|
||||
return parentCase == null? "": checkNotNull( parentCase.siteContext );
|
||||
}
|
||||
} );
|
||||
result = ifNotNullElse( result, new NNSupplier<String>() {
|
||||
@Nonnull
|
||||
@Override
|
||||
public String get() {
|
||||
return parentCase == null? "": checkNotNull( parentCase.result );
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public String getIdentifier() {
|
||||
return identifier;
|
||||
}
|
||||
@@ -144,40 +157,53 @@ public class MPWTests {
|
||||
return parentCase;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public MasterKey.Version getAlgorithm() {
|
||||
return MasterKey.Version.fromInt( ConversionUtils.toIntegerNN( algorithm ) );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public String getFullName() {
|
||||
return fullName;
|
||||
return checkNotNull( fullName );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public char[] getMasterPassword() {
|
||||
return masterPassword == null? null: masterPassword.toCharArray();
|
||||
return checkNotNull( masterPassword ).toCharArray();
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public String getKeyID() {
|
||||
return keyID;
|
||||
return checkNotNull( keyID );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public String getSiteName() {
|
||||
return siteName;
|
||||
return checkNotNull( siteName );
|
||||
}
|
||||
|
||||
public int getSiteCounter() {
|
||||
return ifNotNullElse( siteCounter, 1 );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public MPSiteType getSiteType() {
|
||||
return MPSiteType.forName( siteType );
|
||||
return MPSiteType.forName( checkNotNull( siteType ) );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public MPSiteVariant getSiteVariant() {
|
||||
return MPSiteVariant.forName( siteVariant );
|
||||
return MPSiteVariant.forName( checkNotNull( siteVariant ) );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public String getSiteContext() {
|
||||
return siteContext;
|
||||
return checkNotNull( siteContext );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public String getResult() {
|
||||
return result;
|
||||
return checkNotNull( result );
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -5,6 +5,7 @@ import static org.testng.Assert.*;
|
||||
import com.google.common.io.Resources;
|
||||
import com.lyndir.lhunath.opal.system.CodeUtils;
|
||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||
import com.lyndir.lhunath.opal.system.util.StringUtils;
|
||||
import java.net.URL;
|
||||
import javax.xml.bind.JAXBContext;
|
||||
import org.testng.annotations.BeforeMethod;
|
||||
@@ -26,7 +27,7 @@ public class MasterKeyTest {
|
||||
URL testCasesResource = Resources.getResource( "mpw_tests.xml" );
|
||||
tests = (MPWTests) JAXBContext.newInstance( MPWTests.class ).createUnmarshaller().unmarshal( testCasesResource );
|
||||
for (MPWTests.Case testCase : tests.getCases())
|
||||
testCase.setTests( tests );
|
||||
testCase.initializeParentHierarchy( tests );
|
||||
defaultCase = tests.getCase( MPWTests.ID_DEFAULT );
|
||||
}
|
||||
|
||||
@@ -35,10 +36,15 @@ public class MasterKeyTest {
|
||||
throws Exception {
|
||||
|
||||
for (MPWTests.Case testCase : tests.getCases()) {
|
||||
MasterKey masterKey = MasterKey.create( testCase.getFullName(), testCase.getMasterPassword() );
|
||||
if (testCase.getResult().isEmpty())
|
||||
continue;
|
||||
|
||||
logger.inf( "Running test case: %s [testEncode]", testCase.getIdentifier() );
|
||||
MasterKey masterKey = MasterKey.create( testCase.getAlgorithm(), testCase.getFullName(), testCase.getMasterPassword() );
|
||||
assertEquals(
|
||||
masterKey.encode( testCase.getSiteName(), testCase.getSiteType(), testCase.getSiteCounter(), testCase.getSiteVariant(),
|
||||
testCase.getSiteContext() ), testCase.getResult(), "Failed test case: " + testCase );
|
||||
logger.inf( "passed!" );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,8 +61,13 @@ public class MasterKeyTest {
|
||||
throws Exception {
|
||||
|
||||
for (MPWTests.Case testCase : tests.getCases()) {
|
||||
if (testCase.getResult().isEmpty())
|
||||
continue;
|
||||
|
||||
logger.inf( "Running test case: %s [testGetKeyID]", testCase.getIdentifier() );
|
||||
MasterKey masterKey = MasterKey.create( testCase.getFullName(), testCase.getMasterPassword() );
|
||||
assertEquals( CodeUtils.encodeHex( masterKey.getKeyID() ), testCase.getKeyID(), "Failed test case: " + testCase );
|
||||
logger.inf( "passed!" );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<tests>
|
||||
<!-- Default values for all parameters. -->
|
||||
<case id="default">
|
||||
<algorithm><!-- current --></algorithm>
|
||||
<fullName>Robert Lee Mitchell</fullName>
|
||||
<masterPassword>banana colored duckling</masterPassword>
|
||||
<keyID>98EEF4D1DF46D849574A82A03C3177056B15DFFCA29BB3899DE4628453675302</keyID>
|
||||
@@ -7,67 +9,271 @@
|
||||
<siteCounter>1</siteCounter>
|
||||
<siteType>GeneratedLong</siteType>
|
||||
<siteVariant>Password</siteVariant>
|
||||
<result><!-- abstract --></result>
|
||||
</case>
|
||||
|
||||
<!-- Algorithm 3 -->
|
||||
<case id="v3" parent="default">
|
||||
<algorithm>3</algorithm>
|
||||
<result>Jejr5[RepuSosp</result>
|
||||
</case>
|
||||
<case id="mb_fullName" parent="default">
|
||||
<case id="v3_mb_fullName" parent="v3">
|
||||
<fullName>⛄</fullName>
|
||||
<keyID>1717AA1F9BF5BA56CD0965CDA3D78E6D2E6A1EA8C067A8EA621F3DDAD4A87EB8</keyID>
|
||||
<result>NopaDajh8=Fene</result>
|
||||
</case>
|
||||
<case id="mb_masterPassword" parent="default">
|
||||
<case id="v3_mb_masterPassword" parent="v3">
|
||||
<masterPassword>⛄</masterPassword>
|
||||
<keyID>351432B8528A5ABECAB768CA95015097DE76FE14C41E10AF36C67DCFB8917E08</keyID>
|
||||
<result>QesuHirv5-Xepl</result>
|
||||
</case>
|
||||
<case id="mb_siteName" parent="default">
|
||||
<case id="v3_mb_siteName" parent="v3">
|
||||
<siteName>⛄</siteName>
|
||||
<result>LiheCuwhSerz6)</result>
|
||||
</case>
|
||||
<case id="loginName" parent="default">
|
||||
<case id="v3_loginName" parent="v3">
|
||||
<siteVariant>Login</siteVariant>
|
||||
<siteType>GeneratedName</siteType>
|
||||
<result>wohzaqage</result>
|
||||
</case>
|
||||
<case id="securityAnswer" parent="default">
|
||||
<case id="v3_securityAnswer" parent="v3">
|
||||
<siteVariant>Answer</siteVariant>
|
||||
<siteType>GeneratedPhrase</siteType>
|
||||
<result>xin diyjiqoja hubu</result>
|
||||
</case>
|
||||
<case id="securityAnswer_context" parent="securityAnswer">
|
||||
<case id="v3_securityAnswer_context" parent="v3_securityAnswer">
|
||||
<siteContext>question</siteContext>
|
||||
<result>xogx tem cegyiva jab</result>
|
||||
</case>
|
||||
<case id="type_maximum" parent="default">
|
||||
<case id="v3_type_maximum" parent="v3">
|
||||
<siteType>GeneratedMaximum</siteType>
|
||||
<result>W6@692^B1#&@gVdSdLZ@</result>
|
||||
</case>
|
||||
<case id="type_medium" parent="default">
|
||||
<case id="v3_type_medium" parent="v3">
|
||||
<siteType>GeneratedMedium</siteType>
|
||||
<result>Jej2$Quv</result>
|
||||
</case>
|
||||
<case id="type_basic" parent="default">
|
||||
<case id="v3_type_basic" parent="v3">
|
||||
<siteType>GeneratedBasic</siteType>
|
||||
<result>WAo2xIg6</result>
|
||||
</case>
|
||||
<case id="type_short" parent="default">
|
||||
<case id="v3_type_short" parent="v3">
|
||||
<siteType>GeneratedShort</siteType>
|
||||
<result>Jej2</result>
|
||||
</case>
|
||||
<case id="type_pin" parent="default">
|
||||
<case id="v3_type_pin" parent="v3">
|
||||
<siteType>GeneratedPIN</siteType>
|
||||
<result>7662</result>
|
||||
</case>
|
||||
<case id="type_name" parent="default">
|
||||
<case id="v3_type_name" parent="v3">
|
||||
<siteType>GeneratedName</siteType>
|
||||
<result>jejraquvo</result>
|
||||
</case>
|
||||
<case id="type_phrase" parent="default">
|
||||
<case id="v3_type_phrase" parent="v3">
|
||||
<siteType>GeneratedPhrase</siteType>
|
||||
<result>jejr quv cabsibu tam</result>
|
||||
</case>
|
||||
<case id="counter_ceiling" parent="default">
|
||||
<case id="v3_counter_ceiling" parent="v3">
|
||||
<siteCounter>4294967295</siteCounter>
|
||||
<result>XambHoqo6[Peni</result>
|
||||
</case>
|
||||
|
||||
<!-- Algorithm 2 -->
|
||||
<case id="v2" parent="default">
|
||||
<algorithm>2</algorithm>
|
||||
<result>Jejr5[RepuSosp</result>
|
||||
</case>
|
||||
<case id="v2_mb_fullName" parent="v2">
|
||||
<fullName>⛄</fullName>
|
||||
<keyID>1717AA1F9BF5BA56CD0965CDA3D78E6D2E6A1EA8C067A8EA621F3DDAD4A87EB8</keyID>
|
||||
<result>WaqoGuho2[Xaxw</result>
|
||||
</case>
|
||||
<case id="v2_mb_masterPassword" parent="v2">
|
||||
<masterPassword>⛄</masterPassword>
|
||||
<keyID>351432B8528A5ABECAB768CA95015097DE76FE14C41E10AF36C67DCFB8917E08</keyID>
|
||||
<result>QesuHirv5-Xepl</result>
|
||||
</case>
|
||||
<case id="v2_mb_siteName" parent="v2">
|
||||
<siteName>⛄</siteName>
|
||||
<result>LiheCuwhSerz6)</result>
|
||||
</case>
|
||||
<case id="v2_loginName" parent="v2">
|
||||
<siteVariant>Login</siteVariant>
|
||||
<siteType>GeneratedName</siteType>
|
||||
<result>wohzaqage</result>
|
||||
</case>
|
||||
<case id="v2_securityAnswer" parent="v2">
|
||||
<siteVariant>Answer</siteVariant>
|
||||
<siteType>GeneratedPhrase</siteType>
|
||||
<result>xin diyjiqoja hubu</result>
|
||||
</case>
|
||||
<case id="v2_securityAnswer_context" parent="v2_securityAnswer">
|
||||
<siteContext>question</siteContext>
|
||||
<result>xogx tem cegyiva jab</result>
|
||||
</case>
|
||||
<case id="v2_type_maximum" parent="v2">
|
||||
<siteType>GeneratedMaximum</siteType>
|
||||
<result>W6@692^B1#&@gVdSdLZ@</result>
|
||||
</case>
|
||||
<case id="v2_type_medium" parent="v2">
|
||||
<siteType>GeneratedMedium</siteType>
|
||||
<result>Jej2$Quv</result>
|
||||
</case>
|
||||
<case id="v2_type_basic" parent="v2">
|
||||
<siteType>GeneratedBasic</siteType>
|
||||
<result>WAo2xIg6</result>
|
||||
</case>
|
||||
<case id="v2_type_short" parent="v2">
|
||||
<siteType>GeneratedShort</siteType>
|
||||
<result>Jej2</result>
|
||||
</case>
|
||||
<case id="v2_type_pin" parent="v2">
|
||||
<siteType>GeneratedPIN</siteType>
|
||||
<result>7662</result>
|
||||
</case>
|
||||
<case id="v2_type_name" parent="v2">
|
||||
<siteType>GeneratedName</siteType>
|
||||
<result>jejraquvo</result>
|
||||
</case>
|
||||
<case id="v2_type_phrase" parent="v2">
|
||||
<siteType>GeneratedPhrase</siteType>
|
||||
<result>jejr quv cabsibu tam</result>
|
||||
</case>
|
||||
<case id="v2_counter_ceiling" parent="v2">
|
||||
<siteCounter>4294967295</siteCounter>
|
||||
<result>XambHoqo6[Peni</result>
|
||||
</case>
|
||||
|
||||
<!-- Algorithm 1 -->
|
||||
<case id="v1" parent="default">
|
||||
<algorithm>1</algorithm>
|
||||
<result>Jejr5[RepuSosp</result>
|
||||
</case>
|
||||
<case id="v1_mb_fullName" parent="v1">
|
||||
<fullName>⛄</fullName>
|
||||
<keyID>1717AA1F9BF5BA56CD0965CDA3D78E6D2E6A1EA8C067A8EA621F3DDAD4A87EB8</keyID>
|
||||
<result>WaqoGuho2[Xaxw</result>
|
||||
</case>
|
||||
<case id="v1_mb_masterPassword" parent="v1">
|
||||
<masterPassword>⛄</masterPassword>
|
||||
<keyID>351432B8528A5ABECAB768CA95015097DE76FE14C41E10AF36C67DCFB8917E08</keyID>
|
||||
<result>QesuHirv5-Xepl</result>
|
||||
</case>
|
||||
<case id="v1_mb_siteName" parent="v1">
|
||||
<siteName>⛄</siteName>
|
||||
<result>WawiYarp2@Kodh</result>
|
||||
</case>
|
||||
<case id="v1_loginName" parent="v1">
|
||||
<siteVariant>Login</siteVariant>
|
||||
<siteType>GeneratedName</siteType>
|
||||
<result>wohzaqage</result>
|
||||
</case>
|
||||
<case id="v1_securityAnswer" parent="v1">
|
||||
<siteVariant>Answer</siteVariant>
|
||||
<siteType>GeneratedPhrase</siteType>
|
||||
<result>xin diyjiqoja hubu</result>
|
||||
</case>
|
||||
<case id="v1_securityAnswer_context" parent="v1_securityAnswer">
|
||||
<siteContext>question</siteContext>
|
||||
<result>xogx tem cegyiva jab</result>
|
||||
</case>
|
||||
<case id="v1_type_maximum" parent="v1">
|
||||
<siteType>GeneratedMaximum</siteType>
|
||||
<result>W6@692^B1#&@gVdSdLZ@</result>
|
||||
</case>
|
||||
<case id="v1_type_medium" parent="v1">
|
||||
<siteType>GeneratedMedium</siteType>
|
||||
<result>Jej2$Quv</result>
|
||||
</case>
|
||||
<case id="v1_type_basic" parent="v1">
|
||||
<siteType>GeneratedBasic</siteType>
|
||||
<result>WAo2xIg6</result>
|
||||
</case>
|
||||
<case id="v1_type_short" parent="v1">
|
||||
<siteType>GeneratedShort</siteType>
|
||||
<result>Jej2</result>
|
||||
</case>
|
||||
<case id="v1_type_pin" parent="v1">
|
||||
<siteType>GeneratedPIN</siteType>
|
||||
<result>7662</result>
|
||||
</case>
|
||||
<case id="v1_type_name" parent="v1">
|
||||
<siteType>GeneratedName</siteType>
|
||||
<result>jejraquvo</result>
|
||||
</case>
|
||||
<case id="v1_type_phrase" parent="v1">
|
||||
<siteType>GeneratedPhrase</siteType>
|
||||
<result>jejr quv cabsibu tam</result>
|
||||
</case>
|
||||
<case id="v1_counter_ceiling" parent="v1">
|
||||
<siteCounter>4294967295</siteCounter>
|
||||
<result>XambHoqo6[Peni</result>
|
||||
</case>
|
||||
|
||||
<!-- Algorithm 0 -->
|
||||
<case id="v0" parent="default">
|
||||
<algorithm>0</algorithm>
|
||||
<result>Feji5@ReduWosh</result>
|
||||
</case>
|
||||
<case id="v0_mb_fullName" parent="v0">
|
||||
<fullName>⛄</fullName>
|
||||
<keyID>1717AA1F9BF5BA56CD0965CDA3D78E6D2E6A1EA8C067A8EA621F3DDAD4A87EB8</keyID>
|
||||
<result>HajrYudo7@Mamh</result>
|
||||
</case>
|
||||
<case id="v0_mb_masterPassword" parent="v0">
|
||||
<masterPassword>⛄</masterPassword>
|
||||
<keyID>351432B8528A5ABECAB768CA95015097DE76FE14C41E10AF36C67DCFB8917E08</keyID>
|
||||
<result>MewmDini0]Meho</result>
|
||||
</case>
|
||||
<case id="v0_mb_siteName" parent="v0">
|
||||
<siteName>⛄</siteName>
|
||||
<result>HahiVana2@Nole</result>
|
||||
</case>
|
||||
<case id="v0_loginName" parent="v0">
|
||||
<siteVariant>Login</siteVariant>
|
||||
<siteType>GeneratedName</siteType>
|
||||
<result>lozwajave</result>
|
||||
</case>
|
||||
<case id="v0_securityAnswer" parent="v0">
|
||||
<siteVariant>Answer</siteVariant>
|
||||
<siteType>GeneratedPhrase</siteType>
|
||||
<result>miy lirfijoja dubu</result>
|
||||
</case>
|
||||
<case id="v0_securityAnswer_context" parent="v0_securityAnswer">
|
||||
<siteContext>question</siteContext>
|
||||
<result>movm bex gevrica jaf</result>
|
||||
</case>
|
||||
<case id="v0_type_maximum" parent="v0">
|
||||
<siteType>GeneratedMaximum</siteType>
|
||||
<result>w1!3bA3icmRAc)SS@lwl</result>
|
||||
</case>
|
||||
<case id="v0_type_medium" parent="v0">
|
||||
<siteType>GeneratedMedium</siteType>
|
||||
<result>Fej7]Jug</result>
|
||||
</case>
|
||||
<case id="v0_type_basic" parent="v0">
|
||||
<siteType>GeneratedBasic</siteType>
|
||||
<result>wvH7irC1</result>
|
||||
</case>
|
||||
<case id="v0_type_short" parent="v0">
|
||||
<siteType>GeneratedShort</siteType>
|
||||
<result>Fej7</result>
|
||||
</case>
|
||||
<case id="v0_type_pin" parent="v0">
|
||||
<siteType>GeneratedPIN</siteType>
|
||||
<result>2117</result>
|
||||
</case>
|
||||
<case id="v0_type_name" parent="v0">
|
||||
<siteType>GeneratedName</siteType>
|
||||
<result>fejrajugo</result>
|
||||
</case>
|
||||
<case id="v0_type_phrase" parent="v0">
|
||||
<siteType>GeneratedPhrase</siteType>
|
||||
<result>fejr jug gabsibu bax</result>
|
||||
</case>
|
||||
<case id="v0_counter_ceiling" parent="v0">
|
||||
<siteCounter>4294967295</siteCounter>
|
||||
<result>QateDojh1@Hecn</result>
|
||||
</case>
|
||||
</tests>
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.lyndir.masterpassword"
|
||||
android:versionCode="1"
|
||||
android:versionName="2.2">
|
||||
android:versionCode="2003"
|
||||
android:versionName="2.3">
|
||||
|
||||
<uses-sdk
|
||||
android:minSdkVersion="19"
|
||||
|
||||
13
MasterPassword/Java/masterpassword-android/README
Normal file
@@ -0,0 +1,13 @@
|
||||
To build this module, please ensure you've done the following setup:
|
||||
|
||||
1. Installed the Android SDK and fully downloaded the Android SDK platform 21 in it.
|
||||
2. Set the environment variable ANDROID_HOME in your shell or in ~/.mavenrc to point to the root of your Android SDK install.
|
||||
3. Installed the Android SDK into your Maven's local repository.
|
||||
3a. Clone the maven-android-sdk-deployer available from here: https://github.com/mosabua/maven-android-sdk-deployer.git
|
||||
3b. In the root of this project, run: mvn install -P 5.0
|
||||
|
||||
To build this module:
|
||||
|
||||
1. Build the parent, by going into 'MasterPassword/Java' and running: mvn clean install
|
||||
2. Build this module, by going into 'MasterPassword/Java/masterpassword-android' and running: mvn clean install
|
||||
3. You can then find the APK in: 'MasterPassword/Java/masterpassword-android/target'
|
||||
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>com.lyndir.masterpassword</groupId>
|
||||
<artifactId>masterpassword</artifactId>
|
||||
<version>2.2</version>
|
||||
<version>2.3</version>
|
||||
</parent>
|
||||
|
||||
<name>Master Password Android</name>
|
||||
@@ -104,7 +104,7 @@
|
||||
<dependency>
|
||||
<groupId>com.lyndir.masterpassword</groupId>
|
||||
<artifactId>masterpassword-algorithm</artifactId>
|
||||
<version>2.2</version>
|
||||
<version>2.3</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -115,13 +115,13 @@
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-android</artifactId>
|
||||
<version>1.7.13-underscore</version>
|
||||
</dependency>
|
||||
|
||||
<!-- clone https://github.com/mosabua/maven-android-sdk-deployer.git
|
||||
run mvn install -P 4.4 -->
|
||||
<dependency>
|
||||
<groupId>android</groupId>
|
||||
<artifactId>android</artifactId>
|
||||
<version>5.0.1_r2</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
|
||||
@@ -85,7 +85,7 @@
|
||||
android:layout_gravity="center"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
<Button
|
||||
android:id="@id/sitePasswordField"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
||||
@@ -2,10 +2,11 @@ package com.lyndir.masterpassword;
|
||||
|
||||
import static com.lyndir.lhunath.opal.system.util.StringUtils.strf;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.*;
|
||||
import android.content.*;
|
||||
import android.content.ClipboardManager;
|
||||
import android.graphics.Paint;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.text.*;
|
||||
import android.text.method.PasswordTransformationMethod;
|
||||
@@ -18,7 +19,7 @@ import com.google.common.base.Throwables;
|
||||
import com.google.common.util.concurrent.*;
|
||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||
import com.lyndir.lhunath.opal.system.util.ConversionUtils;
|
||||
import java.util.Arrays;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
@@ -26,7 +27,9 @@ import javax.annotation.Nullable;
|
||||
public class EmergencyActivity extends Activity {
|
||||
|
||||
@SuppressWarnings("UnusedDeclaration")
|
||||
private static final Logger logger = Logger.get( EmergencyActivity.class );
|
||||
private static final Logger logger = Logger.get( EmergencyActivity.class );
|
||||
private static final ClipData EMPTY_CLIP = new ClipData( new ClipDescription( "", new String[0] ), new ClipData.Item( "" ) );
|
||||
private static final int PASSWORD_NOTIFICATION = 0;
|
||||
|
||||
private final ListeningExecutorService executor = MoreExecutors.listeningDecorator( Executors.newSingleThreadExecutor() );
|
||||
private final ValueChangedListener updateMasterKey = new ValueChangedListener() {
|
||||
@@ -66,7 +69,7 @@ public class EmergencyActivity extends Activity {
|
||||
Spinner siteVersionField;
|
||||
|
||||
@InjectView(R.id.sitePasswordField)
|
||||
TextView sitePasswordField;
|
||||
Button sitePasswordField;
|
||||
|
||||
@InjectView(R.id.sitePasswordTip)
|
||||
TextView sitePasswordTip;
|
||||
@@ -306,13 +309,40 @@ public class EmergencyActivity extends Activity {
|
||||
}
|
||||
|
||||
public void copySitePassword(View view) {
|
||||
if (TextUtils.isEmpty( sitePassword ))
|
||||
final String currentSitePassword = this.sitePassword;
|
||||
if (TextUtils.isEmpty( currentSitePassword ))
|
||||
return;
|
||||
|
||||
ClipDescription description = new ClipDescription( strf( "Password for %s", siteNameField.getText() ),
|
||||
new String[]{ ClipDescription.MIMETYPE_TEXT_PLAIN } );
|
||||
ClipData clipData = new ClipData( description, new ClipData.Item( sitePassword ) );
|
||||
((ClipboardManager) getSystemService( CLIPBOARD_SERVICE )).setPrimaryClip( clipData );
|
||||
final ClipboardManager clipboardManager = (ClipboardManager) getSystemService( CLIPBOARD_SERVICE );
|
||||
final NotificationManager notificationManager = (NotificationManager) getSystemService( Context.NOTIFICATION_SERVICE );
|
||||
|
||||
String title = strf( "Password for %s", siteNameField.getText() );
|
||||
ClipDescription description = new ClipDescription( title, new String[]{ ClipDescription.MIMETYPE_TEXT_PLAIN } );
|
||||
clipboardManager.setPrimaryClip( new ClipData( description, new ClipData.Item( currentSitePassword ) ) );
|
||||
|
||||
Notification.Builder notificationBuilder = new Notification.Builder( this ).setContentTitle( title )
|
||||
.setContentText( "Paste the password into your app." )
|
||||
.setSmallIcon( R.drawable.icon )
|
||||
.setAutoCancel( true );
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
|
||||
notificationBuilder.setVisibility( Notification.VISIBILITY_SECRET )
|
||||
.setCategory( Notification.CATEGORY_RECOMMENDATION )
|
||||
.setLocalOnly( true );
|
||||
notificationManager.notify( PASSWORD_NOTIFICATION, notificationBuilder.build() );
|
||||
final Timer timer = new Timer();
|
||||
timer.schedule( new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
ClipData clip = clipboardManager.getPrimaryClip();
|
||||
for (int i = 0; i < clip.getItemCount(); ++i)
|
||||
if (currentSitePassword.equals( clip.getItemAt( i ).coerceToText( EmergencyActivity.this ) )) {
|
||||
clipboardManager.setPrimaryClip( EMPTY_CLIP );
|
||||
break;
|
||||
}
|
||||
notificationManager.cancel( PASSWORD_NOTIFICATION );
|
||||
timer.cancel();
|
||||
}
|
||||
}, 20000 );
|
||||
|
||||
Intent startMain = new Intent( Intent.ACTION_MAIN );
|
||||
startMain.addCategory( Intent.CATEGORY_HOME );
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>com.lyndir.masterpassword</groupId>
|
||||
<artifactId>masterpassword</artifactId>
|
||||
<version>2.2</version>
|
||||
<version>2.3</version>
|
||||
</parent>
|
||||
|
||||
<name>Master Password CLI</name>
|
||||
@@ -85,7 +85,7 @@
|
||||
<dependency>
|
||||
<groupId>com.lyndir.masterpassword</groupId>
|
||||
<artifactId>masterpassword-algorithm</artifactId>
|
||||
<version>2.2</version>
|
||||
<version>2.3</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>com.lyndir.masterpassword</groupId>
|
||||
<artifactId>masterpassword</artifactId>
|
||||
<version>2.2</version>
|
||||
<version>2.3</version>
|
||||
</parent>
|
||||
|
||||
<name>Master Password GUI</name>
|
||||
@@ -134,7 +134,7 @@
|
||||
<dependency>
|
||||
<groupId>com.lyndir.masterpassword</groupId>
|
||||
<artifactId>masterpassword-model</artifactId>
|
||||
<version>2.2</version>
|
||||
<version>2.3</version>
|
||||
</dependency>
|
||||
|
||||
<!-- EXTERNAL DEPENDENCIES -->
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
package com.lyndir.masterpassword.gui;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.lyndir.masterpassword.MPIdenticon;
|
||||
import com.lyndir.masterpassword.gui.util.Components;
|
||||
import java.awt.*;
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.swing.*;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
|
||||
/**
|
||||
@@ -29,6 +33,8 @@ public abstract class AuthenticationPanel extends Components.GradientPanel {
|
||||
}
|
||||
} );
|
||||
add( Box.createVerticalGlue() );
|
||||
|
||||
avatarLabel.setToolTipText( "The avatar for your user. Click to change it." );
|
||||
}
|
||||
|
||||
protected void updateUser(boolean repack) {
|
||||
@@ -39,8 +45,11 @@ public abstract class AuthenticationPanel extends Components.GradientPanel {
|
||||
unlockFrame.repack();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
protected abstract User getSelectedUser();
|
||||
|
||||
@NotNull
|
||||
@Nonnull
|
||||
public abstract char[] getMasterPassword();
|
||||
|
||||
public Component getFocusComponent() {
|
||||
|
||||
@@ -21,6 +21,7 @@ import com.google.common.base.Charsets;
|
||||
import com.google.common.io.*;
|
||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||
import com.lyndir.lhunath.opal.system.util.TypeUtils;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
@@ -48,7 +49,13 @@ public class GUI implements UnlockFrame.SignInCallback {
|
||||
if (Config.get().checkForUpdates())
|
||||
checkUpdate();
|
||||
|
||||
TypeUtils.<GUI>newInstance( AppleGUI.class ).or( new GUI() ).open();
|
||||
try {
|
||||
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
|
||||
}
|
||||
catch (UnsupportedLookAndFeelException | ClassNotFoundException | InstantiationException | IllegalAccessException ignored) {
|
||||
}
|
||||
|
||||
TypeUtils.<GUI>newInstance( "com.lyndir.masterpassword.gui.platform.mac.AppleGUI" ).or( new GUI() ).open();
|
||||
}
|
||||
|
||||
private static void checkUpdate() {
|
||||
@@ -80,7 +87,7 @@ public class GUI implements UnlockFrame.SignInCallback {
|
||||
}
|
||||
}
|
||||
|
||||
void open() {
|
||||
protected void open() {
|
||||
SwingUtilities.invokeLater( new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
@@ -7,6 +7,7 @@ import java.awt.event.ActionListener;
|
||||
import javax.swing.*;
|
||||
import javax.swing.event.DocumentEvent;
|
||||
import javax.swing.event.DocumentListener;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
|
||||
/**
|
||||
@@ -58,6 +59,7 @@ public class IncognitoAuthenticationPanel extends AuthenticationPanel implements
|
||||
return new IncognitoUser( fullNameField.getText() );
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public char[] getMasterPassword() {
|
||||
return masterPasswordField.getPassword();
|
||||
|
||||
4
MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/masterpassword/gui/IncognitoUser.java
Normal file → Executable file
@@ -41,4 +41,8 @@ public class IncognitoUser extends User {
|
||||
@Override
|
||||
public void addSite(final Site site) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteSite(Site site) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.lyndir.masterpassword.gui;
|
||||
|
||||
import static com.lyndir.lhunath.opal.system.util.StringUtils.strf;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.collect.*;
|
||||
@@ -13,6 +15,8 @@ import javax.annotation.Nullable;
|
||||
import javax.swing.*;
|
||||
import javax.swing.event.DocumentEvent;
|
||||
import javax.swing.event.DocumentListener;
|
||||
import javax.swing.plaf.metal.MetalComboBoxEditor;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
|
||||
/**
|
||||
@@ -51,6 +55,15 @@ public class ModelAuthenticationPanel extends AuthenticationPanel implements Ite
|
||||
userField.setFont( Res.valueFont().deriveFont( 12f ) );
|
||||
userField.addItemListener( this );
|
||||
userField.addActionListener( this );
|
||||
userField.setEditor( new MetalComboBoxEditor() {
|
||||
@Override
|
||||
protected JTextField createEditorComponent() {
|
||||
JTextField editorComponents = Components.textField();
|
||||
editorComponents.setForeground( Color.red );
|
||||
return editorComponents;
|
||||
}
|
||||
} );
|
||||
|
||||
add( userField );
|
||||
add( Components.stud() );
|
||||
|
||||
@@ -94,6 +107,7 @@ public class ModelAuthenticationPanel extends AuthenticationPanel implements Ite
|
||||
return userField.getModel().getElementAt( selectedIndex );
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public char[] getMasterPassword() {
|
||||
return masterPasswordField.getPassword();
|
||||
@@ -114,6 +128,29 @@ public class ModelAuthenticationPanel extends AuthenticationPanel implements Ite
|
||||
updateUser( true );
|
||||
}
|
||||
} );
|
||||
setToolTipText( "Add a new user to the list." );
|
||||
}
|
||||
}, new JButton( Res.iconDelete() ) {
|
||||
{
|
||||
addActionListener( new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(final ActionEvent e) {
|
||||
ModelUser deleteUser = getSelectedUser();
|
||||
if (deleteUser == null)
|
||||
return;
|
||||
|
||||
if (JOptionPane.showConfirmDialog( ModelAuthenticationPanel.this, //
|
||||
strf( "Are you sure you want to delete the user and sites remembered for:\n%s.",
|
||||
deleteUser.getFullName() ), //
|
||||
"Delete User", JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE ) == JOptionPane.CANCEL_OPTION)
|
||||
return;
|
||||
|
||||
MPUserFileManager.get().deleteUser( deleteUser.getModel() );
|
||||
userField.setModel( new DefaultComboBoxModel<>( readConfigUsers() ) );
|
||||
updateUser( true );
|
||||
}
|
||||
} );
|
||||
setToolTipText( "Delete the selected user." );
|
||||
}
|
||||
}, new JButton( Res.iconQuestion() ) {
|
||||
{
|
||||
@@ -121,10 +158,12 @@ public class ModelAuthenticationPanel extends AuthenticationPanel implements Ite
|
||||
@Override
|
||||
public void actionPerformed(final ActionEvent e) {
|
||||
JOptionPane.showMessageDialog( ModelAuthenticationPanel.this, //
|
||||
"Reads users and sites from the directory at ~/.mpw.", //
|
||||
strf( "Reads users and sites from the directory at:\n%s",
|
||||
MPUserFileManager.get().getPath().getAbsolutePath() ), //
|
||||
"Help", JOptionPane.INFORMATION_MESSAGE );
|
||||
}
|
||||
} );
|
||||
setToolTipText( "More information." );
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
4
MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/masterpassword/gui/ModelSite.java
Normal file → Executable file
@@ -16,6 +16,10 @@ public class ModelSite extends Site {
|
||||
this.model = result.getSite();
|
||||
}
|
||||
|
||||
public MPSite getModel() {
|
||||
return model;
|
||||
}
|
||||
|
||||
public String getSiteName() {
|
||||
return model.getSiteName();
|
||||
}
|
||||
|
||||
14
MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/masterpassword/gui/ModelUser.java
Normal file → Executable file
@@ -42,7 +42,7 @@ public class ModelUser extends User {
|
||||
}
|
||||
|
||||
public void setAvatar(final int avatar) {
|
||||
model.setAvatar( avatar % Res.avatars() );
|
||||
model.setAvatar(avatar % Res.avatars());
|
||||
MPUserFileManager.get().save();
|
||||
}
|
||||
|
||||
@@ -67,8 +67,8 @@ public class ModelUser extends User {
|
||||
return FluentIterable.from( model.findSitesByName( query ) ).transform( new Function<MPSiteResult, Site>() {
|
||||
@Nullable
|
||||
@Override
|
||||
public Site apply(@Nullable final MPSiteResult result) {
|
||||
return new ModelSite( Preconditions.checkNotNull( result ) );
|
||||
public Site apply(@Nullable final MPSiteResult site) {
|
||||
return new ModelSite( Preconditions.checkNotNull( site ) );
|
||||
}
|
||||
} );
|
||||
}
|
||||
@@ -80,6 +80,14 @@ public class ModelUser extends User {
|
||||
MPUserFileManager.get().save();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteSite(Site site) {
|
||||
if (site instanceof ModelSite) {
|
||||
model.deleteSite(((ModelSite) site).getModel());
|
||||
MPUserFileManager.get().save();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean keySaved() {
|
||||
// TODO
|
||||
return false;
|
||||
|
||||
121
MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/masterpassword/gui/PasswordFrame.java
Normal file → Executable file
@@ -1,9 +1,13 @@
|
||||
package com.lyndir.masterpassword.gui;
|
||||
|
||||
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.ifNotNullElse;
|
||||
import static com.lyndir.lhunath.opal.system.util.StringUtils.*;
|
||||
|
||||
import com.google.common.collect.FluentIterable;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.util.concurrent.*;
|
||||
import com.lyndir.lhunath.opal.system.util.NNSupplier;
|
||||
import com.lyndir.lhunath.opal.system.util.PredicateNN;
|
||||
import com.lyndir.masterpassword.*;
|
||||
import com.lyndir.masterpassword.gui.util.Components;
|
||||
import java.awt.*;
|
||||
@@ -24,7 +28,7 @@ public class PasswordFrame extends JFrame implements DocumentListener {
|
||||
private final User user;
|
||||
private final Components.GradientPanel root;
|
||||
private final JTextField siteNameField;
|
||||
private final JButton siteAddButton;
|
||||
private final JButton siteActionButton;
|
||||
private final JComboBox<MPSiteType> siteTypeField;
|
||||
private final JComboBox<MasterKey.Version> siteVersionField;
|
||||
private final JSpinner siteCounterField;
|
||||
@@ -33,8 +37,10 @@ public class PasswordFrame extends JFrame implements DocumentListener {
|
||||
private final JCheckBox maskPasswordField;
|
||||
private final char passwordEchoChar;
|
||||
private final Font passwordEchoFont;
|
||||
private boolean updatingUI;
|
||||
|
||||
@Nullable
|
||||
private Site currentSite;
|
||||
private boolean updatingUI;
|
||||
|
||||
public PasswordFrame(User user)
|
||||
throws HeadlessException {
|
||||
@@ -42,7 +48,8 @@ public class PasswordFrame extends JFrame implements DocumentListener {
|
||||
this.user = user;
|
||||
|
||||
setDefaultCloseOperation( DISPOSE_ON_CLOSE );
|
||||
setContentPane( root = Components.gradientPanel( new BorderLayout( 20, 20 ), Res.colors().frameBg() ) );
|
||||
setContentPane( root = Components.gradientPanel( new FlowLayout(), Res.colors().frameBg() ) );
|
||||
root.setLayout( new BoxLayout( root, BoxLayout.PAGE_AXIS ) );
|
||||
root.setBorder( BorderFactory.createEmptyBorder( 20, 20, 20, 20 ) );
|
||||
|
||||
// Site
|
||||
@@ -50,52 +57,55 @@ public class PasswordFrame extends JFrame implements DocumentListener {
|
||||
sitePanel.setOpaque( true );
|
||||
sitePanel.setBackground( Res.colors().controlBg() );
|
||||
sitePanel.setBorder( BorderFactory.createEmptyBorder( 20, 20, 20, 20 ) );
|
||||
add( Components.borderPanel( sitePanel, BorderFactory.createRaisedBevelBorder(), Res.colors().frameBg() ), BorderLayout.CENTER );
|
||||
root.add( Components.borderPanel( sitePanel, BorderFactory.createRaisedBevelBorder(), Res.colors().frameBg() ) );
|
||||
|
||||
// User
|
||||
sitePanel.add( Components.label( strf( "Generating passwords for: %s", user.getFullName() ), JLabel.CENTER ) );
|
||||
sitePanel.add( Components.label( strf( "Generating passwords for: %s", user.getFullName() ), SwingConstants.CENTER ) );
|
||||
sitePanel.add( Components.stud() );
|
||||
|
||||
// Site Name
|
||||
sitePanel.add( Components.label( "Site Name:" ) );
|
||||
sitePanel.add(Components.label("Site Name:"));
|
||||
JComponent siteControls = Components.boxLayout( BoxLayout.LINE_AXIS, //
|
||||
siteNameField = Components.textField(), Components.stud(),
|
||||
siteAddButton = Components.button( "Add Site" ) );
|
||||
siteNameField.getDocument().addDocumentListener( this );
|
||||
siteNameField.addActionListener( new ActionListener() {
|
||||
siteActionButton = Components.button( "Add Site" ) );
|
||||
siteNameField.getDocument().addDocumentListener(this);
|
||||
siteNameField.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(final ActionEvent e) {
|
||||
Futures.addCallback( updatePassword(), new FutureCallback<String>() {
|
||||
Futures.addCallback(updatePassword(true), new FutureCallback<String>() {
|
||||
@Override
|
||||
public void onSuccess(@Nullable final String sitePassword) {
|
||||
StringSelection clipboardContents = new StringSelection( sitePassword );
|
||||
Toolkit.getDefaultToolkit().getSystemClipboard().setContents( clipboardContents, null );
|
||||
StringSelection clipboardContents = new StringSelection(sitePassword);
|
||||
Toolkit.getDefaultToolkit().getSystemClipboard().setContents(clipboardContents, null);
|
||||
|
||||
SwingUtilities.invokeLater( new Runnable() {
|
||||
SwingUtilities.invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
passwordField.setText( null );
|
||||
siteNameField.setText( null );
|
||||
passwordField.setText(null);
|
||||
siteNameField.setText(null);
|
||||
|
||||
dispatchEvent( new WindowEvent( PasswordFrame.this, WindowEvent.WINDOW_CLOSING ) );
|
||||
dispatchEvent(new WindowEvent(PasswordFrame.this, WindowEvent.WINDOW_CLOSING));
|
||||
}
|
||||
} );
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(final Throwable t) {
|
||||
}
|
||||
} );
|
||||
});
|
||||
}
|
||||
} );
|
||||
siteAddButton.setVisible( false );
|
||||
siteAddButton.addActionListener( new ActionListener() {
|
||||
});
|
||||
siteActionButton.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(final ActionEvent e) {
|
||||
PasswordFrame.this.user.addSite( currentSite );
|
||||
siteAddButton.setVisible( false );
|
||||
if (currentSite instanceof ModelSite)
|
||||
PasswordFrame.this.user.deleteSite(currentSite);
|
||||
else
|
||||
PasswordFrame.this.user.addSite(currentSite);
|
||||
|
||||
updatePassword(true);
|
||||
}
|
||||
} );
|
||||
});
|
||||
sitePanel.add( siteControls );
|
||||
sitePanel.add( Components.stud() );
|
||||
|
||||
@@ -114,7 +124,7 @@ public class PasswordFrame extends JFrame implements DocumentListener {
|
||||
siteTypeField.addItemListener( new ItemListener() {
|
||||
@Override
|
||||
public void itemStateChanged(final ItemEvent e) {
|
||||
updatePassword();
|
||||
updatePassword(true);
|
||||
}
|
||||
} );
|
||||
|
||||
@@ -124,7 +134,7 @@ public class PasswordFrame extends JFrame implements DocumentListener {
|
||||
siteVersionField.addItemListener( new ItemListener() {
|
||||
@Override
|
||||
public void itemStateChanged(final ItemEvent e) {
|
||||
updatePassword();
|
||||
updatePassword(true);
|
||||
}
|
||||
} );
|
||||
|
||||
@@ -133,7 +143,7 @@ public class PasswordFrame extends JFrame implements DocumentListener {
|
||||
siteCounterField.addChangeListener( new ChangeListener() {
|
||||
@Override
|
||||
public void stateChanged(final ChangeEvent e) {
|
||||
updatePassword();
|
||||
updatePassword(true);
|
||||
}
|
||||
} );
|
||||
|
||||
@@ -149,25 +159,27 @@ public class PasswordFrame extends JFrame implements DocumentListener {
|
||||
} );
|
||||
|
||||
// Password
|
||||
passwordField = new JPasswordField();
|
||||
passwordField.setAlignmentX( Component.CENTER_ALIGNMENT );
|
||||
passwordField.setEditable( false );
|
||||
passwordField.setHorizontalAlignment( JTextField.CENTER );
|
||||
passwordField.putClientProperty( "JPasswordField.cutCopyAllowed", true );
|
||||
passwordField = Components.passwordField();
|
||||
passwordField.setAlignmentX(Component.CENTER_ALIGNMENT);
|
||||
passwordField.setHorizontalAlignment(JTextField.CENTER);
|
||||
passwordField.putClientProperty("JPasswordField.cutCopyAllowed", true);
|
||||
passwordField.setEditable(false);
|
||||
passwordField.setBackground(null);
|
||||
passwordField.setBorder( null );
|
||||
passwordEchoChar = passwordField.getEchoChar();
|
||||
passwordEchoFont = passwordField.getFont().deriveFont( 40f );
|
||||
updateMask();
|
||||
|
||||
// Tip
|
||||
tipLabel = Components.label( " ", JLabel.CENTER );
|
||||
tipLabel = Components.label( " ", SwingConstants.CENTER );
|
||||
tipLabel.setAlignmentX( Component.CENTER_ALIGNMENT );
|
||||
JPanel passwordContainer = Components.boxLayout( BoxLayout.PAGE_AXIS, maskPasswordField, passwordField, tipLabel );
|
||||
JPanel passwordContainer = Components.boxLayout( BoxLayout.PAGE_AXIS, maskPasswordField, Box.createGlue(), passwordField, Box.createGlue(), tipLabel );
|
||||
passwordContainer.setOpaque( true );
|
||||
passwordContainer.setBackground( Color.white );
|
||||
passwordContainer.setBorder( BorderFactory.createEmptyBorder( 8, 8, 8, 8 ) );
|
||||
add( Components.borderPanel( passwordContainer, BorderFactory.createLoweredSoftBevelBorder(), Res.colors().frameBg() ),
|
||||
BorderLayout.SOUTH );
|
||||
root.add( Box.createVerticalStrut( 8 ) );
|
||||
root.add( Components.borderPanel( passwordContainer, BorderFactory.createLoweredSoftBevelBorder(), Res.colors().frameBg() ),
|
||||
BorderLayout.SOUTH );
|
||||
|
||||
pack();
|
||||
setMinimumSize( new Dimension( Math.max( 600, getPreferredSize().width ), Math.max( 300, getPreferredSize().height ) ) );
|
||||
@@ -183,25 +195,33 @@ public class PasswordFrame extends JFrame implements DocumentListener {
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
private ListenableFuture<String> updatePassword() {
|
||||
private ListenableFuture<String> updatePassword(boolean allowNameCompletion) {
|
||||
|
||||
final String siteNameQuery = siteNameField.getText();
|
||||
if (updatingUI)
|
||||
return Futures.immediateCancelledFuture();
|
||||
if (siteNameQuery == null || siteNameQuery.isEmpty() || !user.isKeyAvailable()) {
|
||||
siteActionButton.setVisible(false);
|
||||
tipLabel.setText( null );
|
||||
passwordField.setText( null );
|
||||
return Futures.immediateCancelledFuture();
|
||||
}
|
||||
|
||||
final MPSiteType siteType = siteTypeField.getModel().getElementAt( siteTypeField.getSelectedIndex() );
|
||||
final MasterKey.Version siteVersion = siteVersionField.getItemAt( siteVersionField.getSelectedIndex() );
|
||||
final MPSiteType siteType = siteTypeField.getModel().getElementAt(siteTypeField.getSelectedIndex());
|
||||
final MasterKey.Version siteVersion = siteVersionField.getItemAt(siteVersionField.getSelectedIndex());
|
||||
final int siteCounter = (Integer) siteCounterField.getValue();
|
||||
final Site site = currentSite != null && currentSite.getSiteName().equals( siteNameQuery )? currentSite
|
||||
: Iterables.getFirst( user.findSitesByName( siteNameQuery ),
|
||||
new IncognitoSite( siteNameQuery, siteType, siteCounter, siteVersion ) );
|
||||
assert site != null;
|
||||
if (site == currentSite) {
|
||||
|
||||
Iterable<Site> siteResults = user.findSitesByName(siteNameQuery);
|
||||
if (!allowNameCompletion)
|
||||
siteResults = FluentIterable.from(siteResults).filter(new PredicateNN<Site>() {
|
||||
@Override
|
||||
public boolean apply(Site input) {
|
||||
return siteNameQuery.equals(input.getSiteName());
|
||||
}
|
||||
});
|
||||
final Site site = Iterables.getFirst(siteResults,
|
||||
new IncognitoSite(siteNameQuery, siteType, siteCounter, siteVersion) );
|
||||
if (currentSite != null && site.getSiteName().equals(currentSite.getSiteName())) {
|
||||
site.setSiteType( siteType );
|
||||
site.setAlgorithmVersion( siteVersion );
|
||||
site.setSiteCounter( siteCounter );
|
||||
@@ -223,8 +243,12 @@ public class PasswordFrame extends JFrame implements DocumentListener {
|
||||
public void run() {
|
||||
updatingUI = true;
|
||||
currentSite = site;
|
||||
siteAddButton.setVisible( user instanceof ModelUser && !(currentSite instanceof ModelSite) );
|
||||
siteTypeField.setSelectedItem( currentSite.getSiteType() );
|
||||
siteActionButton.setVisible(user instanceof ModelUser);
|
||||
if (currentSite instanceof ModelSite)
|
||||
siteActionButton.setText("Delete Site");
|
||||
else
|
||||
siteActionButton.setText("Add Site");
|
||||
siteTypeField.setSelectedItem(currentSite.getSiteType());
|
||||
siteVersionField.setSelectedItem( currentSite.getAlgorithmVersion() );
|
||||
siteCounterField.setValue( currentSite.getSiteCounter() );
|
||||
siteNameField.setText( currentSite.getSiteName() );
|
||||
@@ -248,15 +272,16 @@ public class PasswordFrame extends JFrame implements DocumentListener {
|
||||
|
||||
@Override
|
||||
public void insertUpdate(final DocumentEvent e) {
|
||||
updatePassword();
|
||||
updatePassword(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeUpdate(final DocumentEvent e) {
|
||||
updatePassword(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changedUpdate(final DocumentEvent e) {
|
||||
updatePassword();
|
||||
updatePassword(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,6 +85,10 @@ public abstract class Res {
|
||||
return new RetinaIcon( Resources.getResource( "media/icon_add@2x.png" ) );
|
||||
}
|
||||
|
||||
public static Icon iconDelete() {
|
||||
return new RetinaIcon( Resources.getResource( "media/icon_delete@2x.png" ) );
|
||||
}
|
||||
|
||||
public static Icon iconQuestion() {
|
||||
return new RetinaIcon( Resources.getResource( "media/icon_question@2x.png" ) );
|
||||
}
|
||||
@@ -97,6 +101,10 @@ public abstract class Res {
|
||||
return 19;
|
||||
}
|
||||
|
||||
public static Font emoticonsFont() {
|
||||
return emoticonsRegular();
|
||||
}
|
||||
|
||||
public static Font controlFont() {
|
||||
return arimoRegular();
|
||||
}
|
||||
@@ -109,6 +117,10 @@ public abstract class Res {
|
||||
return sourceSansProBlack();
|
||||
}
|
||||
|
||||
public static Font emoticonsRegular() {
|
||||
return font( "fonts/Emoticons-Regular.otf" );
|
||||
}
|
||||
|
||||
public static Font sourceCodeProRegular() {
|
||||
return font( "fonts/SourceCodePro-Regular.otf" );
|
||||
}
|
||||
|
||||
@@ -2,10 +2,12 @@ package com.lyndir.masterpassword.gui;
|
||||
|
||||
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.*;
|
||||
|
||||
import com.lyndir.masterpassword.MPIdenticon;
|
||||
import com.lyndir.masterpassword.gui.util.Components;
|
||||
import com.lyndir.masterpassword.model.IncorrectMasterPasswordException;
|
||||
import java.awt.*;
|
||||
import java.awt.event.*;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.swing.*;
|
||||
|
||||
|
||||
@@ -16,6 +18,7 @@ public class UnlockFrame extends JFrame {
|
||||
|
||||
private final SignInCallback signInCallback;
|
||||
private final Components.GradientPanel root;
|
||||
private final JLabel identiconLabel;
|
||||
private final JButton signInButton;
|
||||
private final JPanel authenticationContainer;
|
||||
private AuthenticationPanel authenticationPanel;
|
||||
@@ -28,20 +31,39 @@ public class UnlockFrame extends JFrame {
|
||||
this.signInCallback = signInCallback;
|
||||
|
||||
setDefaultCloseOperation( DISPOSE_ON_CLOSE );
|
||||
setContentPane( root = Components.gradientPanel( new BorderLayout( 20, 20 ), Res.colors().frameBg() ) );
|
||||
root.setBorder( BorderFactory.createEmptyBorder( 20, 20, 20, 20 ) );
|
||||
addWindowFocusListener( new WindowAdapter() {
|
||||
@Override
|
||||
public void windowGainedFocus(WindowEvent e) {
|
||||
root.setGradientColor( Res.colors().frameBg() );
|
||||
}
|
||||
|
||||
authenticationContainer = Components.boxLayout( BoxLayout.PAGE_AXIS );
|
||||
authenticationContainer.setOpaque( true );
|
||||
authenticationContainer.setBackground( Res.colors().controlBg() );
|
||||
authenticationContainer.setBorder( BorderFactory.createEmptyBorder( 20, 20, 20, 20 ) );
|
||||
add( Components.borderPanel( authenticationContainer, BorderFactory.createRaisedBevelBorder(), Res.colors().frameBg() ) );
|
||||
@Override
|
||||
public void windowLostFocus(WindowEvent e) {
|
||||
root.setGradientColor( Color.RED );
|
||||
}
|
||||
} );
|
||||
|
||||
// Sign In
|
||||
JPanel signInBox = Components.boxLayout( BoxLayout.LINE_AXIS, Box.createGlue(), signInButton = Components.button( "Sign In" ),
|
||||
Box.createGlue() );
|
||||
signInBox.setBackground( null );
|
||||
root.add( signInBox, BorderLayout.SOUTH );
|
||||
|
||||
setContentPane( root = Components.gradientPanel( new FlowLayout(), Res.colors().frameBg() ) );
|
||||
root.setLayout( new BoxLayout( root, BoxLayout.PAGE_AXIS ) );
|
||||
root.setBorder( BorderFactory.createEmptyBorder( 20, 20, 20, 20 ) );
|
||||
root.add( Components.borderPanel( authenticationContainer = Components.boxLayout( BoxLayout.PAGE_AXIS ),
|
||||
BorderFactory.createRaisedBevelBorder(), Res.colors().frameBg() ) );
|
||||
root.add( Box.createVerticalStrut( 8 ) );
|
||||
root.add( identiconLabel = Components.label( " ", SwingConstants.CENTER ) );
|
||||
root.add( Box.createVerticalStrut( 8 ) );
|
||||
root.add( signInBox );
|
||||
|
||||
authenticationContainer.setOpaque( true );
|
||||
authenticationContainer.setBackground( Res.colors().controlBg() );
|
||||
authenticationContainer.setBorder( BorderFactory.createEmptyBorder( 20, 20, 20, 20 ) );
|
||||
identiconLabel.setFont( Res.emoticonsFont().deriveFont( 14.f ) );
|
||||
identiconLabel.setToolTipText(
|
||||
"A representation of your identity across all Master Password apps.\nIt should always be the same." );
|
||||
signInButton.addActionListener( new AbstractAction() {
|
||||
@Override
|
||||
public void actionPerformed(final ActionEvent e) {
|
||||
@@ -74,6 +96,7 @@ public class UnlockFrame extends JFrame {
|
||||
authenticationContainer.add( Components.stud() );
|
||||
|
||||
final JCheckBox incognitoCheckBox = Components.checkBox( "Incognito" );
|
||||
incognitoCheckBox.setToolTipText( "Log in without saving any information." );
|
||||
incognitoCheckBox.setSelected( incognito );
|
||||
incognitoCheckBox.addItemListener( new ItemListener() {
|
||||
@Override
|
||||
@@ -91,10 +114,11 @@ public class UnlockFrame extends JFrame {
|
||||
JComponent toolsPanel = Components.boxLayout( BoxLayout.LINE_AXIS, incognitoCheckBox, Box.createGlue() );
|
||||
authenticationContainer.add( toolsPanel );
|
||||
for (JButton button : authenticationPanel.getButtons()) {
|
||||
toolsPanel.add( button );
|
||||
button.setBorder( BorderFactory.createEmptyBorder() );
|
||||
button.setMargin( new Insets( 0, 0, 0, 0 ) );
|
||||
button.setAlignmentX( RIGHT_ALIGNMENT );
|
||||
button.setBorder( null );
|
||||
toolsPanel.add( button );
|
||||
button.setContentAreaFilled( false );
|
||||
}
|
||||
|
||||
checkSignIn();
|
||||
@@ -109,13 +133,24 @@ public class UnlockFrame extends JFrame {
|
||||
} );
|
||||
}
|
||||
|
||||
void updateUser(User user) {
|
||||
void updateUser(@Nullable User user) {
|
||||
this.user = user;
|
||||
checkSignIn();
|
||||
}
|
||||
|
||||
boolean checkSignIn() {
|
||||
boolean enabled = user != null && !user.getFullName().isEmpty() && authenticationPanel.getMasterPassword().length > 0;
|
||||
String fullName = user == null? "": user.getFullName();
|
||||
char[] masterPassword = authenticationPanel.getMasterPassword();
|
||||
boolean enabled = !fullName.isEmpty() && masterPassword.length > 0;
|
||||
|
||||
if (fullName.isEmpty() || masterPassword.length == 0)
|
||||
identiconLabel.setText( " " );
|
||||
else {
|
||||
MPIdenticon identicon = new MPIdenticon( fullName, masterPassword );
|
||||
identiconLabel.setText( identicon.getText() );
|
||||
identiconLabel.setForeground( identicon.getColor().getAWTColor( MPIdenticon.BackgroundMode.DARK ) );
|
||||
}
|
||||
|
||||
signInButton.setEnabled( enabled );
|
||||
|
||||
return enabled;
|
||||
@@ -158,7 +193,6 @@ public class UnlockFrame extends JFrame {
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
2
MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/masterpassword/gui/User.java
Normal file → Executable file
@@ -62,6 +62,8 @@ public abstract class User {
|
||||
|
||||
public abstract void addSite(final Site site);
|
||||
|
||||
public abstract void deleteSite(final Site site);
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object obj) {
|
||||
return this == obj || obj instanceof User && Objects.equals( getFullName(), ((User) obj).getFullName() );
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
package com.lyndir.masterpassword.gui;
|
||||
package com.lyndir.masterpassword.gui.platform.mac;
|
||||
|
||||
import com.apple.eawt.*;
|
||||
import com.lyndir.masterpassword.gui.GUI;
|
||||
import com.lyndir.masterpassword.gui.PasswordFrame;
|
||||
import com.lyndir.masterpassword.gui.User;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
|
||||
@@ -116,6 +116,7 @@ public abstract class Components {
|
||||
((DefaultEditor) getEditor()).getTextField().setBorder( editorBorder );
|
||||
setAlignmentX( LEFT_ALIGNMENT );
|
||||
setAlignmentY( BOTTOM_ALIGNMENT );
|
||||
setBorder( null );
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -125,12 +126,21 @@ public abstract class Components {
|
||||
};
|
||||
}
|
||||
|
||||
public static JLabel label(final String label) {
|
||||
return label( label, JLabel.LEADING );
|
||||
public static JLabel label(@Nullable String label) {
|
||||
return label( label, SwingConstants.LEADING );
|
||||
}
|
||||
|
||||
public static JLabel label(final String label, final int alignment) {
|
||||
return new JLabel( label, alignment ) {
|
||||
/**
|
||||
* @param horizontalAlignment One of the following constants
|
||||
* defined in <code>SwingConstants</code>:
|
||||
* <code>LEFT</code>,
|
||||
* <code>CENTER</code>,
|
||||
* <code>RIGHT</code>,
|
||||
* <code>LEADING</code> or
|
||||
* <code>TRAILING</code>.
|
||||
*/
|
||||
public static JLabel label(@Nullable final String label, final int horizontalAlignment) {
|
||||
return new JLabel( label, horizontalAlignment ) {
|
||||
{
|
||||
setFont( Res.controlFont().deriveFont( 12f ) );
|
||||
setAlignmentX( LEFT_ALIGNMENT );
|
||||
@@ -148,6 +158,7 @@ public abstract class Components {
|
||||
return new JCheckBox( label ) {
|
||||
{
|
||||
setFont( Res.controlFont().deriveFont( 12f ) );
|
||||
setBackground( null );
|
||||
setAlignmentX( LEFT_ALIGNMENT );
|
||||
setAlignmentY( BOTTOM_ALIGNMENT );
|
||||
}
|
||||
@@ -162,9 +173,14 @@ public abstract class Components {
|
||||
public static <M> JComboBox<M> comboBox(final ComboBoxModel<M> model) {
|
||||
return new JComboBox<M>( model ) {
|
||||
{
|
||||
// CompoundBorder editorBorder = BorderFactory.createCompoundBorder(
|
||||
// BorderFactory.createLineBorder( Res.colors().controlBorder(), 1, true ),
|
||||
// BorderFactory.createEmptyBorder( 4, 4, 4, 4 ) );
|
||||
// ((JComponent) ((BasicComboBoxEditor) getEditor()).getEditorComponent()).setBorder(editorBorder);
|
||||
setFont( Res.controlFont().deriveFont( 12f ) );
|
||||
setAlignmentX( LEFT_ALIGNMENT );
|
||||
setAlignmentY( BOTTOM_ALIGNMENT );
|
||||
// setBorder(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -195,14 +211,17 @@ public abstract class Components {
|
||||
|
||||
public void setGradientColor(@Nullable final Color gradientColor) {
|
||||
this.gradientColor = gradientColor;
|
||||
revalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doLayout() {
|
||||
super.doLayout();
|
||||
|
||||
if (gradientColor != null)
|
||||
if (gradientColor != null) {
|
||||
paint = new GradientPaint( new Point( 0, 0 ), gradientColor, new Point( getWidth(), getHeight() ), gradientColor.darker() );
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>com.lyndir.masterpassword</groupId>
|
||||
<artifactId>masterpassword</artifactId>
|
||||
<version>2.2</version>
|
||||
<version>2.3</version>
|
||||
</parent>
|
||||
|
||||
<name>Master Password Site Model</name>
|
||||
@@ -23,7 +23,7 @@
|
||||
<dependency>
|
||||
<groupId>com.lyndir.masterpassword</groupId>
|
||||
<artifactId>masterpassword-algorithm</artifactId>
|
||||
<version>2.2</version>
|
||||
<version>2.3</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
|
||||
4
MasterPassword/Java/masterpassword-model/src/main/java/com/lyndir/masterpassword/model/MPUser.java
Normal file → Executable file
@@ -59,6 +59,10 @@ public class MPUser implements Comparable<MPUser> {
|
||||
sites.add( site );
|
||||
}
|
||||
|
||||
public void deleteSite(final MPSite site) {
|
||||
sites.remove( site );
|
||||
}
|
||||
|
||||
public String getFullName() {
|
||||
return fullName;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import com.google.common.collect.*;
|
||||
import com.google.common.io.CharSink;
|
||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||
import java.io.*;
|
||||
import java.util.SortedSet;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
|
||||
@@ -15,7 +16,7 @@ public class MPUserFileManager extends MPUserManager {
|
||||
|
||||
@SuppressWarnings("UnusedDeclaration")
|
||||
private static final Logger logger = Logger.get( MPUserFileManager.class );
|
||||
private static final File mpwd = new File( System.getProperty( "user.home" ), ".mpw.d" );
|
||||
private static final File mpwd = new File( System.getProperty( "user.home" ), ".mpw.d" );
|
||||
private static final MPUserFileManager instance;
|
||||
|
||||
static {
|
||||
@@ -70,19 +71,53 @@ public class MPUserFileManager extends MPUserManager {
|
||||
} ).filter( Predicates.notNull() );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addUser(final MPUser user) {
|
||||
super.addUser( user );
|
||||
save();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteUser(final MPUser user) {
|
||||
super.deleteUser( user );
|
||||
save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the current user state to disk.
|
||||
*/
|
||||
public void save() {
|
||||
// Save existing users.
|
||||
for (final MPUser user : getUsers())
|
||||
try {
|
||||
new CharSink() {
|
||||
@Override
|
||||
public Writer openStream()
|
||||
throws IOException {
|
||||
return new FileWriter( new File(userFilesDirectory, user.getFullName() + ".mpsites" ) );
|
||||
return new FileWriter( new File( userFilesDirectory, user.getFullName() + ".mpsites" ) );
|
||||
}
|
||||
}.write( MPSiteMarshaller.marshallSafe( user ).getExport() );
|
||||
}
|
||||
catch (IOException e) {
|
||||
logger.err( e, "Unable to save sites for user: %s", user );
|
||||
}
|
||||
|
||||
// Remove deleted users.
|
||||
for (File userFile : userFilesDirectory.listFiles( new FilenameFilter() {
|
||||
@Override
|
||||
public boolean accept(final File dir, final String name) {
|
||||
return name.endsWith( ".mpsites" );
|
||||
}
|
||||
} ))
|
||||
if (getUserNamed( userFile.getName().replaceFirst( "\\.mpsites$", "" ) ) == null)
|
||||
if (!userFile.delete())
|
||||
logger.err( "Couldn't delete file: %s", userFile );
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The location on the file system where the user models are stored.
|
||||
*/
|
||||
public File getPath() {
|
||||
return mpwd;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,14 +18,22 @@ public abstract class MPUserManager {
|
||||
|
||||
protected MPUserManager(final Iterable<MPUser> users) {
|
||||
for (MPUser user : users)
|
||||
addUser( user );
|
||||
usersByName.put( user.getFullName(), user );
|
||||
}
|
||||
|
||||
public SortedSet<MPUser> getUsers() {
|
||||
return FluentIterable.from( usersByName.values() ).toSortedSet( Ordering.natural() );
|
||||
}
|
||||
|
||||
public MPUser getUserNamed(String fullName) {
|
||||
return usersByName.get( fullName );
|
||||
}
|
||||
|
||||
public void addUser(final MPUser user) {
|
||||
usersByName.put( user.getFullName(), user );
|
||||
}
|
||||
|
||||
public void deleteUser(final MPUser user) {
|
||||
usersByName.remove( user.getFullName() );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>com.lyndir.lhunath</groupId>
|
||||
<artifactId>lyndir</artifactId>
|
||||
<version>1.20</version>
|
||||
<version>1.22</version>
|
||||
</parent>
|
||||
|
||||
<name>Master Password</name>
|
||||
@@ -15,7 +15,7 @@
|
||||
|
||||
<groupId>com.lyndir.masterpassword</groupId>
|
||||
<artifactId>masterpassword</artifactId>
|
||||
<version>2.2</version>
|
||||
<version>2.3</version>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<modules>
|
||||
@@ -26,6 +26,12 @@
|
||||
</modules>
|
||||
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>release</id>
|
||||
<modules>
|
||||
<module>masterpassword-android</module>
|
||||
</modules>
|
||||
</profile>
|
||||
<profile>
|
||||
<id>mod:android</id>
|
||||
<modules>
|
||||
@@ -52,4 +58,7 @@
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<scm>
|
||||
<tag>2.3</tag>
|
||||
</scm>
|
||||
</project>
|
||||
|
||||
@@ -48,9 +48,8 @@ NSString *NSStringFromTimeToCrack(TimeToCrack timeToCrack);
|
||||
- (BOOL)tryMigrateUser:(MPUserEntity *)user inContext:(NSManagedObjectContext *)moc;
|
||||
- (BOOL)tryMigrateSite:(MPSiteEntity *)site explicit:(BOOL)explicit;
|
||||
|
||||
- (MPKey *)keyForPassword:(NSString *)password ofUserNamed:(NSString *)userName;
|
||||
- (MPKey *)keyFromKeyData:(NSData *)keyData;
|
||||
- (NSData *)keyIDForKeyData:(NSData *)keyData;
|
||||
- (NSData *)keyDataForFullName:(NSString *)fullName withMasterPassword:(NSString *)masterPassword;
|
||||
|
||||
- (NSString *)nameOfType:(MPSiteType)type;
|
||||
- (NSString *)shortNameOfType:(MPSiteType)type;
|
||||
|
||||
@@ -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;
|
||||
@@ -89,7 +111,7 @@
|
||||
|
||||
- (BOOL)tryMigrateSite:(MPSiteEntity *)site explicit:(BOOL)explicit {
|
||||
|
||||
if (site.version != [self version] - 1)
|
||||
if ([site.algorithm version] != [self version] - 1)
|
||||
// Only migrate from previous version.
|
||||
return NO;
|
||||
|
||||
@@ -101,24 +123,23 @@
|
||||
|
||||
// Apply migration.
|
||||
site.requiresExplicitMigration = NO;
|
||||
site.version = [self version];
|
||||
site.algorithm = self;
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (MPKey *)keyForPassword:(NSString *)password ofUserNamed:(NSString *)userName {
|
||||
- (NSData *)keyDataForFullName:(NSString *)fullName withMasterPassword:(NSString *)masterPassword {
|
||||
|
||||
NSDate *start = [NSDate date];
|
||||
uint8_t const *masterKeyBytes = mpw_masterKeyForUser( userName.UTF8String, password.UTF8String, [self version] );
|
||||
MPKey *masterKey = [self keyFromKeyData:[NSData dataWithBytes:masterKeyBytes length:MP_dkLen]];
|
||||
mpw_free( masterKeyBytes, MP_dkLen );
|
||||
trc( @"User: %@, password: %@ derives to key ID: %@ (took %0.2fs)", userName, password, [masterKey.keyID encodeHex],
|
||||
-[start timeIntervalSinceNow] );
|
||||
return masterKey;
|
||||
}
|
||||
__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 );
|
||||
}];
|
||||
|
||||
- (MPKey *)keyFromKeyData:(NSData *)keyData {
|
||||
|
||||
return [[MPKey alloc] initWithKeyData:keyData algorithm:self];
|
||||
return keyData;
|
||||
}
|
||||
|
||||
- (NSData *)keyIDForKeyData:(NSData *)keyData {
|
||||
@@ -322,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.keyData.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;
|
||||
}
|
||||
@@ -342,7 +366,7 @@
|
||||
|
||||
- (BOOL)savePassword:(NSString *)clearContent toSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey {
|
||||
|
||||
NSAssert( [siteKey.keyID isEqualToData:site.user.keyID], @"Site does not belong to current user." );
|
||||
NSAssert( [[siteKey keyIDForAlgorithm:site.user.algorithm] isEqualToData:site.user.keyID], @"Site does not belong to current user." );
|
||||
switch (site.type) {
|
||||
case MPSiteTypeGeneratedMaximum:
|
||||
case MPSiteTypeGeneratedLong:
|
||||
@@ -363,8 +387,9 @@
|
||||
return NO;
|
||||
}
|
||||
|
||||
NSData *encryptionKey = [siteKey keyDataForAlgorithm:self trimmedLength:PearlCryptKeySize];
|
||||
NSData *encryptedContent = [[clearContent dataUsingEncoding:NSUTF8StringEncoding]
|
||||
encryptWithSymmetricKey:[siteKey subKeyOfLength:PearlCryptKeySize].keyData padding:YES];
|
||||
encryptWithSymmetricKey:encryptionKey padding:YES];
|
||||
if ([((MPStoredSiteEntity *)site).contentObject isEqualToData:encryptedContent])
|
||||
return NO;
|
||||
|
||||
@@ -378,14 +403,15 @@
|
||||
return NO;
|
||||
}
|
||||
|
||||
NSData *encryptionKey = [siteKey keyDataForAlgorithm:self trimmedLength:PearlCryptKeySize];
|
||||
NSData *encryptedContent = [[clearContent dataUsingEncoding:NSUTF8StringEncoding]
|
||||
encryptWithSymmetricKey:[siteKey subKeyOfLength:PearlCryptKeySize].keyData padding:YES];
|
||||
encryptWithSymmetricKey:encryptionKey padding:YES];
|
||||
NSDictionary *siteQuery = [self queryForDevicePrivateSiteNamed:site.name];
|
||||
if (!encryptedContent)
|
||||
[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
|
||||
@@ -456,14 +482,14 @@
|
||||
|
||||
- (void)resolveLoginForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey result:(void ( ^ )(NSString *result))resultBlock {
|
||||
|
||||
NSAssert( [siteKey.keyID isEqualToData:site.user.keyID], @"Site does not belong to current user." );
|
||||
NSAssert( [[siteKey keyIDForAlgorithm:site.user.algorithm] isEqualToData:site.user.keyID], @"Site does not belong to current user." );
|
||||
NSString *name = site.name;
|
||||
BOOL loginGenerated = site.loginGenerated && [[MPAppDelegate_Shared get] isFeatureUnlocked:MPProductGenerateLogins];
|
||||
NSString *loginName = loginGenerated? nil: site.loginName;
|
||||
id<MPAlgorithm> algorithm = nil;
|
||||
if (!name.length)
|
||||
err( @"Missing name." );
|
||||
else if (!siteKey.keyData.length)
|
||||
else if (!siteKey)
|
||||
err( @"Missing key." );
|
||||
else
|
||||
algorithm = site.algorithm;
|
||||
@@ -478,7 +504,7 @@
|
||||
|
||||
- (void)resolvePasswordForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey result:(void ( ^ )(NSString *result))resultBlock {
|
||||
|
||||
NSAssert( [siteKey.keyID isEqualToData:site.user.keyID], @"Site does not belong to current user." );
|
||||
NSAssert( [[siteKey keyIDForAlgorithm:site.user.algorithm] isEqualToData:site.user.keyID], @"Site does not belong to current user." );
|
||||
switch (site.type) {
|
||||
case MPSiteTypeGeneratedMaximum:
|
||||
case MPSiteTypeGeneratedLong:
|
||||
@@ -500,7 +526,7 @@
|
||||
id<MPAlgorithm> algorithm = nil;
|
||||
if (!site.name.length)
|
||||
err( @"Missing name." );
|
||||
else if (!siteKey.keyData.length)
|
||||
else if (!siteKey)
|
||||
err( @"Missing key." );
|
||||
else
|
||||
algorithm = site.algorithm;
|
||||
@@ -546,12 +572,12 @@
|
||||
|
||||
- (void)resolveAnswerForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey result:(void ( ^ )(NSString *result))resultBlock {
|
||||
|
||||
NSAssert( [siteKey.keyID isEqualToData:site.user.keyID], @"Site does not belong to current user." );
|
||||
NSAssert( [[siteKey keyIDForAlgorithm:site.user.algorithm] isEqualToData:site.user.keyID], @"Site does not belong to current user." );
|
||||
NSString *name = site.name;
|
||||
id<MPAlgorithm> algorithm = nil;
|
||||
if (!site.name.length)
|
||||
err( @"Missing name." );
|
||||
else if (!siteKey.keyData.length)
|
||||
else if (!siteKey)
|
||||
err( @"Missing key." );
|
||||
else
|
||||
algorithm = site.algorithm;
|
||||
@@ -565,13 +591,14 @@
|
||||
- (void)resolveAnswerForQuestion:(MPSiteQuestionEntity *)question usingKey:(MPKey *)siteKey
|
||||
result:(void ( ^ )(NSString *result))resultBlock {
|
||||
|
||||
NSAssert( [siteKey.keyID 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;
|
||||
if (!name.length)
|
||||
err( @"Missing name." );
|
||||
else if (!siteKey.keyData.length)
|
||||
else if (!siteKey)
|
||||
err( @"Missing key." );
|
||||
else
|
||||
algorithm = question.site.algorithm;
|
||||
@@ -585,7 +612,7 @@
|
||||
- (void)importProtectedPassword:(NSString *)protectedContent protectedByKey:(MPKey *)importKey
|
||||
intoSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey {
|
||||
|
||||
NSAssert( [siteKey.keyID isEqualToData:site.user.keyID], @"Site does not belong to current user." );
|
||||
NSAssert( [[siteKey keyIDForAlgorithm:site.user.algorithm] isEqualToData:site.user.keyID], @"Site does not belong to current user." );
|
||||
switch (site.type) {
|
||||
case MPSiteTypeGeneratedMaximum:
|
||||
case MPSiteTypeGeneratedLong:
|
||||
@@ -603,7 +630,7 @@
|
||||
(long)site.type, [site class] );
|
||||
break;
|
||||
}
|
||||
if ([importKey.keyID isEqualToData:siteKey.keyID])
|
||||
if ([[importKey keyIDForAlgorithm:self] isEqualToData:[siteKey keyIDForAlgorithm:self]])
|
||||
((MPStoredSiteEntity *)site).contentObject = [protectedContent decodeBase64];
|
||||
|
||||
else {
|
||||
@@ -620,7 +647,7 @@
|
||||
|
||||
- (void)importClearTextPassword:(NSString *)clearContent intoSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey {
|
||||
|
||||
NSAssert( [siteKey.keyID isEqualToData:site.user.keyID], @"Site does not belong to current user." );
|
||||
NSAssert( [[siteKey keyIDForAlgorithm:site.user.algorithm] isEqualToData:site.user.keyID], @"Site does not belong to current user." );
|
||||
switch (site.type) {
|
||||
case MPSiteTypeGeneratedMaximum:
|
||||
case MPSiteTypeGeneratedLong:
|
||||
@@ -644,7 +671,7 @@
|
||||
|
||||
- (NSString *)exportPasswordForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey {
|
||||
|
||||
NSAssert( [siteKey.keyID isEqualToData:site.user.keyID], @"Site does not belong to current user." );
|
||||
NSAssert( [[siteKey keyIDForAlgorithm:site.user.algorithm] isEqualToData:site.user.keyID], @"Site does not belong to current user." );
|
||||
if (!(site.type & MPSiteFeatureExportContent))
|
||||
return nil;
|
||||
|
||||
@@ -701,8 +728,10 @@
|
||||
if (!key)
|
||||
return nil;
|
||||
NSData *decryptedContent = nil;
|
||||
if ([encryptedContent length])
|
||||
decryptedContent = [encryptedContent decryptWithSymmetricKey:[key subKeyOfLength:PearlCryptKeySize].keyData padding:YES];
|
||||
if ([encryptedContent length]) {
|
||||
NSData *encryptionKey = [key keyDataForAlgorithm:self trimmedLength:PearlCryptKeySize];
|
||||
decryptedContent = [encryptedContent decryptWithSymmetricKey:encryptionKey padding:YES];
|
||||
}
|
||||
if (!decryptedContent)
|
||||
return nil;
|
||||
|
||||
@@ -711,7 +740,7 @@
|
||||
|
||||
- (BOOL)timeToCrack:(out TimeToCrack *)timeToCrack passwordOfType:(MPSiteType)type byAttacker:(MPAttacker)attacker {
|
||||
|
||||
if (!type)
|
||||
if (!(type & MPSiteTypeClassGenerated))
|
||||
return NO;
|
||||
size_t count = 0;
|
||||
const char **templates = mpw_templatesForType( type, &count );
|
||||
@@ -730,6 +759,7 @@
|
||||
BN_add( permutations, permutations, templatePermutations );
|
||||
}
|
||||
BN_free( templatePermutations );
|
||||
free( templates );
|
||||
|
||||
return [self timeToCrack:timeToCrack permutations:permutations forAttacker:attacker];
|
||||
}
|
||||
@@ -748,7 +778,7 @@
|
||||
|
||||
if (strchr( charactersForClass, passwordCharacter )) {
|
||||
// Found class for password character.
|
||||
characterEntropy = (BN_ULONG)strlen(charactersForClass);
|
||||
characterEntropy = (BN_ULONG)strlen( charactersForClass );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
|
||||
- (BOOL)tryMigrateSite:(MPSiteEntity *)site explicit:(BOOL)explicit {
|
||||
|
||||
if (site.version != [self version] - 1)
|
||||
if ([site.algorithm version] != [self version] - 1)
|
||||
// Only migrate from previous version.
|
||||
return NO;
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
|
||||
// Apply migration.
|
||||
site.requiresExplicitMigration = NO;
|
||||
site.version = [self version];
|
||||
site.algorithm = self;
|
||||
return YES;
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
// Copyright 2012 lhunath (Maarten Billemont). All rights reserved.
|
||||
//
|
||||
|
||||
#import <objc/runtime.h>
|
||||
#import "MPAlgorithmV2.h"
|
||||
#import "MPEntities.h"
|
||||
|
||||
@@ -28,7 +27,7 @@
|
||||
|
||||
- (BOOL)tryMigrateSite:(MPSiteEntity *)site explicit:(BOOL)explicit {
|
||||
|
||||
if (site.version != [self version] - 1)
|
||||
if ([site.algorithm version] != [self version] - 1)
|
||||
// Only migrate from previous version.
|
||||
return NO;
|
||||
|
||||
@@ -42,7 +41,7 @@
|
||||
|
||||
// Apply migration.
|
||||
site.requiresExplicitMigration = NO;
|
||||
site.version = [self version];
|
||||
site.algorithm = self;
|
||||
return YES;
|
||||
}
|
||||
|
||||
|
||||
@@ -27,12 +27,13 @@
|
||||
|
||||
- (BOOL)tryMigrateSite:(MPSiteEntity *)site explicit:(BOOL)explicit {
|
||||
|
||||
if (site.version != [self version] - 1)
|
||||
if ([site.algorithm version] != [self version] - 1)
|
||||
// Only migrate from previous version.
|
||||
return NO;
|
||||
|
||||
if (!explicit) {
|
||||
if (site.type & MPSiteTypeClassGenerated && site.name.length != [site.name dataUsingEncoding:NSUTF8StringEncoding].length) {
|
||||
if (site.type & MPSiteTypeClassGenerated &&
|
||||
site.user.name.length != [site.user.name dataUsingEncoding:NSUTF8StringEncoding].length) {
|
||||
// This migration requires explicit permission for types of the generated class.
|
||||
site.requiresExplicitMigration = YES;
|
||||
return NO;
|
||||
@@ -41,7 +42,7 @@
|
||||
|
||||
// Apply migration.
|
||||
site.requiresExplicitMigration = NO;
|
||||
site.version = [self version];
|
||||
site.algorithm = self;
|
||||
return YES;
|
||||
}
|
||||
|
||||
|
||||
@@ -31,12 +31,12 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
|
||||
|
||||
NSData *keyData = [PearlKeyChain dataOfItemForQuery:keyQuery( user )];
|
||||
if (!keyData) {
|
||||
inf( @"No key found in keychain for: %@", user.userID );
|
||||
inf( @"No key found in keychain for user: %@", user.userID );
|
||||
return nil;
|
||||
}
|
||||
|
||||
inf( @"Found key in keychain for: %@", user.userID );
|
||||
return [MPAlgorithmDefault keyFromKeyData:keyData];
|
||||
inf( @"Found key in keychain for user: %@", user.userID );
|
||||
return [[MPKey alloc] initForFullName:user.name withKeyData:keyData forAlgorithm:user.algorithm];
|
||||
}
|
||||
|
||||
- (void)storeSavedKeyFor:(MPUserEntity *)user {
|
||||
@@ -44,12 +44,12 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
|
||||
if (user.saveKey) {
|
||||
NSData *existingKeyData = [PearlKeyChain dataOfItemForQuery:keyQuery( user )];
|
||||
|
||||
if (![existingKeyData isEqualToData:self.key.keyData]) {
|
||||
inf( @"Saving key in keychain for: %@", user.userID );
|
||||
if (![existingKeyData isEqualToData:[self.key keyDataForAlgorithm:user.algorithm]]) {
|
||||
inf( @"Saving key in keychain for user: %@", user.userID );
|
||||
|
||||
[PearlKeyChain addOrUpdateItemForQuery:keyQuery( user )
|
||||
withAttributes:@{
|
||||
(__bridge id)kSecValueData : self.key.keyData,
|
||||
(__bridge id)kSecValueData : [self.key keyDataForAlgorithm:user.algorithm],
|
||||
#if TARGET_OS_IPHONE
|
||||
(__bridge id)kSecAttrAccessible : (__bridge id)kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
|
||||
#endif
|
||||
@@ -62,7 +62,7 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
|
||||
|
||||
OSStatus result = [PearlKeyChain deleteItemForQuery:keyQuery( user )];
|
||||
if (result == noErr) {
|
||||
inf( @"Removed key from keychain for: %@", user.userID );
|
||||
inf( @"Removed key from keychain for user: %@", user.userID );
|
||||
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:MPKeyForgottenNotification object:self];
|
||||
}
|
||||
@@ -88,8 +88,8 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
|
||||
|
||||
// Method 1: When the user has no keyID set, set a new key from the given master password.
|
||||
if (!user.keyID) {
|
||||
if ([password length] && (tryKey = [MPAlgorithmDefault keyForPassword:password ofUserNamed:user.name])) {
|
||||
user.keyID = tryKey.keyID;
|
||||
if ([password length] && (tryKey = [[MPKey alloc] initForFullName:user.name withMasterPassword:password])) {
|
||||
user.keyID = [tryKey keyIDForAlgorithm:MPAlgorithmDefault];
|
||||
|
||||
// Migrate existing sites.
|
||||
[self migrateSitesForUser:user saveInContext:moc toKey:tryKey];
|
||||
@@ -103,9 +103,11 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
|
||||
|
||||
else if (!tryKey) {
|
||||
// Key should be saved in keychain. Load it.
|
||||
if ((tryKey = [self loadSavedKeyFor:user]) && ![user.keyID isEqual:tryKey.keyID]) {
|
||||
if ((tryKey = [self loadSavedKeyFor:user]) && ![user.keyID isEqual:[tryKey keyIDForAlgorithm:user.algorithm]]) {
|
||||
// Loaded password doesn't match user's keyID. Forget saved password: it is incorrect.
|
||||
inf( @"Saved password doesn't match keyID for: %@", user.userID );
|
||||
inf( @"Saved password doesn't match keyID for user: %@", user.userID );
|
||||
trc( @"user keyID: %@ (version: %d) != authentication keyID: %@",
|
||||
user.keyID, user.algorithm.version, [tryKey keyIDForAlgorithm:user.algorithm] );
|
||||
|
||||
tryKey = nil;
|
||||
[self forgetSavedKeyFor:user];
|
||||
@@ -113,9 +115,11 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
|
||||
}
|
||||
|
||||
// Method 3: Check the given master password string.
|
||||
if (!tryKey && [password length] && (tryKey = [MPAlgorithmDefault keyForPassword:password ofUserNamed:user.name]) &&
|
||||
![user.keyID isEqual:tryKey.keyID]) {
|
||||
inf( @"Key derived from password doesn't match keyID for: %@", user.userID );
|
||||
if (!tryKey && [password length] && (tryKey = [[MPKey alloc] initForFullName:user.name withMasterPassword:password]) &&
|
||||
![user.keyID isEqual:[tryKey keyIDForAlgorithm:user.algorithm]]) {
|
||||
inf( @"Key derived from password doesn't match keyID for user: %@", user.userID );
|
||||
trc( @"user keyID: %@ (version: %u) != authentication keyID: %@",
|
||||
user.keyID, user.algorithm.version, [tryKey keyIDForAlgorithm:user.algorithm] );
|
||||
|
||||
tryKey = nil;
|
||||
}
|
||||
@@ -123,13 +127,22 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
|
||||
// No more methods left, fail if key still not known.
|
||||
if (!tryKey) {
|
||||
if (password)
|
||||
inf( @"Login failed for: %@", user.userID );
|
||||
inf( @"Password login failed for user: %@", user.userID );
|
||||
else
|
||||
dbg( @"Automatic login failed for user: %@", user.userID );
|
||||
|
||||
return NO;
|
||||
}
|
||||
inf( @"Logged in: %@", user.userID );
|
||||
inf( @"Logged in user: %@", user.userID );
|
||||
|
||||
if (![self.key isEqualToKey:tryKey]) {
|
||||
// Upgrade the user's keyID if not at the default version yet.
|
||||
if (user.algorithm.version != MPAlgorithmDefaultVersion) {
|
||||
user.algorithm = MPAlgorithmDefault;
|
||||
user.keyID = [tryKey keyIDForAlgorithm:user.algorithm];
|
||||
inf( @"Upgraded keyID to version %u for user: %@", user.algorithm.version, user.userID );
|
||||
}
|
||||
|
||||
self.key = tryKey;
|
||||
[self storeSavedKeyFor:user];
|
||||
}
|
||||
@@ -205,7 +218,7 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
|
||||
// Don't Migrate
|
||||
break;
|
||||
|
||||
recoverKey = [site.algorithm keyForPassword:masterPassword ofUserNamed:user.name];
|
||||
recoverKey = [[MPKey alloc] initForFullName:user.name withMasterPassword:masterPassword];
|
||||
}
|
||||
|
||||
if (!content)
|
||||
|
||||
@@ -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] );
|
||||
}
|
||||
}];
|
||||
} );
|
||||
|
||||
|
||||
@@ -423,14 +423,15 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
|
||||
}
|
||||
|
||||
MPSiteType type = activeUser.defaultType;
|
||||
NSString *typeEntityName = [MPAlgorithmDefault classNameOfType:type];
|
||||
id<MPAlgorithm> algorithm = MPAlgorithmDefault;
|
||||
Class entityType = [algorithm classOfType:type];
|
||||
|
||||
MPSiteEntity *site = [NSEntityDescription insertNewObjectForEntityForName:typeEntityName inManagedObjectContext:context];
|
||||
MPSiteEntity *site = (MPSiteEntity *)[entityType insertNewObjectInContext:context];
|
||||
site.name = siteName;
|
||||
site.user = activeUser;
|
||||
site.type = type;
|
||||
site.lastUsed = [NSDate date];
|
||||
site.version = MPAlgorithmDefaultVersion;
|
||||
site.algorithm = algorithm;
|
||||
|
||||
NSError *error = nil;
|
||||
if (site.objectID.isTemporaryID && ![context obtainPermanentIDsForObjects:@[ site ] error:&error])
|
||||
@@ -454,14 +455,14 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
|
||||
|
||||
else {
|
||||
// Type requires a different class of site. Recreate the site.
|
||||
NSString *typeEntityName = [site.algorithm classNameOfType:type];
|
||||
MPSiteEntity *newSite = [NSEntityDescription insertNewObjectForEntityForName:typeEntityName inManagedObjectContext:context];
|
||||
Class entityType = [site.algorithm classOfType:type];
|
||||
MPSiteEntity *newSite = (MPSiteEntity *)[entityType insertNewObjectInContext:context];
|
||||
newSite.type = type;
|
||||
newSite.name = site.name;
|
||||
newSite.user = site.user;
|
||||
newSite.uses = site.uses;
|
||||
newSite.lastUsed = site.lastUsed;
|
||||
newSite.version = site.version;
|
||||
newSite.algorithm = site.algorithm;
|
||||
newSite.loginName = site.loginName;
|
||||
|
||||
NSError *error = nil;
|
||||
@@ -685,13 +686,13 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
|
||||
inf( @"Import cancelled." );
|
||||
return MPImportResultCancelled;
|
||||
}
|
||||
MPKey *userKey = [MPAlgorithmDefault keyForPassword:userMasterPassword ofUserNamed:user? user.name: importUserName];
|
||||
if (user && ![userKey.keyID isEqualToData:user.keyID])
|
||||
MPKey *userKey = [[MPKey alloc] initForFullName:user? user.name: importUserName withMasterPassword:userMasterPassword];
|
||||
if (user && ![[userKey keyIDForAlgorithm:user.algorithm] isEqualToData:user.keyID])
|
||||
return MPImportResultInvalidPassword;
|
||||
__block MPKey *importKey = userKey;
|
||||
if (importKeyID && ![importKey.keyID isEqualToData:importKeyID])
|
||||
importKey = [importAlgorithm keyForPassword:askImportPassword( importUserName ) ofUserNamed:importUserName];
|
||||
if (importKeyID && ![importKey.keyID isEqualToData:importKeyID])
|
||||
if (importKeyID && ![[importKey keyIDForAlgorithm:importAlgorithm] isEqualToData:importKeyID])
|
||||
importKey = [[MPKey alloc] initForFullName:importUserName withMasterPassword:askImportPassword( importUserName )];
|
||||
if (importKeyID && ![[importKey keyIDForAlgorithm:importAlgorithm] isEqualToData:importKeyID])
|
||||
return MPImportResultInvalidPassword;
|
||||
|
||||
// Delete existing sites.
|
||||
@@ -710,7 +711,8 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
|
||||
else {
|
||||
user = [MPUserEntity insertNewObjectInContext:context];
|
||||
user.name = importUserName;
|
||||
user.keyID = [userKey keyID];
|
||||
user.algorithm = MPAlgorithmDefault;
|
||||
user.keyID = [userKey keyIDForAlgorithm:user.algorithm];
|
||||
if (importAvatar != NSNotFound)
|
||||
user.avatar = importAvatar;
|
||||
dbg( @"Created User: %@", [user debugDescription] );
|
||||
@@ -728,19 +730,20 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
|
||||
NSString *exportContent = siteElements[7];
|
||||
|
||||
// Create new site.
|
||||
NSString *typeEntityName = [MPAlgorithmForVersion( version ) classNameOfType:type];
|
||||
if (!typeEntityName) {
|
||||
id<MPAlgorithm> algorithm = MPAlgorithmForVersion( version );
|
||||
Class entityType = [algorithm classOfType:type];
|
||||
if (!entityType) {
|
||||
err( @"Invalid site type in import file: %@ has type %lu", siteName, (long)type );
|
||||
return MPImportResultInternalError;
|
||||
}
|
||||
MPSiteEntity *site = [NSEntityDescription insertNewObjectForEntityForName:typeEntityName inManagedObjectContext:context];
|
||||
MPSiteEntity *site = (MPSiteEntity *)[entityType insertNewObjectInContext:context];
|
||||
site.name = siteName;
|
||||
site.loginName = loginName;
|
||||
site.user = user;
|
||||
site.type = type;
|
||||
site.uses = uses;
|
||||
site.lastUsed = lastUsed;
|
||||
site.version = version;
|
||||
site.algorithm = algorithm;
|
||||
if ([exportContent length]) {
|
||||
if (clearText)
|
||||
[site.algorithm importClearTextPassword:exportContent intoSite:site usingKey:userKey];
|
||||
@@ -768,7 +771,7 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
|
||||
- (NSString *)exportSitesRevealPasswords:(BOOL)revealPasswords {
|
||||
|
||||
MPUserEntity *activeUser = [self activeUserForMainThread];
|
||||
inf( @"Exporting sites, %@, for: %@", revealPasswords? @"revealing passwords": @"omitting passwords", activeUser.userID );
|
||||
inf( @"Exporting sites, %@, for user: %@", revealPasswords? @"revealing passwords": @"omitting passwords", activeUser.userID );
|
||||
|
||||
// Header.
|
||||
NSMutableString *export = [NSMutableString new];
|
||||
@@ -799,7 +802,7 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
|
||||
NSDate *lastUsed = site.lastUsed;
|
||||
NSUInteger uses = site.uses;
|
||||
MPSiteType type = site.type;
|
||||
NSUInteger version = site.version;
|
||||
id<MPAlgorithm> algorithm = site.algorithm;
|
||||
NSUInteger counter = 0;
|
||||
NSString *loginName = site.loginName;
|
||||
NSString *siteName = site.name;
|
||||
@@ -818,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)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;
|
||||
|
||||
@@ -31,9 +31,8 @@
|
||||
@property(readonly) NSString *typeClassName;
|
||||
@property(readonly) Class typeClass;
|
||||
@property(assign) NSUInteger uses;
|
||||
@property(assign) NSUInteger version;
|
||||
@property(assign) BOOL requiresExplicitMigration;
|
||||
@property(readonly) id<MPAlgorithm> algorithm;
|
||||
@property(strong) id<MPAlgorithm> algorithm;
|
||||
|
||||
- (NSUInteger)use;
|
||||
- (BOOL)tryMigrateExplicitly:(BOOL)explicit;
|
||||
@@ -56,6 +55,7 @@
|
||||
@property(assign) BOOL saveKey;
|
||||
@property(assign) MPSiteType defaultType;
|
||||
@property(readonly) NSString *userID;
|
||||
@property(strong) id<MPAlgorithm> algorithm;
|
||||
|
||||
+ (NSString *)idFor:(NSString *)userName;
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#import "MPEntities.h"
|
||||
#import "MPAppDelegate_Shared.h"
|
||||
#import "MPAppDelegate_Key.h"
|
||||
|
||||
@implementation NSManagedObjectContext(MP)
|
||||
|
||||
@@ -90,14 +91,14 @@
|
||||
self.uses_ = @(anUses);
|
||||
}
|
||||
|
||||
- (NSUInteger)version {
|
||||
- (id<MPAlgorithm>)algorithm {
|
||||
|
||||
return [self.version_ unsignedIntegerValue];
|
||||
return MPAlgorithmForVersion( MIN( MPAlgorithmVersionCurrent, MAX( MPAlgorithmVersion0, [self.version_ unsignedIntegerValue] ) ) );
|
||||
}
|
||||
|
||||
- (void)setVersion:(NSUInteger)version {
|
||||
- (void)setAlgorithm:(id<MPAlgorithm>)algorithm {
|
||||
|
||||
self.version_ = @(version);
|
||||
self.version_ = @([algorithm version]);
|
||||
}
|
||||
|
||||
- (BOOL)requiresExplicitMigration {
|
||||
@@ -110,11 +111,6 @@
|
||||
self.requiresExplicitMigration_ = @(requiresExplicitMigration);
|
||||
}
|
||||
|
||||
- (id<MPAlgorithm>)algorithm {
|
||||
|
||||
return MPAlgorithmForVersion( self.version );
|
||||
}
|
||||
|
||||
- (NSUInteger)use {
|
||||
|
||||
self.lastUsed = [NSDate date];
|
||||
@@ -128,21 +124,31 @@
|
||||
|
||||
- (NSString *)debugDescription {
|
||||
|
||||
@try {
|
||||
return strf( @"{%@: name=%@, user=%@, type=%lu, uses=%ld, lastUsed=%@, version=%ld, loginName=%@, requiresExplicitMigration=%d}",
|
||||
NSStringFromClass( [self class] ), self.name, self.user.name, (long)self.type, (long)self.uses, self.lastUsed,
|
||||
(long)self.version,
|
||||
self.loginName, self.requiresExplicitMigration );
|
||||
} @catch (NSException *exception) {
|
||||
return strf( @"{%@: inaccessible: %@}",
|
||||
NSStringFromClass( [self class] ), [exception fullDescription] );
|
||||
}
|
||||
__block NSString *debugDescription = strf( @"{%@: [recursing]}", [self class] );
|
||||
|
||||
static BOOL recursing = NO;
|
||||
PearlIfNotRecursing( &recursing, ^{
|
||||
@try {
|
||||
debugDescription = strf(
|
||||
@"{%@: name=%@, user=%@, type=%lu, uses=%ld, lastUsed=%@, version=%ld, loginName=%@, requiresExplicitMigration=%d}",
|
||||
NSStringFromClass( [self class] ), self.name, self.user.name, (long)self.type, (long)self.uses, self.lastUsed,
|
||||
(long)[self.algorithm version],
|
||||
self.loginName, self.requiresExplicitMigration );
|
||||
}
|
||||
@catch (NSException *exception) {
|
||||
debugDescription = strf( @"{%@: inaccessible: %@}",
|
||||
NSStringFromClass( [self class] ), [exception fullDescription] );
|
||||
}
|
||||
} );
|
||||
|
||||
return debugDescription;
|
||||
}
|
||||
|
||||
- (BOOL)tryMigrateExplicitly:(BOOL)explicit {
|
||||
|
||||
while (self.version < MPAlgorithmDefaultVersion) {
|
||||
NSUInteger toVersion = self.version + 1;
|
||||
MPAlgorithmVersion algorithmVersion;
|
||||
while ((algorithmVersion = [self.algorithm version]) < MPAlgorithmDefaultVersion) {
|
||||
NSUInteger toVersion = algorithmVersion + 1;
|
||||
if (![MPAlgorithmForVersion( toVersion ) tryMigrateSite:self explicit:explicit]) {
|
||||
wrn( @"%@ migration to version: %ld failed for site: %@",
|
||||
explicit? @"Explicit": @"Automatic", (long)toVersion, self );
|
||||
@@ -287,6 +293,17 @@
|
||||
self.defaultType_ = @(aDefaultType);
|
||||
}
|
||||
|
||||
- (id<MPAlgorithm>)algorithm {
|
||||
|
||||
return MPAlgorithmForVersion( MIN( MPAlgorithmVersionCurrent, MAX( MPAlgorithmVersion0, [self.version_ unsignedIntegerValue] ) ) );
|
||||
}
|
||||
|
||||
- (void)setAlgorithm:(id<MPAlgorithm>)version {
|
||||
|
||||
self.version_ = @([version version]);
|
||||
[[MPAppDelegate_Shared get] forgetSavedKeyFor:self];
|
||||
}
|
||||
|
||||
- (NSString *)userID {
|
||||
|
||||
return [MPUserEntity idFor:self.name];
|
||||
|
||||
@@ -16,17 +16,21 @@
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "MPAlgorithm.h"
|
||||
|
||||
@protocol MPAlgorithm;
|
||||
|
||||
@interface MPKey : NSObject
|
||||
|
||||
@property(nonatomic, readonly, strong) id<MPAlgorithm> algorithm;
|
||||
@property(nonatomic, readonly, strong) NSData *keyData;
|
||||
@property(nonatomic, readonly, strong) NSData *keyID;
|
||||
@property(nonatomic, readonly) NSString *fullName;
|
||||
|
||||
- (instancetype)initForFullName:(NSString *)fullName withMasterPassword:(NSString *)masterPassword;
|
||||
- (instancetype)initForFullName:(NSString *)fullName withKeyData:(NSData *)keyData forAlgorithm:(id<MPAlgorithm>)algorithm;
|
||||
|
||||
- (NSData *)keyIDForAlgorithm:(id<MPAlgorithm>)algorithm;
|
||||
- (NSData *)keyDataForAlgorithm:(id<MPAlgorithm>)algorithm;
|
||||
- (NSData *)keyDataForAlgorithm:(id<MPAlgorithm>)algorithm trimmedLength:(NSUInteger)subKeyLength;
|
||||
|
||||
- (id)initWithKeyData:(NSData *)keyData algorithm:(id<MPAlgorithm>)algorithm;
|
||||
- (MPKey *)subKeyOfLength:(NSUInteger)subKeyLength;
|
||||
- (BOOL)isEqualToKey:(MPKey *)key;
|
||||
|
||||
@end
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
/**
|
||||
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
|
||||
*
|
||||
* See the enclosed file LICENSE for license information (LGPLv3). If you did
|
||||
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
*
|
||||
* @author Maarten Billemont <lhunath@lyndir.com>
|
||||
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
*/
|
||||
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
|
||||
*
|
||||
* See the enclosed file LICENSE for license information (LGPLv3). If you did
|
||||
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
*
|
||||
* @author Maarten Billemont <lhunath@lyndir.com>
|
||||
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
*/
|
||||
|
||||
//
|
||||
// MPKey
|
||||
@@ -19,38 +19,61 @@
|
||||
|
||||
@interface MPKey()
|
||||
|
||||
@property(nonatomic, readwrite, strong) id<MPAlgorithm> algorithm;
|
||||
@property(nonatomic, readwrite, strong) NSData *keyData;
|
||||
@property(nonatomic, readwrite, strong) NSData *keyID;
|
||||
@property(nonatomic) NSString *fullName;
|
||||
@property(nonatomic) NSString *masterPassword;
|
||||
|
||||
@end
|
||||
|
||||
@implementation MPKey
|
||||
@implementation MPKey {
|
||||
NSCache *_keyCache;
|
||||
};
|
||||
|
||||
@synthesize algorithm = _algorithm, keyData = _keyData, keyID = _keyID;
|
||||
|
||||
- (id)initWithKeyData:(NSData *)keyData algorithm:(id<MPAlgorithm>)algorithm {
|
||||
- (instancetype)initForFullName:(NSString *)fullName withMasterPassword:(NSString *)masterPassword {
|
||||
|
||||
if (!(self = [super init]))
|
||||
return nil;
|
||||
|
||||
self.keyData = keyData;
|
||||
self.algorithm = algorithm;
|
||||
self.keyID = [self.algorithm keyIDForKeyData:keyData];
|
||||
_keyCache = [NSCache new];
|
||||
self.fullName = fullName;
|
||||
self.masterPassword = masterPassword;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (MPKey *)subKeyOfLength:(NSUInteger)subKeyLength {
|
||||
- (instancetype)initForFullName:(NSString *)fullName withKeyData:(NSData *)keyData forAlgorithm:(id<MPAlgorithm>)algorithm {
|
||||
|
||||
NSData *subKeyData = [self.keyData subdataWithRange:NSMakeRange( 0, MIN(subKeyLength, self.keyData.length) )];
|
||||
if (!(self = [self initForFullName:fullName withMasterPassword:nil]))
|
||||
return nil;
|
||||
|
||||
return [self.algorithm keyFromKeyData:subKeyData];
|
||||
[_keyCache setObject:keyData forKey:algorithm];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSData *)keyIDForAlgorithm:(id<MPAlgorithm>)algorithm {
|
||||
|
||||
return [algorithm keyIDForKeyData:[self keyDataForAlgorithm:algorithm]];
|
||||
}
|
||||
|
||||
- (NSData *)keyDataForAlgorithm:(id<MPAlgorithm>)algorithm {
|
||||
|
||||
NSData *keyData = [_keyCache objectForKey:algorithm];
|
||||
if (!keyData)
|
||||
[_keyCache setObject:keyData = [algorithm keyDataForFullName:self.fullName withMasterPassword:self.masterPassword]
|
||||
forKey:algorithm];
|
||||
|
||||
return keyData;
|
||||
}
|
||||
|
||||
- (NSData *)keyDataForAlgorithm:(id<MPAlgorithm>)algorithm trimmedLength:(NSUInteger)subKeyLength {
|
||||
|
||||
NSData *keyData = [self keyDataForAlgorithm:algorithm];
|
||||
return [keyData subdataWithRange:NSMakeRange( 0, MIN( subKeyLength, keyData.length ) )];
|
||||
}
|
||||
|
||||
- (BOOL)isEqualToKey:(MPKey *)key {
|
||||
|
||||
return [self.keyID isEqualToData:key.keyID];
|
||||
return [self.fullName isEqualToString:key.fullName] && [self.masterPassword isEqualToString:self.masterPassword];
|
||||
}
|
||||
|
||||
- (BOOL)isEqual:(id)object {
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
@property (nonatomic, retain) NSDate * lastUsed;
|
||||
@property (nonatomic, retain) NSString * name;
|
||||
@property (nonatomic, retain) NSNumber * saveKey_;
|
||||
@property (nonatomic, retain) NSNumber * version_;
|
||||
@property (nonatomic, retain) NSSet *sites;
|
||||
@end
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
@dynamic lastUsed;
|
||||
@dynamic name;
|
||||
@dynamic saveKey_;
|
||||
@dynamic version_;
|
||||
@dynamic sites;
|
||||
|
||||
@end
|
||||
|
||||
@@ -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;
|
||||
} );
|
||||
}];
|
||||
}
|
||||
|
||||
@@ -605,6 +624,8 @@
|
||||
CGDirectDisplayID displayID = [self.window.screen.deviceDescription[@"NSScreenNumber"] unsignedIntValue];
|
||||
CGImageRef capturedImage = CGDisplayCreateImage( displayID );
|
||||
if (!capturedImage || CGImageGetWidth( capturedImage ) <= 1) {
|
||||
if (capturedImage)
|
||||
CFRelease( capturedImage );
|
||||
wrn( @"Failed to capture screen image for display: %d", displayID );
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -31,7 +31,6 @@
|
||||
DA16B345170661F2000A0EAB /* libPearl.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DAC77CAD148291A600BCF976 /* libPearl.a */; };
|
||||
DA2508F119511D3600AC23F1 /* MPPasswordWindowController.xib in Resources */ = {isa = PBXBuildFile; fileRef = DA2508F019511D3600AC23F1 /* MPPasswordWindowController.xib */; };
|
||||
DA250925195148E200AC23F1 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DAEBC45214F6364500987BF6 /* QuartzCore.framework */; };
|
||||
DA29992C19C6A89900AF7DF1 /* MasterPassword.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = DA29992619C6A89900AF7DF1 /* MasterPassword.xcdatamodeld */; };
|
||||
DA2CA4ED18D323D3007798F8 /* NSError+PearlFullDescription.m in Sources */ = {isa = PBXBuildFile; fileRef = DA2CA4E718D323D3007798F8 /* NSError+PearlFullDescription.m */; };
|
||||
DA2CA4EE18D323D3007798F8 /* NSError+PearlFullDescription.h in Headers */ = {isa = PBXBuildFile; fileRef = DA2CA4E818D323D3007798F8 /* NSError+PearlFullDescription.h */; };
|
||||
DA2CA4EF18D323D3007798F8 /* NSArray+Pearl.m in Sources */ = {isa = PBXBuildFile; fileRef = DA2CA4E918D323D3007798F8 /* NSArray+Pearl.m */; };
|
||||
@@ -92,6 +91,7 @@
|
||||
DA6774451A474A3B004F356A /* mpw-types.c in Sources */ = {isa = PBXBuildFile; fileRef = DA6773C21A4746AF004F356A /* mpw-types.c */; };
|
||||
DA6774461A474A3B004F356A /* mpw-util.c in Sources */ = {isa = PBXBuildFile; fileRef = DA6773C51A4746AF004F356A /* mpw-util.c */; };
|
||||
DA67744A1A47C8F7004F356A /* mpw-tests-util.c in Sources */ = {isa = PBXBuildFile; fileRef = DA6774481A47C8F7004F356A /* mpw-tests-util.c */; };
|
||||
DA8495301A915EF400B3053D /* MasterPassword.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = DA8495281A915EF400B3053D /* MasterPassword.xcdatamodeld */; };
|
||||
DA89D4EC1A51EABD00AC64D7 /* Pearl-Cocoa.h in Headers */ = {isa = PBXBuildFile; fileRef = DA89D4EA1A51EABD00AC64D7 /* Pearl-Cocoa.h */; };
|
||||
DA89D4ED1A51EABD00AC64D7 /* Pearl-Cocoa.m in Sources */ = {isa = PBXBuildFile; fileRef = DA89D4EB1A51EABD00AC64D7 /* Pearl-Cocoa.m */; };
|
||||
DA8ED895192906920099B726 /* PearlTween.m in Sources */ = {isa = PBXBuildFile; fileRef = DA8ED891192906920099B726 /* PearlTween.m */; };
|
||||
@@ -294,11 +294,6 @@
|
||||
DA25090719513C1400AC23F1 /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; };
|
||||
DA2509261951B86C00AC23F1 /* small-screen.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "small-screen.png"; sourceTree = "<group>"; };
|
||||
DA2509271951B86C00AC23F1 /* screen.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = screen.png; sourceTree = "<group>"; };
|
||||
DA29992719C6A89900AF7DF1 /* MasterPassword 1.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MasterPassword 1.xcdatamodel"; sourceTree = "<group>"; };
|
||||
DA29992819C6A89900AF7DF1 /* MasterPassword 2.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MasterPassword 2.xcdatamodel"; sourceTree = "<group>"; };
|
||||
DA29992919C6A89900AF7DF1 /* MasterPassword 3.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MasterPassword 3.xcdatamodel"; sourceTree = "<group>"; };
|
||||
DA29992A19C6A89900AF7DF1 /* MasterPassword 4.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MasterPassword 4.xcdatamodel"; sourceTree = "<group>"; };
|
||||
DA29992B19C6A89900AF7DF1 /* MasterPassword 5.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MasterPassword 5.xcdatamodel"; sourceTree = "<group>"; };
|
||||
DA2CA4E718D323D3007798F8 /* NSError+PearlFullDescription.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSError+PearlFullDescription.m"; sourceTree = "<group>"; };
|
||||
DA2CA4E818D323D3007798F8 /* NSError+PearlFullDescription.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSError+PearlFullDescription.h"; sourceTree = "<group>"; };
|
||||
DA2CA4E918D323D3007798F8 /* NSArray+Pearl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSArray+Pearl.m"; sourceTree = "<group>"; };
|
||||
@@ -321,7 +316,6 @@
|
||||
DA32CFE119CF1C71004F3F0E /* MPSiteQuestionEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPSiteQuestionEntity.m; sourceTree = "<group>"; };
|
||||
DA32CFE319CF1C71004F3F0E /* MPUserEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPUserEntity.h; sourceTree = "<group>"; };
|
||||
DA32CFE419CF1C71004F3F0E /* MPUserEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPUserEntity.m; sourceTree = "<group>"; };
|
||||
DA32D00019CF470E004F3F0E /* MasterPassword 6.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MasterPassword 6.xcdatamodel"; sourceTree = "<group>"; };
|
||||
DA3509FC15F101A500C14A8E /* PearlQueue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PearlQueue.h; sourceTree = "<group>"; };
|
||||
DA3509FD15F101A500C14A8E /* PearlQueue.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PearlQueue.m; sourceTree = "<group>"; };
|
||||
DA3B844A190FC5A900246EEA /* Crashlytics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = Crashlytics.framework; sourceTree = "<group>"; };
|
||||
@@ -796,6 +790,13 @@
|
||||
DA831A281A6E1146000AC234 /* mpw-algorithm_v1.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = "mpw-algorithm_v1.c"; sourceTree = "<group>"; };
|
||||
DA831A291A6E1146000AC234 /* mpw-algorithm_v2.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = "mpw-algorithm_v2.c"; sourceTree = "<group>"; };
|
||||
DA831A2A1A6E1146000AC234 /* mpw-algorithm_v3.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = "mpw-algorithm_v3.c"; sourceTree = "<group>"; };
|
||||
DA8495291A915EF400B3053D /* MasterPassword 1.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MasterPassword 1.xcdatamodel"; sourceTree = "<group>"; };
|
||||
DA84952A1A915EF400B3053D /* MasterPassword 2.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MasterPassword 2.xcdatamodel"; sourceTree = "<group>"; };
|
||||
DA84952B1A915EF400B3053D /* MasterPassword 3.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MasterPassword 3.xcdatamodel"; sourceTree = "<group>"; };
|
||||
DA84952C1A915EF400B3053D /* MasterPassword 4.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MasterPassword 4.xcdatamodel"; sourceTree = "<group>"; };
|
||||
DA84952D1A915EF400B3053D /* MasterPassword 5.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MasterPassword 5.xcdatamodel"; sourceTree = "<group>"; };
|
||||
DA84952E1A915EF400B3053D /* MasterPassword 6.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MasterPassword 6.xcdatamodel"; sourceTree = "<group>"; };
|
||||
DA84952F1A915EF400B3053D /* MasterPassword 7.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MasterPassword 7.xcdatamodel"; sourceTree = "<group>"; };
|
||||
DA89D4EA1A51EABD00AC64D7 /* Pearl-Cocoa.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "Pearl-Cocoa.h"; sourceTree = "<group>"; };
|
||||
DA89D4EB1A51EABD00AC64D7 /* Pearl-Cocoa.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "Pearl-Cocoa.m"; sourceTree = "<group>"; };
|
||||
DA8ED891192906920099B726 /* PearlTween.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PearlTween.m; sourceTree = "<group>"; };
|
||||
@@ -1060,8 +1061,8 @@
|
||||
DA5E5C961724A667003798D8 /* ObjC */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DA8495281A915EF400B3053D /* MasterPassword.xcdatamodeld */,
|
||||
DA5E5CB21724A667003798D8 /* Mac */,
|
||||
DA29992619C6A89900AF7DF1 /* MasterPassword.xcdatamodeld */,
|
||||
DA5E5C971724A667003798D8 /* MPAlgorithm.h */,
|
||||
DA5E5C981724A667003798D8 /* MPAlgorithm.m */,
|
||||
DA5E5C991724A667003798D8 /* MPAlgorithmV0.h */,
|
||||
@@ -2245,7 +2246,6 @@
|
||||
DA5180CE19FF307E00A587E9 /* MPAppDelegate_Store.m in Sources */,
|
||||
DA5E5CFA1724A667003798D8 /* MPAppDelegate_Shared.m in Sources */,
|
||||
DA5E5CFC1724A667003798D8 /* MPConfig.m in Sources */,
|
||||
DA29992C19C6A89900AF7DF1 /* MasterPassword.xcdatamodeld in Sources */,
|
||||
DA3B8456190FC89700246EEA /* MPFixable.m in Sources */,
|
||||
DA5E5D001724A667003798D8 /* MPEntities.m in Sources */,
|
||||
DA5E5D011724A667003798D8 /* MPKey.m in Sources */,
|
||||
@@ -2262,6 +2262,7 @@
|
||||
93D39784E725A34D1EE3FB3B /* MPInitialWindowController.m in Sources */,
|
||||
DA32CFDF19CF1C70004F3F0E /* MPSiteEntity.m in Sources */,
|
||||
93D394C4254EEB45FB335AFB /* MPSitesTableView.m in Sources */,
|
||||
DA8495301A915EF400B3053D /* MasterPassword.xcdatamodeld in Sources */,
|
||||
DA6774291A4746AF004F356A /* mpw-algorithm.c in Sources */,
|
||||
93D395E4830290EBB6E71F34 /* MPNoStateButton.m in Sources */,
|
||||
DA4DAE941A7D8117003E5423 /* MPAlgorithmV3.m in Sources */,
|
||||
@@ -2943,17 +2944,18 @@
|
||||
/* End XCConfigurationList section */
|
||||
|
||||
/* Begin XCVersionGroup section */
|
||||
DA29992619C6A89900AF7DF1 /* MasterPassword.xcdatamodeld */ = {
|
||||
DA8495281A915EF400B3053D /* MasterPassword.xcdatamodeld */ = {
|
||||
isa = XCVersionGroup;
|
||||
children = (
|
||||
DA29992719C6A89900AF7DF1 /* MasterPassword 1.xcdatamodel */,
|
||||
DA29992819C6A89900AF7DF1 /* MasterPassword 2.xcdatamodel */,
|
||||
DA29992919C6A89900AF7DF1 /* MasterPassword 3.xcdatamodel */,
|
||||
DA29992A19C6A89900AF7DF1 /* MasterPassword 4.xcdatamodel */,
|
||||
DA29992B19C6A89900AF7DF1 /* MasterPassword 5.xcdatamodel */,
|
||||
DA32D00019CF470E004F3F0E /* MasterPassword 6.xcdatamodel */,
|
||||
DA8495291A915EF400B3053D /* MasterPassword 1.xcdatamodel */,
|
||||
DA84952A1A915EF400B3053D /* MasterPassword 2.xcdatamodel */,
|
||||
DA84952B1A915EF400B3053D /* MasterPassword 3.xcdatamodel */,
|
||||
DA84952C1A915EF400B3053D /* MasterPassword 4.xcdatamodel */,
|
||||
DA84952D1A915EF400B3053D /* MasterPassword 5.xcdatamodel */,
|
||||
DA84952E1A915EF400B3053D /* MasterPassword 6.xcdatamodel */,
|
||||
DA84952F1A915EF400B3053D /* MasterPassword 7.xcdatamodel */,
|
||||
);
|
||||
currentVersion = DA32D00019CF470E004F3F0E /* MasterPassword 6.xcdatamodel */;
|
||||
currentVersion = DA84952F1A915EF400B3053D /* MasterPassword 7.xcdatamodel */;
|
||||
path = MasterPassword.xcdatamodeld;
|
||||
sourceTree = "<group>";
|
||||
versionGroupType = wrapper.xcdatamodel;
|
||||
|
||||
@@ -3,6 +3,6 @@
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>_XCCurrentVersionName</key>
|
||||
<string>MasterPassword 6.xcdatamodel</string>
|
||||
<string>MasterPassword 7.xcdatamodel</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<model userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="6244" systemVersion="13E28" minimumToolsVersion="Automatic" macOSVersion="Automatic" iOSVersion="Automatic">
|
||||
<model userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="6254" systemVersion="14C109" minimumToolsVersion="Automatic" macOSVersion="Automatic" iOSVersion="Automatic">
|
||||
<entity name="MPGeneratedSiteEntity" representedClassName="MPGeneratedSiteEntity" parentEntity="MPSiteEntity" elementID="789304EA-070D-4982-8C20-54EECFC20CB6" syncable="YES">
|
||||
<attribute name="counter_" optional="YES" attributeType="Integer 32" defaultValueString="1" syncable="YES"/>
|
||||
</entity>
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<model userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="6254" systemVersion="14C109" minimumToolsVersion="Automatic" macOSVersion="Automatic" iOSVersion="Automatic">
|
||||
<entity name="MPGeneratedSiteEntity" representedClassName="MPGeneratedSiteEntity" parentEntity="MPSiteEntity" elementID="789304EA-070D-4982-8C20-54EECFC20CB6" syncable="YES">
|
||||
<attribute name="counter_" optional="YES" attributeType="Integer 32" defaultValueString="1" syncable="YES"/>
|
||||
</entity>
|
||||
<entity name="MPSiteEntity" representedClassName="MPSiteEntity" isAbstract="YES" elementID="58EE245C-6827-4C11-BB7E-5722F2426EC2" syncable="YES">
|
||||
<attribute name="content" optional="YES" transient="YES" attributeType="Transformable" syncable="YES"/>
|
||||
<attribute name="lastUsed" attributeType="Date" indexed="YES" syncable="YES"/>
|
||||
<attribute name="loginGenerated_" attributeType="Boolean" defaultValueString="NO" syncable="YES"/>
|
||||
<attribute name="loginName" optional="YES" attributeType="String" elementID="A1B9F981-D33C-4BFE-9F94-C9D3E1F78E51" syncable="YES"/>
|
||||
<attribute name="name" attributeType="String" minValueString="1" indexed="YES" syncable="YES"/>
|
||||
<attribute name="requiresExplicitMigration_" attributeType="Boolean" defaultValueString="NO">
|
||||
<userInfo/>
|
||||
</attribute>
|
||||
<attribute name="type_" attributeType="Integer 16" defaultValueString="17" syncable="YES"/>
|
||||
<attribute name="uses_" attributeType="Integer 16" defaultValueString="0" indexed="YES" syncable="YES"/>
|
||||
<attribute name="version_" attributeType="Integer 16" minValueString="0" defaultValueString="0" syncable="YES"/>
|
||||
<relationship name="questions" optional="YES" toMany="YES" deletionRule="Cascade" ordered="YES" destinationEntity="MPSiteQuestionEntity" inverseName="site" inverseEntity="MPSiteQuestionEntity" syncable="YES"/>
|
||||
<relationship name="user" minCount="1" maxCount="1" deletionRule="Nullify" destinationEntity="MPUserEntity" inverseName="sites" inverseEntity="MPUserEntity" elementID="FC8AE32E-7BE3-4FA6-8611-B7DC0DB063EF" syncable="YES"/>
|
||||
</entity>
|
||||
<entity name="MPSiteQuestionEntity" representedClassName="MPSiteQuestionEntity" syncable="YES">
|
||||
<attribute name="keyword" attributeType="String" syncable="YES"/>
|
||||
<relationship name="site" maxCount="1" deletionRule="Nullify" destinationEntity="MPSiteEntity" inverseName="questions" inverseEntity="MPSiteEntity" syncable="YES"/>
|
||||
</entity>
|
||||
<entity name="MPStoredSiteEntity" representedClassName="MPStoredSiteEntity" parentEntity="MPSiteEntity" elementID="BEEF1688-0CCD-4699-A86A-4D860FE2CEB8" syncable="YES">
|
||||
<attribute name="contentObject" optional="YES" attributeType="Transformable" storedInTruthFile="YES" syncable="YES"/>
|
||||
</entity>
|
||||
<entity name="MPUserEntity" representedClassName="MPUserEntity" syncable="YES">
|
||||
<attribute name="avatar_" attributeType="Integer 16" defaultValueString="0" syncable="YES"/>
|
||||
<attribute name="defaultType_" attributeType="Integer 16" defaultValueString="17" syncable="YES"/>
|
||||
<attribute name="keyID" optional="YES" attributeType="Binary" syncable="YES"/>
|
||||
<attribute name="lastUsed" optional="YES" attributeType="Date" syncable="YES"/>
|
||||
<attribute name="name" attributeType="String" syncable="YES"/>
|
||||
<attribute name="saveKey_" attributeType="Boolean" defaultValueString="NO">
|
||||
<userInfo/>
|
||||
</attribute>
|
||||
<attribute name="version_" attributeType="Integer 16" minValueString="0" defaultValueString="2" syncable="YES"/>
|
||||
<relationship name="sites" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="MPSiteEntity" inverseName="user" inverseEntity="MPSiteEntity" elementID="D18D6772-040E-4CFE-8F32-A34B08E9E9BC" syncable="YES"/>
|
||||
</entity>
|
||||
<elements>
|
||||
<element name="MPGeneratedSiteEntity" positionX="216" positionY="-288" width="128" height="58"/>
|
||||
<element name="MPSiteEntity" positionX="-0" positionY="-286" width="128" height="208"/>
|
||||
<element name="MPSiteQuestionEntity" positionX="-2" positionY="-9" width="128" height="73"/>
|
||||
<element name="MPStoredSiteEntity" positionX="214" positionY="-171" width="128" height="58"/>
|
||||
<element name="MPUserEntity" positionX="-218" positionY="-288" width="128" height="165"/>
|
||||
</elements>
|
||||
</model>
|
||||
@@ -22,7 +22,7 @@
|
||||
@property(weak, nonatomic) IBOutlet UIScrollView *scrollView;
|
||||
@property(weak, nonatomic) IBOutlet UIView *dialogView;
|
||||
@property(weak, nonatomic) IBOutlet UIView *containerView;
|
||||
@property(weak, nonatomic) IBOutlet UITextField *userNameField;
|
||||
@property(weak, nonatomic) IBOutlet UITextField *fullNameField;
|
||||
@property(weak, nonatomic) IBOutlet UITextField *masterPasswordField;
|
||||
@property(weak, nonatomic) IBOutlet UITextField *siteField;
|
||||
@property(weak, nonatomic) IBOutlet UIStepper *counterStepper;
|
||||
|
||||
@@ -75,7 +75,7 @@
|
||||
|
||||
- (IBAction)controlChanged:(UIControl *)control {
|
||||
|
||||
if (control == self.userNameField || control == self.masterPasswordField)
|
||||
if (control == self.fullNameField || control == self.masterPasswordField)
|
||||
[self updateKey];
|
||||
else
|
||||
[self updatePassword];
|
||||
@@ -103,15 +103,15 @@
|
||||
|
||||
- (void)updateKey {
|
||||
|
||||
NSString *userName = self.userNameField.text;
|
||||
NSString *fullName = self.fullNameField.text;
|
||||
NSString *masterPassword = self.masterPasswordField.text;
|
||||
|
||||
self.passwordLabel.text = nil;
|
||||
[self.activity startAnimating];
|
||||
[_emergencyKeyQueue cancelAllOperations];
|
||||
[_emergencyKeyQueue addOperationWithBlock:^{
|
||||
if ([masterPassword length] && [userName length])
|
||||
_key = [MPAlgorithmDefault keyForPassword:masterPassword ofUserNamed:userName];
|
||||
if ([masterPassword length] && [fullName length])
|
||||
_key = [[MPKey alloc] initForFullName:fullName withMasterPassword:masterPassword];
|
||||
else
|
||||
_key = nil;
|
||||
|
||||
@@ -165,7 +165,7 @@
|
||||
|
||||
- (void)reset {
|
||||
|
||||
self.userNameField.text = nil;
|
||||
self.fullNameField.text = nil;
|
||||
self.masterPasswordField.text = nil;
|
||||
self.siteField.text = nil;
|
||||
self.counterStepper.value = 1;
|
||||
|
||||
25
MasterPassword/ObjC/iOS/MPMessageViewController.h
Normal file
@@ -0,0 +1,25 @@
|
||||
//
|
||||
// MPPreferencesViewController.h
|
||||
// MasterPassword-iOS
|
||||
//
|
||||
// Created by Maarten Billemont on 04/06/12.
|
||||
// Copyright (c) 2012 Lyndir. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface MPMessage : NSObject
|
||||
|
||||
@property(nonatomic) NSString *title;
|
||||
@property(nonatomic) NSString *text;
|
||||
@property(nonatomic) BOOL info;
|
||||
|
||||
+ (instancetype)messageWithTitle:(NSString *)title text:(NSString *)text info:(BOOL)info;
|
||||
|
||||
@end
|
||||
|
||||
@interface MPMessageViewController : UIViewController
|
||||
|
||||
@property (nonatomic) MPMessage *message;
|
||||
|
||||
@end
|
||||
78
MasterPassword/ObjC/iOS/MPMessageViewController.m
Normal file
@@ -0,0 +1,78 @@
|
||||
//
|
||||
// MPPreferencesViewController.m
|
||||
// MasterPassword-iOS
|
||||
//
|
||||
// Created by Maarten Billemont on 04/06/12.
|
||||
// Copyright (c) 2012 Lyndir. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPMessageViewController.h"
|
||||
#import "MPiOSAppDelegate.h"
|
||||
#import "MPAppDelegate_Store.h"
|
||||
#import "MPOverlayViewController.h"
|
||||
|
||||
@interface MPMessageViewController()
|
||||
|
||||
@property(nonatomic) IBOutlet UILabel *titleLabel;
|
||||
@property(nonatomic) IBOutlet UILabel *messageLabel;
|
||||
@property(nonatomic) IBOutlet UIView *infoView;
|
||||
|
||||
@end
|
||||
|
||||
@implementation MPMessage
|
||||
|
||||
+ (instancetype)messageWithTitle:(NSString *)title text:(NSString *)text info:(BOOL)info {
|
||||
|
||||
MPMessage *message = [MPMessage new];
|
||||
message.title = title;
|
||||
message.text = text;
|
||||
message.info = info;
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation MPMessageViewController
|
||||
|
||||
#pragma mark - Life
|
||||
|
||||
- (void)viewDidLoad {
|
||||
|
||||
[super viewDidLoad];
|
||||
|
||||
self.view.backgroundColor = [UIColor clearColor];
|
||||
}
|
||||
|
||||
- (void)viewWillAppear:(BOOL)animated {
|
||||
|
||||
[super viewWillAppear:animated];
|
||||
|
||||
self.titleLabel.text = self.message.title;
|
||||
self.messageLabel.text = self.message.text;
|
||||
self.infoView.gone = !self.message.info;
|
||||
|
||||
PearlAddNotificationObserver( MPSignedOutNotification, nil, [NSOperationQueue mainQueue],
|
||||
^(MPMessageViewController *self, NSNotification *note) {
|
||||
if (![note.userInfo[@"animated"] boolValue])
|
||||
[UIView setAnimationsEnabled:NO];
|
||||
[[MPOverlaySegue dismissViewController:self] perform];
|
||||
[UIView setAnimationsEnabled:YES];
|
||||
} );
|
||||
}
|
||||
|
||||
- (void)viewWillDisappear:(BOOL)animated {
|
||||
|
||||
[super viewWillDisappear:animated];
|
||||
|
||||
PearlRemoveNotificationObservers();
|
||||
}
|
||||
|
||||
- (UIStatusBarStyle)preferredStatusBarStyle {
|
||||
|
||||
return UIStatusBarStyleLightContent;
|
||||
}
|
||||
|
||||
#pragma mark - State
|
||||
|
||||
@end
|
||||
@@ -59,6 +59,8 @@
|
||||
[[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector( doRevealPassword: )]];
|
||||
[self.counterButton addGestureRecognizer:
|
||||
[[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector( doResetCounter: )]];
|
||||
[self.upgradeButton addGestureRecognizer:
|
||||
[[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector( doDowngrade: )]];
|
||||
|
||||
[self setupLayer];
|
||||
|
||||
@@ -331,13 +333,36 @@
|
||||
- (IBAction)doUpgrade:(UIButton *)sender {
|
||||
|
||||
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
||||
if (![[self siteInContext:context] tryMigrateExplicitly:YES]) {
|
||||
MPSiteEntity *siteEntity = [self siteInContext:context];
|
||||
if (![siteEntity tryMigrateExplicitly:YES]) {
|
||||
[PearlOverlay showTemporaryOverlayWithTitle:@"Couldn't Upgrade Site" dismissAfter:2];
|
||||
return;
|
||||
}
|
||||
|
||||
[context saveToStore];
|
||||
[PearlOverlay showTemporaryOverlayWithTitle:@"Site Upgraded" dismissAfter:2];
|
||||
[PearlOverlay showTemporaryOverlayWithTitle:strf( @"Site Upgraded to V%d", siteEntity.algorithm.version )
|
||||
dismissAfter:2];
|
||||
[self updateAnimated:YES];
|
||||
}];
|
||||
}
|
||||
|
||||
- (IBAction)doDowngrade:(UILongPressGestureRecognizer *)recognizer {
|
||||
|
||||
if (recognizer.state != UIGestureRecognizerStateBegan)
|
||||
return;
|
||||
|
||||
if (![[MPiOSConfig get].allowDowngrade boolValue])
|
||||
return;
|
||||
|
||||
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
||||
MPSiteEntity *siteEntity = [self siteInContext:context];
|
||||
if (siteEntity.algorithm.version <= 0)
|
||||
return;
|
||||
|
||||
siteEntity.algorithm = MPAlgorithmForVersion( siteEntity.algorithm.version - 1 );
|
||||
[context saveToStore];
|
||||
[PearlOverlay showTemporaryOverlayWithTitle:strf( @"Site Downgraded to V%d", siteEntity.algorithm.version )
|
||||
dismissAfter:2];
|
||||
[self updateAnimated:YES];
|
||||
}];
|
||||
}
|
||||
@@ -479,7 +504,7 @@
|
||||
MPSiteEntity *mainSite = [self siteInContext:[MPiOSAppDelegate managedObjectContextForMainThreadIfReady]];
|
||||
|
||||
// UI
|
||||
self.upgradeButton.gone = !mainSite.requiresExplicitMigration;
|
||||
self.upgradeButton.gone = !mainSite.requiresExplicitMigration && ![[MPiOSConfig get].allowDowngrade boolValue];
|
||||
self.answersButton.gone = ![[MPiOSAppDelegate get] isFeatureUnlocked:MPProductGenerateAnswers];
|
||||
BOOL settingsMode = self.mode == MPPasswordCellModeSettings;
|
||||
self.loginNameContainer.alpha = settingsMode || mainSite.loginGenerated || [mainSite.loginName length]? 0.7f: 0;
|
||||
@@ -533,15 +558,15 @@
|
||||
[algorithm timeToCrack:&timeToCrack passwordString:password byAttacker:attackHardware])
|
||||
timeToCrackString = NSStringFromTimeToCrack( timeToCrack );
|
||||
|
||||
BOOL requiresExplicitMigration = site.requiresExplicitMigration;
|
||||
|
||||
PearlMainQueue( ^{
|
||||
self.loginNameField.text = loginName;
|
||||
self.passwordField.text = password;
|
||||
self.strengthLabel.text = timeToCrackString;
|
||||
self.loginNameButton.titleLabel.alpha = [loginName length] || self.loginNameField.enabled? 0: 1;
|
||||
|
||||
if ([password length])
|
||||
self.indicatorView.alpha = 0;
|
||||
else {
|
||||
if (![password length]) {
|
||||
self.indicatorView.alpha = 1;
|
||||
[self.indicatorView removeFromSuperview];
|
||||
[self.modeScrollView addSubview:self.indicatorView];
|
||||
@@ -551,6 +576,18 @@
|
||||
@"target" : settingsMode? self.editButton: self.modeButton
|
||||
}];
|
||||
}
|
||||
else if (requiresExplicitMigration) {
|
||||
self.indicatorView.alpha = 1;
|
||||
[self.indicatorView removeFromSuperview];
|
||||
[self.modeScrollView addSubview:self.indicatorView];
|
||||
[self.contentView addConstraintsWithVisualFormat:@"V:[indicator][target]" options:NSLayoutFormatAlignAllCenterX
|
||||
metrics:nil views:@{
|
||||
@"indicator" : self.indicatorView,
|
||||
@"target" : settingsMode? self.upgradeButton: self.modeButton
|
||||
}];
|
||||
}
|
||||
else
|
||||
self.indicatorView.alpha = 0;
|
||||
} );
|
||||
}];
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#import "MPAppDelegate_Key.h"
|
||||
#import "MPPasswordCell.h"
|
||||
#import "MPAnswersViewController.h"
|
||||
#import "MPMessageViewController.h"
|
||||
|
||||
typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
|
||||
MPPasswordsBadNameTip = 1 << 0,
|
||||
@@ -90,7 +91,13 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
|
||||
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
||||
MPUserEntity *activeUser = [[MPiOSAppDelegate get] activeUserInContext:context];
|
||||
if (![MPAlgorithmDefault tryMigrateUser:activeUser inContext:context])
|
||||
[PearlOverlay showTemporaryOverlayWithTitle:@"Some Sites Need Upgrade" dismissAfter:2];
|
||||
[self performSegueWithIdentifier:@"message" sender:
|
||||
[MPMessage messageWithTitle:@"You have sites that can be upgraded." text:
|
||||
@"Upgrading a site allows it to take advantage of the latest improvements in the Master Password algorithm.\n\n"
|
||||
"When you upgrade a site, a new and stronger password will be generated for it. To upgrade a site, first log into the site, navigate to your account preferences where you can change the site's password. Make sure you fill in any \"current password\" fields on the website first, then press the upgrade button here to get your new site password.\n\n"
|
||||
"You can then update your site's account with the new and stronger password.\n\n"
|
||||
"The upgrade button can be found in the site's settings and looks like this:"
|
||||
info:YES]];
|
||||
[context saveToStore];
|
||||
}];
|
||||
}
|
||||
@@ -109,6 +116,8 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
|
||||
if ([segue.identifier isEqualToString:@"answers"])
|
||||
((MPAnswersViewController *)segue.destinationViewController).site =
|
||||
[[MPPasswordCell findAsSuperviewOf:sender] siteInContext:[MPiOSAppDelegate managedObjectContextForMainThreadIfReady]];
|
||||
if ([segue.identifier isEqualToString:@"message"])
|
||||
((MPMessageViewController *)segue.destinationViewController).message = sender;
|
||||
}
|
||||
|
||||
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
|
||||
@@ -383,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) {
|
||||
|
||||
@@ -214,6 +214,7 @@ typedef NS_ENUM( NSUInteger, MPActiveUserState ) {
|
||||
MPUserEntity *user = [self userForAvatar:avatarCell inContext:context isNew:&isNew];
|
||||
if (isNew) {
|
||||
user = [MPUserEntity insertNewObjectInContext:context];
|
||||
user.algorithm = MPAlgorithmDefault;
|
||||
user.avatar = avatarCell.avatar;
|
||||
user.name = avatarCell.name;
|
||||
}
|
||||
|
||||
@@ -73,9 +73,9 @@
|
||||
PearlAddNotificationObserver( MPCheckConfigNotification, nil, [NSOperationQueue mainQueue], ^(id self, NSNotification *note) {
|
||||
[self updateConfigKey:note.object];
|
||||
} );
|
||||
PearlAddNotificationObserver( kIASKAppSettingChanged, nil, nil, ^(id self, NSNotification *note) {
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:MPCheckConfigNotification object:note.object];
|
||||
} );
|
||||
// PearlAddNotificationObserver( kIASKAppSettingChanged, nil, nil, ^(id self, NSNotification *note) {
|
||||
// [[NSNotificationCenter defaultCenter] postNotificationName:MPCheckConfigNotification object:note.object];
|
||||
// } );
|
||||
PearlAddNotificationObserver( NSUserDefaultsDidChangeNotification, nil, nil, ^(id self, NSNotification *note) {
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:MPCheckConfigNotification object:nil];
|
||||
} );
|
||||
@@ -496,7 +496,7 @@
|
||||
return;
|
||||
|
||||
[moc performBlockAndWait:^{
|
||||
inf( @"Unsetting master password for: %@.", user.userID );
|
||||
inf( @"Clearing keyID for user: %@.", user.userID );
|
||||
user.keyID = nil;
|
||||
[self forgetSavedKeyFor:user];
|
||||
[moc saveToStore];
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
@property(nonatomic, retain) NSNumber *loginNameTipShown;
|
||||
@property(nonatomic, retain) NSNumber *traceMode;
|
||||
@property(nonatomic, retain) NSNumber *dictationSearch;
|
||||
@property(nonatomic, retain) NSNumber *allowDowngrade;
|
||||
@property(nonatomic, retain) NSNumber *developmentFuelRemaining;
|
||||
@property(nonatomic, retain) NSNumber *developmentFuelInvested;
|
||||
@property(nonatomic, retain) NSNumber *developmentFuelConsumption;
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
@implementation MPiOSConfig
|
||||
|
||||
@dynamic helpHidden, siteInfoHidden, showSetup, actionsTipShown, typeTipShown, loginNameTipShown, traceMode, dictationSearch;
|
||||
@dynamic helpHidden, siteInfoHidden, showSetup, actionsTipShown, typeTipShown, loginNameTipShown, traceMode, dictationSearch, allowDowngrade;
|
||||
@dynamic developmentFuelRemaining, developmentFuelInvested, developmentFuelConsumption, developmentFuelChecked;
|
||||
|
||||
- (id)init {
|
||||
@@ -26,6 +26,7 @@
|
||||
NSStringFromSelector( @selector( loginNameTipShown ) ) : @NO,
|
||||
NSStringFromSelector( @selector( traceMode ) ) : @NO,
|
||||
NSStringFromSelector( @selector( dictationSearch ) ) : @NO,
|
||||
NSStringFromSelector( @selector( allowDowngrade ) ) : @NO,
|
||||
}];
|
||||
|
||||
return self;
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
93D393DB5325820241BA90A7 /* PearlSizedTextView.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D39A4759186F6D2D34AA6B /* PearlSizedTextView.h */; };
|
||||
93D394982CBD25D46692DD7C /* MPWebViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D3990E0CD1B5CF9FBB2C07 /* MPWebViewController.m */; };
|
||||
93D394B5036C882B33C71872 /* MPPasswordsSegue.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39E7A12CC352B2825AA66 /* MPPasswordsSegue.m */; };
|
||||
93D39508A6814612A5B3C226 /* MPMessageViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D399B36CDB2004D7C51391 /* MPMessageViewController.m */; };
|
||||
93D39536EB550E811CCD04BC /* UIResponder+PearlFirstResponder.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D394482BB07F90E8FD1314 /* UIResponder+PearlFirstResponder.h */; };
|
||||
93D3954E96236384AFA00453 /* UIScrollView+PearlAdjustInsets.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D390FB3110DCCE68E600DC /* UIScrollView+PearlAdjustInsets.m */; };
|
||||
93D3954FCE045A3CC7E804B7 /* MPUsersViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D399E571F61E50A9BF8FAF /* MPUsersViewController.m */; };
|
||||
@@ -179,6 +180,8 @@
|
||||
DA7304E5194E025900E72520 /* tip_basic_black.png in Resources */ = {isa = PBXBuildFile; fileRef = DABD38901711E29700CF925C /* tip_basic_black.png */; };
|
||||
DA7304E6194E025900E72520 /* tip_basic_black@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DABD38911711E29700CF925C /* tip_basic_black@2x.png */; };
|
||||
DA7304E7194E027C00E72520 /* Square-bottom.png in Resources */ = {isa = PBXBuildFile; fileRef = DA5E5C3C1723681B003798D8 /* Square-bottom.png */; };
|
||||
DA8495311A93049300B3053D /* icon_down.png in Resources */ = {isa = PBXBuildFile; fileRef = DABD375C1711E29500CF925C /* icon_down.png */; };
|
||||
DA8495321A93049300B3053D /* icon_down@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DABD375D1711E29500CF925C /* icon_down@2x.png */; };
|
||||
DA854C8318D4CFBF00106317 /* avatar-add@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA854C8118D4CFBF00106317 /* avatar-add@2x.png */; };
|
||||
DA854C8418D4CFBF00106317 /* avatar-add.png in Resources */ = {isa = PBXBuildFile; fileRef = DA854C8218D4CFBF00106317 /* avatar-add.png */; };
|
||||
DA945C8717E3F3FD0053236B /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DA945C8617E3F3FD0053236B /* Images.xcassets */; };
|
||||
@@ -515,6 +518,7 @@
|
||||
93D39975CE5AEC99E3F086C7 /* MPPasswordCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPPasswordCell.h; sourceTree = "<group>"; };
|
||||
93D3999693660C89A7465F4E /* MPCoachmarkViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPCoachmarkViewController.h; sourceTree = "<group>"; };
|
||||
93D399A8E3181B442D347CD7 /* MPAlgorithmV2.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPAlgorithmV2.m; sourceTree = "<group>"; };
|
||||
93D399B36CDB2004D7C51391 /* MPMessageViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPMessageViewController.m; sourceTree = "<group>"; };
|
||||
93D399C2F3D48E57C4803BDC /* NSPersistentStore+PearlMigration.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSPersistentStore+PearlMigration.m"; sourceTree = "<group>"; };
|
||||
93D399E571F61E50A9BF8FAF /* MPUsersViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPUsersViewController.m; sourceTree = "<group>"; };
|
||||
93D399F244BB522A317811BB /* MPFixable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPFixable.h; sourceTree = "<group>"; };
|
||||
@@ -540,6 +544,7 @@
|
||||
93D39C426E03358384018E85 /* MPAnswersViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPAnswersViewController.m; sourceTree = "<group>"; };
|
||||
93D39C44361BE57AF0B3071F /* MPPasswordsSegue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPPasswordsSegue.h; sourceTree = "<group>"; };
|
||||
93D39C86E984EC65DA5ACB1D /* MPAppSettingsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPAppSettingsViewController.h; sourceTree = "<group>"; };
|
||||
93D39CB0EABD2748740992D8 /* MPMessageViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPMessageViewController.h; sourceTree = "<group>"; };
|
||||
93D39CC01630D0421205C4C4 /* MPNavigationController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPNavigationController.m; sourceTree = "<group>"; };
|
||||
93D39CDD434AFD6E1B0DA359 /* MPEmergencyViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPEmergencyViewController.h; sourceTree = "<group>"; };
|
||||
93D39CECA10BCCB0BA581BF1 /* MPAppDelegate_InApp.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPAppDelegate_InApp.h; sourceTree = "<group>"; };
|
||||
@@ -669,6 +674,7 @@
|
||||
DA70EC7F1811B13C00F65DB2 /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = System/Library/Frameworks/StoreKit.framework; sourceTree = SDKROOT; };
|
||||
DA72BD7419C133BF00E6ACFE /* libscryptenc-ios-sim.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libscryptenc-ios-sim.a"; sourceTree = "<group>"; };
|
||||
DA72BD7719C137D500E6ACFE /* libopensslcrypto-ios-dev.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libopensslcrypto-ios-dev.a"; sourceTree = "<group>"; };
|
||||
DA8495271A9146E600B3053D /* MasterPassword 7.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MasterPassword 7.xcdatamodel"; sourceTree = "<group>"; };
|
||||
DA854C8118D4CFBF00106317 /* avatar-add@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-add@2x.png"; sourceTree = "<group>"; };
|
||||
DA854C8218D4CFBF00106317 /* avatar-add.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-add.png"; sourceTree = "<group>"; };
|
||||
DA945C8617E3F3FD0053236B /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = "<group>"; };
|
||||
@@ -1745,6 +1751,8 @@
|
||||
93D3924D6F77E6BF41AC32D3 /* MPRootSegue.h */,
|
||||
93D39C41A27AA42D044D68AE /* NSString+MPMarkDown.m */,
|
||||
93D393CB0B1F4EC8C17CFE43 /* NSString+MPMarkDown.h */,
|
||||
93D399B36CDB2004D7C51391 /* MPMessageViewController.m */,
|
||||
93D39CB0EABD2748740992D8 /* MPMessageViewController.h */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
@@ -2606,55 +2614,55 @@
|
||||
DABD3BD71711E2DC00CF925C /* iOS */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DA38D6A218CCB5BF009AEB3E /* Storyboard.storyboard */,
|
||||
93D39975CE5AEC99E3F086C7 /* MPPasswordCell.h */,
|
||||
93D39DEA995041A13DC9CAF7 /* MPPasswordCell.m */,
|
||||
93D39ACBA9F4878B6A1CC33B /* MPEmergencyViewController.m */,
|
||||
93D39CDD434AFD6E1B0DA359 /* MPEmergencyViewController.h */,
|
||||
DABD3BD81711E2DC00CF925C /* MPiOSAppDelegate.h */,
|
||||
DABD3BD91711E2DC00CF925C /* MPiOSAppDelegate.m */,
|
||||
DABD3BE61711E2DC00CF925C /* MPGuideViewController.h */,
|
||||
DABD3BE71711E2DC00CF925C /* MPGuideViewController.m */,
|
||||
DABD3BEA1711E2DC00CF925C /* MPPreferencesViewController.h */,
|
||||
DABD3BEB1711E2DC00CF925C /* MPPreferencesViewController.m */,
|
||||
DABD3BEC1711E2DC00CF925C /* MPTypeViewController.h */,
|
||||
DABD3BED1711E2DC00CF925C /* MPTypeViewController.m */,
|
||||
DABD3BF01711E2DC00CF925C /* MPiOSConfig.h */,
|
||||
DABD3BF11711E2DC00CF925C /* MPiOSConfig.m */,
|
||||
DABD3BFA1711E2DC00CF925C /* InfoPlist.strings */,
|
||||
DABD3BFC1711E2DC00CF925C /* main.m */,
|
||||
DABD3BF31711E2DC00CF925C /* MasterPassword-Info.plist */,
|
||||
DABD3BF41711E2DC00CF925C /* MasterPassword-Prefix.pch */,
|
||||
DABD3BF81711E2DC00CF925C /* MasterPassword.entitlements */,
|
||||
DABD3BF91711E2DC00CF925C /* Settings.bundle */,
|
||||
DABD3BFA1711E2DC00CF925C /* InfoPlist.strings */,
|
||||
DABD3BFC1711E2DC00CF925C /* main.m */,
|
||||
93D39A28369954D147E239BA /* MPSetupViewController.m */,
|
||||
93D39730673227EFF6DEFF19 /* MPSetupViewController.h */,
|
||||
93D3979190DACEBD1F6AE9F4 /* MPLogsViewController.m */,
|
||||
93D391943675426839501BB8 /* MPLogsViewController.h */,
|
||||
93D3924EE15017F8A12CB436 /* MPPasswordsViewController.m */,
|
||||
93D3914D7597F9A28DB9D85E /* MPPasswordsViewController.h */,
|
||||
93D393310223DDB35218467A /* MPCombinedViewController.m */,
|
||||
93D39CF8ADF4542CDC4CD385 /* MPCombinedViewController.h */,
|
||||
93D39B381350802A194BF332 /* MPAvatarCell.m */,
|
||||
93D39DA27D768B53C8B1330C /* MPAvatarCell.h */,
|
||||
93D399E571F61E50A9BF8FAF /* MPUsersViewController.m */,
|
||||
93D3971FE104BB4052484151 /* MPUsersViewController.h */,
|
||||
93D39BAA71DE51B4D8A1286C /* MPCell.m */,
|
||||
93D390519405B76CC6A57C4F /* MPCell.h */,
|
||||
93D39E7A12CC352B2825AA66 /* MPPasswordsSegue.m */,
|
||||
93D39C44361BE57AF0B3071F /* MPPasswordsSegue.h */,
|
||||
93D39B050DD5F55E9794EFD4 /* MPPopdownSegue.m */,
|
||||
93D392876BE5C011DE73B43F /* MPPopdownSegue.h */,
|
||||
93D3916C1D8F1427DFBDEBCA /* MPAppSettingsViewController.m */,
|
||||
93D39C86E984EC65DA5ACB1D /* MPAppSettingsViewController.h */,
|
||||
93D3995B1D4DCE5A30D882BA /* MPCoachmarkViewController.m */,
|
||||
93D3916C1D8F1427DFBDEBCA /* MPAppSettingsViewController.m */,
|
||||
93D39DA27D768B53C8B1330C /* MPAvatarCell.h */,
|
||||
93D39B381350802A194BF332 /* MPAvatarCell.m */,
|
||||
93D390519405B76CC6A57C4F /* MPCell.h */,
|
||||
93D39BAA71DE51B4D8A1286C /* MPCell.m */,
|
||||
93D3999693660C89A7465F4E /* MPCoachmarkViewController.h */,
|
||||
93D3990E0CD1B5CF9FBB2C07 /* MPWebViewController.m */,
|
||||
93D39F556F2F142740A65E59 /* MPWebViewController.h */,
|
||||
93D39CC01630D0421205C4C4 /* MPNavigationController.m */,
|
||||
93D3995B1D4DCE5A30D882BA /* MPCoachmarkViewController.m */,
|
||||
93D39CF8ADF4542CDC4CD385 /* MPCombinedViewController.h */,
|
||||
93D393310223DDB35218467A /* MPCombinedViewController.m */,
|
||||
93D39CDD434AFD6E1B0DA359 /* MPEmergencyViewController.h */,
|
||||
93D39ACBA9F4878B6A1CC33B /* MPEmergencyViewController.m */,
|
||||
DABD3BE61711E2DC00CF925C /* MPGuideViewController.h */,
|
||||
DABD3BE71711E2DC00CF925C /* MPGuideViewController.m */,
|
||||
DABD3BD81711E2DC00CF925C /* MPiOSAppDelegate.h */,
|
||||
DABD3BD91711E2DC00CF925C /* MPiOSAppDelegate.m */,
|
||||
DABD3BF01711E2DC00CF925C /* MPiOSConfig.h */,
|
||||
DABD3BF11711E2DC00CF925C /* MPiOSConfig.m */,
|
||||
93D391943675426839501BB8 /* MPLogsViewController.h */,
|
||||
93D3979190DACEBD1F6AE9F4 /* MPLogsViewController.m */,
|
||||
93D3970502644794E8A027BE /* MPNavigationController.h */,
|
||||
93D395105935859D71679931 /* MPOverlayViewController.m */,
|
||||
93D39CC01630D0421205C4C4 /* MPNavigationController.m */,
|
||||
93D39B455A71EC98C749E623 /* MPOverlayViewController.h */,
|
||||
93D395105935859D71679931 /* MPOverlayViewController.m */,
|
||||
93D39975CE5AEC99E3F086C7 /* MPPasswordCell.h */,
|
||||
93D39DEA995041A13DC9CAF7 /* MPPasswordCell.m */,
|
||||
93D39C44361BE57AF0B3071F /* MPPasswordsSegue.h */,
|
||||
93D39E7A12CC352B2825AA66 /* MPPasswordsSegue.m */,
|
||||
93D3914D7597F9A28DB9D85E /* MPPasswordsViewController.h */,
|
||||
93D3924EE15017F8A12CB436 /* MPPasswordsViewController.m */,
|
||||
93D392876BE5C011DE73B43F /* MPPopdownSegue.h */,
|
||||
93D39B050DD5F55E9794EFD4 /* MPPopdownSegue.m */,
|
||||
DABD3BEA1711E2DC00CF925C /* MPPreferencesViewController.h */,
|
||||
DABD3BEB1711E2DC00CF925C /* MPPreferencesViewController.m */,
|
||||
93D39730673227EFF6DEFF19 /* MPSetupViewController.h */,
|
||||
93D39A28369954D147E239BA /* MPSetupViewController.m */,
|
||||
DABD3BEC1711E2DC00CF925C /* MPTypeViewController.h */,
|
||||
DABD3BED1711E2DC00CF925C /* MPTypeViewController.m */,
|
||||
93D3971FE104BB4052484151 /* MPUsersViewController.h */,
|
||||
93D399E571F61E50A9BF8FAF /* MPUsersViewController.m */,
|
||||
93D39F556F2F142740A65E59 /* MPWebViewController.h */,
|
||||
93D3990E0CD1B5CF9FBB2C07 /* MPWebViewController.m */,
|
||||
DABD3BF91711E2DC00CF925C /* Settings.bundle */,
|
||||
DA38D6A218CCB5BF009AEB3E /* Storyboard.storyboard */,
|
||||
);
|
||||
path = iOS;
|
||||
sourceTree = "<group>";
|
||||
@@ -3503,6 +3511,7 @@
|
||||
DABD3B961711E29800CF925C /* pull-down@2x.png in Resources */,
|
||||
DABD3B971711E29800CF925C /* pull-up.png in Resources */,
|
||||
DA24EBEA19DAD6EE00FF010B /* Icon-Small.png in Resources */,
|
||||
DA8495321A93049300B3053D /* icon_down@2x.png in Resources */,
|
||||
DABD3B981711E29800CF925C /* pull-up@2x.png in Resources */,
|
||||
DA7304A0194E022B00E72520 /* ui_textfield@2x.png in Resources */,
|
||||
DAA1765119D8B82B0044227B /* copy_pw@2x.png in Resources */,
|
||||
@@ -3526,6 +3535,7 @@
|
||||
DABD3FCF1714F45C00CF925C /* identity@2x.png in Resources */,
|
||||
DAA1764619D8B82B0044227B /* name_new.png in Resources */,
|
||||
DA45224B190628B2008F650A /* icon_gear.png in Resources */,
|
||||
DA8495311A93049300B3053D /* icon_down.png in Resources */,
|
||||
DA25C5FF197DBF200046CDCF /* icon_thumbs-up@2x.png in Resources */,
|
||||
DAE1EF2217E942DE00BC0086 /* Localizable.strings in Resources */,
|
||||
DA38D6A318CCB5BF009AEB3E /* Storyboard.storyboard in Resources */,
|
||||
@@ -3656,6 +3666,7 @@
|
||||
93D39943D01E70DAC3B0DF76 /* mpw-util.c in Sources */,
|
||||
93D39577FD8BB0945DB2F0A3 /* MPAlgorithmV3.m in Sources */,
|
||||
93D39E5F7F6D7F5C0FAD090F /* MPTypes.m in Sources */,
|
||||
93D39508A6814612A5B3C226 /* MPMessageViewController.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -4423,6 +4434,7 @@
|
||||
DA32D00119CF4735004F3F0E /* MasterPassword.xcdatamodeld */ = {
|
||||
isa = XCVersionGroup;
|
||||
children = (
|
||||
DA8495271A9146E600B3053D /* MasterPassword 7.xcdatamodel */,
|
||||
DA32D00219CF4735004F3F0E /* MasterPassword 1.xcdatamodel */,
|
||||
DA32D00319CF4735004F3F0E /* MasterPassword 2.xcdatamodel */,
|
||||
DA32D00419CF4735004F3F0E /* MasterPassword 3.xcdatamodel */,
|
||||
@@ -4430,7 +4442,7 @@
|
||||
DA32D00619CF4735004F3F0E /* MasterPassword 5.xcdatamodel */,
|
||||
DA32D00719CF4735004F3F0E /* MasterPassword 6.xcdatamodel */,
|
||||
);
|
||||
currentVersion = DA32D00719CF4735004F3F0E /* MasterPassword 6.xcdatamodel */;
|
||||
currentVersion = DA8495271A9146E600B3053D /* MasterPassword 7.xcdatamodel */;
|
||||
path = MasterPassword.xcdatamodeld;
|
||||
sourceTree = "<group>";
|
||||
versionGroupType = wrapper.xcdatamodel;
|
||||
|
||||
@@ -147,6 +147,24 @@ To see a site's password anyway, tap and hold your finger down for a while
|
||||
<key>Type</key>
|
||||
<string>PSToggleSwitchSpecifier</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>FooterText</key>
|
||||
<string>When site downgrades are enabled, a long tap on the upgrade button will downgrade the site instead. This is useful if you accidentally upgraded a site and need to downgrade it again temporarily to see your old password.</string>
|
||||
<key>Title</key>
|
||||
<string></string>
|
||||
<key>Type</key>
|
||||
<string>PSGroupSpecifier</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>DefaultValue</key>
|
||||
<false/>
|
||||
<key>Key</key>
|
||||
<string>allowDowngrade</string>
|
||||
<key>Title</key>
|
||||
<string>Allow Downgrade</string>
|
||||
<key>Type</key>
|
||||
<string>PSToggleSwitchSpecifier</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>Type</key>
|
||||
<string>PSGroupSpecifier</string>
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="6254" systemVersion="14B25" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" initialViewController="Q1S-vU-GGO">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="6254" systemVersion="14C109" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" initialViewController="Q1S-vU-GGO">
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="6247"/>
|
||||
<capability name="Alignment constraints with different attributes" minToolsVersion="5.1"/>
|
||||
<capability name="Aspect ratio constraints" minToolsVersion="5.1"/>
|
||||
<capability name="Constraints to layout margins" minToolsVersion="6.0"/>
|
||||
<capability name="Constraints with non-1.0 multipliers" minToolsVersion="5.1"/>
|
||||
<capability name="Unknown constraint types" minToolsVersion="5.1"/>
|
||||
</dependencies>
|
||||
@@ -31,6 +32,7 @@
|
||||
<string>Exo2.0-Bold</string>
|
||||
<string>Exo2.0-Bold</string>
|
||||
<string>Exo2.0-Bold</string>
|
||||
<string>Exo2.0-Bold</string>
|
||||
</mutableArray>
|
||||
<mutableArray key="Exo2.0-ExtraBold.otf">
|
||||
<string>Exo2.0-ExtraBold</string>
|
||||
@@ -182,10 +184,10 @@
|
||||
<constraint firstAttribute="width" secondItem="Aca-he-7Qi" secondAttribute="height" multiplier="1:1" id="a8Q-UO-SH0"/>
|
||||
</constraints>
|
||||
</imageView>
|
||||
<view contentMode="scaleToFill" misplaced="YES" translatesAutoresizingMaskIntoConstraints="NO" id="0Sa-Vg-EEI" userLabel="Name Backdrop">
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="0Sa-Vg-EEI" userLabel="Name Backdrop">
|
||||
<rect key="frame" x="43" y="263" width="128.5" height="16"/>
|
||||
<subviews>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalCompressionResistancePriority="1000" misplaced="YES" text="Maarten Billemont" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="10" adjustsLetterSpacingToFitWidth="YES" translatesAutoresizingMaskIntoConstraints="NO" id="cLT-s0-4SQ" userLabel="Name Field">
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalCompressionResistancePriority="1000" text="Maarten Billemont" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="10" adjustsLetterSpacingToFitWidth="YES" translatesAutoresizingMaskIntoConstraints="NO" id="cLT-s0-4SQ" userLabel="Name Field">
|
||||
<rect key="frame" x="5" y="0.0" width="118.5" height="16"/>
|
||||
<fontDescription key="fontDescription" name="Exo2.0-ExtraBold" family="Exo 2.0" pointSize="13"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
@@ -249,7 +251,7 @@
|
||||
<outlet property="delegate" destination="S8q-YF-Kt9" id="det-Eh-phM"/>
|
||||
</connections>
|
||||
</collectionView>
|
||||
<button opaque="NO" alpha="0.69999999999999996" contentMode="scaleToFill" misplaced="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="9u7-pu-Wtv" userLabel="Previous Avatar">
|
||||
<button opaque="NO" alpha="0.69999999999999996" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="9u7-pu-Wtv" userLabel="Previous Avatar">
|
||||
<rect key="frame" x="0.0" y="244" width="44" height="53"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="44" id="Ay6-Jg-c3T"/>
|
||||
@@ -262,7 +264,7 @@
|
||||
<action selector="changeAvatar:" destination="S8q-YF-Kt9" eventType="touchUpInside" id="lNu-mK-3zD"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" alpha="0.69999999999999996" contentMode="scaleToFill" misplaced="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="fUK-gJ-NRE" userLabel="Next Avatar">
|
||||
<button opaque="NO" alpha="0.69999999999999996" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="fUK-gJ-NRE" userLabel="Next Avatar">
|
||||
<rect key="frame" x="331" y="244" width="44" height="53"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="44" id="oAm-YX-Fx5"/>
|
||||
@@ -297,20 +299,20 @@
|
||||
<outlet property="delegate" destination="S8q-YF-Kt9" id="5u3-XN-LOe"/>
|
||||
</connections>
|
||||
</textField>
|
||||
<view userInteractionEnabled="NO" alpha="0.0" contentMode="scaleToFill" misplaced="YES" translatesAutoresizingMaskIntoConstraints="NO" id="fdS-zb-K9I" userLabel="Entry Tip">
|
||||
<view userInteractionEnabled="NO" alpha="0.0" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="fdS-zb-K9I" userLabel="Entry Tip">
|
||||
<rect key="frame" x="71" y="-33" width="234.5" height="82.5"/>
|
||||
<subviews>
|
||||
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" misplaced="YES" image="tip_basic_black.png" translatesAutoresizingMaskIntoConstraints="NO" id="g2g-5i-er4">
|
||||
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" image="tip_basic_black.png" translatesAutoresizingMaskIntoConstraints="NO" id="g2g-5i-er4">
|
||||
<rect key="frame" x="0.0" y="0.0" width="234.5" height="82.5"/>
|
||||
<rect key="contentStretch" x="0.15000000000000002" y="0.14999999999999999" width="0.69999999999999973" height="0.44999999999999996"/>
|
||||
</imageView>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" misplaced="YES" text="Looks like a typo!" textAlignment="center" lineBreakMode="tailTruncation" minimumFontSize="10" translatesAutoresizingMaskIntoConstraints="NO" id="ZI7-qg-7OW">
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Looks like a typo!" textAlignment="center" lineBreakMode="tailTruncation" minimumFontSize="10" translatesAutoresizingMaskIntoConstraints="NO" id="ZI7-qg-7OW">
|
||||
<rect key="frame" x="20" y="12" width="194.5" height="17"/>
|
||||
<fontDescription key="fontDescription" name="Exo2.0-Regular" family="Exo 2.0" pointSize="14"/>
|
||||
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="calibratedRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" misplaced="YES" text="Try again; the password was wrong." textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" minimumFontSize="10" adjustsLetterSpacingToFitWidth="YES" translatesAutoresizingMaskIntoConstraints="NO" id="KhE-Yj-Kvm">
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Try again; the password was wrong." textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" minimumFontSize="10" adjustsLetterSpacingToFitWidth="YES" translatesAutoresizingMaskIntoConstraints="NO" id="KhE-Yj-Kvm">
|
||||
<rect key="frame" x="20" y="37" width="194.5" height="14.5"/>
|
||||
<fontDescription key="fontDescription" name="Exo2.0-Regular" family="Exo 2.0" pointSize="12"/>
|
||||
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="calibratedRGB"/>
|
||||
@@ -369,14 +371,14 @@
|
||||
<segue destination="Sd5-eW-Cx2" kind="modal" identifier="web" id="gtb-zE-u9H"/>
|
||||
</connections>
|
||||
</button>
|
||||
<view userInteractionEnabled="NO" alpha="0.0" contentMode="scaleToFill" misplaced="YES" translatesAutoresizingMaskIntoConstraints="NO" id="069-Pu-yXe" userLabel="Thanks Tip">
|
||||
<view userInteractionEnabled="NO" alpha="0.0" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="069-Pu-yXe" userLabel="Thanks Tip">
|
||||
<rect key="frame" x="72" y="0.0" width="232.5" height="60"/>
|
||||
<subviews>
|
||||
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" misplaced="YES" image="tip_basic_black.png" translatesAutoresizingMaskIntoConstraints="NO" id="Z8P-ZK-aS0">
|
||||
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" image="tip_basic_black.png" translatesAutoresizingMaskIntoConstraints="NO" id="Z8P-ZK-aS0">
|
||||
<rect key="frame" x="0.0" y="0.0" width="232.5" height="60"/>
|
||||
<rect key="contentStretch" x="0.15000000000000002" y="0.0" width="0.69999999999999973" height="1"/>
|
||||
</imageView>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" misplaced="YES" text="Why is Master Password free?" textAlignment="center" lineBreakMode="tailTruncation" minimumFontSize="10" translatesAutoresizingMaskIntoConstraints="NO" id="BLV-3x-Q0z">
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Why is Master Password free?" textAlignment="center" lineBreakMode="tailTruncation" minimumFontSize="10" translatesAutoresizingMaskIntoConstraints="NO" id="BLV-3x-Q0z">
|
||||
<rect key="frame" x="20" y="12" width="192.5" height="17"/>
|
||||
<fontDescription key="fontDescription" name="Exo2.0-Regular" family="Exo 2.0" pointSize="14"/>
|
||||
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="calibratedRGB"/>
|
||||
@@ -407,14 +409,14 @@
|
||||
<userDefinedRuntimeAttribute type="boolean" keyPath="ignoreTouches" value="YES"/>
|
||||
</userDefinedRuntimeAttributes>
|
||||
</view>
|
||||
<view userInteractionEnabled="NO" alpha="0.0" contentMode="scaleToFill" misplaced="YES" translatesAutoresizingMaskIntoConstraints="NO" id="cF4-TE-GEj" userLabel="Avatar Tip">
|
||||
<view userInteractionEnabled="NO" alpha="0.0" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="cF4-TE-GEj" userLabel="Avatar Tip">
|
||||
<rect key="frame" x="49.5" y="184" width="276" height="60"/>
|
||||
<subviews>
|
||||
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" misplaced="YES" image="tip_basic_black.png" translatesAutoresizingMaskIntoConstraints="NO" id="V4W-bK-age">
|
||||
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" image="tip_basic_black.png" translatesAutoresizingMaskIntoConstraints="NO" id="V4W-bK-age">
|
||||
<rect key="frame" x="0.0" y="0.0" width="276" height="60"/>
|
||||
<rect key="contentStretch" x="0.15000000000000002" y="0.0" width="0.69999999999999973" height="1"/>
|
||||
</imageView>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" misplaced="YES" text="Change your avatar using the arrows." textAlignment="center" lineBreakMode="tailTruncation" minimumFontSize="10" translatesAutoresizingMaskIntoConstraints="NO" id="MoM-8d-jlm">
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Change your avatar using the arrows." textAlignment="center" lineBreakMode="tailTruncation" minimumFontSize="10" translatesAutoresizingMaskIntoConstraints="NO" id="MoM-8d-jlm">
|
||||
<rect key="frame" x="20" y="12" width="236" height="17"/>
|
||||
<fontDescription key="fontDescription" name="Exo2.0-Regular" family="Exo 2.0" pointSize="14"/>
|
||||
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="calibratedRGB"/>
|
||||
@@ -432,14 +434,14 @@
|
||||
<constraint firstAttribute="trailing" secondItem="MoM-8d-jlm" secondAttribute="trailing" constant="20" id="Hc3-Mb-x5c"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<view userInteractionEnabled="NO" alpha="0.0" contentMode="scaleToFill" misplaced="YES" translatesAutoresizingMaskIntoConstraints="NO" id="0Um-Ot-hI6" userLabel="Preferences Tip">
|
||||
<view userInteractionEnabled="NO" alpha="0.0" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="0Um-Ot-hI6" userLabel="Preferences Tip">
|
||||
<rect key="frame" x="72" y="42" width="230" height="60"/>
|
||||
<subviews>
|
||||
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" misplaced="YES" image="tip_basic_black_top.png" translatesAutoresizingMaskIntoConstraints="NO" id="5H0-ml-Uso">
|
||||
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" image="tip_basic_black_top.png" translatesAutoresizingMaskIntoConstraints="NO" id="5H0-ml-Uso">
|
||||
<rect key="frame" x="0.0" y="0.0" width="230" height="60"/>
|
||||
<rect key="contentStretch" x="0.15000000000000002" y="0.0" width="0.69999999999999973" height="1"/>
|
||||
</imageView>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" misplaced="YES" text="Tap for preferences and more." textAlignment="center" lineBreakMode="tailTruncation" minimumFontSize="10" translatesAutoresizingMaskIntoConstraints="NO" id="Er5-X1-ejQ">
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Tap for preferences and more." textAlignment="center" lineBreakMode="tailTruncation" minimumFontSize="10" translatesAutoresizingMaskIntoConstraints="NO" id="Er5-X1-ejQ">
|
||||
<rect key="frame" x="20" y="31.5" width="190" height="17"/>
|
||||
<fontDescription key="fontDescription" name="Exo2.0-Regular" family="Exo 2.0" pointSize="14"/>
|
||||
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="calibratedRGB"/>
|
||||
@@ -847,7 +849,7 @@
|
||||
<constraint firstAttribute="height" constant="110" id="zBf-EA-iDN"/>
|
||||
</constraints>
|
||||
</imageView>
|
||||
<button opaque="NO" alpha="0.69999998807907104" contentMode="scaleToFill" misplaced="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="DzC-Ts-gew" userLabel="Previous Avatar">
|
||||
<button opaque="NO" alpha="0.69999998807907104" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="DzC-Ts-gew" userLabel="Previous Avatar">
|
||||
<rect key="frame" x="20" y="117" width="44" height="53"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" relation="greaterThanOrEqual" constant="44" id="1Wu-gS-flK"/>
|
||||
@@ -861,7 +863,7 @@
|
||||
<action selector="previousAvatar:" destination="JFc-sj-awD" eventType="touchUpInside" id="D92-6I-zFd"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" alpha="0.69999998807907104" contentMode="scaleToFill" misplaced="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="yAf-fc-SKl" userLabel="Next Avatar">
|
||||
<button opaque="NO" alpha="0.69999998807907104" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="yAf-fc-SKl" userLabel="Next Avatar">
|
||||
<rect key="frame" x="311" y="117" width="44" height="53"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" relation="greaterThanOrEqual" constant="44" id="pEf-Vp-D7s"/>
|
||||
@@ -1040,13 +1042,13 @@
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="152"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="1000" verticalCompressionResistancePriority="1000" ambiguous="YES" misplaced="YES" text="© 2012-2014, Maarten Billemont (lhunath)" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="sPw-mV-mFF">
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="1000" verticalCompressionResistancePriority="1000" text="© 2012-2014, Maarten Billemont (lhunath)" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="sPw-mV-mFF">
|
||||
<rect key="frame" x="20" y="4" width="335" height="12"/>
|
||||
<fontDescription key="fontDescription" name="Exo2.0-Thin" family="Exo 2.0" pointSize="10"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<button opaque="NO" contentMode="scaleToFill" verticalHuggingPriority="1000" verticalCompressionResistancePriority="1000" ambiguous="YES" misplaced="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Rl7-cr-FHf">
|
||||
<button opaque="NO" contentMode="scaleToFill" verticalHuggingPriority="1000" verticalCompressionResistancePriority="1000" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Rl7-cr-FHf">
|
||||
<rect key="frame" x="20" y="24" width="335" height="26"/>
|
||||
<fontDescription key="fontDescription" name="Exo2.0-Regular" family="Exo 2.0" pointSize="11"/>
|
||||
<state key="normal" title="Home Page">
|
||||
@@ -1056,7 +1058,7 @@
|
||||
<action selector="homePageButton:" destination="JFc-sj-awD" eventType="touchUpInside" id="ptD-cv-NMr"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" verticalHuggingPriority="1000" verticalCompressionResistancePriority="1000" ambiguous="YES" misplaced="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="epW-Rm-9St">
|
||||
<button opaque="NO" contentMode="scaleToFill" verticalHuggingPriority="1000" verticalCompressionResistancePriority="1000" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="epW-Rm-9St">
|
||||
<rect key="frame" x="20" y="58" width="335" height="26"/>
|
||||
<fontDescription key="fontDescription" name="Exo2.0-Regular" family="Exo 2.0" pointSize="11"/>
|
||||
<state key="normal" title="Understanding Master Password's Security">
|
||||
@@ -1066,7 +1068,7 @@
|
||||
<action selector="securityButton:" destination="JFc-sj-awD" eventType="touchUpInside" id="Efv-cp-Xfh"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" verticalHuggingPriority="1000" verticalCompressionResistancePriority="1000" ambiguous="YES" misplaced="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="LTN-ch-h8D">
|
||||
<button opaque="NO" contentMode="scaleToFill" verticalHuggingPriority="1000" verticalCompressionResistancePriority="1000" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="LTN-ch-h8D">
|
||||
<rect key="frame" x="20" y="92" width="335" height="26"/>
|
||||
<fontDescription key="fontDescription" name="Exo2.0-Regular" family="Exo 2.0" pointSize="11"/>
|
||||
<state key="normal" title="Get the Master Password source code">
|
||||
@@ -1076,7 +1078,7 @@
|
||||
<action selector="sourceButton:" destination="JFc-sj-awD" eventType="touchUpInside" id="Y3O-di-CZo"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" verticalHuggingPriority="1000" verticalCompressionResistancePriority="1000" ambiguous="YES" misplaced="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Z60-lc-Nka">
|
||||
<button opaque="NO" contentMode="scaleToFill" verticalHuggingPriority="1000" verticalCompressionResistancePriority="1000" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Z60-lc-Nka">
|
||||
<rect key="frame" x="20" y="126" width="335" height="26"/>
|
||||
<fontDescription key="fontDescription" name="Exo2.0-Regular" family="Exo 2.0" pointSize="11"/>
|
||||
<state key="normal" title="Send Thanks">
|
||||
@@ -1158,7 +1160,7 @@
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
|
||||
<inset key="scrollIndicatorInsets" minX="0.0" minY="108" maxX="0.0" maxY="0.0"/>
|
||||
<collectionViewFlowLayout key="collectionViewLayout" minimumLineSpacing="10" minimumInteritemSpacing="10" id="Mv1-29-TWx">
|
||||
<size key="itemSize" width="300" height="100"/>
|
||||
<size key="itemSize" width="355" height="100"/>
|
||||
<size key="headerReferenceSize" width="0.0" height="0.0"/>
|
||||
<size key="footerReferenceSize" width="0.0" height="0.0"/>
|
||||
<inset key="sectionInset" minX="10" minY="118" maxX="10" maxY="10"/>
|
||||
@@ -1168,34 +1170,34 @@
|
||||
<rect key="frame" x="10" y="118" width="300" height="100"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center">
|
||||
<rect key="frame" x="0.0" y="0.0" width="300" height="100"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="355" height="100"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="xph-TW-9QO" userLabel="Content">
|
||||
<rect key="frame" x="0.0" y="0.0" width="300" height="100"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="355" height="100"/>
|
||||
<subviews>
|
||||
<scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" directionalLockEnabled="YES" pagingEnabled="YES" translatesAutoresizingMaskIntoConstraints="NO" id="bff-RU-OcY">
|
||||
<rect key="frame" x="0.0" y="0.0" width="300" height="100"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="355" height="100"/>
|
||||
<subviews>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="aDw-qY-VjU" userLabel="Copy Content">
|
||||
<rect key="frame" x="0.0" y="0.0" width="300" height="100"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="355" height="100"/>
|
||||
<color key="backgroundColor" red="0.18431372549019609" green="0.15686274509803921" blue="0.15686274509803921" alpha="0.5" colorSpace="calibratedRGB"/>
|
||||
<connections>
|
||||
<action selector="doContent:" destination="W2g-yv-V3V" eventType="touchUpInside" id="ukg-D8-8O3"/>
|
||||
</connections>
|
||||
</button>
|
||||
<view alpha="0.59999999999999998" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="w2g-zN-1wZ" userLabel="Login Container">
|
||||
<rect key="frame" x="0.0" y="0.0" width="300" height="21"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="355" height="21"/>
|
||||
<subviews>
|
||||
<view alpha="0.80000000000000004" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="q3g-CJ-LbN" userLabel="Separator">
|
||||
<rect key="frame" x="0.0" y="20" width="300" height="1"/>
|
||||
<rect key="frame" x="0.0" y="20" width="355" height="1"/>
|
||||
<color key="backgroundColor" red="0.14901960784313725" green="0.14901960784313725" blue="0.14901960784313725" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="1" id="jyk-dC-QLb"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="top" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Iwe-rQ-ma0" userLabel="Copy Login">
|
||||
<rect key="frame" x="0.0" y="0.0" width="300" height="33"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="355" height="33"/>
|
||||
<color key="backgroundColor" red="0.18431372549019609" green="0.15686274509803921" blue="0.15686274509803921" alpha="0.01" colorSpace="calibratedRGB"/>
|
||||
<fontDescription key="fontDescription" name="Exo2.0-Regular" family="Exo 2.0" pointSize="11"/>
|
||||
<inset key="titleEdgeInsets" minX="0.0" minY="-4" maxX="0.0" maxY="0.0"/>
|
||||
@@ -1207,7 +1209,7 @@
|
||||
</connections>
|
||||
</button>
|
||||
<textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" enabled="NO" contentHorizontalAlignment="left" contentVerticalAlignment="center" textAlignment="center" minimumFontSize="9" translatesAutoresizingMaskIntoConstraints="NO" id="3I9-vf-IZK" userLabel="Login">
|
||||
<rect key="frame" x="8" y="0.0" width="284" height="20"/>
|
||||
<rect key="frame" x="8" y="0.0" width="339" height="20"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="20" id="9gA-Ti-g1e"/>
|
||||
@@ -1235,11 +1237,11 @@
|
||||
<constraint firstItem="Iwe-rQ-ma0" firstAttribute="top" secondItem="w2g-zN-1wZ" secondAttribute="top" id="q2j-Aa-lEd"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<view contentMode="scaleToFill" misplaced="YES" translatesAutoresizingMaskIntoConstraints="NO" id="2tX-WK-ASq" userLabel="Password Container">
|
||||
<rect key="frame" x="0.0" y="20" width="300" height="43"/>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="2tX-WK-ASq" userLabel="Password Container">
|
||||
<rect key="frame" x="0.0" y="20" width="355" height="43"/>
|
||||
<subviews>
|
||||
<textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" misplaced="YES" enabled="NO" contentHorizontalAlignment="left" contentVerticalAlignment="center" text="CuzaSasy3*Rimo" textAlignment="center" minimumFontSize="17" clearButtonMode="whileEditing" translatesAutoresizingMaskIntoConstraints="NO" id="blw-Ou-8I8" userLabel="Password">
|
||||
<rect key="frame" x="8" y="0.0" width="284" height="31"/>
|
||||
<textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" enabled="NO" contentHorizontalAlignment="left" contentVerticalAlignment="center" text="CuzaSasy3*Rimo" textAlignment="center" minimumFontSize="17" clearButtonMode="whileEditing" translatesAutoresizingMaskIntoConstraints="NO" id="blw-Ou-8I8" userLabel="Password">
|
||||
<rect key="frame" x="8" y="0.0" width="339" height="31"/>
|
||||
<color key="textColor" red="0.40000000600000002" green="0.80000001190000003" blue="1" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<fontDescription key="fontDescription" name="SourceCodePro-Black" family="Source Code Pro" pointSize="24"/>
|
||||
<textInputTraits key="textInputTraits" autocorrectionType="no" keyboardType="alphabet" keyboardAppearance="alert" returnKeyType="next"/>
|
||||
@@ -1248,8 +1250,8 @@
|
||||
<outlet property="delegate" destination="W2g-yv-V3V" id="YKp-IE-zEQ"/>
|
||||
</connections>
|
||||
</textField>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" tag="3" contentMode="left" misplaced="YES" text="> age of the universe" textAlignment="center" lineBreakMode="tailTruncation" minimumFontSize="10" translatesAutoresizingMaskIntoConstraints="NO" id="wfM-xz-roR" userLabel="Strength">
|
||||
<rect key="frame" x="0.0" y="31" width="300" height="12"/>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" tag="3" contentMode="left" text="> age of the universe" textAlignment="center" lineBreakMode="tailTruncation" minimumFontSize="10" translatesAutoresizingMaskIntoConstraints="NO" id="wfM-xz-roR" userLabel="Strength">
|
||||
<rect key="frame" x="0.0" y="31" width="355" height="12"/>
|
||||
<fontDescription key="fontDescription" name="Exo2.0-Regular" family="Exo 2.0" pointSize="10"/>
|
||||
<color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<nil key="highlightedColor"/>
|
||||
@@ -1271,10 +1273,10 @@
|
||||
</userDefinedRuntimeAttributes>
|
||||
</view>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="tl3-hd-x35" userLabel="Main Container">
|
||||
<rect key="frame" x="0.0" y="0.0" width="300" height="100"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="355" height="100"/>
|
||||
<subviews>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" alpha="0.20000000000000001" contentMode="left" text="apple.com - Long" lineBreakMode="tailTruncation" minimumFontSize="8" adjustsLetterSpacingToFitWidth="YES" translatesAutoresizingMaskIntoConstraints="NO" id="OwP-sb-Wxl" userLabel="Site Name">
|
||||
<rect key="frame" x="8" y="76" width="248" height="15.5"/>
|
||||
<rect key="frame" x="8" y="76" width="303" height="15.5"/>
|
||||
<accessibility key="accessibilityConfiguration" label="">
|
||||
<accessibilityTraits key="traits" none="YES" staticText="YES" summaryElement="YES"/>
|
||||
</accessibility>
|
||||
@@ -1295,12 +1297,13 @@
|
||||
<userDefinedRuntimeAttribute type="boolean" keyPath="ignoreTouches" value="YES"/>
|
||||
</userDefinedRuntimeAttributes>
|
||||
</view>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="LvK-28-fbm" userLabel="Settings Container">
|
||||
<rect key="frame" x="300" y="0.0" width="300" height="100"/>
|
||||
<view contentMode="scaleToFill" misplaced="YES" translatesAutoresizingMaskIntoConstraints="NO" id="LvK-28-fbm" userLabel="Settings Container">
|
||||
<rect key="frame" x="0.0" y="0.0" width="355" height="100"/>
|
||||
<subviews>
|
||||
<button opaque="NO" alpha="0.5" contentMode="scaleToFill" misplaced="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" showsTouchWhenHighlighted="YES" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="IKd-Ot-0n4" userLabel="Upgrade">
|
||||
<rect key="frame" x="-15" y="56" width="44" height="44"/>
|
||||
<button opaque="NO" alpha="0.5" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" showsTouchWhenHighlighted="YES" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="IKd-Ot-0n4" userLabel="Upgrade">
|
||||
<rect key="frame" x="40" y="56" width="44" height="44"/>
|
||||
<accessibility key="accessibilityConfiguration" hint="Upgrades the password."/>
|
||||
<gestureRecognizers/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="44" id="5DT-m6-RYu"/>
|
||||
<constraint firstAttribute="height" constant="44" id="kyZ-o1-ntZ"/>
|
||||
@@ -1318,8 +1321,8 @@
|
||||
<action selector="doUpgrade:" destination="W2g-yv-V3V" eventType="touchUpInside" id="kTZ-AM-qGa"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" alpha="0.5" contentMode="scaleToFill" misplaced="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" showsTouchWhenHighlighted="YES" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="vGk-t6-hZn" userLabel="Answers">
|
||||
<rect key="frame" x="29" y="56" width="44" height="44"/>
|
||||
<button opaque="NO" alpha="0.5" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" showsTouchWhenHighlighted="YES" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="vGk-t6-hZn" userLabel="Answers">
|
||||
<rect key="frame" x="84" y="56" width="44" height="44"/>
|
||||
<accessibility key="accessibilityConfiguration" hint="Upgrades the password."/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="44" id="8gK-8v-Q0K"/>
|
||||
@@ -1338,8 +1341,8 @@
|
||||
<segue destination="aow-In-vb8" kind="custom" identifier="answers" customClass="MPOverlaySegue" id="5Wo-YZ-8HZ"/>
|
||||
</connections>
|
||||
</button>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" alpha="0.5" contentMode="left" misplaced="YES" text="1" textAlignment="right" lineBreakMode="tailTruncation" minimumFontSize="10" translatesAutoresizingMaskIntoConstraints="NO" id="PKP-M9-T8E" userLabel="Counter">
|
||||
<rect key="frame" x="73" y="67" width="7" height="21.5"/>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" alpha="0.5" contentMode="left" text="1" textAlignment="right" lineBreakMode="tailTruncation" minimumFontSize="10" translatesAutoresizingMaskIntoConstraints="NO" id="PKP-M9-T8E" userLabel="Counter">
|
||||
<rect key="frame" x="128" y="67" width="7" height="21.5"/>
|
||||
<accessibility key="accessibilityConfiguration" hint="Site's counter."/>
|
||||
<fontDescription key="fontDescription" name="Exo2.0-Regular" family="Exo 2.0" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
@@ -1348,7 +1351,7 @@
|
||||
<size key="shadowOffset" width="0.0" height="1"/>
|
||||
</label>
|
||||
<button opaque="NO" alpha="0.5" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" showsTouchWhenHighlighted="YES" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="uZi-FT-Fe8" userLabel="Incrementer">
|
||||
<rect key="frame" x="80" y="56" width="44" height="44"/>
|
||||
<rect key="frame" x="135" y="56" width="44" height="44"/>
|
||||
<accessibility key="accessibilityConfiguration" hint="Increments the site counter."/>
|
||||
<gestureRecognizers/>
|
||||
<constraints>
|
||||
@@ -1369,7 +1372,7 @@
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" alpha="0.5" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" showsTouchWhenHighlighted="YES" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="qBo-Kw-vN9" userLabel="Edit">
|
||||
<rect key="frame" x="124" y="56" width="44" height="44"/>
|
||||
<rect key="frame" x="179" y="56" width="44" height="44"/>
|
||||
<accessibility key="accessibilityConfiguration" hint="Edits the password."/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="44" id="j1z-K8-OXJ"/>
|
||||
@@ -1389,7 +1392,7 @@
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" alpha="0.5" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" showsTouchWhenHighlighted="YES" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="I0v-ou-hDb" userLabel="Delete">
|
||||
<rect key="frame" x="168" y="56" width="44" height="44"/>
|
||||
<rect key="frame" x="223" y="56" width="44" height="44"/>
|
||||
<accessibility key="accessibilityConfiguration" hint="Edits the password."/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="44" id="XvV-Lr-Z21"/>
|
||||
@@ -1409,7 +1412,7 @@
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" alpha="0.5" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" showsTouchWhenHighlighted="YES" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="K8q-bM-tzh" userLabel="Type">
|
||||
<rect key="frame" x="212" y="56" width="44" height="44"/>
|
||||
<rect key="frame" x="267" y="56" width="44" height="44"/>
|
||||
<accessibility key="accessibilityConfiguration" hint="Edits the password."/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="44" id="K3c-Ok-krB"/>
|
||||
@@ -1446,7 +1449,7 @@
|
||||
</userDefinedRuntimeAttributes>
|
||||
</view>
|
||||
<button opaque="NO" alpha="0.5" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" showsTouchWhenHighlighted="YES" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="b5f-wN-2xb" userLabel="Site Mode">
|
||||
<rect key="frame" x="256" y="56" width="44" height="44"/>
|
||||
<rect key="frame" x="311" y="56" width="44" height="44"/>
|
||||
<accessibility key="accessibilityConfiguration" hint="Upgrades the password."/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="44" id="I1C-Ok-DUv"/>
|
||||
@@ -1467,7 +1470,7 @@
|
||||
</connections>
|
||||
</button>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="⬇︎" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="tuh-Au-J9k">
|
||||
<rect key="frame" x="270" y="35" width="17" height="20.5"/>
|
||||
<rect key="frame" x="325" y="35" width="17" height="20.5"/>
|
||||
<fontDescription key="fontDescription" name="Exo2.0-Regular" family="Exo 2.0" pointSize="17"/>
|
||||
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="calibratedRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
@@ -1599,14 +1602,14 @@
|
||||
<outlet property="delegate" destination="nkY-z6-8jd" id="ENG-q5-XwX"/>
|
||||
</connections>
|
||||
</searchBar>
|
||||
<view userInteractionEnabled="NO" alpha="0.0" contentMode="scaleToFill" misplaced="YES" translatesAutoresizingMaskIntoConstraints="NO" id="LEX-BK-PdS" userLabel="Bad Name Tip">
|
||||
<view userInteractionEnabled="NO" alpha="0.0" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="LEX-BK-PdS" userLabel="Bad Name Tip">
|
||||
<rect key="frame" x="37" y="86" width="300.5" height="75.5"/>
|
||||
<subviews>
|
||||
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" misplaced="YES" image="tip_basic_black_top.png" translatesAutoresizingMaskIntoConstraints="NO" id="Rt5-v4-I0R">
|
||||
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" image="tip_basic_black_top.png" translatesAutoresizingMaskIntoConstraints="NO" id="Rt5-v4-I0R">
|
||||
<rect key="frame" x="0.0" y="0.0" width="300.5" height="75.5"/>
|
||||
<rect key="contentStretch" x="0.050000000000000003" y="0.49999999999999994" width="0.90000000000000002" height="0.20000000000000001"/>
|
||||
</imageView>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" verticalCompressionResistancePriority="1000" misplaced="YES" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" minimumFontSize="10" translatesAutoresizingMaskIntoConstraints="NO" id="Eie-8u-hV2">
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" verticalCompressionResistancePriority="1000" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" minimumFontSize="10" translatesAutoresizingMaskIntoConstraints="NO" id="Eie-8u-hV2">
|
||||
<rect key="frame" x="20" y="26" width="260.5" height="43.5"/>
|
||||
<string key="text">Try using exclusively bare domain names.
|
||||
Avoid capitals and use @ to include a user name.
|
||||
@@ -1725,6 +1728,7 @@ eg. apple.com, rmitchell@twitter.com</string>
|
||||
<outlet property="popdownToTopConstraint" destination="BdD-Kc-eHl" id="59Y-ap-Yn4"/>
|
||||
<outlet property="popdownView" destination="XNM-XQ-rMe" id="FaW-4m-Fff"/>
|
||||
<segue destination="z9O-w0-6oR" kind="modal" identifier="guide" id="Ql4-wf-T8u"/>
|
||||
<segue destination="Foa-Er-RBr" kind="custom" identifier="message" customClass="MPOverlaySegue" id="Xne-Sm-HQt"/>
|
||||
</connections>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="APh-u5-vFI" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
@@ -1750,7 +1754,7 @@ eg. apple.com, rmitchell@twitter.com</string>
|
||||
<rect key="frame" x="0.0" y="0.5" width="375" height="333.5"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
|
||||
</view>
|
||||
<view contentMode="scaleToFill" misplaced="YES" translatesAutoresizingMaskIntoConstraints="NO" id="1lc-e7-Qme" userLabel="Emergency Generator">
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="1lc-e7-Qme" userLabel="Emergency Generator">
|
||||
<rect key="frame" x="20" y="140" width="335" height="387"/>
|
||||
<subviews>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Emergency Generator" textAlignment="center" lineBreakMode="tailTruncation" minimumFontSize="10" translatesAutoresizingMaskIntoConstraints="NO" id="4Lh-s0-Dbt">
|
||||
@@ -1761,7 +1765,7 @@ eg. apple.com, rmitchell@twitter.com</string>
|
||||
<color key="shadowColor" cocoaTouchSystemColor="darkTextColor"/>
|
||||
<size key="shadowOffset" width="0.0" height="1"/>
|
||||
</label>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" misplaced="YES" text="Generate your password without logging in. Great for if you're borrowing a friend's device or are having trouble logging in." lineBreakMode="tailTruncation" numberOfLines="0" minimumFontSize="10" translatesAutoresizingMaskIntoConstraints="NO" id="vHS-3A-Tae">
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Generate your password without logging in. Great for if you're borrowing a friend's device or are having trouble logging in." lineBreakMode="tailTruncation" numberOfLines="0" minimumFontSize="10" translatesAutoresizingMaskIntoConstraints="NO" id="vHS-3A-Tae">
|
||||
<rect key="frame" x="20" y="49" width="295" height="51.5"/>
|
||||
<fontDescription key="fontDescription" name="Exo2.0-Thin" family="Exo 2.0" pointSize="14"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
@@ -1769,7 +1773,7 @@ eg. apple.com, rmitchell@twitter.com</string>
|
||||
<color key="shadowColor" cocoaTouchSystemColor="darkTextColor"/>
|
||||
<size key="shadowOffset" width="0.0" height="1"/>
|
||||
</label>
|
||||
<textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" misplaced="YES" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" placeholder="Your Name" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="XAC-Da-lpf" userLabel="User Name">
|
||||
<textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" placeholder="Your Name" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="XAC-Da-lpf" userLabel="User Name">
|
||||
<rect key="frame" x="20" y="109" width="295" height="30"/>
|
||||
<fontDescription key="fontDescription" name="Exo2.0-Regular" family="Exo 2.0" pointSize="14"/>
|
||||
<textInputTraits key="textInputTraits" autocapitalizationType="words" keyboardAppearance="alert" returnKeyType="next" enablesReturnKeyAutomatically="YES"/>
|
||||
@@ -1778,7 +1782,7 @@ eg. apple.com, rmitchell@twitter.com</string>
|
||||
<outlet property="delegate" destination="osn-5H-SWW" id="VQI-Lq-GWG"/>
|
||||
</connections>
|
||||
</textField>
|
||||
<textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" misplaced="YES" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" placeholder="Your Master Password" clearsOnBeginEditing="YES" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="J46-0E-no3" userLabel="Master Password">
|
||||
<textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" placeholder="Your Master Password" clearsOnBeginEditing="YES" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="J46-0E-no3" userLabel="Master Password">
|
||||
<rect key="frame" x="20" y="147" width="295" height="30"/>
|
||||
<fontDescription key="fontDescription" name="Exo2.0-Regular" family="Exo 2.0" pointSize="14"/>
|
||||
<textInputTraits key="textInputTraits" autocorrectionType="no" keyboardAppearance="alert" returnKeyType="next" enablesReturnKeyAutomatically="YES" secureTextEntry="YES"/>
|
||||
@@ -1787,7 +1791,7 @@ eg. apple.com, rmitchell@twitter.com</string>
|
||||
<outlet property="delegate" destination="osn-5H-SWW" id="bpf-YA-5XP"/>
|
||||
</connections>
|
||||
</textField>
|
||||
<textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" misplaced="YES" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" placeholder="Site Name" clearsOnBeginEditing="YES" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="56H-xR-09J" userLabel="Site Name">
|
||||
<textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" placeholder="Site Name" clearsOnBeginEditing="YES" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="56H-xR-09J" userLabel="Site Name">
|
||||
<rect key="frame" x="20" y="185" width="295" height="30"/>
|
||||
<fontDescription key="fontDescription" name="Exo2.0-Regular" family="Exo 2.0" pointSize="14"/>
|
||||
<textInputTraits key="textInputTraits" autocorrectionType="no" keyboardType="URL" keyboardAppearance="alert" returnKeyType="done" enablesReturnKeyAutomatically="YES"/>
|
||||
@@ -1796,7 +1800,7 @@ eg. apple.com, rmitchell@twitter.com</string>
|
||||
<outlet property="delegate" destination="osn-5H-SWW" id="QgA-TS-5KG"/>
|
||||
</connections>
|
||||
</textField>
|
||||
<segmentedControl opaque="NO" contentMode="scaleToFill" misplaced="YES" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="bar" selectedSegmentIndex="1" translatesAutoresizingMaskIntoConstraints="NO" id="e4b-Iv-Pk9" userLabel="Type">
|
||||
<segmentedControl opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="bar" selectedSegmentIndex="1" translatesAutoresizingMaskIntoConstraints="NO" id="e4b-Iv-Pk9" userLabel="Type">
|
||||
<rect key="frame" x="20" y="223" width="295" height="29"/>
|
||||
<segments>
|
||||
<segment title="Max"/>
|
||||
@@ -1811,7 +1815,7 @@ eg. apple.com, rmitchell@twitter.com</string>
|
||||
<action selector="controlChanged:" destination="osn-5H-SWW" eventType="valueChanged" id="sRc-3g-wqY"/>
|
||||
</connections>
|
||||
</segmentedControl>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" misplaced="YES" text="Counter" lineBreakMode="tailTruncation" numberOfLines="0" minimumFontSize="10" translatesAutoresizingMaskIntoConstraints="NO" id="cAo-K2-E23">
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Counter" lineBreakMode="tailTruncation" numberOfLines="0" minimumFontSize="10" translatesAutoresizingMaskIntoConstraints="NO" id="cAo-K2-E23">
|
||||
<rect key="frame" x="20" y="262.5" width="64" height="21.5"/>
|
||||
<fontDescription key="fontDescription" name="Exo2.0-Bold" family="Exo 2.0" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
@@ -1819,13 +1823,13 @@ eg. apple.com, rmitchell@twitter.com</string>
|
||||
<color key="shadowColor" cocoaTouchSystemColor="darkTextColor"/>
|
||||
<size key="shadowOffset" width="0.0" height="1"/>
|
||||
</label>
|
||||
<stepper opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" misplaced="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" maximumValue="100" translatesAutoresizingMaskIntoConstraints="NO" id="ZPT-EI-yuv" userLabel="Counter">
|
||||
<stepper opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" maximumValue="100" translatesAutoresizingMaskIntoConstraints="NO" id="ZPT-EI-yuv" userLabel="Counter">
|
||||
<rect key="frame" x="221" y="259" width="94" height="29"/>
|
||||
<connections>
|
||||
<action selector="controlChanged:" destination="osn-5H-SWW" eventType="valueChanged" id="eQA-3X-uc9"/>
|
||||
</connections>
|
||||
</stepper>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" misplaced="YES" text="1" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="10" translatesAutoresizingMaskIntoConstraints="NO" id="3Cd-XH-Wau">
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="1" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="10" translatesAutoresizingMaskIntoConstraints="NO" id="3Cd-XH-Wau">
|
||||
<rect key="frame" x="206" y="262.5" width="7" height="20.5"/>
|
||||
<fontDescription key="fontDescription" name="Exo2.0-Regular" family="Exo 2.0" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
@@ -1866,10 +1870,10 @@ eg. apple.com, rmitchell@twitter.com</string>
|
||||
<color key="titleColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
</state>
|
||||
</button>
|
||||
<activityIndicatorView hidden="YES" opaque="NO" contentMode="scaleToFill" misplaced="YES" hidesWhenStopped="YES" style="whiteLarge" translatesAutoresizingMaskIntoConstraints="NO" id="4sN-hm-xio">
|
||||
<activityIndicatorView hidden="YES" opaque="NO" contentMode="scaleToFill" hidesWhenStopped="YES" style="whiteLarge" translatesAutoresizingMaskIntoConstraints="NO" id="4sN-hm-xio">
|
||||
<rect key="frame" x="149" y="329" width="37" height="37"/>
|
||||
</activityIndicatorView>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" misplaced="YES" text="XapaNuwjFihn6$" textAlignment="center" lineBreakMode="clip" baselineAdjustment="alignBaselines" minimumFontSize="10" adjustsLetterSpacingToFitWidth="YES" translatesAutoresizingMaskIntoConstraints="NO" id="bHR-he-dnZ" userLabel="Password Label">
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="XapaNuwjFihn6$" textAlignment="center" lineBreakMode="clip" baselineAdjustment="alignBaselines" minimumFontSize="10" adjustsLetterSpacingToFitWidth="YES" translatesAutoresizingMaskIntoConstraints="NO" id="bHR-he-dnZ" userLabel="Password Label">
|
||||
<rect key="frame" x="20" y="328" width="295" height="39"/>
|
||||
<gestureRecognizers/>
|
||||
<fontDescription key="fontDescription" name="SourceCodePro-Black" family="Source Code Pro" pointSize="30"/>
|
||||
@@ -1880,7 +1884,7 @@ eg. apple.com, rmitchell@twitter.com</string>
|
||||
<outletCollection property="gestureRecognizers" destination="gJb-50-mjy" appends="YES" id="3Ho-tp-mDE"/>
|
||||
</connections>
|
||||
</label>
|
||||
<view userInteractionEnabled="NO" alpha="0.0" contentMode="scaleToFill" misplaced="YES" translatesAutoresizingMaskIntoConstraints="NO" id="beo-cJ-jIn" userLabel="View - Content Tip">
|
||||
<view userInteractionEnabled="NO" alpha="0.0" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="beo-cJ-jIn" userLabel="View - Content Tip">
|
||||
<rect key="frame" x="62" y="287.5" width="210" height="60"/>
|
||||
<subviews>
|
||||
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" image="tip_basic_black.png" translatesAutoresizingMaskIntoConstraints="NO" id="nyL-cO-aPa">
|
||||
@@ -1984,13 +1988,13 @@ eg. apple.com, rmitchell@twitter.com</string>
|
||||
<outlet property="counterLabel" destination="3Cd-XH-Wau" id="wv7-jZ-6tX"/>
|
||||
<outlet property="counterStepper" destination="ZPT-EI-yuv" id="w5c-hO-T51"/>
|
||||
<outlet property="dialogView" destination="1lc-e7-Qme" id="JYt-mv-XV2"/>
|
||||
<outlet property="fullNameField" destination="XAC-Da-lpf" id="XCk-0H-IcI"/>
|
||||
<outlet property="masterPasswordField" destination="J46-0E-no3" id="DfH-4n-cop"/>
|
||||
<outlet property="passwordLabel" destination="bHR-he-dnZ" id="0Mo-gc-Ls2"/>
|
||||
<outlet property="scrollView" destination="gRG-Ys-94p" id="K0v-cS-3VW"/>
|
||||
<outlet property="siteField" destination="56H-xR-09J" id="8no-IN-nsH"/>
|
||||
<outlet property="tipContainer" destination="beo-cJ-jIn" id="BdT-0M-8qC"/>
|
||||
<outlet property="typeControl" destination="e4b-Iv-Pk9" id="S69-yO-7bv"/>
|
||||
<outlet property="userNameField" destination="XAC-Da-lpf" id="XCk-0H-IcI"/>
|
||||
<segue destination="p6o-h3-NRH" kind="unwind" identifier="unwind-popover" unwindAction="unwindToCombined:" id="XI2-ax-Rkg"/>
|
||||
</connections>
|
||||
</viewController>
|
||||
@@ -2082,7 +2086,7 @@ eg. apple.com, rmitchell@twitter.com</string>
|
||||
<pageControl opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" contentHorizontalAlignment="center" contentVerticalAlignment="center" numberOfPages="3" translatesAutoresizingMaskIntoConstraints="NO" id="8A2-ly-WTX">
|
||||
<rect key="frame" x="168" y="630" width="39" height="37"/>
|
||||
</pageControl>
|
||||
<collectionView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" misplaced="YES" pagingEnabled="YES" showsHorizontalScrollIndicator="NO" showsVerticalScrollIndicator="NO" minimumZoomScale="0.0" maximumZoomScale="0.0" dataMode="prototypes" translatesAutoresizingMaskIntoConstraints="NO" id="i2y-lo-HXR">
|
||||
<collectionView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" pagingEnabled="YES" showsHorizontalScrollIndicator="NO" showsVerticalScrollIndicator="NO" minimumZoomScale="0.0" maximumZoomScale="0.0" dataMode="prototypes" translatesAutoresizingMaskIntoConstraints="NO" id="i2y-lo-HXR">
|
||||
<rect key="frame" x="0.0" y="64" width="375" height="476"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
|
||||
<collectionViewFlowLayout key="collectionViewLayout" scrollDirection="horizontal" minimumLineSpacing="0.0" minimumInteritemSpacing="0.0" id="m8O-kY-22j">
|
||||
@@ -2122,13 +2126,13 @@ eg. apple.com, rmitchell@twitter.com</string>
|
||||
<outlet property="delegate" destination="z9O-w0-6oR" id="RcD-gy-C1W"/>
|
||||
</connections>
|
||||
</collectionView>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" misplaced="YES" text="To begin, tap the "New User" icon and add yourself as a user to the application." textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ciw-56-nNy" userLabel="Caption">
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="To begin, tap the "New User" icon and add yourself as a user to the application." textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ciw-56-nNy" userLabel="Caption">
|
||||
<rect key="frame" x="8" y="548" width="359" height="82"/>
|
||||
<fontDescription key="fontDescription" name="Exo2.0-Regular" family="Exo 2.0" pointSize="13"/>
|
||||
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="calibratedRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label hidden="YES" opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" misplaced="YES" lineBreakMode="tailTruncation" numberOfLines="3" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Oop-Ff-gbz" userLabel="Caption Height Strut">
|
||||
<label hidden="YES" opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" lineBreakMode="tailTruncation" numberOfLines="3" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Oop-Ff-gbz" userLabel="Caption Height Strut">
|
||||
<rect key="frame" x="8" y="548" width="1" height="82"/>
|
||||
<string key="text" base64-UTF8="YES">
|
||||
CgoKCgoKCgoKCgoKCg
|
||||
@@ -2308,7 +2312,7 @@ Suspendisse potenti. Etiam ut nisi id augue tempor ultrices et sit amet sapien.
|
||||
<rect key="frame" x="163" y="532" width="51" height="31"/>
|
||||
<color key="onTintColor" red="0.37254901959999998" green="0.3921568627" blue="0.42745098040000001" alpha="1" colorSpace="calibratedRGB"/>
|
||||
</switch>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" misplaced="YES" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="e80-98-V6D">
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="e80-98-V6D">
|
||||
<rect key="frame" x="20" y="137" width="335" height="151.5"/>
|
||||
<string key="text">The right balance between security and convenience is often very personal.
|
||||
|
||||
@@ -2392,7 +2396,7 @@ However, it means that anyone who finds your device unlocked can do the same.</s
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" misplaced="YES" usesAttributedText="YES" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="J90-SQ-ljR">
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" usesAttributedText="YES" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="J90-SQ-ljR">
|
||||
<rect key="frame" x="20" y="136" width="335" height="522.5"/>
|
||||
<attributedString key="attributedText">
|
||||
<fragment content="The passwords generated by this app are not stored but ">
|
||||
@@ -2580,7 +2584,7 @@ See </string>
|
||||
<activityIndicatorView hidden="YES" opaque="NO" tag="2" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" hidesWhenStopped="YES" style="whiteLarge" translatesAutoresizingMaskIntoConstraints="NO" id="cef-sc-aph">
|
||||
<rect key="frame" x="168" y="100" width="37" height="37"/>
|
||||
</activityIndicatorView>
|
||||
<label opaque="NO" userInteractionEnabled="NO" tag="3" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" misplaced="YES" text="✔︎" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="FWu-V6-mLT">
|
||||
<label opaque="NO" userInteractionEnabled="NO" tag="3" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="✔︎" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="FWu-V6-mLT">
|
||||
<rect key="frame" x="200" y="-6" width="92.5" height="132"/>
|
||||
<fontDescription key="fontDescription" name="Exo2.0-Regular" family="Exo 2.0" pointSize="110"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
@@ -2645,7 +2649,7 @@ See </string>
|
||||
<activityIndicatorView hidden="YES" opaque="NO" tag="2" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" hidesWhenStopped="YES" style="whiteLarge" translatesAutoresizingMaskIntoConstraints="NO" id="X2g-Go-2Hz">
|
||||
<rect key="frame" x="169" y="100" width="37" height="37"/>
|
||||
</activityIndicatorView>
|
||||
<label opaque="NO" userInteractionEnabled="NO" tag="2" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" misplaced="YES" text="✔︎" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="N9y-ue-L8d">
|
||||
<label opaque="NO" userInteractionEnabled="NO" tag="2" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="✔︎" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="N9y-ue-L8d">
|
||||
<rect key="frame" x="200" y="-6" width="92.5" height="132"/>
|
||||
<fontDescription key="fontDescription" name="Exo2.0-Regular" family="Exo 2.0" pointSize="110"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
@@ -2691,7 +2695,7 @@ See </string>
|
||||
<rect key="frame" x="0.0" y="0.0" width="287" height="96"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" horizontalCompressionResistancePriority="751" verticalCompressionResistancePriority="751" misplaced="YES" text="iOS Integration" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="12" adjustsLetterSpacingToFitWidth="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Zch-DS-J3I">
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" horizontalCompressionResistancePriority="751" verticalCompressionResistancePriority="751" text="iOS Integration" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="12" adjustsLetterSpacingToFitWidth="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Zch-DS-J3I">
|
||||
<rect key="frame" x="20" y="226" width="244" height="20.5"/>
|
||||
<fontDescription key="fontDescription" name="Exo2.0-Bold" family="Exo 2.0" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
@@ -2710,13 +2714,13 @@ See </string>
|
||||
<activityIndicatorView hidden="YES" opaque="NO" tag="2" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" hidesWhenStopped="YES" style="whiteLarge" translatesAutoresizingMaskIntoConstraints="NO" id="yUe-TX-fli">
|
||||
<rect key="frame" x="169" y="100" width="37" height="37"/>
|
||||
</activityIndicatorView>
|
||||
<label opaque="NO" userInteractionEnabled="NO" tag="2" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" misplaced="YES" text="✔︎" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ec8-P9-KPY">
|
||||
<label opaque="NO" userInteractionEnabled="NO" tag="2" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="✔︎" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ec8-P9-KPY">
|
||||
<rect key="frame" x="200" y="-6" width="92.5" height="132"/>
|
||||
<fontDescription key="fontDescription" name="Exo2.0-Regular" family="Exo 2.0" pointSize="110"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" tag="1" contentMode="left" horizontalHuggingPriority="750" verticalHuggingPriority="251" horizontalCompressionResistancePriority="751" verticalCompressionResistancePriority="751" misplaced="YES" text="Coming Soon" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="3jH-eX-9N2">
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" tag="1" contentMode="left" horizontalHuggingPriority="750" verticalHuggingPriority="251" horizontalCompressionResistancePriority="751" verticalCompressionResistancePriority="751" text="Coming Soon" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="3jH-eX-9N2">
|
||||
<rect key="frame" x="272" y="226" width="83" height="20.5"/>
|
||||
<fontDescription key="fontDescription" name="Exo2.0-Regular" family="Exo 2.0" pointSize="14"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
@@ -2756,7 +2760,7 @@ See </string>
|
||||
<rect key="frame" x="0.0" y="0.0" width="287" height="96"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" horizontalCompressionResistancePriority="751" verticalCompressionResistancePriority="751" misplaced="YES" text="TouchID Login" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="12" adjustsLetterSpacingToFitWidth="YES" translatesAutoresizingMaskIntoConstraints="NO" id="e1D-jp-GBs">
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" horizontalCompressionResistancePriority="751" verticalCompressionResistancePriority="751" text="TouchID Login" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="12" adjustsLetterSpacingToFitWidth="YES" translatesAutoresizingMaskIntoConstraints="NO" id="e1D-jp-GBs">
|
||||
<rect key="frame" x="20" y="226" width="244.5" height="20.5"/>
|
||||
<fontDescription key="fontDescription" name="Exo2.0-Bold" family="Exo 2.0" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
@@ -2775,13 +2779,13 @@ See </string>
|
||||
<activityIndicatorView hidden="YES" opaque="NO" tag="2" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" hidesWhenStopped="YES" style="whiteLarge" translatesAutoresizingMaskIntoConstraints="NO" id="Dv5-t7-lL1">
|
||||
<rect key="frame" x="169" y="100" width="37" height="37"/>
|
||||
</activityIndicatorView>
|
||||
<label opaque="NO" userInteractionEnabled="NO" tag="2" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" misplaced="YES" text="✔︎" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="yZX-ns-8oV">
|
||||
<label opaque="NO" userInteractionEnabled="NO" tag="2" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="✔︎" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="yZX-ns-8oV">
|
||||
<rect key="frame" x="200" y="-6" width="92.5" height="132"/>
|
||||
<fontDescription key="fontDescription" name="Exo2.0-Regular" family="Exo 2.0" pointSize="110"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" tag="1" contentMode="left" horizontalHuggingPriority="750" verticalHuggingPriority="251" horizontalCompressionResistancePriority="751" verticalCompressionResistancePriority="751" misplaced="YES" text="Coming Soon" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ZGg-O6-rsg">
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" tag="1" contentMode="left" horizontalHuggingPriority="750" verticalHuggingPriority="251" horizontalCompressionResistancePriority="751" verticalCompressionResistancePriority="751" text="Coming Soon" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ZGg-O6-rsg">
|
||||
<rect key="frame" x="272" y="226" width="83" height="20.5"/>
|
||||
<fontDescription key="fontDescription" name="Exo2.0-Regular" family="Exo 2.0" pointSize="14"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
@@ -2821,13 +2825,13 @@ See </string>
|
||||
<rect key="frame" x="0.0" y="0.0" width="287" height="96"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" horizontalCompressionResistancePriority="751" verticalCompressionResistancePriority="751" ambiguous="YES" misplaced="YES" text="Fuel Top-Up" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="12" adjustsLetterSpacingToFitWidth="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Jnv-uN-xeg">
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" horizontalCompressionResistancePriority="751" verticalCompressionResistancePriority="751" text="Fuel Top-Up" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="12" adjustsLetterSpacingToFitWidth="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Jnv-uN-xeg">
|
||||
<rect key="frame" x="20" y="226" width="292" height="20.5"/>
|
||||
<fontDescription key="fontDescription" name="Exo2.0-Bold" family="Exo 2.0" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" ambiguous="YES" misplaced="YES" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="fz2-AO-aGW">
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="fz2-AO-aGW">
|
||||
<rect key="frame" x="20" y="254" width="335" height="132"/>
|
||||
<string key="text">You really love Master Password and how it's solving your password problems. You're eager to encourage the maintenance, technical support and development of new features. I am a one-man shop, fuel enables me to allocate more work hours to Master Password.
|
||||
|
||||
@@ -2840,22 +2844,22 @@ UPCOMING:
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" misplaced="YES" image="thumb_fuel.png" translatesAutoresizingMaskIntoConstraints="NO" id="PnG-hP-syh">
|
||||
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="thumb_fuel.png" translatesAutoresizingMaskIntoConstraints="NO" id="PnG-hP-syh">
|
||||
<rect key="frame" x="88" y="20" width="198" height="198"/>
|
||||
</imageView>
|
||||
<activityIndicatorView hidden="YES" opaque="NO" tag="2" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" ambiguous="YES" misplaced="YES" hidesWhenStopped="YES" style="whiteLarge" translatesAutoresizingMaskIntoConstraints="NO" id="eS4-59-Xny">
|
||||
<activityIndicatorView hidden="YES" opaque="NO" tag="2" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" hidesWhenStopped="YES" style="whiteLarge" translatesAutoresizingMaskIntoConstraints="NO" id="eS4-59-Xny">
|
||||
<rect key="frame" x="169" y="100" width="37" height="37"/>
|
||||
</activityIndicatorView>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" tag="1" contentMode="left" horizontalHuggingPriority="750" verticalHuggingPriority="251" horizontalCompressionResistancePriority="751" verticalCompressionResistancePriority="751" ambiguous="YES" misplaced="YES" text="$2.95" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="EbU-DV-fKF">
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" tag="1" contentMode="left" horizontalHuggingPriority="750" verticalHuggingPriority="251" horizontalCompressionResistancePriority="751" verticalCompressionResistancePriority="751" text="$2.95" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="EbU-DV-fKF">
|
||||
<rect key="frame" x="320" y="226" width="34.5" height="20.5"/>
|
||||
<fontDescription key="fontDescription" name="Exo2.0-Regular" family="Exo 2.0" pointSize="14"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" misplaced="YES" image="meter_fuel.png" translatesAutoresizingMaskIntoConstraints="NO" id="aGb-QC-A92">
|
||||
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="meter_fuel.png" translatesAutoresizingMaskIntoConstraints="NO" id="aGb-QC-A92">
|
||||
<rect key="frame" x="261" y="208" width="12" height="10"/>
|
||||
</imageView>
|
||||
<button opaque="NO" contentMode="scaleToFill" misplaced="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="dsR-fr-dY4">
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="dsR-fr-dY4">
|
||||
<rect key="frame" x="20" y="20" width="107" height="44"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="44" id="W6p-kB-VBX"/>
|
||||
@@ -2868,7 +2872,7 @@ UPCOMING:
|
||||
<action selector="toggleFuelConsumption:" destination="pdl-xv-zjX" eventType="touchUpInside" id="NkB-Dy-IeY"/>
|
||||
</connections>
|
||||
</button>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" misplaced="YES" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="kYb-j4-32C">
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="kYb-j4-32C">
|
||||
<rect key="frame" x="20" y="64" width="117.5" height="26.5"/>
|
||||
<string key="text">fuel left: 0.3 work hours
|
||||
invested: 3.7 work hours</string>
|
||||
@@ -2920,7 +2924,7 @@ invested: 3.7 work hours</string>
|
||||
<activityIndicatorView opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" hidesWhenStopped="YES" animating="YES" style="whiteLarge" translatesAutoresizingMaskIntoConstraints="NO" id="Vjt-7c-BJ4">
|
||||
<rect key="frame" x="169" y="20" width="37" height="37"/>
|
||||
</activityIndicatorView>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" horizontalCompressionResistancePriority="751" verticalCompressionResistancePriority="751" misplaced="YES" text="Loading products..." textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="12" adjustsLetterSpacingToFitWidth="YES" translatesAutoresizingMaskIntoConstraints="NO" id="ARC-xH-0U0">
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" horizontalCompressionResistancePriority="751" verticalCompressionResistancePriority="751" text="Loading products..." textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="12" adjustsLetterSpacingToFitWidth="YES" translatesAutoresizingMaskIntoConstraints="NO" id="ARC-xH-0U0">
|
||||
<rect key="frame" x="114.5" y="97" width="145.5" height="20.5"/>
|
||||
<fontDescription key="fontDescription" name="Exo2.0-Regular" family="Exo 2.0" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
@@ -2944,7 +2948,7 @@ invested: 3.7 work hours</string>
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="152"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<button opaque="NO" contentMode="scaleToFill" verticalHuggingPriority="1000" verticalCompressionResistancePriority="1000" ambiguous="YES" misplaced="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="IOk-WZ-HJ8">
|
||||
<button opaque="NO" contentMode="scaleToFill" verticalHuggingPriority="1000" verticalCompressionResistancePriority="1000" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="IOk-WZ-HJ8">
|
||||
<rect key="frame" x="20" y="58" width="335" height="26"/>
|
||||
<fontDescription key="fontDescription" name="Exo2.0-Regular" family="Exo 2.0" pointSize="11"/>
|
||||
<state key="normal" title="Restore Previous Purchases">
|
||||
@@ -2954,7 +2958,7 @@ invested: 3.7 work hours</string>
|
||||
<action selector="restorePurchases:" destination="pdl-xv-zjX" eventType="touchUpInside" id="lKu-PL-PfX"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" verticalHuggingPriority="1000" verticalCompressionResistancePriority="1000" ambiguous="YES" misplaced="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="bCe-a3-cDC">
|
||||
<button opaque="NO" contentMode="scaleToFill" verticalHuggingPriority="1000" verticalCompressionResistancePriority="1000" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="bCe-a3-cDC">
|
||||
<rect key="frame" x="8" y="24" width="359" height="26"/>
|
||||
<fontDescription key="fontDescription" name="Exo2.0-Regular" family="Exo 2.0" pointSize="11"/>
|
||||
<state key="normal" title="Send Thanks">
|
||||
@@ -2964,7 +2968,7 @@ invested: 3.7 work hours</string>
|
||||
<action selector="sendThanks:" destination="pdl-xv-zjX" eventType="touchUpInside" id="r5l-3U-jCA"/>
|
||||
</connections>
|
||||
</button>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="1000" verticalCompressionResistancePriority="1000" ambiguous="YES" text="© 2012-2014, Maarten Billemont (lhunath)" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="DxV-2L-bxL">
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="1000" verticalCompressionResistancePriority="1000" text="© 2012-2014, Maarten Billemont (lhunath)" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="DxV-2L-bxL">
|
||||
<rect key="frame" x="20" y="4" width="335" height="12"/>
|
||||
<fontDescription key="fontDescription" name="Exo2.0-Thin" family="Exo 2.0" pointSize="10"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
@@ -3042,13 +3046,13 @@ invested: 3.7 work hours</string>
|
||||
<rect key="frame" x="0.0" y="0.0" width="287" height="96"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="252" verticalHuggingPriority="251" horizontalCompressionResistancePriority="751" misplaced="YES" text="Answer for lyndir.com:" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Tal-1I-HQw" userLabel="Title Label">
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="252" verticalHuggingPriority="251" horizontalCompressionResistancePriority="751" text="Answer for lyndir.com:" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Tal-1I-HQw" userLabel="Title Label">
|
||||
<rect key="frame" x="8" y="8" width="180" height="20.5"/>
|
||||
<fontDescription key="fontDescription" name="Exo2.0-Bold" family="Exo 2.0" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" misplaced="YES" enabled="NO" contentHorizontalAlignment="left" contentVerticalAlignment="center" text="hok petwuvaqu xixo" textAlignment="center" minimumFontSize="17" clearButtonMode="whileEditing" translatesAutoresizingMaskIntoConstraints="NO" id="GfC-j4-Qx7" userLabel="Answer Field">
|
||||
<textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" enabled="NO" contentHorizontalAlignment="left" contentVerticalAlignment="center" text="hok petwuvaqu xixo" textAlignment="center" minimumFontSize="17" clearButtonMode="whileEditing" translatesAutoresizingMaskIntoConstraints="NO" id="GfC-j4-Qx7" userLabel="Answer Field">
|
||||
<rect key="frame" x="8" y="48" width="359" height="43"/>
|
||||
<color key="textColor" red="0.40000000600000002" green="0.80000001190000003" blue="1" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<fontDescription key="fontDescription" name="SourceCodePro-Black" family="Source Code Pro" pointSize="24"/>
|
||||
@@ -3132,7 +3136,7 @@ invested: 3.7 work hours</string>
|
||||
<rect key="frame" x="0.0" y="0.0" width="287" height="96"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" misplaced="YES" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" placeholder="mother" textAlignment="center" minimumFontSize="14" clearButtonMode="unlessEditing" translatesAutoresizingMaskIntoConstraints="NO" id="T2F-PD-Nw8" userLabel="Question Field">
|
||||
<textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" placeholder="mother" textAlignment="center" minimumFontSize="14" clearButtonMode="unlessEditing" translatesAutoresizingMaskIntoConstraints="NO" id="T2F-PD-Nw8" userLabel="Question Field">
|
||||
<rect key="frame" x="8" y="19" width="359" height="30"/>
|
||||
<color key="backgroundColor" red="0.37254901959999998" green="0.3921568627" blue="0.42745098040000001" alpha="0.5" colorSpace="calibratedRGB"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
@@ -3143,13 +3147,13 @@ invested: 3.7 work hours</string>
|
||||
<outlet property="delegate" destination="iFm-3w-hOv" id="olS-Rx-56Y"/>
|
||||
</connections>
|
||||
</textField>
|
||||
<textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" misplaced="YES" enabled="NO" contentHorizontalAlignment="left" contentVerticalAlignment="center" text="pifm gup balvabi yiz" textAlignment="center" minimumFontSize="17" clearButtonMode="whileEditing" translatesAutoresizingMaskIntoConstraints="NO" id="3xA-ez-efa" userLabel="Answer Field">
|
||||
<textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" enabled="NO" contentHorizontalAlignment="left" contentVerticalAlignment="center" text="pifm gup balvabi yiz" textAlignment="center" minimumFontSize="17" clearButtonMode="whileEditing" translatesAutoresizingMaskIntoConstraints="NO" id="3xA-ez-efa" userLabel="Answer Field">
|
||||
<rect key="frame" x="20" y="90" width="335" height="31"/>
|
||||
<color key="textColor" red="0.40000000600000002" green="0.80000001190000003" blue="1" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<fontDescription key="fontDescription" name="SourceCodePro-Black" family="Source Code Pro" pointSize="24"/>
|
||||
<textInputTraits key="textInputTraits" autocorrectionType="no" keyboardType="alphabet" keyboardAppearance="alert" returnKeyType="next"/>
|
||||
</textField>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" verticalCompressionResistancePriority="751" misplaced="YES" text="Enter the single most significant word in the question above." textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Qqg-Ny-7Po">
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" verticalCompressionResistancePriority="751" text="Enter the single most significant word in the question above." textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Qqg-Ny-7Po">
|
||||
<rect key="frame" x="8" y="57" width="359" height="13.5"/>
|
||||
<fontDescription key="fontDescription" name="Exo2.0-Thin" family="Exo 2.0" pointSize="11"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
@@ -3242,7 +3246,7 @@ invested: 3.7 work hours</string>
|
||||
<rect key="frame" x="163" y="532" width="51" height="31"/>
|
||||
<color key="onTintColor" red="0.37254901959999998" green="0.3921568627" blue="0.42745098040000001" alpha="1" colorSpace="calibratedRGB"/>
|
||||
</switch>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" misplaced="YES" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="yx2-Eh-hM0">
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="yx2-Eh-hM0">
|
||||
<rect key="frame" x="20" y="137" width="335" height="202"/>
|
||||
<string key="text">To make it easy for you to recognize and copy passwords manually using a keyboard or other means, Master Password makes your site passwords visible on your screen by default.
|
||||
|
||||
@@ -3299,6 +3303,198 @@ You can temporarily reveal a password by holding your finger down on the site's
|
||||
</objects>
|
||||
<point key="canvasLocation" x="-152.5" y="2175.5"/>
|
||||
</scene>
|
||||
<!--Message View Controller-->
|
||||
<scene sceneID="5u0-2d-uNC">
|
||||
<objects>
|
||||
<viewController id="Foa-Er-RBr" customClass="MPMessageViewController" sceneMemberID="viewController">
|
||||
<layoutGuides>
|
||||
<viewControllerLayoutGuide type="top" id="TXr-gp-hra"/>
|
||||
<viewControllerLayoutGuide type="bottom" id="Ol8-pt-KDt"/>
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="LFy-pG-nMQ" userLabel="Root">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<toolbar opaque="NO" clearsContextBeforeDrawing="NO" contentMode="scaleToFill" barStyle="black" translatesAutoresizingMaskIntoConstraints="NO" id="aNU-Nq-clY">
|
||||
<rect key="frame" x="0.0" y="341" width="375" height="325.5"/>
|
||||
<items/>
|
||||
</toolbar>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="252" verticalHuggingPriority="251" horizontalCompressionResistancePriority="751" text="You have sites that can be upgraded." lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="3Qi-GN-vhQ" userLabel="Title Label">
|
||||
<rect key="frame" x="16" y="361" width="343" height="20.5"/>
|
||||
<fontDescription key="fontDescription" name="Exo2.0-Bold" family="Exo 2.0" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" verticalCompressionResistancePriority="1000" usesAttributedText="YES" lineBreakMode="tailTruncation" numberOfLines="0" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="rnV-lT-dTT">
|
||||
<rect key="frame" x="16" y="390" width="343" height="216.5"/>
|
||||
<attributedString key="attributedText">
|
||||
<fragment>
|
||||
<string key="content">Upgrading a site allows it to take advantage of
|
||||
the latest improvements in the Master Password algorithm.
|
||||
</string>
|
||||
<attributes>
|
||||
<color key="NSBackgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
|
||||
<color key="NSColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<font key="NSFont" size="12" name="Exo2.0-Regular"/>
|
||||
<paragraphStyle key="NSParagraphStyle" alignment="left" lineBreakMode="wordWrapping" baseWritingDirection="natural"/>
|
||||
</attributes>
|
||||
</fragment>
|
||||
<fragment>
|
||||
<string key="content">
|
||||
When you upgrade a site, </string>
|
||||
<attributes>
|
||||
<color key="NSBackgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
|
||||
<color key="NSColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<font key="NSFont" size="12" name="Exo2.0-Regular"/>
|
||||
<paragraphStyle key="NSParagraphStyle" alignment="justified" lineBreakMode="wordWrapping" baseWritingDirection="natural"/>
|
||||
</attributes>
|
||||
</fragment>
|
||||
<fragment content="a new and stronger password will be generated for it">
|
||||
<attributes>
|
||||
<color key="NSBackgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
|
||||
<color key="NSColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<font key="NSFont" size="12" name="Exo2.0-Bold"/>
|
||||
<paragraphStyle key="NSParagraphStyle" alignment="justified" lineBreakMode="wordWrapping" baseWritingDirection="natural"/>
|
||||
</attributes>
|
||||
</fragment>
|
||||
<fragment>
|
||||
<string key="content">. To upgrade a site, first log into the site, navigate to your account preferences where you can change the site's password. Make sure you fill in any "current password" fields on the website first, then press the upgrade button here to get your new site password.
|
||||
</string>
|
||||
<attributes>
|
||||
<color key="NSBackgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
|
||||
<color key="NSColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<font key="NSFont" size="12" name="Exo2.0-Regular"/>
|
||||
<paragraphStyle key="NSParagraphStyle" alignment="justified" lineBreakMode="wordWrapping" baseWritingDirection="natural"/>
|
||||
</attributes>
|
||||
</fragment>
|
||||
<fragment>
|
||||
<string key="content">
|
||||
You can then update your site's account with the new and stronger password.
|
||||
|
||||
</string>
|
||||
<attributes>
|
||||
<color key="NSBackgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
|
||||
<color key="NSColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<font key="NSFont" size="12" name="Exo2.0-Regular"/>
|
||||
<paragraphStyle key="NSParagraphStyle" alignment="left" lineBreakMode="wordWrapping" baseWritingDirection="natural"/>
|
||||
</attributes>
|
||||
</fragment>
|
||||
<fragment content="The upgrade button can be found in the site's settings and looks like this:">
|
||||
<attributes>
|
||||
<color key="NSBackgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
|
||||
<color key="NSColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<font key="NSFont" size="12" name="Exo2.0-Regular"/>
|
||||
<paragraphStyle key="NSParagraphStyle" alignment="center" lineBreakMode="wordWrapping" baseWritingDirection="natural"/>
|
||||
</attributes>
|
||||
</fragment>
|
||||
</attributedString>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="kPc-D9-n24" userLabel="Info">
|
||||
<rect key="frame" x="16" y="615" width="343" height="44"/>
|
||||
<subviews>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="3Ld-qt-w7n" userLabel="Stud">
|
||||
<rect key="frame" x="0.0" y="0.0" width="120.5" height="44"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
|
||||
</view>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="wkN-yK-VVb" userLabel="Stud">
|
||||
<rect key="frame" x="222.5" y="0.0" width="120.5" height="44"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
|
||||
</view>
|
||||
<button opaque="NO" alpha="0.5" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" showsTouchWhenHighlighted="YES" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="hMB-S7-ldC" userLabel="Site Mode">
|
||||
<rect key="frame" x="120.5" y="0.0" width="44" height="44"/>
|
||||
<accessibility key="accessibilityConfiguration" hint="Upgrades the password."/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="44" id="HIL-wf-3AX"/>
|
||||
<constraint firstAttribute="width" constant="44" id="WBC-Zk-skQ"/>
|
||||
</constraints>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="15"/>
|
||||
<inset key="contentEdgeInsets" minX="6" minY="6" maxX="6" maxY="6"/>
|
||||
<state key="normal" image="icon_tools.png">
|
||||
<color key="titleColor" red="0.19607843459999999" green="0.30980393290000002" blue="0.52156865600000002" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<color key="titleShadowColor" white="0.5" alpha="1" colorSpace="calibratedWhite"/>
|
||||
</state>
|
||||
<state key="selected" image="icon_thumbs-up.png"/>
|
||||
<state key="highlighted">
|
||||
<color key="titleColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
</state>
|
||||
</button>
|
||||
<button opaque="NO" alpha="0.5" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" showsTouchWhenHighlighted="YES" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="xbh-Rx-L5u" userLabel="Upgrade">
|
||||
<rect key="frame" x="179" y="0.0" width="44" height="44"/>
|
||||
<accessibility key="accessibilityConfiguration" hint="Upgrades the password."/>
|
||||
<gestureRecognizers/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="44" id="O4i-vP-Uf4"/>
|
||||
<constraint firstAttribute="width" constant="44" id="kMf-yo-8c5"/>
|
||||
</constraints>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="15"/>
|
||||
<inset key="contentEdgeInsets" minX="6" minY="6" maxX="6" maxY="6"/>
|
||||
<state key="normal" image="icon_up.png">
|
||||
<color key="titleColor" red="0.19607843459999999" green="0.30980393290000002" blue="0.52156865600000002" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<color key="titleShadowColor" white="0.5" alpha="1" colorSpace="calibratedWhite"/>
|
||||
</state>
|
||||
<state key="highlighted">
|
||||
<color key="titleColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
</state>
|
||||
</button>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="➙" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="kta-kZ-FbX">
|
||||
<rect key="frame" x="164.5" y="12" width="14.5" height="20.5"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="calibratedRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
|
||||
<constraints>
|
||||
<constraint firstItem="3Ld-qt-w7n" firstAttribute="leading" secondItem="kPc-D9-n24" secondAttribute="leading" id="1BM-jA-NHO"/>
|
||||
<constraint firstAttribute="bottom" secondItem="xbh-Rx-L5u" secondAttribute="bottom" id="1wo-8P-DpL"/>
|
||||
<constraint firstItem="3Ld-qt-w7n" firstAttribute="top" secondItem="kPc-D9-n24" secondAttribute="top" id="2v6-tn-vzT"/>
|
||||
<constraint firstItem="wkN-yK-VVb" firstAttribute="width" secondItem="3Ld-qt-w7n" secondAttribute="width" id="AHa-IC-DwT"/>
|
||||
<constraint firstAttribute="bottom" secondItem="3Ld-qt-w7n" secondAttribute="bottom" id="Cu9-Dt-vRu"/>
|
||||
<constraint firstAttribute="centerY" secondItem="kta-kZ-FbX" secondAttribute="centerY" id="EgL-EZ-xNL"/>
|
||||
<constraint firstItem="hMB-S7-ldC" firstAttribute="top" secondItem="kPc-D9-n24" secondAttribute="top" id="HVS-r6-AFH"/>
|
||||
<constraint firstItem="xbh-Rx-L5u" firstAttribute="leading" secondItem="kta-kZ-FbX" secondAttribute="trailing" id="Ksm-jT-qOL"/>
|
||||
<constraint firstAttribute="bottom" secondItem="wkN-yK-VVb" secondAttribute="bottom" id="LyD-YV-Yoh"/>
|
||||
<constraint firstItem="kta-kZ-FbX" firstAttribute="leading" secondItem="hMB-S7-ldC" secondAttribute="trailing" id="Mqi-c7-yB1"/>
|
||||
<constraint firstItem="wkN-yK-VVb" firstAttribute="leading" secondItem="xbh-Rx-L5u" secondAttribute="trailing" id="TYI-ve-ABO"/>
|
||||
<constraint firstItem="xbh-Rx-L5u" firstAttribute="top" secondItem="kPc-D9-n24" secondAttribute="top" id="aTm-i3-Niu"/>
|
||||
<constraint firstAttribute="trailing" secondItem="wkN-yK-VVb" secondAttribute="trailing" id="dY8-BI-M6Q"/>
|
||||
<constraint firstItem="wkN-yK-VVb" firstAttribute="top" secondItem="kPc-D9-n24" secondAttribute="top" id="hUt-8M-m1o"/>
|
||||
<constraint firstAttribute="bottom" secondItem="hMB-S7-ldC" secondAttribute="bottom" id="t4s-92-9p4"/>
|
||||
<constraint firstItem="hMB-S7-ldC" firstAttribute="leading" secondItem="3Ld-qt-w7n" secondAttribute="trailing" id="wiD-xK-udZ"/>
|
||||
</constraints>
|
||||
</view>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="0.0" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="trailingMargin" secondItem="3Qi-GN-vhQ" secondAttribute="trailing" id="01x-aR-50B"/>
|
||||
<constraint firstItem="rnV-lT-dTT" firstAttribute="leading" secondItem="LFy-pG-nMQ" secondAttribute="leadingMargin" id="5sg-Cn-A5f"/>
|
||||
<constraint firstItem="3Qi-GN-vhQ" firstAttribute="leading" secondItem="LFy-pG-nMQ" secondAttribute="leadingMargin" id="B93-hf-itk"/>
|
||||
<constraint firstItem="kPc-D9-n24" firstAttribute="top" secondItem="rnV-lT-dTT" secondAttribute="bottom" constant="8" id="Jd6-5C-J2q"/>
|
||||
<constraint firstItem="aNU-Nq-clY" firstAttribute="leading" secondItem="LFy-pG-nMQ" secondAttribute="leading" id="Jo0-Nd-LRa"/>
|
||||
<constraint firstAttribute="trailingMargin" secondItem="rnV-lT-dTT" secondAttribute="trailing" id="LsP-s5-0Cd"/>
|
||||
<constraint firstAttribute="trailing" secondItem="aNU-Nq-clY" secondAttribute="trailing" id="OOa-cS-g9q"/>
|
||||
<constraint firstItem="Ol8-pt-KDt" firstAttribute="top" secondItem="aNU-Nq-clY" secondAttribute="bottom" id="T27-2g-NuB"/>
|
||||
<constraint firstAttribute="trailingMargin" secondItem="kPc-D9-n24" secondAttribute="trailing" id="gVh-xu-hF3"/>
|
||||
<constraint firstItem="rnV-lT-dTT" firstAttribute="top" secondItem="3Qi-GN-vhQ" secondAttribute="bottom" constant="8" id="gz3-TT-ylK"/>
|
||||
<constraint firstItem="3Qi-GN-vhQ" firstAttribute="top" secondItem="aNU-Nq-clY" secondAttribute="top" constant="20" id="ozH-hI-ID9"/>
|
||||
<constraint firstItem="kPc-D9-n24" firstAttribute="leading" secondItem="LFy-pG-nMQ" secondAttribute="leadingMargin" id="pTx-Ay-1WY"/>
|
||||
<constraint firstItem="Ol8-pt-KDt" firstAttribute="top" secondItem="kPc-D9-n24" secondAttribute="bottom" constant="8" id="sNM-dC-2ES"/>
|
||||
</constraints>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="boolean" keyPath="ignoreTouches" value="YES"/>
|
||||
</userDefinedRuntimeAttributes>
|
||||
</view>
|
||||
<connections>
|
||||
<outlet property="infoView" destination="kPc-D9-n24" id="e7c-dG-hqT"/>
|
||||
<outlet property="messageLabel" destination="rnV-lT-dTT" id="AdF-oe-XmL"/>
|
||||
<outlet property="titleLabel" destination="3Qi-GN-vhQ" id="w2W-Tk-eMH"/>
|
||||
</connections>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="gdm-wd-QhM" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="1408.5" y="2995.5"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
<resources>
|
||||
<image name="avatar-0.png" width="110" height="110"/>
|
||||
|
||||
@@ -51,10 +51,11 @@ setSettingWithTitle "Copyright" "$(getPlistWithKey NSHumanReadableCopyright)"
|
||||
|
||||
if [[ $DEPLOYMENT_LOCATION = YES ]]; then
|
||||
# This build is a release. Do some release checks.
|
||||
crashlyticsPlist="$BUILT_PRODUCTS_DIR/$CONTENTS_FOLDER_PATH/Crashlytics.plist"
|
||||
passed=1
|
||||
[[ $description != *-dirty ]] || \
|
||||
{ passed=0; err 'ERROR: Cannot release a dirty version, first commit any changes.'; }
|
||||
[[ $(PlistBuddy -c "Print :'API Key'" "$BUILT_PRODUCTS_DIR/$CONTENTS_FOLDER_PATH/Crashlytics.plist") ]] || \
|
||||
[[ -r "$crashlyticsPlist" && $(PlistBuddy -c "Print :'API Key'" "$crashlyticsPlist" 2>/dev/null) ]] || \
|
||||
{ passed=0; err 'ERROR: Cannot release: Crashlytics API key is missing.'; }
|
||||
(( passed )) || \
|
||||
{ ftl "Failed to pass release checks. Fix the above errors and re-try. Aborting."; exit 1; }
|
||||
|
||||
|
Before Width: | Height: | Size: 284 KiB After Width: | Height: | Size: 1.6 MiB |
|
Before Width: | Height: | Size: 111 KiB After Width: | Height: | Size: 79 KiB |
BIN
Site/2013-05/img/mac-homebrew.png
Normal file
|
After Width: | Height: | Size: 364 KiB |
@@ -80,9 +80,10 @@
|
||||
<h4>
|
||||
Get it for:
|
||||
<a title="iPhone, iPad, iPod touch" href="https://itunes.apple.com/app/id510296984" onclick="_gaq.push(['_trackPageview', '/outbound/ios']);">iPhone / iPad<img class="popup" src="img/ios.png" /></a> |
|
||||
<a title="Mac (graphical interface)" href="https://itunes.apple.com/app/id662763204" onclick="_gaq.push(['_trackPageview', '/outbound/gui/mac']);">Mac<img class="popup border" src="img/mac-gui.png" /></a> |
|
||||
Mac <a title="Mac (graphical interface)" href="https://itunes.apple.com/app/id662763204" onclick="_gaq.push(['_trackPageview', '/outbound/gui/mac']);">App Store<img class="popup border" src="img/mac-gui.png" /></a>,
|
||||
<a title="Mac (command-line interface)" href="#">Homebrew<img class="popup" src="img/mac-homebrew.png" /></a> |
|
||||
<a title="Mac, Linux, UNIX, Windows (graphical interface)" href="https://ssl.masterpasswordapp.com/masterpassword-gui.jar" onclick="_gaq.push(['_trackPageview', '/outbound/gui/java']);">Desktop (Java)<img class="popup" src="img/java-gui.png" /></a> |
|
||||
Terminal (<a title="Mac, Linux, POSIX (command line interface)" href="https://ssl.masterpasswordapp.com/masterpassword-cli.tar.gz" onclick="_gaq.push(['_trackPageview', '/outbound/cli/c']);">Native C<img class="popup" src="img/c-cli.png" /></a>) |
|
||||
<a title="Mac, Linux, POSIX (command line interface)" href="https://ssl.masterpasswordapp.com/masterpassword-cli.tar.gz" onclick="_gaq.push(['_trackPageview', '/outbound/cli/c']);">Terminal (C)<img class="popup" src="img/c-cli.png" /></a> |
|
||||
<a title="Android" href="https://ssl.masterpasswordapp.com/masterpassword-android.apk" onclick="_gaq.push(['_trackPageview', '/outbound/android']);">Android (Beta)<img class="popup" src="img/android.png" /></a> |
|
||||
<a title="JavaScript" href="https://js.masterpasswordapp.com/" onclick="_gaq.push(['_trackPageview', '/outbound/js']);">Web (Beta)<img class="popup border" src="img/web.png" /></a>
|
||||
</h4>
|
||||
|
||||