mirror of
https://github.com/hashicorp/vault.git
synced 2026-05-05 04:16:31 +02:00
UI: Fix direct login link for auth mounts (#30800)
* fix typos, check for supported auth method and render direct link in display view too * add namespace * linebreak * add tests
This commit is contained in:
parent
8442bcac7b
commit
d82d82ef80
@ -11,10 +11,10 @@
|
||||
{{#each @model.tuneAttrs as |attr|}}
|
||||
{{#if (not (includes attr.name @model.userLockoutConfig.modelAttrs))}}
|
||||
<FormField data-test-field @attr={{attr}} @model={{@model}} />
|
||||
{{#if (eq attr.name "config.listingVisibility")}}
|
||||
{{#if (and (eq attr.name "config.listingVisibility") @model.directLoginLink)}}
|
||||
<div class="has-top-margin-negative-s has-bottom-margin-l is-flex-center">
|
||||
<Hds::Text::Body @tag="p" @color="faint">UI login link:</Hds::Text::Body>
|
||||
<Hds::Copy::Snippet @textToCopy={{this.getLoginLink}} />
|
||||
<Hds::Copy::Snippet @textToCopy={{@model.directLoginLink}} />
|
||||
</div>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
||||
@ -65,8 +65,4 @@ export default class AuthConfigOptions extends AuthConfigComponent {
|
||||
this.router.transitionTo('vault.cluster.access.methods').followRedirects();
|
||||
this.flashMessages.success('The configuration was saved successfully.');
|
||||
}
|
||||
|
||||
get getLoginLink() {
|
||||
return `${window.origin}/ui/vault/auth?with=${encodeURIComponent(this.args.model.path)}`;
|
||||
}
|
||||
}
|
||||
|
||||
@ -154,7 +154,7 @@ export default class AuthPage extends Component<Args> {
|
||||
get initialAuthType(): string {
|
||||
// First, prioritize canceledMfaAuth since it's set by user interaction.
|
||||
// Next, "type" from direct link since the URL query param overrides any login settings.
|
||||
// Then, first tab which is either the first backup method or visible mount tab.
|
||||
// Then, first tab which is either the default method, first backup method or first visible mount tab.
|
||||
// Finally, fallback to the most recently used auth method in localStorage.
|
||||
// Token is the default otherwise.
|
||||
const directLinkType = this.args.directLinkData?.type;
|
||||
@ -192,7 +192,7 @@ export default class AuthPage extends Component<Args> {
|
||||
const defaultType = loginSettings?.defaultType;
|
||||
const backupTypes = loginSettings?.backupTypes;
|
||||
|
||||
// If a default is not set, render backup methods as the initial view
|
||||
// If a default type is not set, render backup methods as the initial view
|
||||
const preferredTypes = defaultType ? [defaultType] : backupTypes;
|
||||
let defaultView;
|
||||
if (preferredTypes) {
|
||||
|
||||
@ -13,6 +13,7 @@ import lazyCapabilities from 'vault/macros/lazy-capabilities';
|
||||
import { action } from '@ember/object';
|
||||
import { camelize } from '@ember/string';
|
||||
import { WHITESPACE_WARNING } from 'vault/utils/forms/validators';
|
||||
import { supportedTypes } from 'vault/utils/supported-login-methods';
|
||||
|
||||
const validations = {
|
||||
path: [
|
||||
@ -27,7 +28,9 @@ const validations = {
|
||||
|
||||
@withModelValidations(validations)
|
||||
export default class AuthMethodModel extends Model {
|
||||
@service namespace;
|
||||
@service store;
|
||||
@service version;
|
||||
|
||||
@belongsTo('mount-config', { async: false, inverse: null }) config; // one-to-none that replaces former fragment
|
||||
@hasMany('auth-config', { polymorphic: true, inverse: 'backend', async: false }) authConfigs;
|
||||
@ -40,11 +43,22 @@ export default class AuthMethodModel extends Model {
|
||||
get methodType() {
|
||||
return this.type.replace(/^ns_/, '');
|
||||
}
|
||||
|
||||
get icon() {
|
||||
const authMethods = allMethods().find((backend) => backend.type === this.methodType);
|
||||
|
||||
return authMethods?.glyph || 'users';
|
||||
}
|
||||
|
||||
get directLoginLink() {
|
||||
const ns = this.namespace.path;
|
||||
const nsQueryParam = ns ? `namespace=${encodeURIComponent(ns)}&` : '';
|
||||
const isSupported = supportedTypes(this.version.isEnterprise).includes(this.methodType);
|
||||
return isSupported
|
||||
? `${window.origin}/ui/vault/auth?${nsQueryParam}with=${encodeURIComponent(this.path)}`
|
||||
: '';
|
||||
}
|
||||
|
||||
@attr('string', {
|
||||
editType: 'textarea',
|
||||
})
|
||||
|
||||
@ -4,6 +4,11 @@
|
||||
}}
|
||||
|
||||
<div class="box is-fullwidth is-sideless is-paddingless is-marginless">
|
||||
{{#if @model.directLoginLink}}
|
||||
<InfoTableRow @alwaysRender={{true}} @label="UI login link">
|
||||
<Hds::Copy::Snippet @textToCopy={{@model.directLoginLink}} />
|
||||
</InfoTableRow>
|
||||
{{/if}}
|
||||
{{#each @model.attrs as |attr|}}
|
||||
{{#if (eq attr.type "object")}}
|
||||
<InfoTableRow
|
||||
|
||||
@ -65,7 +65,7 @@
|
||||
{{else}}
|
||||
<EmptyState
|
||||
@title="No UI login settings yet"
|
||||
@message="Login rules can be used to select default and back up login methods and customize which methods display in the web UI login form. Available to be created via the CLI or HTTP API."
|
||||
@message="Login settings can be used to customize which methods display in the web UI login form by setting a default and back up login methods. Available to be created via the CLI or HTTP API."
|
||||
>
|
||||
{{! TODO: update href with tutorial link }}
|
||||
{{! <Hds::Link::Standalone @icon="arrow-right" @iconPosition="trailing" @text="Learn more" @href="/" /> }}
|
||||
|
||||
@ -59,6 +59,9 @@ module('Acceptance | settings/auth/enable', function (hooks) {
|
||||
.dom(GENERAL.infoRowValue('Default Lease TTL'))
|
||||
.hasText('1 month 1 day', 'shows system default TTL');
|
||||
assert.dom(GENERAL.infoRowValue('Max Lease TTL')).hasText('1 month 1 day', 'shows the proper max TTL');
|
||||
assert
|
||||
.dom(GENERAL.infoRowValue('UI login link'))
|
||||
.doesNotExist('Login link does not render for unsupported methods');
|
||||
|
||||
// check edit form TTL values
|
||||
await click('[data-test-configure-link]');
|
||||
@ -68,4 +71,18 @@ module('Acceptance | settings/auth/enable', function (hooks) {
|
||||
// cleanup
|
||||
await runCmd(deleteAuthCmd(path));
|
||||
});
|
||||
|
||||
test('it renders direct login link for supported method', async function (assert) {
|
||||
const path = `oidc-config-${this.uid}`;
|
||||
const type = 'oidc';
|
||||
await visit('/vault/settings/auth/enable');
|
||||
await mountBackend(type, path);
|
||||
await click(GENERAL.breadcrumbAtIdx(1));
|
||||
assert
|
||||
.dom(GENERAL.infoRowValue('UI login link'))
|
||||
.hasText(`${window.origin}/ui/vault/auth?with=${path}%2F`);
|
||||
|
||||
// cleanup
|
||||
await runCmd(deleteAuthCmd(path));
|
||||
});
|
||||
});
|
||||
|
||||
@ -0,0 +1,44 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import { module, test } from 'qunit';
|
||||
import { setupRenderingTest } from 'ember-qunit';
|
||||
import { render } from '@ember/test-helpers';
|
||||
import hbs from 'htmlbars-inline-precompile';
|
||||
import { GENERAL } from 'vault/tests/helpers/general-selectors';
|
||||
|
||||
module('Integration | Component | auth-method/configuration', function (hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
|
||||
hooks.beforeEach(function () {
|
||||
this.store = this.owner.lookup('service:store');
|
||||
this.createModel = (path, type) => {
|
||||
this.model = this.store.createRecord('auth-method', { path, type });
|
||||
this.model.set('config', this.store.createRecord('mount-config'));
|
||||
};
|
||||
this.renderComponent = async () => await render(hbs`<AuthMethod::Configuration @model={{this.model}} />`);
|
||||
});
|
||||
|
||||
test('it renders direct link for supported method', async function (assert) {
|
||||
this.createModel('token/', 'token');
|
||||
await this.renderComponent();
|
||||
assert.dom(GENERAL.infoRowValue('UI login link')).hasText(`${window.origin}/ui/vault/auth?with=token%2F`);
|
||||
});
|
||||
|
||||
test('it does not render direct link for unsupported method', async function (assert) {
|
||||
this.createModel('my-approle/', 'approle');
|
||||
await this.renderComponent();
|
||||
assert.dom(GENERAL.infoRowValue('UI login link')).doesNotExist();
|
||||
});
|
||||
|
||||
test('it renders direct link if within a namespace', async function (assert) {
|
||||
this.owner.lookup('service:namespace').set('path', 'foo/bar');
|
||||
this.createModel('token/', 'token');
|
||||
await this.renderComponent();
|
||||
assert
|
||||
.dom(GENERAL.infoRowValue('UI login link'))
|
||||
.hasText(`${window.origin}/ui/vault/auth?namespace=foo%2Fbar&with=token%2F`);
|
||||
});
|
||||
});
|
||||
Loading…
x
Reference in New Issue
Block a user