blob: 43e39223ae05089673b144c36f7e2672421e93d8 [file] [log] [blame]
/**
* @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);