Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dynamic share providers #31571

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 80 additions & 24 deletions apps/files_sharing/lib/Controller/ShareAPIController.php
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,8 @@ class ShareAPIController extends OCSController {
/** @var IPreview */
private $previewManager;

private $dynamicShareTypes;

/**
* Share20OCS constructor.
*
Expand Down Expand Up @@ -161,6 +163,33 @@ public function __construct(
$this->serverContainer = $serverContainer;
$this->userStatusManager = $userStatusManager;
$this->previewManager = $previewManager;
$this->dynamicShareTypes = [];
// FIXME: Move this line into the sciencemesh app:
$this->registerHelper(IShare::TYPE_SCIENCEMESH, 'sciencemesh', '\OCA\ScienceMesh\ShareProvider\ShareAPIHelper');
}

public function registerHelper($shareType, $identifier, $helperClassName) {
$this->dynamicShareTypes[$shareType] = [
"identifier" => $identifier,
"helperClass" => $helperClassName
];
}

private function haveHelperFor($shareType) {
return isset($this->dynamicShareTypes[$shareType]);
}

private function getIdentifierFor($shareType) {
return $this->dynamicShareTypes[$shareType]["identifier"];
}

private function getHelperFor($shareType) {
$identifier = $this->getIdentifierFor($shareType);
if (!$this->appManager->isEnabledForUser($identifier)) {
throw new QueryException();
}

return $this->serverContainer->get($this->dynamicShareTypes[$shareType]["helperClass"]);
}

/**
Expand Down Expand Up @@ -318,6 +347,14 @@ protected function formatShare(IShare $share, Node $recipientNode = null): array
$result = array_merge($result, $this->getDeckShareHelper()->formatShare($share));
} catch (QueryException $e) {
}
} elseif ($this->haveHelperFor($share->getShareType())) {
$result['share_with'] = $share->getSharedWith();
$result['share_with_displayname'] = '';

try {
$result = array_merge($result, $this->getHelperFor($share->getShareType())->formatShare($share));
} catch (QueryException $e) {
}
}


Expand Down Expand Up @@ -663,6 +700,12 @@ public function createShare(
} catch (QueryException $e) {
throw new OCSForbiddenException($this->l->t('Sharing %s failed because the back end does not support room shares', [$node->getPath()]));
}
} elseif ($this->haveHelperFor($shareType)) {
try {
$this->getHelperFor($shareType)->createShare($share, $shareWith, $permissions, $expireDate);
} catch (QueryException $e) {
throw new OCSForbiddenException($this->l->t('Sharing %s failed because the back end does not support this type of shares', [$node->getPath()]));
}
} else {
throw new OCSBadRequestException($this->l->t('Unknown share type'));
}
Expand Down Expand Up @@ -1568,6 +1611,17 @@ private function getShareById(string $id): IShare {
// Do nothing, just try the other share type
}

foreach ($this->dynamicShareTypes as $shareType => $details) {
try {
if ($this->shareManager->shareProviderExists($shareType)) {
$share = $this->shareManager->getShareById($details["identifier"] . ":" . $id, $this->currentUser);
return $share;
}
} catch (ShareNotFound $e) {
// Do nothing, just try the other share type
}
}

if (!$this->shareManager->outgoingServer2ServerSharesAllowed()) {
throw new ShareNotFound();
}
Expand Down Expand Up @@ -1631,14 +1685,7 @@ private function getDeckShareHelper() {
return $this->serverContainer->get('\OCA\Deck\Sharing\ShareAPIHelper');
}

/**
* @param string $viewer
* @param Node $node
* @param bool $reShares
*
* @return IShare[]
*/
private function getSharesFromNode(string $viewer, $node, bool $reShares): array {
private function getProvidersExceptOutgoing() {
$providers = [
IShare::TYPE_USER,
IShare::TYPE_GROUP,
Expand All @@ -1648,6 +1695,23 @@ private function getSharesFromNode(string $viewer, $node, bool $reShares): array
IShare::TYPE_ROOM,
IShare::TYPE_DECK
];
foreach ($this->dynamicShareTypes as $shareType => $details) {
if ($this->shareManager->shareProviderExists($shareType)) {
array_push($providers, $shareType);
}
}
return $providers;
}

/**
* @param string $viewer
* @param Node $node
* @param bool $reShares
*
* @return IShare[]
*/
private function getSharesFromNode(string $viewer, $node, bool $reShares): array {
$providers = $this->getProvidersExceptOutgoing();

// Should we assume that the (currentUser) viewer is the owner of the node !?
$shares = [];
Expand Down Expand Up @@ -1785,21 +1849,13 @@ private function shareProviderResharingRights(string $userId, IShare $share, $no
* @return IShare[]
*/
private function getAllShares(?Node $path = null, bool $reshares = false) {
// Get all shares
$userShares = $this->shareManager->getSharesBy($this->currentUser, IShare::TYPE_USER, $path, $reshares, -1, 0);
$groupShares = $this->shareManager->getSharesBy($this->currentUser, IShare::TYPE_GROUP, $path, $reshares, -1, 0);
$linkShares = $this->shareManager->getSharesBy($this->currentUser, IShare::TYPE_LINK, $path, $reshares, -1, 0);

// EMAIL SHARES
$mailShares = $this->shareManager->getSharesBy($this->currentUser, IShare::TYPE_EMAIL, $path, $reshares, -1, 0);

// CIRCLE SHARES
$circleShares = $this->shareManager->getSharesBy($this->currentUser, IShare::TYPE_CIRCLE, $path, $reshares, -1, 0);

// TALK SHARES
$roomShares = $this->shareManager->getSharesBy($this->currentUser, IShare::TYPE_ROOM, $path, $reshares, -1, 0);

$deckShares = $this->shareManager->getSharesBy($this->currentUser, IShare::TYPE_DECK, $path, $reshares, -1, 0);
$providers = $this->getProvidersExceptOutgoing();
$shares = [];
foreach ($providers as $provider) {
$providerShares =
$this->shareManager->getSharesBy($this->currentUser, $provider, $path, $reshares, -1, 0);
$shares = array_merge($shares, $providerShares);
}

// FEDERATION
if ($this->shareManager->outgoingServer2ServerSharesAllowed()) {
Expand All @@ -1813,7 +1869,7 @@ private function getAllShares(?Node $path = null, bool $reshares = false) {
$federatedGroupShares = [];
}

return array_merge($userShares, $groupShares, $linkShares, $mailShares, $circleShares, $roomShares, $deckShares, $federatedShares, $federatedGroupShares);
return array_merge($shares, $federatedShares, $federatedGroupShares);
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -199,10 +199,10 @@ public function testDeleteShareShareNotFound() {
$this->expectExceptionMessage('Wrong share ID, share doesn\'t exist');

$this->shareManager
->expects($this->exactly(6))
->expects($this->exactly(7))
->method('getShareById')
->willReturnCallback(function ($id) {
if ($id === 'ocinternal:42' || $id === 'ocRoomShare:42' || $id === 'ocFederatedSharing:42' || $id === 'ocCircleShare:42' || $id === 'ocMailShare:42' || $id === 'deck:42') {
if ($id === 'ocinternal:42' || $id === 'ocRoomShare:42' || $id === 'ocFederatedSharing:42' || $id === 'ocCircleShare:42' || $id === 'ocMailShare:42' || $id === 'deck:42' || $id === 'sciencemesh:42') {
throw new \OCP\Share\Exceptions\ShareNotFound();
} else {
throw new \Exception();
Expand Down
1 change: 1 addition & 0 deletions lib/private/Share20/Manager.php
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,7 @@ protected function generalCreateChecks(IShare $share) {
}
} elseif ($share->getShareType() === IShare::TYPE_ROOM) {
} elseif ($share->getShareType() === IShare::TYPE_DECK) {
} elseif ($this->factory->getProviderForType($share->getShareType())) {
} else {
// We cannot handle other types yet
throw new \InvalidArgumentException('unknown share type');
Expand Down
14 changes: 14 additions & 0 deletions lib/private/Share20/ProviderFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,20 @@ public function getProviderForType($shareType) {
$provider = $this->getRoomShareProvider();
} elseif ($shareType === IShare::TYPE_DECK) {
$provider = $this->getProvider('deck');
} else {
// try to autodetect in the registered providers;
foreach ($this->registeredShareProviders as $shareProvider) {
/** @var IShareProvider $instance */
$instance = $this->serverContainer->get($shareProvider);

// not all instances will have the isShareTypeSupported function;
if (method_exists($instance, "isShareTypeSupported")) {
if ($instance->isShareTypeSupported($shareType)) {
$provider = $this->getProvider($instance->identifier());
break;
}
}
}
}


Expand Down
5 changes: 5 additions & 0 deletions lib/public/Share/IShare.php
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,11 @@ interface IShare {
*/
public const TYPE_DECK_USER = 13;

/**
* @since ?
*/
public const TYPE_SCIENCEMESH = 1000;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's not really a dynamic share type if app developers still need to add constants here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, true! We should move this into https://github.com/nextcloud/server/pull/31571/files#diff-dbbe017dd357504abc442a6f1d0305166520ebf80353f42814b3f879a3e241bcR168 probably. And then if two apps are installed that try to claim the same number, it should complain that you cannot have both those apps installed at the same time.


/**
* @since 18.0.0
*/
Expand Down
3 changes: 3 additions & 0 deletions tests/lib/Share20/ManagerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4663,6 +4663,9 @@ public function getProvider($id) {
* @return IShareProvider
*/
public function getProviderForType($shareType) {
if ($shareType == -1) {
return null;
}
return $this->provider;
}

Expand Down
73 changes: 73 additions & 0 deletions tests/lib/Share20/ProviderFactoryTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<?php
/**
* @author Michiel de Jong <michiel@unhosted.org>
*
* @copyright Copyright (c) 2022, Nextcloud, Inc.
* @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 Test\Share20;

use OCP\IServerContainer;

const EXAMPLE_SHARE_TYPE = 123;
const UNKNOWN_SHARE_TYPE = 456;

class ExampleProvider {
public function isShareTypeSupported($shareType) {
return ($shareType == EXAMPLE_SHARE_TYPE);
}
public function identifier() {
return "example";
}
}

/**
* Class ProviderFactoryTest
*
* @package Test\Share20
*/
class ProviderFactoryTest extends \Test\TestCase {

/** @var \OCP\IServerContainer|\PHPUnit\Framework\MockObject\MockObject */
protected $serverContainer;
/** @var \OCP\Share20\ProviderFactory */
protected $factory;

/** @var ExampleProvider */
protected $dynamicProvider;

protected function setUp(): void {
$this->dynamicProvider = new ExampleProvider();
$this->serverContainer = $this->createMock(IServerContainer::class);
$this->serverContainer->method('get')
->with(ExampleProvider::class)
->willReturn($this->dynamicProvider);
$this->factory = new \OC\Share20\ProviderFactory($this->serverContainer);
$this->factory->registerProvider(ExampleProvider::class);
}


public function testDynamicProvider() {
$provider = $this->factory->getProviderForType(EXAMPLE_SHARE_TYPE);
$this->assertEquals($provider, $this->dynamicProvider);
}

public function testUnknownType() {
$this->expectExceptionMessage('No share provider for share type 456');
$provider = $this->factory->getProviderForType(UNKNOWN_SHARE_TYPE);
}
}