Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OIDC Logout Bug #14545

Merged
merged 3 commits into from
Mar 18, 2022
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions changelog/14545.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:bug
ui: Fixes issue with correct auth method not selected when logging out from OIDC or JWT methods
```
37 changes: 24 additions & 13 deletions ui/app/components/auth-form.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,18 @@ export default Component.extend(DEFAULTS, {
this.setProperties(DEFAULTS);
},

getAuthBackend(type) {
const { wrappedToken, methods, selectedAuth, selectedAuthIsPath: keyIsPath } = this;
const selected = type || selectedAuth;
if (!methods && !wrappedToken) {
return {};
}
if (keyIsPath) {
return methods.findBy('path', selected);
}
return BACKENDS.findBy('type', selected);
},

selectedAuthIsPath: match('selectedAuth', /\/$/),
selectedAuthBackend: computed(
'wrappedToken',
Expand All @@ -118,14 +130,7 @@ export default Component.extend(DEFAULTS, {
'selectedAuth',
'selectedAuthIsPath',
function () {
hashishaw marked this conversation as resolved.
Show resolved Hide resolved
let { wrappedToken, methods, selectedAuth, selectedAuthIsPath: keyIsPath } = this;
if (!methods && !wrappedToken) {
return {};
}
if (keyIsPath) {
return methods.findBy('path', selectedAuth);
}
return BACKENDS.findBy('type', selectedAuth);
return this.getAuthBackend();
}
),

Expand Down Expand Up @@ -206,12 +211,15 @@ export default Component.extend(DEFAULTS, {
showLoading: or('isLoading', 'authenticate.isRunning', 'fetchMethods.isRunning', 'unwrapToken.isRunning'),

authenticate: task(
waitFor(function* (backendType, data) {
let clusterId = this.cluster.id;
waitFor(function* (backend, data) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do like that the previous name was clear that this is the backend type, not the backend name. Could we add a comment or keep the old name here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure I can change it back. The auth service authenticate method has it named backend so I thought I would align it that way.

const {
selectedAuth,
cluster: { id: clusterId },
} = this;
try {
this.delayAuthMessageReminder.perform();
const authResponse = yield this.auth.authenticate({ clusterId, backend: backendType, data });
this.onSuccess(authResponse, backendType, data);
const authResponse = yield this.auth.authenticate({ clusterId, backend, data, selectedAuth });
this.onSuccess(authResponse, backend, data);
} catch (e) {
this.set('loading', false);
if (!this.auth.mfaError) {
Expand Down Expand Up @@ -245,7 +253,10 @@ export default Component.extend(DEFAULTS, {
this.setProperties({
error: null,
});
let backend = this.selectedAuthBackend || {};
// if callback from oidc or jwt we have a token at this point
let backend = ['oidc', 'jwt'].includes(this.selectedAuth)
? this.getAuthBackend('token')
: this.selectedAuthBackend || {};
let backendMeta = BACKENDS.find(
(b) => (b.type || '').toLowerCase() === (backend.type || '').toLowerCase()
);
Expand Down
1 change: 0 additions & 1 deletion ui/app/components/auth-jwt.js
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,6 @@ export default Component.extend({
return this.handleOIDCError(e);
}
let token = resp.auth.client_token;
this.onSelectedAuth('token');
this.onToken(token);
yield this.onSubmit();
}),
Expand Down
11 changes: 8 additions & 3 deletions ui/app/services/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -358,7 +358,7 @@ export default Service.extend({
return {};
},

async authenticate(/*{clusterId, backend, data}*/) {
async authenticate(/*{clusterId, backend, data, selectedAuth}*/) {
const [options] = arguments;
const adapter = this.clusterAdapter();

Expand Down Expand Up @@ -389,6 +389,8 @@ export default Service.extend({
},

async authSuccess(options, response) {
// persist selectedAuth to sessionStorage to rehydrate auth form on logout
sessionStorage.setItem('selectedAuth', options.selectedAuth);
const authData = await this.persistAuthData(options, response, this.namespaceService.path);
await this.permissions.getPaths.perform();
return authData;
Expand All @@ -407,8 +409,11 @@ export default Service.extend({
},

getAuthType() {
if (!this.authData) return;
return this.authData.backend.type;
// check sessionStorage first
const selectedAuth = sessionStorage.getItem('selectedAuth');
if (selectedAuth) return selectedAuth;
// fallback to authData which discerns backend type from token
return this.authData ? this.authData.backend.type : null;
},

deleteCurrentToken() {
Expand Down
1 change: 0 additions & 1 deletion ui/app/templates/components/auth-form.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,6 @@
@onToken={{action (mut this.token)}}
@namespace={{this.namespace}}
@onNamespace={{action (mut this.namespace)}}
@onSelectedAuth={{action (mut this.selectedAuth)}}
@onSubmit={{action "doSubmit"}}
@onRoleName={{action (mut this.roleName)}}
@roleName={{this.roleName}}
Expand Down
42 changes: 42 additions & 0 deletions ui/tests/acceptance/logout-auth-method-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { module, test } from 'qunit';
import { setupApplicationTest } from 'ember-qunit';
import { click, visit, fillIn, settled } from '@ember/test-helpers';
import { setupMirage } from 'ember-cli-mirage/test-support';
import { fakeWindow, buildMessage } from '../helpers/oidc-window-stub';
import sinon from 'sinon';
import { later, run } from '@ember/runloop';

module('Acceptance | logout auth method', function (hooks) {
setupApplicationTest(hooks);
setupMirage(hooks);

hooks.beforeEach(function () {
this.openStub = sinon.stub(window, 'open').callsFake(() => fakeWindow.create());
});
hooks.afterEach(function () {
this.openStub.restore();
});

// coverage for bug where token was selected as auth method for oidc and jwt
test('it should populate oidc auth method on logout', async function (assert) {
this.server.post('/auth/oidc/oidc/auth_url', () => ({
data: { auth_url: 'http://example.com' },
}));
this.server.get('/auth/foo/oidc/callback', () => ({
auth: { client_token: 'root' },
}));
// ensure clean state
sessionStorage.removeItem('selectedAuth');
await visit('/vault/auth');
await fillIn('[data-test-select="auth-method"]', 'oidc');
later(() => run.cancelTimers(), 50);
await click('[data-test-auth-submit]');
window.postMessage(buildMessage().data, window.origin);
await settled();
await click('.nav-user-button button');
await click('#logout');
assert
.dom('[data-test-select="auth-method"]')
.hasValue('oidc', 'Previous auth method selected on logout');
});
});
33 changes: 33 additions & 0 deletions ui/tests/helpers/oidc-window-stub.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import EmberObject, { computed } from '@ember/object';
import Evented from '@ember/object/evented';

export const fakeWindow = EmberObject.extend(Evented, {
init() {
this._super(...arguments);
this.on('close', () => {
this.set('closed', true);
});
},
screen: computed(function () {
return {
height: 600,
width: 500,
};
}),
origin: 'https://my-vault.com',
closed: false,
open() {},
close() {},
});

export const buildMessage = (opts) => ({
isTrusted: true,
origin: 'https://my-vault.com',
data: {
source: 'oidc-callback',
path: 'foo',
state: 'state',
code: 'code',
},
...opts,
});
65 changes: 6 additions & 59 deletions ui/tests/integration/components/auth-jwt-test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import { run } from '@ember/runloop';
import EmberObject, { computed } from '@ember/object';
import Evented from '@ember/object/evented';
import Service from '@ember/service';
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
Expand All @@ -12,38 +10,19 @@ import { resolve } from 'rsvp';
import { create } from 'ember-cli-page-object';
import form from '../../pages/components/auth-jwt';
import { ERROR_WINDOW_CLOSED, ERROR_MISSING_PARAMS, ERROR_JWT_LOGIN } from 'vault/components/auth-jwt';
import { fakeWindow, buildMessage } from '../../helpers/oidc-window-stub';

const component = create(form);
const windows = [];
const buildMessage = (opts) => ({
isTrusted: true,
origin: 'https://my-vault.com',
data: {},
...opts,
});
const fakeWindow = EmberObject.extend(Evented, {

fakeWindow.reopen({
init() {
this._super(...arguments);
this.on('close', () => {
this.set('closed', true);
});
windows.push(this);
},
screen: computed(function () {
return {
height: 600,
width: 500,
};
}),
origin: 'https://my-vault.com',
closed: false,
});

fakeWindow.reopen({
open() {
return fakeWindow.create();
},

close() {
windows.forEach((w) => w.trigger('close'));
},
Expand Down Expand Up @@ -227,17 +206,7 @@ module('Integration | Component | auth jwt', function (hooks) {
await waitUntil(() => {
return this.openSpy.calledOnce;
});
this.window.trigger(
'message',
buildMessage({
data: {
source: 'oidc-callback',
path: 'foo',
state: 'state',
code: 'code',
},
})
);
this.window.trigger('message', buildMessage());
await settled();
assert.equal(this.selectedAuth, 'token', 'calls onSelectedAuth with token');
assert.equal(this.token, 'token', 'calls onToken with token');
Expand All @@ -252,18 +221,7 @@ module('Integration | Component | auth jwt', function (hooks) {
await waitUntil(() => {
return this.openSpy.calledOnce;
});
this.window.trigger(
'message',
buildMessage({
origin: 'http://hackerz.com',
data: {
source: 'oidc-callback',
path: 'foo',
state: 'state',
code: 'code',
},
})
);
this.window.trigger('message', buildMessage({ origin: 'http://hackerz.com' }));
run.cancelTimers();
await settled();
assert.notOk(this.handler.called, 'should not call the submit handler');
Expand All @@ -277,18 +235,7 @@ module('Integration | Component | auth jwt', function (hooks) {
await waitUntil(() => {
return this.openSpy.calledOnce;
});
this.window.trigger(
'message',
buildMessage({
isTrusted: false,
data: {
source: 'oidc-callback',
path: 'foo',
state: 'state',
code: 'code',
},
})
);
this.window.trigger('message', buildMessage({ isTrusted: false }));
run.cancelTimers();
await settled();
assert.notOk(this.handler.called, 'should not call the submit handler');
Expand Down