Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: custom end meeting url with restriction #202

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion lib/BigBlueButton/API.php
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ private function buildMeetingParams(Room $room, Presentation $presentation = nul
$createMeetingParams->setModeratorPW($room->moderatorPassword);
$createMeetingParams->setRecord($room->record);
$createMeetingParams->setAllowStartStopRecording($room->record);
$createMeetingParams->setLogoutURL($this->urlGenerator->getBaseUrl());
$createMeetingParams->setLogoutURL($room->logoutURL);
$createMeetingParams->setMuteOnStart($room->getJoinMuted());

$createMeetingParams->addMeta('bbb-origin-version', $this->appManager->getAppVersion(Application::ID));
Expand All @@ -188,6 +188,10 @@ private function buildMeetingParams(Room $room, Presentation $presentation = nul
$invitationUrl = $this->urlHelper->linkToInvitationAbsolute($room);
$createMeetingParams->setModeratorOnlyMessage($this->l10n->t('To invite someone to the meeting, send them this link: %s', [$invitationUrl]));

if (!empty($room->logoutURL)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should probably check if the logoutURL is empty. Anyway I would prefer to have the assignment at one place. Maybe

$createMeetingParams->setLogoutURL($room->logoutURL || $this->urlGenerator->getBaseUrl());

$createMeetingParams->setLogoutURL($this->urlGenerator->getBaseUrl());
}

if (!empty($room->welcome)) {
$createMeetingParams->setWelcome($room->welcome);
}
Expand Down
9 changes: 6 additions & 3 deletions lib/Controller/RestrictionController.php
Original file line number Diff line number Diff line change
Expand Up @@ -83,22 +83,25 @@ public function update(
int $maxRooms,
array $roomTypes,
int $maxParticipants,
bool $allowRecording
bool $allowRecording,
bool $allowLogoutURL
sualko marked this conversation as resolved.
Show resolved Hide resolved
): DataResponse {
return $this->handleNotFound(function () use (
$id,
$groupId,
$maxRooms,
$roomTypes,
$maxParticipants,
$allowRecording) {
$allowRecording,
$allowLogoutURL) {
return $this->service->update(
$id,
$groupId,
$maxRooms,
$roomTypes,
$maxParticipants,
$allowRecording
$allowRecording,
$allowLogoutURL
);
});
}
Expand Down
11 changes: 8 additions & 3 deletions lib/Controller/RoomController.php
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,8 @@ public function update(
bool $listenOnly,
bool $mediaCheck,
bool $cleanLayout,
bool $joinMuted
bool $joinMuted,
string $logoutURL
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need some type of validation for the logoutURL. At least it has to start with http:// or https://.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This validation should also be done on the server side, otherwise an attacker could bypass the client logic.

): DataResponse {
$room = $this->service->find($id);

Expand All @@ -137,13 +138,17 @@ public function update(
return new DataResponse(['message' => 'Not allowed to enable recordings.'], Http::STATUS_BAD_REQUEST);
}

if (!$restriction->getAllowLogoutURL() && $logoutURL !== $room->getLogoutURL()) {
return new DataResponse(['message' => 'Not allowed to enable custom logout URLs'], Https::STATUS_BAD_REQUEST);
}

$disabledRoomTypes = \json_decode($restriction->getRoomTypes());
if ((in_array($access, $disabledRoomTypes) && $access !== $room->getAccess()) || !in_array($access, Room::ACCESS)) {
return new DataResponse(['message' => 'Access type not allowed.'], Http::STATUS_BAD_REQUEST);
}

return $this->handleNotFound(function () use ($id, $name, $welcome, $maxParticipants, $record, $access, $everyoneIsModerator, $requireModerator, $moderatorToken, $listenOnly, $mediaCheck, $cleanLayout, $joinMuted) {
return $this->service->update($id, $name, $welcome, $maxParticipants, $record, $access, $everyoneIsModerator, $requireModerator, $moderatorToken, $listenOnly, $mediaCheck, $cleanLayout, $joinMuted);
return $this->handleNotFound(function () use ($id, $name, $welcome, $maxParticipants, $record, $access, $everyoneIsModerator, $requireModerator, $moderatorToken, $listenOnly, $mediaCheck, $cleanLayout, $joinMuted, $logoutURL) {
return $this->service->update($id, $name, $welcome, $maxParticipants, $record, $access, $everyoneIsModerator, $requireModerator, $moderatorToken, $listenOnly, $mediaCheck, $cleanLayout, $joinMuted, $logoutURL);
});
}

Expand Down
5 changes: 5 additions & 0 deletions lib/Db/Restriction.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,12 @@
* @method string getRoomTypes()
* @method int getMaxParticipants()
* @method bool getAllowRecording()
* @method bool getAllowLogoutURL()
* @method void setRoomId(string $id)
* @method void setMaxRooms(int $number)
* @method void setMaxParticipants(int $number)
* @method void setAllowRecording(bool $allow)
* @method void setAllowLogoutURL(bool $allow)
*/
class Restriction extends Entity implements JsonSerializable {
public const ALL_ID = '';
Expand All @@ -25,11 +27,13 @@ class Restriction extends Entity implements JsonSerializable {
protected $roomTypes = '[]';
protected $maxParticipants = -1;
protected $allowRecording = true;
protected $allowLogoutURL = true;

public function __construct() {
$this->addType('maxRooms', 'integer');
$this->addType('maxParticipants', 'integer');
$this->addType('allowRecording', 'boolean');
$this->addType('allowLogoutURL', 'boolean');
}

public function jsonSerialize(): array {
Expand All @@ -40,6 +44,7 @@ public function jsonSerialize(): array {
'roomTypes' => \json_decode($this->roomTypes),
'maxParticipants' => (int) $this->maxParticipants,
'allowRecording' => boolval($this->allowRecording),
'allowLogoutURL' => boolval($this->allowLogoutURL),
];
}
}
4 changes: 4 additions & 0 deletions lib/Db/Room.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
* @method bool getMediaCheck()
* @method bool getCleanLayout()
* @method bool getJoinMuted()
* @method string getLogoutURL()
* @method void setUid(string $uid)
* @method void setName(string $name)
* @method void setAttendeePassword(string $pw)
Expand All @@ -42,6 +43,7 @@
* @method void setMediaCheck(bool $mediaCheck)
* @method void setCleanLayout(bool $cleanLayout)
* @method void setJoinMuted(bool $joinMuted)
* @method void setLogoutURL(string $logoutURL)
*/
class Room extends Entity implements JsonSerializable {
public const ACCESS_PUBLIC = 'public';
Expand Down Expand Up @@ -71,6 +73,7 @@ class Room extends Entity implements JsonSerializable {
public $mediaCheck;
public $cleanLayout;
public $joinMuted;
public $logoutURL;

public function __construct() {
$this->addType('maxParticipants', 'integer');
Expand All @@ -95,6 +98,7 @@ public function jsonSerialize(): array {
'record' => boolval($this->record),
'access' => $this->access,
'password' => $this->password,
'logoutURL' => $this->logoutURL,
'everyoneIsModerator' => boolval($this->everyoneIsModerator),
'requireModerator' => boolval($this->requireModerator),
'shared' => boolval($this->shared),
Expand Down
49 changes: 49 additions & 0 deletions lib/Migration/Version000000Date20220316165602.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php

declare(strict_types=1);

namespace OCA\BigBlueButton\Migration;

use Closure;
use OCP\DB\ISchemaWrapper;
use OCP\Migration\IOutput;
use OCP\Migration\SimpleMigrationStep;

/**
* Auto-generated migration step: Please modify to your needs!
*/
class Version000000Date20220316165602 extends SimpleMigrationStep {
sualko marked this conversation as resolved.
Show resolved Hide resolved

/**
* @param IOutput $output
* @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
* @param array $options
* @return null|ISchemaWrapper
*/
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper {
$schema = $schemaClosure();

if ($schema->hasTable('bbb_rooms')) {
$table = $schema->getTable('bbb_rooms');

if (!$table->hasColumn('logout_u_r_l')) {
$table->addColumn('logout_u_r_l', 'string', [
'notnull' => false
]);
}
}

if ($schema->hasTable('bbb_restrictions')) {
$table = $schema->getTable('bbb_restrictions');

if (!$table->hasColumn('allow_logout_u_r_l')) {
$table->addColumn('allow_logout_u_r_l', 'boolean', [
'notnull' => false,
'default' => false
]);
}
}

return $schema;
}
}
7 changes: 6 additions & 1 deletion lib/Service/RestrictionService.php
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ public function findByGroupIds(array $groupIds): Restriction {
if (!$restriction->getAllowRecording() && $r->getAllowRecording()) {
$restriction->setAllowRecording($r->getAllowRecording());
}

if (!$restriction->getAllowLogoutURL() && $r->getAllowLogoutURL()) {
$restriction->setAllowLogoutURL($r->getAllowLogoutURL());
}
}

$restriction->setId(0);
Expand All @@ -82,7 +86,7 @@ public function create(string $groupId): Restriction {
return $this->mapper->insert($restriction);
}

public function update(int $id, string $groupId, int $maxRooms, array $roomTypes, int $maxParticipants, bool $allowRecording): Restriction {
public function update(int $id, string $groupId, int $maxRooms, array $roomTypes, int $maxParticipants, bool $allowRecording, bool $allowLogoutURL): Restriction {
try {
$restriction = $this->mapper->find($id);

Expand All @@ -91,6 +95,7 @@ public function update(int $id, string $groupId, int $maxRooms, array $roomTypes
$restriction->setRoomTypes(\json_encode($roomTypes));
$restriction->setMaxParticipants(\max($maxParticipants, -1));
$restriction->setAllowRecording($allowRecording);
$restriction->setAllowLogoutURL($allowLogoutURL);

return $this->mapper->update($restriction);
} catch (Exception $e) {
Expand Down
5 changes: 4 additions & 1 deletion lib/Service/RoomService.php
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ public function create(string $name, string $welcome, int $maxParticipants, bool
$room->setMediaCheck($mediaCheck);
$room->setCleanLayout(false);
$room->setJoinMuted(false);
$room->setLogoutURL('');

if ($access === Room::ACCESS_PASSWORD) {
$room->setPassword($this->humanReadableRandom(8));
Expand Down Expand Up @@ -133,7 +134,8 @@ public function update(
bool $listenOnly,
bool $mediaCheck,
bool $cleanLayout,
bool $joinMuted) {
bool $joinMuted,
string $logoutURL) {
try {
$room = $this->mapper->find($id);

Expand All @@ -156,6 +158,7 @@ public function update(
$room->setMediaCheck($mediaCheck);
$room->setCleanLayout($cleanLayout);
$room->setJoinMuted($joinMuted);
$room->setLogoutURL($logoutURL);

return $this->mapper->update($room);
} catch (Exception $e) {
Expand Down
1 change: 1 addition & 0 deletions tests/Integration/Service/RestrictionServiceTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ public function testUpdate() {
$this->assertEquals(10, $updatedRestriction->getMaxRooms());
$this->assertEquals(15, $updatedRestriction->getMaxParticipants());
$this->assertEquals(false, $updatedRestriction->getAllowRecording());
$this->assertEqauls(false, $updatedRestriction->getAllowLogoutURL());

$this->service->delete($restriction->getId());
}
Expand Down
3 changes: 3 additions & 0 deletions tests/Unit/Service/RestrictionServiceTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,14 @@ public function testFindByGroupIds() {
$restriction0->setRoomTypes(\json_encode([Room::ACCESS_INTERNAL]));
$restriction0->setMaxParticipants(50);
$restriction0->setAllowRecording(false);
$restriction0->setAllowLogoutURL(false);

$restriction1 = new Restriction();
$restriction1->setRoomTypes(\json_encode([Room::ACCESS_INTERNAL, Room::ACCESS_INTERNAL_RESTRICTED]));
$restriction1->setMaxRooms(10);
$restriction1->setMaxParticipants(100);
$restriction1->setAllowRecording(true);
$restriction1->setAllowRecording(true);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should probably setAllowLogoutURL.


$this->mapper
->expects($this->once())
Expand All @@ -48,5 +50,6 @@ public function testFindByGroupIds() {
$this->assertEquals(-1, $result->getMaxRooms());
$this->assertEquals(100, $result->getMaxParticipants());
$this->assertTrue($result->getAllowRecording());
$this->assertTrue($result->getAllowLogoutURL());
}
}
2 changes: 2 additions & 0 deletions ts/Common/Api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export interface Restriction {
roomTypes: string[];
maxParticipants: number;
allowRecording: boolean;
allowLogoutURL: boolean;
}

export interface Room {
Expand All @@ -44,6 +45,7 @@ export interface Room {
mediaCheck: boolean,
cleanLayout: boolean,
joinMuted: boolean,
logoutURL: string,
}

export interface RoomShare {
Expand Down
16 changes: 16 additions & 0 deletions ts/Manager/EditRoomDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const descriptions: { [key: string]: string } = {
mediaCheck: t('bbb', 'If enabled, the user has not to perform an echo call and webcam preview on the first join (available since BBB server 2.3).'),
cleanLayout: t('bbb', 'If enabled, the user list, chat area and presentation are hidden by default.'),
joinMuted: t('bbb', 'If enabled, all users will join the meeting muted.'),
logoutURL: t('bbb', 'After the meeting ends, all users will be redirected to this URL'),
};

const LOGO_QR = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAOxAAADsQBlSsOGwAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAGnSURBVFiF7de9TxRBHMbxzxG5BonRBOGsVRJfIvGFPwFKX0tjJGqsrfwvCC0YtbJSQyT4J0hB1BhtjZFCI4FoqTRCsbO43g24e+5Q3ZNsZm9+z8zzvZns7Rw9/a0jeIx1bNZ8rYe5WzuFt7CSILj9WsFwHtooADzA7XD/DG/CgDrUwHlcDZ/ncLfdtBoCn9cUGtN8yPiWd/QVikOhfZcQ4G1oD8cA8u2oa9ljyufe3vq+HYx7ph7Avv8YO4Rx2b4uy35oKqubFWhiBl+wiJf4imn0V52smxWYxc22vn7cwwHcqjJZ1RUYi4QXNYUzKQEm/1FvYCIlwEAJz/6UAB9KeN6nBFjAp13qH2VPRjKADdkr9Uek9h3XgicZwGk8wcFI7VConUoFMIZXOLGL5ySWVHgUywI08RSDJbyDwdusE+AGjpb0wjFcrxPgSoXwXJerAnScVgo63gXAaKSv49RVBFgL7dnIwN9dAMR0LrSreUfxbfgCd3BJdix/7Q/pBn5WDPuF++G+gQu4WMjq0Ii9+WPyWeFU3K4WHsm2o+7gNTwMX7SnbW0BScCZl0uGVe8AAAAASUVORK5CYII=';
Expand Down Expand Up @@ -75,6 +76,19 @@ const EditRoomDialog: React.FC<Props> = ({ room, restriction, updateProperty, op
);
}

function inputElementRestricted(label: string, field: string, type: 'text' | 'number' | 'url' = 'text', restricted: boolean) {
return (
<div className="bbb-form-element">
<label htmlFor={`bbb-${field}`}>
<h3>{label}</h3>
</label>

<SubmitInput initialValue={room[field]} type={type} name={field} onSubmitValue={value => updateProperty(field, value)} disabled={restricted}/>
{descriptions[field] && <em>{descriptions[field]}</em>}
</div>
);
}

function selectElement(label: string, field: string, value: string, options: { [key: string]: string }, onChange: (value: string) => void) {
return (
<div className="bbb-form-element">
Expand Down Expand Up @@ -123,6 +137,8 @@ const EditRoomDialog: React.FC<Props> = ({ room, restriction, updateProperty, op
updateProperty('access', value);
})}

{inputElementRestricted(t('bbb', 'Custom redirect after meeting'), 'logoutURL', 'url', !restriction?.allowLogoutURL)}

{room.access === Access.InternalRestricted && <div className="bbb-form-element bbb-form-shareWith">
<ShareWith permission={Permission.User} room={room} shares={shares} setShares={setShares} />
<em>{descriptions.internalRestrictedShareWith}</em>
Expand Down
4 changes: 3 additions & 1 deletion ts/Manager/SubmitInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ import {
} from 'react';

export interface SubmitInputProps extends InputHTMLAttributes<HTMLInputElement> {
type?: 'text' | 'number';
type?: 'text' | 'number' | 'url';
initialValue?: string;
name: string;
onSubmitValue: (value: string) => void;
focus?: boolean;
disabled?: boolean;
}

export interface SubmitInputState {
Expand Down Expand Up @@ -42,6 +43,7 @@ export class SubmitInput extends Component<SubmitInputProps, SubmitInputState> {
autoFocus={this.props.focus}
min={this.props.min}
max={this.props.max}
disabled={this.props.disabled}
/>
</form>;
}
Expand Down
5 changes: 4 additions & 1 deletion ts/Restrictions/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,10 @@ const App: React.FC<Props> = () => {
<th>
{t('bbb', 'Recording')}
</th>
<th/>
<th>
{t('bbb', 'Custom redirect after meeting')}
</th>
<th />
</tr>
</thead>
<tbody>
Expand Down
10 changes: 10 additions & 0 deletions ts/Restrictions/RestrictionRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,16 @@ const RestrictionRoom: React.FC<Props> = (props) => {
<label htmlFor={`bbb-record-${restriction.id}`}></label>
</td>

<td className="logoutURL bbb-shrink">
<input
id={`bbb-logoutURL-${restriction.id}`}
type="checkbox"
className="checkbox"
checked={restriction.allowLogoutURL}
onChange={(event) => updateRestriction('allowLogoutURL', event.target.checked)} />
<label htmlFor={`bbb-logoutURL-${restriction.id}`}></label>
</td>

<td className="remove icon-col">
<button disabled={!restriction.groupId} className="action-item" onClick={deleteRow as any} title={t('bbb', 'Delete')}>
<span className="icon icon-delete icon-visible"></span>
Expand Down