Skip to content

Commit c6a5c07

Browse files
committed
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 #31005 Signed-off-by: Cyrille Bollu <cyrpub@bollu.be>
1 parent 60f946a commit c6a5c07

23 files changed

+446
-87
lines changed

apps/files_sharing/appinfo/info.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
Turning the feature off removes shared files and folders on the server for all share recipients, and also on the sync clients and mobile apps. More information is available in the Nextcloud Documentation.
1010

1111
</description>
12-
<version>1.16.1</version>
12+
<version>1.16.2</version>
1313
<licence>agpl</licence>
1414
<author>Michael Gapczynski</author>
1515
<author>Bjoern Schiessle</author>

apps/files_sharing/composer/composer/autoload_classmap.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@
6868
'OCA\\Files_Sharing\\Migration\\Version11300Date20201120141438' => $baseDir . '/../lib/Migration/Version11300Date20201120141438.php',
6969
'OCA\\Files_Sharing\\Migration\\Version21000Date20201223143245' => $baseDir . '/../lib/Migration/Version21000Date20201223143245.php',
7070
'OCA\\Files_Sharing\\Migration\\Version22000Date20210216084241' => $baseDir . '/../lib/Migration/Version22000Date20210216084241.php',
71+
'OCA\\Files_Sharing\\Migration\\Version24000Date20220208195521' => $baseDir . '/../lib/Migration/Version24000Date20220208195521.php',
7172
'OCA\\Files_Sharing\\Migration\\Version24000Date20220404142216' => $baseDir . '/../lib/Migration/Version24000Date20220404142216.php',
7273
'OCA\\Files_Sharing\\MountProvider' => $baseDir . '/../lib/MountProvider.php',
7374
'OCA\\Files_Sharing\\Notification\\Listener' => $baseDir . '/../lib/Notification/Listener.php',

apps/files_sharing/composer/composer/autoload_static.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ class ComposerStaticInitFiles_Sharing
8383
'OCA\\Files_Sharing\\Migration\\Version11300Date20201120141438' => __DIR__ . '/..' . '/../lib/Migration/Version11300Date20201120141438.php',
8484
'OCA\\Files_Sharing\\Migration\\Version21000Date20201223143245' => __DIR__ . '/..' . '/../lib/Migration/Version21000Date20201223143245.php',
8585
'OCA\\Files_Sharing\\Migration\\Version22000Date20210216084241' => __DIR__ . '/..' . '/../lib/Migration/Version22000Date20210216084241.php',
86+
'OCA\\Files_Sharing\\Migration\\Version24000Date20220208195521' => __DIR__ . '/..' . '/../lib/Migration/Version24000Date20220208195521.php',
8687
'OCA\\Files_Sharing\\Migration\\Version24000Date20220404142216' => __DIR__ . '/..' . '/../lib/Migration/Version24000Date20220404142216.php',
8788
'OCA\\Files_Sharing\\MountProvider' => __DIR__ . '/..' . '/../lib/MountProvider.php',
8889
'OCA\\Files_Sharing\\Notification\\Listener' => __DIR__ . '/..' . '/../lib/Notification/Listener.php',

apps/files_sharing/composer/composer/installed.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
'type' => 'library',
66
'install_path' => __DIR__ . '/../',
77
'aliases' => array(),
8-
'reference' => 'c6429e6cd19c57582364338362e543580821cf99',
8+
'reference' => 'ea4531aaaa6eb9fb3859e05b69ab773bfbfe7437',
99
'name' => '__root__',
1010
'dev' => false,
1111
),
@@ -16,7 +16,7 @@
1616
'type' => 'library',
1717
'install_path' => __DIR__ . '/../',
1818
'aliases' => array(),
19-
'reference' => 'c6429e6cd19c57582364338362e543580821cf99',
19+
'reference' => 'ea4531aaaa6eb9fb3859e05b69ab773bfbfe7437',
2020
'dev_requirement' => false,
2121
),
2222
),

apps/files_sharing/lib/Controller/ShareAPIController.php

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,7 @@ protected function formatShare(IShare $share, Node $recipientNode = null): array
279279
} elseif ($share->getShareType() === IShare::TYPE_EMAIL) {
280280
$result['share_with'] = $share->getSharedWith();
281281
$result['password'] = $share->getPassword();
282+
$result['password_expiration_time'] = $share->getPasswordExpirationTime();
282283
$result['send_password_by_talk'] = $share->getSendPasswordByTalk();
283284
$result['share_with_displayname'] = $this->getDisplayNameFromAddressBook($share->getSharedWith(), 'EMAIL');
284285
$result['token'] = $share->getToken();
@@ -570,6 +571,10 @@ public function createShare(
570571
// Set password
571572
if ($password !== '') {
572573
$share->setPassword($password);
574+
// Shares shared by email have temporary passwords by default
575+
if ($shareType === IShare::TYPE_EMAIL) {
576+
$this->setSharePasswordExpirationTime($share);
577+
}
573578
}
574579

575580
// Only share by mail have a recipient
@@ -1177,6 +1182,9 @@ public function updateShare(
11771182
$share->setPassword(null);
11781183
} elseif ($password !== null) {
11791184
$share->setPassword($password);
1185+
if ($share->getShareType() === IShare::TYPE_EMAIL) {
1186+
$this->setSharePasswordExpirationTime($share);
1187+
}
11801188
}
11811189

11821190
if ($label !== null) {
@@ -1513,6 +1521,35 @@ private function parseDate(string $expireDate): \DateTime {
15131521
return $date;
15141522
}
15151523

1524+
/**
1525+
* Set the share's password expiration time
1526+
*/
1527+
private function setSharePasswordExpirationTime(IShare $share): void {
1528+
if ($this->config->getSystemValue('allow_mail_share_permanent_password')) {
1529+
// Sets password expiration date to NULL
1530+
$share->setPasswordExpirationTime();
1531+
return;
1532+
}
1533+
// Sets password expiration date
1534+
$expirationTime = null;
1535+
try {
1536+
$now = new \DateTime();
1537+
$expirationInterval = $this->config->getSystemValue('share_temporary_password_expiration_interval');
1538+
if ($expirationInterval === '' || is_null($expirationInterval)) {
1539+
$expirationInterval = 'P0DT15M';
1540+
}
1541+
$expirationTime = $now->add(new \DateInterval($expirationInterval));
1542+
} catch (\Exception $e) {
1543+
// Catches invalid format for system value 'share_temporary_password_expiration_interval'
1544+
\OC::$server->getLogger()->logException($e, [
1545+
'message' => 'The \'share_temporary_password_expiration_interval\' system setting does not respect the DateInterval::__construct() format. Setting it to \'P0DT15M\''
1546+
]);
1547+
$expirationTime = $now->add(new \DateInterval('P0DT15M'));
1548+
} finally {
1549+
$share->setPasswordExpirationTime($expirationTime);
1550+
}
1551+
}
1552+
15161553
/**
15171554
* Since we have multiple providers but the OCS Share API v1 does
15181555
* not support this we need to check all backends.

apps/files_sharing/lib/Controller/ShareController.php

Lines changed: 67 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@
7272
use OCP\IURLGenerator;
7373
use OCP\IUser;
7474
use OCP\IUserManager;
75+
use OCP\Security\ISecureRandom;
7576
use OCP\Share;
7677
use OCP\Share\Exceptions\ShareNotFound;
7778
use OCP\Share\IManager as ShareManager;
@@ -84,53 +85,21 @@
8485
* @package OCA\Files_Sharing\Controllers
8586
*/
8687
class ShareController extends AuthPublicShareController {
88+
protected IConfig $config;
89+
protected IUserManager $userManager;
90+
protected ILogger $logger;
91+
protected \OCP\Activity\IManager $activityManager;
92+
protected IPreview $previewManager;
93+
protected IRootFolder $rootFolder;
94+
protected FederatedShareProvider $federatedShareProvider;
95+
protected IAccountManager $accountManager;
96+
protected IEventDispatcher $eventDispatcher;
97+
protected IL10N $l10n;
98+
protected Defaults $defaults;
99+
protected ShareManager $shareManager;
100+
protected ISecureRandom $secureRandom;
101+
protected ?Share\IShare $share = null;
87102

88-
/** @var IConfig */
89-
protected $config;
90-
/** @var IUserManager */
91-
protected $userManager;
92-
/** @var ILogger */
93-
protected $logger;
94-
/** @var \OCP\Activity\IManager */
95-
protected $activityManager;
96-
/** @var IPreview */
97-
protected $previewManager;
98-
/** @var IRootFolder */
99-
protected $rootFolder;
100-
/** @var FederatedShareProvider */
101-
protected $federatedShareProvider;
102-
/** @var IAccountManager */
103-
protected $accountManager;
104-
/** @var IEventDispatcher */
105-
protected $eventDispatcher;
106-
/** @var IL10N */
107-
protected $l10n;
108-
/** @var Defaults */
109-
protected $defaults;
110-
/** @var ShareManager */
111-
protected $shareManager;
112-
113-
/** @var Share\IShare */
114-
protected $share;
115-
116-
/**
117-
* @param string $appName
118-
* @param IRequest $request
119-
* @param IConfig $config
120-
* @param IURLGenerator $urlGenerator
121-
* @param IUserManager $userManager
122-
* @param ILogger $logger
123-
* @param \OCP\Activity\IManager $activityManager
124-
* @param \OCP\Share\IManager $shareManager
125-
* @param ISession $session
126-
* @param IPreview $previewManager
127-
* @param IRootFolder $rootFolder
128-
* @param FederatedShareProvider $federatedShareProvider
129-
* @param IAccountManager $accountManager
130-
* @param IEventDispatcher $eventDispatcher
131-
* @param IL10N $l10n
132-
* @param Defaults $defaults
133-
*/
134103
public function __construct(string $appName,
135104
IRequest $request,
136105
IConfig $config,
@@ -146,6 +115,7 @@ public function __construct(string $appName,
146115
IAccountManager $accountManager,
147116
IEventDispatcher $eventDispatcher,
148117
IL10N $l10n,
118+
ISecureRandom $secureRandom,
149119
Defaults $defaults) {
150120
parent::__construct($appName, $request, $session, $urlGenerator);
151121

@@ -159,6 +129,7 @@ public function __construct(string $appName,
159129
$this->accountManager = $accountManager;
160130
$this->eventDispatcher = $eventDispatcher;
161131
$this->l10n = $l10n;
132+
$this->secureRandom = $secureRandom;
162133
$this->defaults = $defaults;
163134
$this->shareManager = $shareManager;
164135
}
@@ -209,6 +180,56 @@ protected function showAuthFailed(): TemplateResponse {
209180
return $response;
210181
}
211182

183+
/**
184+
* The template to show after user identification
185+
*/
186+
protected function showIdentificationResult(bool $success = false): TemplateResponse {
187+
$templateParameters = ['share' => $this->share, 'identityOk' => $success];
188+
189+
$this->eventDispatcher->dispatchTyped(new BeforeTemplateRenderedEvent($this->share, BeforeTemplateRenderedEvent::SCOPE_PUBLIC_SHARE_AUTH));
190+
191+
$response = new TemplateResponse('core', 'publicshareauth', $templateParameters, 'guest');
192+
if ($this->share->getSendPasswordByTalk()) {
193+
$csp = new ContentSecurityPolicy();
194+
$csp->addAllowedConnectDomain('*');
195+
$csp->addAllowedMediaDomain('blob:');
196+
$response->setContentSecurityPolicy($csp);
197+
}
198+
199+
return $response;
200+
}
201+
202+
/**
203+
* Validate the identity token of a public share
204+
*
205+
* @param ?string $identityToken
206+
* @return bool
207+
*/
208+
protected function validateIdentity(?string $identityToken = null): bool {
209+
210+
if ($this->share->getShareType() !== IShare::TYPE_EMAIL) {
211+
return false;
212+
}
213+
214+
if ($identityToken === null || $this->share->getSharedWith() === null) {
215+
return false;
216+
}
217+
218+
return $identityToken === $this->share->getSharedWith();
219+
}
220+
221+
/**
222+
* Generates a password for the share, respecting any password policy defined
223+
*/
224+
protected function generatePassword(): void {
225+
$event = new \OCP\Security\Events\GenerateSecurePasswordEvent();
226+
$this->eventDispatcher->dispatchTyped($event);
227+
$password = $event->getPassword() ?? $this->secureRandom->generate(20);
228+
229+
$this->share->setPassword($password);
230+
$this->shareManager->updateShare($this->share);
231+
}
232+
212233
protected function verifyPassword(string $password): bool {
213234
return $this->shareManager->checkPassword($this->share, $password);
214235
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* @copyright Copyright (c) 2022 Vincent Petry <vincent@nextloud.com>
7+
*
8+
* @author Vincent Petry <vincent@nextcloud.com>
9+
*
10+
* @license GNU AGPL version 3 or any later version
11+
*
12+
* This program is free software: you can redistribute it and/or modify
13+
* it under the terms of the GNU Affero General Public License as
14+
* published by the Free Software Foundation, either version 3 of the
15+
* License, or (at your option) any later version.
16+
*
17+
* This program is distributed in the hope that it will be useful,
18+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
19+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20+
* GNU Affero General Public License for more details.
21+
*
22+
* You should have received a copy of the GNU Affero General Public License
23+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
24+
*
25+
*/
26+
namespace OCA\Files_Sharing\Migration;
27+
28+
use Closure;
29+
use OCP\DB\Types;
30+
use OCP\DB\ISchemaWrapper;
31+
use OCP\Migration\IOutput;
32+
use OCP\Migration\SimpleMigrationStep;
33+
34+
class Version24000Date20220208195521 extends SimpleMigrationStep {
35+
36+
/**
37+
* @param IOutput $output
38+
* @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
39+
* @param array $options
40+
* @return null|ISchemaWrapper
41+
*/
42+
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper {
43+
$schema = $schemaClosure();
44+
$table = $schema->getTable('share');
45+
$table->addColumn('password_expiration_time', Types::DATETIME, [
46+
'notnull' => false,
47+
]);
48+
return $schema;
49+
}
50+
51+
}

apps/files_sharing/src/components/SharingEntryLink.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -780,8 +780,8 @@ export default {
780780
/**
781781
* Uncheck password protection
782782
* We need this method because @update:checked
783-
* is ran simultaneously as @uncheck, so
784-
* so we cannot ensure data is up-to-date
783+
* is ran simultaneously as @uncheck, so we
784+
* cannot ensure data is up-to-date
785785
*/
786786
onPasswordDisable() {
787787
this.share.password = ''

apps/files_sharing/tests/Controller/ShareAPIControllerTest.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4410,6 +4410,7 @@ public function dataFormatShare() {
44104410
'hide_download' => 0,
44114411
'can_edit' => false,
44124412
'can_delete' => false,
4413+
'password_expiration_time' => null,
44134414
], $share, [], false
44144415
];
44154416

@@ -4459,6 +4460,7 @@ public function dataFormatShare() {
44594460
'hide_download' => 0,
44604461
'can_edit' => false,
44614462
'can_delete' => false,
4463+
'password_expiration_time' => null,
44624464
], $share, [], false
44634465
];
44644466

apps/files_sharing/tests/Controller/ShareControllerTest.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,8 @@ class ShareControllerTest extends \Test\TestCase {
106106
private $eventDispatcher;
107107
/** @var IL10N */
108108
private $l10n;
109+
/** @var ISecureRandom */
110+
private $secureRandom;
109111
/** @var Defaults|MockObject */
110112
private $defaults;
111113

@@ -127,6 +129,7 @@ protected function setUp(): void {
127129
$this->accountManager = $this->createMock(IAccountManager::class);
128130
$this->eventDispatcher = $this->createMock(IEventDispatcher::class);
129131
$this->l10n = $this->createMock(IL10N::class);
132+
$this->secureRandom = $this->createMock(ISecureRandom::class);
130133
$this->defaults = $this->createMock(Defaults::class);
131134

132135
$this->shareController = new \OCA\Files_Sharing\Controller\ShareController(
@@ -145,6 +148,7 @@ protected function setUp(): void {
145148
$this->accountManager,
146149
$this->eventDispatcher,
147150
$this->l10n,
151+
$this->secureRandom,
148152
$this->defaults
149153
);
150154

0 commit comments

Comments
 (0)