Page MenuHomePhorge

D1894.1742049068.diff
No OneTemporary

Size
43 KB
Referenced Files
None
Subscribers
None

D1894.1742049068.diff

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,11 +162,74 @@
}
// 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
*
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 @@
<button class="btn btn-link link-fullscreen open hidden" @click="switchFullscreen" title="Full screen">
<svg-icon icon="compress"></svg-icon>
</button>
+ <button class="btn btn-link link-security" v-if="session && session.owner" @click="securityOptions" title="Security options">
+ <svg-icon icon="shield-alt"></svg-icon>
+ </button>
<button class="btn btn-link link-logout" @click="logout" title="Leave session">
<svg-icon icon="power-off"></svg-icon>
</button>
@@ -35,38 +38,52 @@
<video class="rounded"></video>
<div class="volume"><div class="bar"></div></div>
</div>
- <div class="col-sm-6">
- <div class="form-group">
- <label for="setup-microphone">Microphone</label>
+ <div class="col-sm-6 align-self-center">
+ <div class="input-group">
+ <label for="setup-microphone" class="input-group-prepend mb-0">
+ <span class="input-group-text" title="Microphone"><svg-icon icon="microphone"></svg-icon></span>
+ </label>
<select class="custom-select" id="setup-microphone" v-model="microphone" @change="setupMicrophoneChange">
<option value="">None</option>
<option v-for="mic in setup.microphones" :value="mic.deviceId" :key="mic.deviceId">{{ mic.label }}</option>
</select>
</div>
- <div class="form-group">
- <label for="setup-camera">Camera</label>
+ <div class="input-group mt-2">
+ <label for="setup-camera" class="input-group-prepend mb-0">
+ <span class="input-group-text" title="Camera"><svg-icon icon="video"></svg-icon></span>
+ </label>
<select class="custom-select" id="setup-camera" v-model="camera" @change="setupCameraChange">
<option value="">None</option>
<option v-for="cam in setup.cameras" :value="cam.deviceId" :key="cam.deviceId">{{ cam.label }}</option>
</select>
</div>
- <div class="form-group mb-0">
- <label for="setup-nickname">Nickname</label>
- <input class="form-control" type="text" id="setup-nickname" v-model="nickname">
+ <div class="input-group mt-2">
+ <label for="setup-nickname" class="input-group-prepend mb-0">
+ <span class="input-group-text" title="Nickname"><svg-icon icon="user"></svg-icon></span>
+ </label>
+ <input class="form-control" type="text" id="setup-nickname" v-model="nickname" placeholder="Your name">
+ </div>
+ <div class="input-group mt-2" v-if="session.config && session.config.requires_password">
+ <label for="setup-password" class="input-group-prepend mb-0">
+ <span class="input-group-text" title="Password"><svg-icon icon="key"></svg-icon></span>
+ </label>
+ <input type="password" class="form-control" id="setup-password" v-model="password" placeholder="Password">
+ </div>
+ <div class="mt-3">
+ <button v-if="roomState == 'ready' || roomState == 424 || roomState == 425"
+ type="button"
+ @click="joinSession"
+ :class="'btn w-100 btn-' + (roomState == 'ready' ? 'success' : 'primary')"
+ >JOIN</button>
+ <button v-if="roomState == 423"
+ type="button"
+ @click="joinSession"
+ class="btn btn-primary w-100"
+ >I'm the owner</button>
</div>
</div>
- <div class="text-center mt-4 col-sm-12">
- <status-message :status="roomState" :status-labels="roomStateLabels" class="mb-3"></status-message>
- <button v-if="roomState == 'ready' || roomState == 424"
- type="button"
- @click="joinSession"
- class="btn btn-primary pl-5 pr-5"
- >JOIN</button>
- <button v-if="roomState == 423"
- type="button"
- @click="joinSession"
- class="btn btn-primary pl-5 pr-5"
- >I'm the owner</button>
+ <div class="mt-4 col-sm-12">
+ <status-message :status="roomState" :status-labels="roomStateLabels"></status-message>
</div>
</form>
</div>
@@ -103,6 +120,8 @@
</div>
</div>
</div>
+
+ <session-security-options v-if="session.config" :config="session.config" :room="room" @config-update="configUpdate"></session-security-options>
</div>
</template>
@@ -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 @@
+<template>
+ <div v-if="config">
+ <div id="security-options-dialog" class="modal" tabindex="-1" role="dialog">
+ <div class="modal-dialog" role="document">
+ <div class="modal-content">
+ <div class="modal-header">
+ <h5 class="modal-title">Security options</h5>
+ <button type="button" class="close" data-dismiss="modal" aria-label="Close">
+ <span aria-hidden="true">&times;</span>
+ </button>
+ </div>
+ <div class="modal-body">
+ <form id="security-options-password">
+ <div id="password-input" class="input-group input-group-activable">
+ <span class="input-group-text label">Password:</span>
+ <span v-if="config.password" id="password-input-text" class="input-group-text">{{ config.password }}</span>
+ <span v-else id="password-input-text" class="input-group-text text-muted">none</span>
+ <input type="text" :value="config.password" name="password" class="form-control rounded-left activable">
+ <div class="input-group-append">
+ <button type="button" @click="passwordSave" id="password-save-btn" class="btn btn-outline-primary activable rounded-right">Save</button>
+ <button type="button" v-if="config.password" id="password-clear-btn" @click="passwordClear" class="btn btn-outline-danger rounded">Clear password</button>
+ <button type="button" v-else @click="passwordSet" id="password-set-btn" class="btn btn-outline-primary rounded">Set password</button>
+ </div>
+ </div>
+ <small class="form-text text-muted">
+ You can add a password to your meeting. Participants will have to provide
+ the password before they are allowed to join the meeting.
+ </small>
+ </form>
+ <hr v-if="false">
+ <form v-if="false" id="security-options-lock">
+ <div id="room-lock" class="">
+ <span class="">Locked room:</span>
+ <input type="checkbox" name="lock" value="1" :checked="config.locked" @click="lockSave">
+ </div>
+ <small class="form-text text-muted">
+ When the room is locked participants have to be approved by you
+ before they could join the meeting.
+ </small>
+ </form>
+ </div>
+ <div class="modal-footer">
+ <button type="button" class="btn btn-secondary modal-action" data-dismiss="modal">Close</button>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+</template>
+
+<script>
+ export default {
+ props: {
+ config: { type: Object, default: () => null },
+ room: { type: String, default: () => null }
+ },
+ data() {
+ return {
+ }
+ },
+ mounted() {
+ $('#security-options-dialog').on('show.bs.modal', e => {
+ $(e.target).find('.input-group-activable.active').removeClass('active')
+ })
+ },
+ methods: {
+ configSave(name, value, callback) {
+ const post = {}
+ post[name] = value
+
+ axios.post('/api/v4/openvidu/rooms/' + this.room + '/config', post)
+ .then(response => {
+ this.config[name] = value
+ if (callback) {
+ callback(response.data)
+ }
+ this.$emit('config-update', this.config)
+ this.$toast.success(response.data.message)
+ })
+ },
+ lockSave(e) {
+ this.configSave('locked', $(e.target).prop('checked') ? 1 : 0)
+ },
+ passwordClear() {
+ this.configSave('password', '')
+ },
+ passwordSave() {
+ this.configSave('password', $('#password-input input').val(), () => {
+ $('#password-input').removeClass('active')
+ })
+ },
+ passwordSet() {
+ $('#password-input').addClass('active').find('input')
+ .off('keydown.pass')
+ .on('keydown.pass', e => {
+ if (e.which == 13) {
+ // On ENTER save the password
+ this.passwordSave()
+ e.preventDefault()
+ } else if (e.which == 27) {
+ // On ESC escape from the input, but not the dialog
+ $('#password-input').removeClass('active')
+ e.stopPropagation()
+ }
+ })
+ .focus()
+ }
+ }
+ }
+</script>
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 @@
+<?php
+
+namespace Tests\Browser\Meet;
+
+use App\OpenVidu\Room;
+use Tests\Browser;
+use Tests\Browser\Components\Dialog;
+use Tests\Browser\Components\Toast;
+use Tests\Browser\Pages\Meet\Room as RoomPage;
+use Tests\TestCaseDusk;
+
+class RoomSecurityTest extends TestCaseDusk
+{
+ /**
+ * {@inheritDoc}
+ */
+ public function setUp(): void
+ {
+ parent::setUp();
+
+ $this->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'));
+ }
}

File Metadata

Mime Type
text/plain
Expires
Sat, Mar 15, 3:31 PM (9 h, 24 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
11313600
Default Alt Text
D1894.1742049068.diff (43 KB)

Event Timeline