From c6a5c07041d2e5d20771409aede8b755d28372ac Mon Sep 17 00:00:00 2001 From: Cyrille Bollu Date: Sat, 5 Feb 2022 20:49:17 +0100 Subject: Adds a "Request password" button to the public share authentication page for shares of type TYPE_EMAIL, when the "video verification" checkbox isn't checked. Users accessing non-anonymous public shares (TYPE_EMAIL shares) can now request a temporary password themselves. - Creates a migration step for the files_sharing app to add the 'password_expiration_time' attribute to the oc_shares table. - Makes share temporary passwords' expiration time configurable via a system value. - Adds a system config value to allow permanent share passwords -Fixes a typo in a comment in apps/files_sharing/src/components/SharingEntryLink.vue See https://github.com/nextcloud/server/issues/31005 Signed-off-by: Cyrille Bollu --- .../lib/Controller/ShareAPIController.php | 37 +++++++ .../lib/Controller/ShareController.php | 113 ++++++++++++--------- .../Migration/Version24000Date20220208195521.php | 51 ++++++++++ 3 files changed, 155 insertions(+), 46 deletions(-) create mode 100644 apps/files_sharing/lib/Migration/Version24000Date20220208195521.php (limited to 'apps/files_sharing/lib') diff --git a/apps/files_sharing/lib/Controller/ShareAPIController.php b/apps/files_sharing/lib/Controller/ShareAPIController.php index 069cba42bb6..d324af3e9f2 100644 --- a/apps/files_sharing/lib/Controller/ShareAPIController.php +++ b/apps/files_sharing/lib/Controller/ShareAPIController.php @@ -279,6 +279,7 @@ class ShareAPIController extends OCSController { } elseif ($share->getShareType() === IShare::TYPE_EMAIL) { $result['share_with'] = $share->getSharedWith(); $result['password'] = $share->getPassword(); + $result['password_expiration_time'] = $share->getPasswordExpirationTime(); $result['send_password_by_talk'] = $share->getSendPasswordByTalk(); $result['share_with_displayname'] = $this->getDisplayNameFromAddressBook($share->getSharedWith(), 'EMAIL'); $result['token'] = $share->getToken(); @@ -570,6 +571,10 @@ class ShareAPIController extends OCSController { // Set password if ($password !== '') { $share->setPassword($password); + // Shares shared by email have temporary passwords by default + if ($shareType === IShare::TYPE_EMAIL) { + $this->setSharePasswordExpirationTime($share); + } } // Only share by mail have a recipient @@ -1177,6 +1182,9 @@ class ShareAPIController extends OCSController { $share->setPassword(null); } elseif ($password !== null) { $share->setPassword($password); + if ($share->getShareType() === IShare::TYPE_EMAIL) { + $this->setSharePasswordExpirationTime($share); + } } if ($label !== null) { @@ -1513,6 +1521,35 @@ class ShareAPIController extends OCSController { return $date; } + /** + * Set the share's password expiration time + */ + private function setSharePasswordExpirationTime(IShare $share): void { + if ($this->config->getSystemValue('allow_mail_share_permanent_password')) { + // Sets password expiration date to NULL + $share->setPasswordExpirationTime(); + return; + } + // Sets password expiration date + $expirationTime = null; + try { + $now = new \DateTime(); + $expirationInterval = $this->config->getSystemValue('share_temporary_password_expiration_interval'); + if ($expirationInterval === '' || is_null($expirationInterval)) { + $expirationInterval = 'P0DT15M'; + } + $expirationTime = $now->add(new \DateInterval($expirationInterval)); + } catch (\Exception $e) { + // Catches invalid format for system value 'share_temporary_password_expiration_interval' + \OC::$server->getLogger()->logException($e, [ + 'message' => 'The \'share_temporary_password_expiration_interval\' system setting does not respect the DateInterval::__construct() format. Setting it to \'P0DT15M\'' + ]); + $expirationTime = $now->add(new \DateInterval('P0DT15M')); + } finally { + $share->setPasswordExpirationTime($expirationTime); + } + } + /** * Since we have multiple providers but the OCS Share API v1 does * not support this we need to check all backends. diff --git a/apps/files_sharing/lib/Controller/ShareController.php b/apps/files_sharing/lib/Controller/ShareController.php index 411873c9c86..a12878e6de2 100644 --- a/apps/files_sharing/lib/Controller/ShareController.php +++ b/apps/files_sharing/lib/Controller/ShareController.php @@ -72,6 +72,7 @@ use OCP\ISession; use OCP\IURLGenerator; use OCP\IUser; use OCP\IUserManager; +use OCP\Security\ISecureRandom; use OCP\Share; use OCP\Share\Exceptions\ShareNotFound; use OCP\Share\IManager as ShareManager; @@ -84,53 +85,21 @@ use OCP\Template; * @package OCA\Files_Sharing\Controllers */ class ShareController extends AuthPublicShareController { + protected IConfig $config; + protected IUserManager $userManager; + protected ILogger $logger; + protected \OCP\Activity\IManager $activityManager; + protected IPreview $previewManager; + protected IRootFolder $rootFolder; + protected FederatedShareProvider $federatedShareProvider; + protected IAccountManager $accountManager; + protected IEventDispatcher $eventDispatcher; + protected IL10N $l10n; + protected Defaults $defaults; + protected ShareManager $shareManager; + protected ISecureRandom $secureRandom; + protected ?Share\IShare $share = null; - /** @var IConfig */ - protected $config; - /** @var IUserManager */ - protected $userManager; - /** @var ILogger */ - protected $logger; - /** @var \OCP\Activity\IManager */ - protected $activityManager; - /** @var IPreview */ - protected $previewManager; - /** @var IRootFolder */ - protected $rootFolder; - /** @var FederatedShareProvider */ - protected $federatedShareProvider; - /** @var IAccountManager */ - protected $accountManager; - /** @var IEventDispatcher */ - protected $eventDispatcher; - /** @var IL10N */ - protected $l10n; - /** @var Defaults */ - protected $defaults; - /** @var ShareManager */ - protected $shareManager; - - /** @var Share\IShare */ - protected $share; - - /** - * @param string $appName - * @param IRequest $request - * @param IConfig $config - * @param IURLGenerator $urlGenerator - * @param IUserManager $userManager - * @param ILogger $logger - * @param \OCP\Activity\IManager $activityManager - * @param \OCP\Share\IManager $shareManager - * @param ISession $session - * @param IPreview $previewManager - * @param IRootFolder $rootFolder - * @param FederatedShareProvider $federatedShareProvider - * @param IAccountManager $accountManager - * @param IEventDispatcher $eventDispatcher - * @param IL10N $l10n - * @param Defaults $defaults - */ public function __construct(string $appName, IRequest $request, IConfig $config, @@ -146,6 +115,7 @@ class ShareController extends AuthPublicShareController { IAccountManager $accountManager, IEventDispatcher $eventDispatcher, IL10N $l10n, + ISecureRandom $secureRandom, Defaults $defaults) { parent::__construct($appName, $request, $session, $urlGenerator); @@ -159,6 +129,7 @@ class ShareController extends AuthPublicShareController { $this->accountManager = $accountManager; $this->eventDispatcher = $eventDispatcher; $this->l10n = $l10n; + $this->secureRandom = $secureRandom; $this->defaults = $defaults; $this->shareManager = $shareManager; } @@ -209,6 +180,56 @@ class ShareController extends AuthPublicShareController { return $response; } + /** + * The template to show after user identification + */ + protected function showIdentificationResult(bool $success = false): TemplateResponse { + $templateParameters = ['share' => $this->share, 'identityOk' => $success]; + + $this->eventDispatcher->dispatchTyped(new BeforeTemplateRenderedEvent($this->share, BeforeTemplateRenderedEvent::SCOPE_PUBLIC_SHARE_AUTH)); + + $response = new TemplateResponse('core', 'publicshareauth', $templateParameters, 'guest'); + if ($this->share->getSendPasswordByTalk()) { + $csp = new ContentSecurityPolicy(); + $csp->addAllowedConnectDomain('*'); + $csp->addAllowedMediaDomain('blob:'); + $response->setContentSecurityPolicy($csp); + } + + return $response; + } + + /** + * Validate the identity token of a public share + * + * @param ?string $identityToken + * @return bool + */ + protected function validateIdentity(?string $identityToken = null): bool { + + if ($this->share->getShareType() !== IShare::TYPE_EMAIL) { + return false; + } + + if ($identityToken === null || $this->share->getSharedWith() === null) { + return false; + } + + return $identityToken === $this->share->getSharedWith(); + } + + /** + * Generates a password for the share, respecting any password policy defined + */ + protected function generatePassword(): void { + $event = new \OCP\Security\Events\GenerateSecurePasswordEvent(); + $this->eventDispatcher->dispatchTyped($event); + $password = $event->getPassword() ?? $this->secureRandom->generate(20); + + $this->share->setPassword($password); + $this->shareManager->updateShare($this->share); + } + protected function verifyPassword(string $password): bool { return $this->shareManager->checkPassword($this->share, $password); } diff --git a/apps/files_sharing/lib/Migration/Version24000Date20220208195521.php b/apps/files_sharing/lib/Migration/Version24000Date20220208195521.php new file mode 100644 index 00000000000..d5f938dde6d --- /dev/null +++ b/apps/files_sharing/lib/Migration/Version24000Date20220208195521.php @@ -0,0 +1,51 @@ + + * + * @author Vincent Petry + * + * @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 . + * + */ +namespace OCA\Files_Sharing\Migration; + +use Closure; +use OCP\DB\Types; +use OCP\DB\ISchemaWrapper; +use OCP\Migration\IOutput; +use OCP\Migration\SimpleMigrationStep; + +class Version24000Date20220208195521 extends SimpleMigrationStep { + + /** + * @param IOutput $output + * @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper` + * @param array $options + * @return null|ISchemaWrapper + */ + public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper { + $schema = $schemaClosure(); + $table = $schema->getTable('share'); + $table->addColumn('password_expiration_time', Types::DATETIME, [ + 'notnull' => false, + ]); + return $schema; + } + +} -- cgit v1.2.3