Skip to content

Commit

Permalink
feat(email): Recognize guests invited via email
Browse files Browse the repository at this point in the history
Signed-off-by: Joas Schilling <[email protected]>
  • Loading branch information
nickvergessen committed Oct 9, 2024
1 parent 930d3f9 commit c2c1274
Show file tree
Hide file tree
Showing 8 changed files with 144 additions and 18 deletions.
2 changes: 1 addition & 1 deletion lib/Chat/MessageParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ protected function getActorInformation(Message $message, string $actorType, stri
$displayName = $this->guestNames[$actorId];
} else {
try {
$participant = $this->participantService->getParticipantByActor($message->getRoom(), Attendee::ACTOR_GUESTS, $actorId);
$participant = $this->participantService->getParticipantByActor($message->getRoom(), str_contains($actorId, '@') ? Attendee::ACTOR_EMAILS : Attendee::ACTOR_GUESTS, $actorId);
$displayName = $participant->getAttendee()->getDisplayName();
} catch (ParticipantNotFoundException) {
}
Expand Down
92 changes: 84 additions & 8 deletions lib/Controller/PageController.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
use OCA\Talk\Exceptions\ParticipantNotFoundException;
use OCA\Talk\Exceptions\RoomNotFoundException;
use OCA\Talk\Manager;
use OCA\Talk\Model\Attendee;
use OCA\Talk\Participant;
use OCA\Talk\Room;
use OCA\Talk\Service\ParticipantService;
Expand Down Expand Up @@ -50,6 +51,7 @@
use OCP\Notification\IManager as INotificationManager;
use OCP\Security\Bruteforce\IThrottler;
use Psr\Log\LoggerInterface;
use SensitiveParameter;

#[OpenAPI(scope: OpenAPI::SCOPE_IGNORE)]
class PageController extends Controller {
Expand Down Expand Up @@ -96,9 +98,9 @@ public function __construct(
#[PublicPage]
#[UseSession]
#[BruteForceProtection(action: 'talkRoomToken')]
public function showCall(string $token): Response {
public function showCall(string $token, string $email = '', string $access = ''): Response {
// This is the entry point from the `/call/{token}` URL which is hardcoded in the server.
return $this->index($token);
return $this->pageHandler($token, email: $email, accessToken: $access);
}

/**
Expand All @@ -113,7 +115,7 @@ public function showCall(string $token): Response {
#[BruteForceProtection(action: 'talkRoomPassword')]
public function authenticatePassword(string $token, string $password = ''): Response {
// This is the entry point from the `/call/{token}` URL which is hardcoded in the server.
return $this->pageHandler($token, '', $password);
return $this->pageHandler($token, password: $password);
}

#[NoCSRFRequired]
Expand Down Expand Up @@ -152,11 +154,18 @@ public function index(string $token = '', string $callUser = ''): Response {
* @return TemplateResponse|RedirectResponse
* @throws HintException
*/
protected function pageHandler(string $token = '', string $callUser = '', string $password = ''): Response {
protected function pageHandler(
string $token = '',
string $callUser = '',
string $password = '',
string $email = '',
#[SensitiveParameter]
string $accessToken = '',
): Response {
$bruteForceToken = $token;
$user = $this->userSession->getUser();
if (!$user instanceof IUser) {
return $this->guestEnterRoom($token, $password);
return $this->guestEnterRoom($token, $password, $email, $accessToken);
}

$throttle = false;
Expand Down Expand Up @@ -332,12 +341,23 @@ public function recording(string $token): Response {
}

/**
* @param string $token
* @param string $password
* @return TemplateResponse|RedirectResponse
* @throws HintException
*/
protected function guestEnterRoom(string $token, string $password): Response {
protected function guestEnterRoom(
string $token,
string $password,
string $email,
#[SensitiveParameter]
string $accessToken,
): Response {
if ($email && $accessToken) {
return $this->invitedEmail(
$token,
$email,
$accessToken,
);
}
try {
$room = $this->manager->getRoomByToken($token);
if ($room->getType() !== Room::TYPE_PUBLIC) {
Expand Down Expand Up @@ -405,6 +425,62 @@ protected function guestEnterRoom(string $token, string $password): Response {
return $response;
}

/**
* @return TemplateResponse|RedirectResponse
* @throws HintException
*/
protected function invitedEmail(
string $token,
string $email,
#[SensitiveParameter]
string $accessToken,
): Response {
try {
$this->manager->getRoomByAccessToken(
$token,
Attendee::ACTOR_EMAILS,
$email,
$accessToken,
);
$this->talkSession->renewSessionId();
$this->talkSession->setAuthedEmailActorIdForRoom($token, $email);
} catch (RoomNotFoundException) {
$redirectUrl = $this->url->linkToRoute('spreed.Page.index');
if ($token) {
$redirectUrl = $this->url->linkToRoute('spreed.Page.showCall', ['token' => $token]);
}
$response = new RedirectResponse($this->url->linkToRoute('core.login.showLoginForm', [
'redirect_url' => $redirectUrl,
]));
$response->throttle(['token' => $token, 'action' => 'talkRoomToken']);
return $response;
}

$this->publishInitialStateForGuest();
$this->eventDispatcher->dispatchTyped(new RenderReferenceEvent());

$response = new PublicTemplateResponse($this->appName, 'index', [
'id-app-content' => '#content-vue',
'id-app-navigation' => null,
]);

$response->setFooterVisible(false);
$csp = new ContentSecurityPolicy();
$csp->addAllowedConnectDomain('*');
$csp->addAllowedMediaDomain('blob:');
$csp->addAllowedWorkerSrcDomain('blob:');
$csp->addAllowedWorkerSrcDomain("'self'");
$csp->addAllowedChildSrcDomain('blob:');
$csp->addAllowedChildSrcDomain("'self'");
$csp->addAllowedScriptDomain('blob:');
$csp->addAllowedScriptDomain("'self'");
$csp->addAllowedConnectDomain('blob:');
$csp->addAllowedConnectDomain("'self'");
$csp->addAllowedImageDomain('https://*.tile.openstreetmap.org');
$response->setContentSecurityPolicy($csp);
return $response;
}

/**
* @param string $token
* @return RedirectResponse
Expand Down
12 changes: 11 additions & 1 deletion lib/Controller/RoomController.php
Original file line number Diff line number Diff line change
Expand Up @@ -1076,6 +1076,8 @@ protected function formatParticipantList(array $participants, bool $includeStatu
$result['displayName'] = $participant->getAttendee()->getDisplayName();
} elseif ($participant->getAttendee()->getActorType() === Attendee::ACTOR_CIRCLES) {
$result['displayName'] = $participant->getAttendee()->getDisplayName();
} elseif ($participant->getAttendee()->getActorType() === Attendee::ACTOR_EMAILS) {
$result['displayName'] = $participant->getAttendee()->getDisplayName();
} elseif ($participant->getAttendee()->getActorType() === Attendee::ACTOR_FEDERATED_USERS) {
if ($participant->getSession() instanceof Session && $participant->getSession()->getLastPing() <= $maxPingAge) {
$this->participantService->leaveRoomAsSession($this->room, $participant);
Expand Down Expand Up @@ -1643,8 +1645,10 @@ public function joinRoom(string $token, string $password = '', bool $force = tru
}
}

$authenticatedEmailGuest = $this->session->getAuthedEmailActorIdForRoom($token);

$headers = [];
if ($room->isFederatedConversation()) {
if ($authenticatedEmailGuest !== null || $room->isFederatedConversation()) {
// Skip password checking
$result = [
'result' => true,
Expand All @@ -1659,6 +1663,12 @@ public function joinRoom(string $token, string $password = '', bool $force = tru
$participant = $this->participantService->joinRoom($this->roomService, $room, $user, $password, $result['result']);
$this->participantService->generatePinForParticipant($room, $participant);
} else {
if ($authenticatedEmailGuest !== null && $previousParticipant === null) {
try {
$previousParticipant = $this->participantService->getParticipantByActor($room, Attendee::ACTOR_EMAILS, $authenticatedEmailGuest);
} catch (ParticipantNotFoundException $e) {
}
}
$participant = $this->participantService->joinRoomAsNewGuest($this->roomService, $room, $password, $result['result'], $previousParticipant);
$this->session->setGuestActorIdForRoom($room->getToken(), $participant->getAttendee()->getActorId());
}
Expand Down
8 changes: 4 additions & 4 deletions lib/Controller/SignalingController.php
Original file line number Diff line number Diff line change
Expand Up @@ -515,7 +515,7 @@ public function pullMessages(string $token): DataResponse {
$sessionId = $this->session->getSessionForRoom($token);
if ($sessionId === null) {
// User is not active in this room
return new DataResponse([['type' => 'usersInRoom', 'data' => []]], Http::STATUS_NOT_FOUND);
return new DataResponse([['type' => 'usersInRoom', 'data' => [1]]], Http::STATUS_NOT_FOUND);

Check failure on line 518 in lib/Controller/SignalingController.php

View workflow job for this annotation

GitHub Actions / static-psalm-analysis

InvalidReturnStatement

lib/Controller/SignalingController.php:518:12: InvalidReturnStatement: The inferred type 'OCP\AppFramework\Http\DataResponse<404, list{array{data: list{1}, type: 'usersInRoom'}}, array<never, never>>' does not match the declared return type 'OCP\AppFramework\Http\DataResponse<200|404|409, list<array{data: array<array-key, array{actorId: string, actorType: string, inCall: int, lastPing: int, participantPermissions: int, roomId: int, sessionId: string, userId: string}>|string, type: string}>, array<never, never>>|OCP\AppFramework\Http\DataResponse<400, string, array<never, never>>' for OCA\Talk\Controller\SignalingController::pullMessages (see https://psalm.dev/128)
}

$room = $this->manager->getRoomForSession($this->userId, $sessionId);
Expand All @@ -527,7 +527,7 @@ public function pullMessages(string $token): DataResponse {
}
} catch (RoomNotFoundException) {
$this->banIpIfGuestGotBanned($token);
return new DataResponse([['type' => 'usersInRoom', 'data' => []]], Http::STATUS_NOT_FOUND);
return new DataResponse([['type' => 'usersInRoom', 'data' => [2]]], Http::STATUS_NOT_FOUND);

Check failure on line 530 in lib/Controller/SignalingController.php

View workflow job for this annotation

GitHub Actions / static-psalm-analysis

InvalidReturnStatement

lib/Controller/SignalingController.php:530:11: InvalidReturnStatement: The inferred type 'OCP\AppFramework\Http\DataResponse<404, list{array{data: list{2}, type: 'usersInRoom'}}, array<never, never>>' does not match the declared return type 'OCP\AppFramework\Http\DataResponse<200|404|409, list<array{data: array<array-key, array{actorId: string, actorType: string, inCall: int, lastPing: int, participantPermissions: int, roomId: int, sessionId: string, userId: string}>|string, type: string}>, array<never, never>>|OCP\AppFramework\Http\DataResponse<400, string, array<never, never>>' for OCA\Talk\Controller\SignalingController::pullMessages (see https://psalm.dev/128)
}

while ($seconds > 0) {
Expand Down Expand Up @@ -559,7 +559,7 @@ public function pullMessages(string $token): DataResponse {
$sessionId = $this->session->getSessionForRoom($token);
if ($sessionId === null) {
// User is not active in this room
return new DataResponse([['type' => 'usersInRoom', 'data' => []]], Http::STATUS_NOT_FOUND);
return new DataResponse([['type' => 'usersInRoom', 'data' => [3]]], Http::STATUS_NOT_FOUND);

Check failure on line 562 in lib/Controller/SignalingController.php

View workflow job for this annotation

GitHub Actions / static-psalm-analysis

InvalidReturnStatement

lib/Controller/SignalingController.php:562:12: InvalidReturnStatement: The inferred type 'OCP\AppFramework\Http\DataResponse<404, list{array{data: list{3}, type: 'usersInRoom'}}, array<never, never>>' does not match the declared return type 'OCP\AppFramework\Http\DataResponse<200|404|409, list<array{data: array<array-key, array{actorId: string, actorType: string, inCall: int, lastPing: int, participantPermissions: int, roomId: int, sessionId: string, userId: string}>|string, type: string}>, array<never, never>>|OCP\AppFramework\Http\DataResponse<400, string, array<never, never>>' for OCA\Talk\Controller\SignalingController::pullMessages (see https://psalm.dev/128)
}
}

Expand All @@ -569,7 +569,7 @@ public function pullMessages(string $token): DataResponse {
$data[] = ['type' => 'usersInRoom', 'data' => $this->getUsersInRoom($room, $pingTimestamp)];
} catch (RoomNotFoundException) {
$this->banIpIfGuestGotBanned($token);
$data[] = ['type' => 'usersInRoom', 'data' => []];
$data[] = ['type' => 'usersInRoom', 'data' => [4]];

// Was the session killed or the complete conversation?
try {
Expand Down
2 changes: 1 addition & 1 deletion lib/GuestManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ public function sendEmailInvitation(Room $room, Participant $participant): void
$event = new BeforeEmailInvitationSentEvent($room, $participant->getAttendee());
$this->dispatcher->dispatchTyped($event);

$link = $this->url->linkToRouteAbsolute('spreed.Page.showCall', ['token' => $room->getToken()]);
$link = $this->url->linkToRouteAbsolute('spreed.Page.showCall', ['token' => $room->getToken(), 'email' => $email, 'access' => $participant->getAttendee()->getAccessToken()]);

$message = $this->mailer->createMessage();

Expand Down
34 changes: 31 additions & 3 deletions lib/Manager.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
use OCP\Security\IHasher;
use OCP\Security\ISecureRandom;
use OCP\Server;
use SensitiveParameter;

class Manager {

Expand Down Expand Up @@ -746,7 +747,34 @@ public function getRoomByActor(string $token, string $actorType, string $actorId
* @return Room
* @throws RoomNotFoundException
*/
public function getRoomByRemoteAccess(string $token, string $actorType, string $actorId, string $remoteAccess, ?string $sessionId = null): Room {
public function getRoomByRemoteAccess(
string $token,
string $actorType,
string $actorId,
#[SensitiveParameter]
string $remoteAccess,
?string $sessionId = null,
): Room {
return $this->getRoomByAccessToken($token, $actorType, $actorId, $sessionId, $remoteAccess, $sessionId);

Check failure on line 758 in lib/Manager.php

View workflow job for this annotation

GitHub Actions / static-psalm-analysis

TooManyArguments

lib/Manager.php:758:17: TooManyArguments: Too many arguments for method OCA\Talk\Manager::getroombyaccesstoken - saw 6 (see https://psalm.dev/026)
}

/**
* @param string $token
* @param string $actorType
* @param string $actorId
* @param string $remoteAccess
* @param ?string $sessionId
* @return Room
* @throws RoomNotFoundException
*/
public function getRoomByAccessToken(
string $token,
string $actorType,
string $actorId,
#[SensitiveParameter]
string $accessToken,
?string $sessionId = null
): Room {
$query = $this->db->getQueryBuilder();
$helper = new SelectHelper();
$helper->selectRoomsTable($query);
Expand All @@ -755,7 +783,7 @@ public function getRoomByRemoteAccess(string $token, string $actorType, string $
->leftJoin('r', 'talk_attendees', 'a', $query->expr()->andX(
$query->expr()->eq('a.actor_type', $query->createNamedParameter($actorType)),
$query->expr()->eq('a.actor_id', $query->createNamedParameter($actorId)),
$query->expr()->eq('a.access_token', $query->createNamedParameter($remoteAccess)),
$query->expr()->eq('a.access_token', $query->createNamedParameter($accessToken)),
$query->expr()->eq('a.room_id', 'r.id')
))
->where($query->expr()->eq('r.token', $query->createNamedParameter($token)));
Expand Down Expand Up @@ -946,7 +974,7 @@ public function getRoomForSession(?string $userId, ?string $sessionId): Room {
throw new RoomNotFoundException();
}
} else {
if ($row['actor_type'] !== Attendee::ACTOR_GUESTS) {
if ($row['actor_type'] !== Attendee::ACTOR_GUESTS && $row['actor_type'] !== Attendee::ACTOR_EMAILS) {
throw new RoomNotFoundException();
}
}
Expand Down
4 changes: 4 additions & 0 deletions lib/Service/ParticipantService.php
Original file line number Diff line number Diff line change
Expand Up @@ -821,6 +821,10 @@ public function inviteEmailAddress(Room $room, string $email): Participant {
$attendee->setRoomId($room->getId());
$attendee->setActorType(Attendee::ACTOR_EMAILS);
$attendee->setActorId($email);
$attendee->setAccessToken($this->secureRandom->generate(
FederationManager::TOKEN_LENGTH,
ISecureRandom::CHAR_HUMAN_READABLE
));

if ($room->getSIPEnabled() !== Webinary::SIP_DISABLED
&& $this->talkConfig->isSIPConfigured()) {
Expand Down
8 changes: 8 additions & 0 deletions lib/TalkSession.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,14 @@ public function removeGuestActorIdForRoom(string $token): void {
$this->removeValue('spreed-guest-id', $token);
}

public function getAuthedEmailActorIdForRoom(string $token): ?string {
return $this->getValue('spreed-authed-email', $token);
}

public function setAuthedEmailActorIdForRoom(string $token, string $actorId): void {
$this->setValue('spreed-authed-email', $token, $actorId);
}

public function getFileShareTokenForRoom(string $roomToken): ?string {
return $this->getValue('spreed-file-share-token', $roomToken);
}
Expand Down

0 comments on commit c2c1274

Please sign in to comment.