|  | // Copyright (C) 2017 The Android Open Source Project | 
|  | // | 
|  | // Licensed under the Apache License, Version 2.0 (the "License"); | 
|  | // you may not use this file except in compliance with the License. | 
|  | // You may obtain a copy of the License at | 
|  | // | 
|  | // http://www.apache.org/licenses/LICENSE-2.0 | 
|  | // | 
|  | // Unless required by applicable law or agreed to in writing, software | 
|  | // distributed under the License is distributed on an "AS IS" BASIS, | 
|  | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
|  | // See the License for the specific language governing permissions and | 
|  | // limitations under the License. | 
|  | (function(window) { | 
|  | 'use strict'; | 
|  |  | 
|  | // Prevent redefinition. | 
|  | if (window.Gerrit.Auth) { return; } | 
|  |  | 
|  | const MAX_GET_TOKEN_RETRIES = 2; | 
|  |  | 
|  | Gerrit.Auth = { | 
|  | TYPE: { | 
|  | XSRF_TOKEN: 'xsrf_token', | 
|  | ACCESS_TOKEN: 'access_token', | 
|  | }, | 
|  |  | 
|  | _type: null, | 
|  | _cachedTokenPromise: null, | 
|  | _defaultOptions: {}, | 
|  | _retriesLeft: MAX_GET_TOKEN_RETRIES, | 
|  |  | 
|  | _getToken() { | 
|  | return Promise.resolve(this._cachedTokenPromise); | 
|  | }, | 
|  |  | 
|  | /** | 
|  | * Enable cross-domain authentication using OAuth access token. | 
|  | * | 
|  | * @param { | 
|  | *   function(): !Promise<{ | 
|  | *     access_token: string, | 
|  | *     expires_at: number | 
|  | *   }> | 
|  | * } getToken | 
|  | * @param {?{credentials:string}} defaultOptions | 
|  | */ | 
|  | setup(getToken, defaultOptions) { | 
|  | this._retriesLeft = MAX_GET_TOKEN_RETRIES; | 
|  | if (getToken) { | 
|  | this._type = Gerrit.Auth.TYPE.ACCESS_TOKEN; | 
|  | this._cachedTokenPromise = null; | 
|  | this._getToken = getToken; | 
|  | } | 
|  | this._defaultOptions = {}; | 
|  | if (defaultOptions) { | 
|  | for (const p of ['credentials']) { | 
|  | this._defaultOptions[p] = defaultOptions[p]; | 
|  | } | 
|  | } | 
|  | }, | 
|  |  | 
|  | /** | 
|  | * Perform network fetch with authentication. | 
|  | * | 
|  | * @param {string} url | 
|  | * @param {Object=} opt_options | 
|  | * @return {!Promise<!Response>} | 
|  | */ | 
|  | fetch(url, opt_options) { | 
|  | const options = Object.assign({ | 
|  | headers: new Headers(), | 
|  | }, this._defaultOptions, opt_options); | 
|  | if (this._type === Gerrit.Auth.TYPE.ACCESS_TOKEN) { | 
|  | return this._getAccessToken().then( | 
|  | accessToken => this._fetchWithAccessToken(url, options, accessToken) | 
|  | ); | 
|  | } else { | 
|  | return this._fetchWithXsrfToken(url, options); | 
|  | } | 
|  | }, | 
|  |  | 
|  | _getCookie(name) { | 
|  | const key = name + '='; | 
|  | let result = ''; | 
|  | document.cookie.split(';').some(c => { | 
|  | c = c.trim(); | 
|  | if (c.startsWith(key)) { | 
|  | result = c.substring(key.length); | 
|  | return true; | 
|  | } | 
|  | }); | 
|  | return result; | 
|  | }, | 
|  |  | 
|  | _isTokenValid(token) { | 
|  | if (!token) { return false; } | 
|  | if (!token.access_token || !token.expires_at) { return false; } | 
|  |  | 
|  | const expiration = new Date(parseInt(token.expires_at, 10) * 1000); | 
|  | if (Date.now() >= expiration.getTime()) { return false; } | 
|  |  | 
|  | return true; | 
|  | }, | 
|  |  | 
|  | _fetchWithXsrfToken(url, options) { | 
|  | if (options.method && options.method !== 'GET') { | 
|  | const token = this._getCookie('XSRF_TOKEN'); | 
|  | if (token) { | 
|  | options.headers.append('X-Gerrit-Auth', token); | 
|  | } | 
|  | } | 
|  | options.credentials = 'same-origin'; | 
|  | return fetch(url, options); | 
|  | }, | 
|  |  | 
|  | /** | 
|  | * @return {!Promise<string>} | 
|  | */ | 
|  | _getAccessToken() { | 
|  | if (!this._cachedTokenPromise) { | 
|  | this._cachedTokenPromise = this._getToken(); | 
|  | } | 
|  | return this._cachedTokenPromise.then(token => { | 
|  | if (this._isTokenValid(token)) { | 
|  | this._retriesLeft = MAX_GET_TOKEN_RETRIES; | 
|  | return token.access_token; | 
|  | } | 
|  | if (this._retriesLeft > 0) { | 
|  | this._retriesLeft--; | 
|  | this._cachedTokenPromise = null; | 
|  | return this._getAccessToken(); | 
|  | } | 
|  | // Fall back to anonymous access. | 
|  | return null; | 
|  | }); | 
|  | }, | 
|  |  | 
|  | _fetchWithAccessToken(url, options, accessToken) { | 
|  | const params = []; | 
|  |  | 
|  | if (accessToken) { | 
|  | params.push(`access_token=${accessToken}`); | 
|  | const baseUrl = Gerrit.BaseUrlBehavior.getBaseUrl(); | 
|  | const pathname = baseUrl ? | 
|  | url.substring(url.indexOf(baseUrl) + baseUrl.length) : url; | 
|  | if (!pathname.startsWith('/a/')) { | 
|  | url = url.replace(pathname, '/a' + pathname); | 
|  | } | 
|  | } | 
|  |  | 
|  | const method = options.method || 'GET'; | 
|  | let contentType = options.headers.get('Content-Type'); | 
|  |  | 
|  | // For all requests with body, ensure json content type. | 
|  | if (!contentType && options.body) { | 
|  | contentType = 'application/json'; | 
|  | } | 
|  |  | 
|  | if (method !== 'GET') { | 
|  | options.method = 'POST'; | 
|  | params.push(`$m=${method}`); | 
|  | // If a request is not GET, and does not have a body, ensure text/plain | 
|  | // content type. | 
|  | if (!contentType) { | 
|  | contentType = 'text/plain'; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (contentType) { | 
|  | options.headers.set('Content-Type', 'text/plain'); | 
|  | params.push(`$ct=${encodeURIComponent(contentType)}`); | 
|  | } | 
|  |  | 
|  | if (params.length) { | 
|  | url = url + (url.indexOf('?') === -1 ? '?' : '&') + params.join('&'); | 
|  | } | 
|  | return fetch(url, options); | 
|  | }, | 
|  | }; | 
|  |  | 
|  | window.Gerrit.Auth = Gerrit.Auth; | 
|  | })(window); |