Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ workflows:
- build-dev
filters:
branches:
only: ['feature/faqs', 'feature/project_september']
only: ['feature/unified-permissions']

- deployProd:
context : org-global
Expand Down
13 changes: 8 additions & 5 deletions docs/permissions-guide/permissions-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,23 @@ Let's say you would like to add a new place in code where you want to check user

2. After you add a new permission, regenerate [permissions list](https://htmlpreview.github.io/?https://github.com/appirio-tech/connect-app/blob/dev/docs/permissions.html) by running `npm run generate:doc:permissions`.

3. To check if user has permission in code use method `hasPermission(permission)`.
3. To check if logged-in user has permission in code use method `hasPermission(permission)`.

Example:<br>

```js
import PERMISSIONS from 'config/permissions'
import { PERMISSIONS } from 'config/permissions'
import { hasPermission } from 'helpers/permissions'

if (hasPermission(PERMISSIONS.MANAGE_PROJECT_PLAN)) {
...
}
```

- Note, optionally, you may pass the `project` object like this `hasPermission(permission, project)`. But you don't have to as `hasPermission` gets `project` object from the Redux Store (`projectState.project`) automatically. Only in case if you want to check user permission to another project which is not loaded into the Redux Store then you may pass `project` explicitly.
4. If you would like to check permissions for other user (not the current user) or for other project (not the current project) you may pass the second argument `entities: { project?: object, user?: object }`:
- `hasPermission(permission, { project })` - check permissions for another project
- `hasPermission(permission, { user })` - check permissions for another user
- `hasPermission(permission, { project, user })` - check permissions for another project and user

## Roles

Expand All @@ -49,4 +52,4 @@ By default every user has one role `Topcoder User`, generally this means that su

When user joins some project and become a member of the project, such a user has one **Project Role** inside that project. One user may have different **Project Role** in different projects. See [the list of all Project Roles](https://github.com/appirio-tech/connect-app/blob/dev/src/config/constants.js#L638-L647) which we use in Connect App.

<img src="./images/project-roles.png" width="720">
<img src="./images/project-roles.png" width="720">
1,209 changes: 983 additions & 226 deletions docs/permissions.html

Large diffs are not rendered by default.

20 changes: 13 additions & 7 deletions scripts/permissions-doc/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import _ from 'lodash'
import fs from 'fs'
import path from 'path'
import handlebars from 'handlebars'
import PERMISSIONS from '../../src/config/permissions'
import { PERMISSIONS } from '../../src/config/permissions'
import {
PROJECT_ROLE_CUSTOMER,
PROJECT_ROLE_COPILOT,
Expand Down Expand Up @@ -42,6 +42,8 @@ import {
const docTemplatePath = path.resolve(__dirname, './template.hbs')
const outputDocPath = path.resolve(__dirname, '../../docs/permissions.html')

handlebars.registerHelper('istrue', value => value === true)

/**
* All Project Roles
*/
Expand Down Expand Up @@ -121,8 +123,8 @@ function normalizePermission(permission) {

if (!normalizedPermission.allowRule) {
normalizedPermission = {
_meta: permission._meta,
allowRule: _.omit(permission, '_meta')
meta: permission.meta,
allowRule: _.omit(permission, 'meta')
}
}

Expand All @@ -143,13 +145,17 @@ const renderDocument = handlebars.compile(templateStr)
const permissionKeys = _.keys(PERMISSIONS)
const permissions = permissionKeys.map((key) => ({
...PERMISSIONS[key],
_meta: {
...PERMISSIONS[key]._meta,
meta: {
...PERMISSIONS[key].meta,
key,
}
}))
const groupsObj = _.groupBy(permissions, '_meta.group')
const groups = _.toPairs(groupsObj).map(([title, permissions]) => ({ title, permissions }))
const groupsObj = _.groupBy(permissions, 'meta.group')
const groups = _.toPairs(groupsObj).map(([title, permissions]) => ({
title,
permissions,
anchor: `section-${title.toLowerCase().replace(/\s+/g, '-')}`,
}))

groups.forEach((group) => {
group.permissions = group.permissions.map(normalizePermission)
Expand Down
98 changes: 79 additions & 19 deletions scripts/permissions-doc/template.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,53 @@
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
<title>Permissions</title>
<style>
body {
padding-bottom: 100px;
}

.small-text {
font-size: 62.5%;
line-height: 120%;
}

.permission-variable {
line-height: 80%;
margin: 5px 0;
word-break: break-word;
}

.permission-title {
line-height: 120%;
margin: 5px 0;
}

.anchor-container .anchor {
float: left;
opacity: 1;
line-height: 1;
margin-left: -20px;
}

.anchor-container .anchor::before {
visibility: hidden;
font-size: 16px;
content: '🔗';
display: inline-block;
width: 20px;
}

.anchor-container:hover .anchor {
text-decoration: none;
}

.anchor-container:hover .anchor::before {
visibility: visible;
}

.badge-crossed {
opacity: 0.4;
text-decoration: line-through;
}
</style>
</head>
<body>
Expand All @@ -21,42 +64,59 @@
<p>Legend:</p>
<ul>
<li><span class="badge badge-primary">allowed Project Role</span> - users with such a <strong>Project Role</strong> are allowed to perform the action</li>
<li><span class="badge badge-warning">denied Project Role</span> - users with such a <strong>Project Role</strong> are denied to perform the action even they have some other allow roles</li>
<li><span class="badge badge-primary badge-crossed">denied Project Role</span> - users with such a <strong>Project Role</strong> are denied to perform the action even they have some other allow roles</li>
<li><span class="badge badge-success">allowed Topcoder Role</span> - users with such a <strong>Topcoder Role</strong> are allowed to perform the action</li>
<li><span class="badge badge-danger">denied Topcoder Role</span> - users with such a <strong>Topcoder Role</strong> are denied to perform the action even they have some other allow roles</li>
<li><span class="badge badge-success badge-crossed">denied Topcoder Role</span> - users with such a <strong>Topcoder Role</strong> are denied to perform the action even they have some other allow roles</li>
</ul>
</div>

{{#each groups}}
<div class="row">
<div class="col pt-5 pb-2">
<h2>{{title}}</h2>
<h2 class="anchor-container">
<a href="#{{anchor}}" name="{{anchor}}" class="anchor"></a>{{title}}
</h2>
</div>
</div>
{{#each permissions}}
<div class="row border-top">
<div class="col py-2">
{{_meta.title}}
<div><small><code>{{_meta.key}}</code></small></div>
<div class="text-black-50 small-text">{{_meta.description}}</div>
<div class="permission-title anchor-container">
<a href="#{{meta.key}}" name="{{meta.key}}" class="anchor"></a>{{meta.title}}
</div>
<div class="permission-variable"><small><code>{{meta.key}}</code></small></div>
<div class="text-black-50 small-text">{{meta.description}}</div>
</div>
<div class="col-9 py-2">
{{#each allowRule.projectRoles}}
<span class="badge badge-primary" title="Allowed Project Role">{{this}}</span>
{{/each}}
{{#each denyRule.projectRoles}}
<span class="badge badge-warning" title="Denied Project Role">{{this}}</span>
{{/each}}
{{#each allowRule.topcoderRoles}}
<span class="badge badge-success" title="Allowed Topcoder Role">{{this}}</span>
{{/each}}
{{#each denyRule.topcoderRoles}}
<span class="badge badge-danger" title="Denied Topcoder Role">{{this}}</span>
{{/each}}
<div>
{{#if (istrue allowRule.projectRoles)}}
<span class="badge badge-primary" title="Allowed">Any Project Member</span>
{{else}}
{{#each allowRule.projectRoles}}
<span class="badge badge-primary" title="Allowed Project Role">{{this}}</span>
{{/each}}
{{/if}}
{{#each denyRule.projectRoles}}
<span class="badge badge-primary badge-crossed" title="Denied Project Role">{{this}}</span>
{{/each}}
</div>

<div>
{{#if (istrue allowRule.topcoderRoles)}}
<span class="badge badge-success" title="Allowed">Any Logged-in User</span>
{{else}}
{{#each allowRule.topcoderRoles}}
<span class="badge badge-success" title="Allowed Topcoder Role">{{this}}</span>
{{/each}}
{{/if}}
{{#each denyRule.topcoderRoles}}
<span class="badge badge-success badge-crossed" title="Denied Topcoder Role">{{this}}</span>
{{/each}}
</div>
</div>
</div>
{{/each}}
{{/each}}
</div>
</body>
</html>
</html>
2 changes: 1 addition & 1 deletion src/components/AssetsLibrary/FilesGridView.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import {
PROJECT_FEED_TYPE_MESSAGES
} from '../../config/constants'
import { hasPermission } from '../../helpers/permissions'
import PERMISSIONS from '../../config/permissions'
import { PERMISSIONS } from '../../config/permissions'

let selectedLink
let clearing = false
Expand Down
2 changes: 1 addition & 1 deletion src/components/AssetsLibrary/LinksGridView.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import {
} from '../../config/constants'
import FilterColHeader from './FilterColHeader'
import { hasPermission } from '../../helpers/permissions'
import PERMISSIONS from '../../config/permissions'
import { PERMISSIONS } from '../../config/permissions'

let selectedLink
let clearing = false
Expand Down
6 changes: 4 additions & 2 deletions src/components/Feed/Feed.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import RichTextArea from '../RichTextArea/RichTextArea'
import NotificationsReader from '../../components/NotificationsReader'
import IconButton from '../IconButton/IconButton'

import { EVENT_TYPE, PROJECT_FEED_TYPE_MESSAGES, PROJECT_ROLE_CUSTOMER } from '../../config/constants'
import { EVENT_TYPE, PROJECT_FEED_TYPE_MESSAGES } from '../../config/constants'

import XMarkIcon from '../../assets/icons/x-mark.svg'
import FullscreenIcon from '../../assets/icons/ui-fullscreen.svg'
Expand All @@ -20,6 +20,8 @@ import CloseIcon from 'appirio-tech-react-components/components/Icons/CloseIcon'
import MoreIcon from '../../assets/icons/more.svg'

import './Feed.scss'
import { hasPermission } from '../../helpers/permissions'
import { PERMISSIONS } from '../../config/permissions'

class Feed extends React.Component {
constructor(props) {
Expand Down Expand Up @@ -95,7 +97,7 @@ class Feed extends React.Component {
}

filterProjectMembers(projectMembers, isPrivate) {
return isPrivate ? _.pickBy(projectMembers, pm => pm.role !== PROJECT_ROLE_CUSTOMER) : projectMembers
return isPrivate ? _.filter(projectMembers, member => hasPermission(PERMISSIONS.ACCESS_PRIVATE_POST, { user: member })) : projectMembers
}

render() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ const IncompleteUserProfileDialog = ({
IncompleteUserProfileDialog.propTypes = {
profileSettings: PT.object.isRequired,
saveProfileSettings: PT.func.isRequired,
isTopcoderUser: PT.bool.isRequired,
user: PT.object.isRequired,
onCloseDialog: PT.func.isRequired,
title: PT.string.isRequired,
Expand Down
11 changes: 5 additions & 6 deletions src/components/IncompleteUserProfile/IncompleteUserProfile.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,19 @@
*/
import React from 'react'
import PT from 'prop-types'
import { PROFILE_FIELDS_CONFIG } from '../../config/constants'
import ProfileSettingsForm from '../../routes/settings/routes/profile/components/ProfileSettingsForm'
import { getDefaultTopcoderRole } from '../../helpers/permissions'
import { getDefaultTopcoderRole, hasPermission } from '../../helpers/permissions'
import { timezones } from 'appirio-tech-react-components/constants/timezones'
import { getUserProfileFieldsConfig } from '../../helpers/tcHelpers'
import { PERMISSIONS } from '../../config/permissions'

const IncompleteUserProfile = ({
profileSettings,
saveProfileSettings,
isTopcoderUser,
user,
...restProps
}) => {
const fieldsConfig = isTopcoderUser ? PROFILE_FIELDS_CONFIG.TOPCODER : PROFILE_FIELDS_CONFIG.CUSTOMER
const fieldsConfig = getUserProfileFieldsConfig()
// never show avatar
delete fieldsConfig.avatar
// config the form to only show required fields which doesn't have the value yet
Expand All @@ -40,7 +40,7 @@ const IncompleteUserProfile = ({
console.log('Auto-detected timezone', prefilledProfileSettings.settings.timeZone)
}

if (isTopcoderUser) {
if (!hasPermission(PERMISSIONS.VIEW_USER_PROFILE_AS_CUSTOMER)) {
// We don't ask Topcoder User for "Company Name" and "Title"
// but server requires them, so if they are not yet defined, we set them automatically
if (!profileSettings.settings.companyName) {
Expand Down Expand Up @@ -71,7 +71,6 @@ const IncompleteUserProfile = ({
IncompleteUserProfile.propTypes = {
profileSettings: PT.object.isRequired,
saveProfileSettings: PT.func.isRequired,
isTopcoderUser: PT.bool.isRequired,
user: PT.object.isRequired,
}

Expand Down
6 changes: 3 additions & 3 deletions src/components/Posts/PostsContainer.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ class PostsContainer extends React.Component {
* which is accepted by Feed component
*/
prepareFeed() {
const { topic, error, allMembers, currentMemberRole, tag } = this.props
const { topic, error, allMembers, tag } = this.props
const { showAll } = this.state

if (!topic || !tag) {
Expand All @@ -73,7 +73,7 @@ class PostsContainer extends React.Component {
// Github issue##623, allow comments on all posts (including system posts)
allowComments: true,
user: isSystemUser(topic.userId) ? SYSTEM_USER : allMembers[topic.userId],
unread: !topic.read && !!currentMemberRole,
unread: !topic.read,
totalComments: topic.totalPosts,
comments: [],
}
Expand All @@ -88,7 +88,7 @@ class PostsContainer extends React.Component {
isSavingComment: p.isSavingComment,
isDeletingComment: p.isDeletingComment,
error: p.error,
unread: !p.read && !!currentMemberRole,
unread: !p.read,
date,
createdAt: p.date,
edited,
Expand Down
Loading