@@ -19,61 +19,135 @@ class TestStreamer extends Streamer<any> {
1919}
2020const validateActionStub = sinon . stub ( ) ;
2121const processSerializedSignalStub = sinon . stub ( ) ;
22+ const countByRoomIdAndUserIdStub = sinon . stub ( ) ;
23+ const findSubscriptionsExcludingUserStub = sinon . stub ( ) ;
2224
2325const { NotificationsModule } = proxyquire . noCallThru ( ) . load ( '../../../../../server/modules/notifications/notifications.module' , {
2426 '@rocket.chat/core-services' : {
2527 VideoConf : { validateAction : validateActionStub } ,
2628 MediaCall : { processSerializedSignal : processSerializedSignalStub } ,
2729 } ,
30+ '@rocket.chat/models' : {
31+ Subscriptions : {
32+ countByRoomIdAndUserId : countByRoomIdAndUserIdStub ,
33+ findByRoomIdAndNotUserId : findSubscriptionsExcludingUserStub ,
34+ } ,
35+ Rooms : { } ,
36+ Users : { } ,
37+ } ,
2838} ) ;
2939
30- describe ( 'NotificationsModule notify-user allowWrite ' , ( ) => {
40+ describe ( 'NotificationsModule' , ( ) => {
3141 let notifications : any ;
3242
33- // `isWriteAllowed` is public on the concrete Streamer class but not on the IStreamer
34- // interface that `streamUser` is typed as, so cast to reach it.
35- const writeAllowed = ( eventName : string , ...args : unknown [ ] ) =>
36- ( notifications . streamUser as unknown as Streamer < 'notify-user' > ) . isWriteAllowed ( { userId : 'userId' } as any , eventName , args ) ;
37-
3843 beforeEach ( ( ) => {
39- validateActionStub . reset ( ) ;
40- validateActionStub . resolves ( true ) ;
41- processSerializedSignalStub . reset ( ) ;
42- processSerializedSignalStub . resolves ( undefined ) ;
43-
4444 notifications = new NotificationsModule ( TestStreamer as any ) ;
4545 notifications . configure ( ) ;
4646 } ) ;
4747
48- afterEach ( ( ) => {
49- Object . keys ( StreamerCentral . instances ) . forEach ( ( name ) => delete StreamerCentral . instances [ name ] ) ;
50- } ) ;
48+ describe ( 'notify-user allowWrite' , ( ) => {
49+ // `isWriteAllowed` is public on the concrete Streamer class but not on the IStreamer
50+ // interface that `streamUser` is typed as, so cast to reach it.
51+ const writeAllowed = ( eventName : string , ...args : unknown [ ] ) =>
52+ ( notifications . streamUser as unknown as Streamer < 'notify-user' > ) . isWriteAllowed ( { userId : 'userId' } as any , eventName , args ) ;
53+
54+ beforeEach ( ( ) => {
55+ validateActionStub . reset ( ) ;
56+ validateActionStub . resolves ( true ) ;
57+ processSerializedSignalStub . reset ( ) ;
58+ processSerializedSignalStub . resolves ( undefined ) ;
59+ countByRoomIdAndUserIdStub . reset ( ) ;
60+ findSubscriptionsExcludingUserStub . reset ( ) ;
61+ } ) ;
5162
52- [ 'force_logout' , 'notification' , 'message' , 'uiInteraction' , 'subscriptions-changed' , 'webdav' , 'banners' ] . forEach ( ( event ) => {
53- it ( `should deny a logged-in client writing "${ event } " to another user's stream` , async ( ) => {
54- expect ( await writeAllowed ( `victim/${ event } ` , { foo : 'bar' } ) ) . to . equal ( false ) ;
63+ afterEach ( ( ) => {
64+ Object . keys ( StreamerCentral . instances ) . forEach ( ( name ) => delete StreamerCentral . instances [ name ] ) ;
5565 } ) ;
56- } ) ;
5766
58- it ( "should deny writes even to the client's own stream" , async ( ) => {
59- expect ( await writeAllowed ( `userId/force_logout` , undefined ) ) . to . equal ( false ) ;
60- } ) ;
67+ [ 'force_logout' , 'notification' , 'message' , 'uiInteraction' , 'subscriptions-changed' , 'webdav' , 'banners' ] . forEach ( ( event ) => {
68+ it ( `should deny a logged-in client writing "${ event } " to another user's stream` , async ( ) => {
69+ expect ( await writeAllowed ( `victim/${ event } ` , { foo : 'bar' } ) ) . to . equal ( false ) ;
70+ } ) ;
71+ } ) ;
72+
73+ it ( "should deny writes even to the client's own stream" , async ( ) => {
74+ expect ( await writeAllowed ( `userId/force_logout` , undefined ) ) . to . equal ( false ) ;
75+ } ) ;
76+
77+ it ( 'should accept "video-conference" and delegate authorization to VideoConf.validateAction' , async ( ) => {
78+ const result = await writeAllowed ( `userId/video-conference` , {
79+ action : 'call-start' ,
80+ params : { callId : '123' , uid : '456' , rid : '789' } ,
81+ } ) ;
6182
62- it ( 'should accept "video-conference" and delegate authorization to VideoConf.validateAction' , async ( ) => {
63- const result = await writeAllowed ( `userId/video-conference` , {
64- action : 'call-start' ,
65- params : { callId : '123' , uid : '456' , rid : '789' } ,
83+ expect ( result ) . to . be . true ;
84+ expect ( validateActionStub . calledOnceWith ( 'call-start' , 'userId' , { callId : '123' , uid : '456' , rid : '789' } ) ) . to . be . true ;
6685 } ) ;
6786
68- expect ( result ) . to . be . true ;
69- expect ( validateActionStub . calledOnceWith ( 'call-start' , 'userId' , { callId : '123' , uid : '456' , rid : '789' } ) ) . to . be . true ;
87+ it ( 'should process "media-calls" signals server-side and never broadcast them' , async ( ) => {
88+ const signal = '{"type":"offer"}' ;
89+ const result = await writeAllowed ( `userId/media-calls` , signal ) ;
90+
91+ expect ( result ) . to . equal ( false ) ;
92+ expect ( processSerializedSignalStub . calledOnceWith ( 'userId' , signal ) ) . to . be . true ;
93+ } ) ;
7094 } ) ;
7195
72- it ( 'should process "media-calls" signals server-side and never broadcast them' , async ( ) => {
73- const signal = '{"type":"offer"}' ;
74- const result = await writeAllowed ( `userId/media-calls` , signal ) ;
96+ describe ( 'notify-room-users allowWrite' , ( ) => {
97+ const writeAllowed = ( eventName : string , ...args : unknown [ ] ) =>
98+ ( notifications . streamRoomUsers as unknown as Streamer < 'notify-room-users' > ) . isWriteAllowed (
99+ { userId : 'attacker' } as any ,
100+ eventName ,
101+ args ,
102+ ) ;
103+
104+ beforeEach ( ( ) => {
105+ countByRoomIdAndUserIdStub . reset ( ) ;
106+ countByRoomIdAndUserIdStub . resolves ( 1 ) ; // attacker is subscribed to the room
107+ findSubscriptionsExcludingUserStub . reset ( ) ;
108+ findSubscriptionsExcludingUserStub . returns ( { toArray : async ( ) => [ { u : { _id : 'victim' } } ] } ) ;
109+ } ) ;
75110
76- expect ( result ) . to . equal ( false ) ;
77- expect ( processSerializedSignalStub . calledOnceWith ( 'userId' , signal ) ) . to . be . true ;
111+ afterEach ( ( ) => {
112+ Object . keys ( StreamerCentral . instances ) . forEach ( ( name ) => delete StreamerCentral . instances [ name ] ) ;
113+ } ) ;
114+
115+ [ 'force_logout' , 'notification' , 'message' , 'uiInteraction' , 'subscriptions-changed' , 'webdav' , 'banners' ] . forEach ( ( event ) => {
116+ it ( `should deny and not relay an arbitrary "${ event } " event to other room members` , async ( ) => {
117+ const emitSpy = sinon . spy ( notifications . streamUser , 'emit' ) ;
118+
119+ const result = await writeAllowed ( `room1/${ event } ` , { foo : 'bar' } ) ;
120+
121+ expect ( result ) . to . equal ( false ) ;
122+ expect ( findSubscriptionsExcludingUserStub . called ) . to . equal ( false ) ;
123+ expect ( emitSpy . called ) . to . equal ( false ) ;
124+ } ) ;
125+ } ) ;
126+
127+ it ( 'should not relay anything for a user not subscribed to the room' , async ( ) => {
128+ countByRoomIdAndUserIdStub . resolves ( 0 ) ;
129+ const emitSpy = sinon . spy ( notifications . streamUser , 'emit' ) ;
130+
131+ const result = await writeAllowed ( 'room1/video-conference' , {
132+ action : 'call-start' ,
133+ params : { callId : '123' , uid : 'victim' , rid : 'room1' } ,
134+ } ) ;
135+
136+ expect ( result ) . to . equal ( false ) ;
137+ expect ( findSubscriptionsExcludingUserStub . called ) . to . equal ( false ) ;
138+ expect ( emitSpy . called ) . to . equal ( false ) ;
139+ } ) ;
140+
141+ [ 'video-conference' , 'userData' ] . forEach ( ( event ) => {
142+ it ( `should relay "${ event } " event to other room members` , async ( ) => {
143+ const emitSpy = sinon . spy ( notifications . streamUser , 'emit' ) ;
144+
145+ const result = await writeAllowed ( `room1/${ event } ` , { foo : 'bar' } ) ;
146+
147+ expect ( result ) . to . equal ( false ) ;
148+ expect ( findSubscriptionsExcludingUserStub . called ) . to . equal ( true ) ;
149+ expect ( emitSpy . called ) . to . equal ( true ) ;
150+ } ) ;
151+ } ) ;
78152 } ) ;
79153} ) ;
0 commit comments