diff --git a/CHANGES.rst b/CHANGES.rst
index a6bade52f..dc536476e 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -1,6 +1,9 @@
Changes in 0.8.3 (2019-xx-xx)
===============================================
+Bug fix:
+ * Widgets: Attempt to re-register for a scalar token if ours is invalid (#2326).
+ * Widgets: Pass scalar_token only when required.
Changes in 0.8.2 (2019-03-11)
diff --git a/Riot/Assets/Riot-Defaults.plist b/Riot/Assets/Riot-Defaults.plist
index 9c18e135d..7ad072f6b 100644
--- a/Riot/Assets/Riot-Defaults.plist
+++ b/Riot/Assets/Riot-Defaults.plist
@@ -28,6 +28,11 @@
https://scalar-staging.riot.im/scalar-web/
integrationsRestUrl
https://scalar-staging.riot.im/scalar/api
+ integrationsWidgetsUrls
+
+ https://scalar-staging.riot.im/scalar/api
+ https://scalar.vector.im/api
+
piwik
url
diff --git a/Riot/Generated/RiotDefaults.swift b/Riot/Generated/RiotDefaults.swift
index 5c063181b..51897ad61 100644
--- a/Riot/Generated/RiotDefaults.swift
+++ b/Riot/Generated/RiotDefaults.swift
@@ -21,6 +21,7 @@ internal enum RiotDefaults {
internal static let identityserverurl: String = _document["identityserverurl"]
internal static let integrationsRestUrl: String = _document["integrationsRestUrl"]
internal static let integrationsUiUrl: String = _document["integrationsUiUrl"]
+ internal static let integrationsWidgetsUrls: [String] = _document["integrationsWidgetsUrls"]
internal static let matrixApps: Bool = _document["matrixApps"]
internal static let maxAllowedMediaCacheSize: Int = _document["maxAllowedMediaCacheSize"]
internal static let pinRoomsWithMissedNotif: Bool = _document["pinRoomsWithMissedNotif"]
diff --git a/Riot/Managers/Widgets/Widget.m b/Riot/Managers/Widgets/Widget.m
index 3831d9a02..482f54e9c 100644
--- a/Riot/Managers/Widgets/Widget.m
+++ b/Riot/Managers/Widgets/Widget.m
@@ -49,68 +49,73 @@
- (MXHTTPOperation *)widgetUrl:(void (^)(NSString * _Nonnull))success failure:(void (^)(NSError * _Nonnull))failure
{
- // Format the url string with user data (including their scalar token)
- __weak typeof(self) weakSelf = self;
- return [[WidgetManager sharedManager] getScalarTokenForMXSession:_mxSession success:^(NSString *scalarToken) {
+ __block NSString *widgetUrl = _url;
- if (weakSelf)
+ // Format the url string with user data
+ NSString *userId = self.mxSession.myUser.userId;
+ NSString *displayName = self.mxSession.myUser.displayname ? self.mxSession.myUser.displayname : self.mxSession.myUser.userId;
+ NSString *avatarUrl = self.mxSession.myUser.avatarUrl ? self.mxSession.myUser.avatarUrl : @"";
+
+ // Escape everything to build a valid URL string
+ // We can't know where the values escaped here will be inserted in the URL, so the alphanumeric charset is used
+ userId = [MXTools encodeURIComponent:userId];
+ displayName = [MXTools encodeURIComponent:displayName];
+ avatarUrl = [MXTools encodeURIComponent:avatarUrl];
+
+ widgetUrl = [widgetUrl stringByReplacingOccurrencesOfString:@"$matrix_user_id" withString:userId];
+ widgetUrl = [widgetUrl stringByReplacingOccurrencesOfString:@"$matrix_display_name" withString:displayName];
+ widgetUrl = [widgetUrl stringByReplacingOccurrencesOfString:@"$matrix_avatar_url" withString:avatarUrl];
+
+ // Integrate widget data into widget url
+ for (NSString *key in _data)
+ {
+ NSString *paramKey = [NSString stringWithFormat:@"$%@", key];
+
+ NSString *dataString;
+ MXJSONModelSetString(dataString, _data[key]);
+
+ // Fix number data instead of expected string data
+ if (!dataString && [_data[key] isKindOfClass:NSNumber.class])
{
- typeof(self) self = weakSelf;
- NSString *userId = self.mxSession.myUser.userId;
- NSString *displayName = self.mxSession.myUser.displayname ? self.mxSession.myUser.displayname : self.mxSession.myUser.userId;
- NSString *avatarUrl = self.mxSession.myUser.avatarUrl ? self.mxSession.myUser.avatarUrl : @"";
-
- // Escape everything to build a valid URL string
- // We can't know where the values escaped here will be inserted in the URL, so the alphanumeric charset is used
- userId = [MXTools encodeURIComponent:userId];
- displayName = [MXTools encodeURIComponent:displayName];
- avatarUrl = [MXTools encodeURIComponent:avatarUrl];
-
- NSString *widgetUrl = _url;
- widgetUrl = [widgetUrl stringByReplacingOccurrencesOfString:@"$matrix_user_id" withString:userId];
- widgetUrl = [widgetUrl stringByReplacingOccurrencesOfString:@"$matrix_display_name" withString:displayName];
- widgetUrl = [widgetUrl stringByReplacingOccurrencesOfString:@"$matrix_avatar_url" withString:avatarUrl];
-
- // Integrate widget data into widget url
- for (NSString *key in _data)
- {
- NSString *paramKey = [NSString stringWithFormat:@"$%@", key];
-
- NSString *dataString;
- MXJSONModelSetString(dataString, _data[key]);
-
- // Fix number data instead of expected string data
- if (!dataString && [_data[key] isKindOfClass:NSNumber.class])
- {
- dataString = [((NSNumber*)_data[key]) stringValue];
- }
-
- if (dataString)
- {
- // same question as above
- NSString *value = [MXTools encodeURIComponent:dataString];
-
- widgetUrl = [widgetUrl stringByReplacingOccurrencesOfString:paramKey
- withString:value];
- }
- else
- {
- NSLog(@"[Widget] Error: Invalid data field value in %@ for key %@ in data %@", self, key, _data);
- }
- }
-
- // Add the user scalar token
- widgetUrl = [widgetUrl stringByAppendingString:[NSString stringWithFormat:@"%@scalar_token=%@",
- [widgetUrl containsString:@"?"] ? @"&" : @"?",
- scalarToken]];
-
- // Add the widget id
- widgetUrl = [widgetUrl stringByAppendingString:[NSString stringWithFormat:@"&widgetId=%@", _widgetId]];
-
- success(widgetUrl);
+ dataString = [((NSNumber*)_data[key]) stringValue];
}
- } failure:failure];
+ if (dataString)
+ {
+ // same question as above
+ NSString *value = [MXTools encodeURIComponent:dataString];
+
+ widgetUrl = [widgetUrl stringByReplacingOccurrencesOfString:paramKey
+ withString:value];
+ }
+ else
+ {
+ NSLog(@"[Widget] Error: Invalid data field value in %@ for key %@ in data %@", self, key, _data);
+ }
+ }
+
+ // Add the widget id
+ widgetUrl = [widgetUrl stringByAppendingString:[NSString stringWithFormat:@"%@widgetId=%@",
+ [widgetUrl containsString:@"?"] ? @"&" : @"?",
+ _widgetId]];
+
+ // Check if their scalar token must added
+ if ([WidgetManager isScalarUrl:widgetUrl])
+ {
+ return [[WidgetManager sharedManager] getScalarTokenForMXSession:_mxSession validate:NO success:^(NSString *scalarToken) {
+ // Add the user scalar token
+ widgetUrl = [widgetUrl stringByAppendingString:[NSString stringWithFormat:@"&scalar_token=%@",
+ scalarToken]];
+
+ success(widgetUrl);
+ } failure:failure];
+ }
+ else
+ {
+ success(widgetUrl);
+ }
+
+ return nil;
}
- (BOOL)isActive
diff --git a/Riot/Managers/Widgets/WidgetManager.h b/Riot/Managers/Widgets/WidgetManager.h
index a95ba83e1..83b41ac2f 100644
--- a/Riot/Managers/Widgets/WidgetManager.h
+++ b/Riot/Managers/Widgets/WidgetManager.h
@@ -187,12 +187,22 @@ WidgetManagerErrorCode;
to get one.
@param mxSession the session to check.
+ @param validate if it is cached, check its validity on the scalar server.
@param success A block object called when the operation succeeds.
@param failure A block object called when the operation fails.
*/
- (MXHTTPOperation *)getScalarTokenForMXSession:(MXSession*)mxSession
+ validate:(BOOL)validate
success:(void (^)(NSString *scalarToken))success
failure:(void (^)(NSError *error))failure;
+/**
+ Returns true if specified url is a scalar URL, typically https://scalar.vector.im/api
+
+ @param urlString the URL to check.
+ @return YES if specified URL is a scalar URL.
+ */
++ (BOOL)isScalarUrl:(NSString*)urlString;
+
@end
diff --git a/Riot/Managers/Widgets/WidgetManager.m b/Riot/Managers/Widgets/WidgetManager.m
index c225e140e..8f761bf08 100644
--- a/Riot/Managers/Widgets/WidgetManager.m
+++ b/Riot/Managers/Widgets/WidgetManager.m
@@ -442,6 +442,7 @@ NSString *const WidgetManagerErrorDomain = @"WidgetManagerErrorDomain";
#pragma mark - Modular interface
- (MXHTTPOperation *)getScalarTokenForMXSession:(MXSession*)mxSession
+ validate:(BOOL)validate
success:(void (^)(NSString *scalarToken))success
failure:(void (^)(NSError *error))failure;
{
@@ -450,65 +451,156 @@ NSString *const WidgetManagerErrorDomain = @"WidgetManagerErrorDomain";
__block NSString *scalarToken = [self scalarTokenForMXSession:mxSession];
if (scalarToken)
{
- success(scalarToken);
+ if (!validate)
+ {
+ success(scalarToken);
+ }
+ else
+ {
+ operation = [self validateScalarToken:scalarToken forMXSession:mxSession complete:^(BOOL valid) {
+
+ if (valid)
+ {
+ success(scalarToken);
+ }
+ else
+ {
+ NSLog(@"[WidgetManager] getScalarTokenForMXSession: Invalid stored token. Need to register for a new token");
+ MXHTTPOperation *operation2 = [self registerForScalarToken:mxSession success:success failure:failure];
+ [operation mutateTo:operation2];
+ }
+
+ } failure:failure];
+ }
}
else
{
- NSLog(@"[WidgetManager] getScalarTokenForMXSession: Need to register to get a token");
-
- __weak __typeof__(self) weakSelf = self;
- operation = [mxSession.matrixRestClient openIdToken:^(MXOpenIdToken *tokenObject) {
-
- typeof(self) self = weakSelf;
-
- if (self)
- {
- // Exchange the token for a scalar token
- NSString *modularRestUrl = [[NSUserDefaults standardUserDefaults] objectForKey:@"integrationsRestUrl"];
-
- MXHTTPClient *httpClient = [[MXHTTPClient alloc] initWithBaseURL:modularRestUrl andOnUnrecognizedCertificateBlock:nil];
-
- MXHTTPOperation *operation2 = [httpClient requestWithMethod:@"POST"
- path:@"register"
- parameters:tokenObject.JSONDictionary
- success:^(NSDictionary *JSONResponse) {
-
- MXJSONModelSetString(scalarToken, JSONResponse[@"scalar_token"])
- self->scalarTokens[mxSession.myUser.userId] = scalarToken;
-
- [self save];
-
- if (success)
- {
- success(scalarToken);
- }
-
- } failure:^(NSError *error) {
- NSLog(@"[WidgetManager] getScalarTokenForMXSession. Error in modular/register request");
-
- if (failure)
- {
- failure(error);
- }
- }];
-
- [operation mutateTo:operation2];
-
- }
-
- } failure:^(NSError *error) {
- NSLog(@"[WidgetManager] getScalarTokenForMXSession. Error in openIdToken request");
-
- if (failure)
- {
- failure(error);
- }
- }];
+ NSLog(@"[WidgetManager] getScalarTokenForMXSession: Need to register for a token");
+ operation = [self registerForScalarToken:mxSession success:success failure:failure];
}
return operation;
}
+- (MXHTTPOperation *)registerForScalarToken:(MXSession*)mxSession
+ success:(void (^)(NSString *scalarToken))success
+ failure:(void (^)(NSError *error))failure
+{
+ MXHTTPOperation *operation;
+
+ MXWeakify(self);
+ operation = [mxSession.matrixRestClient openIdToken:^(MXOpenIdToken *tokenObject) {
+ MXStrongifyAndReturnIfNil(self);
+
+ // Exchange the token for a scalar token
+ NSString *modularRestUrl = [[NSUserDefaults standardUserDefaults] objectForKey:@"integrationsRestUrl"];
+
+ MXHTTPClient *httpClient = [[MXHTTPClient alloc] initWithBaseURL:modularRestUrl andOnUnrecognizedCertificateBlock:nil];
+
+ MXHTTPOperation *operation2 =
+ [httpClient requestWithMethod:@"POST"
+ path:@"register?v=1.1"
+ parameters:tokenObject.JSONDictionary
+ success:^(NSDictionary *JSONResponse)
+ {
+
+ NSString *scalarToken;
+ MXJSONModelSetString(scalarToken, JSONResponse[@"scalar_token"])
+ self->scalarTokens[mxSession.myUser.userId] = scalarToken;
+
+ [self save];
+
+ if (success)
+ {
+ success(scalarToken);
+ }
+
+ } failure:^(NSError *error) {
+ NSLog(@"[WidgetManager] registerForScalarToken. Error in modular/register request");
+
+ if (failure)
+ {
+ failure(error);
+ }
+ }];
+
+ [operation mutateTo:operation2];
+
+ } failure:^(NSError *error) {
+ NSLog(@"[WidgetManager] registerForScalarToken. Error in openIdToken request");
+
+ if (failure)
+ {
+ failure(error);
+ }
+ }];
+
+ return operation;
+}
+
+- (MXHTTPOperation *)validateScalarToken:(NSString*)scalarToken forMXSession:(MXSession*)mxSession
+ complete:(void (^)(BOOL valid))complete
+ failure:(void (^)(NSError *error))failure
+{
+ NSString *modularRestUrl = [[NSUserDefaults standardUserDefaults] objectForKey:@"integrationsRestUrl"];
+ MXHTTPClient *httpClient = [[MXHTTPClient alloc] initWithBaseURL:modularRestUrl andOnUnrecognizedCertificateBlock:nil];
+
+ return [httpClient requestWithMethod:@"GET"
+ path:[NSString stringWithFormat:@"account?v=1.1&scalar_token=%@", scalarToken]
+ parameters:nil
+ success:^(NSDictionary *JSONResponse) {
+
+ NSString *userId;
+ MXJSONModelSetString(userId, JSONResponse[@"user_id"])
+
+ if ([userId isEqualToString:mxSession.myUser.userId])
+ {
+ complete(YES);
+ }
+ else
+ {
+ NSLog(@"[WidgetManager] validateScalarToken. Unexpected modular/account response: %@", JSONResponse);
+ complete(NO);
+ }
+
+ } failure:^(NSError *error) {
+ NSHTTPURLResponse *urlResponse = [MXHTTPOperation urlResponseFromError:error];
+
+ NSLog(@"[WidgetManager] validateScalarToken. Error in modular/account request. statusCode: %@", @(urlResponse.statusCode));
+
+ if (urlResponse && urlResponse.statusCode / 100 != 2)
+ {
+ complete(NO);
+ }
+ else if (failure)
+ {
+ failure(error);
+ }
+ }];
+}
+
++ (BOOL)isScalarUrl:(NSString *)urlString
+{
+ BOOL isScalarUrl = NO;
+
+ NSArray *scalarUrlStrings = [[NSUserDefaults standardUserDefaults] objectForKey:@"integrationsWidgetsUrls"];
+ if (scalarUrlStrings.count == 0)
+ {
+ scalarUrlStrings = @[[[NSUserDefaults standardUserDefaults] objectForKey:@"integrationsRestUrl"]];
+ }
+
+ for (NSString *scalarUrlString in scalarUrlStrings)
+ {
+ if ([urlString hasPrefix:scalarUrlString])
+ {
+ isScalarUrl = YES;
+ break;
+ }
+ }
+
+ return isScalarUrl;
+}
+
#pragma mark - Private methods
- (NSString *)scalarTokenForMXSession:(MXSession *)mxSession
diff --git a/Riot/Modules/Integrations/IntegrationManagerViewController.m b/Riot/Modules/Integrations/IntegrationManagerViewController.m
index b8dd28448..37bab65fa 100644
--- a/Riot/Modules/Integrations/IntegrationManagerViewController.m
+++ b/Riot/Modules/Integrations/IntegrationManagerViewController.m
@@ -73,7 +73,7 @@ NSString *const kIntegrationManagerAddIntegrationScreen = @"add_integ";
// Make sure we have a scalar token
MXWeakify(self);
- operation = [[WidgetManager sharedManager] getScalarTokenForMXSession:mxSession success:^(NSString *theScalarToken) {
+ operation = [[WidgetManager sharedManager] getScalarTokenForMXSession:mxSession validate:YES success:^(NSString *theScalarToken) {
MXStrongifyAndReturnIfNil(self);
self->operation = nil;
diff --git a/Riot/Modules/Integrations/Widgets/WidgetViewController.m b/Riot/Modules/Integrations/Widgets/WidgetViewController.m
index 64e9be433..f67456292 100644
--- a/Riot/Modules/Integrations/Widgets/WidgetViewController.m
+++ b/Riot/Modules/Integrations/Widgets/WidgetViewController.m
@@ -172,12 +172,7 @@ NSString *const kJavascriptSendResponseToPostMessageAPI = @"riotIOS.sendResponse
{
// Filter out the users's scalar token
NSString *errorDescription = error.description;
- NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"scalar_token=\\w*"
- options:NSRegularExpressionCaseInsensitive error:nil];
- errorDescription = [regex stringByReplacingMatchesInString:errorDescription
- options:0
- range:NSMakeRange(0, errorDescription.length)
- withTemplate:@"scalar_token=..."];
+ errorDescription = [self stringByReplacingScalarTokenInString:errorDescription byScalarToken:@"..."];
NSLog(@"[WidgetVC] didFailLoadWithError: %@", errorDescription);
@@ -185,6 +180,24 @@ NSString *const kJavascriptSendResponseToPostMessageAPI = @"riotIOS.sendResponse
[self showErrorAsAlert:error];
}
+- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler {
+
+ if ([navigationResponse.response isKindOfClass:[NSHTTPURLResponse class]])
+ {
+ NSHTTPURLResponse * response = (NSHTTPURLResponse *)navigationResponse.response;
+ if (response.statusCode != 200)
+ {
+ NSLog(@"[WidgetVC] decidePolicyForNavigationResponse: statusCode: %@", @(response.statusCode));
+ }
+
+ if (response.statusCode == 403 && [WidgetManager isScalarUrl:self.URL])
+ {
+ [self fixScalarToken];
+ }
+ }
+ decisionHandler(WKNavigationResponsePolicyAllow);
+}
+
#pragma mark - postMessage API
- (void)onPostMessageRequest:(NSString*)requestId data:(NSDictionary*)requestData
@@ -315,4 +328,50 @@ NSString *const kJavascriptSendResponseToPostMessageAPI = @"riotIOS.sendResponse
[self sendError:NSLocalizedStringFromTable(errorKey, @"Vector", nil) toRequest:requestId];
}
+
+#pragma mark - Private methods
+
+- (NSString *)stringByReplacingScalarTokenInString:(NSString*)string byScalarToken:(NSString*)scalarToken
+{
+ if (!string)
+ {
+ return nil;
+ }
+
+ NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"scalar_token=\\w*"
+ options:NSRegularExpressionCaseInsensitive error:nil];
+ return [regex stringByReplacingMatchesInString:string
+ options:0
+ range:NSMakeRange(0, string.length)
+ withTemplate:[NSString stringWithFormat:@"scalar_token=%@", scalarToken]];
+}
+
+/**
+ Reset the scalar token used in the webview URL.
+ */
+- (void)fixScalarToken
+{
+ NSLog(@"[WidgetVC] fixScalarToken");
+
+ self->webView.hidden = YES;
+
+ // Get a fresh new scalar token
+ [WidgetManager.sharedManager deleteDataForUser:widget.mxSession.myUser.userId];
+
+ MXWeakify(self);
+ [WidgetManager.sharedManager getScalarTokenForMXSession:widget.mxSession validate:NO success:^(NSString *scalarToken) {
+ MXStrongifyAndReturnIfNil(self);
+
+ NSLog(@"[WidgetVC] fixScalarToken: DONE");
+
+ self.URL = [self stringByReplacingScalarTokenInString:self.URL byScalarToken:scalarToken];
+
+ self->webView.hidden = NO;
+
+ } failure:^(NSError *error) {
+ NSLog(@"[WidgetVC] fixScalarToken: Error: %@", error);
+ [self showErrorAsAlert:error];
+ }];
+}
+
@end