Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F117792882
D2191.1775259419.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Authored By
Unknown
Size
27 KB
Referenced Files
None
Subscribers
None
D2191.1775259419.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
@@ -11,6 +11,8 @@
class OpenViduController extends Controller
{
+ public const AUTH_HEADER = 'X-Meet-Auth-Token';
+
/**
* Accept the room join request.
*
@@ -394,6 +396,19 @@
foreach (request()->input() as $key => $value) {
switch ($key) {
case 'role':
+ // The 'owner' role is not assignable
+ if (
+ ($value & Room::ROLE_OWNER && !($connection->role & Room::ROLE_OWNER))
+ || (!($value & Room::ROLE_OWNER) && ($connection->role & Room::ROLE_OWNER))
+ ) {
+ return $this->errorResponse(403);
+ }
+
+ // The room owner has always a 'moderator' role
+ if (!($value & Room::ROLE_MODERATOR) && $connection->role & Room::ROLE_OWNER) {
+ $value |= Room::ROLE_MODERATOR;
+ }
+
$connection->{$key} = $value;
break;
}
@@ -456,7 +471,19 @@
return true;
}
- // TODO: Moderators authentication
+ // Moderator's authentication via the extra request header
+ if ($token = request()->header(self::AUTH_HEADER)) {
+ list($connId, ) = explode(':', base64_decode($token), 2);
+
+ if (
+ ($connection = Connection::find($connId))
+ && $connection->session_id === $room->session_id
+ && $connection->metadata['authToken'] === $token
+ && $connection->role & Room::ROLE_MODERATOR
+ ) {
+ return true;
+ }
+ }
return false;
}
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
@@ -230,6 +230,8 @@
if ($response->getStatusCode() == 200) {
$json = json_decode($response->getBody(), true);
+ $authToken = base64_encode($json['id'] . ':' . \random_bytes(16));
+
// 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).
@@ -242,12 +244,13 @@
$conn->session_id = $this->session_id;
$conn->room_id = $this->id;
$conn->role = $role;
- $conn->metadata = ['token' => $url['token']];
+ $conn->metadata = ['token' => $url['token'], 'authToken' => $authToken];
$conn->save();
return [
'session' => $this->session_id,
'token' => $json['token'],
+ 'authToken' => $authToken,
'connectionId' => $json['id'],
'role' => $role,
];
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
@@ -721,7 +721,9 @@
// It's me
if (session.connection.connectionId == data.connectionId) {
const rolePublisher = data.role && data.role & Roles.PUBLISHER
+ const roleModerator = data.role && data.role & Roles.MODERATOR
const isPublisher = sessionData.role & Roles.PUBLISHER
+ const isModerator = sessionData.role & Roles.MODERATOR
// Inform the vue component, so it can update some UI controls
let update = () => {
@@ -746,6 +748,17 @@
// update the participant element
sessionData.element = participantUpdate(sessionData.element, sessionData)
+ // promoted/demoted to/from a moderator
+ if ('role' in data) {
+ if ((!isModerator && roleModerator) || (isModerator && !roleModerator)) {
+ // Update all participants, to enable/disable the popup menu
+ Object.keys(connections).forEach(key => {
+ const conn = connections[key]
+ participantUpdate(conn.element, conn)
+ })
+ }
+ }
+
// promoted to a publisher
if ('role' in data && !isPublisher && rolePublisher) {
publisher.createVideoElement(sessionData.element, 'PREPEND')
@@ -894,18 +907,19 @@
const element = $(wrapper)
const isModerator = sessionData.role & Roles.MODERATOR
const isSelf = session.connection.connectionId == params.connectionId
+ const rolePublisher = params.role & Roles.PUBLISHER
+ const roleModerator = params.role & Roles.MODERATOR
+ const roleScreen = params.role & Roles.SCREEN
+ const roleOwner = params.role & Roles.OWNER
// Handle publisher-to-subscriber and subscriber-to-publisher change
- if ('role' in params && !(params.role & Roles.SCREEN)) {
- const rolePublisher = params.role & Roles.PUBLISHER
+ if (!roleScreen) {
const isPublisher = element.is('.meet-video')
if ((rolePublisher && !isPublisher) || (!rolePublisher && isPublisher)) {
element.remove()
return participantCreate(params)
}
-
- element.find('.action-role-publisher input').prop('checked', params.role & Roles.PUBLISHER)
}
if ('audioActive' in params) {
@@ -928,13 +942,24 @@
element.addClass('moderated')
}
- element.find('.dropdown-menu')[isSelf || isModerator ? 'removeClass' : 'addClass']('hidden')
- element.find('.permissions')[isModerator ? 'removeClass' : 'addClass']('hidden')
+ const withPerm = isModerator && !roleScreen && !(roleOwner && !isSelf);
+ const withMenu = isSelf || (isModerator && !roleOwner)
- if ('role' in params && params.role & Roles.SCREEN) {
- element.find('.permissions').addClass('hidden')
+ let elements = {
+ '.dropdown-menu': withMenu,
+ '.permissions': withPerm,
+ 'svg.moderator': roleModerator,
+ 'svg.user': !roleModerator
}
+ Object.keys(elements).forEach(key => {
+ element.find(key)[elements[key] ? 'removeClass' : 'addClass']('hidden')
+ })
+
+ element.find('.action-role-publisher input').prop('checked', rolePublisher)
+ element.find('.action-role-moderator input').prop('checked', roleModerator)
+ .prop('disabled', roleOwner)
+
return wrapper
}
@@ -965,7 +990,10 @@
'<div class="dropdown">'
+ '<a href="#" class="meet-nickname btn" aria-haspopup="true" aria-expanded="false" role="button">'
+ '<span class="content"></span>'
- + '<span class="icon">' + svgIcon('user') + '</span>'
+ + '<span class="icon">'
+ + svgIcon('user', null, 'user')
+ + svgIcon('crown', null, 'moderator hidden')
+ + '</span>'
+ '</a>'
+ '<div class="dropdown-menu">'
+ '<a class="dropdown-item action-nickname" href="#">Nickname</a>'
@@ -977,10 +1005,10 @@
+ '<input type="checkbox" class="custom-control-input">'
+ ' <span class="custom-control-label">Audio & Video publishing</span>'
+ '</label>'
- //+ '<label class="dropdown-item action-role-moderator custom-control custom-switch">'
- // + '<input type="checkbox" class="custom-control-input">'
- // + ' <span class="custom-control-label">Moderation</span>'
- //+ '</label>'
+ + '<label class="dropdown-item action-role-moderator custom-control custom-switch">'
+ + '<input type="checkbox" class="custom-control-input">'
+ + ' <span class="custom-control-label">Moderation</span>'
+ + '</label>'
+ '</div>'
+ '</div>'
+ '</div>'
@@ -1026,13 +1054,23 @@
})
}
+ let connectionRole = () => {
+ if (params.isSelf) {
+ return sessionData.role
+ }
+ if (params.connectionId in connections) {
+ return connections[params.connectionId].role
+ }
+ return 0
+ }
+
// Don't close the menu on permission change
element.find('.dropdown-menu > label').on('click', e => { e.stopPropagation() })
if (sessionData.onConnectionChange) {
element.find('.action-role-publisher input').on('change', e => {
const enabled = e.target.checked
- let role = params.role
+ let role = connectionRole()
if (enabled) {
role |= Roles.PUBLISHER
@@ -1048,7 +1086,7 @@
element.find('.action-role-moderator input').on('change', e => {
const enabled = e.target.checked
- let role = params.role
+ let role = connectionRole()
if (enabled) {
role |= Roles.MODERATOR
diff --git a/src/resources/themes/app.scss b/src/resources/themes/app.scss
--- a/src/resources/themes/app.scss
+++ b/src/resources/themes/app.scss
@@ -293,6 +293,10 @@
}
}
+#logon-form {
+ flex-basis: auto; // Bootstrap issue? See logon page with width < 992
+}
+
#logon-form-footer {
a:not(:first-child) {
margin-left: 2em;
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
@@ -174,6 +174,7 @@
faAlignLeft,
faCog,
faCompress,
+ faCrown,
faDesktop,
faExpand,
faMicrophone,
@@ -189,6 +190,7 @@
faAlignLeft,
faCog,
faCompress,
+ faCrown,
faDesktop,
faExpand,
faMicrophone,
@@ -200,6 +202,7 @@
)
let roomRequest
+ const authHeader = 'X-Meet-Auth-Token'
export default {
components: {
@@ -253,6 +256,8 @@
if (this.meet) {
this.meet.leaveRoom()
}
+
+ delete axios.defaults.headers.common[authHeader]
},
methods: {
authSuccess() {
@@ -298,6 +303,10 @@
if (init) {
this.joinSession()
}
+
+ if (this.session.authToken) {
+ axios.defaults.headers.common[authHeader] = this.session.authToken
+ }
})
.catch(error => {
if (!error.response) {
diff --git a/src/routes/api.php b/src/routes/api.php
--- a/src/routes/api.php
+++ b/src/routes/api.php
@@ -84,11 +84,6 @@
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');
- // FIXME: I'm not sure about this one, should we use DELETE request maybe?
- Route::post('openvidu/rooms/{id}/connections/{conn}/dismiss', 'API\V4\OpenViduController@dismissConnection');
- Route::put('openvidu/rooms/{id}/connections/{conn}', 'API\V4\OpenViduController@updateConnection');
- Route::post('openvidu/rooms/{id}/request/{reqid}/accept', 'API\V4\OpenViduController@acceptJoinRequest');
- Route::post('openvidu/rooms/{id}/request/{reqid}/deny', 'API\V4\OpenViduController@denyJoinRequest');
}
);
@@ -100,6 +95,11 @@
],
function () {
Route::post('openvidu/rooms/{id}', 'API\V4\OpenViduController@joinRoom');
+ // FIXME: I'm not sure about this one, should we use DELETE request maybe?
+ Route::post('openvidu/rooms/{id}/connections/{conn}/dismiss', 'API\V4\OpenViduController@dismissConnection');
+ Route::put('openvidu/rooms/{id}/connections/{conn}', 'API\V4\OpenViduController@updateConnection');
+ Route::post('openvidu/rooms/{id}/request/{reqid}/accept', 'API\V4\OpenViduController@acceptJoinRequest');
+ Route::post('openvidu/rooms/{id}/request/{reqid}/deny', 'API\V4\OpenViduController@denyJoinRequest');
}
);
diff --git a/src/tests/Browser/Meet/RoomModeratorTest.php b/src/tests/Browser/Meet/RoomModeratorTest.php
new file mode 100644
--- /dev/null
+++ b/src/tests/Browser/Meet/RoomModeratorTest.php
@@ -0,0 +1,185 @@
+<?php
+
+namespace Tests\Browser\Meet;
+
+use App\OpenVidu\Room;
+use Tests\Browser;
+use Tests\Browser\Components\Dialog;
+use Tests\Browser\Components\Menu;
+use Tests\Browser\Pages\Meet\Room as RoomPage;
+use Tests\TestCaseDusk;
+
+class RoomModeratorTest extends TestCaseDusk
+{
+ /**
+ * {@inheritDoc}
+ */
+ public function setUp(): void
+ {
+ parent::setUp();
+ $this->clearMeetEntitlements();
+ $this->assignMeetEntitlement('john@kolab.org');
+
+ $room = Room::where('name', 'john')->first();
+ $room->setSettings(['password' => null, 'locked' => null]);
+ if ($room->session_id) {
+ $room->session_id = null;
+ $room->save();
+ }
+ }
+
+ public function tearDown(): void
+ {
+ $this->clearMeetEntitlements();
+
+ $room = Room::where('name', 'john')->first();
+ $room->setSettings(['password' => null, 'locked' => null]);
+ if ($room->session_id) {
+ $room->session_id = null;
+ $room->save();
+ }
+
+ parent::tearDown();
+ }
+
+ /**
+ * Test three users in a room, one will be promoted/demoted to/from a moderator
+ *
+ * @group openvidu
+ */
+ public function testModeratorPromotion(): void
+ {
+ $this->browse(function (Browser $browser, Browser $guest1, Browser $guest2) {
+ // In one browser window join as a room owner
+ $browser->visit(new RoomPage('john'))
+ ->click('@setup-button')
+ ->submitLogon('john@kolab.org', 'simple123')
+ ->waitFor('@setup-form')
+ ->waitUntilMissing('@setup-status-message.loading')
+ ->type('@setup-nickname-input', 'John')
+ ->select('@setup-mic-select', '')
+ ->select('@setup-cam-select', '')
+ ->clickWhenEnabled('@setup-button')
+ ->waitFor('@session');
+
+ // In one browser window join as a guest (to be promoted)
+ $guest1->visit(new RoomPage('john'))
+ ->waitFor('@setup-form')
+ ->waitUntilMissing('@setup-status-message.loading')
+ ->assertMissing('@setup-status-message')
+ ->assertSeeIn('@setup-button', "JOIN")
+ ->type('@setup-nickname-input', 'Guest1')
+ // Join the room, disable cam/mic
+ ->select('@setup-mic-select', '')
+ ->select('@setup-cam-select', '')
+ ->clickWhenEnabled('@setup-button')
+ ->waitFor('@session');
+
+ // In one browser window join as a guest
+ $guest2->visit(new RoomPage('john'))
+ ->waitFor('@setup-form')
+ ->waitUntilMissing('@setup-status-message.loading')
+ ->assertMissing('@setup-status-message')
+ ->assertSeeIn('@setup-button', "JOIN")
+ // Join the room, disable mic
+ ->select('@setup-mic-select', '')
+ ->clickWhenEnabled('@setup-button')
+ ->waitFor('@session');
+
+ // Assert that only the owner is a moderator right now
+ $guest1->waitFor('@session video')
+ ->assertMissing('@session div.meet-video .meet-nickname') // guest2
+ ->assertVisible('@session div.meet-subscriber.self svg.user') // self
+ ->assertMissing('@session div.meet-subscriber.self svg.moderator') // self
+ ->assertMissing('@session div.meet-subscriber:not(.self) svg.user') // owner
+ ->assertVisible('@session div.meet-subscriber:not(.self) svg.moderator') // owner
+ ->click('@session div.meet-subscriber.self .meet-nickname')
+ ->whenAvailable('@session .dropdown-menu', function (Browser $browser) {
+ $browser->assertMissing('.permissions');
+ })
+ ->click('@session div.meet-subscriber:not(.self) .meet-nickname')
+ ->assertMissing('.dropdown-menu');
+
+ $guest2->waitFor('@session video')
+ ->assertVisible('@session div.meet-video svg.user') // self
+ ->assertMissing('@session div.meet-video svg.moderator'); // self
+ /*
+ it does not work because the order is different all the time
+
+ ->assertMissing('@session div.meet-subscriber:nth-child(1) svg.user') // owner
+ ->assertVisible('@session div.meet-subscriber:nth-child(1) svg.moderator') // owner
+ ->assertVisible('@session div.meet-subscriber:nth-child(2) svg.user') // guest1
+ ->assertMissing('@session div.meet-subscriber:nth-child(2) svg.moderator'); // guest1
+ */
+
+ // Promote guest1 to a moderator
+ $browser->waitFor('@session video')
+ ->assertMissing('@session div.meet-subscriber.self svg.user') // self
+ ->assertVisible('@session div.meet-subscriber.self svg.moderator') // self
+ ->click('@session div.meet-subscriber.self .meet-nickname')
+ ->whenAvailable('@session .dropdown-menu', function (Browser $browser) {
+ $browser->assertChecked('.action-role-moderator input')
+ ->assertDisabled('.action-role-moderator input');
+ })
+ ->click('@session div.meet-subscriber:not(.self) .meet-nickname')
+ ->whenAvailable('@session div.meet-subscriber:not(.self) .dropdown-menu', function (Browser $browser) {
+ $browser->assertNotChecked('.action-role-moderator input')
+ ->click('.action-role-moderator input');
+ });
+
+ // Assert that we have two moderators now
+ $guest2->waitFor('@session div.meet-subscriber:nth-child(2) svg.moderator')
+ ->assertMissing('@session div.meet-subscriber:nth-child(2) svg.user'); // guest1
+
+ $guest1->waitFor('@session div.meet-subscriber.self svg.moderator')
+ ->assertMissing('@session div.meet-subscriber.self svg.user') // self
+ ->assertVisible('@session div.meet-video svg.user') // guest2
+ ->assertMissing('@session div.meet-video svg.moderator') // guest2
+ ->assertMissing('@session div.meet-subscriber:not(.self) svg.user') // owner
+ ->assertVisible('@session div.meet-subscriber:not(.self) svg.moderator') // owner
+ ->click('@session div.meet-subscriber:not(.self) .meet-nickname') // owner
+ ->assertMissing('@session div.meet-subscriber:not(.self) .dropdown-menu')
+ ->click('@session div.meet-subscriber.self .meet-nickname')
+ ->whenAvailable('@session div.meet-subscriber.self .dropdown-menu', function (Browser $browser) {
+ $browser->assertChecked('.action-role-moderator input')
+ ->assertEnabled('.action-role-moderator input')
+ ->assertNotChecked('.action-role-publisher input')
+ ->assertEnabled('.action-role-publisher input');
+ });
+
+ $browser->waitFor('@session div.meet-subscriber:not(.self) svg.moderator')
+ ->assertMissing('@session div.meet-subscriber:not(.self) svg.user');
+
+ // Check if a moderator can unpublish another user
+ $guest1->click('@session div.meet-video .meet-nickname')
+ ->whenAvailable('@session div.meet-video .dropdown-menu', function (Browser $browser) {
+ $browser->assertNotChecked('.action-role-moderator input')
+ ->assertEnabled('.action-role-moderator input')
+ ->assertChecked('.action-role-publisher input')
+ ->assertEnabled('.action-role-publisher input')
+ ->click('.action-role-publisher input');
+ })
+ ->waitUntilMissing('@session div.meet-video');
+
+ $guest2->waitUntilMissing('@session div.meet-video');
+
+ // Demote guest1 back to a normal user
+ $browser->waitFor('@session div.meet-subscriber:nth-child(3)')
+ ->click('@session') // somehow needed to make the next line invoke the menu
+ ->click('@session div.meet-subscriber:nth-child(2) .meet-nickname')
+ ->whenAvailable('@session div.meet-subscriber:nth-child(2) .dropdown-menu', function ($browser) {
+ $browser->assertChecked('.action-role-moderator input')
+ ->click('.action-role-moderator input');
+ })
+ ->waitFor('@session div.meet-subscriber:nth-child(2) svg.user')
+ ->assertMissing('@session div.meet-subscriber:nth-child(2) svg.moderator');
+
+ $guest1->waitFor('@session div.meet-subscriber.self svg.user')
+ ->assertMissing('@session div.meet-subscriber.self svg.moderator')
+ ->click('@session div.meet-subscriber.self .meet-nickname')
+ ->whenAvailable('@session .dropdown-menu', function (Browser $browser) {
+ $browser->assertMissing('.permissions');
+ });
+ });
+ }
+}
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
@@ -74,7 +74,7 @@
$room->save();
}
- $this->assignMeetEntitlement('john@kolab.org', 'meet');
+ $this->assignMeetEntitlement('john@kolab.org');
$this->browse(function (Browser $browser) {
$browser->visit(new RoomPage('john'))
@@ -130,7 +130,7 @@
*/
public function testTwoUsersInARoom(): void
{
- $this->assignMeetEntitlement('john@kolab.org', 'meet');
+ $this->assignMeetEntitlement('john@kolab.org');
$this->browse(function (Browser $browser, Browser $guest) {
// In one browser window act as a guest
@@ -293,7 +293,7 @@
*/
public function testSubscribers(): void
{
- $this->assignMeetEntitlement('john@kolab.org', 'meet');
+ $this->assignMeetEntitlement('john@kolab.org');
$this->browse(function (Browser $browser, Browser $guest) {
// Join the room as the owner
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
@@ -332,6 +332,7 @@
$this->assertTrue(strpos($json['token'], 'wss://') === 0);
// TODO: Test a scenario where both password and lock are enabled
+ // TODO: Test accepting/denying as a non-owner moderator
}
/**
@@ -476,6 +477,20 @@
$this->assertSame('success', $json['status']);
$this->assertNull($room->getOVConnection($conn_id));
+
+ // Test acting as a moderator
+ $response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}", ['init' => 1]);
+ $response->assertStatus(200);
+ $json = $response->json();
+ $conn_id = $json['connectionId'];
+
+ // Note: We're acting as Jack because there's no easy way to unset a 'actingAs' user
+ // throughout the test
+ $response = $this->actingAs($jack)
+ ->withHeaders([OpenViduController::AUTH_HEADER => $this->getModeratorToken($room)])
+ ->post("api/v4/openvidu/rooms/{$room->name}/connections/{$conn_id}/dismiss");
+
+ $response->assertStatus(200);
}
/**
@@ -576,6 +591,7 @@
$response->assertStatus(200);
$json = $response->json();
+ $owner_conn_id = $json['connectionId'];
// And the other user connection
$response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}", ['init' => 1]);
@@ -584,6 +600,7 @@
$json = $response->json();
$conn_id = $json['connectionId'];
+ $auth_token = $json['authToken'];
$room->refresh();
$conn_data = $room->getOVConnection($conn_id);
@@ -616,5 +633,60 @@
$this->assertSame('success', $json['status']);
$this->assertSame($post['role'], Connection::find($conn_id)->role);
+
+ // Access as moderator
+ // Note: We're acting as Jack because there's no easy way to unset a 'actingAs' user
+ // throughout the test
+ $token = $this->getModeratorToken($room);
+ $post = ['role' => Room::ROLE_PUBLISHER];
+ $response = $this->actingAs($jack)->withHeaders([OpenViduController::AUTH_HEADER => $token])
+ ->put("api/v4/openvidu/rooms/{$room->name}/connections/{$conn_id}", $post);
+ $response->assertStatus(200);
+
+ $this->assertSame('success', $json['status']);
+ $this->assertSame($post['role'], Connection::find($conn_id)->role);
+
+ // Assert that it's not possible to add/remove the 'owner' role
+ $post = ['role' => Room::ROLE_PUBLISHER | Room::ROLE_OWNER];
+ $response = $this->actingAs($jack)->withHeaders([OpenViduController::AUTH_HEADER => $token])
+ ->put("api/v4/openvidu/rooms/{$room->name}/connections/{$conn_id}", $post);
+
+ $response->assertStatus(403);
+
+ $post = ['role' => Room::ROLE_PUBLISHER];
+ $response = $this->actingAs($jack)->withHeaders([OpenViduController::AUTH_HEADER => $token])
+ ->put("api/v4/openvidu/rooms/{$room->name}/connections/{$owner_conn_id}", $post);
+
+ $response->assertStatus(403);
+
+ // Assert that removing a 'moderator' role from the owner is not possible
+ $post = ['role' => Room::ROLE_PUBLISHER | Room::ROLE_OWNER];
+ $response = $this->actingAs($jack)->withHeaders([OpenViduController::AUTH_HEADER => $token])
+ ->put("api/v4/openvidu/rooms/{$room->name}/connections/{$owner_conn_id}", $post);
+
+ $response->assertStatus(200);
+
+ $this->assertSame($post['role'] | Room::ROLE_MODERATOR, Connection::find($owner_conn_id)->role);
+
+ // Assert that non-moderator token does not allow access
+ $post = ['role' => Room::ROLE_SUBSCRIBER];
+ $response = $this->actingAs($jack)->withHeaders([OpenViduController::AUTH_HEADER => $auth_token])
+ ->put("api/v4/openvidu/rooms/{$room->name}/connections/{$conn_id}", $post);
+
+ $response->assertStatus(403);
+ }
+
+ /**
+ * Create a moderator connection to the room session.
+ *
+ * @param \App\Room $room The room
+ *
+ * @return string The connection authentication token
+ */
+ private function getModeratorToken(Room $room): string
+ {
+ $result = $room->getSessionToken(Room::ROLE_MODERATOR);
+
+ return $result['authToken'];
}
}
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Fri, Apr 3, 11:36 PM (12 h, 4 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
18826429
Default Alt Text
D2191.1775259419.diff (27 KB)
Attached To
Mode
D2191: OpenVidu: Moderators
Attached
Detach File
Event Timeline