[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 <diegolmello@gmail.com>
This commit is contained in:
Gung Wah 2021-02-26 00:41:44 +08:00 committed by GitHub
parent 6e32a15cad
commit e98116587d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 297 additions and 153 deletions

View File

@ -70,3 +70,5 @@ export const SETTINGS = createRequestTypes('SETTINGS', ['CLEAR', 'ADD']);
export const APP_STATE = createRequestTypes('APP_STATE', ['FOREGROUND', 'BACKGROUND']); export const APP_STATE = createRequestTypes('APP_STATE', ['FOREGROUND', 'BACKGROUND']);
export const ENTERPRISE_MODULES = createRequestTypes('ENTERPRISE_MODULES', ['CLEAR', 'SET']); export const ENTERPRISE_MODULES = createRequestTypes('ENTERPRISE_MODULES', ['CLEAR', 'SET']);
export const ENCRYPTION = createRequestTypes('ENCRYPTION', ['INIT', 'STOP', 'DECODE_KEY', 'SET', 'SET_BANNER']); export const ENCRYPTION = createRequestTypes('ENCRYPTION', ['INIT', 'STOP', 'DECODE_KEY', 'SET', 'SET_BANNER']);
export const PERMISSIONS = createRequestTypes('PERMISSIONS', ['SET']);

View File

@ -0,0 +1,8 @@
import * as types from './actionsTypes';
export function setPermissions(permissions) {
return {
type: types.PERMISSIONS.SET,
permissions
};
}

View File

@ -34,20 +34,24 @@ const MessageActions = React.memo(forwardRef(({
Message_AllowPinning, Message_AllowPinning,
Message_AllowStarring, Message_AllowStarring,
Message_Read_Receipt_Store_Users, Message_Read_Receipt_Store_Users,
isMasterDetail isMasterDetail,
editMessagePermission,
deleteMessagePermission,
forceDeleteMessagePermission,
pinMessagePermission
}, ref) => { }, ref) => {
let permissions = {}; let permissions = {};
const { showActionSheet, hideActionSheet } = useActionSheet(); const { showActionSheet, hideActionSheet } = useActionSheet();
const getPermissions = async() => { const getPermissions = async() => {
try { 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); const result = await RocketChat.hasPermission(permission, room.rid);
permissions = { permissions = {
hasEditPermission: result[permission[0]], hasEditPermission: result[0],
hasDeletePermission: result[permission[1]], hasDeletePermission: result[1],
hasForceDeletePermission: result[permission[2]], hasForceDeletePermission: result[2],
hasPinPermission: result[permission[3]] hasPinPermission: result[3]
}; };
} catch { } catch {
// Do nothing // Do nothing
@ -440,7 +444,11 @@ MessageActions.propTypes = {
Message_AllowPinning: PropTypes.bool, Message_AllowPinning: PropTypes.bool,
Message_AllowStarring: PropTypes.bool, Message_AllowStarring: PropTypes.bool,
Message_Read_Receipt_Store_Users: 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 => ({ const mapStateToProps = state => ({
@ -452,7 +460,11 @@ const mapStateToProps = state => ({
Message_AllowPinning: state.settings.Message_AllowPinning, Message_AllowPinning: state.settings.Message_AllowPinning,
Message_AllowStarring: state.settings.Message_AllowStarring, Message_AllowStarring: state.settings.Message_AllowStarring,
Message_Read_Receipt_Store_Users: state.settings.Message_Read_Receipt_Store_Users, 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); export default connect(mapStateToProps, null, null, { forwardRef: true })(MessageActions);

View File

@ -1,11 +1,55 @@
import lt from 'semver/functions/lt'; import lt from 'semver/functions/lt';
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord'; import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
import { Q } from '@nozbe/watermelondb';
import coerce from 'semver/functions/coerce';
import orderBy from 'lodash/orderBy'; import orderBy from 'lodash/orderBy';
import database from '../database'; import database from '../database';
import log from '../../utils/log'; import log from '../../utils/log';
import reduxStore from '../createStore'; import reduxStore from '../createStore';
import protectedFunction from './helpers/protectedFunction'; 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) => { const getUpdatedSince = (allRecords) => {
try { try {
@ -64,12 +108,13 @@ const updatePermissions = async({ update = [], remove = [], allRecords }) => {
await db.action(async() => { await db.action(async() => {
await db.batch(...batch); await db.batch(...batch);
}); });
return true;
} catch (e) { } catch (e) {
log(e); log(e);
} }
}; };
export default function() { export function getPermissions() {
return new Promise(async(resolve) => { return new Promise(async(resolve) => {
try { try {
const serverVersion = reduxStore.getState().server.version; const serverVersion = reduxStore.getState().server.version;
@ -78,17 +123,20 @@ export default function() {
const allRecords = await permissionsCollection.query().fetch(); const allRecords = await permissionsCollection.query().fetch();
// if server version is lower than 0.73.0, fetches from old api // 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 // RC 0.66.0
const result = await this.sdk.get('permissions.list'); const result = await this.sdk.get('permissions.list');
if (!result.success) { if (!result.success) {
return resolve(); return resolve();
} }
await updatePermissions({ update: result.permissions, allRecords }); const changePermissions = await updatePermissions({ update: result.permissions, allRecords });
if (changePermissions) {
setPermissions();
}
return resolve(); return resolve();
} else { } else {
const params = {}; const params = {};
const updatedSince = await getUpdatedSince(allRecords); const updatedSince = getUpdatedSince(allRecords);
if (updatedSince) { if (updatedSince) {
params.updatedSince = updatedSince; params.updatedSince = updatedSince;
} }
@ -99,7 +147,10 @@ export default function() {
return resolve(); 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(); return resolve();
} }
} catch (e) { } catch (e) {

View File

@ -32,7 +32,7 @@ import readMessages from './methods/readMessages';
import getSettings, { getLoginSettings, setSettings } from './methods/getSettings'; import getSettings, { getLoginSettings, setSettings } from './methods/getSettings';
import getRooms from './methods/getRooms'; import getRooms from './methods/getRooms';
import getPermissions from './methods/getPermissions'; import { setPermissions, getPermissions } from './methods/getPermissions';
import { getCustomEmojis, setCustomEmojis } from './methods/getCustomEmojis'; import { getCustomEmojis, setCustomEmojis } from './methods/getCustomEmojis';
import { import {
getEnterpriseModules, setEnterpriseModules, hasLicense, isOmnichannelModuleAvailable 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 THEME_PREFERENCES_KEY = 'RC_THEME_PREFERENCES_KEY';
export const CRASH_REPORT_KEY = 'RC_CRASH_REPORT_KEY'; export const CRASH_REPORT_KEY = 'RC_CRASH_REPORT_KEY';
export const ANALYTICS_EVENTS_KEY = 'RC_ANALYTICS_EVENTS_KEY'; export const ANALYTICS_EVENTS_KEY = 'RC_ANALYTICS_EVENTS_KEY';
const returnAnArray = obj => obj || [];
const MIN_ROCKETCHAT_VERSION = '0.70.0'; const MIN_ROCKETCHAT_VERSION = '0.70.0';
const STATUSES = ['offline', 'online', 'away', 'busy']; const STATUSES = ['offline', 'online', 'away', 'busy'];
@ -740,6 +739,7 @@ const RocketChat = {
getLoginSettings, getLoginSettings,
setSettings, setSettings,
getPermissions, getPermissions,
setPermissions,
getCustomEmojis, getCustomEmojis,
setCustomEmojis, setCustomEmojis,
getEnterpriseModules, getEnterpriseModules,
@ -1172,10 +1172,13 @@ const RocketChat = {
// RC 0.65.0 // RC 0.65.0
return this.sdk.get(`${ this.roomTypeToApiType(type) }.roles`, { roomId }); 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) { async hasPermission(permissions, rid) {
const db = database.active; const db = database.active;
const subsCollection = db.collections.get('subscriptions'); const subsCollection = db.collections.get('subscriptions');
const permissionsCollection = db.collections.get('permissions');
let roomRoles = []; let roomRoles = [];
try { try {
// get the room from database // get the room from database
@ -1184,31 +1187,16 @@ const RocketChat = {
roomRoles = room.roles || []; roomRoles = room.roles || [];
} catch (error) { } catch (error) {
console.log('hasPermission -> Room not found'); console.log('hasPermission -> Room not found');
return permissions.reduce((result, permission) => { return permissions.map(() => false);
result[permission] = false;
return result;
}, {});
} }
// get permissions from database
try { try {
const permissionsFiltered = await permissionsCollection.query(Q.where('id', Q.oneOf(permissions))).fetch();
const shareUser = reduxStore.getState().share.user; const shareUser = reduxStore.getState().share.user;
const loginUser = reduxStore.getState().login.user; const loginUser = reduxStore.getState().login.user;
// get user roles on the server from redux // get user roles on the server from redux
const userRoles = (shareUser?.roles || loginUser?.roles) || []; const userRoles = (shareUser?.roles || loginUser?.roles) || [];
// merge both roles
const mergedRoles = [...new Set([...roomRoles, ...userRoles])]; const mergedRoles = [...new Set([...roomRoles, ...userRoles])];
return permissions.map(permission => permission.some(r => mergedRoles.includes(r) ?? false));
// 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;
}, {});
} catch (e) { } catch (e) {
log(e); log(e);
} }
@ -1438,17 +1426,15 @@ const RocketChat = {
query, count, offset, sort query, count, offset, sort
}); });
}, },
async canAutoTranslate() { canAutoTranslate() {
const db = database.active;
try { try {
const AutoTranslate_Enabled = reduxStore.getState().settings && reduxStore.getState().settings.AutoTranslate_Enabled; const { AutoTranslate_Enabled } = reduxStore.getState().settings;
if (!AutoTranslate_Enabled) { if (!AutoTranslate_Enabled) {
return false; return false;
} }
const permissionsCollection = db.collections.get('permissions'); const autoTranslatePermission = reduxStore.getState().permissions['auto-translate'];
const autoTranslatePermission = await permissionsCollection.find('auto-translate'); const userRoles = (reduxStore.getState().login?.user?.roles) ?? [];
const userRoles = (reduxStore.getState().login.user && reduxStore.getState().login.user.roles) || []; return autoTranslatePermission?.some(role => userRoles.includes(role));
return autoTranslatePermission.roles.some(role => userRoles.includes(role));
} catch (e) { } catch (e) {
log(e); log(e);
return false; return false;

View File

@ -18,6 +18,7 @@ import inviteLinks from './inviteLinks';
import createDiscussion from './createDiscussion'; import createDiscussion from './createDiscussion';
import enterpriseModules from './enterpriseModules'; import enterpriseModules from './enterpriseModules';
import encryption from './encryption'; import encryption from './encryption';
import permissions from './permissions';
import inquiry from '../ee/omnichannel/reducers/inquiry'; import inquiry from '../ee/omnichannel/reducers/inquiry';
@ -41,5 +42,6 @@ export default combineReducers({
createDiscussion, createDiscussion,
inquiry, inquiry,
enterpriseModules, enterpriseModules,
encryption encryption,
permissions
}); });

View File

@ -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;
}
}

View File

@ -124,6 +124,7 @@ const handleSelectServer = function* handleSelectServer({ server, version, fetch
// and block the selectServerSuccess raising multiples errors // and block the selectServerSuccess raising multiples errors
RocketChat.setSettings(); RocketChat.setSettings();
RocketChat.setCustomEmojis(); RocketChat.setCustomEmojis();
RocketChat.setPermissions();
RocketChat.setEnterpriseModules(); RocketChat.setEnterpriseModules();
let serverInfo; let serverInfo;

View File

@ -1,13 +1,11 @@
import RocketChat from '../lib/rocketchat'; import RocketChat from '../lib/rocketchat';
import reduxStore from '../lib/createStore';
const canPost = async({ rid }) => { const canPostReadOnly = async({ rid }) => {
try { // TODO: this is not reactive. If this permission changes, the component won't be updated
const permission = await RocketChat.hasPermission(['post-readonly'], rid); const postReadOnlyPermission = reduxStore.getState().permissions['post-readonly'];
return permission && permission['post-readonly']; const permission = await RocketChat.hasPermission([postReadOnlyPermission], rid);
} catch { return permission[0];
// do nothing
}
return false;
}; };
const isMuted = (room, user) => room && room.muted && room.muted.find && !!room.muted.find(m => m === user.username); 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; return true;
} }
if (room?.ro) { if (room?.ro) {
const allowPost = await canPost(room); const allowPost = await canPostReadOnly(room);
if (allowPost) { if (allowPost) {
return false; return false;
} }

View File

@ -53,7 +53,15 @@ class RoomActionsView extends React.Component {
closeRoom: PropTypes.func, closeRoom: PropTypes.func,
theme: PropTypes.string, theme: PropTypes.string,
fontScale: PropTypes.number, 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) { constructor(props) {
@ -118,7 +126,7 @@ class RoomActionsView extends React.Component {
this.updateRoomMember(); this.updateRoomMember();
} }
const canAutoTranslate = await RocketChat.canAutoTranslate(); const canAutoTranslate = RocketChat.canAutoTranslate();
this.setState({ canAutoTranslate }); this.setState({ canAutoTranslate });
this.canAddUser(); this.canAddUser();
@ -159,60 +167,62 @@ class RoomActionsView extends React.Component {
canAddUser = async() => { canAddUser = async() => {
const { room, joined } = this.state; const { room, joined } = this.state;
const { addUserToJoinedRoomPermission, addUserToAnyCRoomPermission, addUserToAnyPRoomPermission } = this.props;
const { rid, t } = room; const { rid, t } = room;
let canAdd = false; let canAddUser = false;
const userInRoom = joined; 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[0]) {
if (userInRoom && permissions['add-user-to-joined-room']) { canAddUser = true;
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;
}
} }
this.setState({ canAddUser: canAdd }); if (t === 'c' && permissions[1]) {
canAddUser = true;
}
if (t === 'p' && permissions[2]) {
canAddUser = true;
}
this.setState({ canAddUser });
} }
canInviteUser = async() => { canInviteUser = async() => {
const { room } = this.state; const { room } = this.state;
const { createInviteLinksPermission } = this.props;
const { rid } = room; 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 }); this.setState({ canInviteUser });
} }
canEdit = async() => { canEdit = async() => {
const { room } = this.state; const { room } = this.state;
const { editRoomPermission } = this.props;
const { rid } = room; 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 }); this.setState({ canEdit });
} }
canToggleEncryption = async() => { canToggleEncryption = async() => {
const { room } = this.state; const { room } = this.state;
const { toggleRoomE2EEncryptionPermission } = this.props;
const { rid } = room; 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 }); this.setState({ canToggleEncryption });
} }
canViewMembers = async() => { canViewMembers = async() => {
const { room } = this.state; const { room } = this.state;
const { viewBroadcastMemberListPermission } = this.props;
const { rid, t, broadcast } = room; const { rid, t, broadcast } = room;
if (broadcast) { if (broadcast) {
const viewBroadcastMemberListPermission = 'view-broadcast-member-list';
const permissions = await RocketChat.hasPermission([viewBroadcastMemberListPermission], rid); const permissions = await RocketChat.hasPermission([viewBroadcastMemberListPermission], rid);
if (!permissions[viewBroadcastMemberListPermission]) { if (!permissions[0]) {
return false; return false;
} }
} }
@ -226,16 +236,10 @@ class RoomActionsView extends React.Component {
canForwardGuest = async() => { canForwardGuest = async() => {
const { room } = this.state; const { room } = this.state;
const { transferLivechatGuestPermission } = this.props;
const { rid } = room; const { rid } = room;
let result = true; const permissions = await RocketChat.hasPermission([transferLivechatGuestPermission], rid);
this.setState({ canForwardGuest: permissions[0] });
const transferLivechatGuest = 'transfer-livechat-guest';
const permissions = await RocketChat.hasPermission([transferLivechatGuest], rid);
if (!permissions[transferLivechatGuest]) {
result = false;
}
this.setState({ canForwardGuest: result });
} }
canReturnQueue = async() => { canReturnQueue = async() => {
@ -866,7 +870,15 @@ class RoomActionsView extends React.Component {
const mapStateToProps = state => ({ const mapStateToProps = state => ({
jitsiEnabled: state.settings.Jitsi_Enabled || false, jitsiEnabled: state.settings.Jitsi_Enabled || false,
encryptionEnabled: state.encryption.enabled, 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 => ({ const mapDispatchToProps = dispatch => ({

View File

@ -44,14 +44,6 @@ const PERMISSION_ARCHIVE = 'archive-room';
const PERMISSION_UNARCHIVE = 'unarchive-room'; const PERMISSION_UNARCHIVE = 'unarchive-room';
const PERMISSION_DELETE_C = 'delete-c'; const PERMISSION_DELETE_C = 'delete-c';
const PERMISSION_DELETE_P = 'delete-p'; 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 { class RoomInfoEditView extends React.Component {
static navigationOptions = () => ({ static navigationOptions = () => ({
@ -63,7 +55,13 @@ class RoomInfoEditView extends React.Component {
deleteRoom: PropTypes.func, deleteRoom: PropTypes.func,
serverVersion: PropTypes.string, serverVersion: PropTypes.string,
encryptionEnabled: PropTypes.bool, 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) { constructor(props) {
@ -108,7 +106,15 @@ class RoomInfoEditView extends React.Component {
// eslint-disable-next-line react/sort-comp // eslint-disable-next-line react/sort-comp
loadRoom = async() => { loadRoom = async() => {
const { route } = this.props; const {
route,
setReadOnlyPermission,
setReactWhenReadOnlyPermission,
archiveRoomPermission,
unarchiveRoomPermission,
deleteCPermission,
deletePPermission
} = this.props;
const rid = route.params?.rid; const rid = route.params?.rid;
if (!rid) { if (!rid) {
return; return;
@ -123,8 +129,25 @@ class RoomInfoEditView extends React.Component {
this.init(this.room); this.init(this.room);
}); });
const permissions = await RocketChat.hasPermission(PERMISSIONS_ARRAY, rid); const result = await RocketChat.hasPermission([
this.setState({ permissions }); 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) { } catch (e) {
log(e); log(e);
} }
@ -667,7 +690,13 @@ class RoomInfoEditView extends React.Component {
const mapStateToProps = state => ({ const mapStateToProps = state => ({
serverVersion: state.server.version, 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 => ({ const mapDispatchToProps = dispatch => ({

View File

@ -31,7 +31,6 @@ import SafeAreaView from '../../containers/SafeAreaView';
import { goRoom } from '../../utils/goRoom'; import { goRoom } from '../../utils/goRoom';
import Navigation from '../../lib/Navigation'; import Navigation from '../../lib/Navigation';
const PERMISSION_EDIT_ROOM = 'edit-room';
const getRoomTitle = (room, type, name, username, statusText, theme) => (type === 'd' const getRoomTitle = (room, type, name, username, statusText, theme) => (type === 'd'
? ( ? (
<> <>
@ -55,7 +54,8 @@ class RoomInfoView extends React.Component {
rooms: PropTypes.array, rooms: PropTypes.array,
theme: PropTypes.string, theme: PropTypes.string,
isMasterDetail: PropTypes.bool, isMasterDetail: PropTypes.bool,
jitsiEnabled: PropTypes.bool jitsiEnabled: PropTypes.bool,
editRoomPermission: PropTypes.array
} }
constructor(props) { constructor(props) {
@ -193,7 +193,7 @@ class RoomInfoView extends React.Component {
loadRoom = async() => { loadRoom = async() => {
const { room: roomState } = this.state; const { room: roomState } = this.state;
const { route } = this.props; const { route, editRoomPermission } = this.props;
let room = route.params?.room; let room = route.params?.room;
if (room && room.observe) { if (room && room.observe) {
this.roomObservable = 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); const permissions = await RocketChat.hasPermission([editRoomPermission], room.rid);
if (permissions[PERMISSION_EDIT_ROOM] && !room.prid) { if (permissions[0] && !room.prid) {
this.setState({ showEdit: true }, () => this.setHeader()); this.setState({ showEdit: true }, () => this.setHeader());
} }
} }
@ -369,7 +369,8 @@ class RoomInfoView extends React.Component {
const mapStateToProps = state => ({ const mapStateToProps = state => ({
rooms: state.room.rooms, rooms: state.room.rooms,
isMasterDetail: state.app.isMasterDetail, 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)); export default connect(mapStateToProps)(withTheme(RoomInfoView));

View File

@ -28,6 +28,12 @@ import { goRoom } from '../../utils/goRoom';
const PAGE_SIZE = 25; 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 { class RoomMembersView extends React.Component {
static propTypes = { static propTypes = {
navigation: PropTypes.object, navigation: PropTypes.object,
@ -43,7 +49,12 @@ class RoomMembersView extends React.Component {
showActionSheet: PropTypes.func, showActionSheet: PropTypes.func,
theme: PropTypes.string, theme: PropTypes.string,
isMasterDetail: PropTypes.bool, 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) { constructor(props) {
@ -81,7 +92,20 @@ class RoomMembersView extends React.Component {
this.fetchMembers(); this.fetchMembers();
const { room } = this.state; 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); const hasSinglePermission = Object.values(this.permissions).some(p => !!p);
if (hasSinglePermission) { if (hasSinglePermission) {
@ -452,7 +476,12 @@ const mapStateToProps = state => ({
baseUrl: state.server.server, baseUrl: state.server.server,
user: getUserSelector(state), user: getUserSelector(state),
isMasterDetail: state.app.isMasterDetail, 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))); export default connect(mapStateToProps)(withActionSheet(withTheme(RoomMembersView)));

View File

@ -436,10 +436,7 @@ class RoomView extends React.Component {
} }
} }
// We run `canAutoTranslate` again in order to refetch auto translate permission const canAutoTranslate = RocketChat.canAutoTranslate();
// in case of a missing connection or poor connection on room open
const canAutoTranslate = await RocketChat.canAutoTranslate();
const member = await this.getRoomMember(); const member = await this.getRoomMember();
this.setState({ canAutoTranslate, member, loading: false }); this.setState({ canAutoTranslate, member, loading: false });

View File

@ -4,19 +4,16 @@ import {
ScrollView, Text, View, TouchableWithoutFeedback ScrollView, Text, View, TouchableWithoutFeedback
} from 'react-native'; } from 'react-native';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { Q } from '@nozbe/watermelondb';
import isEqual from 'react-fast-compare'; import isEqual from 'react-fast-compare';
import Avatar from '../../containers/Avatar'; import Avatar from '../../containers/Avatar';
import Status from '../../containers/Status/Status'; import Status from '../../containers/Status/Status';
import log, { logEvent, events } from '../../utils/log'; import { logEvent, events } from '../../utils/log';
import I18n from '../../i18n'; import I18n from '../../i18n';
import scrollPersistTaps from '../../utils/scrollPersistTaps'; import scrollPersistTaps from '../../utils/scrollPersistTaps';
import { CustomIcon } from '../../lib/Icons'; import { CustomIcon } from '../../lib/Icons';
import styles from './styles'; import styles from './styles';
import SidebarItem from './SidebarItem'; import SidebarItem from './SidebarItem';
import { themes } from '../../constants/colors'; import { themes } from '../../constants/colors';
import database from '../../lib/database';
import { withTheme } from '../../theme'; import { withTheme } from '../../theme';
import { getUserSelector } from '../../selectors/login'; import { getUserSelector } from '../../selectors/login';
import SafeAreaView from '../../containers/SafeAreaView'; import SafeAreaView from '../../containers/SafeAreaView';
@ -27,13 +24,6 @@ Separator.propTypes = {
theme: PropTypes.string theme: PropTypes.string
}; };
const permissions = [
'view-statistics',
'view-room-administration',
'view-user-administration',
'view-privileged-setting'
];
class Sidebar extends Component { class Sidebar extends Component {
static propTypes = { static propTypes = {
baseUrl: PropTypes.string, baseUrl: PropTypes.string,
@ -45,32 +35,24 @@ class Sidebar extends Component {
loadingServer: PropTypes.bool, loadingServer: PropTypes.bool,
useRealName: PropTypes.bool, useRealName: PropTypes.bool,
allowStatusMessage: 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) { constructor(props) {
super(props); super(props);
this.state = { this.state = {
showStatus: false, showStatus: false
isAdmin: false
}; };
} }
componentDidMount() {
this.setIsAdmin();
}
UNSAFE_componentWillReceiveProps(nextProps) {
const { loadingServer } = this.props;
if (loadingServer && nextProps.loadingServer !== loadingServer) {
this.setIsAdmin();
}
}
shouldComponentUpdate(nextProps, nextState) { shouldComponentUpdate(nextProps, nextState) {
const { showStatus, isAdmin } = this.state; const { showStatus, isAdmin } = this.state;
const { const {
Site_Name, user, baseUrl, state, isMasterDetail, useRealName, theme Site_Name, user, baseUrl, state, isMasterDetail, useRealName, theme, viewStatisticsPermission, viewRoomAdministrationPermission, viewUserAdministrationPermission, viewPrivilegedSettingPermission
} = this.props; } = this.props;
// Drawer navigation state // Drawer navigation state
if (state?.index !== nextProps.state?.index) { if (state?.index !== nextProps.state?.index) {
@ -103,25 +85,42 @@ class Sidebar extends Component {
if (nextState.isAdmin !== isAdmin) { if (nextState.isAdmin !== isAdmin) {
return true; 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; return false;
} }
async setIsAdmin() {
const db = database.active; getIsAdmin() {
const { user } = this.props; const {
user, viewStatisticsPermission, viewRoomAdministrationPermission, viewUserAdministrationPermission, viewPrivilegedSettingPermission
} = this.props;
const { roles } = user; const { roles } = user;
try { const allPermissions = [viewStatisticsPermission, viewRoomAdministrationPermission, viewUserAdministrationPermission, viewPrivilegedSettingPermission];
if (roles) { let isAdmin = false;
const permissionsCollection = db.collections.get('permissions');
const permissionsFiltered = await permissionsCollection.query(Q.where('id', Q.oneOf(permissions))).fetch(); if (roles) {
const isAdmin = permissionsFiltered.reduce((result, permission) => ( isAdmin = allPermissions.reduce((result, permission) => {
result || permission.roles.some(r => roles.indexOf(r) !== -1)), if (permission) {
false); return (
this.setState({ isAdmin }); result || permission.some(r => roles.indexOf(r) !== -1)
} );
} catch (e) { }
log(e); return result;
},
false);
} }
return isAdmin;
} }
sidebarNavigate = (route) => { sidebarNavigate = (route) => {
@ -143,9 +142,8 @@ class Sidebar extends Component {
} }
renderAdmin = () => { renderAdmin = () => {
const { isAdmin } = this.state;
const { theme, isMasterDetail } = this.props; const { theme, isMasterDetail } = this.props;
if (!isAdmin) { if (!this.getIsAdmin()) {
return null; return null;
} }
const routeName = isMasterDetail ? 'AdminPanelView' : 'AdminPanelStackNavigator'; const routeName = isMasterDetail ? 'AdminPanelView' : 'AdminPanelStackNavigator';
@ -275,7 +273,11 @@ const mapStateToProps = state => ({
loadingServer: state.server.loading, loadingServer: state.server.loading,
useRealName: state.settings.UI_Use_Real_Name, useRealName: state.settings.UI_Use_Real_Name,
allowStatusMessage: state.settings.Accounts_AllowUserStatusMessageChange, 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)); export default connect(mapStateToProps)(withTheme(Sidebar));