enforce password on new share

Signed-off-by: Maxence Lange <maxence@artificial-owl.com>
This commit is contained in:
Maxence Lange 2021-12-28 15:27:45 -01:00
parent 8172baabe5
commit 2cf48068f2
26 changed files with 793 additions and 93 deletions

View file

@ -40,8 +40,10 @@ use OCA\Circles\Events\AddingCircleMemberEvent;
use OCA\Circles\Events\CircleMemberAddedEvent;
use OCA\Circles\Events\Files\CreatingFileShareEvent;
use OCA\Circles\Events\Files\FileShareCreatedEvent;
use OCA\Circles\Events\Files\PreparingFileShareEvent;
use OCA\Circles\Events\MembershipsCreatedEvent;
use OCA\Circles\Events\MembershipsRemovedEvent;
use OCA\Circles\Events\PreparingCircleMemberEvent;
use OCA\Circles\Events\RemovingCircleMemberEvent;
use OCA\Circles\Events\RequestingCircleMemberEvent;
use OCA\Circles\Handlers\WebfingerHandler;
@ -52,7 +54,9 @@ use OCA\Circles\Listeners\Examples\ExampleMembershipsRemoved;
use OCA\Circles\Listeners\Examples\ExampleRequestingCircleMember;
use OCA\Circles\Listeners\Files\AddingMemberSendMail as ListenerFilesAddingMemberSendMail;
use OCA\Circles\Listeners\Files\CreatingShareSendMail as ListenerFilesCreatingShareSendMail;
use OCA\Circles\Listeners\Files\PreparingShareSendMail as ListenerFilesPreparingShareSendMail;
use OCA\Circles\Listeners\Files\MemberAddedSendMail as ListenerFilesMemberAddedSendMail;
use OCA\Circles\Listeners\Files\PreparingMemberSendMail as ListenerFilesPreparingMemberSendMail;
use OCA\Circles\Listeners\Files\RemovingMember as ListenerFilesRemovingMember;
use OCA\Circles\Listeners\Files\ShareCreatedSendMail as ListenerFilesShareCreatedSendMail;
use OCA\Circles\Listeners\GroupCreated;
@ -141,6 +145,10 @@ class Application extends App implements IBootstrap {
$context->registerEventListener(UserRemovedEvent::class, GroupMemberRemoved::class);
// Local Events (for Files/Shares/Notifications management)
$context->registerEventListener(
PreparingCircleMemberEvent::class,
ListenerFilesPreparingMemberSendMail::class
);
$context->registerEventListener(
AddingCircleMemberEvent::class,
ListenerFilesAddingMemberSendMail::class
@ -149,6 +157,10 @@ class Application extends App implements IBootstrap {
CircleMemberAddedEvent::class,
ListenerFilesMemberAddedSendMail::class
);
$context->registerEventListener(
PreparingFileShareEvent::class,
ListenerFilesPreparingShareSendMail::class
);
$context->registerEventListener(
CreatingFileShareEvent::class,
ListenerFilesCreatingShareSendMail::class

View file

@ -1049,6 +1049,13 @@ class CoreQueryBuilder extends NC22ExtendedQueryBuilder {
$this->leftJoinShareToken($alias);
$aliasShareToken = $this->generateAlias($alias, self::TOKEN, $options);
$this->generateSelectAlias(
CoreRequestBuilder::$tables[CoreRequestBuilder::TABLE_TOKEN],
$aliasShareToken,
$aliasShareToken,
[]
);
$this->limit('token', $token, $aliasShareToken);
}

View file

@ -37,7 +37,7 @@ use OCA\Circles\IFederatedUser;
use OCA\Circles\Model\Mount;
/**
* Class GSSharesRequest
* Class MountRequest
*
* @package OCA\Circles\Db
*/
@ -49,8 +49,6 @@ class MountRequest extends MountRequestBuilder {
* @param Mount $mount
*/
public function save(Mount $mount): void {
// TODO: fix hash
$hash = $this->token();
$qb = $this->getMountInsertSql();
$qb->setValue('circle_id', $qb->createNamedParameter($mount->getCircleId()))
->setValue('mount_id', $qb->createNamedParameter($mount->getMountId()))
@ -58,7 +56,7 @@ class MountRequest extends MountRequestBuilder {
->setValue('token', $qb->createNamedParameter($mount->getToken()))
->setValue('parent', $qb->createNamedParameter($mount->getParent()))
->setValue('mountpoint', $qb->createNamedParameter($mount->getMountPoint()))
->setValue('mountpoint_hash', $qb->createNamedParameter($hash));
->setValue('mountpoint_hash', $qb->createNamedParameter(md5($mount->getMountPoint())));
$qb->execute();
}

View file

@ -0,0 +1,59 @@
<?php
declare(strict_types=1);
/**
* Circles - Bring cloud-users closer together.
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Maxence Lange <maxence@artificial-owl.com>
* @copyright 2021
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Circles\Events\Files;
use OCA\Circles\Events\CircleGenericEvent;
use OCA\Circles\Model\Federated\FederatedEvent;
use OCA\Circles\Model\Mount;
/**
* Class PreparingFileShareEvent
*
* @package OCA\Circles\Events\Files
*/
class PreparingFileShareEvent extends CircleGenericEvent {
/** @var Mount */
private $mount;
/**
* PreparingFileShareEvent constructor.
*
* @param FederatedEvent $federatedEvent
*/
public function __construct(FederatedEvent $federatedEvent) {
parent::__construct($federatedEvent);
}
}

View file

@ -0,0 +1,56 @@
<?php
declare(strict_types=1);
/**
* Circles - Bring cloud-users closer together.
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Maxence Lange <maxence@artificial-owl.com>
* @copyright 2021
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Circles\Events;
use OCA\Circles\Model\Federated\FederatedEvent;
/**
* Class PreparingCircleMemberEvent
*
* This event is called when one or multiple members are added to a Circle.
*
* This event is called on the master instance of the circle, before AddingCircleMemberEvent.
*
* @package OCA\Circles\Events
*/
class PreparingCircleMemberEvent extends CircleMemberGenericEvent {
/**
* PreparingCircleMemberEvent constructor.
*
* @param FederatedEvent $federatedEvent
*/
public function __construct(FederatedEvent $federatedEvent) {
parent::__construct($federatedEvent);
}
}

View file

@ -32,7 +32,6 @@ declare(strict_types=1);
namespace OCA\Circles\FederatedItems\Files;
use ArtificialOwl\MySmallPhpTools\Exceptions\InvalidItemException;
use ArtificialOwl\MySmallPhpTools\Exceptions\ItemNotFoundException;
use ArtificialOwl\MySmallPhpTools\Exceptions\UnknownTypeException;
use ArtificialOwl\MySmallPhpTools\Traits\Nextcloud\nc22\TNC22Logger;
use ArtificialOwl\MySmallPhpTools\Traits\TStringTools;
@ -94,24 +93,26 @@ class FileShare implements
* @param FederatedEvent $event
*/
public function verify(FederatedEvent $event): void {
// TODO: check and improve
// TODO: Could we use a share lock ?
// TODO: check (origin of file ?) and improve
// TODO: Use a share lock
$this->eventService->fileSharePreparing($event);
}
/**
* @param FederatedEvent $event
*
* @throws CircleNotFoundException
* @throws InvalidItemException
* @throws UnknownTypeException
* @throws CircleNotFoundException
* @throws ItemNotFoundException
*/
public function manage(FederatedEvent $event): void {
$mount = null;
if (!$this->configService->isLocalInstance($event->getOrigin())) {
/** @var ShareWrapper $wrappedShare */
$wrappedShare = $event->getParams()->gObj('wrappedShare', ShareWrapper::class);
$mount = new Mount();
$mount->fromShare($wrappedShare);
$mount->setMountId($this->token(15));

View file

@ -81,6 +81,11 @@ class MassiveMemberAdd extends SingleMemberAdd implements
$event->setMembers($filtered);
$event->setOutcome($this->serializeArray($filtered));
foreach ($event->getMembers() as $member) {
$event->setMember($member);
$this->eventService->memberPreparing($event);
}
}

View file

@ -76,9 +76,9 @@ use OCA\Circles\StatusCode;
use OCP\IUserManager;
/**
* Class MemberAdd
* Class SingleMemberAdd
*
* @package OCA\Circles\GlobalScale
* @package OCA\Circles\FederatedItems
*/
class SingleMemberAdd implements
IFederatedItem,
@ -182,35 +182,7 @@ class SingleMemberAdd implements
$event->setMembers([$member]);
$event->setOutcome($this->serialize($member));
return;
// $member = $this->membersRequest->getFreshNewMember(
// $circle->getUniqueId(), $ident, $eventMember->getType(), $eventMember->getInstance()
// );
// $member->hasToBeInviteAble()
//
// $this->membersService->addMemberBasedOnItsType($circle, $member);
//
// $password = '';
// $sendPasswordByMail = false;
// if ($this->configService->enforcePasswordProtection($circle)) {
// if ($circle->getSetting('password_single_enabled') === 'true') {
// $password = $circle->getPasswordSingle();
// } else {
// $sendPasswordByMail = true;
// $password = $this->miscService->token(15);
// }
// }
//
// $event->setData(
// new SimpleDataStore(
// [
// 'password' => $password,
// 'passwordByMail' => $sendPasswordByMail
// ]
// )
// );
$this->eventService->memberPreparing($event);
}
@ -234,7 +206,7 @@ class SingleMemberAdd implements
$this->eventService->memberAdding($event);
}
//
// //
// // TODO: verifiez comment se passe le cached name sur un member_add
// //

View file

@ -46,7 +46,7 @@ use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventListener;
/**
* Class AddingMember
* Class AddingMemberSendMail
*
* @package OCA\Circles\Listeners\Files
*/
@ -101,9 +101,7 @@ class AddingMemberSendMail implements IEventListener {
return;
}
$result = [];
$member = $event->getMember();
if ($member->getUserType() === Member::TYPE_CIRCLE) {
$members = $member->getBasedOn()->getInheritedMembers();
} else {
@ -111,9 +109,11 @@ class AddingMemberSendMail implements IEventListener {
}
$circle = $event->getCircle();
$federatedEvent = $event->getFederatedEvent();
$shares = $this->shareWrapperService->getSharesToCircle($circle->getSingleId());
$hashedPasswords = $federatedEvent->getParams()->gArray('hashedPasswords');
/** @var Member[] $members */
$result = [];
foreach ($members as $member) {
if ($member->getUserType() !== Member::TYPE_MAIL
&& $member->getUserType() !== Member::TYPE_CONTACT
@ -124,13 +124,17 @@ class AddingMemberSendMail implements IEventListener {
$files = [];
foreach ($shares as $share) {
try {
$shareToken = $this->shareTokenService->generateShareToken($share, $member);
$shareToken = $this->shareTokenService->generateShareToken(
$share,
$member,
$this->get($member->getSingleId(), $hashedPasswords)
);
} catch (Exception $e) {
continue;
}
$share->setShareToken($shareToken);
$files[] = $share;
$files[] = clone $share;
}
$result[$member->getId()] = [
@ -139,6 +143,6 @@ class AddingMemberSendMail implements IEventListener {
];
}
$event->getFederatedEvent()->setResultEntry('files', $result);
$federatedEvent->addResultEntry('files', $result);
}
}

View file

@ -114,8 +114,8 @@ class CreatingShareSendMail implements IEventListener {
}
$circle = $event->getCircle();
$federatedEvent = $event->getFederatedEvent();
$hashedPasswords = $federatedEvent->getParams()->gArray('hashedPasswords');
$result = [];
foreach ($circle->getInheritedMembers(false, true) as $member) {
@ -136,7 +136,12 @@ class CreatingShareSendMail implements IEventListener {
throw new ShareWrapperNotFoundException();
}
$shareToken = $this->shareTokenService->generateShareToken($share, $member);
$shareToken = $this->shareTokenService->generateShareToken(
$share,
$member,
$this->get($member->getSingleId(), $hashedPasswords)
);
$share->setShareToken($shareToken);
} catch (Exception $e) {
$share = null;
@ -149,6 +154,6 @@ class CreatingShareSendMail implements IEventListener {
];
}
$event->getFederatedEvent()->setResultEntry('info', $result);
$federatedEvent->setResultEntry('info', $result);
}
}

View file

@ -31,6 +31,7 @@ declare(strict_types=1);
namespace OCA\Circles\Listeners\Files;
use ArtificialOwl\MySmallPhpTools\Model\SimpleDataStore;
use ArtificialOwl\MySmallPhpTools\Traits\Nextcloud\nc22\TNC22Logger;
use ArtificialOwl\MySmallPhpTools\Traits\TStringTools;
use OCA\Circles\AppInfo\Application;
@ -93,6 +94,8 @@ class MemberAddedSendMail implements IEventListener {
$members = [$member];
}
$clearPasswords = $event->getFederatedEvent()->getInternal()->gArray('clearPasswords');
/** @var Member[] $members */
foreach ($members as $member) {
if ($member->getUserType() !== Member::TYPE_MAIL
@ -101,23 +104,24 @@ class MemberAddedSendMail implements IEventListener {
continue;
}
$mails = [];
$shares = [];
$mails = $shares = [];
foreach ($event->getResults() as $origin => $item) {
$files = $item->gData('files');
if (!$files->hasKey($member->getId())) {
continue;
}
foreach ($item->gArray('files') as $filesArray) {
$files = new SimpleDataStore($filesArray);
if (!$files->hasKey($member->getId())) {
continue;
}
$data = $files->gData($member->getId());
$shares = array_merge($shares, $data->gObjs('shares', ShareWrapper::class));
$data = $files->gData($member->getId());
$shares = array_merge($shares, $data->gObjs('shares', ShareWrapper::class));
// TODO: is it safe to use $origin to compare getInstance() ?
// TODO: do we need to check the $origin ?
// TODO: Solution would be to check the origin based on aliases using RemoteInstanceService
// TODO: is it safe to use $origin to compare getInstance() ?
// TODO: do we need to check the $origin ?
// TODO: Solution would be to check the origin based on aliases using RemoteInstanceService
// if ($member->getUserType() === Member::TYPE_CONTACT && $member->getInstance() === $origin) {
$mails = $data->gArray('mails');
$mails = array_merge($mails, $data->gArray('mails'));
// }
}
}
if ($member->hasInvitedBy()) {
@ -126,7 +130,14 @@ class MemberAddedSendMail implements IEventListener {
$author = 'someone';
}
$this->sendMailService->generateMail($author, $circle, $member, $shares, $mails);
$this->sendMailService->generateMail(
$author,
$circle,
$member,
$shares,
$mails,
$this->get($member->getSingleId(), $clearPasswords)
);
}
}
}

View file

@ -0,0 +1,148 @@
<?php
declare(strict_types=1);
/**
* Circles - Bring cloud-users closer together.
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Maxence Lange <maxence@artificial-owl.com>
* @copyright 2021
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Circles\Listeners\Files;
use ArtificialOwl\MySmallPhpTools\Traits\Nextcloud\nc22\TNC22Logger;
use ArtificialOwl\MySmallPhpTools\Traits\TStringTools;
use OCA\Circles\AppInfo\Application;
use OCA\Circles\Events\PreparingCircleMemberEvent;
use OCA\Circles\Exceptions\FederatedItemException;
use OCA\Circles\Exceptions\RemoteInstanceException;
use OCA\Circles\Exceptions\RemoteNotFoundException;
use OCA\Circles\Exceptions\RemoteResourceNotFoundException;
use OCA\Circles\Exceptions\RequestBuilderException;
use OCA\Circles\Exceptions\UnknownRemoteException;
use OCA\Circles\Model\Member;
use OCA\Circles\Service\ConfigService;
use OCA\Circles\Service\ContactService;
use OCA\Circles\Service\ShareTokenService;
use OCA\Circles\Service\ShareWrapperService;
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventListener;
use OCP\Security\IHasher;
/**
* Class PreparingMemberSendMail
*
* @package OCA\Circles\Listeners\Files
*/
class PreparingMemberSendMail implements IEventListener {
use TStringTools;
use TNC22Logger;
/** @var IHasher */
private $hasher;
/** @var ShareWrapperService */
private $shareWrapperService;
/** @var ShareTokenService */
private $shareTokenService;
/** @var ConfigService */
private $configService;
/** @var ContactService */
private $contactService;
/**
* AddingMember constructor.
*
* @param IHasher $hasher
* @param ShareWrapperService $shareWrapperService
* @param ShareTokenService $shareTokenService
* @param ContactService $contactService
* @param ConfigService $configService
*/
public function __construct(
IHasher $hasher,
ShareWrapperService $shareWrapperService,
ShareTokenService $shareTokenService,
ContactService $contactService,
ConfigService $configService
) {
$this->hasher = $hasher;
$this->shareWrapperService = $shareWrapperService;
$this->shareTokenService = $shareTokenService;
$this->contactService = $contactService;
$this->configService = $configService;
$this->setup('app', Application::APP_ID);
}
/**
* @param Event $event
*
* @throws RequestBuilderException
* @throws FederatedItemException
* @throws RemoteInstanceException
* @throws RemoteNotFoundException
* @throws RemoteResourceNotFoundException
* @throws UnknownRemoteException
*/
public function handle(Event $event): void {
if (!$event instanceof PreparingCircleMemberEvent
|| !$this->configService->enforcePasswordOnSharedFile()) {
return;
}
$member = $event->getMember();
if ($member->getUserType() === Member::TYPE_CIRCLE) {
$members = $member->getBasedOn()->getInheritedMembers();
} else {
$members = [$member];
}
$federatedEvent = $event->getFederatedEvent();
$clearPasswords = $federatedEvent->getInternal()->gArray('clearPasswords');
$hashedPasswords = $federatedEvent->getParams()->gArray('hashedPasswords');
foreach ($members as $member) {
if (($member->getUserType() !== Member::TYPE_MAIL
&& $member->getUserType() !== Member::TYPE_CONTACT)
|| array_key_exists($member->getSingleId(), $clearPasswords)
) {
continue;
}
$clearPassword = $this->token(14);
$clearPasswords[$member->getSingleId()] = $clearPassword;
$hashedPasswords[$member->getSingleId()] = $this->hasher->hash($clearPassword);
}
$federatedEvent->getInternal()->aArray('clearPasswords', $clearPasswords);
$federatedEvent->getParams()->aArray('hashedPasswords', $hashedPasswords);
}
}

View file

@ -0,0 +1,142 @@
<?php
declare(strict_types=1);
/**
* Circles - Bring cloud-users closer together.
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Maxence Lange <maxence@artificial-owl.com>
* @copyright 2021
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Circles\Listeners\Files;
use ArtificialOwl\MySmallPhpTools\Traits\Nextcloud\nc22\TNC22Logger;
use ArtificialOwl\MySmallPhpTools\Traits\TStringTools;
use OCA\Circles\AppInfo\Application;
use OCA\Circles\Events\Files\PreparingFileShareEvent;
use OCA\Circles\Exceptions\FederatedItemException;
use OCA\Circles\Exceptions\RemoteInstanceException;
use OCA\Circles\Exceptions\RemoteNotFoundException;
use OCA\Circles\Exceptions\RemoteResourceNotFoundException;
use OCA\Circles\Exceptions\RequestBuilderException;
use OCA\Circles\Exceptions\UnknownRemoteException;
use OCA\Circles\Model\Member;
use OCA\Circles\Service\ConfigService;
use OCA\Circles\Service\ContactService;
use OCA\Circles\Service\ShareTokenService;
use OCA\Circles\Service\ShareWrapperService;
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventListener;
use OCP\Security\IHasher;
/**
* Class PreparingShareSendMail
*
* @package OCA\Circles\Listeners\Files
*/
class PreparingShareSendMail implements IEventListener {
use TStringTools;
use TNC22Logger;
/** @var IHasher */
private $hasher;
/** @var ShareWrapperService */
private $shareWrapperService;
/** @var ShareTokenService */
private $shareTokenService;
/** @var ConfigService */
private $configService;
/** @var ContactService */
private $contactService;
/**
* PreparingShareSendMail constructor.
*
* @param IHasher $hasher
* @param ShareWrapperService $shareWrapperService
* @param ShareTokenService $shareTokenService
* @param ContactService $contactService
* @param ConfigService $configService
*/
public function __construct(
IHasher $hasher,
ShareWrapperService $shareWrapperService,
ShareTokenService $shareTokenService,
ContactService $contactService,
ConfigService $configService
) {
$this->hasher = $hasher;
$this->shareWrapperService = $shareWrapperService;
$this->shareTokenService = $shareTokenService;
$this->contactService = $contactService;
$this->configService = $configService;
$this->setup('app', Application::APP_ID);
}
/**
* @param Event $event
*
* @throws FederatedItemException
* @throws RemoteInstanceException
* @throws RemoteNotFoundException
* @throws RemoteResourceNotFoundException
* @throws RequestBuilderException
* @throws UnknownRemoteException
*/
public function handle(Event $event): void {
if (!$event instanceof PreparingFileShareEvent
|| !$this->configService->enforcePasswordOnSharedFile()) {
return;
}
$circle = $event->getCircle();
$federatedEvent = $event->getFederatedEvent();
$clearPasswords = $federatedEvent->getInternal()->gArray('clearPasswords');
$hashedPasswords = $federatedEvent->getParams()->gArray('hashedPasswords');
foreach ($circle->getInheritedMembers(false, true) as $member) {
if (($member->getUserType() !== Member::TYPE_MAIL
&& $member->getUserType() !== Member::TYPE_CONTACT)
|| array_key_exists($member->getSingleId(), $clearPasswords)
) {
continue;
}
$clearPassword = $this->token(14);
$clearPasswords[$member->getSingleId()] = $clearPassword;
$hashedPasswords[$member->getSingleId()] = $this->hasher->hash($clearPassword);
}
$federatedEvent->getInternal()->aArray('clearPasswords', $clearPasswords);
$federatedEvent->getParams()->aArray('hashedPasswords', $hashedPasswords);
}
}

View file

@ -126,6 +126,7 @@ class ShareCreatedSendMail implements IEventListener {
}
$circle = $event->getCircle();
$clearPasswords = $event->getFederatedEvent()->getInternal()->gArray('clearPasswords');
foreach ($circle->getInheritedMembers(false, true) as $member) {
if ($member->getUserType() !== Member::TYPE_MAIL
@ -153,6 +154,7 @@ class ShareCreatedSendMail implements IEventListener {
}
try {
// are we sure the 'share' entry is valid and not spoofed !?
/** @var ShareWrapper $share */
$share = $data->gObj('share', ShareWrapper::class);
} catch (Exception $e) {
@ -166,7 +168,14 @@ class ShareCreatedSendMail implements IEventListener {
$author = 'someone';
}
$this->sendMailService->generateMail($author, $circle, $member, [$share], $mails);
$this->sendMailService->generateMail(
$author,
$circle,
$member,
[$share],
$mails,
$this->get($member->getSingleId(), $clearPasswords)
);
}
}
}

View file

@ -0,0 +1,84 @@
<?php
declare(strict_types=1);
/**
* Circles - Bring cloud-users closer together.
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Maxence Lange <maxence@artificial-owl.com>
* @copyright 2021
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Circles\Migration;
use Closure;
use Doctrine\DBAL\Schema\SchemaException;
use OCP\DB\ISchemaWrapper;
use OCP\IDBConnection;
use OCP\Migration\IOutput;
use OCP\Migration\SimpleMigrationStep;
/**
* Class Version0023Date20211216113101
*
* @package OCA\Circles\Migration
*/
class Version0023Date20211216113101 extends SimpleMigrationStep {
/**
* @param IDBConnection $connection
*/
public function __construct(IDBConnection $connection) {
}
/**
* @param IOutput $output
* @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
* @param array $options
*
* @return null|ISchemaWrapper
* @throws SchemaException
*/
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper {
/** @var ISchemaWrapper $schema */
$schema = $schemaClosure();
if ($schema->hasTable('circles_token')) {
$table = $schema->getTable('circles_token');
$table->changeColumn(
'password', [
'length' => 127
]
);
}
if ($schema->hasTable('circles_event')) {
$table = $schema->getTable('circles_event');
$table->setPrimaryKey(['token']);
}
return $schema;
}
}

View file

@ -80,6 +80,9 @@ class FederatedEvent implements JsonSerializable {
/** @var SimpleDataStore */
private $params;
/** @var SimpleDataStore */
private $internal;
/** @var SimpleDataStore */
private $data;
@ -120,6 +123,7 @@ class FederatedEvent implements JsonSerializable {
public function __construct(string $class = '') {
$this->class = $class;
$this->params = new SimpleDataStore();
$this->internal = new SimpleDataStore();
$this->data = new SimpleDataStore();
$this->result = new SimpleDataStore();
}
@ -391,6 +395,34 @@ class FederatedEvent implements JsonSerializable {
}
/**
* @param SimpleDataStore $internal
*
* @return self
*/
public function setInternal(SimpleDataStore $internal): self {
$this->internal = $internal;
return $this;
}
/**
* @return SimpleDataStore
*/
public function getInternal(): SimpleDataStore {
return $this->internal;
}
/**
* @return $this
*/
public function resetInternal(): self {
$this->internal = new SimpleDataStore();
return $this;
}
/**
* @param SimpleDataStore $data
*
@ -413,6 +445,7 @@ class FederatedEvent implements JsonSerializable {
* @return $this
*/
public function resetData(): self {
$this->resetInternal();
$this->data = new SimpleDataStore();
return $this;
@ -550,6 +583,7 @@ class FederatedEvent implements JsonSerializable {
$this->setClass($this->get('class', $data));
$this->setSeverity($this->getInt('severity', $data));
$this->setParams(new SimpleDataStore($this->getArray('params', $data)));
$this->setInternal(new SimpleDataStore($this->getArray('internal', $data)));
$this->setData(new SimpleDataStore($this->getArray('data', $data)));
$this->setResult(new SimpleDataStore($this->getArray('result', $data)));
$this->setOrigin($this->get('origin', $data));
@ -587,6 +621,7 @@ class FederatedEvent implements JsonSerializable {
'class' => $this->getClass(),
'severity' => $this->getSeverity(),
'params' => $this->getParams(),
'internal' => $this->getInternal(),
'data' => $this->getData(),
'result' => $this->getResult(),
'origin' => $this->getOrigin(),

View file

@ -48,6 +48,7 @@ use OCA\Circles\Exceptions\RemoteInstanceException;
use OCA\Circles\Exceptions\RemoteNotFoundException;
use OCA\Circles\Exceptions\RemoteResourceNotFoundException;
use OCA\Circles\Exceptions\RequestBuilderException;
use OCA\Circles\Exceptions\ShareTokenNotFoundException;
use OCA\Circles\Exceptions\UnknownInterfaceException;
use OCA\Circles\Exceptions\UnknownRemoteException;
use OCA\Circles\IEntity;
@ -463,6 +464,15 @@ class ModelManager {
} catch (FileCacheNotFoundException $e) {
}
break;
case CoreQueryBuilder::TOKEN:
try {
$token = new ShareToken();
$token->importFromDatabase($data, $prefix);
$shareWrapper->setShareToken($token);
} catch (ShareTokenNotFoundException $e) {
}
break;
}
}

View file

@ -36,6 +36,7 @@ use ArtificialOwl\MySmallPhpTools\Exceptions\InvalidItemException;
use ArtificialOwl\MySmallPhpTools\IDeserializable;
use ArtificialOwl\MySmallPhpTools\Traits\TArrayTools;
use JsonSerializable;
use OCA\Circles\Exceptions\ShareTokenNotFoundException;
use OCP\Share\IShare;
class ShareToken implements IDeserializable, INC22QueryRow, JsonSerializable {
@ -277,8 +278,13 @@ class ShareToken implements IDeserializable, INC22QueryRow, JsonSerializable {
* @param string $prefix
*
* @return INC22QueryRow
* @throws ShareTokenNotFoundException
*/
public function importFromDatabase(array $data, string $prefix = ''): INC22QueryRow {
if ($this->get($prefix . 'token', $data) === '') {
throw new ShareTokenNotFoundException();
}
$this->setShareId($this->getInt($prefix . 'share_id', $data));
$this->setCircleId($this->get($prefix . 'circle_id', $data));
$this->setSingleId($this->get($prefix . 'single_id', $data));

View file

@ -623,6 +623,9 @@ class ShareWrapper extends ManagedModel implements IDeserializable, INC22QueryRo
$share->setTarget($this->getFileTarget());
$share->setProviderId($this->getProviderId());
$share->setStatus($this->getStatus());
if ($this->hasShareToken() && $this->getShareToken()->getPassword() !== '') {
$share->setPassword($this->getShareToken()->getPassword());
}
$share->setShareTime($this->getShareTime())
->setSharedWith($this->getSharedWith())

View file

@ -99,6 +99,7 @@ class ConfigService {
public const KEYHOLE_CFG_REQUEST = 'keyhole_cfg_request';
public const ROUTE_TO_CIRCLE = 'route_to_circle';
public const EVENT_EXAMPLES = 'event_examples';
public const ENFORCE_PASSWORD = 'enforce_password';
public const SELF_SIGNED_CERT = 'self_signed_cert';
public const MEMBERS_LIMIT = 'members_limit';
@ -129,7 +130,7 @@ class ConfigService {
public const TEST_NC_BASE = 'test_nc_base';
private $defaults = [
private static $defaults = [
self::FRONTAL_CLOUD_BASE => '',
self::FRONTAL_CLOUD_ID => '',
self::FRONTAL_CLOUD_SCHEME => 'https',
@ -173,6 +174,7 @@ class ConfigService {
self::KEYHOLE_CFG_REQUEST => '0',
self::ROUTE_TO_CIRCLE => 'contacts.contacts.directcircle',
self::EVENT_EXAMPLES => '0',
self::ENFORCE_PASSWORD => '2',
self::SELF_SIGNED_CERT => '0',
self::MEMBERS_LIMIT => '-1',
@ -237,7 +239,7 @@ class ConfigService {
return $value;
}
return $this->get($key, $this->defaults);
return $this->get($key, self::$defaults);
}
/**
@ -329,16 +331,43 @@ class ConfigService {
* @return bool
* @deprecated
* should the password for a mail share be send to the recipient
*
*/
public function sendPasswordByMail() {
if ($this->isContactsBackend()) {
return false;
public function sendPasswordByMail(): bool {
return false;
}
/**
* @return bool
*/
public function enforcePasswordOnSharedFile(): bool {
$localPolicy = $this->getAppValueInt(ConfigService::ENFORCE_PASSWORD);
if ($localPolicy !== $this->getInt(ConfigService::ENFORCE_PASSWORD, self::$defaults)) {
return ($localPolicy === 1);
}
return ($this->config->getAppValue('sharebymail', 'sendpasswordmail', 'yes') === 'yes');
// TODO: reimplement a way to set password protection on a single Circle
// if ($circle->getSetting('password_enforcement') === 'true') {
// return true;
// }
$sendPasswordMail = $this->config->getAppValue(
'sharebymail',
'sendpasswordmail',
'yes'
);
$enforcePasswordProtection = $this->config->getAppValue(
'core',
'shareapi_enforce_links_password',
'no'
);
return ($sendPasswordMail === 'yes'
&& $enforcePasswordProtection === 'yes');
}
/**
* @param DeprecatedCircle $circle
*
@ -348,15 +377,7 @@ class ConfigService {
*
*/
public function enforcePasswordProtection(DeprecatedCircle $circle) {
if ($this->isContactsBackend()) {
return false;
}
if ($circle->getSetting('password_enforcement') === 'true') {
return true;
}
return ($this->config->getAppValue('sharebymail', 'enforcePasswordProtection', 'no') === 'yes');
return false;
}

View file

@ -52,8 +52,10 @@ use OCA\Circles\Events\EditingCircleEvent;
use OCA\Circles\Events\EditingCircleMemberEvent;
use OCA\Circles\Events\Files\CreatingFileShareEvent;
use OCA\Circles\Events\Files\FileShareCreatedEvent;
use OCA\Circles\Events\Files\PreparingFileShareEvent;
use OCA\Circles\Events\MembershipsCreatedEvent;
use OCA\Circles\Events\MembershipsRemovedEvent;
use OCA\Circles\Events\PreparingCircleMemberEvent;
use OCA\Circles\Events\RemovingCircleMemberEvent;
use OCA\Circles\Events\RequestingCircleMemberEvent;
use OCA\Circles\Model\Federated\FederatedEvent;
@ -141,6 +143,14 @@ class EventService {
}
/**
* @param FederatedEvent $federatedEvent
*/
public function memberPreparing(FederatedEvent $federatedEvent): void {
$event = new PreparingCircleMemberEvent($federatedEvent);
$this->eventDispatcher->dispatchTyped($event);
}
/**
* @param FederatedEvent $federatedEvent
*/
@ -305,6 +315,14 @@ class EventService {
}
/**
* @param FederatedEvent $federatedEvent
*/
public function fileSharePreparing(FederatedEvent $federatedEvent): void {
$event = new PreparingFileShareEvent($federatedEvent);
$this->eventDispatcher->dispatchTyped($event);
}
/**
* @param FederatedEvent $federatedEvent
* @param Mount|null $mount

View file

@ -155,7 +155,8 @@ class FederatedEventService extends NC22Signature {
* @throws RequestBuilderException
*/
public function newEvent(FederatedEvent $event): array {
$event->setOrigin($this->interfaceService->getLocalInstance());
$event->setOrigin($this->interfaceService->getLocalInstance())
->resetData();
$federatedItem = $this->getFederatedItem($event, false);
$this->confirmInitiator($event, true);

View file

@ -108,12 +108,15 @@ class RemoteUpstreamService {
* @throws UnknownRemoteException
*/
public function broadcastEvent(EventWrapper $wrapper): void {
$event = clone $wrapper->getEvent();
$event->resetInternal();
$this->interfaceService->setCurrentInterface($wrapper->getInterface());
$data = $this->remoteStreamService->resultRequestRemoteInstance(
$wrapper->getInstance(),
RemoteInstance::INCOMING,
Request::TYPE_POST,
$wrapper->getEvent()
$event
);
$wrapper->getEvent()->setResult(new SimpleDataStore($data));

View file

@ -58,6 +58,9 @@ class SendMailService {
/** @var Defaults */
private $defaults;
/** @var ConfigService */
private $configService;
/**
* SendMailService constructor.
@ -65,15 +68,18 @@ class SendMailService {
* @param IL10N $l10n
* @param IMailer $mailer
* @param Defaults $defaults
* @param ConfigService $configService
*/
public function __construct(
IL10N $l10n,
IMailer $mailer,
Defaults $defaults
Defaults $defaults,
ConfigService $configService
) {
$this->l10n = $l10n;
$this->mailer = $mailer;
$this->defaults = $defaults;
$this->configService = $configService;
}
@ -83,8 +89,16 @@ class SendMailService {
* @param Member $member
* @param ShareWrapper[] $shares
* @param array $mails
* @param string $password
*/
public function generateMail(string $author, Circle $circle, Member $member, array $shares, array $mails): void {
public function generateMail(
string $author,
Circle $circle,
Member $member,
array $shares,
array $mails,
string $password = ''
): void {
if (empty($shares)) {
return;
}
@ -117,6 +131,8 @@ class SendMailService {
$this->sendMailExistingShares($template, $author, $mail, sizeof($links) > 1);
} catch (Exception $e) {
}
$this->sendMailPassword($author, $circle->getDisplayName(), $mail, $password);
}
}
@ -194,4 +210,74 @@ class SendMailService {
$this->mailer->send($message);
}
/**
* @param string $author
* @param string $circleName
* @param string $email
* @param string $password
*/
private function sendMailPassword(
string $author,
string $circleName,
string $email,
string $password
): void {
if (!$this->configService->enforcePasswordOnSharedFile() || $password === '') {
return;
}
$message = $this->mailer->createMessage();
$plainBodyPart = $this->l10n->t(
"%1\$s shared some content with you.\nYou should have already received a separate email with a link to access it.\n",
[$author]
);
$htmlBodyPart = $this->l10n->t(
'%1$s shared some content with you. You should have already received a separate email with a link to access it.',
[$author]
);
$emailTemplate = $this->mailer->createEMailTemplate(
'sharebymail.RecipientPasswordNotification',
[
'filename' => '',
'password' => $password,
'initiator' => $author,
// 'initiatorEmail' => Util::getDefaultEmailAddress(''),
'initiatorEmail' => '',
'shareWith' => $circleName
]
);
$emailTemplate->setSubject(
$this->l10n->t('Password to access content shared to you by %1$s', [$author])
);
$emailTemplate->addHeader();
$emailTemplate->addHeading($this->l10n->t('Password to access content'), false);
$emailTemplate->addBodyText(htmlspecialchars($htmlBodyPart), $plainBodyPart);
$emailTemplate->addBodyText($this->l10n->t('It is protected with the following password:'));
$emailTemplate->addBodyText($password);
// The "From" contains the sharers name
$instanceName = $this->defaults->getName();
$senderName = $this->l10n->t(
'%1$s via %2$s',
[
$author,
$instanceName
]
);
$message->setFrom([\OCP\Util::getDefaultEmailAddress($instanceName) => $senderName]);
// if ($initiatorEmailAddress !== null) {
// $message->setReplyTo([$initiatorEmailAddress => $initiatorDisplayName]);
// $emailTemplate->addFooter($instanceName . ' - ' . $this->defaults->getSlogan());
// } else {
$emailTemplate->addFooter();
// }
$message->setTo([$email]);
$message->useTemplate($emailTemplate);
$this->mailer->send($message);
}
}

View file

@ -87,7 +87,7 @@ class ShareTokenService {
/**
* @param ShareWrapper $share
* @param Member $member
* @param string $password
* @param string $hashedPassword
*
* @return ShareToken
* @throws ShareTokenAlreadyExistException
@ -96,7 +96,7 @@ class ShareTokenService {
public function generateShareToken(
ShareWrapper $share,
Member $member,
string $password = ''
string $hashedPassword = ''
): ShareToken {
if ($member->getUserType() !== Member::TYPE_MAIL
&& $member->getUserType() !== Member::TYPE_CONTACT) {
@ -104,14 +104,13 @@ class ShareTokenService {
}
$token = $this->token(19);
$shareToken = new ShareToken();
$shareToken->setShareId((int)$share->getId())
->setCircleId($share->getSharedWith())
->setSingleId($member->getSingleId())
->setMemberId($member->getId())
->setToken($token)
->setPassword($password)
->setPassword($hashedPassword)
->setAccepted(IShare::STATUS_ACCEPTED);
try {

View file

@ -143,8 +143,13 @@ class ShareByCircleProvider implements IShareProvider {
* @param IURLGenerator $urlGenerator
*/
public function __construct(
IDBConnection $connection, ISecureRandom $secureRandom, IUserManager $userManager,
IRootFolder $rootFolder, IL10N $l10n, ILogger $logger, IURLGenerator $urlGenerator
IDBConnection $connection,
ISecureRandom $secureRandom,
IUserManager $userManager,
IRootFolder $rootFolder,
IL10N $l10n,
ILogger $logger,
IURLGenerator $urlGenerator
) {
$this->userManager = $userManager;
$this->rootFolder = $rootFolder;
@ -232,8 +237,8 @@ class ShareByCircleProvider implements IShareProvider {
}
$event = new FederatedEvent(FileShare::class);
$event->setCircle($circle)
->getParams()->sObj('wrappedShare', $wrappedShare);
$event->setCircle($circle);
$event->getParams()->sObj('wrappedShare', $wrappedShare);
$this->federatedEventService->newEvent($event);
$this->eventService->localShareCreated($wrappedShare);