Skip to content

Commit b867296

Browse files
author
Kent Delante
authored
Merge pull request #1371 from nextcloud/leftybournes/feat/guest_creation_simplification
feat: provide the guest form to allow guest creation anywhere
2 parents fdb5a69 + 0c42d5c commit b867296

14 files changed

Lines changed: 290 additions & 504 deletions

File tree

lib/AppInfo/Application.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@
1111
use OCA\Guests\Capabilities;
1212
use OCA\Guests\GroupBackend;
1313
use OCA\Guests\Hooks;
14+
use OCA\Guests\Listener\BeforeTemplateRenderedListener;
1415
use OCA\Guests\Listener\BeforeUserManagementRenderedListener;
1516
use OCA\Guests\Listener\LoadAdditionalScriptsListener;
1617
use OCA\Guests\Listener\ShareAutoAcceptListener;
17-
use OCA\Guests\Listener\TalkIntegrationListener;
1818
use OCA\Guests\Listener\UserChangedListener;
1919
use OCA\Guests\Notifications\Notifier;
2020
use OCA\Guests\RestrictionManager;
@@ -33,6 +33,7 @@
3333
use OCP\Share\Events\ShareCreatedEvent;
3434
use OCP\User\Events\UserChangedEvent;
3535
use OCP\User\Events\UserFirstTimeLoggedInEvent;
36+
use OCP\Util;
3637
use Psr\Container\ContainerInterface;
3738

3839
class Application extends App implements IBootstrap {
@@ -47,7 +48,7 @@ public function register(IRegistrationContext $context): void {
4748

4849
$context->registerEventListener(LoadAdditionalScriptsEvent::class, LoadAdditionalScriptsListener::class);
4950
$context->registerEventListener(ShareCreatedEvent::class, ShareAutoAcceptListener::class);
50-
$context->registerEventListener(BeforeTemplateRenderedEvent::class, TalkIntegrationListener::class);
51+
$context->registerEventListener(BeforeTemplateRenderedEvent::class, BeforeTemplateRenderedListener::class);
5152
$context->registerEventListener(BeforeUserManagementRenderedEvent::class, BeforeUserManagementRenderedListener::class);
5253
$context->registerEventListener(UserChangedEvent::class, UserChangedListener::class);
5354
}
@@ -62,6 +63,7 @@ public function boot(IBootContext $context): void {
6263
$this->setupGuestRestrictions($context->getAppContainer(), $context->getServerContainer());
6364
$this->setupNotifications($context->getAppContainer());
6465
$context->getAppContainer()->get(RestrictionManager::class)->lateSetupRestrictions();
66+
Util::addScript('guests', 'guests-init');
6567
}
6668

6769
private function setupGuestManagement(ContainerInterface $container, ContainerInterface $server): void {

lib/Controller/UsersController.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use OCA\Guests\Db\Transfer;
1515
use OCA\Guests\Db\TransferMapper;
1616
use OCA\Guests\GuestManager;
17+
use OCA\Guests\Service\InviteService;
1718
use OCA\Guests\TransferService;
1819
use OCP\AppFramework\Db\DoesNotExistException;
1920
use OCP\AppFramework\Http;
@@ -42,6 +43,7 @@ public function __construct(
4243
private IGroupManager $groupManager,
4344
private TransferService $transferService,
4445
private TransferMapper $transferMapper,
46+
private InviteService $inviteService,
4547
) {
4648
parent::__construct($appName, $request);
4749
}
@@ -55,7 +57,7 @@ public function __construct(
5557
* @param array $groups
5658
* @return DataResponse
5759
*/
58-
public function create(string $email, string $displayName, string $language, array $groups): DataResponse {
60+
public function create(string $email, string $displayName, string $language, array $groups, bool $sendInvite = true): DataResponse {
5961
$errorMessages = [];
6062
$currentUser = $this->userSession->getUser();
6163

@@ -154,6 +156,10 @@ public function create(string $email, string $displayName, string $language, arr
154156
);
155157
}
156158

159+
if ($sendInvite) {
160+
$this->inviteService->sendInvite($currentUser->getUID(), $username);
161+
}
162+
157163
return new DataResponse(
158164
[
159165
'message' => $this->l10n->t(

lib/Hooks.php

Lines changed: 3 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@
1010

1111
use OC\Files\Filesystem;
1212
use OCA\Guests\AppInfo\Application;
13+
use OCA\Guests\Service\InviteService;
1314
use OCA\Guests\Storage\ReadOnlyJail;
14-
use OCP\AppFramework\Db\DoesNotExistException;
1515
use OCP\AppFramework\IAppContainer;
1616
use OCP\Constants;
1717
use OCP\Files\Storage\IStorage;
@@ -36,6 +36,7 @@ public function __construct(
3636
private UserBackend $userBackend,
3737
private IAppContainer $container,
3838
private TransferService $transferService,
39+
private InviteService $inviteService,
3940
) {
4041
}
4142

@@ -76,38 +77,9 @@ public function handlePostShare(ShareCreatedEvent $event): void {
7677
$this->logger->debug("checking if '$shareWith' has a password",
7778
['app' => Application::APP_ID]);
7879

79-
80-
$passwordToken = $this->config->getUserValue(
81-
$shareWith,
82-
'core',
83-
'lostpassword',
84-
null
85-
);
86-
8780
$uid = $user->getUID();
8881

89-
try {
90-
if ($passwordToken) {
91-
// user has not yet activated his account
92-
93-
$decryptedToken = $this->crypto->decrypt($passwordToken, strtolower($targetUser->getEMailAddress()) . $this->config->getSystemValue('secret'));
94-
[, $token] = explode(':', $decryptedToken);
95-
$lang = $this->config->getUserValue($targetUser->getUID(), 'core', 'lang', '');
96-
// send invitation
97-
$this->mail->sendGuestInviteMail(
98-
$uid,
99-
$shareWith,
100-
$share,
101-
$token,
102-
$lang
103-
);
104-
$share->setMailSend(false);
105-
}
106-
} catch (DoesNotExistException $ex) {
107-
$this->logger->error("'$shareWith' does not exist", ['app' => Application::APP_ID]);
108-
} catch (\Exception $e) {
109-
$this->logger->error('Failed to send guest activation mail', ['app' => Application::APP_ID, 'exception' => $e]);
110-
}
82+
$this->inviteService->sendInvite($uid, $shareWith, $share);
11183
}
11284

11385
public function setupReadonlyFilesystem(array $params): void {

lib/Listener/TalkIntegrationListener.php renamed to lib/Listener/BeforeTemplateRenderedListener.php

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,30 @@
1010

1111
namespace OCA\Guests\Listener;
1212

13+
use OCA\Guests\Config;
1314
use OCP\AppFramework\Http\Events\BeforeTemplateRenderedEvent;
15+
use OCP\AppFramework\Services\IInitialState;
1416
use OCP\EventDispatcher\Event;
1517
use OCP\EventDispatcher\IEventListener;
1618
use OCP\Util;
1719

1820
/**
1921
* @template-implements IEventListener<Event>
2022
*/
21-
class TalkIntegrationListener implements IEventListener {
23+
class BeforeTemplateRenderedListener implements IEventListener {
24+
public function __construct(
25+
private Config $config,
26+
private IInitialState $initialState,
27+
) {
28+
}
29+
2230
public function handle(Event $event): void {
2331
if (!$event instanceof BeforeTemplateRenderedEvent) {
2432
return;
2533
}
34+
35+
$this->initialState->provideInitialState('canCreateGuests', $this->config->canCreateGuests());
36+
2637
if (!$event->isLoggedIn() || $event->getResponse()->getTemplateName() !== 'index') {
2738
return;
2839
}

lib/Mail.php

Lines changed: 75 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
use OCP\Defaults;
1212
use OCP\IConfig;
13+
use OCP\IL10N;
1314
use OCP\IURLGenerator;
1415
use OCP\IUserManager;
1516
use OCP\IUserSession;
@@ -40,53 +41,83 @@ public function __construct(
4041
* @param $uid
4142
* @throws \Exception
4243
*/
43-
public function sendGuestInviteMail(string $uid, string $shareWith, Share\IShare $share, string $token, string $language = ''): void {
44+
public function sendGuestInviteMail(string $uid, string $guest, string $token, string $language = '', ?Share\IShare $share = null): void {
4445
if ($language === '') {
4546
$language = null;
4647
}
4748
$l10n = $this->l10nFactory->get('guests', $language);
4849

4950
$passwordLink = $this->urlGenerator->linkToRouteAbsolute(
5051
'core.lost.resetform',
51-
['userId' => $shareWith, 'token' => $token]
52+
['userId' => $guest, 'token' => $token]
5253
);
5354

54-
$this->logger->debug("sending invite to $shareWith: $passwordLink", ['app' => 'guests']);
55-
56-
$targetUser = $this->userManager->get($shareWith);
57-
$shareWithEmail = $targetUser->getEMailAddress();
58-
if (!$shareWithEmail) {
55+
$targetUser = $this->userManager->get($guest);
56+
$guestEmail = $targetUser->getEMailAddress();
57+
if (!$guestEmail) {
5958
throw new \Exception('Guest user created without email');
6059
}
6160
$replyTo = $this->userManager->get($uid)->getEMailAddress();
6261
$senderDisplayName = $this->userSession->getUser()->getDisplayName();
6362

63+
if (empty($share)) {
64+
[ $subject, $emailTemplate ] = $this->composeInviteMessage($senderDisplayName, $guestEmail, $passwordLink, $l10n);
65+
} else {
66+
[ $subject, $emailTemplate ] = $this->composeShareMessage($share, $senderDisplayName, $guestEmail, $passwordLink, $l10n);
67+
}
68+
69+
try {
70+
$message = $this->mailer->createMessage();
71+
$message->setTo([$guestEmail => $targetUser->getDisplayName()]);
72+
$message->setSubject($subject);
73+
$message->setHtmlBody($emailTemplate->renderHtml());
74+
$message->setPlainBody($emailTemplate->renderText());
75+
$message->setFrom([
76+
Util::getDefaultEmailAddress('sharing-noreply')
77+
=> $l10n->t('%s via %s', [
78+
$senderDisplayName,
79+
$this->defaults->getName()
80+
]),
81+
]);
82+
83+
if (!is_null($replyTo)) {
84+
$message->setReplyTo([$replyTo]);
85+
}
86+
87+
$this->mailer->send($message);
88+
} catch (\Exception $e) {
89+
throw new \Exception($l10n->t(
90+
'Couldn\'t send reset email. Please contact your administrator.'
91+
));
92+
}
93+
}
94+
95+
private function composeShareMessage(Share\IShare $share, string $senderDisplayName, string $guestEmail, string $passwordLink, IL10N $l10n): array {
6496
$filename = trim($share->getTarget(), '/');
65-
$subject = $l10n->t('%s shared »%s« with you', [$senderDisplayName, $filename]);
97+
$subject = $l10n->t('%s shared a file with you', [$senderDisplayName]);
6698
$expiration = $share->getExpirationDate();
6799

68100
$link = $this->urlGenerator->linkToRouteAbsolute(
69101
'files.viewcontroller.showFile', ['fileid' => $share->getNodeId(), 'direct' => 1]
70102
);
71-
72-
$emailTemplate = $this->mailer->createEMailTemplate('guest.invite');
103+
$emailTemplate = $this->mailer->createEMailTemplate('guest.share');
73104

74105
$emailTemplate->addHeader();
75-
$emailTemplate->addHeading($l10n->t('Incoming share'));
106+
$emailTemplate->addHeading($l10n->t('%s shared a file with you', [$senderDisplayName]));
76107

77108
$emailTemplate->addBodyText(
78109
$l10n->t('Hey there,')
79110
);
80111

81112
$emailTemplate->addBodyText(
82-
$l10n->t('%s just shared »%s« with you.', [$senderDisplayName, $filename])
113+
$l10n->t('%s just invited you and shared »%s« with you.', [$senderDisplayName, $filename])
83114
);
84115

85116
$emailTemplate->addBodyText(
86117
$l10n->t('You can access the shared file by activating your guest account.')
87118
);
88119
$emailTemplate->addBodyText(
89-
$l10n->t('After your account is activated you can view the share by logging in with %s.', [$shareWithEmail])
120+
$l10n->t('After your account is activated you can view the share by logging in with %s.', [$guestEmail])
90121
);
91122

92123
if ($expiration) {
@@ -104,29 +135,38 @@ public function sendGuestInviteMail(string $uid, string $shareWith, Share\IShare
104135
);
105136
$emailTemplate->addFooter();
106137

107-
try {
108-
$message = $this->mailer->createMessage();
109-
$message->setTo([$shareWithEmail => $targetUser->getDisplayName()]);
110-
$message->setSubject($subject);
111-
$message->setHtmlBody($emailTemplate->renderHtml());
112-
$message->setPlainBody($emailTemplate->renderText());
113-
$message->setFrom([
114-
Util::getDefaultEmailAddress('sharing-noreply')
115-
=> $l10n->t('%s via %s', [
116-
$senderDisplayName,
117-
$this->defaults->getName()
118-
]),
119-
]);
138+
return [ $subject, $emailTemplate ];
139+
}
120140

121-
if (!is_null($replyTo)) {
122-
$message->setReplyTo([$replyTo]);
123-
}
141+
private function composeInviteMessage(string $senderDisplayName, string $guestEmail, string $passwordLink, IL10N $l10n): array {
142+
$subject = $l10n->t('%s invited you as a guest', [$senderDisplayName]);
124143

125-
$this->mailer->send($message);
126-
} catch (\Exception $e) {
127-
throw new \Exception($l10n->t(
128-
'Couldn\'t send reset email. Please contact your administrator.'
129-
));
130-
}
144+
$emailTemplate = $this->mailer->createEMailTemplate('guest.invite');
145+
146+
$emailTemplate->addHeader();
147+
$emailTemplate->addHeading($l10n->t('You have been invited'));
148+
149+
$emailTemplate->addBodyText(
150+
$l10n->t('Hey there,')
151+
);
152+
153+
$emailTemplate->addBodyText(
154+
$l10n->t('%s just invited you.', [$senderDisplayName])
155+
);
156+
157+
$emailTemplate->addBodyText(
158+
$l10n->t('You can activate your guest account with the button below.')
159+
);
160+
$emailTemplate->addBodyText(
161+
$l10n->t('After your account is activated you can log in with %s.', [$guestEmail])
162+
);
163+
164+
$emailTemplate->addBodyButton(
165+
$l10n->t('Activate account'),
166+
$passwordLink
167+
);
168+
$emailTemplate->addFooter();
169+
170+
return [ $subject, $emailTemplate ];
131171
}
132172
}

lib/Service/InviteService.php

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
7+
* SPDX-License-Identifier: AGPL-3.0-or-later
8+
*/
9+
10+
namespace OCA\Guests\Service;
11+
12+
use OCA\Guests\Mail;
13+
use OCP\AppFramework\Db\DoesNotExistException;
14+
use OCP\IConfig;
15+
use OCP\Security\ICrypto;
16+
use OCP\Share\IShare;
17+
use Psr\Log\LoggerInterface;
18+
19+
class InviteService {
20+
public function __construct(
21+
private LoggerInterface $logger,
22+
private IConfig $config,
23+
private ICrypto $crypto,
24+
private Mail $mail,
25+
) {
26+
}
27+
28+
public function sendInvite(string $userId, string $guest, ?IShare $share = null): bool {
29+
$passwordToken = $this->config->getUserValue($guest, 'core', 'lostpassword', null);
30+
31+
if (!$passwordToken) {
32+
return false;
33+
}
34+
35+
try {
36+
// user has not yet activated his account
37+
$decryptedToken = $this->crypto->decrypt($passwordToken, strtolower($guest) . $this->config->getSystemValue('secret'));
38+
[, $token] = explode(':', $decryptedToken);
39+
$lang = $this->config->getUserValue($guest, 'core', 'lang', '');
40+
// send invitation
41+
$this->mail->sendGuestInviteMail(
42+
$userId,
43+
$guest,
44+
$token,
45+
$lang,
46+
$share
47+
);
48+
49+
if ($share) {
50+
$share->setMailSend(false);
51+
}
52+
} catch (DoesNotExistException $ex) {
53+
$this->logger->error("'$guest' does not exist");
54+
} catch (\Exception $e) {
55+
$this->logger->error('Failed to send guest activation mail', ['exception' => $e]);
56+
}
57+
58+
return true;
59+
}
60+
}

0 commit comments

Comments
 (0)