From da6af286c61dc3bf605627688d6f2593e34cf424 Mon Sep 17 00:00:00 2001 From: Reinaldo Neto <47038980+reinaldonetof@users.noreply.github.com> Date: Tue, 5 Oct 2021 10:59:40 -0300 Subject: [PATCH] [IMPROVE] Check permission to create a room (#3233) * [IMPROVE] Check permission to create new message, channels, teams * Show or not the button to create at RoomListView * Check permission for each button inside NewMessageView * Check permission to create private or public channel * Minor tweak * Refactor to create a function tuserHasRolePermission * Refactor to use only one function at rocketchat to check the user permission * Minor tweaks * Reactive create channel * reactive new message view, and handleHasPermission out of constructor * handleHasPermission to didMount in roomListView * remove console.log * Call the function in componentDidMount * Changed === to dequal, to check array Co-authored-by: Gerzon Z Co-authored-by: Gerzon Z Co-authored-by: AlexAlexandre Co-authored-by: Diego Mello --- app/lib/methods/getPermissions.js | 4 ++ app/lib/rocketchat.js | 22 ++++--- app/views/CreateChannelView.js | 56 +++++++++++++--- app/views/NewMessageView.js | 102 ++++++++++++++++++++++++------ app/views/RoomsListView/index.js | 84 +++++++++++++++++++++--- 5 files changed, 222 insertions(+), 46 deletions(-) diff --git a/app/lib/methods/getPermissions.js b/app/lib/methods/getPermissions.js index d23268f39..589aa6bd1 100644 --- a/app/lib/methods/getPermissions.js +++ b/app/lib/methods/getPermissions.js @@ -18,6 +18,10 @@ const PERMISSIONS = [ 'archive-room', 'auto-translate', 'create-invite-links', + 'create-c', + 'create-p', + 'create-d', + 'start-discussion', 'create-team', 'delete-c', 'delete-message', diff --git a/app/lib/rocketchat.js b/app/lib/rocketchat.js index b88c02d28..40722348f 100644 --- a/app/lib/rocketchat.js +++ b/app/lib/rocketchat.js @@ -1380,17 +1380,19 @@ const RocketChat = { * Returns an array of boolean for each permission from permissions arg */ async hasPermission(permissions, rid) { - const db = database.active; - const subsCollection = db.get('subscriptions'); let roomRoles = []; - try { - // get the room from database - const room = await subsCollection.find(rid); - // get room roles - roomRoles = room.roles || []; - } catch (error) { - console.log('hasPermission -> Room not found'); - return permissions.map(() => false); + if (rid) { + const db = database.active; + const subsCollection = db.get('subscriptions'); + try { + // get the room from database + const room = await subsCollection.find(rid); + // get room roles + roomRoles = room.roles || []; + } catch (error) { + console.log('hasPermission -> Room not found'); + return permissions.map(() => false); + } } try { diff --git a/app/views/CreateChannelView.js b/app/views/CreateChannelView.js index af17961fb..0aa3c7035 100644 --- a/app/views/CreateChannelView.js +++ b/app/views/CreateChannelView.js @@ -21,6 +21,7 @@ import { Review } from '../utils/review'; import { getUserSelector } from '../selectors/login'; import { events, logEvent } from '../utils/log'; import SafeAreaView from '../containers/SafeAreaView'; +import RocketChat from '../lib/rocketchat'; import sharedStyles from './Styles'; const styles = StyleSheet.create({ @@ -79,10 +80,13 @@ class CreateChannelView extends React.Component { users: PropTypes.array.isRequired, user: PropTypes.shape({ id: PropTypes.string, - token: PropTypes.string + token: PropTypes.string, + roles: PropTypes.array }), theme: PropTypes.string, - teamId: PropTypes.string + teamId: PropTypes.string, + createPublicChannelPermission: PropTypes.array, + createPrivateChannelPermission: PropTypes.array }; constructor(props) { @@ -96,14 +100,20 @@ class CreateChannelView extends React.Component { readOnly: false, encrypted: false, broadcast: false, - isTeam + isTeam, + permissions: [] }; this.setHeader(); } + componentDidMount() { + this.handleHasPermission(); + } + shouldComponentUpdate(nextProps, nextState) { - const { channelName, type, readOnly, broadcast, encrypted } = this.state; - const { users, isFetching, encryptionEnabled, theme } = this.props; + const { channelName, type, readOnly, broadcast, encrypted, permissions } = this.state; + const { users, isFetching, encryptionEnabled, theme, createPublicChannelPermission, createPrivateChannelPermission } = + this.props; if (nextProps.theme !== theme) { return true; } @@ -122,18 +132,37 @@ class CreateChannelView extends React.Component { if (nextState.broadcast !== broadcast) { return true; } + if (nextState.permissions !== permissions) { + return true; + } if (nextProps.isFetching !== isFetching) { return true; } if (nextProps.encryptionEnabled !== encryptionEnabled) { return true; } + if (!dequal(nextProps.createPublicChannelPermission, createPublicChannelPermission)) { + return true; + } + if (!dequal(nextProps.createPrivateChannelPermission, createPrivateChannelPermission)) { + return true; + } if (!dequal(nextProps.users, users)) { return true; } return false; } + componentDidUpdate(prevProps) { + const { createPublicChannelPermission, createPrivateChannelPermission } = this.props; + if ( + !dequal(createPublicChannelPermission, prevProps.createPublicChannelPermission) || + !dequal(createPrivateChannelPermission, prevProps.createPrivateChannelPermission) + ) { + this.handleHasPermission(); + } + } + setHeader = () => { const { navigation } = this.props; const { isTeam } = this.state; @@ -208,12 +237,21 @@ class CreateChannelView extends React.Component { ); }; + handleHasPermission = async () => { + const { createPublicChannelPermission, createPrivateChannelPermission } = this.props; + const permissions = [createPublicChannelPermission, createPrivateChannelPermission]; + const permissionsToCreate = await RocketChat.hasPermission(permissions); + this.setState({ permissions: permissionsToCreate }); + }; + renderType() { - const { type, isTeam } = this.state; + const { type, isTeam, permissions } = this.state; + const isDisabled = permissions.filter(r => r === true).length <= 1; return this.renderSwitch({ id: 'type', - value: type, + value: permissions[1] ? type : false, + disabled: isDisabled, label: isTeam ? 'Private_Team' : 'Private_Channel', onValueChange: value => { logEvent(events.CR_TOGGLE_TYPE); @@ -373,7 +411,9 @@ const mapStateToProps = state => ({ isFetching: state.createChannel.isFetching, encryptionEnabled: state.encryption.enabled, users: state.selectedUsers.users, - user: getUserSelector(state) + user: getUserSelector(state), + createPublicChannelPermission: state.permissions['create-c'], + createPrivateChannelPermission: state.permissions['create-p'] }); const mapDispatchToProps = dispatch => ({ diff --git a/app/views/NewMessageView.js b/app/views/NewMessageView.js index 4b210e7cb..020588ffa 100644 --- a/app/views/NewMessageView.js +++ b/app/views/NewMessageView.js @@ -3,8 +3,9 @@ import PropTypes from 'prop-types'; import { FlatList, StyleSheet, Text, View } from 'react-native'; import { connect } from 'react-redux'; import { Q } from '@nozbe/watermelondb'; - +import { dequal } from 'dequal'; import * as List from '../containers/List'; + import Touch from '../utils/touch'; import database from '../lib/database'; import RocketChat from '../lib/rocketchat'; @@ -57,13 +58,19 @@ class NewMessageView extends React.Component { baseUrl: PropTypes.string, user: PropTypes.shape({ id: PropTypes.string, - token: PropTypes.string + token: PropTypes.string, + roles: PropTypes.array }), create: PropTypes.func, maxUsers: PropTypes.number, theme: PropTypes.string, isMasterDetail: PropTypes.bool, - serverVersion: PropTypes.string + serverVersion: PropTypes.string, + createTeamPermission: PropTypes.array, + createDirectMessagePermission: PropTypes.array, + createPublicChannelPermission: PropTypes.array, + createPrivateChannelPermission: PropTypes.array, + createDiscussionPermission: PropTypes.array }; constructor(props) { @@ -71,7 +78,8 @@ class NewMessageView extends React.Component { this.init(); this.state = { search: [], - chats: [] + chats: [], + permissions: [] }; } @@ -90,6 +98,30 @@ class NewMessageView extends React.Component { } }; + componentDidMount() { + this.handleHasPermission(); + } + + componentDidUpdate(prevProps) { + const { + createTeamPermission, + createPublicChannelPermission, + createPrivateChannelPermission, + createDirectMessagePermission, + createDiscussionPermission + } = this.props; + + if ( + !dequal(createTeamPermission, prevProps.createTeamPermission) || + !dequal(createPublicChannelPermission, prevProps.createPublicChannelPermission) || + !dequal(createPrivateChannelPermission, prevProps.createPrivateChannelPermission) || + !dequal(createDirectMessagePermission, prevProps.createDirectMessagePermission) || + !dequal(createDiscussionPermission, prevProps.createDiscussionPermission) + ) { + this.handleHasPermission(); + } + } + onSearchChangeText(text) { this.search(text); } @@ -161,20 +193,43 @@ class NewMessageView extends React.Component { Navigation.navigate('CreateDiscussionView'); }; + handleHasPermission = async () => { + const { + createTeamPermission, + createDirectMessagePermission, + createPublicChannelPermission, + createPrivateChannelPermission, + createDiscussionPermission + } = this.props; + const permissions = [ + createPublicChannelPermission, + createPrivateChannelPermission, + createTeamPermission, + createDirectMessagePermission, + createDiscussionPermission + ]; + const permissionsToCreate = await RocketChat.hasPermission(permissions); + this.setState({ permissions: permissionsToCreate }); + }; + renderHeader = () => { const { maxUsers, theme, serverVersion } = this.props; + const { permissions } = this.state; + return ( this.onSearchChangeText(text)} testID='new-message-view-search' /> - {this.renderButton({ - onPress: this.createChannel, - title: I18n.t('Create_Channel'), - icon: 'channel-public', - testID: 'new-message-view-create-channel', - first: true - })} - {compareServerVersion(serverVersion, '3.13.0', methods.greaterThanOrEqualTo) + {permissions[0] || permissions[1] + ? this.renderButton({ + onPress: this.createChannel, + title: I18n.t('Create_Channel'), + icon: 'channel-public', + testID: 'new-message-view-create-channel', + first: true + }) + : null} + {compareServerVersion(serverVersion, '3.13.0', methods.greaterThanOrEqualTo) && permissions[2] ? this.renderButton({ onPress: this.createTeam, title: I18n.t('Create_Team'), @@ -182,7 +237,7 @@ class NewMessageView extends React.Component { testID: 'new-message-view-create-team' }) : null} - {maxUsers > 2 + {maxUsers > 2 && permissions[3] ? this.renderButton({ onPress: this.createGroupChat, title: I18n.t('Create_Direct_Messages'), @@ -190,12 +245,14 @@ class NewMessageView extends React.Component { testID: 'new-message-view-create-direct-message' }) : null} - {this.renderButton({ - onPress: this.createDiscussion, - title: I18n.t('Create_Discussion'), - icon: 'discussions', - testID: 'new-message-view-create-discussion' - })} + {permissions[4] + ? this.renderButton({ + onPress: this.createDiscussion, + title: I18n.t('Create_Discussion'), + icon: 'discussions', + testID: 'new-message-view-create-discussion' + }) + : null} ); @@ -261,7 +318,12 @@ const mapStateToProps = state => ({ isMasterDetail: state.app.isMasterDetail, baseUrl: state.server.server, maxUsers: state.settings.DirectMesssage_maxUsers || 1, - user: getUserSelector(state) + user: getUserSelector(state), + createTeamPermission: state.permissions['create-team'], + createDirectMessagePermission: state.permissions['create-d'], + createPublicChannelPermission: state.permissions['create-c'], + createPrivateChannelPermission: state.permissions['create-p'], + createDiscussionPermission: state.permissions['start-discussion'] }); const mapDispatchToProps = dispatch => ({ diff --git a/app/views/RoomsListView/index.js b/app/views/RoomsListView/index.js index 5f4d12553..4c9b48269 100644 --- a/app/views/RoomsListView/index.js +++ b/app/views/RoomsListView/index.js @@ -89,7 +89,12 @@ const shouldUpdateProps = [ 'refreshing', 'queueSize', 'inquiryEnabled', - 'encryptionBanner' + 'encryptionBanner', + 'createTeamPermission', + 'createDirectMessagePermission', + 'createPublicChannelPermission', + 'createPrivateChannelPermission', + 'createDiscussionPermission' ]; const getItemLayout = (data, index) => ({ length: ROW_HEIGHT, @@ -106,7 +111,7 @@ class RoomsListView extends React.Component { username: PropTypes.string, token: PropTypes.string, statusLivechat: PropTypes.string, - roles: PropTypes.object + roles: PropTypes.array }), server: PropTypes.string, searchText: PropTypes.string, @@ -135,6 +140,11 @@ class RoomsListView extends React.Component { queueSize: PropTypes.number, inquiryEnabled: PropTypes.bool, encryptionBanner: PropTypes.string, + createTeamPermission: PropTypes.array, + createDirectMessagePermission: PropTypes.array, + createPublicChannelPermission: PropTypes.array, + createPrivateChannelPermission: PropTypes.array, + createDiscussionPermission: PropTypes.array, initAdd: PropTypes.func }; @@ -152,7 +162,8 @@ class RoomsListView extends React.Component { loading: true, chatsUpdate: [], chats: [], - item: {} + item: {}, + canCreateRoom: false }; this.setHeader(); this.getSubscriptions(); @@ -160,6 +171,7 @@ class RoomsListView extends React.Component { componentDidMount() { const { navigation, closeServerDropdown } = this.props; + this.handleHasPermission(); this.mounted = true; if (isTablet) { @@ -203,7 +215,7 @@ class RoomsListView extends React.Component { } shouldComponentUpdate(nextProps, nextState) { - const { chatsUpdate, searching, item } = this.state; + const { chatsUpdate, searching, item, canCreateRoom } = this.state; // eslint-disable-next-line react/destructuring-assignment const propsUpdated = shouldUpdateProps.some(key => nextProps[key] !== this.props[key]); if (propsUpdated) { @@ -222,6 +234,10 @@ class RoomsListView extends React.Component { return true; } + if (nextState.canCreateRoom !== canCreateRoom) { + return true; + } + if (nextState.item?.rid !== item?.rid) { return true; } @@ -257,7 +273,20 @@ class RoomsListView extends React.Component { } componentDidUpdate(prevProps) { - const { sortBy, groupByType, showFavorites, showUnread, rooms, isMasterDetail, insets } = this.props; + const { + sortBy, + groupByType, + showFavorites, + showUnread, + rooms, + isMasterDetail, + insets, + createTeamPermission, + createPublicChannelPermission, + createPrivateChannelPermission, + createDirectMessagePermission, + createDiscussionPermission + } = this.props; const { item } = this.state; if ( @@ -278,6 +307,17 @@ class RoomsListView extends React.Component { if (insets.left !== prevProps.insets.left || insets.right !== prevProps.insets.right) { this.setHeader(); } + + if ( + !dequal(createTeamPermission, prevProps.createTeamPermission) || + !dequal(createPublicChannelPermission, prevProps.createPublicChannelPermission) || + !dequal(createPrivateChannelPermission, prevProps.createPrivateChannelPermission) || + !dequal(createDirectMessagePermission, prevProps.createDirectMessagePermission) || + !dequal(createDiscussionPermission, prevProps.createDiscussionPermission) + ) { + this.handleHasPermission(); + this.setHeader(); + } } componentWillUnmount() { @@ -297,10 +337,31 @@ class RoomsListView extends React.Component { console.countReset(`${this.constructor.name}.render calls`); } + handleHasPermission = async () => { + const { + createTeamPermission, + createDirectMessagePermission, + createPublicChannelPermission, + createPrivateChannelPermission, + createDiscussionPermission + } = this.props; + const permissions = [ + createPublicChannelPermission, + createPrivateChannelPermission, + createTeamPermission, + createDirectMessagePermission, + createDiscussionPermission + ]; + const permissionsToCreate = await RocketChat.hasPermission(permissions); + const canCreateRoom = permissionsToCreate.filter(r => r === true).length > 0; + this.setState({ canCreateRoom }, () => this.setHeader()); + }; + getHeader = () => { - const { searching } = this.state; + const { searching, canCreateRoom } = this.state; const { navigation, isMasterDetail, insets } = this.props; const headerTitlePosition = getHeaderTitlePosition({ insets, numIconsRight: searching ? 0 : 3 }); + return { headerTitleAlign: 'left', headerLeft: () => @@ -327,7 +388,9 @@ class RoomsListView extends React.Component { headerRight: () => searching ? null : ( - + {canCreateRoom ? ( + + ) : null} @@ -963,7 +1026,12 @@ const mapStateToProps = state => ({ rooms: state.room.rooms, queueSize: getInquiryQueueSelector(state).length, inquiryEnabled: state.inquiry.enabled, - encryptionBanner: state.encryption.banner + encryptionBanner: state.encryption.banner, + createTeamPermission: state.permissions['create-team'], + createDirectMessagePermission: state.permissions['create-d'], + createPublicChannelPermission: state.permissions['create-c'], + createPrivateChannelPermission: state.permissions['create-p'], + createDiscussionPermission: state.permissions['start-discussion'] }); const mapDispatchToProps = dispatch => ({