diff --git a/appinfo/app.php b/appinfo/app.php index dfaac5a..aca4835 100644 --- a/appinfo/app.php +++ b/appinfo/app.php @@ -1,3 +1,15 @@ getConfig()->getAppValue('bbb', 'api.url'); +$parsedApiUrl = @parse_url($apiUrl); + +if ($parsedApiUrl !== false) { + $manager = \OC::$server->getContentSecurityPolicyManager(); + $policy = new \OCP\AppFramework\Http\EmptyContentSecurityPolicy(); + + $policy->addAllowedFormActionDomain(($parsedApiUrl['scheme'] ?: 'https') . '://' . $parsedApiUrl['host']); + + $manager->addDefaultPolicy($policy); +} \ No newline at end of file diff --git a/lib/BigBlueButton/API.php b/lib/BigBlueButton/API.php index f56acf7..5ffaf0a 100644 --- a/lib/BigBlueButton/API.php +++ b/lib/BigBlueButton/API.php @@ -58,6 +58,7 @@ class API $joinMeetingParams->setCreationTime($creationTime); $joinMeetingParams->setJoinViaHtml5(true); $joinMeetingParams->setRedirect(true); + $joinMeetingParams->setGuest($uid === null); if ($uid) { $joinMeetingParams->setUserId($uid); @@ -114,6 +115,10 @@ class API $createMeetingParams->addPresentation($presentation->getUrl(), null, $presentation->getFilename()); } + if ($room->access === Room::ACCESS_WAITING_ROOM) { + $createMeetingParams->setGuestPolicyAskModerator(); + } + return $createMeetingParams; } diff --git a/lib/Controller/JoinController.php b/lib/Controller/JoinController.php index 0853acb..33e2e9f 100644 --- a/lib/Controller/JoinController.php +++ b/lib/Controller/JoinController.php @@ -8,6 +8,7 @@ use OCA\BigBlueButton\NotFoundException; use OCP\AppFramework\Http\RedirectResponse; use OCP\IRequest; use OCP\ISession; +use OCP\IURLGenerator; use OCP\IUserSession; use OCP\IConfig; use OCA\BigBlueButton\Service\RoomService; @@ -25,6 +26,9 @@ class JoinController extends Controller /** @var RoomService */ private $service; + /** @var IURLGenerator */ + private $urlGenerator; + /** @var IUserSession */ private $userSession; @@ -39,6 +43,7 @@ class JoinController extends Controller IRequest $request, ISession $session, RoomService $service, + IURLGenerator $urlGenerator, IUserSession $userSession, IConfig $config, API $api @@ -46,6 +51,7 @@ class JoinController extends Controller parent::__construct($appName, $request, $session); $this->service = $service; + $this->urlGenerator = $urlGenerator; $this->userSession = $userSession; $this->config = $config; $this->api = $api; @@ -68,7 +74,7 @@ class JoinController extends Controller * @PublicPage * @NoCSRFRequired */ - public function index($displayname, $u = '', $filename = '') + public function index($displayname, $u = '', $filename = '', $password = '') { $room = $this->getRoom(); @@ -87,10 +93,21 @@ class JoinController extends Controller if ($userId === $room->userId) { $presentation = new Presentation($u, $filename); } - } elseif (empty($displayname) || strlen($displayname) < 3) { - $response = new TemplateResponse($this->appName, 'publicdisplayname', [ + } elseif ($room->access === Room::ACCESS_INTERNAL) { + return new RedirectResponse( + $this->urlGenerator->linkToRoute('core.login.showLoginForm', [ + 'redirect_url' => $this->urlGenerator->linkToRoute( + 'bbb.join.index', + ['token' => $this->token] + ), + ]) + ); + } elseif (empty($displayname) || strlen($displayname) < 3 || ($room->access === Room::ACCESS_PASSWORD && $password !== $room->password)) { + $response = new TemplateResponse($this->appName, 'join', [ 'room' => $room->name, - 'wrongdisplayname' => !empty($displayname) && strlen($displayname) < 3 + 'wrongdisplayname' => !empty($displayname) && strlen($displayname) < 3, + 'passwordRequired' => $room->access === Room::ACCESS_PASSWORD, + 'wrongPassword' => $password !== $room->password && $password !== '', ], 'guest'); $this->addFormActionDomain($response); @@ -116,7 +133,7 @@ class JoinController extends Controller $response->getContentSecurityPolicy()->addAllowedFormActionDomain(($parsedApiUrl['scheme'] ?: 'https') . '://' . $parsedApiUrl['host']); } - private function getRoom(): Room + private function getRoom(): ?Room { if ($this->room === null) { $this->room = $this->service->findByUid($this->token); diff --git a/lib/Controller/RoomApiController.php b/lib/Controller/RoomApiController.php index 8482114..0228259 100644 --- a/lib/Controller/RoomApiController.php +++ b/lib/Controller/RoomApiController.php @@ -81,10 +81,11 @@ class RoomApiController extends ApiController string $name, string $welcome, int $maxParticipants, - bool $record + bool $record, + string $access ): DataResponse { - return $this->handleNotFound(function () use ($id, $name, $welcome, $maxParticipants, $record) { - return $this->service->update($id, $name, $welcome, $maxParticipants, $record, $this->userId); + return $this->handleNotFound(function () use ($id, $name, $welcome, $maxParticipants, $record, $access) { + return $this->service->update($id, $name, $welcome, $maxParticipants, $record, $access, $this->userId); }); } diff --git a/lib/Controller/RoomController.php b/lib/Controller/RoomController.php index d271680..edd2b3b 100644 --- a/lib/Controller/RoomController.php +++ b/lib/Controller/RoomController.php @@ -73,10 +73,11 @@ class RoomController extends Controller string $name, string $welcome, int $maxParticipants, - bool $record + bool $record, + string $access ): DataResponse { - return $this->handleNotFound(function () use ($id, $name, $welcome, $maxParticipants, $record) { - return $this->service->update($id, $name, $welcome, $maxParticipants, $record, $this->userId); + return $this->handleNotFound(function () use ($id, $name, $welcome, $maxParticipants, $record, $access) { + return $this->service->update($id, $name, $welcome, $maxParticipants, $record, $access, $this->userId); }); } diff --git a/lib/Db/Room.php b/lib/Db/Room.php index b98b828..25eddb5 100644 --- a/lib/Db/Room.php +++ b/lib/Db/Room.php @@ -7,6 +7,12 @@ use OCP\AppFramework\Db\Entity; class Room extends Entity implements JsonSerializable { + const ACCESS_PUBLIC = 'public'; + const ACCESS_PASSWORD = 'password'; + const ACCESS_WAITING_ROOM = 'waiting_room'; + const ACCESS_INTERNAL = 'internal'; + const ACCESS_INTERNAL_RESTRICTED = 'internal_restricted'; + public $uid; public $name; public $attendeePassword; @@ -15,6 +21,8 @@ class Room extends Entity implements JsonSerializable public $maxParticipants; public $record; public $userId; + public $access; + public $password; public function __construct() { @@ -31,6 +39,8 @@ class Room extends Entity implements JsonSerializable 'welcome' => $this->welcome, 'maxParticipants' => (int) $this->maxParticipants, 'record' => boolval($this->record), + 'access' => $this->access, + 'password' => $this->password, ]; } } diff --git a/lib/Migration/Version000000Date20200604130935.php b/lib/Migration/Version000000Date20200604130935.php new file mode 100644 index 0000000..ec9f342 --- /dev/null +++ b/lib/Migration/Version000000Date20200604130935.php @@ -0,0 +1,51 @@ +hasTable('bbb_rooms')) { + $table = $schema->getTable('bbb_rooms'); + + if (!$table->hasColumn('access')) { + $table->addColumn('access', 'string', [ + 'notnull' => true, + 'default' => Room::ACCESS_PUBLIC, + ]); + } + + if (!$table->hasColumn('password')) { + $table->addColumn('password', 'string', [ + 'length' => 64, + 'notnull' => false, + ]); + } + + return $schema; + } + + return null; + } +} diff --git a/lib/Service/RoomService.php b/lib/Service/RoomService.php index f0ce7cc..ff3fe74 100644 --- a/lib/Service/RoomService.php +++ b/lib/Service/RoomService.php @@ -75,15 +75,20 @@ class RoomService return $this->mapper->insert($room); } - public function update($id, $name, $welcome, $maxParticipants, $record, $userId) + public function update($id, $name, $welcome, $maxParticipants, $record, $access, $userId) { try { $room = $this->mapper->find($id, $userId); + if ($room->access !== $access) { + $room->setPassword($access === Room::ACCESS_PASSWORD ? $this->humanReadableRandom(8) : null); + } + $room->setName($name); $room->setWelcome($welcome); $room->setMaxParticipants($maxParticipants); $room->setRecord($record); + $room->setAccess($access); $room->setUserId($userId); return $this->mapper->update($room); @@ -102,4 +107,9 @@ class RoomService $this->handleException($e); } } + + private function humanReadableRandom($length) + { + return \OC::$server->getSecureRandom()->generate($length, \OCP\Security\ISecureRandom::CHAR_HUMAN_READABLE); + } } diff --git a/templates/join.php b/templates/join.php new file mode 100644 index 0000000..0317e68 --- /dev/null +++ b/templates/join.php @@ -0,0 +1,38 @@ + +
diff --git a/templates/publicdisplayname.php b/templates/publicdisplayname.php deleted file mode 100644 index 363d465..0000000 --- a/templates/publicdisplayname.php +++ /dev/null @@ -1,25 +0,0 @@ - - diff --git a/ts/Manager/Api.ts b/ts/Manager/Api.ts index 6f85076..ce2cf34 100644 --- a/ts/Manager/Api.ts +++ b/ts/Manager/Api.ts @@ -1,5 +1,13 @@ import axios from '@nextcloud/axios'; +export enum Access { + Public = 'public', + Password = 'password', + WaitingRoom = 'waiting_room', + Internal = 'internal', + InternalRestricted = 'internal_restricted', +} + export interface Room { id: number; uid: string; @@ -7,6 +15,8 @@ export interface Room { welcome: string; maxParticipants: number; record: boolean; + access: Access; + password?: string; } export type Recording = { diff --git a/ts/Manager/App.scss b/ts/Manager/App.scss index 0d93b9f..715d0eb 100644 --- a/ts/Manager/App.scss +++ b/ts/Manager/App.scss @@ -168,8 +168,14 @@ min-width: 300px; margin: 2em 0; - input:not([type="checkbox"]) { + input:not([type="checkbox"]), + select { width: 100%; + display: block; + } + + [readonly] { + background-color: #f1f1f1; } em { diff --git a/ts/Manager/EditRoomDialog.tsx b/ts/Manager/EditRoomDialog.tsx index c73ddf1..48460dd 100644 --- a/ts/Manager/EditRoomDialog.tsx +++ b/ts/Manager/EditRoomDialog.tsx @@ -1,6 +1,6 @@ import React, { useState } from 'react'; import Dialog from './Dialog'; -import { Room } from './Api'; +import { Room, Access } from './Api'; import { SubmitInput } from './SubmitInput'; const descriptions: { [key: string]: string } = { @@ -8,6 +8,7 @@ const descriptions: { [key: string]: string } = { welcome: t('bbb', 'This message is shown to all users in the chat area after they joined.'), maxParticipants: t('bbb', 'Sets a limit on the number of participants for this room. Zero means there is no limit.'), recording: t('bbb', 'If enabled, the moderator is able to start the recording.'), + access: t('bbb', 'Public: Everyone knowing the link is able to join. Password: Guests have to provide a password. Waiting room: A moderator has to accept every guest before they can join. Internal: Only Nextcloud users can join.'), }; type Props = { @@ -18,11 +19,11 @@ type Props = { const EditRoomDialog: React.FC