Compare commits
46 Commits
1.2.0-apps
...
1.3.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c928b1ca2c | ||
|
|
553a14dced | ||
|
|
8b8d727ee0 | ||
|
|
4cdeab4256 | ||
|
|
bc3aa3255e | ||
|
|
f2fdca6a03 | ||
|
|
647235616e | ||
|
|
b0b6dcc56b | ||
|
|
918a240dba | ||
|
|
830dcb45ff | ||
|
|
7be9884075 | ||
|
|
5ca0d954bb | ||
|
|
02d69261df | ||
|
|
fc60460935 | ||
|
|
559e11b16e | ||
|
|
0a72809b02 | ||
|
|
8c71ed0081 | ||
|
|
3e19a026ba | ||
|
|
8fa3c6c75d | ||
|
|
217cf56d94 | ||
|
|
6f37f28a4c | ||
|
|
3b7d2dc08e | ||
|
|
5e9af44736 | ||
|
|
be33a29fa0 | ||
|
|
29ed22d0b7 | ||
|
|
8b997528c9 | ||
|
|
04a6c8e68d | ||
|
|
0e8e4dc06d | ||
|
|
8b91c2a0b8 | ||
|
|
e967affddb | ||
|
|
dea7434bd4 | ||
|
|
1da63e450d | ||
|
|
029d240999 | ||
|
|
110f7069e1 | ||
|
|
d77cde1929 | ||
|
|
2dba4c87ba | ||
|
|
6841ae2b0d | ||
|
|
a3698b9e47 | ||
|
|
5b65c8c6bd | ||
|
|
94c9d50a12 | ||
|
|
5849c9668f | ||
|
|
4d4ba3425e | ||
|
|
b7e91358be | ||
|
|
da860d74c4 | ||
|
|
4e2ceb33a0 | ||
|
|
d14bde07bd |
8
.gitignore
vendored
@@ -20,6 +20,14 @@
|
||||
!/*.xcodeproj/project.xcworkspace/*
|
||||
/*.xcodeproj/project.xcworkspace/xcuserdata
|
||||
|
||||
# Media
|
||||
Press/Background.png
|
||||
Press/Front-Page.png
|
||||
Press/MasterPassword_PressKit/MasterPassword_pressrelease_*.pdf
|
||||
|
||||
# IPA
|
||||
/sendipa/*
|
||||
!/sendipa/sendipa.conf
|
||||
|
||||
# Java
|
||||
MasterPassword/Java/**/target
|
||||
|
||||
3
.gitmodules
vendored
@@ -7,3 +7,6 @@
|
||||
[submodule "External/iCloudStoreManager"]
|
||||
path = External/iCloudStoreManager
|
||||
url = git://github.com/lhunath/iCloudStoreManager.git
|
||||
[submodule "External/FontReplacer"]
|
||||
path = External/FontReplacer
|
||||
url = git://github.com/0xced/FontReplacer.git
|
||||
|
||||
2
.idea/inspectionProfiles/Project_Default.xml
generated
@@ -2,7 +2,9 @@
|
||||
<profile version="1.0" is_locked="false">
|
||||
<option name="myName" value="Project Default" />
|
||||
<option name="myLocal" value="false" />
|
||||
<inspection_tool class="FunctionImplicitDeclarationInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="LossyEncoding" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="MethodIsLaterInTheScope" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="OCUnusedMethodInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="UnusedLocalVariable" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
</profile>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>BuildMachineOSBuild</key>
|
||||
<string>11C74</string>
|
||||
<string>11E53</string>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>English</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
@@ -17,7 +17,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.1.1</string>
|
||||
<string>1.1.5</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleSupportedPlatforms</key>
|
||||
@@ -25,7 +25,7 @@
|
||||
<string>iPhoneOS</string>
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>0101.01.00</string>
|
||||
<string>0101.05.00</string>
|
||||
<key>CrashlyticsAPIKey</key>
|
||||
<string>0d10c90776f5ef5acd01ddbeaca9a6cba4814560</string>
|
||||
<key>DTCompiler</key>
|
||||
|
||||
1
External/FontReplacer
vendored
Submodule
2
External/Pearl
vendored
2
External/iCloudStoreManager
vendored
@@ -7,12 +7,20 @@
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
93D390BC6AE7A1C9B91A3668 /* MPKey.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39E81EFABC6085AC8AE69 /* MPKey.m */; };
|
||||
93D392B30CE6C58A9A905E0A /* MPAlgorithmV0.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D3938863322199C3E7E2E3 /* MPAlgorithmV0.m */; };
|
||||
93D394744B5485303B326ECB /* MPAlgorithm.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39B0DF5E3C56355186738 /* MPAlgorithm.m */; };
|
||||
93D39DC7A7282137B08C8D82 /* MPAlgorithmV1.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39E9D7B9005211E7D5262 /* MPAlgorithmV1.m */; };
|
||||
DA04E33E14B1E70400ECA4F3 /* MobileCoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA04E33D14B1E70400ECA4F3 /* MobileCoreServices.framework */; };
|
||||
DA0A1D0515690A9A0092735D /* Default.png in Resources */ = {isa = PBXBuildFile; fileRef = DA0A1D0315690A9A0092735D /* Default.png */; };
|
||||
DA0A1D0615690A9A0092735D /* Default@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA0A1D0415690A9A0092735D /* Default@2x.png */; };
|
||||
DA0A1D1515690AF40092735D /* Icon-72@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA0A1D1315690AF30092735D /* Icon-72@2x.png */; };
|
||||
DA0A1D1615690AF40092735D /* Icon-Small-50@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA0A1D1415690AF40092735D /* Icon-Small-50@2x.png */; };
|
||||
DA0E07961577FE490008A67E /* MPEntities.m in Sources */ = {isa = PBXBuildFile; fileRef = DA0E07951577FE490008A67E /* MPEntities.m */; };
|
||||
DA0F9F3315B55397007ED9BC /* MPUserEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DA0F9F3215B55397007ED9BC /* MPUserEntity.m */; };
|
||||
DA0F9F3615B55397007ED9BC /* MPElementGeneratedEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DA0F9F3515B55397007ED9BC /* MPElementGeneratedEntity.m */; };
|
||||
DA0F9F3915B55397007ED9BC /* MPElementStoredEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DA0F9F3815B55397007ED9BC /* MPElementStoredEntity.m */; };
|
||||
DA0F9F3C15B55397007ED9BC /* MPElementEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DA0F9F3B15B55397007ED9BC /* MPElementEntity.m */; };
|
||||
DA30E9CE15722ECA00A68B4C /* NSBundle+PearlMutableInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = DA30E9CB15722ECA00A68B4C /* NSBundle+PearlMutableInfo.h */; };
|
||||
DA30E9CF15722ECA00A68B4C /* NSBundle+PearlMutableInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = DA30E9CC15722ECA00A68B4C /* NSBundle+PearlMutableInfo.m */; };
|
||||
DA30E9D015722ECA00A68B4C /* Pearl.m in Sources */ = {isa = PBXBuildFile; fileRef = DA30E9CD15722ECA00A68B4C /* Pearl.m */; };
|
||||
@@ -20,16 +28,19 @@
|
||||
DA30E9D415722EF400A68B4C /* Pearl-UIKit.m in Sources */ = {isa = PBXBuildFile; fileRef = DA30E9D315722EF400A68B4C /* Pearl-UIKit.m */; };
|
||||
DA30E9D715723E6900A68B4C /* PearlLazy.h in Headers */ = {isa = PBXBuildFile; fileRef = DA30E9D515723E6900A68B4C /* PearlLazy.h */; };
|
||||
DA30E9D815723E6900A68B4C /* PearlLazy.m in Sources */ = {isa = PBXBuildFile; fileRef = DA30E9D615723E6900A68B4C /* PearlLazy.m */; };
|
||||
DA40C2611586099D0079CE6E /* MPUserEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DA40C2601586099D0079CE6E /* MPUserEntity.m */; };
|
||||
DA40C2641586099E0079CE6E /* MPElementEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DA40C2631586099E0079CE6E /* MPElementEntity.m */; };
|
||||
DA40C2671586099E0079CE6E /* MPElementGeneratedEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DA40C2661586099E0079CE6E /* MPElementGeneratedEntity.m */; };
|
||||
DA40C26A1586099E0079CE6E /* MPElementStoredEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DA40C2691586099E0079CE6E /* MPElementStoredEntity.m */; };
|
||||
DA3EF17B15A47744003ABF4E /* SenTestingKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA3EF17A15A47744003ABF4E /* SenTestingKit.framework */; };
|
||||
DA3EF17C15A47744003ABF4E /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA5BFA48147E415C00F98B1E /* UIKit.framework */; };
|
||||
DA3EF17D15A47744003ABF4E /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA5BFA4A147E415C00F98B1E /* Foundation.framework */; };
|
||||
DA3EF18315A47744003ABF4E /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = DA3EF18115A47744003ABF4E /* InfoPlist.strings */; };
|
||||
DA3EF18615A47744003ABF4E /* Tests.m in Sources */ = {isa = PBXBuildFile; fileRef = DA3EF18515A47744003ABF4E /* Tests.m */; };
|
||||
DA4425CC1557BED40052177D /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA5BFA4A147E415C00F98B1E /* Foundation.framework */; };
|
||||
DA4426001557BF260052177D /* UbiquityStoreManager.h in Headers */ = {isa = PBXBuildFile; fileRef = DA4425F11557BF260052177D /* UbiquityStoreManager.h */; };
|
||||
DA4426011557BF260052177D /* UbiquityStoreManager.m in Sources */ = {isa = PBXBuildFile; fileRef = DA4425F21557BF260052177D /* UbiquityStoreManager.m */; };
|
||||
DA4426081557C1990052177D /* MPAppDelegate_Shared.m in Sources */ = {isa = PBXBuildFile; fileRef = DA4426051557C1990052177D /* MPAppDelegate_Shared.m */; };
|
||||
DA4426091557C1990052177D /* MPAppDelegate_Store.m in Sources */ = {isa = PBXBuildFile; fileRef = DA4426071557C1990052177D /* MPAppDelegate_Store.m */; };
|
||||
DA44260A1557D9E40052177D /* libiCloudStoreManager.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DA4425CB1557BED40052177D /* libiCloudStoreManager.a */; };
|
||||
DA46826F15AB843200FB09E7 /* tip_basic_black_bottom_right.png in Resources */ = {isa = PBXBuildFile; fileRef = DA46826D15AB843200FB09E7 /* tip_basic_black_bottom_right.png */; };
|
||||
DA46827015AB843200FB09E7 /* tip_basic_black_bottom_right@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA46826E15AB843200FB09E7 /* tip_basic_black_bottom_right@2x.png */; };
|
||||
DA4DA1D91564471A00F6F596 /* libjrswizzle.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DAC6326C148680650075AEA5 /* libjrswizzle.a */; };
|
||||
DA4DA1DA1564471F00F6F596 /* libuicolor-utilities.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DAC6325D1486805C0075AEA5 /* libuicolor-utilities.a */; };
|
||||
DA4DA1DB1564475E00F6F596 /* libscryptenc-ios.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DA79A9BB1557DB6F00BAA07A /* libscryptenc-ios.a */; };
|
||||
@@ -41,6 +52,10 @@
|
||||
DA600C2815056428008E9AB6 /* MPConfig.m in Sources */ = {isa = PBXBuildFile; fileRef = DA600C2715056427008E9AB6 /* MPConfig.m */; };
|
||||
DA672D2F14F92C6B004A189C /* libz.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = DA672D2E14F92C6B004A189C /* libz.dylib */; };
|
||||
DA672D3014F9413D004A189C /* libPearl.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DAC77CAD148291A600BCF976 /* libPearl.a */; };
|
||||
DA829E52159847E0002417D3 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA5BFA4A147E415C00F98B1E /* Foundation.framework */; };
|
||||
DA829E6015984813002417D3 /* UIFont+Replacement.h in Headers */ = {isa = PBXBuildFile; fileRef = DA829E5E15984812002417D3 /* UIFont+Replacement.h */; };
|
||||
DA829E6115984813002417D3 /* UIFont+Replacement.m in Sources */ = {isa = PBXBuildFile; fileRef = DA829E5F15984812002417D3 /* UIFont+Replacement.m */; };
|
||||
DA829E6215984832002417D3 /* libFontReplacer.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DA829E51159847E0002417D3 /* libFontReplacer.a */; };
|
||||
DA95D59D14DF063C008D1B94 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA5BFA4A147E415C00F98B1E /* Foundation.framework */; };
|
||||
DA95D5CF14DF0691008D1B94 /* IASKAppSettingsViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = DA95D5A814DF0691008D1B94 /* IASKAppSettingsViewController.h */; };
|
||||
DA95D5D014DF0691008D1B94 /* IASKAppSettingsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = DA95D5A914DF0691008D1B94 /* IASKAppSettingsViewController.m */; };
|
||||
@@ -93,7 +108,6 @@
|
||||
DAB8D46815036BCF00CED3BC /* MPTypeViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = DAB8D45115036BCF00CED3BC /* MPTypeViewController.m */; };
|
||||
DAB8D46915036BCF00CED3BC /* MPUnlockViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = DAB8D45315036BCF00CED3BC /* MPUnlockViewController.m */; };
|
||||
DAB8D46A15036BCF00CED3BC /* Settings.bundle in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D45415036BCF00CED3BC /* Settings.bundle */; };
|
||||
DAB8D46C15036BCF00CED3BC /* MPTypes.m in Sources */ = {isa = PBXBuildFile; fileRef = DAB8D45615036BCF00CED3BC /* MPTypes.m */; };
|
||||
DAB8D6FA15036BF600CED3BC /* ui_background.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D47115036BF600CED3BC /* ui_background.png */; };
|
||||
DAB8D6FB15036BF600CED3BC /* ui_background@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D47215036BF600CED3BC /* ui_background@2x.png */; };
|
||||
DAB8D6FC15036BF600CED3BC /* ui_box_checked.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D47315036BF600CED3BC /* ui_box_checked.png */; };
|
||||
@@ -666,6 +680,7 @@
|
||||
DAB8D93915036BF700CED3BC /* logo-bare.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6B615036BF600CED3BC /* logo-bare.png */; };
|
||||
DAB8D97C1503718B00CED3BC /* jquery-1.6.1.min.js in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6AB15036BF600CED3BC /* jquery-1.6.1.min.js */; };
|
||||
DABB981615100B4000B05417 /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DABB981515100B4000B05417 /* SystemConfiguration.framework */; };
|
||||
DAC4149215C53C48007A716E /* dictionary.lst in Resources */ = {isa = PBXBuildFile; fileRef = DAC4149115C53C48007A716E /* dictionary.lst */; };
|
||||
DAC6325E1486805C0075AEA5 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA5BFA4A147E415C00F98B1E /* Foundation.framework */; };
|
||||
DAC6326D148680650075AEA5 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA5BFA4A147E415C00F98B1E /* Foundation.framework */; };
|
||||
DAC6327B1486809A0075AEA5 /* JRSwizzle.h in Headers */ = {isa = PBXBuildFile; fileRef = DAC632791486809A0075AEA5 /* JRSwizzle.h */; };
|
||||
@@ -811,7 +826,6 @@
|
||||
DAFE4A5A1503982E003ABA7C /* Pearl.strings in Resources */ = {isa = PBXBuildFile; fileRef = DAFE45FA15039823003ABA7C /* Pearl.strings */; };
|
||||
DAFE4A62150399FF003ABA7C /* PearlAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = DAFE4A60150399FF003ABA7C /* PearlAppDelegate.m */; };
|
||||
DAFE4A63150399FF003ABA7C /* PearlAppDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = DAFE4A61150399FF003ABA7C /* PearlAppDelegate.h */; };
|
||||
DAFE4A63150399FF003ABA7E /* (null) in Sources */ = {isa = PBXBuildFile; };
|
||||
DAFE4A63150399FF003ABA82 /* UIControl+PearlBlocks.m in Sources */ = {isa = PBXBuildFile; fileRef = DAFE4A63150399FF003ABA81 /* UIControl+PearlBlocks.m */; };
|
||||
DAFE4A63150399FF003ABA84 /* UIControl+PearlBlocks.h in Headers */ = {isa = PBXBuildFile; fileRef = DAFE4A63150399FF003ABA83 /* UIControl+PearlBlocks.h */; };
|
||||
DAFE4A63150399FF003ABA86 /* NSObject+PearlKVO.m in Sources */ = {isa = PBXBuildFile; fileRef = DAFE4A63150399FF003ABA85 /* NSObject+PearlKVO.m */; };
|
||||
@@ -825,6 +839,13 @@
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
DA3EF19D15A47AEB003ABF4E /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = DA5BFA3B147E415C00F98B1E /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = DA5BFA43147E415C00F98B1E;
|
||||
remoteInfo = MasterPassword;
|
||||
};
|
||||
DA4DA1D71564470200F6F596 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = DA79A9BD1557DDC700BAA07A /* scrypt.xcodeproj */;
|
||||
@@ -870,6 +891,14 @@
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
93D3938863322199C3E7E2E3 /* MPAlgorithmV0.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPAlgorithmV0.m; sourceTree = "<group>"; };
|
||||
93D398E394E311C545E0A057 /* MPAlgorithm.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPAlgorithm.h; sourceTree = "<group>"; };
|
||||
93D39AAB616A652A4847E4CF /* MPAlgorithmV0.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPAlgorithmV0.h; sourceTree = "<group>"; };
|
||||
93D39B0DF5E3C56355186738 /* MPAlgorithm.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPAlgorithm.m; sourceTree = "<group>"; };
|
||||
93D39C68AFA48A13015E4FAC /* MPKey.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPKey.h; sourceTree = "<group>"; };
|
||||
93D39D0EF77FEC36EA0FB334 /* MPAlgorithmV1.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPAlgorithmV1.h; sourceTree = "<group>"; };
|
||||
93D39E81EFABC6085AC8AE69 /* MPKey.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPKey.m; sourceTree = "<group>"; };
|
||||
93D39E9D7B9005211E7D5262 /* MPAlgorithmV1.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPAlgorithmV1.m; sourceTree = "<group>"; };
|
||||
DA04E33D14B1E70400ECA4F3 /* MobileCoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MobileCoreServices.framework; path = System/Library/Frameworks/MobileCoreServices.framework; sourceTree = SDKROOT; };
|
||||
DA0A1D0315690A9A0092735D /* Default.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = Default.png; path = Resources/Default.png; sourceTree = SOURCE_ROOT; };
|
||||
DA0A1D0415690A9A0092735D /* Default@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "Default@2x.png"; path = "Resources/Default@2x.png"; sourceTree = SOURCE_ROOT; };
|
||||
@@ -883,6 +912,14 @@
|
||||
DA0A1D1415690AF40092735D /* Icon-Small-50@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Icon-Small-50@2x.png"; sourceTree = "<group>"; };
|
||||
DA0E07941577FE490008A67E /* MPEntities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPEntities.h; sourceTree = "<group>"; };
|
||||
DA0E07951577FE490008A67E /* MPEntities.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPEntities.m; sourceTree = "<group>"; };
|
||||
DA0F9F3115B55397007ED9BC /* MPUserEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPUserEntity.h; sourceTree = "<group>"; };
|
||||
DA0F9F3215B55397007ED9BC /* MPUserEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPUserEntity.m; sourceTree = "<group>"; };
|
||||
DA0F9F3415B55397007ED9BC /* MPElementGeneratedEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPElementGeneratedEntity.h; sourceTree = "<group>"; };
|
||||
DA0F9F3515B55397007ED9BC /* MPElementGeneratedEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPElementGeneratedEntity.m; sourceTree = "<group>"; };
|
||||
DA0F9F3715B55397007ED9BC /* MPElementStoredEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPElementStoredEntity.h; sourceTree = "<group>"; };
|
||||
DA0F9F3815B55397007ED9BC /* MPElementStoredEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPElementStoredEntity.m; sourceTree = "<group>"; };
|
||||
DA0F9F3A15B55397007ED9BC /* MPElementEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPElementEntity.h; sourceTree = "<group>"; };
|
||||
DA0F9F3B15B55397007ED9BC /* MPElementEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPElementEntity.m; sourceTree = "<group>"; };
|
||||
DA30E9CB15722ECA00A68B4C /* NSBundle+PearlMutableInfo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSBundle+PearlMutableInfo.h"; sourceTree = "<group>"; };
|
||||
DA30E9CC15722ECA00A68B4C /* NSBundle+PearlMutableInfo.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSBundle+PearlMutableInfo.m"; sourceTree = "<group>"; };
|
||||
DA30E9CD15722ECA00A68B4C /* Pearl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Pearl.m; sourceTree = "<group>"; };
|
||||
@@ -890,14 +927,13 @@
|
||||
DA30E9D315722EF400A68B4C /* Pearl-UIKit.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "Pearl-UIKit.m"; sourceTree = "<group>"; };
|
||||
DA30E9D515723E6900A68B4C /* PearlLazy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PearlLazy.h; sourceTree = "<group>"; };
|
||||
DA30E9D615723E6900A68B4C /* PearlLazy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PearlLazy.m; sourceTree = "<group>"; };
|
||||
DA40C25F1586099D0079CE6E /* MPUserEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPUserEntity.h; sourceTree = "<group>"; };
|
||||
DA40C2601586099D0079CE6E /* MPUserEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPUserEntity.m; sourceTree = "<group>"; };
|
||||
DA40C2621586099E0079CE6E /* MPElementEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPElementEntity.h; sourceTree = "<group>"; };
|
||||
DA40C2631586099E0079CE6E /* MPElementEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPElementEntity.m; sourceTree = "<group>"; };
|
||||
DA40C2651586099E0079CE6E /* MPElementGeneratedEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPElementGeneratedEntity.h; sourceTree = "<group>"; };
|
||||
DA40C2661586099E0079CE6E /* MPElementGeneratedEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPElementGeneratedEntity.m; sourceTree = "<group>"; };
|
||||
DA40C2681586099E0079CE6E /* MPElementStoredEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPElementStoredEntity.h; sourceTree = "<group>"; };
|
||||
DA40C2691586099E0079CE6E /* MPElementStoredEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPElementStoredEntity.m; sourceTree = "<group>"; };
|
||||
DA3EF17915A47744003ABF4E /* Tests.octest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Tests.octest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
DA3EF17A15A47744003ABF4E /* SenTestingKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SenTestingKit.framework; path = Library/Frameworks/SenTestingKit.framework; sourceTree = DEVELOPER_DIR; };
|
||||
DA3EF18015A47744003ABF4E /* Tests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Tests-Info.plist"; sourceTree = "<group>"; };
|
||||
DA3EF18215A47744003ABF4E /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
||||
DA3EF18415A47744003ABF4E /* Tests.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Tests.h; sourceTree = "<group>"; };
|
||||
DA3EF18515A47744003ABF4E /* Tests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = Tests.m; sourceTree = "<group>"; };
|
||||
DA3EF18715A47744003ABF4E /* Tests-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Tests-Prefix.pch"; sourceTree = "<group>"; };
|
||||
DA4425CB1557BED40052177D /* libiCloudStoreManager.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libiCloudStoreManager.a; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
DA4425F11557BF260052177D /* UbiquityStoreManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UbiquityStoreManager.h; sourceTree = "<group>"; };
|
||||
DA4425F21557BF260052177D /* UbiquityStoreManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UbiquityStoreManager.m; sourceTree = "<group>"; };
|
||||
@@ -905,6 +941,9 @@
|
||||
DA4426051557C1990052177D /* MPAppDelegate_Shared.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPAppDelegate_Shared.m; sourceTree = "<group>"; };
|
||||
DA4426061557C1990052177D /* MPAppDelegate_Store.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPAppDelegate_Store.h; sourceTree = "<group>"; };
|
||||
DA4426071557C1990052177D /* MPAppDelegate_Store.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPAppDelegate_Store.m; sourceTree = "<group>"; };
|
||||
DA46826C15AB48F100FB09E7 /* MasterPassword 2.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MasterPassword 2.xcdatamodel"; sourceTree = "<group>"; };
|
||||
DA46826D15AB843200FB09E7 /* tip_basic_black_bottom_right.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = tip_basic_black_bottom_right.png; sourceTree = "<group>"; };
|
||||
DA46826E15AB843200FB09E7 /* tip_basic_black_bottom_right@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "tip_basic_black_bottom_right@2x.png"; sourceTree = "<group>"; };
|
||||
DA5BFA44147E415C00F98B1E /* MasterPassword.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MasterPassword.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
DA5BFA48147E415C00F98B1E /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; };
|
||||
DA5BFA4A147E415C00F98B1E /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
|
||||
@@ -917,6 +956,9 @@
|
||||
DA672D2E14F92C6B004A189C /* libz.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libz.dylib; path = usr/lib/libz.dylib; sourceTree = SDKROOT; };
|
||||
DA79A9BB1557DB6F00BAA07A /* libscryptenc-ios.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libscryptenc-ios.a"; sourceTree = "<group>"; };
|
||||
DA79A9BD1557DDC700BAA07A /* scrypt.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = scrypt.xcodeproj; path = External/Pearl/External/iOSPorts/ports/security/scrypt/scrypt.xcodeproj; sourceTree = "<group>"; };
|
||||
DA829E51159847E0002417D3 /* libFontReplacer.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libFontReplacer.a; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
DA829E5E15984812002417D3 /* UIFont+Replacement.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIFont+Replacement.h"; sourceTree = "<group>"; };
|
||||
DA829E5F15984812002417D3 /* UIFont+Replacement.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIFont+Replacement.m"; sourceTree = "<group>"; };
|
||||
DA902BD01576CA4A00C38161 /* keypad.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = keypad.png; sourceTree = "<group>"; };
|
||||
DA95D59C14DF063C008D1B94 /* libInAppSettingsKit.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libInAppSettingsKit.a; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
DA95D5A814DF0691008D1B94 /* IASKAppSettingsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IASKAppSettingsViewController.h; sourceTree = "<group>"; };
|
||||
@@ -957,7 +999,7 @@
|
||||
DA95D5CE14DF0691008D1B94 /* IASKSpecifierValuesView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = IASKSpecifierValuesView.xib; sourceTree = "<group>"; };
|
||||
DA95D5F014DF0B1E008D1B94 /* MessageUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MessageUI.framework; path = System/Library/Frameworks/MessageUI.framework; sourceTree = SDKROOT; };
|
||||
DAAC35DD156BD77D00C5FD93 /* CoreTelephony.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreTelephony.framework; path = System/Library/Frameworks/CoreTelephony.framework; sourceTree = SDKROOT; };
|
||||
DAB8D43D15036BCF00CED3BC /* MasterPassword.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MasterPassword.xcdatamodel; sourceTree = "<group>"; };
|
||||
DAB8D43D15036BCF00CED3BC /* MasterPassword 1.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MasterPassword 1.xcdatamodel"; sourceTree = "<group>"; };
|
||||
DAB8D44015036BCF00CED3BC /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
||||
DAB8D44115036BCF00CED3BC /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
|
||||
DAB8D44215036BCF00CED3BC /* MainStoryboard_iPhone.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = MainStoryboard_iPhone.storyboard; sourceTree = "<group>"; };
|
||||
@@ -979,7 +1021,6 @@
|
||||
DAB8D45215036BCF00CED3BC /* MPUnlockViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPUnlockViewController.h; sourceTree = "<group>"; };
|
||||
DAB8D45315036BCF00CED3BC /* MPUnlockViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPUnlockViewController.m; sourceTree = "<group>"; };
|
||||
DAB8D45415036BCF00CED3BC /* Settings.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = Settings.bundle; sourceTree = "<group>"; };
|
||||
DAB8D45615036BCF00CED3BC /* MPTypes.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPTypes.m; sourceTree = "<group>"; };
|
||||
DAB8D45915036BCF00CED3BC /* MPTypes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPTypes.h; sourceTree = "<group>"; };
|
||||
DAB8D47115036BF600CED3BC /* ui_background.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = ui_background.png; sourceTree = "<group>"; };
|
||||
DAB8D47215036BF600CED3BC /* ui_background@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "ui_background@2x.png"; sourceTree = "<group>"; };
|
||||
@@ -1622,6 +1663,7 @@
|
||||
DABB980D150FF40100B05417 /* SendToMac.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SendToMac.h; sourceTree = "<group>"; };
|
||||
DABB980E150FF40100B05417 /* SendToMac.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SendToMac.m; sourceTree = "<group>"; };
|
||||
DABB981515100B4000B05417 /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; };
|
||||
DAC4149115C53C48007A716E /* dictionary.lst */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = dictionary.lst; sourceTree = "<group>"; };
|
||||
DAC6325D1486805C0075AEA5 /* libuicolor-utilities.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libuicolor-utilities.a"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
DAC6326C148680650075AEA5 /* libjrswizzle.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libjrswizzle.a; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
DAC632791486809A0075AEA5 /* JRSwizzle.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = JRSwizzle.h; path = External/Pearl/External/jrswizzle/JRSwizzle.h; sourceTree = SOURCE_ROOT; };
|
||||
@@ -1781,6 +1823,16 @@
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
DA3EF17515A47744003ABF4E /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
DA3EF17B15A47744003ABF4E /* SenTestingKit.framework in Frameworks */,
|
||||
DA3EF17C15A47744003ABF4E /* UIKit.framework in Frameworks */,
|
||||
DA3EF17D15A47744003ABF4E /* Foundation.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
DA4425C81557BED40052177D /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
@@ -1793,6 +1845,7 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
DA829E6215984832002417D3 /* libFontReplacer.a in Frameworks */,
|
||||
DA44260A1557D9E40052177D /* libiCloudStoreManager.a in Frameworks */,
|
||||
DAD312C21552A22700A3F9ED /* libsqlite3.dylib in Frameworks */,
|
||||
DAD312BF1552A1BD00A3F9ED /* libLocalytics.a in Frameworks */,
|
||||
@@ -1813,6 +1866,14 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
DA829E4E159847E0002417D3 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
DA829E52159847E0002417D3 /* Foundation.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
DA95D59914DF063C008D1B94 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
@@ -1859,6 +1920,26 @@
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
DA3EF17E15A47744003ABF4E /* Tests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DA3EF18415A47744003ABF4E /* Tests.h */,
|
||||
DA3EF18515A47744003ABF4E /* Tests.m */,
|
||||
DA3EF17F15A47744003ABF4E /* Supporting Files */,
|
||||
);
|
||||
path = Tests;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DA3EF17F15A47744003ABF4E /* Supporting Files */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DA3EF18015A47744003ABF4E /* Tests-Info.plist */,
|
||||
DA3EF18115A47744003ABF4E /* InfoPlist.strings */,
|
||||
DA3EF18715A47744003ABF4E /* Tests-Prefix.pch */,
|
||||
);
|
||||
name = "Supporting Files";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DA4425D71557BF260052177D /* iCloudStoreManager */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -1884,6 +1965,8 @@
|
||||
DAD3126115528C9C00A3F9ED /* TestFlight */,
|
||||
DAD3127315528CD200A3F9ED /* Localytics */,
|
||||
DA4425D71557BF260052177D /* iCloudStoreManager */,
|
||||
DA829E5D15984812002417D3 /* FontReplacer */,
|
||||
DA3EF17E15A47744003ABF4E /* Tests */,
|
||||
DA5BFA47147E415C00F98B1E /* Frameworks */,
|
||||
DA5BFA45147E415C00F98B1E /* Products */,
|
||||
);
|
||||
@@ -1899,6 +1982,8 @@
|
||||
DA95D59C14DF063C008D1B94 /* libInAppSettingsKit.a */,
|
||||
DAD3127115528CD200A3F9ED /* libLocalytics.a */,
|
||||
DA4425CB1557BED40052177D /* libiCloudStoreManager.a */,
|
||||
DA829E51159847E0002417D3 /* libFontReplacer.a */,
|
||||
DA3EF17915A47744003ABF4E /* Tests.octest */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
@@ -1918,6 +2003,7 @@
|
||||
DA5BFA4A147E415C00F98B1E /* Foundation.framework */,
|
||||
DA5BFA4C147E415C00F98B1E /* CoreGraphics.framework */,
|
||||
DA5BFA4E147E415C00F98B1E /* CoreData.framework */,
|
||||
DA3EF17A15A47744003ABF4E /* SenTestingKit.framework */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
@@ -1925,17 +2011,25 @@
|
||||
DA5BFA50147E415C00F98B1E /* MasterPassword */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DA40C2681586099E0079CE6E /* MPElementStoredEntity.h */,
|
||||
DA40C2691586099E0079CE6E /* MPElementStoredEntity.m */,
|
||||
DA40C2651586099E0079CE6E /* MPElementGeneratedEntity.h */,
|
||||
DA40C2661586099E0079CE6E /* MPElementGeneratedEntity.m */,
|
||||
DA40C2621586099E0079CE6E /* MPElementEntity.h */,
|
||||
DA40C2631586099E0079CE6E /* MPElementEntity.m */,
|
||||
DA40C25F1586099D0079CE6E /* MPUserEntity.h */,
|
||||
DA40C2601586099D0079CE6E /* MPUserEntity.m */,
|
||||
93D39D0EF77FEC36EA0FB334 /* MPAlgorithmV1.h */,
|
||||
93D39E9D7B9005211E7D5262 /* MPAlgorithmV1.m */,
|
||||
93D39B0DF5E3C56355186738 /* MPAlgorithm.m */,
|
||||
93D39C68AFA48A13015E4FAC /* MPKey.h */,
|
||||
93D39E81EFABC6085AC8AE69 /* MPKey.m */,
|
||||
93D39AAB616A652A4847E4CF /* MPAlgorithmV0.h */,
|
||||
93D3938863322199C3E7E2E3 /* MPAlgorithmV0.m */,
|
||||
93D398E394E311C545E0A057 /* MPAlgorithm.h */,
|
||||
DA0E07941577FE490008A67E /* MPEntities.h */,
|
||||
DA0E07951577FE490008A67E /* MPEntities.m */,
|
||||
DAB8D43C15036BCF00CED3BC /* MasterPassword.xcdatamodeld */,
|
||||
DA0F9F3115B55397007ED9BC /* MPUserEntity.h */,
|
||||
DA0F9F3215B55397007ED9BC /* MPUserEntity.m */,
|
||||
DA0F9F3415B55397007ED9BC /* MPElementGeneratedEntity.h */,
|
||||
DA0F9F3515B55397007ED9BC /* MPElementGeneratedEntity.m */,
|
||||
DA0F9F3715B55397007ED9BC /* MPElementStoredEntity.h */,
|
||||
DA0F9F3A15B55397007ED9BC /* MPElementEntity.h */,
|
||||
DA0F9F3B15B55397007ED9BC /* MPElementEntity.m */,
|
||||
DA0F9F3815B55397007ED9BC /* MPElementStoredEntity.m */,
|
||||
DA600C2415054F3A008E9AB6 /* MPAppDelegate_Key.h */,
|
||||
DA600C2315054F3A008E9AB6 /* MPAppDelegate_Key.m */,
|
||||
DA4426041557C1990052177D /* MPAppDelegate_Shared.h */,
|
||||
@@ -1945,7 +2039,6 @@
|
||||
DA600C2615056427008E9AB6 /* MPConfig.h */,
|
||||
DA600C2715056427008E9AB6 /* MPConfig.m */,
|
||||
DAB8D45915036BCF00CED3BC /* MPTypes.h */,
|
||||
DAB8D45615036BCF00CED3BC /* MPTypes.m */,
|
||||
DAB8D43E15036BCF00CED3BC /* iOS */,
|
||||
);
|
||||
path = MasterPassword;
|
||||
@@ -1961,6 +2054,16 @@
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DA829E5D15984812002417D3 /* FontReplacer */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DA829E5E15984812002417D3 /* UIFont+Replacement.h */,
|
||||
DA829E5F15984812002417D3 /* UIFont+Replacement.m */,
|
||||
);
|
||||
name = FontReplacer;
|
||||
path = "External/FontReplacer/UIFont+Replacement";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DA902B931576C0FB00C38161 /* Avatars */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -2114,6 +2217,7 @@
|
||||
DAB8D46F15036BF600CED3BC /* Resources */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DAC4149115C53C48007A716E /* dictionary.lst */,
|
||||
DA902BD01576CA4A00C38161 /* keypad.png */,
|
||||
DA902B931576C0FB00C38161 /* Avatars */,
|
||||
DAB8D47015036BF600CED3BC /* Automaton */,
|
||||
@@ -2753,6 +2857,8 @@
|
||||
DAB8D6B715036BF600CED3BC /* Tooltips */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DA46826D15AB843200FB09E7 /* tip_basic_black_bottom_right.png */,
|
||||
DA46826E15AB843200FB09E7 /* tip_basic_black_bottom_right@2x.png */,
|
||||
DACABB8E1572B769008BA211 /* tip_basic_black_top.png */,
|
||||
DACABB8F1572B769008BA211 /* tip_basic_black_top@2x.png */,
|
||||
DACABB8A1572A4A4008BA211 /* tip_basic_black_top_right.png */,
|
||||
@@ -3082,6 +3188,14 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
DA829E4F159847E0002417D3 /* Headers */ = {
|
||||
isa = PBXHeadersBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
DA829E6015984813002417D3 /* UIFont+Replacement.h in Headers */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
DA95D59A14DF063C008D1B94 /* Headers */ = {
|
||||
isa = PBXHeadersBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
@@ -3187,6 +3301,25 @@
|
||||
/* End PBXHeadersBuildPhase section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
DA3EF17815A47744003ABF4E /* Tests */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = DA3EF18815A47744003ABF4E /* Build configuration list for PBXNativeTarget "Tests" */;
|
||||
buildPhases = (
|
||||
DA3EF17415A47744003ABF4E /* Sources */,
|
||||
DA3EF17515A47744003ABF4E /* Frameworks */,
|
||||
DA3EF17615A47744003ABF4E /* Resources */,
|
||||
DA3EF17715A47744003ABF4E /* ShellScript */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
DA3EF19E15A47AEB003ABF4E /* PBXTargetDependency */,
|
||||
);
|
||||
name = Tests;
|
||||
productName = Tests;
|
||||
productReference = DA3EF17915A47744003ABF4E /* Tests.octest */;
|
||||
productType = "com.apple.product-type.bundle";
|
||||
};
|
||||
DA4425CA1557BED40052177D /* iCloudStoreManager */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = DA4425D31557BED40052177D /* Build configuration list for PBXNativeTarget "iCloudStoreManager" */;
|
||||
@@ -3223,6 +3356,23 @@
|
||||
productReference = DA5BFA44147E415C00F98B1E /* MasterPassword.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
DA829E50159847E0002417D3 /* FontReplacer */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = DA829E59159847E0002417D3 /* Build configuration list for PBXNativeTarget "FontReplacer" */;
|
||||
buildPhases = (
|
||||
DA829E4D159847E0002417D3 /* Sources */,
|
||||
DA829E4E159847E0002417D3 /* Frameworks */,
|
||||
DA829E4F159847E0002417D3 /* Headers */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = FontReplacer;
|
||||
productName = FontReplacer;
|
||||
productReference = DA829E51159847E0002417D3 /* libFontReplacer.a */;
|
||||
productType = "com.apple.product-type.library.static";
|
||||
};
|
||||
DA95D59B14DF063C008D1B94 /* InAppSettingsKit */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = DA95D5A414DF063C008D1B94 /* Build configuration list for PBXNativeTarget "InAppSettingsKit" */;
|
||||
@@ -3347,6 +3497,8 @@
|
||||
DA95D59B14DF063C008D1B94 /* InAppSettingsKit */,
|
||||
DAD3127015528CD200A3F9ED /* Localytics */,
|
||||
DA4425CA1557BED40052177D /* iCloudStoreManager */,
|
||||
DA829E50159847E0002417D3 /* FontReplacer */,
|
||||
DA3EF17815A47744003ABF4E /* Tests */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
@@ -3376,6 +3528,14 @@
|
||||
/* End PBXReferenceProxy section */
|
||||
|
||||
/* Begin PBXResourcesBuildPhase section */
|
||||
DA3EF17615A47744003ABF4E /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
DA3EF18315A47744003ABF4E /* InfoPlist.strings in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
DA5BFA42147E415C00F98B1E /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
@@ -4015,12 +4175,28 @@
|
||||
DAE4C98C157E63BE00EFE047 /* avatar-17@2x.png in Resources */,
|
||||
DAE4C98D157E63BE00EFE047 /* avatar-18.png in Resources */,
|
||||
DAE4C98E157E63BE00EFE047 /* avatar-18@2x.png in Resources */,
|
||||
DA46826F15AB843200FB09E7 /* tip_basic_black_bottom_right.png in Resources */,
|
||||
DA46827015AB843200FB09E7 /* tip_basic_black_bottom_right@2x.png in Resources */,
|
||||
DAC4149215C53C48007A716E /* dictionary.lst in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
DA3EF17715A47744003ABF4E /* ShellScript */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "# Run the unit tests in this test bundle.\n\"${SYSTEM_DEVELOPER_DIR}/Tools/RunUnitTests\"\n";
|
||||
};
|
||||
DA6556E314D55F3000841C99 /* Run Script: GIT version -> Info.plist */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
@@ -4033,7 +4209,7 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = "/bin/bash -e";
|
||||
shellScript = "PATH+=:/usr/libexec\n\naddPlistWithKey() {\n local key=$1 type=$2 value=$3 plist=${4:-\"$BUILT_PRODUCTS_DIR/$INFOPLIST_PATH\"}\n \n PlistBuddy -c \"Delete :'$key'\" \"$plist\" || true\n PlistBuddy -c \"Add :'$key' '$type' '$value'\" \"$plist\"\n}\nsetPlistWithKey() {\n local key=$1 value=$2 plist=${3:-\"$BUILT_PRODUCTS_DIR/$INFOPLIST_PATH\"}\n \n PlistBuddy -c \"Set :'$key' '$value'\" \"$plist\"\n}\ngetPlistWithKey() {\n local key=$1 plist=${2:-\"$BUILT_PRODUCTS_DIR/$INFOPLIST_PATH\"}\n \n PlistBuddy -c \"Print :'$key'\" \"$plist\"\n}\nsetSettingWithTitle() {\n local i title=$1 value=$2 plist=${3:-\"$BUILT_PRODUCTS_DIR/$CONTENTS_FOLDER_PATH/Settings.bundle/Root.plist\"}\n \n for (( i=0; 1; ++i )); do\n PlistBuddy -c \"Print :PreferenceSpecifiers:$i\" \"$plist\" &>/dev/null || break\n echo \"Checking preference specifier $i\"\n \n [[ $(PlistBuddy -c \"Print :PreferenceSpecifiers:$i:Title\" \"$plist\" 2>/dev/null) = $title ]] || continue\n \n echo \"Correct title, setting value.\"\n PlistBuddy -c \"Set :PreferenceSpecifiers:$i:DefaultValue $value\" \"$plist\"\n break\n done\n}\n\ndescription=$(git describe --tags --always --dirty --long)\nbuild=${description%-g*} build=${build//-/.}\ntag=${description%%-*}\n\naddPlistWithKey GITDescription string \"$description\"\nsetPlistWithKey CFBundleVersion \"$build\"\nsetPlistWithKey CFBundleShortVersionString \"$tag\"\n\nsetSettingWithTitle \"Build\" \"$build\"\nsetSettingWithTitle \"Version\" \"$tag\"\nsetSettingWithTitle \"Copyright\" \"$(getPlistWithKey NSHumanReadableCopyright)\"\n";
|
||||
shellScript = "PATH+=:/usr/libexec\n\naddPlistWithKey() {\n local key=$1 type=$2 value=$3 plist=${4:-\"$BUILT_PRODUCTS_DIR/$INFOPLIST_PATH\"}\n \n PlistBuddy -c \"Delete :'$key'\" \"$plist\" || true\n PlistBuddy -c \"Add :'$key' '$type' '$value'\" \"$plist\"\n}\nsetPlistWithKey() {\n local key=$1 value=$2 plist=${3:-\"$BUILT_PRODUCTS_DIR/$INFOPLIST_PATH\"}\n \n PlistBuddy -c \"Set :'$key' '$value'\" \"$plist\"\n}\ngetPlistWithKey() {\n local key=$1 plist=${2:-\"$BUILT_PRODUCTS_DIR/$INFOPLIST_PATH\"}\n \n PlistBuddy -c \"Print :'$key'\" \"$plist\"\n}\nsetSettingWithTitle() {\n local i title=$1 value=$2 plist=${3:-\"$BUILT_PRODUCTS_DIR/$CONTENTS_FOLDER_PATH/Settings.bundle/Root.plist\"}\n \n for (( i=0; 1; ++i )); do\n PlistBuddy -c \"Print :PreferenceSpecifiers:$i\" \"$plist\" &>/dev/null || break\n echo \"Checking preference specifier $i\"\n \n [[ $(PlistBuddy -c \"Print :PreferenceSpecifiers:$i:Title\" \"$plist\" 2>/dev/null) = $title ]] || continue\n \n echo \"Correct title, setting value.\"\n PlistBuddy -c \"Set :PreferenceSpecifiers:$i:DefaultValue $value\" \"$plist\"\n break\n done\n}\n\ndescription=$(git describe --always --dirty --long)\nbuild=${description%-g*} build=${build//-/.}\ntag=${description%%-*}\n\naddPlistWithKey GITDescription string \"$description\"\nsetPlistWithKey CFBundleVersion \"$build\"\nsetPlistWithKey CFBundleShortVersionString \"$tag\"\n\nsetSettingWithTitle \"Build\" \"$build\"\nsetSettingWithTitle \"Version\" \"$tag\"\nsetSettingWithTitle \"Copyright\" \"$(getPlistWithKey NSHumanReadableCopyright)\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
DAD3125D155288AA00A3F9ED /* Run Script: Crashlytics */ = {
|
||||
@@ -4054,6 +4230,14 @@
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
DA3EF17415A47744003ABF4E /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
DA3EF18615A47744003ABF4E /* Tests.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
DA4425C71557BED40052177D /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
@@ -4075,17 +4259,28 @@
|
||||
DAB8D46715036BCF00CED3BC /* MPSearchDelegate.m in Sources */,
|
||||
DAB8D46815036BCF00CED3BC /* MPTypeViewController.m in Sources */,
|
||||
DAB8D46915036BCF00CED3BC /* MPUnlockViewController.m in Sources */,
|
||||
DAB8D46C15036BCF00CED3BC /* MPTypes.m in Sources */,
|
||||
DA600C2515054F3A008E9AB6 /* MPAppDelegate_Key.m in Sources */,
|
||||
DA600C2815056428008E9AB6 /* MPConfig.m in Sources */,
|
||||
DA4426081557C1990052177D /* MPAppDelegate_Shared.m in Sources */,
|
||||
DA4426091557C1990052177D /* MPAppDelegate_Store.m in Sources */,
|
||||
DA0E07961577FE490008A67E /* MPEntities.m in Sources */,
|
||||
DAC728CA157C247B00889EF2 /* MPPreferencesViewController.m in Sources */,
|
||||
DA40C2611586099D0079CE6E /* MPUserEntity.m in Sources */,
|
||||
DA40C2641586099E0079CE6E /* MPElementEntity.m in Sources */,
|
||||
DA40C2671586099E0079CE6E /* MPElementGeneratedEntity.m in Sources */,
|
||||
DA40C26A1586099E0079CE6E /* MPElementStoredEntity.m in Sources */,
|
||||
93D392B30CE6C58A9A905E0A /* MPAlgorithmV0.m in Sources */,
|
||||
93D390BC6AE7A1C9B91A3668 /* MPKey.m in Sources */,
|
||||
93D394744B5485303B326ECB /* MPAlgorithm.m in Sources */,
|
||||
93D39DC7A7282137B08C8D82 /* MPAlgorithmV1.m in Sources */,
|
||||
DA0F9F3315B55397007ED9BC /* MPUserEntity.m in Sources */,
|
||||
DA0F9F3615B55397007ED9BC /* MPElementGeneratedEntity.m in Sources */,
|
||||
DA0F9F3915B55397007ED9BC /* MPElementStoredEntity.m in Sources */,
|
||||
DA0F9F3C15B55397007ED9BC /* MPElementEntity.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
DA829E4D159847E0002417D3 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
DA829E6115984813002417D3 /* UIFont+Replacement.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -4169,7 +4364,6 @@
|
||||
DA30E9D215722EE500A68B4C /* Pearl-Crypto.m in Sources */,
|
||||
DA30E9D415722EF400A68B4C /* Pearl-UIKit.m in Sources */,
|
||||
DA30E9D815723E6900A68B4C /* PearlLazy.m in Sources */,
|
||||
DAFE4A63150399FF003ABA7E /* (null) in Sources */,
|
||||
DAFE4A63150399FF003ABA82 /* UIControl+PearlBlocks.m in Sources */,
|
||||
DAFE4A63150399FF003ABA86 /* NSObject+PearlKVO.m in Sources */,
|
||||
DAFE4A63150399FF003ABA8A /* UIControl+PearlSelect.m in Sources */,
|
||||
@@ -4192,6 +4386,11 @@
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXTargetDependency section */
|
||||
DA3EF19E15A47AEB003ABF4E /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = DA5BFA43147E415C00F98B1E /* MasterPassword */;
|
||||
targetProxy = DA3EF19D15A47AEB003ABF4E /* PBXContainerItemProxy */;
|
||||
};
|
||||
DA4DA1D81564470200F6F596 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
name = "Makefile-scrypt";
|
||||
@@ -4210,6 +4409,14 @@
|
||||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin PBXVariantGroup section */
|
||||
DA3EF18115A47744003ABF4E /* InfoPlist.strings */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
DA3EF18215A47744003ABF4E /* en */,
|
||||
);
|
||||
name = InfoPlist.strings;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DAB8D43F15036BCF00CED3BC /* InfoPlist.strings */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
@@ -4229,6 +4436,88 @@
|
||||
/* End PBXVariantGroup section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
DA3EF18915A47744003ABF4E /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/MasterPassword.app/MasterPassword";
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(SDKROOT)/Developer/Library/Frameworks",
|
||||
"$(DEVELOPER_LIBRARY_DIR)/Frameworks",
|
||||
"\"$(SRCROOT)/Crashlytics\"",
|
||||
);
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_PRECOMPILE_PREFIX_HEADER = YES;
|
||||
GCC_PREFIX_HEADER = "Tests/Tests-Prefix.pch";
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES;
|
||||
INFOPLIST_FILE = "Tests/Tests-Info.plist";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 5.1;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
TEST_HOST = "$(BUNDLE_LOADER)";
|
||||
WRAPPER_EXTENSION = octest;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
DA3EF18A15A47744003ABF4E /* AdHoc */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
ARCHS = "$(ARCHS_STANDARD_32_BIT)";
|
||||
BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/MasterPassword.app/MasterPassword";
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
COPY_PHASE_STRIP = YES;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(SDKROOT)/Developer/Library/Frameworks",
|
||||
"$(DEVELOPER_LIBRARY_DIR)/Frameworks",
|
||||
"\"$(SRCROOT)/Crashlytics\"",
|
||||
);
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_PRECOMPILE_PREFIX_HEADER = YES;
|
||||
GCC_PREFIX_HEADER = "Tests/Tests-Prefix.pch";
|
||||
GCC_VERSION = com.apple.compilers.llvm.clang.1_0;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
INFOPLIST_FILE = "Tests/Tests-Info.plist";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 5.1;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = iphoneos;
|
||||
TEST_HOST = "$(BUNDLE_LOADER)";
|
||||
VALIDATE_PRODUCT = YES;
|
||||
WRAPPER_EXTENSION = octest;
|
||||
};
|
||||
name = AdHoc;
|
||||
};
|
||||
DA3EF18B15A47744003ABF4E /* AppStore */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
ARCHS = "$(ARCHS_STANDARD_32_BIT)";
|
||||
BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/MasterPassword.app/MasterPassword";
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
COPY_PHASE_STRIP = YES;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(SDKROOT)/Developer/Library/Frameworks",
|
||||
"$(DEVELOPER_LIBRARY_DIR)/Frameworks",
|
||||
"\"$(SRCROOT)/Crashlytics\"",
|
||||
);
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_PRECOMPILE_PREFIX_HEADER = YES;
|
||||
GCC_PREFIX_HEADER = "Tests/Tests-Prefix.pch";
|
||||
GCC_VERSION = com.apple.compilers.llvm.clang.1_0;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
INFOPLIST_FILE = "Tests/Tests-Info.plist";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 5.1;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = iphoneos;
|
||||
TEST_HOST = "$(BUNDLE_LOADER)";
|
||||
VALIDATE_PRODUCT = YES;
|
||||
WRAPPER_EXTENSION = octest;
|
||||
};
|
||||
name = AppStore;
|
||||
};
|
||||
DA4425D41557BED40052177D /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
@@ -4261,7 +4550,8 @@
|
||||
CLANG_WARN_OBJCPP_ARC_ABI = YES;
|
||||
CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES;
|
||||
CLANG_WARN__EXIT_TIME_DESTRUCTORS = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer: Maarten Billemont (DWGU95U4ZD)";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PRECOMPILE_PREFIX_HEADER = YES;
|
||||
@@ -4301,7 +4591,7 @@
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
OTHER_LDFLAGS = "-ObjC";
|
||||
PRODUCT_NAME = "${TARGET_NAME}";
|
||||
"PROVISIONING_PROFILE[sdk=iphoneos*]" = "";
|
||||
"PROVISIONING_PROFILE[sdk=iphoneos*]" = "FCADE039-9917-4715-9172-ABBAC9806763";
|
||||
SDKROOT = iphoneos;
|
||||
SKIP_INSTALL = YES;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
@@ -4319,7 +4609,8 @@
|
||||
CLANG_WARN_OBJCPP_ARC_ABI = YES;
|
||||
CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES;
|
||||
CLANG_WARN__EXIT_TIME_DESTRUCTORS = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution: Maarten Billemont";
|
||||
COPY_PHASE_STRIP = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_PRECOMPILE_PREFIX_HEADER = YES;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
@@ -4358,10 +4649,11 @@
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 5.0;
|
||||
OTHER_LDFLAGS = "-ObjC";
|
||||
PRODUCT_NAME = "${TARGET_NAME}";
|
||||
"PROVISIONING_PROFILE[sdk=iphoneos*]" = "";
|
||||
"PROVISIONING_PROFILE[sdk=iphoneos*]" = "1CC091A5-5890-4382-A72F-1397B66FE136";
|
||||
SDKROOT = iphoneos;
|
||||
SKIP_INSTALL = YES;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
name = AdHoc;
|
||||
};
|
||||
@@ -4409,6 +4701,27 @@
|
||||
};
|
||||
name = AdHoc;
|
||||
};
|
||||
DA829E5A159847E0002417D3 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
GCC_WARN_INHIBIT_ALL_WARNINGS = YES;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
DA829E5B159847E0002417D3 /* AdHoc */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
GCC_WARN_INHIBIT_ALL_WARNINGS = YES;
|
||||
};
|
||||
name = AdHoc;
|
||||
};
|
||||
DA829E5C159847E0002417D3 /* AppStore */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
GCC_WARN_INHIBIT_ALL_WARNINGS = YES;
|
||||
};
|
||||
name = AppStore;
|
||||
};
|
||||
DA95D5A514DF063C008D1B94 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
@@ -4442,7 +4755,8 @@
|
||||
CLANG_WARN_OBJCPP_ARC_ABI = YES;
|
||||
CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES;
|
||||
CLANG_WARN__EXIT_TIME_DESTRUCTORS = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution: Maarten Billemont";
|
||||
COPY_PHASE_STRIP = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_PRECOMPILE_PREFIX_HEADER = YES;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
@@ -4481,10 +4795,11 @@
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 5.0;
|
||||
OTHER_LDFLAGS = "-ObjC";
|
||||
PRODUCT_NAME = "${TARGET_NAME}";
|
||||
"PROVISIONING_PROFILE[sdk=iphoneos*]" = "";
|
||||
"PROVISIONING_PROFILE[sdk=iphoneos*]" = "7BF77F10-5D46-4762-B27F-55A39E089052";
|
||||
SDKROOT = iphoneos;
|
||||
SKIP_INSTALL = YES;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
name = AppStore;
|
||||
};
|
||||
@@ -4671,6 +4986,16 @@
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
DA3EF18815A47744003ABF4E /* Build configuration list for PBXNativeTarget "Tests" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
DA3EF18915A47744003ABF4E /* Debug */,
|
||||
DA3EF18A15A47744003ABF4E /* AdHoc */,
|
||||
DA3EF18B15A47744003ABF4E /* AppStore */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = AdHoc;
|
||||
};
|
||||
DA4425D31557BED40052177D /* Build configuration list for PBXNativeTarget "iCloudStoreManager" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
@@ -4701,6 +5026,16 @@
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = AdHoc;
|
||||
};
|
||||
DA829E59159847E0002417D3 /* Build configuration list for PBXNativeTarget "FontReplacer" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
DA829E5A159847E0002417D3 /* Debug */,
|
||||
DA829E5B159847E0002417D3 /* AdHoc */,
|
||||
DA829E5C159847E0002417D3 /* AppStore */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = AdHoc;
|
||||
};
|
||||
DA95D5A414DF063C008D1B94 /* Build configuration list for PBXNativeTarget "InAppSettingsKit" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
@@ -4757,9 +5092,10 @@
|
||||
DAB8D43C15036BCF00CED3BC /* MasterPassword.xcdatamodeld */ = {
|
||||
isa = XCVersionGroup;
|
||||
children = (
|
||||
DAB8D43D15036BCF00CED3BC /* MasterPassword.xcdatamodel */,
|
||||
DA46826C15AB48F100FB09E7 /* MasterPassword 2.xcdatamodel */,
|
||||
DAB8D43D15036BCF00CED3BC /* MasterPassword 1.xcdatamodel */,
|
||||
);
|
||||
currentVersion = DAB8D43D15036BCF00CED3BC /* MasterPassword.xcdatamodel */;
|
||||
currentVersion = DA46826C15AB48F100FB09E7 /* MasterPassword 2.xcdatamodel */;
|
||||
path = MasterPassword.xcdatamodeld;
|
||||
sourceTree = "<group>";
|
||||
versionGroupType = wrapper.xcdatamodel;
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "NO"
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
@@ -28,6 +28,16 @@
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
buildConfiguration = "Debug">
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "DA3EF17815A47744003ABF4E"
|
||||
BuildableName = "Tests.octest"
|
||||
BlueprintName = "Tests"
|
||||
ReferencedContainer = "container:MasterPassword-iOS.xcodeproj">
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
</Testables>
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
@@ -40,8 +50,8 @@
|
||||
</MacroExpansion>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.GDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.GDB"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
buildConfiguration = "Debug"
|
||||
|
||||
BIN
MasterPassword.sketch/QuickLook/Preview.png
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 43 KiB |
BIN
MasterPassword.sketch/QuickLook/Thumbnail.png
Normal file
|
After Width: | Height: | Size: 67 KiB |
49
MasterPassword/Java/masterpassword-algorithm/pom.xml
Normal file
@@ -0,0 +1,49 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<!-- PROJECT METADATA -->
|
||||
<parent>
|
||||
<groupId>com.lyndir.lhunath.masterpassword</groupId>
|
||||
<artifactId>masterpassword</artifactId>
|
||||
<version>GIT-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<name>Master Password Algorithm Implementation</name>
|
||||
<description>The implementation of the Master Password algorithm</description>
|
||||
|
||||
<groupId>com.lyndir.lhunath.masterpassword</groupId>
|
||||
<artifactId>masterpassword-algorithm</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<!-- DEPENDENCY MANAGEMENT -->
|
||||
<dependencies>
|
||||
|
||||
<!-- PROJECT REFERENCES -->
|
||||
<dependency>
|
||||
<groupId>com.lyndir.lhunath.opal</groupId>
|
||||
<artifactId>opal-system</artifactId>
|
||||
<version>GIT-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.lyndir.lhunath.opal</groupId>
|
||||
<artifactId>opal-crypto</artifactId>
|
||||
<version>GIT-SNAPSHOT</version>
|
||||
</dependency>
|
||||
|
||||
<!-- EXTERNAL DEPENDENCIES -->
|
||||
<dependency>
|
||||
<groupId>net.sf.plist</groupId>
|
||||
<artifactId>property-list</artifactId>
|
||||
<version>svn-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.lambdaworks</groupId>
|
||||
<artifactId>scrypt</artifactId>
|
||||
<version>1.3.2</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.lyndir.lhunath.masterpassword;
|
||||
|
||||
/**
|
||||
* <i>07 04, 2012</i>
|
||||
*
|
||||
* @author lhunath
|
||||
*/
|
||||
public enum MPElementFeature {
|
||||
|
||||
/** Export the key-protected content data. */
|
||||
ExportContent,
|
||||
/** Never export content. */
|
||||
DevicePrivate,
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package com.lyndir.lhunath.masterpassword;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||
import java.util.Set;
|
||||
|
||||
|
||||
/**
|
||||
* <i>07 04, 2012</i>
|
||||
*
|
||||
* @author lhunath
|
||||
*/
|
||||
public enum MPElementType {
|
||||
|
||||
GeneratedMaximum( "Maximum Security Password", "Maximum", "20 characters, contains symbols.", MPElementTypeClass.Generated ),
|
||||
GeneratedLong( "Long Password", "Long", "Copy-friendly, 14 characters, contains symbols.", MPElementTypeClass.Generated ),
|
||||
GeneratedMedium( "Medium Password", "Medium", "Copy-friendly, 8 characters, contains symbols.", MPElementTypeClass.Generated ),
|
||||
GeneratedShort( "Short Password", "Short", "Copy-friendly, 4 characters, no symbols.", MPElementTypeClass.Generated ),
|
||||
GeneratedBasic( "Basic Password", "Basic", "8 characters, no symbols.", MPElementTypeClass.Generated ),
|
||||
GeneratedPIN( "PIN", "PIN", "4 numbers.", MPElementTypeClass.Generated ),
|
||||
|
||||
StoredPersonal( "Personal Password", "Personal", "AES-encrypted, exportable.", MPElementTypeClass.Stored, MPElementFeature.ExportContent ),
|
||||
StoredDevicePrivate( "Device Private Password", "Private", "AES-encrypted, not exported.", MPElementTypeClass.Stored, MPElementFeature.DevicePrivate );
|
||||
|
||||
static final Logger logger = Logger.get( MPElementType.class );
|
||||
|
||||
private final MPElementTypeClass typeClass;
|
||||
private final Set<MPElementFeature> typeFeatures;
|
||||
private final String name;
|
||||
private final String shortName;
|
||||
private final String description;
|
||||
|
||||
MPElementType(final String name, final String shortName, final String description, final MPElementTypeClass typeClass, final MPElementFeature... typeFeatures) {
|
||||
|
||||
this.name = name;
|
||||
this.shortName = shortName;
|
||||
this.typeClass = typeClass;
|
||||
this.description = description;
|
||||
|
||||
ImmutableSet.Builder<MPElementFeature> typeFeaturesBuilder = ImmutableSet.builder();
|
||||
for (final MPElementFeature typeFeature : typeFeatures)
|
||||
typeFeaturesBuilder.add( typeFeature );
|
||||
this.typeFeatures = typeFeaturesBuilder.build();
|
||||
}
|
||||
|
||||
public MPElementTypeClass getTypeClass() {
|
||||
|
||||
return typeClass;
|
||||
}
|
||||
|
||||
public Set<MPElementFeature> getTypeFeatures() {
|
||||
|
||||
return typeFeatures;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getShortName() {
|
||||
|
||||
return shortName;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
|
||||
return description;
|
||||
}
|
||||
|
||||
public static MPElementType forName(final String name) {
|
||||
|
||||
for (final MPElementType type : values())
|
||||
if (type.getName().equals( name ))
|
||||
return type;
|
||||
|
||||
throw logger.bug( "Element type not known: %s", name );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.lyndir.lhunath.masterpassword;
|
||||
|
||||
import com.lyndir.lhunath.masterpassword.entity.*;
|
||||
|
||||
|
||||
/**
|
||||
* <i>07 04, 2012</i>
|
||||
*
|
||||
* @author lhunath
|
||||
*/
|
||||
public enum MPElementTypeClass {
|
||||
|
||||
Generated(MPElementGeneratedEntity.class),
|
||||
Stored(MPElementStoredEntity.class);
|
||||
|
||||
private final Class<? extends MPElementEntity> entityClass;
|
||||
|
||||
MPElementTypeClass(final Class<? extends MPElementEntity> entityClass) {
|
||||
|
||||
this.entityClass = entityClass;
|
||||
}
|
||||
|
||||
public Class<? extends MPElementEntity> getEntityClass() {
|
||||
|
||||
return entityClass;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.lyndir.lhunath.masterpassword;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.lyndir.lhunath.opal.system.util.MetaObject;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
/**
|
||||
* <i>07 04, 2012</i>
|
||||
*
|
||||
* @author lhunath
|
||||
*/
|
||||
public class MPTemplate extends MetaObject {
|
||||
|
||||
private final List<MPTemplateCharacterClass> template;
|
||||
|
||||
public MPTemplate(final String template, final Map<Character, MPTemplateCharacterClass> characterClasses) {
|
||||
|
||||
ImmutableList.Builder<MPTemplateCharacterClass> builder = ImmutableList.<MPTemplateCharacterClass>builder();
|
||||
for (int i = 0; i < template.length(); ++i)
|
||||
builder.add( characterClasses.get( template.charAt( i ) ) );
|
||||
|
||||
this.template = builder.build();
|
||||
}
|
||||
|
||||
public MPTemplate(final List<MPTemplateCharacterClass> template) {
|
||||
|
||||
this.template = template;
|
||||
}
|
||||
|
||||
public MPTemplateCharacterClass getCharacterClassAtIndex(final int index) {
|
||||
|
||||
return template.get( index );
|
||||
}
|
||||
|
||||
public int length() {
|
||||
|
||||
return template.size();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.lyndir.lhunath.masterpassword;
|
||||
|
||||
import com.lyndir.lhunath.opal.system.util.MetaObject;
|
||||
import com.lyndir.lhunath.opal.system.util.ObjectMeta;
|
||||
|
||||
|
||||
/**
|
||||
* <i>07 04, 2012</i>
|
||||
*
|
||||
* @author lhunath
|
||||
*/
|
||||
public class MPTemplateCharacterClass extends MetaObject {
|
||||
|
||||
private final char identifier;
|
||||
@ObjectMeta(useFor = { })
|
||||
private final char[] characters;
|
||||
|
||||
public MPTemplateCharacterClass(final char identifier, final char[] characters) {
|
||||
|
||||
this.identifier = identifier;
|
||||
this.characters = characters;
|
||||
}
|
||||
|
||||
public char getIdentifier() {
|
||||
|
||||
return identifier;
|
||||
}
|
||||
|
||||
public char getCharacterAtRollingIndex(final int index) {
|
||||
|
||||
return characters[index % characters.length];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
package com.lyndir.lhunath.masterpassword;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.base.Throwables;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.io.Closeables;
|
||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||
import com.lyndir.lhunath.opal.system.util.MetaObject;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import net.sf.plist.*;
|
||||
import net.sf.plist.io.PropertyListException;
|
||||
import net.sf.plist.io.PropertyListParser;
|
||||
|
||||
|
||||
/**
|
||||
* <i>07 04, 2012</i>
|
||||
*
|
||||
* @author lhunath
|
||||
*/
|
||||
public class MPTemplates extends MetaObject {
|
||||
|
||||
static final Logger logger = Logger.get( MPTemplates.class );
|
||||
|
||||
private final Map<MPElementType, List<MPTemplate>> templates;
|
||||
|
||||
public MPTemplates(final Map<MPElementType, List<MPTemplate>> templates) {
|
||||
|
||||
this.templates = templates;
|
||||
}
|
||||
|
||||
public static MPTemplates loadFromPList(final String templateResource) {
|
||||
|
||||
@SuppressWarnings("IOResourceOpenedButNotSafelyClosed")
|
||||
InputStream templateStream = Thread.currentThread().getContextClassLoader().getResourceAsStream( templateResource );
|
||||
try {
|
||||
NSObject plistObject = PropertyListParser.parse( templateStream );
|
||||
Preconditions.checkState( NSDictionary.class.isAssignableFrom( plistObject.getClass() ) );
|
||||
NSDictionary plist = (NSDictionary) plistObject;
|
||||
|
||||
NSDictionary characterClassesDict = (NSDictionary) plist.get( "MPCharacterClasses" );
|
||||
NSDictionary templatesDict = (NSDictionary) plist.get( "MPElementGeneratedEntity" );
|
||||
|
||||
ImmutableMap.Builder<Character, MPTemplateCharacterClass> characterClassesBuilder = ImmutableMap.builder();
|
||||
for (final Map.Entry<String, NSObject> characterClassEntry : characterClassesDict.entrySet()) {
|
||||
String key = characterClassEntry.getKey();
|
||||
NSObject value = characterClassEntry.getValue();
|
||||
Preconditions.checkState( key.length() == 1 );
|
||||
Preconditions.checkState( NSString.class.isAssignableFrom( value.getClass() ));
|
||||
|
||||
char character = key.charAt( 0 );
|
||||
char[] characterClass = ((NSString)value).getValue().toCharArray();
|
||||
characterClassesBuilder.put( character, new MPTemplateCharacterClass( character, characterClass ) );
|
||||
}
|
||||
ImmutableMap<Character, MPTemplateCharacterClass> characterClasses = characterClassesBuilder.build();
|
||||
|
||||
ImmutableMap.Builder<MPElementType, List<MPTemplate>> templatesBuilder = ImmutableMap.builder();
|
||||
for (final Map.Entry<String, NSObject> template : templatesDict.entrySet()) {
|
||||
String key = template.getKey();
|
||||
NSObject value = template.getValue();
|
||||
Preconditions.checkState( NSArray.class.isAssignableFrom( value.getClass() ) );
|
||||
|
||||
MPElementType type = MPElementType.forName( key );
|
||||
List<NSObject> templateStrings = ((NSArray) value).getValue();
|
||||
|
||||
ImmutableList.Builder<MPTemplate> typeTemplatesBuilder = ImmutableList.<MPTemplate>builder();
|
||||
for (final NSObject templateString : templateStrings)
|
||||
typeTemplatesBuilder.add( new MPTemplate( ((NSString) templateString).getValue(), characterClasses ) );
|
||||
|
||||
templatesBuilder.put( type, typeTemplatesBuilder.build() );
|
||||
}
|
||||
ImmutableMap<MPElementType, List<MPTemplate>> templates = templatesBuilder.build();
|
||||
|
||||
return new MPTemplates( templates );
|
||||
}
|
||||
catch (PropertyListException e) {
|
||||
logger.err( e, "Could not parse templates from: %s", templateResource );
|
||||
throw Throwables.propagate( e );
|
||||
}
|
||||
catch (IOException e) {
|
||||
logger.err( e, "Could not read templates from: %s", templateResource );
|
||||
throw Throwables.propagate( e );
|
||||
}
|
||||
finally {
|
||||
Closeables.closeQuietly( templateStream );
|
||||
}
|
||||
}
|
||||
|
||||
public MPTemplate getTemplateForTypeAtRollingIndex(final MPElementType type, final int templateIndex) {
|
||||
|
||||
List<MPTemplate> typeTemplates = templates.get( type );
|
||||
|
||||
return typeTemplates.get( templateIndex % typeTemplates.size() );
|
||||
}
|
||||
|
||||
public static void main(final String... arguments) {
|
||||
|
||||
loadFromPList( "templates.plist" );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
package com.lyndir.lhunath.masterpassword;
|
||||
|
||||
import com.google.common.base.Charsets;
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.primitives.Bytes;
|
||||
import com.lambdaworks.crypto.SCrypt;
|
||||
import com.lyndir.lhunath.opal.crypto.CryptUtils;
|
||||
import com.lyndir.lhunath.opal.system.*;
|
||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.charset.Charset;
|
||||
import java.security.GeneralSecurityException;
|
||||
|
||||
|
||||
/**
|
||||
* Implementation of the Master Password algorithm.
|
||||
*
|
||||
* <i>07 04, 2012</i>
|
||||
*
|
||||
* @author lhunath
|
||||
*/
|
||||
public abstract class MasterPassword {
|
||||
|
||||
static final Logger logger = Logger.get( MasterPassword.class );
|
||||
private static final int MP_N = 32768;
|
||||
private static final int MP_r = 8;
|
||||
private static final int MP_p = 2;
|
||||
private static final int MP_dkLen = 64;
|
||||
private static final Charset MP_charset = Charsets.UTF_8;
|
||||
private static final ByteOrder MP_byteOrder = ByteOrder.BIG_ENDIAN;
|
||||
private static final MessageDigests MP_hash = MessageDigests.SHA256;
|
||||
private static final MessageAuthenticationDigests MP_mac = MessageAuthenticationDigests.HmacSHA256;
|
||||
private static final MPTemplates templates = MPTemplates.loadFromPList( "templates.plist" );
|
||||
|
||||
public static byte[] keyForPassword(final String password, final String username) {
|
||||
|
||||
long start = System.currentTimeMillis();
|
||||
byte[] nusernameLengthBytes = ByteBuffer.allocate( Integer.SIZE / Byte.SIZE )
|
||||
.order( MP_byteOrder )
|
||||
.putInt( username.length() )
|
||||
.array();
|
||||
byte[] salt = Bytes.concat( "com.lyndir.masterpassword".getBytes( MP_charset ), //
|
||||
nusernameLengthBytes, //
|
||||
username.getBytes( MP_charset ) );
|
||||
|
||||
try {
|
||||
byte[] key = SCrypt.scrypt( password.getBytes( MP_charset ), salt, MP_N, MP_r, MP_p, MP_dkLen );
|
||||
logger.trc( "User: %s, password: %s derives to key ID: %s (took %.2fs)", username, password,
|
||||
CodeUtils.encodeHex( keyIDForKey( key ) ), (double) (System.currentTimeMillis() - start) / 1000 );
|
||||
|
||||
return key;
|
||||
}
|
||||
catch (GeneralSecurityException e) {
|
||||
throw logger.bug( e );
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] subkeyForKey(final byte[] key, final int subkeyLength) {
|
||||
|
||||
byte[] subkey = new byte[Math.min( subkeyLength, key.length )];
|
||||
System.arraycopy( key, 0, subkey, 0, subkey.length );
|
||||
|
||||
return subkey;
|
||||
}
|
||||
|
||||
public static byte[] keyIDForPassword(final String password, final String username) {
|
||||
|
||||
return keyIDForKey( keyForPassword( password, username ) );
|
||||
}
|
||||
|
||||
public static byte[] keyIDForKey(final byte[] key) {
|
||||
|
||||
return MP_hash.of( key );
|
||||
}
|
||||
|
||||
public static String generateContent(final MPElementType type, final String name, final byte[] key, int counter) {
|
||||
|
||||
Preconditions.checkArgument( type.getTypeClass() == MPElementTypeClass.Generated );
|
||||
Preconditions.checkArgument( !name.isEmpty() );
|
||||
Preconditions.checkArgument( key.length > 0 );
|
||||
|
||||
if (counter == 0)
|
||||
counter = (int) (System.currentTimeMillis() / (300 * 1000)) * 300;
|
||||
|
||||
byte[] nameLengthBytes = ByteBuffer.allocate( Integer.SIZE / Byte.SIZE ).order( MP_byteOrder ).putInt( name.length() ).array();
|
||||
byte[] counterBytes = ByteBuffer.allocate( Integer.SIZE / Byte.SIZE ).order( MP_byteOrder ).putInt( counter ).array();
|
||||
logger.trc( "seed from: hmac-sha256(%s, 'com.lyndir.masterpassword' | %s | %s | %s)", CryptUtils.encodeBase64( key ),
|
||||
CodeUtils.encodeHex( nameLengthBytes ), name, CodeUtils.encodeHex( counterBytes ) );
|
||||
byte[] seed = MP_mac.of( key, Bytes.concat( "com.lyndir.masterpassword".getBytes( MP_charset ), //
|
||||
nameLengthBytes, //
|
||||
name.getBytes( MP_charset ), //
|
||||
counterBytes ) );
|
||||
logger.trc( "seed is: %s", CryptUtils.encodeBase64( seed ) );
|
||||
|
||||
Preconditions.checkState( seed.length > 0 );
|
||||
int templateIndex = seed[0] & 0xFF; // Mask the integer's sign.
|
||||
MPTemplate template = templates.getTemplateForTypeAtRollingIndex( type, templateIndex );
|
||||
logger.trc( "type: %s, template: %s", type, template );
|
||||
|
||||
StringBuilder password = new StringBuilder( template.length() );
|
||||
for (int i = 0; i < template.length(); ++i) {
|
||||
int characterIndex = seed[i + 1] & 0xFF; // Mask the integer's sign.
|
||||
MPTemplateCharacterClass characterClass = template.getCharacterClassAtIndex( i );
|
||||
char passwordCharacter = characterClass.getCharacterAtRollingIndex( characterIndex );
|
||||
logger.trc( "class: %s, index: %d, byte: 0x%02X, chosen password character: %s", characterClass, characterIndex, seed[i + 1],
|
||||
passwordCharacter );
|
||||
|
||||
password.append( passwordCharacter );
|
||||
}
|
||||
|
||||
return password.toString();
|
||||
}
|
||||
|
||||
public static void main(final String... arguments) {
|
||||
|
||||
String masterPassword = "test-mp";
|
||||
String username = "test-user";
|
||||
String siteName = "test-site";
|
||||
MPElementType siteType = MPElementType.GeneratedLong;
|
||||
int siteCounter = 42;
|
||||
|
||||
String sitePassword = generateContent( siteType, siteName, keyForPassword( masterPassword, username ), siteCounter );
|
||||
|
||||
logger.inf( "master password: %s, username: %s\nsite name: %s, site type: %s, site counter: %d\n => site password: %s",
|
||||
masterPassword, username, siteName, siteType, siteCounter, sitePassword );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.lyndir.lhunath.masterpassword.entity;
|
||||
|
||||
/**
|
||||
* <i>07 04, 2012</i>
|
||||
*
|
||||
* @author lhunath
|
||||
*/
|
||||
public class MPElementEntity {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.lyndir.lhunath.masterpassword.entity;
|
||||
|
||||
/**
|
||||
* <i>07 04, 2012</i>
|
||||
*
|
||||
* @author lhunath
|
||||
*/
|
||||
public class MPElementGeneratedEntity extends MPElementEntity {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.lyndir.lhunath.masterpassword.entity;
|
||||
|
||||
/**
|
||||
* <i>07 04, 2012</i>
|
||||
*
|
||||
* @author lhunath
|
||||
*/
|
||||
public class MPElementStoredEntity extends MPElementEntity {
|
||||
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
../../../../../../Resources/ciphers.plist
|
||||
80
MasterPassword/Java/masterpassword-cli/pom.xml
Normal file
@@ -0,0 +1,80 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<!-- PROJECT METADATA -->
|
||||
<parent>
|
||||
<groupId>com.lyndir.lhunath.masterpassword</groupId>
|
||||
<artifactId>masterpassword</artifactId>
|
||||
<version>GIT-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<name>Master Password CLI</name>
|
||||
<description>A CLI interface to the Master Password algorithm</description>
|
||||
|
||||
<groupId>com.lyndir.lhunath.masterpassword</groupId>
|
||||
<artifactId>masterpassword-cli</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<!-- BUILD CONFIGURATION -->
|
||||
<build>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>src/main/scripts</directory>
|
||||
<filtering>true</filtering>
|
||||
<targetPath>${project.build.directory}</targetPath>
|
||||
</resource>
|
||||
</resources>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<configuration>
|
||||
<archive>
|
||||
<manifest>
|
||||
<mainClass>com.lyndir.lhunath.masterpassword.CLI</mainClass>
|
||||
<addClasspath>true</addClasspath>
|
||||
<classpathPrefix>lib/</classpathPrefix>
|
||||
</manifest>
|
||||
</archive>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-dependency-plugin</artifactId>
|
||||
<version>2.4</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>copy-dependencies</id>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>copy-dependencies</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<outputDirectory>${project.build.directory}/lib</outputDirectory>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<!-- DEPENDENCY MANAGEMENT -->
|
||||
<dependencies>
|
||||
|
||||
<!-- PROJECT REFERENCES -->
|
||||
<dependency>
|
||||
<groupId>com.lyndir.lhunath.masterpassword</groupId>
|
||||
<artifactId>masterpassword-algorithm</artifactId>
|
||||
<version>GIT-SNAPSHOT</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-classic</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
* Copyright 2008, Maarten Billemont
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.lyndir.lhunath.masterpassword;
|
||||
|
||||
import com.google.common.io.LineReader;
|
||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||
import com.lyndir.lhunath.opal.system.util.ConversionUtils;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.Arrays;
|
||||
|
||||
|
||||
/**
|
||||
* <p> <i>Jun 10, 2008</i> </p>
|
||||
*
|
||||
* @author mbillemo
|
||||
*/
|
||||
public class CLI {
|
||||
|
||||
static final Logger logger = Logger.get( CLI.class );
|
||||
private static final String ENV_USERNAME = "MP_USERNAME";
|
||||
private static final String ENV_PASSWORD = "MP_PASSWORD";
|
||||
|
||||
public static void main(final String[] args)
|
||||
throws IOException {
|
||||
|
||||
String userName, masterPassword, siteName = null;
|
||||
|
||||
/* Environment. */
|
||||
userName = System.getenv().get( ENV_USERNAME );
|
||||
masterPassword = System.getenv().get( ENV_PASSWORD );
|
||||
|
||||
/* Arguments. */
|
||||
int counter = 1;
|
||||
MPElementType type = MPElementType.GeneratedLong;
|
||||
boolean typeArg = false, counterArg = false, userNameArg = false;
|
||||
for (final String arg : Arrays.asList( args ))
|
||||
if ("-t".equals( arg ) || "--type".equals( arg ))
|
||||
typeArg = true;
|
||||
else if (typeArg) {
|
||||
if ("list".equalsIgnoreCase( arg )) {
|
||||
System.out.format( "%30s | %s\n", "type", "description" );
|
||||
for (final MPElementType aType : MPElementType.values())
|
||||
System.out.format( "%30s | %s\n", aType.getName(), aType.getDescription() );
|
||||
System.exit( 0 );
|
||||
}
|
||||
|
||||
type = MPElementType.forName( arg );
|
||||
typeArg = false;
|
||||
} else if ("-c".equals( arg ) || "--counter".equals( arg ))
|
||||
counterArg = true;
|
||||
else if (counterArg) {
|
||||
counter = ConversionUtils.toIntegerNN( arg );
|
||||
counterArg = false;
|
||||
} else if ("-u".equals( arg ) || "--username".equals( arg ))
|
||||
userNameArg = true;
|
||||
else if (userNameArg) {
|
||||
userName = arg;
|
||||
userNameArg = false;
|
||||
} else if ("-h".equals( arg ) || "--help".equals( arg )) {
|
||||
System.out.println();
|
||||
System.out.println( "\tMaster Password CLI" );
|
||||
System.out.println( "\t\tLyndir" );
|
||||
|
||||
System.out.println( "[options] [site name]" );
|
||||
System.out.println();
|
||||
System.out.println( "Available options:" );
|
||||
|
||||
System.out.println( "\t-t | --type [site password type]" );
|
||||
System.out.format( "\t\tDefault: %s. The password type to use for this site.\n", type.getName() );
|
||||
System.out.println( "\t\tUse 'list' to see the available types." );
|
||||
|
||||
System.out.println();
|
||||
System.out.println( "\t-c | --counter [site counter]" );
|
||||
System.out.format( "\t\tDefault: %d. The counter to use for this site.\n", counter );
|
||||
System.out.println( "\t\tIncrement the counter if you need a new password." );
|
||||
|
||||
System.out.println();
|
||||
System.out.println( "\t-u | --username [user's name]" );
|
||||
System.out.println( "\t\tDefault: asked. The name of the user." );
|
||||
|
||||
System.out.println();
|
||||
System.out.println( "Available environment variables:" );
|
||||
|
||||
System.out.format( "\t%s\n", ENV_USERNAME );
|
||||
System.out.println( "\t\tThe name of the user." );
|
||||
|
||||
System.out.format( "\t%s\n", ENV_PASSWORD );
|
||||
System.out.println( "\t\tThe master password of the user." );
|
||||
|
||||
System.out.println();
|
||||
return;
|
||||
} else
|
||||
siteName = arg;
|
||||
|
||||
InputStreamReader inReader = new InputStreamReader( System.in );
|
||||
try {
|
||||
LineReader lineReader = new LineReader( inReader );
|
||||
if (siteName == null) {
|
||||
System.err.format( "Site name: " );
|
||||
siteName = lineReader.readLine();
|
||||
}
|
||||
if (userName == null) {
|
||||
System.err.format( "User's name: " );
|
||||
userName = lineReader.readLine();
|
||||
}
|
||||
if (masterPassword == null) {
|
||||
System.err.format( "%s's master password: ", userName );
|
||||
masterPassword = lineReader.readLine();
|
||||
}
|
||||
|
||||
byte[] masterKey = MasterPassword.keyForPassword( masterPassword, userName );
|
||||
String sitePassword = MasterPassword.generateContent( type, siteName, masterKey, counter );
|
||||
System.out.println( sitePassword );
|
||||
}
|
||||
finally {
|
||||
inReader.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<configuration scan="true">
|
||||
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<layout class="ch.qos.logback.classic.PatternLayout">
|
||||
<Pattern>%-8relative %22c{0} [%-5level] %msg%n</Pattern>
|
||||
</layout>
|
||||
</appender>
|
||||
|
||||
<logger name="com.lyndir" level="TRACE" />
|
||||
|
||||
<!--
|
||||
<logger name="org.apache.wicket" level="DEBUG" />
|
||||
-->
|
||||
|
||||
<root level="INFO">
|
||||
<appender-ref ref="STDOUT" />
|
||||
</root>
|
||||
|
||||
</configuration>
|
||||
4
MasterPassword/Java/masterpassword-cli/src/main/scripts/mpw
Executable file
@@ -0,0 +1,4 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
cd "${BASH_SOURCE[0]%/*}"
|
||||
java -jar masterpassword-cli-GIT-SNAPSHOT.jar "$@"
|
||||
26
MasterPassword/Java/pom.xml
Normal file
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<!-- PROJECT METADATA -->
|
||||
<parent>
|
||||
<groupId>com.lyndir.lhunath</groupId>
|
||||
<artifactId>lyndir</artifactId>
|
||||
<version>GIT-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<name>Master Password</name>
|
||||
<description>A Java implementation of the Master Password algorithm.</description>
|
||||
|
||||
<groupId>com.lyndir.lhunath.masterpassword</groupId>
|
||||
<artifactId>masterpassword</artifactId>
|
||||
<version>GIT-SNAPSHOT</version>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<modules>
|
||||
<module>masterpassword-algorithm</module>
|
||||
<module>masterpassword-cli</module>
|
||||
</modules>
|
||||
|
||||
</project>
|
||||
44
MasterPassword/MPAlgorithm.h
Normal file
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
|
||||
*
|
||||
* See the enclosed file LICENSE for license information (LGPLv3). If you did
|
||||
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
*
|
||||
* @author Maarten Billemont <lhunath@lyndir.com>
|
||||
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
*/
|
||||
|
||||
//
|
||||
// MPAlgorithm
|
||||
//
|
||||
// Created by Maarten Billemont on 16/07/12.
|
||||
// Copyright 2012 lhunath (Maarten Billemont). All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPKey.h"
|
||||
#import "MPElementGeneratedEntity.h"
|
||||
|
||||
#define MPAlgorithmDefaultVersion 1
|
||||
#define MPAlgorithmDefault MPAlgorithmForVersion(MPAlgorithmDefaultVersion)
|
||||
|
||||
@protocol MPAlgorithm<NSObject>
|
||||
|
||||
@required
|
||||
- (NSUInteger)version;
|
||||
- (BOOL)migrateElement:(MPElementEntity *)element explicit:(BOOL)explicit;
|
||||
|
||||
- (MPKey *)keyForPassword:(NSString *)password ofUserNamed:(NSString *)userName;
|
||||
- (MPKey *)keyFromKeyData:(NSData *)keyData;
|
||||
- (NSData *)keyIDForKeyData:(NSData *)keyData;
|
||||
|
||||
- (NSString *)nameOfType:(MPElementType)type;
|
||||
- (NSString *)shortNameOfType:(MPElementType)type;
|
||||
- (NSString *)classNameOfType:(MPElementType)type;
|
||||
- (Class)classOfType:(MPElementType)type;
|
||||
|
||||
- (NSString *)generateContentForElement:(MPElementGeneratedEntity *)element usingKey:(MPKey *)key;
|
||||
|
||||
@end
|
||||
|
||||
id<MPAlgorithm> MPAlgorithmForVersion(NSUInteger version);
|
||||
id<MPAlgorithm> MPAlgorithmDefaultForBundleVersion(NSString *bundleVersion);
|
||||
42
MasterPassword/MPAlgorithm.m
Normal file
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
|
||||
*
|
||||
* See the enclosed file LICENSE for license information (LGPLv3). If you did
|
||||
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
*
|
||||
* @author Maarten Billemont <lhunath@lyndir.com>
|
||||
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
*/
|
||||
|
||||
//
|
||||
// MPAlgorithm
|
||||
//
|
||||
// Created by Maarten Billemont on 16/07/12.
|
||||
// Copyright 2012 lhunath (Maarten Billemont). All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPAlgorithm.h"
|
||||
#import "MPEntities.h"
|
||||
|
||||
id<MPAlgorithm> MPAlgorithmForVersion(NSUInteger version) {
|
||||
|
||||
static NSMutableDictionary *versionToAlgorithm = nil;
|
||||
if (!versionToAlgorithm)
|
||||
versionToAlgorithm = [NSMutableDictionary dictionary];
|
||||
|
||||
id<MPAlgorithm> algorithm = [versionToAlgorithm objectForKey:PearlUnsignedInteger(version)];
|
||||
if (!algorithm)
|
||||
if ((algorithm = [NSClassFromString(PearlString(@"MPAlgorithmV%u", version)) new]))
|
||||
[versionToAlgorithm setObject:algorithm forKey:PearlUnsignedInteger(version)];
|
||||
|
||||
return algorithm;
|
||||
}
|
||||
|
||||
id<MPAlgorithm> MPAlgorithmDefaultForBundleVersion(NSString *bundleVersion) {
|
||||
|
||||
if (PearlCFBundleVersionCompare(bundleVersion, @"1.3") == NSOrderedAscending)
|
||||
// Pre-1.3
|
||||
return MPAlgorithmForVersion(0);
|
||||
|
||||
return MPAlgorithmDefault;
|
||||
}
|
||||
21
MasterPassword/MPAlgorithmV0.h
Normal file
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
|
||||
*
|
||||
* See the enclosed file LICENSE for license information (LGPLv3). If you did
|
||||
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
*
|
||||
* @author Maarten Billemont <lhunath@lyndir.com>
|
||||
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
*/
|
||||
|
||||
//
|
||||
// MPAlgorithmV0
|
||||
//
|
||||
// Created by Maarten Billemont on 16/07/12.
|
||||
// Copyright 2012 lhunath (Maarten Billemont). All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPAlgorithm.h"
|
||||
|
||||
@interface MPAlgorithmV0 : NSObject <MPAlgorithm>
|
||||
@end
|
||||
@@ -1,46 +1,83 @@
|
||||
/**
|
||||
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
|
||||
*
|
||||
* See the enclosed file LICENSE for license information (LGPLv3). If you did
|
||||
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
*
|
||||
* @author Maarten Billemont <lhunath@lyndir.com>
|
||||
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
*/
|
||||
|
||||
//
|
||||
// MPTypes.m
|
||||
// MasterPassword
|
||||
// MPAlgorithmV0
|
||||
//
|
||||
// Created by Maarten Billemont on 02/01/12.
|
||||
// Copyright (c) 2012 Lyndir. All rights reserved.
|
||||
// Created by Maarten Billemont on 16/07/12.
|
||||
// Copyright 2012 lhunath (Maarten Billemont). All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPAlgorithmV0.h"
|
||||
#import "MPEntities.h"
|
||||
|
||||
|
||||
#define MP_N 131072
|
||||
#define MP_N 32768
|
||||
#define MP_r 8
|
||||
#define MP_p 1
|
||||
#define MP_p 2
|
||||
#define MP_dkLen 64
|
||||
#define MP_hash PearlHashSHA256
|
||||
|
||||
NSData *keyForPassword(NSString *password, NSString *username) {
|
||||
@implementation MPAlgorithmV0
|
||||
|
||||
uint32_t nusernameLength = htonl(username.length);
|
||||
NSData *key = [PearlSCrypt deriveKeyWithLength:MP_dkLen fromPassword:[password dataUsingEncoding:NSUTF8StringEncoding]
|
||||
usingSalt:[NSData dataByConcatenatingDatas:
|
||||
[@"com.lyndir.masterpassword" dataUsingEncoding:NSUTF8StringEncoding],
|
||||
[NSData dataWithBytes:&nusernameLength
|
||||
length:sizeof(nusernameLength)],
|
||||
[username dataUsingEncoding:NSUTF8StringEncoding],
|
||||
nil] N:MP_N r:MP_r p:MP_p];
|
||||
- (NSUInteger)version {
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
- (BOOL)migrateElement:(MPElementEntity *)element explicit:(BOOL)explicit {
|
||||
|
||||
if (element.version != [self version] - 1)
|
||||
// Only migrate from previous version.
|
||||
return NO;
|
||||
|
||||
if (!explicit) {
|
||||
// This migration requires explicit permission.
|
||||
element.requiresExplicitMigration = YES;
|
||||
return NO;
|
||||
}
|
||||
|
||||
// Apply migration.
|
||||
element.requiresExplicitMigration = NO;
|
||||
element.version = [self version];
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (MPKey *)keyForPassword:(NSString *)password ofUserNamed:(NSString *)userName {
|
||||
|
||||
uint32_t nuserNameLength = htonl(userName.length);
|
||||
NSDate *start = [NSDate date];
|
||||
NSData *keyData = [PearlSCrypt deriveKeyWithLength:MP_dkLen fromPassword:[password dataUsingEncoding:NSUTF8StringEncoding]
|
||||
usingSalt:[NSData dataByConcatenatingDatas:
|
||||
[@"com.lyndir.masterpassword" dataUsingEncoding:NSUTF8StringEncoding],
|
||||
[NSData dataWithBytes:&nuserNameLength
|
||||
length:sizeof(nuserNameLength)],
|
||||
[userName dataUsingEncoding:NSUTF8StringEncoding],
|
||||
nil] N:MP_N r:MP_r p:MP_p];
|
||||
|
||||
MPKey *key = [self keyFromKeyData:keyData];
|
||||
trc(@"User: %@, password: %@ derives to key ID: %@ (took %0.2fs)", userName, password, [key.keyID encodeHex], -[start timeIntervalSinceNow]);
|
||||
|
||||
trc(@"User: %@, password: %@ derives to key ID: %@", username, password, [keyIDForKey(key) encodeHex]);
|
||||
return key;
|
||||
}
|
||||
|
||||
NSData *keyIDForPassword(NSString *password, NSString *username) {
|
||||
- (MPKey *)keyFromKeyData:(NSData *)keyData {
|
||||
|
||||
return keyIDForKey(keyForPassword(password, username));
|
||||
return [[MPKey alloc] initWithKeyData:keyData algorithm:self];
|
||||
}
|
||||
|
||||
NSData *keyIDForKey(NSData *key) {
|
||||
- (NSData *)keyIDForKeyData:(NSData *)keyData {
|
||||
|
||||
return [key hashWith:MP_hash];
|
||||
return [keyData hashWith:MP_hash];
|
||||
}
|
||||
|
||||
NSString *NSStringFromMPElementType(MPElementType type) {
|
||||
- (NSString *)nameOfType:(MPElementType)type {
|
||||
|
||||
if (!type)
|
||||
return nil;
|
||||
@@ -69,13 +106,12 @@ NSString *NSStringFromMPElementType(MPElementType type) {
|
||||
|
||||
case MPElementTypeStoredDevicePrivate:
|
||||
return @"Device Private Password";
|
||||
|
||||
default:
|
||||
Throw(@"Type not supported: %d", type);
|
||||
}
|
||||
|
||||
Throw(@"Type not supported: %d", type);
|
||||
}
|
||||
|
||||
NSString *NSStringShortFromMPElementType(MPElementType type) {
|
||||
- (NSString *)shortNameOfType:(MPElementType)type {
|
||||
|
||||
if (!type)
|
||||
return nil;
|
||||
@@ -104,13 +140,17 @@ NSString *NSStringShortFromMPElementType(MPElementType type) {
|
||||
|
||||
case MPElementTypeStoredDevicePrivate:
|
||||
return @"Device";
|
||||
|
||||
default:
|
||||
Throw(@"Type not supported: %d", type);
|
||||
}
|
||||
|
||||
Throw(@"Type not supported: %d", type);
|
||||
}
|
||||
|
||||
Class ClassFromMPElementType(MPElementType type) {
|
||||
- (NSString *)classNameOfType:(MPElementType)type {
|
||||
|
||||
return NSStringFromClass([self classOfType:type]);
|
||||
}
|
||||
|
||||
- (Class)classOfType:(MPElementType)type {
|
||||
|
||||
if (!type)
|
||||
return nil;
|
||||
@@ -139,61 +179,56 @@ Class ClassFromMPElementType(MPElementType type) {
|
||||
|
||||
case MPElementTypeStoredDevicePrivate:
|
||||
return [MPElementStoredEntity class];
|
||||
|
||||
default:
|
||||
Throw(@"Type not supported: %d", type);
|
||||
}
|
||||
|
||||
Throw(@"Type not supported: %d", type);
|
||||
}
|
||||
|
||||
NSString *ClassNameFromMPElementType(MPElementType type) {
|
||||
- (NSString *)generateContentForElement:(MPElementGeneratedEntity *)element usingKey:(MPKey *)key {
|
||||
|
||||
return NSStringFromClass(ClassFromMPElementType(type));
|
||||
}
|
||||
static NSDictionary *MPTypes_ciphers = nil;
|
||||
|
||||
static NSDictionary *MPTypes_ciphers = nil;
|
||||
if (!element)
|
||||
return nil;
|
||||
|
||||
NSString *MPCalculateContent(MPElementType type, NSString *name, NSData *key, uint32_t counter) {
|
||||
|
||||
if (!(type & MPElementTypeClassGenerated)) {
|
||||
err(@"Incorrect type (is not MPElementTypeClassGenerated): %d, for: %@", type, name);
|
||||
if (!(element.type & MPElementTypeClassGenerated)) {
|
||||
err(@"Incorrect type (is not MPElementTypeClassGenerated): %@, for: %@", [self nameOfType:element.type], element.name);
|
||||
return nil;
|
||||
}
|
||||
if (!name.length) {
|
||||
if (!element.name.length) {
|
||||
err(@"Missing name.");
|
||||
return nil;
|
||||
}
|
||||
if (!key.length) {
|
||||
if (!key.keyData.length) {
|
||||
err(@"Missing key.");
|
||||
return nil;
|
||||
}
|
||||
if (!counter)
|
||||
// Counter unset, go into OTP mode.
|
||||
// Get the UNIX timestamp of the start of the interval of 5 minutes that the current time is in.
|
||||
counter = ((uint32_t)([[NSDate date] timeIntervalSince1970] / 300)) * 300;
|
||||
|
||||
if (MPTypes_ciphers == nil)
|
||||
MPTypes_ciphers = [NSDictionary dictionaryWithContentsOfURL:[[NSBundle mainBundle] URLForResource:@"ciphers"
|
||||
withExtension:@"plist"]];
|
||||
|
||||
// Determine the seed whose bytes will be used for calculating a password
|
||||
trc(@"seed from: hmac-sha256(%@, 'com.lyndir.masterpassword' | %u | %@ | %u)", key, name.length, name, counter);
|
||||
uint32_t ncounter = htonl(counter), nnameLength = htonl(name.length);
|
||||
uint32_t ncounter = htonl(element.counter), nnameLength = htonl(element.name.length);
|
||||
NSData *counterBytes = [NSData dataWithBytes:&ncounter length:sizeof(ncounter)];
|
||||
NSData *nameLengthBytes = [NSData dataWithBytes:&nnameLength length:sizeof(nnameLength)];
|
||||
trc(@"seed from: hmac-sha256(%@, 'com.lyndir.masterpassword' | %@ | %@ | %@)", [key.keyData encodeBase64], [nameLengthBytes encodeHex], element.name, [counterBytes encodeHex]);
|
||||
NSData *seed = [[NSData dataByConcatenatingDatas:
|
||||
[@"com.lyndir.masterpassword" dataUsingEncoding:NSUTF8StringEncoding],
|
||||
[NSData dataWithBytes:&nnameLength length:sizeof(nnameLength)],
|
||||
[name dataUsingEncoding:NSUTF8StringEncoding],
|
||||
[NSData dataWithBytes:&ncounter length:sizeof(ncounter)],
|
||||
nameLengthBytes,
|
||||
[element.name dataUsingEncoding:NSUTF8StringEncoding],
|
||||
counterBytes,
|
||||
nil]
|
||||
hmacWith:PearlHashSHA256 key:key];
|
||||
trc(@"seed is: %@", seed);
|
||||
hmacWith:PearlHashSHA256 key:key.keyData];
|
||||
trc(@"seed is: %@", [seed encodeBase64]);
|
||||
const char *seedBytes = seed.bytes;
|
||||
|
||||
// Determine the cipher from the first seed byte.
|
||||
assert([seed length]);
|
||||
NSArray *typeCiphers = [[MPTypes_ciphers valueForKey:ClassNameFromMPElementType(type)]
|
||||
valueForKey:NSStringFromMPElementType(type)];
|
||||
NSArray *typeCiphers = [[MPTypes_ciphers valueForKey:[self classNameOfType:element.type]]
|
||||
valueForKey:[self nameOfType:element.type]];
|
||||
NSString *cipher = [typeCiphers objectAtIndex:htons(seedBytes[0]) % [typeCiphers count]];
|
||||
trc(@"type %d, ciphers: %@, selected: %@", type, typeCiphers, cipher);
|
||||
trc(@"type %@, ciphers: %@, selected: %@", [self nameOfType:element.type], typeCiphers, cipher);
|
||||
|
||||
// Encode the content, character by character, using subsequent seed bytes and the cipher.
|
||||
assert([seed length] >= [cipher length] + 1);
|
||||
@@ -205,9 +240,11 @@ NSString *MPCalculateContent(MPElementType type, NSString *name, NSData *key, ui
|
||||
NSString *character = [cipherClassCharacters substringWithRange:NSMakeRange(keyByte % [cipherClassCharacters length],
|
||||
1)];
|
||||
|
||||
trc(@"class %@ has characters: %@, selected: %@", cipherClass, cipherClassCharacters, character);
|
||||
trc(@"class %@ has characters: %@, index: %u, selected: %@", cipherClass, cipherClassCharacters, keyByte, character);
|
||||
[content appendString:character];
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
@end
|
||||
22
MasterPassword/MPAlgorithmV1.h
Normal file
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
|
||||
*
|
||||
* See the enclosed file LICENSE for license information (LGPLv3). If you did
|
||||
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
*
|
||||
* @author Maarten Billemont <lhunath@lyndir.com>
|
||||
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
*/
|
||||
|
||||
//
|
||||
// MPAlgorithmV1
|
||||
//
|
||||
// Created by Maarten Billemont on 17/07/12.
|
||||
// Copyright 2012 lhunath (Maarten Billemont). All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPAlgorithmV0.h"
|
||||
|
||||
|
||||
@interface MPAlgorithmV1 : MPAlgorithmV0
|
||||
@end
|
||||
113
MasterPassword/MPAlgorithmV1.m
Normal file
@@ -0,0 +1,113 @@
|
||||
/**
|
||||
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
|
||||
*
|
||||
* See the enclosed file LICENSE for license information (LGPLv3). If you did
|
||||
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
*
|
||||
* @author Maarten Billemont <lhunath@lyndir.com>
|
||||
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
*/
|
||||
|
||||
//
|
||||
// MPAlgorithmV1
|
||||
//
|
||||
// Created by Maarten Billemont on 17/07/12.
|
||||
// Copyright 2012 lhunath (Maarten Billemont). All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPAlgorithmV1.h"
|
||||
#import "MPEntities.h"
|
||||
|
||||
|
||||
@implementation MPAlgorithmV1
|
||||
|
||||
- (NSUInteger)version {
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
- (BOOL)migrateElement:(MPElementEntity *)element explicit:(BOOL)explicit {
|
||||
|
||||
if (element.version != [self version] - 1)
|
||||
// Only migrate from previous version.
|
||||
return NO;
|
||||
|
||||
if (!explicit) {
|
||||
if (element.type & MPElementTypeClassGenerated) {
|
||||
// This migration requires explicit permission for types of the generated class.
|
||||
element.requiresExplicitMigration = YES;
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
|
||||
// Apply migration.
|
||||
element.requiresExplicitMigration = NO;
|
||||
element.version = [self version];
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (NSString *)generateContentForElement:(MPElementGeneratedEntity *)element usingKey:(MPKey *)key {
|
||||
|
||||
static NSDictionary *MPTypes_ciphers = nil;
|
||||
|
||||
if (!element)
|
||||
return nil;
|
||||
|
||||
if (!(element.type & MPElementTypeClassGenerated)) {
|
||||
err(@"Incorrect type (is not MPElementTypeClassGenerated): %@, for: %@", [self nameOfType:element.type], element.name);
|
||||
return nil;
|
||||
}
|
||||
if (!element.name.length) {
|
||||
err(@"Missing name.");
|
||||
return nil;
|
||||
}
|
||||
if (!key.keyData.length) {
|
||||
err(@"Missing key.");
|
||||
return nil;
|
||||
}
|
||||
|
||||
if (MPTypes_ciphers == nil)
|
||||
MPTypes_ciphers = [NSDictionary dictionaryWithContentsOfURL:[[NSBundle mainBundle] URLForResource:@"ciphers"
|
||||
withExtension:@"plist"]];
|
||||
|
||||
// Determine the seed whose bytes will be used for calculating a password
|
||||
uint32_t ncounter = htonl(element.counter), nnameLength = htonl(element.name.length);
|
||||
NSData *counterBytes = [NSData dataWithBytes:&ncounter length:sizeof(ncounter)];
|
||||
NSData *nameLengthBytes = [NSData dataWithBytes:&nnameLength length:sizeof(nnameLength)];
|
||||
trc(@"seed from: hmac-sha256(%@, 'com.lyndir.masterpassword' | %@ | %@ | %@)", [key.keyData encodeBase64], [nameLengthBytes encodeHex], element.name, [counterBytes encodeHex]);
|
||||
NSData *seed = [[NSData dataByConcatenatingDatas:
|
||||
[@"com.lyndir.masterpassword" dataUsingEncoding:NSUTF8StringEncoding],
|
||||
nameLengthBytes,
|
||||
[element.name dataUsingEncoding:NSUTF8StringEncoding],
|
||||
counterBytes,
|
||||
nil]
|
||||
hmacWith:PearlHashSHA256 key:key.keyData];
|
||||
trc(@"seed is: %@", [seed encodeBase64]);
|
||||
const unsigned char *seedBytes = seed.bytes;
|
||||
|
||||
// Determine the cipher from the first seed byte.
|
||||
assert([seed length]);
|
||||
NSArray *typeCiphers = [[MPTypes_ciphers valueForKey:[self classNameOfType:element.type]]
|
||||
valueForKey:[self nameOfType:element.type]];
|
||||
NSString *cipher = [typeCiphers objectAtIndex:seedBytes[0] % [typeCiphers count]];
|
||||
trc(@"type %@, ciphers: %@, selected: %@", [self nameOfType:element.type], typeCiphers, cipher);
|
||||
|
||||
// Encode the content, character by character, using subsequent seed bytes and the cipher.
|
||||
assert([seed length] >= [cipher length] + 1);
|
||||
NSMutableString *content = [NSMutableString stringWithCapacity:[cipher length]];
|
||||
for (NSUInteger c = 0; c < [cipher length]; ++c) {
|
||||
uint16_t keyByte = seedBytes[c + 1];
|
||||
NSString *cipherClass = [cipher substringWithRange:NSMakeRange(c, 1)];
|
||||
NSString *cipherClassCharacters = [[MPTypes_ciphers valueForKey:@"MPCharacterClasses"] valueForKey:cipherClass];
|
||||
NSString *character = [cipherClassCharacters substringWithRange:NSMakeRange(keyByte % [cipherClassCharacters length],
|
||||
1)];
|
||||
|
||||
trc(@"class %@ has characters: %@, index: %u, selected: %@", cipherClass, cipherClassCharacters, keyByte, character);
|
||||
[content appendString:character];
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
@@ -11,11 +11,9 @@
|
||||
@interface MPAppDelegate_Shared (Key)
|
||||
|
||||
- (BOOL)signInAsUser:(MPUserEntity *)user usingMasterPassword:(NSString *)password;
|
||||
- (void)signOut;
|
||||
- (void)signOutAnimated:(BOOL)animated;
|
||||
|
||||
- (void)storeSavedKeyFor:(MPUserEntity *)user;
|
||||
- (void)forgetSavedKeyFor:(MPUserEntity *)user;
|
||||
|
||||
- (NSData *)keyWithLength:(NSUInteger)keyLength;
|
||||
|
||||
@end
|
||||
|
||||
@@ -23,10 +23,10 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
|
||||
matches:nil];
|
||||
}
|
||||
|
||||
- (NSData *)loadSavedKeyFor:(MPUserEntity *)user {
|
||||
- (MPKey *)loadSavedKeyFor:(MPUserEntity *)user {
|
||||
|
||||
NSData *key = [PearlKeyChain dataOfItemForQuery:keyQuery(user)];
|
||||
if (key)
|
||||
NSData *keyData = [PearlKeyChain dataOfItemForQuery:keyQuery(user)];
|
||||
if (keyData)
|
||||
inf(@"Found key in keychain for: %@", user.userID);
|
||||
|
||||
else {
|
||||
@@ -34,22 +34,22 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
|
||||
inf(@"No key found in keychain for: %@", user.userID);
|
||||
}
|
||||
|
||||
return key;
|
||||
return [MPAlgorithmDefault keyFromKeyData:keyData];
|
||||
}
|
||||
|
||||
- (void)storeSavedKeyFor:(MPUserEntity *)user {
|
||||
|
||||
if (user.saveKey) {
|
||||
NSData *existingKey = [PearlKeyChain dataOfItemForQuery:keyQuery(user)];
|
||||
NSData *existingKeyData = [PearlKeyChain dataOfItemForQuery:keyQuery(user)];
|
||||
|
||||
if (![existingKey isEqualToData:self.key]) {
|
||||
if (![existingKeyData isEqualToData:self.key.keyData]) {
|
||||
inf(@"Saving key in keychain for: %@", user.userID);
|
||||
|
||||
[PearlKeyChain addOrUpdateItemForQuery:keyQuery(user)
|
||||
withAttributes:[NSDictionary dictionaryWithObjectsAndKeys:
|
||||
self.key, (__bridge id)kSecValueData,
|
||||
self.key.keyData, (__bridge id)kSecValueData,
|
||||
#if TARGET_OS_IPHONE
|
||||
kSecAttrAccessibleWhenUnlockedThisDeviceOnly, (__bridge id)kSecAttrAccessible,
|
||||
(__bridge id)kSecAttrAccessibleWhenUnlockedThisDeviceOnly, (__bridge id)kSecAttrAccessible,
|
||||
#endif
|
||||
nil]];
|
||||
}
|
||||
@@ -73,26 +73,27 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
|
||||
}
|
||||
}
|
||||
|
||||
- (void)signOut {
|
||||
- (void)signOutAnimated:(BOOL)animated {
|
||||
|
||||
if (self.key)
|
||||
self.key = nil;
|
||||
|
||||
if (self.activeUser) {
|
||||
self.activeUser = nil;
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:MPNotificationSignedOut object:self];
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:MPNotificationSignedOut object:self userInfo:
|
||||
[NSDictionary dictionaryWithObject:PearlBool(animated) forKey:@"animated"]];
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)signInAsUser:(MPUserEntity *)user usingMasterPassword:(NSString *)password {
|
||||
|
||||
NSData *tryKey = nil;
|
||||
MPKey *tryKey = nil;
|
||||
|
||||
// Method 1: When the user has no keyID set, set a new key from the given master password.
|
||||
if (!user.keyID) {
|
||||
if ([password length])
|
||||
if ((tryKey = keyForPassword(password, user.name))) {
|
||||
user.keyID = keyIDForKey(tryKey);
|
||||
if ((tryKey = [MPAlgorithmDefault keyForPassword:password ofUserNamed:user.name])) {
|
||||
user.keyID = tryKey.keyID;
|
||||
[[MPAppDelegate_Shared get] saveContext];
|
||||
}
|
||||
}
|
||||
@@ -106,7 +107,7 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
|
||||
if (!tryKey) {
|
||||
// Key should be saved in keychain. Load it.
|
||||
if ((tryKey = [self loadSavedKeyFor:user]))
|
||||
if (![user.keyID isEqual:keyIDForKey(tryKey)]) {
|
||||
if (![user.keyID isEqual:tryKey.keyID]) {
|
||||
// Loaded password doesn't match user's keyID. Forget saved password: it is incorrect.
|
||||
inf(@"Saved password doesn't match keyID for: %@", user.userID);
|
||||
|
||||
@@ -118,8 +119,8 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
|
||||
// Method 3: Check the given master password string.
|
||||
if (!tryKey) {
|
||||
if ([password length])
|
||||
if ((tryKey = keyForPassword(password, user.name)))
|
||||
if (![user.keyID isEqual:keyIDForKey(tryKey)]) {
|
||||
if ((tryKey = [MPAlgorithmDefault keyForPassword:password ofUserNamed:user.name]))
|
||||
if (![user.keyID isEqual:tryKey.keyID]) {
|
||||
inf(@"Key derived from password doesn't match keyID for: %@", user.userID);
|
||||
|
||||
tryKey = nil;
|
||||
@@ -128,18 +129,20 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
|
||||
|
||||
// No more methods left, fail if key still not known.
|
||||
if (!tryKey) {
|
||||
inf(@"Login failed for: %@", user.userID);
|
||||
|
||||
if (password) {
|
||||
inf(@"Login failed for: %@", user.userID);
|
||||
|
||||
#ifdef TESTFLIGHT_SDK_VERSION
|
||||
[TestFlight passCheckpoint:MPCheckpointSignInFailed];
|
||||
[TestFlight passCheckpoint:MPCheckpointSignInFailed];
|
||||
#endif
|
||||
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointSignInFailed attributes:nil];
|
||||
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointSignInFailed attributes:nil];
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
inf(@"Logged in: %@", user.userID);
|
||||
|
||||
if (![self.key isEqualToData:tryKey]) {
|
||||
if (![self.key isEqualToKey:tryKey]) {
|
||||
self.key = tryKey;
|
||||
[self storeSavedKeyFor:user];
|
||||
}
|
||||
@@ -157,6 +160,7 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
|
||||
|
||||
user.lastUsed = [NSDate date];
|
||||
self.activeUser = user;
|
||||
self.activeUser.requiresExplicitMigration = NO;
|
||||
[[MPAppDelegate_Shared get] saveContext];
|
||||
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:MPNotificationSignedIn object:self];
|
||||
@@ -169,9 +173,4 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (NSData *)keyWithLength:(NSUInteger)keyLength {
|
||||
|
||||
return [self.key subdataWithRange:NSMakeRange(0, MIN(keyLength, self.key.length))];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
#endif
|
||||
|
||||
@property (strong, nonatomic) MPUserEntity *activeUser;
|
||||
@property (strong, nonatomic) NSData *key;
|
||||
@property (strong, nonatomic) MPKey *key;
|
||||
|
||||
+ (MPAppDelegate_Shared *)get;
|
||||
|
||||
|
||||
@@ -20,9 +20,9 @@ typedef enum {
|
||||
|
||||
@interface MPAppDelegate_Shared (Store)<UbiquityStoreManagerDelegate>
|
||||
|
||||
+ (NSManagedObjectContext *)managedObjectContext;
|
||||
+ (NSManagedObjectContext *)managedObjectContextIfReady;
|
||||
+ (NSManagedObjectModel *)managedObjectModel;
|
||||
- (NSManagedObjectContext *)managedObjectContext;
|
||||
- (NSManagedObjectContext *)managedObjectContextIfReady;
|
||||
- (NSManagedObjectModel *)managedObjectModel;
|
||||
|
||||
- (UbiquityStoreManager *)storeManager;
|
||||
|
||||
@@ -13,9 +13,9 @@
|
||||
|
||||
#pragma mark - Core Data setup
|
||||
|
||||
+ (NSManagedObjectContext *)managedObjectContext {
|
||||
+ (NSManagedObjectContext *)managedObjectContextIfReady {
|
||||
|
||||
return [[self get] managedObjectContext];
|
||||
return [[self get] managedObjectContextIfReady];
|
||||
}
|
||||
|
||||
+ (NSManagedObjectModel *)managedObjectModel {
|
||||
@@ -33,7 +33,10 @@
|
||||
return managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
|
||||
}
|
||||
|
||||
- (NSManagedObjectContext *)managedObjectContext {
|
||||
- (NSManagedObjectContext *)managedObjectContextIfReady {
|
||||
|
||||
if (![self storeManager].isReady)
|
||||
return nil;
|
||||
|
||||
static NSManagedObjectContext *managedObjectContext = nil;
|
||||
if (managedObjectContext)
|
||||
@@ -41,25 +44,14 @@
|
||||
|
||||
managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
|
||||
[managedObjectContext performBlockAndWait:^{
|
||||
managedObjectContext.persistentStoreCoordinator = [self persistentStoreCoordinator];
|
||||
managedObjectContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy;
|
||||
managedObjectContext.persistentStoreCoordinator = [self storeManager].persistentStoreCoordinator;
|
||||
managedObjectContext.undoManager = [NSUndoManager new];
|
||||
}];
|
||||
|
||||
return managedObjectContext;
|
||||
}
|
||||
|
||||
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
|
||||
|
||||
// Start loading the store.
|
||||
[self storeManager];
|
||||
|
||||
// Wait until the storeManager is ready.
|
||||
while (![self storeManager].isReady)
|
||||
[NSThread sleepForTimeInterval:0.1];
|
||||
|
||||
return [self storeManager].persistentStoreCoordinator;
|
||||
}
|
||||
|
||||
- (UbiquityStoreManager *)storeManager {
|
||||
|
||||
static UbiquityStoreManager *storeManager = nil;
|
||||
@@ -67,11 +59,11 @@
|
||||
return storeManager;
|
||||
|
||||
storeManager = [[UbiquityStoreManager alloc] initWithManagedObjectModel:[self managedObjectModel]
|
||||
localStoreURL:[[self applicationFilesDirectory] URLByAppendingPathComponent:@"MasterPassword.sqlite"]
|
||||
containerIdentifier:@"HL3Q45LX9N.com.lyndir.lhunath.MasterPassword.shared"
|
||||
localStoreURL:[[self applicationFilesDirectory] URLByAppendingPathComponent:@"MasterPassword.sqlite"]
|
||||
containerIdentifier:@"HL3Q45LX9N.com.lyndir.lhunath.MasterPassword.shared"
|
||||
#if TARGET_OS_IPHONE
|
||||
additionalStoreOptions:[NSDictionary dictionaryWithObject:NSFileProtectionComplete
|
||||
forKey:NSPersistentStoreFileProtectionKey]
|
||||
additionalStoreOptions:[NSDictionary dictionaryWithObject:NSFileProtectionComplete
|
||||
forKey:NSPersistentStoreFileProtectionKey]
|
||||
#else
|
||||
additionalStoreOptions:nil
|
||||
#endif
|
||||
@@ -83,9 +75,9 @@
|
||||
#if TARGET_OS_IPHONE
|
||||
[[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillEnterForegroundNotification
|
||||
object:[UIApplication sharedApplication] queue:nil
|
||||
usingBlock:^(NSNotification *note) {
|
||||
[storeManager checkiCloudStatus];
|
||||
}];
|
||||
usingBlock:^(NSNotification *note) {
|
||||
[storeManager checkiCloudStatus];
|
||||
}];
|
||||
#else
|
||||
[[NSNotificationCenter defaultCenter] addObserverForName:NSApplicationWillBecomeActiveNotification
|
||||
object:[NSApplication sharedApplication] queue:nil
|
||||
@@ -96,9 +88,9 @@
|
||||
#if TARGET_OS_IPHONE
|
||||
[[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillTerminateNotification
|
||||
object:[UIApplication sharedApplication] queue:nil
|
||||
usingBlock:^(NSNotification *note) {
|
||||
[self saveContext];
|
||||
}];
|
||||
usingBlock:^(NSNotification *note) {
|
||||
[self saveContext];
|
||||
}];
|
||||
#else
|
||||
[[NSNotificationCenter defaultCenter] addObserverForName:NSApplicationWillTerminateNotification
|
||||
object:[NSApplication sharedApplication] queue:nil
|
||||
@@ -112,10 +104,10 @@
|
||||
|
||||
- (void)saveContext {
|
||||
|
||||
[self.managedObjectContext performBlock:^{
|
||||
[self.managedObjectContextIfReady performBlock:^{
|
||||
NSError *error = nil;
|
||||
if ([self.managedObjectContext hasChanges])
|
||||
if (![self.managedObjectContext save:&error])
|
||||
if ([self.managedObjectContextIfReady hasChanges])
|
||||
if (![self.managedObjectContextIfReady save:&error])
|
||||
err(@"While saving context: %@", error);
|
||||
}];
|
||||
}
|
||||
@@ -124,7 +116,7 @@
|
||||
|
||||
- (NSManagedObjectContext *)managedObjectContextForUbiquityStoreManager:(UbiquityStoreManager *)usm {
|
||||
|
||||
return self.managedObjectContext;
|
||||
return self.managedObjectContextIfReady;
|
||||
}
|
||||
|
||||
- (void)ubiquityStoreManager:(UbiquityStoreManager *)manager log:(NSString *)message {
|
||||
@@ -142,7 +134,8 @@
|
||||
[TestFlight passCheckpoint:iCloudEnabled? MPCheckpointCloudEnabled: MPCheckpointCloudDisabled];
|
||||
#endif
|
||||
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointCloud
|
||||
attributes:[NSDictionary dictionaryWithObject:iCloudEnabled? @"YES": @"NO" forKey:@"enabled"]];
|
||||
attributes:[NSDictionary dictionaryWithObject:iCloudEnabled? @"YES": @"NO"
|
||||
forKey:@"enabled"]];
|
||||
|
||||
[MPConfig get].iCloud = [NSNumber numberWithBool:iCloudEnabled];
|
||||
}
|
||||
@@ -168,13 +161,11 @@
|
||||
#ifdef TESTFLIGHT_SDK_VERSION
|
||||
[TestFlight passCheckpoint:MPCheckpointLocalStoreIncompatible];
|
||||
#endif
|
||||
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointLocalStoreIncompatible
|
||||
attributes:nil];
|
||||
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointLocalStoreIncompatible attributes:nil];
|
||||
manager.hardResetEnabled = YES;
|
||||
[manager hardResetLocalStorage];
|
||||
|
||||
[NSException raise:NSGenericException format:@"Local store was reset, application must be restarted to use it."];
|
||||
return;
|
||||
Throw(@"Local store was reset, application must be restarted to use it.");
|
||||
}
|
||||
case UbiquityStoreManagerErrorCauseOpenCloudStore: {
|
||||
wrn(@"iCloud store could not be opened, resetting it.");
|
||||
@@ -182,8 +173,7 @@
|
||||
#ifdef TESTFLIGHT_SDK_VERSION
|
||||
[TestFlight passCheckpoint:MPCheckpointCloudStoreIncompatible];
|
||||
#endif
|
||||
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointCloudStoreIncompatible
|
||||
attributes:nil];
|
||||
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointCloudStoreIncompatible attributes:nil];
|
||||
manager.hardResetEnabled = YES;
|
||||
[manager hardResetCloudStorage];
|
||||
break;
|
||||
@@ -199,27 +189,26 @@
|
||||
inf(@"Importing sites.");
|
||||
|
||||
static NSRegularExpression *headerPattern, *sitePattern;
|
||||
__autoreleasing NSError *error;
|
||||
__block NSError *error = nil;
|
||||
if (!headerPattern) {
|
||||
headerPattern = [[NSRegularExpression alloc]
|
||||
initWithPattern:@"^#[[:space:]]*([^:]+): (.*)"
|
||||
options:0 error:&error];
|
||||
headerPattern = [[NSRegularExpression alloc] initWithPattern:@"^#[[:space:]]*([^:]+): (.*)"
|
||||
options:0 error:&error];
|
||||
if (error)
|
||||
err(@"Error loading the header pattern: %@", error);
|
||||
}
|
||||
if (!sitePattern) {
|
||||
sitePattern = [[NSRegularExpression alloc]
|
||||
initWithPattern:@"^([^[:space:]]+)[[:space:]]+([[:digit:]]+)[[:space:]]+([[:digit:]]+)[[:space:]]+([^\t]+)\t(.*)"
|
||||
options:0 error:&error];
|
||||
sitePattern = [[NSRegularExpression alloc] initWithPattern:@"^([^[:space:]]+)[[:space:]]+([[:digit:]]+)[[:space:]]+([[:digit:]]+)(:[[:digit:]]+)?[[:space:]]+([^\t]+)\t(.*)"
|
||||
options:0 error:&error];
|
||||
if (error)
|
||||
err(@"Error loading the site pattern: %@", error);
|
||||
}
|
||||
if (!headerPattern || !sitePattern)
|
||||
return MPImportResultInternalError;
|
||||
|
||||
NSString *keyIDHex = nil, *userName = nil;
|
||||
MPUserEntity *user = nil;
|
||||
BOOL headerStarted = NO, headerEnded = NO;
|
||||
MPKey *key = nil;
|
||||
__block MPUserEntity *user = nil;
|
||||
NSString *bundleVersion = nil, *keyIDHex = nil, *userName = nil;
|
||||
BOOL headerStarted = NO, headerEnded = NO, clearText = NO;
|
||||
NSArray *importedSiteLines = [importedSitesString componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]];
|
||||
NSMutableSet *elementsToDelete = [NSMutableSet set];
|
||||
NSMutableArray *importedSiteElements = [NSMutableArray arrayWithCapacity:[importedSiteLines count]];
|
||||
@@ -246,18 +235,36 @@
|
||||
}
|
||||
NSTextCheckingResult *headerElements = [[headerPattern matchesInString:importedSiteLine options:0
|
||||
range:NSMakeRange(0, [importedSiteLine length])] lastObject];
|
||||
NSString *key = [importedSiteLine substringWithRange:[headerElements rangeAtIndex:1]];
|
||||
NSString *value = [importedSiteLine substringWithRange:[headerElements rangeAtIndex:2]];
|
||||
if ([key isEqualToString:@"User Name"]) {
|
||||
userName = value;
|
||||
NSString *headerName = [importedSiteLine substringWithRange:[headerElements rangeAtIndex:1]];
|
||||
NSString *headerValue = [importedSiteLine substringWithRange:[headerElements rangeAtIndex:2]];
|
||||
if ([headerName isEqualToString:@"User Name"]) {
|
||||
userName = headerValue;
|
||||
|
||||
NSFetchRequest *userFetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass([MPUserEntity class])];
|
||||
userFetchRequest.predicate = [NSPredicate predicateWithFormat:@"name == %@", userName];
|
||||
user = [[self.managedObjectContext executeFetchRequest:fetchRequest error:&error] lastObject];
|
||||
__block NSArray *users = nil;
|
||||
[self.managedObjectContextIfReady performBlockAndWait:^{
|
||||
users = [self.managedObjectContextIfReady executeFetchRequest:userFetchRequest error:&error];
|
||||
}];
|
||||
if (!users) {
|
||||
err(@"While looking for user: %@, error: %@", userName, error);
|
||||
return MPImportResultInternalError;
|
||||
}
|
||||
if ([users count] > 1) {
|
||||
err(@"While looking for user: %@, found more than one: %u", userName, [users count]);
|
||||
return MPImportResultInternalError;
|
||||
}
|
||||
|
||||
user = [users count]? [users lastObject]: nil;
|
||||
dbg(@"Found user: %@", [user debugDescription]);
|
||||
}
|
||||
if ([key isEqualToString:@"Key ID"]) {
|
||||
if (![(keyIDHex = value) isEqualToString:[keyIDForPassword(password, userName) encodeHex]])
|
||||
return MPImportResultInvalidPassword;
|
||||
if ([headerName isEqualToString:@"Key ID"])
|
||||
keyIDHex = headerValue;
|
||||
if ([headerName isEqualToString:@"Version"])
|
||||
bundleVersion = headerValue;
|
||||
if ([headerName isEqualToString:@"Passwords"]) {
|
||||
if ([headerValue isEqualToString:@"VISIBLE"])
|
||||
clearText = YES;
|
||||
}
|
||||
|
||||
continue;
|
||||
@@ -266,6 +273,11 @@
|
||||
continue;
|
||||
if (!keyIDHex || ![userName length])
|
||||
return MPImportResultMalformedInput;
|
||||
if (!key) {
|
||||
key = [MPAlgorithmDefaultForBundleVersion(bundleVersion) keyForPassword:password ofUserNamed:userName];
|
||||
if (![keyIDHex isEqualToString:[key.keyID encodeHex]])
|
||||
return MPImportResultInvalidPassword;
|
||||
}
|
||||
if (![importedSiteLine length])
|
||||
continue;
|
||||
|
||||
@@ -279,22 +291,26 @@
|
||||
NSString *lastUsed = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:1]];
|
||||
NSString *uses = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:2]];
|
||||
NSString *type = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:3]];
|
||||
NSString *name = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:4]];
|
||||
NSString *exportContent = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:5]];
|
||||
NSString *version = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:4]];
|
||||
NSString *name = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:5]];
|
||||
NSString *exportContent = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:6]];
|
||||
|
||||
// Find existing site.
|
||||
if (user) {
|
||||
fetchRequest.predicate = [NSPredicate predicateWithFormat:@"name == %@ AND user == %@", name, user];
|
||||
NSArray *existingSites = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
|
||||
if (error)
|
||||
err(@"Couldn't search existing sites: %@", error);
|
||||
__block NSArray *existingSites = nil;
|
||||
[self.managedObjectContextIfReady performBlockAndWait:^{
|
||||
existingSites = [self.managedObjectContextIfReady executeFetchRequest:fetchRequest error:&error];
|
||||
}];
|
||||
if (!existingSites) {
|
||||
err(@"Lookup of existing sites failed for site: %@, user: %@", name, user.userID);
|
||||
err(@"Lookup of existing sites failed for site: %@, user: %@, error: %@", name, user.userID, error);
|
||||
return MPImportResultInternalError;
|
||||
}
|
||||
} else
|
||||
if (existingSites.count)
|
||||
dbg(@"Existing sites: %@", existingSites);
|
||||
|
||||
[elementsToDelete addObjectsFromArray:existingSites];
|
||||
[importedSiteElements addObject:[NSArray arrayWithObjects:lastUsed, uses, type, name, exportContent, nil]];
|
||||
[importedSiteElements addObject:[NSArray arrayWithObjects:lastUsed, uses, type, version, name, exportContent, nil]];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -307,48 +323,74 @@
|
||||
return MPImportResultCancelled;
|
||||
}
|
||||
|
||||
// Delete existing sites.
|
||||
[elementsToDelete enumerateObjectsUsingBlock:^(id obj, BOOL *stop) {
|
||||
inf(@"Deleting site: %@, it will be replaced by an imported site.", [obj name]);
|
||||
[self.managedObjectContext deleteObject:obj];
|
||||
}];
|
||||
[self saveContext];
|
||||
BOOL success = NO;
|
||||
[self.managedObjectContextIfReady.undoManager beginUndoGrouping];
|
||||
@try {
|
||||
|
||||
// Import new sites.
|
||||
if (!user) {
|
||||
user = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass([MPUserEntity class])
|
||||
inManagedObjectContext:self.managedObjectContext];
|
||||
user.name = userName;
|
||||
user.keyID = [keyIDHex decodeHex];
|
||||
}
|
||||
for (NSArray *siteElements in importedSiteElements) {
|
||||
NSDate *lastUsed = [[NSDateFormatter rfc3339DateFormatter] dateFromString:[siteElements objectAtIndex:0]];
|
||||
NSUInteger uses = (unsigned)[[siteElements objectAtIndex:1] integerValue];
|
||||
MPElementType type = (MPElementType)[[siteElements objectAtIndex:2] integerValue];
|
||||
NSString *name = [siteElements objectAtIndex:3];
|
||||
NSString *exportContent = [siteElements objectAtIndex:4];
|
||||
// Delete existing sites.
|
||||
if (elementsToDelete.count)
|
||||
[self.managedObjectContextIfReady performBlockAndWait:^{
|
||||
[elementsToDelete enumerateObjectsUsingBlock:^(id obj, BOOL *stop) {
|
||||
inf(@"Deleting site: %@, it will be replaced by an imported site.", [obj name]);
|
||||
dbg(@"Deleted Element: %@", [obj debugDescription]);
|
||||
[self.managedObjectContextIfReady deleteObject:obj];
|
||||
}];
|
||||
}];
|
||||
|
||||
// Create new site.
|
||||
MPElementEntity *element = [NSEntityDescription insertNewObjectForEntityForName:ClassNameFromMPElementType(type)
|
||||
inManagedObjectContext:self.managedObjectContext];
|
||||
element.name = name;
|
||||
element.user = user;
|
||||
element.type = type;
|
||||
element.uses = uses;
|
||||
element.lastUsed = lastUsed;
|
||||
if ([exportContent length])
|
||||
[element importContent:exportContent];
|
||||
}
|
||||
[self saveContext];
|
||||
// Import new sites.
|
||||
if (!user) {
|
||||
[self.managedObjectContextIfReady performBlockAndWait:^{
|
||||
user = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass([MPUserEntity class])
|
||||
inManagedObjectContext:self.managedObjectContextIfReady];
|
||||
user.name = userName;
|
||||
user.keyID = [keyIDHex decodeHex];
|
||||
}];
|
||||
dbg(@"Created User: %@", [user debugDescription]);
|
||||
}
|
||||
for (NSArray *siteElements in importedSiteElements) {
|
||||
NSDate *lastUsed = [[NSDateFormatter rfc3339DateFormatter] dateFromString:[siteElements objectAtIndex:0]];
|
||||
NSUInteger uses = (unsigned)[[siteElements objectAtIndex:1] integerValue];
|
||||
MPElementType type = (MPElementType)[[siteElements objectAtIndex:2] integerValue];
|
||||
NSUInteger version = (unsigned)[[siteElements objectAtIndex:3] integerValue];
|
||||
NSString *name = [siteElements objectAtIndex:4];
|
||||
NSString *exportContent = [siteElements objectAtIndex:5];
|
||||
|
||||
inf(@"Import completed successfully.");
|
||||
// Create new site.
|
||||
[self.managedObjectContextIfReady performBlockAndWait:^{
|
||||
MPElementEntity *element = [NSEntityDescription insertNewObjectForEntityForName:[key.algorithm classNameOfType:type]
|
||||
inManagedObjectContext:self.managedObjectContextIfReady];
|
||||
element.name = name;
|
||||
element.user = user;
|
||||
element.type = type;
|
||||
element.uses = uses;
|
||||
element.lastUsed = lastUsed;
|
||||
element.version = version;
|
||||
if ([exportContent length]) {
|
||||
if (clearText)
|
||||
[element importClearTextContent:exportContent usingKey:key];
|
||||
else
|
||||
[element importProtectedContent:exportContent];
|
||||
}
|
||||
dbg(@"Created Element: %@", [element debugDescription]);
|
||||
}];
|
||||
}
|
||||
|
||||
[self saveContext];
|
||||
success = YES;
|
||||
inf(@"Import completed successfully.");
|
||||
#ifdef TESTFLIGHT_SDK_VERSION
|
||||
[TestFlight passCheckpoint:MPCheckpointSitesImported];
|
||||
[TestFlight passCheckpoint:MPCheckpointSitesImported];
|
||||
#endif
|
||||
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointSitesImported
|
||||
attributes:nil];
|
||||
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointSitesImported attributes:nil];
|
||||
|
||||
return MPImportResultSuccess;
|
||||
return MPImportResultSuccess;
|
||||
}
|
||||
@finally {
|
||||
[self.managedObjectContextIfReady.undoManager endUndoGrouping];
|
||||
|
||||
if (!success)
|
||||
[self.managedObjectContextIfReady.undoManager undoNestedGroup];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString *)exportSitesShowingPasswords:(BOOL)showPasswords {
|
||||
@@ -380,8 +422,9 @@
|
||||
// Sites.
|
||||
for (MPElementEntity *element in self.activeUser.elements) {
|
||||
NSDate *lastUsed = element.lastUsed;
|
||||
NSUInteger uses = element.uses;
|
||||
MPElementType type = element.type;
|
||||
NSUInteger uses = element.uses;
|
||||
MPElementType type = element.type;
|
||||
NSUInteger version = element.version;
|
||||
NSString *name = element.name;
|
||||
NSString *content = nil;
|
||||
|
||||
@@ -394,8 +437,9 @@
|
||||
content = element.exportContent;
|
||||
}
|
||||
|
||||
[export appendFormat:@"%@ %8d %8d %20s\t%@\n",
|
||||
[[NSDateFormatter rfc3339DateFormatter] stringFromDate:lastUsed], uses, type, [name cStringUsingEncoding:NSUTF8StringEncoding], content
|
||||
[export appendFormat:@"%@ %8d %8s %20s\t%@\n",
|
||||
[[NSDateFormatter rfc3339DateFormatter] stringFromDate:lastUsed], uses,
|
||||
[PearlString(@"%u:%u", type, version) UTF8String], [name UTF8String], content
|
||||
? content: @""];
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// MPElementEntity.h
|
||||
// MasterPassword-iOS
|
||||
//
|
||||
// Created by Maarten Billemont on 11/06/12.
|
||||
// Created by Maarten Billemont on 17/07/12.
|
||||
// Copyright (c) 2012 Lyndir. All rights reserved.
|
||||
//
|
||||
|
||||
@@ -16,8 +16,11 @@
|
||||
@property (nonatomic, retain) id content;
|
||||
@property (nonatomic, retain) NSDate * lastUsed;
|
||||
@property (nonatomic, retain) NSString * name;
|
||||
@property (nonatomic, retain) NSNumber * requiresExplicitMigration_;
|
||||
@property (nonatomic, retain) NSNumber * type_;
|
||||
@property (nonatomic, retain) NSString * userName;
|
||||
@property (nonatomic, retain) NSNumber * uses_;
|
||||
@property (nonatomic, retain) NSNumber * version_;
|
||||
@property (nonatomic, retain) MPUserEntity *user;
|
||||
|
||||
@end
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// MPElementEntity.m
|
||||
// MasterPassword-iOS
|
||||
//
|
||||
// Created by Maarten Billemont on 11/06/12.
|
||||
// Created by Maarten Billemont on 17/07/12.
|
||||
// Copyright (c) 2012 Lyndir. All rights reserved.
|
||||
//
|
||||
|
||||
@@ -15,8 +15,11 @@
|
||||
@dynamic content;
|
||||
@dynamic lastUsed;
|
||||
@dynamic name;
|
||||
@dynamic requiresExplicitMigration_;
|
||||
@dynamic type_;
|
||||
@dynamic userName;
|
||||
@dynamic uses_;
|
||||
@dynamic version_;
|
||||
@dynamic user;
|
||||
|
||||
@end
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// MPElementGeneratedEntity.h
|
||||
// MasterPassword-iOS
|
||||
//
|
||||
// Created by Maarten Billemont on 11/06/12.
|
||||
// Created by Maarten Billemont on 17/07/12.
|
||||
// Copyright (c) 2012 Lyndir. All rights reserved.
|
||||
//
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// MPElementGeneratedEntity.m
|
||||
// MasterPassword-iOS
|
||||
//
|
||||
// Created by Maarten Billemont on 11/06/12.
|
||||
// Created by Maarten Billemont on 17/07/12.
|
||||
// Copyright (c) 2012 Lyndir. All rights reserved.
|
||||
//
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// MPElementStoredEntity.h
|
||||
// MasterPassword-iOS
|
||||
//
|
||||
// Created by Maarten Billemont on 11/06/12.
|
||||
// Created by Maarten Billemont on 17/07/12.
|
||||
// Copyright (c) 2012 Lyndir. All rights reserved.
|
||||
//
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// MPElementStoredEntity.m
|
||||
// MasterPassword-iOS
|
||||
//
|
||||
// Created by Maarten Billemont on 11/06/12.
|
||||
// Created by Maarten Billemont on 17/07/12.
|
||||
// Copyright (c) 2012 Lyndir. All rights reserved.
|
||||
//
|
||||
|
||||
|
||||
@@ -11,17 +11,27 @@
|
||||
#import "MPElementStoredEntity.h"
|
||||
#import "MPElementGeneratedEntity.h"
|
||||
#import "MPUserEntity.h"
|
||||
#import "MPAlgorithm.h"
|
||||
|
||||
#define MPAvatarCount 19
|
||||
|
||||
@interface MPElementEntity (MP)
|
||||
|
||||
@property (assign) MPElementType type;
|
||||
@property (readonly) NSString *typeName;
|
||||
@property (readonly) NSString *typeShortName;
|
||||
@property (readonly) NSString *typeClassName;
|
||||
@property (readonly) Class typeClass;
|
||||
@property (assign) NSUInteger uses;
|
||||
@property (assign) NSUInteger version;
|
||||
@property (assign) BOOL requiresExplicitMigration;
|
||||
@property (readonly) id<MPAlgorithm> algorithm;
|
||||
|
||||
- (NSUInteger)use;
|
||||
- (NSString *)exportContent;
|
||||
- (void)importContent:(NSString *)content;
|
||||
- (void)importProtectedContent:(NSString *)protectedContent;
|
||||
- (void)importClearTextContent:(NSString *)clearContent usingKey:(MPKey *)key;
|
||||
- (BOOL)migrateExplicitly:(BOOL)explicit;
|
||||
|
||||
@end
|
||||
|
||||
@@ -36,6 +46,7 @@
|
||||
@property (assign) NSUInteger avatar;
|
||||
@property (assign) BOOL saveKey;
|
||||
@property (assign) MPElementType defaultType;
|
||||
@property (assign) BOOL requiresExplicitMigration;
|
||||
@property (readonly) NSString *userID;
|
||||
|
||||
+ (NSString *)idFor:(NSString *)userName;
|
||||
|
||||
@@ -8,8 +8,6 @@
|
||||
|
||||
#import "MPEntities.h"
|
||||
#import "MPAppDelegate.h"
|
||||
#import "MPAppDelegate_Key.h"
|
||||
#import "MPUserEntity.h"
|
||||
|
||||
@implementation MPElementEntity (MP)
|
||||
|
||||
@@ -23,6 +21,26 @@
|
||||
self.type_ = PearlUnsignedInteger(aType);
|
||||
}
|
||||
|
||||
- (NSString *)typeName {
|
||||
|
||||
return [self.algorithm nameOfType:self.type];
|
||||
}
|
||||
|
||||
- (NSString *)typeShortName {
|
||||
|
||||
return [self.algorithm shortNameOfType:self.type];
|
||||
}
|
||||
|
||||
- (NSString *)typeClassName {
|
||||
|
||||
return [self.algorithm classNameOfType:self.type];
|
||||
}
|
||||
|
||||
- (Class)typeClass {
|
||||
|
||||
return [self.algorithm classOfType:self.type];
|
||||
}
|
||||
|
||||
- (NSUInteger)uses {
|
||||
|
||||
return [self.uses_ unsignedIntegerValue];
|
||||
@@ -33,6 +51,30 @@
|
||||
self.uses_ = PearlUnsignedInteger(anUses);
|
||||
}
|
||||
|
||||
- (NSUInteger)version {
|
||||
|
||||
return [self.version_ unsignedIntegerValue];
|
||||
}
|
||||
|
||||
- (void)setVersion:(NSUInteger)version {
|
||||
|
||||
self.version_ = PearlUnsignedInteger(version);
|
||||
}
|
||||
|
||||
- (BOOL)requiresExplicitMigration {
|
||||
|
||||
return [self.requiresExplicitMigration_ boolValue];
|
||||
}
|
||||
|
||||
- (void)setRequiresExplicitMigration:(BOOL)requiresExplicitMigration {
|
||||
|
||||
self.requiresExplicitMigration_ = PearlBool(requiresExplicitMigration);
|
||||
}
|
||||
|
||||
- (id<MPAlgorithm>)algorithm {
|
||||
|
||||
return MPAlgorithmForVersion(self.version);
|
||||
}
|
||||
|
||||
- (NSUInteger)use {
|
||||
|
||||
@@ -50,7 +92,11 @@
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (void)importContent:(NSString *)content {
|
||||
- (void)importProtectedContent:(NSString *)content {
|
||||
|
||||
}
|
||||
|
||||
- (void)importClearTextContent:(NSString *)clearContent usingKey:(MPKey *)key {
|
||||
|
||||
}
|
||||
|
||||
@@ -61,8 +107,22 @@
|
||||
|
||||
- (NSString *)debugDescription {
|
||||
|
||||
return PearlString(@"{%@: name=%@, user=%@, type=%d, uses=%d, lastUsed=%@}",
|
||||
NSStringFromClass([self class]), self.name, self.user.name, self.type, self.uses, self.lastUsed);
|
||||
return PearlString(@"{%@: name=%@, user=%@, type=%d, uses=%d, lastUsed=%@, version=%d, userName=%@, requiresExplicitMigration=%d}",
|
||||
NSStringFromClass([self class]), self.name, self.user.name, self.type, self.uses, self.lastUsed, self.version,
|
||||
self.userName, self.requiresExplicitMigration);
|
||||
}
|
||||
|
||||
- (BOOL)migrateExplicitly:(BOOL)explicit {
|
||||
|
||||
while (self.version < MPAlgorithmDefaultVersion)
|
||||
if ([MPAlgorithmForVersion(self.version + 1) migrateElement:self explicit:explicit])
|
||||
inf(@"%@ migration to version: %d succeeded for element: %@", explicit? @"Explicit": @"Automatic", self.version + 1, self);
|
||||
else {
|
||||
wrn(@"%@ migration to version: %d failed for element: %@", explicit? @"Explicit": @"Automatic", self.version + 1, self);
|
||||
return NO;
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -81,6 +141,10 @@
|
||||
|
||||
- (id)content {
|
||||
|
||||
MPKey *key = [MPAppDelegate get].key;
|
||||
if (!key)
|
||||
return nil;
|
||||
|
||||
if (!(self.type & MPElementTypeClassGenerated)) {
|
||||
err(@"Corrupt element: %@, type: %d is not in MPElementTypeClassGenerated", self.name, self.type);
|
||||
return nil;
|
||||
@@ -89,7 +153,7 @@
|
||||
if (![self.name length])
|
||||
return nil;
|
||||
|
||||
return MPCalculateContent(self.type, self.name, [MPAppDelegate get].key, self.counter);
|
||||
return [self.algorithm generateContentForElement:self usingKey:key];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -103,12 +167,31 @@
|
||||
@"DevicePrivate", (__bridge id)kSecAttrService,
|
||||
name, (__bridge id)kSecAttrAccount,
|
||||
nil]
|
||||
matches:nil];
|
||||
matches:nil];
|
||||
}
|
||||
|
||||
- (id)content {
|
||||
|
||||
MPKey *key = [MPAppDelegate get].key;
|
||||
if (!key)
|
||||
return nil;
|
||||
|
||||
return [self contentUsingKey:key];
|
||||
}
|
||||
|
||||
- (void)setContent:(id)content {
|
||||
|
||||
MPKey *key = [MPAppDelegate get].key;
|
||||
if (!key)
|
||||
return;
|
||||
|
||||
[self setContent:content usingKey:key];
|
||||
}
|
||||
|
||||
- (id)contentUsingKey:(MPKey *)key {
|
||||
|
||||
assert(self.type & MPElementTypeClassStored);
|
||||
assert([key.keyID isEqualToData:self.user.keyID]);
|
||||
|
||||
NSData *encryptedContent;
|
||||
if (self.type & MPElementFeatureDevicePrivate)
|
||||
@@ -116,24 +199,29 @@
|
||||
else
|
||||
encryptedContent = self.contentObject;
|
||||
|
||||
NSData *decryptedContent = [encryptedContent decryptWithSymmetricKey:[[MPAppDelegate get] keyWithLength:PearlCryptKeySize]
|
||||
padding:YES];
|
||||
NSData *decryptedContent = nil;
|
||||
if ([encryptedContent length])
|
||||
decryptedContent = [encryptedContent decryptWithSymmetricKey:[key subKeyOfLength:PearlCryptKeySize].keyData padding:YES];
|
||||
|
||||
return [[NSString alloc] initWithBytes:decryptedContent.bytes length:decryptedContent.length encoding:NSUTF8StringEncoding];
|
||||
}
|
||||
|
||||
- (void)setContent:(id)content {
|
||||
- (void)setContent:(id)content usingKey:(MPKey *)key {
|
||||
|
||||
NSData *encryptedContent = [[content description] encryptWithSymmetricKey:[[MPAppDelegate get] keyWithLength:PearlCryptKeySize]
|
||||
padding:YES];
|
||||
assert(self.type & MPElementTypeClassStored);
|
||||
assert([key.keyID isEqualToData:self.user.keyID]);
|
||||
|
||||
NSData *encryptedContent = [[content description] encryptWithSymmetricKey:[key subKeyOfLength:PearlCryptKeySize].keyData padding:YES];
|
||||
|
||||
if (self.type & MPElementFeatureDevicePrivate) {
|
||||
[PearlKeyChain addOrUpdateItemForQuery:[MPElementStoredEntity queryForDevicePrivateElementNamed:self.name]
|
||||
withAttributes:[NSDictionary dictionaryWithObjectsAndKeys:
|
||||
encryptedContent, (__bridge id)kSecValueData,
|
||||
#if TARGET_OS_IPHONE
|
||||
kSecAttrAccessibleWhenUnlockedThisDeviceOnly, (__bridge id)kSecAttrAccessible,
|
||||
#endif
|
||||
nil]];
|
||||
withAttributes:[NSDictionary dictionaryWithObjectsAndKeys:
|
||||
encryptedContent, (__bridge id)kSecValueData,
|
||||
#if TARGET_OS_IPHONE
|
||||
(__bridge id)kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
|
||||
(__bridge id)kSecAttrAccessible,
|
||||
#endif
|
||||
nil]];
|
||||
self.contentObject = nil;
|
||||
} else
|
||||
self.contentObject = encryptedContent;
|
||||
@@ -144,9 +232,14 @@
|
||||
return [self.contentObject encodeBase64];
|
||||
}
|
||||
|
||||
- (void)importContent:(NSString *)content {
|
||||
- (void)importProtectedContent:(NSString *)protectedContent {
|
||||
|
||||
self.contentObject = [content decodeBase64];
|
||||
self.contentObject = [protectedContent decodeBase64];
|
||||
}
|
||||
|
||||
- (void)importClearTextContent:(NSString *)clearContent usingKey:(MPKey *)key {
|
||||
|
||||
[self setContent:clearContent usingKey:key];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -178,17 +271,26 @@
|
||||
return (MPElementType)[self.defaultType_ unsignedIntegerValue];
|
||||
}
|
||||
|
||||
- (NSString *)userID {
|
||||
|
||||
return [MPUserEntity idFor:self.name];
|
||||
}
|
||||
|
||||
|
||||
- (void)setDefaultType:(MPElementType)aDefaultType {
|
||||
|
||||
self.defaultType_ = PearlUnsignedInteger(aDefaultType);
|
||||
}
|
||||
|
||||
- (BOOL)requiresExplicitMigration {
|
||||
|
||||
return [self.requiresExplicitMigration_ boolValue];
|
||||
}
|
||||
|
||||
- (void)setRequiresExplicitMigration:(BOOL)requiresExplicitMigration {
|
||||
|
||||
self.requiresExplicitMigration_ = PearlBool(requiresExplicitMigration);
|
||||
}
|
||||
|
||||
- (NSString *)userID {
|
||||
|
||||
return [MPUserEntity idFor:self.name];
|
||||
}
|
||||
|
||||
+ (NSString *)idFor:(NSString *)userName {
|
||||
|
||||
return [[userName hashWith:PearlHashSHA1] encodeHex];
|
||||
|
||||
33
MasterPassword/MPKey.h
Normal file
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
|
||||
*
|
||||
* See the enclosed file LICENSE for license information (LGPLv3). If you did
|
||||
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
*
|
||||
* @author Maarten Billemont <lhunath@lyndir.com>
|
||||
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
*/
|
||||
|
||||
//
|
||||
// MPKey
|
||||
//
|
||||
// Created by Maarten Billemont on 16/07/12.
|
||||
// Copyright 2012 lhunath (Maarten Billemont). All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@protocol MPAlgorithm;
|
||||
|
||||
|
||||
@interface MPKey : NSObject
|
||||
|
||||
@property (nonatomic, readonly, strong) id<MPAlgorithm> algorithm;
|
||||
@property (nonatomic, readonly, strong) NSData *keyData;
|
||||
@property (nonatomic, readonly, strong) NSData *keyID;
|
||||
|
||||
- (id)initWithKeyData:(NSData *)keyData algorithm:(id<MPAlgorithm>)algorithm;
|
||||
- (MPKey *)subKeyOfLength:(NSUInteger)subKeyLength;
|
||||
- (BOOL)isEqualToKey:(MPKey *)key;
|
||||
|
||||
@end
|
||||
66
MasterPassword/MPKey.m
Normal file
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
|
||||
*
|
||||
* See the enclosed file LICENSE for license information (LGPLv3). If you did
|
||||
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
*
|
||||
* @author Maarten Billemont <lhunath@lyndir.com>
|
||||
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
*/
|
||||
|
||||
//
|
||||
// MPKey
|
||||
//
|
||||
// Created by Maarten Billemont on 16/07/12.
|
||||
// Copyright 2012 lhunath (Maarten Billemont). All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPKey.h"
|
||||
#import "MPAlgorithm.h"
|
||||
|
||||
|
||||
@interface MPKey ()
|
||||
|
||||
@property (nonatomic, readwrite, strong) id<MPAlgorithm> algorithm;
|
||||
@property (nonatomic, readwrite, strong) NSData *keyData;
|
||||
@property (nonatomic, readwrite, strong) NSData *keyID;
|
||||
|
||||
@end
|
||||
|
||||
@implementation MPKey
|
||||
@synthesize algorithm = _algorithm, keyData = _keyData, keyID = _keyID;
|
||||
|
||||
- (id)initWithKeyData:(NSData *)keyData algorithm:(id<MPAlgorithm>)algorithm {
|
||||
|
||||
if (!(self = [super init]))
|
||||
return nil;
|
||||
|
||||
self.keyData = keyData;
|
||||
self.algorithm = algorithm;
|
||||
self.keyID = [self.algorithm keyIDForKeyData:keyData];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (MPKey *)subKeyOfLength:(NSUInteger)subKeyLength {
|
||||
|
||||
NSData *subKeyData = [self.keyData subdataWithRange:NSMakeRange(0, MIN(subKeyLength, self.keyData.length))];
|
||||
|
||||
return [self.algorithm keyFromKeyData:subKeyData];
|
||||
}
|
||||
|
||||
- (BOOL)isEqualToKey:(MPKey *)key {
|
||||
|
||||
return [self.keyID isEqualToData:key.keyID];
|
||||
}
|
||||
|
||||
- (BOOL)isEqual:(id)object {
|
||||
|
||||
if (![object isKindOfClass:[MPKey class]])
|
||||
return NO;
|
||||
|
||||
return [self isEqualToKey:object];
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
@@ -6,9 +6,7 @@
|
||||
// Copyright (c) 2012 Lyndir. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#define MPPersistentStoreDidChangeNotification @"MPPersistentStoreDidChange"
|
||||
#import "MPKey.h"
|
||||
|
||||
typedef enum {
|
||||
MPElementContentTypePassword,
|
||||
@@ -45,10 +43,13 @@ typedef enum {
|
||||
#define MPCheckpointAction @"MPCheckpointAction"
|
||||
#define MPCheckpointHelpChapter @"MPCheckpointHelpChapter"
|
||||
#define MPCheckpointCopyToPasteboard @"MPCheckpointCopyToPasteboard"
|
||||
#define MPCheckpointCopyUserNameToPasteboard @"MPCheckpointCopyUserNameToPasteboard"
|
||||
#define MPCheckpointResetPasswordCounter @"MPCheckpointResetPasswordCounter"
|
||||
#define MPCheckpointIncrementPasswordCounter @"MPCheckpointIncrementPasswordCounter"
|
||||
#define MPCheckpointEditPassword @"MPCheckpointEditPassword"
|
||||
#define MPCheckpointEditUserName @"MPCheckpointEditUserName"
|
||||
#define MPCheckpointCloseAlert @"MPCheckpointCloseAlert"
|
||||
#define MPCheckpointCloseOutdatedAlert @"MPCheckpointCloseOutdatedAlert"
|
||||
#define MPCheckpointUseType @"MPCheckpointUseType"
|
||||
#define MPCheckpointDeleteElement @"MPCheckpointDeleteElement"
|
||||
#define MPCheckpointCancelSearch @"MPCheckpointCancelSearch"
|
||||
@@ -70,18 +71,9 @@ typedef enum {
|
||||
#define MPCheckpointCloudDisabled @"MPCheckpointCloudDisabled"
|
||||
#define MPCheckpointSitesImported @"MPCheckpointSitesImported"
|
||||
#define MPCheckpointSitesExported @"MPCheckpointSitesExported"
|
||||
#define MPCheckpointExplicitMigration @"MPCheckpointExplicitMigration"
|
||||
|
||||
#define MPNotificationStoreUpdated @"MPNotificationStoreUpdated"
|
||||
#define MPNotificationSignedIn @"MPNotificationKeySet"
|
||||
#define MPNotificationSignedOut @"MPNotificationKeyUnset"
|
||||
#define MPNotificationKeyForgotten @"MPNotificationKeyForgotten"
|
||||
#define MPNotificationElementUsed @"MPNotificationElementUsed"
|
||||
|
||||
NSData *keyForPassword(NSString *password, NSString *username);
|
||||
NSData *keyIDForPassword(NSString *password, NSString *username);
|
||||
NSData *keyIDForKey(NSData *key);
|
||||
NSString *NSStringFromMPElementType(MPElementType type);
|
||||
NSString *NSStringShortFromMPElementType(MPElementType type);
|
||||
NSString *ClassNameFromMPElementType(MPElementType type);
|
||||
Class ClassFromMPElementType(MPElementType type);
|
||||
NSString *MPCalculateContent(MPElementType type, NSString *name, NSData *key, uint32_t counter);
|
||||
#define MPNotificationElementUpdated @"MPNotificationElementUpdated"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// MPUserEntity.h
|
||||
// MasterPassword-iOS
|
||||
//
|
||||
// Created by Maarten Billemont on 11/06/12.
|
||||
// Created by Maarten Billemont on 17/07/12.
|
||||
// Copyright (c) 2012 Lyndir. All rights reserved.
|
||||
//
|
||||
|
||||
@@ -14,11 +14,12 @@
|
||||
@interface MPUserEntity : NSManagedObject
|
||||
|
||||
@property (nonatomic, retain) NSNumber * avatar_;
|
||||
@property (nonatomic, retain) NSNumber * defaultType_;
|
||||
@property (nonatomic, retain) NSData * keyID;
|
||||
@property (nonatomic, retain) NSDate * lastUsed;
|
||||
@property (nonatomic, retain) NSString * name;
|
||||
@property (nonatomic, retain) NSNumber * saveKey_;
|
||||
@property (nonatomic, retain) NSNumber * defaultType_;
|
||||
@property (nonatomic, retain) NSNumber * requiresExplicitMigration_;
|
||||
@property (nonatomic, retain) NSSet *elements;
|
||||
@end
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// MPUserEntity.m
|
||||
// MasterPassword-iOS
|
||||
//
|
||||
// Created by Maarten Billemont on 11/06/12.
|
||||
// Created by Maarten Billemont on 17/07/12.
|
||||
// Copyright (c) 2012 Lyndir. All rights reserved.
|
||||
//
|
||||
|
||||
@@ -13,11 +13,12 @@
|
||||
@implementation MPUserEntity
|
||||
|
||||
@dynamic avatar_;
|
||||
@dynamic defaultType_;
|
||||
@dynamic keyID;
|
||||
@dynamic lastUsed;
|
||||
@dynamic name;
|
||||
@dynamic saveKey_;
|
||||
@dynamic defaultType_;
|
||||
@dynamic requiresExplicitMigration_;
|
||||
@dynamic elements;
|
||||
|
||||
@end
|
||||
|
||||
@@ -27,8 +27,10 @@
|
||||
@synthesize key;
|
||||
@synthesize keyID;
|
||||
|
||||
#pragma GCC diagnostic ignored "-Wfour-char-constants"
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wfour-char-constants"
|
||||
static EventHotKeyID MPShowHotKey = {.signature = 'show', .id = 1};
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
+ (void)initialize {
|
||||
|
||||
|
||||
@@ -3,6 +3,6 @@
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>_XCCurrentVersionName</key>
|
||||
<string>MasterPassword.xcdatamodel</string>
|
||||
<string>MasterPassword 2.xcdatamodel</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<model name="" userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="1487" systemVersion="12A269" minimumToolsVersion="Automatic" macOSVersion="Automatic" iOSVersion="Automatic">
|
||||
<entity name="MPElementEntity" representedClassName="MPElementEntity" isAbstract="YES" syncable="YES">
|
||||
<attribute name="content" optional="YES" transient="YES" attributeType="Transformable" syncable="YES"/>
|
||||
<attribute name="lastUsed" attributeType="Date" indexed="YES" syncable="YES"/>
|
||||
<attribute name="name" attributeType="String" minValueString="1" indexed="YES" syncable="YES"/>
|
||||
<attribute name="requiresExplicitMigration_" attributeType="Boolean" defaultValueString="NO">
|
||||
<userInfo/>
|
||||
</attribute>
|
||||
<attribute name="type_" attributeType="Integer 16" defaultValueString="17" syncable="YES"/>
|
||||
<attribute name="userName" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="uses_" attributeType="Integer 16" defaultValueString="0" indexed="YES" syncable="YES"/>
|
||||
<attribute name="version_" attributeType="Integer 16" minValueString="0" defaultValueString="0" syncable="YES"/>
|
||||
<relationship name="user" minCount="1" maxCount="1" deletionRule="Nullify" destinationEntity="MPUserEntity" inverseName="elements" inverseEntity="MPUserEntity" syncable="YES"/>
|
||||
</entity>
|
||||
<entity name="MPElementGeneratedEntity" representedClassName="MPElementGeneratedEntity" parentEntity="MPElementEntity" syncable="YES">
|
||||
<attribute name="counter_" optional="YES" attributeType="Integer 32" defaultValueString="1" syncable="YES"/>
|
||||
</entity>
|
||||
<entity name="MPElementStoredEntity" representedClassName="MPElementStoredEntity" parentEntity="MPElementEntity" syncable="YES">
|
||||
<attribute name="contentObject" optional="YES" attributeType="Transformable" storedInTruthFile="YES" syncable="YES"/>
|
||||
</entity>
|
||||
<entity name="MPUserEntity" representedClassName="MPUserEntity" syncable="YES">
|
||||
<attribute name="avatar_" attributeType="Integer 16" defaultValueString="0" syncable="YES"/>
|
||||
<attribute name="defaultType_" attributeType="Integer 16" defaultValueString="17" syncable="YES"/>
|
||||
<attribute name="keyID" optional="YES" attributeType="Binary" syncable="YES"/>
|
||||
<attribute name="lastUsed" optional="YES" attributeType="Date" syncable="YES"/>
|
||||
<attribute name="name" attributeType="String" syncable="YES"/>
|
||||
<attribute name="requiresExplicitMigration_" transient="YES" attributeType="Boolean" defaultValueString="NO" syncable="YES"/>
|
||||
<attribute name="saveKey_" attributeType="Boolean" defaultValueString="NO">
|
||||
<userInfo/>
|
||||
</attribute>
|
||||
<relationship name="elements" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="MPElementEntity" inverseName="user" inverseEntity="MPElementEntity" syncable="YES"/>
|
||||
</entity>
|
||||
<elements>
|
||||
<element name="MPElementEntity" positionX="160" positionY="192" width="128" height="180"/>
|
||||
<element name="MPElementGeneratedEntity" positionX="160" positionY="192" width="128" height="60"/>
|
||||
<element name="MPElementStoredEntity" positionX="160" positionY="192" width="128" height="60"/>
|
||||
<element name="MPUserEntity" positionX="160" positionY="192" width="128" height="150"/>
|
||||
</elements>
|
||||
</model>
|
||||
@@ -18,6 +18,6 @@
|
||||
- (void)showGuide;
|
||||
|
||||
- (void)export;
|
||||
- (void)changeMasterPasswordFor:(MPUserEntity *)user;
|
||||
- (void)changeMasterPasswordFor:(MPUserEntity *)user didResetBlock:(void(^)(void))didReset;
|
||||
|
||||
@end
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
// Copyright (c) 2011 Lyndir. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Crashlytics/Crashlytics.h>
|
||||
#import "MPAppDelegate.h"
|
||||
#import "MPAppDelegate_Key.h"
|
||||
#import "MPAppDelegate_Store.h"
|
||||
@@ -35,7 +34,7 @@
|
||||
[MPiOSConfig get];
|
||||
|
||||
#ifdef DEBUG
|
||||
[PearlLogger get].autoprintLevel = PearlLogLevelDebug;
|
||||
[PearlLogger get].printLevel = PearlLogLevelDebug;
|
||||
//[NSClassFromString(@"WebView") performSelector:NSSelectorFromString(@"_enableRemoteInspector")];
|
||||
#endif
|
||||
}
|
||||
@@ -50,16 +49,14 @@
|
||||
[[[NSBundle mainBundle] mutableInfoDictionary] setObject:@"Master Password" forKey:@"CFBundleDisplayName"];
|
||||
[[[NSBundle mainBundle] mutableLocalizedInfoDictionary] setObject:@"Master Password" forKey:@"CFBundleDisplayName"];
|
||||
|
||||
#ifdef ADHOC
|
||||
@try {
|
||||
NSString *testFlightToken = [self testFlightToken];
|
||||
if ([testFlightToken length]) {
|
||||
inf(@"Initializing TestFlight");
|
||||
[TestFlight addCustomEnvironmentInformation:@"Anonymous" forKey:@"username"];
|
||||
#ifdef ADHOC
|
||||
[TestFlight setDeviceIdentifier:[(id)[UIDevice currentDevice] uniqueIdentifier]];
|
||||
#else
|
||||
[TestFlight setDeviceIdentifier:[PearlKeyChain deviceIdentifier]];
|
||||
#endif
|
||||
// [TestFlight setDeviceIdentifier:[PearlKeyChain deviceIdentifier]];
|
||||
[TestFlight setOptions:[NSDictionary dictionaryWithObjectsAndKeys:
|
||||
[NSNumber numberWithBool:NO], @"logToConsole",
|
||||
[NSNumber numberWithBool:NO], @"logToSTDERR",
|
||||
@@ -75,11 +72,14 @@
|
||||
|
||||
return YES;
|
||||
}];
|
||||
TFLog(@"TestFlight (%@) initialized for: %@ v%@.", //
|
||||
TESTFLIGHT_SDK_VERSION, [PearlInfoPlist get].CFBundleName, [PearlInfoPlist get].CFBundleVersion);
|
||||
}
|
||||
}
|
||||
@catch (id exception) {
|
||||
err(@"TestFlight: %@", exception);
|
||||
}
|
||||
#endif
|
||||
@try {
|
||||
NSString *crashlyticsAPIKey = [self crashlyticsAPIKey];
|
||||
if ([crashlyticsAPIKey length]) {
|
||||
@@ -100,6 +100,8 @@
|
||||
|
||||
return YES;
|
||||
}];
|
||||
CLSLog(@"Crashlytics (%@) initialized for: %@ v%@.", //
|
||||
[Crashlytics sharedInstance].version, [PearlInfoPlist get].CFBundleName, [PearlInfoPlist get].CFBundleVersion);
|
||||
}
|
||||
}
|
||||
@catch (id exception) {
|
||||
@@ -112,13 +114,13 @@
|
||||
[[LocalyticsSession sharedLocalyticsSession] startSession:localyticsKey];
|
||||
[[PearlLogger get] registerListener:^BOOL(PearlLogMessage *message) {
|
||||
if (message.level >= PearlLogLevelWarn)
|
||||
[[LocalyticsSession sharedLocalyticsSession] tagEvent:@"Problem" attributes:
|
||||
[NSDictionary dictionaryWithObjectsAndKeys:
|
||||
[message levelDescription],
|
||||
@"level",
|
||||
message.message,
|
||||
@"message",
|
||||
nil]];
|
||||
[[LocalyticsSession sharedLocalyticsSession] tagEvent:@"Problem"
|
||||
attributes:[NSDictionary
|
||||
dictionaryWithObjectsAndKeys:
|
||||
[NSString stringWithCString:PearlLogLevelStr(message.level)
|
||||
encoding:NSASCIIStringEncoding], @"level",
|
||||
message.message, @"message",
|
||||
nil]];
|
||||
|
||||
return YES;
|
||||
}];
|
||||
@@ -152,7 +154,7 @@
|
||||
[NSValue valueWithUIOffset:UIOffsetMake(0, 1)], UITextAttributeTextShadowOffset,
|
||||
[UIFont fontWithName:@"Helvetica-Neue" size:0.0f], UITextAttributeFont,
|
||||
nil]
|
||||
forState:UIControlStateNormal];
|
||||
forState:UIControlStateNormal];
|
||||
|
||||
UIImage *toolBarImage = [[UIImage imageNamed:@"ui_toolbar_container"] resizableImageWithCapInsets:UIEdgeInsetsMake(25, 5, 5, 5)];
|
||||
[[UISearchBar appearance] setBackgroundImage:toolBarImage];
|
||||
@@ -183,7 +185,11 @@
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserverForName:MPNotificationSignedOut object:nil queue:nil
|
||||
usingBlock:^(NSNotification *note) {
|
||||
[self.navigationController performSegueWithIdentifier:@"MP_Unlock" sender:nil];
|
||||
if ([[note.userInfo objectForKey:@"animated"] boolValue])
|
||||
[self.navigationController performSegueWithIdentifier:@"MP_Unlock" sender:nil];
|
||||
else
|
||||
[self.navigationController presentViewController:[self.navigationController.storyboard instantiateViewControllerWithIdentifier:@"MPUnlockViewController"]
|
||||
animated:NO completion:nil];
|
||||
}];
|
||||
[[NSNotificationCenter defaultCenter] addObserverForName:kIASKAppSettingChanged object:nil queue:nil
|
||||
usingBlock:^(NSNotification *note) {
|
||||
@@ -202,8 +208,6 @@
|
||||
cancelTitle:nil otherTitles:[PearlStrings get].commonButtonOkay, nil];
|
||||
#endif
|
||||
|
||||
[[UIApplication sharedApplication] setStatusBarHidden:NO withAnimation:UIStatusBarAnimationSlide];
|
||||
|
||||
[super application:application didFinishLaunchingWithOptions:launchOptions];
|
||||
|
||||
inf(@"Started up with device identifier: %@", [PearlKeyChain deviceIdentifier]);
|
||||
@@ -219,7 +223,7 @@
|
||||
__autoreleasing NSError *error;
|
||||
__autoreleasing NSURLResponse *response;
|
||||
NSData *importedSitesData = [NSURLConnection sendSynchronousRequest:[NSURLRequest requestWithURL:url]
|
||||
returningResponse:&response error:&error];
|
||||
returningResponse:&response error:&error];
|
||||
if (error)
|
||||
err(@"While reading imported sites from %@: %@", url, error);
|
||||
if (!importedSitesData)
|
||||
@@ -232,33 +236,33 @@
|
||||
^(UIAlertView *alert, NSInteger buttonIndex) {
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
MPImportResult result = [self importSites:importedSitesString withPassword:[alert textFieldAtIndex:0].text
|
||||
askConfirmation:^BOOL(NSUInteger importCount, NSUInteger deleteCount) {
|
||||
__block BOOL confirmation = NO;
|
||||
askConfirmation:^BOOL(NSUInteger importCount, NSUInteger deleteCount) {
|
||||
__block BOOL confirmation = NO;
|
||||
|
||||
dispatch_group_t confirmationGroup = dispatch_group_create();
|
||||
dispatch_group_enter(confirmationGroup);
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[PearlAlert showAlertWithTitle:@"Import Sites?"
|
||||
message:PearlString(
|
||||
@"Import %d sites, overwriting %d existing sites?",
|
||||
importCount, deleteCount)
|
||||
viewStyle:UIAlertViewStyleDefault
|
||||
initAlert:nil
|
||||
tappedButtonBlock:^(UIAlertView *alert_, NSInteger buttonIndex_) {
|
||||
if (buttonIndex_
|
||||
!= [alert_ cancelButtonIndex])
|
||||
confirmation = YES;
|
||||
dispatch_group_t confirmationGroup = dispatch_group_create();
|
||||
dispatch_group_enter(confirmationGroup);
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[PearlAlert showAlertWithTitle:@"Import Sites?"
|
||||
message:PearlString(
|
||||
@"Import %d sites, overwriting %d existing sites?",
|
||||
importCount, deleteCount)
|
||||
viewStyle:UIAlertViewStyleDefault
|
||||
initAlert:nil
|
||||
tappedButtonBlock:^(UIAlertView *alert_, NSInteger buttonIndex_) {
|
||||
if (buttonIndex_
|
||||
!= [alert_ cancelButtonIndex])
|
||||
confirmation = YES;
|
||||
|
||||
dispatch_group_leave(confirmationGroup);
|
||||
}
|
||||
cancelTitle:[PearlStrings get].commonButtonCancel
|
||||
otherTitles:@"Import", nil];
|
||||
});
|
||||
dispatch_group_wait(
|
||||
confirmationGroup, DISPATCH_TIME_FOREVER);
|
||||
dispatch_group_leave(confirmationGroup);
|
||||
}
|
||||
cancelTitle:[PearlStrings get].commonButtonCancel
|
||||
otherTitles:@"Import", nil];
|
||||
});
|
||||
dispatch_group_wait(
|
||||
confirmationGroup, DISPATCH_TIME_FOREVER);
|
||||
|
||||
return confirmation;
|
||||
}];
|
||||
return confirmation;
|
||||
}];
|
||||
|
||||
switch (result) {
|
||||
case MPImportResultSuccess:
|
||||
@@ -276,11 +280,18 @@
|
||||
}
|
||||
});
|
||||
}
|
||||
cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:@"Unlock File", nil];
|
||||
cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:@"Unlock File", nil];
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application {
|
||||
|
||||
wrn(@"Received memory warning.");
|
||||
|
||||
[super applicationDidReceiveMemoryWarning:application];
|
||||
}
|
||||
|
||||
- (void)applicationDidBecomeActive:(UIApplication *)application {
|
||||
|
||||
inf(@"Re-activated");
|
||||
@@ -328,7 +339,7 @@
|
||||
[self saveContext];
|
||||
|
||||
if (![[MPiOSConfig get].rememberLogin boolValue])
|
||||
[self signOut];
|
||||
[self signOutAnimated:NO];
|
||||
|
||||
[TestFlight passCheckpoint:MPCheckpointDeactivated];
|
||||
}
|
||||
@@ -340,8 +351,8 @@
|
||||
if ([[MPConfig get].iCloud boolValue] != [self.storeManager iCloudEnabled])
|
||||
[self.storeManager useiCloudStore:[[MPConfig get].iCloud boolValue] alertUser:YES];
|
||||
if ([[MPiOSConfig get].sendInfo boolValue]) {
|
||||
if ([PearlLogger get].autoprintLevel > PearlLogLevelInfo)
|
||||
[PearlLogger get].autoprintLevel = PearlLogLevelInfo;
|
||||
if ([PearlLogger get].printLevel > PearlLogLevelInfo)
|
||||
[PearlLogger get].printLevel = PearlLogLevelInfo;
|
||||
|
||||
[[Crashlytics sharedInstance] setBoolValue:[[MPConfig get].rememberLogin boolValue] forKey:@"rememberLogin"];
|
||||
[[Crashlytics sharedInstance] setBoolValue:[[MPConfig get].iCloud boolValue] forKey:@"iCloud"];
|
||||
@@ -384,11 +395,14 @@
|
||||
? @"YES": @"NO", @"showQuickStart",
|
||||
[[PearlConfig get].firstRun boolValue]
|
||||
? @"YES": @"NO", @"firstRun",
|
||||
[[PearlConfig get].launchCount description], @"launchCount",
|
||||
[[PearlConfig get].launchCount description],
|
||||
@"launchCount",
|
||||
[[PearlConfig get].askForReviews boolValue]
|
||||
? @"YES": @"NO", @"askForReviews",
|
||||
[[PearlConfig get].reviewAfterLaunches description], @"reviewAfterLaunches",
|
||||
[PearlConfig get].reviewedVersion, @"reviewedVersion",
|
||||
[[PearlConfig get].reviewAfterLaunches description],
|
||||
@"reviewAfterLaunches",
|
||||
[PearlConfig get].reviewedVersion,
|
||||
@"reviewedVersion",
|
||||
nil]];
|
||||
}
|
||||
}
|
||||
@@ -419,28 +433,48 @@
|
||||
// Safe Export
|
||||
[self exportShowPasswords:NO];
|
||||
if (buttonIndex_ == [alert_ firstOtherButtonIndex] + 1)
|
||||
// Safe Export
|
||||
// Show Passwords
|
||||
[self exportShowPasswords:YES];
|
||||
} cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:@"Safe Export", @"Show Passwords", nil];
|
||||
} otherTitles:nil];
|
||||
} cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:@"Safe Export", @"Show Passwords", nil];
|
||||
} otherTitles:nil];
|
||||
}
|
||||
|
||||
- (void)exportShowPasswords:(BOOL)showPasswords {
|
||||
|
||||
if (![MFMailComposeViewController canSendMail]) {
|
||||
[PearlAlert showAlertWithTitle:@"Cannot Send Mail"
|
||||
message:
|
||||
@"Your device is not yet set up for sending mail.\n"
|
||||
@"Close Master Password, go into Settings and add a Mail account."
|
||||
viewStyle:UIAlertViewStyleDefault
|
||||
initAlert:nil tappedButtonBlock:nil cancelTitle:[PearlStrings get].commonButtonOkay
|
||||
otherTitles:nil];
|
||||
return;
|
||||
}
|
||||
|
||||
NSString *exportedSites = [self exportSitesShowingPasswords:showPasswords];
|
||||
NSString *message;
|
||||
|
||||
if (showPasswords)
|
||||
message = PearlString(
|
||||
@"Export of %@'s Master Password sites with passwords visible.\n"
|
||||
@"REMINDER: Make sure nobody else sees this file! All passwords are visible!\n",
|
||||
self.activeUser.name);
|
||||
message = PearlString(@"Export of Master Password sites with passwords included.\n"
|
||||
@"REMINDER: Make sure nobody else sees this file! Passwords are visible!\n\n\n"
|
||||
@"--\n"
|
||||
@"%@\n"
|
||||
@"Master Password %@, build %@",
|
||||
self.activeUser.name,
|
||||
[PearlInfoPlist get].CFBundleShortVersionString,
|
||||
[PearlInfoPlist get].CFBundleVersion);
|
||||
else
|
||||
message = PearlString(
|
||||
@"Backup of %@'s Master Password sites.\n",
|
||||
self.activeUser.name);
|
||||
message = PearlString(@"Backup of Master Password sites.\n\n\n"
|
||||
@"--\n"
|
||||
@"%@\n"
|
||||
@"Master Password %@, build %@",
|
||||
self.activeUser.name,
|
||||
[PearlInfoPlist get].CFBundleShortVersionString,
|
||||
[PearlInfoPlist get].CFBundleVersion);
|
||||
|
||||
NSDateFormatter *exportDateFormatter = [NSDateFormatter new];
|
||||
[exportDateFormatter setDateFormat:@"yyyy'-'MM'-'DD"];
|
||||
[exportDateFormatter setDateFormat:@"yyyy'-'MM'-'dd"];
|
||||
|
||||
MFMailComposeViewController *composer = [MFMailComposeViewController new];
|
||||
[composer setMailComposeDelegate:self];
|
||||
@@ -448,13 +482,13 @@
|
||||
[composer setMessageBody:message isHTML:NO];
|
||||
[composer addAttachmentData:
|
||||
[exportedSites dataUsingEncoding:NSUTF8StringEncoding] mimeType:@"text/plain"
|
||||
fileName:PearlString(@"%@ (%@).mpsites",
|
||||
self.activeUser.name,
|
||||
[exportDateFormatter stringFromDate:[NSDate date]])];
|
||||
fileName:PearlString(@"%@ (%@).mpsites",
|
||||
self.activeUser.name,
|
||||
[exportDateFormatter stringFromDate:[NSDate date]])];
|
||||
[self.window.rootViewController presentModalViewController:composer animated:YES];
|
||||
}
|
||||
|
||||
- (void)changeMasterPasswordFor:(MPUserEntity *)user {
|
||||
- (void)changeMasterPasswordFor:(MPUserEntity *)user didResetBlock:(void (^)(void))didReset {
|
||||
|
||||
[PearlAlert showAlertWithTitle:@"Changing Master Password"
|
||||
message:
|
||||
@@ -469,14 +503,16 @@
|
||||
inf(@"Unsetting master password for: %@.", user.userID);
|
||||
user.keyID = nil;
|
||||
[self forgetSavedKeyFor:user];
|
||||
[self signOut];
|
||||
[self signOutAnimated:YES];
|
||||
|
||||
if (didReset)
|
||||
didReset();
|
||||
|
||||
[TestFlight passCheckpoint:MPCheckpointChangeMP];
|
||||
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointChangeMP
|
||||
attributes:nil];
|
||||
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointChangeMP attributes:nil];
|
||||
}
|
||||
cancelTitle:[PearlStrings get].commonButtonAbort
|
||||
otherTitles:[PearlStrings get].commonButtonContinue, nil];
|
||||
cancelTitle:[PearlStrings get].commonButtonAbort
|
||||
otherTitles:[PearlStrings get].commonButtonContinue, nil];
|
||||
}
|
||||
|
||||
#pragma mark - PearlConfigDelegate
|
||||
@@ -504,7 +540,7 @@
|
||||
tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
|
||||
if (buttonIndex == [alert firstOtherButtonIndex])
|
||||
return;
|
||||
} otherTitles:@"Retry", nil];
|
||||
} otherTitles:@"Retry", nil];
|
||||
return;
|
||||
case MFMailComposeResultCancelled:
|
||||
break;
|
||||
@@ -545,7 +581,7 @@
|
||||
initAlert:nil tappedButtonBlock:^(UIAlertView *alert_, NSInteger buttonIndex_) {
|
||||
[self ubiquityStoreManager:manager didSwitchToiCloud:iCloudEnabled];
|
||||
}
|
||||
cancelTitle:[PearlStrings get].commonButtonThanks otherTitles:nil];
|
||||
cancelTitle:[PearlStrings get].commonButtonThanks otherTitles:nil];
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -554,7 +590,7 @@
|
||||
return;
|
||||
if (buttonIndex == [alert firstOtherButtonIndex] + 1)
|
||||
[manager useiCloudStore:YES alertUser:NO];
|
||||
} cancelTitle:@"Leave iCloud Off" otherTitles:@"Explain?", @"Enable iCloud", nil];
|
||||
} cancelTitle:@"Leave iCloud Off" otherTitles:@"Explain?", @"Enable iCloud", nil];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,9 +8,10 @@
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface MPGuideViewController : UIViewController
|
||||
@interface MPGuideViewController : UIViewController <UIScrollViewDelegate>
|
||||
|
||||
@property (weak, nonatomic) IBOutlet UIScrollView *scrollView;
|
||||
@property (weak, nonatomic) IBOutlet UIPageControl *pageControl;
|
||||
|
||||
- (IBAction)close;
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
@implementation MPGuideViewController
|
||||
@synthesize scrollView;
|
||||
@synthesize pageControl;
|
||||
|
||||
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
|
||||
|
||||
@@ -48,6 +49,7 @@
|
||||
- (void)viewDidUnload {
|
||||
|
||||
[self setScrollView:nil];
|
||||
[self setPageControl:nil];
|
||||
[super viewDidUnload];
|
||||
}
|
||||
|
||||
@@ -56,4 +58,12 @@
|
||||
[self.presentingViewController dismissModalViewControllerAnimated:YES];
|
||||
}
|
||||
|
||||
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView_ {
|
||||
|
||||
NSInteger page = (NSInteger)(self.scrollView.contentOffset.x / self.scrollView.bounds.size.width);
|
||||
|
||||
self.pageControl.currentPage = page;
|
||||
self.pageControl.hidden = (page == self.pageControl.numberOfPages - 1);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -11,10 +11,11 @@
|
||||
#import "MPElementEntity.h"
|
||||
#import "MPSearchDelegate.h"
|
||||
|
||||
@interface MPMainViewController : UIViewController<MPTypeDelegate, UITextFieldDelegate, MPSearchResultsDelegate, UIWebViewDelegate, MFMailComposeViewControllerDelegate>
|
||||
@interface MPMainViewController : UIViewController<MPTypeDelegate, UITextFieldDelegate, MPSearchResultsDelegate, UIWebViewDelegate, MFMailComposeViewControllerDelegate, UIGestureRecognizerDelegate>
|
||||
|
||||
@property (nonatomic, assign) BOOL userNameHidden;
|
||||
@property (strong, nonatomic) MPElementEntity *activeElement;
|
||||
@property (strong, nonatomic) IBOutlet MPSearchDelegate *searchResultsController;
|
||||
@property (strong, nonatomic) IBOutlet MPSearchDelegate *searchDelegate;
|
||||
@property (weak, nonatomic) IBOutlet UITextField *contentField;
|
||||
@property (weak, nonatomic) IBOutlet UIButton *typeButton;
|
||||
@property (weak, nonatomic) IBOutlet UIWebView *helpView;
|
||||
@@ -22,29 +23,47 @@
|
||||
@property (weak, nonatomic) IBOutlet UILabel *passwordCounter;
|
||||
@property (weak, nonatomic) IBOutlet UIButton *passwordIncrementer;
|
||||
@property (weak, nonatomic) IBOutlet UIButton *passwordEdit;
|
||||
@property (weak, nonatomic) IBOutlet UIButton *passwordUpgrade;
|
||||
@property (weak, nonatomic) IBOutlet UIView *contentContainer;
|
||||
@property (weak, nonatomic) IBOutlet UIView *displayContainer;
|
||||
@property (weak, nonatomic) IBOutlet UIView *helpContainer;
|
||||
@property (weak, nonatomic) IBOutlet UIView *contentTipContainer;
|
||||
@property (weak, nonatomic) IBOutlet UIView *userNameTipContainer;
|
||||
@property (weak, nonatomic) IBOutlet UIView *alertContainer;
|
||||
@property (weak, nonatomic) IBOutlet UILabel *alertTitle;
|
||||
@property (weak, nonatomic) IBOutlet UITextView *alertBody;
|
||||
@property (weak, nonatomic) IBOutlet UILabel *contentTipBody;
|
||||
@property (weak, nonatomic) IBOutlet UIImageView *contentTipEditIcon;
|
||||
@property (weak, nonatomic) IBOutlet UILabel *userNameTipBody;
|
||||
@property (weak, nonatomic) IBOutlet UIImageView *toolTipEditIcon;
|
||||
@property (weak, nonatomic) IBOutlet UIView *searchTipContainer;
|
||||
@property (weak, nonatomic) IBOutlet UIView *actionsTipContainer;
|
||||
@property (weak, nonatomic) IBOutlet UIView *typeTipContainer;
|
||||
@property (strong, nonatomic) IBOutlet UILongPressGestureRecognizer *resetPasswordCounterGesture;
|
||||
@property (weak, nonatomic) IBOutlet UIView *toolTipContainer;
|
||||
@property (weak, nonatomic) IBOutlet UILabel *toolTipBody;
|
||||
@property (weak, nonatomic) IBOutlet UIView *userNameContainer;
|
||||
@property (weak, nonatomic) IBOutlet UITextField *userNameField;
|
||||
@property (weak, nonatomic) IBOutlet UIButton *passwordUser;
|
||||
@property (weak, nonatomic) IBOutlet UIView *outdatedAlertContainer;
|
||||
@property (weak, nonatomic) IBOutlet UIImageView *outdatedAlertBack;
|
||||
@property (weak, nonatomic) IBOutlet UIButton *outdatedAlertCloseButton;
|
||||
|
||||
|
||||
@property (copy) void (^contentTipCleanup)(BOOL finished);
|
||||
@property (copy) void (^toolTipCleanup)(BOOL finished);
|
||||
|
||||
- (IBAction)copyContent;
|
||||
- (IBAction)incrementPasswordCounter;
|
||||
- (IBAction)resetPasswordCounter:(UILongPressGestureRecognizer *)sender;
|
||||
- (IBAction)editUserName:(UILongPressGestureRecognizer *)sender;
|
||||
- (IBAction)editPassword;
|
||||
- (IBAction)closeAlert;
|
||||
- (IBAction)upgradePassword;
|
||||
- (IBAction)action:(UIBarButtonItem *)sender;
|
||||
- (IBAction)toggleUser;
|
||||
- (IBAction)searchOutdatedElements;
|
||||
- (IBAction)closeOutdatedAlert;
|
||||
- (IBAction)infoOutdatedAlert;
|
||||
|
||||
- (BOOL)isHelpVisible;
|
||||
- (void)toggleHelpAnimated:(BOOL)animated;
|
||||
- (void)setHelpHidden:(BOOL)hidden animated:(BOOL)animated;
|
||||
- (void)setHelpChapter:(NSString *)chapter;
|
||||
|
||||
@@ -13,39 +13,41 @@
|
||||
#import "LocalyticsSession.h"
|
||||
|
||||
|
||||
@interface MPMainViewController (Private)
|
||||
|
||||
- (void)updateAnimated:(BOOL)animated;
|
||||
- (void)showContentTip:(NSString *)message withIcon:(UIImageView *)icon;
|
||||
- (void)showAlertWithTitle:(NSString *)title message:(NSString *)message;
|
||||
- (void)changeElementWithWarning:(NSString *)warning do:(void (^)(void))task;
|
||||
- (void)changeElementWithoutWarningDo:(void (^)(void))task;
|
||||
|
||||
@end
|
||||
|
||||
@implementation MPMainViewController
|
||||
@synthesize userNameHidden = _userNameHidden;
|
||||
@synthesize activeElement = _activeElement;
|
||||
@synthesize searchResultsController = _searchResultsController;
|
||||
@synthesize searchDelegate = _searchDelegate;
|
||||
@synthesize typeButton = _typeButton;
|
||||
@synthesize helpView = _helpView;
|
||||
@synthesize siteName = _siteName;
|
||||
@synthesize passwordCounter = _passwordCounter;
|
||||
@synthesize passwordIncrementer = _passwordIncrementer;
|
||||
@synthesize passwordEdit = _passwordEdit;
|
||||
@synthesize passwordUpgrade = _passwordUpgrade;
|
||||
@synthesize contentContainer = _contentContainer;
|
||||
@synthesize displayContainer = _displayContainer;
|
||||
@synthesize helpContainer = _helpContainer;
|
||||
@synthesize contentTipContainer = _copiedContainer;
|
||||
@synthesize userNameTipContainer = _userNameTipContainer;
|
||||
@synthesize alertContainer = _alertContainer;
|
||||
@synthesize alertTitle = _alertTitle;
|
||||
@synthesize alertBody = _alertBody;
|
||||
@synthesize contentTipBody = _contentTipBody;
|
||||
@synthesize contentTipEditIcon = _contentTipEditIcon;
|
||||
@synthesize userNameTipBody = _userNameTipBody;
|
||||
@synthesize toolTipEditIcon = _contentTipEditIcon;
|
||||
@synthesize searchTipContainer = _searchTipContainer;
|
||||
@synthesize actionsTipContainer = _actionsTipContainer;
|
||||
@synthesize typeTipContainer = _typeTipContainer;
|
||||
@synthesize resetPasswordCounterGesture = _resetPasswordCounterGesture;
|
||||
@synthesize toolTipContainer = _toolTipContainer;
|
||||
@synthesize toolTipBody = _toolTipBody;
|
||||
@synthesize userNameContainer = _userNameContainer;
|
||||
@synthesize userNameField = _userNameField;
|
||||
@synthesize passwordUser = _passwordUser;
|
||||
@synthesize outdatedAlertContainer = _outdatedAlertContainer;
|
||||
@synthesize outdatedAlertBack = _outdatedAlertBack;
|
||||
@synthesize outdatedAlertCloseButton = _outdatedAlertCloseButton;
|
||||
@synthesize contentField = _contentField;
|
||||
@synthesize contentTipCleanup;
|
||||
@synthesize contentTipCleanup = _contentTipCleanup, toolTipCleanup = _toolTipCleanup;
|
||||
|
||||
#pragma mark - View lifecycle
|
||||
|
||||
@@ -56,7 +58,8 @@
|
||||
|
||||
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
|
||||
|
||||
[self setHelpHidden:![self isHelpVisible] animated:NO];
|
||||
[self updateHelpHiddenAnimated:NO];
|
||||
[self updateUserHiddenAnimated:NO];
|
||||
}
|
||||
|
||||
|
||||
@@ -68,12 +71,46 @@
|
||||
|
||||
- (void)viewDidLoad {
|
||||
|
||||
self.searchDelegate = [MPSearchDelegate new];
|
||||
self.searchDelegate.delegate = self;
|
||||
self.searchDelegate.searchDisplayController = self.searchDisplayController;
|
||||
self.searchDelegate.searchTipContainer = self.searchTipContainer;
|
||||
self.searchDisplayController.searchBar.delegate = self.searchDelegate;
|
||||
self.searchDisplayController.delegate = self.searchDelegate;
|
||||
self.searchDisplayController.searchResultsDelegate = self.searchDelegate;
|
||||
self.searchDisplayController.searchResultsDataSource = self.searchDelegate;
|
||||
|
||||
[self.passwordIncrementer addGestureRecognizer:[[UILongPressGestureRecognizer alloc] initWithTarget:self
|
||||
action:@selector(resetPasswordCounter:)]];
|
||||
[self.userNameContainer addGestureRecognizer:[[UILongPressGestureRecognizer alloc] initWithTarget:self
|
||||
action:@selector(editUserName:)]];
|
||||
[self.userNameContainer addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(copyUserName:)]];
|
||||
[self.outdatedAlertBack addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self
|
||||
action:@selector(infoOutdatedAlert)]];
|
||||
|
||||
self.view.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"ui_background"]];
|
||||
|
||||
self.contentField.font = [UIFont fontWithName:@"Exo-Black" size:self.contentField.font.pointSize];
|
||||
|
||||
self.alertBody.text = nil;
|
||||
self.contentTipEditIcon.hidden = YES;
|
||||
self.alertBody.text = nil;
|
||||
self.toolTipEditIcon.hidden = YES;
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidEnterBackgroundNotification object:self queue:nil
|
||||
usingBlock:^(NSNotification *note) {
|
||||
[MPAppDelegate get].activeUser.requiresExplicitMigration = NO;
|
||||
}];
|
||||
[[NSNotificationCenter defaultCenter] addObserverForName:MPNotificationElementUpdated object:nil queue:nil
|
||||
usingBlock:^void(NSNotification *note) {
|
||||
if (self.activeElement.type & MPElementTypeClassStored
|
||||
&& ![[self.activeElement.content description] length])
|
||||
[self showToolTip:@"Tap to set a password." withIcon:self.toolTipEditIcon];
|
||||
if (self.activeElement.requiresExplicitMigration)
|
||||
[self showToolTip:@"Password outdated. Tap to upgrade it." withIcon:nil];
|
||||
}];
|
||||
[[NSNotificationCenter defaultCenter] addObserverForName:MPNotificationSignedOut object:nil queue:nil
|
||||
usingBlock:^void(NSNotification *note) {
|
||||
self.activeElement = nil;
|
||||
}];
|
||||
|
||||
[super viewDidLoad];
|
||||
}
|
||||
@@ -85,28 +122,59 @@
|
||||
|
||||
if (![MPAppDelegate get].activeUser)
|
||||
[self.navigationController presentViewController:[self.storyboard instantiateViewControllerWithIdentifier:@"MPUnlockViewController"]
|
||||
animated:animated completion:nil];
|
||||
animated:animated completion:nil];
|
||||
if (self.activeElement.user != [MPAppDelegate get].activeUser)
|
||||
self.activeElement = nil;
|
||||
self.searchDisplayController.searchBar.text = nil;
|
||||
|
||||
self.searchTipContainer.alpha = 0;
|
||||
self.actionsTipContainer.alpha = 0;
|
||||
self.typeTipContainer.alpha = 0;
|
||||
self.alertContainer.alpha = 0;
|
||||
self.outdatedAlertContainer.alpha = 0;
|
||||
self.searchTipContainer.alpha = 0;
|
||||
self.actionsTipContainer.alpha = 0;
|
||||
self.typeTipContainer.alpha = 0;
|
||||
self.toolTipContainer.alpha = 0;
|
||||
|
||||
[self setHelpHidden:[[MPiOSConfig get].helpHidden boolValue] animated:animated];
|
||||
[self updateAnimated:animated];
|
||||
|
||||
if ([MPAppDelegate get].activeUser)
|
||||
[[MPAppDelegate get].managedObjectContextIfReady performBlock:^void() {
|
||||
NSError *error = nil;
|
||||
NSFetchRequest *migrationRequest = [NSFetchRequest
|
||||
fetchRequestWithEntityName:NSStringFromClass([MPElementEntity class])];
|
||||
migrationRequest.predicate = [NSPredicate predicateWithFormat:@"version_ < %d", MPAlgorithmDefaultVersion];
|
||||
NSArray *migrationElements = [[MPAppDelegate get].managedObjectContextIfReady executeFetchRequest:migrationRequest
|
||||
error:&error];
|
||||
if (!migrationElements) {
|
||||
err(@"While looking for elements to migrate: %@", error);
|
||||
return;
|
||||
}
|
||||
|
||||
BOOL didRequireExplicitMigration = [MPAppDelegate_Shared get].activeUser.requiresExplicitMigration;
|
||||
if (didRequireExplicitMigration)
|
||||
[MPAppDelegate_Shared get].activeUser.requiresExplicitMigration = NO;
|
||||
for (MPElementEntity *migrationElement in migrationElements)
|
||||
if (![migrationElement migrateExplicitly:NO])
|
||||
[MPAppDelegate_Shared get].activeUser.requiresExplicitMigration = YES;
|
||||
|
||||
if (!didRequireExplicitMigration && [MPAppDelegate_Shared get].activeUser.requiresExplicitMigration)
|
||||
[UIView animateWithDuration:0.3f animations:^{
|
||||
self.outdatedAlertContainer.alpha = 1;
|
||||
}];
|
||||
|
||||
}];
|
||||
|
||||
[super viewWillAppear:animated];
|
||||
}
|
||||
|
||||
- (void)viewDidAppear:(BOOL)animated {
|
||||
|
||||
if ([[MPiOSConfig get].firstRun boolValue])
|
||||
if (![[MPiOSConfig get].actionsTipShown boolValue])
|
||||
[UIView animateWithDuration:animated? 0.3f: 0 animations:^{
|
||||
self.actionsTipContainer.alpha = 1;
|
||||
} completion:^(BOOL finished) {
|
||||
if (finished) {
|
||||
[MPiOSConfig get].actionsTipShown = PearlBool(YES);
|
||||
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
[UIView animateWithDuration:0.2f animations:^{
|
||||
self.actionsTipContainer.alpha = 0;
|
||||
@@ -133,12 +201,12 @@
|
||||
|
||||
[self setContentField:nil];
|
||||
[self setTypeButton:nil];
|
||||
[self setSearchResultsController:nil];
|
||||
[self setHelpView:nil];
|
||||
[self setSiteName:nil];
|
||||
[self setPasswordCounter:nil];
|
||||
[self setPasswordIncrementer:nil];
|
||||
[self setPasswordEdit:nil];
|
||||
[self setPasswordUpgrade:nil];
|
||||
[self setContentContainer:nil];
|
||||
[self setHelpContainer:nil];
|
||||
[self setContentTipContainer:nil];
|
||||
@@ -146,11 +214,21 @@
|
||||
[self setAlertTitle:nil];
|
||||
[self setAlertBody:nil];
|
||||
[self setContentTipBody:nil];
|
||||
[self setContentTipEditIcon:nil];
|
||||
[self setToolTipEditIcon:nil];
|
||||
[self setSearchTipContainer:nil];
|
||||
[self setActionsTipContainer:nil];
|
||||
[self setTypeTipContainer:nil];
|
||||
[self setResetPasswordCounterGesture:nil];
|
||||
[self setToolTipContainer:nil];
|
||||
[self setToolTipBody:nil];
|
||||
[self setDisplayContainer:nil];
|
||||
[self setUserNameField:nil];
|
||||
[self setUserNameTipContainer:nil];
|
||||
[self setUserNameTipBody:nil];
|
||||
[self setUserNameContainer:nil];
|
||||
[self setPasswordUser:nil];
|
||||
[self setOutdatedAlertContainer:nil];
|
||||
[self setOutdatedAlertCloseButton:nil];
|
||||
[self setOutdatedAlertBack:nil];
|
||||
[super viewDidUnload];
|
||||
}
|
||||
|
||||
@@ -164,22 +242,40 @@
|
||||
}
|
||||
|
||||
[self setHelpChapter:self.activeElement? @"2": @"1"];
|
||||
[self updateHelpHiddenAnimated:NO];
|
||||
|
||||
self.passwordCounter.alpha = 0;
|
||||
self.passwordIncrementer.alpha = 0;
|
||||
self.passwordEdit.alpha = 0;
|
||||
self.passwordUpgrade.alpha = 0;
|
||||
self.passwordUser.alpha = 0;
|
||||
|
||||
if (self.activeElement)
|
||||
self.passwordUser.alpha = 0.5f;
|
||||
|
||||
if (self.activeElement.requiresExplicitMigration)
|
||||
self.passwordUpgrade.alpha = 0.5f;
|
||||
|
||||
else {
|
||||
if (self.activeElement.type & MPElementTypeClassGenerated) {
|
||||
self.passwordCounter.alpha = 0.5f;
|
||||
self.passwordIncrementer.alpha = 0.5f;
|
||||
} else
|
||||
if (self.activeElement.type & MPElementTypeClassStored)
|
||||
self.passwordEdit.alpha = 0.5f;
|
||||
}
|
||||
|
||||
self.siteName.text = self.activeElement.name;
|
||||
|
||||
self.passwordCounter.alpha = self.activeElement.type & MPElementTypeClassGenerated? 0.5f: 0;
|
||||
self.passwordIncrementer.alpha = self.activeElement.type & MPElementTypeClassGenerated? 0.5f: 0;
|
||||
self.passwordEdit.alpha = self.activeElement.type & MPElementTypeClassStored? 0.5f: 0;
|
||||
|
||||
[self.typeButton setTitle:NSStringFromMPElementType(self.activeElement.type)
|
||||
self.typeButton.alpha = self.activeElement? 1: 0;
|
||||
[self.typeButton setTitle:self.activeElement.typeName
|
||||
forState:UIControlStateNormal];
|
||||
self.typeButton.alpha = NSStringFromMPElementType(self.activeElement.type).length? 1: 0;
|
||||
|
||||
self.contentField.enabled = NO;
|
||||
|
||||
if ([self.activeElement isKindOfClass:[MPElementGeneratedEntity class]])
|
||||
self.passwordCounter.text = PearlString(@"%u", ((MPElementGeneratedEntity *)self.activeElement).counter);
|
||||
|
||||
self.contentField.text = @"";
|
||||
self.contentField.enabled = NO;
|
||||
self.contentField.text = @"";
|
||||
if (self.activeElement.name && ![self.activeElement isDeleted])
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
|
||||
NSString *description = [self.activeElement.content description];
|
||||
@@ -188,33 +284,71 @@
|
||||
self.contentField.text = description;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
- (BOOL)isHelpVisible {
|
||||
|
||||
return self.helpContainer.frame.origin.y == 216;
|
||||
self.userNameField.enabled = NO;
|
||||
self.userNameField.text = self.activeElement.userName;
|
||||
self.userNameHidden = !self.activeElement || ([[MPiOSConfig get].userNameHidden boolValue] && (self.activeElement.userName
|
||||
== nil));
|
||||
[self updateUserHiddenAnimated:NO];
|
||||
}
|
||||
|
||||
- (void)toggleHelpAnimated:(BOOL)animated {
|
||||
|
||||
[self setHelpHidden:[self isHelpVisible] animated:animated];
|
||||
[self setHelpHidden:![[MPiOSConfig get].helpHidden boolValue] animated:animated];
|
||||
}
|
||||
|
||||
- (void)setHelpHidden:(BOOL)hidden animated:(BOOL)animated {
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[UIView animateWithDuration:animated? 0.3f: 0 animations:^{
|
||||
if (hidden) {
|
||||
self.contentContainer.frame = CGRectSetHeight(self.contentContainer.frame, self.view.bounds.size.height - 44);
|
||||
self.helpContainer.frame = CGRectSetY(self.helpContainer.frame, self.view.bounds.size.height);
|
||||
[MPiOSConfig get].helpHidden = [NSNumber numberWithBool:YES];
|
||||
} else {
|
||||
self.contentContainer.frame = CGRectSetHeight(self.contentContainer.frame, 175);
|
||||
self.helpContainer.frame = CGRectSetY(self.helpContainer.frame, 216);
|
||||
[MPiOSConfig get].helpHidden = [NSNumber numberWithBool:NO];
|
||||
}
|
||||
[MPiOSConfig get].helpHidden = PearlBool(hidden);
|
||||
[self updateHelpHiddenAnimated:animated];
|
||||
}
|
||||
|
||||
- (void)updateHelpHiddenAnimated:(BOOL)animated {
|
||||
|
||||
if (animated) {
|
||||
[UIView animateWithDuration:0.3f animations:^{
|
||||
[self updateHelpHiddenAnimated:NO];
|
||||
}];
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if ([[MPiOSConfig get].helpHidden boolValue]) {
|
||||
self.contentContainer.frame = CGRectSetHeight(self.contentContainer.frame, self.view.bounds.size.height - 44 /* search bar */);
|
||||
self.helpContainer.frame = CGRectSetY(self.helpContainer.frame,
|
||||
self.view.bounds.size.height + 20 /* view moves up a bit when search appears. */);
|
||||
} else {
|
||||
self.contentContainer.frame = CGRectSetHeight(self.contentContainer.frame, 225);
|
||||
self.helpContainer.frame = CGRectSetY(self.helpContainer.frame, 266);
|
||||
}
|
||||
}
|
||||
|
||||
- (IBAction)toggleUser {
|
||||
|
||||
[self toggleUserAnimated:YES];
|
||||
}
|
||||
|
||||
- (void)toggleUserAnimated:(BOOL)animated {
|
||||
|
||||
[MPiOSConfig get].userNameHidden = PearlBool(!self.userNameHidden);
|
||||
self.userNameHidden = [[MPiOSConfig get].userNameHidden boolValue];
|
||||
[self updateUserHiddenAnimated:animated];
|
||||
}
|
||||
|
||||
- (void)updateUserHiddenAnimated:(BOOL)animated {
|
||||
|
||||
if (animated) {
|
||||
[UIView animateWithDuration:0.3f animations:^{
|
||||
[self updateUserHiddenAnimated:NO];
|
||||
}];
|
||||
return;
|
||||
}
|
||||
|
||||
if (self.userNameHidden) {
|
||||
self.displayContainer.frame = CGRectSetHeight(self.displayContainer.frame, 87);
|
||||
} else {
|
||||
self.displayContainer.frame = CGRectSetHeight(self.displayContainer.frame, 137);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
- (void)setHelpChapter:(NSString *)chapter {
|
||||
@@ -231,7 +365,7 @@
|
||||
- (void)webViewDidFinishLoad:(UIWebView *)webView {
|
||||
|
||||
NSString *error = [self.helpView stringByEvaluatingJavaScriptFromString:
|
||||
PearlString(@"setClass('%@');", ClassNameFromMPElementType(self.activeElement.type))];
|
||||
PearlString(@"setClass('%@');", self.activeElement.typeClassName)];
|
||||
if (error.length)
|
||||
err(@"helpView.setClass: %@", error);
|
||||
}
|
||||
@@ -264,6 +398,54 @@
|
||||
});
|
||||
}
|
||||
|
||||
- (void)showUserNameTip:(NSString *)message {
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
self.userNameTipBody.text = message;
|
||||
|
||||
[UIView animateWithDuration:0.3f animations:^{
|
||||
self.userNameTipContainer.alpha = 1;
|
||||
} completion:^(BOOL finished) {
|
||||
if (finished) {
|
||||
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC);
|
||||
dispatch_after(popTime, dispatch_get_main_queue(), ^(void) {
|
||||
[UIView animateWithDuration:0.2f animations:^{
|
||||
self.userNameTipContainer.alpha = 0;
|
||||
}];
|
||||
});
|
||||
}
|
||||
}];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)showToolTip:(NSString *)message withIcon:(UIImageView *)icon {
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if (self.toolTipCleanup)
|
||||
self.toolTipCleanup(NO);
|
||||
|
||||
self.toolTipBody.text = message;
|
||||
self.toolTipCleanup = ^(BOOL finished) {
|
||||
icon.hidden = YES;
|
||||
self.toolTipCleanup = nil;
|
||||
};
|
||||
|
||||
icon.hidden = NO;
|
||||
[UIView animateWithDuration:0.3f animations:^{
|
||||
self.toolTipContainer.alpha = 1;
|
||||
} completion:^(BOOL finished) {
|
||||
if (finished) {
|
||||
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC);
|
||||
dispatch_after(popTime, dispatch_get_main_queue(), ^(void) {
|
||||
[UIView animateWithDuration:0.2f animations:^{
|
||||
self.toolTipContainer.alpha = 0;
|
||||
} completion:self.toolTipCleanup];
|
||||
});
|
||||
}
|
||||
}];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)showAlertWithTitle:(NSString *)title message:(NSString *)message {
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
@@ -296,7 +478,28 @@
|
||||
[TestFlight passCheckpoint:MPCheckpointCopyToPasteboard];
|
||||
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointCopyToPasteboard
|
||||
attributes:[NSDictionary dictionaryWithObjectsAndKeys:
|
||||
NSStringFromMPElementType(self.activeElement.type), @"type",
|
||||
self.activeElement.typeName, @"type",
|
||||
PearlUnsignedInteger(self.activeElement.version),
|
||||
@"version",
|
||||
nil]];
|
||||
}
|
||||
|
||||
- (IBAction)copyUserName:(UITapGestureRecognizer *)sender {
|
||||
|
||||
if (!self.activeElement.userName)
|
||||
return;
|
||||
|
||||
inf(@"Copying user name for: %@", self.activeElement.name);
|
||||
[UIPasteboard generalPasteboard].string = [self.activeElement.content description];
|
||||
|
||||
[self showUserNameTip:@"Copied!"];
|
||||
|
||||
[TestFlight passCheckpoint:MPCheckpointCopyUserNameToPasteboard];
|
||||
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointCopyUserNameToPasteboard
|
||||
attributes:[NSDictionary dictionaryWithObjectsAndKeys:
|
||||
self.activeElement.typeName, @"type",
|
||||
PearlUnsignedInteger(self.activeElement.version),
|
||||
@"version",
|
||||
nil]];
|
||||
}
|
||||
|
||||
@@ -318,7 +521,10 @@
|
||||
[TestFlight passCheckpoint:MPCheckpointIncrementPasswordCounter];
|
||||
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointIncrementPasswordCounter
|
||||
attributes:[NSDictionary dictionaryWithObjectsAndKeys:
|
||||
NSStringFromMPElementType(self.activeElement.type), @"type",
|
||||
self.activeElement.typeName,
|
||||
@"type",
|
||||
PearlUnsignedInteger(self.activeElement.version),
|
||||
@"version",
|
||||
nil]];
|
||||
}];
|
||||
}
|
||||
@@ -346,11 +552,35 @@
|
||||
[TestFlight passCheckpoint:MPCheckpointResetPasswordCounter];
|
||||
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointResetPasswordCounter
|
||||
attributes:[NSDictionary dictionaryWithObjectsAndKeys:
|
||||
NSStringFromMPElementType(self.activeElement.type), @"type",
|
||||
self.activeElement.typeName,
|
||||
@"type",
|
||||
PearlUnsignedInteger(self.activeElement.version),
|
||||
@"version",
|
||||
nil]];
|
||||
}];
|
||||
}
|
||||
|
||||
- (IBAction)editUserName:(UILongPressGestureRecognizer *)sender {
|
||||
|
||||
if (sender.state != UIGestureRecognizerStateBegan)
|
||||
// Only fire when the gesture was first detected.
|
||||
return;
|
||||
|
||||
if (!self.activeElement)
|
||||
return;
|
||||
|
||||
self.userNameField.enabled = YES;
|
||||
[self.userNameField becomeFirstResponder];
|
||||
|
||||
[TestFlight passCheckpoint:MPCheckpointEditUserName];
|
||||
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointEditUserName attributes:[NSDictionary dictionaryWithObjectsAndKeys:
|
||||
self.activeElement.typeName,
|
||||
@"type",
|
||||
PearlUnsignedInteger(self.activeElement.version),
|
||||
@"version",
|
||||
nil]];
|
||||
}
|
||||
|
||||
- (void)changeElementWithWarning:(NSString *)warning do:(void (^)(void))task; {
|
||||
|
||||
[PearlAlert showAlertWithTitle:@"Password Change" message:warning viewStyle:UIAlertViewStyleDefault
|
||||
@@ -359,7 +589,7 @@
|
||||
return;
|
||||
|
||||
[self changeElementWithoutWarningDo:task];
|
||||
} cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:[PearlStrings get].commonButtonContinue, nil];
|
||||
} cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:[PearlStrings get].commonButtonContinue, nil];
|
||||
}
|
||||
|
||||
- (void)changeElementWithoutWarningDo:(void (^)(void))task; {
|
||||
@@ -391,12 +621,45 @@
|
||||
[TestFlight passCheckpoint:MPCheckpointEditPassword];
|
||||
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointEditPassword
|
||||
attributes:[NSDictionary dictionaryWithObjectsAndKeys:
|
||||
NSStringFromMPElementType(
|
||||
self.activeElement.type), @"type",
|
||||
self.activeElement.typeName, @"type",
|
||||
PearlUnsignedInteger(self.activeElement.version),
|
||||
@"version",
|
||||
nil]];
|
||||
}
|
||||
}
|
||||
|
||||
- (IBAction)upgradePassword {
|
||||
|
||||
[self changeElementWithWarning:
|
||||
self.activeElement.type & MPElementTypeClassGenerated?
|
||||
@"You are upgrading the site.\n\n"
|
||||
@"This upgrade improves the site's compatibility with the latest version of Master Password.\n\n"
|
||||
@"Your password will change and you will need to update your site's account."
|
||||
:
|
||||
@"You are upgrading the site.\n\n"
|
||||
@"This upgrade improves the site's compatibility with the latest version of Master Password."
|
||||
do:^{
|
||||
inf(@"Explicitly migrating element: %@", self.activeElement);
|
||||
[self.activeElement migrateExplicitly:YES];
|
||||
|
||||
[TestFlight passCheckpoint:MPCheckpointExplicitMigration];
|
||||
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointExplicitMigration
|
||||
attributes:[NSDictionary dictionaryWithObjectsAndKeys:
|
||||
self.activeElement.typeName,
|
||||
@"type",
|
||||
PearlUnsignedInteger(self.activeElement.version),
|
||||
@"version",
|
||||
nil]];
|
||||
}];
|
||||
}
|
||||
|
||||
- (IBAction)searchOutdatedElements {
|
||||
|
||||
self.searchDisplayController.searchBar.selectedScopeButtonIndex = MPSearchScopeOutdated;
|
||||
self.searchDisplayController.searchBar.searchResultsButtonSelected = YES;
|
||||
[self.searchDisplayController.searchBar becomeFirstResponder];
|
||||
}
|
||||
|
||||
- (IBAction)closeAlert {
|
||||
|
||||
[UIView animateWithDuration:0.3f animations:^{
|
||||
@@ -409,9 +672,27 @@
|
||||
[TestFlight passCheckpoint:MPCheckpointCloseAlert];
|
||||
}
|
||||
|
||||
- (IBAction)closeOutdatedAlert {
|
||||
|
||||
[UIView animateWithDuration:0.3f animations:^{
|
||||
self.outdatedAlertContainer.alpha = 0;
|
||||
}];
|
||||
|
||||
[TestFlight passCheckpoint:MPCheckpointCloseOutdatedAlert];
|
||||
}
|
||||
|
||||
- (IBAction)infoOutdatedAlert {
|
||||
|
||||
[self setHelpChapter:@"outdated"];
|
||||
[self setHelpHidden:NO animated:YES];
|
||||
[self closeOutdatedAlert];
|
||||
[MPAppDelegate get].activeUser.requiresExplicitMigration = NO;
|
||||
}
|
||||
|
||||
- (IBAction)action:(id)sender {
|
||||
|
||||
[PearlSheet showSheetWithTitle:nil message:nil viewStyle:UIActionSheetStyleAutomatic
|
||||
initSheet:nil
|
||||
tappedButtonBlock:^(UIActionSheet *sheet, NSInteger buttonIndex) {
|
||||
if (buttonIndex == [sheet cancelButtonIndex])
|
||||
return;
|
||||
@@ -449,17 +730,17 @@
|
||||
case 4: {
|
||||
inf(@"Action: Feedback via Mail");
|
||||
if (![MFMailComposeViewController canSendMail])
|
||||
[PearlAlert showAlertWithTitle:@"Feedback"
|
||||
[PearlAlert showAlertWithTitle:@"Sending Feedback"
|
||||
message:
|
||||
@"We'd love to hear what you think!\n\n"
|
||||
@"Please send any comments or reports to:\n"
|
||||
@"masterpassword@lyndir.com"
|
||||
viewStyle:UIAlertViewStyleDefault
|
||||
initAlert:nil tappedButtonBlock:nil cancelTitle:[PearlStrings get].commonButtonOkay
|
||||
otherTitles:nil];
|
||||
otherTitles:nil];
|
||||
|
||||
else {
|
||||
[PearlAlert showAlertWithTitle:@"Feedback"
|
||||
[PearlAlert showAlertWithTitle:@"Sending Feedback"
|
||||
message:
|
||||
@"We'd love to hear what you think!\n\n"
|
||||
@"If you're having trouble, it may help us if you can first reproduce the problem "
|
||||
@@ -469,7 +750,8 @@
|
||||
MFMailComposeViewController *composer = [MFMailComposeViewController new];
|
||||
[composer setMailComposeDelegate:self];
|
||||
[composer setToRecipients:[NSArray arrayWithObject:@"Master Password Development <masterpassword@lyndir.com>"]];
|
||||
[composer setSubject:PearlString(@"Feedback for Master Password [%@]", [[PearlKeyChain deviceIdentifier] stringByDeletingMatchesOf:@"-.*"])];
|
||||
[composer setSubject:PearlString(@"Feedback for Master Password [%@]",
|
||||
[[PearlKeyChain deviceIdentifier] stringByDeletingMatchesOf:@"-.*"])];
|
||||
[composer setMessageBody:
|
||||
PearlString(
|
||||
@"\n\n\n"
|
||||
@@ -479,20 +761,21 @@
|
||||
[MPAppDelegate get].activeUser.name,
|
||||
[PearlInfoPlist get].CFBundleShortVersionString,
|
||||
[PearlInfoPlist get].CFBundleVersion)
|
||||
isHTML:NO];
|
||||
isHTML:NO];
|
||||
|
||||
if (buttonIndex_ == [alert_ firstOtherButtonIndex]) {
|
||||
PearlLogLevel logLevel = [[MPiOSConfig get].sendInfo boolValue]? PearlLogLevelDebug: PearlLogLevelInfo;
|
||||
PearlLogLevel logLevel = [[MPiOSConfig get].sendInfo boolValue]? PearlLogLevelDebug
|
||||
: PearlLogLevelInfo;
|
||||
[composer addAttachmentData:[[[PearlLogger get] formatMessagesWithLevel:logLevel] dataUsingEncoding:NSUTF8StringEncoding]
|
||||
mimeType:@"text/plain"
|
||||
fileName:PearlString(@"%@-%@.log",
|
||||
[[NSDateFormatter rfc3339DateFormatter] stringFromDate:[NSDate date]],
|
||||
[PearlKeyChain deviceIdentifier])];
|
||||
mimeType:@"text/plain"
|
||||
fileName:PearlString(@"%@-%@.log",
|
||||
[[NSDateFormatter rfc3339DateFormatter] stringFromDate:[NSDate date]],
|
||||
[PearlKeyChain deviceIdentifier])];
|
||||
}
|
||||
|
||||
[self presentModalViewController:composer animated:YES];
|
||||
}
|
||||
cancelTitle:nil otherTitles:@"Include Logs", @"No Logs", nil];
|
||||
cancelTitle:nil otherTitles:@"Include Logs", @"No Logs", nil];
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -500,15 +783,21 @@
|
||||
#endif
|
||||
{
|
||||
inf(@"Action: Sign out");
|
||||
[[MPAppDelegate get] signOut];
|
||||
[[MPAppDelegate get] signOutAnimated:YES];
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
wrn(@"Unsupported action: %u", buttonIndex - [sheet firstOtherButtonIndex]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
[TestFlight passCheckpoint:MPCheckpointAction];
|
||||
}
|
||||
cancelTitle:[PearlStrings get].commonButtonCancel destructiveTitle:nil otherTitles:
|
||||
[self isHelpVisible]? @"Hide Help": @"Show Help", @"FAQ", @"Tutorial", @"Preferences", @"Feedback", @"Sign Out", nil];
|
||||
cancelTitle:[PearlStrings get].commonButtonCancel destructiveTitle:nil otherTitles:
|
||||
[[MPiOSConfig get].helpHidden boolValue]? @"Show Help": @"Hide Help", @"FAQ", @"Tutorial", @"Preferences", @"Feedback", @"Sign Out",
|
||||
nil];
|
||||
}
|
||||
|
||||
- (void)mailComposeController:(MFMailComposeViewController *)controller didFinishWithResult:(MFMailComposeResult)result
|
||||
@@ -535,25 +824,25 @@
|
||||
@"You will need to update your account's old password to the new one."
|
||||
do:^{
|
||||
// Update password type.
|
||||
if (ClassFromMPElementType(type) != ClassFromMPElementType(self.activeElement.type))
|
||||
if ([self.activeElement.algorithm classOfType:type] != self.activeElement.typeClass)
|
||||
// Type requires a different class of element. Recreate the element.
|
||||
[[MPAppDelegate managedObjectContext] performBlockAndWait:^{
|
||||
MPElementEntity *newElement = [NSEntityDescription insertNewObjectForEntityForName:ClassNameFromMPElementType(
|
||||
type)
|
||||
inManagedObjectContext:[MPAppDelegate managedObjectContext]];
|
||||
[[MPAppDelegate managedObjectContextIfReady] performBlockAndWait:^{
|
||||
MPElementEntity *newElement = [NSEntityDescription insertNewObjectForEntityForName:[self.activeElement.algorithm classNameOfType:type]
|
||||
inManagedObjectContext:[MPAppDelegate managedObjectContextIfReady]];
|
||||
newElement.name = self.activeElement.name;
|
||||
newElement.user = self.activeElement.user;
|
||||
newElement.uses = self.activeElement.uses;
|
||||
newElement.lastUsed = self.activeElement.lastUsed;
|
||||
newElement.version = self.activeElement.version;
|
||||
|
||||
[[MPAppDelegate managedObjectContext] deleteObject:self.activeElement];
|
||||
[[MPAppDelegate managedObjectContextIfReady] deleteObject:self.activeElement];
|
||||
self.activeElement = newElement;
|
||||
}];
|
||||
|
||||
self.activeElement.type = type;
|
||||
|
||||
if (type & MPElementTypeClassStored && ![[self.activeElement.content description] length])
|
||||
[self showContentTip:@"Tap to set a password." withIcon:self.contentTipEditIcon];
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:MPNotificationElementUpdated
|
||||
object:self.activeElement];
|
||||
}];
|
||||
}
|
||||
|
||||
@@ -574,30 +863,35 @@
|
||||
self.activeElement.name, self.activeElement.name)];
|
||||
[[MPAppDelegate get] saveContext];
|
||||
|
||||
if ([[MPiOSConfig get].firstRun boolValue])
|
||||
if (![[MPiOSConfig get].typeTipShown boolValue])
|
||||
[UIView animateWithDuration:0.5f animations:^{
|
||||
self.typeTipContainer.alpha = 1;
|
||||
} completion:^(BOOL finished) {
|
||||
if (finished) {
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
[UIView animateWithDuration:0.2f animations:^{
|
||||
self.typeTipContainer.alpha = 0;
|
||||
}];
|
||||
});
|
||||
[MPiOSConfig get].typeTipShown = PearlBool(YES);
|
||||
|
||||
dispatch_after(
|
||||
dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
[UIView animateWithDuration:0.2f animations:^{
|
||||
self.typeTipContainer.alpha = 0;
|
||||
}];
|
||||
});
|
||||
}
|
||||
}];
|
||||
|
||||
[self.searchDisplayController setActive:NO animated:YES];
|
||||
self.searchDisplayController.searchBar.text = self.activeElement.name;
|
||||
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:MPNotificationElementUsed object:self.activeElement];
|
||||
[TestFlight passCheckpoint:PearlString(MPCheckpointUseType @"_%@", NSStringFromMPElementType(self.activeElement.type))];
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:MPNotificationElementUpdated object:self.activeElement];
|
||||
[TestFlight passCheckpoint:PearlString(MPCheckpointUseType @"_%@", self.activeElement.typeShortName)];
|
||||
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointUseType attributes:[NSDictionary dictionaryWithObjectsAndKeys:
|
||||
NSStringFromMPElementType(
|
||||
self.activeElement.type), @"type",
|
||||
self.activeElement.typeName,
|
||||
@"type",
|
||||
PearlUnsignedInteger(self.activeElement.version),
|
||||
@"version",
|
||||
nil]];
|
||||
}
|
||||
|
||||
[self.searchDisplayController setActive:NO animated:YES];
|
||||
self.searchDisplayController.searchBar.text = self.activeElement.name;
|
||||
|
||||
[self updateAnimated:YES];
|
||||
}
|
||||
|
||||
@@ -605,6 +899,8 @@
|
||||
|
||||
if (textField == self.contentField)
|
||||
[self.contentField resignFirstResponder];
|
||||
if (textField == self.userNameField)
|
||||
[self.userNameField resignFirstResponder];
|
||||
|
||||
return YES;
|
||||
}
|
||||
@@ -625,13 +921,32 @@
|
||||
((MPElementStoredEntity *)self.activeElement).content = self.contentField.text;
|
||||
}];
|
||||
}
|
||||
|
||||
if (textField == self.userNameField) {
|
||||
self.userNameField.enabled = NO;
|
||||
if (![[MPiOSConfig get].userNameTipShown boolValue]) {
|
||||
[self showUserNameTip:@"Tap to copy or hold to edit."];
|
||||
[MPiOSConfig get].userNameTipShown = PearlBool(YES);
|
||||
}
|
||||
|
||||
if ([self.userNameField.text length])
|
||||
self.activeElement.userName = self.userNameField.text;
|
||||
else
|
||||
self.activeElement.userName = nil;
|
||||
|
||||
[[MPAppDelegate get] saveContext];
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request
|
||||
navigationType:(UIWebViewNavigationType)navigationType {
|
||||
|
||||
if (navigationType == UIWebViewNavigationTypeLinkClicked) {
|
||||
inf(@"External link: %@", [request URL]);
|
||||
if ([[[request URL] query] isEqualToString:@"outdated"]) {
|
||||
[self searchOutdatedElements];
|
||||
return NO;
|
||||
}
|
||||
|
||||
[TestFlight passCheckpoint:MPCheckpointExternalLink];
|
||||
|
||||
[[UIApplication sharedApplication] openURL:[request URL]];
|
||||
|
||||
@@ -75,7 +75,7 @@
|
||||
} recurse:NO];
|
||||
|
||||
self.savePasswordSwitch.on = [MPAppDelegate get].activeUser.saveKey;
|
||||
self.defaultTypeLabel.text = NSStringShortFromMPElementType([MPAppDelegate get].activeUser.defaultType);
|
||||
self.defaultTypeLabel.text = [[MPAppDelegate get].key.algorithm shortNameOfType:[MPAppDelegate get].activeUser.defaultType];
|
||||
|
||||
[super viewWillAppear:animated];
|
||||
}
|
||||
@@ -119,7 +119,7 @@
|
||||
|
||||
else
|
||||
if (cell == self.changeMPCell)
|
||||
[[MPAppDelegate get] changeMasterPasswordFor:[MPAppDelegate get].activeUser];
|
||||
[[MPAppDelegate get] changeMasterPasswordFor:[MPAppDelegate get].activeUser didResetBlock:nil];
|
||||
|
||||
[tableView deselectRowAtIndexPath:indexPath animated:YES];
|
||||
}
|
||||
@@ -139,7 +139,7 @@
|
||||
[MPAppDelegate get].activeUser.defaultType = type;
|
||||
[[MPAppDelegate get] saveContext];
|
||||
|
||||
self.defaultTypeLabel.text = NSStringShortFromMPElementType([MPAppDelegate get].activeUser.defaultType);
|
||||
self.defaultTypeLabel.text = [[MPAppDelegate get].key.algorithm shortNameOfType:[MPAppDelegate get].activeUser.defaultType];
|
||||
}
|
||||
|
||||
- (MPElementType)selectedType {
|
||||
|
||||
@@ -9,6 +9,11 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "MPElementEntity.h"
|
||||
|
||||
typedef enum {
|
||||
MPSearchScopeAll,
|
||||
MPSearchScopeOutdated,
|
||||
} MPSearchScope;
|
||||
|
||||
@protocol MPSearchResultsDelegate<NSObject>
|
||||
|
||||
- (void)didSelectElement:(MPElementEntity *)element;
|
||||
@@ -17,13 +22,13 @@
|
||||
|
||||
@interface MPSearchDelegate : NSObject<UITableViewDelegate, UITableViewDataSource, UISearchBarDelegate, UISearchDisplayDelegate, NSFetchedResultsControllerDelegate>
|
||||
|
||||
@property (strong, nonatomic) NSDateFormatter *dateFormatter;
|
||||
@property (strong, nonatomic) NSFetchedResultsController *fetchedResultsController;
|
||||
@property (strong, nonatomic) NSString *query;
|
||||
@property (strong, nonatomic) UILabel *tipView;
|
||||
@property (strong, nonatomic) NSDateFormatter *dateFormatter;
|
||||
@property (strong, readonly) NSFetchedResultsController *fetchedResultsController;
|
||||
@property (strong, nonatomic) NSString *query;
|
||||
@property (strong, nonatomic) UILabel *tipView;
|
||||
|
||||
@property (weak, nonatomic) IBOutlet id<MPSearchResultsDelegate> delegate;
|
||||
@property (weak, nonatomic) IBOutlet UISearchDisplayController *searchDisplayController;
|
||||
@property (weak, nonatomic) IBOutlet UIView *searchTipContainer;
|
||||
@property (strong, nonatomic) IBOutlet UISearchDisplayController *searchDisplayController;
|
||||
@property (weak, nonatomic) IBOutlet UIView *searchTipContainer;
|
||||
|
||||
@end
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
|
||||
#import "MPSearchDelegate.h"
|
||||
#import "MPAppDelegate.h"
|
||||
#import "MPAppDelegate_Store.h"
|
||||
#import "LocalyticsSession.h"
|
||||
#import "MPAppDelegate_Store.h"
|
||||
|
||||
@interface MPSearchDelegate (Private)
|
||||
|
||||
@@ -17,11 +17,13 @@
|
||||
|
||||
@end
|
||||
|
||||
@implementation MPSearchDelegate
|
||||
@implementation MPSearchDelegate {
|
||||
|
||||
NSFetchedResultsController *_fetchedResultsController;
|
||||
}
|
||||
@synthesize tipView;
|
||||
@synthesize query;
|
||||
@synthesize dateFormatter;
|
||||
@synthesize fetchedResultsController;
|
||||
@synthesize delegate;
|
||||
@synthesize searchDisplayController;
|
||||
@synthesize searchTipContainer;
|
||||
@@ -35,13 +37,6 @@
|
||||
self.dateFormatter.dateStyle = NSDateFormatterShortStyle;
|
||||
self.query = @"";
|
||||
|
||||
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass([MPElementEntity class])];
|
||||
fetchRequest.sortDescriptors = [NSArray arrayWithObject:[[NSSortDescriptor alloc] initWithKey:@"uses_" ascending:NO]];
|
||||
self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
|
||||
managedObjectContext:[MPAppDelegate managedObjectContext]
|
||||
sectionNameKeyPath:nil cacheName:nil];
|
||||
self.fetchedResultsController.delegate = self;
|
||||
|
||||
self.tipView = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 320, 170)];
|
||||
self.tipView.textAlignment = UITextAlignmentCenter;
|
||||
self.tipView.backgroundColor = [UIColor clearColor];
|
||||
@@ -62,6 +57,23 @@
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSFetchedResultsController *)fetchedResultsController {
|
||||
|
||||
if (!_fetchedResultsController) {
|
||||
NSManagedObjectContext *moc = [MPAppDelegate managedObjectContextIfReady];
|
||||
if (!moc)
|
||||
return nil;
|
||||
|
||||
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass([MPElementEntity class])];
|
||||
fetchRequest.sortDescriptors = [NSArray arrayWithObject:[[NSSortDescriptor alloc] initWithKey:@"uses_" ascending:NO]];
|
||||
_fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:moc
|
||||
sectionNameKeyPath:nil cacheName:nil];
|
||||
_fetchedResultsController.delegate = self;
|
||||
}
|
||||
|
||||
return _fetchedResultsController;
|
||||
}
|
||||
|
||||
- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar {
|
||||
|
||||
UITableView *tableView = self.searchDisplayController.searchResultsTableView;
|
||||
@@ -95,7 +107,12 @@
|
||||
|
||||
- (void)searchDisplayControllerWillBeginSearch:(UISearchDisplayController *)controller {
|
||||
|
||||
controller.searchBar.prompt = @"Enter the site's name:";
|
||||
controller.searchBar.prompt = @"Enter the site's name:";
|
||||
controller.searchBar.showsScopeBar = controller.searchBar.selectedScopeButtonIndex != MPSearchScopeAll;
|
||||
if (controller.searchBar.showsScopeBar)
|
||||
controller.searchBar.scopeButtonTitles = [NSArray arrayWithObjects:@"All", @"Outdated", nil];
|
||||
else
|
||||
controller.searchBar.scopeButtonTitles = nil;
|
||||
|
||||
[UIView animateWithDuration:0.2f animations:^{
|
||||
self.searchTipContainer.alpha = 0;
|
||||
@@ -110,9 +127,10 @@
|
||||
|
||||
- (void)searchDisplayControllerWillEndSearch:(UISearchDisplayController *)controller {
|
||||
|
||||
dbg(@"Search ended with: %@", controller.searchBar.text);
|
||||
controller.searchBar.prompt = nil;
|
||||
controller.searchBar.searchResultsButtonSelected = NO;
|
||||
controller.searchBar.selectedScopeButtonIndex = MPSearchScopeAll;
|
||||
controller.searchBar.showsScopeBar = NO;
|
||||
}
|
||||
|
||||
- (void)searchDisplayController:(UISearchDisplayController *)controller didLoadSearchResultsTableView:(UITableView *)tableView {
|
||||
@@ -127,26 +145,59 @@
|
||||
if (!controller.active)
|
||||
return NO;
|
||||
|
||||
assert(self.query);
|
||||
[self fetchData];
|
||||
|
||||
self.fetchedResultsController.fetchRequest.predicate = [NSPredicate predicateWithFormat:@"(%@ == '' OR name BEGINSWITH[cd] %@) AND user == %@",
|
||||
self.query, self.query, NilToNSNull([MPAppDelegate get].activeUser)];
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchScope:(NSInteger)searchOption {
|
||||
|
||||
if (!controller.active)
|
||||
return NO;
|
||||
|
||||
[self fetchData];
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)fetchData {
|
||||
|
||||
assert(self.query);
|
||||
assert([MPAppDelegate get].activeUser);
|
||||
|
||||
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"user == %@", [MPAppDelegate get].activeUser];
|
||||
if (self.query.length)
|
||||
predicate = [NSCompoundPredicate
|
||||
andPredicateWithSubpredicates:[NSArray arrayWithObjects:[NSPredicate predicateWithFormat:@"name BEGINSWITH[cd] %@", self.query],
|
||||
predicate, nil]];
|
||||
|
||||
switch ((MPSearchScope)self.searchDisplayController.searchBar.selectedScopeButtonIndex) {
|
||||
|
||||
case MPSearchScopeAll:
|
||||
break;
|
||||
case MPSearchScopeOutdated:
|
||||
predicate = [NSCompoundPredicate
|
||||
andPredicateWithSubpredicates:[NSArray arrayWithObjects:[NSPredicate predicateWithFormat:@"requiresExplicitMigration_ == YES"],
|
||||
predicate, nil]];
|
||||
break;
|
||||
}
|
||||
self.fetchedResultsController.fetchRequest.predicate = predicate;
|
||||
|
||||
NSError *error;
|
||||
if (![self.fetchedResultsController performFetch:&error])
|
||||
err(@"Couldn't fetch elements: %@", error);
|
||||
|
||||
NSArray *subviews = self.searchDisplayController.searchBar.superview.subviews;
|
||||
NSUInteger overlayIndex = [subviews indexOfObject:self.searchDisplayController.searchBar] + 1;
|
||||
UIView *overlay = [subviews count] > overlayIndex? [subviews objectAtIndex:overlayIndex]: nil;
|
||||
if (overlay == self.searchDisplayController.searchResultsTableView || ![overlay isKindOfClass:[UIControl class]])
|
||||
overlay = nil;
|
||||
if (self.tipView.superview != overlay) {
|
||||
[self.tipView removeFromSuperview];
|
||||
[overlay addSubview:self.tipView];
|
||||
}
|
||||
|
||||
return YES;
|
||||
[self.searchDisplayController.searchBar.superview enumerateSubviews:^(UIView *subview, BOOL *stop, BOOL *recurse) {
|
||||
CGRect searchBarFrame = self.searchDisplayController.searchBar.frame;
|
||||
if ([subview isKindOfClass:[UIControl class]] &&
|
||||
CGPointEqualToPoint(
|
||||
CGPointDistanceBetweenCGPoints(searchBarFrame.origin, subview.frame.origin),
|
||||
CGPointMake(0, searchBarFrame.size.height))) {
|
||||
[self.tipView removeFromSuperview];
|
||||
[subview addSubview:self.tipView];
|
||||
*stop = YES;
|
||||
}
|
||||
} recurse:NO];
|
||||
}
|
||||
|
||||
// See MP-14, also crashes easily on internal assertions etc..
|
||||
@@ -204,6 +255,7 @@
|
||||
|
||||
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
|
||||
|
||||
dbg(@"controllerDidChangeContent on thread: %@", [NSThread currentThread].name);
|
||||
[self.searchDisplayController.searchResultsTableView reloadData];
|
||||
// [self.searchDisplayController.searchResultsTableView endUpdates];
|
||||
}
|
||||
@@ -305,19 +357,20 @@
|
||||
|
||||
[self.fetchedResultsController.managedObjectContext performBlock:^{
|
||||
MPElementType type = [MPAppDelegate get].activeUser.defaultType;
|
||||
MPElementEntity *element = [NSEntityDescription insertNewObjectForEntityForName:ClassNameFromMPElementType(type)
|
||||
MPElementEntity *element = [NSEntityDescription insertNewObjectForEntityForName:[MPAlgorithmDefault classNameOfType:type]
|
||||
inManagedObjectContext:self.fetchedResultsController.managedObjectContext];
|
||||
assert([MPAppDelegate get].activeUser);
|
||||
|
||||
element.name = siteName;
|
||||
element.user = [MPAppDelegate get].activeUser;
|
||||
element.type = type;
|
||||
element.name = siteName;
|
||||
element.user = [MPAppDelegate get].activeUser;
|
||||
element.type = type;
|
||||
element.version = MPAlgorithmDefaultVersion;
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self.delegate didSelectElement:element];
|
||||
});
|
||||
}];
|
||||
} cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:[PearlStrings get].commonButtonYes, nil];
|
||||
} cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:[PearlStrings get].commonButtonYes, nil];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -353,7 +406,8 @@ forRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
[TestFlight passCheckpoint:MPCheckpointDeleteElement];
|
||||
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointDeleteElement
|
||||
attributes:[NSDictionary dictionaryWithObjectsAndKeys:
|
||||
NSStringFromMPElementType(element.type), @"type",
|
||||
element.typeName, @"type",
|
||||
PearlUnsignedInteger(element.version), @"version",
|
||||
nil]];
|
||||
}];
|
||||
}
|
||||
|
||||
@@ -79,7 +79,12 @@
|
||||
|
||||
assert(self.navigationController.topViewController == self);
|
||||
|
||||
[delegate didSelectType:[self typeAtIndexPath:indexPath]];
|
||||
MPElementType type = [self typeAtIndexPath:indexPath];
|
||||
if (type == NSNotFound)
|
||||
// Selected a non-type row.
|
||||
return;
|
||||
|
||||
[delegate didSelectType:type];
|
||||
[self.navigationController popViewControllerAnimated:YES];
|
||||
}
|
||||
|
||||
@@ -106,8 +111,9 @@
|
||||
case 7:
|
||||
return NSNotFound;
|
||||
|
||||
default:
|
||||
default: {
|
||||
Throw(@"Unsupported row: %d, when selecting generated element type.", indexPath.row);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,8 +129,9 @@
|
||||
case 3:
|
||||
return NSNotFound;
|
||||
|
||||
default:
|
||||
default: {
|
||||
Throw(@"Unsupported row: %d, when selecting stored element type.", indexPath.row);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,16 +11,20 @@
|
||||
@interface MPUnlockViewController : UIViewController<UITextFieldDelegate, UIScrollViewDelegate>
|
||||
|
||||
@property (weak, nonatomic) IBOutlet UIImageView *spinner;
|
||||
@property (weak, nonatomic) IBOutlet UILabel *passwordFieldLabel;
|
||||
@property (weak, nonatomic) IBOutlet UITextField *passwordField;
|
||||
@property (weak, nonatomic) IBOutlet UIView *passwordView;
|
||||
@property (weak, nonatomic) IBOutlet UIScrollView *avatarsView;
|
||||
@property (weak, nonatomic) IBOutlet UILabel *nameLabel;
|
||||
@property (weak, nonatomic) IBOutlet UILabel *oldNameLabel;
|
||||
@property (weak, nonatomic) IBOutlet UIButton *avatarTemplate;
|
||||
@property (weak, nonatomic) IBOutlet UILabel *deleteTip;
|
||||
@property (weak, nonatomic) IBOutlet UIView *createPasswordTipView;
|
||||
@property (weak, nonatomic) IBOutlet UILabel *tip;
|
||||
@property (weak, nonatomic) IBOutlet UIView *passwordTipView;
|
||||
@property (weak, nonatomic) IBOutlet UILabel *passwordTipLabel;
|
||||
@property (weak, nonatomic) IBOutlet UIView *wordWall;
|
||||
@property (strong, nonatomic) IBOutlet UILongPressGestureRecognizer *targetedUserActionGesture;
|
||||
@property (weak, nonatomic) IBOutlet UIActivityIndicatorView *loadingUsersIndicator;
|
||||
|
||||
@property (nonatomic, strong) UIColor *avatarShadowColor;
|
||||
|
||||
|
||||
@@ -17,6 +17,9 @@
|
||||
|
||||
@property (strong, nonatomic) MPUserEntity *selectedUser;
|
||||
@property (strong, nonatomic) NSMutableDictionary *avatarToUser;
|
||||
@property (nonatomic) BOOL wordWallAnimating;
|
||||
@property (nonatomic, strong) NSArray *wordList;
|
||||
|
||||
|
||||
@end
|
||||
|
||||
@@ -24,19 +27,25 @@
|
||||
@synthesize selectedUser;
|
||||
@synthesize avatarToUser;
|
||||
@synthesize spinner;
|
||||
@synthesize passwordFieldLabel;
|
||||
@synthesize passwordField;
|
||||
@synthesize passwordView;
|
||||
@synthesize avatarsView;
|
||||
@synthesize nameLabel, oldNameLabel;
|
||||
@synthesize avatarTemplate;
|
||||
@synthesize deleteTip;
|
||||
@synthesize createPasswordTipView;
|
||||
@synthesize tip;
|
||||
@synthesize passwordTipView;
|
||||
@synthesize passwordTipLabel;
|
||||
@synthesize wordWall;
|
||||
@synthesize targetedUserActionGesture;
|
||||
@synthesize loadingUsersIndicator;
|
||||
@synthesize avatarShadowColor = _avatarShadowColor;
|
||||
@synthesize wordWallAnimating = _wordWallAnimating;
|
||||
@synthesize wordList = _wordList;
|
||||
|
||||
|
||||
- (void)initAvatarAlert:(UIAlertView *)alert forUser:(MPUserEntity *)user {
|
||||
- (void)initializeAvatarAlert:(UIAlertView *)alert forUser:(MPUserEntity *)user {
|
||||
|
||||
UIScrollView *alertAvatarScrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(12, 30, 260, 150)];
|
||||
alertAvatarScrollView.indicatorStyle = UIScrollViewIndicatorStyleWhite;
|
||||
@@ -66,7 +75,7 @@
|
||||
avatar.backgroundColor = self.avatarTemplate.backgroundColor;
|
||||
else
|
||||
avatar.backgroundColor = [UIColor clearColor];
|
||||
} options:0];
|
||||
} options:0];
|
||||
[avatar onSelect:^(BOOL selected) {
|
||||
if (selected)
|
||||
user.avatar = (unsigned)avatar.tag;
|
||||
@@ -80,7 +89,7 @@
|
||||
[alertAvatarScrollView setContentOffset:selectedOffset animated:YES];
|
||||
}
|
||||
|
||||
- (void)initConfirmationAlert:(UIAlertView *)alert forUser:(MPUserEntity *)user {
|
||||
- (void)initializeConfirmationAlert:(UIAlertView *)alert forUser:(MPUserEntity *)user {
|
||||
|
||||
UIView *container = [[UIView alloc] initWithFrame:CGRectMake(12, 70, 260, 110)];
|
||||
[alert addSubview:container];
|
||||
@@ -121,6 +130,30 @@
|
||||
self.avatarTemplate.hidden = YES;
|
||||
self.spinner.alpha = 0;
|
||||
self.passwordTipView.alpha = 0;
|
||||
self.createPasswordTipView.alpha = 0;
|
||||
|
||||
NSMutableArray *wordListLines = [NSMutableArray arrayWithCapacity:27413];
|
||||
[[[NSString alloc] initWithData:[NSData dataWithContentsOfURL:[[NSBundle mainBundle] URLForResource:@"dictionary" withExtension:@"lst"]]
|
||||
encoding:NSUTF8StringEncoding] enumerateLinesUsingBlock:^(NSString *line, BOOL *stop) {
|
||||
[wordListLines addObject:line];
|
||||
}];
|
||||
self.wordList = wordListLines;
|
||||
|
||||
self.wordWall.alpha = 0;
|
||||
[self.wordWall enumerateSubviews:^(UIView *subview, BOOL *stop, BOOL *recurse) {
|
||||
UILabel *wordLabel = (UILabel *)subview;
|
||||
|
||||
[self initializeWordLabel:wordLabel];
|
||||
} recurse:NO];
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserverForName:PersistentStoreDidChange object:nil queue:nil usingBlock:
|
||||
^(NSNotification *note) {
|
||||
[self updateUsers];
|
||||
}];
|
||||
[[NSNotificationCenter defaultCenter] addObserverForName:PersistentStoreDidMergeChanges object:nil queue:nil usingBlock:
|
||||
^(NSNotification *note) {
|
||||
[self updateUsers];
|
||||
}];
|
||||
|
||||
[self updateLayoutAnimated:NO allowScroll:YES completion:nil];
|
||||
|
||||
@@ -135,10 +168,14 @@
|
||||
[self setAvatarsView:nil];
|
||||
[self setNameLabel:nil];
|
||||
[self setAvatarTemplate:nil];
|
||||
[self setDeleteTip:nil];
|
||||
[self setTip:nil];
|
||||
[self setPasswordTipView:nil];
|
||||
[self setPasswordTipLabel:nil];
|
||||
[self setTargetedUserActionGesture:nil];
|
||||
[self setWordWall:nil];
|
||||
[self setCreatePasswordTipView:nil];
|
||||
[self setPasswordFieldLabel:nil];
|
||||
[self setLoadingUsersIndicator:nil];
|
||||
[super viewDidUnload];
|
||||
}
|
||||
|
||||
@@ -155,6 +192,9 @@
|
||||
|
||||
[[UIApplication sharedApplication] setStatusBarHidden:YES withAnimation:animated? UIStatusBarAnimationSlide: UIStatusBarAnimationNone];
|
||||
|
||||
if (!animated)
|
||||
[[self findTargetedAvatar] setSelected:YES];
|
||||
|
||||
[super viewDidAppear:animated];
|
||||
}
|
||||
|
||||
@@ -166,9 +206,19 @@
|
||||
|
||||
- (void)updateUsers {
|
||||
|
||||
NSManagedObjectContext *moc = [MPAppDelegate managedObjectContextIfReady];
|
||||
if (!moc) {
|
||||
self.tip.text = @"Loading...";
|
||||
[self.loadingUsersIndicator startAnimating];
|
||||
return;
|
||||
}
|
||||
|
||||
self.tip.text = @"Tap and hold to delete or reset.";
|
||||
[self.loadingUsersIndicator stopAnimating];
|
||||
|
||||
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass([MPUserEntity class])];
|
||||
fetchRequest.sortDescriptors = [NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:@"lastUsed" ascending:NO]];
|
||||
NSArray *users = [[MPAppDelegate managedObjectContext] executeFetchRequest:fetchRequest error:nil];
|
||||
NSArray *users = [moc executeFetchRequest:fetchRequest error:nil];
|
||||
|
||||
// Clean up avatars.
|
||||
for (UIView *subview in [self.avatarsView subviews])
|
||||
@@ -201,14 +251,14 @@
|
||||
avatar.tag = user.avatar;
|
||||
|
||||
[avatar setBackgroundImage:[UIImage imageNamed:PearlString(@"avatar-%u", user.avatar)]
|
||||
forState:UIControlStateNormal];
|
||||
forState:UIControlStateNormal];
|
||||
[avatar setSelectionInSuperviewCandidate:YES isClearable:YES];
|
||||
[avatar onHighlightOrSelect:^(BOOL highlighted, BOOL selected) {
|
||||
if (highlighted || selected)
|
||||
avatar.backgroundColor = self.avatarTemplate.backgroundColor;
|
||||
else
|
||||
avatar.backgroundColor = [UIColor clearColor];
|
||||
} options:0];
|
||||
} options:0];
|
||||
[avatar onSelect:^(BOOL selected) {
|
||||
if (selected) {
|
||||
if ((self.selectedUser = user))
|
||||
@@ -219,7 +269,7 @@
|
||||
self.selectedUser = nil;
|
||||
[self didToggleUserSelection];
|
||||
}
|
||||
} options:0];
|
||||
} options:0];
|
||||
|
||||
[self.avatarToUser setObject:NilToNSNull(user) forKey:[NSValue valueWithNonretainedObject:avatar]];
|
||||
|
||||
@@ -248,13 +298,18 @@
|
||||
|
||||
- (void)didSelectNewUserAvatar:(UIButton *)newUserAvatar {
|
||||
|
||||
MPUserEntity *newUser = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass([MPUserEntity class])
|
||||
inManagedObjectContext:[MPAppDelegate managedObjectContext]];
|
||||
__block MPUserEntity *newUser = nil;
|
||||
[[MPAppDelegate managedObjectContextIfReady] performBlockAndWait:^{
|
||||
newUser = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass([MPUserEntity class])
|
||||
inManagedObjectContext:[MPAppDelegate managedObjectContextIfReady]];
|
||||
}];
|
||||
|
||||
[self showNewUserNameAlertFor:newUser completion:^(BOOL finished){
|
||||
[self showNewUserNameAlertFor:newUser completion:^(BOOL finished) {
|
||||
newUserAvatar.selected = NO;
|
||||
if (!finished)
|
||||
[[MPAppDelegate managedObjectContext] deleteObject:newUser];
|
||||
[[MPAppDelegate managedObjectContextIfReady] performBlock:^{
|
||||
[[MPAppDelegate managedObjectContextIfReady] deleteObject:newUser];
|
||||
}];
|
||||
}];
|
||||
}
|
||||
|
||||
@@ -278,7 +333,7 @@
|
||||
newUser.name = [alert textFieldAtIndex:0].text;
|
||||
[self showNewUserAvatarAlertFor:newUser completion:completion];
|
||||
}
|
||||
cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:[PearlStrings get].commonButtonSave, nil];
|
||||
cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:[PearlStrings get].commonButtonSave, nil];
|
||||
}
|
||||
|
||||
- (void)showNewUserAvatarAlertFor:(MPUserEntity *)newUser completion:(void (^)(BOOL finished))completion {
|
||||
@@ -286,13 +341,13 @@
|
||||
[PearlAlert showAlertWithTitle:@"Choose Your Avatar"
|
||||
message:@"\n\n\n\n\n\n" viewStyle:UIAlertViewStyleDefault
|
||||
initAlert:^(UIAlertView *_alert, UITextField *_firstField) {
|
||||
[self initAvatarAlert:_alert forUser:newUser];
|
||||
[self initializeAvatarAlert:_alert forUser:newUser];
|
||||
}
|
||||
tappedButtonBlock:^(UIAlertView *_alert, NSInteger _buttonIndex) {
|
||||
tappedButtonBlock:^(UIAlertView *_alert, NSInteger _buttonIndex) {
|
||||
|
||||
// Okay
|
||||
[self showNewUserConfirmationAlertFor:newUser completion:completion];
|
||||
} cancelTitle:nil otherTitles:[PearlStrings get].commonButtonOkay, nil];
|
||||
// Okay
|
||||
[self showNewUserConfirmationAlertFor:newUser completion:completion];
|
||||
} cancelTitle:nil otherTitles:[PearlStrings get].commonButtonOkay, nil];
|
||||
}
|
||||
|
||||
- (void)showNewUserConfirmationAlertFor:(MPUserEntity *)newUser completion:(void (^)(BOOL finished))completion {
|
||||
@@ -303,21 +358,21 @@
|
||||
@"\n\n\n\n\n\n"
|
||||
viewStyle:UIAlertViewStyleDefault
|
||||
initAlert:^void(UIAlertView *__alert, UITextField *__firstField) {
|
||||
[self initConfirmationAlert:__alert forUser:newUser];
|
||||
[self initializeConfirmationAlert:__alert forUser:newUser];
|
||||
}
|
||||
tappedButtonBlock:^void(UIAlertView *__alert, NSInteger __buttonIndex) {
|
||||
if (__buttonIndex == [__alert cancelButtonIndex]) {
|
||||
[self showNewUserNameAlertFor:newUser completion:completion];
|
||||
return;
|
||||
}
|
||||
tappedButtonBlock:^void(UIAlertView *__alert, NSInteger __buttonIndex) {
|
||||
if (__buttonIndex == [__alert cancelButtonIndex]) {
|
||||
[self showNewUserNameAlertFor:newUser completion:completion];
|
||||
return;
|
||||
}
|
||||
|
||||
// Confirm
|
||||
completion(YES);
|
||||
self.selectedUser = newUser;
|
||||
// Confirm
|
||||
completion(YES);
|
||||
self.selectedUser = newUser;
|
||||
|
||||
[self updateUsers];
|
||||
}
|
||||
cancelTitle:@"Change" otherTitles:@"Confirm", nil];
|
||||
[self updateUsers];
|
||||
}
|
||||
cancelTitle:@"Change" otherTitles:@"Confirm", nil];
|
||||
}
|
||||
|
||||
- (void)updateLayoutAnimated:(BOOL)animated allowScroll:(BOOL)allowScroll completion:(void (^)(BOOL finished))completion {
|
||||
@@ -332,14 +387,16 @@
|
||||
|
||||
self.oldNameLabel.alpha = 0;
|
||||
self.nameLabel.alpha = 1;
|
||||
} completion:^(BOOL finished) {
|
||||
} completion:^(BOOL finished) {
|
||||
if (completion)
|
||||
completion(finished);
|
||||
}];
|
||||
return;
|
||||
}
|
||||
|
||||
// Lay out password entry and user selection views.
|
||||
if (self.selectedUser && !self.passwordView.alpha) {
|
||||
// User was just selected.
|
||||
self.passwordView.alpha = 1;
|
||||
self.avatarsView.center = CGPointMake(160, 170);
|
||||
self.avatarsView.scrollEnabled = NO;
|
||||
@@ -347,9 +404,9 @@
|
||||
self.nameLabel.backgroundColor = [UIColor blackColor];
|
||||
self.oldNameLabel.center = self.nameLabel.center;
|
||||
self.avatarShadowColor = [UIColor whiteColor];
|
||||
self.deleteTip.alpha = 0;
|
||||
} else
|
||||
if (!self.selectedUser && self.passwordView.alpha == 1) {
|
||||
// User was just deselected.
|
||||
self.passwordField.text = nil;
|
||||
self.passwordView.alpha = 0;
|
||||
self.avatarsView.center = CGPointMake(160, 310);
|
||||
@@ -358,9 +415,36 @@
|
||||
self.nameLabel.backgroundColor = [UIColor clearColor];
|
||||
self.oldNameLabel.center = self.nameLabel.center;
|
||||
self.avatarShadowColor = [UIColor lightGrayColor];
|
||||
self.deleteTip.alpha = 0.5;
|
||||
}
|
||||
|
||||
// Lay out the word wall.
|
||||
if (!self.selectedUser || self.selectedUser.keyID) {
|
||||
self.passwordFieldLabel.text = @"Enter your master password:";
|
||||
|
||||
self.wordWall.alpha = 0;
|
||||
self.createPasswordTipView.alpha = 0;
|
||||
self.wordWallAnimating = NO;
|
||||
} else {
|
||||
self.passwordFieldLabel.text = @"Create your master password:";
|
||||
|
||||
if (!self.wordWallAnimating) {
|
||||
self.wordWallAnimating = YES;
|
||||
self.wordWall.alpha = 1;
|
||||
self.createPasswordTipView.alpha = 1;
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
// Jump out of our UIView animation block.
|
||||
[self beginWordWallAnimation];
|
||||
});
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 15 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
|
||||
[UIView animateWithDuration:1 animations:^{
|
||||
self.createPasswordTipView.alpha = 0;
|
||||
}];
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Lay out user targeting.
|
||||
MPUserEntity *targetedUser = self.selectedUser;
|
||||
UIButton *selectedAvatar = [self avatarForUser:self.selectedUser];
|
||||
UIButton *targetedAvatar = selectedAvatar;
|
||||
@@ -381,7 +465,7 @@
|
||||
avatar.alpha = isTargeted? 1: self.selectedUser? 0.1: 0.4;
|
||||
|
||||
[self updateAvatarShadowColor:avatar isTargeted:isTargeted];
|
||||
} recurse:NO];
|
||||
} recurse:NO];
|
||||
|
||||
if (allowScroll) {
|
||||
CGPoint targetContentOffset = CGPointMake(MAX(0, targetedAvatar.center.x - self.avatarsView.bounds.size.width / 2),
|
||||
@@ -390,7 +474,8 @@
|
||||
[self.avatarsView setContentOffset:targetContentOffset animated:animated];
|
||||
}
|
||||
|
||||
self.nameLabel.text = targetedUser? targetedUser.name: @"New User";
|
||||
// Lay out user name label.
|
||||
self.nameLabel.text = targetedAvatar? targetedUser? targetedUser.name: @"New User": nil;
|
||||
self.nameLabel.bounds = CGRectSetHeight(self.nameLabel.bounds,
|
||||
[self.nameLabel.text sizeWithFont:self.nameLabel.font
|
||||
constrainedToSize:CGSizeMake(self.nameLabel.bounds.size.width - 10, 100)
|
||||
@@ -400,6 +485,36 @@
|
||||
completion(YES);
|
||||
}
|
||||
|
||||
- (void)beginWordWallAnimation {
|
||||
|
||||
[self.wordWall enumerateSubviews:^(UIView *subview, BOOL *stop, BOOL *recurse) {
|
||||
UILabel *wordLabel = (UILabel *)subview;
|
||||
|
||||
if (wordLabel.frame.origin.x < -self.wordWall.frame.size.width / 3) {
|
||||
wordLabel.frame = CGRectSetX(wordLabel.frame, wordLabel.frame.origin.x + self.wordWall.frame.size.width);
|
||||
[self initializeWordLabel:wordLabel];
|
||||
}
|
||||
} recurse:NO];
|
||||
|
||||
if (self.wordWallAnimating)
|
||||
[UIView animateWithDuration:15 delay:0 options:UIViewAnimationOptionCurveLinear animations:^{
|
||||
[self.wordWall enumerateSubviews:^(UIView *subview, BOOL *stop, BOOL *recurse) {
|
||||
UILabel *wordLabel = (UILabel *)subview;
|
||||
|
||||
wordLabel.frame = CGRectSetX(wordLabel.frame, wordLabel.frame.origin.x - self.wordWall.frame.size.width / 3);
|
||||
} recurse:NO];
|
||||
} completion:^(BOOL finished) {
|
||||
if (finished)
|
||||
[self beginWordWallAnimation];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)initializeWordLabel:(UILabel *)wordLabel {
|
||||
|
||||
wordLabel.alpha = 0.05 + (random() % 35) / 100.0F;
|
||||
wordLabel.text = [self.wordList objectAtIndex:(NSUInteger)random() % [self.wordList count]];
|
||||
}
|
||||
|
||||
- (void)setPasswordTip:(NSString *)string {
|
||||
|
||||
if (string.length)
|
||||
@@ -418,15 +533,15 @@
|
||||
BOOL unlocked = [[MPAppDelegate get] signInAsUser:self.selectedUser usingMasterPassword:self.passwordField.text];
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if (unlocked) {
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (long)(NSEC_PER_SEC * 0.5f)), dispatch_get_main_queue(), ^{
|
||||
[self dismissViewControllerAnimated:YES completion:nil];
|
||||
});
|
||||
} else
|
||||
if (unlocked)
|
||||
[self dismissViewControllerAnimated:YES completion:nil];
|
||||
|
||||
else {
|
||||
if (self.passwordField.text.length)
|
||||
[self setPasswordTip:@"Incorrect password."];
|
||||
|
||||
[self setSpinnerActive:NO];
|
||||
[self setSpinnerActive:NO];
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -509,7 +624,8 @@
|
||||
pulseShadowOpacityAnimation.repeatCount = MAXFLOAT;
|
||||
|
||||
CAAnimationGroup *group = [[CAAnimationGroup alloc] init];
|
||||
group.animations = [NSArray arrayWithObjects:toShadowColorAnimation, toShadowOpacityAnimation, pulseShadowOpacityAnimation, nil];
|
||||
group.animations = [NSArray arrayWithObjects:toShadowColorAnimation, toShadowOpacityAnimation, pulseShadowOpacityAnimation,
|
||||
nil];
|
||||
group.duration = MAXFLOAT;
|
||||
|
||||
[avatar.layer removeAnimationForKey:@"inactiveShadow"];
|
||||
@@ -574,8 +690,8 @@
|
||||
|
||||
[self tryMasterPassword];
|
||||
}
|
||||
cancelTitle:[PearlStrings get].commonButtonCancel
|
||||
otherTitles:[PearlStrings get].commonButtonContinue, nil];
|
||||
cancelTitle:[PearlStrings get].commonButtonCancel
|
||||
otherTitles:[PearlStrings get].commonButtonContinue, nil];
|
||||
|
||||
|
||||
return YES;
|
||||
@@ -617,24 +733,30 @@
|
||||
MPUserEntity *targetedUser = [self userForAvatar:[self findTargetedAvatar]];
|
||||
if (!targetedUser)
|
||||
return;
|
||||
|
||||
[PearlAlert showAlertWithTitle:@"Delete Or Reset User"
|
||||
message:
|
||||
PearlString(@"Do you want to reset the master password or delete all record of the following user?\n\n%@",
|
||||
targetedUser.name)
|
||||
viewStyle:UIAlertViewStyleDefault
|
||||
initAlert:nil tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
|
||||
if (buttonIndex == [alert cancelButtonIndex])
|
||||
return;
|
||||
|
||||
if (buttonIndex == [alert firstOtherButtonIndex]) {
|
||||
[[MPAppDelegate get].managedObjectContext deleteObject:targetedUser];
|
||||
[[MPAppDelegate get] saveContext];
|
||||
[self updateUsers];
|
||||
} else if (buttonIndex == [alert firstOtherButtonIndex] + 1) {
|
||||
[[MPAppDelegate get] changeMasterPasswordFor:targetedUser];
|
||||
}
|
||||
|
||||
} cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:@"Delete", @"Reset", nil];
|
||||
|
||||
[PearlSheet showSheetWithTitle:targetedUser.name
|
||||
message:nil viewStyle:UIActionSheetStyleBlackTranslucent
|
||||
initSheet:nil
|
||||
tappedButtonBlock:^(UIActionSheet *sheet, NSInteger buttonIndex) {
|
||||
if (buttonIndex == [sheet cancelButtonIndex])
|
||||
return;
|
||||
|
||||
if (buttonIndex == [sheet destructiveButtonIndex]) {
|
||||
[[MPAppDelegate get].managedObjectContextIfReady performBlockAndWait:^{
|
||||
[[MPAppDelegate get].managedObjectContextIfReady deleteObject:targetedUser];
|
||||
}];
|
||||
[[MPAppDelegate get] saveContext];
|
||||
[self updateUsers];
|
||||
} else
|
||||
if (buttonIndex == [sheet firstOtherButtonIndex]) {
|
||||
[[MPAppDelegate get] changeMasterPasswordFor:targetedUser didResetBlock:^{
|
||||
[[self avatarForUser:targetedUser] setSelected:YES];
|
||||
}];
|
||||
}
|
||||
else
|
||||
[[MPAppDelegate get] saveContext];
|
||||
} cancelTitle:[PearlStrings get].commonButtonCancel destructiveTitle:@"Delete User" otherTitles:@"Reset Password",
|
||||
@"Save",
|
||||
nil];
|
||||
}
|
||||
@end
|
||||
|
||||
@@ -12,7 +12,11 @@
|
||||
|
||||
@property (nonatomic, retain) NSNumber *sendInfo;
|
||||
@property (nonatomic, retain) NSNumber *helpHidden;
|
||||
@property (nonatomic, retain) NSNumber *userNameHidden;
|
||||
@property (nonatomic, retain) NSNumber *showQuickStart;
|
||||
@property (nonatomic, retain) NSNumber *actionsTipShown;
|
||||
@property (nonatomic, retain) NSNumber *typeTipShown;
|
||||
@property (nonatomic, retain) NSNumber *userNameTipShown;
|
||||
|
||||
+ (MPiOSConfig *)get;
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
//
|
||||
|
||||
@implementation MPiOSConfig
|
||||
@dynamic sendInfo, helpHidden, showQuickStart;
|
||||
@dynamic sendInfo, helpHidden, userNameHidden, showQuickStart, actionsTipShown, typeTipShown, userNameTipShown;
|
||||
|
||||
- (id)init {
|
||||
|
||||
@@ -17,8 +17,12 @@
|
||||
[self.defaults registerDefaults:[NSDictionary dictionaryWithObjectsAndKeys:
|
||||
[NSNumber numberWithBool:NO], NSStringFromSelector(@selector(sendInfo)),
|
||||
[NSNumber numberWithBool:NO], NSStringFromSelector(@selector(helpHidden)),
|
||||
[NSNumber numberWithBool:YES], NSStringFromSelector(@selector(userNameHidden)),
|
||||
[NSNumber numberWithBool:YES], NSStringFromSelector(@selector(showQuickStart)),
|
||||
@"510296984", NSStringFromSelector(@selector(iTunesID)),
|
||||
PearlBoolNot(self.firstRun), NSStringFromSelector(@selector(actionsTipShown)),
|
||||
PearlBoolNot(self.firstRun), NSStringFromSelector(@selector(typeTipShown)),
|
||||
PearlBool(NO), NSStringFromSelector(@selector(userNameTipShown)),
|
||||
nil]];
|
||||
|
||||
return self;
|
||||
|
||||
@@ -67,6 +67,15 @@
|
||||
<true/>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string>© 2011-2012, Lyndir</string>
|
||||
<key>ReplacementFonts</key>
|
||||
<dict>
|
||||
<key>Futura-CondensedExtraBold</key>
|
||||
<string>Exo-ExtraBold</string>
|
||||
<key>Futura-CondensedMedium</key>
|
||||
<string>Exo-Light</string>
|
||||
<key>Futura-Medium</key>
|
||||
<string>Exo</string>
|
||||
</dict>
|
||||
<key>UIAppFonts</key>
|
||||
<array>
|
||||
<string>Exo-Black.otf</string>
|
||||
|
||||
@@ -17,6 +17,10 @@
|
||||
#import <CoreData/CoreData.h>
|
||||
|
||||
#import "TestFlight.h"
|
||||
#import <Crashlytics/Crashlytics.h>
|
||||
|
||||
//#define Crashlytics PearlNil
|
||||
//#define TestFlight PearlNil
|
||||
|
||||
#import "MPTypes.h"
|
||||
#import "MPiOSConfig.h"
|
||||
|
||||
BIN
Resources/AppStore-iOS.png
Normal file
|
After Width: | Height: | Size: 571 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 101 KiB After Width: | Height: | Size: 85 KiB |
|
Before Width: | Height: | Size: 71 KiB After Width: | Height: | Size: 65 KiB |
|
Before Width: | Height: | Size: 173 KiB After Width: | Height: | Size: 157 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 59 KiB |
|
Before Width: | Height: | Size: 147 KiB After Width: | Height: | Size: 133 KiB |
|
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 164 KiB After Width: | Height: | Size: 133 KiB |
|
Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 63 KiB |
|
Before Width: | Height: | Size: 180 KiB After Width: | Height: | Size: 141 KiB |
|
Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 45 KiB |
|
Before Width: | Height: | Size: 155 KiB After Width: | Height: | Size: 108 KiB |
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 115 KiB After Width: | Height: | Size: 111 KiB |
|
Before Width: | Height: | Size: 6.0 KiB After Width: | Height: | Size: 6.3 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 9.9 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 4.0 KiB |