Compare commits
27 Commits
2.4-releas
...
2.4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b4efe301a4 | ||
|
|
9a82e617b3 | ||
|
|
2647279ff6 | ||
|
|
dae84c4a2e | ||
|
|
3b74543c51 | ||
|
|
1918c30bed | ||
|
|
bbeb6bb948 | ||
|
|
15b14d67db | ||
|
|
35c443d82d | ||
|
|
d950d4be3b | ||
|
|
d7aae64b5d | ||
|
|
f238bb723d | ||
|
|
b4da801bb0 | ||
|
|
ce7aa46af6 | ||
|
|
83c28692ad | ||
|
|
f21d0f7cfc | ||
|
|
7eb10cb5a6 | ||
|
|
64829c99d8 | ||
|
|
0269c2741a | ||
|
|
f5638ea798 | ||
|
|
6a1768a50d | ||
|
|
b346b3be65 | ||
|
|
060ec0b5cd | ||
|
|
7f8a36e32e | ||
|
|
024899f311 | ||
|
|
6ffef78469 | ||
|
|
b574158d92 |
1
.idea/inspectionProfiles/Project_Default.xml
generated
@@ -5,7 +5,6 @@
|
||||
<inspection_tool class="Convert to string" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="FunctionImplicitDeclarationInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="ImplicitIntegerAndEnumConversion" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="LossyEncoding" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="MethodIsLaterInTheScope" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="OCNotLocalizedStringInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="OCUnusedMacroInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
|
||||
2
External/Pearl
vendored
@@ -8,6 +8,7 @@
|
||||
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <arpa/inet.h>
|
||||
|
||||
#include "mpw-types.h"
|
||||
#include "mpw-util.h"
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <arpa/inet.h>
|
||||
|
||||
#include "mpw-types.h"
|
||||
#include "mpw-util.h"
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <arpa/inet.h>
|
||||
|
||||
#include "mpw-types.h"
|
||||
#include "mpw-util.h"
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <arpa/inet.h>
|
||||
|
||||
#include "mpw-types.h"
|
||||
#include "mpw-util.h"
|
||||
|
||||
@@ -16,12 +16,12 @@ import org.jetbrains.annotations.Contract;
|
||||
*/
|
||||
public enum MPSiteType {
|
||||
|
||||
GeneratedMaximum( "20 characters, contains symbols.", //
|
||||
GeneratedMaximum( "Max", "20 characters, contains symbols.", //
|
||||
ImmutableList.of( "x", "max", "maximum" ), //
|
||||
ImmutableList.of( new MPTemplate( "anoxxxxxxxxxxxxxxxxx" ), new MPTemplate( "axxxxxxxxxxxxxxxxxno" ) ), //
|
||||
MPSiteTypeClass.Generated, 0x0 ),
|
||||
|
||||
GeneratedLong( "Copy-friendly, 14 characters, contains symbols.", //
|
||||
GeneratedLong( "Long", "Copy-friendly, 14 characters, contains symbols.", //
|
||||
ImmutableList.of( "l", "long" ), //
|
||||
ImmutableList.of( new MPTemplate( "CvcvnoCvcvCvcv" ), new MPTemplate( "CvcvCvcvnoCvcv" ),
|
||||
new MPTemplate( "CvcvCvcvCvcvno" ), new MPTemplate( "CvccnoCvcvCvcv" ),
|
||||
@@ -36,49 +36,50 @@ public enum MPSiteType {
|
||||
new MPTemplate( "CvccCvcvCvccno" ) ), //
|
||||
MPSiteTypeClass.Generated, 0x1 ),
|
||||
|
||||
GeneratedMedium( "Copy-friendly, 8 characters, contains symbols.", //
|
||||
GeneratedMedium( "Medium", "Copy-friendly, 8 characters, contains symbols.", //
|
||||
ImmutableList.of( "m", "med", "medium" ), //
|
||||
ImmutableList.of( new MPTemplate( "CvcnoCvc" ), new MPTemplate( "CvcCvcno" ) ), //
|
||||
MPSiteTypeClass.Generated, 0x2 ),
|
||||
|
||||
GeneratedBasic( "8 characters, no symbols.", //
|
||||
GeneratedBasic( "Basic", "8 characters, no symbols.", //
|
||||
ImmutableList.of( "b", "basic" ), //
|
||||
ImmutableList.of( new MPTemplate( "aaanaaan" ), new MPTemplate( "aannaaan" ), new MPTemplate( "aaannaaa" ) ), //
|
||||
MPSiteTypeClass.Generated, 0x3 ),
|
||||
|
||||
GeneratedShort( "Copy-friendly, 4 characters, no symbols.", //
|
||||
GeneratedShort( "Short", "Copy-friendly, 4 characters, no symbols.", //
|
||||
ImmutableList.of( "s", "short" ), //
|
||||
ImmutableList.of( new MPTemplate( "Cvcn" ) ), //
|
||||
MPSiteTypeClass.Generated, 0x4 ),
|
||||
|
||||
GeneratedPIN( "4 numbers.", //
|
||||
GeneratedPIN( "PIN", "4 numbers.", //
|
||||
ImmutableList.of( "i", "pin" ), //
|
||||
ImmutableList.of( new MPTemplate( "nnnn" ) ), //
|
||||
MPSiteTypeClass.Generated, 0x5 ),
|
||||
|
||||
GeneratedName( "9 letter name.", //
|
||||
GeneratedName( "Name", "9 letter name.", //
|
||||
ImmutableList.of( "n", "name" ), //
|
||||
ImmutableList.of( new MPTemplate( "cvccvcvcv" ) ), //
|
||||
MPSiteTypeClass.Generated, 0xE ),
|
||||
|
||||
GeneratedPhrase( "20 character sentence.", //
|
||||
GeneratedPhrase( "Phrase", "20 character sentence.", //
|
||||
ImmutableList.of( "p", "phrase" ), //
|
||||
ImmutableList.of( new MPTemplate( "cvcc cvc cvccvcv cvc" ), new MPTemplate( "cvc cvccvcvcv cvcv" ),
|
||||
new MPTemplate( "cv cvccv cvc cvcvccv" ) ), //
|
||||
MPSiteTypeClass.Generated, 0xF ),
|
||||
|
||||
StoredPersonal( "AES-encrypted, exportable.", //
|
||||
StoredPersonal( "Personal", "AES-encrypted, exportable.", //
|
||||
ImmutableList.of( "personal" ), //
|
||||
ImmutableList.<MPTemplate>of(), //
|
||||
MPSiteTypeClass.Stored, 0x0, MPSiteFeature.ExportContent ),
|
||||
|
||||
StoredDevicePrivate( "AES-encrypted, not exported.", //
|
||||
StoredDevicePrivate( "Device", "AES-encrypted, not exported.", //
|
||||
ImmutableList.of( "device" ), //
|
||||
ImmutableList.<MPTemplate>of(), //
|
||||
MPSiteTypeClass.Stored, 0x1, MPSiteFeature.DevicePrivate );
|
||||
|
||||
static final Logger logger = Logger.get( MPSiteType.class );
|
||||
|
||||
private final String shortName;
|
||||
private final String description;
|
||||
private final List<String> options;
|
||||
private final List<MPTemplate> templates;
|
||||
@@ -86,9 +87,10 @@ public enum MPSiteType {
|
||||
private final int typeIndex;
|
||||
private final Set<MPSiteFeature> typeFeatures;
|
||||
|
||||
MPSiteType(final String description, final List<String> options, final List<MPTemplate> templates, final MPSiteTypeClass typeClass,
|
||||
final int typeIndex, final MPSiteFeature... typeFeatures) {
|
||||
MPSiteType(final String shortName, final String description, final List<String> options, final List<MPTemplate> templates,
|
||||
final MPSiteTypeClass typeClass, final int typeIndex, final MPSiteFeature... typeFeatures) {
|
||||
|
||||
this.shortName = shortName;
|
||||
this.description = description;
|
||||
this.options = options;
|
||||
this.templates = templates;
|
||||
@@ -102,6 +104,10 @@ public enum MPSiteType {
|
||||
this.typeFeatures = typeFeaturesBuilder.build();
|
||||
}
|
||||
|
||||
public String getShortName() {
|
||||
return shortName;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
|
||||
return description;
|
||||
|
||||
@@ -16,10 +16,12 @@ import org.jetbrains.annotations.NotNull;
|
||||
public abstract class MasterKey {
|
||||
|
||||
@SuppressWarnings("UnusedDeclaration")
|
||||
private static final Logger logger = Logger.get( MasterKey.class );
|
||||
private static final Logger logger = Logger.get( MasterKey.class );
|
||||
private static boolean allowNativeByDefault = true;
|
||||
|
||||
@Nonnull
|
||||
private final String fullName;
|
||||
private boolean allowNative = allowNativeByDefault;
|
||||
|
||||
@Nullable
|
||||
private byte[] masterKey;
|
||||
@@ -46,6 +48,23 @@ public abstract class MasterKey {
|
||||
throw new UnsupportedOperationException( "Unsupported version: " + version );
|
||||
}
|
||||
|
||||
public static boolean isAllowNativeByDefault() {
|
||||
return allowNativeByDefault;
|
||||
}
|
||||
|
||||
/**
|
||||
* Native libraries are useful for speeding up the performance of cryptographical functions.
|
||||
* Sometimes, however, we may prefer to use Java-only code.
|
||||
* For instance, for auditability / trust or because the native code doesn't work on our CPU/platform.
|
||||
* <p/>
|
||||
* This setter affects the default setting for any newly created {@link MasterKey}s.
|
||||
*
|
||||
* @param allowNative false to disallow the use of native libraries.
|
||||
*/
|
||||
public static void setAllowNativeByDefault(final boolean allowNative) {
|
||||
allowNativeByDefault = allowNative;
|
||||
}
|
||||
|
||||
protected MasterKey(@NotNull final String fullName) {
|
||||
|
||||
this.fullName = fullName;
|
||||
@@ -63,6 +82,15 @@ public abstract class MasterKey {
|
||||
return fullName;
|
||||
}
|
||||
|
||||
public boolean isAllowNative() {
|
||||
return allowNative;
|
||||
}
|
||||
|
||||
public MasterKey setAllowNative(final boolean allowNative) {
|
||||
this.allowNative = allowNative;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
protected byte[] getKey() {
|
||||
|
||||
|
||||
@@ -65,8 +65,15 @@ public class MasterKeyV0 extends MasterKey {
|
||||
mpBytesBuf.get( mpBytes, 0, mpBytes.length );
|
||||
Arrays.fill( mpBytesBuf.array(), (byte) 0 );
|
||||
|
||||
return scrypt( masterKeySalt, mpBytes );
|
||||
}
|
||||
|
||||
protected byte[] scrypt(final byte[] masterKeySalt, final byte[] mpBytes) {
|
||||
try {
|
||||
return SCrypt.scrypt( mpBytes, masterKeySalt, MP_N, MP_r, MP_p, MP_dkLen );
|
||||
if (isAllowNative())
|
||||
return SCrypt.scrypt( mpBytes, masterKeySalt, MP_N, MP_r, MP_p, MP_dkLen );
|
||||
else
|
||||
return SCrypt.scryptJ( mpBytes, masterKeySalt, MP_N, MP_r, MP_p, MP_dkLen );
|
||||
}
|
||||
catch (GeneralSecurityException e) {
|
||||
logger.bug( e );
|
||||
|
||||
@@ -14,7 +14,7 @@ import javax.annotation.Nullable;
|
||||
/**
|
||||
* bugs:
|
||||
* - no known issues.
|
||||
*
|
||||
*
|
||||
* @author lhunath, 2014-08-30
|
||||
*/
|
||||
public class MasterKeyV3 extends MasterKeyV2 {
|
||||
@@ -46,17 +46,8 @@ public class MasterKeyV3 extends MasterKeyV2 {
|
||||
ByteBuffer mpBytesBuf = MP_charset.encode( CharBuffer.wrap( masterPassword ) );
|
||||
byte[] mpBytes = new byte[mpBytesBuf.remaining()];
|
||||
mpBytesBuf.get( mpBytes, 0, mpBytes.length );
|
||||
Arrays.fill( mpBytesBuf.array(), (byte)0 );
|
||||
Arrays.fill( mpBytesBuf.array(), (byte) 0 );
|
||||
|
||||
try {
|
||||
return SCrypt.scrypt( mpBytes, masterKeySalt, MP_N, MP_r, MP_p, MP_dkLen );
|
||||
}
|
||||
catch (GeneralSecurityException e) {
|
||||
logger.bug( e );
|
||||
return null;
|
||||
}
|
||||
finally {
|
||||
Arrays.fill( mpBytes, (byte) 0 );
|
||||
}
|
||||
return scrypt( masterKeySalt, mpBytes );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,14 +11,15 @@
|
||||
<application
|
||||
android:icon="@drawable/icon"
|
||||
android:label="@string/app_name"
|
||||
android:allowBackup="true">
|
||||
<activity android:name=".TestActivity" android:theme="@style/MPTheme">
|
||||
android:allowBackup="true"
|
||||
android:debuggable="true">
|
||||
<activity android:name=".EmergencyActivity" android:theme="@style/MPTheme">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity android:name=".EmergencyActivity" android:theme="@style/MPTheme" />
|
||||
<activity android:name=".TestActivity" android:theme="@style/MPTheme" />
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
||||
@@ -67,6 +67,7 @@
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jarsigner-plugin</artifactId>
|
||||
<version>1.4</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>signing</id>
|
||||
|
||||
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 4.9 KiB |
|
After Width: | Height: | Size: 4.0 KiB |
|
After Width: | Height: | Size: 4.9 KiB |
|
After Width: | Height: | Size: 4.0 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 697 B |
@@ -1,5 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fillViewport="true"
|
||||
@@ -17,6 +18,13 @@
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1" />
|
||||
|
||||
<ImageView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:importantForAccessibility="no"
|
||||
android:src="@drawable/img_identity" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/fullNameField"
|
||||
android:layout_width="match_parent"
|
||||
@@ -26,14 +34,14 @@
|
||||
android:hint="@string/fullName_hint"
|
||||
android:gravity="center"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textSize="26sp" />
|
||||
android:textSize="16sp" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/rememberFullNameField"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:nextFocusForward="@+id/rememberPasswordField"
|
||||
android:textSize="14sp"
|
||||
android:textSize="12sp"
|
||||
android:textColor="@android:color/tertiary_text_dark"
|
||||
android:text="@string/remember" />
|
||||
|
||||
@@ -46,16 +54,24 @@
|
||||
android:hint="@string/masterPassword_hint"
|
||||
android:gravity="center"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textSize="18sp" />
|
||||
android:textSize="16sp" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@id/rememberPasswordField"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="14sp"
|
||||
android:textSize="12sp"
|
||||
android:textColor="@android:color/tertiary_text_dark"
|
||||
android:text="@string/forgetOnClose" />
|
||||
|
||||
<ImageView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="20dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:importantForAccessibility="no"
|
||||
android:src="@drawable/img_key" />
|
||||
|
||||
<EditText
|
||||
android:id="@id/siteNameField"
|
||||
android:layout_width="match_parent"
|
||||
@@ -65,7 +81,7 @@
|
||||
android:hint="@string/siteName_hint"
|
||||
android:gravity="center"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textSize="18sp" />
|
||||
android:textSize="16sp" />
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
@@ -76,8 +92,9 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_margin="20dp"
|
||||
android:indeterminate="true" />
|
||||
android:visibility="invisible"
|
||||
android:indeterminate="true"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
@@ -89,12 +106,12 @@
|
||||
android:id="@id/sitePasswordField"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:nextFocusForward="@+id/siteTypeField"
|
||||
android:nextFocusForward="@+id/siteTypeButton"
|
||||
android:gravity="center"
|
||||
android:background="@android:color/transparent"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textSize="32sp"
|
||||
android:text="LuxdZozvDuma4["
|
||||
android:textSize="28sp"
|
||||
tools:text="LuxdZozvDuma4["
|
||||
android:onClick="copySitePassword" />
|
||||
|
||||
<TextView
|
||||
@@ -104,7 +121,7 @@
|
||||
android:labelFor="@id/sitePasswordField"
|
||||
android:gravity="center"
|
||||
android:background="@android:color/transparent"
|
||||
android:textSize="14sp"
|
||||
android:textSize="12sp"
|
||||
android:textColor="@android:color/tertiary_text_dark"
|
||||
android:text="@string/sitePassword_hint" />
|
||||
|
||||
@@ -115,54 +132,136 @@
|
||||
android:id="@+id/maskPasswordField"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="14sp"
|
||||
android:textSize="12sp"
|
||||
android:textColor="@android:color/tertiary_text_dark"
|
||||
android:text="@string/maskPassword" />
|
||||
|
||||
<Spinner
|
||||
android:id="@id/siteTypeField"
|
||||
<ImageView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:nextFocusForward="@+id/counterField"
|
||||
android:gravity="center" />
|
||||
android:layout_marginTop="20dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:importantForAccessibility="no"
|
||||
android:src="@drawable/divider" />
|
||||
|
||||
<EditText
|
||||
android:id="@id/counterField"
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:nextFocusForward="@+id/siteVersionField"
|
||||
android:gravity="center"
|
||||
android:inputType="text|textNoSuggestions"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textSize="18sp"
|
||||
android:text="1" />
|
||||
style="?android:buttonBarStyle"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:labelFor="@id/counterField"
|
||||
android:gravity="center"
|
||||
android:background="@android:color/transparent"
|
||||
android:textSize="14sp"
|
||||
android:textColor="@android:color/tertiary_text_dark"
|
||||
android:text="@string/siteCounter_hint" />
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center">
|
||||
|
||||
<Spinner
|
||||
android:id="@id/siteVersionField"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:nextFocusForward="@id/rememberFullNameField"
|
||||
android:gravity="center" />
|
||||
<Button
|
||||
android:id="@id/siteTypeButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
style="?android:buttonBarButtonStyle"
|
||||
android:nextFocusForward="@+id/counterField"
|
||||
android:gravity="center"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textSize="16sp"
|
||||
android:drawableStart="@drawable/icon_key"
|
||||
android:drawablePadding="8dp"
|
||||
android:background="@android:color/transparent"
|
||||
tools:text="Long" />
|
||||
|
||||
<TextView
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:labelFor="@id/siteTypeButton"
|
||||
android:gravity="center"
|
||||
android:background="@android:color/transparent"
|
||||
android:textSize="12sp"
|
||||
android:textColor="@android:color/tertiary_text_dark"
|
||||
android:text="@string/siteType_hint" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center">
|
||||
|
||||
<Button
|
||||
android:id="@id/counterField"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
style="?android:buttonBarButtonStyle"
|
||||
android:nextFocusForward="@+id/siteVersionButton"
|
||||
android:gravity="center"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textSize="16sp"
|
||||
android:drawableStart="@drawable/icon_plus"
|
||||
android:drawablePadding="8dp"
|
||||
android:background="@android:color/transparent"
|
||||
tools:text="1" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:labelFor="@id/siteVersionButton"
|
||||
android:gravity="center"
|
||||
android:background="@android:color/transparent"
|
||||
android:textSize="12sp"
|
||||
android:textColor="@android:color/tertiary_text_dark"
|
||||
android:text="@string/siteCounter_hint" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center">
|
||||
|
||||
<Button
|
||||
android:id="@id/siteVersionButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
style="?android:buttonBarButtonStyle"
|
||||
android:nextFocusForward="@+id/rememberFullNameField"
|
||||
android:gravity="center"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textSize="16sp"
|
||||
android:drawableStart="@drawable/icon_gears"
|
||||
android:drawablePadding="8dp"
|
||||
android:background="@android:color/transparent"
|
||||
tools:text="3" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:labelFor="@id/siteVersionButton"
|
||||
android:gravity="center"
|
||||
android:background="@android:color/transparent"
|
||||
android:textSize="12sp"
|
||||
android:textColor="@android:color/tertiary_text_dark"
|
||||
android:text="@string/siteVersion_hint" />
|
||||
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<Button
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:labelFor="@id/siteVersionField"
|
||||
android:gravity="center"
|
||||
android:background="@android:color/transparent"
|
||||
android:textSize="14sp"
|
||||
android:textColor="@android:color/tertiary_text_dark"
|
||||
android:text="@string/siteVersion_hint" />
|
||||
android:layout_marginTop="8dp"
|
||||
android:textSize="16sp"
|
||||
android:text="@string/btn_tests"
|
||||
android:onClick="integrityTests"
|
||||
android:background="@android:color/transparent" />
|
||||
|
||||
<View
|
||||
android:layout_width="1dp"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fillViewport="true"
|
||||
@@ -17,22 +18,31 @@
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1" />
|
||||
|
||||
<ImageView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:importantForAccessibility="no"
|
||||
android:src="@drawable/img_stats" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progressView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_margin="8dp"
|
||||
style="@android:style/Widget.ProgressBar.Horizontal" />
|
||||
tools:max="100"
|
||||
tools:progress="80"
|
||||
style="?android:progressBarStyleHorizontal" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/statusView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:labelFor="@id/sitePasswordField"
|
||||
android:gravity="center"
|
||||
android:background="@android:color/transparent"
|
||||
android:textSize="14sp"
|
||||
android:textColor="@android:color/secondary_text_dark"
|
||||
android:textSize="12sp"
|
||||
android:textColor="@android:color/tertiary_text_dark"
|
||||
android:text="@string/tests_testing" />
|
||||
|
||||
<TextView
|
||||
@@ -53,8 +63,15 @@
|
||||
android:layout_marginTop="8dp"
|
||||
android:enabled="false"
|
||||
android:text="@string/tests_btn_testing"
|
||||
android:onClick="onAction"/>
|
||||
android:onClick="onAction" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/nativeKDFField"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="12sp"
|
||||
android:textColor="@android:color/tertiary_text_dark"
|
||||
android:text="@string/nativeKDF" />
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
||||
|
||||
@@ -8,15 +8,18 @@
|
||||
<string name="masterPassword_hint">Your master password</string>
|
||||
<string name="siteName_hint">eg. google.com</string>
|
||||
<string name="sitePassword_hint">Tap to copy</string>
|
||||
<string name="siteCounter_hint">Password #</string>
|
||||
<string name="siteType_hint">Type</string>
|
||||
<string name="siteCounter_hint">Counter</string>
|
||||
<string name="siteVersion_hint">Algorithm</string>
|
||||
<string name="empty" />
|
||||
<string name="btn_tests">Integrity Tests ❯</string>
|
||||
<string name="tests_unavailable">Test suite unavailable.</string>
|
||||
<string name="tests_btn_unavailable">Exit</string>
|
||||
<string name="tests_btn_unavailable">Retest</string>
|
||||
<string name="tests_testing">Testing device\'s password generation integrity…</string>
|
||||
<string name="tests_btn_testing">Please Stand By…</string>
|
||||
<string name="tests_failed">Incompatible device or OS.</string>
|
||||
<string name="tests_btn_failed">Exit</string>
|
||||
<string name="tests_btn_failed">Retest</string>
|
||||
<string name="tests_passed">Integrity checks passed!</string>
|
||||
<string name="tests_btn_passed">Continue</string>
|
||||
<string name="tests_btn_passed">Close</string>
|
||||
<string name="nativeKDF">Use native key derivation</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package com.lyndir.masterpassword;
|
||||
|
||||
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.ifNotNullElse;
|
||||
import static com.lyndir.lhunath.opal.system.util.StringUtils.strf;
|
||||
|
||||
import android.app.*;
|
||||
@@ -17,9 +16,11 @@ import android.widget.*;
|
||||
import butterknife.ButterKnife;
|
||||
import butterknife.InjectView;
|
||||
import com.google.common.base.Throwables;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.primitives.UnsignedInteger;
|
||||
import com.google.common.util.concurrent.*;
|
||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||
import java.text.MessageFormat;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
import javax.annotation.Nullable;
|
||||
@@ -32,19 +33,10 @@ public class EmergencyActivity extends Activity {
|
||||
private static final ClipData EMPTY_CLIP = new ClipData( new ClipDescription( "", new String[0] ), new ClipData.Item( "" ) );
|
||||
private static final int PASSWORD_NOTIFICATION = 0;
|
||||
|
||||
private final ListeningExecutorService executor = MoreExecutors.listeningDecorator( Executors.newSingleThreadExecutor() );
|
||||
private final ValueChangedListener updateMasterKey = new ValueChangedListener() {
|
||||
@Override
|
||||
void update() {
|
||||
updateMasterKey();
|
||||
}
|
||||
};
|
||||
private final ValueChangedListener updateSitePassword = new ValueChangedListener() {
|
||||
@Override
|
||||
void update() {
|
||||
updateSitePassword();
|
||||
}
|
||||
};
|
||||
private final Preferences preferences = Preferences.get( this );
|
||||
private final ListeningExecutorService executor = MoreExecutors.listeningDecorator( Executors.newSingleThreadExecutor() );
|
||||
private final ImmutableList<MPSiteType> allSiteTypes = ImmutableList.copyOf( MPSiteType.forClass( MPSiteTypeClass.Generated ) );
|
||||
private final ImmutableList<MasterKey.Version> allVersions = ImmutableList.copyOf( MasterKey.Version.values() );
|
||||
|
||||
private ListenableFuture<MasterKey> masterKeyFuture;
|
||||
|
||||
@@ -60,14 +52,14 @@ public class EmergencyActivity extends Activity {
|
||||
@InjectView(R.id.siteNameField)
|
||||
EditText siteNameField;
|
||||
|
||||
@InjectView(R.id.siteTypeField)
|
||||
Spinner siteTypeField;
|
||||
@InjectView(R.id.siteTypeButton)
|
||||
Button siteTypeButton;
|
||||
|
||||
@InjectView(R.id.counterField)
|
||||
EditText counterField;
|
||||
Button siteCounterButton;
|
||||
|
||||
@InjectView(R.id.siteVersionField)
|
||||
Spinner siteVersionField;
|
||||
@InjectView(R.id.siteVersionButton)
|
||||
Button siteVersionButton;
|
||||
|
||||
@InjectView(R.id.sitePasswordField)
|
||||
Button sitePasswordField;
|
||||
@@ -84,8 +76,9 @@ public class EmergencyActivity extends Activity {
|
||||
@InjectView(R.id.maskPasswordField)
|
||||
CheckBox maskPasswordField;
|
||||
|
||||
private int hc_userName;
|
||||
private int hc_masterPassword;
|
||||
private int id_userName;
|
||||
private int id_masterPassword;
|
||||
private int id_version;
|
||||
private String sitePassword;
|
||||
|
||||
public static void start(Context context) {
|
||||
@@ -101,12 +94,58 @@ public class EmergencyActivity extends Activity {
|
||||
setContentView( R.layout.activity_emergency );
|
||||
ButterKnife.inject( this );
|
||||
|
||||
fullNameField.setOnFocusChangeListener( updateMasterKey );
|
||||
masterPasswordField.setOnFocusChangeListener( updateMasterKey );
|
||||
siteNameField.addTextChangedListener( updateSitePassword );
|
||||
siteTypeField.setOnItemSelectedListener( updateSitePassword );
|
||||
counterField.addTextChangedListener( updateSitePassword );
|
||||
siteVersionField.setOnItemSelectedListener( updateMasterKey );
|
||||
fullNameField.setOnFocusChangeListener( new ValueChangedListener() {
|
||||
@Override
|
||||
void update() {
|
||||
updateMasterKey();
|
||||
}
|
||||
} );
|
||||
masterPasswordField.setOnFocusChangeListener( new ValueChangedListener() {
|
||||
@Override
|
||||
void update() {
|
||||
updateMasterKey();
|
||||
}
|
||||
} );
|
||||
siteNameField.addTextChangedListener( new ValueChangedListener() {
|
||||
@Override
|
||||
void update() {
|
||||
siteCounterButton.setText( MessageFormat.format( "{0}", 1 ) );
|
||||
updateSitePassword();
|
||||
}
|
||||
} );
|
||||
siteTypeButton.setOnClickListener( new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(final View v) {
|
||||
@SuppressWarnings("SuspiciousMethodCalls")
|
||||
MPSiteType siteType =
|
||||
allSiteTypes.get( (allSiteTypes.indexOf( siteTypeButton.getTag() ) + 1) % allSiteTypes.size() );
|
||||
preferences.setDefaultSiteType( siteType );
|
||||
siteTypeButton.setTag( siteType );
|
||||
siteTypeButton.setText( siteType.getShortName() );
|
||||
updateSitePassword();
|
||||
}
|
||||
} );
|
||||
siteCounterButton.setOnClickListener( new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(final View v) {
|
||||
UnsignedInteger counter =
|
||||
UnsignedInteger.valueOf( siteCounterButton.getText().toString() ).plus( UnsignedInteger.ONE );
|
||||
siteCounterButton.setText( MessageFormat.format( "{0}", counter ) );
|
||||
updateSitePassword();
|
||||
}
|
||||
} );
|
||||
siteVersionButton.setOnClickListener( new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(final View v) {
|
||||
@SuppressWarnings("SuspiciousMethodCalls")
|
||||
MasterKey.Version siteVersion =
|
||||
allVersions.get( (allVersions.indexOf( siteVersionButton.getTag() ) + 1) % allVersions.size() );
|
||||
preferences.setDefaultVersion( siteVersion );
|
||||
siteVersionButton.setTag( siteVersion );
|
||||
siteVersionButton.setText( siteVersion.name() );
|
||||
updateMasterKey();
|
||||
}
|
||||
} );
|
||||
sitePasswordField.addTextChangedListener( new ValueChangedListener() {
|
||||
@Override
|
||||
void update() {
|
||||
@@ -127,32 +166,26 @@ public class EmergencyActivity extends Activity {
|
||||
sitePasswordField.setTypeface( Res.sourceCodePro_Black );
|
||||
sitePasswordField.setPaintFlags( sitePasswordField.getPaintFlags() | Paint.SUBPIXEL_TEXT_FLAG );
|
||||
|
||||
siteTypeField.setAdapter( new ArrayAdapter<>( this, R.layout.spinner_item, MPSiteType.forClass( MPSiteTypeClass.Generated ) ) );
|
||||
siteTypeField.setSelection( MPSiteType.GeneratedLong.ordinal() );
|
||||
|
||||
siteVersionField.setAdapter( new ArrayAdapter<>( this, R.layout.spinner_item, MasterKey.Version.values() ) );
|
||||
siteVersionField.setSelection( MasterKey.Version.CURRENT.ordinal() );
|
||||
|
||||
rememberFullNameField.setOnCheckedChangeListener( new CompoundButton.OnCheckedChangeListener() {
|
||||
@Override
|
||||
public void onCheckedChanged(final CompoundButton buttonView, final boolean isChecked) {
|
||||
getPreferences( MODE_PRIVATE ).edit().putBoolean( "rememberFullName", isChecked ).apply();
|
||||
preferences.setRememberFullName( isChecked );
|
||||
if (isChecked)
|
||||
getPreferences( MODE_PRIVATE ).edit().putString( "fullName", fullNameField.getText().toString() ).apply();
|
||||
preferences.setFullName( fullNameField.getText().toString() );
|
||||
else
|
||||
getPreferences( MODE_PRIVATE ).edit().putString( "fullName", "" ).apply();
|
||||
preferences.setFullName( null );
|
||||
}
|
||||
} );
|
||||
forgetPasswordField.setOnCheckedChangeListener( new CompoundButton.OnCheckedChangeListener() {
|
||||
@Override
|
||||
public void onCheckedChanged(final CompoundButton buttonView, final boolean isChecked) {
|
||||
getPreferences( MODE_PRIVATE ).edit().putBoolean( "forgetPassword", isChecked ).apply();
|
||||
preferences.setForgetPassword( isChecked );
|
||||
}
|
||||
} );
|
||||
maskPasswordField.setOnCheckedChangeListener( new CompoundButton.OnCheckedChangeListener() {
|
||||
@Override
|
||||
public void onCheckedChanged(final CompoundButton buttonView, final boolean isChecked) {
|
||||
getPreferences( MODE_PRIVATE ).edit().putBoolean( "maskPassword", isChecked ).apply();
|
||||
preferences.setMaskPassword( isChecked );
|
||||
sitePasswordField.setTransformationMethod( isChecked? new PasswordTransformationMethod(): null );
|
||||
}
|
||||
} );
|
||||
@@ -162,13 +195,24 @@ public class EmergencyActivity extends Activity {
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
|
||||
fullNameField.setText( getPreferences( MODE_PRIVATE ).getString( "fullName", "" ) );
|
||||
rememberFullNameField.setChecked( isRememberFullNameEnabled() );
|
||||
forgetPasswordField.setChecked( isForgetPasswordEnabled() );
|
||||
maskPasswordField.setChecked( isMaskPasswordEnabled() );
|
||||
sitePasswordField.setTransformationMethod( isMaskPasswordEnabled()? new PasswordTransformationMethod(): null );
|
||||
MasterKey.setAllowNativeByDefault( preferences.isAllowNativeKDF() );
|
||||
|
||||
if (TextUtils.isEmpty( masterPasswordField.getText() ))
|
||||
fullNameField.setText( preferences.getFullName() );
|
||||
rememberFullNameField.setChecked( preferences.isRememberFullName() );
|
||||
forgetPasswordField.setChecked( preferences.isForgetPassword() );
|
||||
maskPasswordField.setChecked( preferences.isMaskPassword() );
|
||||
sitePasswordField.setTransformationMethod( preferences.isMaskPassword()? new PasswordTransformationMethod(): null );
|
||||
MPSiteType defaultSiteType = preferences.getDefaultSiteType();
|
||||
siteTypeButton.setTag( defaultSiteType );
|
||||
siteTypeButton.setText( defaultSiteType.getShortName() );
|
||||
MasterKey.Version defaultVersion = preferences.getDefaultVersion();
|
||||
siteVersionButton.setTag( defaultVersion );
|
||||
siteVersionButton.setText( defaultVersion.name() );
|
||||
siteCounterButton.setText( MessageFormat.format( "{0}", 1 ) );
|
||||
|
||||
if (TextUtils.isEmpty( fullNameField.getText() ))
|
||||
fullNameField.requestFocus();
|
||||
else if (TextUtils.isEmpty( masterPasswordField.getText() ))
|
||||
masterPasswordField.requestFocus();
|
||||
else
|
||||
siteNameField.requestFocus();
|
||||
@@ -176,9 +220,9 @@ public class EmergencyActivity extends Activity {
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
if (isForgetPasswordEnabled()) {
|
||||
if (preferences.isForgetPassword()) {
|
||||
synchronized (this) {
|
||||
hc_userName = hc_masterPassword = 0;
|
||||
id_userName = id_masterPassword = 0;
|
||||
if (masterKeyFuture != null) {
|
||||
masterKeyFuture.cancel( true );
|
||||
masterKeyFuture = null;
|
||||
@@ -195,35 +239,20 @@ public class EmergencyActivity extends Activity {
|
||||
super.onPause();
|
||||
}
|
||||
|
||||
private boolean isRememberFullNameEnabled() {
|
||||
return getPreferences( MODE_PRIVATE ).getBoolean( "rememberFullName", false );
|
||||
}
|
||||
|
||||
private boolean isForgetPasswordEnabled() {
|
||||
return getPreferences( MODE_PRIVATE ).getBoolean( "forgetPassword", false );
|
||||
}
|
||||
|
||||
private boolean isMaskPasswordEnabled() {
|
||||
return getPreferences( MODE_PRIVATE ).getBoolean( "maskPassword", false );
|
||||
}
|
||||
|
||||
private synchronized void updateMasterKey() {
|
||||
final String fullName = fullNameField.getText().toString();
|
||||
final char[] masterPassword = masterPasswordField.getText().toString().toCharArray();
|
||||
final MasterKey.Version version = (MasterKey.Version) siteVersionField.getSelectedItem();
|
||||
try {
|
||||
if (fullName.hashCode() == hc_userName && Arrays.hashCode( masterPassword ) == hc_masterPassword &&
|
||||
masterKeyFuture != null && masterKeyFuture.get().getAlgorithmVersion() == version)
|
||||
return;
|
||||
}
|
||||
catch (InterruptedException | ExecutionException e) {
|
||||
final MasterKey.Version version = (MasterKey.Version) siteVersionButton.getTag();
|
||||
if (fullName.hashCode() == id_userName && Arrays.hashCode( masterPassword ) == id_masterPassword &&
|
||||
version.ordinal() == id_version && masterKeyFuture != null && !masterKeyFuture.isCancelled())
|
||||
return;
|
||||
}
|
||||
hc_userName = fullName.hashCode();
|
||||
hc_masterPassword = Arrays.hashCode( masterPassword );
|
||||
|
||||
if (isRememberFullNameEnabled())
|
||||
getPreferences( MODE_PRIVATE ).edit().putString( "fullName", fullName ).apply();
|
||||
id_userName = fullName.hashCode();
|
||||
id_masterPassword = Arrays.hashCode( masterPassword );
|
||||
id_version = version.ordinal();
|
||||
|
||||
if (preferences.isRememberFullName())
|
||||
preferences.setFullName( fullName );
|
||||
|
||||
if (masterKeyFuture != null)
|
||||
masterKeyFuture.cancel( true );
|
||||
@@ -243,7 +272,7 @@ public class EmergencyActivity extends Activity {
|
||||
try {
|
||||
return MasterKey.create( version, fullName, masterPassword );
|
||||
}
|
||||
catch (RuntimeException e) {
|
||||
catch (Exception e) {
|
||||
sitePasswordField.setText( "" );
|
||||
progressView.setVisibility( View.INVISIBLE );
|
||||
logger.err( e, "While generating master key." );
|
||||
@@ -265,8 +294,8 @@ public class EmergencyActivity extends Activity {
|
||||
|
||||
private void updateSitePassword() {
|
||||
final String siteName = siteNameField.getText().toString();
|
||||
final MPSiteType type = (MPSiteType) siteTypeField.getSelectedItem();
|
||||
final UnsignedInteger counter = UnsignedInteger.valueOf( ifNotNullElse( counterField.getText(), "1" ).toString() );
|
||||
final MPSiteType type = (MPSiteType) siteTypeButton.getTag();
|
||||
final UnsignedInteger counter = UnsignedInteger.valueOf( siteCounterButton.getText().toString() );
|
||||
|
||||
if (masterKeyFuture == null || siteName.isEmpty() || type == null) {
|
||||
sitePasswordField.setText( "" );
|
||||
@@ -313,6 +342,14 @@ public class EmergencyActivity extends Activity {
|
||||
} );
|
||||
}
|
||||
|
||||
public void integrityTests(View view) {
|
||||
if (masterKeyFuture != null) {
|
||||
masterKeyFuture.cancel( true );
|
||||
masterKeyFuture = null;
|
||||
}
|
||||
TestActivity.startNoSkip( this );
|
||||
}
|
||||
|
||||
public void copySitePassword(View view) {
|
||||
final String currentSitePassword = this.sitePassword;
|
||||
if (TextUtils.isEmpty( currentSitePassword ))
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
package com.lyndir.masterpassword;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Sets;
|
||||
import java.util.Set;
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 2016-02-20
|
||||
*/
|
||||
public class Preferences {
|
||||
|
||||
private static final String PREF_TESTS_PASSED = "integrityTestsPassed";
|
||||
private static final String PREF_NATIVE_KDF = "nativeKDF";
|
||||
private static final String PREF_REMEMBER_FULL_NAME = "rememberFullName";
|
||||
private static final String PREF_FORGET_PASSWORD = "forgetPassword";
|
||||
private static final String PREF_MASK_PASSWORD = "maskPassword";
|
||||
private static final String PREF_FULL_NAME = "fullName";
|
||||
private static final String PREF_SITE_TYPE = "siteType";
|
||||
private static final String PREF_ALGORITHM_VERSION = "algorithmVersion";
|
||||
private static Preferences instance;
|
||||
|
||||
private Context context;
|
||||
@Nullable
|
||||
private SharedPreferences prefs;
|
||||
|
||||
public static synchronized Preferences get(final Context context) {
|
||||
if (instance == null)
|
||||
instance = new Preferences( context );
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
private Preferences(Context context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
private SharedPreferences prefs() {
|
||||
if (prefs == null)
|
||||
prefs = (context = context.getApplicationContext()).getSharedPreferences( getClass().getCanonicalName(), Context.MODE_PRIVATE );
|
||||
|
||||
return prefs;
|
||||
}
|
||||
|
||||
public boolean setNativeKDFEnabled(boolean enabled) {
|
||||
if (isAllowNativeKDF() == enabled)
|
||||
return false;
|
||||
|
||||
prefs().edit().putBoolean( PREF_NATIVE_KDF, enabled ).apply();
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean isAllowNativeKDF() {
|
||||
return prefs().getBoolean( PREF_NATIVE_KDF, MasterKey.isAllowNativeByDefault() );
|
||||
}
|
||||
|
||||
public boolean setTestsPassed(final Set<String> value) {
|
||||
if (Sets.symmetricDifference( getTestsPassed(), value ).isEmpty())
|
||||
return false;
|
||||
|
||||
prefs().edit().putStringSet( PREF_TESTS_PASSED, value ).apply();
|
||||
return true;
|
||||
}
|
||||
|
||||
public Set<String> getTestsPassed() {
|
||||
return prefs().getStringSet( PREF_TESTS_PASSED, ImmutableSet.<String>of() );
|
||||
}
|
||||
|
||||
public boolean setRememberFullName(boolean enabled) {
|
||||
if (isRememberFullName() == enabled)
|
||||
return false;
|
||||
|
||||
prefs().edit().putBoolean( PREF_REMEMBER_FULL_NAME, enabled ).apply();
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean isRememberFullName() {
|
||||
return prefs().getBoolean( PREF_REMEMBER_FULL_NAME, false );
|
||||
}
|
||||
|
||||
public boolean setForgetPassword(boolean enabled) {
|
||||
if (isForgetPassword() == enabled)
|
||||
return false;
|
||||
|
||||
prefs().edit().putBoolean( PREF_FORGET_PASSWORD, enabled ).apply();
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean isForgetPassword() {
|
||||
return prefs().getBoolean( PREF_FORGET_PASSWORD, false );
|
||||
}
|
||||
|
||||
public boolean setMaskPassword(boolean enabled) {
|
||||
if (isMaskPassword() == enabled)
|
||||
return false;
|
||||
|
||||
prefs().edit().putBoolean( PREF_MASK_PASSWORD, enabled ).apply();
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean isMaskPassword() {
|
||||
return prefs().getBoolean( PREF_MASK_PASSWORD, false );
|
||||
}
|
||||
|
||||
public boolean setFullName(@Nullable String value) {
|
||||
if (getFullName().equals( value ))
|
||||
return false;
|
||||
|
||||
prefs().edit().putString( PREF_FULL_NAME, value ).apply();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public String getFullName() {
|
||||
return prefs().getString( PREF_FULL_NAME, "" );
|
||||
}
|
||||
|
||||
public boolean setDefaultSiteType(@Nonnull MPSiteType value) {
|
||||
if (getDefaultSiteType().equals( value ))
|
||||
return false;
|
||||
|
||||
prefs().edit().putInt( PREF_SITE_TYPE, value.ordinal() ).apply();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public MPSiteType getDefaultSiteType() {
|
||||
return MPSiteType.values()[prefs().getInt( PREF_SITE_TYPE, MPSiteType.GeneratedLong.ordinal() )];
|
||||
}
|
||||
|
||||
public boolean setDefaultVersion(@Nonnull MasterKey.Version value) {
|
||||
if (getDefaultVersion().equals( value ))
|
||||
return false;
|
||||
|
||||
prefs().edit().putInt( PREF_ALGORITHM_VERSION, value.ordinal() ).apply();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public MasterKey.Version getDefaultVersion() {
|
||||
return MasterKey.Version.values()[prefs().getInt( PREF_ALGORITHM_VERSION, MasterKey.Version.CURRENT.ordinal() )];
|
||||
}
|
||||
}
|
||||
@@ -3,9 +3,10 @@ package com.lyndir.masterpassword;
|
||||
import static com.lyndir.lhunath.opal.system.util.StringUtils.strf;
|
||||
|
||||
import android.app.*;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.*;
|
||||
import android.view.View;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.*;
|
||||
import butterknife.ButterKnife;
|
||||
import butterknife.InjectView;
|
||||
@@ -13,7 +14,6 @@ import com.google.common.base.*;
|
||||
import com.google.common.collect.*;
|
||||
import com.google.common.util.concurrent.*;
|
||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.*;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
@@ -23,6 +23,7 @@ public class TestActivity extends Activity implements MPTestSuite.Listener {
|
||||
@SuppressWarnings("UnusedDeclaration")
|
||||
private static final Logger logger = Logger.get( TestActivity.class );
|
||||
|
||||
private final Preferences preferences = Preferences.get( this );
|
||||
private final ListeningExecutorService backgroundExecutor = MoreExecutors.listeningDecorator( Executors.newSingleThreadExecutor() );
|
||||
private final ListeningExecutorService mainExecutor = MoreExecutors.listeningDecorator( new MainThreadExecutor() );
|
||||
|
||||
@@ -38,20 +39,34 @@ public class TestActivity extends Activity implements MPTestSuite.Listener {
|
||||
@InjectView(R.id.actionButton)
|
||||
Button actionButton;
|
||||
|
||||
@InjectView(R.id.nativeKDFField)
|
||||
CheckBox nativeKDFField;
|
||||
|
||||
private MPTestSuite testSuite;
|
||||
private ListenableFuture<Boolean> testFuture;
|
||||
private Runnable action;
|
||||
private ImmutableSet<String> testNames;
|
||||
|
||||
public static void startNoSkip(Context context) {
|
||||
context.startActivity( new Intent( context, TestActivity.class ) );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate( savedInstanceState );
|
||||
Res.init( getResources() );
|
||||
|
||||
getWindow().setFlags( WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE );
|
||||
setContentView( R.layout.activity_test );
|
||||
ButterKnife.inject( this );
|
||||
|
||||
nativeKDFField.setOnCheckedChangeListener( new CompoundButton.OnCheckedChangeListener() {
|
||||
@Override
|
||||
public void onCheckedChanged(final CompoundButton buttonView, final boolean isChecked) {
|
||||
preferences.setNativeKDFEnabled( isChecked );
|
||||
MasterKey.setAllowNativeByDefault( isChecked );
|
||||
}
|
||||
} );
|
||||
|
||||
try {
|
||||
setStatus( 0, 0, null );
|
||||
testSuite = new MPTestSuite();
|
||||
@@ -76,63 +91,54 @@ public class TestActivity extends Activity implements MPTestSuite.Listener {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStart() {
|
||||
super.onStart();
|
||||
|
||||
final Set<String> integrityTestsPassed = getPreferences( MODE_PRIVATE ).getStringSet( "integrityTestsPassed",
|
||||
ImmutableSet.<String>of() );
|
||||
if (!FluentIterable.from( testNames ).anyMatch( new Predicate<String>() {
|
||||
@Override
|
||||
public boolean apply(@Nullable final String testName) {
|
||||
return !integrityTestsPassed.contains( testName );
|
||||
}
|
||||
} )) {
|
||||
// None of the tests we need to perform were missing from the tests that have already been passed on this device.
|
||||
finish();
|
||||
EmergencyActivity.start( TestActivity.this );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
|
||||
if (testFuture == null) {
|
||||
setStatus( R.string.tests_testing, R.string.tests_btn_testing, null );
|
||||
Futures.addCallback( testFuture = backgroundExecutor.submit( testSuite ), new FutureCallback<Boolean>() {
|
||||
@Override
|
||||
public void onSuccess(@Nullable final Boolean result) {
|
||||
if (result != null && result)
|
||||
setStatus( R.string.tests_passed, R.string.tests_btn_passed, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
getPreferences( MODE_PRIVATE ).edit().putStringSet( "integrityTestsPassed", testNames ).apply();
|
||||
finish();
|
||||
EmergencyActivity.start( TestActivity.this );
|
||||
}
|
||||
} );
|
||||
else
|
||||
setStatus( R.string.tests_failed, R.string.tests_btn_failed, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
finish();
|
||||
}
|
||||
} );
|
||||
}
|
||||
nativeKDFField.setChecked( preferences.isAllowNativeKDF() );
|
||||
|
||||
@Override
|
||||
public void onFailure(final Throwable t) {
|
||||
logger.err( t, "While running test suite" );
|
||||
setStatus( R.string.tests_failed, R.string.tests_btn_failed, new Runnable() {
|
||||
if (testFuture == null)
|
||||
startTestSuite();
|
||||
}
|
||||
|
||||
private void startTestSuite() {
|
||||
if (testFuture != null)
|
||||
testFuture.cancel( true );
|
||||
|
||||
MasterKey.setAllowNativeByDefault( preferences.isAllowNativeKDF() );
|
||||
|
||||
setStatus( R.string.tests_testing, R.string.tests_btn_testing, null );
|
||||
Futures.addCallback( testFuture = backgroundExecutor.submit( testSuite ), new FutureCallback<Boolean>() {
|
||||
@Override
|
||||
public void onSuccess(@Nullable final Boolean result) {
|
||||
if (result != null && result)
|
||||
setStatus( R.string.tests_passed, R.string.tests_btn_passed, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
preferences.setTestsPassed( testNames );
|
||||
finish();
|
||||
}
|
||||
} );
|
||||
}
|
||||
}, mainExecutor );
|
||||
}
|
||||
else
|
||||
setStatus( R.string.tests_failed, R.string.tests_btn_failed, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
startTestSuite();
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(final Throwable t) {
|
||||
logger.err( t, "While running test suite" );
|
||||
setStatus( R.string.tests_failed, R.string.tests_btn_failed, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
finish();
|
||||
}
|
||||
} );
|
||||
}
|
||||
}, mainExecutor );
|
||||
}
|
||||
|
||||
public void onAction(View v) {
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
<configuration scan="false">
|
||||
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<layout class="ch.qos.logback.classic.PatternLayout">
|
||||
<Pattern>%-8relative %22c{0} [%-5level] %msg%n</Pattern>
|
||||
</layout>
|
||||
<appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%-8relative %22c{0} [%-5level] %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<logger name="com.lyndir" level="${mp.log.level:-INFO}" />
|
||||
|
||||
<root level="INFO">
|
||||
<appender-ref ref="STDOUT" />
|
||||
<appender-ref ref="stdout" />
|
||||
</root>
|
||||
|
||||
</configuration>
|
||||
|
||||
@@ -97,6 +97,7 @@
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jarsigner-plugin</artifactId>
|
||||
<version>1.4</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>signing</id>
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
<configuration scan="false">
|
||||
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<layout class="ch.qos.logback.classic.PatternLayout">
|
||||
<Pattern>%-8relative %22c{0} [%-5level] %msg%n</Pattern>
|
||||
</layout>
|
||||
<appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%-8relative %22c{0} [%-5level] %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<logger name="com.lyndir" level="${mp.log.level:-INFO}" />
|
||||
|
||||
<root level="INFO">
|
||||
<appender-ref ref="STDOUT" />
|
||||
<appender-ref ref="stdout" />
|
||||
</root>
|
||||
|
||||
</configuration>
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
<configuration scan="false">
|
||||
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<layout class="ch.qos.logback.classic.PatternLayout">
|
||||
<Pattern>%-8relative %22c{0} [%-5level] %msg%n</Pattern>
|
||||
</layout>
|
||||
<appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%-8relative %22c{0} [%-5level] %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<logger name="com.lyndir.masterpassword" level="${mp.log.level:-TRACE}" />
|
||||
<logger name="com.lyndir" level="${mp.log.level:-INFO}" />
|
||||
|
||||
<root level="INFO">
|
||||
<appender-ref ref="STDOUT" />
|
||||
<appender-ref ref="stdout" />
|
||||
</root>
|
||||
|
||||
</configuration>
|
||||
|
||||
@@ -179,7 +179,7 @@ NSOperationQueue *_mpwQueue = nil;
|
||||
return @"PIN";
|
||||
|
||||
case MPSiteTypeGeneratedName:
|
||||
return @"Login Name";
|
||||
return @"Name";
|
||||
|
||||
case MPSiteTypeGeneratedPhrase:
|
||||
return @"Phrase";
|
||||
@@ -281,7 +281,7 @@ NSOperationQueue *_mpwQueue = nil;
|
||||
|
||||
- (NSArray *)allTypes {
|
||||
|
||||
return [self allTypesStartingWith:MPSiteTypeGeneratedMaximum];
|
||||
return [self allTypesStartingWith:MPSiteTypeGeneratedPhrase];
|
||||
}
|
||||
|
||||
- (NSArray *)allTypesStartingWith:(MPSiteType)startingType {
|
||||
@@ -298,6 +298,10 @@ NSOperationQueue *_mpwQueue = nil;
|
||||
- (MPSiteType)nextType:(MPSiteType)type {
|
||||
|
||||
switch (type) {
|
||||
case MPSiteTypeGeneratedPhrase:
|
||||
return MPSiteTypeGeneratedName;
|
||||
case MPSiteTypeGeneratedName:
|
||||
return MPSiteTypeGeneratedMaximum;
|
||||
case MPSiteTypeGeneratedMaximum:
|
||||
return MPSiteTypeGeneratedLong;
|
||||
case MPSiteTypeGeneratedLong:
|
||||
@@ -313,7 +317,7 @@ NSOperationQueue *_mpwQueue = nil;
|
||||
case MPSiteTypeStoredPersonal:
|
||||
return MPSiteTypeStoredDevicePrivate;
|
||||
case MPSiteTypeStoredDevicePrivate:
|
||||
return MPSiteTypeGeneratedMaximum;
|
||||
return MPSiteTypeGeneratedPhrase;
|
||||
default:
|
||||
return MPSiteTypeGeneratedLong;
|
||||
}
|
||||
|
||||
@@ -22,6 +22,13 @@
|
||||
|
||||
@end
|
||||
|
||||
@interface MPSiteQuestionEntity(MP)
|
||||
|
||||
- (NSString *)resolveQuestionAnswerUsingKey:(MPKey *)key;
|
||||
- (void)resolveQuestionAnswerUsingKey:(MPKey *)key result:(void ( ^ )(NSString *))result;
|
||||
|
||||
@end
|
||||
|
||||
@interface MPSiteEntity(MP)<MPFixable>
|
||||
|
||||
@property(assign) BOOL loginGenerated;
|
||||
@@ -38,8 +45,11 @@
|
||||
- (BOOL)tryMigrateExplicitly:(BOOL)explicit;
|
||||
- (NSString *)resolveLoginUsingKey:(MPKey *)key;
|
||||
- (NSString *)resolvePasswordUsingKey:(MPKey *)key;
|
||||
- (NSString *)resolveSiteAnswerUsingKey:(MPKey *)key;
|
||||
- (void)resolveLoginUsingKey:(MPKey *)key result:(void ( ^ )(NSString *))result;
|
||||
- (void)resolvePasswordUsingKey:(MPKey *)key result:(void ( ^ )(NSString *))result;
|
||||
- (void)resolveSiteAnswerUsingKey:(MPKey *)key result:(void ( ^ )(NSString *))result;
|
||||
- (void)resolveAnswerUsingKey:(MPKey *)key forQuestion:(NSString *)question result:(void ( ^ )(NSString *))result;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@@ -35,6 +35,20 @@
|
||||
|
||||
@end
|
||||
|
||||
@implementation MPSiteQuestionEntity(MP)
|
||||
|
||||
- (NSString *)resolveQuestionAnswerUsingKey:(MPKey *)key {
|
||||
|
||||
return [self.site.algorithm resolveAnswerForQuestion:self usingKey:key];
|
||||
}
|
||||
|
||||
- (void)resolveQuestionAnswerUsingKey:(MPKey *)key result:(void ( ^ )(NSString *))result {
|
||||
|
||||
[self.site.algorithm resolveAnswerForQuestion:self usingKey:key result:result];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation MPSiteEntity(MP)
|
||||
|
||||
- (MPFixableResult)findAndFixInconsistenciesInContext:(NSManagedObjectContext *)context {
|
||||
@@ -175,6 +189,11 @@
|
||||
return [self.algorithm resolvePasswordForSite:self usingKey:key];
|
||||
}
|
||||
|
||||
- (NSString *)resolveSiteAnswerUsingKey:(MPKey *)key {
|
||||
|
||||
return [self.algorithm resolveAnswerForSite:self usingKey:key];
|
||||
}
|
||||
|
||||
- (void)resolveLoginUsingKey:(MPKey *)key result:(void ( ^ )(NSString *))result {
|
||||
|
||||
[self.algorithm resolveLoginForSite:self usingKey:key result:result];
|
||||
@@ -185,6 +204,19 @@
|
||||
[self.algorithm resolvePasswordForSite:self usingKey:key result:result];
|
||||
}
|
||||
|
||||
- (void)resolveSiteAnswerUsingKey:(MPKey *)key result:(void ( ^ )(NSString *))result {
|
||||
|
||||
[self.algorithm resolveAnswerForSite:self usingKey:key result:result];
|
||||
}
|
||||
|
||||
- (void)resolveAnswerUsingKey:(MPKey *)key forQuestion:(NSString *)question result:(void ( ^ )(NSString *))result {
|
||||
|
||||
MPSiteQuestionEntity *questionEntity = [MPSiteQuestionEntity new];
|
||||
questionEntity.site = self;
|
||||
questionEntity.keyword = question;
|
||||
[questionEntity resolveQuestionAnswerUsingKey:key result:result];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation MPGeneratedSiteEntity(MP)
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="7706" systemVersion="14D136" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="10116" systemVersion="15E65" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="7706"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="10116"/>
|
||||
<capability name="Alignment constraints with different attributes" minToolsVersion="5.1"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
@@ -102,8 +102,8 @@ from anywhere.</string>
|
||||
<constraint firstItem="C4d-ih-ARL" firstAttribute="top" secondItem="283" secondAttribute="bottom" constant="8" symbolic="YES" id="tqS-gV-jpd"/>
|
||||
</constraints>
|
||||
</customView>
|
||||
<button verticalHuggingPriority="750" misplaced="YES" translatesAutoresizingMaskIntoConstraints="NO" id="508">
|
||||
<rect key="frame" x="766" y="8" width="214" height="25"/>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="508">
|
||||
<rect key="frame" x="858" y="8" width="122" height="25"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="122" id="509"/>
|
||||
</constraints>
|
||||
@@ -115,8 +115,8 @@ from anywhere.</string>
|
||||
<action selector="togglePreference:" target="-2" id="574"/>
|
||||
</connections>
|
||||
</button>
|
||||
<textField verticalHuggingPriority="750" misplaced="YES" translatesAutoresizingMaskIntoConstraints="NO" id="562">
|
||||
<rect key="frame" x="21" y="13" width="739" height="17"/>
|
||||
<textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="562">
|
||||
<rect key="frame" x="21" y="13" width="831" height="17"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="To begin: hold command, control and P (⌘⌃P) or access the ●●●| menu icon next to your clock." id="563">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
@property(nonatomic, weak) IBOutlet NSMenuItem *hidePasswordsItem;
|
||||
@property(nonatomic, weak) IBOutlet NSMenuItem *rememberPasswordItem;
|
||||
@property(nonatomic, weak) IBOutlet NSMenuItem *openAtLoginItem;
|
||||
@property(nonatomic, weak) IBOutlet NSMenuItem *showFullScreenItem;
|
||||
@property(nonatomic, weak) IBOutlet NSMenuItem *savePasswordItem;
|
||||
@property(nonatomic, weak) IBOutlet NSMenuItem *createUserItem;
|
||||
@property(nonatomic, weak) IBOutlet NSMenuItem *deleteUserItem;
|
||||
|
||||
@@ -335,11 +335,15 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
|
||||
- (IBAction)togglePreference:(id)sender {
|
||||
|
||||
if (sender == self.hidePasswordsItem)
|
||||
[MPConfig get].hidePasswords = @(![[MPConfig get].hidePasswords boolValue]);
|
||||
[MPConfig get].hidePasswords = @(self.hidePasswordsItem.state != NSOnState);
|
||||
if (sender == self.rememberPasswordItem)
|
||||
[MPConfig get].rememberLogin = @(![[MPConfig get].rememberLogin boolValue]);
|
||||
[MPConfig get].rememberLogin = @(self.rememberPasswordItem.state != NSOnState);
|
||||
if (sender == self.openAtLoginItem)
|
||||
[self setLoginItemEnabled:self.openAtLoginItem.state != NSOnState];
|
||||
if (sender == self.showFullScreenItem) {
|
||||
[MPMacConfig get].fullScreen = @(self.showFullScreenItem.state != NSOnState);
|
||||
[NSApp updateWindows];
|
||||
}
|
||||
if (sender == self.savePasswordItem) {
|
||||
[MPMacAppDelegate managedObjectContextPerformBlockAndWait:^(NSManagedObjectContext *context) {
|
||||
MPUserEntity *activeUser = [[MPMacAppDelegate get] activeUserInContext:context];
|
||||
@@ -610,6 +614,7 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
|
||||
|
||||
BOOL loginItemEnabled = [self loginItemEnabled];
|
||||
self.openAtLoginItem.state = loginItemEnabled? NSOnState: NSOffState;
|
||||
self.showFullScreenItem.state = [[MPMacConfig get].fullScreen boolValue]? NSOnState: NSOffState;
|
||||
self.initialWindowController.openAtLoginButton.state = loginItemEnabled? NSOnState: NSOffState;
|
||||
self.rememberPasswordItem.state = [[MPConfig get].rememberLogin boolValue]? NSOnState: NSOffState;
|
||||
|
||||
|
||||
9
MasterPassword/ObjC/Mac/MPMacApplication.h
Normal file
@@ -0,0 +1,9 @@
|
||||
//
|
||||
// Created by Maarten Billemont on 2016-04-30.
|
||||
// Copyright (c) 2016 Lyndir. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@interface MPMacApplication : NSApplication
|
||||
@end
|
||||
54
MasterPassword/ObjC/Mac/MPMacApplication.m
Normal file
@@ -0,0 +1,54 @@
|
||||
//
|
||||
// Created by Maarten Billemont on 2016-04-30.
|
||||
// Copyright (c) 2016 Lyndir. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPMacApplication.h"
|
||||
|
||||
@interface NSResponder (Editing)
|
||||
|
||||
- (void)undo:(id)sender;
|
||||
- (void)redo:(id)sender;
|
||||
|
||||
@end
|
||||
|
||||
@implementation MPMacApplication {
|
||||
}
|
||||
|
||||
- (void)sendEvent:(NSEvent *)event {
|
||||
|
||||
if ([event type] == NSKeyDown) {
|
||||
if (([event modifierFlags] & NSDeviceIndependentModifierFlagsMask) == NSCommandKeyMask) {
|
||||
if ([[event charactersIgnoringModifiers] isEqualToString:@"x"]) {
|
||||
if ([self sendAction:@selector( cut: ) to:nil from:self])
|
||||
return;
|
||||
}
|
||||
else if ([[event charactersIgnoringModifiers] isEqualToString:@"c"]) {
|
||||
if ([self sendAction:@selector( copy: ) to:nil from:self])
|
||||
return;
|
||||
}
|
||||
else if ([[event charactersIgnoringModifiers] isEqualToString:@"v"]) {
|
||||
if ([self sendAction:@selector( paste: ) to:nil from:self])
|
||||
return;
|
||||
}
|
||||
else if ([[event charactersIgnoringModifiers] isEqualToString:@"z"]) {
|
||||
if ([self sendAction:@selector( undo: ) to:nil from:self])
|
||||
return;
|
||||
}
|
||||
else if ([[event charactersIgnoringModifiers] isEqualToString:@"a"]) {
|
||||
if ([self sendAction:@selector( selectAll: ) to:nil from:self])
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (([event modifierFlags] & NSDeviceIndependentModifierFlagsMask) == (NSCommandKeyMask | NSShiftKeyMask)) {
|
||||
if ([[event charactersIgnoringModifiers] isEqualToString:@"Z"]) {
|
||||
if ([self sendAction:@selector( redo: ) to:nil from:self])
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[super sendEvent:event];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -11,5 +11,6 @@
|
||||
@interface MPMacConfig : MPConfig
|
||||
|
||||
@property(nonatomic, retain) NSString *usedUserName;
|
||||
@property(nonatomic, retain) NSNumber *fullScreen;
|
||||
|
||||
@end
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
@implementation MPMacConfig
|
||||
|
||||
@dynamic usedUserName;
|
||||
@dynamic fullScreen;
|
||||
|
||||
- (id)init {
|
||||
|
||||
@@ -16,7 +17,8 @@
|
||||
return self;
|
||||
|
||||
[self.defaults registerDefaults:@{
|
||||
NSStringFromSelector( @selector(appleID) ) : @"510296984",
|
||||
NSStringFromSelector( @selector( appleID ) ) : @"510296984",
|
||||
NSStringFromSelector( @selector( fullScreen ) ) : @YES,
|
||||
}];
|
||||
|
||||
return self;
|
||||
|
||||
@@ -20,4 +20,5 @@
|
||||
|
||||
|
||||
@interface MPPasswordWindow : NSWindow
|
||||
|
||||
@end
|
||||
|
||||
@@ -18,21 +18,10 @@
|
||||
|
||||
#import "MPPasswordWindow.h"
|
||||
|
||||
|
||||
@implementation MPPasswordWindow
|
||||
|
||||
#pragma mark - Life
|
||||
|
||||
- (void)awakeFromNib {
|
||||
|
||||
[super awakeFromNib];
|
||||
|
||||
self.opaque = NO;
|
||||
self.backgroundColor = [NSColor clearColor];
|
||||
self.level = NSScreenSaverWindowLevel;
|
||||
self.alphaValue = 0;
|
||||
}
|
||||
|
||||
- (BOOL)canBecomeKeyWindow {
|
||||
|
||||
return YES;
|
||||
@@ -40,6 +29,21 @@
|
||||
|
||||
#pragma mark - State
|
||||
|
||||
- (void)update {
|
||||
|
||||
if ([[MPMacConfig get].fullScreen boolValue]) {
|
||||
[self setLevel:NSScreenSaverWindowLevel];
|
||||
[self setFrame:self.screen.frame display:YES];
|
||||
}
|
||||
else if (self.level != NSNormalWindowLevel) {
|
||||
[self setLevel:NSNormalWindowLevel];
|
||||
[self setFrame:NSMakeRect( 0, 0, 640, 600) display:NO];
|
||||
[self center];
|
||||
}
|
||||
|
||||
[super update];
|
||||
}
|
||||
|
||||
#pragma mark - Private
|
||||
|
||||
@end
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#import "MPSiteModel.h"
|
||||
#import "MPSitesTableView.h"
|
||||
#import "MPPasswordWindow.h"
|
||||
|
||||
@class MPMacAppDelegate;
|
||||
|
||||
@@ -28,11 +29,11 @@
|
||||
@property(nonatomic) NSString *masterPassword;
|
||||
@property(nonatomic) BOOL showVersionContainer;
|
||||
@property(nonatomic) BOOL alternatePressed;
|
||||
@property(nonatomic) BOOL shiftPressed;
|
||||
@property(nonatomic) BOOL locked;
|
||||
@property(nonatomic) BOOL newUser;
|
||||
|
||||
@property(nonatomic, weak) IBOutlet NSArrayController *sitesController;
|
||||
@property(nonatomic, weak) IBOutlet NSImageView *blurView;
|
||||
@property(nonatomic, weak) IBOutlet NSTextField *inputLabel;
|
||||
@property(nonatomic, weak) IBOutlet NSTextField *securePasswordField;
|
||||
@property(nonatomic, weak) IBOutlet NSTextField *revealPasswordField;
|
||||
@@ -44,6 +45,10 @@
|
||||
@property(nonatomic, strong) IBOutlet NSBox *passwordTypesBox;
|
||||
@property(nonatomic, weak) IBOutlet NSMatrix *passwordTypesMatrix;
|
||||
|
||||
@property(nonatomic, strong) IBOutlet NSBox *securityQuestionsBox;
|
||||
@property(nonatomic, weak) IBOutlet NSTextField *securityQuestionField;
|
||||
@property(nonatomic, weak) IBOutlet NSTextField *securityAnswerField;
|
||||
|
||||
- (BOOL)handleCommand:(SEL)commandSelector;
|
||||
|
||||
@end
|
||||
|
||||
@@ -23,14 +23,6 @@
|
||||
#import "MPAppDelegate_Store.h"
|
||||
#import "MPAppDelegate_Key.h"
|
||||
|
||||
#define MPAlertIncorrectMP @"MPAlertIncorrectMP"
|
||||
#define MPAlertChangeMP @"MPAlertChangeMP"
|
||||
#define MPAlertCreateSite @"MPAlertCreateSite"
|
||||
#define MPAlertChangeType @"MPAlertChangeType"
|
||||
#define MPAlertChangeLogin @"MPAlertChangeLogin"
|
||||
#define MPAlertChangeContent @"MPAlertChangeContent"
|
||||
#define MPAlertDeleteSite @"MPAlertDeleteSite"
|
||||
|
||||
@interface MPPasswordWindowController()
|
||||
|
||||
@property(nonatomic, strong) CAGradientLayer *siteGradient;
|
||||
@@ -61,7 +53,7 @@
|
||||
[[NSNotificationCenter defaultCenter] addObserverForName:NSWindowDidBecomeKeyNotification object:self.window
|
||||
queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
|
||||
prof_new( @"didBecomeKey" );
|
||||
[self fadeIn];
|
||||
[self.window makeKeyAndOrderFront:nil];
|
||||
prof_rewind( @"fadeIn" );
|
||||
[self updateUser];
|
||||
prof_finish( @"updateUser" );
|
||||
@@ -75,7 +67,7 @@
|
||||
[[NSNotificationCenter defaultCenter] addObserverForName:NSApplicationWillResignActiveNotification object:nil
|
||||
queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
|
||||
#ifndef DEBUG
|
||||
[self fadeOut];
|
||||
[self.window fadeOut];
|
||||
#endif
|
||||
}];
|
||||
[[NSNotificationCenter defaultCenter] addObserverForName:MPSignedInNotification object:nil
|
||||
@@ -124,6 +116,10 @@
|
||||
|
||||
- (void)flagsChanged:(NSEvent *)theEvent {
|
||||
|
||||
BOOL shiftPressed = (theEvent.modifierFlags & NSShiftKeyMask) != 0;
|
||||
if (shiftPressed != self.shiftPressed)
|
||||
self.shiftPressed = shiftPressed;
|
||||
|
||||
BOOL alternatePressed = (theEvent.modifierFlags & NSAlternateKeyMask) != 0;
|
||||
if (alternatePressed != self.alternatePressed) {
|
||||
self.alternatePressed = alternatePressed;
|
||||
@@ -189,8 +185,7 @@
|
||||
if (!success)
|
||||
[[NSAlert alertWithError:[NSError errorWithDomain:MPErrorDomain code:0 userInfo:@{
|
||||
NSLocalizedDescriptionKey : strf( @"Incorrect master password for user %@", userName )
|
||||
}]] beginSheetModalForWindow:self.window modalDelegate:self
|
||||
didEndSelector:@selector( alertDidEnd:returnCode:contextInfo: ) contextInfo:MPAlertIncorrectMP];
|
||||
}]] beginSheetModalForWindow:self.window completionHandler:nil];
|
||||
} );
|
||||
}];
|
||||
}
|
||||
@@ -221,13 +216,96 @@
|
||||
[self replaceFonts:rowView];
|
||||
}
|
||||
|
||||
#pragma mark - NSAlert
|
||||
#pragma mark - State
|
||||
|
||||
- (void)alertDidEnd:(NSAlert *)alert returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo {
|
||||
- (void)insertObject:(MPSiteModel *)model inSitesAtIndex:(NSUInteger)index {
|
||||
|
||||
if (contextInfo == MPAlertIncorrectMP)
|
||||
return;
|
||||
if (contextInfo == MPAlertChangeMP) {
|
||||
[self.sites insertObject:model atIndex:index];
|
||||
}
|
||||
|
||||
- (void)removeObjectFromSitesAtIndex:(NSUInteger)index {
|
||||
|
||||
[self.sites removeObjectAtIndex:index];
|
||||
}
|
||||
|
||||
- (MPSiteModel *)selectedSite {
|
||||
|
||||
return [self.sitesController.selectedObjects firstObject];
|
||||
}
|
||||
|
||||
#pragma mark - Actions
|
||||
|
||||
- (IBAction)settings:(id)sender {
|
||||
|
||||
[self.window close];
|
||||
[[MPMacAppDelegate get] showPopup:sender];
|
||||
}
|
||||
|
||||
- (IBAction)deleteSite:(id)sender {
|
||||
|
||||
NSAlert *alert = [NSAlert new];
|
||||
[alert addButtonWithTitle:@"Delete"];
|
||||
[alert addButtonWithTitle:@"Cancel"];
|
||||
[alert setMessageText:@"Delete Site?"];
|
||||
[alert setInformativeText:strf( @"Do you want to delete the site named:\n\n%@", self.selectedSite.name )];
|
||||
[alert beginSheetModalForWindow:self.window completionHandler:^(NSModalResponse returnCode) {
|
||||
switch (returnCode) {
|
||||
case NSAlertFirstButtonReturn: {
|
||||
// "Delete" button.
|
||||
[MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
||||
[context deleteObject:[self.selectedSite entityInContext:context]];
|
||||
[context saveToStore];
|
||||
}];
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (IBAction)changeLogin:(id)sender {
|
||||
|
||||
NSAlert *alert = [NSAlert new];
|
||||
[alert addButtonWithTitle:@"Save"];
|
||||
[alert addButtonWithTitle:@"Cancel"];
|
||||
[alert setMessageText:@"Change Login Name"];
|
||||
[alert setInformativeText:strf( @"Enter the login name for: %@", self.selectedSite.name )];
|
||||
NSTextField *loginField = [[NSTextField alloc] initWithFrame:NSMakeRect( 0, 0, 200, 22 )];
|
||||
loginField.stringValue = self.selectedSite.loginName?: @"";
|
||||
[loginField selectText:self];
|
||||
[alert setAccessoryView:loginField];
|
||||
[alert layout];
|
||||
[alert beginSheetModalForWindow:self.window completionHandler:^(NSModalResponse returnCode) {
|
||||
switch (returnCode) {
|
||||
case NSAlertFirstButtonReturn: {
|
||||
// "Save" button.
|
||||
NSString *loginName = [(NSSecureTextField *)alert.accessoryView stringValue];
|
||||
[MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
||||
MPSiteEntity *entity = [self.selectedSite entityInContext:context];
|
||||
entity.loginName = loginName;
|
||||
[context saveToStore];
|
||||
}];
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (IBAction)resetMasterPassword:(id)sender {
|
||||
|
||||
NSAlert *alert = [NSAlert new];
|
||||
[alert addButtonWithTitle:@"Reset"];
|
||||
[alert addButtonWithTitle:@"Cancel"];
|
||||
[alert setMessageText:@"Reset My Master Password"];
|
||||
[alert setInformativeText:strf( @"This will allow you to change %@'s master password.\n\n"
|
||||
@"WARNING: All your site passwords will change. Do this only if you've forgotten your "
|
||||
@"master password and are fully prepared to change all your sites' passwords to the new ones.",
|
||||
[MPMacAppDelegate get].activeUserForMainThread.name )];
|
||||
|
||||
[alert beginSheetModalForWindow:self.window completionHandler:^(NSModalResponse returnCode) {
|
||||
switch (returnCode) {
|
||||
case NSAlertFirstButtonReturn: {
|
||||
// "Reset" button.
|
||||
@@ -253,24 +331,66 @@
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (IBAction)changePassword:(id)sender {
|
||||
|
||||
if (!self.selectedSite.stored)
|
||||
return;
|
||||
}
|
||||
if (contextInfo == MPAlertCreateSite) {
|
||||
|
||||
NSAlert *alert = [NSAlert new];
|
||||
[alert addButtonWithTitle:@"Save"];
|
||||
[alert addButtonWithTitle:@"Cancel"];
|
||||
[alert setMessageText:@"Change Password"];
|
||||
[alert setInformativeText:strf( @"Enter the new password for: %@", self.selectedSite.name )];
|
||||
[alert setAccessoryView:[[NSSecureTextField alloc] initWithFrame:NSMakeRect( 0, 0, 200, 22 )]];
|
||||
[alert layout];
|
||||
[alert beginSheetModalForWindow:self.window completionHandler:^(NSModalResponse returnCode) {
|
||||
switch (returnCode) {
|
||||
case NSAlertFirstButtonReturn: {
|
||||
// "Create" button.
|
||||
[[MPMacAppDelegate get] addSiteNamed:[self.siteField stringValue] completion:
|
||||
^(MPSiteEntity *site, NSManagedObjectContext *context) {
|
||||
if (site)
|
||||
PearlMainQueue( ^{ [self updateSites]; } );
|
||||
}];
|
||||
// "Save" button.
|
||||
NSString *password = [(NSSecureTextField *)alert.accessoryView stringValue];
|
||||
[MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
||||
MPSiteEntity *entity = [self.selectedSite entityInContext:context];
|
||||
[entity.algorithm savePassword:password toSite:entity usingKey:[MPMacAppDelegate get].key];
|
||||
[context saveToStore];
|
||||
}];
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (IBAction)changeType:(id)sender {
|
||||
|
||||
MPSiteModel *site = self.selectedSite;
|
||||
NSArray *types = [site.algorithm allTypes];
|
||||
[self.passwordTypesMatrix renewRows:(NSInteger)[types count] columns:1];
|
||||
for (NSUInteger t = 0; t < [types count]; ++t) {
|
||||
MPSiteType type = (MPSiteType)[types[t] unsignedIntegerValue];
|
||||
NSString *title = [site.algorithm nameOfType:type];
|
||||
if (type & MPSiteTypeClassGenerated)
|
||||
title = strf( @"%@ – %@", [site.algorithm generatePasswordForSiteNamed:site.name ofType:type withCounter:site.counter
|
||||
usingKey:[MPMacAppDelegate get].key], title );
|
||||
|
||||
NSButtonCell *cell = [self.passwordTypesMatrix cellAtRow:(NSInteger)t column:0];
|
||||
cell.tag = type;
|
||||
cell.state = type == site.type? NSOnState: NSOffState;
|
||||
cell.title = title;
|
||||
}
|
||||
if (contextInfo == MPAlertChangeType) {
|
||||
|
||||
self.passwordTypesBox.title = strf( @"Choose a password type for %@:", site.name );
|
||||
|
||||
NSAlert *alert = [NSAlert new];
|
||||
[alert addButtonWithTitle:@"Save"];
|
||||
[alert addButtonWithTitle:@"Cancel"];
|
||||
[alert setMessageText:@"Change Password Type"];
|
||||
[alert setAccessoryView:self.passwordTypesBox];
|
||||
[alert layout];
|
||||
[alert beginSheetModalForWindow:self.window completionHandler:^(NSModalResponse returnCode) {
|
||||
switch (returnCode) {
|
||||
case NSAlertFirstButtonReturn: {
|
||||
// "Save" button.
|
||||
@@ -288,166 +408,32 @@
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (contextInfo == MPAlertChangeLogin) {
|
||||
switch (returnCode) {
|
||||
case NSAlertFirstButtonReturn: {
|
||||
// "Save" button.
|
||||
NSString *loginName = [(NSSecureTextField *)alert.accessoryView stringValue];
|
||||
[MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
||||
MPSiteEntity *entity = [self.selectedSite entityInContext:context];
|
||||
entity.loginName = loginName;
|
||||
[context saveToStore];
|
||||
}];
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (contextInfo == MPAlertChangeContent) {
|
||||
switch (returnCode) {
|
||||
case NSAlertFirstButtonReturn: {
|
||||
// "Save" button.
|
||||
NSString *password = [(NSSecureTextField *)alert.accessoryView stringValue];
|
||||
[MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
||||
MPSiteEntity *entity = [self.selectedSite entityInContext:context];
|
||||
[entity.algorithm savePassword:password toSite:entity usingKey:[MPMacAppDelegate get].key];
|
||||
[context saveToStore];
|
||||
}];
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (contextInfo == MPAlertDeleteSite) {
|
||||
switch (returnCode) {
|
||||
case NSAlertFirstButtonReturn: {
|
||||
// "Delete" button.
|
||||
[MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
||||
[context deleteObject:[self.selectedSite entityInContext:context]];
|
||||
[context saveToStore];
|
||||
}];
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
#pragma mark - State
|
||||
|
||||
- (void)insertObject:(MPSiteModel *)model inSitesAtIndex:(NSUInteger)index {
|
||||
|
||||
[self.sites insertObject:model atIndex:index];
|
||||
}
|
||||
|
||||
- (void)removeObjectFromSitesAtIndex:(NSUInteger)index {
|
||||
|
||||
[self.sites removeObjectAtIndex:index];
|
||||
}
|
||||
|
||||
- (MPSiteModel *)selectedSite {
|
||||
|
||||
return [self.sitesController.selectedObjects firstObject];
|
||||
}
|
||||
|
||||
#pragma mark - Actions
|
||||
|
||||
- (IBAction)settings:(id)sender {
|
||||
|
||||
[self fadeOut:NO];
|
||||
[[MPMacAppDelegate get] showPopup:sender];
|
||||
}
|
||||
|
||||
- (IBAction)deleteSite:(id)sender {
|
||||
|
||||
NSAlert *alert = [NSAlert new];
|
||||
[alert addButtonWithTitle:@"Delete"];
|
||||
[alert addButtonWithTitle:@"Cancel"];
|
||||
[alert setMessageText:@"Delete Site?"];
|
||||
[alert setInformativeText:strf( @"Do you want to delete the site named:\n\n%@", self.selectedSite.name )];
|
||||
[alert beginSheetModalForWindow:self.window modalDelegate:self
|
||||
didEndSelector:@selector( alertDidEnd:returnCode:contextInfo: ) contextInfo:MPAlertDeleteSite];
|
||||
}
|
||||
|
||||
- (IBAction)changeLogin:(id)sender {
|
||||
|
||||
NSAlert *alert = [NSAlert new];
|
||||
[alert addButtonWithTitle:@"Save"];
|
||||
[alert addButtonWithTitle:@"Cancel"];
|
||||
[alert setMessageText:@"Change Login Name"];
|
||||
[alert setInformativeText:strf( @"Enter the login name for: %@", self.selectedSite.name )];
|
||||
NSTextField *loginField = [[NSTextField alloc] initWithFrame:NSMakeRect( 0, 0, 200, 22 )];
|
||||
loginField.stringValue = self.selectedSite.loginName?: @"";
|
||||
[loginField selectText:self];
|
||||
[alert setAccessoryView:loginField];
|
||||
[alert layout];
|
||||
[alert beginSheetModalForWindow:self.window modalDelegate:self
|
||||
didEndSelector:@selector( alertDidEnd:returnCode:contextInfo: ) contextInfo:MPAlertChangeLogin];
|
||||
}
|
||||
|
||||
- (IBAction)resetMasterPassword:(id)sender {
|
||||
|
||||
MPUserEntity *activeUser = [MPMacAppDelegate get].activeUserForMainThread;
|
||||
|
||||
NSAlert *alert = [NSAlert new];
|
||||
[alert addButtonWithTitle:@"Reset"];
|
||||
[alert addButtonWithTitle:@"Cancel"];
|
||||
[alert setMessageText:@"Reset My Master Password"];
|
||||
[alert setInformativeText:strf( @"This will allow you to change %@'s master password.\n\n"
|
||||
@"WARNING: All your site passwords will change. Do this only if you've forgotten your "
|
||||
@"master password and are fully prepared to change all your sites' passwords to the new ones.", activeUser.name )];
|
||||
[alert beginSheetModalForWindow:self.window modalDelegate:self
|
||||
didEndSelector:@selector( alertDidEnd:returnCode:contextInfo: ) contextInfo:MPAlertChangeMP];
|
||||
}
|
||||
|
||||
- (IBAction)changePassword:(id)sender {
|
||||
|
||||
if (!self.selectedSite.stored)
|
||||
return;
|
||||
|
||||
NSAlert *alert = [NSAlert new];
|
||||
[alert addButtonWithTitle:@"Save"];
|
||||
[alert addButtonWithTitle:@"Cancel"];
|
||||
[alert setMessageText:@"Change Password"];
|
||||
[alert setInformativeText:strf( @"Enter the new password for: %@", self.selectedSite.name )];
|
||||
[alert setAccessoryView:[[NSSecureTextField alloc] initWithFrame:NSMakeRect( 0, 0, 200, 22 )]];
|
||||
[alert layout];
|
||||
[alert beginSheetModalForWindow:self.window modalDelegate:self
|
||||
didEndSelector:@selector( alertDidEnd:returnCode:contextInfo: ) contextInfo:MPAlertChangeContent];
|
||||
}
|
||||
|
||||
- (IBAction)changeType:(id)sender {
|
||||
- (IBAction)securityQuestions:(id)sender {
|
||||
|
||||
MPSiteModel *site = self.selectedSite;
|
||||
NSArray *types = [site.algorithm allTypesStartingWith:MPSiteTypeGeneratedPIN];
|
||||
[self.passwordTypesMatrix renewRows:(NSInteger)[types count] columns:1];
|
||||
for (NSUInteger t = 0; t < [types count]; ++t) {
|
||||
MPSiteType type = (MPSiteType)[types[t] unsignedIntegerValue];
|
||||
NSString *title = [site.algorithm nameOfType:type];
|
||||
if (type & MPSiteTypeClassGenerated)
|
||||
title = [site.algorithm generatePasswordForSiteNamed:site.name ofType:type
|
||||
withCounter:site.counter
|
||||
usingKey:[MPMacAppDelegate get].key];
|
||||
|
||||
NSButtonCell *cell = [self.passwordTypesMatrix cellAtRow:(NSInteger)t column:0];
|
||||
cell.tag = type;
|
||||
cell.state = type == site.type? NSOnState: NSOffState;
|
||||
cell.title = title;
|
||||
}
|
||||
self.securityQuestionsBox.title = strf( @"Answer to security questions for %@:", site.name );
|
||||
|
||||
NSAlert *alert = [NSAlert new];
|
||||
[alert addButtonWithTitle:@"Save"];
|
||||
[alert addButtonWithTitle:@"Cancel"];
|
||||
[alert setMessageText:@"Change Password Type"];
|
||||
[alert setInformativeText:strf( @"Choose a new password type for: %@", site.name )];
|
||||
[alert setAccessoryView:self.passwordTypesBox];
|
||||
[alert addButtonWithTitle:@"Copy Answer"];
|
||||
[alert addButtonWithTitle:@"Close"];
|
||||
[alert setMessageText:@"Security Questions"];
|
||||
[alert setAccessoryView:self.securityQuestionsBox];
|
||||
[alert layout];
|
||||
[alert beginSheetModalForWindow:self.window modalDelegate:self
|
||||
didEndSelector:@selector( alertDidEnd:returnCode:contextInfo: ) contextInfo:MPAlertChangeType];
|
||||
[alert beginSheetModalForWindow:self.window completionHandler:^(NSModalResponse returnCode) {
|
||||
switch (returnCode) {
|
||||
case NSAlertFirstButtonReturn: {
|
||||
// "Copy Answer" button.
|
||||
[self copyContent:self.securityAnswerField.stringValue];
|
||||
[self.window close];
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
#pragma mark - Private
|
||||
@@ -467,7 +453,7 @@
|
||||
return YES;
|
||||
}
|
||||
if (commandSelector == @selector( cancel: ) || commandSelector == @selector( cancelOperation: )) {
|
||||
[self fadeOut];
|
||||
[self.window close];
|
||||
return YES;
|
||||
}
|
||||
|
||||
@@ -486,9 +472,8 @@
|
||||
}
|
||||
|
||||
// Performing action while content is available. Copy it.
|
||||
[self copyContent:selectedSite.content];
|
||||
|
||||
[self fadeOut];
|
||||
[self copyContent:self.shiftPressed? selectedSite.answer: selectedSite.content];
|
||||
[self.window close];
|
||||
|
||||
NSUserNotification *notification = [NSUserNotification new];
|
||||
notification.title = @"Password Copied";
|
||||
@@ -598,6 +583,7 @@
|
||||
CGFloat gradientOpacity = selectedOffset / siteScrollView.bounds.size.height;
|
||||
self.siteGradient.colors = @[
|
||||
(__bridge id)[NSColor whiteColor].CGColor,
|
||||
(__bridge id)[NSColor colorWithDeviceWhite:1 alpha:1 - (1 - gradientOpacity) * 4 / 5].CGColor,
|
||||
(__bridge id)[NSColor colorWithDeviceWhite:1 alpha:gradientOpacity].CGColor
|
||||
];
|
||||
|
||||
@@ -613,8 +599,21 @@
|
||||
[alert addButtonWithTitle:@"Cancel"];
|
||||
[alert setMessageText:@"Create site?"];
|
||||
[alert setInformativeText:strf( @"Do you want to create a new site named:\n\n%@", siteName )];
|
||||
[alert beginSheetModalForWindow:self.window modalDelegate:self
|
||||
didEndSelector:@selector( alertDidEnd:returnCode:contextInfo: ) contextInfo:MPAlertCreateSite];
|
||||
[alert beginSheetModalForWindow:self.window completionHandler:^(NSModalResponse returnCode) {
|
||||
switch (returnCode) {
|
||||
case NSAlertFirstButtonReturn: {
|
||||
// "Create" button.
|
||||
[[MPMacAppDelegate get] addSiteNamed:[self.siteField stringValue] completion:
|
||||
^(MPSiteEntity *site, NSManagedObjectContext *context) {
|
||||
if (site)
|
||||
PearlMainQueue( ^{ [self updateSites]; } );
|
||||
}];
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}];
|
||||
} );
|
||||
}
|
||||
|
||||
@@ -632,70 +631,4 @@
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)fadeIn {
|
||||
|
||||
prof_new( @"fadeIn" );
|
||||
if ([self.window isOnActiveSpace] && self.window.alphaValue > FLT_EPSILON) {
|
||||
prof_finish( @"showing" );
|
||||
return;
|
||||
}
|
||||
|
||||
CGDirectDisplayID displayID = [self.window.screen.deviceDescription[@"NSScreenNumber"] unsignedIntValue];
|
||||
CGImageRef capturedImage = CGDisplayCreateImage( displayID );
|
||||
if (!capturedImage || CGImageGetWidth( capturedImage ) <= 1) {
|
||||
if (capturedImage)
|
||||
CFRelease( capturedImage );
|
||||
wrn( @"Failed to capture screen image for display: %d", displayID );
|
||||
prof_finish( @"capture failed" );
|
||||
return;
|
||||
}
|
||||
prof_rewind( @"capture" );
|
||||
|
||||
NSImage *screenImage = [[NSImage alloc] initWithCGImage:capturedImage size:NSMakeSize(
|
||||
CGImageGetWidth( capturedImage ) / self.window.backingScaleFactor,
|
||||
CGImageGetHeight( capturedImage ) / self.window.backingScaleFactor )];
|
||||
prof_rewind( @"screenImage" );
|
||||
|
||||
NSImage *smallImage = [[NSImage alloc] initWithSize:NSMakeSize(
|
||||
CGImageGetWidth( capturedImage ) / 20,
|
||||
CGImageGetHeight( capturedImage ) / 20 )];
|
||||
prof_rewind( @"smallImage" );
|
||||
CFRelease( capturedImage );
|
||||
[smallImage lockFocus];
|
||||
[screenImage drawInRect:(NSRect){ .origin = CGPointZero, .size = smallImage.size, }
|
||||
fromRect:NSZeroRect
|
||||
operation:NSCompositeSourceOver
|
||||
fraction:1.0];
|
||||
[smallImage unlockFocus];
|
||||
prof_rewind( @"scale" );
|
||||
|
||||
self.blurView.image = smallImage;
|
||||
prof_rewind( @"blurView" );
|
||||
|
||||
[self.window setFrame:self.window.screen.frame display:YES];
|
||||
[NSAnimationContext currentContext].timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
|
||||
self.window.animator.alphaValue = 1.0;
|
||||
prof_finish( @"window setup" );
|
||||
}
|
||||
|
||||
- (void)fadeOut {
|
||||
|
||||
[self fadeOut:YES];
|
||||
}
|
||||
|
||||
- (void)fadeOut:(BOOL)hide {
|
||||
|
||||
if (![NSApp isActive] && self.window.alphaValue <= FLT_EPSILON)
|
||||
return;
|
||||
|
||||
[[NSAnimationContext currentContext] setCompletionHandler:^{
|
||||
[self close];
|
||||
|
||||
if (hide)
|
||||
[NSApp hide:self];
|
||||
}];
|
||||
[NSAnimationContext currentContext].timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
|
||||
[self.window animator].alphaValue = 0.0;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -31,6 +31,8 @@
|
||||
@property (nonatomic) NSString *typeName;
|
||||
@property (nonatomic) NSString *content;
|
||||
@property (nonatomic) NSString *displayedContent;
|
||||
@property (nonatomic) NSString *question;
|
||||
@property (nonatomic) NSString *answer;
|
||||
@property (nonatomic) NSString *loginName;
|
||||
@property (nonatomic) NSNumber *uses;
|
||||
@property (nonatomic) NSUInteger counter;
|
||||
|
||||
@@ -168,6 +168,15 @@
|
||||
[self updateContent];
|
||||
}
|
||||
|
||||
- (void)setQuestion:(NSString *)question {
|
||||
|
||||
if ([question isEqualToString:_question])
|
||||
return;
|
||||
_question = question;
|
||||
|
||||
[self updateContent];
|
||||
}
|
||||
|
||||
- (BOOL)outdated {
|
||||
|
||||
return self.algorithmVersion < MPAlgorithmVersionCurrent;
|
||||
@@ -194,13 +203,18 @@
|
||||
[MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
||||
[self updateContent:[MPSiteEntity existingObjectWithID:_entityOID inContext:context]];
|
||||
}];
|
||||
|
||||
else
|
||||
PearlNotMainQueue( ^{
|
||||
NSString *password = [self.algorithm generatePasswordForSiteNamed:self.name ofType:self.type withCounter:self.counter
|
||||
usingKey:[MPAppDelegate_Shared get].key];
|
||||
NSString *loginName = [self.algorithm generateLoginForSiteNamed:self.name usingKey:[MPAppDelegate_Shared get].key];
|
||||
[self updatePasswordWithResult:password];
|
||||
[self updateLoginNameWithResult:loginName];
|
||||
[self updatePasswordWithResult:
|
||||
[self.algorithm generatePasswordForSiteNamed:self.name ofType:self.type withCounter:self.counter
|
||||
usingKey:[MPAppDelegate_Shared get].key]];
|
||||
[self updateLoginNameWithResult:
|
||||
[self.algorithm generateLoginForSiteNamed:self.name
|
||||
usingKey:[MPAppDelegate_Shared get].key]];
|
||||
[self updateAnswerWithResult:
|
||||
[self.algorithm generateAnswerForSiteNamed:self.name onQuestion:self.question
|
||||
usingKey:[MPAppDelegate_Shared get].key]];
|
||||
} );
|
||||
}
|
||||
|
||||
@@ -212,6 +226,8 @@
|
||||
[entity resolveLoginUsingKey:[MPAppDelegate_Shared get].key result:^(NSString *result) {
|
||||
[self updateLoginNameWithResult:result];
|
||||
}];
|
||||
[self updateAnswerWithResult:[self.algorithm generateAnswerForSiteNamed:self.name onQuestion:self.question
|
||||
usingKey:[MPAppDelegate_Shared get].key]];
|
||||
}
|
||||
|
||||
- (void)updatePasswordWithResult:(NSString *)result {
|
||||
@@ -239,4 +255,11 @@
|
||||
} );
|
||||
}
|
||||
|
||||
- (void)updateAnswerWithResult:(NSString *)answer {
|
||||
|
||||
PearlMainQueue( ^{
|
||||
self.answer = answer;
|
||||
} );
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -78,7 +78,7 @@
|
||||
<key>NSMainNibFile</key>
|
||||
<string>MainMenu</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string>NSApplication</string>
|
||||
<string>MPMacApplication</string>
|
||||
<key>UTExportedTypeDeclarations</key>
|
||||
<array>
|
||||
<dict>
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
93D390C676DF52DA7E459F19 /* MPPasswordWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39D9D0061FF1159998F06 /* MPPasswordWindow.m */; };
|
||||
93D391E61DC23E128DA4446C /* NSView+Traversing.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D393EE88DE554BCCBC1C2D /* NSView+Traversing.h */; };
|
||||
93D392EC39DA43C46C692C12 /* NSDictionary+Indexing.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D393B97158D7BE9332EA53 /* NSDictionary+Indexing.h */; };
|
||||
93D393A1646430FAAC73E7FE /* MPMacApplication.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39F83DD151985F2C7345A /* MPMacApplication.m */; };
|
||||
93D394C4254EEB45FB335AFB /* MPSitesTableView.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39423D7BF4FD31FE6D27C /* MPSitesTableView.m */; };
|
||||
93D395E4830290EBB6E71F34 /* MPNoStateButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39538C4CEFF46DF379254 /* MPNoStateButton.m */; };
|
||||
93D395F08A087F8A24689347 /* NSArray+Indexing.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39067C0AFDC581794E2B8 /* NSArray+Indexing.m */; };
|
||||
@@ -273,6 +274,7 @@
|
||||
93D39368EF3CBFEF2AFCA15A /* MPInitialWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPInitialWindowController.h; sourceTree = "<group>"; };
|
||||
93D393B97158D7BE9332EA53 /* NSDictionary+Indexing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSDictionary+Indexing.h"; sourceTree = "<group>"; };
|
||||
93D393EE88DE554BCCBC1C2D /* NSView+Traversing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSView+Traversing.h"; sourceTree = "<group>"; };
|
||||
93D3942099C9AD0374B5777D /* MPMacApplication.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPMacApplication.h; sourceTree = "<group>"; };
|
||||
93D39423D7BF4FD31FE6D27C /* MPSitesTableView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPSitesTableView.m; sourceTree = "<group>"; };
|
||||
93D39538C4CEFF46DF379254 /* MPNoStateButton.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPNoStateButton.m; sourceTree = "<group>"; };
|
||||
93D396D04E57792A54D437AC /* NSArray+Indexing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSArray+Indexing.h"; sourceTree = "<group>"; };
|
||||
@@ -283,6 +285,7 @@
|
||||
93D39D3CB30874147D9A9E1B /* MPInitialWindowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPInitialWindowController.m; sourceTree = "<group>"; };
|
||||
93D39D9D0061FF1159998F06 /* MPPasswordWindow.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPPasswordWindow.m; sourceTree = "<group>"; };
|
||||
93D39E73BF5CBF8E5B005CD3 /* MPSiteModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPSiteModel.m; sourceTree = "<group>"; };
|
||||
93D39F83DD151985F2C7345A /* MPMacApplication.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPMacApplication.m; sourceTree = "<group>"; };
|
||||
DA0933C91747A56A00DE1CEF /* MPInitialWindow.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MPInitialWindow.xib; sourceTree = "<group>"; };
|
||||
DA0933CB1747AD2D00DE1CEF /* shot-laptop-leaning-iphone.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "shot-laptop-leaning-iphone.png"; sourceTree = "<group>"; };
|
||||
DA0933CF1747B91B00DE1CEF /* appstore.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = appstore.png; sourceTree = "<group>"; };
|
||||
@@ -1140,6 +1143,8 @@
|
||||
93D39E73BF5CBF8E5B005CD3 /* MPSiteModel.m */,
|
||||
93D39AC6360DDC16AEAA4119 /* MPSitesTableView.h */,
|
||||
93D39423D7BF4FD31FE6D27C /* MPSitesTableView.m */,
|
||||
93D39F83DD151985F2C7345A /* MPMacApplication.m */,
|
||||
93D3942099C9AD0374B5777D /* MPMacApplication.h */,
|
||||
);
|
||||
path = Mac;
|
||||
sourceTree = "<group>";
|
||||
@@ -2263,6 +2268,7 @@
|
||||
93D395E4830290EBB6E71F34 /* MPNoStateButton.m in Sources */,
|
||||
DA4DAE941A7D8117003E5423 /* MPAlgorithmV3.m in Sources */,
|
||||
DA4DAE951A7D8117003E5423 /* MPTypes.m in Sources */,
|
||||
93D393A1646430FAAC73E7FE /* MPMacApplication.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -2456,7 +2462,7 @@
|
||||
"$(inherit)",
|
||||
"\"$(SRCROOT)/../../../External\"/**",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.9;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.11;
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
OTHER_LDFLAGS = "-ObjC";
|
||||
@@ -2546,7 +2552,7 @@
|
||||
"$(inherit)",
|
||||
"\"$(SRCROOT)/../../../External\"/**",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.9;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.11;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
OTHER_LDFLAGS = "-ObjC";
|
||||
PRODUCT_NAME = "${TARGET_NAME}";
|
||||
@@ -2569,14 +2575,15 @@
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
/Users/lhunath/Documents/workspace/lyndir/MasterPassword/External/Mac,
|
||||
"\"$(SRCROOT)/../../../External/Mac\"",
|
||||
);
|
||||
GCC_PREFIX_HEADER = "MasterPassword-Prefix.pch";
|
||||
GCC_WARN_ABOUT_DEPRECATED_FUNCTIONS = NO;
|
||||
INFOPLIST_FILE = "MasterPassword-Info.plist";
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
|
||||
LIBRARY_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"/Users/lhunath/Documents/workspace/lyndir/MasterPassword/External/Pearl/Pearl-Crypto/lib",
|
||||
"\"$(SRCROOT)/../../../External/Pearl/Pearl-Crypto/lib\"",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.lyndir.lhunath.MasterPassword.Mac;
|
||||
PROVISIONING_PROFILE = "";
|
||||
@@ -2597,14 +2604,15 @@
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
/Users/lhunath/Documents/workspace/lyndir/MasterPassword/External/Mac,
|
||||
"\"$(SRCROOT)/../../../External/Mac\"",
|
||||
);
|
||||
GCC_PREFIX_HEADER = "MasterPassword-Prefix.pch";
|
||||
GCC_WARN_ABOUT_DEPRECATED_FUNCTIONS = NO;
|
||||
INFOPLIST_FILE = "MasterPassword-Info.plist";
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
|
||||
LIBRARY_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"/Users/lhunath/Documents/workspace/lyndir/MasterPassword/External/Pearl/Pearl-Crypto/lib",
|
||||
"\"$(SRCROOT)/../../../External/Pearl/Pearl-Crypto/lib\"",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.lyndir.lhunath.MasterPassword.Mac;
|
||||
PROVISIONING_PROFILE = "";
|
||||
@@ -2728,7 +2736,7 @@
|
||||
"$(inherit)",
|
||||
"\"$(SRCROOT)/../../../External\"/**",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.9;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.11;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
OTHER_LDFLAGS = "-ObjC";
|
||||
PRODUCT_NAME = "${TARGET_NAME}";
|
||||
@@ -2751,14 +2759,15 @@
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
/Users/lhunath/Documents/workspace/lyndir/MasterPassword/External/Mac,
|
||||
"\"$(SRCROOT)/../../../External/Mac\"",
|
||||
);
|
||||
GCC_PREFIX_HEADER = "MasterPassword-Prefix.pch";
|
||||
GCC_WARN_ABOUT_DEPRECATED_FUNCTIONS = NO;
|
||||
INFOPLIST_FILE = "MasterPassword-Info.plist";
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
|
||||
LIBRARY_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"/Users/lhunath/Documents/workspace/lyndir/MasterPassword/External/Pearl/Pearl-Crypto/lib",
|
||||
"\"$(SRCROOT)/../../../External/Pearl/Pearl-Crypto/lib\"",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.lyndir.lhunath.MasterPassword.Mac;
|
||||
PROVISIONING_PROFILE = "";
|
||||
@@ -2776,9 +2785,7 @@
|
||||
GCC_PREFIX_HEADER = "../Pearl/Pearl-Prefix.pch";
|
||||
LIBRARY_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"\"$(SRCROOT)/../../../External/Pearl/Pearl-Crypto\"",
|
||||
"\"$(SRCROOT)/../../../External/Pearl/Pearl-Crypto/lib\"",
|
||||
"/Users/lhunath/Documents/workspace/lyndir/MasterPassword/External/Pearl/Pearl-Crypto/lib",
|
||||
);
|
||||
OTHER_LDFLAGS = "-ObjC";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
@@ -2858,9 +2865,7 @@
|
||||
GCC_PREFIX_HEADER = "../Pearl/Pearl-Prefix.pch";
|
||||
LIBRARY_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"\"$(SRCROOT)/../../../External/Pearl/Pearl-Crypto\"",
|
||||
"\"$(SRCROOT)/../../../External/Pearl/Pearl-Crypto/lib\"",
|
||||
"/Users/lhunath/Documents/workspace/lyndir/MasterPassword/External/Pearl/Pearl-Crypto/lib",
|
||||
);
|
||||
OTHER_LDFLAGS = "-ObjC";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
@@ -2877,9 +2882,7 @@
|
||||
GCC_PREFIX_HEADER = "../Pearl/Pearl-Prefix.pch";
|
||||
LIBRARY_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"\"$(SRCROOT)/../../../External/Pearl/Pearl-Crypto\"",
|
||||
"\"$(SRCROOT)/../../../External/Pearl/Pearl-Crypto/lib\"",
|
||||
"/Users/lhunath/Documents/workspace/lyndir/MasterPassword/External/Pearl/Pearl-Crypto/lib",
|
||||
);
|
||||
OTHER_LDFLAGS = "-ObjC";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="5056" systemVersion="13E28" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="10116" systemVersion="15E65" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
|
||||
<dependencies>
|
||||
<deployment version="1070" defaultVersion="1080" identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="5056"/>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="10116"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<customObject id="-2" userLabel="File's Owner" customClass="NSApplication">
|
||||
@@ -11,7 +11,7 @@
|
||||
</connections>
|
||||
</customObject>
|
||||
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
|
||||
<customObject id="-3" userLabel="Application"/>
|
||||
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
|
||||
<menu title="AMainMenu" systemMenu="main" id="29"/>
|
||||
<customObject id="494" customClass="MPMacAppDelegate">
|
||||
<connections>
|
||||
@@ -22,6 +22,7 @@
|
||||
<outlet property="openAtLoginItem" destination="785" id="788"/>
|
||||
<outlet property="rememberPasswordItem" destination="744" id="750"/>
|
||||
<outlet property="savePasswordItem" destination="747" id="751"/>
|
||||
<outlet property="showFullScreenItem" destination="Rvo-MK-GgF" id="NYi-85-Yuc"/>
|
||||
<outlet property="showItem" destination="719" id="783"/>
|
||||
<outlet property="statusMenu" destination="716" id="731"/>
|
||||
<outlet property="usersItem" destination="755" id="762"/>
|
||||
@@ -71,6 +72,23 @@
|
||||
</attributedString>
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
</menuItem>
|
||||
<menuItem title="Show Full Screen" id="Rvo-MK-GgF">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="togglePreference:" target="494" id="wfI-yQ-Kla"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Claim the entire screen while looking up passwords." enabled="NO" id="3k1-4V-IZ7">
|
||||
<attributedString key="attributedTitle">
|
||||
<fragment content="Always open Master Password at start-up.">
|
||||
<attributes>
|
||||
<font key="NSFont" size="12" name="Helvetica"/>
|
||||
<paragraphStyle key="NSParagraphStyle" alignment="natural" lineBreakMode="wordWrapping" baseWritingDirection="natural"/>
|
||||
</attributes>
|
||||
</fragment>
|
||||
</attributedString>
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
</menuItem>
|
||||
<menuItem title="Hide Passwords" id="9G7-17-PzY">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
|
||||
@@ -178,7 +178,7 @@
|
||||
else if ([cell isKindOfClass:[MPSendAnswersCell class]]) {
|
||||
NSString *body;
|
||||
if (!_multiple) {
|
||||
NSObject *answer = [site.algorithm resolveAnswerForSite:site usingKey:[MPiOSAppDelegate get].key];
|
||||
NSObject *answer = [site resolveSiteAnswerUsingKey:[MPiOSAppDelegate get].key];
|
||||
body = strf( @"Master Password generated the following security answer for your site: %@\n\n"
|
||||
@"%@\n"
|
||||
@"\n\nYou should use this as the answer to each security question the site asks you.\n"
|
||||
@@ -188,7 +188,7 @@
|
||||
NSMutableString *bodyBuilder = [NSMutableString string];
|
||||
[bodyBuilder appendFormat:@"Master Password generated the following security answers for your site: %@\n\n", site.name];
|
||||
for (MPSiteQuestionEntity *question in site.questions) {
|
||||
NSObject *answer = [site.algorithm resolveAnswerForQuestion:question usingKey:[MPiOSAppDelegate get].key];
|
||||
NSObject *answer = [question resolveQuestionAnswerUsingKey:[MPiOSAppDelegate get].key];
|
||||
[bodyBuilder appendFormat:@"For question: '%@', use answer: %@\n", question.keyword, answer];
|
||||
}
|
||||
[bodyBuilder appendFormat:@"\n\nUse the answer for the matching security question.\n"
|
||||
@@ -241,7 +241,7 @@
|
||||
|
||||
self.titleLabel.text = strl( @"Answer for %@:", site.name );
|
||||
self.answerField.text = @"...";
|
||||
[site.algorithm resolveAnswerForSite:site usingKey:[MPiOSAppDelegate get].key result:^(NSString *result) {
|
||||
[site resolveSiteAnswerUsingKey:[MPiOSAppDelegate get].key result:^(NSString *result) {
|
||||
PearlMainQueue( ^{
|
||||
self.answerField.text = result;
|
||||
} );
|
||||
@@ -330,7 +330,7 @@
|
||||
PearlMainQueue( ^{
|
||||
self.answerField.text = @"...";
|
||||
} );
|
||||
[site.algorithm resolveAnswerForQuestion:question usingKey:[MPiOSAppDelegate get].key result:^(NSString *result) {
|
||||
[question resolveQuestionAnswerUsingKey:[MPiOSAppDelegate get].key result:^(NSString *result) {
|
||||
PearlMainQueue( ^{
|
||||
self.questionField.text = keyword;
|
||||
self.answerField.text = result;
|
||||
|
||||
@@ -4225,7 +4225,6 @@
|
||||
LIBRARY_SEARCH_PATHS = (
|
||||
"\"$(SRCROOT)/../../../External/Pearl/Pearl-Crypto/lib\"",
|
||||
"$(inherited)",
|
||||
"/Users/lhunath/Documents/workspace/lyndir/MasterPassword/External/Pearl/Pearl-Crypto/lib",
|
||||
);
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
@@ -4325,7 +4324,6 @@
|
||||
LIBRARY_SEARCH_PATHS = (
|
||||
"\"$(SRCROOT)/../../../External/Pearl/Pearl-Crypto/lib\"",
|
||||
"$(inherited)",
|
||||
"/Users/lhunath/Documents/workspace/lyndir/MasterPassword/External/Pearl/Pearl-Crypto/lib",
|
||||
);
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
@@ -4341,7 +4339,6 @@
|
||||
LIBRARY_SEARCH_PATHS = (
|
||||
"\"$(SRCROOT)/../../../External/Pearl/Pearl-Crypto/lib\"",
|
||||
"$(inherited)",
|
||||
"/Users/lhunath/Documents/workspace/lyndir/MasterPassword/External/Pearl/Pearl-Crypto/lib",
|
||||
);
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
|
||||
|
Before Width: | Height: | Size: 4.8 KiB After Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 585 KiB |
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 8.6 KiB After Width: | Height: | Size: 8.7 KiB |
|
Before Width: | Height: | Size: 84 KiB After Width: | Height: | Size: 69 KiB |
|
Before Width: | Height: | Size: 294 KiB After Width: | Height: | Size: 211 KiB |
|
Before Width: | Height: | Size: 1.0 MiB After Width: | Height: | Size: 764 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 79 KiB After Width: | Height: | Size: 71 KiB |
|
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 47 KiB |
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 7.7 KiB |
|
Before Width: | Height: | Size: 113 KiB After Width: | Height: | Size: 90 KiB |
|
Before Width: | Height: | Size: 400 KiB After Width: | Height: | Size: 277 KiB |
|
Before Width: | Height: | Size: 1.4 MiB After Width: | Height: | Size: 1.0 MiB |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 6.6 KiB After Width: | Height: | Size: 7.2 KiB |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 1.6 MiB |
|
Before Width: | Height: | Size: 6.0 KiB |
|
Before Width: | Height: | Size: 906 B After Width: | Height: | Size: 887 B |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 965 B |
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 118 KiB After Width: | Height: | Size: 106 KiB |
|
Before Width: | Height: | Size: 254 KiB After Width: | Height: | Size: 211 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 105 KiB After Width: | Height: | Size: 94 KiB |
|
Before Width: | Height: | Size: 224 KiB After Width: | Height: | Size: 192 KiB |
|
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 133 KiB After Width: | Height: | Size: 128 KiB |
|
Before Width: | Height: | Size: 341 KiB After Width: | Height: | Size: 341 KiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 96 KiB After Width: | Height: | Size: 94 KiB |
|
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 4.4 KiB |