feat(email): Recognize guests invited via email

Signed-off-by: Joas Schilling <coding@schilljs.com>
This commit is contained in:
Joas Schilling 2024-10-09 06:34:06 +02:00
parent dbe4b80f90
commit 5e276f5dc2
No known key found for this signature in database
GPG key ID: F72FA5B49FFA96B0
7 changed files with 140 additions and 14 deletions

View file

@ -142,7 +142,7 @@ class MessageParser {
$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) {
}

View file

@ -13,6 +13,7 @@ use OCA\Talk\Config;
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;
@ -50,6 +51,7 @@ use OCP\IUserSession;
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 {
@ -96,9 +98,9 @@ class PageController extends Controller {
#[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);
}
/**
@ -113,7 +115,7 @@ class PageController extends Controller {
#[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]
@ -152,11 +154,18 @@ class PageController extends Controller {
* @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;
@ -332,12 +341,23 @@ class PageController extends Controller {
}
/**
* @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) {
@ -405,6 +425,62 @@ class PageController extends Controller {
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

View file

@ -1076,6 +1076,8 @@ class RoomController extends AEnvironmentAwareController {
$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);
@ -1643,8 +1645,10 @@ class RoomController extends AEnvironmentAwareController {
}
}
$authenticatedEmailGuest = $this->session->getAuthedEmailActorIdForRoom($token);
$headers = [];
if ($room->isFederatedConversation()) {
if ($authenticatedEmailGuest !== null || $room->isFederatedConversation()) {
// Skip password checking
$result = [
'result' => true,
@ -1659,6 +1663,12 @@ class RoomController extends AEnvironmentAwareController {
$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());
}

View file

@ -79,7 +79,7 @@ class GuestManager {
$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();

View file

@ -37,6 +37,7 @@ use OCP\IUserManager;
use OCP\Security\IHasher;
use OCP\Security\ISecureRandom;
use OCP\Server;
use SensitiveParameter;
class Manager {
@ -746,7 +747,34 @@ class Manager {
* @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, $remoteAccess, $sessionId);
}
/**
* @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);
@ -755,7 +783,7 @@ class Manager {
->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)));
@ -946,7 +974,7 @@ class Manager {
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();
}
}

View file

@ -821,6 +821,10 @@ class ParticipantService {
$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()) {

View file

@ -49,6 +49,14 @@ class TalkSession {
$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);
}