Skip to content

Commit

Permalink
Authority management (#508)
Browse files Browse the repository at this point in the history
* temp save

* rebase master

* fix same error

* Add a new user and different permissions

* fix eol-last

* add Secured decorator

* fix list redirect bug (#507)

* Mobile menu (#463)

* Increase the sliding menu

* Add a simple animation

* update mobile menu

* update

* update

* update

* rebase master

* recovery import/first

* fix error

* Fix some bugs
Change "ALL" to "NONE"
Remove the "!" Support
After landing successfully reload
Reset the format

* Pump your public logic

* add some test

* Add documents

* use default currentRole in Authorized/AuthorizedRoute

* rename props & change some authority setting

* A big change
😄 unified router and Secured parameters
😭 loginOut logout also changed to reload

* fix siderMeun bugs

* Decoupled SiderMenu

* Remove the handsome head of information

* Add a simple error

* rebase master
  • Loading branch information
ddcat1115 authored Jan 4, 2018
1 parent 74f0a0a commit cfa27b1
Show file tree
Hide file tree
Showing 21 changed files with 473 additions and 99 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@ npm-debug.log*

/coverage
.idea
yarn.lock
21 changes: 19 additions & 2 deletions .roadhogrc.mock.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,13 +70,30 @@ const proxy = {
'GET /api/profile/advanced': getProfileAdvancedData,
'POST /api/login/account': (req, res) => {
const { password, userName, type } = req.body;
if(password === '888888' && userName === 'admin'){
res.send({
status: 'ok',
type,
currentAuthority: 'admin'
});
return ;
}
if(password === '123456' && userName === 'user'){
res.send({
status: 'ok',
type,
currentAuthority: 'user'
});
return ;
}
res.send({
status: password === '888888' && userName === 'admin' ? 'ok' : 'error',
status: 'error',
type,
currentAuthority: 'guest'
});
},
'POST /api/register': (req, res) => {
res.send({ status: 'ok' });
res.send({ status: 'ok', currentAuthority: 'user' });
},
'GET /api/notices': getNotices,
'GET /api/500': (req, res) => {
Expand Down
7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,12 @@
"lint-staged": "lint-staged",
"lint-staged:js": "eslint --ext .js",
"test": "jest",
"test:comps": "jest ./src/components",
"test:all": "node ./tests/run-tests.js"
},
"dependencies": {
"@antv/data-set": "^0.8.0",
"antd": "^3.0.0",
"antd": "^3.1.0",
"babel-polyfill": "^6.26.0",
"babel-runtime": "^6.9.2",
"bizcharts": "^3.1.0-beta.4",
Expand All @@ -37,10 +38,10 @@
"prop-types": "^15.5.10",
"qs": "^6.5.0",
"rc-drawer-menu": "^0.5.0",
"react": "^16.0.0",
"react": "^16.2.0",
"react-container-query": "^0.9.1",
"react-document-title": "^2.0.3",
"react-dom": "^16.0.0",
"react-dom": "^16.2.0",
"react-fittext": "^1.0.0"
},
"devDependencies": {
Expand Down
9 changes: 7 additions & 2 deletions src/common/menu.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ const menuData = [{
path: 'step-form',
}, {
name: '高级表单',
authority: 'admin',
path: 'advanced-form',
}],
}, {
Expand Down Expand Up @@ -64,6 +65,7 @@ const menuData = [{
}, {
name: '高级详情页',
path: 'advanced',
authority: 'admin',
}],
}, {
name: '结果页',
Expand All @@ -83,6 +85,7 @@ const menuData = [{
children: [{
name: '403',
path: '403',
authority: 'user',
}, {
name: '404',
path: '404',
Expand All @@ -97,6 +100,7 @@ const menuData = [{
name: '账户',
icon: 'user',
path: 'user',
authority: 'guest',
children: [{
name: '登录',
path: 'login',
Expand All @@ -114,14 +118,15 @@ const menuData = [{
target: '_blank',
}];

function formatter(data, parentPath = '') {
function formatter(data, parentPath = '', parentAuthority) {
return data.map((item) => {
const result = {
...item,
path: `${parentPath}${item.path}`,
authority: item.authority || parentAuthority,
};
if (item.children) {
result.children = formatter(item.children, `${parentPath}${item.path}/`);
result.children = formatter(item.children, `${parentPath}${item.path}/`, item.authority);
}
return result;
});
Expand Down
21 changes: 12 additions & 9 deletions src/common/router.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,17 @@ function getFlatMenuData(menus) {
let keys = {};
menus.forEach((item) => {
if (item.children) {
keys[item.path] = item.name;
keys[item.path] = { ...item };
keys = { ...keys, ...getFlatMenuData(item.children) };
} else {
keys[item.path] = item.name;
keys[item.path] = { ...item };
}
});
return keys;
}

export const getRouterData = (app) => {
const routerData = {
const routerConfig = {
'/': {
component: dynamicWrapper(app, ['user', 'login'], () => import('../layouts/BasicLayout')),
},
Expand All @@ -45,6 +45,7 @@ export const getRouterData = (app) => {
component: dynamicWrapper(app, ['project', 'activities', 'chart'], () => import('../routes/Dashboard/Workplace')),
// hideInBreadcrumb: true,
// name: '工作台',
// authority: 'admin',
},
'/form/basic-form': {
component: dynamicWrapper(app, ['form'], () => import('../routes/Forms/BasicForm')),
Expand Down Expand Up @@ -127,12 +128,14 @@ export const getRouterData = (app) => {
};
// Get name from ./menu.js or just set it in the router data.
const menuData = getFlatMenuData(getMenuData());
const routerDataWithName = {};
Object.keys(routerData).forEach((item) => {
routerDataWithName[item] = {
...routerData[item],
name: routerData[item].name || menuData[item.replace(/^\//, '')],
const routerData = {};
Object.keys(routerConfig).forEach((item) => {
const menuItem = menuData[item.replace(/^\//, '')] || {};
routerData[item] = {
...routerConfig[item],
name: routerConfig[item].name || menuItem.name,
authority: routerConfig[item].authority || menuItem.authority,
};
});
return routerDataWithName;
return routerData;
};
16 changes: 16 additions & 0 deletions src/components/Authorized/Authorized.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import React from 'react';
import CheckPermissions from './CheckPermissions';

class Authorized extends React.Component {
render() {
const { children, authority, noMatch = null } = this.props;
const childrenRender = typeof children === 'undefined' ? null : children;
return CheckPermissions(
authority,
childrenRender,
noMatch
);
}
}

export default Authorized;
23 changes: 23 additions & 0 deletions src/components/Authorized/AuthorizedRoute.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import React from 'react';
import { Route, Redirect } from 'dva/router';
import Authorized from './Authorized';

class AuthorizedRoute extends React.Component {
render() {
const { component: Component, render, authority,
redirectPath, ...rest } = this.props;
return (
<Authorized
authority={authority}
noMatch={<Route {...rest} render={() => <Redirect to={{ pathname: redirectPath }} />} />}
>
<Route
{...rest}
render={props => (Component ? <Component {...props} /> : render(props))}
/>
</Authorized>
);
}
}

export default AuthorizedRoute;
62 changes: 62 additions & 0 deletions src/components/Authorized/CheckPermissions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import React from 'react';
import PromiseRender from './PromiseRender';
import { CURRENT } from './index';
/**
* 通用权限检查方法
* Common check permissions method
* @param { 权限判定 Permission judgment type string |array | Promise | Function } authority
* @param { 你的权限 Your permission description type:string} currentAuthority
* @param { 通过的组件 Passing components } target
* @param { 未通过的组件 no pass components } Exception
*/
const checkPermissions = (authority, currentAuthority, target, Exception) => {
// 没有判定权限.默认查看所有
// Retirement authority, return target;
if (!authority) {
return target;
}
// 数组处理
if (authority.constructor.name === 'Array') {
if (authority.includes(currentAuthority)) {
return target;
}
return Exception;
}

// string 处理
if (authority.constructor.name === 'String') {
if (authority === currentAuthority) {
return target;
}
return Exception;
}

// Promise 处理
if (authority.constructor.name === 'Promise') {
return () => (
<PromiseRender ok={target} error={Exception} promise={authority} />
);
}

// Function 处理
if (authority.constructor.name === 'Function') {
try {
const bool = authority();
if (bool) {
return target;
}
return Exception;
} catch (error) {
throw error;
}
}
throw new Error('unsupported parameters');
};

export { checkPermissions };

const check = (authority, target, Exception) => {
return checkPermissions(authority, CURRENT, target, Exception);
};

export default check;
42 changes: 42 additions & 0 deletions src/components/Authorized/CheckPermissions.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { checkPermissions } from './CheckPermissions.js';

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('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');
});
});
39 changes: 39 additions & 0 deletions src/components/Authorized/PromiseRender.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import React from 'react';
import { Spin } from 'antd';

export default class PromiseRender extends React.PureComponent {
state = {
component: false,
};
async componentDidMount() {
this.props.promise
.then(() => {
this.setState({
component: this.props.ok,
});
})
.catch(() => {
this.setState({
component: this.props.error,
});
});
}
render() {
const C = this.state.component;
return C ? (
<C {...this.props} />
) : (
<div
style={{
width: '100%',
height: '100%',
margin: 'auto',
paddingTop: 50,
textAlign: 'center',
}}
>
<Spin size="large" />
</div>
);
}
}
Loading

0 comments on commit cfa27b1

Please sign in to comment.