Improvements to password import.
[FIXED] Importing of mpsites with passwords showing for stored password types. [FIXED] Don't try to show mail composition dialog when the user has no mail account configured. This will crash. Instead, show a friendly popup explaining things. [IMPROVED] Message of password export emails. [FIXED] Hierarchy of MPUnlockVC so password field becomes touchable.
This commit is contained in:
parent
b7e91358be
commit
4d4ba3425e
@ -16,6 +16,4 @@
|
||||
- (void)storeSavedKeyFor:(MPUserEntity *)user;
|
||||
- (void)forgetSavedKeyFor:(MPUserEntity *)user;
|
||||
|
||||
- (NSData *)keyWithLength:(NSUInteger)keyLength;
|
||||
|
||||
@end
|
||||
|
@ -169,9 +169,4 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (NSData *)keyWithLength:(NSUInteger)keyLength {
|
||||
|
||||
return [self.key subdataWithRange:NSMakeRange(0, MIN(keyLength, self.key.length))];
|
||||
}
|
||||
|
||||
@end
|
||||
|
@ -217,9 +217,10 @@
|
||||
if (!headerPattern || !sitePattern)
|
||||
return MPImportResultInternalError;
|
||||
|
||||
NSData *key = nil;
|
||||
NSString *keyIDHex = nil, *userName = nil;
|
||||
MPUserEntity *user = nil;
|
||||
BOOL headerStarted = NO, headerEnded = NO;
|
||||
BOOL headerStarted = NO, headerEnded = NO, clearText = NO;
|
||||
NSArray *importedSiteLines = [importedSitesString componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]];
|
||||
NSMutableSet *elementsToDelete = [NSMutableSet set];
|
||||
NSMutableArray *importedSiteElements = [NSMutableArray arrayWithCapacity:[importedSiteLines count]];
|
||||
@ -246,19 +247,24 @@
|
||||
}
|
||||
NSTextCheckingResult *headerElements = [[headerPattern matchesInString:importedSiteLine options:0
|
||||
range:NSMakeRange(0, [importedSiteLine length])] lastObject];
|
||||
NSString *key = [importedSiteLine substringWithRange:[headerElements rangeAtIndex:1]];
|
||||
NSString *value = [importedSiteLine substringWithRange:[headerElements rangeAtIndex:2]];
|
||||
if ([key isEqualToString:@"User Name"]) {
|
||||
userName = value;
|
||||
NSString *headerName = [importedSiteLine substringWithRange:[headerElements rangeAtIndex:1]];
|
||||
NSString *headerValue = [importedSiteLine substringWithRange:[headerElements rangeAtIndex:2]];
|
||||
if ([headerName isEqualToString:@"User Name"]) {
|
||||
userName = headerValue;
|
||||
key = keyForPassword(password, userName);
|
||||
|
||||
NSFetchRequest *userFetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass([MPUserEntity class])];
|
||||
userFetchRequest.predicate = [NSPredicate predicateWithFormat:@"name == %@", userName];
|
||||
user = [[self.managedObjectContext executeFetchRequest:fetchRequest error:&error] lastObject];
|
||||
}
|
||||
if ([key isEqualToString:@"Key ID"]) {
|
||||
if (![(keyIDHex = value) isEqualToString:[keyIDForPassword(password, userName) encodeHex]])
|
||||
if ([headerName isEqualToString:@"Key ID"]) {
|
||||
if (![(keyIDHex = headerValue) isEqualToString:[keyIDForKey(key) encodeHex]])
|
||||
return MPImportResultInvalidPassword;
|
||||
}
|
||||
if ([headerName isEqualToString:@"Passwords"]) {
|
||||
if ([headerValue isEqualToString:@"VISIBLE"])
|
||||
clearText = YES;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
@ -337,7 +343,10 @@
|
||||
element.uses = uses;
|
||||
element.lastUsed = lastUsed;
|
||||
if ([exportContent length])
|
||||
[element importContent:exportContent];
|
||||
if (clearText)
|
||||
[element importClearTextContent:exportContent usingKey:key];
|
||||
else
|
||||
[element importProtectedContent:exportContent];
|
||||
}
|
||||
[self saveContext];
|
||||
|
||||
|
@ -21,7 +21,8 @@
|
||||
|
||||
- (NSUInteger)use;
|
||||
- (NSString *)exportContent;
|
||||
- (void)importContent:(NSString *)content;
|
||||
- (void)importProtectedContent:(NSString *)protectedContent;
|
||||
- (void)importClearTextContent:(NSString *)clearContent usingKey:(NSData *)key;
|
||||
|
||||
@end
|
||||
|
||||
|
@ -50,10 +50,14 @@
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (void)importContent:(NSString *)content {
|
||||
- (void)importProtectedContent:(NSString *)content {
|
||||
|
||||
}
|
||||
|
||||
- (void)importClearTextContent:(NSString *)content usingKey:(NSData *)key {
|
||||
|
||||
}
|
||||
|
||||
- (NSString *)description {
|
||||
|
||||
return PearlString(@"%@:%@", [self class], [self name]);
|
||||
@ -107,8 +111,19 @@
|
||||
}
|
||||
|
||||
- (id)content {
|
||||
|
||||
return [self contentUsingKey:[MPAppDelegate get].key];
|
||||
}
|
||||
|
||||
- (void)setContent:(id)content {
|
||||
|
||||
[self setContent:content usingKey:[MPAppDelegate get].key];
|
||||
}
|
||||
|
||||
- (id)contentUsingKey:(NSData *)key {
|
||||
|
||||
assert(self.type & MPElementTypeClassStored);
|
||||
assert([keyIDForKey(key) isEqualToData:self.user.keyID]);
|
||||
|
||||
NSData *encryptedContent;
|
||||
if (self.type & MPElementFeatureDevicePrivate)
|
||||
@ -116,15 +131,16 @@
|
||||
else
|
||||
encryptedContent = self.contentObject;
|
||||
|
||||
NSData *decryptedContent = [encryptedContent decryptWithSymmetricKey:[[MPAppDelegate get] keyWithLength:PearlCryptKeySize]
|
||||
padding:YES];
|
||||
NSData *decryptedContent = [encryptedContent decryptWithSymmetricKey:subkeyForKey(key, PearlCryptKeySize) padding:YES];
|
||||
return [[NSString alloc] initWithBytes:decryptedContent.bytes length:decryptedContent.length encoding:NSUTF8StringEncoding];
|
||||
}
|
||||
|
||||
- (void)setContent:(id)content {
|
||||
- (void)setContent:(id)content usingKey:(NSData *)key {
|
||||
|
||||
NSData *encryptedContent = [[content description] encryptWithSymmetricKey:[[MPAppDelegate get] keyWithLength:PearlCryptKeySize]
|
||||
padding:YES];
|
||||
assert(self.type & MPElementTypeClassStored);
|
||||
assert([keyIDForKey(key) isEqualToData:self.user.keyID]);
|
||||
|
||||
NSData *encryptedContent = [[content description] encryptWithSymmetricKey:subkeyForKey(key, PearlCryptKeySize) padding:YES];
|
||||
|
||||
if (self.type & MPElementFeatureDevicePrivate) {
|
||||
[PearlKeyChain addOrUpdateItemForQuery:[MPElementStoredEntity queryForDevicePrivateElementNamed:self.name]
|
||||
@ -144,9 +160,14 @@
|
||||
return [self.contentObject encodeBase64];
|
||||
}
|
||||
|
||||
- (void)importContent:(NSString *)content {
|
||||
- (void)importProtectedContent:(NSString *)protectedContent {
|
||||
|
||||
self.contentObject = [content decodeBase64];
|
||||
self.contentObject = [protectedContent decodeBase64];
|
||||
}
|
||||
|
||||
- (void)importClearTextContent:(NSString *)clearContent usingKey:(NSData *)key {
|
||||
|
||||
[self setContent:clearContent usingKey:key];
|
||||
}
|
||||
|
||||
@end
|
||||
|
@ -78,6 +78,7 @@ typedef enum {
|
||||
#define MPNotificationElementUsed @"MPNotificationElementUsed"
|
||||
|
||||
NSData *keyForPassword(NSString *password, NSString *username);
|
||||
NSData *subkeyForKey(NSData *key, NSUInteger subkeyLength);
|
||||
NSData *keyIDForPassword(NSString *password, NSString *username);
|
||||
NSData *keyIDForKey(NSData *key);
|
||||
NSString *NSStringFromMPElementType(MPElementType type);
|
||||
|
@ -30,6 +30,13 @@ NSData *keyForPassword(NSString *password, NSString *username) {
|
||||
return key;
|
||||
}
|
||||
|
||||
|
||||
NSData *subkeyForKey(NSData *key, NSUInteger subkeyLength) {
|
||||
|
||||
return [key subdataWithRange:NSMakeRange(0, MIN(subkeyLength, key.length))];
|
||||
}
|
||||
|
||||
|
||||
NSData *keyIDForPassword(NSString *password, NSString *username) {
|
||||
|
||||
return keyIDForKey(keyForPassword(password, username));
|
||||
|
@ -202,8 +202,6 @@
|
||||
cancelTitle:nil otherTitles:[PearlStrings get].commonButtonOkay, nil];
|
||||
#endif
|
||||
|
||||
[[UIApplication sharedApplication] setStatusBarHidden:NO withAnimation:UIStatusBarAnimationSlide];
|
||||
|
||||
[super application:application didFinishLaunchingWithOptions:launchOptions];
|
||||
|
||||
inf(@"Started up with device identifier: %@", [PearlKeyChain deviceIdentifier]);
|
||||
@ -419,26 +417,47 @@
|
||||
// Safe Export
|
||||
[self exportShowPasswords:NO];
|
||||
if (buttonIndex_ == [alert_ firstOtherButtonIndex] + 1)
|
||||
// Safe Export
|
||||
// Show Passwords
|
||||
[self exportShowPasswords:YES];
|
||||
} cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:@"Safe Export", @"Show Passwords", nil];
|
||||
} otherTitles:nil];
|
||||
}
|
||||
|
||||
- (void)exportShowPasswords:(BOOL)showPasswords {
|
||||
|
||||
if (![MFMailComposeViewController canSendMail]) {
|
||||
[PearlAlert showAlertWithTitle:@"Cannot Send Mail"
|
||||
message:
|
||||
@"Your device is not yet set up for sending mail.\n"
|
||||
@"Close Master Password, go into Settings and add a Mail account."
|
||||
viewStyle:UIAlertViewStyleDefault
|
||||
initAlert:nil tappedButtonBlock:nil
|
||||
cancelTitle:[PearlStrings get].commonButtonOkay
|
||||
otherTitles:nil];
|
||||
return;
|
||||
}
|
||||
|
||||
NSString *exportedSites = [self exportSitesShowingPasswords:showPasswords];
|
||||
NSString *message;
|
||||
if (showPasswords)
|
||||
message = PearlString(
|
||||
@"Export of %@'s Master Password sites with passwords visible.\n"
|
||||
@"REMINDER: Make sure nobody else sees this file! All passwords are visible!\n",
|
||||
self.activeUser.name);
|
||||
else
|
||||
message = PearlString(
|
||||
@"Backup of %@'s Master Password sites.\n",
|
||||
self.activeUser.name);
|
||||
|
||||
if (showPasswords)
|
||||
message = PearlString(@"Export of Master Password sites with passwords included.\n"
|
||||
@"REMINDER: Make sure nobody else sees this file! Passwords are visible!\n\n\n"
|
||||
@"--\n"
|
||||
@"%@\n"
|
||||
@"Master Password %@, build %@",
|
||||
self.activeUser.name,
|
||||
[PearlInfoPlist get].CFBundleShortVersionString,
|
||||
[PearlInfoPlist get].CFBundleVersion);
|
||||
else
|
||||
message = PearlString(@"Backup of Master Password sites.\n\n\n"
|
||||
@"--\n"
|
||||
@"%@\n"
|
||||
@"Master Password %@, build %@",
|
||||
self.activeUser.name,
|
||||
[PearlInfoPlist get].CFBundleShortVersionString,
|
||||
[PearlInfoPlist get].CFBundleVersion);
|
||||
|
||||
NSDateFormatter *exportDateFormatter = [NSDateFormatter new];
|
||||
[exportDateFormatter setDateFormat:@"yyyy'-'MM'-'DD"];
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="1.1" toolsVersion="2182" systemVersion="11E53" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" initialViewController="KZF-fe-y9n">
|
||||
<dependencies>
|
||||
<deployment defaultVersion="1296" identifier="iOS"/>
|
||||
<development defaultVersion="4200" identifier="xcode"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="1181"/>
|
||||
</dependencies>
|
||||
@ -828,54 +827,6 @@ L4m3P4sSw0rD</string>
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="480"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
</imageView>
|
||||
<view contentMode="scaleToFill" id="7cc-yu-i0m">
|
||||
<rect key="frame" x="20" y="168" width="280" height="88"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
|
||||
<subviews>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Enter your master password:" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="10" id="RhX-bA-EhC">
|
||||
<rect key="frame" x="12" y="0.0" width="256" height="20"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" name="Copperplate" family="Copperplate" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" image="ui_textfield.png" id="ivR-Xl-NrT">
|
||||
<rect key="frame" x="0.0" y="28" width="280" height="60"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
|
||||
<rect key="contentStretch" x="0.25" y="0.25" width="0.49999999999999961" height="0.49999999999999961"/>
|
||||
</imageView>
|
||||
<textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" textAlignment="center" clearsOnBeginEditing="YES" minimumFontSize="17" id="rTR-7Q-X8o">
|
||||
<rect key="frame" x="0.0" y="28" width="280" height="60"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<fontDescription key="fontDescription" name="Copperplate" family="Copperplate" pointSize="36"/>
|
||||
<textInputTraits key="textInputTraits" enablesReturnKeyAutomatically="YES" secureTextEntry="YES"/>
|
||||
<connections>
|
||||
<outlet property="delegate" destination="Nbn-Rv-sP1" id="Y0T-cI-gF1"/>
|
||||
</connections>
|
||||
</textField>
|
||||
<view userInteractionEnabled="NO" contentMode="scaleToFill" id="NvG-0R-eTZ">
|
||||
<rect key="frame" x="35" y="0.0" width="210" height="60"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<subviews>
|
||||
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" image="tip_basic_black.png" id="3se-By-a9W">
|
||||
<rect key="frame" x="0.0" y="0.0" width="210" height="60"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
</imageView>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Incorrect password." textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="10" id="gQ2-mB-BP4">
|
||||
<rect key="frame" x="15" y="0.5" width="180" height="40"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" red="1" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
<color key="shadowColor" white="0.0" alpha="1" colorSpace="calibratedWhite"/>
|
||||
</label>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
|
||||
</view>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
|
||||
</view>
|
||||
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" image="ui_spinner.png" id="27q-lX-0vy">
|
||||
<rect key="frame" x="105" y="30" width="110" height="110"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
@ -904,6 +855,54 @@ L4m3P4sSw0rD</string>
|
||||
<outletCollection property="gestureRecognizers" destination="9WS-yS-aqQ" appends="YES" id="B9k-bg-gqA"/>
|
||||
</connections>
|
||||
</scrollView>
|
||||
<view contentMode="scaleToFill" id="7cc-yu-i0m">
|
||||
<rect key="frame" x="20" y="168" width="280" height="88"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
|
||||
<subviews>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Enter your master password:" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="10" id="RhX-bA-EhC">
|
||||
<rect key="frame" x="12" y="0.0" width="256" height="20"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" name="Copperplate" family="Copperplate" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" image="ui_textfield.png" id="ivR-Xl-NrT">
|
||||
<rect key="frame" x="0.0" y="28" width="280" height="60"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
|
||||
<rect key="contentStretch" x="0.25" y="0.25" width="0.49999999999999961" height="0.49999999999999961"/>
|
||||
</imageView>
|
||||
<textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" textAlignment="center" clearsOnBeginEditing="YES" minimumFontSize="17" id="rTR-7Q-X8o">
|
||||
<rect key="frame" x="10" y="28" width="260" height="60"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="36"/>
|
||||
<textInputTraits key="textInputTraits" enablesReturnKeyAutomatically="YES" secureTextEntry="YES"/>
|
||||
<connections>
|
||||
<outlet property="delegate" destination="Nbn-Rv-sP1" id="Y0T-cI-gF1"/>
|
||||
</connections>
|
||||
</textField>
|
||||
<view userInteractionEnabled="NO" contentMode="scaleToFill" id="NvG-0R-eTZ">
|
||||
<rect key="frame" x="35" y="0.0" width="210" height="60"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<subviews>
|
||||
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" image="tip_basic_black.png" id="3se-By-a9W">
|
||||
<rect key="frame" x="0.0" y="0.0" width="210" height="60"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
</imageView>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Incorrect password." textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="10" id="gQ2-mB-BP4">
|
||||
<rect key="frame" x="15" y="0.5" width="180" height="40"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" red="1" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
<color key="shadowColor" white="0.0" alpha="1" colorSpace="calibratedWhite"/>
|
||||
</label>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
|
||||
</view>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
|
||||
</view>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" alpha="0.0" contentMode="left" text="Maarten Billemont" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" minimumFontSize="10" id="8s0-nT-Aoq">
|
||||
<rect key="frame" x="90" y="289" width="140" height="15"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
@ -942,7 +941,7 @@ L4m3P4sSw0rD</string>
|
||||
<outlet property="deleteTip" destination="DBJ-Qi-ZcF" id="VXD-Zc-UYi"/>
|
||||
<outlet property="nameLabel" destination="0NM-NI-7UR" id="GBg-Ry-sqj"/>
|
||||
<outlet property="oldNameLabel" destination="8s0-nT-Aoq" id="plu-1H-MVc"/>
|
||||
<outlet property="passwordField" destination="rTR-7Q-X8o" id="CDA-iP-kCm"/>
|
||||
<outlet property="passwordField" destination="rTR-7Q-X8o" id="Vsn-uO-4lO"/>
|
||||
<outlet property="passwordTipLabel" destination="gQ2-mB-BP4" id="aHU-tn-duI"/>
|
||||
<outlet property="passwordTipView" destination="NvG-0R-eTZ" id="4Mx-TL-yfu"/>
|
||||
<outlet property="passwordView" destination="7cc-yu-i0m" id="WoF-Ab-PPC"/>
|
||||
|
Loading…
Reference in New Issue
Block a user