Compare commits
62 Commits
2.0-appsto
...
2.0-appsto
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d35434705c | ||
|
|
512f6faf84 | ||
|
|
82195ee090 | ||
|
|
eb2e070fe3 | ||
|
|
14311dfe66 | ||
|
|
10838aad00 | ||
|
|
d340278ec5 | ||
|
|
2eb074be87 | ||
|
|
b84c9b79c8 | ||
|
|
c367d5e6cb | ||
|
|
f8973d6c52 | ||
|
|
beaf4e67e7 | ||
|
|
06a150792d | ||
|
|
9816e783e7 | ||
|
|
8b26c301cf | ||
|
|
6ddd608a3f | ||
|
|
f1a72d8160 | ||
|
|
6d4d57441b | ||
|
|
971538d6b5 | ||
|
|
34d8dc375f | ||
|
|
f294a8c9f5 | ||
|
|
2bd4d57869 | ||
|
|
c7344dbb0c | ||
|
|
97809a1bc7 | ||
|
|
5056f9e2e4 | ||
|
|
150304941c | ||
|
|
ff36f6ce87 | ||
|
|
6422084fa0 | ||
|
|
651a398798 | ||
|
|
31b667c7f7 | ||
|
|
10affd615c | ||
|
|
126c962a98 | ||
|
|
2d6a298095 | ||
|
|
cc086e2dff | ||
|
|
c5fc87b7b5 | ||
|
|
1433c0851e | ||
|
|
c60780a197 | ||
|
|
bb48771989 | ||
|
|
5466e48629 | ||
|
|
d4969a776a | ||
|
|
bb81f5bd5b | ||
|
|
c38920f238 | ||
|
|
be0ae7ff45 | ||
|
|
6808016ab7 | ||
|
|
d3c2698790 | ||
|
|
d1cc9481c3 | ||
|
|
cced75cdfe | ||
|
|
b74bc79699 | ||
|
|
585268eb0f | ||
|
|
a713a639cd | ||
|
|
113c3790fb | ||
|
|
d732b03828 | ||
|
|
cf9dabcc82 | ||
|
|
2bc357cee6 | ||
|
|
c4dca14800 | ||
|
|
d8e2707ac0 | ||
|
|
25df56f90a | ||
|
|
2006e382d4 | ||
|
|
98bffa3755 | ||
|
|
789aa26066 | ||
|
|
773058da78 | ||
|
|
339ef8d1bc |
1
.gitignore
vendored
@@ -29,6 +29,7 @@ Press/MasterPassword_PressKit/MasterPassword_pressrelease_*.pdf
|
|||||||
MasterPassword/Java/**/target
|
MasterPassword/Java/**/target
|
||||||
|
|
||||||
# C
|
# C
|
||||||
|
MasterPassword/C/*.o
|
||||||
MasterPassword/C/mpw
|
MasterPassword/C/mpw
|
||||||
MasterPassword/C/lib/*/*
|
MasterPassword/C/lib/*/*
|
||||||
!MasterPassword/C/lib/*/.source
|
!MasterPassword/C/lib/*/.source
|
||||||
|
|||||||
3
.gitmodules
vendored
@@ -10,6 +10,3 @@
|
|||||||
[submodule "External/RHStatusItemView"]
|
[submodule "External/RHStatusItemView"]
|
||||||
path = External/RHStatusItemView
|
path = External/RHStatusItemView
|
||||||
url = git://github.com/lhunath/RHStatusItemView.git
|
url = git://github.com/lhunath/RHStatusItemView.git
|
||||||
[submodule "External/LoveLyndir"]
|
|
||||||
path = External/LoveLyndir
|
|
||||||
url = git://github.com/Lyndir/love-lyndir.client.git
|
|
||||||
|
|||||||
1
External/LoveLyndir
vendored
2
External/Pearl
vendored
223
External/TestFlight/README.md
vendored
@@ -1,223 +0,0 @@
|
|||||||
## Introduction
|
|
||||||
|
|
||||||
The TestFlight SDK allows you to track how beta testers are testing your application. Out of the box we track simple usage information, such as which tester is using your application, their device model/OS, how long they used the application, and automatic recording of any crashes they encounter.
|
|
||||||
|
|
||||||
The SDK can track more information if you pass it to TestFlight. The Checkpoint API is used to help you track exactly how your testers are using your application. Curious about which users passed level 5 in your game, or posted their high score to Twitter, or found that obscure feature? See "Checkpoint API" down below to see how.
|
|
||||||
|
|
||||||
The SDK also offers a remote logging solution. Find out more about our logging system in the "Remote Logging" section.
|
|
||||||
|
|
||||||
|
|
||||||
## Requirements
|
|
||||||
|
|
||||||
The TestFlight SDK requires iOS 4.3 or above, the Apple LLVM compiler, and the libz library to run.
|
|
||||||
|
|
||||||
|
|
||||||
## Integration
|
|
||||||
|
|
||||||
1. Add the files to your project: File -> Add Files to " "
|
|
||||||
1. Find and select the folder that contains the SDK
|
|
||||||
2. Make sure that "Copy items into destination folder (if needed)" is checked
|
|
||||||
3. Set Folders to "Create groups for any added folders"
|
|
||||||
4. Select all targets that you want to add the SDK to
|
|
||||||
|
|
||||||
2. Verify that libTestFlight.a has been added to the Link Binary With Libraries Build Phase for the targets you want to use the SDK with
|
|
||||||
1. Select your Project in the Project Navigator
|
|
||||||
2. Select the target you want to enable the SDK for
|
|
||||||
3. Select the Build Phases tab
|
|
||||||
4. Open the Link Binary With Libraries Phase
|
|
||||||
5. If libTestFlight.a is not listed, drag and drop the library from your Project Navigator to the Link Binary With Libraries area
|
|
||||||
6. Repeat Steps 2 - 5 until all targets you want to use the SDK with have the SDK linked
|
|
||||||
|
|
||||||
3. Add libz to your Link Binary With Libraries Build Phase
|
|
||||||
1. Select your Project in the Project Navigator
|
|
||||||
2. Select the target you want to enable the SDK for
|
|
||||||
3. Select the Build Phases tab
|
|
||||||
4. Open the Link Binary With Libraries Phase
|
|
||||||
5. Click the + to add a new library
|
|
||||||
6. Find libz.dylib in the list and add it
|
|
||||||
7. Repeat Steps 2 - 6 until all targets you want to use the SDK with have libz.dylib
|
|
||||||
|
|
||||||
4. Get your App Token
|
|
||||||
|
|
||||||
1. If this is a new application, and you have not uploaded it to TestFlight before, first register it here: [https://testflightapp.com/dashboard/applications/create/](https://testflightapp.com/dashboard/applications/create/).
|
|
||||||
|
|
||||||
Otherwise, if you have previously uploaded your app to TestFlight, go to your list of applications ([http://testflightapp.com/dashboard/applications/](http://testflightapp.com/dashboard/applications/)) and click on the application you are using from the list.
|
|
||||||
|
|
||||||
2. Click on the "App Token" tab on the left. The App Token for that application will be there.
|
|
||||||
|
|
||||||
5. In your Application Delegate:
|
|
||||||
|
|
||||||
1. Import TestFlight: `#import "TestFlight.h"`
|
|
||||||
|
|
||||||
2. Launch TestFlight with your App Token
|
|
||||||
|
|
||||||
In your `-application:didFinishLaunchingWithOptions:` method, call `+[TestFlight takeOff:]` with your App Token.
|
|
||||||
|
|
||||||
-(BOOL)application:(UIApplication *)application
|
|
||||||
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
|
|
||||||
// start of your application:didFinishLaunchingWithOptions
|
|
||||||
|
|
||||||
[TestFlight takeOff:@"Insert your Application Token here"];
|
|
||||||
|
|
||||||
// The rest of your application:didFinishLaunchingWithOptions method
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
|
|
||||||
3. To report crashes to you we install our own uncaught exception handler. If you are not currently using an exception handler of your own then all you need to do is go to the next step. If you currently use an Exception Handler, or you use another framework that does please go to the section on advanced exception handling.
|
|
||||||
|
|
||||||
|
|
||||||
## Uploading your build
|
|
||||||
|
|
||||||
After you have integrated the SDK into your application you need to upload your build to TestFlight. You can upload your build on our [website](https://testflightapp.com/dashboard/builds/add/), using our [desktop app](https://testflightapp.com/desktop/), or by using our [upload API](https://testflightapp.com/api/doc/).
|
|
||||||
|
|
||||||
|
|
||||||
## Basic Features
|
|
||||||
|
|
||||||
### Session Information
|
|
||||||
|
|
||||||
View information about how often users use your app, how long they use it for, and when they use it. You can see what type of device the user is using, which OS, which language, etc.
|
|
||||||
|
|
||||||
Sessions automatically start at when the app becomes active and end when the app resigns active. Sessions that start shortly after an end continue the session instead of starting a new one.
|
|
||||||
|
|
||||||
NB: Sessions do not start when `takeOff:` is called, `takeOff:` registers callbacks to start sessions when the app is active.
|
|
||||||
|
|
||||||
For **beta** users, you can see who the users are if they have a TestFlight account and their device is registered with TestFlight.
|
|
||||||
|
|
||||||
|
|
||||||
### Crash Reports
|
|
||||||
|
|
||||||
The TestFlight SDK automatically reports all crashes (beta and prod) to TestFlight's website where you can view them. Crash reports are sent **at** crash time. TestFlight will also automatically symbolicate all crashes (if you have uploaded your dSYM). For **beta** apps, on the site, you can see which checkpoints the user passed before the crash and see remote logs that were sent before the crash.
|
|
||||||
|
|
||||||
|
|
||||||
### Beta In App Updates
|
|
||||||
|
|
||||||
If a user is using a **beta** version of your app and that user has permission to install it; an in app popup will ask them if they would like to install the update. If they tap "Install", the new version is installed from inside the app.
|
|
||||||
|
|
||||||
NB: For this to work, you must increment your build version before uploading. Otherwise the new and old builds will have the same version number and we won't know if the user needs to update or is already using the new version.
|
|
||||||
|
|
||||||
To turn this off set this option before calling `takeOff:`
|
|
||||||
|
|
||||||
[TestFlight setOptions:@{ TFOptionDisableInAppUpdates : @YES }];
|
|
||||||
|
|
||||||
|
|
||||||
## Additional Features
|
|
||||||
|
|
||||||
### Checkpoints
|
|
||||||
|
|
||||||
When a tester does something you care about in your app, you can pass a checkpoint. For example completing a level, adding a todo item, etc. The checkpoint progress is used to provide insight into how your testers are testing your apps. The passed checkpoints are also attached to crashes, which can help when creating steps to replicate. Checkpoints are visible for all beta builds.
|
|
||||||
|
|
||||||
[TestFlight passCheckpoint:@"CHECKPOINT_NAME"];
|
|
||||||
|
|
||||||
Use `passCheckpoint:` to track when a user performs certain tasks in your application. This can be useful for making sure testers are hitting all parts of your application, as well as tracking which testers are being thorough.
|
|
||||||
|
|
||||||
Checkpoints are meant to tell you if a user visited a place in your app or completed a task. They should not be used for debugging purposes. Instead, use Remote Logging for debugging information (more information below).
|
|
||||||
|
|
||||||
NB: Checkpoints are only recorded during BETA sessions.
|
|
||||||
|
|
||||||
|
|
||||||
### Custom Environment Information
|
|
||||||
|
|
||||||
In **beta** builds, if you want to see some extra information about your user, you can add some custom environment information. You must add this information before the session starts (a session starts at `takeOff:`) to see it on TestFlight's website. NB: You can only see this information for **beta** users.
|
|
||||||
|
|
||||||
[TestFlight addCustomEnvironmentInformation:@"info" forKey:@"key"];
|
|
||||||
|
|
||||||
You may call this method as many times as you would like to add more information.
|
|
||||||
|
|
||||||
|
|
||||||
### User Feedback
|
|
||||||
|
|
||||||
In **beta** builds, if you collect feedback from your users, you may pass it back to TestFlight which will associate it with the user's current session.
|
|
||||||
|
|
||||||
[TestFlight submitFeedback:feedback];
|
|
||||||
|
|
||||||
Once users have submitted feedback from inside of the application you can view it in the feedback area of your build page.
|
|
||||||
|
|
||||||
|
|
||||||
### Remote Logging
|
|
||||||
|
|
||||||
Remote Logging allows you to see the logs your app prints out remotely, on TestFlight's website. You can see logs for **beta sessions**.
|
|
||||||
|
|
||||||
To use it, simply replace all of your `NSLog` calls with `TFLog` calls. An easy way to do this without rewriting all your `NSLog` calls is to add the following macro to your `.pch` file.
|
|
||||||
|
|
||||||
#import "TestFlight.h"
|
|
||||||
#define NSLog TFLog
|
|
||||||
|
|
||||||
Not only will `TFLog` log remotely to TestFlight, it will also log to the console (viewable in a device's logs) and STDERR (shown while debugging) just like NSLog does, providing a complete replacement.
|
|
||||||
|
|
||||||
For even better information in your remote logs, such as file name and line number, you can use this macro instead:
|
|
||||||
|
|
||||||
#define NSLog(__FORMAT__, ...) TFLog((@"%s [Line %d] " __FORMAT__), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__)
|
|
||||||
|
|
||||||
Which will produce output that looks like
|
|
||||||
|
|
||||||
-[MyAppDelegate application:didFinishLaunchingWithOptions:] [Line 45] Launched!
|
|
||||||
|
|
||||||
NB: Logs are only recorded during sessions.
|
|
||||||
|
|
||||||
**Custom Logging**
|
|
||||||
|
|
||||||
If you have your own custom logging, call `TFLog` from your custom logging function. If you do not need `TFLog` to log to the console or STDERR because you handle those yourself, you can turn them off with these calls:
|
|
||||||
|
|
||||||
[TestFlight setOptions:@{ TFOptionLogToConsole : @NO }];
|
|
||||||
[TestFlight setOptions:@{ TFOptionLogToSTDERR : @NO }];
|
|
||||||
|
|
||||||
|
|
||||||
## Advanced Notes
|
|
||||||
|
|
||||||
### Checkpoint API
|
|
||||||
|
|
||||||
When passing a checkpoint, TestFlight logs the checkpoint synchronously (See Remote Logging for more information). If your app has very high performance needs, you can turn the logging off with the `TFOptionLogOnCheckpoint` option.
|
|
||||||
|
|
||||||
|
|
||||||
### Remote Logging
|
|
||||||
|
|
||||||
All logging is done synchronously. Every time the SDK logs, it must write data to a file. This is to ensure log integrity at crash time. Without this, we could not trust logs at crash time. If you have a high performance app, please email support@testflightapp.com for more options.
|
|
||||||
|
|
||||||
|
|
||||||
### Advanced Session Control
|
|
||||||
|
|
||||||
Continuing sessions: You can adjust the amount of time a user can leave the app for and still continue the same session when they come back by changing the `TFOptionSessionKeepAliveTimeout` option. Change it to 0 to turn the feature off.
|
|
||||||
|
|
||||||
Manual Session Control: If your app is a music player that continues to play music in the background, a navigation app that continues to function in the background, or any app where a user is considered to be "using" the app even while the app is not active you should use Manual Session Control. Please only use manual session control if you know exactly what you are doing. There are many pitfalls which can result in bad session duration and counts. See `TestFlight+ManualSessions.h` for more information and instructions.
|
|
||||||
|
|
||||||
|
|
||||||
### Advanced Exception/Signal Handling
|
|
||||||
|
|
||||||
An uncaught exception means that your application is in an unknown state and there is not much that you can do but try and exit gracefully. Our SDK does its best to get the data we collect in this situation to you while it is crashing, but it is designed in such a way that the important act of saving the data occurs in as safe way a way as possible before trying to send anything. If you do use uncaught exception or signal handlers, install your handlers before calling `takeOff:`. Our SDK will then call your handler while ours is running. For example:
|
|
||||||
|
|
||||||
/*
|
|
||||||
My Apps Custom uncaught exception catcher, we do special stuff here, and TestFlight takes care of the rest
|
|
||||||
*/
|
|
||||||
void HandleExceptions(NSException *exception) {
|
|
||||||
NSLog(@"This is where we save the application data during a exception");
|
|
||||||
// Save application data on crash
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
My Apps Custom signal catcher, we do special stuff here, and TestFlight takes care of the rest
|
|
||||||
*/
|
|
||||||
void SignalHandler(int sig) {
|
|
||||||
NSLog(@"This is where we save the application data during a signal");
|
|
||||||
// Save application data on crash
|
|
||||||
}
|
|
||||||
|
|
||||||
-(BOOL)application:(UIApplication *)application
|
|
||||||
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
|
|
||||||
// installs HandleExceptions as the Uncaught Exception Handler
|
|
||||||
NSSetUncaughtExceptionHandler(&HandleExceptions);
|
|
||||||
// create the signal action structure
|
|
||||||
struct sigaction newSignalAction;
|
|
||||||
// initialize the signal action structure
|
|
||||||
memset(&newSignalAction, 0, sizeof(newSignalAction));
|
|
||||||
// set SignalHandler as the handler in the signal action structure
|
|
||||||
newSignalAction.sa_handler = &SignalHandler;
|
|
||||||
// set SignalHandler as the handlers for SIGABRT, SIGILL and SIGBUS
|
|
||||||
sigaction(SIGABRT, &newSignalAction, NULL);
|
|
||||||
sigaction(SIGILL, &newSignalAction, NULL);
|
|
||||||
sigaction(SIGBUS, &newSignalAction, NULL);
|
|
||||||
// Call takeOff after install your own unhandled exception and signal handlers
|
|
||||||
[TestFlight takeOff:@"Insert your Application Token here"];
|
|
||||||
// continue with your application initialization
|
|
||||||
}
|
|
||||||
|
|
||||||
You do not need to add the above code if your application does not use exception handling already.
|
|
||||||
|
|
||||||
30
External/TestFlight/TestFlight+AsyncLogging.h
vendored
@@ -1,30 +0,0 @@
|
|||||||
//
|
|
||||||
// TestFlight+AsyncLogging.h
|
|
||||||
// libTestFlight
|
|
||||||
//
|
|
||||||
// Created by Jason Gregori on 2/12/13.
|
|
||||||
// Copyright (c) 2013 TestFlight. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
When logging, it is important that logs are written synchronously. In the event of a crash, all logs that happened before the crash are gauranteed to be on disk. If they were written asynchronously and a crash occurs, you might lose some very valuable logs that might have helped fixed the crash.
|
|
||||||
|
|
||||||
However, because TFLog waits until writing to disk is complete, it takes a while. If you have a very high preformance app that can't afford to wait for logs, these functions are for you.
|
|
||||||
|
|
||||||
USE THESE, BUT KNOW YOU RISK LOSING SOME LOGS AT CRASH TIME
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
#import "TestFlight.h"
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#if __cplusplus
|
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
void TFLog_async(NSString *format, ...) __attribute__((format(__NSString__, 1, 2)));
|
|
||||||
void TFLogv_async(NSString *format, va_list arg_list);
|
|
||||||
#if __cplusplus
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
58
External/TestFlight/TestFlight+ManualSessions.h
vendored
@@ -1,58 +0,0 @@
|
|||||||
//
|
|
||||||
// TestFlight+ManualSessions.h
|
|
||||||
// libTestFlight
|
|
||||||
//
|
|
||||||
// Created by Jason Gregori on 5/16/13.
|
|
||||||
// Copyright (c) 2013 TestFlight. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
YOU ARE STRONGLY ADVISED NOT TO USE THESE METHODS unless you know exactly what you are doing. By using these you take on the responsibility of ensuring your session data is reported accurately.
|
|
||||||
|
|
||||||
The way TestFlight normally does sessions is to automatically start them at app launch, app did become active, and app will enter foreground and end them at app will resign active, app did enter background, or app will terminate.
|
|
||||||
|
|
||||||
If your app is a music player that continues to play music in the background, a navigation app that continues to function in the background, or any app where a user is considered to be "using" the app even while the app is not active, this file is for you.
|
|
||||||
|
|
||||||
|
|
||||||
Usage
|
|
||||||
-----
|
|
||||||
|
|
||||||
1. Add this file to your project.
|
|
||||||
|
|
||||||
2. Set the manual sessions option to true **before** calling `takeOff:`
|
|
||||||
|
|
||||||
[TestFlight setOptions:@{ TFOptionManualSessions : @YES }];
|
|
||||||
|
|
||||||
3. Use the manually start/end session methods to control you sessions.
|
|
||||||
|
|
||||||
|
|
||||||
Pitfalls
|
|
||||||
--------
|
|
||||||
|
|
||||||
When using manual sessions in the background, you must always be aware of the fact that iOS may suspend your app at any time without any warning. You must end your session before that happens. If you do not, the session will continue and include all the time the app was suspended in it's duration if the app is brought back from suspension. This will lead to very inaccurate session lengths and counts.
|
|
||||||
|
|
||||||
On app termination: For the most accurate sessions, try to end your session if you know the app is about to terminate. If you do not, the session will still be ended on the next launch, however, it's end time will not be exact. In that case, the end time will be within 30 seconds of the correct time (session information is saved every 30 seconds and when a checkpoint is sent).
|
|
||||||
|
|
||||||
Sessions do not continue across termination if you do not end a session before termination.
|
|
||||||
|
|
||||||
On crashes: Do not worry about ending sessions in the event of a crash. Even manual sessions are automatically ended in the event of a crash.
|
|
||||||
|
|
||||||
Continuing sessions: If a session is started without 30 seconds of the last session ending (and their was no termination between the sessions), the last session will continue instead of a new session starting. This is the case in manual and automatic sessions. You may change the timeout or turn this feature off using the `TFOptionSessionKeepAliveTimeout` option.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
#import "TestFlight.h"
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
extern NSString *const TFOptionManualSessions; // Defaults to @NO. Set to @YES before calling `takeOff:` in order to use manual session methods.
|
|
||||||
|
|
||||||
|
|
||||||
@interface TestFlight (ManualSessions)
|
|
||||||
|
|
||||||
// these methods are thread safe
|
|
||||||
+ (void)manuallyStartSession;
|
|
||||||
+ (void)manuallyEndSession;
|
|
||||||
|
|
||||||
@end
|
|
||||||
104
External/TestFlight/TestFlight.h
vendored
@@ -1,104 +0,0 @@
|
|||||||
//
|
|
||||||
// TestFlight.h
|
|
||||||
// libTestFlight
|
|
||||||
//
|
|
||||||
// Created by Jonathan Janzen on 06/11/11.
|
|
||||||
// Copyright 2011 TestFlight. All rights reserved.
|
|
||||||
|
|
||||||
#import <Foundation/Foundation.h>
|
|
||||||
#define TESTFLIGHT_SDK_VERSION @"3.0.0"
|
|
||||||
#undef TFLog
|
|
||||||
|
|
||||||
#if __cplusplus
|
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
/*
|
|
||||||
* Remote Logging
|
|
||||||
* BETA only
|
|
||||||
* Note: All Logging is synchronous, see the README for more information.
|
|
||||||
*/
|
|
||||||
void TFLog(NSString *format, ...) __attribute__((format(__NSString__, 1, 2)));
|
|
||||||
void TFLogv(NSString *format, va_list arg_list);
|
|
||||||
void TFLogPreFormatted(NSString *message);
|
|
||||||
#if __cplusplus
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/**
|
|
||||||
* TestFlight object
|
|
||||||
* All methods are class level
|
|
||||||
*/
|
|
||||||
@interface TestFlight : NSObject
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add custom environment information
|
|
||||||
* BETA only
|
|
||||||
* If you want to track custom information such as a user name from your application you can add it here.
|
|
||||||
* NB: This information must be added before the session starts, it is recorded only on session start.
|
|
||||||
*
|
|
||||||
* @param information A string containing the environment you are storing
|
|
||||||
* @param key The key to store the information with
|
|
||||||
*/
|
|
||||||
+ (void)addCustomEnvironmentInformation:(NSString *)information forKey:(NSString*)key;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets up TestFlight's infrastructure.
|
|
||||||
*
|
|
||||||
* - Saves App Token
|
|
||||||
* - Starts automatic session management
|
|
||||||
* - Installs Crash Handlers
|
|
||||||
* - Kicks off sending of old session data
|
|
||||||
*
|
|
||||||
* @param applicationToken Will be the application token for the current application.
|
|
||||||
* The token for this application can be retrieved by going to https://testflightapp.com/dashboard/applications/
|
|
||||||
* selecting this application from the list then selecting SDK.
|
|
||||||
*/
|
|
||||||
+ (void)takeOff:(NSString *)applicationToken;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets custom options
|
|
||||||
*
|
|
||||||
* @param options NSDictionary containing the options you want to set. Available options are described below at "TestFlight Option Keys"
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
+ (void)setOptions:(NSDictionary*)options;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Track when a user has passed a checkpoint after the flight has taken off. Eg. passed level 1, posted high score.
|
|
||||||
* BETA only
|
|
||||||
* Checkpoints are sent in the background.
|
|
||||||
* Note: The checkpoint is logged synchronously (See TFLog and TFOptionLogOnCheckpoint for more information).
|
|
||||||
*
|
|
||||||
* @param checkpointName The name of the checkpoint, this should be a static string
|
|
||||||
*/
|
|
||||||
+ (void)passCheckpoint:(NSString *)checkpointName;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Submits custom feedback to the site. Sends the data in feedback to the site. This is to be used as the method to submit
|
|
||||||
* feedback from custom feedback forms.
|
|
||||||
* BETA only
|
|
||||||
*
|
|
||||||
* @param feedback Your users feedback, method does nothing if feedback is nil
|
|
||||||
*/
|
|
||||||
+ (void)submitFeedback:(NSString*)feedback;
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* TestFlight Option Keys
|
|
||||||
*
|
|
||||||
* Pass these as keys to the dictionary you pass to +`[TestFlight setOptions:]`.
|
|
||||||
* The values should be NSNumber BOOLs (`[NSNumber numberWithBool:YES]` or `@YES`)
|
|
||||||
*/
|
|
||||||
extern NSString *const TFOptionDisableInAppUpdates; // Defaults to @NO. Setting to @YES, disables the in app update screen shown in BETA apps when there is a new version available on TestFlight.
|
|
||||||
extern NSString *const TFOptionFlushSecondsInterval; // Defaults to @60. Set to a number. @0 turns off the flush timer. 30 seconds is the minimum flush interval.
|
|
||||||
extern NSString *const TFOptionLogOnCheckpoint; // Defaults to @YES. Because logging is synchronous, if you have a high preformance app, you might want to turn this off.
|
|
||||||
extern NSString *const TFOptionLogToConsole; // Defaults to @YES. Prints remote logs to Apple System Log.
|
|
||||||
extern NSString *const TFOptionLogToSTDERR; // Defaults to @YES. Sends remote logs to STDERR when debugger is attached.
|
|
||||||
extern NSString *const TFOptionReinstallCrashHandlers; // If set to @YES: Reinstalls crash handlers, to be used if a third party library installs crash handlers overtop of the TestFlight Crash Handlers.
|
|
||||||
extern NSString *const TFOptionReportCrashes; // Defaults to @YES. If set to @NO, crash handlers are never installed. Must be set **before** calling `takeOff:`.
|
|
||||||
extern NSString *const TFOptionSendLogOnlyOnCrash; // Defaults to @NO. Setting to @YES stops remote logs from being sent when sessions end. They would only be sent in the event of a crash.
|
|
||||||
extern NSString *const TFOptionSessionKeepAliveTimeout; // Defaults to @30. This is the amount of time a user can leave the app for and still continue the same session when they come back. If they are away from the app for longer, a new session is created when they come back. Must be a number. Change to @0 to turn off.
|
|
||||||
|
|
||||||
BIN
External/TestFlight/libTestFlight.a
vendored
318
External/TestFlight/release_notes.md
vendored
@@ -1,318 +0,0 @@
|
|||||||
## 3.0.0 - February 18, 2014
|
|
||||||
|
|
||||||
- Remove checkpoints, feedback, and logs from production apps.
|
|
||||||
|
|
||||||
## 2.2.2 - February 5, 2014
|
|
||||||
|
|
||||||
- Remove `+ (void)setDeviceIdentifier:(NSString *)deviceIdentifer`, it is no longer used
|
|
||||||
- On start up, if unsent events are found only attempt to send some of them (this is in case a device doesn't have internet for a while and unsent events build up)
|
|
||||||
- Fix crash if you try to run `TFLog(nil)` (thanks Florian!)
|
|
||||||
|
|
||||||
## 2.2.1 - January 16, 2014
|
|
||||||
|
|
||||||
- Consolidate both SDK versions into one which removes all access to `ASIdentifierManager`
|
|
||||||
|
|
||||||
## 2.2 - December 17, 2013
|
|
||||||
|
|
||||||
- Restore In App Updates
|
|
||||||
- Automatic identification of beta testers
|
|
||||||
|
|
||||||
## 2.1.3 - November 25, 2013
|
|
||||||
|
|
||||||
- Fix bug in 2.1.2-noadid which caused adid to be collected
|
|
||||||
|
|
||||||
## 2.1.2 - November 19, 2013
|
|
||||||
|
|
||||||
- Fix for bug that caused events to not get sent properly when using the `TFOptionSessionKeepAliveTimeout` option
|
|
||||||
- Fix for bug that caused logs that were sent immediately after start session to sometimes not be sent to server
|
|
||||||
|
|
||||||
## 2.1.1 - October 2, 2013
|
|
||||||
|
|
||||||
- Create sdk version that removes all access to `ASIdentifierManager`
|
|
||||||
- Add UIDevice's `identifierForVendor`
|
|
||||||
|
|
||||||
## 2.1 - September 30, 2013
|
|
||||||
|
|
||||||
- Full support for the iPhone 5s’ ARM64 processor while still supporting down to iOS 4.3
|
|
||||||
|
|
||||||
## 2.0.2 - August 30, 2013
|
|
||||||
|
|
||||||
- Fixed a bug where the sdk would cause an app's CPU usage to rise significantly if the device had no internet connection when the app started
|
|
||||||
|
|
||||||
## 2.0.1 - August 22, 2013
|
|
||||||
|
|
||||||
- Fixed rare `8badf00d` crash in TFNetworkManager that happened when the app was in the background
|
|
||||||
|
|
||||||
## 2.0 - August 12, 2013
|
|
||||||
|
|
||||||
Improvements
|
|
||||||
|
|
||||||
- ARC
|
|
||||||
- All public TestFlight methods may be called from any thread or dispatch_queue
|
|
||||||
- All public TestFlight methods (except for `TFLog` and `takeOff:`) are asynchronous, so there is never a wait on them
|
|
||||||
- TestFlight never uses more than 1 network connection at a time
|
|
||||||
- All network traffic is grouped together, sent at once, and transferred in MessagePack. This results in using less bandwidth and less network calls.
|
|
||||||
- All network traffic if server is not reachable
|
|
||||||
- Size of SDK reduced by 70%
|
|
||||||
- New In App Update UI in an alert with landscape support. Should work for all different types of apps.
|
|
||||||
- Manual Sessions: You can manually control session start and end. See `TestFlight+ManualSessions.h` for more information
|
|
||||||
- Combining of back to back sessions. If a session starts less than 30 seconds from the last session which ended, the previous session is continued. You may change the time limit (or turn this off) using the `TFOptionSessionKeepAliveTimeout` option key.
|
|
||||||
- No longer automatically starts a session on `+takeOff:` in order to support new background modes that might launch an app in the background.
|
|
||||||
- `TFOptionReportCrashes` option to not install crash handlers
|
|
||||||
- Remove all calls to `dispatch_get_current_queue`, it is deprecated
|
|
||||||
|
|
||||||
Changes
|
|
||||||
|
|
||||||
- Removed all access to mac address
|
|
||||||
- Added AdSupport.framework requirement (as a replacement for mac address to get accurate user counts)
|
|
||||||
- Add format attribute to TFLog to show warnings for wrong format specifiers or not using a format string
|
|
||||||
- Removed Questions
|
|
||||||
- Removed Feedback View (along with backtrace option)
|
|
||||||
|
|
||||||
Bug Fixes
|
|
||||||
|
|
||||||
- Fixed addrinfo memory leak
|
|
||||||
- Fixed possible `-[TFAirTrafficController getNumberOrNilFrom:withKey:]` crash when bad data is received.
|
|
||||||
- CoreTelephony crash work around: this is a workaround of a iOS bug that causes deallocated instances of `CTTelephonyNetworkInfo` to receive notifications which causes crashes. Core Telephony is used to retrieve the device's mobile carrier.
|
|
||||||
- Fix bug with crash reporting in iOS 7
|
|
||||||
|
|
||||||
|
|
||||||
## 1.2.4 - February 19, 2013
|
|
||||||
|
|
||||||
- Fixed bug that caused crash reports to sometimes not send immediately (they would be resent later)
|
|
||||||
|
|
||||||
## 1.2.3 - January 8, 2013
|
|
||||||
|
|
||||||
- Fixed typos in readme
|
|
||||||
- Fixed bug where logs not sent on crash
|
|
||||||
- Fixed bug where empty crash files were created (but not sent)
|
|
||||||
- Cache path to TF's directory so it does not need to be regenerated every time
|
|
||||||
- Use consts for `setOptions:`
|
|
||||||
- Updated `setDeviceIdentifier:` comments to make them clearer
|
|
||||||
- Remove potentially conflicting function name `UIColorFromRGB`
|
|
||||||
- Fixed crash on bad in app update data
|
|
||||||
|
|
||||||
## 1.2.2 - December 26, 2012
|
|
||||||
|
|
||||||
- Fix typo in app token error message
|
|
||||||
|
|
||||||
## 1.2.1 - December 26, 2012
|
|
||||||
|
|
||||||
- The max number of concurrent network connections has been reduced from 4 to 2.
|
|
||||||
|
|
||||||
##1.2 - November 12, 2012
|
|
||||||
|
|
||||||
* Removed Team Token support. As of version 1.2 takeOff must be called with the Application Token, https://testflightapp.com/dashboard/applications/, choose your application, select SDK, get the Token for this Application.
|
|
||||||
|
|
||||||
##1.2 BETA 3 - October 11, 2012
|
|
||||||
|
|
||||||
* Added application token support. Application Tokens are currently optional if you do not have one you do not need one
|
|
||||||
|
|
||||||
##1.2 BETA 2 - October 9, 2012
|
|
||||||
|
|
||||||
* Resolved an instance of close_file being called on a bad file descriptor
|
|
||||||
|
|
||||||
##1.2 BETA 1 - October 1, 2012
|
|
||||||
|
|
||||||
* Removed support for armv6
|
|
||||||
* Exception handler now returns instead of raising a SIGTRAP
|
|
||||||
|
|
||||||
##1.1 - September 13, 2012
|
|
||||||
|
|
||||||
* armv7s and iOS 6 support
|
|
||||||
* Updated for general release
|
|
||||||
|
|
||||||
##1.1 BETA 3 - September 12, 2012
|
|
||||||
|
|
||||||
* armv7s slice added to library
|
|
||||||
* fixed typo for in application updates, inAppUdates changed to inAppUpdates
|
|
||||||
|
|
||||||
##1.1 BETA 2 - September 6, 2012
|
|
||||||
|
|
||||||
* Re-enabled armv6 support
|
|
||||||
* Added option to disable in application updates
|
|
||||||
|
|
||||||
##1.1 BETA 1 - July 13, 2012
|
|
||||||
|
|
||||||
* Added TFLogv to allow for log customizations. Check the README or online docs for more information.
|
|
||||||
* Added option attachBacktraceToFeedback, which attaches a backtrace to feedback sent from the SDK. For users who use feedback in more than one location in the application.
|
|
||||||
* Resolved issue where other exception handlers would not be called during an exception.
|
|
||||||
* SDK now sends the device language for a session.
|
|
||||||
* Documentation fixes.
|
|
||||||
* Stability fixes.
|
|
||||||
|
|
||||||
###1.0 - March 29, 2012
|
|
||||||
|
|
||||||
* Resolved occurrences of exceptions with the message "No background task exists with identifier 0"
|
|
||||||
|
|
||||||
###1.0 BETA 1 - March 23, 2012
|
|
||||||
|
|
||||||
* Privacy Updates
|
|
||||||
* UDID is no longer collected by the SDK. During testing please use `[TestFlight setDeviceIdentifier:[[UIDevice currentDevice] uniqueIdentifier]];` to send the UDID so you can identify your testers. For release do not set `+setDeviceIdentifier`. See Beta Testing and Release Differentiation in the README or online at [https://testflightapp.com/sdk/doc/1.0beta1/](http://testflightapp.com/sdk/doc/1.0beta1/)
|
|
||||||
|
|
||||||
###0.8.3 - February 14, 2012
|
|
||||||
|
|
||||||
* Rolled previous beta code into release builds
|
|
||||||
* No longer allow in application updates to occur in applications that were obtained from the app store.
|
|
||||||
|
|
||||||
**Tested compiled library with:**
|
|
||||||
|
|
||||||
* Xcode 4.3
|
|
||||||
* Xcode 4.2
|
|
||||||
* Xcode 4.1
|
|
||||||
* Xcode 3.2.6
|
|
||||||
|
|
||||||
###0.8.3 BETA 5 - February 10, 2012
|
|
||||||
|
|
||||||
* Changed logging from asynchronous to synchronous.
|
|
||||||
* Resolved crash when looking for a log path failed.
|
|
||||||
* Added submitFeedback to the TestFlight class to allow for custom feedback forms.
|
|
||||||
|
|
||||||
###0.8.3 BETA 4 - January 20, 2012
|
|
||||||
|
|
||||||
* Resolved an issue that occured when an application was upgraded from 0.8.3 BETA 1 to 0.8.3 BETA 3+ with unsent data from 0.8.3 BETA 1
|
|
||||||
|
|
||||||
###0.8.3 BETA 3 - January 19, 2012
|
|
||||||
|
|
||||||
* On crash log files over 64k will not be sent until next launch.
|
|
||||||
|
|
||||||
**Known Issues:**
|
|
||||||
|
|
||||||
* Logging massive amounts of data at the end of a session may prevent the application from launching in time on next launch
|
|
||||||
|
|
||||||
###0.8.3 BETA 2 - January 13, 2012
|
|
||||||
|
|
||||||
* libz.dylib is now required to be added to your "Link Binary with Libraries" build phase
|
|
||||||
* Log file compression, The compression is done on an as needed basis rather than before sending
|
|
||||||
* Changed all outgoing data from JSON to MessagePack
|
|
||||||
* Added option `logToSTDERR` to disable the `STDERR` logger
|
|
||||||
|
|
||||||
###0.8.3 BETA 1 - December 29, 2011
|
|
||||||
|
|
||||||
* In rare occurrences old session data that had not been sent to our server may have been discarded or attached to the wrong build. It is now no longer discarded
|
|
||||||
* Made sending of Session End events more robust
|
|
||||||
* Network queuing system does better bursting of unsent data
|
|
||||||
* Log files that are larger than 64K are now sent sometime after the next launch
|
|
||||||
* Log files that are larger than 16MB are no longer supported and will be replaced with a message indicating the log file was too large
|
|
||||||
* Fixed crashes while resuming from background
|
|
||||||
|
|
||||||
###0.8.2 - December 20, 2011
|
|
||||||
|
|
||||||
* Promoted 0.8.2 BETA 4 to stable
|
|
||||||
|
|
||||||
**Known Issues:**
|
|
||||||
|
|
||||||
* Under some circumstances Session End events may not be sent until the next launch.
|
|
||||||
* With large log files Session End events may take a long time to show up.
|
|
||||||
|
|
||||||
**Tested compiled library with:**
|
|
||||||
|
|
||||||
* Xcode 4.3
|
|
||||||
* Xcode 4.2
|
|
||||||
* Xcode 4.1
|
|
||||||
* Xcode 3.2.6
|
|
||||||
|
|
||||||
###0.8.2 BETA 4 - December 12, 2011
|
|
||||||
|
|
||||||
* Prevented "The string argument is NULL" from occuring during finishedHandshake in rare cases
|
|
||||||
* Resolved issue where data recorded while offline may not be sent
|
|
||||||
|
|
||||||
###0.8.2 BETA 3 - December 8, 2011
|
|
||||||
|
|
||||||
* Added auto-release pools to background setup and tear down
|
|
||||||
|
|
||||||
###0.8.2 BETA 2 - December 5, 2011
|
|
||||||
|
|
||||||
* Fixed the "pointer being freed was not allocated" bug
|
|
||||||
|
|
||||||
###0.8.1 - November 18, 2011
|
|
||||||
|
|
||||||
* Implemented TFLog logging system, see README for more information
|
|
||||||
* Fixed an issue where Session End events may not be sent until next launch
|
|
||||||
* Fixed an issue where duplicate events could be sent
|
|
||||||
* Fixed an issue with Session End events not being sent from some iPod touch models
|
|
||||||
|
|
||||||
**Tested compiled library with:**
|
|
||||||
|
|
||||||
* Xcode 4.2
|
|
||||||
* Xcode 4.1
|
|
||||||
* Xcode 3.2.6
|
|
||||||
|
|
||||||
###0.8 - November 8, 2011
|
|
||||||
|
|
||||||
* Added `SIGTRAP` as a signal type that we catch
|
|
||||||
* Removed all Objective-c from crash reporting
|
|
||||||
* Removed the use of non signal safe functions from signal handling
|
|
||||||
* Created a signal safe way to get symbols from a stack trace
|
|
||||||
* Changed the keyboardType for Long Answer Questions and Feedback to allow for international character input
|
|
||||||
* Changed `TESTFLIGHT_SDK_VERSION` string to be an `NSString`
|
|
||||||
* Changed cache folder from Library/Caches/TestFlight to Library/Caches/com.testflight.testflightsdk
|
|
||||||
* Fixed issue with saving data when device is offline
|
|
||||||
* Fixed compability issues with iOS 3
|
|
||||||
* Added calling into the rootViewController shouldAutorotateToInterfaceOrientation if a rootViewController is set
|
|
||||||
* Made the comments in TestFlight.h compatible with Appledoc
|
|
||||||
|
|
||||||
Tested compiled library with:
|
|
||||||
|
|
||||||
* Xcode 4.2
|
|
||||||
* Xcode 4.1
|
|
||||||
* Xcode 3.2
|
|
||||||
|
|
||||||
###0.7.2 - September 29, 2011
|
|
||||||
|
|
||||||
* Changed `TESTFLIGHT_SDK_VERSION` string to be an `NSString`
|
|
||||||
* Fixed an issue where exiting an application while the SDK is active caused modal views to be dismissed
|
|
||||||
|
|
||||||
###0.7.1 - September 22, 2011
|
|
||||||
|
|
||||||
* Internal release
|
|
||||||
* Refactoring
|
|
||||||
|
|
||||||
###0.7 - September 21, 2011
|
|
||||||
|
|
||||||
* Moved TestFlight images and data to the Library/Caches folder
|
|
||||||
* Resolved an issue where sometimes the rootViewController could not be found and feedback, questions and upgrade views would not be displayed
|
|
||||||
* In application upgrade changed to allow skipping until the next version is installed and allows upgrades to be forced
|
|
||||||
* Fixed a memory leak when launching questions
|
|
||||||
|
|
||||||
###0.6 - September 2, 2011
|
|
||||||
|
|
||||||
* Renamed base64_encode to testflight_base64_encode to remove a conflict with other third party libraries
|
|
||||||
* Added ability to reinstall crash handlers when they are overwritten using the setOptions API
|
|
||||||
* Fixed an issue where crash reports might not get sent under certain circumstances
|
|
||||||
* Fixed a deadlock when the application is put in the background and then resumed before all information can be sent
|
|
||||||
* Fixed an issue when attempting to un-install all signal handlers during a signal
|
|
||||||
* Added support for landscape mode on the iPad to the Questions and Feedback views
|
|
||||||
* Crash reporting now works in versions of Xcode earlier than 4.2
|
|
||||||
* Fixed a memory leak during handshake
|
|
||||||
|
|
||||||
###0.5 - August 19, 2011
|
|
||||||
|
|
||||||
* Feedback that is not attached to a checkpoint [TestFlight openFeedbackView]
|
|
||||||
* Usability changes to question views
|
|
||||||
* Removed pause and resume sessions, replaced with sessions being stopped and started
|
|
||||||
* Added text auto correction to the Long Answer question type
|
|
||||||
* Crash reports now send on crash instead of next launch
|
|
||||||
|
|
||||||
###0.4 - August 15, 2011
|
|
||||||
|
|
||||||
* In Application Feedback with Questions
|
|
||||||
* In application updates
|
|
||||||
* Custom Environment Information added
|
|
||||||
* Networking stack reimplementation
|
|
||||||
* Exception handling fixes
|
|
||||||
|
|
||||||
###0.3 - June 15, 2011
|
|
||||||
|
|
||||||
* Removed all mention of JSONKit from the README
|
|
||||||
* Added support for using both the Bundle Version and the Bundle Short Version string
|
|
||||||
|
|
||||||
###0.2 - June 14, 2011
|
|
||||||
|
|
||||||
* Removed all categories this allows users to use the SDK without having to set -ObjC and -load_all
|
|
||||||
* Prefixed JSONKit for use in TestFlight to remove reported issues where some users were already using JSONKit
|
|
||||||
* Added support for armv6 again
|
|
||||||
|
|
||||||
###0.1 - June 11, 2011
|
|
||||||
|
|
||||||
* Initial Version
|
|
||||||
@@ -15,13 +15,13 @@
|
|||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>FMWK</string>
|
<string>FMWK</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>2.2.0</string>
|
<string>2.2.1</string>
|
||||||
<key>CFBundleSupportedPlatforms</key>
|
<key>CFBundleSupportedPlatforms</key>
|
||||||
<array>
|
<array>
|
||||||
<string>iPhoneOS</string>
|
<string>iPhoneOS</string>
|
||||||
</array>
|
</array>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>33</string>
|
<string>35</string>
|
||||||
<key>DTPlatformName</key>
|
<key>DTPlatformName</key>
|
||||||
<string>iphoneos</string>
|
<string>iphoneos</string>
|
||||||
<key>MinimumOSVersion</key>
|
<key>MinimumOSVersion</key>
|
||||||
|
|||||||
BIN
External/iOS/Crashlytics.framework/run
vendored
BIN
External/iOS/Crashlytics.framework/submit
vendored
1920
MasterPassword/C/bashlib
Executable file
@@ -1,2 +1,9 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
gcc -I"lib/scrypt/lib" -I"lib/scrypt/libcperciva" -I"lib/proplib/include" -l "crypto_aesctr.o" -l "sha256.o" -l "crypto_scrypt-nosse.o" -l "memlimit.o" -l "scryptenc_cpuperf.o" -l"scryptenc.o" -l"crypto" -l"prop" -L"lib/scrypt" -L"lib/proplib/src/.libs" mpw.c -o mpw
|
# Run with -DDEBUG to enable trace-level output.
|
||||||
|
|
||||||
|
[[ -e lib/scrypt/scryptenc.o ]] || { echo >&2 "Missing scrypt. First get and build the scrypt source in lib/scrypt from <$(<lib/scrypt/.source)>.\n"; exit 1; }
|
||||||
|
|
||||||
|
deps=( -I"lib/scrypt/lib" -I"lib/scrypt/libcperciva" -l "crypto_aesctr.o" -l "sha256.o" -l "crypto_scrypt-nosse.o" -l "memlimit.o" -l "scryptenc_cpuperf.o" -l"scryptenc.o" -l"crypto" -L"." -L"lib/scrypt" )
|
||||||
|
|
||||||
|
gcc "${deps[@]}" -Qunused-arguments -c types.c -o types.o "$@"
|
||||||
|
gcc "${deps[@]}" -Qunused-arguments -l"types.o" mpw.c -o mpw "$@"
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
../Resources/Data/ciphers.plist
|
|
||||||
@@ -1,15 +1,53 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
BINDIR=${BINDIR:+${PREFIX:+$PREFIX/bin}}
|
#
|
||||||
[[ $BINDIR ]] && mkdir -p "$BINDIR"
|
# Install the Master Password CLI tool.
|
||||||
if [[ ! -w $BINDIR ]]; then
|
set -e
|
||||||
for dir in /usr/local/bin ~/.bin ~/bin /usr/bin; do
|
cd "${BASH_SOURCE%/*}"
|
||||||
[[ -w $dir ]] && BINDIR=$dir && break
|
source bashlib
|
||||||
done
|
|
||||||
if [[ ! -w $BINDIR ]]; then
|
inf "This will install the mpw tool."
|
||||||
echo >&2 "Could not find directory to install to."
|
|
||||||
echo >&2 "You can specify a prefix to install to, eg. PREFIX=/usr/local ./install"
|
# Try to guess then ask for the bin dir to install to.
|
||||||
echo >&2 "You can specify a bin directory to install to, eg. BINDIR=~/bin ./install"
|
IFS=: read -a paths <<< "$PATH"
|
||||||
echo >&2 "Make sure you have write permission to the bin directory."
|
if inArray ~/bin "${paths[@]}"; then
|
||||||
|
bindir=~/bin
|
||||||
|
elif inArray ~/.bin "${paths[@]}"; then
|
||||||
|
bindir=~/.bin
|
||||||
|
elif inArray /usr/local/bin "${paths[@]}"; then
|
||||||
|
bindir=/usr/local/bin
|
||||||
|
else
|
||||||
|
bindir=~/bin
|
||||||
fi
|
fi
|
||||||
|
bindir=$(ask -d "$bindir" "What bin directory should I install to?")
|
||||||
|
[[ -d "$bindir" ]] || mkdir "$bindir" || ftl 'Cannot create missing bin directory: %s' "$bindir" || exit
|
||||||
|
[[ -w "$bindir" ]] || ftl 'Cannot write to bin directory: %s' "$bindir" || exit
|
||||||
|
|
||||||
|
# Install Master Password.
|
||||||
|
install mpw "$bindir"
|
||||||
|
[[ ! -e "$bindir/bashlib" ]] && install bashlib "$bindir" ||:
|
||||||
|
|
||||||
|
# Convenience bash function.
|
||||||
|
inf "Installation successful!"
|
||||||
|
echo
|
||||||
|
|
||||||
|
inf "To improve usability, you can install an mpw function in your bash shell."
|
||||||
|
inf "This function adds the following features:"
|
||||||
|
inf " - Automatically remember your user name in the shell if not set."
|
||||||
|
inf " - Automatically put the password in the clipboard (some platforms)."
|
||||||
|
echo
|
||||||
|
inf "To do this you need the following function in ~/.bashrc:\n%s" "$(<mpw.bashrc)"
|
||||||
|
echo
|
||||||
|
inf "We can do this for you automatically now."
|
||||||
|
if ask -c Y!n "Append the mpw function to your .bashrc?"; then
|
||||||
|
cat mpw.bashrc >> ~/.bashrc
|
||||||
|
inf "Done! Don't forget to run '%s' to apply the changes!" "source ~/.bashrc"
|
||||||
fi
|
fi
|
||||||
install -v -s mpw "$BINDIR"
|
echo
|
||||||
|
|
||||||
|
inf "You can also save your user name in ~/.bashrc. Leave blank to skip this step."
|
||||||
|
if MP_USERNAME=$(ask "Your full name:") && [[ $MP_USERNAME ]] ; then
|
||||||
|
printf 'export MP_USERNAME=%q\n' "$MP_USERNAME" >> ~/.bashrc
|
||||||
|
fi
|
||||||
|
echo
|
||||||
|
|
||||||
|
inf "To begin using Master Password, type: mpw [site name]"
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
https://code.google.com/p/portableproplib/
|
|
||||||
BIN
MasterPassword/C/masterpassword-cli-freebsd.zip
Normal file
BIN
MasterPassword/C/masterpassword-cli-osx.zip
Normal file
24
MasterPassword/C/mpw.bashrc
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
## Added by Master Password
|
||||||
|
source bashlib
|
||||||
|
mpw() {
|
||||||
|
_copy() {
|
||||||
|
if hash pbcopy 2>/dev/null; then
|
||||||
|
pbcopy
|
||||||
|
elif hash xclip 2>/dev/null; then
|
||||||
|
xclip
|
||||||
|
else
|
||||||
|
cat; echo 2>/dev/null
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
echo >&2 "Copied!"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Empty the clipboard
|
||||||
|
:| _copy 2>/dev/null
|
||||||
|
|
||||||
|
# Ask for the user's name and password if not yet known.
|
||||||
|
MP_USERNAME=${MP_USERNAME:-$(ask 'Your Full Name:')}
|
||||||
|
|
||||||
|
# Start Master Password and copy the output.
|
||||||
|
printf %s "$(MP_USERNAME=$MP_USERNAME command mpw "$@")" | _copy
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
#define _WITH_GETLINE
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <sys/ioctl.h>
|
#include <sys/ioctl.h>
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
@@ -13,14 +14,14 @@
|
|||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
#include <pwd.h>
|
#include <pwd.h>
|
||||||
#include <uuid/uuid.h>
|
#include <netinet/in.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
|
|
||||||
#include <alg/sha256.h>
|
#include <alg/sha256.h>
|
||||||
#include <crypto/crypto_scrypt.h>
|
#include <crypto/crypto_scrypt.h>
|
||||||
#include <prop/proplib.h>
|
#include "types.h"
|
||||||
|
|
||||||
#define MP_N 32768
|
#define MP_N 32768
|
||||||
#define MP_r 8
|
#define MP_r 8
|
||||||
@@ -32,6 +33,23 @@
|
|||||||
#define MP_env_sitetype "MP_SITETYPE"
|
#define MP_env_sitetype "MP_SITETYPE"
|
||||||
#define MP_env_sitecounter "MP_SITECOUNTER"
|
#define MP_env_sitecounter "MP_SITECOUNTER"
|
||||||
|
|
||||||
|
void usage() {
|
||||||
|
fprintf(stderr, "Usage: mpw [-u name] [-t type] [-c counter] site\n\n");
|
||||||
|
fprintf(stderr, " -u name Specify the full name of the user.\n"
|
||||||
|
" Defaults to %s in env.\n\n", MP_env_username);
|
||||||
|
fprintf(stderr, " -t type Specify the password's template.\n"
|
||||||
|
" Defaults to %s in env or 'long'.\n"
|
||||||
|
" x, max, maximum | 20 characters, contains symbols.\n"
|
||||||
|
" l, long | Copy-friendly, 14 characters, contains symbols.\n"
|
||||||
|
" m, med, medium | Copy-friendly, 8 characters, contains symbols.\n"
|
||||||
|
" b, basic | 8 characters, no symbols.\n"
|
||||||
|
" s, short | Copy-friendly, 4 characters, no symbols.\n"
|
||||||
|
" p, pin | 4 numbers.\n\n", MP_env_sitetype);
|
||||||
|
fprintf(stderr, " -c counter The value of the counter.\n"
|
||||||
|
" Defaults to %s in env or '1'.\n\n", MP_env_sitecounter);
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
char *homedir(const char *filename) {
|
char *homedir(const char *filename) {
|
||||||
char *homedir = NULL;
|
char *homedir = NULL;
|
||||||
#if defined(__CYGWIN__)
|
#if defined(__CYGWIN__)
|
||||||
@@ -59,18 +77,25 @@ char *homedir(const char *filename) {
|
|||||||
|
|
||||||
int main(int argc, char *const argv[]) {
|
int main(int argc, char *const argv[]) {
|
||||||
|
|
||||||
|
if (argc < 2)
|
||||||
|
usage();
|
||||||
|
|
||||||
// Read the environment.
|
// Read the environment.
|
||||||
const char *userName = getenv( MP_env_username );
|
const char *userName = getenv( MP_env_username );
|
||||||
const char *masterPassword = NULL;
|
const char *masterPassword = NULL;
|
||||||
const char *siteName = NULL;
|
const char *siteName = NULL;
|
||||||
|
MPElementType siteType = MPElementTypeGeneratedLong;
|
||||||
const char *siteTypeString = getenv( MP_env_sitetype );
|
const char *siteTypeString = getenv( MP_env_sitetype );
|
||||||
uint32_t siteCounter = 1;
|
uint32_t siteCounter = 1;
|
||||||
const char *siteCounterString = getenv( MP_env_sitecounter );
|
const char *siteCounterString = getenv( MP_env_sitecounter );
|
||||||
|
|
||||||
// Read the options.
|
// Read the options.
|
||||||
char opt;
|
char opt;
|
||||||
while ((opt = getopt(argc, argv, "u:t:c:")) != -1)
|
while ((opt = getopt(argc, argv, "u:t:c:h")) != -1)
|
||||||
switch (opt) {
|
switch (opt) {
|
||||||
|
case 'h':
|
||||||
|
usage();
|
||||||
|
break;
|
||||||
case 'u':
|
case 'u':
|
||||||
userName = optarg;
|
userName = optarg;
|
||||||
break;
|
break;
|
||||||
@@ -106,16 +131,22 @@ int main(int argc, char *const argv[]) {
|
|||||||
fprintf(stderr, "Missing user name.\n");
|
fprintf(stderr, "Missing user name.\n");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
trc("userName: %s\n", userName);
|
||||||
if (!siteName) {
|
if (!siteName) {
|
||||||
fprintf(stderr, "Missing site name.\n");
|
fprintf(stderr, "Missing site name.\n");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
trc("siteName: %s\n", siteName);
|
||||||
if (siteCounterString)
|
if (siteCounterString)
|
||||||
siteCounter = atoi( siteCounterString );
|
siteCounter = atoi( siteCounterString );
|
||||||
if (siteCounter < 1) {
|
if (siteCounter < 1) {
|
||||||
fprintf(stderr, "Invalid site counter: %d\n", siteCounter);
|
fprintf(stderr, "Invalid site counter: %d\n", siteCounter);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
trc("siteCounter: %d\n", siteCounter);
|
||||||
|
if (siteTypeString)
|
||||||
|
siteType = TypeWithName( siteTypeString );
|
||||||
|
trc("siteType: %d (%s)\n", siteType, siteTypeString);
|
||||||
|
|
||||||
// Read the master password.
|
// Read the master password.
|
||||||
char *mpwConfigPath = homedir(".mpw");
|
char *mpwConfigPath = homedir(".mpw");
|
||||||
@@ -123,6 +154,7 @@ int main(int argc, char *const argv[]) {
|
|||||||
fprintf(stderr, "Couldn't resolve path for configuration file: %d\n", errno);
|
fprintf(stderr, "Couldn't resolve path for configuration file: %d\n", errno);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
trc("mpwConfigPath: %s\n", mpwConfigPath);
|
||||||
FILE *mpwConfig = fopen(mpwConfigPath, "r");
|
FILE *mpwConfig = fopen(mpwConfigPath, "r");
|
||||||
if (!mpwConfig) {
|
if (!mpwConfig) {
|
||||||
fprintf(stderr, "Couldn't open configuration file: %s: %d\n", mpwConfigPath, errno);
|
fprintf(stderr, "Couldn't open configuration file: %s: %d\n", mpwConfigPath, errno);
|
||||||
@@ -134,13 +166,32 @@ int main(int argc, char *const argv[]) {
|
|||||||
ssize_t linelen;
|
ssize_t linelen;
|
||||||
while ((linelen = getline(&line, &linecap, mpwConfig)) > 0)
|
while ((linelen = getline(&line, &linecap, mpwConfig)) > 0)
|
||||||
if (strcmp(strsep(&line, ":"), userName) == 0) {
|
if (strcmp(strsep(&line, ":"), userName) == 0) {
|
||||||
masterPassword = line;
|
masterPassword = strsep(&line, "\n");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (!masterPassword) {
|
if (!masterPassword) {
|
||||||
fprintf(stderr, "Missing master password for user: %s\n", userName);
|
fprintf(stderr, "Missing master password for user: %s\n", userName);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
trc("masterPassword: %s\n", masterPassword);
|
||||||
|
|
||||||
|
// Calculate the master key salt.
|
||||||
|
char *mpNameSpace = "com.lyndir.masterpassword";
|
||||||
|
const uint32_t n_userNameLength = htonl(strlen(userName));
|
||||||
|
size_t masterKeySaltLength = strlen(mpNameSpace) + sizeof(n_userNameLength) + strlen(userName);
|
||||||
|
char *masterKeySalt = malloc( masterKeySaltLength );
|
||||||
|
if (!masterKeySalt) {
|
||||||
|
fprintf(stderr, "Could not allocate master key salt: %d\n", errno);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *mKS = masterKeySalt;
|
||||||
|
memcpy(mKS, mpNameSpace, strlen(mpNameSpace)); mKS += strlen(mpNameSpace);
|
||||||
|
memcpy(mKS, &n_userNameLength, sizeof(n_userNameLength)); mKS += sizeof(n_userNameLength);
|
||||||
|
memcpy(mKS, userName, strlen(userName)); mKS += strlen(userName);
|
||||||
|
if (mKS - masterKeySalt != masterKeySaltLength)
|
||||||
|
abort();
|
||||||
|
trc("masterKeySalt ID: %s\n", IDForBuf(masterKeySalt, masterKeySaltLength));
|
||||||
|
|
||||||
// Calculate the master key.
|
// Calculate the master key.
|
||||||
uint8_t *masterKey = malloc( MP_dkLen );
|
uint8_t *masterKey = malloc( MP_dkLen );
|
||||||
@@ -148,67 +199,55 @@ int main(int argc, char *const argv[]) {
|
|||||||
fprintf(stderr, "Could not allocate master key: %d\n", errno);
|
fprintf(stderr, "Could not allocate master key: %d\n", errno);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
const uint32_t n_userNameLength = htonl(strlen(userName));
|
|
||||||
char *masterKeySalt = NULL;
|
|
||||||
size_t masterKeySaltLength = asprintf(&masterKeySalt, "com.lyndir.masterpassword%s%s", (const char *) &n_userNameLength, userName);
|
|
||||||
if (!masterKeySalt) {
|
|
||||||
fprintf (stderr, "Could not allocate master key salt: %d\n", errno);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
if (crypto_scrypt( (const uint8_t *)masterPassword, strlen(masterPassword), (const uint8_t *)masterKeySalt, masterKeySaltLength, MP_N, MP_r, MP_p, masterKey, MP_dkLen ) < 0) {
|
if (crypto_scrypt( (const uint8_t *)masterPassword, strlen(masterPassword), (const uint8_t *)masterKeySalt, masterKeySaltLength, MP_N, MP_r, MP_p, masterKey, MP_dkLen ) < 0) {
|
||||||
fprintf(stderr, "Could not generate master key: %d\n", errno);
|
fprintf(stderr, "Could not generate master key: %d\n", errno);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
memset(masterKeySalt, 0, masterKeySaltLength);
|
memset(masterKeySalt, 0, masterKeySaltLength);
|
||||||
free(masterKeySalt);
|
free(masterKeySalt);
|
||||||
|
trc("masterPassword Hex: %s\n", Hex(masterPassword, strlen(masterPassword)));
|
||||||
|
trc("masterPassword ID: %s\n", IDForBuf(masterPassword, strlen(masterPassword)));
|
||||||
|
trc("masterKey ID: %s\n", IDForBuf(masterKey, MP_dkLen));
|
||||||
|
|
||||||
// Calculate the site seed.
|
// Calculate the site seed.
|
||||||
const uint32_t n_siteCounter = htonl(siteCounter), n_siteNameLength = htonl(strlen(siteName));
|
const uint32_t n_siteNameLength = htonl(strlen(siteName));
|
||||||
char *sitePasswordInfo = NULL;
|
const uint32_t n_siteCounter = htonl(siteCounter);
|
||||||
size_t sitePasswordInfoLength = asprintf(&sitePasswordInfo, "com.lyndir.masterpassword%s%s%s", (const char *) &n_siteNameLength, siteName, (const char *) &n_siteCounter);
|
size_t sitePasswordInfoLength = strlen(mpNameSpace) + sizeof(n_siteNameLength) + strlen(siteName) + sizeof(n_siteCounter);
|
||||||
|
char *sitePasswordInfo = malloc( sitePasswordInfoLength );
|
||||||
if (!sitePasswordInfo) {
|
if (!sitePasswordInfo) {
|
||||||
fprintf(stderr, "Could not allocate site seed: %d\n", errno);
|
fprintf(stderr, "Could not allocate site seed: %d\n", errno);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
char *sPI = sitePasswordInfo;
|
||||||
|
memcpy(sPI, mpNameSpace, strlen(mpNameSpace)); sPI += strlen(mpNameSpace);
|
||||||
|
memcpy(sPI, &n_siteNameLength, sizeof(n_siteNameLength)); sPI += sizeof(n_siteNameLength);
|
||||||
|
memcpy(sPI, siteName, strlen(siteName)); sPI += strlen(siteName);
|
||||||
|
memcpy(sPI, &n_siteCounter, sizeof(n_siteCounter)); sPI += sizeof(n_siteCounter);
|
||||||
|
if (sPI - sitePasswordInfo != sitePasswordInfoLength)
|
||||||
|
abort();
|
||||||
|
trc("seed from: hmac-sha256(masterKey, 'com.lyndir.masterpassword' | %s | %s | %s)\n", Hex(&n_siteNameLength, sizeof(n_siteNameLength)), siteName, Hex(&n_siteCounter, sizeof(n_siteCounter)));
|
||||||
|
trc("sitePasswordInfo ID: %s\n", IDForBuf(sitePasswordInfo, sitePasswordInfoLength));
|
||||||
|
|
||||||
uint8_t sitePasswordSeed[32];
|
uint8_t sitePasswordSeed[32];
|
||||||
HMAC_SHA256_Buf(masterKey, MP_dkLen, sitePasswordInfo, sitePasswordInfoLength, sitePasswordSeed);
|
HMAC_SHA256_Buf(masterKey, MP_dkLen, sitePasswordInfo, sitePasswordInfoLength, sitePasswordSeed);
|
||||||
memset(masterKey, 0, MP_dkLen);
|
memset(masterKey, 0, MP_dkLen);
|
||||||
memset(sitePasswordInfo, 0, sitePasswordInfoLength);
|
memset(sitePasswordInfo, 0, sitePasswordInfoLength);
|
||||||
free(masterKey);
|
free(masterKey);
|
||||||
free(sitePasswordInfo);
|
free(sitePasswordInfo);
|
||||||
|
trc("sitePasswordSeed ID: %s\n", IDForBuf(sitePasswordSeed, 32));
|
||||||
|
|
||||||
// Determine the cipher.
|
// Determine the cipher.
|
||||||
prop_dictionary_t MPTypes_ciphers = prop_dictionary_internalize_from_file("ciphers.plist");
|
const char *cipher = CipherForType(siteType, sitePasswordSeed[0]);
|
||||||
if (!MPTypes_ciphers) {
|
trc("type %s, cipher: %s\n", siteTypeString, cipher);
|
||||||
fprintf (stderr, "Could not read cipher definitions: %d\n", errno);
|
if (strlen(cipher) > 32)
|
||||||
return 1;
|
abort();
|
||||||
}
|
|
||||||
prop_array_t typeCiphers = prop_dictionary_get(prop_dictionary_get(MPTypes_ciphers, "[self classNameOfType:type]"), "[self nameOfType:type]");
|
|
||||||
if (!typeCiphers) {
|
|
||||||
fprintf (stderr, "Could not find cipher definition for type: %s\n", siteTypeString);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
prop_string_t cipher = prop_array_get(typeCiphers, sitePasswordSeed[0] % prop_array_count(typeCiphers));
|
|
||||||
if (!typeCiphers) {
|
|
||||||
fprintf (stderr, "Missing cipher definitions for type: %s\n", siteTypeString);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
//trc(@"type %@, ciphers: %@, selected: %@", [self nameOfType:type], typeCiphers, cipher);
|
|
||||||
|
|
||||||
// Encode the password from the seed using the cipher.
|
// Encode the password from the seed using the cipher.
|
||||||
//NSAssert([seed length] >= [cipher length] + 1, @"Insufficient seed bytes to encode cipher.");
|
char *sitePassword = calloc(strlen(cipher) + 1, sizeof(char));
|
||||||
const prop_dictionary_t characterClasses = prop_dictionary_get(MPTypes_ciphers, "MPCharacterClasses");
|
for (int c = 0; c < strlen(cipher); ++c) {
|
||||||
char *sitePassword = calloc(prop_string_size(cipher) + 1, sizeof(char));
|
sitePassword[c] = CharacterFromClass(cipher[c], sitePasswordSeed[c + 1]);
|
||||||
char cipherClass[2] = {0, 0};
|
trc("class %c, character: %c\n", cipher[c], sitePassword[c]);
|
||||||
for (int c = 0; c < prop_string_size(cipher); ++c) {
|
|
||||||
|
|
||||||
const uint16_t keyByte = sitePasswordSeed[c + 1];
|
|
||||||
cipherClass[0] = prop_string_cstring_nocopy(cipher)[c];
|
|
||||||
const prop_string_t cipherClassCharacters = prop_dictionary_get(characterClasses, cipherClass);
|
|
||||||
const char character = prop_string_cstring_nocopy(cipherClassCharacters)[ keyByte % prop_string_size(cipherClassCharacters) ];
|
|
||||||
|
|
||||||
//trc(@"class %@ has characters: %@, index: %u, selected: %@", cipherClass, cipherClassCharacters, keyByte, character);
|
|
||||||
sitePassword[c] = character;
|
|
||||||
}
|
}
|
||||||
memset(sitePasswordSeed, 0, sizeof(sitePasswordSeed));
|
memset(sitePasswordSeed, 0, sizeof(sitePasswordSeed));
|
||||||
|
|
||||||
|
|||||||
140
MasterPassword/C/types.c
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
//
|
||||||
|
// MPTypes.h
|
||||||
|
// MasterPassword
|
||||||
|
//
|
||||||
|
// Created by Maarten Billemont on 02/01/12.
|
||||||
|
// Copyright (c) 2012 Lyndir. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <ctype.h>
|
||||||
|
|
||||||
|
#include <alg/sha256.h>
|
||||||
|
|
||||||
|
#include "types.h"
|
||||||
|
|
||||||
|
const MPElementType TypeWithName(const char *typeName) {
|
||||||
|
char lowerTypeName[strlen(typeName)];
|
||||||
|
strcpy(lowerTypeName, typeName);
|
||||||
|
for (char *tN = lowerTypeName; *tN; ++tN)
|
||||||
|
*tN = tolower(*tN);
|
||||||
|
|
||||||
|
if (0 == strcmp(lowerTypeName, "x") || 0 == strcmp(lowerTypeName, "max") || 0 == strcmp(lowerTypeName, "maximum"))
|
||||||
|
return MPElementTypeGeneratedMaximum;
|
||||||
|
if (0 == strcmp(lowerTypeName, "l") || 0 == strcmp(lowerTypeName, "long"))
|
||||||
|
return MPElementTypeGeneratedLong;
|
||||||
|
if (0 == strcmp(lowerTypeName, "m") || 0 == strcmp(lowerTypeName, "med") || 0 == strcmp(lowerTypeName, "medium"))
|
||||||
|
return MPElementTypeGeneratedMedium;
|
||||||
|
if (0 == strcmp(lowerTypeName, "b") || 0 == strcmp(lowerTypeName, "basic"))
|
||||||
|
return MPElementTypeGeneratedBasic;
|
||||||
|
if (0 == strcmp(lowerTypeName, "s") || 0 == strcmp(lowerTypeName, "short"))
|
||||||
|
return MPElementTypeGeneratedShort;
|
||||||
|
if (0 == strcmp(lowerTypeName, "p") || 0 == strcmp(lowerTypeName, "pin"))
|
||||||
|
return MPElementTypeGeneratedPIN;
|
||||||
|
|
||||||
|
fprintf(stderr, "Not a generated type name: %s", lowerTypeName);
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *CipherForType(MPElementType type, uint8_t seedByte) {
|
||||||
|
if (!(type & MPElementTypeClassGenerated)) {
|
||||||
|
fprintf(stderr, "Not a generated type: %d", type);
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case MPElementTypeGeneratedMaximum: {
|
||||||
|
char *ciphers[] = { "anoxxxxxxxxxxxxxxxxx", "axxxxxxxxxxxxxxxxxno" };
|
||||||
|
return ciphers[seedByte % 2];
|
||||||
|
}
|
||||||
|
case MPElementTypeGeneratedLong: {
|
||||||
|
char *ciphers[] = { "CvcvnoCvcvCvcv", "CvcvCvcvnoCvcv", "CvcvCvcvCvcvno", "CvccnoCvcvCvcv", "CvccCvcvnoCvcv", "CvccCvcvCvcvno", "CvcvnoCvccCvcv", "CvcvCvccnoCvcv", "CvcvCvccCvcvno", "CvcvnoCvcvCvcc", "CvcvCvcvnoCvcc", "CvcvCvcvCvccno", "CvccnoCvccCvcv", "CvccCvccnoCvcv", "CvccCvccCvcvno", "CvcvnoCvccCvcc", "CvcvCvccnoCvcc", "CvcvCvccCvccno", "CvccnoCvcvCvcc", "CvccCvcvnoCvcc", "CvccCvcvCvccno" };
|
||||||
|
return ciphers[seedByte % 21];
|
||||||
|
}
|
||||||
|
case MPElementTypeGeneratedMedium: {
|
||||||
|
char *ciphers[] = { "CvcnoCvc", "CvcCvcno" };
|
||||||
|
return ciphers[seedByte % 2];
|
||||||
|
}
|
||||||
|
case MPElementTypeGeneratedBasic: {
|
||||||
|
char *ciphers[] = { "aaanaaan", "aannaaan", "aaannaaa" };
|
||||||
|
return ciphers[seedByte % 3];
|
||||||
|
}
|
||||||
|
case MPElementTypeGeneratedShort: {
|
||||||
|
return "Cvcn";
|
||||||
|
}
|
||||||
|
case MPElementTypeGeneratedPIN: {
|
||||||
|
return "nnnn";
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
fprintf(stderr, "Unknown generated type: %d", type);
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const char CharacterFromClass(char characterClass, uint8_t seedByte) {
|
||||||
|
const char *classCharacters;
|
||||||
|
switch (characterClass) {
|
||||||
|
case 'V': {
|
||||||
|
classCharacters = "AEIOU";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'C': {
|
||||||
|
classCharacters = "BCDFGHJKLMNPQRSTVWXYZ";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'v': {
|
||||||
|
classCharacters = "aeiou";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'c': {
|
||||||
|
classCharacters = "bcdfghjklmnpqrstvwxyz";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'A': {
|
||||||
|
classCharacters = "AEIOUBCDFGHJKLMNPQRSTVWXYZ";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'a': {
|
||||||
|
classCharacters = "AEIOUaeiouBCDFGHJKLMNPQRSTVWXYZbcdfghjklmnpqrstvwxyz";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'n': {
|
||||||
|
classCharacters = "0123456789";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'o': {
|
||||||
|
classCharacters = "@&%?,=[]_:-+*$#!'^~;()/.";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'x': {
|
||||||
|
classCharacters = "AEIOUaeiouBCDFGHJKLMNPQRSTVWXYZbcdfghjklmnpqrstvwxyz0123456789!@#$%^&*()";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
fprintf(stderr, "Unknown character class: %c", characterClass);
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return classCharacters[seedByte % strlen(classCharacters)];
|
||||||
|
}
|
||||||
|
const char *IDForBuf(const void *buf, size_t length) {
|
||||||
|
uint8_t hash[32];
|
||||||
|
SHA256_Buf(buf, length, hash);
|
||||||
|
|
||||||
|
char *id = calloc(65, sizeof(char));
|
||||||
|
for (int kH = 0; kH < 32; kH++)
|
||||||
|
sprintf(&(id[kH * 2]), "%02X", hash[kH]);
|
||||||
|
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *Hex(const void *buf, size_t length) {
|
||||||
|
char *id = calloc(length*2+1, sizeof(char));
|
||||||
|
for (int kH = 0; kH < length; kH++)
|
||||||
|
sprintf(&(id[kH * 2]), "%02X", ((const uint8_t*)buf)[kH]);
|
||||||
|
return id;
|
||||||
|
}
|
||||||
52
MasterPassword/C/types.h
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
//
|
||||||
|
// MPTypes.h
|
||||||
|
// MasterPassword
|
||||||
|
//
|
||||||
|
// Created by Maarten Billemont on 02/01/12.
|
||||||
|
// Copyright (c) 2012 Lyndir. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
MPElementContentTypePassword,
|
||||||
|
MPElementContentTypeNote,
|
||||||
|
MPElementContentTypePicture,
|
||||||
|
} MPElementContentType;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
/** Generate the password. */
|
||||||
|
MPElementTypeClassGenerated = 1 << 4,
|
||||||
|
/** Store the password. */
|
||||||
|
MPElementTypeClassStored = 1 << 5,
|
||||||
|
} MPElementTypeClass;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
/** Export the key-protected content data. */
|
||||||
|
MPElementFeatureExportContent = 1 << 10,
|
||||||
|
/** Never export content. */
|
||||||
|
MPElementFeatureDevicePrivate = 1 << 11,
|
||||||
|
} MPElementFeature;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
MPElementTypeGeneratedMaximum = 0x0 | MPElementTypeClassGenerated | 0x0,
|
||||||
|
MPElementTypeGeneratedLong = 0x1 | MPElementTypeClassGenerated | 0x0,
|
||||||
|
MPElementTypeGeneratedMedium = 0x2 | MPElementTypeClassGenerated | 0x0,
|
||||||
|
MPElementTypeGeneratedBasic = 0x4 | MPElementTypeClassGenerated | 0x0,
|
||||||
|
MPElementTypeGeneratedShort = 0x3 | MPElementTypeClassGenerated | 0x0,
|
||||||
|
MPElementTypeGeneratedPIN = 0x5 | MPElementTypeClassGenerated | 0x0,
|
||||||
|
|
||||||
|
MPElementTypeStoredPersonal = 0x0 | MPElementTypeClassStored | MPElementFeatureExportContent,
|
||||||
|
MPElementTypeStoredDevicePrivate = 0x1 | MPElementTypeClassStored | MPElementFeatureDevicePrivate,
|
||||||
|
} MPElementType;
|
||||||
|
|
||||||
|
#ifdef DEBUG
|
||||||
|
#define trc(...) fprintf(stderr, __VA_ARGS__)
|
||||||
|
#else
|
||||||
|
#define trc(...) do {} while (0)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
const MPElementType TypeWithName(const char *typeName);
|
||||||
|
const char *CipherForType(MPElementType type, uint8_t seedByte);
|
||||||
|
const char CharacterFromClass(char characterClass, uint8_t seedByte);
|
||||||
|
const char *IDForBuf(const void *buf, size_t length);
|
||||||
|
const char *Hex(const void *buf, size_t length);
|
||||||
|
|
||||||
@@ -19,8 +19,10 @@ public enum MPElementType {
|
|||||||
GeneratedShort( "Short Password", "Short", "Copy-friendly, 4 characters, no symbols.", MPElementTypeClass.Generated ),
|
GeneratedShort( "Short Password", "Short", "Copy-friendly, 4 characters, no symbols.", MPElementTypeClass.Generated ),
|
||||||
GeneratedPIN( "PIN", "PIN", "4 numbers.", MPElementTypeClass.Generated ),
|
GeneratedPIN( "PIN", "PIN", "4 numbers.", MPElementTypeClass.Generated ),
|
||||||
|
|
||||||
StoredPersonal( "Personal Password", "Personal", "AES-encrypted, exportable.", MPElementTypeClass.Stored, MPElementFeature.ExportContent ),
|
StoredPersonal( "Personal Password", "Personal", "AES-encrypted, exportable.", MPElementTypeClass.Stored,
|
||||||
StoredDevicePrivate( "Device Private Password", "Private", "AES-encrypted, not exported.", MPElementTypeClass.Stored, MPElementFeature.DevicePrivate );
|
MPElementFeature.ExportContent ),
|
||||||
|
StoredDevicePrivate( "Device Private Password", "Private", "AES-encrypted, not exported.", MPElementTypeClass.Stored,
|
||||||
|
MPElementFeature.DevicePrivate );
|
||||||
|
|
||||||
static final Logger logger = Logger.get( MPElementType.class );
|
static final Logger logger = Logger.get( MPElementType.class );
|
||||||
|
|
||||||
@@ -30,7 +32,8 @@ public enum MPElementType {
|
|||||||
private final String shortName;
|
private final String shortName;
|
||||||
private final String description;
|
private final String description;
|
||||||
|
|
||||||
MPElementType(final String name, final String shortName, final String description, final MPElementTypeClass typeClass, final MPElementFeature... typeFeatures) {
|
MPElementType(final String name, final String shortName, final String description, final MPElementTypeClass typeClass,
|
||||||
|
final MPElementFeature... typeFeatures) {
|
||||||
|
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.shortName = shortName;
|
this.shortName = shortName;
|
||||||
@@ -38,8 +41,9 @@ public enum MPElementType {
|
|||||||
this.description = description;
|
this.description = description;
|
||||||
|
|
||||||
ImmutableSet.Builder<MPElementFeature> typeFeaturesBuilder = ImmutableSet.builder();
|
ImmutableSet.Builder<MPElementFeature> typeFeaturesBuilder = ImmutableSet.builder();
|
||||||
for (final MPElementFeature typeFeature : typeFeatures)
|
for (final MPElementFeature typeFeature : typeFeatures) {
|
||||||
typeFeaturesBuilder.add( typeFeature );
|
typeFeaturesBuilder.add( typeFeature );
|
||||||
|
}
|
||||||
this.typeFeatures = typeFeaturesBuilder.build();
|
this.typeFeatures = typeFeaturesBuilder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,12 +72,37 @@ public enum MPElementType {
|
|||||||
return description;
|
return description;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param name The full or short name of the type we want to look up. It is matched case insensitively.
|
||||||
|
*
|
||||||
|
* @return The type with the given name.
|
||||||
|
*/
|
||||||
public static MPElementType forName(final String name) {
|
public static MPElementType forName(final String name) {
|
||||||
|
|
||||||
for (final MPElementType type : values())
|
for (final MPElementType type : values()) {
|
||||||
if (type.getName().equals( name ))
|
if (type.getName().equalsIgnoreCase( name ) || type.getShortName().equalsIgnoreCase( name )) {
|
||||||
return type;
|
return type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
throw logger.bug( "Element type not known: %s", name );
|
throw logger.bug( "Element type not known: %s", name );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param typeClass The class for which we look up types.
|
||||||
|
*
|
||||||
|
* @return All types that support the given class.
|
||||||
|
*/
|
||||||
|
public static ImmutableSet<MPElementType> forClass(final MPElementTypeClass typeClass) {
|
||||||
|
|
||||||
|
ImmutableSet.Builder<MPElementType> types = ImmutableSet.builder();
|
||||||
|
for (final MPElementType type : values()) {
|
||||||
|
if (type.getTypeClass() == typeClass) {
|
||||||
|
types.add( type );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return types.build();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
79
MasterPassword/Java/masterpassword-gui/pom.xml
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<!-- PROJECT METADATA -->
|
||||||
|
<parent>
|
||||||
|
<groupId>com.lyndir.lhunath.masterpassword</groupId>
|
||||||
|
<artifactId>masterpassword</artifactId>
|
||||||
|
<version>GIT-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<name>Master Password GUI</name>
|
||||||
|
<description>A GUI interface to the Master Password algorithm</description>
|
||||||
|
|
||||||
|
<groupId>com.lyndir.lhunath.masterpassword</groupId>
|
||||||
|
<artifactId>masterpassword-gui</artifactId>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
|
<!-- BUILD CONFIGURATION -->
|
||||||
|
<build>
|
||||||
|
<resources>
|
||||||
|
<resource>
|
||||||
|
<directory>src/main/resources</directory>
|
||||||
|
<filtering>false</filtering>
|
||||||
|
</resource>
|
||||||
|
</resources>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-shade-plugin</artifactId>
|
||||||
|
<version>2.2</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<phase>package</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>shade</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<transformers>
|
||||||
|
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
|
||||||
|
<mainClass>com.lyndir.lhunath.masterpassword.GUI</mainClass>
|
||||||
|
</transformer>
|
||||||
|
</transformers>
|
||||||
|
<filters>
|
||||||
|
<filter>
|
||||||
|
<artifact>*:*</artifact>
|
||||||
|
<excludes>
|
||||||
|
<exclude>META-INF/*.SF</exclude>
|
||||||
|
<exclude>META-INF/*.DSA</exclude>
|
||||||
|
<exclude>META-INF/*.RSA</exclude>
|
||||||
|
</excludes>
|
||||||
|
</filter>
|
||||||
|
</filters>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
<!-- DEPENDENCY MANAGEMENT -->
|
||||||
|
<dependencies>
|
||||||
|
|
||||||
|
<!-- PROJECT REFERENCES -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.lyndir.lhunath.masterpassword</groupId>
|
||||||
|
<artifactId>masterpassword-algorithm</artifactId>
|
||||||
|
<version>GIT-SNAPSHOT</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>ch.qos.logback</groupId>
|
||||||
|
<artifactId>logback-classic</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package com.lyndir.lhunath.masterpassword;
|
||||||
|
|
||||||
|
import com.apple.eawt.*;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author lhunath, 2014-06-10
|
||||||
|
*/
|
||||||
|
public class AppleGUI extends GUI {
|
||||||
|
|
||||||
|
public AppleGUI() {
|
||||||
|
|
||||||
|
Application application = Application.getApplication();
|
||||||
|
application.addAppEventListener( new AppForegroundListener() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void appMovedToBackground(AppEvent.AppForegroundEvent arg0) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void appRaisedToForeground(AppEvent.AppForegroundEvent arg0) {
|
||||||
|
open();
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
application.addAppEventListener( new AppReOpenedListener() {
|
||||||
|
@Override
|
||||||
|
public void appReOpened(AppEvent.AppReOpenedEvent arg0) {
|
||||||
|
open();
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package com.lyndir.lhunath.masterpassword;
|
||||||
|
|
||||||
|
import com.google.common.io.Resources;
|
||||||
|
import java.awt.*;
|
||||||
|
import javax.swing.*;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author lhunath, 2014-06-11
|
||||||
|
*/
|
||||||
|
public abstract class AuthenticationPanel extends JPanel {
|
||||||
|
|
||||||
|
protected final UnlockFrame unlockFrame;
|
||||||
|
|
||||||
|
public AuthenticationPanel(final UnlockFrame unlockFrame) {
|
||||||
|
this.unlockFrame = unlockFrame;
|
||||||
|
|
||||||
|
setLayout( new BoxLayout( this, BoxLayout.PAGE_AXIS ) );
|
||||||
|
|
||||||
|
// Avatar
|
||||||
|
add( Box.createVerticalGlue() );
|
||||||
|
add( new JLabel( Res.avatar(0) ) {
|
||||||
|
@Override
|
||||||
|
public Dimension getMaximumSize() {
|
||||||
|
return new Dimension( Integer.MAX_VALUE, Integer.MAX_VALUE );
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
add( Box.createVerticalGlue() );
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void updateUser(boolean repack) {
|
||||||
|
unlockFrame.setUser( getUser() );
|
||||||
|
validate();
|
||||||
|
|
||||||
|
if (repack)
|
||||||
|
unlockFrame.repack();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract User getUser();
|
||||||
|
|
||||||
|
public Component getFocusComponent() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getHelpText() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,164 @@
|
|||||||
|
package com.lyndir.lhunath.masterpassword;
|
||||||
|
|
||||||
|
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.*;
|
||||||
|
|
||||||
|
import com.google.common.base.Splitter;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.google.common.collect.Iterables;
|
||||||
|
import com.google.common.io.CharStreams;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.event.*;
|
||||||
|
import java.io.*;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.NoSuchElementException;
|
||||||
|
import javax.swing.*;
|
||||||
|
import javax.swing.event.DocumentEvent;
|
||||||
|
import javax.swing.event.DocumentListener;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author lhunath, 2014-06-11
|
||||||
|
*/
|
||||||
|
public class ConfigAuthenticationPanel extends AuthenticationPanel implements ItemListener, ActionListener, DocumentListener {
|
||||||
|
|
||||||
|
private final JComboBox userField;
|
||||||
|
private final JLabel masterPasswordLabel;
|
||||||
|
private final JPasswordField masterPasswordField;
|
||||||
|
|
||||||
|
public ConfigAuthenticationPanel(final UnlockFrame unlockFrame) {
|
||||||
|
|
||||||
|
// User
|
||||||
|
super( unlockFrame );
|
||||||
|
JLabel userLabel = new JLabel( "User:" );
|
||||||
|
userLabel.setAlignmentX( LEFT_ALIGNMENT );
|
||||||
|
userLabel.setHorizontalAlignment( SwingConstants.CENTER );
|
||||||
|
userLabel.setVerticalAlignment( SwingConstants.BOTTOM );
|
||||||
|
add( userLabel );
|
||||||
|
|
||||||
|
userField = new JComboBox<User>( new DefaultComboBoxModel<>( readConfigUsers() ) ) {
|
||||||
|
@Override
|
||||||
|
public Dimension getMaximumSize() {
|
||||||
|
return new Dimension( Integer.MAX_VALUE, getPreferredSize().height );
|
||||||
|
}
|
||||||
|
};
|
||||||
|
userField.setAlignmentX( LEFT_ALIGNMENT );
|
||||||
|
userField.addItemListener( this );
|
||||||
|
userField.addActionListener( this );
|
||||||
|
add( userField );
|
||||||
|
|
||||||
|
// Master Password
|
||||||
|
masterPasswordLabel = new JLabel( "Master Password:" );
|
||||||
|
masterPasswordLabel.setAlignmentX( Component.LEFT_ALIGNMENT );
|
||||||
|
masterPasswordLabel.setHorizontalAlignment( SwingConstants.CENTER );
|
||||||
|
masterPasswordLabel.setVerticalAlignment( SwingConstants.BOTTOM );
|
||||||
|
add( masterPasswordLabel );
|
||||||
|
|
||||||
|
masterPasswordField = new JPasswordField() {
|
||||||
|
@Override
|
||||||
|
public Dimension getMaximumSize() {
|
||||||
|
return new Dimension( Integer.MAX_VALUE, getPreferredSize().height );
|
||||||
|
}
|
||||||
|
};
|
||||||
|
masterPasswordField.setAlignmentX( Component.LEFT_ALIGNMENT );
|
||||||
|
masterPasswordField.addActionListener( this );
|
||||||
|
masterPasswordField.getDocument().addDocumentListener( this );
|
||||||
|
add( masterPasswordField );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Component getFocusComponent() {
|
||||||
|
return masterPasswordField.isVisible()? masterPasswordField: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void updateUser(boolean repack) {
|
||||||
|
boolean masterPasswordMissing = userField.getSelectedItem() == null || !((User) userField.getSelectedItem()).hasKey();
|
||||||
|
if (masterPasswordField.isVisible() != masterPasswordMissing) {
|
||||||
|
masterPasswordLabel.setVisible( masterPasswordMissing );
|
||||||
|
masterPasswordField.setVisible( masterPasswordMissing );
|
||||||
|
repack = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
super.updateUser( repack );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected User getUser() {
|
||||||
|
User selectedUser = (User) userField.getSelectedItem();
|
||||||
|
if (selectedUser.hasKey()) {
|
||||||
|
return selectedUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new User( selectedUser.getName(), new String( masterPasswordField.getPassword() ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getHelpText() {
|
||||||
|
return "Reads users from ~/.mpw, the following syntax applies:\nUser Name:masterpassword"
|
||||||
|
+ "\n\nEnsure the file's permissions make it only readable by you!";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean hasConfigUsers() {
|
||||||
|
return new File( System.getProperty( "user.home" ), ".mpw" ).canRead();
|
||||||
|
}
|
||||||
|
|
||||||
|
private User[] readConfigUsers() {
|
||||||
|
ImmutableList.Builder<User> users = ImmutableList.builder();
|
||||||
|
File mpwConfig = new File( System.getProperty( "user.home" ), ".mpw" );
|
||||||
|
try (FileReader mpwReader = new FileReader( mpwConfig )) {
|
||||||
|
for (String line : CharStreams.readLines( mpwReader )) {
|
||||||
|
if (line.startsWith( "#" ) || line.startsWith( "//" ) || line.isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Iterator<String> fields = Splitter.on( ':' ).limit( 2 ).split( line ).iterator();
|
||||||
|
String userName = fields.next(), masterPassword = fields.next();
|
||||||
|
users.add( new User( userName, masterPassword ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
return Iterables.toArray( users.build(), User.class );
|
||||||
|
}
|
||||||
|
catch (FileNotFoundException e) {
|
||||||
|
JOptionPane.showMessageDialog( this, "First create the config file at:\n" + mpwConfig.getAbsolutePath() +
|
||||||
|
"\n\nIt should contain a line for each user of the following format:" +
|
||||||
|
"\nUser Name:masterpassword" +
|
||||||
|
"\n\nEnsure the file's permissions make it only readable by you!", //
|
||||||
|
"Config File Not Found", JOptionPane.WARNING_MESSAGE );
|
||||||
|
return new User[0];
|
||||||
|
}
|
||||||
|
catch (IOException | NoSuchElementException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
String error = ifNotNullElse( e.getLocalizedMessage(), ifNotNullElse( e.getMessage(), e.toString() ) );
|
||||||
|
JOptionPane.showMessageDialog( this, //
|
||||||
|
"Problem reading config file:\n" + mpwConfig.getAbsolutePath() //
|
||||||
|
+ "\n\n" + error, //
|
||||||
|
"Config File Not Readable", JOptionPane.WARNING_MESSAGE );
|
||||||
|
return new User[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void itemStateChanged(final ItemEvent e) {
|
||||||
|
updateUser( false );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void actionPerformed(final ActionEvent e) {
|
||||||
|
updateUser( false );
|
||||||
|
unlockFrame.trySignIn( userField );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void insertUpdate(final DocumentEvent e) {
|
||||||
|
updateUser( false );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeUpdate(final DocumentEvent e) {
|
||||||
|
updateUser( false );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void changedUpdate(final DocumentEvent e) {
|
||||||
|
updateUser( false );
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2008, Maarten Billemont
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package com.lyndir.lhunath.masterpassword;
|
||||||
|
|
||||||
|
import com.google.common.base.Optional;
|
||||||
|
import com.lyndir.lhunath.opal.system.util.TypeUtils;
|
||||||
|
import java.io.IOException;
|
||||||
|
import javax.swing.*;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p> <i>Jun 10, 2008</i> </p>
|
||||||
|
*
|
||||||
|
* @author mbillemo
|
||||||
|
*/
|
||||||
|
public class GUI implements UnlockFrame.SignInCallback {
|
||||||
|
|
||||||
|
private UnlockFrame unlockFrame = new UnlockFrame( this );
|
||||||
|
private PasswordFrame passwordFrame;
|
||||||
|
|
||||||
|
public static void main(final String[] args)
|
||||||
|
throws IOException {
|
||||||
|
|
||||||
|
// Apple
|
||||||
|
Optional<? extends GUI> appleGUI = TypeUtils.newInstance( AppleGUI.class );
|
||||||
|
if (appleGUI.isPresent()) {
|
||||||
|
appleGUI.get().open();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// All others
|
||||||
|
new GUI().open();
|
||||||
|
}
|
||||||
|
|
||||||
|
void open() {
|
||||||
|
SwingUtilities.invokeLater( new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (passwordFrame == null) {
|
||||||
|
unlockFrame.setVisible( true );
|
||||||
|
} else {
|
||||||
|
passwordFrame.setVisible( true );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean signedIn(final User user) {
|
||||||
|
if (!user.hasKey()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
user.getKey();
|
||||||
|
|
||||||
|
passwordFrame = new PasswordFrame( user );
|
||||||
|
|
||||||
|
open();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,175 @@
|
|||||||
|
package com.lyndir.lhunath.masterpassword;
|
||||||
|
|
||||||
|
import static com.lyndir.lhunath.opal.system.util.StringUtils.*;
|
||||||
|
|
||||||
|
import com.google.common.collect.Iterables;
|
||||||
|
import com.lyndir.lhunath.masterpassword.util.Components;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.datatransfer.StringSelection;
|
||||||
|
import java.awt.event.*;
|
||||||
|
import javax.swing.*;
|
||||||
|
import javax.swing.border.*;
|
||||||
|
import javax.swing.event.*;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author lhunath, 2014-06-08
|
||||||
|
*/
|
||||||
|
public class PasswordFrame extends JFrame implements DocumentListener {
|
||||||
|
|
||||||
|
private final User user;
|
||||||
|
private final JTextField siteNameField;
|
||||||
|
private final JComboBox<MPElementType> siteTypeField;
|
||||||
|
private final JSpinner siteCounterField;
|
||||||
|
private final JLabel passwordLabel;
|
||||||
|
|
||||||
|
public PasswordFrame(User user)
|
||||||
|
throws HeadlessException {
|
||||||
|
super( "Master Password" );
|
||||||
|
this.user = user;
|
||||||
|
|
||||||
|
JLabel label;
|
||||||
|
|
||||||
|
setDefaultCloseOperation( DISPOSE_ON_CLOSE );
|
||||||
|
setContentPane( new JPanel( new BorderLayout( 20, 20 ) ) {
|
||||||
|
{
|
||||||
|
setBorder( new EmptyBorder( 20, 20, 20, 20 ) );
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
|
||||||
|
// User
|
||||||
|
add( label = new JLabel( strf( "Generating passwords for: %s", user.getName() ) ), BorderLayout.NORTH );
|
||||||
|
label.setAlignmentX( LEFT_ALIGNMENT );
|
||||||
|
|
||||||
|
// Site
|
||||||
|
JPanel sitePanel = new JPanel();
|
||||||
|
sitePanel.setLayout( new BoxLayout( sitePanel, BoxLayout.PAGE_AXIS ) );
|
||||||
|
sitePanel.setBorder( new CompoundBorder( new EtchedBorder( EtchedBorder.RAISED ), new EmptyBorder( 8, 8, 8, 8 ) ) );
|
||||||
|
add( sitePanel, BorderLayout.CENTER );
|
||||||
|
|
||||||
|
// Site Name
|
||||||
|
sitePanel.add( label = new JLabel( "Site Name:", JLabel.LEADING ) );
|
||||||
|
label.setAlignmentX( LEFT_ALIGNMENT );
|
||||||
|
|
||||||
|
sitePanel.add( siteNameField = new JTextField() {
|
||||||
|
@Override
|
||||||
|
public Dimension getMaximumSize() {
|
||||||
|
return new Dimension( Integer.MAX_VALUE, getPreferredSize().height );
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
siteNameField.setAlignmentX( LEFT_ALIGNMENT );
|
||||||
|
siteNameField.getDocument().addDocumentListener( this );
|
||||||
|
siteNameField.addActionListener( new ActionListener() {
|
||||||
|
@Override
|
||||||
|
public void actionPerformed(final ActionEvent e) {
|
||||||
|
updatePassword( new PasswordCallback() {
|
||||||
|
@Override
|
||||||
|
public void passwordGenerated(final String siteName, final String sitePassword) {
|
||||||
|
StringSelection clipboardContents = new StringSelection( sitePassword );
|
||||||
|
Toolkit.getDefaultToolkit().getSystemClipboard().setContents( clipboardContents, null );
|
||||||
|
|
||||||
|
SwingUtilities.invokeLater( new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
dispose();
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
|
||||||
|
// Site Type & Counter
|
||||||
|
MPElementType[] types = Iterables.toArray( MPElementType.forClass( MPElementTypeClass.Generated ), MPElementType.class );
|
||||||
|
JComponent siteSettings = Components.boxLayout( BoxLayout.LINE_AXIS, //
|
||||||
|
siteTypeField = new JComboBox<>( types ), //
|
||||||
|
siteCounterField = new JSpinner(
|
||||||
|
new SpinnerNumberModel( 1, 1, Integer.MAX_VALUE, 1 ) ) {
|
||||||
|
@Override
|
||||||
|
public Dimension getMaximumSize() {
|
||||||
|
return new Dimension( 20, getPreferredSize().height );
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
siteSettings.setAlignmentX( LEFT_ALIGNMENT );
|
||||||
|
sitePanel.add( siteSettings );
|
||||||
|
siteTypeField.setAlignmentX( LEFT_ALIGNMENT );
|
||||||
|
siteTypeField.setAlignmentY( CENTER_ALIGNMENT );
|
||||||
|
siteTypeField.setSelectedItem( MPElementType.GeneratedLong );
|
||||||
|
siteTypeField.addItemListener( new ItemListener() {
|
||||||
|
@Override
|
||||||
|
public void itemStateChanged(final ItemEvent e) {
|
||||||
|
updatePassword( null );
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
|
||||||
|
siteCounterField.setAlignmentX( RIGHT_ALIGNMENT );
|
||||||
|
siteCounterField.setAlignmentY( CENTER_ALIGNMENT );
|
||||||
|
siteCounterField.addChangeListener( new ChangeListener() {
|
||||||
|
@Override
|
||||||
|
public void stateChanged(final ChangeEvent e) {
|
||||||
|
updatePassword( null );
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
|
||||||
|
// Password
|
||||||
|
add( passwordLabel = new JLabel( " ", JLabel.CENTER ), BorderLayout.SOUTH );
|
||||||
|
passwordLabel.setAlignmentX( LEFT_ALIGNMENT );
|
||||||
|
passwordLabel.setFont( Res.sourceCodeProBlack().deriveFont( 40f ) );
|
||||||
|
|
||||||
|
pack();
|
||||||
|
setMinimumSize( getSize() );
|
||||||
|
setPreferredSize( new Dimension( 600, getSize().height ) );
|
||||||
|
pack();
|
||||||
|
|
||||||
|
setLocationByPlatform( true );
|
||||||
|
setLocationRelativeTo( null );
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updatePassword(final PasswordCallback callback) {
|
||||||
|
final MPElementType siteType = (MPElementType) siteTypeField.getSelectedItem();
|
||||||
|
final String siteName = siteNameField.getText();
|
||||||
|
final int siteCounter = (Integer) siteCounterField.getValue();
|
||||||
|
|
||||||
|
if (siteType.getTypeClass() != MPElementTypeClass.Generated || siteName == null || siteName.isEmpty() || !user.hasKey()) {
|
||||||
|
passwordLabel.setText( null );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Res.execute( new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
final String sitePassword = MasterPassword.generateContent( siteType, siteName, user.getKey(), siteCounter );
|
||||||
|
if (callback != null) {
|
||||||
|
callback.passwordGenerated( siteName, sitePassword );
|
||||||
|
}
|
||||||
|
|
||||||
|
SwingUtilities.invokeLater( new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
passwordLabel.setText( sitePassword );
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void insertUpdate(final DocumentEvent e) {
|
||||||
|
updatePassword( null );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeUpdate(final DocumentEvent e) {
|
||||||
|
updatePassword( null );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void changedUpdate(final DocumentEvent e) {
|
||||||
|
updatePassword( null );
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PasswordCallback {
|
||||||
|
|
||||||
|
void passwordGenerated(String siteName, String sitePassword);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
package com.lyndir.lhunath.masterpassword;
|
||||||
|
|
||||||
|
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.ifNotNullElse;
|
||||||
|
import static com.lyndir.lhunath.opal.system.util.StringUtils.*;
|
||||||
|
|
||||||
|
import com.google.common.base.Throwables;
|
||||||
|
import com.google.common.io.Resources;
|
||||||
|
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.image.ImageObserver;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
import javax.swing.*;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author lhunath, 2014-06-11
|
||||||
|
*/
|
||||||
|
public abstract class Res {
|
||||||
|
|
||||||
|
private static final ExecutorService executor = Executors.newSingleThreadExecutor();
|
||||||
|
private static final Logger logger = Logger.get( Res.class );
|
||||||
|
|
||||||
|
private static Font sourceCodeProBlack;
|
||||||
|
|
||||||
|
public static void execute(final Runnable job) {
|
||||||
|
executor.submit( new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
job.run();
|
||||||
|
}
|
||||||
|
catch (Throwable t) {
|
||||||
|
logger.err( t, "Unexpected: %s", t.getLocalizedMessage() );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Icon iconQuestion() {
|
||||||
|
return new RetinaIcon( Resources.getResource( "media/icon_question@2x.png" ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Icon avatar(final int index) {
|
||||||
|
return new RetinaIcon( Resources.getResource( strf( "media/avatar-%d@2x.png", index ) ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Font sourceCodeProBlack() {
|
||||||
|
try {
|
||||||
|
URL resource = Resources.getResource( "fonts/SourceCodePro-Bold.otf" );
|
||||||
|
Font font = Font.createFont( Font.TRUETYPE_FONT, resource.openStream() );
|
||||||
|
return sourceCodeProBlack != null? sourceCodeProBlack: //
|
||||||
|
(sourceCodeProBlack = font);
|
||||||
|
}
|
||||||
|
catch (FontFormatException | IOException e) {
|
||||||
|
throw Throwables.propagate( e );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class RetinaIcon extends ImageIcon {
|
||||||
|
|
||||||
|
private static final Pattern scalePattern = Pattern.compile(".*@(\\d+)x.[^.]+$");
|
||||||
|
|
||||||
|
private final float scale;
|
||||||
|
|
||||||
|
public RetinaIcon(final URL url) {
|
||||||
|
super( url );
|
||||||
|
|
||||||
|
Matcher scaleMatcher = scalePattern.matcher( url.getPath() );
|
||||||
|
if (scaleMatcher.matches())
|
||||||
|
scale = Float.parseFloat( scaleMatcher.group( 1 ) );
|
||||||
|
else
|
||||||
|
scale = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
//private static URL retinaURL(final URL url) {
|
||||||
|
// try {
|
||||||
|
// final boolean[] isRetina = new boolean[1];
|
||||||
|
// new apple.awt.CImage.HiDPIScaledImage(1,1, BufferedImage.TYPE_INT_ARGB) {
|
||||||
|
// @Override
|
||||||
|
// public void drawIntoImage(BufferedImage image, float v) {
|
||||||
|
// isRetina[0] = v > 1;
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
// return isRetina[0];
|
||||||
|
// } catch (Throwable e) {
|
||||||
|
// e.printStackTrace();
|
||||||
|
// return url;
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getIconWidth() {
|
||||||
|
return (int) (super.getIconWidth() / scale);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getIconHeight() {
|
||||||
|
return (int) (super.getIconHeight() / scale);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void paintIcon(Component c, Graphics g, int x, int y) {
|
||||||
|
ImageObserver observer = ifNotNullElse( getImageObserver(), c );
|
||||||
|
|
||||||
|
Image image = getImage();
|
||||||
|
int width = image.getWidth( observer );
|
||||||
|
int height = image.getHeight( observer );
|
||||||
|
final Graphics2D g2d = (Graphics2D) g.create( x, y, width, height );
|
||||||
|
|
||||||
|
g2d.scale( 1 / scale, 1 / scale );
|
||||||
|
g2d.drawImage( image, 0, 0, observer );
|
||||||
|
g2d.scale( 1, 1 );
|
||||||
|
g2d.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
package com.lyndir.lhunath.masterpassword;
|
||||||
|
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.event.ActionEvent;
|
||||||
|
import java.awt.event.ActionListener;
|
||||||
|
import javax.swing.*;
|
||||||
|
import javax.swing.event.DocumentEvent;
|
||||||
|
import javax.swing.event.DocumentListener;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author lhunath, 2014-06-11
|
||||||
|
*/
|
||||||
|
public class TextAuthenticationPanel extends AuthenticationPanel implements DocumentListener, ActionListener {
|
||||||
|
|
||||||
|
private final JTextField userNameField;
|
||||||
|
private final JPasswordField masterPasswordField;
|
||||||
|
|
||||||
|
public TextAuthenticationPanel(final UnlockFrame unlockFrame) {
|
||||||
|
|
||||||
|
// User Name
|
||||||
|
super( unlockFrame );
|
||||||
|
JLabel userNameLabel = new JLabel( "User Name:" );
|
||||||
|
userNameLabel.setAlignmentX( Component.LEFT_ALIGNMENT );
|
||||||
|
userNameLabel.setHorizontalAlignment( SwingConstants.CENTER );
|
||||||
|
userNameLabel.setVerticalAlignment( SwingConstants.BOTTOM );
|
||||||
|
add( userNameLabel );
|
||||||
|
|
||||||
|
userNameField = new JTextField() {
|
||||||
|
@Override
|
||||||
|
public Dimension getMaximumSize() {
|
||||||
|
return new Dimension( Integer.MAX_VALUE, getPreferredSize().height );
|
||||||
|
}
|
||||||
|
};
|
||||||
|
userNameField.setAlignmentX( Component.LEFT_ALIGNMENT );
|
||||||
|
userNameField.getDocument().addDocumentListener( this );
|
||||||
|
userNameField.addActionListener( this );
|
||||||
|
add( userNameField );
|
||||||
|
|
||||||
|
// Master Password
|
||||||
|
JLabel masterPasswordLabel = new JLabel( "Master Password:" );
|
||||||
|
masterPasswordLabel.setAlignmentX( Component.LEFT_ALIGNMENT );
|
||||||
|
masterPasswordLabel.setHorizontalAlignment( SwingConstants.CENTER );
|
||||||
|
masterPasswordLabel.setVerticalAlignment( SwingConstants.BOTTOM );
|
||||||
|
add( masterPasswordLabel );
|
||||||
|
|
||||||
|
masterPasswordField = new JPasswordField() {
|
||||||
|
@Override
|
||||||
|
public Dimension getMaximumSize() {
|
||||||
|
return new Dimension( Integer.MAX_VALUE, getPreferredSize().height );
|
||||||
|
}
|
||||||
|
};
|
||||||
|
masterPasswordField.setAlignmentX( Component.LEFT_ALIGNMENT );
|
||||||
|
masterPasswordField.addActionListener( this );
|
||||||
|
masterPasswordField.getDocument().addDocumentListener( this );
|
||||||
|
add( masterPasswordField );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Component getFocusComponent() {
|
||||||
|
return userNameField;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected User getUser() {
|
||||||
|
return new User( userNameField.getText(), new String( masterPasswordField.getPassword() ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void insertUpdate(final DocumentEvent e) {
|
||||||
|
updateUser( false );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeUpdate(final DocumentEvent e) {
|
||||||
|
updateUser( false );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void changedUpdate(final DocumentEvent e) {
|
||||||
|
updateUser( false );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void actionPerformed(final ActionEvent e) {
|
||||||
|
updateUser( false );
|
||||||
|
unlockFrame.trySignIn( userNameField, masterPasswordField );
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,175 @@
|
|||||||
|
package com.lyndir.lhunath.masterpassword;
|
||||||
|
|
||||||
|
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.*;
|
||||||
|
|
||||||
|
import com.lyndir.lhunath.masterpassword.util.Components;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.event.*;
|
||||||
|
import javax.swing.*;
|
||||||
|
import javax.swing.border.*;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author lhunath, 2014-06-08
|
||||||
|
*/
|
||||||
|
public class UnlockFrame extends JFrame {
|
||||||
|
|
||||||
|
private final SignInCallback signInCallback;
|
||||||
|
private final JPanel root;
|
||||||
|
private final JButton signInButton;
|
||||||
|
private final JPanel authenticationContainer;
|
||||||
|
private boolean useConfig;
|
||||||
|
public User user;
|
||||||
|
|
||||||
|
public UnlockFrame(final SignInCallback signInCallback)
|
||||||
|
throws HeadlessException {
|
||||||
|
super( "Unlock Master Password" );
|
||||||
|
this.signInCallback = signInCallback;
|
||||||
|
|
||||||
|
setDefaultCloseOperation( DISPOSE_ON_CLOSE );
|
||||||
|
setContentPane( root = new JPanel( new BorderLayout( 20, 20 ) ) );
|
||||||
|
root.setBorder( new EmptyBorder( 20, 20, 20, 20 ) );
|
||||||
|
|
||||||
|
authenticationContainer = new JPanel();
|
||||||
|
authenticationContainer.setLayout( new BoxLayout( authenticationContainer, BoxLayout.PAGE_AXIS ) );
|
||||||
|
authenticationContainer.setBorder( new CompoundBorder( new EtchedBorder( EtchedBorder.RAISED ), new EmptyBorder( 8, 8, 8, 8 ) ) );
|
||||||
|
add( authenticationContainer );
|
||||||
|
|
||||||
|
// Sign In
|
||||||
|
root.add( Components.boxLayout( BoxLayout.LINE_AXIS, Box.createGlue(), signInButton = new JButton( "Sign In" ), Box.createGlue() ),
|
||||||
|
BorderLayout.SOUTH );
|
||||||
|
signInButton.setAlignmentX( LEFT_ALIGNMENT );
|
||||||
|
signInButton.addActionListener( new AbstractAction() {
|
||||||
|
@Override
|
||||||
|
public void actionPerformed(final ActionEvent e) {
|
||||||
|
trySignIn();
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
|
||||||
|
useConfig = ConfigAuthenticationPanel.hasConfigUsers();
|
||||||
|
createAuthenticationPanel();
|
||||||
|
|
||||||
|
setLocationByPlatform( true );
|
||||||
|
setLocationRelativeTo( null );
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void repack() {
|
||||||
|
setPreferredSize( null );
|
||||||
|
pack();
|
||||||
|
setMinimumSize( getSize() );
|
||||||
|
setPreferredSize( new Dimension( 300, 300 ) );
|
||||||
|
pack();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createAuthenticationPanel() {
|
||||||
|
authenticationContainer.removeAll();
|
||||||
|
|
||||||
|
final AuthenticationPanel authenticationPanel;
|
||||||
|
if (useConfig) {
|
||||||
|
authenticationPanel = new ConfigAuthenticationPanel( this );
|
||||||
|
} else {
|
||||||
|
authenticationPanel = new TextAuthenticationPanel( this );
|
||||||
|
}
|
||||||
|
authenticationPanel.updateUser( false );
|
||||||
|
authenticationContainer.add( authenticationPanel, BorderLayout.CENTER );
|
||||||
|
|
||||||
|
final JCheckBox typeCheckBox = new JCheckBox( "Use Config File" );
|
||||||
|
typeCheckBox.setAlignmentX( LEFT_ALIGNMENT );
|
||||||
|
typeCheckBox.setSelected( useConfig );
|
||||||
|
typeCheckBox.addItemListener( new ItemListener() {
|
||||||
|
@Override
|
||||||
|
public void itemStateChanged(final ItemEvent e) {
|
||||||
|
useConfig = typeCheckBox.isSelected();
|
||||||
|
SwingUtilities.invokeLater( new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
createAuthenticationPanel();
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
|
||||||
|
JButton typeHelp = new JButton( Res.iconQuestion() );
|
||||||
|
typeHelp.setMargin( new Insets( 0, 0, 0, 0 ) );
|
||||||
|
typeHelp.setBackground( Color.red );
|
||||||
|
typeHelp.setAlignmentX( RIGHT_ALIGNMENT );
|
||||||
|
typeHelp.setBorder( null );
|
||||||
|
typeHelp.addActionListener( new ActionListener() {
|
||||||
|
@Override
|
||||||
|
public void actionPerformed(final ActionEvent e) {
|
||||||
|
JOptionPane.showMessageDialog( UnlockFrame.this, authenticationPanel.getHelpText(), "Help",
|
||||||
|
JOptionPane.INFORMATION_MESSAGE );
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
if (authenticationPanel.getHelpText() == null) {
|
||||||
|
typeHelp.setVisible( false );
|
||||||
|
}
|
||||||
|
JComponent typePanel = Components.boxLayout( BoxLayout.LINE_AXIS, typeCheckBox, Box.createGlue(), typeHelp );
|
||||||
|
typePanel.setAlignmentX( Component.LEFT_ALIGNMENT );
|
||||||
|
authenticationContainer.add( typePanel );
|
||||||
|
|
||||||
|
checkSignIn();
|
||||||
|
validate();
|
||||||
|
repack();
|
||||||
|
|
||||||
|
SwingUtilities.invokeLater( new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
ifNotNullElse( authenticationPanel.getFocusComponent(), signInButton ).requestFocusInWindow();
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
void setUser(User user) {
|
||||||
|
this.user = user;
|
||||||
|
checkSignIn();
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean checkSignIn() {
|
||||||
|
boolean enabled = user != null && !user.getName().isEmpty() && user.hasKey();
|
||||||
|
signInButton.setEnabled( enabled );
|
||||||
|
|
||||||
|
return enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
void trySignIn(final JComponent... signInComponents) {
|
||||||
|
if (!checkSignIn()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (JComponent signInComponent : signInComponents) {
|
||||||
|
signInComponent.setEnabled( false );
|
||||||
|
}
|
||||||
|
|
||||||
|
signInButton.setEnabled( false );
|
||||||
|
signInButton.setText( "Signing In..." );
|
||||||
|
|
||||||
|
Res.execute( new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
final boolean success = signInCallback.signedIn( user );
|
||||||
|
|
||||||
|
SwingUtilities.invokeLater( new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (success) {
|
||||||
|
dispose();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
signInButton.setText( "Sign In" );
|
||||||
|
for (JComponent signInComponent : signInComponents) {
|
||||||
|
signInComponent.setEnabled( true );
|
||||||
|
}
|
||||||
|
checkSignIn();
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SignInCallback {
|
||||||
|
|
||||||
|
boolean signedIn(User user);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
package com.lyndir.lhunath.masterpassword;
|
||||||
|
|
||||||
|
import static com.lyndir.lhunath.opal.system.util.StringUtils.*;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author lhunath, 2014-06-08
|
||||||
|
*/
|
||||||
|
public class User {
|
||||||
|
|
||||||
|
private final String name;
|
||||||
|
private final String masterPassword;
|
||||||
|
private byte[] key;
|
||||||
|
|
||||||
|
public User(final String name, final String masterPassword) {
|
||||||
|
this.name = name;
|
||||||
|
this.masterPassword = masterPassword;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasKey() {
|
||||||
|
return key != null || (masterPassword != null && !masterPassword.isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getKey() {
|
||||||
|
if (key == null) {
|
||||||
|
if (!hasKey()) {
|
||||||
|
throw new IllegalStateException( strf( "Master password unknown for user: %s", name ) );
|
||||||
|
} else {
|
||||||
|
key = MasterPassword.keyForPassword( masterPassword, name );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return name.hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package com.lyndir.lhunath.masterpassword.util;
|
||||||
|
|
||||||
|
import java.awt.*;
|
||||||
|
import javax.swing.*;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author lhunath, 2014-06-08
|
||||||
|
*/
|
||||||
|
public abstract class Components {
|
||||||
|
|
||||||
|
public static JComponent boxLayout(int axis, Component... components) {
|
||||||
|
JPanel container = new JPanel();
|
||||||
|
container.setLayout( new BoxLayout( container, axis ) );
|
||||||
|
for (Component component : components)
|
||||||
|
container.add( component );
|
||||||
|
|
||||||
|
return container;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
<configuration scan="true">
|
||||||
|
|
||||||
|
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||||
|
<layout class="ch.qos.logback.classic.PatternLayout">
|
||||||
|
<Pattern>%-8relative %22c{0} [%-5level] %msg%n</Pattern>
|
||||||
|
</layout>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<logger name="com.lyndir" level="TRACE" />
|
||||||
|
|
||||||
|
<root level="INFO">
|
||||||
|
<appender-ref ref="STDOUT" />
|
||||||
|
</root>
|
||||||
|
|
||||||
|
</configuration>
|
||||||
|
After Width: | Height: | Size: 8.7 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 8.7 KiB |
|
After Width: | Height: | Size: 8.9 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 8.5 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 9.1 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 9.3 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 8.7 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 9.1 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 9.0 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 8.6 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 8.8 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 8.7 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 8.7 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 8.7 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 8.4 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 8.8 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 8.6 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 8.7 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 8.6 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 7.6 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
@@ -0,0 +1 @@
|
|||||||
|
3269
|
||||||
@@ -21,6 +21,7 @@
|
|||||||
<modules>
|
<modules>
|
||||||
<module>masterpassword-algorithm</module>
|
<module>masterpassword-algorithm</module>
|
||||||
<module>masterpassword-cli</module>
|
<module>masterpassword-cli</module>
|
||||||
|
<module>masterpassword-gui</module>
|
||||||
<!--module>masterpassword-android</module-->
|
<!--module>masterpassword-android</module-->
|
||||||
</modules>
|
</modules>
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ id<MPAlgorithm> MPAlgorithmForVersion(NSUInteger version) {
|
|||||||
versionToAlgorithm = [NSMutableDictionary dictionary];
|
versionToAlgorithm = [NSMutableDictionary dictionary];
|
||||||
|
|
||||||
id<MPAlgorithm> algorithm = versionToAlgorithm[@(version)];
|
id<MPAlgorithm> algorithm = versionToAlgorithm[@(version)];
|
||||||
if (!algorithm) if ((algorithm = [NSClassFromString( PearlString( @"MPAlgorithmV%lu", (unsigned long)version ) ) new]))
|
if (!algorithm) if ((algorithm = [NSClassFromString( strf( @"MPAlgorithmV%lu", (unsigned long)version ) ) new]))
|
||||||
versionToAlgorithm[@(version)] = algorithm;
|
versionToAlgorithm[@(version)] = algorithm;
|
||||||
|
|
||||||
return algorithm;
|
return algorithm;
|
||||||
|
|||||||
@@ -47,10 +47,12 @@
|
|||||||
|
|
||||||
- (NSString *)generateContentNamed:(NSString *)name ofType:(MPElementType)type withCounter:(NSUInteger)counter usingKey:(MPKey *)key {
|
- (NSString *)generateContentNamed:(NSString *)name ofType:(MPElementType)type withCounter:(NSUInteger)counter usingKey:(MPKey *)key {
|
||||||
|
|
||||||
static NSDictionary *MPTypes_ciphers = nil;
|
static __strong NSDictionary *MPTypes_ciphers = nil;
|
||||||
if (MPTypes_ciphers == nil)
|
static dispatch_once_t once = 0;
|
||||||
|
dispatch_once(&once, ^{
|
||||||
MPTypes_ciphers = [NSDictionary dictionaryWithContentsOfURL:
|
MPTypes_ciphers = [NSDictionary dictionaryWithContentsOfURL:
|
||||||
[[NSBundle mainBundle] URLForResource:@"ciphers" withExtension:@"plist"]];
|
[[NSBundle mainBundle] URLForResource:@"ciphers" withExtension:@"plist"]];
|
||||||
|
});
|
||||||
|
|
||||||
// Determine the seed whose bytes will be used for calculating a password
|
// Determine the seed whose bytes will be used for calculating a password
|
||||||
uint32_t ncounter = htonl(counter), nnameLength = htonl(name.length);
|
uint32_t ncounter = htonl(counter), nnameLength = htonl(name.length);
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
|
|||||||
return [PearlKeyChain createQueryForClass:kSecClassGenericPassword
|
return [PearlKeyChain createQueryForClass:kSecClassGenericPassword
|
||||||
attributes:@{
|
attributes:@{
|
||||||
(__bridge id)kSecAttrService : @"Saved Master Password",
|
(__bridge id)kSecAttrService : @"Saved Master Password",
|
||||||
(__bridge id)kSecAttrAccount : IfNotNilElse( user.name, @"" )
|
(__bridge id)kSecAttrAccount : user.name?: @""
|
||||||
}
|
}
|
||||||
matches:nil];
|
matches:nil];
|
||||||
}
|
}
|
||||||
@@ -132,9 +132,6 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
|
|||||||
|
|
||||||
@try {
|
@try {
|
||||||
if ([[MPConfig get].sendInfo boolValue]) {
|
if ([[MPConfig get].sendInfo boolValue]) {
|
||||||
#ifdef TESTFLIGHT_SDK_VERSION
|
|
||||||
[TestFlight addCustomEnvironmentInformation:user.userID forKey:@"username"];
|
|
||||||
#endif
|
|
||||||
#ifdef CRASHLYTICS
|
#ifdef CRASHLYTICS
|
||||||
[Crashlytics setObjectValue:user.userID forKey:@"username"];
|
[Crashlytics setObjectValue:user.userID forKey:@"username"];
|
||||||
[Crashlytics setUserName:user.userID];
|
[Crashlytics setUserName:user.userID];
|
||||||
|
|||||||
@@ -11,13 +11,13 @@
|
|||||||
#import "UbiquityStoreManager.h"
|
#import "UbiquityStoreManager.h"
|
||||||
#import "MPFixable.h"
|
#import "MPFixable.h"
|
||||||
|
|
||||||
typedef enum {
|
typedef NS_ENUM( NSUInteger, MPImportResult ) {
|
||||||
MPImportResultSuccess,
|
MPImportResultSuccess,
|
||||||
MPImportResultCancelled,
|
MPImportResultCancelled,
|
||||||
MPImportResultInvalidPassword,
|
MPImportResultInvalidPassword,
|
||||||
MPImportResultMalformedInput,
|
MPImportResultMalformedInput,
|
||||||
MPImportResultInternalError,
|
MPImportResultInternalError,
|
||||||
} MPImportResult;
|
};
|
||||||
|
|
||||||
@interface MPAppDelegate_Shared(Store)<UbiquityStoreManagerDelegate>
|
@interface MPAppDelegate_Shared(Store)<UbiquityStoreManagerDelegate>
|
||||||
|
|
||||||
|
|||||||
@@ -136,8 +136,7 @@ PearlAssociatedObjectProperty( NSManagedObjectContext*, MainManagedObjectContext
|
|||||||
^(NSNotification *note) {
|
^(NSNotification *note) {
|
||||||
[[self mainManagedObjectContext] saveToStore];
|
[[self mainManagedObjectContext] saveToStore];
|
||||||
}];
|
}];
|
||||||
[[NSNotificationCenter defaultCenter]
|
[[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillResignActiveNotification object:UIApp
|
||||||
addObserverForName:UIApplicationWillResignActiveNotification object:UIApp
|
|
||||||
queue:[NSOperationQueue mainQueue] usingBlock:
|
queue:[NSOperationQueue mainQueue] usingBlock:
|
||||||
^(NSNotification *note) {
|
^(NSNotification *note) {
|
||||||
[[self mainManagedObjectContext] saveToStore];
|
[[self mainManagedObjectContext] saveToStore];
|
||||||
@@ -291,7 +290,7 @@ PearlAssociatedObjectProperty( NSManagedObjectContext*, MainManagedObjectContext
|
|||||||
URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
|
URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
|
||||||
NSURL *oldLocalStoreURL = [[applicationFilesDirectory
|
NSURL *oldLocalStoreURL = [[applicationFilesDirectory
|
||||||
URLByAppendingPathComponent:@"MasterPassword" isDirectory:NO] URLByAppendingPathExtension:@"sqlite"];
|
URLByAppendingPathComponent:@"MasterPassword" isDirectory:NO] URLByAppendingPathExtension:@"sqlite"];
|
||||||
if (![[NSFileManager defaultManager] fileExistsAtPath:oldLocalStoreURL.path isDirectory:NO]) {
|
if (![[NSFileManager defaultManager] fileExistsAtPath:oldLocalStoreURL.path isDirectory:NULL]) {
|
||||||
inf( @"No V1 local store to migrate." );
|
inf( @"No V1 local store to migrate." );
|
||||||
return YES;
|
return YES;
|
||||||
}
|
}
|
||||||
@@ -303,7 +302,7 @@ PearlAssociatedObjectProperty( NSManagedObjectContext*, MainManagedObjectContext
|
|||||||
- (BOOL)migrateFromLocalStore:(NSURL *)oldLocalStoreURL {
|
- (BOOL)migrateFromLocalStore:(NSURL *)oldLocalStoreURL {
|
||||||
|
|
||||||
NSURL *newLocalStoreURL = [self.storeManager URLForLocalStore];
|
NSURL *newLocalStoreURL = [self.storeManager URLForLocalStore];
|
||||||
if ([[NSFileManager defaultManager] fileExistsAtPath:newLocalStoreURL.path isDirectory:NO]) {
|
if ([[NSFileManager defaultManager] fileExistsAtPath:newLocalStoreURL.path isDirectory:NULL]) {
|
||||||
wrn( @"Can't migrate local store: A new local store already exists." );
|
wrn( @"Can't migrate local store: A new local store already exists." );
|
||||||
return YES;
|
return YES;
|
||||||
}
|
}
|
||||||
@@ -430,7 +429,7 @@ PearlAssociatedObjectProperty( NSManagedObjectContext*, MainManagedObjectContext
|
|||||||
@"cause" : @(cause),
|
@"cause" : @(cause),
|
||||||
@"error.code" : @(error.code),
|
@"error.code" : @(error.code),
|
||||||
@"error.domain" : NilToNSNull( error.domain ),
|
@"error.domain" : NilToNSNull( error.domain ),
|
||||||
@"error.reason" : NilToNSNull( IfNotNilElse( [error localizedFailureReason], [error localizedDescription] ) ),
|
@"error.reason" : NilToNSNull( [error localizedFailureReason]?: [error localizedDescription] ),
|
||||||
} );
|
} );
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -784,7 +783,7 @@ PearlAssociatedObjectProperty( NSManagedObjectContext*, MainManagedObjectContext
|
|||||||
|
|
||||||
[export appendFormat:@"%@ %8ld %8s %20s\t%@\n",
|
[export appendFormat:@"%@ %8ld %8s %20s\t%@\n",
|
||||||
[[NSDateFormatter rfc3339DateFormatter] stringFromDate:lastUsed], (long)uses,
|
[[NSDateFormatter rfc3339DateFormatter] stringFromDate:lastUsed], (long)uses,
|
||||||
[PearlString( @"%lu:%lu", (long)type, (unsigned long)version ) UTF8String], [name UTF8String], content
|
[strf( @"%lu:%lu", (long)type, (unsigned long)version ) UTF8String], [name UTF8String], content
|
||||||
? content: @""];
|
? content: @""];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
|
|
||||||
@property(nonatomic, retain) NSNumber *sendInfo;
|
@property(nonatomic, retain) NSNumber *sendInfo;
|
||||||
@property(nonatomic, retain) NSNumber *rememberLogin;
|
@property(nonatomic, retain) NSNumber *rememberLogin;
|
||||||
|
@property(nonatomic, retain) NSNumber *hidePasswords;
|
||||||
|
|
||||||
@property(nonatomic, retain) NSNumber *iCloudDecided;
|
@property(nonatomic, retain) NSNumber *iCloudDecided;
|
||||||
@property(nonatomic, retain) NSNumber *checkInconsistency;
|
@property(nonatomic, retain) NSNumber *checkInconsistency;
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
@implementation MPConfig
|
@implementation MPConfig
|
||||||
|
|
||||||
@dynamic sendInfo, rememberLogin, iCloudDecided, checkInconsistency;
|
@dynamic sendInfo, rememberLogin, iCloudDecided, checkInconsistency, hidePasswords;
|
||||||
|
|
||||||
- (id)init {
|
- (id)init {
|
||||||
|
|
||||||
@@ -22,6 +22,7 @@
|
|||||||
|
|
||||||
NSStringFromSelector( @selector( sendInfo ) ) : @NO,
|
NSStringFromSelector( @selector( sendInfo ) ) : @NO,
|
||||||
NSStringFromSelector( @selector( rememberLogin ) ) : @NO,
|
NSStringFromSelector( @selector( rememberLogin ) ) : @NO,
|
||||||
|
NSStringFromSelector( @selector( hidePasswords ) ) : @NO,
|
||||||
NSStringFromSelector( @selector( iCloudDecided ) ) : @NO,
|
NSStringFromSelector( @selector( iCloudDecided ) ) : @NO,
|
||||||
NSStringFromSelector( @selector( checkInconsistency ) ) : @NO
|
NSStringFromSelector( @selector( checkInconsistency ) ) : @NO
|
||||||
}];
|
}];
|
||||||
|
|||||||
@@ -186,7 +186,7 @@
|
|||||||
|
|
||||||
- (MPElementType)defaultType {
|
- (MPElementType)defaultType {
|
||||||
|
|
||||||
return IfElse((MPElementType)[self.defaultType_ unsignedIntegerValue], MPElementTypeGeneratedLong);
|
return (MPElementType)[self.defaultType_ unsignedIntegerValue]?: MPElementTypeGeneratedLong;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)setDefaultType:(MPElementType)aDefaultType {
|
- (void)setDefaultType:(MPElementType)aDefaultType {
|
||||||
|
|||||||
@@ -85,7 +85,4 @@ typedef NS_ENUM(NSUInteger, MPElementType) {
|
|||||||
static void MPCheckpoint(NSString *checkpoint, NSDictionary *attributes) {
|
static void MPCheckpoint(NSString *checkpoint, NSDictionary *attributes) {
|
||||||
|
|
||||||
inf(@"%@: %@", checkpoint, attributes);
|
inf(@"%@: %@", checkpoint, attributes);
|
||||||
#ifdef TESTFLIGHT_SDK_VERSION
|
|
||||||
[TestFlight passCheckpoint:checkpoint];
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,33 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
|
|
||||||
*
|
|
||||||
* See the enclosed file LICENSE for license information (LGPLv3). If you did
|
|
||||||
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
|
|
||||||
*
|
|
||||||
* @author Maarten Billemont <lhunath@lyndir.com>
|
|
||||||
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
//
|
|
||||||
// MPElementCollectionView.h
|
|
||||||
// MPElementCollectionView
|
|
||||||
//
|
|
||||||
// Created by lhunath on 2/11/2014.
|
|
||||||
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
#import <Foundation/Foundation.h>
|
|
||||||
@class MPElementModel;
|
|
||||||
|
|
||||||
@interface MPElementCollectionView : NSCollectionViewItem
|
|
||||||
|
|
||||||
@property (nonatomic) MPElementModel *representedObject;
|
|
||||||
@property (nonatomic) BOOL counterHidden;
|
|
||||||
@property (nonatomic) BOOL updateContentHidden;
|
|
||||||
|
|
||||||
- (IBAction)toggleType:(id)sender;
|
|
||||||
- (IBAction)updateLoginName:(id)sender;
|
|
||||||
- (IBAction)updateContent:(id)sender;
|
|
||||||
- (IBAction)delete:(id)sender;
|
|
||||||
|
|
||||||
@end
|
|
||||||
@@ -1,225 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
|
|
||||||
*
|
|
||||||
* See the enclosed file LICENSE for license information (LGPLv3). If you did
|
|
||||||
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
|
|
||||||
*
|
|
||||||
* @author Maarten Billemont <lhunath@lyndir.com>
|
|
||||||
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
//
|
|
||||||
// MPElementCollectionView.h
|
|
||||||
// MPElementCollectionView
|
|
||||||
//
|
|
||||||
// Created by lhunath on 2/11/2014.
|
|
||||||
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
#import <Foundation/Foundation.h>
|
|
||||||
#import "MPElementCollectionView.h"
|
|
||||||
#import "MPElementModel.h"
|
|
||||||
#import "MPMacAppDelegate.h"
|
|
||||||
#import "MPAppDelegate_Store.h"
|
|
||||||
|
|
||||||
#define MPAlertChangeType @"MPAlertChangeType"
|
|
||||||
#define MPAlertChangeLogin @"MPAlertChangeLogin"
|
|
||||||
#define MPAlertChangeContent @"MPAlertChangeContent"
|
|
||||||
#define MPAlertDeleteSite @"MPAlertDeleteSite"
|
|
||||||
|
|
||||||
@implementation MPElementCollectionView {
|
|
||||||
}
|
|
||||||
|
|
||||||
@dynamic representedObject;
|
|
||||||
|
|
||||||
- (id)initWithCoder:(NSCoder *)coder {
|
|
||||||
|
|
||||||
if (!(self = [super initWithCoder:coder]))
|
|
||||||
return nil;
|
|
||||||
|
|
||||||
[self addObserver:self forKeyPath:@"representedObject" options:0 context:nil];
|
|
||||||
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
|
|
||||||
|
|
||||||
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
|
|
||||||
self.counterHidden = !(MPElementTypeClassGenerated & self.representedObject.type);
|
|
||||||
self.updateContentHidden = !(MPElementTypeClassStored & self.representedObject.type);
|
|
||||||
}];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)dealloc {
|
|
||||||
|
|
||||||
[self removeObserver:self forKeyPath:@"representedObject"];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (IBAction)toggleType:(id)sender {
|
|
||||||
|
|
||||||
id<MPAlgorithm> algorithm = self.representedObject.algorithm;
|
|
||||||
NSString *previousType = [algorithm nameOfType:[algorithm previousType:self.representedObject.type]];
|
|
||||||
NSString *nextType = [algorithm nameOfType:[algorithm nextType:self.representedObject.type]];
|
|
||||||
|
|
||||||
[[NSAlert alertWithMessageText:@"Change Password Type"
|
|
||||||
defaultButton:nextType alternateButton:@"Cancel" otherButton:previousType
|
|
||||||
informativeTextWithFormat:@"Changing the password type for this site will cause the password to change.\n"
|
|
||||||
@"You will need to update your account with the new password.\n\n"
|
|
||||||
@"Changing back to the old type will restore your current password."]
|
|
||||||
beginSheetModalForWindow:self.view.window modalDelegate:self
|
|
||||||
didEndSelector:@selector(alertDidEnd:returnCode:contextInfo:) contextInfo:MPAlertChangeType];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (IBAction)updateLoginName:(id)sender {
|
|
||||||
|
|
||||||
NSAlert *alert = [NSAlert alertWithMessageText:@"Update Login Name"
|
|
||||||
defaultButton:@"Update" alternateButton:@"Cancel" otherButton:nil
|
|
||||||
informativeTextWithFormat:@"Enter the login name for %@:", self.representedObject.site];
|
|
||||||
NSTextField *passwordField = [[NSTextField alloc] initWithFrame:NSMakeRect( 0, 0, 200, 22 )];
|
|
||||||
[alert setAccessoryView:passwordField];
|
|
||||||
[alert layout];
|
|
||||||
[passwordField becomeFirstResponder];
|
|
||||||
[alert beginSheetModalForWindow:self.view.window modalDelegate:self
|
|
||||||
didEndSelector:@selector(alertDidEnd:returnCode:contextInfo:) contextInfo:MPAlertChangeLogin];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (IBAction)updateContent:(id)sender {
|
|
||||||
|
|
||||||
NSAlert *alert = [NSAlert alertWithMessageText:@"Update Password"
|
|
||||||
defaultButton:@"Update" alternateButton:@"Cancel" otherButton:nil
|
|
||||||
informativeTextWithFormat:@"Enter the new password for %@:", self.representedObject.site];
|
|
||||||
NSSecureTextField *passwordField = [[NSSecureTextField alloc] initWithFrame:NSMakeRect( 0, 0, 200, 22 )];
|
|
||||||
[alert setAccessoryView:passwordField];
|
|
||||||
[alert layout];
|
|
||||||
[passwordField becomeFirstResponder];
|
|
||||||
[alert beginSheetModalForWindow:self.view.window modalDelegate:self
|
|
||||||
didEndSelector:@selector(alertDidEnd:returnCode:contextInfo:) contextInfo:MPAlertChangeContent];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (IBAction)delete:(id)sender {
|
|
||||||
|
|
||||||
NSAlert *alert = [NSAlert alertWithMessageText:@"Delete Site"
|
|
||||||
defaultButton:@"Delete" alternateButton:@"Cancel" otherButton:nil
|
|
||||||
informativeTextWithFormat:@"Are you sure you want to delete the site: %@?", self.representedObject.site];
|
|
||||||
[alert beginSheetModalForWindow:self.view.window modalDelegate:self
|
|
||||||
didEndSelector:@selector(alertDidEnd:returnCode:contextInfo:) contextInfo:MPAlertDeleteSite];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)alertDidEnd:(NSAlert *)alert returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo {
|
|
||||||
|
|
||||||
if (contextInfo == MPAlertChangeType) {
|
|
||||||
switch (returnCode) {
|
|
||||||
case NSAlertDefaultReturn: {
|
|
||||||
// "Next type" button.
|
|
||||||
[MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
|
||||||
MPElementEntity *element = [self.representedObject entityInContext:context];
|
|
||||||
element = [[MPMacAppDelegate get] changeElement:element saveInContext:context
|
|
||||||
toType:[element.algorithm nextType:element.type]];
|
|
||||||
|
|
||||||
self.representedObject = [[MPElementModel alloc] initWithEntity:element];
|
|
||||||
}];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case NSAlertAlternateReturn: {
|
|
||||||
// "Cancel" button.
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case NSAlertOtherReturn: {
|
|
||||||
// "Previous type" button.
|
|
||||||
[MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
|
||||||
MPElementEntity *element = [self.representedObject entityInContext:context];
|
|
||||||
element = [[MPMacAppDelegate get] changeElement:element saveInContext:context
|
|
||||||
toType:[element.algorithm previousType:element.type]];
|
|
||||||
|
|
||||||
self.representedObject = [[MPElementModel alloc] initWithEntity:element];
|
|
||||||
}];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (contextInfo == MPAlertChangeLogin) {
|
|
||||||
switch (returnCode) {
|
|
||||||
case NSAlertDefaultReturn: {
|
|
||||||
// "Update" button.
|
|
||||||
[MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
|
||||||
MPElementEntity *element = [self.representedObject entityInContext:context];
|
|
||||||
element.loginName = [(NSTextField *)alert.accessoryView stringValue];
|
|
||||||
[context saveToStore];
|
|
||||||
|
|
||||||
self.representedObject = [[MPElementModel alloc] initWithEntity:element];
|
|
||||||
}];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case NSAlertAlternateReturn: {
|
|
||||||
// "Cancel" button.
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (contextInfo == MPAlertChangeContent) {
|
|
||||||
switch (returnCode) {
|
|
||||||
case NSAlertDefaultReturn: {
|
|
||||||
// "Update" button.
|
|
||||||
[MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
|
||||||
MPElementEntity *element = [self.representedObject entityInContext:context];
|
|
||||||
[element.algorithm saveContent:[(NSSecureTextField *)alert.accessoryView stringValue]
|
|
||||||
toElement:element usingKey:[MPMacAppDelegate get].key];
|
|
||||||
[context saveToStore];
|
|
||||||
|
|
||||||
self.representedObject = [[MPElementModel alloc] initWithEntity:element];
|
|
||||||
}];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case NSAlertAlternateReturn: {
|
|
||||||
// "Cancel" button.
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (contextInfo == MPAlertDeleteSite) {
|
|
||||||
switch (returnCode) {
|
|
||||||
case NSAlertDefaultReturn: {
|
|
||||||
// "Delete" button.
|
|
||||||
[MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
|
||||||
MPElementEntity *element = [self.representedObject entityInContext:context];
|
|
||||||
[context deleteObject:element];
|
|
||||||
[context saveToStore];
|
|
||||||
|
|
||||||
[((MPPasswordWindowController *)self.collectionView.window.windowController) updateElements];
|
|
||||||
}];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case NSAlertAlternateReturn: {
|
|
||||||
// "Cancel" button.
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@end
|
|
||||||
@@ -20,19 +20,22 @@
|
|||||||
@class MPElementEntity;
|
@class MPElementEntity;
|
||||||
|
|
||||||
@interface MPElementModel : NSObject
|
@interface MPElementModel : NSObject
|
||||||
@property (nonatomic) NSString *site;
|
|
||||||
|
@property (nonatomic) NSString *siteName;
|
||||||
@property (nonatomic) MPElementType type;
|
@property (nonatomic) MPElementType type;
|
||||||
@property (nonatomic) NSString *typeName;
|
@property (nonatomic) NSString *typeName;
|
||||||
@property (nonatomic) NSString *content;
|
@property (nonatomic) NSString *content;
|
||||||
|
@property (nonatomic) NSString *contentDisplay;
|
||||||
@property (nonatomic) NSString *loginName;
|
@property (nonatomic) NSString *loginName;
|
||||||
@property (nonatomic) NSNumber *uses;
|
@property (nonatomic) NSNumber *uses;
|
||||||
@property (nonatomic) NSUInteger counter;
|
@property (nonatomic) NSUInteger counter;
|
||||||
@property (nonatomic) NSDate *lastUsed;
|
@property (nonatomic) NSDate *lastUsed;
|
||||||
@property (nonatomic) id<MPAlgorithm> algorithm;
|
@property (nonatomic) id<MPAlgorithm> algorithm;
|
||||||
@property (nonatomic) NSArray *typeNames;
|
@property (nonatomic) BOOL generated;
|
||||||
@property (nonatomic) NSUInteger typeIndex;
|
@property (nonatomic) BOOL stored;
|
||||||
|
|
||||||
- (id)initWithEntity:(MPElementEntity *)entity;
|
- (id)initWithEntity:(MPElementEntity *)entity;
|
||||||
- (MPElementEntity *)entityInContext:(NSManagedObjectContext *)moc;
|
- (MPElementEntity *)entityInContext:(NSManagedObjectContext *)moc;
|
||||||
|
|
||||||
|
- (void)updateContent;
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -25,7 +25,6 @@
|
|||||||
|
|
||||||
@implementation MPElementModel {
|
@implementation MPElementModel {
|
||||||
NSManagedObjectID *_entityOID;
|
NSManagedObjectID *_entityOID;
|
||||||
NSMutableDictionary *_typesByName;
|
|
||||||
BOOL _initialized;
|
BOOL _initialized;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,7 +46,7 @@
|
|||||||
_entityOID = entity.objectID;
|
_entityOID = entity.objectID;
|
||||||
|
|
||||||
self.algorithm = entity.algorithm;
|
self.algorithm = entity.algorithm;
|
||||||
self.site = entity.name;
|
self.siteName = entity.name;
|
||||||
self.lastUsed = entity.lastUsed;
|
self.lastUsed = entity.lastUsed;
|
||||||
self.loginName = entity.loginName;
|
self.loginName = entity.loginName;
|
||||||
self.type = entity.type;
|
self.type = entity.type;
|
||||||
@@ -56,18 +55,7 @@
|
|||||||
self.counter = [entity isKindOfClass:[MPElementGeneratedEntity class]]? [(MPElementGeneratedEntity *)entity counter]: 0;
|
self.counter = [entity isKindOfClass:[MPElementGeneratedEntity class]]? [(MPElementGeneratedEntity *)entity counter]: 0;
|
||||||
|
|
||||||
// Find all password types and the index of the current type amongst them.
|
// Find all password types and the index of the current type amongst them.
|
||||||
_typesByName = [NSMutableDictionary dictionary];
|
[self updateContent:entity];
|
||||||
MPElementType type = self.type;
|
|
||||||
do {
|
|
||||||
[_typesByName setObject:@(type) forKey:[self.algorithm shortNameOfType:type]];
|
|
||||||
} while (self.type != (type = [self.algorithm nextType:type]));
|
|
||||||
self.typeNames = [_typesByName keysSortedByValueUsingSelector:@selector( compare: )];
|
|
||||||
self.typeIndex = [[[_typesByName allValues] sortedArrayUsingSelector:@selector( compare: )] indexOfObject:@(self.type)];
|
|
||||||
|
|
||||||
[entity.algorithm resolveContentForElement:entity usingKey:[MPAppDelegate_Shared get].key
|
|
||||||
result:^(NSString *result) {
|
|
||||||
PearlMainQueue( ^{ self.content = result; } );
|
|
||||||
}];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (MPElementEntity *)entityInContext:(NSManagedObjectContext *)moc {
|
- (MPElementEntity *)entityInContext:(NSManagedObjectContext *)moc {
|
||||||
@@ -99,27 +87,45 @@
|
|||||||
((MPElementGeneratedEntity *)entity).counter = counter;
|
((MPElementGeneratedEntity *)entity).counter = counter;
|
||||||
[context saveToStore];
|
[context saveToStore];
|
||||||
|
|
||||||
[entity.algorithm resolveContentForElement:entity usingKey:[MPAppDelegate_Shared get].key
|
[self updateContent:entity];
|
||||||
result:^(NSString *result) {
|
|
||||||
PearlMainQueue( ^{ self.content = result; } );
|
|
||||||
}];
|
|
||||||
}
|
}
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)setTypeIndex:(NSUInteger)typeIndex {
|
- (BOOL)generated {
|
||||||
|
|
||||||
if (typeIndex == _typeIndex)
|
return self.type & MPElementTypeClassGenerated;
|
||||||
return;
|
}
|
||||||
_typeIndex = typeIndex;
|
|
||||||
|
|
||||||
if (!_initialized)
|
- (BOOL)stored {
|
||||||
// This wasn't a change to the entity.
|
|
||||||
return;
|
return self.type & MPElementTypeClassStored;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)updateContent {
|
||||||
|
|
||||||
[MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
[MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
||||||
[self setEntity:[[MPAppDelegate_Shared get] changeElement:[self entityInContext:context] saveInContext:context
|
[self updateContent:[MPElementEntity existingObjectWithID:_entityOID inContext:context]];
|
||||||
toType:[_typesByName[self.typeNames[typeIndex]] unsignedIntegerValue]]];
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)updateContent:(MPElementEntity *)entity {
|
||||||
|
|
||||||
|
static NSRegularExpression *re_anyChar;
|
||||||
|
static dispatch_once_t once = 0;
|
||||||
|
dispatch_once( &once, ^{
|
||||||
|
re_anyChar = [NSRegularExpression regularExpressionWithPattern:@"." options:0 error:nil];
|
||||||
|
} );
|
||||||
|
|
||||||
|
[entity resolveContentUsingKey:[MPAppDelegate_Shared get].key result:^(NSString *result) {
|
||||||
|
NSString *displayResult = result;
|
||||||
|
if ([[MPConfig get].hidePasswords boolValue] && !([NSEvent modifierFlags] & NSAlternateKeyMask))
|
||||||
|
displayResult = [displayResult stringByReplacingMatchesOfExpression:re_anyChar withTemplate:@"●"];
|
||||||
|
|
||||||
|
PearlMainQueue( ^{
|
||||||
|
self.content = result;
|
||||||
|
self.contentDisplay = displayResult;
|
||||||
|
} );
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
27
MasterPassword/ObjC/Mac/MPElementsTableView.h
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
/**
|
||||||
|
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
|
||||||
|
*
|
||||||
|
* See the enclosed file LICENSE for license information (LGPLv3). If you did
|
||||||
|
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
|
||||||
|
*
|
||||||
|
* @author Maarten Billemont <lhunath@lyndir.com>
|
||||||
|
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
|
||||||
|
*/
|
||||||
|
|
||||||
|
//
|
||||||
|
// MPElementsTableView.h
|
||||||
|
// MPElementsTableView
|
||||||
|
//
|
||||||
|
// Created by lhunath on 2014-06-30.
|
||||||
|
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <AppKit/AppKit.h>
|
||||||
|
|
||||||
|
@class MPPasswordWindowController;
|
||||||
|
|
||||||
|
@interface MPElementsTableView : NSTableView
|
||||||
|
|
||||||
|
@property(nonatomic, weak) MPPasswordWindowController *controller;
|
||||||
|
|
||||||
|
@end
|
||||||
34
MasterPassword/ObjC/Mac/MPElementsTableView.m
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
/**
|
||||||
|
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
|
||||||
|
*
|
||||||
|
* See the enclosed file LICENSE for license information (LGPLv3). If you did
|
||||||
|
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
|
||||||
|
*
|
||||||
|
* @author Maarten Billemont <lhunath@lyndir.com>
|
||||||
|
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
|
||||||
|
*/
|
||||||
|
|
||||||
|
//
|
||||||
|
// MPElementsTableView.h
|
||||||
|
// MPElementsTableView
|
||||||
|
//
|
||||||
|
// Created by lhunath on 2014-06-30.
|
||||||
|
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "MPElementsTableView.h"
|
||||||
|
#import "MPPasswordWindowController.h"
|
||||||
|
|
||||||
|
@implementation MPElementsTableView
|
||||||
|
|
||||||
|
- (void)doCommandBySelector:(SEL)aSelector {
|
||||||
|
|
||||||
|
[self.controller handleCommand:aSelector];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)keyDown:(NSEvent *)theEvent {
|
||||||
|
|
||||||
|
[self interpretKeyEvents:@[ theEvent ]];
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
@@ -1,40 +1,38 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="4514" systemVersion="13B3116" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
|
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="5056" systemVersion="13E28" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<deployment defaultVersion="1080" identifier="macosx"/>
|
<deployment defaultVersion="1080" identifier="macosx"/>
|
||||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="4514"/>
|
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="5056"/>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
<objects>
|
<objects>
|
||||||
<customObject id="-2" userLabel="File's Owner" customClass="MPMacAppDelegate">
|
<customObject id="-2" userLabel="File's Owner" customClass="MPInitialWindowController">
|
||||||
<connections>
|
<connections>
|
||||||
<outlet property="enableCloudButton" destination="409" id="572"/>
|
|
||||||
<outlet property="openAtLoginButton" destination="508" id="571"/>
|
<outlet property="openAtLoginButton" destination="508" id="571"/>
|
||||||
|
<outlet property="window" destination="1" id="ngh-EA-JYN"/>
|
||||||
</connections>
|
</connections>
|
||||||
</customObject>
|
</customObject>
|
||||||
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
|
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
|
||||||
<customObject id="-3" userLabel="Application"/>
|
<customObject id="-3" userLabel="Application"/>
|
||||||
<window allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" oneShot="NO" wantsToBeColor="NO" animationBehavior="default" id="1">
|
<window title="Welcome to Master Password" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" oneShot="NO" animationBehavior="default" id="1">
|
||||||
<windowStyleMask key="styleMask" titled="YES" closable="YES"/>
|
<windowStyleMask key="styleMask" titled="YES" closable="YES"/>
|
||||||
<rect key="contentRect" x="196" y="240" width="833" height="540"/>
|
<windowCollectionBehavior key="collectionBehavior" moveToActiveSpace="YES"/>
|
||||||
|
<rect key="contentRect" x="196" y="240" width="1000" height="640"/>
|
||||||
<rect key="screenRect" x="0.0" y="0.0" width="1440" height="900"/>
|
<rect key="screenRect" x="0.0" y="0.0" width="1440" height="900"/>
|
||||||
<view key="contentView" id="2" userLabel="Container">
|
<view key="contentView" id="2" userLabel="Container">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="833" height="540"/>
|
<rect key="frame" x="0.0" y="0.0" width="1000" height="640"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<customView wantsLayer="YES" translatesAutoresizingMaskIntoConstraints="NO" id="369">
|
<customView wantsLayer="YES" translatesAutoresizingMaskIntoConstraints="NO" id="369">
|
||||||
<rect key="frame" x="0.0" y="40" width="833" height="500"/>
|
<rect key="frame" x="0.0" y="40" width="1000" height="600"/>
|
||||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<imageView translatesAutoresizingMaskIntoConstraints="NO" id="3" userLabel="Image">
|
<imageView translatesAutoresizingMaskIntoConstraints="NO" id="3" userLabel="Image">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="833" height="500"/>
|
<rect key="frame" x="0.0" y="0.0" width="1000" height="600"/>
|
||||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||||
<constraints>
|
|
||||||
<constraint firstAttribute="height" constant="500" id="396"/>
|
|
||||||
</constraints>
|
|
||||||
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyDown" image="shot-laptop-leaning-iphone" id="4"/>
|
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyDown" image="shot-laptop-leaning-iphone" id="4"/>
|
||||||
</imageView>
|
</imageView>
|
||||||
<textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="268">
|
<textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="268">
|
||||||
<rect key="frame" x="18" y="402" width="797" height="78"/>
|
<rect key="frame" x="18" y="502" width="964" height="78"/>
|
||||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||||
<shadow key="shadow" blurRadius="1">
|
<shadow key="shadow" blurRadius="1">
|
||||||
<size key="offset" width="0.0" height="1"/>
|
<size key="offset" width="0.0" height="1"/>
|
||||||
@@ -49,7 +47,7 @@ from anywhere.</string>
|
|||||||
</textFieldCell>
|
</textFieldCell>
|
||||||
</textField>
|
</textField>
|
||||||
<textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="283">
|
<textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="283">
|
||||||
<rect key="frame" x="245" y="72" width="344" height="29"/>
|
<rect key="frame" x="328" y="72" width="344" height="29"/>
|
||||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||||
<shadow key="shadow" blurRadius="1">
|
<shadow key="shadow" blurRadius="1">
|
||||||
<size key="offset" width="0.0" height="1"/>
|
<size key="offset" width="0.0" height="1"/>
|
||||||
@@ -62,7 +60,7 @@ from anywhere.</string>
|
|||||||
</textFieldCell>
|
</textFieldCell>
|
||||||
</textField>
|
</textField>
|
||||||
<button translatesAutoresizingMaskIntoConstraints="NO" id="173" userLabel="Store">
|
<button translatesAutoresizingMaskIntoConstraints="NO" id="173" userLabel="Store">
|
||||||
<rect key="frame" x="342" y="20" width="150" height="44"/>
|
<rect key="frame" x="425" y="20" width="150" height="44"/>
|
||||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||||
<shadow key="shadow" blurRadius="1">
|
<shadow key="shadow" blurRadius="1">
|
||||||
<size key="offset" width="0.0" height="1"/>
|
<size key="offset" width="0.0" height="1"/>
|
||||||
@@ -91,22 +89,8 @@ from anywhere.</string>
|
|||||||
<constraint firstItem="283" firstAttribute="centerX" secondItem="268" secondAttribute="centerX" id="544"/>
|
<constraint firstItem="283" firstAttribute="centerX" secondItem="268" secondAttribute="centerX" id="544"/>
|
||||||
</constraints>
|
</constraints>
|
||||||
</customView>
|
</customView>
|
||||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="409">
|
|
||||||
<rect key="frame" x="691" y="8" width="122" height="25"/>
|
|
||||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
|
||||||
<constraints>
|
|
||||||
<constraint firstAttribute="width" constant="122" id="444"/>
|
|
||||||
</constraints>
|
|
||||||
<buttonCell key="cell" type="roundTextured" title="Use iCloud" bezelStyle="texturedRounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="410">
|
|
||||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES" changeBackground="YES" changeGray="YES"/>
|
|
||||||
<font key="font" metaFont="system"/>
|
|
||||||
</buttonCell>
|
|
||||||
<connections>
|
|
||||||
<action selector="togglePreference:" target="-2" id="573"/>
|
|
||||||
</connections>
|
|
||||||
</button>
|
|
||||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="508">
|
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="508">
|
||||||
<rect key="frame" x="561" y="8" width="122" height="25"/>
|
<rect key="frame" x="858" y="8" width="122" height="25"/>
|
||||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||||
<constraints>
|
<constraints>
|
||||||
<constraint firstAttribute="width" constant="122" id="509"/>
|
<constraint firstAttribute="width" constant="122" id="509"/>
|
||||||
@@ -120,12 +104,9 @@ from anywhere.</string>
|
|||||||
</connections>
|
</connections>
|
||||||
</button>
|
</button>
|
||||||
<textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="562">
|
<textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="562">
|
||||||
<rect key="frame" x="21" y="13" width="534" height="17"/>
|
<rect key="frame" x="21" y="13" width="831" height="17"/>
|
||||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||||
<constraints>
|
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="To begin, hold: command, control and P (⌘⌃P) or access the ●●●| menu icon." id="563">
|
||||||
<constraint firstAttribute="width" constant="530" id="566"/>
|
|
||||||
</constraints>
|
|
||||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="To begin, press: command, control and P or access the ●●●| menu icon." id="563">
|
|
||||||
<font key="font" metaFont="system"/>
|
<font key="font" metaFont="system"/>
|
||||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||||
@@ -135,16 +116,13 @@ from anywhere.</string>
|
|||||||
<constraints>
|
<constraints>
|
||||||
<constraint firstItem="369" firstAttribute="top" secondItem="2" secondAttribute="top" id="374"/>
|
<constraint firstItem="369" firstAttribute="top" secondItem="2" secondAttribute="top" id="374"/>
|
||||||
<constraint firstItem="369" firstAttribute="leading" secondItem="2" secondAttribute="leading" id="375"/>
|
<constraint firstItem="369" firstAttribute="leading" secondItem="2" secondAttribute="leading" id="375"/>
|
||||||
<constraint firstAttribute="trailing" secondItem="409" secondAttribute="trailing" constant="20" symbolic="YES" id="507"/>
|
|
||||||
<constraint firstItem="508" firstAttribute="baseline" secondItem="409" secondAttribute="baseline" id="513"/>
|
|
||||||
<constraint firstItem="508" firstAttribute="top" secondItem="369" secondAttribute="bottom" constant="8" symbolic="YES" id="514"/>
|
<constraint firstItem="508" firstAttribute="top" secondItem="369" secondAttribute="bottom" constant="8" symbolic="YES" id="514"/>
|
||||||
<constraint firstItem="409" firstAttribute="leading" secondItem="508" secondAttribute="trailing" constant="8" symbolic="YES" id="515"/>
|
|
||||||
<constraint firstAttribute="bottom" secondItem="508" secondAttribute="bottom" constant="10" id="545"/>
|
<constraint firstAttribute="bottom" secondItem="508" secondAttribute="bottom" constant="10" id="545"/>
|
||||||
<constraint firstAttribute="bottom" secondItem="409" secondAttribute="bottom" constant="10" id="546"/>
|
|
||||||
<constraint firstItem="562" firstAttribute="leading" secondItem="2" secondAttribute="leading" constant="23" id="565"/>
|
<constraint firstItem="562" firstAttribute="leading" secondItem="2" secondAttribute="leading" constant="23" id="565"/>
|
||||||
<constraint firstItem="369" firstAttribute="trailing" secondItem="2" secondAttribute="trailing" id="567"/>
|
<constraint firstItem="369" firstAttribute="trailing" secondItem="2" secondAttribute="trailing" id="567"/>
|
||||||
<constraint firstItem="508" firstAttribute="leading" secondItem="562" secondAttribute="trailing" constant="8" symbolic="YES" id="568"/>
|
<constraint firstItem="508" firstAttribute="leading" secondItem="562" secondAttribute="trailing" constant="8" symbolic="YES" id="568"/>
|
||||||
<constraint firstItem="562" firstAttribute="baseline" secondItem="508" secondAttribute="baseline" id="569"/>
|
<constraint firstItem="562" firstAttribute="baseline" secondItem="508" secondAttribute="baseline" id="569"/>
|
||||||
|
<constraint firstAttribute="trailing" secondItem="508" secondAttribute="trailing" constant="20" symbolic="YES" id="nH9-cd-trF"/>
|
||||||
</constraints>
|
</constraints>
|
||||||
</view>
|
</view>
|
||||||
</window>
|
</window>
|
||||||
|
|||||||