You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

195 lines
5.8 KiB

<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2017 Joachim Bauch <bauch@struktur.de>
* @copyright Copyright (c) 2023 Daniel Calviño Sánchez <danxuliu@gmail.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Talk\Recording;
use GuzzleHttp\Exception\ClientException;
use GuzzleHttp\Exception\ConnectException;
use GuzzleHttp\Exception\ServerException;
use OCA\Talk\Config;
use OCA\Talk\Exceptions\RecordingNotFoundException;
use OCA\Talk\Participant;
use OCA\Talk\Room;
use OCP\AppFramework\Http;
use OCP\Http\Client\IClientService;
use OCP\IURLGenerator;
use OCP\Security\ISecureRandom;
use Psr\Log\LoggerInterface;
class BackendNotifier {
private Config $config;
private LoggerInterface $logger;
private IClientService $clientService;
private ISecureRandom $secureRandom;
private IURLGenerator $urlGenerator;
public function __construct(
Config $config,
LoggerInterface $logger,
IClientService $clientService,
ISecureRandom $secureRandom,
IURLGenerator $urlGenerator,
) {
$this->config = $config;
$this->logger = $logger;
$this->clientService = $clientService;
$this->secureRandom = $secureRandom;
$this->urlGenerator = $urlGenerator;
}
/**
* Perform actual network request to the recording backend.
* This can be overridden in tests.
*
* @param string $url
* @param array $params
* @param int $retries
* @throws \Exception
*/
protected function doRequest(string $url, array $params, int $retries = 3): void {
if (defined('PHPUNIT_RUN')) {
// Don't perform network requests when running tests.
return;
}
$client = $this->clientService->newClient();
try {
$response = $client->post($url, $params);
} catch (ServerException | ConnectException $e) {
if ($retries > 1) {
$this->logger->error('Failed to send message to recording server, ' . $retries . ' retries left!', ['exception' => $e]);
$this->doRequest($url, $params, $retries - 1);
} else {
$this->logger->error('Failed to send message to recording server, giving up!', ['exception' => $e]);
throw $e;
}
} catch (\Exception $e) {
$this->logger->error('Failed to send message to recording server', ['exception' => $e]);
throw $e;
}
}
/**
* Perform a request to the recording backend.
*
* @param Room $room
* @param array $data
* @throws \Exception
*/
private function backendRequest(Room $room, array $data): void {
$recordingServers = $this->config->getRecordingServers();
if (empty($recordingServers)) {
$this->logger->error('No configured recording server');
return;
}
// FIXME Currently clustering is not implemented in the recording
// server, so for now only the first configured server is taken into
// account to ensure that all the "stop" requests are sent to the same
// server that received the "start" request.
$recording = $recordingServers[0];
$recording['server'] = rtrim($recording['server'], '/');
$url = '/api/v1/room/' . $room->getToken();
$url = $recording['server'] . $url;
if (strpos($url, 'wss://') === 0) {
$url = 'https://' . substr($url, 6);
} elseif (strpos($url, 'ws://') === 0) {
$url = 'http://' . substr($url, 5);
}
$body = json_encode($data);
$headers = [
'Content-Type' => 'application/json',
];
$random = $this->secureRandom->generate(64);
$hash = hash_hmac('sha256', $random . $body, $this->config->getRecordingSecret());
$headers['Talk-Recording-Random'] = $random;
$headers['Talk-Recording-Checksum'] = $hash;
$headers['Talk-Recording-Backend'] = $this->urlGenerator->getAbsoluteURL('');
$params = [
'headers' => $headers,
'body' => $body,
'nextcloud' => [
'allow_local_address' => true,
],
];
if (empty($recording['verify'])) {
$params['verify'] = false;
}
$this->doRequest($url, $params);
}
public function start(Room $room, int $status, string $owner, Participant $participant): void {
$start = microtime(true);
$this->backendRequest($room, [
'type' => 'start',
'start' => [
'status' => $status,
'owner' => $owner,
'actor' => [
'type' => $participant->getAttendee()->getActorType(),
'id' => $participant->getAttendee()->getActorId(),
],
],
]);
$duration = microtime(true) - $start;
$this->logger->debug('Send start message: {token} ({duration})', [
'token' => $room->getToken(),
'duration' => sprintf('%.2f', $duration),
'app' => 'spreed-recording',
]);
}
public function stop(Room $room, ?Participant $participant = null): void {
$parameters = [];
if ($participant !== null) {
$parameters['actor'] = [
'type' => $participant->getAttendee()->getActorType(),
'id' => $participant->getAttendee()->getActorId(),
];
}
$start = microtime(true);
try {
$this->backendRequest($room, [
'type' => 'stop',
'stop' => $parameters,
]);
} catch (ClientException $e) {
if ($e->getResponse()->getStatusCode() === Http::STATUS_NOT_FOUND) {
throw new RecordingNotFoundException();
}
throw $e;
}
$duration = microtime(true) - $start;
$this->logger->debug('Send stop message: {token} ({duration})', [
'token' => $room->getToken(),
'duration' => sprintf('%.2f', $duration),
'app' => 'spreed-recording',
]);
}
}