Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F117757024
D2098.1775206802.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Authored By
Unknown
Size
22 KB
Referenced Files
None
Subscribers
None
D2098.1775206802.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
@@ -3,6 +3,7 @@
namespace App\Http\Controllers\API\V4;
use App\Http\Controllers\Controller;
+use App\OpenVidu\Connection;
use App\OpenVidu\Room;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
@@ -115,22 +116,22 @@
*/
public function dismissConnection($id, $conn)
{
- $room = Room::where('name', $id)->first();
+ $connection = Connection::where('id', $conn)->first();
- // This isn't a room, bye bye
- if (!$room) {
- return $this->errorResponse(404, \trans('meet.room-not-found'));
+ // There's no such connection, bye bye
+ if (!$connection || $connection->room->name != $id) {
+ return $this->errorResponse(404, \trans('meet.connection-not-found'));
}
$user = Auth::guard()->user();
- // Only the room owner can do it
- if (!$user || $user->id != $room->user_id) {
+ // Only the room owner can do it (for now)
+ if (!$user || $user->id != $connection->room->user_id) {
return $this->errorResponse(403);
}
- if (!$room->closeOVConnection($conn)) {
- return $this->errorResponse(500, \trans('meet.session-dismiss-connection-error'));
+ if (!$connection->dismiss()) {
+ return $this->errorResponse(500, \trans('meet.connection-dismiss-error'));
}
return response()->json(['status' => 'success']);
@@ -281,28 +282,29 @@
if ($init) {
// Choose the connection role
$canPublish = !empty(request()->input('canPublish'));
- $reqRole = $canPublish ? Room::ROLE_PUBLISHER : Room::ROLE_SUBSCRIBER;
- $role = $isOwner ? Room::ROLE_MODERATOR : $reqRole;
+ $role = $canPublish ? Room::ROLE_PUBLISHER : Room::ROLE_SUBSCRIBER;
+ if ($isOwner) {
+ $role |= Room::ROLE_MODERATOR;
+ $role |= Room::ROLE_OWNER;
+ }
// Create session token for the current user/connection
- $response = $room->getSessionToken($role, ['canPublish' => $canPublish]);
+ $response = $room->getSessionToken($role);
if (empty($response)) {
return $this->errorResponse(500, \trans('meet.session-join-error'));
}
// Create session token for screen sharing connection
- if ($role != Room::ROLE_SUBSCRIBER && !empty(request()->input('screenShare'))) {
- $add_token = $room->getSessionToken(Room::ROLE_PUBLISHER, ['canPublish' => true]);
+ if (($role & Room::ROLE_PUBLISHER) && !empty(request()->input('screenShare'))) {
+ $add_token = $room->getSessionToken(Room::ROLE_SCREEN);
$response['shareToken'] = $add_token['token'];
}
$response_code = 200;
$response['role'] = $role;
- $response['owner'] = $isOwner;
$response['config'] = $config;
- $response['canPublish'] = $canPublish;
} else {
$response_code = 422;
$response['code'] = 322;
@@ -393,6 +395,9 @@
$room->save();
}
+ // Remove all connections
+ Connection::where('session_id', $sessionId)->delete();
+
break;
}
diff --git a/src/app/OpenVidu/Connection.php b/src/app/OpenVidu/Connection.php
new file mode 100644
--- /dev/null
+++ b/src/app/OpenVidu/Connection.php
@@ -0,0 +1,57 @@
+<?php
+
+namespace App\OpenVidu;
+
+use Illuminate\Database\Eloquent\Model;
+
+/**
+ * The eloquent definition of a Connection.
+ *
+ * @property string $id OpenVidu connection identifier
+ * @property array $metadata Connection metadata
+ * @property int $role Connection role
+ * @property int $room_id Room identifier
+ * @property string $session_id OpenVidu session identifier
+ */
+class Connection extends Model
+{
+ protected $table = 'openvidu_connections';
+
+ public $incrementing = false;
+ protected $keyType = 'string';
+
+ /**
+ * The attributes that should be cast.
+ *
+ * @var array
+ */
+ protected $casts = [
+ 'metadata' => 'array',
+ ];
+
+ /**
+ * Dismiss (close) the connection.
+ *
+ * @return bool True on success, False on failure
+ */
+ public function dismiss()
+ {
+ if ($this->room->closeOVConnection($this->id)) {
+ $this->delete();
+
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * The room to which this connection belongs.
+ *
+ * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
+ */
+ public function room()
+ {
+ return $this->belongsTo(Room::class, 'room_id', 'id');
+ }
+}
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
@@ -18,13 +18,19 @@
{
use SettingsTrait;
- public const ROLE_MODERATOR = 'MODERATOR';
- public const ROLE_PUBLISHER = 'PUBLISHER';
- public const ROLE_SUBSCRIBER = 'SUBSCRIBER';
+ public const ROLE_SUBSCRIBER = 1 << 0;
+ public const ROLE_PUBLISHER = 1 << 1;
+ public const ROLE_MODERATOR = 1 << 2;
+ public const ROLE_SCREEN = 1 << 3;
+ public const ROLE_OWNER = 1 << 4;
public const REQUEST_ACCEPTED = 'accepted';
public const REQUEST_DENIED = 'denied';
+ private const OV_ROLE_MODERATOR = 'MODERATOR';
+ private const OV_ROLE_PUBLISHER = 'PUBLISHER';
+ private const OV_ROLE_SUBSCRIBER = 'SUBSCRIBER';
+
protected $fillable = [
'user_id',
'name'
@@ -169,14 +175,12 @@
/**
* Create a OpenVidu session (connection) token
*
- * @param string $role User role
- * @param array $data User data to attach to the connection.
- * It will be available client-side for everybody.
+ * @param int $role User role (see self::ROLE_* constants)
*
* @return array|null Token data on success, NULL otherwise
* @throws \Exception if session does not exist
*/
- public function getSessionToken($role = self::ROLE_PUBLISHER, $data = []): ?array
+ public function getSessionToken($role = self::ROLE_SUBSCRIBER): ?array
{
if (!$this->session_id) {
throw new \Exception("The room session does not exist");
@@ -186,16 +190,12 @@
// to make it visible for everyone in a room. So, for example we can
// handle/style subscribers/publishers/moderators differently on the
// client-side. Is this a security issue?
- if (!empty($data)) {
- $data += ['role' => $role];
- } else {
- $data = ['role' => $role];
- }
+ $data = ['role' => $role];
$url = 'sessions/' . $this->session_id . '/connection';
$post = [
'json' => [
- 'role' => $role,
+ 'role' => self::OV_ROLE_PUBLISHER,
'data' => json_encode($data)
]
];
@@ -205,11 +205,26 @@
if ($response->getStatusCode() == 200) {
$json = json_decode($response->getBody(), true);
+ // Extract the 'token' part of the token, it will be used to authenticate the connection.
+ // It will be needed in next iterations e.g. to authenticate moderators that aren't
+ // Kolab4 users (or are just not logged in to Kolab4).
+ // FIXME: we could as well generate our own token for auth purposes
+ parse_str(parse_url($json['token'], PHP_URL_QUERY), $url);
+
+ // Create the connection reference in our database
+ $conn = new Connection();
+ $conn->id = $json['id'];
+ $conn->session_id = $this->session_id;
+ $conn->room_id = $this->id;
+ $conn->role = $role;
+ $conn->metadata = ['token' => $url['token']];
+ $conn->save();
+
return [
'session' => $this->session_id,
'token' => $json['token'],
- 'role' => $json['role'],
'connectionId' => $json['id'],
+ 'role' => $role,
];
}
@@ -324,10 +339,10 @@
/**
* Send a OpenVidu signal to the session participants (connections)
*
- * @param string $name Signal name (type)
- * @param array $data Signal data array
- * @param array|string $target List of target connections, Null for all connections.
- * It can be also a participant role.
+ * @param string $name Signal name (type)
+ * @param array $data Signal data array
+ * @param null|int|string[] $target List of target connections, Null for all connections.
+ * It can be also a participant role.
*
* @return bool True on success, False on failure
* @throws \Exception if session does not exist
@@ -345,26 +360,12 @@
];
// Get connection IDs by participant role
- if (is_string($target)) {
- // TODO: We should probably store this in our database/redis. I foresee a use-case
- // for such a connections store on our side, e.g. keeping participant
- // metadata, e.g. selected language, extra roles like a "language interpreter", etc.
-
- $response = $this->client()->request('GET', 'sessions/' . $this->session_id);
-
- if ($response->getStatusCode() !== 200) {
- return false;
- }
-
- $json = json_decode($response->getBody(), true);
- $connections = [];
-
- foreach ($json['connections']['content'] as $connection) {
- if ($connection['role'] === $target) {
- $connections[] = $connection['id'];
- break;
- }
- }
+ if (is_int($target)) {
+ $connections = Connection::where('room_id', $this->id)
+ ->where('session_id', $this->session_id)
+ ->whereRaw("(role & $target)")
+ ->pluck('id')
+ ->all();
if (empty($connections)) {
return false;
diff --git a/src/database/migrations/2021_01_13_120000_create_openvidu_connections_table.php b/src/database/migrations/2021_01_13_120000_create_openvidu_connections_table.php
new file mode 100644
--- /dev/null
+++ b/src/database/migrations/2021_01_13_120000_create_openvidu_connections_table.php
@@ -0,0 +1,45 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+// phpcs:ignore
+class CreateOpenviduConnectionsTable extends Migration
+{
+ /**
+ * Run the migrations.
+ *
+ * @return void
+ */
+ public function up()
+ {
+ Schema::create(
+ 'openvidu_connections',
+ function (Blueprint $table) {
+ // I'm not sure about the max. length of the OpenVidu identifiers
+ // In examples they have 14 characters, so 16 should be enough, but
+ // let's be on the safe side with 24.
+ $table->string('id', 24);
+ $table->string('session_id', 24);
+ $table->bigInteger('room_id')->unsigned();
+ $table->smallInteger('role')->default(0);
+ $table->text('metadata')->nullable(); // should be json, but mariadb
+ $table->timestamps();
+
+ $table->primary('id');
+ $table->foreign('room_id')->references('id')->on('openvidu_rooms')->onDelete('cascade');
+ }
+ );
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('openvidu_connections');
+ }
+}
diff --git a/src/resources/js/meet/app.js b/src/resources/js/meet/app.js
--- a/src/resources/js/meet/app.js
+++ b/src/resources/js/meet/app.js
@@ -2,6 +2,13 @@
import { library } from '@fortawesome/fontawesome-svg-core'
import { OpenVidu } from 'openvidu-browser'
+class Roles {
+ static get SUBSCRIBER() { return 1 << 0; }
+ static get PUBLISHER() { return 1 << 1; }
+ static get MODERATOR() { return 1 << 2; }
+ static get SCREEN() { return 1 << 3; }
+ static get OWNER() { return 1 << 4; }
+}
function Meet(container)
{
@@ -14,7 +21,6 @@
let audioSource = '' // Currently selected microphone
let videoSource = '' // Currently selected camera
let sessionData // Room session metadata
- let role // Current user role
let screenOV // OpenVidu object to initialize a screen sharing session
let screenSession // Session object where the user will connect for screen sharing
@@ -67,12 +73,11 @@
this.switchVideo = switchVideo
this.updateSession = updateSession
-
/**
* Join the room session
*
- * @param data Session metadata and event handlers (session, token, shareToken, nickname,
- * canPublish, chatElement, menuElement, onDestroy, onJoinRequest)
+ * @param data Session metadata and event handlers (session, token, shareToken, nickname, role,
+ * chatElement, menuElement, onDestroy, onJoinRequest)
*/
function joinRoom(data) {
resize();
@@ -97,7 +102,6 @@
session.on('connectionCreated', event => {
// Ignore the current user connection
if (event.connection.role) {
- role = event.connection.role
return
}
@@ -182,7 +186,7 @@
session.connect(data.token, data.params)
.then(() => {
let wrapper
- let params = { self: true, canPublish: data.canPublish, audioActive, videoActive }
+ let params = { self: true, role: data.role, audioActive, videoActive }
params = Object.assign({}, data.params, params)
publisher.on('videoElementCreated', event => {
@@ -196,7 +200,7 @@
wrapper = participantCreate(params)
- if (data.canPublish) {
+ if (data.role & Roles.PUBLISHER) {
publisher.createVideoElement(wrapper, 'PREPEND')
session.publish(publisher)
}
@@ -654,7 +658,7 @@
}
/**
- * Create a participant element in the matrix. Depending on the `canPublish`
+ * Create a participant element in the matrix. Depending on the connection role
* parameter it will be a video element wrapper inside the matrix or a simple
* tag-like element on the subscribers list.
*
@@ -663,7 +667,7 @@
* @return The element
*/
function participantCreate(params) {
- if (params.canPublish) {
+ if (params.role & Roles.PUBLISHER || params.role & Roles.SCREEN) {
return publisherCreate(params)
}
@@ -760,7 +764,7 @@
$element.addClass('self')
}
- if (role == 'MODERATOR') {
+ if (sessionData.role & Roles.MODERATOR) {
$element.addClass('moderated')
}
}
@@ -827,7 +831,7 @@
return false
}
})
- } else if (role == 'MODERATOR') {
+ } else if (sessionData.role & Roles.MODERATOR) {
nickname.attr({title: 'Options', 'data-toggle': 'dropdown'})
.dropdown({boundary: container})
@@ -1071,4 +1075,4 @@
}
}
-export default Meet
+export { Meet, Roles }
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
@@ -13,6 +13,8 @@
|
*/
+ 'connection-not-found' => 'The connection does not exist.',
+ 'connection-dismiss-error' => 'Failed to dismiss the connection.',
'room-not-found' => 'The room does not exist.',
'room-setconfig-success' => 'Room configuration updated successfully.',
'room-unsupported-option-error' => 'Invalid room configuration option.',
@@ -21,7 +23,6 @@
'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-dismiss-connection-error' => 'Failed to dismiss the connection.',
'session-password-error' => 'Failed to join the session. Invalid password.',
'session-request-accept-error' => 'Failed to accept the join request.',
'session-request-deny-error' => 'Failed to deny the join request.',
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,7 +20,7 @@
<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">
+ <button class="btn btn-link link-security" v-if="isRoomOwner()" @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">
@@ -123,7 +123,7 @@
</template>
<script>
- import Meet from '../../js/meet/app.js'
+ import { Meet, Roles } from '../../js/meet/app.js'
import StatusMessage from '../Widgets/StatusMessage'
import LogonForm from '../Login'
import SessionSecurityOptions from './SessionSecurityOptions'
@@ -317,7 +317,10 @@
}
},
isPublisher() {
- return this.session && this.session.canPublish
+ return !!this.session.role && (this.session.role & Roles.PUBLISHER) > 0
+ },
+ isRoomOwner() {
+ return !!this.session.role && (this.session.role & Roles.OWNER) > 0
},
isRoomReady() {
return ['ready', 322, 324, 325, 326, 327].includes(this.roomState)
@@ -402,7 +405,7 @@
this.session.onDestroy = event => {
// TODO: Display different message for each reason: forceDisconnectByUser,
// forceDisconnectByServer, sessionClosedByServer?
- if (event.reason != 'disconnect' && event.reason != 'networkDisconnect' && !this.session.owner) {
+ if (event.reason != 'disconnect' && event.reason != 'networkDisconnect' && !this.isRoomOwner()) {
$('#leave-dialog').on('hide.bs.modal', () => {
// FIXME: Where exactly the user should land? Currently he'll land
// on dashboard (if he's logged in) or login form (if he's not).
@@ -414,7 +417,7 @@
this.session.onDismiss = connId => { this.dismissParticipant(connId) }
- if (this.session.owner) {
+ if (this.isRoomOwner()) {
this.session.onJoinRequest = data => { this.joinRequest(data) }
}
@@ -427,7 +430,7 @@
this.$router.push({ name: 'dashboard' })
}
- if (this.session.owner) {
+ if (this.isRoomOwner()) {
axios.post('/api/v4/openvidu/rooms/' + this.room + '/close').then(logout)
} else {
logout()
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
@@ -114,9 +114,8 @@
$session_id = $room->fresh()->session_id;
- $this->assertSame(Room::ROLE_MODERATOR, $json['role']);
+ $this->assertSame(Room::ROLE_SUBSCRIBER | Room::ROLE_MODERATOR | Room::ROLE_OWNER, $json['role']);
$this->assertSame($session_id, $json['session']);
- $this->assertFalse($json['canPublish']);
$this->assertTrue(is_string($session_id) && !empty($session_id));
$this->assertTrue(strpos($json['token'], 'wss://') === 0);
$this->assertTrue(!array_key_exists('shareToken', $json));
@@ -140,7 +139,6 @@
$json = $response->json();
$this->assertSame(Room::ROLE_SUBSCRIBER, $json['role']);
- $this->assertFalse($json['canPublish']);
$this->assertSame($session_id, $json['session']);
$this->assertTrue(strpos($json['token'], 'wss://') === 0);
$this->assertTrue($json['token'] != $john_token);
@@ -155,7 +153,6 @@
$this->assertSame(Room::ROLE_PUBLISHER, $json['role']);
$this->assertSame($session_id, $json['session']);
- $this->assertTrue($json['canPublish']);
$this->assertTrue(strpos($json['token'], 'wss://') === 0);
$this->assertTrue($json['token'] != $john_token);
$this->assertTrue(!array_key_exists('shareToken', $json));
@@ -331,7 +328,6 @@
$json = $response->json();
$this->assertSame(Room::ROLE_PUBLISHER, $json['role']);
- $this->assertTrue($json['canPublish']);
$this->assertTrue(strpos($json['token'], 'wss://') === 0);
// TODO: Test a scenario where both password and lock are enabled
@@ -359,7 +355,6 @@
$json = $response->json();
$this->assertSame(Room::ROLE_PUBLISHER, $json['role']);
- $this->assertTrue($json['canPublish']);
$this->assertSame($room->session_id, $json['session']);
$this->assertTrue(strpos($json['token'], 'wss://') === 0);
$this->assertTrue(strpos($json['shareToken'], 'wss://') === 0);
@@ -460,13 +455,13 @@
// Non-existing connection
$response = $this->actingAs($john)->post("api/v4/openvidu/rooms/{$room->name}/connections/123/dismiss");
- $response->assertStatus(500);
+ $response->assertStatus(404);
$json = $response->json();
$this->assertCount(2, $json);
$this->assertSame('error', $json['status']);
- $this->assertSame('Failed to dismiss the connection.', $json['message']);
+ $this->assertSame('The connection does not exist.', $json['message']);
// Non-owner access
$response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}/connections/{$conn_id}/dismiss");
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Fri, Apr 3, 9:00 AM (14 h, 56 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
18823107
Default Alt Text
D2098.1775206802.diff (22 KB)
Attached To
Mode
D2098: [MEET]: Generic implementation of Roles/Permissions
Attached
Detach File
Event Timeline