Merge pull request #2328 from vector-im/scalar_token_refresh

Widgets: Attempt to re-register for a scalar token if ours is invalid
This commit is contained in:
manuroe
2019-03-12 18:13:05 +01:00
committed by GitHub
8 changed files with 291 additions and 116 deletions
+3
View File
@@ -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)
+5
View File
@@ -28,6 +28,11 @@
<string>https://scalar-staging.riot.im/scalar-web/</string>
<key>integrationsRestUrl</key>
<string>https://scalar-staging.riot.im/scalar/api</string>
<key>integrationsWidgetsUrls</key>
<array>
<string>https://scalar-staging.riot.im/scalar/api</string>
<string>https://scalar.vector.im/api</string>
</array>
<key>piwik</key>
<dict>
<key>url</key>
+1
View File
@@ -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"]
+63 -58
View File
@@ -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
+10
View File
@@ -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
+143 -51
View File
@@ -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<NSString*> *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
@@ -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;
@@ -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