Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

OpenAPI CRUD views #6702

Merged
merged 38 commits into from
Jun 21, 2019
Merged
Show file tree
Hide file tree
Changes from 37 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
f032067
add list endpoint tab links to auth section page
Apr 16, 2019
82d072e
add generic auth method list route and controller, create model in pa…
Apr 18, 2019
ae8bb90
successfully make generic list call and render template if items don'…
Apr 19, 2019
34e2c1d
add tabs menu to list page
Apr 19, 2019
4f68001
add show route and show properties for item
Apr 22, 2019
5e4a4af
restructure generic routes to include a parent item route that constr…
Apr 30, 2019
8603ab3
add create page (WIP) to generic pages
Apr 30, 2019
83e905d
add beginning of edit view
May 1, 2019
4bbf98a
refactor adapter to pull urls from path info, adapt pages to work wit…
May 1, 2019
97b0112
box off ldap, make sure path links are generated properly for configu…
May 2, 2019
36b7ff4
fix routing for models with factories
May 2, 2019
190640c
add comments, try to debug pki role weirdness
May 2, 2019
73f7865
Merge branch 'master' of github.com:hashicorp/vault into openapi-crud
May 2, 2019
b684338
commentssssss
May 2, 2019
8172468
add delete functionality for generated items
May 8, 2019
61589f6
Merge branch 'master' into openapi-crud
madalynrose May 8, 2019
79ecf47
adjust linting errors
May 8, 2019
676ee89
Merge branch 'openapi-crud' of github.com:hashicorp/vault into openap…
May 8, 2019
0682a89
Merge branch 'master' into openapi-crud
madalynrose May 9, 2019
51c1489
merge master
May 10, 2019
9cc9c1e
update pages to use new toolbar components'
May 10, 2019
5e50388
Merge branch 'openapi-crud' of github.com:hashicorp/vault into openap…
May 10, 2019
ce6ecdf
update Model fieldGroups to be build from openAPIif they aren't suppl…
May 22, 2019
9ee259f
use .compact instead of .filter, return paths promise
Jun 10, 2019
325559b
small fixes from PR comments
Jun 11, 2019
469dfde
use new displayAttributes when parsing openAPI document
Jun 11, 2019
5d9eb50
correctly parse incoming displayAtttrs
Jun 12, 2019
081dda0
use displayAttrs.navigation for nav links
Jun 17, 2019
7e28fa9
merge in master
Jun 17, 2019
417e4b7
fix icon stuff
Jun 18, 2019
177109a
update openapi util tests
Jun 18, 2019
f523cde
revert github path config to master so we can update on another branch
Jun 19, 2019
caf9e05
use fieldGroupShow component
Jun 19, 2019
c3f1aaa
remove old definition of deprecated
Jun 20, 2019
5fcb5bd
update fieldGroups for github to put things in correct order
Jun 20, 2019
b40623c
add debug calls back in (they disappeared somewhere)
Jun 20, 2019
56f0704
link ldap list item to the new access.method route
meirish Jun 20, 2019
2d7795f
remove duplicate delete button on item edit page
Jun 21, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions ui/app/adapters/generated-item-list.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { assign } from '@ember/polyfills';
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 => {
const data = {
id,
name: id,
method,
};

return assign({}, resp, data);
});
},

query(store, type, query) {
return this.fetchByQuery(store, query);
},

queryRecord(store, type, query) {
return this.fetchByQuery(store, query);
},
});
21 changes: 21 additions & 0 deletions ui/app/components/field-group-show.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/**
* @module FieldGroupShow
* FieldGroupShow components loop through a Model's fieldGroups
* to display their attributes
*
* @example
* ```js
* <FieldGroupShow @model={{model}} @showAllFields=true />
* ```
*
* @param model {Object} - the model
* @param [showAllFields=false] {boolean} - whether to show fields with empty values
*/
import Component from '@ember/component';
import layout from '../templates/components/field-group-show';

export default Component.extend({
layout,
model: null,
showAllFields: false,
});
52 changes: 52 additions & 0 deletions ui/app/components/generated-item.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { inject as service } from '@ember/service';
import Component from '@ember/component';
import { computed } from '@ember/object';
import { task } from 'ember-concurrency';
import DS from 'ember-data';

/**
* @module GeneratedItem
* The `GeneratedItem` is the form to configure generated items related to mounts (e.g. groups, roles, users)
*
* @example
* ```js
* <GeneratedItem @model={{model}} @mode={{mode}} @itemType={{itemType/>
* ```
*
* @property model=null {DS.Model} - The corresponding item model that is being configured.
* @property mode {String} - which config mode to use. either `show`, `edit`, or `create`
* @property itemType {String} - the type of item displayed
*
*/

export default Component.extend({
model: null,
itemType: null,
flashMessages: service(),
router: service(),
props: computed(function() {
return this.model.serialize();
}),
saveModel: task(function*() {
try {
yield this.model.save();
} catch (err) {
// AdapterErrors are handled by the error-message component
// in the form
if (err instanceof DS.AdapterError === false) {
throw err;
}
return;
}
this.router.transitionTo('vault.cluster.access.method.item.list').followRedirects();
this.flashMessages.success(`The ${this.itemType} configuration was saved successfully.`);
}).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.`);
});
},
},
});
3 changes: 1 addition & 2 deletions ui/app/components/section-tabs.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,12 @@ import Component from '@ember/component';

const SectionTabs = Component.extend({
tagName: '',

model: null,
tabType: 'authSettings',
});

SectionTabs.reopenClass({
positionalParams: ['model', 'tabType'],
positionalParams: ['model', 'tabType', 'paths'],
Copy link
Contributor

Choose a reason for hiding this comment

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

Just a note here that positionParams are going away in Glimmer components, so we'll eventually want to move away from this.

});

export default SectionTabs;
4 changes: 4 additions & 0 deletions ui/app/controllers/vault/cluster/access/method/item/list.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import Controller from '@ember/controller';
import ListController from 'vault/mixins/list-controller';

export default Controller.extend(ListController, {});
19 changes: 15 additions & 4 deletions ui/app/helpers/tabs-for-auth-section.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { helper as buildHelper } from '@ember/component/helper';
import { pluralize } from 'ember-inflector';
import { capitalize } from '@ember/string';

const TABS_FOR_SETTINGS = {
aws: [
Expand Down Expand Up @@ -73,19 +75,28 @@ const TABS_FOR_SETTINGS = {

const TABS_FOR_SHOW = {};

export function tabsForAuthSection([methodType, sectionType = 'authSettings']) {
export function tabsForAuthSection([model, sectionType = 'authSettings', paths]) {
let tabs;

if (sectionType === 'authSettings') {
tabs = (TABS_FOR_SETTINGS[methodType] || []).slice();
tabs = (TABS_FOR_SETTINGS[model.type] || []).slice();
tabs.push({
label: 'Method Options',
routeParams: ['vault.cluster.settings.auth.configure.section', 'options'],
});
return tabs;
}

tabs = (TABS_FOR_SHOW[methodType] || []).slice();
if (paths) {
tabs = paths.map(path => {
let itemName = path.slice(1); //get rid of leading slash
return {
label: capitalize(pluralize(itemName)),
routeParams: ['vault.cluster.access.method.item.list', itemName],
};
});
} else {
tabs = (TABS_FOR_SHOW[model.type] || []).slice();
}
tabs.push({
label: 'Configuration',
routeParams: ['vault.cluster.access.method.section', 'configuration'],
Expand Down
1 change: 1 addition & 0 deletions ui/app/models/auth-config/github.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export default AuthConfig.extend({
{
'GitHub Options': ['baseUrl'],
},
{ TTLs: ['ttl', 'maxTtl'] },
];
if (this.newFields) {
groups = combineFieldGroups(groups, this.newFields, []);
Expand Down
39 changes: 0 additions & 39 deletions ui/app/models/auth-config/ldap.js
Original file line number Diff line number Diff line change
@@ -1,50 +1,11 @@
import { computed } from '@ember/object';
import DS from 'ember-data';

import AuthConfig from '../auth-config';
import fieldToAttrs from 'vault/utils/field-to-attrs';
import { combineFieldGroups } from 'vault/utils/openapi-to-attrs';

const { attr } = DS;

export default AuthConfig.extend({
useOpenAPI: true,
binddn: attr('string', {
Copy link
Contributor

Choose a reason for hiding this comment

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

notice here that when we set useOpenAPI to true, we can get rid of the duplicate attrs. OpenAPI gives us the attrs and sets them on the model here: https://github.com/hashicorp/vault/pull/6702/files#diff-cd3b672bded0741ca8ec360a4deb124fR37

helpText: 'Used when performing user search. Example: cn=vault,ou=Users,dc=example,dc=com',
}),
bindpass: attr('string', {
helpText: 'Used along with binddn when performing user search',
sensitive: true,
}),
userdn: attr('string', {
helpText: 'Base DN under which to perform user search. Example: ou=Users,dc=example,dc=com',
}),
userattr: attr('string', {
helpText:
'Attribute on user attribute object matching the username passed when authenticating. Examples: sAMAccountName, cn, uid',
}),
upndomain: attr('string', {
helpText:
'The userPrincipalDomain used to construct the UPN string for the authenticating user. The constructed UPN will appear as [username]@UPNDomain. Example: example.com, which will cause vault to bind as [email protected].',
}),

groupfilter: attr('string', {
helpText:
'Go template used when constructing the group membership query. The template can access the following context variables: [UserDN, Username]. The default is (|(memberUid={{.Username}})(member={{.UserDN}})(uniqueMember={{.UserDN}})), which is compatible with several common directory schemas. To support nested group resolution for Active Directory, instead use the following query: (&(objectClass=group)(member:1.2.840.113556.1.4.1941:={{.UserDN}}))',
}),
groupdn: attr('string', {
helpText:
'LDAP search base for group membership search. This can be the root containing either groups or users. Example: ou=Groups,dc=example,dc=com',
}),
groupattr: attr('string', {
helpText:
'LDAP attribute to follow on objects returned by groupfilter in order to enumerate user group membership. Examples: for groupfilter queries returning group objects, use: cn. For queries returning user objects, use: memberOf. The default is cn.',
}),
useTokenGroups: attr('boolean', {
helpText:
'Use the Active Directory tokenGroups constructed attribute to find the group memberships. This returns all security groups for the user, including nested groups. In an Active Directory environment with a large number of groups this method offers increased performance. Selecting this will cause Group DN, Attribute, and Filter to be ignored.',
}),

Copy link
Contributor

Choose a reason for hiding this comment

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

uau 👏

fieldGroups: computed(function() {
let groups = [
{
Expand Down
6 changes: 6 additions & 0 deletions ui/app/router.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@ Router.map(function() {
this.route('methods', { path: '/' });
this.route('method', { path: '/:path' }, function() {
this.route('index', { path: '/' });
this.route('item', { path: '/item/:item_type' }, function() {
madalynrose marked this conversation as resolved.
Show resolved Hide resolved
this.route('list', { path: '/' });
this.route('create');
this.route('edit', { path: '/edit/:item_id' });
this.route('show', { path: '/show/:item_id' });
});
this.route('section', { path: '/:section_name' });
});
this.route('leases', function() {
Expand Down
8 changes: 7 additions & 1 deletion ui/app/routes/vault/cluster/access/method.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { set } from '@ember/object';
import Route from '@ember/routing/route';
import DS from 'ember-data';
import { inject as service } from '@ember/service';

export default Route.extend({
pathHelp: service('path-help'),
model(params) {
// eslint-disable-line rule
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this can be removed.

const { path } = params;
return this.store.findAll('auth-method').then(modelArray => {
const model = modelArray.findBy('id', path);
Expand All @@ -12,7 +15,10 @@ export default Route.extend({
set(error, 'httpStatus', 404);
throw error;
}
return model;
return this.pathHelp.getPaths(model.apiPath, path).then(paths => {
model.set('paths', paths);
Copy link
Contributor

Choose a reason for hiding this comment

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

(for later) we should document what attributes OpenAPI models have on them - paths, newFields, etc.

return model;
});
});
},
});
29 changes: 29 additions & 0 deletions ui/app/routes/vault/cluster/access/method/item.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { inject as service } from '@ember/service';
import Route from '@ember/routing/route';
import { singularize } from 'ember-inflector';

export default Route.extend({
wizard: service(),
pathHelp: service('path-help'),

beforeModel() {
const { item_type: itemType } = this.paramsFor(this.routeName);
const { path: method } = this.paramsFor('vault.cluster.access.method');
let methodModel = this.modelFor('vault.cluster.access.method');
let { apiPath, type } = methodModel;
let modelType = `generated-${singularize(itemType)}-${type}`;
madalynrose marked this conversation as resolved.
Show resolved Hide resolved
return this.pathHelp.getNewModel(modelType, method, apiPath, itemType);
Copy link
Contributor

Choose a reason for hiding this comment

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

this is an example of how we call OpenAPI and generate a model on the auth method items pages.

},

setupController(controller) {
this._super(...arguments);
const { item_type: itemType } = this.paramsFor(this.routeName);
const { path } = this.paramsFor('vault.cluster.access.method');
const { apiPath } = this.modelFor('vault.cluster.access.method');
Copy link
Contributor

Choose a reason for hiding this comment

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

We could wrap these into a helper method to use in both places.

controller.set('itemType', itemType);
controller.set('method', path);
this.pathHelp.getPaths(apiPath, path, itemType).then(paths => {
controller.set('paths', Array.from(paths.list, pathInfo => pathInfo.path));
});
},
});
28 changes: 28 additions & 0 deletions ui/app/routes/vault/cluster/access/method/item/create.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import Route from '@ember/routing/route';
import UnloadModelRoute from 'vault/mixins/unload-model-route';
import UnsavedModelRoute from 'vault/mixins/unsaved-model-route';
import { singularize } from 'ember-inflector';

export default Route.extend(UnloadModelRoute, UnsavedModelRoute, {
model() {
const { item_type: itemType } = this.paramsFor('vault.cluster.access.method.item');
const methodModel = this.modelFor('vault.cluster.access.method');
const { type } = methodModel;
const { path: method } = this.paramsFor('vault.cluster.access.method');
const modelType = `generated-${singularize(itemType)}-${type}`;
return this.store.createRecord(modelType, {
itemType,
method,
adapterOptions: { path: `${method}/${itemType}` },
});
},

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');
controller.set('itemType', singularize(itemType));
controller.set('mode', 'create');
controller.set('method', method);
},
});
25 changes: 25 additions & 0 deletions ui/app/routes/vault/cluster/access/method/item/edit.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import Route from '@ember/routing/route';
import UnloadModelRoute from 'vault/mixins/unload-model-route';
import UnsavedModelRoute from 'vault/mixins/unsaved-model-route';
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 { item_type: itemType } = this.paramsFor('vault.cluster.access.method.item');
let modelType = `generated-${singularize(itemType)}-${type}`;
return this.store.findRecord(modelType, params.item_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);
},
});
Loading