Merge branch 'develop' into nested-resource-skip-scope-id
This commit is contained in:
24 files changed
+298
-21
No files matched your search
@@ -10,6 +10,22 @@ export default class ScopesScopeRolesRoleGrantsRoute extends Route {
|
||||
|
||||
// =methods
|
||||
|
||||
/**
|
||||
* Renders the grants-specific header template.
|
||||
* Empties the actions and navigation outlets and renders a custom empty header.
|
||||
* @override
|
||||
*/
|
||||
renderTemplate() {
|
||||
super.renderTemplate(...arguments);
|
||||
|
||||
this.render('scopes/scope/roles/role/grants/-header', {
|
||||
into: 'scopes/scope/roles/role',
|
||||
outlet: 'header',
|
||||
});
|
||||
}
|
||||
|
||||
// =actions
|
||||
|
||||
/**
|
||||
* Adds a new grant to the role at the beginning of the grants list.
|
||||
* Grant creation is not immediately permanent; users may rollback the change
|
||||
|
||||
@@ -39,6 +39,20 @@ export default class ScopesScopeRolesRolePrincipalsRoute extends Route {
|
||||
return all(users.concat(groups));
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the principals-specific header template.
|
||||
* Empties the actions and navigation outlets and renders a custom empty header.
|
||||
* @override
|
||||
*/
|
||||
renderTemplate() {
|
||||
super.renderTemplate(...arguments);
|
||||
|
||||
this.render('scopes/scope/roles/role/principals/-header', {
|
||||
into: 'scopes/scope/roles/role',
|
||||
outlet: 'header',
|
||||
});
|
||||
}
|
||||
|
||||
// =actions
|
||||
|
||||
/**
|
||||
@@ -57,4 +71,5 @@ export default class ScopesScopeRolesRolePrincipalsRoute extends Route {
|
||||
this.notify.error(error.message, { closeAfter: null });
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1 +1,2 @@
|
||||
<h2>{{t "resources.auth-method"}}</h2>
|
||||
<p>{{t "resources.descriptions.auth-method"}}</p>
|
||||
@@ -1,6 +1,7 @@
|
||||
<Rose::Layout::Page as |page|>
|
||||
<page.header>
|
||||
<h2>{{t "resources.auth-methods"}}</h2>
|
||||
<p>{{t "resources.descriptions.auth-methods"}}</p>
|
||||
</page.header>
|
||||
|
||||
<page.breadcrumbs>
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
<h2>{{t "resources.group"}}</h2>
|
||||
<p>{{t "resources.descriptions.group"}}</p>
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
<page.header>
|
||||
<h2>{{t "resources.groups"}}</h2>
|
||||
<p>{{t "resources.descriptions.groups"}}</p>
|
||||
</page.header>
|
||||
|
||||
<page.actions>
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
<page.header>
|
||||
<h2>{{t "resources.orgs"}}</h2>
|
||||
<p>{{t "resources.descriptions.orgs"}}</p>
|
||||
</page.header>
|
||||
|
||||
<page.actions>
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
<page.header>
|
||||
<h2>{{t "resources.projects"}}</h2>
|
||||
<p>{{t "resources.descriptions.projects"}}</p>
|
||||
</page.header>
|
||||
|
||||
<page.actions>
|
||||
|
||||
+1
@@ -1 +1,2 @@
|
||||
<h2>{{t "resources.host-catalog"}}</h2>
|
||||
<p>{{t "resources.descriptions.host-catalog"}}</p>
|
||||
+2
-1
@@ -1 +1,2 @@
|
||||
<h2>{{t "resources.host-set"}}</h2>
|
||||
<h2>{{t "resources.host-set"}}</h2>
|
||||
<p>{{t "resources.descriptions.host-set"}}</p>
|
||||
+2
-1
@@ -1 +1,2 @@
|
||||
<h2>{{t "resources.host"}}</h2>
|
||||
<h2>{{t "resources.host"}}</h2>
|
||||
<p>{{t "resources.descriptions.host"}}</p>
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
<page.header>
|
||||
<h2>{{t "resources.host-catalogs"}}</h2>
|
||||
<p>{{t "descriptions.host-catalogs"}}</p>
|
||||
<p>{{t "resources.descriptions.host-catalogs"}}</p>
|
||||
</page.header>
|
||||
|
||||
<page.actions>
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
<page.header>
|
||||
<h2>{{t "resources.project"}}</h2>
|
||||
<p>{{t "resources.descriptions.project"}}</p>
|
||||
</page.header>
|
||||
|
||||
<page.navigation>
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
<page.header>
|
||||
<h2>{{t "resources.targets"}}</h2>
|
||||
<p>{{t "resources.descriptions.targets"}}</p>
|
||||
</page.header>
|
||||
|
||||
<page.actions>
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
<h2>{{t "resources.target"}}</h2>
|
||||
<p>{{t "resources.descriptions.target"}}</p>
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
<page.header>
|
||||
<h2>{{t "resources.roles"}}</h2>
|
||||
<p>{{t "resources.descriptions.roles"}}</p>
|
||||
</page.header>
|
||||
|
||||
<page.actions>
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
<h2>{{t "resources.role"}}</h2>
|
||||
<p>{{t "resources.descriptions.role"}}</p>
|
||||
@@ -1 +1 @@
|
||||
<h2>{{t "actions.add-principals"}}</h2>
|
||||
<h2>{{t "actions.add-principals"}}</h2>
|
||||
@@ -0,0 +1,2 @@
|
||||
<h2>{{t "resources.role"}}</h2>
|
||||
<p>{{t "resources.descriptions.grants"}}</p>
|
||||
@@ -0,0 +1,2 @@
|
||||
<h2>{{t "resources.role"}}</h2>
|
||||
<p>{{t "resources.descriptions.principals"}}</p>
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
<page.header>
|
||||
<h2>{{t "resources.users"}}</h2>
|
||||
<p>{{t "resources.descriptions.users"}}</p>
|
||||
</page.header>
|
||||
|
||||
<page.actions>
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
<page.header>
|
||||
<h2>{{t "resources.user"}}</h2>
|
||||
<p>{{t "resources.descriptions.user"}}</p>
|
||||
</page.header>
|
||||
|
||||
<page.actions>
|
||||
|
||||
@@ -1,69 +1,268 @@
|
||||
import { module, test } from 'qunit';
|
||||
//import { visit, currentURL } from '@ember/test-helpers';
|
||||
import { visit, currentURL, click, find, fillIn } from '@ember/test-helpers';
|
||||
import { setupApplicationTest } from 'ember-qunit';
|
||||
import setupMirage from 'ember-cli-mirage/test-support/setup-mirage';
|
||||
//import a11yAudit from 'ember-a11y-testing/test-support/audit';
|
||||
// import {
|
||||
// authenticateSession,
|
||||
import a11yAudit from 'ember-a11y-testing/test-support/audit';
|
||||
import { Response } from 'miragejs';
|
||||
import {
|
||||
authenticateSession,
|
||||
// // These are left here intentionally for future reference.
|
||||
// //currentSession,
|
||||
// //invalidateSession,
|
||||
// } from 'ember-simple-auth/test-support';
|
||||
} from 'ember-simple-auth/test-support';
|
||||
|
||||
module('Acceptance | auth methods', function (hooks) {
|
||||
setupApplicationTest(hooks);
|
||||
setupMirage(hooks);
|
||||
|
||||
const instances = {
|
||||
scopes: {
|
||||
global: null,
|
||||
org: null,
|
||||
},
|
||||
authMethod: null,
|
||||
};
|
||||
const urls = {
|
||||
orgScope: null,
|
||||
authMethods: null,
|
||||
newAuthMethod: null,
|
||||
authMethod: null,
|
||||
};
|
||||
|
||||
hooks.beforeEach(function () {
|
||||
// Setup Mirage mock resources for this test
|
||||
authenticateSession({});
|
||||
instances.scopes.global = this.server.create('scope', { id: 'global' });
|
||||
instances.scopes.org = this.server.create('scope', {
|
||||
type: 'org',
|
||||
scope: { id: 'global', type: 'global' },
|
||||
});
|
||||
instances.authMethod = this.server.create('auth-method', {
|
||||
scope: instances.scopes.org,
|
||||
});
|
||||
|
||||
// Generate route URLs for resources
|
||||
urls.orgScope = `/scopes/${instances.scopes.org.id}`;
|
||||
urls.authMethods = `${urls.orgScope}/auth-methods`;
|
||||
urls.newAuthMethod = `${urls.authMethods}/new`;
|
||||
urls.authMethod = `${urls.authMethods}/${instances.authMethod.id}`;
|
||||
});
|
||||
|
||||
test('visiting auth methods', async function (assert) {
|
||||
assert.expect(0);
|
||||
assert.expect(1);
|
||||
await visit(urls.authMethods);
|
||||
await a11yAudit();
|
||||
assert.equal(currentURL(), urls.authMethods);
|
||||
});
|
||||
|
||||
test('can navigate to an auth method form', async function (assert) {
|
||||
assert.expect(0);
|
||||
assert.expect(1);
|
||||
await visit(urls.authMethods);
|
||||
await click('main tbody .rose-table-header-cell:nth-child(1) a');
|
||||
await a11yAudit();
|
||||
assert.equal(currentURL(), urls.authMethod);
|
||||
});
|
||||
|
||||
test('can update an auth method and save changes', async function (assert) {
|
||||
assert.expect(0);
|
||||
assert.expect(1);
|
||||
await visit(urls.authMethod);
|
||||
await fillIn('[name="name"]', 'update name');
|
||||
await click('form [type="submit"]:not(:disabled)');
|
||||
assert.equal(this.server.db.authMethods[0].name, 'update name');
|
||||
});
|
||||
|
||||
test('can update an auth method and cancel changes', async function (assert) {
|
||||
assert.expect(0);
|
||||
assert.expect(1);
|
||||
await visit(urls.authMethod)
|
||||
await fillIn('[name="name"]', 'update name');
|
||||
await click('form button:not([type="submit"])');
|
||||
assert.notEqual(this.server.db.authMethods[0].name, 'update name');
|
||||
});
|
||||
|
||||
test('can create new auth method', async function (assert) {
|
||||
assert.expect(0);
|
||||
assert.expect(1);
|
||||
const authMethodsCount = this.server.db.authMethods.length;
|
||||
await visit(urls.newAuthMethod);
|
||||
await fillIn('[name="name"]', 'AuthMethod name');
|
||||
await fillIn('[name="description"]', 'description');
|
||||
await click('form [type="submit"]:not(:disabled)');
|
||||
assert.equal(this.server.db.authMethods.length, authMethodsCount + 1);
|
||||
});
|
||||
|
||||
test('can cancel new auth method creation', async function (assert) {
|
||||
assert.expect(0);
|
||||
assert.expect(2);
|
||||
const authMethodsCount = this.server.db.authMethods.length;
|
||||
await visit(urls.newAuthMethod);
|
||||
await fillIn('[name="name"]', 'AuthMethod name');
|
||||
await fillIn('[name="description"]', 'description');
|
||||
await click('form button:not([type="submit"])');
|
||||
assert.equal(this.server.db.authMethods.length, authMethodsCount);
|
||||
assert.equal(currentURL(), urls.authMethods);
|
||||
});
|
||||
|
||||
test('can delete an auth method', async function (assert) {
|
||||
assert.expect(0);
|
||||
assert.expect(1);
|
||||
const authMethodsCount = this.server.db.authMethods.length;
|
||||
await visit(urls.authMethod);
|
||||
await click('.rose-layout-page-actions .rose-dropdown-button-danger');
|
||||
assert.equal(this.server.db.authMethods.length, authMethodsCount - 1);
|
||||
});
|
||||
|
||||
test('saving a new auth method with invalid fields displays error messages', async function (assert) {
|
||||
assert.expect(0);
|
||||
assert.expect(2);
|
||||
this.server.post('/auth-methods', () => {
|
||||
return new Response(
|
||||
400,
|
||||
{},
|
||||
{
|
||||
status: 400,
|
||||
code: 'invalid_argument',
|
||||
message: 'The request was invalid.',
|
||||
details: {
|
||||
request_fields: [
|
||||
{
|
||||
name: 'name',
|
||||
description: 'Name is required.',
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
);
|
||||
});
|
||||
await visit(urls.newAuthMethod);
|
||||
await fillIn('[name="name"]', 'new auth method');
|
||||
await click('form [type="submit"]');
|
||||
await a11yAudit();
|
||||
assert.ok(
|
||||
find('[role="alert"]').textContent.trim(),
|
||||
'The request was invalid.',
|
||||
'Displays primary error message.'
|
||||
);
|
||||
assert.ok(
|
||||
find('.rose-form-error-message').textContent.trim(),
|
||||
'Name is required.',
|
||||
'Displays field-level errors.'
|
||||
);
|
||||
});
|
||||
|
||||
test('errors are displayed when save on auth method fails', async function (assert) {
|
||||
assert.expect(0);
|
||||
assert.expect(1);
|
||||
this.server.patch('/auth-methods/:id', () => {
|
||||
return new Response(
|
||||
490,
|
||||
{},
|
||||
{
|
||||
status: 490,
|
||||
code: 'error',
|
||||
message: 'Oops.',
|
||||
}
|
||||
);
|
||||
});
|
||||
await visit(urls.authMethod);
|
||||
await fillIn('[name="name"]', 'save auth method');
|
||||
await click('form [type="submit"]');
|
||||
await a11yAudit();
|
||||
assert.ok(
|
||||
find('[role="alert"]').textContent.trim(),
|
||||
'Oops.',
|
||||
'Displays primary error message.'
|
||||
);
|
||||
});
|
||||
|
||||
test('errors are displayed when delete on an auth method fails', async function (assert) {
|
||||
assert.expect(0);
|
||||
assert.expect(1);
|
||||
this.server.del('/auth-methods/:id', () => {
|
||||
return new Response(
|
||||
490,
|
||||
{},
|
||||
{
|
||||
status: 490,
|
||||
code: 'error',
|
||||
message: 'Oops.',
|
||||
}
|
||||
);
|
||||
});
|
||||
await visit(urls.authMethod);
|
||||
await click('.rose-layout-page-actions .rose-dropdown-button-danger');
|
||||
await a11yAudit();
|
||||
assert.ok(
|
||||
find('[role="alert"]').textContent.trim(),
|
||||
'Oops.',
|
||||
'Displays primary error message.'
|
||||
);
|
||||
});
|
||||
|
||||
test('saving a new auth method with invalid fields displays error messages', async function (assert) {
|
||||
assert.expect(0);
|
||||
assert.expect(2);
|
||||
this.server.patch('/auth-methods/:id', () => {
|
||||
return new Response(
|
||||
400,
|
||||
{},
|
||||
{
|
||||
status: 400,
|
||||
code: 'invalid_argument',
|
||||
message: 'The request was invalid.',
|
||||
details: {
|
||||
request_fields: [
|
||||
{
|
||||
name: 'name',
|
||||
description: 'Name is required.',
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
);
|
||||
});
|
||||
await visit(urls.authMethod);
|
||||
await fillIn('[name="name"]', 'existing auth method');
|
||||
await click('form [type="submit"]');
|
||||
await a11yAudit();
|
||||
assert.ok(
|
||||
find('[role="alert"]').textContent.trim(),
|
||||
'The request was invalid.',
|
||||
'Displays primary error message.'
|
||||
);
|
||||
assert.ok(
|
||||
find('.rose-form-error-message').textContent.trim(),
|
||||
'Name is required.',
|
||||
'Displays field-level errors.'
|
||||
);
|
||||
});
|
||||
|
||||
test('saving an existing auth method with invalid fields displays error messages', async function (assert) {
|
||||
assert.expect(0);
|
||||
assert.expect(2);
|
||||
this.server.patch('/auth-methods/:id', () => {
|
||||
return new Response(
|
||||
400,
|
||||
{},
|
||||
{
|
||||
status: 400,
|
||||
code: 'invalid_argument',
|
||||
message: 'The request was invalid.',
|
||||
details: {
|
||||
request_fields: [
|
||||
{
|
||||
name: 'name',
|
||||
description: 'Name is required.',
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
);
|
||||
});
|
||||
await visit(urls.authMethod);
|
||||
await fillIn('[name="name"]', 'existing auth method');
|
||||
await click('form [type="submit"]');
|
||||
await a11yAudit();
|
||||
assert.ok(
|
||||
find('[role="alert"]').textContent.trim(),
|
||||
'The request was invalid.',
|
||||
'Displays primary error message.'
|
||||
);
|
||||
assert.ok(
|
||||
find('.rose-form-error-message').textContent.trim(),
|
||||
'Name is required.',
|
||||
'Displays field-level errors.'
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -79,6 +79,33 @@ resources:
|
||||
account: Account
|
||||
sessions: Sessions
|
||||
session: Session
|
||||
descriptions:
|
||||
user: A User inherits from Principal ("is-a" Principal) allowing it to be associated with zero or more Roles.
|
||||
users: A user is any entity authorized to access Boundary using authentication credentials specific to one of the configured Authentication Methods. A User is owned by one and only one Organization. A User is associated with zero or one Authentication Methods. A User not associated with an Authentication Method can not log in to the system. A User can belong to zero or more Groups. A User is deleted when the Organization it belongs to is deleted. The lifecycle of a User is not tied to the lifecycle of any Authentication Method, Group or Role, but rather to its Organization only.
|
||||
role: A Role can be assigned to zero or more Principals. A Principal assigned a Role receives all capabilities granted by any Direct Grant owned by the Role. A Role is deleted when the Scope it belongs to is deleted. All Direct Grants owned by a Role are deleted when the Role is deleted.
|
||||
roles: A role is a collection of capabilities granted to any Principal the Role is assigned to. A Role belongs to one and only one Scope. A Role owns zero or more Direct Grants. The lifecycle of a Role is not tied to the lifecycle of any Principal.
|
||||
grants: A grant represents a set of capabilities granted to a role. It couples a set of Actions to either a set of Resource Types or an individual Resource. A Direct Grant belongs exclusively to one and only one Role. However, equivalent Direct Grants may exist across different Roles. A Direct Grant references one or more Actions and either one or more Resource types or one specific Resource. A Direct Grant is deleted when the Role it belongs to is deleted. A Direct Grant is also deleted if it is associated with a specific Resource and that Resource itself is deleted. The lifecycle of a Direct Grant is not tied to the lifecycle of any Action or Resource Type.
|
||||
principals: A principal is any entity which can be assigned capabilities. Principal is abstract. User, Group, and Project are concrete Principals. A Principal can be assigned to zero or more Roles.
|
||||
group: All Users in a Group are granted the capabilities of all Roles the Group is associated with. A Group is deleted when the Scope it belongs to is deleted.
|
||||
groups: Groups are collections of Users used only for access control purposes. A Group is owned by one and only one Scope. A Group can contain zero or more Users. A Group inherits from Principal ("is-a" Principal) allowing it to be associated with zero or more Roles. The lifecycle of a Group is not tied to the lifecycle of any User or Role.
|
||||
org: When an Organization is deleted, all resources owned by it are also deleted.
|
||||
orgs: An Organization is a top-level container, and owns zero to many Projects and zero to many Authentication Methods. An Organization inherits from Scope ("is-a" Scope) allowing it to own zero to many Groups, Roles, Targets, or Host Catalogs. The lifecycle of an Organization is not tied to anything else. A Role at the Organization level can grant permissions at the Project level.
|
||||
project: When a Project is deleted, all resources owned by it are also deleted. The lifecycle of a Project is not tied to any resource it contains.
|
||||
projects: A project is a child scope of an organization and is owned by an Organization. A Project inherits from Scope ("is-a" Scope) allowing it to own zero to many Groups, Roles, Targets, or Host Catalogs. A Project also inherits from Principal ("is-a" Principal) allowing it to be associated with zero or more Roles. A Project has no additional relationships beyond those defined in Scope and Principal. A Project is deleted when the Organization it belongs to is deleted.
|
||||
host-catalog: A host catalog is a permission boundary modeled as a container.
|
||||
host-catalogs: A host catalog is a permission boundary modeled as a container.
|
||||
auth-method: An Authentication Method creates and references zero or more Users. An Authentication Method is deleted when the Organization it belongs to is deleted. The lifecycle of an Authentication Method is not tied to the lifecycle of any User.
|
||||
auth-methods: An authentication method is a method configured for an Organization to authenticate Users. An Authentication Method is owned by one and only one Organization.
|
||||
host-set: Host Set is abstract. A Host Set is deleted when the Host Catalog it belongs to is deleted.
|
||||
host-sets: A host set is a resource which provides a subset of Hosts from the set of Hosts of the Host Catalog it belongs to. A Host Set belongs to one and only one Host Catalog. A Host Set can contain zero or more Hosts. A Host Set can be contained by zero or more Targets. The lifecycle of a Host Set is not tied to the lifecycle of any Targets or Hosts.
|
||||
host: Host is abstract. A Host is deleted when the Host Catalog it belongs to is deleted.
|
||||
hosts: A host is a computing element with a network address reachable from Boundary. A Host belongs to one and only one Host Catalog. A Host can be contained by zero or more Host Sets. The lifecycle of a Host is not tied to the lifecycle of any Host Sets.
|
||||
target: Target is abstract. A Target is deleted when the Scope it belongs to is deleted.
|
||||
targets: A target is a networked service a User can connect to and interact with through Boundary. A Target does not directly contain any secrets. A Target contains a collection of Hosts. A Target belongs to one and only one Scope. A Target can contain zero or more Host Sets. The lifecycle of a Target is not tied to the lifecycle of any Host Set.
|
||||
accounts: Accounts
|
||||
account: Account
|
||||
sessions: Sessions
|
||||
session: Session
|
||||
titles:
|
||||
iam: IAM
|
||||
new: New
|
||||
|
||||
Reference in new issue
Block a user