Page MenuHomePhorge

D2191.1775221187.diff
No OneTemporary

Authored By
Unknown
Size
27 KB
Referenced Files
None
Subscribers
None

D2191.1775221187.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
@@ -11,6 +11,8 @@
class OpenViduController extends Controller
{
+ public const AUTH_HEADER = 'X-Meet-Auth-Token';
+
/**
* Accept the room join request.
*
@@ -394,6 +396,18 @@
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)) {
+ return $this->errorResponse(403);
+ } elseif (!($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 +470,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 &amp; 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,6 +84,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');
+
// 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');
@@ -100,6 +101,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

Mime Type
text/plain
Expires
Fri, Apr 3, 12:59 PM (3 h, 40 m ago)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
37/a9/9b57c419a55871c5d299828ae7db
Default Alt Text
D2191.1775221187.diff (27 KB)

Event Timeline