diff --git a/MatrixSDK.xcodeproj/project.pbxproj b/MatrixSDK.xcodeproj/project.pbxproj index 162f653aeb..8b7e07f3fc 100644 --- a/MatrixSDK.xcodeproj/project.pbxproj +++ b/MatrixSDK.xcodeproj/project.pbxproj @@ -697,6 +697,12 @@ 3AF85F9226FC7AE800A9E67B /* MXSpaceNotificationState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AF85F9126FC7AE800A9E67B /* MXSpaceNotificationState.swift */; }; 3AF85F9326FC7AE800A9E67B /* MXSpaceNotificationState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AF85F9126FC7AE800A9E67B /* MXSpaceNotificationState.swift */; }; 3B60DCAB92D470B91262BF0C /* libPods-MatrixSDKTests-macOS.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 15D7F292D95EB58AEE801C4E /* libPods-MatrixSDKTests-macOS.a */; }; + 66398BA527A4085B00466E89 /* MXRefreshResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = 66398BA427A4085B00466E89 /* MXRefreshResponse.m */; }; + 66398BA627A4085B00466E89 /* MXRefreshResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = 66398BA427A4085B00466E89 /* MXRefreshResponse.m */; }; + 66AC9D23278CE627002E9B8F /* MXRefreshTokenData.h in Headers */ = {isa = PBXBuildFile; fileRef = 66AC9D21278CE626002E9B8F /* MXRefreshTokenData.h */; }; + 66AC9D24278CE627002E9B8F /* MXRefreshTokenData.h in Headers */ = {isa = PBXBuildFile; fileRef = 66AC9D21278CE626002E9B8F /* MXRefreshTokenData.h */; }; + 66AC9D25278CE627002E9B8F /* MXRefreshTokenData.m in Sources */ = {isa = PBXBuildFile; fileRef = 66AC9D22278CE626002E9B8F /* MXRefreshTokenData.m */; }; + 66AC9D26278CE627002E9B8F /* MXRefreshTokenData.m in Sources */ = {isa = PBXBuildFile; fileRef = 66AC9D22278CE626002E9B8F /* MXRefreshTokenData.m */; }; 71DE22E01BC7C51200284153 /* MXReceiptData.m in Sources */ = {isa = PBXBuildFile; fileRef = 71DE22DC1BC7C51200284153 /* MXReceiptData.m */; }; 71DE22E11BC7C51200284153 /* MXReceiptData.h in Headers */ = {isa = PBXBuildFile; fileRef = 71DE22DD1BC7C51200284153 /* MXReceiptData.h */; settings = {ATTRIBUTES = (Public, ); }; }; 8EC511042568216B00EC4E5B /* MXTaggedEventInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = 8EC511022568216B00EC4E5B /* MXTaggedEventInfo.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -2239,6 +2245,10 @@ 3AF85F9126FC7AE800A9E67B /* MXSpaceNotificationState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MXSpaceNotificationState.swift; sourceTree = ""; }; 492C3485159D57C2116EDCBD /* Pods-SDK-MatrixSDK-macOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SDK-MatrixSDK-macOS.debug.xcconfig"; path = "Target Support Files/Pods-SDK-MatrixSDK-macOS/Pods-SDK-MatrixSDK-macOS.debug.xcconfig"; sourceTree = ""; }; 6257D945D115DA658F44E8D4 /* Pods-SDK-MatrixSDK-macOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SDK-MatrixSDK-macOS.release.xcconfig"; path = "Target Support Files/Pods-SDK-MatrixSDK-macOS/Pods-SDK-MatrixSDK-macOS.release.xcconfig"; sourceTree = ""; }; + 66398BA427A4085B00466E89 /* MXRefreshResponse.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MXRefreshResponse.m; sourceTree = ""; }; + 66398BA727A408C000466E89 /* MXRefreshResponse.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MXRefreshResponse.h; sourceTree = ""; }; + 66AC9D21278CE626002E9B8F /* MXRefreshTokenData.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MXRefreshTokenData.h; sourceTree = ""; }; + 66AC9D22278CE626002E9B8F /* MXRefreshTokenData.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MXRefreshTokenData.m; sourceTree = ""; }; 6869D206EA7C7FB05EAB75B0 /* Pods-MatrixSDKTests-iOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MatrixSDKTests-iOS.release.xcconfig"; path = "Target Support Files/Pods-MatrixSDKTests-iOS/Pods-MatrixSDKTests-iOS.release.xcconfig"; sourceTree = ""; }; 71DE22DC1BC7C51200284153 /* MXReceiptData.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MXReceiptData.m; sourceTree = ""; }; 71DE22DD1BC7C51200284153 /* MXReceiptData.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MXReceiptData.h; sourceTree = ""; }; @@ -2739,6 +2749,8 @@ F082946C1DB66C3D00CEAB63 /* MXInvite3PID.m */, 327137251A24D50A00DB6757 /* MXMyUser.h */, 327137261A24D50A00DB6757 /* MXMyUser.m */, + 66AC9D21278CE626002E9B8F /* MXRefreshTokenData.h */, + 66AC9D22278CE626002E9B8F /* MXRefreshTokenData.m */, ECD2899726EB3C7A00F268CF /* MXRoomSummaryProtocol.h */, ECD289A426EB97E800F268CF /* MXRoomSummaryDataTypes.h */, ECD289B326F9F00E00F268CF /* MXRoomSummarySentStatus.h */, @@ -3360,6 +3372,8 @@ B14EECD42577DE4400448735 /* Login */, 3281E8B319E42DFE00976E1A /* MXJSONModel.h */, 3281E8B419E42DFE00976E1A /* MXJSONModel.m */, + 66398BA727A408C000466E89 /* MXRefreshResponse.h */, + 66398BA427A4085B00466E89 /* MXRefreshResponse.m */, 3281E8B519E42DFE00976E1A /* MXJSONModels.h */, 3281E8B619E42DFE00976E1A /* MXJSONModels.m */, 323E0C591A306D7A00A31D73 /* MXEvent.h */, @@ -4638,6 +4652,7 @@ 3297912E23AA126500F7BB9B /* MXKeyVerificationStatusResolver.h in Headers */, 3245A7521AF7B2930001D8A7 /* MXCallManager.h in Headers */, 32B94E05228EE90300716A26 /* MXRealmReactionRelation.h in Headers */, + 66AC9D23278CE627002E9B8F /* MXRefreshTokenData.h in Headers */, C6FE1EF01E65C4F7008587E4 /* MXAnalyticsDelegate.h in Headers */, 92634B7F1EF2A37A00DB9F60 /* MXCallAudioSessionConfigurator.h in Headers */, 323F8864212D4E470001C73C /* MXMatrixVersions.h in Headers */, @@ -4933,6 +4948,7 @@ 324AAC812399143400380A66 /* MXKeyVerificationKey.h in Headers */, B1798D0724091A0100308A8F /* MXBase64Tools.h in Headers */, B14EF29D2397E90400758AF0 /* MXRealmEventScan.h in Headers */, + 66AC9D24278CE627002E9B8F /* MXRefreshTokenData.h in Headers */, 323F8774255460B5009E9E67 /* MXProfiler.h in Headers */, EC8A53B425B1BC77004E0802 /* MXUserModel.h in Headers */, 32CEEF4423AD2A6C0039BA98 /* MXCrossSigningKey.h in Headers */, @@ -5555,6 +5571,7 @@ F03EF4FF1DF014D9009DF592 /* MXMediaLoader.m in Sources */, 320A8841217F4E3F002EA952 /* MXMegolmBackupAuthData.m in Sources */, B1A0270226162110001AADFF /* MXSpaceChildrenResponse.m in Sources */, + 66398BA527A4085B00466E89 /* MXRefreshResponse.m in Sources */, 327E9AF72289D53800A98BC1 /* MXReactionCount.m in Sources */, 32FFB4F1217E146A00C96002 /* MXRecoveryKey.m in Sources */, 32DC15D51A8CF874006F9AD3 /* MXPushRuleEventMatchConditionChecker.m in Sources */, @@ -5701,6 +5718,7 @@ 1838927A2702F553003F0C4F /* MXSendReplyEventDefaultStringLocalizer.m in Sources */, EC0B941E27186C3500B4D440 /* MXRoomListDataSortable.swift in Sources */, 32581DEA23C8C0C900832EAA /* MXUserTrustLevel.m in Sources */, + 66AC9D25278CE627002E9B8F /* MXRefreshTokenData.m in Sources */, 3271878A1DA7DCE50071C818 /* MXOlmEncryption.m in Sources */, 327187861DA7D0220071C818 /* MXOlmDecryption.m in Sources */, EC383BA8253DE6EE002FBBE6 /* MXSyncResponseFileStore.swift in Sources */, @@ -6044,6 +6062,7 @@ B14EF1DF2397E90400758AF0 /* MXAggregatedEditsUpdater.m in Sources */, B14EF1E02397E90400758AF0 /* MXRealmCryptoStore.m in Sources */, B14EF1E12397E90400758AF0 /* MXRoomSummary.m in Sources */, + 66398BA627A4085B00466E89 /* MXRefreshResponse.m in Sources */, B14EF1E22397E90400758AF0 /* MXPushRuleRoomMemberCountConditionChecker.m in Sources */, B14EF1E32397E90400758AF0 /* MXCall.m in Sources */, B14EF1E42397E90400758AF0 /* MXWellknownIntegrations.m in Sources */, @@ -6190,6 +6209,7 @@ 1838927B2702F553003F0C4F /* MXSendReplyEventDefaultStringLocalizer.m in Sources */, EC0B941F27186C3500B4D440 /* MXRoomListDataSortable.swift in Sources */, EC8A539825B1BC77004E0802 /* MXCallReplacesEventContent.m in Sources */, + 66AC9D26278CE627002E9B8F /* MXRefreshTokenData.m in Sources */, B18B0E6825FBDC3000E32151 /* MXSpace.swift in Sources */, B14EF22D2397E90400758AF0 /* MXRealmReactionCount.m in Sources */, B14EF22E2397E90400758AF0 /* MXCryptoTools.m in Sources */, diff --git a/MatrixSDK/Background/MXBackgroundSyncService.swift b/MatrixSDK/Background/MXBackgroundSyncService.swift index c7e40f0e9f..ade9004837 100644 --- a/MatrixSDK/Background/MXBackgroundSyncService.swift +++ b/MatrixSDK/Background/MXBackgroundSyncService.swift @@ -66,7 +66,7 @@ public enum MXBackgroundSyncServiceError: Error { /// Initializer /// - Parameter credentials: account credentials - public init(withCredentials credentials: MXCredentials) { + public init(withCredentials credentials: MXCredentials, persistTokenDataHandler: MXRestClientPersistTokenDataHandler? = nil, unauthenticatedHandler: MXRestClientUnauthenticatedHandler? = nil) { processingQueue = DispatchQueue(label: "MXBackgroundSyncServiceQueue-" + MXTools.generateSecret()) self.credentials = credentials @@ -76,7 +76,7 @@ public enum MXBackgroundSyncServiceError: Error { let syncResponseStore = MXSyncResponseFileStore(withCredentials: credentials) syncResponseStoreManager = MXSyncResponseStoreManager(syncResponseStore: syncResponseStore) - restClient = MXRestClient(credentials: credentials, unrecognizedCertificateHandler: nil) + restClient = MXRestClient(credentials: credentials, unrecognizedCertificateHandler: nil, persistentTokenDataHandler: persistTokenDataHandler, unauthenticatedHandler: unauthenticatedHandler) restClient.completionQueue = processingQueue store = MXBackgroundStore(withCredentials: credentials) // We can flush any crypto data if our sync response store is empty diff --git a/MatrixSDK/Contrib/Swift/MXEnumConstants.swift b/MatrixSDK/Contrib/Swift/MXEnumConstants.swift index dc57d47854..aabbe39da9 100644 --- a/MatrixSDK/Contrib/Swift/MXEnumConstants.swift +++ b/MatrixSDK/Contrib/Swift/MXEnumConstants.swift @@ -211,10 +211,6 @@ extension MXSessionState: CustomStringConvertible { return "pauseRequested" case .initialSyncFailed: return "initialSyncFailed" - case .unknownToken: - return "unknownToken" - case .softLogout: - return "softLogout" @unknown default: return "\(self.rawValue)" } diff --git a/MatrixSDK/Contrib/Swift/MXRestClient.swift b/MatrixSDK/Contrib/Swift/MXRestClient.swift index 7eb03ebb67..153de40ce2 100644 --- a/MatrixSDK/Contrib/Swift/MXRestClient.swift +++ b/MatrixSDK/Contrib/Swift/MXRestClient.swift @@ -88,8 +88,8 @@ public extension MXRestClient { - returns: a `MXRestClient` instance. */ - @nonobjc convenience init(credentials: MXCredentials, unrecognizedCertificateHandler handler: MXHTTPClientOnUnrecognizedCertificate?) { - self.init(__credentials: credentials, andOnUnrecognizedCertificateBlock: handler) + @nonobjc convenience init(credentials: MXCredentials, unrecognizedCertificateHandler handler: MXHTTPClientOnUnrecognizedCertificate? = nil, persistentTokenDataHandler: MXRestClientPersistTokenDataHandler? = nil, unauthenticatedHandler: MXRestClientUnauthenticatedHandler? = nil) { + self.init(__credentials: credentials, andOnUnrecognizedCertificateBlock: handler, andPersistentTokenDataHandler: persistentTokenDataHandler, andUnauthenticatedHandler: unauthenticatedHandler) } diff --git a/MatrixSDK/Crypto/MXCrypto.m b/MatrixSDK/Crypto/MXCrypto.m index 6191ec9065..0fba5e1287 100644 --- a/MatrixSDK/Crypto/MXCrypto.m +++ b/MatrixSDK/Crypto/MXCrypto.m @@ -1915,7 +1915,7 @@ - (instancetype)initWithMatrixSession:(MXSession*)matrixSession cryptoQueue:(dis _deviceList = [[MXDeviceList alloc] initWithCrypto:self]; // Use our own REST client that answers on the crypto thread - _matrixRestClient = [[MXRestClient alloc] initWithCredentials:_mxSession.matrixRestClient.credentials andOnUnrecognizedCertificateBlock:nil]; + _matrixRestClient = [[MXRestClient alloc] initWithCredentials:_mxSession.matrixRestClient.credentials andOnUnrecognizedCertificateBlock:nil andPersistentTokenDataHandler:_mxSession.matrixRestClient.persistTokenDataHandler andUnauthenticatedHandler:_mxSession.matrixRestClient.unauthenticatedHandler]; _matrixRestClient.completionQueue = _cryptoQueue; roomEncryptors = [NSMutableDictionary dictionary]; diff --git a/MatrixSDK/Data/MXCredentials.h b/MatrixSDK/Data/MXCredentials.h index d06345a30a..7d16d057dc 100644 --- a/MatrixSDK/Data/MXCredentials.h +++ b/MatrixSDK/Data/MXCredentials.h @@ -18,6 +18,7 @@ #import @class MXLoginResponse; +@class MXRefreshResponse; NS_ASSUME_NONNULL_BEGIN @@ -47,6 +48,16 @@ NS_ASSUME_NONNULL_BEGIN */ @property (nonatomic, nullable) NSString *accessToken; +/** + The timestamp in milliseconds for when the access token will expire + */ +@property (nonatomic) uint64_t accessTokenExpiresAt; + +/** + The refresh token, which can be used to obtain new access tokens. (optional) +*/ +@property (nonatomic, nullable) NSString *refreshToken; + /** The access token to create a MXIdentityServerRestClient */ diff --git a/MatrixSDK/Data/MXCredentials.m b/MatrixSDK/Data/MXCredentials.m index 8eb47bb171..9c9ee8d991 100644 --- a/MatrixSDK/Data/MXCredentials.m +++ b/MatrixSDK/Data/MXCredentials.m @@ -20,6 +20,8 @@ #import "MXJSONModels.h" #import "MXTools.h" +#import "MXRestClient.h" +#import "MXRefreshTokenData.h" @implementation MXCredentials @@ -31,6 +33,8 @@ - (instancetype)initWithHomeServer:(NSString *)homeServer userId:(NSString *)use _homeServer = [homeServer copy]; _userId = [userId copy]; _accessToken = [accessToken copy]; + + [self registerRestClientWillRefreshTokensNotification]; } return self; } @@ -43,6 +47,8 @@ - (instancetype)initWithLoginResponse:(MXLoginResponse*)loginResponse { _userId = loginResponse.userId; _accessToken = loginResponse.accessToken; + _accessTokenExpiresAt = ((uint64_t)[NSDate date].timeIntervalSince1970 * 1000) + loginResponse.expiresInMs; + _refreshToken = loginResponse.refreshToken; _deviceId = loginResponse.deviceId; _loginOthers = loginResponse.others; @@ -91,10 +97,16 @@ - (instancetype)initWithLoginResponse:(MXLoginResponse*)loginResponse { _identityServer = [defaultCredentials.identityServer copy]; } + [self registerRestClientWillRefreshTokensNotification]; } return self; } +- (void)dealloc +{ + [self unregisterRestClientWillRefreshTokensNotification]; +} + + (instancetype)initialSyncCacheCredentialsFrom:(MXCredentials *)credentials { MXCredentials *result = [credentials copy]; @@ -119,17 +131,21 @@ - (BOOL)isEqual:(id)other return [_homeServer isEqualToString:otherCredentials.homeServer] && [_userId isEqualToString:otherCredentials.userId] - && [_accessToken isEqualToString:otherCredentials.accessToken]; + && [_accessToken isEqualToString:otherCredentials.accessToken] + && _accessTokenExpiresAt == otherCredentials.accessTokenExpiresAt + && ((_refreshToken == nil && otherCredentials.refreshToken == nil) || [_refreshToken isEqualToString:otherCredentials.refreshToken]); } - (NSUInteger)hash { NSUInteger prime = 31; NSUInteger result = 1; - + result = prime * result + [_homeServer hash]; result = prime * result + [_userId hash]; result = prime * result + [_accessToken hash]; + result = prime * result + (NSUInteger)_accessTokenExpiresAt; + result = prime * result + [_refreshToken hash]; return result; } @@ -143,6 +159,9 @@ - (id)copyWithZone:(NSZone *)zone credentials.userId = [_userId copyWithZone:zone]; credentials.homeServer = [_homeServer copyWithZone:zone]; credentials.accessToken = [_accessToken copyWithZone:zone]; + credentials.accessTokenExpiresAt = _accessTokenExpiresAt; + credentials.refreshToken = [_refreshToken copyWithZone:zone]; + credentials.accessToken = [_accessToken copyWithZone:zone]; credentials.identityServer = [_identityServer copyWithZone:zone]; credentials.identityServerAccessToken = [_identityServerAccessToken copyWithZone:zone]; credentials.deviceId = [_deviceId copyWithZone:zone]; @@ -152,4 +171,31 @@ - (id)copyWithZone:(NSZone *)zone return credentials; } +#pragma mark - Homeserver Access/Refresh Token updates + +- (void)registerRestClientWillRefreshTokensNotification +{ + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleRestClientWillRefreshTokensNotification:) name:MXCredentialsUpdateTokensNotification object:nil]; +} + +- (void)unregisterRestClientWillRefreshTokensNotification +{ + [[NSNotificationCenter defaultCenter] removeObserver:self name:MXCredentialsUpdateTokensNotification object:nil]; +} + +- (void)handleRestClientWillRefreshTokensNotification:(NSNotification*)notification +{ + MXRefreshTokenData *tokenData = notification.userInfo[kMXCredentialsNewRefreshTokenDataKey]; + + if(tokenData && tokenData.userId && self.userId && [self.userId isEqualToString:tokenData.userId] + && tokenData.homeserver && self.homeServer && [tokenData.homeserver isEqualToString:self.homeServer]) + { + if (tokenData.refreshToken) { + self.refreshToken = tokenData.refreshToken; + } + self.accessToken = tokenData.accessToken; + self.accessTokenExpiresAt = tokenData.accessTokenExpiresAt; + } +} + @end diff --git a/MatrixSDK/Data/MXRefreshTokenData.h b/MatrixSDK/Data/MXRefreshTokenData.h new file mode 100644 index 0000000000..dcc32569cd --- /dev/null +++ b/MatrixSDK/Data/MXRefreshTokenData.h @@ -0,0 +1,53 @@ +// +// Copyright 2021 New Vector 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. +// + +/** + `MXRefreshTokenData` used to update `MXCredentials` that it's token data has updated. see `MXCredentialsUpdateTokensNotification`. + */ +@interface MXRefreshTokenData : NSObject + +/** + The user id. + */ +@property (nonatomic, readonly, nonnull) NSString *userId; + +/** + The homeserver URL. + */ +@property (nonatomic, readonly, nonnull) NSString *homeserver; + + +/** + The access token to create a MXRestClient + */ +@property (nonatomic, readonly, nonnull) NSString *accessToken; + +/** + The timestamp in milliseconds for when the access token will expire + */ +@property (nonatomic, readonly) uint64_t accessTokenExpiresAt; + +/** + The refresh token, which can be used to obtain new access tokens. (optional) +*/ +@property (nonatomic, readonly, nullable) NSString *refreshToken; + +- (instancetype _Nonnull)initWithUserId:(NSString*_Nonnull)userId + homeserver:(NSString*_Nonnull)homeserver + accessToken:(NSString*_Nonnull)accessToken + refreshToken:(NSString*_Nonnull)refreshToken + accessTokenExpiresAt:(uint64_t)accessTokenExpiresAt; +@end diff --git a/MatrixSDK/Data/MXRefreshTokenData.m b/MatrixSDK/Data/MXRefreshTokenData.m new file mode 100644 index 0000000000..b153189634 --- /dev/null +++ b/MatrixSDK/Data/MXRefreshTokenData.m @@ -0,0 +1,30 @@ +// +// MXRefreshTokenData.m +// MatrixSDK +// +// Created by David Langley on 17/12/2021. +// + +#import +#import "MXRefreshTokenData.h" + +@implementation MXRefreshTokenData +- (instancetype)initWithUserId:(NSString*)userId + homeserver:(NSString*)homeserver + accessToken:(NSString*)accessToken + refreshToken:(NSString*)refreshToken + accessTokenExpiresAt:(uint64_t)accessTokenExpiresAt +{ + self = [super init]; + if (self) + { + _userId = userId; + _homeserver = homeserver; + _accessToken = accessToken; + _refreshToken = refreshToken; + _accessTokenExpiresAt = accessTokenExpiresAt; + } + return self; +} + +@end diff --git a/MatrixSDK/IdentityServer/MXIdentityServerRestClient.h b/MatrixSDK/IdentityServer/MXIdentityServerRestClient.h index 3a03f56449..d290962220 100644 --- a/MatrixSDK/IdentityServer/MXIdentityServerRestClient.h +++ b/MatrixSDK/IdentityServer/MXIdentityServerRestClient.h @@ -80,14 +80,14 @@ NS_ERROR_ENUM(MXIdentityServerRestClientErrorDomain) @property (nonatomic, strong, nullable) NSString *preferredAPIPathPrefix; /** - Block called when a request needs authorization and access token should be renewed. + Block called when an authenticated request fails and is used to validate the response error. */ -@property (nonatomic, copy) MXHTTPClientShouldRenewTokenHandler shouldRenewTokenHandler; +@property (nonatomic, copy) MXHTTPClientTokenValidationResponseHandler tokenValidationResponseHandler; /** - Block called when a request fails and needs authorization to determine if the access token should be renewed. + Block called when an authenticated request requires an access token. */ -@property (nonatomic, copy) MXHTTPClientRenewTokenHandler renewTokenHandler; +@property (nonatomic, copy) MXHTTPClientTokenProviderHandler tokenProviderHandler; #pragma mark - Setup @@ -319,20 +319,6 @@ NS_ERROR_ENUM(MXIdentityServerRestClientErrorDomain) success:(void (^)(NSDictionary *thirdPartySigned))success failure:(void (^)(NSError *error))failure NS_REFINED_FOR_SWIFT; -/** - Get current access or get a new one if not exist. - Note: There is no guarantee that current access token is valid. - - @param success A block object called when the operation succeeds. It provides the access token. - @param failure A block object called when the operation fails. - - @return a MXHTTPOperation instance. Nil if the access token is already known - and no HTTP request is required. - */ -- (MXHTTPOperation *)getAccessTokenAndRenewIfNeededWithSuccess:(void (^)(NSString *accessToken))success - failure:(void (^)(NSError *error))failure; - - @end NS_ASSUME_NONNULL_END diff --git a/MatrixSDK/IdentityServer/MXIdentityServerRestClient.m b/MatrixSDK/IdentityServer/MXIdentityServerRestClient.m index 59d6bfcd61..68279159c6 100644 --- a/MatrixSDK/IdentityServer/MXIdentityServerRestClient.m +++ b/MatrixSDK/IdentityServer/MXIdentityServerRestClient.m @@ -63,24 +63,24 @@ - (NSString *)accessToken return self.httpClient.accessToken; } -- (void)setShouldRenewTokenHandler:(MXHTTPClientShouldRenewTokenHandler)shouldRenewTokenHandler +- (void)setTokenValidationResponseHandler:(MXHTTPClientTokenValidationResponseHandler)tokenValidationResponseHandler { - self.httpClient.shouldRenewTokenHandler = shouldRenewTokenHandler; + self.httpClient.tokenValidationResponseHandler = tokenValidationResponseHandler; } -- (MXHTTPClientShouldRenewTokenHandler)shouldRenewTokenHandler +- (MXHTTPClientTokenValidationResponseHandler)tokenValidationResponseHandler { - return self.httpClient.shouldRenewTokenHandler; + return self.httpClient.tokenValidationResponseHandler; } -- (void)setRenewTokenHandler:(MXHTTPClientRenewTokenHandler)renewTokenHandler +- (void)setTokenProviderHandler:(MXHTTPClientTokenProviderHandler)tokenProviderHandler { - self.httpClient.renewTokenHandler = renewTokenHandler; + self.httpClient.tokenProviderHandler = tokenProviderHandler; } -- (MXHTTPClientRenewTokenHandler)renewTokenHandler +- (MXHTTPClientTokenProviderHandler)tokenProviderHandler { - return self.httpClient.renewTokenHandler; + return self.httpClient.tokenProviderHandler; } - (BOOL)isUsingV2API @@ -97,7 +97,7 @@ - (instancetype)initWithIdentityServer:(NSString *)identityServer self = [super init]; if (self) { - MXHTTPClient *httpClient = [[MXHTTPClient alloc] initWithBaseURL:identityServer accessToken:accessToken andOnUnrecognizedCertificateBlock:onUnrecognizedCertBlock]; + MXHTTPClient *httpClient = [[MXHTTPClient alloc] initWithBaseURL:identityServer authenticated:accessToken!=nil andOnUnrecognizedCertificateBlock:onUnrecognizedCertBlock]; // The identity server accepts parameters in form data form for some requests httpClient.requestParametersInJSON = NO; @@ -173,7 +173,7 @@ - (MXHTTPOperation*)accountWithSuccess:(void (^)(NSString *userId))success return [self.httpClient requestWithMethod:@"GET" path:path parameters:nil - needsAuthorization:YES + needsAuthentication:YES success:^(NSDictionary *JSONResponse) { if (success) { @@ -316,7 +316,7 @@ - (MXHTTPOperation*)hashDetailsWithSuccess:(void (^)(MXIdentityServerHashDetails return [self.httpClient requestWithMethod:@"GET" path:path parameters:nil - needsAuthorization:YES + needsAuthentication:YES success:^(NSDictionary *JSONResponse) { if (success) { @@ -417,7 +417,7 @@ - (MXHTTPOperation*)lookup3pids:(NSArray*> *)threepids return [self.httpClient requestWithMethod:@"POST" path:path parameters:nil - needsAuthorization:YES + needsAuthentication:YES data:payloadData headers:@{@"Content-Type": @"application/json"} timeout:-1 @@ -517,7 +517,7 @@ - (MXHTTPOperation*)requestEmailValidation:(NSString*)email return [self.httpClient requestWithMethod:@"POST" path:path parameters:nil - needsAuthorization:self.isUsingV2API + needsAuthentication:self.isUsingV2API data:payloadData headers:@{@"Content-Type": @"application/json"} timeout:-1 @@ -571,7 +571,7 @@ - (MXHTTPOperation*)requestPhoneNumberValidation:(NSString*)phoneNumber return [self.httpClient requestWithMethod:@"POST" path:path parameters:nil - needsAuthorization:self.isUsingV2API + needsAuthentication:self.isUsingV2API data:payloadData headers:@{@"Content-Type": @"application/json"} timeout:-1 @@ -629,7 +629,7 @@ - (MXHTTPOperation *)submit3PIDValidationToken:(NSString *)token return [self.httpClient requestWithMethod:@"POST" path:apiPath parameters:nil - needsAuthorization:self.isUsingV2API + needsAuthentication:self.isUsingV2API data:payloadData headers:@{@"Content-Type": @"application/json"} timeout:-1 @@ -738,12 +738,6 @@ - (MXHTTPOperation*)signUrl:(NSString*)signUrl }]; } -- (MXHTTPOperation *)getAccessTokenAndRenewIfNeededWithSuccess:(void (^)(NSString *accessToken))success - failure:(void (^)(NSError *error))failure -{ - return [self.httpClient getAccessTokenAndRenewIfNeededWithSuccess:success failure:failure]; -} - #pragma mark - Private methods /** diff --git a/MatrixSDK/IdentityServer/MXIdentityService.m b/MatrixSDK/IdentityServer/MXIdentityService.m index 5b14dfa1f0..5aae018798 100644 --- a/MatrixSDK/IdentityServer/MXIdentityService.m +++ b/MatrixSDK/IdentityServer/MXIdentityService.m @@ -91,7 +91,7 @@ - (instancetype)initWithIdentityServerRestClient:(MXIdentityServerRestClient*)id self = [super init]; if (self) { - identityServerRestClient.shouldRenewTokenHandler = ^BOOL(NSError *error) { + identityServerRestClient.tokenValidationResponseHandler = ^BOOL(NSError *error) { BOOL shouldRenewAccesToken = NO; @@ -100,6 +100,7 @@ - (instancetype)initWithIdentityServerRestClient:(MXIdentityServerRestClient*)id MXError *mxError = [[MXError alloc] initWithNSError:error]; if ([mxError.errcode isEqualToString:kMXErrCodeStringUnauthorized]) { + self.accessToken = nil; shouldRenewAccesToken = YES; } } @@ -108,14 +109,9 @@ - (instancetype)initWithIdentityServerRestClient:(MXIdentityServerRestClient*)id }; MXWeakify(self); - - identityServerRestClient.renewTokenHandler = ^MXHTTPOperation* (void (^success)(NSString *), void (^failure)(NSError *)) { - MXStrongifyAndReturnValueIfNil(self, nil); - - return [self renewAccessTokenWithSuccess:^(NSString *accessToken) { - self.accessToken = accessToken; - success(accessToken); - } failure:failure]; + identityServerRestClient.tokenProviderHandler = ^(NSError *error, void (^success)(NSString *accessToken), void (^failure)(NSError *error)) { + MXStrongifyAndReturnIfNil(self); + [self accessTokenWithSuccess:success failure:failure]; }; self.restClient = identityServerRestClient; @@ -139,10 +135,9 @@ - (nullable MXHTTPOperation *)accessTokenWithSuccess:(void (^)(NSString * _Nulla success(self.accessToken); return nil; } - - return [self.restClient getAccessTokenAndRenewIfNeededWithSuccess:^(NSString * _Nonnull accessToken) { - // If we get here, we have an access token - success(self.accessToken); + return [self renewAccessTokenWithSuccess:^(NSString *accessToken) { + self.accessToken = accessToken; + success(accessToken); } failure:failure]; } diff --git a/MatrixSDK/JSONModels/MXJSONModels.h b/MatrixSDK/JSONModels/MXJSONModels.h index 9147a6b8e7..dce13c530f 100644 --- a/MatrixSDK/JSONModels/MXJSONModels.h +++ b/MatrixSDK/JSONModels/MXJSONModels.h @@ -222,6 +222,16 @@ FOUNDATION_EXPORT NSString *const kMXLoginIdentifierTypePhone; */ @property (nonatomic) NSString *accessToken; + /** + The lifetime in milliseconds of the access token. (optional) + */ + @property (nonatomic) uint64_t expiresInMs; + + /** + The refresh token, which can be used to obtain new access tokens. (optional) + */ + @property (nonatomic) NSString *refreshToken; + /** The device id. */ @@ -234,7 +244,6 @@ FOUNDATION_EXPORT NSString *const kMXLoginIdentifierTypePhone; @end - /** `MXThirdPartyIdentifier` represents the response to /account/3pid GET request. */ @@ -262,7 +271,6 @@ FOUNDATION_EXPORT NSString *const kMXLoginIdentifierTypePhone; @end - /** `MXCreateRoomResponse` represents the response to createRoom request. */ diff --git a/MatrixSDK/JSONModels/MXJSONModels.m b/MatrixSDK/JSONModels/MXJSONModels.m index befebb2ded..83706ad0b6 100644 --- a/MatrixSDK/JSONModels/MXJSONModels.m +++ b/MatrixSDK/JSONModels/MXJSONModels.m @@ -204,6 +204,8 @@ + (id)modelFromJSON:(NSDictionary *)JSONDictionary MXJSONModelSetString(loginResponse.homeserver, JSONDictionary[@"home_server"]); MXJSONModelSetString(loginResponse.userId, JSONDictionary[@"user_id"]); MXJSONModelSetString(loginResponse.accessToken, JSONDictionary[@"access_token"]); + MXJSONModelSetUInt64(loginResponse.expiresInMs, JSONDictionary[@"expires_in_ms"]); + MXJSONModelSetString(loginResponse.refreshToken, JSONDictionary[@"refresh_token"]); MXJSONModelSetString(loginResponse.deviceId, JSONDictionary[@"device_id"]); MXJSONModelSetMXJSONModel(loginResponse.wellknown, MXWellKnown, JSONDictionary[@"well_known"]); diff --git a/MatrixSDK/JSONModels/MXRefreshResponse.h b/MatrixSDK/JSONModels/MXRefreshResponse.h new file mode 100644 index 0000000000..f6e4e34930 --- /dev/null +++ b/MatrixSDK/JSONModels/MXRefreshResponse.h @@ -0,0 +1,38 @@ +// +// Copyright 2021 The Matrix.org Foundation C.I.C +// +// 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 "MXJSONModel.h" + +/** + `MXRefreshResponse` represents the response to an auth refresh request. + */ +@interface MXRefreshResponse : MXJSONModel + /** + The access token to create a MXRestClient + */ + @property (nonatomic, nonnull) NSString *accessToken; + + /** + The lifetime in milliseconds of the access token. (optional) + */ + @property (nonatomic) uint64_t expiresInMs; + + /** + The refresh token, which can be used to obtain new access tokens. (optional) + */ + @property (nonatomic, nullable) NSString *refreshToken; + +@end diff --git a/MatrixSDK/JSONModels/MXRefreshResponse.m b/MatrixSDK/JSONModels/MXRefreshResponse.m new file mode 100644 index 0000000000..8af2cc8154 --- /dev/null +++ b/MatrixSDK/JSONModels/MXRefreshResponse.m @@ -0,0 +1,35 @@ +// +// Copyright 2021 The Matrix.org Foundation C.I.C +// +// 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 "MXRefreshResponse.h" + +@implementation MXRefreshResponse + ++ (id)modelFromJSON:(NSDictionary *)JSONDictionary +{ + MXRefreshResponse *refreshResponse = [[MXRefreshResponse alloc] init]; + if (refreshResponse) + { + MXJSONModelSetString(refreshResponse.accessToken, JSONDictionary[@"access_token"]); + MXJSONModelSetUInt64(refreshResponse.expiresInMs, JSONDictionary[@"expires_in_ms"]); + MXJSONModelSetString(refreshResponse.refreshToken, JSONDictionary[@"refresh_token"]); + } + + return refreshResponse; +} + +@end diff --git a/MatrixSDK/MXError.h b/MatrixSDK/MXError.h index b206f97a7e..5dbcd99206 100644 --- a/MatrixSDK/MXError.h +++ b/MatrixSDK/MXError.h @@ -56,6 +56,8 @@ FOUNDATION_EXPORT NSString *const kMXErrCodeStringWeakPassword; FOUNDATION_EXPORT NSString *const kMXErrCodeStringTermsNotSigned; FOUNDATION_EXPORT NSString *const kMXErrCodeStringInvalidPepper; +FOUNDATION_EXPORT NSString *const kMXErrCodeStringClientError; + FOUNDATION_EXPORT NSString *const kMXErrorStringInvalidToken; diff --git a/MatrixSDK/MXError.m b/MatrixSDK/MXError.m index 0e1af51b75..ca6f0fbe4c 100644 --- a/MatrixSDK/MXError.m +++ b/MatrixSDK/MXError.m @@ -53,6 +53,9 @@ NSString *const kMXErrCodeStringTermsNotSigned = @"M_TERMS_NOT_SIGNED"; NSString *const kMXErrCodeStringInvalidPepper = @"M_INVALID_PEPPER"; +/* Used for client side generated errors */ +NSString *const kMXErrCodeStringClientError = @"CLIENT_ERROR"; + NSString *const kMXErrorStringInvalidToken = @"Invalid token"; NSString *const kMXErrorCodeKey = @"errcode"; diff --git a/MatrixSDK/MXRestClient.h b/MatrixSDK/MXRestClient.h index df98546bb8..8718bc034c 100644 --- a/MatrixSDK/MXRestClient.h +++ b/MatrixSDK/MXRestClient.h @@ -116,6 +116,18 @@ FOUNDATION_EXPORT NSString *const kMXMembersOfRoomParametersNotMembership; */ typedef MXHTTPOperation* (^MXRestClientIdentityServerAccessTokenHandler)(void (^success)(NSString *accessToken), void (^failure)(NSError *error)); +/** + Block called when the rest client has become unauthenticated(E.g. refresh failed or server invalidated an access token). + + @param error The error from the failed refresh. + */ +typedef void(^MXRestClientUnauthenticatedHandler)(MXError *error, BOOL isSoftLogout, BOOL isRefreshTokenAuth, void (^completion)(void)); + +/** + Block called when the rest client needs to check the persisted refresh token data is valid and optionally persist new refresh data to disk if it is not. + @param handler A closure that accepts the current persisted credentials. These can optionally be updated and saved back initWithCredentials returning YES from the closure. + */ +typedef void (^MXRestClientPersistTokenDataHandler)(void (^handler)(NSArray *credentials, void (^shouldPersistCompletion)(BOOL didUpdateCredentials))); /** `MXRestClient` makes requests to Matrix servers. @@ -127,11 +139,31 @@ typedef MXHTTPOperation* (^MXRestClientIdentityServerAccessTokenHandler)(void (^ */ @interface MXRestClient : NSObject +/** + Notification name sent when the refresh/access tokens should be updated in the credential. userInfo contains 'kMXCredentialsNewRefreshTokenDataKey'. + */ +extern NSString *const MXCredentialsUpdateTokensNotification; + +/** + A key for getting the refresh response from `MXCredentialsWillUpdateTokensNotification` userInfo. + */ +extern NSString *const kMXCredentialsNewRefreshTokenDataKey; + /** Credentials for the Matrix Client-Server API. */ @property (nonatomic, readonly) MXCredentials *credentials; +/** + Block called when the rest client failed to refresh it's tokens and session is now unauthenticated. + */ +@property (nonatomic, copy) MXRestClientUnauthenticatedHandler unauthenticatedHandler; + +/** + Block called when the rest client needs to check the persisted refresh token data is valid and optionally persist new data to disk if it is not. + */ +@property (nonatomic, copy) MXRestClientPersistTokenDataHandler persistTokenDataHandler; + /** The homeserver URL. Shortcut to credentials.homeServer. @@ -213,7 +245,26 @@ typedef MXHTTPOperation* (^MXRestClientIdentityServerAccessTokenHandler)(void (^ @param onUnrecognizedCertBlock the block called to handle unrecognized certificate (nil if unrecognized certificates are ignored). @return a MXRestClient instance. */ --(id)initWithCredentials:(MXCredentials*)credentials andOnUnrecognizedCertificateBlock:(MXHTTPClientOnUnrecognizedCertificate)onUnrecognizedCertBlock NS_REFINED_FOR_SWIFT; + +-(id)initWithCredentials:(MXCredentials*)credentials +andOnUnrecognizedCertificateBlock:(MXHTTPClientOnUnrecognizedCertificate)onUnrecognizedCertBlock +NS_REFINED_FOR_SWIFT; + +/** + Create an instance based on a matrix user account. + + @param credentials the response to a login or a register request. + @param onUnrecognizedCertBlock the block called to handle unrecognized certificate (nil if unrecognized certificates are ignored). + @param persistentTokenDataHandler the block called when the rest client needs to check the persisted refresh token data is valid and optionally persist new refresh data to disk if it is not. + @param unauthenticatedHandler the block called when the rest client has become unauthenticated(E.g. refresh failed or server invalidated an access token). + @return a MXRestClient instance. + */ + +-(id)initWithCredentials:(MXCredentials*)credentials +andOnUnrecognizedCertificateBlock:(MXHTTPClientOnUnrecognizedCertificate)onUnrecognizedCertBlock +andPersistentTokenDataHandler: (MXRestClientPersistTokenDataHandler)persistentTokenDataHandler +andUnauthenticatedHandler: (MXRestClientUnauthenticatedHandler)unauthenticatedHandler +NS_REFINED_FOR_SWIFT; - (void)close; diff --git a/MatrixSDK/MXRestClient.m b/MatrixSDK/MXRestClient.m index f074f2757a..3c17830759 100644 --- a/MatrixSDK/MXRestClient.m +++ b/MatrixSDK/MXRestClient.m @@ -21,6 +21,7 @@ #import "MXSDKOptions.h" #import "MXJSONModel.h" +#import "MXRefreshResponse.h" #import "MXTools.h" #import "MXError.h" @@ -28,7 +29,7 @@ #import "MXThirdpartyProtocolsResponse.h" #import "MXThirdPartyUsersResponse.h" - +#import "MXRefreshTokenData.h" #import "MatrixSDKSwiftHeader.h" #pragma mark - Constants definitions @@ -36,6 +37,7 @@ Prefix used in path of home server API requests. */ NSString *const kMXAPIPrefixPathR0 = @"_matrix/client/r0"; +NSString *const kMXAPIPrefixPathV1 = @"_matrix/client/v1"; NSString *const kMXAPIPrefixPathUnstable = @"_matrix/client/unstable"; /** @@ -74,6 +76,15 @@ NSString *const kMXMembersOfRoomParametersMembership = @"membership"; NSString *const kMXMembersOfRoomParametersNotMembership = @"not_membership"; +NSString *const MXCredentialsUpdateTokensNotification = @"MXCredentialsUpdateTokensNotification"; +NSString *const kMXCredentialsNewRefreshTokenDataKey = @"refresh_token_data"; +/** + The time interval before the access token expires that we will start trying to refresh the token. + This avoids us having to block other users requests while the token refreshes. + Choosing a value larger than SERVER_TIMEOUT_MS guarantees an authenticated request will be attempted(causing a refresh) before the token expires. + */ +#define PREEMPT_REFRESH_EXPIRATION_INTERVAL 60000 + /** Authentication flow: register or login */ @@ -109,15 +120,50 @@ @interface MXRestClient () @implementation MXRestClient @synthesize credentials, apiPathPrefix, contentPathPrefix, completionQueue, antivirusServerPathPrefix; ++ (dispatch_queue_t)refreshQueue +{ + static dispatch_once_t pred = 0; + static id _refreshQueue = nil; + dispatch_once(&pred, ^{ + _refreshQueue = dispatch_queue_create("MXRestClient.refreshQueue", DISPATCH_QUEUE_SERIAL); + }); + return _refreshQueue; + +} + ++ (dispatch_group_t)refreshDispatchGroup +{ + static dispatch_once_t pred = 0; + static id _refreshDispatchGroup = nil; + dispatch_once(&pred, ^{ + _refreshDispatchGroup = dispatch_group_create(); + }); + return _refreshDispatchGroup; +} + -(id)initWithHomeServer:(NSString *)homeserver andOnUnrecognizedCertificateBlock:(MXHTTPClientOnUnrecognizedCertificate)onUnrecognizedCertBlock { MXCredentials *credentials = [MXCredentials new]; credentials.homeServer = homeserver; - return [self initWithCredentials:credentials andOnUnrecognizedCertificateBlock:onUnrecognizedCertBlock]; + return [self initWithCredentials:credentials andOnUnrecognizedCertificateBlock:onUnrecognizedCertBlock andPersistentTokenDataHandler:nil andUnauthenticatedHandler:nil]; +} + +-(id)initWithCredentials:(MXCredentials*)inCredentials +andOnUnrecognizedCertificateBlock:(MXHTTPClientOnUnrecognizedCertificate)onUnrecognizedCertBlock +{ + return [self initWithCredentials:credentials andOnUnrecognizedCertificateBlock:onUnrecognizedCertBlock andPersistentTokenDataHandler:nil andUnauthenticatedHandler:nil]; +} + +-(id)initWithCredentials:(MXCredentials*)inCredentials +{ + return [self initWithCredentials:inCredentials andOnUnrecognizedCertificateBlock:nil andPersistentTokenDataHandler:nil andUnauthenticatedHandler:nil]; } --(id)initWithCredentials:(MXCredentials*)inCredentials andOnUnrecognizedCertificateBlock:(MXHTTPClientOnUnrecognizedCertificate)onUnrecognizedCertBlock +-(id)initWithCredentials:(MXCredentials*)inCredentials +andOnUnrecognizedCertificateBlock:(MXHTTPClientOnUnrecognizedCertificate)onUnrecognizedCertBlock +andPersistentTokenDataHandler: (MXRestClientPersistTokenDataHandler)persistentTokenDataHandler +andUnauthenticatedHandler: (MXRestClientUnauthenticatedHandler)unauthenticatedHandler { self = [super init]; if (self) @@ -128,61 +174,255 @@ -(id)initWithCredentials:(MXCredentials*)inCredentials andOnUnrecognizedCertific credentials = inCredentials; _identityServer = credentials.identityServer; - + _unauthenticatedHandler = unauthenticatedHandler; + _persistTokenDataHandler = persistentTokenDataHandler; if (credentials.homeServer) { httpClient = [[MXHTTPClient alloc] initWithBaseURL:credentials.homeServer - accessToken:credentials.accessToken + authenticated: credentials.accessToken != nil andOnUnrecognizedCertificateBlock:^BOOL(NSData *certificate) { - - // Check whether the provided certificate has been already trusted - if ([[MXAllowedCertificates sharedInstance] isCertificateAllowed:certificate]) - { - return YES; - } - - // Check whether the provided certificate is the already trusted by the user. - if (inCredentials.allowedCertificate && [inCredentials.allowedCertificate isEqualToData:certificate]) - { - // Store the allowed certificate for further requests (from MXMediaManager) - [[MXAllowedCertificates sharedInstance] addCertificate:certificate]; - return YES; - } - - // Check whether the user has already ignored this certificate change. - if (inCredentials.ignoredCertificate && [inCredentials.ignoredCertificate isEqualToData:certificate]) - { - return NO; - } - - // Let the app ask the end user to verify it - if (onUnrecognizedCertBlock) - { - BOOL allowed = onUnrecognizedCertBlock(certificate); - - if (allowed) - { - // Store the allowed certificate for further requests - [[MXAllowedCertificates sharedInstance] addCertificate:certificate]; - } - - return allowed; - } - else - { - return NO; - } - }]; + + // Check whether the provided certificate has been already trusted + if ([[MXAllowedCertificates sharedInstance] isCertificateAllowed:certificate]) + { + return YES; + } + + // Check whether the provided certificate is the already trusted by the user. + if (inCredentials.allowedCertificate && [inCredentials.allowedCertificate isEqualToData:certificate]) + { + // Store the allowed certificate for further requests (from MXMediaManager) + [[MXAllowedCertificates sharedInstance] addCertificate:certificate]; + return YES; + } + + // Check whether the user has already ignored this certificate change. + if (inCredentials.ignoredCertificate && [inCredentials.ignoredCertificate isEqualToData:certificate]) + { + return NO; + } + + // Let the app ask the end user to verify it + if (onUnrecognizedCertBlock) + { + BOOL allowed = onUnrecognizedCertBlock(certificate); + + if (allowed) + { + // Store the allowed certificate for further requests + [[MXAllowedCertificates sharedInstance] addCertificate:certificate]; + } + + return allowed; + } + else + { + return NO; + } + }]; + + httpClient.tokenValidationResponseHandler = ^BOOL(NSError *error) { + if (![MXError isMXError:error]) + { + return NO; + } + MXError *mxError = [[MXError alloc] initWithNSError:error]; + return [mxError.errcode isEqualToString:kMXErrCodeStringUnknownToken]; + }; + MXWeakify(self); + httpClient.tokenProviderHandler = ^(NSError *error, void (^success)(NSString *accessToken), void (^failure)(NSError *error)) { + MXStrongifyAndReturnIfNil(self); + dispatch_async([MXRestClient refreshQueue], ^{ + NSString *logId = [NSString stringWithFormat:@"%d-%@", [[NSProcessInfo processInfo] processIdentifier], [[NSUUID UUID] UUIDString]]; + MXLogDebug(@"[MXRestClient] tokenProviderHandler: %@ refreshQueue enter - %@", logId, [MXRestClient refreshDispatchGroup]); + + // If refreshDispatchGroup is unmatched(a request for a new access token is in-flight) wait. + dispatch_group_wait([MXRestClient refreshDispatchGroup], DISPATCH_TIME_FOREVER); + if(!weakself.completionQueue) { + MXLogError(@"[MXRestClient] tokenProviderHandler: %@ Client closed, exit tokenProviderHandler early", logId); + return; + } + MXLogDebug(@"[MXRestClient] tokenProviderHandler: %@ Wait finished", logId); + if(self.credentials.accessTokenExpiresAt) + { + NSDate *expiry = [NSDate dateWithTimeIntervalSince1970:self.credentials.accessTokenExpiresAt/1000]; + NSDate *preemptiveExpiry = [NSDate dateWithTimeIntervalSince1970:(self.credentials.accessTokenExpiresAt - PREEMPT_REFRESH_EXPIRATION_INTERVAL)/1000]; + MXLogDebug(@"[MXRestClient] tokenProviderHandler: %@ - server expiry: %@ preemptive expiry %@", logId, expiry, preemptiveExpiry); + } + MXError *mxError = [[MXError alloc] initWithNSError:error]; + BOOL isTokenUnknownResponse = mxError && [mxError.errcode isEqualToString:kMXErrCodeStringUnknownToken]; + if(!self.credentials.refreshToken && isTokenUnknownResponse) + { + // non-refresh token auth failed + dispatch_async(self.completionQueue, ^{ + BOOL isSoftLogout = [MXRestClient isSoftLogout:mxError]; + MXLogDebug(@"[MXRestClient] tokenProviderHandler: %@: non-refresh(access token) token auth failed", logId); + self.unauthenticatedHandler(mxError, isSoftLogout, NO, ^{ + failure(error); + }); + }); + return; + } + if (!self.credentials.refreshToken || (!isTokenUnknownResponse && (!self.credentials.accessTokenExpiresAt || [NSDate date].timeIntervalSince1970 * 1000 < (self.credentials.accessTokenExpiresAt - PREEMPT_REFRESH_EXPIRATION_INTERVAL)))) { + // If it's non-refresh token auth return the access token, + // or if it is refresh token auth and access token is valid also return it. + MXLogDebug(@"[MXRestClient] tokenProviderHandler: %@ success token %@, %tu", logId, self.credentials.refreshToken, (NSUInteger)self.credentials.accessTokenExpiresAt) + dispatch_async(self.completionQueue, ^{ + success(self.credentials.accessToken); + }); + return; + } + + // Continue with token refresh if access token is not valid(indicated by server response or the expiry date) + NSAssert(self.persistTokenDataHandler, @"If we are attempting to refresh there must be a way to persist the tokens to share across processes."); + + dispatch_group_enter([MXRestClient refreshDispatchGroup]); + + MXWeakify(self); + [self startTokenRefreshWithLogId:logId andRefreshCompletion:^(NSString *accessToken) { + if(!weakself || !weakself.completionQueue) { + dispatch_group_leave([MXRestClient refreshDispatchGroup]); + MXLogWarning(@"[MXRestClient] tokenProviderHandler: %@ Client closed, exit tokenProviderHandler early.", logId); + return; + } + MXStrongifyAndReturnIfNil(self); + dispatch_group_leave([MXRestClient refreshDispatchGroup]); + dispatch_async(self.completionQueue, ^{ + if(accessToken) + { + success(self.credentials.accessToken); + } + else + { + MXError *mxError = [[MXError alloc] initWithErrorCode:kMXErrCodeStringClientError error: @"Token refresh failed"]; + failure(mxError.createNSError); + } + }); + }]; + }); + }; } - + completionQueue = dispatch_get_main_queue(); - + processingQueue = dispatch_queue_create("MXRestClient", DISPATCH_QUEUE_SERIAL); } return self; } + +- (void)startTokenRefreshWithLogId:(NSString*)logId andRefreshCompletion:(void (^)(NSString *accessToken))refreshCompletion +{ + MXWeakify(self); + // check persisted credentials (could have been updated in another process) + self.persistTokenDataHandler(^(NSArray *credentials, void (^shouldPersistCompletion)(BOOL didUpdateCredentials)) { + if(!weakself || !weakself.completionQueue) { + shouldPersistCompletion(NO); + refreshCompletion(nil); + MXLogWarning(@"[MXRestClient] %@: Client closed, exit tokenProviderHandler early.", logId); + return; + } + MXStrongifyAndReturnIfNil(self); + + MXCredentials *credential = [MXRestClient findMatchingCredential:self.credentials inCredentials:credentials]; + if (!credential) { + MXLogWarning(@"[MXRestClient] %@: Could not find rest client credential in persisted credentials.", logId); + shouldPersistCompletion(NO); + refreshCompletion(nil); + return; + } + // We found the matching, persisted credential. + if([NSDate date].timeIntervalSince1970 * 1000 < (credential.accessTokenExpiresAt - PREEMPT_REFRESH_EXPIRATION_INTERVAL)) { + MXLogDebug(@"[MXRestClient] %@: found valid persisted token, using.", logId) + // There exists a persisted credential that is not expired, use it. + MXRefreshTokenData *tokenData = [[MXRefreshTokenData alloc] initWithUserId:credential.userId + homeserver:credential.homeServer + accessToken:credential.accessToken + refreshToken:credential.refreshToken + accessTokenExpiresAt:credential.accessTokenExpiresAt + ]; + [[NSNotificationCenter defaultCenter] postNotificationName:MXCredentialsUpdateTokensNotification object:nil userInfo:@{ + kMXCredentialsNewRefreshTokenDataKey: tokenData + }]; + shouldPersistCompletion(NO); + refreshCompletion(credential.accessToken); + } else { + // refresh token and use the new values. + MXLogDebug(@"[MXRestClient] %@: refreshQueue refresh token start", logId) + MXWeakify(self); + id operation = [self refreshAccessToken:self.credentials.refreshToken success:^(MXRefreshResponse *refreshResponse) { + MXLogDebug(@"[MXRestClient] %@: refreshQueue refresh token request success", logId) + uint64_t accessTokenExpiresAt = ((uint64_t)[NSDate date].timeIntervalSince1970 * 1000) + refreshResponse.expiresInMs; + MXRefreshTokenData *tokenData = [[MXRefreshTokenData alloc] initWithUserId:credential.userId + homeserver:credential.homeServer + accessToken:refreshResponse.accessToken + refreshToken:refreshResponse.refreshToken + accessTokenExpiresAt:accessTokenExpiresAt + ]; + [[NSNotificationCenter defaultCenter] postNotificationName:MXCredentialsUpdateTokensNotification object:nil userInfo:@{ + kMXCredentialsNewRefreshTokenDataKey: tokenData + }]; + credential.accessToken = refreshResponse.accessToken; + credential.refreshToken = refreshResponse.refreshToken; + credential.accessTokenExpiresAt = accessTokenExpiresAt; + shouldPersistCompletion(YES); + MXLogDebug(@"[MXRestClient] %@: refreshQueue refresh token success", logId) + refreshCompletion(refreshResponse.accessToken); + } failure:^(NSError *error) { + if (!weakself) { + shouldPersistCompletion(NO); + refreshCompletion(nil); + } + MXStrongifyAndReturnIfNil(self); + MXLogDebug(@"[MXRestClient] %@: refreshQueue refresh token request failure", logId) + shouldPersistCompletion(NO); + MXError *mxError = [[MXError alloc] initWithNSError:error]; + if (self.unauthenticatedHandler && + ([mxError.errcode isEqualToString:kMXErrCodeStringForbidden] || + [mxError.errcode isEqualToString:kMXErrCodeStringUnknownToken])) + { + dispatch_async(self.completionQueue, ^{ + BOOL isSoftLogout = [MXRestClient isSoftLogout:mxError]; + MXLogDebug(@"[MXRestClient] %@: refreshQueue unauthenticatedHandler", logId) + self.unauthenticatedHandler(mxError, isSoftLogout, YES,^{ + refreshCompletion(nil); + }); + }); + } else { + refreshCompletion(nil); + } + }]; + + if(!operation) + { + MXLogWarning(@"[MXRestClient] %@: Did not start refresh as http client was nil", logId); + shouldPersistCompletion(NO); + refreshCompletion(nil); + } + } + + }); +} + ++ (BOOL)isSoftLogout:(MXError*)error +{ + return error.httpResponse.statusCode == 401 + && [error.userInfo[kMXErrorSoftLogoutKey] isEqual:@(YES)]; +} + ++ (MXCredentials*)findMatchingCredential:(MXCredentials*)credential inCredentials:(NSArray *)credentials +{ + for (MXCredentials *nextCredential in credentials) + { + if (nextCredential.homeServer && [nextCredential.homeServer isEqualToString:credential.homeServer] + && nextCredential.userId && [nextCredential.userId isEqualToString:credential.userId] ) { + return nextCredential; + } + } + return nil; +} + - (void)close { credentials = nil; @@ -707,6 +947,15 @@ - (MXHTTPOperation*)registerOrLogin:(MXAuthAction)authAction parameters:(NSDicti parameters = newParameters; } } + + if (MXSDKOptions.sharedInstance.authEnableRefreshTokens) + { + NSMutableDictionary *paramsWithRefresh = [NSMutableDictionary dictionaryWithDictionary:parameters]; + paramsWithRefresh[@"refresh_token"] = @(YES); + + parameters = paramsWithRefresh; + } + MXWeakify(self); return [httpClient requestWithMethod:@"POST" @@ -745,6 +994,35 @@ - (MXHTTPOperation*)logout:(void (^)(void))success }]; } +- (MXHTTPOperation*)refreshAccessToken:(NSString*)refreshToken + success:(void (^)(MXRefreshResponse *refreshResponse))success + failure:(void (^)(NSError *error))failure + +{ + NSDictionary *jsonBodyParameters = @{ + @"refresh_token": refreshToken, + }; + + NSData *payloadData = [NSJSONSerialization dataWithJSONObject:jsonBodyParameters options:0 error:nil]; + return [httpClient requestWithMethod:@"POST" + path:[NSString stringWithFormat:@"%@/refresh", kMXAPIPrefixPathV1] + parameters:nil + needsAuthentication:NO + data:payloadData + headers:@{@"Content-Type": @"application/json"} + timeout:-1 + uploadProgress:nil + success:^(NSDictionary *JSONResponse) { + MXRefreshResponse *refreshResponse; + MXJSONModelSetMXJSONModel(refreshResponse, MXRefreshResponse, JSONResponse); + if (success) + { + success(refreshResponse); + } + } + failure:failure]; +} + - (MXHTTPOperation*)deactivateAccountWithAuthParameters:(NSDictionary*)authParameters eraseAccount:(BOOL)eraseAccount success:(void (^)(void))success diff --git a/MatrixSDK/MXSDKOptions.h b/MatrixSDK/MXSDKOptions.h index 610672080a..636941c9c0 100644 --- a/MatrixSDK/MXSDKOptions.h +++ b/MatrixSDK/MXSDKOptions.h @@ -182,6 +182,13 @@ NS_ASSUME_NONNULL_BEGIN */ @property (nonatomic, nullable) NSString *clientPermalinkBaseUrl; +/** + Use refresh tokens and expiring access tokens as the auth mechanism as opposed to long-lived access tokens. + + @remark NO by default. + */ +@property (nonatomic, assign) BOOL authEnableRefreshTokens; + @end NS_ASSUME_NONNULL_END diff --git a/MatrixSDK/MXSDKOptions.m b/MatrixSDK/MXSDKOptions.m index c975c5ecb8..3134cee842 100644 --- a/MatrixSDK/MXSDKOptions.m +++ b/MatrixSDK/MXSDKOptions.m @@ -51,6 +51,7 @@ - (instancetype)init _callTransferType = MXCallTransferTypeBridged; self.roomListDataManagerClass = [MXCoreDataRoomListDataManager class]; _clientPermalinkBaseUrl = nil; + _authEnableRefreshTokens = NO; } return self; diff --git a/MatrixSDK/MXSession.h b/MatrixSDK/MXSession.h index 19ed192c32..9dee32743d 100644 --- a/MatrixSDK/MXSession.h +++ b/MatrixSDK/MXSession.h @@ -123,25 +123,7 @@ typedef NS_ENUM(NSUInteger, MXSessionState) @discussion The Matrix session will stay in this state until a new call of [MXSession start:failure:]. */ - MXSessionStateInitialSyncFailed, - - /** - The access token is no more valid. - - @discussion - This can happen when the user made a forget password request for example. - The Matrix session is no more usable. The user must log in again. - */ - MXSessionStateUnknownToken, - - /** - The user is logged out (invalid token) but they still have their local storage. - The user should log back in to rehydrate the client. - - @discussion - This happens when the homeserver admin has signed the user out. - */ - MXSessionStateSoftLogout + MXSessionStateInitialSyncFailed }; diff --git a/MatrixSDK/MXSession.m b/MatrixSDK/MXSession.m index 95d43ff2f3..7d9a6af8d0 100644 --- a/MatrixSDK/MXSession.m +++ b/MatrixSDK/MXSession.m @@ -892,13 +892,6 @@ - (void)_startWithSyncFilterId:(NSString *)syncFilterId onServerSyncDone:(void ( MXLogError(@"[MXSession] Crypto failed to start. Error: %@", error); - // Check whether the token is valid - if ([self isUnknownTokenError:error]) - { - // Do nothing more because without a valid access_token, the session is useless - return; - } - [self setState:MXSessionStateInitialSyncFailed]; failure(error); @@ -908,12 +901,6 @@ - (void)_startWithSyncFilterId:(NSString *)syncFilterId onServerSyncDone:(void ( MXLogError(@"[MXSession] Get the user's profile information failed with error %@", error); - // Check whether the token is valid - if ([self isUnknownTokenError:error]) - { - // Do nothing more because without a valid access_token, the session is useless - return; - } [self setState:MXSessionStateInitialSyncFailed]; failure(error); @@ -1243,37 +1230,6 @@ - (BOOL)isEventStreamInitialised return (self.store.eventStreamToken != nil); } -#pragma mark - Invalid Token handling - -- (BOOL)isUnknownTokenError:(NSError *)error -{ - // Detect invalidated access token - // This can happen when the user made a forget password request for example - if ([MXError isMXError:error]) - { - MXError *mxError = [[MXError alloc] initWithNSError:error]; - if ([mxError.errcode isEqualToString:kMXErrCodeStringUnknownToken]) - { - MXLogDebug(@"[MXSession] isUnknownTokenError: The access token is no more valid."); - - if (mxError.httpResponse.statusCode == 401 - && [mxError.userInfo[kMXErrorSoftLogoutKey] isEqual:@(YES)]) - { - MXLogDebug(@"[MXSession] isUnknownTokenError: Go to MXSessionStateSoftLogout state."); - [self setState:MXSessionStateSoftLogout]; - } - else - { - MXLogDebug(@"[MXSession] isUnknownTokenError: Go to MXSessionStateUnknownToken state."); - [self setState:MXSessionStateUnknownToken]; - } - - return YES; - } - } - return NO; -} - #pragma mark - MXSession pause prevention - (void)retainPreventPause { @@ -1541,13 +1497,6 @@ - (void)handleServerSyncError:(NSError*)error forRequestWithServerTimeout:(NSUIn return; } - // Check whether the token is valid - if ([self isUnknownTokenError:error]) - { - // Do nothing more because without a valid access_token, the session is useless - return; - } - // Handle failure during catch up first if (self->onBackgroundSyncFail) { diff --git a/MatrixSDK/NotificationCenter/ServiceTerms/MXServiceTermsRestClient.m b/MatrixSDK/NotificationCenter/ServiceTerms/MXServiceTermsRestClient.m index f61571befd..b98a1f62cc 100644 --- a/MatrixSDK/NotificationCenter/ServiceTerms/MXServiceTermsRestClient.m +++ b/MatrixSDK/NotificationCenter/ServiceTerms/MXServiceTermsRestClient.m @@ -31,7 +31,13 @@ - (instancetype)initWithBaseUrl:(NSString*)baseUrl accessToken:(nullable NSStrin self = [super init]; if (self) { - _httpClient = [[MXHTTPClient alloc] initWithBaseURL:baseUrl accessToken:accessToken andOnUnrecognizedCertificateBlock:nil]; + _httpClient = [[MXHTTPClient alloc] initWithBaseURL:baseUrl authenticated:YES andOnUnrecognizedCertificateBlock:nil]; + _httpClient.tokenProviderHandler = ^(NSError *error, void (^success)(NSString *accessToken), void (^failure)(NSError *error)) { + success(accessToken); + }; + _httpClient.tokenValidationResponseHandler = ^BOOL(NSError *error) { + return false; + }; } return self; } diff --git a/MatrixSDK/Utils/MXHTTPClient.h b/MatrixSDK/Utils/MXHTTPClient.h index 8136924fda..77df9c276d 100644 --- a/MatrixSDK/Utils/MXHTTPClient.h +++ b/MatrixSDK/Utils/MXHTTPClient.h @@ -50,23 +50,22 @@ FOUNDATION_EXPORT NSString* const kMXHTTPClientMatrixErrorNotificationErrorKey; typedef BOOL (^MXHTTPClientOnUnrecognizedCertificate)(NSData *certificate); /** - Block called when a request fails and needs authorization to determine if the access token should be renewed. - + Block called when an authenticated request fails and is used to validate the response error. + @param error A request error. - @return YES if the access token should be renewed for the given error. + @return YES if the access token should be refreshed for the given error. */ -typedef BOOL (^MXHTTPClientShouldRenewTokenHandler)(NSError *error); +typedef BOOL (^MXHTTPClientTokenValidationResponseHandler)(NSError *error); /** - Block called when a request needs authorization and access token should be renewed. + Block called when an authenticated request requires an access token. + @param error A request error. Passed if token provider is called as a result of `MXHTTPClientTokenValidationResponseHandler` returning true on after an access token error in a response. @param success A block object called when the operation succeeds. It provides the access token. @param failure A block object called when the operation fails. - - @return a MXHTTPOperation instance. */ -typedef MXHTTPOperation* (^MXHTTPClientRenewTokenHandler)(void (^success)(NSString *accessToken), void (^failure)(NSError *error)); +typedef void (^MXHTTPClientTokenProviderHandler)(NSError *error, void (^success)( NSString *accessToken), void (^failure)(NSError *error)); /** SSL Pinning mode @@ -123,35 +122,35 @@ typedef NS_ENUM(NSUInteger, MXHTTPClientSSLPinningMode) { @property (nonatomic, readonly) NSString *accessToken; /** - Block called when a request needs authorization and access token should be renewed. + Block called when a request needs authentication and access token should be renewed. */ -@property (nonatomic, copy) MXHTTPClientShouldRenewTokenHandler shouldRenewTokenHandler; +@property (nonatomic, copy) MXHTTPClientTokenValidationResponseHandler tokenValidationResponseHandler; /** - Block called when a request fails and needs authorization to determine if the access token should be renewed. + Block called when an authenticated request requires an access token. */ -@property (nonatomic, copy) MXHTTPClientRenewTokenHandler renewTokenHandler; +@property (nonatomic, copy) MXHTTPClientTokenProviderHandler tokenProviderHandler; #pragma mark - Public methods /** Create an instance to make requests to the server. @param baseURL the server URL from which requests will be done. - @param onUnrecognizedCertBlock the block called to handle unrecognized certificate (nil if unrecognized certificates are ignored). + @param onUnrecognizedCertBlock the block called to handle unrecognised certificate (nil if unrecognised certificates are ignored). @return a MXHTTPClient instance. */ - (id)initWithBaseURL:(NSString*)baseURL andOnUnrecognizedCertificateBlock:(MXHTTPClientOnUnrecognizedCertificate)onUnrecognizedCertBlock; /** - Create an intance to make access-token-authenticated requests to the server. + Create an instance to make requests to the server. MXHTTPClient will automatically add the access token to requested URLs @param baseURL the server URL from which requests will be done. - @param accessToken the access token to authenticate requests. - @param onUnrecognizedCertBlock the block called to handle unrecognized certificate (nil if unrecognized certificates are ignored). + @param authenticated whether the client is required make authenticated requests. + @param onUnrecognizedCertBlock the block called to handle unrecognised certificate (nil if unrecognised certificates are ignored). @return a MXHTTPClient instance. */ -- (id)initWithBaseURL:(NSString*)baseURL accessToken:(NSString*)accessToken andOnUnrecognizedCertificateBlock:(MXHTTPClientOnUnrecognizedCertificate)onUnrecognizedCertBlock; +- (id)initWithBaseURL:(NSString*)baseURL authenticated:(BOOL)authenticated andOnUnrecognizedCertificateBlock:(MXHTTPClientOnUnrecognizedCertificate)onUnrecognizedCertBlock; /** Make a HTTP request to the server. @@ -224,7 +223,7 @@ typedef NS_ENUM(NSUInteger, MXHTTPClientSSLPinningMode) { @param httpMethod the HTTP method (GET, PUT, ...) @param path the relative path of the server API to call. @param parameters the parameters to be set as a query string for `GET` requests, or the request HTTP body. - @param needsAuthorization Indicate YES if the request is authenticated. + @param needsAuthentication Indicate YES if the request is authenticated. @param success A block object called when the operation succeeds. It provides the JSON response object from the the server. @param failure A block object called when the operation fails. @@ -234,7 +233,7 @@ typedef NS_ENUM(NSUInteger, MXHTTPClientSSLPinningMode) { - (MXHTTPOperation*)requestWithMethod:(NSString *)httpMethod path:(NSString *)path parameters:(NSDictionary*)parameters - needsAuthorization:(BOOL)needsAuthorization + needsAuthentication:(BOOL)needsAuthentication success:(void (^)(NSDictionary *JSONResponse))success failure:(void (^)(NSError *error))failure; @@ -244,7 +243,7 @@ typedef NS_ENUM(NSUInteger, MXHTTPClientSSLPinningMode) { @param httpMethod the HTTP method (GET, PUT, ...) @param path the relative path of the server API to call. @param parameters the parameters to be set as a query string for `GET` requests, or the request HTTP body. - @param needsAuthorization Indicate YES if the request is authenticated. + @param needsAuthentication Indicate YES if the request is authenticated. @param timeoutInSeconds the timeout allocated for the request. @param success A block object called when the operation succeeds. It provides the JSON response object from the the server. @@ -255,7 +254,7 @@ typedef NS_ENUM(NSUInteger, MXHTTPClientSSLPinningMode) { - (MXHTTPOperation*)requestWithMethod:(NSString *)httpMethod path:(NSString *)path parameters:(NSDictionary*)parameters - needsAuthorization:(BOOL)needsAuthorization + needsAuthentication:(BOOL)needsAuthentication timeout:(NSTimeInterval)timeoutInSeconds success:(void (^)(NSDictionary *JSONResponse))success failure:(void (^)(NSError *error))failure; @@ -265,7 +264,7 @@ typedef NS_ENUM(NSUInteger, MXHTTPClientSSLPinningMode) { @param path the relative path of the server API to call. @param parameters (optional) the parameters to be set as a query string for `GET` requests, or the request HTTP body. - @param needsAuthorization Indicate YES if the request is authenticated. + @param needsAuthentication Indicate YES if the request is authenticated. @param data (optional) the data to post. @param headers (optional) the HTTP headers to set. @param timeoutInSeconds (optional) the timeout allocated for the request. @@ -280,7 +279,7 @@ typedef NS_ENUM(NSUInteger, MXHTTPClientSSLPinningMode) { - (MXHTTPOperation*)requestWithMethod:(NSString *)httpMethod path:(NSString *)path parameters:(NSDictionary*)parameters - needsAuthorization:(BOOL)needsAuthorization + needsAuthentication:(BOOL)needsAuthentication data:(NSData *)data headers:(NSDictionary*)headers timeout:(NSTimeInterval)timeoutInSeconds @@ -288,19 +287,6 @@ typedef NS_ENUM(NSUInteger, MXHTTPClientSSLPinningMode) { success:(void (^)(NSDictionary *JSONResponse))success failure:(void (^)(NSError *error))failure; -/** - Get current access or get a new one if not exist. - Note: There is no guarantee that current access token is valid. - - @param success A block object called when the operation succeeds. It provides the access token. - @param failure A block object called when the operation fails. - - @return a MXHTTPOperation instance. Nil if the access token is already known - and no HTTP request is required. - */ -- (MXHTTPOperation *)getAccessTokenAndRenewIfNeededWithSuccess:(void (^)(NSString *accessToken))success - failure:(void (^)(NSError *error))failure; - /** Return the amount of time to wait before retrying a request. diff --git a/MatrixSDK/Utils/MXHTTPClient.m b/MatrixSDK/Utils/MXHTTPClient.m index 25504e45ea..33e9d4292c 100644 --- a/MatrixSDK/Utils/MXHTTPClient.m +++ b/MatrixSDK/Utils/MXHTTPClient.m @@ -24,6 +24,7 @@ #import "MXBackgroundModeHandler.h" #import "MXTools.h" #import "MXHTTPClient_Private.h" +#import "MXCredentials.h" #import @@ -80,7 +81,7 @@ @interface MXHTTPClient () /** The access token used for authenticated requests. */ -@property (nonatomic, strong) NSString *accessToken; +@property (nonatomic) BOOL isAuthenticatedClient; /** The current background task id if any. @@ -93,14 +94,6 @@ @implementation MXHTTPClient #pragma mark - Properties override -// TODO: Set Authorization field only for authenticated requests -- (void)setAccessToken:(NSString *)accessToken -{ - _accessToken = accessToken; - - [self updateAuthorizationBearHTTPHeaderFieldWithAccessToken:accessToken]; -} - - (NSURL *)baseURL { return httpManager.baseURL; @@ -109,14 +102,15 @@ - (NSURL *)baseURL #pragma mark - Public methods -(id)initWithBaseURL:(NSString *)baseURL andOnUnrecognizedCertificateBlock:(MXHTTPClientOnUnrecognizedCertificate)onUnrecognizedCertBlock { - return [self initWithBaseURL:baseURL accessToken:nil andOnUnrecognizedCertificateBlock:onUnrecognizedCertBlock]; + return [self initWithBaseURL:baseURL authenticated:NO andOnUnrecognizedCertificateBlock:onUnrecognizedCertBlock]; } --(id)initWithBaseURL:(NSString *)baseURL accessToken:(NSString *)accessToken andOnUnrecognizedCertificateBlock:(MXHTTPClientOnUnrecognizedCertificate)onUnrecognizedCertBlock +-(id)initWithBaseURL:(NSString *)baseURL authenticated:(BOOL)authenticated andOnUnrecognizedCertificateBlock:(MXHTTPClientOnUnrecognizedCertificate)onUnrecognizedCertBlock { self = [super init]; if (self) { + self.isAuthenticatedClient = authenticated; httpManager = [[AFHTTPSessionManager alloc] initWithBaseURL:[NSURL URLWithString:baseURL]]; [self setDefaultSecurityPolicy]; @@ -129,13 +123,6 @@ -(id)initWithBaseURL:(NSString *)baseURL accessToken:(NSString *)accessToken and // No need for caching. The sdk caches the data it needs [httpManager.requestSerializer setCachePolicy:NSURLRequestReloadIgnoringLocalCacheData]; - // Set authorization HTTP header if access token is present - if (accessToken) - { - _accessToken = accessToken; - [httpManager.requestSerializer setValue:[NSString stringWithFormat:@"Bearer %@", accessToken] forHTTPHeaderField:@"Authorization"]; - } - [self setUpNetworkReachibility]; [self setUpSSLCertificatesHandler]; @@ -175,52 +162,47 @@ - (MXHTTPOperation*)requestWithMethod:(NSString *)httpMethod success:(void (^)(NSDictionary *JSONResponse))success failure:(void (^)(NSError *error))failure { - return [self requestWithMethod:httpMethod path:path parameters:parameters data:nil headers:nil timeout:timeoutInSeconds uploadProgress:nil success:success failure:failure ]; -} - -- (MXHTTPOperation*)requestWithMethod:(NSString *)httpMethod - path:(NSString *)path - parameters:(NSDictionary*)parameters - data:(NSData *)data - headers:(NSDictionary*)headers - timeout:(NSTimeInterval)timeoutInSeconds - uploadProgress:(void (^)(NSProgress *uploadProgress))uploadProgress - success:(void (^)(NSDictionary *JSONResponse))success - failure:(void (^)(NSError *error))failure -{ - MXHTTPOperation *mxHTTPOperation = [[MXHTTPOperation alloc] init]; - - [self tryRequest:mxHTTPOperation method:httpMethod path:path parameters:parameters data:data headers:headers timeout:timeoutInSeconds uploadProgress:uploadProgress success:success failure:failure]; - - return mxHTTPOperation; + return [self requestWithMethod:httpMethod path:path parameters:parameters needsAuthentication: self.isAuthenticatedClient data:nil headers:nil timeout:timeoutInSeconds uploadProgress:nil success:success failure:failure]; } - (MXHTTPOperation*)requestWithMethod:(NSString *)httpMethod path:(NSString *)path parameters:(NSDictionary*)parameters - needsAuthorization:(BOOL)needsAuthorization + needsAuthentication:(BOOL)needsAuthentication success:(void (^)(NSDictionary *JSONResponse))success failure:(void (^)(NSError *error))failure { - return [self requestWithMethod:httpMethod path:path parameters:parameters needsAuthorization:needsAuthorization timeout:-1 success:success failure:failure]; + return [self requestWithMethod:httpMethod path:path parameters:parameters needsAuthentication:needsAuthentication timeout:-1 success:success failure:failure]; } - (MXHTTPOperation*)requestWithMethod:(NSString *)httpMethod path:(NSString *)path parameters:(NSDictionary*)parameters - needsAuthorization:(BOOL)needsAuthorization + needsAuthentication:(BOOL)needsAuthentication timeout:(NSTimeInterval)timeoutInSeconds success:(void (^)(NSDictionary *JSONResponse))success failure:(void (^)(NSError *error))failure { - return [self requestWithMethod:httpMethod path:path parameters:parameters needsAuthorization:needsAuthorization data:nil headers:nil timeout:timeoutInSeconds uploadProgress:nil success:success failure:failure]; + return [self requestWithMethod:httpMethod path:path parameters:parameters needsAuthentication: needsAuthentication data:nil headers:nil timeout:timeoutInSeconds uploadProgress:nil success:success failure:failure]; } +- (MXHTTPOperation*)requestWithMethod:(NSString *)httpMethod + path:(NSString *)path + parameters:(NSDictionary*)parameters + data:(NSData *)data + headers:(NSDictionary*)headers + timeout:(NSTimeInterval)timeoutInSeconds + uploadProgress:(void (^)(NSProgress *uploadProgress))uploadProgress + success:(void (^)(NSDictionary *JSONResponse))success + failure:(void (^)(NSError *error))failure; +{ + return [self requestWithMethod:httpMethod path:path parameters:parameters needsAuthentication: self.isAuthenticatedClient data:data headers:headers timeout:timeoutInSeconds uploadProgress:uploadProgress success:success failure:failure]; +} - (MXHTTPOperation*)requestWithMethod:(NSString *)httpMethod path:(NSString *)path parameters:(NSDictionary*)parameters - needsAuthorization:(BOOL)needsAuthorization + needsAuthentication:(BOOL)needsAuthentication data:(NSData *)data headers:(NSDictionary*)headers timeout:(NSTimeInterval)timeoutInSeconds @@ -230,97 +212,84 @@ - (MXHTTPOperation*)requestWithMethod:(NSString *)httpMethod { MXHTTPOperation *mxHTTPOperation = [[MXHTTPOperation alloc] init]; - [self tryRequest:mxHTTPOperation - method:httpMethod - path:path - parameters:parameters - data:data - headers:headers - timeout:timeoutInSeconds - uploadProgress:uploadProgress - success:success - failure:^(NSError *error) { - - if (needsAuthorization - && error - && self.shouldRenewTokenHandler(error) - && self.renewTokenHandler) - { - dispatch_async(dispatch_get_main_queue(), ^{ - - // Remove current access token - self.accessToken = nil; - - mxHTTPOperation.operation = nil; - - typeof(self) __weak weakSelf = self; - - self.renewTokenHandler(^(NSString *accessToken) { - - typeof(self) strongSelf = weakSelf; - - if (strongSelf) - { - strongSelf.accessToken = accessToken; - - [strongSelf tryRequest:mxHTTPOperation - method:httpMethod - path:path - parameters:parameters - data:data - headers:headers - timeout:timeoutInSeconds - uploadProgress:uploadProgress - success:success - failure:failure]; - } - }, ^(NSError *error) { - failure(error); - }); - }); - } - else - { - failure(error); - } - }]; - - return mxHTTPOperation; -} - -- (MXHTTPOperation *)getAccessTokenAndRenewIfNeededWithSuccess:(void (^)(NSString *accessToken))success - failure:(void (^)(NSError *error))failure -{ - if (self.accessToken) - { - success(self.accessToken); - return nil; + if (!self.isAuthenticatedClient || !needsAuthentication) { + [self tryRequest:mxHTTPOperation + method:httpMethod + path:path + parameters:parameters + data:data + headers:headers + accessToken:nil + timeout:timeoutInSeconds + uploadProgress:uploadProgress + success:success + failure:failure]; + return mxHTTPOperation; } - typeof(self) __weak weakSelf = self; - - return self.renewTokenHandler(^(NSString *accessToken) { - - typeof(self) strongSelf = weakSelf; - - if (strongSelf) - { - strongSelf.accessToken = accessToken; - } - - success(accessToken); + // Before each authenticated request verify access token is valid. + MXWeakify(self); + self.tokenProviderHandler(nil, ^(NSString *accessToken) { + MXStrongifyAndReturnIfNil(self); + MXWeakify(self); + [self tryRequest:mxHTTPOperation method:httpMethod path:path parameters:parameters data:data headers:headers accessToken:accessToken timeout:timeoutInSeconds uploadProgress:uploadProgress success:success failure:^(NSError *error) { + if (!weakself) { + failure(error); + } + MXStrongifyAndReturnIfNil(self); + // Check if we received an invalid token response. + if (error + && self.tokenValidationResponseHandler(error) + && self.tokenProviderHandler) + { + MXWeakify(self); + dispatch_async(dispatch_get_main_queue(), ^{ + if (!weakself) { + failure(error); + } + MXStrongifyAndReturnIfNil(self); + mxHTTPOperation.operation = nil; + // If was an invalid token response verify we can get a new one and retry the original request with new token. + MXWeakify(self); + self.tokenProviderHandler(error, ^(NSString *retryAccessToken) { + if (!weakself) { + failure(error); + } + MXStrongifyAndReturnIfNil(self); + [self tryRequest:mxHTTPOperation + method:httpMethod + path:path + parameters:parameters + data:data + headers:headers + accessToken:retryAccessToken + timeout:timeoutInSeconds + uploadProgress:uploadProgress + success:success + failure:failure]; + }, failure); + }); + } + else + { + failure(error); + } + }]; }, ^(NSError *error) { failure(error); }); + return mxHTTPOperation; } +#pragma mark - Request attempt method - (void)tryRequest:(MXHTTPOperation*)mxHTTPOperation method:(NSString *)httpMethod path:(NSString *)path parameters:(NSDictionary*)parameters data:(NSData *)data headers:(NSDictionary*)headers + accessToken:(NSString*)accessToken timeout:(NSTimeInterval)timeoutInSeconds uploadProgress:(void (^)(NSProgress *uploadProgress))uploadProgress success:(void (^)(NSDictionary *JSONResponse))success @@ -329,10 +298,18 @@ - (void)tryRequest:(MXHTTPOperation*)mxHTTPOperation // Sanity check if (invalidatedSession) { - // This MXLogDebug(@"[MXHTTPClient] tryRequest: ignore the request as the NSURLSession has been invalidated"); return; } + + if (accessToken) + { + [httpManager.requestSerializer setValue:[NSString stringWithFormat:@"Bearer %@", accessToken] forHTTPHeaderField:@"Authorization"]; + } + else + { + [httpManager.requestSerializer clearAuthorizationHeader]; + } NSString *URLString = [[NSURL URLWithString:path relativeToURL:httpManager.baseURL] absoluteString]; @@ -448,7 +425,7 @@ - (void)tryRequest:(MXHTTPOperation*)mxHTTPOperation MXLogDebug(@"[MXHTTPClient] Retry rate limited request %p", mxHTTPOperation); - [self tryRequest:mxHTTPOperation method:httpMethod path:path parameters:parameters data:data headers:headers timeout:timeoutInSeconds uploadProgress:uploadProgress success:^(NSDictionary *JSONResponse) { + [self tryRequest:mxHTTPOperation method:httpMethod path:path parameters:parameters data:data headers:headers accessToken: accessToken timeout:timeoutInSeconds uploadProgress:uploadProgress success:^(NSDictionary *JSONResponse) { MXLogDebug(@"[MXHTTPClient] Success of rate limited request %p after %tu tries", mxHTTPOperation, mxHTTPOperation.numberOfTries); @@ -536,7 +513,7 @@ - (void)tryRequest:(MXHTTPOperation*)mxHTTPOperation MXLogDebug(@"[MXHTTPClient] Retry request %p. Try #%tu/%tu. Age: %tums. Max retries time: %tums", mxHTTPOperation, mxHTTPOperation.numberOfTries + 1, mxHTTPOperation.maxNumberOfTries, mxHTTPOperation.age, mxHTTPOperation.maxRetriesTime); - [self tryRequest:mxHTTPOperation method:httpMethod path:path parameters:parameters data:data headers:headers timeout:timeoutInSeconds uploadProgress:uploadProgress success:^(NSDictionary *JSONResponse) { + [self tryRequest:mxHTTPOperation method:httpMethod path:path parameters:parameters data:data headers:headers accessToken: accessToken timeout:timeoutInSeconds uploadProgress:uploadProgress success:^(NSDictionary *JSONResponse) { MXLogDebug(@"[MXHTTPClient] Request %p finally succeeded after %tu tries and %tums", mxHTTPOperation, mxHTTPOperation.numberOfTries, mxHTTPOperation.age); @@ -568,7 +545,7 @@ - (void)tryRequest:(MXHTTPOperation*)mxHTTPOperation MXLogDebug(@"[MXHTTPClient] Retry request %p. Try #%tu/%tu. Age: %tums. Max retries time: %tums", mxHTTPOperation, mxHTTPOperation.numberOfTries + 1, mxHTTPOperation.maxNumberOfTries, mxHTTPOperation.age, mxHTTPOperation.maxRetriesTime); MXWeakify(self); - [self tryRequest:mxHTTPOperation method:httpMethod path:path parameters:parameters data:data headers:headers timeout:timeoutInSeconds uploadProgress:uploadProgress success:^(NSDictionary *JSONResponse) { + [self tryRequest:mxHTTPOperation method:httpMethod path:path parameters:parameters data:data headers:headers accessToken: accessToken timeout:timeoutInSeconds uploadProgress:uploadProgress success:^(NSDictionary *JSONResponse) { MXStrongifyAndReturnIfNil(self); MXLogDebug(@"[MXHTTPClient] Request %p finally succeeded after %tu tries and %tums", mxHTTPOperation, mxHTTPOperation.numberOfTries, mxHTTPOperation.age); @@ -677,9 +654,7 @@ - (void)setRequestParametersInJSON:(BOOL)requestParametersInJSON [MXSDKOptions.sharedInstance.HTTPAdditionalHeaders enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, NSString * _Nonnull value, BOOL * _Nonnull stop) { [httpManager.requestSerializer setValue:value forHTTPHeaderField:key]; }]; - - // Refresh authorization HTTP header field - [self updateAuthorizationBearHTTPHeaderFieldWithAccessToken:self.accessToken]; + } @@ -938,18 +913,6 @@ - (MXError*)mxErrorFromJSON:(NSDictionary*)json userInfo:mxErrorUserInfo]; } -- (void)updateAuthorizationBearHTTPHeaderFieldWithAccessToken:(NSString *)accessToken -{ - if (accessToken) - { - [httpManager.requestSerializer setValue:[NSString stringWithFormat:@"Bearer %@", accessToken] forHTTPHeaderField:@"Authorization"]; - } - else - { - [httpManager.requestSerializer clearAuthorizationHeader]; - } -} - + (void)logRequestFailure:(MXHTTPOperation*)mxHTTPOperation path:(NSString*)path statusCode:(NSUInteger)statusCode @@ -958,7 +921,6 @@ + (void)logRequestFailure:(MXHTTPOperation*)mxHTTPOperation MXLogDebug(@"[MXHTTPClient] Request %p failed for path: %@ - HTTP code: %@. Error: %@", mxHTTPOperation, path, @(statusCode), error); } - #pragma mark - MXHTTPClient_Private // See MXHTTPClient_Private.h for details + (NSMutableDictionary *)delayedRequests diff --git a/MatrixSDKTests/MXBackgroundSyncServiceTests.swift b/MatrixSDKTests/MXBackgroundSyncServiceTests.swift index 3492cb2065..cfbdbb6fb1 100644 --- a/MatrixSDKTests/MXBackgroundSyncServiceTests.swift +++ b/MatrixSDKTests/MXBackgroundSyncServiceTests.swift @@ -678,7 +678,7 @@ class MXBackgroundSyncServiceTests: XCTestCase { self.bgSyncService?.event(withEventId: "aRandomEventId", inRoom: roomId) { _ in // -> MXBackgroundSyncService should have detected that the MXSession ran in parallel. - // It must have reset its cache. syncResponseStore.prevBatch must not be the same + // It must have reset its cache. syncResponseStore.prevBatch must not be the same XCTAssertNotEqual(syncResponseStoreSyncToken, syncResponseStoreManager.syncToken()) expectation?.fulfill() diff --git a/MatrixSDKTests/MXCoreDataRoomListDataManagerUnitTests.swift b/MatrixSDKTests/MXCoreDataRoomListDataManagerUnitTests.swift index 79fe5262f7..a811f940a6 100644 --- a/MatrixSDKTests/MXCoreDataRoomListDataManagerUnitTests.swift +++ b/MatrixSDKTests/MXCoreDataRoomListDataManagerUnitTests.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 The Matrix.org Foundation C.I.C // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/MatrixSDKTests/MXCryptoTests.m b/MatrixSDKTests/MXCryptoTests.m index df21b5c68c..1981964ea2 100644 --- a/MatrixSDKTests/MXCryptoTests.m +++ b/MatrixSDKTests/MXCryptoTests.m @@ -2749,7 +2749,7 @@ - (void)testIncomingRoomKeyRequest XCTAssertEqual(0, [self checkEncryptedEvent:event roomId:roomId clearMessage:messageFromAlice senderSession:aliceSession2]); // 5 - Instantiante a MXRestclient, alice1MatrixRestClient - MXRestClient *alice1MatrixRestClient = [[MXRestClient alloc] initWithCredentials:alice1Credentials andOnUnrecognizedCertificateBlock:nil]; + MXRestClient *alice1MatrixRestClient = [[MXRestClient alloc] initWithCredentials:alice1Credentials andOnUnrecognizedCertificateBlock:nil andPersistentTokenDataHandler:nil andUnauthenticatedHandler:nil]; [matrixSDKTestsData retain:alice1MatrixRestClient]; // 6 - Make alice1MatrixRestClient make a fake room key request for the message sent at step #4 diff --git a/MatrixSDKTests/MXSelfSignedHomeserverTests.m b/MatrixSDKTests/MXSelfSignedHomeserverTests.m index cc3770a52c..f0acf360f9 100644 --- a/MatrixSDKTests/MXSelfSignedHomeserverTests.m +++ b/MatrixSDKTests/MXSelfSignedHomeserverTests.m @@ -96,7 +96,7 @@ - (void)testTrustedCertificate XCTFail(@"We have already accepted the certificate. We should not be asked again"); return NO; - }]; + } andPersistentTokenDataHandler:nil andUnauthenticatedHandler:nil]; [matrixSDKTestsData retain:mxRestClient]; diff --git a/MatrixSDKTests/MXStoreRoomListDataManagerUnitTests.swift b/MatrixSDKTests/MXStoreRoomListDataManagerUnitTests.swift index 45606f083e..562530b042 100644 --- a/MatrixSDKTests/MXStoreRoomListDataManagerUnitTests.swift +++ b/MatrixSDKTests/MXStoreRoomListDataManagerUnitTests.swift @@ -93,7 +93,7 @@ class MXStoreRoomListDataManagerUnitTests: XCTestCase { func testDataManagerInitStandalone() { let manager = MXStoreRoomListDataManager() - let restClient = MXRestClient(credentials: Constants.credentials, unrecognizedCertificateHandler: nil) + let restClient = MXRestClient(credentials: Constants.credentials, unrecognizedCertificateHandler: nil, persistentTokenDataHandler: nil, unauthenticatedHandler: nil) guard let session = MXSession(matrixRestClient: restClient) else { XCTFail("Failed to setup test conditions") return @@ -119,7 +119,7 @@ class MXStoreRoomListDataManagerUnitTests: XCTestCase { } func testDataManagerInitFromSession() { - let restClient = MXRestClient(credentials: Constants.credentials, unrecognizedCertificateHandler: nil) + let restClient = MXRestClient(credentials: Constants.credentials, unrecognizedCertificateHandler: nil, persistentTokenDataHandler: nil, unauthenticatedHandler: nil) guard let session = MXSession(matrixRestClient: restClient) else { XCTFail("Failed to setup test conditions") return @@ -245,7 +245,7 @@ class MXStoreRoomListDataManagerUnitTests: XCTestCase { } private func generateDefaultFetcher(_ completion: @escaping (MXRoomListDataFetcher) -> Void) { - let restClient = MXRestClient(credentials: Constants.credentials, unrecognizedCertificateHandler: nil) + let restClient = MXRestClient(credentials: Constants.credentials, unrecognizedCertificateHandler: nil, persistentTokenDataHandler: nil, unauthenticatedHandler: nil) guard let session = MXSession(matrixRestClient: restClient) else { XCTFail("Failed to setup test conditions") return diff --git a/MatrixSDKTests/MatrixSDKTestsData.m b/MatrixSDKTests/MatrixSDKTestsData.m index b1f7d17fb2..a69d2a1d88 100644 --- a/MatrixSDKTests/MatrixSDKTestsData.m +++ b/MatrixSDKTests/MatrixSDKTestsData.m @@ -30,7 +30,7 @@ /* Out of the box, the tests are supposed to be run with the iOS simulator attacking a test home server running on the same Mac machine. - The reason is that the simulator can access to the home server running on the Mac + The reason is that the simulator can access to the home server running on the Mac via localhost. So everyone can use a localhost HS url that works everywhere. Here, we use one of the home servers launched by the ./demo/start.sh script @@ -1051,4 +1051,3 @@ - (void)releaseRetainedObjects @end #pragma clang diagnostic pop - diff --git a/MatrixSDKTests/MatrixSDKTestsE2EData.m b/MatrixSDKTests/MatrixSDKTestsE2EData.m index 5d2848a717..0603a76b3f 100644 --- a/MatrixSDKTests/MatrixSDKTestsE2EData.m +++ b/MatrixSDKTests/MatrixSDKTestsE2EData.m @@ -301,7 +301,7 @@ - (void)loginUserOnANewDevice:(XCTestCase*)testCase [mxRestClient loginWithLoginType:kMXLoginFlowTypePassword username:credentials.userId password:password success:^(MXCredentials *credentials2) { - MXRestClient *mxRestClient2 = [[MXRestClient alloc] initWithCredentials:credentials2 andOnUnrecognizedCertificateBlock:nil]; + MXRestClient *mxRestClient2 = [[MXRestClient alloc] initWithCredentials:credentials2 andOnUnrecognizedCertificateBlock:nil andPersistentTokenDataHandler:nil andUnauthenticatedHandler:nil]; [matrixSDKTestsData retain:mxRestClient2]; MXSession *newSession = [[MXSession alloc] initWithMatrixRestClient:mxRestClient2];