12 changed files with 420 additions and 26 deletions
@ -0,0 +1,29 @@ |
|||
import CheckPermissions from './CheckPermissions'; |
|||
import { IAuthorityType } from './CheckPermissions'; |
|||
import Secured from './Secured'; |
|||
import check from './CheckPermissions'; |
|||
import AuthorizedRoute from './AuthorizedRoute'; |
|||
import React from 'react'; |
|||
|
|||
interface IAuthorizedProps { |
|||
authority: IAuthorityType; |
|||
noMatch?: React.ReactNode; |
|||
} |
|||
|
|||
type IAuthorizedType = React.FunctionComponent<IAuthorizedProps> & { |
|||
Secured: typeof Secured; |
|||
check: typeof check; |
|||
AuthorizedRoute: typeof AuthorizedRoute; |
|||
}; |
|||
|
|||
const Authorized: React.FunctionComponent<IAuthorizedProps> = ({ |
|||
children, |
|||
authority, |
|||
noMatch = null, |
|||
}) => { |
|||
const childrenRender: React.ReactNode = typeof children === 'undefined' ? null : children; |
|||
const dom = CheckPermissions(authority, childrenRender, noMatch); |
|||
return <>{dom}</>; |
|||
}; |
|||
|
|||
export default Authorized as IAuthorizedType; |
|||
@ -0,0 +1,32 @@ |
|||
import React from 'react'; |
|||
import { Route, Redirect } from 'umi'; |
|||
import Authorized from './Authorized'; |
|||
import { IAuthorityType } from './CheckPermissions'; |
|||
|
|||
interface IAuthorizedRoutePops { |
|||
currentAuthority: string; |
|||
component: React.ComponentClass<any, any>; |
|||
render: () => React.ReactNode; |
|||
redirectPath: string; |
|||
authority: IAuthorityType; |
|||
} |
|||
|
|||
const AuthorizedRoute: React.SFC<IAuthorizedRoutePops> = ({ |
|||
component: Component, |
|||
render, |
|||
authority, |
|||
redirectPath, |
|||
...rest |
|||
}) => ( |
|||
<Authorized |
|||
authority={authority} |
|||
noMatch={<Route {...rest} render={() => <Redirect to={{ pathname: redirectPath }} />} />} |
|||
> |
|||
<Route |
|||
{...rest} |
|||
render={(props: any) => (Component ? <Component {...props} /> : render(props))} |
|||
/> |
|||
</Authorized> |
|||
); |
|||
|
|||
export default AuthorizedRoute; |
|||
@ -0,0 +1,55 @@ |
|||
import { checkPermissions } from './CheckPermissions'; |
|||
|
|||
const target = 'ok'; |
|||
const error = 'error'; |
|||
|
|||
describe('test CheckPermissions', () => { |
|||
it('Correct string permission authentication', () => { |
|||
expect(checkPermissions('user', 'user', target, error)).toEqual('ok'); |
|||
}); |
|||
it('Correct string permission authentication', () => { |
|||
expect(checkPermissions('user', 'NULL', target, error)).toEqual('error'); |
|||
}); |
|||
it('authority is undefined , return ok', () => { |
|||
expect(checkPermissions(null, 'NULL', target, error)).toEqual('ok'); |
|||
}); |
|||
it('currentAuthority is undefined , return error', () => { |
|||
expect(checkPermissions('admin', null, target, error)).toEqual('error'); |
|||
}); |
|||
it('Wrong string permission authentication', () => { |
|||
expect(checkPermissions('admin', 'user', target, error)).toEqual('error'); |
|||
}); |
|||
it('Correct Array permission authentication', () => { |
|||
expect(checkPermissions(['user', 'admin'], 'user', target, error)).toEqual('ok'); |
|||
}); |
|||
it('Wrong Array permission authentication,currentAuthority error', () => { |
|||
expect(checkPermissions(['user', 'admin'], 'user,admin', target, error)).toEqual('error'); |
|||
}); |
|||
it('Wrong Array permission authentication', () => { |
|||
expect(checkPermissions(['user', 'admin'], 'guest', target, error)).toEqual('error'); |
|||
}); |
|||
it('Wrong Function permission authentication', () => { |
|||
expect(checkPermissions(() => false, 'guest', target, error)).toEqual('error'); |
|||
}); |
|||
it('Correct Function permission authentication', () => { |
|||
expect(checkPermissions(() => true, 'guest', target, error)).toEqual('ok'); |
|||
}); |
|||
it('authority is string, currentAuthority is array, return ok', () => { |
|||
expect(checkPermissions('user', ['user'], target, error)).toEqual('ok'); |
|||
}); |
|||
it('authority is string, currentAuthority is array, return ok', () => { |
|||
expect(checkPermissions('user', ['user', 'admin'], target, error)).toEqual('ok'); |
|||
}); |
|||
it('authority is array, currentAuthority is array, return ok', () => { |
|||
expect(checkPermissions(['user', 'admin'], ['user', 'admin'], target, error)).toEqual('ok'); |
|||
}); |
|||
it('Wrong Function permission authentication', () => { |
|||
expect(checkPermissions(() => false, ['user'], target, error)).toEqual('error'); |
|||
}); |
|||
it('Correct Function permission authentication', () => { |
|||
expect(checkPermissions(() => true, ['user'], target, error)).toEqual('ok'); |
|||
}); |
|||
it('authority is undefined , return ok', () => { |
|||
expect(checkPermissions(null, ['user'], target, error)).toEqual('ok'); |
|||
}); |
|||
}); |
|||
@ -0,0 +1,84 @@ |
|||
import React from 'react'; |
|||
// eslint-disable-next-line import/no-cycle
|
|||
import PromiseRender from './PromiseRender'; |
|||
import { CURRENT } from './renderAuthorize'; |
|||
|
|||
export type IAuthorityType = |
|||
| string |
|||
| string[] |
|||
| Promise<any> |
|||
| ((currentAuthority: string | string[]) => IAuthorityType); |
|||
|
|||
/** |
|||
* 通用权限检查方法 |
|||
* Common check permissions method |
|||
* @param { 权限判定 | Permission judgment } authority |
|||
* @param { 你的权限 | Your permission description } currentAuthority |
|||
* @param { 通过的组件 | Passing components } target |
|||
* @param { 未通过的组件 | no pass components } Exception |
|||
*/ |
|||
const checkPermissions = ( |
|||
authority: IAuthorityType, |
|||
currentAuthority: string | string[], |
|||
target: React.ComponentClass<any, any> | React.ReactNode, |
|||
Exception: React.ReactNode, |
|||
): React.ReactNode => { |
|||
// 没有判定权限.默认查看所有
|
|||
// Retirement authority, return target;
|
|||
if (!authority) { |
|||
return target; |
|||
} |
|||
// 数组处理
|
|||
if (Array.isArray(authority)) { |
|||
if (Array.isArray(currentAuthority)) { |
|||
if (currentAuthority.some(item => authority.includes(item))) { |
|||
return target; |
|||
} |
|||
} else if (authority.includes(currentAuthority)) { |
|||
return target; |
|||
} |
|||
return Exception; |
|||
} |
|||
// string 处理
|
|||
if (typeof authority === 'string') { |
|||
if (Array.isArray(currentAuthority)) { |
|||
if (currentAuthority.some(item => authority === item)) { |
|||
return target; |
|||
} |
|||
} else if (authority === currentAuthority) { |
|||
return target; |
|||
} |
|||
return Exception; |
|||
} |
|||
// Promise 处理
|
|||
if (authority instanceof Promise) { |
|||
return <PromiseRender ok={target} error={Exception} promise={authority} />; |
|||
} |
|||
// Function 处理
|
|||
if (typeof authority === 'function') { |
|||
try { |
|||
const bool = authority(currentAuthority); |
|||
// 函数执行后返回值是 Promise
|
|||
if (bool instanceof Promise) { |
|||
return <PromiseRender ok={target} error={Exception} promise={bool} />; |
|||
} |
|||
if (bool) { |
|||
return target; |
|||
} |
|||
return Exception; |
|||
} catch (error) { |
|||
throw error; |
|||
} |
|||
} |
|||
throw new Error('unsupported parameters'); |
|||
}; |
|||
|
|||
export { checkPermissions }; |
|||
|
|||
const check = ( |
|||
authority: IAuthorityType, |
|||
target: React.ComponentClass<any, any> | React.ReactNode, |
|||
Exception: React.ReactNode, |
|||
): React.ReactNode => checkPermissions(authority, CURRENT, target, Exception); |
|||
|
|||
export default check; |
|||
@ -0,0 +1,91 @@ |
|||
import { Spin } from 'antd'; |
|||
import isEqual from 'lodash/isEqual'; |
|||
import React from 'react'; |
|||
// eslint-disable-next-line import/no-cycle
|
|||
import { isComponentClass } from './Secured'; |
|||
|
|||
interface IPromiseRenderProps { |
|||
ok: React.ReactNode; |
|||
error: React.ReactNode; |
|||
promise: Promise<any>; |
|||
} |
|||
|
|||
interface IPromiseRenderState { |
|||
component: React.ComponentClass<any, any> | React.FunctionComponent<any>; |
|||
} |
|||
|
|||
export default class PromiseRender extends React.Component< |
|||
IPromiseRenderProps, |
|||
IPromiseRenderState |
|||
> { |
|||
state: IPromiseRenderState = { |
|||
component: () => null, |
|||
}; |
|||
|
|||
componentDidMount() { |
|||
this.setRenderComponent(this.props); |
|||
} |
|||
|
|||
shouldComponentUpdate = (nextProps: IPromiseRenderProps, nextState: IPromiseRenderState) => { |
|||
const { component } = this.state; |
|||
if (!isEqual(nextProps, this.props)) { |
|||
this.setRenderComponent(nextProps); |
|||
} |
|||
if (nextState.component !== component) return true; |
|||
return false; |
|||
}; |
|||
|
|||
// set render Component : ok or error
|
|||
setRenderComponent(props: IPromiseRenderProps) { |
|||
const ok = this.checkIsInstantiation(props.ok); |
|||
const error = this.checkIsInstantiation(props.error); |
|||
props.promise |
|||
.then(() => { |
|||
this.setState({ |
|||
component: ok, |
|||
}); |
|||
}) |
|||
.catch(() => { |
|||
this.setState({ |
|||
component: error, |
|||
}); |
|||
}); |
|||
} |
|||
|
|||
// Determine whether the incoming component has been instantiated
|
|||
// AuthorizedRoute is already instantiated
|
|||
// Authorized render is already instantiated, children is no instantiated
|
|||
// Secured is not instantiated
|
|||
checkIsInstantiation = ( |
|||
target: React.ReactNode | React.ComponentClass<any, any>, |
|||
): React.FunctionComponent<any> => { |
|||
if (isComponentClass(target)) { |
|||
const Target = target as React.ComponentClass<any, any>; |
|||
return (props: any) => <Target {...props} />; |
|||
} |
|||
if (React.isValidElement(target)) { |
|||
return (props: any) => React.cloneElement(target, props); |
|||
} |
|||
return () => target as (React.ReactNode & null); |
|||
}; |
|||
|
|||
render() { |
|||
const { component: Component } = this.state; |
|||
const { ok, error, promise, ...rest } = this.props; |
|||
return Component ? ( |
|||
<Component {...rest} /> |
|||
) : ( |
|||
<div |
|||
style={{ |
|||
width: '100%', |
|||
height: '100%', |
|||
margin: 'auto', |
|||
paddingTop: 50, |
|||
textAlign: 'center', |
|||
}} |
|||
> |
|||
<Spin size="large" /> |
|||
</div> |
|||
); |
|||
} |
|||
} |
|||
@ -0,0 +1,68 @@ |
|||
import React from 'react'; |
|||
import CheckPermissions from './CheckPermissions'; |
|||
|
|||
/** |
|||
* 默认不能访问任何页面 |
|||
* default is "NULL" |
|||
*/ |
|||
const Exception403 = () => 403; |
|||
|
|||
export const isComponentClass = ( |
|||
component: React.ComponentClass<any, any> | React.ReactNode, |
|||
): boolean => { |
|||
if (!component) return false; |
|||
const proto = Object.getPrototypeOf(component); |
|||
if (proto === React.Component || proto === Function.prototype) return true; |
|||
return isComponentClass(proto); |
|||
}; |
|||
|
|||
// Determine whether the incoming component has been instantiated
|
|||
// AuthorizedRoute is already instantiated
|
|||
// Authorized render is already instantiated, children is no instantiated
|
|||
// Secured is not instantiated
|
|||
const checkIsInstantiation = (target: React.ComponentClass<any, any> | React.ReactNode) => { |
|||
if (isComponentClass(target)) { |
|||
const Target = target as React.ComponentClass<any, any>; |
|||
return (props: any) => <Target {...props} />; |
|||
} |
|||
if (React.isValidElement(target)) { |
|||
return (props: any) => React.cloneElement(target, props); |
|||
} |
|||
return () => target; |
|||
}; |
|||
|
|||
/** |
|||
* 用于判断是否拥有权限访问此 view 权限 |
|||
* authority 支持传入 string, () => boolean | Promise |
|||
* e.g. 'user' 只有 user 用户能访问 |
|||
* e.g. 'user,admin' user 和 admin 都能访问 |
|||
* e.g. ()=>boolean 返回true能访问,返回false不能访问 |
|||
* e.g. Promise then 能访问 catch不能访问 |
|||
* e.g. authority support incoming string, () => boolean | Promise |
|||
* e.g. 'user' only user user can access |
|||
* e.g. 'user, admin' user and admin can access |
|||
* e.g. () => boolean true to be able to visit, return false can not be accessed |
|||
* e.g. Promise then can not access the visit to catch |
|||
* @param {string | function | Promise} authority |
|||
* @param {ReactNode} error 非必需参数 |
|||
*/ |
|||
const authorize = (authority: string, error?: React.ReactNode) => { |
|||
/** |
|||
* conversion into a class |
|||
* 防止传入字符串时找不到staticContext造成报错 |
|||
* String parameters can cause staticContext not found error |
|||
*/ |
|||
let classError: boolean | React.FunctionComponent<any> = false; |
|||
if (error) { |
|||
classError = (() => error) as React.FunctionComponent<any>; |
|||
} |
|||
if (!authority) { |
|||
throw new Error('authority is required'); |
|||
} |
|||
return function decideAuthority(target: React.ComponentClass<any, any> | React.ReactNode) { |
|||
const component = CheckPermissions(authority, target, classError || Exception403); |
|||
return checkIsInstantiation(component); |
|||
}; |
|||
}; |
|||
|
|||
export default authorize; |
|||
@ -0,0 +1,12 @@ |
|||
import Authorized from './Authorized'; |
|||
import AuthorizedRoute from './AuthorizedRoute'; |
|||
import Secured from './Secured'; |
|||
import check from './CheckPermissions'; |
|||
import renderAuthorize from './renderAuthorize'; |
|||
|
|||
Authorized.Secured = Secured; |
|||
Authorized.AuthorizedRoute = AuthorizedRoute; |
|||
Authorized.check = check; |
|||
|
|||
const RenderAuthorize = renderAuthorize(Authorized); |
|||
export default RenderAuthorize; |
|||
@ -0,0 +1,27 @@ |
|||
/* eslint-disable import/no-mutable-exports */ |
|||
let CURRENT: string | string[] = 'NULL'; |
|||
/** |
|||
* use authority or getAuthority |
|||
* @param {string|()=>String} currentAuthority |
|||
*/ |
|||
const renderAuthorize = (Authorized: any) => ( |
|||
currentAuthority: string | string[] | (() => typeof CURRENT), |
|||
) => { |
|||
if (currentAuthority) { |
|||
if (typeof currentAuthority === 'function') { |
|||
CURRENT = currentAuthority(); |
|||
} |
|||
if ( |
|||
Object.prototype.toString.call(currentAuthority) === '[object String]' || |
|||
Array.isArray(currentAuthority) |
|||
) { |
|||
CURRENT = currentAuthority as string[]; |
|||
} |
|||
} else { |
|||
CURRENT = 'NULL'; |
|||
} |
|||
return Authorized; |
|||
}; |
|||
|
|||
export { CURRENT }; |
|||
export default (Authorized: any) => renderAuthorize(Authorized); |
|||
Loading…
Reference in new issue