Skip to content

Commit

Permalink
Convert permission settings in client side
Browse files Browse the repository at this point in the history
Signed-off-by: Lin Wang <[email protected]>
  • Loading branch information
wanglam committed Apr 3, 2024
1 parent 227a0d8 commit 9b6e6c8
Show file tree
Hide file tree
Showing 12 changed files with 152 additions and 64 deletions.
2 changes: 2 additions & 0 deletions src/core/types/saved_objects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,3 +126,5 @@ export interface SavedObjectError {
statusCode: number;
metadata?: Record<string, unknown>;
}

export type SavedObjectPermissions = Permissions;
8 changes: 7 additions & 1 deletion src/core/types/workspace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
* SPDX-License-Identifier: Apache-2.0
*/

import { Permissions } from '../server/saved_objects';

export interface WorkspaceAttribute {
id: string;
name: string;
Expand All @@ -14,6 +16,10 @@ export interface WorkspaceAttribute {
defaultVISTheme?: string;
}

export interface WorkspaceObject extends WorkspaceAttribute {
export interface WorkspaceAttributeWithPermission extends WorkspaceAttribute {
permissions?: Permissions;
}

export interface WorkspaceObject extends WorkspaceAttributeWithPermission {
readonly?: boolean;
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { WorkspaceForm, WorkspaceFormSubmitData, WorkspaceOperationType } from '
import { WORKSPACE_OVERVIEW_APP_ID } from '../../../common/constants';
import { formatUrlWithWorkspaceId } from '../../../../../core/public/utils';
import { WorkspaceClient } from '../../workspace_client';
import { convertPermissionSettingsToPermissions } from '../workspace_form/utils';

export const WorkspaceCreator = () => {
const {
Expand All @@ -22,8 +23,11 @@ export const WorkspaceCreator = () => {
async (data: WorkspaceFormSubmitData) => {
let result;
try {
const { permissions, ...attributes } = data;
result = await workspaceClient.create(attributes, permissions);
const { permissionSettings, ...attributes } = data;
result = await workspaceClient.create(
attributes,
convertPermissionSettingsToPermissions(permissionSettings)
);
} catch (error) {
notifications?.toasts.addDanger({
title: i18n.translate('workspace.create.failed', {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import type { WorkspacePermissionItemType, WorkspaceOperationType } from './constants';
import type { WorkspacePermissionMode } from '../../../common/constants';
import type { App, ApplicationStart } from '../../../../../core/public';
import type { ApplicationStart } from '../../../../../core/public';

export type WorkspacePermissionSetting =
| { type: WorkspacePermissionItemType.User; userId: string; modes: WorkspacePermissionMode[] }
Expand All @@ -18,7 +18,7 @@ export interface WorkspaceFormSubmitData {
color?: string;
icon?: string;
defaultVISTheme?: string;
permissions: WorkspacePermissionSetting[];
permissionSettings?: WorkspacePermissionSetting[];
}

export interface WorkspaceFormData extends WorkspaceFormSubmitData {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ export const useWorkspaceForm = ({ application, defaultValues, onSubmit }: Works
const [permissionSettings, setPermissionSettings] = useState<
Array<Partial<WorkspacePermissionSetting>>
>(
defaultValues?.permissions && defaultValues.permissions.length > 0
? defaultValues.permissions
defaultValues?.permissionSettings && defaultValues.permissionSettings.length > 0
? defaultValues.permissionSettings
: []
);

Expand All @@ -58,7 +58,7 @@ export const useWorkspaceForm = ({ application, defaultValues, onSubmit }: Works
description,
features: selectedFeatureIds,
color,
permissions: permissionSettings,
permissionSettings,
});
const getFormDataRef = useRef(getFormData);
getFormDataRef.current = getFormData;
Expand Down Expand Up @@ -96,12 +96,15 @@ export const useWorkspaceForm = ({ application, defaultValues, onSubmit }: Works
}),
};
}
const permissionErrors: string[] = new Array(formData.permissions.length);
for (let i = 0; i < formData.permissions.length; i++) {
const permission = formData.permissions[i];
const permissionErrors: string[] = new Array(formData.permissionSettings.length);
for (let i = 0; i < formData.permissionSettings.length; i++) {
const permission = formData.permissionSettings[i];
if (isValidWorkspacePermissionSetting(permission)) {
if (
isUserOrGroupPermissionSettingDuplicated(formData.permissions.slice(0, i), permission)
isUserOrGroupPermissionSettingDuplicated(
formData.permissionSettings.slice(0, i),
permission
)
) {
permissionErrors[i] = i18n.translate('workspace.form.permission.invalidate.group', {
defaultMessage: 'Duplicate permission setting',
Expand Down Expand Up @@ -162,8 +165,11 @@ export const useWorkspaceForm = ({ application, defaultValues, onSubmit }: Works
formData.features = defaultValues?.features ?? [];
}

const permissions = formData.permissions.filter(isValidWorkspacePermissionSetting);
onSubmit?.({ ...formData, name: formData.name!, permissions });
onSubmit?.({
...formData,
name: formData.name!,
permissionSettings: formData.permissionSettings.filter(isValidWorkspacePermissionSetting),
});
},
[defaultFeatures, onSubmit, defaultValues?.features]
);
Expand Down
76 changes: 76 additions & 0 deletions src/plugins/workspace/public/components/workspace_form/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
*/

import { WorkspacePermissionMode, DEFAULT_CHECKED_FEATURES_IDS } from '../../../common/constants';
import type { SavedObjectPermissions } from '../../../../../core/types';

import {
WorkspaceFeature,
Expand Down Expand Up @@ -95,3 +96,78 @@ export const getPermissionModeId = (modes: WorkspacePermissionMode[]) => {
}
return PermissionModeId.Read;
};

export const convertPermissionSettingsToPermissions = (
permissionItems: WorkspacePermissionSetting[] | undefined
) => {
if (!permissionItems || permissionItems.length === 0) {
return undefined;
}
return permissionItems.reduce<SavedObjectPermissions>((previous, current) => {
current.modes.forEach((mode) => {
if (!previous[mode]) {
previous[mode] = {};
}
switch (current.type) {
case 'user':
previous[mode].users = [...(previous[mode].users || []), current.userId];
break;
case 'group':
previous[mode].groups = [...(previous[mode].groups || []), current.group];
break;
}
});
return previous;
}, {});
};

const isWorkspacePermissionMode = (test: string): test is WorkspacePermissionMode =>
test === WorkspacePermissionMode.LibraryRead ||
test === WorkspacePermissionMode.LibraryWrite ||
test === WorkspacePermissionMode.Read ||
test === WorkspacePermissionMode.Write;

export const convertPermissionsToPermissionSettings = (permissions: SavedObjectPermissions) => {
const userPermissionSettings: WorkspacePermissionSetting[] = [];
const groupPermissionSettings: WorkspacePermissionSetting[] = [];
const settingType2Modes: { [key: string]: WorkspacePermissionMode[] } = {};

Object.keys(permissions).forEach((mode) => {
if (!isWorkspacePermissionMode(mode)) {
return;
}
if (permissions[mode].users) {
permissions[mode].users?.forEach((userId) => {
const settingTypeKey = `userId-${userId}`;
const modes = settingType2Modes[settingTypeKey] ? settingType2Modes[settingTypeKey] : [];

modes.push(mode);
if (modes.length === 1) {
userPermissionSettings.push({
type: WorkspacePermissionItemType.User,
userId,
modes,
});
settingType2Modes[settingTypeKey] = modes;
}
});
permissions[mode].groups?.forEach((group) => {
const settingTypeKey = `group-${group}`;
const modes = settingType2Modes[settingTypeKey] ? settingType2Modes[settingTypeKey] : [];

modes.push(mode);
if (modes.length === 1) {
userPermissionSettings.push({
type: WorkspacePermissionItemType.Group,
group,
modes,
});
}
});
}
});

return [...userPermissionSettings, ...groupPermissionSettings].filter(
isValidWorkspacePermissionSetting
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ export const WorkspaceForm = (props: WorkspaceFormProps) => {
<WorkspacePermissionSettingPanel
errors={formErrors.permissions}
onChange={setPermissionSettings}
permissionSettings={formData.permissions}
permissionSettings={formData.permissionSettings}
lastAdminItemDeletable={!!permissionLastAdminItemDeletable}
data-test-subj={`workspaceForm-permissionSettingPanel`}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,30 @@
import React, { useCallback, useEffect, useState } from 'react';
import { EuiPage, EuiPageBody, EuiPageHeader, EuiPageContent } from '@elastic/eui';
import { i18n } from '@osd/i18n';
import { WorkspaceAttribute } from 'opensearch-dashboards/public';
import { useObservable } from 'react-use';
import { of } from 'rxjs';
import { useOpenSearchDashboards } from '../../../../opensearch_dashboards_react/public';
import { WorkspaceForm, WorkspaceFormSubmitData, WorkspaceOperationType } from '../workspace_form';
import { WORKSPACE_OVERVIEW_APP_ID } from '../../../common/constants';
import { formatUrlWithWorkspaceId } from '../../../../../core/public/utils';
import { WorkspaceAttributeWithPermission } from '../../../../../core/types';
import { WorkspaceClient } from '../../workspace_client';
import { WorkspaceFormData, WorkspacePermissionSetting } from '../workspace_form/types';

interface WorkspaceWithPermission extends WorkspaceAttribute {
permissions?: WorkspacePermissionSetting[];
}
import {
convertPermissionSettingsToPermissions,
convertPermissionsToPermissionSettings,
} from '../workspace_form/utils';

function getFormDataFromWorkspace(
currentWorkspace: WorkspaceAttribute | null | undefined
): WorkspaceFormData {
const currentWorkspaceWithPermission = (currentWorkspace || {}) as WorkspaceWithPermission;
currentWorkspace: WorkspaceAttributeWithPermission | null | undefined
) {
if (!currentWorkspace) {
return null;
}
return {
...currentWorkspaceWithPermission,
permissions: currentWorkspaceWithPermission.permissions || [],
...currentWorkspace,
permissionSettings: currentWorkspace.permissions
? convertPermissionsToPermissionSettings(currentWorkspace.permissions)
: currentWorkspace.permissions,
};
}

Expand All @@ -38,7 +41,7 @@ export const WorkspaceUpdater = () => {
const isPermissionEnabled = application?.capabilities.workspaces.permissionEnabled;

const currentWorkspace = useObservable(workspaces ? workspaces.currentWorkspace$ : of(null));
const [currentWorkspaceFormData, setCurrentWorkspaceFormData] = useState<WorkspaceFormData>(
const [currentWorkspaceFormData, setCurrentWorkspaceFormData] = useState(
getFormDataFromWorkspace(currentWorkspace)
);

Expand All @@ -59,8 +62,12 @@ export const WorkspaceUpdater = () => {
}

try {
const { permissions, ...attributes } = data;
result = await workspaceClient.update(currentWorkspace.id, attributes, permissions);
const { permissionSettings, ...attributes } = data;
result = await workspaceClient.update(
currentWorkspace.id,
attributes,
convertPermissionSettingsToPermissions(permissionSettings)
);
} catch (error) {
notifications?.toasts.addDanger({
title: i18n.translate('workspace.update.failed', {
Expand Down Expand Up @@ -100,7 +107,7 @@ export const WorkspaceUpdater = () => {
[notifications?.toasts, currentWorkspace, http, application, workspaceClient]
);

if (!currentWorkspaceFormData.name) {
if (!currentWorkspaceFormData) {
return null;
}

Expand Down
23 changes: 7 additions & 16 deletions src/plugins/workspace/public/workspace_client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
WorkspaceAttribute,
WorkspacesSetup,
} from '../../../core/public';
import { WorkspacePermissionMode } from '../common/constants';
import { SavedObjectPermissions, WorkspaceAttributeWithPermission } from '../../../core/types';

const WORKSPACES_API_BASE_URL = '/api/workspaces';

Expand All @@ -31,15 +31,6 @@ type IResponse<T> =
error?: string;
};

type WorkspacePermissionItem = {
modes: Array<
| WorkspacePermissionMode.LibraryRead
| WorkspacePermissionMode.LibraryWrite
| WorkspacePermissionMode.Read
| WorkspacePermissionMode.Write
>;
} & ({ type: 'user'; userId: string } | { type: 'group'; group: string });

interface WorkspaceFindOptions {
page?: number;
perPage?: number;
Expand Down Expand Up @@ -195,7 +186,7 @@ export class WorkspaceClient {
*/
public async create(
attributes: Omit<WorkspaceAttribute, 'id'>,
permissions?: WorkspacePermissionItem[]
permissions?: SavedObjectPermissions
): Promise<IResponse<Pick<WorkspaceAttribute, 'id'>>> {
const path = this.getPath();

Expand Down Expand Up @@ -246,7 +237,7 @@ export class WorkspaceClient {
options?: WorkspaceFindOptions
): Promise<
IResponse<{
workspaces: WorkspaceAttribute[];
workspaces: WorkspaceAttributeWithPermission[];
total: number;
per_page: number;
page: number;
Expand All @@ -263,9 +254,9 @@ export class WorkspaceClient {
* Fetches a single workspace by a workspace id
*
* @param {string} id
* @returns {Promise<IResponse<WorkspaceAttribute>>} The metadata of the workspace for the given id.
* @returns {Promise<IResponse<WorkspaceAttributeWithPermission>>} The metadata of the workspace for the given id.
*/
public get(id: string): Promise<IResponse<WorkspaceAttribute>> {
public get(id: string): Promise<IResponse<WorkspaceAttributeWithPermission>> {
const path = this.getPath(id);
return this.safeFetch(path, {
method: 'GET',
Expand All @@ -277,13 +268,13 @@ export class WorkspaceClient {
*
* @param {string} id
* @param {object} attributes
* @param {WorkspacePermissionItem[]} permissions
* @param {WorkspacePermissionItem[]} permissionItems
* @returns {Promise<IResponse<boolean>>} result for this operation
*/
public async update(
id: string,
attributes: Partial<WorkspaceAttribute>,
permissions?: WorkspacePermissionItem[]
permissions?: SavedObjectPermissions
): Promise<IResponse<boolean>> {
const path = this.getPath(id);
const body = {
Expand Down
3 changes: 2 additions & 1 deletion src/plugins/workspace/server/routes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@

import { schema } from '@osd/config-schema';
import { CoreSetup, Logger, PrincipalType, ACL } from '../../../../core/server';
import { WorkspaceAttributeWithPermission } from '../../../../core/types';
import { WorkspacePermissionMode } from '../../common/constants';
import { IWorkspaceClientImpl, WorkspaceAttributeWithPermission } from '../types';
import { IWorkspaceClientImpl } from '../types';
import { SavedObjectsPermissionControlContract } from '../permission_control/client';

const WORKSPACES_API_BASE_URL = '/api/workspaces';
Expand Down
Loading

0 comments on commit 9b6e6c8

Please sign in to comment.