Merge branch 'dev' into redesign

This commit is contained in:
Niels Andriesse 2019-12-03 13:21:58 +11:00
commit c3ccd08ace
21 changed files with 378 additions and 378 deletions

View File

@ -4166,7 +4166,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 22;
CURRENT_PROJECT_VERSION = 23;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = SUQ8J2PCT7;
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
@ -4180,7 +4180,7 @@
INFOPLIST_FILE = SignalShareExtension/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
MARKETING_VERSION = 1.5.0;
MARKETING_VERSION = 1.5.1;
MTL_ENABLE_DEBUG_INFO = YES;
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.share-extension";
PRODUCT_NAME = "$(TARGET_NAME)";
@ -4228,7 +4228,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 22;
CURRENT_PROJECT_VERSION = 23;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = SUQ8J2PCT7;
ENABLE_NS_ASSERTIONS = NO;
@ -4247,7 +4247,7 @@
INFOPLIST_FILE = SignalShareExtension/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
MARKETING_VERSION = 1.5.0;
MARKETING_VERSION = 1.5.1;
MTL_ENABLE_DEBUG_INFO = NO;
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.share-extension";
PRODUCT_NAME = "$(TARGET_NAME)";
@ -4301,7 +4301,7 @@
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
MARKETING_VERSION = 1.5.0;
MARKETING_VERSION = 1.5.1;
MTL_ENABLE_DEBUG_INFO = YES;
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.utilities";
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
@ -4375,7 +4375,7 @@
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
MARKETING_VERSION = 1.5.0;
MARKETING_VERSION = 1.5.1;
MTL_ENABLE_DEBUG_INFO = NO;
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.utilities";
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
@ -4560,7 +4560,7 @@
CODE_SIGN_ENTITLEMENTS = Signal/Signal.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CURRENT_PROJECT_VERSION = 22;
CURRENT_PROJECT_VERSION = 23;
DEVELOPMENT_TEAM = SUQ8J2PCT7;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
@ -4595,7 +4595,7 @@
"$(SRCROOT)",
);
LLVM_LTO = NO;
MARKETING_VERSION = 1.5.0;
MARKETING_VERSION = 1.5.1;
OTHER_LDFLAGS = "$(inherited)";
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger";
PRODUCT_NAME = "Loki Messenger";
@ -4627,7 +4627,7 @@
CODE_SIGN_ENTITLEMENTS = Signal/Signal.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CURRENT_PROJECT_VERSION = 22;
CURRENT_PROJECT_VERSION = 23;
DEVELOPMENT_TEAM = SUQ8J2PCT7;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
@ -4662,7 +4662,7 @@
"$(SRCROOT)",
);
LLVM_LTO = NO;
MARKETING_VERSION = 1.5.0;
MARKETING_VERSION = 1.5.1;
OTHER_LDFLAGS = "$(inherited)";
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger";
PRODUCT_NAME = "Loki Messenger";

View File

@ -314,6 +314,7 @@
- (UITableViewCell *)profileHeaderCell
{
NSString *masterDeviceHexEncodedPublicKey = [NSUserDefaults.standardUserDefaults stringForKey:@"masterDeviceHexEncodedPublicKey"];
NSString *hexEncodedPublicKey = masterDeviceHexEncodedPublicKey ?: TSAccountManager.localNumber;
BOOL isMasterDevice = (masterDeviceHexEncodedPublicKey == nil);
UITableViewCell *cell = [OWSTableItem newCell];
@ -321,7 +322,7 @@
cell.contentView.preservesSuperviewLayoutMargins = YES;
cell.selectionStyle = UITableViewCellSelectionStyleNone;
UIImage *_Nullable localProfileAvatarImage = [OWSProfileManager.sharedManager localProfileAvatarImage];
UIImage *_Nullable localProfileAvatarImage = [OWSProfileManager.sharedManager profileAvatarForRecipientId:hexEncodedPublicKey];
UIImage *avatarImage = (localProfileAvatarImage
?: [[[OWSContactAvatarBuilder alloc] initForLocalUserWithDiameter:kLargeAvatarSize] buildDefaultImage]);
OWSAssertDebug(avatarImage);
@ -334,7 +335,7 @@
[avatarView autoSetDimension:ALDimensionHeight toSize:kLargeAvatarSize];
avatarView.contactID = OWSIdentityManager.sharedManager.identityKeyPair.hexEncodedPublicKey;
if (isMasterDevice && !localProfileAvatarImage) {
if (isMasterDevice && !OWSProfileManager.sharedManager.localProfileAvatarImage) {
UIImage *cameraImage = [UIImage imageNamed:@"settings-avatar-camera"];
UIImageView *cameraImageView = [[UIImageView alloc] initWithImage:cameraImage];
[cell.contentView addSubview:cameraImageView];
@ -348,7 +349,7 @@
[nameView autoPinLeadingToTrailingEdgeOfView:avatarView offset:16.f];
UILabel *titleLabel = [UILabel new];
NSString *_Nullable localProfileName = [OWSProfileManager.sharedManager localProfileName];
NSString *_Nullable localProfileName = [OWSProfileManager.sharedManager profileNameForRecipientId:hexEncodedPublicKey];
if (localProfileName.length > 0) {
titleLabel.text = localProfileName;
titleLabel.textColor = [Theme primaryColor];
@ -370,7 +371,6 @@
UILabel *subtitleLabel = [UILabel new];
subtitleLabel.textColor = [Theme secondaryColor];
subtitleLabel.font = [UIFont ows_regularFontWithSize:kSubtitlePointSize];
NSString *hexEncodedPublicKey = masterDeviceHexEncodedPublicKey ?: TSAccountManager.localNumber;
subtitleLabel.attributedText = [[NSAttributedString alloc] initWithString:hexEncodedPublicKey];
subtitleLabel.lineBreakMode = NSLineBreakByTruncatingTail;
[nameView addSubview:subtitleLabel];

View File

@ -58,8 +58,7 @@ NS_ASSUME_NONNULL_BEGIN
[self.delegate clearAvatar];
}];
// TODO: enable this once we support removing avatars (as opposed to replacing)
// [actionSheet addAction:clearAction];
[actionSheet addAction:clearAction];
}
[self.delegate.fromViewController presentAlert:actionSheet];

View File

@ -759,7 +759,9 @@ typedef NS_ENUM(NSInteger, HomeViewControllerSection) {
UIBarButtonItem *settingsButton;
if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(11, 0)) {
const NSUInteger kAvatarSize = 28;
UIImage *_Nullable localProfileAvatarImage = [OWSProfileManager.sharedManager localProfileAvatarImage];
NSString *masterDeviceHexEncodedPublicKey = [NSUserDefaults.standardUserDefaults stringForKey:@"masterDeviceHexEncodedPublicKey"];
NSString *hexEncodedPublicKey = masterDeviceHexEncodedPublicKey ?: TSAccountManager.localNumber;
UIImage *_Nullable localProfileAvatarImage = [OWSProfileManager.sharedManager profileAvatarForRecipientId:hexEncodedPublicKey];
UIImage *avatarImage = (localProfileAvatarImage
?: [[[OWSContactAvatarBuilder alloc] initForLocalUserWithDiameter:kAvatarSize] buildDefaultImage]);
OWSAssertDebug(avatarImage);

View File

@ -83,8 +83,6 @@ extern const NSUInteger kOWSProfileManager_MaxAvatarDiameter;
profileNameEncrypted:(nullable NSData *)profileNameEncrypted
avatarUrlPath:(nullable NSString *)avatarUrlPath;
- (void)updateProfileForContactWithID:(NSString *)contactID displayName:(NSString *)displayName profilePictureURL:(NSString *)profilePictureURL with:(YapDatabaseReadWriteTransaction *)transaction;
#pragma mark - User Interface
- (void)presentAddThreadToProfileWhitelist:(TSThread *)thread

View File

@ -270,6 +270,7 @@ typedef void (^ProfileManagerFailureBlock)(NSError *error);
void (^tryToUpdateService)(NSString *_Nullable, NSString *_Nullable) = ^(
NSString *_Nullable avatarUrlPath, NSString *_Nullable avatarFileName) {
[self updateServiceWithProfileName:profileName
avatarUrl:avatarUrlPath
success:^{
OWSUserProfile *userProfile = self.localUserProfile;
OWSAssertDebug(userProfile);
@ -340,11 +341,6 @@ typedef void (^ProfileManagerFailureBlock)(NSError *error);
}
}
- (void)updateUserProfileWithDisplayName:(nullable NSString*)displayName profilePictureURL:(nullable NSString*)profilePictureURL transaction:(YapDatabaseReadWriteTransaction *)transaction
{
[self updateLocalProfileName:displayName avatarImage:nil success:^{ } failure:^{ }];
}
- (nullable NSString *)profilePictureURL
{
return self.localUserProfile.avatarUrlPath;
@ -405,153 +401,45 @@ 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];
// 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);
if (avatarData) {
NSData *encryptedAvatarData = [self encryptProfileData:avatarData profileKey:newProfileKey];
OWSAssertDebug(encryptedAvatarData.length > 0);
[[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);
}];
}
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();
} else {
failureBlock(result);
}
OWSLogError(@"Failed to get profile avatar upload form: %@", error);
return failureBlock(error);
}) retainUntilComplete];
} else {
// Update our profile key and set the url to nil if avatar data is nil
[self.localUserProfile updateWithProfileKey:newProfileKey dbConnection:self.dbConnection completion:^{
successBlock(nil);
}];
}
});
*/
}
- (void)updateServiceWithProfileName:(nullable NSString *)localProfileName
avatarUrl:(nullable NSString *)avatarURL
success:(void (^)(void))successBlock
failure:(ProfileManagerFailureBlock)failureBlock {
OWSAssertDebug(successBlock);
@ -566,11 +454,16 @@ typedef void (^ProfileManagerFailureBlock)(NSError *error);
for (NSString *server in servers) {
[[LKPublicChatAPI setDisplayName:localProfileName on:server] retainUntilComplete];
[[LKPublicChatAPI setProfilePictureURL:avatarURL usingProfileKey:self.localProfileKey.keyData on:server] retainUntilComplete];
}
successBlock();
}
- (void)updateServiceWithProfileName:(nullable NSString *)localProfileName avatarUrl:(nullable NSString *)avatarURL {
[self updateServiceWithProfileName:localProfileName avatarUrl:avatarURL success:^{} failure:^(NSError * _Nonnull error) {}];
}
- (void)fetchLocalUsersProfile
{
OWSAssertIsOnMainThread();
@ -741,6 +634,7 @@ typedef void (^ProfileManagerFailureBlock)(NSError *error);
}
return [AnyPromise promiseWithResolverBlock:^(PMKResolver resolve) {
[self updateServiceWithProfileName:oldProfileName
avatarUrl:localUserProfile.avatarUrlPath
success:^{
OWSLogInfo(@"Update to profile name succeeded.");
@ -1021,6 +915,8 @@ typedef void (^ProfileManagerFailureBlock)(NSError *error);
TSGroupThread *groupThread = (TSGroupThread *)thread;
NSData *groupId = groupThread.groupModel.groupId;
return [self isGroupIdInProfileWhitelist:groupId];
} else if (thread.friendRequestStatus == LKThreadFriendRequestStatusFriends) {
return true;
} else {
NSString *recipientId = thread.contactIdentifier;
return [self isUserInProfileWhitelist:recipientId];
@ -1056,7 +952,7 @@ typedef void (^ProfileManagerFailureBlock)(NSError *error);
}];
}
- (void)setProfileKeyData:(NSData *)profileKeyData forRecipientId:(NSString *)recipientId
- (void)setProfileKeyData:(NSData *)profileKeyData forRecipientId:(NSString *)recipientId avatarURL:(nullable NSString *)avatarURL
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
OWSAES256Key *_Nullable profileKey = [OWSAES256Key keyWithData:profileKeyData];
@ -1064,28 +960,35 @@ typedef void (^ProfileManagerFailureBlock)(NSError *error);
OWSFailDebug(@"Failed to make profile key for key data");
return;
}
OWSUserProfile *userProfile =
[OWSUserProfile getOrBuildUserProfileForRecipientId:recipientId dbConnection:self.dbConnection];
OWSUserProfile *userProfile = [OWSUserProfile getOrBuildUserProfileForRecipientId:recipientId dbConnection:self.dbConnection];
OWSAssertDebug(userProfile);
if (userProfile.profileKey && [userProfile.profileKey.keyData isEqual:profileKey.keyData]) {
// Ignore redundant update.
return;
}
[userProfile clearWithProfileKey:profileKey
dbConnection:self.dbConnection
completion:^{
dispatch_async(dispatch_get_main_queue(), ^{
[self.udManager setUnidentifiedAccessMode:UnidentifiedAccessModeUnknown
recipientId:recipientId];
[self fetchProfileForRecipientId:recipientId];
});
}];
dispatch_async(dispatch_get_main_queue(), ^{
[self.udManager setUnidentifiedAccessMode:UnidentifiedAccessModeUnknown
recipientId:recipientId];
[userProfile updateWithAvatarUrlPath:avatarURL avatarFileName:nil dbConnection:self.dbConnection completion:^{
[self downloadAvatarForUserProfile:userProfile];
}];
// [self fetchProfileForRecipientId:recipientId];
});
}];
});
}
- (void)setProfileKeyData:(NSData *)profileKeyData forRecipientId:(NSString *)recipientId
{
[self setProfileKeyData:profileKeyData forRecipientId:recipientId avatarURL:nil];
}
- (nullable NSData *)profileKeyDataForRecipientId:(NSString *)recipientId
{
return [self profileKeyForRecipientId:recipientId].keyData;
@ -1165,14 +1068,16 @@ typedef void (^ProfileManagerFailureBlock)(NSError *error);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
if (userProfile.avatarUrlPath.length < 1) {
OWSFailDebug(@"Malformed avatar URL: %@", userProfile.avatarUrlPath);
OWSLogDebug(@"Skipping downloading avatar for %@ because url is not set", userProfile.recipientId);
return;
}
NSString *_Nullable avatarUrlPathAtStart = userProfile.avatarUrlPath;
if (userProfile.avatarUrlPath.length < 1) {
if (userProfile.profileKey.keyData.length < 1 || userProfile.avatarUrlPath.length < 1) {
return;
}
OWSAES256Key *profileKeyAtStart = userProfile.profileKey;
NSString *fileName = [self generateAvatarFilename];
NSString *filePath = [OWSUserProfile profileAvatarFilepathWithFilename:fileName];
@ -1191,7 +1096,6 @@ typedef void (^ProfileManagerFailureBlock)(NSError *error);
NSString *tempDirectory = OWSTemporaryDirectory();
NSString *tempFilePath = [tempDirectory stringByAppendingPathComponent:fileName];
NSString *profilePictureURL = userProfile.avatarUrlPath;
NSError *serializationError;
NSMutableURLRequest *request =
@ -1205,9 +1109,8 @@ typedef void (^ProfileManagerFailureBlock)(NSError *error);
}
NSURLSession* session = [NSURLSession sharedSession];
NSURLSessionTask* downloadTask = [session downloadTaskWithRequest:request completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) {
@synchronized(self.currentAvatarDownloads)
{
[self.currentAvatarDownloads removeObject:userProfile.recipientId];
@ -1219,32 +1122,68 @@ typedef void (^ProfileManagerFailureBlock)(NSError *error);
}
NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *fileURL = [NSURL fileURLWithPath:filePath];
NSURL *tempFileUrl = [NSURL fileURLWithPath:tempFilePath];
NSError *moveError;
if (![fileManager moveItemAtURL:location toURL:fileURL error:&moveError]) {
if (![fileManager moveItemAtURL:location toURL:tempFileUrl error:&moveError]) {
OWSLogError(@"MoveItemAtURL for avatar failed: %@", moveError);
return;
}
NSData *_Nullable encryptedData = (error ? nil : [NSData dataWithContentsOfFile:tempFilePath]);
NSData *_Nullable decryptedData = [self decryptProfileData:encryptedData profileKey:profileKeyAtStart];
UIImage *_Nullable image = nil;
if (decryptedData) {
BOOL success = [decryptedData writeToFile:filePath atomically:YES];
if (success) {
image = [UIImage imageWithContentsOfFile:filePath];
}
}
OWSUserProfile *latestUserProfile = [OWSUserProfile getOrBuildUserProfileForRecipientId:userProfile.recipientId dbConnection:self.dbConnection];
UIImage *image = [UIImage imageWithContentsOfFile:[fileURL path]];
if (image) {
dispatch_async(dispatch_get_main_queue(), ^{
if (latestUserProfile.profileKey.keyData.length < 1
|| ![latestUserProfile.profileKey isEqual:userProfile.profileKey]) {
OWSLogWarn(@"Ignoring avatar download for obsolete user profile.");
} else if (![avatarUrlPathAtStart isEqualToString:latestUserProfile.avatarUrlPath]) {
OWSLogInfo(@"avatar url has changed during download");
if (latestUserProfile.avatarUrlPath.length > 0) {
[self downloadAvatarForUserProfile:latestUserProfile];
}
} else if (error) {
if ([response isKindOfClass:NSHTTPURLResponse.class]
&& ((NSHTTPURLResponse *)response).statusCode == 403) {
OWSLogInfo(@"no avatar for: %@", userProfile.recipientId);
} else {
OWSLogError(@"avatar download for %@ failed with error: %@", userProfile.recipientId, error);
}
} else if (!encryptedData) {
OWSLogError(@"avatar encrypted data for %@ could not be read.", userProfile.recipientId);
} else if (!decryptedData) {
OWSLogError(@"avatar data for %@ could not be decrypted.", userProfile.recipientId);
} else if (!image) {
OWSLogError(@"avatar image for %@ could not be loaded with error: %@", userProfile.recipientId, error);
} else {
[self updateProfileAvatarCache:image filename:fileName];
[self updateProfileAvatarCache:image filename:fileName];
OWSUserProfile *latestUserProfile =
[OWSUserProfile getOrBuildUserProfileForRecipientId:userProfile.recipientId
dbConnection:self.dbConnection];
[latestUserProfile updateWithAvatarFileName:fileName dbConnection:self.dbConnection completion:^{
[[NSNotificationCenter defaultCenter]
postNotificationNameAsync:OWSContactsManagerSignalAccountsDidChangeNotification
object:nil];
}];
});
[latestUserProfile updateWithAvatarFileName:fileName dbConnection:self.dbConnection completion:^{
[[NSNotificationCenter defaultCenter]
postNotificationNameAsync:OWSContactsManagerSignalAccountsDidChangeNotification
object:nil];
}];
}
// If we're updating the profile that corresponds to our local number,
// update the local profile as well.
if ([self.tsAccountManager.localNumber isEqualToString:userProfile.recipientId]) {
OWSUserProfile *localUserProfile = self.localUserProfile;
OWSAssertDebug(localUserProfile);
[localUserProfile updateWithAvatarFileName:fileName dbConnection:self.dbConnection completion:nil];
[self updateProfileAvatarCache:image filename:fileName];
}
OWSAssertDebug(backgroundTask);
backgroundTask = nil;
}];
[downloadTask resume];
@ -1307,17 +1246,10 @@ typedef void (^ProfileManagerFailureBlock)(NSError *error);
});
}
- (void)updateProfileForContactWithID:(NSString *)contactID displayName:(NSString *)displayName profilePictureURL:(NSString *)profilePictureURL with:(YapDatabaseReadWriteTransaction *)transaction
- (void)updateProfileForContactWithID:(NSString *)contactID displayName:(NSString *)displayName with:(YapDatabaseReadWriteTransaction *)transaction
{
OWSUserProfile *userProfile = [OWSUserProfile getOrBuildUserProfileForRecipientId:contactID transaction:transaction];
NSString *oldProfilePictureURL = userProfile.avatarUrlPath;
// Note: we keep using the old file name until we have the new one
// (otherwise the profile picuture would disspear for a short time)
NSString *oldAvatarFileName = userProfile.avatarFileName;
[userProfile updateWithProfileName:displayName avatarUrlPath:profilePictureURL avatarFileName:oldAvatarFileName transaction:transaction completion:nil];
if (profilePictureURL && ![oldProfilePictureURL isEqual:profilePictureURL]) {
[self downloadAvatarForUserProfile:userProfile];
}
[userProfile updateWithProfileName:displayName avatarUrlPath:userProfile.avatarUrlPath avatarFileName:userProfile.avatarFileName transaction:transaction completion:nil];
}
- (BOOL)isNullableDataEqual:(NSData *_Nullable)left toData:(NSData *_Nullable)right

View File

@ -73,6 +73,10 @@ extern NSString *const kLocalProfileUniqueId;
dbConnection:(YapDatabaseConnection *)dbConnection
completion:(nullable OWSUserProfileCompletion)completion;
- (void)updateWithProfileKey:(OWSAES256Key *)profileKey
transaction:(YapDatabaseReadWriteTransaction *)transaction
completion:(nullable OWSUserProfileCompletion)completion;
- (void)clearWithProfileKey:(OWSAES256Key *)profileKey
dbConnection:(YapDatabaseConnection *)dbConnection
completion:(nullable OWSUserProfileCompletion)completion;

View File

@ -234,8 +234,9 @@ NSString *const kLocalProfileUniqueId = @"kLocalProfileUniqueId";
if (!didChange) {
return;
}
BOOL isLocalUserProfile = [self.recipientId isEqualToString:kLocalProfileUniqueId];
NSString *masterDeviceHexEncodedPublicKey = [NSUserDefaults.standardUserDefaults stringForKey:@"masterDeviceHexEncodedPublicKey"];
BOOL isLocalUserProfile = [self.recipientId isEqualToString:kLocalProfileUniqueId] || (masterDeviceHexEncodedPublicKey != nil && [self.recipientId isEqualToString:masterDeviceHexEncodedPublicKey]);
dispatch_async(dispatch_get_main_queue(), ^{
if (isLocalUserProfile) {
@ -350,7 +351,7 @@ NSString *const kLocalProfileUniqueId = @"kLocalProfileUniqueId";
{
[self applyChanges:^(OWSUserProfile *userProfile) {
[userProfile setProfileKey:profileKey];
[userProfile setProfileName:nil];
// [userProfile setProfileName:nil]; - Loki disabled until we include profile name inside the encrypted profile from the url
// Always setAvatarUrlPath: before you setAvatarFileName: since
// setAvatarUrlPath: may clear the avatar filename.
[userProfile setAvatarUrlPath:nil];

View File

@ -93,7 +93,8 @@ NS_ASSUME_NONNULL_BEGIN
- (nullable UIImage *)buildSavedImage
{
if ([self.signalId isEqualToString:TSAccountManager.localNumber]) {
NSString *masterDeviceHexEncodedPublicKey = [NSUserDefaults.standardUserDefaults stringForKey:@"masterDeviceHexEncodedPublicKey"];
if ([self.signalId isEqualToString:TSAccountManager.localNumber] || (masterDeviceHexEncodedPublicKey != nil && [self.signalId isEqualToString:masterDeviceHexEncodedPublicKey])) {
NSString *noteToSelfCacheKey = [NSString stringWithFormat:@"%@:note-to-self", self.cacheKey];
UIImage *_Nullable cachedAvatar =
[OWSContactAvatarBuilder.contactsManager.avatarCache imageForKey:noteToSelfCacheKey

View File

@ -211,8 +211,10 @@ ConversationColorName const kConversationColorName_Default = ConversationColorNa
if (!IsNoteToSelfEnabled()) {
return NO;
}
return (!self.isGroupThread && self.contactIdentifier != nil &&
[self.contactIdentifier isEqualToString:self.tsAccountManager.localNumber]);
NSString *localNumber = self.tsAccountManager.localNumber;
NSString *masterDeviceHexEncodedPublicKey = [NSUserDefaults.standardUserDefaults stringForKey:@"masterDeviceHexEncodedPublicKey"];
bool isOurNumber = [self.contactIdentifier isEqualToString:localNumber] || (masterDeviceHexEncodedPublicKey != nil && [self.contactIdentifier isEqualToString:masterDeviceHexEncodedPublicKey]);
return (!self.isGroupThread && self.contactIdentifier != nil && isOurNumber);
}
#pragma mark - To be subclassed.

View File

@ -65,7 +65,16 @@ NS_ASSUME_NONNULL_BEGIN
- (BOOL)writeUInt32:(UInt32)value {
NSData *data = [[NSData alloc] initWithBytes:&value length:sizeof(value)];
return [self writeData:data];
// Both Android and desktop seem to like this better
const char *bytes = data.bytes;
char *reversedBytes = malloc(sizeof(char) * data.length);
int i = data.length - 1;
for (int j = 0; j < data.length; j++) {
reversedBytes[i] = bytes[j];
i = i - 1;
}
NSData *reversedData = [NSData dataWithBytes:reversedBytes length:data.length];
return [self writeData:reversedData];
}
- (BOOL)writeVariableLengthUInt32:(UInt32)value

View File

@ -12,6 +12,7 @@ public final class LokiPublicChatAPI : LokiDotNetAPI {
// MARK: Public Chat
private static let channelInfoType = "net.patter-app.settings"
private static let attachmentType = "net.app.core.oembed"
public static let avatarType = "network.loki.messenger.avatar"
@objc public static let publicChatMessageType = "network.loki.messenger.publicChat"
@objc public static let defaultChats: [LokiPublicChat] = {
@ -98,8 +99,12 @@ public final class LokiPublicChatAPI : LokiDotNetAPI {
print("[Loki] Couldn't parse message for public chat channel with ID: \(channel) on server: \(server) from: \(message).")
return nil
}
let profilePictureURL = value["avatar"] as? String ?? nil
var avatar: LokiPublicChatMessage.Avatar? = nil
let displayName = user["name"] as? String ?? NSLocalizedString("Anonymous", comment: "")
if let userAnnotations = user["annotations"] as? [JSON], let avatarAnnotation = userAnnotations.first(where: { $0["type"] as? String == avatarType }),
let avatarValue = avatarAnnotation["value"] as? JSON, let profileKeyString = avatarValue["profileKey"] as? String, let profileKey = Data(base64Encoded: profileKeyString), let url = avatarValue["url"] as? String {
avatar = LokiPublicChatMessage.Avatar(profileKey: profileKey, url: url)
}
let lastMessageServerID = getLastMessageServerID(for: channel, on: server)
if serverID > (lastMessageServerID ?? 0) { setLastMessageServerID(for: channel, on: server, to: serverID) }
let quote: LokiPublicChatMessage.Quote?
@ -128,7 +133,7 @@ public final class LokiPublicChatAPI : LokiDotNetAPI {
}
return LokiPublicChatMessage.Attachment(kind: kind, server: server, serverID: serverID, contentType: contentType, size: size, fileName: fileName, flags: flags, width: width, height: height, caption: caption, url: url, linkPreviewURL: linkPreviewURL, linkPreviewTitle: linkPreviewTitle)
}
let result = LokiPublicChatMessage(serverID: serverID, hexEncodedPublicKey: hexEncodedPublicKey, displayName: displayName, avatar: profilePictureURL, body: body, type: publicChatMessageType, timestamp: timestamp, quote: quote, attachments: attachments, signature: signature)
let result = LokiPublicChatMessage(serverID: serverID, hexEncodedPublicKey: hexEncodedPublicKey, displayName: displayName, avatar: avatar, body: body, type: publicChatMessageType, timestamp: timestamp, quote: quote, attachments: attachments, signature: signature)
guard result.hasValidSignature() else {
print("[Loki] Ignoring public chat message with invalid signature.")
return nil
@ -287,6 +292,24 @@ public final class LokiPublicChatAPI : LokiDotNetAPI {
}.retryingIfNeeded(maxRetryCount: 3)
}
public static func setProfilePictureURL(to url: String?, using profileKey: Data, on server: String) -> Promise<Void> {
print("[Loki] Updating profile picture on server: \(server).")
return getAuthToken(for: server).then(on: DispatchQueue.global()) { token -> Promise<Void> in
var annotation: JSON = [ "type" : avatarType ]
if let url = url {
annotation["value"] = [ "profileKey" : profileKey.base64EncodedString(), "url" : url ]
}
let parameters: JSON = [ "annotations" : [ annotation ] ]
let url = URL(string: "\(server)/users/me")!
let request = TSRequest(url: url, method: "PATCH", parameters: parameters)
request.allHTTPHeaderFields = [ "Content-Type" : "application/json", "Authorization" : "Bearer \(token)" ]
return TSNetworkManager.shared().perform(request, withCompletionQueue: DispatchQueue.global()).map { _ in }.recover(on: DispatchQueue.global()) { error in
print("[Loki] Couldn't update profile picture due to error: \(error).")
throw error
}
}.retryingIfNeeded(maxRetryCount: 3)
}
public static func getInfo(for channel: UInt64, on server: String) -> Promise<LokiPublicChatInfo> {
let url = URL(string: "\(server)/channels/\(channel)?include_annotations=1")!
let request = TSRequest(url: url)
@ -329,4 +352,9 @@ public final class LokiPublicChatAPI : LokiDotNetAPI {
public static func objc_setDisplayName(to newDisplayName: String?, on server: String) -> AnyPromise {
return AnyPromise.from(setDisplayName(to: newDisplayName, on: server))
}
@objc(setProfilePictureURL:usingProfileKey:on:)
public static func objc_setProfilePicture(to url: String?, using profileKey: Data, on server: String) -> AnyPromise {
return AnyPromise.from(setProfilePictureURL(to: url, using: profileKey, on: server))
}
}

View File

@ -5,7 +5,7 @@ public final class LokiPublicChatMessage : NSObject {
public let serverID: UInt64?
public let hexEncodedPublicKey: String
public let displayName: String
public let avatar: String?
public let avatar: Avatar?
public let body: String
/// - Note: Expressed as milliseconds since 00:00:00 UTC on 1 January 1970.
public let timestamp: UInt64
@ -22,6 +22,11 @@ public final class LokiPublicChatMessage : NSObject {
private let attachmentType = "net.app.core.oembed"
// MARK: Types
public struct Avatar {
public let profileKey: Data
public let url: String
}
public struct Quote {
public let quotedMessageTimestamp: UInt64
public let quoteeHexEncodedPublicKey: String
@ -67,7 +72,7 @@ public final class LokiPublicChatMessage : NSObject {
}
// MARK: Initialization
public init(serverID: UInt64?, hexEncodedPublicKey: String, displayName: String, avatar: String?, body: String, type: String, timestamp: UInt64, quote: Quote?, attachments: [Attachment], signature: Signature?) {
public init(serverID: UInt64?, hexEncodedPublicKey: String, displayName: String, avatar: Avatar?, body: String, type: String, timestamp: UInt64, quote: Quote?, attachments: [Attachment], signature: Signature?) {
self.serverID = serverID
self.hexEncodedPublicKey = hexEncodedPublicKey
self.displayName = displayName
@ -81,7 +86,7 @@ public final class LokiPublicChatMessage : NSObject {
super.init()
}
@objc public convenience init(hexEncodedPublicKey: String, displayName: String, avatar: String?, body: String, type: String, timestamp: UInt64, quotedMessageTimestamp: UInt64, quoteeHexEncodedPublicKey: String?, quotedMessageBody: String?, quotedMessageServerID: UInt64, signatureData: Data?, signatureVersion: UInt64) {
@objc public convenience init(hexEncodedPublicKey: String, displayName: String, body: String, type: String, timestamp: UInt64, quotedMessageTimestamp: UInt64, quoteeHexEncodedPublicKey: String?, quotedMessageBody: String?, quotedMessageServerID: UInt64, signatureData: Data?, signatureVersion: UInt64) {
let quote: Quote?
if quotedMessageTimestamp != 0, let quoteeHexEncodedPublicKey = quoteeHexEncodedPublicKey, let quotedMessageBody = quotedMessageBody {
let quotedMessageServerID = (quotedMessageServerID != 0) ? quotedMessageServerID : nil
@ -95,7 +100,7 @@ public final class LokiPublicChatMessage : NSObject {
} else {
signature = nil
}
self.init(serverID: nil, hexEncodedPublicKey: hexEncodedPublicKey, displayName: displayName, avatar: avatar, body: body, type: type, timestamp: timestamp, quote: quote, attachments: [], signature: signature)
self.init(serverID: nil, hexEncodedPublicKey: hexEncodedPublicKey, displayName: displayName, avatar: nil, body: body, type: type, timestamp: timestamp, quote: quote, attachments: [], signature: signature)
}
// MARK: Crypto

View File

@ -122,11 +122,12 @@ public final class LokiPublicChatPoller : NSObject {
dataMessage.setPreview([ try! signalLinkPreview.build() ])
}
let profile = SSKProtoDataMessageLokiProfile.builder()
if let profilePicture = message.avatar {
profile.setProfilePicture(profilePicture)
profile.setDisplayName(message.displayName)
dataMessage.setProfile(try! profile.build())
profile.setDisplayName(message.displayName)
if let avatar = message.avatar {
profile.setProfilePicture(avatar.url)
dataMessage.setProfileKey(avatar.profileKey)
}
dataMessage.setProfile(try! profile.build())
dataMessage.setTimestamp(message.timestamp)
dataMessage.setGroup(try! groupContext.build())
if let quote = message.quote {
@ -162,6 +163,14 @@ public final class LokiPublicChatPoller : NSObject {
transaction.setObject(senderDisplayName, forKey: senderHexEncodedPublicKey, inCollection: publicChat.id)
let messageServerID = message.serverID
SSKEnvironment.shared.messageManager.throws_processEnvelope(try! envelope.build(), plaintextData: try! content.build().serializedData(), wasReceivedByUD: false, transaction: transaction, serverID: messageServerID ?? 0)
// If we got a message from our master device then we should use its profile picture
if let avatar = message.avatar, masterHexEncodedPublicKey == message.hexEncodedPublicKey {
if (message.displayName.count > 0) {
SSKEnvironment.shared.profileManager.updateProfileForContact(withID: masterHexEncodedPublicKey!, displayName: message.displayName, with: transaction)
}
SSKEnvironment.shared.profileManager.updateService(withProfileName: message.displayName, avatarUrl: avatar.url)
SSKEnvironment.shared.profileManager.setProfileKeyData(avatar.profileKey, forRecipientId: masterHexEncodedPublicKey!, avatarURL: avatar.url)
}
}
}
}

View File

@ -10,10 +10,14 @@
var index = 0
var result: [String] = []
while index < data.endIndex {
let uncheckedSize: UInt32? = try? data[index..<(index+4)].withUnsafeBytes { $0.pointee }
guard let size = uncheckedSize else { break }
var uncheckedSize: UInt32? = try? data[index..<(index+4)].withUnsafeBytes { $0.pointee }
if let size = uncheckedSize, size >= data.count, let intermediate = try? data[index..<(index+4)].reversed() {
uncheckedSize = Data(intermediate).withUnsafeBytes { $0.pointee }
}
guard let size = uncheckedSize, size < data.count else { break }
let sizeAsInt = Int(size)
index += 4
guard index + sizeAsInt < data.count else { break }
let protoAsData = data[index..<(index+sizeAsInt)]
guard let proto = try? SSKProtoContactDetails.parseData(protoAsData) else { break }
index += sizeAsInt

View File

@ -1,6 +1,9 @@
#import "LKDeviceLinkMessage.h"
#import "OWSIdentityManager.h"
#import "OWSPrimaryStorage+Loki.h"
#import "ProfileManagerProtocol.h"
#import "ProtoUtils.h"
#import "SSKEnvironment.h"
#import "SignalRecipient.h"
#import <SignalCoreKit/NSData+OWS.h>
#import <SignalCoreKit/NSDate+OWS.h>
@ -34,8 +37,8 @@
- (SSKProtoContentBuilder *)prepareCustomContentBuilder:(SignalRecipient *)recipient {
SSKProtoContentBuilder *contentBuilder = SSKProtoContent.builder;
NSError *error;
// Build the pre key bundle message
if (self.kind == LKDeviceLinkMessageKindRequest) {
// Build the pre key bundle message
PreKeyBundle *preKeyBundle = [OWSPrimaryStorage.sharedManager generatePreKeyBundleForContact:recipient.recipientId];
SSKProtoPrekeyBundleMessageBuilder *preKeyBundleMessageBuilder = [SSKProtoPrekeyBundleMessage builderFromPreKeyBundle:preKeyBundle];
SSKProtoPrekeyBundleMessage *preKeyBundleMessage = [preKeyBundleMessageBuilder buildAndReturnError:&error];
@ -45,6 +48,18 @@
} else {
[contentBuilder setPrekeyBundleMessage:preKeyBundleMessage];
}
} else {
// Set display name & profile picture
id<ProfileManagerProtocol> profileManager = SSKEnvironment.shared.profileManager;
NSString *displayName = profileManager.localProfileName;
NSString *profilePictureURL = profileManager.profilePictureURL;
SSKProtoDataMessageLokiProfileBuilder *profileBuilder = [SSKProtoDataMessageLokiProfile builder];
[profileBuilder setDisplayName:displayName];
[profileBuilder setProfilePicture:profilePictureURL ?: @""];
SSKProtoDataMessageBuilder *messageBuilder = [SSKProtoDataMessage builder];
[messageBuilder setProfile:[profileBuilder buildAndReturnError:nil]];
[ProtoUtils addLocalProfileKeyToDataMessageBuilder:messageBuilder];
[contentBuilder setDataMessage:messageBuilder];
}
// Build the device link message
SSKProtoLokiDeviceLinkMessageBuilder *deviceLinkMessageBuilder = [SSKProtoLokiDeviceLinkMessage builder];

View File

@ -8,6 +8,7 @@
#import "OWSContactsOutputStream.h"
#import "OWSIdentityManager.h"
#import "ProfileManagerProtocol.h"
#import "ProtoUtils.h"
#import "SSKEnvironment.h"
#import "SignalAccount.h"
#import "TSAccountManager.h"
@ -102,19 +103,6 @@ NS_ASSUME_NONNULL_BEGIN
SSKProtoSyncMessageBuilder *syncMessageBuilder = [SSKProtoSyncMessage builder];
[syncMessageBuilder setContacts:contactsProto];
// Loki: Set display name & profile picture
id<ProfileManagerProtocol> profileManager = SSKEnvironment.shared.profileManager;
NSString *displayName = profileManager.localProfileName;
NSString *profilePictureURL = profileManager.profilePictureURL;
SSKProtoDataMessageLokiProfileBuilder *profileBuilder = [SSKProtoDataMessageLokiProfile builder];
[profileBuilder setDisplayName:displayName];
[profileBuilder setProfilePicture:profilePictureURL ?: @""];
SSKProtoDataMessageBuilder *messageBuilder = [SSKProtoDataMessage builder];
[messageBuilder setProfile:[profileBuilder buildAndReturnError:nil]];
SSKProtoSyncMessageSentBuilder *transcriptBuilder = [SSKProtoSyncMessageSent builder];
[transcriptBuilder setMessage:[messageBuilder buildAndReturnError:nil]];
[syncMessageBuilder setSent:[transcriptBuilder buildAndReturnError:nil]];
return syncMessageBuilder;
}

View File

@ -438,22 +438,7 @@ NS_ASSUME_NONNULL_BEGIN
BOOL isDuplicate = [LKAPI isDuplicateSyncMessage:contentProto.syncMessage.sent from:envelope.source];
if (isDuplicate) { return; }
}
// Loki: Handle device linking message if needed
if (contentProto.lokiDeviceLinkMessage != nil) {
NSString *masterHexEncodedPublicKey = contentProto.lokiDeviceLinkMessage.masterHexEncodedPublicKey;
NSString *slaveHexEncodedPublicKey = contentProto.lokiDeviceLinkMessage.slaveHexEncodedPublicKey;
NSData *masterSignature = contentProto.lokiDeviceLinkMessage.masterSignature;
NSData *slaveSignature = contentProto.lokiDeviceLinkMessage.slaveSignature;
if (masterSignature != nil) { // Authorization
OWSLogInfo(@"[Loki] Received a device linking authorization from: %@", envelope.source); // Not masterHexEncodedPublicKey
[LKDeviceLinkingSession.current processLinkingAuthorizationFrom:masterHexEncodedPublicKey for:slaveHexEncodedPublicKey masterSignature:masterSignature slaveSignature:slaveSignature];
} else if (slaveSignature != nil) { // Request
OWSLogInfo(@"[Loki] Received a device linking request from: %@", envelope.source); // Not slaveHexEncodedPublicKey
[LKDeviceLinkingSession.current processLinkingRequestFrom:slaveHexEncodedPublicKey to:masterHexEncodedPublicKey with:slaveSignature];
}
}
// Loki: Handle pre key bundle message if needed
if (contentProto.prekeyBundleMessage != nil) {
OWSLogInfo(@"[Loki] Received a pre key bundle message from: %@.", envelope.source);
@ -471,7 +456,26 @@ NS_ASSUME_NONNULL_BEGIN
[LKP2PAPI didReceiveLokiAddressMessageForContact:envelope.source address:address port:port receivedThroughP2P:envelope.isPtpMessage];
}
if (contentProto.syncMessage) {
// Loki: Handle device linking message if needed
if (contentProto.lokiDeviceLinkMessage != nil) {
NSString *masterHexEncodedPublicKey = contentProto.lokiDeviceLinkMessage.masterHexEncodedPublicKey;
NSString *slaveHexEncodedPublicKey = contentProto.lokiDeviceLinkMessage.slaveHexEncodedPublicKey;
NSData *masterSignature = contentProto.lokiDeviceLinkMessage.masterSignature;
NSData *slaveSignature = contentProto.lokiDeviceLinkMessage.slaveSignature;
if (masterSignature != nil) { // Authorization
OWSLogInfo(@"[Loki] Received a device linking authorization from: %@", envelope.source); // Not masterHexEncodedPublicKey
[LKDeviceLinkingSession.current processLinkingAuthorizationFrom:masterHexEncodedPublicKey for:slaveHexEncodedPublicKey masterSignature:masterSignature slaveSignature:slaveSignature];
// Set any profile info
if (contentProto.dataMessage) {
SSKProtoDataMessage *dataMessage = contentProto.dataMessage;
[self handleProfileNameUpdateIfNeeded:dataMessage recipientId:masterHexEncodedPublicKey transaction:transaction];
[self handleProfileKeyUpdateIfNeeded:dataMessage recipientId:masterHexEncodedPublicKey];
}
} else if (slaveSignature != nil) { // Request
OWSLogInfo(@"[Loki] Received a device linking request from: %@", envelope.source); // Not slaveHexEncodedPublicKey
[LKDeviceLinkingSession.current processLinkingRequestFrom:slaveHexEncodedPublicKey to:masterHexEncodedPublicKey with:slaveSignature];
}
} else if (contentProto.syncMessage) {
[self throws_handleIncomingEnvelope:envelope
withSyncMessage:contentProto.syncMessage
transaction:transaction
@ -554,17 +558,9 @@ NS_ASSUME_NONNULL_BEGIN
return;
}
}
[self handleProfileKeyUpdateIfNeeded:dataMessage recipientId:envelope.source];
if ([dataMessage hasProfileKey]) {
NSData *profileKey = [dataMessage profileKey];
NSString *recipientId = envelope.source;
if (profileKey.length == kAES256_KeyByteLength) {
[self.profileManager setProfileKeyData:profileKey forRecipientId:recipientId];
} else {
OWSFailDebug(
@"Unexpected profile key length:%lu on message from:%@", (unsigned long)profileKey.length, recipientId);
}
}
if (dataMessage.group) {
TSGroupThread *_Nullable groupThread =
@ -923,106 +919,69 @@ NS_ASSUME_NONNULL_BEGIN
OWSProdErrorWEnvelope([OWSAnalyticsEvents messageManagerErrorSyncMessageFromUnknownSource], envelope);
return;
}
NSString *masterHexEncodedPublicKey = [LKDatabaseUtilities getMasterHexEncodedPublicKeyFor:userHexEncodedPublicKey in:transaction];
BOOL wasSentByMasterDevice = [masterHexEncodedPublicKey isEqual:envelope.source];
if (syncMessage.sent) {
NSString *userHexEncodedPublicKey = OWSIdentityManager.sharedManager.identityKeyPair.hexEncodedPublicKey;
NSString *masterHexEncodedPublicKey = [LKDatabaseUtilities getMasterHexEncodedPublicKeyFor:userHexEncodedPublicKey in:transaction];
BOOL wasSentByMasterDevice = [masterHexEncodedPublicKey isEqual:envelope.source];
OWSIncomingSentMessageTranscript *transcript =
[[OWSIncomingSentMessageTranscript alloc] initWithProto:syncMessage.sent transaction:transaction];
SSKProtoDataMessage *_Nullable dataMessage = syncMessage.sent.message;
if (!dataMessage) {
OWSFailDebug(@"Missing dataMessage.");
return;
}
// Loki: Try to update using the provided profile
if (wasSentByMasterDevice) {
dispatch_async(dispatch_get_main_queue(), ^{
SSKProtoDataMessageLokiProfile *profile = syncMessage.sent.message.profile;
NSString *displayName = profile.displayName;
NSString *profilePictureURL = profile.profilePicture;
[self.profileManager updateUserProfileWithDisplayName:displayName profilePictureURL:profilePictureURL transaction:transaction];
});
}
// Loki: Handle contact sync if needed
if (syncMessage.contacts != nil) {
if (wasSentByMasterDevice) {
NSLog(@"[Loki] Received contact sync message.");
NSData *data = syncMessage.contacts.data;
ContactParser *parser = [[ContactParser alloc] initWithData:data];
NSArray<NSString *> *hexEncodedPublicKeys = [parser parseHexEncodedPublicKeys];
// Try to establish sessions
for (NSString *hexEncodedPublicKey in hexEncodedPublicKeys) {
TSContactThread *thread = [TSContactThread getOrCreateThreadWithContactId:hexEncodedPublicKey transaction:transaction];
LKThreadFriendRequestStatus friendRequestStatus = thread.friendRequestStatus;
switch (friendRequestStatus) {
case LKThreadFriendRequestStatusNone: {
OWSMessageSender *messageSender = SSKEnvironment.shared.messageSender;
OWSMessageSend *automatedFriendRequestMessage = [messageSender getMultiDeviceFriendRequestMessageForHexEncodedPublicKey:hexEncodedPublicKey];
dispatch_async(OWSDispatch.sendingQueue, ^{
[messageSender sendMessage:automatedFriendRequestMessage];
});
break;
}
case LKThreadFriendRequestStatusRequestReceived: {
[thread saveFriendRequestStatus:LKThreadFriendRequestStatusFriends withTransaction:transaction];
// The two lines below are equivalent to calling [ThreadUtil enqueueFriendRequestAcceptanceMessageInThread:thread]
LKEphemeralMessage *backgroundMessage = [[LKEphemeralMessage alloc] initInThread:thread];
[self.messageSenderJobQueue addMessage:backgroundMessage transaction:transaction];
break;
}
default: break; // Do nothing
}
}
}
} else {
OWSIncomingSentMessageTranscript *transcript =
[[OWSIncomingSentMessageTranscript alloc] initWithProto:syncMessage.sent transaction:transaction];
SSKProtoDataMessage *_Nullable dataMessage = syncMessage.sent.message;
if (!dataMessage) {
OWSFailDebug(@"Missing dataMessage.");
return;
}
NSString *destination = syncMessage.sent.destination;
if (dataMessage && destination.length > 0 && dataMessage.hasProfileKey) {
// If we observe a linked device sending our profile key to another
// user, we can infer that that user belongs in our profile whitelist.
if (dataMessage.group) {
[self.profileManager addGroupIdToProfileWhitelist:dataMessage.group.id];
} else {
[self.profileManager addUserToProfileWhitelist:destination];
}
}
if ([self isDataMessageGroupAvatarUpdate:syncMessage.sent.message] && !syncMessage.sent.isRecipientUpdate) {
[OWSRecordTranscriptJob
processIncomingSentMessageTranscript:transcript
serverID:0
attachmentHandler:^(NSArray<TSAttachmentStream *> *attachmentStreams) {
OWSAssertDebug(attachmentStreams.count == 1);
TSAttachmentStream *attachmentStream = attachmentStreams.firstObject;
[self.dbConnection readWriteWithBlock:^(
YapDatabaseReadWriteTransaction *transaction) {
TSGroupThread *_Nullable groupThread =
[TSGroupThread threadWithGroupId:dataMessage.group.id
transaction:transaction];
if (!groupThread) {
OWSFailDebug(@"ignoring sync group avatar update for unknown group.");
return;
}
[groupThread updateAvatarWithAttachmentStream:attachmentStream
transaction:transaction];
}];
}
transaction:transaction];
if (wasSentByMasterDevice) {
[self handleProfileNameUpdateIfNeeded:dataMessage recipientId:masterHexEncodedPublicKey transaction:transaction];
[self handleProfileKeyUpdateIfNeeded:dataMessage recipientId:masterHexEncodedPublicKey];
}
NSString *destination = syncMessage.sent.destination;
if (dataMessage && destination.length > 0 && dataMessage.hasProfileKey) {
// If we observe a linked device sending our profile key to another
// user, we can infer that that user belongs in our profile whitelist.
if (dataMessage.group) {
[self.profileManager addGroupIdToProfileWhitelist:dataMessage.group.id];
} else {
[OWSRecordTranscriptJob
processIncomingSentMessageTranscript:transcript
serverID:serverID
attachmentHandler:^(NSArray<TSAttachmentStream *> *attachmentStreams) {
OWSLogDebug(@"successfully fetched transcript attachments: %lu",
(unsigned long)attachmentStreams.count);
}
transaction:transaction];
[self.profileManager addUserToProfileWhitelist:destination];
}
}
if ([self isDataMessageGroupAvatarUpdate:syncMessage.sent.message] && !syncMessage.sent.isRecipientUpdate) {
[OWSRecordTranscriptJob
processIncomingSentMessageTranscript:transcript
serverID:0
attachmentHandler:^(NSArray<TSAttachmentStream *> *attachmentStreams) {
OWSAssertDebug(attachmentStreams.count == 1);
TSAttachmentStream *attachmentStream = attachmentStreams.firstObject;
[self.dbConnection readWriteWithBlock:^(
YapDatabaseReadWriteTransaction *transaction) {
TSGroupThread *_Nullable groupThread =
[TSGroupThread threadWithGroupId:dataMessage.group.id
transaction:transaction];
if (!groupThread) {
OWSFailDebug(@"ignoring sync group avatar update for unknown group.");
return;
}
[groupThread updateAvatarWithAttachmentStream:attachmentStream
transaction:transaction];
}];
}
transaction:transaction];
} else {
[OWSRecordTranscriptJob
processIncomingSentMessageTranscript:transcript
serverID:serverID
attachmentHandler:^(NSArray<TSAttachmentStream *> *attachmentStreams) {
OWSLogDebug(@"successfully fetched transcript attachments: %lu",
(unsigned long)attachmentStreams.count);
}
transaction:transaction];
}
} else if (syncMessage.request) {
if (syncMessage.request.type == SSKProtoSyncMessageRequestTypeContacts) {
// We respond asynchronously because populating the sync message will
@ -1070,6 +1029,36 @@ NS_ASSUME_NONNULL_BEGIN
} else if (syncMessage.verified) {
OWSLogInfo(@"Received verification state for %@", syncMessage.verified.destination);
[self.identityManager throws_processIncomingSyncMessage:syncMessage.verified transaction:transaction];
} else if (syncMessage.contacts != nil) {
if (wasSentByMasterDevice && syncMessage.contacts.data.length > 0) {
NSLog(@"[Loki] Received contact sync message.");
NSData *data = syncMessage.contacts.data;
ContactParser *parser = [[ContactParser alloc] initWithData:data];
NSArray<NSString *> *hexEncodedPublicKeys = [parser parseHexEncodedPublicKeys];
// Try to establish sessions
for (NSString *hexEncodedPublicKey in hexEncodedPublicKeys) {
TSContactThread *thread = [TSContactThread getOrCreateThreadWithContactId:hexEncodedPublicKey transaction:transaction];
LKThreadFriendRequestStatus friendRequestStatus = thread.friendRequestStatus;
switch (friendRequestStatus) {
case LKThreadFriendRequestStatusNone: {
OWSMessageSender *messageSender = SSKEnvironment.shared.messageSender;
OWSMessageSend *automatedFriendRequestMessage = [messageSender getMultiDeviceFriendRequestMessageForHexEncodedPublicKey:hexEncodedPublicKey];
dispatch_async(OWSDispatch.sendingQueue, ^{
[messageSender sendMessage:automatedFriendRequestMessage];
});
break;
}
case LKThreadFriendRequestStatusRequestReceived: {
[thread saveFriendRequestStatus:LKThreadFriendRequestStatusFriends withTransaction:transaction];
// The two lines below are equivalent to calling [ThreadUtil enqueueFriendRequestAcceptanceMessageInThread:thread]
LKEphemeralMessage *backgroundMessage = [[LKEphemeralMessage alloc] initInThread:thread];
[self.messageSenderJobQueue addMessage:backgroundMessage transaction:transaction];
break;
}
default: break; // Do nothing
}
}
}
} else {
OWSLogWarn(@"Ignoring unsupported sync message.");
}
@ -1203,9 +1192,14 @@ NS_ASSUME_NONNULL_BEGIN
envelopeAddress(envelope));
return;
}
if (dataMessage.profile == nil) {
OWSFailDebug(@"received profile key message without loki profile attached from: %@", envelopeAddress(envelope));
return;
}
id<ProfileManagerProtocol> profileManager = SSKEnvironment.shared.profileManager;
[profileManager setProfileKeyData:profileKey forRecipientId:recipientId];
[profileManager setProfileKeyData:profileKey forRecipientId:recipientId avatarURL:dataMessage.profile.profilePicture];
}
- (void)handleUnlinkDeviceMessageWithEnvelope:(SSKProtoEnvelope *)envelope dataMessage:(SSKProtoDataMessage *)dataMessage transaction:(YapDatabaseReadWriteTransaction *)transaction
@ -1388,9 +1382,8 @@ NS_ASSUME_NONNULL_BEGIN
TSContactThread *thread =
[TSContactThread getOrCreateThreadWithContactId:hexEncodedPublicKey transaction:transaction];
NSString *profilePictureURL = dataMessage.profile.profilePicture;
NSString *displayName = dataMessage.profile.displayName;
[self.profileManager updateProfileForContactWithID:thread.contactIdentifier displayName:displayName profilePictureURL:profilePictureURL with:transaction];
// Only set the display name here, the logic for updating profile pictures is handled when we're setting profile key
[self handleProfileNameUpdateIfNeeded:dataMessage recipientId:thread.contactIdentifier transaction:transaction];
switch (dataMessage.group.type) {
case SSKProtoGroupContextTypeUpdate: {
@ -1588,13 +1581,8 @@ NS_ASSUME_NONNULL_BEGIN
if (rawDisplayName != nil && rawDisplayName.length > 0) {
displayName = [NSString stringWithFormat:@"%@ (...%@)", rawDisplayName, [incomingMessage.authorId substringFromIndex:incomingMessage.authorId.length - 8]];
}
NSString *rawProfilePictureURL = dataMessage.profile.profilePicture;
NSString *profilePictureURL = nil;
if (rawProfilePictureURL != nil && rawProfilePictureURL.length > 0) {
profilePictureURL = rawProfilePictureURL;
}
[self.profileManager updateProfileForContactWithID:thread.contactIdentifier displayName:displayName profilePictureURL:profilePictureURL with:transaction];
[self.profileManager updateProfileForContactWithID:thread.contactIdentifier displayName:displayName with:transaction];
[self handleProfileKeyUpdateIfNeeded:dataMessage recipientId:thread.contactIdentifier];
// Loki: Parse Loki specific properties if needed
if (envelope.isPtpMessage) { incomingMessage.isP2P = YES; }
@ -1635,6 +1623,25 @@ NS_ASSUME_NONNULL_BEGIN
}
}
- (void)handleProfileNameUpdateIfNeeded:(SSKProtoDataMessage *)dataMessage recipientId:(NSString *)recipientId transaction:(YapDatabaseReadWriteTransaction *)transaction {
if (dataMessage != nil && dataMessage.profile != nil) {
[self.profileManager updateProfileForContactWithID:recipientId displayName:dataMessage.profile.displayName with:transaction];
}
}
- (void)handleProfileKeyUpdateIfNeeded:(SSKProtoDataMessage *)dataMessage recipientId:(NSString *)recipientId {
if (dataMessage != nil && [dataMessage hasProfileKey]) {
NSData *profileKey = [dataMessage profileKey];
NSString *url = dataMessage.profile != nil ? dataMessage.profile.profilePicture : nil;
if (profileKey.length == kAES256_KeyByteLength) {
[self.profileManager setProfileKeyData:profileKey forRecipientId:recipientId avatarURL:url];
} else {
OWSFailDebug(
@"Unexpected profile key length:%lu on message from:%@", (unsigned long)profileKey.length, recipientId);
}
}
}
- (BOOL)canFriendRequestBeAutoAcceptedForThread:(TSContactThread *)thread transaction:(YapDatabaseReadWriteTransaction *)transaction
{
NSString *senderHexEncodedPublicKey = thread.contactIdentifier;

View File

@ -1207,7 +1207,6 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
NSString *userHexEncodedPublicKey = OWSIdentityManager.sharedManager.identityKeyPair.hexEncodedPublicKey;
NSString *displayName = SSKEnvironment.shared.profileManager.localProfileName;
if (displayName == nil) { displayName = @"Anonymous"; }
NSString *avatarUrl = SSKEnvironment.shared.profileManager.profilePictureURL;
TSQuotedMessage *quote = message.quotedMessage;
uint64_t quoteID = quote.timestamp;
NSString *quoteeHexEncodedPublicKey = quote.authorId;
@ -1218,7 +1217,7 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
}];
}
NSString *body = (message.body != nil && message.body.length > 0) ? message.body : [NSString stringWithFormat:@"%@", @(message.timestamp)]; // Workaround for the fact that the back-end doesn't accept messages without a body
LKGroupMessage *groupMessage = [[LKGroupMessage alloc] initWithHexEncodedPublicKey:userHexEncodedPublicKey displayName:displayName avatar:avatarUrl body:body type:LKPublicChatAPI.publicChatMessageType
LKGroupMessage *groupMessage = [[LKGroupMessage alloc] initWithHexEncodedPublicKey:userHexEncodedPublicKey displayName:displayName body:body type:LKPublicChatAPI.publicChatMessageType
timestamp:message.timestamp quotedMessageTimestamp:quoteID quoteeHexEncodedPublicKey:quoteeHexEncodedPublicKey quotedMessageBody:quote.body quotedMessageServerID:quotedMessageServerID signatureData:nil signatureVersion:0];
OWSLinkPreview *linkPreview = message.linkPreview;
if (linkPreview != nil) {

View File

@ -5,6 +5,7 @@
@class OWSAES256Key;
@class TSThread;
@class YapDatabaseReadWriteTransaction;
@class OWSUserProfile;
NS_ASSUME_NONNULL_BEGIN
@ -17,8 +18,8 @@ NS_ASSUME_NONNULL_BEGIN
- (nullable NSString *)profilePictureURL;
- (nullable NSData *)profileKeyDataForRecipientId:(NSString *)recipientId;
- (void)updateUserProfileWithDisplayName:(nullable NSString*)displayName profilePictureURL:(nullable NSString*)profilePictureURL transaction:(YapDatabaseReadWriteTransaction *)transaction;
- (void)setProfileKeyData:(NSData *)profileKeyData forRecipientId:(NSString *)recipientId;
- (void)setProfileKeyData:(NSData *)profileKeyData forRecipientId:(NSString *)recipientId avatarURL:(nullable NSString *)avatarURL;
- (BOOL)isUserInProfileWhitelist:(NSString *)recipientId;
@ -31,7 +32,8 @@ NS_ASSUME_NONNULL_BEGIN
- (void)fetchProfileForRecipientId:(NSString *)recipientId;
- (void)updateProfileForContactWithID:(NSString *)contactID displayName:(NSString *)displayName profilePictureURL:(NSString *)profilePictureURL with:(YapDatabaseReadWriteTransaction *)transaction;
- (void)updateProfileForContactWithID:(NSString *)contactID displayName:(NSString *)displayName with:(YapDatabaseReadWriteTransaction *)transaction;
- (void)updateServiceWithProfileName:(nullable NSString *)localProfileName avatarUrl:(nullable NSString *)avatarURL;
@end

View File

@ -48,11 +48,6 @@ NS_ASSUME_NONNULL_BEGIN
return _localProfileKey;
}
- (void)updateUserProfileWithDisplayName:(nullable NSString*)displayName profilePictureURL:(nullable NSString*)profilePictureURL transaction:(YapDatabaseReadWriteTransaction *)transaction
{
// Do nothing
}
- (void)setProfileKeyData:(NSData *)profileKey forRecipientId:(NSString *)recipientId
{
OWSAES256Key *_Nullable key = [OWSAES256Key keyWithData:profileKey];