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: Add example modal to policy form #21583

Merged
merged 19 commits into from
Jul 20, 2023
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
9a27786
created new JsonTemplate component
malinac02 Jul 1, 2023
33a3a66
used JsonTemplate in modal PolicyTemplate to replace code there
malinac02 Jul 1, 2023
1b44f5b
renamed component and fixed when the editor content shows up
malinac02 Jul 1, 2023
8e01af6
changed PolicyForm to render example modal only conditionally. added …
malinac02 Jul 5, 2023
b80fb98
fixed bug in policy-example.js & edited description of that file, rem…
malinac02 Jul 5, 2023
61d2f2e
changed margin on text to better match Figma design, added example mo…
malinac02 Jul 5, 2023
7a971d4
added tests for PolicyExample in policy-example-tests
malinac02 Jul 10, 2023
936b089
added PolicyForm tests for (1) cancelling the creation/edit of policy…
malinac02 Jul 10, 2023
e4d7987
add changelog
malinac02 Jul 11, 2023
ca3b4cd
clean up code by removing unnecessary comments
malinac02 Jul 11, 2023
58f0569
changed a conditional in policy-form.hbs for better readability (Kian…
malinac02 Jul 12, 2023
e506003
Merge branch 'main' into ui/VAULT-9724/add-policy-template
malinac02 Jul 13, 2023
6798ae3
fixed description in policy-example.js, changed wording for RGP examp…
malinac02 Jul 14, 2023
a381787
added 2 more asserts in policy-form-test.js. Changed some naming for …
malinac02 Jul 14, 2023
228ab97
added EGP policy to PolicyExample component, moved some functionality…
malinac02 Jul 15, 2023
91ecac1
added tests to policy-exammple-test.js and policy-form-test.js to acc…
malinac02 Jul 18, 2023
0b18b22
simplified all PolicyExample tests in policy-exmaple-test.js
malinac02 Jul 19, 2023
f42fb20
removed beforeEach hook in policy-exmaple-test.js
malinac02 Jul 19, 2023
8fcf0b5
Merge branch 'main' into ui/VAULT-9724/add-policy-template
malinac02 Jul 20, 2023
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/21583.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:feature
ui: add example modal to policy form
```
32 changes: 1 addition & 31 deletions ui/app/components/modal-form/policy-template.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -27,37 +27,7 @@
</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 (
<ExternalLink @href="https://github.com/hashicorp/hcl">HCL</ExternalLink>
) 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 @path="/vault/tutorials/policies/policies">ACL policies</DocLink>. They use
<DocLink @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}}
/>
<PolicyExample @policyType={{this.policy.policyType}} />
{{else}}
<Select
@name="policyType"
Expand Down
33 changes: 0 additions & 33 deletions ui/app/components/modal-form/policy-template.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,39 +37,6 @@ export default class PolicyTemplate extends Component {
{ 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) {
Expand Down
77 changes: 58 additions & 19 deletions ui/app/components/policy-form.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@
{{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)}}
Expand All @@ -55,35 +54,53 @@
{{! 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 class="has-top-margin-xs">
<span class="is-size-9 has-text-grey has-bottom-margin-l">
You can use Alt+Tab (Option+Tab on MacOS) in the code editor to skip to the next field.
</span>
{{! Only renders button (and modal) if not already in the "create policy" modal }}
malinac02 marked this conversation as resolved.
Show resolved Hide resolved
{{#if @renderPolicyExampleModal}}
<span class="is-size-9 has-text-grey has-bottom-margin-l">
See
<button
type="button"
class="text-button has-text-info"
{{on "click" (fn (mut this.showTemplateModal))}}
data-test-policy-example
>
example template
</button>.
</span>
{{! Only renders more information if already in the "create policy" modal }}
{{else}}
<p class="has-top-margin-l">
More information about
{{uppercase @model.policyType}}
policies can be found
<DocLink
@path={{if
(eq @model.policyType "acl")
"/vault/docs/concepts/policies#capabilities"
"/vault/tutorials/policies/sentinel#role-governing-policies-rgps"
}}
>
here.
</DocLink>
</p>
{{/if}}
</div>
</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
@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
Expand All @@ -105,4 +122,26 @@
</button>
</div>
</div>
</form>
</form>
{{! SAMPLE POLICY MODAL. Only renders modal if not already in create policy modal }}
{{#if @renderPolicyExampleModal}}
<Modal
@title="Example {{uppercase @model.policyType}} Policy"
@onClose={{fn (mut this.showTemplateModal) false}}
@isActive={{this.showTemplateModal}}
@showCloseButton={{true}}
>
<section class="modal-card-body">
{{! code-mirror modifier does not render value initially until focus event fires }}
{{! wait until the Modal is rendered and then show the PolicyExample (contains JsonEditor) }}
{{#if this.showTemplateModal}}
<PolicyExample @policyType={{@model.policyType}} />
{{/if}}
</section>
<div class="modal-card-head has-border-top-light">
<button type="button" class="button" {{on "click" (fn (mut this.showTemplateModal) false)}} data-test-close-modal>
Close
</button>
</div>
</Modal>
{{/if}}
3 changes: 3 additions & 0 deletions ui/app/components/policy-form.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,21 @@ import { tracked } from '@glimmer/tracking';
* @model={{this.model}}
* @onSave={{transition-to "vault.cluster.policy.show" this.model.policyType this.model.name}}
* @onCancel={{transition-to "vault.cluster.policies.index"}}
* @renderPolicyExampleModal={{true}}
* />
* ```
* @callback onCancel - callback triggered when cancel button is clicked
* @callback onSave - callback triggered when save button is clicked. Passes saved model
* @param {object} model - ember data model from createRecord
* @param {boolean} renderPolicyExampleModal - whether or not the policy form should render the modal containing the policy example
*/

export default class PolicyFormComponent extends Component {
@service flashMessages;

@tracked errorBanner = '';
@tracked showFileUpload = false;
@tracked showTemplateModal = false;

@task
*save(event) {
Expand Down
1 change: 1 addition & 0 deletions ui/app/templates/vault/cluster/policies/create.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,5 @@
@model={{this.model}}
@onSave={{transition-to "vault.cluster.policy.show" this.model.policyType this.model.name}}
@onCancel={{transition-to "vault.cluster.policies.index"}}
@renderPolicyExampleModal={{true}}
/>
1 change: 1 addition & 0 deletions ui/app/templates/vault/cluster/policy/edit.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,5 @@
@model={{this.model}}
@onSave={{transition-to "vault.cluster.policy.show" this.model.policyType this.model.name}}
@onCancel={{transition-to "vault.cluster.policy.show" this.model.policyType this.model.name}}
@renderPolicyExampleModal={{true}}
/>
1 change: 1 addition & 0 deletions ui/lib/core/addon/components/json-editor.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
}}
class={{if @readOnly "readonly-codemirror"}}
data-test-component="code-mirror-modifier"
data-test-example-modal-json-text
></div>

{{#if @helpText}}
Expand Down
43 changes: 43 additions & 0 deletions ui/lib/core/addon/components/policy-example.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<div class="has-bottom-margin-s">
{{#if (eq @policyType "acl")}}
<p data-test-example-modal-text-acl>
ACL Policies are written in Hashicorp Configuration Language (
<ExternalLink @href="https://github.com/hashicorp/hcl">HCL</ExternalLink>
) 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" data-test-example-modal-text-rgp>
Role Governing Policies (RGPs) are tied to client tokens or identities which is similar to
<DocLink @path="/vault/tutorials/policies/policies">ACL policies</DocLink>. They use
<DocLink @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
malinac02 marked this conversation as resolved.
Show resolved Hide resolved
<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={{this.policyTemplate}} @mode="ruby" @readOnly={{true}} @showToolbar={{true}} />
<div class="has-bottom-margin-m has-top-padding-s">
<p>
More information about
{{uppercase @policyType}}
policies can be found
<DocLink
@path={{if
malinac02 marked this conversation as resolved.
Show resolved Hide resolved
(eq @policyType "acl")
"/vault/docs/concepts/policies#capabilities"
"/vault/tutorials/policies/sentinel#role-governing-policies-rgps"
}}
data-test-example-modal-information-link
>
here.
</DocLink>
</p>
</div>
78 changes: 78 additions & 0 deletions ui/lib/core/addon/components/policy-example.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/

import Component from '@glimmer/component';

/**
* @module PolicyExample
* PolicyExample component is meant to show an example of a policy. The PolicyExample can rendered by the PolicyTemplate
* component (as in the first example below). PolicyExample can also be rendered in a modal (second example below), which
* is how it is conditionally rendered by the PolicyForm component.
malinac02 marked this conversation as resolved.
Show resolved Hide resolved
*
* @example
* <PolicyExample
* @policyType={{@model.policyType}}
* />
*
* @example (in modal)
* <Modal
* @onClose={{fn (mut this.showTemplateModal) false}}
* @isActive={{this.showTemplateModal}}
* >
* <section class="modal-card-body">
* {{! code-mirror modifier does not render value initially until focus event fires }}
* {{! wait until the Modal is rendered and then show the PolicyExample (contains JsonEditor) }}
* {{#if this.showTemplateModal}}
* <PolicyExample @policyType={{@model.policyType}}/>
* {{/if}}
* </section>
* <div class="modal-card-head has-border-top-light">
* <button type="button" class="button" {{on "click" (fn (mut this.showTemplateModal) false)}} data-test-close-modal>
* Close
* </button>
* </div>
* </Modal>
* ```
* @param {string} policyType - policy type to decide which template to render; can either be "acl" or "rgp"
*/

export default class PolicyExampleComponent extends Component {
// formatting here is purposeful so that whitespace renders correctly in JsonEditor
policyTemplates = {
malinac02 marked this conversation as resolved.
Show resolved Hide resolved
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"
}
`,
};
get policyTemplate() {
return this.policyTemplates[this.args.policyType];
}
malinac02 marked this conversation as resolved.
Show resolved Hide resolved
}
1 change: 1 addition & 0 deletions ui/lib/core/app/components/policy-example.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from 'core/components/policy-example';
Loading