migrate therinaldos.com data
Build & Deploy to DigitalOcean Space / build (push) Failing after 2m38s

This commit is contained in:
2024-05-05 15:50:45 -04:00
commit ef1ff240d4
23182 changed files with 3801898 additions and 0 deletions
@@ -0,0 +1,100 @@
<?php
/**
* @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com>
*
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Joas Schilling <coding@schilljs.com>
*
* @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 <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Files_Sharing\Activity;
use OCP\Activity\IFilter;
use OCP\IL10N;
use OCP\IURLGenerator;
class Filter implements IFilter {
public const TYPE_REMOTE_SHARE = 'remote_share';
public const TYPE_SHARED = 'shared';
/** @var IL10N */
protected $l;
/** @var IURLGenerator */
protected $url;
public function __construct(IL10N $l, IURLGenerator $url) {
$this->l = $l;
$this->url = $url;
}
/**
* @return string Lowercase a-z only identifier
* @since 11.0.0
*/
public function getIdentifier() {
return 'files_sharing';
}
/**
* @return string A translated string
* @since 11.0.0
*/
public function getName() {
return $this->l->t('File shares');
}
/**
* @return int
* @since 11.0.0
*/
public function getPriority() {
return 31;
}
/**
* @return string Full URL to an icon, empty string when none is given
* @since 11.0.0
*/
public function getIcon() {
return $this->url->getAbsoluteURL($this->url->imagePath('core', 'actions/share.svg'));
}
/**
* @param string[] $types
* @return string[] An array of allowed apps from which activities should be displayed
* @since 11.0.0
*/
public function filterTypes(array $types) {
return array_intersect([
self::TYPE_SHARED,
self::TYPE_REMOTE_SHARE,
'file_downloaded',
], $types);
}
/**
* @return string[] An array of allowed apps from which activities should be displayed
* @since 11.0.0
*/
public function allowedApps() {
return [
'files_sharing',
'files_downloadactivity',
];
}
}
@@ -0,0 +1,228 @@
<?php
/**
* @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com>
*
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Joas Schilling <coding@schilljs.com>
*
* @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 <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Files_Sharing\Activity\Providers;
use OCP\Activity\IEvent;
use OCP\Activity\IEventMerger;
use OCP\Activity\IManager;
use OCP\Activity\IProvider;
use OCP\Contacts\IManager as IContactsManager;
use OCP\Federation\ICloudIdManager;
use OCP\IL10N;
use OCP\IURLGenerator;
use OCP\IUserManager;
use OCP\L10N\IFactory;
abstract class Base implements IProvider {
/** @var IFactory */
protected $languageFactory;
/** @var IL10N */
protected $l;
/** @var IURLGenerator */
protected $url;
/** @var IManager */
protected $activityManager;
/** @var IUserManager */
protected $userManager;
/** @var IEventMerger */
protected $eventMerger;
/** @var IContactsManager */
protected $contactsManager;
/** @var ICloudIdManager */
protected $cloudIdManager;
/** @var array */
protected $displayNames = [];
public function __construct(IFactory $languageFactory,
IURLGenerator $url,
IManager $activityManager,
IUserManager $userManager,
ICloudIdManager $cloudIdManager,
IContactsManager $contactsManager,
IEventMerger $eventMerger) {
$this->languageFactory = $languageFactory;
$this->url = $url;
$this->activityManager = $activityManager;
$this->userManager = $userManager;
$this->cloudIdManager = $cloudIdManager;
$this->contactsManager = $contactsManager;
$this->eventMerger = $eventMerger;
}
/**
* @param string $language
* @param IEvent $event
* @param IEvent|null $previousEvent
* @return IEvent
* @throws \InvalidArgumentException
* @since 11.0.0
*/
public function parse($language, IEvent $event, IEvent $previousEvent = null) {
if ($event->getApp() !== 'files_sharing') {
throw new \InvalidArgumentException();
}
$this->l = $this->languageFactory->get('files_sharing', $language);
if ($this->activityManager->isFormattingFilteredObject()) {
try {
return $this->parseShortVersion($event);
} catch (\InvalidArgumentException $e) {
// Ignore and simply use the long version...
}
}
return $this->parseLongVersion($event, $previousEvent);
}
/**
* @param IEvent $event
* @return IEvent
* @throws \InvalidArgumentException
* @since 11.0.0
*/
abstract protected function parseShortVersion(IEvent $event);
/**
* @param IEvent $event
* @param IEvent|null $previousEvent
* @return IEvent
* @throws \InvalidArgumentException
* @since 11.0.0
*/
abstract protected function parseLongVersion(IEvent $event, IEvent $previousEvent = null);
/**
* @throws \InvalidArgumentException
*/
protected function setSubjects(IEvent $event, string $subject, array $parameters): void {
$event->setRichSubject($subject, $parameters);
}
/**
* @param array|string $parameter
* @param IEvent|null $event
* @return array
* @throws \InvalidArgumentException
*/
protected function getFile($parameter, IEvent $event = null) {
if (is_array($parameter)) {
$path = reset($parameter);
$id = (string) key($parameter);
} elseif ($event !== null) {
// Legacy from before ownCloud 8.2
$path = $parameter;
$id = $event->getObjectId();
} else {
throw new \InvalidArgumentException('Could not generate file parameter');
}
return [
'type' => 'file',
'id' => $id,
'name' => basename($path),
'path' => trim($path, '/'),
'link' => $this->url->linkToRouteAbsolute('files.viewcontroller.showFile', ['fileid' => $id]),
];
}
/**
* @param string $uid
* @param string $overwriteDisplayName - overwrite display name, only if user is not local
*
* @return array
*/
protected function getUser(string $uid, string $overwriteDisplayName = '') {
// First try local user
$displayName = $this->userManager->getDisplayName($uid);
if ($displayName !== null) {
return [
'type' => 'user',
'id' => $uid,
'name' => $displayName,
];
}
// Then a contact from the addressbook
if ($this->cloudIdManager->isValidCloudId($uid)) {
$cloudId = $this->cloudIdManager->resolveCloudId($uid);
return [
'type' => 'user',
'id' => $cloudId->getUser(),
'name' => (($overwriteDisplayName !== '') ? $overwriteDisplayName : $this->getDisplayNameFromAddressBook($cloudId->getDisplayId())),
'server' => $cloudId->getRemote(),
];
}
// Fallback to empty dummy data
return [
'type' => 'user',
'id' => $uid,
'name' => (($overwriteDisplayName !== '') ? $overwriteDisplayName : $uid),
];
}
protected function getDisplayNameFromAddressBook(string $search): string {
if (isset($this->displayNames[$search])) {
return $this->displayNames[$search];
}
$addressBookContacts = $this->contactsManager->search($search, ['CLOUD'], [
'limit' => 1,
'enumeration' => false,
'fullmatch' => false,
'strict_search' => true,
]);
foreach ($addressBookContacts as $contact) {
if (isset($contact['isLocalSystemBook'])) {
continue;
}
if (isset($contact['CLOUD'])) {
$cloudIds = $contact['CLOUD'];
if (is_string($cloudIds)) {
$cloudIds = [$cloudIds];
}
$lowerSearch = strtolower($search);
foreach ($cloudIds as $cloudId) {
if (strtolower($cloudId) === $lowerSearch) {
$this->displayNames[$search] = $contact['FN'] . " ($cloudId)";
return $this->displayNames[$search];
}
}
}
}
return $search;
}
}
@@ -0,0 +1,141 @@
<?php
/**
* @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com>
*
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Joas Schilling <coding@schilljs.com>
*
* @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 <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Files_Sharing\Activity\Providers;
use OCP\Activity\IEvent;
class Downloads extends Base {
public const SUBJECT_PUBLIC_SHARED_FILE_DOWNLOADED = 'public_shared_file_downloaded';
public const SUBJECT_PUBLIC_SHARED_FOLDER_DOWNLOADED = 'public_shared_folder_downloaded';
public const SUBJECT_SHARED_FILE_BY_EMAIL_DOWNLOADED = 'file_shared_with_email_downloaded';
public const SUBJECT_SHARED_FOLDER_BY_EMAIL_DOWNLOADED = 'folder_shared_with_email_downloaded';
/**
* @param IEvent $event
* @return IEvent
* @throws \InvalidArgumentException
* @since 11.0.0
*/
public function parseShortVersion(IEvent $event) {
$parsedParameters = $this->getParsedParameters($event);
if ($event->getSubject() === self::SUBJECT_PUBLIC_SHARED_FILE_DOWNLOADED ||
$event->getSubject() === self::SUBJECT_PUBLIC_SHARED_FOLDER_DOWNLOADED) {
$subject = $this->l->t('Downloaded via public link');
} elseif ($event->getSubject() === self::SUBJECT_SHARED_FILE_BY_EMAIL_DOWNLOADED ||
$event->getSubject() === self::SUBJECT_SHARED_FOLDER_BY_EMAIL_DOWNLOADED) {
$subject = $this->l->t('Downloaded by {email}');
} else {
throw new \InvalidArgumentException();
}
if ($this->activityManager->getRequirePNG()) {
$event->setIcon($this->url->getAbsoluteURL($this->url->imagePath('core', 'actions/download.png')));
} else {
$event->setIcon($this->url->getAbsoluteURL($this->url->imagePath('core', 'actions/download.svg')));
}
$this->setSubjects($event, $subject, $parsedParameters);
return $event;
}
/**
* @param IEvent $event
* @param IEvent|null $previousEvent
* @return IEvent
* @throws \InvalidArgumentException
* @since 11.0.0
*/
public function parseLongVersion(IEvent $event, IEvent $previousEvent = null) {
$parsedParameters = $this->getParsedParameters($event);
if ($event->getSubject() === self::SUBJECT_PUBLIC_SHARED_FILE_DOWNLOADED ||
$event->getSubject() === self::SUBJECT_PUBLIC_SHARED_FOLDER_DOWNLOADED) {
if (!isset($parsedParameters['remote-address-hash']['type'])) {
$subject = $this->l->t('{file} downloaded via public link');
$this->setSubjects($event, $subject, $parsedParameters);
} else {
$subject = $this->l->t('{file} downloaded via public link');
$this->setSubjects($event, $subject, $parsedParameters);
$event = $this->eventMerger->mergeEvents('file', $event, $previousEvent);
}
} elseif ($event->getSubject() === self::SUBJECT_SHARED_FILE_BY_EMAIL_DOWNLOADED ||
$event->getSubject() === self::SUBJECT_SHARED_FOLDER_BY_EMAIL_DOWNLOADED) {
$subject = $this->l->t('{email} downloaded {file}');
$this->setSubjects($event, $subject, $parsedParameters);
} else {
throw new \InvalidArgumentException();
}
if ($this->activityManager->getRequirePNG()) {
$event->setIcon($this->url->getAbsoluteURL($this->url->imagePath('core', 'actions/download.png')));
} else {
$event->setIcon($this->url->getAbsoluteURL($this->url->imagePath('core', 'actions/download.svg')));
}
return $event;
}
/**
* @param IEvent $event
* @return array
* @throws \InvalidArgumentException
*/
protected function getParsedParameters(IEvent $event) {
$subject = $event->getSubject();
$parameters = $event->getSubjectParameters();
switch ($subject) {
case self::SUBJECT_PUBLIC_SHARED_FILE_DOWNLOADED:
case self::SUBJECT_PUBLIC_SHARED_FOLDER_DOWNLOADED:
if (isset($parameters[1])) {
return [
'file' => $this->getFile($parameters[0], $event),
'remote-address-hash' => [
'type' => 'highlight',
'id' => $parameters[1],
'name' => $parameters[1],
'link' => '',
],
];
}
return [
'file' => $this->getFile($parameters[0], $event),
];
case self::SUBJECT_SHARED_FILE_BY_EMAIL_DOWNLOADED:
case self::SUBJECT_SHARED_FOLDER_BY_EMAIL_DOWNLOADED:
return [
'file' => $this->getFile($parameters[0], $event),
'email' => [
'type' => 'email',
'id' => $parameters[1],
'name' => $parameters[1],
],
];
}
throw new \InvalidArgumentException();
}
}
@@ -0,0 +1,182 @@
<?php
/**
* @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com>
*
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Joas Schilling <coding@schilljs.com>
* @author John Molakvoæ <skjnldsv@protonmail.com>
*
* @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 <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Files_Sharing\Activity\Providers;
use OCP\Activity\IEvent;
use OCP\Activity\IEventMerger;
use OCP\Activity\IManager;
use OCP\Contacts\IManager as IContactsManager;
use OCP\Federation\ICloudIdManager;
use OCP\IGroup;
use OCP\IGroupManager;
use OCP\IURLGenerator;
use OCP\IUserManager;
use OCP\L10N\IFactory;
class Groups extends Base {
public const SUBJECT_SHARED_GROUP_SELF = 'shared_group_self';
public const SUBJECT_RESHARED_GROUP_BY = 'reshared_group_by';
public const SUBJECT_UNSHARED_GROUP_SELF = 'unshared_group_self';
public const SUBJECT_UNSHARED_GROUP_BY = 'unshared_group_by';
public const SUBJECT_EXPIRED_GROUP = 'expired_group';
/** @var IGroupManager */
protected $groupManager;
/** @var string[] */
protected $groupDisplayNames = [];
public function __construct(IFactory $languageFactory,
IURLGenerator $url,
IManager $activityManager,
IUserManager $userManager,
ICloudIdManager $cloudIdManager,
IContactsManager $contactsManager,
IEventMerger $eventMerger,
IGroupManager $groupManager) {
parent::__construct($languageFactory, $url, $activityManager, $userManager, $cloudIdManager, $contactsManager, $eventMerger);
$this->groupManager = $groupManager;
}
/**
* @param IEvent $event
* @return IEvent
* @throws \InvalidArgumentException
* @since 11.0.0
*/
public function parseShortVersion(IEvent $event) {
$parsedParameters = $this->getParsedParameters($event);
if ($event->getSubject() === self::SUBJECT_SHARED_GROUP_SELF) {
$subject = $this->l->t('Shared with group {group}');
} elseif ($event->getSubject() === self::SUBJECT_UNSHARED_GROUP_SELF) {
$subject = $this->l->t('Removed share for group {group}');
} elseif ($event->getSubject() === self::SUBJECT_RESHARED_GROUP_BY) {
$subject = $this->l->t('{actor} shared with group {group}');
} elseif ($event->getSubject() === self::SUBJECT_UNSHARED_GROUP_BY) {
$subject = $this->l->t('{actor} removed share for group {group}');
} elseif ($event->getSubject() === self::SUBJECT_EXPIRED_GROUP) {
$subject = $this->l->t('Share for group {group} expired');
} else {
throw new \InvalidArgumentException();
}
if ($this->activityManager->getRequirePNG()) {
$event->setIcon($this->url->getAbsoluteURL($this->url->imagePath('core', 'actions/share.png')));
} else {
$event->setIcon($this->url->getAbsoluteURL($this->url->imagePath('core', 'actions/share.svg')));
}
$this->setSubjects($event, $subject, $parsedParameters);
return $event;
}
/**
* @param IEvent $event
* @param IEvent|null $previousEvent
* @return IEvent
* @throws \InvalidArgumentException
* @since 11.0.0
*/
public function parseLongVersion(IEvent $event, IEvent $previousEvent = null) {
$parsedParameters = $this->getParsedParameters($event);
if ($event->getSubject() === self::SUBJECT_SHARED_GROUP_SELF) {
$subject = $this->l->t('You shared {file} with group {group}');
} elseif ($event->getSubject() === self::SUBJECT_UNSHARED_GROUP_SELF) {
$subject = $this->l->t('You removed group {group} from {file}');
} elseif ($event->getSubject() === self::SUBJECT_RESHARED_GROUP_BY) {
$subject = $this->l->t('{actor} shared {file} with group {group}');
} elseif ($event->getSubject() === self::SUBJECT_UNSHARED_GROUP_BY) {
$subject = $this->l->t('{actor} removed group {group} from {file}');
} elseif ($event->getSubject() === self::SUBJECT_EXPIRED_GROUP) {
$subject = $this->l->t('Share for file {file} with group {group} expired');
} else {
throw new \InvalidArgumentException();
}
if ($this->activityManager->getRequirePNG()) {
$event->setIcon($this->url->getAbsoluteURL($this->url->imagePath('core', 'actions/share.png')));
} else {
$event->setIcon($this->url->getAbsoluteURL($this->url->imagePath('core', 'actions/share.svg')));
}
$this->setSubjects($event, $subject, $parsedParameters);
return $event;
}
protected function getParsedParameters(IEvent $event) {
$subject = $event->getSubject();
$parameters = $event->getSubjectParameters();
switch ($subject) {
case self::SUBJECT_RESHARED_GROUP_BY:
case self::SUBJECT_UNSHARED_GROUP_BY:
return [
'file' => $this->getFile($parameters[0], $event),
'group' => $this->generateGroupParameter($parameters[2]),
'actor' => $this->getUser($parameters[1]),
];
case self::SUBJECT_SHARED_GROUP_SELF:
case self::SUBJECT_UNSHARED_GROUP_SELF:
case self::SUBJECT_EXPIRED_GROUP:
return [
'file' => $this->getFile($parameters[0], $event),
'group' => $this->generateGroupParameter($parameters[1]),
];
}
return [];
}
/**
* @param string $gid
* @return array
*/
protected function generateGroupParameter($gid) {
if (!isset($this->groupDisplayNames[$gid])) {
$this->groupDisplayNames[$gid] = $this->getGroupDisplayName($gid);
}
return [
'type' => 'user-group',
'id' => $gid,
'name' => $this->groupDisplayNames[$gid],
];
}
/**
* @param string $gid
* @return string
*/
protected function getGroupDisplayName($gid) {
$group = $this->groupManager->get($gid);
if ($group instanceof IGroup) {
return $group->getDisplayName();
}
return $gid;
}
}
@@ -0,0 +1,128 @@
<?php
/**
* @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com>
*
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Joas Schilling <coding@schilljs.com>
*
* @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 <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Files_Sharing\Activity\Providers;
use OCP\Activity\IEvent;
class PublicLinks extends Base {
public const SUBJECT_SHARED_LINK_SELF = 'shared_link_self';
public const SUBJECT_RESHARED_LINK_BY = 'reshared_link_by';
public const SUBJECT_UNSHARED_LINK_SELF = 'unshared_link_self';
public const SUBJECT_UNSHARED_LINK_BY = 'unshared_link_by';
public const SUBJECT_LINK_EXPIRED = 'link_expired';
public const SUBJECT_LINK_BY_EXPIRED = 'link_by_expired';
/**
* @param IEvent $event
* @return IEvent
* @throws \InvalidArgumentException
* @since 11.0.0
*/
public function parseShortVersion(IEvent $event) {
$parsedParameters = $this->getParsedParameters($event);
if ($event->getSubject() === self::SUBJECT_SHARED_LINK_SELF) {
$subject = $this->l->t('Shared as public link');
} elseif ($event->getSubject() === self::SUBJECT_UNSHARED_LINK_SELF) {
$subject = $this->l->t('Removed public link');
} elseif ($event->getSubject() === self::SUBJECT_LINK_EXPIRED) {
$subject = $this->l->t('Public link expired');
} elseif ($event->getSubject() === self::SUBJECT_RESHARED_LINK_BY) {
$subject = $this->l->t('{actor} shared as public link');
} elseif ($event->getSubject() === self::SUBJECT_UNSHARED_LINK_BY) {
$subject = $this->l->t('{actor} removed public link');
} elseif ($event->getSubject() === self::SUBJECT_LINK_BY_EXPIRED) {
$subject = $this->l->t('Public link of {actor} expired');
} else {
throw new \InvalidArgumentException();
}
if ($this->activityManager->getRequirePNG()) {
$event->setIcon($this->url->getAbsoluteURL($this->url->imagePath('core', 'actions/share.png')));
} else {
$event->setIcon($this->url->getAbsoluteURL($this->url->imagePath('core', 'actions/share.svg')));
}
$this->setSubjects($event, $subject, $parsedParameters);
return $event;
}
/**
* @param IEvent $event
* @param IEvent|null $previousEvent
* @return IEvent
* @throws \InvalidArgumentException
* @since 11.0.0
*/
public function parseLongVersion(IEvent $event, IEvent $previousEvent = null) {
$parsedParameters = $this->getParsedParameters($event);
if ($event->getSubject() === self::SUBJECT_SHARED_LINK_SELF) {
$subject = $this->l->t('You shared {file} as public link');
} elseif ($event->getSubject() === self::SUBJECT_UNSHARED_LINK_SELF) {
$subject = $this->l->t('You removed public link for {file}');
} elseif ($event->getSubject() === self::SUBJECT_LINK_EXPIRED) {
$subject = $this->l->t('Public link expired for {file}');
} elseif ($event->getSubject() === self::SUBJECT_RESHARED_LINK_BY) {
$subject = $this->l->t('{actor} shared {file} as public link');
} elseif ($event->getSubject() === self::SUBJECT_UNSHARED_LINK_BY) {
$subject = $this->l->t('{actor} removed public link for {file}');
} elseif ($event->getSubject() === self::SUBJECT_LINK_BY_EXPIRED) {
$subject = $this->l->t('Public link of {actor} for {file} expired');
} else {
throw new \InvalidArgumentException();
}
if ($this->activityManager->getRequirePNG()) {
$event->setIcon($this->url->getAbsoluteURL($this->url->imagePath('core', 'actions/share.png')));
} else {
$event->setIcon($this->url->getAbsoluteURL($this->url->imagePath('core', 'actions/share.svg')));
}
$this->setSubjects($event, $subject, $parsedParameters);
return $event;
}
protected function getParsedParameters(IEvent $event) {
$subject = $event->getSubject();
$parameters = $event->getSubjectParameters();
switch ($subject) {
case self::SUBJECT_SHARED_LINK_SELF:
case self::SUBJECT_UNSHARED_LINK_SELF:
case self::SUBJECT_LINK_EXPIRED:
return [
'file' => $this->getFile($parameters[0], $event),
];
case self::SUBJECT_RESHARED_LINK_BY:
case self::SUBJECT_UNSHARED_LINK_BY:
case self::SUBJECT_LINK_BY_EXPIRED:
return [
'file' => $this->getFile($parameters[0], $event),
'actor' => $this->getUser($parameters[1]),
];
}
return [];
}
}
@@ -0,0 +1,140 @@
<?php
/**
* @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com>
*
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Joas Schilling <coding@schilljs.com>
* @author Robin Appelman <robin@icewind.nl>
*
* @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 <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Files_Sharing\Activity\Providers;
use OCP\Activity\IEvent;
use OCP\Activity\IEventMerger;
use OCP\Activity\IManager;
use OCP\Contacts\IManager as IContactsManager;
use OCP\Federation\ICloudIdManager;
use OCP\IURLGenerator;
use OCP\IUserManager;
use OCP\L10N\IFactory;
class RemoteShares extends Base {
public const SUBJECT_REMOTE_SHARE_ACCEPTED = 'remote_share_accepted';
public const SUBJECT_REMOTE_SHARE_DECLINED = 'remote_share_declined';
public const SUBJECT_REMOTE_SHARE_RECEIVED = 'remote_share_received';
public const SUBJECT_REMOTE_SHARE_UNSHARED = 'remote_share_unshared';
public function __construct(IFactory $languageFactory,
IURLGenerator $url,
IManager $activityManager,
IUserManager $userManager,
ICloudIdManager $cloudIdManager,
IContactsManager $contactsManager,
IEventMerger $eventMerger) {
parent::__construct($languageFactory, $url, $activityManager, $userManager, $cloudIdManager, $contactsManager, $eventMerger);
}
/**
* @param IEvent $event
* @return IEvent
* @throws \InvalidArgumentException
* @since 11.0.0
*/
public function parseShortVersion(IEvent $event) {
$parsedParameters = $this->getParsedParameters($event);
if ($event->getSubject() === self::SUBJECT_REMOTE_SHARE_ACCEPTED) {
$subject = $this->l->t('{user} accepted the remote share');
} elseif ($event->getSubject() === self::SUBJECT_REMOTE_SHARE_DECLINED) {
$subject = $this->l->t('{user} declined the remote share');
} else {
throw new \InvalidArgumentException();
}
if ($this->activityManager->getRequirePNG()) {
$event->setIcon($this->url->getAbsoluteURL($this->url->imagePath('core', 'actions/share.png')));
} else {
$event->setIcon($this->url->getAbsoluteURL($this->url->imagePath('core', 'actions/share.svg')));
}
$this->setSubjects($event, $subject, $parsedParameters);
return $event;
}
/**
* @param IEvent $event
* @param IEvent|null $previousEvent
* @return IEvent
* @throws \InvalidArgumentException
* @since 11.0.0
*/
public function parseLongVersion(IEvent $event, IEvent $previousEvent = null) {
$parsedParameters = $this->getParsedParameters($event);
if ($event->getSubject() === self::SUBJECT_REMOTE_SHARE_RECEIVED) {
$subject = $this->l->t('You received a new remote share {file} from {user}');
} elseif ($event->getSubject() === self::SUBJECT_REMOTE_SHARE_ACCEPTED) {
$subject = $this->l->t('{user} accepted the remote share of {file}');
} elseif ($event->getSubject() === self::SUBJECT_REMOTE_SHARE_DECLINED) {
$subject = $this->l->t('{user} declined the remote share of {file}');
} elseif ($event->getSubject() === self::SUBJECT_REMOTE_SHARE_UNSHARED) {
$subject = $this->l->t('{user} unshared {file} from you');
} else {
throw new \InvalidArgumentException();
}
if ($this->activityManager->getRequirePNG()) {
$event->setIcon($this->url->getAbsoluteURL($this->url->imagePath('core', 'actions/share.png')));
} else {
$event->setIcon($this->url->getAbsoluteURL($this->url->imagePath('core', 'actions/share.svg')));
}
$this->setSubjects($event, $subject, $parsedParameters);
return $event;
}
protected function getParsedParameters(IEvent $event) {
$subject = $event->getSubject();
$parameters = $event->getSubjectParameters();
switch ($subject) {
case self::SUBJECT_REMOTE_SHARE_RECEIVED:
case self::SUBJECT_REMOTE_SHARE_UNSHARED:
$displayName = (count($parameters) > 2) ? $parameters[2] : '';
return [
'file' => [
'type' => 'pending-federated-share',
'id' => $parameters[1],
'name' => $parameters[1],
],
'user' => $this->getUser($parameters[0], $displayName)
];
case self::SUBJECT_REMOTE_SHARE_ACCEPTED:
case self::SUBJECT_REMOTE_SHARE_DECLINED:
$fileParameter = $parameters[1];
if (!is_array($fileParameter)) {
$fileParameter = [$event->getObjectId() => $event->getObjectName()];
}
return [
'file' => $this->getFile($fileParameter),
'user' => $this->getUser($parameters[0]),
];
}
throw new \InvalidArgumentException();
}
}
@@ -0,0 +1,162 @@
<?php
/**
* @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com>
*
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Joas Schilling <coding@schilljs.com>
* @author John Molakvoæ <skjnldsv@protonmail.com>
* @author Kevin Ndung'u <kevgathuku@gmail.com>
*
* @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 <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Files_Sharing\Activity\Providers;
use OCP\Activity\IEvent;
class Users extends Base {
public const SUBJECT_SHARED_USER_SELF = 'shared_user_self';
public const SUBJECT_RESHARED_USER_BY = 'reshared_user_by';
public const SUBJECT_UNSHARED_USER_SELF = 'unshared_user_self';
public const SUBJECT_UNSHARED_USER_BY = 'unshared_user_by';
public const SUBJECT_SHARED_WITH_BY = 'shared_with_by';
public const SUBJECT_UNSHARED_BY = 'unshared_by';
public const SUBJECT_SELF_UNSHARED = 'self_unshared';
public const SUBJECT_SELF_UNSHARED_BY = 'self_unshared_by';
public const SUBJECT_EXPIRED_USER = 'expired_user';
public const SUBJECT_EXPIRED = 'expired';
/**
* @param IEvent $event
* @return IEvent
* @throws \InvalidArgumentException
* @since 11.0.0
*/
public function parseShortVersion(IEvent $event) {
$parsedParameters = $this->getParsedParameters($event);
if ($event->getSubject() === self::SUBJECT_SHARED_USER_SELF) {
$subject = $this->l->t('Shared with {user}');
} elseif ($event->getSubject() === self::SUBJECT_UNSHARED_USER_SELF) {
$subject = $this->l->t('Removed share for {user}');
} elseif ($event->getSubject() === self::SUBJECT_SELF_UNSHARED) {
$subject = $this->l->t('You removed yourself');
} elseif ($event->getSubject() === self::SUBJECT_SELF_UNSHARED_BY) {
$subject = $this->l->t('{actor} removed themselves');
} elseif ($event->getSubject() === self::SUBJECT_RESHARED_USER_BY) {
$subject = $this->l->t('{actor} shared with {user}');
} elseif ($event->getSubject() === self::SUBJECT_UNSHARED_USER_BY) {
$subject = $this->l->t('{actor} removed share for {user}');
} elseif ($event->getSubject() === self::SUBJECT_SHARED_WITH_BY) {
$subject = $this->l->t('Shared by {actor}');
} elseif ($event->getSubject() === self::SUBJECT_UNSHARED_BY) {
$subject = $this->l->t('{actor} removed share');
} elseif ($event->getSubject() === self::SUBJECT_EXPIRED_USER) {
$subject = $this->l->t('Share for {user} expired');
} elseif ($event->getSubject() === self::SUBJECT_EXPIRED) {
$subject = $this->l->t('Share expired');
} else {
throw new \InvalidArgumentException();
}
if ($this->activityManager->getRequirePNG()) {
$event->setIcon($this->url->getAbsoluteURL($this->url->imagePath('core', 'actions/share.png')));
} else {
$event->setIcon($this->url->getAbsoluteURL($this->url->imagePath('core', 'actions/share.svg')));
}
$this->setSubjects($event, $subject, $parsedParameters);
return $event;
}
/**
* @param IEvent $event
* @param IEvent|null $previousEvent
* @return IEvent
* @throws \InvalidArgumentException
* @since 11.0.0
*/
public function parseLongVersion(IEvent $event, IEvent $previousEvent = null) {
$parsedParameters = $this->getParsedParameters($event);
if ($event->getSubject() === self::SUBJECT_SHARED_USER_SELF) {
$subject = $this->l->t('You shared {file} with {user}');
} elseif ($event->getSubject() === self::SUBJECT_UNSHARED_USER_SELF) {
$subject = $this->l->t('You removed {user} from {file}');
} elseif ($event->getSubject() === self::SUBJECT_SELF_UNSHARED) {
$subject = $this->l->t('You removed yourself from {file}');
} elseif ($event->getSubject() === self::SUBJECT_SELF_UNSHARED_BY) {
$subject = $this->l->t('{actor} removed themselves from {file}');
} elseif ($event->getSubject() === self::SUBJECT_RESHARED_USER_BY) {
$subject = $this->l->t('{actor} shared {file} with {user}');
} elseif ($event->getSubject() === self::SUBJECT_UNSHARED_USER_BY) {
$subject = $this->l->t('{actor} removed {user} from {file}');
} elseif ($event->getSubject() === self::SUBJECT_SHARED_WITH_BY) {
$subject = $this->l->t('{actor} shared {file} with you');
} elseif ($event->getSubject() === self::SUBJECT_UNSHARED_BY) {
$subject = $this->l->t('{actor} removed you from the share named {file}');
} elseif ($event->getSubject() === self::SUBJECT_EXPIRED_USER) {
$subject = $this->l->t('Share for file {file} with {user} expired');
} elseif ($event->getSubject() === self::SUBJECT_EXPIRED) {
$subject = $this->l->t('Share for file {file} expired');
} else {
throw new \InvalidArgumentException();
}
if ($this->activityManager->getRequirePNG()) {
$event->setIcon($this->url->getAbsoluteURL($this->url->imagePath('core', 'actions/share.png')));
} else {
$event->setIcon($this->url->getAbsoluteURL($this->url->imagePath('core', 'actions/share.svg')));
}
$this->setSubjects($event, $subject, $parsedParameters);
return $event;
}
protected function getParsedParameters(IEvent $event) {
$subject = $event->getSubject();
$parameters = $event->getSubjectParameters();
switch ($subject) {
case self::SUBJECT_SHARED_USER_SELF:
case self::SUBJECT_UNSHARED_USER_SELF:
case self::SUBJECT_EXPIRED_USER:
case self::SUBJECT_EXPIRED:
return [
'file' => $this->getFile($parameters[0], $event),
'user' => $this->getUser($parameters[1]),
];
case self::SUBJECT_SHARED_WITH_BY:
case self::SUBJECT_UNSHARED_BY:
case self::SUBJECT_SELF_UNSHARED:
case self::SUBJECT_SELF_UNSHARED_BY:
return [
'file' => $this->getFile($parameters[0], $event),
'actor' => $this->getUser($parameters[1]),
];
case self::SUBJECT_RESHARED_USER_BY:
case self::SUBJECT_UNSHARED_USER_BY:
return [
'file' => $this->getFile($parameters[0], $event),
'user' => $this->getUser($parameters[2]),
'actor' => $this->getUser($parameters[1]),
];
}
return [];
}
}
@@ -0,0 +1,84 @@
<?php
/**
* @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com>
*
* @author Joas Schilling <coding@schilljs.com>
* @author Robin Appelman <robin@icewind.nl>
*
* @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 <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Files_Sharing\Activity\Settings;
class PublicLinks extends ShareActivitySettings {
/**
* @return string Lowercase a-z and underscore only identifier
* @since 11.0.0
*/
public function getIdentifier() {
return 'public_links';
}
/**
* @return string A translated string
* @since 11.0.0
*/
public function getName() {
return $this->l->t('A file or folder shared by mail or by public link was <strong>downloaded</strong>');
}
/**
* @return int whether the filter should be rather on the top or bottom of
* the admin section. The filters are arranged in ascending order of the
* priority values. It is required to return a value between 0 and 100.
* @since 11.0.0
*/
public function getPriority() {
return 20;
}
/**
* @return bool True when the option can be changed for the stream
* @since 11.0.0
*/
public function canChangeStream() {
return true;
}
/**
* @return bool True when the option can be changed for the stream
* @since 11.0.0
*/
public function isDefaultEnabledStream() {
return true;
}
/**
* @return bool True when the option can be changed for the mail
* @since 11.0.0
*/
public function canChangeMail() {
return true;
}
/**
* @return bool True when the option can be changed for the stream
* @since 11.0.0
*/
public function isDefaultEnabledMail() {
return false;
}
}
@@ -0,0 +1,85 @@
<?php
/**
* @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com>
*
* @author Joas Schilling <coding@schilljs.com>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Robin Appelman <robin@icewind.nl>
*
* @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 <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Files_Sharing\Activity\Settings;
class RemoteShare extends ShareActivitySettings {
/**
* @return string Lowercase a-z and underscore only identifier
* @since 11.0.0
*/
public function getIdentifier() {
return 'remote_share';
}
/**
* @return string A translated string
* @since 11.0.0
*/
public function getName() {
return $this->l->t('A file or folder was shared from <strong>another server</strong>');
}
/**
* @return int whether the filter should be rather on the top or bottom of
* the admin section. The filters are arranged in ascending order of the
* priority values. It is required to return a value between 0 and 100.
* @since 11.0.0
*/
public function getPriority() {
return 11;
}
/**
* @return bool True when the option can be changed for the stream
* @since 11.0.0
*/
public function canChangeStream() {
return true;
}
/**
* @return bool True when the option can be changed for the stream
* @since 11.0.0
*/
public function isDefaultEnabledStream() {
return true;
}
/**
* @return bool True when the option can be changed for the mail
* @since 11.0.0
*/
public function canChangeMail() {
return true;
}
/**
* @return bool True when the option can be changed for the stream
* @since 11.0.0
*/
public function isDefaultEnabledMail() {
return false;
}
}
@@ -0,0 +1,49 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2020 Robin Appelman <robin@icewind.nl>
*
* @author Robin Appelman <robin@icewind.nl>
*
* @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 <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Files_Sharing\Activity\Settings;
use OCP\Activity\ActivitySettings;
use OCP\IL10N;
abstract class ShareActivitySettings extends ActivitySettings {
/** @var IL10N */
protected $l;
/**
* @param IL10N $l
*/
public function __construct(IL10N $l) {
$this->l = $l;
}
public function getGroupIdentifier() {
return 'files';
}
public function getGroupName() {
return $this->l->t('Files');
}
}
@@ -0,0 +1,85 @@
<?php
/**
* @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com>
*
* @author Joas Schilling <coding@schilljs.com>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Robin Appelman <robin@icewind.nl>
*
* @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 <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Files_Sharing\Activity\Settings;
class Shared extends ShareActivitySettings {
/**
* @return string Lowercase a-z and underscore only identifier
* @since 11.0.0
*/
public function getIdentifier() {
return 'shared';
}
/**
* @return string A translated string
* @since 11.0.0
*/
public function getName() {
return $this->l->t('A file or folder has been <strong>shared</strong>');
}
/**
* @return int whether the filter should be rather on the top or bottom of
* the admin section. The filters are arranged in ascending order of the
* priority values. It is required to return a value between 0 and 100.
* @since 11.0.0
*/
public function getPriority() {
return 10;
}
/**
* @return bool True when the option can be changed for the stream
* @since 11.0.0
*/
public function canChangeStream() {
return true;
}
/**
* @return bool True when the option can be changed for the stream
* @since 11.0.0
*/
public function isDefaultEnabledStream() {
return true;
}
/**
* @return bool True when the option can be changed for the mail
* @since 11.0.0
*/
public function canChangeMail() {
return true;
}
/**
* @return bool True when the option can be changed for the stream
* @since 11.0.0
*/
public function isDefaultEnabledMail() {
return false;
}
}
@@ -0,0 +1,216 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Bjoern Schiessle <bjoern@schiessle.org>
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Joas Schilling <coding@schilljs.com>
* @author John Molakvoæ <skjnldsv@protonmail.com>
* @author Julius Härtl <jus@bitgrid.net>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Robin Appelman <robin@icewind.nl>
* @author Roeland Jago Douma <roeland@famdouma.nl>
* @author Thomas Müller <thomas.mueller@tmit.eu>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* 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, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OCA\Files_Sharing\AppInfo;
use OC\Group\DisplayNameCache as GroupDisplayNameCache;
use OC\Share\Share;
use OC\User\DisplayNameCache;
use OCA\Files\Event\LoadAdditionalScriptsEvent;
use OCA\Files\Event\LoadSidebar;
use OCA\Files_Sharing\Capabilities;
use OCA\Files_Sharing\External\Manager;
use OCA\Files_Sharing\External\MountProvider as ExternalMountProvider;
use OCA\Files_Sharing\Helper;
use OCA\Files_Sharing\Listener\LoadAdditionalListener;
use OCA\Files_Sharing\Listener\LoadSidebarListener;
use OCA\Files_Sharing\Listener\ShareInteractionListener;
use OCA\Files_Sharing\Listener\UserAddedToGroupListener;
use OCA\Files_Sharing\Listener\UserShareAcceptanceListener;
use OCA\Files_Sharing\Middleware\OCSShareAPIMiddleware;
use OCA\Files_Sharing\Middleware\ShareInfoMiddleware;
use OCA\Files_Sharing\Middleware\SharingCheckMiddleware;
use OCA\Files_Sharing\MountProvider;
use OCA\Files_Sharing\Notification\Listener;
use OCA\Files_Sharing\Notification\Notifier;
use OCA\Files_Sharing\ShareBackend\File;
use OCA\Files_Sharing\ShareBackend\Folder;
use OCA\Files_Sharing\ViewOnly;
use OCP\AppFramework\App;
use OCP\AppFramework\Bootstrap\IBootContext;
use OCP\AppFramework\Bootstrap\IBootstrap;
use OCP\AppFramework\Bootstrap\IRegistrationContext;
use OCP\Collaboration\Resources\LoadAdditionalScriptsEvent as ResourcesLoadAdditionalScriptsEvent;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\Federation\ICloudIdManager;
use OCP\Files\Config\IMountProviderCollection;
use OCP\Files\Events\BeforeDirectFileDownloadEvent;
use OCP\Files\Events\BeforeZipCreatedEvent;
use OCP\Files\IRootFolder;
use OCP\Group\Events\GroupChangedEvent;
use OCP\Group\Events\GroupDeletedEvent;
use OCP\Group\Events\UserAddedEvent;
use OCP\IDBConnection;
use OCP\IGroup;
use OCP\IUserSession;
use OCP\Share\Events\ShareCreatedEvent;
use OCP\User\Events\UserChangedEvent;
use OCP\User\Events\UserDeletedEvent;
use OCP\Util;
use Psr\Container\ContainerInterface;
use Symfony\Component\EventDispatcher\GenericEvent as OldGenericEvent;
class Application extends App implements IBootstrap {
public const APP_ID = 'files_sharing';
public function __construct(array $urlParams = []) {
parent::__construct(self::APP_ID, $urlParams);
}
public function register(IRegistrationContext $context): void {
$context->registerService(ExternalMountProvider::class, function (ContainerInterface $c) {
return new ExternalMountProvider(
$c->get(IDBConnection::class),
function () use ($c) {
return $c->get(Manager::class);
},
$c->get(ICloudIdManager::class)
);
});
/**
* Middleware
*/
$context->registerMiddleWare(SharingCheckMiddleware::class);
$context->registerMiddleWare(OCSShareAPIMiddleware::class);
$context->registerMiddleWare(ShareInfoMiddleware::class);
$context->registerCapability(Capabilities::class);
$context->registerNotifierService(Notifier::class);
$context->registerEventListener(UserChangedEvent::class, DisplayNameCache::class);
$context->registerEventListener(UserDeletedEvent::class, DisplayNameCache::class);
$context->registerEventListener(GroupChangedEvent::class, GroupDisplayNameCache::class);
$context->registerEventListener(GroupDeletedEvent::class, GroupDisplayNameCache::class);
}
public function boot(IBootContext $context): void {
$context->injectFn([$this, 'registerMountProviders']);
$context->injectFn([$this, 'registerEventsScripts']);
$context->injectFn([$this, 'registerDownloadEvents']);
Helper::registerHooks();
Share::registerBackend('file', File::class);
Share::registerBackend('folder', Folder::class, 'file');
}
public function registerMountProviders(IMountProviderCollection $mountProviderCollection, MountProvider $mountProvider, ExternalMountProvider $externalMountProvider): void {
$mountProviderCollection->registerProvider($mountProvider);
$mountProviderCollection->registerProvider($externalMountProvider);
}
public function registerEventsScripts(IEventDispatcher $dispatcher): void {
// sidebar and files scripts
$dispatcher->addServiceListener(LoadAdditionalScriptsEvent::class, LoadAdditionalListener::class);
$dispatcher->addServiceListener(LoadSidebar::class, LoadSidebarListener::class);
$dispatcher->addServiceListener(ShareCreatedEvent::class, ShareInteractionListener::class);
$dispatcher->addServiceListener(ShareCreatedEvent::class, UserShareAcceptanceListener::class);
$dispatcher->addServiceListener(UserAddedEvent::class, UserAddedToGroupListener::class);
$dispatcher->addListener(ResourcesLoadAdditionalScriptsEvent::class, function () {
\OCP\Util::addScript('files_sharing', 'collaboration');
});
$dispatcher->addListener(\OCP\AppFramework\Http\Events\BeforeTemplateRenderedEvent::class, function () {
/**
* Always add main sharing script
*/
Util::addScript(self::APP_ID, 'main');
});
// notifications api to accept incoming user shares
$dispatcher->addListener(ShareCreatedEvent::class, function (ShareCreatedEvent $event) {
/** @var Listener $listener */
$listener = $this->getContainer()->query(Listener::class);
$listener->shareNotification($event);
});
$dispatcher->addListener(IGroup::class . '::postAddUser', function ($event) {
if (!$event instanceof OldGenericEvent) {
return;
}
/** @var Listener $listener */
$listener = $this->getContainer()->query(Listener::class);
$listener->userAddedToGroup($event);
});
}
public function registerDownloadEvents(
IEventDispatcher $dispatcher,
IUserSession $userSession,
IRootFolder $rootFolder
): void {
$dispatcher->addListener(
BeforeDirectFileDownloadEvent::class,
function (BeforeDirectFileDownloadEvent $event) use ($userSession, $rootFolder): void {
$pathsToCheck = [$event->getPath()];
// Check only for user/group shares. Don't restrict e.g. share links
$user = $userSession->getUser();
if ($user) {
$viewOnlyHandler = new ViewOnly(
$rootFolder->getUserFolder($user->getUID())
);
if (!$viewOnlyHandler->check($pathsToCheck)) {
$event->setSuccessful(false);
$event->setErrorMessage('Access to this resource or one of its sub-items has been denied.');
}
}
}
);
$dispatcher->addListener(
BeforeZipCreatedEvent::class,
function (BeforeZipCreatedEvent $event) use ($userSession, $rootFolder): void {
$dir = $event->getDirectory();
$files = $event->getFiles();
$pathsToCheck = [];
foreach ($files as $file) {
$pathsToCheck[] = $dir . '/' . $file;
}
// Check only for user/group shares. Don't restrict e.g. share links
$user = $userSession->getUser();
if ($user) {
$viewOnlyHandler = new ViewOnly(
$rootFolder->getUserFolder($user->getUID())
);
if (!$viewOnlyHandler->check($pathsToCheck)) {
$event->setErrorMessage('Access to this resource or one of its sub-items has been denied.');
$event->setSuccessful(false);
} else {
$event->setSuccessful(true);
}
} else {
$event->setSuccessful(true);
}
}
);
}
}
@@ -0,0 +1,68 @@
<?php
declare(strict_types=1);
/**
* @copyright 2018, Roeland Jago Douma <roeland@famdouma.nl>
*
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Maxence Lange <maxence@artificial-owl.com>
* @author Roeland Jago Douma <roeland@famdouma.nl>
*
* @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 <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Files_Sharing\BackgroundJob;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\BackgroundJob\TimedJob;
use OCP\IDBConnection;
use OCP\OCM\Exceptions\OCMProviderException;
use OCP\OCM\IOCMDiscoveryService;
use OCP\OCS\IDiscoveryService;
use Psr\Log\LoggerInterface;
class FederatedSharesDiscoverJob extends TimedJob {
public function __construct(
ITimeFactory $time,
private IDBConnection $connection,
private IDiscoveryService $discoveryService,
private IOCMDiscoveryService $ocmDiscoveryService,
private LoggerInterface $logger,
) {
parent::__construct($time);
$this->setInterval(86400);
}
public function run($argument) {
$qb = $this->connection->getQueryBuilder();
$qb->selectDistinct('remote')
->from('share_external');
$result = $qb->execute();
while ($row = $result->fetch()) {
$this->discoveryService->discover($row['remote'], 'FEDERATED_SHARING', true);
try {
$this->ocmDiscoveryService->discover($row['remote'], true);
} catch (OCMProviderException $e) {
$this->logger->info('exception while running files_sharing/lib/BackgroundJob/FederatedSharesDiscoverJob', ['exception' => $e]);
}
}
$result->closeCursor();
}
}
@@ -0,0 +1,220 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Arthur Schiwon <blizzz@arthur-schiwon.de>
* @author Christopher Schäpers <kondou@ts.unde.re>
* @author Joas Schilling <coding@schilljs.com>
* @author Jörn Friedrich Dreyer <jfd@butonic.de>
* @author Michael Gapczynski <GapczynskiM@gmail.com>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Robin Appelman <robin@icewind.nl>
* @author Roeland Jago Douma <roeland@famdouma.nl>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* 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, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OCA\Files_Sharing;
use OC\Files\Cache\FailedCache;
use OC\Files\Cache\Wrapper\CacheJail;
use OC\Files\Search\SearchBinaryOperator;
use OC\Files\Search\SearchComparison;
use OC\Files\Storage\Wrapper\Jail;
use OC\User\DisplayNameCache;
use OCP\Files\Cache\ICacheEntry;
use OCP\Files\Search\ISearchBinaryOperator;
use OCP\Files\Search\ISearchComparison;
use OCP\Files\Search\ISearchOperator;
use OCP\Files\StorageNotAvailableException;
use OCP\Share\IShare;
/**
* Metadata cache for shared files
*
* don't use this class directly if you need to get metadata, use \OC\Files\Filesystem::getFileInfo instead
*/
class Cache extends CacheJail {
/** @var SharedStorage */
private $storage;
private ICacheEntry $sourceRootInfo;
private bool $rootUnchanged = true;
private ?string $ownerDisplayName = null;
private $numericId;
private DisplayNameCache $displayNameCache;
private IShare $share;
/**
* @param SharedStorage $storage
*/
public function __construct(
$storage,
ICacheEntry $sourceRootInfo,
DisplayNameCache $displayNameCache,
IShare $share
) {
$this->storage = $storage;
$this->sourceRootInfo = $sourceRootInfo;
$this->numericId = $sourceRootInfo->getStorageId();
$this->displayNameCache = $displayNameCache;
$this->share = $share;
parent::__construct(
null,
''
);
}
protected function getRoot() {
if ($this->root === '') {
$absoluteRoot = $this->sourceRootInfo->getPath();
// the sourceRootInfo path is the absolute path of the folder in the "real" storage
// in the case where a folder is shared from a Jail we need to ensure that the share Jail
// has its root set relative to the source Jail
$currentStorage = $this->storage->getSourceStorage();
if ($currentStorage->instanceOfStorage(Jail::class)) {
/** @var Jail $currentStorage */
$absoluteRoot = $currentStorage->getJailedPath($absoluteRoot);
}
$this->root = $absoluteRoot;
}
return $this->root;
}
protected function getGetUnjailedRoot() {
return $this->sourceRootInfo->getPath();
}
public function getCache() {
if (is_null($this->cache)) {
$sourceStorage = $this->storage->getSourceStorage();
if ($sourceStorage) {
$this->cache = $sourceStorage->getCache();
} else {
// don't set $this->cache here since sourceStorage will be set later
return new FailedCache();
}
}
return $this->cache;
}
public function getNumericStorageId() {
if (isset($this->numericId)) {
return $this->numericId;
} else {
return -1;
}
}
public function get($file) {
if ($this->rootUnchanged && ($file === '' || $file === $this->sourceRootInfo->getId())) {
return $this->formatCacheEntry(clone $this->sourceRootInfo, '');
}
return parent::get($file);
}
public function update($id, array $data) {
$this->rootUnchanged = false;
parent::update($id, $data);
}
public function insert($file, array $data) {
$this->rootUnchanged = false;
return parent::insert($file, $data);
}
public function remove($file) {
$this->rootUnchanged = false;
parent::remove($file);
}
public function moveFromCache(\OCP\Files\Cache\ICache $sourceCache, $sourcePath, $targetPath) {
$this->rootUnchanged = false;
return parent::moveFromCache($sourceCache, $sourcePath, $targetPath);
}
protected function formatCacheEntry($entry, $path = null) {
if (is_null($path)) {
$path = $entry['path'] ?? '';
$entry['path'] = $this->getJailedPath($path);
} else {
$entry['path'] = $path;
}
try {
if (isset($entry['permissions'])) {
$entry['permissions'] &= $this->share->getPermissions();
} else {
$entry['permissions'] = $this->storage->getPermissions($entry['path']);
}
if ($this->share->getNodeId() === $entry['fileid']) {
$entry['name'] = basename($this->share->getTarget());
}
} catch (StorageNotAvailableException $e) {
// thrown by FailedStorage e.g. when the sharer does not exist anymore
// (IDE may say the exception is never thrown – false negative)
$sharePermissions = 0;
}
$entry['uid_owner'] = $this->share->getShareOwner();
$entry['displayname_owner'] = $this->getOwnerDisplayName();
if ($path === '') {
$entry['is_share_mount_point'] = true;
}
return $entry;
}
private function getOwnerDisplayName() {
if (!$this->ownerDisplayName) {
$uid = $this->share->getShareOwner();
$this->ownerDisplayName = $this->displayNameCache->getDisplayName($uid) ?? $uid;
}
return $this->ownerDisplayName;
}
/**
* remove all entries for files that are stored on the storage from the cache
*/
public function clear() {
// Not a valid action for Shared Cache
}
public function getQueryFilterForStorage(): ISearchOperator {
$storageFilter = \OC\Files\Cache\Cache::getQueryFilterForStorage();
// Do the normal jail behavior for non files
if ($this->storage->getItemType() !== 'file') {
return $this->addJailFilterQuery($storageFilter);
}
// for single file shares we don't need to do the LIKE
return new SearchBinaryOperator(
ISearchBinaryOperator::OPERATOR_AND,
[
$storageFilter,
new SearchComparison(ISearchComparison::COMPARE_EQUAL, 'path', $this->getGetUnjailedRoot()),
]
);
}
public function getCacheEntryFromSearchResult(ICacheEntry $rawEntry): ?ICacheEntry {
if ($rawEntry->getStorageId() === $this->getNumericStorageId()) {
return parent::getCacheEntryFromSearchResult($rawEntry);
} else {
return null;
}
}
}
@@ -0,0 +1,202 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Bjoern Schiessle <bjoern@schiessle.org>
* @author Julius Härtl <jus@bitgrid.net>
* @author Robin Appelman <robin@icewind.nl>
* @author Roeland Jago Douma <roeland@famdouma.nl>
* @author Tobias Kaminsky <tobias@kaminsky.me>
* @author Vincent Petry <vincent@nextcloud.com>
* @author Kate Döen <kate.doeen@nextcloud.com>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* 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, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OCA\Files_Sharing;
use OCP\Capabilities\ICapability;
use OCP\Constants;
use OCP\IConfig;
use OCP\Share\IManager;
/**
* Class Capabilities
*
* @package OCA\Files_Sharing
*/
class Capabilities implements ICapability {
/** @var IConfig */
private $config;
/** @var IManager */
private $shareManager;
public function __construct(IConfig $config, IManager $shareManager) {
$this->config = $config;
$this->shareManager = $shareManager;
}
/**
* Return this classes capabilities
*
* @return array{
* files_sharing: array{
* api_enabled: bool,
* public: array{
* enabled: bool,
* password?: array{
* enforced: bool,
* askForOptionalPassword: bool
* },
* multiple_links?: bool,
* expire_date?: array{
* enabled: bool,
* days?: int,
* enforced?: bool,
* },
* expire_date_internal?: array{
* enabled: bool,
* days?: int,
* enforced?: bool,
* },
* expire_date_remote?: array{
* enabled: bool,
* days?: int,
* enforced?: bool,
* },
* send_mail?: bool,
* upload?: bool,
* upload_files_drop?: bool,
* },
* user: array{
* send_mail: bool,
* expire_date?: array{
* enabled: bool,
* },
* },
* resharing: bool,
* group_sharing?: bool,
* group?: array{
* enabled: bool,
* expire_date?: array{
* enabled: bool,
* },
* },
* default_permissions?: int,
* federation: array{
* outgoing: bool,
* incoming: bool,
* expire_date: array{
* enabled: bool,
* },
* expire_date_supported: array{
* enabled: bool,
* },
* },
* sharee: array{
* query_lookup_default: bool,
* always_show_unique: bool,
* },
* },
* }
*/
public function getCapabilities() {
$res = [];
if (!$this->shareManager->shareApiEnabled()) {
$res['api_enabled'] = false;
$res['public'] = ['enabled' => false];
$res['user'] = ['send_mail' => false];
$res['resharing'] = false;
} else {
$res['api_enabled'] = true;
$public = [];
$public['enabled'] = $this->shareManager->shareApiAllowLinks();
if ($public['enabled']) {
$public['password'] = [];
$public['password']['enforced'] = $this->shareManager->shareApiLinkEnforcePassword();
if ($public['password']['enforced']) {
$public['password']['askForOptionalPassword'] = false;
} else {
$public['password']['askForOptionalPassword'] = ($this->config->getAppValue('core', 'shareapi_enable_link_password_by_default', 'no') === 'yes');
}
$public['expire_date'] = [];
$public['multiple_links'] = true;
$public['expire_date']['enabled'] = $this->shareManager->shareApiLinkDefaultExpireDate();
if ($public['expire_date']['enabled']) {
$public['expire_date']['days'] = $this->shareManager->shareApiLinkDefaultExpireDays();
$public['expire_date']['enforced'] = $this->shareManager->shareApiLinkDefaultExpireDateEnforced();
}
$public['expire_date_internal'] = [];
$public['expire_date_internal']['enabled'] = $this->shareManager->shareApiInternalDefaultExpireDate();
if ($public['expire_date_internal']['enabled']) {
$public['expire_date_internal']['days'] = $this->shareManager->shareApiInternalDefaultExpireDays();
$public['expire_date_internal']['enforced'] = $this->shareManager->shareApiInternalDefaultExpireDateEnforced();
}
$public['expire_date_remote'] = [];
$public['expire_date_remote']['enabled'] = $this->shareManager->shareApiRemoteDefaultExpireDate();
if ($public['expire_date_remote']['enabled']) {
$public['expire_date_remote']['days'] = $this->shareManager->shareApiRemoteDefaultExpireDays();
$public['expire_date_remote']['enforced'] = $this->shareManager->shareApiRemoteDefaultExpireDateEnforced();
}
$public['send_mail'] = $this->config->getAppValue('core', 'shareapi_allow_public_notification', 'no') === 'yes';
$public['upload'] = $this->shareManager->shareApiLinkAllowPublicUpload();
$public['upload_files_drop'] = $public['upload'];
}
$res['public'] = $public;
$res['resharing'] = $this->config->getAppValue('core', 'shareapi_allow_resharing', 'yes') === 'yes';
$res['user']['send_mail'] = false;
$res['user']['expire_date']['enabled'] = true;
// deprecated in favour of 'group', but we need to keep it for now
// in order to stay compatible with older clients
$res['group_sharing'] = $this->shareManager->allowGroupSharing();
$res['group'] = [];
$res['group']['enabled'] = $this->shareManager->allowGroupSharing();
$res['group']['expire_date']['enabled'] = true;
$res['default_permissions'] = (int)$this->config->getAppValue('core', 'shareapi_default_permissions', (string)Constants::PERMISSION_ALL);
}
//Federated sharing
$res['federation'] = [
'outgoing' => $this->shareManager->outgoingServer2ServerSharesAllowed(),
'incoming' => $this->config->getAppValue('files_sharing', 'incoming_server2server_share_enabled', 'yes') === 'yes',
// old bogus one, expire_date was not working before, keeping for compatibility
'expire_date' => ['enabled' => true],
// the real deal, signifies that expiration date can be set on federated shares
'expire_date_supported' => ['enabled' => true],
];
// Sharee searches
$res['sharee'] = [
'query_lookup_default' => $this->config->getSystemValueBool('gs.enabled', false),
'always_show_unique' => $this->config->getAppValue('files_sharing', 'always_show_unique', 'yes') === 'yes',
];
return [
'files_sharing' => $res,
];
}
}
@@ -0,0 +1,105 @@
<?php
/**
* @copyright Copyright (c) 2017 Arthur Schiwon <blizzz@arthur-schiwon.de>
*
* @author Arthur Schiwon <blizzz@arthur-schiwon.de>
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @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 <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Files_Sharing\Collaboration;
use OCP\Collaboration\AutoComplete\ISorter;
use OCP\Files\IRootFolder;
use OCP\Files\Node;
use OCP\IUserSession;
use OCP\Share\IManager;
class ShareRecipientSorter implements ISorter {
private IManager $shareManager;
private IRootFolder $rootFolder;
private IUserSession $userSession;
public function __construct(IManager $shareManager, IRootFolder $rootFolder, IUserSession $userSession) {
$this->shareManager = $shareManager;
$this->rootFolder = $rootFolder;
$this->userSession = $userSession;
}
public function getId(): string {
return 'share-recipients';
}
public function sort(array &$sortArray, array $context) {
// let's be tolerant. Comments uses "files" by default, other usages are often singular
if ($context['itemType'] !== 'files' && $context['itemType'] !== 'file') {
return;
}
$user = $this->userSession->getUser();
if ($user === null) {
return;
}
$userFolder = $this->rootFolder->getUserFolder($user->getUID());
/** @var Node[] $nodes */
$nodes = $userFolder->getById((int)$context['itemId']);
if (count($nodes) === 0) {
return;
}
$al = $this->shareManager->getAccessList($nodes[0]);
foreach ($sortArray as $type => &$byType) {
if (!isset($al[$type]) || !is_array($al[$type])) {
continue;
}
// at least on PHP 5.6 usort turned out to be not stable. So we add
// the current index to the value and compare it on a draw
$i = 0;
$workArray = array_map(function ($element) use (&$i) {
return [$i++, $element];
}, $byType);
usort($workArray, function ($a, $b) use ($al, $type) {
$result = $this->compare($a[1], $b[1], $al[$type]);
if ($result === 0) {
$result = $a[0] - $b[0];
}
return $result;
});
// and remove the index values again
$byType = array_column($workArray, 1);
}
}
/**
* @param array $a
* @param array $b
* @param array $al
* @return int
*/
protected function compare(array $a, array $b, array $al) {
$a = $a['value']['shareWith'];
$b = $b['value']['shareWith'];
$valueA = (int)in_array($a, $al, true);
$valueB = (int)in_array($b, $al, true);
return $valueB - $valueA;
}
}
@@ -0,0 +1,196 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud GmbH.
*
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Daniel Calviño Sánchez <danxuliu@gmail.com>
* @author Joas Schilling <coding@schilljs.com>
* @author Jörn Friedrich Dreyer <jfd@butonic.de>
* @author Roeland Jago Douma <roeland@famdouma.nl>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* 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, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OCA\Files_Sharing\Command;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\Federation\ICloudIdManager;
use OCP\IDBConnection;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Cleanup 'shared::' storage entries that have no matching entries in the
* shares_external table.
*/
class CleanupRemoteStorages extends Command {
/**
* @var IDBConnection
*/
protected $connection;
/**
* @var ICloudIdManager
*/
private $cloudIdManager;
public function __construct(IDBConnection $connection, ICloudIdManager $cloudIdManager) {
$this->connection = $connection;
$this->cloudIdManager = $cloudIdManager;
parent::__construct();
}
protected function configure() {
$this
->setName('sharing:cleanup-remote-storages')
->setDescription('Cleanup shared storage entries that have no matching entry in the shares_external table')
->addOption(
'dry-run',
null,
InputOption::VALUE_NONE,
'only show which storages would be deleted'
);
}
public function execute(InputInterface $input, OutputInterface $output): int {
$remoteStorages = $this->getRemoteStorages();
$output->writeln(count($remoteStorages) . ' remote storage(s) need(s) to be checked');
$remoteShareIds = $this->getRemoteShareIds();
$output->writeln(count($remoteShareIds) . ' remote share(s) exist');
foreach ($remoteShareIds as $id => $remoteShareId) {
if (isset($remoteStorages[$remoteShareId])) {
if ($input->getOption('dry-run') || $output->isVerbose()) {
$output->writeln("<info>$remoteShareId belongs to remote share $id</info>");
}
unset($remoteStorages[$remoteShareId]);
} else {
$output->writeln("<comment>$remoteShareId for share $id has no matching storage, yet</comment>");
}
}
if (empty($remoteStorages)) {
$output->writeln('<info>no storages deleted</info>');
} else {
$dryRun = $input->getOption('dry-run');
foreach ($remoteStorages as $id => $numericId) {
if ($dryRun) {
$output->writeln("<error>$id [$numericId] can be deleted</error>");
$this->countFiles($numericId, $output);
} else {
$this->deleteStorage($id, $numericId, $output);
}
}
}
return 0;
}
public function countFiles($numericId, OutputInterface $output) {
$queryBuilder = $this->connection->getQueryBuilder();
$queryBuilder->select($queryBuilder->func()->count('fileid'))
->from('filecache')
->where($queryBuilder->expr()->eq(
'storage',
$queryBuilder->createNamedParameter($numericId, IQueryBuilder::PARAM_STR),
IQueryBuilder::PARAM_STR)
);
$result = $queryBuilder->executeQuery();
$count = $result->fetchOne();
$result->closeCursor();
$output->writeln("$count files can be deleted for storage $numericId");
}
public function deleteStorage($id, $numericId, OutputInterface $output) {
$queryBuilder = $this->connection->getQueryBuilder();
$queryBuilder->delete('storages')
->where($queryBuilder->expr()->eq(
'id',
$queryBuilder->createNamedParameter($id, IQueryBuilder::PARAM_STR),
IQueryBuilder::PARAM_STR)
);
$output->write("deleting $id [$numericId] ... ");
$count = $queryBuilder->executeStatement();
$output->writeln("deleted $count storage");
$this->deleteFiles($numericId, $output);
}
public function deleteFiles($numericId, OutputInterface $output) {
$queryBuilder = $this->connection->getQueryBuilder();
$queryBuilder->delete('filecache')
->where($queryBuilder->expr()->eq(
'storage',
$queryBuilder->createNamedParameter($numericId, IQueryBuilder::PARAM_STR),
IQueryBuilder::PARAM_STR)
);
$output->write("deleting files for storage $numericId ... ");
$count = $queryBuilder->executeStatement();
$output->writeln("deleted $count files");
}
public function getRemoteStorages() {
$queryBuilder = $this->connection->getQueryBuilder();
$queryBuilder->select(['id', 'numeric_id'])
->from('storages')
->where($queryBuilder->expr()->like(
'id',
// match all 'shared::' + 32 characters storages
$queryBuilder->createNamedParameter($this->connection->escapeLikeParameter('shared::') . str_repeat('_', 32)),
IQueryBuilder::PARAM_STR)
)
->andWhere($queryBuilder->expr()->notLike(
'id',
// but not the ones starting with a '/', they are for normal shares
$queryBuilder->createNamedParameter($this->connection->escapeLikeParameter('shared::/') . '%'),
IQueryBuilder::PARAM_STR)
)
->orderBy('numeric_id');
$result = $queryBuilder->executeQuery();
$remoteStorages = [];
while ($row = $result->fetch()) {
$remoteStorages[$row['id']] = $row['numeric_id'];
}
$result->closeCursor();
return $remoteStorages;
}
public function getRemoteShareIds() {
$queryBuilder = $this->connection->getQueryBuilder();
$queryBuilder->select(['id', 'share_token', 'owner', 'remote'])
->from('share_external');
$result = $queryBuilder->executeQuery();
$remoteShareIds = [];
while ($row = $result->fetch()) {
$cloudId = $this->cloudIdManager->getCloudId($row['owner'], $row['remote']);
$remote = $cloudId->getRemote();
$remoteShareIds[$row['id']] = 'shared::' . md5($row['share_token'] . '@' . $remote);
}
$result->closeCursor();
return $remoteShareIds;
}
}
@@ -0,0 +1,95 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2023 Robin Appelman <robin@icewind.nl>
*
* @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 <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Files_Sharing\Command;
use OC\Core\Command\Base;
use OCA\Files_Sharing\OrphanHelper;
use Symfony\Component\Console\Helper\QuestionHelper;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ConfirmationQuestion;
class DeleteOrphanShares extends Base {
private OrphanHelper $orphanHelper;
public function __construct(OrphanHelper $orphanHelper) {
parent::__construct();
$this->orphanHelper = $orphanHelper;
}
protected function configure(): void {
$this
->setName('sharing:delete-orphan-shares')
->setDescription('Delete shares where the owner no longer has access to the file')
->addOption(
'force',
'f',
InputOption::VALUE_NONE,
'delete the shares without asking'
);
}
public function execute(InputInterface $input, OutputInterface $output): int {
$force = $input->getOption('force');
$shares = $this->orphanHelper->getAllShares();
$orphans = [];
foreach ($shares as $share) {
if (!$this->orphanHelper->isShareValid($share['owner'], $share['fileid'])) {
$orphans[] = $share['id'];
$exists = $this->orphanHelper->fileExists($share['fileid']);
$output->writeln("<info>{$share['target']}</info> owned by <info>{$share['owner']}</info>");
if ($exists) {
$output->writeln(" file still exists but the share owner lost access to it, run <info>occ info:file {$share['fileid']}</info> for more information about the file");
} else {
$output->writeln(" file no longer exists");
}
}
}
$count = count($orphans);
if ($count === 0) {
$output->writeln("No orphan shares detected");
return 0;
}
if ($force) {
$doDelete = true;
} else {
$output->writeln("");
/** @var QuestionHelper $helper */
$helper = $this->getHelper('question');
$question = new ConfirmationQuestion("Delete <info>$count</info> orphan shares? [y/N] ", false);
$doDelete = $helper->ask($input, $output, $question);
}
if ($doDelete) {
$this->orphanHelper->deleteShares($orphans);
}
return 0;
}
}
@@ -0,0 +1,99 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2019, Roeland Jago Douma <roeland@famdouma.nl>
*
* @author Joas Schilling <coding@schilljs.com>
* @author Roeland Jago Douma <roeland@famdouma.nl>
*
* @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 <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Files_Sharing\Command;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\IDBConnection;
use OCP\Notification\IManager as NotificationManager;
use OCP\Share\IManager as ShareManager;
use OCP\Share\IShare;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class ExiprationNotification extends Command {
/** @var NotificationManager */
private $notificationManager;
/** @var IDBConnection */
private $connection;
/** @var ITimeFactory */
private $time;
/** @var ShareManager */
private $shareManager;
public function __construct(ITimeFactory $time,
NotificationManager $notificationManager,
IDBConnection $connection,
ShareManager $shareManager) {
parent::__construct();
$this->notificationManager = $notificationManager;
$this->connection = $connection;
$this->time = $time;
$this->shareManager = $shareManager;
}
protected function configure() {
$this
->setName('sharing:expiration-notification')
->setDescription('Notify share initiators when a share will expire the next day.');
}
public function execute(InputInterface $input, OutputInterface $output): int {
//Current time
$minTime = $this->time->getDateTime();
$minTime->add(new \DateInterval('P1D'));
$minTime->setTime(0, 0, 0);
$maxTime = clone $minTime;
$maxTime->setTime(23, 59, 59);
$shares = $this->shareManager->getAllShares();
$now = $this->time->getDateTime();
/** @var IShare $share */
foreach ($shares as $share) {
if ($share->getExpirationDate() === null
|| $share->getExpirationDate()->getTimestamp() < $minTime->getTimestamp()
|| $share->getExpirationDate()->getTimestamp() > $maxTime->getTimestamp()) {
continue;
}
$notification = $this->notificationManager->createNotification();
$notification->setApp('files_sharing')
->setDateTime($now)
->setObject('share', $share->getFullId())
->setSubject('expiresTomorrow');
// Only send to initiator for now
$notification->setUser($share->getSharedBy());
$this->notificationManager->notify($notification);
}
return 0;
}
}
@@ -0,0 +1,87 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2020, Roeland Jago Douma <roeland@famdouma.nl>
*
* @author Roeland Jago Douma <roeland@famdouma.nl>
* @author Kate Döen <kate.doeen@nextcloud.com>
*
* @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 <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Files_Sharing\Controller;
use OCA\Files_Sharing\AppInfo\Application;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\Attribute\OpenAPI;
use OCP\AppFramework\Http\NotFoundResponse;
use OCP\AppFramework\Http\RedirectResponse;
use OCP\AppFramework\Http\Response;
use OCP\IRequest;
use OCP\IURLGenerator;
use OCP\IUserSession;
use OCP\Share\Exceptions\ShareNotFound;
use OCP\Share\IManager as ShareManager;
#[OpenAPI(scope: OpenAPI::SCOPE_IGNORE)]
class AcceptController extends Controller {
/** @var ShareManager */
private $shareManager;
/** @var IUserSession */
private $userSession;
/** @var IURLGenerator */
private $urlGenerator;
public function __construct(IRequest $request, ShareManager $shareManager, IUserSession $userSession, IURLGenerator $urlGenerator) {
parent::__construct(Application::APP_ID, $request);
$this->shareManager = $shareManager;
$this->userSession = $userSession;
$this->urlGenerator = $urlGenerator;
}
/**
* @NoAdminRequired
* @NoCSRFRequired
*/
public function accept(string $shareId): Response {
try {
$share = $this->shareManager->getShareById($shareId);
} catch (ShareNotFound $e) {
return new NotFoundResponse();
}
$user = $this->userSession->getUser();
if ($user === null) {
return new NotFoundResponse();
}
try {
$share = $this->shareManager->acceptShare($share, $user->getUID());
} catch (\Exception $e) {
// Just ignore
}
$url = $this->urlGenerator->linkToRouteAbsolute('files.viewcontroller.showFile', ['fileid' => $share->getNode()->getId()]);
return new RedirectResponse($url);
}
}
@@ -0,0 +1,291 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2016 Roeland Jago Douma <roeland@famdouma.nl>
*
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Daniel Calviño Sánchez <danxuliu@gmail.com>
* @author Joas Schilling <coding@schilljs.com>
* @author John Molakvoæ <skjnldsv@protonmail.com>
* @author Julius Härtl <jus@bitgrid.net>
* @author Roeland Jago Douma <roeland@famdouma.nl>
* @author Kate Döen <kate.doeen@nextcloud.com>
*
* @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 <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Files_Sharing\Controller;
use OCA\Files_Sharing\ResponseDefinitions;
use OCP\App\IAppManager;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\OCS\OCSException;
use OCP\AppFramework\OCS\OCSNotFoundException;
use OCP\AppFramework\OCSController;
use OCP\AppFramework\QueryException;
use OCP\Files\IRootFolder;
use OCP\Files\NotFoundException;
use OCP\IGroupManager;
use OCP\IRequest;
use OCP\IServerContainer;
use OCP\IUserManager;
use OCP\Share\Exceptions\GenericShareException;
use OCP\Share\Exceptions\ShareNotFound;
use OCP\Share\IManager as ShareManager;
use OCP\Share\IShare;
/**
* @psalm-import-type Files_SharingDeletedShare from ResponseDefinitions
*/
class DeletedShareAPIController extends OCSController {
/** @var ShareManager */
private $shareManager;
/** @var string */
private $userId;
/** @var IUserManager */
private $userManager;
/** @var IGroupManager */
private $groupManager;
/** @var IRootFolder */
private $rootFolder;
/** @var IAppManager */
private $appManager;
/** @var IServerContainer */
private $serverContainer;
public function __construct(string $appName,
IRequest $request,
ShareManager $shareManager,
string $UserId,
IUserManager $userManager,
IGroupManager $groupManager,
IRootFolder $rootFolder,
IAppManager $appManager,
IServerContainer $serverContainer) {
parent::__construct($appName, $request);
$this->shareManager = $shareManager;
$this->userId = $UserId;
$this->userManager = $userManager;
$this->groupManager = $groupManager;
$this->rootFolder = $rootFolder;
$this->appManager = $appManager;
$this->serverContainer = $serverContainer;
}
/**
* @suppress PhanUndeclaredClassMethod
*
* @return Files_SharingDeletedShare
*/
private function formatShare(IShare $share): array {
$result = [
'id' => $share->getFullId(),
'share_type' => $share->getShareType(),
'uid_owner' => $share->getSharedBy(),
'displayname_owner' => $this->userManager->get($share->getSharedBy())->getDisplayName(),
'permissions' => 0,
'stime' => $share->getShareTime()->getTimestamp(),
'parent' => null,
'expiration' => null,
'token' => null,
'uid_file_owner' => $share->getShareOwner(),
'displayname_file_owner' => $this->userManager->get($share->getShareOwner())->getDisplayName(),
'path' => $share->getTarget(),
];
$userFolder = $this->rootFolder->getUserFolder($share->getSharedBy());
$nodes = $userFolder->getById($share->getNodeId());
if (empty($nodes)) {
// fallback to guessing the path
$node = $userFolder->get($share->getTarget());
if ($node === null || $share->getTarget() === '') {
throw new NotFoundException();
}
} else {
$node = $nodes[0];
}
$result['path'] = $userFolder->getRelativePath($node->getPath());
if ($node instanceof \OCP\Files\Folder) {
$result['item_type'] = 'folder';
} else {
$result['item_type'] = 'file';
}
$result['mimetype'] = $node->getMimetype();
$result['storage_id'] = $node->getStorage()->getId();
$result['storage'] = $node->getStorage()->getCache()->getNumericStorageId();
$result['item_source'] = $node->getId();
$result['file_source'] = $node->getId();
$result['file_parent'] = $node->getParent()->getId();
$result['file_target'] = $share->getTarget();
$result['item_size'] = $node->getSize();
$result['item_mtime'] = $node->getMTime();
$expiration = $share->getExpirationDate();
if ($expiration !== null) {
$result['expiration'] = $expiration->format('Y-m-d 00:00:00');
}
if ($share->getShareType() === IShare::TYPE_GROUP) {
$group = $this->groupManager->get($share->getSharedWith());
$result['share_with'] = $share->getSharedWith();
$result['share_with_displayname'] = $group !== null ? $group->getDisplayName() : $share->getSharedWith();
} elseif ($share->getShareType() === IShare::TYPE_ROOM) {
$result['share_with'] = $share->getSharedWith();
$result['share_with_displayname'] = '';
try {
$result = array_merge($result, $this->getRoomShareHelper()->formatShare($share));
} catch (QueryException $e) {
}
} elseif ($share->getShareType() === IShare::TYPE_DECK) {
$result['share_with'] = $share->getSharedWith();
$result['share_with_displayname'] = '';
try {
$result = array_merge($result, $this->getDeckShareHelper()->formatShare($share));
} catch (QueryException $e) {
}
} elseif ($share->getShareType() === IShare::TYPE_SCIENCEMESH) {
$result['share_with'] = $share->getSharedWith();
$result['share_with_displayname'] = '';
try {
$result = array_merge($result, $this->getSciencemeshShareHelper()->formatShare($share));
} catch (QueryException $e) {
}
}
return $result;
}
/**
* @NoAdminRequired
*
* Get a list of all deleted shares
*
* @return DataResponse<Http::STATUS_OK, Files_SharingDeletedShare[], array{}>
*
* 200: Deleted shares returned
*/
public function index(): DataResponse {
$groupShares = $this->shareManager->getDeletedSharedWith($this->userId, IShare::TYPE_GROUP, null, -1, 0);
$roomShares = $this->shareManager->getDeletedSharedWith($this->userId, IShare::TYPE_ROOM, null, -1, 0);
$deckShares = $this->shareManager->getDeletedSharedWith($this->userId, IShare::TYPE_DECK, null, -1, 0);
$sciencemeshShares = $this->shareManager->getDeletedSharedWith($this->userId, IShare::TYPE_SCIENCEMESH, null, -1, 0);
$shares = array_merge($groupShares, $roomShares, $deckShares, $sciencemeshShares);
$shares = array_map(function (IShare $share) {
return $this->formatShare($share);
}, $shares);
return new DataResponse($shares);
}
/**
* @NoAdminRequired
*
* Undelete a deleted share
*
* @param string $id ID of the share
* @return DataResponse<Http::STATUS_OK, array<empty>, array{}>
* @throws OCSException
* @throws OCSNotFoundException Share not found
*
* 200: Share undeleted successfully
*/
public function undelete(string $id): DataResponse {
try {
$share = $this->shareManager->getShareById($id, $this->userId);
} catch (ShareNotFound $e) {
throw new OCSNotFoundException('Share not found');
}
if ($share->getPermissions() !== 0) {
throw new OCSNotFoundException('No deleted share found');
}
try {
$this->shareManager->restoreShare($share, $this->userId);
} catch (GenericShareException $e) {
throw new OCSException('Something went wrong');
}
return new DataResponse([]);
}
/**
* Returns the helper of DeletedShareAPIController for room shares.
*
* If the Talk application is not enabled or the helper is not available
* a QueryException is thrown instead.
*
* @return \OCA\Talk\Share\Helper\DeletedShareAPIController
* @throws QueryException
*/
private function getRoomShareHelper() {
if (!$this->appManager->isEnabledForUser('spreed')) {
throw new QueryException();
}
return $this->serverContainer->get('\OCA\Talk\Share\Helper\DeletedShareAPIController');
}
/**
* Returns the helper of DeletedShareAPIHelper for deck shares.
*
* If the Deck application is not enabled or the helper is not available
* a QueryException is thrown instead.
*
* @return \OCA\Deck\Sharing\ShareAPIHelper
* @throws QueryException
*/
private function getDeckShareHelper() {
if (!$this->appManager->isEnabledForUser('deck')) {
throw new QueryException();
}
return $this->serverContainer->get('\OCA\Deck\Sharing\ShareAPIHelper');
}
/**
* Returns the helper of DeletedShareAPIHelper for sciencemesh shares.
*
* If the sciencemesh application is not enabled or the helper is not available
* a QueryException is thrown instead.
*
* @return \OCA\Deck\Sharing\ShareAPIHelper
* @throws QueryException
*/
private function getSciencemeshShareHelper() {
if (!$this->appManager->isEnabledForUser('sciencemesh')) {
throw new QueryException();
}
return $this->serverContainer->get('\OCA\ScienceMesh\Sharing\ShareAPIHelper');
}
}
@@ -0,0 +1,143 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Björn Schießle <bjoern@schiessle.org>
* @author Joas Schilling <coding@schilljs.com>
* @author Lukas Reschke <lukas@statuscode.ch>
* @author Robin Appelman <robin@icewind.nl>
* @author Roeland Jago Douma <roeland@famdouma.nl>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* 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, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OCA\Files_Sharing\Controller;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\Http\JSONResponse;
use OCP\Http\Client\IClientService;
use OCP\IConfig;
use OCP\IRequest;
/**
* Class ExternalSharesController
*
* @package OCA\Files_Sharing\Controller
*/
class ExternalSharesController extends Controller {
public function __construct(
string $appName,
IRequest $request,
private \OCA\Files_Sharing\External\Manager $externalManager,
private IClientService $clientService,
private IConfig $config,
) {
parent::__construct($appName, $request);
}
/**
* @NoAdminRequired
* @NoOutgoingFederatedSharingRequired
*
* @return JSONResponse
*/
public function index() {
return new JSONResponse($this->externalManager->getOpenShares());
}
/**
* @NoAdminRequired
* @NoOutgoingFederatedSharingRequired
*
* @param int $id
* @return JSONResponse
*/
public function create($id) {
$this->externalManager->acceptShare($id);
return new JSONResponse();
}
/**
* @NoAdminRequired
* @NoOutgoingFederatedSharingRequired
*
* @param integer $id
* @return JSONResponse
*/
public function destroy($id) {
$this->externalManager->declineShare($id);
return new JSONResponse();
}
/**
* Test whether the specified remote is accessible
*
* @param string $remote
* @param bool $checkVersion
* @return bool
*/
protected function testUrl($remote, $checkVersion = false) {
try {
$client = $this->clientService->newClient();
$response = json_decode($client->get(
$remote,
[
'timeout' => 3,
'connect_timeout' => 3,
'verify' => !$this->config->getSystemValueBool('sharing.federation.allowSelfSignedCertificates', false),
]
)->getBody());
if ($checkVersion) {
return !empty($response->version) && version_compare($response->version, '7.0.0', '>=');
} else {
return is_object($response);
}
} catch (\Exception $e) {
return false;
}
}
/**
* @PublicPage
* @NoOutgoingFederatedSharingRequired
* @NoIncomingFederatedSharingRequired
*
* @param string $remote
* @return DataResponse
*/
public function testRemote($remote) {
if (str_contains($remote, '#') || str_contains($remote, '?') || str_contains($remote, ';')) {
return new DataResponse(false);
}
if (
$this->testUrl('https://' . $remote . '/ocm-provider/') ||
$this->testUrl('https://' . $remote . '/ocm-provider/index.php') ||
$this->testUrl('https://' . $remote . '/status.php', true)
) {
return new DataResponse('https');
} elseif (
$this->testUrl('http://' . $remote . '/ocm-provider/') ||
$this->testUrl('http://' . $remote . '/ocm-provider/index.php') ||
$this->testUrl('http://' . $remote . '/status.php', true)
) {
return new DataResponse('http');
} else {
return new DataResponse(false);
}
}
}
@@ -0,0 +1,205 @@
<?php
/**
* @copyright Copyright (c) 2016, Roeland Jago Douma <roeland@famdouma.nl>
*
* @author Julius Härtl <jus@bitgrid.net>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Roeland Jago Douma <roeland@famdouma.nl>
* @author Kate Döen <kate.doeen@nextcloud.com>
*
* @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 <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Files_Sharing\Controller;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\Http\FileDisplayResponse;
use OCP\AppFramework\PublicShareController;
use OCP\Constants;
use OCP\Files\Folder;
use OCP\Files\NotFoundException;
use OCP\IPreview;
use OCP\IRequest;
use OCP\ISession;
use OCP\Share\Exceptions\ShareNotFound;
use OCP\Share\IManager as ShareManager;
use OCP\Share\IShare;
class PublicPreviewController extends PublicShareController {
/** @var ShareManager */
private $shareManager;
/** @var IPreview */
private $previewManager;
/** @var IShare */
private $share;
public function __construct(string $appName,
IRequest $request,
ShareManager $shareManger,
ISession $session,
IPreview $previewManager) {
parent::__construct($appName, $request, $session);
$this->shareManager = $shareManger;
$this->previewManager = $previewManager;
}
protected function getPasswordHash(): string {
return $this->share->getPassword();
}
public function isValidToken(): bool {
try {
$this->share = $this->shareManager->getShareByToken($this->getToken());
return true;
} catch (ShareNotFound $e) {
return false;
}
}
protected function isPasswordProtected(): bool {
return $this->share->getPassword() !== null;
}
/**
* @PublicPage
* @NoCSRFRequired
*
* Get a preview for a shared file
*
* @param string $token Token of the share
* @param string $file File in the share
* @param int $x Width of the preview
* @param int $y Height of the preview
* @param bool $a Whether to not crop the preview
* @return FileDisplayResponse<Http::STATUS_OK, array{Content-Type: string}>|DataResponse<Http::STATUS_BAD_REQUEST|Http::STATUS_FORBIDDEN|Http::STATUS_NOT_FOUND, array<empty>, array{}>
*
* 200: Preview returned
* 400: Getting preview is not possible
* 403: Getting preview is not allowed
* 404: Share or preview not found
*/
public function getPreview(
string $token,
string $file = '',
int $x = 32,
int $y = 32,
$a = false
) {
if ($token === '' || $x === 0 || $y === 0) {
return new DataResponse([], Http::STATUS_BAD_REQUEST);
}
try {
$share = $this->shareManager->getShareByToken($token);
} catch (ShareNotFound $e) {
return new DataResponse([], Http::STATUS_NOT_FOUND);
}
if (($share->getPermissions() & Constants::PERMISSION_READ) === 0) {
return new DataResponse([], Http::STATUS_FORBIDDEN);
}
$attributes = $share->getAttributes();
if ($attributes !== null && $attributes->getAttribute('permissions', 'download') === false) {
return new DataResponse([], Http::STATUS_FORBIDDEN);
}
try {
$node = $share->getNode();
if ($node instanceof Folder) {
$file = $node->get($file);
} else {
$file = $node;
}
$f = $this->previewManager->getPreview($file, $x, $y, !$a);
$response = new FileDisplayResponse($f, Http::STATUS_OK, ['Content-Type' => $f->getMimeType()]);
$response->cacheFor(3600 * 24);
return $response;
} catch (NotFoundException $e) {
return new DataResponse([], Http::STATUS_NOT_FOUND);
} catch (\InvalidArgumentException $e) {
return new DataResponse([], Http::STATUS_BAD_REQUEST);
}
}
/**
* @PublicPage
* @NoCSRFRequired
* @NoSameSiteCookieRequired
*
* Get a direct link preview for a shared file
*
* @param string $token Token of the share
* @return FileDisplayResponse<Http::STATUS_OK, array{Content-Type: string}>|DataResponse<Http::STATUS_BAD_REQUEST|Http::STATUS_FORBIDDEN|Http::STATUS_NOT_FOUND, array<empty>, array{}>
*
* 200: Preview returned
* 400: Getting preview is not possible
* 403: Getting preview is not allowed
* 404: Share or preview not found
*/
public function directLink(string $token) {
// No token no image
if ($token === '') {
return new DataResponse([], Http::STATUS_BAD_REQUEST);
}
// No share no image
try {
$share = $this->shareManager->getShareByToken($token);
} catch (ShareNotFound $e) {
return new DataResponse([], Http::STATUS_NOT_FOUND);
}
// No permissions no image
if (($share->getPermissions() & Constants::PERMISSION_READ) === 0) {
return new DataResponse([], Http::STATUS_FORBIDDEN);
}
// Password protected shares have no direct link!
if ($share->getPassword() !== null) {
return new DataResponse([], Http::STATUS_FORBIDDEN);
}
$attributes = $share->getAttributes();
if ($attributes !== null && $attributes->getAttribute('permissions', 'download') === false) {
return new DataResponse([], Http::STATUS_FORBIDDEN);
}
try {
$node = $share->getNode();
if ($node instanceof Folder) {
// Direct link only works for single files
return new DataResponse([], Http::STATUS_BAD_REQUEST);
}
$f = $this->previewManager->getPreview($node, -1, -1, false);
$response = new FileDisplayResponse($f, Http::STATUS_OK, ['Content-Type' => $f->getMimeType()]);
$response->cacheFor(3600 * 24);
return $response;
} catch (NotFoundException $e) {
return new DataResponse([], Http::STATUS_NOT_FOUND);
} catch (\InvalidArgumentException $e) {
return new DataResponse([], Http::STATUS_BAD_REQUEST);
}
}
}
@@ -0,0 +1,202 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Bjoern Schiessle <bjoern@schiessle.org>
* @author Joas Schilling <coding@schilljs.com>
* @author Roeland Jago Douma <roeland@famdouma.nl>
* @author Kate Döen <kate.doeen@nextcloud.com>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* 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, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OCA\Files_Sharing\Controller;
use OCA\Files_Sharing\External\Manager;
use OCA\Files_Sharing\ResponseDefinitions;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\OCS\OCSForbiddenException;
use OCP\AppFramework\OCS\OCSNotFoundException;
use OCP\AppFramework\OCSController;
use OCP\IRequest;
use Psr\Log\LoggerInterface;
/**
* @psalm-import-type Files_SharingRemoteShare from ResponseDefinitions
*/
class RemoteController extends OCSController {
/**
* @NoAdminRequired
*
* Remote constructor.
*
* @param string $appName
* @param IRequest $request
* @param Manager $externalManager
*/
public function __construct(
$appName,
IRequest $request,
private Manager $externalManager,
private LoggerInterface $logger,
) {
parent::__construct($appName, $request);
}
/**
* @NoAdminRequired
*
* Get list of pending remote shares
*
* @return DataResponse<Http::STATUS_OK, Files_SharingRemoteShare[], array{}>
*
* 200: Pending remote shares returned
*/
public function getOpenShares() {
return new DataResponse($this->externalManager->getOpenShares());
}
/**
* @NoAdminRequired
*
* Accept a remote share
*
* @param int $id ID of the share
* @return DataResponse<Http::STATUS_OK, array<empty>, array{}>
* @throws OCSNotFoundException Share not found
*
* 200: Share accepted successfully
*/
public function acceptShare($id) {
if ($this->externalManager->acceptShare($id)) {
return new DataResponse();
}
$this->logger->error('Could not accept federated share with id: ' . $id,
['app' => 'files_sharing']);
throw new OCSNotFoundException('wrong share ID, share does not exist.');
}
/**
* @NoAdminRequired
*
* Decline a remote share
*
* @param int $id ID of the share
* @return DataResponse<Http::STATUS_OK, array<empty>, array{}>
* @throws OCSNotFoundException Share not found
*
* 200: Share declined successfully
*/
public function declineShare($id) {
if ($this->externalManager->declineShare($id)) {
return new DataResponse();
}
// Make sure the user has no notification for something that does not exist anymore.
$this->externalManager->processNotification($id);
throw new OCSNotFoundException('wrong share ID, share does not exist.');
}
/**
* @param array $share Share with info from the share_external table
* @return array enriched share info with data from the filecache
*/
private static function extendShareInfo($share) {
$view = new \OC\Files\View('/' . \OC_User::getUser() . '/files/');
$info = $view->getFileInfo($share['mountpoint']);
if ($info === false) {
return $share;
}
$share['mimetype'] = $info->getMimetype();
$share['mtime'] = $info->getMTime();
$share['permissions'] = $info->getPermissions();
$share['type'] = $info->getType();
$share['file_id'] = $info->getId();
return $share;
}
/**
* @NoAdminRequired
*
* Get a list of accepted remote shares
*
* @return DataResponse<Http::STATUS_OK, Files_SharingRemoteShare[], array{}>
*
* 200: Accepted remote shares returned
*/
public function getShares() {
$shares = $this->externalManager->getAcceptedShares();
$shares = array_map('self::extendShareInfo', $shares);
return new DataResponse($shares);
}
/**
* @NoAdminRequired
*
* Get info of a remote share
*
* @param int $id ID of the share
* @return DataResponse<Http::STATUS_OK, Files_SharingRemoteShare, array{}>
* @throws OCSNotFoundException Share not found
*
* 200: Share returned
*/
public function getShare($id) {
$shareInfo = $this->externalManager->getShare($id);
if ($shareInfo === false) {
throw new OCSNotFoundException('share does not exist');
} else {
$shareInfo = self::extendShareInfo($shareInfo);
return new DataResponse($shareInfo);
}
}
/**
* @NoAdminRequired
*
* Unshare a remote share
*
* @param int $id ID of the share
* @return DataResponse<Http::STATUS_OK, array<empty>, array{}>
* @throws OCSNotFoundException Share not found
* @throws OCSForbiddenException Unsharing is not possible
*
* 200: Share unshared successfully
*/
public function unshare($id) {
$shareInfo = $this->externalManager->getShare($id);
if ($shareInfo === false) {
throw new OCSNotFoundException('Share does not exist');
}
$mountPoint = '/' . \OC_User::getUser() . '/files' . $shareInfo['mountpoint'];
if ($this->externalManager->removeShare($mountPoint) === true) {
return new DataResponse();
} else {
throw new OCSForbiddenException('Could not unshare');
}
}
}
@@ -0,0 +1,75 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2019, Roeland Jago Douma <roeland@famdouma.nl>
*
* @author Roeland Jago Douma <roeland@famdouma.nl>
* @author Hinrich Mahler <nextcloud@mahlerhome.de>
*
* @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 <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Files_Sharing\Controller;
use OCA\Files_Sharing\AppInfo\Application;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\JSONResponse;
use OCP\IConfig;
use OCP\IRequest;
class SettingsController extends Controller {
/** @var IConfig */
private $config;
/** @var string */
private $userId;
public function __construct(IRequest $request,
IConfig $config,
string $userId) {
parent::__construct(Application::APP_ID, $request);
$this->config = $config;
$this->userId = $userId;
}
/**
* @NoAdminRequired
*/
public function setDefaultAccept(bool $accept): JSONResponse {
$this->config->setUserValue($this->userId, Application::APP_ID, 'default_accept', $accept ? 'yes' : 'no');
return new JSONResponse();
}
/**
* @NoAdminRequired
*/
public function setUserShareFolder(string $shareFolder): JSONResponse {
$this->config->setUserValue($this->userId, Application::APP_ID, 'share_folder', $shareFolder);
return new JSONResponse();
}
/**
* @NoAdminRequired
*/
public function resetUserShareFolder(): JSONResponse {
$this->config->deleteUserValue($this->userId, Application::APP_ID, 'share_folder');
return new JSONResponse();
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,584 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Arthur Schiwon <blizzz@arthur-schiwon.de>
* @author Bjoern Schiessle <bjoern@schiessle.org>
* @author Björn Schießle <bjoern@schiessle.org>
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Daniel Calviño Sánchez <danxuliu@gmail.com>
* @author Georg Ehrke <oc.list@georgehrke.com>
* @author j3l11234 <297259024@qq.com>
* @author Joas Schilling <coding@schilljs.com>
* @author John Molakvoæ <skjnldsv@protonmail.com>
* @author Jonas Sulzer <jonas@violoncello.ch>
* @author Julius Härtl <jus@bitgrid.net>
* @author Lukas Reschke <lukas@statuscode.ch>
* @author MartB <mart.b@outlook.de>
* @author Maxence Lange <maxence@pontapreta.net>
* @author Michael Weimann <mail@michael-weimann.eu>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Piotr Filiciak <piotr@filiciak.pl>
* @author Robin Appelman <robin@icewind.nl>
* @author Roeland Jago Douma <roeland@famdouma.nl>
* @author Sascha Sambale <mastixmc@gmail.com>
* @author Thomas Müller <thomas.mueller@tmit.eu>
* @author Vincent Petry <vincent@nextcloud.com>
* @author Kate Döen <kate.doeen@nextcloud.com>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* 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, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OCA\Files_Sharing\Controller;
use OC\Security\CSP\ContentSecurityPolicy;
use OC_Files;
use OC_Util;
use OCA\FederatedFileSharing\FederatedShareProvider;
use OCA\Files_Sharing\Activity\Providers\Downloads;
use OCA\Files_Sharing\Event\BeforeTemplateRenderedEvent;
use OCA\Files_Sharing\Event\ShareLinkAccessedEvent;
use OCP\Accounts\IAccountManager;
use OCP\AppFramework\AuthPublicShareController;
use OCP\AppFramework\Http\Attribute\OpenAPI;
use OCP\AppFramework\Http\NotFoundResponse;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\Defaults;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\Files\Folder;
use OCP\Files\IRootFolder;
use OCP\Files\NotFoundException;
use OCP\IConfig;
use OCP\IL10N;
use OCP\IPreview;
use OCP\IRequest;
use OCP\ISession;
use OCP\IURLGenerator;
use OCP\IUserManager;
use OCP\Security\ISecureRandom;
use OCP\Share;
use OCP\Share\Exceptions\ShareNotFound;
use OCP\Share\IManager as ShareManager;
use OCP\Share\IPublicShareTemplateFactory;
use OCP\Share\IShare;
use OCP\Template;
/**
* @package OCA\Files_Sharing\Controllers
*/
#[OpenAPI(scope: OpenAPI::SCOPE_IGNORE)]
class ShareController extends AuthPublicShareController {
protected ?Share\IShare $share = null;
public const SHARE_ACCESS = 'access';
public const SHARE_AUTH = 'auth';
public const SHARE_DOWNLOAD = 'download';
public function __construct(
string $appName,
IRequest $request,
protected IConfig $config,
IURLGenerator $urlGenerator,
protected IUserManager $userManager,
protected \OCP\Activity\IManager $activityManager,
protected ShareManager $shareManager,
ISession $session,
protected IPreview $previewManager,
protected IRootFolder $rootFolder,
protected FederatedShareProvider $federatedShareProvider,
protected IAccountManager $accountManager,
protected IEventDispatcher $eventDispatcher,
protected IL10N $l10n,
protected ISecureRandom $secureRandom,
protected Defaults $defaults,
private IPublicShareTemplateFactory $publicShareTemplateFactory,
) {
parent::__construct($appName, $request, $session, $urlGenerator);
}
/**
* @PublicPage
* @NoCSRFRequired
*
* Show the authentication page
* The form has to submit to the authenticate method route
*/
public function showAuthenticate(): TemplateResponse {
$templateParameters = ['share' => $this->share];
$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;
}
/**
* The template to show when authentication failed
*/
protected function showAuthFailed(): TemplateResponse {
$templateParameters = ['share' => $this->share, 'wrongpw' => true];
$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;
}
/**
* 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);
}
protected function getPasswordHash(): string {
return $this->share->getPassword();
}
public function isValidToken(): bool {
try {
$this->share = $this->shareManager->getShareByToken($this->getToken());
} catch (ShareNotFound $e) {
return false;
}
return true;
}
protected function isPasswordProtected(): bool {
return $this->share->getPassword() !== null;
}
protected function authSucceeded() {
// For share this was always set so it is still used in other apps
$this->session->set('public_link_authenticated', (string)$this->share->getId());
}
protected function authFailed() {
$this->emitAccessShareHook($this->share, 403, 'Wrong password');
$this->emitShareAccessEvent($this->share, self::SHARE_AUTH, 403, 'Wrong password');
}
/**
* throws hooks when a share is attempted to be accessed
*
* @param \OCP\Share\IShare|string $share the Share instance if available,
* otherwise token
* @param int $errorCode
* @param string $errorMessage
*
* @throws \OCP\HintException
* @throws \OC\ServerNotAvailableException
*
* @deprecated use OCP\Files_Sharing\Event\ShareLinkAccessedEvent
*/
protected function emitAccessShareHook($share, int $errorCode = 200, string $errorMessage = '') {
$itemType = $itemSource = $uidOwner = '';
$token = $share;
$exception = null;
if ($share instanceof \OCP\Share\IShare) {
try {
$token = $share->getToken();
$uidOwner = $share->getSharedBy();
$itemType = $share->getNodeType();
$itemSource = $share->getNodeId();
} catch (\Exception $e) {
// we log what we know and pass on the exception afterwards
$exception = $e;
}
}
\OC_Hook::emit(Share::class, 'share_link_access', [
'itemType' => $itemType,
'itemSource' => $itemSource,
'uidOwner' => $uidOwner,
'token' => $token,
'errorCode' => $errorCode,
'errorMessage' => $errorMessage
]);
if (!is_null($exception)) {
throw $exception;
}
}
/**
* Emit a ShareLinkAccessedEvent event when a share is accessed, downloaded, auth...
*/
protected function emitShareAccessEvent(IShare $share, string $step = '', int $errorCode = 200, string $errorMessage = ''): void {
if ($step !== self::SHARE_ACCESS &&
$step !== self::SHARE_AUTH &&
$step !== self::SHARE_DOWNLOAD) {
return;
}
$this->eventDispatcher->dispatchTyped(new ShareLinkAccessedEvent($share, $step, $errorCode, $errorMessage));
}
/**
* Validate the permissions of the share
*
* @param Share\IShare $share
* @return bool
*/
private function validateShare(\OCP\Share\IShare $share) {
// If the owner is disabled no access to the link is granted
$owner = $this->userManager->get($share->getShareOwner());
if ($owner === null || !$owner->isEnabled()) {
return false;
}
// If the initiator of the share is disabled no access is granted
$initiator = $this->userManager->get($share->getSharedBy());
if ($initiator === null || !$initiator->isEnabled()) {
return false;
}
return $share->getNode()->isReadable() && $share->getNode()->isShareable();
}
/**
* @PublicPage
* @NoCSRFRequired
*
*
* @param string $path
* @return TemplateResponse
* @throws NotFoundException
* @throws \Exception
*/
public function showShare($path = ''): TemplateResponse {
\OC_User::setIncognitoMode(true);
// Check whether share exists
try {
$share = $this->shareManager->getShareByToken($this->getToken());
} catch (ShareNotFound $e) {
// The share does not exists, we do not emit an ShareLinkAccessedEvent
$this->emitAccessShareHook($this->getToken(), 404, 'Share not found');
throw new NotFoundException();
}
if (!$this->validateShare($share)) {
throw new NotFoundException();
}
$shareNode = $share->getNode();
try {
$templateProvider = $this->publicShareTemplateFactory->getProvider($share);
$response = $templateProvider->renderPage($share, $this->getToken(), $path);
} catch (NotFoundException $e) {
$this->emitAccessShareHook($share, 404, 'Share not found');
$this->emitShareAccessEvent($share, ShareController::SHARE_ACCESS, 404, 'Share not found');
throw new NotFoundException();
}
// We can't get the path of a file share
try {
if ($shareNode instanceof \OCP\Files\File && $path !== '') {
$this->emitAccessShareHook($share, 404, 'Share not found');
$this->emitShareAccessEvent($share, self::SHARE_ACCESS, 404, 'Share not found');
throw new NotFoundException();
}
} catch (\Exception $e) {
$this->emitAccessShareHook($share, 404, 'Share not found');
$this->emitShareAccessEvent($share, self::SHARE_ACCESS, 404, 'Share not found');
throw $e;
}
$this->emitAccessShareHook($share);
$this->emitShareAccessEvent($share, self::SHARE_ACCESS);
return $response;
}
/**
* @PublicPage
* @NoCSRFRequired
* @NoSameSiteCookieRequired
*
* @param string $token
* @param string $files
* @param string $path
* @param string $downloadStartSecret
* @return void|\OCP\AppFramework\Http\Response
* @throws NotFoundException
*/
public function downloadShare($token, $files = null, $path = '', $downloadStartSecret = '') {
\OC_User::setIncognitoMode(true);
$share = $this->shareManager->getShareByToken($token);
if (!($share->getPermissions() & \OCP\Constants::PERMISSION_READ)) {
return new \OCP\AppFramework\Http\DataResponse('Share has no read permission');
}
$files_list = null;
if (!is_null($files)) { // download selected files
$files_list = json_decode($files);
// in case we get only a single file
if ($files_list === null) {
$files_list = [$files];
}
// Just in case $files is a single int like '1234'
if (!is_array($files_list)) {
$files_list = [$files_list];
}
}
if (!$this->validateShare($share)) {
throw new NotFoundException();
}
$userFolder = $this->rootFolder->getUserFolder($share->getShareOwner());
$originalSharePath = $userFolder->getRelativePath($share->getNode()->getPath());
// Single file share
if ($share->getNode() instanceof \OCP\Files\File) {
// Single file download
$this->singleFileDownloaded($share, $share->getNode());
}
// Directory share
else {
/** @var \OCP\Files\Folder $node */
$node = $share->getNode();
// Try to get the path
if ($path !== '') {
try {
$node = $node->get($path);
} catch (NotFoundException $e) {
$this->emitAccessShareHook($share, 404, 'Share not found');
$this->emitShareAccessEvent($share, self::SHARE_DOWNLOAD, 404, 'Share not found');
return new NotFoundResponse();
}
}
$originalSharePath = $userFolder->getRelativePath($node->getPath());
if ($node instanceof \OCP\Files\File) {
// Single file download
$this->singleFileDownloaded($share, $share->getNode());
} else {
try {
if (!empty($files_list)) {
$this->fileListDownloaded($share, $files_list, $node);
} else {
// The folder is downloaded
$this->singleFileDownloaded($share, $share->getNode());
}
} catch (NotFoundException $e) {
return new NotFoundResponse();
}
}
}
/* FIXME: We should do this all nicely in OCP */
OC_Util::tearDownFS();
OC_Util::setupFS($share->getShareOwner());
/**
* this sets a cookie to be able to recognize the start of the download
* the content must not be longer than 32 characters and must only contain
* alphanumeric characters
*/
if (!empty($downloadStartSecret)
&& !isset($downloadStartSecret[32])
&& preg_match('!^[a-zA-Z0-9]+$!', $downloadStartSecret) === 1) {
// FIXME: set on the response once we use an actual app framework response
setcookie('ocDownloadStarted', $downloadStartSecret, time() + 20, '/');
}
$this->emitAccessShareHook($share);
$this->emitShareAccessEvent($share, self::SHARE_DOWNLOAD);
$server_params = [ 'head' => $this->request->getMethod() === 'HEAD' ];
/**
* Http range requests support
*/
if (isset($_SERVER['HTTP_RANGE'])) {
$server_params['range'] = $this->request->getHeader('Range');
}
// download selected files
if (!is_null($files) && $files !== '') {
// FIXME: The exit is required here because otherwise the AppFramework is trying to add headers as well
// after dispatching the request which results in a "Cannot modify header information" notice.
OC_Files::get($originalSharePath, $files_list, $server_params);
exit();
} else {
// FIXME: The exit is required here because otherwise the AppFramework is trying to add headers as well
// after dispatching the request which results in a "Cannot modify header information" notice.
OC_Files::get(dirname($originalSharePath), basename($originalSharePath), $server_params);
exit();
}
}
/**
* create activity for every downloaded file
*
* @param Share\IShare $share
* @param array $files_list
* @param \OCP\Files\Folder $node
* @throws NotFoundException when trying to download a folder or multiple files of a "hide download" share
*/
protected function fileListDownloaded(Share\IShare $share, array $files_list, \OCP\Files\Folder $node) {
if ($share->getHideDownload() && count($files_list) > 1) {
throw new NotFoundException('Downloading more than 1 file');
}
foreach ($files_list as $file) {
$subNode = $node->get($file);
$this->singleFileDownloaded($share, $subNode);
}
}
/**
* create activity if a single file was downloaded from a link share
*
* @param Share\IShare $share
* @throws NotFoundException when trying to download a folder of a "hide download" share
*/
protected function singleFileDownloaded(Share\IShare $share, \OCP\Files\Node $node) {
if ($share->getHideDownload() && $node instanceof Folder) {
throw new NotFoundException('Downloading a folder');
}
$fileId = $node->getId();
$userFolder = $this->rootFolder->getUserFolder($share->getSharedBy());
$userNodeList = $userFolder->getById($fileId);
$userNode = $userNodeList[0];
$ownerFolder = $this->rootFolder->getUserFolder($share->getShareOwner());
$userPath = $userFolder->getRelativePath($userNode->getPath());
$ownerPath = $ownerFolder->getRelativePath($node->getPath());
$remoteAddress = $this->request->getRemoteAddress();
$dateTime = new \DateTime();
$dateTime = $dateTime->format('Y-m-d H');
$remoteAddressHash = md5($dateTime . '-' . $remoteAddress);
$parameters = [$userPath];
if ($share->getShareType() === IShare::TYPE_EMAIL) {
if ($node instanceof \OCP\Files\File) {
$subject = Downloads::SUBJECT_SHARED_FILE_BY_EMAIL_DOWNLOADED;
} else {
$subject = Downloads::SUBJECT_SHARED_FOLDER_BY_EMAIL_DOWNLOADED;
}
$parameters[] = $share->getSharedWith();
} else {
if ($node instanceof \OCP\Files\File) {
$subject = Downloads::SUBJECT_PUBLIC_SHARED_FILE_DOWNLOADED;
$parameters[] = $remoteAddressHash;
} else {
$subject = Downloads::SUBJECT_PUBLIC_SHARED_FOLDER_DOWNLOADED;
$parameters[] = $remoteAddressHash;
}
}
$this->publishActivity($subject, $parameters, $share->getSharedBy(), $fileId, $userPath);
if ($share->getShareOwner() !== $share->getSharedBy()) {
$parameters[0] = $ownerPath;
$this->publishActivity($subject, $parameters, $share->getShareOwner(), $fileId, $ownerPath);
}
}
/**
* publish activity
*
* @param string $subject
* @param array $parameters
* @param string $affectedUser
* @param int $fileId
* @param string $filePath
*/
protected function publishActivity($subject,
array $parameters,
$affectedUser,
$fileId,
$filePath) {
$event = $this->activityManager->generateEvent();
$event->setApp('files_sharing')
->setType('public_links')
->setSubject($subject, $parameters)
->setAffectedUser($affectedUser)
->setObject('files', $fileId, $filePath);
$this->activityManager->publish($event);
}
}
@@ -0,0 +1,171 @@
<?php
/**
* @copyright Copyright (c) 2016 Roeland Jago Douma <roeland@famdouma.nl>
*
* @author Morris Jobke <hey@morrisjobke.de>
* @author Roeland Jago Douma <roeland@famdouma.nl>
* @author Kate Döen <kate.doeen@nextcloud.com>
*
* @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 <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Files_Sharing\Controller;
use OCA\Files_External\NotFoundException;
use OCA\Files_Sharing\ResponseDefinitions;
use OCP\AppFramework\ApiController;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\JSONResponse;
use OCP\Constants;
use OCP\Files\File;
use OCP\Files\Folder;
use OCP\Files\Node;
use OCP\IRequest;
use OCP\Share\Exceptions\ShareNotFound;
use OCP\Share\IManager;
/**
* @psalm-import-type Files_SharingShareInfo from ResponseDefinitions
*/
class ShareInfoController extends ApiController {
/** @var IManager */
private $shareManager;
/**
* ShareInfoController constructor.
*
* @param string $appName
* @param IRequest $request
* @param IManager $shareManager
*/
public function __construct(string $appName,
IRequest $request,
IManager $shareManager) {
parent::__construct($appName, $request);
$this->shareManager = $shareManager;
}
/**
* @PublicPage
* @NoCSRFRequired
* @BruteForceProtection(action=shareinfo)
*
* Get the info about a share
*
* @param string $t Token of the share
* @param string|null $password Password of the share
* @param string|null $dir Subdirectory to get info about
* @param int $depth Maximum depth to get info about
* @return JSONResponse<Http::STATUS_OK, Files_SharingShareInfo, array{}>|JSONResponse<Http::STATUS_FORBIDDEN|Http::STATUS_NOT_FOUND, array<empty>, array{}>
*
* 200: Share info returned
* 403: Getting share info is not allowed
* 404: Share not found
*/
public function info(string $t, ?string $password = null, ?string $dir = null, int $depth = -1): JSONResponse {
try {
$share = $this->shareManager->getShareByToken($t);
} catch (ShareNotFound $e) {
$response = new JSONResponse([], Http::STATUS_NOT_FOUND);
$response->throttle(['token' => $t]);
return $response;
}
if ($share->getPassword() && !$this->shareManager->checkPassword($share, $password)) {
$response = new JSONResponse([], Http::STATUS_FORBIDDEN);
$response->throttle(['token' => $t]);
return $response;
}
if (!($share->getPermissions() & Constants::PERMISSION_READ)) {
$response = new JSONResponse([], Http::STATUS_FORBIDDEN);
$response->throttle(['token' => $t]);
return $response;
}
$permissionMask = $share->getPermissions();
$node = $share->getNode();
if ($dir !== null && $node instanceof Folder) {
try {
$node = $node->get($dir);
} catch (NotFoundException $e) {
}
}
return new JSONResponse($this->parseNode($node, $permissionMask, $depth));
}
/**
* @return Files_SharingShareInfo
*/
private function parseNode(Node $node, int $permissionMask, int $depth): array {
if ($node instanceof File) {
return $this->parseFile($node, $permissionMask);
}
/** @var Folder $node */
return $this->parseFolder($node, $permissionMask, $depth);
}
/**
* @return Files_SharingShareInfo
*/
private function parseFile(File $file, int $permissionMask): array {
return $this->format($file, $permissionMask);
}
/**
* @return Files_SharingShareInfo
*/
private function parseFolder(Folder $folder, int $permissionMask, int $depth): array {
$data = $this->format($folder, $permissionMask);
if ($depth === 0) {
return $data;
}
$data['children'] = [];
$nodes = $folder->getDirectoryListing();
foreach ($nodes as $node) {
$data['children'][] = $this->parseNode($node, $permissionMask, $depth <= -1 ? -1 : $depth - 1);
}
return $data;
}
/**
* @return Files_SharingShareInfo
*/
private function format(Node $node, int $permissionMask): array {
$entry = [];
$entry['id'] = $node->getId();
$entry['parentId'] = $node->getParent()->getId();
$entry['mtime'] = $node->getMTime();
$entry['name'] = $node->getName();
$entry['permissions'] = $node->getPermissions() & $permissionMask;
$entry['mimetype'] = $node->getMimetype();
$entry['size'] = $node->getSize();
$entry['type'] = $node->getType();
$entry['etag'] = $node->getEtag();
return $entry;
}
}
@@ -0,0 +1,452 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Arthur Schiwon <blizzz@arthur-schiwon.de>
* @author Bjoern Schiessle <bjoern@schiessle.org>
* @author Björn Schießle <bjoern@schiessle.org>
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Daniel Calviño Sánchez <danxuliu@gmail.com>
* @author Daniel Kesselberg <mail@danielkesselberg.de>
* @author J0WI <J0WI@users.noreply.github.com>
* @author Joas Schilling <coding@schilljs.com>
* @author John Molakvoæ <skjnldsv@protonmail.com>
* @author Julius Härtl <jus@bitgrid.net>
* @author Maxence Lange <maxence@nextcloud.com>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Robin Appelman <robin@icewind.nl>
* @author Roeland Jago Douma <roeland@famdouma.nl>
* @author Kate Döen <kate.doeen@nextcloud.com>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* 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, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OCA\Files_Sharing\Controller;
use Generator;
use OC\Collaboration\Collaborators\SearchResult;
use OCA\Files_Sharing\ResponseDefinitions;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\OCS\OCSBadRequestException;
use OCP\AppFramework\OCSController;
use OCP\Collaboration\Collaborators\ISearch;
use OCP\Collaboration\Collaborators\ISearchResult;
use OCP\Collaboration\Collaborators\SearchResultType;
use OCP\Constants;
use OCP\IConfig;
use OCP\IRequest;
use OCP\IURLGenerator;
use OCP\Share\IManager;
use OCP\Share\IShare;
use function array_slice;
use function array_values;
use function usort;
/**
* @psalm-import-type Files_SharingShareesSearchResult from ResponseDefinitions
* @psalm-import-type Files_SharingShareesRecommendedResult from ResponseDefinitions
*/
class ShareesAPIController extends OCSController {
/** @var string */
protected $userId;
/** @var IConfig */
protected $config;
/** @var IURLGenerator */
protected $urlGenerator;
/** @var IManager */
protected $shareManager;
/** @var int */
protected $offset = 0;
/** @var int */
protected $limit = 10;
/** @var Files_SharingShareesSearchResult */
protected $result = [
'exact' => [
'users' => [],
'groups' => [],
'remotes' => [],
'remote_groups' => [],
'emails' => [],
'circles' => [],
'rooms' => [],
],
'users' => [],
'groups' => [],
'remotes' => [],
'remote_groups' => [],
'emails' => [],
'lookup' => [],
'circles' => [],
'rooms' => [],
'lookupEnabled' => false,
];
protected $reachedEndFor = [];
/** @var ISearch */
private $collaboratorSearch;
/**
* @param string $UserId
* @param string $appName
* @param IRequest $request
* @param IConfig $config
* @param IURLGenerator $urlGenerator
* @param IManager $shareManager
* @param ISearch $collaboratorSearch
*/
public function __construct(
$UserId,
string $appName,
IRequest $request,
IConfig $config,
IURLGenerator $urlGenerator,
IManager $shareManager,
ISearch $collaboratorSearch
) {
parent::__construct($appName, $request);
$this->userId = $UserId;
$this->config = $config;
$this->urlGenerator = $urlGenerator;
$this->shareManager = $shareManager;
$this->collaboratorSearch = $collaboratorSearch;
}
/**
* @NoAdminRequired
*
* Search for sharees
*
* @param string $search Text to search for
* @param string|null $itemType Limit to specific item types
* @param int $page Page offset for searching
* @param int $perPage Limit amount of search results per page
* @param int|int[]|null $shareType Limit to specific share types
* @param bool $lookup If a global lookup should be performed too
* @return DataResponse<Http::STATUS_OK, Files_SharingShareesSearchResult, array{Link?: string}>
* @throws OCSBadRequestException Invalid search parameters
*
* 200: Sharees search result returned
*/
public function search(string $search = '', string $itemType = null, int $page = 1, int $perPage = 200, $shareType = null, bool $lookup = false): DataResponse {
// only search for string larger than a given threshold
$threshold = $this->config->getSystemValueInt('sharing.minSearchStringLength', 0);
if (strlen($search) < $threshold) {
return new DataResponse($this->result);
}
// never return more than the max. number of results configured in the config.php
$maxResults = $this->config->getSystemValueInt('sharing.maxAutocompleteResults', Constants::SHARING_MAX_AUTOCOMPLETE_RESULTS_DEFAULT);
if ($maxResults > 0) {
$perPage = min($perPage, $maxResults);
}
if ($perPage <= 0) {
throw new OCSBadRequestException('Invalid perPage argument');
}
if ($page <= 0) {
throw new OCSBadRequestException('Invalid page');
}
$shareTypes = [
IShare::TYPE_USER,
];
if ($itemType === null) {
throw new OCSBadRequestException('Missing itemType');
} elseif ($itemType === 'file' || $itemType === 'folder') {
if ($this->shareManager->allowGroupSharing()) {
$shareTypes[] = IShare::TYPE_GROUP;
}
if ($this->isRemoteSharingAllowed($itemType)) {
$shareTypes[] = IShare::TYPE_REMOTE;
}
if ($this->isRemoteGroupSharingAllowed($itemType)) {
$shareTypes[] = IShare::TYPE_REMOTE_GROUP;
}
if ($this->shareManager->shareProviderExists(IShare::TYPE_EMAIL)) {
$shareTypes[] = IShare::TYPE_EMAIL;
}
if ($this->shareManager->shareProviderExists(IShare::TYPE_ROOM)) {
$shareTypes[] = IShare::TYPE_ROOM;
}
if ($this->shareManager->shareProviderExists(IShare::TYPE_SCIENCEMESH)) {
$shareTypes[] = IShare::TYPE_SCIENCEMESH;
}
} else {
if ($this->shareManager->allowGroupSharing()) {
$shareTypes[] = IShare::TYPE_GROUP;
}
$shareTypes[] = IShare::TYPE_EMAIL;
}
// FIXME: DI
if (\OC::$server->getAppManager()->isEnabledForUser('circles') && class_exists('\OCA\Circles\ShareByCircleProvider')) {
$shareTypes[] = IShare::TYPE_CIRCLE;
}
if ($this->shareManager->shareProviderExists(IShare::TYPE_SCIENCEMESH)) {
$shareTypes[] = IShare::TYPE_SCIENCEMESH;
}
if ($shareType !== null && is_array($shareType)) {
$shareTypes = array_intersect($shareTypes, $shareType);
} elseif (is_numeric($shareType)) {
$shareTypes = array_intersect($shareTypes, [(int) $shareType]);
}
sort($shareTypes);
$this->limit = $perPage;
$this->offset = $perPage * ($page - 1);
// In global scale mode we always search the loogup server
if ($this->config->getSystemValueBool('gs.enabled', false)) {
$lookup = true;
$this->result['lookupEnabled'] = true;
} else {
$this->result['lookupEnabled'] = $this->config->getAppValue('files_sharing', 'lookupServerEnabled', 'yes') === 'yes';
}
[$result, $hasMoreResults] = $this->collaboratorSearch->search($search, $shareTypes, $lookup, $this->limit, $this->offset);
// extra treatment for 'exact' subarray, with a single merge expected keys might be lost
if (isset($result['exact'])) {
$result['exact'] = array_merge($this->result['exact'], $result['exact']);
}
$this->result = array_merge($this->result, $result);
$response = new DataResponse($this->result);
if ($hasMoreResults) {
$response->setHeaders(['Link' => $this->getPaginationLink($page, [
'search' => $search,
'itemType' => $itemType,
'shareType' => $shareTypes,
'perPage' => $perPage,
])]);
}
return $response;
}
/**
* @param string $user
* @param int $shareType
*
* @return Generator<array<string>>
*/
private function getAllShareesByType(string $user, int $shareType): Generator {
$offset = 0;
$pageSize = 50;
while (count($page = $this->shareManager->getSharesBy(
$user,
$shareType,
null,
false,
$pageSize,
$offset
))) {
foreach ($page as $share) {
yield [$share->getSharedWith(), $share->getSharedWithDisplayName() ?? $share->getSharedWith()];
}
$offset += $pageSize;
}
}
private function sortShareesByFrequency(array $sharees): array {
usort($sharees, function (array $s1, array $s2): int {
return $s2['count'] - $s1['count'];
});
return $sharees;
}
private $searchResultTypeMap = [
IShare::TYPE_USER => 'users',
IShare::TYPE_GROUP => 'groups',
IShare::TYPE_REMOTE => 'remotes',
IShare::TYPE_REMOTE_GROUP => 'remote_groups',
IShare::TYPE_EMAIL => 'emails',
];
private function getAllSharees(string $user, array $shareTypes): ISearchResult {
$result = [];
foreach ($shareTypes as $shareType) {
$sharees = $this->getAllShareesByType($user, $shareType);
$shareTypeResults = [];
foreach ($sharees as [$sharee, $displayname]) {
if (!isset($this->searchResultTypeMap[$shareType])) {
continue;
}
if (!isset($shareTypeResults[$sharee])) {
$shareTypeResults[$sharee] = [
'count' => 1,
'label' => $displayname,
'value' => [
'shareType' => $shareType,
'shareWith' => $sharee,
],
];
} else {
$shareTypeResults[$sharee]['count']++;
}
}
$result = array_merge($result, array_values($shareTypeResults));
}
$top5 = array_slice(
$this->sortShareesByFrequency($result),
0,
5
);
$searchResult = new SearchResult();
foreach ($this->searchResultTypeMap as $int => $str) {
$searchResult->addResultSet(new SearchResultType($str), [], []);
foreach ($top5 as $x) {
if ($x['value']['shareType'] === $int) {
$searchResult->addResultSet(new SearchResultType($str), [], [$x]);
}
}
}
return $searchResult;
}
/**
* @NoAdminRequired
*
* Find recommended sharees
*
* @param string $itemType Limit to specific item types
* @param int|int[]|null $shareType Limit to specific share types
* @return DataResponse<Http::STATUS_OK, Files_SharingShareesRecommendedResult, array{}>
*
* 200: Recommended sharees returned
*/
public function findRecommended(string $itemType, $shareType = null): DataResponse {
$shareTypes = [
IShare::TYPE_USER,
];
if ($itemType === 'file' || $itemType === 'folder') {
if ($this->shareManager->allowGroupSharing()) {
$shareTypes[] = IShare::TYPE_GROUP;
}
if ($this->isRemoteSharingAllowed($itemType)) {
$shareTypes[] = IShare::TYPE_REMOTE;
}
if ($this->isRemoteGroupSharingAllowed($itemType)) {
$shareTypes[] = IShare::TYPE_REMOTE_GROUP;
}
if ($this->shareManager->shareProviderExists(IShare::TYPE_EMAIL)) {
$shareTypes[] = IShare::TYPE_EMAIL;
}
if ($this->shareManager->shareProviderExists(IShare::TYPE_ROOM)) {
$shareTypes[] = IShare::TYPE_ROOM;
}
} else {
$shareTypes[] = IShare::TYPE_GROUP;
$shareTypes[] = IShare::TYPE_EMAIL;
}
// FIXME: DI
if (\OC::$server->getAppManager()->isEnabledForUser('circles') && class_exists('\OCA\Circles\ShareByCircleProvider')) {
$shareTypes[] = IShare::TYPE_CIRCLE;
}
if (isset($_GET['shareType']) && is_array($_GET['shareType'])) {
$shareTypes = array_intersect($shareTypes, $_GET['shareType']);
sort($shareTypes);
} elseif (is_numeric($shareType)) {
$shareTypes = array_intersect($shareTypes, [(int) $shareType]);
sort($shareTypes);
}
return new DataResponse(
$this->getAllSharees($this->userId, $shareTypes)->asArray()
);
}
/**
* Method to get out the static call for better testing
*
* @param string $itemType
* @return bool
*/
protected function isRemoteSharingAllowed(string $itemType): bool {
try {
// FIXME: static foo makes unit testing unnecessarily difficult
$backend = \OC\Share\Share::getBackend($itemType);
return $backend->isShareTypeAllowed(IShare::TYPE_REMOTE);
} catch (\Exception $e) {
return false;
}
}
protected function isRemoteGroupSharingAllowed(string $itemType): bool {
try {
// FIXME: static foo makes unit testing unnecessarily difficult
$backend = \OC\Share\Share::getBackend($itemType);
return $backend->isShareTypeAllowed(IShare::TYPE_REMOTE_GROUP);
} catch (\Exception $e) {
return false;
}
}
/**
* Generates a bunch of pagination links for the current page
*
* @param int $page Current page
* @param array $params Parameters for the URL
* @return string
*/
protected function getPaginationLink(int $page, array $params): string {
if ($this->isV2()) {
$url = $this->urlGenerator->getAbsoluteURL('/ocs/v2.php/apps/files_sharing/api/v1/sharees') . '?';
} else {
$url = $this->urlGenerator->getAbsoluteURL('/ocs/v1.php/apps/files_sharing/api/v1/sharees') . '?';
}
$params['page'] = $page + 1;
return '<' . $url . http_build_query($params) . '>; rel="next"';
}
/**
* @return bool
*/
protected function isV2(): bool {
return $this->request->getScriptName() === '/ocs/v2.php';
}
}
@@ -0,0 +1,291 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2023 Louis Chemineau <louis@chmn.me>
*
* @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 <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Files_Sharing;
use OCA\FederatedFileSharing\FederatedShareProvider;
use OCA\Files_Sharing\AppInfo\Application;
use OCA\Files_Sharing\Event\BeforeTemplateRenderedEvent;
use OCA\Viewer\Event\LoadViewer;
use OCP\Accounts\IAccountManager;
use OCP\AppFramework\Http\ContentSecurityPolicy;
use OCP\AppFramework\Http\Template\ExternalShareMenuAction;
use OCP\AppFramework\Http\Template\LinkMenuAction;
use OCP\AppFramework\Http\Template\PublicTemplateResponse;
use OCP\AppFramework\Http\Template\SimpleMenuAction;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\Constants;
use OCP\Defaults;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\Files\FileInfo;
use OCP\Files\Folder;
use OCP\IConfig;
use OCP\IL10N;
use OCP\IPreview;
use OCP\IRequest;
use OCP\IURLGenerator;
use OCP\IUser;
use OCP\IUserManager;
use OCP\Share\IPublicShareTemplateProvider;
use OCP\Share\IShare;
use OCP\Template;
use OCP\Util;
class DefaultPublicShareTemplateProvider implements IPublicShareTemplateProvider {
private IUserManager $userManager;
private IAccountManager $accountManager;
private IPreview $previewManager;
protected FederatedShareProvider $federatedShareProvider;
private IURLGenerator $urlGenerator;
private IEventDispatcher $eventDispatcher;
private IL10N $l10n;
private Defaults $defaults;
private IConfig $config;
private IRequest $request;
public function __construct(
IUserManager $userManager,
IAccountManager $accountManager,
IPreview $previewManager,
FederatedShareProvider $federatedShareProvider,
IUrlGenerator $urlGenerator,
IEventDispatcher $eventDispatcher,
IL10N $l10n,
Defaults $defaults,
IConfig $config,
IRequest $request
) {
$this->userManager = $userManager;
$this->accountManager = $accountManager;
$this->previewManager = $previewManager;
$this->federatedShareProvider = $federatedShareProvider;
$this->urlGenerator = $urlGenerator;
$this->eventDispatcher = $eventDispatcher;
$this->l10n = $l10n;
$this->defaults = $defaults;
$this->config = $config;
$this->request = $request;
}
public function shouldRespond(IShare $share): bool {
return true;
}
public function renderPage(IShare $share, string $token, string $path): TemplateResponse {
$shareNode = $share->getNode();
$shareTmpl = [];
$shareTmpl['owner'] = '';
$shareTmpl['shareOwner'] = '';
$owner = $this->userManager->get($share->getShareOwner());
if ($owner instanceof IUser) {
$ownerAccount = $this->accountManager->getAccount($owner);
$ownerName = $ownerAccount->getProperty(IAccountManager::PROPERTY_DISPLAYNAME);
if ($ownerName->getScope() === IAccountManager::SCOPE_PUBLISHED) {
$shareTmpl['owner'] = $owner->getUID();
$shareTmpl['shareOwner'] = $owner->getDisplayName();
}
}
$shareTmpl['filename'] = $shareNode->getName();
$shareTmpl['directory_path'] = $share->getTarget();
$shareTmpl['note'] = $share->getNote();
$shareTmpl['mimetype'] = $shareNode->getMimetype();
$shareTmpl['previewSupported'] = $this->previewManager->isMimeSupported($shareNode->getMimetype());
$shareTmpl['dirToken'] = $token;
$shareTmpl['sharingToken'] = $token;
$shareTmpl['server2serversharing'] = $this->federatedShareProvider->isOutgoingServer2serverShareEnabled();
$shareTmpl['protected'] = $share->getPassword() !== null ? 'true' : 'false';
$shareTmpl['dir'] = '';
$shareTmpl['nonHumanFileSize'] = $shareNode->getSize();
$shareTmpl['fileSize'] = Util::humanFileSize($shareNode->getSize());
$shareTmpl['hideDownload'] = $share->getHideDownload();
$hideFileList = false;
if ($shareNode instanceof Folder) {
$shareIsFolder = true;
$folderNode = $shareNode->get($path);
$shareTmpl['dir'] = $shareNode->getRelativePath($folderNode->getPath());
/*
* The OC_Util methods require a view. This just uses the node API
*/
$freeSpace = $share->getNode()->getStorage()->free_space($share->getNode()->getInternalPath());
if ($freeSpace < FileInfo::SPACE_UNLIMITED) {
$freeSpace = (int)max($freeSpace, 0);
} else {
$freeSpace = (INF > 0) ? INF: PHP_INT_MAX; // work around https://bugs.php.net/bug.php?id=69188
}
$hideFileList = !($share->getPermissions() & Constants::PERMISSION_READ);
$maxUploadFilesize = $freeSpace;
$folder = new Template('files', 'list', '');
$folder->assign('dir', $shareNode->getRelativePath($folderNode->getPath()));
$folder->assign('dirToken', $token);
$folder->assign('permissions', Constants::PERMISSION_READ);
$folder->assign('isPublic', true);
$folder->assign('hideFileList', $hideFileList);
$folder->assign('publicUploadEnabled', 'no');
// default to list view
$folder->assign('showgridview', false);
$folder->assign('uploadMaxFilesize', $maxUploadFilesize);
$folder->assign('uploadMaxHumanFilesize', Util::humanFileSize($maxUploadFilesize));
$folder->assign('freeSpace', $freeSpace);
$folder->assign('usedSpacePercent', 0);
$folder->assign('trash', false);
$shareTmpl['folder'] = $folder->fetchPage();
} else {
$shareIsFolder = false;
}
// default to list view
$shareTmpl['showgridview'] = false;
$shareTmpl['hideFileList'] = $hideFileList;
$shareTmpl['downloadURL'] = $this->urlGenerator->linkToRouteAbsolute('files_sharing.sharecontroller.downloadShare', [
'token' => $token,
'filename' => $shareIsFolder ? null : $shareNode->getName()
]);
$shareTmpl['shareUrl'] = $this->urlGenerator->linkToRouteAbsolute('files_sharing.sharecontroller.showShare', ['token' => $token]);
$shareTmpl['maxSizeAnimateGif'] = $this->config->getSystemValue('max_filesize_animated_gifs_public_sharing', 10);
$shareTmpl['previewEnabled'] = $this->config->getSystemValue('enable_previews', true);
$shareTmpl['previewMaxX'] = $this->config->getSystemValue('preview_max_x', 1024);
$shareTmpl['previewMaxY'] = $this->config->getSystemValue('preview_max_y', 1024);
$shareTmpl['disclaimer'] = $this->config->getAppValue('core', 'shareapi_public_link_disclaimertext', '');
$shareTmpl['previewURL'] = $shareTmpl['downloadURL'];
if ($shareTmpl['previewSupported']) {
$shareTmpl['previewImage'] = $this->urlGenerator->linkToRouteAbsolute('files_sharing.PublicPreview.getPreview',
['x' => 200, 'y' => 200, 'file' => $shareTmpl['directory_path'], 'token' => $shareTmpl['dirToken']]);
$ogPreview = $shareTmpl['previewImage'];
// We just have direct previews for image files
if ($shareNode->getMimePart() === 'image') {
$shareTmpl['previewURL'] = $this->urlGenerator->linkToRouteAbsolute('files_sharing.publicpreview.directLink', ['token' => $token]);
$ogPreview = $shareTmpl['previewURL'];
//Whatapp is kind of picky about their size requirements
if ($this->request->isUserAgent(['/^WhatsApp/'])) {
$ogPreview = $this->urlGenerator->linkToRouteAbsolute('files_sharing.PublicPreview.getPreview', [
'token' => $token,
'x' => 256,
'y' => 256,
'a' => true,
]);
}
}
} else {
$shareTmpl['previewImage'] = $this->urlGenerator->getAbsoluteURL($this->urlGenerator->imagePath('core', 'favicon-fb.png'));
$ogPreview = $shareTmpl['previewImage'];
}
// Load files we need
Util::addScript('files', 'semaphore');
Util::addScript('files', 'file-upload');
Util::addStyle('files_sharing', 'publicView');
Util::addScript('files_sharing', 'public');
Util::addScript('files_sharing', 'templates');
Util::addScript('files', 'fileactions');
Util::addScript('files', 'fileactionsmenu');
Util::addScript('files', 'jquery.fileupload');
Util::addScript('files_sharing', 'files_drop');
if (isset($shareTmpl['folder'])) {
// JS required for folders
Util::addStyle('files', 'merged');
Util::addScript('files', 'filesummary');
Util::addScript('files', 'templates');
Util::addScript('files', 'breadcrumb');
Util::addScript('files', 'fileinfomodel');
Util::addScript('files', 'newfilemenu');
Util::addScript('files', 'files');
Util::addScript('files', 'filemultiselectmenu');
Util::addScript('files', 'filelist');
Util::addScript('files', 'keyboardshortcuts');
Util::addScript('files', 'operationprogressbar');
}
// Load Viewer scripts
if (class_exists(LoadViewer::class)) {
$this->eventDispatcher->dispatchTyped(new LoadViewer());
}
// OpenGraph Support: http://ogp.me/
Util::addHeader('meta', ['property' => "og:title", 'content' => $shareTmpl['filename']]);
Util::addHeader('meta', ['property' => "og:description", 'content' => $this->defaults->getName() . ($this->defaults->getSlogan() !== '' ? ' - ' . $this->defaults->getSlogan() : '')]);
Util::addHeader('meta', ['property' => "og:site_name", 'content' => $this->defaults->getName()]);
Util::addHeader('meta', ['property' => "og:url", 'content' => $shareTmpl['shareUrl']]);
Util::addHeader('meta', ['property' => "og:type", 'content' => "object"]);
Util::addHeader('meta', ['property' => "og:image", 'content' => $ogPreview]);
$this->eventDispatcher->dispatchTyped(new BeforeTemplateRenderedEvent($share));
$csp = new ContentSecurityPolicy();
$csp->addAllowedFrameDomain('\'self\'');
$response = new PublicTemplateResponse(Application::APP_ID, 'public', $shareTmpl);
$response->setHeaderTitle($shareTmpl['filename']);
if ($shareTmpl['shareOwner'] !== '') {
$response->setHeaderDetails($this->l10n->t('shared by %s', [$shareTmpl['shareOwner']]));
}
$isNoneFileDropFolder = $shareIsFolder === false || $share->getPermissions() !== Constants::PERMISSION_CREATE;
if ($isNoneFileDropFolder && !$share->getHideDownload()) {
Util::addScript('files_sharing', 'public_note');
$downloadWhite = new SimpleMenuAction('download', $this->l10n->t('Download'), 'icon-download-white', $shareTmpl['downloadURL'], 0);
$downloadAllWhite = new SimpleMenuAction('download', $this->l10n->t('Download all files'), 'icon-download-white', $shareTmpl['downloadURL'], 0);
$download = new SimpleMenuAction('download', $this->l10n->t('Download'), 'icon-download', $shareTmpl['downloadURL'], 10, $shareTmpl['fileSize']);
$downloadAll = new SimpleMenuAction('download', $this->l10n->t('Download all files'), 'icon-download', $shareTmpl['downloadURL'], 10, $shareTmpl['fileSize']);
$directLink = new LinkMenuAction($this->l10n->t('Direct link'), 'icon-public', $shareTmpl['previewURL']);
// TRANSLATORS The placeholder refers to the software product name as in 'Add to your Nextcloud'
$externalShare = new ExternalShareMenuAction($this->l10n->t('Add to your %s', [$this->defaults->getProductName()]), 'icon-external', $shareTmpl['owner'], $shareTmpl['shareOwner'], $shareTmpl['filename']);
$responseComposer = [];
if ($shareIsFolder) {
$responseComposer[] = $downloadAllWhite;
$responseComposer[] = $downloadAll;
} else {
$responseComposer[] = $downloadWhite;
$responseComposer[] = $download;
}
$responseComposer[] = $directLink;
if ($this->federatedShareProvider->isOutgoingServer2serverShareEnabled()) {
$responseComposer[] = $externalShare;
}
$response->setHeaderActions($responseComposer);
}
$response->setContentSecurityPolicy($csp);
return $response;
}
}
@@ -0,0 +1,67 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Joas Schilling <coding@schilljs.com>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Vincent Petry <vincent@nextcloud.com>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* 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, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OCA\Files_Sharing;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\BackgroundJob\TimedJob;
/**
* Delete all share entries that have no matching entries in the file cache table.
*/
class DeleteOrphanedSharesJob extends TimedJob {
/**
* Default interval in minutes
*
* @var int $defaultIntervalMin
**/
protected $defaultIntervalMin = 15;
/**
* sets the correct interval for this timed job
*/
public function __construct(ITimeFactory $time) {
parent::__construct($time);
$this->interval = $this->defaultIntervalMin * 60;
}
/**
* Makes the background job do its work
*
* @param array $argument unused argument
*/
public function run($argument) {
$connection = \OC::$server->getDatabaseConnection();
$logger = \OC::$server->getLogger();
$sql =
'DELETE FROM `*PREFIX*share` ' .
'WHERE `item_type` in (\'file\', \'folder\') ' .
'AND NOT EXISTS (SELECT `fileid` FROM `*PREFIX*filecache` WHERE `file_source` = `fileid`)';
$deletedEntries = $connection->executeUpdate($sql);
$logger->debug("$deletedEntries orphaned share(s) deleted", ['app' => 'DeleteOrphanedSharesJob']);
}
}
@@ -0,0 +1,72 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2020 Julius Härtl <jus@bitgrid.net>
*
* @author Julius Härtl <jus@bitgrid.net>
* @author Morris Jobke <hey@morrisjobke.de>
*
* @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 <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Files_Sharing\Event;
use OCP\EventDispatcher\Event;
use OCP\Share\IShare;
/**
* Emitted before the rendering step of the public share page happens. The event
* holds a flag that specifies if it is the authentication page of a public share.
*
* @since 20.0.0
*/
class BeforeTemplateRenderedEvent extends Event {
/**
* @since 20.0.0
*/
public const SCOPE_PUBLIC_SHARE_AUTH = 'publicShareAuth';
/** @var IShare */
private $share;
/** @var string|null */
private $scope;
/**
* @since 20.0.0
*/
public function __construct(IShare $share, ?string $scope = null) {
parent::__construct();
$this->share = $share;
$this->scope = $scope;
}
/**
* @since 20.0.0
*/
public function getShare(): IShare {
return $this->share;
}
/**
* @since 20.0.0
*/
public function getScope(): ?string {
return $this->scope;
}
}
@@ -0,0 +1,68 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2021 John Molakvoæ <skjnldsv@protonmail.com>
*
* @author John Molakvoæ <skjnldsv@protonmail.com>
*
* @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 <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Files_Sharing\Event;
use OCP\EventDispatcher\Event;
use OCP\Share\IShare;
class ShareLinkAccessedEvent extends Event {
/** @var IShare */
private $share;
/** @var string */
private $step;
/** @var int */
private $errorCode;
/** @var string */
private $errorMessage;
public function __construct(IShare $share, string $step = '', int $errorCode = 200, string $errorMessage = '') {
parent::__construct();
$this->share = $share;
$this->step = $step;
$this->errorCode = $errorCode;
$this->errorMessage = $errorMessage;
}
public function getShare(): IShare {
return $this->share;
}
public function getStep(): string {
return $this->step;
}
public function getErrorCode(): int {
return $this->errorCode;
}
public function getErrorMessage(): string {
return $this->errorMessage;
}
}
@@ -0,0 +1,56 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2021 Robin Appelman <robin@icewind.nl>
*
* @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 <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Files_Sharing\Event;
use OCA\Files_Sharing\SharedMount;
use OCP\EventDispatcher\Event;
use OCP\Files\Mount\IMountPoint;
class ShareMountedEvent extends Event {
/** @var SharedMount */
private $mount;
/** @var IMountPoint[] */
private $additionalMounts = [];
public function __construct(SharedMount $mount) {
parent::__construct();
$this->mount = $mount;
}
public function getMount(): SharedMount {
return $this->mount;
}
public function addAdditionalMount(IMountPoint $mountPoint): void {
$this->additionalMounts[] = $mountPoint;
}
/**
* @return IMountPoint[]
*/
public function getAdditionalMounts(): array {
return $this->additionalMounts;
}
}
@@ -0,0 +1,32 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Björn Schießle <bjoern@schiessle.org>
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* 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, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OCA\Files_Sharing\Exceptions;
/**
* Expected path with a different root
* Possible Error Codes:
* 10 - Path not relative to data/ and point to the users file directory
*
*/
class BrokenPath extends \Exception {
}
@@ -0,0 +1,29 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Björn Schießle <bjoern@schiessle.org>
* @author Roeland Jago Douma <roeland@famdouma.nl>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* 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, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OCA\Files_Sharing\Exceptions;
/**
* S2S sharing not allowed
*/
class S2SException extends \Exception {
}
@@ -0,0 +1,35 @@
<?php
/**
* @copyright Copyright (c) 2019, Maxence Lange <maxence@artificial-owl.com>
*
* @author Maxence Lange <maxence@artificial-owl.com>
*
* @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 <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Files_Sharing\Exceptions;
use Exception;
/**
* Sharing and Resharing rights.
*
* Class SharingRightsException
*
* @package OCA\Files_Sharing\Exceptions
*/
class SharingRightsException extends Exception {
}
@@ -0,0 +1,107 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Joas Schilling <coding@schilljs.com>
* @author John Molakvoæ <skjnldsv@protonmail.com>
* @author Roeland Jago Douma <roeland@famdouma.nl>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* 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, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OCA\Files_Sharing;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\BackgroundJob\IJob;
use OCP\BackgroundJob\TimedJob;
use OCP\IDBConnection;
use OCP\Share\Exceptions\ShareNotFound;
use OCP\Share\IManager;
use OCP\Share\IShare;
/**
* Delete all shares that are expired
*/
class ExpireSharesJob extends TimedJob {
/** @var IManager */
private $shareManager;
/** @var IDBConnection */
private $db;
public function __construct(ITimeFactory $time, IManager $shareManager, IDBConnection $db) {
$this->shareManager = $shareManager;
$this->db = $db;
parent::__construct($time);
// Run once a day
$this->setInterval(24 * 60 * 60);
$this->setTimeSensitivity(IJob::TIME_INSENSITIVE);
}
/**
* Makes the background job do its work
*
* @param array $argument unused argument
*/
public function run($argument) {
//Current time
$now = new \DateTime();
$now = $now->format('Y-m-d H:i:s');
/*
* Expire file link shares only (for now)
*/
$qb = $this->db->getQueryBuilder();
$qb->select('id', 'share_type')
->from('share')
->where(
$qb->expr()->andX(
$qb->expr()->orX(
$qb->expr()->eq('share_type', $qb->expr()->literal(IShare::TYPE_LINK)),
$qb->expr()->eq('share_type', $qb->expr()->literal(IShare::TYPE_EMAIL))
),
$qb->expr()->lte('expiration', $qb->expr()->literal($now)),
$qb->expr()->orX(
$qb->expr()->eq('item_type', $qb->expr()->literal('file')),
$qb->expr()->eq('item_type', $qb->expr()->literal('folder'))
)
)
);
$shares = $qb->executeQuery();
while ($share = $shares->fetch()) {
if ((int)$share['share_type'] === IShare::TYPE_LINK) {
$id = 'ocinternal';
} elseif ((int)$share['share_type'] === IShare::TYPE_EMAIL) {
$id = 'ocMailShare';
}
$id .= ':' . $share['id'];
try {
$share = $this->shareManager->getShareById($id);
$this->shareManager->deleteShare($share);
} catch (ShareNotFound $e) {
// Normally the share gets automatically expired on fetching it
}
}
$shares->closeCursor();
}
}
+68
View File
@@ -0,0 +1,68 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Robin Appelman <robin@icewind.nl>
* @author Vincent Petry <vincent@nextcloud.com>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* 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, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OCA\Files_Sharing\External;
use OCP\Federation\ICloudId;
class Cache extends \OC\Files\Cache\Cache {
/** @var ICloudId */
private $cloudId;
private $remote;
private $remoteUser;
private $storage;
/**
* @param \OCA\Files_Sharing\External\Storage $storage
* @param ICloudId $cloudId
*/
public function __construct($storage, ICloudId $cloudId) {
$this->cloudId = $cloudId;
$this->storage = $storage;
[, $remote] = explode('://', $cloudId->getRemote(), 2);
$this->remote = $remote;
$this->remoteUser = $cloudId->getUser();
parent::__construct($storage);
}
public function get($file) {
$result = parent::get($file);
if (!$result) {
return false;
}
$result['displayname_owner'] = $this->cloudId->getDisplayId();
if (!$file || $file === '') {
$result['is_share_mount_point'] = true;
$mountPoint = rtrim($this->storage->getMountPoint());
$result['name'] = basename($mountPoint);
}
return $result;
}
public function getFolderContentsById($fileId) {
$results = parent::getFolderContentsById($fileId);
foreach ($results as &$file) {
$file['displayname_owner'] = $this->cloudId->getDisplayId();
}
return $results;
}
}
+831
View File
@@ -0,0 +1,831 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Bjoern Schiessle <bjoern@schiessle.org>
* @author Björn Schießle <bjoern@schiessle.org>
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Daniel Hansson <daniel@techandme.se>
* @author Joas Schilling <coding@schilljs.com>
* @author Jörn Friedrich Dreyer <jfd@butonic.de>
* @author Julius Härtl <jus@bitgrid.net>
* @author Lukas Reschke <lukas@statuscode.ch>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Robin Appelman <robin@icewind.nl>
* @author Roeland Jago Douma <roeland@famdouma.nl>
* @author Stefan Weil <sw@weilnetz.de>
* @author Vincent Petry <vincent@nextcloud.com>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* 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, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OCA\Files_Sharing\External;
use Doctrine\DBAL\Driver\Exception;
use OC\Files\Filesystem;
use OCA\FederatedFileSharing\Events\FederatedShareAddedEvent;
use OCA\Files_Sharing\Helper;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\Federation\ICloudFederationFactory;
use OCP\Federation\ICloudFederationProviderManager;
use OCP\Files;
use OCP\Files\NotFoundException;
use OCP\Files\Storage\IStorageFactory;
use OCP\Http\Client\IClientService;
use OCP\IDBConnection;
use OCP\IGroupManager;
use OCP\IUserManager;
use OCP\IUserSession;
use OCP\Notification\IManager;
use OCP\OCS\IDiscoveryService;
use OCP\Share;
use OCP\Share\IShare;
use Psr\Log\LoggerInterface;
class Manager {
public const STORAGE = '\OCA\Files_Sharing\External\Storage';
/** @var string|null */
private $uid;
/** @var IDBConnection */
private $connection;
/** @var \OC\Files\Mount\Manager */
private $mountManager;
/** @var IStorageFactory */
private $storageLoader;
/** @var IClientService */
private $clientService;
/** @var IManager */
private $notificationManager;
/** @var IDiscoveryService */
private $discoveryService;
/** @var ICloudFederationProviderManager */
private $cloudFederationProviderManager;
/** @var ICloudFederationFactory */
private $cloudFederationFactory;
/** @var IGroupManager */
private $groupManager;
/** @var IUserManager */
private $userManager;
/** @var IEventDispatcher */
private $eventDispatcher;
/** @var LoggerInterface */
private $logger;
public function __construct(
IDBConnection $connection,
\OC\Files\Mount\Manager $mountManager,
IStorageFactory $storageLoader,
IClientService $clientService,
IManager $notificationManager,
IDiscoveryService $discoveryService,
ICloudFederationProviderManager $cloudFederationProviderManager,
ICloudFederationFactory $cloudFederationFactory,
IGroupManager $groupManager,
IUserManager $userManager,
IUserSession $userSession,
IEventDispatcher $eventDispatcher,
LoggerInterface $logger
) {
$user = $userSession->getUser();
$this->connection = $connection;
$this->mountManager = $mountManager;
$this->storageLoader = $storageLoader;
$this->clientService = $clientService;
$this->uid = $user ? $user->getUID() : null;
$this->notificationManager = $notificationManager;
$this->discoveryService = $discoveryService;
$this->cloudFederationProviderManager = $cloudFederationProviderManager;
$this->cloudFederationFactory = $cloudFederationFactory;
$this->groupManager = $groupManager;
$this->userManager = $userManager;
$this->eventDispatcher = $eventDispatcher;
$this->logger = $logger;
}
/**
* add new server-to-server share
*
* @param string $remote
* @param string $token
* @param string $password
* @param string $name
* @param string $owner
* @param int $shareType
* @param boolean $accepted
* @param string $user
* @param string $remoteId
* @param int $parent
* @return Mount|null
* @throws \Doctrine\DBAL\Exception
*/
public function addShare($remote, $token, $password, $name, $owner, $shareType, $accepted = false, $user = null, $remoteId = '', $parent = -1) {
$user = $user ? $user : $this->uid;
$accepted = $accepted ? IShare::STATUS_ACCEPTED : IShare::STATUS_PENDING;
$name = Filesystem::normalizePath('/' . $name);
if ($accepted !== IShare::STATUS_ACCEPTED) {
// To avoid conflicts with the mount point generation later,
// we only use a temporary mount point name here. The real
// mount point name will be generated when accepting the share,
// using the original share item name.
$tmpMountPointName = '{{TemporaryMountPointName#' . $name . '}}';
$mountPoint = $tmpMountPointName;
$hash = md5($tmpMountPointName);
$data = [
'remote' => $remote,
'share_token' => $token,
'password' => $password,
'name' => $name,
'owner' => $owner,
'user' => $user,
'mountpoint' => $mountPoint,
'mountpoint_hash' => $hash,
'accepted' => $accepted,
'remote_id' => $remoteId,
'share_type' => $shareType,
];
$i = 1;
while (!$this->connection->insertIfNotExist('*PREFIX*share_external', $data, ['user', 'mountpoint_hash'])) {
// The external share already exists for the user
$data['mountpoint'] = $tmpMountPointName . '-' . $i;
$data['mountpoint_hash'] = md5($data['mountpoint']);
$i++;
}
return null;
}
$mountPoint = Files::buildNotExistingFileName('/', $name);
$mountPoint = Filesystem::normalizePath('/' . $mountPoint);
$hash = md5($mountPoint);
$this->writeShareToDb($remote, $token, $password, $name, $owner, $user, $mountPoint, $hash, $accepted, $remoteId, $parent, $shareType);
$options = [
'remote' => $remote,
'token' => $token,
'password' => $password,
'mountpoint' => $mountPoint,
'owner' => $owner
];
return $this->mountShare($options);
}
/**
* write remote share to the database
*
* @param $remote
* @param $token
* @param $password
* @param $name
* @param $owner
* @param $user
* @param $mountPoint
* @param $hash
* @param $accepted
* @param $remoteId
* @param $parent
* @param $shareType
*
* @return void
* @throws \Doctrine\DBAL\Driver\Exception
*/
private function writeShareToDb($remote, $token, $password, $name, $owner, $user, $mountPoint, $hash, $accepted, $remoteId, $parent, $shareType): void {
$query = $this->connection->prepare('
INSERT INTO `*PREFIX*share_external`
(`remote`, `share_token`, `password`, `name`, `owner`, `user`, `mountpoint`, `mountpoint_hash`, `accepted`, `remote_id`, `parent`, `share_type`)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
');
$query->execute([$remote, $token, $password, $name, $owner, $user, $mountPoint, $hash, $accepted, $remoteId, $parent, $shareType]);
}
/**
* get share
*
* @param int $id share id
* @return mixed share of false
*/
private function fetchShare($id) {
$getShare = $this->connection->prepare('
SELECT `id`, `remote`, `remote_id`, `share_token`, `name`, `owner`, `user`, `mountpoint`, `accepted`, `parent`, `share_type`, `password`, `mountpoint_hash`
FROM `*PREFIX*share_external`
WHERE `id` = ?');
$result = $getShare->execute([$id]);
$share = $result->fetch();
$result->closeCursor();
return $share;
}
private function fetchUserShare($parentId, $uid) {
$getShare = $this->connection->prepare('
SELECT `id`, `remote`, `remote_id`, `share_token`, `name`, `owner`, `user`, `mountpoint`, `accepted`, `parent`, `share_type`, `password`, `mountpoint_hash`
FROM `*PREFIX*share_external`
WHERE `parent` = ? AND `user` = ?');
$result = $getShare->execute([$parentId, $uid]);
$share = $result->fetch();
$result->closeCursor();
if ($share !== false) {
return $share;
}
return null;
}
/**
* get share
*
* @param int $id share id
* @return mixed share of false
*/
public function getShare($id) {
$share = $this->fetchShare($id);
$validShare = is_array($share) && isset($share['share_type']) && isset($share['user']);
// check if the user is allowed to access it
if ($validShare && (int)$share['share_type'] === IShare::TYPE_USER && $share['user'] === $this->uid) {
return $share;
} elseif ($validShare && (int)$share['share_type'] === IShare::TYPE_GROUP) {
$parentId = (int)$share['parent'];
if ($parentId !== -1) {
// we just retrieved a sub-share, switch to the parent entry for verification
$groupShare = $this->fetchShare($parentId);
} else {
$groupShare = $share;
}
$user = $this->userManager->get($this->uid);
if ($this->groupManager->get($groupShare['user'])->inGroup($user)) {
return $share;
}
}
return false;
}
/**
* Updates accepted flag in the database
*
* @param int $id
*/
private function updateAccepted(int $shareId, bool $accepted) : void {
$query = $this->connection->prepare('
UPDATE `*PREFIX*share_external`
SET `accepted` = ?
WHERE `id` = ?');
$updateResult = $query->execute([$accepted ? 1 : 0, $shareId]);
$updateResult->closeCursor();
}
/**
* accept server-to-server share
*
* @param int $id
* @return bool True if the share could be accepted, false otherwise
*/
public function acceptShare($id) {
$share = $this->getShare($id);
$result = false;
if ($share) {
\OC_Util::setupFS($this->uid);
$shareFolder = Helper::getShareFolder(null, $this->uid);
$mountPoint = Files::buildNotExistingFileName($shareFolder, $share['name']);
$mountPoint = Filesystem::normalizePath($mountPoint);
$hash = md5($mountPoint);
$userShareAccepted = false;
if ((int)$share['share_type'] === IShare::TYPE_USER) {
$acceptShare = $this->connection->prepare('
UPDATE `*PREFIX*share_external`
SET `accepted` = ?,
`mountpoint` = ?,
`mountpoint_hash` = ?
WHERE `id` = ? AND `user` = ?');
$userShareAccepted = $acceptShare->execute([1, $mountPoint, $hash, $id, $this->uid]);
} else {
$parentId = (int)$share['parent'];
if ($parentId !== -1) {
// this is the sub-share
$subshare = $share;
} else {
$subshare = $this->fetchUserShare($id, $this->uid);
}
if ($subshare !== null) {
try {
$acceptShare = $this->connection->prepare('
UPDATE `*PREFIX*share_external`
SET `accepted` = ?,
`mountpoint` = ?,
`mountpoint_hash` = ?
WHERE `id` = ? AND `user` = ?');
$acceptShare->execute([1, $mountPoint, $hash, $subshare['id'], $this->uid]);
$result = true;
} catch (Exception $e) {
$this->logger->emergency('Could not update share', ['exception' => $e]);
$result = false;
}
} else {
try {
$this->writeShareToDb(
$share['remote'],
$share['share_token'],
$share['password'],
$share['name'],
$share['owner'],
$this->uid,
$mountPoint, $hash, 1,
$share['remote_id'],
$id,
$share['share_type']);
$result = true;
} catch (Exception $e) {
$this->logger->emergency('Could not create share', ['exception' => $e]);
$result = false;
}
}
}
if ($userShareAccepted !== false) {
$this->sendFeedbackToRemote($share['remote'], $share['share_token'], $share['remote_id'], 'accept');
$event = new FederatedShareAddedEvent($share['remote']);
$this->eventDispatcher->dispatchTyped($event);
$this->eventDispatcher->dispatchTyped(new Files\Events\InvalidateMountCacheEvent($this->userManager->get($this->uid)));
$result = true;
}
}
// Make sure the user has no notification for something that does not exist anymore.
$this->processNotification($id);
return $result;
}
/**
* decline server-to-server share
*
* @param int $id
* @return bool True if the share could be declined, false otherwise
*/
public function declineShare($id) {
$share = $this->getShare($id);
$result = false;
if ($share && (int)$share['share_type'] === IShare::TYPE_USER) {
$removeShare = $this->connection->prepare('
DELETE FROM `*PREFIX*share_external` WHERE `id` = ? AND `user` = ?');
$removeShare->execute([$id, $this->uid]);
$this->sendFeedbackToRemote($share['remote'], $share['share_token'], $share['remote_id'], 'decline');
$this->processNotification($id);
$result = true;
} elseif ($share && (int)$share['share_type'] === IShare::TYPE_GROUP) {
$parentId = (int)$share['parent'];
if ($parentId !== -1) {
// this is the sub-share
$subshare = $share;
} else {
$subshare = $this->fetchUserShare($id, $this->uid);
}
if ($subshare !== null) {
try {
$this->updateAccepted((int)$subshare['id'], false);
$result = true;
} catch (Exception $e) {
$this->logger->emergency('Could not update share', ['exception' => $e]);
$result = false;
}
} else {
try {
$this->writeShareToDb(
$share['remote'],
$share['share_token'],
$share['password'],
$share['name'],
$share['owner'],
$this->uid,
$share['mountpoint'],
$share['mountpoint_hash'],
0,
$share['remote_id'],
$id,
$share['share_type']);
$result = true;
} catch (Exception $e) {
$this->logger->emergency('Could not create share', ['exception' => $e]);
$result = false;
}
}
$this->processNotification($id);
}
return $result;
}
public function processNotification(int $remoteShare): void {
$filter = $this->notificationManager->createNotification();
$filter->setApp('files_sharing')
->setUser($this->uid)
->setObject('remote_share', (string)$remoteShare);
$this->notificationManager->markProcessed($filter);
}
/**
* inform remote server whether server-to-server share was accepted/declined
*
* @param string $remote
* @param string $token
* @param string $remoteId Share id on the remote host
* @param string $feedback
* @return boolean
*/
private function sendFeedbackToRemote($remote, $token, $remoteId, $feedback) {
$result = $this->tryOCMEndPoint($remote, $token, $remoteId, $feedback);
if (is_array($result)) {
return true;
}
$federationEndpoints = $this->discoveryService->discover($remote, 'FEDERATED_SHARING');
$endpoint = $federationEndpoints['share'] ?? '/ocs/v2.php/cloud/shares';
$url = rtrim($remote, '/') . $endpoint . '/' . $remoteId . '/' . $feedback . '?format=' . Share::RESPONSE_FORMAT;
$fields = ['token' => $token];
$client = $this->clientService->newClient();
try {
$response = $client->post(
$url,
[
'body' => $fields,
'connect_timeout' => 10,
]
);
} catch (\Exception $e) {
return false;
}
$status = json_decode($response->getBody(), true);
return ($status['ocs']['meta']['statuscode'] === 100 || $status['ocs']['meta']['statuscode'] === 200);
}
/**
* try send accept message to ocm end-point
*
* @param string $remoteDomain
* @param string $token
* @param string $remoteId id of the share
* @param string $feedback
* @return array|false
*/
protected function tryOCMEndPoint($remoteDomain, $token, $remoteId, $feedback) {
switch ($feedback) {
case 'accept':
$notification = $this->cloudFederationFactory->getCloudFederationNotification();
$notification->setMessage(
'SHARE_ACCEPTED',
'file',
$remoteId,
[
'sharedSecret' => $token,
'message' => 'Recipient accept the share'
]
);
return $this->cloudFederationProviderManager->sendNotification($remoteDomain, $notification);
case 'decline':
$notification = $this->cloudFederationFactory->getCloudFederationNotification();
$notification->setMessage(
'SHARE_DECLINED',
'file',
$remoteId,
[
'sharedSecret' => $token,
'message' => 'Recipient declined the share'
]
);
return $this->cloudFederationProviderManager->sendNotification($remoteDomain, $notification);
}
return false;
}
/**
* remove '/user/files' from the path and trailing slashes
*
* @param string $path
* @return string
*/
protected function stripPath($path) {
$prefix = '/' . $this->uid . '/files';
return rtrim(substr($path, strlen($prefix)), '/');
}
public function getMount($data) {
$data['manager'] = $this;
$mountPoint = '/' . $this->uid . '/files' . $data['mountpoint'];
$data['mountpoint'] = $mountPoint;
$data['certificateManager'] = \OC::$server->getCertificateManager();
return new Mount(self::STORAGE, $mountPoint, $data, $this, $this->storageLoader);
}
/**
* @param array $data
* @return Mount
*/
protected function mountShare($data) {
$mount = $this->getMount($data);
$this->mountManager->addMount($mount);
return $mount;
}
/**
* @return \OC\Files\Mount\Manager
*/
public function getMountManager() {
return $this->mountManager;
}
/**
* @param string $source
* @param string $target
* @return bool
*/
public function setMountPoint($source, $target) {
$source = $this->stripPath($source);
$target = $this->stripPath($target);
$sourceHash = md5($source);
$targetHash = md5($target);
$query = $this->connection->prepare('
UPDATE `*PREFIX*share_external`
SET `mountpoint` = ?, `mountpoint_hash` = ?
WHERE `mountpoint_hash` = ?
AND `user` = ?
');
$result = (bool)$query->execute([$target, $targetHash, $sourceHash, $this->uid]);
$this->eventDispatcher->dispatchTyped(new Files\Events\InvalidateMountCacheEvent($this->userManager->get($this->uid)));
return $result;
}
public function removeShare($mountPoint): bool {
try {
$mountPointObj = $this->mountManager->find($mountPoint);
} catch (NotFoundException $e) {
$this->logger->error('Mount point to remove share not found', ['mountPoint' => $mountPoint]);
return false;
}
if (!$mountPointObj instanceof Mount) {
$this->logger->error('Mount point to remove share is not an external share, share probably doesn\'t exist', ['mountPoint' => $mountPoint]);
return false;
}
$id = $mountPointObj->getStorage()->getCache()->getId('');
$mountPoint = $this->stripPath($mountPoint);
$hash = md5($mountPoint);
try {
$getShare = $this->connection->prepare('
SELECT `remote`, `share_token`, `remote_id`, `share_type`, `id`
FROM `*PREFIX*share_external`
WHERE `mountpoint_hash` = ? AND `user` = ?');
$result = $getShare->execute([$hash, $this->uid]);
$share = $result->fetch();
$result->closeCursor();
if ($share !== false && (int)$share['share_type'] === IShare::TYPE_USER) {
try {
$this->sendFeedbackToRemote($share['remote'], $share['share_token'], $share['remote_id'], 'decline');
} catch (\Throwable $e) {
// if we fail to notify the remote (probably cause the remote is down)
// we still want the share to be gone to prevent undeletable remotes
}
$query = $this->connection->prepare('
DELETE FROM `*PREFIX*share_external`
WHERE `id` = ?
');
$deleteResult = $query->execute([(int)$share['id']]);
$deleteResult->closeCursor();
} elseif ($share !== false && (int)$share['share_type'] === IShare::TYPE_GROUP) {
$this->updateAccepted((int)$share['id'], false);
}
$this->removeReShares($id);
} catch (\Doctrine\DBAL\Exception $ex) {
$this->logger->emergency('Could not update share', ['exception' => $ex]);
return false;
}
return true;
}
/**
* remove re-shares from share table and mapping in the federated_reshares table
*
* @param $mountPointId
*/
protected function removeReShares($mountPointId) {
$selectQuery = $this->connection->getQueryBuilder();
$query = $this->connection->getQueryBuilder();
$selectQuery->select('id')->from('share')
->where($selectQuery->expr()->eq('file_source', $query->createNamedParameter($mountPointId)));
$select = $selectQuery->getSQL();
$query->delete('federated_reshares')
->where($query->expr()->in('share_id', $query->createFunction($select)));
$query->execute();
$deleteReShares = $this->connection->getQueryBuilder();
$deleteReShares->delete('share')
->where($deleteReShares->expr()->eq('file_source', $deleteReShares->createNamedParameter($mountPointId)));
$deleteReShares->execute();
}
/**
* remove all shares for user $uid if the user was deleted
*
* @param string $uid
*/
public function removeUserShares($uid): bool {
try {
// TODO: use query builder
$getShare = $this->connection->prepare('
SELECT `id`, `remote`, `share_type`, `share_token`, `remote_id`
FROM `*PREFIX*share_external`
WHERE `user` = ?
AND `share_type` = ?');
$result = $getShare->execute([$uid, IShare::TYPE_USER]);
$shares = $result->fetchAll();
$result->closeCursor();
foreach ($shares as $share) {
$this->sendFeedbackToRemote($share['remote'], $share['share_token'], $share['remote_id'], 'decline');
}
$qb = $this->connection->getQueryBuilder();
$qb->delete('share_external')
// user field can specify a user or a group
->where($qb->expr()->eq('user', $qb->createNamedParameter($uid)))
->andWhere(
$qb->expr()->orX(
// delete direct shares
$qb->expr()->eq('share_type', $qb->expr()->literal(IShare::TYPE_USER)),
// delete sub-shares of group shares for that user
$qb->expr()->andX(
$qb->expr()->eq('share_type', $qb->expr()->literal(IShare::TYPE_GROUP)),
$qb->expr()->neq('parent', $qb->expr()->literal(-1)),
)
)
);
$qb->execute();
} catch (\Doctrine\DBAL\Exception $ex) {
$this->logger->emergency('Could not delete user shares', ['exception' => $ex]);
return false;
}
return true;
}
public function removeGroupShares($gid): bool {
try {
$getShare = $this->connection->prepare('
SELECT `id`, `remote`, `share_type`, `share_token`, `remote_id`
FROM `*PREFIX*share_external`
WHERE `user` = ?
AND `share_type` = ?');
$result = $getShare->execute([$gid, IShare::TYPE_GROUP]);
$shares = $result->fetchAll();
$result->closeCursor();
$deletedGroupShares = [];
$qb = $this->connection->getQueryBuilder();
// delete group share entry and matching sub-entries
$qb->delete('share_external')
->where(
$qb->expr()->orX(
$qb->expr()->eq('id', $qb->createParameter('share_id')),
$qb->expr()->eq('parent', $qb->createParameter('share_parent_id'))
)
);
foreach ($shares as $share) {
$qb->setParameter('share_id', $share['id']);
$qb->setParameter('share_parent_id', $share['id']);
$qb->execute();
}
} catch (\Doctrine\DBAL\Exception $ex) {
$this->logger->emergency('Could not delete user shares', ['exception' => $ex]);
return false;
}
return true;
}
/**
* return a list of shares which are not yet accepted by the user
*
* @return array list of open server-to-server shares
*/
public function getOpenShares() {
return $this->getShares(false);
}
/**
* return a list of shares which are accepted by the user
*
* @return array list of accepted server-to-server shares
*/
public function getAcceptedShares() {
return $this->getShares(true);
}
/**
* return a list of shares for the user
*
* @param bool|null $accepted True for accepted only,
* false for not accepted,
* null for all shares of the user
* @return array list of open server-to-server shares
*/
private function getShares($accepted) {
$user = $this->userManager->get($this->uid);
$groups = $this->groupManager->getUserGroups($user);
$userGroups = [];
foreach ($groups as $group) {
$userGroups[] = $group->getGID();
}
$qb = $this->connection->getQueryBuilder();
$qb->select('id', 'share_type', 'parent', 'remote', 'remote_id', 'share_token', 'name', 'owner', 'user', 'mountpoint', 'accepted')
->from('share_external')
->where(
$qb->expr()->orX(
$qb->expr()->eq('user', $qb->createNamedParameter($this->uid)),
$qb->expr()->in(
'user',
$qb->createNamedParameter($userGroups, IQueryBuilder::PARAM_STR_ARRAY)
)
)
)
->orderBy('id', 'ASC');
try {
$result = $qb->execute();
$shares = $result->fetchAll();
$result->closeCursor();
// remove parent group share entry if we have a specific user share entry for the user
$toRemove = [];
foreach ($shares as $share) {
if ((int)$share['share_type'] === IShare::TYPE_GROUP && (int)$share['parent'] > 0) {
$toRemove[] = $share['parent'];
}
}
$shares = array_filter($shares, function ($share) use ($toRemove) {
return !in_array($share['id'], $toRemove, true);
});
if (!is_null($accepted)) {
$shares = array_filter($shares, function ($share) use ($accepted) {
return (bool)$share['accepted'] === $accepted;
});
}
return array_values($shares);
} catch (\Doctrine\DBAL\Exception $e) {
$this->logger->emergency('Error when retrieving shares', ['exception' => $e]);
return [];
}
}
}
+79
View File
@@ -0,0 +1,79 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Bjoern Schiessle <bjoern@schiessle.org>
* @author Björn Schießle <bjoern@schiessle.org>
* @author Robin Appelman <robin@icewind.nl>
* @author szaimen <szaimen@e.mail.de>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* 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, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OCA\Files_Sharing\External;
use OC\Files\Mount\MountPoint;
use OC\Files\Mount\MoveableMount;
use OCA\Files_Sharing\ISharedMountPoint;
class Mount extends MountPoint implements MoveableMount, ISharedMountPoint {
/**
* @var \OCA\Files_Sharing\External\Manager
*/
protected $manager;
/**
* @param string|\OC\Files\Storage\Storage $storage
* @param string $mountpoint
* @param array $options
* @param \OCA\Files_Sharing\External\Manager $manager
* @param \OC\Files\Storage\StorageFactory $loader
*/
public function __construct($storage, $mountpoint, $options, $manager, $loader = null) {
parent::__construct($storage, $mountpoint, $options, $loader, null, null, MountProvider::class);
$this->manager = $manager;
}
/**
* Move the mount point to $target
*
* @param string $target the target mount point
* @return bool
*/
public function moveMount($target) {
$result = $this->manager->setMountPoint($this->mountPoint, $target);
$this->setMountPoint($target);
return $result;
}
/**
* Remove the mount points
*/
public function removeMount(): bool {
return $this->manager->removeShare($this->mountPoint);
}
/**
* Get the type of mount point, used to distinguish things like shares and external storage
* in the web interface
*
* @return string
*/
public function getMountType() {
return 'shared';
}
}
@@ -0,0 +1,91 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Lukas Reschke <lukas@statuscode.ch>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Robin Appelman <robin@icewind.nl>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* 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, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OCA\Files_Sharing\External;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\Federation\ICloudIdManager;
use OCP\Files\Config\IMountProvider;
use OCP\Files\Storage\IStorageFactory;
use OCP\IDBConnection;
use OCP\IUser;
class MountProvider implements IMountProvider {
public const STORAGE = '\OCA\Files_Sharing\External\Storage';
/**
* @var \OCP\IDBConnection
*/
private $connection;
/**
* @var callable
*/
private $managerProvider;
/**
* @var ICloudIdManager
*/
private $cloudIdManager;
/**
* @param \OCP\IDBConnection $connection
* @param callable $managerProvider due to setup order we need a callable that return the manager instead of the manager itself
* @param ICloudIdManager $cloudIdManager
*/
public function __construct(IDBConnection $connection, callable $managerProvider, ICloudIdManager $cloudIdManager) {
$this->connection = $connection;
$this->managerProvider = $managerProvider;
$this->cloudIdManager = $cloudIdManager;
}
public function getMount(IUser $user, $data, IStorageFactory $storageFactory) {
$managerProvider = $this->managerProvider;
$manager = $managerProvider();
$data['manager'] = $manager;
$mountPoint = '/' . $user->getUID() . '/files/' . ltrim($data['mountpoint'], '/');
$data['mountpoint'] = $mountPoint;
$data['cloudId'] = $this->cloudIdManager->getCloudId($data['owner'], $data['remote']);
$data['certificateManager'] = \OC::$server->getCertificateManager();
$data['HttpClientService'] = \OC::$server->getHTTPClientService();
return new Mount(self::STORAGE, $mountPoint, $data, $manager, $storageFactory);
}
public function getMountsForUser(IUser $user, IStorageFactory $loader) {
$qb = $this->connection->getQueryBuilder();
$qb->select('remote', 'share_token', 'password', 'mountpoint', 'owner')
->from('share_external')
->where($qb->expr()->eq('user', $qb->createNamedParameter($user->getUID())))
->andWhere($qb->expr()->eq('accepted', $qb->createNamedParameter(1, IQueryBuilder::PARAM_INT)));
$result = $qb->executeQuery();
$mounts = [];
while ($row = $result->fetch()) {
$row['manager'] = $this;
$row['token'] = $row['share_token'];
$mounts[] = $this->getMount($user, $row, $loader);
}
$result->closeCursor();
return $mounts;
}
}
@@ -0,0 +1,71 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Lukas Reschke <lukas@statuscode.ch>
* @author Olivier Paroz <github@oparoz.com>
* @author Robin Appelman <robin@icewind.nl>
* @author Vincent Petry <vincent@nextcloud.com>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* 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, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OCA\Files_Sharing\External;
use OC\ForbiddenException;
use OCP\Files\NotFoundException;
use OCP\Files\StorageInvalidException;
use OCP\Files\StorageNotAvailableException;
class Scanner extends \OC\Files\Cache\Scanner {
/** @var \OCA\Files_Sharing\External\Storage */
protected $storage;
public function scan($path, $recursive = self::SCAN_RECURSIVE, $reuse = -1, $lock = true) {
// Disable locking for federated shares
parent::scan($path, $recursive, $reuse, false);
}
/**
* Scan a single file and store it in the cache.
* If an exception happened while accessing the external storage,
* the storage will be checked for availability and removed
* if it is not available any more.
*
* @param string $file file to scan
* @param int $reuseExisting
* @param int $parentId
* @param array | null $cacheData existing data in the cache for the file to be scanned
* @param bool $lock set to false to disable getting an additional read lock during scanning
* @return array | null an array of metadata of the scanned file
*/
public function scanFile($file, $reuseExisting = 0, $parentId = -1, $cacheData = null, $lock = true, $data = null) {
try {
return parent::scanFile($file, $reuseExisting, $parentId, $cacheData, $lock, $data);
} catch (ForbiddenException $e) {
$this->storage->checkStorageAvailability();
} catch (NotFoundException $e) {
// if the storage isn't found, the call to
// checkStorageAvailable() will verify it and remove it
// if appropriate
$this->storage->checkStorageAvailability();
} catch (StorageInvalidException $e) {
$this->storage->checkStorageAvailability();
} catch (StorageNotAvailableException $e) {
$this->storage->checkStorageAvailability();
}
}
}
+456
View File
@@ -0,0 +1,456 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Bjoern Schiessle <bjoern@schiessle.org>
* @author Björn Schießle <bjoern@schiessle.org>
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Daniel Kesselberg <mail@danielkesselberg.de>
* @author Joas Schilling <coding@schilljs.com>
* @author Lukas Reschke <lukas@statuscode.ch>
* @author Maxence Lange <maxence@artificial-owl.com>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Robin Appelman <robin@icewind.nl>
* @author Roeland Jago Douma <roeland@famdouma.nl>
* @author Thomas Müller <thomas.mueller@tmit.eu>
* @author Vincent Petry <vincent@nextcloud.com>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* 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, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OCA\Files_Sharing\External;
use GuzzleHttp\Exception\ClientException;
use GuzzleHttp\Exception\ConnectException;
use GuzzleHttp\Exception\RequestException;
use OC\Files\Storage\DAV;
use OC\ForbiddenException;
use OCA\Files_Sharing\External\Manager as ExternalShareManager;
use OCA\Files_Sharing\ISharedStorage;
use OCP\AppFramework\Http;
use OCP\Constants;
use OCP\Federation\ICloudId;
use OCP\Files\NotFoundException;
use OCP\Files\Storage\IDisableEncryptionStorage;
use OCP\Files\Storage\IReliableEtagStorage;
use OCP\Files\StorageInvalidException;
use OCP\Files\StorageNotAvailableException;
use OCP\Http\Client\IClientService;
use OCP\Http\Client\LocalServerException;
use OCP\ICacheFactory;
use OCP\IConfig;
use OCP\OCM\Exceptions\OCMArgumentException;
use OCP\OCM\Exceptions\OCMProviderException;
use OCP\OCM\IOCMDiscoveryService;
use OCP\Server;
use Psr\Log\LoggerInterface;
class Storage extends DAV implements ISharedStorage, IDisableEncryptionStorage, IReliableEtagStorage {
private ICloudId $cloudId;
private string $mountPoint;
private string $token;
private ICacheFactory $memcacheFactory;
private IClientService $httpClient;
private bool $updateChecked = false;
private ExternalShareManager $manager;
private IConfig $config;
/**
* @param array{HttpClientService: IClientService, manager: ExternalShareManager, cloudId: ICloudId, mountpoint: string, token: string, password: ?string}|array $options
*/
public function __construct($options) {
$this->memcacheFactory = \OC::$server->getMemCacheFactory();
$this->httpClient = $options['HttpClientService'];
$this->manager = $options['manager'];
$this->cloudId = $options['cloudId'];
$this->logger = Server::get(LoggerInterface::class);
$discoveryService = Server::get(IOCMDiscoveryService::class);
$this->config = Server::get(IConfig::class);
// use default path to webdav if not found on discovery
try {
$ocmProvider = $discoveryService->discover($this->cloudId->getRemote());
$webDavEndpoint = $ocmProvider->extractProtocolEntry('file', 'webdav');
$remote = $ocmProvider->getEndPoint();
} catch (OCMProviderException|OCMArgumentException $e) {
$this->logger->notice('exception while retrieving webdav endpoint', ['exception' => $e]);
$webDavEndpoint = '/public.php/webdav';
$remote = $this->cloudId->getRemote();
}
$host = parse_url($remote, PHP_URL_HOST);
$port = parse_url($remote, PHP_URL_PORT);
$host .= (null === $port) ? '' : ':' . $port; // we add port if available
// in case remote NC is on a sub folder and using deprecated ocm provider
$tmpPath = rtrim(parse_url($this->cloudId->getRemote(), PHP_URL_PATH) ?? '', '/');
if (!str_starts_with($webDavEndpoint, $tmpPath)) {
$webDavEndpoint = $tmpPath . $webDavEndpoint;
}
$this->mountPoint = $options['mountpoint'];
$this->token = $options['token'];
parent::__construct(
[
'secure' => ((parse_url($remote, PHP_URL_SCHEME) ?? 'https') === 'https'),
'host' => $host,
'root' => $webDavEndpoint,
'user' => $options['token'],
'authType' => \Sabre\DAV\Client::AUTH_BASIC,
'password' => (string)$options['password']
]
);
}
public function getWatcher($path = '', $storage = null) {
if (!$storage) {
$storage = $this;
}
if (!isset($this->watcher)) {
$this->watcher = new Watcher($storage);
$this->watcher->setPolicy(\OC\Files\Cache\Watcher::CHECK_ONCE);
}
return $this->watcher;
}
public function getRemoteUser(): string {
return $this->cloudId->getUser();
}
public function getRemote(): string {
return $this->cloudId->getRemote();
}
public function getMountPoint(): string {
return $this->mountPoint;
}
public function getToken(): string {
return $this->token;
}
public function getPassword(): ?string {
return $this->password;
}
/**
* Get id of the mount point.
* @return string
*/
public function getId() {
return 'shared::' . md5($this->token . '@' . $this->getRemote());
}
public function getCache($path = '', $storage = null) {
if (is_null($this->cache)) {
$this->cache = new Cache($this, $this->cloudId);
}
return $this->cache;
}
/**
* @param string $path
* @param \OC\Files\Storage\Storage $storage
* @return \OCA\Files_Sharing\External\Scanner
*/
public function getScanner($path = '', $storage = null) {
if (!$storage) {
$storage = $this;
}
if (!isset($this->scanner)) {
$this->scanner = new Scanner($storage);
}
return $this->scanner;
}
/**
* Check if a file or folder has been updated since $time
*
* @param string $path
* @param int $time
* @throws \OCP\Files\StorageNotAvailableException
* @throws \OCP\Files\StorageInvalidException
* @return bool
*/
public function hasUpdated($path, $time) {
// since for owncloud webdav servers we can rely on etag propagation we only need to check the root of the storage
// because of that we only do one check for the entire storage per request
if ($this->updateChecked) {
return false;
}
$this->updateChecked = true;
try {
return parent::hasUpdated('', $time);
} catch (StorageInvalidException $e) {
// check if it needs to be removed
$this->checkStorageAvailability();
throw $e;
} catch (StorageNotAvailableException $e) {
// check if it needs to be removed or just temp unavailable
$this->checkStorageAvailability();
throw $e;
}
}
public function test() {
try {
return parent::test();
} catch (StorageInvalidException $e) {
// check if it needs to be removed
$this->checkStorageAvailability();
throw $e;
} catch (StorageNotAvailableException $e) {
// check if it needs to be removed or just temp unavailable
$this->checkStorageAvailability();
throw $e;
}
}
/**
* Check whether this storage is permanently or temporarily
* unavailable
*
* @throws \OCP\Files\StorageNotAvailableException
* @throws \OCP\Files\StorageInvalidException
*/
public function checkStorageAvailability() {
// see if we can find out why the share is unavailable
try {
$this->getShareInfo(0);
} catch (NotFoundException $e) {
// a 404 can either mean that the share no longer exists or there is no Nextcloud on the remote
if ($this->testRemote()) {
// valid Nextcloud instance means that the public share no longer exists
// since this is permanent (re-sharing the file will create a new token)
// we remove the invalid storage
$this->manager->removeShare($this->mountPoint);
$this->manager->getMountManager()->removeMount($this->mountPoint);
throw new StorageInvalidException("Remote share not found", 0, $e);
} else {
// Nextcloud instance is gone, likely to be a temporary server configuration error
throw new StorageNotAvailableException("No nextcloud instance found at remote", 0, $e);
}
} catch (ForbiddenException $e) {
// auth error, remove share for now (provide a dialog in the future)
$this->manager->removeShare($this->mountPoint);
$this->manager->getMountManager()->removeMount($this->mountPoint);
throw new StorageInvalidException("Auth error when getting remote share");
} catch (\GuzzleHttp\Exception\ConnectException $e) {
throw new StorageNotAvailableException("Failed to connect to remote instance", 0, $e);
} catch (\GuzzleHttp\Exception\RequestException $e) {
throw new StorageNotAvailableException("Error while sending request to remote instance", 0, $e);
}
}
public function file_exists($path) {
if ($path === '') {
return true;
} else {
return parent::file_exists($path);
}
}
/**
* Check if the configured remote is a valid federated share provider
*
* @return bool
*/
protected function testRemote(): bool {
try {
return $this->testRemoteUrl($this->getRemote() . '/ocm-provider/index.php')
|| $this->testRemoteUrl($this->getRemote() . '/ocm-provider/')
|| $this->testRemoteUrl($this->getRemote() . '/status.php');
} catch (\Exception $e) {
return false;
}
}
private function testRemoteUrl(string $url): bool {
$cache = $this->memcacheFactory->createDistributed('files_sharing_remote_url');
$cached = $cache->get($url);
if ($cached !== null) {
return (bool)$cached;
}
$client = $this->httpClient->newClient();
try {
$result = $client->get($url, [
'timeout' => 10,
'connect_timeout' => 10,
'verify' => !$this->config->getSystemValueBool('sharing.federation.allowSelfSignedCertificates', false),
])->getBody();
$data = json_decode($result);
$returnValue = (is_object($data) && !empty($data->version));
} catch (ConnectException $e) {
$returnValue = false;
} catch (ClientException $e) {
$returnValue = false;
} catch (RequestException $e) {
$returnValue = false;
}
$cache->set($url, $returnValue, 60 * 60 * 24);
return $returnValue;
}
/**
* Check whether the remote is an ownCloud/Nextcloud. This is needed since some sharing
* features are not standardized.
*
* @throws LocalServerException
*/
public function remoteIsOwnCloud(): bool {
if (defined('PHPUNIT_RUN') || !$this->testRemoteUrl($this->getRemote() . '/status.php')) {
return false;
}
return true;
}
/**
* @return mixed
* @throws ForbiddenException
* @throws NotFoundException
* @throws \Exception
*/
public function getShareInfo(int $depth = -1) {
$remote = $this->getRemote();
$token = $this->getToken();
$password = $this->getPassword();
try {
// If remote is not an ownCloud do not try to get any share info
if (!$this->remoteIsOwnCloud()) {
return ['status' => 'unsupported'];
}
} catch (LocalServerException $e) {
// throw this to be on the safe side: the share will still be visible
// in the UI in case the failure is intermittent, and the user will
// be able to decide whether to remove it if it's really gone
throw new StorageNotAvailableException();
}
$url = rtrim($remote, '/') . '/index.php/apps/files_sharing/shareinfo?t=' . $token;
// TODO: DI
$client = \OC::$server->getHTTPClientService()->newClient();
try {
$response = $client->post($url, [
'body' => ['password' => $password, 'depth' => $depth],
'timeout' => 10,
'connect_timeout' => 10,
]);
} catch (\GuzzleHttp\Exception\RequestException $e) {
if ($e->getCode() === Http::STATUS_UNAUTHORIZED || $e->getCode() === Http::STATUS_FORBIDDEN) {
throw new ForbiddenException();
}
if ($e->getCode() === Http::STATUS_NOT_FOUND) {
throw new NotFoundException();
}
// throw this to be on the safe side: the share will still be visible
// in the UI in case the failure is intermittent, and the user will
// be able to decide whether to remove it if it's really gone
throw new StorageNotAvailableException();
}
return json_decode($response->getBody(), true);
}
public function getOwner($path) {
return $this->cloudId->getDisplayId();
}
public function isSharable($path): bool {
if (\OCP\Util::isSharingDisabledForUser() || !\OC\Share\Share::isResharingAllowed()) {
return false;
}
return (bool)($this->getPermissions($path) & Constants::PERMISSION_SHARE);
}
public function getPermissions($path): int {
$response = $this->propfind($path);
// old federated sharing permissions
if (isset($response['{http://open-collaboration-services.org/ns}share-permissions'])) {
$permissions = (int)$response['{http://open-collaboration-services.org/ns}share-permissions'];
} elseif (isset($response['{http://open-cloud-mesh.org/ns}share-permissions'])) {
// permissions provided by the OCM API
$permissions = $this->ocmPermissions2ncPermissions($response['{http://open-collaboration-services.org/ns}share-permissions'], $path);
} elseif (isset($response['{http://owncloud.org/ns}permissions'])) {
return $this->parsePermissions($response['{http://owncloud.org/ns}permissions']);
} else {
// use default permission if remote server doesn't provide the share permissions
$permissions = $this->getDefaultPermissions($path);
}
return $permissions;
}
public function needsPartFile() {
return false;
}
/**
* Translate OCM Permissions to Nextcloud permissions
*
* @param string $ocmPermissions json encoded OCM permissions
* @param string $path path to file
* @return int
*/
protected function ocmPermissions2ncPermissions(string $ocmPermissions, string $path): int {
try {
$ocmPermissions = json_decode($ocmPermissions);
$ncPermissions = 0;
foreach ($ocmPermissions as $permission) {
switch (strtolower($permission)) {
case 'read':
$ncPermissions += Constants::PERMISSION_READ;
break;
case 'write':
$ncPermissions += Constants::PERMISSION_CREATE + Constants::PERMISSION_UPDATE;
break;
case 'share':
$ncPermissions += Constants::PERMISSION_SHARE;
break;
default:
throw new \Exception();
}
}
} catch (\Exception $e) {
$ncPermissions = $this->getDefaultPermissions($path);
}
return $ncPermissions;
}
/**
* Calculate the default permissions in case no permissions are provided
*/
protected function getDefaultPermissions(string $path): int {
if ($this->is_dir($path)) {
$permissions = Constants::PERMISSION_ALL;
} else {
$permissions = Constants::PERMISSION_ALL & ~Constants::PERMISSION_CREATE;
}
return $permissions;
}
public function free_space($path) {
return parent::free_space("");
}
}
@@ -0,0 +1,33 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Robin Appelman <robin@icewind.nl>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* 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, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OCA\Files_Sharing\External;
class Watcher extends \OC\Files\Cache\Watcher {
/**
* remove deleted files in $path from the cache
*
* @param string $path
*/
public function cleanFolder($path) {
// not needed, the scanner takes care of this
}
}
@@ -0,0 +1,112 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Björn Schießle <bjoern@schiessle.org>
* @author Daniel Kesselberg <mail@danielkesselberg.de>
* @author Lukas Reschke <lukas@statuscode.ch>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Robin Appelman <robin@icewind.nl>
* @author Roeland Jago Douma <roeland@famdouma.nl>
* @author Vincent Petry <vincent@nextcloud.com>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* 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, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OCA\Files_Sharing;
use OC\Files\Filesystem;
use OC\Files\View;
use OCA\Files_Sharing\AppInfo\Application;
class Helper {
public static function registerHooks() {
\OCP\Util::connectHook('OC_Filesystem', 'post_rename', '\OCA\Files_Sharing\Updater', 'renameHook');
\OCP\Util::connectHook('OC_Filesystem', 'post_delete', '\OCA\Files_Sharing\Hooks', 'unshareChildren');
\OCP\Util::connectHook('OC_User', 'post_deleteUser', '\OCA\Files_Sharing\Hooks', 'deleteUser');
}
/**
* check if file name already exists and generate unique target
*
* @param string $path
* @param array $excludeList
* @param View $view
* @return string $path
*/
public static function generateUniqueTarget($path, $excludeList, $view) {
$pathinfo = pathinfo($path);
$ext = isset($pathinfo['extension']) ? '.'.$pathinfo['extension'] : '';
$name = $pathinfo['filename'];
$dir = $pathinfo['dirname'];
$i = 2;
while ($view->file_exists($path) || in_array($path, $excludeList)) {
$path = Filesystem::normalizePath($dir . '/' . $name . ' ('.$i.')' . $ext);
$i++;
}
return $path;
}
/**
* get default share folder
*
* @param \OC\Files\View|null $view
* @param string|null $userId
* @return string
*/
public static function getShareFolder(View $view = null, string $userId = null): string {
if ($view === null) {
$view = Filesystem::getView();
}
$config = \OC::$server->getConfig();
$systemDefault = $config->getSystemValue('share_folder', '/');
$allowCustomShareFolder = $config->getSystemValueBool('sharing.allow_custom_share_folder', true);
// Init custom shareFolder
$shareFolder = $systemDefault;
if ($userId !== null && $allowCustomShareFolder) {
$shareFolder = $config->getUserValue($userId, Application::APP_ID, 'share_folder', $systemDefault);
}
// Verify and sanitize path
$shareFolder = Filesystem::normalizePath($shareFolder);
// Init path if folder doesn't exists
if (!$view->file_exists($shareFolder)) {
$dir = '';
$subdirs = explode('/', $shareFolder);
foreach ($subdirs as $subdir) {
$dir = $dir . '/' . $subdir;
if (!$view->is_dir($dir)) {
$view->mkdir($dir);
}
}
}
return $shareFolder;
}
/**
* set default share folder
*
* @param string $shareFolder
*/
public static function setShareFolder($shareFolder) {
\OC::$server->getConfig()->setSystemValue('share_folder', $shareFolder);
}
}
@@ -0,0 +1,52 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Bjoern Schiessle <bjoern@schiessle.org>
* @author Björn Schießle <bjoern@schiessle.org>
* @author Joas Schilling <coding@schilljs.com>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Robin Appelman <robin@icewind.nl>
* @author Roeland Jago Douma <roeland@famdouma.nl>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* 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, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OCA\Files_Sharing;
use OC\Files\Filesystem;
class Hooks {
public static function deleteUser($params) {
$manager = \OC::$server->get(External\Manager::class);
$manager->removeUserShares($params['uid']);
}
public static function unshareChildren($params) {
$path = Filesystem::getView()->getAbsolutePath($params['path']);
$view = new \OC\Files\View('/');
// find share mount points within $path and unmount them
$mountManager = \OC\Files\Filesystem::getMountManager();
$mountedShares = $mountManager->findIn($path);
foreach ($mountedShares as $mount) {
if ($mount->getStorage()->instanceOfStorage(ISharedStorage::class)) {
$mountPoint = $mount->getMountPoint();
$view->unlink($mountPoint);
}
}
}
}
@@ -0,0 +1,28 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2023 Robin Appelman <robin@icewind.nl>
*
* @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 <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Files_Sharing;
interface ISharedMountPoint {
}
@@ -0,0 +1,28 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Robin Appelman <robin@icewind.nl>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* 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, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OCA\Files_Sharing;
use OCP\Files\Storage\IStorage;
interface ISharedStorage extends IStorage {
}
@@ -0,0 +1,50 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2019, Roeland Jago Douma <roeland@famdouma.nl>
*
* @author John Molakvoæ <skjnldsv@protonmail.com>
*
* @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 <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Files_Sharing\Listener;
use OCA\Files\Event\LoadAdditionalScriptsEvent;
use OCA\Files_Sharing\AppInfo\Application;
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventListener;
use OCP\Share\IManager;
use OCP\Util;
class LoadAdditionalListener implements IEventListener {
public function handle(Event $event): void {
if (!($event instanceof LoadAdditionalScriptsEvent)) {
return;
}
// After files for the breadcrumb share indicator
Util::addScript(Application::APP_ID, 'additionalScripts', 'files');
Util::addStyle(Application::APP_ID, 'icons');
$shareManager = \OC::$server->get(IManager::class);
if ($shareManager->shareApiEnabled() && class_exists('\OCA\Files\App')) {
Util::addInitScript(Application::APP_ID, 'init');
}
}
}
@@ -0,0 +1,52 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2019, Roeland Jago Douma <roeland@famdouma.nl>
*
* @author John Molakvoæ <skjnldsv@protonmail.com>
*
* @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 <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Files_Sharing\Listener;
use OCA\Files\Event\LoadSidebar;
use OCA\Files_Sharing\AppInfo\Application;
use OCP\AppFramework\Services\IInitialState;
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventListener;
use OCP\Share\IManager;
use OCP\Util;
/**
* @template-implements IEventListener<Event>
*/
class LoadSidebarListener implements IEventListener {
public function __construct(private IInitialState $initialState, private IManager $shareManager) {
}
public function handle(Event $event): void {
if (!($event instanceof LoadSidebar)) {
return;
}
Util::addScript(Application::APP_ID, 'files_sharing_tab', 'files');
}
}
@@ -0,0 +1,88 @@
<?php
declare(strict_types=1);
/**
* @copyright 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author John Molakvoæ <skjnldsv@protonmail.com>
*
* @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 <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Files_Sharing\Listener;
use OCP\Contacts\Events\ContactInteractedWithEvent;
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\EventDispatcher\IEventListener;
use OCP\IUserManager;
use OCP\Share\Events\ShareCreatedEvent;
use OCP\Share\IShare;
use Psr\Log\LoggerInterface;
use function in_array;
class ShareInteractionListener implements IEventListener {
private const SUPPORTED_SHARE_TYPES = [
IShare::TYPE_USER,
IShare::TYPE_EMAIL,
IShare::TYPE_REMOTE,
];
public function __construct(
private IEventDispatcher $dispatcher,
private IUserManager $userManager,
private LoggerInterface $logger,
) {
}
public function handle(Event $event): void {
if (!($event instanceof ShareCreatedEvent)) {
// Unrelated
return;
}
$share = $event->getShare();
if (!in_array($share->getShareType(), self::SUPPORTED_SHARE_TYPES, true)) {
$this->logger->debug('Share type does not allow to emit interaction event');
return;
}
$actor = $this->userManager->get($share->getSharedBy());
$sharedWith = $this->userManager->get($share->getSharedWith());
if ($actor === null) {
$this->logger->warning('Share was not created by a user, can\'t emit interaction event');
return;
}
$interactionEvent = new ContactInteractedWithEvent($actor);
switch ($share->getShareType()) {
case IShare::TYPE_USER:
$interactionEvent->setUid($share->getSharedWith());
if ($sharedWith !== null) {
$interactionEvent->setFederatedCloudId($sharedWith->getCloudId());
}
break;
case IShare::TYPE_EMAIL:
$interactionEvent->setEmail($share->getSharedWith());
break;
case IShare::TYPE_REMOTE:
$interactionEvent->setFederatedCloudId($share->getSharedWith());
break;
}
$this->dispatcher->dispatchTyped($interactionEvent);
}
}
@@ -0,0 +1,82 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2020, Roeland Jago Douma <roeland@famdouma.nl>
*
* @author Roeland Jago Douma <roeland@famdouma.nl>
*
* @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 <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Files_Sharing\Listener;
use OCA\Files_Sharing\AppInfo\Application;
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventListener;
use OCP\Group\Events\UserAddedEvent;
use OCP\IConfig;
use OCP\Share\IManager;
use OCP\Share\IShare;
class UserAddedToGroupListener implements IEventListener {
/** @var IManager */
private $shareManager;
/** @var IConfig */
private $config;
public function __construct(IManager $shareManager, IConfig $config) {
$this->shareManager = $shareManager;
$this->config = $config;
}
public function handle(Event $event): void {
if (!($event instanceof UserAddedEvent)) {
return;
}
$user = $event->getUser();
$group = $event->getGroup();
// This user doesn't have autoaccept so we can skip it all
if (!$this->hasAutoAccept($user->getUID())) {
return;
}
// Get all group shares this user has access to now to filter later
$shares = $this->shareManager->getSharedWith($user->getUID(), IShare::TYPE_GROUP, null, -1);
foreach ($shares as $share) {
// If this is not the new group we can skip it
if ($share->getSharedWith() !== $group->getGID()) {
continue;
}
// Accept the share if needed
$this->shareManager->acceptShare($share, $user->getUID());
}
}
private function hasAutoAccept(string $userId): bool {
$defaultAcceptSystemConfig = $this->config->getSystemValueBool('sharing.enable_share_accept', false) ? 'no' : 'yes';
$acceptDefault = $this->config->getUserValue($userId, Application::APP_ID, 'default_accept', $defaultAcceptSystemConfig) === 'yes';
return (!$this->config->getSystemValueBool('sharing.force_share_accept', false) && $acceptDefault);
}
}
@@ -0,0 +1,84 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2019, Roeland Jago Douma <roeland@famdouma.nl>
*
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Julius Härtl <jus@bitgrid.net>
* @author Roeland Jago Douma <roeland@famdouma.nl>
*
* @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 <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Files_Sharing\Listener;
use OCA\Files_Sharing\AppInfo\Application;
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventListener;
use OCP\IConfig;
use OCP\IGroupManager;
use OCP\Share\Events\ShareCreatedEvent;
use OCP\Share\IManager;
use OCP\Share\IShare;
class UserShareAcceptanceListener implements IEventListener {
/** @var IConfig */
private $config;
/** @var IManager */
private $shareManager;
/** @var IGroupManager */
private $groupManager;
public function __construct(IConfig $config, IManager $shareManager, IGroupManager $groupManager) {
$this->config = $config;
$this->shareManager = $shareManager;
$this->groupManager = $groupManager;
}
public function handle(Event $event): void {
if (!($event instanceof ShareCreatedEvent)) {
return;
}
$share = $event->getShare();
if ($share->getShareType() === IShare::TYPE_USER) {
$this->handleAutoAccept($share, $share->getSharedWith());
} elseif ($share->getShareType() === IShare::TYPE_GROUP) {
$group = $this->groupManager->get($share->getSharedWith());
if ($group === null) {
return;
}
$users = $group->getUsers();
foreach ($users as $user) {
$this->handleAutoAccept($share, $user->getUID());
}
}
}
private function handleAutoAccept(IShare $share, string $userId) {
$defaultAcceptSystemConfig = $this->config->getSystemValueBool('sharing.enable_share_accept', false) ? 'no' : 'yes';
$acceptDefault = $this->config->getUserValue($userId, Application::APP_ID, 'default_accept', $defaultAcceptSystemConfig) === 'yes';
if (!$this->config->getSystemValueBool('sharing.force_share_accept', false) && $acceptDefault) {
$this->shareManager->acceptShare($share, $userId);
}
}
}
@@ -0,0 +1,75 @@
<?php
/**
* @copyright Copyright (c) 2016 Roeland Jago Douma <roeland@famdouma.nl>
*
* @author Lukas Reschke <lukas@statuscode.ch>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Roeland Jago Douma <roeland@famdouma.nl>
*
* @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 <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Files_Sharing\Middleware;
use OCA\Files_Sharing\Controller\ShareAPIController;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\Response;
use OCP\AppFramework\Middleware;
use OCP\AppFramework\OCS\OCSNotFoundException;
use OCP\IL10N;
use OCP\Share\IManager;
class OCSShareAPIMiddleware extends Middleware {
/** @var IManager */
private $shareManager;
/** @var IL10N */
private $l;
public function __construct(IManager $shareManager,
IL10N $l) {
$this->shareManager = $shareManager;
$this->l = $l;
}
/**
* @param Controller $controller
* @param string $methodName
*
* @throws OCSNotFoundException
*/
public function beforeController($controller, $methodName) {
if ($controller instanceof ShareAPIController) {
if (!$this->shareManager->shareApiEnabled()) {
throw new OCSNotFoundException($this->l->t('Share API is disabled'));
}
}
}
/**
* @param Controller $controller
* @param string $methodName
* @param Response $response
* @return Response
*/
public function afterController($controller, $methodName, Response $response) {
if ($controller instanceof ShareAPIController) {
/** @var ShareAPIController $controller */
$controller->cleanup();
}
return $response;
}
}
@@ -0,0 +1,106 @@
<?php
/**
* @copyright Copyright (c) 2016 Roeland Jago Douma <roeland@famdouma.nl>
*
* @author Morris Jobke <hey@morrisjobke.de>
* @author Roeland Jago Douma <roeland@famdouma.nl>
*
* @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 <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Files_Sharing\Middleware;
use OCA\Files_Sharing\Controller\ShareInfoController;
use OCA\Files_Sharing\Exceptions\S2SException;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\JSONResponse;
use OCP\AppFramework\Http\Response;
use OCP\AppFramework\Middleware;
use OCP\Share\IManager;
class ShareInfoMiddleware extends Middleware {
/** @var IManager */
private $shareManager;
public function __construct(IManager $shareManager) {
$this->shareManager = $shareManager;
}
/**
* @param Controller $controller
* @param string $methodName
* @throws S2SException
*/
public function beforeController($controller, $methodName) {
if (!($controller instanceof ShareInfoController)) {
return;
}
if (!$this->shareManager->outgoingServer2ServerSharesAllowed()) {
throw new S2SException();
}
}
/**
* @param Controller $controller
* @param string $methodName
* @param \Exception $exception
* @throws \Exception
* @return Response
*/
public function afterException($controller, $methodName, \Exception $exception) {
if (!($controller instanceof ShareInfoController)) {
throw $exception;
}
if ($exception instanceof S2SException) {
return new JSONResponse([], Http::STATUS_NOT_FOUND);
}
throw $exception;
}
/**
* @param Controller $controller
* @param string $methodName
* @param Response $response
* @return Response
*/
public function afterController($controller, $methodName, Response $response) {
if (!($controller instanceof ShareInfoController)) {
return $response;
}
if (!($response instanceof JSONResponse)) {
return $response;
}
$data = $response->getData();
$status = 'error';
if ($response->getStatus() === Http::STATUS_OK) {
$status = 'success';
}
$response->setData([
'data' => $data,
'status' => $status,
]);
return $response;
}
}
@@ -0,0 +1,152 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Bjoern Schiessle <bjoern@schiessle.org>
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Lukas Reschke <lukas@statuscode.ch>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Roeland Jago Douma <roeland@famdouma.nl>
* @author Thomas Müller <thomas.mueller@tmit.eu>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* 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, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OCA\Files_Sharing\Middleware;
use OCA\Files_Sharing\Controller\ExternalSharesController;
use OCA\Files_Sharing\Exceptions\S2SException;
use OCP\App\IAppManager;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\JSONResponse;
use OCP\AppFramework\Http\NotFoundResponse;
use OCP\AppFramework\Http\Response;
use OCP\AppFramework\Middleware;
use OCP\AppFramework\Utility\IControllerMethodReflector;
use OCP\Files\NotFoundException;
use OCP\IConfig;
use OCP\IRequest;
use OCP\Share\IManager;
/**
* Checks whether the "sharing check" is enabled
*
* @package OCA\Files_Sharing\Middleware
*/
class SharingCheckMiddleware extends Middleware {
/** @var string */
protected $appName;
/** @var IConfig */
protected $config;
/** @var IAppManager */
protected $appManager;
/** @var IControllerMethodReflector */
protected $reflector;
/** @var IManager */
protected $shareManager;
/** @var IRequest */
protected $request;
public function __construct(string $appName,
IConfig $config,
IAppManager $appManager,
IControllerMethodReflector $reflector,
IManager $shareManager,
IRequest $request
) {
$this->appName = $appName;
$this->config = $config;
$this->appManager = $appManager;
$this->reflector = $reflector;
$this->shareManager = $shareManager;
$this->request = $request;
}
/**
* Check if sharing is enabled before the controllers is executed
*
* @param Controller $controller
* @param string $methodName
* @throws NotFoundException
* @throws S2SException
*/
public function beforeController($controller, $methodName): void {
if (!$this->isSharingEnabled()) {
throw new NotFoundException('Sharing is disabled.');
}
if ($controller instanceof ExternalSharesController &&
!$this->externalSharesChecks()) {
throw new S2SException('Federated sharing not allowed');
}
}
/**
* Return 404 page in case of a not found exception
*
* @param Controller $controller
* @param string $methodName
* @param \Exception $exception
* @return Response
* @throws \Exception
*/
public function afterException($controller, $methodName, \Exception $exception): Response {
if (is_a($exception, NotFoundException::class)) {
return new NotFoundResponse();
}
if (is_a($exception, S2SException::class)) {
return new JSONResponse($exception->getMessage(), 405);
}
throw $exception;
}
/**
* Checks for externalshares controller
* @return bool
*/
private function externalSharesChecks(): bool {
if (!$this->reflector->hasAnnotation('NoIncomingFederatedSharingRequired') &&
$this->config->getAppValue('files_sharing', 'incoming_server2server_share_enabled', 'yes') !== 'yes') {
return false;
}
if (!$this->reflector->hasAnnotation('NoOutgoingFederatedSharingRequired') &&
$this->config->getAppValue('files_sharing', 'outgoing_server2server_share_enabled', 'yes') !== 'yes') {
return false;
}
return true;
}
/**
* Check whether sharing is enabled
* @return bool
*/
private function isSharingEnabled(): bool {
// FIXME: This check is done here since the route is globally defined and not inside the files_sharing app
// Check whether the sharing application is enabled
if (!$this->appManager->isEnabledForUser($this->appName)) {
return false;
}
return true;
}
}
@@ -0,0 +1,81 @@
<?php
/**
* @copyright Copyright (c) 2017 Joas Schilling <coding@schilljs.com>
*
* @author Joas Schilling <coding@schilljs.com>
* @author Morris Jobke <hey@morrisjobke.de>
*
* @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 <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Files_Sharing\Migration;
use OCP\IConfig;
use OCP\IDBConnection;
use OCP\Migration\IOutput;
use OCP\Migration\IRepairStep;
use OCP\Share\IShare;
/**
* Class OwncloudGuestShareType
*
* @package OCA\Files_Sharing\Migration
*/
class OwncloudGuestShareType implements IRepairStep {
/** @var IDBConnection */
private $connection;
/** @var IConfig */
private $config;
public function __construct(IDBConnection $connection, IConfig $config) {
$this->connection = $connection;
$this->config = $config;
}
/**
* Returns the step's name
*
* @return string
* @since 9.1.0
*/
public function getName() {
return 'Fix the share type of guest shares when migrating from ownCloud';
}
/**
* @param IOutput $output
*/
public function run(IOutput $output) {
if (!$this->shouldRun()) {
return;
}
$query = $this->connection->getQueryBuilder();
$query->update('share')
->set('share_type', $query->createNamedParameter(IShare::TYPE_GUEST))
->where($query->expr()->eq('share_type', $query->createNamedParameter(IShare::TYPE_EMAIL)));
$query->execute();
}
protected function shouldRun() {
$appVersion = $this->config->getAppValue('files_sharing', 'installed_version', '0.0.0');
return $appVersion === '0.10.0' ||
$this->config->getAppValue('core', 'vendor', '') === 'owncloud';
}
}
@@ -0,0 +1,79 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2019, Joas Schilling <coding@schilljs.com>
*
* @author Joas Schilling <coding@schilljs.com>
*
* @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 <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Files_Sharing\Migration;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IConfig;
use OCP\IDBConnection;
use OCP\Migration\IOutput;
use OCP\Migration\IRepairStep;
use OCP\Share\IShare;
class SetAcceptedStatus implements IRepairStep {
/** @var IDBConnection */
private $connection;
/** @var IConfig */
private $config;
public function __construct(IDBConnection $connection, IConfig $config) {
$this->connection = $connection;
$this->config = $config;
}
/**
* Returns the step's name
*
* @return string
* @since 9.1.0
*/
public function getName(): string {
return 'Set existing shares as accepted';
}
/**
* @param IOutput $output
*/
public function run(IOutput $output): void {
if (!$this->shouldRun()) {
return;
}
$query = $this->connection->getQueryBuilder();
$query
->update('share')
->set('accepted', $query->createNamedParameter(IShare::STATUS_ACCEPTED))
->where($query->expr()->in('share_type', $query->createNamedParameter([IShare::TYPE_USER, IShare::TYPE_GROUP, IShare::TYPE_USERGROUP], IQueryBuilder::PARAM_INT_ARRAY)));
$query->execute();
}
protected function shouldRun() {
$appVersion = $this->config->getAppValue('files_sharing', 'installed_version', '0.0.0');
return version_compare($appVersion, '1.10.1', '<');
}
}
@@ -0,0 +1,94 @@
<?php
/**
* @copyright Copyright (c) 2017 Joas Schilling <coding@schilljs.com>
*
* @author Joas Schilling <coding@schilljs.com>
*
* @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 <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Files_Sharing\Migration;
use OCP\IConfig;
use OCP\IDBConnection;
use OCP\Migration\IOutput;
use OCP\Migration\IRepairStep;
use OCP\Share\IShare;
/**
* Class SetPasswordColumn
*
* @package OCA\Files_Sharing\Migration
*/
class SetPasswordColumn implements IRepairStep {
/** @var IDBConnection */
private $connection;
/** @var IConfig */
private $config;
public function __construct(IDBConnection $connection, IConfig $config) {
$this->connection = $connection;
$this->config = $config;
}
/**
* Returns the step's name
*
* @return string
* @since 9.1.0
*/
public function getName() {
return 'Copy the share password into the dedicated column';
}
/**
* @param IOutput $output
*/
public function run(IOutput $output) {
if (!$this->shouldRun()) {
return;
}
$query = $this->connection->getQueryBuilder();
$query
->update('share')
->set('password', 'share_with')
->where($query->expr()->eq('share_type', $query->createNamedParameter(IShare::TYPE_LINK)))
->andWhere($query->expr()->isNotNull('share_with'));
$result = $query->execute();
if ($result === 0) {
// No link updated, no need to run the second query
return;
}
$clearQuery = $this->connection->getQueryBuilder();
$clearQuery
->update('share')
->set('share_with', $clearQuery->createNamedParameter(null))
->where($clearQuery->expr()->eq('share_type', $clearQuery->createNamedParameter(IShare::TYPE_LINK)));
$clearQuery->execute();
}
protected function shouldRun() {
$appVersion = $this->config->getAppValue('files_sharing', 'installed_version', '0.0.0');
return version_compare($appVersion, '1.4.0', '<');
}
}
@@ -0,0 +1,145 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2020 Julius Härtl <jus@bitgrid.net>
*
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Julius Härtl <jus@bitgrid.net>
* @author Vincent Petry <vincent@nextcloud.com>
*
* @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 <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Files_Sharing\Migration;
use Closure;
use Doctrine\DBAL\Types\Type;
use OCP\DB\ISchemaWrapper;
use OCP\DB\Types;
use OCP\IDBConnection;
use OCP\Migration\IOutput;
use OCP\Migration\SimpleMigrationStep;
class Version11300Date20201120141438 extends SimpleMigrationStep {
/** @var IDBConnection */
private $connection;
public function __construct(IDBConnection $connection) {
$this->connection = $connection;
}
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper {
/** @var ISchemaWrapper $schema */
$schema = $schemaClosure();
if (!$schema->hasTable('share_external')) {
$table = $schema->createTable('share_external');
$table->addColumn('id', Types::BIGINT, [
'autoincrement' => true,
'notnull' => true,
]);
$table->addColumn('parent', Types::BIGINT, [
'notnull' => false,
'default' => -1,
]);
$table->addColumn('share_type', Types::INTEGER, [
'notnull' => false,
'length' => 4,
]);
$table->addColumn('remote', Types::STRING, [
'notnull' => true,
'length' => 512,
]);
$table->addColumn('remote_id', Types::STRING, [
'notnull' => false,
'length' => 255,
'default' => '',
]);
$table->addColumn('share_token', Types::STRING, [
'notnull' => true,
'length' => 64,
]);
$table->addColumn('password', Types::STRING, [
'notnull' => false,
'length' => 64,
]);
$table->addColumn('name', Types::STRING, [
'notnull' => true,
'length' => 64,
]);
$table->addColumn('owner', Types::STRING, [
'notnull' => true,
'length' => 64,
]);
$table->addColumn('user', Types::STRING, [
'notnull' => true,
'length' => 64,
]);
$table->addColumn('mountpoint', Types::STRING, [
'notnull' => true,
'length' => 4000,
]);
$table->addColumn('mountpoint_hash', Types::STRING, [
'notnull' => true,
'length' => 32,
]);
$table->addColumn('accepted', Types::INTEGER, [
'notnull' => true,
'length' => 4,
'default' => 0,
]);
$table->setPrimaryKey(['id']);
$table->addUniqueIndex(['user', 'mountpoint_hash'], 'sh_external_mp');
} else {
$table = $schema->getTable('share_external');
$remoteIdColumn = $table->getColumn('remote_id');
if ($remoteIdColumn && $remoteIdColumn->getType()->getName() !== Types::STRING) {
$remoteIdColumn->setNotnull(false);
$remoteIdColumn->setType(Type::getType(Types::STRING));
$remoteIdColumn->setOptions(['length' => 255]);
$remoteIdColumn->setDefault('');
}
if (!$table->hasColumn('parent')) {
$table->addColumn('parent', Types::BIGINT, [
'notnull' => false,
'default' => -1,
]);
}
if (!$table->hasColumn('share_type')) {
$table->addColumn('share_type', Types::INTEGER, [
'notnull' => false,
'length' => 4,
]);
}
if ($table->hasColumn('lastscan')) {
$table->dropColumn('lastscan');
}
}
return $schema;
}
public function postSchemaChange(IOutput $output, \Closure $schemaClosure, array $options) {
$qb = $this->connection->getQueryBuilder();
$qb->update('share_external')
->set('remote_id', $qb->createNamedParameter(''))
->where($qb->expr()->eq('remote_id', $qb->createNamedParameter('-1')));
$qb->execute();
}
}
@@ -0,0 +1,69 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2020 Vincent Petry <vincent@nextcloud.com>
*
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Vincent Petry <vincent@nextcloud.com>
*
* @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 <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Files_Sharing\Migration;
use Closure;
use OCP\DB\ISchemaWrapper;
use OCP\DB\Types;
use OCP\Migration\IOutput;
use OCP\Migration\SimpleMigrationStep;
class Version21000Date20201223143245 extends SimpleMigrationStep {
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper {
/** @var ISchemaWrapper $schema */
$schema = $schemaClosure();
if ($schema->hasTable('share_external')) {
$table = $schema->getTable('share_external');
$changed = false;
if (!$table->hasColumn('parent')) {
$table->addColumn('parent', Types::BIGINT, [
'notnull' => false,
'default' => -1,
]);
$changed = true;
}
if (!$table->hasColumn('share_type')) {
$table->addColumn('share_type', Types::INTEGER, [
'notnull' => false,
'length' => 4,
]);
$changed = true;
}
if ($table->hasColumn('lastscan')) {
$table->dropColumn('lastscan');
$changed = true;
}
if ($changed) {
return $schema;
}
}
return null;
}
}
@@ -0,0 +1,54 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2021 Roeland Jago Douma <roeland@famdouma.nl>
*
* @author Roeland Jago Douma <roeland@famdouma.nl>
*
* @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 <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Files_Sharing\Migration;
use Closure;
use OCP\DB\ISchemaWrapper;
use OCP\Migration\IOutput;
use OCP\Migration\SimpleMigrationStep;
/**
* Auto-generated migration step: Please modify to your needs!
*/
class Version22000Date20210216084241 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 {
/** @var ISchemaWrapper $schema */
$schema = $schemaClosure();
$table = $schema->getTable('share_external');
if ($table->hasIndex('sh_external_user')) {
$table->dropIndex('sh_external_user');
}
return $schema;
}
}
@@ -0,0 +1,51 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2022 Vincent Petry <vincent@nextloud.com>
*
* @author Vincent Petry <vincent@nextcloud.com>
*
* @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 <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Files_Sharing\Migration;
use Closure;
use OCP\DB\ISchemaWrapper;
use OCP\DB\Types;
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;
}
}
@@ -0,0 +1,56 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2022 Côme Chilliet <come.chilliet@nextcloud.com>
*
* @author Côme Chilliet <come.chilliet@nextcloud.com>
*
* @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 <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Files_Sharing\Migration;
use Closure;
use OCP\DB\ISchemaWrapper;
use OCP\Migration\IOutput;
use OCP\Migration\SimpleMigrationStep;
/**
* Auto-generated migration step: Please modify to your needs!
*/
class Version24000Date20220404142216 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 {
/** @var ISchemaWrapper $schema */
$schema = $schemaClosure();
$table = $schema->getTable('share_external');
$column = $table->getColumn('name');
if ($column->getLength() < 4000) {
$column->setLength(4000);
return $schema;
}
return null;
}
}
@@ -0,0 +1,279 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Joas Schilling <coding@schilljs.com>
* @author Julius Härtl <jus@bitgrid.net>
* @author Maxence Lange <maxence@nextcloud.com>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Robin Appelman <robin@icewind.nl>
* @author Roeland Jago Douma <roeland@famdouma.nl>
* @author Vincent Petry <vincent@nextcloud.com>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* 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, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OCA\Files_Sharing;
use OC\Files\View;
use OCA\Files_Sharing\Event\ShareMountedEvent;
use OCP\Cache\CappedMemoryCache;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\Files\Config\IMountProvider;
use OCP\Files\Storage\IStorageFactory;
use OCP\ICacheFactory;
use OCP\IConfig;
use OCP\IUser;
use OCP\Share\IManager;
use OCP\Share\IShare;
use Psr\Log\LoggerInterface;
class MountProvider implements IMountProvider {
/**
* @param \OCP\IConfig $config
* @param IManager $shareManager
* @param LoggerInterface $logger
*/
public function __construct(
protected IConfig $config,
protected IManager $shareManager,
protected LoggerInterface $logger,
protected IEventDispatcher $eventDispatcher,
protected ICacheFactory $cacheFactory
) {
}
/**
* Get all mountpoints applicable for the user and check for shares where we need to update the etags
*
* @param \OCP\IUser $user
* @param \OCP\Files\Storage\IStorageFactory $loader
* @return \OCP\Files\Mount\IMountPoint[]
*/
public function getMountsForUser(IUser $user, IStorageFactory $loader) {
$shares = $this->shareManager->getSharedWith($user->getUID(), IShare::TYPE_USER, null, -1);
$shares = array_merge($shares, $this->shareManager->getSharedWith($user->getUID(), IShare::TYPE_GROUP, null, -1));
$shares = array_merge($shares, $this->shareManager->getSharedWith($user->getUID(), IShare::TYPE_CIRCLE, null, -1));
$shares = array_merge($shares, $this->shareManager->getSharedWith($user->getUID(), IShare::TYPE_ROOM, null, -1));
$shares = array_merge($shares, $this->shareManager->getSharedWith($user->getUID(), IShare::TYPE_DECK, null, -1));
$shares = array_merge($shares, $this->shareManager->getSharedWith($user->getUID(), IShare::TYPE_SCIENCEMESH, null, -1));
// filter out excluded shares and group shares that includes self
$shares = array_filter($shares, function (\OCP\Share\IShare $share) use ($user) {
return $share->getPermissions() > 0 && $share->getShareOwner() !== $user->getUID();
});
$superShares = $this->buildSuperShares($shares, $user);
$mounts = [];
$view = new View('/' . $user->getUID() . '/files');
$ownerViews = [];
$sharingDisabledForUser = $this->shareManager->sharingDisabledForUser($user->getUID());
/** @var CappedMemoryCache<bool> $folderExistCache */
$foldersExistCache = new CappedMemoryCache();
foreach ($superShares as $share) {
try {
/** @var \OCP\Share\IShare $parentShare */
$parentShare = $share[0];
if ($parentShare->getStatus() !== IShare::STATUS_ACCEPTED &&
($parentShare->getShareType() === IShare::TYPE_GROUP ||
$parentShare->getShareType() === IShare::TYPE_USERGROUP ||
$parentShare->getShareType() === IShare::TYPE_USER)) {
continue;
}
$owner = $parentShare->getShareOwner();
if (!isset($ownerViews[$owner])) {
$ownerViews[$owner] = new View('/' . $parentShare->getShareOwner() . '/files');
}
$mount = new SharedMount(
'\OCA\Files_Sharing\SharedStorage',
$mounts,
[
'user' => $user->getUID(),
// parent share
'superShare' => $parentShare,
// children/component of the superShare
'groupedShares' => $share[1],
'ownerView' => $ownerViews[$owner],
'sharingDisabledForUser' => $sharingDisabledForUser
],
$loader,
$view,
$foldersExistCache,
$this->eventDispatcher,
$user,
$this->cacheFactory->createLocal('share-valid-mountpoint')
);
$event = new ShareMountedEvent($mount);
$this->eventDispatcher->dispatchTyped($event);
$mounts[$mount->getMountPoint()] = $mount;
foreach ($event->getAdditionalMounts() as $additionalMount) {
$mounts[$additionalMount->getMountPoint()] = $additionalMount;
}
} catch (\Exception $e) {
$this->logger->error(
'Error while trying to create shared mount',
[
'app' => 'files_sharing',
'exception' => $e,
],
);
}
}
// array_filter removes the null values from the array
return array_values(array_filter($mounts));
}
/**
* Groups shares by path (nodeId) and target path
*
* @param \OCP\Share\IShare[] $shares
* @return \OCP\Share\IShare[][] array of grouped shares, each element in the
* array is a group which itself is an array of shares
*/
private function groupShares(array $shares) {
$tmp = [];
foreach ($shares as $share) {
if (!isset($tmp[$share->getNodeId()])) {
$tmp[$share->getNodeId()] = [];
}
$tmp[$share->getNodeId()][] = $share;
}
$result = [];
// sort by stime, the super share will be based on the least recent share
foreach ($tmp as &$tmp2) {
@usort($tmp2, function ($a, $b) {
$aTime = $a->getShareTime()->getTimestamp();
$bTime = $b->getShareTime()->getTimestamp();
if ($aTime === $bTime) {
return $a->getId() < $b->getId() ? -1 : 1;
}
return $aTime < $bTime ? -1 : 1;
});
$result[] = $tmp2;
}
return array_values($result);
}
/**
* Build super shares (virtual share) by grouping them by node id and target,
* then for each group compute the super share and return it along with the matching
* grouped shares. The most permissive permissions are used based on the permissions
* of all shares within the group.
*
* @param \OCP\Share\IShare[] $allShares
* @param \OCP\IUser $user user
* @return array Tuple of [superShare, groupedShares]
*/
private function buildSuperShares(array $allShares, \OCP\IUser $user) {
$result = [];
$groupedShares = $this->groupShares($allShares);
/** @var \OCP\Share\IShare[] $shares */
foreach ($groupedShares as $shares) {
if (count($shares) === 0) {
continue;
}
$superShare = $this->shareManager->newShare();
// compute super share based on first entry of the group
$superShare->setId($shares[0]->getId())
->setShareOwner($shares[0]->getShareOwner())
->setNodeId($shares[0]->getNodeId())
->setShareType($shares[0]->getShareType())
->setTarget($shares[0]->getTarget());
// Gather notes from all the shares.
// Since these are readly available here, storing them
// enables the DAV FilesPlugin to avoid executing many
// DB queries to retrieve the same information.
$allNotes = implode("\n", array_map(function ($sh) {
return $sh->getNote();
}, $shares));
$superShare->setNote($allNotes);
// use most permissive permissions
// this covers the case where there are multiple shares for the same
// file e.g. from different groups and different permissions
$superPermissions = 0;
$superAttributes = $this->shareManager->newShare()->newAttributes();
$status = IShare::STATUS_PENDING;
foreach ($shares as $share) {
$superPermissions |= $share->getPermissions();
$status = max($status, $share->getStatus());
// update permissions
$superPermissions |= $share->getPermissions();
// update share permission attributes
$attributes = $share->getAttributes();
if ($attributes !== null) {
foreach ($attributes->toArray() as $attribute) {
if ($superAttributes->getAttribute($attribute['scope'], $attribute['key']) === true) {
// if super share attribute is already enabled, it is most permissive
continue;
}
// update supershare attributes with subshare attribute
$superAttributes->setAttribute($attribute['scope'], $attribute['key'], $attribute['enabled']);
}
}
// adjust target, for database consistency if needed
if ($share->getTarget() !== $superShare->getTarget()) {
$share->setTarget($superShare->getTarget());
try {
$this->shareManager->moveShare($share, $user->getUID());
} catch (\InvalidArgumentException $e) {
// ignore as it is not important and we don't want to
// block FS setup
// the subsequent code anyway only uses the target of the
// super share
// such issue can usually happen when dealing with
// null groups which usually appear with group backend
// caching inconsistencies
$this->logger->debug(
'Could not adjust share target for share ' . $share->getId() . ' to make it consistent: ' . $e->getMessage(),
['app' => 'files_sharing']
);
}
}
if (!is_null($share->getNodeCacheEntry())) {
$superShare->setNodeCacheEntry($share->getNodeCacheEntry());
}
}
$superShare->setPermissions($superPermissions);
$superShare->setStatus($status);
$superShare->setAttributes($superAttributes);
$result[] = [$superShare, $shares];
}
return $result;
}
}
@@ -0,0 +1,130 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2019, Joas Schilling <coding@schilljs.com>
*
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Joas Schilling <coding@schilljs.com>
*
* @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 <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Files_Sharing\Notification;
use OCP\IGroup;
use OCP\IGroupManager;
use OCP\IUser;
use OCP\Notification\IManager as INotificationManager;
use OCP\Notification\INotification;
use OCP\Share\Events\ShareCreatedEvent;
use OCP\Share\IManager as IShareManager;
use OCP\Share\IShare;
use Symfony\Component\EventDispatcher\GenericEvent;
class Listener {
/** @var INotificationManager */
protected $notificationManager;
/** @var IShareManager */
protected $shareManager;
/** @var IGroupManager */
protected $groupManager;
public function __construct(
INotificationManager $notificationManager,
IShareManager $shareManager,
IGroupManager $groupManager
) {
$this->notificationManager = $notificationManager;
$this->shareManager = $shareManager;
$this->groupManager = $groupManager;
}
public function shareNotification(ShareCreatedEvent $event): void {
$share = $event->getShare();
$notification = $this->instantiateNotification($share);
if ($share->getShareType() === IShare::TYPE_USER) {
$notification->setSubject(Notifier::INCOMING_USER_SHARE)
->setUser($share->getSharedWith());
$this->notificationManager->notify($notification);
} elseif ($share->getShareType() === IShare::TYPE_GROUP) {
$notification->setSubject(Notifier::INCOMING_GROUP_SHARE);
$group = $this->groupManager->get($share->getSharedWith());
foreach ($group->getUsers() as $user) {
if ($user->getUID() === $share->getShareOwner() ||
$user->getUID() === $share->getSharedBy()) {
continue;
}
$notification->setUser($user->getUID());
$this->notificationManager->notify($notification);
}
}
}
/**
* @param GenericEvent $event
*/
public function userAddedToGroup(GenericEvent $event): void {
/** @var IGroup $group */
$group = $event->getSubject();
/** @var IUser $user */
$user = $event->getArgument('user');
$offset = 0;
while (true) {
$shares = $this->shareManager->getSharedWith($user->getUID(), IShare::TYPE_GROUP, null, 50, $offset);
if (empty($shares)) {
break;
}
foreach ($shares as $share) {
if ($share->getSharedWith() !== $group->getGID()) {
continue;
}
if ($user->getUID() === $share->getShareOwner() ||
$user->getUID() === $share->getSharedBy()) {
continue;
}
$notification = $this->instantiateNotification($share);
$notification->setSubject(Notifier::INCOMING_GROUP_SHARE)
->setUser($user->getUID());
$this->notificationManager->notify($notification);
}
$offset += 50;
}
}
/**
* @param IShare $share
* @return INotification
*/
protected function instantiateNotification(IShare $share): INotification {
$notification = $this->notificationManager->createNotification();
$notification
->setApp('files_sharing')
->setObject('share', $share->getFullId())
->setDateTime($share->getShareTime());
return $notification;
}
}
@@ -0,0 +1,259 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2019, Roeland Jago Douma <roeland@famdouma.nl>
* @copyright Copyright (c) 2019, Joas Schilling <coding@schilljs.com>
*
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Joas Schilling <coding@schilljs.com>
* @author Roeland Jago Douma <roeland@famdouma.nl>
*
* @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 <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Files_Sharing\Notification;
use OCP\Files\IRootFolder;
use OCP\Files\NotFoundException;
use OCP\IGroupManager;
use OCP\IL10N;
use OCP\IURLGenerator;
use OCP\IUser;
use OCP\IUserManager;
use OCP\L10N\IFactory;
use OCP\Notification\AlreadyProcessedException;
use OCP\Notification\INotification;
use OCP\Notification\INotifier;
use OCP\Share\Exceptions\ShareNotFound;
use OCP\Share\IManager;
use OCP\Share\IShare;
class Notifier implements INotifier {
public const INCOMING_USER_SHARE = 'incoming_user_share';
public const INCOMING_GROUP_SHARE = 'incoming_group_share';
/** @var IFactory */
protected $l10nFactory;
/** @var IManager */
private $shareManager;
/** @var IRootFolder */
private $rootFolder;
/** @var IGroupManager */
protected $groupManager;
/** @var IUserManager */
protected $userManager;
/** @var IURLGenerator */
protected $url;
public function __construct(IFactory $l10nFactory,
IManager $shareManager,
IRootFolder $rootFolder,
IGroupManager $groupManager,
IUserManager $userManager,
IURLGenerator $url) {
$this->l10nFactory = $l10nFactory;
$this->shareManager = $shareManager;
$this->rootFolder = $rootFolder;
$this->groupManager = $groupManager;
$this->userManager = $userManager;
$this->url = $url;
}
/**
* Identifier of the notifier, only use [a-z0-9_]
*
* @return string
* @since 17.0.0
*/
public function getID(): string {
return 'files_sharing';
}
/**
* Human readable name describing the notifier
*
* @return string
* @since 17.0.0
*/
public function getName(): string {
return $this->l10nFactory->get('files_sharing')->t('File sharing');
}
/**
* @param INotification $notification
* @param string $languageCode The code of the language that should be used to prepare the notification
* @return INotification
* @throws \InvalidArgumentException When the notification was not prepared by a notifier
* @throws AlreadyProcessedException When the notification is not needed anymore and should be deleted
* @since 9.0.0
*/
public function prepare(INotification $notification, string $languageCode): INotification {
if ($notification->getApp() !== 'files_sharing' ||
($notification->getSubject() !== 'expiresTomorrow' &&
$notification->getObjectType() !== 'share')) {
throw new \InvalidArgumentException('Unhandled app or subject');
}
$l = $this->l10nFactory->get('files_sharing', $languageCode);
$attemptId = $notification->getObjectId();
try {
$share = $this->shareManager->getShareById($attemptId, $notification->getUser());
} catch (ShareNotFound $e) {
throw new AlreadyProcessedException();
}
try {
$share->getNode();
} catch (NotFoundException $e) {
// Node is already deleted, so discard the notification
throw new AlreadyProcessedException();
}
if ($notification->getSubject() === 'expiresTomorrow') {
$notification = $this->parseShareExpiration($share, $notification, $l);
} else {
$notification = $this->parseShareInvitation($share, $notification, $l);
}
return $notification;
}
protected function parseShareExpiration(IShare $share, INotification $notification, IL10N $l): INotification {
$node = $share->getNode();
$userFolder = $this->rootFolder->getUserFolder($notification->getUser());
$path = $userFolder->getRelativePath($node->getPath());
$notification
->setParsedSubject($l->t('Share will expire tomorrow'))
->setRichMessage(
$l->t('Your share of {node} will expire tomorrow'),
[
'node' => [
'type' => 'file',
'id' => $node->getId(),
'name' => $node->getName(),
'path' => $path,
],
]
);
return $notification;
}
protected function parseShareInvitation(IShare $share, INotification $notification, IL10N $l): INotification {
if ($share->getShareType() === IShare::TYPE_USER) {
if ($share->getStatus() !== IShare::STATUS_PENDING) {
throw new AlreadyProcessedException();
}
} elseif ($share->getShareType() === IShare::TYPE_GROUP) {
if ($share->getStatus() !== IShare::STATUS_PENDING) {
throw new AlreadyProcessedException();
}
} else {
throw new \InvalidArgumentException('Invalid share type');
}
switch ($notification->getSubject()) {
case self::INCOMING_USER_SHARE:
if ($share->getSharedWith() !== $notification->getUser()) {
throw new AlreadyProcessedException();
}
$sharer = $this->userManager->get($share->getSharedBy());
if (!$sharer instanceof IUser) {
throw new \InvalidArgumentException('Temporary failure');
}
$subject = $l->t('You received {share} as a share by {user}');
$subjectParameters = [
'share' => [
'type' => 'highlight',
'id' => $notification->getObjectId(),
'name' => $share->getTarget(),
],
'user' => [
'type' => 'user',
'id' => $sharer->getUID(),
'name' => $sharer->getDisplayName(),
],
];
break;
case self::INCOMING_GROUP_SHARE:
$user = $this->userManager->get($notification->getUser());
if (!$user instanceof IUser) {
throw new AlreadyProcessedException();
}
$group = $this->groupManager->get($share->getSharedWith());
if ($group === null || !$group->inGroup($user)) {
throw new AlreadyProcessedException();
}
if ($share->getPermissions() === 0) {
// Already rejected
throw new AlreadyProcessedException();
}
$sharer = $this->userManager->get($share->getSharedBy());
if (!$sharer instanceof IUser) {
throw new \InvalidArgumentException('Temporary failure');
}
$subject = $l->t('You received {share} to group {group} as a share by {user}');
$subjectParameters = [
'share' => [
'type' => 'highlight',
'id' => $notification->getObjectId(),
'name' => $share->getTarget(),
],
'group' => [
'type' => 'user-group',
'id' => $group->getGID(),
'name' => $group->getDisplayName(),
],
'user' => [
'type' => 'user',
'id' => $sharer->getUID(),
'name' => $sharer->getDisplayName(),
],
];
break;
default:
throw new \InvalidArgumentException('Invalid subject');
}
$notification->setRichSubject($subject, $subjectParameters)
->setIcon($this->url->getAbsoluteURL($this->url->imagePath('core', 'actions/share.svg')));
$acceptAction = $notification->createAction();
$acceptAction->setParsedLabel($l->t('Accept'))
->setLink($this->url->linkToOCSRouteAbsolute('files_sharing.ShareAPI.acceptShare', ['id' => $share->getId()]), 'POST')
->setPrimary(true);
$notification->addParsedAction($acceptAction);
$rejectAction = $notification->createAction();
$rejectAction->setParsedLabel($l->t('Reject'))
->setLink($this->url->linkToOCSRouteAbsolute('files_sharing.ShareAPI.deleteShare', ['id' => $share->getId()]), 'DELETE')
->setPrimary(false);
$notification->addParsedAction($rejectAction);
return $notification;
}
}
@@ -0,0 +1,91 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2023 Robin Appelman <robin@icewind.nl>
*
* @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 <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Files_Sharing;
use OC\User\NoUserException;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\Files\IRootFolder;
use OCP\IDBConnection;
class OrphanHelper {
private IDBConnection $connection;
private IRootFolder $rootFolder;
public function __construct(
IDBConnection $connection,
IRootFolder $rootFolder
) {
$this->connection = $connection;
$this->rootFolder = $rootFolder;
}
public function isShareValid(string $owner, int $fileId): bool {
try {
$userFolder = $this->rootFolder->getUserFolder($owner);
} catch (NoUserException $e) {
return false;
}
$nodes = $userFolder->getById($fileId);
return count($nodes) > 0;
}
/**
* @param int[] $ids
* @return void
*/
public function deleteShares(array $ids): void {
$query = $this->connection->getQueryBuilder();
$query->delete('share')
->where($query->expr()->in('id', $query->createNamedParameter($ids, IQueryBuilder::PARAM_INT_ARRAY)));
$query->executeStatement();
}
public function fileExists(int $fileId): bool {
$query = $this->connection->getQueryBuilder();
$query->select('fileid')
->from('filecache')
->where($query->expr()->eq('fileid', $query->createNamedParameter($fileId, IQueryBuilder::PARAM_INT)));
return $query->executeQuery()->fetchOne() !== false;
}
/**
* @return \Traversable<int, array{id: int, owner: string, fileid: int, target: string}>
*/
public function getAllShares() {
$query = $this->connection->getQueryBuilder();
$query->select('id', 'file_source', 'uid_owner', 'file_target')
->from('share')
->where($query->expr()->eq('item_type', $query->createNamedParameter('file')))
->orWhere($query->expr()->eq('item_type', $query->createNamedParameter('folder')));
$result = $query->executeQuery();
while ($row = $result->fetch()) {
yield [
'id' => (int)$row['id'],
'owner' => (string)$row['uid_owner'],
'fileid' => (int)$row['file_source'],
'target' => (string)$row['file_target'],
];
}
}
}
@@ -0,0 +1,244 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2023 Kate Döen <kate.doeen@nextcloud.com>
*
* @author Kate Döen <kate.doeen@nextcloud.com>
*
* @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 <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Files_Sharing;
/**
* @psalm-type Files_SharingShare = array{
* attributes: ?string,
* can_delete: bool,
* can_edit: bool,
* displayname_file_owner: string,
* displayname_owner: string,
* expiration: ?string,
* file_parent: int,
* file_source: int,
* file_target: string,
* has_preview: bool,
* hide_download: 0|1,
* id: string,
* item_mtime: int,
* item_permissions?: int,
* item_size: float|int,
* item_source: int,
* item_type: 'file'|'folder',
* label: string,
* mail_send: 0|1,
* mimetype: string,
* note: string,
* parent: null,
* password?: string,
* password_expiration_time?: ?string,
* path: ?string,
* permissions: int,
* send_password_by_talk?: bool,
* share_type: int,
* share_with?: string,
* share_with_avatar?: string,
* share_with_displayname?: string,
* share_with_displayname_unique?: ?string,
* share_with_link?: string,
* status?: array{clearAt?: int|null, icon?: ?string, message?: ?string, status?: string},
* stime: int,
* storage: int,
* storage_id: string,
* token: ?string,
* uid_file_owner: string,
* uid_owner: string,
* url?: string,
* }
*
* @psalm-type Files_SharingDeletedShare = array{
* id: string,
* share_type: int,
* uid_owner: string,
* displayname_owner: string,
* permissions: int,
* stime: int,
* uid_file_owner: string,
* displayname_file_owner: string,
* path: string,
* item_type: string,
* mimetype: string,
* storage: int,
* item_source: int,
* file_source: int,
* file_parent: int,
* file_target: int,
* expiration: string|null,
* share_with: string|null,
* share_with_displayname: string|null,
* share_with_link: string|null,
* }
*
* @psalm-type Files_SharingRemoteShare = array{
* accepted: bool,
* file_id: int|null,
* id: int,
* mimetype: string|null,
* mountpoint: string,
* mtime: int|null,
* name: string,
* owner: string,
* parent: int|null,
* permissions: int|null,
* remote: string,
* remote_id: string,
* share_token: string,
* share_type: int,
* type: string|null,
* user: string,
* }
*
* @psalm-type Files_SharingSharee = array{
* count: int|null,
* label: string,
* }
*
* @psalm-type Files_SharingShareeValue = array{
* shareType: int,
* shareWith: string,
* }
*
* @psalm-type Files_SharingShareeUser = Files_SharingSharee&array{
* subline: string,
* icon: string,
* shareWithDisplayNameUnique: string,
* status: array{
* status: string,
* message: string,
* icon: string,
* clearAt: int|null,
* },
* value: Files_SharingShareeValue,
* }
*
* @psalm-type Files_SharingShareeRemoteGroup = Files_SharingSharee&array{
* guid: string,
* name: string,
* value: Files_SharingShareeValue&array{
* server: string,
* }
* }
*
* @psalm-type Files_SharingLookup = array{
* value: string,
* verified: int,
* }
*
* @psalm-type Files_SharingShareeLookup = Files_SharingSharee&array{
* extra: array{
* federationId: string,
* name: Files_SharingLookup|null,
* email: Files_SharingLookup|null,
* address: Files_SharingLookup|null,
* website: Files_SharingLookup|null,
* twitter: Files_SharingLookup|null,
* phone: Files_SharingLookup|null,
* twitter_signature: Files_SharingLookup|null,
* website_signature: Files_SharingLookup|null,
* userid: Files_SharingLookup|null,
* },
* value: Files_SharingShareeValue&array{
* globalScale: bool,
* }
* }
*
* @psalm-type Files_SharingShareeEmail = Files_SharingSharee&array{
* uuid: string,
* name: string,
* type: string,
* shareWithDisplayNameUnique: string,
* value: Files_SharingShareeValue,
* }
*
* @psalm-type Files_SharingShareeRemote = Files_SharingSharee&array{
* uuid: string,
* name: string,
* type: string,
* value: Files_SharingShareeValue&array{
* server: string,
* }
* }
*
* @psalm-type Files_SharingShareeCircle = Files_SharingSharee&array{
* shareWithDescription: string,
* value: Files_SharingShareeValue&array{
* circle: string,
* }
* }
*
* @psalm-type Files_SharingShareesSearchResult = array{
* exact: array{
* circles: Files_SharingShareeCircle[],
* emails: Files_SharingShareeEmail[],
* groups: Files_SharingSharee[],
* remote_groups: Files_SharingShareeRemoteGroup[],
* remotes: Files_SharingShareeRemote[],
* rooms: Files_SharingSharee[],
* users: Files_SharingShareeUser[],
* },
* circles: Files_SharingShareeCircle[],
* emails: Files_SharingShareeEmail[],
* groups: Files_SharingSharee[],
* lookup: Files_SharingShareeLookup[],
* remote_groups: Files_SharingShareeRemoteGroup[],
* remotes: Files_SharingShareeRemote[],
* rooms: Files_SharingSharee[],
* users: Files_SharingShareeUser[],
* lookupEnabled: bool,
* }
*
* @psalm-type Files_SharingShareesRecommendedResult = array{
* exact: array{
* emails: Files_SharingShareeEmail[],
* groups: Files_SharingSharee[],
* remote_groups: Files_SharingShareeRemoteGroup[],
* remotes: Files_SharingShareeRemote[],
* users: Files_SharingShareeUser[],
* },
* emails: Files_SharingShareeEmail[],
* groups: Files_SharingSharee[],
* remote_groups: Files_SharingShareeRemoteGroup[],
* remotes: Files_SharingShareeRemote[],
* users: Files_SharingShareeUser[],
* }
*
* @psalm-type Files_SharingShareInfo = array{
* id: int,
* parentId: int,
* mtime: int,
* name: string,
* permissions: int,
* mimetype: string,
* size: int|float,
* type: string,
* etag: string,
* children?: array<string, mixed>[],
* }
*/
class ResponseDefinitions {
}
@@ -0,0 +1,82 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Joas Schilling <coding@schilljs.com>
* @author Robin Appelman <robin@icewind.nl>
* @author Roeland Jago Douma <roeland@famdouma.nl>
* @author Vincent Petry <vincent@nextcloud.com>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* 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, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OCA\Files_Sharing;
use OC\Files\ObjectStore\ObjectStoreScanner;
/**
* Scanner for SharedStorage
*/
class Scanner extends \OC\Files\Cache\Scanner {
/**
* @var \OCA\Files_Sharing\SharedStorage $storage
*/
protected $storage;
private $sourceScanner;
/**
* Returns metadata from the shared storage, but
* with permissions from the source storage.
*
* @param string $path path of the file for which to retrieve metadata
*
* @return array|null an array of metadata of the file
*/
public function getData($path) {
$data = parent::getData($path);
if ($data === null) {
return null;
}
$internalPath = $this->storage->getUnjailedPath($path);
$data['permissions'] = $this->storage->getSourceStorage()->getPermissions($internalPath);
return $data;
}
private function getSourceScanner() {
if ($this->sourceScanner) {
return $this->sourceScanner;
}
if ($this->storage->instanceOfStorage('\OCA\Files_Sharing\SharedStorage')) {
/** @var \OC\Files\Storage\Storage $storage */
[$storage] = $this->storage->resolvePath('');
$this->sourceScanner = $storage->getScanner();
return $this->sourceScanner;
} else {
return null;
}
}
public function scanFile($file, $reuseExisting = 0, $parentId = -1, $cacheData = null, $lock = true, $data = null) {
$sourceScanner = $this->getSourceScanner();
if ($sourceScanner instanceof ObjectStoreScanner) {
// ObjectStoreScanner doesn't scan
return [];
} else {
return parent::scanFile($file, $reuseExisting, $parentId, $cacheData, $lock);
}
}
}
@@ -0,0 +1,73 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2019, Roeland Jago Douma <roeland@famdouma.nl>
*
* @author Julius Härtl <jus@bitgrid.net>
* @author Roeland Jago Douma <roeland@famdouma.nl>
* @author Hinrich Mahler <nextcloud@mahlerhome.de>
*
* @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 <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Files_Sharing\Settings;
use OCA\Files_Sharing\AppInfo\Application;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\AppFramework\Services\IInitialState;
use OCP\IConfig;
use OCP\Settings\ISettings;
class Personal implements ISettings {
/** @var IConfig */
private $config;
/** @var IInitialState */
private $initialState;
/** @var string */
private $userId;
public function __construct(IConfig $config, IInitialState $initialState, string $userId) {
$this->config = $config;
$this->initialState = $initialState;
$this->userId = $userId;
}
public function getForm(): TemplateResponse {
$defaultAcceptSystemConfig = $this->config->getSystemValueBool('sharing.enable_share_accept', false) ? 'no' : 'yes';
$shareFolderSystemConfig = $this->config->getSystemValue('share_folder', '/');
$acceptDefault = $this->config->getUserValue($this->userId, Application::APP_ID, 'default_accept', $defaultAcceptSystemConfig) === 'yes';
$enforceAccept = $this->config->getSystemValueBool('sharing.force_share_accept', false);
$allowCustomDirectory = $this->config->getSystemValueBool('sharing.allow_custom_share_folder', true);
$shareFolderDefault = $this->config->getUserValue($this->userId, Application::APP_ID, 'share_folder', $shareFolderSystemConfig);
$this->initialState->provideInitialState('accept_default', $acceptDefault);
$this->initialState->provideInitialState('enforce_accept', $enforceAccept);
$this->initialState->provideInitialState('allow_custom_share_folder', $allowCustomDirectory);
$this->initialState->provideInitialState('share_folder', $shareFolderDefault);
$this->initialState->provideInitialState('default_share_folder', $shareFolderSystemConfig);
return new TemplateResponse('files_sharing', 'Settings/personal');
}
public function getSection(): string {
return 'sharing';
}
public function getPriority(): int {
return 90;
}
}
@@ -0,0 +1,255 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Andreas Fischer <bantu@owncloud.com>
* @author Bart Visscher <bartv@thisnet.nl>
* @author Bjoern Schiessle <bjoern@schiessle.org>
* @author Björn Schießle <bjoern@schiessle.org>
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Joas Schilling <coding@schilljs.com>
* @author Lukas Reschke <lukas@statuscode.ch>
* @author Michael Gapczynski <GapczynskiM@gmail.com>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Robin Appelman <robin@icewind.nl>
* @author Roeland Jago Douma <roeland@famdouma.nl>
* @author Thomas Müller <thomas.mueller@tmit.eu>
* @author Vincent Petry <vincent@nextcloud.com>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* 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, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OCA\Files_Sharing\ShareBackend;
use OCA\FederatedFileSharing\FederatedShareProvider;
use OCP\Share\IShare;
class File implements \OCP\Share_Backend_File_Dependent {
public const FORMAT_SHARED_STORAGE = 0;
public const FORMAT_GET_FOLDER_CONTENTS = 1;
public const FORMAT_FILE_APP_ROOT = 2;
public const FORMAT_OPENDIR = 3;
public const FORMAT_GET_ALL = 4;
public const FORMAT_PERMISSIONS = 5;
public const FORMAT_TARGET_NAMES = 6;
private $path;
/** @var FederatedShareProvider */
private $federatedShareProvider;
public function __construct(FederatedShareProvider $federatedShareProvider = null) {
if ($federatedShareProvider) {
$this->federatedShareProvider = $federatedShareProvider;
} else {
$this->federatedShareProvider = \OC::$server->query(FederatedShareProvider::class);
}
}
public function isValidSource($itemSource, $uidOwner) {
try {
$path = \OC\Files\Filesystem::getPath($itemSource);
// FIXME: attributes should not be set here,
// keeping this pattern for now to avoid unexpected
// regressions
$this->path = \OC\Files\Filesystem::normalizePath(basename($path));
return true;
} catch (\OCP\Files\NotFoundException $e) {
return false;
}
}
public function getFilePath($itemSource, $uidOwner) {
if (isset($this->path)) {
$path = $this->path;
$this->path = null;
return $path;
} else {
try {
$path = \OC\Files\Filesystem::getPath($itemSource);
return $path;
} catch (\OCP\Files\NotFoundException $e) {
return false;
}
}
}
/**
* create unique target
*
* @param string $itemSource
* @param string $shareWith
* @param array $exclude (optional)
* @return string
*/
public function generateTarget($itemSource, $shareWith, $exclude = null) {
$shareFolder = \OCA\Files_Sharing\Helper::getShareFolder();
$target = \OC\Files\Filesystem::normalizePath($shareFolder . '/' . basename($itemSource));
// for group shares we return the target right away
if ($shareWith === false) {
return $target;
}
\OC\Files\Filesystem::initMountPoints($shareWith);
$view = new \OC\Files\View('/' . $shareWith . '/files');
if (!$view->is_dir($shareFolder)) {
$dir = '';
$subdirs = explode('/', $shareFolder);
foreach ($subdirs as $subdir) {
$dir = $dir . '/' . $subdir;
if (!$view->is_dir($dir)) {
$view->mkdir($dir);
}
}
}
$excludeList = is_array($exclude) ? $exclude : [];
return \OCA\Files_Sharing\Helper::generateUniqueTarget($target, $excludeList, $view);
}
public function formatItems($items, $format, $parameters = null) {
if ($format === self::FORMAT_SHARED_STORAGE) {
// Only 1 item should come through for this format call
$item = array_shift($items);
return [
'parent' => $item['parent'],
'path' => $item['path'],
'storage' => $item['storage'],
'permissions' => $item['permissions'],
'uid_owner' => $item['uid_owner'],
];
} elseif ($format === self::FORMAT_GET_FOLDER_CONTENTS) {
$files = [];
foreach ($items as $item) {
$file = [];
$file['fileid'] = $item['file_source'];
$file['storage'] = $item['storage'];
$file['path'] = $item['file_target'];
$file['parent'] = $item['file_parent'];
$file['name'] = basename($item['file_target']);
$file['mimetype'] = $item['mimetype'];
$file['mimepart'] = $item['mimepart'];
$file['mtime'] = $item['mtime'];
$file['encrypted'] = $item['encrypted'];
$file['etag'] = $item['etag'];
$file['uid_owner'] = $item['uid_owner'];
$file['displayname_owner'] = $item['displayname_owner'];
$storage = \OC\Files\Filesystem::getStorage('/');
$cache = $storage->getCache();
$file['size'] = $item['size'];
$files[] = $file;
}
return $files;
} elseif ($format === self::FORMAT_OPENDIR) {
$files = [];
foreach ($items as $item) {
$files[] = basename($item['file_target']);
}
return $files;
} elseif ($format === self::FORMAT_GET_ALL) {
$ids = [];
foreach ($items as $item) {
$ids[] = $item['file_source'];
}
return $ids;
} elseif ($format === self::FORMAT_PERMISSIONS) {
$filePermissions = [];
foreach ($items as $item) {
$filePermissions[$item['file_source']] = $item['permissions'];
}
return $filePermissions;
} elseif ($format === self::FORMAT_TARGET_NAMES) {
$targets = [];
foreach ($items as $item) {
$targets[] = $item['file_target'];
}
return $targets;
}
return [];
}
/**
* check if server2server share is enabled
*
* @param int $shareType
* @return boolean
*/
public function isShareTypeAllowed($shareType) {
if ($shareType === IShare::TYPE_REMOTE) {
return $this->federatedShareProvider->isOutgoingServer2serverShareEnabled();
}
if ($shareType === IShare::TYPE_REMOTE_GROUP) {
return $this->federatedShareProvider->isOutgoingServer2serverGroupShareEnabled();
}
return true;
}
/**
* resolve reshares to return the correct source item
* @param array $source
* @return array source item
*/
protected static function resolveReshares($source) {
if (isset($source['parent'])) {
$parent = $source['parent'];
while (isset($parent)) {
$qb = \OC::$server->getDatabaseConnection()->getQueryBuilder();
$qb->select('parent', 'uid_owner')
->from('share')
->where(
$qb->expr()->eq('id', $qb->createNamedParameter($parent))
);
$result = $qb->execute();
$item = $result->fetch();
$result->closeCursor();
if (isset($item['parent'])) {
$parent = $item['parent'];
} else {
$fileOwner = $item['uid_owner'];
break;
}
}
} else {
$fileOwner = $source['uid_owner'];
}
if (isset($fileOwner)) {
$source['fileOwner'] = $fileOwner;
} else {
\OC::$server->getLogger()->error('No owner found for reshare', ['app' => 'files_sharing']);
}
return $source;
}
/**
* @param string $target
* @param array $share
* @return array|false source item
*/
public static function getSource($target, $share) {
if ($share['item_type'] === 'folder' && $target !== '') {
// note: in case of ext storage mount points the path might be empty
// which would cause a leading slash to appear
$share['path'] = ltrim($share['path'] . '/' . $target, '/');
}
return self::resolveReshares($share);
}
}
@@ -0,0 +1,137 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Bart Visscher <bartv@thisnet.nl>
* @author Björn Schießle <bjoern@schiessle.org>
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Joas Schilling <coding@schilljs.com>
* @author Michael Gapczynski <GapczynskiM@gmail.com>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Robin McCorkell <robin@mccorkell.me.uk>
* @author Roeland Jago Douma <roeland@famdouma.nl>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* 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, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OCA\Files_Sharing\ShareBackend;
class Folder extends File implements \OCP\Share_Backend_Collection {
/**
* get shared parents
*
* @param int $itemSource item source ID
* @param string $shareWith with whom should the item be shared
* @param string $owner owner of the item
* @return array with shares
*/
public function getParents($itemSource, $shareWith = null, $owner = null) {
$result = [];
$parent = $this->getParentId($itemSource);
$userManager = \OC::$server->getUserManager();
while ($parent) {
$shares = \OCP\Share::getItemSharedWithUser('folder', $parent, $shareWith, $owner);
if ($shares) {
foreach ($shares as $share) {
$name = basename($share['path']);
$share['collection']['path'] = $name;
$share['collection']['item_type'] = 'folder';
$share['file_path'] = $name;
$ownerUser = $userManager->get($share['uid_owner']);
$displayNameOwner = $ownerUser === null ? $share['uid_owner'] : $ownerUser->getDisplayName();
$shareWithUser = $userManager->get($share['share_with']);
$displayNameShareWith = $shareWithUser === null ? $share['share_with'] : $shareWithUser->getDisplayName();
$share['displayname_owner'] = $displayNameOwner ? $displayNameOwner : $share['uid_owner'];
$share['share_with_displayname'] = $displayNameShareWith ? $displayNameShareWith : $share['uid_owner'];
$result[] = $share;
}
}
$parent = $this->getParentId($parent);
}
return $result;
}
/**
* get file cache ID of parent
*
* @param int $child file cache ID of child
* @return mixed parent ID or null
*/
private function getParentId($child) {
$qb = \OC::$server->getDatabaseConnection()->getQueryBuilder();
$qb->select('parent')
->from('filecache')
->where(
$qb->expr()->eq('fileid', $qb->createNamedParameter($child))
);
$result = $qb->execute();
$row = $result->fetch();
$result->closeCursor();
return $row ? $row['parent'] : null;
}
public function getChildren($itemSource) {
$children = [];
$parents = [$itemSource];
$qb = \OC::$server->getDatabaseConnection()->getQueryBuilder();
$qb->select('id')
->from('mimetypes')
->where(
$qb->expr()->eq('mimetype', $qb->createNamedParameter('httpd/unix-directory'))
);
$result = $qb->execute();
$row = $result->fetch();
$result->closeCursor();
if ($row = $result->fetchRow()) {
$mimetype = (int) $row['id'];
} else {
$mimetype = -1;
}
while (!empty($parents)) {
$qb = \OC::$server->getDatabaseConnection()->getQueryBuilder();
$parents = array_map(function ($parent) use ($qb) {
return $qb->createNamedParameter($parent);
}, $parents);
$qb->select('`fileid', 'name', '`mimetype')
->from('filecache')
->where(
$qb->expr()->in('parent', $parents)
);
$result = $qb->execute();
$parents = [];
while ($file = $result->fetch()) {
$children[] = ['source' => $file['fileid'], 'file_path' => $file['name']];
// If a child folder is found look inside it
if ((int) $file['mimetype'] === $mimetype) {
$parents[] = $file['fileid'];
}
}
$result->closeCursor();
}
return $children;
}
}
@@ -0,0 +1,299 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Björn Schießle <bjoern@schiessle.org>
* @author Daniel Kesselberg <mail@danielkesselberg.de>
* @author Frédéric Fortier <frederic.fortier@oronospolytechnique.com>
* @author Joas Schilling <coding@schilljs.com>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Robin Appelman <robin@icewind.nl>
* @author Roeland Jago Douma <roeland@famdouma.nl>
* @author Vincent Petry <vincent@nextcloud.com>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* 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, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OCA\Files_Sharing;
use OC\Files\Filesystem;
use OC\Files\Mount\MountPoint;
use OC\Files\Mount\MoveableMount;
use OC\Files\View;
use OCP\Cache\CappedMemoryCache;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\Files\Events\InvalidateMountCacheEvent;
use OCP\Files\Storage\IStorageFactory;
use OCP\ICache;
use OCP\IUser;
use OCP\Share\Events\VerifyMountPointEvent;
/**
* Shared mount points can be moved by the user
*/
class SharedMount extends MountPoint implements MoveableMount, ISharedMountPoint {
/**
* @var \OCA\Files_Sharing\SharedStorage $storage
*/
protected $storage = null;
/**
* @var \OC\Files\View
*/
private $recipientView;
private IUser $user;
/** @var \OCP\Share\IShare */
private $superShare;
/** @var \OCP\Share\IShare[] */
private $groupedShares;
private IEventDispatcher $eventDispatcher;
private ICache $cache;
public function __construct(
$storage,
array $mountpoints,
$arguments,
IStorageFactory $loader,
View $recipientView,
CappedMemoryCache $folderExistCache,
IEventDispatcher $eventDispatcher,
IUser $user,
ICache $cache
) {
$this->user = $user;
$this->recipientView = $recipientView;
$this->eventDispatcher = $eventDispatcher;
$this->cache = $cache;
$this->superShare = $arguments['superShare'];
$this->groupedShares = $arguments['groupedShares'];
$newMountPoint = $this->verifyMountPoint($this->superShare, $mountpoints, $folderExistCache);
$absMountPoint = '/' . $user->getUID() . '/files' . $newMountPoint;
parent::__construct($storage, $absMountPoint, $arguments, $loader, null, null, MountProvider::class);
}
/**
* check if the parent folder exists otherwise move the mount point up
*
* @param \OCP\Share\IShare $share
* @param SharedMount[] $mountpoints
* @param CappedMemoryCache<bool> $folderExistCache
* @return string
*/
private function verifyMountPoint(
\OCP\Share\IShare $share,
array $mountpoints,
CappedMemoryCache $folderExistCache
) {
$cacheKey = $this->user->getUID() . '/' . $share->getId() . '/' . $share->getTarget();
$cached = $this->cache->get($cacheKey);
if ($cached !== null) {
return $cached;
}
$mountPoint = basename($share->getTarget());
$parent = dirname($share->getTarget());
$event = new VerifyMountPointEvent($share, $this->recipientView, $parent);
$this->eventDispatcher->dispatchTyped($event);
$parent = $event->getParent();
$cached = $folderExistCache->get($parent);
if ($cached) {
$parentExists = $cached;
} else {
$parentExists = $this->recipientView->is_dir($parent);
$folderExistCache->set($parent, $parentExists);
}
if (!$parentExists) {
$parent = Helper::getShareFolder($this->recipientView, $this->user->getUID());
}
$newMountPoint = $this->generateUniqueTarget(
\OC\Files\Filesystem::normalizePath($parent . '/' . $mountPoint),
$this->recipientView,
$mountpoints
);
if ($newMountPoint !== $share->getTarget()) {
$this->updateFileTarget($newMountPoint, $share);
}
$this->cache->set($cacheKey, $newMountPoint, 60 * 60);
return $newMountPoint;
}
/**
* update fileTarget in the database if the mount point changed
*
* @param string $newPath
* @param \OCP\Share\IShare $share
* @return bool
*/
private function updateFileTarget($newPath, &$share) {
$share->setTarget($newPath);
foreach ($this->groupedShares as $tmpShare) {
$tmpShare->setTarget($newPath);
\OC::$server->getShareManager()->moveShare($tmpShare, $this->user->getUID());
}
$this->eventDispatcher->dispatchTyped(new InvalidateMountCacheEvent($this->user));
}
/**
* @param string $path
* @param View $view
* @param SharedMount[] $mountpoints
* @return mixed
*/
private function generateUniqueTarget($path, $view, array $mountpoints) {
$pathinfo = pathinfo($path);
$ext = isset($pathinfo['extension']) ? '.' . $pathinfo['extension'] : '';
$name = $pathinfo['filename'];
$dir = $pathinfo['dirname'];
$i = 2;
$absolutePath = $this->recipientView->getAbsolutePath($path) . '/';
while ($view->file_exists($path) || isset($mountpoints[$absolutePath])) {
$path = Filesystem::normalizePath($dir . '/' . $name . ' (' . $i . ')' . $ext);
$absolutePath = $this->recipientView->getAbsolutePath($path) . '/';
$i++;
}
return $path;
}
/**
* Format a path to be relative to the /user/files/ directory
*
* @param string $path the absolute path
* @return string e.g. turns '/admin/files/test.txt' into '/test.txt'
* @throws \OCA\Files_Sharing\Exceptions\BrokenPath
*/
protected function stripUserFilesPath($path) {
$trimmed = ltrim($path, '/');
$split = explode('/', $trimmed);
// it is not a file relative to data/user/files
if (count($split) < 3 || $split[1] !== 'files') {
\OC::$server->getLogger()->error('Can not strip userid and "files/" from path: ' . $path, ['app' => 'files_sharing']);
throw new \OCA\Files_Sharing\Exceptions\BrokenPath('Path does not start with /user/files', 10);
}
// skip 'user' and 'files'
$sliced = array_slice($split, 2);
$relPath = implode('/', $sliced);
return '/' . $relPath;
}
/**
* Move the mount point to $target
*
* @param string $target the target mount point
* @return bool
*/
public function moveMount($target) {
$relTargetPath = $this->stripUserFilesPath($target);
$share = $this->storage->getShare();
$result = true;
try {
$this->updateFileTarget($relTargetPath, $share);
$this->setMountPoint($target);
$this->storage->setMountPoint($relTargetPath);
} catch (\Exception $e) {
\OC::$server->getLogger()->logException($e, ['app' => 'files_sharing', 'message' => 'Could not rename mount point for shared folder "' . $this->getMountPoint() . '" to "' . $target . '"']);
}
return $result;
}
/**
* Remove the mount points
*
* @return bool
*/
public function removeMount() {
$mountManager = \OC\Files\Filesystem::getMountManager();
/** @var \OCA\Files_Sharing\SharedStorage $storage */
$storage = $this->getStorage();
$result = $storage->unshareStorage();
$mountManager->removeMount($this->mountPoint);
return $result;
}
/**
* @return \OCP\Share\IShare
*/
public function getShare() {
return $this->superShare;
}
/**
* @return \OCP\Share\IShare[]
*/
public function getGroupedShares(): array {
return $this->groupedShares;
}
/**
* Get the file id of the root of the storage
*
* @return int
*/
public function getStorageRootId() {
return $this->getShare()->getNodeId();
}
/**
* @return int
*/
public function getNumericStorageId() {
if (!is_null($this->getShare()->getNodeCacheEntry())) {
return $this->getShare()->getNodeCacheEntry()->getStorageId();
} else {
$builder = \OC::$server->getDatabaseConnection()->getQueryBuilder();
$query = $builder->select('storage')
->from('filecache')
->where($builder->expr()->eq('fileid', $builder->createNamedParameter($this->getStorageRootId())));
$result = $query->execute();
$row = $result->fetch();
$result->closeCursor();
if ($row) {
return (int)$row['storage'];
}
return -1;
}
}
public function getMountType() {
return 'shared';
}
}
@@ -0,0 +1,565 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Bart Visscher <bartv@thisnet.nl>
* @author Björn Schießle <bjoern@schiessle.org>
* @author J0WI <J0WI@users.noreply.github.com>
* @author Joas Schilling <coding@schilljs.com>
* @author Michael Gapczynski <GapczynskiM@gmail.com>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Robin Appelman <robin@icewind.nl>
* @author Robin McCorkell <robin@mccorkell.me.uk>
* @author Roeland Jago Douma <roeland@famdouma.nl>
* @author scambra <sergio@entrecables.com>
* @author Thomas Müller <thomas.mueller@tmit.eu>
* @author Vincent Petry <vincent@nextcloud.com>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* 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, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OCA\Files_Sharing;
use OC\Files\Cache\FailedCache;
use OC\Files\Cache\NullWatcher;
use OC\Files\Cache\Watcher;
use OC\Files\ObjectStore\HomeObjectStoreStorage;
use OC\Files\Storage\Common;
use OC\Files\Storage\FailedStorage;
use OC\Files\Storage\Home;
use OC\Files\Storage\Wrapper\PermissionsMask;
use OC\User\DisplayNameCache;
use OC\User\NoUserException;
use OCA\Files_External\Config\ExternalMountPoint;
use OCP\Constants;
use OCP\Files\Cache\ICacheEntry;
use OCP\Files\Folder;
use OCP\Files\IHomeStorage;
use OCP\Files\IRootFolder;
use OCP\Files\Node;
use OCP\Files\NotFoundException;
use OCP\Files\Storage\IDisableEncryptionStorage;
use OCP\Files\Storage\IStorage;
use OCP\Lock\ILockingProvider;
use OCP\Share\IShare;
use Psr\Log\LoggerInterface;
/**
* Convert target path to source path and pass the function call to the correct storage provider
*/
class SharedStorage extends \OC\Files\Storage\Wrapper\Jail implements ISharedStorage, IDisableEncryptionStorage {
/** @var \OCP\Share\IShare */
private $superShare;
/** @var \OCP\Share\IShare[] */
private $groupedShares;
/**
* @var \OC\Files\View
*/
private $ownerView;
private $initialized = false;
/**
* @var ICacheEntry
*/
private $sourceRootInfo;
/** @var string */
private $user;
private LoggerInterface $logger;
/** @var IStorage */
private $nonMaskedStorage;
private array $mountOptions = [];
/** @var boolean */
private $sharingDisabledForUser;
/** @var ?Folder $ownerUserFolder */
private $ownerUserFolder = null;
private string $sourcePath = '';
public function __construct($arguments) {
$this->ownerView = $arguments['ownerView'];
$this->logger = \OC::$server->get(LoggerInterface::class);
$this->superShare = $arguments['superShare'];
$this->groupedShares = $arguments['groupedShares'];
$this->user = $arguments['user'];
if (isset($arguments['sharingDisabledForUser'])) {
$this->sharingDisabledForUser = $arguments['sharingDisabledForUser'];
} else {
$this->sharingDisabledForUser = false;
}
parent::__construct([
'storage' => null,
'root' => null,
]);
}
/**
* @return ICacheEntry
*/
private function getSourceRootInfo() {
if (is_null($this->sourceRootInfo)) {
if (is_null($this->superShare->getNodeCacheEntry())) {
$this->init();
$this->sourceRootInfo = $this->nonMaskedStorage->getCache()->get($this->rootPath);
} else {
$this->sourceRootInfo = $this->superShare->getNodeCacheEntry();
}
}
return $this->sourceRootInfo;
}
private function init() {
if ($this->initialized) {
return;
}
$this->initialized = true;
try {
/** @var IRootFolder $rootFolder */
$rootFolder = \OC::$server->get(IRootFolder::class);
$this->ownerUserFolder = $rootFolder->getUserFolder($this->superShare->getShareOwner());
$sourceId = $this->superShare->getNodeId();
$ownerNodes = $this->ownerUserFolder->getById($sourceId);
/** @var Node|false $ownerNode */
$ownerNode = current($ownerNodes);
if (!$ownerNode) {
$this->storage = new FailedStorage(['exception' => new NotFoundException("File by id $sourceId not found")]);
$this->cache = new FailedCache();
$this->rootPath = '';
} else {
$this->nonMaskedStorage = $ownerNode->getStorage();
$this->sourcePath = $ownerNode->getPath();
$this->rootPath = $ownerNode->getInternalPath();
$this->storage = new PermissionsMask([
'storage' => $this->nonMaskedStorage,
'mask' => $this->superShare->getPermissions(),
]);
}
} catch (NotFoundException $e) {
// original file not accessible or deleted, set FailedStorage
$this->storage = new FailedStorage(['exception' => $e]);
$this->cache = new FailedCache();
$this->rootPath = '';
} catch (NoUserException $e) {
// sharer user deleted, set FailedStorage
$this->storage = new FailedStorage(['exception' => $e]);
$this->cache = new FailedCache();
$this->rootPath = '';
} catch (\Exception $e) {
$this->storage = new FailedStorage(['exception' => $e]);
$this->cache = new FailedCache();
$this->rootPath = '';
$this->logger->error($e->getMessage(), ['exception' => $e]);
}
if (!$this->nonMaskedStorage) {
$this->nonMaskedStorage = $this->storage;
}
}
/**
* @inheritdoc
*/
public function instanceOfStorage($class): bool {
if ($class === '\OC\Files\Storage\Common' || $class == Common::class) {
return true;
}
if (in_array($class, [
'\OC\Files\Storage\Home',
'\OC\Files\ObjectStore\HomeObjectStoreStorage',
'\OCP\Files\IHomeStorage',
Home::class,
HomeObjectStoreStorage::class,
IHomeStorage::class
])) {
return false;
}
return parent::instanceOfStorage($class);
}
/**
* @return string
*/
public function getShareId() {
return $this->superShare->getId();
}
private function isValid(): bool {
return $this->getSourceRootInfo() && ($this->getSourceRootInfo()->getPermissions() & Constants::PERMISSION_SHARE) === Constants::PERMISSION_SHARE;
}
/**
* get id of the mount point
*
* @return string
*/
public function getId(): string {
return 'shared::' . $this->getMountPoint();
}
/**
* Get the permissions granted for a shared file
*
* @param string $path Shared target file path
* @return int CRUDS permissions granted
*/
public function getPermissions($path = ''): int {
if (!$this->isValid()) {
return 0;
}
$permissions = parent::getPermissions($path) & $this->superShare->getPermissions();
// part files and the mount point always have delete permissions
if ($path === '' || pathinfo($path, PATHINFO_EXTENSION) === 'part') {
$permissions |= \OCP\Constants::PERMISSION_DELETE;
}
if ($this->sharingDisabledForUser) {
$permissions &= ~\OCP\Constants::PERMISSION_SHARE;
}
return $permissions;
}
public function isCreatable($path): bool {
return (bool)($this->getPermissions($path) & \OCP\Constants::PERMISSION_CREATE);
}
public function isReadable($path): bool {
if (!$this->isValid()) {
return false;
}
if (!$this->file_exists($path)) {
return false;
}
/** @var IStorage $storage */
/** @var string $internalPath */
[$storage, $internalPath] = $this->resolvePath($path);
return $storage->isReadable($internalPath);
}
public function isUpdatable($path): bool {
return (bool)($this->getPermissions($path) & \OCP\Constants::PERMISSION_UPDATE);
}
public function isDeletable($path): bool {
return (bool)($this->getPermissions($path) & \OCP\Constants::PERMISSION_DELETE);
}
public function isSharable($path): bool {
if (\OCP\Util::isSharingDisabledForUser() || !\OC\Share\Share::isResharingAllowed()) {
return false;
}
return (bool)($this->getPermissions($path) & \OCP\Constants::PERMISSION_SHARE);
}
public function fopen($path, $mode) {
$source = $this->getUnjailedPath($path);
switch ($mode) {
case 'r+':
case 'rb+':
case 'w+':
case 'wb+':
case 'x+':
case 'xb+':
case 'a+':
case 'ab+':
case 'w':
case 'wb':
case 'x':
case 'xb':
case 'a':
case 'ab':
$creatable = $this->isCreatable(dirname($path));
$updatable = $this->isUpdatable($path);
// if neither permissions given, no need to continue
if (!$creatable && !$updatable) {
if (pathinfo($path, PATHINFO_EXTENSION) === 'part') {
$updatable = $this->isUpdatable(dirname($path));
}
if (!$updatable) {
return false;
}
}
$exists = $this->file_exists($path);
// if a file exists, updatable permissions are required
if ($exists && !$updatable) {
return false;
}
// part file is allowed if !$creatable but the final file is $updatable
if (pathinfo($path, PATHINFO_EXTENSION) !== 'part') {
if (!$exists && !$creatable) {
return false;
}
}
}
$info = [
'target' => $this->getMountPoint() . '/' . $path,
'source' => $source,
'mode' => $mode,
];
\OCP\Util::emitHook('\OC\Files\Storage\Shared', 'fopen', $info);
return $this->nonMaskedStorage->fopen($this->getUnjailedPath($path), $mode);
}
/**
* see https://www.php.net/manual/en/function.rename.php
*
* @param string $source
* @param string $target
* @return bool
*/
public function rename($source, $target): bool {
$this->init();
$isPartFile = pathinfo($source, PATHINFO_EXTENSION) === 'part';
$targetExists = $this->file_exists($target);
$sameFolder = dirname($source) === dirname($target);
if ($targetExists || ($sameFolder && !$isPartFile)) {
if (!$this->isUpdatable('')) {
return false;
}
} else {
if (!$this->isCreatable('')) {
return false;
}
}
return $this->nonMaskedStorage->rename($this->getUnjailedPath($source), $this->getUnjailedPath($target));
}
/**
* return mount point of share, relative to data/user/files
*
* @return string
*/
public function getMountPoint(): string {
return $this->superShare->getTarget();
}
/**
* @param string $path
*/
public function setMountPoint($path): void {
$this->superShare->setTarget($path);
foreach ($this->groupedShares as $share) {
$share->setTarget($path);
}
}
/**
* get the user who shared the file
*
* @return string
*/
public function getSharedFrom(): string {
return $this->superShare->getShareOwner();
}
/**
* @return \OCP\Share\IShare
*/
public function getShare(): IShare {
return $this->superShare;
}
/**
* return share type, can be "file" or "folder"
*
* @return string
*/
public function getItemType(): string {
return $this->superShare->getNodeType();
}
public function getCache($path = '', $storage = null) {
if ($this->cache) {
return $this->cache;
}
if (!$storage) {
$storage = $this;
}
$sourceRoot = $this->getSourceRootInfo();
if ($this->storage instanceof FailedStorage) {
return new FailedCache();
}
$this->cache = new \OCA\Files_Sharing\Cache(
$storage,
$sourceRoot,
\OC::$server->get(DisplayNameCache::class),
$this->getShare()
);
return $this->cache;
}
public function getScanner($path = '', $storage = null) {
if (!$storage) {
$storage = $this;
}
return new \OCA\Files_Sharing\Scanner($storage);
}
public function getOwner($path): string {
return $this->superShare->getShareOwner();
}
public function getWatcher($path = '', $storage = null): Watcher {
$mountManager = \OC::$server->getMountManager();
// Get node information
$node = $this->getShare()->getNodeCacheEntry();
if ($node) {
$mount = $mountManager->findByNumericId($node->getStorageId());
// If the share is originating from an external storage
if (count($mount) > 0 && $mount[0] instanceof ExternalMountPoint) {
// Propagate original storage scan
return parent::getWatcher($path, $storage);
}
}
// cache updating is handled by the share source
return new NullWatcher();
}
/**
* unshare complete storage, also the grouped shares
*
* @return bool
*/
public function unshareStorage(): bool {
foreach ($this->groupedShares as $share) {
\OC::$server->getShareManager()->deleteFromSelf($share, $this->user);
}
return true;
}
/**
* @param string $path
* @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
* @param \OCP\Lock\ILockingProvider $provider
* @throws \OCP\Lock\LockedException
*/
public function acquireLock($path, $type, ILockingProvider $provider) {
/** @var \OCP\Files\Storage $targetStorage */
[$targetStorage, $targetInternalPath] = $this->resolvePath($path);
$targetStorage->acquireLock($targetInternalPath, $type, $provider);
// lock the parent folders of the owner when locking the share as recipient
if ($path === '') {
$sourcePath = $this->ownerUserFolder->getRelativePath($this->sourcePath);
$this->ownerView->lockFile(dirname($sourcePath), ILockingProvider::LOCK_SHARED, true);
}
}
/**
* @param string $path
* @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
* @param \OCP\Lock\ILockingProvider $provider
*/
public function releaseLock($path, $type, ILockingProvider $provider) {
/** @var \OCP\Files\Storage $targetStorage */
[$targetStorage, $targetInternalPath] = $this->resolvePath($path);
$targetStorage->releaseLock($targetInternalPath, $type, $provider);
// unlock the parent folders of the owner when unlocking the share as recipient
if ($path === '') {
$sourcePath = $this->ownerUserFolder->getRelativePath($this->sourcePath);
$this->ownerView->unlockFile(dirname($sourcePath), ILockingProvider::LOCK_SHARED, true);
}
}
/**
* @param string $path
* @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
* @param \OCP\Lock\ILockingProvider $provider
*/
public function changeLock($path, $type, ILockingProvider $provider) {
/** @var \OCP\Files\Storage $targetStorage */
[$targetStorage, $targetInternalPath] = $this->resolvePath($path);
$targetStorage->changeLock($targetInternalPath, $type, $provider);
}
/**
* @return array [ available, last_checked ]
*/
public function getAvailability() {
// shares do not participate in availability logic
return [
'available' => true,
'last_checked' => 0,
];
}
/**
* @param bool $isAvailable
*/
public function setAvailability($isAvailable) {
// shares do not participate in availability logic
}
public function getSourceStorage() {
$this->init();
return $this->nonMaskedStorage;
}
public function getWrapperStorage() {
$this->init();
return $this->storage;
}
public function file_get_contents($path) {
$info = [
'target' => $this->getMountPoint() . '/' . $path,
'source' => $this->getUnjailedPath($path),
];
\OCP\Util::emitHook('\OC\Files\Storage\Shared', 'file_get_contents', $info);
return parent::file_get_contents($path);
}
public function file_put_contents($path, $data) {
$info = [
'target' => $this->getMountPoint() . '/' . $path,
'source' => $this->getUnjailedPath($path),
];
\OCP\Util::emitHook('\OC\Files\Storage\Shared', 'file_put_contents', $info);
return parent::file_put_contents($path, $data);
}
/**
* @return void
*/
public function setMountOptions(array $options) {
/* Note: This value is never read */
$this->mountOptions = $options;
}
public function getUnjailedPath($path) {
$this->init();
return parent::getUnjailedPath($path);
}
}
@@ -0,0 +1,136 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Björn Schießle <bjoern@schiessle.org>
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Joas Schilling <coding@schilljs.com>
* @author Michael Gapczynski <GapczynskiM@gmail.com>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Roeland Jago Douma <roeland@famdouma.nl>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* 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, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OCA\Files_Sharing;
use OC\Files\Mount\MountPoint;
use OCP\Constants;
use OCP\Files\Folder;
use OCP\Share\IShare;
class Updater {
/**
* @param array $params
*/
public static function renameHook($params) {
self::renameChildren($params['oldpath'], $params['newpath']);
self::moveShareInOrOutOfShare($params['newpath']);
}
/**
* Fix for https://github.com/owncloud/core/issues/20769
*
* The owner is allowed to move their files (if they are shared) into a receiving folder
* In this case we need to update the parent of the moved share. Since they are
* effectively handing over ownership of the file the rest of the code needs to know
* they need to build up the reshare tree.
*
* @param string $path
*/
private static function moveShareInOrOutOfShare($path): void {
$userFolder = \OC::$server->getUserFolder();
// If the user folder can't be constructed (e.g. link share) just return.
if ($userFolder === null) {
return;
}
$src = $userFolder->get($path);
$shareManager = \OC::$server->getShareManager();
// FIXME: should CIRCLES be included here ??
$shares = $shareManager->getSharesBy($userFolder->getOwner()->getUID(), IShare::TYPE_USER, $src, false, -1);
$shares = array_merge($shares, $shareManager->getSharesBy($userFolder->getOwner()->getUID(), IShare::TYPE_GROUP, $src, false, -1));
$shares = array_merge($shares, $shareManager->getSharesBy($userFolder->getOwner()->getUID(), IShare::TYPE_ROOM, $src, false, -1));
if ($src instanceof Folder) {
$subShares = $shareManager->getSharesInFolder($userFolder->getOwner()->getUID(), $src, false, false);
foreach ($subShares as $subShare) {
$shares = array_merge($shares, array_values($subShare));
}
}
// If the path we move is not a share we don't care
if (empty($shares)) {
return;
}
// Check if the destination is inside a share
$mountManager = \OC::$server->getMountManager();
$dstMount = $mountManager->find($src->getPath());
//Ownership is moved over
foreach ($shares as $share) {
if (
$share->getShareType() !== IShare::TYPE_USER &&
$share->getShareType() !== IShare::TYPE_GROUP &&
$share->getShareType() !== IShare::TYPE_ROOM
) {
continue;
}
if ($dstMount instanceof \OCA\Files_Sharing\SharedMount) {
if (!($dstMount->getShare()->getPermissions() & Constants::PERMISSION_SHARE)) {
$shareManager->deleteShare($share);
continue;
}
$newOwner = $dstMount->getShare()->getShareOwner();
$newPermissions = $share->getPermissions() & $dstMount->getShare()->getPermissions();
} else {
$newOwner = $userFolder->getOwner()->getUID();
$newPermissions = $share->getPermissions();
}
$share->setShareOwner($newOwner);
$share->setPermissions($newPermissions);
$shareManager->updateShare($share);
}
}
/**
* rename mount point from the children if the parent was renamed
*
* @param string $oldPath old path relative to data/user/files
* @param string $newPath new path relative to data/user/files
*/
private static function renameChildren($oldPath, $newPath) {
$absNewPath = \OC\Files\Filesystem::normalizePath('/' . \OC_User::getUser() . '/files/' . $newPath);
$absOldPath = \OC\Files\Filesystem::normalizePath('/' . \OC_User::getUser() . '/files/' . $oldPath);
$mountManager = \OC\Files\Filesystem::getMountManager();
$mountedShares = $mountManager->findIn('/' . \OC_User::getUser() . '/files/' . $oldPath);
foreach ($mountedShares as $mount) {
/** @var MountPoint $mount */
if ($mount->getStorage()->instanceOfStorage(ISharedStorage::class)) {
$mountPoint = $mount->getMountPoint();
$target = str_replace($absOldPath, $absNewPath, $mountPoint);
$mount->moveMount($target);
}
}
}
}
@@ -0,0 +1,121 @@
<?php
/**
* @author Piotr Mrowczynski piotr@owncloud.com
*
* @copyright Copyright (c) 2019, ownCloud GmbH
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* 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, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OCA\Files_Sharing;
use OCP\Files\File;
use OCP\Files\Folder;
use OCP\Files\Node;
use OCP\Files\NotFoundException;
/**
* Handles restricting for download of files
*/
class ViewOnly {
/** @var Folder */
private $userFolder;
public function __construct(Folder $userFolder) {
$this->userFolder = $userFolder;
}
/**
* @param string[] $pathsToCheck
* @return bool
*/
public function check(array $pathsToCheck): bool {
// If any of elements cannot be downloaded, prevent whole download
foreach ($pathsToCheck as $file) {
try {
$info = $this->userFolder->get($file);
if ($info instanceof File) {
// access to filecache is expensive in the loop
if (!$this->checkFileInfo($info)) {
return false;
}
} elseif ($info instanceof Folder) {
// get directory content is rather cheap query
if (!$this->dirRecursiveCheck($info)) {
return false;
}
}
} catch (NotFoundException $e) {
continue;
}
}
return true;
}
/**
* @param Folder $dirInfo
* @return bool
* @throws NotFoundException
*/
private function dirRecursiveCheck(Folder $dirInfo): bool {
if (!$this->checkFileInfo($dirInfo)) {
return false;
}
// If any of elements cannot be downloaded, prevent whole download
$files = $dirInfo->getDirectoryListing();
foreach ($files as $file) {
if ($file instanceof File) {
if (!$this->checkFileInfo($file)) {
return false;
}
} elseif ($file instanceof Folder) {
return $this->dirRecursiveCheck($file);
}
}
return true;
}
/**
* @param Node $fileInfo
* @return bool
* @throws NotFoundException
*/
private function checkFileInfo(Node $fileInfo): bool {
// Restrict view-only to nodes which are shared
$storage = $fileInfo->getStorage();
if (!$storage->instanceOfStorage(SharedStorage::class)) {
return true;
}
// Extract extra permissions
/** @var \OCA\Files_Sharing\SharedStorage $storage */
$share = $storage->getShare();
$canDownload = true;
// Check if read-only and on whether permission can download is both set and disabled.
$attributes = $share->getAttributes();
if ($attributes !== null) {
$canDownload = $attributes->getAttribute('permissions', 'download');
}
if ($canDownload !== null && !$canDownload) {
return false;
}
return true;
}
}