Commit 0157b3db authored by jrw's avatar jrw Committed by Commit bot

Added ability to register new hosts using GCD.

Hosts registered through GCD can't be brought online yet, because the
host doesn't know how to use the XMPP credentials it gets from GCD.

BUG=471928

Review URL: https://codereview.chromium.org/1111603002

Cr-Commit-Position: refs/heads/master@{#327427}
parent 9f3e5497
......@@ -165,15 +165,15 @@ remoting.HostController.prototype.start = function(hostPin, consent) {
});
var newHostId = base.generateUuid();
var pinHashPromise = this.hostDaemonFacade_.getPinHash(newHostId, hostPin);
var hostOwnerPromise = this.getClientBaseJid_();
/** @type {boolean} */
var hostRegistered = false;
// Register the host and extract an optional auth code from the host
// response. The absence of an auth code is represented by an empty
// string.
/** @type {!Promise<string>} */
var authCodePromise = Promise.all([
// Register the host and extract an auth code from the host response
// and, optionally an email address for the robot account.
/** @type {!Promise<remoting.HostListApi.RegisterResult>} */
var regResultPromise = Promise.all([
hostClientIdPromise,
hostNamePromise,
keyPairPromise
......@@ -184,79 +184,57 @@ remoting.HostController.prototype.start = function(hostPin, consent) {
return remoting.HostListApi.getInstance().register(
newHostId, hostName, keyPair.publicKey, hostClientId);
}).then(function(/** string */ result) {
}).then(function(/** remoting.HostListApi.RegisterResult */ result) {
hostRegistered = true;
return result;
});
// Get XMPP creditials.
var xmppCredsPromise = authCodePromise.then(function(authCode) {
if (authCode) {
// Use auth code supplied by Chromoting registry.
return that.hostDaemonFacade_.getCredentialsFromAuthCode(authCode);
} else {
// No authorization code returned, use regular Chrome
// identity credential flow.
return remoting.identity.getEmail().then(function(/** string */ email) {
return {
userEmail: email,
refreshToken: remoting.oauth2.getRefreshToken()
};
});
}
});
// Get as JID to use as the host owner.
var hostOwnerPromise = authCodePromise.then(function(authCode) {
if (authCode) {
return that.getClientBaseJid_();
var xmppCredsPromise = regResultPromise.then(function(regResult) {
base.debug.assert(regResult.authCode != '');
if (regResult.email) {
// Use auth code and email supplied by GCD.
return that.hostDaemonFacade_.getRefreshTokenFromAuthCode(
regResult.authCode).then(function(token) {
return {
userEmail: regResult.email,
refreshToken: token
};
});
} else {
return remoting.identity.getEmail();
// Use auth code supplied by Chromoting registry.
return that.hostDaemonFacade_.getCredentialsFromAuthCode(
regResult.authCode);
}
});
// Build an initial host configuration.
// Build the host configuration.
/** @type {!Promise<!Object>} */
var hostConfigNoOwnerPromise = Promise.all([
var hostConfigPromise = Promise.all([
hostNamePromise,
pinHashPromise,
xmppCredsPromise,
keyPairPromise
keyPairPromise,
hostOwnerPromise,
remoting.identity.getEmail()
]).then(function(/** Array */ a) {
var hostName = /** @type {string} */ (a[0]);
var hostSecretHash = /** @type {string} */ (a[1]);
var xmppCreds = /** @type {remoting.XmppCredentials} */ (a[2]);
var keyPair = /** @type {remoting.KeyPair} */ (a[3]);
return {
var hostOwner = /** @type {string} */ (a[4]);
var hostOwnerEmail = /** @type {string} */ (a[5]);
var hostConfig = {
xmpp_login: xmppCreds.userEmail,
oauth_refresh_token: xmppCreds.refreshToken,
host_id: newHostId,
host_name: hostName,
host_secret_hash: hostSecretHash,
private_key: keyPair.privateKey
private_key: keyPair.privateKey,
host_owner: hostOwner
};
});
// Add host_owner and host_owner_email fields to the host config if
// necessary. This promise resolves to the same value as
// hostConfigNoOwnerPromise, with not until the extra fields are
// either added or determined to be redundant.
/** @type {!Promise<!Object>} */
var hostConfigWithOwnerPromise = Promise.all([
hostConfigNoOwnerPromise,
hostOwnerPromise,
remoting.identity.getEmail(),
xmppCredsPromise
]).then(function(/** Array */ a) {
var hostConfig = /** @type {!Object} */ (a[0]);
var hostOwner = /** @type {string} */ (a[1]);
var hostOwnerEmail = /** @type {string} */ (a[2]);
var xmppCreds = /** @type {remoting.XmppCredentials} */ (a[3]);
if (hostOwner != xmppCreds.userEmail) {
hostConfig['host_owner'] = hostOwner;
if (hostOwnerEmail != hostOwner) {
hostConfig['host_owner_email'] = hostOwnerEmail;
}
if (hostOwnerEmail != hostOwner) {
hostConfig['host_owner_email'] = hostOwnerEmail;
}
return hostConfig;
});
......@@ -264,7 +242,7 @@ remoting.HostController.prototype.start = function(hostPin, consent) {
// Start the daemon.
/** @type {!Promise<remoting.HostController.AsyncResult>} */
var startDaemonResultPromise =
hostConfigWithOwnerPromise.then(function(hostConfig) {
hostConfigPromise.then(function(hostConfig) {
return that.hostDaemonFacade_.startDaemon(hostConfig, consent);
});
......
......@@ -81,7 +81,8 @@ QUnit.module('host_controller', {
remoting.settings = new remoting.Settings();
remoting.identity = new remoting.Identity();
mockHostListApi = new remoting.MockHostListApi;
mockHostListApi.registerResult = FAKE_AUTH_CODE;
mockHostListApi.authCodeFromRegister = FAKE_AUTH_CODE;
mockHostListApi.emailFromRegister = '';
remoting.HostListApi.setInstance(mockHostListApi);
base.debug.assert(remoting.oauth2 === null);
remoting.oauth2 = new remoting.OAuth2();
......@@ -250,7 +251,7 @@ QUnit.test('start with getHostClientId failure', function(assert) {
// Check what happens when the registry returns an HTTP when we try to
// register a host.
QUnit.test('start with host registration failure', function(assert) {
mockHostListApi.registerResult = null;
mockHostListApi.authCodeFromRegister = null;
return controller.start(FAKE_HOST_PIN, true).then(function() {
throw 'test failed';
}, function(/** remoting.Error */ e) {
......@@ -277,22 +278,6 @@ QUnit.test('start with getCredentialsFromAuthCode failure', function(assert) {
});
});
// Check what happens when the HostDaemonFacade's getPinHash method
// fails, and verify that getPinHash is called when the registry
// does't return an auth code.
QUnit.test('start with getRefreshToken+getPinHash failure', function(assert) {
mockHostDaemonFacade.pinHashFunc = null;
mockHostListApi.registerResult = '';
return controller.start(FAKE_HOST_PIN, true).then(function() {
throw 'test failed';
}, function(/** remoting.Error */ e) {
assert.equal(e.getDetail(), 'getPinHash');
assert.equal(e.getTag(), remoting.Error.Tag.UNEXPECTED);
assert.equal(onLocalHostStartedSpy.callCount, 0);
assert.equal(startDaemonSpy.callCount, 0);
});
});
// Check what happens when the SignalStrategy fails to connect.
QUnit.test('start with signalStrategy failure', function(assert) {
stubSignalStrategyConnect(false);
......@@ -356,7 +341,7 @@ QUnit.test('start with startDaemon returning failure code', function(assert) {
// Check what happens when the entire host registration process
// succeeds.
[false, true].forEach(function(/** boolean */ consent) {
QUnit.test('start with auth code, consent=' + consent, function(assert) {
QUnit.test('start with consent=' + consent, function(assert) {
/** @const */
var fakePinHash = fakePinHashFunc(FAKE_HOST_ID, FAKE_HOST_PIN);
stubSignalStrategyConnect(true);
......@@ -388,37 +373,6 @@ QUnit.test('start with startDaemon returning failure code', function(assert) {
});
});
// Check alternative host registration without a registry-supplied
// auth code.
[false, true].forEach(function(/** boolean */ consent) {
QUnit.test('start without auth code, consent=' + consent, function(assert) {
/** @const */
var fakePinHash = fakePinHashFunc(FAKE_HOST_ID, FAKE_HOST_PIN);
mockHostListApi.registerResult = '';
stubSignalStrategyConnect(true);
return controller.start(FAKE_HOST_PIN, consent).then(function() {
assert.equal(getCredentialsFromAuthCodeSpy.callCount, 0);
assert.equal(getPinHashSpy.callCount, 1);
assert.deepEqual(
getPinHashSpy.args[0].slice(0, 2),
[FAKE_HOST_ID, FAKE_HOST_PIN]);
assert.equal(unregisterHostByIdSpy.callCount, 0);
assert.equal(onLocalHostStartedSpy.callCount, 1);
assert.equal(startDaemonSpy.callCount, 1);
assert.deepEqual(
startDaemonSpy.args[0].slice(0, 2),
[{
xmpp_login: FAKE_USER_EMAIL,
oauth_refresh_token: FAKE_REFRESH_TOKEN,
host_id: FAKE_HOST_ID,
host_name: FAKE_HOST_NAME,
host_secret_hash: fakePinHash,
private_key: FAKE_PRIVATE_KEY
}, consent]);
});
});
});
// Check what happens when stopDaemon calls onError.
// TODO(jrw): Should stopDaemon even have an onError callback?
QUnit.test('stop with stopDaemon failure', function(assert) {
......
......@@ -26,7 +26,7 @@ remoting.HostListApi = function() {
* @param {string} hostName The user-visible name of the new host.
* @param {string} publicKey The public half of the host's key pair.
* @param {string} hostClientId The OAuth2 client ID of the host.
* @return {!Promise<string>} An OAuth2 auth code or the empty string.
* @return {!Promise<remoting.HostListApi.RegisterResult>}
*/
remoting.HostListApi.prototype.register = function(
newHostId, hostName, publicKey, hostClientId) {
......@@ -87,3 +87,15 @@ remoting.HostListApi.setInstance = function(newInstance) {
};
})();
/**
* A pair of an OAuth2 auth code and a robot account email. Depending
* on the specifics of the registration process, either could be the
* empty string.
*
* @typedef {{
* authCode: string,
* email: string
* }}
*/
remoting.HostListApi.RegisterResult;
\ No newline at end of file
......@@ -40,7 +40,7 @@ remoting.HostListApiGcdImpl.prototype.register = function(
}
};
return /** @type {!Promise<string>} */ (
return /** @type {!Promise<remoting.HostListApi.RegisterResult>} */ (
this.gcd_.insertRegistrationTicket().
then(function(ticket) {
return self.gcd_.patchRegistrationTicket(
......@@ -50,7 +50,10 @@ remoting.HostListApiGcdImpl.prototype.register = function(
return self.gcd_.finalizeRegistrationTicket(ticket.id);
}).
then(function(/**remoting.gcd.RegistrationTicket*/ ticket) {
return ticket.robotAccountAuthorizationCode;
return {
authCode: ticket.robotAccountAuthorizationCode,
email: ticket.robotAccountEmail
};
}).
catch(function(error) {
console.error('Error registering device with GCD: ' + error);
......
......@@ -41,12 +41,10 @@ remoting.HostListApiImpl.prototype.register = function(
useIdentity: true
}).start().then(function(response) {
if (response.status == 200) {
var result = response.getJson();
if (result['data']) {
return base.getStringAttr(result['data'], 'authorizationCode', '');
} else {
return '';
}
var result = /** @type {!Object} */ (response.getJson());
var data = base.getObjectAttr(result, 'data');
var authCode = base.getStringAttr(data, 'authorizationCode');
return { authCode: authCode, email: '' };
} else {
console.log(
'Failed to register the host. Status: ' + response.status +
......
......@@ -31,17 +31,13 @@ QUnit.module('host_list_api_impl', {
/**
* Install an HTTP response for requests to the registry.
* @param {QUnit.Assert} assert
* @param {boolean} withAuthCode
*/
function queueRegistryResponse(assert, withAuthCode) {
function queueRegistryResponse(assert) {
var responseJson = {
data: {
authorizationCode: FAKE_AUTH_CODE
}
};
if (!withAuthCode) {
delete responseJson.data.authorizationCode;
}
remoting.MockXhr.setResponseFor(
'POST', 'DIRECTORY_API_BASE_URL/@me/hosts',
......@@ -57,29 +53,17 @@ function queueRegistryResponse(assert, withAuthCode) {
});
}
QUnit.test('register with auth code', function(assert) {
var impl = new remoting.HostListApiImpl();
queueRegistryResponse(assert, true);
return impl.register(
FAKE_HOST_ID,
FAKE_HOST_NAME,
FAKE_PUBLIC_KEY,
FAKE_HOST_CLIENT_ID
). then(function(authCode) {
assert.equal(authCode, FAKE_AUTH_CODE);
});
});
QUnit.test('register without auth code', function(assert) {
QUnit.test('register', function(assert) {
var impl = new remoting.HostListApiImpl();
queueRegistryResponse(assert, false);
queueRegistryResponse(assert);
return impl.register(
FAKE_HOST_ID,
FAKE_HOST_NAME,
FAKE_PUBLIC_KEY,
FAKE_HOST_CLIENT_ID
). then(function(authCode) {
assert.equal(authCode, '');
). then(function(regResult) {
assert.equal(regResult.authCode, FAKE_AUTH_CODE);
assert.equal(regResult.email, '');
});
});
......
......@@ -18,11 +18,18 @@ var remoting = remoting || {};
*/
remoting.MockHostListApi = function() {
/**
* The auth code for the |register| method to return, or null if it
* should fail.
* The auth code value for the |register| method to return, or null
* if it should fail.
* @type {?string}
*/
this.registerResult = null;
this.authCodeFromRegister = null;
/**
* The email value for the |register| method to return, or null if
* it should fail.
* @type {?string}
*/
this.emailFromRegister = null;
/** @type {Array<remoting.Host>} */
this.hosts = [
......@@ -50,13 +57,16 @@ remoting.MockHostListApi = function() {
/** @override */
remoting.MockHostListApi.prototype.register = function(
newHostId, hostName, publicKey, hostClientId) {
if (this.registerResult === null) {
if (this.authCodeFromRegister === null || this.emailFromRegister === null) {
return Promise.reject(
new remoting.Error(
remoting.Error.Tag.REGISTRATION_FAILED,
'MockHostListApi.register'));
} else {
return Promise.resolve(this.registerResult);
return Promise.resolve({
authCode: this.authCodeFromRegister,
email: this.emailFromRegister
});
}
};
......@@ -82,6 +92,7 @@ remoting.MockHostListApi.prototype.put =
return new Promise(function(resolve, reject) {
var onTokenValid = function() {
for (var i = 0; i < that.hosts.length; ++i) {
/** type {remoting.Host} */
var host = that.hosts[i];
if (host.hostId == hostId) {
host.hostName = hostName;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment