| Index: remoting/webapp/third_party_token_fetcher.js
|
| diff --git a/remoting/webapp/third_party_token_fetcher.js b/remoting/webapp/third_party_token_fetcher.js
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..a16246cd0109067ea9411bdb557c6b3a53fc30e5
|
| --- /dev/null
|
| +++ b/remoting/webapp/third_party_token_fetcher.js
|
| @@ -0,0 +1,171 @@
|
| +// Copyright 2013 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +/**
|
| + * @fileoverview
|
| + * Third party authentication support for the remoting web-app.
|
| + *
|
| + * When third party authentication is being used, the client must request both a
|
| + * token and a shared secret from a third-party server. The server can then
|
| + * present the user with an authentication page, or use any other method to
|
| + * authenticate the user via the browser. Once the user is authenticated, the
|
| + * server will redirect the browser to a URL containing the token and shared
|
| + * secret in its fragment. The client then sends only the token to the host.
|
| + * The host signs the token, then contacts the third-party server to exchange
|
| + * the token for the shared secret. Once both client and host have the shared
|
| + * secret, they use a zero-disclosure mutual authentication protocol to
|
| + * negotiate an authentication key, which is used to establish the connection.
|
| + */
|
| +
|
| +'use strict';
|
| +
|
| +/** @suppress {duplicate} */
|
| +var remoting = remoting || {};
|
| +
|
| +/**
|
| + * @constructor
|
| + * Encapsulates the logic to fetch a third party authentication token.
|
| + *
|
| + * @param {string} tokenUrl Token-issue URL received from the host.
|
| + * @param {string} hostPublicKey Host public key (DER and Base64 encoded).
|
| + * @param {string} scope OAuth scope to request the token for.
|
| + * @param {Array.<string>} tokenUrlPatterns Token URL patterns allowed for the
|
| + * domain, received from the directory server.
|
| + * @param {function(string, string):void} onThirdPartyTokenFetched Callback.
|
| + */
|
| +remoting.ThirdPartyTokenFetcher = function(
|
| + tokenUrl, hostPublicKey, scope, tokenUrlPatterns,
|
| + onThirdPartyTokenFetched) {
|
| + this.tokenUrl_ = tokenUrl;
|
| + this.tokenScope_ = scope;
|
| + this.onThirdPartyTokenFetched_ = onThirdPartyTokenFetched;
|
| + this.failFetchToken_ = function() { onThirdPartyTokenFetched('', ''); };
|
| + this.xsrfToken_ = remoting.generateXsrfToken();
|
| + this.tokenUrlPatterns_ = tokenUrlPatterns;
|
| + this.hostPublicKey_ = hostPublicKey;
|
| + if (chrome.experimental && chrome.experimental.identity) {
|
| + /** @type {function():void}
|
| + * @private */
|
| + this.fetchTokenInternal_ = this.fetchTokenIdentityApi_.bind(this);
|
| + this.redirectUri_ = 'https://' + window.location.hostname +
|
| + '.chromiumapp.org/ThirdPartyAuth';
|
| + } else {
|
| + this.fetchTokenInternal_ = this.fetchTokenWindowOpen_.bind(this);
|
| + this.redirectUri_ = remoting.settings.THIRD_PARTY_AUTH_REDIRECT_URI;
|
| + }
|
| +};
|
| +
|
| +/**
|
| + * Fetch a token with the parameters configured in this object.
|
| + */
|
| +remoting.ThirdPartyTokenFetcher.prototype.fetchToken = function() {
|
| + // Verify the host-supplied URL matches the domain's allowed URL patterns.
|
| + for (var i = 0; i < this.tokenUrlPatterns_.length; i++) {
|
| + if (this.tokenUrl_.match(this.tokenUrlPatterns_[i])) {
|
| + var hostPermissions = new remoting.ThirdPartyHostPermissions(
|
| + this.tokenUrl_);
|
| + hostPermissions.getPermission(
|
| + this.fetchTokenInternal_,
|
| + this.failFetchToken_);
|
| +
|
| + return;
|
| + }
|
| + }
|
| + // If the URL doesn't match any pattern in the list, refuse to access it.
|
| + console.error('Token URL does not match the domain\'s allowed URL patterns.' +
|
| + ' URL: ' + this.tokenUrl_ + ', patterns: ' + this.tokenUrlPatterns_);
|
| + this.failFetchToken_();
|
| +};
|
| +
|
| +/**
|
| + * Parse the access token from the URL to which we were redirected.
|
| + *
|
| + * @param {string} responseUrl The URL to which we were redirected.
|
| + * @private
|
| + */
|
| +remoting.ThirdPartyTokenFetcher.prototype.parseRedirectUrl_ =
|
| + function(responseUrl) {
|
| + var token = '';
|
| + var sharedSecret = '';
|
| + if (responseUrl &&
|
| + responseUrl.search(this.redirectUri_ + '#') == 0) {
|
| + var query = responseUrl.substring(this.redirectUri_.length + 1);
|
| + var parts = query.split('&');
|
| + /** @type {Object.<string>} */
|
| + var queryArgs = {};
|
| + for (var i = 0; i < parts.length; i++) {
|
| + var pair = parts[i].split('=');
|
| + queryArgs[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1]);
|
| + }
|
| +
|
| + // Check that 'state' contains the same XSRF token we sent in the request.
|
| + var xsrfToken = queryArgs['state'];
|
| + if (xsrfToken == this.xsrfToken_ &&
|
| + 'code' in queryArgs && 'access_token' in queryArgs) {
|
| + // Terminology note:
|
| + // In the OAuth code/token exchange semantics, 'code' refers to the value
|
| + // obtained when the *user* authenticates itself, while 'access_token' is
|
| + // the value obtained when the *application* authenticates itself to the
|
| + // server ("implicitly", by receiving it directly in the URL fragment, or
|
| + // explicitly, by sending the 'code' and a 'client_secret' to the server).
|
| + // Internally, the piece of data obtained when the user authenticates
|
| + // itself is called the 'token', and the one obtained when the host
|
| + // authenticates itself (using the 'token' received from the client and
|
| + // its private key) is called the 'shared secret'.
|
| + // The client implicitly authenticates itself, and directly obtains the
|
| + // 'shared secret', along with the 'token' from the redirect URL fragment.
|
| + token = queryArgs['code'];
|
| + sharedSecret = queryArgs['access_token'];
|
| + }
|
| + }
|
| + this.onThirdPartyTokenFetched_(token, sharedSecret);
|
| +};
|
| +
|
| +/**
|
| + * Build a full token request URL from the parameters in this object.
|
| + *
|
| + * @return {string} Full URL to request a token.
|
| + * @private
|
| + */
|
| +remoting.ThirdPartyTokenFetcher.prototype.getFullTokenUrl_ = function() {
|
| + return this.tokenUrl_ + '?' + remoting.xhr.urlencodeParamHash({
|
| + 'redirect_uri': this.redirectUri_,
|
| + 'scope': this.tokenScope_,
|
| + 'client_id': this.hostPublicKey_,
|
| + // The webapp uses an "implicit" OAuth flow with multiple response types to
|
| + // obtain both the code and the shared secret in a single request.
|
| + 'response_type': 'code token',
|
| + 'state': this.xsrfToken_
|
| + });
|
| +};
|
| +
|
| +/**
|
| + * Fetch a token by opening a new window and redirecting to a content script.
|
| + * @private
|
| + */
|
| +remoting.ThirdPartyTokenFetcher.prototype.fetchTokenWindowOpen_ = function() {
|
| + /** @type {remoting.ThirdPartyTokenFetcher} */
|
| + var that = this;
|
| + var fullTokenUrl = this.getFullTokenUrl_();
|
| + // The function below can't be anonymous, since it needs to reference itself.
|
| + /** @param {string} message Message received from the content script. */
|
| + function tokenMessageListener(message) {
|
| + that.parseRedirectUrl_(message);
|
| + chrome.extension.onMessage.removeListener(tokenMessageListener);
|
| + }
|
| + chrome.extension.onMessage.addListener(tokenMessageListener);
|
| + window.open(fullTokenUrl, '_blank', 'location=yes,toolbar=no,menubar=no');
|
| +};
|
| +
|
| +/**
|
| + * Fetch a token from a token server using the identity.launchWebAuthFlow API.
|
| + * @private
|
| + */
|
| +remoting.ThirdPartyTokenFetcher.prototype.fetchTokenIdentityApi_ = function() {
|
| + var fullTokenUrl = this.getFullTokenUrl_();
|
| + // TODO(rmsousa): chrome.identity.launchWebAuthFlow is experimental.
|
| + chrome.experimental.identity.launchWebAuthFlow(
|
| + {'url': fullTokenUrl, 'interactive': true},
|
| + this.parseRedirectUrl_.bind(this));
|
| +};
|
|
|