tachybase_todo/packages/client/src/acl/ACLProvider.tsx
chenos 271e91b452
feat: improve acl module (#283)
* feat: improve code

* fix: rowKey

* fix: ctx.state.currentUser

* fix: improve code

* fix: menu item permission

* fix: x-acl-action

* fix: skipScopeCheck

* feat: relationship resource permission

* fix: createdById
2022-04-12 12:02:58 +08:00

169 lines
5.0 KiB
TypeScript

import { useFieldSchema } from '@formily/react';
import { useCookieState } from 'ahooks';
import { Spin } from 'antd';
import React, { createContext, useContext } from 'react';
import { Redirect } from 'react-router-dom';
import { useRequest } from '../api-client';
import { useCollection } from '../collection-manager';
import { useRecordIsOwn } from '../record-provider';
import { SchemaComponentOptions, useDesignable } from '../schema-component';
export const ACLContext = createContext(null);
export const ACLProvider = (props) => {
return (
<SchemaComponentOptions
components={{ ACLCollectionFieldProvider, ACLActionProvider, ACLMenuItemProvider, ACLCollectionProvider }}
>
{props.children}
</SchemaComponentOptions>
);
};
export const ACLRolesCheckProvider = (props) => {
const { setDesignable } = useDesignable();
const [roleName, setRoleName] = useCookieState('currentRoleName');
const result = useRequest(
{
url: 'roles:check',
},
{
onSuccess(data) {
if (!data?.data?.allowConfigure && !data?.data?.allowAll) {
setDesignable(false);
}
if (data?.data?.role !== roleName) {
setRoleName(data?.data?.role);
}
},
},
);
if (result.loading) {
return <Spin />;
}
if (result.error) {
return <Redirect to={'/signin'} />;
}
return <ACLContext.Provider value={result}>{props.children}</ACLContext.Provider>;
};
export const useRoleRecheck = () => {
const ctx = useContext(ACLContext);
const { allowAll, allowConfigure } = useACLRoleContext();
return () => {
if (allowAll || allowConfigure) {
return;
}
ctx.refresh();
};
};
export const useACLContext = () => {
return useContext(ACLContext);
};
export const useACLRoleContext = () => {
const ctx = useContext(ACLContext);
const data = ctx.data?.data;
return {
...data,
getActionParams(path: string, { skipOwnCheck, isOwn }) {
const [resourceName, act] = path.split(':');
const currentAction = data?.actionAlias?.[act] || act;
const hasResource = data?.resources?.includes(resourceName);
const params = data?.actions?.[`${resourceName}:${currentAction}`];
if (hasResource) {
if (!skipOwnCheck && params?.own) {
return isOwn ? params : null;
}
return params;
}
const strategyActions = data?.strategy?.actions || [];
const strategyAction = strategyActions?.find((action) => {
const [value] = action.split(':');
return value === currentAction;
});
if (!strategyAction) {
return;
}
if (skipOwnCheck) {
return {};
}
const [, actionScope] = strategyAction.split(':');
if (actionScope === 'own') {
return isOwn;
}
return {};
},
};
};
export const ACLAllowConfigure = (props) => {
const { allowAll, allowConfigure } = useACLRoleContext();
if (allowAll || allowConfigure) {
return <>{props.children}</>;
}
return null;
};
const ACLActionParamsContext = createContext<any>({});
export const ACLCollectionProvider = (props) => {
const { allowAll, allowConfigure, getActionParams } = useACLRoleContext();
const fieldSchema = useFieldSchema();
const isOwn = useRecordIsOwn();
if (allowAll || allowConfigure) {
return <>{props.children}</>;
}
const path = fieldSchema['x-acl-action'];
const skipScopeCheck = fieldSchema['x-acl-action-props']?.skipScopeCheck;
if (!path) {
return <>{props.children}</>;
}
const params = getActionParams(path, { isOwn, skipOwnCheck: skipScopeCheck === false ? false : true });
if (!params) {
return null;
}
return <ACLActionParamsContext.Provider value={params}>{props.children}</ACLActionParamsContext.Provider>;
};
export const ACLActionProvider = (props) => {
const { name } = useCollection();
const fieldSchema = useFieldSchema();
const isOwn = useRecordIsOwn();
const { allowAll, allowConfigure, getActionParams } = useACLRoleContext();
if (!name || allowAll || allowConfigure) {
return <>{props.children}</>;
}
const actionName = fieldSchema['x-action'];
const path = fieldSchema['x-acl-action'] || `${name}:${actionName}`;
const skipScopeCheck = fieldSchema['x-acl-action-props']?.skipScopeCheck;
const params = getActionParams(path, { skipOwnCheck: skipScopeCheck, isOwn });
if (!params) {
return null;
}
return <ACLActionParamsContext.Provider value={params}>{props.children}</ACLActionParamsContext.Provider>;
};
export const ACLCollectionFieldProvider = (props) => {
return <>{props.children}</>;
};
export const ACLMenuItemProvider = (props) => {
const { allowAll, allowConfigure, allowMenuItemIds = [] } = useACLRoleContext();
const fieldSchema = useFieldSchema();
if (allowAll || allowConfigure) {
return <>{props.children}</>;
}
if (!fieldSchema['x-uid']) {
return <>{props.children}</>;
}
if (allowMenuItemIds.includes(fieldSchema['x-uid'])) {
return <>{props.children}</>;
}
return null;
};
export default ACLProvider;