Skip to content
代码片段 群组 项目
未验证 提交 cc5c31bb 编辑于 作者: Daniel Tian's avatar Daniel Tian 提交者: GitLab
浏览文件

Auto check/uncheck dependent permissions when creating custom roles

上级 4c53a22f
No related branches found
No related tags found
无相关合并请求
...@@ -9,7 +9,7 @@ import { ...@@ -9,7 +9,7 @@ import {
GlFormSelect, GlFormSelect,
GlFormTextarea, GlFormTextarea,
} from '@gitlab/ui'; } from '@gitlab/ui';
import { difference, pull } from 'lodash';
import { createAlert } from '~/alert'; import { createAlert } from '~/alert';
import { sprintf, s__ } from '~/locale'; import { sprintf, s__ } from '~/locale';
import { ACCESS_LEVEL_GUEST_INTEGER, ACCESS_LEVEL_LABELS } from '~/access_level/constants'; import { ACCESS_LEVEL_GUEST_INTEGER, ACCESS_LEVEL_LABELS } from '~/access_level/constants';
...@@ -68,8 +68,59 @@ export default { ...@@ -68,8 +68,59 @@ export default {
refetchMemberRolesQuery() { refetchMemberRolesQuery() {
return this.groupFullPath ? groupMemberRolesQuery : instanceMemberRolesQuery; return this.groupFullPath ? groupMemberRolesQuery : instanceMemberRolesQuery;
}, },
parentPermissionsLookup() {
return this.availablePermissions.reduce((acc, { value, requirements }) => {
if (requirements) {
acc[value] = requirements;
}
return acc;
}, {});
},
childPermissionsLookup() {
return this.availablePermissions.reduce((acc, { value, requirements }) => {
requirements?.forEach((requirement) => {
// Create the array if it doesn't exist, then add the requirement to it.
acc[requirement] = acc[requirement] || [];
acc[requirement].push(value);
});
return acc;
}, {});
},
},
watch: {
permissions(newPermissions, oldPermissions) {
const added = difference(newPermissions, oldPermissions);
const removed = difference(oldPermissions, newPermissions);
added.forEach((permission) => this.selectParentPermissions(permission));
removed.forEach((permission) => this.deselectChildPermissions(permission));
},
}, },
methods: { methods: {
selectParentPermissions(permission) {
const parentPermissions = this.parentPermissionsLookup[permission];
parentPermissions?.forEach((parentPermission) => {
// Only select the parent permission if it's not already selected.
if (!this.permissions.includes(parentPermission)) {
this.permissions.push(parentPermission);
this.selectParentPermissions(parentPermission);
}
});
},
deselectChildPermissions(permission) {
const childPermissions = this.childPermissionsLookup[permission];
childPermissions?.forEach((childPermission) => {
// Only remove the child permission if it's selected.
if (this.permissions.includes(childPermission)) {
pull(this.permissions, childPermission);
this.deselectChildPermissions(childPermission);
}
});
},
validateFields() { validateFields() {
this.baseRoleValid = this.baseRole !== null; this.baseRoleValid = this.baseRole !== null;
this.nameValid = Boolean(this.name); this.nameValid = Boolean(this.name);
......
...@@ -4,6 +4,7 @@ query memberRolePermissions { ...@@ -4,6 +4,7 @@ query memberRolePermissions {
description description
name name
value value
requirements
} }
} }
} }
...@@ -66,35 +66,19 @@ def created_role(name, id, access_level, permissions) ...@@ -66,35 +66,19 @@ def created_role(name, id, access_level, permissions)
let(:requirement) { permissions[permission][:requirements].first } let(:requirement) { permissions[permission][:requirements].first }
let(:requirement_name) { requirement.to_s.humanize } let(:requirement_name) { requirement.to_s.humanize }
context 'when the requirement has not been met' do it 'creates the custom role' do
it 'show an error message' do create_role(access_level, name, [permission_name])
create_role(access_level, name, [permission_name])
created_member_role = MemberRole.find_by(
name: name,
base_access_level: Gitlab::Access.options[access_level],
permission => true)
expect(created_member_role).to be_nil
expect(page).to have_content("#{requirement_name} has to be enabled in order to enable #{permission_name}")
end
end
context 'when the requirement has been met' do
it 'creates the custom role' do
create_role(access_level, name, [permission_name, requirement_name])
created_member_role = MemberRole.find_by( created_member_role = MemberRole.find_by(
name: name, name: name,
base_access_level: Gitlab::Access.options[access_level], base_access_level: Gitlab::Access.options[access_level],
permission => true, permission => true,
requirement => true) requirement => true)
expect(created_member_role).not_to be_nil expect(created_member_role).not_to be_nil
role = created_role(name, created_member_role.id, access_level, [permission_name, requirement_name]) role = created_role(name, created_member_role.id, access_level, [permission_name, requirement_name])
expect(page).to have_content(role) expect(page).to have_content(role)
end
end end
end end
end end
......
import { GlFormInput, GlFormSelect, GlFormTextarea, GlFormCheckbox } from '@gitlab/ui'; import {
GlFormInput,
GlFormSelect,
GlFormTextarea,
GlFormCheckbox,
GlFormCheckboxGroup,
} from '@gitlab/ui';
import Vue, { nextTick } from 'vue'; import Vue, { nextTick } from 'vue';
import VueApollo from 'vue-apollo'; import VueApollo from 'vue-apollo';
import { createAlert, VARIANT_DANGER } from '~/alert'; import { createAlert, VARIANT_DANGER } from '~/alert';
...@@ -256,4 +262,72 @@ describe('CreateMemberRole', () => { ...@@ -256,4 +262,72 @@ describe('CreateMemberRole', () => {
expect(mockAlertDismiss).toHaveBeenCalledTimes(1); expect(mockAlertDismiss).toHaveBeenCalledTimes(1);
}); });
}); });
describe('dependent permissions', () => {
const availablePermissions = [
{ value: 'A' },
{ value: 'B', requirements: ['A'] },
{ value: 'C', requirements: ['B'] }, // Nested dependency: C -> B -> A
{ value: 'D', requirements: ['C'] }, // Nested dependency: D -> C -> B -> A
{ value: 'E', requirements: ['F'] }, // Circular dependency
{ value: 'F', requirements: ['E'] }, // Circular dependency
{ value: 'G', requirements: ['A', 'B', 'C'] }, // Multiple dependencies
];
const checkPermissions = (permissions) => {
wrapper.findComponent(GlFormCheckboxGroup).vm.$emit('input', permissions);
};
const expectCheckedPermissions = (expected) => {
const selectedValues = wrapper
.findComponent(GlFormCheckboxGroup)
.attributes('checked')
.split(',')
.sort();
expect(selectedValues).toEqual(expected.sort());
};
beforeEach(() => {
createComponent({ availablePermissions, stubs: { GlFormCheckboxGroup: true } });
});
it.each`
permission | expected
${'A'} | ${['A']}
${'B'} | ${['A', 'B']}
${'C'} | ${['A', 'B', 'C']}
${'D'} | ${['A', 'B', 'C', 'D']}
${'E'} | ${['E', 'F']}
${'F'} | ${['E', 'F']}
${'G'} | ${['A', 'B', 'C', 'G']}
`('selects $expected when $permission is selected', async ({ permission, expected }) => {
await checkPermissions([permission]);
expectCheckedPermissions(expected);
});
it.each`
permission | expected
${'A'} | ${['E', 'F']}
${'B'} | ${['A', 'E', 'F']}
${'C'} | ${['A', 'B', 'E', 'F']}
${'D'} | ${['A', 'B', 'C', 'E', 'F', 'G']}
${'E'} | ${['A', 'B', 'C', 'D', 'G']}
${'F'} | ${['A', 'B', 'C', 'D', 'G']}
${'G'} | ${['A', 'B', 'C', 'D', 'E', 'F']}
`(
'selects $expected when all permissions are selected and $permission is unselected',
async ({ permission, expected }) => {
const allPermissions = availablePermissions.map((p) => p.value);
const selectedPermissions = allPermissions.filter((v) => v !== permission);
// Start by checking all the permissions.
await checkPermissions(allPermissions);
// Uncheck the permission by removing it from all permissions.
await checkPermissions(selectedPermissions);
expectCheckedPermissions(expected);
},
);
});
}); });
export const mockDefaultPermissions = [ export const mockDefaultPermissions = [
{ name: 'Permission A', description: 'Description A', value: 'READ_CODE' }, {
{ name: 'Permission B', description: 'Description B', value: 'READ_VULNERABILITY' }, name: 'Permission A',
{ name: 'Permission C', description: 'Description C', value: 'ADMIN_VULNERABILITY' }, description: 'Description A',
value: 'READ_CODE',
requirements: null,
},
{
name: 'Permission B',
description: 'Description B',
value: 'READ_VULNERABILITY',
requirements: null,
},
{
name: 'Permission C',
description: 'Description C',
value: 'ADMIN_VULNERABILITY',
requirements: null,
},
]; ];
export const mockPermissions = { export const mockPermissions = {
......
0% 加载中 .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册