allow one single password per Circles

Signed-off-by: Maxence Lange <maxence@artificial-owl.com>
This commit is contained in:
Maxence Lange 2022-03-07 17:43:55 -01:00
parent 1675a83921
commit 658615fd87
16 changed files with 553 additions and 155 deletions

View file

@ -57,6 +57,7 @@ Those groups of users (or "circles") can then be used by any other app for shari
<command>OCA\Circles\Command\CirclesDestroy</command>
<command>OCA\Circles\Command\CirclesDetails</command>
<command>OCA\Circles\Command\CirclesConfig</command>
<command>OCA\Circles\Command\CirclesSetting</command>
<command>OCA\Circles\Command\CirclesSync</command>
<command>OCA\Circles\Command\CirclesCheck</command>
<command>OCA\Circles\Command\CirclesTest</command>

View file

@ -58,7 +58,7 @@ return [
['name' => 'Local#editName', 'url' => '/circles/{circleId}/name', 'verb' => 'PUT'],
['name' => 'Local#editDescription', 'url' => '/circles/{circleId}/description', 'verb' => 'PUT'],
['name' => 'Local#editSettings', 'url' => '/circles/{circleId}/settings', 'verb' => 'PUT'],
['name' => 'Local#editSetting', 'url' => '/circles/{circleId}/setting', 'verb' => 'PUT'],
['name' => 'Local#editConfig', 'url' => '/circles/{circleId}/config', 'verb' => 'PUT'],
['name' => 'Local#link', 'url' => '/link/{circleId}/{singleId}', 'verb' => 'GET'],
@ -95,7 +95,7 @@ return [
['name' => 'Admin#circleLeave', 'url' => '/admin/{emulated}/circles/{circleId}/leave', 'verb' => 'PUT'],
['name' => 'Admin#editName', 'url' => '/admin/{emulated}/circles/{circleId}/name', 'verb' => 'PUT'],
['name' => 'Admin#editDescription', 'url' => '/admin/{emulated}/circles/{circleId}/description', 'verb' => 'PUT'],
['name' => 'Admin#editSettings', 'url' => '/admin/{emulated}/circles/{circleId}/settings', 'verb' => 'PUT'],
['name' => 'Admin#editSetting', 'url' => '/admin/{emulated}/circles/{circleId}/setting', 'verb' => 'PUT'],
['name' => 'Admin#editConfig', 'url' => '/admin/{emulated}/circles/{circleId}/config', 'verb' => 'PUT'],
['name' => 'Admin#link', 'url' => '/admin/{emulated}/link/{circleId}/{singleId}', 'verb' => 'GET']
],

View file

@ -220,14 +220,14 @@ class FileSharingBroadcaster implements IBroadcaster {
$password = '';
$sendPasswordByMail = true;
if ($this->configService->enforcePasswordProtection($circle)) {
if ($circle->getSetting('password_single_enabled') === 'true') {
$password = $circle->getPasswordSingle();
$sendPasswordByMail = false;
} else {
$password = $this->miscService->token(15);
}
}
// if ($this->configService->enforcePasswordProtection($circle)) {
// if ($circle->getSetting('password_single_enabled') === 'true') {
// $password = $circle->getPasswordSingle();
// $sendPasswordByMail = false;
// } else {
// $password = $this->miscService->token(15);
// }
// }
$sharesToken =
$this->tokensRequest->generateTokenForMember($member, $share->getId(), $password);
@ -473,9 +473,9 @@ class FileSharingBroadcaster implements IBroadcaster {
* @throws Exception
*/
protected function sendPasswordByMail(IShare $share, $circleName, $email, $password) {
if (!$this->configService->sendPasswordByMail() || $password === '') {
return;
}
// if (!$this->configService->sendPasswordByMail() || $password === '') {
// return;
// }
$message = $this->mailer->createMessage();
@ -594,9 +594,9 @@ class FileSharingBroadcaster implements IBroadcaster {
$data = [];
$password = '';
if ($this->configService->enforcePasswordProtection($circle)) {
$password = $this->miscService->token(15);
}
// if ($this->configService->enforcePasswordProtection($circle)) {
// $password = $this->miscService->token(15);
// }
foreach ($unknownShares as $share) {
try {
@ -628,9 +628,9 @@ class FileSharingBroadcaster implements IBroadcaster {
* @throws Exception
*/
protected function sendPasswordExistingShares(DeprecatedMember $author, string $email, string $password) {
if (!$this->configService->sendPasswordByMail() || $password === '') {
return;
}
// if (!$this->configService->sendPasswordByMail() || $password === '') {
// return;
// }
$message = $this->mailer->createMessage();

View file

@ -0,0 +1,171 @@
<?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 2022
* @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\Command;
use OC\Core\Command\Base;
use OCA\Circles\Exceptions\CircleNotFoundException;
use OCA\Circles\Exceptions\FederatedEventException;
use OCA\Circles\Exceptions\FederatedItemException;
use OCA\Circles\Exceptions\FederatedUserException;
use OCA\Circles\Exceptions\FederatedUserNotFoundException;
use OCA\Circles\Exceptions\InitiatorNotConfirmedException;
use OCA\Circles\Exceptions\InitiatorNotFoundException;
use OCA\Circles\Exceptions\InvalidIdException;
use OCA\Circles\Exceptions\MemberNotFoundException;
use OCA\Circles\Exceptions\OwnerNotFoundException;
use OCA\Circles\Exceptions\RemoteInstanceException;
use OCA\Circles\Exceptions\RemoteNotFoundException;
use OCA\Circles\Exceptions\RemoteResourceNotFoundException;
use OCA\Circles\Exceptions\RequestBuilderException;
use OCA\Circles\Exceptions\SingleCircleNotFoundException;
use OCA\Circles\Exceptions\UnknownRemoteException;
use OCA\Circles\Exceptions\UserTypeNotFoundException;
use OCA\Circles\Model\Helpers\MemberHelper;
use OCA\Circles\Model\Member;
use OCA\Circles\Service\CircleService;
use OCA\Circles\Service\FederatedUserService;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
class CirclesSetting extends Base {
/** @var FederatedUserService */
private $federatedUserService;
/** @var CircleService */
private $circleService;
/**
* @param FederatedUserService $federatedUserService
* @param CircleService $circlesService
*/
public function __construct(FederatedUserService $federatedUserService, CircleService $circlesService) {
parent::__construct();
$this->federatedUserService = $federatedUserService;
$this->circleService = $circlesService;
}
/**
*
*/
protected function configure() {
parent::configure();
$this->setName('circles:manage:setting')
->setDescription('edit setting for a Circle')
->addArgument('circle_id', InputArgument::REQUIRED, 'ID of the circle')
->addArgument('setting', InputArgument::OPTIONAL, 'setting to edit', '')
->addArgument('value', InputArgument::OPTIONAL, 'value', '')
->addOption('unset', '', InputOption::VALUE_NONE, 'unset the setting')
->addOption('initiator', '', InputOption::VALUE_REQUIRED, 'set an initiator to the request', '')
->addOption('initiator-type', '', InputOption::VALUE_REQUIRED, 'set initiator type', '0')
->addOption('status-code', '', InputOption::VALUE_NONE, 'display status code on exception');
}
/**
* @param InputInterface $input
* @param OutputInterface $output
*
* @return int
* @throws FederatedEventException
* @throws FederatedItemException
* @throws InitiatorNotFoundException
* @throws RequestBuilderException
* @throws CircleNotFoundException
* @throws FederatedUserException
* @throws FederatedUserNotFoundException
* @throws InitiatorNotConfirmedException
* @throws InvalidIdException
* @throws MemberNotFoundException
* @throws OwnerNotFoundException
* @throws RemoteInstanceException
* @throws RemoteNotFoundException
* @throws RemoteResourceNotFoundException
* @throws SingleCircleNotFoundException
* @throws UnknownRemoteException
* @throws UserTypeNotFoundException
*/
protected function execute(InputInterface $input, OutputInterface $output): int {
$circleId = (string)$input->getArgument('circle_id');
$setting = (string)$input->getArgument('setting');
$value = (string)$input->getArgument('value');
try {
$this->federatedUserService->commandLineInitiator(
$input->getOption('initiator'),
Member::parseTypeString($input->getOption('initiator-type')),
$circleId,
false
);
if ($setting === '') {
$circle = $this->circleService->getCircle($circleId);
$initiatorHelper = new MemberHelper($circle->getInitiator());
$initiatorHelper->mustBeAdmin();
$output->writeln(json_encode($circle->getSettings(), JSON_PRETTY_PRINT));
return 0;
}
if (!$input->getOption('unset') && $value === '') {
throw new InvalidArgumentException('you need to specify a value');
}
$outcome = $this->circleService->updateSetting(
$circleId,
$setting,
($input->getOption('unset')) ? null : $value,
);
} catch (FederatedItemException $e) {
if ($input->getOption('status-code')) {
throw new FederatedItemException(
' [' . get_class($e) . ', ' . $e->getStatus() . ']' . "\n" . $e->getMessage()
);
}
throw $e;
}
if (strtolower($input->getOption('output')) === 'json') {
$output->writeln(json_encode($outcome, JSON_PRETTY_PRINT));
}
return 0;
}
}

View file

@ -462,25 +462,29 @@ class AdminController extends OcsController {
/**
* @param string $emulated
* @param string $circleId
* @param array $value
* @param string $setting
* @param string|null $value
*
* @return DataResponse
* @throws OCSException
*/
public function editSettings(string $emulated, string $circleId, array $value): DataResponse {
public function editSetting(string $emulated, string $circleId, string $setting, ?string $value = null): DataResponse {
try {
$this->setLocalFederatedUser($emulated);
$outcome = $this->circleService->updateSettings($circleId, $value);
$outcome = $this->circleService->updateSetting($circleId, $setting, $value);
return new DataResponse($this->serializeArray($outcome));
} catch (Exception $e) {
$this->e($e, ['emulated' => $emulated, 'circleId' => $circleId, 'value' => $value]);
$this->e($e, ['circleId' => $circleId, 'setting' => $setting, 'value' => $value]);
throw new OCSException($e->getMessage(), $e->getCode());
}
}
/**
* @param string $emulated
* @param string $circleId

View file

@ -413,6 +413,7 @@ class LocalController extends OcsController {
*
* @param int $limit
* @param int $offset
*
* @return DataResponse
* @throws OCSException
*/
@ -505,20 +506,21 @@ class LocalController extends OcsController {
* @NoAdminRequired
*
* @param string $circleId
* @param array $value
* @param string $setting
* @param string|null $value
*
* @return DataResponse
* @throws OCSException
*/
public function editSettings(string $circleId, array $value): DataResponse {
public function editSetting(string $circleId, string $setting, ?string $value = null): DataResponse {
try {
$this->setCurrentFederatedUser();
$outcome = $this->circleService->updateSettings($circleId, $value);
$outcome = $this->circleService->updateSetting($circleId, $setting, $value);
return new DataResponse($this->serializeArray($outcome));
} catch (Exception $e) {
$this->e($e, ['circleId' => $circleId, 'value' => $value]);
$this->e($e, ['circleId' => $circleId, 'setting' => $setting, 'value' => $value]);
throw new OCSException($e->getMessage(), $e->getCode());
}
}

View file

@ -150,6 +150,18 @@ class CircleRequest extends CircleRequestBuilder {
}
/**
* @param Circle $circle
*/
public function updateSettings(Circle $circle) {
$qb = $this->getCircleUpdateSql();
$qb->set('settings', $qb->createNamedParameter(json_encode($circle->getSettings())));
$qb->limitToUniqueId($circle->getSingleId());
$qb->execute();
}
/**
* @param IFederatedUser|null $initiator
* @param CircleProbe $probe

View file

@ -0,0 +1,118 @@
<?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 2022
* @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\FederatedItems;
use ArtificialOwl\MySmallPhpTools\Traits\Nextcloud\nc22\TNC22Deserialize;
use OCA\Circles\Db\CircleRequest;
use OCA\Circles\IFederatedItem;
use OCA\Circles\IFederatedItemAsyncProcess;
use OCA\Circles\Model\Federated\FederatedEvent;
use OCA\Circles\Model\Helpers\MemberHelper;
class CircleSetting implements
IFederatedItem,
IFederatedItemAsyncProcess {
use TNC22Deserialize;
/** @var CircleRequest */
private $circleRequest;
/**
* CircleConfig constructor.
*
* @param CircleRequest $circleRequest
*/
public function __construct(CircleRequest $circleRequest) {
$this->circleRequest = $circleRequest;
}
/**
* @param FederatedEvent $event
*/
public function verify(FederatedEvent $event): void {
$circle = $event->getCircle();
$initiatorHelper = new MemberHelper($circle->getInitiator());
$initiatorHelper->mustBeAdmin();
$params = $event->getParams();
$setting = $params->g('setting');
$value = $params->gBool('unset') ? null : $params->g('value');
$settings = $circle->getSettings();
if (!is_null($value)) {
$settings[$setting] = $value;
} elseif (array_key_exists($setting, $settings)) {
unset($settings[$setting]);
}
$event->getData()->sArray('settings', $settings);
$new = clone $circle;
$new->setSettings($settings);
$event->setOutcome($this->serialize($new));
}
/**
* @param FederatedEvent $event
*/
public function manage(FederatedEvent $event): void {
$circle = clone $event->getCircle();
$settings = $event->getData()->gArray('settings');
$circle->setSettings($settings);
// TODO list imported from FederatedItem/CircleConfig.php - need to check first there.
// TODO: Check locally that circle is not un-federated during the process
// TODO: if the circle is managed remotely, remove the circle locally
// TODO: if the circle is managed locally, remove non-local users
// TODO: Check locally that circle is not federated during the process
// TODO: sync if it is to broadcast to Trusted RemoteInstance
$this->circleRequest->updateSettings($circle);
}
/**
* @param FederatedEvent $event
* @param array $results
*/
public function result(FederatedEvent $event, array $results): void {
}
}

View file

@ -198,14 +198,14 @@ class FileShare extends AGlobalScaleEvent {
$newCircle = $this->circlesRequest->forceGetCircle($circle->getUniqueId(), true);
$password = '';
$sendPasswordByMail = true;
if ($this->configService->enforcePasswordProtection($newCircle)) {
if ($newCircle->getSetting('password_single_enabled') === 'true') {
$password = $newCircle->getPasswordSingle();
$sendPasswordByMail = false;
} else {
$password = $this->miscService->token(15);
}
}
// if ($this->configService->enforcePasswordProtection($newCircle)) {
// if ($newCircle->getSetting('password_single_enabled') === 'true') {
// $password = $newCircle->getPasswordSingle();
// $sendPasswordByMail = false;
// } else {
// $password = $this->miscService->token(15);
// }
// }
try {
$sharesToken =
@ -310,9 +310,9 @@ class FileShare extends AGlobalScaleEvent {
* @throws Exception
*/
protected function sendPasswordByMail(IShare $share, $circleName, $email, $password) {
if (!$this->configService->sendPasswordByMail() || $password === '') {
return;
}
// if (!$this->configService->sendPasswordByMail() || $password === '') {
// return;
// }
$message = $this->mailer->createMessage();

View file

@ -55,7 +55,7 @@ use OCP\Util;
/**
* Class MemberAdd
*
* @deprecated
* @package OCA\Circles\GlobalScale
*/
class MemberAdd extends AGlobalScaleEvent {
@ -111,14 +111,14 @@ class MemberAdd extends AGlobalScaleEvent {
$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);
}
}
// 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(

View file

@ -44,11 +44,11 @@ use OCA\Circles\Exceptions\UnknownRemoteException;
use OCA\Circles\Model\Member;
use OCA\Circles\Service\ConfigService;
use OCA\Circles\Service\ContactService;
use OCA\Circles\Service\SendMailService;
use OCA\Circles\Service\ShareTokenService;
use OCA\Circles\Service\ShareWrapperService;
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventListener;
use OCP\Security\IHasher;
/**
* Class PreparingMemberSendMail
@ -60,15 +60,15 @@ class PreparingMemberSendMail implements IEventListener {
use TNC22Logger;
/** @var IHasher */
private $hasher;
/** @var ShareWrapperService */
private $shareWrapperService;
/** @var ShareTokenService */
private $shareTokenService;
/** @var SendMailService */
private $sendMailService;
/** @var ConfigService */
private $configService;
@ -79,22 +79,22 @@ class PreparingMemberSendMail implements IEventListener {
/**
* AddingMember constructor.
*
* @param IHasher $hasher
* @param ShareWrapperService $shareWrapperService
* @param ShareTokenService $shareTokenService
* @param SendMailService $sendMailService
* @param ContactService $contactService
* @param ConfigService $configService
*/
public function __construct(
IHasher $hasher,
ShareWrapperService $shareWrapperService,
ShareTokenService $shareTokenService,
SendMailService $sendMailService,
ContactService $contactService,
ConfigService $configService
) {
$this->hasher = $hasher;
$this->shareWrapperService = $shareWrapperService;
$this->shareTokenService = $shareTokenService;
$this->sendMailService = $sendMailService;
$this->contactService = $contactService;
$this->configService = $configService;
@ -113,8 +113,12 @@ class PreparingMemberSendMail implements IEventListener {
* @throws UnknownRemoteException
*/
public function handle(Event $event): void {
if (!$event instanceof PreparingCircleMemberEvent
|| !$this->configService->enforcePasswordOnSharedFile()) {
if (!$event instanceof PreparingCircleMemberEvent) {
return;
}
$circle = $event->getCircle();
if (!$this->configService->enforcePasswordOnSharedFile($circle)) {
return;
}
@ -126,9 +130,8 @@ class PreparingMemberSendMail implements IEventListener {
}
$federatedEvent = $event->getFederatedEvent();
$clearPasswords = $federatedEvent->getInternal()->gArray('clearPasswords');
$hashedPasswords = $federatedEvent->getParams()->gArray('hashedPasswords');
$hashedPasswords = $clearPasswords = [];
foreach ($members as $member) {
if (($member->getUserType() !== Member::TYPE_MAIL
&& $member->getUserType() !== Member::TYPE_CONTACT)
@ -137,9 +140,9 @@ class PreparingMemberSendMail implements IEventListener {
continue;
}
$clearPassword = $this->token(14);
[$clearPassword, $hashedPassword] = $this->sendMailService->getPassword($circle);
$clearPasswords[$member->getSingleId()] = $clearPassword;
$hashedPasswords[$member->getSingleId()] = $this->hasher->hash($clearPassword);
$hashedPasswords[$member->getSingleId()] = $hashedPassword;
}
$federatedEvent->getInternal()->aArray('clearPasswords', $clearPasswords);

View file

@ -44,6 +44,7 @@ use OCA\Circles\Exceptions\UnknownRemoteException;
use OCA\Circles\Model\Member;
use OCA\Circles\Service\ConfigService;
use OCA\Circles\Service\ContactService;
use OCA\Circles\Service\SendMailService;
use OCA\Circles\Service\ShareTokenService;
use OCA\Circles\Service\ShareWrapperService;
use OCP\EventDispatcher\Event;
@ -69,6 +70,9 @@ class PreparingShareSendMail implements IEventListener {
/** @var ShareTokenService */
private $shareTokenService;
/** @var SendMailService */
private $sendMailService;
/** @var ConfigService */
private $configService;
@ -82,6 +86,7 @@ class PreparingShareSendMail implements IEventListener {
* @param IHasher $hasher
* @param ShareWrapperService $shareWrapperService
* @param ShareTokenService $shareTokenService
* @param SendMailService $sendMailService
* @param ContactService $contactService
* @param ConfigService $configService
*/
@ -89,12 +94,14 @@ class PreparingShareSendMail implements IEventListener {
IHasher $hasher,
ShareWrapperService $shareWrapperService,
ShareTokenService $shareTokenService,
SendMailService $sendMailService,
ContactService $contactService,
ConfigService $configService
) {
$this->hasher = $hasher;
$this->shareWrapperService = $shareWrapperService;
$this->shareTokenService = $shareTokenService;
$this->sendMailService = $sendMailService;
$this->contactService = $contactService;
$this->configService = $configService;
@ -113,16 +120,18 @@ class PreparingShareSendMail implements IEventListener {
* @throws UnknownRemoteException
*/
public function handle(Event $event): void {
if (!$event instanceof PreparingFileShareEvent
|| !$this->configService->enforcePasswordOnSharedFile()) {
if (!$event instanceof PreparingFileShareEvent) {
return;
}
$circle = $event->getCircle();
$federatedEvent = $event->getFederatedEvent();
$clearPasswords = $federatedEvent->getInternal()->gArray('clearPasswords');
$hashedPasswords = $federatedEvent->getParams()->gArray('hashedPasswords');
if (!$this->configService->enforcePasswordOnSharedFile($circle)) {
return;
}
$federatedEvent = $event->getFederatedEvent();
$hashedPasswords = $clearPasswords = [];
foreach ($circle->getInheritedMembers(false, true) as $member) {
if (($member->getUserType() !== Member::TYPE_MAIL
&& $member->getUserType() !== Member::TYPE_CONTACT)
@ -132,9 +141,9 @@ class PreparingShareSendMail implements IEventListener {
continue;
}
$clearPassword = $this->token(14);
[$clearPassword, $hashedPassword] = $this->sendMailService->getPassword($circle);
$clearPasswords[$member->getSingleId()] = $clearPassword;
$hashedPasswords[$member->getSingleId()] = $this->hasher->hash($clearPassword);
$hashedPasswords[$member->getSingleId()] = $hashedPassword;
}
$federatedEvent->getInternal()->aArray('clearPasswords', $clearPasswords);

View file

@ -38,8 +38,11 @@ use ArtificialOwl\MySmallPhpTools\Traits\Nextcloud\nc22\TNC22Deserialize;
use ArtificialOwl\MySmallPhpTools\Traits\TArrayTools;
use DateTime;
use JsonSerializable;
use OCA\Circles\Db\CircleRequest;
use OCA\Circles\Exceptions\CircleNotFoundException;
use OCA\Circles\Exceptions\FederatedItemException;
use OCA\Circles\Exceptions\MemberHelperException;
use OCA\Circles\Exceptions\MemberLevelException;
use OCA\Circles\Exceptions\MembershipNotFoundException;
use OCA\Circles\Exceptions\OwnerNotFoundException;
use OCA\Circles\Exceptions\RemoteInstanceException;
@ -48,6 +51,8 @@ use OCA\Circles\Exceptions\RemoteResourceNotFoundException;
use OCA\Circles\Exceptions\RequestBuilderException;
use OCA\Circles\Exceptions\UnknownRemoteException;
use OCA\Circles\IEntity;
use OCA\Circles\Model\Helpers\MemberHelper;
use OCP\Security\IHasher;
/**
* Class Circle
@ -805,6 +810,12 @@ class Circle extends ManagedModel implements IEntity, IDeserializable, INC22Quer
/**
* @return array
* @throws FederatedItemException
* @throws RemoteInstanceException
* @throws RemoteNotFoundException
* @throws RemoteResourceNotFoundException
* @throws RequestBuilderException
* @throws UnknownRemoteException
*/
public function jsonSerialize(): array {
$arr = [
@ -816,7 +827,6 @@ class Circle extends ManagedModel implements IEntity, IDeserializable, INC22Quer
'population' => $this->getPopulation(),
'config' => $this->getConfig(),
'description' => $this->getDescription(),
'settings' => $this->getSettings(),
'url' => $this->getUrl(),
'creation' => $this->getCreation(),
'initiator' => ($this->hasInitiator()) ? $this->getInitiator() : null
@ -838,6 +848,16 @@ class Circle extends ManagedModel implements IEntity, IDeserializable, INC22Quer
$arr['memberships'] = $this->getMemberships();
}
// settings should only be available to admin
if ($this->hasInitiator()) {
$initiatorHelper = new MemberHelper($this->getInitiator());
try {
$initiatorHelper->mustBeAdmin();
$arr['settings'] = $this->getSettings();
} catch (MemberHelperException | MemberLevelException $e) {
}
}
return $arr;
}
@ -872,6 +892,26 @@ class Circle extends ManagedModel implements IEntity, IDeserializable, INC22Quer
$this->getManager()->manageImportFromDatabase($this, $data, $prefix);
// TODO: deprecated in NC27, remove those (17) lines that was needed to finalise migration to 24
// if password is not hashed (pre-22), hash it and update new settings in DB
$curr = $this->get('password_single', $this->getSettings());
if (strlen($curr) >= 1 && strlen($curr) < 64) {
/** @var IHasher $hasher */
$hasher = \OC::$server->get(IHasher::class);
/** @var CircleRequest $circleRequest */
$circleRequest = \OC::$server->get(CircleRequest::class);
$new = $hasher->hash($curr);
$settings = $this->getSettings();
$settings['password_single'] = $new;
$this->setSettings($settings);
$circleRequest->updateSettings($this);
}
// END deprecated NC27
return $this;
}

View file

@ -57,7 +57,7 @@ use OCA\Circles\FederatedItems\CircleDestroy;
use OCA\Circles\FederatedItems\CircleEdit;
use OCA\Circles\FederatedItems\CircleJoin;
use OCA\Circles\FederatedItems\CircleLeave;
use OCA\Circles\FederatedItems\CircleSettings;
use OCA\Circles\FederatedItems\CircleSetting;
use OCA\Circles\IEntity;
use OCA\Circles\IFederatedUser;
use OCA\Circles\Model\Circle;
@ -69,12 +69,8 @@ use OCA\Circles\Model\Probes\CircleProbe;
use OCA\Circles\Model\Probes\MemberProbe;
use OCA\Circles\StatusCode;
use OCP\IL10N;
use OCP\Security\IHasher;
/**
* Class CircleService
*
* @package OCA\Circles\Service
*/
class CircleService {
use TArrayTools;
use TStringTools;
@ -84,6 +80,9 @@ class CircleService {
/** @var IL10N */
private $l10n;
/** @var IHasher */
private $hasher;
/** @var CircleRequest */
private $circleRequest;
@ -107,8 +106,8 @@ class CircleService {
/**
* CircleService constructor.
*
* @param IL10N $l10n
* @param IHasher $hasher
* @param CircleRequest $circleRequest
* @param MemberRequest $memberRequest
* @param RemoteStreamService $remoteStreamService
@ -119,6 +118,7 @@ class CircleService {
*/
public function __construct(
IL10N $l10n,
IHasher $hasher,
CircleRequest $circleRequest,
MemberRequest $memberRequest,
RemoteStreamService $remoteStreamService,
@ -128,6 +128,7 @@ class CircleService {
ConfigService $configService
) {
$this->l10n = $l10n;
$this->hasher = $hasher;
$this->circleRequest = $circleRequest;
$this->memberRequest = $memberRequest;
$this->remoteStreamService = $remoteStreamService;
@ -273,6 +274,51 @@ class CircleService {
}
/**
* if $value is null, setting is unset
*
* @param string $circleId
* @param string $setting
* @param string|null $value
*
* @return array
* @throws CircleNotFoundException
* @throws FederatedEventException
* @throws FederatedItemException
* @throws InitiatorNotConfirmedException
* @throws InitiatorNotFoundException
* @throws OwnerNotFoundException
* @throws RemoteInstanceException
* @throws RemoteNotFoundException
* @throws RemoteResourceNotFoundException
* @throws RequestBuilderException
* @throws UnknownRemoteException
*/
public function updateSetting(string $circleId, string $setting, ?string $value): array {
$circle = $this->getCircle($circleId);
if (strtolower($setting) === 'password_single' && !is_null($value)) {
$value = $this->hasher->hash($value);
}
$event = new FederatedEvent(CircleSetting::class);
$event->setCircle($circle);
$event->setParams(
new SimpleDataStore(
[
'setting' => $setting,
'value' => $value,
'unset' => is_null($value)
]
)
);
$this->federatedEventService->newEvent($event);
return $event->getOutcome();
}
/**
* @param string $circleId
* @param string $name
@ -331,35 +377,6 @@ class CircleService {
return $event->getOutcome();
}
/**
* @param string $circleId
* @param array $settings
*
* @return array
* @throws CircleNotFoundException
* @throws FederatedEventException
* @throws FederatedItemException
* @throws InitiatorNotConfirmedException
* @throws InitiatorNotFoundException
* @throws OwnerNotFoundException
* @throws RemoteInstanceException
* @throws RemoteNotFoundException
* @throws RemoteResourceNotFoundException
* @throws RequestBuilderException
* @throws UnknownRemoteException
*/
public function updateSettings(string $circleId, array $settings): array {
$circle = $this->getCircle($circleId);
$event = new FederatedEvent(CircleSettings::class);
$event->setCircle($circle);
$event->setParams(new SimpleDataStore(['settings' => $settings]));
$this->federatedEventService->newEvent($event);
return $event->getOutcome();
}
/**
* @param string $circleId

View file

@ -40,7 +40,6 @@ use OCA\Circles\AppInfo\Application;
use OCA\Circles\Exceptions\GSStatusException;
use OCA\Circles\IFederatedUser;
use OCA\Circles\Model\Circle;
use OCA\Circles\Model\DeprecatedCircle;
use OCA\Circles\Model\Member;
use OCP\IConfig;
use OCP\IURLGenerator;
@ -331,56 +330,52 @@ class ConfigService {
/**
* @return bool
* @deprecated
* should the password for a mail share be send to the recipient
*/
public function sendPasswordByMail(): bool {
return false;
}
/**
* true if:
* - password is generated randomly
*
* @param Circle $circle
*
* @return bool
*/
public function enforcePasswordOnSharedFile(): bool {
$localPolicy = $this->getAppValueInt(ConfigService::ENFORCE_PASSWORD);
if ($localPolicy !== $this->getInt(ConfigService::ENFORCE_PASSWORD, self::$defaults)) {
return ($localPolicy === 1);
public function sendPasswordByMail(Circle $circle): bool {
if (!$this->enforcePasswordOnSharedFile($circle)) {
return false;
}
// 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');
return (!$this->getBool('password_single_enabled', $circle->getSettings(), false)
|| $this->get('password_single', $circle->getSettings()) === '');
}
/**
* @param DeprecatedCircle $circle
* true if:
* - global setting of Nextcloud enforce password on shares.
* - setting of Circles' app enforce password on shares.
* - setting for specific Circle enforce password on shares.
*
* @param Circle $circle
*
* @return bool
* @deprecated
* do we require a share by mail to be password protected
*
*/
public function enforcePasswordProtection(DeprecatedCircle $circle) {
return false;
public function enforcePasswordOnSharedFile(Circle $circle): bool {
if ($this->config->getAppValue(
'core',
'shareapi_enforce_links_password',
'no'
) === 'yes') {
return true;
}
if ($this->getAppValueInt(ConfigService::ENFORCE_PASSWORD) === 1) {
return true;
}
// Compat NC21
if ($this->getBool('password_enforcement', $circle->getSettings(), false)) {
return true;
}
return $this->getBool('enforce_password', $circle->getSettings(), false);
}

View file

@ -31,6 +31,8 @@ declare(strict_types=1);
namespace OCA\Circles\Service;
use ArtificialOwl\MySmallPhpTools\Traits\TArrayTools;
use ArtificialOwl\MySmallPhpTools\Traits\TStringTools;
use Exception;
use OCA\Circles\Model\Circle;
use OCA\Circles\Model\Member;
@ -39,19 +41,20 @@ use OCP\Defaults;
use OCP\IL10N;
use OCP\Mail\IEMailTemplate;
use OCP\Mail\IMailer;
use OCP\Security\IHasher;
use OCP\Util;
/**
* Class SendMailService
*
* @package OCA\Circles\Service
*/
class SendMailService {
use TArrayTools;
use TStringTools;
/** @var IL10N */
private $l10n;
/** @var IHasher */
private $hasher;
/** @var IMailer */
private $mailer;
@ -72,11 +75,13 @@ class SendMailService {
*/
public function __construct(
IL10N $l10n,
IHasher $hasher,
IMailer $mailer,
Defaults $defaults,
ConfigService $configService
) {
$this->l10n = $l10n;
$this->hasher = $hasher;
$this->mailer = $mailer;
$this->defaults = $defaults;
$this->configService = $configService;
@ -132,7 +137,7 @@ class SendMailService {
} catch (Exception $e) {
}
$this->sendMailPassword($author, $circle->getDisplayName(), $mail, $password);
$this->sendMailPassword($circle, $author, $mail, $password);
}
}
@ -213,18 +218,20 @@ class SendMailService {
/**
* @param Circle $circle
* @param string $author
* @param string $circleName
* @param string $email
* @param string $password
*
* @throws Exception
*/
private function sendMailPassword(
Circle $circle,
string $author,
string $circleName,
string $email,
string $password
): void {
if (!$this->configService->enforcePasswordOnSharedFile() || $password === '') {
if (!$this->configService->sendPasswordByMail($circle) || $password === '') {
return;
}
@ -246,7 +253,7 @@ class SendMailService {
'initiator' => $author,
// 'initiatorEmail' => Util::getDefaultEmailAddress(''),
'initiatorEmail' => '',
'shareWith' => $circleName
'shareWith' => $circle->getDisplayName()
]
);
@ -280,4 +287,23 @@ class SendMailService {
$message->useTemplate($emailTemplate);
$this->mailer->send($message);
}
/**
* @param Circle $circle
*
* @return array
*/
public function getPassword(Circle $circle): array {
$clearPassword = $hashedPassword = '';
if (!$this->configService->sendPasswordByMail($circle)) {
$hashedPassword = $this->get('password_single', $circle->getSettings());
}
if ($hashedPassword === '') {
$clearPassword = $this->token(14);
$hashedPassword = $this->hasher->hash($clearPassword);
}
return [$clearPassword, $hashedPassword];
}
}