fix url when user tries to access resource with incorrect url (#1958)

* refactor: 💡 auth-method, account, & managed abilities & routes

* refactor: 💡 credential store resources abilities & routes

* refactor: 💡 group abilities & routes

* refactor: 💡 host-catalog resources abilities & routes

* refactor: 💡 role abilities & routes

* test: 💍 fix route-info-test

* test: 💍 fix host serializer tests

* test: 💍 fix create-test for host-sets

* refactor: 💡 session recording resources abilities & routes

* refactor: 💡 storage bucket abilities & routes

* refactor: 💡 target abilities & routes

* refactor: 💡 user abilities & routes

* refactor: 💡 worker abilities & routes

* refactor: 💡 remove ability changes and add comments

* Update ui/admin/app/utils/route-info.js

Co-authored-by: Zhihe Li <zhihe.li@hashicorp.com>

* test: 💍 add test cases and rename util file

* refactor: 💡 remove dup file and update imports

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-11282

* docs: ✏️ update comments & remove console.log

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-11282

* Update ui/admin/app/routes/scopes/scope/auth-methods/auth-method.js

Co-authored-by: Dharini Jeeva <dharini@hashicorp.com>

---------

Co-authored-by: Zhihe Li <zhihe.li@hashicorp.com>
Co-authored-by: Dharini Jeeva <dharini@hashicorp.com>
This commit is contained in:
authored and GitHub committed 2023-11-02 16:08:25 -07:00
1 parent 7317ca2ff5
commit 0129784322
80 files changed
+1333 -81

No files matched your search

@@ -54,4 +54,10 @@ export default class GeneratedHostModel extends BaseModel {
readOnly: true,
})
plugin;
@attr({
emptyArrayIfMissing: true,
readOnly: true,
})
host_set_ids;
}
+12 -5
View File
@@ -9,6 +9,7 @@ import permissions from '../helpers/permissions';
import generateId from '../helpers/id';
import {
TYPE_AUTH_METHOD_PASSWORD,
TYPE_AUTH_METHOD_OIDC,
TYPE_AUTH_METHOD_LDAP,
TYPES_AUTH_METHOD,
@@ -23,11 +24,17 @@ export default factory.extend({
'delete',
'authenticate',
],
authorized_collection_actions: () => {
return {
accounts: ['create', 'list'],
'managed-groups': ['create', 'list'],
};
authorized_collection_actions() {
switch (this.type) {
case TYPE_AUTH_METHOD_PASSWORD:
return { accounts: ['create', 'list'] };
case TYPE_AUTH_METHOD_LDAP:
case TYPE_AUTH_METHOD_OIDC:
return {
accounts: ['create', 'list'],
'managed-groups': ['create', 'list'],
};
}
},
id: () => generateId('am_'),
+10 -1
View File
@@ -85,7 +85,16 @@ export default factory.extend({
afterCreate(hostCatalog, server) {
const { scope, type } = hostCatalog;
const hosts = server.createList('host', 10, { scope, hostCatalog, type });
server.createList('host-set', 3, { scope, hostCatalog, hosts, type });
const hostSets = server.createList('host-set', 3, {
scope,
hostCatalog,
hosts,
type,
});
const hostSetIds = hostSets.map((hostSet) => hostSet.id);
hosts.forEach((host) => {
host.update({ hostSetIds });
});
},
}),
});
+9 -6
View File
@@ -22,14 +22,8 @@ export default factory.extend({
authorized_collection_actions() {
const collectionActions = {
scopes: ['create', 'list'],
users: ['create', 'list'],
sessions: ['list'],
groups: ['create', 'list'],
roles: ['create', 'list'],
targets: ['create', 'list'],
'credential-stores': ['create', 'list'],
'auth-methods': ['create', 'list'],
'host-catalogs': ['create', 'list'],
};
// Worker permissions only available on the global scope
@@ -41,6 +35,15 @@ export default factory.extend({
if (this.type === 'global' || this.type === 'org') {
collectionActions['storage-buckets'] = ['create', 'list'];
collectionActions['session-recordings'] = ['list'];
collectionActions.users = ['create', 'list'];
collectionActions['auth-methods'] = ['create', 'list'];
}
if (this.type === 'project') {
collectionActions['credential-stores'] = ['create', 'list'];
collectionActions['host-catalogs'] = ['create', 'list'];
collectionActions.sessions = ['list'];
collectionActions.targets = ['create', 'list'];
}
return collectionActions;
+9
View File
@@ -7,4 +7,13 @@ import ApplicationSerializer from './application';
export default ApplicationSerializer.extend({
modelName: 'host',
_hashForModel(model) {
const json = ApplicationSerializer.prototype._hashForModel.apply(
this,
arguments
);
json.host_catalog_id = model.hostCatalogId;
return json;
},
});
@@ -57,6 +57,7 @@ module('Unit | Serializer | host', function (hooks) {
address: '10.0.0.1',
dns_names: [],
ip_addresses: [],
host_set_ids: [],
},
relationships: {},
},
@@ -78,6 +79,7 @@ module('Unit | Serializer | host', function (hooks) {
authorized_actions: ['no-op', 'read'],
ip_addresses: ['10.0.0.1', '10.0.0.2', '10.0.0.3'],
dns_names: ['test.example.internal', 'test.example.external'],
host_set_ids: ['hst_12345'],
};
const normalized = serializer.normalizeSingleResponse(store, host, payload);
assert.deepEqual(normalized, {
@@ -92,6 +94,7 @@ module('Unit | Serializer | host', function (hooks) {
ip_addresses: ['10.0.0.1', '10.0.0.2', '10.0.0.3'],
dns_names: ['test.example.internal', 'test.example.external'],
plugin: { name: 'aws' },
host_set_ids: ['hst_12345'],
},
relationships: {},
},
@@ -7,10 +7,14 @@ import Route from '@ember/routing/route';
import { action } from '@ember/object';
import { notifySuccess, notifyError } from 'core/decorators/notify';
import { inject as service } from '@ember/service';
import { paramValueFinder } from 'admin/utils/param-value-finder';
export default class ScopesScopeAuthMethodsAuthMethodRoute extends Route {
// =services
@service store;
@service can;
@service router;
// =methods
@@ -18,10 +22,30 @@ export default class ScopesScopeAuthMethodsAuthMethodRoute extends Route {
* Load an auth method by ID.
* @param {object} params
* @param {string} params.auth_method_id
* @return {authMethodModel}
* @return {Promise{AuthMethodModel}}
*/
async model({ auth_method_id }) {
return this.store.findRecord('auth-method', auth_method_id);
return this.store.findRecord('auth-method', auth_method_id, {
reload: true,
});
}
/**
* Redirects to route with correct scope id if incorrect.
* @param {AuthMethodModel} authMethod
* @param {object} transition
*/
redirect(authMethod, transition) {
const scope = this.modelFor('scopes.scope');
if (authMethod.scopeID !== scope.id) {
let paramValues = paramValueFinder('auth-method', transition.to.parent);
this.router.replaceWith(
transition.to.name,
authMethod.scopeID,
authMethod.id,
...paramValues
);
}
}
// =actions
@@ -10,6 +10,8 @@ export default class ScopesScopeAuthMethodsAuthMethodAccountsAccountRoute extend
// =services
@service store;
@service can;
@service router;
// =methods
@@ -17,9 +19,22 @@ export default class ScopesScopeAuthMethodsAuthMethodAccountsAccountRoute extend
* Load an account by ID.
* @param {object} params
* @param {string} params.account_id
* @return {AccountModel}
* @return {Promise{AccountModel}}
*/
model({ account_id }) {
async model({ account_id }) {
return this.store.findRecord('account', account_id, { reload: true });
}
/**
* Redirects to route with correct auth-method id if incorrect.
* @param {AccountModel} account
* @param {object} transition
*/
redirect(account, transition) {
const authMethod = this.modelFor('scopes.scope.auth-methods.auth-method');
const { auth_method_id } = account;
if (auth_method_id !== authMethod.id) {
this.router.replaceWith(transition.to.name, auth_method_id, account.id);
}
}
}
@@ -10,9 +10,23 @@ export default class ScopesScopeAuthMethodsAuthMethodAccountsNewRoute extends Ro
// =services
@service store;
@service can;
@service router;
// =methods
/**
* Redirect to parent route when auth-method does not have create authorized action.
*/
beforeModel() {
const authMethod = this.modelFor('scopes.scope.auth-methods.auth-method');
if (
this.can.cannot('create model', authMethod, { collection: 'accounts' })
) {
this.router.replaceWith('scopes.scope.auth-methods.auth-method.accounts');
}
}
/**
* Creates a new unsaved account in current scope.
* @return {AccountModel}
@@ -11,6 +11,8 @@ export default class ScopesScopeAuthMethodsAuthMethodManagedGroupsManagedGroupRo
// =services
@service store;
@service can;
@service router;
// =methods
@@ -18,14 +20,31 @@ export default class ScopesScopeAuthMethodsAuthMethodManagedGroupsManagedGroupRo
* Load managed group by ID.
* @param {object} params
* @param {string} params.managed_group_id
* @returns {ManagedGroupModel}
* @returns {Promise{ManagedGroupModel}}
*/
model({ managed_group_id }) {
async model({ managed_group_id }) {
return this.store.findRecord('managed-group', managed_group_id, {
reload: true,
});
}
/**
* Redirects to route with correct auth-method id if incorrect.
* @param {ManagedGroupModel} managedGroup
* @param {object} transition
*/
redirect(managedGroup, transition) {
const authMethod = this.modelFor('scopes.scope.auth-methods.auth-method');
const { auth_method_id } = managedGroup;
if (auth_method_id !== authMethod.id) {
this.router.replaceWith(
transition.to.name,
auth_method_id,
managedGroup.id
);
}
}
/**
* Copies the contents of string array fields in order to force the instance
* into a dirty state. This ensures that `model.rollbackAttributes()` reverts
@@ -10,8 +10,27 @@ export default class ScopesScopeAuthMethodsAuthMethodManagedGroupsNewRoute exten
// =services
@service store;
@service can;
@service router;
// =methods
/**
* Redirect to parent route when auth-method does not have create authorized action.
*/
beforeModel() {
const authMethod = this.modelFor('scopes.scope.auth-methods.auth-method');
if (
this.can.cannot('create model', authMethod, {
collection: 'managed-groups',
})
) {
this.router.replaceWith(
'scopes.scope.auth-methods.auth-method.managed-groups'
);
}
}
model() {
const { id: auth_method_id, type } = this.modelFor(
'scopes.scope.auth-methods.auth-method'
@@ -10,6 +10,8 @@ export default class ScopesScopeAuthMethodsNewRoute extends Route {
// =services
@service store;
@service can;
@service router;
// =attributes
@@ -21,6 +23,20 @@ export default class ScopesScopeAuthMethodsNewRoute extends Route {
// =methods
/**
* Redirect to parent route when scope does not have create authorized action.
*/
beforeModel() {
const scopeModel = this.modelFor('scopes.scope');
if (
this.can.cannot('create model', scopeModel, {
collection: 'auth-methods',
})
) {
this.router.replaceWith('scopes.scope.auth-methods');
}
}
/**
* Create a new unsaved auth-method.
* @return {AuthMethodModel}
@@ -5,17 +5,43 @@
import Route from '@ember/routing/route';
import { inject as service } from '@ember/service';
import { paramValueFinder } from 'admin/utils/param-value-finder';
export default class ScopesScopeCredentialStoresCredentialStoreRoute extends Route {
// =services
@service store;
@service can;
@service router;
/**
* Load a specific credential store in current scope
* @return {Promise[CredentialStoreModel]}
* @return {Promise{CredentialStoreModel}}
*/
async model({ credential_store_id }) {
return this.store.findRecord('credential-store', credential_store_id);
return this.store.findRecord('credential-store', credential_store_id, {
reload: true,
});
}
/**
* Redirects to route with correct scope id if incorrect.
* @param {CredentialStoreModel} credentialStore
* @param {object} transition
*/
redirect(credentialStore, transition) {
const scope = this.modelFor('scopes.scope');
if (credentialStore.scopeID !== scope.id) {
let paramValues = paramValueFinder(
'credential-store',
transition.to.parent
);
this.router.replaceWith(
transition.to.name,
credentialStore.scopeID,
credentialStore.id,
...paramValues
);
}
}
}
@@ -11,6 +11,8 @@ export default class ScopesScopeCredentialStoresCredentialStoreCredentialLibrari
// =services
@service store;
@service can;
@service router;
// =methods
@@ -18,7 +20,7 @@ export default class ScopesScopeCredentialStoresCredentialStoreCredentialLibrari
* Load a credential-library using current credential store and its parent scope.
* @param {object} params
* @param {string} params.credential_library_id
* @return {CredentialLibraryModel}
* @return {Promise{CredentialLibraryModel}}
*/
async model({ credential_library_id }) {
return this.store.findRecord('credential-library', credential_library_id, {
@@ -26,6 +28,24 @@ export default class ScopesScopeCredentialStoresCredentialStoreCredentialLibrari
});
}
/**
* Redirects to route with correct credential-store id if incorrect.
* @param {CredentialLibraryModel} credentialLibrary
*/
redirect(credentialLibrary) {
const credentialStore = this.modelFor(
'scopes.scope.credential-stores.credential-store'
);
const { credential_store_id } = credentialLibrary;
if (credential_store_id !== credentialStore.id) {
this.router.replaceWith(
'scopes.scope.credential-stores.credential-store.credential-libraries.credential-library',
credential_store_id,
credentialLibrary.id
);
}
}
/**
* Copies the contents of array fields in order to force the instance
* into a dirty state. This ensures that `model.rollbackAttributes()` reverts
@@ -14,6 +14,7 @@ export default class ScopesScopeCredentialStoresCredentialStoreCredentialLibrari
@service store;
@service router;
@service features;
@service can;
// =attributes
@@ -25,6 +26,24 @@ export default class ScopesScopeCredentialStoresCredentialStoreCredentialLibrari
// =methods
/**
* Redirect to parent route when credential-store does not have create authorized action.
*/
beforeModel() {
const credentialStore = this.modelFor(
'scopes.scope.credential-stores.credential-store'
);
if (
this.can.cannot('create model', credentialStore, {
collection: 'credential-libraries',
})
) {
this.router.replaceWith(
'scopes.scope.credential-stores.credential-store.credential-libraries'
);
}
}
/**
* Creates a new unsaved credential library in current credential store.
* @return {CredentialLibraryModel}
@@ -10,6 +10,8 @@ export default class ScopesScopeCredentialStoresCredentialStoreCredentialsCreden
// =services
@service store;
@service can;
@service router;
// =methods
@@ -17,11 +19,29 @@ export default class ScopesScopeCredentialStoresCredentialStoreCredentialsCreden
* Load a credential using current credential store and its parent scope.
* @param {object} params
* @param {string} params.credential_id
* @return {CredentialModel}
* @return {Promise{CredentialModel}}
*/
async model({ credential_id }) {
return this.store.findRecord('credential', credential_id, {
reload: true,
});
}
/**
* Redirects to route with correct credential-store id if incorrect.
* @param {CredentialModel} credential
*/
redirect(credential) {
const credentialStore = this.modelFor(
'scopes.scope.credential-stores.credential-store'
);
const { credential_store_id } = credential;
if (credential_store_id !== credentialStore.id) {
this.router.replaceWith(
'scopes.scope.credential-stores.credential-store.credentials.credential',
credential_store_id,
credential.id
);
}
}
}
@@ -12,6 +12,7 @@ export default class ScopesScopeCredentialStoresCredentialStoreCredentialsNewRou
@service store;
@service router;
@service can;
// =attributes
@@ -23,6 +24,24 @@ export default class ScopesScopeCredentialStoresCredentialStoreCredentialsNewRou
// =methods
/**
* Redirect to parent route when credential-store does not have create authorized action.
*/
beforeModel() {
const credentialStore = this.modelFor(
'scopes.scope.credential-stores.credential-store'
);
if (
this.can.cannot('create model', credentialStore, {
collection: 'credentials',
})
) {
this.router.replaceWith(
'scopes.scope.credential-stores.credential-store.credentials'
);
}
}
/**
* Creates a new unsaved credential in current credential store.
* Also rollback/destroy any new, unsaved instances from this route before
@@ -13,6 +13,7 @@ export default class ScopesScopeCredentialStoresNewRoute extends Route {
@service store;
@service router;
@service features;
@service can;
// =attributes
@@ -23,6 +24,21 @@ export default class ScopesScopeCredentialStoresNewRoute extends Route {
};
// =methods
/**
* Redirect to parent route when scope does not have create authorized action.
*/
beforeModel() {
const scopeModel = this.modelFor('scopes.scope');
if (
this.can.cannot('create model', scopeModel, {
collection: 'credential-stores',
})
) {
this.router.replaceWith('scopes.scope.credential-stores');
}
}
/**
* Creates a new unsaved credential-store
* Also rollback/destroy any new, unsaved instances from this route before
@@ -10,6 +10,8 @@ export default class ScopesScopeGroupsGroupRoute extends Route {
// =services
@service store;
@service can;
@service router;
// =methods
@@ -17,9 +19,21 @@ export default class ScopesScopeGroupsGroupRoute extends Route {
* Load a group in current scope.
* @param {object} params
* @param {string} params.group_id
* @return {groupModel}
* @return {Promise{GroupModel}}
*/
model({ group_id }) {
async model({ group_id }) {
return this.store.findRecord('group', group_id, { reload: true });
}
/**
* Redirects to route with correct scope id if incorrect.
* @param {GroupModel} credentialStore
* @param {object} transition
*/
redirect(group, transition) {
const scope = this.modelFor('scopes.scope');
if (group.scopeID !== scope.id) {
this.router.replaceWith(transition.to.name, group.scopeID, group.id);
}
}
}
@@ -10,9 +10,21 @@ export default class ScopesScopeGroupsNewRoute extends Route {
// =services
@service store;
@service can;
@service router;
// =methods
/**
* Redirect to parent route when scope does not have create authorized action.
*/
beforeModel() {
const scopeModel = this.modelFor('scopes.scope');
if (this.can.cannot('create model', scopeModel, { collection: 'groups' })) {
this.router.replaceWith('scopes.scope.groups');
}
}
/**
* Creates a new unsaved group.
* @return {GroupModel}
@@ -5,11 +5,14 @@
import Route from '@ember/routing/route';
import { inject as service } from '@ember/service';
import { paramValueFinder } from 'admin/utils/param-value-finder';
export default class ScopesScopeHostCatalogsHostCatalogRoute extends Route {
// =services
@service store;
@service can;
@service router;
// =methods
@@ -17,9 +20,29 @@ export default class ScopesScopeHostCatalogsHostCatalogRoute extends Route {
* Load a host catalog.
* @param {object} params
* @param {string} params.host_catalog_id
* @return {HostCatalogModel}
* @return {Promise{HostCatalogModel}}
*/
async model({ host_catalog_id }) {
return this.store.findRecord('host-catalog', host_catalog_id);
return this.store.findRecord('host-catalog', host_catalog_id, {
reload: true,
});
}
/**
* Redirects to route with correct scope id if incorrect.
* @param {HostCatalogModel} hostCatalog
* @param {object} transition
*/
redirect(hostCatalog, transition) {
const scope = this.modelFor('scopes.scope');
if (hostCatalog.scopeID !== scope.id) {
let paramValues = paramValueFinder('host-catalog', transition.to.parent);
this.router.replaceWith(
transition.to.name,
hostCatalog.scopeID,
hostCatalog.id,
...paramValues
);
}
}
}
@@ -6,10 +6,14 @@
import Route from '@ember/routing/route';
import { action } from '@ember/object';
import { inject as service } from '@ember/service';
import { paramValueFinder } from 'admin/utils/param-value-finder';
export default class ScopesScopeHostCatalogsHostCatalogHostSetsHostSetRoute extends Route {
// =services
@service store;
@service can;
@service router;
// =methods
@@ -17,12 +21,33 @@ export default class ScopesScopeHostCatalogsHostCatalogHostSetsHostSetRoute exte
* Load a host-set using current host-catalog and its parent scope.
* @param {object} params
* @param {string} params.host_set_id
* @return {HostSetModel}
* @return {Promise{HostSetModel}}
*/
async model({ host_set_id }) {
return this.store.findRecord('host-set', host_set_id, { reload: true });
}
/**
* Redirects to route with correct host-catalog id if incorrect.
* @param {HostSetModel} hostSet
* @param {object} transition
*/
redirect(hostSet, transition) {
const hostCatalog = this.modelFor(
'scopes.scope.host-catalogs.host-catalog'
);
const { host_catalog_id } = hostSet;
if (host_catalog_id !== hostCatalog.id) {
let paramValues = paramValueFinder('host-set', transition.to.parent);
this.router.replaceWith(
transition.to.name,
host_catalog_id,
hostSet.id,
...paramValues
);
}
}
/**
* Copies the contents of string array fields in order to force the instance
* into a dirty state. This ensures that `model.rollbackAttributes()` reverts
@@ -15,9 +15,24 @@ export default class ScopesScopeHostCatalogsHostCatalogHostSetsHostSetCreateAndA
@service store;
@service intl;
@service router;
@service can;
// =methods
/**
* Redirect to parent route when host-catalog does not have create authorized action.
*/
beforeModel() {
const hostCatalog = this.modelFor(
'scopes.scope.host-catalogs.host-catalog'
);
if (this.can.cannot('create model', hostCatalog, { collection: 'hosts' })) {
this.router.replaceWith(
'scopes.scope.host-catalogs.host-catalog.host-sets.host-set.hosts'
);
}
}
/**
* Creates a new unsaved host in current host catalog scope.
* @return {Promise{HostSetModel,HostModel}}
@@ -10,6 +10,8 @@ export default class ScopesScopeHostCatalogsHostCatalogHostSetsHostSetHostsHostR
// =services
@service store;
@service can;
@service router;
// =methods
@@ -17,9 +19,26 @@ export default class ScopesScopeHostCatalogsHostCatalogHostSetsHostSetHostsHostR
* Load a host in current scope.
* @param {object} params
* @param {string} params.host_id
* @return {HostModel}
* @return {Promise{HostModel}}
*/
async model({ host_id }) {
return this.store.findRecord('host', host_id, { reload: true });
}
/**
* Redirects to route with correct host-catalog id if incorrect.
* @param {HostModel} host
*/
redirect(host) {
const hostSet = this.modelFor(
'scopes.scope.host-catalogs.host-catalog.host-sets.host-set'
);
if (host.host_set_ids !== hostSet.id) {
this.router.replaceWith(
'scopes.scope.host-catalogs.host-catalog.host-sets.host-set.hosts.host',
host.host_set_ids[0],
host.id
);
}
}
}
@@ -11,8 +11,26 @@ export default class ScopesScopeHostCatalogsHostCatalogHostSetsNewRoute extends
@service store;
@service router;
@service can;
// =methods
/**
* Redirect to parent route when host-catalog does not have create authorized action.
*/
beforeModel() {
const hostCatalog = this.modelFor(
'scopes.scope.host-catalogs.host-catalog'
);
if (
this.can.cannot('create model', hostCatalog, { collection: 'host-sets' })
) {
this.router.replaceWith(
'scopes.scope.host-catalogs.host-catalog.host-sets'
);
}
}
/**
* Creates a new unsaved host set in current host catalog scope.
* @return {HostSetModel}
@@ -10,6 +10,8 @@ export default class ScopesScopeHostCatalogsHostCatalogHostsHostRoute extends Ro
// =services
@service store;
@service can;
@service router;
// =methods
@@ -17,9 +19,27 @@ export default class ScopesScopeHostCatalogsHostCatalogHostsHostRoute extends Ro
* Load a host using current host-catalog and it's parent scope.
* @param {object} params
* @param {string} params.host_id
* @return {HostModel}
* @return {Promise{HostModel}}
*/
async model({ host_id }) {
return this.store.findRecord('host', host_id, { reload: true });
}
/**
* Redirects to route with correct host-catalog id if incorrect.
* @param {HostModel} host
*/
redirect(host) {
const hostCatalog = this.modelFor(
'scopes.scope.host-catalogs.host-catalog'
);
const { host_catalog_id } = host;
if (host_catalog_id !== hostCatalog.id) {
this.router.replaceWith(
'scopes.scope.host-catalogs.host-catalog.hosts.host',
host_catalog_id,
host.id
);
}
}
}
@@ -10,9 +10,23 @@ export default class ScopesScopeHostCatalogsHostCatalogHostsNewRoute extends Rou
// =services
@service store;
@service can;
@service router;
// =methods
/**
* Redirect to parent route when host-catalog does not have create authorized action.
*/
beforeModel() {
const hostCatalog = this.modelFor(
'scopes.scope.host-catalogs.host-catalog'
);
if (this.can.cannot('create model', hostCatalog, { collection: 'hosts' })) {
this.router.replaceWith('scopes.scope.host-catalogs.host-catalog.hosts');
}
}
/**
* Creates a new unsaved host in current host catalog scope.
* @return {HostModel}
@@ -12,6 +12,7 @@ export default class ScopesScopeHostCatalogsNewRoute extends Route {
@service store;
@service router;
@service can;
// =attributes
@@ -23,6 +24,20 @@ export default class ScopesScopeHostCatalogsNewRoute extends Route {
// =methods
/**
* Redirect to parent route when scope does not have create authorized action.
*/
beforeModel() {
const scopeModel = this.modelFor('scopes.scope');
if (
this.can.cannot('create model', scopeModel, {
collection: 'host-catalogs',
})
) {
this.router.replaceWith('scopes.scope.host-catalogs');
}
}
/**
* Creates a new unsaved host catalog belonging to the current scope.
* Also rollback/destroy any new, unsaved instances from this route before
@@ -10,9 +10,21 @@ export default class ScopesScopeRolesNewRoute extends Route {
// =services
@service store;
@service can;
@service router;
// =methods
/**
* Redirect to parent route when scope does not have create authorized action.
*/
beforeModel() {
const scopeModel = this.modelFor('scopes.scope');
if (this.can.cannot('create model', scopeModel, { collection: 'roles' })) {
this.router.replaceWith('scopes.scope.roles');
}
}
/**
* Creates a new unsaved role.
* @return {RoleModel}
+16 -2
View File
@@ -11,6 +11,8 @@ export default class ScopesScopeRolesRoleRoute extends Route {
// =services
@service store;
@service can;
@service router;
// =methods
@@ -18,9 +20,9 @@ export default class ScopesScopeRolesRoleRoute extends Route {
* Load a role in current scope.
* @param {object} params
* @param {string} params.role_id
* @return {RoleModel}
* @return {Promise{RoleModel}}
*/
model({ role_id }) {
async model({ role_id }) {
const { id: scopeID } = this.modelFor('scopes.scope');
return this.store.findRecord('role', role_id, {
reload: true,
@@ -28,6 +30,18 @@ export default class ScopesScopeRolesRoleRoute extends Route {
});
}
/**
* Redirects to route with correct scope id if incorrect.
* @param {RoleModel} role
* @param {object} transition
*/
redirect(role, transition) {
const scope = this.modelFor('scopes.scope');
if (role.scopeID !== scope.id) {
this.router.replaceWith(transition.to.name, role.scopeID, role.id);
}
}
// =actions
/**
* Rollback changes on a role.
@@ -5,12 +5,14 @@
import Route from '@ember/routing/route';
import { inject as service } from '@ember/service';
import { paramValueFinder } from 'admin/utils/param-value-finder';
export default class ScopesScopeSessionRecordingsSessionRecordingRoute extends Route {
// =services
@service store;
@service session;
@service router;
@service can;
// =methods
@@ -36,4 +38,25 @@ export default class ScopesScopeSessionRecordingsSessionRecordingRoute extends R
return sessionRecording;
}
/**
* Redirects to route with correct scope id if incorrect.
* @param {SessionRecordingModel} sessionRecording
* @param {object} transition
*/
redirect(sessionRecording, transition) {
const scope = this.modelFor('scopes.scope');
if (sessionRecording.scopeID !== scope.id) {
const paramValues = paramValueFinder(
'session-recording',
transition.to.parent
);
this.router.replaceWith(
transition.to.name,
sessionRecording.scopeID,
sessionRecording.id,
...paramValues
);
}
}
}
@@ -9,6 +9,8 @@ import { inject as service } from '@ember/service';
export default class ScopesScopeSessionRecordingsSessionRecordingChannelsByConnectionChannelRoute extends Route {
// =services
@service store;
@service can;
@service router;
// =methods
/**
@@ -31,4 +33,21 @@ export default class ScopesScopeSessionRecordingsSessionRecordingChannelsByConne
storageBucket,
};
}
/**
* Redirects to route with correct session-recording id if incorrect.
* @param {channelRecording: Object, sessionRecording: Object, storageBucket: Object} model
*/
redirect(model) {
const { channelRecording, sessionRecording } = model;
const session_recording_id =
channelRecording.connection_recording.session_recording.id;
if (session_recording_id !== sessionRecording.id) {
this.router.replaceWith(
'scopes.scope.session-recordings.session-recording.channels-by-connection.channel',
session_recording_id,
channelRecording.id
);
}
}
}
@@ -14,9 +14,25 @@ export default class ScopesScopeStorageBucketsNewRoute extends Route {
// =services
@service store;
@service can;
@service router;
// =methods
/**
* Redirect to parent route when scope does not have create authorized action.
*/
beforeModel() {
const scopeModel = this.modelFor('scopes.scope');
if (
this.can.cannot('create scope', scopeModel, {
collection: 'storage-buckets',
})
) {
this.router.replaceWith('scopes.scope.storage-buckets');
}
}
/**
* Creates a new unsaved storage bucket.
* Also rollback/destroy any new, unsaved instances from this route before
@@ -10,6 +10,8 @@ export default class ScopesScopeStorageBucketsStorageBucketRoute extends Route {
// =services
@service store;
@service can;
@service router;
// =methods
@@ -17,9 +19,26 @@ export default class ScopesScopeStorageBucketsStorageBucketRoute extends Route {
* Load a storage bucket in current scope.
* @param {object} params
* @param {string} params.storage_bucket_id
* @return {StorageBucketModel}
* @return {Promise{StorageBucketModel}}
*/
model({ storage_bucket_id }) {
return this.store.findRecord('storage-bucket', storage_bucket_id);
async model({ storage_bucket_id }) {
return this.store.findRecord('storage-bucket', storage_bucket_id, {
reload: true,
});
}
/**
* Redirects to route with correct scope id if incorrect.
* @param {StorageBucketModel} storageBucket
*/
redirect(storageBucket) {
const scope = this.modelFor('scopes.scope');
if (storageBucket.scopeID !== scope.id) {
this.router.replaceWith(
'scopes.scope.storage-buckets.storage-bucket',
storageBucket.scopeID,
storageBucket.id
);
}
}
}
@@ -14,8 +14,7 @@ export default class ScopesScopeTargetsNewRoute extends Route {
@service store;
@service router;
@service features;
// =methods
@service can;
// =attributes
queryParams = {
@@ -23,6 +22,21 @@ export default class ScopesScopeTargetsNewRoute extends Route {
refreshModel: true,
},
};
// =methods
/**
* Redirect to parent route when scope does not have create authorized action.
*/
beforeModel() {
const scopeModel = this.modelFor('scopes.scope');
if (
this.can.cannot('create model', scopeModel, { collection: 'targets' })
) {
this.router.replaceWith('scopes.scope.targets');
}
}
/**
* Creates a new unsaved target in current scope.
* Also rollback/destroy any new, unsaved instances from this route before
@@ -10,6 +10,8 @@ export default class ScopesScopeTargetsTargetRoute extends Route {
// =services
@service store;
@service can;
@service router;
// =methods
@@ -17,9 +19,21 @@ export default class ScopesScopeTargetsTargetRoute extends Route {
* Load a target.
* @param {object} params
* @param {string} params.target_id
* @return {TargetModel}
* @return {Promise{TargetModel}}
*/
async model({ target_id }) {
return this.store.findRecord('target', target_id, { reload: true });
}
/**
* Redirects to route with correct scope id if incorrect.
* @param {TargetModel} target
* @param {object} transition
*/
redirect(target, transition) {
const scope = this.modelFor('scopes.scope');
if (target.scopeID !== scope.id) {
this.router.replaceWith(transition.to.name, target.scopeID, target.id);
}
}
}
@@ -10,9 +10,21 @@ export default class ScopesScopeUsersNewRoute extends Route {
// =services
@service store;
@service can;
@service router;
// =methods
/**
* Redirect to parent route when scope does not have create authorized action.
*/
beforeModel() {
const scopeModel = this.modelFor('scopes.scope');
if (this.can.cannot('create model', scopeModel, { collection: 'users' })) {
this.router.replaceWith('scopes.scope.users');
}
}
/**
* Creates a new unsaved user.
* @return {UserModel}
+17 -3
View File
@@ -10,6 +10,8 @@ export default class ScopesScopeUsersUserRoute extends Route {
// =services
@service store;
@service can;
@service router;
// =methods
@@ -17,9 +19,21 @@ export default class ScopesScopeUsersUserRoute extends Route {
* Load a user in current scope.
* @param {object} params
* @param {string} params.user_id
* @return {UserModel}
* @return {Promise{UserModel}}
*/
model({ user_id }) {
return this.store.findRecord('user', user_id);
async model({ user_id }) {
return this.store.findRecord('user', user_id, { reload: true });
}
/**
* Redirects to route with correct scope id if incorrect.
* @param {UserModel} user
* @param {object} transition
*/
redirect(user, transition) {
const scope = this.modelFor('scopes.scope');
if (user.scopeID !== scope.id) {
this.router.replaceWith(transition.to.name, user.scopeID, user.id);
}
}
}
@@ -13,9 +13,25 @@ export default class ScopesScopeWorkersNewRoute extends Route {
// =services
@service store;
@service can;
@service router;
// =methods
/**
* Redirect to parent route when scope does not have create authorized action.
*/
beforeModel() {
const scopeModel = this.modelFor('scopes.scope');
if (
this.can.cannot('create worker led worker', scopeModel, {
collection: 'workers',
})
) {
this.router.replaceWith('scopes.scope.workers');
}
}
/**
* Creates a new unsaved worker.
* @return {WorkerModel}
@@ -10,6 +10,8 @@ export default class ScopesScopeWorkersWorkerRoute extends Route {
// =services
@service store;
@service can;
@service router;
// =methods
@@ -17,9 +19,24 @@ export default class ScopesScopeWorkersWorkerRoute extends Route {
* Load a worker in current scope.
* @param {object} params
* @param {string} params.worker_id
* @return {WorkerModel}
* @return {Promise{WorkerModel}}
*/
model({ worker_id }) {
return this.store.findRecord('worker', worker_id);
async model({ worker_id }) {
return this.store.findRecord('worker', worker_id, { reload: true });
}
/**
* Redirects to route with correct scope id if incorrect.
* @param {WorkerModel} worker
*/
redirect(worker) {
const scope = this.modelFor('scopes.scope');
if (worker.scopeID !== scope.id) {
this.router.replaceWith(
'scopes.scope.workers.worker',
worker.scopeID,
worker.id
);
}
}
}
+14
View File
@@ -0,0 +1,14 @@
// Traverses through a RouteInfo object in order to find
// the dynamic segments of a route and stops traversing when
// the route name of the caller is reached. In the RouteInfo
// object the params object contains the dynamic segment of
// a particular route.
export function paramValueFinder(routeName, parentInfo) {
if (!parentInfo?.params || parentInfo.localName === routeName) {
return [];
}
let otherParam = Object.values(parentInfo.params);
return [...paramValueFinder(routeName, parentInfo.parent), ...otherParam];
}
@@ -26,11 +26,13 @@ module('Acceptance | accounts | create', function (hooks) {
org: null,
},
authMethods: null,
authMethod: null,
account: null,
};
const urls = {
orgScope: null,
authMethods: null,
authMethod: null,
accounts: null,
newAccount: null,
account: null,
@@ -190,4 +192,21 @@ module('Acceptance | accounts | create', function (hooks) {
'Displays field-level errors.'
);
});
test('users cannot directly navigate to new account route without proper authorization', async function (assert) {
assert.expect(2);
instances.authMethod.authorized_collection_actions.accounts =
instances.authMethod.authorized_collection_actions.accounts.filter(
(item) => item !== 'create'
);
await visit(urls.newAccount);
assert.false(
instances.authMethod.authorized_collection_actions.accounts.includes(
'create'
)
);
assert.strictEqual(currentURL(), urls.accounts);
});
});
@@ -18,6 +18,7 @@ import {
module('Acceptance | accounts | read', function (hooks) {
setupApplicationTest(hooks);
setupMirage(hooks);
const ACCOUNT_SELECTOR = 'main tbody .hds-table__td:nth-child(1) a';
const instances = {
@@ -26,11 +27,13 @@ module('Acceptance | accounts | read', function (hooks) {
org: null,
},
authMethods: null,
authMethod: null,
account: null,
};
const urls = {
orgScope: null,
authMethods: null,
authMethod: null,
accounts: null,
account: null,
};
@@ -80,4 +83,22 @@ module('Acceptance | accounts | read', function (hooks) {
await visit(urls.accounts);
assert.dom(ACCOUNT_SELECTOR).doesNotExist();
});
test('user can navigate to account and incorrect url autocorrects', async function (assert) {
assert.expect(2);
const authMethod = this.server.create('auth-method', {
scope: instances.scopes.org,
});
const account = this.server.create('account', {
scope: instances.scopes.org,
authMethod,
});
const incorrectUrl = `${urls.accounts}/${account.id}`;
const correctUrl = `${urls.orgScope}/auth-methods/${authMethod.id}/accounts/${account.id}`;
await visit(incorrectUrl);
assert.notEqual(currentURL(), incorrectUrl);
assert.strictEqual(currentURL(), correctUrl);
});
});
@@ -444,4 +444,18 @@ module('Acceptance | auth-methods | create', function (hooks) {
.dom(FIELD_ERROR_TEXT_SELECTOR)
.hasText('At least one URL is required.');
});
test('users cannot directly navigate to new auth method route without proper authorization', async function (assert) {
assert.expect(2);
instances.orgScope.authorized_collection_actions['auth-methods'] = ['list'];
await visit(urls.newAuthMethod);
assert.false(
instances.orgScope.authorized_collection_actions['auth-methods'].includes(
'create'
)
);
assert.strictEqual(currentURL(), urls.authMethods);
});
});
@@ -17,7 +17,7 @@ import {
} from 'ember-simple-auth/test-support';
import { TYPE_AUTH_METHOD_LDAP } from 'api/models/auth-method';
module('Acceptance | auth methods | delete', function (hooks) {
module('Acceptance | auth-methods | delete', function (hooks) {
setupApplicationTest(hooks);
setupMirage(hooks);
@@ -25,7 +25,7 @@ import {
import { TYPE_AUTH_METHOD_OIDC } from 'api/models/auth-method';
module('Acceptance | auth methods | oidc', function (hooks) {
module('Acceptance | auth-methods | oidc', function (hooks) {
setupApplicationTest(hooks);
setupMirage(hooks);
@@ -20,7 +20,7 @@ import {
TYPE_AUTH_METHOD_PASSWORD,
} from 'api/models/auth-method';
module('Acceptance | auth methods | read', function (hooks) {
module('Acceptance | auth-methods | read', function (hooks) {
setupApplicationTest(hooks);
setupMirage(hooks);
const AUTH_LINK_SELECTOR = 'main tbody .hds-table__td:nth-child(1) a';
@@ -183,4 +183,22 @@ module('Acceptance | auth methods | read', function (hooks) {
assert.dom(LDAP_AUTH_TYPE_SELECTOR).hasText('LDAP');
assert.dom(LDAP_AUTH_ACTIONS_SELECTOR).doesNotExist();
});
test('users can navigate to auth method and incorrect url autocorrects', async function (assert) {
assert.expect(2);
const orgScope = this.server.create('scope', {
type: 'org',
scope: { id: 'global', type: 'global' },
});
const authMethod = this.server.create('auth-method', {
scope: orgScope,
});
const incorrectUrl = `${urls.globalAuthMethods}/${authMethod.id}/accounts`;
const correctUrl = `/scopes/${orgScope.id}/auth-methods/${authMethod.id}/accounts`;
await visit(incorrectUrl);
assert.notEqual(currentURL(), incorrectUrl);
assert.strictEqual(currentURL(), correctUrl);
});
});
@@ -20,7 +20,7 @@ import {
TYPE_AUTH_METHOD_LDAP,
} from 'api/models/auth-method';
module('Acceptance | auth methods | update', function (hooks) {
module('Acceptance | auth-methods | update', function (hooks) {
setupApplicationTest(hooks);
setupMirage(hooks);
@@ -233,4 +233,22 @@ module('Acceptance | credential-libraries | create', function (hooks) {
assert.false(featuresService.isEnabled('ssh-target'));
assert.dom('[value="vault-ssh-certificate"]').doesNotExist();
});
test('users cannot directly navigate to new credential library route without proper authorization', async function (assert) {
assert.expect(2);
instances.credentialStore.authorized_collection_actions[
'credential-libraries'
] = instances.credentialStore.authorized_collection_actions[
'credential-libraries'
].filter((item) => item !== 'create');
await visit(urls.newCredentialLibrary);
assert.false(
instances.credentialStore.authorized_collection_actions[
'credential-libraries'
].includes('create')
);
assert.strictEqual(currentURL(), urls.credentialLibraries);
});
});
@@ -106,4 +106,22 @@ module('Acceptance | credential-libraries | read', function (hooks) {
await a11yAudit();
assert.ok(find('.rose-message-subtitle').textContent.trim(), 'Error 404');
});
test('users can navigate to credential library and incorrect url autocorrects', async function (assert) {
assert.expect(2);
const credentialStore = this.server.create('credential-store', {
scope: instances.scopes.project,
});
const credentialLibrary = this.server.create('credential-library', {
scope: instances.scopes.project,
credentialStore,
});
const incorrectUrl = `${urls.credentialLibraries}/${credentialLibrary.id}`;
const correctUrl = `${urls.projectScope}/credential-stores/${credentialStore.id}/credential-libraries/${credentialLibrary.id}`;
await visit(incorrectUrl);
assert.notEqual(currentURL(), incorrectUrl);
assert.strictEqual(currentURL(), correctUrl);
});
});
@@ -153,4 +153,22 @@ module('Acceptance | credential-stores | create', function (hooks) {
)
);
});
test('users cannot directly navigate to new credential store route without proper authorization', async function (assert) {
assert.expect(2);
instances.scopes.project.authorized_collection_actions[
'credential-stores'
] = instances.scopes.project.authorized_collection_actions[
'credential-stores'
].filter((item) => item !== 'create');
await visit(urls.newCredentialStore);
assert.false(
instances.scopes.project.authorized_collection_actions[
'credential-stores'
].includes('create')
);
assert.strictEqual(currentURL(), urls.credentialStores);
});
});
@@ -336,5 +336,22 @@ module(
.dom('.hds-form-radio-card:nth-child(2) input')
.hasAttribute('value', 'ssh_private_key');
});
test('users cannot directly navigate to new credential route without proper authorization', async function (assert) {
assert.expect(2);
instances.staticCredentialStore.authorized_collection_actions.credentials =
instances.staticCredentialStore.authorized_collection_actions.credentials.filter(
(item) => item !== 'create'
);
await visit(urls.newCredential);
assert.false(
instances.staticCredentialStore.authorized_collection_actions.credentials.includes(
'create'
)
);
assert.strictEqual(currentURL(), urls.credentials);
});
}
);
@@ -165,4 +165,22 @@ module('Acceptance | credential-stores | credentials | read', function (hooks) {
find(`[href="https://boundaryproject.io/help/admin-ui/credentials"]`)
);
});
test('users can navigate to credential and incorrect url autocorrects', async function (assert) {
assert.expect(2);
const credentialStore = this.server.create('credential-store', {
scope: instances.scopes.project,
});
const credential = this.server.create('credential', {
scope: instances.scopes.project,
credentialStore,
});
const incorrectUrl = `${urls.credentials}/${credential.id}`;
const correctUrl = `${urls.projectScope}/credential-stores/${credentialStore.id}/credentials/${credential.id}`;
await visit(incorrectUrl);
assert.notEqual(currentURL(), incorrectUrl);
assert.strictEqual(currentURL(), correctUrl);
});
});
@@ -142,4 +142,22 @@ module('Acceptance | credential-stores | read', function (hooks) {
)
.exists();
});
test('users can navigate to credential store and incorrect url autocorrects', async function (assert) {
assert.expect(2);
const projectScope = this.server.create('scope', {
type: 'project',
scope: { id: instances.scopes.org.id, type: 'org' },
});
const credentialStore = this.server.create('credential-store', {
scope: projectScope,
});
const incorrectUrl = `${urls.credentialStores}/${credentialStore.id}/credentials`;
const correctUrl = `/scopes/${projectScope.id}/credential-stores/${credentialStore.id}/credentials`;
await visit(incorrectUrl);
assert.notEqual(currentURL(), incorrectUrl);
assert.strictEqual(currentURL(), correctUrl);
});
});
@@ -21,11 +21,9 @@ module('Acceptance | groups | create', function (hooks) {
const instances = {
scopes: {
global: null,
org: null,
},
group: null,
orgScope: null,
};
const urls = {
groups: null,
@@ -35,7 +33,7 @@ module('Acceptance | groups | create', function (hooks) {
hooks.beforeEach(function () {
authenticateSession({});
instances.orgScope = this.server.create(
instances.scopes.org = this.server.create(
'scope',
{
type: 'org',
@@ -44,9 +42,9 @@ module('Acceptance | groups | create', function (hooks) {
'withChildren'
);
instances.group = this.server.create('group', {
scope: instances.orgScope,
scope: instances.scopes.org,
});
urls.groups = `/scopes/${instances.orgScope.id}/groups`;
urls.groups = `/scopes/${instances.scopes.org.id}/groups`;
urls.group = `${urls.groups}/${instances.group.id}`;
urls.newGroup = `${urls.groups}/new`;
});
@@ -64,17 +62,21 @@ module('Acceptance | groups | create', function (hooks) {
assert.expect(2);
await visit(urls.groups);
assert.ok(
instances.orgScope.authorized_collection_actions.groups.includes('create')
instances.scopes.org.authorized_collection_actions.groups.includes(
'create'
)
);
assert.ok(find(`[href="${urls.newGroup}"]`));
});
test('cannot navigate to new groups route without proper authorization', async function (assert) {
assert.expect(2);
instances.orgScope.authorized_collection_actions.groups = [];
instances.scopes.org.authorized_collection_actions.groups = [];
await visit(urls.groups);
assert.notOk(
instances.orgScope.authorized_collection_actions.groups.includes('create')
assert.false(
instances.scopes.org.authorized_collection_actions.groups.includes(
'create'
)
);
assert.notOk(find(`[href="${urls.newGroup}"]`));
});
@@ -123,4 +125,21 @@ module('Acceptance | groups | create', function (hooks) {
'Displays field-level errors.'
);
});
test('users cannot directly navigate to new group route without proper authorization', async function (assert) {
assert.expect(2);
instances.scopes.org.authorized_collection_actions.groups =
instances.scopes.org.authorized_collection_actions.groups.filter(
(item) => item !== 'create'
);
await visit(urls.newGroup);
assert.false(
instances.scopes.org.authorized_collection_actions.groups.includes(
'create'
)
);
assert.strictEqual(currentURL(), urls.groups);
});
});
@@ -62,4 +62,22 @@ module('Acceptance | groups | read', function (hooks) {
await visit(urls.group);
assert.notOk(find('main tbody .rose-table-header-cell:nth-child(1) a'));
});
test('users can navigate to group and incorrect url autocorrects', async function (assert) {
assert.expect(2);
const orgScope = this.server.create('scope', {
type: 'org',
scope: { id: 'global', type: 'global' },
});
const group = this.server.create('group', {
scope: orgScope,
});
const incorrectUrl = `${urls.groups}/${group.id}/members`;
const correctUrl = `/scopes/${orgScope.id}/groups/${group.id}/members`;
await visit(incorrectUrl);
assert.notEqual(currentURL(), incorrectUrl);
assert.strictEqual(currentURL(), correctUrl);
});
});
@@ -201,4 +201,21 @@ module('Acceptance | host-catalogs | create', function (hooks) {
'Name is required.'
);
});
test('users cannot directly navigate to new host catalog route without proper authorization', async function (assert) {
assert.expect(2);
instances.scopes.project.authorized_collection_actions['host-catalogs'] =
instances.scopes.project.authorized_collection_actions[
'host-catalogs'
].filter((item) => item !== 'create');
await visit(urls.newHostCatalog);
assert.false(
instances.scopes.project.authorized_collection_actions[
'host-catalogs'
].includes('create')
);
assert.strictEqual(currentURL(), urls.hostCatalogs);
});
});
@@ -224,4 +224,21 @@ module('Acceptance | host-catalogs | host sets | create', function (hooks) {
'Name is required.'
);
});
test('users cannot directly navigate to new host set route without proper authorization', async function (assert) {
assert.expect(2);
instances.hostCatalog.authorized_collection_actions['host-sets'] =
instances.hostCatalog.authorized_collection_actions['host-sets'].filter(
(item) => item !== 'create'
);
await visit(urls.newHostSet);
assert.false(
instances.hostCatalog.authorized_collection_actions['host-sets'].includes(
'create'
)
);
assert.strictEqual(currentURL(), urls.hostSets);
});
});
@@ -278,4 +278,29 @@ module('Acceptance | host-catalogs | host-sets | hosts', function (hooks) {
await click('form [type="submit"]');
assert.ok(find('[role="alert"]'));
});
test('users can navigate to host and incorrect url autocorrects', async function (assert) {
assert.expect(2);
const hostCatalog = this.server.create('host-catalog', {
scope: instances.scopes.project,
type: 'plugin',
plugin: { name: 'aws' },
});
const hostSet = this.server.create('host-set', {
scope: instances.scopes.project,
hostCatalog,
});
const host = this.server.create('host', {
scope: instances.scopes.project,
hostCatalog,
hostSetIds: [hostSet.id],
});
const incorrectUrl = `${urls.hostCatalogs}/${hostCatalog.id}/host-sets/${instances.hostSet.id}/hosts/${host.id}`;
const correctUrl = `${urls.hostCatalogs}/${hostCatalog.id}/host-sets/${hostSet.id}/hosts/${host.id}`;
await visit(incorrectUrl);
assert.notEqual(currentURL(), incorrectUrl);
assert.strictEqual(currentURL(), correctUrl);
});
});
@@ -15,7 +15,7 @@ import {
//invalidateSession,
} from 'ember-simple-auth/test-support';
module('Acceptance | host-catalogs | host sets | read', function (hooks) {
module('Acceptance | host-catalogs | host-sets | read', function (hooks) {
setupApplicationTest(hooks);
setupMirage(hooks);
@@ -113,4 +113,23 @@ module('Acceptance | host-catalogs | host sets | read', function (hooks) {
.dom(`[href="https://boundaryproject.io/help/admin-ui/host-sets"]`)
.exists();
});
test('users can navigate to host set and incorrect url autocorrects', async function (assert) {
assert.expect(2);
const hostCatalog = this.server.create('host-catalog', {
scope: instances.scopes.project,
type: 'static',
});
const hostSet = this.server.create('host-set', {
scope: instances.scopes.project,
hostCatalog,
});
const incorrectUrl = `${urls.hostSets}/${hostSet.id}`;
const correctUrl = `${urls.hostCatalogs}/${hostCatalog.id}/host-sets/${hostSet.id}`;
await visit(incorrectUrl);
assert.notEqual(currentURL(), incorrectUrl);
assert.strictEqual(currentURL(), correctUrl);
});
});
@@ -54,6 +54,7 @@ module('Acceptance | host-catalogs | hosts | create', function (hooks) {
scope: { id: instances.scopes.org.id, type: 'org' },
});
instances.hostCatalog = this.server.create('host-catalog', {
type: 'static',
scope: instances.scopes.project,
});
instances.host = this.server.create('host', {
@@ -160,4 +161,21 @@ module('Acceptance | host-catalogs | hosts | create', function (hooks) {
'Name is required.'
);
});
test('users cannot directly navigate to new host route without proper authorization', async function (assert) {
assert.expect(2);
instances.hostCatalog.authorized_collection_actions.hosts =
instances.hostCatalog.authorized_collection_actions.hosts.filter(
(item) => item !== 'create'
);
await visit(urls.newHost);
assert.false(
instances.hostCatalog.authorized_collection_actions.hosts.includes(
'create'
)
);
assert.strictEqual(currentURL(), urls.hosts);
});
});
@@ -113,4 +113,23 @@ module('Acceptance | host-catalogs | hosts | read', function (hooks) {
.dom(`[href="https://boundaryproject.io/help/admin-ui/hosts"]`)
.exists();
});
test('users can navigate to host and incorrect url autocorrects', async function (assert) {
assert.expect(2);
const hostCatalog = this.server.create('host-catalog', {
scope: instances.scopes.project,
type: 'static',
});
const host = this.server.create('host', {
scope: instances.scopes.project,
hostCatalog,
});
const incorrectUrl = `${urls.hosts}/${host.id}`;
const correctUrl = `${urls.hostCatalogs}/${hostCatalog.id}/hosts/${host.id}`;
await visit(incorrectUrl);
assert.notEqual(currentURL(), incorrectUrl);
assert.strictEqual(currentURL(), correctUrl);
});
});
@@ -105,4 +105,22 @@ module('Acceptance | host-catalogs | read', function (hooks) {
.dom(`[href="https://boundaryproject.io/help/admin-ui/host-catalogs"]`)
.exists();
});
test('users can navigate to host catalog and incorrect url autocorrects', async function (assert) {
assert.expect(2);
const projectScope = this.server.create('scope', {
type: 'project',
scope: { id: instances.scopes.org.id, type: 'org' },
});
const hostCatalog = this.server.create('host-catalog', {
scope: projectScope,
});
const incorrectUrl = `${urls.hostCatalogs}/${hostCatalog.id}/host-sets`;
const correctUrl = `/scopes/${projectScope.id}/host-catalogs/${hostCatalog.id}/host-sets`;
await visit(incorrectUrl);
assert.notEqual(currentURL(), incorrectUrl);
assert.strictEqual(currentURL(), correctUrl);
});
});
@@ -242,4 +242,21 @@ module('Acceptance | managed-groups | create', function (hooks) {
assert.dom(ERROR_MSG_SELECTOR).hasText('The request was invalid.');
assert.dom(FIELD_ERROR_TEXT_SELECTOR).hasText('Name is required.');
});
test('Users cannot directly navigate to a new managed group route without proper authorization', async function (assert) {
assert.expect(2);
instances.authMethod.authorized_collection_actions['managed-groups'] =
instances.authMethod.authorized_collection_actions[
'managed-groups'
].filter((item) => item !== 'create');
await visit(urls.newManagedGroup);
assert.false(
instances.authMethod.authorized_collection_actions[
'managed-groups'
].includes('create')
);
assert.strictEqual(currentURL(), urls.managedGroups);
});
});
@@ -118,4 +118,23 @@ module('Acceptance | managed-groups | read', function (hooks) {
assert.dom(`[href="${urls.ldapManagedGroup}"]`).doesNotExist();
});
test('User can navigate to managed group and incorrect url autocorrects', async function (assert) {
assert.expect(2);
const authMethod = this.server.create('auth-method', {
scope: instances.scopes.org,
type: TYPE_AUTH_METHOD_LDAP,
});
const managedGroup = this.server.create('managed-group', {
scope: instances.scopes.org,
authMethod,
});
const incorrectUrl = `${urls.managedGroups}/${managedGroup.id}`;
const correctUrl = `${urls.orgScope}/auth-methods/${authMethod.id}/managed-groups/${managedGroup.id}`;
await visit(incorrectUrl);
assert.notEqual(currentURL(), incorrectUrl);
assert.strictEqual(currentURL(), correctUrl);
});
});
+29 -9
View File
@@ -27,7 +27,6 @@ module('Acceptance | roles | create', function (hooks) {
project: null,
},
role: null,
orgScope: null,
};
const urls = {
roles: null,
@@ -38,7 +37,7 @@ module('Acceptance | roles | create', function (hooks) {
hooks.beforeEach(function () {
authenticateSession({});
instances.orgScope = this.server.create(
instances.scopes.org = this.server.create(
'scope',
{
type: 'org',
@@ -52,19 +51,19 @@ module('Acceptance | roles | create', function (hooks) {
// mechanism.
instances.scopes.project = this.server.create('scope', {
type: 'project',
scope: { id: instances.orgScope.id, type: instances.orgScope.type },
scope: { id: instances.scopes.org.id, type: instances.scopes.org.type },
});
instances.role = this.server.create(
'role',
{
scope: instances.orgScope,
scope: instances.scopes.org,
},
'withPrincipals'
);
urls.roles = `/scopes/${instances.orgScope.id}/roles`;
urls.roles = `/scopes/${instances.scopes.org.id}/roles`;
urls.role = `${urls.roles}/${instances.role.id}`;
urls.newRole = `${urls.roles}/new`;
urls.orgScope = `/scopes/${instances.orgScope.id}`;
urls.orgScope = `/scopes/${instances.scopes.org.id}`;
});
test('can create new role', async function (assert) {
@@ -80,17 +79,21 @@ module('Acceptance | roles | create', function (hooks) {
assert.expect(2);
await visit(urls.roles);
assert.ok(
instances.orgScope.authorized_collection_actions.roles.includes('create')
instances.scopes.org.authorized_collection_actions.roles.includes(
'create'
)
);
assert.ok(find(`[href="${urls.newRole}"]`));
});
test('Users cannot navigate to new roles route without proper authorization', async function (assert) {
assert.expect(2);
instances.orgScope.authorized_collection_actions.roles = [];
instances.scopes.org.authorized_collection_actions.roles = [];
await visit(urls.roles);
assert.notOk(
instances.orgScope.authorized_collection_actions.roles.includes('create')
instances.scopes.org.authorized_collection_actions.roles.includes(
'create'
)
);
assert.notOk(find(`[href="${urls.newRole}"]`));
});
@@ -133,4 +136,21 @@ module('Acceptance | roles | create', function (hooks) {
assert.ok(find('[role="alert"]'));
assert.ok(find('.hds-form-error__message'));
});
test('users cannot directly navigate to new role route without proper authorization', async function (assert) {
assert.expect(2);
instances.scopes.org.authorized_collection_actions.roles =
instances.scopes.org.authorized_collection_actions.roles.filter(
(item) => item !== 'create'
);
await visit(urls.newRole);
assert.false(
instances.scopes.org.authorized_collection_actions.roles.includes(
'create'
)
);
assert.strictEqual(currentURL(), urls.roles);
});
});
@@ -96,4 +96,22 @@ module('Acceptance | roles | read', function (hooks) {
await a11yAudit();
assert.strictEqual(currentURL(), urls.role);
});
test('users can navigate to role and incorrect url autocorrects', async function (assert) {
assert.expect(2);
const orgScope = this.server.create('scope', {
type: 'org',
scope: { id: 'global', type: 'global' },
});
const role = this.server.create('role', {
scope: orgScope,
});
const incorrectUrl = `${urls.roles}/${role.id}/grants`;
const correctUrl = `/scopes/${orgScope.id}/roles/${role.id}/grants`;
await visit(incorrectUrl);
assert.notEqual(currentURL(), incorrectUrl);
assert.strictEqual(currentURL(), correctUrl);
});
});
@@ -10,7 +10,7 @@ import setupMirage from 'ember-cli-mirage/test-support/setup-mirage';
import a11yAudit from 'ember-a11y-testing/test-support/audit';
import { authenticateSession } from 'ember-simple-auth/test-support';
module('Acceptance | session recordings | read', function (hooks) {
module('Acceptance | session-recordings | read', function (hooks) {
setupApplicationTest(hooks);
setupMirage(hooks);
@@ -113,4 +113,15 @@ module('Acceptance | session recordings | read', function (hooks) {
await click(LIST_CHANNEL_RECORDING_BUTTON);
assert.strictEqual(currentURL(), urls.channelRecording);
});
test('users can navigate to session recording and incorrect url autocorrects', async function (assert) {
assert.expect(2);
featuresService.enable('ssh-session-recording');
const incorrectUrl = `/scopes/${instances.scopes.org.id}/session-recordings/${instances.sessionRecording.id}/channels-by-connection`;
await visit(incorrectUrl);
assert.notEqual(currentURL(), incorrectUrl);
assert.strictEqual(currentURL(), urls.sessionRecording);
});
});
@@ -11,11 +11,13 @@ import { authenticateSession } from 'ember-simple-auth/test-support';
import { Response } from 'miragejs';
module(
'Acceptance | session recordings | session recording | channels by connection | channel',
'Acceptance | session-recordings | session-recording | channels-by-connection | channel',
function (hooks) {
setupApplicationTest(hooks);
setupMirage(hooks);
let featuresService;
// Instances
const instances = {
scopes: {
@@ -32,6 +34,7 @@ module(
globalScope: null,
sessionRecordings: null,
sessionRecording: null,
channelRecording: null,
};
hooks.beforeEach(function () {
@@ -55,22 +58,23 @@ module(
urls.globalScope = `/scopes/global`;
urls.sessionRecordings = `${urls.globalScope}/session-recordings`;
urls.sessionRecording = `${urls.sessionRecordings}/${instances.sessionRecording.id}/channels-by-connection`;
urls.connectionRecording = `${urls.sessionRecording}/${instances.channelRecording.id}`;
urls.channelRecording = `${urls.sessionRecording}/${instances.channelRecording.id}`;
featuresService = this.owner.lookup('service:features');
authenticateSession({});
});
test('user can navigate to a channel', async function (assert) {
assert.expect(1);
// Visit channel
await visit(urls.connectionRecording);
assert.strictEqual(currentURL(), urls.connectionRecording);
await visit(urls.channelRecording);
assert.strictEqual(currentURL(), urls.channelRecording);
});
test('user can view recording with proper authorization', async function (assert) {
assert.expect(1);
// Visit channel
await visit(urls.connectionRecording);
await visit(urls.channelRecording);
// if authorized player will render
assert.dom('.session-recording-player').exists();
@@ -81,7 +85,7 @@ module(
instances.channelRecording.mime_types = [];
// Visit channel
await visit(urls.connectionRecording);
await visit(urls.channelRecording);
// if unauthorized player will not render
assert.dom('.session-recording-player').doesNotExist();
@@ -95,7 +99,7 @@ module(
);
// Visit channel
await visit(urls.connectionRecording);
await visit(urls.channelRecording);
// if unauthorized player will not render
assert.dom('.session-recording-player').doesNotExist();
@@ -118,7 +122,7 @@ module(
);
// Visit channel
await visit(urls.connectionRecording);
await visit(urls.channelRecording);
// if there was an error player will not render
assert.dom('.session-recording-player').doesNotExist();
@@ -130,11 +134,25 @@ module(
test('user can navigate back to session recording screen', async function (assert) {
assert.expect(1);
// Visit channel
await visit(urls.connectionRecording);
await visit(urls.channelRecording);
// click "Back to channels" link in player header
await click('.session-recording-player-header > a');
assert.strictEqual(currentURL(), urls.sessionRecording);
});
test('users can navigate to channel recording and incorrect url autocorrects', async function (assert) {
assert.expect(2);
featuresService.enable('ssh-session-recording');
const sessionRecording = this.server.create('session-recording', {
scope: instances.scopes.global,
});
const incorrectUrl = `${urls.sessionRecordings}/${sessionRecording.id}/channels-by-connection/${instances.channelRecording.id}`;
await visit(incorrectUrl);
assert.notEqual(currentURL(), incorrectUrl);
assert.strictEqual(currentURL(), urls.channelRecording);
});
}
);
@@ -200,4 +200,21 @@ module('Acceptance | storage-buckets | create', function (hooks) {
assert.dom(ALERT_TEXT_SELECTOR).hasText('The request was invalid.');
assert.dom(FIELD_ERROR_TEXT_SELECTOR).hasText('Name is required.');
});
test('users cannot directly navigate to new storage bucket route without proper authorization', async function (assert) {
assert.expect(2);
instances.scopes.global.authorized_collection_actions['storage-buckets'] =
instances.scopes.global.authorized_collection_actions[
'storage-buckets'
].filter((item) => item !== 'create');
await visit(urls.newStorageBucket);
assert.false(
instances.scopes.global.authorized_collection_actions[
'storage-buckets'
].includes('create')
);
assert.strictEqual(currentURL(), urls.storageBuckets);
});
});
@@ -87,4 +87,14 @@ module('Acceptance | storage-buckets | read', function (hooks) {
assert.dom(MESSAGE_SELECTOR).hasText('Error 404');
});
test('users can navigate to storage bucket and incorrect url autocorrects', async function (assert) {
assert.expect(2);
const incorrectUrl = `/scopes/${instances.scopes.org.id}/storage-buckets/${instances.storageBucket.id}`;
await visit(incorrectUrl);
assert.notEqual(currentURL(), incorrectUrl);
assert.strictEqual(currentURL(), urls.storageBucket);
});
});
@@ -443,4 +443,21 @@ module('Acceptance | targets | create', function (hooks) {
assert.false(featuresService.isEnabled('target-network-address'));
assert.dom('[name="address"]').doesNotExist();
});
test('users cannot directly navigate to new storage bucket route without proper authorization', async function (assert) {
assert.expect(2);
instances.scopes.project.authorized_collection_actions.targets =
instances.scopes.project.authorized_collection_actions.targets.filter(
(item) => item !== 'create'
);
await visit(urls.newSSHTarget);
assert.false(
instances.scopes.project.authorized_collection_actions.targets.includes(
'create'
)
);
assert.strictEqual(currentURL(), urls.targets);
});
});
@@ -49,6 +49,7 @@ module('Acceptance | targets | enable session recording', function (hooks) {
target: null,
enableSessionRecording: null,
storageBuckets: null,
storageBucket: null,
newStorageBucket: null,
};
@@ -78,13 +79,13 @@ module('Acceptance | targets | enable session recording', function (hooks) {
storageBucketTwo = instances.storageBucket[1];
// Generate route URLs for resources
urls.globalScope = `/scopes/global/scopes`;
urls.globalScope = `/scopes/global`;
urls.orgScope = `/scopes/${instances.scopes.org.id}/scopes`;
urls.projectScope = `/scopes/${instances.scopes.project.id}`;
urls.targets = `${urls.projectScope}/targets`;
urls.target = `${urls.targets}/${instances.target.id}`;
urls.enableSessionRecording = `${urls.target}/enable-session-recording`;
urls.storageBuckets = `${urls.projectScope}/storage-buckets`;
urls.storageBuckets = `${urls.globalScope}/storage-buckets`;
urls.newStorageBucket = `${urls.enableSessionRecording}/create-storage-bucket`;
urls.storageBucket = `${urls.storageBuckets}/${storageBucketOne.id}`;
@@ -49,6 +49,7 @@ module('Acceptance | targets | enable session recording', function (hooks) {
target: null,
enableSessionRecording: null,
storageBuckets: null,
storageBucket: null,
newStorageBucket: null,
};
@@ -78,13 +79,13 @@ module('Acceptance | targets | enable session recording', function (hooks) {
storageBucketTwo = instances.storageBucket[1];
// Generate route URLs for resources
urls.globalScope = `/scopes/global/scopes`;
urls.globalScope = `/scopes/global`;
urls.orgScope = `/scopes/${instances.scopes.org.id}/scopes`;
urls.projectScope = `/scopes/${instances.scopes.project.id}`;
urls.targets = `${urls.projectScope}/targets`;
urls.target = `${urls.targets}/${instances.target.id}`;
urls.enableSessionRecording = `${urls.target}/enable-session-recording`;
urls.storageBuckets = `${urls.projectScope}/storage-buckets`;
urls.storageBuckets = `${urls.globalScope}/storage-buckets`;
urls.newStorageBucket = `${urls.enableSessionRecording}/create-storage-bucket`;
urls.storageBucket = `${urls.storageBuckets}/${storageBucketOne.id}`;
@@ -193,4 +193,14 @@ module('Acceptance | targets | read', function (hooks) {
.dom(`[href="https://boundaryproject.io/help/admin-ui/targets"]`)
.exists();
});
test('users can navigate to target and incorrect url autocorrects', async function (assert) {
assert.expect(2);
const incorrectUrl = `/scopes/${instances.scopes.org.id}/targets/${instances.sshTarget.id}`;
await visit(incorrectUrl);
assert.notEqual(currentURL(), incorrectUrl);
assert.strictEqual(currentURL(), urls.sshTarget);
});
});
@@ -145,4 +145,21 @@ module('Acceptance | users | create', function (hooks) {
assert.dom('.rose-notification-body').hasText('The request was invalid.');
assert.dom('.rose-form-error-message').hasText('Name is required.');
});
test('users cannot directly navigate to new user route without proper authorization', async function (assert) {
assert.expect(2);
instances.scopes.org.authorized_collection_actions.users =
instances.scopes.org.authorized_collection_actions.users.filter(
(item) => item !== 'create'
);
await visit(urls.newUser);
assert.false(
instances.scopes.org.authorized_collection_actions.users.includes(
'create'
)
);
assert.strictEqual(currentURL(), urls.users);
});
});
@@ -90,4 +90,14 @@ module('Acceptance | users | read', function (hooks) {
.dom(`[href="https://boundaryproject.io/help/admin-ui/users"]`)
.exists();
});
test('users can navigate to user and incorrect url autocorrects', async function (assert) {
assert.expect(2);
const incorrectUrl = `/scopes/global/users/${instances.user.id}`;
await visit(incorrectUrl);
assert.notEqual(currentURL(), incorrectUrl);
assert.strictEqual(currentURL(), urls.user);
});
});
@@ -4,7 +4,14 @@
*/
import { module, test } from 'qunit';
import { visit, fillIn, click, find, findAll } from '@ember/test-helpers';
import {
visit,
fillIn,
click,
find,
findAll,
currentURL,
} from '@ember/test-helpers';
import { setupApplicationTest } from 'ember-qunit';
import setupMirage from 'ember-cli-mirage/test-support/setup-mirage';
import { Response } from 'miragejs';
@@ -144,4 +151,21 @@ module('Acceptance | workers | create', function (hooks) {
'Displays primary error message.'
);
});
test('users cannot directly navigate to new worker route without proper authorization', async function (assert) {
assert.expect(2);
globalScope.authorized_collection_actions.workers =
globalScope.authorized_collection_actions.workers.filter(
(item) => item !== 'create:worker-led'
);
await visit(newWorkerURL);
assert.false(
globalScope.authorized_collection_actions.workers.includes(
'create:worker-led'
)
);
assert.strictEqual(currentURL(), workersURL);
});
});
@@ -86,4 +86,18 @@ module('Acceptance | workers | read', function (hooks) {
assert.dom('main tbody .rose-table-header-cell:nth-child(1) a').isVisible();
});
test('users can navigate to worker and incorrect url autocorrects', async function (assert) {
assert.expect(2);
const orgScope = this.server.create('scope', {
type: 'org',
scope: { id: 'global', type: 'global' },
});
const incorrectUrl = `/scopes/${orgScope.id}/workers/${instances.worker.id}`;
await visit(incorrectUrl);
assert.notEqual(currentURL(), incorrectUrl);
assert.strictEqual(currentURL(), urls.worker);
});
});
@@ -0,0 +1,36 @@
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
import { paramValueFinder } from 'admin/utils/param-value-finder';
module('Unit | Utility | param-value-finder', function (hooks) {
setupTest(hooks);
let router;
hooks.beforeEach(function () {
router = this.owner.lookup('service:router');
});
test('returns empty array when null or empty routeInfo object is used', function (assert) {
assert.expect(2);
let result = paramValueFinder('scopes', null);
assert.deepEqual(result, []);
result = paramValueFinder('scopes', {});
assert.deepEqual(result, []);
});
test('returns correct array when routeInfo object is used', function (assert) {
assert.expect(2);
let routeInfo = router.recognize('/scopes');
let result = paramValueFinder('scopes', routeInfo.parent);
assert.deepEqual(result, []);
routeInfo = router.recognize('/scopes/global/auth-methods');
result = paramValueFinder('scopes', routeInfo.parent);
assert.deepEqual(result, ['global']);
});
});