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
|
askImportPassword:(NSString *( ^ )(NSString *userName))importPassword
|
||||||
askUserPassword:(NSString *( ^ )(NSString *userName))userPassword
|
askUserPassword:(NSString *( ^ )(NSString *userName))userPassword
|
||||||
result:(void ( ^ )(NSError *error))resultBlock;
|
result:(void ( ^ )(NSError *error))resultBlock;
|
||||||
- (void)exportSitesRevealPasswords:(BOOL)revealPasswords
|
- (NSString *)exportSitesFor:(MPUserEntity *)user
|
||||||
askExportPassword:(NSString *( ^ )(NSString *userName))askImportPassword
|
revealPasswords:(BOOL)revealPasswords
|
||||||
result:(void ( ^ )(NSString *exportedUser, NSError *error))resultBlock;
|
askExportPassword:(NSString *( ^ )(NSString *userName))askExportPassword
|
||||||
|
error:(__autoreleasing NSError **)error;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
@ -704,16 +704,17 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
|
|||||||
site.lastUsed = [NSDate dateWithTimeIntervalSince1970:importSite->lastUsed];
|
site.lastUsed = [NSDate dateWithTimeIntervalSince1970:importSite->lastUsed];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)exportSitesRevealPasswords:(BOOL)revealPasswords
|
- (NSString *)exportSitesFor:(MPUserEntity *)user
|
||||||
askExportPassword:(NSString *( ^ )(NSString *userName))askImportPassword
|
revealPasswords:(BOOL)revealPasswords
|
||||||
result:(void ( ^ )(NSString *exportedUser, NSError *error))resultBlock {
|
askExportPassword:(NSString *( ^ )(NSString *userName))askExportPassword
|
||||||
|
error:(__autoreleasing NSError **)error {
|
||||||
[MPAppDelegate_Shared managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
|
||||||
MPUserEntity *user = [self activeUserInContext:context];
|
|
||||||
|
|
||||||
|
MPMarshalledUser *exportUser = NULL;
|
||||||
|
MPMarshalledFile *exportFile = NULL;
|
||||||
|
@try {
|
||||||
inf( @"Exporting sites, %@, for user: %@", revealPasswords? @"revealing passwords": @"omitting passwords", user.userID );
|
inf( @"Exporting sites, %@, for user: %@", revealPasswords? @"revealing passwords": @"omitting passwords", user.userID );
|
||||||
MPMarshalledUser *exportUser = mpw_marshal_user( user.name.UTF8String,
|
exportUser = mpw_marshal_user( user.name.UTF8String,
|
||||||
mpw_masterKeyProvider_str( askImportPassword( user.name ).UTF8String ), user.algorithm.version );
|
mpw_masterKeyProvider_str( askExportPassword( user.name ).UTF8String ), user.algorithm.version );
|
||||||
exportUser->redacted = !revealPasswords;
|
exportUser->redacted = !revealPasswords;
|
||||||
exportUser->avatar = (unsigned int)user.avatar;
|
exportUser->avatar = (unsigned int)user.avatar;
|
||||||
exportUser->keyID = mpw_strdup( [user.keyID encodeHex].UTF8String );
|
exportUser->keyID = mpw_strdup( [user.keyID encodeHex].UTF8String );
|
||||||
@ -737,22 +738,26 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
|
|||||||
mpw_marshal_question( exportSite, siteQuestion.keyword.UTF8String );
|
mpw_marshal_question( exportSite, siteQuestion.keyword.UTF8String );
|
||||||
}
|
}
|
||||||
|
|
||||||
MPMarshalledFile *exportFile = NULL;
|
|
||||||
const char *export = mpw_marshal_write( MPMarshalFormatDefault, &exportFile, exportUser );
|
const char *export = mpw_marshal_write( MPMarshalFormatDefault, &exportFile, exportUser );
|
||||||
NSString *exportedUser = nil;
|
NSString *exportedUser = nil;
|
||||||
if (export && exportFile && exportFile->error.type == MPMarshalSuccess)
|
if (export && exportFile && exportFile->error.type == MPMarshalSuccess)
|
||||||
exportedUser = [NSString stringWithCString:export encoding:NSUTF8StringEncoding];
|
exportedUser = [NSString stringWithCString:export encoding:NSUTF8StringEncoding];
|
||||||
mpw_free_string( &export );
|
mpw_free_string( &export );
|
||||||
|
|
||||||
resultBlock( exportedUser, exportFile && exportFile->error.type == MPMarshalSuccess? nil:
|
if (error)
|
||||||
[NSError errorWithDomain:MPErrorDomain code:MPErrorMarshalCode userInfo:@{
|
*error = exportFile && exportFile->error.type == MPMarshalSuccess? nil:
|
||||||
@"type" : @(exportFile? exportFile->error.type: MPMarshalErrorInternal),
|
[NSError errorWithDomain:MPErrorDomain code:MPErrorMarshalCode userInfo:@{
|
||||||
NSLocalizedDescriptionKey: @(exportFile? exportFile->error.message: nil),
|
@"type" : @(exportFile? exportFile->error.type: MPMarshalErrorInternal),
|
||||||
}] );
|
NSLocalizedDescriptionKey: @(exportFile? exportFile->error.message: nil),
|
||||||
|
}];
|
||||||
|
|
||||||
|
return exportedUser;
|
||||||
|
}
|
||||||
|
@finally {
|
||||||
mpw_marshal_file_free( &exportFile );
|
mpw_marshal_file_free( &exportFile );
|
||||||
mpw_marshal_user_free( &exportUser );
|
mpw_marshal_user_free( &exportUser );
|
||||||
mpw_masterKeyProvider_free();
|
mpw_masterKeyProvider_free();
|
||||||
}];
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
|
|
||||||
#import "MPNavigationController.h"
|
#import "MPNavigationController.h"
|
||||||
#import "MPWebViewController.h"
|
#import "MPWebViewController.h"
|
||||||
|
#import "MPiOSAppDelegate.h"
|
||||||
|
|
||||||
@implementation MPNavigationController
|
@implementation MPNavigationController
|
||||||
|
|
||||||
@ -29,6 +30,16 @@
|
|||||||
[self performSegueWithIdentifier:@"setup" sender:self];
|
[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 {
|
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
|
||||||
|
|
||||||
if ([segue.identifier isEqualToString:@"web"])
|
if ([segue.identifier isEqualToString:@"web"])
|
||||||
|
@ -33,9 +33,8 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
|
|||||||
MPPasswordsBadNameTip = 1 << 0,
|
MPPasswordsBadNameTip = 1 << 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
@interface MPSitesViewController()<NSFetchedResultsControllerDelegate, SKStoreProductViewControllerDelegate>
|
@interface MPSitesViewController()<NSFetchedResultsControllerDelegate>
|
||||||
|
|
||||||
@property(nonatomic, strong) SKStoreProductViewController *voltoViewController;
|
|
||||||
@property(nonatomic, strong) NSFetchedResultsController *fetchedResultsController;
|
@property(nonatomic, strong) NSFetchedResultsController *fetchedResultsController;
|
||||||
@property(nonatomic, strong) NSArray *fuzzyGroups;
|
@property(nonatomic, strong) NSArray *fuzzyGroups;
|
||||||
@property(nonatomic, strong) NSCharacterSet *siteNameAcceptableCharactersSet;
|
@property(nonatomic, strong) NSCharacterSet *siteNameAcceptableCharactersSet;
|
||||||
@ -435,13 +434,6 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
|
|||||||
} completion:completion];
|
} completion:completion];
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - SKStoreProductViewControllerDelegate
|
|
||||||
|
|
||||||
- (void)productViewControllerDidFinish:(SKStoreProductViewController *)viewController {
|
|
||||||
|
|
||||||
[viewController dismissViewControllerAnimated:YES completion:nil];
|
|
||||||
}
|
|
||||||
|
|
||||||
#pragma mark - Actions
|
#pragma mark - Actions
|
||||||
|
|
||||||
- (IBAction)dismissPopdown:(id)sender {
|
- (IBAction)dismissPopdown:(id)sender {
|
||||||
@ -454,45 +446,7 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
|
|||||||
|
|
||||||
- (IBAction)upgradeVolto:(UIButton *)sender {
|
- (IBAction)upgradeVolto:(UIButton *)sender {
|
||||||
|
|
||||||
if ([UIApp canOpenURL:[[NSURL alloc] initWithString:@"volto:"]]) {
|
[[MPiOSAppDelegate get] migrateFor:[MPiOSAppDelegate get].activeUserForMainThread];
|
||||||
[[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];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - Private
|
#pragma mark - Private
|
||||||
@ -505,23 +459,8 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
|
|||||||
self.voltoMigrateAlert.visible = YES;
|
self.voltoMigrateAlert.visible = YES;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
self.voltoInstallAlert.visible = NO;
|
self.voltoInstallAlert.visible = [MPiOSAppDelegate get].voltoViewController != nil;
|
||||||
self.voltoMigrateAlert.visible = NO;
|
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 "MPWebViewController.h"
|
||||||
|
#import "MPiOSAppDelegate.h"
|
||||||
|
|
||||||
@implementation MPWebViewController
|
@implementation MPWebViewController
|
||||||
|
|
||||||
@ -57,6 +58,18 @@
|
|||||||
|
|
||||||
#pragma mark - WKNavigationDelegate
|
#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 {
|
- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(null_unspecified WKNavigation *)navigation {
|
||||||
|
|
||||||
self.webNavigationItem.title = webView.URL.host;
|
self.webNavigationItem.title = webView.URL.host;
|
||||||
@ -77,7 +90,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
[self.webNavigationItem setLeftBarButtonItem:[[UIBarButtonItem alloc]
|
[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) {
|
[webView evaluateJavaScript:@"document.title" completionHandler:^(id o, NSError *error) {
|
||||||
self.webNavigationItem.prompt = [o description];
|
self.webNavigationItem.prompt = [o description];
|
||||||
}];
|
}];
|
||||||
@ -85,7 +98,7 @@
|
|||||||
|
|
||||||
#pragma mark - Actions
|
#pragma mark - Actions
|
||||||
|
|
||||||
- (IBAction)openURL:(id)sender {
|
- (IBAction)action:(id)sender {
|
||||||
|
|
||||||
UIAlertController *controller = [UIAlertController new];
|
UIAlertController *controller = [UIAlertController new];
|
||||||
controller.title = self.webView.URL.host;
|
controller.title = self.webView.URL.host;
|
||||||
|
@ -17,15 +17,22 @@
|
|||||||
//==============================================================================
|
//==============================================================================
|
||||||
|
|
||||||
#import <UIKit/UIKit.h>
|
#import <UIKit/UIKit.h>
|
||||||
|
#import <StoreKit/StoreKit.h>
|
||||||
|
|
||||||
#import "MPAppDelegate_Shared.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)showFeedbackWithLogs:(BOOL)logs forVC:(UIViewController *)viewController;
|
||||||
- (void)openFeedbackWithLogs:(BOOL)logs forVC:(UIViewController *)viewController;
|
- (void)openFeedbackWithLogs:(BOOL)logs forVC:(UIViewController *)viewController;
|
||||||
|
|
||||||
- (void)showExportForVC:(UIViewController *)viewController;
|
- (void)showExportForVC:(UIViewController *)viewController;
|
||||||
|
- (void)migrateFor:(MPUserEntity *)user;
|
||||||
|
|
||||||
- (void)changeMasterPasswordFor:(MPUserEntity *)user saveInContext:(NSManagedObjectContext *)moc didResetBlock:(void ( ^ )(void))didReset;
|
- (void)changeMasterPasswordFor:(MPUserEntity *)user saveInContext:(NSManagedObjectContext *)moc didResetBlock:(void ( ^ )(void))didReset;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
@ -35,7 +35,7 @@
|
|||||||
@implementation CountlyPushNotifications(MPNotifications)
|
@implementation CountlyPushNotifications(MPNotifications)
|
||||||
|
|
||||||
- (void)openURL:(NSString *)URLString {
|
- (void)openURL:(NSString *)URLString {
|
||||||
[UIApp.keyWindow.rootViewController performSegueWithIdentifier:@"web" sender:[NSURL URLWithString:URLString]];
|
[[MPiOSAppDelegate get].navigationController performSegueWithIdentifier:@"web" sender:[NSURL URLWithString:URLString]];
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@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( ^{
|
PearlMainQueueOperation( ^{
|
||||||
|
[self.navigationController performSegueWithIdentifier:@"web" sender:[NSURL URLWithString:@"masterpassword://foo?bar=quux"]];
|
||||||
if ([[MPiOSConfig get].showSetup boolValue])
|
if ([[MPiOSConfig get].showSetup boolValue])
|
||||||
[self.navigationController performSegueWithIdentifier:@"setup" sender:self];
|
[self.navigationController performSegueWithIdentifier:@"setup" sender:self];
|
||||||
|
|
||||||
@ -207,6 +226,12 @@
|
|||||||
if (!url)
|
if (!url)
|
||||||
return NO;
|
return NO;
|
||||||
|
|
||||||
|
// masterpassword: URLs.
|
||||||
|
if ([url.scheme isEqualToString:@"masterpassword"]) {
|
||||||
|
[self openURL:url];
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
|
||||||
// Arbitrary URL to mpsites data.
|
// Arbitrary URL to mpsites data.
|
||||||
[[[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:
|
[[[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:
|
||||||
^(NSData *importedSitesData, NSURLResponse *response, NSError *error) {
|
^(NSData *importedSitesData, NSURLResponse *response, NSError *error) {
|
||||||
@ -444,6 +469,42 @@
|
|||||||
|
|
||||||
#pragma mark - Behavior
|
#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 {
|
- (void)showFeedbackWithLogs:(BOOL)logs forVC:(UIViewController *)viewController {
|
||||||
|
|
||||||
if (![PearlEMail canSendMail]) {
|
if (![PearlEMail canSendMail]) {
|
||||||
@ -564,89 +625,162 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
[self exportSitesRevealPasswords:revealPasswords askExportPassword:^NSString *(NSString *userName) {
|
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
||||||
return PearlAwait( ^(void (^setResult)(id)) {
|
NSError *error = nil;
|
||||||
PearlMainQueue( ^{
|
NSString *exportedUser = [self exportSitesFor:[self activeUserInContext:context] revealPasswords:revealPasswords askExportPassword:^NSString *(NSString *userName) {
|
||||||
UIAlertController *alert = [UIAlertController alertControllerWithTitle:strf( @"Master Password For:\n%@", userName )
|
return PearlAwait( ^(void (^setResult)(id)) {
|
||||||
message:@"Enter your master password to export the user."
|
PearlMainQueue( ^{
|
||||||
preferredStyle:UIAlertControllerStyleAlert];
|
UIAlertController *alert = [UIAlertController alertControllerWithTitle:strf( @"Master Password For:\n%@", userName )
|
||||||
[alert addTextFieldWithConfigurationHandler:^(UITextField *textField) {
|
message:@"Enter your master password to export the user."
|
||||||
textField.secureTextEntry = YES;
|
preferredStyle:UIAlertControllerStyleAlert];
|
||||||
}];
|
[alert addTextFieldWithConfigurationHandler:^(UITextField *textField) {
|
||||||
[alert addAction:[UIAlertAction actionWithTitle:@"Export" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
|
textField.secureTextEntry = YES;
|
||||||
setResult( alert.textFields.firstObject.text );
|
}];
|
||||||
}]];
|
[alert addAction:[UIAlertAction actionWithTitle:@"Export" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
|
||||||
[alert addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) {
|
setResult( alert.textFields.firstObject.text );
|
||||||
setResult( nil );
|
}]];
|
||||||
}]];
|
[alert addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) {
|
||||||
[self.navigationController presentViewController:alert animated:YES completion:nil];
|
setResult( nil );
|
||||||
|
}]];
|
||||||
|
[self.navigationController presentViewController:alert animated:YES completion:nil];
|
||||||
|
} );
|
||||||
} );
|
} );
|
||||||
} );
|
} error:&error];
|
||||||
} result:^(NSString *exportedUser, NSError *error) {
|
|
||||||
if (!exportedUser || error) {
|
PearlMainQueue( ^{
|
||||||
MPError( error, @"Failed to export mpsites." );
|
if (!exportedUser || error) {
|
||||||
PearlMainQueue( ^{
|
MPError( error, @"Failed to export mpsites." );
|
||||||
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Export Error" message:[error localizedDescription]
|
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Export Error" message:[error localizedDescription]
|
||||||
preferredStyle:UIAlertControllerStyleAlert];
|
preferredStyle:UIAlertControllerStyleAlert];
|
||||||
[alert addAction:[UIAlertAction actionWithTitle:@"Okay" style:UIAlertActionStyleCancel handler:nil]];
|
[alert addAction:[UIAlertAction actionWithTitle:@"Okay" style:UIAlertActionStyleCancel handler:nil]];
|
||||||
[self.navigationController presentViewController:alert animated:YES completion: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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
NSDateFormatter *exportDateFormatter = [NSDateFormatter new];
|
[MPAppDelegate_Shared managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
||||||
[exportDateFormatter setDateFormat:@"yyyy'-'MM'-'dd"];
|
NSError *error = nil;
|
||||||
NSString *exportFileName = strf( @"%@ (%@).mpsites",
|
NSString *exportedUser = [[MPAppDelegate_Shared get] exportSitesFor:[MPUserEntity existingObjectWithID:user.objectID inContext:context]
|
||||||
[self activeUserForMainThread].name, [exportDateFormatter stringFromDate:[NSDate date]] );
|
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
|
PearlMainQueue( ^{
|
||||||
preferredStyle:UIAlertControllerStyleActionSheet];
|
if (!exportedUser || error) {
|
||||||
[alert addAction:[UIAlertAction actionWithTitle:@"Send As E-Mail" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
|
MPError( error, @"Failed to export user." );
|
||||||
NSString *message;
|
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Export Error"
|
||||||
if (revealPasswords)
|
message:[error localizedDescription]
|
||||||
message = strf( @"Export of Master Password sites with passwords included.\n\n"
|
preferredStyle:UIAlertControllerStyleAlert];
|
||||||
@"REMINDER: Make sure nobody else sees this file! Passwords are visible!\n\n\n"
|
[alert addAction:[UIAlertAction actionWithTitle:@"Okay" style:UIAlertActionStyleCancel handler:nil]];
|
||||||
@"--\n"
|
[self.navigationController presentViewController:alert animated:YES completion:nil];
|
||||||
@"%@\n"
|
return;
|
||||||
@"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
|
NSURLComponents *components = [NSURLComponents new];
|
||||||
attachments:[[PearlEMailAttachment alloc] initWithContent:[exportedUser dataUsingEncoding:NSUTF8StringEncoding]
|
components.scheme = @"volto";
|
||||||
mimeType:@"text/plain"
|
components.path = @"import";
|
||||||
fileName:exportFileName], nil];
|
components.queryItems = @[ [[NSURLQueryItem alloc] initWithName:@"data" value:exportedUser] ];
|
||||||
return;
|
[UIApp openURL:components.URL];
|
||||||
}]];
|
} );
|
||||||
[alert addAction:[UIAlertAction actionWithTitle:@"Share / Export" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
|
}];
|
||||||
NSURL *applicationSupportURL = [[[NSFileManager defaultManager] URLsForDirectory:NSApplicationSupportDirectory
|
}
|
||||||
inDomains:NSUserDomainMask] lastObject];
|
|
||||||
NSURL *exportURL = [[applicationSupportURL
|
else if (self.voltoViewController)
|
||||||
URLByAppendingPathComponent:[NSBundle mainBundle].bundleIdentifier isDirectory:YES]
|
[self.navigationController presentViewController:self.voltoViewController animated:YES completion:nil];
|
||||||
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)changeMasterPasswordFor:(MPUserEntity *)user saveInContext:(NSManagedObjectContext *)moc didResetBlock:(void ( ^ )(void))didReset {
|
- (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
|
#pragma mark - UIDocumentInteractionControllerDelegate
|
||||||
|
|
||||||
- (void)documentInteractionController:(UIDocumentInteractionController *)controller didEndSendingToApplication:(NSString *)application {
|
- (void)documentInteractionController:(UIDocumentInteractionController *)controller didEndSendingToApplication:(NSString *)application {
|
||||||
|
@ -37,6 +37,21 @@
|
|||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>[auto]</string>
|
<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>
|
<key>CFBundleVersion</key>
|
||||||
<string>[auto]</string>
|
<string>[auto]</string>
|
||||||
<key>LSApplicationQueriesSchemes</key>
|
<key>LSApplicationQueriesSchemes</key>
|
||||||
@ -44,6 +59,7 @@
|
|||||||
<string>firefox</string>
|
<string>firefox</string>
|
||||||
<string>googlechrome</string>
|
<string>googlechrome</string>
|
||||||
<string>opera-http</string>
|
<string>opera-http</string>
|
||||||
|
<string>volto</string>
|
||||||
</array>
|
</array>
|
||||||
<key>LSRequiresIPhoneOS</key>
|
<key>LSRequiresIPhoneOS</key>
|
||||||
<true/>
|
<true/>
|
||||||
|
Loading…
Reference in New Issue
Block a user