
These tags are preserved by the Closure compiler and vulcanize in order to serve the license notices embedded in the outputs. In a standalone Gerrit server, these license are also covered in the LICENSES.txt served with the documentation. When serving PG assets from a CDN, it's less obvious what the corresponding LICENSES.txt file is, since the CDN is not directly linked to a running Gerrit server. Safer to embed the licenses in the assets themselves. Change-Id: Id1add1451fad1baa7916882a6bda02c326ccc988
187 lines
5.2 KiB
JavaScript
187 lines
5.2 KiB
JavaScript
/**
|
|
* @license
|
|
* 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);
|