[NEW] Permission for uploading files (#3505)

Co-authored-by: Diego Mello <diegolmello@gmail.com>
This commit is contained in:
Reinaldo Neto 2021-12-13 13:27:01 -03:00 committed by GitHub
parent 404c7cff07
commit a1cee02fb0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 128 additions and 44 deletions

View File

@ -13,6 +13,7 @@ import { events, logEvent } from '../../utils/log';
interface IMessageBoxRecordAudioProps { interface IMessageBoxRecordAudioProps {
theme: string; theme: string;
permissionToUpload: boolean;
recordingCallback: Function; recordingCallback: Function;
onFinish: Function; onFinish: Function;
} }
@ -192,9 +193,11 @@ export default class RecordAudio extends React.PureComponent<IMessageBoxRecordAu
}; };
render() { render() {
const { theme } = this.props; const { theme, permissionToUpload } = this.props;
const { isRecording, isRecorderActive } = this.state; const { isRecording, isRecorderActive } = this.state;
if (!permissionToUpload) {
return null;
}
if (!isRecording && !isRecorderActive) { if (!isRecording && !isRecorderActive) {
return ( return (
<BorderlessButton <BorderlessButton

View File

@ -109,6 +109,8 @@ interface IMessageBoxProps {
sharing: boolean; sharing: boolean;
isActionsEnabled: boolean; isActionsEnabled: boolean;
usedCannedResponse: string; usedCannedResponse: string;
uploadFilePermission: string[];
serverVersion: string;
} }
interface IMessageBoxState { interface IMessageBoxState {
@ -124,6 +126,7 @@ interface IMessageBoxState {
}; };
tshow: boolean; tshow: boolean;
mentionLoading: boolean; mentionLoading: boolean;
permissionToUpload: boolean;
} }
class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> { class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
@ -179,41 +182,13 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
showCommandPreview: false, showCommandPreview: false,
command: {}, command: {},
tshow: false, tshow: false,
mentionLoading: false mentionLoading: false,
permissionToUpload: true
}; };
this.text = ''; this.text = '';
this.selection = { start: 0, end: 0 }; this.selection = { start: 0, end: 0 };
this.focused = false; this.focused = false;
// MessageBox Actions
this.options = [
{
title: I18n.t('Take_a_photo'),
icon: 'camera-photo',
onPress: this.takePhoto
},
{
title: I18n.t('Take_a_video'),
icon: 'camera',
onPress: this.takeVideo
},
{
title: I18n.t('Choose_from_library'),
icon: 'image',
onPress: this.chooseFromLibrary
},
{
title: I18n.t('Choose_file'),
icon: 'attach',
onPress: this.chooseFile
},
{
title: I18n.t('Create_Discussion'),
icon: 'discussions',
onPress: this.createDiscussion
}
];
const libPickerLabels = { const libPickerLabels = {
cropperChooseText: I18n.t('Choose'), cropperChooseText: I18n.t('Choose'),
cropperCancelText: I18n.t('Cancel'), cropperCancelText: I18n.t('Cancel'),
@ -277,6 +252,8 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
this.onChangeText(usedCannedResponse); this.onChangeText(usedCannedResponse);
} }
this.setOptions();
this.unsubscribeFocus = navigation.addListener('focus', () => { this.unsubscribeFocus = navigation.addListener('focus', () => {
// didFocus // didFocus
// We should wait pushed views be dismissed // We should wait pushed views be dismissed
@ -321,10 +298,20 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
} }
} }
shouldComponentUpdate(nextProps: any, nextState: any) { shouldComponentUpdate(nextProps: IMessageBoxProps, nextState: IMessageBoxState) {
const { showEmojiKeyboard, showSend, recording, mentions, commandPreview, tshow, mentionLoading, trackingType } = this.state; const {
showEmojiKeyboard,
showSend,
recording,
mentions,
commandPreview,
tshow,
mentionLoading,
trackingType,
permissionToUpload
} = this.state;
const { roomType, replying, editing, isFocused, message, theme, usedCannedResponse } = this.props; const { roomType, replying, editing, isFocused, message, theme, usedCannedResponse, uploadFilePermission } = this.props;
if (nextProps.theme !== theme) { if (nextProps.theme !== theme) {
return true; return true;
} }
@ -358,6 +345,9 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
if (nextState.tshow !== tshow) { if (nextState.tshow !== tshow) {
return true; return true;
} }
if (nextState.permissionToUpload !== permissionToUpload) {
return true;
}
if (!dequal(nextState.mentions, mentions)) { if (!dequal(nextState.mentions, mentions)) {
return true; return true;
} }
@ -367,12 +357,22 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
if (!dequal(nextProps.message?.id, message?.id)) { if (!dequal(nextProps.message?.id, message?.id)) {
return true; return true;
} }
if (!dequal(nextProps.uploadFilePermission, uploadFilePermission)) {
return true;
}
if (nextProps.usedCannedResponse !== usedCannedResponse) { if (nextProps.usedCannedResponse !== usedCannedResponse) {
return true; return true;
} }
return false; return false;
} }
componentDidUpdate(prevProps: IMessageBoxProps) {
const { uploadFilePermission } = this.props;
if (!dequal(prevProps.uploadFilePermission, uploadFilePermission)) {
this.setOptions();
}
}
componentWillUnmount() { componentWillUnmount() {
console.countReset(`${this.constructor.name}.render calls`); console.countReset(`${this.constructor.name}.render calls`);
if (this.onChangeText && this.onChangeText.stop) { if (this.onChangeText && this.onChangeText.stop) {
@ -404,6 +404,19 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
} }
} }
setOptions = async () => {
const { uploadFilePermission, rid } = this.props;
// Servers older than 4.2
if (!uploadFilePermission) {
this.setState({ permissionToUpload: true });
return;
}
const permissionToUpload = await RocketChat.hasPermission([uploadFilePermission], rid);
this.setState({ permissionToUpload: permissionToUpload[0] });
};
onChangeText: any = (text: string): void => { onChangeText: any = (text: string): void => {
const isTextEmpty = text.length === 0; const isTextEmpty = text.length === 0;
this.setShowSend(!isTextEmpty); this.setShowSend(!isTextEmpty);
@ -666,8 +679,9 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
}; };
canUploadFile = (file: any) => { canUploadFile = (file: any) => {
const { permissionToUpload } = this.state;
const { FileUpload_MediaTypeWhiteList, FileUpload_MaxFileSize } = this.props; const { FileUpload_MediaTypeWhiteList, FileUpload_MaxFileSize } = this.props;
const result = canUploadFile(file, FileUpload_MediaTypeWhiteList, FileUpload_MaxFileSize); const result = canUploadFile(file, FileUpload_MediaTypeWhiteList, FileUpload_MaxFileSize, permissionToUpload);
if (result.success) { if (result.success) {
return true; return true;
} }
@ -766,8 +780,41 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
showMessageBoxActions = () => { showMessageBoxActions = () => {
logEvent(events.ROOM_SHOW_BOX_ACTIONS); logEvent(events.ROOM_SHOW_BOX_ACTIONS);
const { permissionToUpload } = this.state;
const { showActionSheet } = this.props; const { showActionSheet } = this.props;
showActionSheet({ options: this.options });
const options = [];
if (permissionToUpload) {
options.push(
{
title: I18n.t('Take_a_photo'),
icon: 'camera-photo',
onPress: this.takePhoto
},
{
title: I18n.t('Take_a_video'),
icon: 'camera',
onPress: this.takeVideo
},
{
title: I18n.t('Choose_from_library'),
icon: 'image',
onPress: this.chooseFromLibrary
},
{
title: I18n.t('Choose_file'),
icon: 'attach',
onPress: this.chooseFile
}
);
}
options.push({
title: I18n.t('Create_Discussion'),
icon: 'discussions',
onPress: this.createDiscussion
});
showActionSheet({ options });
}; };
editCancel = () => { editCancel = () => {
@ -968,8 +1015,17 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
}; };
renderContent = () => { renderContent = () => {
const { recording, showEmojiKeyboard, showSend, mentions, trackingType, commandPreview, showCommandPreview, mentionLoading } = const {
this.state; recording,
showEmojiKeyboard,
showSend,
mentions,
trackingType,
commandPreview,
showCommandPreview,
mentionLoading,
permissionToUpload
} = this.state;
const { const {
editing, editing,
message, message,
@ -995,7 +1051,12 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
const recordAudio = const recordAudio =
showSend || !Message_AudioRecorderEnabled ? null : ( showSend || !Message_AudioRecorderEnabled ? null : (
<RecordAudio theme={theme} recordingCallback={this.recordingCallback} onFinish={this.finishAudioMessage} /> <RecordAudio
theme={theme}
recordingCallback={this.recordingCallback}
onFinish={this.finishAudioMessage}
permissionToUpload={permissionToUpload}
/>
); );
const commandsPreviewAndMentions = !recording ? ( const commandsPreviewAndMentions = !recording ? (
@ -1117,7 +1178,8 @@ const mapStateToProps = (state: any) => ({
user: getUserSelector(state), user: getUserSelector(state),
FileUpload_MediaTypeWhiteList: state.settings.FileUpload_MediaTypeWhiteList, FileUpload_MediaTypeWhiteList: state.settings.FileUpload_MediaTypeWhiteList,
FileUpload_MaxFileSize: state.settings.FileUpload_MaxFileSize, FileUpload_MaxFileSize: state.settings.FileUpload_MaxFileSize,
Message_AudioRecorderEnabled: state.settings.Message_AudioRecorderEnabled Message_AudioRecorderEnabled: state.settings.Message_AudioRecorderEnabled,
uploadFilePermission: state.permissions['mobile-upload-file']
}); });
const dispatchToProps = { const dispatchToProps = {

View File

@ -21,6 +21,7 @@
"error-save-video": "Error while saving video", "error-save-video": "Error while saving video",
"error-field-unavailable": "{{field}} is already in use :(", "error-field-unavailable": "{{field}} is already in use :(",
"error-file-too-large": "File is too large", "error-file-too-large": "File is too large",
"error-not-permission-to-upload-file": "You don't have permission to upload files",
"error-importer-not-defined": "The importer was not defined correctly, it is missing the Import class.", "error-importer-not-defined": "The importer was not defined correctly, it is missing the Import class.",
"error-input-is-not-a-valid-field": "{{input}} is not a valid {{field}}", "error-input-is-not-a-valid-field": "{{input}} is not a valid {{field}}",
"error-invalid-actionlink": "Invalid action link", "error-invalid-actionlink": "Invalid action link",

View File

@ -55,7 +55,8 @@ const PERMISSIONS = [
'convert-team', 'convert-team',
'edit-omnichannel-contact', 'edit-omnichannel-contact',
'edit-livechat-room-customfields', 'edit-livechat-room-customfields',
'view-canned-responses' 'view-canned-responses',
'mobile-upload-file'
]; ];
export async function setPermissions() { export async function setPermissions() {

View File

@ -1,10 +1,13 @@
export const canUploadFile = (file, allowList, maxFileSize) => { export const canUploadFile = (file, allowList, maxFileSize, permissionToUploadFile) => {
if (!(file && file.path)) { if (!(file && file.path)) {
return { success: true }; return { success: true };
} }
if (maxFileSize > -1 && file.size > maxFileSize) { if (maxFileSize > -1 && file.size > maxFileSize) {
return { success: false, error: 'error-file-too-large' }; return { success: false, error: 'error-file-too-large' };
} }
if (!permissionToUploadFile) {
return { success: false, error: 'error-not-permission-to-upload-file' };
}
// if white list is empty, all media types are enabled // if white list is empty, all media types are enabled
if (!allowList || allowList === '*') { if (!allowList || allowList === '*') {
return { success: true }; return { success: true };

View File

@ -4,6 +4,7 @@ import { RouteProp } from '@react-navigation/native';
import { NativeModules, Text, View } from 'react-native'; import { NativeModules, Text, View } from 'react-native';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import ShareExtension from 'rn-extensions-share'; import ShareExtension from 'rn-extensions-share';
import { Q } from '@nozbe/watermelondb';
import { InsideStackParamList } from '../../stacks/types'; import { InsideStackParamList } from '../../stacks/types';
import { themes } from '../../constants/colors'; import { themes } from '../../constants/colors';
@ -141,6 +142,17 @@ class ShareView extends Component<IShareViewProps, IShareViewState> {
} }
}; };
getPermissionMobileUpload = async () => {
const { room } = this.state;
const db = database.active;
const permissionsCollection = db.get('permissions');
const uploadFilePermissionFetch = await permissionsCollection.query(Q.where('id', Q.like('mobile-upload-file'))).fetch();
const uploadFilePermission = uploadFilePermissionFetch[0]?.roles;
const permissionToUpload = await RocketChat.hasPermission([uploadFilePermission], room.rid);
// uploadFilePermission as undefined is considered that there isn't this permission, so all can upload file.
return !uploadFilePermission || permissionToUpload[0];
};
getReadOnly = async () => { getReadOnly = async () => {
const { room } = this.state; const { room } = this.state;
const { user } = this.props; const { user } = this.props;
@ -150,10 +162,12 @@ class ShareView extends Component<IShareViewProps, IShareViewState> {
getAttachments = async () => { getAttachments = async () => {
const { mediaAllowList, maxFileSize } = this.state; const { mediaAllowList, maxFileSize } = this.state;
const permissionToUploadFile = await this.getPermissionMobileUpload();
const items = await Promise.all( const items = await Promise.all(
this.files.map(async item => { this.files.map(async item => {
// Check server settings // Check server settings
const { success: canUpload, error } = canUploadFile(item, mediaAllowList, maxFileSize); const { success: canUpload, error } = canUploadFile(item, mediaAllowList, maxFileSize, permissionToUploadFile);
item.canUpload = canUpload; item.canUpload = canUpload;
item.error = error; item.error = error;