diff --git a/src/core/server/saved_objects/permission_control/acl.test.ts b/src/core/server/saved_objects/permission_control/acl.test.ts index 9bd2eafd0a51..b292fb747b3c 100644 --- a/src/core/server/saved_objects/permission_control/acl.test.ts +++ b/src/core/server/saved_objects/permission_control/acl.test.ts @@ -3,17 +3,12 @@ * SPDX-License-Identifier: Apache-2.0 */ -import exp from 'constants'; import { PermissionMode } from '../../../../core/utils/constants'; import { Principals, Permissions, ACL } from './acl'; describe('SavedObjectTypeRegistry', () => { let acl: ACL; - beforeEach(() => { - acl = new ACL(); - }); - it('test has permission', () => { const principals: Principals = { users: ['user1'], @@ -22,20 +17,37 @@ describe('SavedObjectTypeRegistry', () => { const permissions: Permissions = { read: principals, }; - expect(acl.hasPermission(PermissionMode.Read, permissions, 'user1')).toEqual(true); - expect(acl.hasPermission(PermissionMode.Read, permissions, 'user2')).toEqual(false); + acl = new ACL(permissions); + expect( + acl.hasPermission([PermissionMode.Read], { + users: ['user1'], + groups: [], + }) + ).toEqual(true); + expect( + acl.hasPermission([PermissionMode.Read], { + users: ['user2'], + groups: [], + }) + ).toEqual(false); }); it('test add permission', () => { - const result1 = acl.addPermission([PermissionMode.Read], ['user1']).getPermissions(); + acl = new ACL(); + const result1 = acl + .addPermission([PermissionMode.Read], { + users: ['user1'], + groups: [], + }) + .getPermissions(); expect(result1?.read?.users).toEqual(['user1']); + acl.resetPermissions(); const result2 = acl - .addPermission( - [PermissionMode.Write, PermissionMode.Management], - ['user2'], - ['group1', 'group2'] - ) + .addPermission([PermissionMode.Write, PermissionMode.Management], { + users: ['user2'], + groups: ['group1', 'group2'], + }) .getPermissions(); expect(result2?.write?.users).toEqual(['user2']); expect(result2?.management?.groups).toEqual(['group1', 'group2']); @@ -52,8 +64,14 @@ describe('SavedObjectTypeRegistry', () => { }; acl = new ACL(permissions1); const result1 = acl - .removePermission([PermissionMode.Read], ['user1']) - .removePermission([PermissionMode.Write], [], ['group2']) + .removePermission([PermissionMode.Read], { + users: ['user1'], + groups: [], + }) + .removePermission([PermissionMode.Write], { + users: [], + groups: ['group2'], + }) .getPermissions(); expect(result1?.read?.users).toEqual([]); expect(result1?.write?.groups).toEqual(['group1']); @@ -70,7 +88,10 @@ describe('SavedObjectTypeRegistry', () => { acl = new ACL(permissions2); const result2 = acl - .removePermission([PermissionMode.Read, PermissionMode.Write], ['user1'], ['group1']) + .removePermission([PermissionMode.Read, PermissionMode.Write], { + users: ['user1'], + groups: ['group1'], + }) .getPermissions(); expect(result2?.read?.users).toEqual(['*']); expect(result2?.write?.groups).toEqual(['*']); @@ -85,16 +106,20 @@ describe('SavedObjectTypeRegistry', () => { read: principals, write: principals, }; - const result = acl.transformPermissions(permissions); + acl = new ACL(permissions); + const result = acl.transformPermissions(); expect(result?.length).toEqual(3); }); it('test genereate query DSL', () => { - const result = acl.genereateGetPermittedSavedObjectsQueryDSL( - 'read', - 'workspace', - 'user1', - 'group1' + const principals = { + users: ['user1'], + groups: ['group1'], + }; + const result = ACL.genereateGetPermittedSavedObjectsQueryDSL( + PermissionMode.Read, + principals, + 'workspace' ); expect(result).toEqual({ query: { @@ -104,8 +129,8 @@ describe('SavedObjectTypeRegistry', () => { bool: { should: [ { - term: { - 'permissions.read.users': 'user1', + terms: { + 'permissions.read.users': ['user1'], }, }, { @@ -114,8 +139,8 @@ describe('SavedObjectTypeRegistry', () => { }, }, { - term: { - 'permissions.read.groups': 'group1', + terms: { + 'permissions.read.groups': ['group1'], }, }, { diff --git a/src/core/server/saved_objects/permission_control/acl.ts b/src/core/server/saved_objects/permission_control/acl.ts index 03bf787c23a0..bd26b9e732c3 100644 --- a/src/core/server/saved_objects/permission_control/acl.ts +++ b/src/core/server/saved_objects/permission_control/acl.ts @@ -12,11 +12,13 @@ export interface Principals { export type Permissions = Partial>; -const addPermissionToPrincipals = ( - principals?: Principals, - users?: string[], - groups?: string[] -) => { +export interface TransformedPermission { + type: string; + name: string; + permissions: string[]; +} + +const addToPrincipals = (principals?: Principals, users?: string[], groups?: string[]) => { if (!principals) { principals = {}; } @@ -35,11 +37,7 @@ const addPermissionToPrincipals = ( return principals; }; -const deletePermissionFromPrincipals = ( - principals?: Principals, - users?: string[], - groups?: string[] -) => { +const deleteFromPrincipals = (principals?: Principals, users?: string[], groups?: string[]) => { if (!principals) { return principals; } @@ -52,47 +50,50 @@ const deletePermissionFromPrincipals = ( return principals; }; +const checkPermission = (currentPrincipals: Principals | undefined, principals: Principals) => { + return ( + (currentPrincipals?.users && + principals?.users && + checkPermissionForSinglePrincipalType(currentPrincipals.users, principals.users)) || + (currentPrincipals?.groups && + principals.groups && + checkPermissionForSinglePrincipalType(currentPrincipals.groups, principals.groups)) + ); +}; + +const checkPermissionForSinglePrincipalType = ( + currentPrincipalArray: string[], + principalArray: string[] +) => { + return ( + currentPrincipalArray && + principalArray && + (currentPrincipalArray.includes('*') || + principalArray.some((item) => currentPrincipalArray.includes(item))) + ); +}; + export class ACL { private permissions?: Permissions; constructor(initialPermissions?: Permissions) { - this.permissions = initialPermissions; + this.permissions = initialPermissions || {}; } // parse the permissions object to check whether the specific user or group has the specific permission or not - public hasPermission( - permissionType: string, - permissions: Permissions, - user?: string, - group?: string - ) { - if ((!user && !group) || !permissionType || !permissions) { + public hasPermission(permissionTypes: string[], principals: Principals) { + if (!permissionTypes || permissionTypes.length === 0 || !this.permissions || !principals) { return false; } - const principals = permissions[permissionType]; - if (!!principals) { - if ( - !!user && - !!principals.users && - (principals.users.includes('*') || principals.users.includes(user)) - ) { - return true; - } - if ( - !!group && - !!principals.groups && - (principals.groups.includes('*') || principals.groups.includes(group)) - ) { - return true; - } - } - - return false; + const currentPermissions = this.permissions; + return permissionTypes.some((permissionType) => + checkPermission(currentPermissions[permissionType], principals) + ); } // permissions object build function, add users or groups with specific permission to the object - public addPermission(permissionTypes: string[], users?: string[], groups?: string[]) { - if ((!users && !groups) || !permissionTypes) { + public addPermission(permissionTypes: string[], principals: Principals) { + if (!permissionTypes || !principals) { return this; } if (!this.permissions) { @@ -100,10 +101,10 @@ export class ACL { } for (const permissionType of permissionTypes) { - this.permissions[permissionType] = addPermissionToPrincipals( + this.permissions[permissionType] = addToPrincipals( this.permissions[permissionType], - users, - groups + principals.users, + principals.groups ); } @@ -111,8 +112,8 @@ export class ACL { } // permissions object build funciton, remove specific permission of specific users or groups from the object - public removePermission(permissionTypes: string[], users?: string[], groups?: string[]) { - if ((!users && !groups) || !permissionTypes) { + public removePermission(permissionTypes: string[], principals: Principals) { + if (!permissionTypes || !principals) { return this; } if (!this.permissions) { @@ -120,35 +121,94 @@ export class ACL { } for (const permissionType of permissionTypes) { - this.permissions[permissionType] = deletePermissionFromPrincipals( - this.permissions[permissionType], - users, - groups + this.permissions[permissionType] = deleteFromPrincipals( + this.permissions![permissionType], + principals.users, + principals.groups ); } return this; } - // return the permissions object - public getPermissions() { - const permissions = this.permissions; + /* + transfrom permissions format + original permissions: { + read: { + users:['user1'] + }, + write:{ + groups:['group1'] + } + } + + transformed permissions: [ + {type:'user',name:'user1',permissions:['read']}, + {type:'group',name:'group1',permissions:['write']}, + ] + */ + public transformPermissions(): TransformedPermission[] { + const result: TransformedPermission[] = []; + if (!this.permissions) { + return result; + } + + const permissionMapResult: Record> = {}; + const principalTypes = [PrincipalType.Users, PrincipalType.Groups]; + for (const permissionType in this.permissions) { + if (!!permissionType) { + const value = this.permissions[permissionType]; + principalTypes.forEach((principalType) => { + if (value?.[principalType]) { + for (const principal of value[principalType]!) { + if (!permissionMapResult[principalType]) { + permissionMapResult[principalType] = {}; + } + if (!permissionMapResult[principalType][principal]) { + permissionMapResult[principalType][principal] = []; + } + permissionMapResult[principalType][principal] = [ + ...permissionMapResult[principalType][principal]!, + permissionType, + ]; + } + } + }); + } + } + + Object.entries(permissionMapResult).forEach(([type, permissionMap]) => { + Object.entries(permissionMap).forEach(([principal, permissions]) => { + result.push({ + type, + name: principal, + permissions, + }); + }); + }); + + return result; + } + + public resetPermissions() { // reset permissions this.permissions = {}; + } - return permissions; + // return the permissions object + public getPermissions() { + return this.permissions; } /* - generate query DSL by the specific conditions, used for fetching saved objects from the saved objects index - */ - public genereateGetPermittedSavedObjectsQueryDSL( + generate query DSL by the specific conditions, used for fetching saved objects from the saved objects index + */ + public static genereateGetPermittedSavedObjectsQueryDSL( permissionType: string, - savedObjectType?: string | string[], - user?: string, - group?: string + principals: Principals, + savedObjectType?: string | string[] ) { - if ((!user && !group) || !permissionType) { + if (!principals || !permissionType) { return { query: { match_none: {}, @@ -162,10 +222,10 @@ export class ACL { const subBool: any = { should: [], }; - if (!!user) { + if (!!principals.users) { subBool.should.push({ - term: { - ['permissions.' + permissionType + '.users']: user, + terms: { + ['permissions.' + permissionType + '.users']: principals.users, }, }); subBool.should.push({ @@ -174,10 +234,10 @@ export class ACL { }, }); } - if (!!group) { + if (!!principals.groups) { subBool.should.push({ - term: { - ['permissions.' + permissionType + '.groups']: group, + terms: { + ['permissions.' + permissionType + '.groups']: principals.groups, }, }); subBool.should.push({ @@ -201,68 +261,4 @@ export class ACL { return { query: { bool } }; } - - /* - transfrom permissions format - input: { - read: { - users:['user1'] - }, - write:{ - groups:['group1'] - } - } - - output: [ - {type:'user',name:'user1',permissions:['read']}, - {type:'group',name:'group1',permissions:['write']}, - ] - */ - public transformPermissions(permissions: Permissions) { - const result: any = []; - if (!permissions) { - return result; - } - const userPermissionMap: Map = new Map(); - const groupPermissionMap: Map = new Map(); - for (const permissionType in permissions) { - if (!!permissionType) { - const value = permissions[permissionType]; - if (value?.users) { - for (const user of value?.users) { - if (!userPermissionMap.get(user)) { - userPermissionMap.set(user, []); - } - userPermissionMap.set(user, [...userPermissionMap.get(user)!, permissionType]); - } - } - if (value?.groups) { - for (const group of value?.groups) { - if (!groupPermissionMap.get(group)) { - groupPermissionMap.set(group, []); - } - groupPermissionMap.set(group, [...groupPermissionMap.get(group)!, permissionType]); - } - } - } - } - - for (const [user, userPermissions] of userPermissionMap) { - result.push({ - type: PrincipalType.User, - name: user, - permissions: userPermissions, - }); - } - - for (const [group, groupPermissions] of groupPermissionMap) { - result.push({ - type: PrincipalType.Group, - name: group, - permissions: groupPermissions, - }); - } - - return result; - } } diff --git a/src/core/utils/constants.ts b/src/core/utils/constants.ts index 7099c23cc6d4..a58af8947131 100644 --- a/src/core/utils/constants.ts +++ b/src/core/utils/constants.ts @@ -14,6 +14,6 @@ export enum PermissionMode { } export enum PrincipalType { - User = 'user', - Group = 'group', + Users = 'users', + Groups = 'groups', }