From e98116587ddb4332edea9a9e0693869e83c9e58d Mon Sep 17 00:00:00 2001 From: Gung Wah <41157464+kresnaputra@users.noreply.github.com> Date: Fri, 26 Feb 2021 00:41:44 +0800 Subject: [PATCH] [CHORE] Add permissions to Redux (#2914) * [FIX] Add permissions to Redux store * add only permissions being used in the app * add clear permissions reducer * call RocketChat.hasPermission from reducer * add server version comparison on getPermissions * refactor hasPermission function * refactor hasPermission function * remove uncomment code * use Q.experimentalSortBy() * add coerce function * Change Rocketchat.hasPermission * Apply on isReadOnly * Apply to RoomInfoEditView * Apply to RoomInfoView and RoomInfoEditView * canAutoTranslate * Unnecessary clear permissions * Revert getUpdatedSince * Naming fix Co-authored-by: Diego Mello --- app/actions/actionsTypes.js | 2 + app/actions/permissions.js | 8 +++ app/containers/MessageActions/index.js | 28 +++++--- app/lib/methods/getPermissions.js | 61 ++++++++++++++++-- app/lib/rocketchat.js | 42 ++++-------- app/reducers/index.js | 4 +- app/reducers/permissions.js | 14 ++++ app/sagas/selectServer.js | 1 + app/utils/isReadOnly.js | 16 ++--- app/views/RoomActionsView/index.js | 78 +++++++++++++---------- app/views/RoomInfoEditView/index.js | 55 ++++++++++++---- app/views/RoomInfoView/index.js | 13 ++-- app/views/RoomMembersView/index.js | 35 +++++++++- app/views/RoomView/index.js | 5 +- app/views/SidebarView/index.js | 88 +++++++++++++------------- 15 files changed, 297 insertions(+), 153 deletions(-) create mode 100644 app/actions/permissions.js create mode 100644 app/reducers/permissions.js diff --git a/app/actions/actionsTypes.js b/app/actions/actionsTypes.js index 0d18c9445..1fd8679bc 100644 --- a/app/actions/actionsTypes.js +++ b/app/actions/actionsTypes.js @@ -70,3 +70,5 @@ export const SETTINGS = createRequestTypes('SETTINGS', ['CLEAR', 'ADD']); export const APP_STATE = createRequestTypes('APP_STATE', ['FOREGROUND', 'BACKGROUND']); export const ENTERPRISE_MODULES = createRequestTypes('ENTERPRISE_MODULES', ['CLEAR', 'SET']); export const ENCRYPTION = createRequestTypes('ENCRYPTION', ['INIT', 'STOP', 'DECODE_KEY', 'SET', 'SET_BANNER']); + +export const PERMISSIONS = createRequestTypes('PERMISSIONS', ['SET']); diff --git a/app/actions/permissions.js b/app/actions/permissions.js new file mode 100644 index 000000000..88179c34f --- /dev/null +++ b/app/actions/permissions.js @@ -0,0 +1,8 @@ +import * as types from './actionsTypes'; + +export function setPermissions(permissions) { + return { + type: types.PERMISSIONS.SET, + permissions + }; +} diff --git a/app/containers/MessageActions/index.js b/app/containers/MessageActions/index.js index 383a23397..d5b998629 100644 --- a/app/containers/MessageActions/index.js +++ b/app/containers/MessageActions/index.js @@ -34,20 +34,24 @@ const MessageActions = React.memo(forwardRef(({ Message_AllowPinning, Message_AllowStarring, Message_Read_Receipt_Store_Users, - isMasterDetail + isMasterDetail, + editMessagePermission, + deleteMessagePermission, + forceDeleteMessagePermission, + pinMessagePermission }, ref) => { let permissions = {}; const { showActionSheet, hideActionSheet } = useActionSheet(); const getPermissions = async() => { try { - const permission = ['edit-message', 'delete-message', 'force-delete-message', 'pin-message']; + const permission = [editMessagePermission, deleteMessagePermission, forceDeleteMessagePermission, pinMessagePermission]; const result = await RocketChat.hasPermission(permission, room.rid); permissions = { - hasEditPermission: result[permission[0]], - hasDeletePermission: result[permission[1]], - hasForceDeletePermission: result[permission[2]], - hasPinPermission: result[permission[3]] + hasEditPermission: result[0], + hasDeletePermission: result[1], + hasForceDeletePermission: result[2], + hasPinPermission: result[3] }; } catch { // Do nothing @@ -440,7 +444,11 @@ MessageActions.propTypes = { Message_AllowPinning: PropTypes.bool, Message_AllowStarring: PropTypes.bool, Message_Read_Receipt_Store_Users: PropTypes.bool, - server: PropTypes.string + server: PropTypes.string, + editMessagePermission: PropTypes.array, + deleteMessagePermission: PropTypes.array, + forceDeleteMessagePermission: PropTypes.array, + pinMessagePermission: PropTypes.array }; const mapStateToProps = state => ({ @@ -452,7 +460,11 @@ const mapStateToProps = state => ({ Message_AllowPinning: state.settings.Message_AllowPinning, Message_AllowStarring: state.settings.Message_AllowStarring, Message_Read_Receipt_Store_Users: state.settings.Message_Read_Receipt_Store_Users, - isMasterDetail: state.app.isMasterDetail + isMasterDetail: state.app.isMasterDetail, + editMessagePermission: state.permissions['edit-message'], + deleteMessagePermission: state.permissions['delete-message'], + forceDeleteMessagePermission: state.permissions['force-delete-message'], + pinMessagePermission: state.permissions['pin-message'] }); export default connect(mapStateToProps, null, null, { forwardRef: true })(MessageActions); diff --git a/app/lib/methods/getPermissions.js b/app/lib/methods/getPermissions.js index 06f30e91a..33333cad0 100644 --- a/app/lib/methods/getPermissions.js +++ b/app/lib/methods/getPermissions.js @@ -1,11 +1,55 @@ import lt from 'semver/functions/lt'; import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord'; +import { Q } from '@nozbe/watermelondb'; +import coerce from 'semver/functions/coerce'; import orderBy from 'lodash/orderBy'; import database from '../database'; import log from '../../utils/log'; import reduxStore from '../createStore'; import protectedFunction from './helpers/protectedFunction'; +import { setPermissions as setPermissionsAction } from '../../actions/permissions'; + +const PERMISSIONS = [ + 'add-user-to-any-c-room', + 'add-user-to-any-p-room', + 'add-user-to-joined-room', + 'archive-room', + 'auto-translate', + 'create-invite-links', + 'delete-c', + 'delete-message', + 'delete-p', + 'edit-message', + 'edit-room', + 'force-delete-message', + 'mute-user', + 'pin-message', + 'post-readonly', + 'remove-user', + 'set-leader', + 'set-moderator', + 'set-owner', + 'set-react-when-readonly', + 'set-readonly', + 'toggle-room-e2e-encryption', + 'transfer-livechat-guest', + 'unarchive-room', + 'view-broadcast-member-list', + 'view-privileged-setting', + 'view-room-administration', + 'view-statistics', + 'view-user-administration' +]; + +export async function setPermissions() { + const db = database.active; + const permissionsCollection = db.collections.get('permissions'); + const allPermissions = await permissionsCollection.query(Q.where('id', Q.oneOf(PERMISSIONS))).fetch(); + const parsed = allPermissions.reduce((acc, item) => ({ ...acc, [item.id]: item.roles }), {}); + + reduxStore.dispatch(setPermissionsAction(parsed)); +} const getUpdatedSince = (allRecords) => { try { @@ -64,12 +108,13 @@ const updatePermissions = async({ update = [], remove = [], allRecords }) => { await db.action(async() => { await db.batch(...batch); }); + return true; } catch (e) { log(e); } }; -export default function() { +export function getPermissions() { return new Promise(async(resolve) => { try { const serverVersion = reduxStore.getState().server.version; @@ -78,17 +123,20 @@ export default function() { const allRecords = await permissionsCollection.query().fetch(); // if server version is lower than 0.73.0, fetches from old api - if (serverVersion && lt(serverVersion, '0.73.0')) { + if (serverVersion && lt(coerce(serverVersion), '0.73.0')) { // RC 0.66.0 const result = await this.sdk.get('permissions.list'); if (!result.success) { return resolve(); } - await updatePermissions({ update: result.permissions, allRecords }); + const changePermissions = await updatePermissions({ update: result.permissions, allRecords }); + if (changePermissions) { + setPermissions(); + } return resolve(); } else { const params = {}; - const updatedSince = await getUpdatedSince(allRecords); + const updatedSince = getUpdatedSince(allRecords); if (updatedSince) { params.updatedSince = updatedSince; } @@ -99,7 +147,10 @@ export default function() { return resolve(); } - await updatePermissions({ update: result.update, remove: result.delete, allRecords }); + const changePermissions = await updatePermissions({ update: result.update, remove: result.delete, allRecords }); + if (changePermissions) { + setPermissions(); + } return resolve(); } } catch (e) { diff --git a/app/lib/rocketchat.js b/app/lib/rocketchat.js index 4f9952b80..5c768cfab 100644 --- a/app/lib/rocketchat.js +++ b/app/lib/rocketchat.js @@ -32,7 +32,7 @@ import readMessages from './methods/readMessages'; import getSettings, { getLoginSettings, setSettings } from './methods/getSettings'; import getRooms from './methods/getRooms'; -import getPermissions from './methods/getPermissions'; +import { setPermissions, getPermissions } from './methods/getPermissions'; import { getCustomEmojis, setCustomEmojis } from './methods/getCustomEmojis'; import { getEnterpriseModules, setEnterpriseModules, hasLicense, isOmnichannelModuleAvailable @@ -70,7 +70,6 @@ const CERTIFICATE_KEY = 'RC_CERTIFICATE_KEY'; export const THEME_PREFERENCES_KEY = 'RC_THEME_PREFERENCES_KEY'; export const CRASH_REPORT_KEY = 'RC_CRASH_REPORT_KEY'; export const ANALYTICS_EVENTS_KEY = 'RC_ANALYTICS_EVENTS_KEY'; -const returnAnArray = obj => obj || []; const MIN_ROCKETCHAT_VERSION = '0.70.0'; const STATUSES = ['offline', 'online', 'away', 'busy']; @@ -740,6 +739,7 @@ const RocketChat = { getLoginSettings, setSettings, getPermissions, + setPermissions, getCustomEmojis, setCustomEmojis, getEnterpriseModules, @@ -1172,10 +1172,13 @@ const RocketChat = { // RC 0.65.0 return this.sdk.get(`${ this.roomTypeToApiType(type) }.roles`, { roomId }); }, + /** + * Permissions: array of permissions' roles from redux. Example: [['owner', 'admin'], ['leader']] + * Returns an array of boolean for each permission from permissions arg + */ async hasPermission(permissions, rid) { const db = database.active; const subsCollection = db.collections.get('subscriptions'); - const permissionsCollection = db.collections.get('permissions'); let roomRoles = []; try { // get the room from database @@ -1184,31 +1187,16 @@ const RocketChat = { roomRoles = room.roles || []; } catch (error) { console.log('hasPermission -> Room not found'); - return permissions.reduce((result, permission) => { - result[permission] = false; - return result; - }, {}); + return permissions.map(() => false); } - // get permissions from database + try { - const permissionsFiltered = await permissionsCollection.query(Q.where('id', Q.oneOf(permissions))).fetch(); const shareUser = reduxStore.getState().share.user; const loginUser = reduxStore.getState().login.user; // get user roles on the server from redux const userRoles = (shareUser?.roles || loginUser?.roles) || []; - // merge both roles const mergedRoles = [...new Set([...roomRoles, ...userRoles])]; - - // return permissions in object format - // e.g. { 'edit-room': true, 'set-readonly': false } - return permissions.reduce((result, permission) => { - result[permission] = false; - const permissionFound = permissionsFiltered.find(p => p.id === permission); - if (permissionFound) { - result[permission] = returnAnArray(permissionFound.roles).some(r => mergedRoles.includes(r)); - } - return result; - }, {}); + return permissions.map(permission => permission.some(r => mergedRoles.includes(r) ?? false)); } catch (e) { log(e); } @@ -1438,17 +1426,15 @@ const RocketChat = { query, count, offset, sort }); }, - async canAutoTranslate() { - const db = database.active; + canAutoTranslate() { try { - const AutoTranslate_Enabled = reduxStore.getState().settings && reduxStore.getState().settings.AutoTranslate_Enabled; + const { AutoTranslate_Enabled } = reduxStore.getState().settings; if (!AutoTranslate_Enabled) { return false; } - const permissionsCollection = db.collections.get('permissions'); - const autoTranslatePermission = await permissionsCollection.find('auto-translate'); - const userRoles = (reduxStore.getState().login.user && reduxStore.getState().login.user.roles) || []; - return autoTranslatePermission.roles.some(role => userRoles.includes(role)); + const autoTranslatePermission = reduxStore.getState().permissions['auto-translate']; + const userRoles = (reduxStore.getState().login?.user?.roles) ?? []; + return autoTranslatePermission?.some(role => userRoles.includes(role)); } catch (e) { log(e); return false; diff --git a/app/reducers/index.js b/app/reducers/index.js index 6211ccb6f..dfee5f3eb 100644 --- a/app/reducers/index.js +++ b/app/reducers/index.js @@ -18,6 +18,7 @@ import inviteLinks from './inviteLinks'; import createDiscussion from './createDiscussion'; import enterpriseModules from './enterpriseModules'; import encryption from './encryption'; +import permissions from './permissions'; import inquiry from '../ee/omnichannel/reducers/inquiry'; @@ -41,5 +42,6 @@ export default combineReducers({ createDiscussion, inquiry, enterpriseModules, - encryption + encryption, + permissions }); diff --git a/app/reducers/permissions.js b/app/reducers/permissions.js new file mode 100644 index 000000000..1b3a14ec2 --- /dev/null +++ b/app/reducers/permissions.js @@ -0,0 +1,14 @@ +import { PERMISSIONS } from '../actions/actionsTypes'; + +const initialState = { + permissions: {} +}; + +export default function permissions(state = initialState, action) { + switch (action.type) { + case PERMISSIONS.SET: + return action.permissions; + default: + return state; + } +} diff --git a/app/sagas/selectServer.js b/app/sagas/selectServer.js index 87fb47549..c195f7123 100644 --- a/app/sagas/selectServer.js +++ b/app/sagas/selectServer.js @@ -124,6 +124,7 @@ const handleSelectServer = function* handleSelectServer({ server, version, fetch // and block the selectServerSuccess raising multiples errors RocketChat.setSettings(); RocketChat.setCustomEmojis(); + RocketChat.setPermissions(); RocketChat.setEnterpriseModules(); let serverInfo; diff --git a/app/utils/isReadOnly.js b/app/utils/isReadOnly.js index 7d35a2836..f6f1937f6 100644 --- a/app/utils/isReadOnly.js +++ b/app/utils/isReadOnly.js @@ -1,13 +1,11 @@ import RocketChat from '../lib/rocketchat'; +import reduxStore from '../lib/createStore'; -const canPost = async({ rid }) => { - try { - const permission = await RocketChat.hasPermission(['post-readonly'], rid); - return permission && permission['post-readonly']; - } catch { - // do nothing - } - return false; +const canPostReadOnly = async({ rid }) => { + // TODO: this is not reactive. If this permission changes, the component won't be updated + const postReadOnlyPermission = reduxStore.getState().permissions['post-readonly']; + const permission = await RocketChat.hasPermission([postReadOnlyPermission], rid); + return permission[0]; }; const isMuted = (room, user) => room && room.muted && room.muted.find && !!room.muted.find(m => m === user.username); @@ -20,7 +18,7 @@ export const isReadOnly = async(room, user) => { return true; } if (room?.ro) { - const allowPost = await canPost(room); + const allowPost = await canPostReadOnly(room); if (allowPost) { return false; } diff --git a/app/views/RoomActionsView/index.js b/app/views/RoomActionsView/index.js index 96319edba..5d3591d6a 100644 --- a/app/views/RoomActionsView/index.js +++ b/app/views/RoomActionsView/index.js @@ -53,7 +53,15 @@ class RoomActionsView extends React.Component { closeRoom: PropTypes.func, theme: PropTypes.string, fontScale: PropTypes.number, - serverVersion: PropTypes.string + serverVersion: PropTypes.string, + addUserToJoinedRoomPermission: PropTypes.array, + addUserToAnyCRoomPermission: PropTypes.array, + addUserToAnyPRoomPermission: PropTypes.array, + createInviteLinksPermission: PropTypes.array, + editRoomPermission: PropTypes.array, + toggleRoomE2EEncryptionPermission: PropTypes.array, + viewBroadcastMemberListPermission: PropTypes.array, + transferLivechatGuestPermission: PropTypes.array } constructor(props) { @@ -118,7 +126,7 @@ class RoomActionsView extends React.Component { this.updateRoomMember(); } - const canAutoTranslate = await RocketChat.canAutoTranslate(); + const canAutoTranslate = RocketChat.canAutoTranslate(); this.setState({ canAutoTranslate }); this.canAddUser(); @@ -159,60 +167,62 @@ class RoomActionsView extends React.Component { canAddUser = async() => { const { room, joined } = this.state; + const { addUserToJoinedRoomPermission, addUserToAnyCRoomPermission, addUserToAnyPRoomPermission } = this.props; const { rid, t } = room; - let canAdd = false; + let canAddUser = false; const userInRoom = joined; - const permissions = await RocketChat.hasPermission(['add-user-to-joined-room', 'add-user-to-any-c-room', 'add-user-to-any-p-room'], rid); + const permissions = await RocketChat.hasPermission([addUserToJoinedRoomPermission, addUserToAnyCRoomPermission, addUserToAnyPRoomPermission], rid); - if (permissions) { - if (userInRoom && permissions['add-user-to-joined-room']) { - canAdd = true; - } - if (t === 'c' && permissions['add-user-to-any-c-room']) { - canAdd = true; - } - if (t === 'p' && permissions['add-user-to-any-p-room']) { - canAdd = true; - } + if (userInRoom && permissions[0]) { + canAddUser = true; } - this.setState({ canAddUser: canAdd }); + if (t === 'c' && permissions[1]) { + canAddUser = true; + } + if (t === 'p' && permissions[2]) { + canAddUser = true; + } + this.setState({ canAddUser }); } canInviteUser = async() => { const { room } = this.state; + const { createInviteLinksPermission } = this.props; const { rid } = room; - const permissions = await RocketChat.hasPermission(['create-invite-links'], rid); + const permissions = await RocketChat.hasPermission([createInviteLinksPermission], rid); - const canInviteUser = permissions && permissions['create-invite-links']; + const canInviteUser = permissions[0]; this.setState({ canInviteUser }); } canEdit = async() => { const { room } = this.state; + const { editRoomPermission } = this.props; const { rid } = room; - const permissions = await RocketChat.hasPermission(['edit-room'], rid); + const permissions = await RocketChat.hasPermission([editRoomPermission], rid); - const canEdit = permissions && permissions['edit-room']; + const canEdit = permissions[0]; this.setState({ canEdit }); } canToggleEncryption = async() => { const { room } = this.state; + const { toggleRoomE2EEncryptionPermission } = this.props; const { rid } = room; - const permissions = await RocketChat.hasPermission(['toggle-room-e2e-encryption'], rid); + const permissions = await RocketChat.hasPermission([toggleRoomE2EEncryptionPermission], rid); - const canToggleEncryption = permissions && permissions['toggle-room-e2e-encryption']; + const canToggleEncryption = permissions[0]; this.setState({ canToggleEncryption }); } canViewMembers = async() => { const { room } = this.state; + const { viewBroadcastMemberListPermission } = this.props; const { rid, t, broadcast } = room; if (broadcast) { - const viewBroadcastMemberListPermission = 'view-broadcast-member-list'; const permissions = await RocketChat.hasPermission([viewBroadcastMemberListPermission], rid); - if (!permissions[viewBroadcastMemberListPermission]) { + if (!permissions[0]) { return false; } } @@ -226,16 +236,10 @@ class RoomActionsView extends React.Component { canForwardGuest = async() => { const { room } = this.state; + const { transferLivechatGuestPermission } = this.props; const { rid } = room; - let result = true; - - const transferLivechatGuest = 'transfer-livechat-guest'; - const permissions = await RocketChat.hasPermission([transferLivechatGuest], rid); - if (!permissions[transferLivechatGuest]) { - result = false; - } - - this.setState({ canForwardGuest: result }); + const permissions = await RocketChat.hasPermission([transferLivechatGuestPermission], rid); + this.setState({ canForwardGuest: permissions[0] }); } canReturnQueue = async() => { @@ -866,7 +870,15 @@ class RoomActionsView extends React.Component { const mapStateToProps = state => ({ jitsiEnabled: state.settings.Jitsi_Enabled || false, encryptionEnabled: state.encryption.enabled, - serverVersion: state.server.version + serverVersion: state.server.version, + addUserToJoinedRoomPermission: state.permissions['add-user-to-joined-room'], + addUserToAnyCRoomPermission: state.permissions['add-user-to-any-c-room'], + addUserToAnyPRoomPermission: state.permissions['add-user-to-any-p-room'], + createInviteLinksPermission: state.permissions['create-invite-links'], + editRoomPermission: state.permissions['edit-room'], + toggleRoomE2EEncryptionPermission: state.permissions['toggle-room-e2e-encryption'], + viewBroadcastMemberListPermission: state.permissions['view-broadcast-member-list'], + transferLivechatGuestPermission: state.permissions['transfer-livechat-guest'] }); const mapDispatchToProps = dispatch => ({ diff --git a/app/views/RoomInfoEditView/index.js b/app/views/RoomInfoEditView/index.js index 2669566eb..bf4527e4a 100644 --- a/app/views/RoomInfoEditView/index.js +++ b/app/views/RoomInfoEditView/index.js @@ -44,14 +44,6 @@ const PERMISSION_ARCHIVE = 'archive-room'; const PERMISSION_UNARCHIVE = 'unarchive-room'; const PERMISSION_DELETE_C = 'delete-c'; const PERMISSION_DELETE_P = 'delete-p'; -const PERMISSIONS_ARRAY = [ - PERMISSION_SET_READONLY, - PERMISSION_SET_REACT_WHEN_READONLY, - PERMISSION_ARCHIVE, - PERMISSION_UNARCHIVE, - PERMISSION_DELETE_C, - PERMISSION_DELETE_P -]; class RoomInfoEditView extends React.Component { static navigationOptions = () => ({ @@ -63,7 +55,13 @@ class RoomInfoEditView extends React.Component { deleteRoom: PropTypes.func, serverVersion: PropTypes.string, encryptionEnabled: PropTypes.bool, - theme: PropTypes.string + theme: PropTypes.string, + setReadOnlyPermission: PropTypes.array, + setReactWhenReadOnlyPermission: PropTypes.array, + archiveRoomPermission: PropTypes.array, + unarchiveRoomPermission: PropTypes.array, + deleteCPermission: PropTypes.array, + deletePPermission: PropTypes.array }; constructor(props) { @@ -108,7 +106,15 @@ class RoomInfoEditView extends React.Component { // eslint-disable-next-line react/sort-comp loadRoom = async() => { - const { route } = this.props; + const { + route, + setReadOnlyPermission, + setReactWhenReadOnlyPermission, + archiveRoomPermission, + unarchiveRoomPermission, + deleteCPermission, + deletePPermission + } = this.props; const rid = route.params?.rid; if (!rid) { return; @@ -123,8 +129,25 @@ class RoomInfoEditView extends React.Component { this.init(this.room); }); - const permissions = await RocketChat.hasPermission(PERMISSIONS_ARRAY, rid); - this.setState({ permissions }); + const result = await RocketChat.hasPermission([ + setReadOnlyPermission, + setReactWhenReadOnlyPermission, + archiveRoomPermission, + unarchiveRoomPermission, + deleteCPermission, + deletePPermission + ], rid); + + this.setState({ + permissions: { + [PERMISSION_SET_READONLY]: result[0], + [PERMISSION_SET_REACT_WHEN_READONLY]: result[1], + [PERMISSION_ARCHIVE]: result[2], + [PERMISSION_UNARCHIVE]: result[3], + [PERMISSION_DELETE_C]: result[4], + [PERMISSION_DELETE_P]: result[5] + } + }); } catch (e) { log(e); } @@ -667,7 +690,13 @@ class RoomInfoEditView extends React.Component { const mapStateToProps = state => ({ serverVersion: state.server.version, - encryptionEnabled: state.encryption.enabled + encryptionEnabled: state.encryption.enabled, + setReadOnlyPermission: state.permissions[PERMISSION_SET_READONLY], + setReactWhenReadOnlyPermission: state.permissions[PERMISSION_SET_REACT_WHEN_READONLY], + archiveRoomPermission: state.permissions[PERMISSION_ARCHIVE], + unarchiveRoomPermission: state.permissions[PERMISSION_UNARCHIVE], + deleteCPermission: state.permissions[PERMISSION_DELETE_C], + deletePPermission: state.permissions[PERMISSION_DELETE_P] }); const mapDispatchToProps = dispatch => ({ diff --git a/app/views/RoomInfoView/index.js b/app/views/RoomInfoView/index.js index f34fab6f3..dac40ae7b 100644 --- a/app/views/RoomInfoView/index.js +++ b/app/views/RoomInfoView/index.js @@ -31,7 +31,6 @@ import SafeAreaView from '../../containers/SafeAreaView'; import { goRoom } from '../../utils/goRoom'; import Navigation from '../../lib/Navigation'; -const PERMISSION_EDIT_ROOM = 'edit-room'; const getRoomTitle = (room, type, name, username, statusText, theme) => (type === 'd' ? ( <> @@ -55,7 +54,8 @@ class RoomInfoView extends React.Component { rooms: PropTypes.array, theme: PropTypes.string, isMasterDetail: PropTypes.bool, - jitsiEnabled: PropTypes.bool + jitsiEnabled: PropTypes.bool, + editRoomPermission: PropTypes.array } constructor(props) { @@ -193,7 +193,7 @@ class RoomInfoView extends React.Component { loadRoom = async() => { const { room: roomState } = this.state; - const { route } = this.props; + const { route, editRoomPermission } = this.props; let room = route.params?.room; if (room && room.observe) { this.roomObservable = room.observe(); @@ -213,8 +213,8 @@ class RoomInfoView extends React.Component { } } - const permissions = await RocketChat.hasPermission([PERMISSION_EDIT_ROOM], room.rid); - if (permissions[PERMISSION_EDIT_ROOM] && !room.prid) { + const permissions = await RocketChat.hasPermission([editRoomPermission], room.rid); + if (permissions[0] && !room.prid) { this.setState({ showEdit: true }, () => this.setHeader()); } } @@ -369,7 +369,8 @@ class RoomInfoView extends React.Component { const mapStateToProps = state => ({ rooms: state.room.rooms, isMasterDetail: state.app.isMasterDetail, - jitsiEnabled: state.settings.Jitsi_Enabled || false + jitsiEnabled: state.settings.Jitsi_Enabled || false, + editRoomPermission: state.permissions['edit-room'] }); export default connect(mapStateToProps)(withTheme(RoomInfoView)); diff --git a/app/views/RoomMembersView/index.js b/app/views/RoomMembersView/index.js index 269814669..dd8170b28 100644 --- a/app/views/RoomMembersView/index.js +++ b/app/views/RoomMembersView/index.js @@ -28,6 +28,12 @@ import { goRoom } from '../../utils/goRoom'; const PAGE_SIZE = 25; +const PERMISSION_MUTE_USER = 'mute-user'; +const PERMISSION_SET_LEADER = 'set-leader'; +const PERMISSION_SET_OWNER = 'set-owner'; +const PERMISSION_SET_MODERATOR = 'set-moderator'; +const PERMISSION_REMOVE_USER = 'remove-user'; + class RoomMembersView extends React.Component { static propTypes = { navigation: PropTypes.object, @@ -43,7 +49,12 @@ class RoomMembersView extends React.Component { showActionSheet: PropTypes.func, theme: PropTypes.string, isMasterDetail: PropTypes.bool, - useRealName: PropTypes.bool + useRealName: PropTypes.bool, + muteUserPermission: PropTypes.array, + setLeaderPermission: PropTypes.array, + setOwnerPermission: PropTypes.array, + setModeratorPermission: PropTypes.array, + removeUserPermission: PropTypes.array } constructor(props) { @@ -81,7 +92,20 @@ class RoomMembersView extends React.Component { this.fetchMembers(); const { room } = this.state; - this.permissions = await RocketChat.hasPermission(['mute-user', 'set-leader', 'set-owner', 'set-moderator', 'remove-user'], room.rid); + const { + muteUserPermission, setLeaderPermission, setOwnerPermission, setModeratorPermission, removeUserPermission + } = this.props; + const result = await RocketChat.hasPermission([ + muteUserPermission, setLeaderPermission, setOwnerPermission, setModeratorPermission, removeUserPermission + ], room.rid); + + this.permissions = { + [PERMISSION_MUTE_USER]: result[0], + [PERMISSION_SET_LEADER]: result[1], + [PERMISSION_SET_OWNER]: result[2], + [PERMISSION_SET_MODERATOR]: result[3], + [PERMISSION_REMOVE_USER]: result[4] + }; const hasSinglePermission = Object.values(this.permissions).some(p => !!p); if (hasSinglePermission) { @@ -452,7 +476,12 @@ const mapStateToProps = state => ({ baseUrl: state.server.server, user: getUserSelector(state), isMasterDetail: state.app.isMasterDetail, - useRealName: state.settings.UI_Use_Real_Name + useRealName: state.settings.UI_Use_Real_Name, + muteUserPermission: state.permissions[PERMISSION_MUTE_USER], + setLeaderPermission: state.permissions[PERMISSION_SET_LEADER], + setOwnerPermission: state.permissions[PERMISSION_SET_OWNER], + setModeratorPermission: state.permissions[PERMISSION_SET_MODERATOR], + removeUserPermission: state.permissions[PERMISSION_REMOVE_USER] }); export default connect(mapStateToProps)(withActionSheet(withTheme(RoomMembersView))); diff --git a/app/views/RoomView/index.js b/app/views/RoomView/index.js index ee33b19dc..cd8ebf9a8 100644 --- a/app/views/RoomView/index.js +++ b/app/views/RoomView/index.js @@ -436,10 +436,7 @@ class RoomView extends React.Component { } } - // We run `canAutoTranslate` again in order to refetch auto translate permission - // in case of a missing connection or poor connection on room open - const canAutoTranslate = await RocketChat.canAutoTranslate(); - + const canAutoTranslate = RocketChat.canAutoTranslate(); const member = await this.getRoomMember(); this.setState({ canAutoTranslate, member, loading: false }); diff --git a/app/views/SidebarView/index.js b/app/views/SidebarView/index.js index 9dd63b8dc..6f3ed6023 100644 --- a/app/views/SidebarView/index.js +++ b/app/views/SidebarView/index.js @@ -4,19 +4,16 @@ import { ScrollView, Text, View, TouchableWithoutFeedback } from 'react-native'; import { connect } from 'react-redux'; -import { Q } from '@nozbe/watermelondb'; import isEqual from 'react-fast-compare'; - import Avatar from '../../containers/Avatar'; import Status from '../../containers/Status/Status'; -import log, { logEvent, events } from '../../utils/log'; +import { logEvent, events } from '../../utils/log'; import I18n from '../../i18n'; import scrollPersistTaps from '../../utils/scrollPersistTaps'; import { CustomIcon } from '../../lib/Icons'; import styles from './styles'; import SidebarItem from './SidebarItem'; import { themes } from '../../constants/colors'; -import database from '../../lib/database'; import { withTheme } from '../../theme'; import { getUserSelector } from '../../selectors/login'; import SafeAreaView from '../../containers/SafeAreaView'; @@ -27,13 +24,6 @@ Separator.propTypes = { theme: PropTypes.string }; -const permissions = [ - 'view-statistics', - 'view-room-administration', - 'view-user-administration', - 'view-privileged-setting' -]; - class Sidebar extends Component { static propTypes = { baseUrl: PropTypes.string, @@ -45,32 +35,24 @@ class Sidebar extends Component { loadingServer: PropTypes.bool, useRealName: PropTypes.bool, allowStatusMessage: PropTypes.bool, - isMasterDetail: PropTypes.bool + isMasterDetail: PropTypes.bool, + viewStatisticsPermission: PropTypes.object, + viewRoomAdministrationPermission: PropTypes.object, + viewUserAdministrationPermission: PropTypes.object, + viewPrivilegedSettingPermission: PropTypes.object } constructor(props) { super(props); this.state = { - showStatus: false, - isAdmin: false + showStatus: false }; } - componentDidMount() { - this.setIsAdmin(); - } - - UNSAFE_componentWillReceiveProps(nextProps) { - const { loadingServer } = this.props; - if (loadingServer && nextProps.loadingServer !== loadingServer) { - this.setIsAdmin(); - } - } - shouldComponentUpdate(nextProps, nextState) { const { showStatus, isAdmin } = this.state; const { - Site_Name, user, baseUrl, state, isMasterDetail, useRealName, theme + Site_Name, user, baseUrl, state, isMasterDetail, useRealName, theme, viewStatisticsPermission, viewRoomAdministrationPermission, viewUserAdministrationPermission, viewPrivilegedSettingPermission } = this.props; // Drawer navigation state if (state?.index !== nextProps.state?.index) { @@ -103,25 +85,42 @@ class Sidebar extends Component { if (nextState.isAdmin !== isAdmin) { return true; } + if (!isEqual(nextProps.viewStatisticsPermission, viewStatisticsPermission)) { + return true; + } + if (!isEqual(nextProps.viewRoomAdministrationPermission, viewRoomAdministrationPermission)) { + return true; + } + if (!isEqual(nextProps.viewUserAdministrationPermission, viewUserAdministrationPermission)) { + return true; + } + if (!isEqual(nextProps.viewPrivilegedSettingPermission, viewPrivilegedSettingPermission)) { + return true; + } return false; } - async setIsAdmin() { - const db = database.active; - const { user } = this.props; + + getIsAdmin() { + const { + user, viewStatisticsPermission, viewRoomAdministrationPermission, viewUserAdministrationPermission, viewPrivilegedSettingPermission + } = this.props; const { roles } = user; - try { - if (roles) { - const permissionsCollection = db.collections.get('permissions'); - const permissionsFiltered = await permissionsCollection.query(Q.where('id', Q.oneOf(permissions))).fetch(); - const isAdmin = permissionsFiltered.reduce((result, permission) => ( - result || permission.roles.some(r => roles.indexOf(r) !== -1)), - false); - this.setState({ isAdmin }); - } - } catch (e) { - log(e); + const allPermissions = [viewStatisticsPermission, viewRoomAdministrationPermission, viewUserAdministrationPermission, viewPrivilegedSettingPermission]; + let isAdmin = false; + + if (roles) { + isAdmin = allPermissions.reduce((result, permission) => { + if (permission) { + return ( + result || permission.some(r => roles.indexOf(r) !== -1) + ); + } + return result; + }, + false); } + return isAdmin; } sidebarNavigate = (route) => { @@ -143,9 +142,8 @@ class Sidebar extends Component { } renderAdmin = () => { - const { isAdmin } = this.state; const { theme, isMasterDetail } = this.props; - if (!isAdmin) { + if (!this.getIsAdmin()) { return null; } const routeName = isMasterDetail ? 'AdminPanelView' : 'AdminPanelStackNavigator'; @@ -275,7 +273,11 @@ const mapStateToProps = state => ({ loadingServer: state.server.loading, useRealName: state.settings.UI_Use_Real_Name, allowStatusMessage: state.settings.Accounts_AllowUserStatusMessageChange, - isMasterDetail: state.app.isMasterDetail + isMasterDetail: state.app.isMasterDetail, + viewStatisticsPermission: state.permissions['view-statistics'], + viewRoomAdministrationPermission: state.permissions['view-room-administration'], + viewUserAdministrationPermission: state.permissions['view-user-administration'], + viewPrivilegedSettingPermission: state.permissions['view-privileged-setting'] }); export default connect(mapStateToProps)(withTheme(Sidebar));