Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
fix(shareManager): Respect empty expireDate in server
If `expireDate` is an empty string and not `null` then the server should not set a default.

Signed-off-by: fenn-cs <[email protected]>
  • Loading branch information
nfebe committed May 29, 2024
commit 5dbb96fc8a64fb6469aa085b27f80338e665538e
29 changes: 18 additions & 11 deletions apps/files_sharing/lib/Controller/ShareAPIController.php
Original file line number Diff line number Diff line change
Expand Up @@ -549,7 +549,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
Expand All @@ -571,7 +572,7 @@ public function createShare(
string $publicUpload = 'false',
string $password = '',
?string $sendPasswordByTalk = null,
string $expireDate = '',
?string $expireDate = null,
string $note = '',
string $label = '',
?string $attributes = null
Expand Down Expand Up @@ -646,12 +647,18 @@ public function createShare(
}

//Expire date
if ($expireDate !== '') {
try {
$expireDateTime = $this->parseDate($expireDate);
$share->setExpirationDate($expireDateTime);
} catch (\Exception $e) {
throw new OCSNotFoundException($this->l->t('Invalid date, date format must be YYYY-MM-DD'));
if ($expireDate !== null) {
if ($expireDate !== '') {
try {
$expireDateTime = $this->parseDate($expireDate);
$share->setExpirationDate($expireDateTime);
} catch (\Exception $e) {
throw new OCSNotFoundException($this->l->t('Invalid date, date format must be YYYY-MM-DD'));
}
} else {
// Client sent empty string for expire date.
// Set noExpirationDate to true so overwrite is prevented.
$share->setNoExpirationDate(true);
}
}

Expand Down Expand Up @@ -768,19 +775,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()]));
}
Expand Down
4 changes: 2 additions & 2 deletions apps/files_sharing/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -1781,10 +1781,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
}
},
{
Expand Down
167 changes: 89 additions & 78 deletions lib/private/Share20/Manager.php
Original file line number Diff line number Diff line change
Expand Up @@ -348,26 +348,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();
Expand All @@ -379,28 +359,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);
}
}
}

Expand Down Expand Up @@ -433,51 +438,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;
Expand Down
18 changes: 17 additions & 1 deletion lib/private/Share20/Share.php
Original file line number Diff line number Diff line change
Expand Up @@ -99,10 +99,11 @@ class Share implements IShare {

/** @var ICacheEntry|null */
private $nodeCacheEntry;

/** @var bool */
private $hideDownload = false;

private bool $noExpirationDate = false;

public function __construct(IRootFolder $rootFolder, IUserManager $userManager) {
$this->rootFolder = $rootFolder;
$this->userManager = $userManager;
Expand Down Expand Up @@ -425,6 +426,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
*/
Expand Down
24 changes: 21 additions & 3 deletions lib/public/Share/IShare.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 29.0.1
*/
public function setNoExpirationDate(bool $noExpirationDate);


/**
* Get value of overwrite falsy expiry date flag
*
* @return bool
* @since 29.0.1
*/
public function getNoExpirationDate();

/**
* Is the share expired ?
*
Expand Down