chore(files_sharing): refactor share password mail

Signed-off-by: skjnldsv <skjnldsv@protonmail.com>
pull/46007/head
skjnldsv 2024-07-09 11:00:38 +07:00 committed by John Molakvoæ
parent 208ff8013d
commit 5ce4bb368f
2 changed files with 95 additions and 53 deletions

@ -12,12 +12,10 @@ namespace OCA\Files_Sharing\Controller;
use Exception; use Exception;
use OC\Files\FileInfo; use OC\Files\FileInfo;
use OC\Files\Storage\Wrapper\Wrapper; use OC\Files\Storage\Wrapper\Wrapper;
use OC\Share20\Exception\ProviderException;
use OCA\Files_Sharing\Exceptions\SharingRightsException; use OCA\Files_Sharing\Exceptions\SharingRightsException;
use OCA\Files_Sharing\External\Storage; use OCA\Files_Sharing\External\Storage;
use OCA\Files_Sharing\SharedStorage; use OCA\Files_Sharing\SharedStorage;
use OCA\Files\Helper; use OCA\Files\Helper;
use OCA\ShareByMail\ShareByMailProvider;
use OCP\App\IAppManager; use OCP\App\IAppManager;
use OCP\AppFramework\Http\Attribute\BruteForceProtection; use OCP\AppFramework\Http\Attribute\BruteForceProtection;
use OCP\AppFramework\Http\Attribute\NoAdminRequired; use OCP\AppFramework\Http\Attribute\NoAdminRequired;
@ -45,6 +43,7 @@ use OCP\IURLGenerator;
use OCP\IUserManager; use OCP\IUserManager;
use OCP\Lock\ILockingProvider; use OCP\Lock\ILockingProvider;
use OCP\Lock\LockedException; use OCP\Lock\LockedException;
use OCP\Mail\IMailer;
use OCP\Server; use OCP\Server;
use OCP\Share\Exceptions\GenericShareException; use OCP\Share\Exceptions\GenericShareException;
use OCP\Share\Exceptions\ShareNotFound; use OCP\Share\Exceptions\ShareNotFound;
@ -87,6 +86,7 @@ class ShareAPIController extends OCSController {
private IDateTimeZone $dateTimeZone, private IDateTimeZone $dateTimeZone,
private LoggerInterface $logger, private LoggerInterface $logger,
private IProviderFactory $factory, private IProviderFactory $factory,
private IMailer $mailer,
?string $userId = null ?string $userId = null
) { ) {
parent::__construct($appName, $request); parent::__construct($appName, $request);
@ -544,7 +544,8 @@ class ShareAPIController extends OCSController {
?string $expireDate = null, ?string $expireDate = null,
string $note = '', string $note = '',
string $label = '', string $label = '',
?string $attributes = null ?string $attributes = null,
?string $mailSend = null
): DataResponse { ): DataResponse {
$share = $this->shareManager->newShare(); $share = $this->shareManager->newShare();
@ -615,7 +616,7 @@ class ShareAPIController extends OCSController {
$share = $this->setShareAttributes($share, $attributes); $share = $this->setShareAttributes($share, $attributes);
} }
//Expire date // Expire date
if ($expireDate !== null) { if ($expireDate !== null) {
if ($expireDate !== '') { if ($expireDate !== '') {
try { try {
@ -634,6 +635,11 @@ class ShareAPIController extends OCSController {
$share->setSharedBy($this->currentUser); $share->setSharedBy($this->currentUser);
$this->checkInheritedAttributes($share); $this->checkInheritedAttributes($share);
// Handle mail send
if ($mailSend === 'true' || $mailSend === 'false') {
$share->setMailSend($mailSend === 'true');
}
if ($shareType === IShare::TYPE_USER) { if ($shareType === IShare::TYPE_USER) {
// Valid user is required to share // Valid user is required to share
if ($shareWith === null || !$this->userManager->userExists($shareWith)) { if ($shareWith === null || !$this->userManager->userExists($shareWith)) {
@ -691,6 +697,10 @@ class ShareAPIController extends OCSController {
// Only share by mail have a recipient // Only share by mail have a recipient
if (is_string($shareWith) && $shareType === IShare::TYPE_EMAIL) { if (is_string($shareWith) && $shareType === IShare::TYPE_EMAIL) {
// If sending a mail have been requested, validate the mail address
if ($share->getMailSend() && !$this->mailer->validateMailAddress($shareWith)) {
throw new OCSNotFoundException($this->l->t('Please specify a valid email address'));
}
$share->setSharedWith($shareWith); $share->setSharedWith($shareWith);
} }
@ -1031,7 +1041,6 @@ class ShareAPIController extends OCSController {
* 200: Shares returned * 200: Shares returned
*/ */
public function getInheritedShares(string $path): DataResponse { public function getInheritedShares(string $path): DataResponse {
// get Node from (string) path. // get Node from (string) path.
$userFolder = $this->rootFolder->getUserFolder($this->currentUser); $userFolder = $this->rootFolder->getUserFolder($this->currentUser);
try { try {
@ -1123,6 +1132,10 @@ class ShareAPIController extends OCSController {
* @param string|null $label New label * @param string|null $label New label
* @param string|null $hideDownload New condition if the download should be hidden * @param string|null $hideDownload New condition if the download should be hidden
* @param string|null $attributes New additional attributes * @param string|null $attributes New additional attributes
* @param string|null $mailSend if the share should be send by mail.
* Considering the share already exists, no mail will be send after the share is updated.
* You will have to use the sendMail action to send the mail.
* @param string|null $shareWith New recipient for email shares
* @return DataResponse<Http::STATUS_OK, Files_SharingShare, array{}> * @return DataResponse<Http::STATUS_OK, Files_SharingShare, array{}>
* @throws OCSBadRequestException Share could not be updated because the requested changes are invalid * @throws OCSBadRequestException Share could not be updated because the requested changes are invalid
* @throws OCSForbiddenException Missing permissions to update the share * @throws OCSForbiddenException Missing permissions to update the share
@ -1140,7 +1153,8 @@ class ShareAPIController extends OCSController {
?string $note = null, ?string $note = null,
?string $label = null, ?string $label = null,
?string $hideDownload = null, ?string $hideDownload = null,
?string $attributes = null ?string $attributes = null,
?string $mailSend = null,
): DataResponse { ): DataResponse {
try { try {
$share = $this->getShareById($id); $share = $this->getShareById($id);
@ -1167,7 +1181,8 @@ class ShareAPIController extends OCSController {
$note === null && $note === null &&
$label === null && $label === null &&
$hideDownload === null && $hideDownload === null &&
$attributes === null $attributes === null &&
$mailSend === null
) { ) {
throw new OCSBadRequestException($this->l->t('Wrong or no update parameter given')); throw new OCSBadRequestException($this->l->t('Wrong or no update parameter given'));
} }
@ -1181,6 +1196,11 @@ class ShareAPIController extends OCSController {
} }
$this->checkInheritedAttributes($share); $this->checkInheritedAttributes($share);
// Handle mail send
if ($mailSend === 'true' || $mailSend === 'false') {
$share->setMailSend($mailSend === 'true');
}
/** /**
* expirationdate, password and publicUpload only make sense for link shares * expirationdate, password and publicUpload only make sense for link shares
*/ */
@ -1987,7 +2007,7 @@ class ShareAPIController extends OCSController {
if (is_array($formattedShareAttributes)) { if (is_array($formattedShareAttributes)) {
foreach ($formattedShareAttributes as $formattedAttr) { foreach ($formattedShareAttributes as $formattedAttr) {
// Legacy handling of the 'enabled' attribute // Legacy handling of the 'enabled' attribute
if ($formattedAttr['enabled']) { if (array_key_exists('enabled', $formattedAttr)) {
$formattedAttr['value'] = is_string($formattedAttr['enabled']) $formattedAttr['value'] = is_string($formattedAttr['enabled'])
? (bool) \json_decode($formattedAttr['enabled']) ? (bool) \json_decode($formattedAttr['enabled'])
: $formattedAttr['enabled']; : $formattedAttr['enabled'];
@ -2087,7 +2107,7 @@ class ShareAPIController extends OCSController {
} }
$provider->sendMailNotification($share); $provider->sendMailNotification($share);
return new JSONResponse(['message' => 'ok']); return new DataResponse(['message' => 'ok']);
} catch(OCSBadRequestException $e) { } catch(OCSBadRequestException $e) {
throw $e; throw $e;
} catch (Exception $e) { } catch (Exception $e) {

@ -227,6 +227,7 @@ class ShareByMailProvider extends DefaultShareProvider implements IShareProvider
$share->getExpirationDate(), $share->getExpirationDate(),
$share->getNote(), $share->getNote(),
$share->getAttributes(), $share->getAttributes(),
$share->getMailSend(),
); );
} }
@ -252,16 +253,7 @@ class ShareByMailProvider extends DefaultShareProvider implements IShareProvider
} }
try { try {
$link = $this->urlGenerator->linkToRouteAbsolute('files_sharing.sharecontroller.showShare', $this->sendEmail($share, $validEmails);
['token' => $share->getToken()]);
$this->sendEmail(
$share->getNode()->getName(),
$link,
$share->getSharedBy(),
$emails,
$share->getExpirationDate(),
$share->getNote()
);
// If we have a password set, we send it to the recipient // If we have a password set, we send it to the recipient
if ($share->getPassword()) { if ($share->getPassword()) {
@ -270,7 +262,7 @@ class ShareByMailProvider extends DefaultShareProvider implements IShareProvider
$passwordExpire = $this->config->getSystemValue('sharing.enable_mail_link_password_expiration', false); $passwordExpire = $this->config->getSystemValue('sharing.enable_mail_link_password_expiration', false);
$passwordEnforced = $this->shareManager->shareApiLinkEnforcePassword(); $passwordEnforced = $this->shareManager->shareApiLinkEnforcePassword();
if ($passwordExpire === false || $share->getSendPasswordByTalk()) { if ($passwordExpire === false || $share->getSendPasswordByTalk()) {
$send = $this->sendPassword($share, $share->getPassword()); $send = $this->sendPassword($share, $share->getPassword(), $validEmails);
if ($passwordEnforced && $send === false) { if ($passwordEnforced && $send === false) {
$this->sendPasswordToOwner($share, $share->getPassword()); $this->sendPasswordToOwner($share, $share->getPassword());
} }
@ -298,22 +290,20 @@ class ShareByMailProvider extends DefaultShareProvider implements IShareProvider
} }
/** /**
* @param string $filename file/folder name * @param IShare $share The share to send the email for
* @param string $link link to the file/folder * @param array $emails The email addresses to send the email to
* @param string $initiator user ID of share sender
* @param string[] $shareWith email addresses
* @param \DateTime|null $expiration expiration date
* @param string $note note
* @throws \Exception If mail couldn't be sent
*/ */
protected function sendEmail( protected function sendEmail(IShare $share, array $emails): void {
string $filename, $link = $this->urlGenerator->linkToRouteAbsolute('files_sharing.sharecontroller.showShare', [
string $link, 'token' => $share->getToken()
string $initiator, ]);
array $shareWith,
?\DateTime $expiration = null, $expiration = $share->getExpirationDate();
string $note = '', $filename = $share->getNode()->getName();
): void { $initiator = $share->getSharedBy();
$note = $share->getNote();
$shareWith = $share->getSharedWith();
$initiatorUser = $this->userManager->get($initiator); $initiatorUser = $this->userManager->get($initiator);
$initiatorDisplayName = ($initiatorUser instanceof IUser) ? $initiatorUser->getDisplayName() : $initiator; $initiatorDisplayName = ($initiatorUser instanceof IUser) ? $initiatorUser->getDisplayName() : $initiator;
$message = $this->mailer->createMessage(); $message = $this->mailer->createMessage();
@ -346,11 +336,11 @@ class ShareByMailProvider extends DefaultShareProvider implements IShareProvider
); );
// If multiple recipients are given, we send the mail to all of them // If multiple recipients are given, we send the mail to all of them
if (count($shareWith) > 1) { if (count($emails) > 1) {
// We do not want to expose the email addresses of the other recipients // We do not want to expose the email addresses of the other recipients
$message->setBcc($shareWith); $message->setBcc($emails);
} else { } else {
$message->setTo($shareWith); $message->setTo($emails);
} }
// The "From" contains the sharers name // The "From" contains the sharers name
@ -391,8 +381,13 @@ class ShareByMailProvider extends DefaultShareProvider implements IShareProvider
* 1. the password is empty * 1. the password is empty
* 2. the setting to send the password by mail is disabled * 2. the setting to send the password by mail is disabled
* 3. the share is set to send the password by talk * 3. the share is set to send the password by talk
*
* @param IShare $share
* @param string $password
* @param array $emails
* @return bool
*/ */
protected function sendPassword(IShare $share, string $password): bool { protected function sendPassword(IShare $share, string $password, array $emails) {
$filename = $share->getNode()->getName(); $filename = $share->getNode()->getName();
$initiator = $share->getSharedBy(); $initiator = $share->getSharedBy();
$shareWith = $share->getSharedWith(); $shareWith = $share->getSharedWith();
@ -432,6 +427,14 @@ class ShareByMailProvider extends DefaultShareProvider implements IShareProvider
$emailTemplate->addBodyText($this->l->t('This password will expire at %s', [$expirationTime->format('r')])); $emailTemplate->addBodyText($this->l->t('This password will expire at %s', [$expirationTime->format('r')]));
} }
// If multiple recipients are given, we send the mail to all of them
if (count($emails) > 1) {
// We do not want to expose the email addresses of the other recipients
$message->setBcc($emails);
} else {
$message->setTo($emails);
}
// The "From" contains the sharers name // The "From" contains the sharers name
$instanceName = $this->defaults->getName(); $instanceName = $this->defaults->getName();
$senderName = $instanceName; $senderName = $instanceName;
@ -445,20 +448,25 @@ class ShareByMailProvider extends DefaultShareProvider implements IShareProvider
); );
} }
$message->setFrom([\OCP\Util::getDefaultEmailAddress($instanceName) => $senderName]); $message->setFrom([\OCP\Util::getDefaultEmailAddress($instanceName) => $senderName]);
if ($this->settingsManager->replyToInitiator() && $initiatorEmailAddress !== null) {
$message->setReplyTo([$initiatorEmailAddress => $initiatorDisplayName]); // The "Reply-To" is set to the sharer if an mail address is configured
$emailTemplate->addFooter($instanceName . ' - ' . $this->defaults->getSlogan()); // also the default footer contains a "Do not reply" which needs to be adjusted.
$initiatorEmail = $initiatorUser->getEMailAddress();
if ($this->settingsManager->replyToInitiator() && $initiatorEmail !== null) {
$message->setReplyTo([$initiatorEmail => $initiatorDisplayName]);
$emailTemplate->addFooter($instanceName . ($this->defaults->getSlogan() !== '' ? ' - ' . $this->defaults->getSlogan() : ''));
} else { } else {
$emailTemplate->addFooter(); $emailTemplate->addFooter();
} }
$message->setTo([$shareWith]);
$message->useTemplate($emailTemplate); $message->useTemplate($emailTemplate);
$this->mailer->send($message); $failedRecipients = $this->mailer->send($message);
if (!empty($failedRecipients)) {
$this->logger->error('Share password mail could not be sent to: ' . implode(', ', $failedRecipients));
return;
}
$this->createPasswordSendActivity($share, $shareWith, false); $this->createPasswordSendActivity($share, $shareWith, false);
return true;
} }
protected function sendNote(IShare $share): void { protected function sendNote(IShare $share): void {
@ -634,6 +642,7 @@ class ShareByMailProvider extends DefaultShareProvider implements IShareProvider
?\DateTimeInterface $expirationTime, ?\DateTimeInterface $expirationTime,
?string $note = '', ?string $note = '',
?IAttributes $attributes = null, ?IAttributes $attributes = null,
?bool $mailSend = true
): int { ): int {
$qb = $this->dbConnection->getQueryBuilder(); $qb = $this->dbConnection->getQueryBuilder();
$qb->insert('share') $qb->insert('share')
@ -653,7 +662,7 @@ class ShareByMailProvider extends DefaultShareProvider implements IShareProvider
->setValue('hide_download', $qb->createNamedParameter((int)$hideDownload, IQueryBuilder::PARAM_INT)) ->setValue('hide_download', $qb->createNamedParameter((int)$hideDownload, IQueryBuilder::PARAM_INT))
->setValue('label', $qb->createNamedParameter($label)) ->setValue('label', $qb->createNamedParameter($label))
->setValue('note', $qb->createNamedParameter($note)) ->setValue('note', $qb->createNamedParameter($note))
->setValue('mail_send', $qb->createNamedParameter(1)); ->setValue('mail_send', $qb->createNamedParameter((int)$mailSend, IQueryBuilder::PARAM_INT));
// set share attributes // set share attributes
$shareAttributes = $this->formatShareAttributes($attributes); $shareAttributes = $this->formatShareAttributes($attributes);
@ -678,9 +687,15 @@ class ShareByMailProvider extends DefaultShareProvider implements IShareProvider
if ($validPassword && ($originalShare->getPassword() !== $share->getPassword() || if ($validPassword && ($originalShare->getPassword() !== $share->getPassword() ||
($originalShare->getSendPasswordByTalk() && !$share->getSendPasswordByTalk()))) { ($originalShare->getSendPasswordByTalk() && !$share->getSendPasswordByTalk()))) {
$this->sendPassword($share, $plainTextPassword); $emails = $this->getSharedWithEmails($share);
$validEmails = array_filter($emails, function ($email) {
return $this->mailer->validateMailAddress($email);
});
$this->sendPassword($share, $plainTextPassword, $validEmails);
} }
$shareAttributes = $this->formatShareAttributes($share->getAttributes());
/* /*
* We allow updating the permissions and password of mail shares * We allow updating the permissions and password of mail shares
*/ */
@ -697,7 +712,8 @@ class ShareByMailProvider extends DefaultShareProvider implements IShareProvider
->set('expiration', $qb->createNamedParameter($share->getExpirationDate(), IQueryBuilder::PARAM_DATE)) ->set('expiration', $qb->createNamedParameter($share->getExpirationDate(), IQueryBuilder::PARAM_DATE))
->set('note', $qb->createNamedParameter($share->getNote())) ->set('note', $qb->createNamedParameter($share->getNote()))
->set('hide_download', $qb->createNamedParameter((int)$share->getHideDownload(), IQueryBuilder::PARAM_INT)) ->set('hide_download', $qb->createNamedParameter((int)$share->getHideDownload(), IQueryBuilder::PARAM_INT))
->set('mail_send', $qb->createNamedParameter(1)) ->set('attributes', $qb->createNamedParameter($shareAttributes))
->set('mail_send', $qb->createNamedParameter($share->getMailSend(), IQueryBuilder::PARAM_INT))
->executeStatement(); ->executeStatement();
if ($originalShare->getNote() !== $share->getNote() && $share->getNote() !== '') { if ($originalShare->getNote() !== $share->getNote() && $share->getNote() !== '') {
@ -947,7 +963,7 @@ class ShareByMailProvider extends DefaultShareProvider implements IShareProvider
$shareTime = new \DateTime(); $shareTime = new \DateTime();
$shareTime->setTimestamp((int)$data['stime']); $shareTime->setTimestamp((int)$data['stime']);
$share->setShareTime($shareTime); $share->setShareTime($shareTime);
$share->setSharedWith($data['share_with']); $share->setSharedWith($data['share_with'] ?? '');
$share->setPassword($data['password']); $share->setPassword($data['password']);
$passwordExpirationTime = \DateTime::createFromFormat('Y-m-d H:i:s', $data['password_expiration_time'] ?? ''); $passwordExpirationTime = \DateTime::createFromFormat('Y-m-d H:i:s', $data['password_expiration_time'] ?? '');
$share->setPasswordExpirationTime($passwordExpirationTime !== false ? $passwordExpirationTime : null); $share->setPasswordExpirationTime($passwordExpirationTime !== false ? $passwordExpirationTime : null);
@ -1165,9 +1181,15 @@ class ShareByMailProvider extends DefaultShareProvider implements IShareProvider
* or a list of emails from the emails attributes field. * or a list of emails from the emails attributes field.
*/ */
protected function getSharedWithEmails(IShare $share) { protected function getSharedWithEmails(IShare $share) {
$attributes = $share->getAttributes()->getAttribute('sharedWith', 'emails'); $attributes = $share->getAttributes();
if (isset($attributes) && is_array($attributes) && !empty($attributes)) {
return $attributes; if ($attributes === null) {
return [$share->getSharedWith()];
}
$emails = $attributes->getAttribute('shareWith', 'emails');
if (isset($emails) && is_array($emails) && !empty($emails)) {
return $emails;
} }
return [$share->getSharedWith()]; return [$share->getSharedWith()];
} }