2
0

Compare commits

...

99 Commits
1.1.0 ... 1.2.1

Author SHA1 Message Date
Maarten Billemont
2dba4c87ba Small build fixes.
[FIXED]     Linker warning about PIE.
[UPDATED]   Login failed is no longer recorded when login attempt was
            not using a password (noisy).
2012-06-27 15:01:58 +02:00
Maarten Billemont
6841ae2b0d Updated iTunesArtwork
[IMPROVED]  iTunesArtwork prettier & 1024px.
[ADDED]     FontReplacer target.
2012-06-27 13:53:39 +02:00
Maarten Billemont
a3698b9e47 Guide pager, fonts, crash.
[ADDED]     Page controller in guide to see where in the guide you are.
[ADDED]     FontReplacer to be able to "use" Exo from IB (by
            substituting "Futura").
[FIXED]     A crash when loading the main VC because of the reset
            password gesture recogniser.
[IMPROVED]  Font in tables to standard system fonts.
[IMPROVED]  Guide content positioning and sizing making space for pager.
2012-06-27 09:51:14 +02:00
Maarten Billemont
5b65c8c6bd Improve UI to be more HIG friendly.
[IMPROVED]  Use action sheets instead of alerts when showing destructive
            action choices.
[IMPROVED]  Make tappable regions at least 44x44pt.
[IMPROVED]  Title/message for alert/sheets.
2012-06-25 08:59:54 +02:00
Maarten Billemont
94c9d50a12 Ability to noop TestFlight and Crashlytics 2012-06-24 21:06:00 +02:00
Maarten Billemont
5849c9668f Format noise. 2012-06-24 18:27:10 +02:00
Maarten Billemont
4d4ba3425e Improvements to password import.
[FIXED]     Importing of mpsites with passwords showing for
            stored password types.
[FIXED]     Don't try to show mail composition dialog when the user has
            no mail account configured.  This will crash.  Instead,
            show a friendly popup explaining things.
[IMPROVED]  Message of password export emails.
[FIXED]     Hierarchy of MPUnlockVC so password field becomes touchable.
2012-06-24 16:32:22 +02:00
Maarten Billemont
b7e91358be Use only annotated tags for version determination. 2012-06-24 16:32:22 +02:00
Maarten Billemont
da860d74c4 More internal fixes. 2012-06-24 16:29:51 +02:00
Maarten Billemont
4e2ceb33a0 Fix an odd bug with a missing input handler for certain keyboard types. 2012-06-24 14:53:05 +02:00
Maarten Billemont
d14bde07bd Hardcoded signing profiles to avoid Xcode using the wrong one. 2012-06-23 14:34:26 +02:00
Maarten Billemont
fa2dc822d3 Prettier loading screen and disable Crashlytics debug mode. 2012-06-15 11:34:53 +02:00
Maarten Billemont
d4adafb448 Reset from unlock, FAQ improvements.
[ADDED]     Ability to reset a master password from the unlock screen.
[FIXED]     Manually retain objects that live next to a VC in a
            storyboard within the VC to avoid an OS bug.
[FIXED]     Visibility of the deleteTip.
[ADDED]     An index to the FAQ.
[IMPROVED]  Improved and expanded the FAQ a bit more.
2012-06-15 11:16:02 +02:00
Maarten Billemont
a67d9676ba Improved feedback + logging.
[REMOVED]   Apptentive is now implemented by a standard iOS mail
            composer window and can optionally include logs.
[IMPROVED]  Better inf-level logging of what's going on.
[AUDITED]   Made sure no personal is going out through inf+ levels.
2012-06-14 21:56:54 +02:00
Maarten Billemont
bc2da6a99b Site style updates of what & algorithm pages. 2012-06-12 10:20:06 +02:00
Maarten Billemont
50b5c87f61 Some fixes.
[REMOVED]   Old explanation of saveKey from settings.
[FIXED]     Status-bar display when closing the guide.
[FIXED]     Crash when opening type selection.
2012-06-12 07:52:18 +02:00
Maarten Billemont
d4bcad2658 Small fix of last commit. 2012-06-11 23:44:02 +02:00
Maarten Billemont
13bca309ba Checkpoints update and tag in Localytics too. 2012-06-11 23:39:50 +02:00
Maarten Billemont
83bdb9d626 Checkpoint fixes. 2012-06-11 23:24:23 +02:00
Maarten Billemont
5f1fc453d4 Updated guide + settings bundle + fixes.
[UPDATED]   Guide updated with UI changes.
[FIXED]     Don't animate pushing the unlock VC when appearance of main
            VC is not animated (eg. application startup).
[FIXED]     rememberKey -> rememberLogin in settings bundle.
[REMOVED]   saveKey from settings bundle.
[UPDATED]   Removed lock from the Default image; show a dummy avatar
            instead.
[FIXED]     Don't forget key when signing out.
2012-06-11 23:03:17 +02:00
Maarten Billemont
9c875d4311 Default password type + new user confirmation.
[ADDED]     User preference for default password type.
[RENAMED]   Secure type to Maximum Security.
[FIXED]     Logging bug in password generation.
[ADDED]     Confirmation popup after new user creation.
2012-06-11 16:15:49 +02:00
Maarten Billemont
afafa473a7 Reverted changes to unlock presenting and fixed settings display.
[FIXED]     IASK display by not initializing the VC with the right nib.
2012-06-11 00:53:03 +02:00
Maarten Billemont
4d5f609d72 Fixes.
[REVERTED]  managedObjectContext and persistenceStoreCoordinator needn't
            be lazy anymore.
[FIXED]     AdHoc #def'ed code fixed.
2012-06-10 21:43:18 +02:00
Maarten Billemont
6f1d53ea35 Improved runtime debug logging + new user avatar selection.
[UPDATED]   Crashlytics.
[IMPROVED]  Sending logs and configuration to crashlytics, added
            sendDebugInfo option that allows the user to choose to send
            more info.  Now also sending a device identifier.
[ADDED]     Avatar selection dialog when a new user is created.
2012-06-10 08:36:11 +02:00
Maarten Billemont
a8bf74a925 AppCode code formatting. 2012-06-08 23:46:13 +02:00
Maarten Billemont
d59f77720c Minor improvements. 2012-06-08 21:06:19 +02:00
Maarten Billemont
92be7f7267 Some site layout improvements. 2012-06-08 20:46:08 +02:00
Maarten Billemont
09d5e64c55 Improved signin/signout state logic. 2012-06-08 00:40:30 +02:00
Maarten Billemont
f796888901 Retire version 2 of the site. 2012-06-07 00:27:22 +02:00
Maarten Billemont
679990dc4b Improved algorithm security.
[UPDATED]   Algorithm updated to reflect advice from randombit.net
            cryptography list:
                - Add in a salt (user name) to defeat rainbow tables.
                - Add in a fixed string to scope the algorithm and avoid
                  colliding with someone else's similar or identical
                  algorithm (also helps protect against precalculated
                  rainbow tables).
                - Use HMAC instead of plain SHA to avoid SHA weaknesses.
                  The old implementation wasn't vulnerable to extension
                  attacks or other known weaknesses, but HMAC is a safer
                  choice and will bring up less suspicion.
                - Prefix strings by length as an extra precautionary
                  measure against possible bugs in hash functions.
2012-06-07 00:25:55 +02:00
Maarten Billemont
b472c85c9d Avatar display fixes.
[ADDED]     A new password type: Secure password.  20 characters, not
            word-based, very high entropy.
[FIXED]     UI bugs and improvements with the avatar display and
            password checking state display.
2012-06-06 22:38:43 +02:00
Maarten Billemont
77306e0046 Unlock and user preferences implementation.
[FIXED]     Unlock screen.
[FIXED]     Internal fixes.
[ADDED]     Avatar selection in preferences.
[ADDED]     Implementation of the other preferences.
[IMPROVED]  UI of unlock and preferences screens.
2012-06-06 00:59:09 +02:00
Maarten Billemont
0491ba3f97 Type improvements and fixes.
[IMPROVED]  Cleaner handling of types in entities.
[FIXED]     Lots of little fixes from refactoring and AppCode
            inspections, throughout.
2012-06-05 00:55:02 +02:00
Maarten Billemont
ba299d4674 User support.
[ADDED]     Support for multiple users, each their own master password.
2012-06-04 11:27:02 +02:00
Maarten Billemont
3de9a0c67e Removed apptentive ratings, added some tool tips, and some fixes.
[REMOVED]   Stop using apptentive for rating questions.  Pearl's
            built-in functionality seems nicer and more basic.
[ADDED]     Some more tool-tips to help the user, to be shown on first
            run only.
[FIXED]     The site name tip wasn't showing anymore.
[FIXED]     Some language and formatting in help.html.
2012-05-27 22:35:03 +02:00
Maarten Billemont
7d0ea4b3f5 Some usability improvements + apptentive fixes.
[IMPROVED]  Make persistence more lazy to avoid UI blocks.
[IMPROVED]  Use "Master Password" as CFBundleDisplayName at runtime.  No
            home-screen length restrictions there.
[FIXED]     Inform Apptentive of significant events.
2012-05-27 14:26:13 +02:00
Maarten Billemont
ac14b10752 Integrated Apptentive.
[ADDED]     Experimental support for Apptentive.
2012-05-23 09:30:07 +02:00
Maarten Billemont
2d8f2943e5 Fix headers of other pages of the website. 2012-05-23 08:56:56 +02:00
Maarten Billemont
4c0348b5c8 Disable get satisfaction better. 2012-05-22 00:14:38 +02:00
Maarten Billemont
a5d2f82db4 Site improvements.
[IMPROVED]  Site prepared for production version of Master Password.
[ADDED]     Screenshots and marketing stuff to the site.
[UPDATED]   Texts on the site.
2012-05-21 23:57:06 +02:00
Maarten Billemont
ec8eff2117 Sign distribution builds with distribution profiles.
[FIXED]     When resigning development applications, get-task-allowed
            isn't properly set in the binary.
2012-05-21 08:48:40 +02:00
Maarten Billemont
b7e72e98ac Change bundle display name to match 'Master Password' better. 2012-05-20 23:31:42 +02:00
Maarten Billemont
a0b1e63c74 Not a native iPad application. 2012-05-20 23:21:43 +02:00
Maarten Billemont
42704ab504 CFBundleVersion fixes for App Store validation. 2012-05-20 22:20:01 +02:00
Maarten Billemont
6cc74c8898 Fixed review button and explain button.
[FIXED]     Return to iCloud question after hitting Explain button.
[ADDED]     iTunesID for review button.
2012-05-20 20:52:13 +02:00
Maarten Billemont
44911f1d9e Improvements to UI cleanup, confirm new master passwords, texts updates.
[FIXED]     Immediately open the application on the password entry view
            if the key is forgotten instead of revealing the internals
            in a blink.
[FIXED]     Clean up the UI better when switching between master
            passwords.
[ADDED]     Ask spelling confirmation when a master password is used
            that doesn't have any known sites yet.
[ADDED]     iPad HD quality icon.
[IMPROVED]  Artworks re-generated, downscale improved, convertImages
            improved.
[IMPROVED]  Small guide text improvements.
[UPDATED]   Help texts updated for current operation and UI.
2012-05-20 20:00:39 +02:00
Maarten Billemont
16fc32ee30 Make it work on iPad. 2012-05-17 15:22:37 +02:00
Maarten Billemont
ac9534c680 Ask the user to review the application after a few launches.
[ADDED]     Using Pearl's review tracking to ask the user to review the
            application after he's used it a few times.
2012-05-16 23:52:32 +02:00
Maarten Billemont
2020637f0e Pearl update + search tip.
[ADDED]     A tip when searching for sites suggesting a site name
            format for sites and email accounts.
2012-05-15 23:27:05 +02:00
Maarten Billemont
ada6d6b36a Keychain fix + signout fix.
[FIXED]     GenericPassword class keychain items should have both an
            Account and a Service attribute.
[FIXED]     OS X: Logout when key is saved.
2012-05-13 19:50:40 +02:00
Maarten Billemont
b0875beb36 Bump Pearl for a bug in the PearlLogger. 2012-05-13 16:46:37 +02:00
Maarten Billemont
94818bb242 Build updates for OS X. 2012-05-13 10:24:19 +02:00
Maarten Billemont
1f68a4cb09 Remove iCloud reset. 2012-05-12 21:54:07 +02:00
Maarten Billemont
f7e64fe4b8 Key hash -> key ID. 2012-05-12 20:24:12 +02:00
Maarten Billemont
301366f1f1 Log messages + OTP.
[IMPROVED]  MP-15: Audit and improve log messages.
[ADDED]     If an element's counter is 0, generate a time-based OTP
            instead.  The OTP changes every 5 minutes.
2012-05-12 18:31:05 +02:00
Maarten Billemont
941b428cfc More reliable search results.
[IMPROVED]  MP-14: Made searchResultsTableView more reliable.
2012-05-12 16:04:18 +02:00
Maarten Billemont
0d638a4c3f Guide update.
[FIXED]     MP-1: Guide images were a bit outdated.
2012-05-12 00:59:58 +02:00
Maarten Billemont
cdfafa55cf Fix sign-out.
[FIXED]     MP-13: Sign Out doesn't work -- when password is saved,
            signing out signs you right back in.  Now, the saved key
            password is forgotten.
2012-05-12 00:08:19 +02:00
Maarten Billemont
82b8de5e23 iCloud toggling + internal improvements
[UPDATED]   Change TestFlight preprocessor toggles to
            TESTFLIGHT_SDK_VERSION.
[IMPROVED]  Change logic behind shared MPAppDelegate to inheritance,
            allowing for the common files to implement methods that the
            platform-specific class can override.
[IMPROVED]  Handling and checking of config changes.
[ADDED]     iCloud config toggle, allowing a user to turn on and off
            the iCloud store.
[IMPROVED]  Explanation of iCloud.
2012-05-11 23:42:29 +02:00
Maarten Billemont
6bbd183ac9 TestFlight checkpoint update + iCloud warning.
[UPDATED]   Check TARGET_OS_IPHONE instead of
            __IPHONE_OS_VERSION_MIN_REQUIRED.
[UPDATED]   A few TestFlight checkpoints names have been updated.
[ADDED]     A few new TestFlight checkpoints for new features.
[ADDED]     Warn the user when he's not using iCloud and allow him to
            switch back to iCloud.
2012-05-11 17:04:51 +02:00
Maarten Billemont
7d9131cdca Fix archive build. 2012-05-11 10:48:25 +02:00
Maarten Billemont
12fb7243d6 Launch images. 2012-05-11 09:25:19 +02:00
Maarten Billemont
eb4e25c0ba Fix importing of sites.
[FIXED]     Parsing mpsites for import.
2012-05-11 09:05:11 +02:00
Maarten Billemont
587461144b Persistence fixes on OS X.
[FIXED]     OS X: Handle recovery from unsupported stores.
[FIXED]     OS X: Use predicate instead of predicate template.
2012-05-10 01:02:55 +02:00
Maarten Billemont
9968491e3b Switch back to iCloud after resetting it. 2012-05-10 00:21:22 +02:00
Maarten Billemont
db7d68a091 Fix migration problems non-interactively.
[FIXED]     Migration problems should be fixed non-interactively: The
            main thread may well be unavailable which means we may not
            be able to actually get a message through to the user.  This
            would dead-lock the migration problem recovery.
2012-05-09 23:36:45 +02:00
Maarten Billemont
c8f9f79bb2 Handle store loading errors.
[ADDED]     Detect store loading errors and attempt to recover.
2012-05-09 19:34:00 +02:00
Maarten Billemont
21c0565619 Importing of mpsites + renames + fixes.
[ADDED]     Importing mpsites exports.
[RENAMED]   keyHashHex -> keyID.
[RENAMED]   Calculated -> Generated.
[FIXED]     iOS: Dismiss mail VC when done with it.
[FIXED]     iOS: Properly hide content tip icons when a content tip is
            shown while one with an icon is still active.
[FIXED]     iOS: Settings bundle was using old keys.
2012-05-09 10:24:47 +02:00
Maarten Billemont
04bc7a497c Exporting of sites.
[ADDED]     Ability to export site data.
[UPDATED]   Type of passwords can now include feature bits.
2012-05-08 15:57:11 +02:00
Maarten Billemont
7c5cea9c8d Mac support for preferences: rememberKey, saveKey, useICloud.
[ADDED]     OS X: Lots of warning checks when compiling.
[ADDED]     OS X: Ability to enable and disable iCloud.
[ADDED]     OS X: Support for rememberKey & saveKey.
[UPDATED]   storeKey -> saveKey: makes more sense to people.
[IMPROVED]  OS X: Password display window fancier.
2012-05-08 13:46:08 +02:00
Maarten Billemont
1b90c9bfa3 Make unlock window a sheet of the password window.
[IMPROVED]  Unlock alert a sheet of the password window.
2012-05-08 00:44:49 +02:00
Maarten Billemont
f622b2c7d4 iCloud fixes.
[UPDATED]   Moved shared MPAppDelegate code into separate files
            depending on the code's responsibility.
[UPDATED]   iCloud implementation removed in favor of
            iCloudStoreManager's managed implementation.
[FIXED]     iCloud configuration made AppStore friendly.
2012-05-07 22:32:10 +02:00
Maarten Billemont
98080ceb51 Persistence fixes and improvements.
[UPDATED]   Pearl to ARC.
[FIXED]     Crash related to persistence changes that caused UI updates
            while other UI changes were ongoing.
[IMPROVED]  Real description of MPElementEntities and use content
            whenever the password is requested.
[IMPROVED]  iOS: Handling of search result fetching and table reloading.
2012-05-07 01:18:02 +02:00
Maarten Billemont
f5d9334b06 App Store requires iCloud container name to be bundle identifier.
[FIXED]     iOS: Compile fix.
2012-05-05 16:09:25 +02:00
Maarten Billemont
f882d0fb53 Status menu.
[ADDED]     OS X: A status menu that allows you to lock and quit MP.
2012-05-05 13:32:09 +02:00
Maarten Billemont
dfc62fa8a8 Sign release apps with production cert. 2012-05-05 00:43:06 +02:00
Maarten Billemont
f5551d4823 Add a Mac icon.
[ADDED]     OS X: Icon file.
[REMOVED]   OS X: openssl & scrypt targets in favor of precompiled
            libraries.
2012-05-05 00:38:23 +02:00
Maarten Billemont
424479dada Usability improvements to OS X MP app.
[ADDED]     OS X: A status item to activate the MP window.
[ADDED]     OS X: A global hotkey (cmd-ctrl-p) to activate the MP
            window.
[ADDED]     OS X: Make the MP window dismissable by hitting Esc.
[ADDED]     OS X: Copy the site content by hitting Enter.
[FIXED]     OS X: Make the password field first responder.
[FIXED]     OS X: Don't pop the password window multiple times if the
            application gets activated while the key isn't set yet.
[IMPROVED]  OS X: Remove the MP icon from the dock.
2012-05-05 00:15:51 +02:00
Maarten Billemont
376953ae56 iCloud sync fixes.
[REMOVED]   OS X: Disabled ability to add new sites from OS X until I
            have time to implement it properly without causing
            duplicates etc.
[MOVED]     iCloud and Core Data support was centralised to iOS and OS X
            to make sure both platforms always use the same container
            configuration.
[FIXED]     iCloud sync problems.
[REMOVED]   iCloud KV is not used/needed.
2012-05-04 18:54:58 +02:00
Maarten Billemont
26f8e086bb Fix double reset counter trigger.
[FIXED]     Reset password counter triggered twice.
2012-05-03 20:17:30 +02:00
Maarten Billemont
950ce888e2 Crashlytics, Localytics, TestFlight + password change warnings.
[ADDED]     Crashlytics, Localytics.
[IMPROVED]  Async TestFlight takeOff.
[REMOVED]   TestFlight token hidden.
[FIXED]     Warnings, mostly to do with sign conversions.
[ADDED]     Warning messages whenever site's password changes, allowing
            the user to cancel the operation.
[ADDED]     Make password counter resettable by holding down on the
            counter increment button.
2012-05-03 16:49:15 +02:00
Maarten Billemont
657fef6249 Removed network from MP OS X + initial STM code MP iOS.
[REMOVED]   OS X: Removed networking code.  We're going to use SendToMac
            instead.
[ADDED]     iOS: Initial code for communicating with SendToMac hosts.
2012-03-14 00:55:49 +01:00
Maarten Billemont
0d19202ca7 Beginnings of network listener.
[ADDED]     OS X: Code to open a listening socket (WIP).
[ADDED]     OS X: Code to broadcast socket via Bonjour.
2012-03-12 18:14:01 +01:00
Maarten Billemont
1755bd1607 Bump Pearl for sheet fix.
[FIXED]     MP-9: Action sheet shows up in portrait while in landscape.
2012-03-11 16:12:18 +01:00
Maarten Billemont
dc8b6aa86c Help text update for type.
[FIXED]     MP-12: Help doesn't always properly update for element type.
2012-03-11 15:29:55 +01:00
Maarten Billemont
0c536ef640 Fix some UI issues, mostly with alerts.
[FIXED]     Schedule all UI-changing methods on the main thread.  Fixes
            some UI bugs.
[FIXED]     Attempt to calculate password for deleted element.
[FIXED]     MP-6: Close open alert when switching sites.
[FIXED]     MP-7: Remind user to change site's password when creating a
            new site.
2012-03-11 11:14:28 +01:00
Maarten Billemont
fa3680f9c4 Typo: loose -> lose.
[FIXED]     Misspelled lose as loose.

^MP-11 fixed
2012-03-06 15:49:33 +01:00
Maarten Billemont
c8e03ff016 Fix checkout of InAppSettingsKit dependency. 2012-03-06 11:15:32 +01:00
Maarten Billemont
3fcac696bb Bundle dependency dylibs in OS X app.
[FIXED]     OS X: Application bundle was missing its dependency
            libraries and couldn't run.  Now they're bundled as
            run-path dependent libraries.
2012-03-06 11:03:21 +01:00
Maarten Billemont
d0ae954dbf Working OS X prototype application.
[ADDED]     OS X: Master password input and changing.
[ADDED]     OS X: Autocompletion by searching Core Data for previously
            used sites.
[ADDED]     OS X: Working password generation for sites.
[FIXED]     Bad ciphering on little endian machines: Convert bytes to
            network endian before using them as numbers.
[FIXED]     Opening of Core Data store when iCloud is unavailable.
2012-03-06 01:04:19 +01:00
Maarten Billemont
f3c24fd96f Mac OS X build fixes of shared code from iOS.
[FIXED]     Build problems with code shared from the iOS project in the
            Mac project.
2012-03-05 22:43:20 +01:00
Maarten Billemont
02ffa9611a Prepare key handling logic for sharing with OS X.
[MOVED]     Key logic now in a common class extension on MPAppDelegate
            so it can be shared between iOS and OS X apps.
[MOVED]     MPConfig for sharing between iOS and OS X apps.
[CHANGED]   keyphrase -> key.
2012-03-05 22:19:05 +01:00
Maarten Billemont
6bda70920b Fix Pearl/scrypt/openssl on Mac + password window.
[FIXED]     Dependencies and compilation on OS X.
[ADDED]     MPPasswordWindowController: Window to handle passwords.
2012-03-05 09:53:32 +01:00
Maarten Billemont
2761355180 Initial Mac project.
[ADDED]     A basic Mac OS X project.
[ADDED]     A basic UI for the Mac project.
[FIXED]     Notification at store update from iCloud.
2012-03-04 15:31:26 +01:00
Maarten Billemont
cbf624b3da Changed file hierarchy in preparation for Mac version. 2012-03-04 10:47:20 +01:00
Maarten Billemont
9899104891 Better password font.
[FIXED]     MP-10: The font used to show didn't make a clear enough
            distinction between certain different characters.
2012-03-04 10:12:32 +01:00
Maarten Billemont
d34ec96b94 Make two site look better on lower resolutions. 2012-03-02 20:50:57 +01:00
Maarten Billemont
ac735f3ccb Fix landscape layout.
[FIXED]     View layout problems while in landscape.

- #MP-8 fixed
2012-03-01 00:09:48 +01:00
Maarten Billemont
c508ed8c46 Fix appearance of content tip edit icon.
[FIXED]     Tip on storage type passwords had a missing edit icon.

MP-5 fixed
2012-02-29 19:59:01 +01:00
1440 changed files with 24273 additions and 8392 deletions

6
.gitmodules vendored
View File

@@ -4,3 +4,9 @@
[submodule "External/InAppSettingsKit"]
path = External/InAppSettingsKit
url = git://github.com/futuretap/InAppSettingsKit.git
[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

View File

@@ -0,0 +1,9 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0" is_locked="false">
<option name="myName" value="Project Default" />
<option name="myLocal" value="false" />
<inspection_tool class="LossyEncoding" enabled="true" level="WARNING" enabled_by_default="true" />
<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>
</component>

View File

@@ -0,0 +1,7 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="PROJECT_PROFILE" value="Project Default" />
<option name="USE_PROJECT_PROFILE" value="true" />
<version value="1.0" />
</settings>
</component>

View File

@@ -0,0 +1 @@
Versions/Current/Crashlytics

View File

@@ -0,0 +1 @@
Versions/Current/Headers

View File

@@ -0,0 +1 @@
Versions/Current/Resources

Binary file not shown.

View File

@@ -0,0 +1,172 @@
//
// Crashlytics.h
// Crashlytics
//
// Copyright 2012 Crashlytics, Inc. All rights reserved.
//
#import <Foundation/Foundation.h>
/**
*
* The CLS_LOG macro provides as easy way to gather more information in your log messages that are
* sent with your crash data. CLS_LOG prepends your custom log message with the function name and
* line number where the macro was used. If your app was built with the DEBUG preprocessor macro
* defined CLS_LOG uses the CLSNSLog function which forwards your log message to NSLog and CLSLog.
* If the DEBUG preprocessor macro is not defined CLS_LOG uses CLSLog only.
*
* Example output:
* -[AppDelegate login:] line 134 $ login start
*
* If you would like to change this macro, create a new header file, unset our define and then define
* your own version. Make sure this new header file is imported after the Crashlytics header file.
*
* #undef CLS_LOG
* #define CLS_LOG(__FORMAT__, ...) CLSNSLog...
*
**/
#ifdef DEBUG
#define CLS_LOG(__FORMAT__, ...) CLSNSLog((@"%s line %d $ " __FORMAT__), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__);
#else
#define CLS_LOG(__FORMAT__, ...) CLSLog((@"%s line %d $ " __FORMAT__), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__);
#endif
/**
*
* Add logging that will be sent with your crash data. This logging will not show up in the system.log
* and will only be visible in your Crashlytics dashboard.
*
**/
void CLSLog(NSString *format, ...);
/**
*
* Add logging that will be sent with your crash data. This logging will show up in the system.log
* and your Crashlytics dashboard. It is not reccomended for Release builds.
*
**/
void CLSNSLog(NSString *format, ...);
@protocol CrashlyticsDelegate;
@interface Crashlytics : NSObject {
@private
NSString *_apiKey;
NSString *_dataDirectory;
NSString *_bundleIdentifier;
BOOL _installed;
NSMutableDictionary *_customAttributes;
id _user;
NSInteger _sendButtonIndex;
NSInteger _alwaysSendButtonIndex;
NSObject <CrashlyticsDelegate> *_delegate;
}
@property (nonatomic, readonly, copy) NSString *apiKey;
@property (nonatomic, readonly, copy) NSString *version;
@property (nonatomic, assign) BOOL debugMode;
@property (nonatomic, assign) NSObject <CrashlyticsDelegate> *delegate;
/**
*
* The recommended way to install Crashlytics into your application is to place a call
* to +startWithAPIKey: in your -application:didFinishLaunchingWithOptions: method.
*
* This delay defaults to 1 second in order to generally give the application time to
* fully finish launching.
*
**/
+ (Crashlytics *)startWithAPIKey:(NSString *)apiKey;
+ (Crashlytics *)startWithAPIKey:(NSString *)apiKey afterDelay:(NSTimeInterval)delay;
/**
*
* If you need the functionality provided by the CrashlyticsDelegate protocol, you can use
* these convenience methods to activate the framework and set the delegate in one call.
*
**/
+ (Crashlytics *)startWithAPIKey:(NSString *)apiKey delegate:(NSObject <CrashlyticsDelegate> *)delegate;
+ (Crashlytics *)startWithAPIKey:(NSString *)apiKey delegate:(NSObject <CrashlyticsDelegate> *)delegate afterDelay:(NSTimeInterval)delay;
/**
*
* Access the singleton Crashlytics instance.
*
**/
+ (Crashlytics *)sharedInstance;
/**
*
* The easiest way to cause a crash - great for testing!
*
**/
- (void)crash;
/**
*
* Many of our customers have requested the ability to tie crashes to specific end-users of their
* application in order to facilitate responses to support requests or permit the ability to reach
* out for more information. We allow you to specify up to three separate values for display within
* the Crashlytics UI - but please be mindful of your end-user's privacy.
*
* We recommend specifying a user identifier - an arbitrary string that ties an end-user to a record
* in your system. This could be a database id, hash, or other value that is meaningless to a
* third-party observer but can be indexed and queried by you.
*
* Optionally, you may also specify the end-user's name or username, as well as email address if you
* do not have a system that works well with obscured identifiers.
*
* Pursuant to our EULA, this data is transferred securely throughout our system and we will not
* disseminate end-user data unless required to by law. That said, if you choose to provide end-user
* contact information, we strongly recommend that you disclose this in your application's privacy
* policy. Data privacy is of our utmost concern.
*
**/
- (void)setUserIdentifier:(NSString *)identifier;
- (void)setUserName:(NSString *)name;
- (void)setUserEmail:(NSString *)email;
+ (void)setUserIdentifier:(NSString *)identifier;
+ (void)setUserName:(NSString *)name;
+ (void)setUserEmail:(NSString *)email;
/**
*
* Set a value for a key to be associated with your crash data.
*
**/
- (void)setObjectValue:(id)value forKey:(NSString *)key;
- (void)setIntValue:(int)value forKey:(NSString *)key;
- (void)setBoolValue:(BOOL)value forKey:(NSString *)key;
- (void)setFloatValue:(float)value forKey:(NSString *)key;
+ (void)setObjectValue:(id)value forKey:(NSString *)key;
+ (void)setIntValue:(int)value forKey:(NSString *)key;
+ (void)setBoolValue:(BOOL)value forKey:(NSString *)key;
+ (void)setFloatValue:(float)value forKey:(NSString *)key;
@end
/**
*
* The CrashlyticsDelegate protocol provides a mechanism for your application to take
* action on events that occur in the Crashlytics crash reporting system. You can make
* use of these calls by assigning an object to the Crashlytics' delegate property directly,
* or through the convenience startWithAPIKey:delegate:... methods.
*
**/
@protocol CrashlyticsDelegate <NSObject>
@optional
/**
*
* Called once a Crashlytics instance has determined that the last execution of the
* application ended in a crash. This is called some time after the crash reporting
* process has begun. If you have specified a delay in one of the
* startWithAPIKey:... calls, this will take at least that long to be invoked.
*
**/
- (void)crashlyticsDidDetectCrashDuringPreviousExecution:(Crashlytics *)crashlytics;
@end

View File

@@ -0,0 +1,54 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>BuildMachineOSBuild</key>
<string>11C74</string>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleExecutable</key>
<string>Crashlytics</string>
<key>CFBundleIdentifier</key>
<string>com.crashlytics.ios</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>Crashlytics</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.1.2</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleSupportedPlatforms</key>
<array>
<string>iPhoneOS</string>
</array>
<key>CFBundleVersion</key>
<string>0101.02.00</string>
<key>CrashlyticsAPIKey</key>
<string>0d10c90776f5ef5acd01ddbeaca9a6cba4814560</string>
<key>DTCompiler</key>
<string>com.apple.compilers.llvm.clang.1_0</string>
<key>DTPlatformBuild</key>
<string>8H7</string>
<key>DTPlatformName</key>
<string>iphoneos</string>
<key>DTPlatformVersion</key>
<string>4.3</string>
<key>DTSDKBuild</key>
<string>8H7</string>
<key>DTSDKName</key>
<string>iphoneos4.3</string>
<key>DTXcode</key>
<string>0410</string>
<key>DTXcodeBuild</key>
<string>4B110</string>
<key>MinimumOSVersion</key>
<string>3.1</string>
<key>UIDeviceFamily</key>
<array>
<integer>1</integer>
</array>
</dict>
</plist>

View File

@@ -0,0 +1 @@
../../../run

View File

@@ -0,0 +1,15 @@
#!/usr/bin/ruby
#
# WARNING: DO NOT MODIFY THIS FILE.
#
# Crashlytics
# Crashlytics Version: 1.0.0.1
#
# Copyright Crashlytics, Inc. 2012. All rights reserved.
#
require 'pathname'
path = Pathname.new(__FILE__).parent
`#{path}/../../../run`

View File

@@ -0,0 +1 @@
A

BIN
Crashlytics/Crashlytics.framework/run vendored Executable file

Binary file not shown.

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>API Key</key>
<string></string>
</dict>
</plist>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

1
External/FontReplacer vendored Submodule

Submodule External/FontReplacer added at 4e3dea0870

2
External/Pearl vendored

1
External/iCloudStoreManager vendored Submodule

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Key.development</key>
<string>e6238ceba8ec92832e77b1b-9ccd60bc-c39b-11e0-06e4-007f58cb3154</string>
<key>Key.distribution</key>
<string></string>
</dict>
</plist>

View File

@@ -0,0 +1,57 @@
//
// LocalyticsDatabase.h
// LocalyticsDemo
//
// Created by jkaufman on 5/26/11.
// Copyright 2011 Localytics. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <sqlite3.h>
#define MAX_DATABASE_SIZE 500000 // The maximum allowed disk size of the primary database file at open, in bytes
#define VACUUM_THRESHOLD 0.8 // The database is vacuumed after its size exceeds this proportion of the maximum.
@interface LocalyticsDatabase : NSObject {
sqlite3 *_databaseConnection;
}
+ (LocalyticsDatabase *)sharedLocalyticsDatabase;
- (NSUInteger)databaseSize;
- (int)eventCount;
- (NSTimeInterval)createdTimestamp;
- (BOOL)beginTransaction:(NSString *)name;
- (BOOL)releaseTransaction:(NSString *)name;
- (BOOL)rollbackTransaction:(NSString *)name;
- (BOOL)incrementLastUploadNumber:(int *)uploadNumber;
- (BOOL)incrementLastSessionNumber:(int *)sessionNumber;
- (BOOL)addEventWithBlobString:(NSString *)blob;
- (BOOL)addCloseEventWithBlobString:(NSString *)blob;
- (BOOL)addFlowEventWithBlobString:(NSString *)blob;
- (BOOL)removeLastCloseAndFlowEvents;
- (BOOL)addHeaderWithSequenceNumber:(int)number blobString:(NSString *)blob rowId:(sqlite3_int64 *)insertedRowId;
- (int)unstagedEventCount;
- (BOOL)stageEventsForUpload:(sqlite3_int64)headerId;
- (BOOL)updateAppKey:(NSString *)appKey;
- (NSString *)uploadBlobString;
- (BOOL)deleteUploadedData;
- (BOOL)resetAnalyticsData;
- (BOOL)vacuumIfRequired;
- (NSTimeInterval)lastSessionStartTimestamp;
- (BOOL)setLastsessionStartTimestamp:(NSTimeInterval)timestamp;
- (BOOL)isOptedOut;
- (BOOL)setOptedOut:(BOOL)optOut;
- (NSString *)installId;
- (NSString *)appKey; // Most recent app key-- may not be that used to open the session.
- (NSString *)customDimension:(int)dimension;
- (BOOL)setCustomDimension:(int)dimension value:(NSString *)value;
@end

View File

@@ -0,0 +1,743 @@
//
// LocalyticsDatabase.m
// LocalyticsDemo
//
// Created by jkaufman on 5/26/11.
// Copyright 2011 Localytics. All rights reserved.
//
#import "LocalyticsDatabase.h"
#define LOCALYTICS_DIR @".localytics" // Name for the directory in which Localytics database is stored
#define LOCALYTICS_DB @"localytics" // File name for the database (without extension)
#define BUSY_TIMEOUT 30 // Maximum time SQlite will busy-wait for the database to unlock before returning SQLITE_BUSY
@interface LocalyticsDatabase ()
- (int)schemaVersion;
- (void)createSchema;
- (void)upgradeToSchemaV2;
- (void)upgradeToSchemaV3;
- (void)moveDbToCaches;
- (NSString *)randomUUID;
@end
@implementation LocalyticsDatabase
// The singleton database object.
static LocalyticsDatabase *_sharedLocalyticsDatabase = nil;
+ (NSString *)localyticsDirectoryPath {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
return [[paths objectAtIndex:0] stringByAppendingPathComponent:LOCALYTICS_DIR];
}
+ (NSString *)localyticsDatabasePath {
NSString *path = [[LocalyticsDatabase localyticsDirectoryPath] stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.sqlite", LOCALYTICS_DB]];
return path;
}
#pragma mark Singleton Class
+ (LocalyticsDatabase *)sharedLocalyticsDatabase {
@synchronized(self) {
if (_sharedLocalyticsDatabase == nil) {
_sharedLocalyticsDatabase = [[self alloc] init];
}
}
return _sharedLocalyticsDatabase;
}
- (LocalyticsDatabase *)init {
if((self = [super init])) {
// Mover any data that a previous library may have left in the documents directory
[self moveDbToCaches];
// Create directory structure for Localytics.
NSString *directoryPath = [LocalyticsDatabase localyticsDirectoryPath];
if (![[NSFileManager defaultManager] fileExistsAtPath:directoryPath]) {
[[NSFileManager defaultManager] createDirectoryAtPath:directoryPath withIntermediateDirectories:YES attributes:nil error:nil];
}
// Attempt to open database. It will be created if it does not exist, already.
NSString *dbPath = [LocalyticsDatabase localyticsDatabasePath];
int code = sqlite3_open([dbPath UTF8String], &_databaseConnection);
// If we were unable to open the database, it is likely corrupted. Clobber it and move on.
if (code != SQLITE_OK) {
[[NSFileManager defaultManager] removeItemAtPath:dbPath error:nil];
code = sqlite3_open([dbPath UTF8String], &_databaseConnection);
}
// Check db connection, creating schema if necessary.
if (code == SQLITE_OK) {
sqlite3_busy_timeout(_databaseConnection, BUSY_TIMEOUT); // Defaults to 0, otherwise.
if ([self schemaVersion] == 0) {
[self createSchema];
}
}
// Perform any Migrations if necessary
if ([self schemaVersion] < 2) {
[self upgradeToSchemaV2];
}
if ([self schemaVersion] < 3) {
[self upgradeToSchemaV3];
}
}
return self;
}
#pragma mark - Database
- (BOOL)beginTransaction:(NSString *)name {
const char *sql = [[NSString stringWithFormat:@"SAVEPOINT %@", name] cStringUsingEncoding:NSUTF8StringEncoding];
int code = sqlite3_exec(_databaseConnection, sql, NULL, NULL, NULL);
return code == SQLITE_OK;
}
- (BOOL)releaseTransaction:(NSString *)name {
const char *sql = [[NSString stringWithFormat:@"RELEASE SAVEPOINT %@", name] cStringUsingEncoding:NSUTF8StringEncoding];
int code = sqlite3_exec(_databaseConnection, sql, NULL, NULL, NULL);
return code == SQLITE_OK;
}
- (BOOL)rollbackTransaction:(NSString *)name {
const char *sql = [[NSString stringWithFormat:@"ROLLBACK SAVEPOINT %@", name] cStringUsingEncoding:NSUTF8StringEncoding];
int code = sqlite3_exec(_databaseConnection, sql, NULL, NULL, NULL);
return code == SQLITE_OK;
}
- (int)schemaVersion {
int version = 0;
const char *sql = "SELECT MAX(schema_version) FROM localytics_info";
sqlite3_stmt *selectSchemaVersion;
if(sqlite3_prepare_v2(_databaseConnection, sql, -1, &selectSchemaVersion, NULL) == SQLITE_OK) {
if(sqlite3_step(selectSchemaVersion) == SQLITE_ROW) {
version = sqlite3_column_int(selectSchemaVersion, 0);
}
}
sqlite3_finalize(selectSchemaVersion);
return version;
}
- (NSString *)installId {
NSString *installId = nil;
sqlite3_stmt *selectInstallId;
sqlite3_prepare_v2(_databaseConnection, "SELECT install_id FROM localytics_info", -1, &selectInstallId, NULL);
int code = sqlite3_step(selectInstallId);
if (code == SQLITE_ROW && sqlite3_column_text(selectInstallId, 0)) {
installId = [NSString stringWithUTF8String:(char *)sqlite3_column_text(selectInstallId, 0)];
}
sqlite3_finalize(selectInstallId);
return installId;
}
- (NSString *)appKey {
NSString *appKey = nil;
sqlite3_stmt *selectAppKey;
sqlite3_prepare_v2(_databaseConnection, "SELECT app_key FROM localytics_info", -1, &selectAppKey, NULL);
int code = sqlite3_step(selectAppKey);
if (code == SQLITE_ROW && sqlite3_column_text(selectAppKey, 0)) {
appKey = [NSString stringWithUTF8String:(char *)sqlite3_column_text(selectAppKey, 0)];
}
sqlite3_finalize(selectAppKey);
return appKey;
}
// Due to the new iOS storage guidelines it is necessary to move the database out of the documents directory
// and into the /library/caches directory
- (void)moveDbToCaches {
NSArray *documentPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *localyticsDocumentsDirectory = [[documentPaths objectAtIndex:0] stringByAppendingPathComponent:LOCALYTICS_DIR];
NSArray *cachesPaths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
NSString *localyticsCachesDirectory = [[cachesPaths objectAtIndex:0] stringByAppendingPathComponent:LOCALYTICS_DIR];
// If the old directory doesn't exist, there is nothing else to do here
if([[NSFileManager defaultManager] fileExistsAtPath:localyticsDocumentsDirectory] == NO)
{
return;
}
// Try to move the directory
if(NO == [[NSFileManager defaultManager] moveItemAtPath:localyticsDocumentsDirectory
toPath:localyticsCachesDirectory
error:nil])
{
// If the move failed try and, delete the old directory
[ [NSFileManager defaultManager] removeItemAtPath:localyticsDocumentsDirectory error:nil];
}
}
- (void)createSchema {
int code = SQLITE_OK;
// Execute schema creation within a single transaction.
code = sqlite3_exec(_databaseConnection, "BEGIN", NULL, NULL, NULL);
if (code == SQLITE_OK) {
code = sqlite3_exec(_databaseConnection,
"CREATE TABLE upload_headers ("
"sequence_number INTEGER PRIMARY KEY, "
"blob_string TEXT)",
NULL, NULL, NULL);
}
if (code == SQLITE_OK) {
code = sqlite3_exec(_databaseConnection,
"CREATE TABLE events ("
"event_id INTEGER PRIMARY KEY AUTOINCREMENT, " // In case foreign key constraints are reintroduced.
"upload_header INTEGER, "
"blob_string TEXT NOT NULL)",
NULL, NULL, NULL);
}
if (code == SQLITE_OK) {
code = sqlite3_exec(_databaseConnection,
"CREATE TABLE localytics_info ("
"schema_version INTEGER PRIMARY KEY, "
"last_upload_number INTEGER, "
"last_session_number INTEGER, "
"opt_out BOOLEAN, "
"last_close_event INTEGER, "
"last_flow_event INTEGER, "
"last_session_start REAL, "
"app_key CHAR(64), "
"custom_d0 CHAR(64), "
"custom_d1 CHAR(64), "
"custom_d2 CHAR(64), "
"custom_d3 CHAR(64) "
")",
NULL, NULL, NULL);
}
if (code == SQLITE_OK) {
code = sqlite3_exec(_databaseConnection,
"INSERT INTO localytics_info (schema_version, last_upload_number, last_session_number, opt_out) "
"VALUES (3, 0, 0, 0)", NULL, NULL, NULL);
}
// Commit transaction.
if (code == SQLITE_OK || code == SQLITE_DONE) {
sqlite3_exec(_databaseConnection, "COMMIT", NULL, NULL, NULL);
} else {
sqlite3_exec(_databaseConnection, "ROLLBACK", NULL, NULL, NULL);
}
}
// V2 adds a unique identifier for each installation
// This identifier has been moved to user preferences so the database an live in the caches directory
// Also adds storage for custom dimensions
- (void)upgradeToSchemaV2 {
int code = SQLITE_OK;
code = sqlite3_exec(_databaseConnection, "BEGIN", NULL, NULL, NULL);
if (code == SQLITE_OK) {
code = sqlite3_exec(_databaseConnection,
"ALTER TABLE localytics_info ADD install_id CHAR(40)",
NULL, NULL, NULL);
}
if (code == SQLITE_OK) {
code = sqlite3_exec(_databaseConnection,
"ALTER TABLE localytics_info ADD custom_d0 CHAR(64)",
NULL, NULL, NULL);
}
if (code == SQLITE_OK) {
code = sqlite3_exec(_databaseConnection,
"ALTER TABLE localytics_info ADD custom_d1 CHAR(64)",
NULL, NULL, NULL);
}
if (code == SQLITE_OK) {
code = sqlite3_exec(_databaseConnection,
"ALTER TABLE localytics_info ADD custom_d2 CHAR(64)",
NULL, NULL, NULL);
}
if (code == SQLITE_OK) {
sqlite3_exec(_databaseConnection,
"ALTER TABLE localytics_info ADD custom_d3 CHAR(64)",
NULL, NULL, NULL);
}
// Attempt to set schema version and install_id regardless of the result code following the ALTER statements above.
// This is necessary because a previous version of the library performed the migration without setting these values.
// The transaction will succeed even if the individual statements fail with errors (eg. "duplicate column name").
sqlite3_stmt *updateLocalyticsInfo;
sqlite3_prepare_v2(_databaseConnection, "UPDATE localytics_info set install_id = ?, schema_version = 2 ", -1, &updateLocalyticsInfo, NULL);
sqlite3_bind_text (updateLocalyticsInfo, 1, [[self randomUUID] UTF8String], -1, SQLITE_TRANSIENT);
code = sqlite3_step(updateLocalyticsInfo);
sqlite3_finalize(updateLocalyticsInfo);
// Commit transaction.
if (code == SQLITE_OK || code == SQLITE_DONE) {
sqlite3_exec(_databaseConnection, "COMMIT", NULL, NULL, NULL);
} else {
sqlite3_exec(_databaseConnection, "ROLLBACK", NULL, NULL, NULL);
}
}
// V3 adds a field for the last app key and patches a V2 migration issue.
- (void)upgradeToSchemaV3 {
sqlite3_exec(_databaseConnection,
"ALTER TABLE localytics_info ADD app_key CHAR(64)",
NULL, NULL, NULL);
}
- (NSUInteger)databaseSize {
NSUInteger size = 0;
NSDictionary *fileAttributes = [[NSFileManager defaultManager]
attributesOfItemAtPath:[LocalyticsDatabase localyticsDatabasePath]
error:nil];
size = [fileAttributes fileSize];
return size;
}
- (int) eventCount {
int count = 0;
const char *sql = "SELECT count(*) FROM events";
sqlite3_stmt *selectEventCount;
if(sqlite3_prepare_v2(_databaseConnection, sql, -1, &selectEventCount, NULL) == SQLITE_OK)
{
if(sqlite3_step(selectEventCount) == SQLITE_ROW) {
count = sqlite3_column_int(selectEventCount, 0);
}
}
sqlite3_finalize(selectEventCount);
return count;
}
- (NSTimeInterval)createdTimestamp {
NSTimeInterval timestamp = 0;
NSDictionary *fileAttributes = [[NSFileManager defaultManager]
attributesOfItemAtPath:[LocalyticsDatabase localyticsDatabasePath]
error:nil];
timestamp = [[fileAttributes fileCreationDate] timeIntervalSince1970];
return timestamp;
}
- (NSTimeInterval)lastSessionStartTimestamp {
NSTimeInterval lastSessionStart = 0;
sqlite3_stmt *selectLastSessionStart;
sqlite3_prepare_v2(_databaseConnection, "SELECT last_session_start FROM localytics_info", -1, &selectLastSessionStart, NULL);
int code = sqlite3_step(selectLastSessionStart);
if (code == SQLITE_ROW) {
lastSessionStart = sqlite3_column_double(selectLastSessionStart, 0) == 1;
}
sqlite3_finalize(selectLastSessionStart);
return lastSessionStart;
}
- (BOOL)setLastsessionStartTimestamp:(NSTimeInterval)timestamp {
sqlite3_stmt *updateLastSessionStart;
sqlite3_prepare_v2(_databaseConnection, "UPDATE localytics_info SET last_session_start = ?", -1, &updateLastSessionStart, NULL);
sqlite3_bind_double(updateLastSessionStart, 1, timestamp);
int code = sqlite3_step(updateLastSessionStart);
sqlite3_finalize(updateLastSessionStart);
return code == SQLITE_DONE;
}
- (BOOL)isOptedOut {
BOOL optedOut = NO;
sqlite3_stmt *selectOptOut;
sqlite3_prepare_v2(_databaseConnection, "SELECT opt_out FROM localytics_info", -1, &selectOptOut, NULL);
int code = sqlite3_step(selectOptOut);
if (code == SQLITE_ROW) {
optedOut = sqlite3_column_int(selectOptOut, 0) == 1;
}
sqlite3_finalize(selectOptOut);
return optedOut;
}
- (BOOL)setOptedOut:(BOOL)optOut {
sqlite3_stmt *updateOptedOut;
sqlite3_prepare_v2(_databaseConnection, "UPDATE localytics_info SET opt_out = ?", -1, &updateOptedOut, NULL);
sqlite3_bind_int(updateOptedOut, 1, optOut);
int code = sqlite3_step(updateOptedOut);
sqlite3_finalize(updateOptedOut);
return code == SQLITE_OK;
}
- (NSString *)customDimension:(int)dimension {
if(dimension < 0 || dimension > 3) {
return nil;
}
NSString *value = nil;
NSString *query = [NSString stringWithFormat:@"select custom_d%i from localytics_info", dimension];
sqlite3_stmt *selectCustomDim;
sqlite3_prepare_v2(_databaseConnection, [query UTF8String], -1, &selectCustomDim, NULL);
int code = sqlite3_step(selectCustomDim);
if (code == SQLITE_ROW && sqlite3_column_text(selectCustomDim, 0)) {
value = [NSString stringWithUTF8String:(char *)sqlite3_column_text(selectCustomDim, 0)];
}
sqlite3_finalize(selectCustomDim);
return value;
}
- (BOOL)setCustomDimension:(int)dimension value:(NSString *)value {
if(dimension < 0 || dimension > 3) {
return false;
}
NSString *query = [NSString stringWithFormat:@"update localytics_info SET custom_d%i = %@",
dimension,
(value == nil) ? @"null" : [NSString stringWithFormat:@"\"%@\"", value]];
int code = sqlite3_exec(_databaseConnection, [query UTF8String], NULL, NULL, NULL);
return code == SQLITE_OK;
}
- (BOOL)incrementLastUploadNumber:(int *)uploadNumber {
NSString *t = @"increment_upload_number";
int code = SQLITE_OK;
code = [self beginTransaction:t] ? SQLITE_OK : SQLITE_ERROR;
if(code == SQLITE_OK) {
// Increment value
code = sqlite3_exec(_databaseConnection,
"UPDATE localytics_info "
"SET last_upload_number = (last_upload_number + 1)",
NULL, NULL, NULL);
}
if(code == SQLITE_OK) {
// Retrieve new value
sqlite3_stmt *selectUploadNumber;
sqlite3_prepare_v2(_databaseConnection,
"SELECT last_upload_number FROM localytics_info",
-1, &selectUploadNumber, NULL);
code = sqlite3_step(selectUploadNumber);
if (code == SQLITE_ROW) {
*uploadNumber = sqlite3_column_int(selectUploadNumber, 0);
}
sqlite3_finalize(selectUploadNumber);
}
if(code == SQLITE_ROW) {
[self releaseTransaction:t];
} else {
[self rollbackTransaction:t];
}
return code == SQLITE_ROW;
}
- (BOOL)incrementLastSessionNumber:(int *)sessionNumber {
NSString *t = @"increment_session_number";
int code = [self beginTransaction:t] ? SQLITE_OK : SQLITE_ERROR;
if(code == SQLITE_OK) {
// Increment value
code = sqlite3_exec(_databaseConnection,
"UPDATE localytics_info "
"SET last_session_number = (last_session_number + 1)",
NULL, NULL, NULL);
}
if(code == SQLITE_OK) {
// Retrieve new value
sqlite3_stmt *selectSessionNumber;
sqlite3_prepare_v2(_databaseConnection,
"SELECT last_session_number FROM localytics_info",
-1, &selectSessionNumber, NULL);
code = sqlite3_step(selectSessionNumber);
if (code == SQLITE_ROW && sessionNumber != NULL) {
*sessionNumber = sqlite3_column_int(selectSessionNumber, 0);
}
sqlite3_finalize(selectSessionNumber);
}
if(code == SQLITE_ROW) {
[self releaseTransaction:t];
} else {
[self rollbackTransaction:t];
}
return code == SQLITE_ROW;
}
- (BOOL)addEventWithBlobString:(NSString *)blob {
int code = SQLITE_OK;
sqlite3_stmt *insertEvent;
sqlite3_prepare_v2(_databaseConnection, "INSERT INTO events (blob_string) VALUES (?)", -1, &insertEvent, NULL);
sqlite3_bind_text(insertEvent, 1, [blob UTF8String], -1, SQLITE_TRANSIENT);
code = sqlite3_step(insertEvent);
sqlite3_finalize(insertEvent);
return code == SQLITE_DONE;
}
- (BOOL)addCloseEventWithBlobString:(NSString *)blob {
NSString *t = @"add_close_event";
BOOL success = [self beginTransaction:t];
// Add close event.
if (success) {
success = [self addEventWithBlobString:blob];
}
// Record row id to localytics_info so that it can be removed if the session resumes.
if (success) {
sqlite3_stmt *updateCloseEvent;
sqlite3_prepare_v2(_databaseConnection, "UPDATE localytics_info SET last_close_event = (SELECT event_id FROM events WHERE rowid = ?)", -1, &updateCloseEvent, NULL);
sqlite3_int64 lastRow = sqlite3_last_insert_rowid(_databaseConnection);
sqlite3_bind_int64(updateCloseEvent, 1, lastRow);
int code = sqlite3_step(updateCloseEvent);
sqlite3_finalize(updateCloseEvent);
success = code == SQLITE_DONE;
}
if (success) {
[self releaseTransaction:t];
} else {
[self rollbackTransaction:t];
}
return success;
}
- (BOOL)addFlowEventWithBlobString:(NSString *)blob {
NSString *t = @"add_flow_event";
BOOL success = [self beginTransaction:t];
// Add flow event.
if (success) {
success = [self addEventWithBlobString:blob];
}
// Record row id to localytics_info so that it can be removed if the session resumes.
if (success) {
sqlite3_stmt *updateFlowEvent;
sqlite3_prepare_v2(_databaseConnection, "UPDATE localytics_info SET last_flow_event = (SELECT event_id FROM events WHERE rowid = ?)", -1, &updateFlowEvent, NULL);
sqlite3_int64 lastRow = sqlite3_last_insert_rowid(_databaseConnection);
sqlite3_bind_int64(updateFlowEvent, 1, lastRow);
int code = sqlite3_step(updateFlowEvent);
sqlite3_finalize(updateFlowEvent);
success = code == SQLITE_DONE;
}
if (success) {
[self releaseTransaction:t];
} else {
[self rollbackTransaction:t];
}
return success;
}
- (BOOL)removeLastCloseAndFlowEvents {
// Attempt to remove the last recorded close event.
// Fail quietly if none was saved or it was previously removed.
int code = sqlite3_exec(_databaseConnection, "DELETE FROM events WHERE event_id = (SELECT last_close_event FROM localytics_info) OR event_id = (SELECT last_flow_event FROM localytics_info)", NULL, NULL, NULL);
return code == SQLITE_OK;
}
- (BOOL)addHeaderWithSequenceNumber:(int)number blobString:(NSString *)blob rowId:(sqlite3_int64 *)insertedRowId {
sqlite3_stmt *insertHeader;
sqlite3_prepare_v2(_databaseConnection, "INSERT INTO upload_headers (sequence_number, blob_string) VALUES (?, ?)", -1, &insertHeader, NULL);
sqlite3_bind_int(insertHeader, 1, number);
sqlite3_bind_text(insertHeader, 2, [blob UTF8String], -1, SQLITE_TRANSIENT);
int code = sqlite3_step(insertHeader);
sqlite3_finalize(insertHeader);
if (code == SQLITE_DONE && insertedRowId != NULL) {
*insertedRowId = sqlite3_last_insert_rowid(_databaseConnection);
}
return code == SQLITE_DONE;
}
- (int)unstagedEventCount {
int rowCount = 0;
sqlite3_stmt *selectEventCount;
sqlite3_prepare_v2(_databaseConnection, "SELECT COUNT(*) FROM events WHERE UPLOAD_HEADER IS NULL", -1, &selectEventCount, NULL);
int code = sqlite3_step(selectEventCount);
if (code == SQLITE_ROW) {
rowCount = sqlite3_column_int(selectEventCount, 0);
}
sqlite3_finalize(selectEventCount);
return rowCount;
}
- (BOOL)stageEventsForUpload:(sqlite3_int64)headerId {
// Associate all outstanding events with the given upload header ID.
NSString *stageEvents = [NSString stringWithFormat:@"UPDATE events SET upload_header = ? WHERE upload_header IS NULL"];
sqlite3_stmt *updateEvents;
sqlite3_prepare_v2(_databaseConnection, [stageEvents UTF8String], -1, &updateEvents, NULL);
sqlite3_bind_int(updateEvents, 1, headerId);
int code = sqlite3_step(updateEvents);
sqlite3_finalize(updateEvents);
BOOL success = (code == SQLITE_DONE);
return success;
}
- (BOOL)updateAppKey:(NSString *)appKey {
sqlite3_stmt *updateAppKey;
sqlite3_prepare_v2(_databaseConnection, "UPDATE localytics_info set app_key = ?", -1, &updateAppKey, NULL);
sqlite3_bind_text (updateAppKey, 1, [appKey UTF8String], -1, SQLITE_TRANSIENT);
int code = sqlite3_step(updateAppKey);
sqlite3_finalize(updateAppKey);
BOOL success = (code == SQLITE_DONE);
return success;
}
- (NSString *)uploadBlobString {
// Retrieve the blob strings of each upload header and its child events, in order.
const char *sql = "SELECT * FROM ( "
" SELECT h.blob_string AS 'blob', h.sequence_number as 'seq', 0 FROM upload_headers h"
" UNION ALL "
" SELECT e.blob_string AS 'blob', e.upload_header as 'seq', 1 FROM events e"
") "
"ORDER BY 2, 3";
sqlite3_stmt *selectBlobs;
sqlite3_prepare_v2(_databaseConnection, sql, -1, &selectBlobs, NULL);
NSMutableString *uploadBlobString = [NSMutableString string];
while (sqlite3_step(selectBlobs) == SQLITE_ROW) {
const char *blob = (const char *)sqlite3_column_text(selectBlobs, 0);
if (blob != NULL) {
NSString *blobString = [[NSString alloc] initWithCString:blob encoding:NSUTF8StringEncoding];
[uploadBlobString appendString:blobString];
[blobString release];
}
}
sqlite3_finalize(selectBlobs);
return [[uploadBlobString copy] autorelease];
}
- (BOOL)deleteUploadedData {
// Delete all headers and staged events.
NSString *t = @"delete_upload_data";
int code = [self beginTransaction:t] ? SQLITE_OK : SQLITE_ERROR;
if (code == SQLITE_OK) {
code = sqlite3_exec(_databaseConnection, "DELETE FROM events WHERE upload_header IS NOT NULL", NULL, NULL, NULL);
}
if (code == SQLITE_OK) {
code = sqlite3_exec(_databaseConnection, "DELETE FROM upload_headers", NULL, NULL, NULL);
}
if (code == SQLITE_OK) {
[self releaseTransaction:t];
} else {
[self rollbackTransaction:t];
}
return code == SQLITE_OK;
}
- (BOOL)resetAnalyticsData {
// Delete or zero all analytics data.
// Reset: headers, events, session number, upload number, last session start, last close event, and last flow event.
// Unaffected: schema version, opt out status, install ID (deprecated), and app key.
NSString *t = @"reset_analytics_data";
int code = [self beginTransaction:t] ? SQLITE_OK : SQLITE_ERROR;
if (code == SQLITE_OK) {
code = sqlite3_exec(_databaseConnection, "DELETE FROM events", NULL, NULL, NULL);
}
if (code == SQLITE_OK) {
code = sqlite3_exec(_databaseConnection, "DELETE FROM upload_headers", NULL, NULL, NULL);
}
if (code == SQLITE_OK) {
code = sqlite3_exec(_databaseConnection,"UPDATE localytics_info SET last_session_number = 0, last_upload_number = 0,"
"last_close_event = null, last_flow_event = null, last_session_start = null, "
"custom_d0 = null, custom_d1 = null, custom_d2 = null, custom_d3 = null",
NULL, NULL, NULL);
}
if (code == SQLITE_OK) {
[self releaseTransaction:t];
} else {
[self rollbackTransaction:t];
}
return code == SQLITE_OK;
}
- (BOOL)vacuumIfRequired {
int code = SQLITE_OK;
if ([self databaseSize] > MAX_DATABASE_SIZE * VACUUM_THRESHOLD) {
code = sqlite3_exec(_databaseConnection, "VACUUM", NULL, NULL, NULL);
}
return code == SQLITE_OK;
}
- (NSString *)randomUUID {
CFUUIDRef theUUID = CFUUIDCreate(NULL);
CFStringRef stringUUID = CFUUIDCreateString(NULL, theUUID);
CFRelease(theUUID);
return [(NSString *)stringUUID autorelease];
}
#pragma mark - Lifecycle
+ (id)allocWithZone:(NSZone *)zone {
@synchronized(self) {
if (_sharedLocalyticsDatabase == nil) {
_sharedLocalyticsDatabase = [super allocWithZone:zone];
return _sharedLocalyticsDatabase;
}
}
// returns nil on subsequent allocations
return nil;
}
- (id)copyWithZone:(NSZone *)zone {
return self;
}
- (id)retain {
return self;
}
- (unsigned)retainCount {
// maximum value of an unsigned int - prevents additional retains for the class
return UINT_MAX;
}
- (oneway void)release {
// ignore release commands
}
- (id)autorelease {
return self;
}
- (void)dealloc {
sqlite3_close(_databaseConnection);
[super dealloc];
}
@end

View File

@@ -0,0 +1,216 @@
// LocalyticsSession.h
// Copyright (C) 2009 Char Software Inc., DBA Localytics
//
// This code is provided under the Localytics Modified BSD License.
// A copy of this license has been distributed in a file called LICENSE
// with this source code.
//
// Please visit www.localytics.com for more information.
#import <UIKit/UIKit.h>
// Set this to true to enable localytics traces (useful for debugging)
#define DO_LOCALYTICS_LOGGING false
/*!
@class LocalyticsSession
@discussion The class which manages creating, collecting, & uploading a Localytics session.
Please see the following guides for information on how to best use this
library, sample code, and other useful information:
<ul>
<li><a href="http://wiki.localytics.com/index.php?title=Developer's_Integration_Guide">Main Developer's Integration Guide</a></li>
</ul>
<strong>Best Practices</strong>
<ul>
<li>Instantiate the LocalyticsSession object in applicationDidFinishLaunching.</li>
<li>Open your session and begin your uploads in applicationDidFinishLaunching. This way the
upload has time to complete and it all happens before your users have a
chance to begin any data intensive actions of their own.</li>
<li>Close the session in applicationWillTerminate, and in applicationDidEnterBackground.</li>
<li>Resume the session in applicationWillEnterForeground.</li>
<li>Do not call any Localytics functions inside a loop. Instead, calls
such as <code>tagEvent</code> should follow user actions. This limits the
amount of data which is stored and uploaded.</li>
<li>Do not use multiple LocalticsSession objects to upload data with
multiple application keys. This can cause invalid state.</li>
</ul>
@author Localytics
*/
@interface LocalyticsSession : NSObject {
BOOL _hasInitialized; // Whether or not the session object has been initialized.
BOOL _isSessionOpen; // Whether or not this session has been opened.
float _backgroundSessionTimeout; // If an App stays in the background for more
// than this many seconds, start a new session
// when it returns to foreground.
@private
#pragma mark Member Variables
dispatch_queue_t _queue; // Queue of Localytics block objects.
dispatch_group_t _criticalGroup; // Group of blocks the must complete before backgrounding.
NSString *_sessionUUID; // Unique identifier for this session.
NSString *_applicationKey; // Unique identifier for the instrumented application
NSTimeInterval _lastSessionStartTimestamp; // The start time of the most recent session.
NSDate *_sessionResumeTime; // Time session was started or resumed.
NSDate *_sessionCloseTime; // Time session was closed.
NSMutableString *_unstagedFlowEvents; // Comma-delimited list of app screens and events tagged during this
// session that have NOT been staged for upload.
NSMutableString *_stagedFlowEvents; // App screens and events tagged during this session that HAVE been staged
// for upload.
NSMutableString *_screens; // Comma-delimited list of screens tagged during this session.
NSTimeInterval _sessionActiveDuration; // Duration that session open.
BOOL _sessionHasBeenOpen; // Whether or not this session has ever been open.
}
@property dispatch_queue_t queue;
@property dispatch_group_t criticalGroup;
@property BOOL isSessionOpen;
@property BOOL hasInitialized;
@property float backgroundSessionTimeout;
#pragma mark Public Methods
/*!
@method sharedLocalyticsSession
@abstract Accesses the Session object. This is a Singleton class which maintains
a single session throughout your application. It is possible to manage your own
session, but this is the easiest way to access the Localytics object throughout your code.
The class is accessed within the code using the following syntax:
[[LocalyticsSession sharedLocalyticsSession] functionHere]
So, to tag an event, all that is necessary, anywhere in the code is:
[[LocalyticsSession sharedLocalyticsSession] tagEvent:@"MY_EVENT"];
*/
+ (LocalyticsSession *)sharedLocalyticsSession;
/*!
@method LocalyticsSession
@abstract Initializes the Localytics Object. Not necessary if you choose to use startSession.
@param applicationKey The key unique for each application generated at www.localytics.com
*/
- (void)LocalyticsSession:(NSString *)appKey;
/*!
@method startSession
@abstract An optional convenience initialize method that also calls the LocalyticsSession, open &
upload methods. Best Practice is to call open & upload immediately after Localytics Session when loading an app,
this method fascilitates that behavior.
It is recommended that this call be placed in <code>applicationDidFinishLaunching</code>.
@param applicationKey The key unique for each application generated
at www.localytics.com
*/
- (void)startSession:(NSString *)appKey;
/*!
@method setOptIn
@abstract (OPTIONAL) Allows the application to control whether or not it will collect user data.
Even if this call is used, it is necessary to continue calling upload(). No new data will be
collected, so nothing new will be uploaded but it is necessary to upload an event telling the
server this user has opted out.
@param optedIn True if the user is opted in, false otherwise.
*/
- (void)setOptIn:(BOOL)optedIn;
/*!
@method isOptedIn
@abstract (OPTIONAL) Whether or not this user has is opted in or out. The only way they can be
opted out is if setOptIn(false) has been called before this. This function should only be
used to pre-populate a checkbox in an options menu. It is not recommended that an application
branch based on Localytics instrumentation because this creates an additional test case. If
the app is opted out, all subsequent Localytics calls will return immediately.
@result true if the user is opted in, false otherwise.
*/
- (BOOL)isOptedIn;
/*!
@method open
@abstract Opens the Localytics session. Not necessary if you choose to use startSession.
The session time as presented on the website is the time between <code>open</code> and the
final <code>close</code> so it is recommended to open the session as early as possible, and close
it at the last moment. The session must be opened before any tags can
be written. It is recommended that this call be placed in <code>applicationDidFinishLaunching</code>.
<br>
If for any reason this is called more than once every subsequent open call
will be ignored.
*/
- (void)open;
/*!
@method resume
@abstract Resumes the Localytics session. When the App enters the background, the session is
closed and the time of closing is recorded. When the app returns to the foreground, the session
is resumed. If the time since closing is greater than BACKGROUND_SESSION_TIMEOUT, (15 seconds
by default) a new session is created, and uploading is triggered. Otherwise, the previous session
is reopened.
*/
- (void)resume;
/*!
@method close
@abstract Closes the Localytics session. This should be called in
<code>applicationWillTerminate</code>.
<br>
If close is not called, the session will still be uploaded but no
events will be processed and the session time will not appear. This is
because the session is not yet closed so it should not be used in
comparison with sessions which are closed.
*/
- (void)close;
/*!
@method tagEvent
@abstract Allows a session to tag a particular event as having occurred. For
example, if a view has three buttons, it might make sense to tag
each button click with the name of the button which was clicked.
For another example, in a game with many levels it might be valuable
to create a new tag every time the user gets to a new level in order
to determine how far the average user is progressing in the game.
<br>
<strong>Tagging Best Practices</strong>
<ul>
<li>DO NOT use tags to record personally identifiable information.</li>
<li>The best way to use tags is to create all the tag strings as predefined
constants and only use those. This is more efficient and removes the risk of
collecting personal information.</li>
<li>Do not set tags inside loops or any other place which gets called
frequently. This can cause a lot of data to be stored and uploaded.</li>
</ul>
<br>
See the tagging guide at: http://wiki.localytics.com/
@param event The name of the event which occurred.
*/
- (void)tagEvent:(NSString *)event;
- (void)tagEvent:(NSString *)event attributes:(NSDictionary *)attributes;
- (void)tagEvent:(NSString *)event attributes:(NSDictionary *)attributes reportAttributes:(NSDictionary *)reportAttributes;
/*!
@method tagScreen
@abstract Allows tagging the flow of screens encountered during the session.
@param screen The name of the screen
*/
- (void)tagScreen:(NSString *)screen;
/*!
@method upload
@abstract Creates a low priority thread which uploads any Localytics data already stored
on the device. This should be done early in the process life in order to
guarantee as much time as possible for slow connections to complete. It is also reasonable
to upload again when the application is exiting because if the upload is cancelled the data
will just get uploaded the next time the app comes up.
*/
- (void)upload;
/*!
@method setCustomDimension
@abstract (ENTERPRISE ONLY) Sets the value of a custom dimension. Custom dimensions are dimensions
which contain user defined data unlike the predefined dimensions such as carrier, model, and country.
Once a value for a custom dimension is set, the device it was set on will continue to upload that value
until the value is changed. To clear a value pass nil as the value.
The proper use of custom dimensions involves defining a dimension with less than ten distinct possible
values and assigning it to one of the four available custom dimensions. Once assigned this definition should
never be changed without changing the App Key otherwise old installs of the application will pollute new data.
*/
- (void)setCustomDimension:(int)dimension value:(NSString *)value;
@end

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,42 @@
// LocalyticsUploader.h
// Copyright (C) 2009 Char Software Inc., DBA Localytics
//
// This code is provided under the Localytics Modified BSD License.
// A copy of this license has been distributed in a file called LICENSE
// with this source code.
//
// Please visit www.localytics.com for more information.
#import <UIKit/UIKit.h>
/*!
@class LocalyticsUploader
@discussion Singleton class to handle data uploads
*/
@interface LocalyticsUploader : NSObject {
}
@property (readonly) BOOL isUploading;
/*!
@method sharedLocalyticsUploader
@abstract Establishes this as a Singleton Class allowing for data persistence.
The class is accessed within the code using the following syntax:
[[LocalyticsUploader sharedLocalyticsUploader] functionHere]
*/
+ (LocalyticsUploader *)sharedLocalyticsUploader;
/*!
@method LocalyticsUploader
@abstract Creates a thread which uploads all queued header and event data.
All files starting with sessionFilePrefix are renamed,
uploaded and deleted on upload. This way the sessions can continue
writing data regardless of whether or not the upload succeeds. Files
which have been renamed still count towards the total number of Localytics
files which can be stored on the disk.
@param localyticsApplicationKey the Localytics application ID
*/
- (void)uploaderWithApplicationKey:(NSString *)localyticsApplicationKey;
@end

View File

@@ -0,0 +1,236 @@
// LocalyticsUploader.m
// Copyright (C) 2009 Char Software Inc., DBA Localytics
//
// This code is provided under the Localytics Modified BSD License.
// A copy of this license has been distributed in a file called LICENSE
// with this source code.
//
// Please visit www.localytics.com for more information.
#import "LocalyticsUploader.h"
#import "LocalyticsSession.h"
#import "LocalyticsDatabase.h"
#import <zlib.h>
#define LOCALYTICS_URL @"http://analytics.localytics.com/api/v2/applications/%@/uploads"
static LocalyticsUploader *_sharedUploader = nil;
@interface LocalyticsUploader ()
- (void)finishUpload;
- (NSData *)gzipDeflatedDataWithData:(NSData *)data;
- (void)logMessage:(NSString *)message;
@property (readwrite) BOOL isUploading;
@end
@implementation LocalyticsUploader
@synthesize isUploading = _isUploading;
#pragma mark - Singleton Class
+ (LocalyticsUploader *)sharedLocalyticsUploader {
@synchronized(self) {
if (_sharedUploader == nil) {
_sharedUploader = [[self alloc] init];
}
}
return _sharedUploader;
}
#pragma mark - Class Methods
- (void)uploaderWithApplicationKey:(NSString *)localyticsApplicationKey {
// Do nothing if already uploading.
if (self.isUploading == true)
{
[self logMessage:@"Upload already in progress. Aborting."];
return;
}
[self logMessage:@"Beginning upload process"];
self.isUploading = true;
// Prepare the data for upload. The upload could take a long time, so some effort has to be made to be sure that events
// which get written while the upload is taking place don't get lost or duplicated. To achieve this, the logic is:
// 1) Append every header row blob string and and those of its associated events to the upload string.
// 2) Deflate and upload the data.
// 3) On success, delete all blob headers and staged events. Events added while an upload is in process are not
// deleted because they are not associated a header (and cannot be until the upload completes).
// Step 1
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
LocalyticsDatabase *db = [LocalyticsDatabase sharedLocalyticsDatabase];
NSString *blobString = [db uploadBlobString];
if ([blobString length] == 0) {
// There is nothing outstanding to upload.
[self logMessage:@"Abandoning upload. There are no new events."];
[pool drain];
[self finishUpload];
return;
}
NSData *requestData = [blobString dataUsingEncoding:NSUTF8StringEncoding];
NSString *myString = [[[NSString alloc] initWithData:requestData encoding:NSUTF8StringEncoding] autorelease];
[self logMessage:[NSString stringWithFormat:@"Uploading data (length: %u)", [myString length]]];
// Step 2
NSData *deflatedRequestData = [[self gzipDeflatedDataWithData:requestData] retain];
[pool drain];
NSString *apiUrlString = [NSString stringWithFormat:LOCALYTICS_URL, [localyticsApplicationKey stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
NSMutableURLRequest *submitRequest = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:apiUrlString]
cachePolicy:NSURLRequestReloadIgnoringCacheData
timeoutInterval:60.0];
[submitRequest setHTTPMethod:@"POST"];
[submitRequest setValue:@"application/x-gzip" forHTTPHeaderField:@"Content-Type"];
[submitRequest setValue:@"gzip" forHTTPHeaderField:@"Content-Encoding"];
[submitRequest setValue:[NSString stringWithFormat:@"%d", [deflatedRequestData length]] forHTTPHeaderField:@"Content-Length"];
[submitRequest setHTTPBody:deflatedRequestData];
[deflatedRequestData release];
// Perform synchronous upload in an async dispatch. This is necessary because the calling block will not persist to
// receive the response data.
dispatch_group_async([[LocalyticsSession sharedLocalyticsSession] criticalGroup], [[LocalyticsSession sharedLocalyticsSession] queue], ^{
@try {
NSURLResponse *response = nil;
NSError *responseError = nil;
[NSURLConnection sendSynchronousRequest:submitRequest returningResponse:&response error:&responseError];
NSInteger responseStatusCode = [(NSHTTPURLResponse *)response statusCode];
if (responseError) {
// On error, simply print the error and close the uploader. We have to assume the data was not transmited
// so it is not deleted. In the event that we accidently store data which was succesfully uploaded, the
// duplicate data will be ignored by the server when it is next uploaded.
[self logMessage:[NSString stringWithFormat:
@"Error Uploading. Code: %d, Description: %@",
[responseError code],
[responseError localizedDescription]]];
} else {
// Step 3
// While response status codes in the 5xx range leave upload rows intact, the default case is to delete.
if (responseStatusCode >= 500 && responseStatusCode < 600) {
[self logMessage:[NSString stringWithFormat:@"Upload failed with response status code %d", responseStatusCode]];
} else {
// Because only one instance of the uploader can be running at a time it should not be possible for
// new upload rows to appear so there is no fear of deleting data which has not yet been uploaded.
[self logMessage:[NSString stringWithFormat:@"Upload completed successfully. Response code %d", responseStatusCode]];
[[LocalyticsDatabase sharedLocalyticsDatabase] deleteUploadedData];
}
}
}
@catch (NSException * e) {}
[self finishUpload];
});
}
- (void)finishUpload
{
self.isUploading = false;
// Upload data has been deleted. Recover the disk space if necessary.
[[LocalyticsDatabase sharedLocalyticsDatabase] vacuumIfRequired];
}
/*!
@method gzipDeflatedDataWithData
@abstract Deflates the provided data using gzip at the default compression level (6). Complete NSData gzip category available on CocoaDev. http://www.cocoadev.com/index.pl?NSDataCategory.
@return the deflated data
*/
- (NSData *)gzipDeflatedDataWithData:(NSData *)data
{
if ([data length] == 0) return data;
z_stream strm;
strm.zalloc = Z_NULL;
strm.zfree = Z_NULL;
strm.opaque = Z_NULL;
strm.total_out = 0;
strm.next_in=(Bytef *)[data bytes];
strm.avail_in = [data length];
// Compresssion Levels:
// Z_NO_COMPRESSION
// Z_BEST_SPEED
// Z_BEST_COMPRESSION
// Z_DEFAULT_COMPRESSION
if (deflateInit2(&strm, Z_DEFAULT_COMPRESSION, Z_DEFLATED, (15+16), 8, Z_DEFAULT_STRATEGY) != Z_OK) return nil;
NSMutableData *compressed = [NSMutableData dataWithLength:16384]; // 16K chunks for expansion
do {
if (strm.total_out >= [compressed length])
[compressed increaseLengthBy: 16384];
strm.next_out = [compressed mutableBytes] + strm.total_out;
strm.avail_out = [compressed length] - strm.total_out;
deflate(&strm, Z_FINISH);
} while (strm.avail_out == 0);
deflateEnd(&strm);
[compressed setLength: strm.total_out];
return [NSData dataWithData:compressed];
}
/*!
@method logMessage
@abstract Logs a message with (localytics uploader) prepended to it
@param message The message to log
*/
- (void) logMessage:(NSString *)message {
if(DO_LOCALYTICS_LOGGING) {
NSLog(@"(localytics uploader) %s\n", [message UTF8String]);
}
}
#pragma mark - System Functions
+ (id)allocWithZone:(NSZone *)zone {
@synchronized(self) {
if (_sharedUploader == nil) {
_sharedUploader = [super allocWithZone:zone];
return _sharedUploader;
}
}
// returns nil on subsequent allocations
return nil;
}
- (id)copyWithZone:(NSZone *)zone {
return self;
}
- (id)retain {
return self;
}
- (unsigned)retainCount {
// maximum value of an unsigned int - prevents additional retains for the class
return UINT_MAX;
}
- (oneway void)release {
// ignore release commands
}
- (id)autorelease {
return self;
}
- (void)dealloc {
[_sharedUploader release];
[super dealloc];
}
@end

View File

@@ -0,0 +1,48 @@
// UploaderThread.h
// Copyright (C) 2009 Char Software Inc., DBA Localytics
//
// This code is provided under the Localytics Modified BSD License.
// A copy of this license has been distributed in a file called LICENSE
// with this source code.
//
// Please visit www.localytics.com for more information.
#import <UIKit/UIKit.h>
/*!
@class UploaderThread
@discussion Singleton class to handle data uploads
*/
@interface UploaderThread : NSObject {
NSURLConnection *_uploadConnection; // The connection which uploads the bits
NSInteger _responseStatusCode; // The HTTP response status code for the current connection
BOOL _isUploading; // A flag to gaurantee only one uploader instance can happen at once
}
@property (nonatomic, retain) NSURLConnection *uploadConnection;
@property BOOL isUploading;
/*!
@method sharedUploaderThread
@abstract Establishes this as a Singleton Class allowing for data persistence.
The class is accessed within the code using the following syntax:
[[UploaderThread sharedUploaderThread] functionHere]
*/
+ (UploaderThread *)sharedUploaderThread;
/*!
@method UploaderThread
@abstract Creates a thread which uploads all queued header and event data.
All files starting with sessionFilePrefix are renamed,
uploaded and deleted on upload. This way the sessions can continue
writing data regardless of whether or not the upload succeeds. Files
which have been renamed still count towards the total number of Localytics
files which can be stored on the disk.
@param localyticsApplicationKey the Localytics application ID
*/
- (void)uploaderThreadwithApplicationKey:(NSString *)localyticsApplicationKey;
@end

260
Localytics/UploaderThread.m Normal file
View File

@@ -0,0 +1,260 @@
// UploaderThread.m
// Copyright (C) 2009 Char Software Inc., DBA Localytics
//
// This code is provided under the Localytics Modified BSD License.
// A copy of this license has been distributed in a file called LICENSE
// with this source code.
//
// Please visit www.localytics.com for more information.
#import "UploaderThread.h"
#import "LocalyticsSession.h"
#import "LocalyticsDatabase.h"
#import <zlib.h>
#define LOCALYTICS_URL @"http://analytics.localytics.com/api/v2/applications/%@/uploads" // url to send the
static UploaderThread *_sharedUploaderThread = nil;
@interface UploaderThread ()
- (void)complete;
- (NSData *)gzipDeflatedDataWithData:(NSData *)data;
- (void)logMessage:(NSString *)message;
@end
@implementation UploaderThread
@synthesize uploadConnection = _uploadConnection;
@synthesize isUploading = _isUploading;
#pragma mark Singleton Class
+ (UploaderThread *)sharedUploaderThread {
@synchronized(self) {
if (_sharedUploaderThread == nil)
{
_sharedUploaderThread = [[self alloc] init];
}
}
return _sharedUploaderThread;
}
#pragma mark Class Methods
- (void)uploaderThreadwithApplicationKey:(NSString *)localyticsApplicationKey {
// Do nothing if already uploading.
if (self.uploadConnection != nil || self.isUploading == true)
{
[self logMessage:@"Upload already in progress. Aborting."];
return;
}
[self logMessage:@"Beginning upload process"];
self.isUploading = true;
// Prepare the data for upload. The upload could take a long time, so some effort has to be made to be sure that events
// which get written while the upload is taking place don't get lost or duplicated. To achieve this, the logic is:
// 1) Append every header row blob string and and those of its associated events to the upload string.
// 2) Deflate and upload the data.
// 3) On success, delete all blob headers and staged events. Events added while an upload is in process are not
// deleted because they are not associated a header (and cannot be until the upload completes).
// Step 1
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
LocalyticsDatabase *db = [LocalyticsDatabase sharedLocalyticsDatabase];
NSString *blobString = [db uploadBlobString];
if ([blobString length] == 0) {
// There is nothing outstanding to upload.
[self logMessage:@"Abandoning upload. There are no new events."];
[pool drain];
[self complete];
return;
}
NSData *requestData = [blobString dataUsingEncoding:NSUTF8StringEncoding];
NSString *myString = [[[NSString alloc] initWithData:requestData encoding:NSUTF8StringEncoding] autorelease];
[self logMessage:@"Upload data:"];
[self logMessage:myString];
// Step 2
NSData *deflatedRequestData = [[self gzipDeflatedDataWithData:requestData] retain];
[pool drain];
NSString *apiUrlString = [NSString stringWithFormat:LOCALYTICS_URL, [localyticsApplicationKey stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
NSMutableURLRequest *submitRequest = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:apiUrlString]
cachePolicy:NSURLRequestReloadIgnoringCacheData
timeoutInterval:60.0];
[submitRequest setHTTPMethod:@"POST"];
[submitRequest setValue:@"application/x-gzip" forHTTPHeaderField:@"Content-Type"];
[submitRequest setValue:@"gzip" forHTTPHeaderField:@"Content-Encoding"];
[submitRequest setValue:[NSString stringWithFormat:@"%d", [deflatedRequestData length]] forHTTPHeaderField:@"Content-Length"];
[submitRequest setHTTPBody:deflatedRequestData];
[deflatedRequestData release];
// The NSURLConnection Object automatically spawns its own thread as a default behavior.
@try
{
[self logMessage:@"Spawning new thread for upload"];
self.uploadConnection = [NSURLConnection connectionWithRequest:submitRequest delegate:self];
// Step 3 is handled by connectionDidFinishLoading.
}
@catch (NSException * e)
{
[self complete];
}
}
#pragma mark **** NSURLConnection FUNCTIONS ****
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
// Used to gather response data from server - Not utilized in this version
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
// Could receive multiple response callbacks, likely due to redirection.
// Record status and act only when connection completes load.
_responseStatusCode = [(NSHTTPURLResponse *)response statusCode];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
// If the connection finished loading, the files should be deleted. While response status codes in the 5xx range
// leave upload rows intact, the default case is to delete.
if (_responseStatusCode >= 500 && _responseStatusCode < 600)
{
[self logMessage:[NSString stringWithFormat:@"Upload failed with response status code %d", _responseStatusCode]];
} else
{
// The connection finished loading and uploaded data should be deleted. Because only one instance of the
// uploader can be running at a time it should not be possible for new upload rows to appear so there is no
// fear of deleting data which has not yet been uploaded.
[self logMessage:[NSString stringWithFormat:@"Upload completed successfully. Response code %d", _responseStatusCode]];
[[LocalyticsDatabase sharedLocalyticsDatabase] deleteUploadData];
}
// Close upload session
[self complete];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
// On error, simply print the error and close the uploader. We have to assume the data was not transmited
// so it is not deleted. In the event that we accidently store data which was succesfully uploaded, the
// duplicate data will be ignored by the server when it is next uploaded.
[self logMessage:[NSString stringWithFormat:
@"Error Uploading. Code: %d, Description: %s",
[error code],
[error localizedDescription]]];
[self complete];
}
/*!
@method complete
@abstract closes the upload connection and reports back to the session that the upload is complete
*/
- (void)complete {
_responseStatusCode = 0;
self.uploadConnection = nil;
self.isUploading = false;
}
/*!
@method gzipDeflatedDataWithData
@abstract Deflates the provided data using gzip at the default compression level (6). Complete NSData gzip category available on CocoaDev. http://www.cocoadev.com/index.pl?NSDataCategory.
@return the deflated data
*/
- (NSData *)gzipDeflatedDataWithData:(NSData *)data
{
if ([data length] == 0) return data;
z_stream strm;
strm.zalloc = Z_NULL;
strm.zfree = Z_NULL;
strm.opaque = Z_NULL;
strm.total_out = 0;
strm.next_in=(Bytef *)[data bytes];
strm.avail_in = [data length];
// Compresssion Levels:
// Z_NO_COMPRESSION
// Z_BEST_SPEED
// Z_BEST_COMPRESSION
// Z_DEFAULT_COMPRESSION
if (deflateInit2(&strm, Z_DEFAULT_COMPRESSION, Z_DEFLATED, (15+16), 8, Z_DEFAULT_STRATEGY) != Z_OK) return nil;
NSMutableData *compressed = [NSMutableData dataWithLength:16384]; // 16K chunks for expansion
do {
if (strm.total_out >= [compressed length])
[compressed increaseLengthBy: 16384];
strm.next_out = [compressed mutableBytes] + strm.total_out;
strm.avail_out = [compressed length] - strm.total_out;
deflate(&strm, Z_FINISH);
} while (strm.avail_out == 0);
deflateEnd(&strm);
[compressed setLength: strm.total_out];
return [NSData dataWithData:compressed];
}
/*!
@method logMessage
@abstract Logs a message with (localytics uploader) prepended to it
@param message The message to log
*/
- (void) logMessage:(NSString *)message {
if(DO_LOCALYTICS_LOGGING) {
NSLog(@"(localytics uploader) %s\n", [message UTF8String]);
}
}
#pragma mark System Functions
+ (id)allocWithZone:(NSZone *)zone {
@synchronized(self) {
if (_sharedUploaderThread == nil) {
_sharedUploaderThread = [super allocWithZone:zone];
return _sharedUploaderThread;
}
}
// returns nil on subsequent allocations
return nil;
}
- (id)copyWithZone:(NSZone *)zone {
return self;
}
- (id)retain {
return self;
}
- (unsigned)retainCount {
// maximum value of an unsigned int - prevents additional retains for the class
return UINT_MAX;
}
- (oneway void)release {
// ignore release commands
}
- (id)autorelease {
return self;
}
- (void)dealloc {
[_uploadConnection release];
[_sharedUploaderThread release];
[super dealloc];
}
@end

View File

@@ -0,0 +1,111 @@
// WebserviceConstants.h
// Copyright (C) 2009 Char Software Inc., DBA Localytics
//
// This code is provided under the Localytics Modified BSD License.
// A copy of this license has been distributed in a file called LICENSE
// with this source code.
//
// Please visit www.localytics.com for more information.
// The constants which are used to make up the JSON blob
// To save disk space and network bandwidth all the keywords have been
// abbreviated and are exploded by the server.
/*********************
* Shared Attributes *
*********************/
#define PARAM_UUID @"u" // UUID for JSON document
#define PARAM_DATA_TYPE @"dt" // Data Type
#define PARAM_CLIENT_TIME @"ct" // Client Time, seconds from Unix epoch (int)
#define PARAM_LATITUDE @"lat" // Latitude - if available
#define PARAM_LONGITUDE @"lon" // Longitude - if available
#define PARAM_SESSION_UUID @"su" // UUID for an existing session
#define PARAM_NEW_SESSION_UUID @"u" // UUID for a new session
#define PARAM_ATTRIBUTES @"attrs" // Attributes (dictionary)
/***************
* Blob Header *
***************/
// PARAM_UUID
// PARAM_DATA_TYPE => "h" for Header
// PARAM_ATTRIBUTES => dictionary containing Header Common Attributes
#define PARAM_PERSISTED_AT @"pa" // Persistent Storage Created At. A timestamp created when the app was
// first launched and the persistent storage was created. Stores as
// seconds from Unix epoch. (int)
#define PARAM_SEQUENCE_NUMBER @"seq" // Sequence number - an increasing count for each blob, stored in the
// persistent store Consistent across app starts. (int)
/****************************
* Header Common Attributes *
****************************/
// PARAM_DATA_TYPE
#define PARAM_APP_KEY @"au" // Localytics Application ID
#define PARAM_DEVICE_UUID @"du" // Device UUID
#define PARAM_DEVICE_UUID_HASHED @"udid" // Hashed version of the UUID
#define PARAM_DEVICE_MAC @"wmac" // Hashed version of the device Mac
#define PARAM_INSTALL_ID @"iu" // Install ID
#define PARAM_JAILBROKEN @"j" // Jailbroken (boolean)
#define PARAM_LIBRARY_VERSION @"lv" // Client Version
#define PARAM_APP_VERSION @"av" // Application Version
#define PARAM_DEVICE_PLATFORM @"dp" // Device Platform
#define PARAM_LOCALE_LANGUAGE @"dll" // Locale Language
#define PARAM_LOCALE_COUNTRY @"dlc" // Locale Country
#define PARAM_NETWORK_COUNTRY @"nc" // Network Country (iso code) // ???: Never used on iPhone.
#define PARAM_DEVICE_COUNTRY @"dc" // Device Country (iso code)
#define PARAM_DEVICE_MANUFACTURER @"dma" // Device Manufacturer // ???: Never used on iPhone. Used to be "Device Make".
#define PARAM_DEVICE_MODEL @"dmo" // Device Model
#define PARAM_DEVICE_OS_VERSION @"dov" // Device OS Version
#define PARAM_NETWORK_CARRIER @"nca" // Network Carrier
#define PARAM_DATA_CONNECTION @"dac" // Data Connection Type // ???: Never used on iPhone.
#define PARAM_OPT_VALUE @"optin" // Opt In (boolean)
#define PARAM_DEVICE_MEMORY @"dmem" // Device Memory
/*****************
* Session Start *
*****************/
// PARAM_UUID
// PARAM_DATA_TYPE => "s" for Start
// PARAM_CLIENT_TIME
#define PARAM_SESSION_NUMBER @"nth" // This is the nth session on the device, 1-indexed (int)
/****************
* Session Stop *
****************/
// PARAM_UUID
// PARAM_DATA_TYPE => "c" for Close
// PARAM_CLIENT_TIME
// PARAM_LATITUDE
// PARAM_LONGITUDE
// PARAM_SESSION_UUID => UUID of session being closed
#define PARAM_SESSION_ACTIVE @"cta" // Active time in seconds (time app was active)
#define PARAM_SESSION_TOTAL @"ctl" // Total session length
#define PARAM_SESSION_SCREENFLOW @"fl" // Screens encountered during this session, in order
/*********************
* Application Event *
*********************/
// PARAM_UUID
// PARAM_DATA_TYPE => "e" for Event
// PARAM_CLIENT_TIME
// PARAM_LATITUDE
// PARAM_LONGITUDE
// PARAM_SESSION_UUID => UUID of session event occured in
// PARAM_ATTRIBUTES => dictionary containing attributes for this event as key-value string pairs
#define PARAM_EVENT_NAME @"n" // Event Name, (eg. 'Button Click')
#define PARAM_REPORT_ATTRIBUTES @"rattrs" // Attributes used in custom reports
/********************
* Application flow *
********************/
// PARAM_UUID
// PARAM_DATA_TYPE => "f" for Flow
// PARAM_CLIENT_TIME
#define PARAM_SESSION_START @"ss" // Start time for the current session.
#define PARAM_NEW_FLOW_EVENTS @"nw" // Events and screens encountered during this session that have NOT been staged for upload.
#define PARAM_OLD_FLOW_EVENTS @"od" // Events and screens encountered during this session that HAVE been staged for upload.

File diff suppressed because it is too large Load Diff

View File

@@ -2,6 +2,6 @@
<Workspace
version = "1.0">
<FileRef
location = "self:MasterPassword.xcodeproj">
location = "self:MasterPassword-Mac.xcodeproj">
</FileRef>
</Workspace>

View File

@@ -0,0 +1,91 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "NO"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "DAB8D987150374AD00CED3BC"
BuildableName = "MasterPassword.app"
BlueprintName = "MasterPassword"
ReferencedContainer = "container:MasterPassword-Mac.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
buildConfiguration = "Debug">
<Testables>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "DAB8D987150374AD00CED3BC"
BuildableName = "MasterPassword.app"
BlueprintName = "MasterPassword"
ReferencedContainer = "container:MasterPassword-Mac.xcodeproj">
</BuildableReference>
</MacroExpansion>
</TestAction>
<LaunchAction
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
buildConfiguration = "Debug"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
allowLocationSimulation = "YES">
<BuildableProductRunnable>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "DAB8D987150374AD00CED3BC"
BuildableName = "MasterPassword.app"
BlueprintName = "MasterPassword"
ReferencedContainer = "container:MasterPassword-Mac.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<CommandLineArguments>
<CommandLineArgument
argument = "-com.apple.coredata.ubiquity.logLevel 3"
isEnabled = "NO">
</CommandLineArgument>
</CommandLineArguments>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
buildConfiguration = "Release"
debugDocumentVersioning = "YES">
<BuildableProductRunnable>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "DAB8D987150374AD00CED3BC"
BuildableName = "MasterPassword.app"
BlueprintName = "MasterPassword"
ReferencedContainer = "container:MasterPassword-Mac.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:MasterPassword-iOS.xcodeproj">
</FileRef>
</Workspace>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded</key>
<false/>
</dict>
</plist>

View File

@@ -0,0 +1,85 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "NO"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "DA5BFA43147E415C00F98B1E"
BuildableName = "MasterPassword.app"
BlueprintName = "MasterPassword"
ReferencedContainer = "container:MasterPassword-iOS.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
buildConfiguration = "Debug">
<Testables>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "DA5BFA43147E415C00F98B1E"
BuildableName = "MasterPassword.app"
BlueprintName = "MasterPassword"
ReferencedContainer = "container:MasterPassword-iOS.xcodeproj">
</BuildableReference>
</MacroExpansion>
</TestAction>
<LaunchAction
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
buildConfiguration = "AppStore"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
allowLocationSimulation = "YES">
<BuildableProductRunnable>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "DA5BFA43147E415C00F98B1E"
BuildableName = "MasterPassword.app"
BlueprintName = "MasterPassword"
ReferencedContainer = "container:MasterPassword-iOS.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
buildConfiguration = "AppStore"
debugDocumentVersioning = "YES">
<BuildableProductRunnable>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "DA5BFA43147E415C00F98B1E"
BuildableName = "MasterPassword.app"
BlueprintName = "MasterPassword"
ReferencedContainer = "container:MasterPassword-iOS.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "AppStore"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@@ -0,0 +1,91 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0430"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "NO"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "DA5BFA43147E415C00F98B1E"
BuildableName = "MasterPassword.app"
BlueprintName = "MasterPassword"
ReferencedContainer = "container:MasterPassword-iOS.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
buildConfiguration = "Debug">
<Testables>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "DA5BFA43147E415C00F98B1E"
BuildableName = "MasterPassword.app"
BlueprintName = "MasterPassword"
ReferencedContainer = "container:MasterPassword-iOS.xcodeproj">
</BuildableReference>
</MacroExpansion>
</TestAction>
<LaunchAction
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
buildConfiguration = "AppStore"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
allowLocationSimulation = "YES">
<BuildableProductRunnable>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "DA5BFA43147E415C00F98B1E"
BuildableName = "MasterPassword.app"
BlueprintName = "MasterPassword"
ReferencedContainer = "container:MasterPassword-iOS.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<AdditionalOptions>
<AdditionalOption
key = "NSZombieEnabled"
value = "YES"
isEnabled = "YES">
</AdditionalOption>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
buildConfiguration = "Debug"
debugDocumentVersioning = "YES">
<BuildableProductRunnable>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "DA5BFA43147E415C00F98B1E"
BuildableName = "MasterPassword.app"
BlueprintName = "MasterPassword"
ReferencedContainer = "container:MasterPassword-iOS.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "AdHoc"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

File diff suppressed because it is too large Load Diff

View File

@@ -1,33 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
version = "1.3">
<BuildAction>
<BuildActionEntries>
<BuildActionEntry
buildForRunning = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "DA5BFA43147E415C00F98B1E"
BuildableName = "MasterPassword.app"
BlueprintName = "MasterPassword"
ReferencedContainer = "container:MasterPassword.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<LaunchAction
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
useCustomWorkingDirectory = "NO"
buildConfiguration = "Production">
<BuildableProductRunnable>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "DA5BFA43147E415C00F98B1E"
BuildableName = "MasterPassword.app"
BlueprintName = "MasterPassword"
ReferencedContainer = "container:MasterPassword.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
</Scheme>

View File

@@ -1,33 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
version = "1.3">
<BuildAction>
<BuildActionEntries>
<BuildActionEntry
buildForRunning = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "DA5BFA43147E415C00F98B1E"
BuildableName = "MasterPassword.app"
BlueprintName = "MasterPassword"
ReferencedContainer = "container:MasterPassword.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<LaunchAction
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
useCustomWorkingDirectory = "NO"
buildConfiguration = "Debug">
<BuildableProductRunnable>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "DA5BFA43147E415C00F98B1E"
BuildableName = "MasterPassword.app"
BlueprintName = "MasterPassword"
ReferencedContainer = "container:MasterPassword.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
</Scheme>

Binary file not shown.

Binary file not shown.

View File

@@ -1,34 +0,0 @@
//
// MPAppDelegate.h
// MasterPassword
//
// Created by Maarten Billemont on 24/11/11.
// Copyright (c) 2011 Lyndir. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface MPAppDelegate : PearlAppDelegate
@property (readonly, strong, nonatomic) NSManagedObjectContext *managedObjectContext;
@property (readonly, strong, nonatomic) NSManagedObjectModel *managedObjectModel;
@property (readonly, strong, nonatomic) NSPersistentStoreCoordinator *persistentStoreCoordinator;
@property (readonly, strong, nonatomic) NSData *keyPhrase;
@property (readonly, strong, nonatomic) NSData *keyPhraseHash;
@property (readonly, strong, nonatomic) NSString *keyPhraseHashHex;
+ (MPAppDelegate *)get;
+ (NSManagedObjectModel *)managedObjectModel;
+ (NSManagedObjectContext *)managedObjectContext;
- (void)saveContext;
- (NSURL *)applicationDocumentsDirectory;
- (void)showGuide;
- (void)loadKeyPhrase:(BOOL)animated;
- (void)signOut;
- (void)forgetKeyPhrase;
- (NSData *)keyPhraseWithLength:(NSUInteger)keyLength;
- (BOOL)tryMasterPassword:(NSString *)tryPassword;
@end

View File

@@ -1,521 +0,0 @@
//
// MPAppDelegate.m
// MasterPassword
//
// Created by Maarten Billemont on 24/11/11.
// Copyright (c) 2011 Lyndir. All rights reserved.
//
#import "MPAppDelegate.h"
#import "MPMainViewController.h"
#import "IASKSettingsReader.h"
@interface MPAppDelegate ()
@property (strong, nonatomic) NSData *keyPhrase;
@property (strong, nonatomic) NSData *keyPhraseHash;
@property (strong, nonatomic) NSString *keyPhraseHashHex;
+ (NSDictionary *)keyPhraseQuery;
+ (NSDictionary *)keyPhraseHashQuery;
- (void)loadStoredKeyPhrase;
- (void)askKeyPhrase:(BOOL)animated;
@end
@implementation MPAppDelegate
@synthesize managedObjectModel = __managedObjectModel;
@synthesize managedObjectContext = __managedObjectContext;
@synthesize persistentStoreCoordinator = __persistentStoreCoordinator;
@synthesize keyPhrase = _keyPhrase;
@synthesize keyPhraseHash = _keyPhraseHash;
@synthesize keyPhraseHashHex = _keyPhraseHashHex;
+ (void)initialize {
[MPConfig get];
#ifdef DEBUG
[PearlLogger get].autoprintLevel = PearlLogLevelTrace;
[NSClassFromString(@"WebView") performSelector:@selector(_enableRemoteInspector)];
#endif
}
+ (NSDictionary *)keyPhraseQuery {
static NSDictionary *MPKeyPhraseQuery = nil;
if (!MPKeyPhraseQuery)
MPKeyPhraseQuery = [PearlKeyChain createQueryForClass:kSecClassGenericPassword
attributes:[NSDictionary dictionaryWithObject:@"MasterPassword"
forKey:(__bridge id)kSecAttrService]
matches:nil];
return MPKeyPhraseQuery;
}
+ (NSDictionary *)keyPhraseHashQuery {
static NSDictionary *MPKeyPhraseHashQuery = nil;
if (!MPKeyPhraseHashQuery)
MPKeyPhraseHashQuery = [PearlKeyChain createQueryForClass:kSecClassGenericPassword
attributes:[NSDictionary dictionaryWithObject:@"MasterPasswordHash"
forKey:(__bridge id)kSecAttrService]
matches:nil];
return MPKeyPhraseHashQuery;
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
#ifndef PRODUCTION
@try {
[TestFlight takeOff:@"bd44885deee7adce0645ce8e5498d80a_NDQ5NDQyMDExLTEyLTAyIDExOjM1OjQ4LjQ2NjM4NA"];
[TestFlight setOptions:[NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:NO], @"logToConsole",
[NSNumber numberWithBool:NO], @"logToSTDERR",
nil]];
[TestFlight passCheckpoint:MPTestFlightCheckpointLaunched];
[[PearlLogger get] registerListener:^BOOL(PearlLogMessage *message) {
if (message.level >= PearlLogLevelInfo)
TFLog(@"%@", message);
return YES;
}];
}
@catch (NSException *exception) {
err(@"TestFlight: %@", exception);
}
#endif
UIImage *navBarImage = [[UIImage imageNamed:@"ui_navbar_container"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 5, 0, 5)];
[[UINavigationBar appearance] setBackgroundImage:navBarImage forBarMetrics:UIBarMetricsDefault];
[[UINavigationBar appearance] setBackgroundImage:navBarImage forBarMetrics:UIBarMetricsLandscapePhone];
[[UINavigationBar appearance] setTitleTextAttributes:
[NSDictionary dictionaryWithObjectsAndKeys:
[UIColor colorWithRed:255.0/255.0 green:255.0/255.0 blue:255.0/255.0 alpha:1.0], UITextAttributeTextColor,
[UIColor colorWithRed:0.0 green:0.0 blue:0.0 alpha:0.8], UITextAttributeTextShadowColor,
[NSValue valueWithUIOffset:UIOffsetMake(0, -1)], UITextAttributeTextShadowOffset,
[UIFont fontWithName:@"Helvetica-Neue" size:0.0], UITextAttributeFont,
nil]];
UIImage *navBarButton = [[UIImage imageNamed:@"ui_navbar_button"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 5, 0, 5)];
UIImage *navBarBack = [[UIImage imageNamed:@"ui_navbar_back"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 13, 0, 5)];
[[UIBarButtonItem appearance] setBackgroundImage:navBarButton forState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
[[UIBarButtonItem appearance] setBackgroundImage:nil forState:UIControlStateNormal barMetrics:UIBarMetricsLandscapePhone];
[[UIBarButtonItem appearance] setBackButtonBackgroundImage:navBarBack forState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
[[UIBarButtonItem appearance] setBackButtonBackgroundImage:nil forState:UIControlStateNormal barMetrics:UIBarMetricsLandscapePhone];
[[UIBarButtonItem appearance] setTitleTextAttributes:
[NSDictionary dictionaryWithObjectsAndKeys:
[UIColor colorWithRed:1.0 green:1.0 blue:1.0 alpha:1.0], UITextAttributeTextColor,
[UIColor colorWithRed:0.0 green:0.0 blue:0.0 alpha:0.5], UITextAttributeTextShadowColor,
[NSValue valueWithUIOffset:UIOffsetMake(0, 1)], UITextAttributeTextShadowOffset,
[UIFont fontWithName:@"Helvetica-Neue" size:0.0], UITextAttributeFont,
nil]
forState:UIControlStateNormal];
UIImage *toolBarImage = [[UIImage imageNamed:@"ui_toolbar_container"] resizableImageWithCapInsets:UIEdgeInsetsMake(25, 5, 5, 5)];
[[UISearchBar appearance] setBackgroundImage:toolBarImage];
[[UIToolbar appearance] setBackgroundImage:toolBarImage forToolbarPosition:UIToolbarPositionAny barMetrics:UIBarMetricsDefault];
/*
UIImage *minImage = [[UIImage imageNamed:@"slider-minimum.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 5, 0, 0)];
UIImage *maxImage = [[UIImage imageNamed:@"slider-maximum.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 5, 0, 0)];
UIImage *thumbImage = [UIImage imageNamed:@"slider-handle.png"];
[[UISlider appearance] setMaximumTrackImage:maxImage forState:UIControlStateNormal];
[[UISlider appearance] setMinimumTrackImage:minImage forState:UIControlStateNormal];
[[UISlider appearance] setThumbImage:thumbImage forState:UIControlStateNormal];
UIImage *segmentSelected = [[UIImage imageNamed:@"segcontrol_sel.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 4, 0, 4)];
UIImage *segmentUnselected = [[UIImage imageNamed:@"segcontrol_uns.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 15, 0, 15)];
UIImage *segmentSelectedUnselected = [UIImage imageNamed:@"segcontrol_sel-uns.png"];
UIImage *segUnselectedSelected = [UIImage imageNamed:@"segcontrol_uns-sel.png"];
UIImage *segmentUnselectedUnselected = [UIImage imageNamed:@"segcontrol_uns-uns.png"];
[[UISegmentedControl appearance] setBackgroundImage:segmentUnselected forState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
[[UISegmentedControl appearance] setBackgroundImage:segmentSelected forState:UIControlStateSelected barMetrics:UIBarMetricsDefault];
[[UISegmentedControl appearance] setDividerImage:segmentUnselectedUnselected forLeftSegmentState:UIControlStateNormal rightSegmentState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
[[UISegmentedControl appearance] setDividerImage:segmentSelectedUnselected forLeftSegmentState:UIControlStateSelected rightSegmentState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
[[UISegmentedControl appearance] setDividerImage:segUnselectedSelected forLeftSegmentState:UIControlStateNormal rightSegmentState:UIControlStateSelected barMetrics:UIBarMetricsDefault];*/
[[NSNotificationCenter defaultCenter] addObserverForName:kIASKAppSettingChanged object:nil queue:nil
usingBlock:^(NSNotification *note) {
if ([NSStringFromSelector(@selector(storeKeyPhrase))
isEqualToString:[note.object description]]) {
self.keyPhrase = self.keyPhrase;
[self loadKeyPhrase:YES];
}
if ([NSStringFromSelector(@selector(forgetKeyPhrase))
isEqualToString:[note.object description]])
[self loadKeyPhrase:YES];
}];
#ifndef PRODUCTION
[PearlAlert showAlertWithTitle:@"Welcome, tester!" message:
@"Thank you for taking the time to test Master Password.\n\n"
@"Please provide any feedback, however minor it may seem, via the Feedback action item accessible from the top right.\n\n"
@"Contact me directly at:\n"
@"lhunath@lyndir.com\n"
@"Or report detailed issues at:\n"
@"https://youtrack.lyndir.com\n"
viewStyle:UIAlertViewStyleDefault tappedButtonBlock:nil
cancelTitle:nil otherTitles:[PearlStrings get].commonButtonOkay, nil];
#endif
[[UIApplication sharedApplication] setStatusBarHidden:NO
withAnimation:UIStatusBarAnimationSlide];
return [super application:application didFinishLaunchingWithOptions:launchOptions];
}
- (void)applicationDidBecomeActive:(UIApplication *)application {
if ([[MPConfig get].showQuickStart boolValue])
[self showGuide];
else
[self loadKeyPhrase:NO];
#ifndef PRODUCTION
[TestFlight passCheckpoint:MPTestFlightCheckpointActivated];
#endif
}
- (void)showGuide {
[self.navigationController performSegueWithIdentifier:@"MP_Guide" sender:self];
#ifndef PRODUCTION
[TestFlight passCheckpoint:MPTestFlightCheckpointShowGuide];
#endif
}
- (void)loadKeyPhrase:(BOOL)animated {
if (self.keyPhrase)
return;
[self loadStoredKeyPhrase];
if (!self.keyPhrase) {
// Key phrase is not known. Ask user to set/specify it.
dbg(@"Key phrase not known. Will ask user.");
[self askKeyPhrase:animated];
return;
}
}
- (void)forgetKeyPhrase {
dbg(@"Forgetting key phrase.");
[PearlAlert showAlertWithTitle:@"Changing Master Password"
message:
@"This will allow you to log in with a different master password.\n\n"
@"Note that you will only see the sites and passwords for the master password you log in with.\n"
@"If you log in with a different master password, your current sites will be unavailable.\n\n"
@"You can always change back to your current master password later.\n"
@"Your current sites and passwords will then become available again."
viewStyle:UIAlertViewStyleDefault
tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
if (buttonIndex != [alert cancelButtonIndex]) {
// Key phrase reset. Delete it.
dbg(@"Deleting master key phrase and hash from key chain.");
[PearlKeyChain deleteItemForQuery:[MPAppDelegate keyPhraseQuery]];
[PearlKeyChain deleteItemForQuery:[MPAppDelegate keyPhraseHashQuery]];
[[NSNotificationCenter defaultCenter] postNotificationName:MPNotificationKeyForgotten object:self];
#ifndef PRODUCTION
[TestFlight passCheckpoint:MPTestFlightCheckpointMPForgotten];
#endif
}
[self loadKeyPhrase:YES];
#ifndef PRODUCTION
[TestFlight passCheckpoint:MPTestFlightCheckpointMPChanged];
#endif
}
cancelTitle:[PearlStrings get].commonButtonAbort
otherTitles:[PearlStrings get].commonButtonContinue, nil];
}
- (void)signOut {
self.keyPhrase = nil;
[self loadKeyPhrase:YES];
}
- (void)loadStoredKeyPhrase {
if ([[MPConfig get].storeKeyPhrase boolValue]) {
// Key phrase is stored in keychain. Load it.
dbg(@"Loading master key phrase from key chain.");
self.keyPhrase = [PearlKeyChain dataOfItemForQuery:[MPAppDelegate keyPhraseQuery]];
dbg(@" -> Master key phrase %@.", self.keyPhrase? @"found": @"NOT found");
} else {
// Key phrase should not be stored in keychain. Delete it.
dbg(@"Deleting master key phrase from key chain.");
[PearlKeyChain deleteItemForQuery:[MPAppDelegate keyPhraseQuery]];
#ifndef PRODUCTION
[TestFlight passCheckpoint:MPTestFlightCheckpointMPUnstored];
#endif
}
}
- (void)askKeyPhrase:(BOOL)animated {
dispatch_async(dispatch_get_main_queue(), ^{
[self.navigationController presentViewController:
[self.navigationController.storyboard instantiateViewControllerWithIdentifier:@"MPUnlockViewController"]
animated:animated completion:nil];
});
}
- (void)applicationWillResignActive:(UIApplication *)application {
[self saveContext];
if (![[MPConfig get].rememberKeyPhrase boolValue])
self.keyPhrase = nil;
#ifndef PRODUCTION
[TestFlight passCheckpoint:MPTestFlightCheckpointDeactivated];
#endif
}
- (void)applicationWillTerminate:(UIApplication *)application {
[self saveContext];
#ifndef PRODUCTION
[TestFlight passCheckpoint:MPTestFlightCheckpointTerminated];
#endif
}
+ (MPAppDelegate *)get {
return (MPAppDelegate *)[super get];
}
+ (NSManagedObjectContext *)managedObjectContext {
return [(MPAppDelegate *)[UIApplication sharedApplication].delegate managedObjectContext];
}
+ (NSManagedObjectModel *)managedObjectModel {
return [(MPAppDelegate *)[UIApplication sharedApplication].delegate managedObjectModel];
}
- (void)saveContext {
[self.managedObjectContext performBlock:^{
NSError *error = nil;
if ([self.managedObjectContext hasChanges] && ![self.managedObjectContext save:&error])
err(@"Unresolved error %@", error);
}];
}
- (BOOL)tryMasterPassword:(NSString *)tryPassword {
NSData *keyPhraseHash = [PearlKeyChain dataOfItemForQuery:[MPAppDelegate keyPhraseHashQuery]];
dbg(@"Key phrase hash %@.", keyPhraseHash? @"known": @"NOT known");
if (![tryPassword length])
return NO;
NSData *tryKeyPhrase = keyPhraseForPassword(tryPassword);
NSData *tryKeyPhraseHash = keyPhraseHashForKeyPhrase(tryKeyPhrase);
if (keyPhraseHash)
// A key phrase hash is known -> a key phrase is set.
// Make sure the user's entered key phrase matches it.
if (![keyPhraseHash isEqual:tryKeyPhraseHash]) {
dbg(@"Key phrase hash mismatch. Expected: %@, answer: %@.", keyPhraseHash, tryKeyPhraseHash);
#ifndef PRODUCTION
[TestFlight passCheckpoint:MPTestFlightCheckpointMPMismatch];
#endif
return NO;
}
#ifndef PRODUCTION
[TestFlight passCheckpoint:MPTestFlightCheckpointMPAsked];
#endif
self.keyPhrase = tryKeyPhrase;
return YES;
}
- (void)setKeyPhrase:(NSData *)keyPhrase {
_keyPhrase = keyPhrase;
if (keyPhrase)
[[NSNotificationCenter defaultCenter] postNotificationName:MPNotificationKeySet object:self];
else
[[NSNotificationCenter defaultCenter] postNotificationName:MPNotificationKeyUnset object:self];
if (keyPhrase) {
self.keyPhraseHash = keyPhraseHashForKeyPhrase(keyPhrase);
self.keyPhraseHashHex = [self.keyPhraseHash encodeHex];
dbg(@"Updating master key phrase hash to: %@.", self.keyPhraseHashHex);
[PearlKeyChain addOrUpdateItemForQuery:[MPAppDelegate keyPhraseHashQuery]
withAttributes:[NSDictionary dictionaryWithObjectsAndKeys:
self.keyPhraseHash, (__bridge id)kSecValueData,
kSecAttrAccessibleWhenUnlocked, (__bridge id)kSecAttrAccessible,
nil]];
if ([[MPConfig get].storeKeyPhrase boolValue]) {
dbg(@"Storing master key phrase in key chain.");
[PearlKeyChain addOrUpdateItemForQuery:[MPAppDelegate keyPhraseQuery]
withAttributes:[NSDictionary dictionaryWithObjectsAndKeys:
keyPhrase, (__bridge id)kSecValueData,
kSecAttrAccessibleWhenUnlocked, (__bridge id)kSecAttrAccessible,
nil]];
}
#ifndef PRODUCTION
[TestFlight passCheckpoint:[NSString stringWithFormat:MPTestFlightCheckpointSetKeyphraseLength, _keyPhrase.length]];
#endif
}
}
- (NSData *)keyPhraseWithLength:(NSUInteger)keyLength {
return [self.keyPhrase subdataWithRange:NSMakeRange(0, MIN(keyLength, self.keyPhrase.length))];
}
#pragma mark - Core Data stack
/**
Returns the managed object context for the application.
If the context doesn't already exist, it is created and bound to the persistent store coordinator for the application.
*/
- (NSManagedObjectContext *)managedObjectContext
{
if (__managedObjectContext)
return __managedObjectContext;
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator) {
__managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
__managedObjectContext.persistentStoreCoordinator = coordinator;
[[NSNotificationCenter defaultCenter] addObserverForName:NSPersistentStoreDidImportUbiquitousContentChangesNotification
object:coordinator
queue:nil
usingBlock:^(NSNotification *note) {
dbg(@"Ubiquitous content change: %@", note);
[__managedObjectContext performBlock:^{
[__managedObjectContext mergeChangesFromContextDidSaveNotification:note];
[[NSNotificationCenter defaultCenter] postNotification:
[NSNotification notificationWithName:UIScreenModeDidChangeNotification
object:self userInfo:[note userInfo]]];
}];
}];
}
return __managedObjectContext;
}
/**
Returns the managed object model for the application.
If the model doesn't already exist, it is created from the application's model.
*/
- (NSManagedObjectModel *)managedObjectModel
{
if (__managedObjectModel)
return __managedObjectModel;
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"MasterPassword" withExtension:@"momd"];
return __managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
}
/**
Returns the persistent store coordinator for the application.
If the coordinator doesn't already exist, it is created and the application's store added to it.
*/
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
if (__persistentStoreCoordinator)
return __persistentStoreCoordinator;
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"MasterPassword.sqlite"];
__persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
[__persistentStoreCoordinator lock];
NSError *error = nil;
if (![__persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL
options:[NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption,
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
@"MasterPassword.store", NSPersistentStoreUbiquitousContentNameKey,
[[[NSFileManager defaultManager]
URLForUbiquityContainerIdentifier:nil]
URLByAppendingPathComponent:@"store"
isDirectory:YES], NSPersistentStoreUbiquitousContentURLKey,
nil]
error:&error])
{
/*
Replace this implementation with code to handle the error appropriately.
abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
Typical reasons for an error here include:
* The persistent store is not accessible;
* The schema for the persistent store is incompatible with current managed object model.
Check the error message to determine what the actual problem was.
If the persistent store is not accessible, there is typically something wrong with the file path. Often, a file URL is pointing into the application's resources directory instead of a writeable directory.
If you encounter schema incompatibility errors during development, you can reduce their frequency by:
* Simply deleting the existing store:
[[NSFileManager defaultManager] removeItemAtURL:storeURL error:nil]
* Performing automatic lightweight migration by passing the following dictionary as the options parameter:
[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption, [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
Lightweight migration will only work for a limited set of schema changes; consult "Core Data Model Versioning and Data Migration Programming Guide" for details.
*/
err(@"Unresolved error %@, %@", error, [error userInfo]);
#if DEBUG
wrn(@"Deleted datastore: %@", storeURL);
[[NSFileManager defaultManager] removeItemAtURL:storeURL error:nil];
#endif
#ifndef PRODUCTION
[TestFlight passCheckpoint:MPTestFlightCheckpointStoreIncompatible];
#endif
@throw [NSException exceptionWithName:error.domain reason:error.localizedDescription
userInfo:[NSDictionary dictionaryWithObject:error forKey:@"cause"]];
}
if (![[NSFileManager defaultManager] setAttributes:[NSDictionary dictionaryWithObject:NSFileProtectionComplete
forKey:NSFileProtectionKey]
ofItemAtPath:storeURL.path error:&error])
err(@"Unresolved error %@, %@", error, [error userInfo]);
[__persistentStoreCoordinator unlock];
return __persistentStoreCoordinator;
}
#pragma mark - Application's Documents directory
/**
Returns the URL to the application's Documents directory.
*/
- (NSURL *)applicationDocumentsDirectory
{
return [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
}
@end

View File

@@ -0,0 +1,19 @@
//
// MPAppDelegate_Key.h
// MasterPassword
//
// Created by Maarten Billemont on 24/11/11.
// Copyright (c) 2011 Lyndir. All rights reserved.
//
#import "MPAppDelegate_Shared.h"
@interface MPAppDelegate_Shared (Key)
- (BOOL)signInAsUser:(MPUserEntity *)user usingMasterPassword:(NSString *)password;
- (void)signOut;
- (void)storeSavedKeyFor:(MPUserEntity *)user;
- (void)forgetSavedKeyFor:(MPUserEntity *)user;
@end

View File

@@ -0,0 +1,174 @@
//
// MPAppDelegate.m
// MasterPassword
//
// Created by Maarten Billemont on 24/11/11.
// Copyright (c) 2011 Lyndir. All rights reserved.
//
#import <Crashlytics/Crashlytics.h>
#import "MPAppDelegate_Key.h"
#import "MPAppDelegate_Store.h"
#import "LocalyticsSession.h"
@implementation MPAppDelegate_Shared (Key)
static NSDictionary *keyQuery(MPUserEntity *user) {
return [PearlKeyChain createQueryForClass:kSecClassGenericPassword
attributes:[NSDictionary dictionaryWithObjectsAndKeys:
@"Saved Master Password", (__bridge id)kSecAttrService,
user.name, (__bridge id)kSecAttrAccount,
nil]
matches:nil];
}
- (NSData *)loadSavedKeyFor:(MPUserEntity *)user {
NSData *key = [PearlKeyChain dataOfItemForQuery:keyQuery(user)];
if (key)
inf(@"Found key in keychain for: %@", user.userID);
else {
user.saveKey = NO;
inf(@"No key found in keychain for: %@", user.userID);
}
return key;
}
- (void)storeSavedKeyFor:(MPUserEntity *)user {
if (user.saveKey) {
NSData *existingKey = [PearlKeyChain dataOfItemForQuery:keyQuery(user)];
if (![existingKey isEqualToData:self.key]) {
inf(@"Saving key in keychain for: %@", user.userID);
[PearlKeyChain addOrUpdateItemForQuery:keyQuery(user)
withAttributes:[NSDictionary dictionaryWithObjectsAndKeys:
self.key, (__bridge id)kSecValueData,
#if TARGET_OS_IPHONE
kSecAttrAccessibleWhenUnlockedThisDeviceOnly, (__bridge id)kSecAttrAccessible,
#endif
nil]];
}
}
}
- (void)forgetSavedKeyFor:(MPUserEntity *)user {
OSStatus result = [PearlKeyChain deleteItemForQuery:keyQuery(user)];
if (result == noErr || result == errSecItemNotFound) {
user.saveKey = NO;
if (result == noErr) {
inf(@"Removed key from keychain for: %@", user.userID);
[[NSNotificationCenter defaultCenter] postNotificationName:MPNotificationKeyForgotten object:self];
#ifdef TESTFLIGHT_SDK_VERSION
[TestFlight passCheckpoint:MPCheckpointForgetSavedKey];
#endif
}
}
}
- (void)signOut {
if (self.key)
self.key = nil;
if (self.activeUser) {
self.activeUser = nil;
[[NSNotificationCenter defaultCenter] postNotificationName:MPNotificationSignedOut object:self];
}
}
- (BOOL)signInAsUser:(MPUserEntity *)user usingMasterPassword:(NSString *)password {
NSData *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);
[[MPAppDelegate_Shared get] saveContext];
}
}
// Method 2: Depending on the user's saveKey, load or remove the key from the keychain.
if (!user.saveKey)
// Key should not be stored in keychain. Delete it.
[self forgetSavedKeyFor:user];
else
if (!tryKey) {
// Key should be saved in keychain. Load it.
if ((tryKey = [self loadSavedKeyFor:user]))
if (![user.keyID isEqual:keyIDForKey(tryKey)]) {
// Loaded password doesn't match user's keyID. Forget saved password: it is incorrect.
inf(@"Saved password doesn't match keyID for: %@", user.userID);
tryKey = nil;
[self forgetSavedKeyFor: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)]) {
inf(@"Key derived from password doesn't match keyID for: %@", user.userID);
tryKey = nil;
}
}
// No more methods left, fail if key still not known.
if (!tryKey) {
if (password) {
inf(@"Login failed for: %@", user.userID);
#ifdef TESTFLIGHT_SDK_VERSION
[TestFlight passCheckpoint:MPCheckpointSignInFailed];
#endif
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointSignInFailed attributes:nil];
}
return NO;
}
inf(@"Logged in: %@", user.userID);
if (![self.key isEqualToData:tryKey]) {
self.key = tryKey;
[self storeSavedKeyFor:user];
}
@try {
if ([[MPiOSConfig get].sendInfo boolValue]) {
[TestFlight addCustomEnvironmentInformation:user.userID forKey:@"username"];
[[Crashlytics sharedInstance] setObjectValue:user.userID forKey:@"username"];
}
}
@catch (id exception) {
err(@"While setting username: %@", exception);
}
user.lastUsed = [NSDate date];
self.activeUser = user;
[[MPAppDelegate_Shared get] saveContext];
[[NSNotificationCenter defaultCenter] postNotificationName:MPNotificationSignedIn object:self];
#ifdef TESTFLIGHT_SDK_VERSION
[TestFlight passCheckpoint:MPCheckpointSignedIn];
#endif
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointSignedIn
attributes:nil];
return YES;
}
@end

View File

@@ -0,0 +1,25 @@
//
// MPAppDelegate_Shared.h
// MasterPassword
//
// Created by Maarten Billemont on 24/11/11.
// Copyright (c) 2011 Lyndir. All rights reserved.
//
#import "MPEntities.h"
#if TARGET_OS_IPHONE
@interface MPAppDelegate_Shared : PearlAppDelegate
#else
@interface MPAppDelegate_Shared : NSObject <PearlConfigDelegate>
#endif
@property (strong, nonatomic) MPUserEntity *activeUser;
@property (strong, nonatomic) NSData *key;
+ (MPAppDelegate_Shared *)get;
- (NSURL *)applicationFilesDirectory;
@end

View File

@@ -0,0 +1,44 @@
//
// MPAppDelegate.m
// MasterPassword
//
// Created by Maarten Billemont on 24/11/11.
// Copyright (c) 2011 Lyndir. All rights reserved.
//
#import "MPAppDelegate_Shared.h"
@implementation MPAppDelegate_Shared
@synthesize key;
@synthesize activeUser;
+ (MPAppDelegate_Shared *)get {
#if TARGET_OS_IPHONE
return (MPAppDelegate_Shared *)[UIApplication sharedApplication].delegate;
#elif defined (__MAC_OS_X_VERSION_MIN_REQUIRED)
return (MPAppDelegate_Shared *)[NSApplication sharedApplication].delegate;
#else
#error Unsupported OS.
#endif
}
- (NSURL *)applicationFilesDirectory {
#if TARGET_OS_IPHONE
return [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
#else
NSURL *appSupportURL = [[[NSFileManager defaultManager] URLsForDirectory:NSApplicationSupportDirectory inDomains:NSUserDomainMask] lastObject];
NSURL *applicationFilesDirectory = [appSupportURL URLByAppendingPathComponent:@"com.lyndir.lhunath.MasterPassword"];
NSError *error = nil;
[[NSFileManager defaultManager] createDirectoryAtURL:applicationFilesDirectory withIntermediateDirectories:YES attributes:nil error:&error];
if (error)
err(@"Couldn't create application directory: %@, error occurred: %@", applicationFilesDirectory, error);
return applicationFilesDirectory;
#endif
}
@end

View File

@@ -0,0 +1,35 @@
//
// MPAppDelegate_Key.h
// MasterPassword
//
// Created by Maarten Billemont on 24/11/11.
// Copyright (c) 2011 Lyndir. All rights reserved.
//
#import "MPAppDelegate_Shared.h"
#import "UbiquityStoreManager.h"
typedef enum {
MPImportResultSuccess,
MPImportResultCancelled,
MPImportResultInvalidPassword,
MPImportResultMalformedInput,
MPImportResultInternalError,
} MPImportResult;
@interface MPAppDelegate_Shared (Store)<UbiquityStoreManagerDelegate>
+ (NSManagedObjectContext *)managedObjectContext;
+ (NSManagedObjectModel *)managedObjectModel;
- (NSManagedObjectContext *)managedObjectContext;
- (NSManagedObjectModel *)managedObjectModel;
- (UbiquityStoreManager *)storeManager;
- (void)saveContext;
- (MPImportResult)importSites:(NSString *)importedSitesString withPassword:(NSString *)password
askConfirmation:(BOOL(^)(NSUInteger importCount, NSUInteger deleteCount))confirmation;
- (NSString *)exportSitesShowingPasswords:(BOOL)showPasswords;
@end

View File

@@ -0,0 +1,419 @@
//
// MPAppDelegate.m
// MasterPassword
//
// Created by Maarten Billemont on 24/11/11.
// Copyright (c) 2011 Lyndir. All rights reserved.
//
#import "MPAppDelegate_Store.h"
#import "LocalyticsSession.h"
@implementation MPAppDelegate_Shared (Store)
#pragma mark - Core Data setup
+ (NSManagedObjectContext *)managedObjectContext {
return [[self get] managedObjectContext];
}
+ (NSManagedObjectModel *)managedObjectModel {
return [[self get] managedObjectModel];
}
- (NSManagedObjectModel *)managedObjectModel {
static NSManagedObjectModel *managedObjectModel = nil;
if (managedObjectModel)
return managedObjectModel;
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"MasterPassword" withExtension:@"momd"];
return managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
}
- (NSManagedObjectContext *)managedObjectContext {
static NSManagedObjectContext *managedObjectContext = nil;
if (managedObjectContext)
return managedObjectContext;
managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[managedObjectContext performBlockAndWait:^{
managedObjectContext.persistentStoreCoordinator = [self persistentStoreCoordinator];
managedObjectContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy;
}];
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;
if (storeManager)
return storeManager;
storeManager = [[UbiquityStoreManager alloc] initWithManagedObjectModel:[self managedObjectModel]
localStoreURL:[[self applicationFilesDirectory] URLByAppendingPathComponent:@"MasterPassword.sqlite"]
containerIdentifier:@"HL3Q45LX9N.com.lyndir.lhunath.MasterPassword.shared"
#if TARGET_OS_IPHONE
additionalStoreOptions:[NSDictionary dictionaryWithObject:NSFileProtectionComplete
forKey:NSPersistentStoreFileProtectionKey]
#else
additionalStoreOptions:nil
#endif
];
storeManager.delegate = self;
#ifdef DEBUG
storeManager.hardResetEnabled = YES;
#endif
#if TARGET_OS_IPHONE
[[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillEnterForegroundNotification
object:[UIApplication sharedApplication] queue:nil
usingBlock:^(NSNotification *note) {
[storeManager checkiCloudStatus];
}];
#else
[[NSNotificationCenter defaultCenter] addObserverForName:NSApplicationWillBecomeActiveNotification
object:[NSApplication sharedApplication] queue:nil
usingBlock:^(NSNotification *note) {
[storeManager checkiCloudStatus];
}];
#endif
#if TARGET_OS_IPHONE
[[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillTerminateNotification
object:[UIApplication sharedApplication] queue:nil
usingBlock:^(NSNotification *note) {
[self saveContext];
}];
#else
[[NSNotificationCenter defaultCenter] addObserverForName:NSApplicationWillTerminateNotification
object:[NSApplication sharedApplication] queue:nil
usingBlock:^(NSNotification *note) {
[self saveContext];
}];
#endif
return storeManager;
}
- (void)saveContext {
[self.managedObjectContext performBlock:^{
NSError *error = nil;
if ([self.managedObjectContext hasChanges])
if (![self.managedObjectContext save:&error])
err(@"While saving context: %@", error);
}];
}
#pragma mark - UbiquityStoreManagerDelegate
- (NSManagedObjectContext *)managedObjectContextForUbiquityStoreManager:(UbiquityStoreManager *)usm {
return self.managedObjectContext;
}
- (void)ubiquityStoreManager:(UbiquityStoreManager *)manager log:(NSString *)message {
dbg(@"[StoreManager] %@", message);
}
- (void)ubiquityStoreManager:(UbiquityStoreManager *)manager didSwitchToiCloud:(BOOL)iCloudEnabled {
// manager.iCloudEnabled is more reliable (eg. iOS' MPAppDelegate tampers with didSwitch a bit)
iCloudEnabled = manager.iCloudEnabled;
inf(@"Using iCloud? %@", iCloudEnabled? @"YES": @"NO");
#ifdef TESTFLIGHT_SDK_VERSION
[TestFlight passCheckpoint:iCloudEnabled? MPCheckpointCloudEnabled: MPCheckpointCloudDisabled];
#endif
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointCloud
attributes:[NSDictionary dictionaryWithObject:iCloudEnabled? @"YES": @"NO" forKey:@"enabled"]];
[MPConfig get].iCloud = [NSNumber numberWithBool:iCloudEnabled];
}
- (void)ubiquityStoreManager:(UbiquityStoreManager *)manager didEncounterError:(NSError *)error cause:(UbiquityStoreManagerErrorCause)cause
context:(id)context {
err(@"StoreManager: cause=%d, context=%@, error=%@", cause, context, error);
#ifdef TESTFLIGHT_SDK_VERSION
[TestFlight passCheckpoint:PearlString(@"MPCheckpointMPErrorUbiquity_%d", cause)];
#endif
switch (cause) {
case UbiquityStoreManagerErrorCauseDeleteStore:
case UbiquityStoreManagerErrorCauseDeleteLogs:
case UbiquityStoreManagerErrorCauseCreateStorePath:
case UbiquityStoreManagerErrorCauseClearStore:
break;
case UbiquityStoreManagerErrorCauseOpenLocalStore: {
wrn(@"Local store could not be opened, resetting it.");
#ifdef TESTFLIGHT_SDK_VERSION
[TestFlight passCheckpoint:MPCheckpointLocalStoreIncompatible];
#endif
[[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;
}
case UbiquityStoreManagerErrorCauseOpenCloudStore: {
wrn(@"iCloud store could not be opened, resetting it.");
#ifdef TESTFLIGHT_SDK_VERSION
[TestFlight passCheckpoint:MPCheckpointCloudStoreIncompatible];
#endif
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointCloudStoreIncompatible
attributes:nil];
manager.hardResetEnabled = YES;
[manager hardResetCloudStorage];
break;
}
}
}
#pragma mark - Import / Export
- (MPImportResult)importSites:(NSString *)importedSitesString withPassword:(NSString *)password
askConfirmation:(BOOL(^)(NSUInteger importCount, NSUInteger deleteCount))confirmation {
inf(@"Importing sites.");
static NSRegularExpression *headerPattern, *sitePattern;
__autoreleasing NSError *error;
if (!headerPattern) {
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];
if (error)
err(@"Error loading the site pattern: %@", error);
}
if (!headerPattern || !sitePattern)
return MPImportResultInternalError;
NSData *key = nil;
NSString *keyIDHex = nil, *userName = nil;
MPUserEntity *user = nil;
BOOL headerStarted = NO, headerEnded = NO, clearText = NO;
NSArray *importedSiteLines = [importedSitesString componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]];
NSMutableSet *elementsToDelete = [NSMutableSet set];
NSMutableArray *importedSiteElements = [NSMutableArray arrayWithCapacity:[importedSiteLines count]];
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass([MPElementEntity class])];
for (NSString *importedSiteLine in importedSiteLines) {
if ([importedSiteLine hasPrefix:@"#"]) {
// Comment or header
if (!headerStarted) {
if ([importedSiteLine isEqualToString:@"##"])
headerStarted = YES;
continue;
}
if (headerEnded)
continue;
if ([importedSiteLine isEqualToString:@"##"]) {
headerEnded = YES;
continue;
}
// Header
if ([headerPattern numberOfMatchesInString:importedSiteLine options:0 range:NSMakeRange(0, [importedSiteLine length])] != 1) {
err(@"Invalid header format in line: %@", importedSiteLine);
return MPImportResultMalformedInput;
}
NSTextCheckingResult *headerElements = [[headerPattern matchesInString:importedSiteLine options:0
range:NSMakeRange(0, [importedSiteLine length])] lastObject];
NSString *headerName = [importedSiteLine substringWithRange:[headerElements rangeAtIndex:1]];
NSString *headerValue = [importedSiteLine substringWithRange:[headerElements rangeAtIndex:2]];
if ([headerName isEqualToString:@"User Name"]) {
userName = headerValue;
key = keyForPassword(password, userName);
NSFetchRequest *userFetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass([MPUserEntity class])];
userFetchRequest.predicate = [NSPredicate predicateWithFormat:@"name == %@", userName];
user = [[self.managedObjectContext executeFetchRequest:fetchRequest error:&error] lastObject];
}
if ([headerName isEqualToString:@"Key ID"]) {
if (![(keyIDHex = headerValue) isEqualToString:[keyIDForKey(key) encodeHex]])
return MPImportResultInvalidPassword;
}
if ([headerName isEqualToString:@"Passwords"]) {
if ([headerValue isEqualToString:@"VISIBLE"])
clearText = YES;
}
continue;
}
if (!headerEnded)
continue;
if (!keyIDHex || ![userName length])
return MPImportResultMalformedInput;
if (![importedSiteLine length])
continue;
// Site
if ([sitePattern numberOfMatchesInString:importedSiteLine options:0 range:NSMakeRange(0, [importedSiteLine length])] != 1) {
err(@"Invalid site format in line: %@", importedSiteLine);
return MPImportResultMalformedInput;
}
NSTextCheckingResult *siteElements = [[sitePattern matchesInString:importedSiteLine options:0
range:NSMakeRange(0, [importedSiteLine length])] lastObject];
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]];
// 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);
if (!existingSites) {
err(@"Lookup of existing sites failed for site: %@, user: %@", name, user.userID);
return MPImportResultInternalError;
}
[elementsToDelete addObjectsFromArray:existingSites];
[importedSiteElements addObject:[NSArray arrayWithObjects:lastUsed, uses, type, name, exportContent, nil]];
}
}
inf(@"Importing %u sites, deleting %u sites, for user: %@",
[importedSiteElements count], [elementsToDelete count], [MPUserEntity idFor:userName]);
// Ask for confirmation to import these sites.
if (!confirmation([importedSiteElements count], [elementsToDelete count])) {
inf(@"Import cancelled.");
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];
// 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];
// 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])
if (clearText)
[element importClearTextContent:exportContent usingKey:key];
else
[element importProtectedContent:exportContent];
}
[self saveContext];
inf(@"Import completed successfully.");
#ifdef TESTFLIGHT_SDK_VERSION
[TestFlight passCheckpoint:MPCheckpointSitesImported];
#endif
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointSitesImported
attributes:nil];
return MPImportResultSuccess;
}
- (NSString *)exportSitesShowingPasswords:(BOOL)showPasswords {
inf(@"Exporting sites, %@, for: %@", showPasswords? @"showing passwords": @"omitting passwords", self.activeUser.userID);
// Header.
NSMutableString *export = [NSMutableString new];
[export appendFormat:@"# Master Password site export\n"];
if (showPasswords)
[export appendFormat:@"# Export of site names and passwords in clear-text.\n"];
else
[export appendFormat:@"# Export of site names and stored passwords (unless device-private) encrypted with the master key.\n"];
[export appendFormat:@"# \n"];
[export appendFormat:@"##\n"];
[export appendFormat:@"# Version: %@\n", [PearlInfoPlist get].CFBundleVersion];
[export appendFormat:@"# User Name: %@\n", self.activeUser.name];
[export appendFormat:@"# Key ID: %@\n", [self.activeUser.keyID encodeHex]];
[export appendFormat:@"# Date: %@\n", [[NSDateFormatter rfc3339DateFormatter] stringFromDate:[NSDate date]]];
if (showPasswords)
[export appendFormat:@"# Passwords: VISIBLE\n"];
else
[export appendFormat:@"# Passwords: PROTECTED\n"];
[export appendFormat:@"##\n"];
[export appendFormat:@"#\n"];
[export appendFormat:@"# Last Times Password Site\tSite\n"];
[export appendFormat:@"# used used type name\tpassword\n"];
// Sites.
for (MPElementEntity *element in self.activeUser.elements) {
NSDate *lastUsed = element.lastUsed;
NSUInteger uses = element.uses;
MPElementType type = element.type;
NSString *name = element.name;
NSString *content = nil;
// Determine the content to export.
if (!(type & MPElementFeatureDevicePrivate)) {
if (showPasswords)
content = element.content;
else
if (type & MPElementFeatureExportContent)
content = element.exportContent;
}
[export appendFormat:@"%@ %8d %8d %20s\t%@\n",
[[NSDateFormatter rfc3339DateFormatter] stringFromDate:lastUsed], uses, type, [name cStringUsingEncoding:NSUTF8StringEncoding], content
? content: @""];
}
#ifdef TESTFLIGHT_SDK_VERSION
[TestFlight passCheckpoint:MPCheckpointSitesExported];
#endif
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointSitesExported attributes:nil];
return export;
}
@end

View File

@@ -6,13 +6,14 @@
// Copyright (c) 2012 Lyndir. All rights reserved.
//
#import "Pearl.h"
@interface MPConfig : PearlConfig
@property (nonatomic, retain) NSNumber *dataStoreError;
@property (nonatomic, retain) NSNumber *storeKeyPhrase;
@property (nonatomic, retain) NSNumber *rememberKeyPhrase;
@property (nonatomic, retain) NSNumber *helpHidden;
@property (nonatomic, retain) NSNumber *showQuickStart;
@property (nonatomic, retain) NSNumber *rememberLogin;
@property (nonatomic, retain) NSNumber *iCloud;
@property (nonatomic, retain) NSNumber *iCloudDecided;
+ (MPConfig *)get;

View File

@@ -6,24 +6,26 @@
// Copyright (c) 2012 Lyndir. All rights reserved.
//
#import "MPConfig.h"
#import "MPAppDelegate.h"
@implementation MPConfig
@dynamic dataStoreError, storeKeyPhrase, rememberKeyPhrase, helpHidden, showQuickStart;
@dynamic rememberLogin, iCloud, iCloudDecided;
- (id)init {
if(!(self = [super init]))
return self;
if (!(self = [super init]))
return nil;
[self.defaults registerDefaults:[NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:NO], NSStringFromSelector(@selector(dataStoreError)),
[NSNumber numberWithBool:NO], NSStringFromSelector(@selector(storeKeyPhrase)),
[NSNumber numberWithBool:YES], NSStringFromSelector(@selector(rememberKeyPhrase)),
[NSNumber numberWithBool:NO], NSStringFromSelector(@selector(helpHidden)),
[NSNumber numberWithBool:YES], NSStringFromSelector(@selector(showQuickStart)),
[NSNumber numberWithBool:YES], NSStringFromSelector(@selector(askForReviews)),
[NSNumber numberWithBool:NO], NSStringFromSelector(@selector(rememberLogin)),
[NSNumber numberWithBool:NO], NSStringFromSelector(@selector(iCloud)),
[NSNumber numberWithBool:NO], NSStringFromSelector(@selector(iCloudDecided)),
nil]];
self.delegate = [MPAppDelegate get];
return self;
}

View File

@@ -1,24 +1,23 @@
//
// MPElementEntity.h
// MasterPassword
// MasterPassword-iOS
//
// Created by Maarten Billemont on 02/01/12.
// Created by Maarten Billemont on 11/06/12.
// Copyright (c) 2012 Lyndir. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
@class MPUserEntity;
@interface MPElementEntity : NSManagedObject
@property (nonatomic, retain) NSString *name;
@property (nonatomic, retain) NSString *mpHashHex;
@property (nonatomic, assign) int16_t type;
@property (nonatomic, assign) int16_t uses;
@property (nonatomic, assign) NSTimeInterval lastUsed;
@property (nonatomic, retain, readonly) id content;
- (void)use;
@property (nonatomic, retain) id content;
@property (nonatomic, retain) NSDate * lastUsed;
@property (nonatomic, retain) NSString * name;
@property (nonatomic, retain) NSNumber * type_;
@property (nonatomic, retain) NSNumber * uses_;
@property (nonatomic, retain) MPUserEntity *user;
@end

View File

@@ -1,42 +1,22 @@
//
// MPElementEntity.m
// MasterPassword
// MasterPassword-iOS
//
// Created by Maarten Billemont on 02/01/12.
// Created by Maarten Billemont on 11/06/12.
// Copyright (c) 2012 Lyndir. All rights reserved.
//
#import "MPElementEntity.h"
#import "MPUserEntity.h"
@implementation MPElementEntity
@dynamic name;
@dynamic mpHashHex;
@dynamic type;
@dynamic uses;
@dynamic content;
@dynamic lastUsed;
- (void)use {
++self.uses;
self.lastUsed = [[NSDate date] timeIntervalSinceReferenceDate];
}
- (id)content {
@throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"Content implementation missing." userInfo:nil];
}
- (NSString *)description {
return [[self content] description];
}
- (NSString *)debugDescription {
return [NSString stringWithFormat:@"{%@: name=%@, mpHashHex=%@, type=%d, uses=%d, lastUsed=%@}",
NSStringFromClass([self class]), self.name, self.mpHashHex, self.type, self.uses, self.lastUsed];
}
@dynamic name;
@dynamic type_;
@dynamic uses_;
@dynamic user;
@end

View File

@@ -1,8 +1,8 @@
//
// MPElementGeneratedEntity.h
// MasterPassword
// MasterPassword-iOS
//
// Created by Maarten Billemont on 16/01/12.
// Created by Maarten Billemont on 11/06/12.
// Copyright (c) 2012 Lyndir. All rights reserved.
//
@@ -13,6 +13,6 @@
@interface MPElementGeneratedEntity : MPElementEntity
@property (nonatomic, assign) uint16_t counter;
@property (nonatomic, retain) NSNumber * counter_;
@end

View File

@@ -1,31 +1,16 @@
//
// MPElementGeneratedEntity.m
// MasterPassword
// MasterPassword-iOS
//
// Created by Maarten Billemont on 16/01/12.
// Created by Maarten Billemont on 11/06/12.
// Copyright (c) 2012 Lyndir. All rights reserved.
//
#import "MPElementGeneratedEntity.h"
#import "MPAppDelegate.h"
@implementation MPElementGeneratedEntity
@dynamic counter;
- (id)content {
assert(self.type & MPElementTypeClassCalculated);
if (![self.name length])
return nil;
if (self.type & MPElementTypeClassCalculated)
return MPCalculateContent(self.type, self.name, [MPAppDelegate get].keyPhrase, self.counter);
@throw [NSException exceptionWithName:NSInternalInconsistencyException
reason:[NSString stringWithFormat:@"Unsupported type: %d", self.type] userInfo:nil];
}
@dynamic counter_;
@end

View File

@@ -1,8 +1,8 @@
//
// MPElementStoredEntity.h
// MasterPassword
// MasterPassword-iOS
//
// Created by Maarten Billemont on 02/01/12.
// Created by Maarten Billemont on 11/06/12.
// Copyright (c) 2012 Lyndir. All rights reserved.
//
@@ -13,6 +13,6 @@
@interface MPElementStoredEntity : MPElementEntity
@property (nonatomic, retain, readwrite) id content;
@property (nonatomic, retain) id contentObject;
@end

View File

@@ -1,63 +1,16 @@
//
// MPElementStoredEntity.m
// MasterPassword
// MasterPassword-iOS
//
// Created by Maarten Billemont on 02/01/12.
// Created by Maarten Billemont on 11/06/12.
// Copyright (c) 2012 Lyndir. All rights reserved.
//
#import "MPElementStoredEntity.h"
#import "MPAppDelegate.h"
@interface MPElementStoredEntity ()
@property (nonatomic, retain, readwrite) id contentObject;
@end
@implementation MPElementStoredEntity
@dynamic contentObject;
+ (NSDictionary *)queryForDevicePrivateElementNamed:(NSString *)name {
return [PearlKeyChain createQueryForClass:kSecClassGenericPassword
attributes:[NSDictionary dictionaryWithObjectsAndKeys:
@"DevicePrivate", (__bridge id)kSecAttrService,
name, (__bridge id)kSecAttrAccount,
nil]
matches:nil];
}
- (id)content {
assert(self.type & MPElementTypeClassStored);
NSData *encryptedContent;
if (self.type == MPElementTypeStoredDevicePrivate)
encryptedContent = [PearlKeyChain dataOfItemForQuery:[MPElementStoredEntity queryForDevicePrivateElementNamed:self.name]];
else
encryptedContent = self.contentObject;
NSData *decryptedContent = [encryptedContent decryptWithSymmetricKey:[[MPAppDelegate get] keyPhraseWithLength:PearlCryptKeySize]
usePadding:YES];
return [[NSString alloc] initWithBytes:decryptedContent.bytes length:decryptedContent.length encoding:NSUTF8StringEncoding];
}
- (void)setContent:(id)content {
NSData *encryptedContent = [[content description] encryptWithSymmetricKey:[[MPAppDelegate get] keyPhraseWithLength:PearlCryptKeySize]
usePadding:YES];
if (self.type == MPElementTypeStoredDevicePrivate) {
[PearlKeyChain addOrUpdateItemForQuery:[MPElementStoredEntity queryForDevicePrivateElementNamed:self.name]
withAttributes:[NSDictionary dictionaryWithObjectsAndKeys:
encryptedContent, (__bridge id)kSecValueData,
kSecAttrAccessibleWhenUnlockedThisDeviceOnly, (__bridge id)kSecAttrAccessible,
nil]];
self.contentObject = nil;
} else
self.contentObject = encryptedContent;
}
@end

View File

@@ -0,0 +1,44 @@
//
// MPElementEntities.h
// MasterPassword-iOS
//
// Created by Maarten Billemont on 31/05/12.
// Copyright (c) 2012 Lyndir. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "MPElementEntity.h"
#import "MPElementStoredEntity.h"
#import "MPElementGeneratedEntity.h"
#import "MPUserEntity.h"
#define MPAvatarCount 19
@interface MPElementEntity (MP)
@property (assign) MPElementType type;
@property (assign) NSUInteger uses;
- (NSUInteger)use;
- (NSString *)exportContent;
- (void)importProtectedContent:(NSString *)protectedContent;
- (void)importClearTextContent:(NSString *)clearContent usingKey:(NSData *)key;
@end
@interface MPElementGeneratedEntity (MP)
@property (assign) NSUInteger counter;
@end
@interface MPUserEntity (MP)
@property (assign) NSUInteger avatar;
@property (assign) BOOL saveKey;
@property (assign) MPElementType defaultType;
@property (readonly) NSString *userID;
+ (NSString *)idFor:(NSString *)userName;
@end

218
MasterPassword/MPEntities.m Normal file
View File

@@ -0,0 +1,218 @@
//
// MPElementEntities.m
// MasterPassword-iOS
//
// Created by Maarten Billemont on 31/05/12.
// Copyright (c) 2012 Lyndir. All rights reserved.
//
#import "MPEntities.h"
#import "MPAppDelegate.h"
#import "MPAppDelegate_Key.h"
#import "MPUserEntity.h"
@implementation MPElementEntity (MP)
- (MPElementType)type {
return (MPElementType)[self.type_ unsignedIntegerValue];
}
- (void)setType:(MPElementType)aType {
self.type_ = PearlUnsignedInteger(aType);
}
- (NSUInteger)uses {
return [self.uses_ unsignedIntegerValue];
}
- (void)setUses:(NSUInteger)anUses {
self.uses_ = PearlUnsignedInteger(anUses);
}
- (NSUInteger)use {
self.lastUsed = [NSDate date];
return ++self.uses;
}
- (id)content {
@throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"Content implementation missing." userInfo:nil];
}
- (NSString *)exportContent {
return nil;
}
- (void)importProtectedContent:(NSString *)content {
}
- (void)importClearTextContent:(NSString *)content usingKey:(NSData *)key {
}
- (NSString *)description {
return PearlString(@"%@:%@", [self class], [self name]);
}
- (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);
}
@end
@implementation MPElementGeneratedEntity (MP)
- (NSUInteger)counter {
return [self.counter_ unsignedIntegerValue];
}
- (void)setCounter:(NSUInteger)aCounter {
self.counter_ = PearlUnsignedInteger(aCounter);
}
- (id)content {
if (!(self.type & MPElementTypeClassGenerated)) {
err(@"Corrupt element: %@, type: %d is not in MPElementTypeClassGenerated", self.name, self.type);
return nil;
}
if (![self.name length])
return nil;
return MPCalculateContent(self.type, self.name, [MPAppDelegate get].key, self.counter);
}
@end
@implementation MPElementStoredEntity (MP)
+ (NSDictionary *)queryForDevicePrivateElementNamed:(NSString *)name {
return [PearlKeyChain createQueryForClass:kSecClassGenericPassword
attributes:[NSDictionary dictionaryWithObjectsAndKeys:
@"DevicePrivate", (__bridge id)kSecAttrService,
name, (__bridge id)kSecAttrAccount,
nil]
matches:nil];
}
- (id)content {
return [self contentUsingKey:[MPAppDelegate get].key];
}
- (void)setContent:(id)content {
[self setContent:content usingKey:[MPAppDelegate get].key];
}
- (id)contentUsingKey:(NSData *)key {
assert(self.type & MPElementTypeClassStored);
assert([keyIDForKey(key) isEqualToData:self.user.keyID]);
NSData *encryptedContent;
if (self.type & MPElementFeatureDevicePrivate)
encryptedContent = [PearlKeyChain dataOfItemForQuery:[MPElementStoredEntity queryForDevicePrivateElementNamed:self.name]];
else
encryptedContent = self.contentObject;
NSData *decryptedContent = [encryptedContent decryptWithSymmetricKey:subkeyForKey(key, PearlCryptKeySize) padding:YES];
return [[NSString alloc] initWithBytes:decryptedContent.bytes length:decryptedContent.length encoding:NSUTF8StringEncoding];
}
- (void)setContent:(id)content usingKey:(NSData *)key {
assert(self.type & MPElementTypeClassStored);
assert([keyIDForKey(key) isEqualToData:self.user.keyID]);
NSData *encryptedContent = [[content description] encryptWithSymmetricKey:subkeyForKey(key, PearlCryptKeySize) 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]];
self.contentObject = nil;
} else
self.contentObject = encryptedContent;
}
- (NSString *)exportContent {
return [self.contentObject encodeBase64];
}
- (void)importProtectedContent:(NSString *)protectedContent {
self.contentObject = [protectedContent decodeBase64];
}
- (void)importClearTextContent:(NSString *)clearContent usingKey:(NSData *)key {
[self setContent:clearContent usingKey:key];
}
@end
@implementation MPUserEntity (MP)
- (NSUInteger)avatar {
return [self.avatar_ unsignedIntegerValue];
}
- (void)setAvatar:(NSUInteger)anAvatar {
self.avatar_ = PearlUnsignedInteger(anAvatar);
}
- (BOOL)saveKey {
return [self.saveKey_ boolValue];
}
- (void)setSaveKey:(BOOL)aSaveKey {
self.saveKey_ = [NSNumber numberWithBool:aSaveKey];
}
- (MPElementType)defaultType {
return (MPElementType)[self.defaultType_ unsignedIntegerValue];
}
- (NSString *)userID {
return [MPUserEntity idFor:self.name];
}
- (void)setDefaultType:(MPElementType)aDefaultType {
self.defaultType_ = PearlUnsignedInteger(aDefaultType);
}
+ (NSString *)idFor:(NSString *)userName {
return [[userName hashWith:PearlHashSHA1] encodeHex];
}
@end

View File

@@ -1,53 +0,0 @@
//
// MPGuideViewController.m
// MasterPassword
//
// Created by Maarten Billemont on 30/01/12.
// Copyright (c) 2012 Lyndir. All rights reserved.
//
#import "MPGuideViewController.h"
#import "MPAppDelegate.h"
@implementation MPGuideViewController
@synthesize scrollView;
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
- (void)viewDidLoad {
[super viewDidLoad];
[PearlUIUtils autoSizeContent:self.scrollView];
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[MPConfig get].showQuickStart = [NSNumber numberWithBool:NO];
}
- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
[[MPAppDelegate get] loadKeyPhrase:animated];
}
- (void)viewDidUnload {
[self setScrollView:nil];
[super viewDidUnload];
}
- (IBAction)close {
[self.presentingViewController dismissModalViewControllerAnimated:YES];
}
@end

View File

@@ -1,46 +0,0 @@
//
// MPMainViewController.h
// MasterPassword
//
// Created by Maarten Billemont on 24/11/11.
// Copyright (c) 2011 Lyndir. All rights reserved.
//
#import "MPTypeViewController.h"
#import "MPElementEntity.h"
#import "MPSearchDelegate.h"
#import "IASKAppSettingsViewController.h"
@interface MPMainViewController : UIViewController <MPTypeDelegate, UITextFieldDelegate, MPSearchResultsDelegate, UIWebViewDelegate, IASKSettingsDelegate>
@property (strong, nonatomic) MPElementEntity *activeElement;
@property (strong, nonatomic) IBOutlet MPSearchDelegate *searchResultsController;
@property (weak, nonatomic) IBOutlet UITextField *contentField;
@property (weak, nonatomic) IBOutlet UIButton *typeButton;
@property (weak, nonatomic) IBOutlet UIWebView *helpView;
@property (weak, nonatomic) IBOutlet UILabel *siteName;
@property (weak, nonatomic) IBOutlet UILabel *passwordCounter;
@property (weak, nonatomic) IBOutlet UIButton *passwordIncrementer;
@property (weak, nonatomic) IBOutlet UIButton *passwordEdit;
@property (weak, nonatomic) IBOutlet UIView *contentContainer;
@property (weak, nonatomic) IBOutlet UIView *helpContainer;
@property (weak, nonatomic) IBOutlet UIView *contentTipContainer;
@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 UIView *searchTipContainer;
- (IBAction)copyContent;
- (IBAction)incrementPasswordCounter;
- (IBAction)editPassword;
- (IBAction)closeAlert;
- (IBAction)action:(UIBarButtonItem *)sender;
- (BOOL)isHelpVisible;
- (void)toggleHelpAnimated:(BOOL)animated;
- (void)setHelpHidden:(BOOL)hidden animated:(BOOL)animated;
- (void)setHelpChapter:(NSString *)chapter;
@end

View File

@@ -1,482 +0,0 @@
//
// MPMainViewController.m
// MasterPassword
//
// Created by Maarten Billemont on 24/11/11.
// Copyright (c) 2011 Lyndir. All rights reserved.
//
#import "MPMainViewController.h"
#import "MPAppDelegate.h"
#import "MPElementGeneratedEntity.h"
#import "MPElementStoredEntity.h"
#import "IASKAppSettingsViewController.h"
#import <MobileCoreServices/MobileCoreServices.h>
@interface MPMainViewController (Private)
- (void)updateAnimated:(BOOL)animated;
- (void)updateWasAnimated:(BOOL)animated;
- (void)showContentTip:(NSString *)message withIcon:(UIImageView *)icon;
- (void)showAlertWithTitle:(NSString *)title message:(NSString *)message;
- (void)updateElement:(void (^)(void))updateElement;
@end
@implementation MPMainViewController
@synthesize activeElement = _activeElement;
@synthesize searchResultsController = _searchResultsController;
@synthesize typeButton = _typeButton;
@synthesize helpView = _helpView;
@synthesize siteName = _siteName;
@synthesize passwordCounter = _passwordCounter;
@synthesize passwordIncrementer = _passwordIncrementer;
@synthesize passwordEdit = _passwordEdit;
@synthesize contentContainer = _contentContainer;
@synthesize helpContainer = _helpContainer;
@synthesize contentTipContainer = _copiedContainer;
@synthesize alertContainer = _alertContainer;
@synthesize alertTitle = _alertTitle;
@synthesize alertBody = _alertBody;
@synthesize contentTipBody = _contentTipBody;
@synthesize contentTipEditIcon = _contentTipEditIcon;
@synthesize searchTipContainer = _searchTip;
@synthesize contentField = _contentField;
#pragma mark - View lifecycle
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
return [[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad || interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown;
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([[segue identifier] isEqualToString:@"MP_Main_ChooseType"])
[[segue destinationViewController] setDelegate:self];
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
self.searchTipContainer.hidden = NO;
if (!self.activeElement.name)
[UIView animateWithDuration:animated? 0.2f: 0 animations:^{
self.searchTipContainer.alpha = 1;
}];
[self setHelpHidden:[[MPConfig get].helpHidden boolValue] animated:animated];
[self updateAnimated:animated];
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
self.searchTipContainer.hidden = YES;
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
}
- (void)viewDidLoad {
self.view.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"ui_background"]];
// Put the search tip on the window so it's above the nav bar.
if (![self.searchTipContainer.superview isEqual:self.navigationController.navigationBar.superview]) {
CGRect frameInWindow = [self.searchTipContainer.window convertRect:self.searchTipContainer.frame
fromView:self.searchTipContainer.superview];
[self.searchTipContainer removeFromSuperview];
[self.navigationController.navigationBar.superview addSubview:self.searchTipContainer];
self.searchTipContainer.frame = [self.searchTipContainer.window convertRect:frameInWindow
toView:self.searchTipContainer.superview];
}
[[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillResignActiveNotification object:nil queue:[NSOperationQueue mainQueue]
usingBlock:^(NSNotification *note) {
if (![MPAppDelegate get].keyPhrase) {
self.activeElement = nil;
[self updateAnimated:NO];
}
}];
[[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidBecomeActiveNotification object:nil queue:[NSOperationQueue mainQueue]
usingBlock:^(NSNotification *note) {
if (![MPAppDelegate get].keyPhrase) {
self.activeElement = nil;
[self updateAnimated:NO];
}
}];
self.alertBody.text = nil;
self.contentTipEditIcon.alpha = 0;
[super viewDidLoad];
}
- (void)viewDidUnload {
[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 setContentContainer:nil];
[self setHelpContainer:nil];
[self setContentTipContainer:nil];
[self setAlertContainer:nil];
[self setAlertTitle:nil];
[self setAlertBody:nil];
[self setContentTipBody:nil];
[self setContentTipEditIcon:nil];
[self setSearchTipContainer:nil];
[super viewDidUnload];
}
- (void)updateAnimated:(BOOL)animated {
[[MPAppDelegate get] saveContext];
if (animated)
[UIView animateWithDuration:0.3f animations:^{
[self updateWasAnimated:animated];
}];
else
[self updateWasAnimated:animated];
}
- (void)updateWasAnimated:(BOOL)animated {
[self setHelpChapter:self.activeElement? @"2": @"1"];
self.siteName.text = self.activeElement.name;
self.passwordCounter.alpha = self.activeElement.type & MPElementTypeClassCalculated? 0.5f: 0;
self.passwordIncrementer.alpha = self.activeElement.type & MPElementTypeClassCalculated? 0.5f: 0;
self.passwordEdit.alpha = self.activeElement.type & MPElementTypeClassStored? 0.5f: 0;
[self.typeButton setTitle:NSStringFromMPElementType(self.activeElement.type)
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 = [NSString stringWithFormat:@"%u", ((MPElementGeneratedEntity *) self.activeElement).counter];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
NSString *description = self.activeElement.description;
dispatch_async(dispatch_get_main_queue(), ^{
self.contentField.text = description;
});
});
}
- (BOOL)isHelpVisible {
return self.helpContainer.frame.origin.y < 400;
}
- (void)toggleHelpAnimated:(BOOL)animated {
[self setHelpHidden:[self isHelpVisible] animated:animated];
}
- (void)setHelpHidden:(BOOL)hidden animated:(BOOL)animated {
[UIView animateWithDuration:animated? 0.3f: 0 animations:^{
if (hidden) {
self.contentContainer.frame = CGRectSetHeight(self.contentContainer.frame, 373);
self.helpContainer.frame = CGRectSetY(self.helpContainer.frame, 416);
[MPConfig get].helpHidden = [NSNumber numberWithBool:YES];
} else {
self.contentContainer.frame = CGRectSetHeight(self.contentContainer.frame, 175);
self.helpContainer.frame = CGRectSetY(self.helpContainer.frame, 216);
[MPConfig get].helpHidden = [NSNumber numberWithBool:NO];
}
}];
}
- (void)setHelpChapter:(NSString *)chapter {
#ifndef PRODUCTION
[TestFlight passCheckpoint:[NSString stringWithFormat:MPTestFlightCheckpointHelpChapter, chapter]];
#endif
[self.helpView loadRequest:
[NSURLRequest requestWithURL:
[NSURL URLWithString:[NSString stringWithFormat:@"#%@", chapter] relativeToURL:
[[NSBundle mainBundle] URLForResource:@"help" withExtension:@"html"]]]];
NSString *error = [self.helpView stringByEvaluatingJavaScriptFromString:[NSString stringWithFormat:@"setClass('%@');",
ClassNameFromMPElementType(self.activeElement.type)]];
if (error.length)
err(@"setClass: %@", error);
}
- (void)showContentTip:(NSString *)message withIcon:(UIImageView *)icon {
self.contentTipBody.text = message;
icon.hidden = NO;
[UIView animateWithDuration:0.2f animations:^{
self.contentTipContainer.alpha = 1;
} completion:^(BOOL finished) {
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, 5.0f * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
[UIView animateWithDuration:0.2f animations:^{
self.contentTipContainer.alpha = 0;
} completion:^(BOOL finished) {
icon.hidden = YES;
}];
});
}];
}
- (void)showAlertWithTitle:(NSString *)title message:(NSString *)message {
self.alertTitle.text = title;
NSRange scrollRange = NSMakeRange(self.alertBody.text.length, message.length);
if ([self.alertBody.text length])
self.alertBody.text = [NSString stringWithFormat:@"%@\n\n---\n\n%@", self.alertBody.text, message];
else
self.alertBody.text = message;
[self.alertBody scrollRangeToVisible:scrollRange];
[UIView animateWithDuration:0.2f animations:^{
self.alertContainer.alpha = 1;
}];
}
#pragma mark - Protocols
- (IBAction)copyContent {
if (!self.activeElement)
return;
[[UIPasteboard generalPasteboard] setValue:self.activeElement.content
forPasteboardType:(id)kUTTypeUTF8PlainText];
[self showContentTip:@"Copied!" withIcon:nil];
#ifndef PRODUCTION
[TestFlight passCheckpoint:MPTestFlightCheckpointCopyToPasteboard];
#endif
}
- (IBAction)incrementPasswordCounter {
if (![self.activeElement isKindOfClass:[MPElementGeneratedEntity class]])
// Not of a type that supports a password counter;
return;
[self updateElement:^{
++((MPElementGeneratedEntity *) self.activeElement).counter;
}];
#ifndef PRODUCTION
[TestFlight passCheckpoint:MPTestFlightCheckpointIncrementPasswordCounter];
#endif
}
- (void)updateElement:(void (^)(void))updateElement {
// Update password counter.
NSString *oldPassword = self.activeElement.description;
updateElement();
NSString *newPassword = self.activeElement.description;
[self updateAnimated:YES];
// Show new and old password.
if ([oldPassword length] && ![oldPassword isEqualToString:newPassword])
[self showAlertWithTitle:@"Password Changed!" message:l(@"The password for %@ has changed.\n\n"
@"Don't forget to update the site with your new password! "
@"Your old password was:\n"
@"%@", self.activeElement.name, oldPassword)];
}
- (IBAction)editPassword {
if (self.activeElement.type & MPElementTypeClassStored) {
self.contentField.enabled = YES;
[self.contentField becomeFirstResponder];
}
#ifndef PRODUCTION
[TestFlight passCheckpoint:MPTestFlightCheckpointEditPassword];
#endif
}
- (IBAction)closeAlert {
[UIView animateWithDuration:0.3f animations:^{
self.alertContainer.alpha = 0;
} completion:^(BOOL finished) {
self.alertBody.text = nil;
}];
#ifndef PRODUCTION
[TestFlight passCheckpoint:MPTestFlightCheckpointCloseAlert];
#endif
}
- (IBAction)action:(id)sender {
[PearlSheet showSheetWithTitle:nil message:nil viewStyle:UIActionSheetStyleAutomatic
tappedButtonBlock:^(UIActionSheet *sheet, NSInteger buttonIndex) {
if (buttonIndex == [sheet cancelButtonIndex])
return;
switch (buttonIndex - [sheet firstOtherButtonIndex]) {
case 0:
[self toggleHelpAnimated:YES];
break;
case 1:
[self setHelpChapter:@"faq"];
[self setHelpHidden:NO animated:YES];
break;
case 2:
[[MPAppDelegate get] showGuide];
break;
case 3: {
IASKAppSettingsViewController *settingsVC = [IASKAppSettingsViewController new];
settingsVC.delegate = self;
[self.navigationController pushViewController:settingsVC animated:YES];
break;
}
#ifndef PRODUCTION
case 4:
[TestFlight openFeedbackView];
break;
case 5: {
#else
case 4: {
#endif
[[MPAppDelegate get] signOut];
break;
}
}
#ifndef PRODUCTION
[TestFlight passCheckpoint:MPTestFlightCheckpointAction];
#endif
} cancelTitle:[PearlStrings get].commonButtonCancel destructiveTitle:nil
otherTitles:
[self isHelpVisible]? @"Hide Help": @"Show Help", @"FAQ", @"Tutorial", @"Settings",
#ifndef PRODUCTION
@"Feedback",
#endif
@"Sign Out",
nil];
}
- (MPElementType)selectedType {
return self.activeElement.type;
}
- (void)didSelectType:(MPElementType)type {
[self updateElement:^{
// Update password type.
if (ClassFromMPElementType(type) != ClassFromMPElementType(self.activeElement.type))
// Type requires a different class of element. Recreate the element.
[[MPAppDelegate managedObjectContext] performBlockAndWait:^{
MPElementEntity *newElement = [NSEntityDescription insertNewObjectForEntityForName:ClassNameFromMPElementType(type)
inManagedObjectContext:[MPAppDelegate managedObjectContext]];
newElement.name = self.activeElement.name;
newElement.mpHashHex = self.activeElement.mpHashHex;
newElement.uses = self.activeElement.uses;
newElement.lastUsed = self.activeElement.lastUsed;
[[MPAppDelegate managedObjectContext] deleteObject:self.activeElement];
self.activeElement = newElement;
}];
self.activeElement.type = type;
#ifndef PRODUCTION
[TestFlight passCheckpoint:[NSString stringWithFormat:MPTestFlightCheckpointSelectType, NSStringFromMPElementType(type)]];
#endif
if (type & MPElementTypeClassStored && ![self.activeElement.description length])
[self showContentTip:@"Tap to set a password." withIcon:self.contentTipEditIcon];
}];
}
- (void)didSelectElement:(MPElementEntity *)element {
if (element) {
self.activeElement = element;
[self.activeElement use];
[self.searchDisplayController setActive:NO animated:YES];
self.searchDisplayController.searchBar.text = self.activeElement.name;
#ifndef PRODUCTION
[TestFlight passCheckpoint:MPTestFlightCheckpointSelectElement];
#endif
}
[self updateAnimated:YES];
}
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
if (textField == self.contentField)
[self.contentField resignFirstResponder];
return YES;
}
- (void)textFieldDidEndEditing:(UITextField *)textField {
if (textField == self.contentField) {
self.contentField.enabled = NO;
if (![self.activeElement isKindOfClass:[MPElementStoredEntity class]])
// Not of a type whose content can be edited.
return;
if ([((MPElementStoredEntity *) self.activeElement).content isEqual:self.contentField.text])
// Content hasn't changed.
return;
[self updateElement:^{
((MPElementStoredEntity *) self.activeElement).content = self.contentField.text;
}];
}
}
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request
navigationType:(UIWebViewNavigationType)navigationType {
if (navigationType == UIWebViewNavigationTypeLinkClicked) {
#ifndef PRODUCTION
[TestFlight passCheckpoint:MPTestFlightCheckpointExternalLink];
#endif
[[UIApplication sharedApplication] openURL:[request URL]];
return NO;
}
return YES;
}
- (void)settingsViewControllerDidEnd:(IASKAppSettingsViewController *)sender {
while ([self.navigationController.viewControllers containsObject:sender])
[self.navigationController popViewControllerAnimated:YES];
}
@end

View File

@@ -1,28 +0,0 @@
//
// MPSearchDelegate.h
// MasterPassword
//
// Created by Maarten Billemont on 04/01/12.
// Copyright (c) 2012 Lyndir. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "MPElementEntity.h"
@protocol MPSearchResultsDelegate <NSObject>
- (void)didSelectElement:(MPElementEntity *)element;
@end
@interface MPSearchDelegate : NSObject <UITableViewDelegate, UITableViewDataSource, UISearchBarDelegate, UISearchDisplayDelegate, NSFetchedResultsControllerDelegate>
@property (strong, nonatomic) NSDateFormatter *dateFormatter;
@property (strong, nonatomic) NSFetchedResultsController *fetchedResultsController;
@property (strong, nonatomic) NSString *query;
@property (weak, nonatomic) IBOutlet id<MPSearchResultsDelegate> delegate;
@property (weak, nonatomic) IBOutlet UISearchDisplayController *searchDisplayController;
@property (weak, nonatomic) IBOutlet UIView *searchTipContainer;
@end

View File

@@ -1,313 +0,0 @@
//
// MPSearchDelegate.m
// MasterPassword
//
// Created by Maarten Billemont on 04/01/12.
// Copyright (c) 2012 Lyndir. All rights reserved.
//
#import "MPSearchDelegate.h"
#import "MPAppDelegate.h"
#import "MPElementGeneratedEntity.h"
@interface MPSearchDelegate (Private)
- (void)configureCell:(UITableViewCell *)cell inTableView:(UITableView *)tableView atIndexPath:(NSIndexPath *)indexPath;
- (void)update;
@end
@implementation MPSearchDelegate
@synthesize query;
@synthesize dateFormatter;
@synthesize fetchedResultsController;
@synthesize delegate;
@synthesize searchDisplayController;
@synthesize searchTipContainer;
- (id)init {
if (!([super init]))
return nil;
self.dateFormatter = [NSDateFormatter new];
self.dateFormatter.timeStyle = NSDateFormatterNoStyle;
self.dateFormatter.dateStyle = NSDateFormatterShortStyle;
self.query = @"";
return self;
}
- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar {
UITableView *tableView = self.searchDisplayController.searchResultsTableView;
for (NSUInteger section = 0; section < [self numberOfSectionsInTableView:tableView]; ++section) {
NSUInteger rowCount = [self tableView:tableView numberOfRowsInSection:section];
if (!rowCount)
continue;
if (rowCount == 1)
[self tableView:tableView didSelectRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:section]];
break;
}
}
- (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar {
#ifndef PRODUCTION
[TestFlight passCheckpoint:MPTestFlightCheckpointCancelSearch];
#endif
[self.delegate didSelectElement:nil];
}
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText {
if (searchBar.searchResultsButtonSelected && !searchText.length)
searchBar.text = @" ";
self.query = [searchBar.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
if (!self.query)
self.query = @"";
}
- (void)searchDisplayControllerWillBeginSearch:(UISearchDisplayController *)controller {
controller.searchBar.prompt = @"Enter the site's name (eg. apple.com):";
[UIView animateWithDuration:0.2f animations:^{
self.searchTipContainer.alpha = 0;
}];
}
- (void)searchDisplayControllerDidBeginSearch:(UISearchDisplayController *)controller {
controller.searchBar.text = controller.searchBar.searchResultsButtonSelected? @" ": @"";
self.query = @"";
}
- (void)searchDisplayControllerWillEndSearch:(UISearchDisplayController *)controller {
controller.searchBar.prompt = nil;
controller.searchBar.searchResultsButtonSelected = NO;
}
- (void)searchDisplayController:(UISearchDisplayController *)controller didLoadSearchResultsTableView:(UITableView *)tableView {
tableView.backgroundColor = [UIColor blackColor];
tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
tableView.rowHeight = 48.0f;
}
- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString {
[self update];
return YES;
}
- (void)update {
assert(self.query);
assert([MPAppDelegate get].keyPhraseHashHex);
NSFetchRequest *fetchRequest = [[MPAppDelegate get].managedObjectModel
fetchRequestFromTemplateWithName:@"MPElements"
substitutionVariables:[NSDictionary dictionaryWithObjectsAndKeys:
self.query, @"query",
[MPAppDelegate get].keyPhraseHashHex, @"mpHashHex",
nil]];
[fetchRequest setSortDescriptors:
[NSArray arrayWithObject:[[NSSortDescriptor alloc] initWithKey:@"uses" ascending:NO]]];
self.fetchedResultsController.delegate = nil;
self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:[MPAppDelegate managedObjectContext]
sectionNameKeyPath:nil cacheName:nil];
self.fetchedResultsController.delegate = self;
NSError *error;
if (![self.fetchedResultsController performFetch:&error])
err(@"Couldn't fetch elements: %@", error);
}
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller {
[self.searchDisplayController.searchResultsTableView beginUpdates];
}
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject
atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath {
UITableView *tableView = self.searchDisplayController.searchResultsTableView;
switch(type) {
case NSFetchedResultsChangeInsert:
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeUpdate:
[self configureCell:[tableView cellForRowAtIndexPath:indexPath]
inTableView:tableView atIndexPath:indexPath];
break;
case NSFetchedResultsChangeMove:
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
withRowAnimation:UITableViewRowAnimationFade];
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]
withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id<NSFetchedResultsSectionInfo>)sectionInfo
atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type {
UITableView *tableView = self.searchDisplayController.searchResultsTableView;
switch(type) {
case NSFetchedResultsChangeInsert:
[tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex]
withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex]
withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
[self.searchDisplayController.searchResultsTableView endUpdates];
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return [[self.fetchedResultsController sections] count] + ([self.query length]? 1: 0);
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if (section < [[self.fetchedResultsController sections] count])
return [[[self.fetchedResultsController sections] objectAtIndex:section] numberOfObjects];
return 1;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"MPElementSearch"];
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"MPElementSearch"];
UIImageView *backgroundImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"ui_list_middle"]];
backgroundImageView.frame = CGRectMake(-5, 0, 330, 34);
backgroundImageView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
backgroundImageView.contentStretch = CGRectMake(0.2f, 0.2f, 0.6f, 0.6f);
UIView *backgroundView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 34)];
[backgroundView addSubview:backgroundImageView];
backgroundView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
cell.backgroundView = backgroundView;
cell.textLabel.backgroundColor = [UIColor clearColor];
cell.textLabel.textColor = [UIColor whiteColor];
cell.detailTextLabel.backgroundColor = [UIColor clearColor];
cell.detailTextLabel.textColor = [UIColor lightGrayColor];
cell.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
cell.clipsToBounds = YES;
}
[self configureCell:cell inTableView:tableView atIndexPath:indexPath];
return cell;
}
- (void)configureCell:(UITableViewCell *)cell inTableView:(UITableView *)tableView atIndexPath:(NSIndexPath *)indexPath {
if (indexPath.section < [[self.fetchedResultsController sections] count]) {
MPElementEntity *element = [self.fetchedResultsController objectAtIndexPath:indexPath];
cell.textLabel.text = element.name;
cell.detailTextLabel.text = [NSString stringWithFormat:@"Used %d times, last on %@",
element.uses, [self.dateFormatter stringFromDate:[NSDate dateWithTimeIntervalSinceReferenceDate:element.lastUsed]]];
} else {
// "New" section
cell.textLabel.text = self.query;
cell.detailTextLabel.text = @"Create a new site.";
}
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.section < [[self.fetchedResultsController sections] count])
[self.delegate didSelectElement:[self.fetchedResultsController objectAtIndexPath:indexPath]];
else {
// "New" section.
NSString *siteName = self.query;
[PearlAlert showAlertWithTitle:@"New Site"
message:l(@"Do you want to create a new site named:\n%@", siteName)
viewStyle:UIAlertViewStyleDefault
tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
[tableView deselectRowAtIndexPath:indexPath animated:YES];
if (buttonIndex == [alert cancelButtonIndex])
return;
[self.fetchedResultsController.managedObjectContext performBlock:^{
MPElementEntity *element = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass([MPElementGeneratedEntity class])
inManagedObjectContext:self.fetchedResultsController.managedObjectContext];
assert([element isKindOfClass:ClassFromMPElementType(element.type)]);
element.name = siteName;
element.mpHashHex = [MPAppDelegate get].keyPhraseHashHex;
dispatch_async(dispatch_get_main_queue(), ^{
[self.delegate didSelectElement:element];
});
}];
} cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:[PearlStrings get].commonButtonYes, nil];
}
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
if (section < [[self.fetchedResultsController sections] count])
return [[[self.fetchedResultsController sections] objectAtIndex:section] name];
return @"";
}
- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView {
return [self.fetchedResultsController sectionIndexTitles];
}
- (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index {
return [self.fetchedResultsController sectionForSectionIndexTitle:title atIndex:index];
}
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.section < [[self.fetchedResultsController sections] count]) {
if (editingStyle == UITableViewCellEditingStyleDelete)
[self.fetchedResultsController.managedObjectContext performBlock:^{
MPElementEntity *element = [self.fetchedResultsController objectAtIndexPath:indexPath];
[self.fetchedResultsController.managedObjectContext deleteObject:element];
#ifndef PRODUCTION
[TestFlight passCheckpoint:MPTestFlightCheckpointDeleteElement];
#endif
}];
}
}
@end

View File

@@ -1,111 +0,0 @@
//
// MPTypeViewController.m
// MasterPassword
//
// Created by Maarten Billemont on 27/11/11.
// Copyright (c) 2011 Lyndir. All rights reserved.
//
#import "MPTypeViewController.h"
@interface MPTypeViewController ()
- (MPElementType)typeAtIndexPath:(NSIndexPath *)indexPath;
@end
@implementation MPTypeViewController
@synthesize delegate;
#pragma mark - View lifecycle
- (void)viewDidLoad {
self.view.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"ui_background"]];
[super viewDidLoad];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
return YES;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [super tableView:tableView cellForRowAtIndexPath:indexPath];
if ([delegate respondsToSelector:@selector(selectedType)])
if ([delegate selectedType] == [self typeAtIndexPath:indexPath])
[cell iterateSubviewsContinueAfter:^BOOL(UIView *subview) {
if ([subview isKindOfClass:[UIImageView class]]) {
UIImageView *imageView = ((UIImageView *)subview);
if (!imageView.highlightedImage)
imageView.highlightedImage = [imageView.image highlightedImage];
imageView.highlighted = YES;
return NO;
}
return YES;
}];
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
assert(self.navigationController.topViewController == self);
[delegate didSelectType:[self typeAtIndexPath:indexPath]];
[self.navigationController popViewControllerAnimated:YES];
}
- (MPElementType)typeAtIndexPath:(NSIndexPath *)indexPath {
switch (indexPath.section) {
case 0: {
// Calculated
switch (indexPath.row) {
case 0:
return MPElementTypeCalculatedLong;
case 1:
return MPElementTypeCalculatedMedium;
case 2:
return MPElementTypeCalculatedShort;
case 3:
return MPElementTypeCalculatedBasic;
case 4:
return MPElementTypeCalculatedPIN;
default:
[NSException raise:NSInternalInconsistencyException
format:@"Unsupported row: %d, when selecting calculated element type.", indexPath.row];
}
break;
}
case 1: {
// Stored
switch (indexPath.row) {
case 0:
return MPElementTypeStoredPersonal;
case 1:
return MPElementTypeStoredDevicePrivate;
default:
[NSException raise:NSInternalInconsistencyException
format:@"Unsupported row: %d, when selecting stored element type.", indexPath.row];
}
break;
}
default:
[NSException raise:NSInternalInconsistencyException
format:@"Unsupported section: %d, when selecting element type.", indexPath.section];
}
@throw nil;
}
@end

View File

@@ -17,55 +17,72 @@ typedef enum {
} MPElementContentType;
typedef enum {
MPElementTypeClassCalculated = 2 << 7,
MPElementTypeClassStored = 2 << 8,
/** Generate the password. */
MPElementTypeClassGenerated = 1 << 4,
/** Store the password. */
MPElementTypeClassStored = 1 << 5,
} MPElementTypeClass;
typedef enum {
MPElementTypeCalculatedLong = MPElementTypeClassCalculated | 0x01,
MPElementTypeCalculatedMedium = MPElementTypeClassCalculated | 0x02,
MPElementTypeCalculatedShort = MPElementTypeClassCalculated | 0x03,
MPElementTypeCalculatedBasic = MPElementTypeClassCalculated | 0x04,
MPElementTypeCalculatedPIN = MPElementTypeClassCalculated | 0x05,
/** Export the key-protected content data. */
MPElementFeatureExportContent = 1 << 10,
/** Never export content. */
MPElementFeatureDevicePrivate = 1 << 11,
} MPElementFeature;
MPElementTypeStoredPersonal = MPElementTypeClassStored | 0x01,
MPElementTypeStoredDevicePrivate = MPElementTypeClassStored | 0x02,
typedef enum {
MPElementTypeGeneratedMaximum = 0x0 | MPElementTypeClassGenerated | 0x0,
MPElementTypeGeneratedLong = 0x1 | MPElementTypeClassGenerated | 0x0,
MPElementTypeGeneratedMedium = 0x2 | MPElementTypeClassGenerated | 0x0,
MPElementTypeGeneratedShort = 0x3 | MPElementTypeClassGenerated | 0x0,
MPElementTypeGeneratedBasic = 0x4 | MPElementTypeClassGenerated | 0x0,
MPElementTypeGeneratedPIN = 0x5 | MPElementTypeClassGenerated | 0x0,
MPElementTypeStoredPersonal = 0x0 | MPElementTypeClassStored | MPElementFeatureExportContent,
MPElementTypeStoredDevicePrivate = 0x1 | MPElementTypeClassStored | MPElementFeatureDevicePrivate,
} MPElementType;
#ifndef PRODUCTION
#define MPTestFlightCheckpointAction @"MPTestFlightCheckpointAction"
#define MPTestFlightCheckpointHelpChapter @"MPTestFlightCheckpointHelpChapter_%@"
#define MPTestFlightCheckpointCopyToPasteboard @"MPTestFlightCheckpointCopyToPasteboard"
#define MPTestFlightCheckpointIncrementPasswordCounter @"MPTestFlightCheckpointIncrementPasswordCounter"
#define MPTestFlightCheckpointEditPassword @"MPTestFlightCheckpointEditPassword"
#define MPTestFlightCheckpointCloseAlert @"MPTestFlightCheckpointCloseAlert"
#define MPTestFlightCheckpointSelectType @"MPTestFlightCheckpointSelectType_%@"
#define MPTestFlightCheckpointSelectElement @"MPTestFlightCheckpointSelectElement"
#define MPTestFlightCheckpointDeleteElement @"MPTestFlightCheckpointDeleteElement"
#define MPTestFlightCheckpointCancelSearch @"MPTestFlightCheckpointCancelSearch"
#define MPTestFlightCheckpointExternalLink @"MPTestFlightCheckpointExternalLink"
#define MPTestFlightCheckpointLaunched @"MPTestFlightCheckpointLaunched"
#define MPTestFlightCheckpointActivated @"MPTestFlightCheckpointActivated"
#define MPTestFlightCheckpointDeactivated @"MPTestFlightCheckpointDeactivated"
#define MPTestFlightCheckpointTerminated @"MPTestFlightCheckpointTerminated"
#define MPTestFlightCheckpointShowGuide @"MPTestFlightCheckpointShowGuide"
#define MPTestFlightCheckpointMPForgotten @"MPTestFlightCheckpointMPForgotten"
#define MPTestFlightCheckpointMPChanged @"MPTestFlightCheckpointMPChanged"
#define MPTestFlightCheckpointMPUnstored @"MPTestFlightCheckpointMPUnstored"
#define MPTestFlightCheckpointMPMismatch @"MPTestFlightCheckpointMPMismatch"
#define MPTestFlightCheckpointMPAsked @"MPTestFlightCheckpointMPAsked"
#define MPTestFlightCheckpointStoreIncompatible @"MPTestFlightCheckpointStoreIncompatible"
#define MPTestFlightCheckpointSetKeyphraseLength @"MPTestFlightCheckpointSetKeyphraseLength_%d"
#endif
#define MPCheckpointAction @"MPCheckpointAction"
#define MPCheckpointHelpChapter @"MPCheckpointHelpChapter"
#define MPCheckpointCopyToPasteboard @"MPCheckpointCopyToPasteboard"
#define MPCheckpointResetPasswordCounter @"MPCheckpointResetPasswordCounter"
#define MPCheckpointIncrementPasswordCounter @"MPCheckpointIncrementPasswordCounter"
#define MPCheckpointEditPassword @"MPCheckpointEditPassword"
#define MPCheckpointCloseAlert @"MPCheckpointCloseAlert"
#define MPCheckpointUseType @"MPCheckpointUseType"
#define MPCheckpointDeleteElement @"MPCheckpointDeleteElement"
#define MPCheckpointCancelSearch @"MPCheckpointCancelSearch"
#define MPCheckpointExternalLink @"MPCheckpointExternalLink"
#define MPCheckpointLaunched @"MPCheckpointLaunched"
#define MPCheckpointActivated @"MPCheckpointActivated"
#define MPCheckpointDeactivated @"MPCheckpointDeactivated"
#define MPCheckpointTerminated @"MPCheckpointTerminated"
#define MPCheckpointShowGuide @"MPCheckpointShowGuide"
#define MPCheckpointForgetSavedKey @"MPCheckpointForgetSavedKey"
#define MPCheckpointChangeMP @"MPCheckpointChangeMP"
#define MPCheckpointLocalStoreIncompatible @"MPCheckpointLocalStoreIncompatible"
#define MPCheckpointCloudStoreIncompatible @"MPCheckpointCloudStoreIncompatible"
#define MPCheckpointSignInFailed @"MPCheckpointSignInFailed"
#define MPCheckpointSignedIn @"MPCheckpointSignedIn"
#define MPCheckpointConfig @"MPCheckpointConfig"
#define MPCheckpointCloud @"MPCheckpointCloud"
#define MPCheckpointCloudEnabled @"MPCheckpointCloudEnabled"
#define MPCheckpointCloudDisabled @"MPCheckpointCloudDisabled"
#define MPCheckpointSitesImported @"MPCheckpointSitesImported"
#define MPCheckpointSitesExported @"MPCheckpointSitesExported"
#define MPNotificationKeySet @"MPNotificationKeySet"
#define MPNotificationKeyUnset @"MPNotificationKeyUnset"
#define MPNotificationStoreUpdated @"MPNotificationStoreUpdated"
#define MPNotificationSignedIn @"MPNotificationKeySet"
#define MPNotificationSignedOut @"MPNotificationKeyUnset"
#define MPNotificationKeyForgotten @"MPNotificationKeyForgotten"
#define MPNotificationElementUsed @"MPNotificationElementUsed"
NSData *keyPhraseForPassword(NSString *password);
NSData *keyPhraseHashForPassword(NSString *password);
NSData *keyPhraseHashForKeyPhrase(NSData *keyPhrase);
NSData *keyForPassword(NSString *password, NSString *username);
NSData *subkeyForKey(NSData *key, NSUInteger subkeyLength);
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 *keyPhrase, uint16_t counter);
NSString *MPCalculateContent(MPElementType type, NSString *name, NSData *key, uint32_t counter);

View File

@@ -6,49 +6,70 @@
// Copyright (c) 2012 Lyndir. All rights reserved.
//
#import "MPTypes.h"
#import "MPElementGeneratedEntity.h"
#import "MPElementStoredEntity.h"
#import "MPEntities.h"
#define MP_salt nil
#define MP_N 16384
#define MP_N 131072
#define MP_r 8
#define MP_p 1
#define MP_dkLen 64
#define MP_hash PearlDigestSHA256
#define MP_hash PearlHashSHA256
NSData *keyPhraseForPassword(NSString *password) {
NSData *keyForPassword(NSString *password, NSString *username) {
return [PearlSCrypt deriveKeyWithLength:MP_dkLen fromPassword:[password dataUsingEncoding:NSUTF8StringEncoding]
usingSalt:MP_salt N:MP_N r:MP_r p:MP_p];
uint32_t nusernameLength = htonl(username.length);
NSDate *start = [NSDate date];
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];
trc(@"User: %@, password: %@ derives to key ID: %@ (took %0.2f)", username, password, [keyIDForKey(key) encodeHex], -[start timeIntervalSinceNow]);
return key;
}
NSData *keyPhraseHashForPassword(NSString *password) {
return keyPhraseHashForKeyPhrase(keyPhraseForPassword(password));
}
NSData *keyPhraseHashForKeyPhrase(NSData *keyPhrase) {
return [keyPhrase hashWith:MP_hash];
NSData *subkeyForKey(NSData *key, NSUInteger subkeyLength) {
return [key subdataWithRange:NSMakeRange(0, MIN(subkeyLength, key.length))];
}
NSData *keyIDForPassword(NSString *password, NSString *username) {
return keyIDForKey(keyForPassword(password, username));
}
NSData *keyIDForKey(NSData *key) {
return [key hashWith:MP_hash];
}
NSString *NSStringFromMPElementType(MPElementType type) {
if (!type)
return nil;
switch (type) {
case MPElementTypeCalculatedLong:
case MPElementTypeGeneratedMaximum:
return @"Maximum Security Password";
case MPElementTypeGeneratedLong:
return @"Long Password";
case MPElementTypeCalculatedMedium:
case MPElementTypeGeneratedMedium:
return @"Medium Password";
case MPElementTypeCalculatedShort:
case MPElementTypeGeneratedShort:
return @"Short Password";
case MPElementTypeCalculatedBasic:
case MPElementTypeGeneratedBasic:
return @"Basic Password";
case MPElementTypeCalculatedPIN:
case MPElementTypeGeneratedPIN:
return @"PIN";
case MPElementTypeStoredPersonal:
@@ -58,7 +79,42 @@ NSString *NSStringFromMPElementType(MPElementType type) {
return @"Device Private Password";
default:
[NSException raise:NSInternalInconsistencyException format:@"Type not supported: %d", type];
Throw(@"Type not supported: %d", type);
}
}
NSString *NSStringShortFromMPElementType(MPElementType type) {
if (!type)
return nil;
switch (type) {
case MPElementTypeGeneratedMaximum:
return @"Maximum";
case MPElementTypeGeneratedLong:
return @"Long";
case MPElementTypeGeneratedMedium:
return @"Medium";
case MPElementTypeGeneratedShort:
return @"Short";
case MPElementTypeGeneratedBasic:
return @"Basic";
case MPElementTypeGeneratedPIN:
return @"PIN";
case MPElementTypeStoredPersonal:
return @"Personal";
case MPElementTypeStoredDevicePrivate:
return @"Device";
default:
Throw(@"Type not supported: %d", type);
}
}
@@ -68,19 +124,22 @@ Class ClassFromMPElementType(MPElementType type) {
return nil;
switch (type) {
case MPElementTypeCalculatedLong:
case MPElementTypeGeneratedMaximum:
return [MPElementGeneratedEntity class];
case MPElementTypeCalculatedMedium:
case MPElementTypeGeneratedLong:
return [MPElementGeneratedEntity class];
case MPElementTypeCalculatedShort:
case MPElementTypeGeneratedMedium:
return [MPElementGeneratedEntity class];
case MPElementTypeCalculatedBasic:
case MPElementTypeGeneratedShort:
return [MPElementGeneratedEntity class];
case MPElementTypeCalculatedPIN:
case MPElementTypeGeneratedBasic:
return [MPElementGeneratedEntity class];
case MPElementTypeGeneratedPIN:
return [MPElementGeneratedEntity class];
case MPElementTypeStoredPersonal:
@@ -90,7 +149,7 @@ Class ClassFromMPElementType(MPElementType type) {
return [MPElementStoredEntity class];
default:
[NSException raise:NSInternalInconsistencyException format:@"Type not supported: %d", type];
Throw(@"Type not supported: %d", type);
}
}
@@ -100,39 +159,62 @@ NSString *ClassNameFromMPElementType(MPElementType type) {
}
static NSDictionary *MPTypes_ciphers = nil;
NSString *MPCalculateContent(MPElementType type, NSString *name, NSData *keyPhrase, uint16_t counter) {
assert(type & MPElementTypeClassCalculated);
NSString *MPCalculateContent(MPElementType type, NSString *name, NSData *key, uint32_t counter) {
if (!(type & MPElementTypeClassGenerated)) {
err(@"Incorrect type (is not MPElementTypeClassGenerated): %d, for: %@", type, name);
return nil;
}
if (!name.length) {
err(@"Missing name.");
return nil;
}
if (!key.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 hash whose bytes will be used for calculating a password: md4(name-keyPhrase)
assert(name && keyPhrase);
uint16_t ncounter = htons(counter);
NSData *keyHash = [[NSData dataByConcatenatingWithDelimitor:'-' datas:
// 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);
NSData *seed = [[NSData dataByConcatenatingDatas:
[@"com.lyndir.masterpassword" dataUsingEncoding:NSUTF8StringEncoding],
[NSData dataWithBytes:&nnameLength length:sizeof(nnameLength)],
[name dataUsingEncoding:NSUTF8StringEncoding],
keyPhrase,
[NSData dataWithBytes:&ncounter length:sizeof(ncounter)],
nil] hashWith:PearlDigestSHA1];
const char *keyBytes = keyHash.bytes;
nil]
hmacWith:PearlHashSHA256 key:key];
trc(@"seed is: %@", seed);
const char *seedBytes = seed.bytes;
// Determine the cipher from the first hash byte.
assert([keyHash length]);
// Determine the cipher from the first seed byte.
assert([seed length]);
NSArray *typeCiphers = [[MPTypes_ciphers valueForKey:ClassNameFromMPElementType(type)]
valueForKey:NSStringFromMPElementType(type)];
NSString *cipher = [typeCiphers objectAtIndex:keyBytes[0] % [typeCiphers count]];
NSString *cipher = [typeCiphers objectAtIndex:htons(seedBytes[0]) % [typeCiphers count]];
trc(@"type %d, ciphers: %@, selected: %@", type, typeCiphers, cipher);
// Encode the content, character by character, using subsequent hash bytes and the cipher.
assert([keyHash length] >= [cipher length] + 1);
// 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) {
const char keyByte = keyBytes[c + 1];
uint16_t keyByte = htons(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)];
[content appendString:[cipherClassCharacters substringWithRange:NSMakeRange(keyByte % [cipherClassCharacters length], 1)]];
trc(@"class %@ has characters: %@, selected: %@", cipherClass, cipherClassCharacters, character);
[content appendString:character];
}
return content;

View File

@@ -1,21 +0,0 @@
//
// MBUnlockViewController.h
// MasterPassword
//
// Created by Maarten Billemont on 22/02/12.
// Copyright (c) 2012 Lyndir. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface MPUnlockViewController : UIViewController <UITextFieldDelegate>
@property (weak, nonatomic) IBOutlet UIImageView *lock;
@property (weak, nonatomic) IBOutlet UIImageView *spinner;
@property (weak, nonatomic) IBOutlet UITextField *field;
@property (weak, nonatomic) IBOutlet UILabel *messageLabel;
@property (weak, nonatomic) IBOutlet UIView *changeMPView;
- (IBAction)changeMP;
@end

View File

@@ -1,205 +0,0 @@
//
// MBUnlockViewController.m
// MasterPassword
//
// Created by Maarten Billemont on 22/02/12.
// Copyright (c) 2012 Lyndir. All rights reserved.
//
#import <QuartzCore/QuartzCore.h>
#import "MPUnlockViewController.h"
#import "MPAppDelegate.h"
typedef enum {
MPLockscreenIdle,
MPLockscreenError,
MPLockscreenSuccess,
MPLockscreenProgress,
} MPLockscreen;
@interface MPUnlockViewController ()
@end
@implementation MPUnlockViewController
@synthesize lock;
@synthesize spinner;
@synthesize field;
@synthesize messageLabel;
@synthesize changeMPView;
- (void)showMessage:(NSString *)message state:(MPLockscreen)state {
__block void(^showMessageAnimation)(void) = ^{
self.lock.alpha = 0.0f;
switch (state) {
case MPLockscreenIdle:
[self.lock setImage:[UIImage imageNamed:@"lock_idle"]];
break;
case MPLockscreenError:
[self.lock setImage:[UIImage imageNamed:@"lock_red"]];
break;
case MPLockscreenSuccess:
[self.lock setImage:[UIImage imageNamed:@"lock_green"]];
break;
case MPLockscreenProgress:
[self.lock setImage:[UIImage imageNamed:@"lock_blue"]];
break;
}
self.lock.alpha = 0.0f;
[UIView animateWithDuration:1.0f animations:^{
self.lock.alpha = 1.0f;
} completion:^(BOOL finished) {
if (finished)
[UIView animateWithDuration:1.0f delay:0 options:UIViewAnimationOptionRepeat | UIViewAnimationOptionAutoreverse animations:^{
self.lock.alpha = 0.5f;
} completion:nil];
}];
[UIView animateWithDuration:0.5f animations:^{
self.messageLabel.alpha = 1.0f;
self.messageLabel.text = message;
}];
};
if (self.messageLabel.alpha)
[UIView animateWithDuration:0.3f animations:^{
self.messageLabel.alpha = 0.0f;
} completion:^(BOOL finished) {
if (finished)
showMessageAnimation();
}];
else
showMessageAnimation();
}
- (void)hideMessage {
[UIView animateWithDuration:0.5f animations:^{
self.messageLabel.alpha = 0.0f;
}];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
- (void)viewDidLoad {
self.messageLabel.text = nil;
self.messageLabel.alpha = 0;
self.changeMPView.alpha = 0;
self.spinner.alpha = 0;
self.field.text = nil;
[[NSNotificationCenter defaultCenter] addObserverForName:MPNotificationKeyForgotten
object:nil queue:nil usingBlock:^(NSNotification *note) {
[self.field becomeFirstResponder];
}];
[super viewDidLoad];
}
- (void)viewDidUnload {
[self setSpinner:nil];
[self setField:nil];
[self setMessageLabel:nil];
[self setLock:nil];
[self setChangeMPView:nil];
[super viewDidUnload];
}
- (void)viewWillAppear:(BOOL)animated {
[[UIApplication sharedApplication] setStatusBarHidden:YES
withAnimation:animated? UIStatusBarAnimationSlide: UIStatusBarAnimationNone];
[super viewWillAppear:animated];
}
- (void)viewWillDisappear:(BOOL)animated {
[[UIApplication sharedApplication] setStatusBarHidden:NO
withAnimation:animated? UIStatusBarAnimationSlide: UIStatusBarAnimationNone];
[super viewWillDisappear:animated];
}
- (void)viewDidAppear:(BOOL)animated {
[self.field becomeFirstResponder];
[super viewDidAppear:animated];
}
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
if ([textField.text length]) {
[textField resignFirstResponder];
return YES;
}
return NO;
}
- (void)textFieldDidEndEditing:(UITextField *)textField {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
@try {
dispatch_async(dispatch_get_main_queue(), ^{
CABasicAnimation *rotate = [CABasicAnimation animationWithKeyPath:@"transform.rotation"];
rotate.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
rotate.fromValue = [NSNumber numberWithFloat:0];
rotate.toValue = [NSNumber numberWithFloat:2 * M_PI];
rotate.repeatCount = MAXFLOAT;
rotate.duration = 3.0;
[self.spinner.layer removeAllAnimations];
[self.spinner.layer addAnimation:rotate forKey:@"transform"];
[UIView animateWithDuration:0.3f animations:^{
self.spinner.alpha = 1.0f;
}];
[self showMessage:@"Checking password..." state:MPLockscreenProgress];
});
if ([[MPAppDelegate get] tryMasterPassword:textField.text])
dispatch_async(dispatch_get_main_queue(), ^{
[self showMessage:@"Success!" state:MPLockscreenSuccess];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 1.5f), dispatch_get_main_queue(), ^{
[self dismissModalViewControllerAnimated:YES];
});
});
else
dispatch_async(dispatch_get_main_queue(), ^{
[self showMessage:@"Not valid." state:MPLockscreenError];
[UIView animateWithDuration:0.5f animations:^{
self.changeMPView.alpha = 1.0f;
}];
});
}
@finally {
dispatch_async(dispatch_get_main_queue(), ^{
[UIView animateWithDuration:0.3f animations:^{
self.spinner.alpha = 0.0f;
} completion:^(BOOL finished) {
[self.spinner.layer removeAllAnimations];
}];
});
}
});
}
- (IBAction)changeMP {
[[MPAppDelegate get] forgetKeyPhrase];
}
@end

View File

@@ -0,0 +1,32 @@
//
// MPUserEntity.h
// MasterPassword-iOS
//
// Created by Maarten Billemont on 11/06/12.
// Copyright (c) 2012 Lyndir. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
@class MPElementEntity;
@interface MPUserEntity : NSManagedObject
@property (nonatomic, retain) NSNumber * avatar_;
@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) NSSet *elements;
@end
@interface MPUserEntity (CoreDataGeneratedAccessors)
- (void)addElementsObject:(MPElementEntity *)value;
- (void)removeElementsObject:(MPElementEntity *)value;
- (void)addElements:(NSSet *)values;
- (void)removeElements:(NSSet *)values;
@end

View File

@@ -0,0 +1,23 @@
//
// MPUserEntity.m
// MasterPassword-iOS
//
// Created by Maarten Billemont on 11/06/12.
// Copyright (c) 2012 Lyndir. All rights reserved.
//
#import "MPUserEntity.h"
#import "MPElementEntity.h"
@implementation MPUserEntity
@dynamic avatar_;
@dynamic keyID;
@dynamic lastUsed;
@dynamic name;
@dynamic saveKey_;
@dynamic defaultType_;
@dynamic elements;
@end

View File

@@ -0,0 +1,29 @@
//
// MPAppDelegate.h
// MasterPassword
//
// Created by Maarten Billemont on 04/03/12.
// Copyright (c) 2012 Lyndir. All rights reserved.
//
#import <Cocoa/Cocoa.h>
#import "MPAppDelegate_Shared.h"
#import "MPPasswordWindowController.h"
@interface MPAppDelegate : MPAppDelegate_Shared<NSApplicationDelegate>
@property (strong) NSStatusItem *statusItem;
@property (strong) MPPasswordWindowController *passwordWindow;
@property (weak) IBOutlet NSMenuItem *lockItem;
@property (weak) IBOutlet NSMenuItem *showItem;
@property (strong) IBOutlet NSMenu *statusMenu;
@property (weak) IBOutlet NSMenuItem *useICloudItem;
@property (weak) IBOutlet NSMenuItem *rememberPasswordItem;
@property (weak) IBOutlet NSMenuItem *savePasswordItem;
+ (MPAppDelegate *)get;
- (IBAction)activate:(id)sender;
- (IBAction)togglePreference:(NSMenuItem *)sender;
@end

View File

@@ -0,0 +1,223 @@
//
// MPAppDelegate.m
// MasterPassword
//
// Created by Maarten Billemont on 04/03/12.
// Copyright (c) 2012 Lyndir. All rights reserved.
//
#import "MPAppDelegate.h"
#import "MPAppDelegate_Key.h"
#import "MPAppDelegate_Store.h"
#import "MPConfig.h"
#import "MPElementEntity.h"
#import <Carbon/Carbon.h>
@implementation MPAppDelegate
@synthesize statusItem;
@synthesize lockItem;
@synthesize showItem;
@synthesize statusMenu;
@synthesize useICloudItem;
@synthesize rememberPasswordItem;
@synthesize savePasswordItem;
@synthesize passwordWindow;
@synthesize key;
@synthesize keyID;
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wfour-char-constants"
static EventHotKeyID MPShowHotKey = {.signature = 'show', .id = 1};
#pragma clang diagnostic pop
+ (void)initialize {
[MPConfig get];
#ifdef DEBUG
[PearlLogger get].autoprintLevel = PearlLogLevelTrace;
#endif
}
+ (MPAppDelegate *)get {
return (MPAppDelegate *)[super get];
}
static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEvent, void *userData) {
// Extract the hotkey ID.
EventHotKeyID hotKeyID;
GetEventParameter(theEvent, kEventParamDirectObject, typeEventHotKeyID,
NULL, sizeof(hotKeyID), NULL, &hotKeyID);
// Check which hotkey this was.
if (hotKeyID.signature == MPShowHotKey.signature && hotKeyID.id == MPShowHotKey.id) {
[((__bridge MPAppDelegate *)userData) activate:nil];
return noErr;
}
return eventNotHandledErr;
}
- (void)showMenu {
self.rememberPasswordItem.state = [[MPConfig get].rememberKey boolValue]? NSOnState: NSOffState;
self.savePasswordItem.state = [[MPConfig get].saveKey boolValue]? NSOnState: NSOffState;
self.showItem.enabled = ![self.passwordWindow.window isVisible];
[self.statusItem popUpStatusItemMenu:self.statusMenu];
}
- (IBAction)activate:(id)sender {
if ([[NSApplication sharedApplication] isActive])
[self applicationDidBecomeActive:nil];
else
[[NSApplication sharedApplication] activateIgnoringOtherApps:YES];
}
- (IBAction)togglePreference:(NSMenuItem *)sender {
if (sender == useICloudItem)
[self.storeManager useiCloudStore:sender.state == NSOffState alertUser:YES];
if (sender == rememberPasswordItem)
[MPConfig get].rememberKey = [NSNumber numberWithBool:![[MPConfig get].rememberKey boolValue]];
if (sender == savePasswordItem)
[MPConfig get].saveKey = [NSNumber numberWithBool:![[MPConfig get].saveKey boolValue]];
}
- (void)didUpdateConfigForKey:(SEL)configKey fromValue:(id)oldValue {
if (configKey == @selector(rememberKey))
self.rememberPasswordItem.state = [[MPConfig get].rememberKey boolValue]? NSOnState: NSOffState;
if (configKey == @selector(saveKey))
self.savePasswordItem.state = [[MPConfig get].saveKey boolValue]? NSOnState: NSOffState;
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if ([keyPath isEqualToString:@"key"]) {
if (self.key)
[self.lockItem setEnabled:YES];
else {
[self.lockItem setEnabled:NO];
[self.passwordWindow close];
}
}
}
- (NSUndoManager *)windowWillReturnUndoManager:(NSWindow *)window {
return [[self managedObjectContext] undoManager];
}
#pragma mark - NSApplicationDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// Setup delegates and listeners.
[MPConfig get].delegate = self;
[self addObserver:self forKeyPath:@"key" options:0 context:nil];
// Initially, use iCloud.
if ([[MPConfig get].firstRun boolValue])
[[self storeManager] useiCloudStore:YES alertUser:YES];
// Status item.
self.statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength];
self.statusItem.title = @"•••";
self.statusItem.highlightMode = YES;
self.statusItem.target = self;
self.statusItem.action = @selector(showMenu);
// Global hotkey.
EventHotKeyRef hotKeyRef;
EventTypeSpec hotKeyEvents[1] = {{.eventClass = kEventClassKeyboard, .eventKind = kEventHotKeyPressed}};
OSStatus status = InstallApplicationEventHandler(NewEventHandlerUPP(MPHotKeyHander), GetEventTypeCount(hotKeyEvents),
hotKeyEvents,
(__bridge void *)self, NULL);
if (status != noErr)
err(@"Error installing application event handler: %d", status);
status = RegisterEventHotKey(35 /* p */, controlKey + cmdKey, MPShowHotKey, GetApplicationEventTarget(), 0, &hotKeyRef);
if (status != noErr)
err(@"Error registering hotkey: %d", status);
}
- (void)applicationWillBecomeActive:(NSNotification *)notification {
if (!self.passwordWindow)
self.passwordWindow = [[MPPasswordWindowController alloc] initWithWindowNibName:@"MPPasswordWindowController"];
}
- (void)applicationDidBecomeActive:(NSNotification *)notification {
static BOOL firstTime = YES;
if (firstTime)
firstTime = NO;
else
[self.passwordWindow showWindow:self];
}
- (void)applicationWillResignActive:(NSNotification *)notification {
if (![[MPConfig get].rememberKey boolValue])
self.key = nil;
}
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender {
// Save changes in the application's managed object context before the application terminates.
if (![self managedObjectContext]) {
return NSTerminateNow;
}
if (![[self managedObjectContext] commitEditing]) {
NSLog(@"%@:%@ unable to commit editing to terminate", [self class], NSStringFromSelector(_cmd));
return NSTerminateCancel;
}
if (![[self managedObjectContext] hasChanges]) {
return NSTerminateNow;
}
NSError *error = nil;
if (![[self managedObjectContext] save:&error]) {
// Customize this code block to include application-specific recovery steps.
BOOL result = [sender presentError:error];
if (result) {
return NSTerminateCancel;
}
NSString *question = NSLocalizedString(@"Could not save changes while quitting. Quit anyway?", @"Quit without saves error question message");
NSString *info = NSLocalizedString(@"Quitting now will lose any changes you have made since the last successful save", @"Quit without saves error question info");
NSString *quitButton = NSLocalizedString(@"Quit anyway", @"Quit anyway button title");
NSString *cancelButton = NSLocalizedString(@"Cancel", @"Cancel button title");
NSAlert *alert = [[NSAlert alloc] init];
[alert setMessageText:question];
[alert setInformativeText:info];
[alert addButtonWithTitle:quitButton];
[alert addButtonWithTitle:cancelButton];
NSInteger answer = [alert runModal];
if (answer == NSAlertAlternateReturn) {
return NSTerminateCancel;
}
}
return NSTerminateNow;
}
#pragma mark - UbiquityStoreManagerDelegate
- (void)ubiquityStoreManager:(UbiquityStoreManager *)manager didSwitchToiCloud:(BOOL)iCloudEnabled {
self.useICloudItem.state = iCloudEnabled? NSOnState: NSOffState;
self.useICloudItem.enabled = !iCloudEnabled;
}
@end

View File

@@ -0,0 +1,24 @@
//
// MPPasswordWindowController.h
// MasterPassword-Mac
//
// Created by Maarten Billemont on 04/03/12.
// Copyright (c) 2012 Lyndir. All rights reserved.
//
#import <Cocoa/Cocoa.h>
@interface MPPasswordWindowController : NSWindowController<NSTextFieldDelegate> {
NSString *_content;
}
@property (strong) NSString *content;
@property (weak) IBOutlet NSTextField *siteField;
@property (weak) IBOutlet NSTextField *contentField;
@property (weak) IBOutlet NSTextField *tipField;
- (void)unlock;
@end

View File

@@ -0,0 +1,249 @@
//
// MPPasswordWindowController.m
// MasterPassword-Mac
//
// Created by Maarten Billemont on 04/03/12.
// Copyright (c) 2012 Lyndir. All rights reserved.
//
#import "MPPasswordWindowController.h"
#import "MPAppDelegate.h"
#import "MPAppDelegate_Key.h"
#import "MPAppDelegate_Store.h"
#import "MPElementEntity.h"
#import "MPElementGeneratedEntity.h"
@interface MPPasswordWindowController ()
@property (nonatomic, strong) NSString *oldSiteName;
@property (nonatomic, strong) NSArray /* MPElementEntity */ *siteResults;
@end
@implementation MPPasswordWindowController
@synthesize oldSiteName, siteResults;
@synthesize siteField;
@synthesize contentField;
@synthesize tipField;
- (void)windowDidLoad {
[self setContent:@""];
[self.tipField setStringValue:@""];
[[NSNotificationCenter defaultCenter] addObserverForName:NSWindowDidBecomeKeyNotification object:self.window queue:nil
usingBlock:^(NSNotification *note) {
[self unlock];
[self.siteField selectText:self];
}];
[[NSNotificationCenter defaultCenter] addObserverForName:NSWindowWillCloseNotification object:self.window queue:nil
usingBlock:^(NSNotification *note) {
[[NSApplication sharedApplication] hide:self];
}];
[[NSNotificationCenter defaultCenter] addObserverForName:NSControlTextDidChangeNotification object:self.siteField queue:nil
usingBlock:^(NSNotification *note) {
NSString *newSiteName = [self.siteField stringValue];
BOOL shouldComplete = [self.oldSiteName length] < [newSiteName length];
self.oldSiteName = newSiteName;
if ([self trySite])
shouldComplete = NO;
if (shouldComplete)
[[[note userInfo] objectForKey:@"NSFieldEditor"] complete:nil];
}];
[super windowDidLoad];
}
- (void)unlock {
if (![MPAppDelegate get].key)
// Try and load the key from the keychain.
[[MPAppDelegate get] loadStoredKey];
if (![MPAppDelegate get].key)
// Ask the user to set the key through his master password.
dispatch_async(dispatch_get_main_queue(), ^{
if ([MPAppDelegate get].key)
return;
NSAlert *alert = [NSAlert alertWithMessageText:@"Master Password is locked."
defaultButton:@"Unlock" alternateButton:@"Change" otherButton:@"Quit"
informativeTextWithFormat:@"Your master password is required to unlock the application."];
NSSecureTextField *passwordField = [[NSSecureTextField alloc] initWithFrame:NSMakeRect(0, 0, 200, 22)];
[alert setAccessoryView:passwordField];
[alert layout];
[passwordField becomeFirstResponder];
[alert beginSheetModalForWindow:self.window modalDelegate:self
didEndSelector:@selector(alertDidEnd:returnCode:contextInfo:) contextInfo:NULL];
});
}
- (void)alertDidEnd:(NSAlert *)alert returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo {
switch (returnCode) {
case NSAlertAlternateReturn:
// "Change" button.
if ([[NSAlert alertWithMessageText:@"Changing Master Password"
defaultButton:nil alternateButton:[PearlStrings get].commonButtonCancel otherButton:nil
informativeTextWithFormat:
@"This will allow you to log in with a different master password.\n\n"
@"Note that you will only see the sites and passwords for the master password you log in with.\n"
@"If you log in with a different master password, your current sites will be unavailable.\n\n"
@"You can always change back to your current master password later.\n"
@"Your current sites and passwords will then become available again."] runModal]
== 1)
[[MPAppDelegate get] forgetKey];
break;
case NSAlertOtherReturn:
// "Quit" button.
[[NSApplication sharedApplication] terminate:self];
return;
case NSAlertDefaultReturn:
// "Unlock" button.
[[MPAppDelegate get] tryMasterPassword:[(NSSecureTextField *)alert.accessoryView stringValue]];
}
}
- (NSArray *)control:(NSControl *)control textView:(NSTextView *)textView completions:(NSArray *)words
forPartialWordRange:(NSRange)charRange indexOfSelectedItem:(NSInteger *)index {
NSString *query = [[control stringValue] substringWithRange:charRange];
if (![query length] || ![MPAppDelegate get].keyID)
return nil;
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass([MPElementEntity class])];
fetchRequest.sortDescriptors = [NSArray arrayWithObject:[[NSSortDescriptor alloc] initWithKey:@"uses_" ascending:NO]];
fetchRequest.predicate = [NSPredicate predicateWithFormat:@"(%@ == '' OR name BEGINSWITH[cd] %@) AND user == %@",
query, query, [MPAppDelegate get].activeUser];
NSError *error = nil;
self.siteResults = [[MPAppDelegate managedObjectContext] executeFetchRequest:fetchRequest error:&error];
if (error)
err(@"Couldn't fetch elements: %@", error);
NSMutableArray *mutableResults = [NSMutableArray arrayWithCapacity:[self.siteResults count] + 1];
if (self.siteResults)
for (MPElementEntity *element in self.siteResults)
[mutableResults addObject:element.name];
// [mutableResults addObject:query]; // For when the app should be able to create new sites.
return mutableResults;
}
- (BOOL)control:(NSControl *)control textView:(NSTextView *)fieldEditor doCommandBySelector:(SEL)commandSelector {
if (commandSelector == @selector(cancel:)) {
[self.window close];
return YES;
}
if (commandSelector == @selector(insertNewline:) && [self.content length]) {
[[NSPasteboard generalPasteboard] declareTypes:[NSArray arrayWithObject:NSStringPboardType] owner:nil];
if ([[NSPasteboard generalPasteboard] setString:self.content forType:NSPasteboardTypeString]) {
self.tipField.alphaValue = 1;
[self.tipField setStringValue:@"Copied!"];
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0f * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^{
[NSAnimationContext beginGrouping];
[[NSAnimationContext currentContext] setDuration:0.2f];
[self.tipField.animator setAlphaValue:0];
[NSAnimationContext endGrouping];
});
[[self findElement] use];
return YES;
} else
wrn(@"Couldn't copy password to pasteboard.");
}
return NO;
}
- (void)controlTextDidEndEditing:(NSNotification *)obj {
if (obj.object == self.siteField)
[self trySite];
}
- (NSString *)content {
return _content;
}
- (void)setContent:(NSString *)content {
_content = content;
NSShadow *shadow = [NSShadow new];
shadow.shadowColor = [NSColor colorWithDeviceWhite:0.0f alpha:0.6f];
shadow.shadowOffset = NSMakeSize(1.0f, -1.0f);
shadow.shadowBlurRadius = 1.2f;
NSMutableParagraphStyle *paragraph = [NSMutableParagraphStyle new];
paragraph.alignment = NSCenterTextAlignment;
[self.contentField setAttributedStringValue:
[[NSAttributedString alloc] initWithString:_content
attributes:[[NSMutableDictionary alloc] initWithObjectsAndKeys:
shadow, NSShadowAttributeName,
paragraph, NSParagraphStyleAttributeName,
nil]]];
}
- (BOOL)trySite {
MPElementEntity *result = [self findElement];
if (!result) {
[self setContent:@""];
[self.tipField setStringValue:@""];
return NO;
}
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
NSString *description = [result.content description];
if (!description)
description = @"";
dispatch_async(dispatch_get_main_queue(), ^{
[self setContent:description];
[self.tipField setStringValue:@"Hit enter to copy the password."];
self.tipField.alphaValue = 1;
});
});
// For when the app should be able to create new sites.
/*
else
[[MPAppDelegate get].managedObjectContext performBlock:^{
MPElementEntity *element = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass([MPElementGeneratedEntity class])
inManagedObjectContext:[MPAppDelegate get].managedObjectContext];
assert([element isKindOfClass:ClassFromMPElementType(element.type)]);
assert([MPAppDelegate get].keyID);
element.name = siteName;
element.keyID = [MPAppDelegate get].keyID;
NSString *description = [element.content description];
[element use];
dispatch_async(dispatch_get_main_queue(), ^{
[self setContent:description];
});
}];
*/
return YES;
}
- (MPElementEntity *)findElement {
for (MPElementEntity *element in self.siteResults)
if ([element.name isEqualToString:[self.siteField stringValue]])
return element;
return nil;
}
@end

View File

@@ -0,0 +1,691 @@
<?xml version="1.0" encoding="UTF-8"?>
<archive type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="8.00">
<data>
<int key="IBDocument.SystemTarget">1070</int>
<string key="IBDocument.SystemVersion">11D50</string>
<string key="IBDocument.InterfaceBuilderVersion">2182</string>
<string key="IBDocument.AppKitVersion">1138.32</string>
<string key="IBDocument.HIToolboxVersion">568.00</string>
<object class="NSMutableDictionary" key="IBDocument.PluginVersions">
<string key="NS.key.0">com.apple.InterfaceBuilder.CocoaPlugin</string>
<string key="NS.object.0">2182</string>
</object>
<array key="IBDocument.IntegratedClassDependencies">
<string>NSTextField</string>
<string>NSTextFieldCell</string>
<string>NSWindowTemplate</string>
<string>NSView</string>
<string>IBNSLayoutConstraint</string>
<string>NSCustomObject</string>
</array>
<array key="IBDocument.PluginDependencies">
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
</array>
<object class="NSMutableDictionary" key="IBDocument.Metadata">
<string key="NS.key.0">PluginDependencyRecalculationVersion</string>
<integer value="1" key="NS.object.0"/>
</object>
<array class="NSMutableArray" key="IBDocument.RootObjects" id="1000">
<object class="NSCustomObject" id="1001">
<string key="NSClassName">MPPasswordWindowController</string>
</object>
<object class="NSCustomObject" id="1003">
<string key="NSClassName">FirstResponder</string>
</object>
<object class="NSCustomObject" id="1004">
<string key="NSClassName">NSApplication</string>
</object>
<object class="NSWindowTemplate" id="45434518">
<int key="NSWindowStyleMask">277</int>
<int key="NSWindowBacking">2</int>
<string key="NSWindowRect">{{600, 530}, {480, 159}}</string>
<int key="NSWTFlags">611845120</int>
<string key="NSWindowTitle">Master Password</string>
<string key="NSWindowClass">NSPanel</string>
<nil key="NSViewClass"/>
<nil key="NSUserInterfaceItemIdentifier"/>
<string key="NSWindowContentMaxSize">{480, 320}</string>
<string key="NSWindowContentMinSize">{480, 134}</string>
<object class="NSView" key="NSWindowView" id="258451033">
<reference key="NSNextResponder"/>
<int key="NSvFlags">256</int>
<array class="NSMutableArray" key="NSSubviews">
<object class="NSTextField" id="291791585">
<reference key="NSNextResponder" ref="258451033"/>
<int key="NSvFlags">268</int>
<string key="NSFrame">{{140, 117}, {200, 22}}</string>
<reference key="NSSuperview" ref="258451033"/>
<reference key="NSWindow"/>
<reference key="NSNextKeyView" ref="226215720"/>
<int key="NSViewLayerContentsRedrawPolicy">2</int>
<string key="NSReuseIdentifierKey">_NS:9</string>
<bool key="NSEnabled">YES</bool>
<object class="NSTextFieldCell" key="NSCell" id="213149334">
<int key="NSCellFlags">-1804468671</int>
<int key="NSCellFlags2">138413120</int>
<string key="NSContents"/>
<object class="NSFont" key="NSSupport" id="590895625">
<string key="NSName">LucidaGrande</string>
<double key="NSSize">13</double>
<int key="NSfFlags">1044</int>
</object>
<string key="NSPlaceholderString">Site name</string>
<string key="NSCellIdentifier">_NS:9</string>
<reference key="NSControlView" ref="291791585"/>
<int key="NSTextBezelStyle">1</int>
<object class="NSColor" key="NSBackgroundColor">
<int key="NSColorSpace">6</int>
<string key="NSCatalogName">System</string>
<string key="NSColorName">textBackgroundColor</string>
<object class="NSColor" key="NSColor" id="370404594">
<int key="NSColorSpace">3</int>
<bytes key="NSWhite">MQA</bytes>
</object>
</object>
<object class="NSColor" key="NSTextColor">
<int key="NSColorSpace">6</int>
<string key="NSCatalogName">System</string>
<string key="NSColorName">textColor</string>
<object class="NSColor" key="NSColor">
<int key="NSColorSpace">3</int>
<bytes key="NSWhite">MAA</bytes>
</object>
</object>
</object>
</object>
<object class="NSTextField" id="226215720">
<reference key="NSNextResponder" ref="258451033"/>
<int key="NSvFlags">268</int>
<string key="NSFrame">{{17, 45}, {446, 64}}</string>
<reference key="NSSuperview" ref="258451033"/>
<reference key="NSWindow"/>
<reference key="NSNextKeyView" ref="1020047619"/>
<int key="NSViewLayerContentsRedrawPolicy">2</int>
<string key="NSReuseIdentifierKey">_NS:9</string>
<string key="NSAntiCompressionPriority">{250, 750}</string>
<bool key="NSEnabled">YES</bool>
<object class="NSTextFieldCell" key="NSCell" id="254722226">
<int key="NSCellFlags">67239424</int>
<int key="NSCellFlags2">138412032</int>
<string key="NSContents">S3cretP4s$w0rD</string>
<object class="NSFont" key="NSSupport">
<string key="NSName">Exo-Black</string>
<double key="NSSize">48</double>
<int key="NSfFlags">16</int>
</object>
<string key="NSCellIdentifier">_NS:9</string>
<reference key="NSControlView" ref="226215720"/>
<bool key="NSDrawsBackground">YES</bool>
<object class="NSColor" key="NSBackgroundColor" id="245864165">
<int key="NSColorSpace">6</int>
<string key="NSCatalogName">System</string>
<string key="NSColorName">controlColor</string>
<object class="NSColor" key="NSColor">
<int key="NSColorSpace">3</int>
<bytes key="NSWhite">MC42NjY2NjY2NjY3AA</bytes>
</object>
</object>
<object class="NSColor" key="NSTextColor">
<int key="NSColorSpace">1</int>
<bytes key="NSRGB">MC40NzQ1MDk4MDM5IDAuODY2NjY2NjY2NyAwLjk4NDMxMzcyNTUAA</bytes>
</object>
</object>
</object>
<object class="NSTextField" id="1020047619">
<reference key="NSNextResponder" ref="258451033"/>
<int key="NSvFlags">268</int>
<string key="NSFrame">{{17, 20}, {446, 17}}</string>
<reference key="NSSuperview" ref="258451033"/>
<reference key="NSWindow"/>
<int key="NSViewLayerContentsRedrawPolicy">2</int>
<string key="NSReuseIdentifierKey">_NS:1505</string>
<bool key="NSEnabled">YES</bool>
<object class="NSTextFieldCell" key="NSCell" id="691402180">
<int key="NSCellFlags">68288064</int>
<int key="NSCellFlags2">138413056</int>
<string key="NSContents">Hit enter to copy the password.</string>
<reference key="NSSupport" ref="590895625"/>
<string key="NSCellIdentifier">_NS:1505</string>
<reference key="NSControlView" ref="1020047619"/>
<reference key="NSBackgroundColor" ref="245864165"/>
<object class="NSColor" key="NSTextColor">
<int key="NSColorSpace">6</int>
<string key="NSCatalogName">System</string>
<string key="NSColorName">controlLightHighlightColor</string>
<reference key="NSColor" ref="370404594"/>
</object>
</object>
</object>
</array>
<string key="NSFrameSize">{480, 159}</string>
<reference key="NSSuperview"/>
<reference key="NSWindow"/>
<reference key="NSNextKeyView" ref="291791585"/>
<bool key="NSViewIsLayerTreeHost">YES</bool>
<int key="NSViewLayerContentsRedrawPolicy">2</int>
<string key="NSReuseIdentifierKey">_NS:21</string>
</object>
<string key="NSScreenRect">{{0, 0}, {1680, 1028}}</string>
<string key="NSMinSize">{480, 150}</string>
<string key="NSMaxSize">{480, 336}</string>
<bool key="NSWindowIsRestorable">YES</bool>
</object>
</array>
<object class="IBObjectContainer" key="IBDocument.Objects">
<array class="NSMutableArray" key="connectionRecords">
<object class="IBConnectionRecord">
<object class="IBOutletConnection" key="connection">
<string key="label">window</string>
<reference key="source" ref="1001"/>
<reference key="destination" ref="45434518"/>
</object>
<int key="connectionID">40</int>
</object>
<object class="IBConnectionRecord">
<object class="IBOutletConnection" key="connection">
<string key="label">siteField</string>
<reference key="source" ref="1001"/>
<reference key="destination" ref="291791585"/>
</object>
<int key="connectionID">41</int>
</object>
<object class="IBConnectionRecord">
<object class="IBOutletConnection" key="connection">
<string key="label">contentField</string>
<reference key="source" ref="1001"/>
<reference key="destination" ref="226215720"/>
</object>
<int key="connectionID">42</int>
</object>
<object class="IBConnectionRecord">
<object class="IBOutletConnection" key="connection">
<string key="label">tipField</string>
<reference key="source" ref="1001"/>
<reference key="destination" ref="1020047619"/>
</object>
<int key="connectionID">95</int>
</object>
<object class="IBConnectionRecord">
<object class="IBOutletConnection" key="connection">
<string key="label">delegate</string>
<reference key="source" ref="45434518"/>
<reference key="destination" ref="1001"/>
</object>
<int key="connectionID">39</int>
</object>
<object class="IBConnectionRecord">
<object class="IBOutletConnection" key="connection">
<string key="label">initialFirstResponder</string>
<reference key="source" ref="45434518"/>
<reference key="destination" ref="291791585"/>
</object>
<int key="connectionID">49</int>
</object>
<object class="IBConnectionRecord">
<object class="IBOutletConnection" key="connection">
<string key="label">delegate</string>
<reference key="source" ref="291791585"/>
<reference key="destination" ref="1001"/>
</object>
<int key="connectionID">43</int>
</object>
</array>
<object class="IBMutableOrderedSet" key="objectRecords">
<array key="orderedObjects">
<object class="IBObjectRecord">
<int key="objectID">0</int>
<array key="object" id="0"/>
<reference key="children" ref="1000"/>
<nil key="parent"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">-2</int>
<reference key="object" ref="1001"/>
<reference key="parent" ref="0"/>
<string key="objectName">File's Owner</string>
</object>
<object class="IBObjectRecord">
<int key="objectID">-1</int>
<reference key="object" ref="1003"/>
<reference key="parent" ref="0"/>
<string key="objectName">First Responder</string>
</object>
<object class="IBObjectRecord">
<int key="objectID">-3</int>
<reference key="object" ref="1004"/>
<reference key="parent" ref="0"/>
<string key="objectName">Application</string>
</object>
<object class="IBObjectRecord">
<int key="objectID">22</int>
<reference key="object" ref="45434518"/>
<array class="NSMutableArray" key="children">
<reference ref="258451033"/>
</array>
<reference key="parent" ref="0"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">23</int>
<reference key="object" ref="258451033"/>
<array class="NSMutableArray" key="children">
<object class="IBNSLayoutConstraint" id="490796257">
<reference key="firstItem" ref="226215720"/>
<int key="firstAttribute">3</int>
<int key="relation">0</int>
<reference key="secondItem" ref="258451033"/>
<int key="secondAttribute">3</int>
<float key="multiplier">1</float>
<object class="IBLayoutConstant" key="constant">
<double key="value">50</double>
</object>
<float key="priority">1000</float>
<int key="scoringType">9</int>
<float key="scoringTypeFloat">40</float>
<int key="contentType">3</int>
<reference key="containingView" ref="258451033"/>
</object>
<object class="IBNSLayoutConstraint" id="751044970">
<reference key="firstItem" ref="291791585"/>
<int key="firstAttribute">3</int>
<int key="relation">1</int>
<reference key="secondItem" ref="258451033"/>
<int key="secondAttribute">3</int>
<float key="multiplier">1</float>
<object class="IBLayoutConstant" key="constant">
<double key="value">20</double>
</object>
<float key="priority">1000</float>
<int key="scoringType">9</int>
<float key="scoringTypeFloat">40</float>
<int key="contentType">3</int>
<reference key="containingView" ref="258451033"/>
</object>
<object class="IBNSLayoutConstraint" id="1014829211">
<reference key="firstItem" ref="258451033"/>
<int key="firstAttribute">4</int>
<int key="relation">1</int>
<reference key="secondItem" ref="226215720"/>
<int key="secondAttribute">4</int>
<float key="multiplier">1</float>
<object class="IBLayoutConstant" key="constant">
<double key="value">20</double>
</object>
<float key="priority">1000</float>
<int key="scoringType">9</int>
<float key="scoringTypeFloat">40</float>
<int key="contentType">3</int>
<reference key="containingView" ref="258451033"/>
</object>
<object class="IBNSLayoutConstraint" id="582018795">
<reference key="firstItem" ref="258451033"/>
<int key="firstAttribute">6</int>
<int key="relation">1</int>
<reference key="secondItem" ref="226215720"/>
<int key="secondAttribute">6</int>
<float key="multiplier">1</float>
<object class="IBLayoutConstant" key="constant">
<double key="value">20</double>
</object>
<float key="priority">1000</float>
<int key="scoringType">9</int>
<float key="scoringTypeFloat">40</float>
<int key="contentType">3</int>
<reference key="containingView" ref="258451033"/>
</object>
<object class="IBNSLayoutConstraint" id="137127436">
<reference key="firstItem" ref="226215720"/>
<int key="firstAttribute">5</int>
<int key="relation">1</int>
<reference key="secondItem" ref="258451033"/>
<int key="secondAttribute">5</int>
<float key="multiplier">1</float>
<object class="IBLayoutConstant" key="constant">
<double key="value">20</double>
</object>
<float key="priority">1000</float>
<int key="scoringType">9</int>
<float key="scoringTypeFloat">40</float>
<int key="contentType">3</int>
<reference key="containingView" ref="258451033"/>
</object>
<reference ref="226215720"/>
<reference ref="291791585"/>
<object class="IBNSLayoutConstraint" id="456428551">
<reference key="firstItem" ref="226215720"/>
<int key="firstAttribute">5</int>
<int key="relation">0</int>
<reference key="secondItem" ref="258451033"/>
<int key="secondAttribute">5</int>
<float key="multiplier">1</float>
<object class="IBNSLayoutSymbolicConstant" key="constant">
<double key="value">20</double>
</object>
<float key="priority">1000</float>
<int key="scoringType">8</int>
<float key="scoringTypeFloat">29</float>
<int key="contentType">3</int>
<reference key="containingView" ref="258451033"/>
</object>
<reference ref="1020047619"/>
<object class="IBNSLayoutConstraint" id="51508433">
<reference key="firstItem" ref="291791585"/>
<int key="firstAttribute">9</int>
<int key="relation">0</int>
<reference key="secondItem" ref="226215720"/>
<int key="secondAttribute">9</int>
<float key="multiplier">1</float>
<object class="IBLayoutConstant" key="constant">
<double key="value">0.0</double>
</object>
<float key="priority">1000</float>
<int key="scoringType">6</int>
<float key="scoringTypeFloat">24</float>
<int key="contentType">2</int>
<reference key="containingView" ref="258451033"/>
</object>
<object class="IBNSLayoutConstraint" id="99093936">
<reference key="firstItem" ref="258451033"/>
<int key="firstAttribute">6</int>
<int key="relation">0</int>
<reference key="secondItem" ref="1020047619"/>
<int key="secondAttribute">6</int>
<float key="multiplier">1</float>
<object class="IBNSLayoutSymbolicConstant" key="constant">
<double key="value">20</double>
</object>
<float key="priority">1000</float>
<int key="scoringType">8</int>
<float key="scoringTypeFloat">29</float>
<int key="contentType">3</int>
<reference key="containingView" ref="258451033"/>
</object>
<object class="IBNSLayoutConstraint" id="536265876">
<reference key="firstItem" ref="258451033"/>
<int key="firstAttribute">4</int>
<int key="relation">0</int>
<reference key="secondItem" ref="1020047619"/>
<int key="secondAttribute">4</int>
<float key="multiplier">1</float>
<object class="IBNSLayoutSymbolicConstant" key="constant">
<double key="value">20</double>
</object>
<float key="priority">1000</float>
<int key="scoringType">8</int>
<float key="scoringTypeFloat">29</float>
<int key="contentType">3</int>
<reference key="containingView" ref="258451033"/>
</object>
<object class="IBNSLayoutConstraint" id="1695776">
<reference key="firstItem" ref="258451033"/>
<int key="firstAttribute">6</int>
<int key="relation">0</int>
<reference key="secondItem" ref="226215720"/>
<int key="secondAttribute">6</int>
<float key="multiplier">1</float>
<object class="IBNSLayoutSymbolicConstant" key="constant">
<double key="value">20</double>
</object>
<float key="priority">1000</float>
<int key="scoringType">8</int>
<float key="scoringTypeFloat">29</float>
<int key="contentType">3</int>
<reference key="containingView" ref="258451033"/>
</object>
<object class="IBNSLayoutConstraint" id="400319127">
<reference key="firstItem" ref="291791585"/>
<int key="firstAttribute">3</int>
<int key="relation">0</int>
<reference key="secondItem" ref="258451033"/>
<int key="secondAttribute">3</int>
<float key="multiplier">1</float>
<object class="IBNSLayoutSymbolicConstant" key="constant">
<double key="value">20</double>
</object>
<float key="priority">1000</float>
<int key="scoringType">8</int>
<float key="scoringTypeFloat">29</float>
<int key="contentType">3</int>
<reference key="containingView" ref="258451033"/>
</object>
<object class="IBNSLayoutConstraint" id="105480775">
<reference key="firstItem" ref="1020047619"/>
<int key="firstAttribute">5</int>
<int key="relation">0</int>
<reference key="secondItem" ref="258451033"/>
<int key="secondAttribute">5</int>
<float key="multiplier">1</float>
<object class="IBNSLayoutSymbolicConstant" key="constant">
<double key="value">20</double>
</object>
<float key="priority">1000</float>
<int key="scoringType">8</int>
<float key="scoringTypeFloat">29</float>
<int key="contentType">3</int>
<reference key="containingView" ref="258451033"/>
</object>
</array>
<reference key="parent" ref="45434518"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">26</int>
<reference key="object" ref="490796257"/>
<reference key="parent" ref="258451033"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">27</int>
<reference key="object" ref="751044970"/>
<reference key="parent" ref="258451033"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">28</int>
<reference key="object" ref="1014829211"/>
<reference key="parent" ref="258451033"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">32</int>
<reference key="object" ref="582018795"/>
<reference key="parent" ref="258451033"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">33</int>
<reference key="object" ref="137127436"/>
<reference key="parent" ref="258451033"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">34</int>
<reference key="object" ref="226215720"/>
<array class="NSMutableArray" key="children">
<reference ref="254722226"/>
</array>
<reference key="parent" ref="258451033"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">35</int>
<reference key="object" ref="291791585"/>
<array class="NSMutableArray" key="children">
<reference ref="213149334"/>
<object class="IBNSLayoutConstraint" id="302161926">
<reference key="firstItem" ref="291791585"/>
<int key="firstAttribute">7</int>
<int key="relation">0</int>
<nil key="secondItem"/>
<int key="secondAttribute">0</int>
<float key="multiplier">1</float>
<object class="IBLayoutConstant" key="constant">
<double key="value">200</double>
</object>
<float key="priority">1000</float>
<int key="scoringType">3</int>
<float key="scoringTypeFloat">9</float>
<int key="contentType">1</int>
<reference key="containingView" ref="291791585"/>
</object>
</array>
<reference key="parent" ref="258451033"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">37</int>
<reference key="object" ref="213149334"/>
<reference key="parent" ref="291791585"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">38</int>
<reference key="object" ref="254722226"/>
<reference key="parent" ref="226215720"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">50</int>
<reference key="object" ref="1020047619"/>
<array class="NSMutableArray" key="children">
<reference ref="691402180"/>
</array>
<reference key="parent" ref="258451033"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">51</int>
<reference key="object" ref="691402180"/>
<reference key="parent" ref="1020047619"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">74</int>
<reference key="object" ref="456428551"/>
<reference key="parent" ref="258451033"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">85</int>
<reference key="object" ref="51508433"/>
<reference key="parent" ref="258451033"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">87</int>
<reference key="object" ref="99093936"/>
<reference key="parent" ref="258451033"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">90</int>
<reference key="object" ref="302161926"/>
<reference key="parent" ref="291791585"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">91</int>
<reference key="object" ref="536265876"/>
<reference key="parent" ref="258451033"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">92</int>
<reference key="object" ref="1695776"/>
<reference key="parent" ref="258451033"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">93</int>
<reference key="object" ref="400319127"/>
<reference key="parent" ref="258451033"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">94</int>
<reference key="object" ref="105480775"/>
<reference key="parent" ref="258451033"/>
</object>
</array>
</object>
<dictionary class="NSMutableDictionary" key="flattenedProperties">
<string key="-1.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
<string key="-2.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
<string key="-3.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
<boolean value="YES" key="22.IBNSWindowAutoPositionCentersHorizontal"/>
<boolean value="YES" key="22.IBNSWindowAutoPositionCentersVertical"/>
<string key="22.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
<boolean value="NO" key="22.NSWindowTemplate.visibleAtLaunch"/>
<array class="NSMutableArray" key="23.IBNSViewMetadataConstraints">
<reference ref="137127436"/>
<reference ref="582018795"/>
<reference ref="1014829211"/>
<reference ref="751044970"/>
<reference ref="490796257"/>
<reference ref="456428551"/>
<reference ref="51508433"/>
<reference ref="99093936"/>
<reference ref="536265876"/>
<reference ref="1695776"/>
<reference ref="400319127"/>
<reference ref="105480775"/>
</array>
<string key="23.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
<string key="26.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
<string key="27.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
<string key="28.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
<string key="32.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
<string key="33.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
<boolean value="NO" key="34.IBNSViewMetadataTranslatesAutoresizingMaskIntoConstraints"/>
<string key="34.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
<array class="NSMutableArray" key="35.IBNSViewMetadataConstraints">
<reference ref="302161926"/>
</array>
<boolean value="NO" key="35.IBNSViewMetadataTranslatesAutoresizingMaskIntoConstraints"/>
<string key="35.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
<string key="37.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
<string key="38.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
<boolean value="NO" key="50.IBNSViewMetadataTranslatesAutoresizingMaskIntoConstraints"/>
<string key="50.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
<string key="51.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
<string key="74.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
<string key="85.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
<string key="87.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
<string key="90.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
<string key="91.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
<string key="92.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
<string key="93.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
<string key="94.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
</dictionary>
<dictionary class="NSMutableDictionary" key="unlocalizedProperties"/>
<nil key="activeLocalization"/>
<dictionary class="NSMutableDictionary" key="localizations"/>
<nil key="sourceID"/>
<int key="maxID">95</int>
</object>
<object class="IBClassDescriber" key="IBDocument.Classes">
<array class="NSMutableArray" key="referencedPartialClassDescriptions">
<object class="IBPartialClassDescription">
<string key="className">MPPasswordWindowController</string>
<string key="superclassName">NSWindowController</string>
<dictionary class="NSMutableDictionary" key="outlets">
<string key="contentField">NSTextField</string>
<string key="siteField">NSTextField</string>
<string key="tipField">NSTextField</string>
</dictionary>
<dictionary class="NSMutableDictionary" key="toOneOutletInfosByName">
<object class="IBToOneOutletInfo" key="contentField">
<string key="name">contentField</string>
<string key="candidateClassName">NSTextField</string>
</object>
<object class="IBToOneOutletInfo" key="siteField">
<string key="name">siteField</string>
<string key="candidateClassName">NSTextField</string>
</object>
<object class="IBToOneOutletInfo" key="tipField">
<string key="name">tipField</string>
<string key="candidateClassName">NSTextField</string>
</object>
</dictionary>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBProjectSource</string>
<string key="minorKey">./Classes/MPPasswordWindowController.h</string>
</object>
</object>
<object class="IBPartialClassDescription">
<string key="className">NSLayoutConstraint</string>
<string key="superclassName">NSObject</string>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBProjectSource</string>
<string key="minorKey">./Classes/NSLayoutConstraint.h</string>
</object>
</object>
</array>
</object>
<int key="IBDocument.localizationMode">0</int>
<string key="IBDocument.TargetRuntimeIdentifier">IBCocoaFramework</string>
<bool key="IBDocument.PluginDeclaredDependenciesTrackSystemTargetVersion">YES</bool>
<int key="IBDocument.defaultPropertyAccessControl">3</int>
<bool key="IBDocument.UseAutolayout">YES</bool>
</data>
</archive>

View File

@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>${EXECUTABLE_NAME}</string>
<key>CFBundleIconFile</key>
<string>Icon</string>
<key>CFBundleIdentifier</key>
<string>com.lyndir.lhunath.${PRODUCT_NAME:rfc1034identifier}.Mac</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>${PRODUCT_NAME}</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>[auto]</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>[auto]</string>
<key>LSApplicationCategoryType</key>
<string>public.app-category.productivity</string>
<key>LSMinimumSystemVersion</key>
<string>${MACOSX_DEPLOYMENT_TARGET}</string>
<key>LSUIElement</key>
<true/>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2012 Lyndir. All rights reserved.</string>
<key>NSMainNibFile</key>
<string>MainMenu</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
</dict>
</plist>

View File

@@ -0,0 +1,13 @@
//
// Prefix header for all source files of the 'MasterPassword' target in the 'MasterPassword' project
//
#ifdef __OBJC__
#import <Cocoa/Cocoa.h>
#endif
#import "Pearl-Prefix.pch"
#import "MPTypes.h"

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.developer.ubiquity-container-identifiers</key>
<array>
<string>$(TeamIdentifierPrefix)com.lyndir.lhunath.MasterPassword.Mac</string>
<string>$(TeamIdentifierPrefix)com.lyndir.lhunath.MasterPassword.shared</string>
</array>
<key>com.apple.developer.ubiquity-kvstore-identifier</key>
<string>$(TeamIdentifierPrefix)com.lyndir.lhunath.MasterPassword.shared</string>
<key>com.apple.security.app-sandbox</key>
<true/>
</dict>
</plist>

View File

@@ -0,0 +1,29 @@
{\rtf0\ansi{\fonttbl\f0\fswiss Helvetica;}
{\colortbl;\red255\green255\blue255;}
\paperw9840\paperh8400
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural
\f0\b\fs24 \cf0 Engineering:
\b0 \
Some people\
\
\b Human Interface Design:
\b0 \
Some other people\
\
\b Testing:
\b0 \
Hopefully not nobody\
\
\b Documentation:
\b0 \
Whoever\
\
\b With special thanks to:
\b0 \
Mom\
}

View File

@@ -0,0 +1,523 @@
<?xml version="1.0" encoding="UTF-8"?>
<archive type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="8.00">
<data>
<int key="IBDocument.SystemTarget">1070</int>
<string key="IBDocument.SystemVersion">11D50</string>
<string key="IBDocument.InterfaceBuilderVersion">2182</string>
<string key="IBDocument.AppKitVersion">1138.32</string>
<string key="IBDocument.HIToolboxVersion">568.00</string>
<object class="NSMutableDictionary" key="IBDocument.PluginVersions">
<string key="NS.key.0">com.apple.InterfaceBuilder.CocoaPlugin</string>
<string key="NS.object.0">2182</string>
</object>
<array key="IBDocument.IntegratedClassDependencies">
<string>NSMenuItem</string>
<string>NSMenu</string>
<string>NSCustomObject</string>
<string>NSUserDefaultsController</string>
</array>
<array key="IBDocument.PluginDependencies">
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
</array>
<object class="NSMutableDictionary" key="IBDocument.Metadata">
<string key="NS.key.0">PluginDependencyRecalculationVersion</string>
<integer value="1" key="NS.object.0"/>
</object>
<array class="NSMutableArray" key="IBDocument.RootObjects" id="1048">
<object class="NSCustomObject" id="1021">
<string key="NSClassName">NSApplication</string>
</object>
<object class="NSCustomObject" id="1014">
<string key="NSClassName">FirstResponder</string>
</object>
<object class="NSCustomObject" id="1050">
<string key="NSClassName">NSApplication</string>
</object>
<object class="NSMenu" id="649796088">
<string key="NSTitle">AMainMenu</string>
<array class="NSMutableArray" key="NSMenuItems"/>
<string key="NSName">_NSMainMenu</string>
</object>
<object class="NSCustomObject" id="976324537">
<string key="NSClassName">MPAppDelegate</string>
</object>
<object class="NSUserDefaultsController" id="705910970">
<bool key="NSSharedInstance">YES</bool>
</object>
<object class="NSMenu" id="764588027">
<string key="NSTitle"/>
<array class="NSMutableArray" key="NSMenuItems">
<object class="NSMenuItem" id="846612332">
<reference key="NSMenu" ref="764588027"/>
<string key="NSTitle">Show</string>
<string key="NSKeyEquiv"/>
<int key="NSMnemonicLoc">2147483647</int>
<object class="NSCustomResource" key="NSOnImage" id="552246001">
<string key="NSClassName">NSImage</string>
<string key="NSResourceName">NSMenuCheckmark</string>
</object>
<object class="NSCustomResource" key="NSMixedImage" id="752047669">
<string key="NSClassName">NSImage</string>
<string key="NSResourceName">NSMenuMixedState</string>
</object>
</object>
<object class="NSMenuItem" id="851296005">
<reference key="NSMenu" ref="764588027"/>
<string key="NSTitle">Preferences</string>
<string key="NSKeyEquiv"/>
<int key="NSMnemonicLoc">2147483647</int>
<reference key="NSOnImage" ref="552246001"/>
<reference key="NSMixedImage" ref="752047669"/>
<string key="NSAction">submenuAction:</string>
<object class="NSMenu" key="NSSubmenu" id="800575174">
<string key="NSTitle">Preferences</string>
<array class="NSMutableArray" key="NSMenuItems">
<object class="NSMenuItem" id="14397049">
<reference key="NSMenu" ref="800575174"/>
<string key="NSTitle">Use iCloud</string>
<string key="NSKeyEquiv"/>
<int key="NSMnemonicLoc">2147483647</int>
<reference key="NSOnImage" ref="552246001"/>
<reference key="NSMixedImage" ref="752047669"/>
</object>
<object class="NSMenuItem" id="461686112">
<reference key="NSMenu" ref="800575174"/>
<bool key="NSIsDisabled">YES</bool>
<string key="NSTitle">Synchronize available sites from your iCloud account.</string>
<string key="NSKeyEquiv"/>
<int key="NSMnemonicLoc">2147483647</int>
<reference key="NSOnImage" ref="552246001"/>
<reference key="NSMixedImage" ref="752047669"/>
<object class="NSAttributedString" key="NSAttributedTitle">
<string key="NSString">Synchronize available sites from your iCloud account.</string>
<dictionary key="NSAttributes" id="583461090">
<object class="NSFont" key="NSFont">
<string key="NSName">Helvetica</string>
<double key="NSSize">12</double>
<int key="NSfFlags">16</int>
</object>
<object class="NSParagraphStyle" key="NSParagraphStyle">
<int key="NSAlignment">4</int>
<nil key="NSTabStops"/>
</object>
</dictionary>
</object>
</object>
<object class="NSMenuItem" id="290760748">
<reference key="NSMenu" ref="800575174"/>
<string key="NSTitle">Remember Password</string>
<string key="NSKeyEquiv"/>
<int key="NSMnemonicLoc">2147483647</int>
<reference key="NSOnImage" ref="552246001"/>
<reference key="NSMixedImage" ref="752047669"/>
</object>
<object class="NSMenuItem" id="907921953">
<reference key="NSMenu" ref="800575174"/>
<bool key="NSIsDisabled">YES</bool>
<string key="NSTitle">Remember the password while the application is running.</string>
<string key="NSKeyEquiv"/>
<int key="NSMnemonicLoc">2147483647</int>
<reference key="NSOnImage" ref="552246001"/>
<reference key="NSMixedImage" ref="752047669"/>
<object class="NSAttributedString" key="NSAttributedTitle">
<string key="NSString">Remember the password while the application is running.</string>
<reference key="NSAttributes" ref="583461090"/>
</object>
</object>
<object class="NSMenuItem" id="110488020">
<reference key="NSMenu" ref="800575174"/>
<string key="NSTitle">Save Password</string>
<string key="NSKeyEquiv"/>
<int key="NSMnemonicLoc">2147483647</int>
<reference key="NSOnImage" ref="552246001"/>
<reference key="NSMixedImage" ref="752047669"/>
</object>
<object class="NSMenuItem" id="123831322">
<reference key="NSMenu" ref="800575174"/>
<bool key="NSIsDisabled">YES</bool>
<string key="NSTitle">Save the password in your keychain so you don't need to enter it again.</string>
<string key="NSKeyEquiv"/>
<int key="NSMnemonicLoc">2147483647</int>
<reference key="NSOnImage" ref="552246001"/>
<reference key="NSMixedImage" ref="752047669"/>
<object class="NSAttributedString" key="NSAttributedTitle">
<string key="NSString">Save the password in your keychain so you don't need to enter it again.</string>
<reference key="NSAttributes" ref="583461090"/>
</object>
</object>
</array>
<bool key="NSNoAutoenable">YES</bool>
</object>
</object>
<object class="NSMenuItem" id="466252869">
<reference key="NSMenu" ref="764588027"/>
<bool key="NSIsDisabled">YES</bool>
<bool key="NSIsSeparator">YES</bool>
<string key="NSTitle"/>
<string key="NSKeyEquiv"/>
<int key="NSMnemonicLoc">2147483647</int>
<reference key="NSOnImage" ref="552246001"/>
<reference key="NSMixedImage" ref="752047669"/>
</object>
<object class="NSMenuItem" id="229948989">
<reference key="NSMenu" ref="764588027"/>
<bool key="NSIsDisabled">YES</bool>
<string key="NSTitle">Lock</string>
<string key="NSKeyEquiv"/>
<int key="NSMnemonicLoc">2147483647</int>
<reference key="NSOnImage" ref="552246001"/>
<reference key="NSMixedImage" ref="752047669"/>
</object>
<object class="NSMenuItem" id="291035877">
<reference key="NSMenu" ref="764588027"/>
<string key="NSTitle">Quit</string>
<string key="NSKeyEquiv"/>
<int key="NSMnemonicLoc">2147483647</int>
<reference key="NSOnImage" ref="552246001"/>
<reference key="NSMixedImage" ref="752047669"/>
</object>
</array>
<bool key="NSNoAutoenable">YES</bool>
</object>
</array>
<object class="IBObjectContainer" key="IBDocument.Objects">
<array class="NSMutableArray" key="connectionRecords">
<object class="IBConnectionRecord">
<object class="IBActionConnection" key="connection">
<string key="label">terminate:</string>
<reference key="source" ref="1050"/>
<reference key="destination" ref="291035877"/>
</object>
<int key="connectionID">734</int>
</object>
<object class="IBConnectionRecord">
<object class="IBOutletConnection" key="connection">
<string key="label">delegate</string>
<reference key="source" ref="1021"/>
<reference key="destination" ref="976324537"/>
</object>
<int key="connectionID">495</int>
</object>
<object class="IBConnectionRecord">
<object class="IBOutletConnection" key="connection">
<string key="label">lockItem</string>
<reference key="source" ref="976324537"/>
<reference key="destination" ref="229948989"/>
</object>
<int key="connectionID">726</int>
</object>
<object class="IBConnectionRecord">
<object class="IBActionConnection" key="connection">
<string key="label">signOut:</string>
<reference key="source" ref="976324537"/>
<reference key="destination" ref="229948989"/>
</object>
<int key="connectionID">727</int>
</object>
<object class="IBConnectionRecord">
<object class="IBOutletConnection" key="connection">
<string key="label">showItem</string>
<reference key="source" ref="976324537"/>
<reference key="destination" ref="846612332"/>
</object>
<int key="connectionID">730</int>
</object>
<object class="IBConnectionRecord">
<object class="IBOutletConnection" key="connection">
<string key="label">statusMenu</string>
<reference key="source" ref="976324537"/>
<reference key="destination" ref="764588027"/>
</object>
<int key="connectionID">731</int>
</object>
<object class="IBConnectionRecord">
<object class="IBActionConnection" key="connection">
<string key="label">activate:</string>
<reference key="source" ref="976324537"/>
<reference key="destination" ref="846612332"/>
</object>
<int key="connectionID">736</int>
</object>
<object class="IBConnectionRecord">
<object class="IBOutletConnection" key="connection">
<string key="label">useICloudItem</string>
<reference key="source" ref="976324537"/>
<reference key="destination" ref="14397049"/>
</object>
<int key="connectionID">749</int>
</object>
<object class="IBConnectionRecord">
<object class="IBOutletConnection" key="connection">
<string key="label">rememberPasswordItem</string>
<reference key="source" ref="976324537"/>
<reference key="destination" ref="290760748"/>
</object>
<int key="connectionID">750</int>
</object>
<object class="IBConnectionRecord">
<object class="IBOutletConnection" key="connection">
<string key="label">savePasswordItem</string>
<reference key="source" ref="976324537"/>
<reference key="destination" ref="110488020"/>
</object>
<int key="connectionID">751</int>
</object>
<object class="IBConnectionRecord">
<object class="IBActionConnection" key="connection">
<string key="label">togglePreference:</string>
<reference key="source" ref="976324537"/>
<reference key="destination" ref="14397049"/>
</object>
<int key="connectionID">752</int>
</object>
<object class="IBConnectionRecord">
<object class="IBActionConnection" key="connection">
<string key="label">togglePreference:</string>
<reference key="source" ref="976324537"/>
<reference key="destination" ref="290760748"/>
</object>
<int key="connectionID">753</int>
</object>
<object class="IBConnectionRecord">
<object class="IBActionConnection" key="connection">
<string key="label">togglePreference:</string>
<reference key="source" ref="976324537"/>
<reference key="destination" ref="110488020"/>
</object>
<int key="connectionID">754</int>
</object>
</array>
<object class="IBMutableOrderedSet" key="objectRecords">
<array key="orderedObjects">
<object class="IBObjectRecord">
<int key="objectID">0</int>
<array key="object" id="0"/>
<reference key="children" ref="1048"/>
<nil key="parent"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">-2</int>
<reference key="object" ref="1021"/>
<reference key="parent" ref="0"/>
<string key="objectName">File's Owner</string>
</object>
<object class="IBObjectRecord">
<int key="objectID">-1</int>
<reference key="object" ref="1014"/>
<reference key="parent" ref="0"/>
<string key="objectName">First Responder</string>
</object>
<object class="IBObjectRecord">
<int key="objectID">-3</int>
<reference key="object" ref="1050"/>
<reference key="parent" ref="0"/>
<string key="objectName">Application</string>
</object>
<object class="IBObjectRecord">
<int key="objectID">29</int>
<reference key="object" ref="649796088"/>
<array class="NSMutableArray" key="children"/>
<reference key="parent" ref="0"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">494</int>
<reference key="object" ref="976324537"/>
<reference key="parent" ref="0"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">548</int>
<reference key="object" ref="705910970"/>
<reference key="parent" ref="0"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">716</int>
<reference key="object" ref="764588027"/>
<array class="NSMutableArray" key="children">
<reference ref="291035877"/>
<reference ref="466252869"/>
<reference ref="846612332"/>
<reference ref="229948989"/>
<reference ref="851296005"/>
</array>
<reference key="parent" ref="0"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">717</int>
<reference key="object" ref="291035877"/>
<reference key="parent" ref="764588027"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">718</int>
<reference key="object" ref="466252869"/>
<reference key="parent" ref="764588027"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">719</int>
<reference key="object" ref="846612332"/>
<reference key="parent" ref="764588027"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">720</int>
<reference key="object" ref="229948989"/>
<reference key="parent" ref="764588027"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">739</int>
<reference key="object" ref="851296005"/>
<array class="NSMutableArray" key="children">
<reference ref="800575174"/>
</array>
<reference key="parent" ref="764588027"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">742</int>
<reference key="object" ref="800575174"/>
<array class="NSMutableArray" key="children">
<reference ref="14397049"/>
<reference ref="290760748"/>
<reference ref="907921953"/>
<reference ref="461686112"/>
<reference ref="110488020"/>
<reference ref="123831322"/>
</array>
<reference key="parent" ref="851296005"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">743</int>
<reference key="object" ref="14397049"/>
<reference key="parent" ref="800575174"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">745</int>
<reference key="object" ref="907921953"/>
<reference key="parent" ref="800575174"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">744</int>
<reference key="object" ref="290760748"/>
<reference key="parent" ref="800575174"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">746</int>
<reference key="object" ref="461686112"/>
<reference key="parent" ref="800575174"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">747</int>
<reference key="object" ref="110488020"/>
<reference key="parent" ref="800575174"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">748</int>
<reference key="object" ref="123831322"/>
<reference key="parent" ref="800575174"/>
</object>
</array>
</object>
<dictionary class="NSMutableDictionary" key="flattenedProperties">
<string key="-1.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
<string key="-2.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
<string key="-3.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
<string key="29.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
<string key="494.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
<string key="548.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
<string key="716.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
<string key="717.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
<string key="718.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
<string key="719.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
<string key="720.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
<string key="739.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
<string key="742.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
<string key="743.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
<string key="744.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
<string key="745.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
<string key="746.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
<string key="747.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
<string key="748.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
</dictionary>
<dictionary class="NSMutableDictionary" key="unlocalizedProperties"/>
<nil key="activeLocalization"/>
<dictionary class="NSMutableDictionary" key="localizations"/>
<nil key="sourceID"/>
<int key="maxID">754</int>
</object>
<object class="IBClassDescriber" key="IBDocument.Classes">
<array class="NSMutableArray" key="referencedPartialClassDescriptions">
<object class="IBPartialClassDescription">
<string key="className">MPAppDelegate</string>
<string key="superclassName">NSObject</string>
<dictionary class="NSMutableDictionary" key="actions">
<string key="activate:">id</string>
<string key="signOut:">id</string>
<string key="togglePreference:">NSMenuItem</string>
</dictionary>
<dictionary class="NSMutableDictionary" key="actionInfosByName">
<object class="IBActionInfo" key="activate:">
<string key="name">activate:</string>
<string key="candidateClassName">id</string>
</object>
<object class="IBActionInfo" key="signOut:">
<string key="name">signOut:</string>
<string key="candidateClassName">id</string>
</object>
<object class="IBActionInfo" key="togglePreference:">
<string key="name">togglePreference:</string>
<string key="candidateClassName">NSMenuItem</string>
</object>
</dictionary>
<dictionary class="NSMutableDictionary" key="outlets">
<string key="lockItem">NSMenuItem</string>
<string key="rememberPasswordItem">NSMenuItem</string>
<string key="savePasswordItem">NSMenuItem</string>
<string key="showItem">NSMenuItem</string>
<string key="statusMenu">NSMenu</string>
<string key="useICloudItem">NSMenuItem</string>
</dictionary>
<dictionary class="NSMutableDictionary" key="toOneOutletInfosByName">
<object class="IBToOneOutletInfo" key="lockItem">
<string key="name">lockItem</string>
<string key="candidateClassName">NSMenuItem</string>
</object>
<object class="IBToOneOutletInfo" key="rememberPasswordItem">
<string key="name">rememberPasswordItem</string>
<string key="candidateClassName">NSMenuItem</string>
</object>
<object class="IBToOneOutletInfo" key="savePasswordItem">
<string key="name">savePasswordItem</string>
<string key="candidateClassName">NSMenuItem</string>
</object>
<object class="IBToOneOutletInfo" key="showItem">
<string key="name">showItem</string>
<string key="candidateClassName">NSMenuItem</string>
</object>
<object class="IBToOneOutletInfo" key="statusMenu">
<string key="name">statusMenu</string>
<string key="candidateClassName">NSMenu</string>
</object>
<object class="IBToOneOutletInfo" key="useICloudItem">
<string key="name">useICloudItem</string>
<string key="candidateClassName">NSMenuItem</string>
</object>
</dictionary>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBProjectSource</string>
<string key="minorKey">./Classes/MPAppDelegate.h</string>
</object>
</object>
</array>
</object>
<int key="IBDocument.localizationMode">0</int>
<string key="IBDocument.TargetRuntimeIdentifier">IBCocoaFramework</string>
<object class="NSMutableDictionary" key="IBDocument.PluginDeclaredDependencies">
<string key="NS.key.0">com.apple.InterfaceBuilder.CocoaPlugin.macosx</string>
<integer value="1070" key="NS.object.0"/>
</object>
<bool key="IBDocument.PluginDeclaredDependenciesTrackSystemTargetVersion">YES</bool>
<int key="IBDocument.defaultPropertyAccessControl">3</int>
<dictionary class="NSMutableDictionary" key="IBDocument.LastKnownImageSizes">
<string key="NSMenuCheckmark">{11, 11}</string>
<string key="NSMenuMixedState">{10, 3}</string>
</dictionary>
<bool key="IBDocument.UseAutolayout">YES</bool>
</data>
</archive>

14
MasterPassword/Mac/main.m Normal file
View File

@@ -0,0 +1,14 @@
//
// main.m
// MasterPassword
//
// Created by Maarten Billemont on 04/03/12.
// Copyright (c) 2012 Lyndir. All rights reserved.
//
#import <Cocoa/Cocoa.h>
int main(int argc, char *argv[]) {
return NSApplicationMain(argc, (const char **)argv);
}

View File

@@ -1,877 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="1.1" toolsVersion="2177" systemVersion="11D50" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" initialViewController="KZF-fe-y9n">
<dependencies>
<development defaultVersion="4200" identifier="xcode"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="1173"/>
</dependencies>
<scenes>
<!--Type View Controller - Type-->
<scene sceneID="WkW-SR-cr2">
<objects>
<placeholder placeholderIdentifier="IBFirstResponder" id="jZj-N1-rhF" userLabel="First Responder" sceneMemberID="firstResponder"/>
<tableViewController id="NKe-nv-566" customClass="MPTypeViewController" sceneMemberID="viewController">
<tableView key="view" opaque="NO" clipsSubviews="YES" clearsContextBeforeDrawing="NO" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="static" style="plain" separatorStyle="none" rowHeight="44" sectionHeaderHeight="22" sectionFooterHeight="22" id="btl-G4-V0S">
<rect key="frame" x="0.0" y="64" width="320" height="416"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" red="0.12549020350000001" green="0.1411764771" blue="0.14901961389999999" alpha="1" colorSpace="calibratedRGB"/>
<view key="tableFooterView" contentMode="scaleToFill" id="aNa-wb-cYK">
<rect key="frame" x="0.0" y="588" width="320" height="20"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
</view>
<sections>
<tableViewSection id="gUv-FU-O36">
<cells>
<tableViewCell contentMode="scaleToFill" selectionStyle="blue" hidesAccessoryWhenEditing="NO" indentationLevel="1" indentationWidth="0.0" rowHeight="204" id="NCB-rX-weq">
<rect key="frame" x="0.0" y="0.0" width="320" height="204"/>
<autoresizingMask key="autoresizingMask"/>
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center">
<rect key="frame" x="0.0" y="0.0" width="320" height="203"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Generated" lineBreakMode="tailTruncation" minimumFontSize="10" id="azh-GA-KA1">
<rect key="frame" x="20" y="20" width="280" height="21"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" name="GillSans-Bold" family="Gill Sans" pointSize="17"/>
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/>
<color key="shadowColor" cocoaTouchSystemColor="darkTextColor"/>
<size key="shadowOffset" width="0.0" height="1"/>
</label>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" image="ui_list_first.png" id="WXs-ZQ-JuJ">
<rect key="frame" x="10" y="160" width="300" height="44"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<rect key="contentStretch" x="0.10000000000000001" y="0.20000000000000001" width="0.79999999999999982" height="0.59999999999999964"/>
</imageView>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Long Password" lineBreakMode="tailTruncation" minimumFontSize="10" id="QIm-RX-xY7">
<rect key="frame" x="20" y="160" width="280" height="24"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<fontDescription key="fontDescription" name="GillSans" family="Gill Sans" pointSize="18"/>
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/>
<color key="shadowColor" cocoaTouchSystemColor="darkTextColor"/>
<size key="shadowOffset" width="0.0" height="1"/>
</label>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Word-like, 14 characters, contains symbols." lineBreakMode="tailTruncation" minimumFontSize="10" id="6iu-aM-lJA">
<rect key="frame" x="20" y="183" width="280" height="20"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<fontDescription key="fontDescription" name="GillSans-LightItalic" family="Gill Sans" pointSize="14"/>
<color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" lineBreakMode="tailTruncation" numberOfLines="0" minimumFontSize="10" id="E4a-XS-Qlz">
<rect key="frame" x="23" y="49" width="280" height="112"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<string key="text">These types create a safe password for your site.
The passwords aren't saved anywhere. This is a major advantage: if you loose your device, your passwords cannot be stolen. You can use any other device to get all your passwords back, no need for restoring backups or any other pains.</string>
<fontDescription key="fontDescription" name="GillSans-LightItalic" family="Gill Sans" pointSize="14"/>
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/>
<color key="shadowColor" cocoaTouchSystemColor="darkTextColor"/>
<size key="shadowOffset" width="0.0" height="1"/>
</label>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
</view>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
</tableViewCell>
<tableViewCell contentMode="scaleToFill" selectionStyle="blue" hidesAccessoryWhenEditing="NO" indentationLevel="1" indentationWidth="0.0" id="re0-ZT-bnP">
<rect key="frame" x="0.0" y="204" width="320" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center">
<rect key="frame" x="0.0" y="0.0" width="320" height="43"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" image="ui_list_middle.png" id="EWy-Eb-L5p">
<rect key="frame" x="10" y="0.0" width="300" height="44"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<rect key="contentStretch" x="0.10000000000000001" y="0.20000000000000001" width="0.79999999999999982" height="0.59999999999999964"/>
</imageView>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Medium Password" lineBreakMode="tailTruncation" minimumFontSize="10" id="5NA-XX-th4">
<rect key="frame" x="20" y="0.0" width="280" height="24"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<fontDescription key="fontDescription" name="GillSans" family="Gill Sans" pointSize="18"/>
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/>
<color key="shadowColor" cocoaTouchSystemColor="darkTextColor"/>
<size key="shadowOffset" width="0.0" height="1"/>
</label>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Word-like, 8 characters, contains symbols." lineBreakMode="tailTruncation" minimumFontSize="10" id="MI2-JM-j4b">
<rect key="frame" x="20" y="23" width="280" height="20"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<fontDescription key="fontDescription" name="GillSans-LightItalic" family="Gill Sans" pointSize="14"/>
<color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
</view>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
</tableViewCell>
<tableViewCell contentMode="scaleToFill" selectionStyle="blue" hidesAccessoryWhenEditing="NO" indentationLevel="1" indentationWidth="0.0" id="4pb-c9-i2D">
<rect key="frame" x="0.0" y="248" width="320" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center">
<rect key="frame" x="0.0" y="0.0" width="320" height="43"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" image="ui_list_middle.png" id="Jqw-h1-g2X">
<rect key="frame" x="10" y="0.0" width="300" height="44"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<rect key="contentStretch" x="0.10000000000000001" y="0.20000000000000001" width="0.79999999999999982" height="0.59999999999999964"/>
</imageView>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Short Password" lineBreakMode="tailTruncation" minimumFontSize="10" id="iqW-mf-H1R">
<rect key="frame" x="20" y="0.0" width="280" height="24"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<fontDescription key="fontDescription" name="GillSans" family="Gill Sans" pointSize="18"/>
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/>
<color key="shadowColor" cocoaTouchSystemColor="darkTextColor"/>
<size key="shadowOffset" width="0.0" height="1"/>
</label>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Word-like, 4 characters." lineBreakMode="tailTruncation" minimumFontSize="10" id="Zi3-26-3iq">
<rect key="frame" x="20" y="23" width="280" height="20"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<fontDescription key="fontDescription" name="GillSans-LightItalic" family="Gill Sans" pointSize="14"/>
<color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
</view>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
</tableViewCell>
<tableViewCell contentMode="scaleToFill" selectionStyle="blue" hidesAccessoryWhenEditing="NO" indentationLevel="1" indentationWidth="0.0" id="ywW-4w-Wej">
<rect key="frame" x="0.0" y="292" width="320" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center">
<rect key="frame" x="0.0" y="0.0" width="320" height="43"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" image="ui_list_middle.png" id="Son-EL-Drx">
<rect key="frame" x="10" y="0.0" width="300" height="44"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<rect key="contentStretch" x="0.10000000000000001" y="0.20000000000000001" width="0.79999999999999982" height="0.59999999999999964"/>
</imageView>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Basic Password" lineBreakMode="tailTruncation" minimumFontSize="10" id="Pim-A8-aib">
<rect key="frame" x="20" y="0.0" width="280" height="24"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<fontDescription key="fontDescription" name="GillSans" family="Gill Sans" pointSize="18"/>
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/>
<color key="shadowColor" cocoaTouchSystemColor="darkTextColor"/>
<size key="shadowOffset" width="0.0" height="1"/>
</label>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="8 characters." lineBreakMode="tailTruncation" minimumFontSize="10" id="CYQ-D8-vNS">
<rect key="frame" x="20" y="23" width="280" height="20"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<fontDescription key="fontDescription" name="GillSans-LightItalic" family="Gill Sans" pointSize="14"/>
<color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
</view>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
</tableViewCell>
<tableViewCell contentMode="scaleToFill" selectionStyle="blue" hidesAccessoryWhenEditing="NO" indentationLevel="1" indentationWidth="0.0" id="5p5-OD-zpn">
<rect key="frame" x="0.0" y="336" width="320" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center">
<rect key="frame" x="0.0" y="0.0" width="320" height="43"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" image="ui_list_last.png" id="5nI-rL-CoO">
<rect key="frame" x="10" y="0.0" width="300" height="44"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<rect key="contentStretch" x="0.10000000000000001" y="0.20000000000000001" width="0.79999999999999982" height="0.59999999999999964"/>
</imageView>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="PIN" lineBreakMode="tailTruncation" minimumFontSize="10" id="Yu2-5k-AwW">
<rect key="frame" x="20" y="0.0" width="280" height="24"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<fontDescription key="fontDescription" name="GillSans" family="Gill Sans" pointSize="18"/>
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/>
<color key="shadowColor" cocoaTouchSystemColor="darkTextColor"/>
<size key="shadowOffset" width="0.0" height="1"/>
</label>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="4 numbers." lineBreakMode="tailTruncation" minimumFontSize="10" id="5Zm-AH-bAe">
<rect key="frame" x="20" y="23" width="280" height="20"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<fontDescription key="fontDescription" name="GillSans-LightItalic" family="Gill Sans" pointSize="14"/>
<color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
</view>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
</tableViewCell>
</cells>
</tableViewSection>
<tableViewSection id="Jqk-Jl-vVm">
<cells>
<tableViewCell contentMode="scaleToFill" selectionStyle="blue" hidesAccessoryWhenEditing="NO" indentationLevel="1" indentationWidth="0.0" rowHeight="164" id="OK2-fa-fDw">
<rect key="frame" x="0.0" y="380" width="320" height="164"/>
<autoresizingMask key="autoresizingMask"/>
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center">
<rect key="frame" x="0.0" y="0.0" width="320" height="163"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="User-specified" lineBreakMode="tailTruncation" minimumFontSize="10" id="146-3h-mdD">
<rect key="frame" x="20" y="20" width="280" height="21"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" name="GillSans-Bold" family="Gill Sans" pointSize="17"/>
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/>
<color key="shadowColor" cocoaTouchSystemColor="darkTextColor"/>
<size key="shadowOffset" width="0.0" height="1"/>
</label>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" lineBreakMode="tailTruncation" numberOfLines="0" minimumFontSize="10" id="Bbk-lL-BeB">
<rect key="frame" x="20" y="49" width="280" height="63"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<string key="text">If you need to specify a custom password for your site, use one of these types. The site's password will be encrypted using your master password.</string>
<fontDescription key="fontDescription" name="GillSans-LightItalic" family="Gill Sans" pointSize="14"/>
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/>
<color key="shadowColor" cocoaTouchSystemColor="darkTextColor"/>
<size key="shadowOffset" width="0.0" height="1"/>
</label>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" image="ui_list_first.png" id="fxL-ee-UVP">
<rect key="frame" x="10" y="120" width="300" height="44"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<rect key="contentStretch" x="0.10000000000000001" y="0.20000000000000001" width="0.79999999999999982" height="0.59999999999999964"/>
</imageView>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Personal Password" lineBreakMode="tailTruncation" minimumFontSize="10" id="kzc-rQ-74F">
<rect key="frame" x="20" y="120" width="280" height="24"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<fontDescription key="fontDescription" name="GillSans" family="Gill Sans" pointSize="18"/>
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/>
<color key="shadowColor" cocoaTouchSystemColor="darkTextColor"/>
<size key="shadowOffset" width="0.0" height="1"/>
</label>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="AES-encrypted, iTunes backup, iCloud sync." lineBreakMode="tailTruncation" minimumFontSize="10" id="vNa-Yq-XIJ">
<rect key="frame" x="20" y="143" width="280" height="20"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<fontDescription key="fontDescription" name="GillSans-LightItalic" family="Gill Sans" pointSize="14"/>
<color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
</view>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
</tableViewCell>
<tableViewCell contentMode="scaleToFill" selectionStyle="blue" hidesAccessoryWhenEditing="NO" indentationLevel="1" indentationWidth="0.0" id="ePk-a9-aNi">
<rect key="frame" x="0.0" y="544" width="320" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center">
<rect key="frame" x="0.0" y="0.0" width="320" height="43"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" image="ui_list_last.png" id="PUy-Qk-CeF">
<rect key="frame" x="10" y="0.0" width="300" height="44"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<rect key="contentStretch" x="0.10000000000000001" y="0.20000000000000001" width="0.79999999999999982" height="0.59999999999999964"/>
</imageView>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Device Private Password" lineBreakMode="tailTruncation" minimumFontSize="10" id="2kB-0P-FeR">
<rect key="frame" x="20" y="0.0" width="280" height="24"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<fontDescription key="fontDescription" name="GillSans" family="Gill Sans" pointSize="18"/>
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/>
<color key="shadowColor" cocoaTouchSystemColor="darkTextColor"/>
<size key="shadowOffset" width="0.0" height="1"/>
</label>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="AES-encrypted, stays on this device only." lineBreakMode="tailTruncation" minimumFontSize="10" id="6S8-9Y-pzj">
<rect key="frame" x="20" y="23" width="280" height="20"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<fontDescription key="fontDescription" name="GillSans-LightItalic" family="Gill Sans" pointSize="14"/>
<color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
</view>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
</tableViewCell>
</cells>
</tableViewSection>
</sections>
<connections>
<outlet property="dataSource" destination="NKe-nv-566" id="5yH-j4-f9c"/>
<outlet property="delegate" destination="NKe-nv-566" id="O5k-hI-7Tf"/>
</connections>
</tableView>
<navigationItem key="navigationItem" title="Type" id="rak-Td-wu1"/>
</tableViewController>
</objects>
<point key="canvasLocation" x="996" y="182"/>
</scene>
<!--Main View Controller - Master Password-->
<scene sceneID="U26-Zf-euQ">
<objects>
<placeholder placeholderIdentifier="IBFirstResponder" id="mK2-p1-3zC" userLabel="First Responder" sceneMemberID="firstResponder"/>
<customObject id="0QO-2P-OhD" customClass="MPSearchDelegate">
<connections>
<outlet property="delegate" destination="PQa-Xl-A3x" id="O2f-mW-ab8"/>
<outlet property="searchDisplayController" destination="P8c-gf-nN3" id="exk-dS-Ui3"/>
<outlet property="searchTipContainer" destination="zOR-Du-qRL" id="lk6-2X-lIb"/>
</connections>
</customObject>
<searchDisplayController id="P8c-gf-nN3">
<connections>
<outlet property="delegate" destination="0QO-2P-OhD" id="gst-Hm-5ja"/>
<outlet property="searchBar" destination="qeo-n2-WVh" id="bFO-FC-Xdj"/>
<outlet property="searchContentsController" destination="PQa-Xl-A3x" id="iEu-t3-hJY"/>
<outlet property="searchResultsDataSource" destination="0QO-2P-OhD" id="XJh-rC-kId"/>
<outlet property="searchResultsDelegate" destination="0QO-2P-OhD" id="Bm8-Q3-lLA"/>
</connections>
</searchDisplayController>
<viewController id="PQa-Xl-A3x" customClass="MPMainViewController" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="Ep0-Hn-5TR">
<rect key="frame" x="0.0" y="64" width="320" height="416"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="bottom" image="background.png" id="0hY-LL-ITu">
<rect key="frame" x="0.0" y="44" width="320" height="372"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
</imageView>
<view contentMode="scaleToFill" id="g9q-4d-ZgJ">
<rect key="frame" x="0.0" y="43" width="320" height="175"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
<subviews>
<imageView userInteractionEnabled="NO" alpha="0.80000000000000004" contentMode="scaleToFill" image="ui_panel_container.png" id="0Yh-Py-lB6">
<rect key="frame" x="11" y="20" width="298" height="87"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
<rect key="contentStretch" x="0.050000000000000003" y="0.10000000000000001" width="0.89999999999999991" height="0.79999999999999982"/>
</imageView>
<imageView userInteractionEnabled="NO" alpha="0.80000000000000004" contentMode="scaleToFill" image="ui_panel_display.png" id="cw7-HD-Wht">
<rect key="frame" x="11" y="20" width="298" height="86"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
<rect key="contentStretch" x="0.050000000000000003" y="0.10000000000000001" width="0.89999999999999991" height="0.79999999999999982"/>
</imageView>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" showsTouchWhenHighlighted="YES" lineBreakMode="middleTruncation" id="Zji-Vg-VwX">
<rect key="frame" x="11" y="20" width="298" height="86"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
<fontDescription key="fontDescription" type="boldSystem" size="button"/>
<state key="normal">
<color key="titleColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<color key="titleShadowColor" white="0.5" alpha="1" colorSpace="calibratedWhite"/>
</state>
<state key="highlighted">
<color key="titleColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
</state>
<connections>
<action selector="copyContent" destination="PQa-Xl-A3x" eventType="touchUpInside" id="9tS-J5-wlx"/>
</connections>
</button>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" alpha="0.50000000000000011" contentMode="left" text="apple.com" lineBreakMode="tailTruncation" minimumFontSize="10" id="gSK-aB-wNI">
<rect key="frame" x="25" y="20" width="270" height="26"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" name="Copperplate" family="Copperplate" pointSize="12"/>
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/>
<color key="shadowColor" white="0.0" alpha="1" colorSpace="calibratedWhite"/>
<size key="shadowOffset" width="0.0" height="1"/>
</label>
<textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" text="S3cre7^Pa$swcrD" textAlignment="center" minimumFontSize="17" clearButtonMode="whileEditing" id="fiX-10-fif">
<rect key="frame" x="20" y="46" width="280" height="60"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMaxY="YES"/>
<color key="textColor" red="0.47450980390000003" green="0.86666666670000003" blue="0.98431372549999996" alpha="1" colorSpace="calibratedRGB"/>
<fontDescription key="fontDescription" name="Copperplate-Bold" family="Copperplate" pointSize="26"/>
<textInputTraits key="textInputTraits" autocorrectionType="no"/>
<connections>
<outlet property="delegate" destination="PQa-Xl-A3x" id="qOM-gq-c6g"/>
</connections>
</textField>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" alpha="0.50000000000000011" contentMode="left" text="1" textAlignment="right" lineBreakMode="tailTruncation" minimumFontSize="10" id="Iuf-np-e9C">
<rect key="frame" x="240" y="27" width="40" height="20"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" name="Copperplate-Bold" family="Copperplate" pointSize="17"/>
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/>
<color key="shadowColor" white="0.0" alpha="1" colorSpace="calibratedWhite"/>
<size key="shadowOffset" width="0.0" height="1"/>
</label>
<button opaque="NO" alpha="0.50000000000000011" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" id="jec-mu-nPt">
<rect key="frame" x="272.5" y="18.5" width="36.5" height="36"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="15"/>
<inset key="contentEdgeInsets" minX="5" minY="5" maxX="5" maxY="5"/>
<state key="normal" image="icon_plus.png">
<color key="titleColor" red="0.19607843459999999" green="0.30980393290000002" blue="0.52156865600000002" alpha="1" colorSpace="calibratedRGB"/>
<color key="titleShadowColor" white="0.5" alpha="1" colorSpace="calibratedWhite"/>
</state>
<state key="highlighted">
<color key="titleColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
</state>
<connections>
<action selector="incrementPasswordCounter" destination="PQa-Xl-A3x" eventType="touchUpInside" id="hMc-kb-yFA"/>
</connections>
</button>
<button opaque="NO" alpha="0.5" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" id="9FS-fS-xH6">
<rect key="frame" x="273" y="18.5" width="36" height="36"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="15"/>
<inset key="contentEdgeInsets" minX="5" minY="5" maxX="5" maxY="5"/>
<state key="normal" image="icon_edit.png">
<color key="titleColor" red="0.19607843459999999" green="0.30980393290000002" blue="0.52156865600000002" alpha="1" colorSpace="calibratedRGB"/>
<color key="titleShadowColor" white="0.5" alpha="1" colorSpace="calibratedWhite"/>
</state>
<state key="highlighted">
<color key="titleColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
</state>
<connections>
<action selector="editPassword" destination="PQa-Xl-A3x" eventType="touchUpInside" id="jZC-Jm-ZZ0"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" reversesTitleShadowWhenHighlighted="YES" lineBreakMode="middleTruncation" id="Cei-5z-uWE">
<rect key="frame" x="10" y="115" width="300" height="46"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<fontDescription key="fontDescription" name="Helvetica-Bold" family="Helvetica" pointSize="15"/>
<size key="titleShadowOffset" width="0.0" height="1"/>
<state key="normal" title="Long Password" backgroundImage="ui_button_standard_large.png">
<color key="titleColor" cocoaTouchSystemColor="lightTextColor"/>
<color key="titleShadowColor" white="0.0" alpha="1" colorSpace="calibratedWhite"/>
</state>
<state key="highlighted">
<color key="titleColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
</state>
<connections>
<segue destination="NKe-nv-566" kind="push" identifier="MP_Main_ChooseType" id="vw4-Vd-O6q"/>
</connections>
</button>
<view userInteractionEnabled="NO" alpha="0.0" contentMode="scaleToFill" id="v2m-Gf-pEV">
<rect key="frame" x="55" y="5" width="210" height="60"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" image="tip_basic_black.png" id="fdm-NG-GFC">
<rect key="frame" x="0.0" y="0.0" width="210" height="60"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
</imageView>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Tap to set a password." textAlignment="center" lineBreakMode="tailTruncation" minimumFontSize="10" id="ieN-QQ-PyR">
<rect key="frame" x="20" y="0.0" width="170" height="40"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
</label>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" image="icon_edit.png" id="KEn-n3-qhX">
<rect key="frame" x="48" y="6" width="24" height="24"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMaxY="YES"/>
</imageView>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
</view>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
</view>
<view clipsSubviews="YES" contentMode="scaleToFill" id="61G-By-qLB">
<rect key="frame" x="0.0" y="216" width="320" height="200"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView userInteractionEnabled="NO" contentMode="bottom" image="Square-bottom.png" id="lbm-A0-3PK">
<rect key="frame" x="0.0" y="0.0" width="320" height="58"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
</imageView>
<webView opaque="NO" contentMode="scaleToFill" id="8FQ-x4-lR9">
<rect key="frame" x="0.0" y="1" width="320" height="199"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
<connections>
<outlet property="delegate" destination="PQa-Xl-A3x" id="H1z-xP-1Pm"/>
</connections>
</webView>
</subviews>
<color key="backgroundColor" cocoaTouchSystemColor="scrollViewTexturedBackgroundColor"/>
</view>
<view alpha="0.0" contentMode="scaleToFill" id="yRY-qt-gz8">
<rect key="frame" x="10" y="230" width="300" height="180"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<subviews>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" image="tip_alert_black.png" id="TUv-Tl-xTc">
<rect key="frame" x="0.0" y="0.0" width="300" height="180"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<rect key="contentStretch" x="0.25" y="0.6499999999999998" width="0.64999999999999969" height="0.10000000000000002"/>
</imageView>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Password Changed!" lineBreakMode="tailTruncation" minimumFontSize="10" id="ZdH-we-KcW">
<rect key="frame" x="70" y="20" width="210" height="21"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
</label>
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" editable="NO" id="jCg-t7-RuK">
<rect key="frame" x="20" y="60" width="260" height="100"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
<string key="text">The password for apple.com has changed.
Don't forget to update the site with your new password! Your old password was:
L4m3P4sSw0rD</string>
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<fontDescription key="fontDescription" type="system" pointSize="12"/>
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
</textView>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" id="cX2-vD-vjc">
<rect key="frame" x="259" y="5" width="32" height="32"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="15"/>
<state key="normal" image="icon_cancel.png">
<color key="titleColor" red="0.19607843459999999" green="0.30980393290000002" blue="0.52156865600000002" alpha="1" colorSpace="calibratedRGB"/>
<color key="titleShadowColor" white="0.5" alpha="1" colorSpace="calibratedWhite"/>
</state>
<state key="highlighted">
<color key="titleColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
</state>
<connections>
<action selector="closeAlert" destination="PQa-Xl-A3x" eventType="touchUpInside" id="oJt-DU-l38"/>
</connections>
</button>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
</view>
<searchBar contentMode="redraw" barStyle="blackOpaque" placeholder="Enter site name" showsSearchResultsButton="YES" id="qeo-n2-WVh">
<rect key="frame" x="0.0" y="0.0" width="320" height="44"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
<gestureRecognizers/>
<textInputTraits key="textInputTraits" autocorrectionType="no" keyboardType="URL"/>
<connections>
<outlet property="delegate" destination="0QO-2P-OhD" id="Maj-Iz-6Hb"/>
</connections>
</searchBar>
<view userInteractionEnabled="NO" contentMode="scaleToFill" id="zOR-Du-qRL">
<rect key="frame" x="10" y="-25" width="300" height="60"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" image="tip_basic_black.png" id="ORD-Xv-bOQ">
<rect key="frame" x="0.0" y="0.0" width="300" height="60"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<rect key="contentStretch" x="0.15000000000000002" y="0.15000000000000002" width="0.69999999999999973" height="0.69999999999999973"/>
</imageView>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Begin by entering the name of your site." textAlignment="center" lineBreakMode="tailTruncation" minimumFontSize="10" id="21b-bH-lR9">
<rect key="frame" x="-20" y="9" width="340" height="21"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
</view>
</subviews>
<color key="backgroundColor" red="0.14901960784313725" green="0.16470588235294117" blue="0.1803921568627451" alpha="1" colorSpace="calibratedRGB"/>
</view>
<navigationItem key="navigationItem" title="Master Password" id="hB9-S6-KHC">
<barButtonItem key="rightBarButtonItem" systemItem="action" id="8Y7-Bf-FHa">
<connections>
<action selector="action:" destination="PQa-Xl-A3x" id="mhz-Ey-IOC"/>
</connections>
</barButtonItem>
</navigationItem>
<connections>
<outlet property="alertBody" destination="jCg-t7-RuK" id="Aru-Nr-DY0"/>
<outlet property="alertContainer" destination="yRY-qt-gz8" id="gZx-90-Zgr"/>
<outlet property="alertTitle" destination="ZdH-we-KcW" id="VWi-aS-HRw"/>
<outlet property="contentContainer" destination="g9q-4d-ZgJ" id="Hgw-Hp-FHB"/>
<outlet property="contentField" destination="fiX-10-fif" id="b2c-gv-qMp"/>
<outlet property="contentTipBody" destination="ieN-QQ-PyR" id="flJ-gF-wbI"/>
<outlet property="contentTipContainer" destination="v2m-Gf-pEV" id="9zL-GR-kyH"/>
<outlet property="contentTipEditIcon" destination="KEn-n3-qhX" id="dDG-Nt-OLM"/>
<outlet property="helpContainer" destination="61G-By-qLB" id="Jwt-0d-ZAV"/>
<outlet property="helpView" destination="8FQ-x4-lR9" id="MOg-5s-kvK"/>
<outlet property="passwordCounter" destination="Iuf-np-e9C" id="CIm-Mk-nJh"/>
<outlet property="passwordEdit" destination="9FS-fS-xH6" id="YeB-HF-ZPk"/>
<outlet property="passwordIncrementer" destination="jec-mu-nPt" id="i9B-lX-zzX"/>
<outlet property="searchDisplayController" destination="P8c-gf-nN3" id="CLs-YI-7NC"/>
<outlet property="searchResultsController" destination="0QO-2P-OhD" id="xEC-gV-lHp"/>
<outlet property="searchTipContainer" destination="zOR-Du-qRL" id="X7h-Vh-iCE"/>
<outlet property="siteName" destination="gSK-aB-wNI" id="IIe-z8-zy8"/>
<outlet property="typeButton" destination="Cei-5z-uWE" id="4M1-d7-5Bh"/>
</connections>
</viewController>
</objects>
<point key="canvasLocation" x="455" y="182"/>
</scene>
<!--Guide View Controller-->
<scene sceneID="Rt1-b4-sUB">
<objects>
<placeholder placeholderIdentifier="IBFirstResponder" id="7yf-G7-kVy" userLabel="First Responder" sceneMemberID="firstResponder"/>
<viewController id="qz3-eG-aEi" customClass="MPGuideViewController" sceneMemberID="viewController">
<scrollView key="view" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" directionalLockEnabled="YES" pagingEnabled="YES" showsVerticalScrollIndicator="NO" id="Nr4-hi-8b2">
<rect key="frame" x="0.0" y="20" width="320" height="460"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="bottom" image="guide_page_0.png" id="eJP-cS-VRU">
<rect key="frame" x="0.0" y="0.0" width="320" height="460"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" heightSizable="YES"/>
</imageView>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="bottom" image="guide_page_1.png" id="1J9-z9-h96">
<rect key="frame" x="320" y="0.0" width="320" height="460"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" heightSizable="YES"/>
</imageView>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="bottom" image="guide_page_2.png" id="8kf-Wm-F3L">
<rect key="frame" x="640" y="0.0" width="320" height="460"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" heightSizable="YES"/>
</imageView>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="bottom" image="guide_page_3.png" id="H5W-P4-o7O">
<rect key="frame" x="960" y="0.0" width="320" height="460"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" heightSizable="YES"/>
</imageView>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="bottom" image="guide_page_4.png" id="IXz-T9-Umi">
<rect key="frame" x="1280" y="0.0" width="320" height="460"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" heightSizable="YES"/>
</imageView>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="bottom" image="guide_page_5.png" id="Hd0-Or-naq">
<rect key="frame" x="1280" y="0.0" width="320" height="460"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" heightSizable="YES"/>
</imageView>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="bottom" image="guide_page_6.png" id="1jq-Ic-GVZ">
<rect key="frame" x="1600" y="0.0" width="320" height="460"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" heightSizable="YES"/>
</imageView>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" reversesTitleShadowWhenHighlighted="YES" lineBreakMode="middleTruncation" id="q93-J1-auj">
<rect key="frame" x="1610" y="394" width="300" height="46"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="15"/>
<size key="titleShadowOffset" width="0.0" height="1"/>
<state key="normal" title="Get started" backgroundImage="ui_button_green_large.png">
<color key="titleColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<color key="titleShadowColor" red="0.194560533886145" green="0.30086678832116787" blue="0.1263640535337863" alpha="1" colorSpace="deviceRGB"/>
</state>
<state key="highlighted">
<color key="titleColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
</state>
<connections>
<action selector="close" destination="qz3-eG-aEi" eventType="touchUpInside" id="uYR-EJ-U6H"/>
</connections>
</button>
</subviews>
</scrollView>
<connections>
<outlet property="scrollView" destination="Nr4-hi-8b2" id="8QD-sv-vu6"/>
</connections>
</viewController>
</objects>
<point key="canvasLocation" x="455" y="785"/>
</scene>
<!--Unlock View Controller-->
<scene sceneID="HkH-JR-Fhy">
<objects>
<placeholder placeholderIdentifier="IBFirstResponder" id="OGA-5j-IcQ" userLabel="First Responder" sceneMemberID="firstResponder"/>
<viewController storyboardIdentifier="MPUnlockViewController" modalTransitionStyle="flipHorizontal" id="Nbn-Rv-sP1" customClass="MPUnlockViewController" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="PHH-XC-9QQ">
<rect key="frame" x="0.0" y="0.0" width="320" height="480"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews>
<imageView userInteractionEnabled="NO" contentMode="top" image="background.png" id="QWe-Gw-rD3">
<rect key="frame" x="0.0" y="0.0" width="320" height="480"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
</imageView>
<view contentMode="scaleToFill" id="OP6-72-eij">
<rect key="frame" x="0.0" y="157" width="320" height="130"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
<subviews>
<imageView userInteractionEnabled="NO" contentMode="center" image="lock_idle.png" id="tyv-qJ-bKR">
<rect key="frame" x="110" y="15" width="100" height="100"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
</imageView>
<imageView userInteractionEnabled="NO" alpha="0.0" contentMode="center" image="lock_idle.png" id="Lpf-KA-3CV">
<rect key="frame" x="110" y="15" width="100" height="100"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
</imageView>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" image="ui_spinner.png" id="27q-lX-0vy">
<rect key="frame" x="122" y="28" width="75" height="75"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
</imageView>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Checking password..." textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="10" id="oU9-lf-nnJ">
<rect key="frame" x="20" y="115" width="280" height="15"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<fontDescription key="fontDescription" name="Copperplate-Bold" family="Copperplate" pointSize="13"/>
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
</view>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" image="ui_textfield.png" id="ivR-Xl-NrT">
<rect key="frame" x="20" y="89" width="280" height="60"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
<rect key="contentStretch" x="0.25" y="0.25" width="0.49999999999999961" height="0.49999999999999961"/>
</imageView>
<textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" textAlignment="center" clearsOnBeginEditing="YES" minimumFontSize="17" id="rTR-7Q-X8o">
<rect key="frame" x="20" y="89" width="280" height="60"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<fontDescription key="fontDescription" name="Copperplate" family="Copperplate" pointSize="36"/>
<textInputTraits key="textInputTraits" secureTextEntry="YES"/>
<connections>
<outlet property="delegate" destination="Nbn-Rv-sP1" id="Y0T-cI-gF1"/>
</connections>
</textField>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Enter your master password:" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="10" id="RhX-bA-EhC">
<rect key="frame" x="32" y="61" width="256" height="20"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" name="Copperplate" family="Copperplate" pointSize="17"/>
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/>
</label>
<view contentMode="scaleToFill" id="Wu7-Mg-9SD">
<rect key="frame" x="0.0" y="391" width="320" height="89"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<subviews>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Trying to log in with a different master password?" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="10" id="vnS-n6-NZI">
<rect key="frame" x="0.0" y="0.0" width="320" height="15"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<fontDescription key="fontDescription" name="Copperplate-Bold" family="Copperplate" pointSize="11"/>
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/>
</label>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" reversesTitleShadowWhenHighlighted="YES" lineBreakMode="middleTruncation" id="wad-V1-K3f">
<rect key="frame" x="10" y="23" width="300" height="46"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="15"/>
<size key="titleShadowOffset" width="0.0" height="1"/>
<state key="normal" title="Change master password" backgroundImage="ui_button_standard_large.png">
<color key="titleColor" cocoaTouchSystemColor="lightTextColor"/>
<color key="titleShadowColor" white="0.0" alpha="1" colorSpace="calibratedWhite"/>
</state>
<state key="highlighted">
<color key="titleColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
</state>
<connections>
<action selector="changeMP" destination="Nbn-Rv-sP1" eventType="touchUpInside" id="7RI-hu-SiS"/>
</connections>
</button>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
</view>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
</view>
<nil key="simulatedStatusBarMetrics"/>
<connections>
<outlet property="changeMPView" destination="Wu7-Mg-9SD" id="84H-HT-5ml"/>
<outlet property="field" destination="rTR-7Q-X8o" id="DHg-gg-MVD"/>
<outlet property="lock" destination="Lpf-KA-3CV" id="6cE-2g-4XQ"/>
<outlet property="messageLabel" destination="oU9-lf-nnJ" id="Ahc-hl-KnJ"/>
<outlet property="spinner" destination="27q-lX-0vy" id="jUx-GK-Lgf"/>
</connections>
</viewController>
</objects>
<point key="canvasLocation" x="455" y="1425"/>
</scene>
<!--Navigation Controller-->
<scene sceneID="8r0-wA-Zre">
<objects>
<placeholder placeholderIdentifier="IBFirstResponder" id="Lcz-JH-B5B" userLabel="First Responder" sceneMemberID="firstResponder"/>
<navigationController id="KZF-fe-y9n" sceneMemberID="viewController">
<toolbarItems/>
<simulatedStatusBarMetrics key="simulatedStatusBarMetrics" statusBarStyle="blackTranslucent"/>
<navigationBar key="navigationBar" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" barStyle="blackOpaque" id="sif-x3-Fol">
<rect key="frame" x="0.0" y="0.0" width="320" height="44"/>
<autoresizingMask key="autoresizingMask"/>
</navigationBar>
<nil name="viewControllers"/>
<connections>
<segue destination="PQa-Xl-A3x" kind="relationship" relationship="rootViewController" id="LUg-eF-JQd"/>
<segue destination="qz3-eG-aEi" kind="modal" identifier="MP_Guide" id="vyG-wN-8hU"/>
</connections>
</navigationController>
</objects>
<point key="canvasLocation" x="-85" y="182"/>
</scene>
</scenes>
<resources>
<image name="Square-bottom.png" width="551" height="58"/>
<image name="background.png" width="480" height="480"/>
<image name="guide_page_0.png" width="320" height="480"/>
<image name="guide_page_1.png" width="320" height="480"/>
<image name="guide_page_2.png" width="320" height="480"/>
<image name="guide_page_3.png" width="320" height="480"/>
<image name="guide_page_4.png" width="320" height="480"/>
<image name="guide_page_5.png" width="320" height="480"/>
<image name="guide_page_6.png" width="320" height="480"/>
<image name="icon_cancel.png" width="32" height="32"/>
<image name="icon_edit.png" width="32" height="32"/>
<image name="icon_plus.png" width="32" height="32"/>
<image name="lock_idle.png" width="100" height="100"/>
<image name="tip_alert_black.png" width="235" height="81"/>
<image name="tip_basic_black.png" width="210" height="60"/>
<image name="ui_button_green_large.png" width="300" height="46"/>
<image name="ui_button_standard_large.png" width="300" height="46"/>
<image name="ui_list_first.png" width="300" height="35"/>
<image name="ui_list_last.png" width="300" height="35"/>
<image name="ui_list_middle.png" width="300" height="34"/>
<image name="ui_panel_container.png" width="300" height="87"/>
<image name="ui_panel_display.png" width="300" height="86"/>
<image name="ui_spinner.png" width="75" height="75"/>
<image name="ui_textfield.png" width="158" height="34"/>
</resources>
<classes>
<class className="MPGuideViewController" superclassName="UIViewController">
<source key="sourceIdentifier" type="project" relativePath="./Classes/MPGuideViewController.h"/>
<relationships>
<relationship kind="action" name="close"/>
<relationship kind="outlet" name="scrollView" candidateClass="UIScrollView"/>
</relationships>
</class>
<class className="MPMainViewController" superclassName="UIViewController">
<source key="sourceIdentifier" type="project" relativePath="./Classes/MPMainViewController.h"/>
<relationships>
<relationship kind="action" name="action:" candidateClass="UIBarButtonItem"/>
<relationship kind="action" name="closeAlert"/>
<relationship kind="action" name="copyContent"/>
<relationship kind="action" name="editPassword"/>
<relationship kind="action" name="incrementPasswordCounter"/>
<relationship kind="outlet" name="alertBody" candidateClass="UITextView"/>
<relationship kind="outlet" name="alertContainer" candidateClass="UIView"/>
<relationship kind="outlet" name="alertTitle" candidateClass="UILabel"/>
<relationship kind="outlet" name="contentContainer" candidateClass="UIView"/>
<relationship kind="outlet" name="contentField" candidateClass="UITextField"/>
<relationship kind="outlet" name="contentTipBody" candidateClass="UILabel"/>
<relationship kind="outlet" name="contentTipContainer" candidateClass="UIView"/>
<relationship kind="outlet" name="contentTipEditIcon" candidateClass="UIImageView"/>
<relationship kind="outlet" name="helpContainer" candidateClass="UIView"/>
<relationship kind="outlet" name="helpView" candidateClass="UIWebView"/>
<relationship kind="outlet" name="passwordCounter" candidateClass="UILabel"/>
<relationship kind="outlet" name="passwordEdit" candidateClass="UIButton"/>
<relationship kind="outlet" name="passwordIncrementer" candidateClass="UIButton"/>
<relationship kind="outlet" name="searchResultsController" candidateClass="MPSearchDelegate"/>
<relationship kind="outlet" name="searchTipContainer" candidateClass="UIView"/>
<relationship kind="outlet" name="siteName" candidateClass="UILabel"/>
<relationship kind="outlet" name="typeButton" candidateClass="UIButton"/>
</relationships>
</class>
<class className="MPSearchDelegate" superclassName="NSObject">
<source key="sourceIdentifier" type="project" relativePath="./Classes/MPSearchDelegate.h"/>
<relationships>
<relationship kind="outlet" name="delegate"/>
<relationship kind="outlet" name="searchDisplayController" candidateClass="UISearchDisplayController"/>
<relationship kind="outlet" name="searchTipContainer" candidateClass="UIView"/>
</relationships>
</class>
<class className="MPTypeViewController" superclassName="UITableViewController">
<source key="sourceIdentifier" type="project" relativePath="./Classes/MPTypeViewController.h"/>
</class>
<class className="MPUnlockViewController" superclassName="UIViewController">
<source key="sourceIdentifier" type="project" relativePath="./Classes/MPUnlockViewController.h"/>
<relationships>
<relationship kind="action" name="changeMP"/>
<relationship kind="outlet" name="changeMPView" candidateClass="UIView"/>
<relationship kind="outlet" name="field" candidateClass="UITextField"/>
<relationship kind="outlet" name="lock" candidateClass="UIImageView"/>
<relationship kind="outlet" name="messageLabel" candidateClass="UILabel"/>
<relationship kind="outlet" name="spinner" candidateClass="UIImageView"/>
</relationships>
</class>
</classes>
<simulatedMetricsContainer key="defaultSimulatedMetrics">
<nil key="statusBar"/>
<simulatedOrientationMetrics key="orientation"/>
<simulatedScreenMetrics key="destination"/>
</simulatedMetricsContainer>
</document>

View File

@@ -1,24 +0,0 @@
//
// Prefix header for all source files of the 'MasterPassword' target in the 'MasterPassword' project
//
#import <Availability.h>
#ifndef __IPHONE_5_0
#warning "This project uses features only available in iOS SDK 5.0 and later."
#endif
#ifdef __OBJC__
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
#ifndef PRODUCTION
#import "TestFlight.h"
#endif
#endif
#import "Pearl-Prefix.pch"
#import "MPTypes.h"
#import "MPConfig.h"

View File

@@ -1,22 +1,32 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model name="" userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="1170" systemVersion="11D50" minimumToolsVersion="Automatic" macOSVersion="Automatic" iOSVersion="Automatic">
<model name="" userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="1171" systemVersion="11E53" 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" syncable="YES"/>
<attribute name="mpHashHex" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="name" attributeType="String" indexed="YES" syncable="YES"/>
<attribute name="type" attributeType="Integer 16" defaultValueString="257" syncable="YES"/>
<attribute name="uses" attributeType="Integer 16" defaultValueString="0" syncable="YES"/>
<attribute name="name" attributeType="String" minValueString="1" indexed="YES" syncable="YES"/>
<attribute name="type_" attributeType="Integer 16" defaultValueString="17" syncable="YES"/>
<attribute name="uses_" attributeType="Integer 16" 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 16" defaultValueString="1" 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>
<fetchRequest name="MPElements" entity="MPElementEntity" predicateString="($query == '' OR name BEGINSWITH[cd] $query) AND mpHashHex == $mpHashHex"/>
<entity name="MPUserEntity" representedClassName="MPUserEntity" syncable="YES">
<attribute name="avatar_" attributeType="Integer 16" defaultValueString="0" syncable="YES"/>
<attribute name="defaultType_" attributeType="Integer 16" defaultValueString="17" syncable="YES"/>
<attribute name="keyID" optional="YES" attributeType="Binary" syncable="YES"/>
<attribute name="lastUsed" optional="YES" attributeType="Date" syncable="YES"/>
<attribute name="name" attributeType="String" syncable="YES"/>
<attribute name="saveKey_" attributeType="Boolean" defaultValueString="NO"/>
<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="135"/>
<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>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1023 B

Some files were not shown because too many files have changed in this diff Show More