diff --git a/CHANGES.rst b/CHANGES.rst index a67bb0357a..4c683f7c68 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,3 +1,18 @@ +Changes in Matrix iOS SDK in 0.6.5 (2016-04-08) +=============================================== + +Improvements: + * MXJSONModels: Registration Support - Define MXAunthenticationSession class. This class is used to store the server response on supported flows during the login or the registration. + * MXRestClient: New email binding - validateEmail and bind3PID has been removed. add3PID and treePIDs has been added. + * MXRestClient: Registration Support - Add API to check user id availability. + * MXSession: Added roomWithAlias method. + * MXTools: Add method to validate email address. + +Bug fixes: + * User profile: user settings may be modified during pagination in past timeline. + * Fixed crash in [MXFileStore saveReceipts]. There was a race condition. + * Cancel correctly pending operations. + Changes in Matrix iOS SDK in 0.6.4 (2016-03-17) =============================================== diff --git a/MatrixSDK.podspec b/MatrixSDK.podspec index ac1a24236f..7a728a8c92 100644 --- a/MatrixSDK.podspec +++ b/MatrixSDK.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = "MatrixSDK" - s.version = "0.6.4" + s.version = "0.6.5" s.summary = "The iOS SDK to build apps compatible with Matrix (http://www.matrix.org)" s.description = <<-DESC @@ -19,7 +19,7 @@ Pod::Spec.new do |s| s.platform = :ios, "7.0" - s.source = { :git => "https://github.com/matrix-org/matrix-ios-sdk.git", :tag => "v0.6.4" } + s.source = { :git => "https://github.com/matrix-org/matrix-ios-sdk.git", :tag => "v0.6.5" } s.source_files = "MatrixSDK", "MatrixSDK/**/*.{h,m}" s.resources = "MatrixSDK/Data/Store/MXCoreDataStore/*.xcdatamodeld" diff --git a/MatrixSDK/Data/MXEventTimeline.m b/MatrixSDK/Data/MXEventTimeline.m index e3c3720be7..9a11aa9bce 100644 --- a/MatrixSDK/Data/MXEventTimeline.m +++ b/MatrixSDK/Data/MXEventTimeline.m @@ -574,7 +574,7 @@ - (void)handleStateEvent:(MXEvent*)event direction:(MXTimelineDirection)directio [_state handleStateEvent:event]; // Special handling for presence - if (MXEventTypeRoomMember == event.eventType) + if (_isLiveTimeline && MXEventTypeRoomMember == event.eventType) { // Update MXUser data MXUser *user = [room.mxSession getOrCreateUser:event.sender]; diff --git a/MatrixSDK/Data/Store/MXFileStore/MXFileStore.m b/MatrixSDK/Data/Store/MXFileStore/MXFileStore.m index b5c48476c8..3d78b5fb52 100644 --- a/MatrixSDK/Data/Store/MXFileStore/MXFileStore.m +++ b/MatrixSDK/Data/Store/MXFileStore/MXFileStore.m @@ -951,8 +951,11 @@ - (void)saveReceipts { NSString *receiptsFile = [storeReceiptsPath stringByAppendingPathComponent:roomId]; NSUInteger filesize = [[[[NSFileManager defaultManager] attributesOfItemAtPath:receiptsFile error:nil] objectForKey:NSFileSize] intValue]; - - [NSKeyedArchiver archiveRootObject:receiptsByUserId toFile:receiptsFile]; + + @synchronized (receiptsByUserId) + { + [NSKeyedArchiver archiveRootObject:receiptsByUserId toFile:receiptsFile]; + } deltaCacheSize += [[[[NSFileManager defaultManager] attributesOfItemAtPath:receiptsFile error:nil] objectForKey:NSFileSize] intValue] - filesize; } diff --git a/MatrixSDK/Data/Store/MXMemoryStore/MXMemoryStore.m b/MatrixSDK/Data/Store/MXMemoryStore/MXMemoryStore.m index c63cf3e3e1..eb0963c18c 100644 --- a/MatrixSDK/Data/Store/MXMemoryStore/MXMemoryStore.m +++ b/MatrixSDK/Data/Store/MXMemoryStore/MXMemoryStore.m @@ -213,7 +213,10 @@ - (BOOL)storeReceipt:(MXReceiptData*)receipt inRoom:(NSString*)roomId // not yet defined or a new event if (!curReceipt || (![receipt.eventId isEqualToString:curReceipt.eventId] && (receipt.ts > curReceipt.ts))) { - [receiptsByUserId setObject:receipt forKey:receipt.userId]; + @synchronized (receiptsByUserId) + { + [receiptsByUserId setObject:receipt forKey:receipt.userId]; + } return true; } diff --git a/MatrixSDK/JSONModels/MXJSONModels.h b/MatrixSDK/JSONModels/MXJSONModels.h index 139822cb20..f6b38b9617 100644 --- a/MatrixSDK/JSONModels/MXJSONModels.h +++ b/MatrixSDK/JSONModels/MXJSONModels.h @@ -27,6 +27,14 @@ Note: some such class can be defined in their own file (ex: MXEvent) */ +/** + Types of third party media. + The list is not exhautive and depends on the Identity server capabilities. + */ +typedef NSString* MX3PIDMedium; +FOUNDATION_EXPORT NSString *const kMX3PIDMediumEmail; +FOUNDATION_EXPORT NSString *const kMX3PIDMediumMSISDN; + /** `MXPublicRoom` represents a public room returned by the publicRoom request */ @@ -103,12 +111,41 @@ FOUNDATION_EXPORT NSString *const kMXLoginFlowTypeRecaptcha; @property (nonatomic) NSString *type; /** - The list of stages to proceed the login. This is an array of NSStrings + The list of stages to proceed the login or the registration. */ - @property (nonatomic) NSArray *stages; + @property (nonatomic) NSArray *stages; @end +/** + `MXAuthenticationSession` represents an authentication session returned by the home server. + */ +@interface MXAuthenticationSession : MXJSONModel + + /** + The list of stages the client has completed successfully. + */ + @property (nonatomic) NSArray *completed; + + /** + The session identifier that the client must pass back to the home server, if one is provided, + in subsequent attempts to authenticate in the same API call. + */ + @property (nonatomic) NSString *session; + + /** + The list of supported flows + */ + @property (nonatomic) NSArray *flows; + + /** + The information that the client will need to know in order to use a given type of authentication. + For each login stage type presented, that type may be present as a key in this dictionary. + For example, the public key of reCAPTCHA stage could be given here. + */ + @property (nonatomic) NSDictionary *params; + +@end /** `MXCredentials` represents the response to a login or a register request. @@ -150,6 +187,34 @@ FOUNDATION_EXPORT NSString *const kMXLoginFlowTypeRecaptcha; @end +/** + `MXThirdPartyIdentifier` represents the response to /account/3pid GET request. + */ +@interface MXThirdPartyIdentifier : MXJSONModel + + /** + The medium of the third party identifier. + */ + @property (nonatomic) MX3PIDMedium medium; + + /** + The third party identifier address. + */ + @property (nonatomic) NSString *address; + + /** + The timestamp in milliseconds when this 3PID has been validated. + */ + @property (nonatomic) uint64_t validatedAt; + + /** + The timestamp in milliseconds when this 3PID has been added to the user account. + */ + @property (nonatomic) uint64_t addedAt; + +@end + + /** `MXCreateRoomResponse` represents the response to createRoom request. */ diff --git a/MatrixSDK/JSONModels/MXJSONModels.m b/MatrixSDK/JSONModels/MXJSONModels.m index c551e1b6a2..de3aa273a3 100644 --- a/MatrixSDK/JSONModels/MXJSONModels.m +++ b/MatrixSDK/JSONModels/MXJSONModels.m @@ -91,6 +91,25 @@ + (id)modelFromJSON:(NSDictionary *)JSONDictionary @end +@implementation MXAuthenticationSession + ++ (id)modelFromJSON:(NSDictionary *)JSONDictionary +{ + MXAuthenticationSession *authSession = [[MXAuthenticationSession alloc] init]; + if (authSession) + { + MXJSONModelSetArray(authSession.completed, JSONDictionary[@"completed"]); + MXJSONModelSetString(authSession.session, JSONDictionary[@"session"]); + MXJSONModelSetDictionary(authSession.params, JSONDictionary[@"params"]); + + authSession.flows = [MXLoginFlow modelsFromJSON:JSONDictionary[@"flows"]]; + } + + return authSession; +} + +@end + @implementation MXCredentials + (id)modelFromJSON:(NSDictionary *)JSONDictionary @@ -120,6 +139,45 @@ - (instancetype)initWithHomeServer:(NSString *)homeServer userId:(NSString *)use @end +@implementation MXThirdPartyIdentifier + ++ (id)modelFromJSON:(NSDictionary *)JSONDictionary +{ + MXThirdPartyIdentifier *thirdPartyIdentifier = [[MXThirdPartyIdentifier alloc] init]; + if (thirdPartyIdentifier) + { + MXJSONModelSetString(thirdPartyIdentifier.medium, JSONDictionary[@"medium"]); + MXJSONModelSetString(thirdPartyIdentifier.address, JSONDictionary[@"address"]); + MXJSONModelSetUInt64(thirdPartyIdentifier.validatedAt, JSONDictionary[@"validated_at"]); + MXJSONModelSetUInt64(thirdPartyIdentifier.addedAt, JSONDictionary[@"added_at"]); + } + + return thirdPartyIdentifier; +} + +- (instancetype)initWithCoder:(NSCoder *)aDecoder +{ + self = [super init]; + if (self) + { + _medium = [aDecoder decodeObjectForKey:@"medium"]; + _address = [aDecoder decodeObjectForKey:@"address"]; + _validatedAt = [((NSNumber*)[aDecoder decodeObjectForKey:@"validatedAt"]) unsignedLongLongValue]; + _addedAt = [((NSNumber*)[aDecoder decodeObjectForKey:@"addedAt"]) unsignedLongLongValue]; + } + return self; +} + +- (void)encodeWithCoder:(NSCoder *)aCoder +{ + [aCoder encodeObject:_medium forKey:@"medium"]; + [aCoder encodeObject:_address forKey:@"address"]; + [aCoder encodeObject:@(_validatedAt) forKey:@"validatedAt"]; + [aCoder encodeObject:@(_addedAt) forKey:@"addedAt"]; +} + +@end + @implementation MXCreateRoomResponse + (id)modelFromJSON:(NSDictionary *)JSONDictionary diff --git a/MatrixSDK/MXError.h b/MatrixSDK/MXError.h index f9a0bd3cdc..41aeb9aa6b 100644 --- a/MatrixSDK/MXError.h +++ b/MatrixSDK/MXError.h @@ -33,6 +33,9 @@ FOUNDATION_EXPORT NSString *const kMXErrCodeStringLimitExceeded; FOUNDATION_EXPORT NSString *const kMXErrCodeStringUserInUse; FOUNDATION_EXPORT NSString *const kMXErrCodeStringRoomInUse; FOUNDATION_EXPORT NSString *const kMXErrCodeStringBadPagination; +FOUNDATION_EXPORT NSString *const kMXErrCodeStringUnauthorized; +FOUNDATION_EXPORT NSString *const kMXErrCodeStringLoginEmailURLNotYet; +FOUNDATION_EXPORT NSString *const kMXErrCodeStringThreePIDAuthFailed; FOUNDATION_EXPORT NSString *const kMXErrorStringInvalidToken; diff --git a/MatrixSDK/MXError.m b/MatrixSDK/MXError.m index 2fd4e3c004..cc733f0278 100644 --- a/MatrixSDK/MXError.m +++ b/MatrixSDK/MXError.m @@ -20,16 +20,19 @@ NSString *const kMXNSErrorDomain = @"org.matrix.sdk"; -NSString *const kMXErrCodeStringForbidden = @"M_FORBIDDEN"; -NSString *const kMXErrCodeStringUnknown = @"M_UNKNOWN"; -NSString *const kMXErrCodeStringUnknownToken = @"M_UNKNOWN_TOKEN"; -NSString *const kMXErrCodeStringBadJSON = @"M_BAD_JSON"; -NSString *const kMXErrCodeStringNotJSON = @"M_NOT_JSON"; -NSString *const kMXErrCodeStringNotFound = @"M_NOT_FOUND"; -NSString *const kMXErrCodeStringLimitExceeded = @"M_LIMIT_EXCEEDED"; -NSString *const kMXErrCodeStringUserInUse = @"M_USER_IN_USE"; -NSString *const kMXErrCodeStringRoomInUse = @"M_ROOM_IN_USE"; -NSString *const kMXErrCodeStringBadPagination = @"M_BAD_PAGINATION"; +NSString *const kMXErrCodeStringForbidden = @"M_FORBIDDEN"; +NSString *const kMXErrCodeStringUnknown = @"M_UNKNOWN"; +NSString *const kMXErrCodeStringUnknownToken = @"M_UNKNOWN_TOKEN"; +NSString *const kMXErrCodeStringBadJSON = @"M_BAD_JSON"; +NSString *const kMXErrCodeStringNotJSON = @"M_NOT_JSON"; +NSString *const kMXErrCodeStringNotFound = @"M_NOT_FOUND"; +NSString *const kMXErrCodeStringLimitExceeded = @"M_LIMIT_EXCEEDED"; +NSString *const kMXErrCodeStringUserInUse = @"M_USER_IN_USE"; +NSString *const kMXErrCodeStringRoomInUse = @"M_ROOM_IN_USE"; +NSString *const kMXErrCodeStringBadPagination = @"M_BAD_PAGINATION"; +NSString *const kMXErrCodeStringUnauthorized = @"M_UNAUTHORIZED"; +NSString *const kMXErrCodeStringLoginEmailURLNotYet = @"M_LOGIN_EMAIL_URL_NOT_YET"; +NSString *const kMXErrCodeStringThreePIDAuthFailed = @"M_THREEPID_AUTH_FAILED"; NSString *const kMXErrorStringInvalidToken = @"Invalid token"; diff --git a/MatrixSDK/MXRestClient.h b/MatrixSDK/MXRestClient.h index 6028a55ad5..b06e3b435f 100644 --- a/MatrixSDK/MXRestClient.h +++ b/MatrixSDK/MXRestClient.h @@ -55,14 +55,6 @@ typedef NSString* MXRoomVisibility; FOUNDATION_EXPORT NSString *const kMXRoomVisibilityPublic; FOUNDATION_EXPORT NSString *const kMXRoomVisibilityPrivate; -/** - Types of third party media. - The list is not exhautive and depends on the Identity server capabilities. - */ -typedef NSString* MX3PIDMedium; -FOUNDATION_EXPORT NSString *const kMX3PIDMediumEmail; -FOUNDATION_EXPORT NSString *const kMX3PIDMediumMSISDN; - /** MXRestClient error domain */ @@ -128,6 +120,11 @@ typedef enum : NSUInteger */ @property (nonatomic) NSString *identityServer; +/** + The current trusted certificate (if any). + */ +@property (nonatomic, readonly) NSData* allowedCertificate; + /** Create an instance based on homeserver url. @@ -149,17 +146,27 @@ typedef enum : NSUInteger - (void)close; #pragma mark - Registration operations +/** + Check whether a username is already in use. + + @username the user name to test (This value must not be nil). + @param callback A block object called when the operation is completed. + + @return a MXHTTPOperation instance. + */ +- (MXHTTPOperation*)isUserNameInUse:(NSString*)username + callback:(void (^)(BOOL isUserNameInUse))callback; /** Get the list of register flows supported by the home server. - @param success A block object called when the operation succeeds. It provides the raw JSON response - from the server. + @param success A block object called when the operation succeeds. It provides the server response + as an MXAuthenticationSession instance. @param failure A block object called when the operation fails. @return a MXHTTPOperation instance. */ -- (MXHTTPOperation*)getRegisterFlow:(void (^)(NSDictionary *JSONResponse))success - failure:(void (^)(NSError *error))failure; +- (MXHTTPOperation*)getRegisterSession:(void (^)(MXAuthenticationSession *authSession))success + failure:(void (^)(NSError *error))failure; /** Generic registration action request. @@ -212,14 +219,14 @@ typedef enum : NSUInteger /** Get the list of login flows supported by the home server. - @param success A block object called when the operation succeeds. It provides the raw JSON response - from the server. + @param success A block object called when the operation succeeds. It provides the server response + as an MXAuthenticationSession instance. @param failure A block object called when the operation fails. @return a MXHTTPOperation instance. */ -- (MXHTTPOperation*)getLoginFlow:(void (^)(NSDictionary *JSONResponse))success - failure:(void (^)(NSError *error))failure; +- (MXHTTPOperation*)getLoginSession:(void (^)(MXAuthenticationSession *authSession))success + failure:(void (^)(NSError *error))failure; /** Generic login action request. @@ -903,6 +910,36 @@ typedef enum : NSUInteger success:(void (^)(NSString *avatarUrl))success failure:(void (^)(NSError *error))failure; +/** + Link an authenticated 3rd party id to the Matrix user. + + @param sid the id provided during the 3PID validation session ([MXRestClient requestEmailValidation:]). + @param clientSecret the same secret key used in the validation session. + @param bind whether the homeserver should also bind this third party identifier + to the account's Matrix ID with the identity server. + + @param success A block object called when the operation succeeds. + @param failure A block object called when the operation fails. + + @return a MXHTTPOperation instance. + */ +- (MXHTTPOperation*)add3PID:(NSString*)sid + clientSecret:(NSString*)clientSecret + bind:(BOOL)bind + success:(void (^)())success + failure:(void (^)(NSError *error))failure; + +/** + List all 3PIDs linked to the Matrix user account. + + @param success A block object called when the operation succeeds. + @param failure A block object called when the operation fails. + + @return a MXHTTPOperation instance. + */ +- (MXHTTPOperation*)threePIDs:(void (^)(NSArray *threePIDs))success + failure:(void (^)(NSError *error))failure; + #pragma mark - Presence operations /** @@ -1058,9 +1095,9 @@ typedef enum : NSUInteger @return a MXHTTPOperation instance. */ - (MXHTTPOperation*)lookup3pid:(NSString*)address - forMedium:(MX3PIDMedium)medium - success:(void (^)(NSString *userId))success - failure:(void (^)(NSError *error))failure; + forMedium:(MX3PIDMedium)medium + success:(void (^)(NSString *userId))success + failure:(void (^)(NSError *error))failure; /** Retrieve user matrix ids from a list of 3rd party ids. @@ -1082,11 +1119,13 @@ typedef enum : NSUInteger failure:(void (^)(NSError *error))failure; /** - Start the validation process of an email address. + Request the validation of an email address. + + The identity server will send an email to this address. The end user + will have to click on the link it contains to validate the address. - The identity server will send a validation token to this email. - This validation token must be then send back to the identity server with [MXRestClient validateEmail] - in order to complete the email authentication. + Use the returned sid to complete operations that require authenticated email + like [MXRestClient add3PID:]. @param email the email address to validate. @param clientSecret a secret key generated by the client. ([MXTools generateSecret] creates such key) @@ -1095,7 +1134,7 @@ typedef enum : NSUInteger failed. @param success A block object called when the operation succeeds. It provides the id of the - email validation session. It must be then passed to [MXRestClient validateEmail]. + email validation session. @param failure A block object called when the operation fails. @return a MXHTTPOperation instance. @@ -1106,44 +1145,6 @@ typedef enum : NSUInteger success:(void (^)(NSString *sid))success failure:(void (^)(NSError *error))failure; -/** - Complete the email validation by sending the validation token the user received by email. - - @param sid the id of the email validation session. - @param validationToken the validation token the user received by email. - @param clientSecret the same secret key used in [MXRestClient requestEmailValidation]. - - @param success A block object called when the operation succeeds. It indicates if the - validation has succeeded. - @param failure A block object called when the operation fails. - - @return a MXHTTPOperation instance. - */ -- (MXHTTPOperation*)validateEmail:(NSString*)sid - validationToken:(NSString*)validationToken - clientSecret:(NSString*)clientSecret - success:(void (^)(BOOL success))success - failure:(void (^)(NSError *error))failure; - -/** - Link an authenticated 3rd party id to a Matrix user id. - - @param userId the Matrix user id to link the 3PID with. - @param sid the id provided during the 3PID validation session. - @param clientSecret the same secret key used in the validation session. - - @param success A block object called when the operation succeeds. It provides the raw - server response. - @param failure A block object called when the operation fails. - - @return a MXHTTPOperation instance. - */ -- (MXHTTPOperation*)bind3PID:(NSString*)userId - sid:(NSString*)sid - clientSecret:(NSString*)clientSecret - success:(void (^)(NSDictionary *JSONResponse))success - failure:(void (^)(NSError *error))failure; - #pragma mark - VoIP API /** diff --git a/MatrixSDK/MXRestClient.m b/MatrixSDK/MXRestClient.m index 5adc1d531a..1a70a75086 100644 --- a/MatrixSDK/MXRestClient.m +++ b/MatrixSDK/MXRestClient.m @@ -162,9 +162,31 @@ - (void)setCredentials:(MXCredentials *)inCredentials } } +- (NSData*)allowedCertificate +{ + return httpClient.allowedCertificate; +} + #pragma mark - Registration operations -- (MXHTTPOperation*)getRegisterFlow:(void (^)(NSDictionary *JSONResponse))success - failure:(void (^)(NSError *error))failure +- (MXHTTPOperation*)isUserNameInUse:(NSString*)username + callback:(void (^)(BOOL isUserNameInUse))callback +{ + // Trigger a fake registration to know whether the user name is available or not. + return [self registerOrLogin:MXAuthActionRegister + parameters:@{@"username": username} + success:nil + failure:^(NSError *error) { + + NSDictionary* dict = error.userInfo; + BOOL isUserNameInUse = ([[dict valueForKey:@"errcode"] isEqualToString:kMXErrCodeStringUserInUse]); + + callback(isUserNameInUse); + + }]; +} + +- (MXHTTPOperation*)getRegisterSession:(void (^)(MXAuthenticationSession *authSession))success + failure:(void (^)(NSError *error))failure { return [self getRegisterOrLoginFlow:MXAuthActionRegister success:success failure:failure]; } @@ -190,8 +212,8 @@ - (NSString*)registerFallback; } #pragma mark - Login operations -- (MXHTTPOperation*)getLoginFlow:(void (^)(NSDictionary *JSONResponse))success - failure:(void (^)(NSError *error))failure +- (MXHTTPOperation*)getLoginSession:(void (^)(MXAuthenticationSession *authSession))success + failure:(void (^)(NSError *error))failure { return [self getRegisterOrLoginFlow:MXAuthActionLogin success:success failure:failure]; } @@ -273,7 +295,7 @@ - (NSString*)authActionPath:(MXAuthAction)authAction } - (MXHTTPOperation*)getRegisterOrLoginFlow:(MXAuthAction)authAction - success:(void (^)(NSDictionary *JSONResponse))success failure:(void (^)(NSError *error))failure + success:(void (^)(MXAuthenticationSession *authSession))success failure:(void (^)(NSError *error))failure { NSString *httpMethod = @"GET"; NSDictionary *parameters = nil; @@ -295,13 +317,13 @@ - (MXHTTPOperation*)getRegisterOrLoginFlow:(MXAuthAction)authAction // sanity check if (success) { - success(JSONResponse); + success([MXAuthenticationSession modelFromJSON:JSONResponse]); } } failure:^(NSError *error) { - // C-S API v2: The login mechanism should be available in response data in case of unauthorized request. + // The login mechanism should be available in response data in case of unauthorized request. NSDictionary *JSONResponse = nil; if (error.userInfo[MXHTTPClientErrorResponseDataKey]) { @@ -312,7 +334,7 @@ - (MXHTTPOperation*)getRegisterOrLoginFlow:(MXAuthAction)authAction { if (success) { - success(JSONResponse); + success([MXAuthenticationSession modelFromJSON:JSONResponse]); } } else if (failure) @@ -329,6 +351,7 @@ - (MXHTTPOperation*)registerOrLogin:(MXAuthAction)authAction parameters:(NSDicti path:[self authActionPath:authAction] parameters:parameters success:^(NSDictionary *JSONResponse) { + if (success) { success(JSONResponse); @@ -336,10 +359,12 @@ - (MXHTTPOperation*)registerOrLogin:(MXAuthAction)authAction parameters:(NSDicti } failure:^(NSError *error) { + if (failure) { failure(error); } + }]; } @@ -347,8 +372,7 @@ - (MXHTTPOperation*)registerOrLoginWithUser:(MXAuthAction)authAction user:(NSStr success:(void (^)(MXCredentials *))success failure:(void (^)(NSError *))failure { // Is it an email or a username? - NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"^\\S+@\\S+\\.\\S+$" options:NSRegularExpressionCaseInsensitive error:nil]; - BOOL isEmailAddress = (nil != [regex firstMatchInString:user options:0 range:NSMakeRange(0, user.length)]); + BOOL isEmailAddress = [MXTools isEmailAddress:user]; NSDictionary *parameters; @@ -1765,6 +1789,72 @@ - (MXHTTPOperation*)avatarUrlForUser:(NSString*)userId }]; } +- (MXHTTPOperation*)add3PID:(NSString*)sid + clientSecret:(NSString*)clientSecret + bind:(BOOL)bind + success:(void (^)())success + failure:(void (^)(NSError *error))failure +{ + NSURL *identityServerURL = [NSURL URLWithString:_identityServer]; + NSDictionary *parameters = @{ + @"three_pid_creds": @{ + @"id_server": identityServerURL.host, + @"sid": sid, + @"client_secret": clientSecret + }, + @"bind": @(bind) + }; + + NSString *path = [NSString stringWithFormat:@"%@/account/3pid", apiPathPrefix]; + return [httpClient requestWithMethod:@"POST" + path:path + parameters:parameters + success:^(NSDictionary *JSONResponse) { + if (success) + { + success(); + } + } + failure:^(NSError *error) { + if (failure) + { + failure(error); + } + }]; +} + +- (MXHTTPOperation*)threePIDs:(void (^)(NSArray *threePIDs))success + failure:(void (^)(NSError *error))failure +{ + NSString *path = [NSString stringWithFormat:@"%@/account/3pid", apiPathPrefix]; + return [httpClient requestWithMethod:@"GET" + path:path + parameters:nil + success:^(NSDictionary *JSONResponse) { + if (success) + { + // Use here the processing queue in order to keep the server response order + dispatch_async(processingQueue, ^{ + + dispatch_async(dispatch_get_main_queue(), ^{ + + NSArray *threePIDs; + MXJSONModelSetMXJSONModelArray(threePIDs, MXThirdPartyIdentifier, JSONResponse[@"threepids"]); + success(threePIDs); + + }); + + }); + } + } + failure:^(NSError *error) { + if (failure) + { + failure(error); + } + }]; +} + #pragma mark - Presence operations - (MXHTTPOperation*)setPresence:(MXPresence)presence andStatusMessage:(NSString*)statusMessage @@ -2292,7 +2382,15 @@ - (MXHTTPOperation*)requestEmailValidation:(NSString*)email if (success) { NSString *sid; - MXJSONModelSetString(sid, JSONResponse[@"sid"]); + // Temporary workaround for https://matrix.org/jira/browse/SYD-17 + if ([JSONResponse[@"sid"] isKindOfClass:NSNumber.class]) + { + sid = [(NSNumber*)JSONResponse[@"sid"] stringValue]; + } + else + { + MXJSONModelSetString(sid, JSONResponse[@"sid"]); + } success(sid); } } @@ -2304,58 +2402,6 @@ - (MXHTTPOperation*)requestEmailValidation:(NSString*)email }]; } -- (MXHTTPOperation*)validateEmail:(NSString*)sid - validationToken:(NSString*)validationToken - clientSecret:(NSString*)clientSecret - success:(void (^)(BOOL success))success - failure:(void (^)(NSError *error))failure -{ - // The identity server expects params in the URL - NSString *path = [NSString stringWithFormat:@"validate/email/submitToken?token=%@&sid=%@&clientSecret=%@", validationToken, sid, clientSecret]; - return [identityHttpClient requestWithMethod:@"POST" - path:path - parameters:nil - success:^(NSDictionary *JSONResponse) { - if (success) - { - BOOL succeeded = false; - MXJSONModelSetBoolean(succeeded, JSONResponse[@"success"]); - success(succeeded); - } - } - failure:^(NSError *error) { - if (failure) - { - failure(error); - } - }]; -} - -- (MXHTTPOperation*)bind3PID:(NSString*)userId - sid:(NSString*)sid - clientSecret:(NSString*)clientSecret - success:(void (^)(NSDictionary *JSONResponse))success - failure:(void (^)(NSError *error))failure -{ - // The identity server expects params in the URL - NSString *path = [NSString stringWithFormat:@"3pid/bind?mxid=%@&sid=%@&clientSecret=%@", userId, sid, clientSecret]; - return [identityHttpClient requestWithMethod:@"POST" - path:path - parameters:nil - success:^(NSDictionary *JSONResponse) { - if (success) - { - // For now, provide the JSON response as is - success(JSONResponse); - } - } - failure:^(NSError *error) { - if (failure) - { - failure(error); - } - }]; -} #pragma mark - VoIP API diff --git a/MatrixSDK/MXSession.h b/MatrixSDK/MXSession.h index 4561496ed6..2f654508b4 100644 --- a/MatrixSDK/MXSession.h +++ b/MatrixSDK/MXSession.h @@ -372,6 +372,15 @@ typedef void (^MXOnBackgroundSyncFail)(NSError *error); */ - (MXRoom *)roomWithRoomId:(NSString*)roomId; +/** + Get the MXRoom instance of the room that owns the passed room alias. + + @param roomId The room alias to look for. + + @return the MXRoom instance. + */ +- (MXRoom *)roomWithAlias:(NSString*)alias; + /** Get the list of all rooms data. diff --git a/MatrixSDK/MXSession.m b/MatrixSDK/MXSession.m index af38451028..a82277a5ce 100644 --- a/MatrixSDK/MXSession.m +++ b/MatrixSDK/MXSession.m @@ -28,7 +28,7 @@ #pragma mark - Constants definitions -const NSString *MatrixSDKVersion = @"0.6.4"; +const NSString *MatrixSDKVersion = @"0.6.5"; NSString *const kMXSessionStateDidChangeNotification = @"kMXSessionStateDidChangeNotification"; NSString *const kMXSessionNewRoomNotification = @"kMXSessionNewRoomNotification"; NSString *const kMXSessionWillLeaveRoomNotification = @"kMXSessionWillLeaveRoomNotification"; @@ -936,6 +936,24 @@ - (MXRoom *)roomWithRoomId:(NSString *)roomId } } +- (MXRoom *)roomWithAlias:(NSString *)alias +{ + MXRoom *theRoom; + + if (alias) + { + for (MXRoom *room in rooms.allValues) + { + if (room.state.aliases && NSNotFound != [room.state.aliases indexOfObject:alias]) + { + theRoom = room; + break; + } + } + } + return theRoom; +} + - (NSArray *)rooms { return [rooms allValues]; diff --git a/MatrixSDK/Utils/MXHTTPClient.m b/MatrixSDK/Utils/MXHTTPClient.m index f8b057c2be..6164fa68eb 100644 --- a/MatrixSDK/Utils/MXHTTPClient.m +++ b/MatrixSDK/Utils/MXHTTPClient.m @@ -326,30 +326,43 @@ - (void)tryRequest:(MXHTTPOperation*)mxHTTPOperation // The device is not connected to the internet, wait for the connection to be up again before retrying __weak __typeof(self)weakSelf = self; id networkComeBackObserver = [self addObserverForNetworkComeBack:^{ + __strong __typeof(weakSelf)strongSelf = weakSelf; NSLog(@"[MXHTTPClient] Network is back for request %p", mxHTTPOperation); // Flag this request as retried lastError = nil; - - NSLog(@"[MXHTTPClient] Retry request %p. Try #%tu/%tu. Age: %tums. Max retries time: %tums", mxHTTPOperation, mxHTTPOperation.numberOfTries + 1, mxHTTPOperation.maxNumberOfTries, mxHTTPOperation.age, mxHTTPOperation.maxRetriesTime); - - [strongSelf tryRequest:mxHTTPOperation method:httpMethod path:path parameters:parameters data:data headers:headers timeout:timeoutInSeconds uploadProgress:uploadProgress success:^(NSDictionary *JSONResponse) { - - NSLog(@"[MXHTTPClient] Request %p finally succeeded after %tu tries and %tums", mxHTTPOperation, mxHTTPOperation.numberOfTries, mxHTTPOperation.age); - - success(JSONResponse); - - // The request is complete, managed the next one - [strongSelf wakeUpNextReachabilityServer]; - - } failure:^(NSError *error) { - failure(error); - + + // Check whether the pending operation was not cancelled. + if (mxHTTPOperation.maxNumberOfTries) + { + NSLog(@"[MXHTTPClient] Retry request %p. Try #%tu/%tu. Age: %tums. Max retries time: %tums", mxHTTPOperation, mxHTTPOperation.numberOfTries + 1, mxHTTPOperation.maxNumberOfTries, mxHTTPOperation.age, mxHTTPOperation.maxRetriesTime); + + [strongSelf tryRequest:mxHTTPOperation method:httpMethod path:path parameters:parameters data:data headers:headers timeout:timeoutInSeconds uploadProgress:uploadProgress success:^(NSDictionary *JSONResponse) { + + NSLog(@"[MXHTTPClient] Request %p finally succeeded after %tu tries and %tums", mxHTTPOperation, mxHTTPOperation.numberOfTries, mxHTTPOperation.age); + + success(JSONResponse); + + // The request is complete, managed the next one + [strongSelf wakeUpNextReachabilityServer]; + + } failure:^(NSError *error) { + failure(error); + + // The request is complete, managed the next one + [strongSelf wakeUpNextReachabilityServer]; + }]; + } + else + { + NSLog(@"[MXHTTPClient] The request %p has been cancelled", mxHTTPOperation); + // The request is complete, managed the next one [strongSelf wakeUpNextReachabilityServer]; - }]; + } + }]; // Wait for a limit of time. After that the request is considered expired diff --git a/MatrixSDK/Utils/MXLogger.h b/MatrixSDK/Utils/MXLogger.h index 97930ddabc..1143f3b246 100644 --- a/MatrixSDK/Utils/MXLogger.h +++ b/MatrixSDK/Utils/MXLogger.h @@ -57,6 +57,12 @@ */ + (void)logCrashes:(BOOL)logCrashes; +/** + Set the app build version. + It will be reported in crash report. + */ ++ (void)setBuildVersion:(NSString*)buildVersion; + /** If any, get the file containing the last application crash log. diff --git a/MatrixSDK/Utils/MXLogger.m b/MatrixSDK/Utils/MXLogger.m index 3f53c33920..ecd1b7b5cf 100644 --- a/MatrixSDK/Utils/MXLogger.m +++ b/MatrixSDK/Utils/MXLogger.m @@ -21,6 +21,8 @@ // stderr so it can be restored int stderrSave = 0; +static NSString *buildVersion; + #define MXLOGGER_CRASH_LOG @"crash.log" @implementation MXLogger @@ -142,12 +144,14 @@ static void handleUncaughtException(NSException *exception) NSString *model = [[UIDevice currentDevice] model]; NSString *version = [[UIDevice currentDevice] systemVersion]; NSArray *backtrace = [exception callStackSymbols]; - NSString *description = [NSString stringWithFormat:@"[%@]\n%@\nApplication: %@ (%@)\nApplication version: %@\nMatrix SDK version: %@\n%@ %@\n%@\n", + NSString *description = [NSString stringWithFormat:@"%tu - %@\n%@\nApplication: %@ (%@)\nApplication version: %@\nMatrix SDK version: %@\nBuild: %@\n%@ %@\n%@\n", + [[NSDate date] timeIntervalSince1970], [NSDate date], exception.description, app, appId, appVersion, MatrixSDKVersion, + buildVersion, model, version, backtrace]; @@ -195,6 +199,12 @@ + (void)logCrashes:(BOOL)logCrashes signal(SIGBUS, SIG_DFL); } } + ++ (void)setBuildVersion:(NSString *)buildVersion2 +{ + buildVersion = buildVersion2; +} + // Return the path of the crash log file static NSString* crashLogPath(void) { diff --git a/MatrixSDK/Utils/MXTools.h b/MatrixSDK/Utils/MXTools.h index 70b92bfe2a..6cddc78aea 100644 --- a/MatrixSDK/Utils/MXTools.h +++ b/MatrixSDK/Utils/MXTools.h @@ -36,4 +36,11 @@ */ + (NSString*)generateSecret; +/** + Check whether a string is formatted as an email address. + + @return YES if the provided string is formatted as an email. + */ ++ (BOOL)isEmailAddress:(NSString *)inputString; + @end diff --git a/MatrixSDK/Utils/MXTools.m b/MatrixSDK/Utils/MXTools.m index 90e0841a3b..a24d9635ec 100644 --- a/MatrixSDK/Utils/MXTools.m +++ b/MatrixSDK/Utils/MXTools.m @@ -162,4 +162,11 @@ + (NSString *)generateSecret return [[NSProcessInfo processInfo] globallyUniqueString]; } ++ (BOOL)isEmailAddress:(NSString *)inputString +{ + NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,}$" options:NSRegularExpressionCaseInsensitive error:nil]; + + return (nil != [regex firstMatchInString:inputString options:0 range:NSMakeRange(0, inputString.length)]); +} + @end diff --git a/MatrixSDKTests/MXSessionTests.m b/MatrixSDKTests/MXSessionTests.m index c1f047d007..10003ffe85 100644 --- a/MatrixSDKTests/MXSessionTests.m +++ b/MatrixSDKTests/MXSessionTests.m @@ -55,6 +55,37 @@ - (void)tearDown } +- (void)testRoomWithAlias +{ + [matrixSDKTestsData doMXRestClientTestWithBob:self readyToTest:^(MXRestClient *bobRestClient, XCTestExpectation *expectation) { + + NSString *alias = [[NSProcessInfo processInfo] globallyUniqueString]; + + // Room with a tag with "oranges" order + [bobRestClient createRoom:nil visibility:kMXRoomVisibilityPrivate roomAlias:alias topic:nil success:^(MXCreateRoomResponse *response) { + + mxSession = [[MXSession alloc] initWithMatrixRestClient:bobRestClient]; + [mxSession start:^{ + + MXRoom *room = [mxSession roomWithAlias:response.roomAlias]; + + XCTAssertNotNil(room); + XCTAssertEqual(room.state.aliases.count, 1); + XCTAssertEqualObjects(room.state.aliases[0], response.roomAlias); + + [expectation fulfill]; + + } failure:^(NSError *error) { + XCTFail(@"The request should not fail - NSError: %@", error); + [expectation fulfill]; + }]; + + } failure:^(NSError *error) { + NSAssert(NO, @"Cannot set up intial test conditions - error: %@", error); + }]; + }]; +} + - (void)testRecents { [matrixSDKTestsData doMXRestClientTestInABobRoomAndANewTextMessage:self newTextMessage:@"This is a text message for recents" onReadyToTest:^(MXRestClient *bobRestClient, NSString *roomId, NSString *new_text_message_eventId, XCTestExpectation *expectation) { diff --git a/scripts/symbolicate/.gitignore b/scripts/symbolicate/.gitignore new file mode 100644 index 0000000000..66053aad1c --- /dev/null +++ b/scripts/symbolicate/.gitignore @@ -0,0 +1,6 @@ +# Ignore everything +* + +# But not these files... +!.gitignore +!*.py diff --git a/scripts/symbolicate/credentials.py b/scripts/symbolicate/credentials.py new file mode 100644 index 0000000000..b14c0836d6 --- /dev/null +++ b/scripts/symbolicate/credentials.py @@ -0,0 +1,5 @@ +# Your credentials on Matrix.org Jenkins +jenkins = { + 'login' : '', + 'password' : '' +} \ No newline at end of file diff --git a/scripts/symbolicate/symbolicate.py b/scripts/symbolicate/symbolicate.py new file mode 100755 index 0000000000..7bcbf18508 --- /dev/null +++ b/scripts/symbolicate/symbolicate.py @@ -0,0 +1,197 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# Copyright 2016 OpenMarket 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. + + +''' +Script to symbolicate addresses of a crash dump by using a dSYMs file map +(located in XXXXXX.app.dSYM/Contents/Resources/DWARF). + +It symbolicates only symbols that belong to the app, not system symbols. + +It automatically downloads the symbols file from the Matrix Jenkins and thus +needs your credentials. +Make sure to set them in credentials.py. + +You can put several crash dumps into one txt file. +The script will handle all txt files of the directory as a crash dump +and generates the converted files into the "output" folder. + +''' + +import sys, os, shutil, copy, re, glob +import requests, time +import credentials + +def symbolicate(crashLogData): + + if not ("Build: " in crashLogData): + print "Error: Can't determine build version" + exit(-1) + + # Find the version of the app that generated the crash + app = crashLogData.split("Application: ")[1] .split(" ", 1)[0] + buildVersion = crashLogData.split("Build: ")[1] .split("\n", 1)[0] + + # Remove any white characters + buildVersion = re.sub("\s", "", buildVersion) + + # Check the symbols FILE is here + dSYMSFile = os.path.join("symbols", app, buildVersion, app) + if not os.path.exists(dSYMSFile): + + # No, get it from the Matrix Jenkins + if app == "Console": + if "develop" in buildVersion: + jenkinsJob = "MatrixConsoleiOSDevelop" + else: + jenkinsJob = "MatrixConsoleiOS" + elif app == "Vector": + if "develop" in buildVersion: + jenkinsJob = "VectoriOSDevelop" + else: + jenkinsJob = "VectoriOS" + + jenkinsBuild = re.findall(r"#(.*)", buildVersion) + if len(jenkinsBuild): + jenkinsBuild = jenkinsBuild[0] + + if not jenkinsJob or not jenkinsBuild: + print "Error: Can't extract build version" + exit(-1) + + XCArchiveTarGzURL = "http://matrix.org/jenkins/view/MatrixView/job/%s/%s/artifact/out/*.tar.gz/*zip*/out.zip" % (jenkinsJob, jenkinsBuild) + + print "Downloading symbols for %s (Build %s) at %s..." % (app, buildVersion, XCArchiveTarGzURL) + responseCode = download_xcarchive_tar_gz(app, XCArchiveTarGzURL, dSYMSFile) + + if not 200 == responseCode: + print "Error: Can't download the symbols file (Responce code: %d)" % responseCode + if 401 == responseCode: + print "Check your credentials in credentials.py" + exit(-1) + + # Quick sanitization of HLTM chars + crashLogData = crashLogData.replace("<", "<").replace(">", ">") + + # Extract memory addresses of our symbors + memoryLocations = re.findall(r"(0x.*) %s \+ (.*)" % app, crashLogData) + + for (address, offset) in memoryLocations: + + # Convert the relative address in hex (implicitly required by atos) and the offset + loadAddress = hex(int(address, 16) - int(offset)) + + # Use system tool to retrieve the symbol + #print("atos -arch arm64 -o %s -l %s %s > symbol.tmp" % (dSYMSFile, loadAddress, address)) + os.system("atos -arch arm64 -o %s -l %s %s > symbol.tmp" % (dSYMSFile, loadAddress, address)) + + # Extract the symbol + file = open("symbol.tmp") + symbol = file.read().replace("\n", "") + file.close() + os.remove("symbol.tmp") + + crashLogData = crashLogData.replace(offset, "%s -> %s" % (offset, symbol)) + + # Check result + if "Signal detected:" in crashLogData: + # In the case of a signal, we must find the handleSignal symbol in the result + if not "handleSignal" in crashLogData: + crashLogData = crashLogData + "\n* Warning: This symbolication seems invalid!\n" + else: + # In the case of a NS Exception, if the crash occured on the main thread, we must find the start and the main function at the call stack root. + # If it is from another thread (ie its stack starts with start_wqthread), we can't validate the result + if not "start_wqthread" in crashLogData: + if not ("main" in crashLogData and "start" in crashLogData): + crashLogData = crashLogData + "\n* Warning: This symbolication seems invalid!\n" + + return crashLogData + +def downloadFile(url, login, password, destination): + # Clean first + if os.path.exists(destination): + os.remove(destination) + + r = requests.get(url, stream=True, auth=requests.auth.HTTPBasicAuth(login, password)) + + if 200 == r.status_code: + if not os.path.exists(os.path.dirname(destination)): + os.makedirs(os.path.dirname(destination)) + + with open(destination, 'wb') as fd: + size = 0 + for chunk in r.iter_content(1024): + fd.write(chunk) + + return r.status_code + +def download_xcarchive_tar_gz(app, url, destination, appVersion=""): + # First download the file + targzFile = "/tmp/xcarchive.tar.gz" + + # Clean first + if os.path.exists("/tmp/%s.xcarchive" % app): + os.system("rm -rf /tmp/%s.xcarchive" % app) + + responseCode = downloadFile(url, credentials.jenkins['login'], credentials.jenkins['password'], targzFile) + if 200 == responseCode: + # Not very nice: Let Mac OSX untargzipped everything + os.system("open %s" % targzFile) + print "Sleeping 5s to let time for the system to unzip xcarchive.tar.gz..." + time.sleep(5) + + # And copy the symbol file at requested destination + if not os.path.exists(os.path.dirname(destination)): + os.makedirs(os.path.dirname(destination)) + + print "cp /tmp/%s.xcarchive/dSYMs/%s.app.dSYM/Contents/Resources/DWARF/%s %s" % (app, app, app, destination) + os.system("cp /tmp/%s.xcarchive/dSYMs/%s.app.dSYM/Contents/Resources/DWARF/%s %s" % (app, app, app, destination)) + + # Check the expected symbols file is here + if not os.path.exists(destination): + print "Error: Can't extract the symbols file" + responseCode = 404 + + return responseCode + + +if __name__ == "__main__": + + if not len(glob.glob("*.log")): + print "Error: No .log files found in current folder" + exit(-1) + + # Clean output dir + if os.path.exists("output"): + os.system("rm -rf output") + os.mkdir("output") + + # Process every crash dump (all log files of this directory) + for sFile in glob.glob("*.log"): + print "# Processing %s" % sFile + + oFile = open(sFile) + crashLogData = oFile.read() + oFile.close() + + crashLogData = symbolicate(crashLogData) + + oFile = open("output/" + sFile, "w") + oFile.write(crashLogData) + oFile.close() + + print crashLogData + print "Stored in %s" % ("output/" + sFile)