diff --git a/Riot.xcodeproj/project.pbxproj b/Riot.xcodeproj/project.pbxproj index c2a724622..5a3be7138 100644 --- a/Riot.xcodeproj/project.pbxproj +++ b/Riot.xcodeproj/project.pbxproj @@ -454,6 +454,7 @@ B1B5574F20EE6C4D00210D55 /* RoomsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = B1B556F820EE6C4C00210D55 /* RoomsViewController.m */; }; B1B5575020EE6C4D00210D55 /* AuthenticationViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = B1B556FB20EE6C4C00210D55 /* AuthenticationViewController.xib */; }; B1B5575120EE6C4D00210D55 /* AuthenticationViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = B1B556FC20EE6C4C00210D55 /* AuthenticationViewController.m */; }; + B1B5575220EE6C4D00210D55 /* RoomKeyRequestViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = B1B556FE20EE6C4C00210D55 /* RoomKeyRequestViewController.m */; }; B1B5575920EE6C4D00210D55 /* HomeMessagesSearchViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = B1B5570F20EE6C4C00210D55 /* HomeMessagesSearchViewController.m */; }; B1B5575A20EE6C4D00210D55 /* UnifiedSearchViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = B1B5571120EE6C4C00210D55 /* UnifiedSearchViewController.m */; }; B1B5575B20EE6C4D00210D55 /* HomeFilesSearchViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = B1B5571420EE6C4C00210D55 /* HomeFilesSearchViewController.m */; }; @@ -1608,6 +1609,8 @@ B1B556FA20EE6C4C00210D55 /* AuthenticationViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AuthenticationViewController.h; sourceTree = ""; }; B1B556FB20EE6C4C00210D55 /* AuthenticationViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = AuthenticationViewController.xib; sourceTree = ""; }; B1B556FC20EE6C4C00210D55 /* AuthenticationViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AuthenticationViewController.m; sourceTree = ""; }; + B1B556FE20EE6C4C00210D55 /* RoomKeyRequestViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RoomKeyRequestViewController.m; sourceTree = ""; }; + B1B556FF20EE6C4C00210D55 /* RoomKeyRequestViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RoomKeyRequestViewController.h; sourceTree = ""; }; B1B5570E20EE6C4C00210D55 /* HomeMessagesSearchViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HomeMessagesSearchViewController.h; sourceTree = ""; }; B1B5570F20EE6C4C00210D55 /* HomeMessagesSearchViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HomeMessagesSearchViewController.m; sourceTree = ""; }; B1B5571020EE6C4C00210D55 /* UnifiedSearchViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UnifiedSearchViewController.h; sourceTree = ""; }; @@ -3536,6 +3539,7 @@ B1C3361A22F328AE0021BA8D /* Camera */, B1B556E420EE6C4C00210D55 /* UserDevices */, B1B5596B20EFA85C00210D55 /* EncryptionInfo */, + B1B556FD20EE6C4C00210D55 /* RoomKeyRequest */, B1B556B020EE6C4C00210D55 /* BugReport */, B1098BE921ECFE64000DDA48 /* KeyBackup */, B1550FCF242148FA00CE097B /* KeyVerification */, @@ -3971,6 +3975,15 @@ path = Authentication; sourceTree = ""; }; + B1B556FD20EE6C4C00210D55 /* RoomKeyRequest */ = { + isa = PBXGroup; + children = ( + B1B556FF20EE6C4C00210D55 /* RoomKeyRequestViewController.h */, + B1B556FE20EE6C4C00210D55 /* RoomKeyRequestViewController.m */, + ); + path = RoomKeyRequest; + sourceTree = ""; + }; B1B5570C20EE6C4C00210D55 /* GlobalSearch */ = { isa = PBXGroup; children = ( @@ -6798,6 +6811,7 @@ B1098BFA21ECFE65000DDA48 /* KeyBackupSetupPassphraseViewModel.swift in Sources */, EC51E7A62514D2E100AAE7DB /* RoomInfoCoordinatorBridgePresenter.swift in Sources */, EC1CA87524C8259700DE9EBF /* KeychainStore.swift in Sources */, + B1B5575220EE6C4D00210D55 /* RoomKeyRequestViewController.m in Sources */, 32A6001A22C661100042C1D9 /* EditHistoryCoordinator.swift in Sources */, B1A15BDB25A6680E00BDCA36 /* SSOURLConstants.swift in Sources */, F083BD1E1E7009ED00A9B29C /* LegacyAppDelegate.m in Sources */, diff --git a/Riot/Modules/Application/LegacyAppDelegate.m b/Riot/Modules/Application/LegacyAppDelegate.m index d51efea8d..e33c60c3d 100644 --- a/Riot/Modules/Application/LegacyAppDelegate.m +++ b/Riot/Modules/Application/LegacyAppDelegate.m @@ -33,6 +33,7 @@ #import "ContactDetailsViewController.h" #import "BugReportViewController.h" +#import "RoomKeyRequestViewController.h" #import "DecryptionFailureTracker.h" #import @@ -128,6 +129,11 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni id roomKeyRequestObserver; id roomKeyRequestCancellationObserver; + /** + If any the currently displayed sharing key dialog + */ + RoomKeyRequestViewController *roomKeyRequestViewController; + /** Incoming key verification requests observers */ @@ -3720,28 +3726,95 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni return; } + MXWeakify(self); [mxSession.crypto pendingKeyRequests:^(MXUsersDevicesMap *> *pendingKeyRequests) { - NSLog(@"[AppDelegate] checkPendingRoomKeyRequestsInSession: pendingKeyRequests.count: %lu", - pendingKeyRequests.count); + MXStrongifyAndReturnIfNil(self); + NSLog(@"[AppDelegate] checkPendingRoomKeyRequestsInSession: pendingKeyRequests.count: %@. Already displayed: %@", + @(pendingKeyRequests.count), + self->roomKeyRequestViewController ? @"YES" : @"NO"); - if (pendingKeyRequests.count) + if (!mxSession.crypto.crossSigning) + { + if (self->roomKeyRequestViewController) + { + // Check if the current RoomKeyRequestViewController is still valid + MXSession *currentMXSession = self->roomKeyRequestViewController.mxSession; + NSString *currentUser = self->roomKeyRequestViewController.device.userId; + NSString *currentDevice = self->roomKeyRequestViewController.device.deviceId; + + NSArray *currentPendingRequest = [pendingKeyRequests objectForDevice:currentDevice forUser:currentUser]; + + if (currentMXSession == mxSession && currentPendingRequest.count == 0) + { + NSLog(@"[AppDelegate] checkPendingRoomKeyRequestsInSession: Cancel current dialog"); + + // The key request has been probably cancelled, remove the popup + [self->roomKeyRequestViewController hide]; + self->roomKeyRequestViewController = nil; + } + } + } + + if (!self->roomKeyRequestViewController && pendingKeyRequests.count) { // Pick the first coming user/device pair NSString *userId = pendingKeyRequests.userIds.firstObject; NSString *deviceId = [pendingKeyRequests deviceIdsForUser:userId].firstObject; // Give the client a chance to refresh the device list + MXWeakify(self); [mxSession.crypto downloadKeys:@[userId] forceDownload:NO success:^(MXUsersDevicesMap *usersDevicesInfoMap, NSDictionary *crossSigningKeysMap) { + + MXStrongifyAndReturnIfNil(self); MXDeviceInfo *deviceInfo = [usersDevicesInfoMap objectForDevice:deviceId forUser:userId]; - if (deviceInfo && deviceInfo.trustLevel.isVerified) + if (deviceInfo) { - [mxSession.crypto acceptAllPendingKeyRequestsFromUser:userId andDevice:deviceId onComplete:^{ - [self checkPendingRoomKeyRequests]; - }]; + if (!mxSession.crypto.crossSigning) + { + BOOL wasNewDevice = (deviceInfo.trustLevel.localVerificationStatus == MXDeviceUnknown); + + void (^openDialog)(void) = ^void() + { + NSLog(@"[AppDelegate] checkPendingRoomKeyRequestsInSession: Open dialog for %@", deviceInfo); + + self->roomKeyRequestViewController = [[RoomKeyRequestViewController alloc] initWithDeviceInfo:deviceInfo wasNewDevice:wasNewDevice andMatrixSession:mxSession onComplete:^{ + + self->roomKeyRequestViewController = nil; + + // Check next pending key request, if any + [self checkPendingRoomKeyRequests]; + }]; + + [self->roomKeyRequestViewController show]; + }; + + // If the device was new before, it's not any more. + if (wasNewDevice) + { + [mxSession.crypto setDeviceVerification:MXDeviceUnverified forDevice:deviceId ofUser:userId success:openDialog failure:nil]; + } + else + { + openDialog(); + } + } + else if (deviceInfo.trustLevel.isVerified) + { + [mxSession.crypto acceptAllPendingKeyRequestsFromUser:userId andDevice:deviceId onComplete:^{ + [self checkPendingRoomKeyRequests]; + }]; + } + else + { + [mxSession.crypto ignoreAllPendingKeyRequestsFromUser:userId andDevice:deviceId onComplete:^{ + [self checkPendingRoomKeyRequests]; + }]; + } } else { + NSLog(@"[AppDelegate] checkPendingRoomKeyRequestsInSession: No details found for device %@:%@", userId, deviceId); [mxSession.crypto ignoreAllPendingKeyRequestsFromUser:userId andDevice:deviceId onComplete:^{ [self checkPendingRoomKeyRequests]; }]; diff --git a/Riot/Modules/RoomKeyRequest/RoomKeyRequestViewController.h b/Riot/Modules/RoomKeyRequest/RoomKeyRequestViewController.h new file mode 100644 index 000000000..92358b5a8 --- /dev/null +++ b/Riot/Modules/RoomKeyRequest/RoomKeyRequestViewController.h @@ -0,0 +1,58 @@ +/* + Copyright 2017 Vector Creations Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import + +#import +#import + +/** + The `RoomKeyRequestViewController` display a modal dialog at the top of the + application asking the user if he wants to share room keys with a user's device. + For the moment, the user is himself. + */ +@interface RoomKeyRequestViewController : NSObject + +/** + The UIAlertController instance which handles the dialog. + */ +@property (nonatomic, readonly) UIAlertController *alertController; + +@property (nonatomic, readonly) MXSession *mxSession; +@property (nonatomic, readonly) MXDeviceInfo *device; + +/** + Initialise an `RoomKeyRequestViewController` instance. + + @param deviceInfo the device to share keys to. + @param wasNewDevice flag indicating whether this is the first time we meet the device. + @param session the related matrix session. + @param onComplete a block called when the the dialog is closed. + @return the newly created instance. + */ +- (instancetype)initWithDeviceInfo:(MXDeviceInfo*)deviceInfo wasNewDevice:(BOOL)wasNewDevice andMatrixSession:(MXSession*)session onComplete:(void (^)(void))onComplete; + +/** + Show the dialog in a modal way. + */ +- (void)show; + +/** + Hide the dialog. + */ +- (void)hide; + +@end diff --git a/Riot/Modules/RoomKeyRequest/RoomKeyRequestViewController.m b/Riot/Modules/RoomKeyRequest/RoomKeyRequestViewController.m new file mode 100644 index 000000000..40c44964a --- /dev/null +++ b/Riot/Modules/RoomKeyRequest/RoomKeyRequestViewController.m @@ -0,0 +1,187 @@ +/* + Copyright 2017 Vector Creations Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import "RoomKeyRequestViewController.h" + +#import "Riot-Swift.h" + +@interface RoomKeyRequestViewController () +{ + void (^onComplete)(void); + + KeyVerificationCoordinatorBridgePresenter *keyVerificationCoordinatorBridgePresenter; + + BOOL wasNewDevice; +} +@end + +@implementation RoomKeyRequestViewController + +- (instancetype)initWithDeviceInfo:(MXDeviceInfo *)deviceInfo wasNewDevice:(BOOL)theWasNewDevice andMatrixSession:(MXSession *)session onComplete:(void (^)(void))onCompleteBlock +{ + self = [super init]; + if (self) + { + _mxSession = session; + _device = deviceInfo; + wasNewDevice = theWasNewDevice; + onComplete = onCompleteBlock; + } + return self; +} + +- (void)show +{ + // Show it modally on the root view controller + UIViewController *rootViewController = [AppDelegate theDelegate].window.rootViewController; + if (rootViewController) + { + NSString *title = NSLocalizedStringFromTable(@"e2e_room_key_request_title", @"Vector", nil); + NSString *message; + if (wasNewDevice) + { + message = [NSString stringWithFormat:NSLocalizedStringFromTable(@"e2e_room_key_request_message_new_device", @"Vector", nil), _device.displayName]; + } + else + { + message = [NSString stringWithFormat:NSLocalizedStringFromTable(@"e2e_room_key_request_message", @"Vector", nil), _device.displayName]; + } + + _alertController = [UIAlertController alertControllerWithTitle:title + message:message + preferredStyle:UIAlertControllerStyleAlert]; + + __weak typeof(self) weakSelf = self; + + [_alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"e2e_room_key_request_start_verification", @"Vector", nil) + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) { + + if (weakSelf) + { + typeof(self) self = weakSelf; + + self->_alertController = nil; + [self showVerificationView]; + } + }]]; + + [_alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"e2e_room_key_request_share_without_verifying", @"Vector", nil) + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) { + + if (weakSelf) + { + typeof(self) self = weakSelf; + + self->_alertController = nil; + + // Accept the received requests from this device + [self.mxSession.crypto acceptAllPendingKeyRequestsFromUser:self.device.userId andDevice:self.device.deviceId onComplete:^{ + + onComplete(); + }]; + } + }]]; + + [_alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"e2e_room_key_request_ignore_request", @"Vector", nil) + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) { + + if (weakSelf) + { + typeof(self) self = weakSelf; + + self->_alertController = nil; + + // Ignore all pending requests from this device + [self.mxSession.crypto ignoreAllPendingKeyRequestsFromUser:self.device.userId andDevice:self.device.deviceId onComplete:^{ + + onComplete(); + }]; + } + }]]; + + [rootViewController presentViewController:_alertController animated:YES completion:nil]; + } +} + +- (void)hide +{ + if (_alertController) + { + [_alertController dismissViewControllerAnimated:YES completion:nil]; + _alertController = nil; + } +} + + +- (void)showVerificationView +{ + // Show it modally on the root view controller + UIViewController *rootViewController = [AppDelegate theDelegate].window.rootViewController; + if (rootViewController) + { + keyVerificationCoordinatorBridgePresenter = [[KeyVerificationCoordinatorBridgePresenter alloc] initWithSession:_mxSession]; + keyVerificationCoordinatorBridgePresenter.delegate = self; + + [keyVerificationCoordinatorBridgePresenter presentFrom:rootViewController otherUserId:_device.userId otherDeviceId:_device.deviceId animated:YES]; + } +} + +#pragma mark - DeviceVerificationCoordinatorBridgePresenterDelegate + +- (void)keyVerificationCoordinatorBridgePresenterDelegateDidComplete:(KeyVerificationCoordinatorBridgePresenter *)coordinatorBridgePresenter otherUserId:(NSString * _Nonnull)otherUserId otherDeviceId:(NSString * _Nonnull)otherDeviceId +{ + [self dismissKeyVerificationCoordinatorBridgePresenter]; +} + +- (void)keyVerificationCoordinatorBridgePresenterDelegateDidCancel:(KeyVerificationCoordinatorBridgePresenter * _Nonnull)coordinatorBridgePresenter +{ + [self dismissKeyVerificationCoordinatorBridgePresenter]; +} + +- (void)dismissKeyVerificationCoordinatorBridgePresenter +{ + [keyVerificationCoordinatorBridgePresenter dismissWithAnimated:YES completion:nil]; + keyVerificationCoordinatorBridgePresenter = nil; + + // Check device new status + [self.mxSession.crypto downloadKeys:@[self.device.userId] forceDownload:NO success:^(MXUsersDevicesMap *usersDevicesInfoMap, NSDictionary *crossSigningKeysMap) { + + MXDeviceInfo *deviceInfo = [usersDevicesInfoMap objectForDevice:self.device.deviceId forUser:self.device.userId]; + if (deviceInfo && deviceInfo.trustLevel.localVerificationStatus == MXDeviceVerified) + { + // Accept the received requests from this device + // As the device is now verified, all other key requests will be automatically accepted. + [self.mxSession.crypto acceptAllPendingKeyRequestsFromUser:self.device.userId andDevice:self.device.deviceId onComplete:^{ + + onComplete(); + }]; + } + else + { + // Come back to self.alertController - ie, reopen it + [self show]; + } + } failure:^(NSError *error) { + + // Should not happen (the device is in the crypto db) + [self show]; + }]; +} + +@end