Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F19025534
D1894.1742049068.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
43 KB
Referenced Files
None
Subscribers
None
D1894.1742049068.diff
View Options
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">×</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
Details
Attached
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)
Attached To
Mode
D1894: Room Security Options - Password
Attached
Detach File
Event Timeline
Log In to Comment