Skip to content

Commit

Permalink
Merge pull request #46007 from nextcloud/feat/files-request
Browse files Browse the repository at this point in the history
  • Loading branch information
skjnldsv committed Jul 12, 2024
2 parents 86f5fb0 + 4b3aa01 commit 7773f1b
Show file tree
Hide file tree
Showing 83 changed files with 3,169 additions and 871 deletions.
2 changes: 1 addition & 1 deletion apps/files/js/fileinfomodel.js
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@
for (const i in this.attributes.shareAttributes) {
const attr = this.attributes.shareAttributes[i]
if (attr.scope === 'permissions' && attr.key === 'download') {
return attr.enabled
return attr.value === true
}
}

Expand Down
2 changes: 1 addition & 1 deletion apps/files/src/actions/downloadAction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ const isDownloadable = function(node: Node) {
if (node.attributes['mount-type'] === 'shared') {
const shareAttributes = JSON.parse(node.attributes['share-attributes'] ?? 'null')
const downloadAttribute = shareAttributes?.find?.((attribute: { scope: string; key: string }) => attribute.scope === 'permissions' && attribute.key === 'download')
if (downloadAttribute !== undefined && downloadAttribute.enabled === false) {
if (downloadAttribute !== undefined && downloadAttribute.value === false) {
return false
}
}
Expand Down
4 changes: 2 additions & 2 deletions apps/files/src/actions/moveOrCopyActionUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export const getQueue = () => {
}

type ShareAttribute = {
enabled: boolean
value: boolean|string|number|null|object|Array<unknown>
key: string
scope: string
}
Expand All @@ -48,7 +48,7 @@ export const canMove = (nodes: Node[]) => {
export const canDownload = (nodes: Node[]) => {
return nodes.every(node => {
const shareAttributes = JSON.parse(node.attributes?.['share-attributes'] ?? '[]') as Array<ShareAttribute>
return !shareAttributes.some(attribute => attribute.scope === 'permissions' && attribute.enabled === false && attribute.key === 'download')
return !shareAttributes.some(attribute => attribute.scope === 'permissions' && attribute.value === false && attribute.key === 'download')

})
}
Expand Down
8 changes: 4 additions & 4 deletions apps/files/src/views/FilesList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -396,14 +396,14 @@ export default defineComponent({
return { ...this.$route, query: { dir } }
},
shareAttributes(): number[] | undefined {
shareTypesAttributes(): number[] | undefined {
if (!this.currentFolder?.attributes?.['share-types']) {
return undefined
}
return Object.values(this.currentFolder?.attributes?.['share-types'] || {}).flat() as number[]
},
shareButtonLabel() {
if (!this.shareAttributes) {
if (!this.shareTypesAttributes) {
return t('files', 'Share')
}
Expand All @@ -413,12 +413,12 @@ export default defineComponent({
return t('files', 'Shared')
},
shareButtonType(): Type | null {
if (!this.shareAttributes) {
if (!this.shareTypesAttributes) {
return null
}
// If all types are links, show the link icon
if (this.shareAttributes.some(type => type === Type.SHARE_TYPE_LINK)) {
if (this.shareTypesAttributes.some(type => type === Type.SHARE_TYPE_LINK)) {
return Type.SHARE_TYPE_LINK
}
Expand Down
5 changes: 5 additions & 0 deletions apps/files_sharing/appinfo/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,11 @@
'url' => '/api/v1/shares/{id}',
'verb' => 'DELETE',
],
[
'name' => 'ShareAPI#sendShareEmail',
'url' => '/api/v1/shares/{id}/send-email',
'verb' => 'POST',
],
[
'name' => 'ShareAPI#acceptShare',
'url' => '/api/v1/shares/pending/{id}',
Expand Down
121 changes: 107 additions & 14 deletions apps/files_sharing/lib/Controller/ShareAPIController.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
use OCA\Files_Sharing\SharedStorage;
use OCP\App\IAppManager;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\AppFramework\Http\Attribute\UserRateLimit;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\OCS\OCSBadRequestException;
use OCP\AppFramework\OCS\OCSException;
Expand All @@ -42,11 +44,14 @@
use OCP\IUserManager;
use OCP\Lock\ILockingProvider;
use OCP\Lock\LockedException;
use OCP\Mail\IMailer;
use OCP\Server;
use OCP\Share\Exceptions\GenericShareException;
use OCP\Share\Exceptions\ShareNotFound;
use OCP\Share\IManager;
use OCP\Share\IProviderFactory;
use OCP\Share\IShare;
use OCP\Share\IShareProviderWithNotification;
use OCP\UserStatus\IManager as IUserStatusManager;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\ContainerInterface;
Expand Down Expand Up @@ -81,6 +86,8 @@ public function __construct(
private IPreview $previewManager,
private IDateTimeZone $dateTimeZone,
private LoggerInterface $logger,
private IProviderFactory $factory,
private IMailer $mailer,
?string $userId = null
) {
parent::__construct($appName, $request);
Expand Down Expand Up @@ -517,6 +524,7 @@ public function deleteShare(string $id): DataResponse {
* @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
* @param 'false'|'true'|null $sendMail Send a mail to the recipient
*
* @return DataResponse<Http::STATUS_OK, Files_SharingShare, array{}>
* @throws OCSBadRequestException Unknown share type
Expand All @@ -538,7 +546,8 @@ public function createShare(
?string $expireDate = null,
string $note = '',
string $label = '',
?string $attributes = null
?string $attributes = null,
?string $sendMail = null
): DataResponse {
$share = $this->shareManager->newShare();

Expand Down Expand Up @@ -609,7 +618,7 @@ public function createShare(
$share = $this->setShareAttributes($share, $attributes);
}

//Expire date
// Expire date
if ($expireDate !== null) {
if ($expireDate !== '') {
try {
Expand All @@ -628,6 +637,11 @@ public function createShare(
$share->setSharedBy($this->currentUser);
$this->checkInheritedAttributes($share);

// Handle mail send
if ($sendMail === 'true' || $sendMail === 'false') {
$share->setMailSend($sendMail === 'true');
}

if ($shareType === IShare::TYPE_USER) {
// Valid user is required to share
if ($shareWith === null || !$this->userManager->userExists($shareWith)) {
Expand Down Expand Up @@ -685,6 +699,10 @@ public function createShare(

// Only share by mail have a recipient
if (is_string($shareWith) && $shareType === IShare::TYPE_EMAIL) {
// If sending a mail have been requested, validate the mail address
if ($share->getMailSend() && !$this->mailer->validateMailAddress($shareWith)) {
throw new OCSNotFoundException($this->l->t('Please specify a valid email address'));
}
$share->setSharedWith($shareWith);
}

Expand Down Expand Up @@ -1025,7 +1043,6 @@ private function getFormattedShares(
* 200: Shares returned
*/
public function getInheritedShares(string $path): DataResponse {

// get Node from (string) path.
$userFolder = $this->rootFolder->getUserFolder($this->currentUser);
try {
Expand All @@ -1038,7 +1055,7 @@ public function getInheritedShares(string $path): DataResponse {
}

if (!($node->getPermissions() & Constants::PERMISSION_SHARE)) {
throw new SharingRightsException('no sharing rights on this item');
throw new SharingRightsException($this->l->t('no sharing rights on this item'));
}

// The current top parent we have access to
Expand Down Expand Up @@ -1117,6 +1134,10 @@ private function hasPermission(int $permissionsSet, int $permissionsToCheck): bo
* @param string|null $label New label
* @param string|null $hideDownload New condition if the download should be hidden
* @param string|null $attributes New additional attributes
* @param string|null $sendMail if the share should be send by mail.
* Considering the share already exists, no mail will be send after the share is updated.
* You will have to use the sendMail action to send the mail.
* @param string|null $shareWith New recipient for email shares
* @return DataResponse<Http::STATUS_OK, Files_SharingShare, array{}>
* @throws OCSBadRequestException Share could not be updated because the requested changes are invalid
* @throws OCSForbiddenException Missing permissions to update the share
Expand All @@ -1134,7 +1155,8 @@ public function updateShare(
?string $note = null,
?string $label = null,
?string $hideDownload = null,
?string $attributes = null
?string $attributes = null,
?string $sendMail = null,
): DataResponse {
try {
$share = $this->getShareById($id);
Expand All @@ -1149,7 +1171,7 @@ public function updateShare(
}

if (!$this->canEditShare($share)) {
throw new OCSForbiddenException('You are not allowed to edit incoming shares');
throw new OCSForbiddenException($this->l->t('You are not allowed to edit incoming shares'));
}

if (
Expand All @@ -1161,7 +1183,8 @@ public function updateShare(
$note === null &&
$label === null &&
$hideDownload === null &&
$attributes === null
$attributes === null &&
$sendMail === null
) {
throw new OCSBadRequestException($this->l->t('Wrong or no update parameter given'));
}
Expand All @@ -1175,6 +1198,11 @@ public function updateShare(
}
$this->checkInheritedAttributes($share);

// Handle mail send
if ($sendMail === 'true' || $sendMail === 'false') {
$share->setMailSend($sendMail === 'true');
}

/**
* expirationdate, password and publicUpload only make sense for link shares
*/
Expand All @@ -1190,7 +1218,7 @@ public function updateShare(
*/

if ($share->getSharedBy() !== $this->currentUser) {
throw new OCSForbiddenException('You are not allowed to edit link shares that you don\'t own');
throw new OCSForbiddenException($this->l->t('You are not allowed to edit link shares that you don\'t own'));
}

// Update hide download state
Expand Down Expand Up @@ -1612,7 +1640,7 @@ private function parseDate(string $expireDate): \DateTime {
// Make sure it expires at midnight in owner timezone
$date->setTime(0, 0, 0);
} catch (\Exception $e) {
throw new \Exception('Invalid date. Format must be YYYY-MM-DD');
throw new \Exception($this->l->t('Invalid date. Format must be YYYY-MM-DD'));
}

return $date;
Expand Down Expand Up @@ -1817,7 +1845,7 @@ private function getSharesFromNode(string $viewer, $node, bool $reShares): array
*/
private function confirmSharingRights(Node $node): void {
if (!$this->hasResharingRights($this->currentUser, $node)) {
throw new SharingRightsException('no sharing rights on this item');
throw new SharingRightsException($this->l->t('No sharing rights on this item'));
}
}

Expand Down Expand Up @@ -1983,11 +2011,11 @@ private function setShareAttributes(IShare $share, ?string $attributesString) {
$newShareAttributes->setAttribute(
$formattedAttr['scope'],
$formattedAttr['key'],
is_string($formattedAttr['enabled']) ? (bool) \json_decode($formattedAttr['enabled']) : $formattedAttr['enabled']
$formattedAttr['value'],
);
}
} else {
throw new OCSBadRequestException('Invalid share attributes provided: \"' . $attributesString . '\"');
throw new OCSBadRequestException($this->l->t('Invalid share attributes provided: "%s"', [$attributesString]));
}
}
$share->setAttributes($newShareAttributes);
Expand All @@ -2009,10 +2037,10 @@ private function checkInheritedAttributes(IShare $share): void {
if ($storage instanceof Wrapper) {
$storage = $storage->getInstanceOfStorage(SharedStorage::class);
if ($storage === null) {
throw new \RuntimeException('Should not happen, instanceOfStorage but getInstanceOfStorage return null');
throw new \RuntimeException($this->l->t('Should not happen, instanceOfStorage but getInstanceOfStorage return null'));
}
} else {
throw new \RuntimeException('Should not happen, instanceOfStorage but not a wrapper');
throw new \RuntimeException($this->l->t('Should not happen, instanceOfStorage but not a wrapper'));
}
/** @var \OCA\Files_Sharing\SharedStorage $storage */
$inheritedAttributes = $storage->getShare()->getAttributes();
Expand All @@ -2025,6 +2053,71 @@ private function checkInheritedAttributes(IShare $share): void {
}
}
}
}

/**
* Send a mail notification again for a share.
* The mail_send option must be enabled for the given share.
* @param string $id the share ID
* @param string $password the password to check against. Necessary for password protected shares.
* @throws OCSNotFoundException Share not found
* @throws OCSForbiddenException You are not allowed to send mail notifications
* @throws OCSBadRequestException Invalid request or wrong password
* @throws OCSException Error while sending mail notification
* @return DataResponse<Http::STATUS_OK, array<empty>, array{}>
* 200: The email notification was sent successfully
*/
#[NoAdminRequired]
#[UserRateLimit(limit: 5, period: 120)]
public function sendShareEmail(string $id, $password = ''): DataResponse {
try {
$share = $this->getShareById($id);

if (!$this->canAccessShare($share, false)) {
throw new OCSNotFoundException($this->l->t('Wrong share ID, share does not exist'));
}

if (!$this->canEditShare($share)) {
throw new OCSForbiddenException($this->l->t('You are not allowed to send mail notifications'));
}

// For mail and link shares, the user must be
// the owner of the share, not only the file owner.
if ($share->getShareType() === IShare::TYPE_EMAIL
|| $share->getShareType() === IShare::TYPE_LINK) {
if ($share->getSharedBy() !== $this->currentUser) {
throw new OCSForbiddenException($this->l->t('You are not allowed to send mail notifications'));
}
}

try {
$provider = $this->factory->getProviderForType($share->getShareType());
if (!($provider instanceof IShareProviderWithNotification)) {
throw new OCSBadRequestException($this->l->t('No mail notification configured for this share type'));
}

// Circumvent the password encrypted data by
// setting the password clear. We're not storing
// the password clear, it is just a temporary
// object manipulation. The password will stay
// encrypted in the database.
if ($share->getPassword() !== null && $share->getPassword() !== $password) {
if (!$this->shareManager->checkPassword($share, $password)) {
throw new OCSBadRequestException($this->l->t('Wrong password'));
}
$share = $share->setPassword($password);
}

$provider->sendMailNotification($share);
return new DataResponse();
} catch(OCSBadRequestException $e) {
throw $e;
} catch (Exception $e) {
throw new OCSException($this->l->t('Error while sending mail notification'));
}

} catch (ShareNotFound $e) {
throw new OCSNotFoundException($this->l->t('Wrong share ID, share does not exist'));
}
}
}
2 changes: 1 addition & 1 deletion apps/files_sharing/lib/MountProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ private function buildSuperShares(array $allShares, \OCP\IUser $user) {
continue;
}
// update supershare attributes with subshare attribute
$superAttributes->setAttribute($attribute['scope'], $attribute['key'], $attribute['enabled']);
$superAttributes->setAttribute($attribute['scope'], $attribute['key'], $attribute['value']);
}
}

Expand Down
Loading

0 comments on commit 7773f1b

Please sign in to comment.