395 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			HTML
		
	
	
	
	
	
			
		
		
	
	
			395 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			HTML
		
	
	
	
	
	
| <!DOCTYPE html>
 | |
| <!--
 | |
| @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.
 | |
| -->
 | |
| 
 | |
| <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 | |
| <meta charset="utf-8">
 | |
| <title>gr-auth</title>
 | |
| 
 | |
| <script src="/node_modules/@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 | |
| 
 | |
| <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 | |
| <script src="/components/wct-browser-legacy/browser.js"></script>
 | |
| <script type="module">
 | |
| import '../../../test/common-test-setup.js';
 | |
| import {BaseUrlBehavior} from '../../../behaviors/base-url-behavior/base-url-behavior.js';
 | |
| import {Auth, authService} from './gr-auth.js';
 | |
| import {gerritEventEmitter} from '../gr-event-emitter/gr-event-emitter.js';
 | |
| 
 | |
| suite('gr-auth', () => {
 | |
|   let auth;
 | |
|   let sandbox;
 | |
| 
 | |
|   setup(() => {
 | |
|     sandbox = sinon.sandbox.create();
 | |
|     auth = authService;
 | |
|   });
 | |
| 
 | |
|   teardown(() => {
 | |
|     sandbox.restore();
 | |
|   });
 | |
| 
 | |
|   suite('Auth class methods', () => {
 | |
|     let fakeFetch;
 | |
|     setup(() => {
 | |
|       auth = new Auth();
 | |
|       fakeFetch = sandbox.stub(window, 'fetch');
 | |
|     });
 | |
| 
 | |
|     test('auth-check returns 403', done => {
 | |
|       fakeFetch.returns(Promise.resolve({status: 403}));
 | |
|       auth.authCheck().then(authed => {
 | |
|         assert.isFalse(authed);
 | |
|         assert.equal(auth.status, Auth.STATUS.NOT_AUTHED);
 | |
|         done();
 | |
|       });
 | |
|     });
 | |
| 
 | |
|     test('auth-check returns 204', done => {
 | |
|       fakeFetch.returns(Promise.resolve({status: 204}));
 | |
|       auth.authCheck().then(authed => {
 | |
|         assert.isTrue(authed);
 | |
|         assert.equal(auth.status, Auth.STATUS.AUTHED);
 | |
|         done();
 | |
|       });
 | |
|     });
 | |
| 
 | |
|     test('auth-check returns 502', done => {
 | |
|       fakeFetch.returns(Promise.resolve({status: 502}));
 | |
|       auth.authCheck().then(authed => {
 | |
|         assert.isFalse(authed);
 | |
|         assert.equal(auth.status, Auth.STATUS.NOT_AUTHED);
 | |
|         done();
 | |
|       });
 | |
|     });
 | |
| 
 | |
|     test('auth-check failed', done => {
 | |
|       fakeFetch.returns(Promise.reject(new Error('random error')));
 | |
|       auth.authCheck().then(authed => {
 | |
|         assert.isFalse(authed);
 | |
|         assert.equal(auth.status, Auth.STATUS.ERROR);
 | |
|         done();
 | |
|       });
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   suite('cache and events behavior', () => {
 | |
|     let fakeFetch;
 | |
|     let clock;
 | |
|     setup(() => {
 | |
|       auth = new Auth();
 | |
|       clock = sinon.useFakeTimers();
 | |
|       fakeFetch = sandbox.stub(window, 'fetch');
 | |
|     });
 | |
| 
 | |
|     test('cache auth-check result', done => {
 | |
|       fakeFetch.returns(Promise.resolve({status: 403}));
 | |
|       auth.authCheck().then(authed => {
 | |
|         assert.isFalse(authed);
 | |
|         assert.equal(auth.status, Auth.STATUS.NOT_AUTHED);
 | |
|         fakeFetch.returns(Promise.resolve({status: 204}));
 | |
|         auth.authCheck().then(authed2 => {
 | |
|           assert.isFalse(authed);
 | |
|           assert.equal(auth.status, Auth.STATUS.NOT_AUTHED);
 | |
|           done();
 | |
|         });
 | |
|       });
 | |
|     });
 | |
| 
 | |
|     test('clearCache should refetch auth-check result', done => {
 | |
|       fakeFetch.returns(Promise.resolve({status: 403}));
 | |
|       auth.authCheck().then(authed => {
 | |
|         assert.isFalse(authed);
 | |
|         assert.equal(auth.status, Auth.STATUS.NOT_AUTHED);
 | |
|         fakeFetch.returns(Promise.resolve({status: 204}));
 | |
|         auth.clearCache();
 | |
|         auth.authCheck().then(authed2 => {
 | |
|           assert.isTrue(authed2);
 | |
|           assert.equal(auth.status, Auth.STATUS.AUTHED);
 | |
|           done();
 | |
|         });
 | |
|       });
 | |
|     });
 | |
| 
 | |
|     test('cache expired on auth-check after certain time', done => {
 | |
|       fakeFetch.returns(Promise.resolve({status: 403}));
 | |
|       auth.authCheck().then(authed => {
 | |
|         assert.isFalse(authed);
 | |
|         assert.equal(auth.status, Auth.STATUS.NOT_AUTHED);
 | |
|         clock.tick(1000 * 10000);
 | |
|         fakeFetch.returns(Promise.resolve({status: 204}));
 | |
|         auth.authCheck().then(authed2 => {
 | |
|           assert.isTrue(authed2);
 | |
|           assert.equal(auth.status, Auth.STATUS.AUTHED);
 | |
|           done();
 | |
|         });
 | |
|       });
 | |
|     });
 | |
| 
 | |
|     test('no cache if auth-check failed', done => {
 | |
|       fakeFetch.returns(Promise.reject(new Error('random error')));
 | |
|       auth.authCheck().then(authed => {
 | |
|         assert.isFalse(authed);
 | |
|         assert.equal(auth.status, Auth.STATUS.ERROR);
 | |
|         assert.equal(fakeFetch.callCount, 1);
 | |
|         auth.authCheck().then(() => {
 | |
|           assert.equal(fakeFetch.callCount, 2);
 | |
|           done();
 | |
|         });
 | |
|       });
 | |
|     });
 | |
| 
 | |
|     test('fire event when switch from authed to unauthed', done => {
 | |
|       fakeFetch.returns(Promise.resolve({status: 204}));
 | |
|       auth.authCheck().then(authed => {
 | |
|         assert.isTrue(authed);
 | |
|         assert.equal(auth.status, Auth.STATUS.AUTHED);
 | |
|         clock.tick(1000 * 10000);
 | |
|         fakeFetch.returns(Promise.resolve({status: 403}));
 | |
|         const emitStub = sinon.stub();
 | |
|         gerritEventEmitter.emit = emitStub;
 | |
|         auth.authCheck().then(authed2 => {
 | |
|           assert.isFalse(authed2);
 | |
|           assert.equal(auth.status, Auth.STATUS.NOT_AUTHED);
 | |
|           assert.isTrue(emitStub.called);
 | |
|           done();
 | |
|         });
 | |
|       });
 | |
|     });
 | |
| 
 | |
|     test('fire event when switch from authed to error', done => {
 | |
|       fakeFetch.returns(Promise.resolve({status: 204}));
 | |
|       auth.authCheck().then(authed => {
 | |
|         assert.isTrue(authed);
 | |
|         assert.equal(auth.status, Auth.STATUS.AUTHED);
 | |
|         clock.tick(1000 * 10000);
 | |
|         fakeFetch.returns(Promise.reject(new Error('random error')));
 | |
|         const emitStub = sinon.stub();
 | |
|         gerritEventEmitter.emit = emitStub;
 | |
|         auth.authCheck().then(authed2 => {
 | |
|           assert.isFalse(authed2);
 | |
|           assert.isTrue(emitStub.called);
 | |
|           assert.equal(auth.status, Auth.STATUS.ERROR);
 | |
|           done();
 | |
|         });
 | |
|       });
 | |
|     });
 | |
| 
 | |
|     test('no event from non-authed to other status', done => {
 | |
|       fakeFetch.returns(Promise.resolve({status: 403}));
 | |
|       auth.authCheck().then(authed => {
 | |
|         assert.isFalse(authed);
 | |
|         assert.equal(auth.status, Auth.STATUS.NOT_AUTHED);
 | |
|         clock.tick(1000 * 10000);
 | |
|         fakeFetch.returns(Promise.resolve({status: 204}));
 | |
|         const emitStub = sinon.stub();
 | |
|         gerritEventEmitter.emit = emitStub;
 | |
|         auth.authCheck().then(authed2 => {
 | |
|           assert.isTrue(authed2);
 | |
|           assert.isFalse(emitStub.called);
 | |
|           assert.equal(auth.status, Auth.STATUS.AUTHED);
 | |
|           done();
 | |
|         });
 | |
|       });
 | |
|     });
 | |
| 
 | |
|     test('no event from non-authed to other status', done => {
 | |
|       fakeFetch.returns(Promise.resolve({status: 403}));
 | |
|       auth.authCheck().then(authed => {
 | |
|         assert.isFalse(authed);
 | |
|         assert.equal(auth.status, Auth.STATUS.NOT_AUTHED);
 | |
|         clock.tick(1000 * 10000);
 | |
|         fakeFetch.returns(Promise.reject(new Error('random error')));
 | |
|         const emitStub = sinon.stub();
 | |
|         gerritEventEmitter.emit = emitStub;
 | |
|         auth.authCheck().then(authed2 => {
 | |
|           assert.isFalse(authed2);
 | |
|           assert.isFalse(emitStub.called);
 | |
|           assert.equal(auth.status, Auth.STATUS.ERROR);
 | |
|           done();
 | |
|         });
 | |
|       });
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   suite('default (xsrf token header)', () => {
 | |
|     setup(() => {
 | |
|       sandbox.stub(window, 'fetch').returns(Promise.resolve({ok: true}));
 | |
|     });
 | |
| 
 | |
|     test('GET', done => {
 | |
|       auth.fetch('/url', {bar: 'bar'}).then(() => {
 | |
|         const [url, options] = fetch.lastCall.args;
 | |
|         assert.equal(url, '/url');
 | |
|         assert.equal(options.credentials, 'same-origin');
 | |
|         done();
 | |
|       });
 | |
|     });
 | |
| 
 | |
|     test('POST', done => {
 | |
|       sandbox.stub(auth, '_getCookie')
 | |
|           .withArgs('XSRF_TOKEN')
 | |
|           .returns('foobar');
 | |
|       auth.fetch('/url', {method: 'POST'}).then(() => {
 | |
|         const [url, options] = fetch.lastCall.args;
 | |
|         assert.equal(url, '/url');
 | |
|         assert.equal(options.credentials, 'same-origin');
 | |
|         assert.equal(options.headers.get('X-Gerrit-Auth'), 'foobar');
 | |
|         done();
 | |
|       });
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   suite('cors (access token)', () => {
 | |
|     setup(() => {
 | |
|       sandbox.stub(window, 'fetch').returns(Promise.resolve({ok: true}));
 | |
|     });
 | |
| 
 | |
|     let getToken;
 | |
| 
 | |
|     const makeToken = opt_accessToken => {
 | |
|       return {
 | |
|         access_token: opt_accessToken || 'zbaz',
 | |
|         expires_at: new Date(Date.now() + 10e8).getTime(),
 | |
|       };
 | |
|     };
 | |
| 
 | |
|     setup(() => {
 | |
|       getToken = sandbox.stub();
 | |
|       getToken.returns(Promise.resolve(makeToken()));
 | |
|       auth.setup(getToken);
 | |
|     });
 | |
| 
 | |
|     test('base url support', done => {
 | |
|       const baseUrl = 'http://foo';
 | |
|       sandbox.stub(BaseUrlBehavior, 'getBaseUrl').returns(baseUrl);
 | |
|       auth.fetch(baseUrl + '/url', {bar: 'bar'}).then(() => {
 | |
|         const [url] = fetch.lastCall.args;
 | |
|         assert.equal(url, 'http://foo/a/url?access_token=zbaz');
 | |
|         done();
 | |
|       });
 | |
|     });
 | |
| 
 | |
|     test('fetch not signed in', done => {
 | |
|       getToken.returns(Promise.resolve());
 | |
|       auth.fetch('/url', {bar: 'bar'}).then(() => {
 | |
|         const [url, options] = fetch.lastCall.args;
 | |
|         assert.equal(url, '/url');
 | |
|         assert.equal(options.bar, 'bar');
 | |
|         assert.equal(Object.keys(options.headers).length, 0);
 | |
|         done();
 | |
|       });
 | |
|     });
 | |
| 
 | |
|     test('fetch signed in', done => {
 | |
|       auth.fetch('/url', {bar: 'bar'}).then(() => {
 | |
|         const [url, options] = fetch.lastCall.args;
 | |
|         assert.equal(url, '/a/url?access_token=zbaz');
 | |
|         assert.equal(options.bar, 'bar');
 | |
|         done();
 | |
|       });
 | |
|     });
 | |
| 
 | |
|     test('getToken calls are cached', done => {
 | |
|       Promise.all([
 | |
|         auth.fetch('/url-one'), auth.fetch('/url-two')]).then(() => {
 | |
|         assert.equal(getToken.callCount, 1);
 | |
|         done();
 | |
|       });
 | |
|     });
 | |
| 
 | |
|     test('getToken refreshes token', done => {
 | |
|       sandbox.stub(auth, '_isTokenValid');
 | |
|       auth._isTokenValid
 | |
|           .onFirstCall().returns(true)
 | |
|           .onSecondCall()
 | |
|           .returns(false)
 | |
|           .onThirdCall()
 | |
|           .returns(true);
 | |
|       auth.fetch('/url-one')
 | |
|           .then(() => {
 | |
|             getToken.returns(Promise.resolve(makeToken('bzzbb')));
 | |
|             return auth.fetch('/url-two');
 | |
|           })
 | |
|           .then(() => {
 | |
|             const [[firstUrl], [secondUrl]] = fetch.args;
 | |
|             assert.equal(firstUrl, '/a/url-one?access_token=zbaz');
 | |
|             assert.equal(secondUrl, '/a/url-two?access_token=bzzbb');
 | |
|             done();
 | |
|           });
 | |
|     });
 | |
| 
 | |
|     test('signed in token error falls back to anonymous', done => {
 | |
|       getToken.returns(Promise.resolve('rubbish'));
 | |
|       auth.fetch('/url', {bar: 'bar'}).then(() => {
 | |
|         const [url, options] = fetch.lastCall.args;
 | |
|         assert.equal(url, '/url');
 | |
|         assert.equal(options.bar, 'bar');
 | |
|         done();
 | |
|       });
 | |
|     });
 | |
| 
 | |
|     test('_isTokenValid', () => {
 | |
|       assert.isFalse(auth._isTokenValid());
 | |
|       assert.isFalse(auth._isTokenValid({}));
 | |
|       assert.isFalse(auth._isTokenValid({access_token: 'foo'}));
 | |
|       assert.isFalse(auth._isTokenValid({
 | |
|         access_token: 'foo',
 | |
|         expires_at: Date.now()/1000 - 1,
 | |
|       }));
 | |
|       assert.isTrue(auth._isTokenValid({
 | |
|         access_token: 'foo',
 | |
|         expires_at: Date.now()/1000 + 1,
 | |
|       }));
 | |
|     });
 | |
| 
 | |
|     test('HTTP PUT with content type', done => {
 | |
|       const originalOptions = {
 | |
|         method: 'PUT',
 | |
|         headers: new Headers({'Content-Type': 'mail/pigeon'}),
 | |
|       };
 | |
|       auth.fetch('/url', originalOptions).then(() => {
 | |
|         assert.isTrue(getToken.called);
 | |
|         const [url, options] = fetch.lastCall.args;
 | |
|         assert.include(url, '$ct=mail%2Fpigeon');
 | |
|         assert.include(url, '$m=PUT');
 | |
|         assert.include(url, 'access_token=zbaz');
 | |
|         assert.equal(options.method, 'POST');
 | |
|         assert.equal(options.headers.get('Content-Type'), 'text/plain');
 | |
|         done();
 | |
|       });
 | |
|     });
 | |
| 
 | |
|     test('HTTP PUT without content type', done => {
 | |
|       const originalOptions = {
 | |
|         method: 'PUT',
 | |
|       };
 | |
|       auth.fetch('/url', originalOptions).then(() => {
 | |
|         assert.isTrue(getToken.called);
 | |
|         const [url, options] = fetch.lastCall.args;
 | |
|         assert.include(url, '$ct=text%2Fplain');
 | |
|         assert.include(url, '$m=PUT');
 | |
|         assert.include(url, 'access_token=zbaz');
 | |
|         assert.equal(options.method, 'POST');
 | |
|         assert.equal(options.headers.get('Content-Type'), 'text/plain');
 | |
|         done();
 | |
|       });
 | |
|     });
 | |
|   });
 | |
| });
 | |
| </script>
 | 
