nocobase/packages/core/client/src/acl/ACLProvider.tsx
katherinehhh bc5ecc9b5f
fix(acl): association field acl check error (#2675)
* fix: association field acl check

* fix: association field acl check

* fix: import action form field acl check error
2023-09-19 17:26:29 +08:00

301 lines
8.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { Field } from '@formily/core';
import { Schema, useField, useFieldSchema } from '@formily/react';
import React, { createContext, useContext, useEffect } from 'react';
import { Navigate } from 'react-router-dom';
import { useAPIClient, useRequest } from '../api-client';
import { useAppSpin } from '../application/hooks/useAppSpin';
import { useBlockRequestContext } from '../block-provider/BlockProvider';
import { useCollection } from '../collection-manager';
import { useResourceActionContext } from '../collection-manager/ResourceActionProvider';
import { useRecord } from '../record-provider';
import { SchemaComponentOptions, useDesignable } from '../schema-component';
export const ACLContext = createContext<any>({});
// TODO: delete thisreplace by `ACLPlugin`
export const ACLProvider = (props) => {
return (
<SchemaComponentOptions
components={{ ACLCollectionFieldProvider, ACLActionProvider, ACLMenuItemProvider, ACLCollectionProvider }}
>
{props.children}
</SchemaComponentOptions>
);
};
const getRouteUrl = (props) => {
if (props?.match) {
return props.match;
}
return props && getRouteUrl(props?.children?.props);
};
export const ACLRolesCheckProvider = (props) => {
const route = getRouteUrl(props.children.props);
const { setDesignable } = useDesignable();
const { render } = useAppSpin();
const api = useAPIClient();
const result = useRequest<{
data: {
snippets: string[];
role: string;
resources: string[];
actions: any;
actionAlias: any;
strategy: any;
allowAll: boolean;
};
}>(
{
url: 'roles:check',
},
{
onSuccess(data) {
if (!data?.data?.snippets.includes('ui.*')) {
setDesignable(false);
}
if (data?.data?.role !== api.auth.role) {
api.auth.setRole(data?.data?.role);
}
},
},
);
if (result.loading) {
return render();
}
if (result.error) {
return <Navigate replace to={'/signin'} />;
}
return <ACLContext.Provider value={result}>{props.children}</ACLContext.Provider>;
};
export const useRoleRecheck = () => {
const ctx = useContext(ACLContext);
const { allowAll } = useACLRoleContext();
return () => {
if (allowAll) {
return;
}
ctx.refresh();
};
};
export const useACLContext = () => {
return useContext(ACLContext);
};
export const ACLActionParamsContext = createContext<any>({});
export const useACLRolesCheck = () => {
const ctx = useContext(ACLContext);
const data = ctx?.data?.data;
const getActionAlias = (actionPath: string) => {
const actionName = actionPath.split(':').pop();
return data?.actionAlias?.[actionName] || actionName;
};
return {
data,
getActionAlias,
inResources: (resourceName: string) => {
return data?.resources?.includes?.(resourceName);
},
getResourceActionParams: (actionPath: string) => {
const [resourceName] = actionPath.split(':');
const actionAlias = getActionAlias(actionPath);
return data?.actions?.[`${resourceName}:${actionAlias}`] || data?.actions?.[actionPath];
},
getStrategyActionParams: (actionPath: string) => {
const actionAlias = getActionAlias(actionPath);
const strategyAction = data?.strategy?.actions?.find((action) => {
const [value] = action.split(':');
return value === actionAlias;
});
return strategyAction ? {} : null;
},
};
};
const getIgnoreScope = (options: any = {}) => {
const { schema, recordPkValue } = options;
let ignoreScope = false;
if (options.ignoreScope) {
ignoreScope = true;
}
if (schema?.['x-acl-ignore-scope']) {
ignoreScope = true;
}
if (schema?.['x-acl-action-props']?.['skipScopeCheck']) {
ignoreScope = true;
}
if (!recordPkValue) {
ignoreScope = true;
}
return ignoreScope;
};
const useAllowedActions = () => {
const service = useResourceActionContext();
const result = useBlockRequestContext() || { service };
return result?.allowedActions ?? result?.service?.data?.meta?.allowedActions;
};
const useResourceName = () => {
const service = useResourceActionContext();
const result = useBlockRequestContext() || { service };
return result?.props?.resource || result?.service?.defaultRequest?.resource;
};
export function useACLRoleContext() {
const { data, getActionAlias, inResources, getResourceActionParams, getStrategyActionParams } = useACLRolesCheck();
const allowedActions = useAllowedActions();
const verifyScope = (actionName: string, recordPkValue: any) => {
const actionAlias = getActionAlias(actionName);
if (!Array.isArray(allowedActions?.[actionAlias])) {
return null;
}
return allowedActions[actionAlias].includes(recordPkValue);
};
return {
...data,
parseAction: (actionPath: string, options: any = {}) => {
const [resourceName, actionName] = actionPath.split(':');
if (!getIgnoreScope(options)) {
const r = verifyScope(actionName, options.recordPkValue);
if (r !== null) {
return r ? {} : null;
}
}
if (data?.allowAll) {
return {};
}
if (inResources(resourceName)) {
return getResourceActionParams(actionPath);
}
return getStrategyActionParams(actionPath);
},
};
}
export const ACLCollectionProvider = (props) => {
const { allowAll, parseAction } = useACLRoleContext();
const schema = useFieldSchema();
if (allowAll) {
return props.children;
}
const actionPath = schema?.['x-acl-action'] || props.actionPath;
if (!actionPath) {
return props.children;
}
const params = parseAction(actionPath, { schema });
if (!params) {
return null;
}
const [_, actionName] = actionPath.split(':');
params.actionName = actionName;
return <ACLActionParamsContext.Provider value={params}>{props.children}</ACLActionParamsContext.Provider>;
};
export const useACLActionParamsContext = () => {
return useContext(ACLActionParamsContext);
};
export const useRecordPkValue = () => {
const { getPrimaryKey } = useCollection();
const record = useRecord();
const primaryKey = getPrimaryKey();
return record?.[primaryKey];
};
export const ACLActionProvider = (props) => {
const recordPkValue = useRecordPkValue();
const resource = useResourceName();
const { parseAction } = useACLRoleContext();
const schema = useFieldSchema();
let actionPath = schema['x-acl-action'];
if (!actionPath && resource && schema['x-action']) {
actionPath = `${resource}:${schema['x-action']}`;
}
if (!actionPath?.includes(':')) {
actionPath = `${resource}:${actionPath}`;
}
if (!actionPath) {
return <>{props.children}</>;
}
const params = parseAction(actionPath, { schema, recordPkValue });
if (!params) {
return null;
}
return <ACLActionParamsContext.Provider value={params}>{props.children}</ACLActionParamsContext.Provider>;
};
export const useACLFieldWhitelist = () => {
const params = useContext(ACLActionParamsContext);
const whitelist = []
.concat(params?.whitelist || [])
.concat(params?.fields || [])
.concat(params?.appends || []);
return {
whitelist,
schemaInWhitelist(fieldSchema: Schema, isSkip?) {
if (isSkip) {
return true;
}
if (whitelist.length === 0) {
return true;
}
if (!fieldSchema) {
return true;
}
if (!fieldSchema['x-collection-field']) {
return true;
}
const [key1, key2] = fieldSchema['x-collection-field'].split('.');
return whitelist?.includes(key2 || key1);
},
};
};
export const ACLCollectionFieldProvider = (props) => {
const fieldSchema = useFieldSchema();
const field = useField<Field>();
const { allowAll } = useACLRoleContext();
const { whitelist } = useACLFieldWhitelist();
const [name] = (fieldSchema.name as string).split('.');
const allowed = whitelist.length > 0 ? whitelist.includes(name) : true;
useEffect(() => {
if (!allowed) {
field.required = false;
field.display = 'hidden';
}
}, [allowed]);
if (allowAll) {
return <>{props.children}</>;
}
if (!fieldSchema['x-collection-field']) {
return <>{props.children}</>;
}
if (!allowed) {
return null;
}
return <>{props.children}</>;
};
export const ACLMenuItemProvider = (props) => {
const { allowAll, allowMenuItemIds = [], snippets } = useACLRoleContext();
const fieldSchema = useFieldSchema();
if (allowAll || snippets.includes('ui.*')) {
return <>{props.children}</>;
}
if (!fieldSchema['x-uid']) {
return <>{props.children}</>;
}
if (allowMenuItemIds.includes(fieldSchema['x-uid'])) {
return <>{props.children}</>;
}
return null;
};