Skip to content

Commit

Permalink
add permission control service for saved objects and workspace saved …
Browse files Browse the repository at this point in the history
…objects client wrapper (opensearch-project#230)

* feat: add basic workspace saved objects client wrapper

Signed-off-by: Lin Wang <wonglam@amazon.com>

* feat: add unit test (#2)

Signed-off-by: SuZhou-Joe <suzhou@amazon.com>

* feat: update client wrapper

Signed-off-by: tygao <tygao@amazon.com>

* feat: init permission control in workspace plugin

Signed-off-by: Lin Wang <wonglam@amazon.com>

* Support disable permission check on workspace (opensearch-project#228)

* support disable permission check for workspace

Signed-off-by: Hailong Cui <ihailong@amazon.com>

* fix typos

Signed-off-by: Hailong Cui <ihailong@amazon.com>

---------

Signed-off-by: Hailong Cui <ihailong@amazon.com>

* feat: add ACLSearchParams consumer in repository (#3)

Signed-off-by: SuZhou-Joe <suzhou@amazon.com>

* fix: ACLSearchParams missing in search dsl

Signed-off-by: Lin Wang <wonglam@amazon.com>

* test: add integration test for workspace saved objects client wrapper

Signed-off-by: Lin Wang <wonglam@amazon.com>

* style: add empty line under license

Signed-off-by: Lin Wang <wonglam@amazon.com>

* test: enable workspace permission control for integration tests

Signed-off-by: Lin Wang <wonglam@amazon.com>

* feat: add workspace into includeHiddenTypes (opensearch-project#249)

* feat: add workspace into includeHiddenTypes of client wrapper and permission control client

Signed-off-by: SuZhou-Joe <suzhou@amazon.com>

* fix: hiddenType side effect

Signed-off-by: SuZhou-Joe <suzhou@amazon.com>

---------

Signed-off-by: SuZhou-Joe <suzhou@amazon.com>

* fix workspace client wrapper integration tests

Signed-off-by: Lin Wang <wonglam@amazon.com>

* add permissions fields to workspace CRUD APIs

Signed-off-by: Lin Wang <wonglam@amazon.com>

* Move WorkspacePermissionMode inside workspace plugin

Signed-off-by: Lin Wang <wonglam@amazon.com>

* Address pr comments

Signed-off-by: Lin Wang <wonglam@amazon.com>

* Remove ACLSearchParams in public SavedObjectsFindOptions

Signed-off-by: Lin Wang <wonglam@amazon.com>

* Remove lodash and Add default permissionModes

Signed-off-by: Lin Wang <wonglam@amazon.com>

* feat: address concerns on ensureRawRequest (#4)

* feat: address concerns on ensureRawRequest

Signed-off-by: SuZhou-Joe <suzhou@amazon.com>

* feat: add check for empty array

Signed-off-by: SuZhou-Joe <suzhou@amazon.com>

* feat: make find api backward compatible

Signed-off-by: SuZhou-Joe <suzhou@amazon.com>

* feat: remove useless code

Signed-off-by: SuZhou-Joe <suzhou@amazon.com>

---------

Signed-off-by: SuZhou-Joe <suzhou@amazon.com>

* Update annotations and  error

Signed-off-by: Lin Wang <wonglam@amazon.com>

* Add unit tests for worksapce saved objects client wrapper

Signed-off-by: Lin Wang <wonglam@amazon.com>

* Remove getPrincipalsOfObjects in permission

Signed-off-by: Lin Wang <wonglam@amazon.com>

* Fix permissionEnabled flag missed in workspace plugin setup test

Signed-off-by: Lin Wang <wonglam@amazon.com>

* Change back to Not Authorized error

Signed-off-by: Lin Wang <wonglam@amazon.com>

* Fix unit tests for query_params and plugin setup

Signed-off-by: Lin Wang <wonglam@amazon.com>

* Fix unittests in workspace server utils

Signed-off-by: Lin Wang <wonglam@amazon.com>

* feat: add workspacesSearchOperators to decouple ACLSearchParams

Signed-off-by: SuZhou-Joe <suzhou@amazon.com>

* feat: update test cases

Signed-off-by: SuZhou-Joe <suzhou@amazon.com>

* feat: optimize test cases

Signed-off-by: SuZhou-Joe <suzhou@amazon.com>

* feat: optimize comment

Signed-off-by: SuZhou-Joe <suzhou@amazon.com>

* feat: omit defaultSearchOperator in public savedobjetcs client

Signed-off-by: SuZhou-Joe <suzhou@amazon.com>

* feat: omit workspacesSearchOperator field

Signed-off-by: SuZhou-Joe <suzhou@amazon.com>

---------

Signed-off-by: Lin Wang <wonglam@amazon.com>
Signed-off-by: SuZhou-Joe <suzhou@amazon.com>
Signed-off-by: tygao <tygao@amazon.com>
Signed-off-by: Hailong Cui <ihailong@amazon.com>
Co-authored-by: Lin Wang <wonglam@amazon.com>
Co-authored-by: SuZhou-Joe <suzhou@amazon.com>
Co-authored-by: Hailong Cui <ihailong@amazon.com>
  • Loading branch information
4 people committed Mar 18, 2024
1 parent 856d014 commit e0c9e87
Show file tree
Hide file tree
Showing 27 changed files with 2,600 additions and 25 deletions.
1 change: 1 addition & 0 deletions src/core/public/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ export {
StringValidationRegex,
StringValidationRegexString,
WorkspaceObject,
WorkspaceAttribute,
} from '../types';

export {
Expand Down
6 changes: 5 additions & 1 deletion src/core/public/saved_objects/saved_objects_client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,11 @@ import { HttpFetchOptions, HttpSetup } from '../http';

type SavedObjectsFindOptions = Omit<
SavedObjectFindOptionsServer,
'sortOrder' | 'rootSearchFields' | 'typeToNamespacesMap'
| 'sortOrder'
| 'rootSearchFields'
| 'typeToNamespacesMap'
| 'ACLSearchParams'
| 'workspacesSearchOperator'
>;

type PromiseType<T extends Promise<any>> = T extends Promise<infer U> ? U : never;
Expand Down
5 changes: 5 additions & 0 deletions src/core/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,11 @@ export {
importSavedObjectsFromStream,
resolveSavedObjectsImportErrors,
SavedObjectsDeleteByWorkspaceOptions,
ACL,
Principals,
TransformedPermission,
PrincipalType,
Permissions,
} from './saved_objects';

export {
Expand Down
8 changes: 8 additions & 0 deletions src/core/server/saved_objects/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,3 +84,11 @@ export {

export { savedObjectsConfig, savedObjectsMigrationConfig } from './saved_objects_config';
export { SavedObjectTypeRegistry, ISavedObjectTypeRegistry } from './saved_objects_type_registry';

export {
Permissions,
ACL,
Principals,
TransformedPermission,
PrincipalType,
} from './permission_control/acl';
4 changes: 4 additions & 0 deletions src/core/server/saved_objects/service/lib/repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -823,6 +823,8 @@ export class SavedObjectsRepository {
filter,
preference,
workspaces,
workspacesSearchOperator,
ACLSearchParams,
} = options;

if (!type && !typeToNamespacesMap) {
Expand Down Expand Up @@ -897,6 +899,8 @@ export class SavedObjectsRepository {
hasReference,
kueryNode,
workspaces,
workspacesSearchOperator,
ACLSearchParams,
}),
},
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -646,6 +646,131 @@ describe('#getQueryParams', () => {
});
});
});

describe('when using ACLSearchParams search', () => {
it('no ACLSearchParams provided', () => {
const result: Result = getQueryParams({
registry,
ACLSearchParams: {},
});
expect(result.query.bool.filter[1]).toEqual(undefined);
});

it('workspacesSearchOperator prvided as "OR"', () => {
const result: Result = getQueryParams({
registry,
workspaces: ['foo'],
workspacesSearchOperator: 'OR',
});
expect(result.query.bool.filter[1]).toEqual({
bool: {
should: [
{
bool: {
must_not: [
{
exists: {
field: 'workspaces',
},
},
{
exists: {
field: 'permissions',
},
},
],
},
},
{
bool: {
minimum_should_match: 1,
should: [
{
bool: {
must: [
{
term: {
workspaces: 'foo',
},
},
],
},
},
],
},
},
],
},
});
});

it('principals and permissionModes provided in ACLSearchParams', () => {
const result: Result = getQueryParams({
registry,
ACLSearchParams: {
principals: {
users: ['user-foo'],
groups: ['group-foo'],
},
permissionModes: ['read'],
},
});
expect(result.query.bool.filter[1]).toEqual({
bool: {
should: [
{
bool: {
must_not: [
{
exists: {
field: 'workspaces',
},
},
{
exists: {
field: 'permissions',
},
},
],
},
},
{
bool: {
filter: [
{
bool: {
should: [
{
terms: {
'permissions.read.users': ['user-foo'],
},
},
{
term: {
'permissions.read.users': '*',
},
},
{
terms: {
'permissions.read.groups': ['group-foo'],
},
},
{
term: {
'permissions.read.groups': '*',
},
},
],
},
},
],
},
},
],
},
});
});
});
});

describe('namespaces property', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ type KueryNode = any;

import { ISavedObjectTypeRegistry } from '../../../saved_objects_type_registry';
import { ALL_NAMESPACES_STRING, DEFAULT_NAMESPACE_STRING } from '../utils';
import { SavedObjectsFindOptions } from '../../../types';
import { ACL } from '../../../permission_control/acl';

/**
* Gets the types based on the type. Uses mappings to support
Expand Down Expand Up @@ -187,6 +189,8 @@ interface QueryParams {
hasReference?: HasReferenceQueryParams;
kueryNode?: KueryNode;
workspaces?: string[];
workspacesSearchOperator?: 'AND' | 'OR';
ACLSearchParams?: SavedObjectsFindOptions['ACLSearchParams'];
}

export function getClauseForReference(reference: HasReferenceQueryParams) {
Expand Down Expand Up @@ -244,6 +248,8 @@ export function getQueryParams({
hasReference,
kueryNode,
workspaces,
workspacesSearchOperator = 'AND',
ACLSearchParams,
}: QueryParams) {
const types = getTypes(
registry,
Expand All @@ -268,17 +274,6 @@ export function getQueryParams({
],
};

if (workspaces) {
bool.filter.push({
bool: {
should: workspaces.map((workspace) => {
return getClauseForWorkspace(workspace);
}),
minimum_should_match: 1,
},
});
}

if (search) {
const useMatchPhrasePrefix = shouldUseMatchPhrasePrefix(search);
const simpleQueryStringClause = getSimpleQueryStringClause({
Expand All @@ -300,6 +295,69 @@ export function getQueryParams({
}
}

const ACLSearchParamsShouldClause: any = [];

if (ACLSearchParams) {
if (ACLSearchParams.permissionModes?.length && ACLSearchParams.principals) {
const permissionDSL = ACL.generateGetPermittedSavedObjectsQueryDSL(
ACLSearchParams.permissionModes,
ACLSearchParams.principals
);
ACLSearchParamsShouldClause.push(permissionDSL.query);
}
}

if (workspaces?.length) {
if (workspacesSearchOperator === 'OR') {
ACLSearchParamsShouldClause.push({
bool: {
should: workspaces.map((workspace) => {
return getClauseForWorkspace(workspace);
}),
minimum_should_match: 1,
},
});
} else {
bool.filter.push({
bool: {
should: workspaces.map((workspace) => {
return getClauseForWorkspace(workspace);
}),
minimum_should_match: 1,
},
});
}
}

if (ACLSearchParamsShouldClause.length) {
bool.filter.push({
bool: {
should: [
/**
* Return those objects without workspaces field and permissions field to keep find API backward compatible
*/
{
bool: {
must_not: [
{
exists: {
field: 'workspaces',
},
},
{
exists: {
field: 'permissions',
},
},
],
},
},
...ACLSearchParamsShouldClause,
],
},
});
}

return { query: { bool } };
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import { IndexMapping } from '../../../mappings';
import { getQueryParams } from './query_params';
import { getSortingParams } from './sorting_params';
import { ISavedObjectTypeRegistry } from '../../../saved_objects_type_registry';
import { SavedObjectsFindOptions } from '../../../types';

type KueryNode = any;

Expand All @@ -53,6 +54,8 @@ interface GetSearchDslOptions {
};
kueryNode?: KueryNode;
workspaces?: string[];
workspacesSearchOperator?: 'AND' | 'OR';
ACLSearchParams?: SavedObjectsFindOptions['ACLSearchParams'];
}

export function getSearchDsl(
Expand All @@ -73,6 +76,8 @@ export function getSearchDsl(
hasReference,
kueryNode,
workspaces,
workspacesSearchOperator,
ACLSearchParams,
} = options;

if (!type) {
Expand All @@ -96,6 +101,8 @@ export function getSearchDsl(
hasReference,
kueryNode,
workspaces,
workspacesSearchOperator,
ACLSearchParams,
}),
...getSortingParams(mappings, type, sortField, sortOrder),
};
Expand Down
10 changes: 10 additions & 0 deletions src/core/server/saved_objects/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export {
} from './import/types';

import { SavedObject } from '../../types';
import { Principals } from './permission_control/acl';

type KueryNode = any;

Expand Down Expand Up @@ -112,6 +113,15 @@ export interface SavedObjectsFindOptions {
preference?: string;
/** If specified, will only retrieve objects that are in the workspaces */
workspaces?: string[];
/** By default the operator will be 'AND' */
workspacesSearchOperator?: 'AND' | 'OR';
/**
* The params here will be combined with bool clause and is used for filtering with ACL structure.
*/
ACLSearchParams?: {
principals?: Principals;
permissionModes?: string[];
};
}

/**
Expand Down
7 changes: 7 additions & 0 deletions src/plugins/workspace/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,10 @@ export const WORKSPACE_FATAL_ERROR_APP_ID = 'workspace_fatal_error';
export const WORKSPACE_SAVED_OBJECTS_CLIENT_WRAPPER_ID = 'workspace';
export const WORKSPACE_CONFLICT_CONTROL_SAVED_OBJECTS_CLIENT_WRAPPER_ID =
'workspace_conflict_control';

export enum WorkspacePermissionMode {
Read = 'read',
Write = 'write',
LibraryRead = 'library_read',
LibraryWrite = 'library_write',
}
5 changes: 4 additions & 1 deletion src/plugins/workspace/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ import { schema, TypeOf } from '@osd/config-schema';

export const configSchema = schema.object({
enabled: schema.boolean({ defaultValue: false }),
permission: schema.object({
enabled: schema.boolean({ defaultValue: true }),
}),
});

export type ConfigSchema = TypeOf<typeof configSchema>;
export type WorkspacePluginConfigType = TypeOf<typeof configSchema>;
Loading

0 comments on commit e0c9e87

Please sign in to comment.