mirror of
https://github.com/hashicorp/vault.git
synced 2026-05-05 12:26:34 +02:00
Enable generated items for more auth methods (#7513)
* enable auth method item configuration in go code * properly parse and list generated items * make sure we only set name on attrs if a label comes from openAPI * correctly construct paths object for method index route * set sensitive property on password for userpass * remove debugger statements * pass method model to list route template to use paths on model for tabs * update tab generation in generated item list, undo enabling userpass users * enable openapi generated itams for certs and userpass, update ldap to no longer have action on list endpoint * add editType to DisplayAttributes, pull tokenutil fields into field group * show sensitive message for sensitive fields displayed in fieldGroupShow component * grab sensitive and editType fields from displayAttrs in openapi-to-attrs util * make sure we don't ask for paths for secret backends since that isn't setup yet * fix styling of sensitive text for fieldGroupShow component * update openapi-to-attrs util test to no longer include label by default, change debugger to console.err in path-help, remove dynamic ui auth methods from tab count test * properly log errors to the console * capitalize This value is sensitive... * get rid of extra padding on bottom of fieldgroupshow * make auth methods clickable and use new confirm ux * Update sdk/framework/path.go Co-Authored-By: Jim Kalafut <jkalafut@hashicorp.com> * Update sdk/framework/path.go Co-Authored-By: Jim Kalafut <jkalafut@hashicorp.com> * add whitespace * return intErr instead of err * uncomment out helpUrl because we need it * remove extra box class * use const instead of let * remove extra conditional since we already split the pathName later on * ensure we request the correct url when listing generated items * use const * link to list and show pages * remove dead code * show nested item name instead of id * add comments * show tooltip for text-file inputs * fix storybook * remove extra filter * add TODOs * add comments * comment out unused variables but leave them in function signature * only link to auth methods that can be fully managed in the ui * clean up comments * only render tooltip if there is helpText * rename id authMethodPath * remove optionsForQuery since we don't need it * add indentation * standardize ConfirmMessage and show model name instead of id when editing * standardize ConfirmMessage and show model name instead of id when editing * add comments * post to the correct updateUrl so we can edit users and groups * use pop instead of slice * add TODO for finding a better way to store ids * ensure ids are handled the same way on list and show pages; fix editing and deleting * add comment about difference between list and show urls * use model.id instead of name since we do not need it * remove dead code * ensure list pages have page headers * standardize using authMethodPath instead of method and remove dead code * i love indentation * remove more dead code * use new Confirm * show correct flash message when deleting an item * update flash message for creating and updating * use plus icon for creating group/user instead of an arrow
This commit is contained in:
parent
0272c964c1
commit
8f4530b904
@ -23,6 +23,10 @@ func pathListCerts(b *backend) *framework.Path {
|
||||
|
||||
HelpSynopsis: pathCertHelpSyn,
|
||||
HelpDescription: pathCertHelpDesc,
|
||||
DisplayAttrs: &framework.DisplayAttributes{
|
||||
Navigation: true,
|
||||
ItemType: "Certificate",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@ -39,6 +43,9 @@ func pathCerts(b *backend) *framework.Path {
|
||||
Type: framework.TypeString,
|
||||
Description: `The public certificate that should be trusted.
|
||||
Must be x509 PEM encoded.`,
|
||||
DisplayAttrs: &framework.DisplayAttributes{
|
||||
EditType: "file",
|
||||
},
|
||||
},
|
||||
|
||||
"allowed_names": &framework.FieldSchema{
|
||||
@ -47,36 +54,57 @@ Must be x509 PEM encoded.`,
|
||||
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.`,
|
||||
DisplayAttrs: &framework.DisplayAttributes{
|
||||
Group: "Constraints",
|
||||
},
|
||||
},
|
||||
|
||||
"allowed_common_names": &framework.FieldSchema{
|
||||
Type: framework.TypeCommaStringSlice,
|
||||
Description: `A comma-separated list of names.
|
||||
At least one must exist in the Common Name. Supports globbing.`,
|
||||
DisplayAttrs: &framework.DisplayAttributes{
|
||||
Group: "Constraints",
|
||||
},
|
||||
},
|
||||
|
||||
"allowed_dns_sans": &framework.FieldSchema{
|
||||
Type: framework.TypeCommaStringSlice,
|
||||
Description: `A comma-separated list of DNS names.
|
||||
At least one must exist in the SANs. Supports globbing.`,
|
||||
DisplayAttrs: &framework.DisplayAttributes{
|
||||
Name: "Allowed DNS SANs",
|
||||
Group: "Constraints",
|
||||
},
|
||||
},
|
||||
|
||||
"allowed_email_sans": &framework.FieldSchema{
|
||||
Type: framework.TypeCommaStringSlice,
|
||||
Description: `A comma-separated list of Email Addresses.
|
||||
At least one must exist in the SANs. Supports globbing.`,
|
||||
DisplayAttrs: &framework.DisplayAttributes{
|
||||
Name: "Allowed Email SANs",
|
||||
Group: "Constraints",
|
||||
},
|
||||
},
|
||||
|
||||
"allowed_uri_sans": &framework.FieldSchema{
|
||||
Type: framework.TypeCommaStringSlice,
|
||||
Description: `A comma-separated list of URIs.
|
||||
At least one must exist in the SANs. Supports globbing.`,
|
||||
DisplayAttrs: &framework.DisplayAttributes{
|
||||
Name: "Allowed URI SANs",
|
||||
Group: "Constraints",
|
||||
},
|
||||
},
|
||||
|
||||
"allowed_organizational_units": &framework.FieldSchema{
|
||||
Type: framework.TypeCommaStringSlice,
|
||||
Description: `A comma-separated list of Organizational Units names.
|
||||
At least one must exist in the OU field.`,
|
||||
DisplayAttrs: &framework.DisplayAttributes{
|
||||
Group: "Constraints",
|
||||
},
|
||||
},
|
||||
|
||||
"required_extensions": &framework.FieldSchema{
|
||||
@ -137,6 +165,10 @@ certificate.`,
|
||||
|
||||
HelpSynopsis: pathCertHelpSyn,
|
||||
HelpDescription: pathCertHelpDesc,
|
||||
DisplayAttrs: &framework.DisplayAttributes{
|
||||
Action: "Create",
|
||||
ItemType: "Certificate",
|
||||
},
|
||||
}
|
||||
|
||||
tokenutil.AddTokenFields(p.Fields)
|
||||
|
||||
@ -21,6 +21,7 @@ func pathGroupsList(b *backend) *framework.Path {
|
||||
HelpDescription: pathGroupHelpDesc,
|
||||
DisplayAttrs: &framework.DisplayAttributes{
|
||||
Navigation: true,
|
||||
ItemType: "Group",
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -49,7 +50,8 @@ func pathGroups(b *backend) *framework.Path {
|
||||
HelpSynopsis: pathGroupHelpSyn,
|
||||
HelpDescription: pathGroupHelpDesc,
|
||||
DisplayAttrs: &framework.DisplayAttributes{
|
||||
Action: "Create",
|
||||
Action: "Create",
|
||||
ItemType: "Group",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@ -22,7 +22,7 @@ func pathUsersList(b *backend) *framework.Path {
|
||||
HelpDescription: pathUserHelpDesc,
|
||||
DisplayAttrs: &framework.DisplayAttributes{
|
||||
Navigation: true,
|
||||
Action: "Create",
|
||||
ItemType: "User",
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -56,7 +56,8 @@ func pathUsers(b *backend) *framework.Path {
|
||||
HelpSynopsis: pathUserHelpSyn,
|
||||
HelpDescription: pathUserHelpDesc,
|
||||
DisplayAttrs: &framework.DisplayAttributes{
|
||||
Action: "Create",
|
||||
Action: "Create",
|
||||
ItemType: "User",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@ -87,6 +87,9 @@ func pathConfig(b *backend) *framework.Path {
|
||||
ExistenceCheck: b.pathConfigExistenceCheck,
|
||||
|
||||
HelpSynopsis: pathConfigHelp,
|
||||
DisplayAttrs: &framework.DisplayAttributes{
|
||||
Action: "Configure",
|
||||
},
|
||||
}
|
||||
|
||||
tokenutil.AddTokenFields(p.Fields)
|
||||
|
||||
@ -19,6 +19,10 @@ func pathGroupsList(b *backend) *framework.Path {
|
||||
|
||||
HelpSynopsis: pathGroupHelpSyn,
|
||||
HelpDescription: pathGroupHelpDesc,
|
||||
DisplayAttrs: &framework.DisplayAttributes{
|
||||
Navigation: true,
|
||||
ItemType: "Group",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@ -45,6 +49,10 @@ func pathGroups(b *backend) *framework.Path {
|
||||
|
||||
HelpSynopsis: pathGroupHelpSyn,
|
||||
HelpDescription: pathGroupHelpDesc,
|
||||
DisplayAttrs: &framework.DisplayAttributes{
|
||||
Action: "Create",
|
||||
ItemType: "Group",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -17,6 +17,10 @@ func pathUsersList(b *backend) *framework.Path {
|
||||
|
||||
HelpSynopsis: pathUserHelpSyn,
|
||||
HelpDescription: pathUserHelpDesc,
|
||||
DisplayAttrs: &framework.DisplayAttributes{
|
||||
Navigation: true,
|
||||
ItemType: "User",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@ -48,6 +52,10 @@ func pathUsers(b *backend) *framework.Path {
|
||||
|
||||
HelpSynopsis: pathUserHelpSyn,
|
||||
HelpDescription: pathUserHelpDesc,
|
||||
DisplayAttrs: &framework.DisplayAttributes{
|
||||
Action: "Create",
|
||||
ItemType: "User",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -85,6 +85,9 @@ func pathConfig(b *backend) *framework.Path {
|
||||
|
||||
HelpSynopsis: pathConfigHelpSyn,
|
||||
HelpDescription: pathConfigHelpDesc,
|
||||
DisplayAttrs: &framework.DisplayAttributes{
|
||||
Action: "Configure",
|
||||
},
|
||||
}
|
||||
|
||||
tokenutil.AddTokenFields(p.Fields)
|
||||
|
||||
@ -20,6 +20,10 @@ func pathUsersList(b *backend) *framework.Path {
|
||||
|
||||
HelpSynopsis: pathUserHelpSyn,
|
||||
HelpDescription: pathUserHelpDesc,
|
||||
DisplayAttrs: &framework.DisplayAttributes{
|
||||
Navigation: true,
|
||||
ItemType: "User",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@ -49,6 +53,10 @@ func pathUsers(b *backend) *framework.Path {
|
||||
|
||||
HelpSynopsis: pathUserHelpSyn,
|
||||
HelpDescription: pathUserHelpDesc,
|
||||
DisplayAttrs: &framework.DisplayAttributes{
|
||||
Action: "Create",
|
||||
ItemType: "User",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -22,6 +22,10 @@ func pathUsersList(b *backend) *framework.Path {
|
||||
|
||||
HelpSynopsis: pathUserHelpSyn,
|
||||
HelpDescription: pathUserHelpDesc,
|
||||
DisplayAttrs: &framework.DisplayAttributes{
|
||||
Navigation: true,
|
||||
ItemType: "User",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@ -37,6 +41,9 @@ func pathUsers(b *backend) *framework.Path {
|
||||
"password": &framework.FieldSchema{
|
||||
Type: framework.TypeString,
|
||||
Description: "Password for this user.",
|
||||
DisplayAttrs: &framework.DisplayAttributes{
|
||||
Sensitive: true,
|
||||
},
|
||||
},
|
||||
|
||||
"policies": &framework.FieldSchema{
|
||||
@ -75,6 +82,10 @@ func pathUsers(b *backend) *framework.Path {
|
||||
|
||||
HelpSynopsis: pathUserHelpSyn,
|
||||
HelpDescription: pathUserHelpDesc,
|
||||
DisplayAttrs: &framework.DisplayAttributes{
|
||||
Action: "Create",
|
||||
ItemType: "User",
|
||||
},
|
||||
}
|
||||
|
||||
tokenutil.AddTokenFields(p.Fields)
|
||||
|
||||
@ -173,11 +173,18 @@ type DisplayAttributes struct {
|
||||
// Navigation indicates that the path should be available as a navigation tab
|
||||
Navigation bool `json:"navigation,omitempty"`
|
||||
|
||||
// ItemType is the type of item this path operates on
|
||||
ItemType string `json:"itemType,omitempty"`
|
||||
|
||||
// Group is the suggested UI group to place this field in.
|
||||
Group string `json:"group,omitempty"`
|
||||
|
||||
// Action is the verb to use for the operation.
|
||||
Action string `json:"action,omitempty"`
|
||||
|
||||
// EditType is the type of form field needed for a property
|
||||
// e.g. "textarea" or "file"
|
||||
EditType string `json:"editType,omitempty"`
|
||||
}
|
||||
|
||||
// RequestExample is example of request data.
|
||||
|
||||
@ -76,6 +76,7 @@ func TokenFields() map[string]*framework.FieldSchema {
|
||||
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{
|
||||
Name: "Generated Token's Bound CIDRs",
|
||||
Group: "Tokens",
|
||||
},
|
||||
},
|
||||
|
||||
@ -84,6 +85,7 @@ func TokenFields() map[string]*framework.FieldSchema {
|
||||
Description: tokenExplicitMaxTTLHelp,
|
||||
DisplayAttrs: &framework.DisplayAttributes{
|
||||
Name: "Generated Token's Explicit Maximum TTL",
|
||||
Group: "Tokens",
|
||||
},
|
||||
},
|
||||
|
||||
@ -92,6 +94,7 @@ func TokenFields() map[string]*framework.FieldSchema {
|
||||
Description: "The maximum lifetime of the generated token",
|
||||
DisplayAttrs: &framework.DisplayAttributes{
|
||||
Name: "Generated Token's Maximum TTL",
|
||||
Group: "Tokens",
|
||||
},
|
||||
},
|
||||
|
||||
@ -100,6 +103,7 @@ func TokenFields() map[string]*framework.FieldSchema {
|
||||
Description: "If true, the 'default' policy will not automatically be added to generated tokens",
|
||||
DisplayAttrs: &framework.DisplayAttributes{
|
||||
Name: "Do Not Attach 'default' Policy To Generated Tokens",
|
||||
Group: "Tokens",
|
||||
},
|
||||
},
|
||||
|
||||
@ -108,6 +112,7 @@ func TokenFields() map[string]*framework.FieldSchema {
|
||||
Description: tokenPeriodHelp,
|
||||
DisplayAttrs: &framework.DisplayAttributes{
|
||||
Name: "Generated Token's Period",
|
||||
Group: "Tokens",
|
||||
},
|
||||
},
|
||||
|
||||
@ -116,6 +121,7 @@ func TokenFields() map[string]*framework.FieldSchema {
|
||||
Description: "Comma-separated list of policies",
|
||||
DisplayAttrs: &framework.DisplayAttributes{
|
||||
Name: "Generated Token's Policies",
|
||||
Group: "Tokens",
|
||||
},
|
||||
},
|
||||
|
||||
@ -125,6 +131,7 @@ func TokenFields() map[string]*framework.FieldSchema {
|
||||
Description: "The type of token to generate, service or batch",
|
||||
DisplayAttrs: &framework.DisplayAttributes{
|
||||
Name: "Generated Token's Type",
|
||||
Group: "Tokens",
|
||||
},
|
||||
},
|
||||
|
||||
@ -133,6 +140,7 @@ func TokenFields() map[string]*framework.FieldSchema {
|
||||
Description: "The initial ttl of the token to generate",
|
||||
DisplayAttrs: &framework.DisplayAttributes{
|
||||
Name: "Generated Token's Initial TTL",
|
||||
Group: "Tokens",
|
||||
},
|
||||
},
|
||||
|
||||
@ -141,6 +149,7 @@ func TokenFields() map[string]*framework.FieldSchema {
|
||||
Description: "The maximum number of times a token may be used, a value of zero means unlimited",
|
||||
DisplayAttrs: &framework.DisplayAttributes{
|
||||
Name: "Maximum Uses of Generated Tokens",
|
||||
Group: "Tokens",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@ -2,7 +2,8 @@
|
||||
<meta name="kmip/config/environment" content="%7B%22modulePrefix%22%3A%22kmip%22%2C%22environment%22%3A%22development%22%7D" />
|
||||
<meta name="open-api-explorer/config/environment" content="%7B%22modulePrefix%22%3A%22open-api-explorer%22%2C%22environment%22%3A%22development%22%2C%22APP%22%3A%7B%22NAMESPACE_ROOT_URLS%22%3A%5B%22sys/health%22%2C%22sys/seal-status%22%2C%22sys/license/features%22%5D%7D%7D" />
|
||||
<meta name="replication/config/environment" content="%7B%22modulePrefix%22%3A%22replication%22%2C%22environment%22%3A%22development%22%7D" />
|
||||
<meta name="vault/config/asset-manifest" content="%7B%22bundles%22%3A%7B%22kmip%22%3A%7B%22assets%22%3A%5B%7B%22uri%22%3A%22/ui/engines-dist/kmip/assets/engine-vendor.js%22%2C%22type%22%3A%22js%22%7D%2C%7B%22uri%22%3A%22/ui/engines-dist/kmip/assets/engine.js%22%2C%22type%22%3A%22js%22%7D%5D%7D%2C%22open-api-explorer%22%3A%7B%22assets%22%3A%5B%7B%22uri%22%3A%22/ui/engines-dist/open-api-explorer/assets/engine-vendor.css%22%2C%22type%22%3A%22css%22%7D%2C%7B%22uri%22%3A%22/ui/engines-dist/open-api-explorer/assets/engine-vendor.js%22%2C%22type%22%3A%22js%22%7D%2C%7B%22uri%22%3A%22/ui/engines-dist/open-api-explorer/assets/engine.css%22%2C%22type%22%3A%22css%22%7D%2C%7B%22uri%22%3A%22/ui/engines-dist/open-api-explorer/assets/engine.js%22%2C%22type%22%3A%22js%22%7D%5D%7D%2C%22replication%22%3A%7B%22assets%22%3A%5B%7B%22uri%22%3A%22/ui/engines-dist/replication/assets/engine-vendor.js%22%2C%22type%22%3A%22js%22%7D%2C%7B%22uri%22%3A%22/ui/engines-dist/replication/assets/engine.js%22%2C%22type%22%3A%22js%22%7D%5D%7D%7D%7D" />
|
||||
<meta name="vault/config/asset-manifest"
|
||||
content="%7B%22bundles%22%3A%7B%22kmip%22%3A%7B%22assets%22%3A%5B%7B%22uri%22%3A%22/ui/engines-dist/kmip/assets/engine-vendor.js%22%2C%22type%22%3A%22js%22%7D%2C%7B%22uri%22%3A%22/ui/engines-dist/kmip/assets/engine.js%22%2C%22type%22%3A%22js%22%7D%5D%7D%2C%22open-api-explorer%22%3A%7B%22assets%22%3A%5B%7B%22uri%22%3A%22/ui/engines-dist/open-api-explorer/assets/engine-vendor.css%22%2C%22type%22%3A%22css%22%7D%2C%7B%22uri%22%3A%22/ui/engines-dist/open-api-explorer/assets/engine-vendor.js%22%2C%22type%22%3A%22js%22%7D%2C%7B%22uri%22%3A%22/ui/engines-dist/open-api-explorer/assets/engine.css%22%2C%22type%22%3A%22css%22%7D%2C%7B%22uri%22%3A%22/ui/engines-dist/open-api-explorer/assets/engine.js%22%2C%22type%22%3A%22js%22%7D%5D%7D%2C%22replication%22%3A%7B%22assets%22%3A%5B%7B%22uri%22%3A%22/ui/engines-dist/replication/assets/engine-vendor.js%22%2C%22type%22%3A%22js%22%7D%2C%7B%22uri%22%3A%22/ui/engines-dist/replication/assets/engine.js%22%2C%22type%22%3A%22js%22%7D%5D%7D%7D%7D" />
|
||||
<link rel="stylesheet" href="/assets/vendor.css" />
|
||||
<link rel="stylesheet" href="/assets/vault.css" />
|
||||
<link rel="icon" href="/favicon.png" />
|
||||
@ -23,4 +24,4 @@
|
||||
</script>
|
||||
<script>runningTests = true;</script>
|
||||
<script src="/assets/vendor.js"></script>
|
||||
<script src="/assets/vault.js"></script>
|
||||
<script src="/assets/vault.js"></script>
|
||||
|
||||
@ -4,29 +4,25 @@ import ApplicationAdapter from './application';
|
||||
export default ApplicationAdapter.extend({
|
||||
namespace: 'v1',
|
||||
urlForItem() {},
|
||||
optionsForQuery(id) {
|
||||
let data = {};
|
||||
if (!id) {
|
||||
data['list'] = true;
|
||||
}
|
||||
return { data };
|
||||
},
|
||||
|
||||
fetchByQuery(store, query) {
|
||||
const { id, method, type } = query;
|
||||
return this.ajax(this.urlForItem(method, id, type), 'GET', this.optionsForQuery(id)).then(resp => {
|
||||
fetchByQuery(store, query, isList) {
|
||||
const { id } = query;
|
||||
let data = {};
|
||||
if (isList) {
|
||||
data.list = true;
|
||||
}
|
||||
|
||||
return this.ajax(this.urlForItem(id, isList), 'GET', { data }).then(resp => {
|
||||
const data = {
|
||||
id,
|
||||
name: id,
|
||||
method,
|
||||
method: id,
|
||||
};
|
||||
|
||||
return assign({}, resp, data);
|
||||
});
|
||||
},
|
||||
|
||||
query(store, type, query) {
|
||||
return this.fetchByQuery(store, query);
|
||||
return this.fetchByQuery(store, query, true);
|
||||
},
|
||||
|
||||
queryRecord(store, type, query) {
|
||||
|
||||
@ -39,13 +39,13 @@ export default Component.extend({
|
||||
return;
|
||||
}
|
||||
this.router.transitionTo('vault.cluster.access.method.item.list').followRedirects();
|
||||
this.flashMessages.success(`The ${this.itemType} configuration was saved successfully.`);
|
||||
this.flashMessages.success(`Successfully saved ${this.itemType} ${this.model.id}.`);
|
||||
}).withTestWaiter(),
|
||||
actions: {
|
||||
deleteItem() {
|
||||
this.model.destroyRecord().then(() => {
|
||||
this.router.transitionTo('vault.cluster.access.method.item.list').followRedirects();
|
||||
this.flashMessages.success(`${this.model.id} ${this.itemType} was deleted successfully.`);
|
||||
this.flashMessages.success(`Successfully deleted ${this.itemType} ${this.model.id}.`);
|
||||
});
|
||||
},
|
||||
},
|
||||
|
||||
9
ui/app/helpers/supported-managed-auth-backends.js
Normal file
9
ui/app/helpers/supported-managed-auth-backends.js
Normal file
@ -0,0 +1,9 @@
|
||||
import { helper as buildHelper } from '@ember/component/helper';
|
||||
|
||||
const MANAGED_AUTH_BACKENDS = ['okta', 'radius', 'ldap', 'cert', 'userpass'];
|
||||
|
||||
export function supportedManagedAuthBackends() {
|
||||
return MANAGED_AUTH_BACKENDS;
|
||||
}
|
||||
|
||||
export default buildHelper(supportedManagedAuthBackends);
|
||||
@ -85,12 +85,17 @@ export function tabsForAuthSection([model, sectionType = 'authSettings', paths])
|
||||
});
|
||||
return tabs;
|
||||
}
|
||||
if (paths) {
|
||||
tabs = paths.map(path => {
|
||||
let itemName = path.slice(1); //get rid of leading slash
|
||||
if (paths || model.paths) {
|
||||
if (model.paths) {
|
||||
paths = model.paths.paths.filter(path => path.navigation);
|
||||
}
|
||||
|
||||
// TODO: we're unsure if we actually need compact here
|
||||
// but are leaving it just in case OpenAPI ever returns an empty thing
|
||||
tabs = paths.compact().map(path => {
|
||||
return {
|
||||
label: capitalize(pluralize(itemName)),
|
||||
routeParams: ['vault.cluster.access.method.item.list', itemName],
|
||||
label: capitalize(pluralize(path.itemName)),
|
||||
routeParams: ['vault.cluster.access.method.item.list', path.itemType],
|
||||
};
|
||||
});
|
||||
} else {
|
||||
|
||||
@ -3,7 +3,7 @@ import { tabsForAuthSection } from 'vault/helpers/tabs-for-auth-section';
|
||||
export default Route.extend({
|
||||
beforeModel() {
|
||||
let { methodType, paths } = this.modelFor('vault.cluster.access.method');
|
||||
paths = paths ? paths.navPaths.reduce((acc, cur) => acc.concat(cur.path), []) : null;
|
||||
paths = paths ? paths.paths.filter(path => path.navigation === true) : null;
|
||||
const activeTab = tabsForAuthSection([methodType, 'authConfig', paths])[0].routeParams;
|
||||
return this.transitionTo(...activeTab);
|
||||
},
|
||||
|
||||
@ -7,26 +7,29 @@ export default Route.extend({
|
||||
pathHelp: service('path-help'),
|
||||
|
||||
beforeModel() {
|
||||
const { apiPath, type, method, itemType } = this.getMethodAndModelInfo();
|
||||
const { apiPath, type, authMethodPath, itemType } = this.getMethodAndModelInfo();
|
||||
let modelType = `generated-${singularize(itemType)}-${type}`;
|
||||
return this.pathHelp.getNewModel(modelType, method, apiPath, itemType);
|
||||
return this.pathHelp.getNewModel(modelType, authMethodPath, apiPath, itemType);
|
||||
},
|
||||
|
||||
getMethodAndModelInfo() {
|
||||
const { item_type: itemType } = this.paramsFor(this.routeName);
|
||||
const { path: method } = this.paramsFor('vault.cluster.access.method');
|
||||
const { path: authMethodPath } = this.paramsFor('vault.cluster.access.method');
|
||||
const methodModel = this.modelFor('vault.cluster.access.method');
|
||||
const { apiPath, type } = methodModel;
|
||||
return { apiPath, type, method, itemType };
|
||||
return { apiPath, type, authMethodPath, itemType };
|
||||
},
|
||||
|
||||
setupController(controller) {
|
||||
this._super(...arguments);
|
||||
const { apiPath, method, itemType } = this.getMethodAndModelInfo();
|
||||
const { apiPath, authMethodPath, itemType } = this.getMethodAndModelInfo();
|
||||
controller.set('itemType', itemType);
|
||||
controller.set('method', method);
|
||||
this.pathHelp.getPaths(apiPath, method, itemType).then(paths => {
|
||||
controller.set('paths', Array.from(paths.list, pathInfo => pathInfo.path));
|
||||
this.pathHelp.getPaths(apiPath, authMethodPath, itemType).then(paths => {
|
||||
let navigationPaths = paths.paths.filter(path => path.navigation);
|
||||
controller.set(
|
||||
'paths',
|
||||
navigationPaths.filter(path => path.itemType.includes(itemType)).map(path => path.path)
|
||||
);
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@ -5,21 +5,16 @@ import { singularize } from 'ember-inflector';
|
||||
|
||||
export default Route.extend(UnloadModelRoute, UnsavedModelRoute, {
|
||||
model(params) {
|
||||
const methodModel = this.modelFor('vault.cluster.access.method');
|
||||
const { type } = methodModel;
|
||||
const id = params.item_id;
|
||||
const { item_type: itemType } = this.paramsFor('vault.cluster.access.method.item');
|
||||
let modelType = `generated-${singularize(itemType)}-${type}`;
|
||||
return this.store.findRecord(modelType, params.item_id);
|
||||
const methodModel = this.modelFor('vault.cluster.access.method');
|
||||
const modelType = `generated-${singularize(itemType)}-${methodModel.type}`;
|
||||
return this.store.queryRecord(modelType, { id, authMethodPath: methodModel.id });
|
||||
},
|
||||
|
||||
setupController(controller) {
|
||||
this._super(...arguments);
|
||||
const { item_type: itemType } = this.paramsFor('vault.cluster.access.method.item');
|
||||
const { path: method } = this.paramsFor('vault.cluster.access.method');
|
||||
const { item_id: itemName } = this.paramsFor(this.routeName);
|
||||
controller.set('itemType', singularize(itemType));
|
||||
controller.set('mode', 'edit');
|
||||
controller.set('method', method);
|
||||
controller.set('itemName', itemName);
|
||||
},
|
||||
});
|
||||
|
||||
@ -9,14 +9,14 @@ export default Route.extend(ListRoute, {
|
||||
|
||||
getMethodAndModelInfo() {
|
||||
const { item_type: itemType } = this.paramsFor('vault.cluster.access.method.item');
|
||||
const { path: method } = this.paramsFor('vault.cluster.access.method');
|
||||
const { path: authMethodPath } = this.paramsFor('vault.cluster.access.method');
|
||||
const methodModel = this.modelFor('vault.cluster.access.method');
|
||||
const { apiPath, type } = methodModel;
|
||||
return { apiPath, type, method, itemType };
|
||||
return { apiPath, type, authMethodPath, itemType, methodModel };
|
||||
},
|
||||
|
||||
model() {
|
||||
const { type, method, itemType } = this.getMethodAndModelInfo();
|
||||
const { type, authMethodPath, itemType } = this.getMethodAndModelInfo();
|
||||
const { page, pageFilter } = this.paramsFor(this.routeName);
|
||||
let modelType = `generated-${singularize(itemType)}-${type}`;
|
||||
|
||||
@ -26,7 +26,7 @@ export default Route.extend(ListRoute, {
|
||||
page: page,
|
||||
pageFilter: pageFilter,
|
||||
type: itemType,
|
||||
method: method,
|
||||
id: authMethodPath,
|
||||
})
|
||||
.catch(err => {
|
||||
if (err.httpStatus === 404) {
|
||||
@ -51,11 +51,14 @@ export default Route.extend(ListRoute, {
|
||||
},
|
||||
setupController(controller) {
|
||||
this._super(...arguments);
|
||||
const { apiPath, method, itemType } = this.getMethodAndModelInfo();
|
||||
const { apiPath, authMethodPath, itemType, methodModel } = this.getMethodAndModelInfo();
|
||||
controller.set('itemType', itemType);
|
||||
controller.set('method', method);
|
||||
this.pathHelp.getPaths(apiPath, method, itemType).then(paths => {
|
||||
controller.set('paths', paths.navPaths.reduce((acc, cur) => acc.concat(cur.path), []));
|
||||
controller.set('methodModel', methodModel);
|
||||
this.pathHelp.getPaths(apiPath, authMethodPath, itemType).then(paths => {
|
||||
controller.set(
|
||||
'paths',
|
||||
paths.paths.filter(path => path.navigation && path.itemType.includes(itemType))
|
||||
);
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@ -4,16 +4,12 @@ import Route from '@ember/routing/route';
|
||||
|
||||
export default Route.extend({
|
||||
pathHelp: service('path-help'),
|
||||
model() {
|
||||
const { item_id: itemName } = this.paramsFor(this.routeName);
|
||||
model(params) {
|
||||
const id = params.item_id;
|
||||
const { item_type: itemType } = this.paramsFor('vault.cluster.access.method.item');
|
||||
const { path: method } = this.paramsFor('vault.cluster.access.method');
|
||||
const methodModel = this.modelFor('vault.cluster.access.method');
|
||||
const { type } = methodModel;
|
||||
const modelType = `generated-${singularize(itemType)}-${type}`;
|
||||
return this.store.findRecord(modelType, itemName, {
|
||||
adapterOptions: { path: `${method}/${itemType}` },
|
||||
});
|
||||
const modelType = `generated-${singularize(itemType)}-${methodModel.type}`;
|
||||
return this.store.queryRecord(modelType, { id, authMethodPath: methodModel.id });
|
||||
},
|
||||
|
||||
setupController(controller) {
|
||||
|
||||
@ -24,7 +24,6 @@ export default Route.extend({
|
||||
this._super(...arguments);
|
||||
controller.set('section', section);
|
||||
let method = this.modelFor('vault.cluster.access.method');
|
||||
let paths = method.paths.navPaths.map(pathInfo => pathInfo.path);
|
||||
controller.set('paths', paths);
|
||||
controller.set('paths', method.paths.paths.filter(path => path.navigation));
|
||||
},
|
||||
});
|
||||
|
||||
@ -10,12 +10,14 @@ import { getOwner } from '@ember/application';
|
||||
import { assign } from '@ember/polyfills';
|
||||
import { expandOpenApiProps, combineAttributes } from 'vault/utils/openapi-to-attrs';
|
||||
import fieldToAttrs from 'vault/utils/field-to-attrs';
|
||||
import { resolve } from 'rsvp';
|
||||
import { resolve, reject } from 'rsvp';
|
||||
import { debug } from '@ember/debug';
|
||||
import { dasherize, capitalize } from '@ember/string';
|
||||
import { singularize } from 'ember-inflector';
|
||||
|
||||
import generatedItemAdapter from 'vault/adapters/generated-item-list';
|
||||
export function sanitizePath(path) {
|
||||
//remove whitespace + remove trailing and leading slashes
|
||||
// remove whitespace + remove trailing and leading slashes
|
||||
return path.trim().replace(/^\/+|\/+$/g, '');
|
||||
}
|
||||
|
||||
@ -34,7 +36,7 @@ export default Service.extend({
|
||||
const modelName = `model:${modelType}`;
|
||||
const modelFactory = owner.factoryFor(modelName);
|
||||
let newModel, helpUrl;
|
||||
//if we have a factory, we need to take the existing model into account
|
||||
// if we have a factory, we need to take the existing model into account
|
||||
if (modelFactory) {
|
||||
debug(`Model factory found for ${modelType}`);
|
||||
newModel = modelFactory.class;
|
||||
@ -42,111 +44,151 @@ export default Service.extend({
|
||||
if (newModel.merged || modelProto.useOpenAPI !== true) {
|
||||
return resolve();
|
||||
}
|
||||
|
||||
helpUrl = modelProto.getHelpUrl(backend);
|
||||
return this.registerNewModelWithProps(helpUrl, backend, newModel, modelName);
|
||||
} else {
|
||||
debug(`Creating new Model for ${modelType}`);
|
||||
newModel = DS.Model.extend({});
|
||||
//use paths to dynamically create our openapi help url
|
||||
//if we have a brand new model
|
||||
return this.getPaths(apiPath, backend, itemType).then(paths => {
|
||||
}
|
||||
|
||||
// we don't have an apiPath for dynamic secrets
|
||||
// and we don't need paths for them yet
|
||||
if (!apiPath) {
|
||||
helpUrl = newModel.proto().getHelpUrl(backend);
|
||||
return this.registerNewModelWithProps(helpUrl, backend, newModel, modelName);
|
||||
}
|
||||
|
||||
// use paths to dynamically create our openapi help url
|
||||
// if we have a brand new model
|
||||
return this.getPaths(apiPath, backend, itemType)
|
||||
.then(pathInfo => {
|
||||
const adapterFactory = owner.factoryFor(`adapter:${modelType}`);
|
||||
//if we have an adapter already use that, otherwise create one
|
||||
// if we have an adapter already use that, otherwise create one
|
||||
if (!adapterFactory) {
|
||||
debug(`Creating new adapter for ${modelType}`);
|
||||
const adapter = this.getNewAdapter(paths, itemType);
|
||||
const adapter = this.getNewAdapter(pathInfo, itemType);
|
||||
owner.register(`adapter:${modelType}`, adapter);
|
||||
}
|
||||
//if we have an item we want the create info for that itemType
|
||||
let path;
|
||||
if (itemType) {
|
||||
const createPath = paths.create.find(path => path.path.includes(itemType));
|
||||
path = createPath.path;
|
||||
path = path.slice(0, path.indexOf('{') - 1) + '/example';
|
||||
} else {
|
||||
//we need the mount config
|
||||
path = paths.configPath[0].path;
|
||||
let path, paths;
|
||||
// if we have an item we want the create info for that itemType
|
||||
paths = itemType ? this.filterPathsByItemType(pathInfo, itemType) : pathInfo.paths;
|
||||
const createPath = paths.find(path => path.operations.includes('post') && path.action !== 'Delete');
|
||||
path = createPath.path;
|
||||
path = path.includes('{') ? path.slice(0, path.indexOf('{') - 1) + '/example' : path;
|
||||
if (!path) {
|
||||
// TODO: we don't know if path will ever be falsey
|
||||
// if it is never falsey we can remove this.
|
||||
return reject();
|
||||
}
|
||||
helpUrl = `/v1/${apiPath}${path.slice(1)}?help=true`;
|
||||
|
||||
helpUrl = `/v1/${apiPath}${path.slice(1)}?help=true` || newModel.proto().getHelpUrl(backend);
|
||||
pathInfo.paths = paths;
|
||||
newModel = newModel.extend({ paths: pathInfo });
|
||||
return this.registerNewModelWithProps(helpUrl, backend, newModel, modelName);
|
||||
})
|
||||
.catch(err => {
|
||||
// TODO: we should handle the error better here
|
||||
console.error(err);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
reducePaths(paths, currentPath) {
|
||||
reducePathsByPathName(pathInfo, currentPath) {
|
||||
const pathName = currentPath[0];
|
||||
const pathInfo = currentPath[1];
|
||||
const pathDetails = currentPath[1];
|
||||
const displayAttrs = pathDetails['x-vault-displayAttrs'];
|
||||
|
||||
//config is a get/post endpoint that doesn't take route params
|
||||
//and isn't also a list endpoint and has an Action of Configure
|
||||
if (
|
||||
pathInfo.post &&
|
||||
pathInfo.get &&
|
||||
(pathInfo['x-vault-displayAttrs'] && pathInfo['x-vault-displayAttrs'].action === 'Configure')
|
||||
) {
|
||||
paths.configPath.push({ path: pathName });
|
||||
return paths; //config path should only be config path
|
||||
if (!displayAttrs) {
|
||||
return pathInfo;
|
||||
}
|
||||
|
||||
//list endpoints all have { name: "list" } in their get parameters
|
||||
if (pathInfo.get && pathInfo.get.parameters && pathInfo.get.parameters[0].name === 'list') {
|
||||
paths.list.push({ path: pathName });
|
||||
let itemType, itemName;
|
||||
if (displayAttrs.itemType) {
|
||||
itemType = displayAttrs.itemType;
|
||||
let items = itemType.split(':');
|
||||
itemName = items[items.length - 1];
|
||||
items = items.map(item => dasherize(singularize(item.toLowerCase())));
|
||||
itemType = items.join('~*');
|
||||
}
|
||||
|
||||
if (pathInfo.delete) {
|
||||
paths.delete.push({ path: pathName });
|
||||
if (itemType && !pathInfo.itemTypes.includes(itemType)) {
|
||||
pathInfo.itemTypes.push(itemType);
|
||||
}
|
||||
|
||||
//create endpoints have path an action (e.g. "Create" or "Generate")
|
||||
if (pathInfo.post && pathInfo['x-vault-displayAttrs'] && pathInfo['x-vault-displayAttrs'].action) {
|
||||
paths.create.push({
|
||||
path: pathName,
|
||||
action: pathInfo['x-vault-displayAttrs'].action,
|
||||
});
|
||||
const operations = [];
|
||||
if (pathDetails.get) {
|
||||
operations.push('get');
|
||||
}
|
||||
if (pathDetails.post) {
|
||||
operations.push('post');
|
||||
}
|
||||
if (pathDetails.delete) {
|
||||
operations.push('delete');
|
||||
}
|
||||
if (pathDetails.get && pathDetails.get.parameters && pathDetails.get.parameters[0].name === 'list') {
|
||||
operations.push('list');
|
||||
}
|
||||
|
||||
if (pathInfo['x-vault-displayAttrs'] && pathInfo['x-vault-displayAttrs'].navigation) {
|
||||
paths.navPaths.push({ path: pathName });
|
||||
}
|
||||
pathInfo.paths.push({
|
||||
path: pathName,
|
||||
itemType: itemType || displayAttrs.itemType,
|
||||
itemName: itemName || pathInfo.itemType || displayAttrs.itemType,
|
||||
operations,
|
||||
action: displayAttrs.action,
|
||||
navigation: displayAttrs.navigation === true,
|
||||
param: pathName.includes('{') ? pathName.split('{')[1].split('}')[0] : false,
|
||||
});
|
||||
|
||||
return paths;
|
||||
return pathInfo;
|
||||
},
|
||||
|
||||
getPaths(apiPath, backend) {
|
||||
debug(`Fetching relevant paths for ${backend} from ${apiPath}`);
|
||||
filterPathsByItemType(pathInfo, itemType) {
|
||||
if (!itemType) {
|
||||
return pathInfo.paths;
|
||||
}
|
||||
return pathInfo.paths.filter(path => {
|
||||
return itemType === path.itemType;
|
||||
});
|
||||
},
|
||||
|
||||
getPaths(apiPath, backend, itemType, itemID) {
|
||||
let debugString =
|
||||
itemID && itemType
|
||||
? `Fetching relevant paths for ${backend} ${itemType} ${itemID} from ${apiPath}`
|
||||
: `Fetching relevant paths for ${backend} ${itemType} from ${apiPath}`;
|
||||
debug(debugString);
|
||||
return this.ajax(`/v1/${apiPath}?help=1`, backend).then(help => {
|
||||
const pathInfo = help.openapi.paths;
|
||||
let paths = Object.entries(pathInfo);
|
||||
|
||||
return paths.reduce(this.reducePaths, {
|
||||
apiPath: apiPath,
|
||||
configPath: [],
|
||||
list: [],
|
||||
create: [],
|
||||
delete: [],
|
||||
navPaths: [],
|
||||
return paths.reduce(this.reducePathsByPathName, {
|
||||
apiPath,
|
||||
itemType,
|
||||
itemTypes: [],
|
||||
paths: [],
|
||||
itemID,
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
//Makes a call to grab the OpenAPI document.
|
||||
//Returns relevant information from OpenAPI
|
||||
//as determined by the expandOpenApiProps util
|
||||
// Makes a call to grab the OpenAPI document.
|
||||
// Returns relevant information from OpenAPI
|
||||
// as determined by the expandOpenApiProps util
|
||||
getProps(helpUrl, backend) {
|
||||
debug(`Fetching schema properties for ${backend} from ${helpUrl}`);
|
||||
|
||||
return this.ajax(helpUrl, backend).then(help => {
|
||||
//paths is an array but it will have a single entry
|
||||
// paths is an array but it will have a single entry
|
||||
// for the scope we're in
|
||||
const path = Object.keys(help.openapi.paths)[0];
|
||||
const pathInfo = help.openapi.paths[path];
|
||||
const params = pathInfo.parameters;
|
||||
let paramProp = {};
|
||||
|
||||
//include url params
|
||||
// include url params
|
||||
if (params) {
|
||||
const { name, schema, description } = params[0];
|
||||
let label = name.split('_').join(' ');
|
||||
let label = capitalize(name.split('_').join(' '));
|
||||
|
||||
paramProp[name] = {
|
||||
'x-vault-displayAttrs': {
|
||||
@ -159,51 +201,63 @@ export default Service.extend({
|
||||
};
|
||||
}
|
||||
|
||||
//TODO: handle post endpoints without requestBody
|
||||
// TODO: handle post endpoints without requestBody
|
||||
const props = pathInfo.post.requestBody.content['application/json'].schema.properties;
|
||||
//put url params (e.g. {name}, {role})
|
||||
//at the front of the props list
|
||||
// put url params (e.g. {name}, {role})
|
||||
// at the front of the props list
|
||||
const newProps = assign({}, paramProp, props);
|
||||
return expandOpenApiProps(newProps);
|
||||
});
|
||||
},
|
||||
|
||||
getNewAdapter(paths, itemType) {
|
||||
//we need list and create paths to set the correct urls for actions
|
||||
const { list, create, apiPath } = paths;
|
||||
const createPath = create.find(path => path.path.includes(itemType));
|
||||
const listPath = list.find(pathInfo => pathInfo.path.includes(itemType));
|
||||
const deletePath = paths.delete.find(path => path.path.includes(itemType));
|
||||
getNewAdapter(pathInfo, itemType) {
|
||||
// we need list and create paths to set the correct urls for actions
|
||||
let paths = this.filterPathsByItemType(pathInfo, itemType);
|
||||
let { apiPath } = pathInfo;
|
||||
const getPath = paths.find(path => path.operations.includes('get'));
|
||||
|
||||
// the action might be "Generate" or something like that so we'll grab the first post endpoint if there
|
||||
// isn't one with "Create"
|
||||
// TODO: look into a more sophisticated way to determine the create endpoint
|
||||
const createPath = paths.find(path => path.action === 'Create' || path.operations.includes('post'));
|
||||
const deletePath = paths.find(path => path.operations.includes('delete'));
|
||||
|
||||
return generatedItemAdapter.extend({
|
||||
urlForItem(method, id) {
|
||||
let { path } = listPath;
|
||||
let url = `${this.buildURL()}/${apiPath}${path.slice(1)}/`;
|
||||
if (id) {
|
||||
url = url + encodePath(id);
|
||||
urlForItem(id, isList) {
|
||||
const itemType = getPath.path.slice(1);
|
||||
let url;
|
||||
id = encodePath(id);
|
||||
|
||||
// isList indicates whether we are viewing the list page
|
||||
// of a top-level item such as userpass
|
||||
if (isList) {
|
||||
url = `${this.buildURL()}/${apiPath}${itemType}/`;
|
||||
} else {
|
||||
// build the URL for the show page of a nested item
|
||||
// such as a userpass group
|
||||
url = `${this.buildURL()}/${apiPath}${itemType}/${id}`;
|
||||
}
|
||||
|
||||
return url;
|
||||
},
|
||||
|
||||
urlForFindRecord(id, modelName, snapshot) {
|
||||
return this.urlForItem(modelName, id, snapshot);
|
||||
urlForQueryRecord(id, modelName) {
|
||||
return this.urlForItem(id, modelName);
|
||||
},
|
||||
|
||||
urlForUpdateRecord(id) {
|
||||
let { path } = createPath;
|
||||
path = path.slice(1, path.indexOf('{') - 1);
|
||||
return `${this.buildURL()}/${apiPath}${path}/${id}`;
|
||||
const itemType = createPath.path.slice(1, createPath.path.indexOf('{') - 1);
|
||||
return `${this.buildURL()}/${apiPath}${itemType}/${id}`;
|
||||
},
|
||||
|
||||
urlForCreateRecord(modelType, snapshot) {
|
||||
const { id } = snapshot;
|
||||
let { path } = createPath;
|
||||
path = path.slice(1, path.indexOf('{') - 1);
|
||||
const path = createPath.path.slice(1, createPath.path.indexOf('{') - 1);
|
||||
return `${this.buildURL()}/${apiPath}${path}/${id}`;
|
||||
},
|
||||
|
||||
urlForDeleteRecord(id) {
|
||||
let { path } = deletePath;
|
||||
path = path.slice(1, path.indexOf('{') - 1);
|
||||
const path = deletePath.path.slice(1, deletePath.path.indexOf('{') - 1);
|
||||
return `${this.buildURL()}/${apiPath}${path}/${id}`;
|
||||
},
|
||||
});
|
||||
@ -214,8 +268,8 @@ export default Service.extend({
|
||||
const { attrs, newFields } = combineAttributes(newModel.attributes, props);
|
||||
let owner = getOwner(this);
|
||||
newModel = newModel.extend(attrs, { newFields });
|
||||
//if our newModel doesn't have fieldGroups already
|
||||
//we need to create them
|
||||
// if our newModel doesn't have fieldGroups already
|
||||
// we need to create them
|
||||
try {
|
||||
let fieldGroups = newModel.proto().fieldGroups;
|
||||
if (!fieldGroups) {
|
||||
@ -224,7 +278,7 @@ export default Service.extend({
|
||||
newModel = newModel.extend({ fieldGroups });
|
||||
}
|
||||
} catch (err) {
|
||||
//eat the error, fieldGroups is computed in the model definition
|
||||
// eat the error, fieldGroups is computed in the model definition
|
||||
}
|
||||
newModel.reopenClass({ merged: true });
|
||||
owner.unregister(modelName);
|
||||
@ -237,8 +291,8 @@ export default Service.extend({
|
||||
};
|
||||
let fieldGroups = [];
|
||||
newModel.attributes.forEach(attr => {
|
||||
//if the attr comes in with a fieldGroup from OpenAPI,
|
||||
//add it to that group
|
||||
// if the attr comes in with a fieldGroup from OpenAPI,
|
||||
// add it to that group
|
||||
if (attr.options.fieldGroup) {
|
||||
if (groups[attr.options.fieldGroup]) {
|
||||
groups[attr.options.fieldGroup].push(attr.name);
|
||||
@ -246,7 +300,7 @@ export default Service.extend({
|
||||
groups[attr.options.fieldGroup] = [attr.name];
|
||||
}
|
||||
} else {
|
||||
//otherwise just add that attr to the default group
|
||||
// otherwise just add that attr to the default group
|
||||
groups.default.push(attr.name);
|
||||
}
|
||||
});
|
||||
|
||||
@ -13,14 +13,36 @@
|
||||
</p.top>
|
||||
<p.levelLeft>
|
||||
<h1 class="title is-3">
|
||||
{{method}}
|
||||
{{methodModel.id}}
|
||||
</h1>
|
||||
</p.levelLeft>
|
||||
</PageHeader>
|
||||
{{section-tabs model "authShow" paths}}
|
||||
{{#with (tabs-for-auth-section methodModel 'authConfig' paths) as |tabs|}}
|
||||
{{#if tabs.length}}
|
||||
<div class="tabs-container box is-sideless is-fullwidth is-paddingless is-marginless">
|
||||
<nav class="tabs">
|
||||
<ul>
|
||||
{{#each tabs as |tab|}}
|
||||
{{#link-to
|
||||
params=tab.routeParams
|
||||
tagName="li"
|
||||
data-test-auth-section-tab=true
|
||||
}}
|
||||
{{#link-to params=tab.routeParams}}
|
||||
{{tab.label}}
|
||||
{{/link-to}}
|
||||
{{/link-to}}
|
||||
{{/each}}
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
{{/if}}
|
||||
{{/with}}
|
||||
<Toolbar>
|
||||
<ToolbarActions>
|
||||
<ToolbarLink @params={{array
|
||||
<ToolbarLink
|
||||
@type="add"
|
||||
@params={{array
|
||||
"vault.cluster.access.method.item.create"
|
||||
itemType
|
||||
}}>
|
||||
@ -47,7 +69,7 @@
|
||||
<Item.content>
|
||||
<Icon @glyph="folder-outline" class="has-text-grey-light" @size="l" />{{list.item.id}}
|
||||
</Item.content>
|
||||
<Item.menu>
|
||||
<Item.menu as |Menu|>
|
||||
<li class="action">
|
||||
{{#link-to "vault.cluster.access.method.item.show" list.item.id class="is-block"}}
|
||||
View {{singularize itemType}}
|
||||
@ -59,22 +81,24 @@
|
||||
{{/link-to}}
|
||||
</li>
|
||||
<li>
|
||||
<ConfirmAction @buttonClasses="link is-destroy" @onConfirmAction={{action
|
||||
(perform
|
||||
Item.callMethod
|
||||
"destroyRecord"
|
||||
list.item
|
||||
(concat "Successfully deleted " (singularize itemType) " " list.item.id)
|
||||
(concat "There was an error deleting this " (singularize itemType))
|
||||
(action "refreshItemList")
|
||||
)
|
||||
}} @confirmMessage={{concat "Are you sure you want to delete " list.item.id "?"}}
|
||||
@cancelButtonText="Cancel" data-test-secret-delete="true">
|
||||
Delete
|
||||
{{singularize itemType}}
|
||||
</ConfirmAction>
|
||||
<Menu.Message
|
||||
@id={{list.item.id}}
|
||||
@buttonClasses="link is-destroy"
|
||||
@onConfirm={{action
|
||||
(perform
|
||||
Item.callMethod
|
||||
"destroyRecord"
|
||||
list.item
|
||||
(concat "Successfully deleted " (singularize itemType) " " list.item.id ".")
|
||||
(concat "There was an error deleting this " (singularize itemType))
|
||||
(action "refreshItemList")
|
||||
)
|
||||
}}
|
||||
@message={{concat "Are you sure you want to delete " (singularize itemType) " " list.item.id "?"}}
|
||||
data-test-secret-delete="true"
|
||||
@triggerText={{concat "Delete " (singularize itemType)}}/>
|
||||
</li>
|
||||
</Item.menu>
|
||||
</ListItem>
|
||||
{{/if}}
|
||||
</ListView>
|
||||
</ListView>
|
||||
|
||||
@ -36,7 +36,7 @@
|
||||
<Toolbar>
|
||||
<ToolbarActions>
|
||||
<ConfirmAction @buttonClasses="toolbar-link" @onConfirmAction={{action "deleteItem"}}
|
||||
@confirmMessage={{concat "Are you sure you want to delete " model.id "?"}} @cancelButtonText="Cancel"
|
||||
@confirmMessage={{concat "Are you sure you want to delete " itemType " " model.id "?"}} @cancelButtonText="Cancel"
|
||||
data-test-secret-delete="true">
|
||||
Delete
|
||||
{{itemType}}
|
||||
@ -75,4 +75,4 @@
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
||||
@ -4,6 +4,13 @@
|
||||
<label class="is-label" data-test-text-label=true>
|
||||
{{#if label}}
|
||||
{{label}}
|
||||
{{#if helpText}}
|
||||
{{#info-tooltip}}
|
||||
<span data-test-help-text>
|
||||
{{helpText}}
|
||||
</span>
|
||||
{{/info-tooltip}}
|
||||
{{/if}}
|
||||
{{else}}
|
||||
File
|
||||
{{/if}}
|
||||
|
||||
@ -1 +1,4 @@
|
||||
<GeneratedItem @model={{model}} @mode="edit" @itemType={{itemType}} />
|
||||
<GeneratedItem
|
||||
@model={{model}}
|
||||
@mode="edit"
|
||||
@itemType={{itemType}} />
|
||||
|
||||
@ -1 +1,6 @@
|
||||
<GeneratedItemList @model={{model}} @method={{method}} @itemType={{itemType}} @paths={{paths}} />
|
||||
<GeneratedItemList
|
||||
@model={{model}}
|
||||
@method={{method}}
|
||||
@itemType={{itemType}}
|
||||
@paths={{paths}}
|
||||
@methodModel={{methodModel}} />
|
||||
|
||||
@ -1 +1,4 @@
|
||||
<GeneratedItem @model={{model}} @itemType={{itemType}} @mode="show"/>
|
||||
<GeneratedItem
|
||||
@model={{model}}
|
||||
@itemType={{itemType}}
|
||||
@mode="show"/>
|
||||
|
||||
@ -15,7 +15,7 @@
|
||||
</Toolbar>
|
||||
|
||||
{{#each (sort-by "path" model) as |method|}}
|
||||
{{#if (eq method.methodType "ldap")}}
|
||||
{{#if (contains method.methodType (supported-managed-auth-backends))}}
|
||||
<LinkedBlock @params={{array
|
||||
"vault.cluster.access.method"
|
||||
method.id}} class="list-item-row" data-test-auth-backend-link={{method.id}}>
|
||||
@ -51,6 +51,7 @@
|
||||
<div class="level-right is-flex is-paddingless is-marginless">
|
||||
<div class="level-item">
|
||||
{{#popup-menu name="auth-backend-nav"}}
|
||||
<Confirm as |c|>
|
||||
<nav class="menu">
|
||||
<ul class="menu-list">
|
||||
<li>
|
||||
@ -68,15 +69,18 @@
|
||||
|
||||
{{#if (and (not-eq method.methodType 'token') method.canDisable)}}
|
||||
<li class="action">
|
||||
<ConfirmAction @buttonClasses="link is-destroy" @confirmTitle="Disable method?"
|
||||
@confirmMessage="This may affect access to Vault data." @confirmButtonText="Disable"
|
||||
@onConfirmAction={{perform disableMethod method}}>
|
||||
Disable
|
||||
</ConfirmAction>
|
||||
<c.Message
|
||||
@id={{method.id}}
|
||||
@title="Disable method?"
|
||||
@message="This may affect access to Vault data."
|
||||
@triggerText="Disable"
|
||||
@onConfirm={{perform disableMethod method}}>
|
||||
</c.Message>
|
||||
</li>
|
||||
{{/if}}
|
||||
</ul>
|
||||
</nav>
|
||||
</Confirm>
|
||||
{{/popup-menu}}
|
||||
</div>
|
||||
</div>
|
||||
@ -90,13 +94,13 @@
|
||||
<ToolTip @horizontalPosition="left" as |T|>
|
||||
<T.trigger>
|
||||
<Icon @glyph={{if
|
||||
(or
|
||||
(find-by "type" method.methodType (mountable-auth-methods))
|
||||
(eq method.methodType "token")
|
||||
)
|
||||
method.methodType
|
||||
"auth"
|
||||
}} @size="l" class="has-text-grey-light" />
|
||||
(or
|
||||
(find-by "type" method.methodType (mountable-auth-methods))
|
||||
(eq method.methodType "token")
|
||||
)
|
||||
method.methodType
|
||||
"auth"
|
||||
}} @size="l" class="has-text-grey-light" />
|
||||
</T.trigger>
|
||||
<T.content @class="tool-tip">
|
||||
<div class="box">
|
||||
@ -116,36 +120,37 @@
|
||||
<div class="level-right is-flex is-paddingless is-marginless">
|
||||
<div class="level-item">
|
||||
{{#popup-menu name="auth-backend-nav"}}
|
||||
<nav class="menu">
|
||||
<ul class="menu-list">
|
||||
<li>
|
||||
{{#link-to "vault.cluster.access.method.section" method.id "configuration"}}
|
||||
View configuration
|
||||
{{/link-to}}
|
||||
</li>
|
||||
{{#if method.canEdit}}
|
||||
<Confirm as |c|>
|
||||
<nav class="menu">
|
||||
<ul class="menu-list">
|
||||
<li>
|
||||
{{#link-to "vault.cluster.settings.auth.configure" method.id}}
|
||||
Edit configuration
|
||||
{{#link-to "vault.cluster.access.method.section" method.id "configuration"}}
|
||||
View configuration
|
||||
{{/link-to}}
|
||||
</li>
|
||||
{{/if}}
|
||||
{{#if method.canEdit}}
|
||||
<li>
|
||||
{{#link-to "vault.cluster.settings.auth.configure" method.id}}
|
||||
Edit configuration
|
||||
{{/link-to}}
|
||||
</li>
|
||||
{{/if}}
|
||||
|
||||
{{#if (and (not-eq method.methodType 'token') method.canDisable)}}
|
||||
<li class="action">
|
||||
<ConfirmAction @buttonClasses="link is-destroy" @confirmTitle="Disable method?"
|
||||
@confirmMessage="This may affect access to Vault data." @confirmButtonText="Disable"
|
||||
@onConfirmAction={{perform disableMethod method}}>
|
||||
Disable
|
||||
</ConfirmAction>
|
||||
</li>
|
||||
{{/if}}
|
||||
</ul>
|
||||
</nav>
|
||||
{{#if (and (not-eq method.methodType 'token') method.canDisable)}}
|
||||
<li class="action">
|
||||
<c.Message @id={{method.id}} @title="Disable method?"
|
||||
@message="This may affect access to Vault data." @triggerText="Disable"
|
||||
@onConfirm={{perform disableMethod method}}>
|
||||
</c.Message>
|
||||
</li>
|
||||
{{/if}}
|
||||
</ul>
|
||||
</nav>
|
||||
</Confirm>
|
||||
{{/popup-menu}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
{{/each}}
|
||||
{{/if}}
|
||||
{{/each}}
|
||||
|
||||
@ -12,12 +12,13 @@ export const expandOpenApiProps = function(props) {
|
||||
if (deprecated === true) {
|
||||
continue;
|
||||
}
|
||||
let { name, value, group, sensitive } = prop['x-vault-displayAttrs'] || {};
|
||||
let { name, value, group, sensitive, editType } = prop['x-vault-displayAttrs'] || {};
|
||||
|
||||
if (type === 'integer') {
|
||||
type = 'number';
|
||||
}
|
||||
let editType = type;
|
||||
|
||||
editType = editType || type;
|
||||
|
||||
if (format === 'seconds') {
|
||||
editType = 'ttl';
|
||||
@ -28,7 +29,6 @@ export const expandOpenApiProps = function(props) {
|
||||
let attrDefn = {
|
||||
editType,
|
||||
helpText: description,
|
||||
sensitive: sensitive,
|
||||
possibleValues: prop['enum'],
|
||||
fieldValue: isId ? 'id' : null,
|
||||
fieldGroup: group || 'default',
|
||||
@ -36,13 +36,22 @@ export const expandOpenApiProps = function(props) {
|
||||
defaultValue: value || null,
|
||||
};
|
||||
|
||||
attrDefn.label = capitalize(name || propName);
|
||||
if (sensitive) {
|
||||
attrDefn.sensitive = true;
|
||||
}
|
||||
|
||||
//only set a label if we have one from OpenAPI
|
||||
//otherwise the propName will be humanized by the form-field component
|
||||
if (name) {
|
||||
attrDefn.label = name;
|
||||
}
|
||||
|
||||
// ttls write as a string and read as a number
|
||||
// so setting type on them runs the wrong transform
|
||||
if (editType !== 'ttl' && type !== 'array') {
|
||||
attrDefn.type = type;
|
||||
}
|
||||
|
||||
// loop to remove empty vals
|
||||
for (let attrProp in attrDefn) {
|
||||
if (attrDefn[attrProp] == null) {
|
||||
|
||||
@ -1,26 +1,36 @@
|
||||
<div class="box is-shadowless is-sideless is-fullwidth is-marginless">
|
||||
<div class="box is-fullwidth is-sideless is-paddingless is-marginless">
|
||||
{{#each @model.fieldGroups as |fieldGroup|}}
|
||||
{{#each-in fieldGroup as |group fields|}}
|
||||
{{#if (or (eq group "default") (eq group "Options"))}}
|
||||
{{#each fields as |attr|}}
|
||||
{{#if (not-eq attr.options.fieldValue "id")}}
|
||||
<InfoTableRow @alwaysRender={{showAllFields}}
|
||||
@label={{capitalize (or attr.options.label (humanize (dasherize attr.name)))}}
|
||||
@value={{get @model attr.name}} />
|
||||
@label={{capitalize (or attr.options.label (humanize (dasherize attr.name)))}}>
|
||||
{{#if attr.options.sensitive}}
|
||||
<span class="is-help">This value is sensitive and cannot be shown.</span>
|
||||
{{else}}
|
||||
{{get @model attr.name}}
|
||||
{{/if}}
|
||||
</InfoTableRow>
|
||||
{{/if}}
|
||||
{{/each}}
|
||||
{{else}}
|
||||
<div class="box {{unless showAllFields 'is-shadowless'}} is-sideless is-fullwidth is-marginless">
|
||||
<div class="box {{unless showAllFields 'is-shadowless'}} is-fullwidth is-sideless is-marginless">
|
||||
<h2 class="title is-5">
|
||||
{{group}}
|
||||
</h2>
|
||||
{{#each fields as |attr|}}
|
||||
<InfoTableRow @alwaysRender={{showAllFields}}
|
||||
@label={{capitalize (or attr.options.label (humanize (dasherize attr.name)))}}
|
||||
@value={{get @model attr.name}} />
|
||||
@label={{capitalize (or attr.options.label (humanize (dasherize attr.name)))}}>
|
||||
{{#if attr.options.sensitive}}
|
||||
<span class="is-help">This value is sensitive and cannot be shown.</span>
|
||||
{{else}}
|
||||
{{get @model attr.name}}
|
||||
{{/if}}
|
||||
</InfoTableRow>
|
||||
{{/each}}
|
||||
</div>
|
||||
{{/if}}
|
||||
{{/each-in}}
|
||||
{{/each}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -89,6 +89,7 @@
|
||||
{{else if (eq attr.options.editType "file")}}
|
||||
{{text-file
|
||||
index=""
|
||||
helpText=attr.options.helpText
|
||||
file=file
|
||||
onChange=(action "setFile")
|
||||
warning=attr.options.warning
|
||||
@ -179,4 +180,4 @@
|
||||
value=(if (get model valuePath) (stringify (get model valuePath)) emptyData)
|
||||
valueUpdated=(action "codemirrorUpdated" attr.name false)
|
||||
}}
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
||||
@ -41,7 +41,7 @@ module('Acceptance | settings/auth/configure/section', function(hooks) {
|
||||
assert.ok(keys.includes('max_lease_ttl'), 'passes max_lease_ttl on tune');
|
||||
});
|
||||
|
||||
for (let type of ['aws', 'azure', 'gcp', 'github', 'kubernetes', 'ldap', 'okta', 'radius']) {
|
||||
for (let type of ['aws', 'azure', 'gcp', 'github', 'kubernetes']) {
|
||||
test(`it shows tabs for auth method: ${type}`, async assert => {
|
||||
let path = `${type}-${Date.now()}`;
|
||||
await cli.consoleInput(`write sys/auth/${path} type=${type}`);
|
||||
|
||||
@ -58,28 +58,24 @@ module('Unit | Util | OpenAPI Data Utilities', function() {
|
||||
editType: 'stringArray',
|
||||
defaultValue: 'Grace Hopper,Lady Ada',
|
||||
fieldGroup: 'default',
|
||||
label: 'Awesome-people',
|
||||
},
|
||||
favoriteIceCream: {
|
||||
editType: 'string',
|
||||
type: 'string',
|
||||
possibleValues: ['vanilla', 'chocolate', 'strawberry'],
|
||||
fieldGroup: 'default',
|
||||
label: 'Favorite-ice-cream',
|
||||
},
|
||||
defaultValue: {
|
||||
editType: 'number',
|
||||
type: 'number',
|
||||
defaultValue: 300,
|
||||
fieldGroup: 'default',
|
||||
label: 'Default-value',
|
||||
},
|
||||
default: {
|
||||
editType: 'number',
|
||||
type: 'number',
|
||||
defaultValue: 30,
|
||||
fieldGroup: 'default',
|
||||
label: 'Default',
|
||||
},
|
||||
superSecret: {
|
||||
type: 'string',
|
||||
@ -87,7 +83,6 @@ module('Unit | Util | OpenAPI Data Utilities', function() {
|
||||
sensitive: true,
|
||||
helpText: 'A really secret thing',
|
||||
fieldGroup: 'default',
|
||||
label: 'Super-secret',
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user