UI: remove references to comma separation for string array edit types (#20163)

* remove intercepting helpText

* add subtext directly to StringList input component

* update tests and add coverage for new openapi-attrs util

* update test

* add warning validation to input

* lol is this right i dont know go

* literally no idea what im doing

* add Description to display attrs struct

* update struct comment

* add descriptions to remaining go fields

* add missing comma

* remaining commas..."

* add description to display attrs

* update tests

* update tests

* add changelog;

* Update ui/app/utils/openapi-to-attrs.js

* update tests following backend changes

* clearly name variable

* format files

* no longer need to test for modified tooltip since coming from backend now
This commit is contained in:
claire bontempo 2023-04-19 09:16:30 -07:00 committed by GitHub
parent d115fda4e0
commit 5f64520dac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 183 additions and 102 deletions

View File

@ -87,6 +87,9 @@ auth_type is ec2 or inferred_entity_type is ec2_instance.`,
given instance IDs. Can be a list or comma-separated string of EC2 instance given instance IDs. Can be a list or comma-separated string of EC2 instance
IDs. This is only applicable when auth_type is ec2 or inferred_entity_type is IDs. This is only applicable when auth_type is ec2 or inferred_entity_type is
ec2_instance.`, ec2_instance.`,
DisplayAttrs: &framework.DisplayAttributes{
Description: "If set, defines a constraint on the EC2 instances to have one of the given instance IDs. A list of EC2 instance IDs. This is only applicable when auth_type is ec2 or inferred_entity_type is ec2_instance.",
},
}, },
"resolve_aws_unique_ids": { "resolve_aws_unique_ids": {
Type: framework.TypeBool, Type: framework.TypeBool,

View File

@ -77,6 +77,9 @@ Must be x509 PEM encoded.`,
Type: framework.TypeCommaStringSlice, Type: framework.TypeCommaStringSlice,
Description: `A comma-separated list of OCSP server addresses. If unset, the OCSP server is determined Description: `A comma-separated list of OCSP server addresses. If unset, the OCSP server is determined
from the AuthorityInformationAccess extension on the certificate being inspected.`, from the AuthorityInformationAccess extension on the certificate being inspected.`,
DisplayAttrs: &framework.DisplayAttributes{
Description: "A list of OCSP server addresses. If unset, the OCSP server is determined from the AuthorityInformationAccess extension on the certificate being inspected.",
},
}, },
"ocsp_fail_open": { "ocsp_fail_open": {
Type: framework.TypeBool, Type: framework.TypeBool,
@ -95,7 +98,8 @@ At least one must exist in either the Common Name or SANs. Supports globbing.
This parameter is deprecated, please use allowed_common_names, allowed_dns_sans, This parameter is deprecated, please use allowed_common_names, allowed_dns_sans,
allowed_email_sans, allowed_uri_sans.`, allowed_email_sans, allowed_uri_sans.`,
DisplayAttrs: &framework.DisplayAttributes{ DisplayAttrs: &framework.DisplayAttributes{
Group: "Constraints", Group: "Constraints",
Description: "A list of names. At least one must exist in either the Common Name or SANs. Supports globbing. This parameter is deprecated, please use allowed_common_names, allowed_dns_sans, allowed_email_sans, allowed_uri_sans.",
}, },
}, },
@ -104,7 +108,8 @@ allowed_email_sans, allowed_uri_sans.`,
Description: `A comma-separated list of names. Description: `A comma-separated list of names.
At least one must exist in the Common Name. Supports globbing.`, At least one must exist in the Common Name. Supports globbing.`,
DisplayAttrs: &framework.DisplayAttributes{ DisplayAttrs: &framework.DisplayAttributes{
Group: "Constraints", Group: "Constraints",
Description: "A list of names. At least one must exist in the Common Name. Supports globbing.",
}, },
}, },
@ -113,8 +118,9 @@ At least one must exist in the Common Name. Supports globbing.`,
Description: `A comma-separated list of DNS names. Description: `A comma-separated list of DNS names.
At least one must exist in the SANs. Supports globbing.`, At least one must exist in the SANs. Supports globbing.`,
DisplayAttrs: &framework.DisplayAttributes{ DisplayAttrs: &framework.DisplayAttributes{
Name: "Allowed DNS SANs", Name: "Allowed DNS SANs",
Group: "Constraints", Group: "Constraints",
Description: "A list of DNS names. At least one must exist in the SANs. Supports globbing.",
}, },
}, },
@ -123,8 +129,9 @@ At least one must exist in the SANs. Supports globbing.`,
Description: `A comma-separated list of Email Addresses. Description: `A comma-separated list of Email Addresses.
At least one must exist in the SANs. Supports globbing.`, At least one must exist in the SANs. Supports globbing.`,
DisplayAttrs: &framework.DisplayAttributes{ DisplayAttrs: &framework.DisplayAttributes{
Name: "Allowed Email SANs", Name: "Allowed Email SANs",
Group: "Constraints", Group: "Constraints",
Description: "A list of Email Addresses. At least one must exist in the SANs. Supports globbing.",
}, },
}, },
@ -133,8 +140,9 @@ At least one must exist in the SANs. Supports globbing.`,
Description: `A comma-separated list of URIs. Description: `A comma-separated list of URIs.
At least one must exist in the SANs. Supports globbing.`, At least one must exist in the SANs. Supports globbing.`,
DisplayAttrs: &framework.DisplayAttributes{ DisplayAttrs: &framework.DisplayAttributes{
Name: "Allowed URI SANs", Name: "Allowed URI SANs",
Group: "Constraints", Group: "Constraints",
Description: "A list of URIs. At least one must exist in the SANs. Supports globbing.",
}, },
}, },
@ -143,7 +151,8 @@ At least one must exist in the SANs. Supports globbing.`,
Description: `A comma-separated list of Organizational Units names. Description: `A comma-separated list of Organizational Units names.
At least one must exist in the OU field.`, At least one must exist in the OU field.`,
DisplayAttrs: &framework.DisplayAttributes{ DisplayAttrs: &framework.DisplayAttributes{
Group: "Constraints", Group: "Constraints",
Description: "A list of Organizational Units names. At least one must exist in the OU field.",
}, },
}, },
@ -152,6 +161,9 @@ At least one must exist in the OU field.`,
Description: `A comma-separated string or array of extensions Description: `A comma-separated string or array of extensions
formatted as "oid:value". Expects the extension value to be some type of ASN1 encoded string. formatted as "oid:value". Expects the extension value to be some type of ASN1 encoded string.
All values much match. Supports globbing on "value".`, All values much match. Supports globbing on "value".`,
DisplayAttrs: &framework.DisplayAttributes{
Description: "A list of extensions formatted as 'oid:value'. Expects the extension value to be some type of ASN1 encoded string. All values much match. Supports globbing on 'value'.",
},
}, },
"allowed_metadata_extensions": { "allowed_metadata_extensions": {
@ -160,6 +172,9 @@ All values much match. Supports globbing on "value".`,
Upon successful authentication, these extensions will be added as metadata if they are present Upon successful authentication, these extensions will be added as metadata if they are present
in the certificate. The metadata key will be the string consisting of the oid numbers in the certificate. The metadata key will be the string consisting of the oid numbers
separated by a dash (-) instead of a dot (.) to allow usage in ACL templates.`, separated by a dash (-) instead of a dot (.) to allow usage in ACL templates.`,
DisplayAttrs: &framework.DisplayAttributes{
Description: "A list of OID extensions. Upon successful authentication, these extensions will be added as metadata if they are present in the certificate. The metadata key will be the string consisting of the OID numbers separated by a dash (-) instead of a dot (.) to allow usage in ACL templates.",
},
}, },
"display_name": { "display_name": {

View File

@ -52,6 +52,9 @@ func pathGroups(b *backend) *framework.Path {
"policies": { "policies": {
Type: framework.TypeCommaStringSlice, Type: framework.TypeCommaStringSlice,
Description: "Comma-separated list of policies associated to the group.", Description: "Comma-separated list of policies associated to the group.",
DisplayAttrs: &framework.DisplayAttributes{
Description: "A list of policies associated to the group.",
},
}, },
}, },

View File

@ -53,11 +53,17 @@ func pathUsers(b *backend) *framework.Path {
"groups": { "groups": {
Type: framework.TypeCommaStringSlice, Type: framework.TypeCommaStringSlice,
Description: "Comma-separated list of additional groups associated with the user.", Description: "Comma-separated list of additional groups associated with the user.",
DisplayAttrs: &framework.DisplayAttributes{
Description: "A list of additional groups associated with the user.",
},
}, },
"policies": { "policies": {
Type: framework.TypeCommaStringSlice, Type: framework.TypeCommaStringSlice,
Description: "Comma-separated list of policies associated with the user.", Description: "Comma-separated list of policies associated with the user.",
DisplayAttrs: &framework.DisplayAttributes{
Description: "A list of policies associated with the user.",
},
}, },
}, },

View File

@ -52,6 +52,9 @@ func pathGroups(b *backend) *framework.Path {
"policies": { "policies": {
Type: framework.TypeCommaStringSlice, Type: framework.TypeCommaStringSlice,
Description: "Comma-separated list of policies associated to the group.", Description: "Comma-separated list of policies associated to the group.",
DisplayAttrs: &framework.DisplayAttributes{
Description: "A list of policies associated to the group.",
},
}, },
}, },

View File

@ -44,9 +44,10 @@ func pathConfig(b *backend) *framework.Path {
"unregistered_user_policies": { "unregistered_user_policies": {
Type: framework.TypeString, Type: framework.TypeString,
Default: "", Default: "",
Description: "Comma-separated list of policies to grant upon successful RADIUS authentication of an unregisted user (default: empty)", Description: "Comma-separated list of policies to grant upon successful RADIUS authentication of an unregistered user (default: empty)",
DisplayAttrs: &framework.DisplayAttributes{ DisplayAttrs: &framework.DisplayAttributes{
Name: "Policies for unregistered users", Name: "Policies for unregistered users",
Description: "List of policies to grant upon successful RADIUS authentication of an unregistered user (default: empty)",
}, },
}, },
"dial_timeout": { "dial_timeout": {

View File

@ -53,6 +53,9 @@ func pathUsers(b *backend) *framework.Path {
"policies": { "policies": {
Type: framework.TypeCommaStringSlice, Type: framework.TypeCommaStringSlice,
Description: "Comma-separated list of policies associated to the user.", Description: "Comma-separated list of policies associated to the user.",
DisplayAttrs: &framework.DisplayAttributes{
Description: "A list of policies associated to the user.",
},
}, },
}, },

View File

@ -36,6 +36,9 @@ func pathUserPolicies(b *backend) *framework.Path {
"token_policies": { "token_policies": {
Type: framework.TypeCommaStringSlice, Type: framework.TypeCommaStringSlice,
Description: "Comma-separated list of policies", Description: "Comma-separated list of policies",
DisplayAttrs: &framework.DisplayAttributes{
Description: "A list of policies that will apply to the generated token for this user.",
},
}, },
}, },

3
changelog/20163.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:improvement
ui: adds warning for commas in stringArray inputs and updates tooltip help text to remove references to comma separation
```

View File

@ -207,6 +207,11 @@ type DisplayAttributes struct {
// Name is the name of the field suitable as a label or documentation heading. // Name is the name of the field suitable as a label or documentation heading.
Name string `json:"name,omitempty"` Name string `json:"name,omitempty"`
// Description of the field that renders as tooltip help text beside the label (name) in the UI.
// This may be used to replace descriptions that reference comma separation but correspond
// to UI inputs where only arrays are valid. For example params with Type: framework.TypeCommaStringSlice
Description string `json:"description,omitempty"`
// Value is a sample value to display for this field. This may be used // Value is a sample value to display for this field. This may be used
// to indicate a default value, but it is for display only and completely separate // to indicate a default value, but it is for display only and completely separate
// from any Default member handling. // from any Default member handling.

View File

@ -78,8 +78,9 @@ func TokenFields() map[string]*framework.FieldSchema {
Type: framework.TypeCommaStringSlice, Type: framework.TypeCommaStringSlice,
Description: `Comma separated string or JSON list of CIDR blocks. If set, specifies the blocks of IP addresses which are allowed to use the generated token.`, Description: `Comma separated string or JSON list of CIDR blocks. If set, specifies the blocks of IP addresses which are allowed to use the generated token.`,
DisplayAttrs: &framework.DisplayAttributes{ DisplayAttrs: &framework.DisplayAttributes{
Name: "Generated Token's Bound CIDRs", Name: "Generated Token's Bound CIDRs",
Group: "Tokens", Group: "Tokens",
Description: "A list of CIDR blocks. If set, specifies the blocks of IP addresses which are allowed to use the generated token.",
}, },
}, },
@ -123,8 +124,9 @@ func TokenFields() map[string]*framework.FieldSchema {
Type: framework.TypeCommaStringSlice, Type: framework.TypeCommaStringSlice,
Description: "Comma-separated list of policies", Description: "Comma-separated list of policies",
DisplayAttrs: &framework.DisplayAttributes{ DisplayAttrs: &framework.DisplayAttributes{
Name: "Generated Token's Policies", Name: "Generated Token's Policies",
Group: "Tokens", Group: "Tokens",
Description: "A list of policies that will apply to the generated token for this user.",
}, },
}, },

View File

@ -53,16 +53,6 @@ export default Component.extend({
), ),
init() { init() {
this._super(...arguments); this._super(...arguments);
this.model.fieldGroups.forEach((element) => {
// overwriting the helpText for Token Polices.
// HelpText from the backend says add a comma separated list, which works on the CLI but not here on the UI.
// This effects TLS Certificates, Userpass, and Kubernetes. https://github.com/hashicorp/vault/issues/10346
if (element.Tokens) {
element.Tokens.find((attr) => attr.name === 'tokenPolicies').options.helpText =
'Add policies that will apply to the generated token for this user. One policy per row.';
}
});
if (this.mode === 'edit') { if (this.mode === 'edit') {
// For validation to work in edit mode, // For validation to work in edit mode,
// reconstruct the model values from field group // reconstruct the model values from field group

View File

@ -59,7 +59,7 @@ export default class PkiCertificateBaseModel extends Model {
@attr('string', { @attr('string', {
label: 'Subject Alternative Names (SANs)', label: 'Subject Alternative Names (SANs)',
subText: subText:
'The requested Subject Alternative Names; if email protection is enabled for the role, this may contain email addresses. Add one per row.', 'The requested Subject Alternative Names; if email protection is enabled for the role, this may contain email addresses.',
editType: 'stringArray', editType: 'stringArray',
}) })
altNames; altNames;
@ -67,20 +67,18 @@ export default class PkiCertificateBaseModel extends Model {
// SANs below are editType: stringArray from openApi // SANs below are editType: stringArray from openApi
@attr('string', { @attr('string', {
label: 'IP Subject Alternative Names (IP SANs)', label: 'IP Subject Alternative Names (IP SANs)',
subText: 'Only valid if the role allows IP SANs (which is the default). Add one per row.', subText: 'Only valid if the role allows IP SANs (which is the default).',
}) })
ipSans; ipSans;
@attr('string', { @attr('string', {
label: 'URI Subject Alternative Names (URI SANs)', label: 'URI Subject Alternative Names (URI SANs)',
subText: subText: 'If any requested URIs do not match role policy, the entire request will be denied.',
'If any requested URIs do not match role policy, the entire request will be denied. Add one per row.',
}) })
uriSans; uriSans;
@attr('string', { @attr('string', {
subText: subText: 'Requested other SANs with the format <oid>;UTF8:<utf8 string value> for each entry.',
'Requested other SANs with the format <oid>;UTF8:<utf8 string value> for each entry. Add one per row.',
}) })
otherSans; otherSans;

View File

@ -107,7 +107,7 @@ export default class PkiIssuerModel extends Model {
@attr('string', { @attr('string', {
subText: subText:
'The URL values for the Issuing Certificate field. These are different URLs for the same resource, and should be added individually, not in a comma-separated list.', 'The URL values for the Issuing Certificate field; these are different URLs for the same resource.',
editType: 'stringArray', editType: 'stringArray',
}) })
issuingCertificates; issuingCertificates;

View File

@ -159,7 +159,7 @@ export default class PkiRoleModel extends Model {
/* Overriding OpenApi Domain handling options */ /* Overriding OpenApi Domain handling options */
@attr({ @attr({
label: 'Allowed domains', label: 'Allowed domains',
subText: 'Specifies the domains this role is allowed to issue certificates for. Add one item per row.', subText: 'Specifies the domains this role is allowed to issue certificates for.',
editType: 'stringArray', editType: 'stringArray',
}) })
allowedDomains; allowedDomains;
@ -196,7 +196,7 @@ export default class PkiRoleModel extends Model {
/* Overriding API Policy identifier option */ /* Overriding API Policy identifier option */
@attr({ @attr({
label: 'Policy identifiers', label: 'Policy identifiers',
subText: 'A comma-separated string or list of policy object identifiers (OIDs). Add one per row. ', subText: 'A list of policy object identifiers (OIDs).',
editType: 'stringArray', editType: 'stringArray',
}) })
policyIdentifiers; policyIdentifiers;
@ -213,7 +213,7 @@ export default class PkiRoleModel extends Model {
@attr({ @attr({
label: 'URI Subject Alternative Names (URI SANs)', label: 'URI Subject Alternative Names (URI SANs)',
subText: 'Defines allowed URI Subject Alternative Names. Add one item per row', subText: 'Defines allowed URI Subject Alternative Names.',
editType: 'stringArray', editType: 'stringArray',
docLink: '/vault/docs/concepts/policies', docLink: '/vault/docs/concepts/policies',
}) })
@ -229,7 +229,7 @@ export default class PkiRoleModel extends Model {
@attr({ @attr({
label: 'Other SANs', label: 'Other SANs',
subText: 'Defines allowed custom OID/UTF8-string SANs. Add one item per row.', subText: 'Defines allowed custom OID/UTF8-string SANs.',
editType: 'stringArray', editType: 'stringArray',
}) })
allowedOtherSans; allowedOtherSans;

View File

@ -20,7 +20,7 @@ export default class PkiUrlsModel extends Model {
@attr({ @attr({
label: 'Issuing certificates', label: 'Issuing certificates',
subText: subText:
'The URL values for the Issuing Certificate field. These are different URLs for the same resource, and should be added individually, not in a comma-separated list.', 'The URL values for the Issuing Certificate field; these are different URLs for the same resource.',
showHelpText: false, showHelpText: false,
editType: 'stringArray', editType: 'stringArray',
}) })

View File

@ -341,6 +341,10 @@ fieldset.form-fieldset {
border: none; border: none;
} }
.has-warning-border {
border: 1px solid $yellow-500;
}
.has-error-border, .has-error-border,
select.has-error-border { select.has-error-border {
border: 1px solid $red-500; border: 1px solid $red-500;

View File

@ -16,12 +16,23 @@ export const expandOpenApiProps = function (props) {
if (deprecated === true) { if (deprecated === true) {
continue; continue;
} }
let { name, value, group, sensitive, editType } = prop['x-vault-displayAttrs'] || {}; let {
name,
value,
group,
sensitive,
editType,
description: displayDescription,
} = prop['x-vault-displayAttrs'] || {};
if (type === 'integer') { if (type === 'integer') {
type = 'number'; type = 'number';
} }
if (displayDescription) {
description = displayDescription;
}
editType = editType || type; editType = editType || type;
if (format === 'seconds') { if (format === 'seconds') {
@ -50,8 +61,8 @@ export const expandOpenApiProps = function (props) {
attrDefn.sensitive = true; attrDefn.sensitive = true;
} }
//only set a label if we have one from OpenAPI // only set a label if we have one from OpenAPI
//otherwise the propName will be humanized by the form-field component // otherwise the propName will be humanized by the form-field component
if (name) { if (name) {
attrDefn.label = name; attrDefn.label = name;
} }

View File

@ -209,12 +209,11 @@
<StringList <StringList
data-test-input={{@attr.name}} data-test-input={{@attr.name}}
@label={{this.labelString}} @label={{this.labelString}}
@warning={{@attr.options.warning}}
@helpText={{if this.showHelpText @attr.options.helpText}} @helpText={{if this.showHelpText @attr.options.helpText}}
@inputValue={{get @model this.valuePath}} @inputValue={{get @model this.valuePath}}
@onChange={{this.setAndBroadcast}} @onChange={{this.setAndBroadcast}}
@attrName={{@attr.name}} @attrName={{@attr.name}}
@subText={{@attr.options.subText}} @subText="{{@attr.options.subText}} Add one item per row."
/> />
{{else if (eq @attr.options.sensitive true)}} {{else if (eq @attr.options.sensitive true)}}
{{! Masked Input }} {{! Masked Input }}

View File

@ -9,8 +9,8 @@
{{#if @label}} {{#if @label}}
<label class="is-label" data-test-string-list-label="true"> <label class="is-label" data-test-string-list-label="true">
{{@label}} {{@label}}
{{#if this.helpText}} {{#if @helpText}}
<InfoTooltip>{{this.helpText}}</InfoTooltip> <InfoTooltip>{{@helpText}}</InfoTooltip>
{{/if}} {{/if}}
</label> </label>
{{#if @subText}} {{#if @subText}}
@ -19,15 +19,12 @@
</p> </p>
{{/if}} {{/if}}
{{/if}} {{/if}}
{{#if @warning}}
<AlertBanner @type="warning" @message={{@warning}} />
{{/if}}
{{#each this.inputList as |data index|}} {{#each this.inputList as |data index|}}
<div class="field is-grouped" data-test-string-list-row={{index}}> <div class="field is-grouped" data-test-string-list-row={{index}}>
<div class="control is-expanded"> <div class="control is-expanded">
<Textarea <Textarea
data-test-string-list-input={{index}} data-test-string-list-input={{index}}
class="input" class="input {{if (includes index this.indicesWithComma) 'has-warning-border'}}"
@value={{data.value}} @value={{data.value}}
name={{concat this.elementId "-" index}} name={{concat this.elementId "-" index}}
id={{concat this.elementId "-" index}} id={{concat this.elementId "-" index}}
@ -56,6 +53,16 @@
</button> </button>
{{/if}} {{/if}}
</div> </div>
{{#if (includes index this.indicesWithComma)}}
<Icon class="is-flex-v-centered has-text-highlight" @name="alert-triangle-fill" />
{{/if}}
</div> </div>
{{/each}} {{/each}}
{{#if this.indicesWithComma}}
<AlertInline
@type="warning"
@message="Input contains a comma. Please separate values into individual rows."
@isMarginless={{true}}
/>
{{/if}}
</div> </div>

View File

@ -9,6 +9,7 @@ import autosize from 'autosize';
import { action } from '@ember/object'; import { action } from '@ember/object';
import { set } from '@ember/object'; import { set } from '@ember/object';
import { next } from '@ember/runloop'; import { next } from '@ember/runloop';
import { tracked } from '@glimmer/tracking';
/** /**
* @module StringList * @module StringList
@ -20,7 +21,6 @@ import { next } from '@ember/runloop';
* @param {string} label - Text displayed in the header above all the inputs. * @param {string} label - Text displayed in the header above all the inputs.
* @param {function} onChange - Function called when any of the inputs change. * @param {function} onChange - Function called when any of the inputs change.
* @param {string} inputValue - A string or an array of strings. * @param {string} inputValue - A string or an array of strings.
* @param {string} warning - Text displayed as a warning.
* @param {string} helpText - Text displayed as a tooltip. * @param {string} helpText - Text displayed as a tooltip.
* @param {string} type=array - Optional type for inputValue. * @param {string} type=array - Optional type for inputValue.
* @param {string} attrName - We use this to check the type so we can modify the tooltip content. * @param {string} attrName - We use this to check the type so we can modify the tooltip content.
@ -28,6 +28,8 @@ import { next } from '@ember/runloop';
*/ */
export default class StringList extends Component { export default class StringList extends Component {
@tracked indicesWithComma = [];
constructor() { constructor() {
super(...arguments); super(...arguments);
@ -75,14 +77,6 @@ export default class StringList extends Component {
return inputs; return inputs;
} }
get helpText() {
if (this.args.attrName === 'tokenBoundCidrs') {
return 'Specifies the blocks of IP addresses which are allowed to use the generated token. One entry per row.';
} else {
return this.args.helpText;
}
}
@action @action
autoSize(element) { autoSize(element) {
autosize(element.querySelector('textarea')); autosize(element.querySelector('textarea'));
@ -95,10 +89,14 @@ export default class StringList extends Component {
@action @action
inputChanged(idx, event) { inputChanged(idx, event) {
if (event.target.value.includes(',') && !this.indicesWithComma.includes(idx)) {
this.indicesWithComma.pushObject(idx);
}
if (!event.target.value.includes(',')) this.indicesWithComma.removeObject(idx);
const inputObj = this.inputList.objectAt(idx); const inputObj = this.inputList.objectAt(idx);
const onChange = this.args.onChange;
set(inputObj, 'value', event.target.value); set(inputObj, 'value', event.target.value);
onChange(this.toVal()); this.args.onChange(this.toVal());
} }
@action @action
@ -111,9 +109,8 @@ export default class StringList extends Component {
@action @action
removeInput(idx) { removeInput(idx) {
const onChange = this.args.onChange;
const inputs = this.inputList; const inputs = this.inputList;
inputs.removeObject(inputs.objectAt(idx)); inputs.removeObject(inputs.objectAt(idx));
onChange(this.toVal()); this.args.onChange(this.toVal());
} }
} }

View File

@ -39,7 +39,6 @@
{{else if (eq group "Additional subject fields")}} {{else if (eq group "Additional subject fields")}}
These fields provide more information about the client to which the certificate belongs. These fields provide more information about the client to which the certificate belongs.
{{/if}} {{/if}}
Add one item per row.
</p> </p>
{{#each fields as |fieldName|}} {{#each fields as |fieldName|}}
{{#let (find-by "name" fieldName @model.allFields) as |attr|}} {{#let (find-by "name" fieldName @model.allFields) as |attr|}}

View File

@ -21,11 +21,11 @@
<StringList <StringList
class="is-box-shadowless" class="is-box-shadowless"
data-test-input="extKeyUsageOids" data-test-input="extKeyUsageOids"
@label="Extended key usage oids" @label="Extended key usage OIDs"
@inputValue={{get @model "extKeyUsageOids"}} @inputValue={{get @model "extKeyUsageOids"}}
@onChange={{this.onStringListChange}} @onChange={{this.onStringListChange}}
@attrName="extKeyUsageOids" @attrName="extKeyUsageOids"
@subText="A list of extended key usage oids. Add one item per row." @subText="A list of extended key usage OIDs. Add one item per row."
@showHelpText={{false}} @showHelpText={{false}}
/> />
</div> </div>

View File

@ -4,16 +4,7 @@
*/ */
/* eslint qunit/no-conditional-assertions: "warn" */ /* eslint qunit/no-conditional-assertions: "warn" */
import { import { click, fillIn, settled, visit, triggerKeyEvent, find, waitUntil } from '@ember/test-helpers';
click,
fillIn,
settled,
visit,
triggerEvent,
triggerKeyEvent,
find,
waitUntil,
} from '@ember/test-helpers';
import { module, test } from 'qunit'; import { module, test } from 'qunit';
import { setupApplicationTest } from 'ember-qunit'; import { setupApplicationTest } from 'ember-qunit';
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid';
@ -80,17 +71,6 @@ module('Acceptance | auth backend list', function (hooks) {
await triggerKeyEvent('[data-test-input="username"]', 'keyup', 65); await triggerKeyEvent('[data-test-input="username"]', 'keyup', 65);
await fillIn('[data-test-textarea]', user2); await fillIn('[data-test-textarea]', user2);
await triggerKeyEvent('[data-test-textarea]', 'keyup', 65); await triggerKeyEvent('[data-test-textarea]', 'keyup', 65);
// test for modified helpText on generated token policies
await click('[data-test-toggle-group="Tokens"]');
const policyFormField = document.querySelector('[data-test-input="tokenPolicies"]');
const tooltipTrigger = policyFormField.querySelector('[data-test-tool-tip-trigger]');
await triggerEvent(tooltipTrigger, 'mouseenter');
assert
.dom('[data-test-info-tooltip-content]')
.hasText(
'Add policies that will apply to the generated token for this user. One policy per row.',
'Overwritten tooltip text displays in token form field.'
);
await click('[data-test-save-config="true"]'); await click('[data-test-save-config="true"]');

View File

@ -53,9 +53,7 @@ module('Integration | Component | pki-generate-root', function (hooks) {
await click(SELECTORS.additionalGroupToggle); await click(SELECTORS.additionalGroupToggle);
assert assert
.dom(SELECTORS.toggleGroupDescription) .dom(SELECTORS.toggleGroupDescription)
.hasText( .hasText('These fields provide more information about the client to which the certificate belongs.');
'These fields provide more information about the client to which the certificate belongs. Add one item per row.'
);
assert assert
.dom(`[data-test-group="Additional subject fields"] ${SELECTORS.formField}`) .dom(`[data-test-group="Additional subject fields"] ${SELECTORS.formField}`)
.exists({ count: 7 }, '7 form fields under Additional Fields toggle'); .exists({ count: 7 }, '7 form fields under Additional Fields toggle');
@ -64,7 +62,7 @@ module('Integration | Component | pki-generate-root', function (hooks) {
assert assert
.dom(SELECTORS.toggleGroupDescription) .dom(SELECTORS.toggleGroupDescription)
.hasText( .hasText(
'SAN fields are an extension that allow you specify additional host names (sites, IP addresses, common names, etc.) to be protected by a single certificate. Add one item per row.' 'SAN fields are an extension that allow you specify additional host names (sites, IP addresses, common names, etc.) to be protected by a single certificate.'
); );
assert assert
.dom(`[data-test-group="Subject Alternative Name (SAN) Options"] ${SELECTORS.formField}`) .dom(`[data-test-group="Subject Alternative Name (SAN) Options"] ${SELECTORS.formField}`)

View File

@ -5,7 +5,7 @@
import { module, test } from 'qunit'; import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit'; import { setupRenderingTest } from 'ember-qunit';
import { render, click, fillIn, triggerKeyEvent, triggerEvent } from '@ember/test-helpers'; import { render, click, fillIn, triggerKeyEvent } from '@ember/test-helpers';
import sinon from 'sinon'; import sinon from 'sinon';
import hbs from 'htmlbars-inline-precompile'; import hbs from 'htmlbars-inline-precompile';
@ -137,16 +137,4 @@ module('Integration | Component | string list', function (hooks) {
assert.dom('[data-test-string-list-input="0"]').hasValue('bar'); assert.dom('[data-test-string-list-input="0"]').hasValue('bar');
assert.dom('[data-test-string-list-input="1"]').hasValue(''); assert.dom('[data-test-string-list-input="1"]').hasValue('');
}); });
test('it replaces helpText if name is tokenBoundCidrs', async function (assert) {
assert.expect(1);
await render(hbs`<StringList @label={{'blah'}} @helpText={{'blah'}} @attrName={{'tokenBoundCidrs'}} />`);
const tooltipTrigger = document.querySelector('[data-test-tool-tip-trigger]');
await triggerEvent(tooltipTrigger, 'mouseenter');
assert
.dom('[data-test-info-tooltip-content]')
.hasText(
'Specifies the blocks of IP addresses which are allowed to use the generated token. One entry per row.'
);
});
}); });

View File

@ -6,6 +6,7 @@
import { attr } from '@ember-data/model'; import { attr } from '@ember-data/model';
import { expandOpenApiProps, combineAttributes, combineFieldGroups } from 'vault/utils/openapi-to-attrs'; import { expandOpenApiProps, combineAttributes, combineFieldGroups } from 'vault/utils/openapi-to-attrs';
import { module, test } from 'qunit'; import { module, test } from 'qunit';
import { camelize } from '@ember/string';
module('Unit | Util | OpenAPI Data Utilities', function () { module('Unit | Util | OpenAPI Data Utilities', function () {
const OPENAPI_RESPONSE_PROPS = { const OPENAPI_RESPONSE_PROPS = {
@ -145,6 +146,56 @@ module('Unit | Util | OpenAPI Data Utilities', function () {
const NEW_FIELDS = ['one', 'two', 'three']; const NEW_FIELDS = ['one', 'two', 'three'];
const OPENAPI_DESCRIPTIONS = {
token_bound_cidrs: {
type: 'array',
description:
'Comma separated string or JSON list of CIDR blocks. If set, specifies the blocks of IP addresses which are allowed to use the generated token.',
items: {
type: 'string',
},
'x-vault-displayAttrs': {
description:
'List of CIDR blocks. If set, specifies the blocks of IP addresses which are allowed to use the generated token.',
name: "Generated Token's Bound CIDRs",
group: 'Tokens',
},
},
blah_blah: {
type: 'array',
description: 'Comma-separated list of policies',
items: {
type: 'string',
},
'x-vault-displayAttrs': {
name: "Generated Token's Policies",
group: 'Tokens',
},
},
only_display_description: {
type: 'array',
items: {
type: 'string',
},
'x-vault-displayAttrs': {
description: 'Hello there, you look nice today',
},
},
};
const STRING_ARRAY_DESCRIPTIONS = {
token_bound_cidrs: {
helpText:
'List of CIDR blocks. If set, specifies the blocks of IP addresses which are allowed to use the generated token.',
},
blah_blah: {
helpText: 'Comma-separated list of policies',
},
only_display_description: {
helpText: 'Hello there, you look nice today',
},
};
test('it creates objects from OpenAPI schema props', function (assert) { test('it creates objects from OpenAPI schema props', function (assert) {
assert.expect(6); assert.expect(6);
const generatedProps = expandOpenApiProps(OPENAPI_RESPONSE_PROPS); const generatedProps = expandOpenApiProps(OPENAPI_RESPONSE_PROPS);
@ -229,4 +280,16 @@ module('Unit | Util | OpenAPI Data Utilities', function () {
assert.deepEqual(fieldGroups[groupName], expectedGroups[groupName], 'it incorporates all new fields'); assert.deepEqual(fieldGroups[groupName], expectedGroups[groupName], 'it incorporates all new fields');
} }
}); });
test('it uses the description from the display attrs block if it exists', async function (assert) {
assert.expect(3);
const generatedProps = expandOpenApiProps(OPENAPI_DESCRIPTIONS);
for (const propName in STRING_ARRAY_DESCRIPTIONS) {
assert.strictEqual(
generatedProps[camelize(propName)].helpText,
STRING_ARRAY_DESCRIPTIONS[propName].helpText,
`correctly updates helpText for ${propName}`
);
}
});
}); });