Roll src/third_party/WebKit 3529d49:06e8485 (svn 202554:202555)
[chromium-blink-merge.git] / remoting / webapp / crd / js / third_party_token_fetcher.js
blob5767b798781141257ca955712582e1cb01a6bed8
1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 /**
6  * @fileoverview
7  * Third party authentication support for the remoting web-app.
8  *
9  * When third party authentication is being used, the client must request both a
10  * token and a shared secret from a third-party server. The server can then
11  * present the user with an authentication page, or use any other method to
12  * authenticate the user via the browser. Once the user is authenticated, the
13  * server will redirect the browser to a URL containing the token and shared
14  * secret in its fragment. The client then sends only the token to the host.
15  * The host signs the token, then contacts the third-party server to exchange
16  * the token for the shared secret. Once both client and host have the shared
17  * secret, they use a zero-disclosure mutual authentication protocol to
18  * negotiate an authentication key, which is used to establish the connection.
19  */
21 'use strict';
23 /** @suppress {duplicate} */
24 var remoting = remoting || {};
26 /**
27  * @constructor
28  * Encapsulates the logic to fetch a third party authentication token.
29  *
30  * @param {string} tokenUrl Token-issue URL received from the host.
31  * @param {string} hostPublicKey Host public key (DER and Base64 encoded).
32  * @param {string} scope OAuth scope to request the token for.
33  * @param {Array<string>} tokenUrlPatterns Token URL patterns allowed for the
34  *     domain, received from the directory server.
35  * @param {function(string, string):void} onThirdPartyTokenFetched Callback.
36  */
37 remoting.ThirdPartyTokenFetcher = function(
38     tokenUrl, hostPublicKey, scope, tokenUrlPatterns,
39     onThirdPartyTokenFetched) {
40   this.tokenUrl_ = tokenUrl;
41   this.tokenScope_ = scope;
42   this.onThirdPartyTokenFetched_ = onThirdPartyTokenFetched;
43   this.failFetchToken_ = function() { onThirdPartyTokenFetched('', ''); };
44   this.xsrfToken_ = base.generateXsrfToken();
45   this.tokenUrlPatterns_ = tokenUrlPatterns;
46   this.hostPublicKey_ = hostPublicKey;
47   if (chrome.identity) {
48     /** @private {function():void} */
49     this.fetchTokenInternal_ = this.fetchTokenIdentityApi_.bind(this);
50     this.redirectUri_ = 'https://' + window.location.hostname +
51         '.chromiumapp.org/ThirdPartyAuth';
52   } else {
53     this.fetchTokenInternal_ = this.fetchTokenWindowOpen_.bind(this);
54     this.redirectUri_ = remoting.settings.THIRD_PARTY_AUTH_REDIRECT_URI;
55   }
58 /**
59  * Fetch a token with the parameters configured in this object.
60  */
61 remoting.ThirdPartyTokenFetcher.prototype.fetchToken = function() {
62   // If there is no list of patterns, this host cannot use a token URL.
63   if (!this.tokenUrlPatterns_) {
64     console.error('No token URLs are allowed for this host');
65     this.failFetchToken_();
66   }
68   // Verify the host-supplied URL matches the domain's allowed URL patterns.
69   for (var i = 0; i < this.tokenUrlPatterns_.length; i++) {
70     if (this.tokenUrl_.match(this.tokenUrlPatterns_[i])) {
71       var hostPermissions = new remoting.ThirdPartyHostPermissions(
72           this.tokenUrl_);
73       hostPermissions.getPermission(
74           this.fetchTokenInternal_,
75           this.failFetchToken_);
76       return;
77     }
78   }
79   // If the URL doesn't match any pattern in the list, refuse to access it.
80   console.error('Token URL does not match the domain\'s allowed URL patterns.' +
81       ' URL: ' + this.tokenUrl_ + ', patterns: ' + this.tokenUrlPatterns_);
82   this.failFetchToken_();
85 /**
86  * Parse the access token from the URL to which we were redirected.
87  *
88  * @param {string=} responseUrl The URL to which we were redirected.
89  * @private
90  */
91 remoting.ThirdPartyTokenFetcher.prototype.parseRedirectUrl_ =
92     function(responseUrl) {
93   var token = '';
94   var sharedSecret = '';
96   if (responseUrl && responseUrl.search('#') >= 0) {
97     var query = responseUrl.substring(responseUrl.search('#') + 1);
98     var parts = query.split('&');
99     /** @type {Object<string>} */
100     var queryArgs = {};
101     for (var i = 0; i < parts.length; i++) {
102       var pair = parts[i].split('=');
103       queryArgs[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1]);
104     }
106     // Check that 'state' contains the same XSRF token we sent in the request.
107     if ('state' in queryArgs && queryArgs['state'] == this.xsrfToken_ &&
108         'code' in queryArgs && 'access_token' in queryArgs) {
109       // Terminology note:
110       // In the OAuth code/token exchange semantics, 'code' refers to the value
111       // obtained when the *user* authenticates itself, while 'access_token' is
112       // the value obtained when the *application* authenticates itself to the
113       // server ("implicitly", by receiving it directly in the URL fragment, or
114       // explicitly, by sending the 'code' and a 'client_secret' to the server).
115       // Internally, the piece of data obtained when the user authenticates
116       // itself is called the 'token', and the one obtained when the host
117       // authenticates itself (using the 'token' received from the client and
118       // its private key) is called the 'shared secret'.
119       // The client implicitly authenticates itself, and directly obtains the
120       // 'shared secret', along with the 'token' from the redirect URL fragment.
121       token = queryArgs['code'];
122       sharedSecret = queryArgs['access_token'];
123     }
124   }
125   this.onThirdPartyTokenFetched_(token, sharedSecret);
129  * Build a full token request URL from the parameters in this object.
131  * @return {string} Full URL to request a token.
132  * @private
133  */
134 remoting.ThirdPartyTokenFetcher.prototype.getFullTokenUrl_ = function() {
135   return this.tokenUrl_ + '?' + remoting.Xhr.urlencodeParamHash({
136     'redirect_uri': this.redirectUri_,
137     'scope': this.tokenScope_,
138     'client_id': this.hostPublicKey_,
139     // The webapp uses an "implicit" OAuth flow with multiple response types to
140     // obtain both the code and the shared secret in a single request.
141     'response_type': 'code token',
142     'state': this.xsrfToken_
143   });
147  * Fetch a token by opening a new window and redirecting to a content script.
148  * @private
149  */
150 remoting.ThirdPartyTokenFetcher.prototype.fetchTokenWindowOpen_ = function() {
151   /** @type {remoting.ThirdPartyTokenFetcher} */
152   var that = this;
153   var fullTokenUrl = this.getFullTokenUrl_();
154   // The function below can't be anonymous, since it needs to reference itself.
155   /**
156    * @param {string} message Message received from the content script.
157    * @param {function(*)} sendResponse Function to send response.
158    */
159   function tokenMessageListener(message, sender, sendResponse) {
160     that.parseRedirectUrl_(message);
161     chrome.extension.onMessage.removeListener(tokenMessageListener);
162     sendResponse(null);
163   }
164   chrome.extension.onMessage.addListener(tokenMessageListener);
165   window.open(fullTokenUrl, '_blank', 'location=yes,toolbar=no,menubar=no');
169  * Fetch a token from a token server using the identity.launchWebAuthFlow API.
170  * @private
171  */
172 remoting.ThirdPartyTokenFetcher.prototype.fetchTokenIdentityApi_ = function() {
173   var fullTokenUrl = this.getFullTokenUrl_();
174   chrome.identity.launchWebAuthFlow(
175     {'url': fullTokenUrl, 'interactive': true},
176     this.parseRedirectUrl_.bind(this));