Enable support for internal actions from URLs.
This commit is contained in:
parent
10b205c541
commit
8bedcedfaf
@ -39,8 +39,9 @@
|
||||
askImportPassword:(NSString *( ^ )(NSString *userName))importPassword
|
||||
askUserPassword:(NSString *( ^ )(NSString *userName))userPassword
|
||||
result:(void ( ^ )(NSError *error))resultBlock;
|
||||
- (void)exportSitesRevealPasswords:(BOOL)revealPasswords
|
||||
askExportPassword:(NSString *( ^ )(NSString *userName))askImportPassword
|
||||
result:(void ( ^ )(NSString *exportedUser, NSError *error))resultBlock;
|
||||
- (NSString *)exportSitesFor:(MPUserEntity *)user
|
||||
revealPasswords:(BOOL)revealPasswords
|
||||
askExportPassword:(NSString *( ^ )(NSString *userName))askExportPassword
|
||||
error:(__autoreleasing NSError **)error;
|
||||
|
||||
@end
|
||||
|
@ -704,16 +704,17 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
|
||||
site.lastUsed = [NSDate dateWithTimeIntervalSince1970:importSite->lastUsed];
|
||||
}
|
||||
|
||||
- (void)exportSitesRevealPasswords:(BOOL)revealPasswords
|
||||
askExportPassword:(NSString *( ^ )(NSString *userName))askImportPassword
|
||||
result:(void ( ^ )(NSString *exportedUser, NSError *error))resultBlock {
|
||||
|
||||
[MPAppDelegate_Shared managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
||||
MPUserEntity *user = [self activeUserInContext:context];
|
||||
- (NSString *)exportSitesFor:(MPUserEntity *)user
|
||||
revealPasswords:(BOOL)revealPasswords
|
||||
askExportPassword:(NSString *( ^ )(NSString *userName))askExportPassword
|
||||
error:(__autoreleasing NSError **)error {
|
||||
|
||||
MPMarshalledUser *exportUser = NULL;
|
||||
MPMarshalledFile *exportFile = NULL;
|
||||
@try {
|
||||
inf( @"Exporting sites, %@, for user: %@", revealPasswords? @"revealing passwords": @"omitting passwords", user.userID );
|
||||
MPMarshalledUser *exportUser = mpw_marshal_user( user.name.UTF8String,
|
||||
mpw_masterKeyProvider_str( askImportPassword( user.name ).UTF8String ), user.algorithm.version );
|
||||
exportUser = mpw_marshal_user( user.name.UTF8String,
|
||||
mpw_masterKeyProvider_str( askExportPassword( user.name ).UTF8String ), user.algorithm.version );
|
||||
exportUser->redacted = !revealPasswords;
|
||||
exportUser->avatar = (unsigned int)user.avatar;
|
||||
exportUser->keyID = mpw_strdup( [user.keyID encodeHex].UTF8String );
|
||||
@ -737,22 +738,26 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
|
||||
mpw_marshal_question( exportSite, siteQuestion.keyword.UTF8String );
|
||||
}
|
||||
|
||||
MPMarshalledFile *exportFile = NULL;
|
||||
const char *export = mpw_marshal_write( MPMarshalFormatDefault, &exportFile, exportUser );
|
||||
NSString *exportedUser = nil;
|
||||
if (export && exportFile && exportFile->error.type == MPMarshalSuccess)
|
||||
exportedUser = [NSString stringWithCString:export encoding:NSUTF8StringEncoding];
|
||||
mpw_free_string( &export );
|
||||
|
||||
resultBlock( exportedUser, exportFile && exportFile->error.type == MPMarshalSuccess? nil:
|
||||
[NSError errorWithDomain:MPErrorDomain code:MPErrorMarshalCode userInfo:@{
|
||||
@"type" : @(exportFile? exportFile->error.type: MPMarshalErrorInternal),
|
||||
NSLocalizedDescriptionKey: @(exportFile? exportFile->error.message: nil),
|
||||
}] );
|
||||
if (error)
|
||||
*error = exportFile && exportFile->error.type == MPMarshalSuccess? nil:
|
||||
[NSError errorWithDomain:MPErrorDomain code:MPErrorMarshalCode userInfo:@{
|
||||
@"type" : @(exportFile? exportFile->error.type: MPMarshalErrorInternal),
|
||||
NSLocalizedDescriptionKey: @(exportFile? exportFile->error.message: nil),
|
||||
}];
|
||||
|
||||
return exportedUser;
|
||||
}
|
||||
@finally {
|
||||
mpw_marshal_file_free( &exportFile );
|
||||
mpw_marshal_user_free( &exportUser );
|
||||
mpw_masterKeyProvider_free();
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
@ -18,6 +18,7 @@
|
||||
|
||||
#import "MPNavigationController.h"
|
||||
#import "MPWebViewController.h"
|
||||
#import "MPiOSAppDelegate.h"
|
||||
|
||||
@implementation MPNavigationController
|
||||
|
||||
@ -29,6 +30,16 @@
|
||||
[self performSegueWithIdentifier:@"setup" sender:self];
|
||||
}
|
||||
|
||||
- (void)performSegueWithIdentifier:(NSString *)identifier sender:(id)sender {
|
||||
|
||||
if ([identifier isEqualToString:@"web"] && [[(NSURL *)sender scheme] isEqualToString:@"masterpassword"]) {
|
||||
[[MPiOSAppDelegate get] openURL:sender];
|
||||
return;
|
||||
}
|
||||
|
||||
[super performSegueWithIdentifier:identifier sender:sender];
|
||||
}
|
||||
|
||||
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
|
||||
|
||||
if ([segue.identifier isEqualToString:@"web"])
|
||||
|
@ -33,9 +33,8 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
|
||||
MPPasswordsBadNameTip = 1 << 0,
|
||||
};
|
||||
|
||||
@interface MPSitesViewController()<NSFetchedResultsControllerDelegate, SKStoreProductViewControllerDelegate>
|
||||
@interface MPSitesViewController()<NSFetchedResultsControllerDelegate>
|
||||
|
||||
@property(nonatomic, strong) SKStoreProductViewController *voltoViewController;
|
||||
@property(nonatomic, strong) NSFetchedResultsController *fetchedResultsController;
|
||||
@property(nonatomic, strong) NSArray *fuzzyGroups;
|
||||
@property(nonatomic, strong) NSCharacterSet *siteNameAcceptableCharactersSet;
|
||||
@ -435,13 +434,6 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
|
||||
} completion:completion];
|
||||
}
|
||||
|
||||
#pragma mark - SKStoreProductViewControllerDelegate
|
||||
|
||||
- (void)productViewControllerDidFinish:(SKStoreProductViewController *)viewController {
|
||||
|
||||
[viewController dismissViewControllerAnimated:YES completion:nil];
|
||||
}
|
||||
|
||||
#pragma mark - Actions
|
||||
|
||||
- (IBAction)dismissPopdown:(id)sender {
|
||||
@ -454,45 +446,7 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
|
||||
|
||||
- (IBAction)upgradeVolto:(UIButton *)sender {
|
||||
|
||||
if ([UIApp canOpenURL:[[NSURL alloc] initWithString:@"volto:"]]) {
|
||||
[[MPiOSAppDelegate get] exportSitesRevealPasswords:NO askExportPassword:^NSString *(NSString *userName) {
|
||||
return PearlAwait( ^(void (^setResult)(id)) {
|
||||
PearlMainQueue( ^{
|
||||
UIAlertController *alert = [UIAlertController alertControllerWithTitle:strf( @"Master Password For:\n%@", userName )
|
||||
message:@"Enter your master password to export the user."
|
||||
preferredStyle:UIAlertControllerStyleAlert];
|
||||
[alert addTextFieldWithConfigurationHandler:^(UITextField *textField) {
|
||||
textField.secureTextEntry = YES;
|
||||
}];
|
||||
[alert addAction:[UIAlertAction actionWithTitle:@"Export" style:UIAlertActionStyleDefault handler:
|
||||
^(UIAlertAction *action) { setResult( alert.textFields.firstObject.text ); }]];
|
||||
[alert addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:
|
||||
^(UIAlertAction *action) { setResult( nil ); }]];
|
||||
[self.navigationController presentViewController:alert animated:YES completion:nil];
|
||||
} );
|
||||
} );
|
||||
} result:^(NSString *exportedUser, NSError *error) {
|
||||
if (!exportedUser || error) {
|
||||
MPError( error, @"Failed to export user." );
|
||||
PearlMainQueue( ^{
|
||||
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Export Error"
|
||||
message:[error localizedDescription]
|
||||
preferredStyle:UIAlertControllerStyleAlert];
|
||||
[alert addAction:[UIAlertAction actionWithTitle:@"Okay" style:UIAlertActionStyleCancel handler:nil]];
|
||||
[self.navigationController presentViewController:alert animated:YES completion:nil];
|
||||
} );
|
||||
return;
|
||||
}
|
||||
|
||||
NSURLComponents *components = [NSURLComponents new];
|
||||
components.scheme = @"volto";
|
||||
components.path = @"import";
|
||||
components.queryItems = @[ [[NSURLQueryItem alloc] initWithName:@"data" value:exportedUser] ];
|
||||
[UIApp openURL:components.URL];
|
||||
}];
|
||||
}
|
||||
else if (self.voltoViewController)
|
||||
[self presentViewController:self.voltoViewController animated:YES completion:nil];
|
||||
[[MPiOSAppDelegate get] migrateFor:[MPiOSAppDelegate get].activeUserForMainThread];
|
||||
}
|
||||
|
||||
#pragma mark - Private
|
||||
@ -505,23 +459,8 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
|
||||
self.voltoMigrateAlert.visible = YES;
|
||||
}
|
||||
else {
|
||||
self.voltoInstallAlert.visible = NO;
|
||||
self.voltoInstallAlert.visible = [MPiOSAppDelegate get].voltoViewController != nil;
|
||||
self.voltoMigrateAlert.visible = NO;
|
||||
self.voltoViewController = [SKStoreProductViewController new];
|
||||
self.voltoViewController.delegate = self;
|
||||
[self.voltoViewController loadProductWithParameters:@{
|
||||
SKStoreProductParameterCampaignToken : @"app-masterpassword.ios", /* Campaign: From MasterPassword iOS */
|
||||
SKStoreProductParameterProviderToken : @153897, /* Provider: Maarten Billemont */
|
||||
SKStoreProductParameterITunesItemIdentifier: @510296984, /* Application: MasterPassword iOS */
|
||||
//SKStoreProductParameterITunesItemIdentifier: @1500430196, /* Application: Volto iOS */
|
||||
} completionBlock:^(BOOL result, NSError *error) {
|
||||
if (error)
|
||||
err( @"Failed loading Volto product information: %@", error );
|
||||
|
||||
[UIView animateWithDuration:0.3f animations:^{
|
||||
self.voltoInstallAlert.visible = result;
|
||||
}];
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -17,6 +17,7 @@
|
||||
//==============================================================================
|
||||
|
||||
#import "MPWebViewController.h"
|
||||
#import "MPiOSAppDelegate.h"
|
||||
|
||||
@implementation MPWebViewController
|
||||
|
||||
@ -57,6 +58,18 @@
|
||||
|
||||
#pragma mark - WKNavigationDelegate
|
||||
|
||||
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction
|
||||
decisionHandler:(void ( ^ )(WKNavigationActionPolicy))decisionHandler {
|
||||
|
||||
if ([navigationAction.request.mainDocumentURL.scheme isEqualToString:@"masterpassword"]) {
|
||||
[[MPiOSAppDelegate get] openURL:navigationAction.request.mainDocumentURL];
|
||||
decisionHandler( WKNavigationActionPolicyCancel );
|
||||
return;
|
||||
}
|
||||
|
||||
decisionHandler( WKNavigationActionPolicyAllow );
|
||||
}
|
||||
|
||||
- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(null_unspecified WKNavigation *)navigation {
|
||||
|
||||
self.webNavigationItem.title = webView.URL.host;
|
||||
@ -77,7 +90,7 @@
|
||||
}
|
||||
|
||||
[self.webNavigationItem setLeftBarButtonItem:[[UIBarButtonItem alloc]
|
||||
initWithBarButtonSystemItem:UIBarButtonSystemItemAction target:self action:@selector(openURL:)]];
|
||||
initWithBarButtonSystemItem:UIBarButtonSystemItemAction target:self action:@selector( action: )]];
|
||||
[webView evaluateJavaScript:@"document.title" completionHandler:^(id o, NSError *error) {
|
||||
self.webNavigationItem.prompt = [o description];
|
||||
}];
|
||||
@ -85,7 +98,7 @@
|
||||
|
||||
#pragma mark - Actions
|
||||
|
||||
- (IBAction)openURL:(id)sender {
|
||||
- (IBAction)action:(id)sender {
|
||||
|
||||
UIAlertController *controller = [UIAlertController new];
|
||||
controller.title = self.webView.URL.host;
|
||||
|
@ -17,15 +17,22 @@
|
||||
//==============================================================================
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <StoreKit/StoreKit.h>
|
||||
|
||||
#import "MPAppDelegate_Shared.h"
|
||||
|
||||
@interface MPiOSAppDelegate : MPAppDelegate_Shared
|
||||
@interface MPiOSAppDelegate : MPAppDelegate_Shared <SKStoreProductViewControllerDelegate>
|
||||
|
||||
@property(nonatomic, strong) SKStoreProductViewController *voltoViewController;
|
||||
|
||||
- (void)openURL:(NSURL *)url;
|
||||
|
||||
- (void)showFeedbackWithLogs:(BOOL)logs forVC:(UIViewController *)viewController;
|
||||
- (void)openFeedbackWithLogs:(BOOL)logs forVC:(UIViewController *)viewController;
|
||||
|
||||
- (void)showExportForVC:(UIViewController *)viewController;
|
||||
- (void)migrateFor:(MPUserEntity *)user;
|
||||
|
||||
- (void)changeMasterPasswordFor:(MPUserEntity *)user saveInContext:(NSManagedObjectContext *)moc didResetBlock:(void ( ^ )(void))didReset;
|
||||
|
||||
@end
|
||||
|
@ -35,7 +35,7 @@
|
||||
@implementation CountlyPushNotifications(MPNotifications)
|
||||
|
||||
- (void)openURL:(NSString *)URLString {
|
||||
[UIApp.keyWindow.rootViewController performSegueWithIdentifier:@"web" sender:[NSURL URLWithString:URLString]];
|
||||
[[MPiOSAppDelegate get].navigationController performSegueWithIdentifier:@"web" sender:[NSURL URLWithString:URLString]];
|
||||
}
|
||||
|
||||
@end
|
||||
@ -186,7 +186,26 @@
|
||||
}
|
||||
} );
|
||||
|
||||
SKStoreProductViewController *migrateVC = [SKStoreProductViewController new];
|
||||
[migrateVC loadProductWithParameters:@{
|
||||
SKStoreProductParameterCampaignToken : @"app-masterpassword.ios", /* Campaign: From MasterPassword iOS */
|
||||
SKStoreProductParameterProviderToken : @153897, /* Provider: Maarten Billemont */
|
||||
SKStoreProductParameterITunesItemIdentifier: @510296984, /* Application: MasterPassword iOS */
|
||||
//SKStoreProductParameterITunesItemIdentifier: @1500430196, /* Application: Volto iOS */
|
||||
} completionBlock:^(BOOL result, NSError *error) {
|
||||
if (error)
|
||||
err( @"Failed loading Volto product information: %@", error );
|
||||
|
||||
if (result) {
|
||||
self.voltoViewController = migrateVC;
|
||||
self.voltoViewController.delegate = self;
|
||||
} else {
|
||||
self.voltoViewController = nil;
|
||||
}
|
||||
}];
|
||||
|
||||
PearlMainQueueOperation( ^{
|
||||
[self.navigationController performSegueWithIdentifier:@"web" sender:[NSURL URLWithString:@"masterpassword://foo?bar=quux"]];
|
||||
if ([[MPiOSConfig get].showSetup boolValue])
|
||||
[self.navigationController performSegueWithIdentifier:@"setup" sender:self];
|
||||
|
||||
@ -207,6 +226,12 @@
|
||||
if (!url)
|
||||
return NO;
|
||||
|
||||
// masterpassword: URLs.
|
||||
if ([url.scheme isEqualToString:@"masterpassword"]) {
|
||||
[self openURL:url];
|
||||
return YES;
|
||||
}
|
||||
|
||||
// Arbitrary URL to mpsites data.
|
||||
[[[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:
|
||||
^(NSData *importedSitesData, NSURLResponse *response, NSError *error) {
|
||||
@ -444,6 +469,42 @@
|
||||
|
||||
#pragma mark - Behavior
|
||||
|
||||
- (void)openURL:(NSURL *)url {
|
||||
if ([url.scheme isEqualToString:@"masterpassword"]) {
|
||||
if ([url.host isEqualToString:@"open-url"]) {
|
||||
for (NSURLQueryItem *item in [NSURLComponents componentsWithString:[url absoluteString]].queryItems)
|
||||
if ([item.name isEqualToString:@"url"]) {
|
||||
[UIApp openURL:[NSURL URLWithString:item.value]];
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if ([url.host isEqualToString:@"show-url"]) {
|
||||
for (NSURLQueryItem *item in [NSURLComponents componentsWithString:[url absoluteString]].queryItems)
|
||||
if ([item.name isEqualToString:@"url"]) {
|
||||
[[MPiOSAppDelegate get].navigationController performSegueWithIdentifier:@"web" sender:[NSURL URLWithString:item.value]];
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if ([url.host isEqualToString:@"migrate"]) {
|
||||
for (NSURLQueryItem *item in [NSURLComponents componentsWithString:[url absoluteString]].queryItems)
|
||||
if ([item.name isEqualToString:@"fullName"]) {
|
||||
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
||||
NSFetchRequest
|
||||
*fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPUserEntity class] )];
|
||||
fetchRequest.predicate = [NSPredicate predicateWithFormat:@"name == %@", item.value];
|
||||
NSArray *users = [context executeFetchRequest:fetchRequest error:nil];
|
||||
[self migrateFor:users.firstObject];
|
||||
}];
|
||||
return;
|
||||
}
|
||||
|
||||
[self migrateFor:nil];
|
||||
return;
|
||||
}
|
||||
} else
|
||||
[UIApp openURL:url];
|
||||
}
|
||||
|
||||
- (void)showFeedbackWithLogs:(BOOL)logs forVC:(UIViewController *)viewController {
|
||||
|
||||
if (![PearlEMail canSendMail]) {
|
||||
@ -564,89 +625,162 @@
|
||||
return;
|
||||
}
|
||||
|
||||
[self exportSitesRevealPasswords:revealPasswords askExportPassword:^NSString *(NSString *userName) {
|
||||
return PearlAwait( ^(void (^setResult)(id)) {
|
||||
PearlMainQueue( ^{
|
||||
UIAlertController *alert = [UIAlertController alertControllerWithTitle:strf( @"Master Password For:\n%@", userName )
|
||||
message:@"Enter your master password to export the user."
|
||||
preferredStyle:UIAlertControllerStyleAlert];
|
||||
[alert addTextFieldWithConfigurationHandler:^(UITextField *textField) {
|
||||
textField.secureTextEntry = YES;
|
||||
}];
|
||||
[alert addAction:[UIAlertAction actionWithTitle:@"Export" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
|
||||
setResult( alert.textFields.firstObject.text );
|
||||
}]];
|
||||
[alert addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) {
|
||||
setResult( nil );
|
||||
}]];
|
||||
[self.navigationController presentViewController:alert animated:YES completion:nil];
|
||||
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
||||
NSError *error = nil;
|
||||
NSString *exportedUser = [self exportSitesFor:[self activeUserInContext:context] revealPasswords:revealPasswords askExportPassword:^NSString *(NSString *userName) {
|
||||
return PearlAwait( ^(void (^setResult)(id)) {
|
||||
PearlMainQueue( ^{
|
||||
UIAlertController *alert = [UIAlertController alertControllerWithTitle:strf( @"Master Password For:\n%@", userName )
|
||||
message:@"Enter your master password to export the user."
|
||||
preferredStyle:UIAlertControllerStyleAlert];
|
||||
[alert addTextFieldWithConfigurationHandler:^(UITextField *textField) {
|
||||
textField.secureTextEntry = YES;
|
||||
}];
|
||||
[alert addAction:[UIAlertAction actionWithTitle:@"Export" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
|
||||
setResult( alert.textFields.firstObject.text );
|
||||
}]];
|
||||
[alert addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) {
|
||||
setResult( nil );
|
||||
}]];
|
||||
[self.navigationController presentViewController:alert animated:YES completion:nil];
|
||||
} );
|
||||
} );
|
||||
} );
|
||||
} result:^(NSString *exportedUser, NSError *error) {
|
||||
if (!exportedUser || error) {
|
||||
MPError( error, @"Failed to export mpsites." );
|
||||
PearlMainQueue( ^{
|
||||
} error:&error];
|
||||
|
||||
PearlMainQueue( ^{
|
||||
if (!exportedUser || error) {
|
||||
MPError( error, @"Failed to export mpsites." );
|
||||
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Export Error" message:[error localizedDescription]
|
||||
preferredStyle:UIAlertControllerStyleAlert];
|
||||
[alert addAction:[UIAlertAction actionWithTitle:@"Okay" style:UIAlertActionStyleCancel handler:nil]];
|
||||
[self.navigationController presentViewController:alert animated:YES completion:nil];
|
||||
} );
|
||||
return;
|
||||
}
|
||||
|
||||
NSDateFormatter *exportDateFormatter = [NSDateFormatter new];
|
||||
[exportDateFormatter setDateFormat:@"yyyy'-'MM'-'dd"];
|
||||
NSString *exportFileName = strf( @"%@ (%@).mpsites",
|
||||
[self activeUserForMainThread].name, [exportDateFormatter stringFromDate:[NSDate date]] );
|
||||
|
||||
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Export Destination" message:nil
|
||||
preferredStyle:UIAlertControllerStyleActionSheet];
|
||||
[alert addAction:[UIAlertAction actionWithTitle:@"Send As E-Mail" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
|
||||
NSString *message;
|
||||
if (revealPasswords)
|
||||
message = strf( @"Export of Master Password sites with passwords included.\n\n"
|
||||
@"REMINDER: Make sure nobody else sees this file! Passwords are visible!\n\n\n"
|
||||
@"--\n"
|
||||
@"%@\n"
|
||||
@"Master Password %@, build %@",
|
||||
[self activeUserForMainThread].name,
|
||||
[PearlInfoPlist get].CFBundleShortVersionString,
|
||||
[PearlInfoPlist get].CFBundleVersion );
|
||||
else
|
||||
message = strf( @"Backup of Master Password sites.\n\n\n"
|
||||
@"--\n"
|
||||
@"%@\n"
|
||||
@"Master Password %@, build %@",
|
||||
[self activeUserForMainThread].name,
|
||||
[PearlInfoPlist get].CFBundleShortVersionString,
|
||||
[PearlInfoPlist get].CFBundleVersion );
|
||||
|
||||
[PearlEMail sendEMailTo:nil fromVC:viewController subject:@"Master Password Export" body:message
|
||||
attachments:[[PearlEMailAttachment alloc] initWithContent:[exportedUser dataUsingEncoding:NSUTF8StringEncoding]
|
||||
mimeType:@"text/plain"
|
||||
fileName:exportFileName], nil];
|
||||
return;
|
||||
}]];
|
||||
[alert addAction:[UIAlertAction actionWithTitle:@"Share / Export" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
|
||||
NSURL *applicationSupportURL = [[[NSFileManager defaultManager] URLsForDirectory:NSApplicationSupportDirectory
|
||||
inDomains:NSUserDomainMask] lastObject];
|
||||
NSURL *exportURL = [[applicationSupportURL
|
||||
URLByAppendingPathComponent:[NSBundle mainBundle].bundleIdentifier isDirectory:YES]
|
||||
URLByAppendingPathComponent:exportFileName isDirectory:NO];
|
||||
NSError *writeError = nil;
|
||||
if (![[exportedUser dataUsingEncoding:NSUTF8StringEncoding]
|
||||
writeToURL:exportURL options:NSDataWritingFileProtectionComplete error:&writeError])
|
||||
MPError( writeError, @"Failed to write export data to URL %@.", exportURL );
|
||||
else {
|
||||
self.interactionController = [UIDocumentInteractionController interactionControllerWithURL:exportURL];
|
||||
self.interactionController.UTI = @"com.lyndir.masterpassword.sites";
|
||||
self.interactionController.delegate = self;
|
||||
[self.interactionController presentOpenInMenuFromRect:CGRectZero inView:viewController.view animated:YES];
|
||||
}
|
||||
}]];
|
||||
[alert addAction:[UIAlertAction actionWithTitle:@"Continue" style:UIAlertActionStyleCancel handler:nil]];
|
||||
[self.navigationController presentViewController:alert animated:YES completion:nil];
|
||||
} );
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)migrateFor:(MPUserEntity *)user {
|
||||
|
||||
if ([UIApp canOpenURL:[[NSURL alloc] initWithString:@"volto:"]]) {
|
||||
if (!user) {
|
||||
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
||||
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPUserEntity class] )];
|
||||
NSArray *users = [context executeFetchRequest:fetchRequest error:nil];
|
||||
if (![users count])
|
||||
return;
|
||||
|
||||
UIAlertController *usersSheet = [UIAlertController alertControllerWithTitle:@"Migrate User"
|
||||
message:@"Choose a user to migrate out to Volto."
|
||||
preferredStyle:UIAlertControllerStyleActionSheet];
|
||||
[usersSheet addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:nil]];
|
||||
for (MPUserEntity *user_ in users)
|
||||
[usersSheet addAction:[UIAlertAction actionWithTitle:user_.name style:UIAlertActionStyleDefault handler:
|
||||
^(UIAlertAction *action) { [self migrateFor:user_]; }]];
|
||||
|
||||
PearlMainQueue( ^{
|
||||
[self.navigationController presentViewController:usersSheet animated:YES completion:nil];
|
||||
} );
|
||||
}];
|
||||
return;
|
||||
}
|
||||
|
||||
NSDateFormatter *exportDateFormatter = [NSDateFormatter new];
|
||||
[exportDateFormatter setDateFormat:@"yyyy'-'MM'-'dd"];
|
||||
NSString *exportFileName = strf( @"%@ (%@).mpsites",
|
||||
[self activeUserForMainThread].name, [exportDateFormatter stringFromDate:[NSDate date]] );
|
||||
[MPAppDelegate_Shared managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
||||
NSError *error = nil;
|
||||
NSString *exportedUser = [[MPAppDelegate_Shared get] exportSitesFor:[MPUserEntity existingObjectWithID:user.objectID inContext:context]
|
||||
revealPasswords:NO askExportPassword:^NSString *(NSString *userName) {
|
||||
return PearlAwait( ^(void (^setResult)(id)) {
|
||||
PearlMainQueue( ^{
|
||||
UIAlertController *alert = [UIAlertController alertControllerWithTitle:strf( @"Master Password For:\n%@", userName )
|
||||
message:@"Enter your master password to export the user."
|
||||
preferredStyle:UIAlertControllerStyleAlert];
|
||||
[alert addTextFieldWithConfigurationHandler:^(UITextField *textField) {
|
||||
textField.secureTextEntry = YES;
|
||||
}];
|
||||
[alert addAction:[UIAlertAction actionWithTitle:@"Export" style:UIAlertActionStyleDefault handler:
|
||||
^(UIAlertAction *action) { setResult( alert.textFields.firstObject.text ); }]];
|
||||
[alert addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:
|
||||
^(UIAlertAction *action) { setResult( nil ); }]];
|
||||
[self.navigationController presentViewController:alert animated:YES completion:nil];
|
||||
} );
|
||||
} );
|
||||
} error:&error];
|
||||
|
||||
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Export Destination" message:nil
|
||||
preferredStyle:UIAlertControllerStyleActionSheet];
|
||||
[alert addAction:[UIAlertAction actionWithTitle:@"Send As E-Mail" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
|
||||
NSString *message;
|
||||
if (revealPasswords)
|
||||
message = strf( @"Export of Master Password sites with passwords included.\n\n"
|
||||
@"REMINDER: Make sure nobody else sees this file! Passwords are visible!\n\n\n"
|
||||
@"--\n"
|
||||
@"%@\n"
|
||||
@"Master Password %@, build %@",
|
||||
[self activeUserForMainThread].name,
|
||||
[PearlInfoPlist get].CFBundleShortVersionString,
|
||||
[PearlInfoPlist get].CFBundleVersion );
|
||||
else
|
||||
message = strf( @"Backup of Master Password sites.\n\n\n"
|
||||
@"--\n"
|
||||
@"%@\n"
|
||||
@"Master Password %@, build %@",
|
||||
[self activeUserForMainThread].name,
|
||||
[PearlInfoPlist get].CFBundleShortVersionString,
|
||||
[PearlInfoPlist get].CFBundleVersion );
|
||||
PearlMainQueue( ^{
|
||||
if (!exportedUser || error) {
|
||||
MPError( error, @"Failed to export user." );
|
||||
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Export Error"
|
||||
message:[error localizedDescription]
|
||||
preferredStyle:UIAlertControllerStyleAlert];
|
||||
[alert addAction:[UIAlertAction actionWithTitle:@"Okay" style:UIAlertActionStyleCancel handler:nil]];
|
||||
[self.navigationController presentViewController:alert animated:YES completion:nil];
|
||||
return;
|
||||
}
|
||||
|
||||
[PearlEMail sendEMailTo:nil fromVC:viewController subject:@"Master Password Export" body:message
|
||||
attachments:[[PearlEMailAttachment alloc] initWithContent:[exportedUser dataUsingEncoding:NSUTF8StringEncoding]
|
||||
mimeType:@"text/plain"
|
||||
fileName:exportFileName], nil];
|
||||
return;
|
||||
}]];
|
||||
[alert addAction:[UIAlertAction actionWithTitle:@"Share / Export" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
|
||||
NSURL *applicationSupportURL = [[[NSFileManager defaultManager] URLsForDirectory:NSApplicationSupportDirectory
|
||||
inDomains:NSUserDomainMask] lastObject];
|
||||
NSURL *exportURL = [[applicationSupportURL
|
||||
URLByAppendingPathComponent:[NSBundle mainBundle].bundleIdentifier isDirectory:YES]
|
||||
URLByAppendingPathComponent:exportFileName isDirectory:NO];
|
||||
NSError *writeError = nil;
|
||||
if (![[exportedUser dataUsingEncoding:NSUTF8StringEncoding]
|
||||
writeToURL:exportURL options:NSDataWritingFileProtectionComplete error:&writeError])
|
||||
MPError( writeError, @"Failed to write export data to URL %@.", exportURL );
|
||||
else {
|
||||
self.interactionController = [UIDocumentInteractionController interactionControllerWithURL:exportURL];
|
||||
self.interactionController.UTI = @"com.lyndir.masterpassword.sites";
|
||||
self.interactionController.delegate = self;
|
||||
[self.interactionController presentOpenInMenuFromRect:CGRectZero inView:viewController.view animated:YES];
|
||||
}
|
||||
}]];
|
||||
[alert addAction:[UIAlertAction actionWithTitle:@"Continue" style:UIAlertActionStyleCancel handler:nil]];
|
||||
[self.navigationController presentViewController:alert animated:YES completion:nil];
|
||||
}];
|
||||
NSURLComponents *components = [NSURLComponents new];
|
||||
components.scheme = @"volto";
|
||||
components.path = @"import";
|
||||
components.queryItems = @[ [[NSURLQueryItem alloc] initWithName:@"data" value:exportedUser] ];
|
||||
[UIApp openURL:components.URL];
|
||||
} );
|
||||
}];
|
||||
}
|
||||
|
||||
else if (self.voltoViewController)
|
||||
[self.navigationController presentViewController:self.voltoViewController animated:YES completion:nil];
|
||||
}
|
||||
|
||||
- (void)changeMasterPasswordFor:(MPUserEntity *)user saveInContext:(NSManagedObjectContext *)moc didResetBlock:(void ( ^ )(void))didReset {
|
||||
@ -674,6 +808,13 @@
|
||||
} );
|
||||
}
|
||||
|
||||
#pragma mark - SKStoreProductViewControllerDelegate
|
||||
|
||||
- (void)productViewControllerDidFinish:(SKStoreProductViewController *)viewController {
|
||||
|
||||
[viewController dismissViewControllerAnimated:YES completion:nil];
|
||||
}
|
||||
|
||||
#pragma mark - UIDocumentInteractionControllerDelegate
|
||||
|
||||
- (void)documentInteractionController:(UIDocumentInteractionController *)controller didEndSendingToApplication:(NSString *)application {
|
||||
|
@ -37,6 +37,21 @@
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>[auto]</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Editor</string>
|
||||
<key>CFBundleURLIconFile</key>
|
||||
<string>Icon-320</string>
|
||||
<key>CFBundleURLName</key>
|
||||
<string>com.lyndir.masterpassword</string>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>masterpassword</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>[auto]</string>
|
||||
<key>LSApplicationQueriesSchemes</key>
|
||||
@ -44,6 +59,7 @@
|
||||
<string>firefox</string>
|
||||
<string>googlechrome</string>
|
||||
<string>opera-http</string>
|
||||
<string>volto</string>
|
||||
</array>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
|
Loading…
Reference in New Issue
Block a user