Skip to content

Commit 3715d73

Browse files
authored
Merge branch 'master' into fix/android-chrome-detection
2 parents 2bd6259 + 31af870 commit 3715d73

File tree

7 files changed

+185
-113
lines changed

7 files changed

+185
-113
lines changed

apps/dav/appinfo/v1/publicwebdav.php

Lines changed: 48 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -68,47 +68,55 @@
6868
$linkCheckPlugin = new PublicLinkCheckPlugin();
6969
$filesDropPlugin = new FilesDropPlugin();
7070

71-
$server = $serverFactory->createServer(false, $baseuri, $requestUri, $authPlugin, function (\Sabre\DAV\Server $server) use ($authBackend, $linkCheckPlugin, $filesDropPlugin) {
72-
$isAjax = in_array('XMLHttpRequest', explode(',', $_SERVER['HTTP_X_REQUESTED_WITH'] ?? ''));
73-
/** @var FederatedShareProvider $shareProvider */
74-
$federatedShareProvider = Server::get(FederatedShareProvider::class);
75-
if ($federatedShareProvider->isOutgoingServer2serverShareEnabled() === false && !$isAjax) {
76-
// this is what is thrown when trying to access a non-existing share
77-
throw new \Sabre\DAV\Exception\NotAuthenticated();
78-
}
79-
80-
$share = $authBackend->getShare();
81-
$owner = $share->getShareOwner();
82-
$isReadable = $share->getPermissions() & Constants::PERMISSION_READ;
83-
$fileId = $share->getNodeId();
84-
85-
// FIXME: should not add storage wrappers outside of preSetup, need to find a better way
86-
$previousLog = Filesystem::logWarningWhenAddingStorageWrapper(false);
87-
Filesystem::addStorageWrapper('sharePermissions', function ($mountPoint, $storage) use ($share) {
88-
return new PermissionsMask(['storage' => $storage, 'mask' => $share->getPermissions() | Constants::PERMISSION_SHARE]);
71+
$server = $serverFactory->createServer(
72+
true,
73+
$baseuri,
74+
$requestUri,
75+
$authPlugin,
76+
function (\Sabre\DAV\Server $server) use (
77+
$authBackend,
78+
$linkCheckPlugin,
79+
$filesDropPlugin
80+
) {
81+
$isAjax = in_array('XMLHttpRequest', explode(',', $_SERVER['HTTP_X_REQUESTED_WITH'] ?? ''));
82+
/** @var FederatedShareProvider $shareProvider */
83+
$federatedShareProvider = Server::get(FederatedShareProvider::class);
84+
if ($federatedShareProvider->isOutgoingServer2serverShareEnabled() === false && !$isAjax) {
85+
// this is what is thrown when trying to access a non-existing share
86+
throw new \Sabre\DAV\Exception\NotAuthenticated();
87+
}
88+
89+
$share = $authBackend->getShare();
90+
$owner = $share->getShareOwner();
91+
$isReadable = $share->getPermissions() & Constants::PERMISSION_READ;
92+
$fileId = $share->getNodeId();
93+
94+
// FIXME: should not add storage wrappers outside of preSetup, need to find a better way
95+
$previousLog = Filesystem::logWarningWhenAddingStorageWrapper(false);
96+
Filesystem::addStorageWrapper('sharePermissions', function ($mountPoint, $storage) use ($share) {
97+
return new PermissionsMask(['storage' => $storage, 'mask' => $share->getPermissions() | Constants::PERMISSION_SHARE]);
98+
});
99+
Filesystem::addStorageWrapper('shareOwner', function ($mountPoint, $storage) use ($share) {
100+
return new PublicOwnerWrapper(['storage' => $storage, 'owner' => $share->getShareOwner()]);
101+
});
102+
Filesystem::logWarningWhenAddingStorageWrapper($previousLog);
103+
104+
$rootFolder = Server::get(IRootFolder::class);
105+
$userFolder = $rootFolder->getUserFolder($owner);
106+
$node = $userFolder->getFirstNodeById($fileId);
107+
if (!$node) {
108+
throw new \Sabre\DAV\Exception\NotFound();
109+
}
110+
$linkCheckPlugin->setFileInfo($node);
111+
112+
// If not readable (files_drop) enable the filesdrop plugin
113+
if (!$isReadable) {
114+
$filesDropPlugin->enable();
115+
}
116+
$filesDropPlugin->setShare($share);
117+
118+
return new View($node->getPath());
89119
});
90-
Filesystem::addStorageWrapper('shareOwner', function ($mountPoint, $storage) use ($share) {
91-
return new PublicOwnerWrapper(['storage' => $storage, 'owner' => $share->getShareOwner()]);
92-
});
93-
Filesystem::logWarningWhenAddingStorageWrapper($previousLog);
94-
95-
$rootFolder = Server::get(IRootFolder::class);
96-
$userFolder = $rootFolder->getUserFolder($owner);
97-
$node = $userFolder->getFirstNodeById($fileId);
98-
if (!$node) {
99-
throw new \Sabre\DAV\Exception\NotFound();
100-
}
101-
$linkCheckPlugin->setFileInfo($node);
102-
103-
// If not readable (files_drop) enable the filesdrop plugin
104-
if (!$isReadable) {
105-
$filesDropPlugin->enable();
106-
}
107-
$filesDropPlugin->setShare($share);
108-
109-
$view = new View($node->getPath());
110-
return $view;
111-
});
112120

113121
$server->addPlugin($linkCheckPlugin);
114122
$server->addPlugin($filesDropPlugin);

apps/dav/lib/Connector/Sabre/Directory.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ public function getChild($name, $info = null, ?IRequest $request = null, ?IL10N
181181
// If we are, then only PUT and MKCOL are allowed (see plugin)
182182
// so we are safe to return the directory without a risk of
183183
// leaking files and folders structure.
184-
if ($storage instanceof PublicShareWrapper) {
184+
if ($storage->instanceOfStorage(PublicShareWrapper::class)) {
185185
$share = $storage->getShare();
186186
$allowDirectory = ($share->getPermissions() & Constants::PERMISSION_READ) !== Constants::PERMISSION_READ;
187187
}

apps/dav/lib/Connector/Sabre/ServerFactory.php

Lines changed: 69 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
use OCP\ITagManager;
3838
use OCP\IUserManager;
3939
use OCP\IUserSession;
40+
use OCP\L10N\IFactory;
4041
use OCP\SabrePluginEvent;
4142
use OCP\SystemTag\ISystemTagManager;
4243
use OCP\SystemTag\ISystemTagObjectMapper;
@@ -70,15 +71,13 @@ public function createServer(
7071
Plugin $authPlugin,
7172
callable $viewCallBack,
7273
): Server {
74+
// /public.php/webdav/ shows the files in the share in the root itself
75+
// and not under /public.php/webdav/files/{token} so we should keep
76+
// compatibility for that.
77+
$needsSharesInRoot = $baseUri === '/public.php/webdav/';
78+
$useCollection = $isPublicShare && !$needsSharesInRoot;
7379
$debugEnabled = $this->config->getSystemValue('debug', false);
74-
// Fire up server
75-
if ($isPublicShare) {
76-
$rootCollection = new SimpleCollection('root');
77-
$tree = new CachingTree($rootCollection);
78-
} else {
79-
$rootCollection = null;
80-
$tree = new ObjectTree();
81-
}
80+
[$tree, $rootCollection] = $this->getTree($useCollection);
8281
$server = new Server($tree);
8382
// Set URL explicitly due to reverse-proxy situations
8483
$server->httpRequest->setUrl($requestUri);
@@ -127,8 +126,8 @@ public function createServer(
127126
}
128127

129128
// wait with registering these until auth is handled and the filesystem is setup
130-
$server->on('beforeMethod:*', function () use ($server, $tree,
131-
$viewCallBack, $isPublicShare, $rootCollection, $debugEnabled): void {
129+
$server->on('beforeMethod:*', function () use ($server,
130+
$tree, $viewCallBack, $isPublicShare, $rootCollection, $debugEnabled): void {
132131
// ensure the skeleton is copied
133132
$userFolder = \OC::$server->getUserFolder();
134133

@@ -147,36 +146,8 @@ public function createServer(
147146
$root = new File($view, $rootInfo);
148147
}
149148

150-
if ($isPublicShare) {
151-
$userPrincipalBackend = new Principal(
152-
\OCP\Server::get(IUserManager::class),
153-
\OCP\Server::get(IGroupManager::class),
154-
\OCP\Server::get(IAccountManager::class),
155-
\OCP\Server::get(\OCP\Share\IManager::class),
156-
\OCP\Server::get(IUserSession::class),
157-
\OCP\Server::get(IAppManager::class),
158-
\OCP\Server::get(ProxyMapper::class),
159-
\OCP\Server::get(KnownUserService::class),
160-
\OCP\Server::get(IConfig::class),
161-
\OC::$server->getL10NFactory(),
162-
);
163-
164-
// Mount the share collection at /public.php/dav/shares/<share token>
165-
$rootCollection->addChild(new RootCollection(
166-
$root,
167-
$userPrincipalBackend,
168-
'principals/shares',
169-
));
170-
171-
// Mount the upload collection at /public.php/dav/uploads/<share token>
172-
$rootCollection->addChild(new \OCA\DAV\Upload\RootCollection(
173-
$userPrincipalBackend,
174-
'principals/shares',
175-
\OCP\Server::get(CleanupService::class),
176-
\OCP\Server::get(IRootFolder::class),
177-
\OCP\Server::get(IUserSession::class),
178-
\OCP\Server::get(\OCP\Share\IManager::class),
179-
));
149+
if ($rootCollection !== null) {
150+
$this->initRootCollection($rootCollection, $root);
180151
} else {
181152
/** @var ObjectTree $tree */
182153
$tree->init($root, $view, $this->mountManager);
@@ -191,7 +162,7 @@ public function createServer(
191162
$this->userSession,
192163
\OCP\Server::get(IFilenameValidator::class),
193164
\OCP\Server::get(IAccountManager::class),
194-
false,
165+
$isPublicShare,
195166
!$debugEnabled
196167
)
197168
);
@@ -252,4 +223,61 @@ public function createServer(
252223
}, 30); // priority 30: after auth (10) and acl(20), before lock(50) and handling the request
253224
return $server;
254225
}
226+
227+
/**
228+
* Returns a Tree object and, if $useCollection is true, the collection used
229+
* as root.
230+
*
231+
* @param bool $useCollection Whether to use a collection or the legacy
232+
* ObjectTree, which doesn't use collections.
233+
* @return array{0: CachingTree|ObjectTree, 1: SimpleCollection|null}
234+
*/
235+
public function getTree(bool $useCollection): array {
236+
if ($useCollection) {
237+
$rootCollection = new SimpleCollection('root');
238+
$tree = new CachingTree($rootCollection);
239+
return [$tree, $rootCollection];
240+
}
241+
242+
return [new ObjectTree(), null];
243+
}
244+
245+
/**
246+
* Adds the user's principal backend to $rootCollection.
247+
*/
248+
private function initRootCollection(SimpleCollection $rootCollection, Directory|File $root): void {
249+
$userPrincipalBackend = new Principal(
250+
\OCP\Server::get(IUserManager::class),
251+
\OCP\Server::get(IGroupManager::class),
252+
\OCP\Server::get(IAccountManager::class),
253+
\OCP\Server::get(\OCP\Share\IManager::class),
254+
\OCP\Server::get(IUserSession::class),
255+
\OCP\Server::get(IAppManager::class),
256+
\OCP\Server::get(ProxyMapper::class),
257+
\OCP\Server::get(KnownUserService::class),
258+
\OCP\Server::get(IConfig::class),
259+
\OCP\Server::get(IFactory::class),
260+
);
261+
262+
// Mount the share collection at /public.php/dav/files/<share token>
263+
$rootCollection->addChild(
264+
new RootCollection(
265+
$root,
266+
$userPrincipalBackend,
267+
'principals/shares',
268+
)
269+
);
270+
271+
// Mount the upload collection at /public.php/dav/uploads/<share token>
272+
$rootCollection->addChild(
273+
new \OCA\DAV\Upload\RootCollection(
274+
$userPrincipalBackend,
275+
'principals/shares',
276+
\OCP\Server::get(CleanupService::class),
277+
\OCP\Server::get(IRootFolder::class),
278+
\OCP\Server::get(IUserSession::class),
279+
\OCP\Server::get(\OCP\Share\IManager::class),
280+
)
281+
);
282+
}
255283
}

apps/dav/tests/unit/Connector/Sabre/DirectoryTest.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
use OCP\Files\ForbiddenException;
2222
use OCP\Files\InvalidPathException;
2323
use OCP\Files\Mount\IMountPoint;
24+
use OCP\Files\Storage\IStorage;
2425
use OCP\Files\StorageNotAvailableException;
2526
use PHPUnit\Framework\MockObject\MockObject;
2627
use Test\Traits\UserTrait;
@@ -61,12 +62,16 @@ class DirectoryTest extends \Test\TestCase {
6162

6263
private View&MockObject $view;
6364
private FileInfo&MockObject $info;
65+
private IStorage&MockObject $storage;
6466

6567
protected function setUp(): void {
6668
parent::setUp();
6769

6870
$this->view = $this->createMock(View::class);
6971
$this->info = $this->createMock(FileInfo::class);
72+
$this->storage = $this->createMock(IStorage::class);
73+
$this->info->method('getStorage')
74+
->willReturn($this->storage);
7075
$this->info->method('isReadable')
7176
->willReturn(true);
7277
$this->info->method('getType')

apps/workflowengine/lib/Manager.php

Lines changed: 25 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -422,16 +422,18 @@ public function deleteOperation(int $id, ScopeContext $scopeContext): bool {
422422
* @param array $events
423423
*/
424424
protected function validateEvents(string $entity, array $events, IOperation $operation): void {
425+
/** @psalm-suppress TaintedCallable newInstance is not called */
426+
$reflection = new \ReflectionClass($entity);
427+
if ($entity !== IEntity::class && !in_array(IEntity::class, $reflection->getInterfaceNames())) {
428+
throw new \UnexpectedValueException($this->l->t('Entity %s is invalid', [$entity]));
429+
}
430+
425431
try {
426432
$instance = $this->container->get($entity);
427433
} catch (ContainerExceptionInterface $e) {
428434
throw new \UnexpectedValueException($this->l->t('Entity %s does not exist', [$entity]));
429435
}
430436

431-
if (!$instance instanceof IEntity) {
432-
throw new \UnexpectedValueException($this->l->t('Entity %s is invalid', [$entity]));
433-
}
434-
435437
if (empty($events)) {
436438
if (!$operation instanceof IComplexOperation) {
437439
throw new \UnexpectedValueException($this->l->t('No events are chosen.'));
@@ -458,17 +460,23 @@ protected function validateEvents(string $entity, array $events, IOperation $ope
458460
* @throws \UnexpectedValueException
459461
*/
460462
public function validateOperation(string $class, string $name, array $checks, string $operation, ScopeContext $scope, string $entity, array $events): void {
463+
if (strlen($operation) > IManager::MAX_OPERATION_VALUE_BYTES) {
464+
throw new \UnexpectedValueException($this->l->t('The provided operation data is too long'));
465+
}
466+
467+
/** @psalm-suppress TaintedCallable newInstance is not called */
468+
$reflection = new \ReflectionClass($class);
469+
if ($class !== IOperation::class && !in_array(IOperation::class, $reflection->getInterfaceNames())) {
470+
throw new \UnexpectedValueException($this->l->t('Operation %s is invalid', [$class]) . join(', ', $reflection->getInterfaceNames()));
471+
}
472+
461473
try {
462474
/** @var IOperation $instance */
463475
$instance = $this->container->get($class);
464476
} catch (ContainerExceptionInterface $e) {
465477
throw new \UnexpectedValueException($this->l->t('Operation %s does not exist', [$class]));
466478
}
467479

468-
if (!($instance instanceof IOperation)) {
469-
throw new \UnexpectedValueException($this->l->t('Operation %s is invalid', [$class]));
470-
}
471-
472480
if (!$instance->isAvailableForScope($scope->getScope())) {
473481
throw new \UnexpectedValueException($this->l->t('Operation %s is invalid', [$class]));
474482
}
@@ -479,38 +487,35 @@ public function validateOperation(string $class, string $name, array $checks, st
479487
throw new \UnexpectedValueException($this->l->t('At least one check needs to be provided'));
480488
}
481489

482-
if (strlen($operation) > IManager::MAX_OPERATION_VALUE_BYTES) {
483-
throw new \UnexpectedValueException($this->l->t('The provided operation data is too long'));
484-
}
485-
486490
$instance->validateOperation($name, $checks, $operation);
487491

488492
foreach ($checks as $check) {
489493
if (!is_string($check['class'])) {
490494
throw new \UnexpectedValueException($this->l->t('Invalid check provided'));
491495
}
492496

497+
if (strlen((string)$check['value']) > IManager::MAX_CHECK_VALUE_BYTES) {
498+
throw new \UnexpectedValueException($this->l->t('The provided check value is too long'));
499+
}
500+
501+
$reflection = new \ReflectionClass($check['class']);
502+
if ($check['class'] !== ICheck::class && !in_array(ICheck::class, $reflection->getInterfaceNames())) {
503+
throw new \UnexpectedValueException($this->l->t('Check %s is invalid', [$class]));
504+
}
505+
493506
try {
494507
/** @var ICheck $instance */
495508
$instance = $this->container->get($check['class']);
496509
} catch (ContainerExceptionInterface) {
497510
throw new \UnexpectedValueException($this->l->t('Check %s does not exist', [$class]));
498511
}
499512

500-
if (!($instance instanceof ICheck)) {
501-
throw new \UnexpectedValueException($this->l->t('Check %s is invalid', [$class]));
502-
}
503-
504513
if (!empty($instance->supportedEntities())
505514
&& !in_array($entity, $instance->supportedEntities())
506515
) {
507516
throw new \UnexpectedValueException($this->l->t('Check %s is not allowed with this entity', [$class]));
508517
}
509518

510-
if (strlen((string)$check['value']) > IManager::MAX_CHECK_VALUE_BYTES) {
511-
throw new \UnexpectedValueException($this->l->t('The provided check value is too long'));
512-
}
513-
514519
$instance->validateCheck($check['operator'], $check['value']);
515520
}
516521
}

0 commit comments

Comments
 (0)