Skip to content

Commit 1a2f044

Browse files
committed
feat: unified checkPermission method with support of "allowRoles"/"denyRoles"
This method has been migrated from TC Project Service and adopted. Ref issues #2841, #3300
1 parent 73b6d9c commit 1a2f044

File tree

2 files changed

+108
-20
lines changed

2 files changed

+108
-20
lines changed

src/helpers/permissions.js

Lines changed: 107 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,116 @@
11
import store from '../config/store'
22
import _ from 'lodash'
33

4-
export const checkPermission = (permission, project, entity) => {
5-
const {projectRoles, topcoderRoles, handler} = permission
6-
const currentUser = _.get(store.getState(), 'loadUser.user', {})
7-
const roles = currentUser.roles || []
8-
9-
if(project && projectRoles){
10-
const currentProjectMember = _.find(project.members, m => m.userId===currentUser.userId)
11-
if (currentProjectMember){
12-
const currentUserRole = currentProjectMember.role
13-
if (projectRoles.includes(currentUserRole)){
14-
return true
15-
}
16-
}
4+
/**
5+
* Check if user has permission.
6+
* (The main permission method which should be used).
7+
*
8+
* This method uses permission defined in `permission`
9+
* and checks that the logged-in user from Redux Store matches it.
10+
*
11+
* `permission` may be defined in two ways:
12+
* - **Full** way with defined `allowRule` and optional `denyRule`, example:
13+
* ```js
14+
* {
15+
* allowRule: {
16+
* projectRoles: [],
17+
* topcoderRoles: []
18+
* },
19+
* denyRule: {
20+
* projectRoles: [],
21+
* topcoderRoles: []
22+
* }
23+
* }
24+
* ```
25+
* If user matches `denyRule` then the access would be dined even if matches `allowRule`.
26+
* - **Simplified** way may be used if we only want to define `allowRule`.
27+
* We can skip the `allowRule` property and define `allowRule` directly inside `permission` object, example:
28+
* ```js
29+
* {
30+
* projectRoles: [],
31+
* topcoderRoles: []
32+
* }
33+
* ```
34+
* This **simplified** permission is equal to a **full** permission:
35+
* ```js
36+
* {
37+
* allowRule: {
38+
* projectRoles: [],
39+
* topcoderRoles: []
40+
* }
41+
* }
42+
* ```
43+
*
44+
* If we define any rule with `projectRoles` list, we also should provide `project`
45+
* with the list of project members in `project.members`.
46+
*
47+
* @param {Object} permissionRule permission rule
48+
* @param {Array<String>} permissionRule.projectRoles the list of project roles of the user
49+
* @param {Array<String>} permissionRule.topcoderRoles the list of Topcoder roles of the user
50+
* @param {Object} [project] project object - required to check `topcoderRoles`
51+
* @param {Array} project.members list of project members - required to check `topcoderRoles`
52+
*
53+
* @returns {Boolean} true, if has permission
54+
*/
55+
export const checkPermission = (permission, project) => {
56+
const user = _.get(store.getState(), 'loadUser.user', {})
57+
58+
const allowRule = permission.allowRule ? permission.allowRule : permission
59+
const denyRule = permission.denyRule ? permission.denyRule : null
60+
61+
const allow = matchPermissionRule(allowRule, user, project)
62+
const deny = matchPermissionRule(denyRule, user, project)
63+
64+
return allow && !deny
65+
}
66+
67+
/**
68+
* Check if user match the permission rule.
69+
* (Helper method, most likely wouldn't be used directly).
70+
*
71+
* This method uses permission rule defined in `permissionRule`
72+
* and checks that the `user` matches it.
73+
*
74+
* If we define a rule with `projectRoles` list, we also should provide `projectMembers`
75+
* - the list of project members.
76+
*
77+
* @param {Object} permissionRule permission rule
78+
* @param {Array<String>} permissionRule.projectRoles the list of project roles of the user
79+
* @param {Array<String>} permissionRule.topcoderRoles the list of Topcoder roles of the user
80+
* @param {Object} user user for whom we check permissions
81+
* @param {Object} user.roles list of user roles
82+
* @param {Object} [project] project object - required to check `topcoderRoles`
83+
* @param {Array} project.members list of project members - required to check `topcoderRoles`
84+
*
85+
* @returns {Boolean} true, if has permission
86+
*/
87+
const matchPermissionRule = (permissionRule, user, project) => {
88+
let hasProjectRole = false
89+
let hasTopcoderRole = false
90+
91+
// if no rule defined, no access by default
92+
if (!permissionRule) {
93+
return false
1794
}
1895

19-
if(topcoderRoles && roles.some(role => topcoderRoles.indexOf(role) !== -1)) {
20-
return true
96+
// check Project Roles
97+
if (permissionRule.projectRoles
98+
&& permissionRule.projectRoles.length > 0
99+
&& project
100+
&& project.members
101+
) {
102+
const userId = !_.isNumber(user.userId) ? parseInt(user.userId, 10) : user.userId
103+
const member = _.find(project.members, { userId })
104+
hasProjectRole = member && _.includes(permissionRule.projectRoles, member.role)
21105
}
22106

23-
if(handler && handler(currentUser, entity, project)) {
24-
return true
107+
// check Topcoder Roles
108+
if (permissionRule.topcoderRoles && permissionRule.topcoderRoles.length > 0) {
109+
hasTopcoderRole = _.intersection(
110+
_.get(user, 'roles', []).map(role => role.toLowerCase()),
111+
permissionRule.topcoderRoles.map(role => role.toLowerCase())
112+
).length > 0
25113
}
26114

27-
return false
28-
}
115+
return hasProjectRole || hasTopcoderRole
116+
}

src/projects/detail/containers/DashboardContainer.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,7 @@ class DashboardContainer extends React.Component {
272272
) : (
273273
<ProjectPlanEmpty isManageUser={isManageUser} />
274274
)}
275-
{isProjectLive && checkPermission(PERMISSIONS.EDIT_PROJECT_PLAN, project, phases) && !isLoadingPhases && (<div styleName="add-button-container">
275+
{isProjectLive && checkPermission(PERMISSIONS.EDIT_PROJECT_PLAN, project) && !isLoadingPhases && (<div styleName="add-button-container">
276276
<Link to={`/projects/${project.id}/add-phase`} className="tc-btn tc-btn-primary tc-btn-sm action-btn">Add New Phase</Link>
277277
</div>)}
278278
</TwoColsLayout.Content>

0 commit comments

Comments
 (0)