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 14 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
```
3 changes: 3 additions & 0 deletions ui/app/components/identity/edit-form.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,5 +85,8 @@ export default Component.extend({
return this.onSave({ saveType: 'delete', model });
});
},
handlePolicySelection(selection) {
this.model.set('policies', selection);
},
hellobontempo marked this conversation as resolved.
Show resolved Hide resolved
},
});
135 changes: 135 additions & 0 deletions ui/app/components/policy-form.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
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 onSave
* @param {object} model - The parent's model
* @param {object} modelData - If @model isn't passed in, @modelData is passed to create the record
* @param {string} onCancel - callback triggered when cancel button is clicked
* @param {string} onSave - callback triggered when save button is clicked
*/

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;
@tracked createdModel = null; // set by createRecord() after policyType is selected
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 be an entity named James Thomas
# or the '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"
}
`,
};

get model() {
// the SS + modal form receives @modelData instead of @model
return this.args.model ? this.args.model : this.createdModel;
hellobontempo marked this conversation as resolved.
Show resolved Hide resolved
}

@task
*save(event) {
event.preventDefault();
try {
const { isNew, name, policyType } = this.model;
yield this.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.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.model.name = target.value.toLowerCase();
}

@action
async setPolicyType(type) {
if (this.createdModel) this.cleanup();
this.createdModel = await this.store.createRecord(`policy/${type}`, {});
this.createdModel.name = this.args.modelData.name;
hellobontempo marked this conversation as resolved.
Show resolved Hide resolved
}

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

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

cleanup() {
const method = this.model.isNew ? 'unloadRecord' : 'rollbackAttributes';
this.model[method]();
if (this.createdModel) this.createdModel = null;
}
}
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';

let Model = IdentityModel.extend({
formFields: computed(function () {
Expand All @@ -20,11 +21,8 @@ let 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 @@ let 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
3 changes: 3 additions & 0 deletions ui/app/styles/components/modal.scss
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
border: 1px solid $grey-light;
max-height: calc(100vh - 70px);
margin-top: 60px;
min-width: calc(100vw * 0.3);
max-width: calc(100vw * 0.5);
width: fit-content;

&-head {
border-radius: 0;
Expand Down
29 changes: 28 additions & 1 deletion ui/app/templates/components/identity/edit-form.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,34 @@
/>
{{/if}}
{{#each this.model.fields as |attr|}}
<FormField data-test-field={{true}} @attr={{attr}} @model={{@model}} />
<FormField data-test-field={{true}} @attr={{attr}} @model={{@model}}>
<div class="form-section">
{{#if this.model.canCreatePolicies}}
<SearchSelectWithModal
zofskeez marked this conversation as resolved.
Show resolved Hide resolved
@id="policies"
@label="Policies"
@labelClass="title is-4"
@models={{array "policy/acl" "policy/rgp"}}
@inputValue={{@model.policies}}
@onChange={{action "handlePolicySelection"}}
@fallbackComponent="string-list"
@modalFormComponent="policy-form"
@excludeOptions={{array "root"}}
hellobontempo marked this conversation as resolved.
Show resolved Hide resolved
/>
{{else}}
<SearchSelect
@id="policies"
@label="Policies"
@labelClass="title is-4"
@models={{array "policy/acl" "policy/rgp"}}
@inputValue={{@model.policies}}
@onChange={{action "handlePolicySelection"}}
@fallbackComponent="string-list"
@disallowNewItems={{true}}
/>
{{/if}}
</div>
</FormField>
{{/each}}
</div>

Expand Down
2 changes: 1 addition & 1 deletion ui/app/templates/components/oidc/client-form.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@
@id="assignments"
@label="Assignment name"
@subText="Search for an existing assignment, or type a new name to create it."
@model="oidc/assignment"
@models={{array "oidc/assignment"}}
@inputValue={{this.modelAssignments}}
@onChange={{this.handleAssignmentSelection}}
@excludeOptions={{array "allow_all"}}
Expand Down
Loading