feat: Add oidc status change dropdown to auth method actions
This commit is contained in:
9 files changed
+227
-5
No files matched your search
@@ -3,7 +3,7 @@ import { fragment } from 'ember-data-model-fragments/attributes';
|
||||
import { equal } from '@ember/object/computed';
|
||||
|
||||
/**
|
||||
* Enum ptions per auth method type and field.
|
||||
* Enum options per auth method type and field.
|
||||
*/
|
||||
export const options = {
|
||||
oidc: {
|
||||
@@ -27,6 +27,8 @@ export const options = {
|
||||
};
|
||||
|
||||
export default class AuthMethodModel extends GeneratedAuthMethodModel {
|
||||
STATES_ENUM = options.oidc.state;
|
||||
|
||||
// =attributes
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import Ember from 'ember';
|
||||
import config from '../config/environment';
|
||||
import { authHandler, deauthHandler } from './route-handlers/auth';
|
||||
import { deauthHandler } from './route-handlers/auth';
|
||||
import { pickRandomStatusString } from './factories/session';
|
||||
import { Response } from 'miragejs';
|
||||
import initializeMockIPC from './scenarios/ipc';
|
||||
@@ -71,6 +71,31 @@ export default function () {
|
||||
}
|
||||
return new Response(202);
|
||||
});
|
||||
this.post('/auth-methods/:idMethod', function (schema, request) {
|
||||
const idMethod = request.params.idMethod;
|
||||
const id = idMethod.split(':')[0];
|
||||
const method = idMethod.split(':')[1];
|
||||
|
||||
if (method === 'authenticate') {
|
||||
schema.authHandler.call(this, schema, request);
|
||||
return;
|
||||
}
|
||||
|
||||
const attrs = this.normalizedRequestAttrs();
|
||||
const authMethods = schema.authMethods;
|
||||
const authMethod = authMethods.find(id);
|
||||
let updatedAttrs = {};
|
||||
|
||||
if (method === 'change-state') {
|
||||
updatedAttrs = {
|
||||
attributes: {
|
||||
state: attrs.attributes.state,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return authMethod.update(updatedAttrs);
|
||||
});
|
||||
|
||||
// Auth Method Accounts
|
||||
this.get(
|
||||
@@ -107,7 +132,7 @@ export default function () {
|
||||
);
|
||||
|
||||
// Authenticate/deauthenticate routes
|
||||
this.post('/auth-methods/:id_method', authHandler);
|
||||
// this.post('/auth-methods/:id_method', authHandler);
|
||||
this.post('/scopes/:id_method', deauthHandler);
|
||||
|
||||
// IAM : Users
|
||||
|
||||
@@ -109,7 +109,7 @@ const commandHandlers = {
|
||||
|
||||
export function authHandler({ scopes, authMethods }, request) {
|
||||
const payload = JSON.parse(request.requestBody);
|
||||
const [, id] = request.params.id_method.match(/(?<id>.[^:]*)/);
|
||||
const [, id] = request.params.idMethod.match(/(?<id>.[^:]*)/);
|
||||
const { command } = payload;
|
||||
const authMethod = authMethods.find(id);
|
||||
const scope = scopes.find(authMethod.scopeId);
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { authHandler } from '../route-handlers/auth';
|
||||
|
||||
export default function (server) {
|
||||
// Scope resources
|
||||
|
||||
@@ -50,4 +52,8 @@ export default function (server) {
|
||||
server.createList('target', 2, { scope }, 'withRandomHostSets');
|
||||
server.createList('session', 4, { scope }, 'withAssociations');
|
||||
});
|
||||
|
||||
// Authenticate route handler
|
||||
// TODO: Better way to pass-in auth handler
|
||||
server.schema.authHandler = authHandler;
|
||||
}
|
||||
@@ -38,6 +38,7 @@ auth-method:
|
||||
delete: Delete Auth Method
|
||||
make-primary: Make Primary
|
||||
remove-as-primary: Remove as Primary
|
||||
change-state: State
|
||||
notifications:
|
||||
make-primary-success: Auth method successfully elected to primary.
|
||||
remove-as-primary-success: Auth method successfully removed as primary.
|
||||
|
||||
@@ -8,3 +8,9 @@ connection:
|
||||
success:
|
||||
description: "Local proxy address ({protocol}): {address}:{port}"
|
||||
primary: Primary
|
||||
auth-method:
|
||||
oidc:
|
||||
states:
|
||||
inactive: Inactive
|
||||
active-private: Active Private
|
||||
active-public: Active Public
|
||||
@@ -1,4 +1,6 @@
|
||||
import Route from '@ember/routing/route';
|
||||
import { action } from '@ember/object';
|
||||
import { notifySuccess, notifyError } from 'core/decorators/notify';
|
||||
|
||||
export default class ScopesScopeAuthMethodsAuthMethodRoute extends Route {
|
||||
// =methods
|
||||
@@ -36,4 +38,18 @@ export default class ScopesScopeAuthMethodsAuthMethodRoute extends Route {
|
||||
outlet: 'actions',
|
||||
});
|
||||
}
|
||||
|
||||
// =actions
|
||||
|
||||
/**
|
||||
* Update state of OIDC auth method
|
||||
* @param {string} state - "inactive", "active-private", "active-public"
|
||||
*/
|
||||
@action
|
||||
@notifyError(({ message }) => message)
|
||||
@notifySuccess('notifications.save-success')
|
||||
async changeState(state) {
|
||||
const model = this.modelFor('scopes.scope.auth-methods.auth-method');
|
||||
await model.changeState(state);
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,35 @@
|
||||
{{#if @model.isOIDC}}
|
||||
<Rose::Dropdown
|
||||
@text='{{
|
||||
concat (t 'resources.auth-method.actions.change-state')
|
||||
' : '
|
||||
(t (concat 'states.auth-method.oidc.states.' @model.attributes.state))
|
||||
}}'
|
||||
@dropdownRight={{true}}
|
||||
as |dropdown|
|
||||
>
|
||||
<dropdown.section @title={{t 'resources.auth-method.actions.change-state'}}>
|
||||
<Rose::Form as |form|>
|
||||
<form.radioGroup
|
||||
@name='theme'
|
||||
@selectedValue={{@model.attributes.state}}
|
||||
@changed={{route-action 'changeState'}}
|
||||
as |radioGroup|
|
||||
>
|
||||
{{#each @model.STATES_ENUM as |state|}}
|
||||
<dropdown.item>
|
||||
<radioGroup.radio
|
||||
@label={{t (concat 'states.auth-method.oidc.states.' state)}}
|
||||
@value={{state}}
|
||||
/>
|
||||
</dropdown.item>
|
||||
{{/each}}
|
||||
</form.radioGroup>
|
||||
</Rose::Form>
|
||||
</dropdown.section>
|
||||
</Rose::Dropdown>
|
||||
{{/if}}
|
||||
|
||||
<Rose::Dropdown
|
||||
@text='{{t 'actions.manage'}}'
|
||||
@dropdownRight={{true}}
|
||||
@@ -35,4 +67,4 @@
|
||||
>
|
||||
{{t 'resources.auth-method.actions.delete'}}
|
||||
</dropdown.button>
|
||||
</Rose::Dropdown>
|
||||
</Rose::Dropdown>
|
||||
@@ -0,0 +1,134 @@
|
||||
import { module, test } from 'qunit';
|
||||
import {
|
||||
visit,
|
||||
currentURL,
|
||||
click,
|
||||
// fillIn,
|
||||
find,
|
||||
// findAll,
|
||||
} from '@ember/test-helpers';
|
||||
import { setupApplicationTest } from 'ember-qunit';
|
||||
import setupMirage from 'ember-cli-mirage/test-support/setup-mirage';
|
||||
import a11yAudit from 'ember-a11y-testing/test-support/audit';
|
||||
import { Response } from 'miragejs';
|
||||
import {
|
||||
authenticateSession,
|
||||
// These are left here intentionally for future reference.
|
||||
//currentSession,
|
||||
//invalidateSession,
|
||||
} from 'ember-simple-auth/test-support';
|
||||
|
||||
module('Acceptance | auth methods | oidc', function (hooks) {
|
||||
setupApplicationTest(hooks);
|
||||
setupMirage(hooks);
|
||||
|
||||
const instances = {
|
||||
scopes: {
|
||||
global: null,
|
||||
org: null,
|
||||
},
|
||||
authMethod: null,
|
||||
};
|
||||
const urls = {
|
||||
orgScope: null,
|
||||
authMethods: null,
|
||||
authMethod: null,
|
||||
};
|
||||
|
||||
hooks.beforeEach(function () {
|
||||
authenticateSession({});
|
||||
instances.scopes.global = this.server.create('scope', { id: 'global' });
|
||||
instances.scopes.org = this.server.create('scope', {
|
||||
type: 'org',
|
||||
scope: { id: 'global', type: 'global' },
|
||||
});
|
||||
instances.authMethod = this.server.create('auth-method', {
|
||||
scope: instances.scopes.org,
|
||||
type: 'oidc',
|
||||
});
|
||||
|
||||
urls.orgScope = `/scopes/${instances.scopes.org.id}`;
|
||||
urls.authMethods = `${urls.orgScope}/auth-methods`;
|
||||
urls.authMethod = `${urls.authMethods}/${instances.authMethod.id}`;
|
||||
});
|
||||
|
||||
test('visiting oidc auth method', async function (assert) {
|
||||
assert.expect(1);
|
||||
await visit(urls.authMethod);
|
||||
await a11yAudit();
|
||||
assert.equal(currentURL(), urls.authMethod);
|
||||
});
|
||||
|
||||
test('can view oidc state', async function(assert) {
|
||||
assert.expect(1);
|
||||
await visit(urls.authMethod);
|
||||
await click('.rose-layout-page-actions .rose-dropdown-trigger');
|
||||
assert.equal(
|
||||
find('.rose-dropdown[open] input[aria-checked=true]').value,
|
||||
instances.authMethod.attributes.state
|
||||
);
|
||||
});
|
||||
|
||||
test('can update oidc state', async function(assert) {
|
||||
assert.expect(1);
|
||||
const updateValue = 'inactive';
|
||||
await visit(urls.authMethod);
|
||||
await click('.rose-layout-page-actions .rose-dropdown-trigger');
|
||||
await click(`.rose-dropdown[open] input[value="${updateValue}"]`);
|
||||
const authMethod = this.server.db.authMethods.find(instances.authMethod.id);
|
||||
assert.equal(authMethod.attributes.state, updateValue);
|
||||
});
|
||||
|
||||
test('can update oidc state to active-private', async function(assert) {
|
||||
assert.expect(1);
|
||||
const updateValue = 'active-private';
|
||||
await visit(urls.authMethod);
|
||||
await click('.rose-layout-page-actions .rose-dropdown-trigger');
|
||||
await click(`.rose-dropdown[open] input[value="${updateValue}"]`);
|
||||
const authMethod = this.server.db.authMethods.find(instances.authMethod.id);
|
||||
assert.equal(authMethod.attributes.state, updateValue);
|
||||
});
|
||||
|
||||
test('can update oidc state to active-public', async function(assert) {
|
||||
assert.expect(1);
|
||||
// Update default 'active-public' state to inactive
|
||||
instances.authMethod.attributes.state = 'inactive';
|
||||
const updateValue = 'active-public';
|
||||
await visit(urls.authMethod);
|
||||
await click('.rose-layout-page-actions .rose-dropdown-trigger');
|
||||
await click(`.rose-dropdown[open] input[value="${updateValue}"]`);
|
||||
const authMethod = this.server.db.authMethods.find(instances.authMethod.id);
|
||||
assert.equal(authMethod.attributes.state, updateValue);
|
||||
});
|
||||
|
||||
test('errors are displayed when state update fails', async function (assert) {
|
||||
assert.expect(2);
|
||||
const newState = 'inactive';
|
||||
await visit(urls.authMethod);
|
||||
await click('.rose-layout-page-actions .rose-dropdown-trigger');
|
||||
// Load page before mocking update
|
||||
this.server.post('/auth-methods/:idMethod', () => {
|
||||
return new Response(
|
||||
400,
|
||||
{},
|
||||
{
|
||||
status: 400,
|
||||
code: 'error',
|
||||
message: 'Sorry!',
|
||||
}
|
||||
);
|
||||
});
|
||||
await click(`.rose-dropdown[open] input[value="${newState}"]`);
|
||||
const authMethod = this.server.db.authMethods.find(instances.authMethod.id);
|
||||
assert.notEqual(
|
||||
newState,
|
||||
authMethod.attributes.state,
|
||||
'Auth method state is not be updated.'
|
||||
);
|
||||
assert.ok(
|
||||
find('[role="alert"]').textContent.trim(),
|
||||
'Sorry!',
|
||||
'Displays error message.'
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user