open-vault/ui/tests/integration/components/auth-jwt-test.js
Matthew Irish 0357790fb8
UI - jwt auth (#6188)
* fix default rendering of svg and allow plugins access to mount tune form

* add auth-jwt component

* add callback route, and allow it to be navigated to on load

* add jwt as a supported auth method

* use auth-jwt component and implement intial oidc flow

* allow wrapping un-authed requests

* pass redirect_url and properly redirect with the wrapped token

* popup for login

* center popup window and move to localStorage events for cross window communication because of IE11

* access window via a getter on the auth-form component

* show OIDC provider name on the button

* fetch default role on render of the auth-jwt component

* simplify auth-form template

* style callback page

* refetch auth_url when path changes for auth-jwt component

* fix glimmer error on alias metadata, and add back popup-metadata component

* fix link in metadata page

* add logo-edition component and remove use of partial for logo svg

* render oidc callback template on the loading page if we're going there

* add docs icon and change timeout on the auth form

* move OIDC auth specific things to auth-jwt component

* start to add branded buttons for OIDC providers

* add google button

* finish branded buttons

* update glyph for error messages

* update tests for auth screen not showing tabs, add adapter tests and new auth jwt tests

* start auth-jwt tests

* simplify auth-jwt

* remove negative top margin on AlertInline

* only preventDefault if there's an event

* fill out tests

* sort out some naming

* feedback on templates and styles

* clear error when starting OIDC auth and call for new auth_url

* also allow 'oidc' as the auth method type

* handle namespaces with OIDC auth

* review feedback

* use new getters in popup-metadata
2019-02-14 09:39:19 -06:00

248 lines
7.5 KiB
JavaScript

import { next, later, run } from '@ember/runloop';
import EmberObject, { computed } from '@ember/object';
import Evented from '@ember/object/evented';
import { resolve } from 'rsvp';
import Service from '@ember/service';
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { render, settled } from '@ember/test-helpers';
import hbs from 'htmlbars-inline-precompile';
import sinon from 'sinon';
import Pretender from 'pretender';
import { create } from 'ember-cli-page-object';
import form from '../../pages/components/auth-jwt';
import { ERROR_WINDOW_CLOSED, ERROR_MISSING_PARAMS } from 'vault/components/auth-jwt';
const component = create(form);
const fakeWindow = EmberObject.extend(Evented, {
init() {
this._super(...arguments);
this.__proto__.on('close', () => {
this.set('closed', true);
});
},
screen: computed(function() {
return {
height: 600,
width: 500,
};
}),
localStorage: computed(function() {
return {
removeItem: sinon.stub(),
};
}),
closed: false,
});
fakeWindow.reopen({
open() {
return fakeWindow.create();
},
close() {
fakeWindow.prototype.trigger('close');
},
});
const OIDC_AUTH_RESPONSE = {
auth: {
client_token: 'token',
},
};
const routerStub = Service.extend({
urlFor() {
return 'http://example.com';
},
});
const renderIt = async (context, path) => {
let handler = (data, e) => {
if (e && e.preventDefault) e.preventDefault();
};
let fake = fakeWindow.create();
sinon.spy(fake, 'open');
context.set('window', fake);
context.set('handler', sinon.spy(handler));
context.set('roleName', '');
context.set('selectedAuthPath', path);
await render(hbs`
<AuthJwt
@window={{window}}
@roleName={{roleName}}
@selectedAuthPath={{selectedAuthPath}}
@onError={{action (mut error)}}
@onLoading={{action (mut isLoading)}}
@onToken={{action (mut token)}}
@onNamespace={{action (mut namespace)}}
@onSelectedAuth={{action (mut selectedAuth)}}
@onSubmit={{action handler}}
@onRoleName={{action (mut roleName)}}
/>
`);
};
module('Integration | Component | auth jwt', function(hooks) {
setupRenderingTest(hooks);
hooks.beforeEach(function() {
this.owner.register('service:router', routerStub);
this.server = new Pretender(function() {
this.get('/v1/auth/:path/oidc/callback', request => {
return [200, { 'Content-Type': 'application/json' }, JSON.stringify(OIDC_AUTH_RESPONSE)];
});
this.post('/v1/auth/:path/oidc/auth_url', request => {
let body = JSON.parse(request.requestBody);
if (body.role === 'test') {
return [
200,
{ 'Content-Type': 'application/json' },
JSON.stringify({
data: {
auth_url: 'http://example.com',
},
}),
];
}
if (body.role === 'okta') {
return [
200,
{ 'Content-Type': 'application/json' },
JSON.stringify({
data: {
auth_url: 'http://okta.com',
},
}),
];
}
return [400, { 'Content-Type': 'application/json' }, JSON.stringify({ errors: ['nope'] })];
});
});
});
hooks.afterEach(function() {
this.server.shutdown();
});
test('it renders the yield', async function(assert) {
await render(hbs`<AuthJwt @onSubmit={{action (mut submit)}}>Hello!</AuthJwt>`);
assert.equal(component.yieldContent, 'Hello!', 'yields properly');
});
test('jwt: it renders', async function(assert) {
await renderIt(this);
assert.ok(component.jwtPresent, 'renders jwt field');
assert.ok(component.rolePresent, 'renders jwt field');
assert.equal(this.server.handledRequests.length, 0, 'no requests made when there is no path set');
this.set('selectedAuthPath', 'foo');
await settled();
assert.equal(
this.server.handledRequests[0].url,
'/v1/auth/foo/oidc/auth_url',
'requests when path is set'
);
});
test('jwt: it calls passed action on login', async function(assert) {
await renderIt(this);
await component.login();
assert.ok(this.handler.calledOnce);
});
test('oidc: test role: it renders', async function(assert) {
await renderIt(this);
this.set('selectedAuthPath', 'foo');
await component.role('test');
assert.notOk(component.jwtPresent, 'does not show jwt input for OIDC type login');
assert.equal(component.loginButtonText, 'Sign in with OIDC Provider');
await component.role('okta');
// 1 for initial render, 1 for each time role changed = 3
assert.equal(this.server.handledRequests.length, 3, 'fetches the auth_url when the path changes');
assert.equal(component.loginButtonText, 'Sign in with Okta', 'recognizes auth methods with certain urls');
});
test('oidc: it calls window.open popup window on login', async function(assert) {
await renderIt(this);
this.set('selectedAuthPath', 'foo');
await component.role('test');
component.login();
next(() => {
run.cancelTimers();
let call = this.window.open.getCall(0);
assert.deepEqual(
call.args,
[
'http://example.com',
'vaultOIDCWindow',
'width=500,height=600,resizable,scrollbars=yes,top=0,left=0',
],
'called with expected args'
);
});
});
test('oidc: it calls error handler when popup is closed', async function(assert) {
await renderIt(this);
this.set('selectedAuthPath', 'foo');
await component.role('test');
component.login();
next(async () => {
this.window.close();
await settled();
assert.equal(this.error, ERROR_WINDOW_CLOSED, 'calls onError with error string');
});
});
test('oidc: storage event fires with wrong key', async function(assert) {
await renderIt(this);
this.set('selectedAuthPath', 'foo');
await component.role('test');
component.login();
next(async () => {
run.cancelTimers();
this.window.trigger('storage', { key: 'wrongThing' });
assert.equal(this.window.localStorage.removeItem.callCount, 0, 'never callse removeItem');
});
});
test('oidc: storage event fires with correct key, wrong params', async function(assert) {
await renderIt(this);
this.set('selectedAuthPath', 'foo');
await component.role('test');
component.login();
// need next tick here to let ec tasks set up
next(async () => {
this.window.trigger('storage', { key: 'oidcState', newValue: JSON.stringify({}) });
await settled();
assert.equal(this.window.localStorage.removeItem.callCount, 1, 'calls removeItem');
assert.equal(this.error, ERROR_MISSING_PARAMS, 'calls onError with params missing error');
});
});
test('oidc: storage event fires with correct key, correct params', async function(assert) {
await renderIt(this);
this.set('selectedAuthPath', 'foo');
await component.role('test');
component.login();
// need next tick here to let ec tasks set up
next(async () => {
this.window.trigger('storage', {
key: 'oidcState',
newValue: JSON.stringify({
path: 'foo',
state: 'state',
code: 'code',
}),
});
await settled();
assert.equal(this.selectedAuth, 'token', 'calls onSelectedAuth with token');
assert.equal(this.token, 'token', 'calls onToken with token');
assert.ok(this.handler.calledOnce, 'calls the onSubmit handler');
});
});
});