Skip to content

Commit

Permalink
feat: enable find with acl permission check
Browse files Browse the repository at this point in the history
Signed-off-by: SuZhou-Joe <suzhou@amazon.com>
  • Loading branch information
SuZhou-Joe committed Aug 4, 2023
1 parent 9bf4ffd commit 2fdd9b7
Show file tree
Hide file tree
Showing 12 changed files with 158 additions and 83 deletions.
4 changes: 2 additions & 2 deletions src/core/server/saved_objects/permission_control/acl.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,13 +111,13 @@ describe('SavedObjectTypeRegistry', () => {
expect(result?.length).toEqual(3);
});

it('test genereate query DSL', () => {
it('test generate query DSL', () => {
const principals = {
users: ['user1'],
groups: ['group1'],
};
const result = ACL.genereateGetPermittedSavedObjectsQueryDSL(
PermissionMode.Read,
[PermissionMode.Read],
principals,
'workspace'
);
Expand Down
43 changes: 17 additions & 26 deletions src/core/server/saved_objects/permission_control/acl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export interface Principals {
groups?: string[];
}

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

export interface TransformedPermission {
type: string;
Expand Down Expand Up @@ -204,11 +204,11 @@ export class ACL {
generate query DSL by the specific conditions, used for fetching saved objects from the saved objects index
*/
public static genereateGetPermittedSavedObjectsQueryDSL(
permissionType: string,
permissionTypes: string[],
principals: Principals,
savedObjectType?: string | string[]
) {
if (!principals || !permissionType) {
if (!principals || !permissionTypes) {
return {
query: {
match_none: {},
Expand All @@ -222,30 +222,21 @@ export class ACL {
const subBool: any = {
should: [],
};
if (!!principals.users) {
subBool.should.push({
terms: {
['permissions.' + permissionType + '.users']: principals.users,
},
});
subBool.should.push({
term: {
['permissions.' + permissionType + '.users']: '*',
},
});
}
if (!!principals.groups) {
subBool.should.push({
terms: {
['permissions.' + permissionType + '.groups']: principals.groups,
},
});
subBool.should.push({
term: {
['permissions.' + permissionType + '.groups']: '*',
},

permissionTypes.forEach((permissionType) => {
Object.entries(principals).forEach(([principalType, principalsInCurrentType]) => {
subBool.should.push({
terms: {
['permissions.' + permissionType + `.${principalType}`]: principalsInCurrentType,
},
});
subBool.should.push({
term: {
['permissions.' + permissionType + `.${principalType}`]: '*',
},
});
});
}
});

bool.filter.push({
bool: subBool,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ export const savedObjectsPermissionControlMock: SavedObjectsPermissionControlCon
setup: jest.fn(),
validate: jest.fn(),
batchValidate: jest.fn(),
addPrinciplesToObjects: jest.fn(),
removePrinciplesFromObjects: jest.fn(),
getPrinciplesOfObjects: jest.fn(),
getPermittedWorkspaceIds: jest.fn(),
};
99 changes: 67 additions & 32 deletions src/core/server/saved_objects/permission_control/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,12 @@
*/

import { OpenSearchDashboardsRequest } from '../../http';
import { ensureRawRequest } from '../../http/router';
import { SavedObjectsServiceStart } from '../saved_objects_service';
import { SavedObjectsBulkGetObject } from '../service';
import { ACL, Principals, TransformedPermission } from './acl';
import { PrincipalType } from '../../../utils/constants';
import { WORKSPACE_TYPE } from '../../workspaces';

export type SavedObjectsPermissionControlContract = Pick<
SavedObjectsPermissionControl,
Expand All @@ -14,21 +18,36 @@ export type SavedObjectsPermissionControlContract = Pick<

export type SavedObjectsPermissionModes = string[];

export interface AuthInfo {
backend_roles?: string[];
user_name?: string;
}

export class SavedObjectsPermissionControl {
private getScopedClient?: SavedObjectsServiceStart['getScopedClient'];
private getScopedSavedObjectsClient(request: OpenSearchDashboardsRequest) {
return this.getScopedClient?.(request);
private createScopedRepository?: SavedObjectsServiceStart['createScopedRepository'];
private getScopedRepository(request: OpenSearchDashboardsRequest) {
return this.createScopedRepository?.(request);
}
public getPrincipalsFromRequest(request: OpenSearchDashboardsRequest): Principals {
const rawRequest = ensureRawRequest(request);
const authInfo = rawRequest?.auth?.credentials?.authInfo as AuthInfo | null;
const payload: Principals = {};
if (authInfo?.backend_roles) {
payload[PrincipalType.Groups] = authInfo.backend_roles;
}
if (authInfo?.user_name) {
payload[PrincipalType.Users] = [authInfo.user_name];
}
return payload;
}
private async bulkGetSavedObjects(
request: OpenSearchDashboardsRequest,
savedObjects: SavedObjectsBulkGetObject[]
) {
return (
(await this.getScopedSavedObjectsClient(request)?.bulkGet(savedObjects))?.saved_objects || []
);
return (await this.getScopedRepository(request)?.bulkGet(savedObjects))?.saved_objects || [];
}
public async setup(getScopedClient: SavedObjectsServiceStart['getScopedClient']) {
this.getScopedClient = getScopedClient;
public async setup(createScopedRepository: SavedObjectsServiceStart['createScopedRepository']) {
this.createScopedRepository = createScopedRepository;
}
public async validate(
request: OpenSearchDashboardsRequest,
Expand All @@ -38,54 +57,70 @@ export class SavedObjectsPermissionControl {
return await this.batchValidate(request, [savedObject], permissionModeOrModes);
}

/**
* In batch validate case, the logic is a.withPermission && b.withPermission
* @param request
* @param savedObjects
* @param permissionModeOrModes
* @returns
*/
public async batchValidate(
request: OpenSearchDashboardsRequest,
savedObjects: SavedObjectsBulkGetObject[],
permissionModeOrModes: SavedObjectsPermissionModes
) {
const savedObjectsGet = await this.bulkGetSavedObjects(request, savedObjects);
if (savedObjectsGet) {
const principals = this.getPrincipalsFromRequest(request);
const hasAllPermission = savedObjectsGet.every((item) => {
// item.permissions
const aclInstance = new ACL(item.permissions);
return aclInstance.hasPermission(permissionModeOrModes, principals);
});
return {
success: true,
result: true,
result: hasAllPermission,
};
}

return {
success: true,
result: false,
success: false,
error: 'Can not find target saved objects.',
};
}

public async addPrinciplesToObjects(
request: OpenSearchDashboardsRequest,
savedObjects: SavedObjectsBulkGetObject[],
personas: string[],
permissionModeOrModes: SavedObjectsPermissionModes
): Promise<boolean> {
return true;
}

public async removePrinciplesFromObjects(
request: OpenSearchDashboardsRequest,
savedObjects: SavedObjectsBulkGetObject[],
personas: string[],
permissionModeOrModes: SavedObjectsPermissionModes
): Promise<boolean> {
return true;
}

public async getPrinciplesOfObjects(
request: OpenSearchDashboardsRequest,
savedObjects: SavedObjectsBulkGetObject[]
): Promise<Record<string, unknown>> {
return {};
): Promise<Record<string, TransformedPermission>> {
const detailedSavedObjects = await this.bulkGetSavedObjects(request, savedObjects);
return detailedSavedObjects.reduce((total, current) => {
return {
...total,
[current.id]: new ACL(current.permissions).transformPermissions(),
};
}, {});
}

public async getPermittedWorkspaceIds(
request: OpenSearchDashboardsRequest,
permissionModeOrModes: SavedObjectsPermissionModes
) {
return [];
const principals = this.getPrincipalsFromRequest(request);
const queryDSL = ACL.genereateGetPermittedSavedObjectsQueryDSL(
permissionModeOrModes,
principals,
[WORKSPACE_TYPE]
);
const repository = this.createScopedRepository?.(request);
try {
const result = await repository?.find({
type: [WORKSPACE_TYPE],
queryDSL,
});
return result?.saved_objects.map((item) => item.id);
} catch (e) {
return [];
}
}
}
2 changes: 1 addition & 1 deletion src/core/server/saved_objects/saved_objects_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -500,7 +500,7 @@ export class SavedObjectsService
this.started = true;

const getScopedClient = clientProvider.getClient.bind(clientProvider);
this.permissionControl?.setup(getScopedClient);
this.permissionControl?.setup(repositoryFactory.createScopedRepository);

return {
getScopedClient,
Expand Down
2 changes: 2 additions & 0 deletions src/core/server/saved_objects/serialization/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
* under the License.
*/

import { Permissions } from '../permission_control/acl';
import { SavedObjectsMigrationVersion, SavedObjectReference } from '../types';

/**
Expand All @@ -53,6 +54,7 @@ export interface SavedObjectsRawDocSource {
references?: SavedObjectReference[];
originId?: string;
workspaces?: string[];
permissions?: Permissions;

[typeMapping: string]: any;
}
Expand Down
5 changes: 4 additions & 1 deletion src/core/server/saved_objects/service/lib/repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -769,6 +769,7 @@ export class SavedObjectsRepository {
filter,
preference,
workspaces,
queryDSL,
} = options;

if (!type && !typeToNamespacesMap) {
Expand Down Expand Up @@ -843,6 +844,7 @@ export class SavedObjectsRepository {
hasReference,
kueryNode,
workspaces,
queryDSL,
}),
},
};
Expand Down Expand Up @@ -1897,7 +1899,7 @@ function getSavedObjectFromSource<T>(
id: string,
doc: { _seq_no?: number; _primary_term?: number; _source: SavedObjectsRawDocSource }
): SavedObject<T> {
const { originId, updated_at: updatedAt, workspaces } = doc._source;
const { originId, updated_at: updatedAt, workspaces, permissions } = doc._source;

let namespaces: string[] = [];
if (!registry.isNamespaceAgnostic(type)) {
Expand All @@ -1917,6 +1919,7 @@ function getSavedObjectFromSource<T>(
attributes: doc._source[type],
references: doc._source.references || [],
migrationVersion: doc._source.migrationVersion,
permissions,
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
* specific language governing permissions and limitations
* under the License.
*/

import { mergeWith, isArray } from 'lodash';
// @ts-expect-error no ts
import { opensearchKuery } from '../../../opensearch_query';
type KueryNode = any;
Expand Down Expand Up @@ -174,6 +174,7 @@ interface QueryParams {
hasReference?: HasReferenceQueryParams;
kueryNode?: KueryNode;
workspaces?: string[];
queryDSL?: Record<string, any>;
}

export function getClauseForReference(reference: HasReferenceQueryParams) {
Expand Down Expand Up @@ -231,6 +232,7 @@ export function getQueryParams({
hasReference,
kueryNode,
workspaces,
queryDSL,
}: QueryParams) {
const types = getTypes(
registry,
Expand Down Expand Up @@ -287,7 +289,16 @@ export function getQueryParams({
}
}

return { query: { bool } };
const result = { query: { bool } };

if (queryDSL) {
return mergeWith({}, result, queryDSL, (objValue, srcValue) => {
if (isArray(objValue)) {
return objValue.concat(srcValue);
}
});
}
return result;
}

// we only want to add match_phrase_prefix clauses
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ interface GetSearchDslOptions {
};
kueryNode?: KueryNode;
workspaces?: string[];
queryDSL?: Record<string, any>;
}

export function getSearchDsl(
Expand All @@ -73,6 +74,7 @@ export function getSearchDsl(
hasReference,
kueryNode,
workspaces,
queryDSL,
} = options;

if (!type) {
Expand All @@ -96,6 +98,7 @@ export function getSearchDsl(
hasReference,
kueryNode,
workspaces,
queryDSL,
}),
...getSortingParams(mappings, type, sortField, sortOrder),
};
Expand Down
1 change: 1 addition & 0 deletions src/core/server/saved_objects/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ export interface SavedObjectsFindOptions {
/** An optional OpenSearch preference value to be used for the query **/
preference?: string;
workspaces?: string[];
queryDSL?: Record<string, any>;
}

/**
Expand Down
Loading

0 comments on commit 2fdd9b7

Please sign in to comment.