mirror of https://github.com/vector-im/riot-web
				
				
				
			Make Jitsi widgets in video rooms immutable (#8244)
* Make Jitsi widgets in video rooms immutable * Test video room creationpull/21833/head
							parent
							
								
									e54eb81626
								
							
						
					
					
						commit
						5fbb25c707
					
				|  | @ -126,11 +126,14 @@ export default async function createRoom(opts: IOpts): Promise<string | null> { | |||
|             [RoomCreateTypeField]: opts.roomType, | ||||
|         }; | ||||
| 
 | ||||
|         // In video rooms, allow all users to send video member updates
 | ||||
|         // Video rooms require custom power levels
 | ||||
|         if (opts.roomType === RoomType.ElementVideo) { | ||||
|             createOpts.power_level_content_override = { | ||||
|                 events: { | ||||
|                     // Allow all users to send video member updates
 | ||||
|                     [VIDEO_CHANNEL_MEMBER]: 0, | ||||
|                     // Make widgets immutable, even to admins
 | ||||
|                     "im.vector.modular.widgets": 200, | ||||
|                     // Annoyingly, we have to reiterate all the defaults here
 | ||||
|                     [EventType.RoomName]: 50, | ||||
|                     [EventType.RoomAvatar]: 50, | ||||
|  | @ -141,6 +144,10 @@ export default async function createRoom(opts: IOpts): Promise<string | null> { | |||
|                     [EventType.RoomServerAcl]: 100, | ||||
|                     [EventType.RoomEncryption]: 100, | ||||
|                 }, | ||||
|                 users: { | ||||
|                     // Temporarily give ourselves the power to set up a widget
 | ||||
|                     [client.getUserId()]: 200, | ||||
|                 }, | ||||
|             }; | ||||
|         } | ||||
|     } | ||||
|  | @ -259,10 +266,15 @@ export default async function createRoom(opts: IOpts): Promise<string | null> { | |||
|         if (opts.parentSpace) { | ||||
|             return SpaceStore.instance.addRoomToSpace(opts.parentSpace, roomId, [client.getDomain()], opts.suggested); | ||||
|         } | ||||
|     }).then(() => { | ||||
|         // Set up video rooms with a Jitsi widget
 | ||||
|     }).then(async () => { | ||||
|         if (opts.roomType === RoomType.ElementVideo) { | ||||
|             return addVideoChannel(roomId, createOpts.name); | ||||
|             // Set up video rooms with a Jitsi widget
 | ||||
|             await addVideoChannel(roomId, createOpts.name); | ||||
| 
 | ||||
|             // Reset our power level back to admin so that the widget becomes immutable
 | ||||
|             const room = client.getRoom(roomId); | ||||
|             const plEvent = room?.currentState.getStateEvents(EventType.RoomPowerLevels, ""); | ||||
|             await client.setPowerLevel(roomId, client.getUserId(), 100, plEvent); | ||||
|         } | ||||
|     }).then(function() { | ||||
|         // NB createRoom doesn't block on the client seeing the echo that the
 | ||||
|  |  | |||
|  | @ -1,29 +0,0 @@ | |||
| import { canEncryptToAllUsers } from '../src/createRoom'; | ||||
| 
 | ||||
| describe("canEncryptToAllUsers", () => { | ||||
|     const trueUser = { | ||||
|         "@goodUser:localhost": { | ||||
|             "DEV1": {}, | ||||
|             "DEV2": {}, | ||||
|         }, | ||||
|     }; | ||||
|     const falseUser = { | ||||
|         "@badUser:localhost": {}, | ||||
|     }; | ||||
| 
 | ||||
|     it("returns true if all devices have crypto", async () => { | ||||
|         const client = { | ||||
|             downloadKeys: async function(userIds) { return trueUser; }, | ||||
|         }; | ||||
|         const response = await canEncryptToAllUsers(client, ["@goodUser:localhost"]); | ||||
|         expect(response).toBe(true); | ||||
|     }); | ||||
| 
 | ||||
|     it("returns false if not all users have crypto", async () => { | ||||
|         const client = { | ||||
|             downloadKeys: async function(userIds) { return { ...trueUser, ...falseUser }; }, | ||||
|         }; | ||||
|         const response = await canEncryptToAllUsers(client, ["@goodUser:localhost", "@badUser:localhost"]); | ||||
|         expect(response).toBe(false); | ||||
|     }); | ||||
| }); | ||||
|  | @ -0,0 +1,100 @@ | |||
| /* | ||||
| Copyright 2022 The Matrix.org Foundation C.I.C. | ||||
| 
 | ||||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| you may not use this file except in compliance with the License. | ||||
| You may obtain a copy of the License at | ||||
| 
 | ||||
|     http://www.apache.org/licenses/LICENSE-2.0
 | ||||
| 
 | ||||
| Unless required by applicable law or agreed to in writing, software | ||||
| distributed under the License is distributed on an "AS IS" BASIS, | ||||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| See the License for the specific language governing permissions and | ||||
| limitations under the License. | ||||
| */ | ||||
| 
 | ||||
| import { mocked } from "jest-mock"; | ||||
| import { MatrixClient } from "matrix-js-sdk/src/matrix"; | ||||
| import { IDevice } from "matrix-js-sdk/src/crypto/deviceinfo"; | ||||
| import { RoomType } from "matrix-js-sdk/src/@types/event"; | ||||
| 
 | ||||
| import { stubClient, setupAsyncStoreWithClient } from "./test-utils"; | ||||
| import { MatrixClientPeg } from "../src/MatrixClientPeg"; | ||||
| import WidgetStore from "../src/stores/WidgetStore"; | ||||
| import WidgetUtils from "../src/utils/WidgetUtils"; | ||||
| import { VIDEO_CHANNEL, VIDEO_CHANNEL_MEMBER } from "../src/utils/VideoChannelUtils"; | ||||
| import createRoom, { canEncryptToAllUsers } from '../src/createRoom'; | ||||
| 
 | ||||
| describe("createRoom", () => { | ||||
|     let client: MatrixClient; | ||||
|     beforeEach(() => { | ||||
|         stubClient(); | ||||
|         client = MatrixClientPeg.get(); | ||||
|     }); | ||||
| 
 | ||||
|     it("sets up video rooms correctly", async () => { | ||||
|         setupAsyncStoreWithClient(WidgetStore.instance, client); | ||||
|         jest.spyOn(WidgetUtils, "waitForRoomWidget").mockResolvedValue(); | ||||
| 
 | ||||
|         const userId = client.getUserId(); | ||||
|         const roomId = await createRoom({ roomType: RoomType.ElementVideo }); | ||||
| 
 | ||||
|         const [[{ | ||||
|             power_level_content_override: { | ||||
|                 users: { | ||||
|                     [userId]: userPower, | ||||
|                 }, | ||||
|                 events: { | ||||
|                     "im.vector.modular.widgets": widgetPower, | ||||
|                     [VIDEO_CHANNEL_MEMBER]: videoMemberPower, | ||||
|                 }, | ||||
|             }, | ||||
|         }]] = mocked(client.createRoom).mock.calls as any; | ||||
|         const [[widgetRoomId, widgetStateKey, , widgetId]] = mocked(client.sendStateEvent).mock.calls; | ||||
| 
 | ||||
|         // We should have had enough power to be able to set up the Jitsi widget
 | ||||
|         expect(userPower).toBeGreaterThanOrEqual(widgetPower); | ||||
|         // and should have actually set it up
 | ||||
|         expect(widgetRoomId).toEqual(roomId); | ||||
|         expect(widgetStateKey).toEqual("im.vector.modular.widgets"); | ||||
|         expect(widgetId).toEqual(VIDEO_CHANNEL); | ||||
| 
 | ||||
|         // All members should be able to update their connected devices
 | ||||
|         expect(videoMemberPower).toEqual(0); | ||||
|         // Jitsi widget should be immutable for admins
 | ||||
|         expect(widgetPower).toBeGreaterThan(100); | ||||
|         // and we should have been reset back to admin
 | ||||
|         expect(client.setPowerLevel).toHaveBeenCalledWith(roomId, userId, 100, undefined); | ||||
|     }); | ||||
| }); | ||||
| 
 | ||||
| describe("canEncryptToAllUsers", () => { | ||||
|     const trueUser = { | ||||
|         "@goodUser:localhost": { | ||||
|             "DEV1": {} as unknown as IDevice, | ||||
|             "DEV2": {} as unknown as IDevice, | ||||
|         }, | ||||
|     }; | ||||
|     const falseUser = { | ||||
|         "@badUser:localhost": {}, | ||||
|     }; | ||||
| 
 | ||||
|     let client: MatrixClient; | ||||
|     beforeEach(() => { | ||||
|         stubClient(); | ||||
|         client = MatrixClientPeg.get(); | ||||
|     }); | ||||
| 
 | ||||
|     it("returns true if all devices have crypto", async () => { | ||||
|         mocked(client.downloadKeys).mockResolvedValue(trueUser); | ||||
|         const response = await canEncryptToAllUsers(client, ["@goodUser:localhost"]); | ||||
|         expect(response).toBe(true); | ||||
|     }); | ||||
| 
 | ||||
|     it("returns false if not all users have crypto", async () => { | ||||
|         mocked(client.downloadKeys).mockResolvedValue({ ...trueUser, ...falseUser }); | ||||
|         const response = await canEncryptToAllUsers(client, ["@goodUser:localhost", "@badUser:localhost"]); | ||||
|         expect(response).toBe(false); | ||||
|     }); | ||||
| }); | ||||
|  | @ -108,6 +108,8 @@ export function createTestClient(): MatrixClient { | |||
|         getRoomHierarchy: jest.fn().mockReturnValue({ | ||||
|             rooms: [], | ||||
|         }), | ||||
|         createRoom: jest.fn().mockResolvedValue({ room_id: "!1:example.org" }), | ||||
|         setPowerLevel: jest.fn().mockResolvedValue(undefined), | ||||
| 
 | ||||
|         // Used by various internal bits we aren't concerned with (yet)
 | ||||
|         sessionStore: { | ||||
|  | @ -135,6 +137,7 @@ export function createTestClient(): MatrixClient { | |||
|         setPushRuleActions: jest.fn().mockResolvedValue(undefined), | ||||
|         relations: jest.fn().mockRejectedValue(undefined), | ||||
|         isCryptoEnabled: jest.fn().mockReturnValue(false), | ||||
|         downloadKeys: jest.fn(), | ||||
|         fetchRoomEvent: jest.fn(), | ||||
|     } as unknown as MatrixClient; | ||||
| } | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	 Robin
						Robin