diff --git a/lib/Controller/AEnvironmentAwareOCSController.php b/lib/Controller/AEnvironmentAwareOCSController.php index 988e5115216..402b8326747 100644 --- a/lib/Controller/AEnvironmentAwareOCSController.php +++ b/lib/Controller/AEnvironmentAwareOCSController.php @@ -63,7 +63,7 @@ public function getResponseFormat(): string { // if none is given try the first Accept header if ($format === null) { - $headers = $this->request->getHeader('Accept'); + $headers = $this->request->getHeader('accept'); /** * Default value of * @see OCSController::buildResponse() diff --git a/lib/Controller/AvatarController.php b/lib/Controller/AvatarController.php index 82b94cc950a..ce731ec049d 100644 --- a/lib/Controller/AvatarController.php +++ b/lib/Controller/AvatarController.php @@ -25,6 +25,7 @@ use OCP\AppFramework\Http\Attribute\NoCSRFRequired; use OCP\AppFramework\Http\Attribute\OpenAPI; use OCP\AppFramework\Http\Attribute\PublicPage; +use OCP\AppFramework\Http\Attribute\RequestHeader; use OCP\AppFramework\Http\DataResponse; use OCP\AppFramework\Http\FileDisplayResponse; use OCP\Federation\ICloudIdManager; @@ -129,6 +130,7 @@ public function emojiAvatar(string $emoji, ?string $color): DataResponse { #[NoCSRFRequired] #[AllowWithoutParticipantWhenPendingInvitation] #[RequireParticipantOrLoggedInAndListedConversation] + #[RequestHeader(name: 'x-nextcloud-federation', description: 'Set to 1 when the request is performed by another Nextcloud Server to indicate a federation request', indirect: true)] public function getAvatar(bool $darkTheme = false): FileDisplayResponse { // Cache for 1 day $cacheDuration = 60 * 60 * 24; @@ -162,6 +164,7 @@ public function getAvatar(bool $darkTheme = false): FileDisplayResponse { #[NoCSRFRequired] #[AllowWithoutParticipantWhenPendingInvitation] #[RequireParticipantOrLoggedInAndListedConversation] + #[RequestHeader(name: 'x-nextcloud-federation', description: 'Set to 1 when the request is performed by another Nextcloud Server to indicate a federation request', indirect: true)] public function getAvatarDark(): FileDisplayResponse { return $this->getAvatar(true); } @@ -181,6 +184,7 @@ public function getAvatarDark(): FileDisplayResponse { #[OpenAPI(scope: OpenAPI::SCOPE_FEDERATION)] #[NoAdminRequired] #[NoCSRFRequired] + #[RequestHeader(name: 'x-nextcloud-federation', description: 'Set to 1 when the request is performed by another Nextcloud Server to indicate a federation request', indirect: true)] public function getUserProxyAvatarWithoutRoom(int $size, string $cloudId, bool $darkTheme = false): FileDisplayResponse { return $this->getUserProxyAvatar($size, $cloudId, $darkTheme); } @@ -199,6 +203,7 @@ public function getUserProxyAvatarWithoutRoom(int $size, string $cloudId, bool $ #[OpenAPI(scope: OpenAPI::SCOPE_FEDERATION)] #[NoAdminRequired] #[NoCSRFRequired] + #[RequestHeader(name: 'x-nextcloud-federation', description: 'Set to 1 when the request is performed by another Nextcloud Server to indicate a federation request', indirect: true)] public function getUserProxyAvatarDarkWithoutRoom(int $size, string $cloudId): FileDisplayResponse { return $this->getUserProxyAvatar($size, $cloudId, true); } @@ -221,6 +226,7 @@ public function getUserProxyAvatarDarkWithoutRoom(int $size, string $cloudId): F #[NoCSRFRequired] #[AllowWithoutParticipantWhenPendingInvitation] #[RequireLoggedInParticipant] + #[RequestHeader(name: 'x-nextcloud-federation', description: 'Set to 1 when the request is performed by another Nextcloud Server to indicate a federation request', indirect: true)] public function getUserProxyAvatar(int $size, string $cloudId, bool $darkTheme = false): FileDisplayResponse { try { $resolvedCloudId = $this->cloudIdManager->resolveCloudId($cloudId); @@ -281,6 +287,7 @@ public function getUserProxyAvatar(int $size, string $cloudId, bool $darkTheme = #[NoCSRFRequired] #[AllowWithoutParticipantWhenPendingInvitation] #[RequireLoggedInParticipant] + #[RequestHeader(name: 'x-nextcloud-federation', description: 'Set to 1 when the request is performed by another Nextcloud Server to indicate a federation request', indirect: true)] public function getUserProxyAvatarDark(int $size, string $cloudId): FileDisplayResponse { return $this->getUserProxyAvatar($size, $cloudId, true); } diff --git a/lib/Controller/BotController.php b/lib/Controller/BotController.php index 282d94a88da..0b018c8f488 100644 --- a/lib/Controller/BotController.php +++ b/lib/Controller/BotController.php @@ -36,6 +36,7 @@ use OCP\AppFramework\Http\Attribute\NoAdminRequired; use OCP\AppFramework\Http\Attribute\OpenAPI; use OCP\AppFramework\Http\Attribute\PublicPage; +use OCP\AppFramework\Http\Attribute\RequestHeader; use OCP\AppFramework\Http\DataResponse; use OCP\AppFramework\Utility\ITimeFactory; use OCP\Comments\MessageTooLongException; @@ -73,13 +74,15 @@ public function __construct( * @return Bot * @throws \InvalidArgumentException When the request could not be linked with a bot */ + #[RequestHeader(name: 'x-nextcloud-talk-bot-random', description: 'Random seed used to generate the request signature')] + #[RequestHeader(name: 'x-nextcloud-talk-bot-signature', description: 'Signature over the request body to verify authenticity')] protected function getBotFromHeaders(string $token, string $message): Bot { - $random = $this->request->getHeader('X-Nextcloud-Talk-Bot-Random'); + $random = $this->request->getHeader('x-nextcloud-talk-bot-random'); if (empty($random) || strlen($random) < 32) { $this->logger->error('Invalid Random received from bot response'); throw new \InvalidArgumentException('Invalid Random received from bot response', Http::STATUS_BAD_REQUEST); } - $checksum = $this->request->getHeader('X-Nextcloud-Talk-Bot-Signature'); + $checksum = $this->request->getHeader('x-nextcloud-talk-bot-signature'); if (empty($checksum)) { $this->logger->error('Invalid Signature received from bot response'); throw new \InvalidArgumentException('Invalid Signature received from bot response', Http::STATUS_BAD_REQUEST); diff --git a/lib/Controller/CallController.php b/lib/Controller/CallController.php index 5cdcde8f2da..99b79c85bd0 100644 --- a/lib/Controller/CallController.php +++ b/lib/Controller/CallController.php @@ -33,7 +33,9 @@ use OCP\AppFramework\Db\DoesNotExistException; use OCP\AppFramework\Http; use OCP\AppFramework\Http\Attribute\BruteForceProtection; +use OCP\AppFramework\Http\Attribute\NoCSRFRequired; use OCP\AppFramework\Http\Attribute\PublicPage; +use OCP\AppFramework\Http\Attribute\RequestHeader; use OCP\AppFramework\Http\DataDownloadResponse; use OCP\AppFramework\Http\DataResponse; use OCP\AppFramework\Http\Response; @@ -77,6 +79,7 @@ public function __construct( #[RequireModeratorOrNoLobby] #[RequireParticipant] #[RequireReadWriteConversation] + #[RequestHeader(name: 'x-nextcloud-federation', description: 'Set to 1 when the request is performed by another Nextcloud Server to indicate a federation request', indirect: true)] public function getPeersForCall(): DataResponse { if ($this->room->isFederatedConversation()) { /** @var \OCA\Talk\Federation\Proxy\TalkV1\Controller\CallController $proxy */ @@ -129,7 +132,7 @@ public function getPeersForCall(): DataResponse { */ #[PublicPage] #[RequireModeratorParticipant] - #[Http\Attribute\NoCSRFRequired] + #[NoCSRFRequired] public function downloadParticipantsForCall(string $format = 'csv'): DataDownloadResponse|Response { $callStart = $this->room->getActiveSince()?->getTimestamp() ?? 0; if ($callStart === 0) { @@ -223,6 +226,7 @@ protected function escapeFormulae(string $value): string { #[RequireModeratorOrNoLobby] #[RequireParticipant] #[RequireReadWriteConversation] + #[RequestHeader(name: 'x-nextcloud-federation', description: 'Set to 1 when the request is performed by another Nextcloud Server to indicate a federation request', indirect: true)] public function joinCall(?int $flags = null, bool $silent = false, bool $recordingConsent = false, array $silentFor = []): DataResponse { try { $this->validateRecordingConsent($recordingConsent); @@ -344,6 +348,7 @@ public function joinFederatedCall(string $sessionId, ?int $flags = null, bool $s #[RequireCallEnabled] #[RequireParticipant] #[RequirePermission(permission: RequirePermission::START_CALL)] + #[RequestHeader(name: 'x-nextcloud-federation', description: 'Set to 1 when the request is performed by another Nextcloud Server to indicate a federation request', indirect: true)] public function ringAttendee(int $attendeeId): DataResponse { if ($this->room->isFederatedConversation()) { /** @var \OCA\Talk\Federation\Proxy\TalkV1\Controller\CallController $proxy */ @@ -424,6 +429,7 @@ public function sipDialOut(int $attendeeId): DataResponse { #[FederationSupported] #[PublicPage] #[RequireParticipant] + #[RequestHeader(name: 'x-nextcloud-federation', description: 'Set to 1 when the request is performed by another Nextcloud Server to indicate a federation request', indirect: true)] public function updateCallFlags(int $flags): DataResponse { $session = $this->participant->getSession(); if (!$session instanceof Session) { @@ -496,6 +502,7 @@ public function updateFederatedCallFlags(string $sessionId, int $flags): DataRes #[FederationSupported] #[PublicPage] #[RequireParticipant] + #[RequestHeader(name: 'x-nextcloud-federation', description: 'Set to 1 when the request is performed by another Nextcloud Server to indicate a federation request', indirect: true)] public function leaveCall(bool $all = false): DataResponse { $session = $this->participant->getSession(); if (!$session instanceof Session) { diff --git a/lib/Controller/ChatController.php b/lib/Controller/ChatController.php index ee1025bc464..6378ab11e5a 100644 --- a/lib/Controller/ChatController.php +++ b/lib/Controller/ChatController.php @@ -53,6 +53,7 @@ use OCP\AppFramework\Http; use OCP\AppFramework\Http\Attribute\NoAdminRequired; use OCP\AppFramework\Http\Attribute\PublicPage; +use OCP\AppFramework\Http\Attribute\RequestHeader; use OCP\AppFramework\Http\Attribute\UserRateLimit; use OCP\AppFramework\Http\DataResponse; use OCP\AppFramework\Services\IAppConfig; @@ -215,6 +216,7 @@ protected function parseCommentToResponse(IComment $comment, ?Message $parentMes #[RequireParticipant] #[RequirePermission(permission: RequirePermission::CHAT)] #[RequireReadWriteConversation] + #[RequestHeader(name: 'x-nextcloud-federation', description: 'Set to 1 when the request is performed by another Nextcloud Server to indicate a federation request', indirect: true)] public function sendMessage(string $message, string $actorDisplayName = '', string $referenceId = '', int $replyTo = 0, bool $silent = false): DataResponse { if ($this->room->isFederatedConversation()) { /** @var \OCA\Talk\Federation\Proxy\TalkV1\Controller\ChatController $proxy */ @@ -392,6 +394,7 @@ public function shareObjectToChat(string $objectType, string $objectId, string $ #[PublicPage] #[RequireModeratorOrNoLobby] #[RequireParticipant] + #[RequestHeader(name: 'x-nextcloud-federation', description: 'Set to 1 when the request is performed by another Nextcloud Server to indicate a federation request', indirect: true)] public function receiveMessages(int $lookIntoFuture, int $limit = 100, int $lastKnownMessageId = 0, @@ -749,6 +752,7 @@ protected function prepareCommentsAsDataResponse(array $comments, int $lastCommo #[PublicPage] #[RequireModeratorOrNoLobby] #[RequireParticipant] + #[RequestHeader(name: 'x-nextcloud-federation', description: 'Set to 1 when the request is performed by another Nextcloud Server to indicate a federation request', indirect: true)] public function getMessageContext( int $messageId, int $limit = 50): DataResponse { @@ -839,6 +843,7 @@ protected function loadSelfReactions(array $messages, array $commentIdToIndex): #[RequireAuthenticatedParticipant] #[RequirePermission(permission: RequirePermission::CHAT)] #[RequireReadWriteConversation] + #[RequestHeader(name: 'x-nextcloud-federation', description: 'Set to 1 when the request is performed by another Nextcloud Server to indicate a federation request', indirect: true)] public function deleteMessage(int $messageId): DataResponse { if ($this->room->isFederatedConversation()) { /** @var \OCA\Talk\Federation\Proxy\TalkV1\Controller\ChatController $proxy */ @@ -931,6 +936,7 @@ public function deleteMessage(int $messageId): DataResponse { #[RequireAuthenticatedParticipant] #[RequirePermission(permission: RequirePermission::CHAT)] #[RequireReadWriteConversation] + #[RequestHeader(name: 'x-nextcloud-federation', description: 'Set to 1 when the request is performed by another Nextcloud Server to indicate a federation request', indirect: true)] public function editMessage(int $messageId, string $message): DataResponse { if ($this->room->isFederatedConversation()) { /** @var \OCA\Talk\Federation\Proxy\TalkV1\Controller\ChatController $proxy */ @@ -1037,6 +1043,7 @@ public function editMessage(int $messageId, string $message): DataResponse { #[RequireModeratorOrNoLobby] #[RequireLoggedInParticipant] #[UserRateLimit(limit: 60, period: 3600)] + #[RequestHeader(name: 'x-nextcloud-federation', description: 'Set to 1 when the request is performed by another Nextcloud Server to indicate a federation request', indirect: true)] public function setReminder(int $messageId, int $timestamp): DataResponse { try { // FIXME fail 400 when reminder is after expiration @@ -1071,6 +1078,7 @@ public function setReminder(int $messageId, int $timestamp): DataResponse { #[NoAdminRequired] #[RequireModeratorOrNoLobby] #[RequireLoggedInParticipant] + #[RequestHeader(name: 'x-nextcloud-federation', description: 'Set to 1 when the request is performed by another Nextcloud Server to indicate a federation request', indirect: true)] public function getReminder(int $messageId): DataResponse { try { $this->validateMessageExists($messageId); @@ -1104,6 +1112,7 @@ public function getReminder(int $messageId): DataResponse { #[NoAdminRequired] #[RequireModeratorOrNoLobby] #[RequireLoggedInParticipant] + #[RequestHeader(name: 'x-nextcloud-federation', description: 'Set to 1 when the request is performed by another Nextcloud Server to indicate a federation request', indirect: true)] public function deleteReminder(int $messageId): DataResponse { try { $this->validateMessageExists($messageId); @@ -1290,6 +1299,7 @@ public function clearHistory(): DataResponse { #[FederationSupported] #[PublicPage] #[RequireAuthenticatedParticipant] + #[RequestHeader(name: 'x-nextcloud-federation', description: 'Set to 1 when the request is performed by another Nextcloud Server to indicate a federation request', indirect: true)] public function setReadMarker(?int $lastReadMessage = null): DataResponse { $setToMessage = $lastReadMessage ?? $this->room->getLastMessageId(); if ($setToMessage === 0) { @@ -1339,6 +1349,7 @@ public function setReadMarker(?int $lastReadMessage = null): DataResponse { #[FederationSupported] #[PublicPage] #[RequireAuthenticatedParticipant] + #[RequestHeader(name: 'x-nextcloud-federation', description: 'Set to 1 when the request is performed by another Nextcloud Server to indicate a federation request', indirect: true)] public function markUnread(): DataResponse { if ($this->room->isFederatedConversation()) { /** @var \OCA\Talk\Federation\Proxy\TalkV1\Controller\ChatController $proxy */ @@ -1515,6 +1526,7 @@ protected function getMessagesForRoom(array $messageIds): array { #[RequireParticipant] #[RequirePermission(permission: RequirePermission::CHAT)] #[RequireReadWriteConversation] + #[RequestHeader(name: 'x-nextcloud-federation', description: 'Set to 1 when the request is performed by another Nextcloud Server to indicate a federation request', indirect: true)] public function mentions(string $search, int $limit = 20, bool $includeStatus = false): DataResponse { if ($this->room->isFederatedConversation()) { /** @var \OCA\Talk\Federation\Proxy\TalkV1\Controller\ChatController $proxy */ diff --git a/lib/Controller/FederationController.php b/lib/Controller/FederationController.php index 5c45e5f03ee..1267bccb706 100644 --- a/lib/Controller/FederationController.php +++ b/lib/Controller/FederationController.php @@ -52,7 +52,7 @@ public function getResponseFormat(): string { // if none is given try the first Accept header if ($format === null) { - $headers = $this->request->getHeader('Accept'); + $headers = $this->request->getHeader('accept'); /** * Default value of * @see OCSController::buildResponse() diff --git a/lib/Controller/PollController.php b/lib/Controller/PollController.php index d838e5b97fa..19e9c2eae43 100644 --- a/lib/Controller/PollController.php +++ b/lib/Controller/PollController.php @@ -28,6 +28,7 @@ use OCP\AppFramework\Db\DoesNotExistException; use OCP\AppFramework\Http; use OCP\AppFramework\Http\Attribute\PublicPage; +use OCP\AppFramework\Http\Attribute\RequestHeader; use OCP\AppFramework\Http\DataResponse; use OCP\AppFramework\Utility\ITimeFactory; use OCP\IRequest; @@ -73,6 +74,7 @@ public function __construct( #[RequireParticipant] #[RequirePermission(permission: RequirePermission::CHAT)] #[RequireReadWriteConversation] + #[RequestHeader(name: 'x-nextcloud-federation', description: 'Set to 1 when the request is performed by another Nextcloud Server to indicate a federation request', indirect: true)] public function createPoll(string $question, array $options, int $resultMode, int $maxVotes, bool $draft = false): DataResponse { if ($this->room->isFederatedConversation()) { /** @var \OCA\Talk\Federation\Proxy\TalkV1\Controller\PollController $proxy */ @@ -158,6 +160,7 @@ public function createPoll(string $question, array $options, int $resultMode, in #[RequireParticipant] #[RequirePermission(permission: RequirePermission::CHAT)] #[RequireReadWriteConversation] + #[RequestHeader(name: 'x-nextcloud-federation', description: 'Set to 1 when the request is performed by another Nextcloud Server to indicate a federation request', indirect: true)] public function updateDraftPoll(int $pollId, string $question, array $options, int $resultMode, int $maxVotes): DataResponse { if ($this->room->isFederatedConversation()) { /** @var \OCA\Talk\Federation\Proxy\TalkV1\Controller\PollController $proxy */ @@ -220,6 +223,7 @@ public function updateDraftPoll(int $pollId, string $question, array $options, i #[FederationSupported] #[PublicPage] #[RequireModeratorParticipant] + #[RequestHeader(name: 'x-nextcloud-federation', description: 'Set to 1 when the request is performed by another Nextcloud Server to indicate a federation request', indirect: true)] public function getAllDraftPolls(): DataResponse { if ($this->room->isFederatedConversation()) { /** @var \OCA\Talk\Federation\Proxy\TalkV1\Controller\PollController $proxy */ @@ -250,6 +254,7 @@ public function getAllDraftPolls(): DataResponse { #[PublicPage] #[RequireModeratorOrNoLobby] #[RequireParticipant] + #[RequestHeader(name: 'x-nextcloud-federation', description: 'Set to 1 when the request is performed by another Nextcloud Server to indicate a federation request', indirect: true)] public function showPoll(int $pollId): DataResponse { if ($this->room->isFederatedConversation()) { /** @var \OCA\Talk\Federation\Proxy\TalkV1\Controller\PollController $proxy */ @@ -292,6 +297,7 @@ public function showPoll(int $pollId): DataResponse { #[PublicPage] #[RequireModeratorOrNoLobby] #[RequireParticipant] + #[RequestHeader(name: 'x-nextcloud-federation', description: 'Set to 1 when the request is performed by another Nextcloud Server to indicate a federation request', indirect: true)] public function votePoll(int $pollId, array $optionIds = []): DataResponse { if ($this->room->isFederatedConversation()) { /** @var \OCA\Talk\Federation\Proxy\TalkV1\Controller\PollController $proxy */ @@ -358,6 +364,7 @@ public function votePoll(int $pollId, array $optionIds = []): DataResponse { #[PublicPage] #[RequireModeratorOrNoLobby] #[RequireParticipant] + #[RequestHeader(name: 'x-nextcloud-federation', description: 'Set to 1 when the request is performed by another Nextcloud Server to indicate a federation request', indirect: true)] public function closePoll(int $pollId): DataResponse { if ($this->room->isFederatedConversation()) { /** @var \OCA\Talk\Federation\Proxy\TalkV1\Controller\PollController $proxy */ diff --git a/lib/Controller/ReactionController.php b/lib/Controller/ReactionController.php index 55152d72478..8fa1e215119 100644 --- a/lib/Controller/ReactionController.php +++ b/lib/Controller/ReactionController.php @@ -20,6 +20,7 @@ use OCA\Talk\ResponseDefinitions; use OCP\AppFramework\Http; use OCP\AppFramework\Http\Attribute\PublicPage; +use OCP\AppFramework\Http\Attribute\RequestHeader; use OCP\AppFramework\Http\DataResponse; use OCP\Comments\NotFoundException; use OCP\IRequest; @@ -56,6 +57,7 @@ public function __construct( #[RequireParticipant] #[RequirePermission(permission: RequirePermission::CHAT)] #[RequireReadWriteConversation] + #[RequestHeader(name: 'x-nextcloud-federation', description: 'Set to 1 when the request is performed by another Nextcloud Server to indicate a federation request', indirect: true)] public function react(int $messageId, string $reaction): DataResponse { if ($this->room->isFederatedConversation()) { /** @var \OCA\Talk\Federation\Proxy\TalkV1\Controller\ReactionController $proxy */ @@ -102,6 +104,7 @@ public function react(int $messageId, string $reaction): DataResponse { #[RequireParticipant] #[RequirePermission(permission: RequirePermission::CHAT)] #[RequireReadWriteConversation] + #[RequestHeader(name: 'x-nextcloud-federation', description: 'Set to 1 when the request is performed by another Nextcloud Server to indicate a federation request', indirect: true)] public function delete(int $messageId, string $reaction): DataResponse { if ($this->room->isFederatedConversation()) { /** @var \OCA\Talk\Federation\Proxy\TalkV1\Controller\ReactionController $proxy */ @@ -143,6 +146,7 @@ public function delete(int $messageId, string $reaction): DataResponse { #[PublicPage] #[RequireModeratorOrNoLobby] #[RequireParticipant] + #[RequestHeader(name: 'x-nextcloud-federation', description: 'Set to 1 when the request is performed by another Nextcloud Server to indicate a federation request', indirect: true)] public function getReactions(int $messageId, ?string $reaction): DataResponse { if ($this->room->isFederatedConversation()) { /** @var \OCA\Talk\Federation\Proxy\TalkV1\Controller\ReactionController $proxy */ diff --git a/lib/Controller/RecordingController.php b/lib/Controller/RecordingController.php index 8c56ae21501..1196371177c 100644 --- a/lib/Controller/RecordingController.php +++ b/lib/Controller/RecordingController.php @@ -27,6 +27,7 @@ use OCP\AppFramework\Http\Attribute\NoAdminRequired; use OCP\AppFramework\Http\Attribute\OpenAPI; use OCP\AppFramework\Http\Attribute\PublicPage; +use OCP\AppFramework\Http\Attribute\RequestHeader; use OCP\AppFramework\Http\DataResponse; use OCP\AppFramework\Utility\ITimeFactory; use OCP\Http\Client\IClientService; @@ -136,12 +137,12 @@ public function getWelcomeMessage(int $serverId): DataResponse { * @return bool */ private function validateBackendRequest(string $data): bool { - $random = $this->request->getHeader('Talk-Recording-Random'); + $random = $this->request->getHeader('talk-recording-random'); if (empty($random) || strlen($random) < 32) { $this->logger->debug('Missing random'); return false; } - $checksum = $this->request->getHeader('Talk-Recording-Checksum'); + $checksum = $this->request->getHeader('talk-recording-checksum'); if (empty($checksum)) { $this->logger->debug('Missing checksum'); return false; @@ -174,6 +175,8 @@ protected function getInputStream(): string { #[PublicPage] #[BruteForceProtection(action: 'talkRecordingSecret')] #[BruteForceProtection(action: 'talkRecordingStatus')] + #[RequestHeader(name: 'talk-recording-random', description: 'Random seed used to generate the request checksum', indirect: true)] + #[RequestHeader(name: 'talk-recording-checksum', description: 'Checksum over the request body to verify authenticity from the recording backend', indirect: true)] public function backend(): DataResponse { $json = $this->getInputStream(); if (!$this->validateBackendRequest($json)) { @@ -380,6 +383,8 @@ public function stop(): DataResponse { #[BruteForceProtection(action: 'talkRecordingSecret')] #[OpenAPI(scope: 'backend-recording')] #[RequireRoom] + #[RequestHeader(name: 'talk-recording-random', description: 'Random seed used to generate the request checksum', indirect: true)] + #[RequestHeader(name: 'talk-recording-checksum', description: 'Checksum over the request body to verify authenticity from the recording backend', indirect: true)] public function store(?string $owner): DataResponse { $data = $this->room->getToken(); if (!$this->validateBackendRequest($data)) { diff --git a/lib/Controller/RoomController.php b/lib/Controller/RoomController.php index 2c2fc71a402..f81c8f6c268 100644 --- a/lib/Controller/RoomController.php +++ b/lib/Controller/RoomController.php @@ -77,6 +77,7 @@ use OCP\AppFramework\Http\Attribute\NoAdminRequired; use OCP\AppFramework\Http\Attribute\OpenAPI; use OCP\AppFramework\Http\Attribute\PublicPage; +use OCP\AppFramework\Http\Attribute\RequestHeader; use OCP\AppFramework\Http\DataResponse; use OCP\AppFramework\Services\IAppConfig; use OCP\AppFramework\Utility\ITimeFactory; @@ -392,6 +393,9 @@ public function getBreakoutRooms(): DataResponse { #[BruteForceProtection(action: 'talkSipBridgeSecret')] #[OpenAPI] #[OpenAPI(scope: 'backend-sipbridge')] + #[RequestHeader(name: 'x-nextcloud-federation', description: 'Set to 1 when the request is performed by another Nextcloud Server to indicate a federation request', indirect: true)] + #[RequestHeader(name: 'talk-sipbridge-random', description: 'Random seed used to generate the request checksum', indirect: true)] + #[RequestHeader(name: 'talk-sipbridge-checksum', description: 'Checksum over the request body to verify authenticity from the Sipbridge', indirect: true)] public function getSingleRoom(string $token): DataResponse { try { $isSIPBridgeRequest = $this->validateSIPBridgeRequest($token); @@ -412,7 +416,7 @@ public function getSingleRoom(string $token): DataResponse { $action = 'talkRoomToken'; $participant = null; - $isTalkFederation = $this->request->getHeader('X-Nextcloud-Federation'); + $isTalkFederation = $this->request->getHeader('x-nextcloud-federation'); if (!$isTalkFederation) { $sessionId = $this->session->getSessionForRoom($token); @@ -510,8 +514,8 @@ public function getNoteToSelfConversation(): DataResponse { * @throws UnauthorizedException when the request tried to sign as SIP bridge but is not valid */ private function validateSIPBridgeRequest(string $token): bool { - $random = $this->request->getHeader('TALK_SIPBRIDGE_RANDOM'); - $checksum = $this->request->getHeader('TALK_SIPBRIDGE_CHECKSUM'); + $random = $this->request->getHeader('talk-sipbridge-random'); + $checksum = $this->request->getHeader('talk-sipbridge-checksum'); $secret = $this->talkConfig->getSIPSharedSecret(); return $this->checksumVerificationService->validateRequest($random, $checksum, $secret, $token); } @@ -860,6 +864,7 @@ public function setNotificationLevel(int $level): DataResponse { * 200: Call notification level updated successfully * 400: Updating call notification level is not possible */ + #[FederationSupported] #[NoAdminRequired] #[RequireLoggedInParticipant] public function setNotificationCalls(int $level): DataResponse { @@ -980,6 +985,7 @@ public function unbindRoomFromObject(): DataResponse { #[PublicPage] #[RequireModeratorOrNoLobby] #[RequireParticipant] + #[RequestHeader(name: 'x-nextcloud-federation', description: 'Set to 1 when the request is performed by another Nextcloud Server to indicate a federation request', indirect: true)] public function getParticipants(bool $includeStatus = false): DataResponse { if ($this->room->isFederatedConversation()) { /** @var \OCA\Talk\Federation\Proxy\TalkV1\Controller\RoomController $proxy */ @@ -1454,6 +1460,7 @@ public function addParticipantToRoom(string $newParticipant, string $source = 'u #[FederationSupported] #[NoAdminRequired] #[RequireLoggedInParticipant] + #[RequestHeader(name: 'x-nextcloud-federation', description: 'Set to 1 when the request is performed by another Nextcloud Server to indicate a federation request', indirect: true)] public function removeSelfFromRoom(): DataResponse { return $this->removeSelfFromRoomLogic($this->room, $this->participant); } @@ -1976,6 +1983,7 @@ public function joinRoom(string $token, string $password = '', bool $force = tru #[PublicPage] #[BruteForceProtection(action: 'talkRoomToken')] #[BruteForceProtection(action: 'talkFederationAccess')] + #[RequestHeader(name: 'x-nextcloud-federation', description: 'Set to 1 when the request is performed by another Nextcloud Server to indicate a federation request', indirect: true)] public function joinFederatedRoom(string $token, ?string $sessionId): DataResponse { if (!$this->federationAuthenticator->isFederationRequest()) { $response = new DataResponse(null, Http::STATUS_NOT_FOUND); @@ -2028,6 +2036,8 @@ public function joinFederatedRoom(string $token, ?string $sessionId): DataRespon #[BruteForceProtection(action: 'talkSipBridgeSecret')] #[OpenAPI(scope: 'backend-sipbridge')] #[RequireRoom] + #[RequestHeader(name: 'talk-sipbridge-random', description: 'Random seed used to generate the request checksum', indirect: true)] + #[RequestHeader(name: 'talk-sipbridge-checksum', description: 'Checksum over the request body to verify authenticity from the Sipbridge', indirect: true)] public function verifyDialInPin(string $pin): DataResponse { if (!$this->talkConfig->isSIPConfigured()) { return new DataResponse(null, Http::STATUS_NOT_IMPLEMENTED); @@ -2072,6 +2082,8 @@ public function verifyDialInPin(string $pin): DataResponse { #[PublicPage] #[BruteForceProtection(action: 'talkSipBridgeSecret')] #[OpenAPI(scope: 'backend-sipbridge')] + #[RequestHeader(name: 'talk-sipbridge-random', description: 'Random seed used to generate the request checksum', indirect: true)] + #[RequestHeader(name: 'talk-sipbridge-checksum', description: 'Checksum over the request body to verify authenticity from the Sipbridge', indirect: true)] public function directDialIn(string $phoneNumber, string $caller): DataResponse { if (!$this->talkConfig->isSIPConfigured()) { return new DataResponse(null, Http::STATUS_NOT_IMPLEMENTED); @@ -2133,6 +2145,8 @@ public function directDialIn(string $phoneNumber, string $caller): DataResponse #[BruteForceProtection(action: 'talkSipBridgeSecret')] #[OpenAPI(scope: 'backend-sipbridge')] #[RequireRoom] + #[RequestHeader(name: 'talk-sipbridge-random', description: 'Random seed used to generate the request checksum', indirect: true)] + #[RequestHeader(name: 'talk-sipbridge-checksum', description: 'Checksum over the request body to verify authenticity from the Sipbridge', indirect: true)] public function verifyDialOutNumber(string $number, array $options = []): DataResponse { if (!$this->talkConfig->isSIPConfigured() || !$this->talkConfig->isSIPDialOutEnabled()) { return new DataResponse(null, Http::STATUS_NOT_IMPLEMENTED); @@ -2180,6 +2194,8 @@ public function verifyDialOutNumber(string $number, array $options = []): DataRe #[BruteForceProtection(action: 'talkSipBridgeSecret')] #[OpenAPI(scope: 'backend-sipbridge')] #[RequireRoom] + #[RequestHeader(name: 'talk-sipbridge-random', description: 'Random seed used to generate the request checksum', indirect: true)] + #[RequestHeader(name: 'talk-sipbridge-checksum', description: 'Checksum over the request body to verify authenticity from the Sipbridge', indirect: true)] public function createGuestByDialIn(): DataResponse { try { if (!$this->validateSIPBridgeRequest($this->room->getToken())) { @@ -2219,6 +2235,8 @@ public function createGuestByDialIn(): DataResponse { #[BruteForceProtection(action: 'talkSipBridgeSecret')] #[OpenAPI(scope: 'backend-sipbridge')] #[RequireRoom] + #[RequestHeader(name: 'talk-sipbridge-random', description: 'Random seed used to generate the request checksum', indirect: true)] + #[RequestHeader(name: 'talk-sipbridge-checksum', description: 'Checksum over the request body to verify authenticity from the Sipbridge', indirect: true)] public function rejectedDialOutRequest(string $callId, array $options = []): DataResponse { if (!$this->talkConfig->isSIPConfigured() || !$this->talkConfig->isSIPDialOutEnabled()) { return new DataResponse(null, Http::STATUS_NOT_IMPLEMENTED); @@ -2322,6 +2340,7 @@ public function leaveRoom(string $token): DataResponse { #[OpenAPI(scope: OpenAPI::SCOPE_FEDERATION)] #[PublicPage] #[BruteForceProtection(action: 'talkRoomToken')] + #[RequestHeader(name: 'x-nextcloud-federation', description: 'Set to 1 when the request is performed by another Nextcloud Server to indicate a federation request', indirect: true)] public function leaveFederatedRoom(string $token, string $sessionId): DataResponse { if (!$this->federationAuthenticator->isFederationRequest()) { $response = new DataResponse(null, Http::STATUS_NOT_FOUND); @@ -2762,6 +2781,7 @@ public function importEmailsAsParticipants(bool $testRun = false): DataResponse #[FederationSupported] #[PublicPage] #[RequireParticipant] + #[RequestHeader(name: 'x-nextcloud-federation', description: 'Set to 1 when the request is performed by another Nextcloud Server to indicate a federation request', indirect: true)] public function getCapabilities(): DataResponse { $headers = []; if ($this->room->isFederatedConversation()) { diff --git a/lib/Controller/SignalingController.php b/lib/Controller/SignalingController.php index 07f14d760d0..7699278163d 100644 --- a/lib/Controller/SignalingController.php +++ b/lib/Controller/SignalingController.php @@ -30,6 +30,7 @@ use OCP\AppFramework\Http\Attribute\BruteForceProtection; use OCP\AppFramework\Http\Attribute\OpenAPI; use OCP\AppFramework\Http\Attribute\PublicPage; +use OCP\AppFramework\Http\Attribute\RequestHeader; use OCP\AppFramework\Http\DataResponse; use OCP\AppFramework\OCSController; use OCP\AppFramework\Utility\ITimeFactory; @@ -88,12 +89,12 @@ public function __construct( * @return bool */ private function validateRecordingBackendRequest(string $data): bool { - $random = $this->request->getHeader('Talk-Recording-Random'); + $random = $this->request->getHeader('talk-recording-random'); if (empty($random) || strlen($random) < 32) { $this->logger->debug('Missing random'); return false; } - $checksum = $this->request->getHeader('Talk-Recording-Checksum'); + $checksum = $this->request->getHeader('talk-recording-checksum'); if (empty($checksum)) { $this->logger->debug('Missing checksum'); return false; @@ -117,10 +118,12 @@ private function validateRecordingBackendRequest(string $data): bool { #[BruteForceProtection(action: 'talkRecordingSecret')] #[BruteForceProtection(action: 'talkFederationAccess')] #[OpenAPI(tags: ['internal_signaling', 'external_signaling'])] + #[RequestHeader(name: 'talk-recording-random', description: 'Random seed used to generate the request checksum', indirect: true)] + #[RequestHeader(name: 'talk-recording-checksum', description: 'Checksum over the request body to verify authenticity from the recording backend', indirect: true)] public function getSettings(string $token = ''): DataResponse { $isRecordingRequest = false; - if (!empty($this->request->getHeader('Talk-Recording-Random')) || !empty($this->request->getHeader('Talk-Recording-Checksum'))) { + if (!empty($this->request->getHeader('talk-recording-random')) || !empty($this->request->getHeader('talk-recording-checksum'))) { if (!$this->validateRecordingBackendRequest('')) { $response = new DataResponse(null, Http::STATUS_UNAUTHORIZED); $response->throttle(['action' => 'talkRecordingSecret']); @@ -651,6 +654,8 @@ protected function getInputStream(): string { #[OpenAPI(scope: 'backend-signaling')] #[PublicPage] #[BruteForceProtection(action: 'talkSignalingSecret')] + #[RequestHeader(name: 'spreed-signaling-random', description: 'Random seed used to generate the request checksum', indirect: true)] + #[RequestHeader(name: 'spreed-signaling-checksum', description: 'Checksum over the request body to verify authenticity from the signaling backend', indirect: true)] public function backend(): DataResponse { $json = $this->getInputStream(); if (!$this->validateBackendRequest($json)) { diff --git a/lib/Federation/Authenticator.php b/lib/Federation/Authenticator.php index 57faaae0350..a31ad08e116 100644 --- a/lib/Federation/Authenticator.php +++ b/lib/Federation/Authenticator.php @@ -30,7 +30,7 @@ public function __construct( } protected function readHeaders(): void { - $this->isFederationRequest = (bool)$this->request->getHeader('X-Nextcloud-Federation'); + $this->isFederationRequest = (bool)$this->request->getHeader('x-nextcloud-federation'); if (!$this->isFederationRequest) { $this->federationCloudId = ''; $this->accessToken = ''; diff --git a/openapi-backend-sipbridge.json b/openapi-backend-sipbridge.json index 3e4ff4fa5d6..7064ed7a224 100644 --- a/openapi-backend-sipbridge.json +++ b/openapi-backend-sipbridge.json @@ -979,7 +979,7 @@ } }, { - "name": "X-Nextcloud-Federation", + "name": "x-nextcloud-federation", "in": "header", "schema": { "type": "string" diff --git a/openapi-full.json b/openapi-full.json index 4082be42418..e7b563957b6 100644 --- a/openapi-full.json +++ b/openapi-full.json @@ -13201,7 +13201,7 @@ } }, { - "name": "X-Nextcloud-Federation", + "name": "x-nextcloud-federation", "in": "header", "schema": { "type": "string" @@ -19761,14 +19761,14 @@ } }, { - "name": "Talk-Recording-Random", + "name": "talk-recording-random", "in": "header", "schema": { "type": "string" } }, { - "name": "Talk-Recording-Checksum", + "name": "talk-recording-checksum", "in": "header", "schema": { "type": "string" diff --git a/openapi.json b/openapi.json index 96c17bd4cb0..6fc5ba07b33 100644 --- a/openapi.json +++ b/openapi.json @@ -13106,7 +13106,7 @@ } }, { - "name": "X-Nextcloud-Federation", + "name": "x-nextcloud-federation", "in": "header", "schema": { "type": "string" @@ -19666,14 +19666,14 @@ } }, { - "name": "Talk-Recording-Random", + "name": "talk-recording-random", "in": "header", "schema": { "type": "string" } }, { - "name": "Talk-Recording-Checksum", + "name": "talk-recording-checksum", "in": "header", "schema": { "type": "string" diff --git a/src/types/openapi/openapi-backend-sipbridge.ts b/src/types/openapi/openapi-backend-sipbridge.ts index 915443abd07..516ced26f04 100644 --- a/src/types/openapi/openapi-backend-sipbridge.ts +++ b/src/types/openapi/openapi-backend-sipbridge.ts @@ -515,7 +515,7 @@ export interface operations { parameters: { query?: never; header: { - "X-Nextcloud-Federation"?: string; + "x-nextcloud-federation"?: string; /** @description Required to be true for the API request to pass */ "OCS-APIRequest": boolean; }; diff --git a/src/types/openapi/openapi-full.ts b/src/types/openapi/openapi-full.ts index 30c0220740d..807028e05d9 100644 --- a/src/types/openapi/openapi-full.ts +++ b/src/types/openapi/openapi-full.ts @@ -7212,7 +7212,7 @@ export interface operations { parameters: { query?: never; header: { - "X-Nextcloud-Federation"?: string; + "x-nextcloud-federation"?: string; /** @description Required to be true for the API request to pass */ "OCS-APIRequest": boolean; }; @@ -9789,8 +9789,8 @@ export interface operations { token?: string; }; header: { - "Talk-Recording-Random"?: string; - "Talk-Recording-Checksum"?: string; + "talk-recording-random"?: string; + "talk-recording-checksum"?: string; /** @description Required to be true for the API request to pass */ "OCS-APIRequest": boolean; }; diff --git a/src/types/openapi/openapi.ts b/src/types/openapi/openapi.ts index bb25a193072..bdefa5a0768 100644 --- a/src/types/openapi/openapi.ts +++ b/src/types/openapi/openapi.ts @@ -6674,7 +6674,7 @@ export interface operations { parameters: { query?: never; header: { - "X-Nextcloud-Federation"?: string; + "x-nextcloud-federation"?: string; /** @description Required to be true for the API request to pass */ "OCS-APIRequest": boolean; }; @@ -9251,8 +9251,8 @@ export interface operations { token?: string; }; header: { - "Talk-Recording-Random"?: string; - "Talk-Recording-Checksum"?: string; + "talk-recording-random"?: string; + "talk-recording-checksum"?: string; /** @description Required to be true for the API request to pass */ "OCS-APIRequest": boolean; };