2
0

Exporting of sites.

[ADDED]     Ability to export site data.
[UPDATED]   Type of passwords can now include feature bits.
This commit is contained in:
Maarten Billemont 2012-05-08 15:57:11 +02:00
parent 7c5cea9c8d
commit 04bc7a497c
10 changed files with 178 additions and 28 deletions

View File

@ -1854,6 +1854,7 @@
DA4426071557C1990052177D /* MPAppDelegate_Store.m */,
DA600C2615056427008E9AB6 /* MPConfig.h */,
DA600C2715056427008E9AB6 /* MPConfig.m */,
DAB8D45C15036BCF00CED3BC /* MPElementStoredEntity.h */,
DAB8D45515036BCF00CED3BC /* MPElementStoredEntity.m */,
DAB8D45915036BCF00CED3BC /* MPTypes.h */,
DAB8D45615036BCF00CED3BC /* MPTypes.m */,
@ -1861,7 +1862,6 @@
DAB8D45815036BCF00CED3BC /* MPElementEntity.m */,
DAB8D45A15036BCF00CED3BC /* MPElementGeneratedEntity.h */,
DAB8D45B15036BCF00CED3BC /* MPElementGeneratedEntity.m */,
DAB8D45C15036BCF00CED3BC /* MPElementStoredEntity.h */,
);
path = MasterPassword;
sourceTree = "<group>";

View File

@ -21,4 +21,6 @@
- (void)saveContext;
- (void)printStore;
- (NSString *)exportSitesShowingPasswords:(BOOL)showPasswords;
@end

View File

@ -164,4 +164,63 @@
}];
}
- (NSString *)exportSitesShowingPasswords:(BOOL)showPasswords {
static NSDateFormatter *rfc3339DateFormatter = nil;
if (!rfc3339DateFormatter) {
rfc3339DateFormatter = [NSDateFormatter new];
NSLocale *enUSPOSIXLocale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
[rfc3339DateFormatter setLocale:enUSPOSIXLocale];
[rfc3339DateFormatter setDateFormat:@"yyyy'-'MM'-'dd'T'HH':'mm':'ss'Z'"];
[rfc3339DateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];
}
// Header.
NSMutableString *export = [NSMutableString new];
[export appendFormat:@"# MasterPassword %@\n", [PearlInfoPlist get].CFBundleVersion];
if (showPasswords)
[export appendFormat:@"# Export of site names and passwords in clear-text.\n"];
else
[export appendFormat:@"# Export of site names and stored passwords (unless device-private) encrypted with the master key.\n"];
[export appendFormat:@"\n"];
[export appendFormat:@"# Key ID: %@\n", self.keyHashHex];
[export appendFormat:@"# Date: %@\n", [rfc3339DateFormatter stringFromDate:[NSDate date]]];
if (showPasswords)
[export appendFormat:@"# Passwords: VISIBLE\n"];
else
[export appendFormat:@"# Passwords: PROTECTED\n"];
[export appendFormat:@"\n"];
// Sites.
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass([MPElementEntity class])];
fetchRequest.sortDescriptors = [NSArray arrayWithObject:[[NSSortDescriptor alloc] initWithKey:@"uses" ascending:NO]];
fetchRequest.predicate = [NSPredicate predicateWithFormat:@"mpHashHex == %@", self.keyHashHex];
__autoreleasing NSError *error = nil;
NSArray *elements = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
if (error)
err(@"Error fetching sites for export: %@", error);
for (MPElementEntity *element in elements) {
NSString *name = element.name;
MPElementType type = (unsigned)element.type;
int16_t uses = element.uses;
NSTimeInterval lastUsed = element.lastUsed;
NSString *content = nil;
// Determine the content to export.
if (!(type & MPElementFeatureDevicePrivate)) {
if (showPasswords)
content = element.content;
else if (type & MPElementFeatureExportContent)
content = element.exportContent;
}
[export appendFormat:@"%@\t%d\t%d\t%@\t%@\n",
name, type, uses, [rfc3339DateFormatter stringFromDate:[NSDate dateWithTimeIntervalSinceReferenceDate:lastUsed]], content];
}
return export;
}
@end

View File

@ -17,7 +17,9 @@
@property (nonatomic, assign) int16_t type;
@property (nonatomic, assign) int16_t uses;
@property (nonatomic, assign) NSTimeInterval lastUsed;
@property (nonatomic, retain, readonly) id content;
@property (nonatomic, retain, readonly) NSString *exportContent;
- (int16_t)use;

View File

@ -28,6 +28,11 @@
@throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"Content implementation missing." userInfo:nil];
}
- (NSString *)exportContent {
return nil;
}
- (NSString *)description {
return str(@"%@:%@", [self class], [self name]);
@ -35,8 +40,8 @@
- (NSString *)debugDescription {
return [NSString stringWithFormat:@"{%@: name=%@, mpHashHex=%@, type=%d, uses=%d, lastUsed=%@}",
NSStringFromClass([self class]), self.name, self.mpHashHex, self.type, self.uses, self.lastUsed];
return str(@"{%@: name=%@, mpHashHex=%@, type=%d, uses=%d, lastUsed=%@}",
NSStringFromClass([self class]), self.name, self.mpHashHex, self.type, self.uses, self.lastUsed);
}
@end

View File

@ -34,7 +34,7 @@
assert(self.type & MPElementTypeClassStored);
NSData *encryptedContent;
if (self.type == MPElementTypeStoredDevicePrivate)
if (self.type & MPElementFeatureDevicePrivate)
encryptedContent = [PearlKeyChain dataOfItemForQuery:[MPElementStoredEntity queryForDevicePrivateElementNamed:self.name]];
else
encryptedContent = self.contentObject;
@ -49,7 +49,7 @@
NSData *encryptedContent = [[content description] encryptWithSymmetricKey:[[MPAppDelegate get] keyWithLength:PearlCryptKeySize]
padding:YES];
if (self.type == MPElementTypeStoredDevicePrivate) {
if (self.type & MPElementFeatureDevicePrivate) {
[PearlKeyChain addOrUpdateItemForQuery:[MPElementStoredEntity queryForDevicePrivateElementNamed:self.name]
withAttributes:[NSDictionary dictionaryWithObjectsAndKeys:
encryptedContent, (__bridge id)kSecValueData,
@ -62,4 +62,9 @@
self.contentObject = encryptedContent;
}
- (NSString *)exportContent {
return [self.contentObject encodeBase64];
}
@end

View File

@ -17,19 +17,28 @@ typedef enum {
} MPElementContentType;
typedef enum {
MPElementTypeClassCalculated = 2 << 7,
MPElementTypeClassStored = 2 << 8,
/** Generate the password. */
MPElementTypeClassGenerated = 1 << 4,
/** Store the password. */
MPElementTypeClassStored = 1 << 5,
} MPElementTypeClass;
typedef enum {
MPElementTypeCalculatedLong = MPElementTypeClassCalculated | 0x01,
MPElementTypeCalculatedMedium = MPElementTypeClassCalculated | 0x02,
MPElementTypeCalculatedShort = MPElementTypeClassCalculated | 0x03,
MPElementTypeCalculatedBasic = MPElementTypeClassCalculated | 0x04,
MPElementTypeCalculatedPIN = MPElementTypeClassCalculated | 0x05,
/** Export the key-protected content data. */
MPElementFeatureExportContent = 1 << 10,
/** Never export content. */
MPElementFeatureDevicePrivate = 1 << 11,
} MPElementFeature;
MPElementTypeStoredPersonal = MPElementTypeClassStored | 0x01,
MPElementTypeStoredDevicePrivate = MPElementTypeClassStored | 0x02,
typedef enum {
MPElementTypeGeneratedLong = 0x0 | MPElementTypeClassGenerated | 0x0,
MPElementTypeGeneratedMedium = 0x1 | MPElementTypeClassGenerated | 0x0,
MPElementTypeGeneratedShort = 0x2 | MPElementTypeClassGenerated | 0x0,
MPElementTypeGeneratedBasic = 0x3 | MPElementTypeClassGenerated | 0x0,
MPElementTypeGeneratedPIN = 0x4 | MPElementTypeClassGenerated | 0x0,
MPElementTypeStoredPersonal = 0x0 | MPElementTypeClassStored | MPElementFeatureExportContent | MPElementFeatureDevicePrivate,
MPElementTypeStoredDevicePrivate = 0x1 | MPElementTypeClassStored | 0x0,
} MPElementType;
#define MPTestFlightCheckpointAction @"MPTestFlightCheckpointAction"

View File

@ -7,10 +7,13 @@
//
#import <UIKit/UIKit.h>
#import <MessageUI/MessageUI.h>
@interface MPAppDelegate : PearlAppDelegate
@interface MPAppDelegate : PearlAppDelegate <MFMailComposeViewControllerDelegate>
- (void)showGuide;
- (void)loadKey:(BOOL)animated;
- (void)export;
@end

View File

@ -220,6 +220,48 @@
});
}
- (void)export {
[PearlAlert showNotice:
@"This export contains the names of all your sites. "
@"You can even open it in a text editor to view its contents.\n\n"
@"If you ever loose your device and don't have iCloud enabled or sync with iTunes, "
@"this will help you remember what sites you had an account with.\n"
@"Don't worry: Even if you don't have an export of your sites, "
@"loosing your device never means loosing your generated passwords."];
[PearlAlert showAlertWithTitle:@"Reveal Passwords?" message:
@"Would you like to make all your passwords visible in the export?\n\n"
@"By default, only your stored passwords are exported, in an encrypted manner, "
@"making it safe from falling in the wrong hands.\n"
@"If you make all your passwords visible and somebody else finds the file, "
@"they can gain access to all your sites with it!"
viewStyle:UIAlertViewStyleDefault tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
if (buttonIndex == [alert firstOtherButtonIndex] + 0)
// Safe Export
[self exportShowPasswords:NO];
if (buttonIndex == [alert firstOtherButtonIndex] + 1)
// Safe Export
[self exportShowPasswords:YES];
} cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:@"Safe Export", @"Show Passwords", nil];
}
- (void)exportShowPasswords:(BOOL)showPasswords {
NSString *exportedSites = [self exportSitesShowingPasswords:showPasswords];
NSDateFormatter *exportDateFormatter = [NSDateFormatter new];
[exportDateFormatter setDateFormat:@"yyyy'-'MM'-'DD'T'HH':'mm'.mpsites'"];
MFMailComposeViewController *composer = [[MFMailComposeViewController alloc] init];
[composer setMailComposeDelegate:self];
[composer setSubject:@"Master Password site export"];
[composer addAttachmentData:[exportedSites dataUsingEncoding:NSUTF8StringEncoding] mimeType:@"text/plain"
fileName:[exportDateFormatter stringFromDate:[NSDate date]]];
[self.window.rootViewController presentModalViewController:composer animated:YES];
}
#pragma mark - UIApplicationDelegate
- (void)applicationDidEnterBackground:(UIApplication *)application {
[[LocalyticsSession sharedLocalyticsSession] close];
@ -258,6 +300,26 @@
[TestFlight passCheckpoint:MPTestFlightCheckpointDeactivated];
}
#pragma mark - MFMailComposeViewControllerDelegate
- (void)mailComposeController:(MFMailComposeViewController *)controller
didFinishWithResult:(MFMailComposeResult)result error:(NSError *)error {
if (error)
err(@"Error composing mail message: %@", error);
switch (result) {
case MFMailComposeResultSaved:
case MFMailComposeResultSent:
break;
case MFMailComposeResultFailed:
break;
case MFMailComposeResultCancelled:
break;
}
}
#pragma mark - TestFlight

View File

@ -398,34 +398,37 @@
case 2:
[[MPAppDelegate get] showGuide];
break;
case 3: {
case 3:
{
IASKAppSettingsViewController *settingsVC = [IASKAppSettingsViewController new];
settingsVC.delegate = self;
[self.navigationController pushViewController:settingsVC animated:YES];
break;
}
#ifdef ADHOC
case 4:
[[MPAppDelegate get] export];
break;
#ifdef ADHOC
case 5:
[TestFlight openFeedbackView];
break;
case 5:
case 6:
#else
case 4:
case 5:
#endif
#ifdef DEBUG
{
[[MPAppDelegate get].storeManager hardResetCloudStorage];
}
break;
#ifdef ADHOC
case 6: {
[[MPAppDelegate get].storeManager useiCloudStore:![MPAppDelegate get].storeManager.iCloudEnabled];
}
case 7:
#else
case 5: {
[[MPAppDelegate get].storeManager useiCloudStore:![MPAppDelegate get].storeManager.iCloudEnabled];
}
break;
case 8:
#else
case 6:
[[MPAppDelegate get].storeManager useiCloudStore:![MPAppDelegate get].storeManager.iCloudEnabled];
break;
case 7:
#endif
#endif
{
@ -438,7 +441,7 @@
[TestFlight passCheckpoint:MPTestFlightCheckpointAction];
} cancelTitle:[PearlStrings get].commonButtonCancel destructiveTitle:nil
otherTitles:
[self isHelpVisible]? @"Hide Help": @"Show Help", @"FAQ", @"Tutorial", @"Settings",
[self isHelpVisible]? @"Hide Help": @"Show Help", @"FAQ", @"Tutorial", @"Settings", @"Export", @"Import",
#ifdef ADHOC
@"Feedback",
#endif