diff --git a/src/app/Http/Controllers/API/V4/OpenViduController.php b/src/app/Http/Controllers/API/V4/OpenViduController.php --- a/src/app/Http/Controllers/API/V4/OpenViduController.php +++ b/src/app/Http/Controllers/API/V4/OpenViduController.php @@ -103,11 +103,12 @@ } $user = Auth::guard()->user(); + $isOwner = $user && $user->id == $room->user_id; // There's no existing session if (!$room->hasSession()) { // Participants can't join the room until the session is created by the owner - if (!$user || $user->id != $room->user_id) { + if (!$isOwner) { return $this->errorResponse(423, \trans('meet.session-not-found')); } @@ -123,6 +124,29 @@ } } + $password = (string) $room->getSetting('password'); + + $config = [ + 'locked' => $room->getSetting('locked') === 'true', + 'password' => $isOwner ? $password : '', + 'requires_password' => !$isOwner && strlen($password), + ]; + + // Validate room password + if (!$isOwner && strlen($password)) { + $request_password = request()->input('password'); + if ($request_password !== $password) { + // Note: We send the config to the client so it knows to display the password field + $response = [ + 'config' => $config, + 'message' => \trans('meet.session-password-error'), + 'status' => 'error', + ]; + + return response()->json($response, 425); + } + } + // Create session token for the current user/connection $response = $room->getSessionToken('PUBLISHER'); @@ -138,12 +162,75 @@ } // Tell the UI who's the room owner - $response['owner'] = $user && $user->id == $room->user_id; + $response['owner'] = $isOwner; + + // Append the room configuration + + $response['config'] = $config; return response()->json($response); } /** + * Set the domain configuration. + * + * @param string $id Room identifier (name) + * + * @return \Illuminate\Http\JsonResponse|void + */ + public function setRoomConfig($id) + { + $room = Room::where('name', $id)->first(); + + // Room does not exist, or the owner is deleted + if (!$room || !$room->owner) { + return $this->errorResponse(404); + } + + $user = Auth::guard()->user(); + + // Only room owner can configure the room + if ($user->id != $room->user_id) { + return $this->errorResponse(403); + } + + $input = request()->input(); + $errors = []; + + foreach ($input as $key => $value) { + switch ($key) { + case 'password': + if ($value === null || $value === '') { + $input[$key] = null; + } else { + // TODO: Do we have to validate the password in any way? + } + break; + + case 'locked': + $input[$key] = $value ? 'true' : null; + break; + + default: + $errors[$key] = \trans('meet.room-unsupported-option-error'); + } + } + + if (!empty($errors)) { + return response()->json(['status' => 'error', 'errors' => $errors], 422); + } + + if (!empty($input)) { + $room->setSettings($input); + } + + return response()->json([ + 'status' => 'success', + 'message' => \trans('meet.room-setconfig-success'), + ]); + } + + /** * Webhook as triggered from OpenVidu server * * @param \Illuminate\Http\Request $request The API request. diff --git a/src/app/OpenVidu/Room.php b/src/app/OpenVidu/Room.php --- a/src/app/OpenVidu/Room.php +++ b/src/app/OpenVidu/Room.php @@ -5,6 +5,14 @@ use App\Traits\SettingsTrait; use Illuminate\Database\Eloquent\Model; +/** + * The eloquent definition of a Room. + * + * @property int $id Room identifier + * @property string $name Room name + * @property int $user_id Room owner + * @property ?string $session_id OpenVidu session identifier + */ class Room extends Model { use SettingsTrait; diff --git a/src/resources/js/meet.js b/src/resources/js/meet.js --- a/src/resources/js/meet.js +++ b/src/resources/js/meet.js @@ -20,6 +20,7 @@ faMicrophone, faPowerOff, faUser, + faShieldAlt, faVideo, faVolumeMute } from '@fortawesome/free-solid-svg-icons' @@ -33,6 +34,7 @@ faMicrophone, faPowerOff, faUser, + faShieldAlt, faVideo, faVolumeMute ) diff --git a/src/resources/lang/en/meet.php b/src/resources/lang/en/meet.php --- a/src/resources/lang/en/meet.php +++ b/src/resources/lang/en/meet.php @@ -14,9 +14,13 @@ */ 'room-not-found' => 'The room does not exist.', + 'room-setconfig-success' => 'Room configuration updated successfully.', + 'room-unsupported-option-error' => 'Invalid room configuration option.', 'session-not-found' => 'The session does not exist.', 'session-create-error' => 'Failed to create the session.', 'session-join-error' => 'Failed to join the session.', 'session-close-error' => 'Failed to close the session.', 'session-close-success' => 'The session has been closed successfully.', + 'session-password-error' => 'Failed to join the session. Invalid password.', + ]; diff --git a/src/resources/themes/forms.scss b/src/resources/themes/forms.scss --- a/src/resources/themes/forms.scss +++ b/src/resources/themes/forms.scss @@ -36,6 +36,36 @@ } } +.input-group-activable { + &.active { + :not(.input-group-append):not(.activable) { + display: none; + } + } + + &:not(.active) { + .activable { + display: none; + } + } + + // Label is always visible + .label { + color: $body-color; + display: initial !important; + } + + .input-group-text { + border-color: transparent; + background: transparent; + padding-left: 0; + + &:not(.label) { + flex: 1; + } + } +} + .form-control-plaintext .btn-sm { margin-top: -0.25rem; } diff --git a/src/resources/themes/meet.scss b/src/resources/themes/meet.scss --- a/src/resources/themes/meet.scss +++ b/src/resources/themes/meet.scss @@ -138,6 +138,10 @@ #meet-setup { max-width: 720px; + + .input-group svg { + width: 1em; + } } #meet-auth { diff --git a/src/resources/vue/Meet/Room.vue b/src/resources/vue/Meet/Room.vue --- a/src/resources/vue/Meet/Room.vue +++ b/src/resources/vue/Meet/Room.vue @@ -20,6 +20,9 @@ + @@ -35,38 +38,52 @@
-
-
- +
+
+
-
- +
+
-
- - +
+ + +
+
+ + +
+
+ +
-
- - - +
+
@@ -103,6 +120,8 @@
+ +
@@ -110,10 +129,12 @@ import Meet from '../../js/meet/app.js' import StatusMessage from '../Widgets/StatusMessage' import LogonForm from '../Login' + import SessionSecurityOptions from './SessionSecurityOptions' export default { components: { LogonForm, + SessionSecurityOptions, StatusMessage }, data() { @@ -127,6 +148,7 @@ meet: null, microphone: '', nickname: '', + password: '', room: null, roomState: 'init', roomStateLabels: { @@ -134,6 +156,7 @@ 404: 'The room does not exist.', 423: 'The room is closed. Please, wait for the owner to start the session.', 424: 'The room is closed. It will be open for others after you join.', + 425: 'The room is ready. Please, provide a valid password.', 500: 'Failed to create a session. Server error.' }, session: {} @@ -168,20 +191,20 @@ $('#meet-setup').removeClass('hidden') $('#meet-auth').addClass('hidden') }, + configUpdate(config) { + this.session.config = Object.assign({}, this.session.config, config) + }, initSession(init) { - let params = [] - - if (this.canShareScreen) { - params.push('screenShare=1') + this.post = { + password: this.password, + nickname: this.nickname, + screenShare: this.canShareScreen ? 1 : 0, + init: init ? 1 : 0 } - if (init) { - params.push('init=1') - } + $('#setup-password').removeClass('is-invalid') - const url = '/api/v4/openvidu/rooms/' + this.room + '?' + params.join('&') - - axios.get(url, { ignoreErrors: true }) + axios.post('/api/v4/openvidu/rooms/' + this.room, this.post, { ignoreErrors: true }) .then(response => { // Response data contains: session, token and shareToken this.roomState = 'ready' @@ -194,10 +217,23 @@ .catch(error => { this.roomState = String(error.response.status) - // Waiting for the owner to open the room... - if (error.response.status == 423) { - // Update room state every 10 seconds - window.roomRequest = setTimeout(() => { this.initSession() }, 10000) + if (error.response.data && error.response.data.config) { + this.session.config = error.response.data.config + } + + switch (this.roomState) { + case '423': + // Waiting for the owner to open the room... + // Update room state every 10 seconds + window.roomRequest = setTimeout(() => { this.initSession() }, 10000) + break; + + case '425': + // Missing/invalid password + if (init) { + $('#setup-password').addClass('is-invalid').focus() + } + break; } }) @@ -212,7 +248,7 @@ return } - if (this.roomState == 424) { + if (this.roomState == 424 || this.roomState == 425) { this.initSession(true) return } @@ -223,6 +259,10 @@ $('#meet-setup').addClass('hidden') $('#meet-session-toolbar,#meet-session-layout').removeClass('hidden') + if (!this.canShareScreen) { + this.setMenuItem('screen', false, true) + } + this.session.nickname = this.nickname this.session.menuElement = $('#meet-session-menu')[0] this.session.chatElement = $('#meet-chat')[0] @@ -255,6 +295,9 @@ window.location = window.config['app.url'] } }, + securityOptions() { + $('#security-options-dialog').modal() + }, setMenuItem(type, state, disabled) { let button = $('#meet-session-menu').find('.link-' + type) @@ -338,7 +381,9 @@ // After one screen sharing session ended request a new token // for the next screen sharing session if (!enabled) { - axios.get('/api/v4/openvidu/rooms/' + this.room, { ignoreErrors: true }) + // TODO: This might need to be a different route. E.g. the room password might have + // changed since user joined the session + axios.post('/api/v4/openvidu/rooms/' + this.room, this.post, { ignoreErrors: true }) .then(response => { // Response data contains: session, token and shareToken this.session.shareToken = response.data.token diff --git a/src/resources/vue/Meet/SessionSecurityOptions.vue b/src/resources/vue/Meet/SessionSecurityOptions.vue new file mode 100644 --- /dev/null +++ b/src/resources/vue/Meet/SessionSecurityOptions.vue @@ -0,0 +1,110 @@ + + + diff --git a/src/routes/api.php b/src/routes/api.php --- a/src/routes/api.php +++ b/src/routes/api.php @@ -83,6 +83,7 @@ Route::get('openvidu/rooms', 'API\V4\OpenViduController@index'); Route::post('openvidu/rooms/{id}/close', 'API\V4\OpenViduController@closeRoom'); + Route::post('openvidu/rooms/{id}/config', 'API\V4\OpenViduController@setRoomConfig'); } ); @@ -93,7 +94,7 @@ 'prefix' => $prefix . 'api/v4' ], function () { - Route::get('openvidu/rooms/{id}', 'API\V4\OpenViduController@joinRoom'); + Route::post('openvidu/rooms/{id}', 'API\V4\OpenViduController@joinRoom'); } ); diff --git a/src/routes/websocket.php b/src/routes/websocket.php --- a/src/routes/websocket.php +++ b/src/routes/websocket.php @@ -15,27 +15,23 @@ Websocket::on( 'connect', function ($websocket, Request $request) { - \Log::debug("someone connected"); - $websocket->emit( - 'message', - 'welcome' - ); + return; } ); Websocket::on( 'open', function ($websocket, Request $request) { - \Log::debug("socket opened"); + return; } ); Websocket::on( 'disconnect', function ($websocket) { - \Log::debug("someone disconnected"); + return; } ); -Websocket::on('message', 'App\Http\Controllers\WebsocketController@message'); -Websocket::on('ping', 'App\Http\Controllers\WebsocketController@ping'); +//Websocket::on('message', 'App\Http\Controllers\WebsocketController@message'); +//Websocket::on('ping', 'App\Http\Controllers\WebsocketController@ping'); diff --git a/src/tests/Browser/Meet/RoomControlsTest.php b/src/tests/Browser/Meet/RoomControlsTest.php --- a/src/tests/Browser/Meet/RoomControlsTest.php +++ b/src/tests/Browser/Meet/RoomControlsTest.php @@ -124,9 +124,10 @@ $owner->assertToolbar([ 'audio' => RoomPage::BUTTON_ACTIVE | RoomPage::BUTTON_ENABLED, 'video' => RoomPage::BUTTON_ACTIVE | RoomPage::BUTTON_ENABLED, - 'screen' => RoomPage::BUTTON_INACTIVE | RoomPage::BUTTON_DISABLED, + 'screen' => RoomPage::BUTTON_INACTIVE | RoomPage::BUTTON_ENABLED, 'chat' => RoomPage::BUTTON_INACTIVE | RoomPage::BUTTON_ENABLED, 'fullscreen' => RoomPage::BUTTON_ACTIVE | RoomPage::BUTTON_ENABLED, + 'security' => RoomPage::BUTTON_ACTIVE | RoomPage::BUTTON_ENABLED, 'logout' => RoomPage::BUTTON_ACTIVE | RoomPage::BUTTON_ENABLED, ]) ->whenAvailable('div.meet-video.publisher', function (Browser $browser) { @@ -153,7 +154,7 @@ $guest->assertToolbar([ 'audio' => RoomPage::BUTTON_INACTIVE | RoomPage::BUTTON_DISABLED, 'video' => RoomPage::BUTTON_INACTIVE | RoomPage::BUTTON_DISABLED, - 'screen' => RoomPage::BUTTON_INACTIVE | RoomPage::BUTTON_DISABLED, + 'screen' => RoomPage::BUTTON_INACTIVE | RoomPage::BUTTON_ENABLED, 'chat' => RoomPage::BUTTON_INACTIVE | RoomPage::BUTTON_ENABLED, 'fullscreen' => RoomPage::BUTTON_ACTIVE | RoomPage::BUTTON_ENABLED, 'logout' => RoomPage::BUTTON_ACTIVE | RoomPage::BUTTON_ENABLED, diff --git a/src/tests/Browser/Meet/RoomSecurityTest.php b/src/tests/Browser/Meet/RoomSecurityTest.php new file mode 100644 --- /dev/null +++ b/src/tests/Browser/Meet/RoomSecurityTest.php @@ -0,0 +1,129 @@ +clearBetaEntitlements(); + $this->assignBetaEntitlement('john@kolab.org', 'meet'); + + $room = Room::where('name', 'john')->first(); + $room->setSettings(['password' => null, 'locked' => null]); + } + + public function tearDown(): void + { + $this->clearBetaEntitlements(); + $room = Room::where('name', 'john')->first(); + $room->setSettings(['password' => null, 'locked' => null]); + + parent::tearDown(); + } + + /** + * Test password protected room + * + * @group openvidu + */ + public function testRoomPassword(): void + { + $this->browse(function (Browser $owner, Browser $guest) { + // Make sure there's no session yet + $room = Room::where('name', 'john')->first(); + if ($room->session_id) { + $room->session_id = null; + $room->save(); + } + + // Join the room as an owner (authenticate) + $owner->visit(new RoomPage('john')) + ->click('@setup-button') + ->submitLogon('john@kolab.org', 'simple123') + ->waitFor('@setup-form') + ->waitUntilMissing('@setup-status-message.loading') + ->assertMissing('@setup-password-input') + ->click('@setup-button') + ->waitFor('@session') + // Enter Security option dialog + ->click('@menu button.link-security') + ->with(new Dialog('#security-options-dialog'), function (Browser $browser) use ($room) { + $browser->assertSeeIn('@title', 'Security options') + ->assertSeeIn('@button-action', 'Close') + ->assertElementsCount('.modal-footer button', 1) + ->assertSeeIn('#password-input .label', 'Password:') + ->assertSeeIn('#password-input-text.text-muted', 'none') + ->assertVisible('#password-input + small') + ->assertSeeIn('#password-set-btn', 'Set password') + ->assertElementsCount('#password-input button', 1) + ->assertMissing('#password-input input') + // Test setting a password + ->click('#password-set-btn') + ->assertMissing('#password-input-text') + ->assertVisible('#password-input input') + ->assertValue('#password-input input', '') + ->assertSeeIn('#password-input #password-save-btn', 'Save') + ->assertElementsCount('#password-input button', 1) + ->type('#password-input input', 'pass') + ->click('#password-input #password-save-btn') + ->assertToast(Toast::TYPE_SUCCESS, 'Room configuration updated successfully.') + ->assertMissing('#password-input input') + ->assertSeeIn('#password-input-text:not(.text-muted)', 'pass') + ->assertSeeIn('#password-clear-btn.btn-outline-danger', 'Clear password') + ->assertElementsCount('#password-input button', 1) + ->click('@button-action'); + + $this->assertSame('pass', $room->fresh()->getSetting('password')); + }); + + // In another browser act as a guest, expect password required + $guest->visit(new RoomPage('john')) + ->waitFor('@setup-form') + ->waitUntilMissing('@setup-status-message.loading') + ->assertSeeIn('@setup-status-message.text-danger', "Please, provide a valid password.") + ->assertVisible('@setup-form .input-group:nth-child(4) svg') + ->assertAttribute('@setup-form .input-group:nth-child(4) .input-group-text', 'title', 'Password') + ->assertAttribute('@setup-password-input', 'placeholder', 'Password') + ->assertValue('@setup-password-input', '') + ->assertSeeIn('@setup-button', "JOIN") + // Try to join w/o password + ->click('@setup-button') + ->waitFor('#setup-password.is-invalid') + // Try to join with a valid password + ->type('#setup-password', 'pass') + ->click('@setup-button') + ->waitFor('@session'); + + // Test removing the password + $owner->click('@menu button.link-security') + ->with(new Dialog('#security-options-dialog'), function (Browser $browser) use ($room) { + $browser->assertSeeIn('@title', 'Security options') + ->assertSeeIn('#password-input-text:not(.text-muted)', 'pass') + ->assertSeeIn('#password-clear-btn.btn-outline-danger', 'Clear password') + ->assertElementsCount('#password-input button', 1) + ->click('#password-clear-btn') + ->assertToast(Toast::TYPE_SUCCESS, 'Room configuration updated successfully.') + ->assertMissing('#password-input input') + ->assertSeeIn('#password-input-text.text-muted', 'none') + ->assertSeeIn('#password-set-btn', 'Set password') + ->assertElementsCount('#password-input button', 1) + ->click('@button-action'); + + $this->assertSame(null, $room->fresh()->getSetting('password')); + }); + }); + } +} diff --git a/src/tests/Browser/Meet/RoomSetupTest.php b/src/tests/Browser/Meet/RoomSetupTest.php --- a/src/tests/Browser/Meet/RoomSetupTest.php +++ b/src/tests/Browser/Meet/RoomSetupTest.php @@ -101,12 +101,17 @@ ->assertVisible('@setup-form') ->assertSeeIn('@setup-title', 'Set up your session') ->assertVisible('@setup-video') - ->assertSeeIn('@setup-form .form-group:nth-child(1) label', 'Microphone') + ->assertVisible('@setup-form .input-group:nth-child(1) svg') + ->assertAttribute('@setup-form .input-group:nth-child(1) .input-group-text', 'title', 'Microphone') ->assertVisible('@setup-mic-select') - ->assertSeeIn('@setup-form .form-group:nth-child(2) label', 'Camera') + ->assertVisible('@setup-form .input-group:nth-child(2) svg') + ->assertAttribute('@setup-form .input-group:nth-child(2) .input-group-text', 'title', 'Camera') ->assertVisible('@setup-cam-select') - ->assertSeeIn('@setup-form .form-group:nth-child(3) label', 'Nickname') + ->assertVisible('@setup-form .input-group:nth-child(3) svg') + ->assertAttribute('@setup-form .input-group:nth-child(3) .input-group-text', 'title', 'Nickname') ->assertValue('@setup-nickname-input', '') + ->assertAttribute('@setup-nickname-input', 'placeholder', 'Your name') + ->assertMissing('@setup-password-input') ->assertSeeIn( '@setup-status-message', "The room is closed. Please, wait for the owner to start the session." @@ -171,12 +176,12 @@ ->assertMissing('.status .status-video'); }) ->within(new Menu(), function ($browser) { - $browser->assertMenuItems(['explore', 'blog', 'support', 'logout']); + $browser->assertMenuItems(['explore', 'blog', 'support', 'dashboard', 'logout']); }); if ($browser->isDesktop()) { $browser->within(new Menu('footer'), function ($browser) { - $browser->assertMenuItems(['explore', 'blog', 'support', 'tos', 'logout']); + $browser->assertMenuItems(['explore', 'blog', 'support', 'tos', 'dashboard', 'logout']); }); } diff --git a/src/tests/Browser/Pages/Meet/Room.php b/src/tests/Browser/Pages/Meet/Room.php --- a/src/tests/Browser/Pages/Meet/Room.php +++ b/src/tests/Browser/Pages/Meet/Room.php @@ -63,6 +63,7 @@ '@setup-mic-select' => '#setup-microphone', '@setup-cam-select' => '#setup-camera', '@setup-nickname-input' => '#setup-nickname', + '@setup-password-input' => '#setup-password', '@setup-preview' => '#setup-preview', '@setup-volume' => '#setup-preview .volume', '@setup-video' => '#setup-preview video', diff --git a/src/tests/Feature/Controller/OpenViduTest.php b/src/tests/Feature/Controller/OpenViduTest.php --- a/src/tests/Feature/Controller/OpenViduTest.php +++ b/src/tests/Feature/Controller/OpenViduTest.php @@ -14,12 +14,18 @@ public function setUp(): void { parent::setUp(); + $this->clearBetaEntitlements(); + $room = Room::where('name', 'john')->first(); + $room->setSettings(['password' => null, 'locked' => null]); } public function tearDown(): void { $this->clearBetaEntitlements(); + $room = Room::where('name', 'john')->first(); + $room->setSettings(['password' => null, 'locked' => null]); + parent::tearDown(); } @@ -75,22 +81,22 @@ $this->assignBetaEntitlement($john, 'meet'); // Unauth access, no session yet - $response = $this->get("api/v4/openvidu/rooms/{$room->name}"); + $response = $this->post("api/v4/openvidu/rooms/{$room->name}"); $response->assertStatus(423); // Non-existing room name - $response = $this->actingAs($john)->get("api/v4/openvidu/rooms/non-existing"); + $response = $this->actingAs($john)->post("api/v4/openvidu/rooms/non-existing"); $response->assertStatus(404); // Non-owner, no session yet - $response = $this->actingAs($jack)->get("api/v4/openvidu/rooms/{$room->name}"); + $response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}"); $response->assertStatus(423); // Room owner, no session yet - $response = $this->actingAs($john)->get("api/v4/openvidu/rooms/{$room->name}"); + $response = $this->actingAs($john)->post("api/v4/openvidu/rooms/{$room->name}"); $response->assertStatus(424); - $response = $this->actingAs($john)->get("api/v4/openvidu/rooms/{$room->name}?init=1"); + $response = $this->actingAs($john)->post("api/v4/openvidu/rooms/{$room->name}", ['init' => 1]); $response->assertStatus(200); $json = $response->json(); @@ -106,7 +112,7 @@ $john_token = $json['token']; // Non-owner, now the session exists - $response = $this->actingAs($jack)->get("api/v4/openvidu/rooms/{$room->name}"); + $response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}"); $response->assertStatus(200); $json = $response->json(); @@ -116,6 +122,37 @@ $this->assertTrue(strpos($json['token'], 'wss://') === 0); $this->assertTrue($json['token'] != $john_token); $this->assertTrue(!array_key_exists('shareToken', $json)); + $this->assertEmpty($json['config']['password']); + $this->assertEmpty($json['config']['requires_password']); + + // Non-owner, password protected room, password not provided + $room->setSettings(['password' => 'pass']); + $response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}"); + $response->assertStatus(425); + + $json = $response->json(); + + $this->assertCount(3, $json); + $this->assertSame('error', $json['status']); + $this->assertSame('Failed to join the session. Invalid password.', $json['message']); + $this->assertEmpty($json['config']['password']); + $this->assertTrue($json['config']['requires_password']); + + // Non-owner, password protected room, invalid provided + $response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}", ['password' => 'aa']); + $response->assertStatus(425); + + // Non-owner, password protected room, valid password provided + $response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}", ['password' => 'pass']); + $response->assertStatus(200); + + $json = $response->json(); + + $this->assertSame($session_id, $json['session']); + + // Make sure the room owner can access the password protected room w/o password + $response = $this->actingAs($john)->post("api/v4/openvidu/rooms/{$room->name}"); + $response->assertStatus(200); // TODO: Test accessing an existing room of deleted owner } @@ -135,7 +172,7 @@ $room = Room::where('name', 'john')->first(); // Guest, request with screenShare token - $response = $this->get("api/v4/openvidu/rooms/{$room->name}?screenShare=1"); + $response = $this->post("api/v4/openvidu/rooms/{$room->name}", ['screenShare' => 1]); $response->assertStatus(200); $json = $response->json(); @@ -199,4 +236,82 @@ $this->assertSame("Failed to close the session.", $json['message']); $this->assertCount(2, $json); } + + /** + * Test configuring the room (session) + * + * @group openvidu + */ + public function testSetRoomConfig(): void + { + $john = $this->getTestUser('john@kolab.org'); + $jack = $this->getTestUser('jack@kolab.org'); + $room = Room::where('name', 'john')->first(); + + // Unauth access not allowed + $response = $this->post("api/v4/openvidu/rooms/{$room->name}/config", []); + $response->assertStatus(401); + + // Non-existing room name + $response = $this->actingAs($john)->post("api/v4/openvidu/rooms/non-existing/config", []); + $response->assertStatus(404); + + // TODO: Test a room with a deleted owner + + // Non-owner + $response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}/config", []); + $response->assertStatus(403); + + // Room owner + $response = $this->actingAs($john)->post("api/v4/openvidu/rooms/{$room->name}/config", []); + $response->assertStatus(200); + + $json = $response->json(); + + $this->assertCount(2, $json); + $this->assertSame('success', $json['status']); + $this->assertSame("Room configuration updated successfully.", $json['message']); + + // Set password and room lock + $post = ['password' => 'aaa', 'locked' => 1]; + $response = $this->actingAs($john)->post("api/v4/openvidu/rooms/{$room->name}/config", $post); + $response->assertStatus(200); + + $json = $response->json(); + + $this->assertCount(2, $json); + $this->assertSame('success', $json['status']); + $this->assertSame("Room configuration updated successfully.", $json['message']); + $room->refresh(); + $this->assertSame('aaa', $room->getSetting('password')); + $this->assertSame('true', $room->getSetting('locked')); + + // Unset password and room lock + $post = ['password' => '', 'locked' => 0]; + $response = $this->actingAs($john)->post("api/v4/openvidu/rooms/{$room->name}/config", $post); + $response->assertStatus(200); + + $json = $response->json(); + + $this->assertCount(2, $json); + $this->assertSame('success', $json['status']); + $this->assertSame("Room configuration updated successfully.", $json['message']); + $room->refresh(); + $this->assertSame(null, $room->getSetting('password')); + $this->assertSame(null, $room->getSetting('locked')); + + // Test invalid option error + $post = ['password' => 'eee', 'unknown' => 0]; + $response = $this->actingAs($john)->post("api/v4/openvidu/rooms/{$room->name}/config", $post); + $response->assertStatus(422); + + $json = $response->json(); + + $this->assertCount(2, $json); + $this->assertSame('error', $json['status']); + $this->assertSame("Invalid room configuration option.", $json['errors']['unknown']); + + $room->refresh(); + $this->assertSame(null, $room->getSetting('password')); + } }