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) { ...@@ -165,15 +165,15 @@ remoting.HostController.prototype.start = function(hostPin, consent) {
}); });
var newHostId = base.generateUuid(); var newHostId = base.generateUuid();
var pinHashPromise = this.hostDaemonFacade_.getPinHash(newHostId, hostPin); var pinHashPromise = this.hostDaemonFacade_.getPinHash(newHostId, hostPin);
var hostOwnerPromise = this.getClientBaseJid_();
/** @type {boolean} */ /** @type {boolean} */
var hostRegistered = false; var hostRegistered = false;
// Register the host and extract an optional auth code from the host // Register the host and extract an auth code from the host response
// response. The absence of an auth code is represented by an empty // and, optionally an email address for the robot account.
// string. /** @type {!Promise<remoting.HostListApi.RegisterResult>} */
/** @type {!Promise<string>} */ var regResultPromise = Promise.all([
var authCodePromise = Promise.all([
hostClientIdPromise, hostClientIdPromise,
hostNamePromise, hostNamePromise,
keyPairPromise keyPairPromise
...@@ -184,79 +184,57 @@ remoting.HostController.prototype.start = function(hostPin, consent) { ...@@ -184,79 +184,57 @@ remoting.HostController.prototype.start = function(hostPin, consent) {
return remoting.HostListApi.getInstance().register( return remoting.HostListApi.getInstance().register(
newHostId, hostName, keyPair.publicKey, hostClientId); newHostId, hostName, keyPair.publicKey, hostClientId);
}).then(function(/** string */ result) { }).then(function(/** remoting.HostListApi.RegisterResult */ result) {
hostRegistered = true; hostRegistered = true;
return result; return result;
}); });
// Get XMPP creditials. // Get XMPP creditials.
var xmppCredsPromise = authCodePromise.then(function(authCode) { var xmppCredsPromise = regResultPromise.then(function(regResult) {
if (authCode) { base.debug.assert(regResult.authCode != '');
// Use auth code supplied by Chromoting registry. if (regResult.email) {
return that.hostDaemonFacade_.getCredentialsFromAuthCode(authCode); // Use auth code and email supplied by GCD.
} else { return that.hostDaemonFacade_.getRefreshTokenFromAuthCode(
// No authorization code returned, use regular Chrome regResult.authCode).then(function(token) {
// identity credential flow. return {
return remoting.identity.getEmail().then(function(/** string */ email) { userEmail: regResult.email,
return { refreshToken: token
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_();
} else { } 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>} */ /** @type {!Promise<!Object>} */
var hostConfigNoOwnerPromise = Promise.all([ var hostConfigPromise = Promise.all([
hostNamePromise, hostNamePromise,
pinHashPromise, pinHashPromise,
xmppCredsPromise, xmppCredsPromise,
keyPairPromise keyPairPromise,
hostOwnerPromise,
remoting.identity.getEmail()
]).then(function(/** Array */ a) { ]).then(function(/** Array */ a) {
var hostName = /** @type {string} */ (a[0]); var hostName = /** @type {string} */ (a[0]);
var hostSecretHash = /** @type {string} */ (a[1]); var hostSecretHash = /** @type {string} */ (a[1]);
var xmppCreds = /** @type {remoting.XmppCredentials} */ (a[2]); var xmppCreds = /** @type {remoting.XmppCredentials} */ (a[2]);
var keyPair = /** @type {remoting.KeyPair} */ (a[3]); 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, xmpp_login: xmppCreds.userEmail,
oauth_refresh_token: xmppCreds.refreshToken, oauth_refresh_token: xmppCreds.refreshToken,
host_id: newHostId, host_id: newHostId,
host_name: hostName, host_name: hostName,
host_secret_hash: hostSecretHash, host_secret_hash: hostSecretHash,
private_key: keyPair.privateKey private_key: keyPair.privateKey,
host_owner: hostOwner
}; };
}); if (hostOwnerEmail != hostOwner) {
hostConfig['host_owner_email'] = hostOwnerEmail;
// 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;
}
} }
return hostConfig; return hostConfig;
}); });
...@@ -264,7 +242,7 @@ remoting.HostController.prototype.start = function(hostPin, consent) { ...@@ -264,7 +242,7 @@ remoting.HostController.prototype.start = function(hostPin, consent) {
// Start the daemon. // Start the daemon.
/** @type {!Promise<remoting.HostController.AsyncResult>} */ /** @type {!Promise<remoting.HostController.AsyncResult>} */
var startDaemonResultPromise = var startDaemonResultPromise =
hostConfigWithOwnerPromise.then(function(hostConfig) { hostConfigPromise.then(function(hostConfig) {
return that.hostDaemonFacade_.startDaemon(hostConfig, consent); return that.hostDaemonFacade_.startDaemon(hostConfig, consent);
}); });
......
...@@ -81,7 +81,8 @@ QUnit.module('host_controller', { ...@@ -81,7 +81,8 @@ QUnit.module('host_controller', {
remoting.settings = new remoting.Settings(); remoting.settings = new remoting.Settings();
remoting.identity = new remoting.Identity(); remoting.identity = new remoting.Identity();
mockHostListApi = new remoting.MockHostListApi; mockHostListApi = new remoting.MockHostListApi;
mockHostListApi.registerResult = FAKE_AUTH_CODE; mockHostListApi.authCodeFromRegister = FAKE_AUTH_CODE;
mockHostListApi.emailFromRegister = '';
remoting.HostListApi.setInstance(mockHostListApi); remoting.HostListApi.setInstance(mockHostListApi);
base.debug.assert(remoting.oauth2 === null); base.debug.assert(remoting.oauth2 === null);
remoting.oauth2 = new remoting.OAuth2(); remoting.oauth2 = new remoting.OAuth2();
...@@ -250,7 +251,7 @@ QUnit.test('start with getHostClientId failure', function(assert) { ...@@ -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 // Check what happens when the registry returns an HTTP when we try to
// register a host. // register a host.
QUnit.test('start with host registration failure', function(assert) { QUnit.test('start with host registration failure', function(assert) {
mockHostListApi.registerResult = null; mockHostListApi.authCodeFromRegister = null;
return controller.start(FAKE_HOST_PIN, true).then(function() { return controller.start(FAKE_HOST_PIN, true).then(function() {
throw 'test failed'; throw 'test failed';
}, function(/** remoting.Error */ e) { }, function(/** remoting.Error */ e) {
...@@ -277,22 +278,6 @@ QUnit.test('start with getCredentialsFromAuthCode failure', function(assert) { ...@@ -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. // Check what happens when the SignalStrategy fails to connect.
QUnit.test('start with signalStrategy failure', function(assert) { QUnit.test('start with signalStrategy failure', function(assert) {
stubSignalStrategyConnect(false); stubSignalStrategyConnect(false);
...@@ -356,7 +341,7 @@ QUnit.test('start with startDaemon returning failure code', function(assert) { ...@@ -356,7 +341,7 @@ QUnit.test('start with startDaemon returning failure code', function(assert) {
// Check what happens when the entire host registration process // Check what happens when the entire host registration process
// succeeds. // succeeds.
[false, true].forEach(function(/** boolean */ consent) { [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 */ /** @const */
var fakePinHash = fakePinHashFunc(FAKE_HOST_ID, FAKE_HOST_PIN); var fakePinHash = fakePinHashFunc(FAKE_HOST_ID, FAKE_HOST_PIN);
stubSignalStrategyConnect(true); stubSignalStrategyConnect(true);
...@@ -388,37 +373,6 @@ QUnit.test('start with startDaemon returning failure code', function(assert) { ...@@ -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. // Check what happens when stopDaemon calls onError.
// TODO(jrw): Should stopDaemon even have an onError callback? // TODO(jrw): Should stopDaemon even have an onError callback?
QUnit.test('stop with stopDaemon failure', function(assert) { QUnit.test('stop with stopDaemon failure', function(assert) {
......
...@@ -26,7 +26,7 @@ remoting.HostListApi = function() { ...@@ -26,7 +26,7 @@ remoting.HostListApi = function() {
* @param {string} hostName The user-visible name of the new host. * @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} publicKey The public half of the host's key pair.
* @param {string} hostClientId The OAuth2 client ID of the host. * @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( remoting.HostListApi.prototype.register = function(
newHostId, hostName, publicKey, hostClientId) { newHostId, hostName, publicKey, hostClientId) {
...@@ -87,3 +87,15 @@ remoting.HostListApi.setInstance = function(newInstance) { ...@@ -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( ...@@ -40,7 +40,7 @@ remoting.HostListApiGcdImpl.prototype.register = function(
} }
}; };
return /** @type {!Promise<string>} */ ( return /** @type {!Promise<remoting.HostListApi.RegisterResult>} */ (
this.gcd_.insertRegistrationTicket(). this.gcd_.insertRegistrationTicket().
then(function(ticket) { then(function(ticket) {
return self.gcd_.patchRegistrationTicket( return self.gcd_.patchRegistrationTicket(
...@@ -50,7 +50,10 @@ remoting.HostListApiGcdImpl.prototype.register = function( ...@@ -50,7 +50,10 @@ remoting.HostListApiGcdImpl.prototype.register = function(
return self.gcd_.finalizeRegistrationTicket(ticket.id); return self.gcd_.finalizeRegistrationTicket(ticket.id);
}). }).
then(function(/**remoting.gcd.RegistrationTicket*/ ticket) { then(function(/**remoting.gcd.RegistrationTicket*/ ticket) {
return ticket.robotAccountAuthorizationCode; return {
authCode: ticket.robotAccountAuthorizationCode,
email: ticket.robotAccountEmail
};
}). }).
catch(function(error) { catch(function(error) {
console.error('Error registering device with GCD: ' + error); console.error('Error registering device with GCD: ' + error);
......
...@@ -41,12 +41,10 @@ remoting.HostListApiImpl.prototype.register = function( ...@@ -41,12 +41,10 @@ remoting.HostListApiImpl.prototype.register = function(
useIdentity: true useIdentity: true
}).start().then(function(response) { }).start().then(function(response) {
if (response.status == 200) { if (response.status == 200) {
var result = response.getJson(); var result = /** @type {!Object} */ (response.getJson());
if (result['data']) { var data = base.getObjectAttr(result, 'data');
return base.getStringAttr(result['data'], 'authorizationCode', ''); var authCode = base.getStringAttr(data, 'authorizationCode');
} else { return { authCode: authCode, email: '' };
return '';
}
} else { } else {
console.log( console.log(
'Failed to register the host. Status: ' + response.status + 'Failed to register the host. Status: ' + response.status +
......
...@@ -31,17 +31,13 @@ QUnit.module('host_list_api_impl', { ...@@ -31,17 +31,13 @@ QUnit.module('host_list_api_impl', {
/** /**
* Install an HTTP response for requests to the registry. * Install an HTTP response for requests to the registry.
* @param {QUnit.Assert} assert * @param {QUnit.Assert} assert
* @param {boolean} withAuthCode
*/ */
function queueRegistryResponse(assert, withAuthCode) { function queueRegistryResponse(assert) {
var responseJson = { var responseJson = {
data: { data: {
authorizationCode: FAKE_AUTH_CODE authorizationCode: FAKE_AUTH_CODE
} }
}; };
if (!withAuthCode) {
delete responseJson.data.authorizationCode;
}
remoting.MockXhr.setResponseFor( remoting.MockXhr.setResponseFor(
'POST', 'DIRECTORY_API_BASE_URL/@me/hosts', 'POST', 'DIRECTORY_API_BASE_URL/@me/hosts',
...@@ -57,29 +53,17 @@ function queueRegistryResponse(assert, withAuthCode) { ...@@ -57,29 +53,17 @@ function queueRegistryResponse(assert, withAuthCode) {
}); });
} }
QUnit.test('register with auth code', function(assert) { QUnit.test('register', 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) {
var impl = new remoting.HostListApiImpl(); var impl = new remoting.HostListApiImpl();
queueRegistryResponse(assert, false); queueRegistryResponse(assert);
return impl.register( return impl.register(
FAKE_HOST_ID, FAKE_HOST_ID,
FAKE_HOST_NAME, FAKE_HOST_NAME,
FAKE_PUBLIC_KEY, FAKE_PUBLIC_KEY,
FAKE_HOST_CLIENT_ID FAKE_HOST_CLIENT_ID
). then(function(authCode) { ). then(function(regResult) {
assert.equal(authCode, ''); assert.equal(regResult.authCode, FAKE_AUTH_CODE);
assert.equal(regResult.email, '');
}); });
}); });
......
...@@ -18,11 +18,18 @@ var remoting = remoting || {}; ...@@ -18,11 +18,18 @@ var remoting = remoting || {};
*/ */
remoting.MockHostListApi = function() { remoting.MockHostListApi = function() {
/** /**
* The auth code for the |register| method to return, or null if it * The auth code value for the |register| method to return, or null
* should fail. * if it should fail.
* @type {?string} * @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>} */ /** @type {Array<remoting.Host>} */
this.hosts = [ this.hosts = [
...@@ -50,13 +57,16 @@ remoting.MockHostListApi = function() { ...@@ -50,13 +57,16 @@ remoting.MockHostListApi = function() {
/** @override */ /** @override */
remoting.MockHostListApi.prototype.register = function( remoting.MockHostListApi.prototype.register = function(
newHostId, hostName, publicKey, hostClientId) { newHostId, hostName, publicKey, hostClientId) {
if (this.registerResult === null) { if (this.authCodeFromRegister === null || this.emailFromRegister === null) {
return Promise.reject( return Promise.reject(
new remoting.Error( new remoting.Error(
remoting.Error.Tag.REGISTRATION_FAILED, remoting.Error.Tag.REGISTRATION_FAILED,
'MockHostListApi.register')); 'MockHostListApi.register'));
} else { } else {
return Promise.resolve(this.registerResult); return Promise.resolve({
authCode: this.authCodeFromRegister,
email: this.emailFromRegister
});
} }
}; };
...@@ -82,6 +92,7 @@ remoting.MockHostListApi.prototype.put = ...@@ -82,6 +92,7 @@ remoting.MockHostListApi.prototype.put =
return new Promise(function(resolve, reject) { return new Promise(function(resolve, reject) {
var onTokenValid = function() { var onTokenValid = function() {
for (var i = 0; i < that.hosts.length; ++i) { for (var i = 0; i < that.hosts.length; ++i) {
/** type {remoting.Host} */
var host = that.hosts[i]; var host = that.hosts[i];
if (host.hostId == hostId) { if (host.hostId == hostId) {
host.hostName = hostName; 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