diff --git a/apps/files_sharing/lib/Controller/ShareAPIController.php b/apps/files_sharing/lib/Controller/ShareAPIController.php index 77faa2ab0c97a..988b73060be64 100644 --- a/apps/files_sharing/lib/Controller/ShareAPIController.php +++ b/apps/files_sharing/lib/Controller/ShareAPIController.php @@ -586,7 +586,8 @@ public function deleteShare(string $id): DataResponse { * @param string $publicUpload If public uploading is allowed * @param string $password Password for the share * @param string|null $sendPasswordByTalk Send the password for the share over Talk - * @param string $expireDate Expiry date of the share using user timezone at 00:00. It means date in UTC timezone will be used. + * @param ?string $expireDate The expiry date of the share in the user's timezone (UTC) at 00:00. + * If $expireDate is not supplied or set to `null`, the system default will be used. * @param string $note Note for the share * @param string $label Label for the share (only used in link and email) * @param string|null $attributes Additional attributes for the share @@ -812,19 +813,19 @@ public function createShare( $share->setPermissions($permissions); } elseif ($shareType === IShare::TYPE_ROOM) { try { - $this->getRoomShareHelper()->createShare($share, $shareWith, $permissions, $expireDate); + $this->getRoomShareHelper()->createShare($share, $shareWith, $permissions, $expireDate ?? ''); } catch (QueryException $e) { throw new OCSForbiddenException($this->l->t('Sharing %s failed because the back end does not support room shares', [$node->getPath()])); } } elseif ($shareType === IShare::TYPE_DECK) { try { - $this->getDeckShareHelper()->createShare($share, $shareWith, $permissions, $expireDate); + $this->getDeckShareHelper()->createShare($share, $shareWith, $permissions, $expireDate ?? ''); } catch (QueryException $e) { throw new OCSForbiddenException($this->l->t('Sharing %s failed because the back end does not support room shares', [$node->getPath()])); } } elseif ($shareType === IShare::TYPE_SCIENCEMESH) { try { - $this->getSciencemeshShareHelper()->createShare($share, $shareWith, $permissions, $expireDate); + $this->getSciencemeshShareHelper()->createShare($share, $shareWith, $permissions, $expireDate ?? ''); } catch (QueryException $e) { throw new OCSForbiddenException($this->l->t('Sharing %s failed because the back end does not support ScienceMesh shares', [$node->getPath()])); } diff --git a/apps/files_sharing/openapi.json b/apps/files_sharing/openapi.json index 9a869feb3b2ce..80a8f5de1ed5a 100644 --- a/apps/files_sharing/openapi.json +++ b/apps/files_sharing/openapi.json @@ -1755,10 +1755,10 @@ { "name": "expireDate", "in": "query", - "description": "Expiry date of the share using user timezone at 00:00. It means date in UTC timezone will be used.", + "description": "The expiry date of the share in the user's timezone (UTC) at 00:00. If $expireDate is not supplied or set to `null`, the system default will be used.", "schema": { "type": "string", - "default": "" + "nullable": true } }, { diff --git a/build/integration/features/bootstrap/Sharing.php b/build/integration/features/bootstrap/Sharing.php index a38ef392e61f0..9e4cdb4ab7dec 100644 --- a/build/integration/features/bootstrap/Sharing.php +++ b/build/integration/features/bootstrap/Sharing.php @@ -81,7 +81,9 @@ public function asCreatingAShareWith($user, $body) { $fd = $body->getRowsHash(); if (array_key_exists('expireDate', $fd)) { $dateModification = $fd['expireDate']; - $fd['expireDate'] = date('Y-m-d', strtotime($dateModification)); + if (!empty($dateModification)) { + $fd['expireDate'] = date('Y-m-d', strtotime($dateModification)); + } } $options['form_params'] = $fd; } @@ -328,7 +330,9 @@ public function createShare($user, public function isFieldInResponse($field, $contentExpected) { $data = simplexml_load_string($this->response->getBody())->data[0]; if ((string)$field == 'expiration') { - $contentExpected = date('Y-m-d', strtotime($contentExpected)) . " 00:00:00"; + if(!empty($contentExpected)) { + $contentExpected = date('Y-m-d', strtotime($contentExpected)) . " 00:00:00"; + } } if (count($data->element) > 0) { foreach ($data as $element) { diff --git a/build/integration/sharing_features/sharing-v1.feature b/build/integration/sharing_features/sharing-v1.feature index ca030bd3a31fa..2b5d4e8933184 100644 --- a/build/integration/sharing_features/sharing-v1.feature +++ b/build/integration/sharing_features/sharing-v1.feature @@ -229,6 +229,24 @@ Feature: sharing | url | AN_URL | | mimetype | httpd/unix-directory | + Scenario: Creating a new share with expiration date removed, when default expiration is set + Given user "user0" exists + And user "user1" exists + And parameter "shareapi_default_expire_date" of app "core" is set to "yes" + And As an "user0" + When creating a share with + | path | welcome.txt | + | shareWith | user1 | + | shareType | 0 | + | expireDate | | + Then the OCS status code should be "100" + And the HTTP status code should be "200" + And Getting info of last share + Then the OCS status code should be "100" + And the HTTP status code should be "200" + And Share fields of last share match with + | expiration || + Scenario: Creating a new public share, updating its password and getting its info Given user "user0" exists And As an "user0" diff --git a/lib/private/Share20/Manager.php b/lib/private/Share20/Manager.php index f9280d75887a2..8008c4bebc5a5 100644 --- a/lib/private/Share20/Manager.php +++ b/lib/private/Share20/Manager.php @@ -85,43 +85,9 @@ * This class is the communication hub for all sharing related operations. */ class Manager implements IManager { - /** @var IProviderFactory */ - private $factory; - private LoggerInterface $logger; - /** @var IConfig */ - private $config; - /** @var ISecureRandom */ - private $secureRandom; - /** @var IHasher */ - private $hasher; - /** @var IMountManager */ - private $mountManager; - /** @var IGroupManager */ - private $groupManager; - /** @var IL10N */ - private $l; - /** @var IFactory */ - private $l10nFactory; - /** @var IUserManager */ - private $userManager; - /** @var IRootFolder */ - private $rootFolder; - /** @var LegacyHooks */ - private $legacyHooks; - /** @var IMailer */ - private $mailer; - /** @var IURLGenerator */ - private $urlGenerator; - /** @var \OC_Defaults */ - private $defaults; - /** @var IEventDispatcher */ - private $dispatcher; - /** @var IUserSession */ - private $userSession; - /** @var KnownUserService */ - private $knownUserService; - private ShareDisableChecker $shareDisableChecker; - private IDateTimeZone $dateTimeZone; + + private IL10N|null $l; + private LegacyHooks $legacyHooks; public function __construct( LoggerInterface $logger, @@ -157,15 +123,7 @@ public function __construct( $this->rootFolder = $rootFolder; // The constructor of LegacyHooks registers the listeners of share events // do not remove if those are not properly migrated - $this->legacyHooks = new LegacyHooks($dispatcher); - $this->mailer = $mailer; - $this->urlGenerator = $urlGenerator; - $this->defaults = $defaults; - $this->dispatcher = $dispatcher; - $this->userSession = $userSession; - $this->knownUserService = $knownUserService; - $this->shareDisableChecker = $shareDisableChecker; - $this->dateTimeZone = $dateTimeZone; + $this->legacyHooks = new LegacyHooks($this->dispatcher); } /** @@ -391,26 +349,6 @@ protected function validateExpirationDateInternal(IShare $share) { $expirationDate = $share->getExpirationDate(); - if ($expirationDate !== null) { - $expirationDate->setTimezone($this->dateTimeZone->getTimeZone()); - $expirationDate->setTime(0, 0, 0); - - $date = new \DateTime('now', $this->dateTimeZone->getTimeZone()); - $date->setTime(0, 0, 0); - if ($date >= $expirationDate) { - $message = $this->l->t('Expiration date is in the past'); - throw new GenericShareException($message, $message, 404); - } - } - - // If expiredate is empty set a default one if there is a default - $fullId = null; - try { - $fullId = $share->getFullId(); - } catch (\UnexpectedValueException $e) { - // This is a new share - } - if ($isRemote) { $defaultExpireDate = $this->shareApiRemoteDefaultExpireDate(); $defaultExpireDays = $this->shareApiRemoteDefaultExpireDays(); @@ -422,28 +360,53 @@ protected function validateExpirationDateInternal(IShare $share) { $configProp = 'internal_defaultExpDays'; $isEnforced = $this->shareApiInternalDefaultExpireDateEnforced(); } - if ($fullId === null && $expirationDate === null && $defaultExpireDate) { - $expirationDate = new \DateTime('now', $this->dateTimeZone->getTimeZone()); - $expirationDate->setTime(0, 0, 0); - $days = (int)$this->config->getAppValue('core', $configProp, (string)$defaultExpireDays); - if ($days > $defaultExpireDays) { - $days = $defaultExpireDays; + + // If $expirationDate is falsy, noExpirationDate is true and expiration not enforced + // Then skip expiration date validation as null is accepted + if(!($share->getNoExpirationDate() && !$isEnforced)) { + if ($expirationDate != null) { + $expirationDate->setTimezone($this->dateTimeZone->getTimeZone()); + $expirationDate->setTime(0, 0, 0); + + $date = new \DateTime('now', $this->dateTimeZone->getTimeZone()); + $date->setTime(0, 0, 0); + if ($date >= $expirationDate) { + $message = $this->l->t('Expiration date is in the past'); + throw new GenericShareException($message, $message, 404); + } } - $expirationDate->add(new \DateInterval('P' . $days . 'D')); - } - // If we enforce the expiration date check that is does not exceed - if ($isEnforced) { - if ($expirationDate === null) { - throw new \InvalidArgumentException('Expiration date is enforced'); + // If expiredate is empty set a default one if there is a default + $fullId = null; + try { + $fullId = $share->getFullId(); + } catch (\UnexpectedValueException $e) { + // This is a new share } - $date = new \DateTime('now', $this->dateTimeZone->getTimeZone()); - $date->setTime(0, 0, 0); - $date->add(new \DateInterval('P' . $defaultExpireDays . 'D')); - if ($date < $expirationDate) { - $message = $this->l->n('Cannot set expiration date more than %n day in the future', 'Cannot set expiration date more than %n days in the future', $defaultExpireDays); - throw new GenericShareException($message, $message, 404); + if ($fullId === null && $expirationDate === null && $defaultExpireDate) { + $expirationDate = new \DateTime('now', $this->dateTimeZone->getTimeZone()); + $expirationDate->setTime(0, 0, 0); + $days = (int)$this->config->getAppValue('core', $configProp, (string)$defaultExpireDays); + if ($days > $defaultExpireDays) { + $days = $defaultExpireDays; + } + $expirationDate->add(new \DateInterval('P' . $days . 'D')); + } + + // If we enforce the expiration date check that is does not exceed + if ($isEnforced) { + if (empty($expirationDate)) { + throw new \InvalidArgumentException('Expiration date is enforced'); + } + + $date = new \DateTime('now', $this->dateTimeZone->getTimeZone()); + $date->setTime(0, 0, 0); + $date->add(new \DateInterval('P' . $defaultExpireDays . 'D')); + if ($date < $expirationDate) { + $message = $this->l->n('Cannot set expiration date more than %n day in the future', 'Cannot set expiration date more than %n days in the future', $defaultExpireDays); + throw new GenericShareException($message, $message, 404); + } } } @@ -476,51 +439,57 @@ protected function validateExpirationDateInternal(IShare $share) { */ protected function validateExpirationDateLink(IShare $share) { $expirationDate = $share->getExpirationDate(); - - if ($expirationDate !== null) { - $expirationDate->setTimezone($this->dateTimeZone->getTimeZone()); - $expirationDate->setTime(0, 0, 0); - - $date = new \DateTime('now', $this->dateTimeZone->getTimeZone()); - $date->setTime(0, 0, 0); - if ($date >= $expirationDate) { - $message = $this->l->t('Expiration date is in the past'); - throw new GenericShareException($message, $message, 404); + $isEnforced = $this->shareApiLinkDefaultExpireDateEnforced(); + + // If $expirationDate is falsy, noExpirationDate is true and expiration not enforced + // Then skip expiration date validation as null is accepted + if(!($share->getNoExpirationDate() && !$isEnforced)) { + if ($expirationDate !== null) { + $expirationDate->setTimezone($this->dateTimeZone->getTimeZone()); + $expirationDate->setTime(0, 0, 0); + + $date = new \DateTime('now', $this->dateTimeZone->getTimeZone()); + $date->setTime(0, 0, 0); + if ($date >= $expirationDate) { + $message = $this->l->t('Expiration date is in the past'); + throw new GenericShareException($message, $message, 404); + } } - } - - // If expiredate is empty set a default one if there is a default - $fullId = null; - try { - $fullId = $share->getFullId(); - } catch (\UnexpectedValueException $e) { - // This is a new share - } - if ($fullId === null && $expirationDate === null && $this->shareApiLinkDefaultExpireDate()) { - $expirationDate = new \DateTime('now', $this->dateTimeZone->getTimeZone()); - $expirationDate->setTime(0, 0, 0); - - $days = (int)$this->config->getAppValue('core', 'link_defaultExpDays', (string)$this->shareApiLinkDefaultExpireDays()); - if ($days > $this->shareApiLinkDefaultExpireDays()) { - $days = $this->shareApiLinkDefaultExpireDays(); + // If expiredate is empty set a default one if there is a default + $fullId = null; + try { + $fullId = $share->getFullId(); + } catch (\UnexpectedValueException $e) { + // This is a new share + } + + if ($fullId === null && $expirationDate === null && $this->shareApiLinkDefaultExpireDate()) { + $expirationDate = new \DateTime('now', $this->dateTimeZone->getTimeZone()); + $expirationDate->setTime(0, 0, 0); + + $days = (int)$this->config->getAppValue('core', 'link_defaultExpDays', (string)$this->shareApiLinkDefaultExpireDays()); + if ($days > $this->shareApiLinkDefaultExpireDays()) { + $days = $this->shareApiLinkDefaultExpireDays(); + } + $expirationDate->add(new \DateInterval('P' . $days . 'D')); } - $expirationDate->add(new \DateInterval('P' . $days . 'D')); - } - - // If we enforce the expiration date check that is does not exceed - if ($this->shareApiLinkDefaultExpireDateEnforced()) { - if ($expirationDate === null) { - throw new \InvalidArgumentException('Expiration date is enforced'); + + // If we enforce the expiration date check that is does not exceed + if ($isEnforced) { + if (empty($expirationDate)) { + throw new \InvalidArgumentException('Expiration date is enforced'); + } + + $date = new \DateTime('now', $this->dateTimeZone->getTimeZone()); + $date->setTime(0, 0, 0); + $date->add(new \DateInterval('P' . $this->shareApiLinkDefaultExpireDays() . 'D')); + if ($date < $expirationDate) { + $message = $this->l->n('Cannot set expiration date more than %n day in the future', 'Cannot set expiration date more than %n days in the future', $this->shareApiLinkDefaultExpireDays()); + throw new GenericShareException($message, $message, 404); + } } - $date = new \DateTime('now', $this->dateTimeZone->getTimeZone()); - $date->setTime(0, 0, 0); - $date->add(new \DateInterval('P' . $this->shareApiLinkDefaultExpireDays() . 'D')); - if ($date < $expirationDate) { - $message = $this->l->n('Cannot set expiration date more than %n day in the future', 'Cannot set expiration date more than %n days in the future', $this->shareApiLinkDefaultExpireDays()); - throw new GenericShareException($message, $message, 404); - } } $accepted = true; diff --git a/lib/private/Share20/Share.php b/lib/private/Share20/Share.php index c80d332e9dba2..0493ab4782121 100644 --- a/lib/private/Share20/Share.php +++ b/lib/private/Share20/Share.php @@ -99,7 +99,6 @@ class Share implements IShare { /** @var ICacheEntry|null */ private $nodeCacheEntry; - /** @var bool */ private $hideDownload = false; @@ -421,6 +420,21 @@ public function getExpirationDate() { return $this->expireDate; } + /** + * @inheritdoc + */ + public function setNoExpirationDate(bool $noExpirationDate) { + $this->noExpirationDate = $noExpirationDate; + return $this; + } + + /** + * @inheritdoc + */ + public function getNoExpirationDate(): bool { + return $this->noExpirationDate; + } + /** * @inheritdoc */ diff --git a/lib/public/Share/IShare.php b/lib/public/Share/IShare.php index e5a943b0bac89..14c6523784c70 100644 --- a/lib/public/Share/IShare.php +++ b/lib/public/Share/IShare.php @@ -385,20 +385,38 @@ public function getNote(); /** * Set the expiration date * - * @param null|\DateTime $expireDate + * @param \DateTime|null $expireDate * @return \OCP\Share\IShare The modified object * @since 9.0.0 */ - public function setExpirationDate($expireDate); + public function setExpirationDate(\DateTime|null $expireDate); /** * Get the expiration date * - * @return null|\DateTime + * @return \DateTime|null * @since 9.0.0 */ public function getExpirationDate(); + /** + * Set overwrite flag for falsy expiry date vavlues + * + * @param bool $noExpirationDate + * @return \OCP\Share\IShare The modified object + * @since 28.0.6 + */ + public function setNoExpirationDate(bool $noExpirationDate); + + + /** + * Get value of overwrite falsy expiry date flag + * + * @return bool + * @since 28.0.6 + */ + public function getNoExpirationDate(); + /** * Is the share expired ? *