Unverified Commit a1cee02f authored by Reinaldo Neto's avatar Reinaldo Neto Committed by GitHub
Browse files

[NEW] Permission for uploading files (#3505)


Co-authored-by: default avatarDiego Mello <diegolmello@gmail.com>
parent 404c7cff
......@@ -13,6 +13,7 @@ import { events, logEvent } from '../../utils/log';
interface IMessageBoxRecordAudioProps {
theme: string;
permissionToUpload: boolean;
recordingCallback: Function;
onFinish: Function;
}
......@@ -192,9 +193,11 @@ export default class RecordAudio extends React.PureComponent<IMessageBoxRecordAu
};
render() {
const { theme } = this.props;
const { theme, permissionToUpload } = this.props;
const { isRecording, isRecorderActive } = this.state;
if (!permissionToUpload) {
return null;
}
if (!isRecording && !isRecorderActive) {
return (
<BorderlessButton
......
......@@ -109,6 +109,8 @@ interface IMessageBoxProps {
sharing: boolean;
isActionsEnabled: boolean;
usedCannedResponse: string;
uploadFilePermission: string[];
serverVersion: string;
}
interface IMessageBoxState {
......@@ -124,6 +126,7 @@ interface IMessageBoxState {
};
tshow: boolean;
mentionLoading: boolean;
permissionToUpload: boolean;
}
class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
......@@ -179,41 +182,13 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
showCommandPreview: false,
command: {},
tshow: false,
mentionLoading: false
mentionLoading: false,
permissionToUpload: true
};
this.text = '';
this.selection = { start: 0, end: 0 };
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 = {
cropperChooseText: I18n.t('Choose'),
cropperCancelText: I18n.t('Cancel'),
......@@ -277,6 +252,8 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
this.onChangeText(usedCannedResponse);
}
this.setOptions();
this.unsubscribeFocus = navigation.addListener('focus', () => {
// didFocus
// We should wait pushed views be dismissed
......@@ -321,10 +298,20 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
}
}
shouldComponentUpdate(nextProps: any, nextState: any) {
const { showEmojiKeyboard, showSend, recording, mentions, commandPreview, tshow, mentionLoading, trackingType } = this.state;
const { roomType, replying, editing, isFocused, message, theme, usedCannedResponse } = this.props;
shouldComponentUpdate(nextProps: IMessageBoxProps, nextState: IMessageBoxState) {
const {
showEmojiKeyboard,
showSend,
recording,
mentions,
commandPreview,
tshow,
mentionLoading,
trackingType,
permissionToUpload
} = this.state;
const { roomType, replying, editing, isFocused, message, theme, usedCannedResponse, uploadFilePermission } = this.props;
if (nextProps.theme !== theme) {
return true;
}
......@@ -358,6 +345,9 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
if (nextState.tshow !== tshow) {
return true;
}
if (nextState.permissionToUpload !== permissionToUpload) {
return true;
}
if (!dequal(nextState.mentions, mentions)) {
return true;
}
......@@ -367,12 +357,22 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
if (!dequal(nextProps.message?.id, message?.id)) {
return true;
}
if (!dequal(nextProps.uploadFilePermission, uploadFilePermission)) {
return true;
}
if (nextProps.usedCannedResponse !== usedCannedResponse) {
return true;
}
return false;
}
componentDidUpdate(prevProps: IMessageBoxProps) {
const { uploadFilePermission } = this.props;
if (!dequal(prevProps.uploadFilePermission, uploadFilePermission)) {
this.setOptions();
}
}
componentWillUnmount() {
console.countReset(`${this.constructor.name}.render calls`);
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 => {
const isTextEmpty = text.length === 0;
this.setShowSend(!isTextEmpty);
......@@ -666,8 +679,9 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
};
canUploadFile = (file: any) => {
const { permissionToUpload } = this.state;
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) {
return true;
}
......@@ -766,8 +780,41 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
showMessageBoxActions = () => {
logEvent(events.ROOM_SHOW_BOX_ACTIONS);
const { permissionToUpload } = this.state;
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 = () => {
......@@ -968,8 +1015,17 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
};
renderContent = () => {
const { recording, showEmojiKeyboard, showSend, mentions, trackingType, commandPreview, showCommandPreview, mentionLoading } =
this.state;
const {
recording,
showEmojiKeyboard,
showSend,
mentions,
trackingType,
commandPreview,
showCommandPreview,
mentionLoading,
permissionToUpload
} = this.state;
const {
editing,
message,
......@@ -995,7 +1051,12 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
const recordAudio =
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 ? (
......@@ -1117,7 +1178,8 @@ const mapStateToProps = (state: any) => ({
user: getUserSelector(state),
FileUpload_MediaTypeWhiteList: state.settings.FileUpload_MediaTypeWhiteList,
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 = {
......
......@@ -21,6 +21,7 @@
"error-save-video": "Error while saving video",
"error-field-unavailable": "{{field}} is already in use :(",
"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-input-is-not-a-valid-field": "{{input}} is not a valid {{field}}",
"error-invalid-actionlink": "Invalid action link",
......
......@@ -55,7 +55,8 @@ const PERMISSIONS = [
'convert-team',
'edit-omnichannel-contact',
'edit-livechat-room-customfields',
'view-canned-responses'
'view-canned-responses',
'mobile-upload-file'
];
export async function setPermissions() {
......
export const canUploadFile = (file, allowList, maxFileSize) => {
export const canUploadFile = (file, allowList, maxFileSize, permissionToUploadFile) => {
if (!(file && file.path)) {
return { success: true };
}
if (maxFileSize > -1 && file.size > maxFileSize) {
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 (!allowList || allowList === '*') {
return { success: true };
......
......@@ -4,6 +4,7 @@ import { RouteProp } from '@react-navigation/native';
import { NativeModules, Text, View } from 'react-native';
import { connect } from 'react-redux';
import ShareExtension from 'rn-extensions-share';
import { Q } from '@nozbe/watermelondb';
import { InsideStackParamList } from '../../stacks/types';
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 () => {
const { room } = this.state;
const { user } = this.props;
......@@ -150,10 +162,12 @@ class ShareView extends Component<IShareViewProps, IShareViewState> {
getAttachments = async () => {
const { mediaAllowList, maxFileSize } = this.state;
const permissionToUploadFile = await this.getPermissionMobileUpload();
const items = await Promise.all(
this.files.map(async item => {
// Check server settings
const { success: canUpload, error } = canUploadFile(item, mediaAllowList, maxFileSize);
const { success: canUpload, error } = canUploadFile(item, mediaAllowList, maxFileSize, permissionToUploadFile);
item.canUpload = canUpload;
item.error = error;
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment