@@ -11,10 +11,25 @@ const settingsGetMap = new Map();
1111const messagesModelStub = {
1212 find : sinon . stub ( ) ,
1313} ;
14+ const usersModelStub = {
15+ findOneByIdAndLoginToken : sinon . stub ( ) ,
16+ } ;
17+ const subscriptionsModelStub = {
18+ findOneByRoomIdAndUserId : sinon . stub ( ) ,
19+ } ;
20+ const validateAndDecodeJWTStub = sinon . stub ( ) ;
21+ const systemLoggerStub = {
22+ error : sinon . stub ( ) ,
23+ } ;
24+ const roomCoordinatorStub = {
25+ getRoomDirectives : sinon . stub ( ) ,
26+ } ;
1427
1528const { FileUpload, FileUploadClass } = proxyquire . noCallThru ( ) . load ( './FileUpload' , {
1629 '@rocket.chat/models' : {
1730 Messages : messagesModelStub ,
31+ Users : usersModelStub ,
32+ Subscriptions : subscriptionsModelStub ,
1833 } ,
1934 'meteor/check' : sinon . stub ( ) ,
2035 'meteor/meteor' : sinon . stub ( ) ,
@@ -23,15 +38,19 @@ const { FileUpload, FileUploadClass } = proxyquire.noCallThru().load('./FileUplo
2338 'stream-buffers' : sinon . stub ( ) ,
2439 '@rocket.chat/tools' : sinon . stub ( ) ,
2540 '../../../../server/lib/i18n' : sinon . stub ( ) ,
26- '../../../../server/lib/logger/system' : sinon . stub ( ) ,
27- '../../../../server/lib/rooms/roomCoordinator' : sinon . stub ( ) ,
41+ '../../../../server/lib/logger/system' : { SystemLogger : systemLoggerStub } ,
42+ '../../../../server/lib/rooms/roomCoordinator' : { roomCoordinator : roomCoordinatorStub } ,
2843 '../../../../server/ufs' : sinon . stub ( ) ,
2944 '../../../../server/ufs/ufs-methods' : sinon . stub ( ) ,
3045 '../../../settings/server' : { settings : settingsStub } ,
3146 '../../../utils/lib/mimeTypes' : sinon . stub ( ) ,
32- '../../../utils/server/lib/JWTHelper' : sinon . stub ( ) ,
47+ '../../../utils/server/lib/JWTHelper' : {
48+ validateAndDecodeJWT : validateAndDecodeJWTStub ,
49+ generateJWT : sinon . stub ( ) ,
50+ } ,
3351 '../../../utils/server/restrictions' : sinon . stub ( ) ,
3452 '../../../api/server/lib/MultipartUploadHandler' : sinon . stub ( ) ,
53+ '@rocket.chat/account-utils' : { hashLoginToken : sinon . stub ( ) . callsFake ( ( token ) => `hashed_${ token } ` ) } ,
3554} ) ;
3655
3756describe ( 'FileUpload' , ( ) => {
@@ -45,6 +64,13 @@ describe('FileUpload', () => {
4564 messagesModelStub . find . reset ( ) ;
4665 fakeStorageModel . findOneById . reset ( ) ;
4766 fakeStorageModel . deleteFile . reset ( ) ;
67+ usersModelStub . findOneByIdAndLoginToken . reset ( ) ;
68+ subscriptionsModelStub . findOneByRoomIdAndUserId . reset ( ) ;
69+ validateAndDecodeJWTStub . reset ( ) ;
70+ systemLoggerStub . error . reset ( ) ;
71+ roomCoordinatorStub . getRoomDirectives . reset ( ) ;
72+ settingsGetMap . clear ( ) ;
73+ settingsGetMap . set ( 'FileUpload_Storage_Type' , 'fakeStorage' ) ;
4874 } ) ;
4975
5076 it ( 'should not remove any file if no room id is provided' , async ( ) => {
@@ -101,4 +127,165 @@ describe('FileUpload', () => {
101127 expect ( fakeStorageModel . deleteFile . calledWith ( 'file-id' ) ) . to . be . true ;
102128 expect ( fakeStorageModel . deleteFile . calledWith ( 'thumbnail-id' ) ) . to . be . true ;
103129 } ) ;
130+
131+ describe ( 'requestCanAccessFiles' , ( ) => {
132+ it ( 'should allow access if FileUpload_ProtectFiles is false' , async ( ) => {
133+ settingsGetMap . set ( 'FileUpload_ProtectFiles' , false ) ;
134+
135+ const request = {
136+ headers : { } ,
137+ url : '/file-upload/test-file-id/test-file.png' ,
138+ } as any ;
139+
140+ const result = await FileUpload . requestCanAccessFiles ( request ) ;
141+ expect ( result ) . to . be . true ;
142+ } ) ;
143+
144+ it ( 'should allow access if no url is provided' , async ( ) => {
145+ settingsGetMap . set ( 'FileUpload_ProtectFiles' , true ) ;
146+
147+ const request = {
148+ headers : { } ,
149+ url : undefined ,
150+ } as any ;
151+
152+ const result = await FileUpload . requestCanAccessFiles ( request ) ;
153+ expect ( result ) . to . be . true ;
154+ } ) ;
155+
156+ it ( 'should deny access if FileUpload_Enable_json_web_token_for_files is true but no token is provided' , async ( ) => {
157+ settingsGetMap . set ( 'FileUpload_ProtectFiles' , true ) ;
158+ settingsGetMap . set ( 'FileUpload_Enable_json_web_token_for_files' , true ) ;
159+
160+ const request = {
161+ headers : { } ,
162+ url : '/file-upload/test-file-id/test-file.png' ,
163+ } as any ;
164+
165+ const file = {
166+ _id : 'test-file-id' ,
167+ rid : 'test-room-id' ,
168+ } as any ;
169+
170+ const result = await FileUpload . requestCanAccessFiles ( request , file ) ;
171+ expect ( result ) . to . be . false ;
172+ } ) ;
173+
174+ it ( 'should deny access if FileUpload_json_web_token_secret_for_files is not configured' , async ( ) => {
175+ settingsGetMap . set ( 'FileUpload_ProtectFiles' , true ) ;
176+ settingsGetMap . set ( 'FileUpload_Enable_json_web_token_for_files' , true ) ;
177+ settingsGetMap . set ( 'FileUpload_json_web_token_secret_for_files' , '' ) ;
178+
179+ const request = {
180+ headers : { } ,
181+ url : '/file-upload/test-file-id/test-file.png?token=some-token' ,
182+ } as any ;
183+
184+ const file = {
185+ _id : 'test-file-id' ,
186+ rid : 'test-room-id' ,
187+ } as any ;
188+
189+ const result = await FileUpload . requestCanAccessFiles ( request , file ) ;
190+ expect ( result ) . to . be . false ;
191+ expect ( systemLoggerStub . error . calledOnce ) . to . be . true ;
192+ } ) ;
193+
194+ it ( 'should deny access if an invalid token is provided' , async ( ) => {
195+ settingsGetMap . set ( 'FileUpload_ProtectFiles' , true ) ;
196+ settingsGetMap . set ( 'FileUpload_Enable_json_web_token_for_files' , true ) ;
197+ settingsGetMap . set ( 'FileUpload_json_web_token_secret_for_files' , 'test-secret' ) ;
198+ validateAndDecodeJWTStub . returns ( null ) ;
199+
200+ const request = {
201+ headers : { } ,
202+ url : '/file-upload/test-file-id/test-file.png?token=invalid-token' ,
203+ } as any ;
204+
205+ const file = {
206+ _id : 'test-file-id' ,
207+ rid : 'test-room-id' ,
208+ } as any ;
209+
210+ const result = await FileUpload . requestCanAccessFiles ( request , file ) ;
211+ expect ( result ) . to . be . false ;
212+ expect ( validateAndDecodeJWTStub . calledOnce ) . to . be . true ;
213+ } ) ;
214+
215+ it ( 'should deny access if token is invalid or payload cannot be decoded' , async ( ) => {
216+ settingsGetMap . set ( 'FileUpload_ProtectFiles' , true ) ;
217+ settingsGetMap . set ( 'FileUpload_Enable_json_web_token_for_files' , true ) ;
218+ settingsGetMap . set ( 'FileUpload_json_web_token_secret_for_files' , 'test-secret' ) ;
219+ validateAndDecodeJWTStub . returns ( null ) ;
220+
221+ const request = {
222+ headers : { } ,
223+ url : '/file-upload/test-file-id/test-file.png?token=valid-token' ,
224+ } as any ;
225+
226+ const file = {
227+ _id : 'test-file-id' ,
228+ rid : 'test-room-id' ,
229+ } as any ;
230+
231+ const result = await FileUpload . requestCanAccessFiles ( request , file ) ;
232+ expect ( result ) . to . be . false ;
233+ } ) ;
234+
235+ it ( 'should deny access if the fileId and rid in the token do not match the requested file' , async ( ) => {
236+ settingsGetMap . set ( 'FileUpload_ProtectFiles' , true ) ;
237+ settingsGetMap . set ( 'FileUpload_Enable_json_web_token_for_files' , true ) ;
238+ settingsGetMap . set ( 'FileUpload_json_web_token_secret_for_files' , 'test-secret' ) ;
239+ validateAndDecodeJWTStub . returns ( { fileId : 'different-file-id' , rid : 'different-room-id' , userId : 'test-user-id' } ) ;
240+
241+ const request = {
242+ headers : { } ,
243+ url : '/file-upload/test-file-id/test-file.png?token=valid-token' ,
244+ } as any ;
245+
246+ const file = {
247+ _id : 'test-file-id' ,
248+ rid : 'test-room-id' ,
249+ } as any ;
250+
251+ const result = await FileUpload . requestCanAccessFiles ( request , file ) ;
252+ expect ( result ) . to . be . false ;
253+ } ) ;
254+
255+ it ( 'should deny access if file object is not provided when using JWT' , async ( ) => {
256+ settingsGetMap . set ( 'FileUpload_ProtectFiles' , true ) ;
257+ settingsGetMap . set ( 'FileUpload_Enable_json_web_token_for_files' , true ) ;
258+ settingsGetMap . set ( 'FileUpload_json_web_token_secret_for_files' , 'test-secret' ) ;
259+ validateAndDecodeJWTStub . returns ( { fileId : 'test-file-id' , rid : 'test-room-id' , userId : 'test-user-id' } ) ;
260+
261+ const request = {
262+ headers : { } ,
263+ url : '/file-upload/test-file-id/test-file.png?token=valid-token' ,
264+ } as any ;
265+
266+ const result = await FileUpload . requestCanAccessFiles ( request , undefined ) ;
267+ expect ( result ) . to . be . false ;
268+ } ) ;
269+
270+ it ( 'should allow access when everything is valid: token is valid, secret configured, and file/room match' , async ( ) => {
271+ settingsGetMap . set ( 'FileUpload_ProtectFiles' , true ) ;
272+ settingsGetMap . set ( 'FileUpload_Enable_json_web_token_for_files' , true ) ;
273+ settingsGetMap . set ( 'FileUpload_json_web_token_secret_for_files' , 'test-secret' ) ;
274+ validateAndDecodeJWTStub . returns ( { fileId : 'test-file-id' , rid : 'test-room-id' , userId : 'test-user-id' } ) ;
275+
276+ const request = {
277+ headers : { } ,
278+ url : '/file-upload/test-file-id/test-file.png?token=valid-token' ,
279+ } as any ;
280+
281+ const file = {
282+ _id : 'test-file-id' ,
283+ rid : 'test-room-id' ,
284+ } as any ;
285+
286+ const result = await FileUpload . requestCanAccessFiles ( request , file ) ;
287+ expect ( result ) . to . be . true ;
288+ expect ( validateAndDecodeJWTStub . calledOnceWith ( 'valid-token' , 'test-secret' ) ) . to . be . true ;
289+ } ) ;
290+ } ) ;
104291} ) ;
0 commit comments