Add model support for managed groups (#1544)

* chore: 🤖 streamline managed groups getter on role
This commit is contained in:
Dharini Jeeva authored and GitHub committed 2022-12-22 14:09:56 -08:00
1 parent 392da009d0
commit f2c0458fb5
3 files changed
+134 -40

No files matched your search

+119
View File
@@ -1,11 +1,13 @@
import GeneratedRoleModel from '../generated/models/role'; import GeneratedRoleModel from '../generated/models/role';
import { attr } from '@ember-data/model'; import { attr } from '@ember-data/model';
import { inject as service } from '@ember/service'; import { inject as service } from '@ember/service';
import { all, resolve } from 'rsvp';
export default class RoleModel extends GeneratedRoleModel { export default class RoleModel extends GeneratedRoleModel {
// =services // =services
@service store; @service store;
@service resourceFilterStore;
// =attributes // =attributes
@@ -37,6 +39,123 @@ export default class RoleModel extends GeneratedRoleModel {
: null; : null;
} }
/**
* A list of IDs for principals of type `user`.
* @type {string[]}
*/
get userIDs() {
return this.principals
.filter(({ type }) => type === 'user')
.map(({ principal_id }) => principal_id);
}
/**
* A list of IDs for principals of type `group`.
* @type {string[]}
*/
get groupIDs() {
return this.principals
.filter(({ type }) => type === 'group')
.map(({ principal_id }) => principal_id);
}
/**
* A list of IDs for principals of type `managed group`.
* @type {string[]}
*/
get managedGroupIDs() {
return this.principals
.filter(({ type }) => type === 'managed group')
.map(({ principal_id }) => principal_id);
}
/**
* A promise that resolves to an array of User instances.
* When calling this getter, be sure to await resolution
* before interacting with the results.
* @type {Promise[UserModel]}
*/
get users() {
const ids = this.userIDs;
// Role has prinicipal IDs,
// return a promise which resolves model instances for those IDs
if (ids?.length) {
return this.resourceFilterStore
.queryBy('user', { id: ids }, { scope_id: 'global', recursive: true })
.then((models) => models.map((model) => model));
}
// No principal IDs,
// return a promise resolving to an empty array
return resolve([]);
}
/**
* A promise that resolves to an array of Group instances.
* When calling this getter, be sure to await resolution
* before interacting with the results.
* @type {Promise[GroupModel]}
*/
get groups() {
const ids = this.groupIDs;
// Role has prinicipal IDs,
// return a promise which resolves model instances for those IDs
if (ids?.length) {
return this.resourceFilterStore
.queryBy('group', { id: ids }, { scope_id: 'global', recursive: true })
.then((models) => models.map((model) => model));
}
// No principal IDs,
// return a promise resolving to an empty array
return resolve([]);
}
/**
* A promise that resolves to an array of ManagedGroup instances.
* When calling this getter, be sure to await resolution
* before interacting with the results.
* @type {Promise[ManagedGroupModel]}
*/
get managedGroups() {
const ids = this.managedGroupIDs;
// Role has prinicipal IDs
// return a promise which resolves model instances for those IDs
if (ids?.length) {
// Collect all auth methods.
const authMethods = this.resourceFilterStore.queryBy(
'auth-method',
{ type: 'oidc' },
{ scope_id: 'global', recursive: true }
);
// For each auth method, query all managed groups with IDs
// that match the role's manage group principal IDs.
const managedGroups = authMethods
.then((methods) =>
all(
methods.map(({ id: auth_method_id }) =>
this.resourceFilterStore
.queryBy('managed-group', { id: ids }, { auth_method_id })
.then((models) => models.map((model) => model))
)
)
)
// The result is an array of arrays of model instances (grouped by
// auth methods), so these must be flattened.
.then((grouped) => grouped.flat());
return managedGroups;
}
// No principal IDs,
// return a promise resolving to an empty array
return resolve([]);
}
// =methods // =methods
/** /**
+5 -2
View File
@@ -64,6 +64,7 @@ module('Unit | Model | role', function (hooks) {
principals: [ principals: [
{ id: '1', type: 'user' }, { id: '1', type: 'user' },
{ id: '3', type: 'group' }, { id: '3', type: 'group' },
{ id: '5', type: 'managed group' },
], ],
version: 1, version: 1,
scope: { scope: {
@@ -84,7 +85,7 @@ module('Unit | Model | role', function (hooks) {
(schema, request) => { (schema, request) => {
const body = JSON.parse(request.requestBody); const body = JSON.parse(request.requestBody);
assert.deepEqual(body, { assert.deepEqual(body, {
principal_ids: ['1', '3'], principal_ids: ['1', '3', '5'],
version: 1, version: 1,
}); });
return { id: '123abc' }; return { id: '123abc' };
@@ -101,6 +102,7 @@ module('Unit | Model | role', function (hooks) {
principals: [ principals: [
{ id: '1', type: 'user' }, { id: '1', type: 'user' },
{ id: '3', type: 'group' }, { id: '3', type: 'group' },
{ id: '5', type: 'managed group' },
], ],
version: 1, version: 1,
scope: { scope: {
@@ -111,7 +113,7 @@ module('Unit | Model | role', function (hooks) {
}, },
}); });
const model = store.peekRecord('role', '123abc'); const model = store.peekRecord('role', '123abc');
await model.removePrincipals(['1', '3']); await model.removePrincipals(['1', '3', '5']);
}); });
test('it has a `removePrincipal` method that deletes a single principal using `removePrincipals` method', async function (assert) { test('it has a `removePrincipal` method that deletes a single principal using `removePrincipals` method', async function (assert) {
@@ -138,6 +140,7 @@ module('Unit | Model | role', function (hooks) {
principals: [ principals: [
{ id: '1', type: 'user' }, { id: '1', type: 'user' },
{ id: '3', type: 'group' }, { id: '3', type: 'group' },
{ id: '5', type: 'managed group' },
], ],
version: 1, version: 1,
scope: { scope: {
@@ -8,60 +8,32 @@ import { notifySuccess, notifyError } from 'core/decorators/notify';
export default class ScopesScopeRolesRolePrincipalsRoute extends Route { export default class ScopesScopeRolesRolePrincipalsRoute extends Route {
// =services // =services
@service intl; @service intl;
@service resourceFilterStore; @service resourceFilterStore;
// =methods // =methods
/** /**
* Returns users and groups associated with this role. * Returns users, groups and managed-groups associated with this role.
* @param {object} params * @param {object} params
* @return {Promise{RoleModel}} * @return {Promise{role, principals}}
*/ */
async model() { async model() {
const role = this.modelFor('scopes.scope.roles.role'); const role = this.modelFor('scopes.scope.roles.role');
// Fetch user and group principals.
const users = await role.users;
const groups = await role.groups;
const managedGroups = await role.managedGroups;
// Merge polymorphic principals.
const principals = [...users, ...groups, ...managedGroups];
// Gather user and group IDs as separate arrays, since these return { role, principals };
// will be queried in separate API queries.
const userIDs = role.principals
.filter(({ type }) => type === 'user')
.map(({ principal_id }) => principal_id);
const groupIDs = role.principals
.filter(({ type }) => type === 'group')
.map(({ principal_id }) => principal_id);
// Query for users.
const users = userIDs?.length
? (
await this.resourceFilterStore.queryBy(
'user',
{ id: userIDs },
{ scope_id: 'global', recursive: true }
)
).map((model) => model)
: [];
// Query for groups.
const groups = groupIDs?.length
? (
await this.resourceFilterStore.queryBy(
'group',
{ id: groupIDs },
{ scope_id: 'global', recursive: true }
)
).map((model) => model)
: [];
return {
role,
principals: users.concat(groups),
};
} }
// =actions // =actions
/** /**
* Remove a principal from the current role and redirect to principals index. * Remove a principal from the current role and redirect to principals index.
* @param {UserModel, GroupModel} principal * @param {UserModel, GroupModel, ManagedGroupModel} principal
*/ */
@action @action
@loading @loading