2
0

Compare commits

...

43 Commits

Author SHA1 Message Date
Maarten Billemont
d1104e4028 Need icon 1x icon for iPad. 2020-04-30 09:32:17 -04:00
Maarten Billemont
e9f2a25c9c Project icons update. 2020-04-28 13:49:34 -04:00
Maarten Billemont
171a3f0978 Icon update. 2020-04-28 12:24:41 -04:00
Maarten Billemont
8cfb9a83c5 mpjson support on iOS. 2020-04-27 16:33:10 -04:00
Maarten Billemont
5717375e75 Improve language. 2020-04-27 16:33:01 -04:00
Maarten Billemont
cc2dca3bd0 Report missing support when trying to parse unknown format. 2020-04-27 16:32:30 -04:00
Maarten Billemont
7575924d80 Bump site links to macOS app. 2020-04-26 00:39:08 -04:00
Maarten Billemont
8bedcedfaf Enable support for internal actions from URLs. 2020-04-25 17:13:48 -04:00
Maarten Billemont
10b205c541 Open URLs in external browser. 2020-04-25 11:44:27 -04:00
Maarten Billemont
774f183ac0 Improved web support & trigger notification URLs. 2020-04-25 10:44:59 -04:00
Maarten Billemont
2279aacb5a Change the user's default type from the Mac UI. 2020-04-21 13:27:06 -04:00
Maarten Billemont
1bd654621c Fix persistence of sendInfoDecided.
Don't log it as an event, event logging is probably disabled at this
point anyway.
2020-04-21 13:26:12 -04:00
Maarten Billemont
c4f60e325d Harmonize consent flow on both platforms. 2020-04-21 11:33:31 -04:00
Maarten Billemont
d4de3afb72 Show internal reason for why import fails. 2020-04-20 17:09:38 -04:00
Maarten Billemont
694b5ea227 Make marshal error messages owned by the file.
Error message lifecycle was limited to the static mpw_str buffer, which
is far too limited and also dangerous.  Own the message by the
MPMarshalFile object, freed in mpw_marshal_file_free.
2020-04-20 17:07:35 -04:00
Maarten Billemont
66dd78797b We use bounds-checked interfaces, so need ext1. 2020-04-20 17:05:37 -04:00
Maarten Billemont
61d1660560 Consent tweaks.
Don't conditional Countly events on sendInfo, it already turns Countly
on/off.

Keep Sentry enabled for longer while turning on/off for sendInfo to
cache more errors.

Use sendInfoDecided event only for initial sendInfo prompt, can use
Countly's censent tools to view overall opt-in/opt-outs.
2020-04-19 17:40:28 -04:00
Maarten Billemont
c3568e4744 Enable minor & build numbers > 9. 2020-04-18 22:13:36 -04:00
Maarten Billemont
0c921d4318 Update fonts to stay consistent with system UIs. 2020-04-18 21:50:06 -04:00
Maarten Billemont
0178efaaf7 Ask for consent to sendInfo. 2020-04-18 21:48:55 -04:00
Maarten Billemont
14f919584b Project update. 2020-04-16 17:46:39 -04:00
Maarten Billemont
16f6c3c593 Update preference menu item state. 2020-04-16 17:13:23 -04:00
Maarten Billemont
63ca2ae83e Can lower deployment target to 10.10 2020-04-16 17:13:08 -04:00
Maarten Billemont
1c3ea3826f Move identicon and toID to mpw native.
Clean out all unused Java MPAlgorithm stuff.

Fix master password entries stuck in memory.
2020-04-15 19:09:02 -04:00
Maarten Billemont
ff9596aef0 Update log sink API. 2020-04-15 19:08:11 -04:00
Maarten Billemont
b79ed1ca0b Fix JNI native symbol names after refactor. 2020-04-14 20:07:21 -04:00
Maarten Billemont
9a362f13a3 Use new mpw log sink logging mechanism for JNI logging. 2020-04-14 19:13:49 -04:00
Maarten Billemont
11d6660e5a Sinks report success now & fallback sink triggers on failure.
We now trigger the fallback sink (currently, mpw_log_sink_file) if no
sinks successfully handled the log message.
2020-04-14 19:12:28 -04:00
Maarten Billemont
62e1563fa6 Ensure b64Cursor chars are unsigned when used as index. 2020-04-14 19:11:08 -04:00
Maarten Billemont
9b8ff7ad0c Kill debug code. 2020-04-13 21:33:28 -04:00
Maarten Billemont
f1fc07cf9e FaceID privacy message. 2020-04-13 18:56:57 -04:00
Maarten Billemont
00ac788f4f Consistent flow for enabling notifications. 2020-04-13 18:12:41 -04:00
Maarten Billemont
514c383310 Obsolete sources. 2020-04-12 22:38:40 -04:00
Maarten Billemont
9a3bcd1c6f Fix bad override of forKeyedSubscript: 2020-04-12 21:39:13 -04:00
Maarten Billemont
d30d469663 Sites list scroll & keyboard fixes. 2020-04-12 19:16:13 -04:00
Maarten Billemont
b428ee0003 Honour animated property better.
Also: don't need to force a layout if not in an animation block.
2020-04-12 19:14:25 -04:00
Maarten Billemont
f80ffd078b Move dependencies to Podfile. 2020-04-12 19:12:45 -04:00
Maarten Billemont
7f1a28ffa7 Fixes and updates to iOS.
Move dependencies into Podfile where possible.

IASK doesn't wrap properly; just removed it.

Fixes to safe area and content insets.
2020-04-11 12:13:48 -04:00
Maarten Billemont
8eeba2e005 Don't crash if decrypt fails. 2020-04-09 21:46:09 -04:00
Maarten Billemont
fd6cbaa9a5 Ignore backup files. 2020-04-09 21:33:05 -04:00
Maarten Billemont
1651e9ad4a Automatic configuration cleanup. 2020-04-09 21:04:35 -04:00
Maarten Billemont
02c1e2af46 straf relies on a nil terminator. 2020-04-09 21:03:59 -04:00
Maarten Billemont
b275286b2d Consent and notification permissions on the main thread. 2020-04-09 21:03:11 -04:00
89 changed files with 1573 additions and 2083 deletions

1
.gitignore vendored
View File

@@ -1,6 +1,7 @@
# OS-Specific junk.
.DS_Store
Thumbs.db
*~
# IntelliJ
.idea

15
.gitmodules vendored
View File

@@ -1,21 +1,9 @@
[submodule "External/Pearl"]
path = platform-darwin/External/Pearl
url = git://github.com/Lyndir/Pearl.git
[submodule "External/InAppSettingsKit"]
path = platform-darwin/External/InAppSettingsKit
url = git://github.com/lhunath/InAppSettingsKit.git
[submodule "External/KCOrderedAccessorFix"]
path = platform-darwin/External/KCOrderedAccessorFix
url = https://github.com/lhunath/KCOrderedAccessorFix.git
[submodule "External/AttributedMarkdown"]
path = platform-darwin/External/AttributedMarkdown
url = https://github.com/dreamwieber/AttributedMarkdown.git
[submodule "External/uicolor-utilities"]
path = platform-darwin/External/uicolor-utilities
url = git://github.com/lhunath/uicolor-utilities.git
[submodule "External/jrswizzle"]
path = platform-darwin/External/jrswizzle
url = git://github.com/jonmarimba/jrswizzle.git
[submodule "MasterPassword/Web/js/mpw-js"]
path = platform-independent/web/js/mpw-js
url = https://github.com/tmthrgd/mpw-js.git
@@ -27,7 +15,6 @@
url = https://github.com/json-c/json-c.git
[submodule "public/site"]
path = public/site
url = https://github.com/Lyndir/MasterPassword.git
url = https://gitlab.com/MasterPassword/MasterPassword.git
branch = gh-pages
shallow = true
update = none

File diff suppressed because it is too large Load Diff

View File

@@ -10,17 +10,13 @@
7352E972184B980C428B66A2 /* Pods_MasterPassword_macOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1E9477508F419F29008C7553 /* Pods_MasterPassword_macOS.framework */; };
93D390C676DF52DA7E459F19 /* MPSitesWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39D9D0061FF1159998F06 /* MPSitesWindow.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 */; };
93D39784E725A34D1EE3FB3B /* MPInitialWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39D3CB30874147D9A9E1B /* MPInitialWindowController.m */; };
93D3987F6D9046DBEE4D8364 /* NSView+Traversing.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D392870DF659AFC1870521 /* NSView+Traversing.m */; };
93D398D1F5D8CD5A22AF6929 /* MPGradientView.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39934FD8D5BFABA46F41C /* MPGradientView.m */; };
93D39C34FE35830EF5BE1D2A /* NSArray+Indexing.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D396D04E57792A54D437AC /* NSArray+Indexing.h */; };
93D39C5789EFA607CF788082 /* MPSiteModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39E73BF5CBF8E5B005CD3 /* MPSiteModel.m */; };
93D39E281E3658B30550CB55 /* NSDictionary+Indexing.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39AA1EE2E1E7B81372240 /* NSDictionary+Indexing.m */; };
93D39F833DEC1C89B2F795AC /* MPSitesWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39A57A7823DE98A0FF83C /* MPSitesWindowController.m */; };
DA0933CC1747AD2D00DE1CEF /* shot-laptop-leaning-iphone.png in Resources */ = {isa = PBXBuildFile; fileRef = DA0933CB1747AD2D00DE1CEF /* shot-laptop-leaning-iphone.png */; };
DA0933D01747B91B00DE1CEF /* appstore.png in Resources */ = {isa = PBXBuildFile; fileRef = DA0933CF1747B91B00DE1CEF /* appstore.png */; };
@@ -87,7 +83,6 @@
DA3B8453190FC86F00246EEA /* NSManagedObject+Pearl.h in Headers */ = {isa = PBXBuildFile; fileRef = DA3B8451190FC86F00246EEA /* NSManagedObject+Pearl.h */; };
DA3B8456190FC89700246EEA /* MPFixable.m in Sources */ = {isa = PBXBuildFile; fileRef = DA3B8454190FC89700246EEA /* MPFixable.m */; };
DA3BCFCD19BD09E0006B2681 /* SourceCodePro-Regular.otf in Resources */ = {isa = PBXBuildFile; fileRef = DA3BCFCC19BD09E0006B2681 /* SourceCodePro-Regular.otf */; };
DA4DA1D91564471A00F6F596 /* libjrswizzle.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DAC6326C148680650075AEA5 /* libjrswizzle.a */; };
DA4DAE941A7D8117003E5423 /* MPAlgorithmV3.m in Sources */ = {isa = PBXBuildFile; fileRef = DA4DAE921A7D8117003E5423 /* MPAlgorithmV3.m */; };
DA4DAE951A7D8117003E5423 /* MPTypes.m in Sources */ = {isa = PBXBuildFile; fileRef = DA4DAE931A7D8117003E5423 /* MPTypes.m */; };
DA5180CA19FF2F9200A587E9 /* MPAlgorithmV2.m in Sources */ = {isa = PBXBuildFile; fileRef = DA5180C719FF2F9200A587E9 /* MPAlgorithmV2.m */; };
@@ -119,6 +114,7 @@
DA6774431A474A3B004F356A /* mpw-algorithm.c in Sources */ = {isa = PBXBuildFile; fileRef = DA6773BB1A4746AF004F356A /* mpw-algorithm.c */; };
DA6774451A474A3B004F356A /* mpw-types.c in Sources */ = {isa = PBXBuildFile; fileRef = DA6773C21A4746AF004F356A /* mpw-types.c */; };
DA6774461A474A3B004F356A /* mpw-util.c in Sources */ = {isa = PBXBuildFile; fileRef = DA6773C51A4746AF004F356A /* mpw-util.c */; };
DA72E2272444081200676D4F /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DABB981515100B4000B05417 /* SystemConfiguration.framework */; };
DA7471A31F2B71AE005F3468 /* mpw-marshal-util.c in Sources */ = {isa = PBXBuildFile; fileRef = DA7471A01F2B71A9005F3468 /* mpw-marshal-util.c */; };
DA7471A61F2B71B9005F3468 /* mpw-marshal-util.c in Sources */ = {isa = PBXBuildFile; fileRef = DA7471A01F2B71A9005F3468 /* mpw-marshal-util.c */; };
DA89D4EC1A51EABD00AC64D7 /* Pearl-Cocoa.h in Headers */ = {isa = PBXBuildFile; fileRef = DA89D4EA1A51EABD00AC64D7 /* Pearl-Cocoa.h */; };
@@ -126,7 +122,6 @@
DA8ED895192906920099B726 /* PearlTween.m in Sources */ = {isa = PBXBuildFile; fileRef = DA8ED891192906920099B726 /* PearlTween.m */; };
DA8ED896192906920099B726 /* PearlTween.h in Headers */ = {isa = PBXBuildFile; fileRef = DA8ED892192906920099B726 /* PearlTween.h */; };
DA8ED897192906920099B726 /* map-macro.h in Headers */ = {isa = PBXBuildFile; fileRef = DA8ED894192906920099B726 /* map-macro.h */; };
DA9261511BE1A86700369DE5 /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA92614F1BE1A86700369DE5 /* SystemConfiguration.framework */; };
DA9261541BE1A88900369DE5 /* libc++.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = DA9261531BE1A88900369DE5 /* libc++.tbd */; };
DA9261561BE1A89600369DE5 /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = DA9261551BE1A89600369DE5 /* libz.tbd */; };
DAA449D51EEC4B6B00E7BDD5 /* mpw-marshal.c in Sources */ = {isa = PBXBuildFile; fileRef = DAA449D31EEC4B6B00E7BDD5 /* mpw-marshal.c */; };
@@ -136,8 +131,6 @@
DAADCC4819FAFFAD00987B1D /* NSPersistentStore+PearlMigration.h in Headers */ = {isa = PBXBuildFile; fileRef = DAADCC3F19FAFFAD00987B1D /* NSPersistentStore+PearlMigration.h */; };
DAADCC4919FAFFAD00987B1D /* NSPersistentStore+PearlMigration.m in Sources */ = {isa = PBXBuildFile; fileRef = DAADCC4019FAFFAD00987B1D /* NSPersistentStore+PearlMigration.m */; };
DAADCC4B19FB000C00987B1D /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DAEBC45214F6364500987BF6 /* QuartzCore.framework */; };
DAADCC6919FB007F00987B1D /* NSManagedObjectModel+KCOrderedAccessorFix.m in Sources */ = {isa = PBXBuildFile; fileRef = DAADCC6719FB007F00987B1D /* NSManagedObjectModel+KCOrderedAccessorFix.m */; };
DAADCC6A19FB00B500987B1D /* libKCOrderedAccessorFix.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DAADCC5019FB006500987B1D /* libKCOrderedAccessorFix.a */; };
DAAF16661F5CA3240013B8AE /* mpw-cli-util.c in Sources */ = {isa = PBXBuildFile; fileRef = DAAF16631F5897EA0013B8AE /* mpw-cli-util.c */; };
DAB07CA01F7725D400CC6D43 /* aes.c in Sources */ = {isa = PBXBuildFile; fileRef = DAB07C9E1F7725D400CC6D43 /* aes.c */; };
DAB07CA31F77261400CC6D43 /* aes.c in Sources */ = {isa = PBXBuildFile; fileRef = DAB07C9E1F7725D400CC6D43 /* aes.c */; };
@@ -166,7 +159,6 @@
DABD5EF9242BBCE200200755 /* icon_512x512.png in Resources */ = {isa = PBXBuildFile; fileRef = DAD0C5FE19FD6034009CB08D /* icon_512x512.png */; };
DABD5EFA242BBCE200200755 /* icon_512x512@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAD0C5FF19FD6034009CB08D /* icon_512x512@2x.png */; };
DABD5F00242BC32D00200755 /* MPSecrets.m in Sources */ = {isa = PBXBuildFile; fileRef = DABD5EFE242BC32D00200755 /* MPSecrets.m */; };
DAC6326D148680650075AEA5 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA5BFA4A147E415C00F98B1E /* Foundation.framework */; };
DAC77CAE148291A600BCF976 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA5BFA4A147E415C00F98B1E /* Foundation.framework */; };
DACA26FE1705DF81002C6C22 /* logo-bare.png in Resources */ = {isa = PBXBuildFile; fileRef = DACA241C1705DF7D002C6C22 /* logo-bare.png */; };
DACA27121705DF81002C6C22 /* avatar-13@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DACA24321705DF7D002C6C22 /* avatar-13@2x.png */; };
@@ -211,8 +203,6 @@
DACA29671705DF81002C6C22 /* SourceCodePro-ExtraLight.otf in Resources */ = {isa = PBXBuildFile; fileRef = DACA268E1705DF81002C6C22 /* SourceCodePro-ExtraLight.otf */; };
DACA29681705DF81002C6C22 /* SourceCodePro-Black.otf in Resources */ = {isa = PBXBuildFile; fileRef = DACA268F1705DF81002C6C22 /* SourceCodePro-Black.otf */; };
DACA29741705E1A8002C6C22 /* dictionary.lst in Resources */ = {isa = PBXBuildFile; fileRef = DACA29721705E1A8002C6C22 /* dictionary.lst */; };
DACA298D1705E2BD002C6C22 /* JRSwizzle.h in Headers */ = {isa = PBXBuildFile; fileRef = DACA29771705E2BD002C6C22 /* JRSwizzle.h */; };
DACA299A1705E2BD002C6C22 /* JRSwizzle.m in Sources */ = {isa = PBXBuildFile; fileRef = DACA298C1705E2BD002C6C22 /* JRSwizzle.m */; };
DAD9B5F01762CAA4001835F9 /* ServiceManagement.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DAD9B5EF1762CAA4001835F9 /* ServiceManagement.framework */; };
DAD9B5F11762CAB9001835F9 /* MasterPassword-Mac-LoginHelper.app in Copy LoginHelper */ = {isa = PBXBuildFile; fileRef = DAD9B5E6176299BA001835F9 /* MasterPassword-Mac-LoginHelper.app */; };
DAEB942E18B47FB3000490CC /* MPInitialWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = DA0933C91747A56A00DE1CEF /* MPInitialWindow.xib */; };
@@ -308,13 +298,6 @@
remoteGlobalIDString = DAD9B5C0176299B9001835F9;
remoteInfo = "MasterPassword-Mac-LoginHelper";
};
DAC63285148681200075AEA5 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = DA5BFA3B147E415C00F98B1E /* Project object */;
proxyType = 1;
remoteGlobalIDString = DAC6326B148680650075AEA5;
remoteInfo = jrswizzle;
};
DAD9B5E5176299BA001835F9 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = DAD9B5E1176299B9001835F9 /* MasterPassword-Mac-LoginHelper.xcodeproj */;
@@ -352,15 +335,6 @@
);
runOnlyForDeploymentPostprocessing = 1;
};
DAADCC4E19FB006500987B1D /* CopyFiles */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "include/$(PRODUCT_NAME)";
dstSubfolderSpec = 16;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
DAD9B5EE1762CA3A001835F9 /* Copy LoginHelper */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
@@ -380,23 +354,19 @@
1E9ED9547411486CE0898611 /* Pods-MasterPassword-macOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MasterPassword-macOS.debug.xcconfig"; path = "Target Support Files/Pods-MasterPassword-macOS/Pods-MasterPassword-macOS.debug.xcconfig"; sourceTree = "<group>"; };
28DD0F6129ECC496C8DFE6F8 /* Pods-MasterPassword-macOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MasterPassword-macOS.release.xcconfig"; path = "Target Support Files/Pods-MasterPassword-macOS/Pods-MasterPassword-macOS.release.xcconfig"; sourceTree = "<group>"; };
7791961245EBC3023523FDCD /* Pods-MasterPassword.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MasterPassword.release.xcconfig"; path = "Target Support Files/Pods-MasterPassword/Pods-MasterPassword.release.xcconfig"; sourceTree = "<group>"; };
93D39067C0AFDC581794E2B8 /* NSArray+Indexing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSArray+Indexing.m"; sourceTree = "<group>"; };
93D39240B5143E01F0B75E96 /* MPSiteModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPSiteModel.h; sourceTree = "<group>"; };
93D392870DF659AFC1870521 /* NSView+Traversing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSView+Traversing.m"; sourceTree = "<group>"; };
93D392A4F3DE0BD758B9B056 /* MPNoStateButton.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPNoStateButton.h; sourceTree = "<group>"; };
93D392C3918763B3B72CF366 /* MPSitesWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPSitesWindowController.h; sourceTree = "<group>"; };
93D392FD65A1DF0E8B2A45C6 /* MPGradientView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPGradientView.h; sourceTree = "<group>"; };
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>"; };
93D3977484534E99F9BA579D /* MPSitesWindow.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPSitesWindow.h; sourceTree = "<group>"; };
93D39934FD8D5BFABA46F41C /* MPGradientView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPGradientView.m; sourceTree = "<group>"; };
93D39A57A7823DE98A0FF83C /* MPSitesWindowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPSitesWindowController.m; sourceTree = "<group>"; };
93D39AA1EE2E1E7B81372240 /* NSDictionary+Indexing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSDictionary+Indexing.m"; sourceTree = "<group>"; };
93D39AC6360DDC16AEAA4119 /* MPSitesTableView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPSitesTableView.h; sourceTree = "<group>"; };
93D39D3CB30874147D9A9E1B /* MPInitialWindowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPInitialWindowController.m; sourceTree = "<group>"; };
93D39D9D0061FF1159998F06 /* MPSitesWindow.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPSitesWindow.m; sourceTree = "<group>"; };
@@ -937,7 +907,6 @@
DA8ED891192906920099B726 /* PearlTween.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PearlTween.m; sourceTree = "<group>"; };
DA8ED892192906920099B726 /* PearlTween.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PearlTween.h; sourceTree = "<group>"; };
DA8ED894192906920099B726 /* map-macro.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "map-macro.h"; sourceTree = "<group>"; };
DA92614F1BE1A86700369DE5 /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; };
DA9261531BE1A88900369DE5 /* libc++.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = "libc++.tbd"; path = "usr/lib/libc++.tbd"; sourceTree = SDKROOT; };
DA9261551BE1A89600369DE5 /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; };
DAA449D31EEC4B6B00E7BDD5 /* mpw-marshal.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "mpw-marshal.c"; sourceTree = "<group>"; };
@@ -1032,9 +1001,6 @@
DAADCC3E19FAFFAD00987B1D /* NSNotificationCenter+PearlEasyCleanup.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSNotificationCenter+PearlEasyCleanup.h"; sourceTree = "<group>"; };
DAADCC3F19FAFFAD00987B1D /* NSPersistentStore+PearlMigration.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSPersistentStore+PearlMigration.h"; sourceTree = "<group>"; };
DAADCC4019FAFFAD00987B1D /* NSPersistentStore+PearlMigration.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSPersistentStore+PearlMigration.m"; sourceTree = "<group>"; };
DAADCC5019FB006500987B1D /* libKCOrderedAccessorFix.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libKCOrderedAccessorFix.a; sourceTree = BUILT_PRODUCTS_DIR; };
DAADCC6619FB007F00987B1D /* NSManagedObjectModel+KCOrderedAccessorFix.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSManagedObjectModel+KCOrderedAccessorFix.h"; sourceTree = "<group>"; };
DAADCC6719FB007F00987B1D /* NSManagedObjectModel+KCOrderedAccessorFix.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSManagedObjectModel+KCOrderedAccessorFix.m"; sourceTree = "<group>"; };
DAAF16631F5897EA0013B8AE /* mpw-cli-util.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "mpw-cli-util.c"; sourceTree = "<group>"; };
DAAF16641F5897EA0013B8AE /* mpw-cli-util.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "mpw-cli-util.h"; sourceTree = "<group>"; };
DAB07C9E1F7725D400CC6D43 /* aes.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = aes.c; sourceTree = "<group>"; };
@@ -1050,7 +1016,6 @@
DABB981515100B4000B05417 /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; };
DABD5EFE242BC32D00200755 /* MPSecrets.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPSecrets.m; sourceTree = "<group>"; };
DABD5EFF242BC32D00200755 /* MPSecrets.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPSecrets.h; sourceTree = "<group>"; };
DAC6326C148680650075AEA5 /* libjrswizzle.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libjrswizzle.a; sourceTree = BUILT_PRODUCTS_DIR; };
DAC632871486D95D0075AEA5 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; };
DAC77CAD148291A600BCF976 /* libPearl.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libPearl.a; sourceTree = BUILT_PRODUCTS_DIR; };
DACA241C1705DF7D002C6C22 /* logo-bare.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "logo-bare.png"; sourceTree = "<group>"; };
@@ -1096,8 +1061,6 @@
DACA268E1705DF81002C6C22 /* SourceCodePro-ExtraLight.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "SourceCodePro-ExtraLight.otf"; sourceTree = "<group>"; };
DACA268F1705DF81002C6C22 /* SourceCodePro-Black.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "SourceCodePro-Black.otf"; sourceTree = "<group>"; };
DACA29721705E1A8002C6C22 /* dictionary.lst */ = {isa = PBXFileReference; fileEncoding = 1; lastKnownFileType = text; path = dictionary.lst; sourceTree = "<group>"; };
DACA29771705E2BD002C6C22 /* JRSwizzle.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JRSwizzle.h; sourceTree = "<group>"; };
DACA298C1705E2BD002C6C22 /* JRSwizzle.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JRSwizzle.m; sourceTree = "<group>"; };
DACBFCDB1C59B22E007EF90F /* NSMutableSet+Pearl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSMutableSet+Pearl.h"; sourceTree = "<group>"; };
DACBFCDC1C59B22E007EF90F /* NSMutableSet+Pearl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSMutableSet+Pearl.m"; sourceTree = "<group>"; };
DAD0C5F619FD6034009CB08D /* icon_128x128.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = icon_128x128.png; sourceTree = "<group>"; };
@@ -1185,14 +1148,13 @@
DAB7AE5B1F3D750B00C856B1 /* libjson-c.a in Frameworks */,
DA9261561BE1A89600369DE5 /* libz.tbd in Frameworks */,
DA9261541BE1A88900369DE5 /* libc++.tbd in Frameworks */,
DAADCC6A19FB00B500987B1D /* libKCOrderedAccessorFix.a in Frameworks */,
DA9261511BE1A86700369DE5 /* SystemConfiguration.framework in Frameworks */,
DA250925195148E200AC23F1 /* QuartzCore.framework in Frameworks */,
DAD9B5F01762CAA4001835F9 /* ServiceManagement.framework in Frameworks */,
DA16B341170661DB000A0EAB /* Carbon.framework in Frameworks */,
DA16B342170661E0000A0EAB /* Security.framework in Frameworks */,
DA16B345170661F2000A0EAB /* libPearl.a in Frameworks */,
DA16B344170661EE000A0EAB /* Cocoa.framework in Frameworks */,
DA72E2272444081200676D4F /* SystemConfiguration.framework in Frameworks */,
7352E972184B980C428B66A2 /* Pods_MasterPassword_macOS.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -1205,27 +1167,11 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
DAADCC4D19FB006500987B1D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
DAC63269148680650075AEA5 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
DAC6326D148680650075AEA5 /* Foundation.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
DAC77CAA148291A600BCF976 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
DAADCC4B19FB000C00987B1D /* QuartzCore.framework in Frameworks */,
DA4DA1D91564471A00F6F596 /* libjrswizzle.a in Frameworks */,
DAC77CAE148291A600BCF976 /* Foundation.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -1272,14 +1218,6 @@
name = "Other Frameworks";
sourceTree = "<group>";
};
DA3B8449190FC5A900246EEA /* Mac */ = {
isa = PBXGroup;
children = (
DA92614F1BE1A86700369DE5 /* SystemConfiguration.framework */,
);
path = Mac;
sourceTree = "<group>";
};
DA5BFA39147E415C00F98B1E = {
isa = PBXGroup;
children = (
@@ -1298,8 +1236,6 @@
children = (
DA5BFA44147E415C00F98B1E /* Master Password.app */,
DAC77CAD148291A600BCF976 /* libPearl.a */,
DAC6326C148680650075AEA5 /* libjrswizzle.a */,
DAADCC5019FB006500987B1D /* libKCOrderedAccessorFix.a */,
DA67743B1A474A03004F356A /* mpw-test */,
DA1C7AB61F1A8F24009A3551 /* mpw-cli */,
DA1C7AD61F1A8FD8009A3551 /* mpw-bench */,
@@ -2025,15 +1961,6 @@
path = lib;
sourceTree = "<group>";
};
DAADCC6819FB007F00987B1D /* KCOrderedAccessorFix */ = {
isa = PBXGroup;
children = (
DAADCC6619FB007F00987B1D /* NSManagedObjectModel+KCOrderedAccessorFix.h */,
DAADCC6719FB007F00987B1D /* NSManagedObjectModel+KCOrderedAccessorFix.m */,
);
path = KCOrderedAccessorFix;
sourceTree = "<group>";
};
DAC77CAF148291A600BCF976 /* Pearl */ = {
isa = PBXGroup;
children = (
@@ -2050,9 +1977,6 @@
children = (
DAA7BC0D20C4C1B500101DC7 /* libjson-c-macos */,
DAA7BBC920C4C17300101DC7 /* libsodium-macos */,
DACA29751705E2BD002C6C22 /* jrswizzle */,
DAADCC6819FB007F00987B1D /* KCOrderedAccessorFix */,
DA3B8449190FC5A900246EEA /* Mac */,
DAC77CAF148291A600BCF976 /* Pearl */,
);
path = External;
@@ -2152,15 +2076,6 @@
path = Data;
sourceTree = "<group>";
};
DACA29751705E2BD002C6C22 /* jrswizzle */ = {
isa = PBXGroup;
children = (
DACA29771705E2BD002C6C22 /* JRSwizzle.h */,
DACA298C1705E2BD002C6C22 /* JRSwizzle.m */,
);
path = jrswizzle;
sourceTree = "<group>";
};
DAD0C5F419FD6034009CB08D /* mac */ = {
isa = PBXGroup;
children = (
@@ -2205,14 +2120,10 @@
DAB4FBDD202FF93D002768FB /* PearlHangDetector.h */,
DAB4FBDE202FF93D002768FB /* PearlHangDetector.m */,
DA8ED893192906920099B726 /* include */,
93D396D04E57792A54D437AC /* NSArray+Indexing.h */,
93D39067C0AFDC581794E2B8 /* NSArray+Indexing.m */,
DA2CA4EA18D323D3007798F8 /* NSArray+Pearl.h */,
DA2CA4E918D323D3007798F8 /* NSArray+Pearl.m */,
DAFE4A63150399FF003ABA93 /* NSDateFormatter+RFC3339.h */,
DAFE4A63150399FF003ABA91 /* NSDateFormatter+RFC3339.m */,
93D393B97158D7BE9332EA53 /* NSDictionary+Indexing.h */,
93D39AA1EE2E1E7B81372240 /* NSDictionary+Indexing.m */,
DA2CA4E818D323D3007798F8 /* NSError+PearlFullDescription.h */,
DA2CA4E718D323D3007798F8 /* NSError+PearlFullDescription.m */,
DA3B8451190FC86F00246EEA /* NSManagedObject+Pearl.h */,
@@ -2292,14 +2203,6 @@
/* End PBXGroup section */
/* Begin PBXHeadersBuildPhase section */
DAC6326A148680650075AEA5 /* Headers */ = {
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
DACA298D1705E2BD002C6C22 /* JRSwizzle.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
DAC77CAB148291A600BCF976 /* Headers */ = {
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
@@ -2335,8 +2238,6 @@
DAADCC4719FAFFAD00987B1D /* NSNotificationCenter+PearlEasyCleanup.h in Headers */,
DAFE4A63150399FF003ABA88 /* NSObject+PearlKVO.h in Headers */,
DAFE4A63150399FF003ABA94 /* NSDateFormatter+RFC3339.h in Headers */,
93D39C34FE35830EF5BE1D2A /* NSArray+Indexing.h in Headers */,
93D392EC39DA43C46C692C12 /* NSDictionary+Indexing.h in Headers */,
DA3509FE15F101A500C14A8E /* PearlQueue.h in Headers */,
DA2C3D681BD9665B001137B3 /* PearlProfiler.h in Headers */,
DA2CA4EE18D323D3007798F8 /* NSError+PearlFullDescription.h in Headers */,
@@ -2460,40 +2361,6 @@
productReference = DA67743B1A474A03004F356A /* mpw-test */;
productType = "com.apple.product-type.tool";
};
DAADCC4F19FB006500987B1D /* KCOrderedAccessorFix */ = {
isa = PBXNativeTarget;
buildConfigurationList = DAADCC5E19FB006500987B1D /* Build configuration list for PBXNativeTarget "KCOrderedAccessorFix" */;
buildPhases = (
DAADCC4C19FB006500987B1D /* Sources */,
DAADCC4D19FB006500987B1D /* Frameworks */,
DAADCC4E19FB006500987B1D /* CopyFiles */,
);
buildRules = (
);
dependencies = (
);
name = KCOrderedAccessorFix;
productName = KCOrderedAccessorFix;
productReference = DAADCC5019FB006500987B1D /* libKCOrderedAccessorFix.a */;
productType = "com.apple.product-type.library.static";
};
DAC6326B148680650075AEA5 /* jrswizzle */ = {
isa = PBXNativeTarget;
buildConfigurationList = DAC63274148680650075AEA5 /* Build configuration list for PBXNativeTarget "jrswizzle" */;
buildPhases = (
DAC63268148680650075AEA5 /* Sources */,
DAC63269148680650075AEA5 /* Frameworks */,
DAC6326A148680650075AEA5 /* Headers */,
);
buildRules = (
);
dependencies = (
);
name = jrswizzle;
productName = jrswizzle;
productReference = DAC6326C148680650075AEA5 /* libjrswizzle.a */;
productType = "com.apple.product-type.library.static";
};
DAC77CAC148291A600BCF976 /* Pearl */ = {
isa = PBXNativeTarget;
buildConfigurationList = DAC77CB7148291A600BCF976 /* Build configuration list for PBXNativeTarget "Pearl" */;
@@ -2505,7 +2372,6 @@
buildRules = (
);
dependencies = (
DAC63286148681200075AEA5 /* PBXTargetDependency */,
);
name = Pearl;
productName = Pearl;
@@ -2549,18 +2415,11 @@
CreatedOnToolsVersion = 6.1.1;
DevelopmentTeam = HL3Q45LX9N;
};
DAADCC4F19FB006500987B1D = {
CreatedOnToolsVersion = 6.0.1;
DevelopmentTeam = HL3Q45LX9N;
};
DAB7AE421F3D466D00C856B1 = {
CreatedOnToolsVersion = 8.3.3;
DevelopmentTeam = HL3Q45LX9N;
ProvisioningStyle = Automatic;
};
DAC6326B148680650075AEA5 = {
DevelopmentTeam = HL3Q45LX9N;
};
DAC77CAC148291A600BCF976 = {
DevelopmentTeam = HL3Q45LX9N;
};
@@ -2587,8 +2446,6 @@
targets = (
DA5BFA43147E415C00F98B1E /* MasterPassword-macOS */,
DAC77CAC148291A600BCF976 /* Pearl */,
DAC6326B148680650075AEA5 /* jrswizzle */,
DAADCC4F19FB006500987B1D /* KCOrderedAccessorFix */,
DA67743A1A474A03004F356A /* mpw-test */,
DA1C7AC61F1A8FD8009A3551 /* mpw-bench */,
DA1C7AA61F1A8F24009A3551 /* mpw-cli */,
@@ -2716,15 +2573,12 @@
buildActionMask = 2147483647;
files = (
);
inputPaths = (
"${PODS_ROOT}/Target Support Files/Pods-MasterPassword-macOS/Pods-MasterPassword-macOS-frameworks.sh",
"${BUILT_PRODUCTS_DIR}/Countly-macOS/Countly.framework",
"${BUILT_PRODUCTS_DIR}/Sentry-macOS/Sentry.framework",
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-MasterPassword-macOS/Pods-MasterPassword-macOS-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Embed Pods Frameworks";
outputPaths = (
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Countly.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Sentry.framework",
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-MasterPassword-macOS/Pods-MasterPassword-macOS-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
@@ -2891,22 +2745,6 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
DAADCC4C19FB006500987B1D /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
DAADCC6919FB007F00987B1D /* NSManagedObjectModel+KCOrderedAccessorFix.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
DAC63268148680650075AEA5 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
DACA299A1705E2BD002C6C22 /* JRSwizzle.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
DAC77CA9148291A600BCF976 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
@@ -2942,8 +2780,6 @@
DAB4FBEA202FF951002768FB /* NSMutableSet+Pearl.m in Sources */,
DAFE4A63150399FF003ABA86 /* NSObject+PearlKVO.m in Sources */,
DAFE4A63150399FF003ABA92 /* NSDateFormatter+RFC3339.m in Sources */,
93D395F08A087F8A24689347 /* NSArray+Indexing.m in Sources */,
93D39E281E3658B30550CB55 /* NSDictionary+Indexing.m in Sources */,
DA3509FF15F101A500C14A8E /* PearlQueue.m in Sources */,
93D3987F6D9046DBEE4D8364 /* NSView+Traversing.m in Sources */,
);
@@ -2987,11 +2823,6 @@
name = "MasterPassword-Mac-LoginHelper";
targetProxy = DABFA071176E3FDF00E83589 /* PBXContainerItemProxy */;
};
DAC63286148681200075AEA5 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = DAC6326B148680650075AEA5 /* jrswizzle */;
targetProxy = DAC63285148681200075AEA5 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */
@@ -3370,8 +3201,6 @@
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CODE_SIGN_ENTITLEMENTS = Source/Mac/MasterPassword.entitlements;
CODE_SIGN_IDENTITY = "Mac Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
COMBINE_HIDPI_IMAGES = YES;
ENABLE_HARDENED_RUNTIME = YES;
GCC_PREFIX_HEADER = "Source/MasterPassword-Prefix.pch";
@@ -3391,6 +3220,7 @@
"\"$(PROJECT_DIR)/../lib/libsodium/build-macos~/out/lib\"",
"\"$(PROJECT_DIR)/../lib/libjson-c/build-macos~/out/lib\"",
);
MACOSX_DEPLOYMENT_TARGET = 10.10;
OTHER_CFLAGS = (
"-DMPW_SODIUM=1",
"-DMPW_JSON=1",
@@ -3410,8 +3240,6 @@
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CODE_SIGN_ENTITLEMENTS = Source/Mac/MasterPassword.entitlements;
CODE_SIGN_IDENTITY = "Mac Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
COMBINE_HIDPI_IMAGES = YES;
ENABLE_HARDENED_RUNTIME = YES;
GCC_PREFIX_HEADER = "Source/MasterPassword-Prefix.pch";
@@ -3431,6 +3259,7 @@
"\"$(PROJECT_DIR)/../lib/libsodium/build-macos~/out/lib\"",
"\"$(PROJECT_DIR)/../lib/libjson-c/build-macos~/out/lib\"",
);
MACOSX_DEPLOYMENT_TARGET = 10.10;
OTHER_CFLAGS = (
"-DMPW_SODIUM=1",
"-DMPW_JSON=1",
@@ -3490,22 +3319,6 @@
};
name = Release;
};
DAADCC5F19FB006500987B1D /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_OBJC_ARC = YES;
COMBINE_HIDPI_IMAGES = YES;
};
name = Debug;
};
DAADCC6019FB006500987B1D /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_OBJC_ARC = YES;
COMBINE_HIDPI_IMAGES = YES;
};
name = Release;
};
DAB7AE441F3D466D00C856B1 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
@@ -3547,34 +3360,6 @@
};
name = Release;
};
DAC63275148680650075AEA5 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_OBJC_ARC = NO;
CLANG_ENABLE_OBJC_WEAK = YES;
COMBINE_HIDPI_IMAGES = YES;
DSTROOT = /tmp/jrswizzle.dst;
GCC_WARN_INHIBIT_ALL_WARNINGS = YES;
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
};
name = Debug;
};
DAC63276148680650075AEA5 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_OBJC_ARC = NO;
CLANG_ENABLE_OBJC_WEAK = YES;
COMBINE_HIDPI_IMAGES = YES;
DSTROOT = /tmp/jrswizzle.dst;
GCC_WARN_INHIBIT_ALL_WARNINGS = YES;
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
};
name = Release;
};
DAC77CB5148291A600BCF976 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
@@ -3658,15 +3443,6 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Debug;
};
DAADCC5E19FB006500987B1D /* Build configuration list for PBXNativeTarget "KCOrderedAccessorFix" */ = {
isa = XCConfigurationList;
buildConfigurations = (
DAADCC5F19FB006500987B1D /* Debug */,
DAADCC6019FB006500987B1D /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Debug;
};
DAB7AE431F3D466D00C856B1 /* Build configuration list for PBXLegacyTarget "libjson-c-macos" */ = {
isa = XCConfigurationList;
buildConfigurations = (
@@ -3676,15 +3452,6 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Debug;
};
DAC63274148680650075AEA5 /* Build configuration list for PBXNativeTarget "jrswizzle" */ = {
isa = XCConfigurationList;
buildConfigurations = (
DAC63275148680650075AEA5 /* Debug */,
DAC63276148680650075AEA5 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Debug;
};
DAC77CB7148291A600BCF976 /* Build configuration list for PBXNativeTarget "Pearl" */ = {
isa = XCConfigurationList;
buildConfigurations = (

View File

@@ -11,6 +11,9 @@ target 'MasterPassword-iOS' do
pod 'Sentry', :git => 'https://github.com/getsentry/sentry-cocoa.git'
pod 'Countly'
pod 'UIColor-Utilities'
pod 'KCOrderedAccessorFix'
pod 'JRSwizzle'
end
target 'MasterPassword-macOS' do
@@ -22,4 +25,6 @@ target 'MasterPassword-macOS' do
pod 'Sentry', :git => 'https://github.com/getsentry/sentry-cocoa.git'
pod 'Countly'
pod 'KCOrderedAccessorFix'
pod 'JRSwizzle'
end

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 843 B

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.7 KiB

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.2 KiB

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 236 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 879 KiB

After

Width:  |  Height:  |  Size: 879 KiB

View File

@@ -34,7 +34,7 @@ icons=(
# 57@1@iphone@:Icon.png
# 29@1@iphone@:Icon-Small.png
# iPad
# 76@1@ipad@7.0:Icon-76.png
76@1@ipad@7.0:Icon-76.png
152@2@ipad@7.0:Icon-76@2x.png
167@2@ipad@9.0:Icon-83@2x.png
20@1@ipad@7.0:Icon-Small-20.png

View File

@@ -46,9 +46,10 @@ esac
description=$(git describe --always --dirty --long --match "*-$platform*")
version=${description%-g*} build=${version##*-} version=${version%-$build}
version=${version//-$platform/} version=${version//-/.} commit=${description##*-g}
IFS=. read major minor build <<< "$version"
addPlistWithKey GITDescription string "$description"
setPlistWithKey CFBundleVersion "$(hr "${version%%.*}" 14).${version#*.}"
setPlistWithKey CFBundleVersion "$(hr "$major" 14)$(printf '%02d' "$minor")$(printf '%02d' "$build")"
setPlistWithKey CFBundleShortVersionString "$version"
setSettingWithTitle "Build" "$commit"

View File

@@ -81,8 +81,7 @@ static NSOperationQueue *_mpwQueue = nil;
}
NSOperation *operation = [NSBlockOperation blockOperationWithBlock:operationBlock];
if ([operation respondsToSelector:@selector( qualityOfService )])
operation.qualityOfService = NSQualityOfServiceUserInitiated;
operation.qualityOfService = NSQualityOfServiceUserInitiated;
[_mpwQueue addOperations:@[ operation ] waitUntilFinished:YES];
}

View File

@@ -172,21 +172,13 @@ PearlAssociatedObjectProperty( NSMutableArray*, ProductObservers, productObserve
#endif
}
- (void)requestDidFinish:(SKRequest *)request {
dbg( @"StoreKit request (%@) finished.", request );
}
#pragma mark - SKPaymentTransactionObserver
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions {
for (SKPaymentTransaction *transaction in transactions) {
dbg( @"transaction updated: %@ -> %d", transaction.payment.productIdentifier, (int)(transaction.transactionState) );
switch (transaction.transactionState) {
case SKPaymentTransactionStatePurchased: {
inf( @"Purchased: %@", transaction.payment.productIdentifier );
NSMutableDictionary *attributes = [NSMutableDictionary new];
if ([transaction.payment.productIdentifier isEqualToString:MPProductFuel]) {
@@ -205,22 +197,19 @@ PearlAssociatedObjectProperty( NSMutableArray*, ProductObservers, productObserve
forKey:transaction.payment.productIdentifier];
[queue finishTransaction:transaction];
if ([[MPConfig get].sendInfo boolValue]) {
SKProduct *product = self.products[transaction.payment.productIdentifier];
[attributes addEntriesFromDictionary:@{
@"id": product.productIdentifier,
@"name": product.localizedTitle,
@"price": product.price.description,
@"currency": [product.priceLocale objectForKey:NSLocaleCurrencyCode],
@"state" : @"success",
@"quantity": @(transaction.payment.quantity).description,
}];
[Countly.sharedInstance recordEvent:@"purchase" segmentation:attributes];
}
SKProduct *product = self.products[transaction.payment.productIdentifier];
[attributes addEntriesFromDictionary:@{
@"id": product.productIdentifier,
@"name": product.localizedTitle,
@"price": product.price.description,
@"currency": [product.priceLocale objectForKey:NSLocaleCurrencyCode],
@"state" : @"success",
@"quantity": @(transaction.payment.quantity).description,
}];
[Countly.sharedInstance recordEvent:@"purchase" segmentation:attributes];
break;
}
case SKPaymentTransactionStateRestored: {
inf( @"Restored: %@", transaction.payment.productIdentifier );
[[NSUserDefaults standardUserDefaults] setObject:transaction.transactionIdentifier
forKey:transaction.payment.productIdentifier];
[queue finishTransaction:transaction];
@@ -233,18 +222,16 @@ PearlAssociatedObjectProperty( NSMutableArray*, ProductObservers, productObserve
MPError( transaction.error, @"Transaction failed: %@.", transaction.payment.productIdentifier );
[queue finishTransaction:transaction];
if ([[MPConfig get].sendInfo boolValue]) {
SKProduct *product = self.products[transaction.payment.productIdentifier];
[Countly.sharedInstance recordEvent:@"purchase" segmentation:@{
@"id": product.productIdentifier,
@"name": product.localizedTitle,
@"price": product.price.description,
@"currency": [product.priceLocale objectForKey:NSLocaleCurrencyCode],
@"state" : @"failed",
@"quantity": @(transaction.payment.quantity).description,
@"reason" : [transaction.error localizedFailureReason]?: [transaction.error localizedDescription],
}];
}
SKProduct *product = self.products[transaction.payment.productIdentifier];
[Countly.sharedInstance recordEvent:@"purchase" segmentation:@{
@"id": product.productIdentifier,
@"name": product.localizedTitle,
@"price": product.price.description,
@"currency": [product.priceLocale objectForKey:NSLocaleCurrencyCode],
@"state" : @"failed",
@"quantity": @(transaction.payment.quantity).description,
@"reason" : [transaction.error localizedFailureReason]?: [transaction.error localizedDescription],
}];
break;
}
}

View File

@@ -173,13 +173,11 @@
else
dbg( @"Automatic login failed for user: %@", user.userID );
if ([[MPConfig get].sendInfo boolValue]) {
[Countly.sharedInstance recordEvent:@"login" segmentation:@{
@"method" : password? @"Password": @"Automatic",
@"state" : @"failed",
@"algorithm": @(user.algorithm.version).description,
}];
}
[Countly.sharedInstance recordEvent:@"login" segmentation:@{
@"method" : password? @"Password": @"Automatic",
@"state" : @"failed",
@"algorithm": @(user.algorithm.version).description,
}];
return NO;
}
@@ -203,15 +201,13 @@
}
@try {
if ([[MPConfig get].sendInfo boolValue]) {
[Countly.sharedInstance userLoggedIn:user.userID];
[Countly.sharedInstance userLoggedIn:user.userID];
[Countly.sharedInstance recordEvent:@"login" segmentation:@{
@"method" : password? @"Password": @"Automatic",
@"state" : @"success",
@"algorithm": @(user.algorithm.version).description,
}];
}
[Countly.sharedInstance recordEvent:@"login" segmentation:@{
@"method" : password? @"Password": @"Automatic",
@"state" : @"success",
@"algorithm": @(user.algorithm.version).description,
}];
}
@catch (id exception) {
err( @"While setting username: %@", exception );

View File

@@ -32,7 +32,7 @@
@end
MPLogSink mpw_log_sink_pearl;
void mpw_log_sink_pearl(const MPLogEvent *record) {
bool mpw_log_sink_pearl(const MPLogEvent *record) {
PearlLogLevel level = PearlLogLevelInfo;
switch (record->level) {
@@ -58,6 +58,7 @@ void mpw_log_sink_pearl(const MPLogEvent *record) {
[[PearlLogger get] inFile:[@(record->file) lastPathComponent] atLine:record->line fromFunction:@(record->function)
withLevel:level text:@(record->message)];
return YES;
}
@implementation MPAppDelegate_Shared
@@ -112,18 +113,17 @@ static MPAppDelegate_Shared *instance;
- (void)setActiveUser:(MPUserEntity *)activeUser {
NSManagedObjectID *activeUserOID = activeUser.permanentObjectID;
if ([self.activeUserOID isEqual:activeUserOID])
if (self.activeUserOID == activeUserOID || [self.activeUserOID isEqual:activeUserOID])
return;
if (self.key)
self.key = nil;
if ([[MPConfig get].sendInfo boolValue])
[Countly.sharedInstance userLoggedOut];
[[NSNotificationCenter defaultCenter] postNotificationName:MPSignedOutNotification object:self];
[Countly.sharedInstance userLoggedOut];
self.activeUserOID = activeUserOID;
[[NSNotificationCenter defaultCenter] postNotificationName:MPSignedOutNotification object:self];
}
- (void)handleCoordinatorError:(NSError *)error {

View File

@@ -39,8 +39,9 @@
askImportPassword:(NSString *( ^ )(NSString *userName))importPassword
askUserPassword:(NSString *( ^ )(NSString *userName))userPassword
result:(void ( ^ )(NSError *error))resultBlock;
- (void)exportSitesRevealPasswords:(BOOL)revealPasswords
askExportPassword:(NSString *( ^ )(NSString *userName))askImportPassword
result:(void ( ^ )(NSString *exportedUser, NSError *error))resultBlock;
- (NSString *)exportSitesFor:(MPUserEntity *)user
revealPasswords:(BOOL)revealPasswords
askExportPassword:(NSString *( ^ )(NSString *userName))askExportPassword
error:(__autoreleasing NSError **)error;
@end

View File

@@ -563,50 +563,46 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
// Read metadata for the import file.
MPMarshalledFile *file = mpw_marshal_read( NULL, importData.UTF8String );
if (!file)
return MPError( ([NSError errorWithDomain:MPErrorDomain code:MPErrorMarshalCode userInfo:@{
@"type" : @(MPMarshalErrorInternal),
NSLocalizedDescriptionKey: @"Could not process Master Password import data.",
}]), @"While importing sites." );
if (file->error.type != MPMarshalSuccess) {
MPMarshalErrorType type = file->error.type;
mpw_marshal_file_free( &file );
return MPError( ([NSError errorWithDomain:MPErrorDomain code:MPErrorMarshalCode userInfo:@{
@"type" : @(type),
NSLocalizedDescriptionKey: @"Could not parse Master Password import data.",
}]), @"While importing sites." );
}
if (file->info->format == MPMarshalFormatNone) {
mpw_marshal_file_free( &file );
return MPError( ([NSError errorWithDomain:MPErrorDomain code:MPErrorMarshalCode userInfo:@{
@"type" : @(MPMarshalErrorFormat),
NSLocalizedDescriptionKey: @"This is not a Master Password import file.",
}]), @"While importing sites." );
}
// Get master password for import file.
MPKey *importKey;
NSString *importMasterPassword;
do {
importMasterPassword = askImportPassword( @(file->info->fullName) );
if (!importMasterPassword) {
inf( @"Import cancelled." );
mpw_marshal_file_free( &file );
return MPError( ([NSError errorWithDomain:NSCocoaErrorDomain code:NSUserCancelledError userInfo:nil]), @"" );
MPMarshalledUser *importUser = nil;
@try {
if (!file)
return MPError( ([NSError errorWithDomain:MPErrorDomain code:MPErrorMarshalCode userInfo:@{
@"type" : @(MPMarshalErrorInternal),
NSLocalizedDescriptionKey: @"Could not process Master Password import data.",
}]), @"While importing sites." );
if (file->error.type != MPMarshalSuccess) {
return MPError( ([NSError errorWithDomain:MPErrorDomain code:MPErrorMarshalCode userInfo:@{
@"type" : @(file->error.type),
NSLocalizedDescriptionKey: strf( @"Could not parse Master Password import data:\n%@", @(file->error.message) ),
}]), @"While importing sites." );
}
if (file->info->format == MPMarshalFormatNone) {
return MPError( ([NSError errorWithDomain:MPErrorDomain code:MPErrorMarshalCode userInfo:@{
@"type" : @(MPMarshalErrorFormat),
NSLocalizedDescriptionKey: @"This is not a Master Password import file.",
}]), @"While importing sites." );
}
importKey = [[MPKey alloc] initForFullName:@(file->info->fullName) withMasterPassword:importMasterPassword];
} while ([[[importKey keyIDForAlgorithm:MPAlgorithmForVersion( file->info->algorithm )] encodeHex]
caseInsensitiveCompare:@(file->info->keyID)] != NSOrderedSame);
// Get master password for import file.
MPKey *importKey;
NSString *importMasterPassword;
do {
importMasterPassword = askImportPassword( @(file->info->fullName) );
if (!importMasterPassword) {
inf( @"Import cancelled." );
return MPError( ([NSError errorWithDomain:NSCocoaErrorDomain code:NSUserCancelledError userInfo:nil]), @"" );
}
// Parse import data.
MPMarshalledUser *importUser = mpw_marshal_auth( file, mpw_masterKeyProvider_str( importMasterPassword.UTF8String ) );
importKey = [[MPKey alloc] initForFullName:@(file->info->fullName) withMasterPassword:importMasterPassword];
} while ([[[importKey keyIDForAlgorithm:MPAlgorithmForVersion( file->info->algorithm )] encodeHex]
caseInsensitiveCompare:@(file->info->keyID)] != NSOrderedSame);
@try {
// Parse import data.
importUser = mpw_marshal_auth( file, mpw_masterKeyProvider_str( importMasterPassword.UTF8String ) );
if (!importUser || file->error.type != MPMarshalSuccess)
return MPError( ([NSError errorWithDomain:MPErrorDomain code:MPErrorMarshalCode userInfo:@{
@"type" : @(file->error.type),
NSLocalizedDescriptionKey: @(file->error.message),
NSLocalizedDescriptionKey: strf( @"Could not authenticate Master Password import:\n%@", @(file->error.message) ),
}]), @"While importing sites." );
// Find an existing user to update.
@@ -643,7 +639,6 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
user.avatar = importUser->avatar;
user.defaultType = importUser->defaultType;
user.lastUsed = [NSDate dateWithTimeIntervalSince1970:MAX( user.lastUsed.timeIntervalSince1970, importUser->lastUsed )];
dbg( @"Importing user: %@", [user debugDescription] );
// Update or create sites.
for (size_t s = 0; s < importUser->sites_count; ++s) {
@@ -657,10 +652,8 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
return MPError( error, @"Lookup of existing sites failed for site: %@, user: %@", @(importSite->siteName), user.userID );
if ([existingSites count])
// Update existing site.
for (MPSiteEntity *site in existingSites) {
for (MPSiteEntity *site in existingSites)
[self importSite:importSite protectedByKey:importKey intoSite:site usingKey:userKey];
dbg( @"Updated site: %@", [site debugDescription] );
}
else {
// Create new site.
id<MPAlgorithm> algorithm = MPAlgorithmForVersion( importSite->algorithm );
@@ -673,7 +666,6 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
site.user = user;
[self importSite:importSite protectedByKey:importKey intoSite:site usingKey:userKey];
dbg( @"Created site: %@", [site debugDescription] );
}
}
@@ -712,16 +704,17 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
site.lastUsed = [NSDate dateWithTimeIntervalSince1970:importSite->lastUsed];
}
- (void)exportSitesRevealPasswords:(BOOL)revealPasswords
askExportPassword:(NSString *( ^ )(NSString *userName))askImportPassword
result:(void ( ^ )(NSString *exportedUser, NSError *error))resultBlock {
[MPAppDelegate_Shared managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
MPUserEntity *user = [self activeUserInContext:context];
- (NSString *)exportSitesFor:(MPUserEntity *)user
revealPasswords:(BOOL)revealPasswords
askExportPassword:(NSString *( ^ )(NSString *userName))askExportPassword
error:(__autoreleasing NSError **)error {
MPMarshalledUser *exportUser = NULL;
MPMarshalledFile *exportFile = NULL;
@try {
inf( @"Exporting sites, %@, for user: %@", revealPasswords? @"revealing passwords": @"omitting passwords", user.userID );
MPMarshalledUser *exportUser = mpw_marshal_user( user.name.UTF8String,
mpw_masterKeyProvider_str( askImportPassword( user.name ).UTF8String ), user.algorithm.version );
exportUser = mpw_marshal_user( user.name.UTF8String,
mpw_masterKeyProvider_str( askExportPassword( user.name ).UTF8String ), user.algorithm.version );
exportUser->redacted = !revealPasswords;
exportUser->avatar = (unsigned int)user.avatar;
exportUser->keyID = mpw_strdup( [user.keyID encodeHex].UTF8String );
@@ -745,22 +738,26 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
mpw_marshal_question( exportSite, siteQuestion.keyword.UTF8String );
}
MPMarshalledFile *exportFile = NULL;
const char *export = mpw_marshal_write( MPMarshalFormatDefault, &exportFile, exportUser );
NSString *exportedUser = nil;
if (export && exportFile && exportFile->error.type == MPMarshalSuccess)
exportedUser = [NSString stringWithCString:export encoding:NSUTF8StringEncoding];
mpw_free_string( &export );
resultBlock( exportedUser, exportFile && exportFile->error.type == MPMarshalSuccess? nil:
[NSError errorWithDomain:MPErrorDomain code:MPErrorMarshalCode userInfo:@{
@"type" : @(exportFile? exportFile->error.type: MPMarshalErrorInternal),
NSLocalizedDescriptionKey: @(exportFile? exportFile->error.message: nil),
}] );
if (error)
*error = exportFile && exportFile->error.type == MPMarshalSuccess? nil:
[NSError errorWithDomain:MPErrorDomain code:MPErrorMarshalCode userInfo:@{
@"type" : @(exportFile? exportFile->error.type: MPMarshalErrorInternal),
NSLocalizedDescriptionKey: @(exportFile? exportFile->error.message: nil),
}];
return exportedUser;
}
@finally {
mpw_marshal_file_free( &exportFile );
mpw_marshal_user_free( &exportUser );
mpw_masterKeyProvider_free();
}];
}
}
@end

View File

@@ -21,11 +21,13 @@
@interface MPConfig : PearlConfig
@property(nonatomic, retain) NSNumber *sendInfo;
@property(nonatomic, retain) NSNumber *sendInfoDecided;
@property(nonatomic, retain) NSNumber *notificationsDecided;
@property(nonatomic, retain) NSNumber *rememberLogin;
@property(nonatomic, retain) NSNumber *hidePasswords;
@property(nonatomic, strong) NSNumber *siteAttacker;
@property(nonatomic, retain) NSNumber *checkInconsistency;
@property(nonatomic, strong) NSNumber *siteAttacker;
@end

View File

@@ -21,7 +21,7 @@
@implementation MPConfig
@dynamic sendInfo, rememberLogin, checkInconsistency, hidePasswords, siteAttacker;
@dynamic sendInfo, sendInfoDecided, notificationsDecided, rememberLogin, hidePasswords, siteAttacker, checkInconsistency;
- (id)init {
@@ -29,13 +29,16 @@
return nil;
[self.defaults registerDefaults:@{
NSStringFromSelector( @selector( askForReviews ) ) : @YES,
NSStringFromSelector( @selector( sendInfo ) ) : @NO,
NSStringFromSelector( @selector( sendInfoDecided ) ) : @NO,
NSStringFromSelector( @selector( notificationsDecided ) ): @NO,
NSStringFromSelector( @selector( sendInfo ) ) : @YES,
NSStringFromSelector( @selector( rememberLogin ) ) : @NO,
NSStringFromSelector( @selector( hidePasswords ) ) : @NO,
NSStringFromSelector( @selector( checkInconsistency ) ): @NO,
NSStringFromSelector( @selector( siteAttacker ) ) : @(MPAttacker1),
NSStringFromSelector( @selector( rememberLogin ) ) : @NO,
NSStringFromSelector( @selector( hidePasswords ) ) : @NO,
NSStringFromSelector( @selector( siteAttacker ) ) : @(MPAttacker1),
NSStringFromSelector( @selector( checkInconsistency ) ) : @NO,
NSStringFromSelector( @selector( askForReviews ) ) : @YES,
}];
self.delegate = [MPAppDelegate_Shared get];

View File

@@ -20,11 +20,19 @@
#import "base64.h"
// printf <secret> | openssl enc -[ed] -aes-128-cbc -a -A -K <appSecret> -iv 0
NSString *appSecret = @"";
NSString *appSalt = @"";
NSString *sentryDSN = @"";
NSString *countlyKey = @"";
NSString *countlySalt = @"";
#if TARGET_OS_IOS
NSString *appSecret = @"946a6b12e6e6e004cc35bad1ea11478c";
NSString *appSalt = @"uBcsbZeTB8TfSS7dDw4yUq6wMZD/2nREvR0mqzqsNXvv9guh+62hkt99ly6QcJ5n";
NSString *sentryDSN = @"tmVjdMN9DpZ+0EIrrvHi44hWfaBkwrlrxjBkdeau2rDk+zlvgSdAZkAvNj7m1V+5NUR7i8Y/NumNKOaYlWJvPynEMJ4ZBvPepSbivgVvmr8=";
NSString *countlyKey = @"mDnMZyxwoq4ENgYnGYTzW8wsyiJQlmNKxkRLj88/nrs0mzE+zVjs6Y5LAT3+AYBB";
NSString *countlySalt = @"2COFsZd+4FNAU6jvI/HUu297mkZALzRIyKv5mD3vs55BHXDowh62A7FursCYS+cG";
#elif TARGET_OS_MAC
NSString *appSecret = @"24fcbadccb5789b2a969c0c811f86702";
NSString *appSalt = @"0N1fzSanIOCb7OQ4hEshXSjwEPXAXMhPBKQJeEcYPor8FWz76IpdB8ZHa3Wyb7o9";
NSString *sentryDSN = @"2RbeS9wfzQEOKB9MG3EWLDe+N8iXYNtWc8tovMcBmhuMIeyAHYKqo5eclSEYyM6lA73Y7FFHqUyTLbEmOR6MAU2PtWAitLdxOZlq3VnbXjI=";
NSString *countlyKey = @"uiasXoQNtkPQHvpvNqEE5N/tw/F1Hnzm+4ViSJ38EMeoWGvDQPJ+Kt9zPhb8Qans";
NSString *countlySalt = @"/raQUNxKQdxXRR5VFmCDJdyyJE8f6SPrTO5Y4z0kJH+wCrjaZ1VvCq+JSmOsBkz2";
#endif
NSString *decrypt(NSString *secret) {

View File

@@ -66,7 +66,7 @@
<action selector="exportSitesSecure:" target="494" id="LVH-es-imA"/>
</connections>
</menuItem>
<menuItem title="Your passwords are hidden." enabled="NO" id="ybY-P3-eao">
<menuItem title="Your passwords are not visible." enabled="NO" id="ybY-P3-eao">
<attributedString key="attributedTitle">
<fragment content="Your passwords are not visible.">
<attributes>
@@ -83,7 +83,7 @@
<action selector="exportSitesReveal:" target="494" id="1IW-VT-Oeu"/>
</connections>
</menuItem>
<menuItem title="Handy for backups - keep it in a safe location!" enabled="NO" id="cQu-oR-SUa">
<menuItem title="Keep this file secure or delete it when you're done with it!" enabled="NO" id="cQu-oR-SUa">
<attributedString key="attributedTitle">
<fragment content="Keep this file secure or delete it when you're done with it!">
<attributes>
@@ -210,7 +210,7 @@
</menuItem>
<menuItem title="Crash and usage information is anonymized and sent to development." enabled="NO" id="WfD-lX-C93">
<attributedString key="attributedTitle">
<fragment content="Save the password in your keychain so you don't need to enter it again.">
<fragment content="Crash and usage information is anonymized and sent to development.">
<attributes>
<font key="NSFont" size="11" name="Helvetica"/>
<paragraphStyle key="NSParagraphStyle" alignment="natural" lineBreakMode="wordWrapping" baseWritingDirection="natural" firstLineHeadIndent="8"/>

View File

@@ -29,7 +29,6 @@
#define LOGIN_HELPER_BUNDLE_ID @"com.lyndir.lhunath.MasterPassword.Mac.LoginHelper"
@implementation MPMacAppDelegate
#pragma clang diagnostic push
@@ -70,22 +69,23 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
@try {
// Sentry
[SentrySDK initWithOptions:@{
@"dsn" : decrypt( sentryDSN ),
@"dsn" : NilToNSNull( decrypt( sentryDSN ) ),
#ifdef DEBUG
@"debug" : @(YES),
@"environment": @"Development",
@"debug" : @(YES),
@"environment" : @"Development",
#elif PUBLIC
@"debug" : @(NO),
@"environment": @"Public",
@"debug" : @(NO),
@"environment" : @"Public",
#else
@"debug" : @(NO),
@"environment": @"Private",
@"debug" : @(NO),
@"environment" : @"Private",
#endif
@"enabled" : [MPMacConfig get].sendInfo,
@"enabled" : [MPMacConfig get].sendInfo,
@"enableAutoSessionTracking": @(YES),
}];
[[PearlLogger get] registerListener:^BOOL(PearlLogMessage *message) {
PearlLogLevel level = PearlLogLevelWarn;
if ([[MPConfig get].sendInfo boolValue])
if ([[MPMacConfig get].sendInfo boolValue])
level = PearlLogLevelDebug;
if (message.level >= level) {
@@ -131,11 +131,11 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
countlyConfig.deviceID = [PearlKeyChain deviceIdentifier];
countlyConfig.secretSalt = decrypt( countlySalt );
#if DEBUG
countlyConfig.pushTestMode = CLYPushTestModeDevelopment;
countlyConfig.enableDebug = YES;
#elif ! PUBLIC
countlyConfig.pushTestMode = CLYPushTestModeTestFlightOrAdHoc;
countlyConfig.pushTestMode = CLYPushTestModeDevelopment;
#elif !PUBLIC
countlyConfig.enableDebug = NO;
countlyConfig.pushTestMode = CLYPushTestModeTestFlightOrAdHoc;
#endif
[Countly.sharedInstance startWithConfig:countlyConfig];
}
@@ -144,7 +144,7 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
}
// Setup delegates and listeners.
[MPConfig get].delegate = self;
[MPMacConfig get].delegate = self;
__weak id weakSelf = self;
[self addObserverBlock:^(NSString *keyPath, id object, NSDictionary *change, void *context) {
dispatch_async( dispatch_get_main_queue(), ^{
@@ -203,11 +203,13 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
.window makeKeyAndOrderFront:self];
[NSApp activateIgnoringOtherApps:YES];
}
[self tryNotifications];
}
- (void)applicationWillResignActive:(NSNotification *)notification {
if (![[MPConfig get].rememberLogin boolValue])
if (![[MPMacConfig get].rememberLogin boolValue])
[self lock:nil];
}
@@ -228,6 +230,42 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
return NSTerminateNow;
}
- (void)tryNotifications {
[Countly.sharedInstance giveConsentForFeature:CLYConsentPushNotifications];
if (@available( macOS 10.14, * )) {
[Countly.sharedInstance askForNotificationPermissionWithOptions:UNAuthorizationOptionProvisional | UNAuthorizationOptionAlert
completionHandler:^(BOOL granted, NSError *error) {
if (!granted)
err( @"No provisional notification permission: %@", error );
[self askNotifications];
}];
}
else {
[self askNotifications];
}
}
- (void)askNotifications {
if ([[MPMacConfig get].notificationsDecided boolValue])
return;
PearlMainQueue( ^{
if (@available( macOS 10.14, * )) {
[Countly.sharedInstance askForNotificationPermissionWithOptions:UNAuthorizationOptionAlert completionHandler:
^(BOOL granted, NSError *error) {
[MPMacConfig get].notificationsDecided = @(YES);
}];
}
else {
[Countly.sharedInstance askForNotificationPermission];
[MPMacConfig get].notificationsDecided = @(YES);
}
} );
}
#pragma mark - State
- (void)setActiveUser:(MPUserEntity *)activeUser {
@@ -371,11 +409,11 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
- (IBAction)togglePreference:(id)sender {
if (sender == self.diagnosticsItem)
[MPConfig get].sendInfo = @(self.diagnosticsItem.state != NSOnState);
[MPMacConfig get].sendInfo = @(self.diagnosticsItem.state != NSOnState);
if (sender == self.hidePasswordsItem)
[MPConfig get].hidePasswords = @(self.hidePasswordsItem.state != NSOnState);
[MPMacConfig get].hidePasswords = @(self.hidePasswordsItem.state != NSOnState);
if (sender == self.rememberPasswordItem)
[MPConfig get].rememberLogin = @(self.rememberPasswordItem.state != NSOnState);
[MPMacConfig get].rememberLogin = @(self.rememberPasswordItem.state != NSOnState);
if (sender == self.openAtLoginItem)
[self setLoginItemEnabled:self.openAtLoginItem.state != NSOnState];
if (sender == self.showFullScreenItem) {
@@ -402,8 +440,8 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
NSAlert *alert = [NSAlert new];
[alert setMessageText:@"New User"];
[alert setInformativeText:@"To begin, enter your full name.\n\n"
@"IMPORTANT: Enter your name correctly, including the right capitalization, "
@"as you would on an official document."];
@"IMPORTANT: Enter your name correctly, including the right capitalization, "
@"as you would on an official document."];
[alert addButtonWithTitle:@"Create User"];
[alert addButtonWithTitle:@"Cancel"];
NSTextField *nameField = [[NSTextField alloc] initWithFrame:NSMakeRect( 0, 0, 200, 22 )];
@@ -685,10 +723,13 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
}
BOOL loginItemEnabled = [self loginItemEnabled];
self.initialWindowController.openAtLoginButton.state = loginItemEnabled? NSOnState: NSOffState;
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;
self.rememberPasswordItem.state = [[MPMacConfig get].rememberLogin boolValue]? NSOnState: NSOffState;
self.diagnosticsItem.state = [[MPMacConfig get].sendInfo boolValue]? NSOnState: NSOffState;
self.hidePasswordsItem.state = [[MPMacConfig get].hidePasswords boolValue]? NSOnState: NSOffState;
self.rememberPasswordItem.state = [[MPMacConfig get].rememberLogin boolValue]? NSOnState: NSOffState;
self.savePasswordItem.state = activeUser.saveKey? NSOnState: NSOffState;
if (!activeUser) {
@@ -719,43 +760,45 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
PearlMainQueue( ^{
if (!key || [key isEqualToString:NSStringFromSelector( @selector( sendInfo ) )])
self.diagnosticsItem.state = [[MPConfig get].sendInfo boolValue]? NSOnState: NSOffState;
self.diagnosticsItem.state = [[MPMacConfig get].sendInfo boolValue]? NSOnState: NSOffState;
if (!key || [key isEqualToString:NSStringFromSelector( @selector( hidePasswords ) )])
self.hidePasswordsItem.state = [[MPConfig get].hidePasswords boolValue]? NSOnState: NSOffState;
self.hidePasswordsItem.state = [[MPMacConfig get].hidePasswords boolValue]? NSOnState: NSOffState;
if (!key || [key isEqualToString:NSStringFromSelector( @selector( rememberLogin ) )])
self.rememberPasswordItem.state = [[MPConfig get].rememberLogin boolValue]? NSOnState: NSOffState;
self.rememberPasswordItem.state = [[MPMacConfig get].rememberLogin boolValue]? NSOnState: NSOffState;
} );
// Send info
if ([[MPConfig get].sendInfo boolValue]) {
[Countly.sharedInstance giveConsentForAllFeatures];
[Countly.sharedInstance askForNotificationPermission];
NSArray *countlyFeatures = @[
CLYConsentEvents, CLYConsentUserDetails, CLYConsentCrashReporting, CLYConsentViewTracking, CLYConsentStarRating
];
if ([[MPConfig get].sendInfo boolValue] || ![[MPConfig get].sendInfoDecided boolValue])
[Countly.sharedInstance giveConsentForFeature:CLYConsentSessions];
else
[Countly.sharedInstance cancelConsentForFeature:CLYConsentSessions];
if ([[MPMacConfig get].sendInfo boolValue]) {
if ([PearlLogger get].printLevel > PearlLogLevelInfo)
[PearlLogger get].printLevel = PearlLogLevelInfo;
NSMutableDictionary *prefs = [NSMutableDictionary new];
prefs[@"rememberLogin"] = [MPConfig get].rememberLogin;
prefs[@"sendInfo"] = [MPConfig get].sendInfo;
prefs[@"fullScreen"] = [MPMacConfig get].fullScreen;
prefs[@"firstRun"] = [PearlConfig get].firstRun;
prefs[@"launchCount"] = [PearlConfig get].launchCount;
prefs[@"askForReviews"] = [PearlConfig get].askForReviews;
prefs[@"reviewAfterLaunches"] = [PearlConfig get].reviewAfterLaunches;
prefs[@"reviewedVersion"] = [PearlConfig get].reviewedVersion;
prefs[@"simulator"] = @([PearlDeviceUtils isSimulator]);
prefs[@"encrypted"] = @([PearlDeviceUtils isAppEncrypted]);
prefs[@"platform"] = [PearlDeviceUtils platform];
[SentrySDK.currentHub getClient].options.enabled = @YES;
[SentrySDK configureScope:^(SentryScope *scope) {
for (NSString *pref in prefs.allKeys)
[scope setExtraValue:prefs[pref] forKey:pref];
[scope setExtraValue:[MPMacConfig get].rememberLogin forKey:@"rememberLogin"];
[scope setExtraValue:[MPMacConfig get].sendInfo forKey:@"sendInfo"];
[scope setExtraValue:[MPMacConfig get].fullScreen forKey:@"fullScreen"];
[scope setExtraValue:[PearlConfig get].firstRun forKey:@"firstRun"];
[scope setExtraValue:[PearlConfig get].launchCount forKey:@"launchCount"];
[scope setExtraValue:[PearlConfig get].askForReviews forKey:@"askForReviews"];
[scope setExtraValue:[PearlConfig get].reviewAfterLaunches forKey:@"reviewAfterLaunches"];
[scope setExtraValue:[PearlConfig get].reviewedVersion forKey:@"reviewedVersion"];
[scope setExtraValue:@([PearlDeviceUtils isSimulator]) forKey:@"simulator"];
[scope setExtraValue:@([PearlDeviceUtils isAppEncrypted]) forKey:@"encrypted"];
[scope setExtraValue:[PearlDeviceUtils platform] forKey:@"platform"];
}];
[Countly.sharedInstance giveConsentForFeatures:countlyFeatures];
}
else {
[Countly.sharedInstance cancelConsentForFeatures:countlyFeatures];
[SentrySDK.currentHub getClient].options.enabled = @NO;
[Countly.sharedInstance cancelConsentForAllFeatures];
}
}

View File

@@ -17,6 +17,7 @@
//==============================================================================
#import <QuartzCore/QuartzCore.h>
#import <Countly/Countly.h>
#import "MPSitesWindowController.h"
#import "MPMacAppDelegate.h"
#import "MPAppDelegate_Store.h"
@@ -42,13 +43,30 @@
prof_rewind( @"replaceFonts" );
PearlAddNotificationObserver( NSWindowDidBecomeKeyNotification, self.window, [NSOperationQueue mainQueue],
^(id host, NSNotification *note) {
(^(id host, NSNotification *note) {
prof_new( @"didBecomeKey" );
[self.window makeKeyAndOrderFront:nil];
prof_rewind( @"fadeIn" );
[self updateUser];
prof_finish( @"updateUser" );
} );
prof_rewind( @"updateUser" );
if (![[MPMacConfig get].sendInfoDecided boolValue]) {
NSAlert *alert = [NSAlert new];
alert.messageText = @"Welcome to Master Password!";
alert.informativeText = @"We want you to have a top-notch experience.\n"
@"Using diagnostics, we ensure the application keeps working as designed for you.\n"
@"\n"
@"We look out for application bugs, runtime issues, sudden crashes & usage counters.\n"
@"Needless to say, diagnostics are always scrubbed and personal details will never leave your device.";
[alert addButtonWithTitle:@"Thanks!"];
[alert addButtonWithTitle:@"Disable"];
[alert beginSheetModalForWindow:self.window completionHandler:^(NSModalResponse returnCode) {
[MPMacConfig get].sendInfo = @(returnCode != NSAlertSecondButtonReturn);
[MPMacConfig get].sendInfoDecided = @(YES);
}];
}
prof_finish( @"sendInfoDecided" );
}) );
PearlAddNotificationObserver( NSWindowWillCloseNotification, self.window, [NSOperationQueue mainQueue],
^(id host, NSNotification *note) {
NSWindow *sheet = [self.window attachedSheet];
@@ -376,7 +394,52 @@
}];
}
- (IBAction)changeType:(id)sender {
- (IBAction)changeDefaultType:(id)sender {
MPSiteModel *site = self.selectedSite;
MPUserEntity *user = [MPMacAppDelegate get].activeUserForMainThread;
NSArray *types = [user.algorithm allTypes];
[self.passwordTypesMatrix renewRows:(NSInteger)[types count] columns:1];
for (NSUInteger t = 0; t < [types count]; ++t) {
MPResultType type = (MPResultType)[types[t] unsignedIntegerValue];
NSString *title = [user.algorithm nameOfType:type];
if (type & MPResultTypeClassTemplate)
title = strf( @"%@ %@", [user.algorithm mpwTemplateForSiteNamed:site.name?: @"masterpassword.app" ofType:type
withCounter:site.counter?: MPCounterValueDefault
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;
}
self.passwordTypesBox.title = strf( @"Choose a password type for new sites of %@:", user.name );
NSAlert *alert = [NSAlert new];
[alert addButtonWithTitle:@"Save"];
[alert addButtonWithTitle:@"Cancel"];
[alert setMessageText:@"Change Default Type"];
[alert setAccessoryView:self.passwordTypesBox];
[alert layout];
[alert beginSheetModalForWindow:self.window completionHandler:^(NSModalResponse returnCode) {
switch (returnCode) {
case NSAlertFirstButtonReturn: {
// "Save" button.
MPResultType type = (MPResultType)[self.passwordTypesMatrix.selectedCell tag];
[MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
[[MPMacAppDelegate get] activeUserInContext:context].defaultType = type;
[context saveToStore];
}];
break;
}
default:
break;
}
}];
}
- (IBAction)changeSiteType:(id)sender {
MPSiteModel *site = self.selectedSite;
NSArray *types = [site.algorithm allTypes];
@@ -609,7 +672,7 @@
- (void)updateSelection {
self.showVersionContainer = self.alternatePressed || self.selectedSite.outdated;
[self.sitePasswordTipField setAttributedStringValue:
straf( @"Your %@ for %@:", self.shiftPressed? @"login": @"password", self.selectedSite.displayedName )];
straf( @"Your %@ for %@:", self.shiftPressed? @"login": @"password", self.selectedSite.displayedName, nil )];
}
- (void)createNewSite:(NSString *)siteName {

View File

@@ -31,20 +31,20 @@
<rect key="contentRect" x="0.0" y="0.0" width="640" height="577"/>
<rect key="screenRect" x="0.0" y="0.0" width="1680" height="1027"/>
<view key="contentView" wantsLayer="YES" id="EiT-Mj-1SZ" userLabel="Root">
<rect key="frame" x="0.0" y="0.0" width="640" height="557"/>
<rect key="frame" x="0.0" y="0.0" width="738" height="553"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<visualEffectView blendingMode="behindWindow" material="popover" state="followsWindowActiveState" translatesAutoresizingMaskIntoConstraints="NO" id="eRe-Ef-AZx">
<rect key="frame" x="0.0" y="0.0" width="640" height="557"/>
<rect key="frame" x="0.0" y="0.0" width="738" height="553"/>
</visualEffectView>
<progressIndicator hidden="YES" wantsLayer="YES" horizontalHuggingPriority="750" verticalHuggingPriority="750" maxValue="100" displayedWhenStopped="NO" bezeled="NO" indeterminate="YES" controlSize="small" style="spinning" translatesAutoresizingMaskIntoConstraints="NO" id="oSh-Ec-8Nf" userLabel="Progress Spinner">
<rect key="frame" x="312" y="521" width="16" height="16"/>
<rect key="frame" x="361" y="517" width="16" height="16"/>
</progressIndicator>
<customView translatesAutoresizingMaskIntoConstraints="NO" id="NGk-Io-Buc" userLabel="Top Box">
<rect key="frame" x="20" y="381" width="600" height="132"/>
<rect key="frame" x="20" y="377" width="698" height="132"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Ond-dT-x5d" userLabel="Site Password Label">
<rect key="frame" x="157" y="98" width="287" height="14"/>
<rect key="frame" x="206" y="98" width="287" height="14"/>
<shadow key="shadow" blurRadius="0.5">
<size key="offset" width="0.0" height="1"/>
<color key="color" name="controlLightHighlightColor" catalog="System" colorSpace="catalog"/>
@@ -72,7 +72,7 @@
</connections>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Ia6-7b-dFr">
<rect key="frame" x="127" y="59" width="347" height="14"/>
<rect key="frame" x="176" y="59" width="347" height="14"/>
<shadow key="shadow" blurRadius="0.5">
<size key="offset" width="0.0" height="1"/>
<color key="color" name="controlLightHighlightColor" catalog="System" colorSpace="catalog"/>
@@ -108,13 +108,13 @@
</connections>
</textField>
<textField hidden="YES" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="OaQ-of-zmb" userLabel="Initial Help">
<rect key="frame" x="39" y="109" width="522" height="18"/>
<rect key="frame" x="113" y="109" width="473" height="17"/>
<shadow key="shadow" blurRadius="0.5">
<size key="offset" width="0.0" height="1"/>
<color key="color" name="controlLightHighlightColor" catalog="System" colorSpace="catalog"/>
</shadow>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="center" title="Master Password generates passwords for your sites (and other things)." id="YyD-hd-wi3">
<font key="font" size="16" name="HelveticaNeue"/>
<font key="font" metaFont="menu" size="14"/>
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
@@ -136,18 +136,18 @@
</connections>
</textField>
<textField hidden="YES" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="sYt-eL-uwt" userLabel="Initial Tip">
<rect key="frame" x="-26" y="31" width="652" height="70"/>
<rect key="frame" x="-2" y="31" width="702" height="70"/>
<shadow key="shadow" blurRadius="0.5">
<size key="offset" width="0.0" height="1"/>
<color key="color" name="controlLightHighlightColor" catalog="System" colorSpace="catalog"/>
</shadow>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="left" id="9c4-NI-NM0">
<font key="font" size="12" name="HelveticaNeue"/>
<string key="title"> When you create an account with a site, use Master Password to create a password for it.
For accounts you already have, change the password to that created by Master Password for it.
To get a password, enter the site's bare domain name in the "site name" field (eg. apple.com).
For your master password, think of a strong password (eg. a short sentence). Tell *nobody*.
It's OK to share your site passwords. They can be changed if necessary.</string>
<font key="font" metaFont="message" size="11"/>
<string key="title"> When you create an account on a site, open Master Password to create your account password.
⑵ Consider changing all your existing account passwords to the password Master Password creates for those sites.
To get the password for a site, just enter its domain name in the "site name" field (eg. "apple.com").
⑷ When chosing a master password, make it easy but long (eg. a short sentence).
⑸ Tell *nobody* your master password. It's OK to share your site passwords with people you trust: they can be changed if necessary.</string>
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
@@ -169,7 +169,7 @@
</connections>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="XUV-zU-Y9c" userLabel="Site Password">
<rect key="frame" x="-2" y="26" width="604" height="80"/>
<rect key="frame" x="-2" y="26" width="702" height="80"/>
<shadow key="shadow" blurRadius="0.5">
<size key="offset" width="0.0" height="1"/>
<color key="color" white="0.0" alpha="1" colorSpace="calibratedWhite"/>
@@ -202,6 +202,7 @@
<constraint firstItem="XUV-zU-Y9c" firstAttribute="leading" secondItem="NGk-Io-Buc" secondAttribute="leading" id="6Kl-7u-r90"/>
<constraint firstAttribute="centerY" secondItem="XUV-zU-Y9c" secondAttribute="centerY" id="6Qf-5O-Cvk"/>
<constraint firstAttribute="centerX" secondItem="XUV-zU-Y9c" secondAttribute="centerX" id="7sl-qi-HY9"/>
<constraint firstItem="sYt-eL-uwt" firstAttribute="width" secondItem="NGk-Io-Buc" secondAttribute="width" id="Beb-mW-KDP"/>
<constraint firstItem="Ond-dT-x5d" firstAttribute="top" secondItem="NGk-Io-Buc" secondAttribute="top" constant="20" symbolic="YES" id="IX4-cd-VkJ"/>
<constraint firstItem="Ia6-7b-dFr" firstAttribute="centerY" secondItem="XUV-zU-Y9c" secondAttribute="centerY" id="KqM-uR-Obm"/>
<constraint firstItem="Ia6-7b-dFr" firstAttribute="centerX" secondItem="XUV-zU-Y9c" secondAttribute="centerX" id="NFQ-aw-8tm"/>
@@ -209,14 +210,13 @@
<constraint firstAttribute="trailing" secondItem="XUV-zU-Y9c" secondAttribute="trailing" id="TdB-QV-9JK"/>
<constraint firstItem="Ond-dT-x5d" firstAttribute="bottom" secondItem="XUV-zU-Y9c" secondAttribute="top" constant="8" symbolic="YES" id="UgV-J6-B5T"/>
<constraint firstItem="Ond-dT-x5d" firstAttribute="centerX" secondItem="XUV-zU-Y9c" secondAttribute="centerX" id="UhT-LQ-aZ8"/>
<constraint firstItem="sYt-eL-uwt" firstAttribute="leading" secondItem="NGk-Io-Buc" secondAttribute="leading" constant="-24" id="eaC-ow-ren"/>
<constraint firstItem="sYt-eL-uwt" firstAttribute="top" secondItem="OaQ-of-zmb" secondAttribute="bottom" constant="8" symbolic="YES" id="hjJ-f1-mFv"/>
<constraint firstItem="sYt-eL-uwt" firstAttribute="centerX" secondItem="OaQ-of-zmb" secondAttribute="centerX" id="mu2-se-Mtn"/>
<constraint firstAttribute="centerY" secondItem="sYt-eL-uwt" secondAttribute="centerY" id="zLS-QG-MKS"/>
</constraints>
</customView>
<scrollView focusRingType="none" borderType="none" autohidesScrollers="YES" horizontalLineScroll="34" horizontalPageScroll="10" verticalLineScroll="34" verticalPageScroll="10" hasHorizontalScroller="NO" hasVerticalScroller="NO" horizontalScrollElasticity="none" translatesAutoresizingMaskIntoConstraints="NO" id="Bme-XK-MMc" userLabel="Sites Table">
<rect key="frame" x="64" y="40" width="512" height="177"/>
<rect key="frame" x="113" y="40" width="512" height="177"/>
<clipView key="contentView" drawsBackground="NO" copiesOnScroll="NO" id="e11-59-xSS">
<rect key="frame" x="0.0" y="0.0" width="512" height="177"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
@@ -251,7 +251,7 @@
<color key="color" name="controlLightHighlightColor" catalog="System" colorSpace="catalog"/>
</shadow>
<textFieldCell key="cell" lineBreakMode="truncatingTail" alignment="center" title="apple.com" id="o0g-Zv-pH4">
<font key="font" size="24" name="HelveticaNeue-Thin"/>
<font key="font" metaFont="systemUltraLight" size="24"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
@@ -310,7 +310,7 @@
</connections>
</scrollView>
<customView translatesAutoresizingMaskIntoConstraints="NO" id="nM8-O3-spM" customClass="MPGradientView">
<rect key="frame" x="0.0" y="0.0" width="640" height="212"/>
<rect key="frame" x="0.0" y="0.0" width="738" height="238"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="color" keyPath="startingColor">
<color key="value" red="0.11764705882352941" green="0.11764705882352941" blue="0.11764705882352941" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
@@ -324,7 +324,7 @@
</userDefinedRuntimeAttributes>
</customView>
<button translatesAutoresizingMaskIntoConstraints="NO" id="Aue-Zx-6Mf" userLabel="Settings Gear">
<rect key="frame" x="585" y="493" width="35" height="44"/>
<rect key="frame" x="683" y="489" width="35" height="44"/>
<shadow key="shadow">
<color key="color" name="controlLightHighlightColor" catalog="System" colorSpace="catalog"/>
</shadow>
@@ -339,7 +339,7 @@
</connections>
</button>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="gAU-xs-aae">
<rect key="frame" x="594" y="479" width="18" height="14"/>
<rect key="frame" x="692" y="475" width="18" height="14"/>
<shadow key="shadow" blurRadius="0.5">
<size key="offset" width="0.0" height="1"/>
<color key="color" name="controlLightHighlightColor" catalog="System" colorSpace="catalog"/>
@@ -358,7 +358,7 @@
</connections>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="OnR-s6-d4P" userLabel="Site Name Label">
<rect key="frame" x="209" y="308" width="223" height="16"/>
<rect key="frame" x="258" y="306" width="223" height="16"/>
<shadow key="shadow" blurRadius="0.5">
<size key="offset" width="0.0" height="1"/>
<color key="color" name="controlLightHighlightColor" catalog="System" colorSpace="catalog"/>
@@ -370,7 +370,7 @@
</textFieldCell>
</textField>
<secureTextField focusRingType="none" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="iGR-wo-ual" userLabel="Secure Master Password">
<rect key="frame" x="18" y="257" width="604" height="43"/>
<rect key="frame" x="18" y="255" width="702" height="43"/>
<shadow key="shadow" blurRadius="0.5">
<size key="offset" width="0.0" height="1"/>
<color key="color" name="controlLightHighlightColor" catalog="System" colorSpace="catalog"/>
@@ -405,7 +405,7 @@
</connections>
</secureTextField>
<textField focusRingType="none" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="v80-wd-hUR" userLabel="Revealed Master Password">
<rect key="frame" x="18" y="257" width="604" height="43"/>
<rect key="frame" x="18" y="255" width="702" height="43"/>
<shadow key="shadow" blurRadius="0.5">
<size key="offset" width="0.0" height="1"/>
<color key="color" name="controlLightHighlightColor" catalog="System" colorSpace="catalog"/>
@@ -444,13 +444,13 @@
</connections>
</textField>
<textField hidden="YES" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="lW3-2z-cEa" userLabel="Master Password Tip">
<rect key="frame" x="195" y="237" width="251" height="12"/>
<rect key="frame" x="248" y="236" width="243" height="11"/>
<shadow key="shadow" blurRadius="0.5">
<size key="offset" width="0.0" height="1"/>
<color key="color" name="controlLightHighlightColor" catalog="System" colorSpace="catalog"/>
</shadow>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="center" title="Hold alt ⌥ to reveal tips and unmask ●●●●●●●●." id="4Ep-xX-Ky8">
<font key="font" size="11" name="HelveticaNeue"/>
<font key="font" metaFont="miniSystem"/>
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
@@ -471,13 +471,13 @@
</connections>
</textField>
<textField hidden="YES" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Oy5-B7-hdN" userLabel="Revealed Master Password Tip">
<rect key="frame" x="195" y="237" width="250" height="12"/>
<rect key="frame" x="258" y="236" width="222" height="11"/>
<shadow key="shadow" blurRadius="0.5">
<size key="offset" width="0.0" height="1"/>
<color key="color" name="controlLightHighlightColor" catalog="System" colorSpace="catalog"/>
</shadow>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="center" title="Hold alt ⌥ to reveal tips and unmask passwords." id="bQ4-AN-S1A">
<font key="font" size="11" name="HelveticaNeue"/>
<font key="font" metaFont="miniSystem"/>
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
@@ -499,7 +499,7 @@
</connections>
</textField>
<searchField focusRingType="none" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="CnS-iI-dhr" userLabel="Site Name">
<rect key="frame" x="62" y="257" width="516" height="43"/>
<rect key="frame" x="111" y="255" width="516" height="43"/>
<constraints>
<constraint firstAttribute="width" constant="512" id="rW7-Vq-4Xy"/>
</constraints>
@@ -508,7 +508,7 @@
<color key="color" name="controlLightHighlightColor" catalog="System" colorSpace="catalog"/>
</shadow>
<searchFieldCell key="cell" selectable="YES" editable="YES" focusRingType="none" alignment="center" placeholderString="Site Name" sendsSearchStringImmediately="YES" id="ppl-2c-1E9">
<font key="font" size="36" name="HelveticaNeue-Thin"/>
<font key="font" metaFont="system" size="36"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</searchFieldCell>
@@ -519,13 +519,13 @@
</connections>
</searchField>
<textField hidden="YES" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="npC-Kk-gUM" userLabel="Site Name Tip">
<rect key="frame" x="101" y="237" width="438" height="12"/>
<rect key="frame" x="172" y="236" width="395" height="11"/>
<shadow key="shadow" blurRadius="0.5">
<size key="offset" width="0.0" height="1"/>
<color key="color" name="controlLightHighlightColor" catalog="System" colorSpace="catalog"/>
</shadow>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="center" title="Type the name of your site (eg. apple.com), then hit enter ⏎ to create a password for it." id="QTI-cz-Onx">
<font key="font" size="11" name="HelveticaNeue"/>
<font key="font" metaFont="miniSystem"/>
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
@@ -542,13 +542,13 @@
</connections>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="rhm-sC-xFS" userLabel="Site Name Tip">
<rect key="frame" x="138" y="225" width="365" height="24"/>
<rect key="frame" x="206" y="225" width="326" height="22"/>
<shadow key="shadow" blurRadius="0.5">
<size key="offset" width="0.0" height="1"/>
<color key="color" name="controlLightHighlightColor" catalog="System" colorSpace="catalog"/>
</shadow>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="center" id="n3W-XU-dya">
<font key="font" size="11" name="HelveticaNeue"/>
<font key="font" metaFont="miniSystem"/>
<string key="title">Hit enter ⏎ to copy the site's password, hold shift ⇧ for the login name.
Use the arrows ⇅ to navigate the list or esc ⎋ to exit.</string>
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
@@ -568,7 +568,7 @@ Use the arrows ⇅ to navigate the list or esc ⎋ to exit.</string>
</connections>
</textField>
<stackView distribution="fill" orientation="horizontal" alignment="bottom" horizontalStackHuggingPriority="249.99998474121094" verticalStackHuggingPriority="249.99998474121094" detachesHiddenViews="YES" translatesAutoresizingMaskIntoConstraints="NO" id="pHt-gg-ZNX">
<rect key="frame" x="73" y="20" width="495" height="152"/>
<rect key="frame" x="122" y="20" width="495" height="178"/>
<subviews>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="1Qo-iG-CQt">
<rect key="frame" x="0.0" y="-1" width="85" height="19"/>
@@ -591,10 +591,10 @@ Use the arrows ⇅ to navigate the list or esc ⎋ to exit.</string>
</connections>
</button>
<stackView distribution="fill" orientation="vertical" alignment="centerX" horizontalStackHuggingPriority="249.99998474121094" verticalStackHuggingPriority="249.99998474121094" detachesHiddenViews="YES" translatesAutoresizingMaskIntoConstraints="NO" id="DT0-RU-3LT">
<rect key="frame" x="93" y="0.0" width="177" height="152"/>
<rect key="frame" x="93" y="0.0" width="177" height="178"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="uol-dE-I8H">
<rect key="frame" x="77" y="138" width="22" height="14"/>
<rect key="frame" x="77" y="164" width="22" height="14"/>
<shadow key="shadow" blurRadius="0.5">
<size key="offset" width="0.0" height="1"/>
<color key="color" name="controlLightHighlightColor" catalog="System" colorSpace="catalog"/>
@@ -631,7 +631,7 @@ Use the arrows ⇅ to navigate the list or esc ⎋ to exit.</string>
</connections>
</textField>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="brI-fg-Kav">
<rect key="frame" x="40" y="111" width="96" height="19"/>
<rect key="frame" x="40" y="137" width="96" height="19"/>
<shadow key="shadow">
<color key="color" name="controlLightHighlightColor" catalog="System" colorSpace="catalog"/>
</shadow>
@@ -659,6 +659,35 @@ Use the arrows ⇅ to navigate the list or esc ⎋ to exit.</string>
</binding>
</connections>
</button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="jl4-sS-xbm">
<rect key="frame" x="12" y="111" width="153" height="19"/>
<shadow key="shadow">
<color key="color" name="controlLightHighlightColor" catalog="System" colorSpace="catalog"/>
</shadow>
<buttonCell key="cell" type="recessed" title="Default Password Type" bezelStyle="recessed" alignment="center" state="on" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="Ah6-gK-Rm7" customClass="MPNoStateButtonCell">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES" changeBackground="YES" changeGray="YES"/>
<font key="font" metaFont="systemBold" size="12"/>
<string key="keyEquivalent">p</string>
<modifierMask key="keyEquivalentModifierMask" command="YES"/>
</buttonCell>
<connections>
<action selector="changeDefaultType:" target="-2" id="3Qg-xV-ewc"/>
<binding destination="-2" name="hidden2" keyPath="alternatePressed" previousBinding="2Mv-lM-iXB" id="6QO-NJ-Uyo">
<dictionary key="options">
<integer key="NSMultipleValuesPlaceholder" value="-1"/>
<integer key="NSNoSelectionPlaceholder" value="-1"/>
<integer key="NSNotApplicablePlaceholder" value="-1"/>
<integer key="NSNullPlaceholder" value="-1"/>
<string key="NSValueTransformerName">NSNegateBoolean</string>
</dictionary>
</binding>
<binding destination="mcS-ik-b0n" name="hidden" keyPath="canRemove" id="2Mv-lM-iXB">
<dictionary key="options">
<string key="NSValueTransformerName">NSNegateBoolean</string>
</dictionary>
</binding>
</connections>
</button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="R46-fx-n14">
<rect key="frame" x="0.0" y="85" width="177" height="19"/>
<shadow key="shadow">
@@ -686,7 +715,7 @@ Use the arrows ⇅ to navigate the list or esc ⎋ to exit.</string>
</connections>
</button>
<stackView distribution="fill" orientation="horizontal" alignment="top" horizontalStackHuggingPriority="249.99998474121094" verticalStackHuggingPriority="249.99998474121094" detachesHiddenViews="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Bgn-Ne-fQ7" userLabel="Version Container">
<rect key="frame" x="70" y="56" width="36" height="22"/>
<rect key="frame" x="71" y="56" width="34" height="22"/>
<subviews>
<stepper horizontalHuggingPriority="750" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="mcq-qD-yte">
<rect key="frame" x="-3" y="-3" width="19" height="28"/>
@@ -699,13 +728,13 @@ Use the arrows ⇅ to navigate the list or esc ⎋ to exit.</string>
</connections>
</stepper>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="gyg-Fh-yn7">
<rect key="frame" x="19" y="7" width="19" height="15"/>
<rect key="frame" x="19" y="8" width="17" height="14"/>
<shadow key="shadow" blurRadius="0.5">
<size key="offset" width="0.0" height="1"/>
<color key="color" name="controlLightHighlightColor" catalog="System" colorSpace="catalog"/>
</shadow>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="V1" id="Pjy-Fm-zwB">
<font key="font" size="12" name="HelveticaNeue-Medium"/>
<font key="font" metaFont="message" size="11"/>
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="calibratedRGB"/>
</textFieldCell>
@@ -740,7 +769,7 @@ Use the arrows ⇅ to navigate the list or esc ⎋ to exit.</string>
</connections>
</stackView>
<stackView distribution="fill" orientation="horizontal" alignment="centerY" horizontalStackHuggingPriority="249.99998474121094" verticalStackHuggingPriority="249.99998474121094" detachesHiddenViews="YES" translatesAutoresizingMaskIntoConstraints="NO" id="6II-KA-cNi" userLabel="Counter Container">
<rect key="frame" x="74" y="26" width="28" height="22"/>
<rect key="frame" x="75" y="26" width="27" height="22"/>
<subviews>
<stepper horizontalHuggingPriority="750" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="XgA-Vl-CKh" userLabel="Counter Stepper">
<rect key="frame" x="-3" y="-3" width="19" height="28"/>
@@ -753,13 +782,13 @@ Use the arrows ⇅ to navigate the list or esc ⎋ to exit.</string>
</connections>
</stepper>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="NvO-kt-eZ2" userLabel="Counter Field">
<rect key="frame" x="19" y="4" width="11" height="15"/>
<rect key="frame" x="19" y="4" width="10" height="14"/>
<shadow key="shadow" blurRadius="0.5">
<size key="offset" width="0.0" height="1"/>
<color key="color" name="controlLightHighlightColor" catalog="System" colorSpace="catalog"/>
</shadow>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="1" id="dhQ-bJ-rn3">
<font key="font" size="12" name="HelveticaNeue-Medium"/>
<font key="font" metaFont="message" size="11"/>
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="calibratedRGB"/>
</textFieldCell>
@@ -805,7 +834,7 @@ Use the arrows ⇅ to navigate the list or esc ⎋ to exit.</string>
<modifierMask key="keyEquivalentModifierMask" command="YES"/>
</buttonCell>
<connections>
<action selector="changeType:" target="-2" id="6Jj-7p-da9"/>
<action selector="changeSiteType:" target="-2" id="6Jj-7p-da9"/>
<binding destination="mcS-ik-b0n" name="hidden" keyPath="canRemove" id="Hat-GU-hcQ">
<dictionary key="options">
<string key="NSValueTransformerName">NSNegateBoolean</string>
@@ -821,6 +850,7 @@ Use the arrows ⇅ to navigate the list or esc ⎋ to exit.</string>
<integer value="1000"/>
<integer value="1000"/>
<integer value="1000"/>
<integer value="1000"/>
</visibilityPriorities>
<customSpacing>
<real value="3.4028234663852886e+38"/>
@@ -829,6 +859,7 @@ Use the arrows ⇅ to navigate the list or esc ⎋ to exit.</string>
<real value="3.4028234663852886e+38"/>
<real value="3.4028234663852886e+38"/>
<real value="3.4028234663852886e+38"/>
<real value="3.4028234663852886e+38"/>
</customSpacing>
</stackView>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="whJ-Bw-pr4">
@@ -886,7 +917,7 @@ Use the arrows ⇅ to navigate the list or esc ⎋ to exit.</string>
</customSpacing>
</stackView>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="luC-0j-BeV">
<rect key="frame" x="135" y="50" width="103" height="14"/>
<rect key="frame" x="185" y="50" width="103" height="14"/>
<shadow key="shadow" blurRadius="0.5">
<size key="offset" width="0.0" height="1"/>
<color key="color" name="controlLightHighlightColor" catalog="System" colorSpace="catalog"/>
@@ -923,7 +954,7 @@ Use the arrows ⇅ to navigate the list or esc ⎋ to exit.</string>
</connections>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="gjx-bt-fKM">
<rect key="frame" x="134" y="80" width="100" height="14"/>
<rect key="frame" x="184" y="80" width="100" height="14"/>
<shadow key="shadow" blurRadius="0.5">
<size key="offset" width="0.0" height="1"/>
<color key="color" name="controlLightHighlightColor" catalog="System" colorSpace="catalog"/>
@@ -960,7 +991,7 @@ Use the arrows ⇅ to navigate the list or esc ⎋ to exit.</string>
</connections>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="dbM-ja-dKO" userLabel="Version Tip">
<rect key="frame" x="88" y="106" width="332" height="14"/>
<rect key="frame" x="137" y="106" width="332" height="14"/>
<shadow key="shadow" blurRadius="0.5">
<size key="offset" width="0.0" height="1"/>
<color key="color" name="controlLightHighlightColor" catalog="System" colorSpace="catalog"/>
@@ -988,13 +1019,13 @@ Use the arrows ⇅ to navigate the list or esc ⎋ to exit.</string>
</connections>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Ido-NQ-3MY">
<rect key="frame" x="104" y="4" width="22" height="12"/>
<rect key="frame" x="154" y="2" width="21" height="14"/>
<shadow key="shadow" blurRadius="0.5">
<size key="offset" width="0.0" height="1"/>
<color key="color" name="controlLightHighlightColor" catalog="System" colorSpace="catalog"/>
</shadow>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="⌘L" id="fUB-rF-7x8">
<font key="font" size="11" name="HelveticaNeue"/>
<font key="font" metaFont="message" size="11"/>
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
@@ -1016,7 +1047,7 @@ Use the arrows ⇅ to navigate the list or esc ⎋ to exit.</string>
</connections>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="9b3-wy-KBb">
<rect key="frame" x="243" y="2" width="22" height="14"/>
<rect key="frame" x="292" y="2" width="22" height="14"/>
<shadow key="shadow" blurRadius="0.5">
<size key="offset" width="0.0" height="1"/>
<color key="color" name="controlLightHighlightColor" catalog="System" colorSpace="catalog"/>
@@ -1044,13 +1075,13 @@ Use the arrows ⇅ to navigate the list or esc ⎋ to exit.</string>
</connections>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="qal-PP-YtO">
<rect key="frame" x="516" y="4" width="23" height="12"/>
<rect key="frame" x="565" y="2" width="23" height="14"/>
<shadow key="shadow" blurRadius="0.5">
<size key="offset" width="0.0" height="1"/>
<color key="color" name="controlLightHighlightColor" catalog="System" colorSpace="catalog"/>
</shadow>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="⌘D" id="PPC-be-w4E">
<font key="font" size="11" name="HelveticaNeue"/>
<font key="font" metaFont="message" size="11"/>
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
@@ -1072,13 +1103,13 @@ Use the arrows ⇅ to navigate the list or esc ⎋ to exit.</string>
</connections>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="qLF-A6-ThX">
<rect key="frame" x="404" y="4" width="23" height="12"/>
<rect key="frame" x="453" y="2" width="22" height="14"/>
<shadow key="shadow" blurRadius="0.5">
<size key="offset" width="0.0" height="1"/>
<color key="color" name="controlLightHighlightColor" catalog="System" colorSpace="catalog"/>
</shadow>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="⌘S" id="qh6-k2-MUr">
<font key="font" size="11" name="HelveticaNeue"/>
<font key="font" metaFont="message" size="11"/>
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>

View File

@@ -1,34 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>${EXECUTABLE_NAME}</string>
<key>CFBundleIconFile</key>
<string></string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>${PRODUCT_NAME}</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSBackgroundOnly</key>
<true/>
<key>LSMinimumSystemVersion</key>
<string>${MACOSX_DEPLOYMENT_TARGET}</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2013 Maarten Billemont. All rights reserved.</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
</dict>
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>${EXECUTABLE_NAME}</string>
<key>CFBundleIconFile</key>
<string></string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>${PRODUCT_NAME}</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSApplicationCategoryType</key>
<string>public.app-category.productivity</string>
<key>LSBackgroundOnly</key>
<true/>
<key>LSMinimumSystemVersion</key>
<string>${MACOSX_DEPLOYMENT_TARGET}</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2013 Maarten Billemont. All rights reserved.</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
</dict>
</plist>

View File

@@ -1,23 +0,0 @@
//==============================================================================
// This file is part of Master Password.
// Copyright (c) 2011-2017, Maarten Billemont.
//
// Master Password is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Master Password is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You can find a copy of the GNU General Public License in the
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//==============================================================================
#import <Foundation/Foundation.h>
#import "IASKAppSettingsViewController.h"
@interface MPAppSettingsViewController : IASKAppSettingsViewController
@end

View File

@@ -1,45 +0,0 @@
//==============================================================================
// This file is part of Master Password.
// Copyright (c) 2011-2017, Maarten Billemont.
//
// Master Password is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Master Password is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You can find a copy of the GNU General Public License in the
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//==============================================================================
#import "MPAppSettingsViewController.h"
#import "UIColor+Expanded.h"
@implementation MPAppSettingsViewController
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
self.tableView.contentInset = UIEdgeInsetsMake( 64, 0, 49, 0 );
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [super tableView:tableView cellForRowAtIndexPath:indexPath];
cell.backgroundColor = [UIColor clearColor];
cell.textLabel.textColor = [UIColor whiteColor];
if (cell.selectionStyle != UITableViewCellSelectionStyleNone) {
cell.selectedBackgroundView = [[UIView alloc] initWithFrame:cell.bounds];
cell.selectedBackgroundView.backgroundColor = [UIColor colorWithRGBAHex:0x78DDFB33];
}
return cell;
}
@end

View File

@@ -102,8 +102,6 @@
#pragma mark - Actions
- (IBAction)unwindToCombined:(UIStoryboardSegue *)sender {
dbg( @"unwindToCombined:%@", sender );
}
#pragma mark - State

View File

@@ -18,7 +18,6 @@
#import "MPLogsViewController.h"
#import "MPiOSAppDelegate.h"
#import "MPAppDelegate_Store.h"
@implementation MPLogsViewController
@@ -52,6 +51,17 @@
PearlRemoveNotificationObservers();
}
#pragma clang diagnostic push
#pragma ide diagnostic ignored "UnavailableInDeploymentTarget"
- (void)viewSafeAreaInsetsDidChange {
[super viewSafeAreaInsetsDidChange];
self.logView.contentInset = UIEdgeInsetsMake( 44, 0, 0, 0 );
self.logView.scrollIndicatorInsets = UIEdgeInsetsMake( 44, 0, 0, 0 );
}
#pragma clang diagnostic pop
- (IBAction)toggleLevelControl:(UISegmentedControl *)sender {
BOOL traceEnabled = (BOOL)self.levelControl.selectedSegmentIndex;

View File

@@ -18,6 +18,7 @@
#import "MPNavigationController.h"
#import "MPWebViewController.h"
#import "MPiOSAppDelegate.h"
@implementation MPNavigationController
@@ -29,6 +30,16 @@
[self performSegueWithIdentifier:@"setup" sender:self];
}
- (void)performSegueWithIdentifier:(NSString *)identifier sender:(id)sender {
if ([identifier isEqualToString:@"web"] && [[(NSURL *)sender scheme] isEqualToString:@"masterpassword"]) {
[[MPiOSAppDelegate get] openURL:sender];
return;
}
[super performSegueWithIdentifier:identifier sender:sender];
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:@"web"])

View File

@@ -52,6 +52,17 @@
[self reload];
}
#pragma clang diagnostic push
#pragma ide diagnostic ignored "UnavailableInDeploymentTarget"
- (void)viewSafeAreaInsetsDidChange {
[super viewSafeAreaInsetsDidChange];
self.tableView.contentInset = UIEdgeInsetsMake( 44, 0, 0, 0 );
self.tableView.scrollIndicatorInsets = UIEdgeInsetsMake( 44, 0, 0, 0 );
}
#pragma clang diagnostic pop
- (void)reload {
MPUserEntity *activeUser = [[MPiOSAppDelegate get] activeUserForMainThread];

View File

@@ -512,132 +512,140 @@
- (void)updateAnimated:(BOOL)animated {
Weakify( self );
if (![NSThread isMainThread]) {
PearlMainQueueOperation( ^{
Strongify( self );
[self updateAnimated:animated];
} );
return;
}
[UIView animateWithDuration:animated? .3f: 0 animations:^{
MPSiteEntity *mainSite = [self siteInContext:[MPiOSAppDelegate managedObjectContextForMainThreadIfReady]];
if (animated)
[UIView animateWithDuration:.3f animations:^{
[self updateWasAnimated:animated];
}];
else
[self updateWasAnimated:animated];
}
// UI
//self.backgroundColor = mainSite.url? [UIColor greenColor]: [UIColor redColor];
self.upgradeButton.gone = !mainSite.requiresExplicitMigration && ![[MPiOSConfig get].allowDowngrade boolValue];
self.answersButton.gone = ![[MPiOSAppDelegate get] isFeatureUnlocked:MPProductGenerateAnswers];
BOOL settingsMode = self.mode == MPPasswordCellModeSettings;
self.loginNameContainer.visible = settingsMode || mainSite.loginGenerated || [mainSite.loginName length];
self.modeButton.visible = !self.transientSite;
self.modeButton.alpha = settingsMode? 0.5f: 0.1f;
self.counterLabel.visible = self.counterButton.visible = mainSite.type & MPResultTypeClassTemplate;
self.modeButton.selected = settingsMode;
self.strengthLabel.gone = !settingsMode;
self.modeScrollView.scrollEnabled = !self.transientSite;
[self.modeScrollView setContentOffset:CGPointMake( self.mode * self.modeScrollView.frame.size.width, 0 ) animated:animated];
if (!settingsMode) {
[self.loginNameField resignFirstResponder];
[self.passwordField resignFirstResponder];
}
if ([[MPiOSAppDelegate get] isFeatureUnlocked:MPProductGenerateLogins])
self.loginNameHint.text = @"Tap here to ⚙ generate username or the pencil to type one";
else
self.loginNameHint.text = @"Tap the pencil to type a username";
- (void)updateWasAnimated:(BOOL)animated {
// Site Name
[self updateSiteName:mainSite];
Weakify( self );
MPSiteEntity *mainSite = [self siteInContext:[MPiOSAppDelegate managedObjectContextForMainThreadIfReady]];
// Site Counter
if ([mainSite isKindOfClass:[MPGeneratedSiteEntity class]])
self.counterLabel.text = strf( @"%lu", (unsigned long)((MPGeneratedSiteEntity *)mainSite).counter );
// UI
//self.backgroundColor = mainSite.url? [UIColor greenColor]: [UIColor redColor];
self.upgradeButton.gone = !mainSite.requiresExplicitMigration && ![[MPiOSConfig get].allowDowngrade boolValue];
self.answersButton.gone = ![[MPiOSAppDelegate get] isFeatureUnlocked:MPProductGenerateAnswers];
BOOL settingsMode = self.mode == MPPasswordCellModeSettings;
self.loginNameContainer.visible = settingsMode || mainSite.loginGenerated || [mainSite.loginName length];
self.modeButton.visible = !self.transientSite;
self.modeButton.alpha = settingsMode? 0.5f: 0.1f;
self.counterLabel.visible = self.counterButton.visible = mainSite.type & MPResultTypeClassTemplate;
self.modeButton.selected = settingsMode;
self.strengthLabel.gone = !settingsMode;
self.modeScrollView.scrollEnabled = !self.transientSite;
[self.modeScrollView setContentOffset:CGPointMake( self.mode * self.modeScrollView.frame.size.width, 0 ) animated:animated];
if (!settingsMode) {
[self.loginNameField resignFirstResponder];
[self.passwordField resignFirstResponder];
}
if ([[MPiOSAppDelegate get] isFeatureUnlocked:MPProductGenerateLogins])
self.loginNameHint.text = @"Tap here to ⚙ generate username or the pencil to type one";
else
self.loginNameHint.text = @"Tap the pencil to type a username";
// Site Login Name
self.loginNameField.enabled = self.passwordField.enabled = //
[self.loginNameField isFirstResponder] || [self.passwordField isFirstResponder];
// Site Name
[self updateSiteName:mainSite];
// Site Password
self.passwordField.secureTextEntry = [[MPiOSConfig get].hidePasswords boolValue];
self.passwordField.attributedPlaceholder = stra(
mainSite.type & MPResultTypeClassStateful? strl( @"No password" ):
mainSite.type & MPResultTypeClassTemplate? strl( @"..." ): @"", @{
NSForegroundColorAttributeName: [UIColor whiteColor]
} );
// Site Counter
if ([mainSite isKindOfClass:[MPGeneratedSiteEntity class]])
self.counterLabel.text = strf( @"%lu", (unsigned long)((MPGeneratedSiteEntity *)mainSite).counter );
// Calculate Fields
if (![MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
MPKey *key = [MPiOSAppDelegate get].key;
if (!key) {
wrn( @"Could not load cell content: key unavailable." );
PearlMainQueueOperation( ^{
Strongify( self );
[self updateAnimated:YES];
} );
return;
}
// Site Login Name
self.loginNameField.enabled = self.passwordField.enabled = //
[self.loginNameField isFirstResponder] || [self.passwordField isFirstResponder];
MPSiteEntity *site = [self siteInContext:context];
BOOL loginGenerated = site.loginGenerated;
NSString *password = nil, *loginName = [site resolveLoginUsingKey:key];
MPResultType transientType = [[MPiOSAppDelegate get] activeUserInContext:context].defaultType?: MPAlgorithmDefault.defaultType;
if (self.transientSite && transientType & MPResultTypeClassTemplate)
password = [MPAlgorithmDefault mpwTemplateForSiteNamed:self.transientSite ofType:transientType
withCounter:1 usingKey:key];
else if (site)
password = [site resolvePasswordUsingKey:key];
TimeToCrack timeToCrack;
NSString *timeToCrackString = nil;
id<MPAlgorithm> algorithm = site.algorithm?: MPAlgorithmDefault;
MPAttacker attackHardware = [[MPConfig get].siteAttacker integerValue];
if ([algorithm timeToCrack:&timeToCrack passwordOfType:site.type byAttacker:attackHardware] ||
[algorithm timeToCrack:&timeToCrack passwordString:password byAttacker:attackHardware])
timeToCrackString = NSStringFromTimeToCrack( timeToCrack );
BOOL requiresExplicitMigration = site.requiresExplicitMigration;
PearlMainQueue( ^{
self.passwordField.text = password;
self.strengthLabel.text = timeToCrackString;
self.loginNameGenerated.hidden = !loginGenerated;
self.loginNameField.attributedText =
strarm( stra( loginName?: @"", self.siteNameLabel.textAttributes ), NSParagraphStyleAttributeName, nil );
self.loginNameHint.hidden = [loginName length] || self.loginNameField.enabled;
if (![password length]) {
self.indicatorView.hidden = NO;
[self.indicatorView removeFromSuperview];
[self.modeScrollView addSubview:self.indicatorView];
[self.contentView addConstraintsWithVisualFormat:@"V:[indicator][target]" options:NSLayoutFormatAlignAllCenterX
metrics:nil views:@{
@"indicator": self.indicatorView,
@"target" : settingsMode? self.editButton: self.modeButton
}];
}
else if (requiresExplicitMigration) {
self.indicatorView.hidden = NO;
[self.indicatorView removeFromSuperview];
[self.modeScrollView addSubview:self.indicatorView];
[self.contentView addConstraintsWithVisualFormat:@"V:[indicator][target]" options:NSLayoutFormatAlignAllCenterX
metrics:nil views:@{
@"indicator": self.indicatorView,
@"target" : settingsMode? self.upgradeButton: self.modeButton
}];
}
else
self.indicatorView.hidden = YES;
// Site Password
self.passwordField.secureTextEntry = [[MPiOSConfig get].hidePasswords boolValue];
self.passwordField.attributedPlaceholder = stra(
mainSite.type & MPResultTypeClassStateful? strl( @"No password" ):
mainSite.type & MPResultTypeClassTemplate? strl( @"..." ): @"", @{
NSForegroundColorAttributeName: [UIColor whiteColor]
} );
}]) {
wrn( @"Could not load cell content: store unavailable." );
// Calculate Fields
if (![MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
MPKey *key = [MPiOSAppDelegate get].key;
if (!key) {
wrn( @"Could not load cell content: key unavailable." );
PearlMainQueueOperation( ^{
Strongify( self );
[self updateAnimated:YES];
[self updateAnimated:animated];
} );
return;
}
MPSiteEntity *site = [self siteInContext:context];
BOOL loginGenerated = site.loginGenerated;
NSString *password = nil, *loginName = [site resolveLoginUsingKey:key];
MPResultType transientType = [[MPiOSAppDelegate get] activeUserInContext:context].defaultType?: MPAlgorithmDefault.defaultType;
if (self.transientSite && transientType & MPResultTypeClassTemplate)
password = [MPAlgorithmDefault mpwTemplateForSiteNamed:self.transientSite ofType:transientType
withCounter:1 usingKey:key];
else if (site)
password = [site resolvePasswordUsingKey:key];
TimeToCrack timeToCrack;
NSString *timeToCrackString = nil;
id<MPAlgorithm> algorithm = site.algorithm?: MPAlgorithmDefault;
MPAttacker attackHardware = [[MPConfig get].siteAttacker integerValue];
if ([algorithm timeToCrack:&timeToCrack passwordOfType:site.type byAttacker:attackHardware] ||
[algorithm timeToCrack:&timeToCrack passwordString:password byAttacker:attackHardware])
timeToCrackString = NSStringFromTimeToCrack( timeToCrack );
BOOL requiresExplicitMigration = site.requiresExplicitMigration;
PearlMainQueue( ^{
self.passwordField.text = password;
self.strengthLabel.text = timeToCrackString;
self.loginNameGenerated.hidden = !loginGenerated;
self.loginNameField.attributedText =
strarm( stra( loginName?: @"", self.siteNameLabel.textAttributes ), NSParagraphStyleAttributeName, nil );
self.loginNameHint.hidden = [loginName length] || self.loginNameField.enabled;
if (![password length]) {
self.indicatorView.hidden = NO;
[self.indicatorView removeFromSuperview];
[self.modeScrollView addSubview:self.indicatorView];
[self.contentView addConstraintsWithVisualFormat:@"V:[indicator][target]" options:NSLayoutFormatAlignAllCenterX
metrics:nil views:@{
@"indicator": self.indicatorView,
@"target" : settingsMode? self.editButton: self.modeButton
}];
}
else if (requiresExplicitMigration) {
self.indicatorView.hidden = NO;
[self.indicatorView removeFromSuperview];
[self.modeScrollView addSubview:self.indicatorView];
[self.contentView addConstraintsWithVisualFormat:@"V:[indicator][target]" options:NSLayoutFormatAlignAllCenterX
metrics:nil views:@{
@"indicator": self.indicatorView,
@"target" : settingsMode? self.upgradeButton: self.modeButton
}];
}
else
self.indicatorView.hidden = YES;
} );
}]) {
wrn( @"Could not load cell content: store unavailable." );
PearlMainQueueOperation( ^{
Strongify( self );
[self updateAnimated:animated];
} );
}
if (animated)
[self.contentView layoutIfNeeded];
}];
}
- (void)updateSiteName:(MPSiteEntity *)site {

View File

@@ -33,9 +33,8 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
MPPasswordsBadNameTip = 1 << 0,
};
@interface MPSitesViewController()<NSFetchedResultsControllerDelegate, SKStoreProductViewControllerDelegate>
@interface MPSitesViewController()<NSFetchedResultsControllerDelegate>
@property(nonatomic, strong) SKStoreProductViewController *voltoViewController;
@property(nonatomic, strong) NSFetchedResultsController *fetchedResultsController;
@property(nonatomic, strong) NSArray *fuzzyGroups;
@property(nonatomic, strong) NSCharacterSet *siteNameAcceptableCharactersSet;
@@ -64,13 +63,7 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
self.collectionView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
[self.collectionView automaticallyAdjustInsetsForKeyboard];
self.searchBar.autocapitalizationType = UITextAutocapitalizationTypeNone;
if ([self.searchBar respondsToSelector:@selector( keyboardAppearance )])
self.searchBar.keyboardAppearance = UIKeyboardAppearanceDark;
else
[self.searchBar enumerateViews:^(UIView *subview, BOOL *stop, BOOL *recurse) {
if ([subview isKindOfClass:[UITextField class]])
((UITextField *)subview).keyboardAppearance = UIKeyboardAppearanceDark;
} recurse:YES];
self.searchBar.keyboardAppearance = UIKeyboardAppearanceDark;
}
- (void)viewWillAppear:(BOOL)animated {
@@ -127,6 +120,14 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
PearlRemoveNotificationObservers();
}
- (void)viewWillLayoutSubviews {
self.collectionView.contentInset = [self.collectionView occludedInsets];
self.collectionView.scrollIndicatorInsets = self.collectionView.contentInset;
[super viewWillLayoutSubviews];
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:@"popdown"])
@@ -144,33 +145,6 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
[super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
}
#pragma mark - UICollectionViewDelegateFlowLayout
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout
sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewFlowLayout *layout = (UICollectionViewFlowLayout *)collectionViewLayout;
CGFloat itemWidth = UIEdgeInsetsInsetRect( collectionView.bounds, layout.sectionInset ).size.width;
return CGSizeMake( itemWidth, 100 );
}
- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout
insetForSectionAtIndex:(NSInteger)section {
UICollectionViewFlowLayout *layout = (UICollectionViewFlowLayout *)collectionViewLayout;
UIEdgeInsets occludedInsets = [self.collectionView occludedInsets];
UIEdgeInsets insets = layout.sectionInset;
insets.top = insets.bottom; // Undo storyboard hack for manual top-occluded insets.
if (section == 0)
insets.top += occludedInsets.top;
if (section == collectionView.numberOfSections - 1)
insets.bottom += occludedInsets.bottom;
return insets;
}
#pragma mark - UICollectionViewDataSource
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
@@ -460,13 +434,6 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
} completion:completion];
}
#pragma mark - SKStoreProductViewControllerDelegate
- (void)productViewControllerDidFinish:(SKStoreProductViewController *)viewController {
[viewController dismissViewControllerAnimated:YES completion:nil];
}
#pragma mark - Actions
- (IBAction)dismissPopdown:(id)sender {
@@ -479,45 +446,7 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
- (IBAction)upgradeVolto:(UIButton *)sender {
if ([UIApp canOpenURL:[[NSURL alloc] initWithString:@"volto:"]]) {
[[MPiOSAppDelegate get] exportSitesRevealPasswords:NO askExportPassword:^NSString *(NSString *userName) {
return PearlAwait( ^(void (^setResult)(id)) {
PearlMainQueue( ^{
UIAlertController *alert = [UIAlertController alertControllerWithTitle:strf( @"Master Password For:\n%@", userName )
message:@"Enter your master password to export the user."
preferredStyle:UIAlertControllerStyleAlert];
[alert addTextFieldWithConfigurationHandler:^(UITextField *textField) {
textField.secureTextEntry = YES;
}];
[alert addAction:[UIAlertAction actionWithTitle:@"Export" style:UIAlertActionStyleDefault handler:
^(UIAlertAction *action) { setResult( alert.textFields.firstObject.text ); }]];
[alert addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:
^(UIAlertAction *action) { setResult( nil ); }]];
[self.navigationController presentViewController:alert animated:YES completion:nil];
} );
} );
} result:^(NSString *exportedUser, NSError *error) {
if (!exportedUser || error) {
MPError( error, @"Failed to export user." );
PearlMainQueue( ^{
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Export Error"
message:[error localizedDescription]
preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:@"Okay" style:UIAlertActionStyleCancel handler:nil]];
[self.navigationController presentViewController:alert animated:YES completion:nil];
} );
return;
}
NSURLComponents *components = [NSURLComponents new];
components.scheme = @"volto";
components.path = @"import";
components.queryItems = @[ [[NSURLQueryItem alloc] initWithName:@"data" value:exportedUser] ];
[UIApp openURL:components.URL];
}];
}
else if (self.voltoViewController)
[self presentViewController:self.voltoViewController animated:YES completion:nil];
[[MPiOSAppDelegate get] migrateFor:[MPiOSAppDelegate get].activeUserForMainThread];
}
#pragma mark - Private
@@ -530,23 +459,8 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
self.voltoMigrateAlert.visible = YES;
}
else {
self.voltoInstallAlert.visible = NO;
self.voltoInstallAlert.visible = [MPiOSAppDelegate get].voltoViewController != nil;
self.voltoMigrateAlert.visible = NO;
self.voltoViewController = [SKStoreProductViewController new];
self.voltoViewController.delegate = self;
[self.voltoViewController loadProductWithParameters:@{
SKStoreProductParameterCampaignToken : @"app-masterpassword.ios", /* Campaign: From MasterPassword iOS */
SKStoreProductParameterProviderToken : @153897, /* Provider: Maarten Billemont */
SKStoreProductParameterITunesItemIdentifier: @510296984, /* Application: MasterPassword iOS */
//SKStoreProductParameterITunesItemIdentifier: @1500430196, /* Application: Volto iOS */
} completionBlock:^(BOOL result, NSError *error) {
if (error)
err( @"Failed loading Volto product information: %@", error );
[UIView animateWithDuration:0.3f animations:^{
self.voltoInstallAlert.visible = result;
}];
}];
}
}

View File

@@ -64,6 +64,17 @@ PearlEnum( MPDevelopmentFuelConsumption,
[[MPiOSAppDelegate get] removeProductsObserver:self];
}
#pragma clang diagnostic push
#pragma ide diagnostic ignored "UnavailableInDeploymentTarget"
- (void)viewSafeAreaInsetsDidChange {
[super viewSafeAreaInsetsDidChange];
self.tableView.contentInset = UIEdgeInsetsMake( 44, 0, 0, 0 );
self.tableView.scrollIndicatorInsets = UIEdgeInsetsMake( 44, 0, 0, 0 );
}
#pragma clang diagnostic pop
#pragma mark - UITableViewDataSource
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {

View File

@@ -231,12 +231,10 @@ typedef NS_ENUM( NSUInteger, MPActiveUserState ) {
user.avatar = newUserAvatar;
user.name = newUserName;
if ([[MPConfig get].sendInfo boolValue]) {
[Countly.sharedInstance recordEvent:@"new-user" segmentation:@{
@"algorithm": @(user.algorithm.version).description,
@"avatar" : @(user.avatar).description,
}];
}
[Countly.sharedInstance recordEvent:@"new-user" segmentation:@{
@"algorithm": @(user.algorithm.version).description,
@"avatar" : @(user.avatar).description,
}];
}
BOOL signedIn = [[MPiOSAppDelegate get] signInAsUser:user saveInContext:context

View File

@@ -17,10 +17,12 @@
//==============================================================================
#import <Foundation/Foundation.h>
#import <WebKit/WebKit.h>
@interface MPWebViewController : UIViewController<UIWebViewDelegate>
@interface MPWebViewController : UIViewController<WKNavigationDelegate>
@property(nonatomic) IBOutlet UIWebView *webView;
@property(nonatomic) IBOutlet WKWebView *webView;
@property(nonatomic) IBOutlet UINavigationBar *webNavigationBar;
@property(nonatomic) IBOutlet UINavigationItem *webNavigationItem;
@property(nonatomic) NSURL *initialURL;

View File

@@ -17,6 +17,7 @@
//==============================================================================
#import "MPWebViewController.h"
#import "MPiOSAppDelegate.h"
@implementation MPWebViewController
@@ -24,20 +25,30 @@
[super viewDidLoad];
[self.webView.scrollView insetOcclusion];
if (!self.initialURL)
self.initialURL = [NSURL URLWithString:@"https://masterpassword.app"];
self.webNavigationItem.title = self.initialURL.host;
// WKWebView can't be on the storyboard for iOS pre 11 due to an NSCoding bug.
[self.view insertSubview:self.webView = [WKWebView new] atIndex:0];
[self.webView setNavigationDelegate:self];
[self.webView setTranslatesAutoresizingMaskIntoConstraints:NO];
[self.webView.topAnchor constraintEqualToAnchor:self.view.topAnchor].active = YES;
[self.webView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor].active = YES;
[self.webView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor].active = YES;
[self.webView.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor].active = YES;
[self.webView loadRequest:[[NSURLRequest alloc] initWithURL:self.initialURL]];
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
}
if (!self.initialURL)
self.initialURL = [NSURL URLWithString:@"https://masterpassword.app"];
self.webNavigationItem.title = self.initialURL.host;
self.webView.visible = NO;
[self.webView loadRequest:[[NSURLRequest alloc] initWithURL:self.initialURL]];
- (void)viewDidLayoutSubviews {
[self.webView.scrollView insetOcclusion];
[super viewDidLayoutSubviews];
}
- (UIStatusBarStyle)preferredStatusBarStyle {
@@ -45,26 +56,24 @@
return UIStatusBarStyleLightContent;
}
#pragma mark - UIWebViewDelegate
#pragma mark - WKNavigationDelegate
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request
navigationType:(UIWebViewNavigationType)navigationType {
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction
decisionHandler:(void ( ^ )(WKNavigationActionPolicy))decisionHandler {
if ([[request.URL absoluteString] rangeOfString:@"thanks.lhunath.com"].location != NSNotFound) {
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"tipped.thanks"];
if (![[NSUserDefaults standardUserDefaults] synchronize])
wrn( @"Couldn't synchronize thanks tip." );
if ([navigationAction.request.mainDocumentURL.scheme isEqualToString:@"masterpassword"]) {
[[MPiOSAppDelegate get] openURL:navigationAction.request.mainDocumentURL];
decisionHandler( WKNavigationActionPolicyCancel );
return;
}
if ([request.URL isEqual:request.mainDocumentURL]) {
self.webNavigationItem.title = request.URL.host;
self.webNavigationItem.prompt = strl( @"Loading" );
}
return YES;
decisionHandler( WKNavigationActionPolicyAllow );
}
- (void)webViewDidStartLoad:(UIWebView *)webView {
- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(null_unspecified WKNavigation *)navigation {
self.webNavigationItem.title = webView.URL.host;
self.webNavigationItem.prompt = strl( @"Loading" );
UIActivityIndicatorView *activityView =
[[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite];
@@ -72,19 +81,56 @@
[activityView startAnimating];
}
- (void)webViewDidFinishLoad:(UIWebView *)webView {
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation {
[UIView animateWithDuration:0.3 animations:^{
self.webView.visible = YES;
if ([[webView.URL absoluteString] rangeOfString:@"thanks.lhunath.com"].location != NSNotFound) {
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"tipped.thanks"];
if (![[NSUserDefaults standardUserDefaults] synchronize])
wrn( @"Couldn't synchronize thanks tip." );
}
[self.webNavigationItem setLeftBarButtonItem:[[UIBarButtonItem alloc]
initWithBarButtonSystemItem:UIBarButtonSystemItemAction target:self action:@selector( action: )]];
[webView evaluateJavaScript:@"document.title" completionHandler:^(id o, NSError *error) {
self.webNavigationItem.prompt = [o description];
}];
[self.webNavigationItem setLeftBarButtonItem:[webView canGoBack]? [[UIBarButtonItem alloc]
initWithTitle:@"⬅︎" style:UIBarButtonItemStylePlain target:webView action:@selector( goBack )]: nil];
self.webNavigationItem.prompt = [webView stringByEvaluatingJavaScriptFromString:@"document.title"];
}
#pragma mark - Actions
- (IBAction)action:(id)sender {
UIAlertController *controller = [UIAlertController new];
controller.title = self.webView.URL.host;
controller.message = self.webView.URL.absoluteString;
[controller addAction:[UIAlertAction actionWithTitle:@"Safari" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
[UIApp openURL:self.webView.URL];
}]];
if ([UIApp canOpenURL:[NSURL URLWithString:@"firefox:"]]) {
[controller addAction:[UIAlertAction actionWithTitle:@"Firefox" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
[UIApp openURL:[NSURL URLWithString:strf( @"firefox://open-url?url=%@",
[self.webView.URL.absoluteString stringByAddingPercentEncodingWithAllowedCharacters:
[NSCharacterSet URLQueryAllowedCharacterSet]] )]];
}]];
}
if ([UIApp canOpenURL:[NSURL URLWithString:@"googlechrome:"]]) {
NSURL *url = [[NSURL alloc] initWithScheme:[self.webView.URL.scheme isEqualToString:@"http"]? @"googlechrome": @"googlechromes"
host:self.webView.URL.host path:self.webView.URL.path];
[controller addAction:[UIAlertAction actionWithTitle:@"Chrome" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
[UIApp openURL:url];
}]];
}
if ([UIApp canOpenURL:[NSURL URLWithString:@"opera-http:"]]) {
NSURL *url = [[NSURL alloc] initWithScheme:[self.webView.URL.scheme isEqualToString:@"http"]? @"opera-http": @"opera-https"
host:self.webView.URL.host path:self.webView.URL.path];
[controller addAction:[UIAlertAction actionWithTitle:@"Opera" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
[UIApp openURL:url];
}]];
}
[controller addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:nil]];
[self presentViewController:controller animated:YES completion:nil];
}
- (IBAction)done:(id)sender {
[self dismissViewControllerAnimated:YES completion:nil];

View File

@@ -17,15 +17,22 @@
//==============================================================================
#import <UIKit/UIKit.h>
#import <StoreKit/StoreKit.h>
#import "MPAppDelegate_Shared.h"
@interface MPiOSAppDelegate : MPAppDelegate_Shared
@interface MPiOSAppDelegate : MPAppDelegate_Shared <SKStoreProductViewControllerDelegate>
@property(nonatomic, strong) SKStoreProductViewController *voltoViewController;
- (void)openURL:(NSURL *)url;
- (void)showFeedbackWithLogs:(BOOL)logs forVC:(UIViewController *)viewController;
- (void)openFeedbackWithLogs:(BOOL)logs forVC:(UIViewController *)viewController;
- (void)showExportForVC:(UIViewController *)viewController;
- (void)migrateFor:(MPUserEntity *)user;
- (void)changeMasterPasswordFor:(MPUserEntity *)user saveInContext:(NSManagedObjectContext *)moc didResetBlock:(void ( ^ )(void))didReset;
@end

View File

@@ -26,11 +26,24 @@
#import <Sentry/Sentry.h>
#import <Countly/Countly.h>
@interface CountlyPushNotifications
@end
@interface CountlyPushNotifications(MPNotifications)
@end
@implementation CountlyPushNotifications(MPNotifications)
- (void)openURL:(NSString *)URLString {
[[MPiOSAppDelegate get].navigationController performSegueWithIdentifier:@"web" sender:[NSURL URLWithString:URLString]];
}
@end
@interface MPiOSAppDelegate()<UIDocumentInteractionControllerDelegate>
@property(nonatomic, strong) UIDocumentInteractionController *interactionController;
@property(nonatomic, strong) PearlHangDetector *hangDetector;
@end
@implementation MPiOSAppDelegate
@@ -45,18 +58,19 @@
@try {
// Sentry
[SentrySDK initWithOptions:@{
@"dsn" : decrypt( sentryDSN ),
@"dsn" : NilToNSNull( decrypt( sentryDSN ) ),
#ifdef DEBUG
@"debug" : @(YES),
@"environment": @"Development",
@"debug" : @(YES),
@"environment" : @"Development",
#elif PUBLIC
@"debug" : @(NO),
@"environment": @"Public",
@"debug" : @(NO),
@"environment" : @"Public",
#else
@"debug" : @(NO),
@"environment": @"Private",
@"debug" : @(NO),
@"environment" : @"Private",
#endif
@"enabled" : [MPiOSConfig get].sendInfo,
@"enabled" : [MPiOSConfig get].sendInfo,
@"enableAutoSessionTracking": @(YES),
}];
[[PearlLogger get] registerListener:^BOOL(PearlLogMessage *message) {
PearlLogLevel level = PearlLogLevelWarn;
@@ -102,15 +116,16 @@
countlyConfig.appKey = decrypt( countlyKey );
countlyConfig.features = @[ CLYPushNotifications, CLYAutoViewTracking ];
countlyConfig.requiresConsent = YES;
#if DEBUG
countlyConfig.pushTestMode = CLYPushTestModeDevelopment;
#elif ! PUBLIC
countlyConfig.pushTestMode = CLYPushTestModeTestFlightOrAdHoc;
#endif
countlyConfig.alwaysUsePOST = YES;
countlyConfig.deviceID = [PearlKeyChain deviceIdentifier];
countlyConfig.secretSalt = decrypt( countlySalt );
#if DEBUG
countlyConfig.enableDebug = YES;
countlyConfig.pushTestMode = CLYPushTestModeDevelopment;
#elif ! PUBLIC
countlyConfig.enableDebug = NO;
countlyConfig.pushTestMode = CLYPushTestModeTestFlightOrAdHoc;
#endif
[Countly.sharedInstance startWithConfig:countlyConfig];
#if ! DEBUG
@@ -171,30 +186,30 @@
}
} );
if (@available( iOS 12, * )) {
[Countly.sharedInstance askForNotificationPermissionWithOptions:UNAuthorizationOptionProvisional completionHandler:
^(BOOL granted, NSError *error) {
inf( @"provisional: %d: %@", granted, error );
}];
}
SKStoreProductViewController *migrateVC = [SKStoreProductViewController new];
[migrateVC loadProductWithParameters:@{
SKStoreProductParameterCampaignToken : @"app-masterpassword.ios", /* Campaign: From MasterPassword iOS */
SKStoreProductParameterProviderToken : @153897, /* Provider: Maarten Billemont */
SKStoreProductParameterITunesItemIdentifier: @510296984, /* Application: MasterPassword iOS */
//SKStoreProductParameterITunesItemIdentifier: @1500430196, /* Application: Volto iOS */
} completionBlock:^(BOOL result, NSError *error) {
if (error)
err( @"Failed loading Volto product information: %@", error );
if (result) {
self.voltoViewController = migrateVC;
self.voltoViewController.delegate = self;
} else {
self.voltoViewController = nil;
}
}];
PearlMainQueueOperation( ^{
[self.navigationController performSegueWithIdentifier:@"web" sender:[NSURL URLWithString:@"masterpassword://foo?bar=quux"]];
if ([[MPiOSConfig get].showSetup boolValue])
[self.navigationController performSegueWithIdentifier:@"setup" sender:self];
if (![[NSUserDefaults standardUserDefaults] boolForKey:@"notificationsDecided"]) {
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Coming Soon" message:
@"Master Password is rolling out a new modern personal security platform and we're excited to bring you along.\n\n"
@"When it's time, we'll send you a notification to help you make an effortless transition."
preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:@"Thanks" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
[Countly.sharedInstance askForNotificationPermission];
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"notificationsDecided"];
}]];
[(self.navigationController.presentedViewController?: (UIViewController *)self.navigationController)
presentViewController:alert animated:YES completion:nil];
}
[self consentFeatures];
} );
}
@catch (id exception) {
@@ -211,6 +226,12 @@
if (!url)
return NO;
// masterpassword: URLs.
if ([url.scheme isEqualToString:@"masterpassword"]) {
[self openURL:url];
return YES;
}
// Arbitrary URL to mpsites data.
[[[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:
^(NSData *importedSitesData, NSURLResponse *response, NSError *error) {
@@ -247,6 +268,86 @@
return YES;
}
- (void)consentFeatures {
if ([self askDiagnostics])
return;
[self tryNotifications];
}
- (BOOL)askDiagnostics {
if ([[MPiOSConfig get].sendInfoDecided boolValue])
return NO;
PearlMainQueue( ^{
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Diagnostics" message:
@"We look for bugs, sudden crashes, runtime issues & statistics.\n\n"
@"Diagnostics are scrubbed and personal details will never leave your device."
preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:@"Disable" style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) {
[MPiOSConfig get].sendInfo = @(NO);
[MPiOSConfig get].sendInfoDecided = @(YES);
[self consentFeatures];
}]];
[alert addAction:[UIAlertAction actionWithTitle:@"Engage" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
[MPiOSConfig get].sendInfo = @(YES);
[MPiOSConfig get].sendInfoDecided = @(YES);
[self consentFeatures];
}]];
[(self.navigationController.presentedViewController?: (UIViewController *)self.navigationController)
presentViewController:alert animated:YES completion:nil];
} );
return YES;
}
- (void)tryNotifications {
[Countly.sharedInstance giveConsentForFeature:CLYConsentPushNotifications];
if (@available( iOS 12, * )) {
[Countly.sharedInstance askForNotificationPermissionWithOptions:UNAuthorizationOptionProvisional | UNAuthorizationOptionAlert
completionHandler:^(BOOL granted, NSError *error) {
if (!granted)
err( @"No provisional notification permission: %@", error );
[self askNotifications];
}];
return;
}
[self askNotifications];
}
- (void)askNotifications {
if ([[MPiOSConfig get].notificationsDecided boolValue])
return;
PearlMainQueue( ^{
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Coming Soon" message:
@"Master Password is rolling out a brand new, updated version and we're excited to bring you along.\n\n"
@"When it's time, we'll send you a notification to help you make an effortless transition."
preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:@"Thanks" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
if (@available( iOS 12, * )) {
[Countly.sharedInstance askForNotificationPermissionWithOptions:UNAuthorizationOptionAlert completionHandler:
^(BOOL granted, NSError *error) {
[MPiOSConfig get].notificationsDecided = @(YES);
}];
}
else {
[Countly.sharedInstance askForNotificationPermission];
[MPiOSConfig get].notificationsDecided = @(YES);
}
}]];
[(self.navigationController.presentedViewController?: (UIViewController *)self.navigationController)
presentViewController:alert animated:YES completion:nil];
} );
}
- (void)importSites:(NSString *)importData {
if ([NSThread isMainThread]) {
@@ -362,49 +463,47 @@
[self.hangDetector stop];
// self.task = [application beginBackgroundTaskWithExpirationHandler:^{
// [application endBackgroundTask:self.task];
// dbg( @"background expiring" );
// }];
// PearlNotMainQueueOperation( ^{
// NSString *pbstring = [UIPasteboard generalPasteboard].string;
// while (YES) {
// NSString *newString = [UIPasteboard generalPasteboard].string;
// if (![newString isEqualToString:pbstring]) {
// dbg( @"pasteboard changed to: %@", newString );
// pbstring = newString;
// NSURL *url = [NSURL URLWithString:pbstring];
// if (url) {
// NSString *siteName = [url host];
// }
// MPKey *key = [MPiOSAppDelegate get].key;
// if (key)
// [MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
// NSFetchRequest<MPSiteEntity *>
// *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPSiteEntity class] )];
// fetchRequest.sortDescriptors = @[
// [[NSSortDescriptor alloc] initWithKey:NSStringFromSelector( @selector( lastUsed ) ) ascending:NO]
// ];
// fetchRequest.fetchBatchSize = 2;
// fetchRequest.predicate = [NSPredicate predicateWithFormat:@"(name LIKE[cd] %@) AND user == %@", siteName,
// [[MPiOSAppDelegate get] activeUserOID]];
// NSError *error = nil;
// NSArray<MPSiteEntity *> *results = [fetchRequest execute:&error];
// dbg( @"site search, error: %@, results:\n%@", error, results );
// if ([results count]) {
// [UIPasteboard generalPasteboard].string = [[results firstObject] resolvePasswordUsingKey:key];
// }
// }];
// }
// [NSThread sleepForTimeInterval:5];
// }
// } );
[super applicationDidEnterBackground:application];
}
#pragma mark - Behavior
- (void)openURL:(NSURL *)url {
if ([url.scheme isEqualToString:@"masterpassword"]) {
if ([url.host isEqualToString:@"open-url"]) {
for (NSURLQueryItem *item in [NSURLComponents componentsWithString:[url absoluteString]].queryItems)
if ([item.name isEqualToString:@"url"]) {
[UIApp openURL:[NSURL URLWithString:item.value]];
return;
}
}
else if ([url.host isEqualToString:@"show-url"]) {
for (NSURLQueryItem *item in [NSURLComponents componentsWithString:[url absoluteString]].queryItems)
if ([item.name isEqualToString:@"url"]) {
[[MPiOSAppDelegate get].navigationController performSegueWithIdentifier:@"web" sender:[NSURL URLWithString:item.value]];
return;
}
}
else if ([url.host isEqualToString:@"migrate"]) {
for (NSURLQueryItem *item in [NSURLComponents componentsWithString:[url absoluteString]].queryItems)
if ([item.name isEqualToString:@"fullName"]) {
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
NSFetchRequest
*fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPUserEntity class] )];
fetchRequest.predicate = [NSPredicate predicateWithFormat:@"name == %@", item.value];
NSArray *users = [context executeFetchRequest:fetchRequest error:nil];
[self migrateFor:users.firstObject];
}];
return;
}
[self migrateFor:nil];
return;
}
} else
[UIApp openURL:url];
}
- (void)showFeedbackWithLogs:(BOOL)logs forVC:(UIViewController *)viewController {
if (![PearlEMail canSendMail]) {
@@ -525,89 +624,162 @@
return;
}
[self exportSitesRevealPasswords:revealPasswords askExportPassword:^NSString *(NSString *userName) {
return PearlAwait( ^(void (^setResult)(id)) {
PearlMainQueue( ^{
UIAlertController *alert = [UIAlertController alertControllerWithTitle:strf( @"Master Password For:\n%@", userName )
message:@"Enter your master password to export the user."
preferredStyle:UIAlertControllerStyleAlert];
[alert addTextFieldWithConfigurationHandler:^(UITextField *textField) {
textField.secureTextEntry = YES;
}];
[alert addAction:[UIAlertAction actionWithTitle:@"Export" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
setResult( alert.textFields.firstObject.text );
}]];
[alert addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) {
setResult( nil );
}]];
[self.navigationController presentViewController:alert animated:YES completion:nil];
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
NSError *error = nil;
NSString *exportedUser = [self exportSitesFor:[self activeUserInContext:context] revealPasswords:revealPasswords askExportPassword:^NSString *(NSString *userName) {
return PearlAwait( ^(void (^setResult)(id)) {
PearlMainQueue( ^{
UIAlertController *alert = [UIAlertController alertControllerWithTitle:strf( @"Master Password For:\n%@", userName )
message:@"Enter your master password to export the user."
preferredStyle:UIAlertControllerStyleAlert];
[alert addTextFieldWithConfigurationHandler:^(UITextField *textField) {
textField.secureTextEntry = YES;
}];
[alert addAction:[UIAlertAction actionWithTitle:@"Export" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
setResult( alert.textFields.firstObject.text );
}]];
[alert addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) {
setResult( nil );
}]];
[self.navigationController presentViewController:alert animated:YES completion:nil];
} );
} );
} );
} result:^(NSString *exportedUser, NSError *error) {
if (!exportedUser || error) {
MPError( error, @"Failed to export mpsites." );
PearlMainQueue( ^{
} error:&error];
PearlMainQueue( ^{
if (!exportedUser || error) {
MPError( error, @"Failed to export mpsites." );
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Export Error" message:[error localizedDescription]
preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:@"Okay" style:UIAlertActionStyleCancel handler:nil]];
[self.navigationController presentViewController:alert animated:YES completion:nil];
} );
return;
}
NSDateFormatter *exportDateFormatter = [NSDateFormatter new];
[exportDateFormatter setDateFormat:@"yyyy'-'MM'-'dd"];
NSString *exportFileName = strf( @"%@ (%@).mpsites",
[self activeUserForMainThread].name, [exportDateFormatter stringFromDate:[NSDate date]] );
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Export Destination" message:nil
preferredStyle:UIAlertControllerStyleActionSheet];
[alert addAction:[UIAlertAction actionWithTitle:@"Send As E-Mail" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
NSString *message;
if (revealPasswords)
message = strf( @"Export of Master Password sites with passwords included.\n\n"
@"REMINDER: Make sure nobody else sees this file! Passwords are visible!\n\n\n"
@"--\n"
@"%@\n"
@"Master Password %@, build %@",
[self activeUserForMainThread].name,
[PearlInfoPlist get].CFBundleShortVersionString,
[PearlInfoPlist get].CFBundleVersion );
else
message = strf( @"Backup of Master Password sites.\n\n\n"
@"--\n"
@"%@\n"
@"Master Password %@, build %@",
[self activeUserForMainThread].name,
[PearlInfoPlist get].CFBundleShortVersionString,
[PearlInfoPlist get].CFBundleVersion );
[PearlEMail sendEMailTo:nil fromVC:viewController subject:@"Master Password Export" body:message
attachments:[[PearlEMailAttachment alloc] initWithContent:[exportedUser dataUsingEncoding:NSUTF8StringEncoding]
mimeType:@"text/plain"
fileName:exportFileName], nil];
return;
}]];
[alert addAction:[UIAlertAction actionWithTitle:@"Share / Export" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
NSURL *applicationSupportURL = [[[NSFileManager defaultManager] URLsForDirectory:NSApplicationSupportDirectory
inDomains:NSUserDomainMask] lastObject];
NSURL *exportURL = [[applicationSupportURL
URLByAppendingPathComponent:[NSBundle mainBundle].bundleIdentifier isDirectory:YES]
URLByAppendingPathComponent:exportFileName isDirectory:NO];
NSError *writeError = nil;
if (![[exportedUser dataUsingEncoding:NSUTF8StringEncoding]
writeToURL:exportURL options:NSDataWritingFileProtectionComplete error:&writeError])
MPError( writeError, @"Failed to write export data to URL %@.", exportURL );
else {
self.interactionController = [UIDocumentInteractionController interactionControllerWithURL:exportURL];
self.interactionController.UTI = @"com.lyndir.masterpassword.sites";
self.interactionController.delegate = self;
[self.interactionController presentOpenInMenuFromRect:CGRectZero inView:viewController.view animated:YES];
}
}]];
[alert addAction:[UIAlertAction actionWithTitle:@"Continue" style:UIAlertActionStyleCancel handler:nil]];
[self.navigationController presentViewController:alert animated:YES completion:nil];
} );
}];
}
- (void)migrateFor:(MPUserEntity *)user {
if ([UIApp canOpenURL:[[NSURL alloc] initWithString:@"volto:"]]) {
if (!user) {
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPUserEntity class] )];
NSArray *users = [context executeFetchRequest:fetchRequest error:nil];
if (![users count])
return;
UIAlertController *usersSheet = [UIAlertController alertControllerWithTitle:@"Migrate User"
message:@"Choose a user to migrate out to Volto."
preferredStyle:UIAlertControllerStyleActionSheet];
[usersSheet addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:nil]];
for (MPUserEntity *user_ in users)
[usersSheet addAction:[UIAlertAction actionWithTitle:user_.name style:UIAlertActionStyleDefault handler:
^(UIAlertAction *action) { [self migrateFor:user_]; }]];
PearlMainQueue( ^{
[self.navigationController presentViewController:usersSheet animated:YES completion:nil];
} );
}];
return;
}
NSDateFormatter *exportDateFormatter = [NSDateFormatter new];
[exportDateFormatter setDateFormat:@"yyyy'-'MM'-'dd"];
NSString *exportFileName = strf( @"%@ (%@).mpsites",
[self activeUserForMainThread].name, [exportDateFormatter stringFromDate:[NSDate date]] );
[MPAppDelegate_Shared managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
NSError *error = nil;
NSString *exportedUser = [[MPAppDelegate_Shared get] exportSitesFor:[MPUserEntity existingObjectWithID:user.objectID inContext:context]
revealPasswords:NO askExportPassword:^NSString *(NSString *userName) {
return PearlAwait( ^(void (^setResult)(id)) {
PearlMainQueue( ^{
UIAlertController *alert = [UIAlertController alertControllerWithTitle:strf( @"Master Password For:\n%@", userName )
message:@"Enter your master password to export the user."
preferredStyle:UIAlertControllerStyleAlert];
[alert addTextFieldWithConfigurationHandler:^(UITextField *textField) {
textField.secureTextEntry = YES;
}];
[alert addAction:[UIAlertAction actionWithTitle:@"Export" style:UIAlertActionStyleDefault handler:
^(UIAlertAction *action) { setResult( alert.textFields.firstObject.text ); }]];
[alert addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:
^(UIAlertAction *action) { setResult( nil ); }]];
[self.navigationController presentViewController:alert animated:YES completion:nil];
} );
} );
} error:&error];
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Export Destination" message:nil
preferredStyle:UIAlertControllerStyleActionSheet];
[alert addAction:[UIAlertAction actionWithTitle:@"Send As E-Mail" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
NSString *message;
if (revealPasswords)
message = strf( @"Export of Master Password sites with passwords included.\n\n"
@"REMINDER: Make sure nobody else sees this file! Passwords are visible!\n\n\n"
@"--\n"
@"%@\n"
@"Master Password %@, build %@",
[self activeUserForMainThread].name,
[PearlInfoPlist get].CFBundleShortVersionString,
[PearlInfoPlist get].CFBundleVersion );
else
message = strf( @"Backup of Master Password sites.\n\n\n"
@"--\n"
@"%@\n"
@"Master Password %@, build %@",
[self activeUserForMainThread].name,
[PearlInfoPlist get].CFBundleShortVersionString,
[PearlInfoPlist get].CFBundleVersion );
PearlMainQueue( ^{
if (!exportedUser || error) {
MPError( error, @"Failed to export user." );
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Export Error"
message:[error localizedDescription]
preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:@"Okay" style:UIAlertActionStyleCancel handler:nil]];
[self.navigationController presentViewController:alert animated:YES completion:nil];
return;
}
[PearlEMail sendEMailTo:nil fromVC:viewController subject:@"Master Password Export" body:message
attachments:[[PearlEMailAttachment alloc] initWithContent:[exportedUser dataUsingEncoding:NSUTF8StringEncoding]
mimeType:@"text/plain"
fileName:exportFileName], nil];
return;
}]];
[alert addAction:[UIAlertAction actionWithTitle:@"Share / Export" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
NSURL *applicationSupportURL = [[[NSFileManager defaultManager] URLsForDirectory:NSApplicationSupportDirectory
inDomains:NSUserDomainMask] lastObject];
NSURL *exportURL = [[applicationSupportURL
URLByAppendingPathComponent:[NSBundle mainBundle].bundleIdentifier isDirectory:YES]
URLByAppendingPathComponent:exportFileName isDirectory:NO];
NSError *writeError = nil;
if (![[exportedUser dataUsingEncoding:NSUTF8StringEncoding]
writeToURL:exportURL options:NSDataWritingFileProtectionComplete error:&writeError])
MPError( writeError, @"Failed to write export data to URL %@.", exportURL );
else {
self.interactionController = [UIDocumentInteractionController interactionControllerWithURL:exportURL];
self.interactionController.UTI = @"com.lyndir.masterpassword.sites";
self.interactionController.delegate = self;
[self.interactionController presentOpenInMenuFromRect:CGRectZero inView:viewController.view animated:YES];
}
}]];
[alert addAction:[UIAlertAction actionWithTitle:@"Continue" style:UIAlertActionStyleCancel handler:nil]];
[self.navigationController presentViewController:alert animated:YES completion:nil];
}];
NSURLComponents *components = [NSURLComponents new];
components.scheme = @"volto";
components.path = @"import";
components.queryItems = @[ [[NSURLQueryItem alloc] initWithName:@"data" value:exportedUser] ];
[UIApp openURL:components.URL];
} );
}];
}
else if (self.voltoViewController)
[self.navigationController presentViewController:self.voltoViewController animated:YES completion:nil];
}
- (void)changeMasterPasswordFor:(MPUserEntity *)user saveInContext:(NSManagedObjectContext *)moc didResetBlock:(void ( ^ )(void))didReset {
@@ -635,6 +807,13 @@
} );
}
#pragma mark - SKStoreProductViewControllerDelegate
- (void)productViewControllerDidFinish:(SKStoreProductViewController *)viewController {
[viewController dismissViewControllerAnimated:YES completion:nil];
}
#pragma mark - UIDocumentInteractionControllerDelegate
- (void)documentInteractionController:(UIDocumentInteractionController *)controller didEndSendingToApplication:(NSString *)application {
@@ -655,41 +834,44 @@
[PearlLogger get].historyLevel = [[MPiOSConfig get].traceMode boolValue]? PearlLogLevelTrace: PearlLogLevelInfo;
// Send info
NSArray *countlyFeatures = @[
CLYConsentEvents, CLYConsentUserDetails, CLYConsentCrashReporting, CLYConsentViewTracking, CLYConsentStarRating
];
if ([[MPConfig get].sendInfo boolValue] || ![[MPConfig get].sendInfoDecided boolValue])
[Countly.sharedInstance giveConsentForFeature:CLYConsentSessions];
else
[Countly.sharedInstance cancelConsentForFeature:CLYConsentSessions];
if ([[MPConfig get].sendInfo boolValue]) {
[Countly.sharedInstance giveConsentForAllFeatures];
if ([PearlLogger get].printLevel > PearlLogLevelInfo)
[PearlLogger get].printLevel = PearlLogLevelInfo;
NSMutableDictionary *prefs = [NSMutableDictionary new];
prefs[@"rememberLogin"] = [MPConfig get].rememberLogin;
prefs[@"sendInfo"] = [MPConfig get].sendInfo;
prefs[@"helpHidden"] = [MPiOSConfig get].helpHidden;
prefs[@"showQuickStart"] = [MPiOSConfig get].showSetup;
prefs[@"firstRun"] = [PearlConfig get].firstRun;
prefs[@"launchCount"] = [PearlConfig get].launchCount;
prefs[@"askForReviews"] = [PearlConfig get].askForReviews;
prefs[@"reviewAfterLaunches"] = [PearlConfig get].reviewAfterLaunches;
prefs[@"reviewedVersion"] = [PearlConfig get].reviewedVersion;
prefs[@"simulator"] = @([PearlDeviceUtils isSimulator]);
prefs[@"encrypted"] = @([PearlDeviceUtils isAppEncrypted]);
prefs[@"jailbroken"] = @([PearlDeviceUtils isJailbroken]);
prefs[@"platform"] = [PearlDeviceUtils platform];
#ifdef APPSTORE
prefs[@"reviewedVersion"] = @([PearlDeviceUtils isAppEncrypted]);
#else
prefs[@"reviewedVersion"] = @(YES);
#endif
[SentrySDK.currentHub getClient].options.enabled = @YES;
[SentrySDK configureScope:^(SentryScope *scope) {
for (NSString *pref in prefs.allKeys)
[scope setExtraValue:prefs[pref] forKey:pref];
[scope setExtraValue:[MPConfig get].rememberLogin forKey:@"rememberLogin"];
[scope setExtraValue:[MPConfig get].sendInfo forKey:@"sendInfo"];
[scope setExtraValue:[MPiOSConfig get].helpHidden forKey:@"helpHidden"];
[scope setExtraValue:[MPiOSConfig get].showSetup forKey:@"showQuickStart"];
[scope setExtraValue:[PearlConfig get].firstRun forKey:@"firstRun"];
[scope setExtraValue:[PearlConfig get].launchCount forKey:@"launchCount"];
[scope setExtraValue:[PearlConfig get].askForReviews forKey:@"askForReviews"];
[scope setExtraValue:[PearlConfig get].reviewAfterLaunches forKey:@"reviewAfterLaunches"];
[scope setExtraValue:[PearlConfig get].reviewedVersion forKey:@"reviewedVersion"];
[scope setExtraValue:@([PearlDeviceUtils isSimulator]) forKey:@"simulator"];
[scope setExtraValue:@([PearlDeviceUtils isAppEncrypted]) forKey:@"encrypted"];
[scope setExtraValue:@([PearlDeviceUtils isJailbroken]) forKey:@"jailbroken"];
[scope setExtraValue:[PearlDeviceUtils platform] forKey:@"platform"];
#ifdef APPSTORE
[scope setExtraValue:@([PearlDeviceUtils isAppEncrypted]) forKey:@"reviewedVersion"];
#else
[scope setExtraValue:@(NO) forKey:@"reviewedVersion"];
#endif
[Countly.sharedInstance giveConsentForFeatures:countlyFeatures];
}];
}
else {
[Countly.sharedInstance cancelConsentForFeatures:countlyFeatures];
[SentrySDK.currentHub getClient].options.enabled = @NO;
[Countly.sharedInstance cancelConsentForAllFeatures];
}
}

View File

@@ -11,9 +11,9 @@
<dict>
<key>CFBundleTypeIconFiles</key>
<array>
<string>Icon-Small</string>
<string>Icon-64.png</string>
<string>Icon-320.png</string>
<string>Icon-Small-40.png</string>
<string>Icon-Small-40@2x.png</string>
<string>Icon-Small-40@3x.png</string>
</array>
<key>CFBundleTypeName</key>
<string>Master Password sites</string>
@@ -21,6 +21,7 @@
<string>Alternate</string>
<key>LSItemContentTypes</key>
<array>
<string>com.lyndir.masterpassword.json</string>
<string>com.lyndir.masterpassword.sites</string>
</array>
</dict>
@@ -37,12 +38,36 @@
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>[auto]</string>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLIconFile</key>
<string>Icon-Small-40@3x</string>
<key>CFBundleURLName</key>
<string>com.lyndir.masterpassword</string>
<key>CFBundleURLSchemes</key>
<array>
<string>masterpassword</string>
</array>
</dict>
</array>
<key>CFBundleVersion</key>
<string>[auto]</string>
<key>LSApplicationQueriesSchemes</key>
<array>
<string>firefox</string>
<string>googlechrome</string>
<string>opera-http</string>
<string>volto</string>
</array>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>LSSupportsOpeningDocumentsInPlace</key>
<false/>
<key>NSFaceIDUsageDescription</key>
<string>Biometrics are used to load your master password from the device's keychain.</string>
<key>NSHumanReadableCopyright</key>
<string>© 2011-2020</string>
<key>UIAppFonts</key>
@@ -101,6 +126,34 @@
</array>
<key>UTExportedTypeDeclarations</key>
<array>
<dict>
<key>UTTypeConformsTo</key>
<array>
<string>public.data</string>
</array>
<key>UTTypeDescription</key>
<string>Master Password JSON export</string>
<key>UTTypeIconFiles</key>
<array>
<string>Icon-Small-40.png</string>
<string>Icon-Small-40@2x.png</string>
<string>Icon-Small-40@3x.png</string>
</array>
<key>UTTypeIdentifier</key>
<string>com.lyndir.masterpassword.json</string>
<key>UTTypeReferenceURL</key>
<string>https://gitlab.com/MasterPassword/MasterPassword/-/wikis/File-Format</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>mpjson</string>
<string>mpsites.json</string>
</array>
<key>public.mime-type</key>
<string>text/plain</string>
</dict>
</dict>
<dict>
<key>UTTypeConformsTo</key>
<array>
@@ -110,9 +163,9 @@
<string>Master Password sites</string>
<key>UTTypeIconFiles</key>
<array>
<string>Icon-Small.png</string>
<string>Icon-64.png</string>
<string>Icon-320.png</string>
<string>Icon-Small-40.png</string>
<string>Icon-Small-40@2x.png</string>
<string>Icon-Small-40@3x.png</string>
</array>
<key>UTTypeIdentifier</key>
<string>com.lyndir.masterpassword.sites</string>
@@ -121,6 +174,7 @@
<key>public.filename-extension</key>
<array>
<string>mpsites</string>
<string>mpsites.txt</string>
</array>
</dict>
</dict>

View File

@@ -18,7 +18,7 @@
<key>Title</key>
<string>Version</string>
<key>Key</key>
<string>unset</string>
<string>version</string>
<key>Type</key>
<string>PSTitleValueSpecifier</string>
</dict>
@@ -28,7 +28,7 @@
<key>Title</key>
<string>Build</string>
<key>Key</key>
<string>unset</string>
<string>build</string>
<key>Type</key>
<string>PSTitleValueSpecifier</string>
</dict>
@@ -40,7 +40,7 @@
<key>Type</key>
<string>PSTitleValueSpecifier</string>
<key>Key</key>
<string>unset</string>
<string>copyright</string>
</dict>
<dict>
<key>Type</key>

View File

@@ -472,17 +472,11 @@
<rect key="frame" x="0.0" y="0.0" width="414" height="842"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews>
<webView contentMode="scaleToFill" scalesPageToFit="YES" suppressesIncrementalRendering="YES" translatesAutoresizingMaskIntoConstraints="NO" id="3aB-Hk-Fgd">
<rect key="frame" x="0.0" y="0.0" width="414" height="842"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<connections>
<outlet property="delegate" destination="Sd5-eW-Cx2" id="ePX-qz-XXR"/>
</connections>
</webView>
<navigationBar contentMode="scaleToFill" barStyle="black" translatesAutoresizingMaskIntoConstraints="NO" id="03x-KT-JQN">
<rect key="frame" x="0.0" y="0.0" width="414" height="90"/>
<items>
<navigationItem title="masterpassword.app" prompt="Loading" id="Wpf-6b-UJb">
<barButtonItem key="leftBarButtonItem" systemItem="action" id="d9P-Hd-rdw"/>
<barButtonItem key="rightBarButtonItem" systemItem="done" id="Tbg-c3-qOh">
<connections>
<action selector="done:" destination="Sd5-eW-Cx2" id="cuN-bV-cwl"/>
@@ -494,19 +488,14 @@
</subviews>
<color key="backgroundColor" red="0.12549020350000001" green="0.1411764771" blue="0.14901961389999999" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="3aB-Hk-Fgd" firstAttribute="top" secondItem="zXV-QC-CZm" secondAttribute="bottom" id="3Od-Xa-Ssc"/>
<constraint firstItem="03x-KT-JQN" firstAttribute="leading" secondItem="2Jz-8H-3nG" secondAttribute="leading" id="BAn-wU-k2A"/>
<constraint firstItem="03x-KT-JQN" firstAttribute="top" secondItem="zXV-QC-CZm" secondAttribute="bottom" id="JU6-uj-GA3"/>
<constraint firstAttribute="bottom" secondItem="3aB-Hk-Fgd" secondAttribute="bottom" id="S01-AA-E0a"/>
<constraint firstAttribute="trailing" secondItem="03x-KT-JQN" secondAttribute="trailing" id="sQs-u3-M3r"/>
<constraint firstItem="3aB-Hk-Fgd" firstAttribute="leading" secondItem="2Jz-8H-3nG" secondAttribute="leading" id="w1l-x3-qCi"/>
<constraint firstAttribute="trailing" secondItem="3aB-Hk-Fgd" secondAttribute="trailing" id="xdJ-WF-yw0"/>
</constraints>
</view>
<navigationItem key="navigationItem" id="gmy-ym-z4I"/>
<connections>
<outlet property="webNavigationItem" destination="Wpf-6b-UJb" id="8gM-LU-pHk"/>
<outlet property="webView" destination="3aB-Hk-Fgd" id="Mie-rI-l4h"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="hNt-WS-DuE" userLabel="First Responder" sceneMemberID="firstResponder"/>
@@ -600,7 +589,6 @@
<connections>
<segue destination="JFc-sj-awD" kind="relationship" relationship="viewControllers" id="bwn-ao-Y5g"/>
<segue destination="C0Q-RC-szS" kind="relationship" relationship="viewControllers" id="wap-5K-BMD"/>
<segue destination="LBn-EA-NAH" kind="relationship" relationship="viewControllers" id="sGh-gf-eKK"/>
<segue destination="pdl-xv-zjX" kind="relationship" relationship="viewControllers" id="QTc-cR-VHJ"/>
</connections>
</tabBarController>
@@ -899,14 +887,15 @@
</connections>
</switch>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" verticalCompressionResistancePriority="751" text="Biometrics" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="6xf-vt-aXd">
<rect key="frame" x="20" y="20" width="374" height="21"/>
<rect key="frame" x="20" y="20" width="374" height="20"/>
<fontDescription key="fontDescription" name="Exo2.0-Bold" family="Exo 2.0" pointSize="17"/>
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" alpha="0.69999999999999996" contentMode="left" horizontalHuggingPriority="251" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="URR-yZ-QuC">
<rect key="frame" x="20" y="49" width="374" height="142"/>
<string key="text">When enabled on a biometrics-enabled device, your fingerprint or face scan will be required to load your saved password. Note that this feature requires that the Save Password option is enabled and Biometrics support has been purchased from the in-app store.</string>
<rect key="frame" x="20" y="48" width="374" height="143"/>
<string key="text">When enabled on a biometrics-enabled device, your fingerprint or face scan will be required to load your stored master password.
Note that this feature requires you enable the Save Password option and have purchased Biometrics support from the in-app store.</string>
<fontDescription key="fontDescription" name="Exo2.0-Regular" family="Exo 2.0" pointSize="12"/>
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
@@ -1143,20 +1132,20 @@
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="K2e-Gh-7hH" userLabel="Passwords Container">
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
<subviews>
<collectionView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" minimumZoomScale="0.0" maximumZoomScale="0.0" dataMode="prototypes" translatesAutoresizingMaskIntoConstraints="NO" id="aXw-tn-8Sj" userLabel="Password Collection">
<collectionView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" minimumZoomScale="0.0" maximumZoomScale="0.0" keyboardDismissMode="interactive" dataMode="prototypes" translatesAutoresizingMaskIntoConstraints="NO" id="aXw-tn-8Sj" userLabel="Password Collection">
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<collectionViewFlowLayout key="collectionViewLayout" minimumLineSpacing="10" minimumInteritemSpacing="10" id="Mv1-29-TWx">
<collectionViewFlowLayout key="collectionViewLayout" minimumLineSpacing="10" minimumInteritemSpacing="10" sectionInsetReference="safeArea" id="Mv1-29-TWx">
<size key="itemSize" width="355" height="100"/>
<size key="headerReferenceSize" width="0.0" height="0.0"/>
<size key="footerReferenceSize" width="0.0" height="0.0"/>
<inset key="sectionInset" minX="10" minY="200" maxX="10" maxY="10"/>
<inset key="sectionInset" minX="10" minY="10" maxX="10" maxY="10"/>
</collectionViewFlowLayout>
<cells>
<collectionViewCell opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" reuseIdentifier="MPSiteCell" id="W2g-yv-V3V" customClass="MPSiteCell">
<rect key="frame" x="29.5" y="200" width="355" height="100"/>
<rect key="frame" x="29.5" y="10" width="355" height="100"/>
<autoresizingMask key="autoresizingMask"/>
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center">
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO">
<rect key="frame" x="0.0" y="0.0" width="355" height="100"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
@@ -1547,11 +1536,12 @@
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<gestureRecognizers/>
<constraints>
<constraint firstAttribute="trailing" secondItem="xph-TW-9QO" secondAttribute="trailing" id="Fmt-yZ-Vjo"/>
<constraint firstItem="xph-TW-9QO" firstAttribute="leading" secondItem="W2g-yv-V3V" secondAttribute="leading" id="IsM-Br-kMe"/>
<constraint firstItem="xph-TW-9QO" firstAttribute="top" secondItem="W2g-yv-V3V" secondAttribute="top" id="KDo-0Z-mc1"/>
<constraint firstAttribute="bottom" secondItem="xph-TW-9QO" secondAttribute="bottom" id="W1r-47-bqe"/>
<constraint firstAttribute="trailing" secondItem="xph-TW-9QO" secondAttribute="trailing" priority="900" id="Fmt-yZ-Vjo"/>
<constraint firstItem="xph-TW-9QO" firstAttribute="leading" secondItem="W2g-yv-V3V" secondAttribute="leading" priority="900" id="IsM-Br-kMe"/>
<constraint firstItem="xph-TW-9QO" firstAttribute="top" secondItem="W2g-yv-V3V" secondAttribute="top" priority="900" id="KDo-0Z-mc1"/>
<constraint firstAttribute="bottom" secondItem="xph-TW-9QO" secondAttribute="bottom" priority="900" id="W1r-47-bqe"/>
</constraints>
<size key="customSize" width="355" height="100"/>
<connections>
<outlet property="answersButton" destination="vGk-t6-hZn" id="0oi-2F-M1S"/>
<outlet property="contentButton" destination="aDw-qY-VjU" id="R3R-kq-XMd"/>
@@ -2058,9 +2048,7 @@ This app is now out of maintenance.</string>
<constraint firstAttribute="width" secondItem="whU-l0-2bU" secondAttribute="width" id="AiQ-LE-3bh"/>
<constraint firstItem="IV3-lc-Fnf" firstAttribute="top" secondItem="gRG-Ys-94p" secondAttribute="bottom" id="IMb-wl-Eeb"/>
<constraint firstAttribute="trailing" secondItem="gRG-Ys-94p" secondAttribute="trailing" id="Pb1-eY-0FG"/>
<constraint firstAttribute="trailing" secondItem="GiS-3g-cDj" secondAttribute="trailing" id="TjK-B6-KoI"/>
<constraint firstItem="gRG-Ys-94p" firstAttribute="top" secondItem="GiS-3g-cDj" secondAttribute="top" id="oyk-Xr-zTZ"/>
<constraint firstItem="IV3-lc-Fnf" firstAttribute="top" secondItem="GiS-3g-cDj" secondAttribute="bottom" id="t22-RN-Hm0"/>
<constraint firstItem="gRG-Ys-94p" firstAttribute="leading" secondItem="GiS-3g-cDj" secondAttribute="leading" id="zhC-jf-5dY"/>
</constraints>
<userDefinedRuntimeAttributes>
@@ -2091,58 +2079,6 @@ This app is now out of maintenance.</string>
</objects>
<point key="canvasLocation" x="2038" y="-518"/>
</scene>
<!--Settings-->
<scene sceneID="jGj-Je-kyC">
<objects>
<navigationController definesPresentationContext="YES" id="LBn-EA-NAH" sceneMemberID="viewController">
<tabBarItem key="tabBarItem" title="Settings" image="icon_gears.png" id="6n3-Ay-Knn"/>
<navigationBar key="navigationBar" contentMode="scaleToFill" misplaced="YES" id="h6j-1o-LHf">
<rect key="frame" x="0.0" y="44" width="414" height="44"/>
<autoresizingMask key="autoresizingMask"/>
</navigationBar>
<connections>
<segue destination="IJT-xV-jHt" kind="relationship" relationship="rootViewController" id="ymU-OE-v4t"/>
</connections>
</navigationController>
<placeholder placeholderIdentifier="IBFirstResponder" id="PQz-c8-3Ww" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="3803" y="919"/>
</scene>
<!--Settings-->
<scene sceneID="fmc-CS-nuo">
<objects>
<tableViewController automaticallyAdjustsScrollViewInsets="NO" id="IJT-xV-jHt" customClass="MPAppSettingsViewController" sceneMemberID="viewController">
<tableView key="view" opaque="NO" clipsSubviews="YES" clearsContextBeforeDrawing="NO" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="grouped" separatorStyle="default" rowHeight="44" sectionHeaderHeight="10" sectionFooterHeight="10" id="jPx-Oa-WxU">
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<inset key="scrollIndicatorInsets" minX="0.0" minY="64" maxX="0.0" maxY="49"/>
<color key="separatorColor" red="0.37254901959999998" green="0.3921568627" blue="0.42745098040000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="sectionIndexColor" red="0.37254901959999998" green="0.3921568627" blue="0.42745098040000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<prototypes>
<tableViewCell contentMode="scaleToFill" selectionStyle="blue" hidesAccessoryWhenEditing="NO" indentationLevel="1" indentationWidth="0.0" reuseIdentifier="-" id="3ZC-Sm-lB8">
<rect key="frame" x="0.0" y="55.5" width="414" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="3ZC-Sm-lB8" id="UzB-cq-EM3">
<rect key="frame" x="0.0" y="0.0" width="414" height="44"/>
<autoresizingMask key="autoresizingMask"/>
</tableViewCellContentView>
</tableViewCell>
</prototypes>
<connections>
<outlet property="dataSource" destination="IJT-xV-jHt" id="bMa-HU-UPY"/>
<outlet property="delegate" destination="IJT-xV-jHt" id="MJE-yH-6xk"/>
</connections>
</tableView>
<tabBarItem key="tabBarItem" title="Settings" image="icon_gears.png" id="K62-lO-hO3"/>
<navigationItem key="navigationItem" title="Settings" id="FPc-yy-RoS">
<barButtonItem key="rightBarButtonItem" title="Logs" id="rO8-gt-aLH"/>
</navigationItem>
</tableViewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="sDE-fE-FNc" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="4730" y="919"/>
</scene>
<!--Usage-->
<scene sceneID="9SY-7D-CE9">
<objects>
@@ -2278,7 +2214,7 @@ CgoKCgoKCgoKCgoKCg
<autoresizingMask key="autoresizingMask"/>
<subviews>
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" editable="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ZLZ-Ds-7pp">
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
<rect key="frame" x="0.0" y="44" width="414" height="852"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<inset key="scrollIndicatorInsets" minX="0.0" minY="64" maxX="0.0" maxY="93"/>
<string key="text">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris facilisis tortor leo, iaculis mollis elit dictum et. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. In congue justo porta enim imperdiet, id luctus justo fringilla. Nunc nec sem id augue bibendum hendrerit eu ut eros. Ut fermentum augue quis nunc feugiat vehicula. Quisque in ultrices magna. Praesent quis mollis lectus. Sed fringilla massa vitae eros luctus, eget convallis justo pretium. Duis non tristique ante. Sed suscipit tortor ligula, sed fermentum eros sodales ut. Maecenas sed ante et orci posuere lobortis et sodales diam. Nunc non ullamcorper orci.
@@ -2290,7 +2226,7 @@ Suspendisse potenti. Etiam ut nisi id augue tempor ultrices et sit amet sapien.
<dataDetectorType key="dataDetectorTypes" link="YES"/>
</textView>
<toolbar contentMode="scaleToFill" barStyle="black" translatesAutoresizingMaskIntoConstraints="NO" id="WmH-JB-jp2">
<rect key="frame" x="0.0" y="798" width="414" height="49"/>
<rect key="frame" x="0.0" y="764" width="414" height="49"/>
<items>
<barButtonItem systemItem="compose" id="BSV-3i-01h">
<connections>
@@ -2323,8 +2259,8 @@ Suspendisse potenti. Etiam ut nisi id augue tempor ultrices et sit amet sapien.
</subviews>
<color key="backgroundColor" red="0.1215686275" green="0.12941176469999999" blue="0.14117647059999999" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="bottom" secondItem="WmH-JB-jp2" secondAttribute="bottom" constant="49" id="3MJ-7p-Ko1"/>
<constraint firstItem="ZLZ-Ds-7pp" firstAttribute="top" secondItem="VRk-ZF-UQ6" secondAttribute="top" id="SDj-uX-oNC"/>
<constraint firstAttribute="bottomMargin" secondItem="WmH-JB-jp2" secondAttribute="bottom" id="3MJ-7p-Ko1"/>
<constraint firstItem="ZLZ-Ds-7pp" firstAttribute="top" secondItem="VRk-ZF-UQ6" secondAttribute="topMargin" id="SDj-uX-oNC"/>
<constraint firstAttribute="bottom" secondItem="ZLZ-Ds-7pp" secondAttribute="bottom" id="bpm-15-Rgy"/>
<constraint firstAttribute="trailing" secondItem="WmH-JB-jp2" secondAttribute="trailing" id="f4Q-oI-Dxc"/>
<constraint firstAttribute="trailing" secondItem="ZLZ-Ds-7pp" secondAttribute="trailing" id="iID-PM-DSF"/>
@@ -2377,17 +2313,17 @@ Suspendisse potenti. Etiam ut nisi id augue tempor ultrices et sit amet sapien.
<rect key="frame" x="0.0" y="0.0" width="414" height="842"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<imageView userInteractionEnabled="NO" contentMode="center" image="background.png" translatesAutoresizingMaskIntoConstraints="NO" id="OsH-Mq-gpM">
<imageView userInteractionEnabled="NO" contentMode="scaleAspectFill" image="background.png" translatesAutoresizingMaskIntoConstraints="NO" id="OsH-Mq-gpM">
<rect key="frame" x="0.0" y="0.0" width="414" height="842"/>
</imageView>
<scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Ol8-3I-X99">
<rect key="frame" x="0.0" y="56" width="414" height="752"/>
<subviews>
<imageView userInteractionEnabled="NO" contentMode="top" image="unlocked.png" translatesAutoresizingMaskIntoConstraints="NO" id="Cih-B6-Sfd">
<rect key="frame" x="20" y="20" width="374" height="79.5"/>
<rect key="frame" x="20" y="20" width="374" height="80"/>
</imageView>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Stay Logged In?" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="5X1-J4-TbH">
<rect key="frame" x="20" y="107.5" width="374" height="20"/>
<rect key="frame" x="20" y="108" width="374" height="20"/>
<fontDescription key="fontDescription" name="Exo2.0-Bold" family="Exo 2.0" pointSize="17"/>
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
@@ -2397,7 +2333,7 @@ Suspendisse potenti. Etiam ut nisi id augue tempor ultrices et sit amet sapien.
<color key="onTintColor" red="0.37254901959999998" green="0.3921568627" blue="0.42745098040000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</switch>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="e80-98-V6D">
<rect key="frame" x="20" y="135.5" width="374" height="151.5"/>
<rect key="frame" x="20" y="136" width="374" height="151.5"/>
<string key="text">The right balance between security and convenience is often very personal.
To make getting to your passwords faster, you can remain logged in after you close Master Password. This allows you to skip having to log in the next time.
@@ -2465,7 +2401,7 @@ However, it means that anyone who finds your device unlocked can do the same.</s
<rect key="frame" x="0.0" y="0.0" width="414" height="842"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<imageView userInteractionEnabled="NO" contentMode="center" image="background.png" translatesAutoresizingMaskIntoConstraints="NO" id="Hrg-vp-M2J">
<imageView userInteractionEnabled="NO" contentMode="scaleAspectFill" image="background.png" translatesAutoresizingMaskIntoConstraints="NO" id="Hrg-vp-M2J">
<rect key="frame" x="0.0" y="0.0" width="414" height="842"/>
</imageView>
<scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="fJc-xU-1xA">
@@ -3089,7 +3025,7 @@ Invested: 3.7 work hours</string>
<rect key="frame" x="0.0" y="0.0" width="414" height="842"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<imageView userInteractionEnabled="NO" contentMode="center" image="background.png" translatesAutoresizingMaskIntoConstraints="NO" id="rvb-7K-MHe">
<imageView userInteractionEnabled="NO" contentMode="scaleAspectFill" image="background.png" translatesAutoresizingMaskIntoConstraints="NO" id="rvb-7K-MHe">
<rect key="frame" x="0.0" y="0.0" width="414" height="842"/>
</imageView>
<scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="7A7-Yn-F6L">
@@ -3321,8 +3257,8 @@ Ut in geometria, prima si dederis, danda sunt omnia. Nonne igitur tibi videntur,
</scene>
</scenes>
<inferredMetricsTieBreakers>
<segue reference="Ql4-wf-T8u"/>
<segue reference="GZk-I4-JyH"/>
<segue reference="k2G-nL-x3l"/>
<segue reference="gtb-zE-u9H"/>
</inferredMetricsTieBreakers>
<color key="tintColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<resources>

View File

@@ -44,7 +44,6 @@ library {
def arch = targetMachine.getArchitecture().getName().replace('-', '_')
compile.macros.put("MPW_SODIUM", "1")
compile.macros.put("MPW_LOG", "mpw_log_app")
dependencies {
// libsodium

View File

@@ -81,7 +81,7 @@ static const uint8_t b64ToBits[256] =
size_t mpw_base64_decode_max(const char *b64Text) {
register const char *b64Cursor = b64Text;
for (; b64ToBits[*b64Cursor] <= 63; ++b64Cursor);
for (; b64ToBits[(uint8_t)*b64Cursor] <= 63; ++b64Cursor);
size_t b64Size = b64Cursor - b64Text;
// Every 4 b64 chars yield 3 plain bytes => len = 3 * ceil(b64Size / 4)
@@ -91,7 +91,7 @@ size_t mpw_base64_decode_max(const char *b64Text) {
size_t mpw_base64_decode(uint8_t *plainBuf, const char *b64Text) {
register const uint8_t *b64Cursor = (uint8_t *)b64Text;
for (; b64ToBits[*b64Cursor] <= 63; ++b64Cursor);
for (; b64ToBits[(uint8_t)*b64Cursor] <= 63; ++b64Cursor);
size_t b64Remaining = b64Cursor - (uint8_t *)b64Text;
b64Cursor = (uint8_t *)b64Text;

View File

@@ -7,8 +7,6 @@
#ifdef __cplusplus
extern "C" {
#endif
#undef com_lyndir_masterpassword_MPAlgorithm_Version_AES_BLOCKSIZE
#define com_lyndir_masterpassword_MPAlgorithm_Version_AES_BLOCKSIZE 128L
/*
* Class: com_lyndir_masterpassword_MPAlgorithm_Version
* Method: _masterKey
@@ -41,6 +39,22 @@ JNIEXPORT jstring JNICALL Java_com_lyndir_masterpassword_MPAlgorithm_00024Versio
JNIEXPORT jstring JNICALL Java_com_lyndir_masterpassword_MPAlgorithm_00024Version__1siteState
(JNIEnv *, jobject, jbyteArray, jbyteArray, jstring, jlong, jint, jstring, jint, jstring, jint);
/*
* Class: com_lyndir_masterpassword_MPAlgorithm_Version
* Method: _identicon
* Signature: (Ljava/lang/String;[B)Lcom/lyndir/masterpassword/MPIdenticon;
*/
JNIEXPORT jobject JNICALL Java_com_lyndir_masterpassword_MPAlgorithm_00024Version__1identicon
(JNIEnv *, jobject, jstring, jbyteArray);
/*
* Class: com_lyndir_masterpassword_MPAlgorithm_Version
* Method: _toID
* Signature: ([B)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_lyndir_masterpassword_MPAlgorithm_00024Version__1toID
(JNIEnv *, jobject, jbyteArray);
#ifdef __cplusplus
}
#endif

View File

@@ -10,50 +10,46 @@
static JavaVM* _vm;
static jobject logger;
void mpw_log_app(LogLevel level, const char *format, ...) {
MPLogSink mpw_log_sink_jni;
bool mpw_log_sink_jni(const MPLogEvent *record) {
bool sunk = false;
JNIEnv *env;
if ((*_vm)->GetEnv( _vm, (void **)&env, JNI_VERSION_1_6 ) != JNI_OK)
return;
va_list args;
va_start( args, format );
return sunk;
if (logger && (*env)->PushLocalFrame( env, 16 ) == OK) {
jmethodID method = NULL;
jclass Logger = (*env)->GetObjectClass( env, logger );
if (level >= LogLevelTrace)
method = (*env)->GetMethodID( env, Logger, "trace", "(Ljava/lang/String;)V" );
else if (level == LogLevelDebug)
method = (*env)->GetMethodID( env, Logger, "debug", "(Ljava/lang/String;)V" );
else if (level == LogLevelInfo)
method = (*env)->GetMethodID( env, Logger, "info", "(Ljava/lang/String;)V" );
else if (level == LogLevelWarning)
method = (*env)->GetMethodID( env, Logger, "warn", "(Ljava/lang/String;)V" );
else if (level <= LogLevelError)
method = (*env)->GetMethodID( env, Logger, "error", "(Ljava/lang/String;)V" );
jclass cLogger = (*env)->GetObjectClass( env, logger );
switch (record->level) {
case LogLevelTrace:
method = (*env)->GetMethodID( env, cLogger, "trace", "(Ljava/lang/String;)V" );
break;
case LogLevelDebug:
method = (*env)->GetMethodID( env, cLogger, "debug", "(Ljava/lang/String;)V" );
break;
case LogLevelInfo:
method = (*env)->GetMethodID( env, cLogger, "info", "(Ljava/lang/String;)V" );
break;
case LogLevelWarning:
method = (*env)->GetMethodID( env, cLogger, "warn", "(Ljava/lang/String;)V" );
break;
case LogLevelError:
case LogLevelFatal:
method = (*env)->GetMethodID( env, cLogger, "error", "(Ljava/lang/String;)V" );
break;
}
va_list _args;
va_copy( _args, args );
int length = vsnprintf( NULL, 0, format, _args );
va_end( _args );
if (length > 0) {
size_t size = (size_t) (length + 1);
char *message = malloc( size );
va_copy( _args, args );
if (message && (length = vsnprintf( message, size, format, _args )) > 0)
(*env)->CallVoidMethod( env, logger, method, (*env)->NewStringUTF( env, message ) );
va_end( _args );
mpw_free( &message, (size_t)max( 0, length ) );
if (method && record->message) {
// TODO: log file, line & function as markers?
(*env)->CallVoidMethod( env, logger, method, (*env)->NewStringUTF( env, record->message ) );
sunk = true;
}
(*env)->PopLocalFrame( env, NULL );
}
else
// Can't log via slf4j, fall back to cli logger.
mpw_vlog_cli( level, format, args );
va_end( args );
return sunk;
}
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {
@@ -61,33 +57,45 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {
if ((*vm)->GetEnv( _vm = vm, (void **)&env, JNI_VERSION_1_6 ) != JNI_OK)
return -1;
jclass LoggerFactory = (*env)->FindClass( env, "org/slf4j/LoggerFactory" );
jmethodID method = (*env)->GetStaticMethodID( env, LoggerFactory, "getLogger", "(Ljava/lang/String;)Lorg/slf4j/Logger;" );
jstring name = (*env)->NewStringUTF( env, "com.lyndir.masterpassword.algorithm" );
if (LoggerFactory && method && name)
logger = (*env)->NewGlobalRef( env, (*env)->CallStaticObjectMethod( env, LoggerFactory, method, name ) );
else
wrn( "Couldn't initialize JNI logger." );
do {
jclass cLoggerFactory = (*env)->FindClass( env, "org/slf4j/LoggerFactory" );
if (!cLoggerFactory)
break;
jmethodID method = (*env)->GetStaticMethodID( env, cLoggerFactory, "getLogger", "(Ljava/lang/String;)Lorg/slf4j/Logger;" );
if (!method)
break;
jstring name = (*env)->NewStringUTF( env, "com.lyndir.masterpassword.algorithm" );
if (!name)
break;
logger = (*env)->NewGlobalRef( env, (*env)->CallStaticObjectMethod( env, cLoggerFactory, method, name ) );
if (!logger)
break;
jclass Logger = (*env)->GetObjectClass( env, logger );
if ((*env)->CallBooleanMethod( env, logger, (*env)->GetMethodID( env, Logger, "isTraceEnabled", "()Z" ) ))
mpw_verbosity = LogLevelTrace;
else if ((*env)->CallBooleanMethod( env, logger, (*env)->GetMethodID( env, Logger, "isDebugEnabled", "()Z" ) ))
mpw_verbosity = LogLevelDebug;
else if ((*env)->CallBooleanMethod( env, logger, (*env)->GetMethodID( env, Logger, "isInfoEnabled", "()Z" ) ))
mpw_verbosity = LogLevelInfo;
else if ((*env)->CallBooleanMethod( env, logger, (*env)->GetMethodID( env, Logger, "isWarnEnabled", "()Z" ) ))
mpw_verbosity = LogLevelWarning;
else if ((*env)->CallBooleanMethod( env, logger, (*env)->GetMethodID( env, Logger, "isErrorEnabled", "()Z" ) ))
mpw_verbosity = LogLevelError;
else
mpw_verbosity = LogLevelFatal;
jclass cLogger = (*env)->GetObjectClass( env, logger );
if ((*env)->CallBooleanMethod( env, logger, (*env)->GetMethodID( env, cLogger, "isTraceEnabled", "()Z" ) ))
mpw_verbosity = LogLevelTrace;
else if ((*env)->CallBooleanMethod( env, logger, (*env)->GetMethodID( env, cLogger, "isDebugEnabled", "()Z" ) ))
mpw_verbosity = LogLevelDebug;
else if ((*env)->CallBooleanMethod( env, logger, (*env)->GetMethodID( env, cLogger, "isInfoEnabled", "()Z" ) ))
mpw_verbosity = LogLevelInfo;
else if ((*env)->CallBooleanMethod( env, logger, (*env)->GetMethodID( env, cLogger, "isWarnEnabled", "()Z" ) ))
mpw_verbosity = LogLevelWarning;
else if ((*env)->CallBooleanMethod( env, logger, (*env)->GetMethodID( env, cLogger, "isErrorEnabled", "()Z" ) ))
mpw_verbosity = LogLevelError;
else
mpw_verbosity = LogLevelFatal;
mpw_log_sink_register( &mpw_log_sink_jni );
} while (false);
if (!logger)
wrn( "Couldn't initialize JNI logger." );
return JNI_VERSION_1_6;
}
/* native int _masterKey(final String fullName, final byte[] masterPassword, final Version version) */
JNIEXPORT jbyteArray JNICALL Java_com_lyndir_masterpassword_impl_MPAlgorithmV0__1masterKey(JNIEnv *env, jobject obj,
/* native byte[] _masterKey(final String fullName, final byte[] masterPassword, final int algorithmVersion) */
JNIEXPORT jbyteArray JNICALL Java_com_lyndir_masterpassword_MPAlgorithm_00024Version__1masterKey(JNIEnv *env, jobject obj,
jstring fullName, jbyteArray masterPassword, jint algorithmVersion) {
if (!fullName || !masterPassword)
@@ -110,9 +118,9 @@ JNIEXPORT jbyteArray JNICALL Java_com_lyndir_masterpassword_impl_MPAlgorithmV0__
return masterKey;
}
/* native int _siteKey(final byte[] masterKey, final String siteName, final long siteCounter,
final MPKeyPurpose keyPurpose, @Nullable final String keyContext, final Version version) */
JNIEXPORT jbyteArray JNICALL Java_com_lyndir_masterpassword_impl_MPAlgorithmV0__1siteKey(JNIEnv *env, jobject obj,
/* native byte[] _siteKey(final byte[] masterKey, final String siteName, final long siteCounter,
final int keyPurpose, @Nullable final String keyContext, final int version) */
JNIEXPORT jbyteArray JNICALL Java_com_lyndir_masterpassword_MPAlgorithm_00024Version__1siteKey(JNIEnv *env, jobject obj,
jbyteArray masterKey, jstring siteName, jlong siteCounter, jint keyPurpose, jstring keyContext, jint algorithmVersion) {
if (!masterKey || !siteName)
@@ -140,9 +148,9 @@ JNIEXPORT jbyteArray JNICALL Java_com_lyndir_masterpassword_impl_MPAlgorithmV0__
}
/* native String _siteResult(final byte[] masterKey, final byte[] siteKey, final String siteName, final long siteCounter,
final MPKeyPurpose keyPurpose, @Nullable final String keyContext,
final MPResultType resultType, @Nullable final String resultParam, final Version version) */
JNIEXPORT jstring JNICALL Java_com_lyndir_masterpassword_impl_MPAlgorithmV0__1siteResult(JNIEnv *env, jobject obj,
final int keyPurpose, @Nullable final String keyContext,
final int resultType, @Nullable final String resultParam, final int algorithmVersion) */
JNIEXPORT jstring JNICALL Java_com_lyndir_masterpassword_MPAlgorithm_00024Version__1siteResult(JNIEnv *env, jobject obj,
jbyteArray masterKey, jbyteArray siteKey, jstring siteName, jlong siteCounter, jint keyPurpose, jstring keyContext,
jint resultType, jstring resultParam, jint algorithmVersion) {
@@ -175,9 +183,9 @@ JNIEXPORT jstring JNICALL Java_com_lyndir_masterpassword_impl_MPAlgorithmV0__1si
}
/* native String _siteState(final byte[] masterKey, final byte[] siteKey, final String siteName, final long siteCounter,
final MPKeyPurpose keyPurpose, @Nullable final String keyContext,
final MPResultType resultType, final String resultParam, final Version version) */
JNIEXPORT jstring JNICALL Java_com_lyndir_masterpassword_impl_MPAlgorithmV0__1siteState(JNIEnv *env, jobject obj,
final int keyPurpose, @Nullable final String keyContext,
final int resultType, final String resultParam, final int algorithmVersion) */
JNIEXPORT jstring JNICALL Java_com_lyndir_masterpassword_MPAlgorithm_00024Version__1siteState(JNIEnv *env, jobject obj,
jbyteArray masterKey, jbyteArray siteKey, jstring siteName, jlong siteCounter, jint keyPurpose, jstring keyContext,
jint resultType, jstring resultParam, jint algorithmVersion) {
@@ -208,3 +216,51 @@ JNIEXPORT jstring JNICALL Java_com_lyndir_masterpassword_impl_MPAlgorithmV0__1si
return siteState;
}
/* native MPIdenticon _identicon(final String fullName, final byte[] masterPassword) */
JNIEXPORT jobject JNICALL Java_com_lyndir_masterpassword_MPAlgorithm_00024Version__1identicon(JNIEnv *env, jobject obj,
jstring fullName, jbyteArray masterPassword) {
if (!fullName || !masterPassword)
return NULL;
const char *fullNameString = (*env)->GetStringUTFChars( env, fullName, NULL );
jbyte *masterPasswordString = (*env)->GetByteArrayElements( env, masterPassword, NULL );
MPIdenticon identicon = mpw_identicon( fullNameString, (char *)masterPasswordString );
(*env)->ReleaseStringUTFChars( env, fullName, fullNameString );
(*env)->ReleaseByteArrayElements( env, masterPassword, masterPasswordString, JNI_ABORT );
if (identicon.color == MPIdenticonColorUnset)
return NULL;
jclass cMPIdenticonColor = (*env)->FindClass( env, "com/lyndir/masterpassword/MPIdenticon$Color" );
if (!cMPIdenticonColor)
return NULL;
jmethodID method = (*env)->GetStaticMethodID( env, cMPIdenticonColor, "values", "()[Lcom/lyndir/masterpassword/MPIdenticon$Color;" );
if (!method)
return NULL;
jobject values = (*env)->CallStaticObjectMethod( env, cMPIdenticonColor, method );
if (!values)
return NULL;
jclass cMPIdenticon = (*env)->FindClass( env, "com/lyndir/masterpassword/MPIdenticon" );
if (!cMPIdenticon)
return NULL;
jmethodID init = (*env)->GetMethodID( env, cMPIdenticon, "<init>", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/lyndir/masterpassword/MPIdenticon$Color;)V" );
if (!init)
return NULL;
return (*env)->NewObject( env, cMPIdenticon, init, fullName,
(*env)->NewStringUTF( env, identicon.leftArm ),
(*env)->NewStringUTF( env, identicon.body ),
(*env)->NewStringUTF( env, identicon.rightArm ),
(*env)->NewStringUTF( env, identicon.accessory ),
(*env)->GetObjectArrayElement( env, values, identicon.color ) );
}
/* native String _toID(final byte[] buffer) */
JNIEXPORT jstring JNICALL Java_com_lyndir_masterpassword_MPAlgorithm_00024Version__1toID(JNIEnv *env, jobject obj,
jbyteArray buffer) {
return (*env)->NewStringUTF( env, mpw_id_buf( (*env)->GetByteArrayElements( env, buffer, NULL ), (*env)->GetArrayLength( env, buffer ) ) );
}

View File

@@ -176,6 +176,21 @@ MPMarshalledFile *mpw_marshal_file(
return file;
}
MPMarshalledFile *mpw_marshal_error(
MPMarshalledFile *file, MPMarshalErrorType type, const char *format, ...) {
file = mpw_marshal_file( file, NULL, NULL );
if (!file)
return NULL;
va_list args;
va_start( args, format );
file->error = (MPMarshalError){ type, mpw_strdup( mpw_vstr( format, args ) ) };
va_end( args );
return file;
}
void mpw_marshal_info_free(
MPMarshalledInfo **info) {
@@ -228,6 +243,7 @@ void mpw_marshal_file_free(
mpw_marshal_info_free( &(*file)->info );
mpw_marshal_data_free( &(*file)->data );
mpw_free_string( &(*file)->error.message );
mpw_free( file, sizeof( MPMarshalledFile ) );
}
@@ -539,7 +555,7 @@ static const char *mpw_marshal_write_flat(
const MPMarshalledData *data = file->data;
if (!data) {
file->error = (MPMarshalError){ MPMarshalErrorMissing, "Missing data." };
mpw_marshal_error( file, MPMarshalErrorMissing, "Missing data." );
return NULL;
}
@@ -583,9 +599,9 @@ static const char *mpw_marshal_write_flat(
}
if (!out)
file->error = (MPMarshalError){ MPMarshalErrorFormat, "Couldn't encode JSON." };
mpw_marshal_error( file, MPMarshalErrorFormat, "Couldn't encode JSON." );
else
file->error = (MPMarshalError){ MPMarshalSuccess, NULL };
mpw_marshal_error( file, MPMarshalSuccess, NULL );
return out;
}
@@ -637,7 +653,7 @@ static const char *mpw_marshal_write_json(
// Section: "export"
json_object *json_file = mpw_get_json_data( file->data );
if (!json_file) {
file->error = (MPMarshalError){ MPMarshalErrorFormat, "Couldn't serialize export data." };
mpw_marshal_error( file, MPMarshalErrorFormat, "Couldn't serialize export data." );
return NULL;
}
@@ -650,9 +666,9 @@ static const char *mpw_marshal_write_json(
json_object_put( json_file );
if (!out)
file->error = (MPMarshalError){ MPMarshalErrorFormat, "Couldn't encode JSON." };
mpw_marshal_error( file, MPMarshalErrorFormat, "Couldn't encode JSON." );
else
file->error = (MPMarshalError){ MPMarshalSuccess, NULL };
mpw_marshal_error( file, MPMarshalSuccess, NULL );
return out;
}
@@ -698,17 +714,17 @@ const char *mpw_marshal_write(
if (!file_)
mpw_marshal_file_free( &file );
else
file->error = (MPMarshalError){ MPMarshalErrorInternal, "Couldn't allocate data." };
mpw_marshal_error( file, MPMarshalErrorInternal, "Couldn't allocate data." );
return NULL;
}
if (!user->fullName || !strlen( user->fullName )) {
if (!file_)
mpw_marshal_file_free( &file );
else
file->error = (MPMarshalError){ MPMarshalErrorMissing, "Missing full name." };
mpw_marshal_error( file, MPMarshalErrorMissing, "Missing full name." );
return NULL;
}
file->error = (MPMarshalError){ MPMarshalSuccess, NULL };
mpw_marshal_error( file, MPMarshalSuccess, NULL );
MPMasterKey masterKey = NULL;
if (user->masterKeyProvider)
@@ -749,7 +765,7 @@ const char *mpw_marshal_write(
if (!file_)
mpw_marshal_file_free( &file );
else
file->error = (MPMarshalError){ MPMarshalErrorInternal, "Couldn't derive master key." };
mpw_marshal_error( file, MPMarshalErrorInternal, "Couldn't derive master key." );
return NULL;
}
@@ -807,7 +823,7 @@ const char *mpw_marshal_write(
const char *out = NULL;
switch (outFormat) {
case MPMarshalFormatNone:
file->error = (MPMarshalError){ MPMarshalSuccess, NULL };
mpw_marshal_error( file, MPMarshalSuccess, NULL );
break;
case MPMarshalFormatFlat:
out = mpw_marshal_write_flat( file );
@@ -818,7 +834,7 @@ const char *mpw_marshal_write(
break;
#endif
default:
file->error = (MPMarshalError){ MPMarshalErrorFormat, mpw_str( "Unsupported output format: %u", outFormat ) };
mpw_marshal_error( file, MPMarshalErrorFormat, "Unsupported output format: %u", outFormat );
break;
}
if (out && file->error.type == MPMarshalSuccess)
@@ -839,7 +855,7 @@ static void mpw_marshal_read_flat(
mpw_marshal_file( file, NULL, mpw_marshal_data_new() );
if (!file->data) {
file->error = (MPMarshalError){ MPMarshalErrorInternal, "Couldn't allocate data." };
mpw_marshal_error( file, MPMarshalErrorInternal, "Couldn't allocate data." );
return;
}
@@ -891,10 +907,7 @@ static void mpw_marshal_read_flat(
const char *headerName = mpw_get_token( &positionInLine, endOfLine, ":\n" );
const char *headerValue = mpw_get_token( &positionInLine, endOfLine, "\n" );
if (!headerName || !headerValue) {
file->error = (MPMarshalError){
MPMarshalErrorStructure,
mpw_str( "Invalid header: %s", mpw_strndup( line, (size_t)(endOfLine - line) ) )
};
mpw_marshal_error( file, MPMarshalErrorStructure, "Invalid header: %s", mpw_strndup( line, (size_t)(endOfLine - line) ) );
mpw_free_strings( &headerName, &headerValue, NULL );
continue;
}
@@ -908,7 +921,7 @@ static void mpw_marshal_read_flat(
if (mpw_strcasecmp( headerName, "Algorithm" ) == OK) {
unsigned long value = strtoul( headerValue, NULL, 10 );
if (value < MPAlgorithmVersionFirst || value > MPAlgorithmVersionLast)
file->error = (MPMarshalError){ MPMarshalErrorIllegal, mpw_str( "Invalid user algorithm version: %s", headerValue ) };
mpw_marshal_error( file, MPMarshalErrorIllegal, "Invalid user algorithm version: %s", headerValue );
else
algorithm = (MPAlgorithmVersion)value;
}
@@ -923,7 +936,7 @@ static void mpw_marshal_read_flat(
if (mpw_strcasecmp( headerName, "Default Type" ) == OK) {
unsigned long value = strtoul( headerValue, NULL, 10 );
if (!mpw_type_short_name( (MPResultType)value ))
file->error = (MPMarshalError){ MPMarshalErrorIllegal, mpw_str( "Invalid user default type: %s", headerValue ) };
mpw_marshal_error( file, MPMarshalErrorIllegal, "Invalid user default type: %s", headerValue );
else
defaultType = (MPResultType)value;
}
@@ -934,7 +947,7 @@ static void mpw_marshal_read_flat(
if (!headerEnded)
continue;
if (!fullName)
file->error = (MPMarshalError){ MPMarshalErrorMissing, "Missing header: Full Name" };
mpw_marshal_error( file, MPMarshalErrorMissing, "Missing header: Full Name" );
if (positionInLine >= endOfLine)
continue;
@@ -973,7 +986,7 @@ static void mpw_marshal_read_flat(
break;
}
default: {
file->error = (MPMarshalError){ MPMarshalErrorFormat, mpw_str( "Unexpected import format: %u", format ) };
mpw_marshal_error( file, MPMarshalErrorFormat, "Unexpected import format: %u", format );
continue;
}
}
@@ -981,28 +994,24 @@ static void mpw_marshal_read_flat(
if (siteName && str_type && str_counter && str_algorithm && str_uses && str_lastUsed) {
MPResultType siteType = (MPResultType)strtoul( str_type, NULL, 10 );
if (!mpw_type_short_name( siteType )) {
file->error = (MPMarshalError){ MPMarshalErrorIllegal, mpw_str( "Invalid site type: %s: %s", siteName, str_type ) };
mpw_marshal_error( file, MPMarshalErrorIllegal, "Invalid site type: %s: %s", siteName, str_type );
continue;
}
long long int value = strtoll( str_counter, NULL, 10 );
if (value < MPCounterValueFirst || value > MPCounterValueLast) {
file->error = (MPMarshalError){ MPMarshalErrorIllegal, mpw_str( "Invalid site counter: %s: %s", siteName, str_counter ) };
mpw_marshal_error( file, MPMarshalErrorIllegal, "Invalid site counter: %s: %s", siteName, str_counter );
continue;
}
MPCounterValue siteCounter = (MPCounterValue)value;
value = strtoll( str_algorithm, NULL, 0 );
if (value < MPAlgorithmVersionFirst || value > MPAlgorithmVersionLast) {
file->error = (MPMarshalError){
MPMarshalErrorIllegal, mpw_str( "Invalid site algorithm: %s: %s", siteName, str_algorithm )
};
mpw_marshal_error( file, MPMarshalErrorIllegal, "Invalid site algorithm: %s: %s", siteName, str_algorithm );
continue;
}
MPAlgorithmVersion siteAlgorithm = (MPAlgorithmVersion)value;
time_t siteLastUsed = mpw_timegm( str_lastUsed );
if (!siteLastUsed) {
file->error = (MPMarshalError){
MPMarshalErrorIllegal, mpw_str( "Invalid site last used: %s: %s", siteName, str_lastUsed )
};
mpw_marshal_error( file, MPMarshalErrorIllegal, "Invalid site last used: %s: %s", siteName, str_lastUsed );
continue;
}
@@ -1020,11 +1029,9 @@ static void mpw_marshal_read_flat(
mpw_marshal_data_set_str( dateString, file->data, "sites", siteName, "last_used", NULL );
}
else {
file->error = (MPMarshalError){
MPMarshalErrorMissing,
mpw_str( "Missing one of: lastUsed=%s, uses=%s, type=%s, version=%s, counter=%s, loginName=%s, siteName=%s",
str_lastUsed, str_uses, str_type, str_algorithm, str_counter, siteLoginState, siteName )
};
mpw_marshal_error( file, MPMarshalErrorMissing,
"Missing one of: lastUsed=%s, uses=%s, type=%s, version=%s, counter=%s, loginName=%s, siteName=%s",
str_lastUsed, str_uses, str_type, str_algorithm, str_counter, siteLoginState, siteName );
continue;
}
@@ -1044,7 +1051,7 @@ static void mpw_marshal_read_json(
mpw_marshal_file( file, NULL, mpw_marshal_data_new() );
if (!file->data) {
file->error = (MPMarshalError){ MPMarshalErrorInternal, "Couldn't allocate data." };
mpw_marshal_error( file, MPMarshalErrorInternal, "Couldn't allocate data." );
return;
}
@@ -1052,7 +1059,7 @@ static void mpw_marshal_read_json(
enum json_tokener_error json_error = json_tokener_success;
json_object *json_file = json_tokener_parse_verbose( in, &json_error );
if (!json_file || json_error != json_tokener_success) {
file->error = (MPMarshalError){ MPMarshalErrorFormat, mpw_str( "Couldn't parse JSON: %s", json_tokener_error_desc( json_error ) ) };
mpw_marshal_error( file, MPMarshalErrorFormat, "Couldn't parse JSON: %s", json_tokener_error_desc( json_error ) );
return;
}
@@ -1074,9 +1081,9 @@ MPMarshalledFile *mpw_marshal_read(
if (!file)
return NULL;
file->error = (MPMarshalError){ MPMarshalSuccess, NULL };
mpw_marshal_error( file, MPMarshalSuccess, NULL );
if (!info) {
file->error = (MPMarshalError){ MPMarshalErrorInternal, "Couldn't allocate info." };
mpw_marshal_error( file, MPMarshalErrorInternal, "Couldn't allocate info." );
return file;
}
@@ -1090,6 +1097,8 @@ MPMarshalledFile *mpw_marshal_read(
info->format = MPMarshalFormatJSON;
#if MPW_JSON
mpw_marshal_read_json( file, in );
#else
mpw_marshal_error( file, MPMarshalErrorFormat, "JSON support is not enabled." );
#endif
}
}
@@ -1116,13 +1125,13 @@ MPMarshalledUser *mpw_marshal_auth(
if (!file)
return NULL;
file->error = (MPMarshalError){ MPMarshalSuccess, NULL };
mpw_marshal_error( file, MPMarshalSuccess, NULL );
if (!file->info) {
file->error = (MPMarshalError){ MPMarshalErrorMissing, "File wasn't parsed yet." };
mpw_marshal_error( file, MPMarshalErrorMissing, "File wasn't parsed yet." );
return NULL;
}
if (!file->data) {
file->error = (MPMarshalError){ MPMarshalErrorMissing, "No input data." };
mpw_marshal_error( file, MPMarshalErrorMissing, "No input data." );
return NULL;
}
@@ -1132,43 +1141,43 @@ MPMarshalledUser *mpw_marshal_auth(
MPAlgorithmVersion algorithm =
mpw_default_n( MPAlgorithmVersionCurrent, mpw_marshal_data_get_num( file->data, "user", "algorithm", NULL ) );
if (algorithm < MPAlgorithmVersionFirst || algorithm > MPAlgorithmVersionLast) {
file->error = (MPMarshalError){ MPMarshalErrorIllegal, mpw_str( "Invalid user algorithm: %u", algorithm ) };
mpw_marshal_error( file, MPMarshalErrorIllegal, "Invalid user algorithm: %u", algorithm );
return NULL;
}
unsigned int avatar = mpw_default_n( 0U, mpw_marshal_data_get_num( file->data, "user", "avatar", NULL ) );
const char *fullName = mpw_marshal_data_get_str( file->data, "user", "full_name", NULL );
if (!fullName || !strlen( fullName )) {
file->error = (MPMarshalError){ MPMarshalErrorMissing, "Missing value for full name." };
mpw_marshal_error( file, MPMarshalErrorMissing, "Missing value for full name." );
return NULL;
}
MPIdenticon identicon = mpw_identicon_encoded( mpw_marshal_data_get_str( file->data, "user", "identicon", NULL ) );
const char *keyID = mpw_marshal_data_get_str( file->data, "user", "key_id", NULL );
MPResultType defaultType = mpw_default_n( MPResultTypeDefault, mpw_marshal_data_get_num( file->data, "user", "default_type", NULL ) );
if (!mpw_type_short_name( defaultType )) {
file->error = (MPMarshalError){ MPMarshalErrorIllegal, mpw_str( "Invalid user default type: %u", defaultType ) };
mpw_marshal_error( file, MPMarshalErrorIllegal, "Invalid user default type: %u", defaultType );
return NULL;
}
const char *str_lastUsed = mpw_marshal_data_get_str( file->data, "user", "last_used", NULL );
time_t lastUsed = mpw_timegm( str_lastUsed );
if (!lastUsed) {
file->error = (MPMarshalError){ MPMarshalErrorIllegal, mpw_str( "Invalid user last used: %s", str_lastUsed ) };
mpw_marshal_error( file, MPMarshalErrorIllegal, "Invalid user last used: %s", str_lastUsed );
return NULL;
}
MPMasterKey masterKey = NULL;
if (masterKeyProvider && !(masterKey = masterKeyProvider( algorithm, fullName ))) {
file->error = (MPMarshalError){ MPMarshalErrorInternal, "Couldn't derive master key." };
mpw_marshal_error( file, MPMarshalErrorInternal, "Couldn't derive master key." );
return NULL;
}
if (keyID && masterKey && !mpw_id_buf_equals( keyID, mpw_id_buf( masterKey, MPMasterKeySize ) )) {
file->error = (MPMarshalError){ MPMarshalErrorMasterPassword, "Master key doesn't match key ID." };
mpw_marshal_error( file, MPMarshalErrorMasterPassword, "Master key doesn't match key ID." );
mpw_free( &masterKey, MPMasterKeySize );
return NULL;
}
MPMarshalledUser *user = NULL;
if (!(user = mpw_marshal_user( fullName, masterKeyProvider, algorithm ))) {
file->error = (MPMarshalError){ MPMarshalErrorInternal, "Couldn't allocate a new user." };
mpw_marshal_error( file, MPMarshalErrorInternal, "Couldn't allocate a new user." );
mpw_free( &masterKey, MPMasterKeySize );
mpw_marshal_user_free( &user );
return NULL;
@@ -1189,21 +1198,21 @@ MPMarshalledUser *mpw_marshal_auth(
algorithm = mpw_default_n( user->algorithm, mpw_marshal_data_get_num( siteData, "algorithm", NULL ) );
if (algorithm < MPAlgorithmVersionFirst || algorithm > MPAlgorithmVersionLast) {
file->error = (MPMarshalError){ MPMarshalErrorIllegal, mpw_str( "Invalid site algorithm: %s: %u", siteName, algorithm ) };
mpw_marshal_error( file, MPMarshalErrorIllegal, "Invalid site algorithm: %s: %u", siteName, algorithm );
mpw_free( &masterKey, MPMasterKeySize );
mpw_marshal_user_free( &user );
return NULL;
}
MPCounterValue siteCounter = mpw_default_n( MPCounterValueDefault, mpw_marshal_data_get_num( siteData, "counter", NULL ) );
if (siteCounter < MPCounterValueFirst || siteCounter > MPCounterValueLast) {
file->error = (MPMarshalError){ MPMarshalErrorIllegal, mpw_str( "Invalid site counter: %s: %d", siteName, siteCounter ) };
mpw_marshal_error( file, MPMarshalErrorIllegal, "Invalid site counter: %s: %d", siteName, siteCounter );
mpw_free( &masterKey, MPMasterKeySize );
mpw_marshal_user_free( &user );
return NULL;
}
MPResultType siteType = mpw_default_n( user->defaultType, mpw_marshal_data_get_num( siteData, "type", NULL ) );
if (!mpw_type_short_name( siteType )) {
file->error = (MPMarshalError){ MPMarshalErrorIllegal, mpw_str( "Invalid site type: %s: %u", siteName, siteType ) };
mpw_marshal_error( file, MPMarshalErrorIllegal, "Invalid site type: %s: %u", siteName, siteType );
mpw_free( &masterKey, MPMasterKeySize );
mpw_marshal_user_free( &user );
return NULL;
@@ -1211,7 +1220,7 @@ MPMarshalledUser *mpw_marshal_auth(
const char *siteResultState = mpw_marshal_data_get_str( siteData, "password", NULL );
MPResultType siteLoginType = mpw_default_n( MPResultTypeTemplateName, mpw_marshal_data_get_num( siteData, "login_type", NULL ) );
if (!mpw_type_short_name( siteLoginType )) {
file->error = (MPMarshalError){ MPMarshalErrorIllegal, mpw_str( "Invalid site login type: %s: %u", siteName, siteLoginType ) };
mpw_marshal_error( file, MPMarshalErrorIllegal, "Invalid site login type: %s: %u", siteName, siteLoginType );
mpw_free( &masterKey, MPMasterKeySize );
mpw_marshal_user_free( &user );
return NULL;
@@ -1221,7 +1230,7 @@ MPMarshalledUser *mpw_marshal_auth(
str_lastUsed = mpw_marshal_data_get_str( siteData, "last_used", NULL );
time_t siteLastUsed = mpw_timegm( str_lastUsed );
if (!siteLastUsed) {
file->error = (MPMarshalError){ MPMarshalErrorIllegal, mpw_str( "Invalid site last used: %s: %s", siteName, str_lastUsed ) };
mpw_marshal_error( file, MPMarshalErrorIllegal, "Invalid site last used: %s: %s", siteName, str_lastUsed );
mpw_free( &masterKey, MPMasterKeySize );
mpw_marshal_user_free( &user );
return NULL;
@@ -1231,7 +1240,7 @@ MPMarshalledUser *mpw_marshal_auth(
MPMarshalledSite *site = mpw_marshal_site( user, siteName, siteType, siteCounter, algorithm );
if (!site) {
file->error = (MPMarshalError){ MPMarshalErrorInternal, "Couldn't allocate a new site." };
mpw_marshal_error( file, MPMarshalErrorInternal, "Couldn't allocate a new site." );
mpw_free( &masterKey, MPMasterKeySize );
mpw_marshal_user_free( &user );
return NULL;
@@ -1245,10 +1254,7 @@ MPMarshalledUser *mpw_marshal_auth(
// Clear Text
mpw_free( &masterKey, MPMasterKeySize );
if (!masterKeyProvider || !(masterKey = masterKeyProvider( site->algorithm, user->fullName ))) {
file->error = (MPMarshalError){
MPMarshalErrorInternal,
"Couldn't derive master key."
};
mpw_marshal_error( file, MPMarshalErrorInternal, "Couldn't derive master key." );
mpw_free( &masterKey, MPMasterKeySize );
mpw_marshal_user_free( &user );
return NULL;

View File

@@ -246,6 +246,10 @@ MPMarshalledQuestion *mpw_marshal_question(
* @return The given file or new (allocated) if file is NULL; or NULL if the user is missing or the file couldn't be allocated. */
MPMarshalledFile *mpw_marshal_file(
MPMarshalledFile *file, MPMarshalledInfo *info, MPMarshalledData *data);
/** Record a marshal error.
* @return The given file or new (allocated) if file is NULL; or NULL if the file couldn't be allocated. */
MPMarshalledFile *mpw_marshal_error(
MPMarshalledFile *file, MPMarshalErrorType type, const char *format, ...);
//// Disposing.

View File

@@ -25,6 +25,7 @@
#endif
MP_LIBS_BEGIN
#define __STDC_WANT_LIB_EXT1__ 1
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
@@ -120,18 +121,17 @@ typedef mpw_enum ( uint32_t, MPCounterValue ) {
/** These colours are compatible with the original ANSI SGR. */
typedef mpw_enum( uint8_t, MPIdenticonColor ) {
MPIdenticonColorBlack,
MPIdenticonColorUnset,
MPIdenticonColorRed,
MPIdenticonColorGreen,
MPIdenticonColorYellow,
MPIdenticonColorBlue,
MPIdenticonColorMagenta,
MPIdenticonColorCyan,
MPIdenticonColorWhite,
MPIdenticonColorMono,
MPIdenticonColorUnset = MPIdenticonColorBlack,
MPIdenticonColorFirst = MPIdenticonColorRed,
MPIdenticonColorLast = MPIdenticonColorWhite,
MPIdenticonColorLast = MPIdenticonColorMono,
};
typedef struct {

View File

@@ -97,14 +97,15 @@ void mpw_log_ssink(LogLevel level, const char *file, int line, const char *funct
.message = message,
};
bool sunk = false;
for (unsigned int s = 0; s < sinks_count; ++s) {
MPLogSink *sink = sinks[s];
if (sink)
sink( &record );
sunk |= sink( &record );
}
if (!sinks_count)
mpw_log_sink_file( &record );
if (!sunk)
sunk = mpw_log_sink_file( &record );
if (record.level <= LogLevelError) {
/* error breakpoint */;
@@ -113,7 +114,7 @@ void mpw_log_ssink(LogLevel level, const char *file, int line, const char *funct
abort();
}
void mpw_log_sink_file(const MPLogEvent *record) {
bool mpw_log_sink_file(const MPLogEvent *record) {
if (!mpw_log_sink_file_target)
mpw_log_sink_file_target = stderr;
@@ -145,6 +146,7 @@ void mpw_log_sink_file(const MPLogEvent *record) {
}
fprintf( mpw_log_sink_file_target, "%s\n", record->message );
return true;
}
void mpw_uint16(const uint16_t number, uint8_t buf[2]) {

View File

@@ -54,7 +54,7 @@ typedef struct {
} MPLogEvent;
/** A log sink describes a function that can receive log events. */
typedef void (MPLogSink)(const MPLogEvent *event);
typedef bool (MPLogSink)(const MPLogEvent *event);
/** To receive events, sinks need to be registered. If no sinks are registered, log events are sent to the mpw_log_sink_file sink. */
bool mpw_log_sink_register(MPLogSink *sink);

View File

@@ -87,6 +87,29 @@ public interface MPAlgorithm {
MPKeyPurpose keyPurpose, @Nullable String keyContext,
MPResultType resultType, String resultParam);
/**
* Derive an identicon that represents the user's identity in a visually recognizable way.
*
* @param fullName The name of the user whose identity is described by the key.
* @param masterPassword The user's secret that authenticates his access to the identity.
*/
MPIdenticon identicon(final String fullName, final char[] masterPassword);
/**
* Encode a fingerprint for a message.
*/
String toID(final String string);
/**
* Encode a fingerprint for a char buffer.
*/
String toID(final char[] message);
/**
* Encode a fingerprint for a byte buffer.
*/
String toID(final byte[] buffer);
// Configuration
/**
@@ -125,69 +148,6 @@ public interface MPAlgorithm {
@Nonnull
Charset mpw_charset();
/**
* mpw: Platform-agnostic byte order.
*/
@Nonnull
ByteOrder mpw_byteOrder();
/**
* mpw: Key ID hash.
*/
@Nonnull
MessageDigests mpw_hash();
/**
* mpw: Site digest.
*/
@Nonnull
MessageAuthenticationDigests mpw_digest();
/**
* mpw: Master key size (byte).
*/
int mpw_dkLen();
/**
* mpw: Minimum size for derived keys (bit).
*/
int mpw_keySize_min();
/**
* mpw: Maximum size for derived keys (bit).
*/
int mpw_keySize_max();
/**
* mpw: validity for the time-based rolling counter (s).
*/
long mpw_otp_window();
/**
* scrypt: CPU cost parameter.
*/
int scrypt_N();
/**
* scrypt: Memory cost parameter.
*/
int scrypt_r();
/**
* scrypt: Parallelization parameter.
*/
int scrypt_p();
// Utilities
byte[] toBytes(final int number);
byte[] toBytes(final UnsignedInteger number);
byte[] toBytes(final char[] characters);
byte[] toID(final byte[] bytes);
/**
* The algorithm iterations.
*/
@@ -222,12 +182,9 @@ public interface MPAlgorithm {
public static final Version CURRENT = V3;
@SuppressWarnings("HardcodedFileSeparator")
private static final String AES_TRANSFORMATION = "AES/CBC/PKCS5Padding";
private static final int AES_BLOCKSIZE = 128 /* bit */;
static {
Native.load( MPAlgorithm.class, "mpw" );
if (!Native.load( MPAlgorithm.class, "mpw" ))
Logger.get( MPAlgorithm.class ).err( "Native mpw library unavailable." );
}
protected final Logger logger = Logger.get( getClass() );
@@ -320,6 +277,70 @@ public interface MPAlgorithm {
final int keyPurpose, @Nullable final String keyContext,
final int resultType, final String resultParam, final int algorithmVersion);
@Nullable
@Override
public MPIdenticon identicon(final String fullName, final char[] masterPassword) {
// Create a memory-safe NUL-terminated UTF-8 C-string byte array variant of masterPassword.
CharsetEncoder encoder = mpw_charset().newEncoder();
byte[] masterPasswordBytes = new byte[(int) (masterPassword.length * (double) encoder.maxBytesPerChar()) + 1];
try {
Arrays.fill( masterPasswordBytes, (byte) 0 );
ByteBuffer masterPasswordBuffer = ByteBuffer.wrap( masterPasswordBytes );
CoderResult result = encoder.encode( CharBuffer.wrap( masterPassword ), masterPasswordBuffer, true );
if (result.isError())
throw new IllegalStateException( result.toString() );
result = encoder.flush( masterPasswordBuffer );
if (result.isError())
throw new IllegalStateException( result.toString() );
return _identicon( fullName, masterPasswordBytes );
}
finally {
Arrays.fill( masterPasswordBytes, (byte) 0 );
}
}
@Nullable
protected native MPIdenticon _identicon(final String fullName, final byte[] masterPassword);
@Override
public String toID(final String message) {
return toID( message.toCharArray() );
}
@Override
public String toID(final char[] message) {
// Create a memory-safe NUL-terminated UTF-8 C-string byte array variant of masterPassword.
CharsetEncoder encoder = mpw_charset().newEncoder();
byte[] messageBytes = new byte[(int) (message.length * (double) encoder.maxBytesPerChar()) + 1];
try {
Arrays.fill( messageBytes, (byte) 0 );
ByteBuffer messageBuffer = ByteBuffer.wrap( messageBytes );
CoderResult result = encoder.encode( CharBuffer.wrap( message ), messageBuffer, true );
if (result.isError())
throw new IllegalStateException( result.toString() );
result = encoder.flush( messageBuffer );
if (result.isError())
throw new IllegalStateException( result.toString() );
return toID( messageBytes );
}
finally {
Arrays.fill( messageBytes, (byte) 0 );
}
}
@Override
public String toID(final byte[] buffer) {
return _toID( buffer );
}
@Nullable
protected native String _toID(final byte[] buffer);
// Configuration
@Nonnull
@@ -357,93 +378,5 @@ public interface MPAlgorithm {
public Charset mpw_charset() {
return Charsets.UTF_8;
}
@Nonnull
@Override
public ByteOrder mpw_byteOrder() {
return ByteOrder.BIG_ENDIAN;
}
@Nonnull
@Override
public MessageDigests mpw_hash() {
return MessageDigests.SHA256;
}
@Nonnull
@Override
public MessageAuthenticationDigests mpw_digest() {
return MessageAuthenticationDigests.HmacSHA256;
}
@Override
@SuppressWarnings("MagicNumber")
public int mpw_dkLen() {
return 64;
}
@Override
@SuppressWarnings("MagicNumber")
public int mpw_keySize_min() {
return 128;
}
@Override
@SuppressWarnings("MagicNumber")
public int mpw_keySize_max() {
return 512;
}
@Override
@SuppressWarnings("MagicNumber")
public long mpw_otp_window() {
return 5 * 60 /* s */;
}
@Override
@SuppressWarnings("MagicNumber")
public int scrypt_N() {
return 32768;
}
@Override
@SuppressWarnings("MagicNumber")
public int scrypt_r() {
return 8;
}
@Override
@SuppressWarnings("MagicNumber")
public int scrypt_p() {
return 2;
}
// Utilities
@Nonnull
public byte[] toBytes(final int number) {
return ByteBuffer.allocate( Integer.SIZE / Byte.SIZE ).order( mpw_byteOrder() ).putInt( number ).array();
}
@Nonnull
public byte[] toBytes(final UnsignedInteger number) {
return ByteBuffer.allocate( Integer.SIZE / Byte.SIZE ).order( mpw_byteOrder() ).putInt( number.intValue() ).array();
}
@Nonnull
public byte[] toBytes(final char[] characters) {
ByteBuffer byteBuffer = mpw_charset().encode( CharBuffer.wrap( characters ) );
byte[] bytes = new byte[byteBuffer.remaining()];
byteBuffer.get( bytes );
Arrays.fill( byteBuffer.array(), (byte) 0 );
return bytes;
}
@Nonnull
public byte[] toID(final byte[] bytes) {
return mpw_hash().of( bytes );
}
}
}

View File

@@ -20,14 +20,8 @@ package com.lyndir.masterpassword;
import static com.lyndir.lhunath.opal.system.util.StringUtils.*;
import com.google.common.base.Charsets;
import com.google.common.primitives.UnsignedBytes;
import com.lyndir.lhunath.opal.system.MessageAuthenticationDigests;
import com.lyndir.lhunath.opal.system.logging.Logger;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.nio.*;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.Locale;
@@ -39,43 +33,23 @@ public class MPIdenticon {
@SuppressWarnings("UnusedDeclaration")
private static final Logger logger = Logger.get( MPIdenticon.class );
private static final Charset charset = Charsets.UTF_8;
private static final Color[] colors = {
Color.RED, Color.GREEN, Color.YELLOW, Color.BLUE, Color.MAGENTA, Color.CYAN, Color.MONO };
private static final char[] leftArm = { '╔', '╚', '╰', '═' };
private static final char[] rightArm = { '╗', '╝', '╯', '═' };
private static final char[] body = { '█', '░', '▒', '▓', '☺', '☻' };
private static final char[] accessory = {
'◈', '◎', '◐', '◑', '◒', '◓', '☀', '☁', '☂', '☃', '☄', '★', '☆', '☎', '☏', '⎈', '⌂', '☘', '☢', '☣', '☕', '⌚', '⌛', '⏰', '⚡',
'⛄', '⛅', '☔', '♔', '♕', '♖', '♗', '♘', '♙', '♚', '♛', '♜', '♝', '♞', '♟', '♨', '♩', '♪', '♫', '⚐', '⚑', '⚔', '⚖', '⚙', '⚠',
'⌘', '⏎', '✄', '✆', '✈', '✉', '✌' };
private final String fullName;
private final String leftArm;
private final String body;
private final String rightArm;
private final String accessory;
private final Color color;
private final String text;
public MPIdenticon(final String fullName, final String masterPassword) {
this( fullName, masterPassword.toCharArray() );
}
@SuppressFBWarnings("CLI_CONSTANT_LIST_INDEX")
@SuppressWarnings("MethodCanBeVariableArityMethod")
public MPIdenticon(final String fullName, final char[] masterPassword) {
public MPIdenticon(final String fullName, final String leftArm, final String body, final String rightArm, final String accessory,
final Color color) {
this.fullName = fullName;
byte[] masterPasswordBytes = charset.encode( CharBuffer.wrap( masterPassword ) ).array();
ByteBuffer identiconSeedBytes = ByteBuffer.wrap(
MessageAuthenticationDigests.HmacSHA256.of( masterPasswordBytes, fullName.getBytes( charset ) ) );
Arrays.fill( masterPasswordBytes, (byte) 0 );
IntBuffer identiconSeedBuffer = IntBuffer.allocate( identiconSeedBytes.capacity() );
while (identiconSeedBytes.hasRemaining())
identiconSeedBuffer.put( UnsignedBytes.toInt( identiconSeedBytes.get() ) );
int[] identiconSeed = identiconSeedBuffer.array();
color = colors[identiconSeed[4] % colors.length];
text = strf( "%c%c%c%c", leftArm[identiconSeed[0] % leftArm.length], body[identiconSeed[1] % body.length],
rightArm[identiconSeed[2] % rightArm.length], accessory[identiconSeed[3] % accessory.length] );
this.leftArm = leftArm;
this.body = body;
this.rightArm = rightArm;
this.accessory = accessory;
this.color = color;
}
public String getFullName() {
@@ -83,11 +57,11 @@ public class MPIdenticon {
}
public String getText() {
return text;
return strf( "%s%s%s%s", this.leftArm, this.body, this.rightArm, this.accessory );
}
public String getHTML() {
return strf( "<span style='color: %s'>%s</span>", color.getCSS(), text );
return strf( "<span style='color: %s'>%s</span>", color.getCSS(), getText() );
}
public Color getColor() {
@@ -95,6 +69,12 @@ public class MPIdenticon {
}
public enum Color {
UNSET {
@Override
public String getCSS() {
return "inherit";
}
},
RED,
GREEN,
YELLOW,

View File

@@ -55,6 +55,18 @@ public class MPMasterKey {
Arrays.fill( masterPassword, (char) 0 );
}
@Override
protected void finalize()
throws Throwable {
if (isValid()) {
logger.wrn( "A master key for %s was abandoned without being invalidated.", getFullName() );
invalidate();
}
super.finalize();
}
@Nonnull
public String getFullName() {
@@ -67,7 +79,7 @@ public class MPMasterKey {
* @throws MPKeyUnavailableException {@link #invalidate()} has been called on this object.
*/
@Nonnull
public byte[] getKeyID(final MPAlgorithm algorithm)
public String getKeyID(final MPAlgorithm algorithm)
throws MPKeyUnavailableException, MPAlgorithmException {
return algorithm.toID( masterKey( algorithm ) );
@@ -98,11 +110,6 @@ public class MPMasterKey {
byte[] masterKey = keyByVersion.get( algorithm.version() );
if (masterKey == null) {
logger.trc( "-- mpw_masterKey (algorithm: %s)", algorithm );
logger.trc( "fullName: %s", fullName );
logger.trc( "masterPassword.id: %s", CodeUtils.encodeHex(
algorithm.toID( algorithm.toBytes( masterPassword ) ) ) );
keyByVersion.put( algorithm.version(), masterKey = algorithm.masterKey( fullName, masterPassword ) );
}
if (masterKey == null)
@@ -118,13 +125,6 @@ public class MPMasterKey {
Preconditions.checkArgument( !siteName.isEmpty() );
byte[] masterKey = masterKey( algorithm );
logger.trc( "-- mpw_siteKey (algorithm: %s)", algorithm );
logger.trc( "siteName: %s", siteName );
logger.trc( "siteCounter: %s", siteCounter );
logger.trc( "keyPurpose: %d (%s)", keyPurpose.toInt(), keyPurpose.getShortName() );
logger.trc( "keyContext: %s", keyContext );
byte[] siteKey = algorithm.siteKey( masterKey, siteName, siteCounter, keyPurpose, keyContext );
if (siteKey == null)
throw new MPAlgorithmException( "Could not derive site key." );
@@ -161,10 +161,6 @@ public class MPMasterKey {
byte[] masterKey = masterKey( algorithm );
byte[] siteKey = siteKey( siteName, algorithm, siteCounter, keyPurpose, keyContext );
logger.trc( "-- mpw_siteResult (algorithm: %s)", algorithm );
logger.trc( "resultType: %d (%s)", resultType.getType(), resultType.getShortName() );
logger.trc( "resultParam: %s", resultParam );
String siteResult = algorithm.siteResult(
masterKey, siteKey, siteName, siteCounter, keyPurpose, keyContext, resultType, resultParam );
if (siteResult == null)
@@ -199,10 +195,6 @@ public class MPMasterKey {
byte[] masterKey = masterKey( algorithm );
byte[] siteKey = siteKey( siteName, algorithm, siteCounter, keyPurpose, keyContext );
logger.trc( "-- mpw_siteState (algorithm: %s)", algorithm );
logger.trc( "resultType: %d (%s)", resultType.getType(), resultType.getShortName() );
logger.trc( "resultParam: %d bytes = %s", resultParam.getBytes( algorithm.mpw_charset() ).length, resultParam );
String siteState = algorithm.siteState(
masterKey, siteKey, siteName, siteCounter, keyPurpose, keyContext, resultType, resultParam );
if (siteState == null)

View File

@@ -74,9 +74,9 @@ public final class Native {
// Write the library resource to a temporary file.
libraryFile = File.createTempFile( libraryName, libraryExtension );
libraryFile.deleteOnExit();
FileOutputStream libraryFileStream = new FileOutputStream( libraryFile );
try {
libraryFile.deleteOnExit();
ByteStreams.copy( libraryStream, libraryFileStream );
}
finally {
@@ -91,9 +91,8 @@ public final class Native {
catch (@SuppressWarnings("ErrorNotRethrown") final IOException | UnsatisfiedLinkError e) {
logger.wrn( e, "Couldn't load library: %s", libraryResource );
if (libraryFile != null)
if (libraryFile.exists() && !libraryFile.delete())
logger.wrn( "Couldn't clean up library file: %s", libraryFile );
if (libraryFile != null && libraryFile.exists() && !libraryFile.delete())
logger.wrn( "Couldn't clean up library file: %s", libraryFile );
libraryFile = null;
}
}

View File

@@ -35,7 +35,7 @@ public class MPIncognitoUser extends MPBasicUser<MPIncognitoSite> {
@Nullable
@Override
public byte[] getKeyID() {
public String getKeyID() {
return null;
}

View File

@@ -35,6 +35,7 @@ import javax.swing.border.Border;
import javax.swing.border.CompoundBorder;
import javax.swing.event.HyperlinkEvent;
import javax.swing.text.*;
import javax.swing.undo.UndoableEdit;
import org.jetbrains.annotations.NonNls;
@@ -207,7 +208,13 @@ public abstract class Components {
}
public static JPasswordField passwordField() {
return new JPasswordField() {
return new JPasswordField( new PlainDocument( new GapContent() {
@Override
public String getString(final int where, final int len)
throws BadLocationException {
return "";
}
} ), null, 0 ) {
{
setBorder( BorderFactory.createCompoundBorder( BorderFactory.createLineBorder( Res.colors().controlBorder(), 1, true ),
BorderFactory.createEmptyBorder( 4, 4, 4, 4 ) ) );

View File

@@ -182,7 +182,7 @@ public abstract class Res {
public static final class Fonts {
public Font emoticonsFont(final int size) {
public Font identiconFont(final int size) {
return MPFont.emoticonsRegular.get( size );
}

View File

@@ -317,7 +317,7 @@ public class UserContentPanel extends JPanel implements State.Listener, MPUser.L
add( Components.strut() );
add( identiconLabel = Components.label( SwingConstants.CENTER ) );
identiconLabel.setFont( Res.fonts().emoticonsFont( Components.TEXT_SIZE_CONTROL ) );
identiconLabel.setFont( Res.fonts().identiconFont( Components.TEXT_SIZE_CONTROL ) );
add( Box.createGlue() );
add( Components.label( "Master Password:" ) );
@@ -330,6 +330,15 @@ public class UserContentPanel extends JPanel implements State.Listener, MPUser.L
add( Box.createGlue() );
}
@Override
public void removeNotify() {
char[] password = masterPasswordField.getPassword();
Arrays.fill( password, (char) 0 );
masterPasswordField.setText( new String( password ) );
super.removeNotify();
}
private void exportUser() {
MPFileUser fileUser = (user instanceof MPFileUser)? (MPFileUser) user: null;
if (fileUser == null)
@@ -449,7 +458,8 @@ public class UserContentPanel extends JPanel implements State.Listener, MPUser.L
private void updateIdenticon() {
char[] masterPassword = masterPasswordField.getPassword();
MPIdenticon identicon = ((masterPassword != null) && (masterPassword.length > 0))?
new MPIdenticon( user.getFullName(), masterPassword ): null;
user.getAlgorithm().identicon( user.getFullName(), masterPassword ): null;
Arrays.fill( masterPassword, (char) 0 );
Res.ui( () -> {
if (identicon != null) {
@@ -779,7 +789,7 @@ public class UserContentPanel extends JPanel implements State.Listener, MPUser.L
typeModel.selection( MPResultType.DeriveKey, t -> {
switch (t) {
case DeriveKey:
stateModel.setText( Integer.toString( site.getAlgorithm().mpw_keySize_min() ) );
stateModel.setText( "128" );
break;
default:

View File

@@ -49,10 +49,7 @@ public interface MPUser<S extends MPSite<?>> extends Comparable<MPUser<?>> {
void setAlgorithm(MPAlgorithm algorithm);
@Nullable
byte[] getKeyID();
@Nullable
String exportKeyID();
String getKeyID();
/**
* Performs an authentication attempt against the keyID for this user.

View File

@@ -100,7 +100,7 @@ public abstract class MPBasicUser<S extends MPBasicSite<?, ?>> extends Changeabl
@Nullable
@Override
public byte[] getKeyID() {
public String getKeyID() {
try {
if (isMasterKeyAvailable())
return getMasterKey().getKeyID( getAlgorithm() );
@@ -112,12 +112,6 @@ public abstract class MPBasicUser<S extends MPBasicSite<?, ?>> extends Changeabl
return null;
}
@Nullable
@Override
public String exportKeyID() {
return CodeUtils.encodeHex( getKeyID() );
}
@Override
public void authenticate(final char[] masterPassword)
throws MPIncorrectMasterPasswordException, MPAlgorithmException {
@@ -136,8 +130,8 @@ public abstract class MPBasicUser<S extends MPBasicSite<?, ?>> extends Changeabl
throw new IllegalArgumentException(
"Master key (for " + masterKey.getFullName() + ") is not for this user (" + getFullName() + ")." );
byte[] keyID = getKeyID();
if ((keyID != null) && !Arrays.equals( masterKey.getKeyID( getAlgorithm() ), keyID ))
String keyID = getKeyID();
if (keyID != null && !keyID.equalsIgnoreCase( masterKey.getKeyID( getAlgorithm() ) ))
throw new MPIncorrectMasterPasswordException( this );
this.masterKey = masterKey;

View File

@@ -39,7 +39,7 @@ public class MPFileUser extends MPBasicUser<MPFileSite> {
private static final Logger logger = Logger.get( MPFileUser.class );
@Nullable
private byte[] keyID;
private String keyID;
private File file;
private MPMarshalFormat format;
private MPMarshaller.ContentMode contentMode;
@@ -62,18 +62,18 @@ public class MPFileUser extends MPBasicUser<MPFileSite> {
this( fullName, null, MPAlgorithm.Version.CURRENT, location );
}
public MPFileUser(final String fullName, @Nullable final byte[] keyID, final MPAlgorithm algorithm, final File location) {
public MPFileUser(final String fullName, @Nullable final String keyID, final MPAlgorithm algorithm, final File location) {
this( fullName, keyID, algorithm, 0, null, new Instant(), false,
MPMarshaller.ContentMode.PROTECTED, MPMarshalFormat.DEFAULT, location );
}
@SuppressFBWarnings("PATH_TRAVERSAL_IN")
public MPFileUser(final String fullName, @Nullable final byte[] keyID, final MPAlgorithm algorithm, final int avatar,
public MPFileUser(final String fullName, @Nullable final String keyID, final MPAlgorithm algorithm, final int avatar,
@Nullable final MPResultType defaultType, final ReadableInstant lastUsed, final boolean hidePasswords,
final MPMarshaller.ContentMode contentMode, final MPMarshalFormat format, final File location) {
super( avatar, fullName, algorithm );
this.keyID = (keyID != null)? keyID.clone(): null;
this.keyID = keyID;
this.lastUsed = lastUsed;
this.preferences = new MPFileUserPreferences( this, defaultType, hidePasswords );
this.format = format;
@@ -87,8 +87,8 @@ public class MPFileUser extends MPBasicUser<MPFileSite> {
@Nullable
@Override
public byte[] getKeyID() {
return (keyID == null)? null: keyID.clone();
public String getKeyID() {
return keyID;
}
@Nonnull

View File

@@ -54,7 +54,7 @@ public class MPFlatMarshaller implements MPMarshaller {
content.append( "# User Name: " ).append( user.getFullName() ).append( '\n' );
content.append( "# Full Name: " ).append( user.getFullName() ).append( '\n' );
content.append( "# Avatar: " ).append( user.getAvatar() ).append( '\n' );
content.append( "# Key ID: " ).append( user.exportKeyID() ).append( '\n' );
content.append( "# Key ID: " ).append( user.getKeyID() ).append( '\n' );
content.append( "# Algorithm: " ).append( user.getAlgorithm().version().toInt() ).append( '\n' );
content.append( "# Default Type: " ).append( user.getPreferences().getDefaultType().getType() ).append( '\n' );
content.append( "# Passwords: " ).append( user.getContentMode().name() ).append( '\n' );

View File

@@ -51,7 +51,7 @@ public class MPFlatUnmarshaller implements MPUnmarshaller {
public MPFileUser readUser(@Nonnull final File file)
throws IOException, MPMarshalException {
try (Reader reader = new InputStreamReader( new FileInputStream( file ), Charsets.UTF_8 )) {
byte[] keyID = null;
String keyID = null;
String fullName = null;
int mpVersion = 0, avatar = 0;
boolean clearContent = false, headerStarted = false;
@@ -84,7 +84,7 @@ public class MPFlatUnmarshaller implements MPUnmarshaller {
if ("Full Name".equalsIgnoreCase( name ) || "User Name".equalsIgnoreCase( name ))
fullName = value;
else if ("Key ID".equalsIgnoreCase( name ))
keyID = CodeUtils.decodeHex( value );
keyID = value;
else if ("Algorithm".equalsIgnoreCase( name ))
mpVersion = ConversionUtils.toIntegerNN( value );
else if ("Avatar".equalsIgnoreCase( name ))

View File

@@ -61,7 +61,7 @@ public class MPJSONFile extends MPJSONAnyObject {
user.avatar = modelUser.getAvatar();
user.full_name = modelUser.getFullName();
user.last_used = MPModelConstants.dateTimeFormatter.print( modelUser.getLastUsed() );
user.key_id = modelUser.exportKeyID();
user.key_id = modelUser.getKeyID();
user.algorithm = modelUser.getAlgorithm().version();
user._ext_mpw = new User.Ext() {
{
@@ -131,7 +131,7 @@ public class MPJSONFile extends MPJSONAnyObject {
MPAlgorithm algorithm = ifNotNullElse( user.algorithm, MPAlgorithm.Version.CURRENT );
return new MPFileUser(
user.full_name, CodeUtils.decodeHex( user.key_id ), algorithm, user.avatar,
user.full_name, user.key_id, algorithm, user.avatar,
(user._ext_mpw != null)? user._ext_mpw.default_type: null,
(user.last_used != null)? MPModelConstants.dateTimeFormatter.parseDateTime( user.last_used ): new Instant(),
(user._ext_mpw != null) && user._ext_mpw.hide_passwords,

View File

@@ -53,9 +53,8 @@ public class MPMasterKeyTest {
MPMasterKey masterKey = new MPMasterKey( testCase.getFullName(), masterPassword );
// Test key
assertEquals(
CodeUtils.encodeHex( masterKey.getKeyID( testCase.getAlgorithm() ) ),
testCase.getKeyID(),
assertTrue(
testCase.getKeyID().equalsIgnoreCase( masterKey.getKeyID( testCase.getAlgorithm() ) ),
"[testMasterKey] keyID mismatch for test case: " + testCase );
// Test invalidation