Set encrypted profile picture.

Rotate profileKey every-time we encrypt a new profile picture.
This commit is contained in:
Mikunj 2019-11-29 09:18:40 +11:00
parent ba8c3b1859
commit 2e83a2bb85
1 changed files with 25 additions and 139 deletions

View File

@ -405,150 +405,36 @@ typedef void (^ProfileManagerFailureBlock)(NSError *error);
OWSAssertDebug(successBlock);
OWSAssertDebug(failureBlock);
OWSAssertDebug(avatarData == nil || avatarData.length > 0);
[[LKStorageAPI setProfilePicture:avatarData]
.thenOn(dispatch_get_main_queue(), ^(NSString *url) {
successBlock(url);
})
.catchOn(dispatch_get_main_queue(), ^(id result) {
// There appears to be a bug in PromiseKit that sometimes causes catchOn
// to be invoked with the fulfilled promise's value as the error. The below
// is a quick and dirty workaround.
if ([result isKindOfClass:NSString.class]) {
successBlock(result);
} else {
failureBlock(result);
}
}) retainUntilComplete];
/*
// We want to clear the local user's profile avatar as soon as
// we request the upload form, since that request clears our
// avatar on the service.
//
// TODO: Revisit this so that failed profile updates don't leave
// the profile avatar blank, etc.
void (^clearLocalAvatar)(void) = ^{
OWSUserProfile *userProfile = self.localUserProfile;
[userProfile updateWithAvatarUrlPath:nil avatarFileName:nil dbConnection:self.dbConnection completion:nil];
};
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// We always want to encrypt a profile with a new profile key
// This ensures that other users know that our profile picture was updated
OWSAES256Key newProfileKey = [OWSAES256Key generateRandomKey];
NSData *_Nullable encryptedAvatarData;
if (avatarData) {
encryptedAvatarData = [self encryptProfileData:avatarData profileKey:newProfileKey];
OWSAssertDebug(encryptedAvatarData.length > 0);
}
// See: https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-UsingHTTPPOST.html
TSRequest *formRequest = [OWSRequestFactory profileAvatarUploadFormRequest];
[self.networkManager makeRequest:formRequest
success:^(NSURLSessionDataTask *task, id formResponseObject) {
if (avatarData == nil) {
OWSLogDebug(@"successfully cleared avatar");
clearLocalAvatar();
successBlock(nil);
return;
}
if (![formResponseObject isKindOfClass:[NSDictionary class]]) {
OWSProdFail([OWSAnalyticsEvents profileManagerErrorAvatarUploadFormInvalidResponse]);
return failureBlock(
OWSErrorWithCodeDescription(OWSErrorCodeAvatarUploadFailed, @"Avatar upload failed."));
}
NSDictionary *responseMap = formResponseObject;
OWSLogError(@"responseObject: %@", formResponseObject);
NSString *formAcl = responseMap[@"acl"];
if (![formAcl isKindOfClass:[NSString class]] || formAcl.length < 1) {
OWSProdFail([OWSAnalyticsEvents profileManagerErrorAvatarUploadFormInvalidAcl]);
return failureBlock(
OWSErrorWithCodeDescription(OWSErrorCodeAvatarUploadFailed, @"Avatar upload failed."));
}
NSString *formKey = responseMap[@"key"];
if (![formKey isKindOfClass:[NSString class]] || formKey.length < 1) {
OWSProdFail([OWSAnalyticsEvents profileManagerErrorAvatarUploadFormInvalidKey]);
return failureBlock(
OWSErrorWithCodeDescription(OWSErrorCodeAvatarUploadFailed, @"Avatar upload failed."));
}
NSString *formPolicy = responseMap[@"policy"];
if (![formPolicy isKindOfClass:[NSString class]] || formPolicy.length < 1) {
OWSProdFail([OWSAnalyticsEvents profileManagerErrorAvatarUploadFormInvalidPolicy]);
return failureBlock(
OWSErrorWithCodeDescription(OWSErrorCodeAvatarUploadFailed, @"Avatar upload failed."));
}
NSString *formAlgorithm = responseMap[@"algorithm"];
if (![formAlgorithm isKindOfClass:[NSString class]] || formAlgorithm.length < 1) {
OWSProdFail([OWSAnalyticsEvents profileManagerErrorAvatarUploadFormInvalidAlgorithm]);
return failureBlock(
OWSErrorWithCodeDescription(OWSErrorCodeAvatarUploadFailed, @"Avatar upload failed."));
}
NSString *formCredential = responseMap[@"credential"];
if (![formCredential isKindOfClass:[NSString class]] || formCredential.length < 1) {
OWSProdFail([OWSAnalyticsEvents profileManagerErrorAvatarUploadFormInvalidCredential]);
return failureBlock(
OWSErrorWithCodeDescription(OWSErrorCodeAvatarUploadFailed, @"Avatar upload failed."));
}
NSString *formDate = responseMap[@"date"];
if (![formDate isKindOfClass:[NSString class]] || formDate.length < 1) {
OWSProdFail([OWSAnalyticsEvents profileManagerErrorAvatarUploadFormInvalidDate]);
return failureBlock(
OWSErrorWithCodeDescription(OWSErrorCodeAvatarUploadFailed, @"Avatar upload failed."));
}
NSString *formSignature = responseMap[@"signature"];
if (![formSignature isKindOfClass:[NSString class]] || formSignature.length < 1) {
OWSProdFail([OWSAnalyticsEvents profileManagerErrorAvatarUploadFormInvalidSignature]);
return failureBlock(
OWSErrorWithCodeDescription(OWSErrorCodeAvatarUploadFailed, @"Avatar upload failed."));
}
[self.avatarHTTPManager POST:@""
parameters:nil
constructingBodyWithBlock:^(id<AFMultipartFormData> _Nonnull formData) {
NSData * (^formDataForString)(NSString *formString) = ^(NSString *formString) {
return [formString dataUsingEncoding:NSUTF8StringEncoding];
};
// We have to build up the form manually vs. simply passing in a paramaters dict
// because AWS is sensitive to the order of the form params (at least the "key"
// field must occur early on).
// For consistency, all fields are ordered here in a known working order.
[formData appendPartWithFormData:formDataForString(formKey) name:@"key"];
[formData appendPartWithFormData:formDataForString(formAcl) name:@"acl"];
[formData appendPartWithFormData:formDataForString(formAlgorithm) name:@"x-amz-algorithm"];
[formData appendPartWithFormData:formDataForString(formCredential) name:@"x-amz-credential"];
[formData appendPartWithFormData:formDataForString(formDate) name:@"x-amz-date"];
[formData appendPartWithFormData:formDataForString(formPolicy) name:@"policy"];
[formData appendPartWithFormData:formDataForString(formSignature) name:@"x-amz-signature"];
[formData appendPartWithFormData:formDataForString(OWSMimeTypeApplicationOctetStream)
name:@"Content-Type"];
NSData *encryptedAvatarData = [self encryptProfileData:avatarData];
OWSAssertDebug(encryptedAvatarData.length > 0);
[formData appendPartWithFormData:encryptedAvatarData name:@"file"];
OWSLogVerbose(@"constructed body");
}
progress:^(NSProgress *_Nonnull uploadProgress) {
OWSLogVerbose(@"avatar upload progress: %.2f%%", uploadProgress.fractionCompleted * 100);
}
success:^(NSURLSessionDataTask *_Nonnull uploadTask, id _Nullable responseObject) {
OWSLogInfo(@"successfully uploaded avatar with key: %@", formKey);
successBlock(formKey);
}
failure:^(NSURLSessionDataTask *_Nullable uploadTask, NSError *error) {
OWSLogError(@"uploading avatar failed with error: %@", error);
clearLocalAvatar();
return failureBlock(error);
}];
}
failure:^(NSURLSessionDataTask *task, NSError *error) {
// Only clear the local avatar if we have a response. Otherwise, we
// had a network failure and probably didn't reach the service.
if (task.response != nil) {
clearLocalAvatar();
}
OWSLogError(@"Failed to get profile avatar upload form: %@", error);
return failureBlock(error);
[[LKStorageAPI setProfilePicture:encryptedAvatarData]
.thenOn(dispatch_get_main_queue(), ^(NSString *url) {
[self.localUserProfile updateWithProfileKey:newProfileKey dbConnection:self.dbConnection completion:^{
successBlock(url);
}];
})
.catchOn(dispatch_get_main_queue(), ^(id result) {
// There appears to be a bug in PromiseKit that sometimes causes catchOn
// to be invoked with the fulfilled promise's value as the error. The below
// is a quick and dirty workaround.
if ([result isKindOfClass:NSString.class]) {
[self.localUserProfile updateWithProfileKey:newProfileKey dbConnection:self.dbConnection completion:^{
successBlock(result);
}];
} else {
failureBlock(result);
}
}) retainUntilComplete];
});
*/
}
- (void)updateServiceWithProfileName:(nullable NSString *)localProfileName