Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add acl permission related functions #65

Merged
merged 9 commits into from
Aug 3, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,60 @@ function defaultMapping(): IndexMapping {
workspaces: {
type: 'keyword',
},
permissions: {
properties: {
read: {
gaobinlong marked this conversation as resolved.
Show resolved Hide resolved
properties: {
users: {
type: 'keyword',
},
groups: {
type: 'keyword',
},
},
},
write: {
properties: {
users: {
type: 'keyword',
},
groups: {
type: 'keyword',
},
},
},
management: {
properties: {
users: {
type: 'keyword',
},
groups: {
type: 'keyword',
},
},
},
library_read: {
properties: {
users: {
type: 'keyword',
},
groups: {
type: 'keyword',
},
},
},
library_write: {
properties: {
users: {
type: 'keyword',
},
groups: {
type: 'keyword',
},
},
},
},
},
},
};
}
Expand Down
111 changes: 111 additions & 0 deletions src/core/server/workspaces/workspace_permission_check.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import {
PERMISSION_TYPE,
Principals,
Permissions,
WorkspacePermissionCheck,
} from './workspace_permission_check';

describe('SavedObjectTypeRegistry', () => {
let workspacePermissionCheck: WorkspacePermissionCheck;

beforeEach(() => {
workspacePermissionCheck = new WorkspacePermissionCheck();
});

it('test has permission', () => {
const permissionType = PERMISSION_TYPE.READ;
const principals: Principals = {
users: ['user1'],
groups: [],
};
const permissions: Permissions = {
read: principals,
};
expect(workspacePermissionCheck.hasPermission(permissionType, permissions, 'user1')).toEqual(
true
);
expect(workspacePermissionCheck.hasPermission(permissionType, permissions, 'user2')).toEqual(
false
);
});

it('test add permission', () => {
const permissionType = PERMISSION_TYPE.READ;
const principals: Principals = {
users: ['user1'],
groups: [],
};
const permissions: Permissions = {
read: principals,
};
const result1 = workspacePermissionCheck.addPermission(permissionType, permissions, ['user1']);
expect(result1.read?.users).toEqual(['user1']);

const result2 = workspacePermissionCheck.addPermission(permissionType, permissions, ['user2']);
expect(result2.read?.users).toEqual(['user1', 'user2']);
});

it('test remove permission', () => {
const permissionType = PERMISSION_TYPE.READ;
const principals: Principals = {
users: ['user1'],
groups: [],
};
const permissions: Permissions = {
read: principals,
};
const result1 = workspacePermissionCheck.removePermission(permissionType, permissions, [
'user1',
]);
expect(result1.read?.users).toEqual([]);

const result2 = workspacePermissionCheck.removePermission(permissionType, permissions, [
'user2',
]);
expect(result2.read?.users).toEqual(['user1']);
});

it('test genereate query DSL', () => {
const permissionType = PERMISSION_TYPE.READ;

const result = workspacePermissionCheck.genereateGetPermittedSavedObjectsQueryDSL(
permissionType,
'workspace',
'user1'
);
expect(result).toEqual({
query: {
bool: {
filter: [
{
bool: {
should: [
{
term: {
'permissions.read.users': 'user1',
},
},
{
term: {
'permissions.read.users': '*',
},
},
],
},
},
{
terms: {
type: ['workspace'],
},
},
],
},
},
});
});
});
220 changes: 220 additions & 0 deletions src/core/server/workspaces/workspace_permission_check.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import { cloneDeep } from 'lodash';

export enum PERMISSION_TYPE {
READ = 'read',
WRITE = 'write',
MANAGEMENT = 'management',
LIBRARY_READ = 'library_read',
LIBRARY_WRITE = 'library_write',
}

export interface Principals {
users?: string[];
groups?: string[];
}

export type Permissions = Partial<Record<PERMISSION_TYPE, Principals>>;

const PERMISSION_TYPE_MAP = new Map<string, PERMISSION_TYPE>([
['read', PERMISSION_TYPE.READ],
['write', PERMISSION_TYPE.WRITE],
['management', PERMISSION_TYPE.MANAGEMENT],
['library_read', PERMISSION_TYPE.LIBRARY_READ],
['library_write', PERMISSION_TYPE.LIBRARY_WRITE],
]);

const addPermissionToPrincipals = (
principals?: Principals,
users?: string[],
groups?: string[]
) => {
if (!principals) {
principals = {};
}
if (!!users && !(principals.users?.length === 1 && principals.users[0] === '*')) {
gaobinlong marked this conversation as resolved.
Show resolved Hide resolved
if (!principals.users) {
principals.users = [];
}
principals.users = Array.from(new Set([...principals.users, ...users]));
}
if (!!groups && !(principals.groups?.length === 1 && principals.groups[0] === '*')) {
if (!principals.groups) {
principals.groups = [];
}
principals.groups = Array.from(new Set([...principals.groups, ...groups]));
}
return principals;
};

const deletePermissionFromPrincipals = (
gaobinlong marked this conversation as resolved.
Show resolved Hide resolved
principals?: Principals,
users?: string[],
groups?: string[]
) => {
if (!principals) {
return principals;
}
if (!!users && !!principals.users) {
principals.users = principals.users.filter((item) => !users.includes(item));
}
if (!!groups && !!principals.groups) {
principals.groups = principals.groups.filter((item) => !groups.includes(item));
}
return principals;
};

const isParamsValid = (
permissionType: string,
user?: string | string[],
group?: string | string[]
) => {
if ((!user && !group) || !permissionType || !PERMISSION_TYPE_MAP.get(permissionType)) {
return false;
}
return true;
};

export class WorkspacePermissionCheck {
gaobinlong marked this conversation as resolved.
Show resolved Hide resolved
public hasPermission(
permissonType: string,
permissions: Permissions,
user?: string,
group?: string
) {
if (!isParamsValid(permissonType, user, group) || !permissions) {
return false;
}

const principals =
permissions[(PERMISSION_TYPE_MAP.get(permissonType) as unknown) as PERMISSION_TYPE];
if (!!principals) {
if (
!!user &&
!!principals.users &&
((principals.users.length === 1 && principals.users[0] === '*') ||
principals.users.includes(user))
) {
return true;
}
if (
!!group &&
!!principals.groups &&
((principals.groups.length === 1 && principals.groups[0] === '*') ||
principals.groups.includes(group))
) {
return true;
}
}

return false;
}

public addPermission(
permissonType: string,
permissions: Permissions,
users?: string[],
groups?: string[]
) {
if (!isParamsValid(permissonType, users, groups)) {
return permissions;
}
let newPermissions = cloneDeep(permissions);
if (!newPermissions) {
newPermissions = {};
}

let newPrincipals =
newPermissions[(PERMISSION_TYPE_MAP.get(permissonType) as unknown) as PERMISSION_TYPE];
newPrincipals = addPermissionToPrincipals(newPrincipals, users, groups);

return newPermissions;
}

public removePermission(
permissonType: string,
permissions: Permissions,
users?: string[],
groups?: string[]
) {
if (!isParamsValid(permissonType, users, groups) || !permissions) {
return permissions;
}

const newPermissions = cloneDeep(permissions);

let newPrincipals =
newPermissions[(PERMISSION_TYPE_MAP.get(permissonType) as unknown) as PERMISSION_TYPE];
newPrincipals = deletePermissionFromPrincipals(newPrincipals, users, groups);

return newPermissions;
}

public genereateGetPermittedSavedObjectsQueryDSL(
permissonType: string,
savedObjectType?: string | string[],
user?: string,
group?: string
) {
if (!isParamsValid(permissonType, user, group)) {
return {
query: {
match_none: {},
},
};
}

const bool: any = {
filter: [],
};
if (!!user) {
const subBool: any = {
should: [],
};
subBool.should.push({
term: {
gaobinlong marked this conversation as resolved.
Show resolved Hide resolved
['permissions.' + permissonType + '.users']: user,
},
});
subBool.should.push({
term: {
['permissions.' + permissonType + '.users']: '*',
},
});
bool.filter.push({
bool: subBool,
});
} else if (!!group) {
const subBool: any = {
should: [],
};
subBool.should.push({
term: {
['permissions.' + permissonType + '.groups']: group,
},
});
subBool.should.push({
term: {
['permissions.' + permissonType + '.groups']: '*',
},
});
bool.filter.push({
bool: subBool,
});
}

if (!!savedObjectType) {
bool.filter.push({
terms: {
type: Array.isArray(savedObjectType) ? savedObjectType : [savedObjectType],
},
});
}

return { query: { bool } };
}
}
Loading