blob: d34a7a23dde66529f1d926a39c1fb1a01cdc9e39 [file]
import {assertRejects} from './test-util';
import './test-setup';
import {Authenticator, initAuth, getAuthorizationHeader, resetAuthState, JWT_TOKEN_ID, JWT_TIMEOUT_MILLISECONDS, GERRIT_JWT_FLAG, Token, OAuthConfig} from '../auth';
import {PluginApi} from '@gerritcodereview/typescript-api/plugin';
const platformJsElement = document.getElementById('platformJs') as HTMLScriptElement;
suite('auth', () => {
let sandbox: sinon.SinonSandbox;
let authenticator: Authenticator;
let plugin: PluginApi;
let accessToken: Token;
let jwtToken: Token;
let jwtResponse: {jwts: any};
let clock: sinon.SinonFakeTimers;
let changeId: string;
setup(() => {
sandbox = sinon.createSandbox({});
clock = sinon.useFakeTimers({
now: new Date().getTime(),
});
accessToken = {
access_token: 'token',
expires_at: Date.now() + 60e3,
};
jwtToken = {
signed_jwt: 'abc',
expires_at: new Date().getTime() + JWT_TIMEOUT_MILLISECONDS,
}
window.gapi = {
auth2: {
authorize: (_: any, resolve: any) => resolve(accessToken),
},
};
jwtResponse = {
jwts: {
[JWT_TOKEN_ID]: {
signed_jwt: 'abc',
}
}
};
changeId = '123';
plugin = {
restApi: () => ({
get: () => jwtResponse
})
} as unknown as PluginApi;
// Test with Mendel flag enabled
if (!window.ENABLED_EXPERIMENTS) {
window.ENABLED_EXPERIMENTS = [];
}
window.ENABLED_EXPERIMENTS.push(GERRIT_JWT_FLAG);
authenticator = new Authenticator({
client_id: 'client-id',
email: 'jane@example.com',
}, plugin);
});
teardown(() => {
sandbox.restore();
clock.restore();
});
test('getToken returns expected token object', async () => {
const actualToken = await authenticator.getToken(changeId);
assert.deepEqual(actualToken, jwtToken);
});
test('getToken falls back to access_token when there is no JWT', async () => {
jwtResponse.jwts = {};
const actualToken = await authenticator.getToken(changeId);
assert.deepEqual(actualToken, accessToken);
});
test('getToken rejects if there is an error', async () => {
jwtResponse.jwts[JWT_TOKEN_ID].error = 'bad';
assertRejects(authenticator.getToken(changeId), 'bad');
});
test('getToken rejects when token has no expiry date', async () => {
jwtResponse.jwts = {};
accessToken.expires_at = null;
assertRejects(authenticator.getToken(changeId));
});
test('getToken rejects when token is expired', async () => {
jwtResponse.jwts = {};
accessToken.expires_at = 1543620640;
assertRejects(authenticator.getToken(changeId));
});
test('getToken rejects when authorize times out', async () => {
jwtResponse.jwts = {};
const forever = new Promise(() => {});
sandbox.stub(gapi.auth2, 'authorize').callsFake(forever as any);
assertRejects(authenticator.getToken(changeId), 'timeout');
});
test('getToken uses cached token', async () => {
sandbox.spy(plugin, 'restApi');
const actualToken1 = await authenticator.getToken(changeId);
const actualToken2 = await authenticator.getToken(changeId);
assert.isTrue((plugin.restApi as any).calledOnce);
assert.deepEqual(actualToken1, jwtToken);
assert.deepEqual(actualToken2, jwtToken);
});
test('getToken uses cached token for two async calls', async () => {
sandbox.spy(plugin, 'restApi');
const [actualToken1, actualToken2] = await Promise.all([
authenticator.getToken(changeId),
authenticator.getToken(changeId),
]);
assert.deepEqual(actualToken1, jwtToken);
assert.deepEqual(actualToken2, jwtToken);
assert.isTrue((plugin.restApi as any).calledOnce);
});
});
suite('initAuth', () => {
let sandbox: sinon.SinonSandbox;
let plugin: PluginApi;
let loggedIn: boolean;
let cfg: OAuthConfig;
const accessToken = 'token';
setup(() => {
sandbox = sinon.createSandbox({
useFakeTimers: {now: 1543620642},
});
loggedIn = true;
cfg = {};
plugin = {
restApi: () => ({
getLoggedIn: async () => loggedIn,
get: async (_) => cfg,
}),
} as PluginApi;
window.gapi = {
auth2: {
init: (resolve: any) => resolve(),
},
config: {
get: () => null,
update: () => {},
},
load: (_: any, resolve: any) => resolve(),
};
sandbox.stub(Authenticator.prototype, 'getToken').callsFake(async () => ({
access_token: accessToken,
expires_at: 0,
}));
});
teardown(() => {
sandbox.restore();
resetAuthState();
});
test('initAuth sets access token which can then be gotten', async () => {
initAuth(plugin, platformJsElement);
const actualAuthHeader = await getAuthorizationHeader('123456');
assert.deepEqual(actualAuthHeader, {'authorization': `Bearer ${accessToken}`});
});
test('initAuth throws if called twice', async () => {
initAuth(plugin, platformJsElement);
assert.throws(
() => initAuth(plugin, platformJsElement), 'initAuth is called more than once');
});
test('getAuthorizationHeader rejects if called before init', async () => {
assertRejects(getAuthorizationHeader('123456'), 'initAuth was not called');
});
test('initAuth sets up gapi', async () => {
sandbox.spy(gapi.config, 'update');
sandbox.spy(gapi, 'load');
cfg = {
auth_url: 'https://example.com/auth',
proxy_url: 'https://example.com/proxy',
};
initAuth(plugin, platformJsElement);
await getAuthorizationHeader('123456');
assert.isTrue(
gapi.config.update.calledWith('oauth-flow/authUrl', cfg.auth_url));
assert.isTrue(
gapi.config.update.calledWith('oauth-flow/proxyUrl', cfg.proxy_url));
// `auth` must be loaded after config is updated.
assert.isTrue(
gapi.load.withArgs('auth2', sinon.match.any)
.calledAfter(gapi.config.update));
});
});