Skip to content

Commit

Permalink
Add ability for authUrl to trigger failed using 403
Browse files Browse the repository at this point in the history
per RSA4c/d
  • Loading branch information
SimonWoolf committed Nov 21, 2017
1 parent d31be78 commit 2832e88
Show file tree
Hide file tree
Showing 3 changed files with 55 additions and 35 deletions.
34 changes: 18 additions & 16 deletions common/lib/client/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,17 @@ var Auth = (function() {
var MAX_TOKENSTRING_LENGTH = 384;
function noop() {}
function random() { return ('000000' + Math.floor(Math.random() * 1E16)).slice(-16); }
function normaliseAuthcallbackError(err) {
/* A client auth callback may give errors in any number of formats; normalise to an errorinfo */
if(!Utils.isErrorInfo(err)) {
return new ErrorInfo(Utils.inspectError(err), err.code || 40170, err.statusCode || 401);
}
/* network errors will not have an inherent error code */
if(!err.code) {
err.code = (err.statusCode === 403) ? 40300 : 40170;
}
return err;
}

var hmac, toBase64;
if(Platform.createHmac) {
Expand Down Expand Up @@ -359,7 +370,7 @@ var Auth = (function() {
} else {
var msg = "Need a new token, but authOptions does not include any way to request one";
Logger.logAction(Logger.LOG_ERROR, 'Auth.requestToken()', msg);
callback(new ErrorInfo(msg, 40101, 401));
callback(new ErrorInfo(msg, 40101, 403));
return;
}

Expand Down Expand Up @@ -394,12 +405,7 @@ var Auth = (function() {

if(err) {
Logger.logAction(Logger.LOG_ERROR, 'Auth.requestToken()', 'token request signing call returned error; err = ' + Utils.inspectError(err));
if(!err.code) {
/* network errors don't have an error code, so assign them
* 40170 so they'll by connectionManager as nonfatal */
err = new ErrorInfo(Utils.inspectError(err), 40170, 401);
}
callback(err);
callback(normaliseAuthcallbackError(err));
return;
}
/* the response from the callback might be a token string, a signed request or a token details */
Expand Down Expand Up @@ -437,12 +443,7 @@ var Auth = (function() {
tokenRequest(tokenRequestOrDetails, function(err, tokenResponse, headers, unpacked) {
if(err) {
Logger.logAction(Logger.LOG_ERROR, 'Auth.requestToken()', 'token request API call returned error; err = ' + Utils.inspectError(err));
if(!err.code) {
/* network errors don't have an error code, so assign them
* 40170 so they'll be seen by connectionManager as nonfatal */
err = new ErrorInfo(Utils.inspectError(err), 40170, 401);
}
callback(err);
callback(normaliseAuthcallbackError(err));
return;
}
if(!unpacked) tokenResponse = JSON.parse(tokenResponse);
Expand Down Expand Up @@ -502,15 +503,15 @@ var Auth = (function() {

var key = authOptions.key;
if(!key) {
callback(new Error('No key specified'));
callback(new ErrorInfo('No key specified', 40101, 403));
return;
}
var keyParts = key.split(':'),
keyName = keyParts[0],
keySecret = keyParts[1];

if(!keySecret) {
callback(new Error('Invalid key specified'));
callback(new ErrorInfo('Invalid key specified', 40101, 403));
return;
}

Expand Down Expand Up @@ -679,7 +680,8 @@ var Auth = (function() {

if(token) {
if(this._tokenClientIdMismatch(token.clientId)) {
callback(new ErrorInfo('Mismatch between clientId in token (' + token.clientId + ') and current clientId (' + this.clientId + ')', 40102, 401));
/* 403 to trigger a permanently failed client - RSA15c */
callback(new ErrorInfo('Mismatch between clientId in token (' + token.clientId + ') and current clientId (' + this.clientId + ')', 40102, 403));
return;
}
this.getTimestamp(self.authOptions && self.authOptions.queryTime, function(err, time) {
Expand Down
16 changes: 10 additions & 6 deletions common/lib/transport/connectionmanager.js
Original file line number Diff line number Diff line change
Expand Up @@ -1501,14 +1501,18 @@ var ConnectionManager = (function() {
}
};

/* This method is only used during connection attempts, so implements RSA4c1,
* RSA4c2, and RSA4d. In particular it is not invoked for
* serverside-triggered reauths or manual reauths, so RSA4c3 does not apply */
ConnectionManager.prototype.actOnErrorFromAuthorize = function(err) {
if(err.code === 40170) {
/* Special-case problems with the client auth callback - unlike other
* auth errors these may be nonfatal. (RSA4c) */
err.code = 80019;
this.notifyState({state: this.state.failState, error: err});
if(err.statusCode === 403) {
var msg = 'Client configured authentication provider returned 403; failing the connection';
Logger.logAction(Logger.LOG_ERROR, 'ConnectionManager.actOnErrorFromAuthorize()', msg);
this.notifyState({state: 'failed', error: new ErrorInfo(msg, 80019, 403, err)});
} else {
this.notifyState({state: 'failed', error: err});
var msg = 'Client configured authentication provider request failed';
Logger.logAction(Logger.LOG_MINOR, 'ConnectionManager.actOnErrorFromAuthorize', msg);
this.notifyState({state: this.state.failState, error: new ErrorInfo(msg, 80019, 401, err)});
}
};

Expand Down
40 changes: 27 additions & 13 deletions spec/realtime/auth.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ define(['ably', 'shared_helper', 'async'], function(Ably, helper, async) {
closeAndFinish = helper.closeAndFinish,
monitorConnection = helper.monitorConnection,
testOnAllTransports = helper.testOnAllTransports,
mixin = helper.Utils.mixin;
mixin = helper.Utils.mixin,
echoServer = "http://echo.ably.io";
//echoServer = "http://localhost:5000";

exports.setupauth = function(test) {
test.expect(1);
Expand Down Expand Up @@ -68,7 +70,7 @@ define(['ably', 'shared_helper', 'async'], function(Ably, helper, async) {
return;
}

var authPath = "http://echo.ably.io/?type=json&body=" + encodeURIComponent(JSON.stringify(tokenDetails));
var authPath = echoServer + "/?type=json&body=" + encodeURIComponent(JSON.stringify(tokenDetails));

realtime = helper.AblyRealtime({ authUrl: authPath });

Expand Down Expand Up @@ -96,7 +98,7 @@ define(['ably', 'shared_helper', 'async'], function(Ably, helper, async) {
return;
}

var authUrl = "http://echo.ably.io/?type=json&";
var authUrl = echoServer + "/?type=json&";

realtime = helper.AblyRealtime({ authUrl: authUrl, authMethod: "POST", authParams: tokenDetails});

Expand Down Expand Up @@ -124,7 +126,7 @@ define(['ably', 'shared_helper', 'async'], function(Ably, helper, async) {
return;
}

var authPath = "http://echo.ably.io/?type=text&body=" + tokenDetails['token'];
var authPath = echoServer + "/?type=text&body=" + tokenDetails['token'];

realtime = helper.AblyRealtime({ authUrl: authPath });

Expand Down Expand Up @@ -264,7 +266,7 @@ define(['ably', 'shared_helper', 'async'], function(Ably, helper, async) {
keyName: tokenRequest.keyName,
mac: tokenRequest.mac
};
var authPath = "http://echo.ably.io/qs_to_body" + utils.toQueryString(lowerPrecedenceTokenRequestParts);
var authPath = echoServer + "/qs_to_body" + utils.toQueryString(lowerPrecedenceTokenRequestParts);

realtime = helper.AblyRealtime({ authUrl: authPath, authParams: higherPrecedenceTokenRequestParts });

Expand Down Expand Up @@ -319,7 +321,7 @@ define(['ably', 'shared_helper', 'async'], function(Ably, helper, async) {
* (RSA15a, RSA15c)
*/
exports.auth_clientid_inheritance2 = function(test) {
test.expect(2);
test.expect(3);
var clientRealtime,
testClientId = 'test client id';
var rest = helper.AblyRest();
Expand All @@ -332,7 +334,8 @@ define(['ably', 'shared_helper', 'async'], function(Ably, helper, async) {
clientRealtime = helper.AblyRealtime({token: tokenDetails, clientId: 'WRONG'});
clientRealtime.connection.once('failed', function(stateChange){
test.ok(true, 'Verify connection failed');
test.equal(stateChange.reason.code, 40102);
test.equal(stateChange.reason.code, 80019);
test.equal(stateChange.reason.cause.code, 40102);
clientRealtime.close();
test.done();
});
Expand Down Expand Up @@ -419,15 +422,16 @@ define(['ably', 'shared_helper', 'async'], function(Ably, helper, async) {
/* RSA4c, RSA4e
* Try to connect with an authCallback that fails in various ways (calling back with an error, calling back with nothing, timing out, etc) should go to disconnected, not failed, and wrapped in a 80019 error code
*/
function authCallback_failures(realtimeOptions) {
function authCallback_failures(realtimeOptions, expectFailure) {
return function(test) {
test.expect(2);
test.expect(3);

var realtime = helper.AblyRealtime(realtimeOptions);
realtime.connection.on(function(stateChange) {
if(stateChange.previous !== 'initialized') {
test.equal(stateChange.current, 'disconnected', 'Check connection goes to disconnected, not failed');
test.equal(stateChange.current, expectFailure ? 'failed' : 'disconnected', 'Check connection goes to the expected state');
test.equal(stateChange.reason.code, 80019, 'Check correct error code');
test.equal(stateChange.reason.statusCode, expectFailure ? 403 : 401, 'Check correct cause error code');
realtime.connection.off();
closeAndFinish(test, realtime);
}
Expand Down Expand Up @@ -472,6 +476,15 @@ define(['ably', 'shared_helper', 'async'], function(Ably, helper, async) {
authUrl: 'http://example.com/'
});

exports.authUrl_401 = authCallback_failures({
authUrl: echoServer + '/respondwith?status=401'
});

/* 403 should cause the connection to go to failed, unlike the others */
exports.authUrl_403 = authCallback_failures({
authUrl: echoServer + '/respondwith?status=403'
}, /* expectFailed: */ true);

/*
* Check state change reason is propogated during a disconnect
* (when connecting with a token that expires while connected)
Expand Down Expand Up @@ -633,7 +646,7 @@ define(['ably', 'shared_helper', 'async'], function(Ably, helper, async) {
* Same as previous but with no way to generate a new token
*/
testOnAllTransports(exports, 'auth_token_string_expiry_with_token', function(realtimeOpts) { return function(test) {
test.expect(5);
test.expect(6);

var realtime, rest = helper.AblyRest();
var clientId = "test clientid";
Expand All @@ -652,7 +665,8 @@ define(['ably', 'shared_helper', 'async'], function(Ably, helper, async) {
realtime.connection.once('failed', function(stateChange){
/* Library has no way to generate a new token, so should fail */
test.ok(true, 'Verify connection failed');
test.equal(stateChange.reason.code, 40101, 'Verify correct failure code');
test.equal(stateChange.reason.code, 80019, 'Verify correct failure code');
test.equal(stateChange.reason.cause.code, 40101, 'Verify correct cause failure code');
realtime.close();
test.done();
});
Expand All @@ -679,7 +693,7 @@ define(['ably', 'shared_helper', 'async'], function(Ably, helper, async) {
realtime = helper.AblyRealtime(mixin(realtimeOpts, { token: tokenDetails.token, clientId: clientId }));
realtime.connection.once('failed', function(stateChange){
test.ok(true, 'Verify connection failed');
test.equal(stateChange.reason.code, 40101, 'Verify correct failure code');
test.equal(stateChange.reason.cause.code, 40101, 'Verify correct failure code');
realtime.close();
test.done();
});
Expand Down

0 comments on commit 2832e88

Please sign in to comment.