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

UI: Implement new policy SS + modal designs #17749

Merged
merged 35 commits into from
Nov 19, 2022
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
ca57d91
refactor ss+modal to accept multiple models
hellobontempo Oct 26, 2022
8bfe0ac
create policy form
hellobontempo Oct 31, 2022
81762c8
cleanup and fix test
hellobontempo Oct 31, 2022
b901de6
add tabs to policy modal form
hellobontempo Nov 1, 2022
0f535f5
add search select with modal to entity form
hellobontempo Nov 1, 2022
8c8147b
update group form;
hellobontempo Nov 1, 2022
8f8e77c
allow modal to fit-content
hellobontempo Nov 1, 2022
232a529
add changelog
hellobontempo Nov 1, 2022
4f23b7c
add check for policy create ability
hellobontempo Nov 1, 2022
cb2a708
add id so tests pass
hellobontempo Nov 2, 2022
1160dc7
filter out root option
hellobontempo Nov 2, 2022
f44ba2a
fix test
hellobontempo Nov 2, 2022
0abf3c1
add cleanup method
hellobontempo Nov 2, 2022
73e1f2f
add ACL policy link
hellobontempo Nov 2, 2022
5253a3e
cleanup from comments
hellobontempo Nov 7, 2022
258346b
refactor sending action to parent
hellobontempo Nov 7, 2022
96cea9f
refactor, data down actions up!
hellobontempo Nov 8, 2022
e92cc18
cleanup comments
hellobontempo Nov 15, 2022
8f92621
form field refactor
hellobontempo Nov 15, 2022
f895d10
resolve conflicts
hellobontempo Nov 16, 2022
68768ae
add ternary to options
hellobontempo Nov 16, 2022
4480068
update tests
hellobontempo Nov 16, 2022
e9f0d90
Remodel component structure for clearer logic
hashishaw Nov 18, 2022
5ad89b4
address comments
hellobontempo Nov 18, 2022
8210e50
cleanup args
hellobontempo Nov 18, 2022
0af2e26
refactor inline oidc assignment form
hellobontempo Nov 18, 2022
79a6972
Merge branch 'main' into ui/VAULT-9447/policy-ss-modal-part-2
hellobontempo Nov 18, 2022
7b25851
add line break
hellobontempo Nov 18, 2022
9ba291c
cleanup comments
hellobontempo Nov 18, 2022
9ef6534
fix tests
hellobontempo Nov 18, 2022
b2918ea
add policy template to ss+modal test
hellobontempo Nov 18, 2022
616e9f9
cleanup =true from test
hellobontempo Nov 19, 2022
5eaf353
final cleanup!!!!!!
hellobontempo Nov 19, 2022
fe770b5
actual final cleanup
hellobontempo Nov 19, 2022
960e425
fix typo, please be done
hellobontempo Nov 19, 2022
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
3 changes: 3 additions & 0 deletions changelog/17749.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:feature
ui: Add inline policy creation when creating an identity entity or group
```
9 changes: 8 additions & 1 deletion ui/app/components/identity/edit-form.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@ import { waitFor } from '@ember/test-waiters';

export default Component.extend({
flashMessages: service(),
store: service(),
'data-test-component': 'identity-edit-form',
attributeBindings: ['data-test-component'],
model: null,

policyModel: null,
// 'create', 'edit', 'merge'
mode: 'create',
/*
Expand Down Expand Up @@ -77,6 +78,12 @@ export default Component.extend({
},

actions: {
createSearchSelectModel({ type, name }) {
if (type && name) {
const model = this.store.createRecord(`policy/${type}`, { name });
this.set('policyModel', model);
}
},
deleteItem(model) {
const message = this.getMessage(model, true);
const flash = this.flashMessages;
Expand Down
10 changes: 6 additions & 4 deletions ui/app/components/oidc/assignment-form.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@ import { tracked } from '@glimmer/tracking';
* @onSave={transition-to "vault.cluster.access.oidc.assignments.assignment.details" this.model.name}
* />
* ```
* @callback onCancel
* @callback onSave

* @param {object} model - The parent's model
* @param {string} onCancel - callback triggered when cancel button is clicked
* @param {string} onSave - callback triggered when save button is clicked
* @callback {string} onCancel - callback triggered when cancel button is clicked
* @callback {string} onSave - callback triggered when save button is clicked
* * params when form renders within search-select-with-modal.hbs:
* @callback createSearchSelectModel - callback to fire when new item is selected to create in SS+Modal
*
*/

export default class OidcAssignmentFormComponent extends Component {
Expand Down
16 changes: 12 additions & 4 deletions ui/app/components/oidc/client-form.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,10 @@ import { task } from 'ember-concurrency';
* ```js
* <OidcClientForm @model={{this.model}} />
* ```
* @callback onCancel
* @callback onSave
* @param {Object} model - oidc client model
* @param {onCancel} onCancel - callback triggered when cancel button is clicked
* @param {onSave} onSave - callback triggered on save success
* @callback onCancel - callback triggered when cancel button is clicked
* @callback onSave - callback triggered on save success
* @param {boolean} [isInline=false] - true when form is rendered within a modal
*/

export default class OidcClientForm extends Component {
Expand All @@ -24,6 +23,7 @@ export default class OidcClientForm extends Component {
@tracked modelValidations;
@tracked errorBanner;
@tracked invalidFormAlert;
@tracked assignmentModel; // model for form rendered by ss+modal
@tracked radioCardGroupValue =
!this.args.model.assignments || this.args.model.assignments.includes('allow_all')
? 'allow_all'
Expand All @@ -38,6 +38,14 @@ export default class OidcClientForm extends Component {
}
}

// callback fired by SS+Modal when new item is selected to create
@action
createSearchSelectModel({ name }) {
if (name) {
this.assignmentModel = this.store.createRecord('oidc/assignment', { name });
}
}

@action
handleAssignmentSelection(selection) {
// if array then coming from search-select component, set selection as model assignments
Expand Down
132 changes: 132 additions & 0 deletions ui/app/components/policy-form.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import Component from '@glimmer/component';
import { action } from '@ember/object';
import { inject as service } from '@ember/service';
import { task } from 'ember-concurrency';
import { tracked } from '@glimmer/tracking';
import trimRight from 'vault/utils/trim-right';

/**
* @module PolicyForm
* PolicyForm components are used to display the create and edit forms for all types of policies
*
* @example
* <PolicyForm
* @model={{this.model}}
* @onSave={{transition-to "vault.cluster.policy.show" this.model.policyType this.model.name}}
* @onCancel={{transition-to "vault.cluster.policies.index"}}
* />
* ```
* @callback onCancel - callback triggered when cancel button is clicked
* @callback onSave - callback triggered when save button is clicked
* @param {object} model - ember data model from createRecord
* @param {boolean} [isInline=false] - true when form is rendered within a modal
* * params when form renders within search-select-with-modal.hbs:
* @param {object} [nameInput] - search input from SS passed as name attr when firing createSearchSelectModel callback
* @callback createSearchSelectModel - callback to fire when new item is selected to create in SS+Modal
*/

export default class PolicyFormComponent extends Component {
@service store;
@service wizard;
@service version;
@service flashMessages;
@tracked errorBanner;
@tracked file = null;
@tracked showFileUpload = false;
@tracked showExamplePolicy = false;
policyOptions = [
hellobontempo marked this conversation as resolved.
Show resolved Hide resolved
{ label: 'ACL Policy', value: 'acl', isDisabled: false },
{ label: 'Role Governing Policy', value: 'rgp', isDisabled: !this.version.hasSentinel },
];
// formatting here is purposeful so that whitespace renders correctly in JsonEditor
policyTemplates = {
acl: `
# Grant 'create', 'read' , 'update', and ‘list’ permission
# to paths prefixed by 'secret/*'
path "secret/*" {
capabilities = [ "create", "read", "update", "list" ]
}

# Even though we allowed secret/*, this line explicitly denies
# secret/super-secret. This takes precedence.
path "secret/super-secret" {
capabilities = ["deny"]
}
`,
rgp: `
hellobontempo marked this conversation as resolved.
Show resolved Hide resolved
# Import strings library that exposes common string operations
import "strings"

# Conditional rule (precond) checks the incoming request endpoint
# targeted to sys/policies/acl/admin
precond = rule {
strings.has_prefix(request.path, "sys/policies/admin")
}

# Vault checks to see if the request was made by an entity
# named James Thomas or Team Lead role defined as its metadata
main = rule when precond {
identity.entity.metadata.role is "Team Lead" or
identity.entity.name is "James Thomas"
}
`,
};

@task
*save(event) {
event.preventDefault();
try {
const { isNew, name, policyType } = this.args.model;
yield this.args.model.save();
this.flashMessages.success(
`${policyType.toUpperCase()} policy "${name}" was successfully ${isNew ? 'created' : 'updated'}.`
);
if (this.wizard.featureState === 'create') {
this.wizard.transitionFeatureMachine('create', 'CONTINUE', policyType);
hellobontempo marked this conversation as resolved.
Show resolved Hide resolved
}

// this form is sometimes used in modal, passing the model notifies
// the parent if the save was successful
this.args.onSave(this.args.model);
} catch (error) {
const message = error.errors ? error.errors.join('. ') : error.message;
this.errorBanner = message;
}
this.cleanup();
hellobontempo marked this conversation as resolved.
Show resolved Hide resolved
}

@action
setModelName({ target }) {
this.args.model.name = target.value.toLowerCase();
}

@action
setPolicyType(type) {
// selecting a type only happens in the modal form
// cleanup any model argument before firing parent action to create a new record in identity/edit-form.js
if (this.args.model) this.cleanup();
this.args.createSearchSelectModel({ type, name: this.args.nameInput });
}

@action
setPolicyFromFile(index, fileInfo) {
const { value, fileName } = fileInfo;
this.args.model.policy = value;
if (!this.args.model.name) {
const trimmedFileName = trimRight(fileName, ['.json', '.txt', '.hcl', '.policy']);
this.args.model.name = trimmedFileName.toLowerCase();
}
this.showFileUpload = false;
}

@action
cancel() {
this.cleanup();
this.args.onCancel();
}

cleanup() {
const method = this.args.model.isNew ? 'unloadRecord' : 'rollbackAttributes';
this.args.model[method]();
}
}
20 changes: 0 additions & 20 deletions ui/app/controllers/vault/cluster/policies/create.js

This file was deleted.

23 changes: 21 additions & 2 deletions ui/app/controllers/vault/cluster/policy/edit.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,23 @@
import Controller from '@ember/controller';
import PolicyEditController from 'vault/mixins/policy-edit-controller';
import { action } from '@ember/object';
import { inject as service } from '@ember/service';

export default Controller.extend(PolicyEditController);
export default class PolicyEditController extends Controller {
@service router;
@service flashMessages;

@action
async deletePolicy() {
const { policyType, name } = this.model;
try {
await this.model.destroyRecord();
this.flashMessages.success(`${policyType.toUpperCase()} policy "${name}" was successfully deleted.`);
this.router.transitionTo('vault.cluster.policies', policyType);
} catch (error) {
this.model.rollbackAttributes();
const errors = error.errors ? error.errors.join('. ') : error.message;
const message = `There was an error deleting the ${policyType.toUpperCase()} policy "${name}": ${errors}.`;
this.flashMessages.danger(message);
}
}
}
49 changes: 0 additions & 49 deletions ui/app/mixins/policy-edit-controller.js

This file was deleted.

8 changes: 4 additions & 4 deletions ui/app/models/identity/entity.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { alias } from '@ember/object/computed';
import IdentityModel from './_base';
import apiPath from 'vault/utils/api-path';
import attachCapabilities from 'vault/lib/attach-capabilities';
import lazyCapabilities from 'vault/macros/lazy-capabilities';

const Model = IdentityModel.extend({
formFields: computed(function () {
Expand All @@ -20,11 +21,8 @@ const Model = IdentityModel.extend({
editType: 'kv',
}),
policies: attr({
label: 'Policies',
editType: 'searchSelect',
editType: 'yield',
isSectionHeader: true,
fallbackComponent: 'string-list',
models: ['policy/acl', 'policy/rgp'],
}),
creationTime: attr('string', {
readOnly: true,
Expand All @@ -46,6 +44,8 @@ const Model = IdentityModel.extend({
canEdit: alias('updatePath.canUpdate'),
canRead: alias('updatePath.canRead'),
canAddAlias: alias('aliasPath.canCreate'),
policyPath: lazyCapabilities(apiPath`sys/policies`),
hellobontempo marked this conversation as resolved.
Show resolved Hide resolved
canCreatePolicies: alias('policyPath.canCreate'),
});

export default attachCapabilities(Model, {
Expand Down
8 changes: 3 additions & 5 deletions ui/app/models/identity/group.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,8 @@ export default IdentityModel.extend({
editType: 'kv',
}),
policies: attr({
label: 'Policies',
editType: 'searchSelect',
editType: 'yield',
isSectionHeader: true,
fallbackComponent: 'string-list',
models: ['policy/acl', 'policy/rgp'],
}),
memberGroupIds: attr({
label: 'Member Group IDs',
Expand Down Expand Up @@ -73,7 +70,8 @@ export default IdentityModel.extend({
return numEntities + numGroups > 0;
}
),

policyPath: lazyCapabilities(apiPath`sys/policies`),
canCreatePolicies: alias('policyPath.canCreate'),
alias: belongsTo('identity/group-alias', { async: false, readOnly: true }),
updatePath: identityCapabilities(),
canDelete: alias('updatePath.canDelete'),
Expand Down
1 change: 1 addition & 0 deletions ui/app/styles/components/modal.scss
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
border: 1px solid $grey-light;
max-height: calc(100vh - 70px);
margin-top: 60px;
min-width: calc(100vw * 0.3);

&-head {
border-radius: 0;
Expand Down
Loading