From 6902b50795e5cdf650267f886e3989f865562795 Mon Sep 17 00:00:00 2001 From: sualko Date: Tue, 22 Sep 2020 12:19:48 +0200 Subject: [PATCH] feat: add meeting ended event and recording ready event --- appinfo/routes.php | 2 + lib/AppInfo/Application.php | 2 + lib/BigBlueButton/API.php | 16 ++++++- lib/Controller/HookController.php | 74 +++++++++++++++++++++++++++++++ lib/Crypto.php | 44 ++++++++++++++++++ lib/Event/RecordingReadyEvent.php | 6 +++ lib/Event/RoomEndedEvent.php | 19 ++++++++ lib/Middleware/HookMiddleware.php | 69 ++++++++++++++++++++++++++++ 8 files changed, 231 insertions(+), 1 deletion(-) create mode 100644 lib/Controller/HookController.php create mode 100644 lib/Crypto.php create mode 100644 lib/Event/RecordingReadyEvent.php create mode 100644 lib/Event/RoomEndedEvent.php create mode 100644 lib/Middleware/HookMiddleware.php diff --git a/appinfo/routes.php b/appinfo/routes.php index c5fb8e4..8ddc9dd 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -14,5 +14,7 @@ return [ ['name' => 'server#delete_record', 'url' => '/server/record/{recordId}', 'verb' => 'DELETE'], ['name' => 'join#index', 'url' => '/b/{token}', 'verb' => 'GET'], ['name' => 'restriction#user', 'url' => '/restrictions/user', 'verb' => 'GET'], + ['name' => 'hook#meetingEnded', 'url' => '/hook/ended/{token}/{mac}', 'verb' => 'GET'], + ['name' => 'hook#recordingReady', 'url' => '/hook/recording/{token}/{mac}', 'verb' => 'GET'], ] ]; diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index 6beb076..71ce5aa 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -7,6 +7,7 @@ use \OCP\Settings\IManager as ISettingsManager; use \OCP\AppFramework\App; use \OCP\EventDispatcher\IEventDispatcher; use \OCA\BigBlueButton\Middleware\JoinMiddleware; +use \OCA\BigBlueButton\Middleware\HookMiddleware; use \OCA\BigBlueButton\Event\RoomCreatedEvent; use \OCA\BigBlueButton\Event\RoomDeletedEvent; use \OCA\BigBlueButton\Activity\RoomListener; @@ -35,6 +36,7 @@ class Application extends App { $dispatcher->addServiceListener(RoomShareDeletedEvent::class, RoomShareListener::class); $container->registerMiddleWare(JoinMiddleware::class); + $container->registerMiddleWare(HookMiddleware::class); $config = $container->query(IConfig::class); diff --git a/lib/BigBlueButton/API.php b/lib/BigBlueButton/API.php index a4a51e5..5180dc9 100644 --- a/lib/BigBlueButton/API.php +++ b/lib/BigBlueButton/API.php @@ -11,6 +11,7 @@ use BigBlueButton\Parameters\DeleteRecordingsParameters; use BigBlueButton\Parameters\IsMeetingRunningParameters; use OCA\BigBlueButton\Db\Room; use OCA\BigBlueButton\Permission; +use OCA\BigBlueButton\Crypto; use OCP\IConfig; use OCP\IURLGenerator; @@ -27,14 +28,19 @@ class API { /** @var BigBlueButton|null */ private $server; + /** @var Crypto */ + private $crypto; + public function __construct( IConfig $config, IURLGenerator $urlGenerator, - Permission $permission + Permission $permission, + Crypto $crypto ) { $this->config = $config; $this->urlGenerator = $urlGenerator; $this->permission = $permission; + $this->crypto = $crypto; } private function getServer() { @@ -101,6 +107,14 @@ class API { $createMeetingParams->setAllowStartStopRecording($room->record); $createMeetingParams->setLogoutUrl($this->urlGenerator->getBaseUrl()); + $mac = $this->crypto->calculateHMAC($room->uid); + + $endMeetingUrl = $this->urlGenerator->linkToRouteAbsolute('bbb.hook.meetingEnded', ['token' => $room->uid, 'mac' => $mac]); + $createMeetingParams->setEndCallbackUrl($endMeetingUrl); + + $recordingReadyUrl = $this->urlGenerator->linkToRouteAbsolute('bbb.hook.recordingReady', ['token' => $room->uid, 'mac' => $mac]); + $createMeetingParams->setRecordingReadyCallbackUrl($recordingReadyUrl); + $invitationUrl = $this->urlGenerator->linkToRouteAbsolute('bbb.join.index', ['token' => $room->uid]); $createMeetingParams->setModeratorOnlyMessage('To invite someone to the meeting, send them this link: ' . $invitationUrl); diff --git a/lib/Controller/HookController.php b/lib/Controller/HookController.php new file mode 100644 index 0000000..5e24693 --- /dev/null +++ b/lib/Controller/HookController.php @@ -0,0 +1,74 @@ +service = $service; + $this->eventDispatcher = $eventDispatcher; + } + + public function setToken(string $token) { + $this->token = $token; + $this->room = null; + } + + public function isValidToken(): bool { + $room = $this->getRoom(); + + return $room !== null; + } + + /** + * @PublicPage + * @NoCSRFRequired + */ + public function meetingEnded($recordingmarks = false) { + $recordingmarks = \boolval($recordingmarks); + + $this->eventDispatcher->dispatch(RoomEndedEvent::class, new RoomEndedEvent($this->getRoom(), $recordingmarks)); + } + + /** + * @PublicPage + * @NoCSRFRequired + */ + public function recordingReady() { + $this->eventDispatcher->dispatch(RecordingReadyEvent::class, new RecordingReadyEvent($this->getRoom())); + } + + private function getRoom(): ?Room { + if ($this->room === null) { + $this->room = $this->service->findByUid($this->token); + } + + return $this->room; + } +} diff --git a/lib/Crypto.php b/lib/Crypto.php new file mode 100644 index 0000000..b4429fc --- /dev/null +++ b/lib/Crypto.php @@ -0,0 +1,44 @@ +crypto = $crypto; + } + + public function calculateHMAC(string $message): string { + if ($message === null) { + throw new \InvalidArgumentException(); + } + + return $this->encodeBase64UrlSafe(\sha1($this->crypto->calculateHMAC($message), true)); + } + + public function verifyHMAC(string $message, string $mac) { + if ($message === null || $mac === null) { + return false; + } + + $validMac = $this->encodeBase64UrlSafe(\sha1($this->crypto->calculateHMAC($message), true)); + + return $validMac === $mac; + } + + private function encodeBase64UrlSafe($data) { + $b64 = \base64_encode($data); + + if ($b64 === false) { + return false; + } + + return \rtrim(\strtr($b64, '+/', '-_'), '='); + } +} diff --git a/lib/Event/RecordingReadyEvent.php b/lib/Event/RecordingReadyEvent.php new file mode 100644 index 0000000..79da8b4 --- /dev/null +++ b/lib/Event/RecordingReadyEvent.php @@ -0,0 +1,6 @@ +recordingMarks = $recordingMarks; + } + + public function hasRecordingMarks(): bool { + return $this->recordingMarks; + } +} diff --git a/lib/Middleware/HookMiddleware.php b/lib/Middleware/HookMiddleware.php new file mode 100644 index 0000000..e0749fc --- /dev/null +++ b/lib/Middleware/HookMiddleware.php @@ -0,0 +1,69 @@ +request = $request; + $this->crypto = $crypto; + } + + public function beforeController($controller, $methodName) { + if (!($controller instanceof HookController)) { + return; + } + + $token = $this->request->getParam('token'); + if ($token === null) { + throw new NotFoundException(); + } + + $mac = $this->request->getParam('mac'); + if ($mac === null) { + throw new NoPermissionException(); + } + + if (!$this->crypto->verifyHMAC($token, $mac)) { + throw new NoPermissionException(); + } + + $controller->setToken($token); + + if ($controller->isValidToken()) { + return; + } + + throw new NotFoundException(); + } + + public function afterException($controller, $methodName, \Exception $exception) { + if (!($controller instanceof HookController)) { + throw $exception; + } + + if ($exception instanceof NotFoundException) { + return new JSONResponse(null, Http::STATUS_NOT_FOUND); + } + + if ($exception instanceof NoPermissionException) { + return new JSONResponse(null, Http::STATUS_FORBIDDEN); + } + + throw $exception; + } +}