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 all 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/modal-form/FYI.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
These templates parent form components that render within a modal to create inline items via SearchSelectWithModal.
The modal opens when a user searches for an item that doesn't exist and clicks 'No results found for "${term}". Click here to create it.'
The templates are rendered using the {{component}} helper in 'search-select-with-modal.hbs' and are responsible for creating the model passed to the form.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<Oidc::AssignmentForm @onSave={{this.onSave}} @model={{this.assignment}} @onCancel={{@onCancel}} />
36 changes: 36 additions & 0 deletions ui/app/components/modal-form/oidc-assignment-template.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import Component from '@glimmer/component';
import { action } from '@ember/object';
import { inject as service } from '@ember/service';
import { tracked } from '@glimmer/tracking';

/**
* @module ModalForm::OidcAssignmentTemplate
* ModalForm::OidcAssignmentTemplate components render within a modal and create a model using the input from the search select. The model is passed to the oidc/assignment-form.
*
* @example
* <ModalForm::OidcAssignmentTemplate
* @nameInput="new-item-name"
* @onSave={{this.closeModal}}
* @onCancel={{@onCancel}}
* />
* ```
* @callback onCancel - callback triggered when cancel button is clicked
* @callback onSave - callback triggered when save button is clicked
* @param {string} nameInput - the name of the newly created assignment
*/

export default class OidcAssignmentTemplate extends Component {
@service store;
@tracked assignment = null; // model record passed to oidc/assignment-form

constructor() {
super(...arguments);
this.assignment = this.store.createRecord('oidc/assignment', { name: this.args.nameInput });
}

@action onSave(assignmentModel) {
this.args.onSave(assignmentModel);
// Reset component assignment for next use
this.assignment = null;
}
}
77 changes: 77 additions & 0 deletions ui/app/components/modal-form/policy-template.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
{{#if this.policy.policyType}}
<nav class="tabs">
<ul>
<li class={{unless this.showExamplePolicy "active"}}>
<button
data-test-tab-your-policy
type="button"
name="form"
class="link link-plain tab has-text-weight-semibold {{unless this.showExamplePolicy ' is-active'}}"
{{on "click" (fn (mut this.showExamplePolicy) false)}}
>
Your Policy
</button>
</li>
<li class={{if this.showExamplePolicy "active"}}>
<button
data-test-tab-example-policy
type="button"
name="form"
class="link link-plain tab has-text-weight-semibold {{if this.showExamplePolicy ' is-active'}}"
{{on "click" (fn (mut this.showExamplePolicy) true)}}
>
Example Policy
</button>
</li>
</ul>
</nav>
{{/if}}
{{#if this.showExamplePolicy}}
<div class="has-bottom-margin-s">
{{#if (eq this.policy.policyType "acl")}}
<p>
ACL Policies are written in Hashicorp Configuration Language (
<DocLink @host="https://github.com/hashicorp/hcl">HCL</DocLink>
) or JSON and describe which paths in Vault a user or machine is allowed to access. Here is an example policy:
</p>
{{else}}
<p class="has-bottom-margin-s">
Role Governing Policies (RGPs) are tied to client tokens or identities which is similar to
<DocLink @host="https://developer.hashicorp.com" @path="/vault/tutorials/policies/policies">ACL policies</DocLink>.
They use
<DocLink @host="https://developer.hashicorp.com" @path="/vault/docs/enterprise/sentinel">Sentinel</DocLink>
as a language framework to enable fine-grained policy decisions.
</p>
<p>
Here is an example policy that uses RGP to restrict access to the
<code class="tag is-marginless is-paddingless">admin</code>
policy such that a user named James or has the
<code class="tag is-marginless is-paddingless">Team Lead</code>
role can manage the
<code class="tag is-marginless is-paddingless">admin</code>
policy:
</p>
{{/if}}
</div>
<JsonEditor
@value={{get this.policyTemplates this.policy.policyType}}
@mode="ruby"
@readOnly={{true}}
@showToolbar={{true}}
/>
{{else}}
<Select
@name="policyType"
@label="Type"
@options={{this.policyOptions}}
@isFullwidth={{true}}
@selectedValue={{this.policy.policyType}}
@onChange={{this.setPolicyType}}
@noDefault={{true}}
/>
{{#if this.policy.policyType}}
<PolicyForm @onSave={{this.onSave}} @model={{this.policy}} @onCancel={{@onCancel}} />
{{else}}
<EmptyState @title="No policy type selected" @message="Select a policy type to continue creating." />
{{/if}}
{{/if}}
82 changes: 82 additions & 0 deletions ui/app/components/modal-form/policy-template.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import Component from '@glimmer/component';
import { action } from '@ember/object';
import { inject as service } from '@ember/service';
import { tracked } from '@glimmer/tracking';

/**
* @module ModalForm::PolicyTemplate
* ModalForm::PolicyTemplate components are meant to render within a modal for creating a new policy of unknown type.
*
* @example
* <ModalForm::PolicyTemplate
* @nameInput="new-item-name"
* @onSave={{this.closeModal}}
* @onCancel={{this.closeModal}}
* />
* ```
* @callback onCancel - callback triggered when cancel button is clicked
* @callback onSave - callback triggered when save button is clicked
* @param {string} nameInput - the name of the newly created policy
*/

export default class PolicyTemplate extends Component {
@service store;
@service version;

@tracked policy = null; // model record passed to policy-form
@tracked showExamplePolicy = false;

get policyOptions() {
return [
{ 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: `
# 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"
}
`,
};

@action
setPolicyType(type) {
if (this.policy) this.policy.unloadRecord(); // if user selects a different type, clear from store before creating a new record
// Create form model once type is chosen
this.policy = this.store.createRecord(`policy/${type}`, { name: this.args.nameInput });
}

@action
onSave(policyModel) {
this.args.onSave(policyModel);
// Reset component policy for next use
this.policy = null;
}
}
7 changes: 3 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,10 @@ 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 onCancel - callback triggered when cancel button is clicked
* @callback onSave - callback triggered when save button is clicked*
*/

export default class OidcAssignmentFormComponent extends Component {
Expand Down
8 changes: 3 additions & 5 deletions ui/app/components/oidc/client-form.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,13 @@ 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 {
@service store;
@service flashMessages;
@tracked modelValidations;
@tracked errorBanner;
Expand Down
109 changes: 109 additions & 0 deletions ui/app/components/policy-form.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
<form {{on "submit" (perform this.save)}} data-test-policy-form>
<div class="box is-bottomless is-fullwidth is-marginless">
<MessageError @errorMessage={{this.errorBanner}} />
<NamespaceReminder @mode={{if @model.isNew "create" "edit"}} @noun="policy" />
{{#if @model.isNew}}
<div class="field">
<label for="policy-name" class="is-label">Name</label>
<div class="control">
<Input
@type="text"
@value={{lowercase @model.name}}
id="policy-name"
class="input"
{{on "input" this.setModelName}}
data-test-policy-input="name"
/>
</div>
</div>
{{/if}}
<div class="field">
{{#if @model.isNew}}
<Toolbar>
<label class="is-label">Policy</label>
<ToolbarActions>
<div class="toolbar-separator"></div>
<div class="control is-flex">
<Input
id="fileUploadToggle"
@type="checkbox"
name="fileUploadToggle"
class="switch is-rounded is-success is-small"
@checked={{this.showFileUpload}}
{{on "change" (fn (mut this.showFileUpload) (not this.showFileUpload))}}
data-test-policy-edit-toggle
/>
<label for="fileUploadToggle">Upload file</label>
</div>
</ToolbarActions>
</Toolbar>
{{#if this.showFileUpload}}
<TextFile @inputOnly={{true}} @file={{this.file}} @onChange={{this.setPolicyFromFile}} />
{{else}}
<JsonEditor
@title="Policy"
@helpText="You can use Alt+Tab (Option+Tab on MacOS) in the code editor to skip to the next field"
@showToolbar={{false}}
@value={{@model.policy}}
@valueUpdated={{action (mut @model.policy)}}
@mode="ruby"
@extraKeys={{hash Shift-Enter=(perform this.save)}}
data-test-policy-editor
/>
{{/if}}
{{else}}
{{! EDITING - no file upload toggle}}
<JsonEditor
@title="Policy"
@helpText="You can use Alt+Tab (Option+Tab on MacOS) in the code editor to skip to the next field"
@value={{@model.policy}}
@valueUpdated={{action (mut @model.policy)}}
@mode="ruby"
@extraKeys={{hash Shift-Enter=(perform this.save)}}
data-test-policy-editor
/>
{{/if}}
</div>
{{#each @model.additionalAttrs as |attr|}}
<FormField data-test-field={{true}} @attr={{attr}} @model={{@model}} />
{{/each}}
</div>
<div class="has-bottom-margin-m">
<p>
More information about
{{uppercase @model.policyType}}
policies can be found
<DocLink
@host="https://developer.hashicorp.com"
@path={{if
(eq @model.policyType "acl")
"/vault/docs/concepts/policies#capabilities"
"/vault/tutorials/policies/sentinel#role-governing-policies-rgps"
}}
>
here.
</DocLink>
</p>
</div>
<div class="field is-grouped box is-fullwidth is-bottomless">
<div class="control">
<button
type="submit"
class="button is-primary {{if this.save.isRunning 'is-loading'}}"
disabled={{this.save.isRunning}}
data-test-policy-save
>
{{if @model.isNew "Create policy" "Save"}}
</button>
<button
type="button"
class="button has-left-margin-s"
disabled={{this.save.isRunning}}
{{on "click" this.cancel}}
data-test-policy-cancel
>
Cancel
</button>
</div>
</div>
</form>
Loading