mirror of
https://github.com/MacPass/MacPass.git
synced 2025-12-14 21:13:35 +00:00
Implemented password enforce and recommendation system
This commit is contained in:
@@ -1,8 +1,8 @@
|
|||||||
<?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="5053" systemVersion="13C64" 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="5053"/>
|
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="5056"/>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
<objects>
|
<objects>
|
||||||
<customObject id="-2" userLabel="File's Owner" customClass="MPDatePickingViewController">
|
<customObject id="-2" userLabel="File's Owner" customClass="MPDatePickingViewController">
|
||||||
@@ -49,7 +49,7 @@ AQABAAEAAQAB//+dkAEA//+PgAAE//+dkAEI//+dkAEMUERUAFBTVABQV1QAUFBUAAAAAAEAAAABA
|
|||||||
</calendarDate>
|
</calendarDate>
|
||||||
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
|
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||||
<datePickerElements key="datePickerElements" year="YES" month="YES" day="YES" hour="YES" minute="YES" second="YES"/>
|
<datePickerElements key="datePickerElements" year="YES" month="YES" day="YES" hour="YES" minute="YES"/>
|
||||||
</datePickerCell>
|
</datePickerCell>
|
||||||
</datePicker>
|
</datePicker>
|
||||||
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="15">
|
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="15">
|
||||||
@@ -87,7 +87,6 @@ AQABAAEAAQAB//+dkAEA//+PgAAE//+dkAEI//+dkAEMUERUAFBTVABQV1QAUFBUAAAAAAEAAAABA
|
|||||||
<constraints>
|
<constraints>
|
||||||
<constraint firstItem="3" firstAttribute="leading" secondItem="1" secondAttribute="leading" constant="20" symbolic="YES" id="13"/>
|
<constraint firstItem="3" firstAttribute="leading" secondItem="1" secondAttribute="leading" constant="20" symbolic="YES" id="13"/>
|
||||||
<constraint firstItem="3" firstAttribute="top" secondItem="1" secondAttribute="top" constant="20" symbolic="YES" id="14"/>
|
<constraint firstItem="3" firstAttribute="top" secondItem="1" secondAttribute="top" constant="20" symbolic="YES" id="14"/>
|
||||||
<constraint firstItem="15" firstAttribute="top" secondItem="3" secondAttribute="bottom" constant="20" symbolic="YES" id="21"/>
|
|
||||||
<constraint firstItem="15" firstAttribute="leading" secondItem="1" secondAttribute="leading" constant="20" symbolic="YES" id="22"/>
|
<constraint firstItem="15" firstAttribute="leading" secondItem="1" secondAttribute="leading" constant="20" symbolic="YES" id="22"/>
|
||||||
<constraint firstAttribute="trailing" secondItem="15" secondAttribute="trailing" constant="20" symbolic="YES" id="24"/>
|
<constraint firstAttribute="trailing" secondItem="15" secondAttribute="trailing" constant="20" symbolic="YES" id="24"/>
|
||||||
<constraint firstAttribute="trailing" secondItem="25" secondAttribute="trailing" constant="20" symbolic="YES" id="27"/>
|
<constraint firstAttribute="trailing" secondItem="25" secondAttribute="trailing" constant="20" symbolic="YES" id="27"/>
|
||||||
@@ -96,6 +95,7 @@ AQABAAEAAQAB//+dkAEA//+PgAAE//+dkAEI//+dkAEMUERUAFBTVABQV1QAUFBUAAAAAAEAAAABA
|
|||||||
<constraint firstAttribute="trailing" secondItem="3" secondAttribute="trailing" constant="20" id="6Qs-OP-VRr"/>
|
<constraint firstAttribute="trailing" secondItem="3" secondAttribute="trailing" constant="20" id="6Qs-OP-VRr"/>
|
||||||
<constraint firstItem="25" firstAttribute="centerY" secondItem="29" secondAttribute="centerY" id="M8N-5g-ClS"/>
|
<constraint firstItem="25" firstAttribute="centerY" secondItem="29" secondAttribute="centerY" id="M8N-5g-ClS"/>
|
||||||
<constraint firstItem="25" firstAttribute="leading" secondItem="29" secondAttribute="trailing" constant="8" symbolic="YES" id="on5-xg-jcC"/>
|
<constraint firstItem="25" firstAttribute="leading" secondItem="29" secondAttribute="trailing" constant="8" symbolic="YES" id="on5-xg-jcC"/>
|
||||||
|
<constraint firstItem="15" firstAttribute="top" secondItem="3" secondAttribute="bottom" constant="20" symbolic="YES" id="qKB-vi-OAw"/>
|
||||||
<constraint firstItem="25" firstAttribute="top" secondItem="15" secondAttribute="bottom" constant="8" symbolic="YES" id="sxX-fk-xaJ"/>
|
<constraint firstItem="25" firstAttribute="top" secondItem="15" secondAttribute="bottom" constant="8" symbolic="YES" id="sxX-fk-xaJ"/>
|
||||||
</constraints>
|
</constraints>
|
||||||
</customView>
|
</customView>
|
||||||
|
|||||||
@@ -50,9 +50,7 @@ typedef NS_ENUM(NSUInteger, MPDatePreset) {
|
|||||||
[presetMenu addItem:item];
|
[presetMenu addItem:item];
|
||||||
}
|
}
|
||||||
|
|
||||||
MPDocument *document = [[self windowController] document];
|
[self.datePicker setDateValue:[NSDate date]];
|
||||||
|
|
||||||
[self.datePicker setDateValue:document.selectedItem.timeInfo.expiryTime];
|
|
||||||
[self.presetPopupButton setAction:@selector(setDatePreset:)];
|
[self.presetPopupButton setAction:@selector(setDatePreset:)];
|
||||||
[self.presetPopupButton setMenu:presetMenu];
|
[self.presetPopupButton setMenu:presetMenu];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -161,6 +161,9 @@ typedef NS_OPTIONS(NSUInteger, MPEntrySearchFlags) {
|
|||||||
- (NSArray *)allEntries;
|
- (NSArray *)allEntries;
|
||||||
- (NSArray *)allGroups;
|
- (NSArray *)allGroups;
|
||||||
|
|
||||||
|
- (BOOL)shouldRecommendPasswordChange;
|
||||||
|
- (BOOL)shouldEnforcePasswordChange;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determines, whether the given item is inside the trash.
|
* Determines, whether the given item is inside the trash.
|
||||||
* The trash group itself is not considered as trashed.
|
* The trash group itself is not considered as trashed.
|
||||||
|
|||||||
@@ -282,6 +282,7 @@ NSString *const MPDocumentGroupKey = @"MPDocumentGroupKey
|
|||||||
|
|
||||||
BOOL isUnlocked = (nil != self.tree);
|
BOOL isUnlocked = (nil != self.tree);
|
||||||
if(isUnlocked) {
|
if(isUnlocked) {
|
||||||
|
self.unlockCount += 1;
|
||||||
[[NSNotificationCenter defaultCenter] postNotificationName:MPDocumentDidUnlockDatabaseNotification object:self];
|
[[NSNotificationCenter defaultCenter] postNotificationName:MPDocumentDidUnlockDatabaseNotification object:self];
|
||||||
/* Make sure to only store */
|
/* Make sure to only store */
|
||||||
MPAppDelegate *delegate = [NSApp delegate];
|
MPAppDelegate *delegate = [NSApp delegate];
|
||||||
@@ -292,11 +293,6 @@ NSString *const MPDocumentGroupKey = @"MPDocumentGroupKey
|
|||||||
else {
|
else {
|
||||||
self.compositeKey = nil; // clear the key?
|
self.compositeKey = nil; // clear the key?
|
||||||
}
|
}
|
||||||
if(isUnlocked) {
|
|
||||||
|
|
||||||
self.unlockCount += 1;
|
|
||||||
[[NSNotificationCenter defaultCenter] postNotificationName:MPDocumentDidUnlockDatabaseNotification object:self];
|
|
||||||
}
|
|
||||||
return isUnlocked;
|
return isUnlocked;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -312,6 +308,8 @@ NSString *const MPDocumentGroupKey = @"MPDocumentGroupKey
|
|||||||
[self.compositeKey setPassword:password andKeyfile:keyFileURL];
|
[self.compositeKey setPassword:password andKeyfile:keyFileURL];
|
||||||
}
|
}
|
||||||
self.tree.metaData.masterKeyChanged = [NSDate date];
|
self.tree.metaData.masterKeyChanged = [NSDate date];
|
||||||
|
/* Key change is not undoable so just recored the change as done */
|
||||||
|
[self updateChangeCount:NSChangeDone];
|
||||||
/* We need to store the key file once the user actually writes the database */
|
/* We need to store the key file once the user actually writes the database */
|
||||||
return YES;
|
return YES;
|
||||||
}
|
}
|
||||||
@@ -438,6 +436,18 @@ NSString *const MPDocumentGroupKey = @"MPDocumentGroupKey
|
|||||||
return NO;
|
return NO;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (BOOL)shouldEnforcePasswordChange {
|
||||||
|
KPKMetaData *metaData = self.tree.metaData;
|
||||||
|
if(!metaData.enforceMasterKeyChange) { return NO; }
|
||||||
|
return ( (24*60*60*metaData.masterKeyChangeEnforcementInterval) < -[metaData.masterKeyChanged timeIntervalSinceNow]);
|
||||||
|
}
|
||||||
|
|
||||||
|
- (BOOL)shouldRecommendPasswordChange {
|
||||||
|
KPKMetaData *metaData = self.tree.metaData;
|
||||||
|
if(!metaData.recommendMasterKeyChange) { return NO; }
|
||||||
|
return ( (24*60*60*metaData.masterKeyChangeRecommendationInterval) < -[metaData.masterKeyChanged timeIntervalSinceNow]);
|
||||||
|
}
|
||||||
|
|
||||||
#pragma mark Data manipulation
|
#pragma mark Data manipulation
|
||||||
- (KPKEntry *)createEntry:(KPKGroup *)parent {
|
- (KPKEntry *)createEntry:(KPKGroup *)parent {
|
||||||
if(!parent) {
|
if(!parent) {
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ typedef NS_ENUM(NSUInteger, MPAlertContext) {
|
|||||||
MPAlertLossySaveWarning,
|
MPAlertLossySaveWarning,
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef void (^MPPasswordChangedBlock)(void);
|
typedef void (^MPPasswordChangedBlock)(BOOL didChangePassword);
|
||||||
|
|
||||||
@interface MPDocumentWindowController () {
|
@interface MPDocumentWindowController () {
|
||||||
@private
|
@private
|
||||||
@@ -88,7 +88,7 @@ typedef void (^MPPasswordChangedBlock)(void);
|
|||||||
MPDocument *document = [self document];
|
MPDocument *document = [self document];
|
||||||
|
|
||||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_didRevertDocument:) name:MPDocumentDidRevertNotifiation object:document];
|
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_didRevertDocument:) name:MPDocumentDidRevertNotifiation object:document];
|
||||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(showEntries) name:MPDocumentDidUnlockDatabaseNotification object:document];
|
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_didUnlockDatabase:) name:MPDocumentDidUnlockDatabaseNotification object:document];
|
||||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_didAddEntry:) name:MPDocumentDidAddEntryNotification object:document];
|
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_didAddEntry:) name:MPDocumentDidAddEntryNotification object:document];
|
||||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_didAddGroup:) name:MPDocumentDidAddGroupNotification object:document];
|
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_didAddGroup:) name:MPDocumentDidAddGroupNotification object:document];
|
||||||
|
|
||||||
@@ -181,6 +181,12 @@ typedef void (^MPPasswordChangedBlock)(void);
|
|||||||
[self showInspector:self];
|
[self showInspector:self];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)_didUnlockDatabase:(NSNotification *)notification {
|
||||||
|
[self showEntries];
|
||||||
|
/* Show password reminders */
|
||||||
|
[self _presentPasswordIntervalAlters];
|
||||||
|
}
|
||||||
|
|
||||||
#pragma mark Actions
|
#pragma mark Actions
|
||||||
- (void)saveDocument:(id)sender {
|
- (void)saveDocument:(id)sender {
|
||||||
self.passwordChangedBlock = nil;
|
self.passwordChangedBlock = nil;
|
||||||
@@ -204,8 +210,12 @@ typedef void (^MPPasswordChangedBlock)(void);
|
|||||||
}
|
}
|
||||||
else if(!document.compositeKey) {
|
else if(!document.compositeKey) {
|
||||||
__weak MPDocument *weakDocument = [self document];
|
__weak MPDocument *weakDocument = [self document];
|
||||||
self.passwordChangedBlock = ^void(void){[weakDocument saveDocument:sender];};
|
self.passwordChangedBlock = ^void(BOOL didChangePassword){
|
||||||
[self editPassword:sender];
|
if(didChangePassword) {
|
||||||
|
[weakDocument saveDocument:sender];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
[self _editPasswordRequiringValidInput:YES];
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
/* All set and good ready to save */
|
/* All set and good ready to save */
|
||||||
@@ -216,8 +226,12 @@ typedef void (^MPPasswordChangedBlock)(void);
|
|||||||
MPDocument *document = [self document];
|
MPDocument *document = [self document];
|
||||||
if(!document.compositeKey) {
|
if(!document.compositeKey) {
|
||||||
__weak MPDocument *weakDocument = [self document];
|
__weak MPDocument *weakDocument = [self document];
|
||||||
self.passwordChangedBlock = ^void(void){[weakDocument saveDocumentAs:sender];};
|
self.passwordChangedBlock = ^void(BOOL didChangePassword){
|
||||||
[self editPassword:sender];
|
if(didChangePassword) {
|
||||||
|
[weakDocument saveDocumentAs:sender];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
[self _editPasswordRequiringValidInput:YES];
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
[[self document] saveDocumentAs:sender];
|
[[self document] saveDocumentAs:sender];
|
||||||
@@ -269,12 +283,16 @@ typedef void (^MPPasswordChangedBlock)(void);
|
|||||||
}
|
}
|
||||||
|
|
||||||
- (void)editPassword:(id)sender {
|
- (void)editPassword:(id)sender {
|
||||||
|
[self _editPasswordRequiringValidInput:YES];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)_editPasswordRequiringValidInput:(BOOL)canCancel {
|
||||||
if(!self.passwordEditWindowController) {
|
if(!self.passwordEditWindowController) {
|
||||||
self.passwordEditWindowController = [[MPPasswordEditWindowController alloc] initWithDocument:[self document]];
|
self.passwordEditWindowController = [[MPPasswordEditWindowController alloc] initWithDocument:[self document]];
|
||||||
self.passwordEditWindowController.delegate = self;
|
self.passwordEditWindowController.delegate = self;
|
||||||
}
|
}
|
||||||
/* Disallow empty password if we want to save afterwards, otherwise the dialog keeps poping up */
|
/* Disallow empty password if we want to save afterwards, otherwise the dialog keeps poping up */
|
||||||
self.passwordEditWindowController.allowsEmptyPasswordOrKey = (self.passwordChangedBlock == nil);
|
self.passwordEditWindowController.allowsEmptyPasswordOrKey = canCancel;
|
||||||
[NSApp beginSheet:[self.passwordEditWindowController window] modalForWindow:[self window] modalDelegate:nil didEndSelector:NULL contextInfo:NULL];
|
[NSApp beginSheet:[self.passwordEditWindowController window] modalForWindow:[self window] modalDelegate:nil didEndSelector:NULL contextInfo:NULL];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -327,9 +345,9 @@ typedef void (^MPPasswordChangedBlock)(void);
|
|||||||
else {
|
else {
|
||||||
[self.splitView addSubview:inspectorView];
|
[self.splitView addSubview:inspectorView];
|
||||||
[self.splitView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"[inspectorView(>=200)]"
|
[self.splitView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"[inspectorView(>=200)]"
|
||||||
options:0
|
options:0
|
||||||
metrics:nil
|
metrics:nil
|
||||||
views:NSDictionaryOfVariableBindings(inspectorView)]];
|
views:NSDictionaryOfVariableBindings(inspectorView)]];
|
||||||
[self.inspectorViewController updateResponderChain];
|
[self.inspectorViewController updateResponderChain];
|
||||||
}
|
}
|
||||||
[[NSUserDefaults standardUserDefaults] setBool:!inspectorWasVisible forKey:kMPSettingsKeyShowInspector];
|
[[NSUserDefaults standardUserDefaults] setBool:!inspectorWasVisible forKey:kMPSettingsKeyShowInspector];
|
||||||
@@ -422,13 +440,35 @@ typedef void (^MPPasswordChangedBlock)(void);
|
|||||||
|
|
||||||
#pragma mark MPPasswordEditWindowDelegate
|
#pragma mark MPPasswordEditWindowDelegate
|
||||||
- (void)didFinishPasswordEditing:(BOOL)changedPasswordOrKey {
|
- (void)didFinishPasswordEditing:(BOOL)changedPasswordOrKey {
|
||||||
if(changedPasswordOrKey && self.passwordChangedBlock) {
|
if(self.passwordChangedBlock) {
|
||||||
self.passwordChangedBlock();
|
self.passwordChangedBlock(changedPasswordOrKey);
|
||||||
}
|
}
|
||||||
self.passwordChangedBlock = nil;
|
self.passwordChangedBlock = nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark Alert Delegate
|
#pragma mark NSAlert handling
|
||||||
|
- (void)_presentPasswordIntervalAlters {
|
||||||
|
MPDocument *document = [self document];
|
||||||
|
if(document.shouldEnforcePasswordChange) {
|
||||||
|
NSAlert *alert = [[NSAlert alloc] init];
|
||||||
|
[alert setAlertStyle:NSCriticalAlertStyle];
|
||||||
|
[alert setMessageText:NSLocalizedString(@"ENFORCE_PASSWORD_CHANGE_ALERT_TITLE", "")];
|
||||||
|
[alert setInformativeText:NSLocalizedString(@"ENFORCE_PASSWORD_CHANGE_ALERT_DESCRIPTION", "")];
|
||||||
|
[alert addButtonWithTitle:NSLocalizedString(@"CHANGE_PASSWORD", "")];
|
||||||
|
[alert beginSheetModalForWindow:[self window] modalDelegate:self didEndSelector:@selector(_enforcePasswordChangeAlertDidEnd:returnCode:contextInfo:) contextInfo:NULL];
|
||||||
|
}
|
||||||
|
else if(document.shouldRecommendPasswordChange) {
|
||||||
|
NSAlert *alert = [[NSAlert alloc] init];
|
||||||
|
[alert setAlertStyle:NSInformationalAlertStyle];
|
||||||
|
[alert setMessageText:NSLocalizedString(@"RECOMMEND_PASSWORD_CHANGE_ALERT_TITLE", "")];
|
||||||
|
[alert setInformativeText:NSLocalizedString(@"RECOMMEND_PASSWORD_CHANGE_ALERT_DESCRIPTION", "")];
|
||||||
|
[alert addButtonWithTitle:NSLocalizedString(@"CHANGE_PASSWORD", "")];
|
||||||
|
[alert addButtonWithTitle:NSLocalizedString(@"CANCEL", "")];
|
||||||
|
[[alert buttons][1] setKeyEquivalent:[NSString stringWithFormat:@"%c", 0x1b]];
|
||||||
|
[alert beginSheetModalForWindow:[self window] modalDelegate:self didEndSelector:@selector(_recommentPasswordChangeAlertDidEnd:returnCode:contextInfo:) contextInfo:NULL];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
- (void)_dataLossOnSaveAlertDidEnd:(NSAlert *)alert returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo {
|
- (void)_dataLossOnSaveAlertDidEnd:(NSAlert *)alert returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo {
|
||||||
|
|
||||||
switch(returnCode) {
|
switch(returnCode) {
|
||||||
@@ -450,6 +490,29 @@ typedef void (^MPPasswordChangedBlock)(void);
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)_recommentPasswordChangeAlertDidEnd:(NSAlert *)alert returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo {
|
||||||
|
if(returnCode == NSAlertFirstButtonReturn) {
|
||||||
|
id __weak welf = self;
|
||||||
|
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||||
|
[welf _editPasswordRequiringValidInput:YES];
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)_enforcePasswordChangeAlertDidEnd:(NSAlert *)alert returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo {
|
||||||
|
NSAssert(returnCode == NSAlertFirstButtonReturn, @"Return for password change should always be NSAlertFirstButtonReturn");
|
||||||
|
id __weak welf = self;
|
||||||
|
self.passwordChangedBlock = ^(BOOL didChangePassword){
|
||||||
|
if(!didChangePassword) {
|
||||||
|
[welf _presentPasswordIntervalAlters];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||||
|
[welf _editPasswordRequiringValidInput:NO];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
#pragma mark -
|
#pragma mark -
|
||||||
#pragma mark UI Helper
|
#pragma mark UI Helper
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
Reference in New Issue
Block a user