diff --git a/addons/api/addon/abilities/account.js b/addons/api/addon/abilities/account.js
new file mode 100644
index 000000000..147f73b9f
--- /dev/null
+++ b/addons/api/addon/abilities/account.js
@@ -0,0 +1,15 @@
+import ModelAbility from './model';
+
+/**
+ * Provides abilities for accounts.
+ */
+export default class AccountAbility extends ModelAbility {
+ // =permissions
+
+ /**
+ * @type {boolean}
+ */
+ get canSetPassword() {
+ return this.hasAuthorizedAction('set-password');
+ }
+}
diff --git a/addons/api/addon/abilities/group.js b/addons/api/addon/abilities/group.js
new file mode 100644
index 000000000..810cc82c2
--- /dev/null
+++ b/addons/api/addon/abilities/group.js
@@ -0,0 +1,22 @@
+import ModelAbility from './model';
+
+/**
+ * Provides abilities for groups.
+ */
+export default class GroupAbility extends ModelAbility {
+ // =permissions
+
+ /**
+ * @type {boolean}
+ */
+ get canAddMembers() {
+ return this.hasAuthorizedAction('add-members');
+ }
+
+ /**
+ * @type {boolean}
+ */
+ get canRemoveMembers() {
+ return this.hasAuthorizedAction('remove-members');
+ }
+}
diff --git a/addons/api/addon/abilities/host-set.js b/addons/api/addon/abilities/host-set.js
new file mode 100644
index 000000000..088683178
--- /dev/null
+++ b/addons/api/addon/abilities/host-set.js
@@ -0,0 +1,22 @@
+import ModelAbility from './model';
+
+/**
+ * Provides abilities for host sets.
+ */
+export default class UserAbility extends ModelAbility {
+ // =permissions
+
+ /**
+ * @type {boolean}
+ */
+ get canAddHosts() {
+ return this.hasAuthorizedAction('add-hosts');
+ }
+
+ /**
+ * @type {boolean}
+ */
+ get canRemoveHosts() {
+ return this.hasAuthorizedAction('remove-hosts');
+ }
+}
diff --git a/addons/api/addon/abilities/role.js b/addons/api/addon/abilities/role.js
new file mode 100644
index 000000000..f0c6d62ec
--- /dev/null
+++ b/addons/api/addon/abilities/role.js
@@ -0,0 +1,29 @@
+import ModelAbility from './model';
+
+/**
+ * Provides abilities for roles.
+ */
+export default class RoleAbility extends ModelAbility {
+ // =permissions
+
+ /**
+ * @type {boolean}
+ */
+ get canSetGrants() {
+ return this.hasAuthorizedAction('set-grants');
+ }
+
+ /**
+ * @type {boolean}
+ */
+ get canAddPrincipals() {
+ return this.hasAuthorizedAction('add-principals');
+ }
+
+ /**
+ * @type {boolean}
+ */
+ get canRemovePrincipals() {
+ return this.hasAuthorizedAction('remove-principals');
+ }
+}
diff --git a/addons/api/addon/abilities/target.js b/addons/api/addon/abilities/target.js
index fb6b30d33..11516c3b5 100644
--- a/addons/api/addon/abilities/target.js
+++ b/addons/api/addon/abilities/target.js
@@ -1,7 +1,7 @@
import ModelAbility from './model';
/**
- * Provides a `connect` ability for targets.
+ * Provides abilities for targets.
*/
export default class TargetAbility extends ModelAbility {
// =permissions
diff --git a/addons/api/addon/abilities/user.js b/addons/api/addon/abilities/user.js
new file mode 100644
index 000000000..dd2440fe7
--- /dev/null
+++ b/addons/api/addon/abilities/user.js
@@ -0,0 +1,22 @@
+import ModelAbility from './model';
+
+/**
+ * Provides abilities for users.
+ */
+export default class UserAbility extends ModelAbility {
+ // =permissions
+
+ /**
+ * @type {boolean}
+ */
+ get canAddAccounts() {
+ return this.hasAuthorizedAction('add-accounts');
+ }
+
+ /**
+ * @type {boolean}
+ */
+ get canRemoveAccounts() {
+ return this.hasAuthorizedAction('remove-accounts');
+ }
+}
diff --git a/addons/api/app/abilities/account.js b/addons/api/app/abilities/account.js
new file mode 100644
index 000000000..e64435a5e
--- /dev/null
+++ b/addons/api/app/abilities/account.js
@@ -0,0 +1 @@
+export { default } from 'api/abilities/account';
diff --git a/addons/api/app/abilities/group.js b/addons/api/app/abilities/group.js
new file mode 100644
index 000000000..308aacba4
--- /dev/null
+++ b/addons/api/app/abilities/group.js
@@ -0,0 +1 @@
+export { default } from 'api/abilities/group';
diff --git a/addons/api/app/abilities/host-set.js b/addons/api/app/abilities/host-set.js
new file mode 100644
index 000000000..37ccd5e81
--- /dev/null
+++ b/addons/api/app/abilities/host-set.js
@@ -0,0 +1 @@
+export { default } from 'api/abilities/host-set';
diff --git a/addons/api/app/abilities/role.js b/addons/api/app/abilities/role.js
new file mode 100644
index 000000000..aefb6fd13
--- /dev/null
+++ b/addons/api/app/abilities/role.js
@@ -0,0 +1 @@
+export { default } from 'api/abilities/role';
diff --git a/addons/api/app/abilities/user.js b/addons/api/app/abilities/user.js
new file mode 100644
index 000000000..b02ba6ee2
--- /dev/null
+++ b/addons/api/app/abilities/user.js
@@ -0,0 +1 @@
+export { default } from 'api/abilities/user';
diff --git a/addons/api/mirage/factories/account.js b/addons/api/mirage/factories/account.js
index 5b7a14c09..f5da7b5af 100644
--- a/addons/api/mirage/factories/account.js
+++ b/addons/api/mirage/factories/account.js
@@ -9,6 +9,7 @@ export default factory.extend({
'read',
'update',
'delete',
+ 'set-password',
],
attributes() {
switch (this.type) {
diff --git a/addons/api/mirage/factories/group.js b/addons/api/mirage/factories/group.js
index 1f7256543..6a16d3fb0 100644
--- a/addons/api/mirage/factories/group.js
+++ b/addons/api/mirage/factories/group.js
@@ -9,6 +9,8 @@ export default factory.extend({
'read',
'update',
'delete',
+ 'add-members',
+ 'remove-members',
],
id: (i) => `group-${i}`,
diff --git a/addons/api/mirage/factories/host-set.js b/addons/api/mirage/factories/host-set.js
index def5ff70f..3b88650b7 100644
--- a/addons/api/mirage/factories/host-set.js
+++ b/addons/api/mirage/factories/host-set.js
@@ -8,6 +8,8 @@ export default factory.extend({
'read',
'update',
'delete',
+ 'add-hosts',
+ 'remove-hosts',
],
id: (i) => `host-set-id-${i}`,
});
diff --git a/addons/api/mirage/factories/role.js b/addons/api/mirage/factories/role.js
index a057fc29f..be13712b8 100644
--- a/addons/api/mirage/factories/role.js
+++ b/addons/api/mirage/factories/role.js
@@ -9,6 +9,9 @@ export default factory.extend({
'read',
'update',
'delete',
+ 'set-grants',
+ 'add-principals',
+ 'remove-principals',
],
// eslint-disable-next-line ember/avoid-leaking-state-in-ember-objects
grant_strings: () => [
diff --git a/addons/api/mirage/factories/user.js b/addons/api/mirage/factories/user.js
index b8463506d..837adba44 100644
--- a/addons/api/mirage/factories/user.js
+++ b/addons/api/mirage/factories/user.js
@@ -8,6 +8,8 @@ export default factory.extend({
'read',
'update',
'delete',
+ 'add-accounts',
+ 'remove-accounts',
],
id: (i) => `user-${i}`,
});
diff --git a/addons/api/tests/unit/abilities/account-test-.js b/addons/api/tests/unit/abilities/account-test-.js
new file mode 100644
index 000000000..afd89329d
--- /dev/null
+++ b/addons/api/tests/unit/abilities/account-test-.js
@@ -0,0 +1,17 @@
+import { module, test } from 'qunit';
+import { setupTest } from 'ember-qunit';
+
+module('Unit | Abilities | Account', function (hooks) {
+ setupTest(hooks);
+
+ test('it reflects when a given account may set password based on authorized_actions', function (assert) {
+ assert.expect(2);
+ const service = this.owner.lookup('service:can');
+ const model = {
+ authorized_actions: ['set-password'],
+ };
+ assert.ok(service.can('setPassword target', model));
+ model.authorized_actions = [];
+ assert.notOk(service.can('setPassword target', model));
+ });
+});
diff --git a/addons/api/tests/unit/abilities/group-test.js b/addons/api/tests/unit/abilities/group-test.js
new file mode 100644
index 000000000..a53be03c2
--- /dev/null
+++ b/addons/api/tests/unit/abilities/group-test.js
@@ -0,0 +1,28 @@
+import { module, test } from 'qunit';
+import { setupTest } from 'ember-qunit';
+
+module('Unit | Abilities | Group', function (hooks) {
+ setupTest(hooks);
+
+ test('it reflects when a given group resource may add members based on authorized_actions', function (assert) {
+ assert.expect(2);
+ const service = this.owner.lookup('service:can');
+ const model = {
+ authorized_actions: ['add-members'],
+ };
+ assert.ok(service.can('addMembers group', model));
+ model.authorized_actions = [];
+ assert.notOk(service.can('addMembers group', model));
+ });
+
+ test('it reflects when a given group resource may remove members based on authorized_actions', function (assert) {
+ assert.expect(2);
+ const service = this.owner.lookup('service:can');
+ const model = {
+ authorized_actions: ['remove-members'],
+ };
+ assert.ok(service.can('removeMembers group', model));
+ model.authorized_actions = [];
+ assert.notOk(service.can('removeMembers group', model));
+ });
+});
diff --git a/addons/api/tests/unit/abilities/host-set-test.js b/addons/api/tests/unit/abilities/host-set-test.js
new file mode 100644
index 000000000..24b0efa15
--- /dev/null
+++ b/addons/api/tests/unit/abilities/host-set-test.js
@@ -0,0 +1,28 @@
+import { module, test } from 'qunit';
+import { setupTest } from 'ember-qunit';
+
+module('Unit | Abilities | Host Set', function (hooks) {
+ setupTest(hooks);
+
+ test('it reflects when a given host set resource may add hosts based on authorized_actions', function (assert) {
+ assert.expect(2);
+ const service = this.owner.lookup('service:can');
+ const model = {
+ authorized_actions: ['add-hosts'],
+ };
+ assert.ok(service.can('addHosts hostSet', model));
+ model.authorized_actions = [];
+ assert.notOk(service.can('addHosts hostSet', model));
+ });
+
+ test('it reflects when a given hostSet resource may remove hosts based on authorized_actions', function (assert) {
+ assert.expect(2);
+ const service = this.owner.lookup('service:can');
+ const model = {
+ authorized_actions: ['remove-hosts'],
+ };
+ assert.ok(service.can('removeHosts hostSet', model));
+ model.authorized_actions = [];
+ assert.notOk(service.can('removeHosts hostSet', model));
+ });
+});
diff --git a/addons/api/tests/unit/abilities/role-test.js b/addons/api/tests/unit/abilities/role-test.js
new file mode 100644
index 000000000..462743446
--- /dev/null
+++ b/addons/api/tests/unit/abilities/role-test.js
@@ -0,0 +1,39 @@
+import { module, test } from 'qunit';
+import { setupTest } from 'ember-qunit';
+
+module('Unit | Abilities | Role', function (hooks) {
+ setupTest(hooks);
+
+ test('it reflects when a given role may set grants based on authorized_actions', function (assert) {
+ assert.expect(2);
+ const service = this.owner.lookup('service:can');
+ const model = {
+ authorized_actions: ['set-grants'],
+ };
+ assert.ok(service.can('setGrants role', model));
+ model.authorized_actions = [];
+ assert.notOk(service.can('setGrants role', model));
+ });
+
+ test('it reflects when a given role may add principals based on authorized_actions', function (assert) {
+ assert.expect(2);
+ const service = this.owner.lookup('service:can');
+ const model = {
+ authorized_actions: ['add-principals'],
+ };
+ assert.ok(service.can('addPrincipals role', model));
+ model.authorized_actions = [];
+ assert.notOk(service.can('addPrincipals role', model));
+ });
+
+ test('it reflects when a given role may remove principals based on authorized_actions', function (assert) {
+ assert.expect(2);
+ const service = this.owner.lookup('service:can');
+ const model = {
+ authorized_actions: ['remove-principals'],
+ };
+ assert.ok(service.can('removePrincipals role', model));
+ model.authorized_actions = [];
+ assert.notOk(service.can('removePrincipals role', model));
+ });
+});
diff --git a/addons/api/tests/unit/abilities/user-test.js b/addons/api/tests/unit/abilities/user-test.js
new file mode 100644
index 000000000..33d8f78e7
--- /dev/null
+++ b/addons/api/tests/unit/abilities/user-test.js
@@ -0,0 +1,28 @@
+import { module, test } from 'qunit';
+import { setupTest } from 'ember-qunit';
+
+module('Unit | Abilities | User', function (hooks) {
+ setupTest(hooks);
+
+ test('it reflects when a given user resource may add accounts based on authorized_actions', function (assert) {
+ assert.expect(2);
+ const service = this.owner.lookup('service:can');
+ const model = {
+ authorized_actions: ['add-accounts'],
+ };
+ assert.ok(service.can('addAccounts user', model));
+ model.authorized_actions = [];
+ assert.notOk(service.can('addAccounts user', model));
+ });
+
+ test('it reflects when a given user resource may remove accounts based on authorized_actions', function (assert) {
+ assert.expect(2);
+ const service = this.owner.lookup('service:can');
+ const model = {
+ authorized_actions: ['remove-accounts'],
+ };
+ assert.ok(service.can('removeAccounts user', model));
+ model.authorized_actions = [];
+ assert.notOk(service.can('removeAccounts user', model));
+ });
+});
diff --git a/addons/rose/package.json b/addons/rose/package.json
index 8a0a04d78..c566535c7 100644
--- a/addons/rose/package.json
+++ b/addons/rose/package.json
@@ -29,7 +29,6 @@
"doc:toc": "doctoc README.md"
},
"dependencies": {
- "babel-loader": "^8.2.2",
"ember-cli-babel": "^7.26.6",
"ember-cli-htmlbars": "^5.7.1",
"ember-focus-trap": "^0.7.0",
@@ -39,6 +38,8 @@
"emberx-select": "github:adopted-ember-addons/emberx-select#bc5f774"
},
"devDependencies": {
+ "@babel/core": "^7.15.5",
+ "babel-loader": "^8.2.2",
"@ember/optional-features": "^2.0.0",
"@ember/test-helpers": "^2.4.2",
"@glimmer/component": "^1.0.4",
@@ -47,6 +48,7 @@
"@storybook/addon-actions": "^6.3.8",
"@storybook/addon-docs": "^6.3.8",
"@storybook/addon-knobs": "^6.3.1",
+ "@storybook/addons": "^6.3.9",
"@storybook/ember": "^6.3.8",
"@storybook/ember-cli-storybook": "^0.4.0",
"babel-eslint": "^10.1.0",
diff --git a/ui/admin/app/components/form/role/grants/index.hbs b/ui/admin/app/components/form/role/grants/index.hbs
index 79766a2e1..d6aaad424 100644
--- a/ui/admin/app/components/form/role/grants/index.hbs
+++ b/ui/admin/app/components/form/role/grants/index.hbs
@@ -1,39 +1,43 @@
-
-
- {{! Add grant field (only one per form) }}
-
-
-
- {{t 'resources.role.grant.actions.create'}}
-
-
-
-
-
-
- {{t 'actions.add'}}
-
-
-
-
-
-
+{{#if (can 'setGrants role' @model)}}
+
+
+ {{! Add grant field (only one per form) }}
+
+
+
+ {{t
+ 'resources.role.grant.actions.create'
+ }}
+
+
+
+
+
+
+ {{t 'actions.add'}}
+
+
+
+
+
+
+{{/if}}
@@ -72,23 +77,27 @@
-
- {{t 'actions.remove'}}
-
+ {{#if (can 'setGrants role' @model)}}
+
+ {{t 'actions.remove'}}
+
+ {{/if}}
{{/each}}
-
+ {{#if (can 'setGrants role' @model)}}
+
+ {{/if}}
\ No newline at end of file
diff --git a/ui/admin/app/templates/scopes/scope/auth-methods/auth-method/accounts/account/-navigation.hbs b/ui/admin/app/templates/scopes/scope/auth-methods/auth-method/accounts/account/-navigation.hbs
index da08d1834..12ca372ce 100644
--- a/ui/admin/app/templates/scopes/scope/auth-methods/auth-method/accounts/account/-navigation.hbs
+++ b/ui/admin/app/templates/scopes/scope/auth-methods/auth-method/accounts/account/-navigation.hbs
@@ -5,10 +5,12 @@
{{t 'titles.details'}}
{{#if @model.isPassword}}
-
- {{t 'actions.set-password'}}
-
+ {{#if (can 'setPassword account' @model)}}
+
+ {{t 'actions.set-password'}}
+
+ {{/if}}
{{/if}}
\ No newline at end of file
diff --git a/ui/admin/app/templates/scopes/scope/groups/group/-actions.hbs b/ui/admin/app/templates/scopes/scope/groups/group/-actions.hbs
index 253b39ab0..7dbbf977b 100644
--- a/ui/admin/app/templates/scopes/scope/groups/group/-actions.hbs
+++ b/ui/admin/app/templates/scopes/scope/groups/group/-actions.hbs
@@ -3,9 +3,11 @@
@dropdownRight={{true}}
as |dropdown|
>
-
- {{t 'resources.group.actions.add-members'}}
-
+ {{#if (can 'addMembers group' @model)}}
+
+ {{t 'resources.group.actions.add-members'}}
+
+ {{/if}}
{{#if (can 'delete model' @model)}}
-
-
- {{t 'actions.remove'}}
-
-
+
+ {{t 'actions.remove'}}
+
+
+ {{/if}}
{{/each}}
diff --git a/ui/admin/app/templates/scopes/scope/host-catalogs/host-catalog/host-sets/host-set/-actions.hbs b/ui/admin/app/templates/scopes/scope/host-catalogs/host-catalog/host-sets/host-set/-actions.hbs
index d78e1b940..d0bae064d 100644
--- a/ui/admin/app/templates/scopes/scope/host-catalogs/host-catalog/host-sets/host-set/-actions.hbs
+++ b/ui/admin/app/templates/scopes/scope/host-catalogs/host-catalog/host-sets/host-set/-actions.hbs
@@ -3,16 +3,18 @@
@dropdownRight={{true}}
as |dropdown|
>
-
- {{t 'resources.host-set.actions.create-and-add-host'}}
-
-
- {{t 'resources.host-set.actions.add-hosts'}}
-
+ {{#if (can 'addHosts hostSet' @model)}}
+
+ {{t 'resources.host-set.actions.create-and-add-host'}}
+
+
+ {{t 'resources.host-set.actions.add-hosts'}}
+
+ {{/if}}
{{#if (can 'delete model' @model)}}
-
-
- {{t 'actions.remove'}}
-
-
+
+ {{t 'actions.remove'}}
+
+
+ {{/if}}
{{/each}}
diff --git a/ui/admin/app/templates/scopes/scope/roles/role/-actions.hbs b/ui/admin/app/templates/scopes/scope/roles/role/-actions.hbs
index 88add065c..6c01fcc16 100644
--- a/ui/admin/app/templates/scopes/scope/roles/role/-actions.hbs
+++ b/ui/admin/app/templates/scopes/scope/roles/role/-actions.hbs
@@ -3,9 +3,11 @@
@dropdownRight={{true}}
as |dropdown|
>
-
- {{t 'resources.role.principal.actions.add-principals'}}
-
+ {{#if (can 'addPrincipals role' @model)}}
+
+ {{t 'resources.role.principal.actions.add-principals'}}
+
+ {{/if}}
{{#if (can 'delete model' @model)}}
-
-
- {{t 'actions.remove'}}
-
-
+
+ {{t 'actions.remove'}}
+
+
+ {{/if}}
{{/each}}
diff --git a/ui/admin/app/templates/scopes/scope/users/user/-actions.hbs b/ui/admin/app/templates/scopes/scope/users/user/-actions.hbs
index 280fd98bb..998af871b 100644
--- a/ui/admin/app/templates/scopes/scope/users/user/-actions.hbs
+++ b/ui/admin/app/templates/scopes/scope/users/user/-actions.hbs
@@ -3,9 +3,11 @@
@dropdownRight={{true}}
as |dropdown|
>
-
- {{t 'resources.user.actions.add-accounts'}}
-
+ {{#if (can 'addAccounts user' @model)}}
+
+ {{t 'resources.user.actions.add-accounts'}}
+
+ {{/if}}
{{#if (can 'delete model' @model)}}
-
-
- {{t 'actions.remove'}}
-
-
+
+ {{t 'actions.remove'}}
+
+
+ {{/if}}
{{/each}}
diff --git a/ui/admin/tests/acceptance/accounts/set-password-test.js b/ui/admin/tests/acceptance/accounts/set-password-test.js
index 677e9bd7c..89f9ce3e7 100644
--- a/ui/admin/tests/acceptance/accounts/set-password-test.js
+++ b/ui/admin/tests/acceptance/accounts/set-password-test.js
@@ -49,7 +49,8 @@ module('Acceptance | accounts | set password', function (hooks) {
urls.authMethods = `${urls.orgScope}/auth-methods`;
urls.authMethod = `${urls.authMethods}/${instances.authMethod.id}`;
urls.accounts = `${urls.authMethod}/accounts`;
- urls.setPassword = `${urls.accounts}/${instances.account.id}/set-password`;
+ urls.account = `${urls.accounts}/${instances.account.id}`;
+ urls.setPassword = `${urls.account}/set-password`;
});
test('visiting account set password', async function (assert) {
@@ -59,6 +60,26 @@ module('Acceptance | accounts | set password', function (hooks) {
assert.equal(currentURL(), urls.setPassword);
});
+ test('can navigate to route with proper authorization', async function (assert) {
+ assert.expect(1);
+ await visit(urls.account);
+ assert.ok(
+ find(`.rose-layout-page-navigation [href="${urls.setPassword}"]`)
+ );
+ });
+
+ test('cannot navigate to route without proper authorization', async function (assert) {
+ assert.expect(1);
+ const authorized_actions = instances.account.authorized_actions.filter(
+ (item) => item !== 'set-password'
+ );
+ instances.account.update({ authorized_actions });
+ await visit(urls.account);
+ assert.notOk(
+ find(`.rose-layout-page-navigation [href="${urls.setPassword}"]`)
+ );
+ });
+
test('can set a new password for account', async function (assert) {
assert.expect(1);
this.server.post(
diff --git a/ui/admin/tests/acceptance/groups/members-test.js b/ui/admin/tests/acceptance/groups/members-test.js
index 2d615b8ba..295db8404 100644
--- a/ui/admin/tests/acceptance/groups/members-test.js
+++ b/ui/admin/tests/acceptance/groups/members-test.js
@@ -68,6 +68,16 @@ module('Acceptance | groups | members', function (hooks) {
assert.equal(findAll('tbody tr').length, membersCount - 1);
});
+ test('cannot remove a member without proper authorization', async function (assert) {
+ assert.expect(1);
+ const authorized_actions = instances.group.authorized_actions.filter(
+ (item) => item !== 'remove-members'
+ );
+ instances.group.update({ authorized_actions });
+ await visit(urls.members);
+ assert.notOk(find('tbody tr .rose-dropdown-button-danger'));
+ });
+
test('shows error message on member remove', async function (assert) {
assert.expect(2);
this.server.post('/groups/:idMethod', () => {
@@ -95,6 +105,22 @@ module('Acceptance | groups | members', function (hooks) {
assert.equal(currentURL(), urls.addMembers);
});
+ test('can navigate to add members with proper authorization', async function (assert) {
+ assert.expect(1);
+ await visit(urls.group);
+ assert.ok(find(`[href="${urls.addMembers}"]`));
+ });
+
+ test('cannot navigate to add members without proper authorization', async function (assert) {
+ assert.expect(1);
+ const authorized_actions = instances.group.authorized_actions.filter(
+ (item) => item !== 'add-members'
+ );
+ instances.group.update({ authorized_actions });
+ await visit(urls.group);
+ assert.notOk(find(`[href="${urls.addMembers}"]`));
+ });
+
test('select and save members to add', async function (assert) {
assert.expect(3);
instances.group.update({ memberIds: [] });
diff --git a/ui/admin/tests/acceptance/host-catalogs/host-sets/hosts-test.js b/ui/admin/tests/acceptance/host-catalogs/host-sets/hosts-test.js
index 3140961c7..2423c62ca 100644
--- a/ui/admin/tests/acceptance/host-catalogs/host-sets/hosts-test.js
+++ b/ui/admin/tests/acceptance/host-catalogs/host-sets/hosts-test.js
@@ -99,6 +99,16 @@ module('Acceptance | host-catalogs | host-sets | hosts', function (hooks) {
assert.equal(findAll('tbody tr').length, count - 1);
});
+ test('cannot remove a host without proper authorization', async function (assert) {
+ assert.expect(1);
+ const authorized_actions = instances.hostSet.authorized_actions.filter(
+ (item) => item !== 'remove-hosts'
+ );
+ instances.hostSet.update({ authorized_actions });
+ await visit(urls.hostSetHosts);
+ assert.notOk(find('tbody tr .rose-dropdown-button-danger'));
+ });
+
test('shows error message on host remove error', async function (assert) {
assert.expect(2);
this.server.post('/host-sets/:idMethod', () => {
@@ -126,6 +136,22 @@ module('Acceptance | host-catalogs | host-sets | hosts', function (hooks) {
assert.equal(currentURL(), urls.addHosts);
});
+ test('can navigate to add hosts with proper authorization', async function (assert) {
+ assert.expect(1);
+ await visit(urls.hostSet);
+ assert.ok(find(`[href="${urls.addHosts}"]`));
+ });
+
+ test('cannot navigate to add hosts without proper authorization', async function (assert) {
+ assert.expect(1);
+ const authorized_actions = instances.hostSet.authorized_actions.filter(
+ (item) => item !== 'add-hosts'
+ );
+ instances.hostSet.update({ authorized_actions });
+ await visit(urls.hostSet);
+ assert.notOk(find(`[href="${urls.addHosts}"]`));
+ });
+
test('select and save hosts to add', async function (assert) {
assert.expect(3);
instances.hostSet.update({ hostIds: [] });
diff --git a/ui/admin/tests/acceptance/roles/grants-test.js b/ui/admin/tests/acceptance/roles/grants-test.js
index d13eb4f46..fd169aa46 100644
--- a/ui/admin/tests/acceptance/roles/grants-test.js
+++ b/ui/admin/tests/acceptance/roles/grants-test.js
@@ -63,6 +63,26 @@ module('Acceptance | roles | grants', function (hooks) {
assert.equal(findAll(`${grantsForm} [name="grant"]`).length, grantsCount);
});
+ test('cannot set grants without proper authorization', async function (assert) {
+ assert.expect(4);
+ const authorized_actions = instances.role.authorized_actions.filter(
+ (item) => item !== 'set-grants'
+ );
+ instances.role.update({ authorized_actions });
+ await visit(urls.grants);
+ assert.equal(
+ findAll('main form').length,
+ 1,
+ 'New grant form is not displayed.'
+ );
+ assert.notOk(
+ find('main form button:not([type="submit"])'),
+ 'Grant delete button is not displayed.'
+ );
+ assert.notOk(find('.rose-form-actions'), 'Form actions are not displayed.');
+ assert.ok(find('main form input[disabled]'), 'Grant fields are disabled.');
+ });
+
test('update a grant', async function (assert) {
assert.expect(1);
this.server.post(
diff --git a/ui/admin/tests/acceptance/roles/principals-test.js b/ui/admin/tests/acceptance/roles/principals-test.js
index b26cfe398..998c9acc5 100644
--- a/ui/admin/tests/acceptance/roles/principals-test.js
+++ b/ui/admin/tests/acceptance/roles/principals-test.js
@@ -69,6 +69,16 @@ module('Acceptance | roles | principals', function (hooks) {
assert.equal(findAll('tbody tr').length, principalsCount - 1);
});
+ test('principal cannot be removed from a role without proper authorization', async function (assert) {
+ assert.expect(1);
+ const authorized_actions = instances.role.authorized_actions.filter(
+ (item) => item !== 'remove-principals'
+ );
+ instances.role.update({ authorized_actions });
+ await visit(urls.rolePrincipals);
+ assert.notOk(find('tbody tr .rose-dropdown-button-danger'));
+ });
+
test('shows error message on principal remove', async function (assert) {
assert.expect(2);
this.server.post('/roles/:idMethod', () => {
@@ -89,6 +99,16 @@ module('Acceptance | roles | principals', function (hooks) {
assert.ok(find('[role="alert"]'));
});
+ test('cannot navigate to add principals without proper authorization', async function (assert) {
+ assert.expect(1);
+ const authorized_actions = instances.role.authorized_actions.filter(
+ (item) => item !== 'add-principals'
+ );
+ instances.role.update({ authorized_actions });
+ await visit(urls.rolePrincipals);
+ assert.notOk(find('.rose-layout-page-actions a'));
+ });
+
test('select and save principals to add', async function (assert) {
assert.expect(3);
instances.role.update({ userIds: [], groupIds: [] });
diff --git a/ui/admin/tests/acceptance/users/accounts-test.js b/ui/admin/tests/acceptance/users/accounts-test.js
index 8b7caaba6..fe556f84a 100644
--- a/ui/admin/tests/acceptance/users/accounts-test.js
+++ b/ui/admin/tests/acceptance/users/accounts-test.js
@@ -61,7 +61,7 @@ module('Acceptance | users | accounts', function (hooks) {
assert.equal(findAll('tbody tr').length, accountsCount);
});
- test('can remove a account', async function (assert) {
+ test('can remove an account', async function (assert) {
assert.expect(2);
await visit(urls.accounts);
assert.equal(findAll('tbody tr').length, accountsCount);
@@ -69,6 +69,16 @@ module('Acceptance | users | accounts', function (hooks) {
assert.equal(findAll('tbody tr').length, accountsCount - 1);
});
+ test('cannot remove an account without proper authorization', async function (assert) {
+ assert.expect(1);
+ const authorized_actions = instances.user.authorized_actions.filter(
+ (item) => item !== 'remove-accounts'
+ );
+ instances.user.update({ authorized_actions });
+ await visit(urls.accounts);
+ assert.notOk(find('tbody tr .rose-dropdown-button-danger'));
+ });
+
test('shows error message on account remove', async function (assert) {
assert.expect(2);
this.server.post('/users/:idMethod', () => {
@@ -92,10 +102,27 @@ module('Acceptance | users | accounts', function (hooks) {
test('visiting account add accounts', async function (assert) {
assert.expect(1);
await visit(urls.addAccounts);
+ await visit(urls.addAccounts);
await a11yAudit();
assert.equal(currentURL(), urls.addAccounts);
});
+ test('can navigate to add accounts with proper authorization', async function (assert) {
+ assert.expect(1);
+ await visit(urls.accounts);
+ assert.ok(find(`[href="${urls.addAccounts}"]`));
+ });
+
+ test('cannot navigate to add accounts without proper authorization', async function (assert) {
+ assert.expect(1);
+ const authorized_actions = instances.user.authorized_actions.filter(
+ (item) => item !== 'add-accounts'
+ );
+ instances.user.update({ authorized_actions });
+ await visit(urls.accounts);
+ assert.notOk(find(`[href="${urls.addAccounts}"]`));
+ });
+
test('select and save accounts to add', async function (assert) {
assert.expect(3);
instances.user.update({ accountIds: [] });
diff --git a/ui/desktop/README.md b/ui/desktop/README.md
index ba9ca6e88..f80ab75d2 100644
--- a/ui/desktop/README.md
+++ b/ui/desktop/README.md
@@ -1,6 +1,6 @@
-# Client Connect UI
+# Desktop client UI
-The client UI for Boundary.
+The desktop client UI for Boundary.
@@ -31,7 +31,7 @@ You will need the following things properly installed on your computer.
## Installation
-See boundary-ui README for installation instructions.
+See [boundary-ui README](https://github.com/hashicorp/boundary-ui#installation) for installation instructions.
## Yarn Commands
@@ -39,24 +39,32 @@ List of available project commands. `yarn run `
| Command | Description |
| ------- | ----------- |
-| build:development | Builds the UI in development mode. |
| build | Builds the UI for production. |
+| build:development | Builds the UI in development mode. |
| build:desktop:debianOnMacOS | Builds debian based linux artifacts on MacOS. |
| lint | Runs all lint commands. |
+| lint:fix | Runs all automatic linting fixes. |
| lint:hbs | Lints `hbs` template files. |
+| lint:hbs:fix | Runs the automatic linting fix for `hbs` template files. |
| lint:js | Lints `js` files. |
+| lint:js:fix | Runs the automatic linting fix for `js` files. |
| lint:sass | Lints `scss` files. |
+| lint:electron | Runs a linter tool to identify misconfigurations and security anti-patterns in Electron. |
| format | Runs all auto-formatters. |
+| format:hbs | Auto-formats `hbs` files using Prettier. |
| format:js | Auto-formats `js` files using Prettier. |
| format:sass | Auto-formats `scss` files using Prettier. |
-| start | Runs the dummy app local server. |
+| start | Runs the dummy app local server as web app. |
| start:desktop | Runs the dummy app as an electron app. |
+| clean:coverage | Cleans coverage reporting directory. |
| test | Runs all tests in random order, with coverage reporting. |
+| test:ember | Runs ember tests in random order, with coverage reporting. |
| doc:toc | Automatically generates a table of contents for this README file. |
## Running / Development
+To run as web app:
* `yarn start`
* Visit your app at [http://localhost:4200](http://localhost:4200).
* Visit your tests at [http://localhost:4200/tests](http://localhost:4200/tests).
@@ -90,11 +98,12 @@ These environment variables may be used to customized the build.
| `BYPASS_CLI_SETUP` | Disable download and extraction of cli. For development use only. |
| `BYPASS_APP_UPDATER` | Disable app updater feature. For development use only. |
| `DISABLE_WINDOW_CHROME` | Disable window chrome. For internal use only. |
+| `ENABLE_MIRAGE` | Enable (`true`) or disable (`false`) mirage. Default value is `true`. |
### Building for Production
Before executing a build, be sure to set any environment variables necessary
-for your target [environment](#environment-variables). To build this UI for
+for your target [environment](#environment-variables). To build this UI for
production, run the following commands from this folder:
```bash
diff --git a/ui/desktop/package.json b/ui/desktop/package.json
index 2c30fa816..4f0dfb6ca 100755
--- a/ui/desktop/package.json
+++ b/ui/desktop/package.json
@@ -23,13 +23,13 @@
"lint:hbs": "ember-template-lint .",
"lint:hbs:fix": "ember-template-lint . --fix",
"lint:js": "eslint .",
+ "lint:js:fix": "eslint . --fix",
"lint:sass": "stylelint '**/*.scss'",
"lint:electron": "electronegativity -r -i ./electron-app",
"format": "npm-run-all format:*",
"format:hbs": "prettier --write 'app/**/*.hbs'",
"format:js": "prettier --write '{app,config,stories,tests,electron-app/src}/**/*.js' *.js",
"format:sass": "prettier --write app/**/*.scss",
- "lint:js:fix": "eslint . --fix",
"start": "ember serve",
"prestart:desktop": "cd electron-app && yarn premake",
"start:desktop": "ember electron",
diff --git a/yarn.lock b/yarn.lock
index 66274fcb4..81f406c7e 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2693,6 +2693,21 @@
global "^4.4.0"
regenerator-runtime "^0.13.7"
+"@storybook/addons@^6.3.9":
+ version "6.3.9"
+ resolved "https://registry.yarnpkg.com/@storybook/addons/-/addons-6.3.9.tgz#e12c8d4351b861d67cb6b8d64ed33b1f6b90994f"
+ integrity sha512-5tRkeHgdb/I/rp3GBkxonDLVsA45Vpgh/vFrsecrS/98wkSYfPEhqrDGLOosJHFrN3J2pznAuNFaA05158uBsw==
+ dependencies:
+ "@storybook/api" "6.3.9"
+ "@storybook/channels" "6.3.9"
+ "@storybook/client-logger" "6.3.9"
+ "@storybook/core-events" "6.3.9"
+ "@storybook/router" "6.3.9"
+ "@storybook/theming" "6.3.9"
+ core-js "^3.8.2"
+ global "^4.4.0"
+ regenerator-runtime "^0.13.7"
+
"@storybook/api@6.3.8":
version "6.3.8"
resolved "https://registry.yarnpkg.com/@storybook/api/-/api-6.3.8.tgz#251bcf6cc3a4e0b908bea7fb0aa9e48d6c48d720"
@@ -2719,6 +2734,32 @@
ts-dedent "^2.0.0"
util-deprecate "^1.0.2"
+"@storybook/api@6.3.9":
+ version "6.3.9"
+ resolved "https://registry.yarnpkg.com/@storybook/api/-/api-6.3.9.tgz#f36c8f01cac602c67b8f22b144e8bd6478039abb"
+ integrity sha512-lwen3jcY4YbnD8spAZrmXcToed/pwad9QpxkG0GNf6ctcOumN6HIK93fKeJ0vvPYc3v/uq1qKeLyTZ3NrgHQRg==
+ dependencies:
+ "@reach/router" "^1.3.4"
+ "@storybook/channels" "6.3.9"
+ "@storybook/client-logger" "6.3.9"
+ "@storybook/core-events" "6.3.9"
+ "@storybook/csf" "0.0.1"
+ "@storybook/router" "6.3.9"
+ "@storybook/semver" "^7.3.2"
+ "@storybook/theming" "6.3.9"
+ "@types/reach__router" "^1.3.7"
+ core-js "^3.8.2"
+ fast-deep-equal "^3.1.3"
+ global "^4.4.0"
+ lodash "^4.17.20"
+ memoizerific "^1.11.3"
+ qs "^6.10.0"
+ regenerator-runtime "^0.13.7"
+ store2 "^2.12.0"
+ telejson "^5.3.2"
+ ts-dedent "^2.0.0"
+ util-deprecate "^1.0.2"
+
"@storybook/builder-webpack4@6.3.8":
version "6.3.8"
resolved "https://registry.yarnpkg.com/@storybook/builder-webpack4/-/builder-webpack4-6.3.8.tgz#9ffe866fbd50a94b3cf3ecde1fe4d5f29dd337e8"
@@ -2817,6 +2858,15 @@
ts-dedent "^2.0.0"
util-deprecate "^1.0.2"
+"@storybook/channels@6.3.9":
+ version "6.3.9"
+ resolved "https://registry.yarnpkg.com/@storybook/channels/-/channels-6.3.9.tgz#40907734460fbcb08fe018c794890ffe668e873d"
+ integrity sha512-ZeHXLFJ43Wn6HJMiGgKUWUMtKcXDoWxL50Qr5Wwbsnmtp2BX7R8aak/Vw9TVT46J86QXkdI3CAKAEvb6esiLRQ==
+ dependencies:
+ core-js "^3.8.2"
+ ts-dedent "^2.0.0"
+ util-deprecate "^1.0.2"
+
"@storybook/client-api@6.3.8":
version "6.3.8"
resolved "https://registry.yarnpkg.com/@storybook/client-api/-/client-api-6.3.8.tgz#60e608a5c2afbe6d63a4a063e616bedde8466584"
@@ -2849,6 +2899,14 @@
core-js "^3.8.2"
global "^4.4.0"
+"@storybook/client-logger@6.3.9":
+ version "6.3.9"
+ resolved "https://registry.yarnpkg.com/@storybook/client-logger/-/client-logger-6.3.9.tgz#ef8c89e96f79927d46dd1dacddb58f3a43154b9a"
+ integrity sha512-oww+P062SaOQfsTphAQBL6xe5DCv78Po/f/ROk7iYGAbV8HcCCscpzyJSeLfus2CunFYS2ngPcllbvEnqWk7dQ==
+ dependencies:
+ core-js "^3.8.2"
+ global "^4.4.0"
+
"@storybook/components@6.3.8":
version "6.3.8"
resolved "https://registry.yarnpkg.com/@storybook/components/-/components-6.3.8.tgz#cb84b0245d8784d41e7e6be25a0d5774363e5b87"
@@ -2963,6 +3021,13 @@
dependencies:
core-js "^3.8.2"
+"@storybook/core-events@6.3.9":
+ version "6.3.9"
+ resolved "https://registry.yarnpkg.com/@storybook/core-events/-/core-events-6.3.9.tgz#17127905a2d7773b6e02f15bfbddb1fd1b0bce75"
+ integrity sha512-6ELOkroH0Oz7+OR1SqGMKAC1+ufituqSxDp08AyvrHPSYqK/db+P2kSCJBdqyUXTvt8lPvqlCOidkRhGrNB/+A==
+ dependencies:
+ core-js "^3.8.2"
+
"@storybook/core-server@6.3.8":
version "6.3.8"
resolved "https://registry.yarnpkg.com/@storybook/core-server/-/core-server-6.3.8.tgz#0d141e6f315b735a442aebc0f424f911456c769c"
@@ -3143,6 +3208,22 @@
qs "^6.10.0"
ts-dedent "^2.0.0"
+"@storybook/router@6.3.9":
+ version "6.3.9"
+ resolved "https://registry.yarnpkg.com/@storybook/router/-/router-6.3.9.tgz#a299091426349b9dc0bb8610773918712b453dc4"
+ integrity sha512-uXNrZS9tsZr6fStIv/MHQfy3xSsc7RLYWbY4wkgZH+y5K97RtuwXgtbx7uyEpsQwse1Z4PikKu/ejN46F0oPGQ==
+ dependencies:
+ "@reach/router" "^1.3.4"
+ "@storybook/client-logger" "6.3.9"
+ "@types/reach__router" "^1.3.7"
+ core-js "^3.8.2"
+ fast-deep-equal "^3.1.3"
+ global "^4.4.0"
+ lodash "^4.17.20"
+ memoizerific "^1.11.3"
+ qs "^6.10.0"
+ ts-dedent "^2.0.0"
+
"@storybook/semver@^7.3.2":
version "7.3.2"
resolved "https://registry.yarnpkg.com/@storybook/semver/-/semver-7.3.2.tgz#f3b9c44a1c9a0b933c04e66d0048fcf2fa10dac0"
@@ -3185,6 +3266,24 @@
resolve-from "^5.0.0"
ts-dedent "^2.0.0"
+"@storybook/theming@6.3.9":
+ version "6.3.9"
+ resolved "https://registry.yarnpkg.com/@storybook/theming/-/theming-6.3.9.tgz#8e4500dacd298a2ca6bf47b5729449688338283c"
+ integrity sha512-vyMSLvEXrTC4rnUdWLUNmBNeOdBCl0Nt3R6y/laY+LQZ9Ljz/poRTrIYTkmenYieq4N7787s9zHmxvym/ZvKtw==
+ dependencies:
+ "@emotion/core" "^10.1.1"
+ "@emotion/is-prop-valid" "^0.8.6"
+ "@emotion/styled" "^10.0.27"
+ "@storybook/client-logger" "6.3.9"
+ core-js "^3.8.2"
+ deep-object-diff "^1.1.0"
+ emotion-theming "^10.0.27"
+ global "^4.4.0"
+ memoizerific "^1.11.3"
+ polished "^4.0.5"
+ resolve-from "^5.0.0"
+ ts-dedent "^2.0.0"
+
"@storybook/ui@6.3.8":
version "6.3.8"
resolved "https://registry.yarnpkg.com/@storybook/ui/-/ui-6.3.8.tgz#9fb71befbe76f06c478925610ce998c23260a3f3"
@@ -6333,14 +6432,14 @@ browserify-zlib@^0.2.0:
pako "~1.0.5"
browserslist@4.14.2, browserslist@^3.2.6, browserslist@^4.0.0, browserslist@^4.12.0, browserslist@^4.16.5, browserslist@^4.16.6, browserslist@^4.16.7:
- version "4.17.1"
- resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.17.1.tgz#a98d104f54af441290b7d592626dd541fa642eb9"
- integrity sha512-aLD0ZMDSnF4lUt4ZDNgqi5BUn9BZ7YdQdI/cYlILrhdSSZJLU9aNZoD5/NBmM4SK34APB2e83MOsRt1EnkuyaQ==
+ version "4.17.2"
+ resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.17.2.tgz#aa15dbd2fab399a399fe4df601bb09363c5458a6"
+ integrity sha512-jSDZyqJmkKMEMi7SZAgX5UltFdR5NAO43vY0AwTpu4X3sGH7GLLQ83KiUomgrnvZRCeW0yPPnKqnxPqQOER9zQ==
dependencies:
- caniuse-lite "^1.0.30001259"
- electron-to-chromium "^1.3.846"
+ caniuse-lite "^1.0.30001261"
+ electron-to-chromium "^1.3.854"
escalade "^3.1.1"
- nanocolors "^0.1.5"
+ nanocolors "^0.2.12"
node-releases "^1.1.76"
bser@2.1.1:
@@ -6675,10 +6774,10 @@ caniuse-api@^3.0.0:
lodash.memoize "^4.1.2"
lodash.uniq "^4.5.0"
-caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001157, caniuse-lite@^1.0.30001259:
- version "1.0.30001259"
- resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001259.tgz#ae21691d3da9c4be6144403ac40f71d9f6efd790"
- integrity sha512-V7mQTFhjITxuk9zBpI6nYsiTXhcPe05l+364nZjK7MFK/E7ibvYBSAXr4YcA6oPR8j3ZLM/LN+lUqUVAQEUZFg==
+caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001157, caniuse-lite@^1.0.30001261:
+ version "1.0.30001263"
+ resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001263.tgz#7ce7a6fb482a137585cbc908aaf38e90c53a16a4"
+ integrity sha512-doiV5dft6yzWO1WwU19kt8Qz8R0/8DgEziz6/9n2FxUasteZNwNNYSmJO3GLBH8lCVE73AB1RPDPAeYbcO5Cvw==
capture-exit@^2.0.0:
version "2.0.0"
@@ -7954,7 +8053,7 @@ debug@^3.0.0, debug@^3.0.1, debug@^3.1.0, debug@^3.1.1, debug@^3.2.6:
dependencies:
ms "^2.1.1"
-debuglog@^1.0.1:
+debuglog@*, debuglog@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492"
integrity sha1-qiT/uaw9+aI1GDfPstJ5NgzXhJI=
@@ -8525,10 +8624,10 @@ electron-rebuild@^2.0.3:
tar "^6.0.5"
yargs "^16.0.0"
-electron-to-chromium@^1.3.846:
- version "1.3.846"
- resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.846.tgz#a55fd59613dbcaed609e965e3e88f42b08c401d3"
- integrity sha512-2jtSwgyiRzybHRxrc2nKI+39wH3AwQgn+sogQ+q814gv8hIFwrcZbV07Ea9f8AmK0ufPVZUvvAG1uZJ+obV4Jw==
+electron-to-chromium@^1.3.854:
+ version "1.3.856"
+ resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.856.tgz#75dee0eef9702bffabbf4c1293c989cd3cacb7ba"
+ integrity sha512-lSezYIe1/p5qkEswAfaQUseOBiwGwuCvRl/MKzOEVe++DcmQ92+43dznDl4rFJ4Zpu+kevhwyIf7KjJevyDA/A==
element-resize-detector@^1.2.2:
version "1.2.3"
@@ -12734,7 +12833,7 @@ import-lazy@^4.0.0:
resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-4.0.0.tgz#e8eb627483a0a43da3c03f3e35548be5cb0cc153"
integrity sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==
-imurmurhash@^0.1.4:
+imurmurhash@*, imurmurhash@^0.1.4:
version "0.1.4"
resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
integrity sha1-khi5srkoojixPcT7a21XbyMUU+o=
@@ -14431,6 +14530,11 @@ lodash._baseflatten@^3.0.0:
lodash.isarguments "^3.0.0"
lodash.isarray "^3.0.0"
+lodash._baseindexof@*:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/lodash._baseindexof/-/lodash._baseindexof-3.1.0.tgz#fe52b53a1c6761e42618d654e4a25789ed61822c"
+ integrity sha1-/lK1OhxnYeQmGNZU5KJXie1hgiw=
+
lodash._baseuniq@~4.6.0:
version "4.6.0"
resolved "https://registry.yarnpkg.com/lodash._baseuniq/-/lodash._baseuniq-4.6.0.tgz#0ebb44e456814af7905c6212fa2c9b2d51b841e8"
@@ -14439,11 +14543,16 @@ lodash._baseuniq@~4.6.0:
lodash._createset "~4.0.0"
lodash._root "~3.0.0"
-lodash._bindcallback@^3.0.0:
+lodash._bindcallback@*, lodash._bindcallback@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/lodash._bindcallback/-/lodash._bindcallback-3.0.1.tgz#e531c27644cf8b57a99e17ed95b35c748789392e"
integrity sha1-5THCdkTPi1epnhftlbNcdIeJOS4=
+lodash._cacheindexof@*:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/lodash._cacheindexof/-/lodash._cacheindexof-3.0.2.tgz#3dc69ac82498d2ee5e3ce56091bafd2adc7bde92"
+ integrity sha1-PcaayCSY0u5ePOVgkbr9Ktx73pI=
+
lodash._createassigner@^3.0.0:
version "3.1.1"
resolved "https://registry.yarnpkg.com/lodash._createassigner/-/lodash._createassigner-3.1.1.tgz#838a5bae2fdaca63ac22dee8e19fa4e6d6970b11"
@@ -14453,12 +14562,19 @@ lodash._createassigner@^3.0.0:
lodash._isiterateecall "^3.0.0"
lodash.restparam "^3.0.0"
+lodash._createcache@*:
+ version "3.1.2"
+ resolved "https://registry.yarnpkg.com/lodash._createcache/-/lodash._createcache-3.1.2.tgz#56d6a064017625e79ebca6b8018e17440bdcf093"
+ integrity sha1-VtagZAF2JeeevKa4AY4XRAvc8JM=
+ dependencies:
+ lodash._getnative "^3.0.0"
+
lodash._createset@~4.0.0:
version "4.0.3"
resolved "https://registry.yarnpkg.com/lodash._createset/-/lodash._createset-4.0.3.tgz#0f4659fbb09d75194fa9e2b88a6644d363c9fe26"
integrity sha1-D0ZZ+7CddRlPqeK4imZE02PJ/iY=
-lodash._getnative@^3.0.0:
+lodash._getnative@*, lodash._getnative@^3.0.0:
version "3.9.1"
resolved "https://registry.yarnpkg.com/lodash._getnative/-/lodash._getnative-3.9.1.tgz#570bc7dede46d61cdcde687d65d3eecbaa3aaff5"
integrity sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=
@@ -14676,7 +14792,7 @@ lodash.pick@^4.4.0:
resolved "https://registry.yarnpkg.com/lodash.pick/-/lodash.pick-4.4.0.tgz#52f05610fff9ded422611441ed1fc123a03001b3"
integrity sha1-UvBWEP/53tQiYRRB7R/BI6AwAbM=
-lodash.restparam@^3.0.0:
+lodash.restparam@*, lodash.restparam@^3.0.0:
version "3.6.1"
resolved "https://registry.yarnpkg.com/lodash.restparam/-/lodash.restparam-3.6.1.tgz#936a4e309ef330a7645ed4145986c85ae5b20805"
integrity sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU=
@@ -15695,10 +15811,10 @@ nan@^2.12.1:
resolved "https://registry.yarnpkg.com/nan/-/nan-2.15.0.tgz#3f34a473ff18e15c1b5626b62903b5ad6e665fee"
integrity sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==
-nanocolors@^0.1.5:
- version "0.1.6"
- resolved "https://registry.yarnpkg.com/nanocolors/-/nanocolors-0.1.6.tgz#bc2350d3edfdbfadd7ac018c855ae7c13905a6ad"
- integrity sha512-2pvTw6vYRaBLGir2xR7MxaJtyWkrn+C53EpW8yPotG+pdAwBvt0Xwk4VJ6VHLY0aLthVZPvDfm9TdZvrvAm5UQ==
+nanocolors@^0.2.12:
+ version "0.2.12"
+ resolved "https://registry.yarnpkg.com/nanocolors/-/nanocolors-0.2.12.tgz#4d05932e70116078673ea4cc6699a1c56cc77777"
+ integrity sha512-SFNdALvzW+rVlzqexid6epYdt8H9Zol7xDoQarioEFcFN0JHo4CYNztAxmtfgGTVRCmFlEOqqhBpoFGKqSAMug==
nanoid@3.1.20:
version "3.1.20"