2
0

Compare commits

..

30 Commits

Author SHA1 Message Date
Maarten Billemont
6fae0fe425 2.7-java-6 2018-08-27 13:11:32 -04:00
Maarten Billemont
0558176847 Fix dependencies in native mpw DLLs. 2018-08-27 13:07:38 -04:00
Maarten Billemont
c553201cda Small site update. 2018-08-26 20:41:27 -04:00
Maarten Billemont
665be9494b Release masterpassword-gui-2.7.5 2018-08-26 18:19:01 -04:00
Maarten Billemont
5ca81b4aa7 Don't use daemon when setting release passwords in environment. 2018-08-25 11:57:36 -04:00
Maarten Billemont
3cbb063926 Refactor Native to try and load other architectures. 2018-08-24 16:33:14 -04:00
Maarten Billemont
d5551c8c8c Key calculator and access to the full algorithm. 2018-08-24 13:48:53 -04:00
Maarten Billemont
9a40e52d53 2.7-java-5 2018-08-19 16:13:39 -04:00
Maarten Billemont
6f0d768e69 Saving custom passwords and logins. 2018-08-19 16:11:43 -04:00
Maarten Billemont
40fdc8d248 Fix initialization dependency cycle & load files on init. 2018-08-18 13:43:41 -04:00
Maarten Billemont
6b9e1b8cb8 Standard label font & fix warnings. 2018-08-14 12:06:04 -04:00
Maarten Billemont
f41cdb8742 Site security questions and copy login name. 2018-08-13 17:53:31 -04:00
Maarten Billemont
10c6d203b8 Implement security answers & immediate site lookup. 2018-08-07 00:07:16 -04:00
Maarten Billemont
7d1aa9c9f4 Release 2.7.4 2018-08-02 12:34:24 -04:00
Maarten Billemont
c26281e3b7 2.7-java-4 2018-08-02 12:20:45 -04:00
Maarten Billemont
f0b1f0c9e0 Build for older glibc. 2018-08-02 12:19:49 -04:00
Maarten Billemont
9682efc7c9 Release masterpassword-gui-2.7.3 2018-08-02 01:44:50 -04:00
Maarten Billemont
1264cad377 2.7-java-3 2018-08-02 01:37:37 -04:00
Maarten Billemont
d185a0af14 Add mpw native binary for windows 32-bit. 2018-08-02 01:37:10 -04:00
Maarten Billemont
4275a6cc61 Fix build on Windows. 2018-08-02 01:32:55 -04:00
Maarten Billemont
c94ff429e8 Switch linux build of libmpw to debian for glibc instead of musl libc. 2018-08-01 20:13:42 -04:00
Maarten Billemont
00744cb264 Statically link the mpw library. 2018-08-01 14:20:47 -04:00
Maarten Billemont
7202fe6d1d Bump site for release of masterpassword-gui-2.7.2.jar 2018-07-31 15:35:57 -04:00
Maarten Billemont
63b4d9cd2e 2.7-java-2 2018-07-31 15:32:13 -04:00
Maarten Billemont
36a7c7f423 Clean up iconifying on copy. 2018-07-31 15:31:47 -04:00
Maarten Billemont
c2c4fb18bf Help improvements. 2018-07-31 15:16:33 -04:00
Maarten Billemont
3fc8acba70 Global hotkey, iconifying and application activation, help text. 2018-07-31 14:55:19 -04:00
Maarten Billemont
f5c0c4d787 Fix offsetting local time back to UTC. 2018-07-31 12:44:49 -04:00
Maarten Billemont
86775f1c75 Standardize epoch time calculation. 2018-07-31 09:27:41 -04:00
Maarten Billemont
2bb190f49a Bump site for masterpassword-gui-2.7.1 release. 2018-07-29 15:38:54 -04:00
66 changed files with 1131 additions and 389 deletions

1
.gitignore vendored
View File

@@ -7,6 +7,7 @@ Thumbs.db
*.iml *.iml
*.ipr *.ipr
*.iws *.iws
out
# Xcode IDE # Xcode IDE
xcuserdata/ xcuserdata/

View File

@@ -1,9 +1,12 @@
FROM alpine FROM debian:stable-slim
# For i386 # For i386
#FROM i386/alpine #FROM i386/debian:stable-slim
#ENTRYPOINT ["linux32", "--"] #ENTRYPOINT ["linux32", "--"]
RUN apk add --no-cache git libtool automake autoconf make g++ bash openjdk8 # https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=863199
RUN mkdir -p /usr/share/man/man1
RUN apt-get update && apt-get install -y default-jdk-headless git-core bash libtool automake autoconf make g++
RUN git clone --depth=3 $(: --shallow-submodules) --recurse-submodules https://gitlab.com/MasterPassword/MasterPassword.git /mpw RUN git clone --depth=3 $(: --shallow-submodules) --recurse-submodules https://gitlab.com/MasterPassword/MasterPassword.git /mpw
RUN cd /mpw/gradle && ./gradlew -i clean build RUN cd /mpw/gradle && ./gradlew -i clean build

14
gradle/.idea/misc.xml generated
View File

@@ -5,21 +5,31 @@
<option name="myDefaultNotNull" value="javax.annotation.Nonnull" /> <option name="myDefaultNotNull" value="javax.annotation.Nonnull" />
<option name="myNullables"> <option name="myNullables">
<value> <value>
<list size="4"> <list size="9">
<item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.Nullable" /> <item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.Nullable" />
<item index="1" class="java.lang.String" itemvalue="javax.annotation.Nullable" /> <item index="1" class="java.lang.String" itemvalue="javax.annotation.Nullable" />
<item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.Nullable" /> <item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.Nullable" />
<item index="3" class="java.lang.String" itemvalue="android.support.annotation.Nullable" /> <item index="3" class="java.lang.String" itemvalue="android.support.annotation.Nullable" />
<item index="4" class="java.lang.String" itemvalue="javax.annotation.CheckForNull" />
<item index="5" class="java.lang.String" itemvalue="androidx.annotation.Nullable" />
<item index="6" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.qual.Nullable" />
<item index="7" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NullableDecl" />
<item index="8" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NullableType" />
</list> </list>
</value> </value>
</option> </option>
<option name="myNotNulls"> <option name="myNotNulls">
<value> <value>
<list size="4"> <list size="9">
<item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.NotNull" /> <item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.NotNull" />
<item index="1" class="java.lang.String" itemvalue="javax.annotation.Nonnull" /> <item index="1" class="java.lang.String" itemvalue="javax.annotation.Nonnull" />
<item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.NonNull" /> <item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.NonNull" />
<item index="3" class="java.lang.String" itemvalue="android.support.annotation.NonNull" /> <item index="3" class="java.lang.String" itemvalue="android.support.annotation.NonNull" />
<item index="4" class="java.lang.String" itemvalue="javax.validation.constraints.NotNull" />
<item index="5" class="java.lang.String" itemvalue="androidx.annotation.NonNull" />
<item index="6" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.qual.NonNull" />
<item index="7" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NonNullDecl" />
<item index="8" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NonNullType" />
</list> </list>
</value> </value>
</option> </option>

View File

@@ -2,11 +2,11 @@ To build a release distribution:
Desktop: Desktop:
STORE_PW=$(mpw masterpassword.keystore) KEY_PW_DESKTOP=$(mpw masterpassword-desktop) gradle clean masterpassword-gui:shadowJar STORE_PW=$(mpw masterpassword.keystore) KEY_PW_DESKTOP=$(mpw masterpassword-desktop) gradle --no-daemon clean masterpassword-gui:shadowJar
Android: Android:
STORE_PW=$(mpw masterpassword.keystore) KEY_PW_ANDROID=$(mpw masterpassword-android) gradle clean masterpassword-android:assembleRelease STORE_PW=$(mpw masterpassword.keystore) KEY_PW_ANDROID=$(mpw masterpassword-android) gradle --no-daemon clean masterpassword-android:assembleRelease
Note: Note:

View File

@@ -2,7 +2,7 @@ allprojects {
apply plugin: 'findbugs' apply plugin: 'findbugs'
group = 'com.lyndir.masterpassword' group = 'com.lyndir.masterpassword'
version = '2.7.1' version = '2.7.6'
tasks.withType( JavaCompile ) { tasks.withType( JavaCompile ) {
options.encoding = 'UTF-8' options.encoding = 'UTF-8'

View File

@@ -32,8 +32,15 @@ PATH+=:/usr/local/bin
needs() { _needs "$@"; } needs() { _needs "$@"; }
_needs() { _needs() {
local failed=0 local failed=0
for tool; do for spec; do
hash "$tool" || { echo >&2 "Missing: $tool. Please install this tool."; (( failed++ )); } IFS=: read pkg tools <<< "$spec"
IFS=, read -a tools <<< "${tools:-$pkg}"
for tool in "${tools[@]}"; do
hash "$tool" 2>/dev/null && continue 2
done
echo >&2 "Missing: $pkg. Please install this package."
(( failed++ ))
done done
return $failed return $failed
@@ -51,15 +58,15 @@ _initialize() {
# #
# Check if all tools needed for the default implementations are available. # Check if all tools needed for the default implementations are available.
# #
# By default, this will check for `libtool` (for libtoolize), `automake` (for aclocal) and `autoconf` (for autoreconf). # By default, this will check for `libtool` (for libtoolize), `automake` (for aclocal), `autoconf` (for autoreconf) and make.
initialize_needs() { _initialize_needs "$@"; } initialize_needs() { _initialize_needs "$@"; }
_initialize_needs() { _initialize_needs() {
if [[ $platform = windows ]]; then if [[ $platform = windows ]]; then
needs cmd needs cmd
export VSINSTALLDIR="${VSINSTALLDIR:-$(cd "$(cygpath -F 0x002a)/Microsoft Visual Studio"/*/*/Common7/.. && pwd)}" export VSINSTALLDIR="${VSINSTALLDIR:-$(cd "$(cygpath -F 0x002a)/Microsoft Visual Studio"/*/*/Common7/.. && pwd)}"
[[ -e "$VSINSTALLDIR/Common7/Tools/VsMSBuildCmd.bat" ]] || { echo >&2 "Missing: msbuild. Please install 'Build Tools for Visual Studio'."; return 1; } [[ -e "$VSINSTALLDIR/Common7/Tools/VsMSBuildCmd.bat" ]] || { echo >&2 "Missing: msbuild. Please install 'Build Tools for Visual Studio'. See https://visualstudio.microsoft.com/downloads/?q=build+tools"; return 1; }
else else
needs libtool automake autoconf needs libtool:libtoolize,glibtoolize automake autoconf make
fi fi
} }
@@ -195,7 +202,7 @@ _target_build() {
if [[ $platform = windows ]]; then if [[ $platform = windows ]]; then
# I cannot for the life of me figure out how to pass this command directly into cmd. # I cannot for the life of me figure out how to pass this command directly into cmd.
printf '"%%VSINSTALLDIR%%\Common7\Tools\VsMSBuildCmd.bat" && msbuild /t:Rebuild /p:Configuration=ReleaseDLL;Platform=%s;OutDir=%s' "$arch" "$(cygpath -w "${prefix##$PWD/}/$arch/")" > .build.bat printf '"%%VSINSTALLDIR%%\Common7\Tools\VsMSBuildCmd.bat" && msbuild /t:Rebuild /p:Configuration=Release;Platform=%s;OutDir=%s' "$arch" "$(cygpath -w "${prefix##$PWD/}/$arch/")" > .build.bat
cmd //c .build.bat cmd //c .build.bat
rm -f .build.bat rm -f .build.bat
else else
@@ -271,7 +278,10 @@ _finalize_merge() {
# By default, this will run `make clean`. # By default, this will run `make clean`.
finalize_clean() { _finalize_clean "$@"; } finalize_clean() { _finalize_clean "$@"; }
_finalize_clean() { _finalize_clean() {
[[ ! -e Makefile ]] || make -s clean if [[ $platform = windows ]]; then :
else
[[ ! -e Makefile ]] || make -s clean
fi
} }
# build <name> [<platform>] # build <name> [<platform>]

View File

@@ -1,4 +1,13 @@
#!/usr/bin/env bash #!/usr/bin/env bash
source "${BASH_SOURCE%/*}/build_lib" source "${BASH_SOURCE%/*}/build_lib"
finalize_merge() {
local prefix=$1 platform=$2; shift 2
local archs=( "$@" )
cp -a "src/libsodium/include" "$prefix/out"
_finalize_merge "$prefix" "$platform" "${archs[@]}"
}
build libsodium windows build libsodium windows

View File

@@ -35,7 +35,7 @@ library {
} }
withType( GccCompatibleToolChain ) { withType( GccCompatibleToolChain ) {
eachPlatform { eachPlatform {
cppCompiler.withArguments { addAll( ['-x', 'c', '-O3', '-std=c11', '-Werror', '-DMPW_SODIUM=1'] ) } cppCompiler.withArguments { addAll( ['-x', 'c', '-O3', '-Werror', '-DMPW_SODIUM=1'] ) }
} }
} }
} }
@@ -69,14 +69,14 @@ library {
} }
// libjson-c // libjson-c
archive.dependsOn project.tasks.maybeCreate( "build_libjson-c-${system}", Exec ).configure { /*archive.dependsOn project.tasks.maybeCreate( "build_libjson-c-${system}", Exec ).configure {
commandLine 'bash', "$rootDir/../lib/bin/build_libjson-c-${system}" commandLine 'bash', "$rootDir/../lib/bin/build_libjson-c-${system}"
privateHeaders.from "$rootDir/../lib/libjson-c/build-${system}~/out/include" privateHeaders.from "$rootDir/../lib/libjson-c/build-${system}~/out/include"
add( linkLibraries.name, fileTree( "$rootDir/../lib/libjson-c/build-${system}~/out/lib" ) ) add( linkLibraries.name, fileTree( "$rootDir/../lib/libjson-c/build-${system}~/out/lib" ) )
} }
clean.dependsOn project.tasks.maybeCreate( "clean_libjson-c-${system}", Exec ).configure { clean.dependsOn project.tasks.maybeCreate( "clean_libjson-c-${system}", Exec ).configure {
commandLine 'bash', "$rootDir/../lib/bin/build_libjson-c-${system}", 'clean' commandLine 'bash', "$rootDir/../lib/bin/build_libjson-c-${system}", 'clean'
} }*/
} }
} }
} }

Binary file not shown.

View File

@@ -7,7 +7,7 @@
// TODO: We may need to zero the jbytes safely. // TODO: We may need to zero the jbytes safely.
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) { JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {
JNIEnv* env; JNIEnv* env;
if ((*vm)->GetEnv( vm, (void **)&env, JNI_VERSION_1_6 ) != JNI_OK) if ((*vm)->GetEnv( vm, (void **)&env, JNI_VERSION_1_6 ) != JNI_OK)
return -1; return -1;

View File

@@ -35,10 +35,10 @@ char *mpw_get_token(const char **in, const char *eol, char *delim) {
return token; return token;
} }
time_t mpw_mktime( time_t mpw_timegm(const char *time) {
const char *time) {
// TODO: Support for parsing non-UTC time strings // TODO: Support for parsing non-UTC time strings
// Parse time as a UTC timestamp, into a tm.
struct tm tm = { .tm_isdst = -1 }; struct tm tm = { .tm_isdst = -1 };
if (time && sscanf( time, "%4d-%2d-%2dT%2d:%2d:%2dZ", if (time && sscanf( time, "%4d-%2d-%2dT%2d:%2d:%2dZ",
&tm.tm_year, &tm.tm_mon, &tm.tm_mday, &tm.tm_year, &tm.tm_mon, &tm.tm_mday,
@@ -46,8 +46,10 @@ time_t mpw_mktime(
tm.tm_year -= 1900; // tm_year 0 = rfc3339 year 1900 tm.tm_year -= 1900; // tm_year 0 = rfc3339 year 1900
tm.tm_mon -= 1; // tm_mon 0 = rfc3339 month 1 tm.tm_mon -= 1; // tm_mon 0 = rfc3339 month 1
// mktime converts tm to local, setting tm_gmtoff; use it to offset the result back to UTC. // mktime interprets tm as being local, we need to offset back to UTC (timegm/tm_gmtoff are non-standard).
return mktime( &tm ) + tm.tm_gmtoff; time_t local_time = mktime( &tm ), local_dst = tm.tm_isdst > 0? 3600: 0;
time_t gmtoff = local_time + local_dst - mktime( gmtime( &local_time ) );
return local_time + gmtoff;
} }
return false; return false;

View File

@@ -34,7 +34,7 @@
char *mpw_get_token( char *mpw_get_token(
const char **in, const char *eol, char *delim); const char **in, const char *eol, char *delim);
/** Convert an RFC 3339 time string into epoch time. */ /** Convert an RFC 3339 time string into epoch time. */
time_t mpw_mktime( time_t mpw_timegm(
const char *time); const char *time);
/// JSON parsing. /// JSON parsing.

View File

@@ -407,7 +407,7 @@ static void mpw_marshal_read_flat_info(
if (strcmp( headerName, "Passwords" ) == 0) if (strcmp( headerName, "Passwords" ) == 0)
info->redacted = strcmp( headerValue, "VISIBLE" ) != 0; info->redacted = strcmp( headerValue, "VISIBLE" ) != 0;
if (strcmp( headerName, "Date" ) == 0) if (strcmp( headerName, "Date" ) == 0)
info->date = mpw_mktime( headerValue ); info->date = mpw_timegm( headerValue );
mpw_free_strings( &headerName, &headerValue, NULL ); mpw_free_strings( &headerName, &headerValue, NULL );
continue; continue;
@@ -580,7 +580,7 @@ static MPMarshalledUser *mpw_marshal_read_flat(
return NULL; return NULL;
} }
MPAlgorithmVersion siteAlgorithm = (MPAlgorithmVersion)value; MPAlgorithmVersion siteAlgorithm = (MPAlgorithmVersion)value;
time_t siteLastUsed = mpw_mktime( str_lastUsed ); time_t siteLastUsed = mpw_timegm( str_lastUsed );
if (!siteLastUsed) { if (!siteLastUsed) {
*error = (MPMarshalError){ MPMarshalErrorIllegal, mpw_str( "Invalid site last used: %s: %s", siteName, str_lastUsed ) }; *error = (MPMarshalError){ MPMarshalErrorIllegal, mpw_str( "Invalid site last used: %s: %s", siteName, str_lastUsed ) };
return NULL; return NULL;
@@ -650,7 +650,7 @@ static void mpw_marshal_read_json_info(
if (fileFormat < 1) if (fileFormat < 1)
return; return;
info->redacted = mpw_get_json_boolean( json_file, "export.redacted", true ); info->redacted = mpw_get_json_boolean( json_file, "export.redacted", true );
info->date = mpw_mktime( mpw_get_json_string( json_file, "export.date", NULL ) ); info->date = mpw_timegm( mpw_get_json_string( json_file, "export.date", NULL ) );
// Section: "user" // Section: "user"
info->algorithm = (MPAlgorithmVersion)mpw_get_json_int( json_file, "user.algorithm", MPAlgorithmVersionCurrent ); info->algorithm = (MPAlgorithmVersion)mpw_get_json_int( json_file, "user.algorithm", MPAlgorithmVersionCurrent );
@@ -707,7 +707,7 @@ static MPMarshalledUser *mpw_marshal_read_json(
*error = (MPMarshalError){ MPMarshalErrorIllegal, mpw_str( "Invalid user default type: %u", defaultType ) }; *error = (MPMarshalError){ MPMarshalErrorIllegal, mpw_str( "Invalid user default type: %u", defaultType ) };
return NULL; return NULL;
} }
time_t lastUsed = mpw_mktime( str_lastUsed ); time_t lastUsed = mpw_timegm( str_lastUsed );
if (!lastUsed) { if (!lastUsed) {
*error = (MPMarshalError){ MPMarshalErrorIllegal, mpw_str( "Invalid user last used: %s", str_lastUsed ) }; *error = (MPMarshalError){ MPMarshalErrorIllegal, mpw_str( "Invalid user last used: %s", str_lastUsed ) };
return NULL; return NULL;
@@ -760,7 +760,7 @@ static MPMarshalledUser *mpw_marshal_read_json(
MPResultType siteLoginType = (MPResultType)mpw_get_json_int( json_site.val, "login_type", MPResultTypeTemplateName ); MPResultType siteLoginType = (MPResultType)mpw_get_json_int( json_site.val, "login_type", MPResultTypeTemplateName );
unsigned int siteUses = (unsigned int)mpw_get_json_int( json_site.val, "uses", 0 ); unsigned int siteUses = (unsigned int)mpw_get_json_int( json_site.val, "uses", 0 );
str_lastUsed = mpw_get_json_string( json_site.val, "last_used", NULL ); str_lastUsed = mpw_get_json_string( json_site.val, "last_used", NULL );
time_t siteLastUsed = mpw_mktime( str_lastUsed ); time_t siteLastUsed = mpw_timegm( str_lastUsed );
if (!siteLastUsed) { if (!siteLastUsed) {
*error = (MPMarshalError){ MPMarshalErrorIllegal, mpw_str( "Invalid site last used: %s: %s", siteName, str_lastUsed ) }; *error = (MPMarshalError){ MPMarshalErrorIllegal, mpw_str( "Invalid site last used: %s: %s", siteName, str_lastUsed ) };
return NULL; return NULL;

View File

@@ -66,6 +66,7 @@ public class MPMasterKey {
* *
* @throws MPKeyUnavailableException {@link #invalidate()} has been called on this object. * @throws MPKeyUnavailableException {@link #invalidate()} has been called on this object.
*/ */
@Nonnull
public byte[] getKeyID(final MPAlgorithm algorithm) public byte[] getKeyID(final MPAlgorithm algorithm)
throws MPKeyUnavailableException, MPAlgorithmException { throws MPKeyUnavailableException, MPAlgorithmException {
@@ -87,6 +88,7 @@ public class MPMasterKey {
return !invalidated; return !invalidated;
} }
@Nonnull
private byte[] masterKey(final MPAlgorithm algorithm) private byte[] masterKey(final MPAlgorithm algorithm)
throws MPKeyUnavailableException, MPAlgorithmException { throws MPKeyUnavailableException, MPAlgorithmException {
Preconditions.checkArgument( masterPassword.length > 0 ); Preconditions.checkArgument( masterPassword.length > 0 );
@@ -109,6 +111,7 @@ public class MPMasterKey {
return masterKey; return masterKey;
} }
@Nonnull
private byte[] siteKey(final String siteName, final MPAlgorithm algorithm, final UnsignedInteger siteCounter, private byte[] siteKey(final String siteName, final MPAlgorithm algorithm, final UnsignedInteger siteCounter,
final MPKeyPurpose keyPurpose, @Nullable final String keyContext) final MPKeyPurpose keyPurpose, @Nullable final String keyContext)
throws MPKeyUnavailableException, MPAlgorithmException { throws MPKeyUnavailableException, MPAlgorithmException {
@@ -141,13 +144,19 @@ public class MPMasterKey {
* In the case of {@link MPResultTypeClass#Stateful} types, the result of * In the case of {@link MPResultTypeClass#Stateful} types, the result of
* {@link #siteState(String, MPAlgorithm, UnsignedInteger, MPKeyPurpose, String, MPResultType, String)}. * {@link #siteState(String, MPAlgorithm, UnsignedInteger, MPKeyPurpose, String, MPResultType, String)}.
* *
* @return {@code null} if the result type is missing a required parameter.
*
* @throws MPKeyUnavailableException {@link #invalidate()} has been called on this object. * @throws MPKeyUnavailableException {@link #invalidate()} has been called on this object.
*/ */
@Nullable
public String siteResult(final String siteName, final MPAlgorithm algorithm, final UnsignedInteger siteCounter, public String siteResult(final String siteName, final MPAlgorithm algorithm, final UnsignedInteger siteCounter,
final MPKeyPurpose keyPurpose, @Nullable final String keyContext, final MPKeyPurpose keyPurpose, @Nullable final String keyContext,
final MPResultType resultType, @Nullable final String resultParam) final MPResultType resultType, @Nullable final String resultParam)
throws MPKeyUnavailableException, MPAlgorithmException { throws MPKeyUnavailableException, MPAlgorithmException {
if ((resultType.getTypeClass() == MPResultTypeClass.Stateful) && (resultParam == null))
return null;
byte[] masterKey = masterKey( algorithm ); byte[] masterKey = masterKey( algorithm );
byte[] siteKey = siteKey( siteName, algorithm, siteCounter, keyPurpose, keyContext ); byte[] siteKey = siteKey( siteName, algorithm, siteCounter, keyPurpose, keyContext );
@@ -176,6 +185,7 @@ public class MPMasterKey {
* *
* @throws MPKeyUnavailableException {@link #invalidate()} has been called on this object. * @throws MPKeyUnavailableException {@link #invalidate()} has been called on this object.
*/ */
@Nonnull
public String siteState(final String siteName, final MPAlgorithm algorithm, final UnsignedInteger siteCounter, public String siteState(final String siteName, final MPAlgorithm algorithm, final UnsignedInteger siteCounter,
final MPKeyPurpose keyPurpose, @Nullable final String keyContext, final MPKeyPurpose keyPurpose, @Nullable final String keyContext,
final MPResultType resultType, final String resultParam) final MPResultType resultType, final String resultParam)

View File

@@ -41,7 +41,7 @@ public enum MPResultType {
/** /**
* 16: pg^VMAUBk5x3p%HP%i4= * 16: pg^VMAUBk5x3p%HP%i4=
*/ */
GeneratedMaximum( "maximum", "Maximum Security", "pg^VMAUBk5x3p%HP%i4=", "20 characters, contains symbols.", // GeneratedMaximum( "maximum", "Maximum Security", "pg^VMAUBk5x3p%HP%i4=", "20 characters, contains symbols", //
ImmutableList.of( new MPTemplate( "anoxxxxxxxxxxxxxxxxx" ), ImmutableList.of( new MPTemplate( "anoxxxxxxxxxxxxxxxxx" ),
new MPTemplate( "axxxxxxxxxxxxxxxxxno" ) ), // new MPTemplate( "axxxxxxxxxxxxxxxxxno" ) ), //
MPResultTypeClass.Template, 0x0 ), MPResultTypeClass.Template, 0x0 ),
@@ -49,7 +49,7 @@ public enum MPResultType {
/** /**
* 17: BiroYena8:Kixa * 17: BiroYena8:Kixa
*/ */
GeneratedLong( "long", "Long Password", "BiroYena8:Kixa", "Copy-friendly, 14 characters, contains symbols.", // GeneratedLong( "long", "Long Password", "BiroYena8:Kixa", "Copy-friendly, 14 characters, contains symbols", //
ImmutableList.of( new MPTemplate( "CvcvnoCvcvCvcv" ), new MPTemplate( "CvcvCvcvnoCvcv" ), ImmutableList.of( new MPTemplate( "CvcvnoCvcvCvcv" ), new MPTemplate( "CvcvCvcvnoCvcv" ),
new MPTemplate( "CvcvCvcvCvcvno" ), new MPTemplate( "CvccnoCvcvCvcv" ), new MPTemplate( "CvcvCvcvCvcvno" ), new MPTemplate( "CvccnoCvcvCvcv" ),
new MPTemplate( "CvccCvcvnoCvcv" ), new MPTemplate( "CvccCvcvCvcvno" ), new MPTemplate( "CvccCvcvnoCvcv" ), new MPTemplate( "CvccCvcvCvcvno" ),
@@ -66,7 +66,7 @@ public enum MPResultType {
/** /**
* 18: BirSuj0- * 18: BirSuj0-
*/ */
GeneratedMedium( "medium", "Medium Password", "BirSuj0-", "Copy-friendly, 8 characters, contains symbols.", // GeneratedMedium( "medium", "Medium Password", "BirSuj0-", "Copy-friendly, 8 characters, contains symbols", //
ImmutableList.of( new MPTemplate( "CvcnoCvc" ), ImmutableList.of( new MPTemplate( "CvcnoCvc" ),
new MPTemplate( "CvcCvcno" ) ), // new MPTemplate( "CvcCvcno" ) ), //
MPResultTypeClass.Template, 0x2 ), MPResultTypeClass.Template, 0x2 ),
@@ -74,14 +74,14 @@ public enum MPResultType {
/** /**
* 19: Bir8 * 19: Bir8
*/ */
GeneratedShort( "short", "Short Password", "Bir8", "Copy-friendly, 4 characters, no symbols.", // GeneratedShort( "short", "Short Password", "Bir8", "Copy-friendly, 4 characters, no symbols", //
ImmutableList.of( new MPTemplate( "Cvcn" ) ), // ImmutableList.of( new MPTemplate( "Cvcn" ) ), //
MPResultTypeClass.Template, 0x3 ), MPResultTypeClass.Template, 0x3 ),
/** /**
* 20: pO98MoD0 * 20: pO98MoD0
*/ */
GeneratedBasic( "basic", "Basic Password", "pO98MoD0", "8 characters, no symbols.", // GeneratedBasic( "basic", "Basic Password", "pO98MoD0", "8 characters, no symbols", //
ImmutableList.of( new MPTemplate( "aaanaaan" ), ImmutableList.of( new MPTemplate( "aaanaaan" ),
new MPTemplate( "aannaaan" ), new MPTemplate( "aannaaan" ),
new MPTemplate( "aaannaaa" ) ), // new MPTemplate( "aaannaaa" ) ), //
@@ -90,44 +90,44 @@ public enum MPResultType {
/** /**
* 21: 2798 * 21: 2798
*/ */
GeneratedPIN( "pin", "PIN Code", "2798", "4 numbers.", // GeneratedPIN( "pin", "PIN Code", "2798", "4 numbers", //
ImmutableList.of( new MPTemplate( "nnnn" ) ), // ImmutableList.of( new MPTemplate( "nnnn" ) ), //
MPResultTypeClass.Template, 0x5 ), MPResultTypeClass.Template, 0x5 ),
/** /**
* 30: birsujano * 30: birsujano
*/ */
GeneratedName( "name", "Name", "birsujano", "9 letter name.", // GeneratedName( "name", "Name", "birsujano", "9 letter name", //
ImmutableList.of( new MPTemplate( "cvccvcvcv" ) ), // ImmutableList.of( new MPTemplate( "cvccvcvcv" ) ), //
MPResultTypeClass.Template, 0xE ), MPResultTypeClass.Template, 0xE ),
/** /**
* 31: bir yennoquce fefi * 31: bir yennoquce fefi
*/ */
GeneratedPhrase( "phrase", "Phrase", "bir yennoquce fefi", "20 character sentence.", // GeneratedPhrase( "phrase", "Phrase", "bir yennoquce fefi", "20 character sentence", //
ImmutableList.of( new MPTemplate( "cvcc cvc cvccvcv cvc" ), ImmutableList.of( new MPTemplate( "cvcc cvc cvccvcv cvc" ),
new MPTemplate( "cvc cvccvcvcv cvcv" ), new MPTemplate( "cvc cvccvcvcv cvcv" ),
new MPTemplate( "cv cvccv cvc cvcvccv" ) ), // new MPTemplate( "cv cvccv cvc cvcvccv" ) ), //
MPResultTypeClass.Template, 0xF ), MPResultTypeClass.Template, 0xF ),
/** /**
* 1056: Custom saved password. * 1056: Custom saved value.
*/ */
StoredPersonal( "personal", "Saved Password", null, "AES-encrypted, exportable.", // StoredPersonal( "personal", "Saved", null, "AES-encrypted, exportable", //
ImmutableList.<MPTemplate>of(), // ImmutableList.<MPTemplate>of(), //
MPResultTypeClass.Stateful, 0x0, MPSiteFeature.ExportContent ), MPResultTypeClass.Stateful, 0x0, MPSiteFeature.ExportContent ),
/** /**
* 2081: Custom saved password that should not be exported from the device. * 2081: Custom saved value that should not be exported from the device.
*/ */
StoredDevicePrivate( "device", "Private Password", null, "AES-encrypted, not exported.", // StoredDevicePrivate( "device", "Private", null, "AES-encrypted, not exported", //
ImmutableList.<MPTemplate>of(), // ImmutableList.<MPTemplate>of(), //
MPResultTypeClass.Stateful, 0x1, MPSiteFeature.DevicePrivate ), MPResultTypeClass.Stateful, 0x1, MPSiteFeature.DevicePrivate ),
/** /**
* 4160: Derive a unique binary key. * 4160: Derive a unique binary key.
*/ */
DeriveKey( "key", "Binary Key", null, "Encryption key.", // DeriveKey( "key", "Binary Key", null, "Encryption key", //
ImmutableList.<MPTemplate>of(), // ImmutableList.<MPTemplate>of(), //
MPResultTypeClass.Derive, 0x0, MPSiteFeature.Alternative ); MPResultTypeClass.Derive, 0x0, MPSiteFeature.Alternative );

View File

@@ -18,14 +18,14 @@
package com.lyndir.masterpassword.impl; package com.lyndir.masterpassword.impl;
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.*;
import com.google.common.base.Joiner; import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.io.ByteStreams; import com.google.common.io.ByteStreams;
import com.lyndir.lhunath.opal.system.logging.Logger; import com.lyndir.lhunath.opal.system.logging.Logger;
import java.io.*; import java.io.*;
import java.util.Locale; import java.util.*;
import java.util.function.Predicate;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@@ -43,71 +43,161 @@ public final class Native {
private static final String NATIVES_PATH = "lib"; private static final String NATIVES_PATH = "lib";
@SuppressWarnings({ "HardcodedFileSeparator", "LoadLibraryWithNonConstantString" }) @SuppressWarnings({ "HardcodedFileSeparator", "LoadLibraryWithNonConstantString" })
public static void load(final Class<?> context, final String name) { public static boolean load(final Class<?> context, final String name) {
// Try to load the library using the native system. // Try to load the library using the native system.
try { try {
System.loadLibrary( name ); System.loadLibrary( name );
return; return true;
} }
catch (@SuppressWarnings("ErrorNotRethrown") final UnsatisfiedLinkError ignored) { catch (@SuppressWarnings("ErrorNotRethrown") final UnsatisfiedLinkError ignored) {
} }
// Try to find and open a stream to the packaged library resource. // Try to find and open a stream to the packaged library resource.
try { String library = System.mapLibraryName( name );
String library = System.mapLibraryName( name ); int libraryDot = library.lastIndexOf( EXTENSION_SEPARATOR );
int libraryDot = library.lastIndexOf( EXTENSION_SEPARATOR ); String libraryName = (libraryDot > 0)? library.substring( 0, libraryDot ): library;
String libraryName = (libraryDot > 0)? library.substring( 0, libraryDot ): library; String libraryExtension = (libraryDot > 0)? library.substring( libraryDot ): ".lib";
String libraryExtension = (libraryDot > 0)? library.substring( libraryDot ): ".lib";
String libraryResource = getLibraryResource( library );
InputStream libraryStream = context.getResourceAsStream( libraryResource );
if (libraryStream == null)
throw new IllegalStateException(
"Library: " + name + " (" + libraryResource + "), not found in class loader for: " + context );
// Write the library resource to a temporary file. @Nullable
File libraryFile = File.createTempFile( libraryName, libraryExtension ); File libraryFile = null;
FileOutputStream libraryFileStream = new FileOutputStream( libraryFile ); Set<String> libraryResources = getLibraryResources( library );
for (final String libraryResource : libraryResources) {
try { try {
libraryFile.deleteOnExit(); InputStream libraryStream = context.getResourceAsStream( libraryResource );
ByteStreams.copy( libraryStream, libraryFileStream ); if (libraryStream == null) {
} logger.dbg( "No resource for library: %s", libraryResource );
finally { continue;
libraryFileStream.close(); }
libraryStream.close();
}
// Load the library from the temporary file. // Write the library resource to a temporary file.
System.load( libraryFile.getAbsolutePath() ); libraryFile = File.createTempFile( libraryName, libraryExtension );
} FileOutputStream libraryFileStream = new FileOutputStream( libraryFile );
catch (final IOException e) { try {
throw new IllegalStateException( "Couldn't extract library: " + name, e ); libraryFile.deleteOnExit();
ByteStreams.copy( libraryStream, libraryFileStream );
}
finally {
libraryFileStream.close();
libraryStream.close();
}
// Load the library from the temporary file.
System.load( libraryFile.getAbsolutePath() );
return true;
}
catch (@SuppressWarnings("ErrorNotRethrown") final IOException | UnsatisfiedLinkError e) {
logger.dbg( e, "Couldn't load library: %s", libraryResource );
if (libraryFile != null)
if (libraryFile.exists() && !libraryFile.delete())
logger.wrn( "Couldn't clean up library file: %s", libraryFile );
libraryFile = null;
}
} }
return false;
} }
@Nonnull @Nonnull
private static String getLibraryResource(final String library) { private static Set<String> getLibraryResources(final String library) {
String system = ifNotNullElse( System.getProperty( "os.name" ), "linux" ).toLowerCase( Locale.ROOT );
String architecture = ifNotNullElse( System.getProperty( "os.arch" ), "x86" ).toLowerCase( Locale.ROOT );
// Standardize system naming in accordance with masterpassword-core. // Standardize system naming in accordance with masterpassword-core.
if (system.contains( "windows" )) Sys system = Sys.findCurrent();
system = "windows";
else if (system.contains( "mac os x" ) || system.contains( "darwin" ) || system.contains( "osx" ))
system = "macos";
else
system = "linux";
// Standardize architecture naming in accordance with masterpassword-core. // Standardize architecture naming in accordance with masterpassword-core.
if (ImmutableList.of( "arm", "arm-v7", "armv7", "arm32" ).contains( architecture )) Collection<Arch> architectures = new LinkedHashSet<>();
architecture = "arm"; architectures.add( Arch.findCurrent() );
else if (architecture.startsWith( "arm" )) architectures.addAll( Arrays.asList( Arch.values() ) );
architecture = "arm64";
else if (ImmutableList.of( "x86_64", "amd64", "x64", "x86-64" ).contains( architecture ))
architecture = "x86_64";
else
architecture = "x86";
return Joiner.on( RESOURCE_SEPARATOR ).join( "", NATIVES_PATH, system, architecture, library ); ImmutableSet.Builder<String> resources = ImmutableSet.builder();
for (final Arch arch : architectures)
resources.add( Joiner.on( RESOURCE_SEPARATOR ).join( "", NATIVES_PATH, system, arch, library ) );
return resources.build();
}
private enum Sys implements Predicate<String> {
windows {
@Override
public boolean test(final String system) {
return system.contains( "windows" );
}
},
macos {
@Override
public boolean test(final String system) {
return system.contains( "mac os x" ) || system.contains( "darwin" ) || system.contains( "osx" );
}
},
linux {
@Override
public boolean test(final String system) {
return system.contains( "linux" );
}
};
@Nonnull
public static Sys findCurrent() {
return find( System.getProperty( "os.name" ) );
}
@Nonnull
public static Sys find(@Nullable String name) {
if (name != null) {
name = name.toLowerCase( Locale.ROOT );
for (final Sys sys : values())
if (sys.test( name ))
return sys;
}
return linux;
}
}
private enum Arch implements Predicate<String> {
arm {
@Override
public boolean test(final String architecture) {
return ImmutableList.of( "arm", "arm-v7", "armv7", "arm32" ).contains( architecture );
}
},
arm64 {
@Override
public boolean test(final String architecture) {
return architecture.startsWith( "arm" ) && !arm.test( architecture );
}
},
x86_64 {
@Override
public boolean test(final String architecture) {
return ImmutableList.of( "x86_64", "amd64", "x64", "x86-64" ).contains( architecture );
}
},
x86 {
@Override
public boolean test(final String architecture) {
return ImmutableList.of( "x86", "i386", "i686" ).contains( architecture );
}
};
@Nonnull
public static Arch findCurrent() {
return find( System.getProperty( "os.arch" ) );
}
@Nonnull
public static Arch find(@Nullable String name) {
if (name != null) {
name = name.toLowerCase( Locale.ROOT );
for (final Arch arch : values())
if (arch.test( name ))
return arch;
}
return x86;
}
} }
} }

View File

@@ -19,6 +19,14 @@ public final class Utilities {
return consumer.apply( value ); return consumer.apply( value );
} }
@Nonnull
public static <T> T ifNotNullElse(@Nullable final T value, @Nonnull final T nullValue) {
if (value == null)
return nullValue;
return value;
}
@Nonnull @Nonnull
public static <T, R> R ifNotNullElse(@Nullable final T value, final Function<T, R> consumer, @Nonnull final R nullValue) { public static <T, R> R ifNotNullElse(@Nullable final T value, final Function<T, R> consumer, @Nonnull final R nullValue) {
if (value == null) if (value == null)

View File

@@ -11,6 +11,7 @@ dependencies {
implementation group: 'com.lyndir.lhunath.opal', name: 'opal-system', version: '1.7-p2' implementation group: 'com.lyndir.lhunath.opal', name: 'opal-system', version: '1.7-p2'
implementation group: 'ch.qos.logback', name: 'logback-classic', version: '1.1.2' implementation group: 'ch.qos.logback', name: 'logback-classic', version: '1.1.2'
implementation group: 'com.yuvimasory', name: 'orange-extensions', version: '1.3.0' implementation group: 'com.yuvimasory', name: 'orange-extensions', version: '1.3.0'
implementation group: 'com.github.tulskiy', name: 'jkeymaster', version: '1.2'
compile project( ':masterpassword-model' ) compile project( ':masterpassword-model' )
} }

View File

@@ -1,26 +0,0 @@
package com.lyndir.masterpassword.gui;
import com.lyndir.lhunath.opal.system.logging.Logger;
import com.lyndir.masterpassword.gui.util.Platform;
import com.lyndir.masterpassword.gui.util.Res;
import com.lyndir.masterpassword.gui.view.MasterPasswordFrame;
/**
* @author lhunath, 2018-07-28
*/
public class GUI {
private static final Logger logger = Logger.get( GUI.class );
private final MasterPasswordFrame frame = new MasterPasswordFrame();
public GUI() {
Platform.get().installAppForegroundHandler( this::open );
Platform.get().installAppReopenHandler( this::open );
}
public void open() {
Res.ui( () -> frame.setVisible( true ) );
}
}

View File

@@ -19,22 +19,22 @@
package com.lyndir.masterpassword.gui; package com.lyndir.masterpassword.gui;
import com.lyndir.lhunath.opal.system.util.ConversionUtils; import com.lyndir.lhunath.opal.system.util.ConversionUtils;
import com.lyndir.masterpassword.model.MPConstants; import com.lyndir.masterpassword.model.MPModelConstants;
/** /**
* @author lhunath, 2014-08-31 * @author lhunath, 2014-08-31
*/ */
@SuppressWarnings("CallToSystemGetenv") @SuppressWarnings("CallToSystemGetenv")
public class Config { public class MPConfig {
private static final Config instance = new Config(); private static final MPConfig instance = new MPConfig();
public static Config get() { public static MPConfig get() {
return instance; return instance;
} }
public boolean checkForUpdates() { public boolean checkForUpdates() {
return ConversionUtils.toBoolean( System.getenv( MPConstants.env_checkUpdates ) ).orElse( true ); return ConversionUtils.toBoolean( System.getenv( MPModelConstants.env_checkUpdates ) ).orElse( true );
} }
} }

View File

@@ -0,0 +1,14 @@
package com.lyndir.masterpassword.gui;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import javax.swing.*;
/**
* @author lhunath, 2018-07-31
*/
public final class MPGuiConstants {
public static final KeyStroke ui_hotkey = KeyStroke.getKeyStroke( KeyEvent.VK_P, InputEvent.CTRL_DOWN_MASK | InputEvent.META_DOWN_MASK );
}

View File

@@ -24,7 +24,11 @@ import com.google.common.base.Charsets;
import com.google.common.io.ByteSource; import com.google.common.io.ByteSource;
import com.lyndir.lhunath.opal.system.logging.Logger; import com.lyndir.lhunath.opal.system.logging.Logger;
import com.lyndir.lhunath.opal.system.util.ObjectUtils; import com.lyndir.lhunath.opal.system.util.ObjectUtils;
import com.lyndir.masterpassword.gui.util.*;
import com.lyndir.masterpassword.gui.view.MasterPasswordFrame;
import com.lyndir.masterpassword.model.MPUser; import com.lyndir.masterpassword.model.MPUser;
import com.tulskiy.keymaster.common.Provider;
import java.awt.*;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.net.*; import java.net.*;
@@ -49,18 +53,21 @@ public final class MasterPassword {
private final Collection<Listener> listeners = new CopyOnWriteArraySet<>(); private final Collection<Listener> listeners = new CopyOnWriteArraySet<>();
@Nullable @Nullable
private MPUser<?> activeUser; private MasterPasswordFrame frame;
@Nullable
private MPUser<?> activeUser;
public static MasterPassword get() { public static MasterPassword get() {
return instance; return instance;
} }
public boolean addListener(final Listener listener) { public void addListener(final Listener listener) {
return listeners.add( listener ); if (listeners.add( listener ))
listener.onUserSelected( activeUser );
} }
public boolean removeListener(final Listener listener) { public void removeListener(final Listener listener) {
return listeners.remove( listener ); listeners.remove( listener );
} }
public void activateUser(final MPUser<?> user) { public void activateUser(final MPUser<?> user) {
@@ -72,9 +79,27 @@ public final class MasterPassword {
listener.onUserSelected( activeUser ); listener.onUserSelected( activeUser );
} }
private static void checkUpdate() { @Nullable
public String version() {
return MasterPassword.class.getPackage().getImplementationVersion();
}
public void open() {
Res.ui( () -> {
if (frame == null)
frame = new MasterPasswordFrame();
frame.setAlwaysOnTop( true );
frame.setVisible( true );
frame.setExtendedState( Frame.NORMAL );
Platform.get().requestForeground();
frame.setAlwaysOnTop( false );
} );
}
public void checkUpdate() {
try { try {
String implementationVersion = MasterPassword.class.getPackage().getImplementationVersion(); String implementationVersion = version();
String latestVersion = new ByteSource() { String latestVersion = new ByteSource() {
@Override @Override
public InputStream openStream() public InputStream openStream()
@@ -86,16 +111,14 @@ public final class MasterPassword {
} }
}.asCharSource( Charsets.UTF_8 ).readFirstLine(); }.asCharSource( Charsets.UTF_8 ).readFirstLine();
if ((implementationVersion != null) && (latestVersion != null) && if ((implementationVersion != null) && !implementationVersion.equalsIgnoreCase( latestVersion )) {
!implementationVersion.equalsIgnoreCase( latestVersion )) {
logger.inf( "Implementation: <%s>", implementationVersion ); logger.inf( "Implementation: <%s>", implementationVersion );
logger.inf( "Latest : <%s>", latestVersion ); logger.inf( "Latest : <%s>", latestVersion );
logger.wrn( "You are not running the current official version. Please update from:%n%s", logger.wrn( "You are not running the current official version. Please update from:%n%s",
"https://masterpassword.app/masterpassword-gui.jar" ); "https://masterpassword.app/masterpassword-gui.jar" );
JOptionPane.showMessageDialog( null, JOptionPane.showMessageDialog( null, Components.linkLabel( strf(
strf( "A new version of Master Password is available.%n " "A new version of Master Password is available."
+ "Please download the latest version from %s", + "<p>Please download the latest version from <a href='https://masterpassword.app'>https://masterpassword.app</a>." ) ),
"https://masterpassword.app" ),
"Update Available", JOptionPane.INFORMATION_MESSAGE ); "Update Available", JOptionPane.INFORMATION_MESSAGE );
} }
} }
@@ -105,25 +128,30 @@ public final class MasterPassword {
} }
public static void main(final String... args) { public static void main(final String... args) {
// Thread.setDefaultUncaughtExceptionHandler( //Thread.setDefaultUncaughtExceptionHandler(
// (t, e) -> logger.bug( e, "Uncaught: %s", e.getLocalizedMessage() ) ); // (t, e) -> logger.bug( e, "Uncaught: %s", e.getLocalizedMessage() ) );
// Try and set the system look & feel, if available. // Try and set the system look & feel, if available.
try { try {
UIManager.setLookAndFeel( UIManager.getSystemLookAndFeelClassName() ); UIManager.setLookAndFeel( UIManager.getSystemLookAndFeelClassName() );
Platform.get().installAppForegroundHandler( get()::open );
Platform.get().installAppReopenHandler( get()::open );
Provider.getCurrentProvider( true ).register( MPGuiConstants.ui_hotkey, hotKey -> get().open() );
} }
catch (final UnsupportedLookAndFeelException | ClassNotFoundException | InstantiationException | IllegalAccessException ignored) { catch (final UnsupportedLookAndFeelException | ClassNotFoundException | InstantiationException | IllegalAccessException ignored) {
} }
// Check online to see if this version has been superseded.
if (Config.get().checkForUpdates())
checkUpdate();
// Create a platform-specific GUI and open it. // Create a platform-specific GUI and open it.
new GUI().open(); get().open();
// Check online to see if this version has been superseded.
if (MPConfig.get().checkForUpdates())
get().checkUpdate();
} }
@SuppressWarnings("InterfaceMayBeAnnotatedFunctional")
public interface Listener { public interface Listener {
void onUserSelected(@Nullable MPUser<?> user); void onUserSelected(@Nullable MPUser<?> user);
} }
} }

View File

@@ -31,17 +31,7 @@ import javax.annotation.Nullable;
*/ */
public class MPIncognitoQuestion extends MPBasicQuestion { public class MPIncognitoQuestion extends MPBasicQuestion {
private final MPIncognitoSite site;
public MPIncognitoQuestion(final MPIncognitoSite site, final String keyword, @Nullable final MPResultType type) { public MPIncognitoQuestion(final MPIncognitoSite site, final String keyword, @Nullable final MPResultType type) {
super( keyword, ifNotNullElse( type, site.getAlgorithm().mpw_default_answer_type() ) ); super( site, keyword, ifNotNullElse( type, site.getAlgorithm().mpw_default_answer_type() ) );
this.site = site;
}
@Nonnull
@Override
public MPIncognitoSite getSite() {
return site;
} }
} }

View File

@@ -40,4 +40,10 @@ public class MPIncognitoSite extends MPBasicSite<MPIncognitoUser, MPIncognitoQue
@Nullable final MPResultType resultType, @Nullable final MPResultType loginType) { @Nullable final MPResultType resultType, @Nullable final MPResultType loginType) {
super( user, siteName, (algorithm == null)? user.getAlgorithm(): algorithm, counter, resultType, loginType ); super( user, siteName, (algorithm == null)? user.getAlgorithm(): algorithm, counter, resultType, loginType );
} }
@Nonnull
@Override
public MPIncognitoQuestion addQuestion(final String keyword) {
return addQuestion( new MPIncognitoQuestion( this, keyword, null ) );
}
} }

View File

@@ -20,6 +20,7 @@ package com.lyndir.masterpassword.gui.model;
import com.lyndir.masterpassword.MPAlgorithm; import com.lyndir.masterpassword.MPAlgorithm;
import com.lyndir.masterpassword.model.impl.MPBasicUser; import com.lyndir.masterpassword.model.impl.MPBasicUser;
import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@@ -38,6 +39,7 @@ public class MPIncognitoUser extends MPBasicUser<MPIncognitoSite> {
return null; return null;
} }
@Nonnull
@Override @Override
public MPIncognitoSite addSite(final String siteName) { public MPIncognitoSite addSite(final String siteName) {
return addSite( new MPIncognitoSite( this, siteName ) ); return addSite( new MPIncognitoSite( this, siteName ) );

View File

@@ -0,0 +1,15 @@
package com.lyndir.masterpassword.gui.model;
import com.lyndir.masterpassword.model.MPSite;
import com.lyndir.masterpassword.model.impl.MPBasicQuestion;
/**
* @author lhunath, 2018-07-27
*/
public class MPNewQuestion extends MPBasicQuestion {
public MPNewQuestion(final MPSite<?> site, final String keyword) {
super( site, keyword, site.getAlgorithm().mpw_default_answer_type() );
}
}

View File

@@ -2,6 +2,7 @@ package com.lyndir.masterpassword.gui.model;
import com.lyndir.masterpassword.model.*; import com.lyndir.masterpassword.model.*;
import com.lyndir.masterpassword.model.impl.*; import com.lyndir.masterpassword.model.impl.*;
import javax.annotation.Nonnull;
/** /**
@@ -12,4 +13,10 @@ public class MPNewSite extends MPBasicSite<MPUser<?>, MPQuestion> {
public MPNewSite(final MPUser<?> user, final String siteName) { public MPNewSite(final MPUser<?> user, final String siteName) {
super( user, siteName ); super( user, siteName );
} }
@Nonnull
@Override
public MPQuestion addQuestion(final String keyword) {
throw new UnsupportedOperationException( "Cannot add a question to a site that hasn't been created yet." );
}
} }

View File

@@ -1,6 +1,5 @@
package com.lyndir.masterpassword.gui.util; package com.lyndir.masterpassword.gui.util;
import com.google.common.collect.ImmutableList;
import java.util.*; import java.util.*;
import java.util.function.Consumer; import java.util.function.Consumer;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@@ -18,25 +17,21 @@ public class CollectionListModel<E> extends AbstractListModel<E>
private final List<E> model = new LinkedList<>(); private final List<E> model = new LinkedList<>();
@Nullable @Nullable
private E selectedItem;
private JList<E> list; private JList<E> list;
@Nullable @Nullable
private E selectedItem;
@Nullable
private Consumer<E> selectionConsumer; private Consumer<E> selectionConsumer;
@SafeVarargs @SafeVarargs
public static <E> CollectionListModel<E> copy(final E... elements) { public CollectionListModel(final E... elements) {
return copy( Arrays.asList( elements ) ); this( Arrays.asList( elements ) );
} }
public static <E> CollectionListModel<E> copy(final Collection<? extends E> elements) { public CollectionListModel(final Collection<? extends E> elements) {
CollectionListModel<E> model = new CollectionListModel<>(); model.addAll( elements );
synchronized (model) { selectedItem = getElementAt( 0 );
model.model.addAll( elements ); fireIntervalAdded( this, 0, model.size() );
model.selectedItem = model.getElementAt( 0 );
model.fireIntervalAdded( model, 0, model.model.size() );
return model;
}
} }
@Override @Override
@@ -44,8 +39,8 @@ public class CollectionListModel<E> extends AbstractListModel<E>
return model.size(); return model.size();
} }
@Override
@Nullable @Nullable
@Override
public synchronized E getElementAt(final int index) { public synchronized E getElementAt(final int index) {
return (index < model.size())? model.get( index ): null; return (index < model.size())? model.get( index ): null;
} }
@@ -109,6 +104,11 @@ public class CollectionListModel<E> extends AbstractListModel<E>
return selectedItem; return selectedItem;
} }
public CollectionListModel<E> select(final E selectedItem) {
setSelectedItem( selectedItem );
return this;
}
public synchronized void registerList(final JList<E> list) { public synchronized void registerList(final JList<E> list) {
// TODO: This class should probably implement ListSelectionModel instead. // TODO: This class should probably implement ListSelectionModel instead.
if (this.list != null) if (this.list != null)

View File

@@ -18,10 +18,13 @@
package com.lyndir.masterpassword.gui.util; package com.lyndir.masterpassword.gui.util;
import com.google.common.base.Strings;
import com.lyndir.lhunath.opal.system.logging.Logger;
import java.awt.*; import java.awt.*;
import java.awt.event.ActionEvent; import java.awt.event.ActionEvent;
import java.awt.event.ActionListener; import java.awt.event.ActionListener;
import java.io.File; import java.io.File;
import java.net.URISyntaxException;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.function.Consumer; import java.util.function.Consumer;
@@ -30,9 +33,9 @@ import javax.annotation.Nullable;
import javax.swing.*; import javax.swing.*;
import javax.swing.border.Border; import javax.swing.border.Border;
import javax.swing.border.CompoundBorder; import javax.swing.border.CompoundBorder;
import javax.swing.event.DocumentEvent; import javax.swing.event.HyperlinkEvent;
import javax.swing.event.DocumentListener; import javax.swing.text.*;
import javax.swing.text.DefaultFormatterFactory; import org.jetbrains.annotations.NonNls;
/** /**
@@ -41,6 +44,8 @@ import javax.swing.text.DefaultFormatterFactory;
@SuppressWarnings({ "SerializableStoresNonSerializable", "serial" }) @SuppressWarnings({ "SerializableStoresNonSerializable", "serial" })
public abstract class Components { public abstract class Components {
private static final Logger logger = Logger.get( Components.class );
public static final float TEXT_SIZE_HEADING = 19f; public static final float TEXT_SIZE_HEADING = 19f;
public static final float TEXT_SIZE_CONTROL = 13f; public static final float TEXT_SIZE_CONTROL = 13f;
public static final int SIZE_MARGIN = 12; public static final int SIZE_MARGIN = 12;
@@ -96,19 +101,25 @@ public abstract class Components {
public static int showDialog(@Nullable final Component owner, @Nullable final String title, final JOptionPane pane) { public static int showDialog(@Nullable final Component owner, @Nullable final String title, final JOptionPane pane) {
JDialog dialog = pane.createDialog( owner, title ); JDialog dialog = pane.createDialog( owner, title );
dialog.setMinimumSize( new Dimension( 520, 0 ) );
dialog.setModalityType( Dialog.ModalityType.DOCUMENT_MODAL ); dialog.setModalityType( Dialog.ModalityType.DOCUMENT_MODAL );
showDialog( dialog ); showDialog( dialog );
Object selectedValue = pane.getValue(); Object selectedValue = pane.getValue();
if(selectedValue == null) if (selectedValue == null)
return JOptionPane.CLOSED_OPTION; return JOptionPane.CLOSED_OPTION;
Object[] options = pane.getOptions(); Object[] options = pane.getOptions();
if(options == null) if (options == null)
return (selectedValue instanceof Integer)? (Integer) selectedValue: JOptionPane.CLOSED_OPTION; return (selectedValue instanceof Integer)? (Integer) selectedValue: JOptionPane.CLOSED_OPTION;
int option = Arrays.binarySearch( options, selectedValue ); try {
return (option < 0)? JOptionPane.CLOSED_OPTION: option; int option = Arrays.binarySearch( options, selectedValue );
return (option < 0)? JOptionPane.CLOSED_OPTION: option;
}
catch (final ClassCastException ignored) {
return JOptionPane.CLOSED_OPTION;
}
} }
@Nullable @Nullable
@@ -139,7 +150,6 @@ public abstract class Components {
title, Dialog.ModalityType.DOCUMENT_MODAL ); title, Dialog.ModalityType.DOCUMENT_MODAL );
dialog.setMinimumSize( new Dimension( 320, 0 ) ); dialog.setMinimumSize( new Dimension( 320, 0 ) );
dialog.setLocationRelativeTo( owner ); dialog.setLocationRelativeTo( owner );
dialog.setLocationByPlatform( true );
dialog.setContentPane( content ); dialog.setContentPane( content );
return showDialog( dialog ); return showDialog( dialog );
@@ -151,13 +161,18 @@ public abstract class Components {
dialog.getRootPane().putClientProperty( "Window.style", "small" ); dialog.getRootPane().putClientProperty( "Window.style", "small" );
dialog.pack(); dialog.pack();
dialog.setLocationByPlatform( true );
dialog.setVisible( true ); dialog.setVisible( true );
return dialog; return dialog;
} }
public static JTextField textField() { public static JTextField textField() {
return new JTextField() { return textField( null );
}
public static JTextField textField(@Nullable final Document document) {
return new JTextField( document, null, 0 ) {
{ {
setBorder( BorderFactory.createCompoundBorder( BorderFactory.createLineBorder( Res.colors().controlBorder(), 1, true ), setBorder( BorderFactory.createCompoundBorder( BorderFactory.createLineBorder( Res.colors().controlBorder(), 1, true ),
BorderFactory.createEmptyBorder( 4, 4, 4, 4 ) ) ); BorderFactory.createEmptyBorder( 4, 4, 4, 4 ) ) );
@@ -167,41 +182,30 @@ public abstract class Components {
@Override @Override
public Dimension getMaximumSize() { public Dimension getMaximumSize() {
return new Dimension( Integer.MAX_VALUE, getPreferredSize().height ); return new Dimension( Integer.MAX_VALUE, Integer.MAX_VALUE );
} }
}; };
} }
public static JTextField textField(@Nullable final String text, @Nullable final Consumer<String> selection) { public static JTextField textField(@Nullable final String text, @Nullable final Consumer<String> change) {
return new JTextField( text ) { return textField( new DocumentModel( new PlainDocument() ).selection( text, change ).getDocument() );
}
public static JTextArea textArea() {
return new JTextArea() {
{ {
setBorder( BorderFactory.createCompoundBorder( BorderFactory.createLineBorder( Res.colors().controlBorder(), 1, true ), setBorder( BorderFactory.createCompoundBorder( BorderFactory.createLineBorder( Res.colors().controlBorder(), 1, true ),
BorderFactory.createEmptyBorder( 4, 4, 4, 4 ) ) ); BorderFactory.createEmptyBorder( 4, 4, 4, 4 ) ) );
setFont( Res.fonts().valueFont( TEXT_SIZE_CONTROL ) ); setFont( Res.fonts().valueFont( TEXT_SIZE_CONTROL ) );
setAlignmentX( LEFT_ALIGNMENT ); setAlignmentX( LEFT_ALIGNMENT );
if (selection != null) setLineWrap( true );
getDocument().addDocumentListener( new DocumentListener() { setRows( 3 );
@Override
public void insertUpdate(final DocumentEvent e) {
selection.accept( getText() );
}
@Override
public void removeUpdate(final DocumentEvent e) {
selection.accept( getText() );
}
@Override
public void changedUpdate(final DocumentEvent e) {
selection.accept( getText() );
}
} );
} }
@Override @Override
public Dimension getMaximumSize() { public Dimension getMaximumSize() {
return new Dimension( Integer.MAX_VALUE, getPreferredSize().height ); return new Dimension( Integer.MAX_VALUE, Integer.MAX_VALUE );
} }
}; };
} }
@@ -224,20 +228,24 @@ public abstract class Components {
public static <E> JList<E> list(final ListModel<E> model, final Function<E, String> valueTransformer) { public static <E> JList<E> list(final ListModel<E> model, final Function<E, String> valueTransformer) {
return new JList<E>( model ) { return new JList<E>( model ) {
{ {
setAlignmentX( LEFT_ALIGNMENT );
setFont( Res.fonts().valueFont( TEXT_SIZE_CONTROL ) ); setFont( Res.fonts().valueFont( TEXT_SIZE_CONTROL ) );
setCellRenderer( new DefaultListCellRenderer() { setCellRenderer( new DefaultListCellRenderer() {
@Override @Override
@SuppressWarnings({ "unchecked", "SerializableStoresNonSerializable" }) @SuppressWarnings({ "unchecked", "SerializableStoresNonSerializable" })
public Component getListCellRendererComponent(final JList<?> list, final Object value, final int index, public Component getListCellRendererComponent(final JList<?> list, final Object value, final int index,
final boolean isSelected, final boolean cellHasFocus) { final boolean isSelected, final boolean cellHasFocus) {
String label = valueTransformer.apply( (E) value );
super.getListCellRendererComponent( super.getListCellRendererComponent(
list, valueTransformer.apply( (E) value ), index, isSelected, cellHasFocus ); list, Strings.isNullOrEmpty( label )? " ": label, index, isSelected, cellHasFocus );
setBorder( BorderFactory.createEmptyBorder( 2, 4, 2, 4 ) ); setBorder( BorderFactory.createEmptyBorder( 2, 4, 2, 4 ) );
return this; return this;
} }
} ); } );
setAlignmentX( LEFT_ALIGNMENT );
if (model instanceof CollectionListModel)
((CollectionListModel<E>) model).registerList( this );
} }
@Override @Override
@@ -293,7 +301,6 @@ public abstract class Components {
public static JButton button(final Action action) { public static JButton button(final Action action) {
return new JButton( action ) { return new JButton( action ) {
{ {
setFont( Res.fonts().controlFont( TEXT_SIZE_CONTROL ) );
setAlignmentX( LEFT_ALIGNMENT ); setAlignmentX( LEFT_ALIGNMENT );
if (getText() == null) { if (getText() == null) {
@@ -337,7 +344,7 @@ public abstract class Components {
BorderFactory.createEmptyBorder( 4, 4, 4, 4 ) ); BorderFactory.createEmptyBorder( 4, 4, 4, 4 ) );
DefaultFormatterFactory formatterFactory = new DefaultFormatterFactory(); DefaultFormatterFactory formatterFactory = new DefaultFormatterFactory();
if (model instanceof UnsignedIntegerModel) if (model instanceof UnsignedIntegerModel)
formatterFactory.setDefaultFormatter( ((UnsignedIntegerModel)model).getFormatter() ); formatterFactory.setDefaultFormatter( ((UnsignedIntegerModel) model).getFormatter() );
((DefaultEditor) getEditor()).getTextField().setFormatterFactory( formatterFactory ); ((DefaultEditor) getEditor()).getTextField().setFormatterFactory( formatterFactory );
((DefaultEditor) getEditor()).getTextField().setBorder( editorBorder ); ((DefaultEditor) getEditor()).getTextField().setBorder( editorBorder );
setAlignmentX( LEFT_ALIGNMENT ); setAlignmentX( LEFT_ALIGNMENT );
@@ -370,7 +377,7 @@ public abstract class Components {
public static JLabel heading(@Nullable final String heading, final int horizontalAlignment) { public static JLabel heading(@Nullable final String heading, final int horizontalAlignment) {
return new JLabel( heading, horizontalAlignment ) { return new JLabel( heading, horizontalAlignment ) {
{ {
setFont( Res.fonts().controlFont( TEXT_SIZE_HEADING ).deriveFont( Font.BOLD ) ); setFont( getFont().deriveFont( Font.BOLD, TEXT_SIZE_HEADING ) );
setAlignmentX( LEFT_ALIGNMENT ); setAlignmentX( LEFT_ALIGNMENT );
} }
@@ -405,7 +412,6 @@ public abstract class Components {
public static JLabel label(@Nullable final String label, final int horizontalAlignment) { public static JLabel label(@Nullable final String label, final int horizontalAlignment) {
return new JLabel( label, horizontalAlignment ) { return new JLabel( label, horizontalAlignment ) {
{ {
setFont( Res.fonts().controlFont( TEXT_SIZE_CONTROL ) );
setAlignmentX( LEFT_ALIGNMENT ); setAlignmentX( LEFT_ALIGNMENT );
} }
@@ -419,7 +425,6 @@ public abstract class Components {
public static JCheckBox checkBox(final String label) { public static JCheckBox checkBox(final String label) {
return new JCheckBox( label ) { return new JCheckBox( label ) {
{ {
setFont( Res.fonts().controlFont( TEXT_SIZE_CONTROL ) );
setBackground( null ); setBackground( null );
setAlignmentX( LEFT_ALIGNMENT ); setAlignmentX( LEFT_ALIGNMENT );
} }
@@ -433,17 +438,17 @@ public abstract class Components {
public static <E> JComboBox<E> comboBox(final E[] values, final Function<E, String> valueTransformer, public static <E> JComboBox<E> comboBox(final E[] values, final Function<E, String> valueTransformer,
@Nullable final E selectedItem, @Nullable final Consumer<E> selectionConsumer) { @Nullable final E selectedItem, @Nullable final Consumer<E> selectionConsumer) {
return comboBox( CollectionListModel.copy( values ).selection( selectedItem, selectionConsumer ), valueTransformer ); return comboBox( new CollectionListModel<>( values ).selection( selectedItem, selectionConsumer ), valueTransformer );
} }
public static <E> JComboBox<E> comboBox(final Collection<E> values, final Function<E, String> valueTransformer, public static <E> JComboBox<E> comboBox(final Collection<E> values, final Function<E, String> valueTransformer,
@Nullable final Consumer<E> selectionConsumer) { @Nullable final Consumer<E> selectionConsumer) {
return comboBox( CollectionListModel.copy( values ).selection( selectionConsumer ), valueTransformer ); return comboBox( new CollectionListModel<>( values ).selection( selectionConsumer ), valueTransformer );
} }
public static <E> JComboBox<E> comboBox(final Collection<E> values, final Function<E, String> valueTransformer, public static <E> JComboBox<E> comboBox(final Collection<E> values, final Function<E, String> valueTransformer,
@Nullable final E selectedItem, @Nullable final Consumer<E> selectionConsumer) { @Nullable final E selectedItem, @Nullable final Consumer<E> selectionConsumer) {
return comboBox( CollectionListModel.copy( values ).selection( selectedItem, selectionConsumer ), valueTransformer ); return comboBox( new CollectionListModel<>( values ).selection( selectedItem, selectionConsumer ), valueTransformer );
} }
public static <E> JComboBox<E> comboBox(final ComboBoxModel<E> model, final Function<E, String> valueTransformer) { public static <E> JComboBox<E> comboBox(final ComboBoxModel<E> model, final Function<E, String> valueTransformer) {
@@ -456,8 +461,9 @@ public abstract class Components {
@SuppressWarnings({ "unchecked", "SerializableStoresNonSerializable" }) @SuppressWarnings({ "unchecked", "SerializableStoresNonSerializable" })
public Component getListCellRendererComponent(final JList<?> list, final Object value, final int index, public Component getListCellRendererComponent(final JList<?> list, final Object value, final int index,
final boolean isSelected, final boolean cellHasFocus) { final boolean isSelected, final boolean cellHasFocus) {
String label = valueTransformer.apply( (E) value );
super.getListCellRendererComponent( super.getListCellRendererComponent(
list, valueTransformer.apply( (E) value ), index, isSelected, cellHasFocus ); list, Strings.isNullOrEmpty( label )? " ": label, index, isSelected, cellHasFocus );
setBorder( BorderFactory.createEmptyBorder( 0, 4, 0, 4 ) ); setBorder( BorderFactory.createEmptyBorder( 0, 4, 0, 4 ) );
return this; return this;
@@ -474,6 +480,24 @@ public abstract class Components {
}; };
} }
public static JEditorPane linkLabel(@NonNls final String html) {
return new JEditorPane( "text/html", "<html><body style='width:640;font-family:sans-serif'>" + html ) {
{
setOpaque( false );
setEditable( false );
addHyperlinkListener( event -> {
if (event.getEventType() == HyperlinkEvent.EventType.ACTIVATED)
try {
Platform.get().open( event.getURL().toURI() );
}
catch (final URISyntaxException e) {
logger.err( e, "After triggering hyperlink: %s", event );
}
} );
}
};
}
public static class GradientPanel extends JPanel { public static class GradientPanel extends JPanel {
@Nullable @Nullable

View File

@@ -0,0 +1,27 @@
package com.lyndir.masterpassword.gui.util;
import java.util.function.Consumer;
import javax.annotation.Nullable;
/**
* @author lhunath, 2018-08-23
*/
public class ConsumingTrigger<T> implements Consumer<T> {
private final Runnable trigger;
@Nullable
private T value;
public ConsumingTrigger(final Runnable trigger) {
this.trigger = trigger;
}
@Override
public void accept(final T t) {
value = t;
trigger.run();
}
}

View File

@@ -0,0 +1,95 @@
package com.lyndir.masterpassword.gui.util;
import com.lyndir.lhunath.opal.system.logging.Logger;
import java.util.function.Consumer;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
/**
* @author lhunath, 2018-08-24
*/
public class DocumentModel implements Selectable<String, DocumentModel> {
private static final Logger logger = Logger.get( DocumentModel.class );
private final Document document;
@Nullable
private DocumentListener documentListener;
public DocumentModel(final Document document) {
this.document = document;
}
@Nonnull
public Document getDocument() {
return document;
}
@Nullable
public String getText() {
try {
return (document.getLength() > 0)? document.getText( 0, document.getLength() ): null;
}
catch (final BadLocationException e) {
logger.wrn( "While getting text for model", e );
return null;
}
}
public void setText(@Nullable final String text) {
try {
if (document.getLength() > 0)
document.remove( 0, document.getLength() );
if (text != null)
document.insertString( 0, text, null );
}
catch (final BadLocationException e) {
logger.err( "While setting text for model", e );
}
}
@Override
public DocumentModel selection(@Nullable final Consumer<String> selectionConsumer) {
if (documentListener != null)
document.removeDocumentListener( documentListener );
if (selectionConsumer != null)
document.addDocumentListener( documentListener = new DocumentListener() {
@Override
public void insertUpdate(final DocumentEvent e) {
trigger();
}
@Override
public void removeUpdate(final DocumentEvent e) {
trigger();
}
@Override
public void changedUpdate(final DocumentEvent e) {
trigger();
}
private void trigger() {
selectionConsumer.accept( getText() );
}
} );
return this;
}
@Override
public DocumentModel selection(@Nullable final String selectedItem, @Nullable final Consumer<String> selectionConsumer) {
selection( selectionConsumer );
setText( selectedItem );
return this;
}
}

View File

@@ -25,12 +25,13 @@ import com.google.common.io.Resources;
import com.google.common.util.concurrent.*; import com.google.common.util.concurrent.*;
import com.lyndir.lhunath.opal.system.logging.Logger; import com.lyndir.lhunath.opal.system.logging.Logger;
import com.lyndir.masterpassword.MPIdenticon; import com.lyndir.masterpassword.MPIdenticon;
import com.lyndir.masterpassword.gui.SwingExecutorService;
import java.awt.*; import java.awt.*;
import java.io.IOException; import java.io.IOException;
import java.lang.ref.SoftReference; import java.lang.ref.SoftReference;
import java.util.Map; import java.util.Map;
import java.util.concurrent.*; import java.util.concurrent.*;
import java.util.function.Consumer;
import javax.annotation.Nullable;
import javax.swing.*; import javax.swing.*;
import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NonNls;
import org.joda.time.*; import org.joda.time.*;
@@ -72,6 +73,15 @@ public abstract class Res {
return job( job, 0, TimeUnit.MILLISECONDS ); return job( job, 0, TimeUnit.MILLISECONDS );
} }
public static <V> void job(final Callable<V> job, final Consumer<V> callback) {
Futures.addCallback( job( job, 0, TimeUnit.MILLISECONDS ), new FailableCallback<V>( logger ) {
@Override
public void onSuccess(@Nullable final V result) {
callback.accept( result );
}
}, uiExecutor() );
}
public static <V> ListenableFuture<V> job(final Callable<V> job, final long delay, final TimeUnit timeUnit) { public static <V> ListenableFuture<V> job(final Callable<V> job, final long delay, final TimeUnit timeUnit) {
return jobExecutor.schedule( job, delay, timeUnit ); return jobExecutor.schedule( job, delay, timeUnit );
} }
@@ -138,6 +148,10 @@ public abstract class Res {
return icon( "media/icon_import.png" ); return icon( "media/icon_import.png" );
} }
public Icon help() {
return icon( "media/icon_help.png" );
}
public Icon export() { public Icon export() {
return icon( "media/icon_export.png" ); return icon( "media/icon_export.png" );
} }
@@ -146,6 +160,14 @@ public abstract class Res {
return icon( "media/icon_settings.png" ); return icon( "media/icon_settings.png" );
} }
public Icon edit() {
return icon( "media/icon_edit.png" );
}
public Icon key() {
return icon( "media/icon_key.png" );
}
public Icon avatar(final int index) { public Icon avatar(final int index) {
return icon( strf( "media/avatar-%d.png", index % avatars() ) ); return icon( strf( "media/avatar-%d.png", index % avatars() ) );
} }

View File

@@ -11,5 +11,5 @@ public interface Selectable<E, T> {
T selection(@Nullable Consumer<E> selectionConsumer); T selection(@Nullable Consumer<E> selectionConsumer);
T selection(E selectedItem, @Nullable Consumer<E> selectionConsumer); T selection(@Nullable E selectedItem, @Nullable Consumer<E> selectionConsumer);
} }

View File

@@ -1,4 +1,4 @@
package com.lyndir.masterpassword.gui; package com.lyndir.masterpassword.gui.util;
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.*; import static com.lyndir.lhunath.opal.system.util.ObjectUtils.*;
@@ -29,9 +29,9 @@ public class SwingExecutorService extends AbstractExecutorService {
@Override @Override
public void shutdown() { public void shutdown() {
shutdown = true;
synchronized (pendingCommands) { synchronized (pendingCommands) {
shutdown = true;
if (pendingCommands.isEmpty()) if (pendingCommands.isEmpty())
terminated.add( true ); terminated.add( true );
} }
@@ -49,7 +49,9 @@ public class SwingExecutorService extends AbstractExecutorService {
@Override @Override
public boolean isShutdown() { public boolean isShutdown() {
return shutdown; synchronized (pendingCommands) {
return shutdown;
}
} }
@Override @Override
@@ -65,10 +67,10 @@ public class SwingExecutorService extends AbstractExecutorService {
@Override @Override
public void execute(@NotNull final Runnable command) { public void execute(@NotNull final Runnable command) {
if (shutdown)
throw new RejectedExecutionException( "Executor is shut down." );
synchronized (pendingCommands) { synchronized (pendingCommands) {
if (shutdown)
throw new RejectedExecutionException( "Executor is shut down." );
pendingCommands.add( command ); pendingCommands.add( command );
} }

View File

@@ -109,13 +109,14 @@ public class UnsignedIntegerModel extends SpinnerNumberModel implements Selectab
} }
@Override @Override
public UnsignedIntegerModel selection(final UnsignedInteger selectedItem, @Nullable final Consumer<UnsignedInteger> selectionConsumer) { public UnsignedIntegerModel selection(@Nullable final UnsignedInteger selectedItem,
@Nullable final Consumer<UnsignedInteger> selectionConsumer) {
if (changeListener != null) { if (changeListener != null) {
removeChangeListener( changeListener ); removeChangeListener( changeListener );
changeListener = null; changeListener = null;
} }
setValue( selectedItem ); setValue( (selectedItem != null)? selectedItem: getMinimum() );
return selection( selectionConsumer ); return selection( selectionConsumer );
} }

View File

@@ -3,9 +3,9 @@ package com.lyndir.masterpassword.gui.util.platform;
import com.apple.eawt.*; import com.apple.eawt.*;
import com.apple.eio.FileManager; import com.apple.eio.FileManager;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.google.common.base.Throwables; import com.lyndir.lhunath.opal.system.logging.Logger;
import java.io.File; import java.io.*;
import java.io.FileNotFoundException; import java.net.URI;
/** /**
@@ -13,7 +13,8 @@ import java.io.FileNotFoundException;
*/ */
public class ApplePlatform implements IPlatform { public class ApplePlatform implements IPlatform {
static Application application = Preconditions.checkNotNull( private static final Logger logger = Logger.get( ApplePlatform.class );
private static final Application application = Preconditions.checkNotNull(
Application.getApplication(), "Not an Apple Java application." ); Application.getApplication(), "Not an Apple Java application." );
@Override @Override
@@ -37,12 +38,31 @@ public class ApplePlatform implements IPlatform {
return true; return true;
} }
@Override
public boolean requestForeground() {
application.requestForeground( true );
return true;
}
@Override @Override
public boolean show(final File file) { public boolean show(final File file) {
try { try {
return FileManager.revealInFinder( file ); return FileManager.revealInFinder( file );
} }
catch (final FileNotFoundException ignored) { catch (final FileNotFoundException e) {
logger.err( e, "While showing: %s", file );
return false;
}
}
@Override
public boolean open(final URI url) {
try {
FileManager.openURL( url.toString() );
return true;
}
catch (final IOException e) {
logger.err( e, "While opening: %s", url );
return false; return false;
} }
} }

View File

@@ -1,6 +1,7 @@
package com.lyndir.masterpassword.gui.util.platform; package com.lyndir.masterpassword.gui.util.platform;
import java.io.File; import java.io.File;
import java.net.URI;
/** /**
@@ -18,8 +19,18 @@ public class BasePlatform implements IPlatform {
return false; return false;
} }
@Override
public boolean requestForeground() {
return false;
}
@Override @Override
public boolean show(final File file) { public boolean show(final File file) {
return false; return false;
} }
@Override
public boolean open(final URI url) {
return false;
}
} }

View File

@@ -1,6 +1,8 @@
package com.lyndir.masterpassword.gui.util.platform; package com.lyndir.masterpassword.gui.util.platform;
import java.io.File; import java.io.File;
import java.net.URI;
import java.net.URL;
/** /**
@@ -12,5 +14,9 @@ public interface IPlatform {
boolean installAppReopenHandler(Runnable handler); boolean installAppReopenHandler(Runnable handler);
boolean requestForeground();
boolean show(File file); boolean show(File file);
boolean open(URI url);
} }

View File

@@ -1,8 +1,11 @@
package com.lyndir.masterpassword.gui.util.platform; package com.lyndir.masterpassword.gui.util.platform;
import com.lyndir.lhunath.opal.system.logging.Logger;
import java.awt.*; import java.awt.*;
import java.awt.desktop.*; import java.awt.desktop.*;
import java.io.File; import java.io.File;
import java.io.IOException;
import java.net.URI;
/** /**
@@ -11,9 +14,12 @@ import java.io.File;
@SuppressWarnings("Since15") @SuppressWarnings("Since15")
public class JDK9Platform implements IPlatform { public class JDK9Platform implements IPlatform {
private static final Logger logger = Logger.get( JDK9Platform.class );
private static final Desktop desktop = Desktop.getDesktop();
@Override @Override
public boolean installAppForegroundHandler(final Runnable handler) { public boolean installAppForegroundHandler(final Runnable handler) {
Desktop.getDesktop().addAppEventListener( new AppForegroundListener() { desktop.addAppEventListener( new AppForegroundListener() {
@Override @Override
public void appRaisedToForeground(final AppForegroundEvent e) { public void appRaisedToForeground(final AppForegroundEvent e) {
handler.run(); handler.run();
@@ -28,7 +34,13 @@ public class JDK9Platform implements IPlatform {
@Override @Override
public boolean installAppReopenHandler(final Runnable handler) { public boolean installAppReopenHandler(final Runnable handler) {
Desktop.getDesktop().addAppEventListener( (AppReopenedListener) e -> handler.run() ); desktop.addAppEventListener( (AppReopenedListener) e -> handler.run() );
return true;
}
@Override
public boolean requestForeground() {
desktop.requestForeground( true );
return true; return true;
} }
@@ -37,7 +49,19 @@ public class JDK9Platform implements IPlatform {
if (!file.exists()) if (!file.exists())
return false; return false;
Desktop.getDesktop().browseFileDirectory( file ); desktop.browseFileDirectory( file );
return true; return true;
} }
@Override
public boolean open(final URI url) {
try {
desktop.browse( url );
return true;
}
catch (final IOException e) {
logger.err( e, "While opening: %s", url );
return false;
}
}
} }

View File

@@ -9,8 +9,6 @@ import com.lyndir.masterpassword.model.MPUser;
import com.lyndir.masterpassword.model.impl.MPFileUser; import com.lyndir.masterpassword.model.impl.MPFileUser;
import com.lyndir.masterpassword.model.impl.MPFileUserManager; import com.lyndir.masterpassword.model.impl.MPFileUserManager;
import java.awt.*; import java.awt.*;
import java.util.Collection;
import java.util.concurrent.CopyOnWriteArraySet;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.swing.*; import javax.swing.*;
@@ -25,9 +23,7 @@ public class FilesPanel extends JPanel implements MPFileUserManager.Listener, Ma
"Click to change the user's avatar." ); "Click to change the user's avatar." );
private final CollectionListModel<MPUser<?>> usersModel = private final CollectionListModel<MPUser<?>> usersModel =
CollectionListModel.<MPUser<?>>copy( MPFileUserManager.get().getFiles() ).selection( MasterPassword.get()::activateUser ); new CollectionListModel<MPUser<?>>( MPFileUserManager.get().getFiles() ).selection( MasterPassword.get()::activateUser );
private final JComboBox<? extends MPUser<?>> userField =
Components.comboBox( usersModel, user -> ifNotNull( user, MPUser::getFullName ) );
protected FilesPanel() { protected FilesPanel() {
setOpaque( false ); setOpaque( false );
@@ -46,7 +42,7 @@ public class FilesPanel extends JPanel implements MPFileUserManager.Listener, Ma
add( Components.strut( Components.margin() ) ); add( Components.strut( Components.margin() ) );
// User Selection // User Selection
add( userField ); add( Components.comboBox( usersModel, user -> ifNotNull( user, MPUser::getFullName ) ) );
MPFileUserManager.get().addListener( this ); MPFileUserManager.get().addListener( this );
MasterPassword.get().addListener( this ); MasterPassword.get().addListener( this );

View File

@@ -3,12 +3,10 @@ package com.lyndir.masterpassword.gui.view;
import com.lyndir.lhunath.opal.system.logging.Logger; import com.lyndir.lhunath.opal.system.logging.Logger;
import com.lyndir.masterpassword.gui.util.Components; import com.lyndir.masterpassword.gui.util.Components;
import com.lyndir.masterpassword.gui.util.Res; import com.lyndir.masterpassword.gui.util.Res;
import com.lyndir.masterpassword.model.MPUser;
import com.lyndir.masterpassword.model.impl.MPFileUserManager; import com.lyndir.masterpassword.model.impl.MPFileUserManager;
import java.awt.*; import java.awt.*;
import java.awt.event.ComponentAdapter; import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent; import java.awt.event.ComponentEvent;
import javax.annotation.Nullable;
import javax.swing.*; import javax.swing.*;
import javax.swing.border.BevelBorder; import javax.swing.border.BevelBorder;
@@ -21,26 +19,24 @@ public class MasterPasswordFrame extends JFrame {
private static final Logger logger = Logger.get( MasterPasswordFrame.class ); private static final Logger logger = Logger.get( MasterPasswordFrame.class );
@SuppressWarnings("FieldCanBeLocal") private final UserContentPanel userContent;
private final Components.GradientPanel root = Components.borderPanel( Res.colors().frameBg(), BoxLayout.PAGE_AXIS );
private final FilesPanel filesPanel = new FilesPanel();
private final JPanel userPanel = Components.panel( new BorderLayout( 0, 0 ) );
private final UserContentPanel userContent = new UserContentPanel();
@SuppressWarnings("MagicNumber") @SuppressWarnings("MagicNumber")
public MasterPasswordFrame() { public MasterPasswordFrame() {
super( "Master Password" ); super( "Master Password" );
setContentPane( root ); JPanel root, userPanel;
root.add( filesPanel ); setContentPane( root = Components.borderPanel( Res.colors().frameBg(), BoxLayout.PAGE_AXIS ) );
root.add( Components.strut() );
root.add( userPanel ); root.add( new FilesPanel() );
root.add( Components.strut() );
root.add( userPanel = Components.panel( new BorderLayout( 0, 0 ) ) );
userPanel.add( userContent.getUserToolbar(), BorderLayout.LINE_START );
userPanel.add( userContent.getSiteToolbar(), BorderLayout.LINE_END );
userPanel.add( Components.borderPanel( userPanel.add( Components.borderPanel(
BorderFactory.createBevelBorder( BevelBorder.RAISED, Res.colors().controlBorder(), Res.colors().frameBg() ), BorderFactory.createBevelBorder( BevelBorder.RAISED, Res.colors().controlBorder(), Res.colors().frameBg() ),
Res.colors().controlBg(), BoxLayout.PAGE_AXIS, userContent ), BorderLayout.CENTER ); Res.colors().controlBg(), BoxLayout.PAGE_AXIS, userContent = new UserContentPanel() ), BorderLayout.CENTER );
userPanel.add( userContent.getUserToolbar(), BorderLayout.LINE_START );
userPanel.add( userContent.getSiteToolbar(), BorderLayout.LINE_END );
addComponentListener( new ComponentHandler() ); addComponentListener( new ComponentHandler() );
setPreferredSize( new Dimension( 800, 560 ) ); setPreferredSize( new Dimension( 800, 560 ) );

View File

@@ -8,13 +8,14 @@ import com.google.common.primitives.UnsignedInteger;
import com.lyndir.lhunath.opal.system.logging.Logger; import com.lyndir.lhunath.opal.system.logging.Logger;
import com.lyndir.lhunath.opal.system.util.ObjectUtils; import com.lyndir.lhunath.opal.system.util.ObjectUtils;
import com.lyndir.masterpassword.*; import com.lyndir.masterpassword.*;
import com.lyndir.masterpassword.gui.MPGuiConstants;
import com.lyndir.masterpassword.gui.MasterPassword; import com.lyndir.masterpassword.gui.MasterPassword;
import com.lyndir.masterpassword.gui.model.MPIncognitoUser; import com.lyndir.masterpassword.gui.model.*;
import com.lyndir.masterpassword.gui.model.MPNewSite;
import com.lyndir.masterpassword.gui.util.*; import com.lyndir.masterpassword.gui.util.*;
import com.lyndir.masterpassword.gui.util.Platform; import com.lyndir.masterpassword.gui.util.Platform;
import com.lyndir.masterpassword.model.*; import com.lyndir.masterpassword.model.*;
import com.lyndir.masterpassword.model.impl.*; import com.lyndir.masterpassword.model.impl.*;
import com.lyndir.masterpassword.util.Utilities;
import java.awt.*; import java.awt.*;
import java.awt.datatransfer.StringSelection; import java.awt.datatransfer.StringSelection;
import java.awt.datatransfer.Transferable; import java.awt.datatransfer.Transferable;
@@ -33,6 +34,7 @@ import javax.annotation.Nullable;
import javax.swing.*; import javax.swing.*;
import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener; import javax.swing.event.DocumentListener;
import javax.swing.text.PlainDocument;
/** /**
@@ -41,14 +43,18 @@ import javax.swing.event.DocumentListener;
@SuppressWarnings("SerializableStoresNonSerializable") @SuppressWarnings("SerializableStoresNonSerializable")
public class UserContentPanel extends JPanel implements MasterPassword.Listener, MPUser.Listener { public class UserContentPanel extends JPanel implements MasterPassword.Listener, MPUser.Listener {
private static final Random random = new Random(); private static final Random random = new Random();
private static final Logger logger = Logger.get( UserContentPanel.class ); private static final int SIZE_RESULT = 48;
private static final JButton iconButton = Components.button( Res.icons().user(), null, null ); private static final Logger logger = Logger.get( UserContentPanel.class );
private static final JButton iconButton = Components.button( Res.icons().user(), null, null );
private static final KeyStroke copyLoginKeyStroke = KeyStroke.getKeyStroke( KeyEvent.VK_ENTER, InputEvent.SHIFT_DOWN_MASK );
private final JButton addButton = Components.button( Res.icons().add(), event -> addUser(), private final JButton addButton = Components.button( Res.icons().add(), event -> addUser(),
"Add a new user to Master Password." ); "Add a new user to Master Password." );
private final JButton importButton = Components.button( Res.icons().import_(), event -> importUser(), private final JButton importButton = Components.button( Res.icons().import_(), event -> importUser(),
"Import a user from a backup file into Master Password." ); "Import a user from a backup file into Master Password." );
private final JButton helpButton = Components.button( Res.icons().help(), event -> showHelp(),
"Show information on how to use Master Password." );
private final JPanel userToolbar = Components.panel( BoxLayout.PAGE_AXIS ); private final JPanel userToolbar = Components.panel( BoxLayout.PAGE_AXIS );
private final JPanel siteToolbar = Components.panel( BoxLayout.PAGE_AXIS ); private final JPanel siteToolbar = Components.panel( BoxLayout.PAGE_AXIS );
@@ -128,7 +134,7 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener,
} }
private void addUser() { private void addUser() {
JTextField nameField = Components.textField( "Robert Lee Mitchell", null ); JTextField nameField = Components.textField( "Robert Lee Mitchell", null );
JCheckBox incognitoField = Components.checkBox( "<html>Incognito <em>(Do not save this user to disk)</em></html>" ); JCheckBox incognitoField = Components.checkBox( "<html>Incognito <em>(Do not save this user to disk)</em></html>" );
if (JOptionPane.OK_OPTION != Components.showDialog( this, "Add User", new JOptionPane( Components.panel( if (JOptionPane.OK_OPTION != Components.showDialog( this, "Add User", new JOptionPane( Components.panel(
BoxLayout.PAGE_AXIS, BoxLayout.PAGE_AXIS,
@@ -208,7 +214,7 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener,
this, strf( "<html>Couldn't read import file:<br><pre>%s</pre></html>.", e.getLocalizedMessage() ), this, strf( "<html>Couldn't read import file:<br><pre>%s</pre></html>.", e.getLocalizedMessage() ),
"Import Failed", JOptionPane.ERROR_MESSAGE ); "Import Failed", JOptionPane.ERROR_MESSAGE );
} }
catch (MPMarshalException e) { catch (final MPMarshalException e) {
logger.err( e, "While parsing user import file." ); logger.err( e, "While parsing user import file." );
JOptionPane.showMessageDialog( JOptionPane.showMessageDialog(
this, strf( "<html>Couldn't parse import file:<br><pre>%s</pre></html>.", e.getLocalizedMessage() ), this, strf( "<html>Couldn't parse import file:<br><pre>%s</pre></html>.", e.getLocalizedMessage() ),
@@ -216,6 +222,32 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener,
} }
} }
private void showHelp() {
JOptionPane.showMessageDialog( this, Components.linkLabel( strf(
"<h1>Master Password - v%s</h1>"
+ "<p>The primary goal of this application is to provide a reliable security solution that also "
+ "makes you independent from your computer. If you lose access to this computer or your data, "
+ "the application can regenerate all your secrets from scratch on any new device.</p>"
+ "<h2>Opening Master Password</h2>"
+ "<p>To use Master Password, simply open the application on your computer. "
+ "Once running, you can bring up the user interface at any time by pressing the keys "
+ "<strong><code>%s+%s</code></strong>."
+ "<h2>Persistence</h2>"
+ "<p>Though at the core, Master Password does not require the use of any form of data "
+ "storage, the application does remember the names of the sites you've used in the past to "
+ "make it easier for you to use them again in the future. All user information is saved in "
+ "files on your computer at the following location:<br><pre>%s</pre></p>"
+ "<p>You can read, modify, backup or place new files in this location as you see fit. "
+ "Some people even configure this location to be synced between their different computers "
+ "using services such as those provided by SpiderOak or Dropbox.</p>"
+ "<hr><p><a href='https://masterpassword.app'>https://masterpassword.app</a> — by Maarten Billemont</p>",
MasterPassword.get().version(),
InputEvent.getModifiersExText( MPGuiConstants.ui_hotkey.getModifiers() ),
KeyEvent.getKeyText( MPGuiConstants.ui_hotkey.getKeyCode() ),
MPFileUserManager.get().getPath().getAbsolutePath() ) ),
"About Master Password", JOptionPane.INFORMATION_MESSAGE );
}
private enum ContentMode { private enum ContentMode {
NO_USER, NO_USER,
AUTHENTICATE, AUTHENTICATE,
@@ -239,6 +271,8 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener,
userToolbar.add( addButton ); userToolbar.add( addButton );
userToolbar.add( importButton ); userToolbar.add( importButton );
userToolbar.add( Box.createGlue() );
userToolbar.add( helpButton );
add( Box.createGlue() ); add( Box.createGlue() );
add( Components.heading( "Select a user to proceed." ) ); add( Components.heading( "Select a user to proceed." ) );
@@ -259,9 +293,9 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener,
private final JButton resetButton = Components.button( Res.icons().reset(), event -> resetUser(), private final JButton resetButton = Components.button( Res.icons().reset(), event -> resetUser(),
"Change the master password for this user." ); "Change the master password for this user." );
private final JPasswordField masterPasswordField = Components.passwordField(); private final JPasswordField masterPasswordField;
private final JLabel errorLabel = Components.label(); private final JLabel errorLabel;
private final JLabel identiconLabel = Components.label( SwingConstants.CENTER ); private final JLabel identiconLabel;
private Future<?> identiconJob; private Future<?> identiconJob;
@@ -275,20 +309,22 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener,
userToolbar.add( exportButton ); userToolbar.add( exportButton );
userToolbar.add( deleteButton ); userToolbar.add( deleteButton );
userToolbar.add( resetButton ); userToolbar.add( resetButton );
userToolbar.add( Box.createGlue() );
userToolbar.add( helpButton );
add( Components.heading( user.getFullName(), SwingConstants.CENTER ) ); add( Components.heading( user.getFullName(), SwingConstants.CENTER ) );
add( Components.strut() ); add( Components.strut() );
add( identiconLabel ); add( identiconLabel = Components.label( SwingConstants.CENTER ) );
identiconLabel.setFont( Res.fonts().emoticonsFont( Components.TEXT_SIZE_CONTROL ) ); identiconLabel.setFont( Res.fonts().emoticonsFont( Components.TEXT_SIZE_CONTROL ) );
add( Box.createGlue() ); add( Box.createGlue() );
add( Components.label( "Master Password:" ) ); add( Components.label( "Master Password:" ) );
add( Components.strut() ); add( Components.strut() );
add( masterPasswordField ); add( masterPasswordField = Components.passwordField() );
masterPasswordField.addActionListener( this ); masterPasswordField.addActionListener( this );
masterPasswordField.getDocument().addDocumentListener( this ); masterPasswordField.getDocument().addDocumentListener( this );
add( errorLabel ); add( errorLabel = Components.label() );
errorLabel.setForeground( Res.colors().errorFg() ); errorLabel.setForeground( Res.colors().errorFg() );
add( Box.createGlue() ); add( Box.createGlue() );
} }
@@ -427,29 +463,31 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener,
private final class AuthenticatedUserPanel extends JPanel implements KeyListener, MPUser.Listener { private final class AuthenticatedUserPanel extends JPanel implements KeyListener, MPUser.Listener {
public static final int SIZE_RESULT = 48;
private final JButton userButton = Components.button( Res.icons().user(), event -> showUserPreferences(), private final JButton userButton = Components.button( Res.icons().user(), event -> showUserPreferences(),
"Show user preferences." ); "Show user preferences." );
private final JButton logoutButton = Components.button( Res.icons().lock(), event -> logoutUser(), private final JButton logoutButton = Components.button( Res.icons().lock(), event -> logoutUser(),
"Sign out and lock user." ); "Sign out and lock user." );
private final JButton settingsButton = Components.button( Res.icons().settings(), event -> showSiteSettings(), private final JButton settingsButton = Components.button( Res.icons().settings(), event -> showSiteSettings(),
"Show site settings." ); "Show site settings." );
private final JButton questionsButton = Components.button( Res.icons().question(), null, private final JButton questionsButton = Components.button( Res.icons().question(), event -> showSiteQuestions(),
"Show site recovery questions." ); "Show site recovery questions." );
private final JButton editButton = Components.button( Res.icons().edit(), event -> showSiteValues(),
"Set/save personal password/login." );
private final JButton keyButton = Components.button( Res.icons().key(), event -> showSiteKeys(),
"Cryptographic site keys." );
private final JButton deleteButton = Components.button( Res.icons().delete(), event -> deleteSite(), private final JButton deleteButton = Components.button( Res.icons().delete(), event -> deleteSite(),
"Delete the site from the user." ); "Delete the site from the user." );
@Nonnull @Nonnull
private final MPUser<?> user; private final MPUser<?> user;
private final JLabel passwordLabel = Components.label( SwingConstants.CENTER ); private final JLabel passwordLabel;
private final JLabel passwordField = Components.heading( SwingConstants.CENTER ); private final JLabel passwordField;
private final JLabel queryLabel = Components.label(); private final JLabel answerLabel;
private final JTextField queryField = Components.textField( null, this::updateSites ); private final JLabel answerField;
private final CollectionListModel<MPSite<?>> sitesModel = private final JLabel queryLabel;
new CollectionListModel<MPSite<?>>().selection( this::showSiteResult ); private final JTextField queryField;
private final JList<MPSite<?>> sitesList = private final CollectionListModel<MPSite<?>> sitesModel;
Components.list( sitesModel, this::getSiteDescription ); private final JList<MPSite<?>> sitesList;
private Future<?> updateSitesJob; private Future<?> updateSitesJob;
@@ -461,33 +499,54 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener,
userToolbar.add( addButton ); userToolbar.add( addButton );
userToolbar.add( userButton ); userToolbar.add( userButton );
userToolbar.add( logoutButton ); userToolbar.add( logoutButton );
userToolbar.add( Box.createGlue() );
userToolbar.add( helpButton );
siteToolbar.add( settingsButton ); siteToolbar.add( settingsButton );
siteToolbar.add( questionsButton ); siteToolbar.add( questionsButton );
siteToolbar.add( editButton );
siteToolbar.add( keyButton );
siteToolbar.add( deleteButton ); siteToolbar.add( deleteButton );
settingsButton.setEnabled( false ); settingsButton.setEnabled( false );
questionsButton.setEnabled( false );
editButton.setEnabled( false );
keyButton.setEnabled( false );
deleteButton.setEnabled( false ); deleteButton.setEnabled( false );
answerLabel = Components.label( "Answer:" );
answerField = Components.heading( SwingConstants.CENTER );
answerField.setForeground( Res.colors().highlightFg() );
answerField.setFont( Res.fonts().bigValueFont( SIZE_RESULT ) );
add( Components.heading( user.getFullName(), SwingConstants.CENTER ) ); add( Components.heading( user.getFullName(), SwingConstants.CENTER ) );
add( passwordLabel ); add( passwordLabel = Components.label( SwingConstants.CENTER ) );
add( passwordField ); add( passwordField = Components.heading( SwingConstants.CENTER ) );
passwordField.setForeground( Res.colors().highlightFg() ); passwordField.setForeground( Res.colors().highlightFg() );
passwordField.setFont( Res.fonts().bigValueFont( SIZE_RESULT ) ); passwordField.setFont( Res.fonts().bigValueFont( SIZE_RESULT ) );
add( Box.createGlue() ); add( Box.createGlue() );
add( Components.strut() ); add( Components.strut() );
add( queryLabel ); add( queryLabel = Components.label() );
queryLabel.setText( strf( "%s's password for:", user.getFullName() ) ); queryLabel.setText( strf( "%s's password for:", user.getFullName() ) );
add( queryField ); add( queryField = Components.textField( null, this::updateSites ) );
queryField.putClientProperty( "JTextField.variant", "search" ); queryField.putClientProperty( "JTextField.variant", "search" );
queryField.addActionListener( event -> useSite() ); queryField.addActionListener( this::useSite );
queryField.getInputMap().put( copyLoginKeyStroke, JTextField.notifyAction );
queryField.addKeyListener( this ); queryField.addKeyListener( this );
queryField.requestFocusInWindow(); queryField.requestFocusInWindow();
add( Components.strut() ); add( Components.strut() );
add( Components.scrollPane( sitesList ) );
sitesModel.registerList( sitesList ); add( Components.scrollPane( sitesList = Components.list(
add( Box.createGlue() ); sitesModel = new CollectionListModel<MPSite<?>>().selection( this::showSiteResult ),
this::getSiteDescription ) ) );
add( Components.strut() );
add( Components.label( strf(
"Press %s to copy password, %s+%s to copy login name.",
KeyEvent.getKeyText( KeyEvent.VK_ENTER ),
InputEvent.getModifiersExText( copyLoginKeyStroke.getModifiers() ),
KeyEvent.getKeyText( copyLoginKeyStroke.getKeyCode() ) ) ) );
addHierarchyListener( e -> { addHierarchyListener( e -> {
if (null != SwingUtilities.windowForComponent( this )) if (null != SwingUtilities.windowForComponent( this ))
@@ -537,12 +596,14 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener,
Components.strut() ); Components.strut() );
components.add( Components.label( "Password Type:" ), components.add( Components.label( "Password Type:" ),
Components.comboBox( MPResultType.values(), MPResultType::getLongName, Components.comboBox( MPResultType.values(), type -> getTypeDescription(
type, user.getDefaultType(), user.getAlgorithm().mpw_default_result_type() ),
site.getResultType(), site::setResultType ), site.getResultType(), site::setResultType ),
Components.strut() ); Components.strut() );
components.add( Components.label( "Login Type:" ), components.add( Components.label( "Login Type:" ),
Components.comboBox( MPResultType.values(), MPResultType::getLongName, Components.comboBox( MPResultType.values(), type -> getTypeDescription(
type, user.getAlgorithm().mpw_default_login_type() ),
site.getLoginType(), site::setLoginType ), site.getLoginType(), site::setLoginType ),
Components.strut() ); Components.strut() );
@@ -552,10 +613,179 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener,
Components.textField( fileSite.getUrl(), fileSite::setUrl ), Components.textField( fileSite.getUrl(), fileSite::setUrl ),
Components.strut() ); Components.strut() );
Components.showDialog( this, site.getSiteName(), new JOptionPane( Components.panel( Components.showDialog( this, strf( "Settings for %s", site.getSiteName() ), new JOptionPane( Components.panel(
BoxLayout.PAGE_AXIS, components.build().toArray( new Component[0] ) ) ) ); BoxLayout.PAGE_AXIS, components.build().toArray( new Component[0] ) ) ) );
} }
private String getTypeDescription(final MPResultType type, final MPResultType... defaults) {
boolean isDefault = false;
for (final MPResultType d : defaults)
if (isDefault = type == d)
break;
return strf( "<html>%s%s%s, %s", isDefault? "<b>": "", type.getLongName(), isDefault? "</b>": "", type.getDescription() );
}
public void showSiteQuestions() {
MPSite<?> site = sitesModel.getSelectedItem();
if (site == null)
return;
CollectionListModel<MPQuestion> questionsModel = new CollectionListModel<MPQuestion>().selection( this::showQuestionResult );
JList<MPQuestion> questionsList = Components.list(
questionsModel, question -> Strings.isNullOrEmpty( question.getKeyword() )? "<site>": question.getKeyword() );
JTextField queryField = Components.textField( null, query -> Res.job( () -> {
Collection<MPQuestion> questions = new LinkedList<>( site.findQuestions( query ) );
if (questions.stream().noneMatch( question -> question.getKeyword().equalsIgnoreCase( query ) ))
questions.add( new MPNewQuestion( site, Utilities.ifNotNullElse( query, "" ) ) );
Res.ui( () -> questionsModel.set( questions ) );
} ) );
queryField.putClientProperty( "JTextField.variant", "search" );
queryField.addActionListener( event -> useQuestion( questionsModel.getSelectedItem() ) );
queryField.addKeyListener( new KeyAdapter() {
@Override
public void keyPressed(final KeyEvent event) {
if ((event.getKeyCode() == KeyEvent.VK_UP) || (event.getKeyCode() == KeyEvent.VK_DOWN))
questionsList.dispatchEvent( event );
}
@Override
public void keyReleased(final KeyEvent event) {
if ((event.getKeyCode() == KeyEvent.VK_UP) || (event.getKeyCode() == KeyEvent.VK_DOWN))
questionsList.dispatchEvent( event );
}
} );
Components.showDialog( this, strf( "Recovery answers for %s", site.getSiteName() ), new JOptionPane( Components.panel(
BoxLayout.PAGE_AXIS,
Components.label( "Security Question Keyword:" ), queryField,
Components.strut(),
answerLabel, answerField,
Components.strut(),
Components.scrollPane( questionsList ) ) ) {
@Override
public void selectInitialValue() {
queryField.requestFocusInWindow();
}
} );
}
public void showSiteValues() {
MPSite<?> site = sitesModel.getSelectedItem();
if (site == null)
return;
try {
JTextField passwordField = Components.textField( site.getResult(), null );
JTextField loginField = Components.textField( site.getLogin(), null );
passwordField.setEditable( site.getResultType().getTypeClass() == MPResultTypeClass.Stateful );
loginField.setEditable( site.getLoginType().getTypeClass() == MPResultTypeClass.Stateful );
if (JOptionPane.OK_OPTION == Components.showDialog( this, site.getSiteName(), new JOptionPane(
Components.panel(
BoxLayout.PAGE_AXIS,
Components.label( strf( "<html>Site Login (currently set to: <b>%s</b>):",
getTypeDescription( site.getLoginType() ) ) ),
loginField,
Components.strut(),
Components.label( strf( "<html>Site Password (currently set to: <b>%s</b>):",
getTypeDescription( site.getResultType() ) ) ),
passwordField,
Components.strut(),
Components.label( "<html>To save a personal value in these fields,\n" +
"change the type to <b>Saved</b> in the site's settings." ) ),
JOptionPane.PLAIN_MESSAGE, JOptionPane.OK_CANCEL_OPTION ) {
@Override
public void selectInitialValue() {
passwordField.requestFocusInWindow();
}
} )) {
if (site instanceof MPFileSite) {
MPFileSite fileSite = (MPFileSite) site;
if (site.getResultType().getTypeClass() == MPResultTypeClass.Stateful)
fileSite.setSitePassword( site.getResultType(), passwordField.getText() );
if (site.getLoginType().getTypeClass() == MPResultTypeClass.Stateful)
fileSite.setLoginName( site.getLoginType(), loginField.getText() );
}
}
}
catch (final MPKeyUnavailableException | MPAlgorithmException e) {
logger.err( e, "While computing site edit results." );
}
}
public void showSiteKeys() {
MPSite<?> site = sitesModel.getSelectedItem();
if (site == null)
return;
JTextArea resultField = Components.textArea();
resultField.setEnabled( false );
CollectionListModel<MPKeyPurpose> purposeModel = new CollectionListModel<>( MPKeyPurpose.values() );
DocumentModel contextModel = new DocumentModel( new PlainDocument() );
UnsignedIntegerModel counterModel = new UnsignedIntegerModel( UnsignedInteger.ONE );
CollectionListModel<MPResultType> typeModel = new CollectionListModel<>( MPResultType.values() );
DocumentModel stateModel = new DocumentModel( new PlainDocument() );
Runnable trigger = () -> Res.job( () -> {
try {
MPKeyPurpose purpose = purposeModel.getSelectedItem();
MPResultType type = typeModel.getSelectedItem();
String result = ((purpose == null) || (type == null))? null:
site.getResult( purpose, contextModel.getText(), counterModel.getNumber(), type, stateModel.getText() );
Res.ui( () -> resultField.setText( result ) );
}
catch (final MPKeyUnavailableException | MPAlgorithmException e) {
logger.err( e, "While computing site edit results." );
}
} );
purposeModel.selection( MPKeyPurpose.Authentication, p -> trigger.run() );
contextModel.selection( c -> trigger.run() );
counterModel.selection( c -> trigger.run() );
typeModel.selection( MPResultType.DeriveKey, t -> {
switch (t) {
case DeriveKey:
stateModel.setText( Integer.toString( site.getAlgorithm().mpw_keySize_min() ) );
break;
default:
stateModel.setText( null );
}
trigger.run();
} );
stateModel.selection( c -> trigger.run() );
if (JOptionPane.OK_OPTION == Components.showDialog( this, site.getSiteName(), new JOptionPane( Components.panel(
BoxLayout.PAGE_AXIS,
Components.heading( "Key Calculator" ),
Components.label( "Purpose:" ),
Components.comboBox( purposeModel, MPKeyPurpose::getShortName ),
Components.strut(),
Components.label( "Context:" ),
Components.textField( contextModel.getDocument() ),
Components.label( "Counter:" ),
Components.spinner( counterModel ),
Components.label( "Type:" ),
Components.comboBox( typeModel, this::getTypeDescription ),
Components.label( "State:" ),
Components.scrollPane( Components.textField( stateModel.getDocument() ) ),
Components.strut(),
resultField ) ) {
{
setOptions( new Object[]{ "Copy", "Cancel" } );
setInitialValue( getOptions()[0] );
}
} ))
copyResult( resultField.getText() );
}
public void deleteSite() { public void deleteSite() {
MPSite<?> site = sitesModel.getSelectedItem(); MPSite<?> site = sitesModel.getSelectedItem();
if (site == null) if (site == null)
@@ -591,69 +821,121 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener,
Joiner.on( " - " ).skipNulls().join( parameters.build() ) ); Joiner.on( " - " ).skipNulls().join( parameters.build() ) );
} }
private void useSite() { private void useSite(final ActionEvent event) {
MPSite<?> site = sitesModel.getSelectedItem(); MPSite<?> site = sitesModel.getSelectedItem();
if (site instanceof MPNewSite) { if (site instanceof MPNewSite) {
if (JOptionPane.YES_OPTION == JOptionPane.showConfirmDialog( if (JOptionPane.YES_OPTION == JOptionPane.showConfirmDialog(
this, strf( "<html>Remember the site <strong>%s</strong>?</html>", site.getSiteName() ), this, strf( "<html>Remember the site <strong>%s</strong>?</html>", site.getSiteName() ),
"New Site", JOptionPane.YES_NO_OPTION )) { "New Site", JOptionPane.YES_NO_OPTION )) {
sitesModel.setSelectedItem( user.addSite( site.getSiteName() ) ); sitesModel.setSelectedItem( user.addSite( site.getSiteName() ) );
useSite(); useSite( event );
} }
return; return;
} }
showSiteResult( site, result -> { boolean loginResult = (copyLoginKeyStroke.getModifiers() & event.getModifiers()) != 0;
showSiteResult( site, loginResult, result -> {
if (result == null) if (result == null)
return; return;
if (site instanceof MPFileSite) if (site instanceof MPFileSite)
((MPFileSite) site).use(); ((MPFileSite) site).use();
Transferable clipboardContents = new StringSelection( result ); copyResult( result );
Toolkit.getDefaultToolkit().getSystemClipboard().setContents( clipboardContents, null );
Res.ui( () -> {
Window window = SwingUtilities.windowForComponent( UserContentPanel.this );
if (window != null)
window.dispatchEvent( new WindowEvent( window, WindowEvent.WINDOW_CLOSING ) );
} );
} ); } );
} }
private void showSiteResult(@Nullable final MPSite<?> site) { private void showSiteResult(@Nullable final MPSite<?> site) {
showSiteResult( site, null ); showSiteResult( site, false, result -> {
} );
} }
private void showSiteResult(@Nullable final MPSite<?> site, @Nullable final Consumer<String> resultCallback) { private void showSiteResult(@Nullable final MPSite<?> site, final boolean loginResult, final Consumer<String> resultCallback) {
if (site == null) {
if (resultCallback != null)
resultCallback.accept( null );
Res.ui( () -> {
passwordLabel.setText( " " );
passwordField.setText( " " );
settingsButton.setEnabled( false );
deleteButton.setEnabled( false );
} );
return;
}
Res.job( () -> { Res.job( () -> {
try { try {
String result = site.getResult(); if (site != null)
if (resultCallback != null) return loginResult? site.getLogin(): site.getResult();
resultCallback.accept( result );
Res.ui( () -> {
passwordLabel.setText( strf( "Your password for %s:", site.getSiteName() ) );
passwordField.setText( result );
settingsButton.setEnabled( true );
deleteButton.setEnabled( true );
} );
} }
catch (final MPKeyUnavailableException | MPAlgorithmException e) { catch (final MPKeyUnavailableException | MPAlgorithmException e) {
logger.err( e, "While resolving password for: %s", site ); logger.err( e, "While resolving password for: %s", site );
} }
return null;
}, resultCallback.andThen( result -> Res.ui( () -> {
passwordLabel.setText( ((result != null) && (site != null))? strf( "Your password for %s:", site.getSiteName() ): " " );
passwordField.setText( (result != null)? result: " " );
settingsButton.setEnabled( result != null );
questionsButton.setEnabled( result != null );
editButton.setEnabled( result != null );
keyButton.setEnabled( result != null );
deleteButton.setEnabled( result != null );
} ) ) );
}
private void useQuestion(@Nullable final MPQuestion question) {
if (question instanceof MPNewQuestion) {
if (JOptionPane.YES_OPTION == JOptionPane.showConfirmDialog(
this,
strf( "<html>Remember the security question with keyword <strong>%s</strong>?</html>",
Strings.isNullOrEmpty( question.getKeyword() )? "<empty>": question.getKeyword() ),
"New Question", JOptionPane.YES_NO_OPTION )) {
useQuestion( question.getSite().addQuestion( question.getKeyword() ) );
}
return;
}
showQuestionResult( question, result -> {
if (result == null)
return;
if (question instanceof MPFileQuestion)
((MPFileQuestion) question).use();
copyResult( result );
} );
}
private void showQuestionResult(@Nullable final MPQuestion question) {
showQuestionResult( question, answer -> {
} );
}
private void showQuestionResult(@Nullable final MPQuestion question, final Consumer<String> resultCallback) {
Res.job( () -> {
try {
if (question != null)
return question.getAnswer();
}
catch (final MPKeyUnavailableException | MPAlgorithmException e) {
logger.err( e, "While resolving answer for: %s", question );
}
return null;
}, resultCallback.andThen( answer -> Res.ui( () -> {
if ((answer == null) || (question == null))
answerLabel.setText( " " );
else
answerLabel.setText(
Strings.isNullOrEmpty( question.getKeyword() )?
strf( "<html>Answer for site <b>%s</b>:", question.getSite().getSiteName() ):
strf( "<html>Answer for site <b>%s</b>, of question with keyword <b>%s</b>:",
question.getSite().getSiteName(), question.getKeyword() ) );
answerField.setText( (answer != null)? answer: " " );
} ) ) );
}
private void copyResult(final String result) {
Transferable clipboardContents = new StringSelection( result );
Toolkit.getDefaultToolkit().getSystemClipboard().setContents( clipboardContents, null );
Res.ui( () -> {
Window answerDialog = SwingUtilities.windowForComponent( answerField );
if (answerDialog instanceof Dialog)
answerDialog.setVisible( false );
Window window = SwingUtilities.windowForComponent( UserContentPanel.this );
if (window instanceof Frame)
((Frame) window).setExtendedState( Frame.ICONIFIED );
} ); } );
} }
@@ -678,13 +960,11 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener,
updateSitesJob.cancel( true ); updateSitesJob.cancel( true );
updateSitesJob = Res.job( () -> { updateSitesJob = Res.job( () -> {
Collection<MPSite<?>> sites = new LinkedList<>(); Collection<MPSite<?>> sites = new LinkedList<>( user.findSites( query ) );
if (!Strings.isNullOrEmpty( query )) {
sites.addAll( new LinkedList<>( user.findSites( query ) ) );
if (!Strings.isNullOrEmpty( query ))
if (sites.stream().noneMatch( site -> site.getSiteName().equalsIgnoreCase( query ) )) if (sites.stream().noneMatch( site -> site.getSiteName().equalsIgnoreCase( query ) ))
sites.add( new MPNewSite( user, query ) ); sites.add( new MPNewSite( user, query ) );
}
Res.ui( () -> sitesModel.set( sites ) ); Res.ui( () -> sitesModel.set( sites ) );
} ); } );

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@@ -25,14 +25,15 @@ import org.joda.time.format.ISODateTimeFormat;
/** /**
* @author lhunath, 2016-10-29 * @author lhunath, 2016-10-29
*/ */
public final class MPConstants { public final class MPModelConstants {
/* Environment */ /* Environment */
/** /**
* mpw: default path to look for run configuration files if the platform default is not desired. * mpw: default path to look for run configuration files if the platform default is not desired.
*/ */
public static final String env_rcDir = "MPW_RCDIR"; public static final String env_rcDir = "MPW_RCDIR";
/** /**
* mpw: permit automatic update checks. * mpw: permit automatic update checks.
*/ */

View File

@@ -40,6 +40,13 @@ public interface MPQuestion extends Comparable<MPQuestion> {
void setType(MPResultType type); void setType(MPResultType type);
@Nonnull
default String getAnswer()
throws MPKeyUnavailableException, MPAlgorithmException {
return getAnswer( null );
}
@Nonnull @Nonnull
String getAnswer(@Nullable String state) String getAnswer(@Nullable String state)
throws MPKeyUnavailableException, MPAlgorithmException; throws MPKeyUnavailableException, MPAlgorithmException;

View File

@@ -18,6 +18,7 @@
package com.lyndir.masterpassword.model; package com.lyndir.masterpassword.model;
import com.google.common.collect.ImmutableCollection;
import com.google.common.primitives.UnsignedInteger; import com.google.common.primitives.UnsignedInteger;
import com.lyndir.masterpassword.*; import com.lyndir.masterpassword.*;
import java.util.Collection; import java.util.Collection;
@@ -57,28 +58,38 @@ public interface MPSite<Q extends MPQuestion> extends Comparable<MPSite<?>> {
void setLoginType(@Nullable MPResultType loginType); void setLoginType(@Nullable MPResultType loginType);
@Nullable
default String getResult() default String getResult()
throws MPKeyUnavailableException, MPAlgorithmException { throws MPKeyUnavailableException, MPAlgorithmException {
return getResult( MPKeyPurpose.Authentication ); return getResult( MPKeyPurpose.Authentication );
} }
@Nonnull @Nullable
default String getResult(final MPKeyPurpose keyPurpose) default String getResult(final MPKeyPurpose keyPurpose)
throws MPKeyUnavailableException, MPAlgorithmException { throws MPKeyUnavailableException, MPAlgorithmException {
return getResult( keyPurpose, null ); return getResult( keyPurpose, null );
} }
@Nonnull @Nullable
default String getResult(final MPKeyPurpose keyPurpose, @Nullable final String keyContext) default String getResult(final MPKeyPurpose keyPurpose, @Nullable final String keyContext)
throws MPKeyUnavailableException, MPAlgorithmException { throws MPKeyUnavailableException, MPAlgorithmException {
return getResult( keyPurpose, keyContext, null ); return getResult( keyPurpose, keyContext, null );
} }
@Nonnull @Nullable
String getResult(MPKeyPurpose keyPurpose, @Nullable String keyContext, @Nullable String state) String getResult(MPKeyPurpose keyPurpose, @Nullable String keyContext, @Nullable String state)
throws MPKeyUnavailableException, MPAlgorithmException; throws MPKeyUnavailableException, MPAlgorithmException;
@Nullable
String getResult(MPKeyPurpose keyPurpose, @Nullable String keyContext,
@Nullable UnsignedInteger counter, MPResultType type, @Nullable String state)
throws MPKeyUnavailableException, MPAlgorithmException;
@Nonnull
String getState(MPKeyPurpose keyPurpose, @Nullable String keyContext,
@Nullable UnsignedInteger counter, MPResultType type, String state)
throws MPKeyUnavailableException, MPAlgorithmException;
@Nonnull @Nonnull
default String getLogin() default String getLogin()
throws MPKeyUnavailableException, MPAlgorithmException { throws MPKeyUnavailableException, MPAlgorithmException {
@@ -94,10 +105,17 @@ public interface MPSite<Q extends MPQuestion> extends Comparable<MPSite<?>> {
@Nonnull @Nonnull
MPUser<?> getUser(); MPUser<?> getUser();
boolean addQuestion(Q question); @Nonnull
Q addQuestion(String keyword);
@Nonnull
Q addQuestion(Q question);
boolean deleteQuestion(Q question); boolean deleteQuestion(Q question);
@Nonnull @Nonnull
Collection<Q> getQuestions(); Collection<Q> getQuestions();
@Nonnull
ImmutableCollection<Q> findQuestions(@Nullable String query);
} }

View File

@@ -100,6 +100,7 @@ public interface MPUser<S extends MPSite<?>> extends Comparable<MPUser<?>> {
// - Relations // - Relations
@Nonnull
S addSite(String siteName); S addSite(String siteName);
@Nonnull @Nonnull
@@ -113,9 +114,9 @@ public interface MPUser<S extends MPSite<?>> extends Comparable<MPUser<?>> {
@Nonnull @Nonnull
ImmutableCollection<S> findSites(@Nullable String query); ImmutableCollection<S> findSites(@Nullable String query);
boolean addListener(Listener listener); void addListener(Listener listener);
boolean removeListener(Listener listener); void removeListener(Listener listener);
interface Listener { interface Listener {

View File

@@ -22,6 +22,7 @@ import static com.lyndir.lhunath.opal.system.util.StringUtils.*;
import com.lyndir.masterpassword.*; import com.lyndir.masterpassword.*;
import com.lyndir.masterpassword.model.MPQuestion; import com.lyndir.masterpassword.model.MPQuestion;
import com.lyndir.masterpassword.model.MPSite;
import java.util.Objects; import java.util.Objects;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@@ -33,14 +34,23 @@ import org.jetbrains.annotations.NotNull;
*/ */
public abstract class MPBasicQuestion extends Changeable implements MPQuestion { public abstract class MPBasicQuestion extends Changeable implements MPQuestion {
private final String keyword; private final MPSite<?> site;
private MPResultType type; private final String keyword;
protected MPBasicQuestion(final String keyword, final MPResultType type) { private MPResultType type;
protected MPBasicQuestion(final MPSite<?> site, final String keyword, final MPResultType type) {
this.site = site;
this.keyword = keyword; this.keyword = keyword;
this.type = type; this.type = type;
} }
@Nonnull
@Override
public MPSite<?> getSite() {
return site;
}
@Nonnull @Nonnull
@Override @Override
public String getKeyword() { public String getKeyword() {
@@ -55,7 +65,7 @@ public abstract class MPBasicQuestion extends Changeable implements MPQuestion {
@Override @Override
public void setType(final MPResultType type) { public void setType(final MPResultType type) {
if (Objects.equals(this.type, type)) if (this.type == type)
return; return;
this.type = type; this.type = type;
@@ -70,15 +80,12 @@ public abstract class MPBasicQuestion extends Changeable implements MPQuestion {
return getSite().getResult( MPKeyPurpose.Recovery, getKeyword(), null, getType(), state ); return getSite().getResult( MPKeyPurpose.Recovery, getKeyword(), null, getType(), state );
} }
@Nonnull
@Override
public abstract MPBasicSite<?, ?> getSite();
@Override @Override
protected void onChanged() { protected void onChanged() {
super.onChanged(); super.onChanged();
getSite().setChanged(); if (site instanceof Changeable)
((Changeable) site).setChanged();
} }
@Override @Override

View File

@@ -21,6 +21,9 @@ package com.lyndir.masterpassword.model.impl;
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.*; import static com.lyndir.lhunath.opal.system.util.ObjectUtils.*;
import static com.lyndir.lhunath.opal.system.util.StringUtils.*; import static com.lyndir.lhunath.opal.system.util.StringUtils.*;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.primitives.UnsignedInteger; import com.google.common.primitives.UnsignedInteger;
import com.lyndir.masterpassword.*; import com.lyndir.masterpassword.*;
import com.lyndir.masterpassword.model.*; import com.lyndir.masterpassword.model.*;
@@ -35,9 +38,9 @@ import javax.annotation.Nullable;
public abstract class MPBasicSite<U extends MPUser<?>, Q extends MPQuestion> extends Changeable public abstract class MPBasicSite<U extends MPUser<?>, Q extends MPQuestion> extends Changeable
implements MPSite<Q> { implements MPSite<Q> {
private final Collection<Q> questions = new LinkedHashSet<>();
private final U user; private final U user;
private final String siteName; private final String siteName;
private final Collection<Q> questions = new LinkedHashSet<>();
private MPAlgorithm algorithm; private MPAlgorithm algorithm;
private UnsignedInteger counter; private UnsignedInteger counter;
@@ -104,7 +107,7 @@ public abstract class MPBasicSite<U extends MPUser<?>, Q extends MPQuestion> ext
@Override @Override
public void setResultType(final MPResultType resultType) { public void setResultType(final MPResultType resultType) {
if (Objects.equals( this.resultType, resultType )) if (this.resultType == resultType)
return; return;
this.resultType = resultType; this.resultType = resultType;
@@ -119,14 +122,14 @@ public abstract class MPBasicSite<U extends MPUser<?>, Q extends MPQuestion> ext
@Override @Override
public void setLoginType(@Nullable final MPResultType loginType) { public void setLoginType(@Nullable final MPResultType loginType) {
if (Objects.equals( this.loginType, loginType )) if (this.loginType == loginType)
return; return;
this.loginType = ifNotNullElse( loginType, getAlgorithm().mpw_default_login_type() ); this.loginType = ifNotNullElse( loginType, getAlgorithm().mpw_default_login_type() );
setChanged(); setChanged();
} }
@Nonnull @Nullable
@Override @Override
public String getResult(final MPKeyPurpose keyPurpose, @Nullable final String keyContext, @Nullable final String state) public String getResult(final MPKeyPurpose keyPurpose, @Nullable final String keyContext, @Nullable final String state)
throws MPKeyUnavailableException, MPAlgorithmException { throws MPKeyUnavailableException, MPAlgorithmException {
@@ -134,8 +137,10 @@ public abstract class MPBasicSite<U extends MPUser<?>, Q extends MPQuestion> ext
return getResult( keyPurpose, keyContext, getCounter(), getResultType(), state ); return getResult( keyPurpose, keyContext, getCounter(), getResultType(), state );
} }
protected String getResult(final MPKeyPurpose keyPurpose, @Nullable final String keyContext, @Nullable
@Nullable final UnsignedInteger counter, final MPResultType type, @Nullable final String state) @Override
public String getResult(final MPKeyPurpose keyPurpose, @Nullable final String keyContext,
@Nullable final UnsignedInteger counter, final MPResultType type, @Nullable final String state)
throws MPKeyUnavailableException, MPAlgorithmException { throws MPKeyUnavailableException, MPAlgorithmException {
return getUser().getMasterKey().siteResult( return getUser().getMasterKey().siteResult(
@@ -143,8 +148,10 @@ public abstract class MPBasicSite<U extends MPUser<?>, Q extends MPQuestion> ext
keyPurpose, keyContext, type, state ); keyPurpose, keyContext, type, state );
} }
protected String getState(final MPKeyPurpose keyPurpose, @Nullable final String keyContext, @Nonnull
@Nullable final UnsignedInteger counter, final MPResultType type, final String state) @Override
public String getState(final MPKeyPurpose keyPurpose, @Nullable final String keyContext,
@Nullable final UnsignedInteger counter, final MPResultType type, final String state)
throws MPKeyUnavailableException, MPAlgorithmException { throws MPKeyUnavailableException, MPAlgorithmException {
return getUser().getMasterKey().siteState( return getUser().getMasterKey().siteState(
@@ -160,13 +167,13 @@ public abstract class MPBasicSite<U extends MPUser<?>, Q extends MPQuestion> ext
return getResult( MPKeyPurpose.Identification, null, null, getLoginType(), state ); return getResult( MPKeyPurpose.Identification, null, null, getLoginType(), state );
} }
@Nonnull
@Override @Override
public boolean addQuestion(final Q question) { public Q addQuestion(final Q question) {
if (!questions.add( question )) questions.add( question );
return false;
setChanged(); setChanged();
return true; return question;
} }
@Override @Override
@@ -184,6 +191,17 @@ public abstract class MPBasicSite<U extends MPUser<?>, Q extends MPQuestion> ext
return Collections.unmodifiableCollection( questions ); return Collections.unmodifiableCollection( questions );
} }
@Nonnull
@Override
public ImmutableCollection<Q> findQuestions(@Nullable final String query) {
ImmutableSortedSet.Builder<Q> results = ImmutableSortedSet.naturalOrder();
for (final Q question : getQuestions())
if (Strings.isNullOrEmpty( query ) || question.getKeyword().startsWith( query ))
results.add( question );
return results.build();
}
@Nonnull @Nonnull
@Override @Override
public U getUser() { public U getUser() {

View File

@@ -20,6 +20,7 @@ package com.lyndir.masterpassword.model.impl;
import static com.lyndir.lhunath.opal.system.util.StringUtils.*; import static com.lyndir.lhunath.opal.system.util.StringUtils.*;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableCollection; import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableSortedSet; import com.google.common.collect.ImmutableSortedSet;
import com.lyndir.lhunath.opal.system.CodeUtils; import com.lyndir.lhunath.opal.system.CodeUtils;
@@ -202,22 +203,21 @@ public abstract class MPBasicUser<S extends MPBasicSite<?, ?>> extends Changeabl
@Override @Override
public ImmutableCollection<S> findSites(@Nullable final String query) { public ImmutableCollection<S> findSites(@Nullable final String query) {
ImmutableSortedSet.Builder<S> results = ImmutableSortedSet.naturalOrder(); ImmutableSortedSet.Builder<S> results = ImmutableSortedSet.naturalOrder();
if (query != null) for (final S site : getSites())
for (final S site : getSites()) if (Strings.isNullOrEmpty( query ) || site.getSiteName().startsWith( query ))
if (site.getSiteName().startsWith( query )) results.add( site );
results.add( site );
return results.build(); return results.build();
} }
@Override @Override
public boolean addListener(final Listener listener) { public void addListener(final Listener listener) {
return listeners.add( listener ); listeners.add( listener );
} }
@Override @Override
public boolean removeListener(final Listener listener) { public void removeListener(final Listener listener) {
return listeners.remove( listener ); listeners.remove( listener );
} }
@Override @Override

View File

@@ -30,16 +30,13 @@ import javax.annotation.Nullable;
*/ */
public class MPFileQuestion extends MPBasicQuestion { public class MPFileQuestion extends MPBasicQuestion {
private final MPFileSite site;
@Nullable @Nullable
private String answerState; private String answerState;
public MPFileQuestion(final MPFileSite site, final String keyword, public MPFileQuestion(final MPFileSite site, final String keyword,
@Nullable final MPResultType type, @Nullable final String answerState) { @Nullable final MPResultType type, @Nullable final String answerState) {
super( keyword, ifNotNullElse( type, site.getAlgorithm().mpw_default_answer_type() ) ); super( site, keyword, ifNotNullElse( type, site.getAlgorithm().mpw_default_answer_type() ) );
this.site = site;
this.answerState = answerState; this.answerState = answerState;
} }
@@ -48,6 +45,8 @@ public class MPFileQuestion extends MPBasicQuestion {
return answerState; return answerState;
} }
@Nonnull
@Override
public String getAnswer() public String getAnswer()
throws MPKeyUnavailableException, MPAlgorithmException { throws MPKeyUnavailableException, MPAlgorithmException {
return getAnswer( answerState ); return getAnswer( answerState );
@@ -66,9 +65,8 @@ public class MPFileQuestion extends MPBasicQuestion {
setChanged(); setChanged();
} }
@Nonnull public void use() {
@Override if (getSite() instanceof MPFileSite)
public MPFileSite getSite() { ((MPFileSite) getSite()).use();
return site;
} }
} }

View File

@@ -93,7 +93,7 @@ public class MPFileSite extends MPBasicSite<MPFileUser, MPFileQuestion> {
setChanged(); setChanged();
} }
@Nonnull @Nullable
@Override @Override
public String getResult(final MPKeyPurpose keyPurpose, @Nullable final String keyContext) public String getResult(final MPKeyPurpose keyPurpose, @Nullable final String keyContext)
throws MPKeyUnavailableException, MPAlgorithmException { throws MPKeyUnavailableException, MPAlgorithmException {
@@ -145,6 +145,12 @@ public class MPFileSite extends MPBasicSite<MPFileUser, MPFileQuestion> {
setChanged(); setChanged();
} }
@Nonnull
@Override
public MPFileQuestion addQuestion(final String keyword) {
return addQuestion( new MPFileQuestion( this, keyword, null, null ) );
}
@Override @Override
public int compareTo(@Nonnull final MPSite<?> o) { public int compareTo(@Nonnull final MPSite<?> o) {
int comparison = (o instanceof MPFileSite)? ((MPFileSite) o).getLastUsed().compareTo( getLastUsed() ): 0; int comparison = (o instanceof MPFileSite)? ((MPFileSite) o).getLastUsed().compareTo( getLastUsed() ): 0;

View File

@@ -207,6 +207,7 @@ public class MPFileUser extends MPBasicUser<MPFileSite> {
super.reset(); super.reset();
} }
@Nonnull
@Override @Override
public MPFileSite addSite(final String siteName) { public MPFileSite addSite(final String siteName) {
return addSite( new MPFileSite( this, siteName ) ); return addSite( new MPFileSite( this, siteName ) );

View File

@@ -22,7 +22,7 @@ import static com.lyndir.lhunath.opal.system.util.ObjectUtils.*;
import com.google.common.collect.ImmutableSortedSet; import com.google.common.collect.ImmutableSortedSet;
import com.lyndir.lhunath.opal.system.logging.Logger; import com.lyndir.lhunath.opal.system.logging.Logger;
import com.lyndir.masterpassword.model.MPConstants; import com.lyndir.masterpassword.model.MPModelConstants;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.*; import java.util.*;
@@ -42,7 +42,7 @@ public class MPFileUserManager {
private static final MPFileUserManager instance; private static final MPFileUserManager instance;
static { static {
String rcDir = System.getenv( MPConstants.env_rcDir ); String rcDir = System.getenv( MPModelConstants.env_rcDir );
if (rcDir != null) if (rcDir != null)
instance = create( new File( rcDir ) ); instance = create( new File( rcDir ) );
@@ -66,6 +66,7 @@ public class MPFileUserManager {
protected MPFileUserManager(final File path) { protected MPFileUserManager(final File path) {
this.path = path; this.path = path;
reload();
} }
public void reload() { public void reload() {
@@ -128,12 +129,13 @@ public class MPFileUserManager {
return ImmutableSortedSet.copyOf( userByName.values() ); return ImmutableSortedSet.copyOf( userByName.values() );
} }
public boolean addListener(final Listener listener) { public void addListener(final Listener listener) {
return listeners.add( listener ); if (listeners.add( listener ))
listener.onFilesUpdated( getFiles() );
} }
public boolean removeListener(final Listener listener) { public void removeListener(final Listener listener) {
return listeners.remove( listener ); listeners.remove( listener );
} }
private void fireUpdated() { private void fireUpdated() {
@@ -141,7 +143,6 @@ public class MPFileUserManager {
return; return;
ImmutableSortedSet<MPFileUser> files = getFiles(); ImmutableSortedSet<MPFileUser> files = getFiles();
for (final Listener listener : listeners) for (final Listener listener : listeners)
listener.onFilesUpdated( files ); listener.onFilesUpdated( files );
} }

View File

@@ -25,7 +25,7 @@ import com.google.common.base.Charsets;
import com.google.common.io.CharSink; import com.google.common.io.CharSink;
import com.lyndir.masterpassword.MPAlgorithmException; import com.lyndir.masterpassword.MPAlgorithmException;
import com.lyndir.masterpassword.MPKeyUnavailableException; import com.lyndir.masterpassword.MPKeyUnavailableException;
import com.lyndir.masterpassword.model.MPConstants; import com.lyndir.masterpassword.model.MPModelConstants;
import java.io.*; import java.io.*;
import org.joda.time.Instant; import org.joda.time.Instant;
@@ -50,7 +50,7 @@ public class MPFlatMarshaller implements MPMarshaller {
content.append( "# \n" ); content.append( "# \n" );
content.append( "##\n" ); content.append( "##\n" );
content.append( "# Format: " ).append( FORMAT ).append( '\n' ); content.append( "# Format: " ).append( FORMAT ).append( '\n' );
content.append( "# Date: " ).append( MPConstants.dateTimeFormatter.print( new Instant() ) ).append( '\n' ); content.append( "# Date: " ).append( MPModelConstants.dateTimeFormatter.print( new Instant() ) ).append( '\n' );
content.append( "# User Name: " ).append( user.getFullName() ).append( '\n' ); content.append( "# User Name: " ).append( user.getFullName() ).append( '\n' );
content.append( "# Full Name: " ).append( user.getFullName() ).append( '\n' ); content.append( "# Full Name: " ).append( user.getFullName() ).append( '\n' );
content.append( "# Avatar: " ).append( user.getAvatar() ).append( '\n' ); content.append( "# Avatar: " ).append( user.getAvatar() ).append( '\n' );
@@ -72,7 +72,7 @@ public class MPFlatMarshaller implements MPMarshaller {
} }
content.append( strf( "%s %8d %8s %25s\t%25s\t%s\n", // content.append( strf( "%s %8d %8s %25s\t%25s\t%s\n", //
MPConstants.dateTimeFormatter.print( site.getLastUsed() ), // lastUsed MPModelConstants.dateTimeFormatter.print( site.getLastUsed() ), // lastUsed
site.getUses(), // uses site.getUses(), // uses
strf( "%d:%d:%d", // strf( "%d:%d:%d", //
site.getResultType().getType(), // type site.getResultType().getType(), // type

View File

@@ -25,7 +25,7 @@ import com.lyndir.lhunath.opal.system.CodeUtils;
import com.lyndir.lhunath.opal.system.logging.Logger; import com.lyndir.lhunath.opal.system.logging.Logger;
import com.lyndir.lhunath.opal.system.util.ConversionUtils; import com.lyndir.lhunath.opal.system.util.ConversionUtils;
import com.lyndir.masterpassword.*; import com.lyndir.masterpassword.*;
import com.lyndir.masterpassword.model.MPConstants; import com.lyndir.masterpassword.model.MPModelConstants;
import com.lyndir.masterpassword.model.MPIncorrectMasterPasswordException; import com.lyndir.masterpassword.model.MPIncorrectMasterPasswordException;
import java.io.*; import java.io.*;
import java.util.regex.Matcher; import java.util.regex.Matcher;
@@ -157,7 +157,7 @@ public class MPFlatUnmarshaller implements MPUnmarshaller {
MPResultType.forType( ConversionUtils.toIntegerNN( siteMatcher.group( 3 ) ) ), MPResultType.forType( ConversionUtils.toIntegerNN( siteMatcher.group( 3 ) ) ),
clearContent? null: siteMatcher.group( 6 ), clearContent? null: siteMatcher.group( 6 ),
null, null, null, ConversionUtils.toIntegerNN( siteMatcher.group( 2 ) ), null, null, null, ConversionUtils.toIntegerNN( siteMatcher.group( 2 ) ),
MPConstants.dateTimeFormatter.parseDateTime( siteMatcher.group( 1 ) ).toInstant() ); MPModelConstants.dateTimeFormatter.parseDateTime( siteMatcher.group( 1 ) ).toInstant() );
if (clearContent) if (clearContent)
site.setSitePassword( site.getResultType(), siteMatcher.group( 6 ) ); site.setSitePassword( site.getResultType(), siteMatcher.group( 6 ) );
break; break;
@@ -171,7 +171,7 @@ public class MPFlatUnmarshaller implements MPUnmarshaller {
clearContent? null: siteMatcher.group( 8 ), clearContent? null: siteMatcher.group( 8 ),
MPResultType.GeneratedName, clearContent? null: siteMatcher.group( 6 ), null, MPResultType.GeneratedName, clearContent? null: siteMatcher.group( 6 ), null,
ConversionUtils.toIntegerNN( siteMatcher.group( 2 ) ), ConversionUtils.toIntegerNN( siteMatcher.group( 2 ) ),
MPConstants.dateTimeFormatter.parseDateTime( siteMatcher.group( 1 ) ).toInstant() ); MPModelConstants.dateTimeFormatter.parseDateTime( siteMatcher.group( 1 ) ).toInstant() );
if (clearContent) { if (clearContent) {
site.setSitePassword( site.getResultType(), siteMatcher.group( 8 ) ); site.setSitePassword( site.getResultType(), siteMatcher.group( 8 ) );
site.setLoginName( MPResultType.StoredPersonal, siteMatcher.group( 6 ) ); site.setLoginName( MPResultType.StoredPersonal, siteMatcher.group( 6 ) );

View File

@@ -28,7 +28,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.primitives.UnsignedInteger; import com.google.common.primitives.UnsignedInteger;
import com.lyndir.lhunath.opal.system.CodeUtils; import com.lyndir.lhunath.opal.system.CodeUtils;
import com.lyndir.masterpassword.*; import com.lyndir.masterpassword.*;
import com.lyndir.masterpassword.model.MPConstants; import com.lyndir.masterpassword.model.MPModelConstants;
import com.lyndir.masterpassword.model.MPIncorrectMasterPasswordException; import com.lyndir.masterpassword.model.MPIncorrectMasterPasswordException;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.io.File; import java.io.File;
@@ -70,13 +70,13 @@ public class MPJSONFile extends MPJSONAnyObject {
export = new Export(); export = new Export();
export.format = 1; export.format = 1;
export.redacted = modelUser.getContentMode().isRedacted(); export.redacted = modelUser.getContentMode().isRedacted();
export.date = MPConstants.dateTimeFormatter.print( new Instant() ); export.date = MPModelConstants.dateTimeFormatter.print( new Instant() );
// Section: "user" // Section: "user"
user = new User(); user = new User();
user.avatar = modelUser.getAvatar(); user.avatar = modelUser.getAvatar();
user.full_name = modelUser.getFullName(); user.full_name = modelUser.getFullName();
user.last_used = MPConstants.dateTimeFormatter.print( modelUser.getLastUsed() ); user.last_used = MPModelConstants.dateTimeFormatter.print( modelUser.getLastUsed() );
user.key_id = modelUser.exportKeyID(); user.key_id = modelUser.exportKeyID();
user.algorithm = modelUser.getAlgorithm().version(); user.algorithm = modelUser.getAlgorithm().version();
user.default_type = modelUser.getDefaultType(); user.default_type = modelUser.getDefaultType();
@@ -111,7 +111,7 @@ public class MPJSONFile extends MPJSONAnyObject {
site.login_type = modelSite.getLoginType(); site.login_type = modelSite.getLoginType();
site.uses = modelSite.getUses(); site.uses = modelSite.getUses();
site.last_used = MPConstants.dateTimeFormatter.print( modelSite.getLastUsed() ); site.last_used = MPModelConstants.dateTimeFormatter.print( modelSite.getLastUsed() );
site.questions = new LinkedHashMap<>(); site.questions = new LinkedHashMap<>();
for (final MPFileQuestion question : modelSite.getQuestions()) for (final MPFileQuestion question : modelSite.getQuestions())
@@ -141,7 +141,7 @@ public class MPJSONFile extends MPJSONAnyObject {
return new MPFileUser( return new MPFileUser(
user.full_name, CodeUtils.decodeHex( user.key_id ), algorithm, user.avatar, user.full_name, CodeUtils.decodeHex( user.key_id ), algorithm, user.avatar,
(user.default_type != null)? user.default_type: algorithm.mpw_default_result_type(), (user.default_type != null)? user.default_type: algorithm.mpw_default_result_type(),
(user.last_used != null)? MPConstants.dateTimeFormatter.parseDateTime( user.last_used ): new Instant(), (user.last_used != null)? MPModelConstants.dateTimeFormatter.parseDateTime( user.last_used ): new Instant(),
export.redacted? MPMarshaller.ContentMode.PROTECTED: MPMarshaller.ContentMode.VISIBLE, export.redacted? MPMarshaller.ContentMode.PROTECTED: MPMarshaller.ContentMode.VISIBLE,
MPMarshalFormat.JSON, file.getParentFile() MPMarshalFormat.JSON, file.getParentFile()
); );
@@ -157,7 +157,7 @@ public class MPJSONFile extends MPJSONAnyObject {
fileSite.type, export.redacted? fileSite.password: null, fileSite.type, export.redacted? fileSite.password: null,
fileSite.login_type, export.redacted? fileSite.login_name: null, fileSite.login_type, export.redacted? fileSite.login_name: null,
(fileSite._ext_mpw != null)? fileSite._ext_mpw.url: null, fileSite.uses, (fileSite._ext_mpw != null)? fileSite._ext_mpw.url: null, fileSite.uses,
(fileSite.last_used != null)? MPConstants.dateTimeFormatter.parseDateTime( fileSite.last_used ): new Instant() ); (fileSite.last_used != null)? MPModelConstants.dateTimeFormatter.parseDateTime( fileSite.last_used ): new Instant() );
if (!export.redacted) { if (!export.redacted) {
if (fileSite.password != null) if (fileSite.password != null)
@@ -170,7 +170,7 @@ public class MPJSONFile extends MPJSONAnyObject {
if (fileSite.questions != null) if (fileSite.questions != null)
for (final Map.Entry<String, Site.Question> questionEntry : fileSite.questions.entrySet()) { for (final Map.Entry<String, Site.Question> questionEntry : fileSite.questions.entrySet()) {
Site.Question fileQuestion = questionEntry.getValue(); Site.Question fileQuestion = questionEntry.getValue();
MPFileQuestion question = new MPFileQuestion( site, questionEntry.getKey(), MPFileQuestion question = new MPFileQuestion( site, ifNotNullElse( questionEntry.getKey(), "" ),
fileQuestion.type, export.redacted? fileQuestion.answer: null ); fileQuestion.type, export.redacted? fileQuestion.answer: null );
if (!export.redacted && (fileQuestion.answer != null)) if (!export.redacted && (fileQuestion.answer != null))