diff --git a/apps/admin_audit/lib/Actions/Action.php b/apps/admin_audit/lib/Actions/Action.php index 5d06cf4fbf515..39ea01ef5d907 100644 --- a/apps/admin_audit/lib/Actions/Action.php +++ b/apps/admin_audit/lib/Actions/Action.php @@ -32,18 +32,19 @@ public function log(string $text, if (!isset($params[$element])) { if ($obfuscateParameters) { $this->logger->critical( - '$params["'.$element.'"] was missing.', + '$params["' . $element . '"] was missing.', ['app' => 'admin_audit'] ); } else { $this->logger->critical( sprintf( - '$params["'.$element.'"] was missing. Transferred value: %s', + '$params["' . $element . '"] was missing. Transferred value: %s', print_r($params, true) ), ['app' => 'admin_audit'] ); } + return; } } @@ -53,6 +54,7 @@ public function log(string $text, if ($params[$element] instanceof \DateTime) { $params[$element] = $params[$element]->format('Y-m-d H:i:s'); } + $replaceArray[] = $params[$element]; } diff --git a/apps/admin_audit/lib/Actions/Files.php b/apps/admin_audit/lib/Actions/Files.php index 5c08640d2d2af..40c310ce026ee 100644 --- a/apps/admin_audit/lib/Actions/Files.php +++ b/apps/admin_audit/lib/Actions/Files.php @@ -42,10 +42,12 @@ public function read(BeforeNodeReadEvent $event): void { ]; } catch (InvalidPathException|NotFoundException $e) { \OCP\Server::get(LoggerInterface::class)->error( - 'Exception thrown in file read: '.$e->getMessage(), ['app' => 'admin_audit', 'exception' => $e] + 'Exception thrown in file read: ' . $e->getMessage(), ['app' => 'admin_audit', 'exception' => $e] ); + return; } + $this->log( 'File with id "%s" accessed: "%s"', $params, @@ -64,8 +66,9 @@ public function beforeRename(BeforeNodeRenamedEvent $event): void { $this->renamedNodes[$source->getId()] = $source; } catch (InvalidPathException|NotFoundException $e) { \OCP\Server::get(LoggerInterface::class)->error( - 'Exception thrown in file rename: '.$e->getMessage(), ['app' => 'admin_audit', 'exception' => $e] + 'Exception thrown in file rename: ' . $e->getMessage(), ['app' => 'admin_audit', 'exception' => $e] ); + return; } } @@ -86,8 +89,9 @@ public function afterRename(NodeRenamedEvent $event): void { ]; } catch (InvalidPathException|NotFoundException $e) { \OCP\Server::get(LoggerInterface::class)->error( - 'Exception thrown in file rename: '.$e->getMessage(), ['app' => 'admin_audit', 'exception' => $e] + 'Exception thrown in file rename: ' . $e->getMessage(), ['app' => 'admin_audit', 'exception' => $e] ); + return; } @@ -112,13 +116,16 @@ public function create(NodeCreatedEvent $event): void { ]; } catch (InvalidPathException|NotFoundException $e) { \OCP\Server::get(LoggerInterface::class)->error( - 'Exception thrown in file create: '.$e->getMessage(), ['app' => 'admin_audit', 'exception' => $e] + 'Exception thrown in file create: ' . $e->getMessage(), ['app' => 'admin_audit', 'exception' => $e] ); + return; } + if ($params['path'] === '/' || $params['path'] === '') { return; } + $this->log( 'File with id "%s" created: "%s"', $params, @@ -141,10 +148,12 @@ public function copy(NodeCopiedEvent $event): void { ]; } catch (InvalidPathException|NotFoundException $e) { \OCP\Server::get(LoggerInterface::class)->error( - 'Exception thrown in file copy: '.$e->getMessage(), ['app' => 'admin_audit', 'exception' => $e] + 'Exception thrown in file copy: ' . $e->getMessage(), ['app' => 'admin_audit', 'exception' => $e] ); + return; } + $this->log( 'File id copied from: "%s" to "%s", path from "%s" to "%s"', $params, @@ -166,10 +175,12 @@ public function write(BeforeNodeWrittenEvent $event): void { ]; } catch (InvalidPathException|NotFoundException $e) { \OCP\Server::get(LoggerInterface::class)->error( - 'Exception thrown in file write: '.$e->getMessage(), ['app' => 'admin_audit', 'exception' => $e] + 'Exception thrown in file write: ' . $e->getMessage(), ['app' => 'admin_audit', 'exception' => $e] ); + return; } + if ($params['path'] === '/' || $params['path'] === '') { return; } @@ -194,10 +205,12 @@ public function update(NodeWrittenEvent $event): void { ]; } catch (InvalidPathException|NotFoundException $e) { \OCP\Server::get(LoggerInterface::class)->error( - 'Exception thrown in file update: '.$e->getMessage(), ['app' => 'admin_audit', 'exception' => $e] + 'Exception thrown in file update: ' . $e->getMessage(), ['app' => 'admin_audit', 'exception' => $e] ); + return; } + $this->log( 'File with id "%s" updated: "%s"', $params, @@ -218,10 +231,12 @@ public function delete(NodeDeletedEvent $event): void { ]; } catch (InvalidPathException|NotFoundException $e) { \OCP\Server::get(LoggerInterface::class)->error( - 'Exception thrown in file delete: '.$e->getMessage(), ['app' => 'admin_audit', 'exception' => $e] + 'Exception thrown in file delete: ' . $e->getMessage(), ['app' => 'admin_audit', 'exception' => $e] ); + return; } + $this->log( 'File with id "%s" deleted: "%s"', $params, diff --git a/apps/admin_audit/lib/Listener/FileEventListener.php b/apps/admin_audit/lib/Listener/FileEventListener.php index 74bb2ac836c52..4ebe5e28816cd 100644 --- a/apps/admin_audit/lib/Listener/FileEventListener.php +++ b/apps/admin_audit/lib/Listener/FileEventListener.php @@ -48,8 +48,9 @@ private function beforePreviewFetched(BeforePreviewFetchedEvent $event): void { ); } catch (InvalidPathException|NotFoundException $e) { \OCP\Server::get(LoggerInterface::class)->error( - 'Exception thrown in file preview: '.$e->getMessage(), ['app' => 'admin_audit', 'exception' => $e] + 'Exception thrown in file preview: ' . $e->getMessage(), ['app' => 'admin_audit', 'exception' => $e] ); + return; } } diff --git a/apps/cloud_federation_api/lib/Controller/RequestHandlerController.php b/apps/cloud_federation_api/lib/Controller/RequestHandlerController.php index a49d70b693088..58676b1388580 100644 --- a/apps/cloud_federation_api/lib/Controller/RequestHandlerController.php +++ b/apps/cloud_federation_api/lib/Controller/RequestHandlerController.php @@ -50,7 +50,7 @@ public function __construct( private ICloudFederationProviderManager $cloudFederationProviderManager, private Config $config, private ICloudFederationFactory $factory, - private ICloudIdManager $cloudIdManager + private ICloudIdManager $cloudIdManager, ) { parent::__construct($appName, $request); } @@ -125,6 +125,7 @@ public function addShare($shareWith, $name, $description, $providerId, $owner, $ Http::STATUS_BAD_REQUEST ); $response->throttle(); + return $response; } } @@ -139,6 +140,7 @@ public function addShare($shareWith, $name, $description, $providerId, $owner, $ Http::STATUS_BAD_REQUEST ); $response->throttle(); + return $response; } } @@ -242,6 +244,7 @@ public function receiveNotification($notificationType, $resourceType, $providerI Http::STATUS_BAD_REQUEST ); $response->throttle(); + return $response; } catch (ActionNotSupportedException $e) { return new JSONResponse( @@ -253,6 +256,7 @@ public function receiveNotification($notificationType, $resourceType, $providerI } catch (AuthenticationFailedException $e) { $response = new JSONResponse(['message' => 'RESOURCE_NOT_FOUND'], Http::STATUS_FORBIDDEN); $response->throttle(); + return $response; } catch (\Exception $e) { return new JSONResponse( diff --git a/apps/comments/lib/Activity/Provider.php b/apps/comments/lib/Activity/Provider.php index 4fb0c8d58beb8..822d13c10f0cb 100644 --- a/apps/comments/lib/Activity/Provider.php +++ b/apps/comments/lib/Activity/Provider.php @@ -61,6 +61,7 @@ public function parse($language, IEvent $event, ?IEvent $previousEvent = null): return $this->parseLongVersion($event); } + throw new UnknownActivityException(); } diff --git a/apps/comments/lib/Collaboration/CommentersSorter.php b/apps/comments/lib/Collaboration/CommentersSorter.php index 46fec87c7770a..dc599ad1451ac 100644 --- a/apps/comments/lib/Collaboration/CommentersSorter.php +++ b/apps/comments/lib/Collaboration/CommentersSorter.php @@ -48,6 +48,7 @@ public function sort(array &$sortArray, array $context): void { if ($r === 0) { $r = $a[0] - $b[0]; } + return $r; }); @@ -70,12 +71,14 @@ protected function retrieveCommentsInformation(string $type, string $id): array if (!isset($actors[$comment->getActorType()])) { $actors[$comment->getActorType()] = []; } + if (!isset($actors[$comment->getActorType()][$comment->getActorId()])) { $actors[$comment->getActorType()][$comment->getActorId()] = 1; } else { $actors[$comment->getActorType()][$comment->getActorId()]++; } } + return $actors; } diff --git a/apps/comments/lib/Controller/NotificationsController.php b/apps/comments/lib/Controller/NotificationsController.php index a3228247884ae..2f411c1b9800c 100644 --- a/apps/comments/lib/Controller/NotificationsController.php +++ b/apps/comments/lib/Controller/NotificationsController.php @@ -33,7 +33,7 @@ public function __construct( protected IRootFolder $rootFolder, protected IURLGenerator $urlGenerator, protected IManager $notificationManager, - protected IUserSession $userSession + protected IUserSession $userSession, ) { parent::__construct($appName, $request); } @@ -68,6 +68,7 @@ public function view(string $id): RedirectResponse|NotFoundResponse { if ($comment->getObjectType() !== 'files') { return new NotFoundResponse(); } + $userFolder = $this->rootFolder->getUserFolder($currentUser->getUID()); $files = $userFolder->getById((int)$comment->getObjectId()); diff --git a/apps/comments/lib/Listener/CommentsEventListener.php b/apps/comments/lib/Listener/CommentsEventListener.php index a1e4499516209..db5db1c09b1f8 100644 --- a/apps/comments/lib/Listener/CommentsEventListener.php +++ b/apps/comments/lib/Listener/CommentsEventListener.php @@ -39,6 +39,7 @@ public function handle(Event $event): void { ) { $this->notificationHandler($event); $this->activityHandler($event); + return; } diff --git a/apps/comments/lib/Notification/Listener.php b/apps/comments/lib/Notification/Listener.php index 44fb8fe3f4c4b..19314ef140d13 100644 --- a/apps/comments/lib/Notification/Listener.php +++ b/apps/comments/lib/Notification/Listener.php @@ -16,7 +16,7 @@ class Listener { public function __construct( protected IManager $notificationManager, - protected IUserManager $userManager + protected IUserManager $userManager, ) { } @@ -73,12 +73,14 @@ public function extractMentions(array $mentions): array { if (empty($mentions)) { return []; } + $uids = []; foreach ($mentions as $mention) { if ($mention['type'] === 'user') { $uids[] = $mention['id']; } } + return $uids; } } diff --git a/apps/comments/lib/Notification/Notifier.php b/apps/comments/lib/Notification/Notifier.php index d5563ef7d8557..93e9c8232d1cc 100644 --- a/apps/comments/lib/Notification/Notifier.php +++ b/apps/comments/lib/Notification/Notifier.php @@ -25,7 +25,7 @@ public function __construct( protected IRootFolder $rootFolder, protected ICommentsManager $commentsManager, protected IURLGenerator $url, - protected IUserManager $userManager + protected IUserManager $userManager, ) { } @@ -61,12 +61,14 @@ public function prepare(INotification $notification, string $languageCode): INot if ($notification->getApp() !== 'comments') { throw new UnknownNotificationException(); } + try { $comment = $this->commentsManager->get($notification->getObjectId()); } catch (NotFoundException $e) { // needs to be converted to InvalidArgumentException, otherwise none Notifications will be shown at all throw new UnknownNotificationException('Comment not found', 0, $e); } + $l = $this->l10nFactory->get('comments', $languageCode); $displayName = $comment->getActorId(); $isDeletedActor = $comment->getActorType() === ICommentsManager::DELETED_USER; @@ -83,11 +85,13 @@ public function prepare(INotification $notification, string $languageCode): INot if ($parameters[0] !== 'files') { throw new UnknownNotificationException('Unsupported comment object'); } + $userFolder = $this->rootFolder->getUserFolder($notification->getUser()); $nodes = $userFolder->getById((int)$parameters[1]); if (empty($nodes)) { throw new AlreadyProcessedException(); } + $node = $nodes[0]; $path = rtrim($node->getPath(), '/'); @@ -96,6 +100,7 @@ public function prepare(INotification $notification, string $languageCode): INot $fullPath = $path; [,,, $path] = explode('/', $fullPath, 4); } + $subjectParameters = [ 'file' => [ 'type' => 'file', @@ -116,6 +121,7 @@ public function prepare(INotification $notification, string $languageCode): INot 'name' => $displayName, ]; } + [$message, $messageParameters] = $this->commentToRichMessage($comment); $notification->setRichSubject($subject, $subjectParameters) ->setRichMessage($message, $messageParameters) @@ -145,9 +151,11 @@ public function commentToRichMessage(IComment $comment): array { continue; } } + if (!array_key_exists($mention['type'], $mentionTypeCount)) { $mentionTypeCount[$mention['type']] = 0; } + $mentionTypeCount[$mention['type']]++; // To keep a limited character set in parameter IDs ([a-zA-Z0-9-]) // the mention parameter ID does not include the mention ID (which @@ -166,12 +174,14 @@ public function commentToRichMessage(IComment $comment): array { // type, so the client decides what to display. $displayName = ''; } + $messageParameters[$mentionParameterId] = [ 'type' => $mention['type'], 'id' => $mention['id'], 'name' => $displayName ]; } + return [$message, $messageParameters]; } } diff --git a/apps/comments/lib/Search/CommentsSearchProvider.php b/apps/comments/lib/Search/CommentsSearchProvider.php index 87a658cab1c6c..5ecbc011cebee 100644 --- a/apps/comments/lib/Search/CommentsSearchProvider.php +++ b/apps/comments/lib/Search/CommentsSearchProvider.php @@ -41,6 +41,7 @@ public function getOrder(string $route, array $routeParameters): int { // Files first return 0; } + return 10; } @@ -54,6 +55,7 @@ public function search(IUser $user, ISearchQuery $query): SearchResult { $avatarUrl = $isUser ? $this->urlGenerator->linkToRouteAbsolute('core.avatar.getAvatar', ['userId' => $result->authorId, 'size' => 42]) : $this->urlGenerator->linkToRouteAbsolute('core.GuestAvatar.getAvatar', ['guestName' => $result->authorId, 'size' => 42]); + return new SearchResultEntry( $avatarUrl, $result->name, diff --git a/apps/comments/lib/Search/LegacyProvider.php b/apps/comments/lib/Search/LegacyProvider.php index d78e5fe09f32a..2225e62287888 100644 --- a/apps/comments/lib/Search/LegacyProvider.php +++ b/apps/comments/lib/Search/LegacyProvider.php @@ -33,6 +33,7 @@ public function search($query): array { if (!$user instanceof IUser) { return []; } + $uf = \OC::$server->getUserFolder($user->getUID()); if ($uf === null) { diff --git a/apps/contactsinteraction/lib/AddressBook.php b/apps/contactsinteraction/lib/AddressBook.php index 60c9eccece38c..c6b43e09ffe87 100644 --- a/apps/contactsinteraction/lib/AddressBook.php +++ b/apps/contactsinteraction/lib/AddressBook.php @@ -94,6 +94,7 @@ public function childExists($name): bool { $this->getUid(), (int)$name ); + return true; } catch (DoesNotExistException $e) { return false; diff --git a/apps/contactsinteraction/lib/Db/CardSearchDao.php b/apps/contactsinteraction/lib/Db/CardSearchDao.php index 09fa4711adb29..6b8a20397e5c5 100644 --- a/apps/contactsinteraction/lib/Db/CardSearchDao.php +++ b/apps/contactsinteraction/lib/Db/CardSearchDao.php @@ -36,18 +36,21 @@ public function findExisting(IUser $user, $propQuery->expr()->eq('value', $cardQuery->createNamedParameter($uid)) ); } + if ($email !== null) { $additionalWheres[] = $propQuery->expr()->andX( $propQuery->expr()->eq('name', $cardQuery->createNamedParameter('EMAIL')), $propQuery->expr()->eq('value', $cardQuery->createNamedParameter($email)) ); } + if ($cloudId !== null) { $additionalWheres[] = $propQuery->expr()->andX( $propQuery->expr()->eq('name', $cardQuery->createNamedParameter('CLOUD')), $propQuery->expr()->eq('value', $cardQuery->createNamedParameter($cloudId)) ); } + $addressbooksQuery->selectDistinct('id') ->from('addressbooks') ->where($addressbooksQuery->expr()->eq('principaluri', $cardQuery->createNamedParameter('principals/users/' . $user->getUID()))); @@ -72,6 +75,7 @@ public function findExisting(IUser $user, if ($card === false) { return null; } + if (is_resource($card)) { return stream_get_contents($card); } diff --git a/apps/contactsinteraction/lib/Db/RecentContactMapper.php b/apps/contactsinteraction/lib/Db/RecentContactMapper.php index c835b5287c87f..9b3cdcbaa4ba8 100644 --- a/apps/contactsinteraction/lib/Db/RecentContactMapper.php +++ b/apps/contactsinteraction/lib/Db/RecentContactMapper.php @@ -65,9 +65,11 @@ public function findMatch(IUser $user, if ($uid !== null) { $additionalWheres[] = $qb->expr()->eq('uid', $qb->createNamedParameter($uid)); } + if ($email !== null) { $additionalWheres[] = $qb->expr()->eq('email', $qb->createNamedParameter($email)); } + if ($cloudId !== null) { $additionalWheres[] = $qb->expr()->eq('federated_cloud_id', $qb->createNamedParameter($cloudId)); } @@ -80,6 +82,7 @@ public function findMatch(IUser $user, if (!empty($additionalWheres)) { $select->andWhere($select->expr()->orX(...$additionalWheres)); } + return $this->findEntities($select); } diff --git a/apps/contactsinteraction/lib/Listeners/ContactInteractionListener.php b/apps/contactsinteraction/lib/Listeners/ContactInteractionListener.php index 78b366f015e35..f6e5fa944149a 100644 --- a/apps/contactsinteraction/lib/Listeners/ContactInteractionListener.php +++ b/apps/contactsinteraction/lib/Listeners/ContactInteractionListener.php @@ -89,12 +89,15 @@ public function handle(Event $event): void { if ($uid !== null) { $contact->setUid($uid); } + if ($email !== null) { $contact->setEmail($email); } + if ($federatedCloudId !== null) { $contact->setFederatedCloudId($federatedCloudId); } + $contact->setLastContact($this->timeFactory->getTime()); $contact->setCard($this->generateCard($contact)); @@ -106,6 +109,7 @@ private function getDisplayName(?string $uid): ?string { if ($uid === null) { return null; } + if (($user = $this->userManager->get($uid)) === null) { return null; } @@ -123,6 +127,7 @@ private function generateCard(RecentContact $contact): string { if ($contact->getEmail() !== null) { $props['EMAIL'] = $contact->getEmail(); } + if ($contact->getFederatedCloudId() !== null) { $props['CLOUD'] = $contact->getFederatedCloudId(); } diff --git a/apps/dashboard/lib/Controller/DashboardApiController.php b/apps/dashboard/lib/Controller/DashboardApiController.php index c3d91fd9d3457..116d61aa1c378 100644 --- a/apps/dashboard/lib/Controller/DashboardApiController.php +++ b/apps/dashboard/lib/Controller/DashboardApiController.php @@ -161,15 +161,19 @@ public function getWidgets(): DataResponse { }, $widget->getWidgetButtons($this->userId)), ]; } + if ($widget instanceof IReloadableWidget) { $data['reload_interval'] = $widget->getReloadInterval(); } + if ($widget instanceof IAPIWidget) { $data['item_api_versions'][] = 1; } + if ($widget instanceof IAPIWidgetV2) { $data['item_api_versions'][] = 2; } + return $data; }, $widgets); diff --git a/apps/dav/appinfo/v1/caldav.php b/apps/dav/appinfo/v1/caldav.php index bfa4d6184d51f..ec5e78cd84982 100644 --- a/apps/dav/appinfo/v1/caldav.php +++ b/apps/dav/appinfo/v1/caldav.php @@ -98,6 +98,7 @@ if ($sendInvitations) { $server->addPlugin(\OC::$server->query(\OCA\DAV\CalDAV\Schedule\IMipPlugin::class)); } + $server->addPlugin(new ExceptionLoggerPlugin('caldav', $logger)); $server->addPlugin(\OCP\Server::get(RateLimitingPlugin::class)); $server->addPlugin(\OCP\Server::get(CalDavValidatePlugin::class)); diff --git a/apps/dav/appinfo/v1/publicwebdav.php b/apps/dav/appinfo/v1/publicwebdav.php index 3875337415004..7d3339badd429 100644 --- a/apps/dav/appinfo/v1/publicwebdav.php +++ b/apps/dav/appinfo/v1/publicwebdav.php @@ -78,6 +78,7 @@ if (!$node) { throw new \Sabre\DAV\Exception\NotFound(); } + $linkCheckPlugin->setFileInfo($node); // If not readable (files_drop) enable the filesdrop plugin diff --git a/apps/dav/appinfo/v1/webdav.php b/apps/dav/appinfo/v1/webdav.php index 1683c29ca80f5..fab5fdd861340 100644 --- a/apps/dav/appinfo/v1/webdav.php +++ b/apps/dav/appinfo/v1/webdav.php @@ -11,6 +11,7 @@ if (!str_contains(@ini_get('disable_functions'), 'set_time_limit')) { @set_time_limit(0); } + ignore_user_abort(true); // Turn off output buffering to prevent memory problems diff --git a/apps/dav/appinfo/v2/direct.php b/apps/dav/appinfo/v2/direct.php index 46443a60f8a87..ebc83ed789301 100644 --- a/apps/dav/appinfo/v2/direct.php +++ b/apps/dav/appinfo/v2/direct.php @@ -12,6 +12,7 @@ if (!str_contains(@ini_get('disable_functions'), 'set_time_limit')) { @set_time_limit(0); } + ignore_user_abort(true); // Turn off output buffering to prevent memory problems diff --git a/apps/dav/appinfo/v2/publicremote.php b/apps/dav/appinfo/v2/publicremote.php index 53e85d556eb9f..d660161f33c16 100644 --- a/apps/dav/appinfo/v2/publicremote.php +++ b/apps/dav/appinfo/v2/publicremote.php @@ -114,6 +114,7 @@ if (!$node) { throw new NotFound(); } + $linkCheckPlugin->setFileInfo($node); // If not readable (files_drop) enable the filesdrop plugin diff --git a/apps/dav/appinfo/v2/remote.php b/apps/dav/appinfo/v2/remote.php index 73031c0794fd0..c6c752f8c9b3c 100644 --- a/apps/dav/appinfo/v2/remote.php +++ b/apps/dav/appinfo/v2/remote.php @@ -9,6 +9,7 @@ if (!str_contains(@ini_get('disable_functions'), 'set_time_limit')) { @set_time_limit(0); } + ignore_user_abort(true); // Turn off output buffering to prevent memory problems diff --git a/apps/dav/lib/AppInfo/PluginManager.php b/apps/dav/lib/AppInfo/PluginManager.php index 43fd16ebeb69a..efa9ecda9e72e 100644 --- a/apps/dav/lib/AppInfo/PluginManager.php +++ b/apps/dav/lib/AppInfo/PluginManager.php @@ -124,6 +124,7 @@ private function populate(): void { if ($this->populated) { return; } + $this->populated = true; $this->calendarPlugins[] = $this->container->get(AppCalendarPlugin::class); @@ -134,6 +135,7 @@ private function populate(): void { if (!isset($info['types']) || !in_array('dav', $info['types'], true)) { continue; } + $plugins = $this->loadSabrePluginsFromInfoXml($this->extractPluginList($info)); foreach ($plugins as $plugin) { $this->plugins[] = $plugin; @@ -168,10 +170,12 @@ private function extractPluginList(array $array): array { if (!is_array($items)) { $items = [$items]; } + return $items; } } } + return []; } @@ -187,10 +191,12 @@ private function extractCollectionList(array $array): array { if (!is_array($items)) { $items = [$items]; } + return $items; } } } + return []; } @@ -202,9 +208,11 @@ private function extractAddressBookPluginList(array $array): array { if (!isset($array['sabre']) || !is_array($array['sabre'])) { return []; } + if (!isset($array['sabre']['address-book-plugins']) || !is_array($array['sabre']['address-book-plugins'])) { return []; } + if (!isset($array['sabre']['address-book-plugins']['plugin'])) { return []; } @@ -213,6 +221,7 @@ private function extractAddressBookPluginList(array $array): array { if (!is_array($items)) { $items = [$items]; } + return $items; } @@ -228,10 +237,12 @@ private function extractCalendarPluginList(array $array): array { if (!is_array($items)) { $items = [$items]; } + return $items; } } } + return []; } @@ -259,6 +270,7 @@ private function loadSabrePluginsFromInfoXml(array $classes): array { if (!($instance instanceof ServerPlugin)) { throw new \Exception('Sabre server plugin ' . $className . ' does not implement the ' . ServerPlugin::class . ' interface'); } + return $instance; }, $classes); } @@ -273,6 +285,7 @@ private function loadSabreCollectionsFromInfoXml(array $classes): array { if (!($instance instanceof Collection)) { throw new \Exception('Sabre collection plugin ' . $className . ' does not implement the ' . Collection::class . ' interface'); } + return $instance; }, $classes); } @@ -287,6 +300,7 @@ private function loadSabreAddressBookPluginsFromInfoXml(array $classes): array { if (!($instance instanceof IAddressBookProvider)) { throw new \Exception('Sabre address book plugin class ' . $className . ' does not implement the ' . IAddressBookProvider::class . ' interface'); } + return $instance; }, $classes); } @@ -301,6 +315,7 @@ private function loadSabreCalendarPluginsFromInfoXml(array $classes): array { if (!($instance instanceof ICalendarProvider)) { throw new \Exception('Sabre calendar plugin class ' . $className . ' does not implement the ' . ICalendarProvider::class . ' interface'); } + return $instance; }, $classes); } diff --git a/apps/dav/lib/Avatars/AvatarHome.php b/apps/dav/lib/Avatars/AvatarHome.php index 1f86941e592c6..c6469f4594bf6 100644 --- a/apps/dav/lib/Avatars/AvatarHome.php +++ b/apps/dav/lib/Avatars/AvatarHome.php @@ -47,13 +47,16 @@ public function getChild($name) { if (!in_array($ext, ['jpeg', 'png'], true)) { throw new MethodNotAllowed('File format not allowed'); } + if ($size <= 0 || $size > 1024) { throw new MethodNotAllowed('Invalid image size'); } + $avatar = $this->avatarManager->getAvatar($this->getName()); if (!$avatar->exists()) { throw new NotFound(); } + return new AvatarNode($size, $ext, $avatar); } diff --git a/apps/dav/lib/Avatars/AvatarNode.php b/apps/dav/lib/Avatars/AvatarNode.php index 3931ce3ff7192..2fe8fc84afea1 100644 --- a/apps/dav/lib/Avatars/AvatarNode.php +++ b/apps/dav/lib/Avatars/AvatarNode.php @@ -64,6 +64,7 @@ public function getContentType() { if ($this->ext === 'png') { return 'image/png'; } + return 'image/jpeg'; } @@ -76,6 +77,7 @@ public function getLastModified() { if (!empty($timestamp)) { return (int)$timestamp; } + return $timestamp; } } diff --git a/apps/dav/lib/BackgroundJob/BuildReminderIndexBackgroundJob.php b/apps/dav/lib/BackgroundJob/BuildReminderIndexBackgroundJob.php index c214f8950cb88..3823187a85c84 100644 --- a/apps/dav/lib/BackgroundJob/BuildReminderIndexBackgroundJob.php +++ b/apps/dav/lib/BackgroundJob/BuildReminderIndexBackgroundJob.php @@ -56,7 +56,7 @@ public function run($argument) { $offset = (int)$argument['offset']; $stopAt = (int)$argument['stopAt']; - $this->logger->info('Building calendar reminder index (' . $offset .'/' . $stopAt . ')'); + $this->logger->info('Building calendar reminder index (' . $offset . '/' . $stopAt . ')'); $offset = $this->buildIndex($offset, $stopAt); @@ -92,6 +92,7 @@ private function buildIndex(int $offset, int $stopAt):int { if (is_resource($row['calendardata'])) { $row['calendardata'] = stream_get_contents($row['calendardata']); } + $row['component'] = $row['componenttype']; try { @@ -107,6 +108,7 @@ private function buildIndex(int $offset, int $stopAt):int { } $result->closeCursor(); + return $stopAt; } } diff --git a/apps/dav/lib/BackgroundJob/OutOfOfficeEventDispatcherJob.php b/apps/dav/lib/BackgroundJob/OutOfOfficeEventDispatcherJob.php index cc4fd5dce9dea..53d9dc275b3f4 100644 --- a/apps/dav/lib/BackgroundJob/OutOfOfficeEventDispatcherJob.php +++ b/apps/dav/lib/BackgroundJob/OutOfOfficeEventDispatcherJob.php @@ -46,6 +46,7 @@ public function run($argument): void { 'exception' => $e, 'argument' => $argument, ]); + return; } @@ -55,6 +56,7 @@ public function run($argument): void { $this->logger->error("Failed to dispatch out-of-office event: User $userId does not exist", [ 'argument' => $argument, ]); + return; } diff --git a/apps/dav/lib/BackgroundJob/RefreshWebcalJob.php b/apps/dav/lib/BackgroundJob/RefreshWebcalJob.php index f9d6de9db59f2..30bc11882a5cc 100644 --- a/apps/dav/lib/BackgroundJob/RefreshWebcalJob.php +++ b/apps/dav/lib/BackgroundJob/RefreshWebcalJob.php @@ -55,6 +55,7 @@ public function start(IJobList $jobList): void { "Subscription $subscriptionId could not be refreshed, refreshrate in database is invalid", ['exception' => $ex] ); + return; } diff --git a/apps/dav/lib/BackgroundJob/UploadCleanup.php b/apps/dav/lib/BackgroundJob/UploadCleanup.php index 6d5eca6920c48..ff3caf77de256 100644 --- a/apps/dav/lib/BackgroundJob/UploadCleanup.php +++ b/apps/dav/lib/BackgroundJob/UploadCleanup.php @@ -58,7 +58,9 @@ protected function run($argument) { if ($uploadFolder->getMTime() < $time) { $uploadFolder->delete(); } + $this->jobList->remove(self::class, $argument); + return; } diff --git a/apps/dav/lib/BackgroundJob/UserStatusAutomation.php b/apps/dav/lib/BackgroundJob/UserStatusAutomation.php index f9c4d8dcd74be..8251b880170f7 100644 --- a/apps/dav/lib/BackgroundJob/UserStatusAutomation.php +++ b/apps/dav/lib/BackgroundJob/UserStatusAutomation.php @@ -28,14 +28,16 @@ use Sabre\VObject\Recur\RRuleIterator; class UserStatusAutomation extends TimedJob { - public function __construct(private ITimeFactory $timeFactory, + public function __construct( + private ITimeFactory $timeFactory, private IDBConnection $connection, private IJobList $jobList, private LoggerInterface $logger, private IManager $manager, private IConfig $config, private IAvailabilityCoordinator $coordinator, - private IUserManager $userManager) { + private IUserManager $userManager, + ) { parent::__construct($timeFactory); // Interval 0 might look weird, but the last_checked is always moved @@ -50,6 +52,7 @@ protected function run($argument) { if (!isset($argument['userId'])) { $this->jobList->remove(self::class, $argument); $this->logger->info('Removing invalid ' . self::class . ' background job'); + return; } @@ -75,6 +78,7 @@ protected function run($argument) { $this->jobList->remove(self::class, $argument); $this->manager->revertUserStatus($user->getUID(), IUserStatus::MESSAGE_AVAILABILITY, IUserStatus::DND); $this->manager->revertUserStatus($user->getUID(), IUserStatus::MESSAGE_OUT_OF_OFFICE, IUserStatus::DND); + return; } @@ -133,6 +137,7 @@ private function processAvailability(string $property, string $userId, bool $has if ($component->name !== 'VAVAILABILITY') { continue; } + /** @var VAvailability $component */ $availables = $component->getComponents(); foreach ($availables as $available) { @@ -183,6 +188,7 @@ private function processAvailability(string $property, string $userId, bool $has $this->logger->info('Removing ' . self::class . ' background job for user "' . $userId . '" because the user has no valid availability rules set'); $this->jobList->remove(self::class, ['userId' => $userId]); $this->manager->revertUserStatus($userId, IUserStatus::MESSAGE_AVAILABILITY, IUserStatus::DND); + return; } @@ -193,6 +199,7 @@ private function processAvailability(string $property, string $userId, bool $has $this->logger->debug('User is currently available, reverting DND status if applicable'); $this->manager->revertUserStatus($userId, IUserStatus::MESSAGE_AVAILABILITY, IUserStatus::DND); $this->logger->debug('User status automation ran'); + return; } @@ -226,6 +233,7 @@ private function processOutOfOfficeData(IUser $user, ?IOutOfOfficeData $ooo): bo // if this is the case here $this->setLastRunToNextToggleTime($user->getUID(), $ooo->getStartDate()); } + return true; } @@ -235,6 +243,7 @@ private function processOutOfOfficeData(IUser $user, ?IOutOfOfficeData $ooo): bo // Run at the end of an ooo period to return to availability / regular user status // If it's overwritten by a custom status in the meantime, there's nothing we can do about it $this->setLastRunToNextToggleTime($user->getUID(), $ooo->getEndDate()); + return false; } } diff --git a/apps/dav/lib/BulkUpload/BulkUploadPlugin.php b/apps/dav/lib/BulkUpload/BulkUploadPlugin.php index 8a99beaec6a9a..1aaa1036ef1e7 100644 --- a/apps/dav/lib/BulkUpload/BulkUploadPlugin.php +++ b/apps/dav/lib/BulkUpload/BulkUploadPlugin.php @@ -22,7 +22,7 @@ class BulkUploadPlugin extends ServerPlugin { public function __construct( Folder $userFolder, - LoggerInterface $logger + LoggerInterface $logger, ) { $this->userFolder = $userFolder; $this->logger = $logger; @@ -59,6 +59,7 @@ public function httpPost(RequestInterface $request, ResponseInterface $response) $this->logger->error($e->getMessage()); $response->setStatus(Http::STATUS_BAD_REQUEST); $response->setBody(json_encode($writtenFiles, JSON_THROW_ON_ERROR)); + return false; } diff --git a/apps/dav/lib/BulkUpload/MultipartRequestParser.php b/apps/dav/lib/BulkUpload/MultipartRequestParser.php index 97aa0597b34ea..2efb3dd99155f 100644 --- a/apps/dav/lib/BulkUpload/MultipartRequestParser.php +++ b/apps/dav/lib/BulkUpload/MultipartRequestParser.php @@ -45,8 +45,8 @@ public function __construct( $this->stream = $stream; $boundary = $this->parseBoundaryFromHeaders($contentType); - $this->boundary = '--'.$boundary."\r\n"; - $this->lastBoundary = '--'.$boundary."--\r\n"; + $this->boundary = '--' . $boundary . "\r\n"; + $this->lastBoundary = '--' . $boundary . "--\r\n"; } /** @@ -231,6 +231,7 @@ private function computeMd5Hash(int $length): string { $context = hash_init('md5'); hash_update_stream($context, $this->stream, $length); fseek($this->stream, -$length, SEEK_CUR); + return hash_final($context); } } diff --git a/apps/dav/lib/CalDAV/Activity/Backend.php b/apps/dav/lib/CalDAV/Activity/Backend.php index 56b481097cf8b..7b531dc89bb4f 100644 --- a/apps/dav/lib/CalDAV/Activity/Backend.php +++ b/apps/dav/lib/CalDAV/Activity/Backend.php @@ -202,6 +202,7 @@ public function onCalendarUpdateShares(array $calendarData, array $shares, array if ($parts[0] !== 'principal') { continue; } + $principal = explode('/', $parts[1]); if ($principal[1] === 'users') { @@ -279,6 +280,7 @@ public function onCalendarUpdateShares(array $calendarData, array $shares, array if ($parts[0] !== 'principal') { continue; } + $principal = explode('/', $parts[1]); if ($principal[1] === 'users') { @@ -477,7 +479,6 @@ public function onTouchCalendarObject($action, array $calendarData, array $share $params['object']['link']['owner'] = $owner; } - $event->setAffectedUser($user) ->setSubject( $user === $currentUser ? $action . '_self' : $action, @@ -515,6 +516,7 @@ public function onMovedCalendarObject(array $sourceCalendarData, array $targetCa $targetShares, $objectData ); + return; } @@ -605,6 +607,7 @@ protected function getObjectNameAndType(array $objectData) { if ($componentType === 'VEVENT') { return ['id' => (string)$component->UID, 'name' => (string)$component->SUMMARY, 'type' => 'event']; } + return ['id' => (string)$component->UID, 'name' => (string)$component->SUMMARY, 'type' => 'todo', 'status' => (string)$component->STATUS]; } diff --git a/apps/dav/lib/CalDAV/Activity/Provider/Base.php b/apps/dav/lib/CalDAV/Activity/Provider/Base.php index a063a31d0154b..91a721c0f5f19 100644 --- a/apps/dav/lib/CalDAV/Activity/Provider/Base.php +++ b/apps/dav/lib/CalDAV/Activity/Provider/Base.php @@ -110,6 +110,7 @@ protected function getGroupDisplayName($gid) { if ($group instanceof IGroup) { return $group->getDisplayName(); } + return $gid; } } diff --git a/apps/dav/lib/CalDAV/Activity/Provider/Event.php b/apps/dav/lib/CalDAV/Activity/Provider/Event.php index 26bf69aecdd78..351e35ef75cc4 100644 --- a/apps/dav/lib/CalDAV/Activity/Provider/Event.php +++ b/apps/dav/lib/CalDAV/Activity/Provider/Event.php @@ -87,6 +87,7 @@ protected function generateObjectParameter(array $eventData, string $affectedUse // as seen from the affected user. $objectId = base64_encode($this->url->getWebroot() . '/remote.php/dav/calendars/' . $affectedUser . '/' . $calendarUri . '_shared_by_' . $linkData['owner'] . '/' . $linkData['object_uri']); } + $link = [ 'view' => 'dayGridMonth', 'timeRange' => 'now', @@ -99,6 +100,7 @@ protected function generateObjectParameter(array $eventData, string $affectedUse // Do nothing } } + return $params; } @@ -241,6 +243,7 @@ private function generateClassifiedObjectParameter(array $eventData, string $aff if (!empty($eventData['classified'])) { $parameter['name'] = $this->l->t('Busy'); } + return $parameter; } diff --git a/apps/dav/lib/CalDAV/AppCalendar/AppCalendar.php b/apps/dav/lib/CalDAV/AppCalendar/AppCalendar.php index 2d9beff9cedd2..71c8470647f09 100644 --- a/apps/dav/lib/CalDAV/AppCalendar/AppCalendar.php +++ b/apps/dav/lib/CalDAV/AppCalendar/AppCalendar.php @@ -42,6 +42,7 @@ public function getPermissions(): int { if ($this->calendar instanceof ICreateFromString) { return $this->calendar->getPermissions(); } + return Constants::PERMISSION_READ; } @@ -73,6 +74,7 @@ public function getACL(): array { 'protected' => true, ]; } + return $acl; } @@ -100,7 +102,9 @@ public function createFile($name, $data = null) { if (is_resource($data)) { $data = stream_get_contents($data) ?: null; } + $this->calendar->createFromString($name, is_null($data) ? '' : $data); + return null; } else { throw new Forbidden('Creating a new entry is not allowed'); @@ -178,6 +182,7 @@ public function getChildren(): array { if (!isset($result[$uid])) { $result[$uid] = []; } + $result[$uid][] = $object; } diff --git a/apps/dav/lib/CalDAV/AppCalendar/CalendarObject.php b/apps/dav/lib/CalDAV/AppCalendar/CalendarObject.php index ba3f7074faf51..c4606e4fc0f50 100644 --- a/apps/dav/lib/CalDAV/AppCalendar/CalendarObject.php +++ b/apps/dav/lib/CalDAV/AppCalendar/CalendarObject.php @@ -53,6 +53,7 @@ public function getACL(): array { 'protected' => true, ]; } + return $acl; } @@ -69,6 +70,7 @@ public function put($data): void { if (is_resource($data)) { $data = stream_get_contents($data) ?: ''; } + $this->backend->createFromString($this->getName(), $data); } else { throw new Forbidden('This calendar-object is read-only'); @@ -102,6 +104,7 @@ public function delete(): void { $components[$key]->METHOD = 'CANCEL'; } } + $this->backend->createFromString($this->getName(), (new VCalendar($components))->serialize()); } else { throw new Forbidden('This calendar-object is read-only'); @@ -115,9 +118,11 @@ public function getName(): string { if ($base === null) { throw new NotFound('Invalid node'); } + if (isset($base->{'X-FILENAME'})) { return (string)$base->{'X-FILENAME'}; } + return (string)$base->UID . '.ics'; } @@ -130,8 +135,10 @@ public function getLastModified(): ?int { if ($base !== null && $this->vobject->getBaseComponent()->{'LAST-MODIFIED'}) { /** @var DateTime */ $lastModified = $this->vobject->getBaseComponent()->{'LAST-MODIFIED'}; + return $lastModified->getDateTime()->getTimestamp(); } + return null; } } diff --git a/apps/dav/lib/CalDAV/BirthdayCalendar/EnablePlugin.php b/apps/dav/lib/CalDAV/BirthdayCalendar/EnablePlugin.php index de5b1139880e0..933679436d45f 100644 --- a/apps/dav/lib/CalDAV/BirthdayCalendar/EnablePlugin.php +++ b/apps/dav/lib/CalDAV/BirthdayCalendar/EnablePlugin.php @@ -110,7 +110,7 @@ public function httpPost(RequestInterface $request, ResponseInterface $response) $requestBody = $request->getBodyAsString(); $this->server->xml->parse($requestBody, $request->getUrl(), $documentType); - if ($documentType !== '{'.self::NS_Nextcloud.'}enable-birthday-calendar') { + if ($documentType !== '{' . self::NS_Nextcloud . '}enable-birthday-calendar') { return; } diff --git a/apps/dav/lib/CalDAV/BirthdayService.php b/apps/dav/lib/CalDAV/BirthdayService.php index a10d73c4aa005..17568ee48b18e 100644 --- a/apps/dav/lib/CalDAV/BirthdayService.php +++ b/apps/dav/lib/CalDAV/BirthdayService.php @@ -68,6 +68,7 @@ public function onCardChanged(int $addressBookId, if ($book === null) { return; } + $targetPrincipals[] = $book['principaluri']; $datesToSync = [ ['postfix' => '', 'field' => 'BDAY'], @@ -86,6 +87,7 @@ public function onCardChanged(int $addressBookId, if ($calendar === null) { return; } + foreach ($datesToSync as $type) { $this->updateCalendar($cardUri, $cardData, $book, (int)$calendar['id'], $type, $reminderOffset); } @@ -108,7 +110,7 @@ public function onCardDeleted(int $addressBookId, $calendar = $this->ensureCalendarExists($principalUri); foreach (['', '-death', '-anniversary'] as $tag) { - $objectUri = $book['uri'] . '-' . $cardUri . $tag .'.ics'; + $objectUri = $book['uri'] . '-' . $cardUri . $tag . '.ics'; $this->calDavBackEnd->deleteCalendarObject($calendar['id'], $objectUri, CalDavBackend::CALENDAR_TYPE_CALENDAR, true); } } @@ -122,6 +124,7 @@ public function ensureCalendarExists(string $principal): ?array { if (!is_null($calendar)) { return $calendar; } + $this->calDavBackEnd->createCalendar($principal, self::BIRTHDAY_CALENDAR_URI, [ '{DAV:}displayname' => $this->l10n->t('Contact birthdays'), '{http://apple.com/ns/ical/}calendar-color' => '#E9D859', @@ -146,6 +149,7 @@ public function buildDateFromContact(string $cardData, if (empty($cardData)) { return null; } + try { $doc = Reader::read($cardData); // We're always converting to vCard 4.0 so we can rely on the @@ -153,6 +157,7 @@ public function buildDateFromContact(string $cardData, if (!$doc instanceof VCard) { return null; } + $doc = $doc->convert(Document::VCARD40); } catch (Exception $e) { return null; @@ -165,13 +170,16 @@ public function buildDateFromContact(string $cardData, if (!isset($doc->{$dateField})) { return null; } + if (!isset($doc->FN)) { return null; } + $birthday = $doc->{$dateField}; if (!(string)$birthday) { return null; } + // Skip if the BDAY property is not of the right type. if (!$birthday instanceof DateAndOrTime) { return null; @@ -183,6 +191,7 @@ public function buildDateFromContact(string $cardData, } catch (InvalidDataException $e) { return null; } + if ($dateParts['year'] !== null) { $parameters = $birthday->parameters(); $omitYear = (isset($parameters['X-APPLE-OMIT-YEAR']) @@ -242,6 +251,7 @@ public function buildDateFromContact(string $cardData, * is also set */ $vEvent->{'RRULE'} = 'FREQ=YEARLY;BYMONTH=2;BYMONTHDAY=-1'; } + $vEvent->{'SUMMARY'} = $summary; $vEvent->{'TRANSP'} = 'TRANSPARENT'; $vEvent->{'X-NEXTCLOUD-BC-FIELD-TYPE'} = $dateField; @@ -249,6 +259,7 @@ public function buildDateFromContact(string $cardData, if ($originalYear !== null) { $vEvent->{'X-NEXTCLOUD-BC-YEAR'} = (string)$originalYear; } + if ($reminderOffset) { $alarm = $vCal->createComponent('VALARM'); $alarm->add($vCal->createProperty('TRIGGER', $reminderOffset, ['VALUE' => 'DURATION'])); @@ -256,7 +267,9 @@ public function buildDateFromContact(string $cardData, $alarm->add($vCal->createProperty('DESCRIPTION', $vEvent->{'SUMMARY'})); $vEvent->add($alarm); } + $vCal->add($vEvent); + return $vCal; } @@ -264,11 +277,12 @@ public function buildDateFromContact(string $cardData, * @param string $user */ public function resetForUser(string $user):void { - $principal = 'principals/users/'.$user; + $principal = 'principals/users/' . $user; $calendar = $this->calDavBackEnd->getCalendarByUri($principal, self::BIRTHDAY_CALENDAR_URI); if (!$calendar) { return; // The user's birthday calendar doesn't exist, no need to purge it } + $calendarObjects = $this->calDavBackEnd->getCalendarObjects($calendar['id'], CalDavBackend::CALENDAR_TYPE_CALENDAR); foreach ($calendarObjects as $calendarObject) { @@ -281,7 +295,7 @@ public function resetForUser(string $user):void { * @throws \Sabre\DAV\Exception\BadRequest */ public function syncUser(string $user):void { - $principal = 'principals/users/'.$user; + $principal = 'principals/users/' . $user; $this->ensureCalendarExists($principal); $books = $this->cardDavBackEnd->getAddressBooksForUser($principal); foreach ($books as $book) { @@ -328,6 +342,7 @@ protected function getAllAffectedPrincipals(int $addressBookId) { $targetPrincipals[] = $share['{http://owncloud.org/ns}principal']; } } + return array_values(array_unique($targetPrincipals, SORT_STRING)); } @@ -398,6 +413,7 @@ private function principalToUserId(string $userPrincipal):?string { if (str_starts_with($userPrincipal, 'principals/users/')) { return substr($userPrincipal, 17); } + return null; } diff --git a/apps/dav/lib/CalDAV/CachedSubscription.php b/apps/dav/lib/CalDAV/CachedSubscription.php index 75ee5cb440fd8..9898a972489c8 100644 --- a/apps/dav/lib/CalDAV/CachedSubscription.php +++ b/apps/dav/lib/CalDAV/CachedSubscription.php @@ -93,6 +93,7 @@ public function getOwner() { if (isset($this->calendarInfo['{http://owncloud.org/ns}owner-principal'])) { return $this->calendarInfo['{http://owncloud.org/ns}owner-principal']; } + return parent::getOwner(); } @@ -120,6 +121,7 @@ public function getChild($name) { } $obj['acl'] = $this->getChildACL(); + return new CachedSubscriptionObject($this->caldavBackend, $this->calendarInfo, $obj); } diff --git a/apps/dav/lib/CalDAV/CachedSubscriptionImpl.php b/apps/dav/lib/CalDAV/CachedSubscriptionImpl.php index 17e6f4389cc59..1301fdb600091 100644 --- a/apps/dav/lib/CalDAV/CachedSubscriptionImpl.php +++ b/apps/dav/lib/CalDAV/CachedSubscriptionImpl.php @@ -20,7 +20,7 @@ class CachedSubscriptionImpl implements ICalendar { public function __construct( CachedSubscription $calendar, array $calendarInfo, - CalDavBackend $backend + CalDavBackend $backend, ) { $this->calendar = $calendar; $this->calendarInfo = $calendarInfo; diff --git a/apps/dav/lib/CalDAV/CachedSubscriptionProvider.php b/apps/dav/lib/CalDAV/CachedSubscriptionProvider.php index 73fde0b71a066..3e71e4b0bfe62 100644 --- a/apps/dav/lib/CalDAV/CachedSubscriptionProvider.php +++ b/apps/dav/lib/CalDAV/CachedSubscriptionProvider.php @@ -13,7 +13,7 @@ class CachedSubscriptionProvider implements ICalendarProvider { public function __construct( - private CalDavBackend $calDavBackend + private CalDavBackend $calDavBackend, ) { } @@ -35,6 +35,7 @@ public function getCalendars(string $principalUri, array $calendarUris = []): ar $this->calDavBackend, ); } + return $iCalendars; } } diff --git a/apps/dav/lib/CalDAV/CalDavBackend.php b/apps/dav/lib/CalDAV/CalDavBackend.php index 8b619712b8253..00e817309a522 100644 --- a/apps/dav/lib/CalDAV/CalDavBackend.php +++ b/apps/dav/lib/CalDAV/CalDavBackend.php @@ -222,6 +222,7 @@ public function getCalendarsForUserCount($principalUri, $excludeBirthday = true) $result = $query->executeQuery(); $column = (int)$result->fetchOne(); $result->closeCursor(); + return $column; } @@ -243,6 +244,7 @@ public function getSubscriptionsForUserCount(string $principalUri): int { $result = $query->executeQuery(); $column = (int)$result->fetchOne(); $result->closeCursor(); + return $column; } @@ -263,7 +265,9 @@ public function getDeletedCalendars(int $deletedBefore): array { 'deleted_at' => (int)$row['deleted_at'], ]; } + $result->closeCursor(); + return $calendars; } @@ -345,6 +349,7 @@ public function getCalendarsForUser($principalUri) { $calendars[$calendar['id']] = $calendar; } } + $result->closeCursor(); // query for shared calendars @@ -354,7 +359,7 @@ public function getCalendarsForUser($principalUri) { $fields = array_column($this->propertyMap, 0); $fields = array_map(function (string $field) { - return 'a.'.$field; + return 'a.' . $field; }, $fields); $fields[] = 'a.id'; $fields[] = 'a.uri'; @@ -394,6 +399,7 @@ public function getCalendarsForUser($principalUri) { // New share can not have more permissions than the old one. continue; } + if (isset($calendars[$row['id']][$readOnlyPropertyName]) && $calendars[$row['id']][$readOnlyPropertyName] === 0) { // Old share is already read-write, no more permissions can be gained @@ -408,6 +414,7 @@ public function getCalendarsForUser($principalUri) { if ($row['components']) { $components = explode(',', $row['components']); } + $calendar = [ 'id' => $row['id'], 'uri' => $uri, @@ -426,6 +433,7 @@ public function getCalendarsForUser($principalUri) { $calendars[$calendar['id']] = $calendar; } + $result->closeCursor(); return array_values($calendars); @@ -458,6 +466,7 @@ public function getUsersOwnCalendars($principalUri) { if ($row['components']) { $components = explode(',', $row['components']); } + $calendar = [ 'id' => $row['id'], 'uri' => $row['uri'], @@ -476,7 +485,9 @@ public function getUsersOwnCalendars($principalUri) { $calendars[$calendar['id']] = $calendar; } } + $stmt->closeCursor(); + return array_values($calendars); } @@ -510,6 +521,7 @@ public function getPublicCalendars() { if ($row['components']) { $components = explode(',', $row['components']); } + $calendar = [ 'id' => $row['id'], 'uri' => $row['publicuri'], @@ -531,6 +543,7 @@ public function getPublicCalendars() { $calendars[$calendar['id']] = $calendar; } } + $result->closeCursor(); return array_values($calendars); @@ -575,6 +588,7 @@ public function getPublicCalendar($uri) { if ($row['components']) { $components = explode(',', $row['components']); } + $calendar = [ 'id' => $row['id'], 'uri' => $row['publicuri'], @@ -766,6 +780,7 @@ public function createCalendar($principalUri, $calendarUri, array $properties) { if (!($properties[$sccs] instanceof SupportedCalendarComponentSet)) { throw new DAV\Exception('The ' . $sccs . ' property must be of type: \Sabre\CalDAV\Property\SupportedCalendarComponentSet'); } + $values['components'] = implode(',', $properties[$sccs]->getValue()); } elseif (isset($properties['components'])) { // Allow to provide components internally without having @@ -790,10 +805,12 @@ public function createCalendar($principalUri, $calendarUri, array $properties) { foreach ($values as $column => $value) { $query->setValue($column, $query->createNamedParameter($value)); } + $query->executeStatement(); $calendarId = $query->getLastInsertId(); $calendarData = $this->getCalendarById($calendarId); + return [$calendarId, $calendarData]; }, $this->db); @@ -836,12 +853,14 @@ public function updateCalendar($calendarId, PropPatch $propPatch) { break; } } + [$calendarData, $shares] = $this->atomic(function () use ($calendarId, $newValues) { $query = $this->db->getQueryBuilder(); $query->update('calendars'); foreach ($newValues as $fieldName => $value) { $query->set($fieldName, $query->createNamedParameter($value)); } + $query->where($query->expr()->eq('id', $query->createNamedParameter($calendarId))); $query->executeStatement(); @@ -849,6 +868,7 @@ public function updateCalendar($calendarId, PropPatch $propPatch) { $calendarData = $this->getCalendarById($calendarId); $shares = $this->getShares($calendarId); + return [$calendarData, $shares]; }, $this->db); @@ -938,6 +958,7 @@ public function restoreCalendar(int $id): void { if ($calendarData === null) { throw new RuntimeException('Calendar data that was just written can\'t be read back. Check your database configuration.'); } + $this->dispatcher->dispatchTyped(new CalendarRestoredEvent( $id, $calendarData, @@ -978,6 +999,7 @@ public function getLimitedCalendarObjects(int $calendarId, int $calendarType = s 'calendardata' => $row['calendardata'], ]; } + $stmt->closeCursor(); return $result; @@ -1047,6 +1069,7 @@ public function getCalendarObjects($calendarId, $calendarType = self::CALENDAR_T 'classification' => (int)$row['classification'] ]; } + $stmt->closeCursor(); return $result; @@ -1076,6 +1099,7 @@ public function getDeletedCalendarObjects(int $deletedBefore): array { '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_NEXTCLOUD . '}deleted-at' => $row['deleted_at'] === null ? $row['deleted_at'] : (int)$row['deleted_at'], ]; } + $stmt->closeCursor(); return $result; @@ -1115,6 +1139,7 @@ public function getDeletedCalendarObjectsByPrincipal(string $principalUri): arra '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_NEXTCLOUD . '}deleted-at' => $row['deleted_at'] === null ? $row['deleted_at'] : (int)$row['deleted_at'], ]; } + $stmt->closeCursor(); return $result; @@ -1142,6 +1167,7 @@ public function getCalendarObject($calendarId, $objectUri, int $calendarType = s if (isset($this->cachedObjects[$key])) { return $this->cachedObjects[$key]; } + $query = $this->db->getQueryBuilder(); $query->select(['id', 'uri', 'lastmodified', 'etag', 'calendarid', 'size', 'calendardata', 'componenttype', 'classification', 'deleted_at']) ->from('calendarobjects') @@ -1158,6 +1184,7 @@ public function getCalendarObject($calendarId, $objectUri, int $calendarType = s $object = $this->rowToCalendarObject($row); $this->cachedObjects[$key] = $object; + return $object; } @@ -1222,6 +1249,7 @@ public function getMultipleCalendarObjects($calendarId, array $uris, $calendarTy 'classification' => (int)$row['classification'] ]; } + $result->closeCursor(); } @@ -1267,6 +1295,7 @@ public function createCalendarObject($calendarId, $objectUri, $calendarData, $ca if ($count !== 0) { throw new BadRequest('Calendar object with uid already exists in this calendar collection.'); } + // For a more specific error message we also try to explicitly look up the UID but as a deleted entry $qbDel = $this->db->getQueryBuilder(); $qbDel->select('*') @@ -1435,6 +1464,7 @@ public function moveCalendarObject(int $sourceCalendarId, int $targetCalendarId, $sourceCalendarRow = $this->getCalendarById($sourceCalendarId); $this->dispatcher->dispatchTyped(new CalendarObjectMovedEvent($sourceCalendarId, $sourceCalendarRow, $targetCalendarId, $targetCalendarRow, $sourceShares, $targetShares, $object)); } + return true; }, $this->db); } @@ -1451,6 +1481,7 @@ public function setClassification($calendarObjectId, $classification) { ])) { throw new \InvalidArgumentException(); } + $query = $this->db->getQueryBuilder(); $query->update('calendarobjects') ->set('classification', $query->createNamedParameter($classification)) @@ -1584,12 +1615,14 @@ public function restoreCalendarObject(array $objectData): void { // Welp, this should possibly not have happened, but let's ignore return; } + $this->addChanges($row['calendarid'], [$row['uri']], 1, (int)$row['calendartype']); $calendarRow = $this->getCalendarById((int)$row['calendarid']); if ($calendarRow === null) { throw new RuntimeException('Calendar object data that was just written can\'t be read back. Check your database configuration.'); } + $this->dispatcher->dispatchTyped( new CalendarObjectRestoredEvent( (int)$objectData['calendarid'], @@ -1669,6 +1702,7 @@ public function calendarQuery($calendarId, array $filters, $calendarType = self: if (!$filters['prop-filters'] && !$filters['comp-filters'][0]['comp-filters'] && !$filters['comp-filters'][0]['time-range'] && !$filters['comp-filters'][0]['prop-filters']) { $requirePostFilter = false; } + // There was a time-range filter if ($componentType === 'VEVENT' && isset($filters['comp-filters'][0]['time-range']) && is_array($filters['comp-filters'][0]['time-range'])) { $timeRange = $filters['comp-filters'][0]['time-range']; @@ -1680,6 +1714,7 @@ public function calendarQuery($calendarId, array $filters, $calendarType = self: } } } + $query = $this->db->getQueryBuilder(); $query->select(['id', 'uri', 'lastmodified', 'etag', 'calendarid', 'size', 'calendardata', 'componenttype', 'classification', 'deleted_at']) ->from('calendarobjects') @@ -1694,6 +1729,7 @@ public function calendarQuery($calendarId, array $filters, $calendarType = self: if ($timeRange && $timeRange['start']) { $query->andWhere($query->expr()->gt('lastoccurence', $query->createNamedParameter($timeRange['start']->getTimeStamp()))); } + if ($timeRange && $timeRange['end']) { $query->andWhere($query->expr()->lt('firstoccurence', $query->createNamedParameter($timeRange['end']->getTimeStamp()))); } @@ -1713,19 +1749,19 @@ public function calendarQuery($calendarId, array $filters, $calendarType = self: try { $matches = $this->validateFilterForObject($row, $filters); } catch (ParseException $ex) { - $this->logger->error('Caught parsing exception for calendar data. This usually indicates invalid calendar data. calendar-id:'.$calendarId.' uri:'.$row['uri'], [ + $this->logger->error('Caught parsing exception for calendar data. This usually indicates invalid calendar data. calendar-id:' . $calendarId . ' uri:' . $row['uri'], [ 'app' => 'dav', 'exception' => $ex, ]); continue; } catch (InvalidDataException $ex) { - $this->logger->error('Caught invalid data exception for calendar data. This usually indicates invalid calendar data. calendar-id:'.$calendarId.' uri:'.$row['uri'], [ + $this->logger->error('Caught invalid data exception for calendar data. This usually indicates invalid calendar data. calendar-id:' . $calendarId . ' uri:' . $row['uri'], [ 'app' => 'dav', 'exception' => $ex, ]); continue; } catch (MaxInstancesExceededException $ex) { - $this->logger->warning('Caught max instances exceeded exception for calendar data. This usually indicates too much recurring (more than 3500) event in calendar data. Object uri: '.$row['uri'], [ + $this->logger->warning('Caught max instances exceeded exception for calendar data. This usually indicates too much recurring (more than 3500) event in calendar data. Object uri: ' . $row['uri'], [ 'app' => 'dav', 'exception' => $ex, ]); @@ -1736,6 +1772,7 @@ public function calendarQuery($calendarId, array $filters, $calendarType = self: continue; } } + $result[] = $row['uri']; $key = $calendarId . '::' . $row['uri'] . '::' . $calendarType; $this->cachedObjects[$key] = $this->rowToCalendarObject($row); @@ -1769,8 +1806,10 @@ public function calendarSearch($principalUri, array $filters, $limit = null, $of } else { $sharedCalendars[] = $calendar['id']; } + $uriMapper[$calendar['id']] = $calendar['uri']; } + if (count($ownCalendars) === 0 && count($sharedCalendars) === 0) { return []; } @@ -1785,6 +1824,7 @@ public function calendarSearch($principalUri, array $filters, $limit = null, $of $query->expr()->eq('c.calendartype', $query->createNamedParameter(self::CALENDAR_TYPE_CALENDAR))); } + foreach ($sharedCalendars as $id) { $calendarExpressions[] = $query->expr()->andX( $query->expr()->eq('c.calendarid', @@ -1817,6 +1857,7 @@ public function calendarSearch($principalUri, array $filters, $limit = null, $of if (!isset($filters['props'])) { $filters['props'] = []; } + if (!isset($filters['params'])) { $filters['params'] = []; } @@ -1828,6 +1869,7 @@ public function calendarSearch($principalUri, array $filters, $limit = null, $of $query->expr()->isNull('i.parameter') ); } + foreach ($filters['params'] as $param) { $propParamExpressions[] = $query->expr()->andX( $query->expr()->eq('i.name', $query->createNamedParameter($param['property'])), @@ -1848,12 +1890,13 @@ public function calendarSearch($principalUri, array $filters, $limit = null, $of ->andWhere($compExpr) ->andWhere($propParamExpr) ->andWhere($query->expr()->iLike('i.value', - $query->createNamedParameter('%'.$this->db->escapeLikeParameter($filters['search-term']).'%'))) + $query->createNamedParameter('%' . $this->db->escapeLikeParameter($filters['search-term']) . '%'))) ->andWhere($query->expr()->isNull('deleted_at')); if ($offset) { $query->setFirstResult($offset); } + if ($limit) { $query->setMaxResults($limit); } @@ -1890,7 +1933,7 @@ public function search( array $searchProperties, array $options, $limit, - $offset + $offset, ) { $outerQuery = $this->db->getQueryBuilder(); $innerQuery = $this->db->getQueryBuilder(); @@ -1924,6 +1967,7 @@ public function search( $or[] = $innerQuery->expr()->eq('op.name', $outerQuery->createNamedParameter($searchProperty)); } + $innerQuery->andWhere($innerQuery->expr()->orX(...$or)); } @@ -1973,6 +2017,7 @@ public function search( $or[] = $outerQuery->expr()->eq('componenttype', $outerQuery->createNamedParameter($type)); } + $outerQuery->andWhere($outerQuery->expr()->orX(...$or)); } @@ -2066,7 +2111,7 @@ public function search( return $calendarObjects; } - private function searchCalendarObjects(IQueryBuilder $query, DateTimeInterface|null $start, DateTimeInterface|null $end): array { + private function searchCalendarObjects(IQueryBuilder $query, ?DateTimeInterface $start, ?DateTimeInterface $end): array { $calendarObjects = []; $filterByTimeRange = ($start instanceof DateTimeInterface) || ($end instanceof DateTimeInterface); @@ -2099,7 +2144,7 @@ private function searchCalendarObjects(IQueryBuilder $query, DateTimeInterface|n 'time-range' => null, ]); } catch (MaxInstancesExceededException $ex) { - $this->logger->warning('Caught max instances exceeded exception for calendar data. This usually indicates too much recurring (more than 3500) event in calendar data. Object uri: '.$row['uri'], [ + $this->logger->warning('Caught max instances exceeded exception for calendar data. This usually indicates too much recurring (more than 3500) event in calendar data. Object uri: ' . $row['uri'], [ 'app' => 'dav', 'exception' => $ex, ]); @@ -2140,6 +2185,7 @@ private function transformSearchData(Component $comp) { if (!isset($data[$name])) { $data[$name] = []; } + $data[$name][] = $this->transformSearchData($subComponent); } @@ -2196,7 +2242,7 @@ public function searchPrincipalUri(string $principalUri, array $componentTypes, array $searchProperties, array $searchParameters, - array $options = [] + array $options = [], ): array { return $this->atomic(function () use ($principalUri, $pattern, $componentTypes, $searchProperties, $searchParameters, $options) { $escapePattern = !\array_key_exists('escape_like_param', $options) || $options['escape_like_param'] !== false; @@ -2222,6 +2268,7 @@ public function searchPrincipalUri(string $principalUri, $calendarOr[] = $calendarAnd; } + foreach ($subscriptions as $subscription) { $subscriptionAnd = $calendarObjectIdQuery->expr()->andX( $calendarObjectIdQuery->expr()->eq('cob.calendarid', $calendarObjectIdQuery->createNamedParameter((int)$subscription['id'])), @@ -2245,6 +2292,7 @@ public function searchPrincipalUri(string $principalUri, $searchOr[] = $propertyAnd; } + foreach ($searchParameters as $property => $parameter) { $parameterAnd = $calendarObjectIdQuery->expr()->andX( $calendarObjectIdQuery->expr()->eq('cob.name', $calendarObjectIdQuery->createNamedParameter($property, IQueryBuilder::PARAM_STR)), @@ -2257,6 +2305,7 @@ public function searchPrincipalUri(string $principalUri, if (empty($calendarOr)) { return []; } + if (empty($searchOr)) { return []; } @@ -2280,9 +2329,11 @@ public function searchPrincipalUri(string $principalUri, if (isset($options['limit'])) { $calendarObjectIdQuery->setMaxResults($options['limit']); } + if (isset($options['offset'])) { $calendarObjectIdQuery->setFirstResult($options['offset']); } + if (isset($options['timerange'])) { if (isset($options['timerange']['start']) && $options['timerange']['start'] instanceof DateTimeInterface) { $calendarObjectIdQuery->andWhere($calendarObjectIdQuery->expr()->gt( @@ -2290,6 +2341,7 @@ public function searchPrincipalUri(string $principalUri, $calendarObjectIdQuery->createNamedParameter($options['timerange']['start']->getTimeStamp()), )); } + if (isset($options['timerange']['end']) && $options['timerange']['end'] instanceof DateTimeInterface) { $calendarObjectIdQuery->andWhere($calendarObjectIdQuery->expr()->lt( 'firstoccurence', @@ -2303,6 +2355,7 @@ public function searchPrincipalUri(string $principalUri, while (($row = $result->fetch()) !== false) { $matches[] = (int)$row['objectid']; } + $result->closeCursor(); $query = $this->db->getQueryBuilder(); @@ -2319,7 +2372,9 @@ public function searchPrincipalUri(string $principalUri, $calendarObjects[] = $array; } + $result->closeCursor(); + return $calendarObjects; }, $this->db); } @@ -2486,10 +2541,12 @@ public function getChangesForCalendar($calendarId, $syncToken, $syncLevel, $limi ->andWhere($qb->expr()->lt('synctoken', $qb->createNamedParameter($currentToken))) ->groupBy('uri'); } + // evaluate if limit exists if (is_numeric($limit)) { $qb->setMaxResults($limit); } + // execute command $stmt = $qb->executeQuery(); // build results @@ -2511,6 +2568,7 @@ public function getChangesForCalendar($calendarId, $syncToken, $syncLevel, $limi }; } } + $stmt->closeCursor(); return $result; @@ -2624,6 +2682,7 @@ public function createSubscription($principalUri, $uri, array $properties) { foreach (array_keys($values) as $name) { $valuesToInsert[$name] = $query->createNamedParameter($values[$name]); } + $query->insert('calendarsubscriptions') ->values($valuesToInsert) ->executeStatement(); @@ -2631,6 +2690,7 @@ public function createSubscription($principalUri, $uri, array $properties) { $subscriptionId = $query->getLastInsertId(); $subscriptionRow = $this->getSubscriptionById($subscriptionId); + return [$subscriptionId, $subscriptionRow]; }, $this->db); @@ -2678,6 +2738,7 @@ public function updateSubscription($subscriptionId, PropPatch $propPatch) { foreach ($newValues as $fieldName => $value) { $query->set($fieldName, $query->createNamedParameter($value)); } + $query->where($query->expr()->eq('id', $query->createNamedParameter($subscriptionId))) ->executeStatement(); @@ -2794,6 +2855,7 @@ public function getSchedulingObjects($principalUri) { 'size' => (int)$row['size'], ]; } + $stmt->closeCursor(); return $results; @@ -2833,6 +2895,7 @@ public function deleteOutdatedSchedulingObjects(int $modifiedBefore, int $limit) if ($count === 0) { return; } + $ids = array_map(static function (array $id) { return (int)$id[0]; }, $result->fetchAll(\PDO::FETCH_NUM)); @@ -2996,6 +3059,7 @@ public function getDenormalizedData(string $calendarData): array { $hasDTSTART = true; } } + // Track first component type and uid if ($uid === null) { $componentType = $component->name; @@ -3003,6 +3067,7 @@ public function getDenormalizedData(string $calendarData): array { } } } + if (!$componentType) { throw new BadRequest('Calendar objects must have a VJOURNAL, VEVENT or VTODO component'); } @@ -3036,6 +3101,7 @@ public function getDenormalizedData(string $calendarData): array { ]); throw new Forbidden($e->getMessage()); } + $maxDate = new DateTime(self::MAX_DATE); $firstOccurrence = $it->getDtStart()->getTimestamp(); if ($it->isInfinite()) { @@ -3046,6 +3112,7 @@ public function getDenormalizedData(string $calendarData): array { $end = $it->getDtEnd(); $it->next(); } + $lastOccurrence = $end->getTimestamp(); } } @@ -3062,6 +3129,7 @@ public function getDenormalizedData(string $calendarData): array { break; } } + return [ 'etag' => md5($calendarData), 'size' => strlen($calendarData), @@ -3096,6 +3164,7 @@ public function updateShares(IShareable $shareable, array $add, array $remove): if ($calendarRow === null) { throw new \RuntimeException('Trying to update shares for non-existing calendar: ' . $calendarId); } + $oldShares = $this->getShares($calendarId); $this->calendarSharingBackend->updateShares($shareable, $add, $remove, $oldShares); @@ -3139,14 +3208,17 @@ public function setPublishStatus($value, $calendar) { $query->executeStatement(); $this->dispatcher->dispatchTyped(new CalendarPublishedEvent($calendarId, $calendarData, $publicUri)); + return $publicUri; } + $query->delete('dav_shares') ->where($query->expr()->eq('resourceid', $query->createNamedParameter($calendar->getResourceId()))) ->andWhere($query->expr()->eq('access', $query->createNamedParameter(self::ACCESS_PUBLIC))); $query->executeStatement(); $this->dispatcher->dispatchTyped(new CalendarUnpublishedEvent($calendarId, $calendarData)); + return null; }, $this->db); } @@ -3165,6 +3237,7 @@ public function getPublishStatus($calendar) { $row = $result->fetch(); $result->closeCursor(); + return $row ? reset($row) : false; } @@ -3225,6 +3298,7 @@ public function updateProperties($calendarId, $objectUri, $calendarData, $calend if (!$this->db->supports4ByteText()) { $value = preg_replace('/[\x{10000}-\x{10FFFF}]/u', "\xEF\xBF\xBD", $value); } + $value = mb_strcut($value, 0, 254); $query->setParameter('name', $property->name); @@ -3272,6 +3346,7 @@ public function deleteAllBirthdayCalendars() { true // No data to keep in the trashbin, if the user re-enables then we regenerate ); } + $result->closeCursor(); }, $this->db); } @@ -3292,6 +3367,7 @@ public function purgeAllCachedEventsForSubscription($subscriptionId) { while (($row = $stmt->fetch()) !== false) { $uris[] = $row['uri']; } + $stmt->closeCursor(); $query = $this->db->getQueryBuilder(); @@ -3351,6 +3427,7 @@ public function purgeCachedEventsForSubscription(int $subscriptionId, array $cal ->andWhere($query->expr()->in('uri', $query->createNamedParameter($chunk, IQueryBuilder::PARAM_STR_ARRAY), IQueryBuilder::PARAM_STR_ARRAY)) ->executeStatement(); } + $this->addChanges($subscriptionId, $calendarObjectUris, 3, self::CALENDAR_TYPE_SUBSCRIPTION); }, $this->db); } @@ -3450,6 +3527,7 @@ public function pruneOutdatedSyncTokens(int $keep, int $retention): int { $query->expr()->lte('id', $query->createNamedParameter($maxId - $keep, IQueryBuilder::PARAM_INT), IQueryBuilder::PARAM_INT), $query->expr()->lte('created_at', $query->createNamedParameter($retention)), ); + return $query->executeStatement(); } @@ -3466,8 +3544,10 @@ private function convertPrincipal($principalUri, $toV2) { if ($toV2 === true) { return "principals/users/$name"; } + return "principals/$name"; } + return $principalUri; } @@ -3488,6 +3568,7 @@ private function addOwnerPrincipalToCalendar(array $calendarInfo): array { if (isset($principalInformation['{DAV:}displayname'])) { $calendarInfo[$displaynameKey] = $principalInformation['{DAV:}displayname']; } + return $calendarInfo; } @@ -3502,6 +3583,7 @@ private function addResourceTypeToCalendar(array $row, array $calendar): array { sprintf('{%s}deleted-calendar', \OCA\DAV\DAV\Sharing\Plugin::NS_NEXTCLOUD), ]); } + return $calendar; } @@ -3519,8 +3601,10 @@ private function rowToCalendar($row, array $calendar): array { if ($value !== null) { settype($value, $type); } + $calendar[$xmlName] = $value; } + return $calendar; } @@ -3538,8 +3622,10 @@ private function rowToSubscription($row, array $subscription): array { if ($value !== null) { settype($value, $type); } + $subscription[$xmlName] = $value; } + return $subscription; } } diff --git a/apps/dav/lib/CalDAV/Calendar.php b/apps/dav/lib/CalDAV/Calendar.php index 28267921a6f35..a4515ab432863 100644 --- a/apps/dav/lib/CalDAV/Calendar.php +++ b/apps/dav/lib/CalDAV/Calendar.php @@ -49,6 +49,7 @@ public function __construct(BackendInterface $caldavBackend, $calendarInfo, IL10 if ($this->getName() === BirthdayService::BIRTHDAY_CALENDAR_URI && strcasecmp($this->calendarInfo['{DAV:}displayname'], 'Contact birthdays') === 0) { $this->calendarInfo['{DAV:}displayname'] = $l10n->t('Contact birthdays'); } + if ($this->getName() === CalDavBackend::PERSONAL_CALENDAR_URI && $this->calendarInfo['{DAV:}displayname'] === CalDavBackend::PERSONAL_CALENDAR_NAME) { $this->calendarInfo['{DAV:}displayname'] = $l10n->t('Personal'); @@ -67,6 +68,7 @@ public function updateShares(array $add, array $remove): void { if ($this->isShared()) { throw new Forbidden(); } + $this->caldavBackend->updateShares($this, $add, $remove); } @@ -86,6 +88,7 @@ public function getShares(): array { if ($this->isShared()) { return []; } + return $this->caldavBackend->getShares($this->getResourceId()); } @@ -178,6 +181,7 @@ public function getACL() { ]; } } + if ($this->isPublic()) { $acl[] = [ 'privilege' => '{DAV:}read', @@ -189,8 +193,8 @@ public function getACL() { $acl = $this->caldavBackend->applyShareAcl($this->getResourceId(), $acl); $allowedPrincipals = [ $this->getOwner(), - $this->getOwner(). '/calendar-proxy-read', - $this->getOwner(). '/calendar-proxy-write', + $this->getOwner() . '/calendar-proxy-read', + $this->getOwner() . '/calendar-proxy-write', parent::getOwner(), 'principals/system/public' ]; @@ -198,6 +202,7 @@ public function getACL() { $acl = array_filter($acl, function (array $rule) use ($allowedPrincipals): bool { return \in_array($rule['principal'], $allowedPrincipals, true); }); + return $acl; } @@ -209,6 +214,7 @@ public function getOwner(): ?string { if (isset($this->calendarInfo['{http://owncloud.org/ns}owner-principal'])) { return $this->calendarInfo['{http://owncloud.org/ns}owner-principal']; } + return parent::getOwner(); } @@ -219,6 +225,7 @@ public function delete() { $this->caldavBackend->updateShares($this, [], [ $principal ]); + return; } @@ -268,9 +275,11 @@ public function getChildren() { if ($obj['classification'] === CalDavBackend::CLASSIFICATION_PRIVATE && $this->isShared()) { continue; } + $obj['acl'] = $this->getChildACL(); $children[] = new CalendarObject($this->caldavBackend, $this->l10n, $this->calendarInfo, $obj); } + return $children; } @@ -281,9 +290,11 @@ public function getMultipleChildren(array $paths) { if ($obj['classification'] === CalDavBackend::CLASSIFICATION_PRIVATE && $this->isShared()) { continue; } + $obj['acl'] = $this->getChildACL(); $children[] = new CalendarObject($this->caldavBackend, $this->l10n, $this->calendarInfo, $obj); } + return $children; } @@ -292,6 +303,7 @@ public function childExists($name) { if (!$obj) { return false; } + if ($obj['classification'] === CalDavBackend::CLASSIFICATION_PRIVATE && $this->isShared()) { return false; } @@ -317,6 +329,7 @@ public function calendarQuery(array $filters) { public function setPublishStatus($value) { $publicUri = $this->caldavBackend->setPublishStatus($value, $this); $this->calendarInfo['publicuri'] = $publicUri; + return $publicUri; } @@ -335,6 +348,7 @@ public function canWrite() { if (isset($this->calendarInfo['{http://owncloud.org/ns}read-only'])) { return !$this->calendarInfo['{http://owncloud.org/ns}read-only']; } + return true; } @@ -358,6 +372,7 @@ public function isDeleted(): bool { if (!isset($this->calendarInfo[TrashbinPlugin::PROPERTY_DELETED_AT])) { return false; } + return $this->calendarInfo[TrashbinPlugin::PROPERTY_DELETED_AT] !== null; } diff --git a/apps/dav/lib/CalDAV/CalendarHome.php b/apps/dav/lib/CalDAV/CalendarHome.php index 59a976ca6bc3d..575b2e15fa3d2 100644 --- a/apps/dav/lib/CalDAV/CalendarHome.php +++ b/apps/dav/lib/CalDAV/CalendarHome.php @@ -42,7 +42,7 @@ public function __construct( BackendInterface $caldavBackend, array $principalInfo, LoggerInterface $logger, - private bool $returnCachedSubscriptions + private bool $returnCachedSubscriptions, ) { parent::__construct($caldavBackend, $principalInfo); $this->l10n = \OC::$server->getL10N('dav'); @@ -84,6 +84,7 @@ public function getChildren() { if ($this->cachedChildren) { return $this->cachedChildren; } + $calendars = $this->caldavBackend->getCalendarsForUser($this->principalInfo['uri']); $objects = []; foreach ($calendars as $calendar) { @@ -124,6 +125,7 @@ public function getChildren() { } $this->cachedChildren = $objects; + return $objects; } @@ -137,12 +139,15 @@ public function getChild($name) { if ($name === 'inbox' && $this->caldavBackend instanceof SchedulingSupport) { return new Inbox($this->caldavBackend, $this->principalInfo['uri']); } + if ($name === 'outbox' && $this->caldavBackend instanceof SchedulingSupport) { return new Outbox($this->config, $this->principalInfo['uri']); } + if ($name === 'notifications' && $this->caldavBackend instanceof NotificationSupport) { return new \Sabre\CalDAV\Notifications\Collection($this->caldavBackend, $this->principalInfo['uri']); } + if ($name === TrashbinHome::NAME && $this->caldavBackend instanceof CalDavBackend) { return new TrashbinHome($this->caldavBackend, $this->principalInfo); } diff --git a/apps/dav/lib/CalDAV/CalendarImpl.php b/apps/dav/lib/CalDAV/CalendarImpl.php index ea2ac0e9a62ac..3391650f6f8b3 100644 --- a/apps/dav/lib/CalDAV/CalendarImpl.php +++ b/apps/dav/lib/CalDAV/CalendarImpl.php @@ -78,6 +78,7 @@ public function getSchedulingTimezone(): ?VTimeZone { if (!isset($this->calendarInfo[$tzProp])) { return null; } + // This property contains a VCALENDAR with a single VTIMEZONE /** @var string $timezoneProp */ $timezoneProp = $this->calendarInfo[$tzProp]; @@ -87,8 +88,10 @@ public function getSchedulingTimezone(): ?VTimeZone { if (empty($components)) { return null; } + /** @var VTimeZone $vtimezone */ $vtimezone = $components[0]; + return $vtimezone; } @@ -199,6 +202,7 @@ public function handleIMipMessage(string $name, string $calendarData): void { if (empty($this->calendarInfo['uri'])) { throw new CalendarException('Could not write to calendar as URI parameter is missing'); } + // Force calendar change URI /** @var Schedule\Plugin $schedulingPlugin */ $schedulingPlugin = $server->getServer()->getPlugin('caldav-schedule'); @@ -216,6 +220,7 @@ public function handleIMipMessage(string $name, string $calendarData): void { if (!isset($vEvent->{'ORGANIZER'}) || !isset($vEvent->{'ATTENDEE'})) { throw new CalendarException('Could not process scheduling data, neccessary data missing from ICAL'); } + $organizer = $vEvent->{'ORGANIZER'}->getValue(); $attendee = $vEvent->{'ATTENDEE'}->getValue(); @@ -226,11 +231,13 @@ public function handleIMipMessage(string $name, string $calendarData): void { } else { $iTipMessage->recipient = $attendee; } + $iTipMessage->sender = $attendee; } elseif ($iTipMessage->method === 'CANCEL') { $iTipMessage->recipient = $attendee; $iTipMessage->sender = $organizer; } + $iTipMessage->uid = isset($vEvent->{'UID'}) ? $vEvent->{'UID'}->getValue() : ''; $iTipMessage->component = 'VEVENT'; $iTipMessage->sequence = isset($vEvent->{'SEQUENCE'}) ? (int)$vEvent->{'SEQUENCE'}->getValue() : 0; diff --git a/apps/dav/lib/CalDAV/CalendarObject.php b/apps/dav/lib/CalDAV/CalendarObject.php index 0dd9bee9ce4b6..af5df823c2940 100644 --- a/apps/dav/lib/CalDAV/CalendarObject.php +++ b/apps/dav/lib/CalDAV/CalendarObject.php @@ -87,6 +87,7 @@ private function createConfidentialObject(Component\VCalendar $vObject): void { if (empty($vElement->select('SUMMARY'))) { $vElement->add('SUMMARY', $this->l10n->t('Busy')); // This is needed to mask "Untitled Event" events } + foreach ($vElement->children() as &$property) { /** @var Property $property */ switch ($property->name) { @@ -133,6 +134,7 @@ private function canWrite() { if (isset($this->calendarInfo['{http://owncloud.org/ns}read-only'])) { return !$this->calendarInfo['{http://owncloud.org/ns}read-only']; } + return true; } @@ -148,6 +150,7 @@ public function getOwner(): ?string { if (isset($this->calendarInfo['{http://owncloud.org/ns}owner-principal'])) { return $this->calendarInfo['{http://owncloud.org/ns}owner-principal']; } + return parent::getOwner(); } } diff --git a/apps/dav/lib/CalDAV/CalendarProvider.php b/apps/dav/lib/CalDAV/CalendarProvider.php index 90605d4f76e38..afd9c82fe1f2e 100644 --- a/apps/dav/lib/CalDAV/CalendarProvider.php +++ b/apps/dav/lib/CalDAV/CalendarProvider.php @@ -55,6 +55,7 @@ public function getCalendars(string $principalUri, array $calendarUris = []): ar $this->calDavBackend, ); } + return $iCalendars; } } diff --git a/apps/dav/lib/CalDAV/CalendarRoot.php b/apps/dav/lib/CalDAV/CalendarRoot.php index 7e57b70c48144..9ab3a4dd43f1e 100644 --- a/apps/dav/lib/CalDAV/CalendarRoot.php +++ b/apps/dav/lib/CalDAV/CalendarRoot.php @@ -20,7 +20,7 @@ public function __construct( PrincipalBackend\BackendInterface $principalBackend, Backend\BackendInterface $caldavBackend, $principalPrefix, - LoggerInterface $logger + LoggerInterface $logger, ) { parent::__construct($principalBackend, $caldavBackend, $principalPrefix); $this->logger = $logger; diff --git a/apps/dav/lib/CalDAV/DefaultCalendarValidator.php b/apps/dav/lib/CalDAV/DefaultCalendarValidator.php index 266e07ef25509..42f120aa8fd03 100644 --- a/apps/dav/lib/CalDAV/DefaultCalendarValidator.php +++ b/apps/dav/lib/CalDAV/DefaultCalendarValidator.php @@ -34,6 +34,7 @@ public function validateScheduleDefaultCalendar(Calendar $calendar): void { } else { $supportedComponents = ['VJOURNAL', 'VTODO', 'VEVENT']; } + if (!in_array('VEVENT', $supportedComponents, true)) { throw new DavException('Calendar does not support VEVENT components'); } diff --git a/apps/dav/lib/CalDAV/EventComparisonService.php b/apps/dav/lib/CalDAV/EventComparisonService.php index 63395e7ce1c65..6be6e8a0b2678 100644 --- a/apps/dav/lib/CalDAV/EventComparisonService.php +++ b/apps/dav/lib/CalDAV/EventComparisonService.php @@ -47,12 +47,14 @@ private function removeIfUnchanged(VEvent $filterEvent, array &$eventsToFilter): foreach (self::EVENT_DIFF as $eventDiff) { $eventToFilterData[] = IMipService::readPropertyWithDefault($eventToFilter, $eventDiff, ''); } + // events are identical and can be removed if ($filterEventData === $eventToFilterData) { unset($eventsToFilter[$k]); return true; } } + return false; } @@ -89,6 +91,7 @@ public function findModified(VCalendar $new, ?VCalendar $old): array { unset($oldEventComponents[$k]); continue; } + if ($this->removeIfUnchanged($event, $newEventComponents)) { unset($oldEventComponents[$k]); } diff --git a/apps/dav/lib/CalDAV/EventReader.php b/apps/dav/lib/CalDAV/EventReader.php index bdceb184fb01b..8e8da60d035e5 100644 --- a/apps/dav/lib/CalDAV/EventReader.php +++ b/apps/dav/lib/CalDAV/EventReader.php @@ -75,6 +75,7 @@ public function __construct(VCalendar|VEvent|array|string $input, ?string $uid = if (is_string($input)) { $input = Reader::read($input); } + // evaluate if input is a single event vobject and convert it to a collection if ($input instanceof VEvent) { $events = [$input]; @@ -85,12 +86,14 @@ public function __construct(VCalendar|VEvent|array|string $input, ?string $uid = if ($uid === null) { throw new InvalidArgumentException('The UID argument is required when a VCALENDAR object is used'); } + // extract events from calendar $events = $input->getByUID($uid); // evaluate if any event where found if (count($events) === 0) { - throw new InvalidArgumentException('This VCALENDAR did not have an event with UID: '.$uid); + throw new InvalidArgumentException('This VCALENDAR did not have an event with UID: ' . $uid); } + // extract calendar timezone if (isset($input->VTIMEZONE) && isset($input->VTIMEZONE->TZID)) { $calendarTimeZone = new DateTimeZone($input->VTIMEZONE->TZID->getValue()); @@ -102,6 +105,7 @@ public function __construct(VCalendar|VEvent|array|string $input, ?string $uid = } else { throw new InvalidArgumentException('Invalid input data type'); } + // find base event instance and remove it from events collection foreach ($events as $key => $vevent) { if (!isset($vevent->{'RECURRENCE-ID'})) { @@ -157,6 +161,7 @@ public function __construct(VCalendar|VEvent|array|string $input, ?string $uid = else { $this->baseEventEndTimeZone = clone $this->baseEventStartTimeZone; } + // extract start date and time $this->baseEventStartDate = $this->baseEvent->DTSTART->getDateTime($this->baseEventStartTimeZone); $this->baseEventStartDateFloating = $this->baseEvent->DTSTART->isFloating(); @@ -188,6 +193,7 @@ public function __construct(VCalendar|VEvent|array|string $input, ?string $uid = $this->baseEventDuration = 0; $this->baseEventEndDate = $this->baseEventStartDate; } + // evaluate if RRULE exist and construct iterator if (isset($this->baseEvent->RRULE)) { $this->rruleIterator = new EventReaderRRule( @@ -195,6 +201,7 @@ public function __construct(VCalendar|VEvent|array|string $input, ?string $uid = $this->baseEventStartDate ); } + // evaluate if RDATE exist and construct iterator if (isset($this->baseEvent->RDATE)) { $this->rdateIterator = new EventReaderRDate( @@ -202,6 +209,7 @@ public function __construct(VCalendar|VEvent|array|string $input, ?string $uid = $this->baseEventStartDate ); } + // evaluate if EXRULE exist and construct iterator if (isset($this->baseEvent->EXRULE)) { $this->eruleIterator = new EventReaderRRule( @@ -209,6 +217,7 @@ public function __construct(VCalendar|VEvent|array|string $input, ?string $uid = $this->baseEventStartDate ); } + // evaluate if EXDATE exist and construct iterator if (isset($this->baseEvent->EXDATE)) { $this->edateIterator = new EventReaderRDate( @@ -216,6 +225,7 @@ public function __construct(VCalendar|VEvent|array|string $input, ?string $uid = $this->baseEventStartDate ); } + // construct collection of modified events with recurrence id as hash foreach ($events as $vevent) { $this->recurrenceModified[$vevent->{'RECURRENCE-ID'}->getDateTime($this->baseEventStartTimeZone)->getTimeStamp()] = $vevent; @@ -297,13 +307,15 @@ public function recurs(): bool { * * @return string|null R - Relative or A - Absolute */ - public function recurringPattern(): string|null { + public function recurringPattern(): ?string { if ($this->rruleIterator === null && $this->rdateIterator === null) { return null; } + if ($this->rruleIterator?->isRelative()) { return 'R'; } + return 'A'; } @@ -314,13 +326,15 @@ public function recurringPattern(): string|null { * * @return string|null daily, weekly, monthly, yearly, fixed */ - public function recurringPrecision(): string|null { + public function recurringPrecision(): ?string { if ($this->rruleIterator !== null) { return $this->rruleIterator->precision(); } + if ($this->rdateIterator !== null) { return 'fixed'; } + return null; } @@ -331,7 +345,7 @@ public function recurringPrecision(): string|null { * * @return int|null */ - public function recurringInterval(): int|null { + public function recurringInterval(): ?int { return $this->rruleIterator?->interval(); } @@ -353,6 +367,7 @@ public function recurringConcludes(): bool { $this->rruleIterator?->concludesAfter() !== null) { return true; } + // retrieve rdate conclusions if ($this->rdateIterator?->concludesAfter() !== null) { return true; @@ -374,7 +389,7 @@ public function recurringConcludes(): bool { * * @return int|null */ - public function recurringConcludesAfter(): int|null { + public function recurringConcludesAfter(): ?int { // construct count place holder $count = 0; @@ -399,7 +414,7 @@ public function recurringConcludesAfter(): int|null { * * @return DateTime|null */ - public function recurringConcludesOn(): DateTime|null { + public function recurringConcludesOn(): ?DateTime { if ($this->rruleIterator !== null) { // retrieve rrule conclusion date @@ -410,10 +425,12 @@ public function recurringConcludesOn(): DateTime|null { return null; } } + // retrieve rdate conclusion date if ($this->rdateIterator !== null) { $rdate = $this->rdateIterator->concludes(); } + // evaluate if both rrule and rdate have date if (isset($rdate) && isset($rrule)) { // return the highest date @@ -460,6 +477,7 @@ public function recurringDaysOfWeekNamed(): array { foreach ($days as $key => $value) { $days[$key] = $this->dayNamesMap[$value]; } + // return names collection return $days; } @@ -526,6 +544,7 @@ public function recurringWeeksOfMonthNamed(): array { foreach ($positions as $key => $value) { $positions[$key] = $this->relativePositionNamesMap[$value]; } + // return positions collection return $positions; } @@ -577,6 +596,7 @@ public function recurringMonthsOfYearNamed(): array { foreach ($months as $key => $value) { $months[$key] = $this->monthNamesMap[$value]; } + // return months collection return $months; } @@ -613,6 +633,7 @@ public function recurringRelativePositionNamed(): array { foreach ($positions as $key => $value) { $positions[$key] = $this->relativePositionNamesMap[$value]; } + // return positions collection return $positions; } @@ -626,7 +647,7 @@ public function recurringRelativePositionNamed(): array { * * @return DateTime */ - public function recurrenceDate(): DateTime|null { + public function recurrenceDate(): ?DateTime { if ($this->recurrenceCurrentDate !== null) { return DateTime::createFromInterface($this->recurrenceCurrentDate); } else { @@ -648,18 +669,22 @@ public function recurrenceRewind(): void { if ($this->rruleIterator !== null) { $this->rruleIterator->rewind(); } + // rewind and increment rdate if ($this->rdateIterator !== null) { $this->rdateIterator->rewind(); } + // rewind and increment exrule if ($this->eruleIterator !== null) { $this->eruleIterator->rewind(); } + // rewind and increment exdate if ($this->edateIterator !== null) { $this->edateIterator->rewind(); } + // set current date to event start date $this->recurrenceCurrentDate = clone $this->baseEventStartDate; } @@ -687,16 +712,20 @@ public function recurrenceAdvance(): void { while ($this->rruleIterator->valid() && $this->rruleIterator->current() <= $this->recurrenceCurrentDate) { $this->rruleIterator->next(); } + $rruleDate = $this->rruleIterator->current(); } + // evaludate if rdate is set and advance one interation past current date if ($this->rdateIterator !== null) { // forward rdate to the next future date while ($this->rdateIterator->valid() && $this->rdateIterator->current() <= $this->recurrenceCurrentDate) { $this->rdateIterator->next(); } + $rdateDate = $this->rdateIterator->current(); } + if ($rruleDate !== null && $rdateDate !== null) { $nextOccurrenceDate = ($rruleDate <= $rdateDate) ? $rruleDate : $rdateDate; } elseif ($rruleDate !== null) { @@ -711,16 +740,20 @@ public function recurrenceAdvance(): void { while ($this->eruleIterator->valid() && $this->eruleIterator->current() <= $this->recurrenceCurrentDate) { $this->eruleIterator->next(); } + $eruleDate = $this->eruleIterator->current(); } + // evaludate if exdate is set and advance one interation past current date if ($this->edateIterator !== null) { // forward exdate to the next future date while ($this->edateIterator->valid() && $this->edateIterator->current() <= $this->recurrenceCurrentDate) { $this->edateIterator->next(); } + $edateDate = $this->edateIterator->current(); } + // evaludate if exrule and exdate are set and set nextExDate to the first next date if ($eruleDate !== null && $edateDate !== null) { $nextExceptionDate = ($eruleDate <= $edateDate) ? $eruleDate : $edateDate; @@ -729,6 +762,7 @@ public function recurrenceAdvance(): void { } elseif ($edateDate !== null) { $nextExceptionDate = $edateDate; } + // if the next date is part of exrule or exdate find another date if ($nextOccurrenceDate !== null && $nextExceptionDate !== null && $nextOccurrenceDate == $nextExceptionDate) { $this->recurrenceCurrentDate = $nextOccurrenceDate; diff --git a/apps/dav/lib/CalDAV/EventReaderRDate.php b/apps/dav/lib/CalDAV/EventReaderRDate.php index 9d9669dc0f346..20234d06c00d5 100644 --- a/apps/dav/lib/CalDAV/EventReaderRDate.php +++ b/apps/dav/lib/CalDAV/EventReaderRDate.php @@ -13,15 +13,15 @@ class EventReaderRDate extends \Sabre\VObject\Recur\RDateIterator { - public function concludes(): DateTime|null { + public function concludes(): ?DateTime { return $this->concludesOn(); } - public function concludesAfter(): int|null { + public function concludesAfter(): ?int { return !empty($this->dates) ? count($this->dates) : null; } - public function concludesOn(): DateTime|null { + public function concludesOn(): ?DateTime { if (count($this->dates) > 0) { return new DateTime( $this->dates[array_key_last($this->dates)], diff --git a/apps/dav/lib/CalDAV/EventReaderRRule.php b/apps/dav/lib/CalDAV/EventReaderRRule.php index fc229ddcd4464..b63b304014200 100644 --- a/apps/dav/lib/CalDAV/EventReaderRRule.php +++ b/apps/dav/lib/CalDAV/EventReaderRRule.php @@ -22,11 +22,12 @@ public function interval(): int { return $this->interval; } - public function concludes(): DateTime|null { + public function concludes(): ?DateTime { // evaluate if until value is a date if ($this->until instanceof DateTimeInterface) { return DateTime::createFromInterface($this->until); } + // evaluate if count value is higher than 0 if ($this->count > 0) { // temporarily store current recurrence date and counter @@ -36,6 +37,7 @@ public function concludes(): DateTime|null { while ($this->counter <= ($this->count - 2)) { $this->next(); } + // temporarly store last reccurance date $lastReccuranceDate = $this->currentDate; // restore current recurrence date and counter @@ -48,11 +50,11 @@ public function concludes(): DateTime|null { return null; } - public function concludesAfter(): int|null { + public function concludesAfter(): ?int { return !empty($this->count) ? $this->count : null; } - public function concludesOn(): DateTime|null { + public function concludesOn(): ?DateTime { return isset($this->until) ? DateTime::createFromInterface($this->until) : null; } diff --git a/apps/dav/lib/CalDAV/InvitationResponse/InvitationResponseServer.php b/apps/dav/lib/CalDAV/InvitationResponse/InvitationResponseServer.php index 6fa94eebc91ea..b6d0b39adda15 100644 --- a/apps/dav/lib/CalDAV/InvitationResponse/InvitationResponseServer.php +++ b/apps/dav/lib/CalDAV/InvitationResponse/InvitationResponseServer.php @@ -93,6 +93,7 @@ public function __construct(bool $public = true) { foreach ($pluginManager->getAppPlugins() as $appPlugin) { $this->server->addPlugin($appPlugin); } + foreach ($pluginManager->getAppCollections() as $appCollection) { $root->addChild($appCollection); } @@ -112,6 +113,7 @@ public function handleITipMessage(Message $iTipMessage) { public function isExternalAttendee(string $principalUri): bool { /** @var \Sabre\DAVACL\Plugin $aclPlugin */ $aclPlugin = $this->getServer()->getPlugin('acl'); + return $aclPlugin->getPrincipalByUri($principalUri) === null; } diff --git a/apps/dav/lib/CalDAV/Plugin.php b/apps/dav/lib/CalDAV/Plugin.php index 24448ae71ab3c..3dc21ed0cf0d8 100644 --- a/apps/dav/lib/CalDAV/Plugin.php +++ b/apps/dav/lib/CalDAV/Plugin.php @@ -25,10 +25,12 @@ public function getCalendarHomeForPrincipal($principalUrl) { [, $principalId] = \Sabre\Uri\split($principalUrl); return self::CALENDAR_ROOT . '/' . $principalId; } + if (strrpos($principalUrl, 'principals/calendar-resources', -strlen($principalUrl)) !== false) { [, $principalId] = \Sabre\Uri\split($principalUrl); return self::SYSTEM_CALENDAR_ROOT . '/calendar-resources/' . $principalId; } + if (strrpos($principalUrl, 'principals/calendar-rooms', -strlen($principalUrl)) !== false) { [, $principalId] = \Sabre\Uri\split($principalUrl); return self::SYSTEM_CALENDAR_ROOT . '/calendar-rooms/' . $principalId; diff --git a/apps/dav/lib/CalDAV/Principal/User.php b/apps/dav/lib/CalDAV/Principal/User.php index 60b7953ea6293..75581956d5b59 100644 --- a/apps/dav/lib/CalDAV/Principal/User.php +++ b/apps/dav/lib/CalDAV/Principal/User.php @@ -31,6 +31,7 @@ public function getACL() { 'principal' => '{DAV:}authenticated', 'protected' => true, ]; + return $acl; } } diff --git a/apps/dav/lib/CalDAV/PublicCalendar.php b/apps/dav/lib/CalDAV/PublicCalendar.php index 4ee811efeaeac..b82cc1eda1e5e 100644 --- a/apps/dav/lib/CalDAV/PublicCalendar.php +++ b/apps/dav/lib/CalDAV/PublicCalendar.php @@ -20,9 +20,11 @@ public function getChild($name) { if (!$obj) { throw new NotFound('Calendar object not found'); } + if ($obj['classification'] === CalDavBackend::CLASSIFICATION_PRIVATE) { throw new NotFound('Calendar object not found'); } + $obj['acl'] = $this->getChildACL(); return new PublicCalendarObject($this->caldavBackend, $this->l10n, $this->calendarInfo, $obj); @@ -38,9 +40,11 @@ public function getChildren() { if ($obj['classification'] === CalDavBackend::CLASSIFICATION_PRIVATE) { continue; } + $obj['acl'] = $this->getChildACL(); $children[] = new PublicCalendarObject($this->caldavBackend, $this->l10n, $this->calendarInfo, $obj); } + return $children; } @@ -55,9 +59,11 @@ public function getMultipleChildren(array $paths) { if ($obj['classification'] === CalDavBackend::CLASSIFICATION_PRIVATE) { continue; } + $obj['acl'] = $this->getChildACL(); $children[] = new PublicCalendarObject($this->caldavBackend, $this->l10n, $this->calendarInfo, $obj); } + return $children; } diff --git a/apps/dav/lib/CalDAV/Publishing/PublishPlugin.php b/apps/dav/lib/CalDAV/Publishing/PublishPlugin.php index 1b30884cf12f9..d204bd3415224 100644 --- a/apps/dav/lib/CalDAV/Publishing/PublishPlugin.php +++ b/apps/dav/lib/CalDAV/Publishing/PublishPlugin.php @@ -97,17 +97,17 @@ public function initialize(Server $server) { public function propFind(PropFind $propFind, INode $node) { if ($node instanceof Calendar) { - $propFind->handle('{'.self::NS_CALENDARSERVER.'}publish-url', function () use ($node) { + $propFind->handle('{' . self::NS_CALENDARSERVER . '}publish-url', function () use ($node) { if ($node->getPublishStatus()) { // We return the publish-url only if the calendar is published. $token = $node->getPublishStatus(); - $publishUrl = $this->urlGenerator->getAbsoluteURL($this->server->getBaseUri().'public-calendars/').$token; + $publishUrl = $this->urlGenerator->getAbsoluteURL($this->server->getBaseUri() . 'public-calendars/') . $token; return new Publisher($publishUrl, true); } }); - $propFind->handle('{'.self::NS_CALENDARSERVER.'}allowed-sharing-modes', function () use ($node) { + $propFind->handle('{' . self::NS_CALENDARSERVER . '}allowed-sharing-modes', function () use ($node) { $canShare = (!$node->isSubscription() && $node->canWrite()); $canPublish = (!$node->isSubscription() && $node->canWrite()); @@ -160,12 +160,13 @@ public function httpPost(RequestInterface $request, ResponseInterface $response) switch ($documentType) { - case '{'.self::NS_CALENDARSERVER.'}publish-calendar': + case '{' . self::NS_CALENDARSERVER . '}publish-calendar': // We can only deal with IShareableCalendar objects if (!$node instanceof Calendar) { return; } + $this->server->transactionType = 'post-publish-calendar'; // Getting ACL info @@ -195,12 +196,13 @@ public function httpPost(RequestInterface $request, ResponseInterface $response) // Breaking the event chain return false; - case '{'.self::NS_CALENDARSERVER.'}unpublish-calendar': + case '{' . self::NS_CALENDARSERVER . '}unpublish-calendar': // We can only deal with IShareableCalendar objects if (!$node instanceof Calendar) { return; } + $this->server->transactionType = 'post-unpublish-calendar'; // Getting ACL info diff --git a/apps/dav/lib/CalDAV/Reminder/NotificationProvider/EmailProvider.php b/apps/dav/lib/CalDAV/Reminder/NotificationProvider/EmailProvider.php index 59f412c0a7bea..d5581d1a2bd82 100644 --- a/apps/dav/lib/CalDAV/Reminder/NotificationProvider/EmailProvider.php +++ b/apps/dav/lib/CalDAV/Reminder/NotificationProvider/EmailProvider.php @@ -86,6 +86,7 @@ public function send(VEvent $vevent, if (!$this->hasL10NForLang($lang)) { $lang = $fallbackLanguage; } + $l10n = $this->getL10NForLang($lang); $fromEMail = \OCP\Util::getDefaultEmailAddress('reminders-noreply'); @@ -106,6 +107,7 @@ public function send(VEvent $vevent, if ($organizer) { $message->setReplyTo($organizer); } + $message->setTo([$emailAddress]); $message->useTemplate($template); $message->setAutoSubmitted(AutoSubmitted::VALUE_AUTO_GENERATED); @@ -152,6 +154,7 @@ private function addBulletList(IEMailTemplate $template, $template->addBodyListItem((string)$vevent->LOCATION, $l10n->t('Where:'), $this->getAbsoluteImagePath('actions/address.png')); } + if (isset($vevent->DESCRIPTION)) { $template->addBodyListItem((string)$vevent->DESCRIPTION, $l10n->t('Description:'), $this->getAbsoluteImagePath('actions/more.png')); @@ -241,6 +244,7 @@ private function getAllEMailAddressesFromEvent(VEvent $vevent):array { // Don't send out emails to people who declined continue; } + if ($partstat === 'DELEGATED') { $delegates = $attendee->offsetGet('DELEGATED-TO'); if (!($delegates instanceof VObject\Parameter)) { @@ -310,6 +314,7 @@ private function getEMailAddressOfAttendee(VObject\Property $attendee): ?string if (!$this->hasAttendeeMailURI($attendee)) { return null; } + $attendeeEMail = substr($attendee->getValue(), 7); if (!$this->mailer->validateMailAddress($attendeeEMail)) { return null; diff --git a/apps/dav/lib/CalDAV/Reminder/NotificationProvider/PushProvider.php b/apps/dav/lib/CalDAV/Reminder/NotificationProvider/PushProvider.php index d8abbc3963182..7e48867fcfa6d 100644 --- a/apps/dav/lib/CalDAV/Reminder/NotificationProvider/PushProvider.php +++ b/apps/dav/lib/CalDAV/Reminder/NotificationProvider/PushProvider.php @@ -68,7 +68,9 @@ public function send(VEvent $vevent, $eventUUID = (string)$vevent->UID; if (!$eventUUID) { return; - }; + } + +; $eventUUIDHash = hash('sha256', $eventUUID, false); foreach ($users as $user) { diff --git a/apps/dav/lib/CalDAV/Reminder/NotificationProviderManager.php b/apps/dav/lib/CalDAV/Reminder/NotificationProviderManager.php index f64de85c4493f..e580c9885c285 100644 --- a/apps/dav/lib/CalDAV/Reminder/NotificationProviderManager.php +++ b/apps/dav/lib/CalDAV/Reminder/NotificationProviderManager.php @@ -42,8 +42,10 @@ public function getProvider(string $type):INotificationProvider { if (isset($this->providers[$type])) { return $this->providers[$type]; } + throw new NotificationProvider\ProviderNotAvailableException($type); } + throw new NotificationTypeDoesNotExistException($type); } diff --git a/apps/dav/lib/CalDAV/Reminder/Notifier.php b/apps/dav/lib/CalDAV/Reminder/Notifier.php index f3c784ea21f8a..c3497a1e231e8 100644 --- a/apps/dav/lib/CalDAV/Reminder/Notifier.php +++ b/apps/dav/lib/CalDAV/Reminder/Notifier.php @@ -137,15 +137,19 @@ private function prepareNotificationSubject(INotification $notification): void { if ($diff->y) { $components[] = $this->l10n->n('%n year', '%n years', $diff->y); } + if ($diff->m) { $components[] = $this->l10n->n('%n month', '%n months', $diff->m); } + if ($diff->d) { $components[] = $this->l10n->n('%n day', '%n days', $diff->d); } + if ($diff->h) { $components[] = $this->l10n->n('%n hour', '%n hours', $diff->h); } + if ($diff->i) { $components[] = $this->l10n->n('%n minute', '%n minutes', $diff->i); } @@ -193,6 +197,7 @@ private function prepareNotificationMessage(INotification $notification): void { if ($parameters['description']) { $description[] = $this->l10n->t('Description: %s', $parameters['description']); } + if ($parameters['location']) { $description[] = $this->l10n->t('Where: %s', $parameters['location']); } diff --git a/apps/dav/lib/CalDAV/Reminder/ReminderService.php b/apps/dav/lib/CalDAV/Reminder/ReminderService.php index b6032e733d991..f459d2aaffe77 100644 --- a/apps/dav/lib/CalDAV/Reminder/ReminderService.php +++ b/apps/dav/lib/CalDAV/Reminder/ReminderService.php @@ -206,6 +206,7 @@ public function onCalendarObjectCreate(array $objectData):void { if (!$vcalendar) { return; } + $calendarTimeZone = $this->getCalendarTimeZone((int)$objectData['calendarid']); $vevents = $this->getAllVEventsFromVCalendar($vcalendar); @@ -376,6 +377,7 @@ private function getRemindersForVAlarm(VAlarm $valarm, if ($eventHash === null) { $eventHash = $this->getEventHash($valarm->parent); } + if ($alarmHash === null) { $alarmHash = $this->getAlarmHash($valarm); } @@ -394,6 +396,7 @@ private function getRemindersForVAlarm(VAlarm $valarm, $calendarTimeZone ); } + $clonedNotificationDate = new \DateTime('now', $notificationDate->getTimezone()); $clonedNotificationDate->setTimestamp($notificationDate->getTimestamp()); @@ -446,11 +449,12 @@ private function getRemindersForVAlarm(VAlarm $valarm, private function writeRemindersToDatabase(array $reminders): void { $uniqueReminders = []; foreach ($reminders as $reminder) { - $key = $reminder['notification_date']. $reminder['event_hash'].$reminder['type']; + $key = $reminder['notification_date'] . $reminder['event_hash'] . $reminder['type']; if (!isset($uniqueReminders[$key])) { $uniqueReminders[$key] = $reminder; } } + foreach (array_values($uniqueReminders) as $reminder) { $this->backend->insertReminder( (int)$reminder['calendar_id'], @@ -608,18 +612,23 @@ private function getEventHash(VEvent $vevent):string { if ($vevent->DTEND) { $properties[] = (string)$vevent->DTEND->serialize(); } + if ($vevent->DURATION) { $properties[] = (string)$vevent->DURATION->serialize(); } + if ($vevent->{'RECURRENCE-ID'}) { $properties[] = (string)$vevent->{'RECURRENCE-ID'}->serialize(); } + if ($vevent->RRULE) { $properties[] = (string)$vevent->RRULE->serialize(); } + if ($vevent->EXDATE) { $properties[] = (string)$vevent->EXDATE->serialize(); } + if ($vevent->RDATE) { $properties[] = (string)$vevent->RDATE->serialize(); } @@ -643,6 +652,7 @@ private function getAlarmHash(VAlarm $valarm):string { if ($valarm->DURATION) { $properties[] = (string)$valarm->DURATION->serialize(); } + if ($valarm->REPEAT) { $properties[] = (string)$valarm->REPEAT->serialize(); } @@ -759,6 +769,7 @@ private function getUserFromPrincipalURI(string $principalUri):?IUser { } $userId = substr($principalUri, 17); + return $this->userManager->get($userId); } @@ -777,6 +788,7 @@ private function getAllVEventsFromVCalendar(VObject\Component\VCalendar $vcalend if ($child->name !== 'VEVENT') { continue; } + // Ignore invalid events with no DTSTART if ($child->DTSTART === null) { continue; @@ -810,6 +822,7 @@ private function getMasterItemFromListOfVEvents(array $vevents):?VEvent { if (count($elements) === 0) { return null; } + if (count($elements) > 1) { throw new \TypeError('Multiple master objects'); } @@ -858,6 +871,7 @@ private function getCalendarTimeZone(int $calendarid): DateTimeZone { // Defaulting to UTC return new DateTimeZone('UTC'); } + // This property contains a VCALENDAR with a single VTIMEZONE /** @var string $timezoneProp */ $timezoneProp = $calendarInfo[$tzProp]; @@ -865,6 +879,7 @@ private function getCalendarTimeZone(int $calendarid): DateTimeZone { $vtimezoneObj = VObject\Reader::read($timezoneProp); /** @var VObject\Component\VTimeZone $vtimezone */ $vtimezone = $vtimezoneObj->VTIMEZONE; + return $vtimezone->getTimeZone(); } } diff --git a/apps/dav/lib/CalDAV/ResourceBooking/AbstractPrincipalBackend.php b/apps/dav/lib/CalDAV/ResourceBooking/AbstractPrincipalBackend.php index 83e2935a46e2a..0e100360e71ba 100644 --- a/apps/dav/lib/CalDAV/ResourceBooking/AbstractPrincipalBackend.php +++ b/apps/dav/lib/CalDAV/ResourceBooking/AbstractPrincipalBackend.php @@ -143,6 +143,7 @@ public function getPrincipalByPath($path) { if (!str_starts_with($path, $this->principalPrefix)) { return null; } + [, $name] = \Sabre\Uri\split($path); [$backendId, $resourceId] = explode('-', $name, 2); @@ -225,6 +226,7 @@ public function searchPrincipals($prefixPath, array $searchProperties, $test = ' if (\count($searchProperties) === 0) { return []; } + if ($prefixPath !== $this->principalPrefix) { return []; } @@ -233,6 +235,7 @@ public function searchPrincipals($prefixPath, array $searchProperties, $test = ' if (!$user) { return []; } + $usersGroups = $this->groupManager->getUserGroupIds($user); foreach ($searchProperties as $prop => $value) { @@ -249,8 +252,10 @@ public function searchPrincipals($prefixPath, array $searchProperties, $test = ' if (!$this->isAllowedToAccessResource($row, $usersGroups)) { continue; } + $principals[] = $this->rowToPrincipal($row)['uri']; } + $results[] = $principals; $stmt->closeCursor(); @@ -268,8 +273,10 @@ public function searchPrincipals($prefixPath, array $searchProperties, $test = ' if (!$this->isAllowedToAccessResource($row, $usersGroups)) { continue; } + $principals[] = $this->rowToPrincipal($row)['uri']; } + $results[] = $principals; $stmt->closeCursor(); @@ -323,6 +330,7 @@ private function getMetadataQuery(string $key): IQueryBuilder { $query->select([$this->dbForeignKeyName]) ->from($this->dbMetaDataTableName) ->where($query->expr()->eq('key', $query->createNamedParameter($key))); + return $query; } @@ -340,6 +348,7 @@ private function getMetadataQuery(string $key): IQueryBuilder { private function searchPrincipalsByMetadataKey(string $key, string $value, array $usersGroups = []): array { $query = $this->getMetadataQuery($key); $query->andWhere($query->expr()->iLike('value', $query->createNamedParameter('%' . $this->db->escapeLikeParameter($value) . '%'))); + return $this->getRows($query, $usersGroups); } @@ -358,6 +367,7 @@ private function searchPrincipalsByRoomFeature(string $key, string $value, array foreach (explode(',', $value) as $v) { $query->andWhere($query->expr()->iLike('value', $query->createNamedParameter('%' . $this->db->escapeLikeParameter($v) . '%'))); } + return $this->getRows($query, $usersGroups); } @@ -374,6 +384,7 @@ private function searchPrincipalsByRoomFeature(string $key, string $value, array private function searchPrincipalsByCapacity(string $key, string $value, array $usersGroups = []): array { $query = $this->getMetadataQuery($key); $query->andWhere($query->expr()->gte('value', $query->createNamedParameter($value))); + return $this->getRows($query, $usersGroups); } @@ -421,6 +432,7 @@ public function findByUri($uri, $principalPrefix): ?string { if (!$user) { return null; } + $usersGroups = $this->groupManager->getUserGroupIds($user); if (str_starts_with($uri, 'mailto:')) { @@ -436,6 +448,7 @@ public function findByUri($uri, $principalPrefix): ?string { if (!$row) { return null; } + if (!$this->isAllowedToAccessResource($row, $usersGroups)) { return null; } @@ -463,6 +476,7 @@ public function findByUri($uri, $principalPrefix): ?string { if (!$row) { return null; } + if (!$this->isAllowedToAccessResource($row, $usersGroups)) { return null; } diff --git a/apps/dav/lib/CalDAV/Schedule/IMipPlugin.php b/apps/dav/lib/CalDAV/Schedule/IMipPlugin.php index e46cd039fd5ee..cf2ca55970873 100644 --- a/apps/dav/lib/CalDAV/Schedule/IMipPlugin.php +++ b/apps/dav/lib/CalDAV/Schedule/IMipPlugin.php @@ -97,6 +97,7 @@ public function beforeWriteContent($uri, INode $node, $data, $modified): void { if (!$node instanceof CalendarObject) { return; } + /** @var VCalendar $vCalendar */ $vCalendar = Reader::read($node->get()); $this->setVCalendar($vCalendar); @@ -115,6 +116,7 @@ public function schedule(Message $iTipMessage) { if (!$iTipMessage->scheduleStatus) { $iTipMessage->scheduleStatus = '1.0;We got the message, but it\'s not significant enough to warrant an email'; } + return; } @@ -135,8 +137,10 @@ public function schedule(Message $iTipMessage) { if (!$this->mailer->validateMailAddress($recipient)) { // Nothing to send if the recipient doesn't have a valid email address $iTipMessage->scheduleStatus = '5.0; EMail delivery failed'; + return; } + $recipientName = $iTipMessage->recipientName ? (string)$iTipMessage->recipientName : null; $newEvents = $iTipMessage->message; @@ -154,6 +158,7 @@ public function schedule(Message $iTipMessage) { if (empty($vEvent)) { $this->logger->warning('iTip message said the change was significant but comparison did not detect any updated VEvents'); $iTipMessage->scheduleStatus = '1.0;We got the message, but it\'s not significant enough to warrant an email'; + return; } @@ -167,16 +172,20 @@ public function schedule(Message $iTipMessage) { $uid = $vEvent->UID ?? 'no UID found'; $this->logger->debug('Could not find recipient ' . $recipient . ' as attendee for event with UID ' . $uid); $iTipMessage->scheduleStatus = '5.0; EMail delivery failed'; + return; } + // Don't send emails to things if ($this->imipService->isRoomOrResource($attendee)) { $this->logger->debug('No invitation sent as recipient is room or resource', [ 'attendee' => $recipient, ]); $iTipMessage->scheduleStatus = '1.0;We got the message, but it\'s not significant enough to warrant an email'; + return; } + $this->imipService->setL10n($attendee); // Build the sender name. @@ -272,6 +281,7 @@ public function schedule(Message $iTipMessage) { // retrieve appropriate service with the same address as sender $mailService = $this->mailManager->findServiceByAddress($user->getUID(), $sender); } + // evaluate if a mail service was found and has sending capabilities if ($mailService !== null && $mailService instanceof IMessageSend) { // construct mail message and set required parameters diff --git a/apps/dav/lib/CalDAV/Schedule/IMipService.php b/apps/dav/lib/CalDAV/Schedule/IMipService.php index a101cb05db302..5b9276a4cb809 100644 --- a/apps/dav/lib/CalDAV/Schedule/IMipService.php +++ b/apps/dav/lib/CalDAV/Schedule/IMipService.php @@ -79,6 +79,7 @@ public static function readPropertyWithDefault(VEvent $vevent, string $property, return $value; } } + return $default; } @@ -87,11 +88,13 @@ private function generateDiffString(VEvent $vevent, VEvent $oldVEvent, string $p if (!isset($vevent->$property)) { return $default; } + $newstring = $vevent->$property->getValue(); if (isset($oldVEvent->$property) && $oldVEvent->$property->getValue() !== $newstring) { $oldstring = $oldVEvent->$property->getValue(); return sprintf($strikethrough, $oldstring, $newstring); } + return $newstring; } @@ -102,6 +105,7 @@ private function generateLinkifiedDiffString(VEvent $vevent, VEvent $oldVEvent, if (!isset($vevent->$property)) { return $default; } + /** @var string|null $newString */ $newString = $vevent->$property->getValue(); $oldString = isset($oldVEvent->$property) ? $oldVEvent->$property->getValue() : null; @@ -112,6 +116,7 @@ private function generateLinkifiedDiffString(VEvent $vevent, VEvent $oldVEvent, $this->linkify($newString) ?? $newString ?? '' ); } + return $this->linkify($newString) ?? $newString; } @@ -122,6 +127,7 @@ private function linkify(?string $url): ?string { if ($url === null) { return null; } + if (!str_starts_with($url, 'http://') && !str_starts_with($url, 'https://')) { return null; } @@ -164,6 +170,7 @@ public function buildBodyData(VEvent $vEvent, ?VEvent $oldVEvent): array { $data['meeting_when_html'] = $oldMeetingWhen !== $data['meeting_when'] ? sprintf("%s
%s", $oldMeetingWhen, $data['meeting_when']) : $data['meeting_when']; } + // generate occuring next string if ($eventReaderCurrent->recurs()) { $data['meeting_occurring'] = $this->generateOccurringString($eventReaderCurrent); @@ -208,6 +215,7 @@ public function generateWhenStringSingular(EventReader $er): string { $startTime .= $er->startTimeZone() != $er->endTimeZone() ? ' (' . $er->startTimeZone()->getName() . ')' : ''; $endTime = $this->l10n->l('time', $er->endDateTime(), ['width' => 'short']) . ' (' . $er->endTimeZone()->getName() . ')'; } + // generate localized when string // TRANSLATORS // Indicates when a calendar event will happen, shown on invitation emails @@ -266,10 +274,12 @@ public function generateWhenStringRecurringDaily(EventReader $er): string { $startTime .= $er->startTimeZone() != $er->endTimeZone() ? ' (' . $er->startTimeZone()->getName() . ')' : ''; $endTime = $this->l10n->l('time', $er->endDateTime(), ['width' => 'short']) . ' (' . $er->endTimeZone()->getName() . ')'; } + // conclusion if ($er->recurringConcludes()) { $conclusion = $this->l10n->l('date', $er->recurringConcludesOn(), ['width' => 'long']); } + // generate localized when string // TRANSLATORS // Indicates when a calendar event will happen, shown on invitation emails @@ -320,10 +330,12 @@ public function generateWhenStringRecurringWeekly(EventReader $er): string { $startTime .= $er->startTimeZone() != $er->endTimeZone() ? ' (' . $er->startTimeZone()->getName() . ')' : ''; $endTime = $this->l10n->l('time', $er->endDateTime(), ['width' => 'short']) . ' (' . $er->endTimeZone()->getName() . ')'; } + // conclusion if ($er->recurringConcludes()) { $conclusion = $this->l10n->l('date', $er->recurringConcludesOn(), ['width' => 'long']); } + // generate localized when string // TRANSLATORS // Indicates when a calendar event will happen, shown on invitation emails @@ -373,16 +385,19 @@ public function generateWhenStringRecurringMonthly(EventReader $er): string { } else { $days = implode(', ', $er->recurringDaysOfMonth()); } + // time of the day if (!$er->entireDay()) { $startTime = $this->l10n->l('time', $er->startDateTime(), ['width' => 'short']); $startTime .= $er->startTimeZone() != $er->endTimeZone() ? ' (' . $er->startTimeZone()->getName() . ')' : ''; $endTime = $this->l10n->l('time', $er->endDateTime(), ['width' => 'short']) . ' (' . $er->endTimeZone()->getName() . ')'; } + // conclusion if ($er->recurringConcludes()) { $conclusion = $this->l10n->l('date', $er->recurringConcludesOn(), ['width' => 'long']); } + // generate localized when string // TRANSLATORS // Indicates when a calendar event will happen, shown on invitation emails @@ -441,16 +456,19 @@ public function generateWhenStringRecurringYearly(EventReader $er): string { } else { $days = $er->startDateTime()->format('jS'); } + // time of the day if (!$er->entireDay()) { $startTime = $this->l10n->l('time', $er->startDateTime(), ['width' => 'short']); $startTime .= $er->startTimeZone() != $er->endTimeZone() ? ' (' . $er->startTimeZone()->getName() . ')' : ''; $endTime = $this->l10n->l('time', $er->endDateTime(), ['width' => 'short']) . ' (' . $er->endTimeZone()->getName() . ')'; } + // conclusion if ($er->recurringConcludes()) { $conclusion = $this->l10n->l('date', $er->recurringConcludesOn(), ['width' => 'long']); } + // generate localized when string // TRANSLATORS // Indicates when a calendar event will happen, shown on invitation emails @@ -504,6 +522,7 @@ public function generateWhenStringRecurringFixed(EventReader $er): string { $startTime .= $er->startTimeZone() != $er->endTimeZone() ? ' (' . $er->startTimeZone()->getName() . ')' : ''; $endTime = $this->l10n->l('time', $er->endDateTime(), ['width' => 'short']) . ' (' . $er->endTimeZone()->getName() . ')'; } + // conclusion $conclusion = $this->l10n->l('date', $er->recurringConcludesOn(), ['width' => 'long']); // generate localized when string @@ -551,6 +570,7 @@ public function generateOccurringString(EventReader $er): string { $occurance3 = $this->l10n->l('date', $er->recurrenceDate(), ['width' => 'long']); } } + // generate localized when string // TRANSLATORS // Indicates when a calendar event will happen, shown on invitation emails @@ -601,6 +621,7 @@ public function buildCancelledBodyData(VEvent $vEvent): array { $data['meeting_url'] = isset($vEvent->URL) ? (string)$vEvent->URL : ''; $data['meeting_location_html'] = $newLocationHtml !== '' ? sprintf($strikethrough, $newLocationHtml) : ''; $data['meeting_location'] = $newLocation; + return $data; } @@ -626,6 +647,7 @@ public function getLastOccurrence(VCalendar $vObject) { $end = $it->getDtEnd(); $it->next(); } + return $end->getTimestamp(); } @@ -635,6 +657,7 @@ public function getLastOccurrence(VCalendar $vObject) { if (isset($component->DTEND)) { /** @var Property\ICalendar\DateTime $dtEnd */ $dtEnd = $component->DTEND; + return $dtEnd->getDateTime()->getTimeStamp(); } @@ -643,6 +666,7 @@ public function getLastOccurrence(VCalendar $vObject) { $endDate = clone $dtStart->getDateTime(); // $component->DTEND->getDateTime() returns DateTimeImmutable $endDate = $endDate->add(DateTimeParser::parse($component->DURATION->getValue())); + return $endDate->getTimestamp(); } @@ -651,6 +675,7 @@ public function getLastOccurrence(VCalendar $vObject) { // $component->DTSTART->getDateTime() returns DateTimeImmutable $endDate = clone $dtStart->getDateTime(); $endDate = $endDate->modify('+1 day'); + return $endDate->getTimestamp(); } @@ -686,6 +711,7 @@ public function getAttendeeRsvpOrReqForParticipant(?Property $attendee = null) { if (($rsvp instanceof Parameter) && (strcasecmp($rsvp->getValue(), 'TRUE') === 0)) { return true; } + $role = $attendee->offsetGet('ROLE'); // @see https://datatracker.ietf.org/doc/html/rfc5545#section-3.2.16 // Attendees without a role are assumed required and should receive an invitation link even if they have no RSVP set @@ -794,6 +820,7 @@ public function addAttendees(IEMailTemplate $template, VEvent $vevent) { $organizerText .= ' ✔︎'; } } + $template->addBodyListItem($organizerHTML, $this->l10n->t('Organizer:'), $this->getAbsoluteImagePath('caldav/organizer.png'), $organizerText, '', IMipPlugin::IMIP_INDENT); @@ -821,6 +848,7 @@ public function addAttendees(IEMailTemplate $template, VEvent $vevent) { $attendeeText .= ' ✔︎'; } } + $attendeesHTML[] = $attendeeHTML; $attendeesText[] = $attendeeText; } @@ -843,14 +871,17 @@ public function addBulletList(IEMailTemplate $template, VEvent $vevent, $data) { $template->addBodyListItem($data['meeting_when_html'] ?? $data['meeting_when'], $this->l10n->t('When:'), $this->getAbsoluteImagePath('caldav/time.png'), $data['meeting_when'], '', IMipPlugin::IMIP_INDENT); } + if ($data['meeting_location'] !== '') { $template->addBodyListItem($data['meeting_location_html'] ?? $data['meeting_location'], $this->l10n->t('Location:'), $this->getAbsoluteImagePath('caldav/location.png'), $data['meeting_location'], '', IMipPlugin::IMIP_INDENT); } + if ($data['meeting_url'] !== '') { $template->addBodyListItem($data['meeting_url_html'] ?? $data['meeting_url'], $this->l10n->t('Link:'), $this->getAbsoluteImagePath('caldav/link.png'), $data['meeting_url'], '', IMipPlugin::IMIP_INDENT); } + if (isset($data['meeting_occurring'])) { $template->addBodyListItem($data['meeting_occurring_html'] ?? $data['meeting_occurring'], $this->l10n->t('Occurring:'), $this->getAbsoluteImagePath('caldav/time.png'), $data['meeting_occurring'], '', IMipPlugin::IMIP_INDENT); @@ -882,6 +913,7 @@ public function getCurrentAttendee(Message $iTipMessage): ?Property { return $attendee; } } + return null; } @@ -956,6 +988,7 @@ public function getReplyingAttendee(Message $iTipMessage): ?Property { return $attendee; } } + return null; } @@ -964,11 +997,13 @@ public function isRoomOrResource(Property $attendee): bool { if (!$cuType instanceof Parameter) { return false; } + $type = $cuType->getValue() ?? 'INDIVIDUAL'; if (\in_array(strtoupper($type), ['RESOURCE', 'ROOM', 'UNKNOWN'], true)) { // Don't send emails to things return true; } + return false; } @@ -977,6 +1012,7 @@ public function minimizeInterval(\DateInterval $dateInterval): array { if ($dateInterval->invert == 1) { return [1, 'the past']; } + // evaluate interval parts and return smallest time period if ($dateInterval->y > 0) { $interval = $dateInterval->y; diff --git a/apps/dav/lib/CalDAV/Schedule/Plugin.php b/apps/dav/lib/CalDAV/Schedule/Plugin.php index 48f4cbf22ef0a..4d16c9bbc044e 100644 --- a/apps/dav/lib/CalDAV/Schedule/Plugin.php +++ b/apps/dav/lib/CalDAV/Schedule/Plugin.php @@ -175,6 +175,7 @@ public function calendarObjectChange(RequestInterface $request, ResponseInterfac $this->getAddressesForPrincipal($calendarNode->getPrincipalURI()) ); } + // determine if we are updating a calendar event if (!$isNew) { // retrieve current calendar event node @@ -186,6 +187,7 @@ public function calendarObjectChange(RequestInterface $request, ResponseInterfac } else { $currentObject = null; } + // process request $this->processICalendarChange($currentObject, $vCal, $addresses, [], $modified); @@ -237,6 +239,7 @@ public function scheduleLocalDelivery(ITip\Message $iTipMessage):void { $this->logger->debug('Message not delivered locally with status: ' . $iTipMessage->scheduleStatus); return; } + // We only care about request. reply and cancel are properly handled // by parent::scheduleLocalDelivery already if (strcasecmp($iTipMessage->method, 'REQUEST') !== 0) { @@ -429,6 +432,7 @@ public function propFindDefaultCalendarUrl(PropFind $propFind, INode $node) { $calendar->delete(); } } + $this->createCalendar($calendarHome, $principalUrl, $uri, $displayName); } } @@ -479,6 +483,7 @@ private function getCurrentAttendee(ITip\Message $iTipMessage):?Property { return $attendee; } } + return null; } @@ -493,6 +498,7 @@ private function getAttendeeRSVP(?Property $attendee = null):bool { return true; } } + // RFC 5545 3.2.17: default RSVP is false return false; } @@ -513,6 +519,7 @@ private function getDTEndFromVEvent(VEvent $vevent):Property\ICalendar\DateTime $endDateTime = $end->getDateTime(); $endDateTime = $endDateTime->add(DateTimeParser::parse($vevent->DURATION->getValue())); $end->setDateTime($endDateTime, $isFloating); + return $end; } @@ -523,6 +530,7 @@ private function getDTEndFromVEvent(VEvent $vevent):Property\ICalendar\DateTime $endDateTime = $end->getDateTime(); $endDateTime = $endDateTime->modify('+1 day'); $end->setDateTime($endDateTime, $isFloating); + return $end; } diff --git a/apps/dav/lib/CalDAV/Search/SearchPlugin.php b/apps/dav/lib/CalDAV/Search/SearchPlugin.php index fb55dec593c86..1dfaabecfaf89 100644 --- a/apps/dav/lib/CalDAV/Search/SearchPlugin.php +++ b/apps/dav/lib/CalDAV/Search/SearchPlugin.php @@ -77,6 +77,7 @@ public function report($reportName, $report, $path) { case '{' . self::NS_Nextcloud . '}calendar-search': $this->server->transactionType = 'report-nc-calendar-search'; $this->calendarSearch($report); + return false; } } diff --git a/apps/dav/lib/CalDAV/Search/Xml/Filter/ParamFilter.php b/apps/dav/lib/CalDAV/Search/Xml/Filter/ParamFilter.php index 3b03b63e9093a..e46226277570a 100644 --- a/apps/dav/lib/CalDAV/Search/Xml/Filter/ParamFilter.php +++ b/apps/dav/lib/CalDAV/Search/Xml/Filter/ParamFilter.php @@ -27,6 +27,7 @@ public static function xmlDeserialize(Reader $reader) { if (!is_string($property)) { throw new BadRequest('The {' . SearchPlugin::NS_Nextcloud . '}param-filter requires a valid property attribute'); } + if (!is_string($parameter)) { throw new BadRequest('The {' . SearchPlugin::NS_Nextcloud . '}param-filter requires a valid parameter attribute'); } diff --git a/apps/dav/lib/CalDAV/Search/Xml/Request/CalendarSearchReport.php b/apps/dav/lib/CalDAV/Search/Xml/Request/CalendarSearchReport.php index 639d0b3265561..a92bc09b2b95f 100644 --- a/apps/dav/lib/CalDAV/Search/Xml/Request/CalendarSearchReport.php +++ b/apps/dav/lib/CalDAV/Search/Xml/Request/CalendarSearchReport.php @@ -98,21 +98,25 @@ public static function xmlDeserialize(Reader $reader) { if (!isset($newProps['filters']['comps']) || !is_array($newProps['filters']['comps'])) { $newProps['filters']['comps'] = []; } + $newProps['filters']['comps'][] = $subElem['value']; } elseif ($subElem['name'] === '{' . SearchPlugin::NS_Nextcloud . '}prop-filter') { if (!isset($newProps['filters']['props']) || !is_array($newProps['filters']['props'])) { $newProps['filters']['props'] = []; } + $newProps['filters']['props'][] = $subElem['value']; } elseif ($subElem['name'] === '{' . SearchPlugin::NS_Nextcloud . '}param-filter') { if (!isset($newProps['filters']['params']) || !is_array($newProps['filters']['params'])) { $newProps['filters']['params'] = []; } + $newProps['filters']['params'][] = $subElem['value']; } elseif ($subElem['name'] === '{' . SearchPlugin::NS_Nextcloud . '}search-term') { $newProps['filters']['search-term'] = $subElem['value']; } } + break; case '{' . SearchPlugin::NS_Nextcloud . '}limit': $newProps['limit'] = $elem['value']; @@ -142,11 +146,11 @@ public static function xmlDeserialize(Reader $reader) { throw new BadRequest('At least one{' . SearchPlugin::NS_Nextcloud . '}prop-filter or {' . SearchPlugin::NS_Nextcloud . '}param-filter is required for this request'); } - $obj = new self(); foreach ($newProps as $key => $value) { $obj->$key = $value; } + return $obj; } } diff --git a/apps/dav/lib/CalDAV/Security/RateLimitingPlugin.php b/apps/dav/lib/CalDAV/Security/RateLimitingPlugin.php index 236b5c6d99de8..589befdce14a9 100644 --- a/apps/dav/lib/CalDAV/Security/RateLimitingPlugin.php +++ b/apps/dav/lib/CalDAV/Security/RateLimitingPlugin.php @@ -54,6 +54,7 @@ public function beforeBind(string $path): void { // We only care about authenticated users here return; } + $user = $this->userManager->get($this->userId); if ($user === null) { // We only care about authenticated users here @@ -78,6 +79,7 @@ public function beforeBind(string $path): void { if ($calendarLimit === -1) { return; } + $numCalendars = $this->calDavBackend->getCalendarsForUserCount('principals/users/' . $user->getUID()); $numSubscriptions = $this->calDavBackend->getSubscriptionsForUserCount('principals/users/' . $user->getUID()); diff --git a/apps/dav/lib/CalDAV/Sharing/Backend.php b/apps/dav/lib/CalDAV/Sharing/Backend.php index 40fb7e03e5f2f..fc5d65b5994b4 100644 --- a/apps/dav/lib/CalDAV/Sharing/Backend.php +++ b/apps/dav/lib/CalDAV/Sharing/Backend.php @@ -17,7 +17,8 @@ class Backend extends SharingBackend { - public function __construct(private IUserManager $userManager, + public function __construct( + private IUserManager $userManager, private IGroupManager $groupManager, private Principal $principalBackend, private ICacheFactory $cacheFactory, diff --git a/apps/dav/lib/CalDAV/Sharing/Service.php b/apps/dav/lib/CalDAV/Sharing/Service.php index 7867b8d1adb9f..4f0554f09bd22 100644 --- a/apps/dav/lib/CalDAV/Sharing/Service.php +++ b/apps/dav/lib/CalDAV/Sharing/Service.php @@ -13,7 +13,9 @@ class Service extends SharingService { protected string $resourceType = 'calendar'; - public function __construct(protected SharingMapper $mapper) { + public function __construct( + protected SharingMapper $mapper, + ) { parent::__construct($mapper); } } diff --git a/apps/dav/lib/CalDAV/Status/StatusService.php b/apps/dav/lib/CalDAV/Status/StatusService.php index 56eec5007b8a3..a0449c78fc11c 100644 --- a/apps/dav/lib/CalDAV/Status/StatusService.php +++ b/apps/dav/lib/CalDAV/Status/StatusService.php @@ -27,13 +27,15 @@ class StatusService { private ICache $cache; - public function __construct(private ITimeFactory $timeFactory, + public function __construct( + private ITimeFactory $timeFactory, private IManager $calendarManager, private IUserManager $userManager, private UserStatusService $userStatusService, private IAvailabilityCoordinator $availabilityCoordinator, private ICacheFactory $cacheFactory, - private LoggerInterface $logger) { + private LoggerInterface $logger, + ) { $this->cache = $cacheFactory->createLocal('CalendarStatusService'); } @@ -65,10 +67,13 @@ public function processCalendarStatus(string $userId): void { // We cannot safely restore the status as we don't know which one is valid at this point // So let's silently log this one and exit $this->logger->debug('Unique constraint violation for live user status', ['exception' => $e]); + return; } } + $this->logger->debug('No calendar events found for status check', ['user' => $userId]); + return; } @@ -86,6 +91,7 @@ public function processCalendarStatus(string $userId): void { || ($currentStatus !== null && $currentStatus->getStatus() === IUserStatus::INVISIBLE)) { // We don't overwrite the call status, DND status or Invisible status $this->logger->debug('Higher priority status detected, skipping calendar status change', ['user' => $userId]); + return; } @@ -94,10 +100,12 @@ public function processCalendarStatus(string $userId): void { if (empty($calendarEvent['objects'])) { return false; } + $component = $calendarEvent['objects'][0]; if (isset($component['X-NEXTCLOUD-OUT-OF-OFFICE'])) { return false; } + if (isset($component['DTSTART']) && $userStatusTimestamp !== null) { /** @var DateTimeImmutable $dateTime */ $dateTime = $component['DTSTART'][0]; @@ -105,10 +113,12 @@ public function processCalendarStatus(string $userId): void { return false; } } + // Ignore events that are transparent if (isset($component['TRANSP']) && strcasecmp($component['TRANSP'][0], 'TRANSPARENT') === 0) { return false; } + return true; }); @@ -122,10 +132,13 @@ public function processCalendarStatus(string $userId): void { // We cannot safely restore the status as we don't know which one is valid at this point // So let's silently log this one and exit $this->logger->debug('Unique constraint violation for live user status', ['exception' => $e]); + return; } } + $this->logger->debug('No status relevant events found, skipping calendar status change', ['user' => $userId]); + return; } @@ -165,6 +178,7 @@ private function getCalendarEvents(User $user): array { // ignore it for free-busy purposes. continue; } + $query->addSearchCalendar($calendarObject->getUri()); } @@ -176,6 +190,7 @@ private function getCalendarEvents(User $user): array { // Query the next hour $query->setTimerangeStart($dtStart); $query->setTimerangeEnd($dtEnd); + return $this->calendarManager->searchForPrincipal($query); } diff --git a/apps/dav/lib/CalDAV/TimezoneService.php b/apps/dav/lib/CalDAV/TimezoneService.php index 93f19be1b55d1..3a8f33bca8464 100644 --- a/apps/dav/lib/CalDAV/TimezoneService.php +++ b/apps/dav/lib/CalDAV/TimezoneService.php @@ -20,9 +20,11 @@ class TimezoneService { - public function __construct(private IConfig $config, + public function __construct( + private IConfig $config, private PropertyMapper $propertyMapper, - private IManager $calendarManager) { + private IManager $calendarManager, + ) { } public function getUserTimezone(string $userId): ?string { @@ -57,9 +59,11 @@ public function getUserTimezone(string $userId): ?string { if ($acc !== null) { return $acc; } + if ($calendar->getUri() === $uri && !$calendar->isDeleted() && $calendar instanceof CalendarImpl) { return $calendar->getSchedulingTimezone(); } + return null; }); if ($personalCalendarTimezone !== null) { @@ -73,14 +77,17 @@ public function getUserTimezone(string $userId): ?string { if ($acc !== null) { return $acc; } + if (!$calendar->isDeleted() && $calendar instanceof CalendarImpl) { return $calendar->getSchedulingTimezone(); } + return null; }); if ($firstTimezone !== null) { return $firstTimezone->getTimeZone()->getName(); } + return null; } diff --git a/apps/dav/lib/CalDAV/Trashbin/DeletedCalendarObject.php b/apps/dav/lib/CalDAV/Trashbin/DeletedCalendarObject.php index 1326a42399b3f..06f4a153bf9b1 100644 --- a/apps/dav/lib/CalDAV/Trashbin/DeletedCalendarObject.php +++ b/apps/dav/lib/CalDAV/Trashbin/DeletedCalendarObject.php @@ -72,7 +72,7 @@ public function get() { public function getContentType() { $mime = 'text/calendar; charset=utf-8'; if (isset($this->objectData['component']) && $this->objectData['component']) { - $mime .= '; component='.$this->objectData['component']; + $mime .= '; component=' . $this->objectData['component']; } return $mime; diff --git a/apps/dav/lib/CalDAV/Trashbin/DeletedCalendarObjectsCollection.php b/apps/dav/lib/CalDAV/Trashbin/DeletedCalendarObjectsCollection.php index c9db04f7d3201..f4d923ca1e10f 100644 --- a/apps/dav/lib/CalDAV/Trashbin/DeletedCalendarObjectsCollection.php +++ b/apps/dav/lib/CalDAV/Trashbin/DeletedCalendarObjectsCollection.php @@ -58,6 +58,7 @@ public function getChild($name) { if ($data === null) { throw new NotFound(); } + if (!isset($data['deleted_at'])) { throw new BadRequest('The calendar object you\'re trying to restore is not marked as deleted'); } diff --git a/apps/dav/lib/CalDAV/Trashbin/Plugin.php b/apps/dav/lib/CalDAV/Trashbin/Plugin.php index f3c8342c4759c..70f4d41bbd219 100644 --- a/apps/dav/lib/CalDAV/Trashbin/Plugin.php +++ b/apps/dav/lib/CalDAV/Trashbin/Plugin.php @@ -99,6 +99,7 @@ private function propFind( return $node->getCalendarUri(); }); } + if ($node instanceof TrashbinHome) { $propFind->handle(self::PROPERTY_RETENTION_DURATION, function () use ($node) { return $this->retentionService->getDuration(); diff --git a/apps/dav/lib/CalDAV/UpcomingEvent.php b/apps/dav/lib/CalDAV/UpcomingEvent.php index 26760ffedd5c7..e8b604f460a26 100644 --- a/apps/dav/lib/CalDAV/UpcomingEvent.php +++ b/apps/dav/lib/CalDAV/UpcomingEvent.php @@ -13,13 +13,15 @@ use OCA\DAV\ResponseDefinitions; class UpcomingEvent implements JsonSerializable { - public function __construct(private string $uri, + public function __construct( + private string $uri, private ?int $recurrenceId, private string $calendarUri, private ?int $start, private ?string $summary, private ?string $location, - private ?string $calendarAppUrl) { + private ?string $calendarAppUrl, + ) { } public function getUri(): string { diff --git a/apps/dav/lib/CalDAV/UpcomingEventsService.php b/apps/dav/lib/CalDAV/UpcomingEventsService.php index 04ab1be19b462..016daa80640f5 100644 --- a/apps/dav/lib/CalDAV/UpcomingEventsService.php +++ b/apps/dav/lib/CalDAV/UpcomingEventsService.php @@ -17,11 +17,13 @@ use function array_map; class UpcomingEventsService { - public function __construct(private IManager $calendarManager, + public function __construct( + private IManager $calendarManager, private ITimeFactory $timeFactory, private IUserManager $userManager, private IAppManager $appManager, - private IURLGenerator $urlGenerator) { + private IURLGenerator $urlGenerator, + ) { } /** @@ -33,6 +35,7 @@ public function getEvents(string $userId, ?string $location = null): array { $searchQuery->addSearchProperty('LOCATION'); $searchQuery->setSearchPattern($location); } + $searchQuery->addType('VEVENT'); $searchQuery->setLimit(3); $now = $this->timeFactory->now(); diff --git a/apps/dav/lib/CalDAV/Validation/CalDavValidatePlugin.php b/apps/dav/lib/CalDAV/Validation/CalDavValidatePlugin.php index 4a9721d00eca0..33beefa04b2db 100644 --- a/apps/dav/lib/CalDAV/Validation/CalDavValidatePlugin.php +++ b/apps/dav/lib/CalDAV/Validation/CalDavValidatePlugin.php @@ -19,7 +19,7 @@ class CalDavValidatePlugin extends ServerPlugin { public function __construct( - private IAppConfig $config + private IAppConfig $config, ) { } @@ -33,6 +33,7 @@ public function beforePut(RequestInterface $request, ResponseInterface $response if ((int)$request->getRawServerValue('CONTENT_LENGTH') > $eventSizeLimit) { throw new Forbidden("VEvent or VTodo object exceeds $eventSizeLimit bytes"); } + // all tests passed return true return true; } diff --git a/apps/dav/lib/CalDAV/WebcalCaching/Connection.php b/apps/dav/lib/CalDAV/WebcalCaching/Connection.php index 9d626f2db6dd6..e3238ed640fe6 100644 --- a/apps/dav/lib/CalDAV/WebcalCaching/Connection.php +++ b/apps/dav/lib/CalDAV/WebcalCaching/Connection.php @@ -21,9 +21,11 @@ use Sabre\VObject\Reader; class Connection { - public function __construct(private IClientService $clientService, + public function __construct( + private IClientService $clientService, private IAppConfig $config, - private LoggerInterface $logger) { + private LoggerInterface $logger, + ) { } /** @@ -49,6 +51,7 @@ public function queryWebcalFeed(array $subscription, array &$mutations): ?string $latestLocation = $response->getHeader('Location'); } } + return $response; })); @@ -92,8 +95,10 @@ public function queryWebcalFeed(array $subscription, array &$mutations): ?string } catch (Exception $ex) { // In case of a parsing error return null $this->logger->warning("Subscription $subscriptionId could not be parsed", ['exception' => $ex]); + return null; } + return $jCalendar->serialize(); case 'application/calendar+xml': @@ -102,8 +107,10 @@ public function queryWebcalFeed(array $subscription, array &$mutations): ?string } catch (Exception $ex) { // In case of a parsing error return null $this->logger->warning("Subscription $subscriptionId could not be parsed", ['exception' => $ex]); + return null; } + return $xCalendar->serialize(); case 'text/calendar': @@ -113,8 +120,10 @@ public function queryWebcalFeed(array $subscription, array &$mutations): ?string } catch (Exception $ex) { // In case of a parsing error return null $this->logger->warning("Subscription $subscriptionId could not be parsed", ['exception' => $ex]); + return null; } + return $vCalendar->serialize(); } } catch (LocalServerException $ex) { diff --git a/apps/dav/lib/CalDAV/WebcalCaching/RefreshWebcalService.php b/apps/dav/lib/CalDAV/WebcalCaching/RefreshWebcalService.php index 47fd785d632ca..b2e4de11d2f66 100644 --- a/apps/dav/lib/CalDAV/WebcalCaching/RefreshWebcalService.php +++ b/apps/dav/lib/CalDAV/WebcalCaching/RefreshWebcalService.php @@ -30,10 +30,12 @@ class RefreshWebcalService { public const STRIP_ATTACHMENTS = '{http://calendarserver.org/ns/}subscribed-strip-attachments'; public const STRIP_TODOS = '{http://calendarserver.org/ns/}subscribed-strip-todos'; - public function __construct(private CalDavBackend $calDavBackend, + public function __construct( + private CalDavBackend $calDavBackend, private LoggerInterface $logger, private Connection $connection, - private ITimeFactory $time) { + private ITimeFactory $time, + ) { } public function refreshSubscription(string $principalUri, string $uri) { @@ -54,7 +56,6 @@ public function refreshSubscription(string $principalUri, string $uri) { } } - $webcalData = $this->connection->queryWebcalFeed($subscription, $mutations); if (!$webcalData) { return; @@ -84,6 +85,7 @@ public function refreshSubscription(string $principalUri, string $uri) { if ($stripAlarms) { unset($component->{'VALARM'}); } + if ($stripAttachments) { unset($component->{'ATTACH'}); } @@ -194,6 +196,7 @@ private function checkWebcalDataForRefreshRate(array $subscription, string $webc if (isset($vCalendar->{'X-PUBLISHED-TTL'})) { $newRefreshRate = $vCalendar->{'X-PUBLISHED-TTL'}->getValue(); } + if (isset($vCalendar->{'REFRESH-INTERVAL'})) { $newRefreshRate = $vCalendar->{'REFRESH-INTERVAL'}->getValue(); } diff --git a/apps/dav/lib/Capabilities.php b/apps/dav/lib/Capabilities.php index 1de64e16e4101..7503b882993ce 100644 --- a/apps/dav/lib/Capabilities.php +++ b/apps/dav/lib/Capabilities.php @@ -27,6 +27,7 @@ public function getCapabilities() { if ($this->config->getSystemValueBool('bulkupload.enabled', true)) { $capabilities['dav']['bulkupload'] = '1.0'; } + return $capabilities; } } diff --git a/apps/dav/lib/CardDAV/Activity/Backend.php b/apps/dav/lib/CardDAV/Activity/Backend.php index d2466e34f9d6b..3a850aa6583f2 100644 --- a/apps/dav/lib/CardDAV/Activity/Backend.php +++ b/apps/dav/lib/CardDAV/Activity/Backend.php @@ -180,6 +180,7 @@ public function onAddressbookUpdateShares(array $addressbookData, array $shares, if ($parts[0] !== 'principal') { continue; } + $principal = explode('/', $parts[1]); if ($principal[1] === 'users') { @@ -257,6 +258,7 @@ public function onAddressbookUpdateShares(array $addressbookData, array $shares, if ($parts[0] !== 'principal') { continue; } + $principal = explode('/', $parts[1]); if ($principal[1] === 'users') { diff --git a/apps/dav/lib/CardDAV/Activity/Provider/Base.php b/apps/dav/lib/CardDAV/Activity/Provider/Base.php index 95b4d52b24254..2a56dc81260db 100644 --- a/apps/dav/lib/CardDAV/Activity/Provider/Base.php +++ b/apps/dav/lib/CardDAV/Activity/Provider/Base.php @@ -100,6 +100,7 @@ protected function getGroupDisplayName(string $gid): string { if ($group instanceof IGroup) { return $group->getDisplayName(); } + return $gid; } } diff --git a/apps/dav/lib/CardDAV/Activity/Provider/Card.php b/apps/dav/lib/CardDAV/Activity/Provider/Card.php index aa821f592d9ce..b981ac87febb9 100644 --- a/apps/dav/lib/CardDAV/Activity/Provider/Card.php +++ b/apps/dav/lib/CardDAV/Activity/Provider/Card.php @@ -90,6 +90,7 @@ public function parse($language, IEvent $event, ?IEvent $previousEvent = null): $this->setSubjects($event, $subject, $parsedParameters); $event = $this->eventMerger->mergeEvents('card', $event, $previousEvent); + return $event; } diff --git a/apps/dav/lib/CardDAV/AddressBook.php b/apps/dav/lib/CardDAV/AddressBook.php index 2ec645f04d231..82e26a52479bd 100644 --- a/apps/dav/lib/CardDAV/AddressBook.php +++ b/apps/dav/lib/CardDAV/AddressBook.php @@ -65,6 +65,7 @@ public function updateShares(array $add, array $remove): void { if ($this->isShared()) { throw new Forbidden(); } + $this->carddavBackend->updateShares($this, $add, $remove); } @@ -83,6 +84,7 @@ public function getShares(): array { if ($this->isShared()) { return []; } + return $this->carddavBackend->getShares($this->getResourceId()); } @@ -138,6 +140,7 @@ public function getACL() { $acl = $this->carddavBackend->applyShareAcl($this->getResourceId(), $acl); $allowedPrincipals = [$this->getOwner(), parent::getOwner(), 'principals/system/system', '{DAV:}authenticated']; + return array_filter($acl, function ($rule) use ($allowedPrincipals) { return \in_array($rule['principal'], $allowedPrincipals, true); }); @@ -152,7 +155,9 @@ public function getChild($name) { if (!$obj) { throw new NotFound('Card not found'); } + $obj['acl'] = $this->getChildACL(); + return new Card($this->carddavBackend, $this->addressBookInfo, $obj); } @@ -186,6 +191,7 @@ public function getOwner(): ?string { if (isset($this->addressBookInfo['{http://owncloud.org/ns}owner-principal'])) { return $this->addressBookInfo['{http://owncloud.org/ns}owner-principal']; } + return parent::getOwner(); } @@ -203,8 +209,10 @@ public function delete() { $this->carddavBackend->updateShares($this, [], [ $principal ]); + return; } + parent::delete(); } @@ -230,6 +238,7 @@ private function canWrite(): bool { if (isset($this->addressBookInfo['{http://owncloud.org/ns}read-only'])) { return !$this->addressBookInfo['{http://owncloud.org/ns}read-only']; } + return true; } @@ -254,6 +263,7 @@ public function moveInto($targetName, $sourcePath, INode $sourceNode) { } catch (Exception $e) { // Avoid injecting LoggerInterface everywhere Server::get(LoggerInterface::class)->error('Could not move calendar object: ' . $e->getMessage(), ['exception' => $e]); + return false; } } diff --git a/apps/dav/lib/CardDAV/AddressBookImpl.php b/apps/dav/lib/CardDAV/AddressBookImpl.php index 5dfb22141e89a..926de3a2e7fa6 100644 --- a/apps/dav/lib/CardDAV/AddressBookImpl.php +++ b/apps/dav/lib/CardDAV/AddressBookImpl.php @@ -135,11 +135,13 @@ public function createOrUpdate($properties) { $entry['value'] = stripslashes($entry['value']); $entry['value'] = explode(';', $entry['value']); } + $property = $vCard->createProperty($key, $entry['value']); if (isset($entry['type'])) { $property->add('TYPE', $entry['type']); } } + $vCard->add($property); } } elseif ($key !== 'URI') { @@ -231,6 +233,7 @@ protected function getUid() { protected function createEmptyVCard($uid) { $vCard = new VCard(); $vCard->UID = $uid; + return $vCard; } @@ -281,6 +284,7 @@ protected function vCard2Array($uri, VCard $vCard, $withTypes = false) { if ($this->isSystemAddressBook()) { $result['isLocalSystemBook'] = true; } + return $result; } @@ -296,6 +300,7 @@ protected function getTypeFromProperty(Property $property) { if (isset($parameters['TYPE'])) { /** @var \Sabre\VObject\Parameter $type */ $type = $parameters['TYPE']; + return $type->getValue(); } diff --git a/apps/dav/lib/CardDAV/AddressBookRoot.php b/apps/dav/lib/CardDAV/AddressBookRoot.php index ec6fa6c9fe14b..3b106d55752af 100644 --- a/apps/dav/lib/CardDAV/AddressBookRoot.php +++ b/apps/dav/lib/CardDAV/AddressBookRoot.php @@ -54,6 +54,7 @@ public function getName() { if ($this->principalPrefix === 'principals') { return parent::getName(); } + // Grabbing all the components of the principal path. $parts = explode('/', $this->principalPrefix); diff --git a/apps/dav/lib/CardDAV/Card.php b/apps/dav/lib/CardDAV/Card.php index 8cd4fd7e5ee59..900461049defd 100644 --- a/apps/dav/lib/CardDAV/Card.php +++ b/apps/dav/lib/CardDAV/Card.php @@ -37,6 +37,7 @@ public function getOwner(): ?string { if (isset($this->addressBookInfo['{http://owncloud.org/ns}owner-principal'])) { return $this->addressBookInfo['{http://owncloud.org/ns}owner-principal']; } + return parent::getOwner(); } } diff --git a/apps/dav/lib/CardDAV/CardDavBackend.php b/apps/dav/lib/CardDAV/CardDavBackend.php index 9dee61638a5ab..f21a7c64b315f 100644 --- a/apps/dav/lib/CardDAV/CardDavBackend.php +++ b/apps/dav/lib/CardDAV/CardDavBackend.php @@ -78,6 +78,7 @@ public function getAddressBooksForUserCount($principalUri) { $result = $query->executeQuery(); $column = (int)$result->fetchOne(); $result->closeCursor(); + return $column; } @@ -123,6 +124,7 @@ public function getAddressBooksForUser($principalUri) { $this->addOwnerPrincipal($addressBooks[$row['id']]); } + $result->closeCursor(); // query for shared addressbooks @@ -160,6 +162,7 @@ public function getAddressBooksForUser($principalUri) { // New share can not have more permissions then the old one. continue; } + if (isset($addressBooks[$row['id']][$readOnlyPropertyName]) && $addressBooks[$row['id']][$readOnlyPropertyName] === 0) { // Old share is already read-write, no more permissions can be gained @@ -185,6 +188,7 @@ public function getAddressBooksForUser($principalUri) { $this->addOwnerPrincipal($addressBooks[$row['id']]); } + $result->closeCursor(); return array_values($addressBooks); @@ -214,6 +218,7 @@ public function getUsersOwnAddressBooks($principalUri) { $this->addOwnerPrincipal($addressBooks[$row['id']]); } + $result->closeCursor(); return array_values($addressBooks); @@ -320,6 +325,7 @@ public function updateAddressBook($addressBookId, \Sabre\DAV\PropPatch $propPatc break; } } + [$addressBookRow, $shares] = $this->atomic(function () use ($addressBookId, $updates) { $query = $this->db->getQueryBuilder(); $query->update('addressbooks'); @@ -327,6 +333,7 @@ public function updateAddressBook($addressBookId, \Sabre\DAV\PropPatch $propPatc foreach ($updates as $key => $value) { $query->set($key, $query->createNamedParameter($value)); } + $query->where($query->expr()->eq('id', $query->createNamedParameter($addressBookId))) ->executeStatement(); @@ -334,6 +341,7 @@ public function updateAddressBook($addressBookId, \Sabre\DAV\PropPatch $propPatc $addressBookRow = $this->getAddressBookById((int)$addressBookId); $shares = $this->getShares((int)$addressBookId); + return [$addressBookRow, $shares]; }, $this->db); @@ -398,6 +406,7 @@ public function createAddressBook($principalUri, $url, array $properties) { ->execute(); $addressBookId = $query->getLastInsertId(); + return [ $addressBookId, $this->getAddressBookById($addressBookId), @@ -491,6 +500,7 @@ public function getCards($addressbookId) { $cards[] = $row; } + $result->closeCursor(); return $cards; @@ -521,6 +531,7 @@ public function getCard($addressBookId, $cardUri) { if (!$row) { return false; } + $row['etag'] = '"' . $row['etag'] . '"'; $modified = false; @@ -573,8 +584,10 @@ public function getMultipleCards($addressBookId, array $uris) { $cards[] = $row; } + $result->closeCursor(); } + return $cards; } @@ -607,6 +620,7 @@ public function getMultipleCards($addressBookId, array $uris) { public function createCard($addressBookId, $cardUri, $cardData, bool $checkAlreadyExists = true) { $etag = md5($cardData); $uid = $this->getUID($cardData); + return $this->atomic(function () use ($addressBookId, $cardUri, $cardData, $checkAlreadyExists, $etag, $uid) { if ($checkAlreadyExists) { $q = $this->db->getQueryBuilder(); @@ -708,6 +722,7 @@ public function updateCard($addressBookId, $cardUri, $cardData) { $shares = $this->getShares($addressBookId); $objectRow = $this->getCard($addressBookId, $cardUri); $this->dispatcher->dispatchTyped(new CardUpdatedEvent($addressBookId, $addressBookData, $shares, $objectRow)); + return '"' . $etag . '"'; }, $this->db); } @@ -751,6 +766,7 @@ public function moveCard(int $sourceAddressBookId, int $targetAddressBookId, str $targetShares = $this->getShares($targetAddressBookId); $sourceAddressBookRow = $this->getAddressBookById($sourceAddressBookId); $this->dispatcher->dispatchTyped(new CardMovedEvent($sourceAddressBookId, $sourceAddressBookRow, $targetAddressBookId, $targetAddressBookRow, $sourceShares, $targetShares, $card)); + return true; }, $this->db); } @@ -773,6 +789,7 @@ public function deleteCard($addressBookId, $cardUri) { } catch (\InvalidArgumentException $e) { $cardId = null; } + $query = $this->db->getQueryBuilder(); $ret = $query->delete($this->dbCardsTable) ->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId))) @@ -786,6 +803,7 @@ public function deleteCard($addressBookId, $cardUri) { $this->dispatcher->dispatchTyped(new CardDeletedEvent($addressBookId, $addressBookData, $shares, $objectRow)); $this->purgeProperties($addressBookId, $cardId); } + return true; } @@ -899,6 +917,7 @@ public function getChangesForAddressBook($addressBookId, $syncToken, $syncLevel, while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { $changes[$row['uri']] = $row['operation']; } + $stmt->closeCursor(); foreach ($changes as $uri => $operation) { @@ -926,6 +945,7 @@ public function getChangesForAddressBook($addressBookId, $syncToken, $syncLevel, $result['added'] = $stmt->fetchAll(\PDO::FETCH_COLUMN); $stmt->closeCursor(); } + return $result; }, $this->db); } @@ -1000,12 +1020,14 @@ private function readBlob($cardData, &$modified = false) { if (str_starts_with($line, ' ')) { continue; } + // No leading space means this is a new property $removingPhoto = false; } $cardDataFiltered[] = $line; } + return implode("\r\n", $cardDataFiltered); } @@ -1092,6 +1114,7 @@ private function searchByAddressBookIds(array $addressBookIds, if (empty($addressBookIds)) { return []; } + $escapePattern = !\array_key_exists('escape_like_param', $options) || $options['escape_like_param'] !== false; $useWildcards = !\array_key_exists('wildcard', $options) || $options['wildcard'] !== false; @@ -1132,9 +1155,11 @@ private function searchByAddressBookIds(array $addressBookIds, $query2->andWhere($query2->expr()->ilike('cp.value', $query2->createNamedParameter('%' . $this->db->escapeLikeParameter($pattern) . '%'))); } } + if (isset($options['limit'])) { $query2->setMaxResults($options['limit']); } + if (isset($options['offset'])) { $query2->setFirstResult($options['offset']); } @@ -1142,6 +1167,7 @@ private function searchByAddressBookIds(array $addressBookIds, if (isset($options['person'])) { $query2->andWhere($query2->expr()->ilike('cp.value', $query2->createNamedParameter('%' . $this->db->escapeLikeParameter($options['person']) . '%'))); } + if (isset($options['since']) || isset($options['until'])) { $query2->join('cp', $this->dbCardsPropertiesTable, 'cp_bday', 'cp.cardid = cp_bday.cardid'); $query2->andWhere($query2->expr()->eq('cp_bday.name', $query2->createNamedParameter('BDAY'))); @@ -1192,6 +1218,7 @@ private function searchByAddressBookIds(array $addressBookIds, if ($modified) { $array['size'] = strlen($array['carddata']); } + return $array; }, $cards); } @@ -1314,6 +1341,7 @@ protected function updateProperties($addressBookId, $cardUri, $vCardSerialized) if (!in_array($property->name, self::$indexProperties)) { continue; } + $preferred = 0; foreach ($property->parameters as $parameter) { if ($parameter->name === 'TYPE' && strtoupper($parameter->getValue()) === 'PREF') { @@ -1321,6 +1349,7 @@ protected function updateProperties($addressBookId, $cardUri, $vCardSerialized) break; } } + $query->setParameter('name', $property->name); $query->setParameter('value', mb_strcut($property->getValue(), 0, 254)); $query->setParameter('preferred', $preferred); @@ -1410,6 +1439,7 @@ public function pruneOutdatedSyncTokens(int $keep, int $retention): int { $query->expr()->lte('id', $query->createNamedParameter($maxId - $keep, IQueryBuilder::PARAM_INT), IQueryBuilder::PARAM_INT), $query->expr()->lte('created_at', $query->createNamedParameter($retention)), ); + return $query->executeStatement(); } @@ -1419,8 +1449,10 @@ private function convertPrincipal(string $principalUri, bool $toV2): string { if ($toV2 === true) { return "principals/users/$name"; } + return "principals/$name"; } + return $principalUri; } @@ -1453,9 +1485,11 @@ private function getUID(string $cardData): string { $uid = $vCard->UID->getValue(); return $uid; } + // should already be handled, but just in case throw new BadRequest('vCards on CardDAV servers MUST have a UID property'); } + // should already be handled, but just in case throw new BadRequest('vCard can not be empty'); } diff --git a/apps/dav/lib/CardDAV/Converter.php b/apps/dav/lib/CardDAV/Converter.php index a0a25716ed8fd..d9bba956ba44d 100644 --- a/apps/dav/lib/CardDAV/Converter.php +++ b/apps/dav/lib/CardDAV/Converter.php @@ -71,6 +71,7 @@ public function createCardFromUser(IUser $user): ?VCard { if ($image !== null) { $vCard->add('PHOTO', $image->data(), ['ENCODING' => 'b', 'TYPE' => $image->mimeType(), ['X-NC-SCOPE' => $scope]]); } + break; case IAccountManager::COLLECTION_EMAIL: case IAccountManager::PROPERTY_EMAIL: @@ -93,6 +94,7 @@ public function createCardFromUser(IUser $user): ?VCard { ) ); } + break; case IAccountManager::PROPERTY_PHONE: $vCard->add(new Text($vCard, 'TEL', $property->getValue(), ['TYPE' => 'VOICE', 'X-NC-SCOPE' => $scope])); @@ -135,6 +137,7 @@ public function createCardFromUser(IUser $user): ?VCard { ]); break; } + $dateProperty = new Date($vCard, 'BDAY', null, ['X-NC-SCOPE' => $scope]); $dateProperty->setDateTime($birthdate); $vCard->add($dateProperty); @@ -159,6 +162,7 @@ public function createCardFromUser(IUser $user): ?VCard { if ($publish && !empty($cloudId)) { $vCard->add(new Text($vCard, 'CLOUD', $cloudId)); $vCard->validate(); + return $vCard; } diff --git a/apps/dav/lib/CardDAV/PhotoCache.php b/apps/dav/lib/CardDAV/PhotoCache.php index 00989386df767..0f214eaf1689e 100644 --- a/apps/dav/lib/CardDAV/PhotoCache.php +++ b/apps/dav/lib/CardDAV/PhotoCache.php @@ -179,6 +179,7 @@ private function getPhoto(Card $node) { 'exception' => $e ]); } + return false; } @@ -202,9 +203,11 @@ public function getPhotoFromVObject(Document $vObject) { if ($parsed['scheme'] !== 'data') { return false; } + if (substr_count($parsed['path'], ';') === 1) { [$type] = explode(';', $parsed['path']); } + $val = file_get_contents($val); } else { // get type if binary data @@ -224,6 +227,7 @@ public function getPhotoFromVObject(Document $vObject) { 'exception' => $e ]); } + return false; } @@ -248,6 +252,7 @@ private function getBinaryType(Binary $photo) { return 'image/' . strtolower($type); } } + return ''; } diff --git a/apps/dav/lib/CardDAV/Plugin.php b/apps/dav/lib/CardDAV/Plugin.php index 0ec10306cebb5..b8b61d8647282 100644 --- a/apps/dav/lib/CardDAV/Plugin.php +++ b/apps/dav/lib/CardDAV/Plugin.php @@ -29,10 +29,12 @@ protected function getAddressbookHomeForPrincipal($principal) { [, $principalId] = \Sabre\Uri\split($principal); return self::ADDRESSBOOK_ROOT . '/users/' . $principalId; } + if (strrpos($principal, 'principals/groups', -strlen($principal)) !== false) { [, $principalId] = \Sabre\Uri\split($principal); return self::ADDRESSBOOK_ROOT . '/groups/' . $principalId; } + if (strrpos($principal, 'principals/system', -strlen($principal)) !== false) { [, $principalId] = \Sabre\Uri\split($principal); return self::ADDRESSBOOK_ROOT . '/system/' . $principalId; diff --git a/apps/dav/lib/CardDAV/Security/CardDavRateLimitingPlugin.php b/apps/dav/lib/CardDAV/Security/CardDavRateLimitingPlugin.php index 7f0f1e2862325..0c346044c661d 100644 --- a/apps/dav/lib/CardDAV/Security/CardDavRateLimitingPlugin.php +++ b/apps/dav/lib/CardDAV/Security/CardDavRateLimitingPlugin.php @@ -25,12 +25,14 @@ class CardDavRateLimitingPlugin extends ServerPlugin { private ?string $userId; - public function __construct(private Limiter $limiter, + public function __construct( + private Limiter $limiter, private IUserManager $userManager, private CardDavBackend $cardDavBackend, private LoggerInterface $logger, private IAppConfig $config, - ?string $userId) { + ?string $userId, + ) { $this->limiter = $limiter; $this->userManager = $userManager; $this->cardDavBackend = $cardDavBackend; @@ -48,6 +50,7 @@ public function beforeBind(string $path): void { // We only care about authenticated users here return; } + $user = $this->userManager->get($this->userId); if ($user === null) { // We only care about authenticated users here @@ -72,6 +75,7 @@ public function beforeBind(string $path): void { if ($addressBookLimit === -1) { return; } + $numAddressbooks = $this->cardDavBackend->getAddressBooksForUserCount('principals/users/' . $user->getUID()); if ($numAddressbooks >= $addressBookLimit) { diff --git a/apps/dav/lib/CardDAV/Sharing/Backend.php b/apps/dav/lib/CardDAV/Sharing/Backend.php index 5e03f7699fa7a..557115762fc2c 100644 --- a/apps/dav/lib/CardDAV/Sharing/Backend.php +++ b/apps/dav/lib/CardDAV/Sharing/Backend.php @@ -16,7 +16,8 @@ use Psr\Log\LoggerInterface; class Backend extends SharingBackend { - public function __construct(private IUserManager $userManager, + public function __construct( + private IUserManager $userManager, private IGroupManager $groupManager, private Principal $principalBackend, private ICacheFactory $cacheFactory, diff --git a/apps/dav/lib/CardDAV/Sharing/Service.php b/apps/dav/lib/CardDAV/Sharing/Service.php index 69bcecde227a7..1ab208f7ec3a1 100644 --- a/apps/dav/lib/CardDAV/Sharing/Service.php +++ b/apps/dav/lib/CardDAV/Sharing/Service.php @@ -13,7 +13,9 @@ class Service extends SharingService { protected string $resourceType = 'addressbook'; - public function __construct(protected SharingMapper $mapper) { + public function __construct( + protected SharingMapper $mapper, + ) { parent::__construct($mapper); } } diff --git a/apps/dav/lib/CardDAV/SyncService.php b/apps/dav/lib/CardDAV/SyncService.php index 02b862f193069..655c7c9c5df69 100644 --- a/apps/dav/lib/CardDAV/SyncService.php +++ b/apps/dav/lib/CardDAV/SyncService.php @@ -67,6 +67,7 @@ public function syncRemoteAddressBook(string $url, string $userName, string $add $this->logger->error('Authorization failed, remove address book: ' . $url, ['app' => 'dav']); throw $ex; } + $this->logger->error('Client exception:', ['app' => 'dav', 'exception' => $ex]); throw $ex; } @@ -102,6 +103,7 @@ public function ensureSystemAddressBookExists(string $principal, string $uri, ar if (!is_null($book)) { return $book; } + $this->backend->createAddressBook($principal, $uri, $properties); return $this->backend->getAddressBooksByUri($principal, $uri); @@ -169,6 +171,7 @@ private function buildSyncCollectionRequestBody(?string $syncToken): string { $root->appendChild($sync); $root->appendChild($prop); $dom->appendChild($root); + return $dom->saveXML(); } @@ -229,6 +232,7 @@ public function deleteUser($userOrCardId) { if ($userOrCardId instanceof IUser) { $userOrCardId = self::getCardUri($userOrCardId); } + $this->backend->deleteCard($systemAddressBook['id'], $userOrCardId); } diff --git a/apps/dav/lib/CardDAV/SystemAddressbook.php b/apps/dav/lib/CardDAV/SystemAddressbook.php index aff51ac5b9bc3..83b14fa037308 100644 --- a/apps/dav/lib/CardDAV/SystemAddressbook.php +++ b/apps/dav/lib/CardDAV/SystemAddressbook.php @@ -72,6 +72,7 @@ public function getChildren() { // Should never happen because we don't allow anonymous access return []; } + if ($user->getBackendClassName() === 'Guests' || !$shareEnumeration || (!$shareEnumerationGroup && $shareEnumerationPhone)) { $name = SyncService::getCardUri($user); try { @@ -80,11 +81,13 @@ public function getChildren() { return []; } } + if ($shareEnumerationGroup) { if ($this->groupManager === null) { // Group manager is not available, so we can't determine which data is safe return []; } + $groups = $this->groupManager->getUserGroups($user); $names = []; foreach ($groups as $group) { @@ -93,13 +96,16 @@ public function getChildren() { if ($groupUser->getBackendClassName() === 'Guests') { continue; } + $names[] = SyncService::getCardUri($groupUser); } } + return parent::getMultipleChildren(array_unique($names)); } $children = parent::getChildren(); + return array_filter($children, function (Card $child) { // check only for URIs that begin with Guests: return !str_starts_with($child->getName(), 'Guests:'); @@ -121,6 +127,7 @@ public function getMultipleChildren($paths): array { if ($user === null || !in_array(SyncService::getCardUri($user), $paths, true)) { return []; } + // Only return the own card try { return [parent::getChild(SyncService::getCardUri($user))]; @@ -128,11 +135,13 @@ public function getMultipleChildren($paths): array { return []; } } + if ($shareEnumerationGroup) { if ($this->groupManager === null || $user === null) { // Group manager or user is not available, so we can't determine which data is safe return []; } + $groups = $this->groupManager->getUserGroups($user); $allowedNames = []; foreach ($groups as $group) { @@ -141,11 +150,14 @@ public function getMultipleChildren($paths): array { if ($groupUser->getBackendClassName() === 'Guests') { continue; } + $allowedNames[] = SyncService::getCardUri($groupUser); } } + return parent::getMultipleChildren(array_intersect($paths, $allowedNames)); } + if (!$this->isFederation()) { return parent::getMultipleChildren($paths); } @@ -157,14 +169,17 @@ public function getMultipleChildren($paths): array { if (empty($obj)) { continue; } + $carddata = $this->extractCarddata($obj); if (empty($carddata)) { continue; } else { $obj['carddata'] = $carddata; } + $children[] = new Card($this->carddavBackend, $this->addressBookInfo, $obj); } + return $children; } @@ -184,27 +199,33 @@ public function getChild($name): Card { if ($ownName === $name) { return parent::getChild($name); } + throw new Forbidden(); } + if ($shareEnumerationGroup) { if ($user === null || $this->groupManager === null) { // Group manager is not available, so we can't determine which data is safe throw new Forbidden(); } + $groups = $this->groupManager->getUserGroups($user); foreach ($groups as $group) { foreach ($group->getUsers() as $groupUser) { if ($groupUser->getBackendClassName() === 'Guests') { continue; } + $otherName = SyncService::getCardUri($groupUser); if ($otherName === $name) { return parent::getChild($name); } } } + throw new Forbidden(); } + if (!$this->isFederation()) { return parent::getChild($name); } @@ -213,12 +234,14 @@ public function getChild($name): Card { if (!$obj) { throw new NotFound('Card not found'); } + $carddata = $this->extractCarddata($obj); if (empty($carddata)) { throw new Forbidden(); } else { $obj['carddata'] = $carddata; } + return new Card($this->carddavBackend, $this->addressBookInfo, $obj); } @@ -258,6 +281,7 @@ public function getChanges($syncToken, $syncLevel, $limit = null) { $deleted[] = $uri; } } + foreach ($changed['modified'] as $uri) { try { $this->getChild($uri); @@ -266,9 +290,11 @@ public function getChanges($syncToken, $syncLevel, $limit = null) { $deleted[] = $uri; } } + $changed['added'] = $added; $changed['modified'] = $modified; $changed['deleted'] = $deleted; + return $changed; } @@ -324,6 +350,7 @@ private function extractCarddata(array $obj): ?string { $vCard->remove($child); } } + $messages = $vCard->validate(); if (!empty($messages)) { return null; @@ -340,6 +367,7 @@ public function delete() { if ($this->isFederation()) { parent::delete(); } + throw new Forbidden(); } @@ -348,6 +376,7 @@ public function getACL() { if (in_array($acl['privilege'], ['{DAV:}write', '{DAV:}all'], true)) { return false; } + return true; }); } diff --git a/apps/dav/lib/CardDAV/UserAddressBooks.php b/apps/dav/lib/CardDAV/UserAddressBooks.php index e2d3fe4d8c84d..7bbd80b76eb47 100644 --- a/apps/dav/lib/CardDAV/UserAddressBooks.php +++ b/apps/dav/lib/CardDAV/UserAddressBooks.php @@ -60,6 +60,7 @@ public function getChildren() { if ($this->l10n === null) { $this->l10n = \OC::$server->getL10N('dav'); } + if ($this->config === null) { $this->config = \OC::$server->getConfig(); } @@ -76,6 +77,7 @@ public function getChildren() { $systemAddressBook['uri'] = SystemAddressbook::URI_SHARED; } } + if (!is_null($systemAddressBook)) { $addressBooks[] = $systemAddressBook; } @@ -92,6 +94,7 @@ public function getChildren() { } catch (QueryException|NotFoundExceptionInterface|ContainerExceptionInterface $e) { // nothing to do, the request / trusted servers don't exist } + if ($addressBook['principaluri'] === 'principals/system/system') { return new SystemAddressbook( $this->carddavBackend, @@ -108,6 +111,7 @@ public function getChildren() { return new AddressBook($this->carddavBackend, $addressBook, $this->l10n); }, $addressBooks); } + /** @var IAddressBook[][] $objectsFromPlugins */ $objectsFromPlugins = array_map(function (IAddressBookProvider $plugin): array { return $plugin->fetchAllForAddressBookHome($this->principalUri); diff --git a/apps/dav/lib/CardDAV/Validation/CardDavValidatePlugin.php b/apps/dav/lib/CardDAV/Validation/CardDavValidatePlugin.php index 7e76b9dac92f2..6b378a1d9bd60 100644 --- a/apps/dav/lib/CardDAV/Validation/CardDavValidatePlugin.php +++ b/apps/dav/lib/CardDAV/Validation/CardDavValidatePlugin.php @@ -19,7 +19,7 @@ class CardDavValidatePlugin extends ServerPlugin { public function __construct( - private IAppConfig $config + private IAppConfig $config, ) { } @@ -33,6 +33,7 @@ public function beforePut(RequestInterface $request, ResponseInterface $response if ((int)$request->getRawServerValue('CONTENT_LENGTH') > $cardSizeLimit) { throw new Forbidden("VCard object exceeds $cardSizeLimit bytes"); } + // all tests passed return true return true; } diff --git a/apps/dav/lib/Command/CreateAddressBook.php b/apps/dav/lib/Command/CreateAddressBook.php index 9626edeba260a..6455032a70de7 100644 --- a/apps/dav/lib/Command/CreateAddressBook.php +++ b/apps/dav/lib/Command/CreateAddressBook.php @@ -42,6 +42,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $name = $input->getArgument('name'); $this->cardDavBackend->createAddressBook("principals/users/$user", $name, []); + return self::SUCCESS; } } diff --git a/apps/dav/lib/Command/CreateCalendar.php b/apps/dav/lib/Command/CreateCalendar.php index 5339d9d1c79f5..ac2603206805b 100644 --- a/apps/dav/lib/Command/CreateCalendar.php +++ b/apps/dav/lib/Command/CreateCalendar.php @@ -50,6 +50,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int if (!$this->userManager->userExists($user)) { throw new \InvalidArgumentException("User <$user> in unknown."); } + $principalBackend = new Principal( $this->userManager, $this->groupManager, @@ -78,6 +79,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int \OC::$server->get(Backend::class), ); $caldav->createCalendar("principals/users/$user", $name, []); + return self::SUCCESS; } } diff --git a/apps/dav/lib/Command/CreateSubscription.php b/apps/dav/lib/Command/CreateSubscription.php index 1364070e53024..a79ba578049f6 100644 --- a/apps/dav/lib/Command/CreateSubscription.php +++ b/apps/dav/lib/Command/CreateSubscription.php @@ -72,6 +72,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int '{http://calendarserver.org/ns/}source' => $urlProperty, ]; $this->caldav->createSubscription("principals/users/$user", $name, $properties); + return self::SUCCESS; } diff --git a/apps/dav/lib/Command/DeleteCalendar.php b/apps/dav/lib/Command/DeleteCalendar.php index 53cabcfc5ce2e..f6dbed856e69a 100644 --- a/apps/dav/lib/Command/DeleteCalendar.php +++ b/apps/dav/lib/Command/DeleteCalendar.php @@ -54,7 +54,7 @@ protected function configure(): void { protected function execute( InputInterface $input, - OutputInterface $output + OutputInterface $output, ): int { /** @var string $user */ $user = $input->getArgument('uid'); diff --git a/apps/dav/lib/Command/FixCalendarSyncCommand.php b/apps/dav/lib/Command/FixCalendarSyncCommand.php index e94e1dc9c4af3..18104ab554b8b 100644 --- a/apps/dav/lib/Command/FixCalendarSyncCommand.php +++ b/apps/dav/lib/Command/FixCalendarSyncCommand.php @@ -20,8 +20,10 @@ class FixCalendarSyncCommand extends Command { - public function __construct(private IUserManager $userManager, - private CalDavBackend $calDavBackend) { + public function __construct( + private IUserManager $userManager, + private CalDavBackend $calDavBackend, + ) { parent::__construct('dav:fix-missing-caldav-changes'); } @@ -52,6 +54,7 @@ public function execute(InputInterface $input, OutputInterface $output): int { }); $progress->finish(); } + return 0; } diff --git a/apps/dav/lib/Command/ListCalendars.php b/apps/dav/lib/Command/ListCalendars.php index 5344530e8a528..8e8c5f09e98aa 100644 --- a/apps/dav/lib/Command/ListCalendars.php +++ b/apps/dav/lib/Command/ListCalendars.php @@ -70,6 +70,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int } else { $output->writeln("User <$user> has no calendars"); } + return self::SUCCESS; } } diff --git a/apps/dav/lib/Command/MoveCalendar.php b/apps/dav/lib/Command/MoveCalendar.php index 36f20e5b54789..ce51ab316010e 100644 --- a/apps/dav/lib/Command/MoveCalendar.php +++ b/apps/dav/lib/Command/MoveCalendar.php @@ -105,6 +105,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $this->calDav->moveCalendar($name, self::URI_USERS . $userOrigin, self::URI_USERS . $userDestination, $newName); $this->io->success("Calendar <$name> was moved from user <$userOrigin> to <$userDestination>" . ($newName ? " as <$newName>" : '')); + return self::SUCCESS; } @@ -129,6 +130,7 @@ protected function getNewCalendarName(string $userDestination, string $name): st $this->io->writeln("Found proper new calendar name <$newName>", OutputInterface::VERBOSITY_VERBOSE); break; } + $newName = $name . '-' . $increment; $increment++; } diff --git a/apps/dav/lib/Command/RemoveInvalidShares.php b/apps/dav/lib/Command/RemoveInvalidShares.php index 18f8de5b4e232..9aa5a0c8fdc35 100644 --- a/apps/dav/lib/Command/RemoveInvalidShares.php +++ b/apps/dav/lib/Command/RemoveInvalidShares.php @@ -49,6 +49,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int } $result->closeCursor(); + return self::SUCCESS; } diff --git a/apps/dav/lib/Command/SendEventReminders.php b/apps/dav/lib/Command/SendEventReminders.php index f5afb30ed7076..f9a14bbb49227 100644 --- a/apps/dav/lib/Command/SendEventReminders.php +++ b/apps/dav/lib/Command/SendEventReminders.php @@ -37,16 +37,19 @@ protected function execute(InputInterface $input, OutputInterface $output): int if ($this->config->getAppValue('dav', 'sendEventReminders', 'yes') !== 'yes') { $output->writeln('Sending event reminders disabled!'); $output->writeln('Please run "php occ config:app:set dav sendEventReminders --value yes"'); + return self::FAILURE; } if ($this->config->getAppValue('dav', 'sendEventRemindersMode', 'backgroundjob') !== 'occ') { $output->writeln('Sending event reminders mode set to background-job!'); $output->writeln('Please run "php occ config:app:set dav sendEventRemindersMode --value occ"'); + return self::FAILURE; } $this->reminderService->processReminders(); + return self::SUCCESS; } } diff --git a/apps/dav/lib/Command/SyncBirthdayCalendar.php b/apps/dav/lib/Command/SyncBirthdayCalendar.php index de63a8572bbc4..62c12d22967fe 100644 --- a/apps/dav/lib/Command/SyncBirthdayCalendar.php +++ b/apps/dav/lib/Command/SyncBirthdayCalendar.php @@ -53,8 +53,10 @@ protected function execute(InputInterface $input, OutputInterface $output): int $output->writeln("Start birthday calendar sync for $user"); $this->birthdayService->syncUser($user); + return self::SUCCESS; } + $output->writeln('Start birthday calendar sync for all users ...'); $p = new ProgressBar($output); $p->start(); @@ -73,6 +75,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $p->finish(); $output->writeln(''); + return self::SUCCESS; } diff --git a/apps/dav/lib/Command/SyncSystemAddressBook.php b/apps/dav/lib/Command/SyncSystemAddressBook.php index 715d3d65f877f..016162cdc17c3 100644 --- a/apps/dav/lib/Command/SyncSystemAddressBook.php +++ b/apps/dav/lib/Command/SyncSystemAddressBook.php @@ -39,6 +39,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $progress->finish(); $output->writeln(''); $this->config->setAppValue('dav', 'needs_system_address_book_sync', 'no'); + return self::SUCCESS; } } diff --git a/apps/dav/lib/Comments/CommentNode.php b/apps/dav/lib/Comments/CommentNode.php index a257611dd1520..e26c80c09f2f8 100644 --- a/apps/dav/lib/Comments/CommentNode.php +++ b/apps/dav/lib/Comments/CommentNode.php @@ -55,7 +55,7 @@ public function __construct( IComment $comment, IUserManager $userManager, IUserSession $userSession, - LoggerInterface $logger + LoggerInterface $logger, ) { $this->commentsManager = $commentsManager; $this->comment = $comment; @@ -69,9 +69,11 @@ public function __construct( if ($getter === 'getMentions') { continue; // special treatment } - $name = '{'.self::NS_OWNCLOUD.'}' . lcfirst(substr($getter, 3)); + + $name = '{' . self::NS_OWNCLOUD . '}' . lcfirst(substr($getter, 3)); $this->properties[$name] = $getter; } + $this->userManager = $userManager; $this->userSession = $userSession; } @@ -167,6 +169,7 @@ public function updateComment($propertyValue) { try { $this->comment->setMessage($propertyValue); $this->commentsManager->save($this->comment); + return true; } catch (\Exception $e) { $this->logger->error($e->getMessage(), ['app' => 'dav/comments', 'exception' => $e]); @@ -174,6 +177,7 @@ public function updateComment($propertyValue) { $msg = 'Message exceeds allowed character limit of '; throw new BadRequest($msg . IComment::MAX_MESSAGE_LENGTH, 0, $e); } + throw $e; } } @@ -245,6 +249,7 @@ public function getProperties($properties) { $unread = $unread ? 'true' : 'false'; } } + $result[self::PROPERTY_NAME_UNREAD] = $unread; return $result; diff --git a/apps/dav/lib/Comments/CommentsPlugin.php b/apps/dav/lib/Comments/CommentsPlugin.php index 3be87460d2167..7f644e64e5101 100644 --- a/apps/dav/lib/Comments/CommentsPlugin.php +++ b/apps/dav/lib/Comments/CommentsPlugin.php @@ -113,6 +113,7 @@ public function httpPost(RequestInterface $request, ResponseInterface $response) // created $response->setStatus(201); + return false; } @@ -143,6 +144,7 @@ public function onReport($reportName, $report, $uri) { if (!$node instanceof EntityCollection || $reportName !== self::REPORT_NAME) { throw new ReportNotSupported(); } + $args = ['limit' => 0, 'offset' => 0, 'datetime' => null]; $acceptableParameters = [ $this::REPORT_PARAM_LIMIT, @@ -154,6 +156,7 @@ public function onReport($reportName, $report, $uri) { if (!in_array($parameter['name'], $acceptableParameters) || empty($parameter['value'])) { continue; } + $args[str_replace($ns, '', $parameter['name'])] = $parameter['value']; } @@ -215,8 +218,9 @@ private function createComment($objectType, $objectId, $data, $contentType = 'ap $actorId = $user->getUID(); } } + if (is_null($actorId)) { - throw new BadRequest('Invalid actor "' . $actorType .'"'); + throw new BadRequest('Invalid actor "' . $actorType . '"'); } try { @@ -224,6 +228,7 @@ private function createComment($objectType, $objectId, $data, $contentType = 'ap $comment->setMessage($data['message']); $comment->setVerb($data['verb']); $this->commentsManager->save($comment); + return $comment; } catch (\InvalidArgumentException $e) { throw new BadRequest('Invalid input values', 0, $e); diff --git a/apps/dav/lib/Comments/EntityCollection.php b/apps/dav/lib/Comments/EntityCollection.php index 19af2d9c23401..4101b13352da3 100644 --- a/apps/dav/lib/Comments/EntityCollection.php +++ b/apps/dav/lib/Comments/EntityCollection.php @@ -46,7 +46,7 @@ public function __construct( ICommentsManager $commentsManager, IUserManager $userManager, IUserSession $userSession, - LoggerInterface $logger + LoggerInterface $logger, ) { foreach (['id', 'name'] as $property) { $$property = trim($$property); @@ -54,6 +54,7 @@ public function __construct( throw new \InvalidArgumentException('"' . $property . '" parameter must be non-empty string'); } } + $this->id = $id; $this->name = $name; $this->commentsManager = $commentsManager; @@ -126,6 +127,7 @@ public function findChildren($limit = 0, $offset = 0, ?\DateTime $datetime = nul $this->logger ); } + return $result; } @@ -151,6 +153,7 @@ public function setReadMarker(?string $value): bool { $dateTime = new \DateTime($value ?? 'now'); $user = $this->userSession->getUser(); $this->commentsManager->setReadMark($this->name, $this->id, $dateTime, $user); + return true; } @@ -170,6 +173,7 @@ public function getProperties($properties) { if (!is_null($user)) { $marker = $this->commentsManager->getReadMark($this->name, $this->id, $user); } + return [self::PROPERTY_NAME_READ_MARKER => $marker]; } } diff --git a/apps/dav/lib/Comments/EntityTypeCollection.php b/apps/dav/lib/Comments/EntityTypeCollection.php index da5a18e78c89f..9c3a619c1e881 100644 --- a/apps/dav/lib/Comments/EntityTypeCollection.php +++ b/apps/dav/lib/Comments/EntityTypeCollection.php @@ -38,12 +38,13 @@ public function __construct( IUserManager $userManager, IUserSession $userSession, LoggerInterface $logger, - \Closure $childExistsFunction + \Closure $childExistsFunction, ) { $name = trim($name); if (empty($name)) { throw new \InvalidArgumentException('"name" parameter must be non-empty string'); } + $this->name = $name; $this->commentsManager = $commentsManager; $this->logger = $logger; @@ -66,6 +67,7 @@ public function getChild($name) { if (!$this->childExists($name)) { throw new NotFound('Entity does not exist or is not available'); } + return new EntityCollection( $name, $this->name, diff --git a/apps/dav/lib/Comments/RootCollection.php b/apps/dav/lib/Comments/RootCollection.php index a9aa88458b768..d29a2f78b46ec 100644 --- a/apps/dav/lib/Comments/RootCollection.php +++ b/apps/dav/lib/Comments/RootCollection.php @@ -52,6 +52,7 @@ protected function initCollections() { if ($this->entityTypeCollections !== null) { return; } + $user = $this->userSession->getUser(); if (is_null($user)) { throw new NotAuthenticated(); @@ -111,6 +112,7 @@ public function getChild($name) { if (isset($this->entityTypeCollections[$name])) { return $this->entityTypeCollections[$name]; } + throw new NotFound('Entity type "' . $name . '" not found."'); } @@ -122,6 +124,7 @@ public function getChild($name) { public function getChildren() { $this->initCollections(); assert(!is_null($this->entityTypeCollections)); + return $this->entityTypeCollections; } @@ -134,6 +137,7 @@ public function getChildren() { public function childExists($name) { $this->initCollections(); assert(!is_null($this->entityTypeCollections)); + return isset($this->entityTypeCollections[$name]); } diff --git a/apps/dav/lib/Connector/LegacyDAVACL.php b/apps/dav/lib/Connector/LegacyDAVACL.php index 40ce53b8ab074..993f58dbf7062 100644 --- a/apps/dav/lib/Connector/LegacyDAVACL.php +++ b/apps/dav/lib/Connector/LegacyDAVACL.php @@ -25,6 +25,7 @@ public function getCurrentUserPrincipals() { } $principalV1 = $this->convertPrincipal($principalV2, false); + return array_merge( [ $principalV2, @@ -39,6 +40,7 @@ private function convertPrincipal($principal, $toV2) { if ($toV2) { return "principals/users/$name"; } + return "principals/$name"; } diff --git a/apps/dav/lib/Connector/LegacyPublicAuth.php b/apps/dav/lib/Connector/LegacyPublicAuth.php index 564eae506ac9a..51df041268806 100644 --- a/apps/dav/lib/Connector/LegacyPublicAuth.php +++ b/apps/dav/lib/Connector/LegacyPublicAuth.php @@ -89,6 +89,7 @@ protected function validateUserPass($username, $password) { } $this->throttler->registerAttempt(self::BRUTEFORCE_ACTION, $this->request->getRemoteAddress()); + return false; } } elseif ($share->getShareType() === IShare::TYPE_REMOTE) { @@ -98,6 +99,7 @@ protected function validateUserPass($username, $password) { return false; } } + return true; } diff --git a/apps/dav/lib/Connector/Sabre/AnonymousOptionsPlugin.php b/apps/dav/lib/Connector/Sabre/AnonymousOptionsPlugin.php index b39dc7197b099..504c853f18873 100644 --- a/apps/dav/lib/Connector/Sabre/AnonymousOptionsPlugin.php +++ b/apps/dav/lib/Connector/Sabre/AnonymousOptionsPlugin.php @@ -57,6 +57,7 @@ public function handleAnonymousOptions(RequestInterface $request, ResponseInterf $this->server->emit('afterMethod:OPTIONS', [$request, $response]); $this->server->sapi->sendResponse($response); + return false; } } diff --git a/apps/dav/lib/Connector/Sabre/AppleQuirksPlugin.php b/apps/dav/lib/Connector/Sabre/AppleQuirksPlugin.php index 88fff5e6a5a69..037a621b6800e 100644 --- a/apps/dav/lib/Connector/Sabre/AppleQuirksPlugin.php +++ b/apps/dav/lib/Connector/Sabre/AppleQuirksPlugin.php @@ -68,6 +68,7 @@ public function report($reportName, $report, $path) { /** @var \Sabre\DAVACL\Xml\Request\PrincipalPropertySearchReport $report */ $report->applyToPrincipalCollectionSet = true; } + return true; } @@ -106,6 +107,7 @@ protected function decodeMacOSAgentString(string $userAgent):?array { ], ]; } + return null; } } diff --git a/apps/dav/lib/Connector/Sabre/Auth.php b/apps/dav/lib/Connector/Sabre/Auth.php index 9b67d960107cb..6b1176f061afa 100644 --- a/apps/dav/lib/Connector/Sabre/Auth.php +++ b/apps/dav/lib/Connector/Sabre/Auth.php @@ -87,6 +87,7 @@ protected function validateUserPass($username, $password) { if ($this->userSession->logClientIn($username, $password, $this->request, $this->throttler)) { $this->session->set(self::DAV_AUTHENTICATED, $this->userSession->getUser()->getUID()); $this->session->close(); + return true; } else { $this->session->close(); @@ -181,6 +182,7 @@ private function auth(RequestInterface $request, ResponseInterface $response): a if ($this->twoFactorManager->needsSecondFactor($this->userSession->getUser())) { throw new \Sabre\DAV\Exception\NotAuthenticated('2FA challenge not passed.'); } + if ( //Fix for broken webdav clients ($this->userSession->isLoggedIn() && is_null($this->session->get(self::DAV_AUTHENTICATED))) || @@ -191,6 +193,7 @@ private function auth(RequestInterface $request, ResponseInterface $response): a $user = $this->userSession->getUser()->getUID(); $this->currentUser = $user; $this->session->close(); + return [true, $this->principalPrefix . $user]; } } @@ -206,6 +209,7 @@ private function auth(RequestInterface $request, ResponseInterface $response): a $response->setStatus(401); throw new \Sabre\DAV\Exception\NotAuthenticated('Cannot authenticate over ajax calls'); } + return $data; } } diff --git a/apps/dav/lib/Connector/Sabre/BearerAuth.php b/apps/dav/lib/Connector/Sabre/BearerAuth.php index 8caae8dced9d2..7ab655bf588e2 100644 --- a/apps/dav/lib/Connector/Sabre/BearerAuth.php +++ b/apps/dav/lib/Connector/Sabre/BearerAuth.php @@ -35,6 +35,7 @@ public function __construct(IUserSession $userSession, private function setupUserFs($userId) { \OC_Util::setupFS($userId); $this->session->close(); + return $this->principalPrefix . $userId; } @@ -47,6 +48,7 @@ public function validateBearerToken($bearerToken) { if (!$this->userSession->isLoggedIn()) { $this->userSession->tryTokenLogin($this->request); } + if ($this->userSession->isLoggedIn()) { return $this->setupUserFs($this->userSession->getUser()->getUID()); } diff --git a/apps/dav/lib/Connector/Sabre/CachingTree.php b/apps/dav/lib/Connector/Sabre/CachingTree.php index 86e102677c12a..a20d0e238a53b 100644 --- a/apps/dav/lib/Connector/Sabre/CachingTree.php +++ b/apps/dav/lib/Connector/Sabre/CachingTree.php @@ -15,6 +15,7 @@ public function cacheNode(Node $node, ?string $path = null): void { if (is_null($path)) { $path = $node->getPath(); } + $this->cache[trim($path, '/')] = $node; } diff --git a/apps/dav/lib/Connector/Sabre/CommentPropertiesPlugin.php b/apps/dav/lib/Connector/Sabre/CommentPropertiesPlugin.php index f2da3782def39..b591520ced775 100644 --- a/apps/dav/lib/Connector/Sabre/CommentPropertiesPlugin.php +++ b/apps/dav/lib/Connector/Sabre/CommentPropertiesPlugin.php @@ -80,7 +80,7 @@ private function cacheDirectory(Directory $directory): void { */ public function handleGetProperties( PropFind $propFind, - \Sabre\DAV\INode $node + \Sabre\DAV\INode $node, ) { if (!($node instanceof File) && !($node instanceof Directory)) { return; @@ -117,7 +117,9 @@ public function getCommentsLink(Node $node): ?string { // in case we end up somewhere else, unexpectedly. return null; } + $commentsPart = 'dav/comments/files/' . rawurldecode((string)$node->getId()); + return substr_replace($href, $commentsPart, $entryPoint + strlen('/remote.php/')); } diff --git a/apps/dav/lib/Connector/Sabre/Directory.php b/apps/dav/lib/Connector/Sabre/Directory.php index c1b69323a44e4..e4cc7b4b18c92 100644 --- a/apps/dav/lib/Connector/Sabre/Directory.php +++ b/apps/dav/lib/Connector/Sabre/Directory.php @@ -101,6 +101,7 @@ public function createFile($name, $data = null) { 'type' => FileInfo::TYPE_FILE ], null); } + $node = new \OCA\DAV\Connector\Sabre\File($this->fileView, $info); // only allow 1 process to upload a file at once but still allow reading the file while writing the part file @@ -111,6 +112,7 @@ public function createFile($name, $data = null) { $this->fileView->unlockFile($path . '.upload.part', ILockingProvider::LOCK_EXCLUSIVE); $node->releaseLock(ILockingProvider::LOCK_SHARED); + return $result; } catch (\OCP\Files\StorageNotAvailableException $e) { throw new \Sabre\DAV\Exception\ServiceUnavailable($e->getMessage(), $e->getCode(), $e); @@ -193,9 +195,11 @@ public function getChild($name, $info = null, ?IRequest $request = null, ?IL10N } else { $node = new \OCA\DAV\Connector\Sabre\File($this->fileView, $info, $this->shareManager, $request, $l10n); } + if ($this->tree) { $this->tree->cacheNode($node); } + return $node; } @@ -210,6 +214,7 @@ public function getChildren() { if (!is_null($this->dirContent)) { return $this->dirContent; } + try { if (!$this->info->isReadable()) { // return 403 instead of 404 because a 404 would make @@ -220,6 +225,7 @@ public function getChildren() { throw new Forbidden('No read permissions'); } } + $folderContent = $this->getNode()->getDirectoryListing(); } catch (LockedException $e) { throw new Locked(); @@ -233,7 +239,9 @@ public function getChildren() { $node = $this->getChild($info->getName(), $info, $request, $l10n); $nodes[] = $node; } + $this->dirContent = $nodes; + return $this->dirContent; } @@ -252,6 +260,7 @@ public function childExists($name) { // // TODO: resolve chunk file name here and implement "updateFile" $path = $this->path . '/' . $name; + return $this->fileView->file_exists($path); } @@ -292,6 +301,7 @@ public function getQuotaInfo() { if ($this->quotaInfo) { return $this->quotaInfo; } + $relativePath = $this->fileView->getRelativePath($this->info->getPath()); if ($relativePath === null) { $this->getLogger()->warning('error while getting quota as the relative path cannot be found'); @@ -305,10 +315,12 @@ public function getQuotaInfo() { } else { $free = $storageInfo['free']; } + $this->quotaInfo = [ $storageInfo['used'], $free ]; + return $this->quotaInfo; } catch (\OCP\Files\NotFoundException $e) { $this->getLogger()->warning('error while getting quota into', ['exception' => $e]); @@ -354,6 +366,7 @@ public function moveInto($targetName, $fullSourcePath, INode $sourceNode) { // fallback to default copy+delete handling return false; } + throw new BadRequest('Incompatible node types'); } diff --git a/apps/dav/lib/Connector/Sabre/Exception/FileLocked.php b/apps/dav/lib/Connector/Sabre/Exception/FileLocked.php index bad4bfa12ab58..63b3bbc3237cc 100644 --- a/apps/dav/lib/Connector/Sabre/Exception/FileLocked.php +++ b/apps/dav/lib/Connector/Sabre/Exception/FileLocked.php @@ -18,6 +18,7 @@ public function __construct($message = '', $code = 0, ?Exception $previous = nul if ($previous instanceof \OCP\Files\LockNotAcquiredException) { $message = sprintf('Target file %s is locked by another process.', $previous->path); } + parent::__construct($message, $code, $previous); } diff --git a/apps/dav/lib/Connector/Sabre/ExceptionLoggerPlugin.php b/apps/dav/lib/Connector/Sabre/ExceptionLoggerPlugin.php index 2e8fcefe57741..069e171524bdc 100644 --- a/apps/dav/lib/Connector/Sabre/ExceptionLoggerPlugin.php +++ b/apps/dav/lib/Connector/Sabre/ExceptionLoggerPlugin.php @@ -103,6 +103,7 @@ public function logException(\Throwable $ex) { 'app' => $this->appName, 'exception' => $ex, ]); + return; } diff --git a/apps/dav/lib/Connector/Sabre/FakeLockerPlugin.php b/apps/dav/lib/Connector/Sabre/FakeLockerPlugin.php index 485b6b09a3cde..93b99fcc00712 100644 --- a/apps/dav/lib/Connector/Sabre/FakeLockerPlugin.php +++ b/apps/dav/lib/Connector/Sabre/FakeLockerPlugin.php @@ -136,6 +136,7 @@ public function fakeUnlockProvider(RequestInterface $request, ResponseInterface $response) { $response->setStatus(204); $response->setHeader('Content-Length', '0'); + return false; } } diff --git a/apps/dav/lib/Connector/Sabre/File.php b/apps/dav/lib/Connector/Sabre/File.php index 34cc2b77b37da..e649d64bf7403 100644 --- a/apps/dav/lib/Connector/Sabre/File.php +++ b/apps/dav/lib/Connector/Sabre/File.php @@ -121,6 +121,7 @@ public function put($data) { if ($partStorage === null) { throw new ServiceUnavailable($this->l10n->t('Failed to get storage for file')); } + $needsPartFile = $partStorage->needsPartFile() && (strlen($this->path) > 1); $view = \OC\Files\Filesystem::getView(); @@ -133,6 +134,7 @@ public function put($data) { $needsPartFile = false; } } + if (!$needsPartFile) { // upload file directly as the final path $partFilePath = $this->path; @@ -148,6 +150,7 @@ public function put($data) { if ($partStorage === null || $storage === null) { throw new ServiceUnavailable($this->l10n->t('Failed to get storage for file')); } + try { if (!$needsPartFile) { try { @@ -175,6 +178,7 @@ public function put($data) { fwrite($tmpData, $data); rewind($tmpData); } + $data = $tmpData; } @@ -215,7 +219,6 @@ public function put($data) { throw $e; } - if ($result === false) { $result = $isEOF; if (is_resource($wrappedData)) { @@ -229,6 +232,7 @@ public function put($data) { // because we have no clue about the cause we can only throw back a 500/Internal Server Error throw new Exception($this->l10n->t('Could not write file contents')); } + [$count, $result] = \OC_Helper::streamCopy($data, $target); fclose($target); } @@ -239,6 +243,7 @@ public function put($data) { if ($lengthHeader) { $expected = (int)$lengthHeader; } + if ($expected !== 0) { throw new Exception( $this->l10n->t( @@ -280,6 +285,7 @@ public function put($data) { if ($needsPartFile) { $partStorage->unlink($internalPartPath); } + $this->convertToSabreException($e); } @@ -289,6 +295,7 @@ public function put($data) { $partStorage->unlink($internalPartPath); throw new Exception($this->l10n->t('Could not rename part file to final file, canceled by hook')); } + try { $this->changeLock(ILockingProvider::LOCK_EXCLUSIVE); } catch (LockedException $e) { @@ -306,6 +313,7 @@ public function put($data) { if ($needsPartFile) { $partStorage->unlink($internalPartPath); } + throw new FileLocked($e->getMessage(), $e->getCode(), $e); } } @@ -322,6 +330,7 @@ public function put($data) { if (!$ex->getRetry()) { $partStorage->unlink($internalPartPath); } + throw new DAVForbiddenException($ex->getMessage(), $ex->getRetry()); } catch (\Exception $e) { $partStorage->unlink($internalPartPath); @@ -394,11 +403,13 @@ private function emitPreHooks(bool $exists, ?string $path = null): bool { if (is_null($path)) { $path = $this->path; } + $hookPath = Filesystem::getView()->getRelativePath($this->fileView->getAbsolutePath($path)); if ($hookPath === null) { // We only trigger hooks from inside default view return true; } + $run = true; if (!$exists) { @@ -412,10 +423,12 @@ private function emitPreHooks(bool $exists, ?string $path = null): bool { \OC\Files\Filesystem::signal_param_run => &$run, ]); } + \OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_write, [ \OC\Files\Filesystem::signal_param_path => $hookPath, \OC\Files\Filesystem::signal_param_run => &$run, ]); + return $run; } @@ -423,11 +436,13 @@ private function emitPostHooks(bool $exists, ?string $path = null): void { if (is_null($path)) { $path = $this->path; } + $hookPath = Filesystem::getView()->getRelativePath($this->fileView->getAbsolutePath($path)); if ($hookPath === null) { // We only trigger hooks from inside default view return; } + if (!$exists) { \OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_post_create, [ \OC\Files\Filesystem::signal_param_path => $hookPath @@ -437,6 +452,7 @@ private function emitPostHooks(bool $exists, ?string $path = null): void { \OC\Files\Filesystem::signal_param_path => $hookPath ]); } + \OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_post_write, [ \OC\Files\Filesystem::signal_param_path => $hookPath ]); @@ -456,6 +472,7 @@ public function get() { // do a if the file did not exist throw new NotFound(); } + try { $res = $this->fileView->fopen(ltrim($this->path, '/'), 'rb'); } catch (\Exception $e) { @@ -528,6 +545,7 @@ public function getContentType() { if ($this->request->getMethod() === 'PROPFIND') { return $mimeType; } + return \OC::$server->getMimeTypeDetector()->getSecureMimeType($mimeType); } @@ -538,6 +556,7 @@ public function getDirectDownload() { if (\OCP\Server::get(\OCP\App\IAppManager::class)->isEnabledForUser('encryption')) { return []; } + [$storage, $internalPath] = $this->fileView->resolvePath($this->path); if (is_null($storage)) { return []; @@ -557,38 +576,47 @@ private function convertToSabreException(\Exception $e) { if ($e instanceof \Sabre\DAV\Exception) { throw $e; } + if ($e instanceof NotPermittedException) { // a more general case - due to whatever reason the content could not be written throw new Forbidden($e->getMessage(), 0, $e); } + if ($e instanceof ForbiddenException) { // the path for the file was forbidden throw new DAVForbiddenException($e->getMessage(), $e->getRetry(), $e); } + if ($e instanceof EntityTooLargeException) { // the file is too big to be stored throw new EntityTooLarge($e->getMessage(), 0, $e); } + if ($e instanceof InvalidContentException) { // the file content is not permitted throw new UnsupportedMediaType($e->getMessage(), 0, $e); } + if ($e instanceof InvalidPathException) { // the path for the file was not valid // TODO: find proper http status code for this case throw new Forbidden($e->getMessage(), 0, $e); } + if ($e instanceof LockedException || $e instanceof LockNotAcquiredException) { // the file is currently being written to by another process throw new FileLocked($e->getMessage(), $e->getCode(), $e); } + if ($e instanceof GenericEncryptionException) { // returning 503 will allow retry of the operation at a later point in time throw new ServiceUnavailable($this->l10n->t('Encryption not ready: %1$s', [$e->getMessage()]), 0, $e); } + if ($e instanceof StorageNotAvailableException) { throw new ServiceUnavailable($this->l10n->t('Failed to write file contents: %1$s', [$e->getMessage()]), 0, $e); } + if ($e instanceof NotFoundException) { throw new NotFound($this->l10n->t('File not found: %1$s', [$e->getMessage()]), 0, $e); } diff --git a/apps/dav/lib/Connector/Sabre/FilesPlugin.php b/apps/dav/lib/Connector/Sabre/FilesPlugin.php index 60da92e3bdf2b..b204cda1aecb5 100644 --- a/apps/dav/lib/Connector/Sabre/FilesPlugin.php +++ b/apps/dav/lib/Connector/Sabre/FilesPlugin.php @@ -160,6 +160,7 @@ public function checkCopy($source, $target): void { if ($sourceNodeFileInfo === null) { throw new NotFound($source . ' does not exist'); } + // Ensure the target name is valid try { [$targetPath, $targetName] = \Sabre\Uri\split($target); @@ -167,6 +168,7 @@ public function checkCopy($source, $target): void { } catch (InvalidPathException $e) { throw new InvalidPath($e->getMessage(), false); } + // Ensure the target path is valid $segments = array_slice(explode('/', $targetPath), 2); foreach ($segments as $segment) { @@ -266,6 +268,7 @@ public function httpGet(RequestInterface $request, ResponseInterface $response) $response->addHeader('OC-Checksum', $checksum); } } + $response->addHeader('X-Accel-Buffering', 'no'); } @@ -305,6 +308,7 @@ public function handleGetProperties(PropFind $propFind, \Sabre\DAV\INode $node) // remove mount information $perms = str_replace(['S', 'M'], '', $perms); } + return $perms; }); @@ -313,6 +317,7 @@ public function handleGetProperties(PropFind $propFind, \Sabre\DAV\INode $node) if ($user === null) { return null; } + return $node->getSharePermissions( $user->getUID() ); @@ -323,10 +328,12 @@ public function handleGetProperties(PropFind $propFind, \Sabre\DAV\INode $node) if ($user === null) { return null; } + $ncPermissions = $node->getSharePermissions( $user->getUID() ); $ocmPermissions = $this->ncPermissions2ocmPermissions($ncPermissions); + return json_encode($ocmPermissions, JSON_THROW_ON_ERROR); }); @@ -401,6 +408,7 @@ public function handleGetProperties(PropFind $propFind, \Sabre\DAV\INode $node) $propFind->handle(self::HIDDEN_PROPERTYNAME, function () use ($node) { $isLivePhoto = isset($node->getFileInfo()->getMetadata()['files-live-photo']); $isMovFile = $node->getFileInfo()->getMimetype() === 'video/quicktime'; + return ($isLivePhoto && $isMovFile) ? 'true' : 'false'; }); @@ -427,6 +435,7 @@ public function handleGetProperties(PropFind $propFind, \Sabre\DAV\INode $node) } catch (ForbiddenException $e) { return false; } + return false; }); @@ -512,28 +521,35 @@ public function handleUpdateProperties($path, PropPatch $propPatch) { if (empty($time)) { return false; } + $node->touch($time); + return true; }); $propPatch->handle(self::GETETAG_PROPERTYNAME, function ($etag) use ($node) { if (empty($etag)) { return false; } + return $node->setEtag($etag) !== -1; }); $propPatch->handle(self::CREATIONDATE_PROPERTYNAME, function ($time) use ($node) { if (empty($time)) { return false; } + $dateTime = new \DateTimeImmutable($time); $node->setCreationTime($dateTime->getTimestamp()); + return true; }); $propPatch->handle(self::CREATION_TIME_PROPERTYNAME, function ($time) use ($node) { if (empty($time)) { return false; } + $node->setCreationTime((int)$time); + return true; }); @@ -669,6 +685,7 @@ public function sendFileIdHeader($filePath, ?\Sabre\DAV\INode $node = null) { if (!$this->server->tree->nodeExists($filePath)) { return; } + $node = $this->server->tree->getNodeForPath($filePath); if ($node instanceof \OCA\DAV\Connector\Sabre\Node) { $fileId = $node->getFileId(); diff --git a/apps/dav/lib/Connector/Sabre/FilesReportPlugin.php b/apps/dav/lib/Connector/Sabre/FilesReportPlugin.php index 581adfa418440..b20db114103ff 100644 --- a/apps/dav/lib/Connector/Sabre/FilesReportPlugin.php +++ b/apps/dav/lib/Connector/Sabre/FilesReportPlugin.php @@ -106,7 +106,7 @@ public function __construct(Tree $tree, IUserSession $userSession, IGroupManager $groupManager, Folder $userFolder, - IAppManager $appManager + IAppManager $appManager, ) { $this->tree = $tree; $this->fileView = $view; @@ -258,10 +258,12 @@ private function getFilesBaseUri(string $uri, string $subPath): string { } else { $filesUri = substr($uri, 0, strlen($uri) - strlen($subPath)); } + $filesUri = trim($filesUri, '/'); if (empty($filesUri)) { return ''; } + return '/' . $filesUri; } @@ -280,6 +282,7 @@ protected function processFilterRulesForFileIDs(array $filterRules): array { if ($filterRule['name'] === self::CIRCLE_PROPERTYNAME) { $circlesIds[] = $filterRule['value']; } + if ($filterRule['name'] === $ns . 'favorite') { $favoriteFilter = true; } @@ -332,6 +335,7 @@ protected function processFilterRulesForFileNodes(array $filterRules, ?int $limi return $a->getId() - $b->getId(); }); } + if ($nodes === []) { // there cannot be a common match when nodes are empty early. return $nodes; @@ -355,6 +359,7 @@ private function getCirclesFileIds(array $circlesIds) { if (!$this->appManager->isEnabledForUser('circles') || !class_exists('\OCA\Circles\Api\v1\Circles')) { return []; } + return \OCA\Circles\Api\v1\Circles::getFilesForCircles($circlesIds); } @@ -388,6 +393,7 @@ public function prepareResponses($filesUri, $requestedProps, $nodes) { $result, ); } + return $responses; } @@ -402,6 +408,7 @@ public function findNodesByFileIds(Node $rootNode, array $fileIds): array { if (empty($fileIds)) { return []; } + $folder = $this->userFolder; if (trim($rootNode->getPath(), '/') !== '') { /** @var Folder $folder */ @@ -435,6 +442,7 @@ private function isAdmin() { if ($user !== null) { return $this->groupManager->isAdmin($user->getUID()); } + return false; } } diff --git a/apps/dav/lib/Connector/Sabre/LockPlugin.php b/apps/dav/lib/Connector/Sabre/LockPlugin.php index 6640771dc31d0..bb15b454e4c0f 100644 --- a/apps/dav/lib/Connector/Sabre/LockPlugin.php +++ b/apps/dav/lib/Connector/Sabre/LockPlugin.php @@ -45,17 +45,20 @@ public function getLock(RequestInterface $request) { if ($request->getMethod() !== 'PUT') { return; } + try { $node = $this->server->tree->getNodeForPath($request->getPath()); } catch (NotFound $e) { return; } + if ($node instanceof Node) { try { $node->acquireLock(ILockingProvider::LOCK_SHARED); } catch (LockedException $e) { throw new FileLocked($e->getMessage(), $e->getCode(), $e); } + $this->isLocked = true; } } @@ -65,14 +68,17 @@ public function releaseLock(RequestInterface $request) { if ($this->isLocked === false) { return; } + if ($request->getMethod() !== 'PUT') { return; } + try { $node = $this->server->tree->getNodeForPath($request->getPath()); } catch (NotFound $e) { return; } + if ($node instanceof Node) { $node->releaseLock(ILockingProvider::LOCK_SHARED); $this->isLocked = false; diff --git a/apps/dav/lib/Connector/Sabre/MaintenancePlugin.php b/apps/dav/lib/Connector/Sabre/MaintenancePlugin.php index 8d23a05a3ecb2..756da5258e1b1 100644 --- a/apps/dav/lib/Connector/Sabre/MaintenancePlugin.php +++ b/apps/dav/lib/Connector/Sabre/MaintenancePlugin.php @@ -65,6 +65,7 @@ public function checkMaintenanceMode() { if ($this->config->getSystemValueBool('maintenance')) { throw new ServerMaintenanceMode($this->l10n->t('System is in maintenance mode.')); } + if (Util::needUpgrade()) { throw new ServerMaintenanceMode($this->l10n->t('Upgrade needed')); } diff --git a/apps/dav/lib/Connector/Sabre/Node.php b/apps/dav/lib/Connector/Sabre/Node.php index 3ca542fee73ed..f9f83562ad312 100644 --- a/apps/dav/lib/Connector/Sabre/Node.php +++ b/apps/dav/lib/Connector/Sabre/Node.php @@ -62,6 +62,7 @@ public function __construct(View $view, FileInfo $info, ?IManager $shareManager } else { $this->shareManager = \OC::$server->getShareManager(); } + if ($info instanceof Folder || $info instanceof File) { $this->node = $info; } else { @@ -79,8 +80,9 @@ public function __construct(View $view, FileInfo $info, ?IManager $shareManager protected function refreshInfo(): void { $info = $this->fileView->getFileInfo($this->path); if ($info === false) { - throw new \Sabre\DAV\Exception('Failed to get fileinfo for '. $this->path); + throw new \Sabre\DAV\Exception('Failed to get fileinfo for ' . $this->path); } + $this->info = $info; $root = \OC::$server->get(IRootFolder::class); $rootView = \OC::$server->get(View::class); @@ -131,7 +133,7 @@ public function setName($name) { $this->verifyPath($newPath); if (!$this->fileView->rename($this->path, $newPath)) { - throw new \Sabre\DAV\Exception('Failed to rename '. $this->path . ' to ' . $newPath); + throw new \Sabre\DAV\Exception('Failed to rename ' . $this->path . ' to ' . $newPath); } $this->path = $newPath; @@ -153,6 +155,7 @@ public function getLastModified() { if (!empty($timestamp)) { return (int)$timestamp; } + return $timestamp; } @@ -334,6 +337,7 @@ public function getNoteFromShare(?string $user): ?string { // Note is only for recipient not the owner return null; } + return $share->getNote(); } diff --git a/apps/dav/lib/Connector/Sabre/ObjectTree.php b/apps/dav/lib/Connector/Sabre/ObjectTree.php index 94098b4aca3ba..d0254a6aea96c 100644 --- a/apps/dav/lib/Connector/Sabre/ObjectTree.php +++ b/apps/dav/lib/Connector/Sabre/ObjectTree.php @@ -126,6 +126,7 @@ public function getNodeForPath($path) { } $this->cache[$path] = $node; + return $node; } @@ -152,13 +153,13 @@ public function copy($sourcePath, $destinationPath) { throw new \Sabre\DAV\Exception\ServiceUnavailable('filesystem not setup'); } - $info = $this->fileView->getFileInfo(dirname($destinationPath)); if ($this->fileView->file_exists($destinationPath)) { $destinationPermission = $info && $info->isUpdateable(); } else { $destinationPermission = $info && $info->isCreatable(); } + if (!$destinationPermission) { throw new Forbidden('No permissions to copy object.'); } diff --git a/apps/dav/lib/Connector/Sabre/Principal.php b/apps/dav/lib/Connector/Sabre/Principal.php index 029061694ea27..6d3ec6b6d4ecd 100644 --- a/apps/dav/lib/Connector/Sabre/Principal.php +++ b/apps/dav/lib/Connector/Sabre/Principal.php @@ -145,6 +145,7 @@ public function getPrincipalByPath($path) { 'uri' => 'principals/users/' . $user->getUID() . '/' . $name, ]; } + return null; } } @@ -183,6 +184,7 @@ public function getPrincipalByPath($path) { '{DAV:}displayname' => $this->languageFactory->get('dav')->t('Accounts'), ]; } + return null; } @@ -279,6 +281,7 @@ protected function searchUserPrincipals(array $searchProperties, $test = 'allof' if ($searchLimit <= 0) { $searchLimit = null; } + foreach ($searchProperties as $prop => $value) { switch ($prop) { case '{http://sabredav.org/ns}email-address': @@ -324,6 +327,7 @@ protected function searchUserPrincipals(array $searchProperties, $test = 'allof' } $carry[] = $this->principalPrefix . '/' . $user->getUID(); + return $carry; }, []); break; @@ -377,6 +381,7 @@ protected function searchUserPrincipals(array $searchProperties, $test = 'allof' } $carry[] = $this->principalPrefix . '/' . $user->getUID(); + return $carry; }, []); break; @@ -463,6 +468,7 @@ public function findByUri($uri, $principalPrefix) { if (count($users) !== 1) { return null; } + $user = $users[0]; if ($restrictGroups !== false) { @@ -475,6 +481,7 @@ public function findByUri($uri, $principalPrefix) { return $this->principalPrefix . '/' . $user->getUID(); } } + if (str_starts_with($uri, 'principal:')) { $principal = substr($uri, 10); $principal = $this->getPrincipalByPath($principal); diff --git a/apps/dav/lib/Connector/Sabre/PublicAuth.php b/apps/dav/lib/Connector/Sabre/PublicAuth.php index 3e2cd81a80051..4c0240338f65e 100644 --- a/apps/dav/lib/Connector/Sabre/PublicAuth.php +++ b/apps/dav/lib/Connector/Sabre/PublicAuth.php @@ -182,6 +182,7 @@ protected function validateUserPass($username, $password) { || $this->session->get(self::DAV_AUTHENTICATED) !== $share->getId()) { $this->session->set(self::DAV_AUTHENTICATED, $share->getId()); } + return true; } @@ -198,12 +199,14 @@ protected function validateUserPass($username, $password) { } $this->throttler->registerAttempt(self::BRUTEFORCE_ACTION, $this->request->getRemoteAddress()); + return false; } elseif ($share->getShareType() === IShare::TYPE_REMOTE) { return true; } $this->throttler->registerAttempt(self::BRUTEFORCE_ACTION, $this->request->getRemoteAddress()); + return false; } diff --git a/apps/dav/lib/Connector/Sabre/QuotaPlugin.php b/apps/dav/lib/Connector/Sabre/QuotaPlugin.php index deaa4cf672b4b..0c0ef3dc25ea1 100644 --- a/apps/dav/lib/Connector/Sabre/QuotaPlugin.php +++ b/apps/dav/lib/Connector/Sabre/QuotaPlugin.php @@ -150,6 +150,7 @@ private function getPathForDestination(string $destinationPath): string { if (!$destinationNode instanceof Node) { throw new \Exception('Invalid destination node'); } + return $destinationNode->getPath(); } @@ -185,6 +186,7 @@ public function checkQuota(string $path, $length = null) { if (is_null($parentPath)) { $parentPath = ''; } + $req = $this->server->httpRequest; // Strip any duplicate slashes @@ -211,9 +213,11 @@ public function getLength() { if (!is_numeric($ocLength)) { return $length; } + if (!is_numeric($length)) { return $ocLength; } + return max($length, $ocLength); } diff --git a/apps/dav/lib/Connector/Sabre/ServerFactory.php b/apps/dav/lib/Connector/Sabre/ServerFactory.php index 42d3ce1818ace..fbc50fa0b3351 100644 --- a/apps/dav/lib/Connector/Sabre/ServerFactory.php +++ b/apps/dav/lib/Connector/Sabre/ServerFactory.php @@ -49,7 +49,7 @@ public function __construct( IRequest $request, IPreview $previewManager, IEventDispatcher $eventDispatcher, - IL10N $l10n + IL10N $l10n, ) { $this->config = $config; $this->logger = $logger; @@ -123,6 +123,7 @@ public function createServer(string $baseUri, } else { $root = new \OCA\DAV\Connector\Sabre\File($view, $rootInfo); } + $objectTree->init($root, $view, $this->mountManager); $server->addPlugin( @@ -178,6 +179,7 @@ public function createServer(string $baseUri, ) ); } + $server->addPlugin(new \OCA\DAV\Connector\Sabre\CopyEtagHeaderPlugin()); // Load dav plugins from apps diff --git a/apps/dav/lib/Connector/Sabre/ShareTypeList.php b/apps/dav/lib/Connector/Sabre/ShareTypeList.php index abe56cd0301d6..a637778c939c0 100644 --- a/apps/dav/lib/Connector/Sabre/ShareTypeList.php +++ b/apps/dav/lib/Connector/Sabre/ShareTypeList.php @@ -55,11 +55,13 @@ public static function xmlDeserialize(Reader $reader) { if ($tree === null) { return null; } + foreach ($tree as $elem) { if ($elem['name'] === '{' . self::NS_OWNCLOUD . '}share-type') { $shareTypes[] = (int)$elem['value']; } } + return new self($shareTypes); } diff --git a/apps/dav/lib/Connector/Sabre/SharesPlugin.php b/apps/dav/lib/Connector/Sabre/SharesPlugin.php index 20e94e9aedeb0..b621126582522 100644 --- a/apps/dav/lib/Connector/Sabre/SharesPlugin.php +++ b/apps/dav/lib/Connector/Sabre/SharesPlugin.php @@ -150,6 +150,7 @@ private function getShares(DavNode $sabreNode): array { $shares = $this->getShare($node); $this->cachedShares[$sabreNode->getId()] = $shares; + return $shares; } @@ -164,7 +165,7 @@ private function getShares(DavNode $sabreNode): array { */ public function handleGetProperties( PropFind $propFind, - \Sabre\DAV\INode $sabreNode + \Sabre\DAV\INode $sabreNode, ) { if (!($sabreNode instanceof DavNode)) { return; diff --git a/apps/dav/lib/Connector/Sabre/TagList.php b/apps/dav/lib/Connector/Sabre/TagList.php index 5537acc452c9b..7ada056eade0e 100644 --- a/apps/dav/lib/Connector/Sabre/TagList.php +++ b/apps/dav/lib/Connector/Sabre/TagList.php @@ -70,11 +70,13 @@ public static function xmlDeserialize(Reader $reader) { if ($tree === null) { return null; } + foreach ($tree as $elem) { if ($elem['name'] === '{' . self::NS_OWNCLOUD . '}tag') { $tags[] = $elem['value']; } } + return new self($tags); } diff --git a/apps/dav/lib/Connector/Sabre/TagsPlugin.php b/apps/dav/lib/Connector/Sabre/TagsPlugin.php index 3f5268f513705..df29ef0f7c883 100644 --- a/apps/dav/lib/Connector/Sabre/TagsPlugin.php +++ b/apps/dav/lib/Connector/Sabre/TagsPlugin.php @@ -109,6 +109,7 @@ private function getTagger() { if (!$this->tagger) { $this->tagger = $this->tagManager->load('files'); } + return $this->tagger; } @@ -129,6 +130,7 @@ private function getTagsAndFav($fileId) { unset($tags[$favPos]); } } + return [$tags, $isFav]; } @@ -147,9 +149,11 @@ private function getTags($fileId) { if (empty($tags)) { return []; } + return current($tags); } } + return null; } @@ -168,13 +172,16 @@ private function updateTags($fileId, $tags) { if ($tag === self::TAG_FAVORITE) { continue; } + $tagger->tagAs($fileId, $tag); } + $deletedTags = array_diff($currentTags, $tags); foreach ($deletedTags as $tag) { if ($tag === self::TAG_FAVORITE) { continue; } + $tagger->unTag($fileId, $tag); } } @@ -189,7 +196,7 @@ private function updateTags($fileId, $tags) { */ public function handleGetProperties( PropFind $propFind, - \Sabre\DAV\INode $node + \Sabre\DAV\INode $node, ) { if (!($node instanceof \OCA\DAV\Connector\Sabre\Node)) { return; @@ -207,6 +214,7 @@ public function handleGetProperties( foreach ($folderContent as $info) { $fileIds[] = (int)$info->getId(); } + $tags = $this->getTagger()->getTagsForObjects($fileIds); if ($tags === false) { // the tags API returns false on error... @@ -232,6 +240,7 @@ public function handleGetProperties( if (is_null($isFav)) { [, $isFav] = $this->getTagsAndFav($node->getId()); } + if ($isFav) { return 1; } else { diff --git a/apps/dav/lib/Controller/DirectController.php b/apps/dav/lib/Controller/DirectController.php index 77f5b8f541900..80e00e93ca7c7 100644 --- a/apps/dav/lib/Controller/DirectController.php +++ b/apps/dav/lib/Controller/DirectController.php @@ -117,7 +117,7 @@ public function getUrl(int $fileId, int $expirationTime = 60 * 60 * 8): DataResp $this->mapper->insert($direct); - $url = $this->urlGenerator->getAbsoluteURL('remote.php/direct/'.$token); + $url = $this->urlGenerator->getAbsoluteURL('remote.php/direct/' . $token); return new DataResponse([ 'url' => $url, diff --git a/apps/dav/lib/Controller/OutOfOfficeController.php b/apps/dav/lib/Controller/OutOfOfficeController.php index 4978fb87737c3..668bf81839e9e 100644 --- a/apps/dav/lib/Controller/OutOfOfficeController.php +++ b/apps/dav/lib/Controller/OutOfOfficeController.php @@ -54,6 +54,7 @@ public function getCurrentOutOfOfficeData(string $userId): DataResponse { if ($user === null) { return new DataResponse(null, Http::STATUS_NOT_FOUND); } + try { $data = $this->absenceService->getCurrentAbsence($user); if ($data === null) { @@ -121,7 +122,7 @@ public function setOutOfOffice( string $status, string $message, ?string $replacementUserId, - ?string $replacementUserDisplayName + ?string $replacementUserDisplayName, ): DataResponse { $user = $this->userSession?->getUser(); @@ -182,6 +183,7 @@ public function clearOutOfOffice(): DataResponse { $this->absenceService->clearAbsence($user); $this->coordinator->clearCache($user->getUID()); + return new DataResponse(null); } } diff --git a/apps/dav/lib/DAV/CustomPropertiesBackend.php b/apps/dav/lib/DAV/CustomPropertiesBackend.php index dde97cabf375d..7ecb08a5fb51f 100644 --- a/apps/dav/lib/DAV/CustomPropertiesBackend.php +++ b/apps/dav/lib/DAV/CustomPropertiesBackend.php @@ -249,14 +249,17 @@ public function propFind($path, PropFind $propFind) { } catch (DavException $e) { continue; } + $propFind->set($propName, $propValue); } + foreach ($this->getUserProperties($path, $requestedProps) as $propName => $propValue) { try { $this->validateProperty($path, $propName, $propValue); } catch (DavException $e) { continue; } + $propFind->set($propName, $propValue); } } @@ -354,7 +357,9 @@ private function getPublishedProperties(string $path, array $requestedProperties while ($row = $result->fetch()) { $props[$row['propertyname']] = $this->decodeValueFromDatabase($row['propertyvalue'], $row['valuetype']); } + $result->closeCursor(); + return $props; } @@ -383,10 +388,12 @@ private function cacheDirectory(string $path, Directory $node): void { if (!isset($propsByPath[$childPath])) { $propsByPath[$childPath] = []; } + if (isset($row['propertyname'])) { $propsByPath[$childPath][$row['propertyname']] = $this->decodeValueFromDatabase($row['propertyvalue'], $row['valuetype']); } } + $this->userCache = array_merge($this->userCache, $propsByPath); } @@ -433,6 +440,7 @@ private function getUserProperties(string $path, array $requestedProperties) { $result->closeCursor(); $this->userCache[$path] = $props; + return $props; } @@ -544,6 +552,7 @@ private function encodeValueForDatabase(string $path, string $name, mixed $value $valueType = self::PROPERTY_TYPE_OBJECT; $value = serialize($value); } + return [$value, $valueType]; } @@ -590,6 +599,7 @@ private function createDeleteQuery(): IQueryBuilder { ->where($deleteQuery->expr()->eq('userid', $deleteQuery->createParameter('userid'))) ->andWhere($deleteQuery->expr()->eq('propertypath', $deleteQuery->createParameter('propertyPath'))) ->andWhere($deleteQuery->expr()->eq('propertyname', $deleteQuery->createParameter('propertyName'))); + return $deleteQuery; } @@ -603,6 +613,7 @@ private function createInsertQuery(): IQueryBuilder { 'propertyvalue' => $insertQuery->createParameter('propertyValue'), 'valuetype' => $insertQuery->createParameter('valueType'), ]); + return $insertQuery; } @@ -614,6 +625,7 @@ private function createUpdateQuery(): IQueryBuilder { ->where($updateQuery->expr()->eq('userid', $updateQuery->createParameter('userid'))) ->andWhere($updateQuery->expr()->eq('propertypath', $updateQuery->createParameter('propertyPath'))) ->andWhere($updateQuery->expr()->eq('propertyname', $updateQuery->createParameter('propertyName'))); + return $updateQuery; } } diff --git a/apps/dav/lib/DAV/GroupPrincipalBackend.php b/apps/dav/lib/DAV/GroupPrincipalBackend.php index 7ccc3f02594a7..0ecc093651bfb 100644 --- a/apps/dav/lib/DAV/GroupPrincipalBackend.php +++ b/apps/dav/lib/DAV/GroupPrincipalBackend.php @@ -40,7 +40,7 @@ public function __construct( IGroupManager $IGroupManager, IUserSession $userSession, IShareManager $shareManager, - IConfig $config + IConfig $config, ) { $this->groupManager = $IGroupManager; $this->userSession = $userSession; @@ -86,9 +86,11 @@ public function getPrincipalByPath($path) { if ($elements[0] !== 'principals') { return null; } + if ($elements[1] !== 'groups') { return null; } + $name = urldecode($elements[2]); $group = $this->groupManager->get($name); @@ -111,9 +113,11 @@ public function getGroupMemberSet($principal) { if ($elements[0] !== 'principals') { return []; } + if ($elements[1] !== 'groups') { return []; } + $name = $elements[2]; $group = $this->groupManager->get($name); @@ -171,9 +175,11 @@ public function searchPrincipals($prefixPath, array $searchProperties, $test = ' if (\count($searchProperties) === 0) { return []; } + if ($prefixPath !== self::PRINCIPAL_PREFIX) { return []; } + // If sharing or group sharing is disabled, return the empty array if (!$this->groupSharingEnabled()) { return []; @@ -195,6 +201,7 @@ public function searchPrincipals($prefixPath, array $searchProperties, $test = ' if ($searchLimit <= 0) { $searchLimit = null; } + foreach ($searchProperties as $prop => $value) { switch ($prop) { case '{DAV:}displayname': @@ -210,6 +217,7 @@ public function searchPrincipals($prefixPath, array $searchProperties, $test = ' } $carry[] = self::PRINCIPAL_PREFIX . '/' . urlencode($gid); + return $carry; }, []); break; diff --git a/apps/dav/lib/DAV/PublicAuth.php b/apps/dav/lib/DAV/PublicAuth.php index c2b4ada173aae..fa0aacce097d8 100644 --- a/apps/dav/lib/DAV/PublicAuth.php +++ b/apps/dav/lib/DAV/PublicAuth.php @@ -55,6 +55,7 @@ public function check(RequestInterface $request, ResponseInterface $response) { if ($this->isRequestPublic($request)) { return [true, 'principals/system/public']; } + return [false, 'No public access to this resource.']; } @@ -73,6 +74,7 @@ private function isRequestPublic(RequestInterface $request) { $matchingUrls = array_filter($this->publicURLs, function ($publicUrl) use ($url) { return str_starts_with($url, $publicUrl); }); + return !empty($matchingUrls); } } diff --git a/apps/dav/lib/DAV/Sharing/Backend.php b/apps/dav/lib/DAV/Sharing/Backend.php index 5630336df4276..ede48a51d11a2 100644 --- a/apps/dav/lib/DAV/Sharing/Backend.php +++ b/apps/dav/lib/DAV/Sharing/Backend.php @@ -27,7 +27,8 @@ abstract class Backend { private ICache $shareCache; - public function __construct(private IUserManager $userManager, + public function __construct( + private IUserManager $userManager, private IGroupManager $groupManager, private Principal $principalBackend, private ICacheFactory $cacheFactory, @@ -76,6 +77,7 @@ public function updateShares(IShareable $shareable, array $add, array $remove, a $this->service->shareWith($shareable->getResourceId(), $principal, $access); } + foreach ($remove as $element) { $principal = $this->principalBackend->findByUri($element, ''); if (empty($principal)) { @@ -141,7 +143,9 @@ public function getShares(int $resourceId): array { '{http://owncloud.org/ns}group-share' => isset($p['uri']) && (str_starts_with($p['uri'], 'principals/groups') || str_starts_with($p['uri'], 'principals/circles')) ]; } + $this->shareCache->set((string)$resourceId, $shares); + return $shares; } @@ -201,6 +205,7 @@ public function applyShareAcl(array $shares, array $acl): array { ]; } } + return $acl; } } diff --git a/apps/dav/lib/DAV/Sharing/Plugin.php b/apps/dav/lib/DAV/Sharing/Plugin.php index c228d6993c20c..ed7e83d4da30b 100644 --- a/apps/dav/lib/DAV/Sharing/Plugin.php +++ b/apps/dav/lib/DAV/Sharing/Plugin.php @@ -200,6 +200,7 @@ public function propFind(PropFind $propFind, INode $node) { $backend->preloadShares($resourceIds); } } + if ($node instanceof IShareable) { $propFind->handle('{' . Plugin::NS_OWNCLOUD . '}invite', function () use ($node) { return new Invite( diff --git a/apps/dav/lib/DAV/Sharing/SharingMapper.php b/apps/dav/lib/DAV/Sharing/SharingMapper.php index 59483ac9471a9..bab2f823a758e 100644 --- a/apps/dav/lib/DAV/Sharing/SharingMapper.php +++ b/apps/dav/lib/DAV/Sharing/SharingMapper.php @@ -11,7 +11,9 @@ use OCP\IDBConnection; class SharingMapper { - public function __construct(private IDBConnection $db) { + public function __construct( + private IDBConnection $db, + ) { } protected function getSharesForIdByAccess(int $resourceId, string $resourceType, bool $sharesWithAccess): array { @@ -31,6 +33,7 @@ protected function getSharesForIdByAccess(int $resourceId, string $resourceType, $result = $query->executeQuery(); $rows = $result->fetchAll(); $result->closeCursor(); + return $rows; } @@ -54,6 +57,7 @@ public function getSharesForIds(array $resourceIds, string $resourceType): array $rows = $result->fetchAll(); $result->closeCursor(); + return $rows; } diff --git a/apps/dav/lib/DAV/Sharing/SharingService.php b/apps/dav/lib/DAV/Sharing/SharingService.php index de280d2c34712..b9ac36ea066fa 100644 --- a/apps/dav/lib/DAV/Sharing/SharingService.php +++ b/apps/dav/lib/DAV/Sharing/SharingService.php @@ -9,7 +9,9 @@ abstract class SharingService { protected string $resourceType = ''; - public function __construct(protected SharingMapper $mapper) { + public function __construct( + protected SharingMapper $mapper, + ) { } public function getResourceType(): string { diff --git a/apps/dav/lib/DAV/Sharing/Xml/Invite.php b/apps/dav/lib/DAV/Sharing/Xml/Invite.php index 91eda03f45ba3..e631b9a98d50a 100644 --- a/apps/dav/lib/DAV/Sharing/Xml/Invite.php +++ b/apps/dav/lib/DAV/Sharing/Xml/Invite.php @@ -110,12 +110,15 @@ public function xmlSerialize(Writer $writer) { if (isset($this->organizer['commonName']) && $this->organizer['commonName']) { $writer->writeElement($cs . 'common-name', $this->organizer['commonName']); } + if (isset($this->organizer['firstName']) && $this->organizer['firstName']) { $writer->writeElement($cs . 'first-name', $this->organizer['firstName']); } + if (isset($this->organizer['lastName']) && $this->organizer['lastName']) { $writer->writeElement($cs . 'last-name', $this->organizer['lastName']); } + $writer->endElement(); // organizer } @@ -125,6 +128,7 @@ public function xmlSerialize(Writer $writer) { if (isset($user['commonName']) && $user['commonName']) { $writer->writeElement($cs . 'common-name', $user['commonName']); } + $writer->writeElement($cs . 'invite-accepted'); $writer->startElement($cs . 'access'); @@ -133,6 +137,7 @@ public function xmlSerialize(Writer $writer) { } else { $writer->writeElement($cs . 'read-write'); } + $writer->endElement(); // access if (isset($user['summary']) && $user['summary']) { diff --git a/apps/dav/lib/DAV/Sharing/Xml/ShareRequest.php b/apps/dav/lib/DAV/Sharing/Xml/ShareRequest.php index ca9602fb8c200..aa3a880df6b1c 100644 --- a/apps/dav/lib/DAV/Sharing/Xml/ShareRequest.php +++ b/apps/dav/lib/DAV/Sharing/Xml/ShareRequest.php @@ -29,7 +29,7 @@ public function __construct(array $set, array $remove) { public static function xmlDeserialize(Reader $reader) { $elements = $reader->parseInnerTree([ - '{' . Plugin::NS_OWNCLOUD. '}set' => 'Sabre\\Xml\\Element\\KeyValue', + '{' . Plugin::NS_OWNCLOUD . '}set' => 'Sabre\\Xml\\Element\\KeyValue', '{' . Plugin::NS_OWNCLOUD . '}remove' => 'Sabre\\Xml\\Element\\KeyValue', ]); diff --git a/apps/dav/lib/DAV/SystemPrincipalBackend.php b/apps/dav/lib/DAV/SystemPrincipalBackend.php index 9760d68f05fbf..f41ac4ad0a55c 100644 --- a/apps/dav/lib/DAV/SystemPrincipalBackend.php +++ b/apps/dav/lib/DAV/SystemPrincipalBackend.php @@ -58,13 +58,16 @@ public function getPrincipalByPath($path) { 'uri' => 'principals/system/system', '{DAV:}displayname' => 'system', ]; + return $principal; } + if ($path === 'principals/system/public') { $principal = [ 'uri' => 'principals/system/public', '{DAV:}displayname' => 'public', ]; + return $principal; } @@ -156,6 +159,7 @@ public function getGroupMembership($principal) { return []; } + return []; } diff --git a/apps/dav/lib/DAV/ViewOnlyPlugin.php b/apps/dav/lib/DAV/ViewOnlyPlugin.php index d8c8649b4b7e0..fbf967e31438b 100644 --- a/apps/dav/lib/DAV/ViewOnlyPlugin.php +++ b/apps/dav/lib/DAV/ViewOnlyPlugin.php @@ -85,6 +85,7 @@ public function checkViewOnly(RequestInterface $request): bool { if (!$storage->instanceOfStorage(ISharedStorage::class)) { return true; } + // Extract extra permissions /** @var ISharedStorage $storage */ $share = $storage->getShare(); diff --git a/apps/dav/lib/Db/Absence.php b/apps/dav/lib/Db/Absence.php index d7cd46087c373..ee894072d37b4 100644 --- a/apps/dav/lib/Db/Absence.php +++ b/apps/dav/lib/Db/Absence.php @@ -65,6 +65,7 @@ public function toOutOufOfficeData(IUser $user, string $timezone): IOutOfOfficeD if ($user->getUID() !== $this->getUserId()) { throw new InvalidArgumentException("The user doesn't match the user id of this absence! Expected " . $this->getUserId() . ', got ' . $user->getUID()); } + if ($this->getId() === null) { throw new Exception('Creating out-of-office data without ID'); } @@ -73,6 +74,7 @@ public function toOutOufOfficeData(IUser $user, string $timezone): IOutOfOfficeD $startDate = new DateTime($this->getFirstDay(), $tz); $endDate = new DateTime($this->getLastDay(), $tz); $endDate->setTime(23, 59); + return new OutOfOfficeData( (string)$this->getId(), $user, diff --git a/apps/dav/lib/Db/PropertyMapper.php b/apps/dav/lib/Db/PropertyMapper.php index a0ecb348ba419..d60bcf2034513 100644 --- a/apps/dav/lib/Db/PropertyMapper.php +++ b/apps/dav/lib/Db/PropertyMapper.php @@ -35,6 +35,7 @@ public function findPropertyByPathAndName(string $userId, string $path, string $ $selectQb->expr()->eq('propertypath', $selectQb->createNamedParameter($path)), $selectQb->expr()->eq('propertyname', $selectQb->createNamedParameter($name)), ); + return $this->findEntities($selectQb); } diff --git a/apps/dav/lib/Direct/DirectFile.php b/apps/dav/lib/Direct/DirectFile.php index 3c684b83ef2fd..3c730dd7cfd57 100644 --- a/apps/dav/lib/Direct/DirectFile.php +++ b/apps/dav/lib/Direct/DirectFile.php @@ -95,6 +95,7 @@ private function getFile() { if (!$file) { throw new NotFound(); } + if (!$file instanceof File) { throw new Forbidden('direct download not allowed on directories'); } diff --git a/apps/dav/lib/Direct/DirectHome.php b/apps/dav/lib/Direct/DirectHome.php index 36ff8b213cb80..66fe6f0f3d437 100644 --- a/apps/dav/lib/Direct/DirectHome.php +++ b/apps/dav/lib/Direct/DirectHome.php @@ -46,7 +46,7 @@ public function __construct( ITimeFactory $timeFactory, IThrottler $throttler, IRequest $request, - IEventDispatcher $eventDispatcher + IEventDispatcher $eventDispatcher, ) { $this->rootFolder = $rootFolder; $this->mapper = $mapper; diff --git a/apps/dav/lib/Files/ErrorPagePlugin.php b/apps/dav/lib/Files/ErrorPagePlugin.php index 2b93f0e7a49ac..6732bd0bcf45b 100644 --- a/apps/dav/lib/Files/ErrorPagePlugin.php +++ b/apps/dav/lib/Files/ErrorPagePlugin.php @@ -45,6 +45,7 @@ public function logException(\Throwable $ex): void { $httpCode = 500; $headers = []; } + $this->server->httpResponse->addHeaders($headers); $this->server->httpResponse->setStatus($httpCode); $body = $this->generateBody($ex, $httpCode); @@ -84,6 +85,7 @@ public function generateBody(\Throwable $ex, int $httpCode): mixed { $content->assign('file', $ex->getFile()); $content->assign('line', $ex->getLine()); $content->assign('exception', $ex); + return $content->fetchPage(); } @@ -101,6 +103,7 @@ private function acceptHtml(): bool { return true; } } + return false; } } diff --git a/apps/dav/lib/Files/FileSearchBackend.php b/apps/dav/lib/Files/FileSearchBackend.php index 3bc729d640727..289d28337ef13 100644 --- a/apps/dav/lib/Files/FileSearchBackend.php +++ b/apps/dav/lib/Files/FileSearchBackend.php @@ -210,8 +210,10 @@ public function search(Query $search): array { } else { $davNode = new \OCA\DAV\Connector\Sabre\File($this->view, $node, $this->shareManager); } + $path = $this->getHrefForNode($node); $this->tree->cacheNode($davNode, $path); + return new SearchResult($davNode, $path); }, $results); @@ -240,9 +242,11 @@ private function sort(SearchResult $a, SearchResult $b, array $orders) { if ($v1 === null && $v2 === null) { continue; } + if ($v1 === null) { return $order->order === Order::ASC ? 1 : -1; } + if ($v2 === null) { return $order->order === Order::ASC ? -1 : 1; } @@ -255,6 +259,7 @@ private function sort(SearchResult $a, SearchResult $b, array $orders) { if ($order->order === Order::DESC) { $s = -$s; } + return $s; } @@ -269,17 +274,21 @@ private function compareProperties($a, $b, Order $order) { if ($a === $b) { return 0; } + if ($a === false) { return -1; } + return 1; default: if ($a === $b) { return 0; } + if ($a < $b) { return -1; } + return 1; } } @@ -369,6 +378,7 @@ private function countSearchOperators(Operator $operator): int { case Operator::OPERATION_NOT: /** @var Operator[] $arguments */ $arguments = $operator->arguments; + return array_sum(array_map([$this, 'countSearchOperators'], $arguments)); case Operator::OPERATION_EQUAL: case Operator::OPERATION_GREATER_OR_EQUAL_THAN: @@ -393,6 +403,7 @@ private function transformSearchOperation(Operator $operator) { case Operator::OPERATION_OR: case Operator::OPERATION_NOT: $arguments = array_map([$this, 'transformSearchOperation'], $operator->arguments); + return new SearchBinaryOperator($trimmedType, $arguments); case Operator::OPERATION_EQUAL: case Operator::OPERATION_GREATER_OR_EQUAL_THAN: @@ -403,15 +414,18 @@ private function transformSearchOperation(Operator $operator) { if (count($operator->arguments) !== 2) { throw new \InvalidArgumentException('Invalid number of arguments for ' . $trimmedType . ' operation'); } + if (!($operator->arguments[1] instanceof Literal)) { throw new \InvalidArgumentException('Invalid argument 2 for ' . $trimmedType . ' operation, expected literal'); } + $value = $operator->arguments[1]->value; // no break case Operator::OPERATION_IS_DEFINED: if (!($operator->arguments[0] instanceof SearchPropertyDefinition)) { throw new \InvalidArgumentException('Invalid argument 1 for ' . $trimmedType . ' operation, expected property'); } + $property = $operator->arguments[0]; if (str_starts_with($property->name, FilesPlugin::FILE_METADATA_PREFIX)) { @@ -476,7 +490,9 @@ private function castValue(SearchPropertyDefinition $property, $value) { if (is_numeric($value)) { return max(0, 0 + $value); } + $date = \DateTime::createFromFormat(\DateTimeInterface::ATOM, (string)$value); + return ($date instanceof \DateTime && $date->getTimestamp() !== false) ? $date->getTimestamp() : 0; default: return $value; @@ -497,6 +513,7 @@ private function extractWhereValue(Operator &$operator, string $propertyName, st return $value; } } + return null; case Operator::OPERATION_EQUAL: case Operator::OPERATION_GREATER_OR_EQUAL_THAN: @@ -526,6 +543,7 @@ private function extractWhereValue(Operator &$operator, string $propertyName, st } else { return null; } + // no break default: return null; diff --git a/apps/dav/lib/Files/LazySearchBackend.php b/apps/dav/lib/Files/LazySearchBackend.php index a0ad730ff2bc9..d6c4087c12006 100644 --- a/apps/dav/lib/Files/LazySearchBackend.php +++ b/apps/dav/lib/Files/LazySearchBackend.php @@ -30,6 +30,7 @@ public function isValidScope(string $href, $depth, ?string $path): bool { if ($this->backend) { return $this->backend->getArbiterPath(); } + return false; } @@ -37,6 +38,7 @@ public function getPropertyDefinitionsForScope(string $href, ?String $path): arr if ($this->backend) { return $this->backend->getPropertyDefinitionsForScope($href, $path); } + return []; } @@ -44,6 +46,7 @@ public function search(Query $query): array { if ($this->backend) { return $this->backend->search($query); } + return []; } diff --git a/apps/dav/lib/Files/RootCollection.php b/apps/dav/lib/Files/RootCollection.php index d538d15ec1b75..82eef40d0de5d 100644 --- a/apps/dav/lib/Files/RootCollection.php +++ b/apps/dav/lib/Files/RootCollection.php @@ -33,10 +33,12 @@ public function getChildForPrincipal(array $principalInfo) { // in the future this could be considered to be used for accessing shared files return new SimpleCollection($name); } + $userFolder = \OC::$server->getUserFolder(); if (!($userFolder instanceof FileInfo)) { throw new \Exception('Home does not exist'); } + return new FilesHome($principalInfo, $userFolder); } diff --git a/apps/dav/lib/Files/Sharing/FilesDropPlugin.php b/apps/dav/lib/Files/Sharing/FilesDropPlugin.php index 69328d42272c6..5925584a70909 100644 --- a/apps/dav/lib/Files/Sharing/FilesDropPlugin.php +++ b/apps/dav/lib/Files/Sharing/FilesDropPlugin.php @@ -80,6 +80,7 @@ public function beforeMethod(RequestInterface $request, ResponseInterface $respo if (!($this->view->file_exists($nickName) === true)) { $this->view->mkdir($nickName); } + // Put all files in the subfolder $path = $nickName . '/' . $path; } diff --git a/apps/dav/lib/HookManager.php b/apps/dav/lib/HookManager.php index e3f18febb8098..df17759b5e026 100644 --- a/apps/dav/lib/HookManager.php +++ b/apps/dav/lib/HookManager.php @@ -119,6 +119,7 @@ public function postDeleteUser($params) { $subscription['id'], ); } + $this->calDav->deleteAllSharesByUser('principals/users/' . $uid); foreach ($this->addressBooksToDelete as $addressBook) { @@ -159,6 +160,7 @@ public function firstLogin(?IUser $user = null) { \OC::$server->get(LoggerInterface::class)->error($e->getMessage(), ['exception' => $e]); } } + if ($this->cardDav->getAddressBooksForUserCount($principal) === 0) { try { $this->cardDav->createAddressBook($principal, CardDavBackend::PERSONAL_ADDRESSBOOK_URI, [ diff --git a/apps/dav/lib/Listener/AddMissingIndicesListener.php b/apps/dav/lib/Listener/AddMissingIndicesListener.php index 035c6c9582e3b..8a615a1c464b4 100644 --- a/apps/dav/lib/Listener/AddMissingIndicesListener.php +++ b/apps/dav/lib/Listener/AddMissingIndicesListener.php @@ -20,6 +20,7 @@ public function handle(Event $event): void { if (!($event instanceof AddMissingIndicesEvent)) { return; } + $event->addMissingIndex( 'dav_shares', 'dav_shares_resourceid_type', diff --git a/apps/dav/lib/Listener/CalendarContactInteractionListener.php b/apps/dav/lib/Listener/CalendarContactInteractionListener.php index b4eab7b654482..bf08faf48a22b 100644 --- a/apps/dav/lib/Listener/CalendarContactInteractionListener.php +++ b/apps/dav/lib/Listener/CalendarContactInteractionListener.php @@ -71,6 +71,7 @@ public function handle(Event $event): void { if (!isset($share['href'])) { continue; } + $this->emitFromUri($share['href'], $user); } @@ -99,6 +100,7 @@ public function handle(Event $event): void { // Nothing to work with continue; } + $this->emitFromUri($added['href'], $user); } } @@ -113,6 +115,7 @@ private function emitFromUri(string $uri, IUser $user): void { // Invalid principal return; } + if (!str_starts_with($principal, self::URI_USERS)) { // Not a user principal return; @@ -146,6 +149,7 @@ private function emitFromObject(VEvent $vevent, IUser $user): void { // Doesn't look like an email continue; } + $email = substr($mailTo, strlen('mailto:')); if (!$this->mailer->validateMailAddress($email)) { // This really isn't a valid email diff --git a/apps/dav/lib/Listener/OutOfOfficeListener.php b/apps/dav/lib/Listener/OutOfOfficeListener.php index e0997e8182b67..ce42ebd8ca1fb 100644 --- a/apps/dav/lib/Listener/OutOfOfficeListener.php +++ b/apps/dav/lib/Listener/OutOfOfficeListener.php @@ -39,7 +39,7 @@ public function __construct( private ServerFactory $serverFactory, private IConfig $appConfig, private TimezoneService $timezoneService, - private LoggerInterface $logger + private LoggerInterface $logger, ) { } @@ -51,6 +51,7 @@ public function handle(Event $event): void { if ($calendarNode === null) { return; } + $tzId = $this->timezoneService->getUserTimezone($userId) ?? $this->timezoneService->getDefaultTimezone(); $vCalendarEvent = $this->createVCalendarEvent($event->getData(), $tzId); $stream = fopen('php://memory', 'rb+'); @@ -71,11 +72,13 @@ public function handle(Event $event): void { if ($calendarNode === null) { return; } + $tzId = $this->timezoneService->getUserTimezone($userId) ?? $this->timezoneService->getDefaultTimezone(); $vCalendarEvent = $this->createVCalendarEvent($event->getData(), $tzId); try { $oldEvent = $calendarNode->getChild($this->getEventFileName($event->getData()->getId())); $oldEvent->put($vCalendarEvent->serialize()); + return; } catch (NotFound) { $stream = fopen('php://memory', 'rb+'); @@ -97,6 +100,7 @@ public function handle(Event $event): void { if ($calendarNode === null) { return; } + try { $oldEvent = $calendarNode->getChild($this->getEventFileName($event->getData()->getId())); $oldEvent->delete(); @@ -118,6 +122,7 @@ private function getCalendarNode(string $principal, string $userId): ?Calendar { $this->logger->debug('Principal has no calendar home path'); return null; } + try { /** @var CalendarHome $calendarHome */ $calendarHome = $server->tree->getNodeForPath($calendarHomePath); @@ -125,8 +130,10 @@ private function getCalendarNode(string $principal, string $userId): ?Calendar { $this->logger->debug('Calendar home not found', [ 'exception' => $e, ]); + return null; } + $uri = $this->appConfig->getUserValue($userId, 'dav', 'defaultCalendar', CalDavBackend::PERSONAL_CALENDAR_URI); try { $calendarNode = $calendarHome->getChild($uri); @@ -135,12 +142,15 @@ private function getCalendarNode(string $principal, string $userId): ?Calendar { 'exception' => $e, 'uri' => $uri, ]); + return null; } + if (!($calendarNode instanceof Calendar)) { $this->logger->warning('Personal calendar node is not a calendar'); return null; } + if ($calendarNode->isDeleted()) { $this->logger->warning('Personal calendar has been deleted'); return null; @@ -174,6 +184,7 @@ private function createVCalendarEvent(IOutOfOfficeData $data, string $tzId): VCa 'DTEND' => $end, 'X-NEXTCLOUD-OUT-OF-OFFICE' => $data->getId(), ]); + return $vCalendar; } } diff --git a/apps/dav/lib/Listener/TrustedServerRemovedListener.php b/apps/dav/lib/Listener/TrustedServerRemovedListener.php index 36cb37b88ec92..89492f4974b66 100644 --- a/apps/dav/lib/Listener/TrustedServerRemovedListener.php +++ b/apps/dav/lib/Listener/TrustedServerRemovedListener.php @@ -25,6 +25,7 @@ public function handle(Event $event): void { if (!$event instanceof TrustedServerRemovedEvent) { return; } + $addressBookUri = $event->getUrlHash(); $addressBook = $this->cardDavBackend->getAddressBooksByUri('principals/system/system', $addressBookUri); if (!is_null($addressBook)) { diff --git a/apps/dav/lib/Migration/BuildCalendarSearchIndexBackgroundJob.php b/apps/dav/lib/Migration/BuildCalendarSearchIndexBackgroundJob.php index 4c87e25c6986d..da8f31e7d3d59 100644 --- a/apps/dav/lib/Migration/BuildCalendarSearchIndexBackgroundJob.php +++ b/apps/dav/lib/Migration/BuildCalendarSearchIndexBackgroundJob.php @@ -21,7 +21,7 @@ public function __construct( private CalDavBackend $calDavBackend, private LoggerInterface $logger, private IJobList $jobList, - ITimeFactory $timeFactory + ITimeFactory $timeFactory, ) { parent::__construct($timeFactory); } @@ -30,7 +30,7 @@ public function run($arguments) { $offset = (int)$arguments['offset']; $stopAt = (int)$arguments['stopAt']; - $this->logger->info('Building calendar index (' . $offset .'/' . $stopAt . ')'); + $this->logger->info('Building calendar index (' . $offset . '/' . $stopAt . ')'); $startTime = $this->time->getTime(); while (($this->time->getTime() - $startTime) < 15) { diff --git a/apps/dav/lib/Migration/BuildSocialSearchIndexBackgroundJob.php b/apps/dav/lib/Migration/BuildSocialSearchIndexBackgroundJob.php index 138f764f1e6c0..072a4feacc62e 100644 --- a/apps/dav/lib/Migration/BuildSocialSearchIndexBackgroundJob.php +++ b/apps/dav/lib/Migration/BuildSocialSearchIndexBackgroundJob.php @@ -31,7 +31,7 @@ public function run($arguments) { $offset = $arguments['offset']; $stopAt = $arguments['stopAt']; - $this->logger->info('Indexing social profile data (' . $offset .'/' . $stopAt . ')'); + $this->logger->info('Indexing social profile data (' . $offset . '/' . $stopAt . ')'); $offset = $this->buildIndex($offset, $stopAt); diff --git a/apps/dav/lib/Migration/CalDAVRemoveEmptyValue.php b/apps/dav/lib/Migration/CalDAVRemoveEmptyValue.php index c7f57dcb11718..7dc210c85a9b6 100644 --- a/apps/dav/lib/Migration/CalDAVRemoveEmptyValue.php +++ b/apps/dav/lib/Migration/CalDAVRemoveEmptyValue.php @@ -63,11 +63,13 @@ public function run(IOutput $output) { $count++; } } + $output->finishProgress(); if ($warnings > 0) { $output->warning(sprintf('%d events could not be updated, see log file for more information', $warnings)); } + if ($count > 0) { $output->info(sprintf('Updated %d events', $count)); } @@ -100,8 +102,10 @@ protected function getInvalidObjects($pattern) { $rows[] = $row; } } + $result->closeCursor(); } + return $rows; } diff --git a/apps/dav/lib/Migration/ChunkCleanup.php b/apps/dav/lib/Migration/ChunkCleanup.php index b4d5f48897e90..01053002fd987 100644 --- a/apps/dav/lib/Migration/ChunkCleanup.php +++ b/apps/dav/lib/Migration/ChunkCleanup.php @@ -68,6 +68,7 @@ public function run(IOutput $output) { foreach ($uploads as $upload) { $this->jobList->add(UploadCleanup::class, ['uid' => $user->getUID(), 'folder' => $upload->getName()]); } + $output->advance(); }); $output->finishProgress(); diff --git a/apps/dav/lib/Migration/DeleteSchedulingObjects.php b/apps/dav/lib/Migration/DeleteSchedulingObjects.php index 3919236788bb3..3cb3c9c9b109b 100644 --- a/apps/dav/lib/Migration/DeleteSchedulingObjects.php +++ b/apps/dav/lib/Migration/DeleteSchedulingObjects.php @@ -16,9 +16,10 @@ use OCP\Migration\IRepairStep; class DeleteSchedulingObjects implements IRepairStep { - public function __construct(private IJobList $jobList, + public function __construct( + private IJobList $jobList, private ITimeFactory $time, - private CalDavBackend $calDavBackend + private CalDavBackend $calDavBackend, ) { } diff --git a/apps/dav/lib/Migration/RemoveClassifiedEventActivity.php b/apps/dav/lib/Migration/RemoveClassifiedEventActivity.php index 1b97ce6a25412..9783716bcebb1 100644 --- a/apps/dav/lib/Migration/RemoveClassifiedEventActivity.php +++ b/apps/dav/lib/Migration/RemoveClassifiedEventActivity.php @@ -71,6 +71,7 @@ protected function removePrivateEventActivity(): int { ->setParameter('event_uid', '%' . $this->connection->escapeLikeParameter('{"id":"' . $row['uid'] . '"') . '%'); $deletedEvents += $delete->execute(); } + $result->closeCursor(); return $deletedEvents; @@ -106,6 +107,7 @@ protected function removeConfidentialUncensoredEventActivity(): int { ->setParameter('filtered_name', '%' . $this->connection->escapeLikeParameter('{"id":"' . $row['uid'] . '","name":"Busy"') . '%'); $deletedEvents += $delete->execute(); } + $result->closeCursor(); return $deletedEvents; diff --git a/apps/dav/lib/Migration/RemoveDeletedUsersCalendarSubscriptions.php b/apps/dav/lib/Migration/RemoveDeletedUsersCalendarSubscriptions.php index 0122ed37b06ff..84b9b0b30ea6b 100644 --- a/apps/dav/lib/Migration/RemoveDeletedUsersCalendarSubscriptions.php +++ b/apps/dav/lib/Migration/RemoveDeletedUsersCalendarSubscriptions.php @@ -55,6 +55,7 @@ public function run(IOutput $output) { $this->progress += self::SUBSCRIPTIONS_CHUNK_SIZE; $output->advance(min(self::SUBSCRIPTIONS_CHUNK_SIZE, $nbSubscriptions)); } + $output->finishProgress(); $this->deleteOrphanSubscriptions(); @@ -99,6 +100,7 @@ private function checkSubscriptions(): void { $this->orphanSubscriptionIds[] = (int)$row['id']; } } + $result->closeCursor(); } diff --git a/apps/dav/lib/Migration/RemoveOrphanEventsAndContacts.php b/apps/dav/lib/Migration/RemoveOrphanEventsAndContacts.php index 798fbad40183b..65e720bc0e545 100644 --- a/apps/dav/lib/Migration/RemoveOrphanEventsAndContacts.php +++ b/apps/dav/lib/Migration/RemoveOrphanEventsAndContacts.php @@ -73,6 +73,7 @@ protected function removeOrphanChildren($childTable, $parentTable, $parentId): i while ($row = $result->fetch()) { $orphanItems[] = (int)$row['id']; } + $result->closeCursor(); if (!empty($orphanItems)) { diff --git a/apps/dav/lib/Migration/Version1004Date20170825134824.php b/apps/dav/lib/Migration/Version1004Date20170825134824.php index 54c4c1947784c..01361532763f3 100644 --- a/apps/dav/lib/Migration/Version1004Date20170825134824.php +++ b/apps/dav/lib/Migration/Version1004Date20170825134824.php @@ -475,6 +475,7 @@ public function changeSchema(IOutput $output, \Closure $schemaClosure, array $op $table->addIndex(['resourceid', 'type'], 'dav_shares_resourceid_type'); $table->addIndex(['resourceid', 'access'], 'dav_shares_resourceid_access'); } + return $schema; } } diff --git a/apps/dav/lib/Migration/Version1006Date20180628111625.php b/apps/dav/lib/Migration/Version1006Date20180628111625.php index 5f3aa4b6fe2bd..aac335a30630d 100644 --- a/apps/dav/lib/Migration/Version1006Date20180628111625.php +++ b/apps/dav/lib/Migration/Version1006Date20180628111625.php @@ -35,6 +35,7 @@ public function changeSchema(IOutput $output, \Closure $schemaClosure, array $op if ($calendarChangesTable->hasIndex('calendarid_synctoken')) { $calendarChangesTable->dropIndex('calendarid_synctoken'); } + $calendarChangesTable->addIndex(['calendarid', 'calendartype', 'synctoken'], 'calid_type_synctoken'); } @@ -48,6 +49,7 @@ public function changeSchema(IOutput $output, \Closure $schemaClosure, array $op if ($calendarObjectsTable->hasIndex('calobjects_index')) { $calendarObjectsTable->dropIndex('calobjects_index'); } + $calendarObjectsTable->addUniqueIndex(['calendarid', 'calendartype', 'uri'], 'calobjects_index'); } @@ -62,9 +64,11 @@ public function changeSchema(IOutput $output, \Closure $schemaClosure, array $op if ($calendarObjectsPropsTable->hasIndex('calendarobject_index')) { $calendarObjectsPropsTable->dropIndex('calendarobject_index'); } + if ($calendarObjectsPropsTable->hasIndex('calendarobject_name_index')) { $calendarObjectsPropsTable->dropIndex('calendarobject_name_index'); } + if ($calendarObjectsPropsTable->hasIndex('calendarobject_value_index')) { $calendarObjectsPropsTable->dropIndex('calendarobject_value_index'); } diff --git a/apps/dav/lib/Migration/Version1024Date20211221144219.php b/apps/dav/lib/Migration/Version1024Date20211221144219.php index 656a50809cd79..0f3f4ba803c01 100644 --- a/apps/dav/lib/Migration/Version1024Date20211221144219.php +++ b/apps/dav/lib/Migration/Version1024Date20211221144219.php @@ -43,6 +43,7 @@ public function changeSchema(IOutput $output, Closure $schemaClosure, array $opt if ($propertiesTable->hasColumn('valuetype')) { return null; } + $propertiesTable->addColumn('valuetype', Types::SMALLINT, [ 'notnull' => false, 'default' => CustomPropertiesBackend::PROPERTY_TYPE_STRING diff --git a/apps/dav/lib/Migration/Version1027Date20230504122946.php b/apps/dav/lib/Migration/Version1027Date20230504122946.php index 9e1d1701934d1..84e4baa823b02 100644 --- a/apps/dav/lib/Migration/Version1027Date20230504122946.php +++ b/apps/dav/lib/Migration/Version1027Date20230504122946.php @@ -20,10 +20,12 @@ use Throwable; class Version1027Date20230504122946 extends SimpleMigrationStep { - public function __construct(private SyncService $syncService, + public function __construct( + private SyncService $syncService, private LoggerInterface $logger, private IUserManager $userManager, - private IConfig $config) { + private IConfig $config, + ) { } /** * @param IOutput $output @@ -34,6 +36,7 @@ public function postSchemaChange(IOutput $output, Closure $schemaClosure, array if ($this->userManager->countSeenUsers() > 100 || array_sum($this->userManager->countUsers()) > 100) { $this->config->setAppValue('dav', 'needs_system_address_book_sync', 'yes'); $output->info('Could not sync system address books during update - too many user records have been found. Please call occ dav:sync-system-addressbook manually.'); + return; } diff --git a/apps/dav/lib/Migration/Version1029Date20221114151721.php b/apps/dav/lib/Migration/Version1029Date20221114151721.php index dba5e0b1a488a..61e2384198931 100644 --- a/apps/dav/lib/Migration/Version1029Date20221114151721.php +++ b/apps/dav/lib/Migration/Version1029Date20221114151721.php @@ -32,6 +32,7 @@ public function changeSchema(IOutput $output, Closure $schemaClosure, array $opt $calendarObjectsTable->addIndex(['classification'], 'calobj_clssfction_index'); return $schema; } + return null; } diff --git a/apps/dav/lib/Provisioning/Apple/AppleProvisioningPlugin.php b/apps/dav/lib/Provisioning/Apple/AppleProvisioningPlugin.php index 54818a890f393..fcf095f71d637 100644 --- a/apps/dav/lib/Provisioning/Apple/AppleProvisioningPlugin.php +++ b/apps/dav/lib/Provisioning/Apple/AppleProvisioningPlugin.php @@ -59,7 +59,7 @@ public function __construct( \OC_Defaults $themingDefaults, IRequest $request, IL10N $l10n, - \Closure $uuidClosure + \Closure $uuidClosure, ) { $this->userSession = $userSession; $this->urlGenerator = $urlGenerator; @@ -110,6 +110,7 @@ public function httpGet(RequestInterface $request, ResponseInterface $response): } else { $serverPort = 443; } + $server_url = $parsedUrl['host']; $description = $this->themingDefaults->getName(); diff --git a/apps/dav/lib/Search/ContactsSearchProvider.php b/apps/dav/lib/Search/ContactsSearchProvider.php index 158c0d0813e3a..849ff45c335bc 100644 --- a/apps/dav/lib/Search/ContactsSearchProvider.php +++ b/apps/dav/lib/Search/ContactsSearchProvider.php @@ -130,6 +130,7 @@ private function getPersonDisplayName(?IFilter $person): ?string { if ($user instanceof IUser) { return $user->getDisplayName(); } + return null; } diff --git a/apps/dav/lib/Search/EventsSearchProvider.php b/apps/dav/lib/Search/EventsSearchProvider.php index 4747635bd7ea4..c860cc7176c07 100644 --- a/apps/dav/lib/Search/EventsSearchProvider.php +++ b/apps/dav/lib/Search/EventsSearchProvider.php @@ -115,6 +115,7 @@ public function search( ] ); } + /** @var IUser|null $person */ $person = $query->getFilter('person')?->get(); $personDisplayName = $person?->getDisplayName(); @@ -144,9 +145,11 @@ public function search( // Duplicate continue; } + $searchResults[] = $attendeeResult; } } + $formattedResults = \array_map(function (array $eventRow) use ($calendarsById, $subscriptionsById): SearchResultEntry { $component = $this->getPrimaryComponent($eventRow['calendardata'], self::$componentType); $title = (string)($component->SUMMARY ?? $this->l10n->t('Untitled event')); @@ -157,6 +160,7 @@ public function search( } else { $calendar = $subscriptionsById[$eventRow['calendarid']]; } + $resourceUrl = $this->getDeepLinkToCalendarApp($calendar['principaluri'], $calendar['uri'], $eventRow['uri']); $result = new SearchResultEntry('', $title, $subline, $resourceUrl, 'icon-calendar-dark', false); @@ -194,7 +198,7 @@ protected function getDeepLinkToCalendarApp( protected function getDavUrlForCalendarObject( string $principalUri, string $calendarUri, - string $calendarObjectUri + string $calendarObjectUri, ): string { [,, $principalId] = explode('/', $principalUri, 3); @@ -219,6 +223,7 @@ protected function generateSubline(Component $eventComponent): string { $formattedStart = $this->l10n->l('date', $startDateTime, ['width' => 'medium']); $formattedEnd = $this->l10n->l('date', $endDateTime, ['width' => 'medium']); + return "$formattedStart - $formattedEnd"; } diff --git a/apps/dav/lib/Search/TasksSearchProvider.php b/apps/dav/lib/Search/TasksSearchProvider.php index 176e758fd2ba6..45f6b1ea494b0 100644 --- a/apps/dav/lib/Search/TasksSearchProvider.php +++ b/apps/dav/lib/Search/TasksSearchProvider.php @@ -103,6 +103,7 @@ public function search( } else { $calendar = $subscriptionsById[$taskRow['calendarid']]; } + $resourceUrl = $this->getDeepLinkToTasksApp($calendar['uri'], $taskRow['uri']); return new SearchResultEntry('', $title, $subline, $resourceUrl, 'icon-checkmark', false); @@ -132,6 +133,7 @@ protected function generateSubline(Component $taskComponent): string { if ($taskComponent->COMPLETED) { $completedDateTime = new \DateTime($taskComponent->COMPLETED->getDateTime()->format(\DateTimeInterface::ATOM)); $formattedDate = $this->l10n->l('date', $completedDateTime, ['width' => 'medium']); + return $this->l10n->t('Completed on %s', [$formattedDate]); } diff --git a/apps/dav/lib/Server.php b/apps/dav/lib/Server.php index 2784e99b5f73b..24e3cdbd22961 100644 --- a/apps/dav/lib/Server.php +++ b/apps/dav/lib/Server.php @@ -272,6 +272,7 @@ public function __construct(IRequest $request, string $baseUri) { $this->server->addPlugin( new QuotaPlugin($view)); } + $this->server->addPlugin( new TagsPlugin( $this->server->tree, \OC::$server->getTagManager() @@ -304,6 +305,7 @@ public function __construct(IRequest $request, string $baseUri) { \OC::$server->get(\OCP\Mail\Provider\IManager::class) )); } + $this->server->addPlugin(new \OCA\DAV\CalDAV\Search\SearchPlugin()); if ($view !== null) { $this->server->addPlugin(new FilesReportPlugin( @@ -332,6 +334,7 @@ public function __construct(IRequest $request, string $baseUri) { ) ); } + $this->server->addPlugin(new \OCA\DAV\CalDAV\BirthdayCalendar\EnablePlugin( \OC::$server->getConfig(), \OC::$server->query(BirthdayService::class), @@ -357,6 +360,7 @@ function () { foreach ($pluginManager->getAppPlugins() as $appPlugin) { $this->server->addPlugin($appPlugin); } + foreach ($pluginManager->getAppCollections() as $appCollection) { $root->addChild($appCollection); } @@ -387,6 +391,7 @@ private function requestIsForSubtree(array $subTrees): bool { return true; } } + return false; } diff --git a/apps/dav/lib/Service/AbsenceService.php b/apps/dav/lib/Service/AbsenceService.php index 7cbc0386d4387..51d425f0e29a3 100644 --- a/apps/dav/lib/Service/AbsenceService.php +++ b/apps/dav/lib/Service/AbsenceService.php @@ -91,6 +91,7 @@ public function createOrUpdateAbsence( ], ); } + if ($eventData->getEndDate() > $now) { $this->jobList->scheduleAfter( OutOfOfficeEventDispatcherJob::class, @@ -115,6 +116,7 @@ public function clearAbsence(IUser $user): void { // Nothing to clear return; } + $this->absenceMapper->delete($absence); $this->jobList->remove(OutOfOfficeEventDispatcherJob::class); $eventData = $absence->toOutOufOfficeData( @@ -145,6 +147,7 @@ public function getCurrentAbsence(IUser $user): ?IOutOfOfficeData { } catch (DoesNotExistException) { // Nothing there to process } + return null; } diff --git a/apps/dav/lib/Settings/AvailabilitySettings.php b/apps/dav/lib/Settings/AvailabilitySettings.php index dba4662a5cea2..6afb03bda8ddd 100644 --- a/apps/dav/lib/Settings/AvailabilitySettings.php +++ b/apps/dav/lib/Settings/AvailabilitySettings.php @@ -24,12 +24,14 @@ class AvailabilitySettings implements ISettings { protected IInitialState $initialState; protected ?string $userId; - public function __construct(IConfig $config, + public function __construct( + IConfig $config, IInitialState $initialState, ?string $userId, private LoggerInterface $logger, private IAvailabilityCoordinator $coordinator, - private AbsenceMapper $absenceMapper) { + private AbsenceMapper $absenceMapper, + ) { $this->config = $config; $this->initialState = $initialState; $this->userId = $userId; diff --git a/apps/dav/lib/Settings/CalDAVSettings.php b/apps/dav/lib/Settings/CalDAVSettings.php index 7c738a83148f0..a8d21e31364d2 100644 --- a/apps/dav/lib/Settings/CalDAVSettings.php +++ b/apps/dav/lib/Settings/CalDAVSettings.php @@ -51,6 +51,7 @@ public function getForm(): TemplateResponse { $value = $this->config->getAppValue(Application::APP_ID, $key, $default); $this->initialState->provideInitialState($key, $value === 'yes'); } + return new TemplateResponse(Application::APP_ID, 'settings-admin-caldav'); } diff --git a/apps/dav/lib/SetupChecks/WebdavEndpoint.php b/apps/dav/lib/SetupChecks/WebdavEndpoint.php index c2574202fcd02..919d148602d24 100644 --- a/apps/dav/lib/SetupChecks/WebdavEndpoint.php +++ b/apps/dav/lib/SetupChecks/WebdavEndpoint.php @@ -54,6 +54,7 @@ public function run(): SetupResult { break; } } + // If 'works' is null then we could not connect to the server if ($works === null) { return SetupResult::info( @@ -61,6 +62,7 @@ public function run(): SetupResult { $this->urlGenerator->linkToDocs('admin-setup-well-known-URL'), ); } + // Otherwise if we fail we can abort here if ($works === false) { return SetupResult::error( @@ -68,6 +70,7 @@ public function run(): SetupResult { ); } } + return SetupResult::success( $this->l10n->t('Your web server is properly set up to allow file synchronization over WebDAV.') ); diff --git a/apps/dav/lib/SystemTag/SystemTagMappingNode.php b/apps/dav/lib/SystemTag/SystemTagMappingNode.php index 12d604a7d6e4b..7c21ffbd37280 100644 --- a/apps/dav/lib/SystemTag/SystemTagMappingNode.php +++ b/apps/dav/lib/SystemTag/SystemTagMappingNode.php @@ -100,13 +100,16 @@ public function delete() { if (!$this->tagManager->canUserSeeTag($this->tag, $this->user)) { throw new NotFound('Tag with id ' . $this->tag->getId() . ' not found'); } + if (!$this->tagManager->canUserAssignTag($this->tag, $this->user)) { throw new Forbidden('No permission to unassign tag ' . $this->tag->getId()); } + $writeAccessFunction = $this->childWriteAccessFunction; if (!$writeAccessFunction($this->objectId)) { throw new Forbidden('No permission to unassign tag to ' . $this->objectId); } + $this->tagMapper->unassignTags($this->objectId, $this->objectType, $this->tag->getId()); } catch (TagNotFoundException $e) { // can happen if concurrent deletion occurred diff --git a/apps/dav/lib/SystemTag/SystemTagNode.php b/apps/dav/lib/SystemTag/SystemTagNode.php index 06eead814b27b..1cb129e972596 100644 --- a/apps/dav/lib/SystemTag/SystemTagNode.php +++ b/apps/dav/lib/SystemTag/SystemTagNode.php @@ -112,6 +112,7 @@ public function update($name, $userVisible, $userAssignable): void { if (!$this->tagManager->canUserSeeTag($this->tag, $this->user)) { throw new NotFound('Tag with id ' . $this->tag->getId() . ' does not exist'); } + if (!$this->tagManager->canUserAssignTag($this->tag, $this->user)) { throw new Forbidden('No permission to update tag ' . $this->tag->getId()); } diff --git a/apps/dav/lib/SystemTag/SystemTagPlugin.php b/apps/dav/lib/SystemTag/SystemTagPlugin.php index ec2014163b376..c12d25f5e3a51 100644 --- a/apps/dav/lib/SystemTag/SystemTagPlugin.php +++ b/apps/dav/lib/SystemTag/SystemTagPlugin.php @@ -142,6 +142,7 @@ public function httpPost(RequestInterface $request, ResponseInterface $response) // created $response->setStatus(201); + return false; } } @@ -199,6 +200,7 @@ private function createTag($data, $contentType = 'application/json') { if (!empty($groups)) { $this->tagManager->setTagGroups($tag, $groups); } + return $tag; } catch (TagAlreadyExistsException $e) { throw new Conflict('Tag already exists', 0, $e); @@ -216,7 +218,7 @@ private function createTag($data, $contentType = 'application/json') { */ public function handleGetProperties( PropFind $propFind, - \Sabre\DAV\INode $node + \Sabre\DAV\INode $node, ) { if ($node instanceof Node) { $this->propfindForFile($propFind, $node); @@ -259,11 +261,13 @@ public function handleGetProperties( // property only available for admins throw new Forbidden(); } + $groups = []; // no need to retrieve groups for namespaces that don't qualify if ($node->getSystemTag()->isUserVisible() && !$node->getSystemTag()->isUserAssignable()) { $groups = $this->tagManager->getTagGroups($node->getSystemTag()); } + return implode('|', $groups); }); @@ -313,6 +317,7 @@ private function propfindForFile(PropFind $propFind, Node $node): void { usort($tags, function (ISystemTag $tagA, ISystemTag $tagB): int { return Util::naturalSortCompare($tagA->getName(), $tagB->getName()); }); + return new SystemTagList($tags, $this->tagManager, $user); }); } @@ -348,6 +353,7 @@ private function getTagsForFile(int $fileId, IUser $user): array { foreach ($retrievedTags as $tag) { $this->cachedTags[$tag->getId()] = $tag; } + $tags += $retrievedTags; } diff --git a/apps/dav/lib/SystemTag/SystemTagsByIdCollection.php b/apps/dav/lib/SystemTag/SystemTagsByIdCollection.php index c591001179f26..33e7423128b7c 100644 --- a/apps/dav/lib/SystemTag/SystemTagsByIdCollection.php +++ b/apps/dav/lib/SystemTag/SystemTagsByIdCollection.php @@ -44,7 +44,7 @@ class SystemTagsByIdCollection implements ICollection { public function __construct( ISystemTagManager $tagManager, IUserSession $userSession, - IGroupManager $groupManager + IGroupManager $groupManager, ) { $this->tagManager = $tagManager; $this->userSession = $userSession; @@ -61,6 +61,7 @@ private function isAdmin() { if ($user !== null) { return $this->groupManager->isAdmin($user->getUID()); } + return false; } @@ -97,6 +98,7 @@ public function getChild($name) { if (!$this->tagManager->canUserSeeTag($tag, $this->userSession->getUser())) { throw new NotFound('Tag with id ' . $name . ' not found'); } + return $this->makeNode($tag); } catch (\InvalidArgumentException $e) { throw new BadRequest('Invalid tag id', 0, $e); @@ -117,6 +119,7 @@ public function getChildren() { } $tags = $this->tagManager->getAllTags($visibilityFilter); + return array_map(function ($tag) { return $this->makeNode($tag); }, $tags); @@ -132,6 +135,7 @@ public function childExists($name) { if (!$this->tagManager->canUserSeeTag($tag, $this->userSession->getUser())) { return false; } + return true; } catch (\InvalidArgumentException $e) { throw new BadRequest('Invalid tag id', 0, $e); diff --git a/apps/dav/lib/SystemTag/SystemTagsInUseCollection.php b/apps/dav/lib/SystemTag/SystemTagsInUseCollection.php index fbe393a7d97b6..79473b71060bd 100644 --- a/apps/dav/lib/SystemTag/SystemTagsInUseCollection.php +++ b/apps/dav/lib/SystemTag/SystemTagsInUseCollection.php @@ -33,7 +33,7 @@ public function __construct( IRootFolder $rootFolder, ISystemTagManager $systemTagManager, SystemTagsInFilesDetector $systemTagsInFilesDetector, - string $mediaType = '' + string $mediaType = '', ) { $this->userSession = $userSession; $this->rootFolder = $rootFolder; @@ -54,6 +54,7 @@ public function getChild($name): self { if ($this->mediaType !== '') { throw new NotFound('Invalid media type'); } + return new self($this->userSession, $this->rootFolder, $this->systemTagManager, $this->systemTagsInFilesDetector, $name); } @@ -72,6 +73,7 @@ public function getChildren(): array { } catch (NoUserException) { // will throw a Sabre exception in the next step. } + if ($user === null || $userFolder === null) { throw new Forbidden('Permission denied to read this collection'); } @@ -86,6 +88,7 @@ public function getChildren(): array { $node->setReferenceFileId((int)$tagData['ref_file_id']); $children[] = $node; } + return $children; } } diff --git a/apps/dav/lib/SystemTag/SystemTagsObjectMappingCollection.php b/apps/dav/lib/SystemTag/SystemTagsObjectMappingCollection.php index da58f9bf30807..fbf325769c7cb 100644 --- a/apps/dav/lib/SystemTag/SystemTagsObjectMappingCollection.php +++ b/apps/dav/lib/SystemTag/SystemTagsObjectMappingCollection.php @@ -43,13 +43,16 @@ public function createFile($name, $data = null) { if (!$this->tagManager->canUserSeeTag($tag, $this->user)) { throw new PreconditionFailed('Tag with id ' . $tagId . ' does not exist, cannot assign'); } + if (!$this->tagManager->canUserAssignTag($tag, $this->user)) { throw new Forbidden('No permission to assign tag ' . $tagId); } + $writeAccessFunction = $this->childWriteAccessFunction; if (!$writeAccessFunction($this->objectId)) { throw new Forbidden('No permission to assign tag to ' . $this->objectId); } + $this->tagMapper->assignTags($this->objectId, $this->objectType, $tagId); } catch (TagNotFoundException $e) { throw new PreconditionFailed('Tag with id ' . $tagId . ' does not exist, cannot assign'); @@ -75,6 +78,7 @@ public function getChild($tagName) { return $this->makeNode($tag); } } + throw new NotFound('Tag with id ' . $tagName . ' not present for object ' . $this->objectId); } catch (\InvalidArgumentException $e) { throw new BadRequest('Invalid tag id', 0, $e); @@ -93,6 +97,7 @@ public function getChildren() { if (empty($tagIds)) { return []; } + $tags = $this->tagManager->getTagsByIds($tagIds); // filter out non-visible tags diff --git a/apps/dav/lib/SystemTag/SystemTagsObjectTypeCollection.php b/apps/dav/lib/SystemTag/SystemTagsObjectTypeCollection.php index 9bd66ca0d61b1..81983e0fbd855 100644 --- a/apps/dav/lib/SystemTag/SystemTagsObjectTypeCollection.php +++ b/apps/dav/lib/SystemTag/SystemTagsObjectTypeCollection.php @@ -65,6 +65,7 @@ public function getChild($objectName) { if (!$this->childExists($objectName)) { throw new NotFound('Entity does not exist or is not available'); } + return new SystemTagsObjectMappingCollection( $objectName, $this->objectType, diff --git a/apps/dav/lib/SystemTag/SystemTagsRelationsCollection.php b/apps/dav/lib/SystemTag/SystemTagsRelationsCollection.php index dc0f1bc5d36e3..508f1b2165fe7 100644 --- a/apps/dav/lib/SystemTag/SystemTagsRelationsCollection.php +++ b/apps/dav/lib/SystemTag/SystemTagsRelationsCollection.php @@ -52,6 +52,7 @@ function (string $name) use ($rootFolder, $userSession): bool { return true; } } + return false; } else { return false; diff --git a/apps/dav/lib/Traits/PrincipalProxyTrait.php b/apps/dav/lib/Traits/PrincipalProxyTrait.php index 279f796b72096..8446df2f18320 100644 --- a/apps/dav/lib/Traits/PrincipalProxyTrait.php +++ b/apps/dav/lib/Traits/PrincipalProxyTrait.php @@ -168,6 +168,7 @@ private function isProxyPrincipal(string $principalUri):bool { if (!isset($prefix) || !isset($userId)) { return false; } + if ($prefix !== $this->principalPrefix) { return false; } diff --git a/apps/dav/lib/Upload/AssemblyStream.php b/apps/dav/lib/Upload/AssemblyStream.php index 736905d01c2fa..09e3b3b3bc1cf 100644 --- a/apps/dav/lib/Upload/AssemblyStream.php +++ b/apps/dav/lib/Upload/AssemblyStream.php @@ -85,10 +85,12 @@ public function stream_seek($offset, $whence = SEEK_SET) { if (!isset($this->nodes[$nodeIndex + 1])) { break; } + $node = $this->nodes[$nodeIndex]; if ($nodeStart + $node->getSize() > $offset) { break; } + $nodeIndex++; $nodeStart += $node->getSize(); } @@ -98,6 +100,7 @@ public function stream_seek($offset, $whence = SEEK_SET) { if (fseek($stream, $nodeOffset) === -1) { return false; } + $this->currentNode = $nodeIndex; $this->currentNodeRead = $nodeOffset; $this->currentStream = $stream; @@ -137,6 +140,7 @@ public function stream_read($count) { if ($this->currentNodeRead < $currentNodeSize) { throw new \Exception('Stream from assembly node shorter than expected, got ' . $this->currentNodeRead . ' bytes, expected ' . $currentNodeSize); } + $this->currentNode++; $this->currentNodeRead = 0; if ($this->currentNode < count($this->nodes)) { @@ -152,6 +156,7 @@ public function stream_read($count) { // update position $this->pos += $read; + return $data; } @@ -234,11 +239,13 @@ protected function loadContext($name) { } else { throw new \BadMethodCallException('Invalid context, "' . $name . '" options not set'); } + if (isset($context['nodes']) and is_array($context['nodes'])) { $this->nodes = $context['nodes']; } else { throw new \BadMethodCallException('Invalid context, nodes not set'); } + return $context; } @@ -261,7 +268,9 @@ public static function wrap(array $nodes) { stream_wrapper_unregister('assembly'); throw $e; } + stream_wrapper_unregister('assembly'); + return $wrapped; } @@ -277,6 +286,7 @@ private function getStream(IFile $node) { $tmp = fopen('php://temp', 'w+'); fwrite($tmp, $data); rewind($tmp); + return $tmp; } } diff --git a/apps/dav/lib/Upload/ChunkingPlugin.php b/apps/dav/lib/Upload/ChunkingPlugin.php index cf4d65168ccc9..c78bfaf2bfde1 100644 --- a/apps/dav/lib/Upload/ChunkingPlugin.php +++ b/apps/dav/lib/Upload/ChunkingPlugin.php @@ -55,6 +55,7 @@ public function beforeMove($sourcePath, $destination) { } $this->verifySize(); + return $this->performMove($sourcePath, $destination); } @@ -79,6 +80,7 @@ public function performMove($path, $destination) { if ($sourceNode instanceof FutureFile) { $sourceNode->delete(); } + throw $e; } @@ -102,6 +104,7 @@ private function verifySize() { if ($expectedSize === null) { return; } + $actualSize = $this->sourceNode->getSize(); // casted to string because cast to float cause equality for non equal numbers diff --git a/apps/dav/lib/Upload/ChunkingV2Plugin.php b/apps/dav/lib/Upload/ChunkingV2Plugin.php index 846b4ea42ba40..dec84f546879f 100644 --- a/apps/dav/lib/Upload/ChunkingV2Plugin.php +++ b/apps/dav/lib/Upload/ChunkingV2Plugin.php @@ -101,6 +101,7 @@ private function getUploadFile(string $path, bool $createIfNotExists = false) { /** @var UploadFile $uploadFile */ $uploadFile = $this->uploadFolder->getChild(self::TEMP_TARGET); + return $uploadFile->getFile(); } @@ -125,6 +126,7 @@ public function afterMkcol(RequestInterface $request, ResponseInterface $respons ], 86400); $response->setStatus(201); + return true; } @@ -170,6 +172,7 @@ public function beforePut(RequestInterface $request, ResponseInterface $response } $response->setStatus(201); + return false; } @@ -180,6 +183,7 @@ public function beforeMove($sourcePath, $destination): bool { } catch (StorageInvalidException|BadRequest|NotFound|PreconditionFailed $e) { return true; } + [$storage, $storagePath] = $this->getUploadStorage($this->uploadPath); $targetFile = $this->getUploadFile($this->uploadPath); @@ -196,10 +200,12 @@ public function beforeMove($sourcePath, $destination): bool { $updateFileInfo['mtime'] = $this->sanitizeMtime($this->server->httpRequest->getHeader('X-OC-MTime')); $this->server->httpResponse->setHeader('X-OC-MTime', 'accepted'); } + if ($this->server->httpRequest->getHeader('X-OC-CTime') !== null) { $updateFileInfo['creation_time'] = $this->sanitizeMtime($this->server->httpRequest->getHeader('X-OC-CTime')); $this->server->httpResponse->setHeader('X-OC-CTime', 'accepted'); } + $updateFileInfo['mimetype'] = \OCP\Server::get(IMimeTypeDetector::class)->detectPath($destinationName); if ($storage->instanceOfStorage(ObjectStoreStorage::class) && $storage->getObjectStore() instanceof IObjectStoreMultiPartUpload) { @@ -211,6 +217,7 @@ public function beforeMove($sourcePath, $destination): bool { foreach ($parts as $part) { $size += $part['Size']; } + $free = $destinationParent->getNode()->getFreeSpace(); if ($free >= 0 && ($size > $free)) { throw new InsufficientStorage("Insufficient space in $this->uploadPath"); @@ -236,6 +243,7 @@ public function beforeMove($sourcePath, $destination): bool { $response->setHeader('Content-Type', 'application/xml; charset=utf-8'); $response->setHeader('Content-Length', '0'); $response->setStatus($destinationExists ? 204 : 201); + return false; } @@ -249,6 +257,7 @@ public function beforeDelete(RequestInterface $request, ResponseInterface $respo [$storage, $storagePath] = $this->getUploadStorage($this->uploadPath); $storage->cancelChunkedWrite($storagePath, $this->uploadId); + return true; } @@ -263,12 +272,15 @@ private function checkPrerequisites(bool $checkUploadMetadata = true): void { if ($distributedCacheConfig === null || (!$this->cache instanceof Redis && !$this->cache instanceof Memcached)) { throw new BadRequest('Skipping chunking v2 since no proper distributed cache is available'); } + if (!$this->uploadFolder instanceof UploadFolder || empty($this->server->httpRequest->getHeader(self::DESTINATION_HEADER))) { throw new BadRequest('Skipping chunked file writing as the destination header was not passed'); } + if (!$this->uploadFolder->getStorage()->instanceOfStorage(IChunkedFileWrite::class)) { throw new StorageInvalidException('Storage does not support chunked file writing'); } + if ($this->uploadFolder->getStorage()->instanceOfStorage(ObjectStoreStorage::class) && !$this->uploadFolder->getStorage()->getObjectStore() instanceof IObjectStoreMultiPartUpload) { throw new StorageInvalidException('Storage does not support multi part uploads'); } @@ -286,6 +298,7 @@ private function checkPrerequisites(bool $checkUploadMetadata = true): void { private function getUploadStorage(string $targetPath): array { $storage = $this->uploadFolder->getStorage(); $targetFile = $this->getUploadFile($targetPath); + return [$storage, $targetFile->getInternalPath()]; } @@ -336,6 +349,7 @@ private function completeChunkedWrite(string $targetAbsolutePath): void { $uploadFile->move($targetAbsolutePath); } } + $this->emitPostHooks($targetAbsolutePath, $exists); } catch (Exception $e) { $uploadFile->unlock(ILockingProvider::LOCK_SHARED); @@ -354,6 +368,7 @@ private function emitPreHooks(string $target, bool $exists): void { Filesystem::signal_param_path => $hookPath, ]); } + OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_write, [ Filesystem::signal_param_path => $hookPath, ]); @@ -370,6 +385,7 @@ private function emitPostHooks(string $target, bool $exists): void { Filesystem::signal_param_path => $hookPath, ]); } + OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_post_write, [ Filesystem::signal_param_path => $hookPath, ]); @@ -379,6 +395,7 @@ private function getHookPath(string $path): ?string { if (!Filesystem::getView()) { return $path; } + return Filesystem::getView()->getRelativePath($path); } } diff --git a/apps/dav/lib/Upload/UploadFolder.php b/apps/dav/lib/Upload/UploadFolder.php index 3f84f7a85974a..a885e43c146b6 100644 --- a/apps/dav/lib/Upload/UploadFolder.php +++ b/apps/dav/lib/Upload/UploadFolder.php @@ -37,6 +37,7 @@ public function createFile($name, $data = null) { $child = $this->node->getChild($name); $child->delete(); } + throw $e; } } @@ -49,6 +50,7 @@ public function getChild($name) { if ($name === '.file') { return new FutureFile($this->node, '.file'); } + return new UploadFile($this->node->getChild($name)); } @@ -86,6 +88,7 @@ public function childExists($name) { if ($name === '.file') { return true; } + return $this->node->childExists($name); } diff --git a/apps/dav/lib/Upload/UploadHome.php b/apps/dav/lib/Upload/UploadHome.php index 3e7e3c6c986a8..70305c334c33d 100644 --- a/apps/dav/lib/Upload/UploadHome.php +++ b/apps/dav/lib/Upload/UploadHome.php @@ -72,6 +72,7 @@ public function getLastModified() { private function impl() { $view = $this->getView(); $rootInfo = $view->getFileInfo(''); + return new Directory($view, $rootInfo); } @@ -82,12 +83,14 @@ private function getView() { if (!$rootView->file_exists('/' . $user->getUID() . '/uploads')) { $rootView->mkdir('/' . $user->getUID() . '/uploads'); } + return new View('/' . $user->getUID() . '/uploads'); } private function getStorage() { $view = $this->getView(); $storage = $view->getFileInfo('')->getStorage(); + return $storage; } } diff --git a/apps/dav/lib/UserMigration/CalendarMigrator.php b/apps/dav/lib/UserMigration/CalendarMigrator.php index 80b564e24eebe..1d7e0706e8db7 100644 --- a/apps/dav/lib/UserMigration/CalendarMigrator.php +++ b/apps/dav/lib/UserMigration/CalendarMigrator.php @@ -67,7 +67,7 @@ public function __construct( ICalendarManager $calendarManager, ICSExportPlugin $icsExportPlugin, Defaults $defaults, - IL10N $l10n + IL10N $l10n, ) { $this->calDavBackend = $calDavBackend; $this->calendarManager = $calendarManager; @@ -208,6 +208,7 @@ public function getEstimatedExportSize(IUser $user): int|float { function (array $data): int { /** @var VCalendar $vCalendar */ $vCalendar = $data['vCalendar']; + return count($vCalendar->getComponents()); }, $calendarExports, @@ -314,6 +315,7 @@ private function getRequiredImportComponents(VCalendar $vCalendar, VObjectCompon $component = $this->sanitizeComponent($component); /** @var array $timezoneComponents */ $timezoneComponents = $this->getTimezonesForComponent($vCalendar, $component); + return [ ...$timezoneComponents, $component, @@ -323,6 +325,7 @@ private function getRequiredImportComponents(VCalendar $vCalendar, VObjectCompon private function initCalendarObject(): VCalendar { $vCalendarObject = new VCalendar(); $vCalendarObject->PRODID = '-//IDN nextcloud.com//Migrated calendar//EN'; + return $vCalendarObject; } @@ -403,6 +406,7 @@ function (array $componentNames, VObjectComponent $component) { $vCalendarObject->add($component); } } + $this->importCalendarObject($calendarId, $vCalendarObject, $filename, $output); } @@ -412,6 +416,7 @@ function (array $componentNames, VObjectComponent $component) { foreach ($this->getRequiredImportComponents($vCalendar, $component) as $component) { $vCalendarObject->add($component); } + $this->importCalendarObject($calendarId, $vCalendarObject, $filename, $output); } } @@ -458,6 +463,7 @@ public function import(IUser $user, IImportSource $importSource, OutputInterface $output->writeln("Invalid filename \"$filename\", expected filename of the format \"" . CalendarMigrator::FILENAME_EXT . '", skipping…'); continue; } + [$initialCalendarUri, $ext] = $splitFilename; try { diff --git a/apps/dav/lib/UserMigration/ContactsMigrator.php b/apps/dav/lib/UserMigration/ContactsMigrator.php index a14d3bc5e1ccd..5509de48920da 100644 --- a/apps/dav/lib/UserMigration/ContactsMigrator.php +++ b/apps/dav/lib/UserMigration/ContactsMigrator.php @@ -55,7 +55,7 @@ class ContactsMigrator implements IMigrator, ISizeEstimationMigrator { public function __construct( CardDavBackend $cardDavBackend, - IL10N $l10n + IL10N $l10n, ) { $this->cardDavBackend = $cardDavBackend; $this->l10n = $l10n; @@ -108,6 +108,7 @@ private function getAddressBookExportData(IUser $user, array $addressBookInfo, O $output->writeln('Skipping contact "' . ($vCard->FN ?? 'null') . '" containing invalid contact data'); continue; } + $vCards[] = $vCard; } } @@ -349,6 +350,7 @@ public function import(IUser $user, IImportSource $importSource, OutputInterface $output->writeln('Skipping contact "' . ($vCard->FN ?? 'null') . '" containing invalid contact data'); continue; } + $vCards[] = $vCard; } @@ -357,6 +359,7 @@ public function import(IUser $user, IImportSource $importSource, OutputInterface $output->writeln("Invalid filename \"$addressBookFilename\", expected filename of the format \"." . ContactsMigrator::FILENAME_EXT . '", skipping…'); continue; } + [$initialAddressBookUri, $ext] = $splitFilename; /** @var array{displayName: string, description?: string} $metadata */ diff --git a/apps/dav/templates/schedule-response-error.php b/apps/dav/templates/schedule-response-error.php index 9604c83f9229e..7bdb37f73dd6e 100644 --- a/apps/dav/templates/schedule-response-error.php +++ b/apps/dav/templates/schedule-response-error.php @@ -11,6 +11,8 @@

t('Please contact the organizer directly.'));?>

- + diff --git a/apps/dav/tests/integration/UserMigration/CalendarMigratorTest.php b/apps/dav/tests/integration/UserMigration/CalendarMigratorTest.php index bb7f83e4d34da..3690b1c120eb6 100644 --- a/apps/dav/tests/integration/UserMigration/CalendarMigratorTest.php +++ b/apps/dav/tests/integration/UserMigration/CalendarMigratorTest.php @@ -53,6 +53,7 @@ function (string $filename) { VObjectReader::OPTION_FORGIVING, ); [$initialCalendarUri, $ext] = explode('.', $filename, 2); + return [UUIDUtil::getUUID(), $filename, $initialCalendarUri, $vCalendar]; }, array_diff( diff --git a/apps/dav/tests/integration/UserMigration/ContactsMigratorTest.php b/apps/dav/tests/integration/UserMigration/ContactsMigratorTest.php index 17d4cf2153605..bd26d56d15a9b 100644 --- a/apps/dav/tests/integration/UserMigration/ContactsMigratorTest.php +++ b/apps/dav/tests/integration/UserMigration/ContactsMigratorTest.php @@ -60,6 +60,7 @@ function (string $filename) { [$initialAddressBookUri, $ext] = explode('.', $filename, 2); $metadata = ['displayName' => ucwords(str_replace('-', ' ', $initialAddressBookUri))]; + return [UUIDUtil::getUUID(), $filename, $initialAddressBookUri, $metadata, $vCards]; }, array_diff( diff --git a/apps/dav/tests/unit/BackgroundJob/UserStatusAutomationTest.php b/apps/dav/tests/unit/BackgroundJob/UserStatusAutomationTest.php index 7e30ec3d2fe7b..5fd51b13f2dac 100644 --- a/apps/dav/tests/unit/BackgroundJob/UserStatusAutomationTest.php +++ b/apps/dav/tests/unit/BackgroundJob/UserStatusAutomationTest.php @@ -113,6 +113,7 @@ public function testRunNoOOO(string $ruleDay, string $currentTime, bool $isAvail ->method('setUserStatus') ->with('user', IUserStatus::MESSAGE_AVAILABILITY, IUserStatus::DND, true); } + $automation = $this->getAutomationMock(['getAvailabilityFromPropertiesTable']); $automation->method('getAvailabilityFromPropertiesTable') ->with('user') diff --git a/apps/dav/tests/unit/CalDAV/AbstractCalDavBackend.php b/apps/dav/tests/unit/CalDAV/AbstractCalDavBackend.php index 946a2328bf8df..6e6ffc9679b14 100644 --- a/apps/dav/tests/unit/CalDAV/AbstractCalDavBackend.php +++ b/apps/dav/tests/unit/CalDAV/AbstractCalDavBackend.php @@ -122,6 +122,7 @@ public function cleanUpBackend(): void { if (is_null($this->backend)) { return; } + $this->principal->expects($this->any())->method('getGroupMembership') ->withAnyParameters() ->willReturn([self::UNIT_TEST_GROUP, self::UNIT_TEST_GROUP2]); @@ -136,6 +137,7 @@ private function cleanupForPrincipal($principal): void { foreach ($calendars as $calendar) { $this->backend->deleteCalendar($calendar['id'], true); } + $subscriptions = $this->backend->getSubscriptionsForUser($principal); foreach ($subscriptions as $subscription) { $this->backend->deleteSubscription($subscription['id']); @@ -241,6 +243,7 @@ protected function assertAcl($principal, $privilege, $acl) { return; } } + $this->fail("ACL does not contain $principal / $privilege"); } @@ -251,6 +254,7 @@ protected function assertNotAcl($principal, $privilege, $acl) { return; } } + $this->addToAssertionCount(1); } diff --git a/apps/dav/tests/unit/CalDAV/Activity/BackendTest.php b/apps/dav/tests/unit/CalDAV/Activity/BackendTest.php index 6ace633b072f7..43a2a422d060d 100644 --- a/apps/dav/tests/unit/CalDAV/Activity/BackendTest.php +++ b/apps/dav/tests/unit/CalDAV/Activity/BackendTest.php @@ -363,6 +363,7 @@ protected function getUsers(array $users) { foreach ($users as $user) { $list[] = $this->getUserMock($user); } + return $list; } @@ -375,6 +376,7 @@ protected function getUserMock($uid) { $user->expects($this->once()) ->method('getUID') ->willReturn($uid); + return $user; } } diff --git a/apps/dav/tests/unit/CalDAV/Activity/Provider/EventTest.php b/apps/dav/tests/unit/CalDAV/Activity/Provider/EventTest.php index ec237825731c5..704c031af5f4b 100644 --- a/apps/dav/tests/unit/CalDAV/Activity/Provider/EventTest.php +++ b/apps/dav/tests/unit/CalDAV/Activity/Provider/EventTest.php @@ -114,10 +114,12 @@ public function testGenerateObjectParameter(int $id, string $name, ?array $link, ->willReturn('fullLink'); } } + $objectParameter = ['id' => $id, 'name' => $name]; if ($link) { $objectParameter['link'] = $link; } + $result = [ 'type' => 'calendar-event', 'id' => $id, @@ -126,6 +128,7 @@ public function testGenerateObjectParameter(int $id, string $name, ?array $link, if ($link && $calendarAppEnabled) { $result['link'] = 'fullLink'; } + $this->assertEquals($result, $this->invokePrivate($this->provider, 'generateObjectParameter', [$objectParameter, $affectedUser])); } diff --git a/apps/dav/tests/unit/CalDAV/CalDavBackendTest.php b/apps/dav/tests/unit/CalDAV/CalDavBackendTest.php index c82ca350c909b..ba389f67faa85 100644 --- a/apps/dav/tests/unit/CalDAV/CalDavBackendTest.php +++ b/apps/dav/tests/unit/CalDAV/CalDavBackendTest.php @@ -720,6 +720,7 @@ public function testGetDenormalizedData($expected, $key, $calData): void { if (($e->getMessage() === 'Epoch doesn\'t fit in a PHP integer') && (PHP_INT_SIZE < 8)) { $this->markTestSkipped('This fail on 32bits because of PHP limitations in DateTime'); } + throw $e; } } diff --git a/apps/dav/tests/unit/CalDAV/CalendarTest.php b/apps/dav/tests/unit/CalDAV/CalendarTest.php index 6433af8c3402a..ffc77b5c2165d 100644 --- a/apps/dav/tests/unit/CalDAV/CalendarTest.php +++ b/apps/dav/tests/unit/CalDAV/CalendarTest.php @@ -167,6 +167,7 @@ public function testPropPatch($ownerPrincipal, $principalUri, $mutations, $share ->method('updateCalendar') ->with(666, $propPatch); } + $c->propPatch($propPatch); $this->addToAssertionCount(1); } @@ -187,9 +188,11 @@ public function testAcl($expectsWrite, $readOnlyValue, $hasOwnerSet, $uri = 'def if (!is_null($readOnlyValue)) { $calendarInfo['{http://owncloud.org/ns}read-only'] = $readOnlyValue; } + if ($hasOwnerSet) { $calendarInfo['{http://owncloud.org/ns}owner-principal'] = 'user1'; } + $c = new Calendar($backend, $calendarInfo, $this->l10n, $this->config, $this->logger); $acl = $c->getACL(); $childAcl = $c->getChildACL(); @@ -257,6 +260,7 @@ public function testAcl($expectsWrite, $readOnlyValue, $hasOwnerSet, $uri = 'def ]; } } + $this->assertEquals($expectedAcl, $acl); $this->assertEquals($expectedAcl, $childAcl); } @@ -306,6 +310,7 @@ public function testPrivateClassification($expectedChildren, $isShared): void { if ($isShared) { $calendarInfo['{http://owncloud.org/ns}owner-principal'] = 'user1'; } + $c = new Calendar($backend, $calendarInfo, $this->l10n, $this->config, $this->logger); $children = $c->getChildren(); $this->assertEquals($expectedChildren, count($children)); @@ -423,6 +428,7 @@ public function testConfidentialClassification($expectedChildren, $isShared): vo $l10n->expects($this->never()) ->method('t'); } + $c = new Calendar($backend, $calendarInfo, $l10n, $this->config, $this->logger); $calData = $c->getChild('event-1')->get(); diff --git a/apps/dav/tests/unit/CalDAV/PublicCalendarRootTest.php b/apps/dav/tests/unit/CalDAV/PublicCalendarRootTest.php index 4333754222bad..4c9b1e2507714 100644 --- a/apps/dav/tests/unit/CalDAV/PublicCalendarRootTest.php +++ b/apps/dav/tests/unit/CalDAV/PublicCalendarRootTest.php @@ -94,6 +94,7 @@ protected function tearDown(): void { if (is_null($this->backend)) { return; } + $this->principal->expects($this->any())->method('getGroupMembership') ->withAnyParameters() ->willReturn([]); diff --git a/apps/dav/tests/unit/CalDAV/Publishing/PublisherTest.php b/apps/dav/tests/unit/CalDAV/Publishing/PublisherTest.php index 769e153764671..e60dd0e80723e 100644 --- a/apps/dav/tests/unit/CalDAV/Publishing/PublisherTest.php +++ b/apps/dav/tests/unit/CalDAV/Publishing/PublisherTest.php @@ -54,6 +54,7 @@ private function write($input) { $writer->openMemory(); $writer->setIndent(true); $writer->write($input); + return $writer->outputMemory(); } } diff --git a/apps/dav/tests/unit/CalDAV/Reminder/NotifierTest.php b/apps/dav/tests/unit/CalDAV/Reminder/NotifierTest.php index dcf11a1a6b833..8ccd2a076acd2 100644 --- a/apps/dav/tests/unit/CalDAV/Reminder/NotifierTest.php +++ b/apps/dav/tests/unit/CalDAV/Reminder/NotifierTest.php @@ -47,6 +47,7 @@ protected function setUp(): void { if (!is_array($args)) { $args = [$args]; } + return vsprintf($string, $args); }); $this->l10n->expects($this->any()) @@ -60,6 +61,7 @@ protected function setUp(): void { ->willReturnCallback(function ($textSingular, $textPlural, $count, $args) { $text = $count === 1 ? $textSingular : $textPlural; $text = str_replace('%n', (string)$count, $text); + return vsprintf($text, $args); }); $this->factory = $this->createMock(IFactory::class); diff --git a/apps/dav/tests/unit/CalDAV/Reminder/ReminderServiceTest.php b/apps/dav/tests/unit/CalDAV/Reminder/ReminderServiceTest.php index 198c8d97b125d..9c0865e392e24 100644 --- a/apps/dav/tests/unit/CalDAV/Reminder/ReminderServiceTest.php +++ b/apps/dav/tests/unit/CalDAV/Reminder/ReminderServiceTest.php @@ -713,6 +713,7 @@ public function testProcessReminders():void { if ($vevent->DTSTART->getDateTime()->format(DateTime::ATOM) !== '2016-06-09T00:00:00+00:00') { return false; } + return true; }, 'Displayname 123', $user)); $provider2->expects($this->once()) @@ -721,6 +722,7 @@ public function testProcessReminders():void { if ($vevent->DTSTART->getDateTime()->format(DateTime::ATOM) !== '2016-06-09T00:00:00+00:00') { return false; } + return true; }, 'Displayname 123', $user)); $provider3->expects($this->once()) @@ -729,6 +731,7 @@ public function testProcessReminders():void { if ($vevent->DTSTART->getDateTime()->format(DateTime::ATOM) !== '2016-06-09T00:00:00+00:00') { return false; } + return true; }, 'Displayname 123', $user)); $provider4->expects($this->once()) @@ -737,6 +740,7 @@ public function testProcessReminders():void { if ($vevent->DTSTART->getDateTime()->format(DateTime::ATOM) !== '2016-06-30T00:00:00+00:00') { return false; } + return true; }, 'Displayname 123', $user)); $provider5->expects($this->once()) @@ -745,6 +749,7 @@ public function testProcessReminders():void { if ($vevent->DTSTART->getDateTime()->format(DateTime::ATOM) !== '2016-07-07T00:00:00+00:00') { return false; } + return true; }, 'Displayname 123', $user)); diff --git a/apps/dav/tests/unit/CalDAV/ResourceBooking/AbstractPrincipalBackendTest.php b/apps/dav/tests/unit/CalDAV/ResourceBooking/AbstractPrincipalBackendTest.php index ed39129fa56b4..2018aa60eaa71 100644 --- a/apps/dav/tests/unit/CalDAV/ResourceBooking/AbstractPrincipalBackendTest.php +++ b/apps/dav/tests/unit/CalDAV/ResourceBooking/AbstractPrincipalBackendTest.php @@ -220,9 +220,11 @@ public function testSetGroupMemberSet(): void { if ($proxy->getOwnerId() !== $this->principalPrefix . '/backend1-res1') { return false; } + if ($proxy->getProxyId() !== $this->principalPrefix . '/backend1-res2') { return false; } + if ($proxy->getPermissions() !== 3) { return false; } @@ -234,9 +236,11 @@ public function testSetGroupMemberSet(): void { if ($proxy->getOwnerId() !== $this->principalPrefix . '/backend1-res1') { return false; } + if ($proxy->getProxyId() !== $this->principalPrefix . '/backend2-res3') { return false; } + if ($proxy->getPermissions() !== 3) { return false; } diff --git a/apps/dav/tests/unit/CalDAV/Schedule/IMipPluginTest.php b/apps/dav/tests/unit/CalDAV/Schedule/IMipPluginTest.php index 36ce091fc6907..7b85f33a4acc5 100644 --- a/apps/dav/tests/unit/CalDAV/Schedule/IMipPluginTest.php +++ b/apps/dav/tests/unit/CalDAV/Schedule/IMipPluginTest.php @@ -199,6 +199,7 @@ public function testParsingSingle(): void { $atnd = $attendee; } } + $this->plugin->setVCalendar($oldVCalendar); $this->service->expects(self::once()) ->method('getLastOccurrence') @@ -302,6 +303,7 @@ public function testAttendeeIsResource(): void { $room = $attendee; } } + $this->plugin->setVCalendar($oldVCalendar); $this->service->expects(self::once()) ->method('getLastOccurrence') @@ -403,6 +405,7 @@ public function testParsingRecurrence(): void { $atnd = $attendee; } } + $this->plugin->setVCalendar($oldVCalendar); $this->service->expects(self::once()) ->method('getLastOccurrence') @@ -534,6 +537,7 @@ public function testFailedDelivery(): void { $atnd = $attendee; } } + $this->plugin->setVCalendar($oldVcalendar); $this->service->expects(self::once()) ->method('getLastOccurrence') @@ -626,6 +630,7 @@ public function testMailProviderSend(): void { $attendee = $entry; } } + // construct body data return $data = ['invitee_name' => 'Mr. Wizard', 'meeting_title' => 'Fellowship meeting without (!) Boromir', @@ -735,6 +740,7 @@ public function testNoOldEvent(): void { $atnd = $attendee; } } + $this->service->expects(self::once()) ->method('getLastOccurrence') ->willReturn(1496912700); @@ -828,6 +834,7 @@ public function testNoButtons(): void { $atnd = $attendee; } } + $this->service->expects(self::once()) ->method('getLastOccurrence') ->willReturn(1496912700); diff --git a/apps/dav/tests/unit/CalDAV/Schedule/PluginTest.php b/apps/dav/tests/unit/CalDAV/Schedule/PluginTest.php index 8d98a765fc1bd..9a4e33f8b8275 100644 --- a/apps/dav/tests/unit/CalDAV/Schedule/PluginTest.php +++ b/apps/dav/tests/unit/CalDAV/Schedule/PluginTest.php @@ -295,12 +295,15 @@ public function testPropFindDefaultCalendarUrl(string $principalUri, ?string $ca $this->plugin->propFindDefaultCalendarUrl($propFind, $node); $this->assertNull($propFind->get(Plugin::SCHEDULE_DEFAULT_CALENDAR_URL)); + return; } + if ($principalUri === 'principals/something-else') { $this->plugin->propFindDefaultCalendarUrl($propFind, $node); $this->assertNull($propFind->get(Plugin::SCHEDULE_DEFAULT_CALENDAR_URL)); + return; } @@ -380,7 +383,7 @@ public function testPropFindDefaultCalendarUrl(string $principalUri, ?string $ca $this->server->expects($this->once()) ->method('getPropertiesForPath') - ->with($calendarHome .'/' . $calendarUri, [], 1) + ->with($calendarHome . '/' . $calendarUri, [], 1) ->willReturn($properties); $this->plugin->propFindDefaultCalendarUrl($propFind, $node); @@ -392,7 +395,7 @@ public function testPropFindDefaultCalendarUrl(string $principalUri, ?string $ca /** @var LocalHref $result */ $result = $propFind->get(Plugin::SCHEDULE_DEFAULT_CALENDAR_URL); - $this->assertEquals('/remote.php/dav/'. $calendarHome . '/' . $calendarUri, $result->getHref()); + $this->assertEquals('/remote.php/dav/' . $calendarHome . '/' . $calendarUri, $result->getHref()); } /** @@ -449,6 +452,7 @@ function (string $eventName, array $arguments = [], ?callable $continueCallBack $this->assertEquals('schedule', $eventName); $this->assertCount(1, $arguments); $iTipMessages[] = $arguments[0]; + return true; } ); @@ -554,6 +558,7 @@ function (string $eventName, array $arguments = [], ?callable $continueCallBack $this->assertEquals('schedule', $eventName); $this->assertCount(1, $arguments); $iTipMessages[] = $arguments[0]; + return true; } ); @@ -677,6 +682,7 @@ function (string $eventName, array $arguments = [], ?callable $continueCallBack $this->assertEquals('schedule', $eventName); $this->assertCount(1, $arguments); $iTipMessages[] = $arguments[0]; + return true; } ); diff --git a/apps/dav/tests/unit/CalDAV/Search/Request/CalendarSearchReportTest.php b/apps/dav/tests/unit/CalDAV/Search/Request/CalendarSearchReportTest.php index cbfd4639ed75e..e7fd528a21e5f 100644 --- a/apps/dav/tests/unit/CalDAV/Search/Request/CalendarSearchReportTest.php +++ b/apps/dav/tests/unit/CalDAV/Search/Request/CalendarSearchReportTest.php @@ -317,6 +317,7 @@ private function parse($xml, array $elementMap = []) { $reader = new Reader(); $reader->elementMap = array_merge($this->elementMap, $elementMap); $reader->xml($xml); + return $reader->parse(); } } diff --git a/apps/dav/tests/unit/CardDAV/Activity/BackendTest.php b/apps/dav/tests/unit/CardDAV/Activity/BackendTest.php index 134a6ca5ca04e..4eeabcf9148e0 100644 --- a/apps/dav/tests/unit/CardDAV/Activity/BackendTest.php +++ b/apps/dav/tests/unit/CardDAV/Activity/BackendTest.php @@ -494,6 +494,7 @@ protected function getUsers(array $users): array { foreach ($users as $user) { $list[] = $this->getUserMock($user); } + return $list; } @@ -506,6 +507,7 @@ protected function getUserMock(string $uid) { $user->expects($this->once()) ->method('getUID') ->willReturn($uid); + return $user; } } diff --git a/apps/dav/tests/unit/CardDAV/AddressBookTest.php b/apps/dav/tests/unit/CardDAV/AddressBookTest.php index cbdb9a1402ca4..622f2e72b7164 100644 --- a/apps/dav/tests/unit/CardDAV/AddressBookTest.php +++ b/apps/dav/tests/unit/CardDAV/AddressBookTest.php @@ -131,9 +131,11 @@ public function testAcl($expectsWrite, $readOnlyValue, $hasOwnerSet): void { if (!is_null($readOnlyValue)) { $addressBookInfo['{http://owncloud.org/ns}read-only'] = $readOnlyValue; } + if ($hasOwnerSet) { $addressBookInfo['{http://owncloud.org/ns}owner-principal'] = 'user1'; } + $l10n = $this->createMock(IL10N::class); $logger = $this->createMock(LoggerInterface::class); $addressBook = new AddressBook($backend, $addressBookInfo, $l10n, $logger); @@ -167,6 +169,7 @@ public function testAcl($expectsWrite, $readOnlyValue, $hasOwnerSet): void { ]; } } + $this->assertEquals($expectedAcl, $acl); $this->assertEquals($expectedAcl, $childAcl); } diff --git a/apps/dav/tests/unit/CardDAV/BirthdayServiceTest.php b/apps/dav/tests/unit/CardDAV/BirthdayServiceTest.php index aeee04fd6eee8..3948ef608e931 100644 --- a/apps/dav/tests/unit/CardDAV/BirthdayServiceTest.php +++ b/apps/dav/tests/unit/CardDAV/BirthdayServiceTest.php @@ -254,6 +254,7 @@ public function testOnCardChanged($expectedOp): void { [1234, 'default-gump.vcf-anniversary.ics'] ); } + if ($expectedOp === 'create') { $vCal = new VCalendar(); $vCal->PRODID = '-//Nextcloud testing//mocked object//'; @@ -265,6 +266,7 @@ public function testOnCardChanged($expectedOp): void { [1234, 'default-gump.vcf-anniversary.ics', "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nCALSCALE:GREGORIAN\r\nPRODID:-//Nextcloud testing//mocked object//\r\nEND:VCALENDAR\r\n"] ); } + if ($expectedOp === 'update') { $vCal = new VCalendar(); $vCal->PRODID = '-//Nextcloud testing//mocked object//'; diff --git a/apps/dav/tests/unit/CardDAV/CardDavBackendTest.php b/apps/dav/tests/unit/CardDAV/CardDavBackendTest.php index 91d7cc5f49e86..96968909d1cc6 100644 --- a/apps/dav/tests/unit/CardDAV/CardDavBackendTest.php +++ b/apps/dav/tests/unit/CardDAV/CardDavBackendTest.php @@ -72,35 +72,35 @@ class CardDavBackendTest extends TestCase { public const UNIT_TEST_USER1 = 'principals/users/carddav-unit-test1'; public const UNIT_TEST_GROUP = 'principals/groups/carddav-unit-test-group'; - private $vcardTest0 = 'BEGIN:VCARD'.PHP_EOL. - 'VERSION:3.0'.PHP_EOL. - 'PRODID:-//Sabre//Sabre VObject 4.1.2//EN'.PHP_EOL. - 'UID:Test'.PHP_EOL. - 'FN:Test'.PHP_EOL. - 'N:Test;;;;'.PHP_EOL. + private $vcardTest0 = 'BEGIN:VCARD' . PHP_EOL . + 'VERSION:3.0' . PHP_EOL . + 'PRODID:-//Sabre//Sabre VObject 4.1.2//EN' . PHP_EOL . + 'UID:Test' . PHP_EOL . + 'FN:Test' . PHP_EOL . + 'N:Test;;;;' . PHP_EOL . 'END:VCARD'; - private $vcardTest1 = 'BEGIN:VCARD'.PHP_EOL. - 'VERSION:3.0'.PHP_EOL. - 'PRODID:-//Sabre//Sabre VObject 4.1.2//EN'.PHP_EOL. - 'UID:Test2'.PHP_EOL. - 'FN:Test2'.PHP_EOL. - 'N:Test2;;;;'.PHP_EOL. + private $vcardTest1 = 'BEGIN:VCARD' . PHP_EOL . + 'VERSION:3.0' . PHP_EOL . + 'PRODID:-//Sabre//Sabre VObject 4.1.2//EN' . PHP_EOL . + 'UID:Test2' . PHP_EOL . + 'FN:Test2' . PHP_EOL . + 'N:Test2;;;;' . PHP_EOL . 'END:VCARD'; - private $vcardTest2 = 'BEGIN:VCARD'.PHP_EOL. - 'VERSION:3.0'.PHP_EOL. - 'PRODID:-//Sabre//Sabre VObject 4.1.2//EN'.PHP_EOL. - 'UID:Test3'.PHP_EOL. - 'FN:Test3'.PHP_EOL. - 'N:Test3;;;;'.PHP_EOL. + private $vcardTest2 = 'BEGIN:VCARD' . PHP_EOL . + 'VERSION:3.0' . PHP_EOL . + 'PRODID:-//Sabre//Sabre VObject 4.1.2//EN' . PHP_EOL . + 'UID:Test3' . PHP_EOL . + 'FN:Test3' . PHP_EOL . + 'N:Test3;;;;' . PHP_EOL . 'END:VCARD'; - private $vcardTestNoUID = 'BEGIN:VCARD'.PHP_EOL. - 'VERSION:3.0'.PHP_EOL. - 'PRODID:-//Sabre//Sabre VObject 4.1.2//EN'.PHP_EOL. - 'FN:TestNoUID'.PHP_EOL. - 'N:TestNoUID;;;;'.PHP_EOL. + private $vcardTestNoUID = 'BEGIN:VCARD' . PHP_EOL . + 'VERSION:3.0' . PHP_EOL . + 'PRODID:-//Sabre//Sabre VObject 4.1.2//EN' . PHP_EOL . + 'FN:TestNoUID' . PHP_EOL . + 'N:TestNoUID;;;;' . PHP_EOL . 'END:VCARD'; protected function setUp(): void { @@ -332,7 +332,7 @@ public function testMultiCard(): void { $this->assertArrayHasKey('lastmodified', $card); $this->assertArrayHasKey('etag', $card); $this->assertArrayHasKey('size', $card); - $this->assertEquals($this->{ 'vcardTest'.($index + 1) }, $card['carddata']); + $this->assertEquals($this->{ 'vcardTest' . ($index + 1) }, $card['carddata']); } // delete the card diff --git a/apps/dav/tests/unit/CardDAV/ConverterTest.php b/apps/dav/tests/unit/CardDAV/ConverterTest.php index c29e0db507097..71ef71d8ece6d 100644 --- a/apps/dav/tests/unit/CardDAV/ConverterTest.php +++ b/apps/dav/tests/unit/CardDAV/ConverterTest.php @@ -60,6 +60,7 @@ protected function getAccountPropertyMock(string $name, ?string $value, string $ $property->expects($this->any()) ->method('getVerified') ->willReturn(IAccountManager::NOT_VERIFIED); + return $property; } @@ -135,6 +136,7 @@ protected function compareData($expected, $data) { break; } } + if (!$found) { $this->assertTrue(false, 'Expected data: ' . $key . ' not found.'); } @@ -231,6 +233,7 @@ protected function getUserMock(string $displayName, ?string $eMailAddress, ?stri $user->method('getEMailAddress')->willReturn($eMailAddress); $user->method('getCloudId')->willReturn($cloudId); $user->method('getAvatarImage')->willReturn($image0); + return $user; } } diff --git a/apps/dav/tests/unit/CardDAV/ImageExportPluginTest.php b/apps/dav/tests/unit/CardDAV/ImageExportPluginTest.php index 6718153f91041..98c260b789fe9 100644 --- a/apps/dav/tests/unit/CardDAV/ImageExportPluginTest.php +++ b/apps/dav/tests/unit/CardDAV/ImageExportPluginTest.php @@ -128,6 +128,7 @@ public function testCard($size, $photo): void { } elseif ($path === 'user/book') { return $book; } + $this->fail(); }); diff --git a/apps/dav/tests/unit/Command/DeleteCalendarTest.php b/apps/dav/tests/unit/Command/DeleteCalendarTest.php index 583673c9d3086..b621e6737d546 100644 --- a/apps/dav/tests/unit/Command/DeleteCalendarTest.php +++ b/apps/dav/tests/unit/Command/DeleteCalendarTest.php @@ -100,7 +100,7 @@ public function testNoCalendarName(): void { public function testInvalidCalendar(): void { $this->expectException(\InvalidArgumentException::class); $this->expectExceptionMessage( - 'User <' . self::USER . '> has no calendar named <' . self::NAME . '>.'); + 'User <' . self::USER . '> has no calendar named <' . self::NAME . '>.'); $this->userManager->expects($this->once()) ->method('userExists') diff --git a/apps/dav/tests/unit/Comments/CommentsNodeTest.php b/apps/dav/tests/unit/Comments/CommentsNodeTest.php index c253c59df0f93..9e79352b9da11 100644 --- a/apps/dav/tests/unit/Comments/CommentsNodeTest.php +++ b/apps/dav/tests/unit/Comments/CommentsNodeTest.php @@ -488,6 +488,7 @@ public function testGetProperties(): void { $this->assertSame($expected[$name], $value); unset($expected[$name]); } + $this->assertTrue(empty($expected)); } @@ -498,6 +499,7 @@ public function readCommentProvider() { $readDT1->sub($diff); $readDT2 = clone $creationDT; $readDT2->add($diff); + return [ [$creationDT, $readDT1, 'true'], [$creationDT, $readDT2, 'false'], diff --git a/apps/dav/tests/unit/Connector/Sabre/FileTest.php b/apps/dav/tests/unit/Connector/Sabre/FileTest.php index 55a6783225d8e..f63a4430d3154 100644 --- a/apps/dav/tests/unit/Connector/Sabre/FileTest.php +++ b/apps/dav/tests/unit/Connector/Sabre/FileTest.php @@ -78,6 +78,7 @@ private function getMockStorage(): MockObject&IStorage { ->getMock(); $storage->method('getId') ->willReturn('home::someuser'); + return $storage; } @@ -85,6 +86,7 @@ private function getStream(string $string) { $stream = fopen('php://temp', 'r+'); fwrite($stream, $string); fseek($stream, 0); + return $stream; } @@ -872,6 +874,7 @@ private function listPartFiles(?\OC\Files\View $userView = null, $path = '') { if ($userView === null) { $userView = \OC\Files\Filesystem::getView(); } + $files = []; [$storage, $internalPath] = $userView->resolvePath($path); if ($storage instanceof Local) { @@ -882,8 +885,10 @@ private function listPartFiles(?\OC\Files\View $userView = null, $path = '') { $files[] = $file; } } + closedir($dh); } + return $files; } @@ -898,6 +903,7 @@ private function getFileInfos($path = '', ?View $userView = null) { if ($userView === null) { $userView = Filesystem::getView(); } + return [ 'filesize' => $userView->filesize($path), 'mtime' => $userView->filemtime($path), diff --git a/apps/dav/tests/unit/Connector/Sabre/NodeTest.php b/apps/dav/tests/unit/Connector/Sabre/NodeTest.php index c5e2b03d8b481..d589e71fd5dba 100644 --- a/apps/dav/tests/unit/Connector/Sabre/NodeTest.php +++ b/apps/dav/tests/unit/Connector/Sabre/NodeTest.php @@ -91,6 +91,7 @@ public function testDavPermissions($permissions, $type, $shared, $shareRootPermi $storage->method('instanceOfStorage') ->willReturn(false); } + $info->method('getStorage') ->willReturn($storage); $view = $this->getMockBuilder(View::class) diff --git a/apps/dav/tests/unit/Connector/Sabre/ObjectTreeTest.php b/apps/dav/tests/unit/Connector/Sabre/ObjectTreeTest.php index 5010f7698b5cd..4f2e5174325db 100644 --- a/apps/dav/tests/unit/Connector/Sabre/ObjectTreeTest.php +++ b/apps/dav/tests/unit/Connector/Sabre/ObjectTreeTest.php @@ -127,7 +127,7 @@ public function testGetNodeForPath( $inputFileName, $fileInfoQueryPath, $outputFileName, - $type + $type, ): void { $rootNode = $this->getMockBuilder(Directory::class) ->disableOriginalConstructor() diff --git a/apps/dav/tests/unit/Connector/Sabre/PrincipalTest.php b/apps/dav/tests/unit/Connector/Sabre/PrincipalTest.php index 30e2c995d1811..27c4efdba203c 100644 --- a/apps/dav/tests/unit/Connector/Sabre/PrincipalTest.php +++ b/apps/dav/tests/unit/Connector/Sabre/PrincipalTest.php @@ -453,9 +453,11 @@ public function testSetGroupMembershipProxy(): void { if ($proxy->getOwnerId() !== 'principals/users/foo') { return false; } + if ($proxy->getProxyId() !== 'principals/users/bar') { return false; } + if ($proxy->getPermissions() !== 3) { return false; } diff --git a/apps/dav/tests/unit/Connector/Sabre/RequestTest/Auth.php b/apps/dav/tests/unit/Connector/Sabre/RequestTest/Auth.php index cf1ea4c6bae02..9b3aef991d11e 100644 --- a/apps/dav/tests/unit/Connector/Sabre/RequestTest/Auth.php +++ b/apps/dav/tests/unit/Connector/Sabre/RequestTest/Auth.php @@ -70,8 +70,10 @@ public function check(RequestInterface $request, ResponseInterface $response) { \OC_Util::setupFS($user); //trigger creation of user home and /files folder \OC::$server->getUserFolder($user); + return [true, "principals/$user"]; } + return [false, 'login failed']; } diff --git a/apps/dav/tests/unit/Connector/Sabre/RequestTest/EncryptionMasterKeyUploadTest.php b/apps/dav/tests/unit/Connector/Sabre/RequestTest/EncryptionMasterKeyUploadTest.php index cbaa0c3101b57..6f93a89312ba4 100644 --- a/apps/dav/tests/unit/Connector/Sabre/RequestTest/EncryptionMasterKeyUploadTest.php +++ b/apps/dav/tests/unit/Connector/Sabre/RequestTest/EncryptionMasterKeyUploadTest.php @@ -28,6 +28,7 @@ protected function setupUser($name, $password) { \OC::$server->getConfig()->setAppValue('encryption', 'useMasterKey', '1'); $this->setupForUser($name, $password); $this->loginWithEncryption($name); + return new View('/' . $name . '/files'); } } diff --git a/apps/dav/tests/unit/Connector/Sabre/RequestTest/EncryptionUploadTest.php b/apps/dav/tests/unit/Connector/Sabre/RequestTest/EncryptionUploadTest.php index f830c54fd0d29..b892348f46ec6 100644 --- a/apps/dav/tests/unit/Connector/Sabre/RequestTest/EncryptionUploadTest.php +++ b/apps/dav/tests/unit/Connector/Sabre/RequestTest/EncryptionUploadTest.php @@ -28,6 +28,7 @@ protected function setupUser($name, $password) { \OC::$server->getConfig()->setAppValue('encryption', 'useMasterKey', '0'); $this->setupForUser($name, $password); $this->loginWithEncryption($name); + return new View('/' . $name . '/files'); } } diff --git a/apps/dav/tests/unit/Connector/Sabre/RequestTest/RequestTestCase.php b/apps/dav/tests/unit/Connector/Sabre/RequestTest/RequestTestCase.php index 29574d53bca25..1aab6377a434b 100644 --- a/apps/dav/tests/unit/Connector/Sabre/RequestTest/RequestTestCase.php +++ b/apps/dav/tests/unit/Connector/Sabre/RequestTest/RequestTestCase.php @@ -33,6 +33,7 @@ protected function getStream($string) { $stream = fopen('php://temp', 'r+'); fwrite($stream, $string); fseek($stream, 0); + return $stream; } @@ -60,6 +61,7 @@ protected function setupUser($name, $password) { $tmpFolder = \OC::$server->getTempManager()->getTemporaryFolder(); $this->registerMount($name, '\OC\Files\Storage\Local', '/' . $name, ['datadir' => $tmpFolder]); $this->loginAsUser($name); + return new View('/' . $name . '/files'); } @@ -78,6 +80,7 @@ protected function request($view, $user, $password, $method, $url, $body = null, if (is_string($body)) { $body = $this->getStream($body); } + $this->logout(); $exceptionPlugin = new ExceptionPlugin('webdav', \OC::$server->get(LoggerInterface::class)); $server = $this->getSabreServer($view, $user, $password, $exceptionPlugin); @@ -91,6 +94,7 @@ protected function request($view, $user, $password, $method, $url, $body = null, $serverParams['HTTP_' . strtoupper(str_replace('-', '_', $header))] = $value; } } + $ncRequest = new \OC\AppFramework\Http\Request([ 'server' => $serverParams ], $this->createMock(IRequestId::class), $this->createMock(IConfig::class), null); @@ -104,6 +108,7 @@ protected function request($view, $user, $password, $method, $url, $body = null, foreach ($exceptionPlugin->getExceptions() as $exception) { throw $exception; } + return $result; } @@ -117,6 +122,7 @@ protected function makeRequest(Server $server, Request $request) { $server->sapi = $sapi; $server->httpRequest = $request; $server->exec(); + return $sapi->getResponse(); } diff --git a/apps/dav/tests/unit/Connector/Sabre/RequestTest/Sapi.php b/apps/dav/tests/unit/Connector/Sabre/RequestTest/Sapi.php index f7bcdd930cad6..1f7805dc9e09c 100644 --- a/apps/dav/tests/unit/Connector/Sabre/RequestTest/Sapi.php +++ b/apps/dav/tests/unit/Connector/Sabre/RequestTest/Sapi.php @@ -47,6 +47,7 @@ public function sendResponse(Response $response): void { } elseif (is_resource($response->getBody())) { stream_copy_to_stream($response->getBody(), $copyStream); } + rewind($copyStream); $this->response = new Response($response->getStatus(), $response->getHeaders(), $copyStream); } diff --git a/apps/dav/tests/unit/Connector/Sabre/SharesPluginTest.php b/apps/dav/tests/unit/Connector/Sabre/SharesPluginTest.php index 546be840cd8f9..2074507e3b99e 100644 --- a/apps/dav/tests/unit/Connector/Sabre/SharesPluginTest.php +++ b/apps/dav/tests/unit/Connector/Sabre/SharesPluginTest.php @@ -106,8 +106,10 @@ public function testGetProperties($shareTypes): void { $share = $this->createMock(IShare::class); $share->method('getShareType') ->willReturn($requestedShareType); + return [$share]; } + return []; }); @@ -185,6 +187,7 @@ public function testPreloadThenGetProperties($shareTypes): void { $share->expects($this->any()) ->method('getShareType') ->willReturn($type); + return $share; }, $shareTypes); diff --git a/apps/dav/tests/unit/Controller/DirectControllerTest.php b/apps/dav/tests/unit/Controller/DirectControllerTest.php index 2476681251c70..faaf67d266b24 100644 --- a/apps/dav/tests/unit/Controller/DirectControllerTest.php +++ b/apps/dav/tests/unit/Controller/DirectControllerTest.php @@ -136,7 +136,7 @@ public function testGetUrlValid(): void { $this->urlGenerator->method('getAbsoluteURL') ->willReturnCallback(function (string $url) { - return 'https://my.nextcloud/'.$url; + return 'https://my.nextcloud/' . $url; }); $result = $this->controller->getUrl(101); diff --git a/apps/dav/tests/unit/DAV/AnonymousOptionsTest.php b/apps/dav/tests/unit/DAV/AnonymousOptionsTest.php index ea5450391e8ca..96577289acbff 100644 --- a/apps/dav/tests/unit/DAV/AnonymousOptionsTest.php +++ b/apps/dav/tests/unit/DAV/AnonymousOptionsTest.php @@ -27,6 +27,7 @@ private function sendRequest($method, $path, $userAgent = '') { $server->sapi = new SapiMock(); $server->exec(); + return $server->httpResponse; } diff --git a/apps/dav/tests/unit/DAV/CustomPropertiesBackendTest.php b/apps/dav/tests/unit/DAV/CustomPropertiesBackendTest.php index 6fc87437fe058..ee074ecc88363 100644 --- a/apps/dav/tests/unit/DAV/CustomPropertiesBackendTest.php +++ b/apps/dav/tests/unit/DAV/CustomPropertiesBackendTest.php @@ -123,8 +123,10 @@ protected function getProps(string $user, string $path) { if ((int)$row['valuetype'] === CustomPropertiesBackend::PROPERTY_TYPE_HREF) { $value = new Href($value); } + $data[$row['propertyname']] = $value; } + $result->closeCursor(); return $data; @@ -205,6 +207,7 @@ public function testPropFindPrincipalCall(): void { $node = $this->createMock(Calendar::class); $node->method('getOwner') ->willReturn('principals/users/dummy_user_42'); + return $node; }); @@ -314,6 +317,7 @@ public function testPropFindPrincipalScheduleDefaultCalendarUrl( if (!str_starts_with($uri, self::BASE_URI)) { return trim(substr($uri, strlen(self::BASE_URI)), '/'); } + return null; }); $this->tree->method('getNodeForPath') @@ -321,13 +325,16 @@ public function testPropFindPrincipalScheduleDefaultCalendarUrl( if (str_starts_with($uri, 'principals/')) { return $this->createMock(IPrincipal::class); } + if (array_key_exists($uri, $nodes)) { $owner = explode('/', $uri)[1]; $node = $this->createMock($nodes[$uri]); $node->method('getOwner') ->willReturn("principals/users/$owner"); + return $node; } + throw new NotFound('Node not found'); }); @@ -352,6 +359,7 @@ public function testPropPatch(string $path, array $existing, array $props, array if (str_starts_with($uri, self::BASE_URI)) { return trim(substr($uri, strlen(self::BASE_URI)), '/'); } + return null; }); $this->tree->method('getNodeForPath') @@ -359,6 +367,7 @@ public function testPropPatch(string $path, array $existing, array $props, array $node = $this->createMock(Calendar::class); $node->method('getOwner') ->willReturn('principals/users/' . $this->user->getUID()); + return $node; }); @@ -402,6 +411,7 @@ public function testPropPatchWithUnsuitableCalendar(): void { if (str_starts_with($uri, self::BASE_URI)) { return trim(substr($uri, strlen(self::BASE_URI)), '/'); } + return null; }); $this->tree->expects(self::once()) diff --git a/apps/dav/tests/unit/DAV/GroupPrincipalTest.php b/apps/dav/tests/unit/DAV/GroupPrincipalTest.php index 92c89fc62f83b..1a8bf98173988 100644 --- a/apps/dav/tests/unit/DAV/GroupPrincipalTest.php +++ b/apps/dav/tests/unit/DAV/GroupPrincipalTest.php @@ -349,7 +349,8 @@ private function mockGroup($gid) { $fooGroup ->expects($this->exactly(1)) ->method('getDisplayName') - ->willReturn('Group '.$gid); + ->willReturn('Group ' . $gid); + return $fooGroup; } } diff --git a/apps/dav/tests/unit/DAV/ViewOnlyPluginTest.php b/apps/dav/tests/unit/DAV/ViewOnlyPluginTest.php index 7a4828dd2de53..27574088b1076 100644 --- a/apps/dav/tests/unit/DAV/ViewOnlyPluginTest.php +++ b/apps/dav/tests/unit/DAV/ViewOnlyPluginTest.php @@ -154,6 +154,7 @@ public function testCanGet(bool $isVersion, ?bool $attrEnabled, bool $expectCanD if (!$expectCanDownloadFile) { $this->expectException(Forbidden::class); } + $this->plugin->checkViewOnly($this->request); } } diff --git a/apps/dav/tests/unit/Files/FileSearchBackendTest.php b/apps/dav/tests/unit/Files/FileSearchBackendTest.php index f6fe8b1c1160a..e51e1d0bdcec4 100644 --- a/apps/dav/tests/unit/Files/FileSearchBackendTest.php +++ b/apps/dav/tests/unit/Files/FileSearchBackendTest.php @@ -276,6 +276,7 @@ private function getBasicQuery($type, $property, $value = null) { [new SearchPropertyDefinition($property, true, true, true), new \SearchDAV\Query\Literal($value)] ); } + $limit = new Limit(); return new Query($select, $from, $where, $orderBy, $limit); diff --git a/apps/dav/tests/unit/Files/MultipartRequestParserTest.php b/apps/dav/tests/unit/Files/MultipartRequestParserTest.php index ebe2a0b172e87..e6325ab8ad1e0 100644 --- a/apps/dav/tests/unit/Files/MultipartRequestParserTest.php +++ b/apps/dav/tests/unit/Files/MultipartRequestParserTest.php @@ -36,7 +36,7 @@ private function getMultipartParser(array $parts, array $headers = [], string $b ->disableOriginalConstructor() ->getMock(); - $headers = array_merge(['Content-Type' => 'multipart/related; boundary='.$boundary], $headers); + $headers = array_merge(['Content-Type' => 'multipart/related; boundary=' . $boundary], $headers); $request->expects($this->any()) ->method('getHeader') ->willReturnCallback(function (string $key) use (&$headers) { @@ -45,17 +45,17 @@ private function getMultipartParser(array $parts, array $headers = [], string $b $body = ''; foreach ($parts as $part) { - $body .= '--'.$boundary."\r\n"; + $body .= '--' . $boundary . "\r\n"; foreach ($part['headers'] as $headerKey => $headerPart) { - $body .= $headerKey.': '.$headerPart."\r\n"; + $body .= $headerKey . ': ' . $headerPart . "\r\n"; } $body .= "\r\n"; - $body .= $part['content']."\r\n"; + $body .= $part['content'] . "\r\n"; } - $body .= '--'.$boundary.'--'; + $body .= '--' . $boundary . '--'; $stream = fopen('php://temp', 'r+'); fwrite($stream, $body); diff --git a/apps/dav/tests/unit/Listener/OutOfOfficeListenerTest.php b/apps/dav/tests/unit/Listener/OutOfOfficeListenerTest.php index e1f474678b0d9..db632fe614c17 100644 --- a/apps/dav/tests/unit/Listener/OutOfOfficeListenerTest.php +++ b/apps/dav/tests/unit/Listener/OutOfOfficeListenerTest.php @@ -207,10 +207,12 @@ public function testHandleSchedulingWithDefaultTimezone(): void { if (!($vcalendar instanceof VCalendar)) { throw new InvalidArgumentException('Calendar data should be a VCALENDAR'); } + $vevent = $vcalendar->VEVENT; if ($vevent === null || !($vevent instanceof VEvent)) { throw new InvalidArgumentException('Calendar data should contain a VEVENT'); } + self::assertSame('Europe/Prague', $vevent->DTSTART['TZID']?->getValue()); self::assertSame('Europe/Prague', $vevent->DTEND['TZID']?->getValue()); }); @@ -357,10 +359,12 @@ public function testHandleChangeRecreate(): void { if (!($vcalendar instanceof VCalendar)) { throw new InvalidArgumentException('Calendar data should be a VCALENDAR'); } + $vevent = $vcalendar->VEVENT; if ($vevent === null || !($vevent instanceof VEvent)) { throw new InvalidArgumentException('Calendar data should contain a VEVENT'); } + self::assertSame('Europe/Berlin', $vevent->DTSTART['TZID']?->getValue()); self::assertSame('Europe/Berlin', $vevent->DTEND['TZID']?->getValue()); }); @@ -424,10 +428,12 @@ public function testHandleChangeWithoutTimezone(): void { if (!($vcalendar instanceof VCalendar)) { throw new InvalidArgumentException('Calendar data should be a VCALENDAR'); } + $vevent = $vcalendar->VEVENT; if ($vevent === null || !($vevent instanceof VEvent)) { throw new InvalidArgumentException('Calendar data should contain a VEVENT'); } + // UTC datetimes are stored without a TZID self::assertSame(null, $vevent->DTSTART['TZID']?->getValue()); self::assertSame(null, $vevent->DTEND['TZID']?->getValue()); diff --git a/apps/dav/tests/unit/Migration/RemoveDeletedUsersCalendarSubscriptionsTest.php b/apps/dav/tests/unit/Migration/RemoveDeletedUsersCalendarSubscriptionsTest.php index a3daf1c918a83..09538cec33f9f 100644 --- a/apps/dav/tests/unit/Migration/RemoveDeletedUsersCalendarSubscriptionsTest.php +++ b/apps/dav/tests/unit/Migration/RemoveDeletedUsersCalendarSubscriptionsTest.php @@ -126,6 +126,7 @@ public function testRun(array $subscriptions, array $userExists, int $deletions) return $userExists[$username]; }); } + $this->output->expects($this->once())->method('finishProgress'); $this->output->expects($this->once())->method('info')->with(sprintf('%d calendar subscriptions without an user have been cleaned up', $deletions)); diff --git a/apps/dav/tests/unit/Search/ContactsSearchProviderTest.php b/apps/dav/tests/unit/Search/ContactsSearchProviderTest.php index bfc57dc61d70c..0a83d238f0f5d 100644 --- a/apps/dav/tests/unit/Search/ContactsSearchProviderTest.php +++ b/apps/dav/tests/unit/Search/ContactsSearchProviderTest.php @@ -37,22 +37,22 @@ class ContactsSearchProviderTest extends TestCase { /** @var ContactsSearchProvider */ private $provider; - private $vcardTest0 = 'BEGIN:VCARD'.PHP_EOL. - 'VERSION:3.0'.PHP_EOL. - 'PRODID:-//Sabre//Sabre VObject 4.1.2//EN'.PHP_EOL. - 'UID:Test'.PHP_EOL. - 'FN:FN of Test'.PHP_EOL. - 'N:Test;;;;'.PHP_EOL. - 'EMAIL:forrestgump@example.com'.PHP_EOL. + private $vcardTest0 = 'BEGIN:VCARD' . PHP_EOL . + 'VERSION:3.0' . PHP_EOL . + 'PRODID:-//Sabre//Sabre VObject 4.1.2//EN' . PHP_EOL . + 'UID:Test' . PHP_EOL . + 'FN:FN of Test' . PHP_EOL . + 'N:Test;;;;' . PHP_EOL . + 'EMAIL:forrestgump@example.com' . PHP_EOL . 'END:VCARD'; - private $vcardTest1 = 'BEGIN:VCARD'.PHP_EOL. - 'VERSION:3.0'.PHP_EOL. - 'PRODID:-//Sabre//Sabre VObject 4.1.2//EN'.PHP_EOL. - 'PHOTO;ENCODING=b;TYPE=image/jpeg:'.PHP_EOL. - 'UID:Test2'.PHP_EOL. - 'FN:FN of Test2'.PHP_EOL. - 'N:Test2;;;;'.PHP_EOL. + private $vcardTest1 = 'BEGIN:VCARD' . PHP_EOL . + 'VERSION:3.0' . PHP_EOL . + 'PRODID:-//Sabre//Sabre VObject 4.1.2//EN' . PHP_EOL . + 'PHOTO;ENCODING=b;TYPE=image/jpeg:' . PHP_EOL . + 'UID:Test2' . PHP_EOL . + 'FN:FN of Test2' . PHP_EOL . + 'N:Test2;;;;' . PHP_EOL . 'END:VCARD'; protected function setUp(): void { diff --git a/apps/dav/tests/unit/Search/EventsSearchProviderTest.php b/apps/dav/tests/unit/Search/EventsSearchProviderTest.php index d194b7fa7c659..0eafe0782e5a9 100644 --- a/apps/dav/tests/unit/Search/EventsSearchProviderTest.php +++ b/apps/dav/tests/unit/Search/EventsSearchProviderTest.php @@ -38,183 +38,183 @@ class EventsSearchProviderTest extends TestCase { private $provider; // NO SUMMARY - private $vEvent0 = 'BEGIN:VCALENDAR'.PHP_EOL. - 'VERSION:2.0'.PHP_EOL. - 'PRODID:-//Apple Inc.//Mac OS X 10.11.6//EN'.PHP_EOL. - 'CALSCALE:GREGORIAN'.PHP_EOL. - 'BEGIN:VEVENT'.PHP_EOL. - 'CREATED:20161004T144433Z'.PHP_EOL. - 'UID:85560E76-1B0D-47E1-A735-21625767FCA4'.PHP_EOL. - 'DTEND;VALUE=DATE:20161008'.PHP_EOL. - 'TRANSP:TRANSPARENT'.PHP_EOL. - 'DTSTART;VALUE=DATE:20161005'.PHP_EOL. - 'DTSTAMP:20161004T144437Z'.PHP_EOL. - 'SEQUENCE:0'.PHP_EOL. - 'END:VEVENT'.PHP_EOL. + private $vEvent0 = 'BEGIN:VCALENDAR' . PHP_EOL . + 'VERSION:2.0' . PHP_EOL . + 'PRODID:-//Apple Inc.//Mac OS X 10.11.6//EN' . PHP_EOL . + 'CALSCALE:GREGORIAN' . PHP_EOL . + 'BEGIN:VEVENT' . PHP_EOL . + 'CREATED:20161004T144433Z' . PHP_EOL . + 'UID:85560E76-1B0D-47E1-A735-21625767FCA4' . PHP_EOL . + 'DTEND;VALUE=DATE:20161008' . PHP_EOL . + 'TRANSP:TRANSPARENT' . PHP_EOL . + 'DTSTART;VALUE=DATE:20161005' . PHP_EOL . + 'DTSTAMP:20161004T144437Z' . PHP_EOL . + 'SEQUENCE:0' . PHP_EOL . + 'END:VEVENT' . PHP_EOL . 'END:VCALENDAR'; // TIMED SAME DAY - private $vEvent1 = 'BEGIN:VCALENDAR'.PHP_EOL. - 'VERSION:2.0'.PHP_EOL. - 'PRODID:-//Tests//'.PHP_EOL. - 'CALSCALE:GREGORIAN'.PHP_EOL. - 'BEGIN:VTIMEZONE'.PHP_EOL. - 'TZID:Europe/Berlin'.PHP_EOL. - 'BEGIN:DAYLIGHT'.PHP_EOL. - 'TZOFFSETFROM:+0100'.PHP_EOL. - 'RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU'.PHP_EOL. - 'DTSTART:19810329T020000'.PHP_EOL. - 'TZNAME:GMT+2'.PHP_EOL. - 'TZOFFSETTO:+0200'.PHP_EOL. - 'END:DAYLIGHT'.PHP_EOL. - 'BEGIN:STANDARD'.PHP_EOL. - 'TZOFFSETFROM:+0200'.PHP_EOL. - 'RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU'.PHP_EOL. - 'DTSTART:19961027T030000'.PHP_EOL. - 'TZNAME:GMT+1'.PHP_EOL. - 'TZOFFSETTO:+0100'.PHP_EOL. - 'END:STANDARD'.PHP_EOL. - 'END:VTIMEZONE'.PHP_EOL. - 'BEGIN:VEVENT'.PHP_EOL. - 'CREATED:20160809T163629Z'.PHP_EOL. - 'UID:0AD16F58-01B3-463B-A215-FD09FC729A02'.PHP_EOL. - 'DTEND;TZID=Europe/Berlin:20160816T100000'.PHP_EOL. - 'TRANSP:OPAQUE'.PHP_EOL. - 'SUMMARY:Test Europe Berlin'.PHP_EOL. - 'DTSTART;TZID=Europe/Berlin:20160816T090000'.PHP_EOL. - 'DTSTAMP:20160809T163632Z'.PHP_EOL. - 'SEQUENCE:0'.PHP_EOL. - 'END:VEVENT'.PHP_EOL. + private $vEvent1 = 'BEGIN:VCALENDAR' . PHP_EOL . + 'VERSION:2.0' . PHP_EOL . + 'PRODID:-//Tests//' . PHP_EOL . + 'CALSCALE:GREGORIAN' . PHP_EOL . + 'BEGIN:VTIMEZONE' . PHP_EOL . + 'TZID:Europe/Berlin' . PHP_EOL . + 'BEGIN:DAYLIGHT' . PHP_EOL . + 'TZOFFSETFROM:+0100' . PHP_EOL . + 'RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU' . PHP_EOL . + 'DTSTART:19810329T020000' . PHP_EOL . + 'TZNAME:GMT+2' . PHP_EOL . + 'TZOFFSETTO:+0200' . PHP_EOL . + 'END:DAYLIGHT' . PHP_EOL . + 'BEGIN:STANDARD' . PHP_EOL . + 'TZOFFSETFROM:+0200' . PHP_EOL . + 'RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU' . PHP_EOL . + 'DTSTART:19961027T030000' . PHP_EOL . + 'TZNAME:GMT+1' . PHP_EOL . + 'TZOFFSETTO:+0100' . PHP_EOL . + 'END:STANDARD' . PHP_EOL . + 'END:VTIMEZONE' . PHP_EOL . + 'BEGIN:VEVENT' . PHP_EOL . + 'CREATED:20160809T163629Z' . PHP_EOL . + 'UID:0AD16F58-01B3-463B-A215-FD09FC729A02' . PHP_EOL . + 'DTEND;TZID=Europe/Berlin:20160816T100000' . PHP_EOL . + 'TRANSP:OPAQUE' . PHP_EOL . + 'SUMMARY:Test Europe Berlin' . PHP_EOL . + 'DTSTART;TZID=Europe/Berlin:20160816T090000' . PHP_EOL . + 'DTSTAMP:20160809T163632Z' . PHP_EOL . + 'SEQUENCE:0' . PHP_EOL . + 'END:VEVENT' . PHP_EOL . 'END:VCALENDAR'; // TIMED DIFFERENT DAY - private $vEvent2 = 'BEGIN:VCALENDAR'.PHP_EOL. - 'VERSION:2.0'.PHP_EOL. - 'PRODID:-//Tests//'.PHP_EOL. - 'CALSCALE:GREGORIAN'.PHP_EOL. - 'BEGIN:VTIMEZONE'.PHP_EOL. - 'TZID:Europe/Berlin'.PHP_EOL. - 'BEGIN:DAYLIGHT'.PHP_EOL. - 'TZOFFSETFROM:+0100'.PHP_EOL. - 'RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU'.PHP_EOL. - 'DTSTART:19810329T020000'.PHP_EOL. - 'TZNAME:GMT+2'.PHP_EOL. - 'TZOFFSETTO:+0200'.PHP_EOL. - 'END:DAYLIGHT'.PHP_EOL. - 'BEGIN:STANDARD'.PHP_EOL. - 'TZOFFSETFROM:+0200'.PHP_EOL. - 'RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU'.PHP_EOL. - 'DTSTART:19961027T030000'.PHP_EOL. - 'TZNAME:GMT+1'.PHP_EOL. - 'TZOFFSETTO:+0100'.PHP_EOL. - 'END:STANDARD'.PHP_EOL. - 'END:VTIMEZONE'.PHP_EOL. - 'BEGIN:VEVENT'.PHP_EOL. - 'CREATED:20160809T163629Z'.PHP_EOL. - 'UID:0AD16F58-01B3-463B-A215-FD09FC729A02'.PHP_EOL. - 'DTEND;TZID=Europe/Berlin:20160817T100000'.PHP_EOL. - 'TRANSP:OPAQUE'.PHP_EOL. - 'SUMMARY:Test Europe Berlin'.PHP_EOL. - 'DTSTART;TZID=Europe/Berlin:20160816T090000'.PHP_EOL. - 'DTSTAMP:20160809T163632Z'.PHP_EOL. - 'SEQUENCE:0'.PHP_EOL. - 'END:VEVENT'.PHP_EOL. + private $vEvent2 = 'BEGIN:VCALENDAR' . PHP_EOL . + 'VERSION:2.0' . PHP_EOL . + 'PRODID:-//Tests//' . PHP_EOL . + 'CALSCALE:GREGORIAN' . PHP_EOL . + 'BEGIN:VTIMEZONE' . PHP_EOL . + 'TZID:Europe/Berlin' . PHP_EOL . + 'BEGIN:DAYLIGHT' . PHP_EOL . + 'TZOFFSETFROM:+0100' . PHP_EOL . + 'RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU' . PHP_EOL . + 'DTSTART:19810329T020000' . PHP_EOL . + 'TZNAME:GMT+2' . PHP_EOL . + 'TZOFFSETTO:+0200' . PHP_EOL . + 'END:DAYLIGHT' . PHP_EOL . + 'BEGIN:STANDARD' . PHP_EOL . + 'TZOFFSETFROM:+0200' . PHP_EOL . + 'RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU' . PHP_EOL . + 'DTSTART:19961027T030000' . PHP_EOL . + 'TZNAME:GMT+1' . PHP_EOL . + 'TZOFFSETTO:+0100' . PHP_EOL . + 'END:STANDARD' . PHP_EOL . + 'END:VTIMEZONE' . PHP_EOL . + 'BEGIN:VEVENT' . PHP_EOL . + 'CREATED:20160809T163629Z' . PHP_EOL . + 'UID:0AD16F58-01B3-463B-A215-FD09FC729A02' . PHP_EOL . + 'DTEND;TZID=Europe/Berlin:20160817T100000' . PHP_EOL . + 'TRANSP:OPAQUE' . PHP_EOL . + 'SUMMARY:Test Europe Berlin' . PHP_EOL . + 'DTSTART;TZID=Europe/Berlin:20160816T090000' . PHP_EOL . + 'DTSTAMP:20160809T163632Z' . PHP_EOL . + 'SEQUENCE:0' . PHP_EOL . + 'END:VEVENT' . PHP_EOL . 'END:VCALENDAR'; // ALL-DAY ONE-DAY - private $vEvent3 = 'BEGIN:VCALENDAR'.PHP_EOL. - 'VERSION:2.0'.PHP_EOL. - 'PRODID:-//Apple Inc.//Mac OS X 10.11.6//EN'.PHP_EOL. - 'CALSCALE:GREGORIAN'.PHP_EOL. - 'BEGIN:VEVENT'.PHP_EOL. - 'CREATED:20161004T144433Z'.PHP_EOL. - 'UID:85560E76-1B0D-47E1-A735-21625767FCA4'.PHP_EOL. - 'DTEND;VALUE=DATE:20161006'.PHP_EOL. - 'TRANSP:TRANSPARENT'.PHP_EOL. - 'DTSTART;VALUE=DATE:20161005'.PHP_EOL. - 'DTSTAMP:20161004T144437Z'.PHP_EOL. - 'SEQUENCE:0'.PHP_EOL. - 'END:VEVENT'.PHP_EOL. + private $vEvent3 = 'BEGIN:VCALENDAR' . PHP_EOL . + 'VERSION:2.0' . PHP_EOL . + 'PRODID:-//Apple Inc.//Mac OS X 10.11.6//EN' . PHP_EOL . + 'CALSCALE:GREGORIAN' . PHP_EOL . + 'BEGIN:VEVENT' . PHP_EOL . + 'CREATED:20161004T144433Z' . PHP_EOL . + 'UID:85560E76-1B0D-47E1-A735-21625767FCA4' . PHP_EOL . + 'DTEND;VALUE=DATE:20161006' . PHP_EOL . + 'TRANSP:TRANSPARENT' . PHP_EOL . + 'DTSTART;VALUE=DATE:20161005' . PHP_EOL . + 'DTSTAMP:20161004T144437Z' . PHP_EOL . + 'SEQUENCE:0' . PHP_EOL . + 'END:VEVENT' . PHP_EOL . 'END:VCALENDAR'; // ALL-DAY MULTIPLE DAYS - private $vEvent4 = 'BEGIN:VCALENDAR'.PHP_EOL. - 'VERSION:2.0'.PHP_EOL. - 'PRODID:-//Apple Inc.//Mac OS X 10.11.6//EN'.PHP_EOL. - 'CALSCALE:GREGORIAN'.PHP_EOL. - 'BEGIN:VEVENT'.PHP_EOL. - 'CREATED:20161004T144433Z'.PHP_EOL. - 'UID:85560E76-1B0D-47E1-A735-21625767FCA4'.PHP_EOL. - 'DTEND;VALUE=DATE:20161008'.PHP_EOL. - 'TRANSP:TRANSPARENT'.PHP_EOL. - 'DTSTART;VALUE=DATE:20161005'.PHP_EOL. - 'DTSTAMP:20161004T144437Z'.PHP_EOL. - 'SEQUENCE:0'.PHP_EOL. - 'END:VEVENT'.PHP_EOL. + private $vEvent4 = 'BEGIN:VCALENDAR' . PHP_EOL . + 'VERSION:2.0' . PHP_EOL . + 'PRODID:-//Apple Inc.//Mac OS X 10.11.6//EN' . PHP_EOL . + 'CALSCALE:GREGORIAN' . PHP_EOL . + 'BEGIN:VEVENT' . PHP_EOL . + 'CREATED:20161004T144433Z' . PHP_EOL . + 'UID:85560E76-1B0D-47E1-A735-21625767FCA4' . PHP_EOL . + 'DTEND;VALUE=DATE:20161008' . PHP_EOL . + 'TRANSP:TRANSPARENT' . PHP_EOL . + 'DTSTART;VALUE=DATE:20161005' . PHP_EOL . + 'DTSTAMP:20161004T144437Z' . PHP_EOL . + 'SEQUENCE:0' . PHP_EOL . + 'END:VEVENT' . PHP_EOL . 'END:VCALENDAR'; // DURATION - private $vEvent5 = 'BEGIN:VCALENDAR'.PHP_EOL. - 'VERSION:2.0'.PHP_EOL. - 'PRODID:-//Apple Inc.//Mac OS X 10.11.6//EN'.PHP_EOL. - 'CALSCALE:GREGORIAN'.PHP_EOL. - 'BEGIN:VEVENT'.PHP_EOL. - 'CREATED:20161004T144433Z'.PHP_EOL. - 'UID:85560E76-1B0D-47E1-A735-21625767FCA4'.PHP_EOL. - 'DURATION:P5D'.PHP_EOL. - 'TRANSP:TRANSPARENT'.PHP_EOL. - 'DTSTART;VALUE=DATE:20161005'.PHP_EOL. - 'DTSTAMP:20161004T144437Z'.PHP_EOL. - 'SEQUENCE:0'.PHP_EOL. - 'END:VEVENT'.PHP_EOL. + private $vEvent5 = 'BEGIN:VCALENDAR' . PHP_EOL . + 'VERSION:2.0' . PHP_EOL . + 'PRODID:-//Apple Inc.//Mac OS X 10.11.6//EN' . PHP_EOL . + 'CALSCALE:GREGORIAN' . PHP_EOL . + 'BEGIN:VEVENT' . PHP_EOL . + 'CREATED:20161004T144433Z' . PHP_EOL . + 'UID:85560E76-1B0D-47E1-A735-21625767FCA4' . PHP_EOL . + 'DURATION:P5D' . PHP_EOL . + 'TRANSP:TRANSPARENT' . PHP_EOL . + 'DTSTART;VALUE=DATE:20161005' . PHP_EOL . + 'DTSTAMP:20161004T144437Z' . PHP_EOL . + 'SEQUENCE:0' . PHP_EOL . + 'END:VEVENT' . PHP_EOL . 'END:VCALENDAR'; // NO DTEND - DATE - private $vEvent6 = 'BEGIN:VCALENDAR'.PHP_EOL. - 'VERSION:2.0'.PHP_EOL. - 'PRODID:-//Apple Inc.//Mac OS X 10.11.6//EN'.PHP_EOL. - 'CALSCALE:GREGORIAN'.PHP_EOL. - 'BEGIN:VEVENT'.PHP_EOL. - 'CREATED:20161004T144433Z'.PHP_EOL. - 'UID:85560E76-1B0D-47E1-A735-21625767FCA4'.PHP_EOL. - 'TRANSP:TRANSPARENT'.PHP_EOL. - 'DTSTART;VALUE=DATE:20161005'.PHP_EOL. - 'DTSTAMP:20161004T144437Z'.PHP_EOL. - 'SEQUENCE:0'.PHP_EOL. - 'END:VEVENT'.PHP_EOL. + private $vEvent6 = 'BEGIN:VCALENDAR' . PHP_EOL . + 'VERSION:2.0' . PHP_EOL . + 'PRODID:-//Apple Inc.//Mac OS X 10.11.6//EN' . PHP_EOL . + 'CALSCALE:GREGORIAN' . PHP_EOL . + 'BEGIN:VEVENT' . PHP_EOL . + 'CREATED:20161004T144433Z' . PHP_EOL . + 'UID:85560E76-1B0D-47E1-A735-21625767FCA4' . PHP_EOL . + 'TRANSP:TRANSPARENT' . PHP_EOL . + 'DTSTART;VALUE=DATE:20161005' . PHP_EOL . + 'DTSTAMP:20161004T144437Z' . PHP_EOL . + 'SEQUENCE:0' . PHP_EOL . + 'END:VEVENT' . PHP_EOL . 'END:VCALENDAR'; // NO DTEND - DATE-TIME - private $vEvent7 = 'BEGIN:VCALENDAR'.PHP_EOL. - 'VERSION:2.0'.PHP_EOL. - 'PRODID:-//Tests//'.PHP_EOL. - 'CALSCALE:GREGORIAN'.PHP_EOL. - 'BEGIN:VTIMEZONE'.PHP_EOL. - 'TZID:Europe/Berlin'.PHP_EOL. - 'BEGIN:DAYLIGHT'.PHP_EOL. - 'TZOFFSETFROM:+0100'.PHP_EOL. - 'RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU'.PHP_EOL. - 'DTSTART:19810329T020000'.PHP_EOL. - 'TZNAME:GMT+2'.PHP_EOL. - 'TZOFFSETTO:+0200'.PHP_EOL. - 'END:DAYLIGHT'.PHP_EOL. - 'BEGIN:STANDARD'.PHP_EOL. - 'TZOFFSETFROM:+0200'.PHP_EOL. - 'RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU'.PHP_EOL. - 'DTSTART:19961027T030000'.PHP_EOL. - 'TZNAME:GMT+1'.PHP_EOL. - 'TZOFFSETTO:+0100'.PHP_EOL. - 'END:STANDARD'.PHP_EOL. - 'END:VTIMEZONE'.PHP_EOL. - 'BEGIN:VEVENT'.PHP_EOL. - 'CREATED:20160809T163629Z'.PHP_EOL. - 'UID:0AD16F58-01B3-463B-A215-FD09FC729A02'.PHP_EOL. - 'TRANSP:OPAQUE'.PHP_EOL. - 'SUMMARY:Test Europe Berlin'.PHP_EOL. - 'DTSTART;TZID=Europe/Berlin:20160816T090000'.PHP_EOL. - 'DTSTAMP:20160809T163632Z'.PHP_EOL. - 'SEQUENCE:0'.PHP_EOL. - 'END:VEVENT'.PHP_EOL. + private $vEvent7 = 'BEGIN:VCALENDAR' . PHP_EOL . + 'VERSION:2.0' . PHP_EOL . + 'PRODID:-//Tests//' . PHP_EOL . + 'CALSCALE:GREGORIAN' . PHP_EOL . + 'BEGIN:VTIMEZONE' . PHP_EOL . + 'TZID:Europe/Berlin' . PHP_EOL . + 'BEGIN:DAYLIGHT' . PHP_EOL . + 'TZOFFSETFROM:+0100' . PHP_EOL . + 'RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU' . PHP_EOL . + 'DTSTART:19810329T020000' . PHP_EOL . + 'TZNAME:GMT+2' . PHP_EOL . + 'TZOFFSETTO:+0200' . PHP_EOL . + 'END:DAYLIGHT' . PHP_EOL . + 'BEGIN:STANDARD' . PHP_EOL . + 'TZOFFSETFROM:+0200' . PHP_EOL . + 'RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU' . PHP_EOL . + 'DTSTART:19961027T030000' . PHP_EOL . + 'TZNAME:GMT+1' . PHP_EOL . + 'TZOFFSETTO:+0100' . PHP_EOL . + 'END:STANDARD' . PHP_EOL . + 'END:VTIMEZONE' . PHP_EOL . + 'BEGIN:VEVENT' . PHP_EOL . + 'CREATED:20160809T163629Z' . PHP_EOL . + 'UID:0AD16F58-01B3-463B-A215-FD09FC729A02' . PHP_EOL . + 'TRANSP:OPAQUE' . PHP_EOL . + 'SUMMARY:Test Europe Berlin' . PHP_EOL . + 'DTSTART;TZID=Europe/Berlin:20160816T090000' . PHP_EOL . + 'DTSTAMP:20160809T163632Z' . PHP_EOL . + 'SEQUENCE:0' . PHP_EOL . + 'END:VEVENT' . PHP_EOL . 'END:VCALENDAR'; protected function setUp(): void { diff --git a/apps/dav/tests/unit/Search/TasksSearchProviderTest.php b/apps/dav/tests/unit/Search/TasksSearchProviderTest.php index 18b6f0a593019..0b1bf1ba0d79c 100644 --- a/apps/dav/tests/unit/Search/TasksSearchProviderTest.php +++ b/apps/dav/tests/unit/Search/TasksSearchProviderTest.php @@ -38,67 +38,67 @@ class TasksSearchProviderTest extends TestCase { private $provider; // NO DUE NOR COMPLETED NOR SUMMARY - private $vTodo0 = 'BEGIN:VCALENDAR'.PHP_EOL. - 'PRODID:TEST'.PHP_EOL. - 'VERSION:2.0'.PHP_EOL. - 'BEGIN:VTODO'.PHP_EOL. - 'UID:20070313T123432Z-456553@example.com'.PHP_EOL. - 'DTSTAMP:20070313T123432Z'.PHP_EOL. - 'STATUS:NEEDS-ACTION'.PHP_EOL. - 'END:VTODO'.PHP_EOL. + private $vTodo0 = 'BEGIN:VCALENDAR' . PHP_EOL . + 'PRODID:TEST' . PHP_EOL . + 'VERSION:2.0' . PHP_EOL . + 'BEGIN:VTODO' . PHP_EOL . + 'UID:20070313T123432Z-456553@example.com' . PHP_EOL . + 'DTSTAMP:20070313T123432Z' . PHP_EOL . + 'STATUS:NEEDS-ACTION' . PHP_EOL . + 'END:VTODO' . PHP_EOL . 'END:VCALENDAR'; // DUE AND COMPLETED - private $vTodo1 = 'BEGIN:VCALENDAR'.PHP_EOL. - 'PRODID:TEST'.PHP_EOL. - 'VERSION:2.0'.PHP_EOL. - 'BEGIN:VTODO'.PHP_EOL. - 'UID:20070313T123432Z-456553@example.com'.PHP_EOL. - 'DTSTAMP:20070313T123432Z'.PHP_EOL. - 'COMPLETED:20070707T100000Z'.PHP_EOL. - 'DUE;VALUE=DATE:20070501'.PHP_EOL. - 'SUMMARY:Task title'.PHP_EOL. - 'STATUS:NEEDS-ACTION'.PHP_EOL. - 'END:VTODO'.PHP_EOL. + private $vTodo1 = 'BEGIN:VCALENDAR' . PHP_EOL . + 'PRODID:TEST' . PHP_EOL . + 'VERSION:2.0' . PHP_EOL . + 'BEGIN:VTODO' . PHP_EOL . + 'UID:20070313T123432Z-456553@example.com' . PHP_EOL . + 'DTSTAMP:20070313T123432Z' . PHP_EOL . + 'COMPLETED:20070707T100000Z' . PHP_EOL . + 'DUE;VALUE=DATE:20070501' . PHP_EOL . + 'SUMMARY:Task title' . PHP_EOL . + 'STATUS:NEEDS-ACTION' . PHP_EOL . + 'END:VTODO' . PHP_EOL . 'END:VCALENDAR'; // COMPLETED ONLY - private $vTodo2 = 'BEGIN:VCALENDAR'.PHP_EOL. - 'PRODID:TEST'.PHP_EOL. - 'VERSION:2.0'.PHP_EOL. - 'BEGIN:VTODO'.PHP_EOL. - 'UID:20070313T123432Z-456553@example.com'.PHP_EOL. - 'DTSTAMP:20070313T123432Z'.PHP_EOL. - 'COMPLETED:20070707T100000Z'.PHP_EOL. - 'SUMMARY:Task title'.PHP_EOL. - 'STATUS:NEEDS-ACTION'.PHP_EOL. - 'END:VTODO'.PHP_EOL. + private $vTodo2 = 'BEGIN:VCALENDAR' . PHP_EOL . + 'PRODID:TEST' . PHP_EOL . + 'VERSION:2.0' . PHP_EOL . + 'BEGIN:VTODO' . PHP_EOL . + 'UID:20070313T123432Z-456553@example.com' . PHP_EOL . + 'DTSTAMP:20070313T123432Z' . PHP_EOL . + 'COMPLETED:20070707T100000Z' . PHP_EOL . + 'SUMMARY:Task title' . PHP_EOL . + 'STATUS:NEEDS-ACTION' . PHP_EOL . + 'END:VTODO' . PHP_EOL . 'END:VCALENDAR'; // DUE DATE - private $vTodo3 = 'BEGIN:VCALENDAR'.PHP_EOL. - 'PRODID:TEST'.PHP_EOL. - 'VERSION:2.0'.PHP_EOL. - 'BEGIN:VTODO'.PHP_EOL. - 'UID:20070313T123432Z-456553@example.com'.PHP_EOL. - 'DTSTAMP:20070313T123432Z'.PHP_EOL. - 'DUE;VALUE=DATE:20070501'.PHP_EOL. - 'SUMMARY:Task title'.PHP_EOL. - 'STATUS:NEEDS-ACTION'.PHP_EOL. - 'END:VTODO'.PHP_EOL. + private $vTodo3 = 'BEGIN:VCALENDAR' . PHP_EOL . + 'PRODID:TEST' . PHP_EOL . + 'VERSION:2.0' . PHP_EOL . + 'BEGIN:VTODO' . PHP_EOL . + 'UID:20070313T123432Z-456553@example.com' . PHP_EOL . + 'DTSTAMP:20070313T123432Z' . PHP_EOL . + 'DUE;VALUE=DATE:20070501' . PHP_EOL . + 'SUMMARY:Task title' . PHP_EOL . + 'STATUS:NEEDS-ACTION' . PHP_EOL . + 'END:VTODO' . PHP_EOL . 'END:VCALENDAR'; // DUE DATETIME - private $vTodo4 = 'BEGIN:VCALENDAR'.PHP_EOL. - 'PRODID:TEST'.PHP_EOL. - 'VERSION:2.0'.PHP_EOL. - 'BEGIN:VTODO'.PHP_EOL. - 'UID:20070313T123432Z-456553@example.com'.PHP_EOL. - 'DTSTAMP:20070313T123432Z'.PHP_EOL. - 'DUE:20070709T130000Z'.PHP_EOL. - 'SUMMARY:Task title'.PHP_EOL. - 'STATUS:NEEDS-ACTION'.PHP_EOL. - 'END:VTODO'.PHP_EOL. + private $vTodo4 = 'BEGIN:VCALENDAR' . PHP_EOL . + 'PRODID:TEST' . PHP_EOL . + 'VERSION:2.0' . PHP_EOL . + 'BEGIN:VTODO' . PHP_EOL . + 'UID:20070313T123432Z-456553@example.com' . PHP_EOL . + 'DTSTAMP:20070313T123432Z' . PHP_EOL . + 'DUE:20070709T130000Z' . PHP_EOL . + 'SUMMARY:Task title' . PHP_EOL . + 'STATUS:NEEDS-ACTION' . PHP_EOL . + 'END:VTODO' . PHP_EOL . 'END:VCALENDAR'; protected function setUp(): void { diff --git a/apps/dav/tests/unit/Service/AbsenceServiceTest.php b/apps/dav/tests/unit/Service/AbsenceServiceTest.php index 5cff29a6f61ac..ce7764a44d3b0 100644 --- a/apps/dav/tests/unit/Service/AbsenceServiceTest.php +++ b/apps/dav/tests/unit/Service/AbsenceServiceTest.php @@ -100,6 +100,7 @@ public function testCreateAbsenceEmitsScheduledEvent(): void { ); self::assertEquals('status', $data->getShortMessage()); self::assertEquals('message', $data->getMessage()); + return true; })); $this->timeFactory->expects(self::once()) @@ -140,6 +141,7 @@ public function testUpdateAbsenceEmitsChangedEvent(): void { self::assertEquals('2023-01-10', $absence->getLastDay()); self::assertEquals('status', $absence->getStatus()); self::assertEquals('message', $absence->getMessage()); + return $absence; }); $this->timezoneService->expects(self::once()) @@ -164,6 +166,7 @@ public function testUpdateAbsenceEmitsChangedEvent(): void { ); self::assertEquals('status', $data->getShortMessage()); self::assertEquals('message', $data->getMessage()); + return true; })); $this->timeFactory->expects(self::once()) @@ -331,6 +334,7 @@ public function testUpdateAbsenceSchedulesBothJobs(): void { ->willReturnCallback(static function (Absence $absence) use ($startDateString, $endDateString): Absence { self::assertEquals($startDateString, $absence->getFirstDay()); self::assertEquals($endDateString, $absence->getLastDay()); + return $absence; }); $this->timezoneService->expects(self::once()) @@ -385,6 +389,7 @@ public function testUpdateSchedulesOnlyEndJob(): void { ->willReturnCallback(static function (Absence $absence) use ($endDateString): Absence { self::assertEquals('2023-01-05', $absence->getFirstDay()); self::assertEquals($endDateString, $absence->getLastDay()); + return $absence; }); $this->timezoneService->expects(self::once()) @@ -431,6 +436,7 @@ public function testUpdateAbsenceSchedulesNoJob(): void { ->willReturnCallback(static function (Absence $absence): Absence { self::assertEquals('2023-01-05', $absence->getFirstDay()); self::assertEquals('2023-01-10', $absence->getLastDay()); + return $absence; }); $this->timezoneService->expects(self::once()) diff --git a/apps/dav/tests/unit/SystemTag/SystemTagMappingNodeTest.php b/apps/dav/tests/unit/SystemTag/SystemTagMappingNodeTest.php index 308950cef341f..9091f46eb545d 100644 --- a/apps/dav/tests/unit/SystemTag/SystemTagMappingNodeTest.php +++ b/apps/dav/tests/unit/SystemTag/SystemTagMappingNodeTest.php @@ -34,6 +34,7 @@ public function getMappingNode($tag = null, array $writableNodeIds = []) { if ($tag === null) { $tag = new SystemTag(1, 'Test', true, true); } + return new \OCA\DAV\SystemTag\SystemTagMappingNode( $tag, 123, diff --git a/apps/dav/tests/unit/SystemTag/SystemTagNodeTest.php b/apps/dav/tests/unit/SystemTag/SystemTagNodeTest.php index 82aa81674dfa2..46feef60e031b 100644 --- a/apps/dav/tests/unit/SystemTag/SystemTagNodeTest.php +++ b/apps/dav/tests/unit/SystemTag/SystemTagNodeTest.php @@ -40,6 +40,7 @@ protected function getTagNode($isAdmin = true, $tag = null) { if ($tag === null) { $tag = new SystemTag(1, 'Test', true, true); } + return new \OCA\DAV\SystemTag\SystemTagNode( $tag, $this->user, @@ -234,6 +235,7 @@ public function testDeleteTag($isAdmin): void { if (!$isAdmin) { $this->expectException(Forbidden::class); } + $this->getTagNode($isAdmin, $tag)->delete(); } diff --git a/apps/dav/tests/unit/SystemTag/SystemTagPluginTest.php b/apps/dav/tests/unit/SystemTag/SystemTagPluginTest.php index 67e7afa9c548a..9fdbf625eb55d 100644 --- a/apps/dav/tests/unit/SystemTag/SystemTagPluginTest.php +++ b/apps/dav/tests/unit/SystemTag/SystemTagPluginTest.php @@ -395,6 +395,7 @@ public function testCreateNotAssignableTagAsRegularUser($userVisible, $userAssig if (!empty($groups)) { $requestData['groups'] = $groups; } + $requestData = json_encode($requestData); $node = $this->getMockBuilder(SystemTagsByIdCollection::class) @@ -517,6 +518,7 @@ public function testCreateTagInByIdCollection($userVisible, $userAssignable, $gr if (!empty($groups)) { $requestData['groups'] = $groups; } + $requestData = json_encode($requestData); $node = $this->getMockBuilder(SystemTagsByIdCollection::class) diff --git a/apps/dav/tests/unit/SystemTag/SystemTagsByIdCollectionTest.php b/apps/dav/tests/unit/SystemTag/SystemTagsByIdCollectionTest.php index db55d82adc98a..d2fde989e021f 100644 --- a/apps/dav/tests/unit/SystemTag/SystemTagsByIdCollectionTest.php +++ b/apps/dav/tests/unit/SystemTag/SystemTagsByIdCollectionTest.php @@ -50,6 +50,7 @@ public function getNode($isAdmin = true) { ->method('isAdmin') ->with('testuser') ->willReturn($isAdmin); + return new \OCA\DAV\SystemTag\SystemTagsByIdCollection( $this->tagManager, $userSession, diff --git a/apps/dav/tests/unit/SystemTag/SystemTagsObjectTypeCollectionTest.php b/apps/dav/tests/unit/SystemTag/SystemTagsObjectTypeCollectionTest.php index b202f340e328f..061e9b28234b1 100644 --- a/apps/dav/tests/unit/SystemTag/SystemTagsObjectTypeCollectionTest.php +++ b/apps/dav/tests/unit/SystemTag/SystemTagsObjectTypeCollectionTest.php @@ -76,6 +76,7 @@ protected function setUp(): void { return true; } } + return false; }; diff --git a/apps/dav/tests/unit/Upload/AssemblyStreamTest.php b/apps/dav/tests/unit/Upload/AssemblyStreamTest.php index a8517bf757c78..f23f29e98763d 100644 --- a/apps/dav/tests/unit/Upload/AssemblyStreamTest.php +++ b/apps/dav/tests/unit/Upload/AssemblyStreamTest.php @@ -117,6 +117,7 @@ private function makeData($count) { $j = 0; } } + return $data; } diff --git a/apps/dav/tests/unit/bootstrap.php b/apps/dav/tests/unit/bootstrap.php index 61dbda35ae252..886a7f0b859f2 100644 --- a/apps/dav/tests/unit/bootstrap.php +++ b/apps/dav/tests/unit/bootstrap.php @@ -9,7 +9,7 @@ define('PHPUNIT_RUN', 1); } -require_once __DIR__.'/../../../../lib/base.php'; +require_once __DIR__ . '/../../../../lib/base.php'; \OC::$composerAutoloader->addPsr4('Test\\', OC::$SERVERROOT . '/tests/lib/', true); diff --git a/apps/encryption/lib/Command/DisableMasterKey.php b/apps/encryption/lib/Command/DisableMasterKey.php index 1912d09728d01..f613f14b71973 100644 --- a/apps/encryption/lib/Command/DisableMasterKey.php +++ b/apps/encryption/lib/Command/DisableMasterKey.php @@ -46,10 +46,12 @@ protected function execute(InputInterface $input, OutputInterface $output): int if ($this->questionHelper->ask($input, $output, $question)) { $this->config->setAppValue('encryption', 'useMasterKey', '0'); $output->writeln('Master key successfully disabled.'); + return self::SUCCESS; } $output->writeln('aborted.'); + return self::FAILURE; } } diff --git a/apps/encryption/lib/Command/DropLegacyFileKey.php b/apps/encryption/lib/Command/DropLegacyFileKey.php index 03c24fe8a22af..3a6299712e1da 100644 --- a/apps/encryption/lib/Command/DropLegacyFileKey.php +++ b/apps/encryption/lib/Command/DropLegacyFileKey.php @@ -52,6 +52,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $this->setupUserFS($user); $result = $result && $this->scanFolder($output, '/' . $user); } + $offset += $limit; } while (count($users) >= $limit); } @@ -94,6 +95,7 @@ private function scanFolder(OutputInterface $output, string $folder): bool { $output->writeln('Got a decryption error for legacy filekey for ' . $path . ', continuing', OutputInterface::VERBOSITY_VERBOSE); continue; } + /* If that did not throw and filekey is not empty, a legacy filekey is used */ $clean = false; $output->writeln($path . ' is using a legacy filekey, migrating'); @@ -114,13 +116,16 @@ private function migrateSinglefile(string $path, FileInfo $fileInfo, OutputInter $copyResource = $this->rootView->fopen($target, 'r'); $sourceResource = $this->rootView->fopen($source, 'w'); if ($copyResource === false || $sourceResource === false) { - throw new DecryptionFailedException('Failed to open '.$source.' or '.$target); + throw new DecryptionFailedException('Failed to open ' . $source . ' or ' . $target); } + if (stream_copy_to_stream($copyResource, $sourceResource) === false) { - $output->writeln('Failed to copy '.$target.' data into '.$source.''); + $output->writeln('Failed to copy ' . $target . ' data into ' . $source . ''); $output->writeln('Leaving both files in there to avoid data loss'); + return; } + $this->rootView->touch($source, $fileInfo->getMTime()); $this->rootView->unlink($target); $output->writeln('Migrated ' . $source . '', OutputInterface::VERBOSITY_VERBOSE); @@ -128,12 +133,14 @@ private function migrateSinglefile(string $path, FileInfo $fileInfo, OutputInter if ($this->rootView->file_exists($target)) { $this->rootView->unlink($target); } + $output->writeln('Failed to migrate ' . $path . ''); $output->writeln('' . $e . '', OutputInterface::VERBOSITY_VERBOSE); } finally { if (is_resource($copyResource)) { fclose($copyResource); } + if (is_resource($sourceResource)) { fclose($sourceResource); } diff --git a/apps/encryption/lib/Command/EnableMasterKey.php b/apps/encryption/lib/Command/EnableMasterKey.php index 0d8b893e0e288..a539387aaf569 100644 --- a/apps/encryption/lib/Command/EnableMasterKey.php +++ b/apps/encryption/lib/Command/EnableMasterKey.php @@ -45,10 +45,12 @@ protected function execute(InputInterface $input, OutputInterface $output): int if ($this->questionHelper->ask($input, $output, $question)) { $this->config->setAppValue('encryption', 'useMasterKey', '1'); $output->writeln('Master key successfully enabled.'); + return self::SUCCESS; } $output->writeln('aborted.'); + return self::FAILURE; } } diff --git a/apps/encryption/lib/Command/FixEncryptedVersion.php b/apps/encryption/lib/Command/FixEncryptedVersion.php index 6635bb6cba95e..9523993fec08e 100644 --- a/apps/encryption/lib/Command/FixEncryptedVersion.php +++ b/apps/encryption/lib/Command/FixEncryptedVersion.php @@ -102,8 +102,10 @@ protected function execute(InputInterface $input, OutputInterface $output): int $this->userManager->callForSeenUsers(function (IUser $user) use ($pathOption, $output, &$result) { $output->writeln('Processing files for ' . $user->getUID()); $result = $this->runForUser($user->getUID(), $pathOption, $output); + return $result === 0; }); + return $result; } @@ -112,6 +114,7 @@ private function runForUser(string $user, string $pathOption, OutputInterface $o if ($pathOption !== '') { $pathToWalk = "$pathToWalk/$pathOption"; } + return $this->walkPathOfUser($user, $pathToWalk, $output); } @@ -128,8 +131,10 @@ private function walkPathOfUser(string $user, string $path, OutputInterface $out if ($this->view->is_file($path)) { $output->writeln("Verifying the content of file \"$path\""); $this->verifyFileContent($path, $output); + return self::SUCCESS; } + $directories = []; $directories[] = $path; while ($root = \array_pop($directories)) { @@ -144,6 +149,7 @@ private function walkPathOfUser(string $user, string $path, OutputInterface $out } } } + return self::SUCCESS; } @@ -179,6 +185,7 @@ private function verifyFileContent(string $path, OutputInterface $output, bool $ $output->writeln("File info not found for file: \"$path\""); return true; } + $encryptedVersion = $fileInfo->getEncryptedVersion(); $stat = $this->view->stat($path); if (($encryptedVersion == 0) && isset($stat['hasHeader']) && ($stat['hasHeader'] == true)) { @@ -186,10 +193,13 @@ private function verifyFileContent(string $path, OutputInterface $output, bool $ if ($ignoreCorrectEncVersionCall === true) { // Lets rectify the file by correcting encrypted version $output->writeln("Attempting to fix the path: \"$path\""); + return $this->correctEncryptedVersion($path, $output); } + return false; } + $output->writeln("The file \"$path\" is: OK"); } @@ -203,6 +213,7 @@ private function verifyFileContent(string $path, OutputInterface $output, bool $ $output->writeln("Attempting to fix the path: \"$path\""); return $this->correctEncryptedVersion($path, $output, true); } + return false; } catch (HintException $e) { $this->logger->warning('Issue: ' . $e->getMessage()); @@ -210,8 +221,10 @@ private function verifyFileContent(string $path, OutputInterface $output, bool $ if ($ignoreCorrectEncVersionCall === true) { // Lets rectify the file by correcting encrypted version $output->writeln("Attempting to fix the path: \"$path\""); + return $this->correctEncryptedVersion($path, $output); } + return false; } } @@ -225,11 +238,13 @@ private function correctEncryptedVersion(string $path, OutputInterface $output, $output->writeln("File info not found for file: \"$path\""); return true; } + $fileId = $fileInfo->getId(); if ($fileId === null) { $output->writeln("File info contains no id for file: \"$path\""); return true; } + $encryptedVersion = $fileInfo->getEncryptedVersion(); $wrongEncryptedVersion = $encryptedVersion; @@ -271,6 +286,7 @@ private function correctEncryptedVersion(string $path, OutputInterface $output, $output->writeln("Fixed the file: \"$path\" with version " . $encryptedVersion . ''); return true; } + $encryptedVersion--; } @@ -294,6 +310,7 @@ private function correctEncryptedVersion(string $path, OutputInterface $output, $output->writeln("Fixed the file: \"$path\" with version " . $newEncryptedVersion . ''); return true; } + $increment++; } } diff --git a/apps/encryption/lib/Command/FixKeyLocation.php b/apps/encryption/lib/Command/FixKeyLocation.php index da529a4be2f5f..566d3cf364061 100644 --- a/apps/encryption/lib/Command/FixKeyLocation.php +++ b/apps/encryption/lib/Command/FixKeyLocation.php @@ -44,6 +44,7 @@ public function __construct( if (!$encryptionManager instanceof Manager) { throw new \Exception('Wrong encryption manager'); } + $this->encryptionManager = $encryptionManager; parent::__construct(); @@ -111,6 +112,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int } else { $output->write('' . $file->getPath() . ' needs decryption'); } + $foundKey = $this->findUserKeyForSystemFile($user, $file); if ($foundKey) { $output->writeln(', valid key found at ' . $foundKey . ''); @@ -123,6 +125,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int } else { $output->write('Decrypting ' . $file->getPath() . ''); } + $foundKey = $this->findUserKeyForSystemFile($user, $file); if ($foundKey) { if ($shouldBeEncrypted) { @@ -227,10 +230,12 @@ private function copyUserKeyToSystemAndValidate(IUser $user, File $node): bool { if ($this->tryReadFile($node)) { // cleanup wrong key location $this->rootView->rmdir($userKeyPath); + return true; } else { // remove the copied key if we know it's invalid $this->rootView->rmdir($systemKeyPath); + return false; } } @@ -271,10 +276,12 @@ private function openWithoutDecryption(File $node, string $mode) { } else { $handle = $storage->fopen($internalPath, $mode); } + /** @var resource|false $handle */ if ($handle === false) { throw new \Exception('Failed to open ' . $node->getPath()); } + return $handle; } @@ -287,6 +294,7 @@ private function isDataEncrypted(File $node): bool { fclose($handle); $header = $this->encryptionUtil->parseRawHeader($firstBlock); + return isset($header['oc_encryption_module']); } @@ -301,6 +309,7 @@ private function findUserKeyForSystemFile(IUser $user, File $node): ?string { return $possibleKey; } } + return null; } @@ -318,6 +327,7 @@ private function findKeysByFileName(string $basePath, string $name) { if (!$dh) { throw new \Exception('Invalid base path ' . $basePath); } + while ($child = readdir($dh)) { if ($child != '..' && $child != '.') { $childPath = $basePath . '/' . $child; @@ -345,6 +355,7 @@ private function testSystemKey(IUser $user, string $key, File $node): bool { $this->rootView->copy($key, $systemKeyPath); $isValid = $this->tryReadFile($node); $this->rootView->rmdir($systemKeyPath); + return $isValid; } @@ -363,11 +374,13 @@ private function decryptWithSystemKey(File $node, string $key): void { if (!$storage->instanceOfStorage(Encryption::class)) { $storage = $this->encryptionManager->forceWrapStorage($node->getMountPoint(), $storage); } + /** @var false|resource $source */ $source = $storage->fopen($node->getInternalPath(), 'r'); if (!$source) { throw new \Exception('Failed to open ' . $node->getPath() . ' with ' . $key); } + $decryptedNode = $node->getParent()->newFile($name); $target = $this->openWithoutDecryption($decryptedNode, 'w'); diff --git a/apps/encryption/lib/Command/RecoverUser.php b/apps/encryption/lib/Command/RecoverUser.php index aea90f158f679..85ccf931e5bfe 100644 --- a/apps/encryption/lib/Command/RecoverUser.php +++ b/apps/encryption/lib/Command/RecoverUser.php @@ -71,6 +71,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $output->write('Start to recover users files... This can take some time...'); $this->userManager->get($uid)->setPassword($newLoginPassword, $recoveryPassword); $output->writeln('Done.'); + return self::SUCCESS; } } diff --git a/apps/encryption/lib/Command/ScanLegacyFormat.php b/apps/encryption/lib/Command/ScanLegacyFormat.php index 1e46a3d7545a2..a50f820a52145 100644 --- a/apps/encryption/lib/Command/ScanLegacyFormat.php +++ b/apps/encryption/lib/Command/ScanLegacyFormat.php @@ -52,6 +52,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $this->setupUserFS($user); $result = $result && $this->scanFolder($output, '/' . $user); } + $offset += $limit; } while (count($users) >= $limit); } diff --git a/apps/encryption/lib/Controller/RecoveryController.php b/apps/encryption/lib/Controller/RecoveryController.php index 66011fe24391f..ffd0a0effaa5a 100644 --- a/apps/encryption/lib/Controller/RecoveryController.php +++ b/apps/encryption/lib/Controller/RecoveryController.php @@ -78,13 +78,16 @@ public function adminRecovery($recoveryPassword, $confirmPassword, $adminEnableR if ($this->recovery->enableAdminRecovery($recoveryPassword)) { return new DataResponse(['data' => ['message' => $this->l->t('Recovery key successfully enabled')]]); } + return new DataResponse(['data' => ['message' => $this->l->t('Could not enable recovery key. Please check your recovery key password!')]], Http::STATUS_BAD_REQUEST); } elseif (isset($adminEnableRecovery) && $adminEnableRecovery === '0') { if ($this->recovery->disableAdminRecovery($recoveryPassword)) { return new DataResponse(['data' => ['message' => $this->l->t('Recovery key successfully disabled')]]); } + return new DataResponse(['data' => ['message' => $this->l->t('Could not disable recovery key. Please check your recovery key password!')]], Http::STATUS_BAD_REQUEST); } + // this response should never be sent but just in case. return new DataResponse(['data' => ['message' => $this->l->t('Missing parameters')]], Http::STATUS_BAD_REQUEST); } @@ -128,6 +131,7 @@ public function changeRecoveryPassword($newPassword, $oldPassword, $confirmPassw ] ); } + return new DataResponse( [ 'data' => [ @@ -154,6 +158,7 @@ public function userSetRecovery($userEnableRecovery) { ] ); } + return new DataResponse( [ 'data' => [ @@ -162,6 +167,7 @@ public function userSetRecovery($userEnableRecovery) { ); } } + return new DataResponse( [ 'data' => [ diff --git a/apps/encryption/lib/Controller/SettingsController.php b/apps/encryption/lib/Controller/SettingsController.php index 251f385127a3d..df896e52fa54d 100644 --- a/apps/encryption/lib/Controller/SettingsController.php +++ b/apps/encryption/lib/Controller/SettingsController.php @@ -69,7 +69,7 @@ public function __construct($AppName, Crypt $crypt, Session $session, ISession $ocSession, - Util $util + Util $util, ) { parent::__construct($AppName, $request); $this->l = $l10n; diff --git a/apps/encryption/lib/Controller/StatusController.php b/apps/encryption/lib/Controller/StatusController.php index b5b54ee2f6d71..1a2483a2bef9d 100644 --- a/apps/encryption/lib/Controller/StatusController.php +++ b/apps/encryption/lib/Controller/StatusController.php @@ -37,7 +37,7 @@ public function __construct($AppName, IRequest $request, IL10N $l10n, Session $session, - IManager $encryptionManager + IManager $encryptionManager, ) { parent::__construct($AppName, $request); $this->l = $l10n; @@ -70,6 +70,7 @@ public function getStatus() { 'Please enable server side encryption in the admin settings in order to use the encryption module.' ); } + break; case Session::INIT_SUCCESSFUL: $status = 'success'; diff --git a/apps/encryption/lib/Crypto/Crypt.php b/apps/encryption/lib/Crypto/Crypt.php index 92d6ed6a44393..150d0b316990a 100644 --- a/apps/encryption/lib/Crypto/Crypt.php +++ b/apps/encryption/lib/Crypto/Crypt.php @@ -108,6 +108,7 @@ public function createKeyPair() { 'privateKey' => $privateKey ]; } + $this->logger->error('Encryption library couldn\'t export users private key, please check your servers OpenSSL configuration.' . $this->user, ['app' => 'encryption']); if (openssl_error_string()) { @@ -134,6 +135,7 @@ private function getOpenSSLConfig(): array { $config, $this->config->getSystemValue('openssl', []) ); + return $config; } @@ -144,6 +146,7 @@ public function symmetricEncryptFileContent(string $plainContent, string $passPh if (!$plainContent) { $this->logger->error('Encryption Library, symmetrical encryption failed no content given', ['app' => 'encryption']); + return false; } @@ -155,11 +158,12 @@ public function symmetricEncryptFileContent(string $plainContent, string $passPh $this->getCipher()); // Create a signature based on the key as well as the current version - $sig = $this->createSignature($encryptedContent, $passPhrase.'_'.$version.'_'.$position); + $sig = $this->createSignature($encryptedContent, $passPhrase . '_' . $version . '_' . $position); // combine content to encrypt the IV identifier and actual IV $catFile = $this->concatIV($encryptedContent, $iv); $catFile = $this->concatSig($catFile, $sig); + return $this->addPadding($catFile); } @@ -234,6 +238,7 @@ private function getCachedCipher(): string { // Remember current cipher to avoid frequent lookups $this->currentCipher = $cipher; + return $this->currentCipher; } @@ -480,6 +485,7 @@ private function removePadding(string $padded, bool $hasSignature = false): stri } elseif ($hasSignature === true && substr($padded, -3) === 'xxx') { return substr($padded, 0, -3); } + return false; } @@ -670,6 +676,7 @@ public function multiKeyEncrypt(string $plainContent, array $keyFiles): array { return $mappedShareKeys; } } + throw new MultiKeyEncryptException('multikeyencryption failed ' . openssl_error_string()); } @@ -760,7 +767,7 @@ private function opensslOpen(string $data, string &$output, string $encrypted_ke $result = (strlen($output) === strlen($data)); } } else { - throw new DecryptionFailedException('Unsupported cipher '.$cipher_algo); + throw new DecryptionFailedException('Unsupported cipher ' . $cipher_algo); } return $result; @@ -807,7 +814,7 @@ private function opensslSeal(string $data, string &$sealed_data, array &$encrypt } } } else { - throw new EncryptionFailedException('Unsupported cipher '.$cipher_algo); + throw new EncryptionFailedException('Unsupported cipher ' . $cipher_algo); } return $result; diff --git a/apps/encryption/lib/Crypto/DecryptAll.php b/apps/encryption/lib/Crypto/DecryptAll.php index 80c187571b7db..129fe21b0b3ba 100644 --- a/apps/encryption/lib/Crypto/DecryptAll.php +++ b/apps/encryption/lib/Crypto/DecryptAll.php @@ -45,7 +45,7 @@ public function __construct( KeyManager $keyManager, Crypt $crypt, Session $session, - QuestionHelper $questionHelper + QuestionHelper $questionHelper, ) { $this->util = $util; $this->keyManager = $keyManager; @@ -108,7 +108,6 @@ public function prepare(InputInterface $input, OutputInterface $output, $user) { $output->writeln('Could not decrypt private key, maybe you entered the wrong password?'); } - return false; } diff --git a/apps/encryption/lib/Crypto/EncryptAll.php b/apps/encryption/lib/Crypto/EncryptAll.php index 310f37aba838a..abd9323c83e78 100644 --- a/apps/encryption/lib/Crypto/EncryptAll.php +++ b/apps/encryption/lib/Crypto/EncryptAll.php @@ -82,7 +82,7 @@ public function __construct( IL10N $l, IFactory $l10nFactory, QuestionHelper $questionHelper, - ISecureRandom $secureRandom + ISecureRandom $secureRandom, ) { $this->userSetup = $userSetup; $this->userManager = $userManager; @@ -129,7 +129,6 @@ public function encryptAll(InputInterface $input, OutputInterface $output) { $this->createKeyPairs(); } - // output generated encryption key passwords if ($this->util->isMasterKeyEnabled() === false) { //send-out or display password list and write it to a file @@ -176,6 +175,7 @@ protected function createKeyPairs() { $this->userPasswords[$user] = ''; } } + $offset += $limit; } while (count($users) >= $limit); } @@ -203,6 +203,7 @@ protected function encryptAllUsersFiles() { $userNo++; } } + $progress->setMessage('all files encrypted'); $progress->finish(); } @@ -224,6 +225,7 @@ protected function encryptAllUserFilesWithMasterKey(ProgressBar $progress) { $this->encryptUsersFiles($user, $progress, $userCount); $userNo++; } + $offset += $limit; } while (count($users) >= $limit); } @@ -284,6 +286,7 @@ protected function encryptFile($path) { if ($this->rootView->file_exists($target)) { $this->rootView->unlink($target); } + return false; } @@ -342,6 +345,7 @@ protected function writePasswordsToFile(array $passwords) { foreach ($passwords as $pwd) { fputcsv($fp, $pwd); } + fclose($fp); $this->output->writeln("\n"); $this->output->writeln('A list of all newly created passwords was written to data/oneTimeEncryptionPasswords.csv'); @@ -372,6 +376,7 @@ protected function setupUserFS($uid) { protected function generateOneTimePassword($uid) { $password = $this->secureRandom->generate(16, ISecureRandom::CHAR_HUMAN_READABLE); $this->userPasswords[$uid] = $password; + return $password; } @@ -447,6 +452,7 @@ protected function sendPasswordsByMail() { foreach ($noMail as $uid) { $rows[] = [$uid, $this->userPasswords[$uid]]; } + $table->setRows($rows); $table->render(); } diff --git a/apps/encryption/lib/Crypto/Encryption.php b/apps/encryption/lib/Crypto/Encryption.php index f5b6a40aecc18..1ae829b94d5b9 100644 --- a/apps/encryption/lib/Crypto/Encryption.php +++ b/apps/encryption/lib/Crypto/Encryption.php @@ -199,10 +199,12 @@ public function end($path, $position = '0') { if (Scanner::isPartialFile($path)) { self::$rememberVersion[$this->stripPartFileExtension($path)] = $this->version + 1; } + if (!empty($this->writeCache)) { $result = $this->crypt->symmetricEncryptFileContent($this->writeCache, $this->fileKey, $this->version + 1, $position); $this->writeCache = ''; } + $publicKeys = []; if ($this->useMasterPassword === true) { $publicKeys[$this->keyManager->getMasterKeyId()] = $this->keyManager->getPublicMasterKey(); @@ -231,10 +233,12 @@ public function end($path, $position = '0') { ['app' => 'encryption', 'path' => $path] ); } + foreach ($shareKeys as $uid => $keyFile) { $this->keyManager->setShareKey($this->path, $uid, $keyFile); } } + return $result ?: ''; } @@ -331,6 +335,7 @@ public function update($path, $uid, array $accessList) { $this->keyManager->setVersion($path, self::$rememberVersion[$path], new View()); unset(self::$rememberVersion[$path]); } + return false; } @@ -382,6 +387,7 @@ public function shouldEncrypt($path) { return false; } } + $parts = explode('/', $path); if (count($parts) < 4) { return false; @@ -390,9 +396,11 @@ public function shouldEncrypt($path) { if ($parts[2] === 'files') { return true; } + if ($parts[2] === 'files_versions') { return true; } + if ($parts[2] === 'files_trashbin') { return true; } @@ -451,6 +459,7 @@ public function isReadable($path, $uid) { $this->logger->warning($msg); throw new DecryptionFailedException($msg, $hint); } + return false; } @@ -522,6 +531,7 @@ protected function getOwner($path) { if (!isset($this->owner[$path])) { $this->owner[$path] = $this->util->getOwner($path); } + return $this->owner[$path]; } @@ -539,6 +549,7 @@ public function isReadyForUser($user) { if ($this->util->isMasterKeyEnabled()) { return true; } + return $this->keyManager->userHasKeys($user); } diff --git a/apps/encryption/lib/Exceptions/PrivateKeyMissingException.php b/apps/encryption/lib/Exceptions/PrivateKeyMissingException.php index 15fe8f4e72f68..34995eef20bd0 100644 --- a/apps/encryption/lib/Exceptions/PrivateKeyMissingException.php +++ b/apps/encryption/lib/Exceptions/PrivateKeyMissingException.php @@ -18,6 +18,7 @@ public function __construct($userId) { if (empty($userId)) { $userId = ''; } + parent::__construct("Private Key missing for user: $userId"); } } diff --git a/apps/encryption/lib/Exceptions/PublicKeyMissingException.php b/apps/encryption/lib/Exceptions/PublicKeyMissingException.php index 78eeeccf47d1e..6f6a132876371 100644 --- a/apps/encryption/lib/Exceptions/PublicKeyMissingException.php +++ b/apps/encryption/lib/Exceptions/PublicKeyMissingException.php @@ -18,6 +18,7 @@ public function __construct($userId) { if (empty($userId)) { $userId = ''; } + parent::__construct("Public Key missing for user: $userId"); } } diff --git a/apps/encryption/lib/HookManager.php b/apps/encryption/lib/HookManager.php index 6ad56ebad782c..dedf71983c662 100644 --- a/apps/encryption/lib/HookManager.php +++ b/apps/encryption/lib/HookManager.php @@ -24,11 +24,13 @@ public function registerHook($instances) { if (!$instance instanceof IHook) { return false; } + $this->hookInstances[] = $instance; } } elseif ($instances instanceof IHook) { $this->hookInstances[] = $instances; } + return true; } diff --git a/apps/encryption/lib/Hooks/UserHooks.php b/apps/encryption/lib/Hooks/UserHooks.php index 2424370e47f56..114817740a507 100644 --- a/apps/encryption/lib/Hooks/UserHooks.php +++ b/apps/encryption/lib/Hooks/UserHooks.php @@ -97,6 +97,7 @@ public function login($params) { if (!\OC\Files\Filesystem::$loaded) { $this->setupFS($params['uid']); } + if ($this->util->isMasterKeyEnabled() === false) { $this->userSetup->setupUser($params['uid'], $params['password']); } @@ -205,6 +206,7 @@ public function setPassphrase($params) { } catch (\Exception $e) { $decryptedRecoveryKey = false; } + if ($decryptedRecoveryKey === false) { $message = 'Can not decrypt the recovery key. Maybe you provided the wrong password. Try again.'; throw new GenericEncryptionException($message, $message); diff --git a/apps/encryption/lib/KeyManager.php b/apps/encryption/lib/KeyManager.php index 9fd6c7655afd5..b248c18bddf83 100644 --- a/apps/encryption/lib/KeyManager.php +++ b/apps/encryption/lib/KeyManager.php @@ -88,6 +88,7 @@ public function validateShareKey() { $this->lockingProvider->releaseLock('encryption-generateSharedKey', ILockingProvider::LOCK_EXCLUSIVE); throw $e; } + $this->lockingProvider->releaseLock('encryption-generateSharedKey', ILockingProvider::LOCK_EXCLUSIVE); } } @@ -124,6 +125,7 @@ public function validateMasterKey() { $this->lockingProvider->releaseLock('encryption-generateMasterKey', ILockingProvider::LOCK_EXCLUSIVE); throw $e; } + $this->lockingProvider->releaseLock('encryption-generateMasterKey', ILockingProvider::LOCK_EXCLUSIVE); } elseif (empty($publicMasterKey)) { $this->logger->error('A private master key is available but the public key could not be found. This should never happen.'); @@ -180,6 +182,7 @@ public function checkRecoveryPassword($password) { if ($decryptedRecoveryKey) { return true; } + return false; } @@ -201,6 +204,7 @@ public function storeKeyPair($uid, $password, $keyPair) { $this->setPrivateKey($uid, $header . $encryptedKey); return true; } + return false; } @@ -211,7 +215,7 @@ public function storeKeyPair($uid, $password, $keyPair) { */ public function setRecoveryKey($password, $keyPair) { // Save Public Key - $this->keyStorage->setSystemUserKey($this->getRecoveryKeyId(). + $this->keyStorage->setSystemUserKey($this->getRecoveryKeyId() . '.' . $this->publicKeyId, $keyPair['publicKey'], Encryption::ID); @@ -223,6 +227,7 @@ public function setRecoveryKey($password, $keyPair) { $this->setSystemPrivateKey($this->getRecoveryKeyId(), $header . $encryptedKey); return true; } + return false; } @@ -302,6 +307,7 @@ public function init($uid, $passPhrase) { } else { $privateKey = $this->getPrivateKey($uid); } + $privateKey = $this->crypt->decryptPrivateKey($privateKey, $passPhrase, $uid); } catch (PrivateKeyMissingException $e) { return false; @@ -315,12 +321,14 @@ public function init($uid, $passPhrase) { 'exception' => $e, ] ); + return false; } if ($privateKey) { $this->session->setPrivateKey($privateKey); $this->session->setStatus(Session::INIT_SUCCESSFUL); + return true; } @@ -339,6 +347,7 @@ public function getPrivateKey($userId) { if (strlen($privateKey) !== 0) { return $privateKey; } + throw new PrivateKeyMissingException($userId); } @@ -349,6 +358,7 @@ public function getFileKey(string $path, ?string $uid, ?bool $useLegacyFileKey, if ($uid === '') { $uid = null; } + $publicAccess = is_null($uid); $encryptedFileKey = ''; if ($useLegacyFileKey ?? true) { @@ -358,6 +368,7 @@ public function getFileKey(string $path, ?string $uid, ?bool $useLegacyFileKey, return ''; } } + if ($useDecryptAll) { $shareKey = $this->getShareKey($path, $this->session->getDecryptAllUid()); $privateKey = $this->session->getDecryptAllKey(); @@ -389,6 +400,7 @@ public function getFileKey(string $path, ?string $uid, ?bool $useLegacyFileKey, $privateKey); } } + if (!($useLegacyFileKey ?? false)) { if ($shareKey && $privateKey) { return $this->crypt->multiKeyDecrypt($shareKey, $privateKey); @@ -410,6 +422,7 @@ public function getVersion($path, View $view) { if ($fileInfo === false) { return 0; } + return $fileInfo->getEncryptedVersion(); } @@ -485,6 +498,7 @@ public function userHasKeys($userId) { $privateKey = false; $exception = $e; } + try { $this->getPublicKey($userId); } catch (PublicKeyMissingException $e) { @@ -512,6 +526,7 @@ public function getPublicKey($userId) { if (strlen($publicKey) !== 0) { return $publicKey; } + throw new PublicKeyMissingException($userId); } @@ -628,6 +643,7 @@ public function addSystemKeys(array $accessList, array $publicKeys, $uid) { if (empty($publicShareKey)) { throw new PublicKeyMissingException($this->getPublicShareKeyId()); } + $publicKeys[$this->getPublicShareKeyId()] = $publicShareKey; } diff --git a/apps/encryption/lib/Recovery.php b/apps/encryption/lib/Recovery.php index 3990067f19b23..b896fe0c0549c 100644 --- a/apps/encryption/lib/Recovery.php +++ b/apps/encryption/lib/Recovery.php @@ -101,12 +101,14 @@ public function changeRecoveryKeyPassword($newPassword, $oldPassword) { if ($decryptedRecoveryKey === false) { return false; } + $encryptedRecoveryKey = $this->crypt->encryptPrivateKey($decryptedRecoveryKey, $newPassword); $header = $this->crypt->generateHeader(); if ($encryptedRecoveryKey) { $this->keyManager->setSystemPrivateKey($this->keyManager->getRecoveryKeyId(), $header . $encryptedRecoveryKey); return true; } + return false; } @@ -120,8 +122,10 @@ public function disableAdminRecovery($recoveryPassword) { if ($keyManager->checkRecoveryPassword($recoveryPassword)) { // Set recoveryAdmin as disabled $this->config->setAppValue('encryption', 'recoveryAdminEnabled', '0'); + return true; } + return false; } diff --git a/apps/encryption/lib/Session.php b/apps/encryption/lib/Session.php index e16396e8fafab..59e60b820995f 100644 --- a/apps/encryption/lib/Session.php +++ b/apps/encryption/lib/Session.php @@ -70,6 +70,7 @@ public function getPrivateKey() { if (is_null($key)) { throw new Exceptions\PrivateKeyMissingException('please try to log-out and log-in again', 0); } + return $key; } diff --git a/apps/encryption/lib/Settings/Admin.php b/apps/encryption/lib/Settings/Admin.php index 4f695f61b1b0f..e8290b778ada2 100644 --- a/apps/encryption/lib/Settings/Admin.php +++ b/apps/encryption/lib/Settings/Admin.php @@ -25,7 +25,7 @@ public function __construct( private IUserSession $userSession, private IConfig $config, private IUserManager $userManager, - private ISession $session + private ISession $session, ) { } diff --git a/apps/encryption/lib/Settings/Personal.php b/apps/encryption/lib/Settings/Personal.php index b544ea1dc3e65..5f1f298d9ea9f 100644 --- a/apps/encryption/lib/Settings/Personal.php +++ b/apps/encryption/lib/Settings/Personal.php @@ -51,6 +51,7 @@ public function getForm() { 'privateKeySet' => $privateKeySet, 'initialized' => $this->session->getStatus(), ]; + return new TemplateResponse('encryption', 'settings-personal', $parameters, ''); } diff --git a/apps/encryption/lib/Users/Setup.php b/apps/encryption/lib/Users/Setup.php index 30e7c5461ccb6..68048ff912264 100644 --- a/apps/encryption/lib/Users/Setup.php +++ b/apps/encryption/lib/Users/Setup.php @@ -32,6 +32,7 @@ public function setupUser($uid, $password) { $keyPair = $this->crypt->createKeyPair(); return is_array($keyPair) ? $this->keyManager->storeKeyPair($uid, $password, $keyPair) : false; } + return true; } diff --git a/apps/encryption/lib/Util.php b/apps/encryption/lib/Util.php index 6ca4d2c1e1e8d..2664c33d1b3d9 100644 --- a/apps/encryption/lib/Util.php +++ b/apps/encryption/lib/Util.php @@ -96,6 +96,7 @@ public function setRecoveryForUser($enabled) { 'encryption', 'recoveryEnabled', $value); + return true; } catch (PreConditionNotMetException $e) { return false; diff --git a/apps/encryption/templates/settings-admin.php b/apps/encryption/templates/settings-admin.php index 6d06f28ffe7a4..b2dc63b82571d 100644 --- a/apps/encryption/templates/settings-admin.php +++ b/apps/encryption/templates/settings-admin.php @@ -13,20 +13,26 @@

t('Default encryption module')); ?>

- t('Encryption app is enabled but your keys are not initialized, please log-out and log-in again')); ?> + t('Encryption app is enabled but your keys are not initialized, please log-out and log-in again')); + + ?>

/> + } + + ?> />
t('Enabling this option encrypts all files stored on the main storage, otherwise only files on external storage will be encrypted')); ?>


- t('Enable recovery key')) : p($l->t('Disable recovery key')); ?> + t('Enable recovery key')) : p($l->t('Disable recovery key')); + + ?>
@@ -51,7 +57,9 @@

> + } + +?>> t('Change recovery key password:')); ?>
diff --git a/apps/encryption/templates/settings-personal.php b/apps/encryption/templates/settings-personal.php index 5aa227e7354f5..ff3e3c41cfe02 100644 --- a/apps/encryption/templates/settings-personal.php +++ b/apps/encryption/templates/settings-personal.php @@ -14,7 +14,9 @@ - t('Encryption App is enabled, but your keys are not initialized. Please log-out and log-in again.')); ?> + t('Encryption App is enabled, but your keys are not initialized. Please log-out and log-in again.')); + + ?>

@@ -26,6 +28,7 @@ t('Set your old private key password to your current log-in password:')); ?> t(" If you don't remember your old password you can ask your administrator to recover your files.")); + endif; ?>
1, 'encrypted' => 1]; $cache1->put($fileCache1->getPath(), $cacheInfo); - $absPath = $storage1->getSourcePath('').$fileInfo1->getInternalPath(); + $absPath = $storage1->getSourcePath('') . $fileInfo1->getInternalPath(); // create unencrypted file on disk, the version stays file_put_contents($absPath, 'hello contents'); diff --git a/apps/encryption/tests/Crypto/EncryptAllTest.php b/apps/encryption/tests/Crypto/EncryptAllTest.php index f58ea2119e68c..1febfb4dba2a0 100644 --- a/apps/encryption/tests/Crypto/EncryptAllTest.php +++ b/apps/encryption/tests/Crypto/EncryptAllTest.php @@ -225,6 +225,7 @@ function ($user) { if ($user === 'user1') { return false; } + return true; } ); @@ -323,6 +324,7 @@ function ($path) { if ($path === '/user1/files/foo') { return true; } + return false; } ); diff --git a/apps/encryption/tests/Crypto/EncryptionTest.php b/apps/encryption/tests/Crypto/EncryptionTest.php index 10f85f7e74ed0..04fc33723e9b8 100644 --- a/apps/encryption/tests/Crypto/EncryptionTest.php +++ b/apps/encryption/tests/Crypto/EncryptionTest.php @@ -154,6 +154,7 @@ public function getPublicKeyCallback($uid) { if ($uid === 'user2') { throw new PublicKeyMissingException($uid); } + return $uid; } @@ -161,6 +162,7 @@ public function addSystemKeysCallback($accessList, $publicKeys) { $this->assertSame(2, count($publicKeys)); $this->assertArrayHasKey('user1', $publicKeys); $this->assertArrayHasKey('user3', $publicKeys); + return $publicKeys; } @@ -351,6 +353,7 @@ function ($user) { function ($fileKey, $publicKeys) { $this->assertEmpty($publicKeys); $this->assertSame('fileKey', $fileKey); + return []; } ); diff --git a/apps/encryption/tests/KeyManagerTest.php b/apps/encryption/tests/KeyManagerTest.php index 869e5e2cf96b5..b0a42da1aaf1d 100644 --- a/apps/encryption/tests/KeyManagerTest.php +++ b/apps/encryption/tests/KeyManagerTest.php @@ -215,6 +215,7 @@ public function testUserHasKeysMissingPrivateKey(): void { if ($keyID === 'privateKey') { return ''; } + return 'key'; }); @@ -231,6 +232,7 @@ public function testUserHasKeysMissingPublicKey(): void { if ($keyID === 'publicKey') { return ''; } + return 'key'; }); @@ -491,6 +493,7 @@ public function testAddSystemKeys($accessList, $publicKeys, $uid, $expectedKeys) if ($uid === 'user1') { return true; } + return false; }); diff --git a/apps/encryption/tests/RecoveryTest.php b/apps/encryption/tests/RecoveryTest.php index 4b28d40884469..eca139e2dec25 100644 --- a/apps/encryption/tests/RecoveryTest.php +++ b/apps/encryption/tests/RecoveryTest.php @@ -302,6 +302,7 @@ public function getValueTester($app, $key) { if (!empty(self::$tempStorage[$key])) { return self::$tempStorage[$key]; } + return null; } } diff --git a/apps/encryption/tests/SessionTest.php b/apps/encryption/tests/SessionTest.php index 10c699898fce5..868122d057126 100644 --- a/apps/encryption/tests/SessionTest.php +++ b/apps/encryption/tests/SessionTest.php @@ -163,6 +163,7 @@ public function getValueTester($key) { if (!empty(self::$tempStorage[$key])) { return self::$tempStorage[$key]; } + return null; } diff --git a/apps/encryption/tests/Settings/AdminTest.php b/apps/encryption/tests/Settings/AdminTest.php index 4ddaeb1b84dd9..3abd119489434 100644 --- a/apps/encryption/tests/Settings/AdminTest.php +++ b/apps/encryption/tests/Settings/AdminTest.php @@ -58,9 +58,11 @@ public function testGetForm(): void { if ($app === 'encryption' && $key === 'recoveryAdminEnabled' && $default === '0') { return '1'; } + if ($app === 'encryption' && $key === 'encryptHomeStorage' && $default === '1') { return '1'; } + return $default; })); $params = [ diff --git a/apps/encryption/tests/UtilTest.php b/apps/encryption/tests/UtilTest.php index f2e6f406c3507..5a0f55a11bf5c 100644 --- a/apps/encryption/tests/UtilTest.php +++ b/apps/encryption/tests/UtilTest.php @@ -117,6 +117,7 @@ public function getValueTester($userId, $app, $key, $default) { if (!empty(self::$tempStorage[$key])) { return self::$tempStorage[$key]; } + return $default ?: null; } diff --git a/apps/federatedfilesharing/lib/AddressHandler.php b/apps/federatedfilesharing/lib/AddressHandler.php index 320f65c2b4255..d96c956eb1154 100644 --- a/apps/federatedfilesharing/lib/AddressHandler.php +++ b/apps/federatedfilesharing/lib/AddressHandler.php @@ -38,7 +38,7 @@ class AddressHandler { public function __construct( IURLGenerator $urlGenerator, IL10N $il10n, - ICloudIdManager $cloudIdManager + ICloudIdManager $cloudIdManager, ) { $this->l = $il10n; $this->urlGenerator = $urlGenerator; diff --git a/apps/federatedfilesharing/lib/Controller/MountPublicLinkController.php b/apps/federatedfilesharing/lib/Controller/MountPublicLinkController.php index 0cfc3dba262a2..af6452ddb450e 100644 --- a/apps/federatedfilesharing/lib/Controller/MountPublicLinkController.php +++ b/apps/federatedfilesharing/lib/Controller/MountPublicLinkController.php @@ -85,6 +85,7 @@ public function createFederatedShare($shareWith, $token, $password = '') { } catch (HintException $e) { $response = new JSONResponse(['message' => $e->getHint()], Http::STATUS_BAD_REQUEST); $response->throttle(); + return $response; } @@ -98,6 +99,7 @@ public function createFederatedShare($shareWith, $token, $password = '') { Http::STATUS_BAD_REQUEST ); $response->throttle(); + return $response; } @@ -107,6 +109,7 @@ public function createFederatedShare($shareWith, $token, $password = '') { Http::STATUS_BAD_REQUEST ); $response->throttle(); + return $response; } @@ -120,6 +123,7 @@ public function createFederatedShare($shareWith, $token, $password = '') { 'app' => 'federatedfilesharing', 'exception' => $e, ]); + return new JSONResponse(['message' => $e->getMessage()], Http::STATUS_BAD_REQUEST); } @@ -166,6 +170,7 @@ public function askForFederatedShare($token, $remote, $password = '', $owner = ' } else { $message = $this->l->t("Couldn't establish a federated share, maybe the password was wrong."); } + return new JSONResponse(['message' => $message], Http::STATUS_BAD_REQUEST); } @@ -179,6 +184,7 @@ public function askForFederatedShare($token, $remote, $password = '', $owner = ' // if we doesn't get the expected response we assume that we try to add // a federated share from a Nextcloud <= 9 server $message = $this->l->t("Couldn't establish a federated share, it looks like the server to federate with is too old (Nextcloud <= 9)."); + return new JSONResponse(['message' => $message], Http::STATUS_BAD_REQUEST); } } diff --git a/apps/federatedfilesharing/lib/Controller/RequestHandlerController.php b/apps/federatedfilesharing/lib/Controller/RequestHandlerController.php index 71661efa384b1..53b7e20c4ffdd 100644 --- a/apps/federatedfilesharing/lib/Controller/RequestHandlerController.php +++ b/apps/federatedfilesharing/lib/Controller/RequestHandlerController.php @@ -84,7 +84,7 @@ public function __construct(string $appName, LoggerInterface $logger, ICloudFederationFactory $cloudFederationFactory, ICloudFederationProviderManager $cloudFederationProviderManager, - IEventDispatcher $eventDispatcher + IEventDispatcher $eventDispatcher, ) { parent::__construct($appName, $request); @@ -134,6 +134,7 @@ public function createShare( if ($ownerFederatedId === null) { $ownerFederatedId = $this->cloudIdManager->getCloudId($owner, $this->cleanupRemote($remote))->getId(); } + // if the owner of the share and the initiator are the same user // we also complete the federated share ID for the initiator if ($sharedByFederatedId === null && $owner === $sharedBy) { @@ -206,6 +207,7 @@ public function reShare(int $id, ?string $token = null, ?string $shareWith = nul try { $provider = $this->cloudFederationProviderManager->getCloudFederationProvider('file'); [$newToken, $localId] = $provider->notificationReceived('REQUEST_RESHARE', $id, $notification); + return new Http\DataResponse([ 'token' => $newToken, 'remoteId' => $localId @@ -343,6 +345,7 @@ public function revoke(int $id, ?string $token = null) { $provider = $this->cloudFederationProviderManager->getCloudFederationProvider('file'); $notification = ['sharedSecret' => $token]; $provider->notificationReceived('RESHARE_UNDO', $id, $notification); + return new Http\DataResponse(); } catch (\Exception $e) { throw new OCSBadRequestException(); diff --git a/apps/federatedfilesharing/lib/FederatedShareProvider.php b/apps/federatedfilesharing/lib/FederatedShareProvider.php index 025a4c7d737f8..eea6cb98fa3a0 100644 --- a/apps/federatedfilesharing/lib/FederatedShareProvider.php +++ b/apps/federatedfilesharing/lib/FederatedShareProvider.php @@ -107,7 +107,6 @@ public function create(IShare $share) { throw new \Exception($message_t); } - // don't allow federated shares if source and target server are the same $cloudId = $this->cloudIdManager->resolveCloudId($shareWith); $currentServer = $this->addressHandler->generateRemoteURL(); @@ -149,6 +148,7 @@ public function create(IShare $share) { $this->removeShareFromTable($share); $shareId = $this->createFederatedShare($share); } + if ($send) { $this->updateSuccessfulReshare($shareId, $token); $this->storeRemoteId($shareId, $remoteId); @@ -162,6 +162,7 @@ public function create(IShare $share) { } $data = $this->getRawShare($shareId); + return $this->createShareObject($data); } @@ -195,6 +196,7 @@ protected function createFederatedShare(IShare $share) { $cloudId = $this->cloudIdManager->getCloudId($sharedByFederatedId, $this->addressHandler->generateRemoteURL()); $sharedByFederatedId = $cloudId->getId(); } + $ownerCloudId = $this->cloudIdManager->getCloudId($share->getShareOwner(), $this->addressHandler->generateRemoteURL()); $send = $this->notifications->sendRemoteShare( $token, @@ -314,6 +316,7 @@ private function addShareToDB($itemSource, $itemType, $shareWith, $sharedBy, $ui $qb->setValue('file_target', $qb->createNamedParameter('')); $qb->execute(); + return $qb->getLastInsertId(); } @@ -359,6 +362,7 @@ protected function sendPermissionUpdate(IShare $share) { } else { // ... if not we send the permission change to the owner [, $remote] = $this->addressHandler->splitUserRemote($share->getShareOwner()); } + $this->notifications->sendPermissionChange($remote, $remoteId, $share->getToken(), $share->getPermissions()); } @@ -448,6 +452,7 @@ public function getChildren(IShare $parent) { while ($data = $cursor->fetch()) { $children[] = $this->createShareObject($data); } + $cursor->closeCursor(); return $children; @@ -500,6 +505,7 @@ protected function revokeShare($share, $isOwner) { } else { [, $remote] = $this->addressHandler->splitUserRemote($share->getShareOwner()); } + $remoteId = $this->getRemoteId($share); $this->notifications->sendRevokeShare($remote, $remoteId, $share->getToken()); } @@ -588,6 +594,7 @@ public function getSharesInFolder($userId, Folder $node, $reshares, $shallow = t while ($data = $cursor->fetch()) { $shares[$data['fileid']][] = $this->createShareObject($data); } + $cursor->closeCursor(); return $shares; @@ -644,6 +651,7 @@ public function getSharesBy($userId, $shareType, $node, $reshares, $limit, $offs while ($data = $cursor->fetch()) { $shares[] = $this->createShareObject($data); } + $cursor->closeCursor(); return $shares; @@ -697,6 +705,7 @@ public function getSharesByPath(Node $path) { while ($data = $cursor->fetch()) { $shares[] = $this->createShareObject($data); } + $cursor->closeCursor(); return $shares; @@ -721,6 +730,7 @@ public function getSharedWith($userId, $shareType, $node, $limit, $offset) { if ($limit !== -1) { $qb->setMaxResults($limit); } + $qb->setFirstResult($offset); $qb->where($qb->expr()->in('share_type', $qb->createNamedParameter($this->supportedShareType, IQueryBuilder::PARAM_INT_ARRAY))); @@ -736,6 +746,7 @@ public function getSharedWith($userId, $shareType, $node, $limit, $offset) { while ($data = $cursor->fetch()) { $shares[] = $this->createShareObject($data); } + $cursor->closeCursor(); @@ -915,7 +926,9 @@ public function isOutgoingServer2serverShareEnabled() { if ($this->gsConfig->onlyInternalFederation()) { return false; } + $result = $this->config->getAppValue('files_sharing', 'outgoing_server2server_share_enabled', 'yes'); + return ($result === 'yes'); } @@ -928,7 +941,9 @@ public function isIncomingServer2serverShareEnabled() { if ($this->gsConfig->onlyInternalFederation()) { return false; } + $result = $this->config->getAppValue('files_sharing', 'incoming_server2server_share_enabled', 'yes'); + return ($result === 'yes'); } @@ -942,7 +957,9 @@ public function isOutgoingServer2serverGroupShareEnabled() { if ($this->gsConfig->onlyInternalFederation()) { return false; } + $result = $this->config->getAppValue('files_sharing', 'outgoing_server2server_group_share_enabled', 'no'); + return ($result === 'yes'); } @@ -955,7 +972,9 @@ public function isIncomingServer2serverGroupShareEnabled() { if ($this->gsConfig->onlyInternalFederation()) { return false; } + $result = $this->config->getAppValue('files_sharing', 'incoming_server2server_group_share_enabled', 'no'); + return ($result === 'yes'); } @@ -978,7 +997,9 @@ public function isLookupServerQueriesEnabled() { if ($this->gsConfig->isGlobalScaleEnabled()) { return true; } + $result = $this->config->getAppValue('files_sharing', 'lookupServerEnabled', 'yes'); + return ($result === 'yes'); } @@ -993,7 +1014,9 @@ public function isLookupServerUploadEnabled() { if ($this->gsConfig->isGlobalScaleEnabled()) { return false; } + $result = $this->config->getAppValue('files_sharing', 'lookupServerUploadEnabled', 'yes'); + return ($result === 'yes'); } @@ -1031,6 +1054,7 @@ public function getAccessList($nodes, $currentAccess) { 'token' => $row['token'], ]; } + $cursor->closeCursor(); return ['remote' => $remote]; @@ -1060,6 +1084,7 @@ public function getAllShares(): iterable { yield $share; } + $cursor->closeCursor(); } } diff --git a/apps/federatedfilesharing/lib/Migration/Version1010Date20200630191755.php b/apps/federatedfilesharing/lib/Migration/Version1010Date20200630191755.php index 27197ab67f619..61143455043fb 100644 --- a/apps/federatedfilesharing/lib/Migration/Version1010Date20200630191755.php +++ b/apps/federatedfilesharing/lib/Migration/Version1010Date20200630191755.php @@ -38,6 +38,7 @@ public function changeSchema(IOutput $output, Closure $schemaClosure, array $opt $table->setPrimaryKey(['share_id'], 'federated_res_pk'); // $table->addUniqueIndex(['share_id'], 'share_id_index'); } + return $schema; } } diff --git a/apps/federatedfilesharing/lib/Migration/Version1011Date20201120125158.php b/apps/federatedfilesharing/lib/Migration/Version1011Date20201120125158.php index 78517e3a29731..8ee804490f0e5 100644 --- a/apps/federatedfilesharing/lib/Migration/Version1011Date20201120125158.php +++ b/apps/federatedfilesharing/lib/Migration/Version1011Date20201120125158.php @@ -37,6 +37,7 @@ public function changeSchema(IOutput $output, Closure $schemaClosure, array $opt $remoteIdColumn->setType(Type::getType(Types::STRING)); $remoteIdColumn->setOptions(['length' => 255]); $remoteIdColumn->setDefault(''); + return $schema; } } diff --git a/apps/federatedfilesharing/lib/Notifications.php b/apps/federatedfilesharing/lib/Notifications.php index e107065d0c19d..20d03fd519619 100644 --- a/apps/federatedfilesharing/lib/Notifications.php +++ b/apps/federatedfilesharing/lib/Notifications.php @@ -76,6 +76,7 @@ public function sendRemoteShare($token, $shareWith, $name, $remoteId, $owner, $o if ($result['success'] && (!$ocsStatus || $ocsSuccess)) { $event = new FederatedShareAddedEvent($remote); $this->eventDispatcher->dispatchTyped($event); + return true; } else { $this->logger->info( @@ -302,6 +303,7 @@ protected function tryHttpPostToShareEndpoint($remoteDomain, $urlSuffix, array $ $result['success'] = true; $result['result'] = json_encode([ 'ocs' => ['meta' => ['statuscode' => 200]]]); + return $result; } @@ -372,6 +374,7 @@ protected function tryOCMEndPoint($remoteDomain, $fields, $action) { $fields['shareType'], 'file' ); + return $this->federationProviderManager->sendShare($share); case 'reshare': // ask owner to reshare a file @@ -387,6 +390,7 @@ protected function tryOCMEndPoint($remoteDomain, $fields, $action) { 'message' => 'Ask owner to reshare the file' ] ); + return $this->federationProviderManager->sendNotification($remoteDomain, $notification); case 'unshare': //owner unshares the file from the recipient again @@ -399,6 +403,7 @@ protected function tryOCMEndPoint($remoteDomain, $fields, $action) { 'messgage' => 'file is no longer shared with you' ] ); + return $this->federationProviderManager->sendNotification($remoteDomain, $notification); case 'reshare_undo': // if a reshare was unshared we send the information to the initiator/owner @@ -411,6 +416,7 @@ protected function tryOCMEndPoint($remoteDomain, $fields, $action) { 'message' => 'reshare was revoked' ] ); + return $this->federationProviderManager->sendNotification($remoteDomain, $notification); } diff --git a/apps/federatedfilesharing/lib/Notifier.php b/apps/federatedfilesharing/lib/Notifier.php index 4c4380d0875dd..39a4f75e61522 100644 --- a/apps/federatedfilesharing/lib/Notifier.php +++ b/apps/federatedfilesharing/lib/Notifier.php @@ -138,6 +138,7 @@ public function prepare(INotification $notification, string $languageCode): INot $notification->addParsedAction($action); } + return $notification; default: @@ -158,6 +159,7 @@ protected function createRemoteUser(string $cloudId, string $displayName = '') { if ($displayName === '') { $displayName = $this->getDisplayName($resolvedId); } + $user = $resolvedId->getUser(); $server = $resolvedId->getRemote(); } catch (HintException $e) { diff --git a/apps/federatedfilesharing/lib/OCM/CloudFederationProviderFiles.php b/apps/federatedfilesharing/lib/OCM/CloudFederationProviderFiles.php index 8aa8ba67a2a31..5a82d2328ebac 100644 --- a/apps/federatedfilesharing/lib/OCM/CloudFederationProviderFiles.php +++ b/apps/federatedfilesharing/lib/OCM/CloudFederationProviderFiles.php @@ -171,6 +171,7 @@ public function shareReceived(ICloudFederationShare $share) { $this->notifyAboutNewShare($user->getUID(), $shareId, $ownerFederatedId, $sharedByFederatedId, $name, $ownerDisplayName); } } + return $shareId; } catch (\Exception $e) { $this->logger->error('Server can not add remote share.', [ @@ -214,7 +215,6 @@ public function notificationReceived($notificationType, $providerId, array $noti return $this->updateResharePermissions($providerId, $notification); } - throw new BadRequestException([$notificationType]); } @@ -411,12 +411,14 @@ private function undoReshare($id, array $notification) { if (!isset($notification['sharedSecret'])) { throw new BadRequestException(['sharedSecret']); } + $token = $notification['sharedSecret']; $share = $this->federatedShareProvider->getShareById($id); $this->verifyShare($share, $token); $this->federatedShareProvider->removeShareFromTable($share); + return []; } @@ -437,6 +439,7 @@ private function unshare($id, array $notification) { if (!isset($notification['sharedSecret'])) { throw new BadRequestException(['sharedSecret']); } + $token = $notification['sharedSecret']; $qb = $this->connection->getQueryBuilder(); @@ -485,6 +488,7 @@ private function unshare($id, array $notification) { } else { $path = trim($share['name'], '/'); } + $notification = $this->notificationManager->createNotification(); $notification->setApp('files_sharing') ->setUser($share['user']) @@ -525,16 +529,19 @@ protected function reshareRequested($id, array $notification) { if (!isset($notification['sharedSecret'])) { throw new BadRequestException(['sharedSecret']); } + $token = $notification['sharedSecret']; if (!isset($notification['shareWith'])) { throw new BadRequestException(['shareWith']); } + $shareWith = $notification['shareWith']; if (!isset($notification['senderId'])) { throw new BadRequestException(['senderId']); } + $senderId = $notification['senderId']; $share = $this->federatedShareProvider->getShareById($id); @@ -564,6 +571,7 @@ protected function reshareRequested($id, array $notification) { $share->setSharedWith($shareWith); $result = $this->federatedShareProvider->create($share); $this->federatedShareProvider->storeRemoteId((int)$result->getId(), $senderId); + return ['token' => $result->getToken(), 'providerId' => $result->getId()]; } else { throw new ProviderCouldNotAddShareException('resharing not allowed for share: ' . $id); @@ -642,6 +650,7 @@ private function getFile($user, $fileSource) { } catch (NotFoundException $e) { $file = null; } + $args = Filesystem::is_dir($file) ? ['dir' => $file] : ['dir' => dirname($file), 'scrollto' => $file]; $link = Util::linkToAbsolute('files', 'index.php', $args); @@ -740,6 +749,7 @@ public function getUserDisplayName(string $userId): string { $e->getMessage(), ['exception' => $e] ); + return ''; } diff --git a/apps/federatedfilesharing/lib/Settings/Admin.php b/apps/federatedfilesharing/lib/Settings/Admin.php index 90ee9a8c74835..a7bdc545413d5 100644 --- a/apps/federatedfilesharing/lib/Settings/Admin.php +++ b/apps/federatedfilesharing/lib/Settings/Admin.php @@ -28,7 +28,7 @@ public function __construct( IConfig $globalScaleConfig, IL10N $l, IURLGenerator $urlGenerator, - IInitialState $initialState + IInitialState $initialState, ) { $this->fedShareProvider = $fedShareProvider; $this->gsConfig = $globalScaleConfig; diff --git a/apps/federatedfilesharing/lib/Settings/Personal.php b/apps/federatedfilesharing/lib/Settings/Personal.php index bee6cc538c66b..40f1baeed0522 100644 --- a/apps/federatedfilesharing/lib/Settings/Personal.php +++ b/apps/federatedfilesharing/lib/Settings/Personal.php @@ -28,7 +28,7 @@ public function __construct( IUserSession $userSession, Defaults $defaults, IInitialState $initialState, - IURLGenerator $urlGenerator + IURLGenerator $urlGenerator, ) { $this->federatedShareProvider = $federatedShareProvider; $this->userSession = $userSession; @@ -64,6 +64,7 @@ public function getSection(): ?string { $this->federatedShareProvider->isIncomingServer2serverGroupShareEnabled()) { return 'sharing'; } + return null; } diff --git a/apps/federatedfilesharing/lib/TokenHandler.php b/apps/federatedfilesharing/lib/TokenHandler.php index e0a6d2cbe8ca9..92398027f5a48 100644 --- a/apps/federatedfilesharing/lib/TokenHandler.php +++ b/apps/federatedfilesharing/lib/TokenHandler.php @@ -38,6 +38,7 @@ public function generateToken() { $token = $this->secureRandom->generate( self::TOKEN_LENGTH, ISecureRandom::CHAR_ALPHANUMERIC); + return $token; } } diff --git a/apps/federatedfilesharing/tests/AddressHandlerTest.php b/apps/federatedfilesharing/tests/AddressHandlerTest.php index e235314e00845..8bcd296e3d37a 100644 --- a/apps/federatedfilesharing/tests/AddressHandlerTest.php +++ b/apps/federatedfilesharing/tests/AddressHandlerTest.php @@ -86,6 +86,7 @@ public function dataTestSplitUserRemote() { } } } + return $testCases; } diff --git a/apps/federatedfilesharing/tests/Controller/MountPublicLinkControllerTest.php b/apps/federatedfilesharing/tests/Controller/MountPublicLinkControllerTest.php index 6eb7ce302746b..a9a7f3dc7961b 100644 --- a/apps/federatedfilesharing/tests/Controller/MountPublicLinkControllerTest.php +++ b/apps/federatedfilesharing/tests/Controller/MountPublicLinkControllerTest.php @@ -129,7 +129,7 @@ public function testCreateFederatedShare($shareWith, $validToken, $createSuccessful, $expectedReturnData, - $permissions + $permissions, ): void { $this->federatedShareProvider->expects($this->any()) ->method('isOutgoingServer2serverShareEnabled') @@ -142,6 +142,7 @@ function ($shareWith) use ($validShareWith, $expectedReturnData) { if ($validShareWith) { return ['user', 'server']; } + throw new HintException($expectedReturnData, $expectedReturnData); } ); @@ -156,6 +157,7 @@ function ($token) use ($validToken, $share, $expectedReturnData) { if ($validToken) { return $share; } + throw new HintException($expectedReturnData, $expectedReturnData); } ); @@ -168,6 +170,7 @@ function (IShare $share) use ($createSuccessful, $shareWith, $expectedReturnData if ($createSuccessful) { return $share; } + throw new HintException($expectedReturnData, $expectedReturnData); } ); diff --git a/apps/federatedfilesharing/tests/FederatedShareProviderTest.php b/apps/federatedfilesharing/tests/FederatedShareProviderTest.php index 5255a84b46115..ebf6cf8097c4c 100644 --- a/apps/federatedfilesharing/tests/FederatedShareProviderTest.php +++ b/apps/federatedfilesharing/tests/FederatedShareProviderTest.php @@ -670,6 +670,7 @@ public function testGetSharedByWithLimit(): void { if ($uid === 'user@server.com') { return ['user', 'server.com']; } + return ['user2', 'server.com']; }); diff --git a/apps/federatedfilesharing/tests/NotificationsTest.php b/apps/federatedfilesharing/tests/NotificationsTest.php index 868591cb4dde4..7ac4e964362c0 100644 --- a/apps/federatedfilesharing/tests/NotificationsTest.php +++ b/apps/federatedfilesharing/tests/NotificationsTest.php @@ -113,7 +113,7 @@ public function testSendUpdateToRemote($try, $httpRequestResult, $expected): voi $instance->expects($this->any())->method('getTimestamp')->willReturn($timestamp); $instance->expects($this->once())->method('tryHttpPostToShareEndpoint') - ->with($remote, '/'.$id.'/unshare', ['token' => $token, 'data1Key' => 'data1Value', 'remoteId' => $id], $action) + ->with($remote, '/' . $id . '/unshare', ['token' => $token, 'data1Key' => 'data1Value', 'remoteId' => $id], $action) ->willReturn($httpRequestResult); // only add background job on first try diff --git a/apps/federatedfilesharing/tests/TestCase.php b/apps/federatedfilesharing/tests/TestCase.php index 9564cb7ec09b6..43cb16fcc020b 100644 --- a/apps/federatedfilesharing/tests/TestCase.php +++ b/apps/federatedfilesharing/tests/TestCase.php @@ -48,6 +48,7 @@ public static function tearDownAfterClass(): void { if ($user !== null) { $user->delete(); } + $user = \OC::$server->getUserManager()->get(self::TEST_FILES_SHARING_API_USER2); if ($user !== null) { $user->delete(); diff --git a/apps/federation/lib/BackgroundJob/GetSharedSecret.php b/apps/federation/lib/BackgroundJob/GetSharedSecret.php index 1a23d58a7d100..7cbd2e1a68e2c 100644 --- a/apps/federation/lib/BackgroundJob/GetSharedSecret.php +++ b/apps/federation/lib/BackgroundJob/GetSharedSecret.php @@ -47,7 +47,7 @@ public function __construct( TrustedServers $trustedServers, LoggerInterface $logger, IDiscoveryService $ocsDiscoveryService, - ITimeFactory $timeFactory + ITimeFactory $timeFactory, ) { parent::__construct($timeFactory); $this->logger = $logger; @@ -93,6 +93,7 @@ protected function run($argument) { $this->logger->warning("The job to get the shared secret job is too old and gets stopped now without retention. Setting server status of '{$target}' to failure."); $this->retainJob = false; $this->trustedServers->setServerStatus($target, TrustedServers::STATUS_FAILURE); + return; } diff --git a/apps/federation/lib/BackgroundJob/RequestSharedSecret.php b/apps/federation/lib/BackgroundJob/RequestSharedSecret.php index a1d0d2b0df06a..ccdb2d7151df4 100644 --- a/apps/federation/lib/BackgroundJob/RequestSharedSecret.php +++ b/apps/federation/lib/BackgroundJob/RequestSharedSecret.php @@ -96,6 +96,7 @@ protected function run($argument) { $this->logger->warning("The job to request the shared secret job is too old and gets stopped now without retention. Setting server status of '{$target}' to failure."); $this->retainJob = false; $this->trustedServers->setServerStatus($target, TrustedServers::STATUS_FAILURE); + return; } diff --git a/apps/federation/lib/Controller/OCSAuthAPIController.php b/apps/federation/lib/Controller/OCSAuthAPIController.php index 8412868da42c8..221a9321cd7bf 100644 --- a/apps/federation/lib/Controller/OCSAuthAPIController.php +++ b/apps/federation/lib/Controller/OCSAuthAPIController.php @@ -50,7 +50,7 @@ public function __construct( DbHandler $dbHandler, LoggerInterface $logger, ITimeFactory $timeFactory, - IThrottler $throttler + IThrottler $throttler, ) { parent::__construct($appName, $request); @@ -165,7 +165,7 @@ public function getSharedSecret(string $url, string $token): DataResponse { $this->throttler->registerAttempt('federationSharedSecret', $this->request->getRemoteAddress()); $expectedToken = $this->dbHandler->getToken($url); $this->logger->error( - 'remote server (' . $url . ') didn\'t send a valid token (got "' . $token . '" but expected "'. $expectedToken . '") while getting shared secret', + 'remote server (' . $url . ') didn\'t send a valid token (got "' . $token . '" but expected "' . $expectedToken . '") while getting shared secret', ['app' => 'federation'] ); throw new OCSForbiddenException(); diff --git a/apps/federation/lib/Controller/SettingsController.php b/apps/federation/lib/Controller/SettingsController.php index f5131581d94b7..18ebc516cb8dd 100644 --- a/apps/federation/lib/Controller/SettingsController.php +++ b/apps/federation/lib/Controller/SettingsController.php @@ -23,7 +23,7 @@ class SettingsController extends Controller { public function __construct(string $AppName, IRequest $request, IL10N $l10n, - TrustedServers $trustedServers + TrustedServers $trustedServers, ) { parent::__construct($AppName, $request); $this->l = $l10n; diff --git a/apps/federation/lib/DbHandler.php b/apps/federation/lib/DbHandler.php index 8a189dc56f7ac..8141d6d06cfa3 100644 --- a/apps/federation/lib/DbHandler.php +++ b/apps/federation/lib/DbHandler.php @@ -31,7 +31,7 @@ class DbHandler { public function __construct( IDBConnection $connection, - IL10N $il10n + IL10N $il10n, ) { $this->connection = $connection; $this->IL10N = $il10n; @@ -63,6 +63,7 @@ public function addServer(string $url): int { $message = 'Internal failure, Could not add trusted server: ' . $url; $message_t = $this->IL10N->t('Could not add server'); throw new HintException($message, $message_t); + return -1; } @@ -113,6 +114,7 @@ public function getAllServer(): array { $statement = $query->executeQuery(); $result = $statement->fetchAll(); $statement->closeCursor(); + return $result; } @@ -196,6 +198,7 @@ public function getSharedSecret(string $url): string { $statement = $query->executeQuery(); $result = $statement->fetch(); $statement->closeCursor(); + return (string)$result['shared_secret']; } @@ -211,6 +214,7 @@ public function setServerStatus(string $url, int $status, ?string $token = null) if (!is_null($token)) { $query->set('sync_token', $query->createNamedParameter($token)); } + $query->executeStatement(); } @@ -227,6 +231,7 @@ public function getServerStatus(string $url): int { $statement = $query->executeQuery(); $result = $statement->fetch(); $statement->closeCursor(); + return (int)$result['status']; } @@ -260,6 +265,7 @@ public function auth(string $username, string $password): bool { if ($username !== 'system') { return false; } + $query = $this->connection->getQueryBuilder(); $query->select('url')->from($this->dbTable) ->where($query->expr()->eq('shared_secret', $query->createNamedParameter($password))); @@ -267,6 +273,7 @@ public function auth(string $username, string $password): bool { $statement = $query->executeQuery(); $result = $statement->fetch(); $statement->closeCursor(); + return !empty($result); } } diff --git a/apps/federation/lib/Middleware/AddServerMiddleware.php b/apps/federation/lib/Middleware/AddServerMiddleware.php index 8868e5c920709..b96b7b23c340a 100644 --- a/apps/federation/lib/Middleware/AddServerMiddleware.php +++ b/apps/federation/lib/Middleware/AddServerMiddleware.php @@ -40,6 +40,7 @@ public function afterException($controller, $methodName, \Exception $exception) if (($controller instanceof SettingsController) === false) { throw $exception; } + $this->logger->error($exception->getMessage(), [ 'app' => $this->appName, 'exception' => $exception, diff --git a/apps/federation/lib/Migration/Version1010Date20200630191302.php b/apps/federation/lib/Migration/Version1010Date20200630191302.php index c1a7c38cfc7b5..f38d482935ccb 100644 --- a/apps/federation/lib/Migration/Version1010Date20200630191302.php +++ b/apps/federation/lib/Migration/Version1010Date20200630191302.php @@ -61,6 +61,7 @@ public function changeSchema(IOutput $output, Closure $schemaClosure, array $opt $table->setPrimaryKey(['id']); $table->addUniqueIndex(['url_hash'], 'url_hash'); } + return $schema; } } diff --git a/apps/federation/lib/SyncFederationAddressBooks.php b/apps/federation/lib/SyncFederationAddressBooks.php index aaed6ca9d9469..4b33ffa7246a4 100644 --- a/apps/federation/lib/SyncFederationAddressBooks.php +++ b/apps/federation/lib/SyncFederationAddressBooks.php @@ -22,7 +22,7 @@ class SyncFederationAddressBooks { public function __construct(DbHandler $dbHandler, SyncService $syncService, IDiscoveryService $ocsDiscoveryService, - LoggerInterface $logger + LoggerInterface $logger, ) { $this->syncService = $syncService; $this->dbHandler = $dbHandler; @@ -49,6 +49,7 @@ public function syncThemAll(\Closure $callback) { $this->logger->debug("Shared secret for $url is null"); continue; } + $targetBookId = $trustedServer['url_hash']; $targetPrincipal = 'principals/system/system'; $targetBookProperties = [ @@ -77,6 +78,7 @@ public function syncThemAll(\Closure $callback) { 'exception' => $ex, ]); } + $callback($url, $ex); } } diff --git a/apps/federation/lib/TrustedServers.php b/apps/federation/lib/TrustedServers.php index 48cffd2d274f8..9812a3c3237de 100644 --- a/apps/federation/lib/TrustedServers.php +++ b/apps/federation/lib/TrustedServers.php @@ -51,7 +51,7 @@ public function __construct( ISecureRandom $secureRandom, IConfig $config, IEventDispatcher $dispatcher, - ITimeFactory $timeFactory + ITimeFactory $timeFactory, ) { $this->dbHandler = $dbHandler; $this->httpClientService = $httpClientService; @@ -119,6 +119,7 @@ public function getServers() { if ($this->trustedServersCache === null) { $this->trustedServersCache = $this->dbHandler->getAllServer(); } + return $this->trustedServersCache; } @@ -162,6 +163,7 @@ public function isNextcloudServer(string $url): bool { if (is_resource($body)) { $body = stream_get_contents($body) ?: ''; } + $isValidNextcloud = $this->checkNextcloudVersion($body); } } catch (\Exception $e) { @@ -169,6 +171,7 @@ public function isNextcloudServer(string $url): bool { 'app' => 'federation', 'exception' => $e, ]); + return false; } @@ -185,8 +188,10 @@ protected function checkNextcloudVersion(string $status): bool { if (!version_compare($decoded['version'], '9.0.0', '>=')) { throw new HintException('Remote server version is too low. 9.0 is required.'); } + return true; } + return false; } diff --git a/apps/federation/templates/settings-admin.php b/apps/federation/templates/settings-admin.php index dabd341ce72da..e99bd48b571f8 100644 --- a/apps/federation/templates/settings-admin.php +++ b/apps/federation/templates/settings-admin.php @@ -29,11 +29,15 @@ - + - +

diff --git a/apps/federation/tests/BackgroundJob/GetSharedSecretTest.php b/apps/federation/tests/BackgroundJob/GetSharedSecretTest.php index 4fcb579d6f9a4..d242de59c8498 100644 --- a/apps/federation/tests/BackgroundJob/GetSharedSecretTest.php +++ b/apps/federation/tests/BackgroundJob/GetSharedSecretTest.php @@ -116,6 +116,7 @@ public function testExecute(bool $isTrustedServer, bool $retainBackgroundJob): v } else { $getSharedSecret->expects($this->never())->method('parentStart'); } + $this->invokePrivate($getSharedSecret, 'retainJob', [$retainBackgroundJob]); $this->jobList->expects($this->once())->method('remove'); diff --git a/apps/federation/tests/BackgroundJob/RequestSharedSecretTest.php b/apps/federation/tests/BackgroundJob/RequestSharedSecretTest.php index 63b8324ad2e79..7bd6c06b9fa26 100644 --- a/apps/federation/tests/BackgroundJob/RequestSharedSecretTest.php +++ b/apps/federation/tests/BackgroundJob/RequestSharedSecretTest.php @@ -110,6 +110,7 @@ public function testStart($isTrustedServer, $retainBackgroundJob): void { } else { $requestSharedSecret->expects($this->never())->method('parentStart'); } + $this->invokePrivate($requestSharedSecret, 'retainJob', [$retainBackgroundJob]); $this->jobList->expects($this->once())->method('remove'); diff --git a/apps/federation/tests/Controller/OCSAuthAPIControllerTest.php b/apps/federation/tests/Controller/OCSAuthAPIControllerTest.php index 9f2d41a10724c..a3148108ebbf4 100644 --- a/apps/federation/tests/Controller/OCSAuthAPIControllerTest.php +++ b/apps/federation/tests/Controller/OCSAuthAPIControllerTest.php @@ -102,7 +102,6 @@ public function testRequestSharedSecret(string $token, string $localToken, bool } } - try { $this->ocsAuthApi->requestSharedSecret($url, $token); $this->assertTrue($ok); diff --git a/apps/federation/tests/DbHandlerTest.php b/apps/federation/tests/DbHandlerTest.php index aabbf1472e77c..29bef15daa890 100644 --- a/apps/federation/tests/DbHandlerTest.php +++ b/apps/federation/tests/DbHandlerTest.php @@ -293,6 +293,7 @@ public function testAuth($expectedResult, $user, $password): void { $this->dbHandler->addServer('url1'); $this->dbHandler->addSharedSecret('url1', $password); } + $result = $this->dbHandler->auth($user, $password); $this->assertEquals($expectedResult, $result); } diff --git a/apps/files/ajax/download.php b/apps/files/ajax/download.php index fc434f79e2c2c..d3b494eb88c31 100644 --- a/apps/files/ajax/download.php +++ b/apps/files/ajax/download.php @@ -25,9 +25,11 @@ function cleanCookieInput(string $value): string { if (strlen($value) > 32) { return ''; } + if (preg_match('!^[a-zA-Z0-9]+$!', $_GET['downloadStartSecret']) !== 1) { return ''; } + return $value; } diff --git a/apps/files/lib/Activity/FavoriteProvider.php b/apps/files/lib/Activity/FavoriteProvider.php index 8cfc1a8379881..717f0eb40a95a 100644 --- a/apps/files/lib/Activity/FavoriteProvider.php +++ b/apps/files/lib/Activity/FavoriteProvider.php @@ -130,6 +130,7 @@ public function parseLongVersion(IEvent $event, ?IEvent $previousEvent = null) { $this->setSubjects($event, $subject); $event = $this->eventMerger->mergeEvents('file', $event, $previousEvent); + return $event; } @@ -147,6 +148,7 @@ protected function setSubjects(IEvent $event, $subject) { 'path' => $event->getObjectName(), ]; } + $parameter = [ 'type' => 'file', 'id' => $subjectParams['id'], diff --git a/apps/files/lib/Activity/Filter/Favorites.php b/apps/files/lib/Activity/Filter/Favorites.php index 1e63f6d9aee98..804020bd1ea03 100644 --- a/apps/files/lib/Activity/Filter/Favorites.php +++ b/apps/files/lib/Activity/Filter/Favorites.php @@ -119,6 +119,7 @@ public function filterFavorites(IQueryBuilder $query) { if (!empty($favorites['items'])) { $limitations[] = $query->expr()->in('file', $query->createNamedParameter($favorites['items'], IQueryBuilder::PARAM_STR_ARRAY)); } + foreach ($favorites['folders'] as $favorite) { $limitations[] = $query->expr()->like('file', $query->createNamedParameter( $this->db->escapeLikeParameter($favorite . '/') . '%' diff --git a/apps/files/lib/Activity/Provider.php b/apps/files/lib/Activity/Provider.php index f777d11c02ed4..bcd1d72290ca0 100644 --- a/apps/files/lib/Activity/Provider.php +++ b/apps/files/lib/Activity/Provider.php @@ -168,12 +168,14 @@ public function parseLongVersion(IEvent $event, ?IEvent $previousEvent = null) { if ($this->fileIsEncrypted) { $subject = $this->l->t('You created an encrypted file in {file}'); } + $this->setIcon($event, 'add-color'); } elseif ($event->getSubject() === 'created_by') { $subject = $this->l->t('{user} created {file}'); if ($this->fileIsEncrypted) { $subject = $this->l->t('{user} created an encrypted file in {file}'); } + $this->setIcon($event, 'add-color'); } elseif ($event->getSubject() === 'created_public') { $subject = $this->l->t('{file} was created in a public folder'); @@ -183,24 +185,28 @@ public function parseLongVersion(IEvent $event, ?IEvent $previousEvent = null) { if ($this->fileIsEncrypted) { $subject = $this->l->t('You changed an encrypted file in {file}'); } + $this->setIcon($event, 'change'); } elseif ($event->getSubject() === 'changed_by') { $subject = $this->l->t('{user} changed {file}'); if ($this->fileIsEncrypted) { $subject = $this->l->t('{user} changed an encrypted file in {file}'); } + $this->setIcon($event, 'change'); } elseif ($event->getSubject() === 'deleted_self') { $subject = $this->l->t('You deleted {file}'); if ($this->fileIsEncrypted) { $subject = $this->l->t('You deleted an encrypted file in {file}'); } + $this->setIcon($event, 'delete-color'); } elseif ($event->getSubject() === 'deleted_by') { $subject = $this->l->t('{user} deleted {file}'); if ($this->fileIsEncrypted) { $subject = $this->l->t('{user} deleted an encrypted file in {file}'); } + $this->setIcon($event, 'delete-color'); } elseif ($event->getSubject() === 'restored_self') { $subject = $this->l->t('You restored {file}'); @@ -315,6 +321,7 @@ protected function getParameters(IEvent $event) { 'file' => $this->getFile($parameters[0], $event), ]; } + return [ 'file' => $this->getFile($parameters[0], $event), 'user' => $this->getUser($parameters[1]), @@ -334,12 +341,14 @@ protected function getParameters(IEvent $event) { 'oldfile' => $this->getFile($parameters[2]), ]; } + return [ 'newfile' => $this->getFile($parameters[0]), 'user' => $this->getUser($parameters[1]), 'oldfile' => $this->getFile($parameters[2]), ]; } + return []; } @@ -428,6 +437,7 @@ protected function getEndToEndEncryptionContainer($fileId, $path) { } $this->fileEncrypted[$fileId] = $file; + return $file; } @@ -435,10 +445,12 @@ protected function getEndToEndEncryptionContainer($fileId, $path) { // If the folder is encrypted, it is the Container, // but can be the name is just fine. $this->fileEncrypted[$fileId] = true; + return null; } $this->fileEncrypted[$fileId] = $this->getParentEndToEndEncryptionContainer($userFolder, $file); + return $this->fileEncrypted[$fileId]; } diff --git a/apps/files/lib/App.php b/apps/files/lib/App.php index 3fb93df6f1d02..730cc62006256 100644 --- a/apps/files/lib/App.php +++ b/apps/files/lib/App.php @@ -38,6 +38,7 @@ public static function getNavigationManager(): INavigationManager { ); self::$navigationManager->clear(false); } + return self::$navigationManager; } diff --git a/apps/files/lib/BackgroundJob/DeleteExpiredOpenLocalEditor.php b/apps/files/lib/BackgroundJob/DeleteExpiredOpenLocalEditor.php index 81f3d229bc01f..ce2de9a74bbb9 100644 --- a/apps/files/lib/BackgroundJob/DeleteExpiredOpenLocalEditor.php +++ b/apps/files/lib/BackgroundJob/DeleteExpiredOpenLocalEditor.php @@ -22,7 +22,7 @@ class DeleteExpiredOpenLocalEditor extends TimedJob { public function __construct( ITimeFactory $time, - OpenLocalEditorMapper $mapper + OpenLocalEditorMapper $mapper, ) { parent::__construct($time); $this->mapper = $mapper; diff --git a/apps/files/lib/BackgroundJob/DeleteOrphanedItems.php b/apps/files/lib/BackgroundJob/DeleteOrphanedItems.php index b1a795b775cc9..44b616190f0ea 100644 --- a/apps/files/lib/BackgroundJob/DeleteOrphanedItems.php +++ b/apps/files/lib/BackgroundJob/DeleteOrphanedItems.php @@ -129,6 +129,7 @@ private function findMissingSources(array $ids): array { ->from('filecache') ->where($qb->expr()->in('fileid', $qb->createNamedParameter($ids, IQueryBuilder::PARAM_INT_ARRAY))); $found = $qb->executeQuery()->fetchAll(\PDO::FETCH_COLUMN); + return array_diff($ids, $found); } @@ -140,6 +141,7 @@ private function findMissingSources(array $ids): array { protected function cleanSystemTags() { $deletedEntries = $this->cleanUp('systemtag_object_mapping', 'objectid', 'objecttype'); $this->logger->debug("$deletedEntries orphaned system tag relations deleted", ['app' => 'DeleteOrphanedItems']); + return $deletedEntries; } @@ -151,6 +153,7 @@ protected function cleanSystemTags() { protected function cleanUserTags() { $deletedEntries = $this->cleanUp('vcategory_to_object', 'objid', 'type'); $this->logger->debug("$deletedEntries orphaned user tag relations deleted", ['app' => 'DeleteOrphanedItems']); + return $deletedEntries; } @@ -162,6 +165,7 @@ protected function cleanUserTags() { protected function cleanComments() { $deletedEntries = $this->cleanUp('comments', 'object_id', 'object_type'); $this->logger->debug("$deletedEntries orphaned comments deleted", ['app' => 'DeleteOrphanedItems']); + return $deletedEntries; } @@ -173,6 +177,7 @@ protected function cleanComments() { protected function cleanCommentMarkers() { $deletedEntries = $this->cleanUp('comments_read_markers', 'object_id', 'object_type'); $this->logger->debug("$deletedEntries orphaned comment read marks deleted", ['app' => 'DeleteOrphanedItems']); + return $deletedEntries; } } diff --git a/apps/files/lib/BackgroundJob/ScanFiles.php b/apps/files/lib/BackgroundJob/ScanFiles.php index b7e6e8db10ecb..bfe12dbe741b2 100644 --- a/apps/files/lib/BackgroundJob/ScanFiles.php +++ b/apps/files/lib/BackgroundJob/ScanFiles.php @@ -37,7 +37,7 @@ public function __construct( IEventDispatcher $dispatcher, LoggerInterface $logger, IDBConnection $connection, - ITimeFactory $time + ITimeFactory $time, ) { parent::__construct($time); // Run once per 10 minutes @@ -61,6 +61,7 @@ protected function runScanner(string $user): void { } catch (\Exception $e) { $this->logger->error($e->getMessage(), ['exception' => $e, 'app' => 'files']); } + \OC_Util::tearDownFS(); } @@ -105,6 +106,7 @@ private function getUserToScan() { ->andWhere($query->expr()->in('f.storage', $query->createNamedParameter($storages, IQueryBuilder::PARAM_INT_ARRAY))) ->setMaxResults(1) ->runAcrossAllShards(); + return $query->executeQuery()->fetchOne(); } else { $query = $this->connection->getQueryBuilder(); @@ -124,6 +126,7 @@ private function getAllMountedStorages(): array { $query = $this->connection->getQueryBuilder(); $query->selectDistinct('storage_id') ->from('mounts'); + return $query->executeQuery()->fetchAll(\PDO::FETCH_COLUMN); } diff --git a/apps/files/lib/BackgroundJob/TransferOwnership.php b/apps/files/lib/BackgroundJob/TransferOwnership.php index de8d1989733e9..831bcb84449ea 100644 --- a/apps/files/lib/BackgroundJob/TransferOwnership.php +++ b/apps/files/lib/BackgroundJob/TransferOwnership.php @@ -49,8 +49,10 @@ protected function run($argument) { if (!$node) { $this->logger->alert('Could not transfer ownership: Node not found'); $this->failedNotication($transfer); + return; } + $path = $userFolder->getRelativePath($node->getPath()); $sourceUserObject = $this->userManager->get($sourceUser); @@ -59,12 +61,14 @@ protected function run($argument) { if (!$sourceUserObject instanceof IUser) { $this->logger->alert('Could not transfer ownership: Unknown source user ' . $sourceUser); $this->failedNotication($transfer); + return; } if (!$destinationUserObject instanceof IUser) { $this->logger->alert("Unknown destination user $destinationUser"); $this->failedNotication($transfer); + return; } diff --git a/apps/files/lib/Collaboration/Resources/ResourceProvider.php b/apps/files/lib/Collaboration/Resources/ResourceProvider.php index 0ac54afad7d05..6822fc611b708 100644 --- a/apps/files/lib/Collaboration/Resources/ResourceProvider.php +++ b/apps/files/lib/Collaboration/Resources/ResourceProvider.php @@ -42,11 +42,13 @@ private function getNode(IResource $resource): ?Node { if (isset($this->nodes[(int)$resource->getId()])) { return $this->nodes[(int)$resource->getId()]; } + $node = $this->rootFolder->getFirstNodeById((int)$resource->getId()); if ($node) { $this->nodes[(int)$resource->getId()] = $node; return $this->nodes[(int)$resource->getId()]; } + return null; } @@ -67,6 +69,7 @@ public function getResourceRichObject(IResource $resource): array { 'files.viewcontroller.showFile', ['fileid' => $resource->getId()] ); + return [ 'type' => 'file', 'id' => $resource->getId(), diff --git a/apps/files/lib/Command/Copy.php b/apps/files/lib/Command/Copy.php index e51a16899076b..d4e6c26db1c2a 100644 --- a/apps/files/lib/Command/Copy.php +++ b/apps/files/lib/Command/Copy.php @@ -70,6 +70,7 @@ public function execute(InputInterface $input, OutputInterface $output): int { if (!$force) { $output->writeln("Warning: $sourceInput is a file, but $targetInput is a folder"); } + $wouldRequireDelete = true; } else { $targetInput = $targetNode->getFullPath($node->getName()); @@ -80,6 +81,7 @@ public function execute(InputInterface $input, OutputInterface $output): int { if (!$force) { $output->writeln("Warning: $sourceInput is a folder, but $targetInput is a file"); } + $wouldRequireDelete = true; } } diff --git a/apps/files/lib/Command/Delete.php b/apps/files/lib/Command/Delete.php index d984f839c91d9..fccfaa1a07465 100644 --- a/apps/files/lib/Command/Delete.php +++ b/apps/files/lib/Command/Delete.php @@ -74,6 +74,7 @@ public function execute(InputInterface $input, OutputInterface $output): int { $output->writeln(' - ' . $file->getPath()); } } + $output->writeln(''); } @@ -82,6 +83,7 @@ public function execute(InputInterface $input, OutputInterface $output): int { } else { $maybeContents = ''; } + $question = new ConfirmationQuestion('Delete ' . $node->getPath() . $maybeContents . '? [y/N] ', false); $deleteConfirmed = $helper->ask($input, $output, $question); } diff --git a/apps/files/lib/Command/DeleteOrphanedFiles.php b/apps/files/lib/Command/DeleteOrphanedFiles.php index 4bbee0b45f4f2..0527228c8ef91 100644 --- a/apps/files/lib/Command/DeleteOrphanedFiles.php +++ b/apps/files/lib/Command/DeleteOrphanedFiles.php @@ -61,9 +61,9 @@ public function execute(InputInterface $input, OutputInterface $output): int { $output->writeln("$deletedFileCacheExtended orphaned file cache extended entries deleted"); } - $deletedMounts = $this->cleanupOrphanedMounts(); $output->writeln("$deletedMounts orphaned mount entries deleted"); + return self::SUCCESS; } @@ -73,6 +73,7 @@ private function getReferencedStorages(): array { ->from('filecache') ->groupBy('storage') ->runAcrossAllShards(); + return $query->executeQuery()->fetchAll(\PDO::FETCH_COLUMN); } @@ -81,6 +82,7 @@ private function getExistingStorages(): array { $query->select('numeric_id') ->from('storages') ->groupBy('numeric_id'); + return $query->executeQuery()->fetchAll(\PDO::FETCH_COLUMN); } @@ -103,6 +105,7 @@ private function getFileIdsForStorages(array $storageIds): array { $result[$row['storage']][] = $row['fileid']; } } + return $result; } @@ -153,6 +156,7 @@ private function cleanupOrphanedMounts(): int { $deletedEntries += $deleteQuery->setParameter('storageid', (int)$row['storage_id']) ->execute(); } + $result->closeCursor(); } diff --git a/apps/files/lib/Command/Get.php b/apps/files/lib/Command/Get.php index 60e028f615e00..defe026035a37 100644 --- a/apps/files/lib/Command/Get.php +++ b/apps/files/lib/Command/Get.php @@ -52,13 +52,16 @@ public function execute(InputInterface $input, OutputInterface $output): int { " Use occ files:get $fileInput - to output it to the terminal anyway", " Or occ files:get $fileInput to save to a file instead" ]); + return self::FAILURE; } + $source = $node->fopen('r'); if (!$source) { $output->writeln("Failed to open $fileInput for reading"); return self::FAILURE; } + $target = ($outputName === null || $outputName === '-') ? STDOUT : fopen($outputName, 'w'); if (!$target) { $output->writeln("Failed to open $outputName for reading"); @@ -66,6 +69,7 @@ public function execute(InputInterface $input, OutputInterface $output): int { } stream_copy_to_stream($source, $target); + return self::SUCCESS; } } diff --git a/apps/files/lib/Command/Object/Delete.php b/apps/files/lib/Command/Object/Delete.php index 07613ecc61674..77c54272b630b 100644 --- a/apps/files/lib/Command/Object/Delete.php +++ b/apps/files/lib/Command/Object/Delete.php @@ -55,6 +55,7 @@ public function execute(InputInterface $input, OutputInterface $output): int { if ($helper->ask($input, $output, $question)) { $objectStore->deleteObject($object); } + return self::SUCCESS; } } diff --git a/apps/files/lib/Command/Object/Get.php b/apps/files/lib/Command/Object/Get.php index c32de020c5ae0..7fdde0f5dd238 100644 --- a/apps/files/lib/Command/Object/Get.php +++ b/apps/files/lib/Command/Object/Get.php @@ -48,8 +48,10 @@ public function execute(InputInterface $input, OutputInterface $output): int { } catch (\Exception $e) { $msg = $e->getMessage(); $output->writeln("Failed to read $object from object store: $msg"); + return self::FAILURE; } + $target = $outputName === '-' ? STDOUT : fopen($outputName, 'w'); if (!$target) { $output->writeln("Failed to open $outputName for writing"); @@ -57,6 +59,7 @@ public function execute(InputInterface $input, OutputInterface $output): int { } stream_copy_to_stream($source, $target); + return self::SUCCESS; } diff --git a/apps/files/lib/Command/Object/ObjectUtil.php b/apps/files/lib/Command/Object/ObjectUtil.php index c4ab59608fbac..3f27b2f572492 100644 --- a/apps/files/lib/Command/Object/ObjectUtil.php +++ b/apps/files/lib/Command/Object/ObjectUtil.php @@ -27,11 +27,13 @@ private function getObjectStoreConfig(): ?array { $config['multibucket'] = true; return $config; } + $config = $this->config->getSystemValue('objectstore'); if (is_array($config)) { if (!isset($config['multibucket'])) { $config['multibucket'] = false; } + return $config; } @@ -44,6 +46,7 @@ public function getObjectStore(?string $bucket, OutputInterface $output): ?IObje $output->writeln('Instance is not using primary object store'); return null; } + if ($config['multibucket'] && !$bucket) { $output->writeln('--bucket option required because multi bucket is enabled.'); return null; @@ -52,6 +55,7 @@ public function getObjectStore(?string $bucket, OutputInterface $output): ?IObje if (!isset($config['arguments'])) { throw new \Exception('no arguments configured for object store configuration'); } + if (!isset($config['class'])) { throw new \Exception('no class configured for object store configuration'); } @@ -67,6 +71,7 @@ public function getObjectStore(?string $bucket, OutputInterface $output): ?IObje if (!$store instanceof IObjectStore) { throw new \Exception('configured object store class is not an object store implementation'); } + return $store; } diff --git a/apps/files/lib/Command/Object/Put.php b/apps/files/lib/Command/Object/Put.php index 8516eb511831b..0d0fb85dc8e85 100644 --- a/apps/files/lib/Command/Object/Put.php +++ b/apps/files/lib/Command/Object/Put.php @@ -61,7 +61,9 @@ public function execute(InputInterface $input, OutputInterface $output): int { $output->writeln("Failed to open $inputName"); return self::FAILURE; } + $objectStore->writeObject($object, $source, $this->mimeTypeDetector->detectPath($inputName)); + return self::SUCCESS; } diff --git a/apps/files/lib/Command/Put.php b/apps/files/lib/Command/Put.php index fd9d75db78c11..44382b62ec7f8 100644 --- a/apps/files/lib/Command/Put.php +++ b/apps/files/lib/Command/Put.php @@ -42,6 +42,7 @@ public function execute(InputInterface $input, OutputInterface $output): int { $output->writeln("$fileOutput is a folder"); return self::FAILURE; } + if (!$node and is_numeric($fileOutput)) { $output->writeln("$fileOutput not found"); return self::FAILURE; @@ -52,16 +53,19 @@ public function execute(InputInterface $input, OutputInterface $output): int { $output->writeln("Failed to open $inputName"); return self::FAILURE; } + if ($node instanceof File) { $target = $node->fopen('w'); if (!$target) { $output->writeln("Failed to open $fileOutput"); return self::FAILURE; } + stream_copy_to_stream($source, $target); } else { $this->rootFolder->newFile($fileOutput, $source); } + return self::SUCCESS; } } diff --git a/apps/files/lib/Command/RepairTree.php b/apps/files/lib/Command/RepairTree.php index 622ccba48a39d..522a63573e8ea 100644 --- a/apps/files/lib/Command/RepairTree.php +++ b/apps/files/lib/Command/RepairTree.php @@ -78,6 +78,7 @@ private function getFileId(int $storage, string $path) { ->from('filecache') ->where($query->expr()->eq('storage', $query->createNamedParameter($storage))) ->andWhere($query->expr()->eq('path_hash', $query->createNamedParameter(md5($path)))); + return $query->execute()->fetch(\PDO::FETCH_COLUMN); } diff --git a/apps/files/lib/Command/Scan.php b/apps/files/lib/Command/Scan.php index 68c80f6559ab4..c6c22eb958617 100644 --- a/apps/files/lib/Command/Scan.php +++ b/apps/files/lib/Command/Scan.php @@ -207,6 +207,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int if (is_object($user)) { $user = $user->getUID(); } + $path = $inputPath ?: '/' . $user; ++$user_count; if ($this->userManager->userExists($user)) { @@ -226,6 +227,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int } $this->presentStats($output); + return self::SUCCESS; } @@ -258,10 +260,12 @@ public function exceptionErrorHandler(OutputInterface $output, int $severity, st // Do not show deprecation warnings return false; } + $e = new \ErrorException($message, 0, $severity, $file, $line); $output->writeln('Error during scan: ' . $e->getMessage() . ''); $output->writeln('' . $e->getTraceAsString() . '', OutputInterface::VERBOSITY_VERY_VERBOSE); ++$this->errorsCounter; + return true; } @@ -315,6 +319,7 @@ protected function reconnectToDatabase(OutputInterface $output): Connection { } catch (\Exception $ex) { $output->writeln("Error while disconnecting from database: {$ex->getMessage()}"); } + while (!$connection->isConnected()) { try { $connection->connect(); @@ -323,6 +328,7 @@ protected function reconnectToDatabase(OutputInterface $output): Connection { sleep(60); } } + return $connection; } } diff --git a/apps/files/lib/Command/ScanAppData.php b/apps/files/lib/Command/ScanAppData.php index 5360d38bdb629..56be4aff83519 100644 --- a/apps/files/lib/Command/ScanAppData.php +++ b/apps/files/lib/Command/ScanAppData.php @@ -98,10 +98,12 @@ protected function scanFiles(OutputInterface $output, string $folder): int { } catch (ForbiddenException $e) { $output->writeln('Storage not writable'); $output->writeln('Make sure you\'re running the scan command only as the user the web server runs as'); + return self::FAILURE; } catch (InterruptedException $e) { # exit the function if ctrl-c has been pressed $output->writeln('Interrupted by user'); + return self::FAILURE; } catch (NotFoundException $e) { $output->writeln('Path not found: ' . $e->getMessage() . ''); @@ -109,6 +111,7 @@ protected function scanFiles(OutputInterface $output, string $folder): int { } catch (\Exception $e) { $output->writeln('Exception during scan: ' . $e->getMessage() . ''); $output->writeln('' . $e->getTraceAsString() . ''); + return self::FAILURE; } @@ -133,6 +136,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int if ($exitCode === 0) { $this->presentStats($output); } + return $exitCode; } @@ -163,6 +167,7 @@ public function exceptionErrorHandler($severity, $message, $file, $line) { // This error code is not included in error_reporting return; } + throw new \ErrorException($message, 0, $severity, $file, $line); } @@ -192,6 +197,7 @@ protected function showSummary($headers, $rows, OutputInterface $output): void { $niceDate, ]; } + $table = new Table($output); $table ->setHeaders($headers) @@ -217,6 +223,7 @@ protected function reconnectToDatabase(OutputInterface $output): Connection { } catch (\Exception $ex) { $output->writeln("Error while disconnecting from database: {$ex->getMessage()}"); } + while (!$connection->isConnected()) { try { $connection->connect(); @@ -225,6 +232,7 @@ protected function reconnectToDatabase(OutputInterface $output): Connection { sleep(60); } } + return $connection; } @@ -238,6 +246,6 @@ private function getAppDataFolder(): Node { throw new NotFoundException(); } - return $this->rootFolder->get('appdata_'.$instanceId); + return $this->rootFolder->get('appdata_' . $instanceId); } } diff --git a/apps/files/lib/Command/TransferOwnership.php b/apps/files/lib/Command/TransferOwnership.php index edc73e62c381e..c3cce21768bde 100644 --- a/apps/files/lib/Command/TransferOwnership.php +++ b/apps/files/lib/Command/TransferOwnership.php @@ -103,9 +103,11 @@ protected function execute(InputInterface $input, OutputInterface $output): int $output->writeln(" config.php: 'transfer-incoming-shares': wrong usage. Transfer aborted."); return self::FAILURE; } + break; default: $output->writeln('Option --transfer-incoming-shares: wrong usage. Transfer aborted.'); + return self::FAILURE; } diff --git a/apps/files/lib/Controller/ApiController.php b/apps/files/lib/Controller/ApiController.php index 62ae4e6b0f049..3905cadefd9f4 100644 --- a/apps/files/lib/Controller/ApiController.php +++ b/apps/files/lib/Controller/ApiController.php @@ -47,7 +47,8 @@ * @package OCA\Files\Controller */ class ApiController extends Controller { - public function __construct(string $appName, + public function __construct( + string $appName, IRequest $request, private IUserSession $userSession, private TagService $tagService, @@ -136,8 +137,10 @@ public function updateFileTags($path, $tags = null) { 'message' => $e->getMessage() ], Http::STATUS_NOT_FOUND); } + $result['tags'] = $tags; } + return new DataResponse($result); } @@ -157,9 +160,11 @@ private function formatNodes(array $nodes) { } else { $file['path'] = '/'; } + if (!empty($shareTypes)) { $file['shareTypes'] = $shareTypes; } + return $file; }, $nodes)); } @@ -201,6 +206,7 @@ private function getShareTypesForNodes(array $nodes): array { if (!isset($shareTypes[$fileId])) { $shareTypes[$fileId] = []; } + $shareTypes[$fileId][] = $shareType; unset($nodesLeft[$fileId]); } @@ -213,6 +219,7 @@ private function getShareTypesForNodes(array $nodes): array { } } } + return $shareTypes; } @@ -225,6 +232,7 @@ private function getShareTypesForNodes(array $nodes): array { public function getRecentFiles() { $nodes = $this->userFolder->getRecent(100); $files = $this->formatNodes($nodes); + return new DataResponse(['files' => $files]); } @@ -253,8 +261,10 @@ private function getChildren(array $nodes, int $depth = 1, int $currentDepth = 0 if ($basename !== $displayName) { $entry['displayName'] = $displayName; } + $children[] = $entry; } + return $children; } @@ -280,6 +290,7 @@ public function getFolderTree(string $path = '/', int $depth = 1): JSONResponse 'message' => $this->l10n->t('Failed to authorize'), ], Http::STATUS_UNAUTHORIZED); } + try { $userFolder = $this->rootFolder->getUserFolder($user->getUID()); $userFolderPath = $userFolder->getPath(); @@ -290,6 +301,7 @@ public function getFolderTree(string $path = '/', int $depth = 1): JSONResponse 'message' => $this->l10n->t('Invalid folder path'), ], Http::STATUS_BAD_REQUEST); } + $nodes = $node->getDirectoryListing(); $tree = $this->getChildren($nodes, $depth); } catch (NotFoundException $e) { @@ -300,6 +312,7 @@ public function getFolderTree(string $path = '/', int $depth = 1): JSONResponse $this->logger->error($th->getMessage(), ['exception' => $th]); $tree = []; } + return new JSONResponse($tree); } @@ -314,6 +327,7 @@ public function getStorageStats($dir = '/'): JSONResponse { $storageInfo = \OC_Helper::getStorageInfo($dir ?: '/'); $response = new JSONResponse(['message' => 'ok', 'data' => $storageInfo]); $response->cacheFor(5 * 60); + return $response; } @@ -438,6 +452,7 @@ public function serviceWorker(): StreamResponse { $policy->addAllowedScriptDomain("'self'"); $policy->addAllowedConnectDomain("'self'"); $response->setContentSecurityPolicy($policy); + return $response; } } diff --git a/apps/files/lib/Controller/DirectEditingController.php b/apps/files/lib/Controller/DirectEditingController.php index 693587f9c8a56..ad19d903776d4 100644 --- a/apps/files/lib/Controller/DirectEditingController.php +++ b/apps/files/lib/Controller/DirectEditingController.php @@ -29,7 +29,7 @@ public function __construct( private IURLGenerator $urlGenerator, private IManager $directEditingManager, private DirectEditingService $directEditingService, - private LoggerInterface $logger + private LoggerInterface $logger, ) { parent::__construct($appName, $request, $corsMethods, $corsAllowedHeaders, $corsMaxAge); } @@ -44,6 +44,7 @@ public function __construct( public function info(): DataResponse { $response = new DataResponse($this->directEditingService->getDirectEditingCapabilitites()); $response->setETag($this->directEditingService->getDirectEditingETag()); + return $response; } @@ -65,6 +66,7 @@ public function create(string $path, string $editorId, string $creatorId, ?strin if (!$this->directEditingManager->isEnabled()) { return new DataResponse(['message' => 'Direct editing is not enabled'], Http::STATUS_INTERNAL_SERVER_ERROR); } + $this->eventDispatcher->dispatchTyped(new RegisterDirectEditorEvent($this->directEditingManager)); try { @@ -79,6 +81,7 @@ public function create(string $path, string $editorId, string $creatorId, ?strin 'exception' => $e ], ); + return new DataResponse(['message' => 'Failed to create file: ' . $e->getMessage()], Http::STATUS_FORBIDDEN); } } @@ -100,6 +103,7 @@ public function open(string $path, ?string $editorId = null, ?int $fileId = null if (!$this->directEditingManager->isEnabled()) { return new DataResponse(['message' => 'Direct editing is not enabled'], Http::STATUS_INTERNAL_SERVER_ERROR); } + $this->eventDispatcher->dispatchTyped(new RegisterDirectEditorEvent($this->directEditingManager)); try { @@ -114,6 +118,7 @@ public function open(string $path, ?string $editorId = null, ?int $fileId = null 'exception' => $e ], ); + return new DataResponse(['message' => 'Failed to open file: ' . $e->getMessage()], Http::STATUS_FORBIDDEN); } } @@ -135,6 +140,7 @@ public function templates(string $editorId, string $creatorId): DataResponse { if (!$this->directEditingManager->isEnabled()) { return new DataResponse(['message' => 'Direct editing is not enabled'], Http::STATUS_INTERNAL_SERVER_ERROR); } + $this->eventDispatcher->dispatchTyped(new RegisterDirectEditorEvent($this->directEditingManager)); try { @@ -146,6 +152,7 @@ public function templates(string $editorId, string $creatorId): DataResponse { 'exception' => $e ], ); + return new DataResponse(['message' => 'Failed to obtain template list: ' . $e->getMessage()], Http::STATUS_INTERNAL_SERVER_ERROR); } } diff --git a/apps/files/lib/Controller/OpenLocalEditorController.php b/apps/files/lib/Controller/OpenLocalEditorController.php index 0c13af2a6d2c4..409adcc20aa88 100644 --- a/apps/files/lib/Controller/OpenLocalEditorController.php +++ b/apps/files/lib/Controller/OpenLocalEditorController.php @@ -42,7 +42,7 @@ public function __construct( OpenLocalEditorMapper $mapper, ISecureRandom $secureRandom, LoggerInterface $logger, - ?string $userId + ?string $userId, ) { parent::__construct($appName, $request); @@ -94,6 +94,7 @@ public function create(string $path): DataResponse { } $this->logger->error('Giving up after ' . self::TOKEN_RETRIES . ' retries to generate a unique local editor token for path hash: ' . $pathHash); + return new DataResponse([], Http::STATUS_INTERNAL_SERVER_ERROR); } @@ -118,6 +119,7 @@ public function validate(string $path, string $token): DataResponse { } catch (DoesNotExistException $e) { $response = new DataResponse([], Http::STATUS_NOT_FOUND); $response->throttle(['userId' => $this->userId, 'pathHash' => $pathHash]); + return $response; } @@ -126,6 +128,7 @@ public function validate(string $path, string $token): DataResponse { if ($entity->getExpirationTime() <= $this->timeFactory->getTime()) { $response = new DataResponse([], Http::STATUS_NOT_FOUND); $response->throttle(['userId' => $this->userId, 'pathHash' => $pathHash]); + return $response; } diff --git a/apps/files/lib/Controller/TemplateController.php b/apps/files/lib/Controller/TemplateController.php index d4232763235cf..f5febab966bd9 100644 --- a/apps/files/lib/Controller/TemplateController.php +++ b/apps/files/lib/Controller/TemplateController.php @@ -62,7 +62,7 @@ public function create( string $filePath, string $templatePath = '', string $templateType = 'user', - array $templateFields = [] + array $templateFields = [], ): DataResponse { try { return new DataResponse($this->templateManager->createFromTemplate( @@ -91,6 +91,7 @@ public function path(string $templatePath = '', bool $copySystemTemplates = fals try { /** @var string $templatePath */ $templatePath = $this->templateManager->initializeTemplateDirectory($templatePath, null, $copySystemTemplates); + return new DataResponse([ 'template_path' => $templatePath, 'templates' => array_map(fn (TemplateFileCreator $creator) => $creator->jsonSerialize(), $this->templateManager->listCreators()), diff --git a/apps/files/lib/Controller/ViewController.php b/apps/files/lib/Controller/ViewController.php index c402a4c5bedd3..37cbd0143be09 100644 --- a/apps/files/lib/Controller/ViewController.php +++ b/apps/files/lib/Controller/ViewController.php @@ -237,9 +237,11 @@ private function redirectToFileIfInTrashbin($fileId): RedirectResponse { // set parent path as dir $params['dir'] = $baseFolder->getRelativePath($node->getParent()->getPath()); } + return new RedirectResponse($this->urlGenerator->linkToRoute('files.view.indexViewFileid', $params)); } } + throw new NotFoundException(); } @@ -272,6 +274,7 @@ private function redirectToFile(int $fileId) { // open the file by default (opening the viewer) $params['openfile'] = 'true'; } + return new RedirectResponse($this->urlGenerator->linkToRoute('files.view.indexViewFileid', $params)); } diff --git a/apps/files/lib/Helper.php b/apps/files/lib/Helper.php index 6126c1270ebad..8feddec7aa497 100644 --- a/apps/files/lib/Helper.php +++ b/apps/files/lib/Helper.php @@ -94,6 +94,7 @@ public static function compareFileNames(FileInfo $a, FileInfo $b) { public static function compareTimestamp(FileInfo $a, FileInfo $b) { $aTime = $a->getMTime(); $bTime = $b->getMTime(); + return ($aTime < $bTime) ? -1 : 1; } @@ -107,6 +108,7 @@ public static function compareTimestamp(FileInfo $a, FileInfo $b) { public static function compareSize(FileInfo $a, FileInfo $b) { $aSize = $a->getSize(); $bSize = $b->getSize(); + return ($aSize < $bSize) ? -1 : 1; } @@ -134,12 +136,15 @@ public static function formatFileInfo(FileInfo $i) { if (isset($i['tags'])) { $entry['tags'] = $i['tags']; } + if (isset($i['displayname_owner'])) { $entry['shareOwner'] = $i['displayname_owner']; } + if (isset($i['is_share_mount_point'])) { $entry['isShareMountPoint'] = $i['is_share_mount_point']; } + if (isset($i['extraData'])) { $entry['extraData'] = $i['extraData']; } @@ -151,8 +156,10 @@ public static function formatFileInfo(FileInfo $i) { if ($i->getInternalPath() === '') { $mountType .= '-root'; } + $entry['mountType'] = $mountType; } + return $entry; } @@ -199,6 +206,7 @@ public static function populateTags(array $fileList, $fileIdentifier, ITagManage foreach ($fileList as $fileData) { $ids[] = $fileData[$fileIdentifier]; } + $tagger = $tagManager->load('files'); $tags = $tagger->getTagsForObjects($ids); @@ -241,10 +249,12 @@ public static function sortFiles($files, $sortAttribute = 'name', $sortDescendin } elseif ($sortAttribute === 'size') { $sortFunc = 'compareSize'; } + usort($files, [Helper::class, $sortFunc]); if ($sortDescending) { $files = array_reverse($files); } + return $files; } } diff --git a/apps/files/lib/Listener/SyncLivePhotosListener.php b/apps/files/lib/Listener/SyncLivePhotosListener.php index 049d7319b6433..56286dae9430d 100644 --- a/apps/files/lib/Listener/SyncLivePhotosListener.php +++ b/apps/files/lib/Listener/SyncLivePhotosListener.php @@ -186,6 +186,7 @@ private function handleDeletion(BeforeNodeDeletedEvent $event, Node $peerFile): throw new AbortedEventException($ex->getMessage()); } } + return; } } diff --git a/apps/files/lib/Notification/Notifier.php b/apps/files/lib/Notification/Notifier.php index bae3a513df745..4ad12166e05ea 100644 --- a/apps/files/lib/Notification/Notifier.php +++ b/apps/files/lib/Notification/Notifier.php @@ -157,6 +157,7 @@ public function handleTransferOwnershipRequestDenied(INotification $notification 'name' => $targetUser->getDisplayName(), ], ]); + return $notification; } @@ -180,6 +181,7 @@ public function handleTransferOwnershipFailedSource(INotification $notification, 'name' => $targetUser->getDisplayName(), ], ]); + return $notification; } @@ -287,6 +289,7 @@ protected function getUser(string $userId): IUser { if ($user instanceof IUser) { return $user; } + throw new \InvalidArgumentException('User not found'); } } diff --git a/apps/files/lib/Search/FilesSearchProvider.php b/apps/files/lib/Search/FilesSearchProvider.php index 4dfd9bc0d16ed..39247bc95bae4 100644 --- a/apps/files/lib/Search/FilesSearchProvider.php +++ b/apps/files/lib/Search/FilesSearchProvider.php @@ -82,6 +82,7 @@ public function getOrder(string $route, array $routeParameters): int { // Before comments return -5; } + return 5; } @@ -119,6 +120,7 @@ public function getCustomFilters(): array { public function search(IUser $user, ISearchQuery $query): SearchResult { $userFolder = $this->rootFolder->getUserFolder($user->getUID()); $fileQuery = $this->buildSearchQuery($query, $user); + return SearchResult::paginated( $this->l10n->t('Files'), array_map(function (Node $result) use ($userFolder) { @@ -146,6 +148,7 @@ public function search(IUser $user, ISearchQuery $query): SearchResult { ); $searchResultEntry->addAttribute('fileId', (string)$result->getId()); $searchResultEntry->addAttribute('path', $path); + return $searchResultEntry; }, $userFolder->search($fileQuery)), $query->getCursor() + $query->getLimit() @@ -190,6 +193,7 @@ private function buildPersonSearchQuery(IFilter $person): ISearchOperator { new SearchComparison(ISearchComparison::COMPARE_EQUAL, 'owner', $person->get()->getUID()), ]); } + if ($person instanceof GroupFilter) { return new SearchBinaryOperator(SearchBinaryOperator::OPERATOR_AND, [ new SearchComparison(ISearchComparison::COMPARE_EQUAL, 'share_with', $person->get()->getGID()), diff --git a/apps/files/lib/Service/DirectEditingService.php b/apps/files/lib/Service/DirectEditingService.php index c21742d25ea21..6dfd4ad8ae75b 100644 --- a/apps/files/lib/Service/DirectEditingService.php +++ b/apps/files/lib/Service/DirectEditingService.php @@ -65,6 +65,7 @@ public function getDirectEditingCapabilitites(): array { ]; } } + return $capabilities; } } diff --git a/apps/files/lib/Service/OwnershipTransferService.php b/apps/files/lib/Service/OwnershipTransferService.php index 14cbe882245f5..0b502c6368e68 100644 --- a/apps/files/lib/Service/OwnershipTransferService.php +++ b/apps/files/lib/Service/OwnershipTransferService.php @@ -120,7 +120,6 @@ public function transfer( throw new TransferOwnershipException('Destination path does not exists or is not empty', 1); } - // analyse source folder $this->analyse( $sourceUid, @@ -195,6 +194,7 @@ private function walkFiles(View $view, $path, Closure $callBack) { if (!$callBack($fileInfo)) { return; } + if ($fileInfo->getType() === FileInfo::TYPE_FOLDER) { $this->walkFiles($view, $fileInfo->getPath(), $callBack); } @@ -216,6 +216,7 @@ protected function analyse(string $sourceUid, if ($sourceFileInfo === false) { throw new TransferOwnershipException("Unknown path provided: $sourcePath", 1); } + $size = $sourceFileInfo->getSize(false); $freeSpace = $view->free_space($destinationUid . '/files/'); if ($size > $freeSpace && $freeSpace !== FileInfo::SPACE_UNKNOWN) { @@ -231,6 +232,7 @@ protected function analyse(string $sourceUid, } else { $masterKeyEnabled = false; } + $encryptedFiles = []; if ($sourceFileInfo->getType() === FileInfo::TYPE_FOLDER) { if ($sourceFileInfo->isEncrypted()) { @@ -244,17 +246,21 @@ function (FileInfo $fileInfo) use ($progress, $masterKeyEnabled, &$encryptedFile if (!$fileInfo->getStorage()->instanceOfStorage(IHomeStorage::class)) { return false; } + if ($fileInfo->isEncrypted()) { /* Encrypted folder means e2ee encrypted, we cannot transfer it */ $encryptedFiles[] = $fileInfo; } + return true; } + $progress->advance(); if ($fileInfo->isEncrypted() && !$masterKeyEnabled) { /* Encrypted file means SSE, we can only transfer it if master key is enabled */ $encryptedFiles[] = $fileInfo; } + return true; }); } @@ -262,6 +268,7 @@ function (FileInfo $fileInfo) use ($progress, $masterKeyEnabled, &$encryptedFile /* Encrypted file means SSE, we can only transfer it if master key is enabled */ $encryptedFiles[] = $sourceFileInfo; } + $progress->finish(); $output->writeln(''); @@ -272,6 +279,7 @@ function (FileInfo $fileInfo) use ($progress, $masterKeyEnabled, &$encryptedFile /** @var FileInfo $encryptedFile */ $output->writeln(' ' . $encryptedFile->getPath()); } + throw new TransferOwnershipException('Some files are encrypted - please decrypt them first.', 1); } } @@ -312,6 +320,7 @@ private function collectUsersShares( if (empty($sharePage)) { break; } + if ($path !== "$sourceUid/files") { $sharePage = array_filter($sharePage, function (IShare $share) use ($view, $normalizedPath) { try { @@ -329,6 +338,7 @@ private function collectUsersShares( } }); } + $shares = array_merge($shares, $sharePage); $offset += 50; } @@ -359,6 +369,7 @@ private function collectIncomingShares(string $sourceUid, if (empty($sharePage)) { break; } + if ($addKeys) { foreach ($sharePage as $singleShare) { $shares[$singleShare->getNodeId()] = $singleShare; @@ -372,9 +383,9 @@ private function collectIncomingShares(string $sourceUid, $offset += 50; } - $progress->finish(); $output->writeln(''); + return $shares; } @@ -394,9 +405,11 @@ protected function transferFiles(string $sourceUid, $view->mkdir($finalTarget); $finalTarget = $finalTarget . '/' . basename($sourcePath); } + if ($view->rename($sourcePath, $finalTarget) === false) { throw new TransferOwnershipException('Could not transfer files.', 1); } + if (!is_dir("$sourceUid/files")) { // because the files folder is moved away we need to recreate it $view->mkdir("$sourceUid/files"); @@ -427,11 +440,13 @@ private function restoreShares( if ($shareMountPoint) { $this->mountManager->removeMount($shareMountPoint->getMountPoint()); } + $this->shareManager->deleteShare($share); } else { if ($share->getShareOwner() === $sourceUid) { $share->setShareOwner($destinationUid); } + if ($share->getSharedBy() === $sourceUid) { $share->setSharedBy($destinationUid); } @@ -458,6 +473,7 @@ private function restoreShares( $node = $rootFolder->get(Filesystem::normalizePath($targetLocation . '/' . $suffix)); $newNodeId = $node->getId(); } + $share->setNodeId($newNodeId); $this->shareManager->updateShare($share); @@ -468,8 +484,10 @@ private function restoreShares( } catch (\Throwable $e) { $output->writeln('Could not restore share with id ' . $share->getId() . ':' . $e->getMessage() . ' : ' . $e->getTraceAsString() . ''); } + $progress->advance(); } + $progress->finish(); $output->writeln(''); } @@ -489,6 +507,7 @@ private function transferIncomingShares(string $sourceUid, if (str_starts_with($finalTarget, $prefix)) { $finalShareTarget = substr($finalTarget, strlen($prefix)); } + foreach ($sourceShares as $share) { try { // Only restore if share is in given path. @@ -496,9 +515,11 @@ private function transferIncomingShares(string $sourceUid, if (trim($path, '/') !== '') { $pathToCheck = '/' . trim($path) . '/'; } + if (!str_starts_with($share->getTarget(), $pathToCheck)) { continue; } + $shareTarget = $share->getTarget(); $shareTarget = $finalShareTarget . $shareTarget; if ($share->getShareType() === IShare::TYPE_USER && @@ -520,10 +541,12 @@ private function transferIncomingShares(string $sourceUid, if ($move) { continue; } + $share->setTarget($shareTarget); $this->shareManager->moveShare($share, $destinationUid); continue; } + $this->shareManager->deleteShare($share); } elseif ($share->getShareOwner() === $destinationUid) { $this->shareManager->deleteShare($share); @@ -539,6 +562,7 @@ private function transferIncomingShares(string $sourceUid, if ($move) { continue; } + $share->setTarget($shareTarget); $this->shareManager->moveShare($share, $destinationUid); continue; @@ -548,8 +572,10 @@ private function transferIncomingShares(string $sourceUid, } catch (\Throwable $e) { $output->writeln('Could not restore share with id ' . $share->getId() . ':' . $e->getTraceAsString() . ''); } + $progress->advance(); } + $progress->finish(); $output->writeln(''); } diff --git a/apps/files/lib/Service/SettingsService.php b/apps/files/lib/Service/SettingsService.php index d07e907a5f690..cf11e9227ee9d 100644 --- a/apps/files/lib/Service/SettingsService.php +++ b/apps/files/lib/Service/SettingsService.php @@ -53,6 +53,7 @@ public function setFilesWindowsSupport(bool $enabled = true): void { $characters = array_unique(array_values(array_diff($this->filenameValidator->getForbiddenCharacters(), self::WINDOWS_CHARACTERS))); $extensions = array_unique(array_values(array_diff($this->filenameValidator->getForbiddenExtensions(), self::WINDOWS_EXTENSION))); } + $values = [ 'forbidden_filename_basenames' => empty($basenames) ? null : $basenames, 'forbidden_filename_characters' => empty($characters) ? null : $characters, diff --git a/apps/files/lib/Service/TagService.php b/apps/files/lib/Service/TagService.php index 4bb43145e4a12..ad91bf8071399 100644 --- a/apps/files/lib/Service/TagService.php +++ b/apps/files/lib/Service/TagService.php @@ -61,6 +61,7 @@ public function updateFileTags($path, $tags) { if ($this->tagger === null) { throw new \RuntimeException('No tagger set'); } + if ($this->homeFolder === null) { throw new \RuntimeException('No homeFolder set'); } @@ -78,13 +79,16 @@ public function updateFileTags($path, $tags) { if ($tag === ITags::TAG_FAVORITE) { $this->addActivity(true, $fileId, $path); } + $this->tagger->tagAs($fileId, $tag); } + $deletedTags = array_diff($currentTags, $tags); foreach ($deletedTags as $tag) { if ($tag === ITags::TAG_FAVORITE) { $this->addActivity(false, $fileId, $path); } + $this->tagger->unTag($fileId, $tag); } @@ -109,6 +113,7 @@ protected function addActivity($addToFavorite, $fileId, $path) { } else { $event = new NodeRemovedFromFavorite($user, $fileId, $path); } + $this->dispatcher->dispatchTyped($event); $event = $this->activityManager->generateEvent(); diff --git a/apps/files/lib/Service/UserConfig.php b/apps/files/lib/Service/UserConfig.php index c233996579379..aa99f49b1e82f 100644 --- a/apps/files/lib/Service/UserConfig.php +++ b/apps/files/lib/Service/UserConfig.php @@ -80,6 +80,7 @@ private function getAllowedConfigValues(string $key): array { return $config['allowed']; } } + return []; } @@ -95,6 +96,7 @@ private function getDefaultConfigValue(string $key) { return $config['default']; } } + return ''; } @@ -143,6 +145,7 @@ public function getConfigs(): array { if (is_bool($this->getDefaultConfigValue($key)) && is_string($value)) { return $value === '1'; } + return $value; }, $this->getAllowedConfigKeys()); diff --git a/apps/files/lib/Service/ViewConfig.php b/apps/files/lib/Service/ViewConfig.php index f28bf9cd3c97a..eced5795ec127 100644 --- a/apps/files/lib/Service/ViewConfig.php +++ b/apps/files/lib/Service/ViewConfig.php @@ -64,6 +64,7 @@ private function getAllowedConfigValues(string $key): array { return $config['allowed'] ?? []; } } + return []; } @@ -79,6 +80,7 @@ private function getDefaultConfigValue(string $key) { return $config['default']; } } + return ''; } @@ -141,6 +143,7 @@ public function getConfig(string $view): array { return array_reduce(self::ALLOWED_CONFIGS, function ($carry, $config) use ($view, $configs) { $key = $config['key']; $carry[$key] = $configs[$view][$key] ?? $this->getDefaultConfigValue($key); + return $carry; }, []); } diff --git a/apps/files/templates/list.php b/apps/files/templates/list.php index 3dbcf5ae348ab..966a1b272f98d 100644 --- a/apps/files/templates/list.php +++ b/apps/files/templates/list.php @@ -23,7 +23,9 @@ - + diff --git a/apps/files/tests/Activity/ProviderTest.php b/apps/files/tests/Activity/ProviderTest.php index 73c726dac3c02..9decbef4e50fe 100644 --- a/apps/files/tests/Activity/ProviderTest.php +++ b/apps/files/tests/Activity/ProviderTest.php @@ -76,6 +76,7 @@ protected function getProvider(array $methods = []) { ->setMethods($methods) ->getMock(); } + return new Provider( $this->l10nFactory, $this->url, @@ -163,6 +164,7 @@ public function testGetUser(string $uid, ?string $userDisplayName, ?array $cloud ->with($uid) ->willReturn($userDisplayName); } + if ($cloudIdData !== null) { $this->cloudIdManager->expects($this->once()) ->method('isValidCloudId') diff --git a/apps/files/tests/BackgroundJob/ScanFilesTest.php b/apps/files/tests/BackgroundJob/ScanFilesTest.php index 0fb5163143f81..fdd674955eb33 100644 --- a/apps/files/tests/BackgroundJob/ScanFilesTest.php +++ b/apps/files/tests/BackgroundJob/ScanFilesTest.php @@ -63,6 +63,7 @@ private function getUser(string $userId): IUser { $user = $this->createMock(IUser::class); $user->method('getUID') ->willReturn($userId); + return $user; } diff --git a/apps/files/tests/Command/DeleteOrphanedFilesTest.php b/apps/files/tests/Command/DeleteOrphanedFilesTest.php index 1205e204ec47c..550da99c55f2c 100644 --- a/apps/files/tests/Command/DeleteOrphanedFilesTest.php +++ b/apps/files/tests/Command/DeleteOrphanedFilesTest.php @@ -68,6 +68,7 @@ protected function getFile($fileId) { $query->select('*') ->from('filecache') ->where($query->expr()->eq('fileid', $query->createNamedParameter($fileId))); + return $query->executeQuery()->fetchAll(); } @@ -76,6 +77,7 @@ protected function getMounts($storageId) { $query->select('*') ->from('mounts') ->where($query->expr()->eq('storage_id', $query->createNamedParameter($storageId))); + return $query->executeQuery()->fetchAll(); } diff --git a/apps/files/tests/HelperTest.php b/apps/files/tests/HelperTest.php index fdcef8f7125c0..ebea571473911 100644 --- a/apps/files/tests/HelperTest.php +++ b/apps/files/tests/HelperTest.php @@ -78,12 +78,14 @@ public function testSortByName(string $sort, bool $sortDescending, array $expect if (($sort === 'mtime') && (PHP_INT_SIZE < 8)) { $this->markTestSkipped('Skip mtime sorting on 32bit'); } + $files = self::getTestFileList(); $files = \OCA\Files\Helper::sortFiles($files, $sort, $sortDescending); $fileNames = []; foreach ($files as $fileInfo) { $fileNames[] = $fileInfo->getName(); } + $this->assertEquals( $expectedOrder, $fileNames diff --git a/apps/files/tests/Service/TagServiceTest.php b/apps/files/tests/Service/TagServiceTest.php index 7922c45a63983..714acf1562e87 100644 --- a/apps/files/tests/Service/TagServiceTest.php +++ b/apps/files/tests/Service/TagServiceTest.php @@ -137,6 +137,7 @@ public function testUpdateFileTags(): void { } catch (\OCP\Files\NotFoundException $e) { $caught = true; } + $this->assertTrue($caught); $subdir->delete(); diff --git a/apps/files_external/ajax/applicable.php b/apps/files_external/ajax/applicable.php index c1b7748ad15d5..b81089ee1e434 100644 --- a/apps/files_external/ajax/applicable.php +++ b/apps/files_external/ajax/applicable.php @@ -15,9 +15,11 @@ if (isset($_GET['pattern'])) { $pattern = (string)$_GET['pattern']; } + if (isset($_GET['limit'])) { $limit = (int)$_GET['limit']; } + if (isset($_GET['offset'])) { $offset = (int)$_GET['offset']; } diff --git a/apps/files_external/lib/BackgroundJob/CredentialsCleanup.php b/apps/files_external/lib/BackgroundJob/CredentialsCleanup.php index 1ae80735972c8..5fd233d232e55 100644 --- a/apps/files_external/lib/BackgroundJob/CredentialsCleanup.php +++ b/apps/files_external/lib/BackgroundJob/CredentialsCleanup.php @@ -27,7 +27,7 @@ public function __construct( ITimeFactory $time, ICredentialsManager $credentialsManager, UserGlobalStoragesService $userGlobalStoragesService, - IUserManager $userManager + IUserManager $userManager, ) { parent::__construct($time); diff --git a/apps/files_external/lib/Command/Applicable.php b/apps/files_external/lib/Command/Applicable.php index e7305f5867181..9e4b4aee4d368 100644 --- a/apps/files_external/lib/Command/Applicable.php +++ b/apps/files_external/lib/Command/Applicable.php @@ -93,6 +93,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int return Response::HTTP_NOT_FOUND; } } + foreach ($addGroups as $addGroup) { if (!$this->groupManager->groupExists($addGroup)) { $output->writeln('Group "' . $addGroup . '" not found'); @@ -109,6 +110,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $applicableGroups = array_unique(array_merge($applicableGroups, $addGroups)); $applicableGroups = array_values(array_diff($applicableGroups, $removeGroups)); } + $mount->setApplicableUsers($applicableUsers); $mount->setApplicableGroups($applicableGroups); $this->globalService->updateStorage($mount); @@ -118,6 +120,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int 'users' => $applicableUsers, 'groups' => $applicableGroups ]); + return self::SUCCESS; } } diff --git a/apps/files_external/lib/Command/Backends.php b/apps/files_external/lib/Command/Backends.php index c2d844dc78117..e7d007d4dc6bb 100644 --- a/apps/files_external/lib/Command/Backends.php +++ b/apps/files_external/lib/Command/Backends.php @@ -54,6 +54,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $output->writeln('Invalid type "' . $type . '". Possible values are "authentication" or "storage"'); return self::FAILURE; } + $data = $data[$type]; if ($backend) { @@ -61,11 +62,13 @@ protected function execute(InputInterface $input, OutputInterface $output): int $output->writeln('Unknown backend "' . $backend . '" of type "' . $type . '"'); return self::FAILURE; } + $data = $data[$backend]; } } $this->writeArrayInOutputFormat($input, $output, $data); + return self::SUCCESS; } @@ -85,6 +88,7 @@ private function serializeAuthBackend(\JsonSerializable $backend): array { }, $authBackends); $result['authentication_configuration'] = array_combine(array_keys($authBackends), $authConfig); } + return $result; } @@ -96,6 +100,7 @@ private function formatConfiguration(array $parameters): array { $configuration = array_filter($parameters, function (DefinitionParameter $parameter) { return $parameter->getType() !== DefinitionParameter::VALUE_HIDDEN; }); + return array_map(function (DefinitionParameter $parameter) { return $parameter->getTypeName(); }, $configuration); diff --git a/apps/files_external/lib/Command/Config.php b/apps/files_external/lib/Command/Config.php index fa44a71785988..f86f69cf1e0d4 100644 --- a/apps/files_external/lib/Command/Config.php +++ b/apps/files_external/lib/Command/Config.php @@ -58,6 +58,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int } else { $this->getOption($mount, $key, $output); } + return self::SUCCESS; } @@ -70,9 +71,11 @@ protected function getOption(StorageConfig $mount, $key, OutputInterface $output } else { $value = $mount->getBackendOption($key); } + if (!is_string($value) && json_decode(json_encode($value)) === $value) { // show bools and objects correctly $value = json_encode($value); } + $output->writeln($value); } @@ -85,11 +88,13 @@ protected function setOption(StorageConfig $mount, $key, $value, OutputInterface if (!is_null($decoded) && json_encode($decoded) === $value) { $value = $decoded; } + if ($key === 'mountpoint' || $key === 'mount_point') { $mount->setMountPoint($value); } else { $mount->setBackendOption($key, $value); } + $this->globalService->updateStorage($mount); } } diff --git a/apps/files_external/lib/Command/Create.php b/apps/files_external/lib/Command/Create.php index 0183b3014c8d1..c4847cec61ff5 100644 --- a/apps/files_external/lib/Command/Create.php +++ b/apps/files_external/lib/Command/Create.php @@ -91,14 +91,17 @@ protected function execute(InputInterface $input, OutputInterface $output): int $output->writeln('Invalid mountpoint "' . $mountPoint . '"'); return self::FAILURE; } + if (is_null($storageBackend)) { $output->writeln('Storage backend with identifier "' . $storageIdentifier . '" not found (see `occ files_external:backends` for possible values)'); return Response::HTTP_NOT_FOUND; } + if (is_null($authBackend)) { $output->writeln('Authentication backend with identifier "' . $authIdentifier . '" not found (see `occ files_external:backends` for possible values)'); return Response::HTTP_NOT_FOUND; } + $supportedSchemes = array_keys($storageBackend->getAuthSchemes()); if (!in_array($authBackend->getScheme(), $supportedSchemes)) { $output->writeln('Authentication backend "' . $authIdentifier . '" not valid for storage backend "' . $storageIdentifier . '" (see `occ files_external:backends storage ' . $storageIdentifier . '` for possible values)'); @@ -111,11 +114,13 @@ protected function execute(InputInterface $input, OutputInterface $output): int $output->writeln('Invalid mount configuration option "' . $configOption . '"'); return self::FAILURE; } + [$key, $value] = explode('=', $configOption, 2); if (!$this->validateParam($key, $value, $storageBackend, $authBackend)) { $output->writeln('Unknown configuration for backends "' . $key . '"'); return self::FAILURE; } + $config[$key] = $value; } @@ -130,6 +135,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $output->writeln('User "' . $user . '" not found'); return self::FAILURE; } + $mount->setApplicableUsers([$user]); } @@ -143,6 +149,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $output->writeln((string)$mount->getId()); } } + return self::SUCCESS; } @@ -154,9 +161,11 @@ private function validateParam(string $key, &$value, Backend $storageBackend, Au if ($param->getType() === DefinitionParameter::VALUE_BOOLEAN) { $value = ($value === 'true'); } + return true; } } + return false; } @@ -177,7 +186,9 @@ protected function getStorageService(string $userId): StoragesService { if (is_null($user)) { throw new NoUserException("user $userId not found"); } + $this->userSession->setUser($user); + return $this->userService; } } diff --git a/apps/files_external/lib/Command/Delete.php b/apps/files_external/lib/Command/Delete.php index 077b969d7b269..23a42594da731 100644 --- a/apps/files_external/lib/Command/Delete.php +++ b/apps/files_external/lib/Command/Delete.php @@ -73,6 +73,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int } $this->globalService->removeStorage($mountId); + return self::SUCCESS; } } diff --git a/apps/files_external/lib/Command/Export.php b/apps/files_external/lib/Command/Export.php index 0735d94d89d60..60972131ffd51 100644 --- a/apps/files_external/lib/Command/Export.php +++ b/apps/files_external/lib/Command/Export.php @@ -38,6 +38,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $listInput->setOption('show-password', true); $listInput->setOption('full', true); $listCommand->execute($listInput, $output); + return self::SUCCESS; } } diff --git a/apps/files_external/lib/Command/Import.php b/apps/files_external/lib/Command/Import.php index bf36bf5d5c191..6ca3ccd64a405 100644 --- a/apps/files_external/lib/Command/Import.php +++ b/apps/files_external/lib/Command/Import.php @@ -68,12 +68,15 @@ protected function execute(InputInterface $input, OutputInterface $output): int $output->writeln('File not found: ' . $path . ''); return self::FAILURE; } + $json = file_get_contents($path); } + if (!is_string($json) || strlen($json) < 2) { $output->writeln('Error while reading json'); return self::FAILURE; } + $data = json_decode($json, true); if (!is_array($data)) { $output->writeln('Error while parsing json'); @@ -94,6 +97,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int if (!isset($data[0])) { //normalize to an array of mounts $data = [$data]; } + $mounts = array_map([$this, 'parseData'], $data); } @@ -128,6 +132,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $output->writeln('No mounts to be imported'); return self::FAILURE; } + $listCommand = new ListCommand($this->globalService, $this->userService, $this->userSession, $this->userManager); $listInput = new ArrayInput([], $listCommand->getDefinition()); $listInput->setOption('output', $input->getOption('output')); @@ -138,6 +143,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $storageService->addStorage($mount); } } + return self::SUCCESS; } @@ -151,6 +157,7 @@ private function parseData(array $data): StorageConfig { $mount->setMountOptions($data['options']); $mount->setApplicableUsers($data['applicable_users'] ?? []); $mount->setApplicableGroups($data['applicable_groups'] ?? []); + return $mount; } @@ -172,7 +179,9 @@ protected function getStorageService(string $userId): StoragesService { if (is_null($user)) { throw new NoUserException("user $userId not found"); } + $this->userSession->setUser($user); + return $this->userService; } } diff --git a/apps/files_external/lib/Command/ListCommand.php b/apps/files_external/lib/Command/ListCommand.php index 84c1b569e9dd3..167ece1b6a671 100644 --- a/apps/files_external/lib/Command/ListCommand.php +++ b/apps/files_external/lib/Command/ListCommand.php @@ -71,6 +71,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int } $this->listMounts($userId, $mounts, $input, $output); + return self::SUCCESS; } @@ -92,6 +93,7 @@ public function listMounts($userId, array $mounts, InputInterface $input, Output $output->writeln('No admin mounts configured'); } } + return; } @@ -101,6 +103,7 @@ public function listMounts($userId, array $mounts, InputInterface $input, Output $headers[] = 'Applicable Users'; $headers[] = 'Applicable Groups'; } + if ($userId === self::ALL) { $headers[] = 'Type'; } @@ -135,6 +138,7 @@ public function listMounts($userId, array $mounts, InputInterface $input, Output $values[] = $config->getApplicableUsers(); $values[] = $config->getApplicableGroups(); } + if ($userId === self::ALL) { $values[] = $config->getType() === StorageConfig::MOUNT_TYPE_ADMIN ? 'admin' : 'personal'; } @@ -183,6 +187,7 @@ public function listMounts($userId, array $mounts, InputInterface $input, Output unset($mountOptions[$key]); } } + $keys = array_keys($mountOptions); $values = array_values($mountOptions); @@ -206,9 +211,11 @@ public function listMounts($userId, array $mounts, InputInterface $input, Output if ($applicableUsers === '' && $applicableGroups === '') { $applicableUsers = 'All'; } + $values[] = $applicableUsers; $values[] = $applicableGroups; } + if ($userId === self::ALL) { $values[] = $config->getType() === StorageConfig::MOUNT_TYPE_ADMIN ? 'Admin' : 'Personal'; } @@ -232,7 +239,9 @@ protected function getStorageService(string $userId): StoragesService { if (is_null($user)) { throw new NoUserException("user $userId not found"); } + $this->userSession->setUser($user); + return $this->userService; } } diff --git a/apps/files_external/lib/Command/Notify.php b/apps/files_external/lib/Command/Notify.php index 09be70ecd16fc..adeba0f9d213b 100644 --- a/apps/files_external/lib/Command/Notify.php +++ b/apps/files_external/lib/Command/Notify.php @@ -100,8 +100,10 @@ protected function execute(InputInterface $input, OutputInterface $output): int if ($change instanceof IRenameChange) { $this->markParentAsOutdated($mount->getId(), $change->getTargetPath(), $output, $dryRun); } + $this->markParentAsOutdated($mount->getId(), $change->getPath(), $output, $dryRun); }); + return self::SUCCESS; } @@ -119,6 +121,7 @@ private function markParentAsOutdated($mountId, $path, OutputInterface $output, $output->writeln('Needed to reconnect to the database'); $storages = $this->getStorageIds($mountId, $path); } + if (count($storages) === 0) { $output->writeln(" no users found with access to '$parent', skipping", OutputInterface::VERBOSITY_VERBOSE); return; @@ -170,6 +173,7 @@ private function logUpdate(IChange $change, OutputInterface $output): void { private function getStorageIds(int $mountId, string $path): array { $pathHash = md5(trim((string)\OC_Util::normalizeUnicode($path), '/')); $qb = $this->connection->getQueryBuilder(); + return $qb ->select('storage_id', 'user_id') ->from('mounts', 'm') @@ -183,6 +187,7 @@ private function getStorageIds(int $mountId, string $path): array { private function updateParent(array $storageIds, string $parent): int { $pathHash = md5(trim((string)\OC_Util::normalizeUnicode($parent), '/')); $qb = $this->connection->getQueryBuilder(); + return $qb ->update('filecache') ->set('size', $qb->createNamedParameter(-1, IQueryBuilder::PARAM_INT)) @@ -198,6 +203,7 @@ private function reconnectToDatabase(IDBConnection $connection, OutputInterface $this->logger->warning('Error while disconnecting from DB', ['exception' => $ex]); $output->writeln("Error while disconnecting from database: {$ex->getMessage()}"); } + $connected = false; while (!$connected) { try { @@ -208,6 +214,7 @@ private function reconnectToDatabase(IDBConnection $connection, OutputInterface sleep(60); } } + return $connection; } @@ -218,6 +225,7 @@ private function selfTest(IStorage $storage, INotifyHandler $notifyHandler, Outp $output->writeln('Failed to create test file for self-test'); return; } + $storage->mkdir('/.nc_test_folder'); $storage->file_put_contents('/.nc_test_folder/subfile.txt', 'test content'); diff --git a/apps/files_external/lib/Command/Option.php b/apps/files_external/lib/Command/Option.php index 6b679f1d6f7ba..2d37b5d69aff4 100644 --- a/apps/files_external/lib/Command/Option.php +++ b/apps/files_external/lib/Command/Option.php @@ -38,6 +38,7 @@ protected function getOption(StorageConfig $mount, $key, OutputInterface $output if (!is_string($value)) { // show bools and objects correctly $value = json_encode($value); } + $output->writeln($value); } @@ -50,6 +51,7 @@ protected function setOption(StorageConfig $mount, $key, $value, OutputInterface if (!is_null($decoded)) { $value = $decoded; } + $mount->setMountOption($key, $value); $this->globalService->updateStorage($mount); } diff --git a/apps/files_external/lib/Command/Scan.php b/apps/files_external/lib/Command/Scan.php index 575ee5989f543..65013229c9331 100644 --- a/apps/files_external/lib/Command/Scan.php +++ b/apps/files_external/lib/Command/Scan.php @@ -24,7 +24,7 @@ class Scan extends StorageAuthBase { public function __construct( GlobalStoragesService $globalService, - IUserManager $userManager + IUserManager $userManager, ) { parent::__construct($globalService, $userManager); } @@ -119,6 +119,7 @@ protected function showSummary(array $headers, array $rows, OutputInterface $out $niceDate, ]; } + $table = new Table($output); $table ->setHeaders($headers) diff --git a/apps/files_external/lib/Command/StorageAuthBase.php b/apps/files_external/lib/Command/StorageAuthBase.php index 141a96d43bdcf..4dceddb401109 100644 --- a/apps/files_external/lib/Command/StorageAuthBase.php +++ b/apps/files_external/lib/Command/StorageAuthBase.php @@ -57,10 +57,12 @@ protected function createStorage(InputInterface $input, OutputInterface $output) $output->writeln('Mount not found'); return [null, null]; } + if (is_null($mount)) { $output->writeln('Mount not found'); return [null, null]; } + $noAuth = false; $userOption = $this->getUserOption($input); @@ -82,6 +84,7 @@ protected function createStorage(InputInterface $input, OutputInterface $output) if ($userOption) { $mount->setBackendOption('user', $userOption); } + if ($passwordOption) { $mount->setBackendOption('password', $passwordOption); } @@ -102,12 +105,14 @@ protected function createStorage(InputInterface $input, OutputInterface $output) if (!$storage->test()) { throw new \Exception(); } + return [$mount, $storage]; } catch (\Exception $e) { $output->writeln('Error while trying to create storage'); if ($noAuth) { $output->writeln('Username and/or password required'); } + return [null, null]; } } diff --git a/apps/files_external/lib/Command/Verify.php b/apps/files_external/lib/Command/Verify.php index 25b09fa111602..312603bf41da8 100644 --- a/apps/files_external/lib/Command/Verify.php +++ b/apps/files_external/lib/Command/Verify.php @@ -60,6 +60,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int 'code' => $mount->getStatus(), 'message' => $mount->getStatusMessage() ]); + return self::SUCCESS; } @@ -85,6 +86,7 @@ private function updateStorageStatus(StorageConfig &$storage, $configInput, Outp $output->writeln('Invalid mount configuration option "' . $configOption . '"'); return; } + [$key, $value] = explode('=', $configOption, 2); $storage->setBackendOption($key, $value); } diff --git a/apps/files_external/lib/Config/ConfigAdapter.php b/apps/files_external/lib/Config/ConfigAdapter.php index d0437432427ac..d05ead69b405a 100644 --- a/apps/files_external/lib/Config/ConfigAdapter.php +++ b/apps/files_external/lib/Config/ConfigAdapter.php @@ -48,6 +48,7 @@ private function prepareStorageConfig(StorageConfig &$storage, IUser $user): voi if (!is_subclass_of($objectClass, IObjectStore::class)) { throw new \InvalidArgumentException('Invalid object store'); } + $storage->setBackendOption('objectstore', new $objectClass($objectStore)); } @@ -109,6 +110,7 @@ public function getMountsForUser(IUser $user, IStorageFactory $loader) { // propagate exception into filesystem $storage = new FailedStorage(['exception' => $e]); } + return $storage; }, $storages, $storageConfigs); diff --git a/apps/files_external/lib/Config/SimpleSubstitutionTrait.php b/apps/files_external/lib/Config/SimpleSubstitutionTrait.php index 21269d7526ead..164e4a56d7a28 100644 --- a/apps/files_external/lib/Config/SimpleSubstitutionTrait.php +++ b/apps/files_external/lib/Config/SimpleSubstitutionTrait.php @@ -36,6 +36,7 @@ private function processInput($optionValue, string $replacement) { } else { $optionValue = $this->substituteIfString($optionValue, $replacement); } + return $optionValue; } @@ -49,6 +50,7 @@ protected function checkPlaceholder(): void { 'Invalid placeholder %s, only [a-z0-9] are allowed', $this->sanitizedPlaceholder )); } + if ($this->sanitizedPlaceholder === '') { throw new \RuntimeException('Invalid empty placeholder'); } @@ -63,6 +65,7 @@ protected function substituteIfString($value, string $replacement) { if (is_string($value)) { return str_ireplace('$' . $this->sanitizedPlaceholder, $replacement, $value); } + return $value; } } diff --git a/apps/files_external/lib/Config/UserContext.php b/apps/files_external/lib/Config/UserContext.php index 5d9d2910ea25d..00605638a321f 100644 --- a/apps/files_external/lib/Config/UserContext.php +++ b/apps/files_external/lib/Config/UserContext.php @@ -48,12 +48,15 @@ protected function getUserId(): ?string { if ($this->userId !== null) { return $this->userId; } + if ($this->session && $this->session->getUser() !== null) { return $this->session->getUser()->getUID(); } + try { $shareToken = $this->request->getParam('token'); $share = $this->shareManager->getShareByToken($shareToken); + return $share->getShareOwner(); } catch (ShareNotFound $e) { } @@ -66,6 +69,7 @@ protected function getUser(): ?IUser { if ($userId !== null) { return $this->userManager->get($userId); } + return null; } } diff --git a/apps/files_external/lib/Config/UserPlaceholderHandler.php b/apps/files_external/lib/Config/UserPlaceholderHandler.php index ec91df5fb7a08..7a954f50cccad 100644 --- a/apps/files_external/lib/Config/UserPlaceholderHandler.php +++ b/apps/files_external/lib/Config/UserPlaceholderHandler.php @@ -19,6 +19,7 @@ public function handle($optionValue) { if ($uid === null) { return $optionValue; } + return $this->processInput($optionValue, $uid); } } diff --git a/apps/files_external/lib/Controller/ApiController.php b/apps/files_external/lib/Controller/ApiController.php index 10fd120c3d92f..d46e04ca566ea 100644 --- a/apps/files_external/lib/Controller/ApiController.php +++ b/apps/files_external/lib/Controller/ApiController.php @@ -31,7 +31,7 @@ public function __construct( string $appName, IRequest $request, UserGlobalStoragesService $userGlobalStorageService, - UserStoragesService $userStorageService + UserStoragesService $userStorageService, ) { parent::__construct($appName, $request); $this->userGlobalStoragesService = $userGlobalStorageService; @@ -72,6 +72,7 @@ private function formatMount(string $mountPoint, StorageConfig $mountConfig): ar 'class' => $mountConfig->getBackend()->getIdentifier(), 'config' => $mountConfig->jsonSerialize(true), ]; + return $entry; } @@ -96,6 +97,7 @@ public function getUserMounts(): DataResponse { $mountPoint = $storage->getMountPoint(); $mountPoints[$mountPoint] = $storage; } + foreach ($mountPoints as $mountPoint => $mount) { $entries[] = $this->formatMount($mountPoint, $mount); } diff --git a/apps/files_external/lib/Controller/GlobalStoragesController.php b/apps/files_external/lib/Controller/GlobalStoragesController.php index d773f3ea5e245..ee3341187329a 100644 --- a/apps/files_external/lib/Controller/GlobalStoragesController.php +++ b/apps/files_external/lib/Controller/GlobalStoragesController.php @@ -41,7 +41,7 @@ public function __construct( LoggerInterface $logger, IUserSession $userSession, IGroupManager $groupManager, - IConfig $config + IConfig $config, ) { parent::__construct( $AppName, @@ -77,7 +77,7 @@ public function create( $mountOptions, $applicableUsers, $applicableGroups, - $priority + $priority, ) { $canCreateNewLocalStorage = $this->config->getSystemValue('files_external_allow_create_new_local', true); if (!$canCreateNewLocalStorage && $backend === 'local') { @@ -144,7 +144,7 @@ public function update( $applicableUsers, $applicableGroups, $priority, - $testOnly = true + $testOnly = true, ) { $storage = $this->createStorage( $mountPoint, @@ -159,6 +159,7 @@ public function update( if ($storage instanceof DataResponse) { return $storage; } + $storage->setId($id); $response = $this->validate($storage); diff --git a/apps/files_external/lib/Controller/StoragesController.php b/apps/files_external/lib/Controller/StoragesController.php index ab580987b0e74..ac51dc47533f0 100644 --- a/apps/files_external/lib/Controller/StoragesController.php +++ b/apps/files_external/lib/Controller/StoragesController.php @@ -44,7 +44,7 @@ public function __construct( protected LoggerInterface $logger, protected IUserSession $userSession, protected IGroupManager $groupManager, - protected IConfig $config + protected IConfig $config, ) { parent::__construct($AppName, $request); } @@ -71,7 +71,7 @@ protected function createStorage( $mountOptions = null, $applicableUsers = null, $applicableGroups = null, - $priority = null + $priority = null, ) { $canCreateNewLocalStorage = $this->config->getSystemValue('files_external_allow_create_new_local', true); if (!$canCreateNewLocalStorage && $backend === 'local') { @@ -160,6 +160,7 @@ protected function validate(StorageConfig $storage) { Http::STATUS_UNPROCESSABLE_ENTITY ); } + if (!$authMechanism->isVisibleFor($this->service->getVisibilityType())) { // not permitted to use auth mechanism return new DataResponse( @@ -181,6 +182,7 @@ protected function validate(StorageConfig $storage) { Http::STATUS_UNPROCESSABLE_ENTITY ); } + if (!$authMechanism->validateStorage($storage)) { // unsatisfied parameters return new DataResponse( diff --git a/apps/files_external/lib/Controller/UserGlobalStoragesController.php b/apps/files_external/lib/Controller/UserGlobalStoragesController.php index 3d364fff57dc5..b4cb0d4152249 100644 --- a/apps/files_external/lib/Controller/UserGlobalStoragesController.php +++ b/apps/files_external/lib/Controller/UserGlobalStoragesController.php @@ -47,7 +47,7 @@ public function __construct( LoggerInterface $logger, IUserSession $userSession, IGroupManager $groupManager, - IConfig $config + IConfig $config, ) { parent::__construct( $AppName, @@ -73,6 +73,7 @@ public function index() { $storages = array_map(function ($storage) { // remove configuration data, this must be kept private $this->sanitizeStorage($storage); + return $storage->jsonSerialize(true); }, $service->getUniqueStorages()); @@ -139,7 +140,7 @@ public function show($id, $testOnly = true) { public function update( $id, $backendOptions, - $testOnly = true + $testOnly = true, ) { try { $storage = $this->service->getStorage($id); diff --git a/apps/files_external/lib/Controller/UserStoragesController.php b/apps/files_external/lib/Controller/UserStoragesController.php index a85aa3faa96f3..87d2461276952 100644 --- a/apps/files_external/lib/Controller/UserStoragesController.php +++ b/apps/files_external/lib/Controller/UserStoragesController.php @@ -44,7 +44,7 @@ public function __construct( LoggerInterface $logger, IUserSession $userSession, IGroupManager $groupManager, - IConfig $config + IConfig $config, ) { parent::__construct( $AppName, @@ -104,7 +104,7 @@ public function create( $backend, $authMechanism, $backendOptions, - $mountOptions + $mountOptions, ) { $canCreateNewLocalStorage = $this->config->getSystemValue('files_external_allow_create_new_local', true); if (!$canCreateNewLocalStorage && $backend === 'local') { @@ -115,6 +115,7 @@ public function create( Http::STATUS_FORBIDDEN ); } + $newStorage = $this->createStorage( $mountPoint, $backend, @@ -161,7 +162,7 @@ public function update( $authMechanism, $backendOptions, $mountOptions, - $testOnly = true + $testOnly = true, ) { $storage = $this->createStorage( $mountPoint, @@ -173,6 +174,7 @@ public function update( if ($storage instanceof DataResponse) { return $storage; } + $storage->setId($id); $response = $this->validate($storage); diff --git a/apps/files_external/lib/Lib/Auth/Password/GlobalAuth.php b/apps/files_external/lib/Lib/Auth/Password/GlobalAuth.php index ca1c9ca2beeef..81170bfd91ee0 100644 --- a/apps/files_external/lib/Lib/Auth/Password/GlobalAuth.php +++ b/apps/files_external/lib/Lib/Auth/Password/GlobalAuth.php @@ -63,6 +63,7 @@ public function manipulateStorageConfig(StorageConfig &$storage, ?IUser $user = } else { $uid = $user->getUID(); } + $credentials = $this->credentialsManager->retrieve($uid, self::CREDENTIALS_IDENTIFIER); if (is_array($credentials)) { diff --git a/apps/files_external/lib/Lib/Auth/Password/LoginCredentials.php b/apps/files_external/lib/Lib/Auth/Password/LoginCredentials.php index ea42b0275f71f..5aa97cce1cd65 100644 --- a/apps/files_external/lib/Lib/Auth/Password/LoginCredentials.php +++ b/apps/files_external/lib/Lib/Auth/Password/LoginCredentials.php @@ -46,7 +46,7 @@ public function __construct( ICredentialsManager $credentialsManager, CredentialsStore $credentialsStore, IEventDispatcher $eventDispatcher, - ILDAPProviderFactory $ldapFactory + ILDAPProviderFactory $ldapFactory, ) { $this->session = $session; $this->credentialsManager = $credentialsManager; @@ -98,6 +98,7 @@ public function manipulateStorageConfig(StorageConfig &$storage, ?IUser $user = if (!isset($user)) { throw new InsufficientDataForMeaningfulAnswerException('No login credentials saved'); } + $credentials = $this->getCredentials($user); $loginKey = $storage->getBackendOption('login_ldap_attr'); @@ -108,6 +109,7 @@ public function manipulateStorageConfig(StorageConfig &$storage, ?IUser $user = if ($value === null) { throw new InsufficientDataForMeaningfulAnswerException('Custom ldap attribute not set for user ' . $user->getUID()); } + $storage->setBackendOption('user', $value); } else { throw new InsufficientDataForMeaningfulAnswerException('Custom ldap attribute configured but user ' . $user->getUID() . ' is not an ldap user'); @@ -115,6 +117,7 @@ public function manipulateStorageConfig(StorageConfig &$storage, ?IUser $user = } else { $storage->setBackendOption('user', $credentials['user']); } + $storage->setBackendOption('password', $credentials['password']); } diff --git a/apps/files_external/lib/Lib/Auth/Password/UserProvided.php b/apps/files_external/lib/Lib/Auth/Password/UserProvided.php index a7c51d7353a83..bed4e60bb9c47 100644 --- a/apps/files_external/lib/Lib/Auth/Password/UserProvided.php +++ b/apps/files_external/lib/Lib/Auth/Password/UserProvided.php @@ -65,6 +65,7 @@ public function manipulateStorageConfig(StorageConfig &$storage, ?IUser $user = if (!isset($user)) { throw new InsufficientDataForMeaningfulAnswerException('No credentials saved'); } + $uid = $user->getUID(); $credentials = $this->credentialsManager->retrieve($uid, $this->getCredentialsIdentifier($storage->getId())); diff --git a/apps/files_external/lib/Lib/Auth/PublicKey/RSA.php b/apps/files_external/lib/Lib/Auth/PublicKey/RSA.php index 9b47cf72bb733..9340101eeed6f 100644 --- a/apps/files_external/lib/Lib/Auth/PublicKey/RSA.php +++ b/apps/files_external/lib/Lib/Auth/PublicKey/RSA.php @@ -52,6 +52,7 @@ public function manipulateStorageConfig(StorageConfig &$storage, ?IUser $user = throw new \RuntimeException('unable to load private key'); } } + $storage->setBackendOption('public_key_auth', $auth); } diff --git a/apps/files_external/lib/Lib/Auth/PublicKey/RSAPrivateKey.php b/apps/files_external/lib/Lib/Auth/PublicKey/RSAPrivateKey.php index ab770d25d0999..98ba12671970c 100644 --- a/apps/files_external/lib/Lib/Auth/PublicKey/RSAPrivateKey.php +++ b/apps/files_external/lib/Lib/Auth/PublicKey/RSAPrivateKey.php @@ -50,6 +50,7 @@ public function manipulateStorageConfig(StorageConfig &$storage, ?IUser $user = throw new \RuntimeException('unable to load private key'); } } + $storage->setBackendOption('public_key_auth', $auth); } } diff --git a/apps/files_external/lib/Lib/Backend/Backend.php b/apps/files_external/lib/Lib/Backend/Backend.php index f965e246d4232..415c0f166aa82 100644 --- a/apps/files_external/lib/Lib/Backend/Backend.php +++ b/apps/files_external/lib/Lib/Backend/Backend.php @@ -82,6 +82,7 @@ public function getAuthSchemes() { if (empty($this->authSchemes)) { return [AuthMechanism::SCHEME_NULL => true]; } + return $this->authSchemes; } @@ -102,6 +103,7 @@ public function getLegacyAuthMechanism(array $parameters = []) { if (is_callable($this->legacyAuthMechanism)) { return call_user_func($this->legacyAuthMechanism, $parameters); } + return $this->legacyAuthMechanism; } diff --git a/apps/files_external/lib/Lib/Backend/LegacyBackend.php b/apps/files_external/lib/Lib/Backend/LegacyBackend.php index 11396913fbd42..7b4913a348d88 100644 --- a/apps/files_external/lib/Lib/Backend/LegacyBackend.php +++ b/apps/files_external/lib/Lib/Backend/LegacyBackend.php @@ -43,6 +43,7 @@ public function __construct($class, array $definition, Builtin $authMechanism) { $flags = DefinitionParameter::FLAG_OPTIONAL; $placeholder = substr($placeholder, 1); } + switch ($placeholder[0]) { case '!': $type = DefinitionParameter::VALUE_BOOLEAN; @@ -57,6 +58,7 @@ public function __construct($class, array $definition, Builtin $authMechanism) { $placeholder = substr($placeholder, 1); break; } + $this->addParameter((new DefinitionParameter($name, $placeholder)) ->setType($type) ->setFlags($flags) @@ -66,9 +68,11 @@ public function __construct($class, array $definition, Builtin $authMechanism) { if (isset($definition['priority'])) { $this->setPriority($definition['priority']); } + if (isset($definition['custom'])) { $this->addCustomJs($definition['custom']); } + if (isset($definition['has_dependencies']) && $definition['has_dependencies']) { $this->hasDependencies = true; } @@ -81,6 +85,7 @@ public function checkDependencies() { if ($this->hasDependencies) { return $this->doCheckDependencies(); } + return []; } } diff --git a/apps/files_external/lib/Lib/Backend/SMB.php b/apps/files_external/lib/Lib/Backend/SMB.php index c4a68fea6e18e..b62f97da42150 100644 --- a/apps/files_external/lib/Lib/Backend/SMB.php +++ b/apps/files_external/lib/Lib/Backend/SMB.php @@ -81,6 +81,7 @@ public function manipulateStorageConfig(StorageConfig &$storage, ?IUser $user = if (!$auth instanceof KerberosApacheAuthMechanism) { throw new \InvalidArgumentException('invalid authentication backend'); } + $credentialsStore = $auth->getCredentialsStore(); $kerbAuth = new KerberosApacheAuth(); // check if a kerberos ticket is available, else fallback to session credentials @@ -96,6 +97,7 @@ public function manipulateStorageConfig(StorageConfig &$storage, ?IUser $user = if (empty($realm)) { $realm = 'WORKGROUP'; } + if (count($matches) === 0) { $username = $user; $workgroup = $realm; @@ -103,6 +105,7 @@ public function manipulateStorageConfig(StorageConfig &$storage, ?IUser $user = $username = $matches[1]; $workgroup = $matches[2]; } + $smbAuth = new BasicAuth( $username, $workgroup, diff --git a/apps/files_external/lib/Lib/Backend/Swift.php b/apps/files_external/lib/Lib/Backend/Swift.php index 386604e6e1de4..91c9e01caa58a 100644 --- a/apps/files_external/lib/Lib/Backend/Swift.php +++ b/apps/files_external/lib/Lib/Backend/Swift.php @@ -35,6 +35,7 @@ public function __construct(IL10N $l, OpenStackV2 $openstackAuth, Rackspace $rac if (isset($params['options']['key']) && $params['options']['key']) { return $rackspaceAuth; } + return $openstackAuth; }) ; diff --git a/apps/files_external/lib/Lib/DefinitionParameter.php b/apps/files_external/lib/Lib/DefinitionParameter.php index 584fee1e49859..ad9df60b7bdff 100644 --- a/apps/files_external/lib/Lib/DefinitionParameter.php +++ b/apps/files_external/lib/Lib/DefinitionParameter.php @@ -183,6 +183,7 @@ public function jsonSerialize(): array { if ($defaultValue) { $result['defaultValue'] = $defaultValue; } + return $result; } @@ -212,13 +213,16 @@ public function validateValue(&$value): bool { return false; } } + break; default: if (!$value && !$this->isOptional()) { return false; } + break; } + return true; } } diff --git a/apps/files_external/lib/Lib/FrontendDefinitionTrait.php b/apps/files_external/lib/Lib/FrontendDefinitionTrait.php index 5602345fe89ed..82f12318822f6 100644 --- a/apps/files_external/lib/Lib/FrontendDefinitionTrait.php +++ b/apps/files_external/lib/Lib/FrontendDefinitionTrait.php @@ -47,6 +47,7 @@ public function addParameters(array $parameters): self { foreach ($parameters as $parameter) { $this->addParameter($parameter); } + return $this; } @@ -85,6 +86,7 @@ public function jsonSerializeDefinition(): array { 'configuration' => $configuration, 'custom' => $this->getCustomJs(), ]; + return $data; } @@ -98,9 +100,11 @@ public function validateStorageDefinition(StorageConfig $storage): bool { if (!$parameter->validateValue($value)) { return false; } + $storage->setBackendOption($name, $value); } } + return true; } } diff --git a/apps/files_external/lib/Lib/IdentifierTrait.php b/apps/files_external/lib/Lib/IdentifierTrait.php index ff7bb8a465ee3..02a6a35d60191 100644 --- a/apps/files_external/lib/Lib/IdentifierTrait.php +++ b/apps/files_external/lib/Lib/IdentifierTrait.php @@ -25,6 +25,7 @@ public function getIdentifier(): string { public function setIdentifier(string $identifier): self { $this->identifier = $identifier; $this->identifierAliases[] = $identifier; + return $this; } @@ -57,6 +58,7 @@ public function jsonSerializeIdentifier(): array { if ($this->deprecateTo) { $data['deprecateTo'] = $this->deprecateTo->getIdentifier(); } + return $data; } } diff --git a/apps/files_external/lib/Lib/LegacyDependencyCheckPolyfill.php b/apps/files_external/lib/Lib/LegacyDependencyCheckPolyfill.php index 313d4ae8cb256..b4fc7390fdb17 100644 --- a/apps/files_external/lib/Lib/LegacyDependencyCheckPolyfill.php +++ b/apps/files_external/lib/Lib/LegacyDependencyCheckPolyfill.php @@ -31,6 +31,7 @@ public function checkDependencies() { if (!is_array($result)) { $result = [$result]; } + foreach ($result as $key => $value) { if (!($value instanceof MissingDependency)) { $module = null; @@ -41,9 +42,11 @@ public function checkDependencies() { $module = $key; $message = $value; } + $value = new MissingDependency($module); $value->setMessage($message); } + $ret[] = $value; } } diff --git a/apps/files_external/lib/Lib/Notify/SMBNotifyHandler.php b/apps/files_external/lib/Lib/Notify/SMBNotifyHandler.php index 153cae1bd3828..b6d604d975506 100644 --- a/apps/files_external/lib/Lib/Notify/SMBNotifyHandler.php +++ b/apps/files_external/lib/Lib/Notify/SMBNotifyHandler.php @@ -70,6 +70,7 @@ public function getChanges() { $changes[] = $change; } } + return $changes; } @@ -91,14 +92,17 @@ private function mapChange(\Icewind\SMB\Change $change) { if (is_null($path)) { return null; } + if ($change->getCode() === \Icewind\SMB\INotifyHandler::NOTIFY_RENAMED_OLD) { $this->oldRenamePath = $path; return null; } + $type = $this->mapNotifyType($change->getCode()); if (is_null($type)) { return null; } + if ($type === IChange::RENAMED) { if (!is_null($this->oldRenamePath)) { $result = new RenameChange($type, $this->oldRenamePath, $path); @@ -109,6 +113,7 @@ private function mapChange(\Icewind\SMB\Change $change) { } else { $result = new Change($type, $path); } + return $result; } diff --git a/apps/files_external/lib/Lib/PersonalMount.php b/apps/files_external/lib/Lib/PersonalMount.php index 64e8fdcea695e..3a72a7fb288e2 100644 --- a/apps/files_external/lib/Lib/PersonalMount.php +++ b/apps/files_external/lib/Lib/PersonalMount.php @@ -39,7 +39,7 @@ public function __construct( $arguments = null, $loader = null, $mountOptions = null, - $mountId = null + $mountId = null, ) { parent::__construct($storageConfig, $storage, $mountpoint, $arguments, $loader, $mountOptions, $mountId); $this->storagesService = $storagesService; @@ -59,6 +59,7 @@ public function moveMount($target) { $storage->setMountPoint($targetParts[2]); $this->storagesService->updateStorage($storage); $this->setMountPoint($target); + return true; } diff --git a/apps/files_external/lib/Lib/Storage/AmazonS3.php b/apps/files_external/lib/Lib/Storage/AmazonS3.php index 43646ac681a2f..983534e365538 100644 --- a/apps/files_external/lib/Lib/Storage/AmazonS3.php +++ b/apps/files_external/lib/Lib/Storage/AmazonS3.php @@ -81,6 +81,7 @@ private function cleanKey($path) { if ($this->isRoot($path)) { return '/'; } + return $path; } @@ -99,6 +100,7 @@ private function invalidateCache($key) { unset($this->objectCache[$existingKey]); } } + unset($this->filesCache[$key]); $keys = array_keys($this->directoryCache->getData()); $keyLength = strlen($key); @@ -107,6 +109,7 @@ private function invalidateCache($key) { unset($this->directoryCache[$existingKey]); } } + unset($this->directoryCache[$key]); } @@ -124,6 +127,7 @@ private function headObject(string $key) { if ($e->getStatusCode() >= 500) { throw $e; } + $this->objectCache[$key] = false; } } @@ -132,6 +136,7 @@ private function headObject(string $key) { /** @psalm-suppress InvalidArgument Psalm doesn't understand nested arrays well */ $this->objectCache[$key]['Key'] = $key; } + return $this->objectCache[$key]; } @@ -152,11 +157,13 @@ private function doesDirectoryExist($path) { if ($path === '.' || $path === '') { return true; } + $path = rtrim($path, '/') . '/'; if (isset($this->directoryCache[$path])) { return $this->directoryCache[$path]; } + try { // Maybe this isn't an actual key, but a prefix. // Do a prefix listing of objects to determine. @@ -182,11 +189,12 @@ private function doesDirectoryExist($path) { if ($e->getStatusCode() >= 400 && $e->getStatusCode() < 500) { $this->directoryCache[$path] = false; } + throw $e; } - $this->directoryCache[$path] = false; + return false; } @@ -228,6 +236,7 @@ public function mkdir($path) { 'app' => 'files_external', 'exception' => $e, ]); + return false; } @@ -253,6 +262,7 @@ public function rmdir($path) { } $this->invalidateCache($path); + return $this->batchDelete($path); } @@ -269,6 +279,7 @@ private function batchDelete($path = null) { if ($path !== null) { $params['Prefix'] = $path . '/'; } + try { $connection = $this->getConnection(); // Since there are no real directories on S3, we need @@ -288,6 +299,7 @@ private function batchDelete($path = null) { } // we reached the end when the list is no longer truncated } while ($objects['IsTruncated']); + if ($path !== '' && $path !== null) { $this->deleteObject($path); } @@ -296,8 +308,10 @@ private function batchDelete($path = null) { 'app' => 'files_external', 'exception' => $e, ]); + return false; } + return true; } @@ -322,8 +336,10 @@ public function stat($path) { if ($object === false) { return false; } + $stat = $this->objectToMetaData($object); } + $stat['atime'] = time(); return $stat; @@ -387,6 +403,7 @@ public function is_dir($path) { 'app' => 'files_external', 'exception' => $e, ]); + return false; } } @@ -402,9 +419,11 @@ public function filetype($path) { if (isset($this->directoryCache[$path]) && $this->directoryCache[$path]) { return 'dir'; } + if (isset($this->filesCache[$path]) || $this->headObject($path)) { return 'file'; } + if ($this->doesDirectoryExist($path)) { return 'dir'; } @@ -413,6 +432,7 @@ public function filetype($path) { 'app' => 'files_external', 'exception' => $e, ]); + return false; } @@ -424,6 +444,7 @@ public function getPermissions($path) { if (!$type) { return 0; } + return $type === 'dir' ? Constants::PERMISSION_ALL : Constants::PERMISSION_ALL - Constants::PERMISSION_CREATE; } @@ -442,6 +463,7 @@ public function unlink($path) { 'app' => 'files_external', 'exception' => $e, ]); + return false; } @@ -467,13 +489,16 @@ public function fopen($path, $mode) { 'app' => 'files_external', 'exception' => $e, ]); + return false; } + case 'w': case 'wb': $tmpFile = \OC::$server->getTempManager()->getTemporaryFile(); $handle = fopen($tmpFile, 'w'); + return CallbackWrapper::wrap($handle, null, null, function () use ($path, $tmpFile) { $this->writeBack($tmpFile, $path); }); @@ -492,6 +517,7 @@ public function fopen($path, $mode) { } else { $ext = ''; } + $tmpFile = \OC::$server->getTempManager()->getTemporaryFile($ext); if ($this->file_exists($path)) { $source = $this->readObject($path); @@ -499,10 +525,12 @@ public function fopen($path, $mode) { } $handle = fopen($tmpFile, $mode); + return CallbackWrapper::wrap($handle, null, null, function () use ($path, $tmpFile) { $this->writeBack($tmpFile, $path); }); } + return false; } @@ -510,6 +538,7 @@ public function touch($path, $mtime = null) { if (is_null($mtime)) { $mtime = time(); } + $metadata = [ 'lastmodified' => gmdate(\DateTime::RFC1123, $mtime) ]; @@ -534,10 +563,12 @@ public function touch($path, $mtime = null) { 'app' => 'files_external', 'exception' => $e, ]); + return false; } $this->invalidateCache($path); + return true; } @@ -556,6 +587,7 @@ public function copy($source, $target, $isFile = null) { 'app' => 'files_external', 'exception' => $e, ]); + return false; } } else { @@ -569,6 +601,7 @@ public function copy($source, $target, $isFile = null) { 'app' => 'files_external', 'exception' => $e, ]); + return false; } @@ -615,6 +648,7 @@ public function test() { $this->getConnection()->headBucket([ 'Bucket' => $this->bucket ]); + return true; } @@ -629,12 +663,14 @@ public function writeBack($tmpFile, $path) { $this->invalidateCache($path); unlink($tmpFile); + return true; } catch (S3Exception $e) { $this->logger->error($e->getMessage(), [ 'app' => 'files_external', 'exception' => $e, ]); + return false; } } @@ -671,6 +707,7 @@ public function getDirectoryContent($directory): \Traversable { } } } + if (is_array($result['Contents'])) { foreach ($result['Contents'] as $object) { $this->objectCache[$object['Key']] = $object; @@ -701,6 +738,7 @@ private function getDirectoryMetaData(string $path): ?array { if ($this->versioningEnabled() && !$this->doesDirectoryExist($path)) { return null; } + $cacheEntry = $this->getCache()->get($path); if ($cacheEntry instanceof CacheEntry) { return $cacheEntry->getData(); @@ -727,6 +765,7 @@ public function versioningEnabled(): bool { $this->versioningEnabled = $cached; } } + return $this->versioningEnabled; } @@ -739,6 +778,7 @@ protected function getVersioningStatusFromBucket(): bool { if ($s3Exception->getAwsErrorCode() === 'NotImplemented' || $s3Exception->getAwsErrorCode() === 'AccessDenied') { return false; } + throw $s3Exception; } } diff --git a/apps/files_external/lib/Lib/Storage/FTP.php b/apps/files_external/lib/Lib/Storage/FTP.php index d1f67c5a7412a..0faf0100dd6ea 100644 --- a/apps/files_external/lib/Lib/Storage/FTP.php +++ b/apps/files_external/lib/Lib/Storage/FTP.php @@ -43,6 +43,7 @@ public function __construct($params) { } else { $this->secure = false; } + $this->root = isset($params['root']) ? '/' . ltrim($params['root']) : '/'; $this->port = $params['port'] ?? 21; $this->utf8Mode = isset($params['utf8']) && $params['utf8']; @@ -68,6 +69,7 @@ protected function getConnection(): FtpConnection { } catch (\Exception $e) { throw new StorageNotAvailableException('Failed to create ftp connection', 0, $e); } + if ($this->utf8Mode) { if (!$this->connection->setUtf8Mode()) { throw new StorageNotAvailableException('Could not set UTF-8 mode'); @@ -104,6 +106,7 @@ public function filemtime($path) { \OC::$server->get(LoggerInterface::class)->warning("Unable to get last modified date for ftp folder ($path), failed to list folder contents"); return time(); } + $currentDir = current(array_filter($list, function ($item) { return $item['type'] === 'cdir'; })); @@ -113,6 +116,7 @@ public function filemtime($path) { if ($time === false) { throw new \Exception("Invalid date format for directory: $currentDir"); } + return $time->getTimestamp(); } else { \OC::$server->get(LoggerInterface::class)->warning("Unable to get last modified date for ftp folder ($path), folder contents doesn't include current folder"); @@ -165,6 +169,7 @@ private function recursiveRmDir($path): bool { $result = $result && $this->getConnection()->delete($this->buildPath($path . '/' . $content['name'])); } } + $result = $result && $this->getConnection()->rmdir($this->buildPath($path)); return $result; @@ -182,6 +187,7 @@ public function stat($path) { if (!$this->file_exists($path)) { return false; } + return [ 'mtime' => $this->filemtime($path), 'size' => $this->filesize($path), @@ -192,6 +198,7 @@ public function file_exists($path) { if ($path === '' || $path === '.' || $path === '/') { return true; } + return $this->filetype($path) !== false; } @@ -215,6 +222,7 @@ public function mkdir($path) { if ($this->is_dir($path)) { return false; } + return $this->getConnection()->mkdir($this->buildPath($path)) !== false; } @@ -222,6 +230,7 @@ public function is_dir($path) { if ($path === '') { return true; } + if ($this->getConnection()->chdir($this->buildPath($path)) === true) { $this->getConnection()->chdir('/'); return true; @@ -269,19 +278,24 @@ public function fopen($path, $mode) { if (!$this->isUpdatable($path)) { return false; } + $tmpFile = $this->getCachedFile($path); } else { if (!$this->isCreatable(dirname($path))) { return false; } + $tmpFile = \OC::$server->getTempManager()->getTemporaryFile(); } + $source = fopen($tmpFile, $mode); + return CallbackWrapper::wrap($source, null, null, function () use ($tmpFile, $path) { $this->writeStream($path, fopen($tmpFile, 'r')); unlink($tmpFile); }); } + return false; } @@ -307,6 +321,7 @@ public function readStream(string $path) { fclose($stream); return false; } + return $stream; } @@ -333,6 +348,7 @@ public function getDirectoryContent($directory): \Traversable { if ($file['type'] === 'cdir' || $file['type'] === 'pdir') { continue; } + $permissions = Constants::PERMISSION_ALL - Constants::PERMISSION_CREATE; $isDir = $file['type'] === 'dir'; if ($isDir) { @@ -353,6 +369,7 @@ public function getDirectoryContent($directory): \Traversable { } else { $data['size'] = $this->filesize($directory . '/' . $name); } + $data['etag'] = uniqid(); $data['storage_mtime'] = $data['mtime']; $data['permissions'] = $permissions; diff --git a/apps/files_external/lib/Lib/Storage/FtpConnection.php b/apps/files_external/lib/Lib/Storage/FtpConnection.php index a064bf9b100a3..eba32326f41f7 100644 --- a/apps/files_external/lib/Lib/Storage/FtpConnection.php +++ b/apps/files_external/lib/Lib/Storage/FtpConnection.php @@ -78,6 +78,7 @@ public function mdtm(string $path): int { if ($result === -1 && $path === '') { $result = @ftp_mdtm($this->connection, '/'); } + return $result; } @@ -95,6 +96,7 @@ public function nlist(string $path) { if (str_contains($name, '/')) { $name = basename($name); } + return $name; }, $files); } @@ -107,6 +109,7 @@ public function mlsd(string $path) { if (str_contains($file['name'], '/')) { $file['name'] = basename($file['name']); } + return $file; }, $files); } else { @@ -115,6 +118,7 @@ public function mlsd(string $path) { if ($rawList === false) { return false; } + return $this->parseRawList($rawList, $path); } } @@ -186,6 +190,7 @@ private function normalizePermissions(string $permissions) { if (isset($map[$permission])) { $ftpPermissions .= $map[$permission]; } + return $ftpPermissions; }, ''); } diff --git a/apps/files_external/lib/Lib/Storage/OwnCloud.php b/apps/files_external/lib/Lib/Storage/OwnCloud.php index fb5c720748663..7448e287e1aaa 100644 --- a/apps/files_external/lib/Lib/Storage/OwnCloud.php +++ b/apps/files_external/lib/Lib/Storage/OwnCloud.php @@ -31,6 +31,7 @@ public function __construct($params) { $host = substr($host, 7); $params['secure'] = false; } + $contextPath = ''; $hostSlashPos = strpos($host, '/'); if ($hostSlashPos !== false) { diff --git a/apps/files_external/lib/Lib/Storage/SFTP.php b/apps/files_external/lib/Lib/Storage/SFTP.php index acc531341d9e0..3b297aebdc1c1 100644 --- a/apps/files_external/lib/Lib/Storage/SFTP.php +++ b/apps/files_external/lib/Lib/Storage/SFTP.php @@ -71,11 +71,13 @@ public function __construct($params) { if (!isset($params['user'])) { throw new \UnexpectedValueException('no authentication parameters specified'); } + $this->user = $params['user']; if (isset($params['public_key_auth'])) { $this->auth[] = $params['public_key_auth']; } + if (isset($params['password']) && $params['password'] !== '') { $this->auth[] = $params['password']; } @@ -129,6 +131,7 @@ public function getConnection() { if ($login === false) { throw new \Exception('Login failed'); } + return $this->client; } @@ -142,6 +145,7 @@ public function test() { ) { return false; } + return $this->getConnection()->nlist() !== false; } @@ -153,10 +157,12 @@ public function getId() { if ($this->port !== 22) { $id .= ':' . $this->port; } + // note: this will double the root slash, // we should not change it to keep compatible with // old storage ids $id .= '/' . $this->root; + return $id; } @@ -204,6 +210,7 @@ private function hostKeysPath() { return $view->getLocalFile('ssh_hostKeys'); } catch (\Exception $e) { } + return false; } @@ -219,11 +226,14 @@ protected function writeHostKeys($keys) { foreach ($keys as $host => $key) { fwrite($fp, $host . '::' . $key . "\n"); } + fclose($fp); + return true; } } catch (\Exception $e) { } + return false; } @@ -245,11 +255,13 @@ protected function readHostKeys() { $keys[] = $hostKeyArray[1]; } } + return array_combine($hosts, $keys); } } } catch (\Exception $e) { } + return []; } @@ -273,6 +285,7 @@ public function rmdir($path) { // workaround: stray stat cache entry when deleting empty folders // see https://github.com/phpseclib/phpseclib/issues/706 $this->getConnection()->clearStatCache(); + return $result; } catch (\Exception $e) { return false; @@ -296,6 +309,7 @@ public function opendir($path) { $dirStream[] = $file; } } + return IteratorDirectory::wrap($dirStream); } catch (\Exception $e) { return false; @@ -311,6 +325,7 @@ public function filetype($path) { if (!is_array($stat) || !array_key_exists('type', $stat)) { return false; } + if ((int)$stat['type'] === NET_SFTP_TYPE_REGULAR) { return 'file'; } @@ -320,6 +335,7 @@ public function filetype($path) { } } catch (\Exception $e) { } + return false; } @@ -359,9 +375,11 @@ public function fopen($path, $mode) { if (!$stat) { return false; } + SFTPReadStream::register(); $context = stream_context_create(['sftp' => ['session' => $connection, 'size' => $stat['size']]]); $handle = fopen('sftpread://' . trim($absPath, '/'), 'r', false, $context); + return RetryWrapper::wrap($handle); case 'w': case 'wb': @@ -369,6 +387,7 @@ public function fopen($path, $mode) { // the SFTPWriteStream doesn't go through the "normal" methods so it doesn't clear the stat cache. $connection->_remove_from_stat_cache($absPath); $context = stream_context_create(['sftp' => ['session' => $connection]]); + return fopen('sftpwrite://' . trim($absPath, '/'), 'w', false, $context); case 'a': case 'ab': @@ -382,10 +401,12 @@ public function fopen($path, $mode) { case 'c+': $context = stream_context_create(['sftp' => ['session' => $connection]]); $handle = fopen($this->constructUrl($path), $mode, false, $context); + return RetryWrapper::wrap($handle); } } catch (\Exception $e) { } + return false; } @@ -397,6 +418,7 @@ public function touch($path, $mtime = null) { if (!is_null($mtime)) { return false; } + if (!$this->file_exists($path)) { $this->getConnection()->put($this->absPath($path), ''); } else { @@ -405,6 +427,7 @@ public function touch($path, $mtime = null) { } catch (\Exception $e) { return false; } + return true; } @@ -425,6 +448,7 @@ public function rename($source, $target) { if ($this->file_exists($target)) { $this->unlink($target); } + return $this->getConnection()->rename( $this->absPath($source), $this->absPath($target) @@ -463,6 +487,7 @@ public function constructUrl($path) { // supplied via stream context or fail. We only supply username and // hostname because this might show up in logs (they are not used). $url = 'sftp://' . urlencode($this->user) . '@' . $this->host . ':' . $this->port . $this->root . $path; + return $url; } @@ -485,6 +510,7 @@ public function writeStream(string $path, $stream, ?int $size = null): int { throw new \Exception('Failed to wrap stream'); } } + /** @psalm-suppress InternalMethod */ $result = $this->getConnection()->put($this->absPath($path), $stream); fclose($stream); @@ -492,6 +518,7 @@ public function writeStream(string $path, $stream, ?int $size = null): int { if ($size === null) { throw new \Exception('Failed to get written size from sftp storage wrapper'); } + return $size; } else { throw new \Exception('Failed to write steam to sftp storage'); @@ -510,17 +537,20 @@ public function copy($source, $target) { if ($size === false) { return false; } + for ($i = 0; $i < $size; $i += self::COPY_CHUNK_SIZE) { /** @psalm-suppress InvalidArgument */ $chunk = $connection->get($absSource, false, $i, self::COPY_CHUNK_SIZE); if ($chunk === false) { return false; } + /** @psalm-suppress InternalMethod */ if (!$connection->put($absTarget, $chunk, \phpseclib\Net\SFTP::SOURCE_STRING, $i)) { return false; } } + return true; } } @@ -530,6 +560,7 @@ public function getPermissions($path) { if (!$stat) { return 0; } + if ($stat['type'] === NET_SFTP_TYPE_DIRECTORY) { return Constants::PERMISSION_ALL; } else { @@ -561,6 +592,7 @@ public function getMetaData($path) { $stat['name'] = basename($path); $keys = ['size', 'mtime', 'mimetype', 'etag', 'storage_mtime', 'permissions', 'name']; + return array_intersect_key($stat, array_flip($keys)); } } diff --git a/apps/files_external/lib/Lib/Storage/SFTPReadStream.php b/apps/files_external/lib/Lib/Storage/SFTPReadStream.php index e0b4b4002aad4..040113bb3e37a 100644 --- a/apps/files_external/lib/Lib/Storage/SFTPReadStream.php +++ b/apps/files_external/lib/Lib/Storage/SFTPReadStream.php @@ -38,6 +38,7 @@ public static function register($protocol = 'sftpread') { if (in_array($protocol, stream_get_wrappers(), true)) { return false; } + return stream_wrapper_register($protocol, get_called_class()); } @@ -54,14 +55,17 @@ protected function loadContext($name) { } else { throw new \BadMethodCallException('Invalid context, "' . $name . '" options not set'); } + if (isset($context['session']) and $context['session'] instanceof \phpseclib\Net\SFTP) { $this->sftp = $context['session']; } else { throw new \BadMethodCallException('Invalid context, session not set'); } + if (isset($context['size'])) { $this->size = $context['size']; } + return $context; } @@ -93,9 +97,11 @@ public function stream_open($path, $mode, $options, &$opened_path) { break; case NET_SFTP_STATUS: // presumably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED $this->sftp->_logError($response); + return false; default: user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS'); + return false; } @@ -116,6 +122,7 @@ public function stream_seek($offset, $whence = SEEK_SET) { $this->seekTo($this->size + $offset); break; } + return true; } @@ -153,6 +160,7 @@ private function request_chunk($size) { $packet = pack('Na*N3', strlen($this->handle), $this->handle, $this->internalPosition / 4294967296, $this->internalPosition, $size); $this->pendingRead = true; + return $this->sftp->_send_sftp_packet(NET_SFTP_READ, $packet); } @@ -165,12 +173,14 @@ private function read_chunk() { $temp = substr($response, 4); $len = strlen($temp); $this->internalPosition += $len; + return $temp; case NET_SFTP_STATUS: [1 => $status] = unpack('N', substr($response, 0, 4)); if ($status == NET_SFTP_STATUS_EOF) { $this->eof = true; } + return ''; default: return ''; @@ -210,9 +220,11 @@ public function stream_close() { if ($this->pendingRead) { $this->sftp->_get_sftp_packet(); } + if (!$this->sftp->_close_handle($this->handle)) { return false; } + return true; } } diff --git a/apps/files_external/lib/Lib/Storage/SFTPWriteStream.php b/apps/files_external/lib/Lib/Storage/SFTPWriteStream.php index a652a83cb836c..858f9b354bd57 100644 --- a/apps/files_external/lib/Lib/Storage/SFTPWriteStream.php +++ b/apps/files_external/lib/Lib/Storage/SFTPWriteStream.php @@ -36,6 +36,7 @@ public static function register($protocol = 'sftpwrite') { if (in_array($protocol, stream_get_wrappers(), true)) { return false; } + return stream_wrapper_register($protocol, get_called_class()); } @@ -52,11 +53,13 @@ protected function loadContext($name) { } else { throw new \BadMethodCallException('Invalid context, "' . $name . '" options not set'); } + if (isset($context['session']) and $context['session'] instanceof \phpseclib\Net\SFTP) { $this->sftp = $context['session']; } else { throw new \BadMethodCallException('Invalid context, session not set'); } + return $context; } @@ -88,9 +91,11 @@ public function stream_open($path, $mode, $options, &$opened_path) { break; case NET_SFTP_STATUS: // presumably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED $this->sftp->_logError($response); + return false; default: user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS'); + return false; } @@ -146,6 +151,7 @@ public function stream_flush() { if (!$this->sftp->_send_sftp_packet(NET_SFTP_WRITE, $packet)) { return false; } + $this->internalPosition += $size; $this->buffer = ''; @@ -161,6 +167,7 @@ public function stream_close() { if (!$this->sftp->_close_handle($this->handle)) { return false; } + return true; } } diff --git a/apps/files_external/lib/Lib/Storage/SMB.php b/apps/files_external/lib/Lib/Storage/SMB.php index bd871626bd9e5..c1cb32a6204f8 100644 --- a/apps/files_external/lib/Lib/Storage/SMB.php +++ b/apps/files_external/lib/Lib/Storage/SMB.php @@ -90,6 +90,7 @@ public function __construct($params) { . ' Expected ' . LoggerInterface::class ); } + $this->logger = $params['logger']; } else { $this->logger = \OC::$server->get(LoggerInterface::class); @@ -102,6 +103,7 @@ public function __construct($params) { $options->setTimeout($timeout); } } + $serverFactory = new ServerFactory($options); $this->server = $serverFactory->createServer($params['host'], $auth); $this->share = $this->server->getShare(trim($params['share'], '/')); @@ -170,6 +172,7 @@ protected function getFileInfo($path) { } else { $stat = $this->share->stat($path); $this->statCache[$path] = $stat; + return $stat; } } catch (ConnectException $e) { @@ -182,6 +185,7 @@ protected function getFileInfo($path) { if ($e->getCode() === 1) { $this->throwUnavailable($e); } + throw new \OCP\Files\ForbiddenException($e->getMessage(), false, $e); } } @@ -230,6 +234,7 @@ protected function getFolderContents($path): iterable { } catch (InvalidTypeException $e) { return; } + foreach ($files as $file) { $this->statCache[$path . '/' . $file->getName()] = $file; } @@ -253,6 +258,7 @@ protected function getFolderContents($path): iterable { if ($hide) { $this->logger->debug('hiding hidden file ' . $file->getName()); } + if (!$hide) { yield $file; } @@ -284,6 +290,7 @@ protected function formatInfo($info) { } else { $result['type'] = 'file'; } + return $result; } @@ -298,6 +305,7 @@ public function rename($source, $target, $retry = true): bool { if ($this->isRootDir($source) || $this->isRootDir($target)) { return false; } + if ($this->caseSensitive === false && mb_strtolower($target) === mb_strtolower($source) ) { @@ -329,7 +337,9 @@ public function rename($source, $target, $retry = true): bool { $this->logger->warning($e->getMessage(), ['exception' => $e]); return false; } + unset($this->statCache[$absoluteSource], $this->statCache[$absoluteTarget]); + return $result; } @@ -347,9 +357,11 @@ public function stat($path, $retry = true) { throw $e; } } + if ($this->remoteIsShare() && $this->isRootDir($path)) { $result['mtime'] = $this->shareMTime(); } + return $result; } @@ -372,6 +384,7 @@ private function shareMTime() { // Ignore this too - it's a symlink } } + return $highestMTime; } @@ -410,6 +423,7 @@ public function unlink($path) { $path = $this->buildPath($path); unset($this->statCache[$path]); $this->share->del($path); + return true; } } catch (NotFoundException $e) { @@ -454,10 +468,12 @@ public function fopen($path, $mode) { if (!$this->file_exists($path)) { return false; } + return $this->share->read($fullPath); case 'w': case 'wb': $source = $this->share->write($fullPath); + return CallBackWrapper::wrap($source, null, null, function () use ($fullPath) { unset($this->statCache[$fullPath]); }); @@ -477,25 +493,31 @@ public function fopen($path, $mode) { } else { $ext = ''; } + if ($this->file_exists($path)) { if (!$this->isUpdatable($path)) { return false; } + $tmpFile = $this->getCachedFile($path); } else { if (!$this->isCreatable(dirname($path))) { return false; } + $tmpFile = \OC::$server->getTempManager()->getTemporaryFile($ext); } + $source = fopen($tmpFile, $mode); $share = $this->share; + return CallbackWrapper::wrap($source, null, null, function () use ($tmpFile, $fullPath, $share) { unset($this->statCache[$fullPath]); $share->put($tmpFile, $fullPath); unlink($tmpFile); }); } + return false; } catch (NotFoundException $e) { return false; @@ -524,7 +546,9 @@ public function rmdir($path) { $this->share->del($file->getPath()); } } + $this->share->rmdir($this->buildPath($path)); + return true; } catch (NotFoundException $e) { return false; @@ -541,8 +565,10 @@ public function touch($path, $mtime = null) { if (!$this->file_exists($path)) { $fh = $this->share->write($this->buildPath($path)); fclose($fh); + return true; } + return false; } catch (OutOfSpaceException $e) { throw new EntityTooLargeException('not enough available space to create file', 0, $e); @@ -560,6 +586,7 @@ public function getMetaData($path) { } catch (ForbiddenException $e) { return null; } + if (!$fileInfo) { return null; } @@ -586,12 +613,14 @@ private function getMetaDataFromFileInfo(IFileInfo $fileInfo) { } else { $data['mimetype'] = \OC::$server->getMimeTypeDetector()->detectPath($fileInfo->getPath()); } + $data['mtime'] = $fileInfo->getMTime(); if ($fileInfo->isDirectory()) { $data['size'] = -1; //unknown } else { $data['size'] = $fileInfo->getSize(); } + $data['etag'] = $this->getETag($fileInfo->getPath()); $data['storage_mtime'] = $data['mtime']; $data['permissions'] = $permissions; @@ -608,10 +637,12 @@ public function opendir($path) { } catch (NotPermittedException $e) { return false; } + $names = array_map(function ($info) { /** @var IFileInfo $info */ return $info->getName(); }, iterator_to_array($files)); + return IteratorDirectory::wrap($names); } @@ -662,9 +693,12 @@ public function file_exists($path) { return true; } } + return false; } + $this->getFileInfo($path); + return true; } catch (\OCP\Files\NotFoundException $e) { return false; @@ -751,6 +785,7 @@ public function listen($path, callable $callback) { public function notify($path) { $path = '/' . ltrim($path, '/'); $shareNotifyHandler = $this->share->notify($this->buildPath($path)); + return new SMBNotifyHandler($shareNotifyHandler, $this->root); } } diff --git a/apps/files_external/lib/Lib/Storage/StreamWrapper.php b/apps/files_external/lib/Lib/Storage/StreamWrapper.php index 2928c081505c5..e7e4f9afbdf3b 100644 --- a/apps/files_external/lib/Lib/Storage/StreamWrapper.php +++ b/apps/files_external/lib/Lib/Storage/StreamWrapper.php @@ -24,6 +24,7 @@ public function rmdir($path) { if (!is_resource($dh)) { return false; } + while (($file = readdir($dh)) !== false) { if ($this->is_dir($path . '/' . $file)) { $this->rmdir($path . '/' . $file); @@ -31,9 +32,11 @@ public function rmdir($path) { $this->unlink($path . '/' . $file); } } + $url = $this->constructUrl($path); $success = rmdir($url); clearstatcache(false, $url); + return $success; } else { return false; @@ -58,6 +61,7 @@ public function unlink($path) { // normally unlink() is supposed to do this implicitly, // but doing it anyway just to be sure clearstatcache(false, $url); + return $success; } diff --git a/apps/files_external/lib/Lib/Storage/Swift.php b/apps/files_external/lib/Lib/Storage/Swift.php index 64b3179efefe6..573505075fd8a 100644 --- a/apps/files_external/lib/Lib/Storage/Swift.php +++ b/apps/files_external/lib/Lib/Storage/Swift.php @@ -98,10 +98,12 @@ private function fetchObject(string $path) { // might be "false" if object did not exist from last check return $cached; } + try { $object = $this->getContainer()->getObject($path); $object->retrieve(); $this->objectCache->set($path, $object); + return $object; } catch (BadResponseError $e) { // Expected response is "404 Not Found", so only log if it isn't @@ -111,7 +113,9 @@ private function fetchObject(string $path) { 'app' => 'files_external', ]); } + $this->objectCache->set($path, false); + return false; } } @@ -204,6 +208,7 @@ public function mkdir($path) { 'exception' => $e, 'app' => 'files_external', ]); + return false; } @@ -248,6 +253,7 @@ public function rmdir($path) { 'exception' => $e, 'app' => 'files_external', ]); + return false; } @@ -286,6 +292,7 @@ public function opendir($path) { 'exception' => $e, 'app' => 'files_external', ]); + return false; } } @@ -309,6 +316,7 @@ public function stat($path) { 'exception' => $e, 'app' => 'files_external', ]); + return false; } @@ -327,6 +335,7 @@ public function stat($path) { $stat['size'] = (int)$object->contentLength; $stat['mtime'] = $mtime; $stat['atime'] = time(); + return $stat; } @@ -387,8 +396,10 @@ public function fopen($path, $mode) { 'exception' => $e, 'app' => 'files_external', ]); + return false; } + case 'w': case 'wb': case 'r+': @@ -403,6 +414,7 @@ public function fopen($path, $mode) { } else { $ext = ''; } + $tmpFile = \OC::$server->getTempManager()->getTemporaryFile($ext); // Fetch existing file if required if ($mode[0] !== 'w' && $this->file_exists($path)) { @@ -410,10 +422,13 @@ public function fopen($path, $mode) { // File cannot already exist return false; } + $source = $this->fopen($path, 'r'); file_put_contents($tmpFile, $source); } + $handle = fopen($tmpFile, $mode); + return CallbackWrapper::wrap($handle, null, null, function () use ($path, $tmpFile) { $this->writeBack($tmpFile, $path); }); @@ -425,6 +440,7 @@ public function touch($path, $mtime = null) { if (is_null($mtime)) { $mtime = time(); } + $metadata = ['timestamp' => (string)$mtime]; if ($this->file_exists($path)) { if ($this->is_dir($path) && $path !== '.') { @@ -436,6 +452,7 @@ public function touch($path, $mtime = null) { // invalidate target object to force repopulation on fetch $this->objectCache->remove($path); } + return true; } else { $mimeType = $this->mimeDetector->detectPath($path); @@ -446,6 +463,7 @@ public function touch($path, $mtime = null) { ]); // invalidate target object to force repopulation on fetch $this->objectCache->remove($path); + return true; } } @@ -474,6 +492,7 @@ public function copy($source, $target) { 'exception' => $e, 'app' => 'files_external', ]); + return false; } } elseif ($fileType === 'dir') { @@ -490,6 +509,7 @@ public function copy($source, $target) { 'exception' => $e, 'app' => 'files_external', ]); + return false; } @@ -527,6 +547,7 @@ public function rename($source, $target) { if ($this->unlink($source) === false) { throw new \Exception('failed to remove original'); $this->unlink($target); + return false; } @@ -555,6 +576,7 @@ public function getContainer() { $this->mkdir('.'); } } + return $this->container; } @@ -570,21 +592,25 @@ public function hasUpdated($path, $time) { if ($this->is_file($path)) { return parent::hasUpdated($path, $time); } + $path = $this->normalizePath($path); $dh = $this->opendir($path); $content = []; while (($file = readdir($dh)) !== false) { $content[] = $file; } + if ($path === '.') { $path = ''; } + $cachedContent = $this->getCache()->getFolderContents($path); $cachedNames = array_map(function ($content) { return $content['name']; }, $cachedContent); sort($cachedNames); sort($content); + return $cachedNames !== $content; } diff --git a/apps/files_external/lib/Lib/StorageConfig.php b/apps/files_external/lib/Lib/StorageConfig.php index 682516c73bae3..98434321e74bf 100644 --- a/apps/files_external/lib/Lib/StorageConfig.php +++ b/apps/files_external/lib/Lib/StorageConfig.php @@ -207,6 +207,7 @@ public function setBackendOptions($backendOptions) { $value = (bool)$value; break; } + $backendOptions[$key] = $value; } } @@ -223,6 +224,7 @@ public function getBackendOption($key) { if (isset($this->backendOptions[$key])) { return $this->backendOptions[$key]; } + return null; } @@ -270,6 +272,7 @@ public function setApplicableUsers($applicableUsers) { if (is_null($applicableUsers)) { $applicableUsers = []; } + $this->applicableUsers = $applicableUsers; } @@ -291,6 +294,7 @@ public function setApplicableGroups($applicableGroups) { if (is_null($applicableGroups)) { $applicableGroups = []; } + $this->applicableGroups = $applicableGroups; } @@ -312,6 +316,7 @@ public function setMountOptions($mountOptions) { if (is_null($mountOptions)) { $mountOptions = []; } + $this->mountOptions = $mountOptions; } @@ -323,6 +328,7 @@ public function getMountOption($key) { if (isset($this->mountOptions[$key])) { return $this->mountOptions[$key]; } + return null; } @@ -399,23 +405,30 @@ public function jsonSerialize(bool $obfuscate = false): array { if (!is_null($this->priority)) { $result['priority'] = $this->priority; } + if (!empty($this->applicableUsers)) { $result['applicableUsers'] = $this->applicableUsers; } + if (!empty($this->applicableGroups)) { $result['applicableGroups'] = $this->applicableGroups; } + if (!empty($this->mountOptions)) { $result['mountOptions'] = $this->mountOptions; } + if (!is_null($this->status)) { $result['status'] = $this->status; } + if (!is_null($this->statusMessage)) { $result['statusMessage'] = $this->statusMessage; } + $result['userProvided'] = $this->authMechanism instanceof IUserProvided; $result['type'] = ($this->getType() === self::MOUNT_TYPE_PERSONAL) ? 'personal': 'system'; + return $result; } diff --git a/apps/files_external/lib/Lib/VisibilityTrait.php b/apps/files_external/lib/Lib/VisibilityTrait.php index 63aa709b21c8d..2220a0529feeb 100644 --- a/apps/files_external/lib/Lib/VisibilityTrait.php +++ b/apps/files_external/lib/Lib/VisibilityTrait.php @@ -43,6 +43,7 @@ public function isVisibleFor($visibility) { if ($this->visibility & $visibility) { return true; } + return false; } @@ -53,6 +54,7 @@ public function isVisibleFor($visibility) { public function setVisibility($visibility) { $this->visibility = $visibility; $this->allowedVisibility |= $visibility; + return $this; } @@ -89,6 +91,7 @@ public function isAllowedVisibleFor($allowedVisibility) { if ($this->allowedVisibility & $allowedVisibility) { return true; } + return false; } @@ -99,6 +102,7 @@ public function isAllowedVisibleFor($allowedVisibility) { public function setAllowedVisibility($allowedVisibility) { $this->allowedVisibility = $allowedVisibility; $this->visibility &= $allowedVisibility; + return $this; } diff --git a/apps/files_external/lib/Listener/GroupDeletedListener.php b/apps/files_external/lib/Listener/GroupDeletedListener.php index aced4961e3837..32affea05785d 100644 --- a/apps/files_external/lib/Listener/GroupDeletedListener.php +++ b/apps/files_external/lib/Listener/GroupDeletedListener.php @@ -26,6 +26,7 @@ public function handle(Event $event): void { if (!$event instanceof GroupDeletedEvent) { return; } + $this->config->modifyMountsOnGroupDelete($event->getGroup()->getGID()); } } diff --git a/apps/files_external/lib/Listener/UserDeletedListener.php b/apps/files_external/lib/Listener/UserDeletedListener.php index 24139350ae9e8..acdc3ffe90314 100644 --- a/apps/files_external/lib/Listener/UserDeletedListener.php +++ b/apps/files_external/lib/Listener/UserDeletedListener.php @@ -26,6 +26,7 @@ public function handle(Event $event): void { if (!$event instanceof UserDeletedEvent) { return; } + $this->config->modifyMountsOnUserDelete($event->getUser()->getUID()); } } diff --git a/apps/files_external/lib/Migration/Version1011Date20200630192246.php b/apps/files_external/lib/Migration/Version1011Date20200630192246.php index c87b1cfbc8be9..5e49fa6d14d6b 100644 --- a/apps/files_external/lib/Migration/Version1011Date20200630192246.php +++ b/apps/files_external/lib/Migration/Version1011Date20200630192246.php @@ -131,6 +131,7 @@ public function changeSchema(IOutput $output, Closure $schemaClosure, array $opt $table->setPrimaryKey(['option_id']); $table->addUniqueIndex(['mount_id', 'key'], 'option_mount_key'); } + return $schema; } } diff --git a/apps/files_external/lib/Migration/Version1015Date20211104103506.php b/apps/files_external/lib/Migration/Version1015Date20211104103506.php index 0cd8ffdf27591..dfb4ac148268b 100644 --- a/apps/files_external/lib/Migration/Version1015Date20211104103506.php +++ b/apps/files_external/lib/Migration/Version1015Date20211104103506.php @@ -59,6 +59,7 @@ public function postSchemaChange(IOutput $output, Closure $schemaClosure, array ]); } } + return null; } @@ -74,6 +75,7 @@ private function getS3Mounts() { ->innerJoin('m', 'external_config', 'c', 'c.mount_id = m.mount_id') ->where($qb->expr()->eq('m.storage_backend', $qb->createPositionalParameter('amazons3'))) ->andWhere($qb->expr()->eq('c.key', $qb->createPositionalParameter('bucket'))); + return $qb->execute(); } @@ -89,6 +91,7 @@ private function getStorageConfig(int $mountId): array { foreach ($qb->execute()->fetchAll() as $row) { $config[$row['key']] = $row['value']; } + return $config; } } diff --git a/apps/files_external/lib/MountConfig.php b/apps/files_external/lib/MountConfig.php index b057fb810bd08..e648d77670069 100644 --- a/apps/files_external/lib/MountConfig.php +++ b/apps/files_external/lib/MountConfig.php @@ -41,7 +41,7 @@ class MountConfig { public function __construct( UserGlobalStoragesService $userGlobalStorageService, UserStoragesService $userStorageService, - GlobalStoragesService $globalStorageService + GlobalStoragesService $globalStorageService, ) { $this->userGlobalStorageService = $userGlobalStorageService; $this->userStorageService = $userStorageService; @@ -64,8 +64,10 @@ public static function substitutePlaceholdersInConfig($input, ?string $userId = if ($handler instanceof UserContext && $userId !== null) { $handler->setUserId($userId); } + $input = $handler->handle($input); } + return $input; } @@ -82,13 +84,16 @@ public static function getBackendStatus($class, $options, $isPersonal, $testOnly if (self::$skipTest) { return StorageNotAvailableException::STATUS_SUCCESS; } + foreach ($options as $key => &$option) { if ($key === 'password') { // no replacements in passwords continue; } + $option = self::substitutePlaceholdersInConfig($option); } + if (class_exists($class)) { try { /** @var \OC\Files\Storage\Common $storage */ @@ -109,6 +114,7 @@ public static function getBackendStatus($class, $options, $isPersonal, $testOnly throw $exception; } } + return StorageNotAvailableException::STATUS_ERROR; } @@ -171,6 +177,7 @@ public static function encryptPasswords($options) { // on load... because that's how the UI currently works $options['password'] = ''; } + return $options; } @@ -186,6 +193,7 @@ public static function decryptPasswords($options) { $options['password'] = self::decryptPassword($options['password_encrypted']); unset($options['password_encrypted']); } + return $options; } @@ -199,6 +207,7 @@ private static function encryptPassword($password) { $cipher = self::getCipher(); $iv = \OC::$server->getSecureRandom()->generate(16); $cipher->setIV($iv); + return base64_encode($iv . $cipher->encrypt($password)); } @@ -214,6 +223,7 @@ private static function decryptPassword($encryptedPassword) { $iv = substr($binaryPassword, 0, 16); $cipher->setIV($iv); $binaryPassword = substr($binaryPassword, 16); + return $cipher->decrypt($binaryPassword); } @@ -225,6 +235,7 @@ private static function decryptPassword($encryptedPassword) { private static function getCipher() { $cipher = new AES(AES::MODE_CBC); $cipher->setKey(\OC::$server->getConfig()->getSystemValue('passwordsalt', null)); + return $cipher; } @@ -247,6 +258,7 @@ public static function makeConfigHash($config) { 'mo' => $config['mountOptions'] ?? [], ] ); + return hash('md5', $data); } } diff --git a/apps/files_external/lib/Service/BackendService.php b/apps/files_external/lib/Service/BackendService.php index 5eb0276be6500..94e8d47f2c9a5 100644 --- a/apps/files_external/lib/Service/BackendService.php +++ b/apps/files_external/lib/Service/BackendService.php @@ -62,7 +62,7 @@ class BackendService { * @param IConfig $config */ public function __construct( - IConfig $config + IConfig $config, ) { $this->config = $config; @@ -70,6 +70,7 @@ public function __construct( if ($this->config->getAppValue('files_external', 'allow_user_mounting', 'yes') !== 'yes') { $this->userMountingAllowed = false; } + $this->userMountingBackends = explode(',', $this->config->getAppValue('files_external', 'user_mounting_backends', '') ); @@ -106,6 +107,7 @@ private function loadBackendProviders() { foreach ($this->backendProviders as $provider) { $this->registerBackends($provider->getBackends()); } + $this->backendProviders = []; } @@ -124,6 +126,7 @@ private function loadAuthMechanismProviders() { foreach ($this->authMechanismProviders as $provider) { $this->registerAuthMechanisms($provider->getAuthMechanisms()); } + $this->authMechanismProviders = []; } @@ -137,6 +140,7 @@ public function registerBackend(Backend $backend) { if (!$this->isAllowedUserBackend($backend)) { $backend->removeVisibility(BackendService::VISIBILITY_PERSONAL); } + foreach ($backend->getIdentifierAliases() as $alias) { $this->backends[$alias] = $backend; } @@ -161,6 +165,7 @@ public function registerAuthMechanism(AuthMechanism $authMech) { if (!$this->isAllowedAuthMechanism($authMech)) { $authMech->removeVisibility(BackendService::VISIBILITY_PERSONAL); } + foreach ($authMech->getIdentifierAliases() as $alias) { $this->authMechanisms[$alias] = $authMech; } @@ -188,6 +193,7 @@ public function getBackends() { foreach ($this->backends as $backend) { $backends[$backend->getIdentifier()] = $backend; } + return $backends; } @@ -211,6 +217,7 @@ public function getBackend($identifier) { if (isset($this->backends[$identifier])) { return $this->backends[$identifier]; } + return null; } @@ -226,6 +233,7 @@ public function getAuthMechanisms() { foreach ($this->authMechanisms as $mechanism) { $mechanisms[$mechanism->getIdentifier()] = $mechanism; } + return $mechanisms; } @@ -250,6 +258,7 @@ public function getAuthMechanism($identifier) { if (isset($this->authMechanisms[$identifier])) { return $this->authMechanisms[$identifier]; } + return null; } @@ -272,6 +281,7 @@ protected function isAllowedUserBackend(Backend $backend) { ) { return true; } + return false; } @@ -308,12 +318,15 @@ public function registerConfigHandler(string $placeholder, callable $configHandl 'Invalid placeholder %s, only [a-z0-9] are allowed', $placeholder )); } + if ($placeholder === '') { throw new \RuntimeException('Invalid empty placeholder'); } + if (isset($this->configHandlerLoaders[$placeholder]) || isset($this->configHandlers[$placeholder])) { throw new \RuntimeException(sprintf('A handler is already registered for %s', $placeholder)); } + $this->configHandlerLoaders[$placeholder] = $configHandlerLoader; } @@ -327,9 +340,11 @@ protected function loadConfigHandlers():void { 'Handler for %s is not an instance of IConfigHandler', $placeholder )); } + $this->configHandlers[$placeholder] = $handler; $newLoaded = true; } + $this->configHandlerLoaders = []; if ($newLoaded) { // ensure those with longest placeholders come first, diff --git a/apps/files_external/lib/Service/DBConfigService.php b/apps/files_external/lib/Service/DBConfigService.php index 6fb7e01271e3e..eb10df44a1cd4 100644 --- a/apps/files_external/lib/Service/DBConfigService.php +++ b/apps/files_external/lib/Service/DBConfigService.php @@ -67,6 +67,7 @@ public function getAllMounts() { $builder = $this->connection->getQueryBuilder(); $query = $builder->select(['mount_id', 'mount_point', 'storage_backend', 'auth_backend', 'priority', 'type']) ->from('external_mounts'); + return $this->getMountsFromQuery($query); } @@ -135,6 +136,7 @@ public function getAdminMounts() { $query = $builder->select(['mount_id', 'mount_point', 'storage_backend', 'auth_backend', 'priority', 'type']) ->from('external_mounts') ->where($builder->expr()->eq('type', $builder->expr()->literal(self::MOUNT_TYPE_ADMIN, IQueryBuilder::PARAM_INT))); + return $this->getMountsFromQuery($query); } @@ -234,6 +236,7 @@ public function addMount($mountPoint, $storageBackend, $authBackend, $priority, if (!$priority) { $priority = 100; } + $builder = $this->connection->getQueryBuilder(); $query = $builder->insert('external_mounts') ->values([ @@ -244,6 +247,7 @@ public function addMount($mountPoint, $storageBackend, $authBackend, $priority, 'type' => $builder->createNamedParameter($type, IQueryBuilder::PARAM_INT) ]); $query->execute(); + return $query->getLastInsertId(); } @@ -390,6 +394,7 @@ private function getMountsFromQuery(IQueryBuilder $query) { $uniqueMounts[$id] = $mount; } } + $uniqueMounts = array_values($uniqueMounts); $mountIds = array_map(function ($mount) { @@ -407,6 +412,7 @@ private function getMountsFromQuery(IQueryBuilder $query) { $mount['applicable'] = $applicable; $mount['config'] = $config; $mount['options'] = $options; + return $mount; }, $uniqueMounts, $applicable, $config, $options); } @@ -423,6 +429,7 @@ private function selectForMounts($table, array $fields, array $mountIds) { if (count($mountIds) === 0) { return []; } + $builder = $this->connection->getQueryBuilder(); $fields[] = 'mount_id'; $placeHolders = array_map(function ($id) use ($builder) { @@ -440,12 +447,15 @@ private function selectForMounts($table, array $fields, array $mountIds) { foreach ($mountIds as $mountId) { $result[$mountId] = []; } + foreach ($rows as $row) { if (isset($row['type'])) { $row['type'] = (int)$row['type']; } + $result[$row['mount_id']][] = $row; } + return $result; } @@ -473,6 +483,7 @@ public function getConfigForMounts($mountIds) { public function getOptionsForMounts($mountIds) { $mountOptions = $this->selectForMounts('external_options', ['key', 'value'], $mountIds); $optionsMap = array_map([$this, 'createKeyValueMap'], $mountOptions); + return array_map(function (array $options) { return array_map(function ($option) { return json_decode($option); @@ -489,6 +500,7 @@ private function createKeyValueMap(array $keyValuePairs) { if ($pair['key'] === 'password') { $pair['value'] = $this->decryptValue($pair['value']); } + return $pair; }, $keyValuePairs); $keys = array_map(function ($pair) { diff --git a/apps/files_external/lib/Service/GlobalStoragesService.php b/apps/files_external/lib/Service/GlobalStoragesService.php index c799007cc6dd4..e72e46c0e9162 100644 --- a/apps/files_external/lib/Service/GlobalStoragesService.php +++ b/apps/files_external/lib/Service/GlobalStoragesService.php @@ -32,6 +32,7 @@ protected function triggerHooks(StorageConfig $storage, $signal) { \OCA\Files_External\MountConfig::MOUNT_TYPE_USER, ['all'] ); + return; } @@ -62,6 +63,7 @@ protected function triggerChangeHooks(StorageConfig $oldStorage, StorageConfig $ if ($oldStorage->getMountPoint() !== $newStorage->getMountPoint()) { $this->triggerHooks($oldStorage, Filesystem::signal_delete_mount); $this->triggerHooks($newStorage, Filesystem::signal_create_mount); + return; } diff --git a/apps/files_external/lib/Service/LegacyStoragesService.php b/apps/files_external/lib/Service/LegacyStoragesService.php index ac9273f2afcad..f2163916a2e00 100644 --- a/apps/files_external/lib/Service/LegacyStoragesService.php +++ b/apps/files_external/lib/Service/LegacyStoragesService.php @@ -37,12 +37,13 @@ protected function populateStorageConfigWithLegacyOptions( &$storageConfig, $mountType, $applicable, - $storageOptions + $storageOptions, ) { $backend = $this->backendService->getBackend($storageOptions['backend']); if (!$backend) { throw new \UnexpectedValueException('Invalid backend ' . $storageOptions['backend']); } + $storageConfig->setBackend($backend); if (isset($storageOptions['authMechanism']) && $storageOptions['authMechanism'] !== 'builtin::builtin') { $authMechanism = $this->backendService->getAuthMechanism($storageOptions['authMechanism']); @@ -50,17 +51,21 @@ protected function populateStorageConfigWithLegacyOptions( $authMechanism = $backend->getLegacyAuthMechanism($storageOptions); $storageOptions['authMechanism'] = 'null'; // to make error handling easier } + if (!$authMechanism) { throw new \UnexpectedValueException('Invalid authentication mechanism ' . $storageOptions['authMechanism']); } + $storageConfig->setAuthMechanism($authMechanism); $storageConfig->setBackendOptions($storageOptions['options']); if (isset($storageOptions['mountOptions'])) { $storageConfig->setMountOptions($storageOptions['mountOptions']); } + if (!isset($storageOptions['priority'])) { $storageOptions['priority'] = $backend->getPriority(); } + $storageConfig->setPriority($storageOptions['priority']); if ($mountType === \OCA\Files_External\MountConfig::MOUNT_TYPE_USER) { $applicableUsers = $storageConfig->getApplicableUsers(); @@ -73,6 +78,7 @@ protected function populateStorageConfigWithLegacyOptions( $applicableGroups[] = $applicable; $storageConfig->setApplicableGroups($applicableGroups); } + return $storageConfig; } @@ -126,6 +132,7 @@ public function getAllStorages() { \OC::$server->get(LoggerInterface::class)->error('Could not parse mount point "' . $rootMountPath . '"', ['app' => 'files_external']); continue; } + $relativeMountPath = rtrim($parts[2], '/'); // note: we cannot do this after the loop because the decrypted config // options might be needed for the config hash @@ -133,14 +140,17 @@ public function getAllStorages() { if (!isset($storageOptions['backend'])) { $storageOptions['backend'] = $storageOptions['class']; // legacy compat } + if (!isset($storageOptions['authMechanism'])) { $storageOptions['authMechanism'] = null; // ensure config hash works } + if (isset($storageOptions['id'])) { $configId = (int)$storageOptions['id']; if (isset($storages[$configId])) { $currentStorage = $storages[$configId]; } + $hasId = true; } else { // missing id in legacy config, need to generate @@ -152,11 +162,13 @@ public function getAllStorages() { $currentStorage = $storagesWithConfigHash[$configId]; } } + if (is_null($currentStorage)) { // create new $currentStorage = new StorageConfig($configId); $currentStorage->setMountPoint($relativeMountPath); } + try { $this->populateStorageConfigWithLegacyOptions( $currentStorage, @@ -185,6 +197,7 @@ public function getAllStorages() { $storage->getBackend()->validateStorageDefinition($storage); $storage->getAuthMechanism()->validateStorageDefinition($storage); } + return $storages; } } diff --git a/apps/files_external/lib/Service/StoragesService.php b/apps/files_external/lib/Service/StoragesService.php index 09f7ea954b494..82029e692f729 100644 --- a/apps/files_external/lib/Service/StoragesService.php +++ b/apps/files_external/lib/Service/StoragesService.php @@ -50,7 +50,7 @@ public function __construct( BackendService $backendService, DBConfigService $dbConfigService, IUserMountCache $userMountCache, - IEventDispatcher $eventDispatcher + IEventDispatcher $eventDispatcher, ) { $this->backendService = $backendService; $this->dbConfig = $dbConfigService; @@ -90,6 +90,7 @@ protected function getStorageConfigFromDBMount(array $mount) { ); $config->setType($mount['type']); $config->setId((int)$mount['mount_id']); + return $config; } catch (\UnexpectedValueException $e) { // don't die if a storage backend doesn't exist @@ -97,12 +98,14 @@ protected function getStorageConfigFromDBMount(array $mount) { 'app' => 'files_external', 'exception' => $e, ]); + return null; } catch (\InvalidArgumentException $e) { \OC::$server->get(LoggerInterface::class)->error('Could not load storage.', [ 'app' => 'files_external', 'exception' => $e, ]); + return null; } } @@ -192,6 +195,7 @@ protected function validateStorage(StorageConfig $storage) { // not permitted to use backend return false; } + if (!$authMechanism->isVisibleFor($this->getVisibilityType())) { // not permitted to use auth mechanism return false; @@ -237,12 +241,15 @@ public function addStorage(StorageConfig $newStorage) { foreach ($newStorage->getApplicableUsers() as $user) { $this->dbConfig->addApplicable($configId, DBConfigService::APPLICABLE_TYPE_USER, $user); } + foreach ($newStorage->getApplicableGroups() as $group) { $this->dbConfig->addApplicable($configId, DBConfigService::APPLICABLE_TYPE_GROUP, $group); } + foreach ($newStorage->getBackendOptions() as $key => $value) { $this->dbConfig->setConfig($configId, $key, $value); } + foreach ($newStorage->getMountOptions() as $key => $value) { $this->dbConfig->setOption($configId, $key, $value); } @@ -257,6 +264,7 @@ public function addStorage(StorageConfig $newStorage) { $this->triggerHooks($newStorage, Filesystem::signal_create_mount); $newStorage->setStatus(StorageNotAvailableException::STATUS_SUCCESS); + return $newStorage; } @@ -282,16 +290,18 @@ public function createStorage( $mountOptions = null, $applicableUsers = null, $applicableGroups = null, - $priority = null + $priority = null, ) { $backend = $this->backendService->getBackend($backendIdentifier); if (!$backend) { $backend = new InvalidBackend($backendIdentifier); } + $authMechanism = $this->backendService->getAuthMechanism($authMechanismIdentifier); if (!$authMechanism) { $authMechanism = new InvalidAuth($authMechanismIdentifier); } + $newStorage = new StorageConfig(); $newStorage->setMountPoint($mountPoint); $newStorage->setBackend($backend); @@ -300,12 +310,15 @@ public function createStorage( if (isset($mountOptions)) { $newStorage->setMountOptions($mountOptions); } + if (isset($applicableUsers)) { $newStorage->setApplicableUsers($applicableUsers); } + if (isset($applicableGroups)) { $newStorage->setApplicableGroups($applicableGroups); } + if (isset($priority)) { $newStorage->setPriority($priority); } @@ -393,12 +406,15 @@ public function updateStorage(StorageConfig $updatedStorage) { foreach ($removedUsers as $user) { $this->dbConfig->removeApplicable($id, DBConfigService::APPLICABLE_TYPE_USER, $user); } + foreach ($removedGroups as $group) { $this->dbConfig->removeApplicable($id, DBConfigService::APPLICABLE_TYPE_GROUP, $group); } + foreach ($addedUsers as $user) { $this->dbConfig->addApplicable($id, DBConfigService::APPLICABLE_TYPE_USER, $user); } + foreach ($addedGroups as $group) { $this->dbConfig->addApplicable($id, DBConfigService::APPLICABLE_TYPE_GROUP, $group); } @@ -417,6 +433,7 @@ public function updateStorage(StorageConfig $updatedStorage) { $this->dbConfig->setConfig($id, $key, $value); } } + foreach ($changedOptions as $key => $value) { $this->dbConfig->setOption($id, $key, $value); } diff --git a/apps/files_external/lib/Service/UserGlobalStoragesService.php b/apps/files_external/lib/Service/UserGlobalStoragesService.php index 58590b8d682d1..bddb1653ef8a1 100644 --- a/apps/files_external/lib/Service/UserGlobalStoragesService.php +++ b/apps/files_external/lib/Service/UserGlobalStoragesService.php @@ -37,7 +37,7 @@ public function __construct( IUserSession $userSession, IGroupManager $groupManager, IUserMountCache $userMountCache, - IEventDispatcher $eventDispatcher + IEventDispatcher $eventDispatcher, ) { parent::__construct($backendService, $dbConfig, $userMountCache, $eventDispatcher); $this->userSession = $userSession; @@ -66,6 +66,7 @@ protected function readDBConfig() { } else { $groupMounts = []; } + return array_merge($userMounts, $groupMounts, $globalMounts); } @@ -112,6 +113,7 @@ public function getUniqueStorages() { } } } + return $item; }); $result[$storage->getID()] = $storage; @@ -134,9 +136,11 @@ protected function getPriorityType(StorageConfig $storage) { if ($applicableUsers && $applicableUsers[0] !== 'all') { return 2; } + if ($applicableGroups) { return 1; } + return 0; } @@ -147,15 +151,18 @@ protected function isApplicable(StorageConfig $config) { if (count($applicableUsers) === 0 && count($applicableGroups) === 0) { return true; } + if (in_array($this->getUser()->getUID(), $applicableUsers, true)) { return true; } + $groupIds = $this->groupManager->getUserGroupIds($this->getUser()); foreach ($groupIds as $groupId) { if (in_array($groupId, $applicableGroups, true)) { return true; } } + return false; } @@ -170,9 +177,11 @@ public function getAllStoragesForUser(?IUser $user = null) { if (is_null($user)) { $user = $this->getUser(); } + if (is_null($user)) { return []; } + $groupIds = $this->groupManager->getUserGroupIds($user); $mounts = $this->dbConfig->getMountsForUser($user->getUID(), $groupIds); $configs = array_map([$this, 'getStorageConfigFromDBMount'], $mounts); @@ -185,6 +194,7 @@ public function getAllStoragesForUser(?IUser $user = null) { }, $configs); $storages = array_combine($keys, $configs); + return array_filter($storages, [$this, 'validateStorage']); } } diff --git a/apps/files_external/lib/Service/UserStoragesService.php b/apps/files_external/lib/Service/UserStoragesService.php index ba67815636828..7f8e6224199ff 100644 --- a/apps/files_external/lib/Service/UserStoragesService.php +++ b/apps/files_external/lib/Service/UserStoragesService.php @@ -34,7 +34,7 @@ public function __construct( DBConfigService $dbConfig, IUserSession $userSession, IUserMountCache $userMountCache, - IEventDispatcher $eventDispatcher + IEventDispatcher $eventDispatcher, ) { $this->userSession = $userSession; parent::__construct($backendService, $dbConfig, $userMountCache, $eventDispatcher); @@ -108,6 +108,7 @@ public function updateStorage(StorageConfig $updatedStorage) { $this->getStorage($updatedStorage->getId()); $updatedStorage->setApplicableUsers([$this->getUser()->getUID()]); + return parent::updateStorage($updatedStorage); } diff --git a/apps/files_external/lib/Service/UserTrait.php b/apps/files_external/lib/Service/UserTrait.php index 25713894e1ffb..395768ddce8f5 100644 --- a/apps/files_external/lib/Service/UserTrait.php +++ b/apps/files_external/lib/Service/UserTrait.php @@ -31,6 +31,7 @@ protected function getUser() { if ($this->user) { return $this->user; } + return $this->userSession->getUser(); } diff --git a/apps/files_external/lib/Settings/Admin.php b/apps/files_external/lib/Settings/Admin.php index 63a420e749e6a..707f7704702c3 100644 --- a/apps/files_external/lib/Settings/Admin.php +++ b/apps/files_external/lib/Settings/Admin.php @@ -30,7 +30,7 @@ public function __construct( IManager $encryptionManager, GlobalStoragesService $globalStoragesService, BackendService $backendService, - GlobalAuth $globalAuth + GlobalAuth $globalAuth, ) { $this->encryptionManager = $encryptionManager; $this->globalStoragesService = $globalStoragesService; diff --git a/apps/files_external/lib/Settings/Personal.php b/apps/files_external/lib/Settings/Personal.php index ecd56e632a98b..f84051626a222 100644 --- a/apps/files_external/lib/Settings/Personal.php +++ b/apps/files_external/lib/Settings/Personal.php @@ -35,7 +35,7 @@ public function __construct( UserGlobalStoragesService $userGlobalStoragesService, BackendService $backendService, GlobalAuth $globalAuth, - IUserSession $userSession + IUserSession $userSession, ) { $this->encryptionManager = $encryptionManager; $this->userGlobalStoragesService = $userGlobalStoragesService; diff --git a/apps/files_external/lib/Settings/PersonalSection.php b/apps/files_external/lib/Settings/PersonalSection.php index 41db1fcd9062f..f72ef95ad7f04 100644 --- a/apps/files_external/lib/Settings/PersonalSection.php +++ b/apps/files_external/lib/Settings/PersonalSection.php @@ -26,7 +26,7 @@ public function __construct( IL10N $l, IUserSession $userSession, UserGlobalStoragesService $userGlobalStoragesService, - BackendService $backendService + BackendService $backendService, ) { parent::__construct($url, $l); $this->userSession = $userSession; diff --git a/apps/files_external/templates/settings.php b/apps/files_external/templates/settings.php index c4d14ecb2b4ed..9eed78f3ebbf8 100644 --- a/apps/files_external/templates/settings.php +++ b/apps/files_external/templates/settings.php @@ -36,6 +36,7 @@ script('files_external', $script); } } + foreach ($_['authMechanisms'] as $authMechanism) { /** @var AuthMechanism $authMechanism */ $scripts = $authMechanism->getCustomJs(); @@ -49,6 +50,7 @@ function writeParameterInput($parameter, $options, $classes = []) { if (isset($options[$parameter->getName()])) { $value = $options[$parameter->getName()]; } + $placeholder = $parameter->getText(); $is_optional = $parameter->isFlagSet(DefinitionParameter::FLAG_OPTIONAL); @@ -56,9 +58,13 @@ function writeParameterInput($parameter, $options, $classes = []) { case DefinitionParameter::VALUE_PASSWORD: ?> + } + + ?> class="" + class="" data-parameter="getName()); ?>" value="" placeholder="" @@ -71,9 +77,13 @@ function writeParameterInput($parameter, $options, $classes = []) { @@ -82,7 +92,9 @@ function writeParameterInput($parameter, $options, $classes = []) { break; case DefinitionParameter::VALUE_HIDDEN: ?> class="" + class="" data-parameter="getName()); ?>" value="" /> @@ -91,9 +103,13 @@ function writeParameterInput($parameter, $options, $classes = []) { default: ?> + } + + ?> class="" + class="" data-parameter="getName()); ?>" value="" placeholder="" @@ -116,8 +132,10 @@ function writeParameterInput($parameter, $options, $classes = []) {

t('External storage enables you to mount external storage services and devices as secondary Nextcloud storage devices. You may also allow people to mount their own external storage services.')); ?>

+ print_unescaped('' . $_['dependencies'] . ''); + } + + ?> '> @@ -127,8 +145,10 @@ function writeParameterInput($parameter, $options, $classes = []) { '.$l->t('Available for').''); - } ?> + print_unescaped(''); + } + + ?> @@ -143,7 +163,9 @@ function writeParameterInput($parameter, $options, $classes = []) { style="display: none;" - + >
t('Authentication')); ?> t('Configuration')); ?> ' . $l->t('Available for') . '     
@@ -168,7 +190,9 @@ function writeParameterInput($parameter, $options, $classes = []) { getDeprecateTo() || (!$canCreateNewLocalStorage && $backend->getIdentifier() == 'local')) { continue; - } // ignore deprecated backends?> + } + + // ignore deprecated backends?> @@ -177,7 +201,9 @@ function writeParameterInput($parameter, $options, $classes = []) { - +
@@ -198,10 +224,14 @@ function writeParameterInput($parameter, $options, $classes = []) { /> + } + + ?> /> -

class="hidden"> +

class="hidden"> isAllowedVisibleFor(BackendService::VISIBILITY_PERSONAL); @@ -210,11 +240,15 @@ function writeParameterInput($parameter, $options, $classes = []) { getDeprecateTo()): ?> - + isVisibleFor(BackendService::VISIBILITY_PERSONAL)) { print_unescaped(' checked="checked"'); - } ?> /> + } + + ?> />
@@ -228,7 +262,9 @@ function writeParameterInput($parameter, $options, $classes = []) { id="global_credentials" method="post" class=""> + } + + ?>">

t('Global credentials')); ?>

t('Global credentials can be used to authenticate with multiple external storages that have the same credentials.')); ?>

$value) { $input->setArgument($key, $value); } + foreach ($options as $key => $value) { $input->setOption($key, $value); } + return $input; } protected function executeCommand(Command $command, Input $input) { $output = new BufferedOutput(); $this->invokePrivate($command, 'execute', [$input, $output]); + return $output->fetch(); } } diff --git a/apps/files_external/tests/Controller/StoragesControllerTest.php b/apps/files_external/tests/Controller/StoragesControllerTest.php index 9e9595152aded..12ab8e628570a 100644 --- a/apps/files_external/tests/Controller/StoragesControllerTest.php +++ b/apps/files_external/tests/Controller/StoragesControllerTest.php @@ -47,9 +47,10 @@ protected function getBackendMock($class = '\OCA\Files_External\Lib\Backend\SMB' $backend->method('getStorageClass') ->willReturn($storageClass); $backend->method('getIdentifier') - ->willReturn('identifier:'.$class); + ->willReturn('identifier:' . $class); $backend->method('getParameters') ->willReturn([]); + return $backend; } @@ -63,7 +64,7 @@ protected function getAuthMechMock($scheme = 'null', $class = '\OCA\Files_Extern $authMech->method('getScheme') ->willReturn($scheme); $authMech->method('getIdentifier') - ->willReturn('identifier:'.$class); + ->willReturn('identifier:' . $class); $authMech->method('getParameters') ->willReturn([]); diff --git a/apps/files_external/tests/Service/BackendServiceTest.php b/apps/files_external/tests/Service/BackendServiceTest.php index 6d9754a533579..58d8e334006b7 100644 --- a/apps/files_external/tests/Service/BackendServiceTest.php +++ b/apps/files_external/tests/Service/BackendServiceTest.php @@ -32,8 +32,9 @@ protected function getBackendMock($class) { $backend = $this->getMockBuilder(Backend::class) ->disableOriginalConstructor() ->getMock(); - $backend->method('getIdentifier')->willReturn('identifier:'.$class); - $backend->method('getIdentifierAliases')->willReturn(['identifier:'.$class]); + $backend->method('getIdentifier')->willReturn('identifier:' . $class); + $backend->method('getIdentifierAliases')->willReturn(['identifier:' . $class]); + return $backend; } @@ -46,8 +47,9 @@ protected function getAuthMechanismMock($class) { $backend = $this->getMockBuilder(AuthMechanism::class) ->disableOriginalConstructor() ->getMock(); - $backend->method('getIdentifier')->willReturn('identifier:'.$class); - $backend->method('getIdentifierAliases')->willReturn(['identifier:'.$class]); + $backend->method('getIdentifier')->willReturn('identifier:' . $class); + $backend->method('getIdentifierAliases')->willReturn(['identifier:' . $class]); + return $backend; } diff --git a/apps/files_external/tests/Service/DBConfigServiceTest.php b/apps/files_external/tests/Service/DBConfigServiceTest.php index 968c166a50587..190ee0b38b066 100644 --- a/apps/files_external/tests/Service/DBConfigServiceTest.php +++ b/apps/files_external/tests/Service/DBConfigServiceTest.php @@ -36,12 +36,14 @@ protected function tearDown(): void { foreach ($this->mounts as $mount) { $this->dbConfig->removeMount($mount); } + $this->mounts = []; } private function addMount($mountPoint, $storageBackend, $authBackend, $priority, $type) { $id = $this->dbConfig->addMount($mountPoint, $storageBackend, $authBackend, $priority, $type); $this->mounts[] = $id; + return $id; } diff --git a/apps/files_external/tests/Service/StoragesServiceTest.php b/apps/files_external/tests/Service/StoragesServiceTest.php index 40cba73a74fe2..8602b2bf80697 100644 --- a/apps/files_external/tests/Service/StoragesServiceTest.php +++ b/apps/files_external/tests/Service/StoragesServiceTest.php @@ -33,6 +33,7 @@ class CleaningDBConfig extends DBConfigService { public function addMount($mountPoint, $storageBackend, $authBackend, $priority, $type) { $id = parent::addMount($mountPoint, $storageBackend, $authBackend, $priority, $type); // TODO: Change the autogenerated stub $this->mountIds[] = $id; + return $id; } @@ -112,6 +113,7 @@ protected function setUp(): void { if (isset($authMechanisms[$class])) { return $authMechanisms[$class]; } + return null; }); $this->backendService->method('getAuthMechanismsByScheme') @@ -137,6 +139,7 @@ protected function setUp(): void { if (isset($backends[$backendClass])) { return $backends[$backendClass]; } + return null; }); $this->backendService->method('getBackends') @@ -177,6 +180,7 @@ protected function getBackendMock($class = '\OCA\Files_External\Lib\Backend\SMB' ->willReturn($storageClass); $backend->method('getIdentifier') ->willReturn('identifier:' . $class); + return $backend; } @@ -204,36 +208,45 @@ protected function makeStorageConfig($data) { if (isset($data['id'])) { $storage->setId($data['id']); } + $storage->setMountPoint($data['mountPoint']); if (!isset($data['backend'])) { // data providers are run before $this->backendService is initialised // so $data['backend'] can be specified directly $data['backend'] = $this->backendService->getBackend($data['backendIdentifier']); } + if (!isset($data['backend'])) { throw new \Exception('oops, no backend'); } + if (!isset($data['authMechanism'])) { $data['authMechanism'] = $this->backendService->getAuthMechanism($data['authMechanismIdentifier']); } + if (!isset($data['authMechanism'])) { throw new \Exception('oops, no auth mechanism'); } + $storage->setBackend($data['backend']); $storage->setAuthMechanism($data['authMechanism']); $storage->setBackendOptions($data['backendOptions']); if (isset($data['applicableUsers'])) { $storage->setApplicableUsers($data['applicableUsers']); } + if (isset($data['applicableGroups'])) { $storage->setApplicableGroups($data['applicableGroups']); } + if (isset($data['priority'])) { $storage->setPriority($data['priority']); } + if (isset($data['mountOptions'])) { $storage->setMountOptions($data['mountOptions']); } + return $storage; } diff --git a/apps/files_external/tests/Service/UserGlobalStoragesServiceTest.php b/apps/files_external/tests/Service/UserGlobalStoragesServiceTest.php index 992960a6ea10c..6f6c4424ce57c 100644 --- a/apps/files_external/tests/Service/UserGlobalStoragesServiceTest.php +++ b/apps/files_external/tests/Service/UserGlobalStoragesServiceTest.php @@ -64,6 +64,7 @@ protected function setUp(): void { return true; } } + return false; }); $this->groupManager->method('getUserGroupIds') @@ -243,7 +244,7 @@ public function getUniqueStoragesProvider() { public function testGetUniqueStorages( $priority1, $applicableUsers1, $applicableGroups1, $priority2, $applicableUsers2, $applicableGroups2, - $expectedPrecedence + $expectedPrecedence, ): void { $backend = $this->backendService->getBackend('identifier:\OCA\Files_External\Lib\Backend\SMB'); $backend->method('isVisibleFor') diff --git a/apps/files_external/tests/Storage/Amazons3MultiPartTest.php b/apps/files_external/tests/Storage/Amazons3MultiPartTest.php index 1ac18406d2417..dac16515db93b 100644 --- a/apps/files_external/tests/Storage/Amazons3MultiPartTest.php +++ b/apps/files_external/tests/Storage/Amazons3MultiPartTest.php @@ -26,6 +26,7 @@ protected function setUp(): void { if (! is_array($this->config) or ! $this->config['run']) { $this->markTestSkipped('AmazonS3 backend not configured'); } + $this->instance = new AmazonS3($this->config + [ 'putSizeLimit' => 1, 'copySizeLimit' => 1, diff --git a/apps/files_external/tests/Storage/Amazons3Test.php b/apps/files_external/tests/Storage/Amazons3Test.php index fd7fd9225c930..a8bf42e6e5e76 100644 --- a/apps/files_external/tests/Storage/Amazons3Test.php +++ b/apps/files_external/tests/Storage/Amazons3Test.php @@ -27,6 +27,7 @@ protected function setUp(): void { if (! is_array($this->config) or ! $this->config['run']) { $this->markTestSkipped('AmazonS3 backend not configured'); } + $this->instance = new AmazonS3($this->config); } diff --git a/apps/files_external/tests/Storage/FtpTest.php b/apps/files_external/tests/Storage/FtpTest.php index c6a1d8f77ba79..e90e13d5d0db9 100644 --- a/apps/files_external/tests/Storage/FtpTest.php +++ b/apps/files_external/tests/Storage/FtpTest.php @@ -26,6 +26,7 @@ protected function setUp(): void { if (! is_array($this->config) or ! $this->config['run']) { $this->markTestSkipped('FTP backend not configured'); } + $rootInstance = new FTP($this->config); $rootInstance->mkdir($id); @@ -37,6 +38,7 @@ protected function tearDown(): void { if ($this->instance) { $this->instance->rmdir(''); } + $this->instance = null; parent::tearDown(); diff --git a/apps/files_external/tests/Storage/OwncloudTest.php b/apps/files_external/tests/Storage/OwncloudTest.php index 28041a665f845..c9a9bffd0cb5c 100644 --- a/apps/files_external/tests/Storage/OwncloudTest.php +++ b/apps/files_external/tests/Storage/OwncloudTest.php @@ -26,6 +26,7 @@ protected function setUp(): void { if (! is_array($this->config) or ! isset($this->config['owncloud']) or ! $this->config['owncloud']['run']) { $this->markTestSkipped('Nextcloud backend not configured'); } + $this->config['owncloud']['root'] .= '/' . $id; //make sure we have an new empty folder to work in $this->instance = new OwnCloud($this->config['owncloud']); $this->instance->mkdir('/'); diff --git a/apps/files_external/tests/Storage/SFTP_KeyTest.php b/apps/files_external/tests/Storage/SFTP_KeyTest.php index 9be3cb7d3e841..d834fb6bfe1a5 100644 --- a/apps/files_external/tests/Storage/SFTP_KeyTest.php +++ b/apps/files_external/tests/Storage/SFTP_KeyTest.php @@ -26,6 +26,7 @@ protected function setUp(): void { if (! is_array($this->config) or ! isset($this->config['sftp_key']) or ! $this->config['sftp_key']['run']) { $this->markTestSkipped('SFTP with key backend not configured'); } + // Make sure we have an new empty folder to work in $this->config['sftp_key']['root'] .= '/' . $id; $this->instance = new SFTP_Key($this->config['sftp_key']); diff --git a/apps/files_external/tests/Storage/SftpTest.php b/apps/files_external/tests/Storage/SftpTest.php index cc29486f4268b..e00f472ce70a3 100644 --- a/apps/files_external/tests/Storage/SftpTest.php +++ b/apps/files_external/tests/Storage/SftpTest.php @@ -31,6 +31,7 @@ protected function setUp(): void { if (!is_array($this->config) or !$this->config['run']) { $this->markTestSkipped('SFTP backend not configured'); } + $this->config['root'] .= '/' . $id; //make sure we have an new empty folder to work in $this->instance = new SFTP($this->config); $this->instance->mkdir('/'); diff --git a/apps/files_external/tests/Storage/SmbTest.php b/apps/files_external/tests/Storage/SmbTest.php index d5a83905112f6..20dc8cd9aea36 100644 --- a/apps/files_external/tests/Storage/SmbTest.php +++ b/apps/files_external/tests/Storage/SmbTest.php @@ -33,9 +33,11 @@ protected function setUp(): void { if (!is_array($config) or !$config['run']) { $this->markTestSkipped('Samba backend not configured'); } + if (substr($config['root'], -1, 1) != '/') { $config['root'] .= '/'; } + $config['root'] .= $id; //make sure we have an new empty folder to work in $this->instance = new SMB($config); $this->instance->mkdir('/'); @@ -86,6 +88,7 @@ public function testNotifyGetChanges(): void { sleep(1); } } + throw $lastError; } @@ -108,6 +111,7 @@ private function tryTestNotifyGetChanges(): void { $count++; sleep(1); } + $notifyHandler->stop(); // depending on the server environment, the initial create might be detected as a change instead diff --git a/apps/files_external/tests/Storage/SwiftTest.php b/apps/files_external/tests/Storage/SwiftTest.php index c21c8c6f5064b..ad268fc8e6bae 100644 --- a/apps/files_external/tests/Storage/SwiftTest.php +++ b/apps/files_external/tests/Storage/SwiftTest.php @@ -31,6 +31,7 @@ protected function setUp(): void { if (!is_array($this->config) or !$this->config['run']) { $this->markTestSkipped('OpenStack Object Storage backend not configured'); } + $this->instance = new Swift($this->config); } diff --git a/apps/files_external/tests/Storage/WebdavTest.php b/apps/files_external/tests/Storage/WebdavTest.php index 872f1b5d0e295..37c98ba05bc83 100644 --- a/apps/files_external/tests/Storage/WebdavTest.php +++ b/apps/files_external/tests/Storage/WebdavTest.php @@ -25,9 +25,11 @@ protected function setUp(): void { if (!is_array($config) or !$config['run']) { $this->markTestSkipped('WebDAV backend not configured'); } + if (isset($config['wait'])) { $this->waitDelay = $config['wait']; } + $config['root'] .= '/' . $id; //make sure we have an new empty folder to work in $this->instance = new DAV($config); $this->instance->mkdir('/'); diff --git a/apps/files_reminders/lib/Command/ListCommand.php b/apps/files_reminders/lib/Command/ListCommand.php index 118d00c45d3b5..910c32f09eb0f 100644 --- a/apps/files_reminders/lib/Command/ListCommand.php +++ b/apps/files_reminders/lib/Command/ListCommand.php @@ -74,6 +74,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int ), '', ); + return 0; default: if (empty($reminders)) { @@ -96,6 +97,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $reminders, ), ); + return 0; } } diff --git a/apps/files_reminders/lib/Controller/ApiController.php b/apps/files_reminders/lib/Controller/ApiController.php index a7d02b936c95f..ffceca697ded6 100644 --- a/apps/files_reminders/lib/Controller/ApiController.php +++ b/apps/files_reminders/lib/Controller/ApiController.php @@ -56,11 +56,13 @@ public function get(int $fileId): DataResponse { $reminderData = [ 'dueDate' => $reminder->getDueDate()->format(DateTimeInterface::ATOM), // ISO 8601 ]; + return new DataResponse($reminderData, Http::STATUS_OK); } catch (DoesNotExistException $e) { $reminderData = [ 'dueDate' => null, ]; + return new DataResponse($reminderData, Http::STATUS_OK); } } @@ -98,6 +100,7 @@ public function set(int $fileId, string $dueDate): DataResponse { if ($created) { return new DataResponse([], Http::STATUS_CREATED); } + return new DataResponse([], Http::STATUS_OK); } catch (NodeNotFoundException $e) { return new DataResponse([], Http::STATUS_NOT_FOUND); diff --git a/apps/files_reminders/lib/Db/ReminderMapper.php b/apps/files_reminders/lib/Db/ReminderMapper.php index 16859585bdfe9..9b03f47c6d1e7 100644 --- a/apps/files_reminders/lib/Db/ReminderMapper.php +++ b/apps/files_reminders/lib/Db/ReminderMapper.php @@ -36,6 +36,7 @@ public function markNotified(Reminder $reminder): Reminder { $reminderUpdate = new Reminder(); $reminderUpdate->setId($reminder->getId()); $reminderUpdate->setNotified(true); + return $this->update($reminderUpdate); } diff --git a/apps/files_reminders/lib/Model/RichReminder.php b/apps/files_reminders/lib/Model/RichReminder.php index 4f221252717d9..a2c959a9c273b 100644 --- a/apps/files_reminders/lib/Model/RichReminder.php +++ b/apps/files_reminders/lib/Model/RichReminder.php @@ -32,6 +32,7 @@ public function getNode(): Node { if (!$node) { throw new NodeNotFoundException(); } + return $node; } diff --git a/apps/files_reminders/lib/Service/ReminderService.php b/apps/files_reminders/lib/Service/ReminderService.php index 8bd6887e75477..1f0ec4549c5e1 100644 --- a/apps/files_reminders/lib/Service/ReminderService.php +++ b/apps/files_reminders/lib/Service/ReminderService.php @@ -61,6 +61,7 @@ public function getAll(?IUser $user = null) { $reminders = ($user !== null) ? $this->reminderMapper->findAllForUser($user) : $this->reminderMapper->findAll(); + return array_map( fn (Reminder $reminder) => new RichReminder($reminder, $this->root), $reminders, @@ -79,12 +80,14 @@ public function createOrUpdate(IUser $user, int $fileId, DateTime $dueDate): boo $reminder->setDueDate($dueDate); $reminder->setUpdatedAt($now); $this->reminderMapper->update($reminder); + return false; } catch (DoesNotExistException $e) { $node = $this->root->getUserFolder($user->getUID())->getFirstNodeById($fileId); if (!$node) { throw new NodeNotFoundException(); } + // Create new reminder if no reminder is found $reminder = new Reminder(); $reminder->setUserId($user->getUID()); @@ -93,6 +96,7 @@ public function createOrUpdate(IUser $user, int $fileId, DateTime $dueDate): boo $reminder->setUpdatedAt($now); $reminder->setCreatedAt($now); $this->reminderMapper->insert($reminder); + return true; } } diff --git a/apps/files_sharing/lib/Activity/Providers/Downloads.php b/apps/files_sharing/lib/Activity/Providers/Downloads.php index ac9522ef93b91..7f3e99eeffbee 100644 --- a/apps/files_sharing/lib/Activity/Providers/Downloads.php +++ b/apps/files_sharing/lib/Activity/Providers/Downloads.php @@ -38,6 +38,7 @@ public function parseShortVersion(IEvent $event) { } else { $event->setIcon($this->url->getAbsoluteURL($this->url->imagePath('core', 'actions/download.svg'))); } + $this->setSubjects($event, $subject, $parsedParameters); return $event; @@ -103,6 +104,7 @@ protected function getParsedParameters(IEvent $event) { ], ]; } + return [ 'file' => $this->getFile($parameters[0], $event), ]; diff --git a/apps/files_sharing/lib/Activity/Providers/Groups.php b/apps/files_sharing/lib/Activity/Providers/Groups.php index fffa74258c967..929f055074f08 100644 --- a/apps/files_sharing/lib/Activity/Providers/Groups.php +++ b/apps/files_sharing/lib/Activity/Providers/Groups.php @@ -71,6 +71,7 @@ public function parseShortVersion(IEvent $event) { } else { $event->setIcon($this->url->getAbsoluteURL($this->url->imagePath('core', 'actions/share.svg'))); } + $this->setSubjects($event, $subject, $parsedParameters); return $event; @@ -105,6 +106,7 @@ public function parseLongVersion(IEvent $event, ?IEvent $previousEvent = null) { } else { $event->setIcon($this->url->getAbsoluteURL($this->url->imagePath('core', 'actions/share.svg'))); } + $this->setSubjects($event, $subject, $parsedParameters); return $event; @@ -130,6 +132,7 @@ protected function getParsedParameters(IEvent $event) { 'group' => $this->generateGroupParameter($parameters[1]), ]; } + return []; } @@ -158,6 +161,7 @@ protected function getGroupDisplayName($gid) { if ($group instanceof IGroup) { return $group->getDisplayName(); } + return $gid; } } diff --git a/apps/files_sharing/lib/Activity/Providers/PublicLinks.php b/apps/files_sharing/lib/Activity/Providers/PublicLinks.php index 6cf5c05d8741b..5e93856480276 100644 --- a/apps/files_sharing/lib/Activity/Providers/PublicLinks.php +++ b/apps/files_sharing/lib/Activity/Providers/PublicLinks.php @@ -45,6 +45,7 @@ public function parseShortVersion(IEvent $event) { } else { $event->setIcon($this->url->getAbsoluteURL($this->url->imagePath('core', 'actions/share.svg'))); } + $this->setSubjects($event, $subject, $parsedParameters); return $event; @@ -81,6 +82,7 @@ public function parseLongVersion(IEvent $event, ?IEvent $previousEvent = null) { } else { $event->setIcon($this->url->getAbsoluteURL($this->url->imagePath('core', 'actions/share.svg'))); } + $this->setSubjects($event, $subject, $parsedParameters); return $event; @@ -105,6 +107,7 @@ protected function getParsedParameters(IEvent $event) { 'actor' => $this->getUser($parameters[1]), ]; } + return []; } } diff --git a/apps/files_sharing/lib/Activity/Providers/RemoteShares.php b/apps/files_sharing/lib/Activity/Providers/RemoteShares.php index 1971f935d303f..f5dfa6be32e72 100644 --- a/apps/files_sharing/lib/Activity/Providers/RemoteShares.php +++ b/apps/files_sharing/lib/Activity/Providers/RemoteShares.php @@ -52,6 +52,7 @@ public function parseShortVersion(IEvent $event) { } else { $event->setIcon($this->url->getAbsoluteURL($this->url->imagePath('core', 'actions/share.svg'))); } + $this->setSubjects($event, $subject, $parsedParameters); return $event; @@ -84,6 +85,7 @@ public function parseLongVersion(IEvent $event, ?IEvent $previousEvent = null) { } else { $event->setIcon($this->url->getAbsoluteURL($this->url->imagePath('core', 'actions/share.svg'))); } + $this->setSubjects($event, $subject, $parsedParameters); return $event; @@ -97,6 +99,7 @@ protected function getParsedParameters(IEvent $event) { case self::SUBJECT_REMOTE_SHARE_RECEIVED: case self::SUBJECT_REMOTE_SHARE_UNSHARED: $displayName = (count($parameters) > 2) ? $parameters[2] : ''; + return [ 'file' => [ 'type' => 'pending-federated-share', @@ -111,11 +114,13 @@ protected function getParsedParameters(IEvent $event) { if (!is_array($fileParameter)) { $fileParameter = [$event->getObjectId() => $event->getObjectName()]; } + return [ 'file' => $this->getFile($fileParameter), 'user' => $this->getUser($parameters[0]), ]; } + throw new \InvalidArgumentException(); } } diff --git a/apps/files_sharing/lib/Activity/Providers/Users.php b/apps/files_sharing/lib/Activity/Providers/Users.php index 6c136d1f38357..167495add01be 100644 --- a/apps/files_sharing/lib/Activity/Providers/Users.php +++ b/apps/files_sharing/lib/Activity/Providers/Users.php @@ -59,6 +59,7 @@ public function parseShortVersion(IEvent $event) { } else { $event->setIcon($this->url->getAbsoluteURL($this->url->imagePath('core', 'actions/share.svg'))); } + $this->setSubjects($event, $subject, $parsedParameters); return $event; @@ -103,6 +104,7 @@ public function parseLongVersion(IEvent $event, ?IEvent $previousEvent = null) { } else { $event->setIcon($this->url->getAbsoluteURL($this->url->imagePath('core', 'actions/share.svg'))); } + $this->setSubjects($event, $subject, $parsedParameters); return $event; @@ -137,6 +139,7 @@ protected function getParsedParameters(IEvent $event) { 'actor' => $this->getUser($parameters[1]), ]; } + return []; } } diff --git a/apps/files_sharing/lib/AppInfo/Application.php b/apps/files_sharing/lib/AppInfo/Application.php index 98c2d280856e0..8bf77b4911ddc 100644 --- a/apps/files_sharing/lib/AppInfo/Application.php +++ b/apps/files_sharing/lib/AppInfo/Application.php @@ -139,6 +139,7 @@ public function registerEventsScripts(IEventDispatcher $dispatcher): void { if (!$event instanceof OldGenericEvent) { return; } + /** @var Listener $listener */ $listener = $this->getContainer()->query(Listener::class); $listener->userAddedToGroup($event); diff --git a/apps/files_sharing/lib/BackgroundJob/FederatedSharesDiscoverJob.php b/apps/files_sharing/lib/BackgroundJob/FederatedSharesDiscoverJob.php index 9b1b0062ce5fd..ec7766ed43fb6 100644 --- a/apps/files_sharing/lib/BackgroundJob/FederatedSharesDiscoverJob.php +++ b/apps/files_sharing/lib/BackgroundJob/FederatedSharesDiscoverJob.php @@ -44,6 +44,7 @@ public function run($argument) { $this->logger->info('exception while running files_sharing/lib/BackgroundJob/FederatedSharesDiscoverJob', ['exception' => $e]); } } + $result->closeCursor(); } } diff --git a/apps/files_sharing/lib/Cache.php b/apps/files_sharing/lib/Cache.php index 26a9bc7b5248c..51e6793660fcb 100644 --- a/apps/files_sharing/lib/Cache.php +++ b/apps/files_sharing/lib/Cache.php @@ -43,7 +43,7 @@ public function __construct( $storage, ICacheEntry $sourceRootInfo, CacheDependencies $dependencies, - IShare $share + IShare $share, ) { $this->storage = $storage; $this->sourceRootInfo = $sourceRootInfo; @@ -70,8 +70,10 @@ protected function getRoot() { /** @var Jail $currentStorage */ $absoluteRoot = $currentStorage->getJailedPath($absoluteRoot); } + $this->root = $absoluteRoot; } + return $this->root; } @@ -89,6 +91,7 @@ public function getCache(): ICache { return new FailedCache(); } } + return $this->cache; } @@ -104,6 +107,7 @@ public function get($file) { if ($this->rootUnchanged && ($file === '' || $file === $this->sourceRootInfo->getId())) { return $this->formatCacheEntry(clone $this->sourceRootInfo, ''); } + return parent::get($file); } @@ -150,11 +154,13 @@ protected function formatCacheEntry($entry, $path = null) { // (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; } @@ -163,6 +169,7 @@ private function getOwnerDisplayName() { $uid = $this->share->getShareOwner(); $this->ownerDisplayName = $this->displayNameCache->getDisplayName($uid) ?? $uid; } + return $this->ownerDisplayName; } diff --git a/apps/files_sharing/lib/Capabilities.php b/apps/files_sharing/lib/Capabilities.php index 9afa077fac3bc..62c5c6c37452b 100644 --- a/apps/files_sharing/lib/Capabilities.php +++ b/apps/files_sharing/lib/Capabilities.php @@ -141,6 +141,7 @@ public function getCapabilities() { $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'; diff --git a/apps/files_sharing/lib/Collaboration/ShareRecipientSorter.php b/apps/files_sharing/lib/Collaboration/ShareRecipientSorter.php index f383424fd78cb..cb1433554ce81 100644 --- a/apps/files_sharing/lib/Collaboration/ShareRecipientSorter.php +++ b/apps/files_sharing/lib/Collaboration/ShareRecipientSorter.php @@ -32,16 +32,19 @@ public function sort(array &$sortArray, array $context) { 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 */ $node = $userFolder->getFirstNodeById((int)$context['itemId']); if (!$node) { return; } + $al = $this->shareManager->getAccessList($node); foreach ($sortArray as $type => &$byType) { @@ -61,6 +64,7 @@ public function sort(array &$sortArray, array $context) { if ($result === 0) { $result = $a[0] - $b[0]; } + return $result; }); diff --git a/apps/files_sharing/lib/Command/CleanupRemoteStorages.php b/apps/files_sharing/lib/Command/CleanupRemoteStorages.php index bb7f374de5843..aec0defb1e5fa 100644 --- a/apps/files_sharing/lib/Command/CleanupRemoteStorages.php +++ b/apps/files_sharing/lib/Command/CleanupRemoteStorages.php @@ -82,6 +82,7 @@ public function execute(InputInterface $input, OutputInterface $output): int { } } } + return 0; } @@ -151,6 +152,7 @@ public function getRemoteStorages() { while ($row = $result->fetch()) { $remoteStorages[$row['id']] = $row['numeric_id']; } + $result->closeCursor(); return $remoteStorages; @@ -170,6 +172,7 @@ public function getRemoteShareIds() { $remoteShareIds[$row['id']] = 'shared::' . md5($row['share_token'] . '@' . $remote); } + $result->closeCursor(); return $remoteShareIds; diff --git a/apps/files_sharing/lib/Command/ExiprationNotification.php b/apps/files_sharing/lib/Command/ExiprationNotification.php index f4f41dc7011d0..0695e93a5c336 100644 --- a/apps/files_sharing/lib/Command/ExiprationNotification.php +++ b/apps/files_sharing/lib/Command/ExiprationNotification.php @@ -76,6 +76,7 @@ public function execute(InputInterface $input, OutputInterface $output): int { $notification->setUser($share->getSharedBy()); $this->notificationManager->notify($notification); } + return 0; } } diff --git a/apps/files_sharing/lib/Controller/DeletedShareAPIController.php b/apps/files_sharing/lib/Controller/DeletedShareAPIController.php index b61f9995c02f1..5b0f4ac7a5e98 100644 --- a/apps/files_sharing/lib/Controller/DeletedShareAPIController.php +++ b/apps/files_sharing/lib/Controller/DeletedShareAPIController.php @@ -110,6 +110,7 @@ private function formatShare(IShare $share): array { } else { $result['item_type'] = 'file'; } + $result['mimetype'] = $node->getMimetype(); $result['storage_id'] = $node->getStorage()->getId(); $result['storage'] = $node->getStorage()->getCache()->getNumericStorageId(); diff --git a/apps/files_sharing/lib/Controller/PublicPreviewController.php b/apps/files_sharing/lib/Controller/PublicPreviewController.php index 4dc3989f866d3..8fcd97051a995 100644 --- a/apps/files_sharing/lib/Controller/PublicPreviewController.php +++ b/apps/files_sharing/lib/Controller/PublicPreviewController.php @@ -83,7 +83,7 @@ public function getPreview( string $file = '', int $x = 32, int $y = 32, - $a = false + $a = false, ) { if ($token === '' || $x === 0 || $y === 0) { return new DataResponse([], Http::STATUS_BAD_REQUEST); @@ -115,6 +115,7 @@ public function getPreview( $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); @@ -176,6 +177,7 @@ public function directLink(string $token) { $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); diff --git a/apps/files_sharing/lib/Controller/ShareAPIController.php b/apps/files_sharing/lib/Controller/ShareAPIController.php index 9ca9774013c18..31b145298da54 100644 --- a/apps/files_sharing/lib/Controller/ShareAPIController.php +++ b/apps/files_sharing/lib/Controller/ShareAPIController.php @@ -88,7 +88,7 @@ public function __construct( private LoggerInterface $logger, private IProviderFactory $factory, private IMailer $mailer, - ?string $userId = null + ?string $userId = null, ) { parent::__construct($appName, $request); $this->currentUser = $userId; @@ -171,6 +171,7 @@ protected function formatShare(IShare $share, ?Node $recipientNode = null): arra if ($this->canDeleteShare($share) || $this->canDeleteShareFromSelf($share)) { $result['item_permissions'] |= Constants::PERMISSION_DELETE; } + if ($this->canEditShare($share)) { $result['item_permissions'] |= Constants::PERMISSION_UPDATE; } @@ -301,7 +302,6 @@ protected function formatShare(IShare $share, ?Node $recipientNode = null): arra } } - $result['mail_send'] = $share->getMailSend() ? 1 : 0; $result['hide_download'] = $share->getHideDownload() ? 1 : 0; @@ -334,6 +334,7 @@ private function getDisplayNameFromAddressBook(string $query, string $property): $e->getMessage(), ['exception' => $e] ); + return $query; } @@ -416,6 +417,7 @@ private function retrieveFederatedDisplayName(array $userIds, bool $cacheOnly = $e->getMessage(), ['exception' => $e] ); + return []; } @@ -439,6 +441,7 @@ private function getCachedFederatedDisplayName(string $userId, bool $cacheOnly = } $displayName = $this->getDisplayNameFromAddressBook($userId, 'CLOUD'); + return ($displayName === $userId) ? '' : $displayName; } @@ -565,7 +568,7 @@ public function createShare( string $note = '', string $label = '', ?string $attributes = null, - ?string $sendMail = null + ?string $sendMail = null, ): DataResponse { $share = $this->shareManager->newShare(); @@ -667,6 +670,7 @@ public function createShare( if ($shareWith === null || !$this->userManager->userExists($shareWith)) { throw new OCSNotFoundException($this->l->t('Please specify a valid account to share with')); } + $share->setSharedWith($shareWith); $share->setPermissions($permissions); } elseif ($shareType === IShare::TYPE_GROUP) { @@ -678,6 +682,7 @@ public function createShare( if ($shareWith === null || !$this->groupManager->groupExists($shareWith)) { throw new OCSNotFoundException($this->l->t('Please specify a valid group')); } + $share->setSharedWith($shareWith); $share->setPermissions($permissions); } elseif ($shareType === IShare::TYPE_LINK @@ -723,6 +728,7 @@ public function createShare( if ($share->getMailSend() && !$this->mailer->validateMailAddress($shareWith)) { throw new OCSNotFoundException($this->l->t('Please specify a valid email address')); } + $share->setSharedWith($shareWith); } @@ -772,6 +778,7 @@ public function createShare( if ($circle === null) { throw new OCSNotFoundException($this->l->t('Please specify a valid team')); } + $share->setSharedWith($shareWith); $share->setPermissions($permissions); } elseif ($shareType === IShare::TYPE_ROOM) { @@ -895,6 +902,7 @@ private function getSharesInDir(Node $folder): array { if ($share->getSharedBy() === $this->currentUser) { $miniFormatted[] = $format; } + if (!$resharingRight && $this->shareProviderResharingRights($this->currentUser, $share, $folder)) { $resharingRight = true; } @@ -930,7 +938,7 @@ public function getShares( string $reshares = 'false', string $subfiles = 'false', string $path = '', - string $include_tags = 'false' + string $include_tags = 'false', ): DataResponse { $node = null; if ($path !== '') { @@ -978,7 +986,7 @@ private function getFormattedShares( bool $sharedWithMe = false, bool $reShares = false, bool $subFiles = false, - bool $includeTags = false + bool $includeTags = false, ): array { if ($sharedWithMe) { return $this->getSharedWithMe($node, $includeTags); @@ -1094,6 +1102,7 @@ public function getInheritedShares(string $path): DataResponse { $userFolder = $this->rootFolder->getUserFolder($owner); $node = $userFolder->getFirstNodeById($node->getId()); } + $basePath = $userFolder->getPath(); // generate node list for each parent folders @@ -1104,6 +1113,7 @@ public function getInheritedShares(string $path): DataResponse { if ($node->getPath() === $basePath) { break; } + $nodes[] = $node; } @@ -1126,6 +1136,7 @@ public function getInheritedShares(string $path): DataResponse { $share['via_fileid'] = $parent->getId(); $share['via_path'] = $subPath; } + $this->mergeFormattedShares($shares, $getShares); } @@ -1216,6 +1227,7 @@ public function updateShare( if ($attributes !== null) { $share = $this->setShareAttributes($share, $attributes); } + $this->checkInheritedAttributes($share); // Handle mail send @@ -1250,6 +1262,7 @@ public function updateShare( $share->setHideDownload(false); $attributes->setAttribute('permissions', 'download', true); } + $share->setAttributes($attributes); @@ -1317,6 +1330,7 @@ public function updateShare( if (strlen($label) > 255) { throw new OCSBadRequestException('Maximum label length is 255'); } + $share->setLabel($label); } @@ -1346,6 +1360,7 @@ public function updateShare( } catch (\Exception $e) { throw new OCSBadRequestException($e->getMessage(), $e); } + $share->setExpirationDate($expireDate); } @@ -1402,6 +1417,7 @@ public function pendingShares(): DataResponse { $formattedShare = $this->formatShare($share, $node); $formattedShare['path'] = '/' . $share->getNode()->getName(); $formattedShare['permissions'] = 0; + return $formattedShare; } catch (NotFoundException $e) { return null; @@ -1688,7 +1704,6 @@ private function getShareById(string $id): IShare { // Do nothing, just try the other share type } - try { if ($this->shareManager->shareProviderExists(IShare::TYPE_CIRCLE)) { $share = $this->shareManager->getShareById('ocCircleShare:' . $id, $this->currentUser); @@ -1735,6 +1750,7 @@ private function getShareById(string $id): IShare { if (!$this->shareManager->outgoingServer2ServerSharesAllowed()) { throw new ShareNotFound(); } + $share = $this->shareManager->getShareById('ocFederatedSharing:' . $id, $this->currentUser); return $share; @@ -1945,11 +1961,13 @@ private function shareProviderResharingRights(string $userId, IShare $share, $no } else { $sharedWith = substr($share->getSharedWith(), $shareWithStart, $shareWithLength); } + try { $member = \OCA\Circles\Api\v1\Circles::getMember($sharedWith, $userId, 1); if ($member->getLevel() >= 4) { return true; } + return false; } catch (QueryException $e) { return false; @@ -1993,6 +2011,7 @@ private function getAllShares(?Node $path = null, bool $reshares = false) { } else { $federatedShares = []; } + if ($this->shareManager->outgoingServer2ServerGroupSharesAllowed()) { $federatedGroupShares = $this->shareManager->getSharesBy($this->currentUser, IShare::TYPE_REMOTE_GROUP, $path, $reshares, -1, 0); } else { @@ -2041,6 +2060,7 @@ private function setShareAttributes(IShare $share, ?string $attributesString) { throw new OCSBadRequestException($this->l->t('Invalid share attributes provided: "%s"', [$attributesString])); } } + $share->setAttributes($newShareAttributes); return $share; @@ -2050,11 +2070,13 @@ private function checkInheritedAttributes(IShare $share): void { if (!$share->getSharedBy()) { return; // Probably in a test } + $userFolder = $this->rootFolder->getUserFolder($share->getSharedBy()); $node = $userFolder->getFirstNodeById($share->getNodeId()); if (!$node) { return; } + if ($node->getStorage()->instanceOfStorage(SharedStorage::class)) { $storage = $node->getStorage(); if ($storage instanceof Wrapper) { @@ -2065,6 +2087,7 @@ private function checkInheritedAttributes(IShare $share): void { } else { throw new \RuntimeException('Should not happen, instanceOfStorage but not a wrapper'); } + /** @var \OCA\Files_Sharing\SharedStorage $storage */ $inheritedAttributes = $storage->getShare()->getAttributes(); if ($inheritedAttributes !== null && $inheritedAttributes->getAttribute('permissions', 'download') === false) { @@ -2129,10 +2152,12 @@ public function sendShareEmail(string $id, $password = ''): DataResponse { if (!$this->shareManager->checkPassword($share, $password)) { throw new OCSBadRequestException($this->l->t('Wrong password')); } + $share = $share->setPassword($password); } $provider->sendMailNotification($share); + return new DataResponse(); } catch (OCSBadRequestException $e) { throw $e; diff --git a/apps/files_sharing/lib/Controller/ShareController.php b/apps/files_sharing/lib/Controller/ShareController.php index 17b9c2a2196e1..2f32f4873fa92 100644 --- a/apps/files_sharing/lib/Controller/ShareController.php +++ b/apps/files_sharing/lib/Controller/ShareController.php @@ -253,6 +253,7 @@ protected function emitShareAccessEvent(IShare $share, string $step = '', int $e $step !== self::SHARE_DOWNLOAD) { return; } + $this->eventDispatcher->dispatchTyped(new ShareLinkAccessedEvent($share, $step, $errorCode, $errorMessage)); } @@ -326,7 +327,6 @@ public function showShare($path = ''): TemplateResponse { throw $e; } - $this->emitAccessShareHook($share); $this->emitShareAccessEvent($share, self::SHARE_ACCESS); @@ -361,6 +361,7 @@ public function downloadShare($token, $files = null, $path = '', $downloadStartS 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]; @@ -392,6 +393,7 @@ public function downloadShare($token, $files = null, $path = '', $downloadStartS } catch (NotFoundException $e) { $this->emitAccessShareHook($share, 404, 'Share not found'); $this->emitShareAccessEvent($share, self::SHARE_DOWNLOAD, 404, 'Share not found'); + return new NotFoundResponse(); } } @@ -507,6 +509,7 @@ protected function singleFileDownloaded(Share\IShare $share, \OCP\Files\Node $no } else { $subject = Downloads::SUBJECT_SHARED_FOLDER_BY_EMAIL_DOWNLOADED; } + $parameters[] = $share->getSharedWith(); } else { if ($node instanceof \OCP\Files\File) { diff --git a/apps/files_sharing/lib/Controller/ShareInfoController.php b/apps/files_sharing/lib/Controller/ShareInfoController.php index df93b485e1194..f85be92150db0 100644 --- a/apps/files_sharing/lib/Controller/ShareInfoController.php +++ b/apps/files_sharing/lib/Controller/ShareInfoController.php @@ -66,18 +66,21 @@ public function info(string $t, ?string $password = null, ?string $dir = null, i } 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; } @@ -101,6 +104,7 @@ 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); } diff --git a/apps/files_sharing/lib/Controller/ShareesAPIController.php b/apps/files_sharing/lib/Controller/ShareesAPIController.php index f177cb9d1ee3a..0d588335c0fbc 100644 --- a/apps/files_sharing/lib/Controller/ShareesAPIController.php +++ b/apps/files_sharing/lib/Controller/ShareesAPIController.php @@ -118,9 +118,11 @@ public function search(string $search = '', ?string $itemType = null, int $page if ($maxResults > 0) { $perPage = min($perPage, $maxResults); } + if ($perPage <= 0) { throw new OCSBadRequestException('Invalid perPage argument'); } + if ($page <= 0) { throw new OCSBadRequestException('Invalid page'); } @@ -159,6 +161,7 @@ public function search(string $search = '', ?string $itemType = null, int $page if ($this->shareManager->allowGroupSharing()) { $shareTypes[] = IShare::TYPE_GROUP; } + $shareTypes[] = IShare::TYPE_EMAIL; } @@ -176,6 +179,7 @@ public function search(string $search = '', ?string $itemType = null, int $page } elseif (is_numeric($shareType)) { $shareTypes = array_intersect($shareTypes, [(int)$shareType]); } + sort($shareTypes); $this->limit = $perPage; @@ -195,6 +199,7 @@ public function search(string $search = '', ?string $itemType = null, int $page 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); @@ -240,6 +245,7 @@ private function sortShareesByFrequency(array $sharees): array { usort($sharees, function (array $s1, array $s2): int { return $s2['count'] - $s1['count']; }); + return $sharees; } @@ -274,6 +280,7 @@ private function getAllSharees(string $user, array $shareTypes): ISearchResult { $shareTypeResults[$sharee]['count']++; } } + $result = array_merge($result, array_values($shareTypeResults)); } @@ -292,6 +299,7 @@ private function getAllSharees(string $user, array $shareTypes): ISearchResult { } } } + return $searchResult; } @@ -363,6 +371,7 @@ 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; @@ -373,6 +382,7 @@ 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; @@ -393,7 +403,9 @@ protected function getPaginationLink(int $page, array $params): string { } 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"'; } diff --git a/apps/files_sharing/lib/DefaultPublicShareTemplateProvider.php b/apps/files_sharing/lib/DefaultPublicShareTemplateProvider.php index 784dc4e5e1591..c023c54368497 100644 --- a/apps/files_sharing/lib/DefaultPublicShareTemplateProvider.php +++ b/apps/files_sharing/lib/DefaultPublicShareTemplateProvider.php @@ -89,6 +89,7 @@ public function renderPage(IShare $share, string $token, string $path): Template $this->appConfig->getValueString('core', 'shareapi_public_link_disclaimertext'), ); } + // Set up initial state $this->initialState->provideInitialState('isPublic', true); $this->initialState->provideInitialState('sharingToken', $token); @@ -136,6 +137,7 @@ public function renderPage(IShare $share, string $token, string $path): Template } else { $response->setHeaderTitle($shareNode->getName()); } + if ($ownerName !== '') { $response->setHeaderDetails($this->l10n->t('shared by %s', [$ownerName])); } @@ -176,6 +178,7 @@ public function renderPage(IShare $share, string $token, string $path): Template // Can read and no download restriction, so just download it $directLink = $downloadUrl ?? $shareUrl; } + $headerActions[] = new LinkMenuAction($this->l10n->t('Direct link'), 'icon-public', $directLink); $response->setHeaderActions($headerActions); diff --git a/apps/files_sharing/lib/DeleteOrphanedSharesJob.php b/apps/files_sharing/lib/DeleteOrphanedSharesJob.php index 2315ff5c12070..7509aeb56422f 100644 --- a/apps/files_sharing/lib/DeleteOrphanedSharesJob.php +++ b/apps/files_sharing/lib/DeleteOrphanedSharesJob.php @@ -38,7 +38,7 @@ class DeleteOrphanedSharesJob extends TimedJob { public function __construct( ITimeFactory $time, IDBConnection $db, - LoggerInterface $logger + LoggerInterface $logger, ) { parent::__construct($time); @@ -97,6 +97,7 @@ public function run($argument) { 'app' => 'DeleteOrphanedSharesJob', 'deleted' => $deleted, ]); + return $deleted; }, $this->db); } while ($deleted >= self::CHUNK_SIZE && $this->time->getTime() <= $cutOff); @@ -124,6 +125,7 @@ private function shardingCleanup(): void { 'app' => 'DeleteOrphanedSharesJob', 'deleted' => $deleted, ]); + return $deleted; }, $this->db); } @@ -135,6 +137,7 @@ private function findMissingSources(array $ids): array { ->from('filecache') ->where($qb->expr()->in('fileid', $qb->createNamedParameter($ids, IQueryBuilder::PARAM_INT_ARRAY))); $found = $qb->executeQuery()->fetchAll(\PDO::FETCH_COLUMN); + return array_diff($ids, $found); } } diff --git a/apps/files_sharing/lib/ExpireSharesJob.php b/apps/files_sharing/lib/ExpireSharesJob.php index cd8490291d283..bdfbe6f8b27c2 100644 --- a/apps/files_sharing/lib/ExpireSharesJob.php +++ b/apps/files_sharing/lib/ExpireSharesJob.php @@ -84,6 +84,7 @@ public function run($argument) { // Normally the share gets automatically expired on fetching it } } + $shares->closeCursor(); } } diff --git a/apps/files_sharing/lib/External/Cache.php b/apps/files_sharing/lib/External/Cache.php index 7fad71b9084cc..c7c67f6d5a959 100644 --- a/apps/files_sharing/lib/External/Cache.php +++ b/apps/files_sharing/lib/External/Cache.php @@ -33,12 +33,14 @@ public function 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; } @@ -47,6 +49,7 @@ public function getFolderContentsById($fileId) { foreach ($results as &$file) { $file['displayname_owner'] = $this->cloudId->getDisplayId(); } + return $results; } } diff --git a/apps/files_sharing/lib/External/Manager.php b/apps/files_sharing/lib/External/Manager.php index a3226e9f3dd5b..b103cad51cc36 100644 --- a/apps/files_sharing/lib/External/Manager.php +++ b/apps/files_sharing/lib/External/Manager.php @@ -84,7 +84,7 @@ public function __construct( IUserManager $userManager, IUserSession $userSession, IEventDispatcher $eventDispatcher, - LoggerInterface $logger + LoggerInterface $logger, ) { $user = $userSession->getUser(); $this->connection = $connection; @@ -152,6 +152,7 @@ public function addShare($remote, $token, $password, $name, $owner, $shareType, $data['mountpoint_hash'] = md5($data['mountpoint']); $i++; } + return null; } @@ -168,6 +169,7 @@ public function addShare($remote, $token, $password, $name, $owner, $shareType, 'mountpoint' => $mountPoint, 'owner' => $owner ]; + return $this->mountShare($options); } @@ -213,6 +215,7 @@ private function fetchShare($id) { $result = $getShare->execute([$id]); $share = $result->fetch(); $result->closeCursor(); + return $share; } @@ -227,6 +230,7 @@ private function fetchUserShare($parentId, $uid) { if ($share !== false) { return $share; } + return null; } @@ -251,6 +255,7 @@ public function getShare($id) { } else { $groupShare = $share; } + $user = $this->userManager->get($this->uid); if ($this->groupManager->get($groupShare['user'])->inGroup($user)) { return $share; @@ -343,6 +348,7 @@ public function acceptShare($id) { } } } + if ($userShareAccepted !== false) { $this->sendFeedbackToRemote($share['remote'], $share['share_token'], $share['remote_id'], 'accept'); $event = new FederatedShareAddedEvent($share['remote']); @@ -414,6 +420,7 @@ public function declineShare($id) { $result = false; } } + $this->processNotification($id); } @@ -492,6 +499,7 @@ protected function tryOCMEndPoint($remoteDomain, $token, $remoteId, $feedback) { ] ); + return $this->cloudFederationProviderManager->sendNotification($remoteDomain, $notification); case 'decline': $notification = $this->cloudFederationFactory->getCloudFederationNotification(); @@ -505,6 +513,7 @@ protected function tryOCMEndPoint($remoteDomain, $token, $remoteId, $feedback) { ] ); + return $this->cloudFederationProviderManager->sendNotification($remoteDomain, $notification); } @@ -528,6 +537,7 @@ public function getMount($data) { $mountPoint = '/' . $this->uid . '/files' . $data['mountpoint']; $data['mountpoint'] = $mountPoint; $data['certificateManager'] = \OC::$server->getCertificateManager(); + return new Mount(self::STORAGE, $mountPoint, $data, $this, $this->storageLoader); } @@ -538,6 +548,7 @@ public function getMount($data) { protected function mountShare($data) { $mount = $this->getMount($data); $this->mountManager->addMount($mount); + return $mount; } @@ -579,10 +590,12 @@ public function removeShare($mountPoint): bool { $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); @@ -786,6 +799,7 @@ private function getShares($accepted) { $toRemove[] = $share['parent']; } } + $shares = array_filter($shares, function ($share) use ($toRemove) { return !in_array($share['id'], $toRemove, true); }); @@ -795,6 +809,7 @@ private function getShares($accepted) { return (bool)$share['accepted'] === $accepted; }); } + return array_values($shares); } catch (\Doctrine\DBAL\Exception $e) { $this->logger->emergency('Error when retrieving shares', ['exception' => $e]); diff --git a/apps/files_sharing/lib/External/MountProvider.php b/apps/files_sharing/lib/External/MountProvider.php index 2c639543a87d5..a8ccef2045690 100644 --- a/apps/files_sharing/lib/External/MountProvider.php +++ b/apps/files_sharing/lib/External/MountProvider.php @@ -51,6 +51,7 @@ public function getMount(IUser $user, $data, IStorageFactory $storageFactory) { $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); } @@ -67,7 +68,9 @@ public function getMountsForUser(IUser $user, IStorageFactory $loader) { $row['token'] = $row['share_token']; $mounts[] = $this->getMount($user, $row, $loader); } + $result->closeCursor(); + return $mounts; } } diff --git a/apps/files_sharing/lib/External/Storage.php b/apps/files_sharing/lib/External/Storage.php index bfaf9a994421f..3686ba883aa76 100644 --- a/apps/files_sharing/lib/External/Storage.php +++ b/apps/files_sharing/lib/External/Storage.php @@ -95,10 +95,12 @@ 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; } @@ -134,6 +136,7 @@ public function getCache($path = '', $storage = null) { if (is_null($this->cache)) { $this->cache = new Cache($this, $this->cloudId); } + return $this->cache; } @@ -141,9 +144,11 @@ public function getScanner($path = '', $storage = null) { if (!$storage) { $storage = $this; } + if (!isset($this->scanner)) { $this->scanner = new Scanner($storage); } + /** @var \OCA\Files_Sharing\External\Scanner */ return $this->scanner; } @@ -163,6 +168,7 @@ public function hasUpdated($path, $time) { if ($this->updateChecked) { return false; } + $this->updateChecked = true; try { return parent::hasUpdated('', $time); @@ -275,6 +281,7 @@ private function testRemoteUrl(string $url): bool { } $cache->set($url, $returnValue, 60 * 60 * 24); + return $returnValue; } @@ -288,6 +295,7 @@ public function remoteIsOwnCloud(): bool { if (defined('PHPUNIT_RUN') || !$this->testRemoteUrl($this->getRemote() . '/status.php')) { return false; } + return true; } @@ -328,9 +336,11 @@ public function getShareInfo(int $depth = -1) { 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 @@ -348,6 +358,7 @@ public function isSharable($path): bool { if (\OCP\Util::isSharingDisabledForUser() || !\OC\Share\Share::isResharingAllowed()) { return false; } + return (bool)($this->getPermissions($path) & Constants::PERMISSION_SHARE); } diff --git a/apps/files_sharing/lib/Helper.php b/apps/files_sharing/lib/Helper.php index f2f92679e2f91..3f1937396a01e 100644 --- a/apps/files_sharing/lib/Helper.php +++ b/apps/files_sharing/lib/Helper.php @@ -28,12 +28,12 @@ public static function registerHooks() { */ public static function generateUniqueTarget($path, $excludeList, $view) { $pathinfo = pathinfo($path); - $ext = isset($pathinfo['extension']) ? '.'.$pathinfo['extension'] : ''; + $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); + $path = Filesystem::normalizePath($dir . '/' . $name . ' (' . $i . ')' . $ext); $i++; } diff --git a/apps/files_sharing/lib/Listener/LoadSidebarListener.php b/apps/files_sharing/lib/Listener/LoadSidebarListener.php index 7fd10b3877922..b00e937d6754d 100644 --- a/apps/files_sharing/lib/Listener/LoadSidebarListener.php +++ b/apps/files_sharing/lib/Listener/LoadSidebarListener.php @@ -22,7 +22,10 @@ */ class LoadSidebarListener implements IEventListener { - public function __construct(private IInitialState $initialState, private IManager $shareManager) { + public function __construct( + private IInitialState $initialState, + private IManager $shareManager, + ) { } public function handle(Event $event): void { diff --git a/apps/files_sharing/lib/Listener/ShareInteractionListener.php b/apps/files_sharing/lib/Listener/ShareInteractionListener.php index 7b11a472492a3..fd73918f93272 100644 --- a/apps/files_sharing/lib/Listener/ShareInteractionListener.php +++ b/apps/files_sharing/lib/Listener/ShareInteractionListener.php @@ -44,12 +44,14 @@ public function handle(Event $event): void { $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: @@ -57,6 +59,7 @@ public function handle(Event $event): void { if ($sharedWith !== null) { $interactionEvent->setFederatedCloudId($sharedWith->getCloudId()); } + break; case IShare::TYPE_EMAIL: $interactionEvent->setEmail($share->getSharedWith()); diff --git a/apps/files_sharing/lib/Listener/UserAddedToGroupListener.php b/apps/files_sharing/lib/Listener/UserAddedToGroupListener.php index fc4dd4d798a39..a636552e4b7a1 100644 --- a/apps/files_sharing/lib/Listener/UserAddedToGroupListener.php +++ b/apps/files_sharing/lib/Listener/UserAddedToGroupListener.php @@ -61,6 +61,7 @@ public function handle(Event $event): void { 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); } } diff --git a/apps/files_sharing/lib/Middleware/SharingCheckMiddleware.php b/apps/files_sharing/lib/Middleware/SharingCheckMiddleware.php index e13c3354f27ca..009692c566762 100644 --- a/apps/files_sharing/lib/Middleware/SharingCheckMiddleware.php +++ b/apps/files_sharing/lib/Middleware/SharingCheckMiddleware.php @@ -47,7 +47,7 @@ public function __construct(string $appName, IAppManager $appManager, IControllerMethodReflector $reflector, IManager $shareManager, - IRequest $request + IRequest $request, ) { $this->appName = $appName; $this->config = $config; diff --git a/apps/files_sharing/lib/Migration/Version11300Date20201120141438.php b/apps/files_sharing/lib/Migration/Version11300Date20201120141438.php index 21dbb64fef066..1f68224f31d01 100644 --- a/apps/files_sharing/lib/Migration/Version11300Date20201120141438.php +++ b/apps/files_sharing/lib/Migration/Version11300Date20201120141438.php @@ -96,18 +96,21 @@ public function changeSchema(IOutput $output, Closure $schemaClosure, array $opt $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'); } diff --git a/apps/files_sharing/lib/Migration/Version21000Date20201223143245.php b/apps/files_sharing/lib/Migration/Version21000Date20201223143245.php index 9bd07a198022e..8751e590c341d 100644 --- a/apps/files_sharing/lib/Migration/Version21000Date20201223143245.php +++ b/apps/files_sharing/lib/Migration/Version21000Date20201223143245.php @@ -29,6 +29,7 @@ public function changeSchema(IOutput $output, Closure $schemaClosure, array $opt ]); $changed = true; } + if (!$table->hasColumn('share_type')) { $table->addColumn('share_type', Types::INTEGER, [ 'notnull' => false, @@ -36,6 +37,7 @@ public function changeSchema(IOutput $output, Closure $schemaClosure, array $opt ]); $changed = true; } + if ($table->hasColumn('lastscan')) { $table->dropColumn('lastscan'); $changed = true; diff --git a/apps/files_sharing/lib/Migration/Version24000Date20220208195521.php b/apps/files_sharing/lib/Migration/Version24000Date20220208195521.php index 75da1de1d832c..070e32115a17c 100644 --- a/apps/files_sharing/lib/Migration/Version24000Date20220208195521.php +++ b/apps/files_sharing/lib/Migration/Version24000Date20220208195521.php @@ -28,6 +28,7 @@ public function changeSchema(IOutput $output, Closure $schemaClosure, array $opt $table->addColumn('password_expiration_time', Types::DATETIME, [ 'notnull' => false, ]); + return $schema; } diff --git a/apps/files_sharing/lib/Migration/Version24000Date20220404142216.php b/apps/files_sharing/lib/Migration/Version24000Date20220404142216.php index 03985bd50c75c..d45f9094b6310 100644 --- a/apps/files_sharing/lib/Migration/Version24000Date20220404142216.php +++ b/apps/files_sharing/lib/Migration/Version24000Date20220404142216.php @@ -34,6 +34,7 @@ public function changeSchema(IOutput $output, Closure $schemaClosure, array $opt $column->setLength(4000); return $schema; } + return null; } } diff --git a/apps/files_sharing/lib/Migration/Version31000Date20240821142813.php b/apps/files_sharing/lib/Migration/Version31000Date20240821142813.php index 71b2c1817e6b2..60068b9232afb 100644 --- a/apps/files_sharing/lib/Migration/Version31000Date20240821142813.php +++ b/apps/files_sharing/lib/Migration/Version31000Date20240821142813.php @@ -37,6 +37,7 @@ public function changeSchema(IOutput $output, Closure $schemaClosure, array $opt 'notnull' => false, 'default' => false, ]); + return $schema; } diff --git a/apps/files_sharing/lib/MountProvider.php b/apps/files_sharing/lib/MountProvider.php index 64847bf5a5f1d..dcd7ff4e1d074 100644 --- a/apps/files_sharing/lib/MountProvider.php +++ b/apps/files_sharing/lib/MountProvider.php @@ -30,7 +30,7 @@ public function __construct( protected IManager $shareManager, protected LoggerInterface $logger, protected IEventDispatcher $eventDispatcher, - protected ICacheFactory $cacheFactory + protected ICacheFactory $cacheFactory, ) { } @@ -79,6 +79,7 @@ public function getMountsForUser(IUser $user, IStorageFactory $loader) { if (!isset($ownerViews[$owner])) { $ownerViews[$owner] = new View('/' . $parentShare->getShareOwner() . '/files'); } + $mount = new SharedMount( '\OCA\Files_Sharing\SharedStorage', $mounts, @@ -135,6 +136,7 @@ private function groupShares(array $shares) { if (!isset($tmp[$share->getNodeId()])) { $tmp[$share->getNodeId()] = []; } + $tmp[$share->getNodeId()][] = $share; } @@ -147,6 +149,7 @@ private function groupShares(array $shares) { if ($aTime === $bTime) { return $a->getId() < $b->getId() ? -1 : 1; } + return $aTime < $bTime ? -1 : 1; }); $result[] = $tmp2; @@ -214,6 +217,7 @@ private function buildSuperShares(array $allShares, \OCP\IUser $user) { // 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['value']); } @@ -240,6 +244,7 @@ private function buildSuperShares(array $allShares, \OCP\IUser $user) { ); } } + if (!is_null($share->getNodeCacheEntry())) { $superShare->setNodeCacheEntry($share->getNodeCacheEntry()); } diff --git a/apps/files_sharing/lib/Notification/Listener.php b/apps/files_sharing/lib/Notification/Listener.php index aaac32897d79b..cf384d7e83642 100644 --- a/apps/files_sharing/lib/Notification/Listener.php +++ b/apps/files_sharing/lib/Notification/Listener.php @@ -30,7 +30,7 @@ class Listener { public function __construct( INotificationManager $notificationManager, IShareManager $shareManager, - IGroupManager $groupManager + IGroupManager $groupManager, ) { $this->notificationManager = $notificationManager; $this->shareManager = $shareManager; @@ -92,6 +92,7 @@ public function userAddedToGroup(GenericEvent $event): void { ->setUser($user->getUID()); $this->notificationManager->notify($notification); } + $offset += 50; } } diff --git a/apps/files_sharing/lib/Notification/Notifier.php b/apps/files_sharing/lib/Notification/Notifier.php index 2609f99e691fd..1ba2c5b42c240 100644 --- a/apps/files_sharing/lib/Notification/Notifier.php +++ b/apps/files_sharing/lib/Notification/Notifier.php @@ -111,6 +111,7 @@ public function prepare(INotification $notification, string $languageCode): INot } else { $notification = $this->parseShareInvitation($share, $notification, $l); } + return $notification; } diff --git a/apps/files_sharing/lib/OrphanHelper.php b/apps/files_sharing/lib/OrphanHelper.php index 94fe4f0831838..33dba6f7e4722 100644 --- a/apps/files_sharing/lib/OrphanHelper.php +++ b/apps/files_sharing/lib/OrphanHelper.php @@ -19,7 +19,7 @@ class OrphanHelper { public function __construct( IDBConnection $connection, - IRootFolder $rootFolder + IRootFolder $rootFolder, ) { $this->connection = $connection; $this->rootFolder = $rootFolder; @@ -31,7 +31,9 @@ public function isShareValid(string $owner, int $fileId): bool { } catch (NoUserException $e) { return false; } + $node = $userFolder->getFirstNodeById($fileId); + return $node !== null; } @@ -51,6 +53,7 @@ public function fileExists(int $fileId): bool { $query->select('fileid') ->from('filecache') ->where($query->expr()->eq('fileid', $query->createNamedParameter($fileId, IQueryBuilder::PARAM_INT))); + return $query->executeQuery()->fetchOne() !== false; } diff --git a/apps/files_sharing/lib/Scanner.php b/apps/files_sharing/lib/Scanner.php index 1ff1046bce778..1e488b3819b1a 100644 --- a/apps/files_sharing/lib/Scanner.php +++ b/apps/files_sharing/lib/Scanner.php @@ -33,8 +33,10 @@ public function getData($path) { if ($data === null) { return null; } + $internalPath = $this->storage->getUnjailedPath($path); $data['permissions'] = $this->storage->getSourceStorage()->getPermissions($internalPath); + return $data; } @@ -42,10 +44,12 @@ 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; diff --git a/apps/files_sharing/lib/Settings/Personal.php b/apps/files_sharing/lib/Settings/Personal.php index 4f7ca05d3ad1e..a7d22f99e9ba5 100644 --- a/apps/files_sharing/lib/Settings/Personal.php +++ b/apps/files_sharing/lib/Settings/Personal.php @@ -41,6 +41,7 @@ public function getForm(): TemplateResponse { $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'); } diff --git a/apps/files_sharing/lib/ShareBackend/File.php b/apps/files_sharing/lib/ShareBackend/File.php index 93f98c9e18a63..34187328ebfc3 100644 --- a/apps/files_sharing/lib/ShareBackend/File.php +++ b/apps/files_sharing/lib/ShareBackend/File.php @@ -39,6 +39,7 @@ public function isValidSource($itemSource, $uidOwner) { // 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; @@ -49,6 +50,7 @@ public function getFilePath($itemSource, $uidOwner) { if (isset($this->path)) { $path = $this->path; $this->path = null; + return $path; } else { try { @@ -100,6 +102,7 @@ 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'], @@ -129,32 +132,38 @@ public function formatItems($items, $format, $parameters = null) { $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 []; } @@ -204,6 +213,7 @@ protected static function resolveReshares($source) { } else { $fileOwner = $source['uid_owner']; } + if (isset($fileOwner)) { $source['fileOwner'] = $fileOwner; } else { @@ -224,6 +234,7 @@ public static function getSource($target, $share) { // which would cause a leading slash to appear $share['path'] = ltrim($share['path'] . '/' . $target, '/'); } + return self::resolveReshares($share); } } diff --git a/apps/files_sharing/lib/ShareBackend/Folder.php b/apps/files_sharing/lib/ShareBackend/Folder.php index f64d44faefcee..32693bebf5a82 100644 --- a/apps/files_sharing/lib/ShareBackend/Folder.php +++ b/apps/files_sharing/lib/ShareBackend/Folder.php @@ -26,6 +26,7 @@ public function getChildren($itemSource) { } else { $mimetype = -1; } + while (!empty($parents)) { $qb = \OC::$server->getDatabaseConnection()->getQueryBuilder(); @@ -49,8 +50,10 @@ public function getChildren($itemSource) { $parents[] = $file['fileid']; } } + $result->closeCursor(); } + return $children; } } diff --git a/apps/files_sharing/lib/SharedMount.php b/apps/files_sharing/lib/SharedMount.php index 6c7178132adf3..ffc7dfbbab596 100644 --- a/apps/files_sharing/lib/SharedMount.php +++ b/apps/files_sharing/lib/SharedMount.php @@ -55,7 +55,7 @@ public function __construct( CappedMemoryCache $folderExistCache, IEventDispatcher $eventDispatcher, IUser $user, - ICache $cache + ICache $cache, ) { $this->user = $user; $this->recipientView = $recipientView; @@ -81,7 +81,7 @@ public function __construct( private function verifyMountPoint( \OCP\Share\IShare $share, array $mountpoints, - CappedMemoryCache $folderExistCache + CappedMemoryCache $folderExistCache, ) { $cacheKey = $this->user->getUID() . '/' . $share->getId() . '/' . $share->getTarget(); $cached = $this->cache->get($cacheKey); @@ -103,6 +103,7 @@ private function verifyMountPoint( $parentExists = $this->recipientView->is_dir($parent); $folderExistCache->set($parent, $parentExists); } + if (!$parentExists) { $parent = Helper::getShareFolder($this->recipientView, $this->user->getUID()); } @@ -274,6 +275,7 @@ public function getNumericStorageId() { if ($row) { return (int)$row['storage']; } + return -1; } } diff --git a/apps/files_sharing/lib/SharedStorage.php b/apps/files_sharing/lib/SharedStorage.php index 0a6a068c44156..3d3a9dbec6898 100644 --- a/apps/files_sharing/lib/SharedStorage.php +++ b/apps/files_sharing/lib/SharedStorage.php @@ -114,6 +114,7 @@ private function getSourceRootInfo() { $this->sourceRootInfo = $this->superShare->getNodeCacheEntry(); } } + return $this->sourceRootInfo; } @@ -132,6 +133,7 @@ private function init() { $this->cache = new FailedCache(); $this->rootPath = ''; } + return; } @@ -161,16 +163,19 @@ private function init() { if ($nonMaskedStorage instanceof Wrapper && $nonMaskedStorage->isWrapperOf($this)) { continue; } + $this->nonMaskedStorage = $nonMaskedStorage; $this->sourcePath = $ownerNode->getPath(); $this->rootPath = $ownerNode->getInternalPath(); $this->cache = null; break; } + if (!$this->nonMaskedStorage) { // all potential source nodes would have been recursive throw new \Exception('recursive share detected'); } + $this->storage = new PermissionsMask([ 'storage' => $this->nonMaskedStorage, 'mask' => $this->superShare->getPermissions(), @@ -196,6 +201,7 @@ private function init() { if (!$this->nonMaskedStorage) { $this->nonMaskedStorage = $this->storage; } + self::$initDepth--; } @@ -206,6 +212,7 @@ 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', @@ -216,6 +223,7 @@ public function instanceOfStorage($class): bool { ])) { return false; } + return parent::instanceOfStorage($class); } @@ -249,6 +257,7 @@ 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 @@ -271,12 +280,15 @@ 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); } @@ -292,6 +304,7 @@ public function isSharable($path): bool { if (\OCP\Util::isSharingDisabledForUser() || !\OC\Share\Share::isResharingAllowed()) { return false; } + return (bool)($this->getPermissions($path) & \OCP\Constants::PERMISSION_SHARE); } @@ -338,12 +351,14 @@ public function fopen($path, $mode) { } } } + $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); } @@ -422,9 +437,11 @@ 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(); @@ -436,6 +453,7 @@ public function getCache($path = '', $storage = null) { \OC::$server->get(CacheDependencies::class), $this->getShare() ); + return $this->cache; } @@ -443,6 +461,7 @@ public function getScanner($path = '', $storage = null) { if (!$storage) { $storage = $this; } + return new \OCA\Files_Sharing\Scanner($storage); } @@ -466,6 +485,7 @@ public function getWatcher($path = '', $storage = null): Watcher { if ($mount->getMountProvider() === ConfigAdapter::class) { // Propagate original storage scan $this->watcher = parent::getWatcher($path, $storage); + return $this->watcher; } } @@ -473,6 +493,7 @@ public function getWatcher($path = '', $storage = null): Watcher { // cache updating is handled by the share source $this->watcher = new NullWatcher(); + return $this->watcher; } @@ -485,6 +506,7 @@ public function unshareStorage(): bool { foreach ($this->groupedShares as $share) { \OC::$server->getShareManager()->deleteFromSelf($share, $this->user); } + return true; } @@ -560,6 +582,7 @@ public function file_get_contents($path) { 'source' => $this->getUnjailedPath($path), ]; \OCP\Util::emitHook('\OC\Files\Storage\Shared', 'file_get_contents', $info); + return parent::file_get_contents($path); } @@ -569,6 +592,7 @@ public function file_put_contents($path, $data) { 'source' => $this->getUnjailedPath($path), ]; \OCP\Util::emitHook('\OC\Files\Storage\Shared', 'file_put_contents', $info); + return parent::file_put_contents($path, $data); } diff --git a/apps/files_sharing/lib/SharesReminderJob.php b/apps/files_sharing/lib/SharesReminderJob.php index 9682f7fc77025..fc428de9dc031 100644 --- a/apps/files_sharing/lib/SharesReminderJob.php +++ b/apps/files_sharing/lib/SharesReminderJob.php @@ -117,6 +117,7 @@ private function getShares(): array|\Iterator { $this->logger->error("Share with ID $id not found."); } } + $sharesResult->closeCursor(); } @@ -127,7 +128,7 @@ private function getShares(): array|\Iterator { * @param IShare $share Share that was obtained with {@link getShares} * @return array|null Info needed to send a reminder */ - private function prepareReminder(IShare $share): array|null { + private function prepareReminder(IShare $share): ?array { $sharedWith = $share->getSharedWith(); $reminderInfo = []; if ($share->getShareType() == IShare::TYPE_USER) { @@ -135,6 +136,7 @@ private function prepareReminder(IShare $share): array|null { if ($user === null) { return null; } + $reminderInfo['email'] = $user->getEMailAddress(); $reminderInfo['userLang'] = $this->l10nFactory->getUserLanguage($user); $reminderInfo['folderLink'] = $this->urlGenerator->linkToRouteAbsolute('files.view.index', [ @@ -146,6 +148,7 @@ private function prepareReminder(IShare $share): array|null { 'token' => $share->getToken() ]); } + if (empty($reminderInfo['email'])) { return null; } @@ -156,8 +159,10 @@ private function prepareReminder(IShare $share): array|null { $id = $share->getFullId(); $this->logger->error("File by share ID $id not found."); } + $share->setReminderSent(true); $this->shareManager->updateShare($share); + return $reminderInfo; } @@ -213,6 +218,7 @@ private function generateEMailTemplate(IL10N $l, array $folder): IEMailTemplate $folder['link'] ); $emailTemplate->addFooter(); + return $emailTemplate; } } diff --git a/apps/files_sharing/lib/Updater.php b/apps/files_sharing/lib/Updater.php index 3a3813287b906..2fac36e6fcccc 100644 --- a/apps/files_sharing/lib/Updater.php +++ b/apps/files_sharing/lib/Updater.php @@ -40,6 +40,7 @@ private static function moveShareInOrOutOfShare($path): void { if ($userFolder === null) { return; } + $user = $userFolder->getOwner(); if (!$user) { throw new \Exception('user folder has no owner'); @@ -101,6 +102,7 @@ private static function moveShareInOrOutOfShare($path): void { $shareManager->deleteShare($share); continue; } + $newOwner = $dstMount->getShare()->getShareOwner(); $newPermissions = $share->getPermissions() & $dstMount->getShare()->getPermissions(); } else { diff --git a/apps/files_sharing/lib/ViewOnly.php b/apps/files_sharing/lib/ViewOnly.php index 7b52d79f4d047..2602e275f7849 100644 --- a/apps/files_sharing/lib/ViewOnly.php +++ b/apps/files_sharing/lib/ViewOnly.php @@ -48,6 +48,7 @@ public function check(array $pathsToCheck): bool { continue; } } + return true; } @@ -60,6 +61,7 @@ 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) { @@ -102,6 +104,7 @@ private function checkFileInfo(Node $fileInfo): bool { if ($canDownload !== null && !$canDownload) { return false; } + return true; } } diff --git a/apps/files_sharing/templates/public.php b/apps/files_sharing/templates/public.php index 7620d309ff605..e3ab2df725492 100644 --- a/apps/files_sharing/templates/public.php +++ b/apps/files_sharing/templates/public.php @@ -10,7 +10,9 @@ ?>
- + @@ -18,7 +20,9 @@ - + @@ -36,7 +40,9 @@ - + @@ -46,7 +52,9 @@
@@ -56,16 +64,22 @@ checked="checked" /> + checked="checked" /> - +
- +
@@ -75,11 +89,15 @@ ()
- + t('Download'))?> - +
@@ -100,7 +118,9 @@
+ class="emptycontent has-note">

t('Upload files to %s', [$_['label'] ?: $_['filename']])) ?>

@@ -108,12 +128,16 @@ class="emptycontent has-note">

t('Upload files to %s', [$_['label'] ?: $_['filename']])) ?>

- +

t('Note')); ?>

- + t('Select or drop files')) ?> @@ -129,15 +153,21 @@ class="emptycontent has-note"> ]); ?>
- +
- +
+ data-url="getURLGenerator()->linkTo('files', 'ajax/upload.php')); + + ?>" />
diff --git a/apps/files_sharing/tests/ApiTest.php b/apps/files_sharing/tests/ApiTest.php index fe0739cfcde6c..053d568f91223 100644 --- a/apps/files_sharing/tests/ApiTest.php +++ b/apps/files_sharing/tests/ApiTest.php @@ -63,7 +63,7 @@ protected function setUp(): void { $this->view->mkdir($this->folder); $this->view->mkdir($this->folder . $this->subfolder); $this->view->mkdir($this->folder . $this->subfolder . $this->subsubfolder); - $this->view->file_put_contents($this->folder.$this->filename, $this->data); + $this->view->file_put_contents($this->folder . $this->filename, $this->data); $this->view->file_put_contents($this->folder . $this->subfolder . $this->filename, $this->data); $mount = $this->view->getMount($this->filename); $mount->getStorage()->getScanner()->scan('', Scanner::SCAN_RECURSIVE); @@ -135,7 +135,7 @@ public function testCreateShareUserFile(): void { $this->assertEquals(19, $data['permissions']); $this->assertEmpty($data['expiration']); - $this->shareManager->getShareById('ocinternal:'.$data['id']); + $this->shareManager->getShareById('ocinternal:' . $data['id']); $ocs = $this->createOCS(self::TEST_FILES_SHARING_API_USER1); $ocs->deleteShare($data['id']); @@ -152,7 +152,7 @@ public function testCreateShareUserFolder(): void { $this->assertEquals(31, $data['permissions']); $this->assertEmpty($data['expiration']); - $this->shareManager->getShareById('ocinternal:'.$data['id']); + $this->shareManager->getShareById('ocinternal:' . $data['id']); $ocs = $this->createOCS(self::TEST_FILES_SHARING_API_USER1); $ocs->deleteShare($data['id']); @@ -169,7 +169,7 @@ public function testCreateShareGroupFile(): void { $this->assertEquals(19, $data['permissions']); $this->assertEmpty($data['expiration']); - $this->shareManager->getShareById('ocinternal:'.$data['id']); + $this->shareManager->getShareById('ocinternal:' . $data['id']); $ocs = $this->createOCS(self::TEST_FILES_SHARING_API_USER1); $ocs->deleteShare($data['id']); @@ -185,7 +185,7 @@ public function testCreateShareGroupFolder(): void { $this->assertEquals(31, $data['permissions']); $this->assertEmpty($data['expiration']); - $this->shareManager->getShareById('ocinternal:'.$data['id']); + $this->shareManager->getShareById('ocinternal:' . $data['id']); $ocs = $this->createOCS(self::TEST_FILES_SHARING_API_USER1); $ocs->deleteShare($data['id']); @@ -210,7 +210,7 @@ public function testCreateShareLink(): void { $url = \OC::$server->getURLGenerator()->getAbsoluteURL('/index.php/s/' . $data['token']); $this->assertEquals($url, $data['url']); - $this->shareManager->getShareById('ocinternal:'.$data['id']); + $this->shareManager->getShareById('ocinternal:' . $data['id']); $ocs = $this->createOCS(self::TEST_FILES_SHARING_API_USER1); $ocs->deleteShare($data['id']); @@ -241,7 +241,7 @@ public function testCreateShareLinkPublicUpload(): void { $url = \OC::$server->getURLGenerator()->getAbsoluteURL('/index.php/s/' . $data['token']); $this->assertEquals($url, $data['url']); - $this->shareManager->getShareById('ocinternal:'.$data['id']); + $this->shareManager->getShareById('ocinternal:' . $data['id']); $ocs = $this->createOCS(self::TEST_FILES_SHARING_API_USER1); $ocs->deleteShare($data['id']); @@ -259,6 +259,7 @@ public function testEnforceLinkPassword(): void { $this->fail(); } catch (OCSForbiddenException $e) { } + $ocs->cleanup(); $ocs = $this->createOCS(self::TEST_FILES_SHARING_API_USER1); @@ -267,6 +268,7 @@ public function testEnforceLinkPassword(): void { $this->fail(); } catch (OCSForbiddenException $e) { } + $ocs->cleanup(); // share with password should succeed @@ -288,6 +290,7 @@ public function testEnforceLinkPassword(): void { $this->fail(); } catch (OCSBadRequestException $e) { } + $ocs->cleanup(); // cleanup @@ -313,7 +316,7 @@ public function testSharePermissions(): void { $data = $result->getData(); - $this->shareManager->getShareById('ocinternal:'.$data['id']); + $this->shareManager->getShareById('ocinternal:' . $data['id']); $ocs = $this->createOCS(self::TEST_FILES_SHARING_API_USER1); $ocs->deleteShare($data['id']); @@ -571,7 +574,7 @@ public function testGetShareFromFolder(): void { ->setPermissions(19); $share1 = $this->shareManager->createShare($share1); - $node2 = $this->userFolder->get($this->folder.'/'.$this->filename); + $node2 = $this->userFolder->get($this->folder . '/' . $this->filename); $share2 = $this->shareManager->newShare(); $share2->setNode($node2) ->setSharedBy(self::TEST_FILES_SHARING_API_USER1) @@ -608,6 +611,7 @@ public function testGetShareFromFolderWithFile(): void { } catch (OCSBadRequestException $e) { $this->assertEquals('Not a directory', $e->getMessage()); } + $ocs->cleanup(); $this->shareManager->deleteShare($share1); @@ -629,7 +633,7 @@ public function testGetShareFromFolderReshares(): void { $share1->setStatus(IShare::STATUS_ACCEPTED); $this->shareManager->updateShare($share1); - $node2 = $this->userFolder->get($this->folder.'/'.$this->filename); + $node2 = $this->userFolder->get($this->folder . '/' . $this->filename); $share2 = $this->shareManager->newShare(); $share2->setNode($node2) ->setSharedBy(self::TEST_FILES_SHARING_API_USER2) @@ -639,7 +643,7 @@ public function testGetShareFromFolderReshares(): void { $share2->setStatus(IShare::STATUS_ACCEPTED); $this->shareManager->updateShare($share2); - $node3 = $this->userFolder->get($this->folder.'/'.$this->subfolder.'/'.$this->filename); + $node3 = $this->userFolder->get($this->folder . '/' . $this->subfolder . '/' . $this->filename); $share3 = $this->shareManager->newShare(); $share3->setNode($node3) ->setSharedBy(self::TEST_FILES_SHARING_API_USER2) @@ -850,7 +854,7 @@ public function testGetShareMultipleSharedFolder(): void { $s2 = reset($data2); $this->assertEquals($this->subfolder, $s1['path']); - $this->assertEquals($this->folder.$this->subfolder, $s2['path']); + $this->assertEquals($this->folder . $this->subfolder, $s2['path']); $this->shareManager->deleteShare($share1); $this->shareManager->deleteShare($share2); @@ -924,6 +928,7 @@ public function testGetShareFromUnknownId(): void { } catch (OCSNotFoundException $e) { $this->assertEquals('Wrong share ID, share does not exist', $e->getMessage()); } + $ocs->cleanup(); } @@ -1065,6 +1070,7 @@ public function testUpdateShareExpireDate(): void { $this->fail(); } catch (OCSBadRequestException $e) { } + $ocs->cleanup(); $share1 = $this->shareManager->getShareById($share1->getFullId()); @@ -1079,6 +1085,7 @@ public function testUpdateShareExpireDate(): void { $this->fail(); } catch (OCSBadRequestException $e) { } + $ocs->cleanup(); $share1 = $this->shareManager->getShareById($share1->getFullId()); @@ -1141,7 +1148,7 @@ public function testDeleteReshare(): void { $this->shareManager->updateShare($share1); $user2folder = \OC::$server->getUserFolder(self::TEST_FILES_SHARING_API_USER2); - $node2 = $user2folder->get($this->folder.'/'.$this->filename); + $node2 = $user2folder->get($this->folder . '/' . $this->filename); $share2 = $this->shareManager->newShare(); $share2->setNode($node2) ->setSharedBy(self::TEST_FILES_SHARING_API_USER2) @@ -1184,7 +1191,7 @@ public function testShareFolderWithAMountPoint(): void { $view->mkdir('localDir'); // move mount point to the folder "localDir" - $result = $view->rename($this->folder, 'localDir/'.$this->folder); + $result = $view->rename($this->folder, 'localDir/' . $this->folder); $this->assertTrue($result !== false); // try to share "localDir" @@ -1296,8 +1303,10 @@ public function testPublicLinkExpireDate($date, $valid): void { $this->assertFalse($valid); $this->assertEquals('Invalid date, date format must be YYYY-MM-DD', $e->getMessage()); $ocs->cleanup(); + return; } + $ocs->cleanup(); $data = $result->getData(); @@ -1308,7 +1317,7 @@ public function testPublicLinkExpireDate($date, $valid): void { $url = \OC::$server->getURLGenerator()->getAbsoluteURL('/index.php/s/' . $data['token']); $this->assertEquals($url, $data['url']); - $share = $this->shareManager->getShareById('ocinternal:'.$data['id']); + $share = $this->shareManager->getShareById('ocinternal:' . $data['id']); $this->assertEquals($date, $share->getExpirationDate()->format('Y-m-d H:i:s')); @@ -1340,7 +1349,7 @@ public function testCreatePublicLinkExpireDateValid(): void { $url = \OC::$server->getURLGenerator()->getAbsoluteURL('/index.php/s/' . $data['token']); $this->assertEquals($url, $data['url']); - $share = $this->shareManager->getShareById('ocinternal:'.$data['id']); + $share = $this->shareManager->getShareById('ocinternal:' . $data['id']); $date->setTime(0, 0, 0); $this->assertEquals($date, $share->getExpirationDate()); @@ -1369,6 +1378,7 @@ public function testCreatePublicLinkExpireDateInvalidFuture(): void { $this->assertEquals(404, $e->getCode()); $this->assertEquals('Cannot set expiration date more than 7 days in the future', $e->getMessage()); } + $ocs->cleanup(); $config->setAppValue('core', 'shareapi_default_expire_date', 'no'); @@ -1390,6 +1400,7 @@ public function XtestCreatePublicLinkExpireDateInvalidPast() { $this->assertEquals(404, $e->getCode()); $this->assertEquals('Expiration date is in the past', $e->getMessage()); } + $ocs->cleanup(); $config->setAppValue('core', 'shareapi_default_expire_date', 'no'); diff --git a/apps/files_sharing/tests/CacheTest.php b/apps/files_sharing/tests/CacheTest.php index 4db3adf34a644..c78b557dc7f2f 100644 --- a/apps/files_sharing/tests/CacheTest.php +++ b/apps/files_sharing/tests/CacheTest.php @@ -50,7 +50,7 @@ protected function setUp(): void { self::loginHelper(self::TEST_FILES_SHARING_API_USER1); - $this->user2View = new \OC\Files\View('/'. self::TEST_FILES_SHARING_API_USER2 . '/files'); + $this->user2View = new \OC\Files\View('/' . self::TEST_FILES_SHARING_API_USER2 . '/files'); // prepare user1's dir structure $this->view->mkdir('container'); @@ -386,6 +386,7 @@ private function verifyFiles($examples, $results) { } } } + $this->assertEquals([], $results); } diff --git a/apps/files_sharing/tests/CapabilitiesTest.php b/apps/files_sharing/tests/CapabilitiesTest.php index c188cbfdfcc86..9c93818e78249 100644 --- a/apps/files_sharing/tests/CapabilitiesTest.php +++ b/apps/files_sharing/tests/CapabilitiesTest.php @@ -78,6 +78,7 @@ private function getResults(array $map) { ); $cap = new Capabilities($config, $shareManager); $result = $this->getFilesSharingPart($cap->getCapabilities()); + return $result; } diff --git a/apps/files_sharing/tests/Controller/ShareAPIControllerTest.php b/apps/files_sharing/tests/Controller/ShareAPIControllerTest.php index 67aa15d82faa7..1330e5e223607 100644 --- a/apps/files_sharing/tests/Controller/ShareAPIControllerTest.php +++ b/apps/files_sharing/tests/Controller/ShareAPIControllerTest.php @@ -512,7 +512,7 @@ public function createShare($id, $shareType, $sharedWith, $sharedBy, $shareOwner if ($shareType === IShare::TYPE_USER || $shareType === IShare::TYPE_GROUP || $shareType === IShare::TYPE_LINK) { - $share->method('getFullId')->willReturn('ocinternal:'.$id); + $share->method('getFullId')->willReturn('ocinternal:' . $id); } return $share; @@ -1428,6 +1428,7 @@ function ($user, $shareType, $node) use ($shares) { if (!isset($shares[$node->getName()]) || !isset($shares[$node->getName()][$shareType])) { return []; } + return $shares[$node->getName()][$shareType]; } ); @@ -4972,6 +4973,7 @@ private function getNonSharedUserFolder(): array { $userFolder->method('getStorage')->willReturn($storage); $node->method('getStorage')->willReturn($storage); $node->method('getId')->willReturn(42); + return [$userFolder, $node]; } @@ -4987,6 +4989,7 @@ private function getNonSharedUserFile(): array { $userFolder->method('getStorage')->willReturn($storage); $node->method('getStorage')->willReturn($storage); $node->method('getId')->willReturn(42); + return [$userFolder, $node]; } } diff --git a/apps/files_sharing/tests/Controller/ShareControllerTest.php b/apps/files_sharing/tests/Controller/ShareControllerTest.php index d9acb328f819b..48c07fc27547a 100644 --- a/apps/files_sharing/tests/Controller/ShareControllerTest.php +++ b/apps/files_sharing/tests/Controller/ShareControllerTest.php @@ -155,6 +155,7 @@ protected function tearDown(): void { if ($user !== null) { $user->delete(); } + \OC_User::setIncognitoMode(false); \OC::$server->getSession()->set('public_link_authenticated', ''); @@ -284,9 +285,11 @@ public function testShowShare(): void { if ($uid === 'ownerUID') { return $owner; } + if ($uid === 'initiatorUID') { return $initiator; } + return null; }); @@ -430,9 +433,11 @@ public function testShowFileDropShare(): void { if ($uid === 'ownerUID') { return $owner; } + if ($uid === 'initiatorUID') { return $initiator; } + return null; }); @@ -571,9 +576,11 @@ public function testShowShareWithPrivateName(): void { if ($uid === 'ownerUID') { return $owner; } + if ($uid === 'initiatorUID') { return $initiator; } + return null; }); @@ -715,9 +722,11 @@ public function testDisabledOwner(): void { if ($uid === 'ownerUID') { return $owner; } + if ($uid === 'initiatorUID') { return $initiator; } + return null; }); @@ -756,9 +765,11 @@ public function testDisabledInitiator(): void { if ($uid === 'ownerUID') { return $owner; } + if ($uid === 'initiatorUID') { return $initiator; } + return null; }); diff --git a/apps/files_sharing/tests/DeleteOrphanedSharesJobTest.php b/apps/files_sharing/tests/DeleteOrphanedSharesJobTest.php index e0d2d67d45f1a..8af82a4e9039f 100644 --- a/apps/files_sharing/tests/DeleteOrphanedSharesJobTest.php +++ b/apps/files_sharing/tests/DeleteOrphanedSharesJobTest.php @@ -84,6 +84,7 @@ protected function tearDown(): void { if ($user1) { $user1->delete(); } + $user2 = $userManager->get($this->user2); if ($user2) { $user2->delete(); @@ -100,7 +101,9 @@ private function getShares() { while ($row = $result->fetch()) { $shares[] = $row; } + $result->closeCursor(); + return $shares; } diff --git a/apps/files_sharing/tests/EncryptedSizePropagationTest.php b/apps/files_sharing/tests/EncryptedSizePropagationTest.php index af2cf379358e3..69b7747b0c508 100644 --- a/apps/files_sharing/tests/EncryptedSizePropagationTest.php +++ b/apps/files_sharing/tests/EncryptedSizePropagationTest.php @@ -22,6 +22,7 @@ protected function setupUser($name, $password = '') { $this->config->setAppValue('encryption', 'useMasterKey', '0'); $this->setupForUser($name, $password); $this->loginWithEncryption($name); + return new View('/' . $name . '/files'); } } diff --git a/apps/files_sharing/tests/ExpireSharesJobTest.php b/apps/files_sharing/tests/ExpireSharesJobTest.php index cf7be7b20a0ba..9a2fa0b02b426 100644 --- a/apps/files_sharing/tests/ExpireSharesJobTest.php +++ b/apps/files_sharing/tests/ExpireSharesJobTest.php @@ -59,6 +59,7 @@ protected function tearDown(): void { if ($user1) { $user1->delete(); } + $user2 = $userManager->get($this->user2); if ($user2) { $user2->delete(); @@ -80,7 +81,9 @@ private function getShares() { while ($row = $result->fetch()) { $shares[] = $row; } + $result->closeCursor(); + return $shares; } @@ -136,6 +139,7 @@ public function testExpireLinkShare($addExpiration, $interval, $addInterval, $sh } else { $expire->sub(new \DateInterval($interval)); } + $expire = $expire->format('Y-m-d 00:00:00'); // Set expiration date to yesterday diff --git a/apps/files_sharing/tests/External/CacheTest.php b/apps/files_sharing/tests/External/CacheTest.php index 139227134c684..d0c85be2d0938 100644 --- a/apps/files_sharing/tests/External/CacheTest.php +++ b/apps/files_sharing/tests/External/CacheTest.php @@ -90,6 +90,7 @@ protected function tearDown(): void { if ($this->cache) { $this->cache->clear(); } + parent::tearDown(); } diff --git a/apps/files_sharing/tests/External/ManagerTest.php b/apps/files_sharing/tests/External/ManagerTest.php index 7302fd307f211..f445ab3cc1cb3 100644 --- a/apps/files_sharing/tests/External/ManagerTest.php +++ b/apps/files_sharing/tests/External/ManagerTest.php @@ -420,6 +420,7 @@ private function verifyDeclinedGroupShare($shareData, $tempMount = null) { if ($tempMount === null) { $tempMount = '{{TemporaryMountPointName#/SharedFolder}}'; } + $openShares = $this->manager->getOpenShares(); $this->assertCount(1, $openShares); $acceptedShares = self::invokePrivate($this->manager, 'getShares', [true]); diff --git a/apps/files_sharing/tests/ExternalStorageTest.php b/apps/files_sharing/tests/ExternalStorageTest.php index 56a1f3200308c..32f3428f7b623 100644 --- a/apps/files_sharing/tests/ExternalStorageTest.php +++ b/apps/files_sharing/tests/ExternalStorageTest.php @@ -112,6 +112,7 @@ public function stat($path) { if ($path === '') { return true; } + return parent::stat($path); } } diff --git a/apps/files_sharing/tests/MountProviderTest.php b/apps/files_sharing/tests/MountProviderTest.php index 2dc5365ae2b01..60bfd31ebfa74 100644 --- a/apps/files_sharing/tests/MountProviderTest.php +++ b/apps/files_sharing/tests/MountProviderTest.php @@ -73,9 +73,11 @@ private function makeMockShareAttributes($attrs) { $result = $attr['value']; } } + return $result; }) ); + return $shareAttributes; } @@ -105,6 +107,7 @@ private function makeMockShare($id, $nodeId, $owner = 'user2', $target = null, $ // compute share time based on id, simulating share order new \DateTime('@' . (1469193980 + 1000 * $id)) ); + return $share; } diff --git a/apps/files_sharing/tests/PropagationTestCase.php b/apps/files_sharing/tests/PropagationTestCase.php index dee056f0658fd..76b1904f6a7ee 100644 --- a/apps/files_sharing/tests/PropagationTestCase.php +++ b/apps/files_sharing/tests/PropagationTestCase.php @@ -48,6 +48,7 @@ protected function assertEtagsChanged($users, $subPath = '') { $this->assertNotEquals($this->fileEtags[$id], $etag, 'Failed asserting that the etag for "' . $subPath . '" of user ' . $user . ' has changed'); $this->fileEtags[$id] = $etag; } + $this->loginAsUser($oldUser->getUID()); } @@ -65,6 +66,7 @@ protected function assertEtagsNotChanged($users, $subPath = '') { $this->assertEquals($this->fileEtags[$id], $etag, 'Failed asserting that the etag for "' . $subPath . '" of user ' . $user . ' has not changed'); $this->fileEtags[$id] = $etag; } + $this->loginAsUser($oldUser->getUID()); } diff --git a/apps/files_sharing/tests/ShareTest.php b/apps/files_sharing/tests/ShareTest.php index 651341a560764..6956093a0c16c 100644 --- a/apps/files_sharing/tests/ShareTest.php +++ b/apps/files_sharing/tests/ShareTest.php @@ -34,7 +34,7 @@ protected function setUp(): void { $this->view->mkdir($this->folder); $this->view->mkdir($this->folder . $this->subfolder); $this->view->mkdir($this->folder . $this->subfolder . $this->subsubfolder); - $this->view->file_put_contents($this->folder.$this->filename, $this->data); + $this->view->file_put_contents($this->folder . $this->filename, $this->data); $this->view->file_put_contents($this->folder . $this->subfolder . $this->filename, $this->data); } @@ -103,7 +103,7 @@ public function testUnshareFromSelf(): void { public function verifyDirContent($content, $expected) { foreach ($content as $c) { if (!in_array($c['name'], $expected)) { - $this->assertTrue(false, "folder should only contain '" . implode(',', $expected) . "', found: " .$c['name']); + $this->assertTrue(false, "folder should only contain '" . implode(',', $expected) . "', found: " . $c['name']); } } } diff --git a/apps/files_sharing/tests/SharedMountTest.php b/apps/files_sharing/tests/SharedMountTest.php index 82c0770440d00..b05f81d07c7ec 100644 --- a/apps/files_sharing/tests/SharedMountTest.php +++ b/apps/files_sharing/tests/SharedMountTest.php @@ -54,6 +54,7 @@ protected function tearDown(): void { if ($this->view->file_exists($this->folder)) { $this->view->unlink($this->folder); } + if ($this->view->file_exists($this->filename)) { $this->view->unlink($this->filename); } @@ -358,6 +359,7 @@ public function testShareMountOverShare(): void { if (!isset($caches[$prefix])) { $caches[$prefix] = new ArrayCache($prefix); } + return $caches[$prefix]; }); $cacheFactory->method('createDistributed') @@ -365,6 +367,7 @@ public function testShareMountOverShare(): void { if (!isset($caches[$prefix])) { $caches[$prefix] = new ArrayCache($prefix); } + return $caches[$prefix]; }); diff --git a/apps/files_sharing/tests/SharedStorageTest.php b/apps/files_sharing/tests/SharedStorageTest.php index 49ff97c053a92..d42a6d0093d73 100644 --- a/apps/files_sharing/tests/SharedStorageTest.php +++ b/apps/files_sharing/tests/SharedStorageTest.php @@ -41,6 +41,7 @@ protected function tearDown(): void { if ($this->view->file_exists($this->folder)) { $this->view->unlink($this->folder); } + if ($this->view->file_exists($this->filename)) { $this->view->unlink($this->filename); } diff --git a/apps/files_sharing/tests/SharesReminderJobTest.php b/apps/files_sharing/tests/SharesReminderJobTest.php index 5f01f4850e9e8..234c2613937ce 100644 --- a/apps/files_sharing/tests/SharesReminderJobTest.php +++ b/apps/files_sharing/tests/SharesReminderJobTest.php @@ -82,6 +82,7 @@ protected function tearDown(): void { if ($user1) { $user1->delete(); } + $user2 = $userManager->get($this->user2); if ($user2) { $user2->delete(); @@ -150,7 +151,7 @@ public function dataSharesReminder() { * @param bool $shouldBeReminded */ public function testSharesReminder( - \DateTime|null $expirationDate, string $email, bool $isEmpty, int $permissions, bool $shouldBeReminded + ?\DateTime $expirationDate, string $email, bool $isEmpty, int $permissions, bool $shouldBeReminded, ): void { $this->loginAsUser($this->user1); diff --git a/apps/files_sharing/tests/SizePropagationTest.php b/apps/files_sharing/tests/SizePropagationTest.php index de830c508cdd9..136817bb75a8d 100644 --- a/apps/files_sharing/tests/SizePropagationTest.php +++ b/apps/files_sharing/tests/SizePropagationTest.php @@ -25,6 +25,7 @@ protected function setupUser($name, $password = '') { $tmpFolder = \OC::$server->getTempManager()->getTemporaryFolder(); $this->registerMount($name, '\OC\Files\Storage\Local', '/' . $name, ['datadir' => $tmpFolder]); $this->loginAsUser($name); + return new View('/' . $name . '/files'); } diff --git a/apps/files_sharing/tests/TestCase.php b/apps/files_sharing/tests/TestCase.php index 4545e67be3219..6e0336c78bc11 100644 --- a/apps/files_sharing/tests/TestCase.php +++ b/apps/files_sharing/tests/TestCase.php @@ -130,10 +130,12 @@ public static function tearDownAfterClass(): void { if ($user !== null) { $user->delete(); } + $user = \OC::$server->getUserManager()->get(self::TEST_FILES_SHARING_API_USER2); if ($user !== null) { $user->delete(); } + $user = \OC::$server->getUserManager()->get(self::TEST_FILES_SHARING_API_USER3); if ($user !== null) { $user->delete(); diff --git a/apps/files_sharing/tests/UpdaterTest.php b/apps/files_sharing/tests/UpdaterTest.php index 9d26ff873a5ab..8aee8d7ddfc6a 100644 --- a/apps/files_sharing/tests/UpdaterTest.php +++ b/apps/files_sharing/tests/UpdaterTest.php @@ -85,7 +85,7 @@ public function testDeleteParentFolder(): void { // share mount point should now be moved to the subfolder $this->assertFalse($view->file_exists($this->folder)); - $this->assertTrue($view->file_exists('localFolder/' .$this->folder)); + $this->assertTrue($view->file_exists('localFolder/' . $this->folder)); $view->unlink('localFolder'); @@ -290,7 +290,7 @@ public function testMovedIntoShareChangeOwner(): void { ); // user2 moves folder2 into folder1 - $viewUser2->rename($folder2, $folder1.'/'.$folder2); + $viewUser2->rename($folder2, $folder1 . '/' . $folder2); $folder2Share = $this->shareManager->getShareById($folder2Share->getFullId()); $file1Share = $this->shareManager->getShareById($file1Share->getFullId()); $subfolder1Share = $this->shareManager->getShareById($subfolder1Share->getFullId()); @@ -308,7 +308,7 @@ public function testMovedIntoShareChangeOwner(): void { $this->assertEquals(\OCP\Constants::PERMISSION_READ | \OCP\Constants::PERMISSION_SHARE, $file2Share->getPermissions()); // user2 moves folder2 out of folder1 - $viewUser2->rename($folder1.'/'.$folder2, $folder2); + $viewUser2->rename($folder1 . '/' . $folder2, $folder2); $folder2Share = $this->shareManager->getShareById($folder2Share->getFullId()); $file1Share = $this->shareManager->getShareById($file1Share->getFullId()); $subfolder1Share = $this->shareManager->getShareById($subfolder1Share->getFullId()); diff --git a/apps/files_sharing/tests/WatcherTest.php b/apps/files_sharing/tests/WatcherTest.php index 012acdcd69128..30b794cdd4852 100644 --- a/apps/files_sharing/tests/WatcherTest.php +++ b/apps/files_sharing/tests/WatcherTest.php @@ -157,8 +157,10 @@ public function getOwnerDirSizes($path) { $result[$path] = $cachedData['size']; $path = dirname($path); } + $cachedData = $this->ownerCache->get(''); $result[''] = $cachedData['size']; + return $result; } } diff --git a/apps/files_trashbin/lib/BackgroundJob/ExpireTrash.php b/apps/files_trashbin/lib/BackgroundJob/ExpireTrash.php index e458039bf4f1d..609eb81326c93 100644 --- a/apps/files_trashbin/lib/BackgroundJob/ExpireTrash.php +++ b/apps/files_trashbin/lib/BackgroundJob/ExpireTrash.php @@ -24,7 +24,7 @@ public function __construct( IConfig $config, IUserManager $userManager, Expiration $expiration, - ITimeFactory $time + ITimeFactory $time, ) { parent::__construct($time); // Run once per 30 minutes @@ -55,6 +55,7 @@ protected function run($argument) { if (!$this->setupFS($uid)) { return; } + $dirContent = Helper::getTrashFiles('/', $uid, 'mtime'); Trashbin::deleteExpiredFiles($dirContent, $uid); }); diff --git a/apps/files_trashbin/lib/Command/CleanUp.php b/apps/files_trashbin/lib/Command/CleanUp.php index 007e97123bfa8..e5fc9ad4a6c2a 100644 --- a/apps/files_trashbin/lib/Command/CleanUp.php +++ b/apps/files_trashbin/lib/Command/CleanUp.php @@ -79,6 +79,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int if ($backend instanceof IUserBackend) { $name = $backend->getBackendName(); } + $output->writeln("Remove deleted files for users on backend $name"); $limit = 500; $offset = 0; @@ -88,12 +89,14 @@ protected function execute(InputInterface $input, OutputInterface $output): int $output->writeln(" $user"); $this->removeDeletedFiles($user, $output, $verbose); } + $offset += $limit; } while (count($users) >= $limit); } } else { throw new InvalidOptionException('Either specify a user_id or --all-users'); } + return 0; } @@ -110,11 +113,13 @@ protected function removeDeletedFiles(string $uid, OutputInterface $output, bool if ($verbose) { $output->writeln('Deleting ' . \OC_Helper::humanFileSize($node->getSize()) . " in trash for $uid."); } + $node->delete(); if ($this->rootFolder->nodeExists($path)) { $output->writeln('Trash folder sill exists after attempting to delete it'); return; } + $query = $this->dbConnection->getQueryBuilder(); $query->delete('files_trash') ->where($query->expr()->eq('user', $query->createParameter('uid'))) diff --git a/apps/files_trashbin/lib/Command/ExpireTrash.php b/apps/files_trashbin/lib/Command/ExpireTrash.php index 2c2d9d5853978..8615752ead763 100644 --- a/apps/files_trashbin/lib/Command/ExpireTrash.php +++ b/apps/files_trashbin/lib/Command/ExpireTrash.php @@ -81,6 +81,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $p->finish(); $output->writeln(''); } + return 0; } @@ -89,6 +90,7 @@ public function expireTrashForUser(IUser $user) { if (!$this->setupFS($uid)) { return; } + $dirContent = Helper::getTrashFiles('/', $uid, 'mtime'); Trashbin::deleteExpiredFiles($dirContent, $uid); } diff --git a/apps/files_trashbin/lib/Command/RestoreAllFiles.php b/apps/files_trashbin/lib/Command/RestoreAllFiles.php index 969791379c153..25ba78dd78628 100644 --- a/apps/files_trashbin/lib/Command/RestoreAllFiles.php +++ b/apps/files_trashbin/lib/Command/RestoreAllFiles.php @@ -125,6 +125,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int if ($backend instanceof IUserBackend) { $name = $backend->getBackendName(); } + $output->writeln("Restoring deleted files for users on backend $name"); $limit = 500; $offset = 0; @@ -134,12 +135,14 @@ protected function execute(InputInterface $input, OutputInterface $output): int $output->writeln("$user"); $this->restoreDeletedFiles($user, $scope, $since, $until, $dryRun, $output); } + $offset += $limit; } while (count($users) >= $limit); } } else { throw new InvalidOptionException('Either specify a user_id or --all-users'); } + return 0; } @@ -170,6 +173,7 @@ protected function restoreDeletedFiles(string $uid, int $scope, ?int $since, ?in $output->writeln('User has no deleted files in the trashbin matching the given filters'); return; } + $prepMsg = $dryRun ? 'Would restore' : 'Preparing to restore'; $output->writeln("$prepMsg $trashCount files..."); $count = 0; @@ -233,10 +237,12 @@ protected function parseTimestamp(?string $timestamp): ?int { if ($timestamp === null) { return null; } + $timestamp = strtotime($timestamp); if ($timestamp === false) { throw new InvalidOptionException("Invalid timestamp '$timestamp'"); } + return $timestamp; } @@ -275,6 +281,7 @@ protected function filterTrashItems(array $trashItems, int $scope, ?int $since, $filteredTrashItems[] = $trashItem; } + return $filteredTrashItems; } } diff --git a/apps/files_trashbin/lib/Command/Size.php b/apps/files_trashbin/lib/Command/Size.php index 48ee2ced6cb7e..20a39dd4ceaae 100644 --- a/apps/files_trashbin/lib/Command/Size.php +++ b/apps/files_trashbin/lib/Command/Size.php @@ -26,7 +26,7 @@ class Size extends Base { public function __construct( IConfig $config, IUserManager $userManager, - IBus $commandBus + IBus $commandBus, ) { parent::__construct(); @@ -58,6 +58,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $output->writeln('Failed to parse input size'); return -1; } + if ($user) { $this->config->setUserValue($user, 'files_trashbin', 'trashbin_size', (string)$parsedSize); $this->commandBus->push(new Expire($user)); diff --git a/apps/files_trashbin/lib/Controller/PreviewController.php b/apps/files_trashbin/lib/Controller/PreviewController.php index 19eb15b733a8e..058771595a964 100644 --- a/apps/files_trashbin/lib/Controller/PreviewController.php +++ b/apps/files_trashbin/lib/Controller/PreviewController.php @@ -50,7 +50,7 @@ public function __construct( IUserSession $userSession, IMimeTypeDetector $mimeTypeDetector, IPreview $previewManager, - ITimeFactory $time + ITimeFactory $time, ) { parent::__construct($appName, $request); @@ -93,6 +93,7 @@ public function getPreview( if ($file === null) { return new DataResponse([], Http::STATUS_NOT_FOUND); } + if ($file instanceof Folder) { return new DataResponse([], Http::STATUS_BAD_REQUEST); } @@ -115,6 +116,7 @@ public function getPreview( // Cache previews for 24H $response->cacheFor(3600 * 24); + return $response; } catch (NotFoundException $e) { return new DataResponse([], Http::STATUS_NOT_FOUND); diff --git a/apps/files_trashbin/lib/Events/BeforeNodeRestoredEvent.php b/apps/files_trashbin/lib/Events/BeforeNodeRestoredEvent.php index 2aabd101d28c3..0bc6b37c35b95 100644 --- a/apps/files_trashbin/lib/Events/BeforeNodeRestoredEvent.php +++ b/apps/files_trashbin/lib/Events/BeforeNodeRestoredEvent.php @@ -16,7 +16,11 @@ * @since 28.0.0 */ class BeforeNodeRestoredEvent extends AbstractNodesEvent { - public function __construct(Node $source, Node $target, private bool &$run) { + public function __construct( + Node $source, + Node $target, + private bool &$run, + ) { parent::__construct($source, $target); } diff --git a/apps/files_trashbin/lib/Expiration.php b/apps/files_trashbin/lib/Expiration.php index 26826f63931a5..344ad0b31ddfb 100644 --- a/apps/files_trashbin/lib/Expiration.php +++ b/apps/files_trashbin/lib/Expiration.php @@ -103,6 +103,7 @@ public function getMaxAgeAsTimestamp() { $time = $this->timeFactory->getTime(); $maxAge = $time - ($this->maxAge * 86400); } + return $maxAge; } diff --git a/apps/files_trashbin/lib/Helper.php b/apps/files_trashbin/lib/Helper.php index 2e7916d9c6d05..5189e8a63ff95 100644 --- a/apps/files_trashbin/lib/Helper.php +++ b/apps/files_trashbin/lib/Helper.php @@ -50,6 +50,7 @@ public static function getTrashFiles($dir, $user, $sortAttribute = '', $sortDesc $parts = explode('/', ltrim($dir, '/')); $timestamp = substr(pathinfo($parts[0], PATHINFO_EXTENSION), 1); } + $originalPath = ''; $originalName = substr($entryName, 0, -strlen($timestamp) - 2); if (isset($extraData[$originalName][$timestamp]['location'])) { @@ -58,6 +59,7 @@ public static function getTrashFiles($dir, $user, $sortAttribute = '', $sortDesc $originalPath = substr($originalPath, 0, -1); } } + $type = $entry->getMimeType() === ICacheEntry::DIRECTORY_MIMETYPE ? 'dir' : 'file'; $i = [ 'name' => $name, @@ -77,6 +79,7 @@ public static function getTrashFiles($dir, $user, $sortAttribute = '', $sortDesc $i['extraData'] = $originalName; } } + $i['deletedBy'] = $extraData[$originalName][$timestamp]['deletedBy'] ?? null; $result[] = new FileInfo($absoluteDir . '/' . $i['name'], $storage, $internalPath . '/' . $i['name'], $i, $mount); } @@ -84,6 +87,7 @@ public static function getTrashFiles($dir, $user, $sortAttribute = '', $sortDesc if ($sortAttribute !== '') { return \OCA\Files\Helper::sortFiles($result, $sortAttribute, $sortDescending); } + return $result; } @@ -101,6 +105,7 @@ public static function formatFileInfos($fileInfos) { $entry['permissions'] = \OCP\Constants::PERMISSION_READ; $files[] = $entry; } + return $files; } } diff --git a/apps/files_trashbin/lib/Listeners/SyncLivePhotosListener.php b/apps/files_trashbin/lib/Listeners/SyncLivePhotosListener.php index db9a43be6fc48..2cb3a94aa1dc8 100644 --- a/apps/files_trashbin/lib/Listeners/SyncLivePhotosListener.php +++ b/apps/files_trashbin/lib/Listeners/SyncLivePhotosListener.php @@ -113,8 +113,8 @@ private function handleRestore(BeforeNodeRestoredEvent $event, Node $peerFile): */ private function getTrashItem(array $trashFolder, string $path): ?ITrashItem { foreach ($trashFolder as $trashItem) { - if (str_starts_with($path, 'files_trashbin/files'.$trashItem->getTrashPath())) { - if ($path === 'files_trashbin/files'.$trashItem->getTrashPath()) { + if (str_starts_with($path, 'files_trashbin/files' . $trashItem->getTrashPath())) { + if ($path === 'files_trashbin/files' . $trashItem->getTrashPath()) { return $trashItem; } diff --git a/apps/files_trashbin/lib/Migration/Version1010Date20200630192639.php b/apps/files_trashbin/lib/Migration/Version1010Date20200630192639.php index 3de908e2d7865..da5c3d5f79670 100644 --- a/apps/files_trashbin/lib/Migration/Version1010Date20200630192639.php +++ b/apps/files_trashbin/lib/Migration/Version1010Date20200630192639.php @@ -64,6 +64,7 @@ public function changeSchema(IOutput $output, Closure $schemaClosure, array $opt $table->addIndex(['timestamp'], 'timestamp_index'); $table->addIndex(['user'], 'user_index'); } + return $schema; } } diff --git a/apps/files_trashbin/lib/Sabre/AbstractTrashFolder.php b/apps/files_trashbin/lib/Sabre/AbstractTrashFolder.php index 9e8f67f4db6e2..a33e35dffc0fd 100644 --- a/apps/files_trashbin/lib/Sabre/AbstractTrashFolder.php +++ b/apps/files_trashbin/lib/Sabre/AbstractTrashFolder.php @@ -22,6 +22,7 @@ public function getChildren(): array { if ($entry->getType() === FileInfo::TYPE_FOLDER) { return new TrashFolderFolder($this->trashManager, $entry); } + return new TrashFolderFile($this->trashManager, $entry); }, $entries); diff --git a/apps/files_trashbin/lib/Sabre/RootCollection.php b/apps/files_trashbin/lib/Sabre/RootCollection.php index f626bfd7ee150..878c239dcc76d 100644 --- a/apps/files_trashbin/lib/Sabre/RootCollection.php +++ b/apps/files_trashbin/lib/Sabre/RootCollection.php @@ -21,7 +21,7 @@ class RootCollection extends AbstractPrincipalCollection { public function __construct( ITrashManager $trashManager, PrincipalBackend\BackendInterface $principalBackend, - IConfig $config + IConfig $config, ) { parent::__construct($principalBackend, 'principals/users'); @@ -45,6 +45,7 @@ public function getChildForPrincipal(array $principalInfo): TrashHome { if (is_null($user) || $name !== $user->getUID()) { throw new \Sabre\DAV\Exception\Forbidden(); } + return new TrashHome($principalInfo, $this->trashManager, $user); } diff --git a/apps/files_trashbin/lib/Sabre/TrashHome.php b/apps/files_trashbin/lib/Sabre/TrashHome.php index edea2744e6f8d..1e4da591f9b17 100644 --- a/apps/files_trashbin/lib/Sabre/TrashHome.php +++ b/apps/files_trashbin/lib/Sabre/TrashHome.php @@ -27,7 +27,7 @@ class TrashHome implements ICollection { public function __construct( array $principalInfo, ITrashManager $trashManager, - IUser $user + IUser $user, ) { $this->principalInfo = $principalInfo; $this->trashManager = $trashManager; @@ -59,6 +59,7 @@ public function getChild($name) { if ($name === 'restore') { return new RestoreFolder(); } + if ($name === 'trash') { return new TrashRoot($this->user, $this->trashManager); } diff --git a/apps/files_trashbin/lib/Sabre/TrashRoot.php b/apps/files_trashbin/lib/Sabre/TrashRoot.php index 3421a56bd5acd..1bed42b96c935 100644 --- a/apps/files_trashbin/lib/Sabre/TrashRoot.php +++ b/apps/files_trashbin/lib/Sabre/TrashRoot.php @@ -59,6 +59,7 @@ public function getChildren(): array { if ($entry->getType() === FileInfo::TYPE_FOLDER) { return new TrashFolder($this->trashManager, $entry); } + return new TrashFile($this->trashManager, $entry); }, $entries); diff --git a/apps/files_trashbin/lib/Sabre/TrashbinPlugin.php b/apps/files_trashbin/lib/Sabre/TrashbinPlugin.php index 3299c2d6126e2..83564d2a783db 100644 --- a/apps/files_trashbin/lib/Sabre/TrashbinPlugin.php +++ b/apps/files_trashbin/lib/Sabre/TrashbinPlugin.php @@ -32,7 +32,7 @@ class TrashbinPlugin extends ServerPlugin { private $previewManager; public function __construct( - IPreview $previewManager + IPreview $previewManager, ) { $this->previewManager = $previewManager; } diff --git a/apps/files_trashbin/lib/Storage.php b/apps/files_trashbin/lib/Storage.php index d146f23d70a8d..8379b5e8a723d 100644 --- a/apps/files_trashbin/lib/Storage.php +++ b/apps/files_trashbin/lib/Storage.php @@ -43,7 +43,7 @@ public function __construct( ?IUserManager $userManager = null, ?LoggerInterface $logger = null, ?IEventDispatcher $eventDispatcher = null, - ?IRootFolder $rootFolder = null + ?IRootFolder $rootFolder = null, ) { $this->mountPoint = $parameters['mountPoint']; $this->trashManager = $trashManager; @@ -210,17 +210,20 @@ public function moveFromStorage(IStorage $sourceStorage, $sourceInternalPath, $t /** @var Storage $sourceStorage */ $sourceStorage->disableTrash(); } + $result = parent::moveFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath); if ($sourceIsTrashbin) { /** @var Storage $sourceStorage */ $sourceStorage->enableTrash(); } + return $result; } catch (\Exception $e) { if ($sourceIsTrashbin) { /** @var Storage $sourceStorage */ $sourceStorage->enableTrash(); } + throw $e; } } diff --git a/apps/files_trashbin/lib/Trash/LegacyTrashBackend.php b/apps/files_trashbin/lib/Trash/LegacyTrashBackend.php index 862c746c23914..6a95219caa150 100644 --- a/apps/files_trashbin/lib/Trash/LegacyTrashBackend.php +++ b/apps/files_trashbin/lib/Trash/LegacyTrashBackend.php @@ -37,14 +37,17 @@ public function __construct( private function mapTrashItems(array $items, IUser $user, ?ITrashItem $parent = null): array { $parentTrashPath = ($parent instanceof ITrashItem) ? $parent->getTrashPath() : ''; $isRoot = $parent === null; + return array_map(function (FileInfo $file) use ($parent, $parentTrashPath, $isRoot, $user) { $originalLocation = $isRoot ? $file['extraData'] : $parent->getOriginalLocation() . '/' . $file->getName(); if (!$originalLocation) { $originalLocation = $file->getName(); } + /** @psalm-suppress UndefinedInterfaceMethod */ $deletedBy = $this->userManager->get($file['deletedBy']) ?? $parent?->getDeletedBy(); $trashFilename = Trashbin::getTrashFilename($file->getName(), $file->getMtime()); + return new TrashItem( $this, $originalLocation, @@ -65,6 +68,7 @@ public function listTrashRoot(IUser $user): array { public function listTrashFolder(ITrashItem $folder): array { $user = $folder->getUser(); $entries = Helper::getTrashFiles($folder->getTrashPath(), $user->getUID()); + return $this->mapTrashItems($entries, $user, $folder); } @@ -86,6 +90,7 @@ public function moveToTrash(IStorage $storage, string $internalPath): bool { if (!$storage instanceof Storage) { return false; } + $normalized = Filesystem::normalizePath($storage->getMountPoint() . '/' . $internalPath, true, false, true); $view = Filesystem::getView(); if (!isset($this->deletedFiles[$normalized]) && $view instanceof View) { @@ -96,6 +101,7 @@ public function moveToTrash(IStorage $storage, string $internalPath): bool { } else { $result = false; } + unset($this->deletedFiles[$normalized]); } else { $result = false; diff --git a/apps/files_trashbin/lib/Trash/TrashManager.php b/apps/files_trashbin/lib/Trash/TrashManager.php index bf3eaebdc2a0c..114f1f3f88cbd 100644 --- a/apps/files_trashbin/lib/Trash/TrashManager.php +++ b/apps/files_trashbin/lib/Trash/TrashManager.php @@ -32,6 +32,7 @@ public function listTrashRoot(IUser $user): array { usort($items, function (ITrashItem $a, ITrashItem $b) { return $b->getDeletedTime() - $a->getDeletedTime(); }); + return $items; } @@ -79,11 +80,13 @@ public function moveToTrash(IStorage $storage, string $internalPath): bool { if ($this->trashPaused) { return false; } + try { $backend = $this->getBackendForStorage($storage); $this->trashPaused = true; $result = $backend->moveToTrash($storage, $internalPath); $this->trashPaused = false; + return $result; } catch (BackendNotFoundException $e) { return false; @@ -97,6 +100,7 @@ public function getTrashNodeById(IUser $user, int $fileId) { return $item; } } + return null; } diff --git a/apps/files_trashbin/lib/Trashbin.php b/apps/files_trashbin/lib/Trashbin.php index 544bc877d70a6..def190804c12e 100644 --- a/apps/files_trashbin/lib/Trashbin.php +++ b/apps/files_trashbin/lib/Trashbin.php @@ -73,10 +73,12 @@ public static function getUidAndFilename($filename) { if (!$userManager->userExists($uid)) { $uid = OC_User::getUser(); } + if (!$uid) { // no owner, usually because of share link from ext storage return [null, null]; } + Filesystem::initMountPoints($uid); if ($uid !== OC_User::getUser()) { $info = Filesystem::getFileInfo($filename); @@ -87,6 +89,7 @@ public static function getUidAndFilename($filename) { $filename = null; } } + return [$uid, $filename]; } @@ -109,7 +112,9 @@ public static function getExtraData($user) { 'deletedBy' => (string)$row['deleted_by'], ]; } + $result->closeCursor(); + return $array; } @@ -146,12 +151,15 @@ private static function setUpTrash($user): void { if (!$view->is_dir('files_trashbin')) { $view->mkdir('files_trashbin'); } + if (!$view->is_dir('files_trashbin/files')) { $view->mkdir('files_trashbin/files'); } + if (!$view->is_dir('files_trashbin/versions')) { $view->mkdir('files_trashbin/versions'); } + if (!$view->is_dir('files_trashbin/keys')) { $view->mkdir('files_trashbin/keys'); } @@ -186,7 +194,6 @@ private static function copyFilesToUser($sourcePath, $owner, $targetPath, $user, self::copy_recursive($source, $target, $view); } - if ($view->file_exists($target)) { $query = \OC::$server->getDatabaseConnection()->getQueryBuilder(); $query->insert('files_trash') @@ -299,6 +306,7 @@ public static function move2trash($file_path, $ownerOnly = false) { if ($trashStorage->file_exists($trashInternalPath)) { $trashStorage->unlink($trashInternalPath); } + \OC::$server->get(LoggerInterface::class)->error('Couldn\'t move ' . $file_path . ' to the trash bin', ['app' => 'files_trashbin']); } @@ -315,6 +323,7 @@ public static function move2trash($file_path, $ownerOnly = false) { } else { $trashStorage->getUpdater()->remove($trashInternalPath); } + return false; } @@ -330,6 +339,7 @@ public static function move2trash($file_path, $ownerOnly = false) { if (!$result) { \OC::$server->get(LoggerInterface::class)->error('trash bin database couldn\'t be updated', ['app' => 'files_trashbin']); } + \OCP\Util::emitHook('\OCA\Files_Trashbin\Trashbin', 'post_moveToTrash', ['filePath' => Filesystem::normalizePath($file_path), 'trashPath' => Filesystem::normalizePath(static::getTrashFilename($filename, $timestamp))]); @@ -359,10 +369,12 @@ private static function getConfiguredTrashbinSize(string $user): int|float { if (is_numeric($userTrashbinSize) && ($userTrashbinSize > -1)) { return \OCP\Util::numericToNumber($userTrashbinSize); } + $systemTrashbinSize = $config->getAppValue('files_trashbin', 'trashbin_size', '-1'); if (is_numeric($systemTrashbinSize)) { return \OCP\Util::numericToNumber($systemTrashbinSize); } + return -1; } @@ -383,12 +395,14 @@ private static function retainVersions($filename, $owner, $ownerPath, $timestamp if ($owner !== $user) { self::copy_recursive($owner . '/files_versions/' . $ownerPath, $owner . '/files_trashbin/versions/' . static::getTrashFilename(basename($ownerPath), $timestamp), $rootView); } + self::move($rootView, $owner . '/files_versions/' . $ownerPath, $user . '/files_trashbin/versions/' . static::getTrashFilename($filename, $timestamp)); } elseif ($versions = \OCA\Files_Versions\Storage::getVersions($owner, $ownerPath)) { foreach ($versions as $v) { if ($owner !== $user) { self::copy($rootView, $owner . '/files_versions' . $v['path'] . '.v' . $v['version'], $owner . '/files_trashbin/versions/' . static::getTrashFilename($v['name'] . '.v' . $v['version'], $timestamp)); } + self::move($rootView, $owner . '/files_versions' . $v['path'] . '.v' . $v['version'], $user . '/files_trashbin/versions/' . static::getTrashFilename($filename . '.v' . $v['version'], $timestamp)); } } @@ -414,6 +428,7 @@ private static function move(View $view, $source, $target) { if ($result) { $targetStorage->getUpdater()->renameFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath); } + return $result; } @@ -436,6 +451,7 @@ private static function copy(View $view, $source, $target) { if ($result) { $targetStorage->getUpdater()->update($targetInternalPath); } + return $result; } @@ -477,6 +493,7 @@ public static function restore($file, $filename, $timestamp) { if (!$view->file_exists($source)) { return false; } + $mtime = $view->filemtime($source); // restore file @@ -596,6 +613,7 @@ public static function deleteAll() { foreach ($fileInfos as $fileInfo) { $filePaths[] = $view->getRelativePath($fileInfo->getPath()); } + unset($fileInfos); // save memory // Bulk PreDelete-Hook @@ -717,6 +735,7 @@ private static function deleteVersions(View $view, $file, $filename, $timestamp, } } } + return $size; } @@ -736,6 +755,7 @@ public static function file_exists($filename, $timestamp = null) { } $target = Filesystem::normalizePath('files_trashbin/files/' . $filename); + return $view->file_exists($target); } @@ -749,6 +769,7 @@ public static function deleteUser($uid) { $query = \OC::$server->getDatabaseConnection()->getQueryBuilder(); $query->delete('files_trash') ->where($query->expr()->eq('user', $query->createNamedParameter($uid))); + return (bool)$query->executeStatement(); } @@ -769,6 +790,7 @@ private static function calculateFreeSpace(int|float $trashbinSize, string $user if (is_null($userObject)) { return 0; } + $softQuota = true; $quota = $userObject->getQuota(); if ($quota === null || $quota === 'none') { @@ -793,6 +815,7 @@ private static function calculateFreeSpace(int|float $trashbinSize, string $user if (is_null($userFolder)) { return 0; } + $free = $quota - $userFolder->getSize(false); // remaining free space for user if ($free > 0) { $availableSpace = ($free * self::DEFAULTMAXSIZE / 100) - $trashbinSize; // how much space can be used for versions @@ -881,6 +904,7 @@ protected static function deleteFiles(array $files, string $user, int|float $ava } } } + return $size; } @@ -911,6 +935,7 @@ public static function deleteExpiredFiles($files, $user) { ] ); } + \OC::$server->get(LoggerInterface::class)->info( 'Remove "' . $filename . '" from trashbin because it exceeds max retention obligation term.', ['app' => 'files_trashbin'] @@ -947,6 +972,7 @@ private static function copy_recursive($source, $destination, View $view): int|f if (!$result) { throw new \OCA\Files_Trashbin\Exceptions\CopyRecursiveException(); } + $view->touch($destination . '/' . $i['name'], $view->filemtime($pathDir)); } } @@ -956,8 +982,10 @@ private static function copy_recursive($source, $destination, View $view): int|f if (!$result) { throw new \OCA\Files_Trashbin\Exceptions\CopyRecursiveException(); } + $view->touch($destination, $view->filemtime($source)); } + return $size; } @@ -990,7 +1018,7 @@ private static function getVersionsFromTrash($filename, $timestamp, string $user Server::get(IDBConnection::class)->getQueryBuilder(), Server::get(IFilesMetadataManager::class), ); - $normalizedParentPath = ltrim(Filesystem::normalizePath(dirname('files_trashbin/versions/'. $filename)), '/'); + $normalizedParentPath = ltrim(Filesystem::normalizePath(dirname('files_trashbin/versions/' . $filename)), '/'); $parentId = $cache->getId($normalizedParentPath); if ($parentId === -1) { return []; @@ -1068,6 +1096,7 @@ private static function calculateSize(View $view): int|float { if (!file_exists($root)) { return 0; } + $iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($root), \RecursiveIteratorIterator::CHILD_FIRST); $size = 0; @@ -1083,8 +1112,10 @@ private static function calculateSize(View $view): int|float { if (!$view->is_dir($relpath)) { $size += $view->filesize($relpath); } + $iterator->next(); } + return $size; } @@ -1097,6 +1128,7 @@ private static function calculateSize(View $view): int|float { private static function getTrashbinSize(string $user): int|float { $view = new View('/' . $user); $fileInfo = $view->getFileInfo('/files_trashbin'); + return isset($fileInfo['size']) ? $fileInfo['size'] : 0; } @@ -1115,6 +1147,7 @@ public static function isEmpty($user) { } } } + return true; } @@ -1142,6 +1175,7 @@ public static function getTrashFilename(string $filename, int $timestamp): strin $length - $maxLength ); } + return $trashFilename; } diff --git a/apps/files_trashbin/lib/UserMigration/TrashbinMigrator.php b/apps/files_trashbin/lib/UserMigration/TrashbinMigrator.php index 277c176d191ec..9c12b80b08d3b 100644 --- a/apps/files_trashbin/lib/UserMigration/TrashbinMigrator.php +++ b/apps/files_trashbin/lib/UserMigration/TrashbinMigrator.php @@ -28,8 +28,8 @@ class TrashbinMigrator implements IMigrator, ISizeEstimationMigrator { use TMigratorBasicVersionHandling; - protected const PATH_FILES_FOLDER = Application::APP_ID.'/files'; - protected const PATH_LOCATIONS_FILE = Application::APP_ID.'/locations.json'; + protected const PATH_FILES_FOLDER = Application::APP_ID . '/files'; + protected const PATH_LOCATIONS_FILE = Application::APP_ID . '/locations.json'; protected IRootFolder $root; @@ -40,7 +40,7 @@ class TrashbinMigrator implements IMigrator, ISizeEstimationMigrator { public function __construct( IRootFolder $rootFolder, IDBConnection $dbc, - IL10N $l10n + IL10N $l10n, ) { $this->root = $rootFolder; $this->dbc = $dbc; @@ -54,10 +54,11 @@ public function getEstimatedExportSize(IUser $user): int|float { $uid = $user->getUID(); try { - $trashbinFolder = $this->root->get('/'.$uid.'/files_trashbin'); + $trashbinFolder = $this->root->get('/' . $uid . '/files_trashbin'); if (!$trashbinFolder instanceof Folder) { return 0; } + return ceil($trashbinFolder->getSize() / 1024); } catch (\Throwable $e) { return 0; @@ -73,10 +74,11 @@ public function export(IUser $user, IExportDestination $exportDestination, Outpu $uid = $user->getUID(); try { - $trashbinFolder = $this->root->get('/'.$uid.'/files_trashbin'); + $trashbinFolder = $this->root->get('/' . $uid . '/files_trashbin'); if (!$trashbinFolder instanceof Folder) { - throw new UserMigrationException('/'.$uid.'/files_trashbin is not a folder'); + throw new UserMigrationException('/' . $uid . '/files_trashbin is not a folder'); } + $output->writeln('Exporting trashbin files…'); $exportDestination->copyFolder($trashbinFolder, static::PATH_FILES_FOLDER); $originalLocations = []; @@ -86,13 +88,15 @@ public function export(IUser $user, IExportDestination $exportDestination, Outpu foreach ($extraData as $timestamp => ['location' => $location]) { $locationData[$timestamp] = $location; } + $originalLocations[$filename] = $locationData; } + $exportDestination->addFileContents(static::PATH_LOCATIONS_FILE, json_encode($originalLocations)); } catch (NotFoundException $e) { $output->writeln('No trashbin to export…'); } catch (\Throwable $e) { - throw new UserMigrationException('Could not export trashbin: '.$e->getMessage(), 0, $e); + throw new UserMigrationException('Could not export trashbin: ' . $e->getMessage(), 0, $e); } } @@ -111,19 +115,21 @@ public function import(IUser $user, IImportSource $importSource, OutputInterface if ($importSource->pathExists(static::PATH_FILES_FOLDER)) { try { - $trashbinFolder = $this->root->get('/'.$uid.'/files_trashbin'); + $trashbinFolder = $this->root->get('/' . $uid . '/files_trashbin'); if (!$trashbinFolder instanceof Folder) { - throw new UserMigrationException('Could not import trashbin, /'.$uid.'/files_trashbin is not a folder'); + throw new UserMigrationException('Could not import trashbin, /' . $uid . '/files_trashbin is not a folder'); } } catch (NotFoundException $e) { - $trashbinFolder = $this->root->newFolder('/'.$uid.'/files_trashbin'); + $trashbinFolder = $this->root->newFolder('/' . $uid . '/files_trashbin'); } + $output->writeln('Importing trashbin files…'); try { $importSource->copyToFolder($trashbinFolder, static::PATH_FILES_FOLDER); } catch (\Throwable $e) { throw new UserMigrationException('Could not import trashbin.', 0, $e); } + $locations = json_decode($importSource->getFileContents(static::PATH_LOCATIONS_FILE), true, 512, JSON_THROW_ON_ERROR); $qb = $this->dbc->getQueryBuilder(); $qb->insert('files_trash') diff --git a/apps/files_trashbin/tests/Command/CleanUpTest.php b/apps/files_trashbin/tests/Command/CleanUpTest.php index 49ae103ea303c..462a0131b38b8 100644 --- a/apps/files_trashbin/tests/Command/CleanUpTest.php +++ b/apps/files_trashbin/tests/Command/CleanUpTest.php @@ -64,12 +64,13 @@ public function initTable() { for ($i = 0; $i < 10; $i++) { $query->insert($this->trashTable) ->values([ - 'id' => $query->expr()->literal('file'.$i), + 'id' => $query->expr()->literal('file' . $i), 'timestamp' => $query->expr()->literal($i), 'location' => $query->expr()->literal('.'), - 'user' => $query->expr()->literal('user'.$i % 2) + 'user' => $query->expr()->literal('user' . $i % 2) ])->execute(); } + $getAllQuery = $this->dbConnection->getQueryBuilder(); $result = $getAllQuery->select('id') ->from($this->trashTable) @@ -99,6 +100,7 @@ public function testRemoveDeletedFiles(bool $nodeExists): void { $this->rootFolder->expects($this->never())->method('get'); $this->rootFolder->expects($this->never())->method('delete'); } + $this->invokePrivate($this->cleanup, 'removeDeletedFiles', [$this->user0, new NullOutput(), false]); if ($nodeExists) { diff --git a/apps/files_trashbin/tests/StorageTest.php b/apps/files_trashbin/tests/StorageTest.php index f0f3159b32aae..013b8b362d9a2 100644 --- a/apps/files_trashbin/tests/StorageTest.php +++ b/apps/files_trashbin/tests/StorageTest.php @@ -103,6 +103,7 @@ protected function tearDown(): void { if ($user !== null) { $user->delete(); } + \OC_Hook::clear(); parent::tearDown(); } @@ -676,6 +677,7 @@ public function testMoveFromStoragePreserveFileId(): void { if (!$this->userView->getMount('')->getStorage()->instanceOfStorage(Local::class)) { $this->markTestSkipped('Skipping on non-local users storage'); } + $this->userView->file_put_contents('test.txt', 'foo'); $fileId = $this->userView->getFileInfo('test.txt')->getId(); diff --git a/apps/files_trashbin/tests/TrashbinTest.php b/apps/files_trashbin/tests/TrashbinTest.php index 11501891c779a..d72c43cdd478b 100644 --- a/apps/files_trashbin/tests/TrashbinTest.php +++ b/apps/files_trashbin/tests/TrashbinTest.php @@ -284,6 +284,7 @@ private function verifyArray($result, $expected) { break; } } + if (!$found) { // if we didn't found the expected file, something went wrong $this->assertTrue(false, "can't find expected file '" . $expectedFile . "' in trash bin"); @@ -308,6 +309,7 @@ private function manipulateDeleteTime($files, $trashRoot, $expireDate) { $file['mtime'] = $expireDate; } } + return \OCA\Files\Helper::sortFiles($files, 'mtime'); } diff --git a/apps/files_versions/lib/AppInfo/Application.php b/apps/files_versions/lib/AppInfo/Application.php index a31e9823efdaa..926281244b0ac 100644 --- a/apps/files_versions/lib/AppInfo/Application.php +++ b/apps/files_versions/lib/AppInfo/Application.php @@ -65,6 +65,7 @@ public function register(IRegistrationContext $context): void { $context->registerService('principalBackend', function (ContainerInterface $c) { /** @var IServerContainer $server */ $server = $c->get(IServerContainer::class); + return new Principal( $server->get(IUserManager::class), $server->get(IGroupManager::class), diff --git a/apps/files_versions/lib/BackgroundJob/ExpireVersions.php b/apps/files_versions/lib/BackgroundJob/ExpireVersions.php index 7e5ea74d9f99c..eae0e09fa754f 100644 --- a/apps/files_versions/lib/BackgroundJob/ExpireVersions.php +++ b/apps/files_versions/lib/BackgroundJob/ExpireVersions.php @@ -47,6 +47,7 @@ public function run($argument) { if (!$this->setupFS($uid)) { return; } + Storage::expireOlderThanMaxForUser($uid); }); } diff --git a/apps/files_versions/lib/Capabilities.php b/apps/files_versions/lib/Capabilities.php index ce7239a4bf56c..3566b82842ad8 100644 --- a/apps/files_versions/lib/Capabilities.php +++ b/apps/files_versions/lib/Capabilities.php @@ -16,7 +16,7 @@ class Capabilities implements ICapability { public function __construct( IConfig $config, - IAppManager $appManager + IAppManager $appManager, ) { $this->config = $config; $this->appManager = $appManager; diff --git a/apps/files_versions/lib/Command/CleanUp.php b/apps/files_versions/lib/Command/CleanUp.php index 1cd3dccc4dd2d..489138aed8eea 100644 --- a/apps/files_versions/lib/Command/CleanUp.php +++ b/apps/files_versions/lib/Command/CleanUp.php @@ -67,6 +67,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $output->writeln("Delete versions of $user"); $this->deleteVersions($user, $path); } + return self::SUCCESS; } @@ -88,6 +89,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $output->writeln(" $user"); $this->deleteVersions($user); } + $offset += $limit; } while (count($users) >= $limit); } diff --git a/apps/files_versions/lib/Command/ExpireVersions.php b/apps/files_versions/lib/Command/ExpireVersions.php index f8f4e7ce95254..fc876a8a01ba7 100644 --- a/apps/files_versions/lib/Command/ExpireVersions.php +++ b/apps/files_versions/lib/Command/ExpireVersions.php @@ -54,6 +54,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $userObject = $this->userManager->get($user); $this->expireVersionsForUser($userObject); } + return self::SUCCESS; } @@ -65,6 +66,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int }); $p->finish(); $output->writeln(''); + return self::SUCCESS; } @@ -73,6 +75,7 @@ public function expireVersionsForUser(IUser $user): void { if (!$this->setupFS($uid)) { return; } + Storage::expireOlderThanMaxForUser($uid); } diff --git a/apps/files_versions/lib/Controller/PreviewController.php b/apps/files_versions/lib/Controller/PreviewController.php index 8416503c6432b..6b7012848707d 100644 --- a/apps/files_versions/lib/Controller/PreviewController.php +++ b/apps/files_versions/lib/Controller/PreviewController.php @@ -38,7 +38,7 @@ public function __construct( IRootFolder $rootFolder, IUserSession $userSession, IVersionManager $versionManager, - IPreview $previewManager + IPreview $previewManager, ) { parent::__construct($appName, $request); @@ -67,7 +67,7 @@ public function getPreview( string $file = '', int $x = 44, int $y = 44, - string $version = '' + string $version = '', ) { if ($file === '' || $version === '' || $x === 0 || $y === 0) { return new DataResponse([], Http::STATUS_BAD_REQUEST); @@ -79,6 +79,7 @@ public function getPreview( $file = $userFolder->get($file); $versionFile = $this->versionManager->getVersionFile($user, $file, $version); $preview = $this->previewManager->getPreview($versionFile, $x, $y, true, IPreview::MODE_FILL, $versionFile->getMimetype()); + return new FileDisplayResponse($preview, Http::STATUS_OK, ['Content-Type' => $preview->getMimeType()]); } catch (NotFoundException $e) { return new DataResponse([], Http::STATUS_NOT_FOUND); diff --git a/apps/files_versions/lib/Expiration.php b/apps/files_versions/lib/Expiration.php index 26088aa983845..ee73ba70d3f7d 100644 --- a/apps/files_versions/lib/Expiration.php +++ b/apps/files_versions/lib/Expiration.php @@ -113,6 +113,7 @@ public function getMaxAgeAsTimestamp() { $time = $this->timeFactory->getTime(); $maxAge = $time - ($this->maxAge * 86400); } + return $maxAge; } @@ -157,7 +158,6 @@ private function parseRetentionObligation(): void { $maxValue = 'auto'; } - if ($minValue === 'auto' && $maxValue === 'auto') { // Default: Delete anytime if space needed $this->minAge = self::NO_OBLIGATION; diff --git a/apps/files_versions/lib/Listener/FileEventsListener.php b/apps/files_versions/lib/Listener/FileEventsListener.php index c078f4bc34755..e87559c6846b0 100644 --- a/apps/files_versions/lib/Listener/FileEventsListener.php +++ b/apps/files_versions/lib/Listener/FileEventsListener.php @@ -250,6 +250,7 @@ public function remove_hook(Node $node): void { if (!array_key_exists($path, $this->versionsDeleted)) { return; } + $node = $this->versionsDeleted[$path]; $relativePath = $this->getPathForNode($node); unset($this->versionsDeleted[$path]); diff --git a/apps/files_versions/lib/Listener/VersionAuthorListener.php b/apps/files_versions/lib/Listener/VersionAuthorListener.php index 6f4e6c0ee57fe..e55dbef79c07b 100644 --- a/apps/files_versions/lib/Listener/VersionAuthorListener.php +++ b/apps/files_versions/lib/Listener/VersionAuthorListener.php @@ -45,6 +45,7 @@ public function post_write_hook(Node $node): void { if ($node instanceof Folder || is_null($user)) { return; } + // check if our version manager supports setting the metadata if ($this->versionManager instanceof IMetadataVersionBackend) { $author = $user->getUID(); diff --git a/apps/files_versions/lib/Sabre/RestoreFolder.php b/apps/files_versions/lib/Sabre/RestoreFolder.php index 7904b098a4fb3..12cdadbd5d645 100644 --- a/apps/files_versions/lib/Sabre/RestoreFolder.php +++ b/apps/files_versions/lib/Sabre/RestoreFolder.php @@ -56,6 +56,7 @@ public function moveInto($targetName, $sourcePath, INode $sourceNode): bool { } $sourceNode->rollBack(); + return true; } } diff --git a/apps/files_versions/lib/Sabre/RootCollection.php b/apps/files_versions/lib/Sabre/RootCollection.php index 8ed397069f7af..d8d4f1322a6a2 100644 --- a/apps/files_versions/lib/Sabre/RootCollection.php +++ b/apps/files_versions/lib/Sabre/RootCollection.php @@ -34,7 +34,7 @@ public function __construct( IConfig $config, IUserManager $userManager, IVersionManager $versionManager, - IUserSession $userSession + IUserSession $userSession, ) { parent::__construct($principalBackend, 'principals/users'); @@ -62,6 +62,7 @@ public function getChildForPrincipal(array $principalInfo) { if (is_null($user) || $name !== $user->getUID()) { throw new \Sabre\DAV\Exception\Forbidden(); } + return new VersionHome($principalInfo, $this->rootFolder, $this->userManager, $this->versionManager); } diff --git a/apps/files_versions/lib/Sabre/VersionFile.php b/apps/files_versions/lib/Sabre/VersionFile.php index 94fc101f05e35..67dfd2ae69e8e 100644 --- a/apps/files_versions/lib/Sabre/VersionFile.php +++ b/apps/files_versions/lib/Sabre/VersionFile.php @@ -23,7 +23,7 @@ class VersionFile implements IFile { public function __construct( private IVersion $version, - private IVersionManager $versionManager + private IVersionManager $versionManager, ) { } @@ -91,6 +91,7 @@ public function getMetadataValue(string $key): ?string { } elseif ($key === 'label' && $this->version instanceof INameableVersion) { return $this->version->getLabel(); } + return null; } diff --git a/apps/files_versions/lib/Sabre/VersionHome.php b/apps/files_versions/lib/Sabre/VersionHome.php index d89291a8cf8c3..96ecf41a38c4b 100644 --- a/apps/files_versions/lib/Sabre/VersionHome.php +++ b/apps/files_versions/lib/Sabre/VersionHome.php @@ -39,6 +39,7 @@ private function getUser() { if (!$user) { throw new NoUserException(); } + return $user; } @@ -68,6 +69,7 @@ public function getChild($name) { if ($name === 'versions') { return new VersionRoot($user, $this->rootFolder, $this->versionManager); } + if ($name === 'restore') { return new RestoreFolder(); } diff --git a/apps/files_versions/lib/Storage.php b/apps/files_versions/lib/Storage.php index 41f04b5a1d1b1..aa155f90ed306 100644 --- a/apps/files_versions/lib/Storage.php +++ b/apps/files_versions/lib/Storage.php @@ -88,10 +88,11 @@ public static function getUidAndFilename($filename) { if (!$userManager->userExists($uid)) { $uid = OC_User::getUser(); } + Filesystem::initMountPoints($uid); if ($uid !== OC_User::getUser()) { $info = Filesystem::getFileInfo($filename); - $ownerView = new View('/'.$uid.'/files'); + $ownerView = new View('/' . $uid . '/files'); try { $filename = $ownerView->getPath($info['fileid']); // make sure that the file name doesn't end with a trailing slash @@ -101,6 +102,7 @@ public static function getUidAndFilename($filename) { $filename = null; } } + return [$uid, $filename]; } @@ -128,6 +130,7 @@ public static function getSourcePathAndUser($source) { } else { $uid = $path = false; } + return [$uid, $path]; } @@ -140,6 +143,7 @@ public static function getSourcePathAndUser($source) { private static function getVersionsSize($user) { $view = new View('/' . $user); $fileInfo = $view->getFileInfo('/files_versions'); + return isset($fileInfo['size']) ? $fileInfo['size'] : 0; } @@ -258,6 +262,7 @@ public static function delete($path) { } } } + unset(self::$deletedFiles[$path]); } @@ -303,7 +308,7 @@ public static function renameOrCopy($sourcePath, $targetPath, $operation) { // does the directory exists for versions too ? if ($rootView->is_dir('/' . $sourceOwner . '/files_versions/' . $sourcePath)) { // create missing dirs if necessary - self::createMissingDirectories($targetPath, new View('/'. $targetOwner)); + self::createMissingDirectories($targetPath, new View('/' . $targetOwner)); // move the directory containing the versions $rootView->$operation( @@ -313,13 +318,13 @@ public static function renameOrCopy($sourcePath, $targetPath, $operation) { } } elseif ($versions = Storage::getVersions($sourceOwner, '/' . $sourcePath)) { // create missing dirs if necessary - self::createMissingDirectories($targetPath, new View('/'. $targetOwner)); + self::createMissingDirectories($targetPath, new View('/' . $targetOwner)); foreach ($versions as $v) { // move each version one by one to the target directory $rootView->$operation( - '/' . $sourceOwner . '/files_versions/' . $sourcePath.'.v' . $v['version'], - '/' . $targetOwner . '/files_versions/' . $targetPath.'.v' . $v['version'] + '/' . $sourceOwner . '/files_versions/' . $sourcePath . '.v' . $v['version'], + '/' . $targetOwner . '/files_versions/' . $targetPath . '.v' . $v['version'] ); } } @@ -345,8 +350,8 @@ public static function rollback(string $file, int $revision, IUser $user) { $root = \OC::$server->get(IRootFolder::class); $userFolder = $root->getUserFolder($user->getUID()); - $users_view = new View('/'.$user->getUID()); - $files_view = new View('/'. $user->getUID().'/files'); + $users_view = new View('/' . $user->getUID()); + $files_view = new View('/' . $user->getUID() . '/files'); $versionCreated = false; @@ -358,9 +363,9 @@ public static function rollback(string $file, int $revision, IUser $user) { } //first create a new version - $version = 'files_versions'.$filename.'.v'.$users_view->filemtime('files'.$filename); + $version = 'files_versions' . $filename . '.v' . $users_view->filemtime('files' . $filename); if (!$users_view->file_exists($version)) { - $users_view->copy('files'.$filename, 'files_versions'.$filename.'.v'.$users_view->filemtime('files'.$filename)); + $users_view->copy('files' . $filename, 'files_versions' . $filename . '.v' . $users_view->filemtime('files' . $filename)); $versionCreated = true; } @@ -447,6 +452,7 @@ public static function getVersions($uid, $filename, $userFullPath = '') { if (empty($filename)) { return $versions; } + // fetch for old versions $view = new View('/' . $uid . '/'); @@ -482,6 +488,7 @@ public static function getVersions($uid, $filename, $userFullPath = '') { ); continue; } + $filename = $pathparts['filename']; $key = $timestamp . '#' . $filename; $versions[$key]['version'] = $timestamp; @@ -494,6 +501,7 @@ public static function getVersions($uid, $filename, $userFullPath = '') { $versions[$key]['preview'] = $urlGenerator->linkToRoute('files_version.Preview.getPreview', ['file' => $userFullPath, 'version' => $timestamp]); } + $versions[$key]['path'] = Filesystem::normalizePath($pathinfo['dirname'] . '/' . $filename); $versions[$key]['name'] = $versionedFile; $versions[$key]['size'] = $view->filesize($dir . '/' . $entryName); @@ -501,6 +509,7 @@ public static function getVersions($uid, $filename, $userFullPath = '') { } } } + closedir($dirContent); } @@ -562,7 +571,7 @@ public static function expireOlderThanMaxForUser($uid) { } try { - $node = $userFolder->get(substr($path, 0, -strlen('.v'.$version))); + $node = $userFolder->get(substr($path, 0, -strlen('.v' . $version))); $versionEntity = $versionsMapper->findVersionForFileId($node->getId(), $version); $versionEntities[$info->getId()] = $versionEntity; @@ -668,7 +677,7 @@ private static function getAllVersions($uid) { ]; foreach ($versions as $key => $value) { - $size = $view->filesize(self::VERSIONS_ROOT.'/'.$value['path'].'.v'.$value['timestamp']); + $size = $view->filesize(self::VERSIONS_ROOT . '/' . $value['path'] . '.v' . $value['timestamp']); $filename = $value['path']; $result['all'][$key]['version'] = $value['timestamp']; @@ -703,10 +712,11 @@ protected static function getExpireList($time, $versions, $quotaExceeded = false foreach ($versions as $key => $version) { if (!is_numeric($version['version'])) { \OC::$server->get(LoggerInterface::class)->error( - 'Found a non-numeric timestamp version: '. json_encode($version), + 'Found a non-numeric timestamp version: ' . json_encode($version), ['app' => 'files_versions']); continue; } + if ($expiration->isExpired((int)($version['version']), $quotaExceeded) && !isset($toDelete[$key])) { $size += $version['size']; $toDelete[$key] = $version['path'] . '.v' . $version['version']; @@ -753,11 +763,12 @@ protected static function getAutoExpireList($time, $versions) { //distance between two version too small, mark to delete $toDelete[$key] = $version['path'] . '.v' . $version['version']; $size += $version['size']; - \OC::$server->get(LoggerInterface::class)->info('Mark to expire '. $version['path'] .' next version should be ' . $nextVersion . ' or smaller. (prevTimestamp: ' . $prevTimestamp . '; step: ' . $step, ['app' => 'files_versions']); + \OC::$server->get(LoggerInterface::class)->info('Mark to expire ' . $version['path'] . ' next version should be ' . $nextVersion . ' or smaller. (prevTimestamp: ' . $prevTimestamp . '; step: ' . $step, ['app' => 'files_versions']); } else { $nextVersion = $version['version'] - $step; $prevTimestamp = $version['version']; } + $newInterval = false; // version checked so we can move to the next one } else { // time to move on to the next interval $interval++; @@ -768,6 +779,7 @@ protected static function getAutoExpireList($time, $versions) { } else { $nextInterval = $time - Storage::$max_versions_per_interval[$interval]['intervalEndsAfter']; } + $newInterval = true; // we changed the interval -> check same version with new interval } } @@ -833,7 +845,8 @@ public static function expire($filename, $uid) { // file maybe renamed or deleted return false; } - $versionsFileview = new View('/'.$uid.'/files_versions'); + + $versionsFileview = new View('/' . $uid . '/files_versions'); $softQuota = true; $quota = $user->getQuota(); @@ -888,6 +901,7 @@ public static function expire($filename, $uid) { $toDelete = array_merge($toDelete, $toDeleteNew); $sizeOfDeletedVersions += $size; } + $availableSpace = $availableSpace + $sizeOfDeletedVersions; $versionsSize = $versionsSize - $sizeOfDeletedVersions; } @@ -904,6 +918,7 @@ public static function expire($filename, $uid) { if ($versionEntity->getMetadataValue('label') !== null && $versionEntity->getMetadataValue('label') !== '') { continue; } + $versionsMapper->delete($versionEntity); } catch (DoesNotExistException $e) { } @@ -925,10 +940,10 @@ public static function expire($filename, $uid) { reset($allVersions); while ($availableSpace < 0 && $i < $numOfVersions) { $version = current($allVersions); - \OC_Hook::emit('\OCP\Versions', 'preDelete', ['path' => $version['path'].'.v'.$version['version'], 'trigger' => self::DELETE_TRIGGER_QUOTA_EXCEEDED]); + \OC_Hook::emit('\OCP\Versions', 'preDelete', ['path' => $version['path'] . '.v' . $version['version'], 'trigger' => self::DELETE_TRIGGER_QUOTA_EXCEEDED]); self::deleteVersion($versionsFileview, $version['path'] . '.v' . $version['version']); - \OC_Hook::emit('\OCP\Versions', 'delete', ['path' => $version['path'].'.v'.$version['version'], 'trigger' => self::DELETE_TRIGGER_QUOTA_EXCEEDED]); - $logger->info('running out of space! Delete oldest version: ' . $version['path'].'.v'.$version['version'], ['app' => 'files_versions']); + \OC_Hook::emit('\OCP\Versions', 'delete', ['path' => $version['path'] . '.v' . $version['version'], 'trigger' => self::DELETE_TRIGGER_QUOTA_EXCEEDED]); + $logger->info('running out of space! Delete oldest version: ' . $version['path'] . '.v' . $version['version'], ['app' => 'files_versions']); $versionsSize -= $version['size']; $availableSpace += $version['size']; next($allVersions); @@ -969,6 +984,7 @@ protected static function getExpiration() { if (self::$application === null) { self::$application = \OC::$server->get(Application::class); } + return self::$application->getContainer()->get(Expiration::class); } } diff --git a/apps/files_versions/lib/Versions/LegacyVersionsBackend.php b/apps/files_versions/lib/Versions/LegacyVersionsBackend.php index bc46da857525a..f8fef0d12021a 100644 --- a/apps/files_versions/lib/Versions/LegacyVersionsBackend.php +++ b/apps/files_versions/lib/Versions/LegacyVersionsBackend.php @@ -175,6 +175,7 @@ private function getVersionFolder(IUser $user): Folder { try { /** @var Folder $folder */ $folder = $userRoot->get('files_versions'); + return $folder; } catch (NotFoundException $e) { return $userRoot->newFolder('files_versions'); @@ -185,6 +186,7 @@ public function read(IVersion $version) { $versions = $this->getVersionFolder($version->getUser()); /** @var File $file */ $file = $versions->get($version->getVersionPath() . '.v' . $version->getRevisionId()); + return $file->fopen('r'); } @@ -208,6 +210,7 @@ public function getVersionFile(IUser $user, FileInfo $sourceFile, $revision): Fi $versionFolder = $this->getVersionFolder($user); /** @var File $file */ $file = $versionFolder->get($userFolder->getRelativePath($sourceFile->getPath()) . '.v' . $revision); + return $file; } @@ -246,6 +249,7 @@ public function createVersionEntity(File $file): void { ) { throw $e; } + /* Conflict with another version, increase mtime and try again */ $versionEntity->setTimestamp($versionEntity->getTimestamp() + 1); $tries++; @@ -358,6 +362,7 @@ public function importVersionsForFile(IUser $user, Node $source, Node $target, a if ($version instanceof IMetadataVersion) { $versionEntity->setMetadata($version->getMetadata()); } + $this->versionsMapper->insert($versionEntity); } } diff --git a/apps/files_versions/lib/Versions/VersionManager.php b/apps/files_versions/lib/Versions/VersionManager.php index 0b8e066baeb6f..fddff4d2a4a4c 100644 --- a/apps/files_versions/lib/Versions/VersionManager.php +++ b/apps/files_versions/lib/Versions/VersionManager.php @@ -27,6 +27,7 @@ public function registerBackend(string $storageType, IVersionBackend $backend) { if (!isset($this->backends[$storageType])) { $this->backends[$storageType] = []; } + $this->backends[$storageType][] = $backend; } @@ -92,6 +93,7 @@ public function rollback(IVersion $version) { 'node' => $version->getSourceFile(), ]); } + return $result; } @@ -172,6 +174,7 @@ private static function handleAppLocks(callable $callback): ?bool { if (!in_array($owner, $appsThatHandleUpdates)) { throw $e; } + // The LockWrapper in the files_lock app only compares the lock type and owner // when checking the lock against the current scope. // So we do not need to get the actual node here @@ -183,6 +186,7 @@ private static function handleAppLocks(callable $callback): ?bool { $lockManager->runInScope($lockContext, function () use ($callback, &$result) { $result = $callback(); }); + return $result; } } diff --git a/apps/files_versions/tests/StorageTest.php b/apps/files_versions/tests/StorageTest.php index e085729eddd67..acb751447db3b 100644 --- a/apps/files_versions/tests/StorageTest.php +++ b/apps/files_versions/tests/StorageTest.php @@ -51,6 +51,7 @@ protected function createPastFile(string $path, int $mtime) { } catch (NotFoundException $e) { $file = $this->userFolder->newFile($path); } + $file->putContent((string)$mtime); $file->touch($mtime); } diff --git a/apps/files_versions/tests/VersioningTest.php b/apps/files_versions/tests/VersioningTest.php index 3200b5f784b7c..33200d524385d 100644 --- a/apps/files_versions/tests/VersioningTest.php +++ b/apps/files_versions/tests/VersioningTest.php @@ -57,6 +57,7 @@ public static function tearDownAfterClass(): void { if ($user !== null) { $user->delete(); } + $user = \OC::$server->getUserManager()->get(self::TEST_VERSIONS_USER2); if ($user !== null) { $user->delete(); diff --git a/apps/files_versions/tests/Versions/VersionManagerTest.php b/apps/files_versions/tests/Versions/VersionManagerTest.php index a50781b899dba..328f181414204 100644 --- a/apps/files_versions/tests/Versions/VersionManagerTest.php +++ b/apps/files_versions/tests/Versions/VersionManagerTest.php @@ -19,6 +19,7 @@ private function getBackend(bool $shouldUse = true): IVersionBackend { $backend = $this->createMock(IVersionBackend::class); $backend->method('useBackendForStorage') ->willReturn($shouldUse); + return $backend; } diff --git a/apps/lookup_server_connector/lib/BackgroundJobs/RetryJob.php b/apps/lookup_server_connector/lib/BackgroundJobs/RetryJob.php index fd2a76b477307..b883f1d767a9c 100644 --- a/apps/lookup_server_connector/lib/BackgroundJobs/RetryJob.php +++ b/apps/lookup_server_connector/lib/BackgroundJobs/RetryJob.php @@ -63,6 +63,7 @@ public function start(IJobList $jobList): void { if (!isset($this->argument['userId'])) { // Old background job without user id, just drop it. $jobList->remove($this, $this->argument); + return; } diff --git a/apps/oauth2/lib/BackgroundJob/CleanupExpiredAuthorizationCode.php b/apps/oauth2/lib/BackgroundJob/CleanupExpiredAuthorizationCode.php index 84d62ab6b459b..d1647381ffa76 100644 --- a/apps/oauth2/lib/BackgroundJob/CleanupExpiredAuthorizationCode.php +++ b/apps/oauth2/lib/BackgroundJob/CleanupExpiredAuthorizationCode.php @@ -23,7 +23,6 @@ public function __construct( ITimeFactory $timeFactory, private AccessTokenMapper $accessTokenMapper, private LoggerInterface $logger, - ) { parent::__construct($timeFactory); // 30 days diff --git a/apps/oauth2/lib/Controller/LoginRedirectorController.php b/apps/oauth2/lib/Controller/LoginRedirectorController.php index d9a9ed5c5d0db..2512a9761667b 100644 --- a/apps/oauth2/lib/Controller/LoginRedirectorController.php +++ b/apps/oauth2/lib/Controller/LoginRedirectorController.php @@ -76,12 +76,14 @@ public function authorize($client_id, $params = [ 'content' => $this->l->t('Your client is not authorized to connect. Please inform the administrator of your client.'), ]; + return new TemplateResponse('core', '404', $params, 'guest'); } if ($response_type !== 'code') { //Fail $url = $client->getRedirectUri() . '?error=unsupported_response_type&state=' . $state; + return new RedirectResponse($url); } @@ -93,6 +95,7 @@ public function authorize($client_id, 'clientIdentifier' => $client->getClientIdentifier(), ] ); + return new RedirectResponse($targetUrl); } } diff --git a/apps/oauth2/lib/Controller/OauthApiController.php b/apps/oauth2/lib/Controller/OauthApiController.php index d763779053a35..a365c16fc83bd 100644 --- a/apps/oauth2/lib/Controller/OauthApiController.php +++ b/apps/oauth2/lib/Controller/OauthApiController.php @@ -68,7 +68,7 @@ public function __construct( #[BruteForceProtection(action: 'oauth2GetToken')] public function getToken( string $grant_type, ?string $code, ?string $refresh_token, - ?string $client_id, ?string $client_secret + ?string $client_id, ?string $client_secret, ): JSONResponse { // We only handle two types @@ -77,6 +77,7 @@ public function getToken( 'error' => 'invalid_grant', ], Http::STATUS_BAD_REQUEST); $response->throttle(['invalid_grant' => $grant_type]); + return $response; } @@ -92,6 +93,7 @@ public function getToken( 'error' => 'invalid_request', ], Http::STATUS_BAD_REQUEST); $response->throttle(['invalid_request' => 'token not found', 'code' => $code]); + return $response; } @@ -103,6 +105,7 @@ public function getToken( 'error' => 'invalid_request', ], Http::STATUS_BAD_REQUEST); $response->throttle(['invalid_request' => 'authorization_code_received_for_active_token']); + return $response; } @@ -118,6 +121,7 @@ public function getToken( ], Http::STATUS_BAD_REQUEST); $expiredSince = $now - self::AUTHORIZATION_CODE_EXPIRES_AFTER - $codeCreatedAt; $response->throttle(['invalid_request' => 'authorization_code_expired', 'expired_since' => $expiredSince]); + return $response; } } @@ -129,6 +133,7 @@ public function getToken( 'error' => 'invalid_request', ], Http::STATUS_BAD_REQUEST); $response->throttle(['invalid_request' => 'client not found', 'client_id' => $accessToken->getClientId()]); + return $response; } @@ -147,12 +152,14 @@ public function getToken( 'error' => 'invalid_client', ], Http::STATUS_BAD_REQUEST); } + // The client id and secret must match. Else we don't provide an access token! if ($client->getClientIdentifier() !== $client_id || $storedClientSecretHash !== $clientSecretHash) { $response = new JSONResponse([ 'error' => 'invalid_client', ], Http::STATUS_BAD_REQUEST); $response->throttle(['invalid_client' => 'client ID or secret does not match']); + return $response; } @@ -170,6 +177,7 @@ public function getToken( 'error' => 'invalid_request', ], Http::STATUS_BAD_REQUEST); $response->throttle(['invalid_request' => 'token is invalid']); + return $response; } diff --git a/apps/oauth2/lib/Controller/SettingsController.php b/apps/oauth2/lib/Controller/SettingsController.php index f16b26696c4ab..9e99440261dec 100644 --- a/apps/oauth2/lib/Controller/SettingsController.php +++ b/apps/oauth2/lib/Controller/SettingsController.php @@ -35,7 +35,7 @@ public function __construct( private IL10N $l, private IAuthTokenProvider $tokenProvider, private IUserManager $userManager, - private ICrypto $crypto + private ICrypto $crypto, ) { parent::__construct($appName, $request); } @@ -75,6 +75,7 @@ public function deleteClient(int $id): JSONResponse { $this->accessTokenMapper->deleteByClientId($id); $this->clientMapper->delete($client); + return new JSONResponse([]); } } diff --git a/apps/oauth2/lib/Db/ClientMapper.php b/apps/oauth2/lib/Db/ClientMapper.php index dc19c93c4e103..74dc5af758d7d 100644 --- a/apps/oauth2/lib/Db/ClientMapper.php +++ b/apps/oauth2/lib/Db/ClientMapper.php @@ -41,8 +41,9 @@ public function getByIdentifier(string $clientIdentifier): Client { try { $client = $this->findEntity($qb); } catch (IMapperException $e) { - throw new ClientNotFoundException('could not find client '.$clientIdentifier, 0, $e); + throw new ClientNotFoundException('could not find client ' . $clientIdentifier, 0, $e); } + return $client; } @@ -61,8 +62,9 @@ public function getByUid(int $id): Client { try { $client = $this->findEntity($qb); } catch (IMapperException $e) { - throw new ClientNotFoundException('could not find client with id '.$id, 0, $e); + throw new ClientNotFoundException('could not find client with id ' . $id, 0, $e); } + return $client; } diff --git a/apps/oauth2/lib/Migration/SetTokenExpiration.php b/apps/oauth2/lib/Migration/SetTokenExpiration.php index 5077e74be873b..e0330a0208403 100644 --- a/apps/oauth2/lib/Migration/SetTokenExpiration.php +++ b/apps/oauth2/lib/Migration/SetTokenExpiration.php @@ -56,6 +56,7 @@ public function run(IOutput $output) { //Skip this token } } + $cursor->closeCursor(); } } diff --git a/apps/oauth2/lib/Migration/Version010401Date20181207190718.php b/apps/oauth2/lib/Migration/Version010401Date20181207190718.php index 8648826d53c12..ec6219a57a267 100644 --- a/apps/oauth2/lib/Migration/Version010401Date20181207190718.php +++ b/apps/oauth2/lib/Migration/Version010401Date20181207190718.php @@ -77,6 +77,7 @@ public function changeSchema(IOutput $output, Closure $schemaClosure, array $opt $table->addUniqueIndex(['hashed_code'], 'oauth2_access_hash_idx'); $table->addIndex(['client_id'], 'oauth2_access_client_id_idx'); } + return $schema; } } diff --git a/apps/oauth2/lib/Migration/Version010402Date20190107124745.php b/apps/oauth2/lib/Migration/Version010402Date20190107124745.php index 08099c625f75d..b57c93015192a 100644 --- a/apps/oauth2/lib/Migration/Version010402Date20190107124745.php +++ b/apps/oauth2/lib/Migration/Version010402Date20190107124745.php @@ -30,6 +30,7 @@ public function changeSchema(IOutput $output, Closure $schemaClosure, array $opt $table = $schema->getTable('oauth2_clients'); $table->dropIndex('oauth2_client_id_idx'); $table->addUniqueIndex(['client_identifier'], 'oauth2_client_id_idx'); + return $schema; } } diff --git a/apps/oauth2/lib/Migration/Version011601Date20230522143227.php b/apps/oauth2/lib/Migration/Version011601Date20230522143227.php index f2998202e02ac..36a5d2635aabc 100644 --- a/apps/oauth2/lib/Migration/Version011601Date20230522143227.php +++ b/apps/oauth2/lib/Migration/Version011601Date20230522143227.php @@ -33,6 +33,7 @@ public function changeSchema(IOutput $output, Closure $schemaClosure, array $opt if ($table->hasColumn('secret')) { $column = $table->getColumn('secret'); $column->setLength(512); + return $schema; } } @@ -60,6 +61,7 @@ public function postSchemaChange(IOutput $output, Closure $schemaClosure, array $qbUpdate->setParameter('updateId', $id, IQueryBuilder::PARAM_INT); $qbUpdate->executeStatement(); } + $req->closeCursor(); } } diff --git a/apps/oauth2/lib/Migration/Version011602Date20230613160650.php b/apps/oauth2/lib/Migration/Version011602Date20230613160650.php index 06efce324b2f0..b58687d65a944 100644 --- a/apps/oauth2/lib/Migration/Version011602Date20230613160650.php +++ b/apps/oauth2/lib/Migration/Version011602Date20230613160650.php @@ -30,6 +30,7 @@ public function changeSchema(IOutput $output, Closure $schemaClosure, array $opt // we still change the column length in case Version011601Date20230522143227 // has run before it was changed to set the length to 512 $column->setLength(512); + return $schema; } } diff --git a/apps/oauth2/lib/Migration/Version011603Date20230620111039.php b/apps/oauth2/lib/Migration/Version011603Date20230620111039.php index 853eacd2873a4..7fd767065d9bf 100644 --- a/apps/oauth2/lib/Migration/Version011603Date20230620111039.php +++ b/apps/oauth2/lib/Migration/Version011603Date20230620111039.php @@ -38,6 +38,7 @@ public function changeSchema(IOutput $output, Closure $schemaClosure, array $opt ]); $dbChanged = true; } + if (!$table->hasColumn('token_count')) { $table->addColumn('token_count', Types::BIGINT, [ 'notnull' => true, @@ -46,10 +47,12 @@ public function changeSchema(IOutput $output, Closure $schemaClosure, array $opt ]); $dbChanged = true; } + if (!$table->hasIndex('oauth2_tk_c_created_idx')) { $table->addIndex(['token_count', 'code_created_at'], 'oauth2_tk_c_created_idx'); $dbChanged = true; } + if ($dbChanged) { return $schema; } diff --git a/apps/oauth2/lib/Migration/Version011901Date20240829164356.php b/apps/oauth2/lib/Migration/Version011901Date20240829164356.php index 20f5754bf1138..1f3d8e2e3f7aa 100644 --- a/apps/oauth2/lib/Migration/Version011901Date20240829164356.php +++ b/apps/oauth2/lib/Migration/Version011901Date20240829164356.php @@ -44,6 +44,7 @@ public function postSchemaChange(IOutput $output, Closure $schemaClosure, array $qbUpdate->setParameter('updateId', $id, IQueryBuilder::PARAM_INT); $qbUpdate->executeStatement(); } + $req->closeCursor(); } } diff --git a/apps/oauth2/lib/Settings/Admin.php b/apps/oauth2/lib/Settings/Admin.php index 93b6b7bcc3fda..db6d8c7f0251a 100644 --- a/apps/oauth2/lib/Settings/Admin.php +++ b/apps/oauth2/lib/Settings/Admin.php @@ -42,6 +42,7 @@ public function getForm(): TemplateResponse { $this->logger->error('[Settings] OAuth client secret decryption error', ['exception' => $e]); } } + $this->initialState->provideInitialState('clients', $result); $this->initialState->provideInitialState('oauth2-doc-link', $this->urlGenerator->linkToDocs('admin-oauth2')); diff --git a/apps/oauth2/tests/Controller/OauthApiControllerTest.php b/apps/oauth2/tests/Controller/OauthApiControllerTest.php index fa8c90843e6d3..8d13265ec9e8d 100644 --- a/apps/oauth2/tests/Controller/OauthApiControllerTest.php +++ b/apps/oauth2/tests/Controller/OauthApiControllerTest.php @@ -352,7 +352,7 @@ public function testRefreshTokenValidAppToken(): void { $this->secureRandom->method('generate') ->willReturnCallback(function ($len) { - return 'random'.$len; + return 'random' . $len; }); $this->tokenProvider->expects($this->once()) @@ -448,7 +448,7 @@ public function testRefreshTokenValidAppTokenBasicAuth(): void { $this->secureRandom->method('generate') ->willReturnCallback(function ($len) { - return 'random'.$len; + return 'random' . $len; }); $this->tokenProvider->expects($this->once()) @@ -547,7 +547,7 @@ public function testRefreshTokenExpiredAppToken(): void { $this->secureRandom->method('generate') ->willReturnCallback(function ($len) { - return 'random'.$len; + return 'random' . $len; }); $this->tokenProvider->expects($this->once()) diff --git a/apps/provisioning_api/lib/Controller/AUserData.php b/apps/provisioning_api/lib/Controller/AUserData.php index eb881db45e03e..0f13020784e30 100644 --- a/apps/provisioning_api/lib/Controller/AUserData.php +++ b/apps/provisioning_api/lib/Controller/AUserData.php @@ -157,6 +157,7 @@ protected function getUserData(string $userId, bool $includeScopes = false): ?ar $additionalEmailScopes[] = $property->getScope(); } } + $data[IAccountManager::COLLECTION_EMAIL] = $additionalEmails; if ($includeScopes) { $data[IAccountManager::COLLECTION_EMAIL . self::SCOPE_SUFFIX] = $additionalEmailScopes; @@ -253,10 +254,12 @@ protected function fillStorageInfo(string $userId): array { if ($user === null) { throw new OCSException('User does not exist', 101); } + $quota = $user->getQuota(); if ($quota !== 'none') { $quota = OC_Helper::computerFileSize($quota); } + $data = [ self::USER_FIELD_QUOTA => $quota !== false ? $quota : 'none', 'used' => 0 @@ -272,8 +275,10 @@ protected function fillStorageInfo(string $userId): array { ); /* In case the Exception left things in a bad state */ \OC_Util::tearDownFS(); + return []; } + return $data; } } diff --git a/apps/provisioning_api/lib/Controller/AppConfigController.php b/apps/provisioning_api/lib/Controller/AppConfigController.php index bb161f6cb8c8a..e5047d6aa1d3c 100644 --- a/apps/provisioning_api/lib/Controller/AppConfigController.php +++ b/apps/provisioning_api/lib/Controller/AppConfigController.php @@ -69,6 +69,7 @@ public function getKeys(string $app): DataResponse { } catch (\InvalidArgumentException $e) { return new DataResponse(['data' => ['message' => $e->getMessage()]], Http::STATUS_FORBIDDEN); } + return new DataResponse([ 'data' => $this->appConfig->getKeys($app), ]); @@ -94,6 +95,7 @@ public function getValue(string $app, string $key, string $defaultValue = ''): D /** @psalm-suppress InternalMethod */ $value = $this->appConfig->getValueMixed($app, $key, $defaultValue, null); + return new DataResponse(['data' => $value]); } @@ -169,6 +171,7 @@ public function deleteKey(string $app, string $key): DataResponse { } $this->appConfig->deleteKey($app, $key); + return new DataResponse(); } @@ -220,10 +223,12 @@ private function isAllowedToChangedKey(IUser $user, string $app, string $key): b if (!($setting instanceof IDelegatedSettings)) { continue; } + $allowedKeys = $setting->getAuthorizedAppConfig(); if (!array_key_exists($app, $allowedKeys)) { continue; } + foreach ($allowedKeys[$app] as $regex) { if ($regex === $key || (str_starts_with($regex, '/') && preg_match($regex, $key) === 1)) { @@ -231,6 +236,7 @@ private function isAllowedToChangedKey(IUser $user, string $app, string $key): b } } } + return false; } } diff --git a/apps/provisioning_api/lib/Controller/AppsController.php b/apps/provisioning_api/lib/Controller/AppsController.php index d60a85f374039..7fd055efc7c10 100644 --- a/apps/provisioning_api/lib/Controller/AppsController.php +++ b/apps/provisioning_api/lib/Controller/AppsController.php @@ -25,7 +25,7 @@ class AppsController extends OCSController { public function __construct( string $appName, IRequest $request, - IAppManager $appManager + IAppManager $appManager, ) { parent::__construct($appName, $request); @@ -47,6 +47,7 @@ public function getApps(?string $filter = null): DataResponse { foreach ($apps as $app) { $list[] = $app['id']; } + /** @var string[] $list */ if ($filter) { switch ($filter) { @@ -55,6 +56,7 @@ public function getApps(?string $filter = null): DataResponse { break; case 'disabled': $enabled = OC_App::getEnabledApps(); + return new DataResponse(['apps' => array_diff($list, $enabled)]); break; default: @@ -100,6 +102,7 @@ public function enable(string $app): DataResponse { } catch (AppPathNotFoundException $e) { throw new OCSException('The request app was not found', OCSController::RESPOND_NOT_FOUND); } + return new DataResponse(); } diff --git a/apps/provisioning_api/lib/Controller/GroupsController.php b/apps/provisioning_api/lib/Controller/GroupsController.php index 4b05f772e8f27..8f0ab318374b0 100644 --- a/apps/provisioning_api/lib/Controller/GroupsController.php +++ b/apps/provisioning_api/lib/Controller/GroupsController.php @@ -165,6 +165,7 @@ public function getGroupUsers(string $groupId): DataResponse { }, $users); /** @var string[] $users */ $users = array_values($users); + return new DataResponse(['users' => $users]); } @@ -222,6 +223,7 @@ public function getGroupUsersDetails(string $groupId, string $search = '', ?int // continue if a users ceased to exist. } } + return new DataResponse(['users' => $usersDetails]); } @@ -246,17 +248,21 @@ public function addGroup(string $groupid, string $displayname = ''): DataRespons $this->logger->error('Group name not supplied', ['app' => 'provisioning_api']); throw new OCSException('Invalid group name', 101); } + // Check if it exists if ($this->groupManager->groupExists($groupid)) { throw new OCSException('group exists', 102); } + $group = $this->groupManager->createGroup($groupid); if ($group === null) { throw new OCSException('Not supported by backend', 103); } + if ($displayname !== '') { $group->setDisplayName($displayname); } + return new DataResponse(); } @@ -281,6 +287,7 @@ public function updateGroup(string $groupId, string $key, string $value): DataRe if ($group === null) { throw new OCSException('Group does not exist', OCSController::RESPOND_NOT_FOUND); } + if ($group->setDisplayName($value)) { return new DataResponse(); } diff --git a/apps/provisioning_api/lib/Controller/PreferencesController.php b/apps/provisioning_api/lib/Controller/PreferencesController.php index 2a31e076c838a..f230d1d781fc4 100644 --- a/apps/provisioning_api/lib/Controller/PreferencesController.php +++ b/apps/provisioning_api/lib/Controller/PreferencesController.php @@ -31,7 +31,7 @@ public function __construct( IRequest $request, IConfig $config, IUserSession $userSession, - IEventDispatcher $eventDispatcher + IEventDispatcher $eventDispatcher, ) { parent::__construct($appName, $request); $this->config = $config; diff --git a/apps/provisioning_api/lib/Controller/UsersController.php b/apps/provisioning_api/lib/Controller/UsersController.php index e65585902bcad..a79f4f542b04b 100644 --- a/apps/provisioning_api/lib/Controller/UsersController.php +++ b/apps/provisioning_api/lib/Controller/UsersController.php @@ -162,6 +162,7 @@ public function getUsersDetails(string $search = '', ?int $limit = null, int $of foreach ($subAdminOfGroups as $group) { $users[] = array_keys($this->groupManager->displayNamesInGroup($group, $search, $limit, $offset)); } + $users = array_merge(...$users); } @@ -176,6 +177,7 @@ public function getUsersDetails(string $search = '', ?int $limit = null, int $of $userData = null; $this->logger->warning('Found one enabled account that is removed from its backend, but still exists in Nextcloud database', ['accountId' => $userId]); } + // Do not insert empty entry if ($userData !== null) { $usersDetails[$userId] = $userData; @@ -207,9 +209,11 @@ public function getDisabledUsersDetails(string $search = '', ?int $limit = null, if ($currentUser === null) { return new DataResponse(['users' => []]); } + if ($limit !== null && $limit < 0) { throw new InvalidArgumentException("Invalid limit value: $limit"); } + if ($offset < 0) { throw new InvalidArgumentException("Invalid offset value: $offset"); } @@ -245,6 +249,7 @@ public function getDisabledUsersDetails(string $search = '', ?int $limit = null, break; } } + $users = array_slice($users, $offset); } @@ -258,6 +263,7 @@ public function getDisabledUsersDetails(string $search = '', ?int $limit = null, $userData = null; $this->logger->warning('Found one disabled account that was removed from its backend, but still exists in Nextcloud database', ['accountId' => $userId]); } + // Do not insert empty entry if ($userData !== null) { $usersDetails[$userId] = $userData; @@ -292,9 +298,11 @@ public function getLastLoggedInUsers(string $search = '', if ($currentUser === null) { return new DataResponse(['users' => []]); } + if ($limit !== null && $limit < 0) { throw new InvalidArgumentException("Invalid limit value: $limit"); } + if ($offset < 0) { throw new InvalidArgumentException("Invalid offset value: $offset"); } @@ -314,6 +322,7 @@ public function getLastLoggedInUsers(string $search = '', $userData = null; $this->logger->warning('Found one account that was removed from its backend, but still exists in Nextcloud database', ['accountId' => $userId]); } + // Do not insert empty entry if ($userData !== null) { $usersDetails[$userId] = $userData; @@ -417,8 +426,10 @@ private function createNewUserId(): string { if (!$this->userManager->userExists($uidCandidate)) { return $uidCandidate; } + $attempts++; } while ($attempts < 10); + throw new OCSException($this->l10n->t('Could not create non-existing user ID'), 111); } @@ -472,6 +483,7 @@ public function addUser( if (!$this->groupManager->groupExists($group)) { throw new OCSException($this->l10n->t('Group %1$s does not exist', [$group]), 104); } + if (!$isAdmin && !($isDelegatedAdmin && $group !== 'admin') && !$subAdminManager->isSubAdminOfGroup($user, $this->groupManager->get($group))) { throw new OCSException($this->l10n->t('Insufficient privileges for group %1$s', [$group]), 105); } @@ -490,14 +502,17 @@ public function addUser( if ($group === null) { throw new OCSException($this->l10n->t('Sub-admin group does not exist'), 102); } + // Check if trying to make subadmin of admin group if ($group->getGID() === 'admin') { throw new OCSException($this->l10n->t('Cannot create sub-admins for admin group'), 103); } + // Check if has permission to promote subadmins if (!$subAdminManager->isSubAdminOfGroup($user, $group) && !$isAdmin && !$isDelegatedAdmin) { throw new OCSForbiddenException($this->l10n->t('No permissions to promote sub-admins')); } + $subadminGroups[] = $group; } } @@ -506,6 +521,7 @@ public function addUser( if (strlen($password) > IUserManager::MAX_PASSWORD_LENGTH) { throw new OCSException($this->l10n->t('Invalid password value'), 101); } + if ($password === '') { if ($email === '') { throw new OCSException($this->l10n->t('To send a password link to the user an email address is required.'), 108); @@ -523,6 +539,7 @@ public function addUser( . $this->secureRandom->generate(1, ISecureRandom::CHAR_DIGITS) . $this->secureRandom->generate(1, ISecureRandom::CHAR_SYMBOLS); } + $generatePasswordResetToken = true; } @@ -538,6 +555,7 @@ public function addUser( $this->groupManager->get($group)->addUser($newUser); $this->logger->info('Added userid ' . $userid . ' to group ' . $group, ['app' => 'ocs_api']); } + foreach ($subadminGroups as $group) { $subAdminManager->createSubAdmin($newUser, $group); } @@ -549,6 +567,7 @@ public function addUser( if ($newUser instanceof IUser) { $newUser->delete(); } + throw $e; } } @@ -655,6 +674,7 @@ public function getUser(string $userId): DataResponse { if ($data === null) { throw new OCSException('', OCSController::RESPOND_NOT_FOUND); } + return new DataResponse($data); } @@ -674,6 +694,7 @@ public function getCurrentUser(): DataResponse { if ($user) { /** @var Provisioning_APIUserDetails $data */ $data = $this->getUserData($user->getUID(), true); + return new DataResponse($data); } @@ -747,6 +768,7 @@ public function getEditableFieldsForUser(string $userId): DataResponse { ) { $permittedFields[] = IAccountManager::PROPERTY_DISPLAYNAME; } + $permittedFields[] = IAccountManager::PROPERTY_EMAIL; } @@ -787,7 +809,7 @@ public function editUserMultiValue( string $userId, string $collectionName, string $key, - string $value + string $value, ): DataResponse { $currentLoggedInUser = $this->userSession->getUser(); if ($currentLoggedInUser === null) { @@ -838,10 +860,12 @@ public function editUserMultiValue( $property->setLocallyVerified(IAccountManager::VERIFIED); } } + $this->accountManager->updateAccount($userAccount); if ($value === '' && $key === $targetUser->getPrimaryEMailAddress()) { $targetUser->setPrimaryEMailAddress(''); } + break; case IAccountManager::COLLECTION_EMAIL . self::SCOPE_SUFFIX: @@ -854,6 +878,7 @@ public function editUserMultiValue( break; } } + if ($targetProperty instanceof IAccountProperty) { try { $targetProperty->setScope($value); @@ -864,11 +889,13 @@ public function editUserMultiValue( } else { throw new OCSException('', 102); } + break; default: throw new OCSException('', 103); } + return new DataResponse(); } @@ -907,6 +934,7 @@ public function editUser(string $userId, string $key, string $value): DataRespon $permittedFields[] = self::USER_FIELD_DISPLAYNAME; $permittedFields[] = IAccountManager::PROPERTY_DISPLAYNAME; } + $permittedFields[] = IAccountManager::PROPERTY_EMAIL; } @@ -984,6 +1012,7 @@ public function editUser(string $userId, string $key, string $value): DataRespon $permittedFields[] = self::USER_FIELD_DISPLAYNAME; $permittedFields[] = IAccountManager::PROPERTY_DISPLAYNAME; } + $permittedFields[] = IAccountManager::PROPERTY_EMAIL; $permittedFields[] = IAccountManager::COLLECTION_EMAIL; $permittedFields[] = self::USER_FIELD_PASSWORD; @@ -1009,10 +1038,12 @@ public function editUser(string $userId, string $key, string $value): DataRespon throw new OCSException('', OCSController::RESPOND_NOT_FOUND); } } + // Check if permitted to edit this field if (!in_array($key, $permittedFields)) { throw new OCSException('', 103); } + // Process the edit switch ($key) { case self::USER_FIELD_DISPLAYNAME: @@ -1022,6 +1053,7 @@ public function editUser(string $userId, string $key, string $value): DataRespon } catch (InvalidArgumentException $e) { throw new OCSException($e->getMessage(), 101); } + break; case self::USER_FIELD_QUOTA: $quota = $value; @@ -1031,9 +1063,11 @@ public function editUser(string $userId, string $key, string $value): DataRespon } else { $quota = \OCP\Util::computerFileSize($quota); } + if ($quota === false) { throw new OCSException($this->l10n->t('Invalid quota value: %1$s', [$value]), 102); } + if ($quota === -1) { $quota = 'none'; } else { @@ -1041,9 +1075,11 @@ public function editUser(string $userId, string $key, string $value): DataRespon if ($maxQuota !== -1 && $quota > $maxQuota) { throw new OCSException($this->l10n->t('Invalid quota value. %1$s is exceeding the maximum quota', [$value]), 102); } + $quota = \OCP\Util::humanFileSize($quota); } } + // no else block because quota can be set to 'none' in previous if if ($quota === 'none') { $allowUnlimitedQuota = $this->config->getAppValue('files', 'allow_unlimited_quota', '1') === '1'; @@ -1051,6 +1087,7 @@ public function editUser(string $userId, string $key, string $value): DataRespon throw new OCSException($this->l10n->t('Unlimited quota is forbidden on this instance'), 102); } } + $targetUser->setQuota($quota); break; case self::USER_FIELD_MANAGER: @@ -1061,25 +1098,30 @@ public function editUser(string $userId, string $key, string $value): DataRespon if (strlen($value) > IUserManager::MAX_PASSWORD_LENGTH) { throw new OCSException($this->l10n->t('Invalid password value'), 102); } + if (!$targetUser->canChangePassword()) { throw new OCSException($this->l10n->t('Setting the password is not supported by the users backend'), 103); } + $targetUser->setPassword($value); } catch (HintException $e) { // password policy error throw new OCSException($e->getMessage(), 103); } + break; case self::USER_FIELD_LANGUAGE: $languagesCodes = $this->l10nFactory->findAvailableLanguages(); if (!in_array($value, $languagesCodes, true) && $value !== 'en') { throw new OCSException($this->l10n->t('Invalid language'), 102); } + $this->config->setUserValue($targetUser->getUID(), 'core', 'lang', $value); break; case self::USER_FIELD_LOCALE: if (!$this->l10nFactory->localeExists($value)) { throw new OCSException($this->l10n->t('Invalid locale'), 102); } + $this->config->setUserValue($targetUser->getUID(), 'core', 'locale', $value); break; case self::USER_FIELD_FIRST_DAY_OF_WEEK: @@ -1087,11 +1129,13 @@ public function editUser(string $userId, string $key, string $value): DataRespon if ($intValue < -1 || $intValue > 6) { throw new OCSException($this->l10n->t('Invalid first day of week'), 102); } + if ($intValue === -1) { $this->config->deleteUserValue($targetUser->getUID(), 'core', AUserData::USER_FIELD_FIRST_DAY_OF_WEEK); } else { $this->config->setUserValue($targetUser->getUID(), 'core', AUserData::USER_FIELD_FIRST_DAY_OF_WEEK, $value); } + break; case self::USER_FIELD_NOTIFICATION_EMAIL: $success = false; @@ -1109,9 +1153,11 @@ public function editUser(string $userId, string $key, string $value): DataRespon ); } } + if (!$success) { throw new OCSException('', 102); } + break; case IAccountManager::PROPERTY_EMAIL: if (filter_var($value, FILTER_VALIDATE_EMAIL) || $value === '') { @@ -1119,6 +1165,7 @@ public function editUser(string $userId, string $key, string $value): DataRespon } else { throw new OCSException('', 102); } + break; case IAccountManager::COLLECTION_EMAIL: if (filter_var($value, FILTER_VALIDATE_EMAIL) && $value !== $targetUser->getSystemEMailAddress()) { @@ -1134,6 +1181,7 @@ public function editUser(string $userId, string $key, string $value): DataRespon } else { throw new OCSException('', 102); } + break; case IAccountManager::PROPERTY_PHONE: case IAccountManager::PROPERTY_ADDRESS: @@ -1162,11 +1210,13 @@ public function editUser(string $userId, string $key, string $value): DataRespon } catch (PropertyDoesNotExistException $e) { $userAccount->setProperty($key, $value, IAccountManager::SCOPE_PRIVATE, IAccountManager::NOT_VERIFIED); } + try { $this->accountManager->updateAccount($userAccount); } catch (InvalidArgumentException $e) { throw new OCSException('Invalid ' . $e->getMessage(), 102); } + break; case IAccountManager::PROPERTY_PROFILE_ENABLED: $userAccount = $this->accountManager->getAccount($targetUser); @@ -1178,6 +1228,7 @@ public function editUser(string $userId, string $key, string $value): DataRespon } catch (PropertyDoesNotExistException $e) { $userAccount->setProperty($key, $value, IAccountManager::SCOPE_LOCAL, IAccountManager::NOT_VERIFIED); } + $this->accountManager->updateAccount($userAccount); break; case IAccountManager::PROPERTY_DISPLAYNAME . self::SCOPE_SUFFIX: @@ -1206,10 +1257,12 @@ public function editUser(string $userId, string $key, string $value): DataRespon throw new OCSException('Invalid ' . $e->getMessage(), 102); } } + break; default: throw new OCSException('', 103); } + return new DataResponse(); } @@ -1347,6 +1400,7 @@ private function setEnabled(string $userId, bool $value): DataResponse { // enable/disable the user now $targetUser->setEnabled($value); + return new DataResponse(); } @@ -1388,11 +1442,13 @@ public function getUsersGroups(string $userId): DataResponse { foreach ($getSubAdminsGroups as $key => $group) { $getSubAdminsGroups[$key] = $group->getGID(); } + /** @var string[] $groups */ $groups = array_intersect( $getSubAdminsGroups, $this->groupManager->getUserGroupIds($targetUser) ); + return new DataResponse(['groups' => $groups]); } else { // Not permitted @@ -1423,6 +1479,7 @@ public function addToGroup(string $userId, string $groupid = ''): DataResponse { if ($group === null) { throw new OCSException('', 102); } + if ($targetUser === null) { throw new OCSException('', 103); } @@ -1438,6 +1495,7 @@ public function addToGroup(string $userId, string $groupid = ''): DataResponse { // Add user to group $group->addUser($targetUser); + return new DataResponse(); } @@ -1505,6 +1563,7 @@ public function removeFromGroup(string $userId, string $groupid): DataResponse { // Remove user from group $group->removeUser($targetUser); + return new DataResponse(); } @@ -1528,10 +1587,12 @@ public function addSubAdmin(string $userId, string $groupid): DataResponse { if ($user === null) { throw new OCSException($this->l10n->t('User does not exist'), 101); } + // Check if group exists if ($group === null) { throw new OCSException($this->l10n->t('Group does not exist'), 102); } + // Check if trying to make subadmin of admin group if ($group->getGID() === 'admin') { throw new OCSException($this->l10n->t('Cannot create sub-admins for admin group'), 103); @@ -1543,8 +1604,10 @@ public function addSubAdmin(string $userId, string $groupid): DataResponse { if ($subAdminManager->isSubAdminOfGroup($user, $group)) { return new DataResponse(); } + // Go $subAdminManager->createSubAdmin($user, $group); + return new DataResponse(); } @@ -1569,10 +1632,12 @@ public function removeSubAdmin(string $userId, string $groupid): DataResponse { if ($user === null) { throw new OCSException($this->l10n->t('User does not exist'), 101); } + // Check if the group exists if ($group === null) { throw new OCSException($this->l10n->t('Group does not exist'), 101); } + // Check if they are a subadmin of this said group if (!$subAdminManager->isSubAdminOfGroup($user, $group)) { throw new OCSException($this->l10n->t('User is not a sub-admin of this group'), 102); @@ -1580,6 +1645,7 @@ public function removeSubAdmin(string $userId, string $groupid): DataResponse { // Go $subAdminManager->deleteSubAdmin($user, $group); + return new DataResponse(); } diff --git a/apps/provisioning_api/lib/Controller/VerificationController.php b/apps/provisioning_api/lib/Controller/VerificationController.php index 18113484c8a4e..7342b6259d7cc 100644 --- a/apps/provisioning_api/lib/Controller/VerificationController.php +++ b/apps/provisioning_api/lib/Controller/VerificationController.php @@ -49,7 +49,7 @@ public function __construct( IL10N $l10n, IUserSession $userSession, IAccountManager $accountManager, - Crypto $crypto + Crypto $crypto, ) { parent::__construct($appName, $request); $this->verificationToken = $verificationToken; @@ -70,6 +70,7 @@ public function showVerifyMail(string $token, string $userId, string $key): Temp // not a public page, hence getUser() must return an IUser throw new InvalidArgumentException('Logged in account is not mail address owner'); } + $email = $this->crypto->decrypt($key); return new TemplateResponse( @@ -91,6 +92,7 @@ public function verifyMail(string $token, string $userId, string $key): Template if ($this->userSession->getUser()->getUID() !== $userId) { throw new InvalidArgumentException('Logged in account is not mail address owner'); } + $email = $this->crypto->decrypt($key); $ref = \substr(hash('sha256', $email), 0, 8); @@ -104,6 +106,7 @@ public function verifyMail(string $token, string $userId, string $key): Template if ($emailProperty === null) { throw new InvalidArgumentException($this->l10n->t('Email was already removed from account and cannot be confirmed anymore.')); } + $emailProperty->setLocallyVerified(IAccountManager::VERIFIED); $this->accountManager->updateAccount($userAccount); $this->verificationToken->delete($token, $user, 'verifyMail' . $ref); @@ -128,6 +131,7 @@ public function verifyMail(string $token, string $userId, string $key): Template if ($throttle) { $response->throttle(); } + return $response; } diff --git a/apps/provisioning_api/tests/Controller/AppsControllerTest.php b/apps/provisioning_api/tests/Controller/AppsControllerTest.php index 9c815a5217818..c436a294dcc9e 100644 --- a/apps/provisioning_api/tests/Controller/AppsControllerTest.php +++ b/apps/provisioning_api/tests/Controller/AppsControllerTest.php @@ -88,6 +88,7 @@ public function testGetAppsDisabled(): void { foreach ($apps as $app) { $list[] = $app['id']; } + $disabled = array_diff($list, \OC_App::getEnabledApps()); $this->assertEquals(count($disabled), count($data['apps'])); } diff --git a/apps/provisioning_api/tests/Controller/GroupsControllerTest.php b/apps/provisioning_api/tests/Controller/GroupsControllerTest.php index 8bbc8a29c29fc..42528d7dd75e4 100644 --- a/apps/provisioning_api/tests/Controller/GroupsControllerTest.php +++ b/apps/provisioning_api/tests/Controller/GroupsControllerTest.php @@ -90,7 +90,7 @@ private function createGroup($gid) { ->willReturn($gid); $group ->method('getDisplayName') - ->willReturn($gid.'-name'); + ->willReturn($gid . '-name'); $group ->method('count') ->willReturn(123); @@ -120,6 +120,7 @@ private function createUser($uid) { $user ->method('getBackend') ->willReturn($backendMock); + return $user; } @@ -154,6 +155,7 @@ private function asSubAdminOfGroup($group) { if ($_user === $user && $_group === $group) { return true; } + return false; }); } diff --git a/apps/provisioning_api/tests/Controller/UsersControllerTest.php b/apps/provisioning_api/tests/Controller/UsersControllerTest.php index 3fcac1290db80..c3fdf6b8d710e 100644 --- a/apps/provisioning_api/tests/Controller/UsersControllerTest.php +++ b/apps/provisioning_api/tests/Controller/UsersControllerTest.php @@ -437,6 +437,7 @@ public function testAddUserSuccessfulGenerateUserID(): void { if ($key === 'newUser.generateUserID') { return 'yes'; } + return null; }); $this->userManager @@ -538,6 +539,7 @@ public function testAddUserFailedToGenerateUserID(): void { if ($key === 'newUser.generateUserID') { return 'yes'; } + return null; }); $this->userManager @@ -581,6 +583,7 @@ public function testAddUserEmailRequired(): void { if ($key === 'newUser.requireEmail') { return 'yes'; } + return null; }); $this->userManager @@ -1965,6 +1968,7 @@ public function testEditUserAdminUserSelfEditChangeValidQuota(): void { if ($key === 'max_quota') { return '-1'; } + return null; }); $loggedInUser = $this->getMockBuilder(IUser::class)->disableOriginalConstructor()->getMock(); @@ -2053,6 +2057,7 @@ public function testEditUserAdminUserEditChangeValidQuota(): void { if ($key === 'max_quota') { return '-1'; } + return null; }); $loggedInUser = $this->getMockBuilder(IUser::class)->disableOriginalConstructor()->getMock(); @@ -2310,6 +2315,7 @@ public function testEditUserSubadminUserAccessible(): void { if ($key === 'max_quota') { return '-1'; } + return null; }); $loggedInUser = $this->getMockBuilder(IUser::class)->disableOriginalConstructor()->getMock(); diff --git a/apps/provisioning_api/tests/Middleware/ProvisioningApiMiddlewareTest.php b/apps/provisioning_api/tests/Middleware/ProvisioningApiMiddlewareTest.php index d097febb04fe7..afdeaade5b017 100644 --- a/apps/provisioning_api/tests/Middleware/ProvisioningApiMiddlewareTest.php +++ b/apps/provisioning_api/tests/Middleware/ProvisioningApiMiddlewareTest.php @@ -63,9 +63,11 @@ public function testBeforeController($subadminRequired, $isAdmin, $isSubAdmin, $ if ($annotation === 'NoSubAdminRequired') { return !$subadminRequired; } + if ($annotation === 'AuthorizedAdminSetting') { return $hasSettingAuthorizationAnnotation; } + return false; }); diff --git a/apps/provisioning_api/tests/TestCase.php b/apps/provisioning_api/tests/TestCase.php index de2a01201848c..49884d141f47d 100644 --- a/apps/provisioning_api/tests/TestCase.php +++ b/apps/provisioning_api/tests/TestCase.php @@ -42,6 +42,7 @@ protected function generateUsers($num = 1) { $this->users[] = $user; $users[] = $user; } + return count($users) == 1 ? reset($users) : $users; } diff --git a/apps/settings/lib/Activity/GroupProvider.php b/apps/settings/lib/Activity/GroupProvider.php index fc7189c774db5..2b139b1400931 100644 --- a/apps/settings/lib/Activity/GroupProvider.php +++ b/apps/settings/lib/Activity/GroupProvider.php @@ -78,6 +78,7 @@ public function parse($language, IEvent $event, ?IEvent $previousEvent = null) { } else { $subject = $l->t('An administrator added {user} to group {group}'); } + break; case self::REMOVED_FROM_GROUP: if (isset($parsedParameters['actor'])) { @@ -93,6 +94,7 @@ public function parse($language, IEvent $event, ?IEvent $previousEvent = null) { } else { $subject = $l->t('An administrator removed {user} from group {group}'); } + break; default: throw new UnknownActivityException(); @@ -135,6 +137,7 @@ protected function getGroupDisplayName(string $gid): string { if ($group instanceof IGroup) { return $group->getDisplayName(); } + return $gid; } diff --git a/apps/settings/lib/Activity/SecurityProvider.php b/apps/settings/lib/Activity/SecurityProvider.php index 8e2b4f8f45ac8..c299cdce0ecf6 100644 --- a/apps/settings/lib/Activity/SecurityProvider.php +++ b/apps/settings/lib/Activity/SecurityProvider.php @@ -50,6 +50,7 @@ public function parse($language, IEvent $event, ?IEvent $previousEvent = null) { } else { $event->setIcon($this->urlGenerator->getAbsoluteURL($this->urlGenerator->imagePath('core', 'actions/password.svg'))); } + break; case 'twofactor_failed': $params = $event->getSubjectParameters(); @@ -61,6 +62,7 @@ public function parse($language, IEvent $event, ?IEvent $previousEvent = null) { } else { $event->setIcon($this->urlGenerator->getAbsoluteURL($this->urlGenerator->imagePath('core', 'actions/password.svg'))); } + break; case 'remote_wipe_start': $params = $event->getSubjectParameters(); @@ -72,6 +74,7 @@ public function parse($language, IEvent $event, ?IEvent $previousEvent = null) { } else { $event->setIcon($this->urlGenerator->getAbsoluteURL($this->urlGenerator->imagePath('core', 'actions/delete.svg'))); } + break; case 'remote_wipe_finish': $params = $event->getSubjectParameters(); @@ -83,10 +86,12 @@ public function parse($language, IEvent $event, ?IEvent $previousEvent = null) { } else { $event->setIcon($this->urlGenerator->getAbsoluteURL($this->urlGenerator->imagePath('core', 'actions/delete.svg'))); } + break; default: throw new UnknownActivityException(); } + return $event; } } diff --git a/apps/settings/lib/AppInfo/Application.php b/apps/settings/lib/AppInfo/Application.php index a9b1a5c1d6741..e113842f26284 100644 --- a/apps/settings/lib/AppInfo/Application.php +++ b/apps/settings/lib/AppInfo/Application.php @@ -125,16 +125,19 @@ public function register(IRegistrationContext $context): void { if ($userObject !== null) { $isSubAdmin = \OC::$server->getGroupManager()->getSubAdmin()->isSubAdmin($userObject); } + return $isSubAdmin; }); $context->registerService(IProvider::class, function (IAppContainer $appContainer) { /** @var IServerContainer $serverContainer */ $serverContainer = $appContainer->query(IServerContainer::class); + return $serverContainer->query(IProvider::class); }); $context->registerService(IManager::class, function (IAppContainer $appContainer) { /** @var IServerContainer $serverContainer */ $serverContainer = $appContainer->query(IServerContainer::class); + return $serverContainer->getSettingsManager(); }); diff --git a/apps/settings/lib/BackgroundJobs/VerifyUserData.php b/apps/settings/lib/BackgroundJobs/VerifyUserData.php index 62b7c44c8ef3a..40049baf50464 100644 --- a/apps/settings/lib/BackgroundJobs/VerifyUserData.php +++ b/apps/settings/lib/BackgroundJobs/VerifyUserData.php @@ -107,6 +107,7 @@ protected function verifyWebsite(array $argument) { $this->logger->error($argument['uid'] . ' doesn\'t exist, can\'t verify user data.'); return $result; } + $userAccount = $this->accountManager->getAccount($user); $websiteProp = $userAccount->getProperty(IAccountManager::PROPERTY_WEBSITE); $websiteProp->setVerified($publishedCodeSanitized === $argument['verificationCode'] diff --git a/apps/settings/lib/Command/AdminDelegation/Add.php b/apps/settings/lib/Command/AdminDelegation/Add.php index 26a13b2cd6eb6..5cbef5c5d157b 100644 --- a/apps/settings/lib/Command/AdminDelegation/Add.php +++ b/apps/settings/lib/Command/AdminDelegation/Add.php @@ -52,7 +52,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $this->authorizedGroupService->create($groupId, $settingClass); - $io->success('Administration of '.$settingClass.' delegated to '.$groupId.'.'); + $io->success('Administration of ' . $settingClass . ' delegated to ' . $groupId . '.'); return 0; } diff --git a/apps/settings/lib/Command/AdminDelegation/Remove.php b/apps/settings/lib/Command/AdminDelegation/Remove.php index 584b9201193c9..5a12516bea94a 100644 --- a/apps/settings/lib/Command/AdminDelegation/Remove.php +++ b/apps/settings/lib/Command/AdminDelegation/Remove.php @@ -43,12 +43,13 @@ protected function execute(InputInterface $input, OutputInterface $output): int foreach ($groups as $group) { if ($group->getGroupId() === $groupId) { $this->authorizedGroupService->delete($group->getId()); - $io->success('Removed delegation of '.$settingClass.' to '.$groupId.'.'); + $io->success('Removed delegation of ' . $settingClass . ' to ' . $groupId . '.'); + return 0; } } - $io->success('Group '.$groupId.' didn’t have delegation for '.$settingClass.'.'); + $io->success('Group ' . $groupId . ' didn’t have delegation for ' . $settingClass . '.'); return 0; } diff --git a/apps/settings/lib/Command/AdminDelegation/Show.php b/apps/settings/lib/Command/AdminDelegation/Show.php index 73f89ad0eadff..daeef818fa4f5 100644 --- a/apps/settings/lib/Command/AdminDelegation/Show.php +++ b/apps/settings/lib/Command/AdminDelegation/Show.php @@ -47,7 +47,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int continue; } - $io->section('Section: '.$section->getID()); + $io->section('Section: ' . $section->getID()); $io->table($headers, array_map(function (IDelegatedSettings $setting) use ($section) { $className = get_class($setting); $groups = array_map( @@ -55,6 +55,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $this->authorizedGroupService->findExistingGroupsForClass($className) ); natsort($groups); + return [ $setting->getName() ?: 'Global', $className, diff --git a/apps/settings/lib/Controller/AISettingsController.php b/apps/settings/lib/Controller/AISettingsController.php index 0586c6527028f..2443492ad4467 100644 --- a/apps/settings/lib/Controller/AISettingsController.php +++ b/apps/settings/lib/Controller/AISettingsController.php @@ -43,6 +43,7 @@ public function update($settings) { if (!isset($settings[$key])) { continue; } + $this->config->setAppValue('core', $key, json_encode($settings[$key])); } diff --git a/apps/settings/lib/Controller/AdminSettingsController.php b/apps/settings/lib/Controller/AdminSettingsController.php index 25338f64326a7..00c07c556730e 100644 --- a/apps/settings/lib/Controller/AdminSettingsController.php +++ b/apps/settings/lib/Controller/AdminSettingsController.php @@ -69,7 +69,9 @@ protected function getSettings($section) { if (empty($settings) && empty($declarativeFormIDs)) { throw new NotAdminException("Logged in user doesn't have permission to access these settings."); } + $formatted = $this->formatSettings($settings); + return $formatted; } } diff --git a/apps/settings/lib/Controller/AppSettingsController.php b/apps/settings/lib/Controller/AppSettingsController.php index 2c682ac460035..b01162e4d7c72 100644 --- a/apps/settings/lib/Controller/AppSettingsController.php +++ b/apps/settings/lib/Controller/AppSettingsController.php @@ -162,6 +162,7 @@ public function getAppDiscoverMedia(string $fileName): Response { $response = new FileDisplayResponse($file, Http::STATUS_OK, ['Content-Type' => $contentType]); // cache for 7 days $response->cacheFor(604800, false, true); + return $response; } @@ -193,6 +194,7 @@ private function getAppsWithUpdates() { unset($apps[$key]); } } + return $apps; } @@ -206,6 +208,7 @@ private function getBundles() { 'appIdentifiers' => $bundle->getAppIdentifiers() ]; } + return $result; } @@ -222,6 +225,7 @@ private function getAllCategories() { $currentLanguage = substr($this->l10nFactory->findLanguage(), 0, 2); $categories = $this->categoryFetcher->get(); + return array_map(fn ($category) => [ 'id' => $category['id'], 'displayName' => $category['translations'][$currentLanguage]['name'] ?? $category['translations']['en']['name'], @@ -306,6 +310,7 @@ public function listApps(): JSONResponse { if (is_string($appData['groups'])) { $groups = json_decode($appData['groups']); } + $appData['groups'] = $groups; $appData['canUnInstall'] = !$appData['active'] && $appData['removable']; @@ -353,6 +358,7 @@ private function getAppsForCategory($requestedCategory = ''): array { $isInCategory = true; } } + if (!$isInCategory) { continue; } @@ -361,14 +367,17 @@ private function getAppsForCategory($requestedCategory = ''): array { if (!isset($app['releases'][0]['rawPlatformVersionSpec'])) { continue; } + $nextCloudVersion = $versionParser->getVersion($app['releases'][0]['rawPlatformVersionSpec']); $nextCloudVersionDependencies = []; if ($nextCloudVersion->getMinimumVersion() !== '') { $nextCloudVersionDependencies['nextcloud']['@attributes']['min-version'] = $nextCloudVersion->getMinimumVersion(); } + if ($nextCloudVersion->getMaximumVersion() !== '') { $nextCloudVersionDependencies['nextcloud']['@attributes']['max-version'] = $nextCloudVersion->getMaximumVersion(); } + $phpVersion = $versionParser->getVersion($app['releases'][0]['rawPhpVersionSpec']); try { @@ -382,12 +391,15 @@ private function getAppsForCategory($requestedCategory = ''): array { if ($phpVersion->getMinimumVersion() !== '') { $phpDependencies['php']['@attributes']['min-version'] = $phpVersion->getMinimumVersion(); } + if ($phpVersion->getMaximumVersion() !== '') { $phpDependencies['php']['@attributes']['max-version'] = $phpVersion->getMaximumVersion(); } + if (isset($app['releases'][0]['minIntSize'])) { $phpDependencies['php']['@attributes']['min-int-size'] = $app['releases'][0]['minIntSize']; } + $authors = ''; foreach ($app['authors'] as $key => $author) { $authors .= $author['name']; @@ -437,7 +449,7 @@ private function getAppsForCategory($requestedCategory = ''): array { 'missingMaxOwnCloudVersion' => false, 'missingMinOwnCloudVersion' => false, 'canInstall' => true, - 'screenshot' => isset($app['screenshots'][0]['url']) ? 'https://usercontent.apps.nextcloud.com/'.base64_encode($app['screenshots'][0]['url']) : '', + 'screenshot' => isset($app['screenshots'][0]['url']) ? 'https://usercontent.apps.nextcloud.com/' . base64_encode($app['screenshots'][0]['url']) : '', 'score' => $app['ratingOverall'], 'ratingNumOverall' => $app['ratingNumOverall'], 'ratingNumThresholdReached' => $app['ratingNumOverall'] > 5, @@ -496,10 +508,12 @@ public function enableApps(array $appIds, array $groups = []): JSONResponse { } else { $this->appManager->enableApp($appId); } + if (\OC_App::shouldUpgrade($appId)) { $updateRequired = true; } } + return new JSONResponse(['data' => ['update_required' => $updateRequired]]); } catch (\Throwable $e) { $this->logger->error('could not enable apps', ['exception' => $e]); @@ -516,6 +530,7 @@ private function getGroupList(array $groups) { $groupsList[] = $groupManager->get($group); } } + return $groupsList; } @@ -539,6 +554,7 @@ public function disableApps(array $appIds): JSONResponse { $appId = $this->appManager->cleanAppId($appId); $this->appManager->disableApp($appId); } + return new JSONResponse([]); } catch (\Exception $e) { $this->logger->error('could not disable app', ['exception' => $e]); @@ -558,6 +574,7 @@ public function uninstallApp(string $appId): JSONResponse { $this->appManager->clearAppsCache(); return new JSONResponse(['data' => ['appid' => $appId]]); } + return new JSONResponse(['data' => ['message' => $this->l10n->t('Could not remove app.')]], Http::STATUS_INTERNAL_SERVER_ERROR); } @@ -580,6 +597,7 @@ public function updateApp(string $appId): JSONResponse { if ($result !== false) { return new JSONResponse(['data' => ['appid' => $appId]]); } + return new JSONResponse(['data' => ['message' => $this->l10n->t('Could not update app.')]], Http::STATUS_INTERNAL_SERVER_ERROR); } @@ -589,12 +607,14 @@ private function sortApps($a, $b) { if ($a === $b) { return 0; } + return ($a < $b) ? -1 : 1; } public function force(string $appId): JSONResponse { $appId = $this->appManager->cleanAppId($appId); $this->appManager->ignoreNextcloudRequirementForApp($appId); + return new JSONResponse(); } } diff --git a/apps/settings/lib/Controller/AuthSettingsController.php b/apps/settings/lib/Controller/AuthSettingsController.php index 4b126e879d299..c861a298a03fe 100644 --- a/apps/settings/lib/Controller/AuthSettingsController.php +++ b/apps/settings/lib/Controller/AuthSettingsController.php @@ -107,6 +107,7 @@ public function create($name) { } catch (SessionNotAvailableException $ex) { return $this->getServiceNotAvailableResponse(); } + if ($this->userSession->getImpersonatingUserID() !== null) { return $this->getServiceNotAvailableResponse(); } @@ -148,6 +149,7 @@ public function create($name) { private function getServiceNotAvailableResponse() { $resp = new JSONResponse(); $resp->setStatus(Http::STATUS_SERVICE_UNAVAILABLE); + return $resp; } @@ -163,6 +165,7 @@ private function generateRandomDeviceToken() { for ($i = 0; $i < 5; $i++) { $groups[] = $this->random->generate(5, ISecureRandom::CHAR_HUMAN_READABLE); } + return implode('-', $groups); } @@ -193,6 +196,7 @@ public function destroy($id) { $this->tokenProvider->invalidateTokenById($this->uid, $token->getId()); $this->publishActivity(Provider::APP_TOKEN_DELETED, $token->getId(), ['name' => $token->getName()]); + return []; } @@ -233,6 +237,7 @@ public function update($id, array $scope, string $name) { } $this->tokenProvider->updateToken($token); + return []; } @@ -270,10 +275,12 @@ private function findTokenByIdAndUser(int $id): IToken { } catch (ExpiredTokenException $e) { $token = $e->getToken(); } + if ($token->getUID() !== $this->uid) { /** @psalm-suppress DeprecatedClass We have to throw the OC version so both OC and OCP catches catch it */ throw new OcInvalidTokenException('This token does not belong to you!'); } + return $token; } diff --git a/apps/settings/lib/Controller/AuthorizedGroupController.php b/apps/settings/lib/Controller/AuthorizedGroupController.php index f4a018b0555c1..461a657a32ab1 100644 --- a/apps/settings/lib/Controller/AuthorizedGroupController.php +++ b/apps/settings/lib/Controller/AuthorizedGroupController.php @@ -39,6 +39,7 @@ public function saveSettings(array $newGroups, string $class): DataResponse { break; } } + if ($removed) { $this->authorizedGroupService->delete($group->getId()); } @@ -53,6 +54,7 @@ public function saveSettings(array $newGroups, string $class): DataResponse { break; } } + if ($added) { $this->authorizedGroupService->create($groupData['gid'], $class); } diff --git a/apps/settings/lib/Controller/ChangePasswordController.php b/apps/settings/lib/Controller/ChangePasswordController.php index 32bbebb210c0e..637e7d64f579a 100644 --- a/apps/settings/lib/Controller/ChangePasswordController.php +++ b/apps/settings/lib/Controller/ChangePasswordController.php @@ -68,6 +68,7 @@ public function changePersonalPassword(string $oldpassword = '', ?string $newpas ], ]); $response->throttle(); + return $response; } @@ -183,6 +184,7 @@ public function changeUserPassword(?string $username = null, ?string $password = ], ]); } + if (!$result && $recoveryEnabledForUser) { return new JSONResponse([ 'status' => 'error', diff --git a/apps/settings/lib/Controller/CheckSetupController.php b/apps/settings/lib/Controller/CheckSetupController.php index a8db2a0e2977d..16dc1670f2428 100644 --- a/apps/settings/lib/Controller/CheckSetupController.php +++ b/apps/settings/lib/Controller/CheckSetupController.php @@ -127,7 +127,6 @@ public function getFailedIntegrityCheckFiles(): DataDisplayResponse { $formattedTextResponse = 'No errors have been found.'; } - return new DataDisplayResponse( $formattedTextResponse, Http::STATUS_OK, diff --git a/apps/settings/lib/Controller/CommonSettingsTrait.php b/apps/settings/lib/Controller/CommonSettingsTrait.php index dd307b67fab46..0b2918198ebfd 100644 --- a/apps/settings/lib/Controller/CommonSettingsTrait.php +++ b/apps/settings/lib/Controller/CommonSettingsTrait.php @@ -92,6 +92,7 @@ protected function formatSections(array $sections, string $currentSection, strin ]; } } + return $templateParameters; } @@ -118,6 +119,7 @@ private function formatSettings(array $settings): array { $html .= $form->renderAs('')->render(); } } + return ['content' => $html]; } @@ -148,6 +150,7 @@ private function getIndexResponse(string $type, string $section): TemplateRespon /** @psalm-suppress PossiblyUndefinedArrayOffset */ $templateParams['content'] .= join(array_map(fn (string $id) => '
', $ids)); } + Util::addScript(Application::APP_ID, 'declarative-settings-forms'); /** @psalm-suppress PossiblyNullArgument */ $this->initialState->provideInitialState('declarative-settings-forms', $this->declarativeSettingsManager->getFormsWithValues($this->userSession->getUser(), $type, $section)); diff --git a/apps/settings/lib/Controller/DeclarativeSettingsController.php b/apps/settings/lib/Controller/DeclarativeSettingsController.php index eb9d45839de24..5f4f65cd02304 100644 --- a/apps/settings/lib/Controller/DeclarativeSettingsController.php +++ b/apps/settings/lib/Controller/DeclarativeSettingsController.php @@ -61,6 +61,7 @@ public function setValue(string $app, string $formId, string $fieldId, mixed $va try { $this->declarativeManager->loadSchemas(); $this->declarativeManager->setValue($user, $app, $formId, $fieldId, $value); + return new DataResponse(null); } catch (NotAdminException $e) { throw $e; @@ -85,7 +86,9 @@ public function getForms(): DataResponse { if ($user === null) { throw new NotLoggedInException(); } + $this->declarativeManager->loadSchemas(); + return new DataResponse($this->declarativeManager->getFormsWithValues($user, null, null)); } } diff --git a/apps/settings/lib/Controller/HelpController.php b/apps/settings/lib/Controller/HelpController.php index 9fdab414d3011..5b45c93f26ae5 100644 --- a/apps/settings/lib/Controller/HelpController.php +++ b/apps/settings/lib/Controller/HelpController.php @@ -110,6 +110,7 @@ public function help(string $mode = 'user'): TemplateResponse { $policy = new ContentSecurityPolicy(); $policy->addAllowedFrameDomain('\'self\''); $response->setContentSecurityPolicy($policy); + return $response; } } diff --git a/apps/settings/lib/Controller/LogSettingsController.php b/apps/settings/lib/Controller/LogSettingsController.php index aa5ac9b2cc9fd..353c4cad3d6d7 100644 --- a/apps/settings/lib/Controller/LogSettingsController.php +++ b/apps/settings/lib/Controller/LogSettingsController.php @@ -38,11 +38,13 @@ public function download() { if (!$this->log instanceof Log) { throw new \UnexpectedValueException('Log file not available'); } + $resp = new StreamResponse($this->log->getLogPath()); $resp->setHeaders([ 'Content-Type' => 'application/octet-stream', 'Content-Disposition' => 'attachment; filename="nextcloud.log"', ]); + return $resp; } } diff --git a/apps/settings/lib/Controller/MailSettingsController.php b/apps/settings/lib/Controller/MailSettingsController.php index f6b86c7970e31..2e3c168fc2f2b 100644 --- a/apps/settings/lib/Controller/MailSettingsController.php +++ b/apps/settings/lib/Controller/MailSettingsController.php @@ -150,8 +150,10 @@ public function sendTestMail() { $this->config->setAppValue('core', 'emailTestSuccessful', '0'); throw new \RuntimeException($this->l10n->t('Email could not be sent. Check your mail server log')); } + // Store the successful config in the app config $this->config->setAppValue('core', 'emailTestSuccessful', '1'); + return new DataResponse(); } catch (\Exception $e) { $this->config->setAppValue('core', 'emailTestSuccessful', '0'); @@ -160,6 +162,7 @@ public function sendTestMail() { } $this->config->setAppValue('core', 'emailTestSuccessful', '0'); + return new DataResponse($this->l10n->t('You need to set your account email before being able to send test emails. Go to %s for that.', [$this->urlGenerator->linkToRouteAbsolute('settings.PersonalSettings.index')]), Http::STATUS_BAD_REQUEST); } } diff --git a/apps/settings/lib/Controller/PersonalSettingsController.php b/apps/settings/lib/Controller/PersonalSettingsController.php index 1a31a20eb0407..7e7be0b21e25d 100644 --- a/apps/settings/lib/Controller/PersonalSettingsController.php +++ b/apps/settings/lib/Controller/PersonalSettingsController.php @@ -60,6 +60,7 @@ public function index(string $section): TemplateResponse { protected function getSettings($section) { $settings = $this->settingsManager->getPersonalSettings($section); $formatted = $this->formatSettings($settings); + return $formatted; } } diff --git a/apps/settings/lib/Controller/UsersController.php b/apps/settings/lib/Controller/UsersController.php index eb401f855360e..fe71702112d88 100644 --- a/apps/settings/lib/Controller/UsersController.php +++ b/apps/settings/lib/Controller/UsersController.php @@ -287,6 +287,7 @@ protected function canAdminChangeUserPasswords(): bool { $noUserSpecificEncryptionKeys = true; $isEncryptionModuleLoaded = false; } + $canChangePassword = ($isEncryptionModuleLoaded && $noUserSpecificEncryptionKeys) || (!$isEncryptionModuleLoaded && !$isEncryptionEnabled); @@ -336,7 +337,7 @@ public function setUserSettings(?string $avatarScope = null, ?string $birthdate = null, ?string $birthdateScope = null, ?string $pronouns = null, - ?string $pronounsScope = null + ?string $pronounsScope = null, ) { $user = $this->userSession->getUser(); if (!$user instanceof IUser) { @@ -385,10 +386,12 @@ public function setUserSettings(?string $avatarScope = null, && in_array($property, [IAccountManager::PROPERTY_DISPLAYNAME, IAccountManager::PROPERTY_EMAIL], true)) { continue; } + $property = $userAccount->getProperty($property); if ($data['value'] !== null) { $property->setValue($data['value']); } + if ($data['scope'] !== null) { $property->setScope($data['scope']); } @@ -399,6 +402,7 @@ public function setUserSettings(?string $avatarScope = null, if ($oldPhoneValue !== $userAccount->getProperty(IAccountManager::PROPERTY_PHONE)->getValue()) { $this->knownUserService->deleteByContactUserId($user->getUID()); } + return new DataResponse( [ 'status' => 'success', @@ -462,6 +466,7 @@ protected function saveUserSettings(IAccount $userAccount): void { if (!$userAccount->getUser()->canChangeDisplayName()) { throw new ForbiddenException($this->l10n->t('Unable to change email address')); } + $userAccount->getUser()->setSystemEMailAddress($userAccount->getProperty(IAccountManager::PROPERTY_EMAIL)->getValue()); } @@ -471,9 +476,11 @@ protected function saveUserSettings(IAccount $userAccount): void { if ($e->getMessage() === IAccountManager::PROPERTY_PHONE) { throw new InvalidArgumentException($this->l10n->t('Unable to set invalid phone number')); } + if ($e->getMessage() === IAccountManager::PROPERTY_WEBSITE) { throw new InvalidArgumentException($this->l10n->t('Unable to set invalid website')); } + throw new InvalidArgumentException($this->l10n->t('Some account data was invalid')); } } @@ -561,6 +568,7 @@ protected function getCurrentTime(): int { protected function signMessage(IUser $user, string $message): string { $privateKey = $this->keyManager->getKey($user)->getPrivate(); openssl_sign(json_encode($message), $signature, $privateKey, OPENSSL_ALGO_SHA512); + return base64_encode($signature); } } diff --git a/apps/settings/lib/Hooks.php b/apps/settings/lib/Hooks.php index eb77b676914ee..76c5099743970 100644 --- a/apps/settings/lib/Hooks.php +++ b/apps/settings/lib/Hooks.php @@ -161,8 +161,10 @@ public function onChangeEmail(IUser $user, $oldMailAddress) { if ($this->config->getAppValue('settings', 'disable_activity.email_address_changed_by_admin', 'no') === 'yes') { return; } + $subject = Provider::EMAIL_CHANGED; } + $text = $l->t('Your email address on %s was changed.', [$instanceUrl]); $event->setAuthor($actor->getUID()) ->setSubject($subject); @@ -171,9 +173,11 @@ public function onChangeEmail(IUser $user, $oldMailAddress) { if ($this->config->getAppValue('settings', 'disable_activity.email_address_changed_by_admin', 'no') === 'yes') { return; } + $text = $l->t('Your email address on %s was changed by an administrator.', [$instanceUrl]); $event->setSubject(Provider::EMAIL_CHANGED); } + $this->activityManager->publish($event); @@ -192,6 +196,7 @@ public function onChangeEmail(IUser $user, $oldMailAddress) { if ($user->getEMailAddress()) { $template->addBodyText($l->t('The new email address is %s', [$user->getEMailAddress()])); } + $template->addFooter(); diff --git a/apps/settings/lib/Listener/UserAddedToGroupActivityListener.php b/apps/settings/lib/Listener/UserAddedToGroupActivityListener.php index 7369f54bf371f..36feeaa1035a4 100644 --- a/apps/settings/lib/Listener/UserAddedToGroupActivityListener.php +++ b/apps/settings/lib/Listener/UserAddedToGroupActivityListener.php @@ -32,7 +32,7 @@ class UserAddedToGroupActivityListener implements IEventListener { public function __construct( Manager $groupManager, IManager $activityManager, - IUserSession $userSession + IUserSession $userSession, ) { $this->groupManager = $groupManager; $this->activityManager = $activityManager; diff --git a/apps/settings/lib/Listener/UserRemovedFromGroupActivityListener.php b/apps/settings/lib/Listener/UserRemovedFromGroupActivityListener.php index 711f78212bc1e..82302f748eccb 100644 --- a/apps/settings/lib/Listener/UserRemovedFromGroupActivityListener.php +++ b/apps/settings/lib/Listener/UserRemovedFromGroupActivityListener.php @@ -32,7 +32,7 @@ class UserRemovedFromGroupActivityListener implements IEventListener { public function __construct( Manager $groupManager, IManager $activityManager, - IUserSession $userSession + IUserSession $userSession, ) { $this->groupManager = $groupManager; $this->activityManager = $activityManager; diff --git a/apps/settings/lib/Mailer/NewUserMailHelper.php b/apps/settings/lib/Mailer/NewUserMailHelper.php index 737da8d7b9a69..d8132174a1e15 100644 --- a/apps/settings/lib/Mailer/NewUserMailHelper.php +++ b/apps/settings/lib/Mailer/NewUserMailHelper.php @@ -91,6 +91,7 @@ public function generateTemplate(IUser $user, $generatePasswordResetToken = fals } else { $link = $this->urlGenerator->getAbsoluteURL('/'); } + $displayName = $user->getDisplayName(); $emailTemplate = $this->mailer->createEMailTemplate('settings.Welcome', [ @@ -108,10 +109,12 @@ public function generateTemplate(IUser $user, $generatePasswordResetToken = fals } else { $emailTemplate->addHeading($l10n->t('Welcome aboard %s', [$displayName])); } + $emailTemplate->addBodyText($l10n->t('Welcome to your %s account, you can add, protect, and share your data.', [$this->themingDefaults->getName()])); if ($user->getBackendClassName() !== 'LDAP') { $emailTemplate->addBodyText($l10n->t('Your Login is: %s', [$userId])); } + if ($generatePasswordResetToken) { $leftButtonText = $l10n->t('Set your password'); } else { diff --git a/apps/settings/lib/Middleware/SubadminMiddleware.php b/apps/settings/lib/Middleware/SubadminMiddleware.php index f985cdf278cc7..97700a3e0486c 100644 --- a/apps/settings/lib/Middleware/SubadminMiddleware.php +++ b/apps/settings/lib/Middleware/SubadminMiddleware.php @@ -65,6 +65,7 @@ public function afterException($controller, $methodName, \Exception $exception) if ($exception instanceof NotAdminException) { $response = new TemplateResponse('core', '403', [], 'guest'); $response->setStatus(Http::STATUS_FORBIDDEN); + return $response; } diff --git a/apps/settings/lib/Search/SectionSearch.php b/apps/settings/lib/Search/SectionSearch.php index 321534581b53e..de124de1e43c4 100644 --- a/apps/settings/lib/Search/SectionSearch.php +++ b/apps/settings/lib/Search/SectionSearch.php @@ -64,6 +64,7 @@ public function getOrder(string $route, array $routeParameters): int { if ($route === 'settings.PersonalSettings.index' || $route === 'settings.AdminSettings.index') { return -1; } + // At the very bottom return 500; } diff --git a/apps/settings/lib/Service/AuthorizedGroupService.php b/apps/settings/lib/Service/AuthorizedGroupService.php index 31ffc765f32e1..1057c943d95b2 100644 --- a/apps/settings/lib/Service/AuthorizedGroupService.php +++ b/apps/settings/lib/Service/AuthorizedGroupService.php @@ -64,6 +64,7 @@ public function create(string $groupId, string $class): AuthorizedGroup { $authorizedGroup = new AuthorizedGroup(); $authorizedGroup->setGroupId($groupId); $authorizedGroup->setClass($class); + return $this->mapper->insert($authorizedGroup); } diff --git a/apps/settings/lib/Settings/Admin/ArtificialIntelligence.php b/apps/settings/lib/Settings/Admin/ArtificialIntelligence.php index 4092acecab8e3..4b03b44c5fcff 100644 --- a/apps/settings/lib/Settings/Admin/ArtificialIntelligence.php +++ b/apps/settings/lib/Settings/Admin/ArtificialIntelligence.php @@ -74,6 +74,7 @@ public function getForm() { $textProcessingSettings[$provider->getTaskType()] = $provider instanceof IProviderWithId ? $provider->getId() : $provider::class; } } + $textProcessingTaskTypes = []; foreach ($textProcessingSettings as $taskTypeClass => $providerClass) { /** @var ITaskType $taskType */ @@ -84,6 +85,7 @@ public function getForm() { } catch (ContainerExceptionInterface $e) { continue; } + $textProcessingTaskTypes[] = [ 'class' => $taskTypeClass, 'name' => $taskType->getName(), @@ -112,6 +114,7 @@ public function getForm() { $taskProcessingSettings[$provider->getTaskTypeId()] = $provider->getId(); } } + $taskProcessingTaskTypes = []; foreach ($this->taskProcessingManager->getAvailableTaskTypes() as $taskTypeId => $taskTypeDefinition) { $taskProcessingTaskTypes[] = [ @@ -153,12 +156,14 @@ public function getForm() { if (!is_array($defaultValue)) { break; } + $value = array_values(array_unique(array_merge(array_intersect($value, $defaultValue), $defaultValue), SORT_STRING)); break; default: break; } } + $settings[$key] = $value; } diff --git a/apps/settings/lib/Settings/Admin/Delegation.php b/apps/settings/lib/Settings/Admin/Delegation.php index e38507b4fc148..4b8ef4fe121d2 100644 --- a/apps/settings/lib/Settings/Admin/Delegation.php +++ b/apps/settings/lib/Settings/Admin/Delegation.php @@ -28,7 +28,7 @@ public function __construct( IInitialState $initialStateService, IGroupManager $groupManager, AuthorizedGroupService $authorizedGroupService, - IURLGenerator $urlGenerator + IURLGenerator $urlGenerator, ) { $this->settingManager = $settingManager; $this->initialStateService = $initialStateService; @@ -51,6 +51,7 @@ private function getDelegatedSettings(array $settings, array $innerSection): arr $settings[] = $setting; } } + return $settings; } @@ -76,10 +77,12 @@ private function initSettingState(): void { ); } } + usort($settings, function (array $a, array $b) { if ($a['priority'] == $b['priority']) { return 0; } + return ($a['priority'] < $b['priority']) ? -1 : 1; }); $this->initialStateService->provideInitialState('available-settings', $settings); @@ -93,11 +96,13 @@ public function initAvailableGroupState(): void { if ($group->getGID() === 'admin') { continue; // Admin already have access to everything } + $groups[] = [ 'displayName' => $group->getDisplayName(), 'gid' => $group->getGID(), ]; } + $this->initialStateService->provideInitialState('available-groups', $groups); } diff --git a/apps/settings/lib/Settings/Admin/Server.php b/apps/settings/lib/Settings/Admin/Server.php index 0f253ddf6b1f3..4d75e9184f709 100644 --- a/apps/settings/lib/Settings/Admin/Server.php +++ b/apps/settings/lib/Settings/Admin/Server.php @@ -71,6 +71,7 @@ protected function cronMaxAge(): int { } else { $maxAge = $this->timeFactory->getTime(); } + $result->closeCursor(); return $maxAge; diff --git a/apps/settings/lib/Settings/Admin/Sharing.php b/apps/settings/lib/Settings/Admin/Sharing.php index 34c91f3bce97b..eb5acaa77141f 100644 --- a/apps/settings/lib/Settings/Admin/Sharing.php +++ b/apps/settings/lib/Settings/Admin/Sharing.php @@ -78,6 +78,7 @@ public function getForm() { $this->initialState->provideInitialState('sharingSettings', $parameters); \OCP\Util::addScript($this->appName, 'vue-settings-admin-sharing'); + return new TemplateResponse($this->appName, 'settings/admin/sharing', [], ''); } diff --git a/apps/settings/lib/Settings/Personal/PersonalInfo.php b/apps/settings/lib/Settings/Personal/PersonalInfo.php index 232fea8bd73ef..62691d271a2e3 100644 --- a/apps/settings/lib/Settings/Personal/PersonalInfo.php +++ b/apps/settings/lib/Settings/Personal/PersonalInfo.php @@ -71,7 +71,7 @@ public function __construct( IFactory $l10nFactory, IL10N $l, IInitialState $initialStateService, - IManager $manager + IManager $manager, ) { $this->config = $config; $this->userManager = $userManager; @@ -280,6 +280,7 @@ private function getLanguageMap(IUser $user): array { $userLangIndex = array_search($userConfLang, array_column($languages['otherLanguages'], 'code')); $userLang = $languages['otherLanguages'][$userLangIndex]; } + // if user language is not available but set somehow: show the actual code as name if (!is_array($userLang)) { $userLang = [ @@ -345,8 +346,10 @@ private function getMessageParameters(IAccount $account): array { default: $message = $this->l->t('Verify'); } + $messageParameters[$property . 'Message'] = $message; } + return $messageParameters; } } diff --git a/apps/settings/lib/Settings/Personal/Security/Authtokens.php b/apps/settings/lib/Settings/Personal/Security/Authtokens.php index 90f6e23cbb776..ece280726a2a1 100644 --- a/apps/settings/lib/Settings/Personal/Security/Authtokens.php +++ b/apps/settings/lib/Settings/Personal/Security/Authtokens.php @@ -79,6 +79,7 @@ private function getAppTokens(): array { } catch (SessionNotAvailableException $ex) { return []; } + try { $sessionToken = $this->tokenProvider->getToken($sessionId); } catch (InvalidTokenException $ex) { @@ -94,6 +95,7 @@ private function getAppTokens(): array { $data['canRename'] = false; $data['current'] = true; } + return $data; }, $tokens); } diff --git a/apps/settings/lib/Settings/Personal/Security/TwoFactor.php b/apps/settings/lib/Settings/Personal/Security/TwoFactor.php index 63b647f7c44d0..24b8353a76099 100644 --- a/apps/settings/lib/Settings/Personal/Security/TwoFactor.php +++ b/apps/settings/lib/Settings/Personal/Security/TwoFactor.php @@ -61,6 +61,7 @@ public function getSection(): ?string { if (!$this->shouldShow()) { return null; } + return 'security'; } @@ -88,12 +89,14 @@ private function shouldShow(): bool { // Let's hope for the best return true; } + foreach ($providers as $provider) { if ($provider instanceof IProvidesPersonalSettings && !($provider instanceof BackupCodesProvider)) { return true; } } + return false; } diff --git a/apps/settings/lib/SetupChecks/CheckUserCertificates.php b/apps/settings/lib/SetupChecks/CheckUserCertificates.php index d1e3551c0857a..10f8b1f39d41d 100644 --- a/apps/settings/lib/SetupChecks/CheckUserCertificates.php +++ b/apps/settings/lib/SetupChecks/CheckUserCertificates.php @@ -36,9 +36,11 @@ public function run(): SetupResult { if ($this->configValue === '') { return SetupResult::success(); } + if ($this->configValue === 'not-run-yet') { return SetupResult::info($this->l10n->t('A background job is pending that checks for administration imported SSL certificates. Please check back later.')); } + return SetupResult::error($this->l10n->t('There are some administration imported SSL certificates present, that are not used anymore with Nextcloud 21. They can be imported on the command line via "occ security:certificates:import" command. Their paths inside the data directory are shown below.')); } } diff --git a/apps/settings/lib/SetupChecks/CronErrors.php b/apps/settings/lib/SetupChecks/CronErrors.php index 9a5c5b8af7158..dc625b0447707 100644 --- a/apps/settings/lib/SetupChecks/CronErrors.php +++ b/apps/settings/lib/SetupChecks/CronErrors.php @@ -35,7 +35,7 @@ public function run(): SetupResult { return SetupResult::error( $this->l10n->t( "It was not possible to execute the cron job via CLI. The following technical errors have appeared:\n%s", - implode("\n", array_map(fn (array $error) => '- '.$error['error'].' '.$error['hint'], $errors)) + implode("\n", array_map(fn (array $error) => '- ' . $error['error'] . ' ' . $error['hint'], $errors)) ) ); } else { diff --git a/apps/settings/lib/SetupChecks/DataDirectoryProtected.php b/apps/settings/lib/SetupChecks/DataDirectoryProtected.php index 4280457ced03a..a501743099c76 100644 --- a/apps/settings/lib/SetupChecks/DataDirectoryProtected.php +++ b/apps/settings/lib/SetupChecks/DataDirectoryProtected.php @@ -65,6 +65,7 @@ public function run(): SetupResult { if ($noResponse) { return SetupResult::warning($this->l10n->t('Could not check that the data directory is protected. Please check manually that your server does not allow access to the data directory.') . "\n" . $this->serverConfigHelp()); } + return SetupResult::success(); } diff --git a/apps/settings/lib/SetupChecks/DatabaseHasMissingColumns.php b/apps/settings/lib/SetupChecks/DatabaseHasMissingColumns.php index b004c5ada3590..d8ebe6870b783 100644 --- a/apps/settings/lib/SetupChecks/DatabaseHasMissingColumns.php +++ b/apps/settings/lib/SetupChecks/DatabaseHasMissingColumns.php @@ -62,10 +62,11 @@ public function run(): SetupResult { } else { $list = ''; foreach ($missingColumns as $missingColumn) { - $list .= "\n".$this->l10n->t('Missing optional column "%s" in table "%s".', [$missingColumn['columnName'], $missingColumn['tableName']]); + $list .= "\n" . $this->l10n->t('Missing optional column "%s" in table "%s".', [$missingColumn['columnName'], $missingColumn['tableName']]); } + return SetupResult::warning( - $this->l10n->t('The database is missing some optional columns. Due to the fact that adding columns on big tables could take some time they were not added automatically when they can be optional. By running "occ db:add-missing-columns" those missing columns could be added manually while the instance keeps running. Once the columns are added some features might improve responsiveness or usability.').$list + $this->l10n->t('The database is missing some optional columns. Due to the fact that adding columns on big tables could take some time they were not added automatically when they can be optional. By running "occ db:add-missing-columns" those missing columns could be added manually while the instance keeps running. Once the columns are added some features might improve responsiveness or usability.') . $list ); } } diff --git a/apps/settings/lib/SetupChecks/DatabaseHasMissingIndices.php b/apps/settings/lib/SetupChecks/DatabaseHasMissingIndices.php index 9854a039dd139..3732809cb7579 100644 --- a/apps/settings/lib/SetupChecks/DatabaseHasMissingIndices.php +++ b/apps/settings/lib/SetupChecks/DatabaseHasMissingIndices.php @@ -84,6 +84,7 @@ public function run(): SetupResult { $list .= ', '; } } + return SetupResult::warning( $this->l10n->t('Detected some missing optional indices. Occasionally new indices are added (by Nextcloud or installed applications) to improve database performance. Adding indices can sometimes take awhile and temporarily hurt performance so this is not done automatically during upgrades. Once the indices are added, queries to those tables should be faster. Use the command `occ db:add-missing-indices` to add them. ') . $list . '.', $this->urlGenerator->linkToDocs('admin-long-running-migration-steps') diff --git a/apps/settings/lib/SetupChecks/DatabaseHasMissingPrimaryKeys.php b/apps/settings/lib/SetupChecks/DatabaseHasMissingPrimaryKeys.php index 8e2a0d18e4181..9c64ccfff195b 100644 --- a/apps/settings/lib/SetupChecks/DatabaseHasMissingPrimaryKeys.php +++ b/apps/settings/lib/SetupChecks/DatabaseHasMissingPrimaryKeys.php @@ -62,10 +62,11 @@ public function run(): SetupResult { } else { $list = ''; foreach ($missingPrimaryKeys as $missingPrimaryKey) { - $list .= "\n".$this->l10n->t('Missing primary key on table "%s".', [$missingPrimaryKey['tableName']]); + $list .= "\n" . $this->l10n->t('Missing primary key on table "%s".', [$missingPrimaryKey['tableName']]); } + return SetupResult::warning( - $this->l10n->t('The database is missing some primary keys. Due to the fact that adding primary keys on big tables could take some time they were not added automatically. By running "occ db:add-missing-primary-keys" those missing primary keys could be added manually while the instance keeps running.').$list + $this->l10n->t('The database is missing some primary keys. Due to the fact that adding primary keys on big tables could take some time they were not added automatically. By running "occ db:add-missing-primary-keys" those missing primary keys could be added manually while the instance keeps running.') . $list ); } } diff --git a/apps/settings/lib/SetupChecks/DatabasePendingBigIntConversions.php b/apps/settings/lib/SetupChecks/DatabasePendingBigIntConversions.php index dc9f00e844883..97c08b72d0a65 100644 --- a/apps/settings/lib/SetupChecks/DatabasePendingBigIntConversions.php +++ b/apps/settings/lib/SetupChecks/DatabasePendingBigIntConversions.php @@ -72,9 +72,11 @@ public function run(): SetupResult { foreach ($pendingColumns as $pendingColumn) { $list .= "\n$pendingColumn"; } + $list .= "\n"; + return SetupResult::info( - $this->l10n->t('Some columns in the database are missing a conversion to big int. Due to the fact that changing column types on big tables could take some time they were not changed automatically. By running "occ db:convert-filecache-bigint" those pending changes could be applied manually. This operation needs to be made while the instance is offline.').$list, + $this->l10n->t('Some columns in the database are missing a conversion to big int. Due to the fact that changing column types on big tables could take some time they were not changed automatically. By running "occ db:convert-filecache-bigint" those pending changes could be applied manually. This operation needs to be made while the instance is offline.') . $list, $this->urlGenerator->linkToDocs('admin-bigint-conversion') ); } diff --git a/apps/settings/lib/SetupChecks/HttpsUrlGeneration.php b/apps/settings/lib/SetupChecks/HttpsUrlGeneration.php index 7f76297748bad..14af42aba84c4 100644 --- a/apps/settings/lib/SetupChecks/HttpsUrlGeneration.php +++ b/apps/settings/lib/SetupChecks/HttpsUrlGeneration.php @@ -45,6 +45,7 @@ public function run(): SetupResult { ); } } + $generatedUrl = $this->urlGenerator->getAbsoluteURL('index.php'); if (!str_starts_with($generatedUrl, 'https://')) { if (!\OC::$CLI) { @@ -60,6 +61,7 @@ public function run(): SetupResult { ); } } + return SetupResult::success($this->l10n->t('You are accessing your instance over a secure connection, and your instance is generating secure URLs.')); } } diff --git a/apps/settings/lib/SetupChecks/InternetConnectivity.php b/apps/settings/lib/SetupChecks/InternetConnectivity.php index 3a0af06e71b2e..76400cbf126aa 100644 --- a/apps/settings/lib/SetupChecks/InternetConnectivity.php +++ b/apps/settings/lib/SetupChecks/InternetConnectivity.php @@ -49,6 +49,7 @@ public function run(): SetupResult { return SetupResult::success(); } } + return SetupResult::warning($this->l10n->t('This server has no working internet connection: Multiple endpoints could not be reached. This means that some of the features like mounting external storage, notifications about updates or installation of third-party apps will not work. Accessing files remotely and sending of notification emails might not work, either. Establish a connection from this server to the internet to enjoy all features.')); } @@ -73,8 +74,10 @@ private function isSiteReachable(string $site): bool { 'app' => 'internet_connection_check', 'exception' => $e, ]); + return false; } + return true; } } diff --git a/apps/settings/lib/SetupChecks/JavaScriptModules.php b/apps/settings/lib/SetupChecks/JavaScriptModules.php index e09dc459dc885..7983cecf4a8d4 100644 --- a/apps/settings/lib/SetupChecks/JavaScriptModules.php +++ b/apps/settings/lib/SetupChecks/JavaScriptModules.php @@ -54,6 +54,7 @@ public function run(): SetupResult { if ($noResponse) { return SetupResult::warning($this->l10n->t('Unable to run check for JavaScript support. Please remedy or confirm manually if your webserver serves `.mjs` files using the JavaScript MIME type.') . "\n" . $this->serverConfigHelp()); } + return SetupResult::error($this->l10n->t('Your webserver does not serve `.mjs` files using the JavaScript MIME type. This will break some apps by preventing browsers from executing the JavaScript files. You should configure your webserver to serve `.mjs` files with either the `text/javascript` or `application/javascript` MIME type.')); } diff --git a/apps/settings/lib/SetupChecks/LegacySSEKeyFormat.php b/apps/settings/lib/SetupChecks/LegacySSEKeyFormat.php index 47594e201cb1b..c9b59994e3e37 100644 --- a/apps/settings/lib/SetupChecks/LegacySSEKeyFormat.php +++ b/apps/settings/lib/SetupChecks/LegacySSEKeyFormat.php @@ -34,6 +34,7 @@ public function run(): SetupResult { if ($this->config->getSystemValueBool('encryption.legacy_format_support', false) === false) { return SetupResult::success($this->l10n->t('Disabled')); } + return SetupResult::warning($this->l10n->t('The old server-side-encryption format is enabled. We recommend disabling this.'), $this->urlGenerator->linkToDocs('admin-sse-legacy-format')); } } diff --git a/apps/settings/lib/SetupChecks/MaintenanceWindowStart.php b/apps/settings/lib/SetupChecks/MaintenanceWindowStart.php index ca8df039b1ed2..48c4b7a0ed2c2 100644 --- a/apps/settings/lib/SetupChecks/MaintenanceWindowStart.php +++ b/apps/settings/lib/SetupChecks/MaintenanceWindowStart.php @@ -42,6 +42,7 @@ public function run(): SetupResult { $startValue = (int)$configValue; $endValue = ($startValue + 6) % 24; + return SetupResult::success( str_replace( ['{start}', '{end}'], diff --git a/apps/settings/lib/SetupChecks/MemcacheConfigured.php b/apps/settings/lib/SetupChecks/MemcacheConfigured.php index 03cdc91cb5f46..798d55b8956da 100644 --- a/apps/settings/lib/SetupChecks/MemcacheConfigured.php +++ b/apps/settings/lib/SetupChecks/MemcacheConfigured.php @@ -42,6 +42,7 @@ public function run(): SetupResult { $this->l10n->t('Memcached is configured as distributed cache, but the wrong PHP module ("memcache") is installed. Please install the PHP module "memcached".') ); } + // required PHP module is missing if (!extension_loaded('memcached')) { return SetupResult::warning( @@ -49,12 +50,14 @@ public function run(): SetupResult { ); } } + if ($memcacheLocalClass === null) { return SetupResult::info( $this->l10n->t('No memory cache has been configured. To enhance performance, please configure a memcache, if available.'), $this->urlGenerator->linkToDocs('admin-performance') ); } + return SetupResult::success($this->l10n->t('Configured')); } } diff --git a/apps/settings/lib/SetupChecks/MysqlUnicodeSupport.php b/apps/settings/lib/SetupChecks/MysqlUnicodeSupport.php index ba2fa93e09498..04a1c4468cec4 100644 --- a/apps/settings/lib/SetupChecks/MysqlUnicodeSupport.php +++ b/apps/settings/lib/SetupChecks/MysqlUnicodeSupport.php @@ -34,6 +34,7 @@ public function run(): SetupResult { if ($this->config->getSystemValueString('dbtype') !== 'mysql') { return SetupResult::success($this->l10n->t('You are not using MySQL')); } + if ($this->config->getSystemValueBool('mysql.utf8mb4', false)) { return SetupResult::success($this->l10n->t('MySQL is used as database and does support 4-byte characters')); } else { diff --git a/apps/settings/lib/SetupChecks/OcxProviders.php b/apps/settings/lib/SetupChecks/OcxProviders.php index 191341b0ee431..c53e8087bd959 100644 --- a/apps/settings/lib/SetupChecks/OcxProviders.php +++ b/apps/settings/lib/SetupChecks/OcxProviders.php @@ -76,7 +76,7 @@ public function run(): SetupResult { $this->l10n->t('Your web server is not properly set up to resolve %1$s. This is most likely related to a web server configuration that was not updated to deliver this folder directly. Please compare your configuration against the shipped rewrite rules in ".htaccess" for Apache or the provided one in the documentation for Nginx. -On Nginx those are typically the lines starting with "location ~" that need an update.', [join(', ', array_map(fn ($s) => '"'.$s.'"', $missingProviders))]), +On Nginx those are typically the lines starting with "location ~" that need an update.', [join(', ', array_map(fn ($s) => '"' . $s . '"', $missingProviders))]), $this->urlGenerator->linkToDocs('admin-nginx'), ); } diff --git a/apps/settings/lib/SetupChecks/PhpModules.php b/apps/settings/lib/SetupChecks/PhpModules.php index 6d4246ffdaec8..59e2d78dec6fe 100644 --- a/apps/settings/lib/SetupChecks/PhpModules.php +++ b/apps/settings/lib/SetupChecks/PhpModules.php @@ -77,10 +77,11 @@ public function run(): SetupResult { $moduleList = implode( "\n", array_map( - fn (string $module) => '- '.$module.' '.$this->getRecommendedModuleDescription($module), + fn (string $module) => '- ' . $module . ' ' . $this->getRecommendedModuleDescription($module), $missingRecommendedModules ) ); + return SetupResult::info( $this->l10n->t("This instance is missing some recommended PHP modules. For improved performance and better compatibility it is highly recommended to install them:\n%s", $moduleList), $this->urlGenerator->linkToDocs('admin-php-modules') diff --git a/apps/settings/lib/SetupChecks/PhpOutdated.php b/apps/settings/lib/SetupChecks/PhpOutdated.php index 4c7ed5096c068..7fb6c738f01bb 100644 --- a/apps/settings/lib/SetupChecks/PhpOutdated.php +++ b/apps/settings/lib/SetupChecks/PhpOutdated.php @@ -31,6 +31,7 @@ public function run(): SetupResult { if (PHP_VERSION_ID < 80200) { return SetupResult::warning($this->l10n->t('You are currently running PHP %s. PHP 8.1 is now deprecated in Nextcloud 30. Nextcloud 31 may require at least PHP 8.2. Please upgrade to one of the officially supported PHP versions provided by the PHP Group as soon as possible.', [PHP_VERSION]), 'https://secure.php.net/supported-versions.php'); } + return SetupResult::success($this->l10n->t('You are currently running PHP %s.', [PHP_VERSION])); } } diff --git a/apps/settings/lib/SetupChecks/PushService.php b/apps/settings/lib/SetupChecks/PushService.php index 1f03404d80ed3..88ef1f61a29f5 100644 --- a/apps/settings/lib/SetupChecks/PushService.php +++ b/apps/settings/lib/SetupChecks/PushService.php @@ -43,6 +43,7 @@ private function isFairUseOfFreePushService(): bool { // Notifications app is showing a message already return true; } + return $this->notificationsManager->isFairUseOfFreePushService(); } diff --git a/apps/settings/lib/SetupChecks/RandomnessSecure.php b/apps/settings/lib/SetupChecks/RandomnessSecure.php index 045ddde0b9d9d..682a623601c5a 100644 --- a/apps/settings/lib/SetupChecks/RandomnessSecure.php +++ b/apps/settings/lib/SetupChecks/RandomnessSecure.php @@ -41,6 +41,7 @@ public function run(): SetupResult { $this->urlGenerator->linkToDocs('admin-security') ); } + return SetupResult::success($this->l10n->t('Secure')); } } diff --git a/apps/settings/lib/SetupChecks/SchedulingTableSize.php b/apps/settings/lib/SetupChecks/SchedulingTableSize.php index b23972ca7dc64..4f49aeb33563a 100644 --- a/apps/settings/lib/SetupChecks/SchedulingTableSize.php +++ b/apps/settings/lib/SetupChecks/SchedulingTableSize.php @@ -45,6 +45,7 @@ public function run(): SetupResult { ]) ); } + return SetupResult::success( $this->l10n->t('Scheduling objects table size is within acceptable range.') ); diff --git a/apps/settings/lib/SetupChecks/SecurityHeaders.php b/apps/settings/lib/SetupChecks/SecurityHeaders.php index b85ab9b401804..0c217a38e5068 100644 --- a/apps/settings/lib/SetupChecks/SecurityHeaders.php +++ b/apps/settings/lib/SetupChecks/SecurityHeaders.php @@ -58,6 +58,7 @@ public function run(): SetupResult { $works = false; continue; } + $msg = ''; $msgParameters = []; foreach ($securityHeaders as $header => [$expected, $accepted]) { @@ -65,16 +66,16 @@ public function run(): SetupResult { $value = preg_replace('/,\s+/', ',', strtolower($response->getHeader($header))); if ($value !== $expected) { if ($accepted !== null && $value === $accepted) { - $msg .= $this->l10n->t('- The `%1$s` HTTP header is not set to `%2$s`. Some features might not work correctly, as it is recommended to adjust this setting accordingly.', [$header, $expected])."\n"; + $msg .= $this->l10n->t('- The `%1$s` HTTP header is not set to `%2$s`. Some features might not work correctly, as it is recommended to adjust this setting accordingly.', [$header, $expected]) . "\n"; } else { - $msg .= $this->l10n->t('- The `%1$s` HTTP header is not set to `%2$s`. This is a potential security or privacy risk, as it is recommended to adjust this setting accordingly.', [$header, $expected])."\n"; + $msg .= $this->l10n->t('- The `%1$s` HTTP header is not set to `%2$s`. This is a potential security or privacy risk, as it is recommended to adjust this setting accordingly.', [$header, $expected]) . "\n"; } } } $xssFields = array_map('trim', explode(';', $response->getHeader('X-XSS-Protection'))); if (!in_array('1', $xssFields) || !in_array('mode=block', $xssFields)) { - $msg .= $this->l10n->t('- The `%1$s` HTTP header does not contain `%2$s`. This is a potential security or privacy risk, as it is recommended to adjust this setting accordingly.', ['X-XSS-Protection', '1; mode=block'])."\n"; + $msg .= $this->l10n->t('- The `%1$s` HTTP header does not contain `%2$s`. This is a potential security or privacy risk, as it is recommended to adjust this setting accordingly.', ['X-XSS-Protection', '1; mode=block']) . "\n"; } $referrerPolicy = $response->getHeader('Referrer-Policy'); @@ -89,7 +90,7 @@ public function run(): SetupResult { 'strict-origin-when-cross-origin', 'same-origin', ] - )."\n"; + ) . "\n"; $msgParameters['w3c-recommendation'] = [ 'type' => 'highlight', 'id' => 'w3c-recommendation', @@ -103,25 +104,27 @@ public function run(): SetupResult { if (preg_match('/^max-age=(\d+)(;.*)?$/', $transportSecurityValidity, $m)) { $transportSecurityValidity = (int)$m[1]; if ($transportSecurityValidity < $minimumSeconds) { - $msg .= $this->l10n->t('- The `Strict-Transport-Security` HTTP header is not set to at least `%d` seconds (current value: `%d`). For enhanced security, it is recommended to use a long HSTS policy.', [$minimumSeconds, $transportSecurityValidity])."\n"; + $msg .= $this->l10n->t('- The `Strict-Transport-Security` HTTP header is not set to at least `%d` seconds (current value: `%d`). For enhanced security, it is recommended to use a long HSTS policy.', [$minimumSeconds, $transportSecurityValidity]) . "\n"; } } elseif (!empty($transportSecurityValidity)) { - $msg .= $this->l10n->t('- The `Strict-Transport-Security` HTTP header is malformed: `%s`. For enhanced security, it is recommended to enable HSTS.', [$transportSecurityValidity])."\n"; + $msg .= $this->l10n->t('- The `Strict-Transport-Security` HTTP header is malformed: `%s`. For enhanced security, it is recommended to enable HSTS.', [$transportSecurityValidity]) . "\n"; } else { - $msg .= $this->l10n->t('- The `Strict-Transport-Security` HTTP header is not set (should be at least `%d` seconds). For enhanced security, it is recommended to enable HSTS.', [$minimumSeconds])."\n"; + $msg .= $this->l10n->t('- The `Strict-Transport-Security` HTTP header is not set (should be at least `%d` seconds). For enhanced security, it is recommended to enable HSTS.', [$minimumSeconds]) . "\n"; } if (!empty($msg)) { return SetupResult::warning( - $this->l10n->t('Some headers are not set correctly on your instance')."\n".$msg, + $this->l10n->t('Some headers are not set correctly on your instance') . "\n" . $msg, $this->urlGenerator->linkToDocs('admin-security'), $msgParameters, ); } + // Skip the other requests if one works $works = true; break; } + // If 'works' is null then we could not connect to the server if ($works === null) { return SetupResult::info( @@ -129,6 +132,7 @@ public function run(): SetupResult { $this->urlGenerator->linkToDocs('admin-security'), ); } + // Otherwise if we fail we can abort here if ($works === false) { return SetupResult::warning( @@ -137,6 +141,7 @@ public function run(): SetupResult { ); } } + return SetupResult::success( $this->l10n->t('Your server is correctly configured to send security headers.') ); diff --git a/apps/settings/lib/SetupChecks/SupportedDatabase.php b/apps/settings/lib/SetupChecks/SupportedDatabase.php index 89f789483059b..b8ebaab1fe2ba 100644 --- a/apps/settings/lib/SetupChecks/SupportedDatabase.php +++ b/apps/settings/lib/SetupChecks/SupportedDatabase.php @@ -111,6 +111,7 @@ public function run(): SetupResult { } else { return SetupResult::error($this->l10n->t('Unknown database platform')); } + return SetupResult::success($version); } } diff --git a/apps/settings/lib/SetupChecks/TempSpaceAvailable.php b/apps/settings/lib/SetupChecks/TempSpaceAvailable.php index ef51ffe0e07a1..5327c97bd3d09 100644 --- a/apps/settings/lib/SetupChecks/TempSpaceAvailable.php +++ b/apps/settings/lib/SetupChecks/TempSpaceAvailable.php @@ -84,8 +84,9 @@ public function run(): SetupResult { if ($freeSpaceInNextcloudTemp === false) { return SetupResult::error($this->l10n->t('Error while checking the available disk space of temporary PHP path or no free disk space returned. Temporary path: %s', [$nextcloudTempPath])); } + $freeSpaceInNextcloudTempInGB = $freeSpaceInNextcloudTemp / 1024 / 1024 / 1024; - $spaceDetail .= "\n".$this->l10n->t('- %.1f GiB available in %s (Nextcloud temporary directory)', [round($freeSpaceInNextcloudTempInGB, 1),$nextcloudTempPath]); + $spaceDetail .= "\n" . $this->l10n->t('- %.1f GiB available in %s (Nextcloud temporary directory)', [round($freeSpaceInNextcloudTempInGB, 1),$nextcloudTempPath]); } if (!$this->isPrimaryStorageS3()) { diff --git a/apps/settings/lib/SetupChecks/WellKnownUrls.php b/apps/settings/lib/SetupChecks/WellKnownUrls.php index 9fdaca996b867..8032f7aa03abc 100644 --- a/apps/settings/lib/SetupChecks/WellKnownUrls.php +++ b/apps/settings/lib/SetupChecks/WellKnownUrls.php @@ -68,11 +68,13 @@ public function run(): SetupResult { $works = str_ends_with(rtrim($effectiveUri, '/'), '/remote.php/dav'); } } + // Skip the other requests if one works if ($works === true) { break; } } + // If 'works' is null then we could not connect to the server if ($works === null) { return SetupResult::info( @@ -80,6 +82,7 @@ public function run(): SetupResult { $this->urlGenerator->linkToDocs('admin-setup-well-known-URL'), ); } + // Otherwise if we fail we can abort here if ($works === false) { return SetupResult::warning( @@ -88,6 +91,7 @@ public function run(): SetupResult { ); } } + return SetupResult::success( $this->l10n->t('Your server is correctly configured to serve `.well-known` URLs.') ); diff --git a/apps/settings/lib/SetupChecks/Woff2Loading.php b/apps/settings/lib/SetupChecks/Woff2Loading.php index 27aff4ea9993a..1ffc739d60f02 100644 --- a/apps/settings/lib/SetupChecks/Woff2Loading.php +++ b/apps/settings/lib/SetupChecks/Woff2Loading.php @@ -45,6 +45,7 @@ public function run(): SetupResult { if ($result->getSeverity() !== SetupResult::SUCCESS) { return $result; } + return $this->checkFont('woff2', $this->urlGenerator->linkTo('', 'core/fonts/NotoSans-Regular-latin.woff2')); } @@ -68,6 +69,7 @@ protected function checkFont(string $fileExtension, string $url): SetupResult { $this->urlGenerator->linkToDocs('admin-nginx'), ); } + return SetupResult::warning( str_replace( '{extension}', diff --git a/apps/settings/lib/UserMigration/AccountMigrator.php b/apps/settings/lib/UserMigration/AccountMigrator.php index dc5ca2691f7b2..81107cd81c599 100644 --- a/apps/settings/lib/UserMigration/AccountMigrator.php +++ b/apps/settings/lib/UserMigration/AccountMigrator.php @@ -55,7 +55,7 @@ public function __construct( IAvatarManager $avatarManager, ProfileManager $profileManager, ProfileConfigMapper $configMapper, - IL10N $l10n + IL10N $l10n, ) { $this->accountManager = $accountManager; $this->avatarManager = $avatarManager; diff --git a/apps/settings/lib/WellKnown/ChangePasswordHandler.php b/apps/settings/lib/WellKnown/ChangePasswordHandler.php index 0057a7ff33027..2ce024a3a0e94 100644 --- a/apps/settings/lib/WellKnown/ChangePasswordHandler.php +++ b/apps/settings/lib/WellKnown/ChangePasswordHandler.php @@ -30,6 +30,7 @@ public function handle(string $service, IRequestContext $context, ?IResponse $pr } $response = new RedirectResponse($this->urlGenerator->linkToRouteAbsolute('settings.PersonalSettings.index', ['section' => 'security'])); + return new GenericResponse($response); } } diff --git a/apps/settings/templates/help.php b/apps/settings/templates/help.php index 1488399679122..1e0c7470c113f 100644 --- a/apps/settings/templates/help.php +++ b/apps/settings/templates/help.php @@ -11,9 +11,13 @@
  • " + } + + ?> href=""> t('Account documentation')); ?> @@ -24,16 +28,22 @@
  • " + } + + ?> href=""> t('Administration documentation')); ?>
  • - +
  • @@ -81,12 +91,16 @@ t('Legal notice')); ?> ↗ - + t('Privacy policy')); ?> ↗ - + diff --git a/apps/settings/templates/settings/additional.php b/apps/settings/templates/settings/additional.php index 9e0b61ef65aad..c8da62614d591 100644 --- a/apps/settings/templates/settings/additional.php +++ b/apps/settings/templates/settings/additional.php @@ -13,4 +13,6 @@ if (isset($form['form'])) {?>
    + } + + ?> diff --git a/apps/settings/templates/settings/admin/additional-mail.php b/apps/settings/templates/settings/admin/additional-mail.php index ecc2973f085f6..5ff32b1e50c24 100644 --- a/apps/settings/templates/settings/admin/additional-mail.php +++ b/apps/settings/templates/settings/admin/additional-mail.php @@ -22,6 +22,7 @@ if ($_['sendmail_is_available']) { $mail_smtpmode[] = ['sendmail', 'Sendmail']; } + if ($_['mail_smtpmode'] === 'qmail') { $mail_smtpmode[] = ['qmail', 'qmail']; } @@ -51,8 +52,10 @@ > + } + + ?>> $name): $selected = ''; + if ($secure == $_['mail_smtpsecure']): $selected = 'selected="selected"'; + endif; ?> @@ -87,7 +96,9 @@

    @@ -101,7 +112,9 @@
    diff --git a/apps/settings/templates/settings/admin/server.php b/apps/settings/templates/settings/admin/server.php index aa320a03a2bdd..f4cdfd5b3271a 100644 --- a/apps/settings/templates/settings/admin/server.php +++ b/apps/settings/templates/settings/admin/server.php @@ -16,4 +16,6 @@
    - + diff --git a/apps/settings/templates/settings/frame.php b/apps/settings/templates/settings/frame.php index 27da91152f3f1..6b70575e9641f 100644 --- a/apps/settings/templates/settings/frame.php +++ b/apps/settings/templates/settings/frame.php @@ -14,7 +14,9 @@
    -
    t('Personal')); ?>
    +
    t('Personal')); + + ?>
    -
    t('Administration')); ?>
    +
    t('Administration')); + + ?>
    -
    data-active-section-id="" data-active-section-type="" > +
    data-active-section-id="" data-active-section-type="" >
    diff --git a/apps/settings/templates/settings/personal/personal.info.php b/apps/settings/templates/settings/personal/personal.info.php index e622663fba7d1..a89c06b51fa97 100644 --- a/apps/settings/templates/settings/personal/personal.info.php +++ b/apps/settings/templates/settings/personal/personal.info.php @@ -20,7 +20,9 @@
    - t('This community release of Nextcloud is unsupported and instant notifications are unavailable.')); ?> + t('This community release of Nextcloud is unsupported and instant notifications are unavailable.')); + + ?>
    @@ -39,7 +41,9 @@
    - +
    @@ -89,7 +93,9 @@
    - +
    @@ -98,4 +104,6 @@
    - + diff --git a/apps/settings/templates/settings/personal/security/password.php b/apps/settings/templates/settings/personal/security/password.php index 0531468913a69..e18d2651dbdac 100644 --- a/apps/settings/templates/settings/personal/security/password.php +++ b/apps/settings/templates/settings/personal/security/password.php @@ -7,5 +7,6 @@ if ($_['passwordChangeSupported']) { \OCP\Util::addScript('settings', 'vue-settings-personal-password'); } + ?>
    diff --git a/apps/settings/templates/settings/personal/security/twofactor.php b/apps/settings/templates/settings/personal/security/twofactor.php index 986c28a95e615..e2f87b0f64ff3 100644 --- a/apps/settings/templates/settings/personal/security/twofactor.php +++ b/apps/settings/templates/settings/personal/security/twofactor.php @@ -28,6 +28,7 @@ } else { $icon = image_path('core', 'actions/password.svg'); } + /** @var \OCP\Authentication\TwoFactorAuth\IPersonalProviderSettings $settings */ $settings = $data['settings']; ?> @@ -37,6 +38,8 @@ getBody()->fetchPage()) ?>
  • - + diff --git a/apps/settings/tests/Controller/CheckSetupControllerTest.php b/apps/settings/tests/Controller/CheckSetupControllerTest.php index 27f7aa1b6969f..98bebe8d7c11a 100644 --- a/apps/settings/tests/Controller/CheckSetupControllerTest.php +++ b/apps/settings/tests/Controller/CheckSetupControllerTest.php @@ -108,18 +108,23 @@ public function testCheck(): void { if ($key === 'admin-performance') { return 'http://docs.example.org/server/go.php?to=admin-performance'; } + if ($key === 'admin-security') { return 'https://docs.example.org/server/8.1/admin_manual/configuration_server/hardening.html'; } + if ($key === 'admin-reverse-proxy') { return 'reverse-proxy-doc-link'; } + if ($key === 'admin-code-integrity') { return 'http://docs.example.org/server/go.php?to=admin-code-integrity'; } + if ($key === 'admin-db-conversion') { return 'http://docs.example.org/server/go.php?to=admin-db-conversion'; } + return ''; }); @@ -128,9 +133,11 @@ public function testCheck(): void { if ($url === 'index.php/settings/admin') { return 'https://server/index.php/settings/admin'; } + if ($url === 'index.php') { return 'https://server/index.php'; } + return ''; }); diff --git a/apps/settings/tests/Controller/UsersControllerTest.php b/apps/settings/tests/Controller/UsersControllerTest.php index e8ae965b3bbaa..56f30331060f9 100644 --- a/apps/settings/tests/Controller/UsersControllerTest.php +++ b/apps/settings/tests/Controller/UsersControllerTest.php @@ -238,6 +238,7 @@ protected function getDefaultAccountMock(bool $useDefaultValues = true): MockObj if (isset($propertyMocks[$propertyName])) { return $propertyMocks[$propertyName]; } + throw new PropertyDoesNotExistException($propertyName); }); $account->expects($this->any()) @@ -600,6 +601,7 @@ public function testSetUserSettingsSubset($property, $propertyValue): void { default: $propertyId = '404'; } + $expectedProperties[$propertyId]->expects($this->any()) ->method($isScope ? 'getScope' : 'getValue') ->willReturn($propertyValue); @@ -669,7 +671,7 @@ public function dataTestSetUserSettingsSubset() { */ public function testSaveUserSettings($data, $oldEmailAddress, - $oldDisplayName + $oldDisplayName, ): void { $controller = $this->getController(); $user = $this->createMock(IUser::class); @@ -792,7 +794,7 @@ public function testSaveUserSettingsException( string $oldEmailAddress, string $oldDisplayName, bool $setDisplayNameResult, - bool $canChangeEmail + bool $canChangeEmail, ): void { $this->expectException(ForbiddenException::class); @@ -812,6 +814,7 @@ public function testSaveUserSettingsException( /** @var MockObject|IAccountProperty $property */ $propertyMocks[$propertyName] = $this->buildPropertyMock($propertyName, $propertyData['value'], ''); } + $userAccount->expects($this->any()) ->method('getProperty') ->willReturnCallback(function (string $propertyName) use ($propertyMocks) { diff --git a/apps/settings/tests/Mailer/NewUserMailHelperTest.php b/apps/settings/tests/Mailer/NewUserMailHelperTest.php index 579ab2cdbb0c9..986b09db1f5d5 100644 --- a/apps/settings/tests/Mailer/NewUserMailHelperTest.php +++ b/apps/settings/tests/Mailer/NewUserMailHelperTest.php @@ -77,6 +77,7 @@ protected function setUp(): void { case 'customclient_desktop': return 'https://nextcloud.com/install/#install-clients'; } + return ''; }); $this->crypto = $this->createMock(ICrypto::class); diff --git a/apps/settings/tests/SetupChecks/AppDirsWithDifferentOwnerTest.php b/apps/settings/tests/SetupChecks/AppDirsWithDifferentOwnerTest.php index db3141e3a2675..9ea1d2b02c85b 100644 --- a/apps/settings/tests/SetupChecks/AppDirsWithDifferentOwnerTest.php +++ b/apps/settings/tests/SetupChecks/AppDirsWithDifferentOwnerTest.php @@ -98,6 +98,7 @@ public function removeTestDirectories() { foreach ($this->dirsToRemove as $dirToRemove) { rmdir($dirToRemove); } + $this->dirsToRemove = []; } } diff --git a/apps/settings/tests/SetupChecks/DataDirectoryProtectedTest.php b/apps/settings/tests/SetupChecks/DataDirectoryProtectedTest.php index 51dffe58787c9..d0c0da7da2cb5 100644 --- a/apps/settings/tests/SetupChecks/DataDirectoryProtectedTest.php +++ b/apps/settings/tests/SetupChecks/DataDirectoryProtectedTest.php @@ -64,6 +64,7 @@ public function testStatusCode(array $status, string $expected, bool $hasBody): $response = $this->createMock(IResponse::class); $response->expects($this->any())->method('getStatusCode')->willReturn($state); $response->expects(($this->atMost(1)))->method('getBody')->willReturn($hasBody ? '# Nextcloud data directory' : 'something else'); + return $response; }, $status); diff --git a/apps/settings/tests/SetupChecks/SecurityHeadersTest.php b/apps/settings/tests/SetupChecks/SecurityHeadersTest.php index 2bc5aa69b9c91..d8bb51d47c670 100644 --- a/apps/settings/tests/SetupChecks/SecurityHeadersTest.php +++ b/apps/settings/tests/SetupChecks/SecurityHeadersTest.php @@ -176,7 +176,7 @@ public function testFailure(array $headers, string $msg): void { $result = $this->setupcheck->run(); $this->assertEquals( - 'Some headers are not set correctly on your instance'."\n$msg", + 'Some headers are not set correctly on your instance' . "\n$msg", $result->getDescription() ); $this->assertEquals(SetupResult::WARNING, $result->getSeverity()); diff --git a/apps/settings/tests/SetupChecks/WellKnownUrlsTest.php b/apps/settings/tests/SetupChecks/WellKnownUrlsTest.php index 983f2c427ad18..8e2aa15bed6f9 100644 --- a/apps/settings/tests/SetupChecks/WellKnownUrlsTest.php +++ b/apps/settings/tests/SetupChecks/WellKnownUrlsTest.php @@ -124,6 +124,7 @@ public function dataTestResponses(): array { $response->expects($this->any()) ->method('getHeader') ->willReturnCallback(fn ($name) => $header[$name] ?? ''); + return $response; }; diff --git a/apps/settings/tests/UserMigration/AccountMigratorTest.php b/apps/settings/tests/UserMigration/AccountMigratorTest.php index ab5ffc6b31419..86c9cb5086233 100644 --- a/apps/settings/tests/UserMigration/AccountMigratorTest.php +++ b/apps/settings/tests/UserMigration/AccountMigratorTest.php @@ -70,6 +70,7 @@ function (string $filename) { $basename = pathinfo($filename, PATHINFO_FILENAME); $avatarPath = static::ASSETS_DIR . (file_exists(static::ASSETS_DIR . "$basename.jpg") ? "$basename.jpg" : "$basename.png"); $configPath = static::ASSETS_DIR . "$basename-config." . pathinfo($filename, PATHINFO_EXTENSION); + return [ UUIDUtil::getUUID(), json_decode(file_get_contents($dataPath), true, 512, JSON_THROW_ON_ERROR), diff --git a/apps/sharebymail/lib/Activity.php b/apps/sharebymail/lib/Activity.php index be9deeb7290f3..bcbf87ab1dbcf 100644 --- a/apps/sharebymail/lib/Activity.php +++ b/apps/sharebymail/lib/Activity.php @@ -240,6 +240,7 @@ protected function getParsedParameters(IEvent $event) { 'file' => $this->generateFileParameter($event->getObjectId(), $parameters[0]), ]; } + throw new \InvalidArgumentException(); } diff --git a/apps/sharebymail/lib/Capabilities.php b/apps/sharebymail/lib/Capabilities.php index ff6f634747bc8..60ff313910308 100644 --- a/apps/sharebymail/lib/Capabilities.php +++ b/apps/sharebymail/lib/Capabilities.php @@ -57,6 +57,7 @@ public function getCapabilities(): array { if (!$this->appManager->isEnabledForUser('files_sharing')) { return []; } + return [ 'files_sharing' => [ diff --git a/apps/sharebymail/lib/ShareByMailProvider.php b/apps/sharebymail/lib/ShareByMailProvider.php index 744837717620d..9607252797aba 100644 --- a/apps/sharebymail/lib/ShareByMailProvider.php +++ b/apps/sharebymail/lib/ShareByMailProvider.php @@ -298,6 +298,7 @@ public function sendMailNotification(IShare $share): bool { $e, ); } + return false; } @@ -382,6 +383,7 @@ protected function sendEmail(IShare $share, array $emails): void { ] ); } + $message->setFrom([\OCP\Util::getDefaultEmailAddress($instanceName) => $senderName]); // The "Reply-To" is set to the sharer if an mail address is configured @@ -478,6 +480,7 @@ protected function sendPassword(IShare $share, string $password, array $emails): ] ); } + $message->setFrom([\OCP\Util::getDefaultEmailAddress($instanceName) => $senderName]); // The "Reply-To" is set to the sharer if an mail address is configured @@ -502,6 +505,7 @@ protected function sendPassword(IShare $share, string $password, array $emails): } $this->createPasswordSendActivity($share, $shareWith, false); + return true; } @@ -548,6 +552,7 @@ protected function sendNote(IShare $share): void { ] ); } + $message->setFrom([\OCP\Util::getDefaultEmailAddress($instanceName) => $senderName]); if ($this->settingsManager->replyToInitiator() && $initiatorEmailAddress !== null) { $message->setReplyTo([$initiatorEmailAddress => $initiatorDisplayName]); @@ -660,6 +665,7 @@ public function getChildren(IShare $parent): array { while ($data = $cursor->fetch()) { $children[] = $this->createShareObject($data); } + $cursor->closeCursor(); return $children; @@ -684,7 +690,7 @@ protected function addShareToDB( ?\DateTimeInterface $expirationTime, ?string $note = '', ?IAttributes $attributes = null, - ?bool $mailSend = true + ?bool $mailSend = true, ): int { $qb = $this->dbConnection->getQueryBuilder(); $qb->insert('share') @@ -715,6 +721,7 @@ protected function addShareToDB( } $qb->executeStatement(); + return $qb->getLastInsertId(); } @@ -852,6 +859,7 @@ public function getSharesBy($userId, $shareType, $node, $reshares, $limit, $offs while ($data = $cursor->fetch()) { $shares[] = $this->createShareObject($data); } + $cursor->closeCursor(); return $shares; @@ -903,6 +911,7 @@ public function getSharesByPath(Node $path): array { while ($data = $cursor->fetch()) { $shares[] = $this->createShareObject($data); } + $cursor->closeCursor(); return $shares; @@ -927,6 +936,7 @@ public function getSharedWith($userId, $shareType, $node, $limit, $offset): arra if ($limit !== -1) { $qb->setMaxResults($limit); } + $qb->setFirstResult($offset); $qb->where($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_EMAIL))); @@ -942,6 +952,7 @@ public function getSharedWith($userId, $shareType, $node, $limit, $offset): arra while ($data = $cursor->fetch()) { $shares[] = $this->createShareObject($data); } + $cursor->closeCursor(); @@ -1150,6 +1161,7 @@ public function getSharesInFolder($userId, Folder $node, $reshares, $shallow = t while ($data = $cursor->fetch()) { $shares[$data['fileid']][] = $this->createShareObject($data); } + $cursor->closeCursor(); return $shares; @@ -1188,6 +1200,7 @@ public function getAccessList($nodes, $currentAccess): array { ]; } } + $cursor->closeCursor(); return ['public' => $public, 'mail' => $mail]; @@ -1216,6 +1229,7 @@ public function getAllShares(): iterable { yield $share; } + $cursor->closeCursor(); } @@ -1237,6 +1251,7 @@ protected function getSharedWithEmails(IShare $share): array { if (isset($emails) && is_array($emails) && !empty($emails)) { return $emails; } + return [$share->getSharedWith()]; } } diff --git a/apps/sharebymail/tests/ShareByMailProviderTest.php b/apps/sharebymail/tests/ShareByMailProviderTest.php index c01f5b476325d..f0327282bdb9e 100644 --- a/apps/sharebymail/tests/ShareByMailProviderTest.php +++ b/apps/sharebymail/tests/ShareByMailProviderTest.php @@ -872,6 +872,7 @@ function ($data) use ($uidOwner, $sharedBy, $id2) { $this->assertSame($uidOwner, $data['uid_owner']); $this->assertSame($sharedBy, $data['uid_initiator']); $this->assertSame($id2, (int)$data['id']); + return $this->share; } ); @@ -924,6 +925,7 @@ function ($data) use ($uidOwner, $sharedBy, $id) { $this->assertSame($uidOwner, $data['uid_owner']); $this->assertSame($sharedBy, $data['uid_initiator']); $this->assertSame($id, (int)$data['id']); + return $this->share; } ); diff --git a/apps/systemtags/lib/Activity/Listener.php b/apps/systemtags/lib/Activity/Listener.php index 7ab00787a763f..4dca2dd43d6d9 100644 --- a/apps/systemtags/lib/Activity/Listener.php +++ b/apps/systemtags/lib/Activity/Listener.php @@ -87,6 +87,7 @@ public function event(ManagerEvent $event) { } else { $actor = ''; } + $tag = $event->getTag(); $activity = $this->activityManager->generateEvent(); @@ -122,7 +123,6 @@ public function event(ManagerEvent $event) { } } - if ($actor !== '' && ($event->getEvent() === ManagerEvent::EVENT_CREATE || $event->getEvent() === ManagerEvent::EVENT_UPDATE)) { $this->updateLastUsedTags($actor, $event->getTag()); } @@ -193,6 +193,7 @@ public function mapperEvent(MapperEvent $event) { if (!$tag->isUserVisible() && !$this->groupManager->isAdmin($user)) { continue; } + if ($event->getEvent() === MapperEvent::EVENT_ASSIGN) { $activity->setSubject(Provider::ASSIGN_TAG, [ $actor, diff --git a/apps/systemtags/lib/Activity/Provider.php b/apps/systemtags/lib/Activity/Provider.php index ba24dff31a875..7dcab69fedc58 100644 --- a/apps/systemtags/lib/Activity/Provider.php +++ b/apps/systemtags/lib/Activity/Provider.php @@ -280,6 +280,7 @@ protected function getParameters(IEvent $event) { 'systemtag' => $this->getSystemTagParameter($parameters[2]), ]; } + return []; } diff --git a/apps/systemtags/lib/Activity/Setting.php b/apps/systemtags/lib/Activity/Setting.php index c65cf3d404a58..95001e95cf3ab 100644 --- a/apps/systemtags/lib/Activity/Setting.php +++ b/apps/systemtags/lib/Activity/Setting.php @@ -9,7 +9,9 @@ use OCP\IL10N; class Setting extends ActivitySettings { - public function __construct(protected IL10N $l) { + public function __construct( + protected IL10N $l, + ) { } /** diff --git a/apps/systemtags/lib/Capabilities.php b/apps/systemtags/lib/Capabilities.php index a3168ccf1aecb..f23a632804790 100644 --- a/apps/systemtags/lib/Capabilities.php +++ b/apps/systemtags/lib/Capabilities.php @@ -20,6 +20,7 @@ public function getCapabilities() { 'enabled' => true, ] ]; + return $capabilities; } } diff --git a/apps/systemtags/lib/Controller/LastUsedController.php b/apps/systemtags/lib/Controller/LastUsedController.php index ccdeddd9270ab..b549f2bdad144 100644 --- a/apps/systemtags/lib/Controller/LastUsedController.php +++ b/apps/systemtags/lib/Controller/LastUsedController.php @@ -36,6 +36,7 @@ public function __construct($appName, IRequest $request, IConfig $config, IUserS public function getLastUsedTagIds() { $lastUsed = $this->config->getUserValue($this->userSession->getUser()->getUID(), 'systemtags', 'last_used', '[]'); $tagIds = json_decode($lastUsed, true); + return new DataResponse(array_map(function ($id) { return (string)$id; }, $tagIds)); diff --git a/apps/systemtags/lib/Search/TagSearchProvider.php b/apps/systemtags/lib/Search/TagSearchProvider.php index 80f9d39c141ce..d39b3365beb4b 100644 --- a/apps/systemtags/lib/Search/TagSearchProvider.php +++ b/apps/systemtags/lib/Search/TagSearchProvider.php @@ -52,7 +52,7 @@ public function __construct( IMimeTypeDetector $mimeTypeDetector, IRootFolder $rootFolder, ISystemTagObjectMapper $objectMapper, - ISystemTagManager $tagManager + ISystemTagManager $tagManager, ) { $this->l10n = $l10n; $this->urlGenerator = $urlGenerator; @@ -83,6 +83,7 @@ public function getOrder(string $route, array $routeParameters): int { if ($route === 'files.View.index') { return -4; } + return 6; } @@ -118,7 +119,7 @@ public function search(IUser $user, ISearchQuery $query): SearchResult { $thumbnailUrl = ''; $link = $this->urlGenerator->linkToRoute('files.view.indexView', [ 'view' => 'tags', - ]) . '?dir='.$tag->getId(); + ]) . '?dir=' . $tag->getId(); $searchResultEntry = new SearchResultEntry( $thumbnailUrl, $this->l10n->t('All tagged %s …', [$tag->getName()]), @@ -126,6 +127,7 @@ public function search(IUser $user, ISearchQuery $query): SearchResult { $this->urlGenerator->getAbsoluteURL($link), 'icon-tag' ); + return $searchResultEntry; }, $matchingTags); @@ -153,6 +155,7 @@ public function search(IUser $user, ISearchQuery $query): SearchResult { ); $searchResultEntry->addAttribute('fileId', (string)$result->getId()); $searchResultEntry->addAttribute('path', $path); + return $searchResultEntry; }, $searchResults) + $tagResults, diff --git a/apps/testing/lib/AlternativeHomeUserBackend.php b/apps/testing/lib/AlternativeHomeUserBackend.php index 38ceb40420896..a36950c977918 100644 --- a/apps/testing/lib/AlternativeHomeUserBackend.php +++ b/apps/testing/lib/AlternativeHomeUserBackend.php @@ -32,6 +32,7 @@ public function getHome($uid) { if ($uid !== 'admin') { $uid = md5($uid); } + return \OC::$server->getConfig()->getSystemValue('datadirectory', \OC::$SERVERROOT . '/data') . '/' . $uid; } diff --git a/apps/testing/lib/Controller/LockingController.php b/apps/testing/lib/Controller/LockingController.php index 14135a4929df0..994ad02ba6eb6 100644 --- a/apps/testing/lib/Controller/LockingController.php +++ b/apps/testing/lib/Controller/LockingController.php @@ -70,6 +70,7 @@ protected function getLockingProvider(): ILockingProvider { if ($this->lockingProvider instanceof DBLockingProvider) { return $this->fakeDBLockingProvider; } + throw new \RuntimeException('Lock provisioning is only possible using the DBLockingProvider'); } @@ -110,6 +111,7 @@ public function acquireLock(int $type, string $user, string $path): DataResponse try { $lockingProvider->acquireLock($path, $type); $this->config->setAppValue('testing', 'locking_' . $path, (string)$type); + return new DataResponse(); } catch (LockedException $e) { throw new OCSException('', Http::STATUS_LOCKED, $e); @@ -133,6 +135,7 @@ public function changeLock(int $type, string $user, string $path): DataResponse try { $lockingProvider->changeLock($path, $type); $this->config->setAppValue('testing', 'locking_' . $path, (string)$type); + return new DataResponse(); } catch (LockedException $e) { throw new OCSException('', Http::STATUS_LOCKED, $e); @@ -156,6 +159,7 @@ public function releaseLock(int $type, string $user, string $path): DataResponse try { $lockingProvider->releaseLock($path, $type); $this->config->deleteAppValue('testing', 'locking_' . $path); + return new DataResponse(); } catch (LockedException $e) { throw new OCSException('', Http::STATUS_LOCKED, $e); diff --git a/apps/testing/lib/Listener/GetDeclarativeSettingsValueListener.php b/apps/testing/lib/Listener/GetDeclarativeSettingsValueListener.php index 312f49e5f5c66..0df5816800759 100644 --- a/apps/testing/lib/Listener/GetDeclarativeSettingsValueListener.php +++ b/apps/testing/lib/Listener/GetDeclarativeSettingsValueListener.php @@ -17,7 +17,9 @@ */ class GetDeclarativeSettingsValueListener implements IEventListener { - public function __construct(private IConfig $config) { + public function __construct( + private IConfig $config, + ) { } public function handle(Event $event): void { diff --git a/apps/testing/lib/Listener/SetDeclarativeSettingsValueListener.php b/apps/testing/lib/Listener/SetDeclarativeSettingsValueListener.php index 19f1ae79b7b76..0058e7df43ef8 100644 --- a/apps/testing/lib/Listener/SetDeclarativeSettingsValueListener.php +++ b/apps/testing/lib/Listener/SetDeclarativeSettingsValueListener.php @@ -17,7 +17,9 @@ */ class SetDeclarativeSettingsValueListener implements IEventListener { - public function __construct(private IConfig $config) { + public function __construct( + private IConfig $config, + ) { } public function handle(Event $event): void { diff --git a/apps/testing/lib/Locking/FakeDBLockingProvider.php b/apps/testing/lib/Locking/FakeDBLockingProvider.php index 2c4d394fb0d4e..6e2fd9a77d822 100644 --- a/apps/testing/lib/Locking/FakeDBLockingProvider.php +++ b/apps/testing/lib/Locking/FakeDBLockingProvider.php @@ -21,7 +21,7 @@ class FakeDBLockingProvider extends DBLockingProvider { public function __construct( IDBConnection $connection, - ITimeFactory $timeFactory + ITimeFactory $timeFactory, ) { parent::__construct($connection, $timeFactory); $this->db = $connection; diff --git a/apps/testing/lib/TaskProcessing/FakeContextWriteProvider.php b/apps/testing/lib/TaskProcessing/FakeContextWriteProvider.php index 32aa1ebd7c971..5505056e1b25f 100644 --- a/apps/testing/lib/TaskProcessing/FakeContextWriteProvider.php +++ b/apps/testing/lib/TaskProcessing/FakeContextWriteProvider.php @@ -96,6 +96,7 @@ public function process(?string $userId, array $input, callable $reportProgress) ) { throw new RuntimeException('Invalid inputs'); } + $writingStyle = $input['style_input']; $sourceMaterial = $input['source_input']; diff --git a/apps/testing/lib/TaskProcessing/FakeTextToImageProvider.php b/apps/testing/lib/TaskProcessing/FakeTextToImageProvider.php index 429b3af9d574f..5319a82fd325a 100644 --- a/apps/testing/lib/TaskProcessing/FakeTextToImageProvider.php +++ b/apps/testing/lib/TaskProcessing/FakeTextToImageProvider.php @@ -80,6 +80,7 @@ public function process(?string $userId, array $input, callable $reportProgress) if (!isset($input['input']) || !is_string($input['input'])) { throw new RuntimeException('Invalid prompt'); } + $prompt = $input['input']; $nbImages = 1; @@ -93,6 +94,7 @@ public function process(?string $userId, array $input, callable $reportProgress) foreach (range(1, $nbImages) as $i) { $output['images'][] = $fakeContent; } + /** @var array|numeric|string> $output */ return $output; } diff --git a/apps/testing/lib/TaskProcessing/FakeTextToTextProvider.php b/apps/testing/lib/TaskProcessing/FakeTextToTextProvider.php index 9854cd3e60923..4b81da7a0e3b1 100644 --- a/apps/testing/lib/TaskProcessing/FakeTextToTextProvider.php +++ b/apps/testing/lib/TaskProcessing/FakeTextToTextProvider.php @@ -99,6 +99,7 @@ public function process(?string $userId, array $input, callable $reportProgress) if (!isset($input['input']) || !is_string($input['input'])) { throw new RuntimeException('Invalid prompt'); } + $prompt = $input['input']; $maxTokens = null; diff --git a/apps/testing/lib/TaskProcessing/FakeTranscribeProvider.php b/apps/testing/lib/TaskProcessing/FakeTranscribeProvider.php index 5401c5e16f8d5..73933c20e018b 100644 --- a/apps/testing/lib/TaskProcessing/FakeTranscribeProvider.php +++ b/apps/testing/lib/TaskProcessing/FakeTranscribeProvider.php @@ -72,6 +72,7 @@ public function process(?string $userId, array $input, callable $reportProgress) if (!isset($input['input']) || !$input['input'] instanceof File || !$input['input']->isReadable()) { throw new RuntimeException('Invalid input file'); } + $inputFile = $input['input']; $transcription = 'Fake transcription result'; diff --git a/apps/testing/lib/TaskProcessing/FakeTranslateProvider.php b/apps/testing/lib/TaskProcessing/FakeTranslateProvider.php index 064f54f265065..16a88307d58f0 100644 --- a/apps/testing/lib/TaskProcessing/FakeTranslateProvider.php +++ b/apps/testing/lib/TaskProcessing/FakeTranslateProvider.php @@ -47,6 +47,7 @@ public function getInputShapeEnumValues(): array { return new ShapeEnumValue($language['name'], $language['code']); }, $languages); $detectLanguageEnumValue = new ShapeEnumValue('Detect language', 'detect_language'); + return [ 'origin_language' => array_merge([$detectLanguageEnumValue], $languageEnumValues), 'target_language' => $languageEnumValues, @@ -109,6 +110,7 @@ private function getCoreLanguagesByCode(): array { $carry[$val['code']] = $val['name']; return $carry; }); + return $coreLanguages; } @@ -122,6 +124,7 @@ public function process(?string $userId, array $input, callable $reportProgress) if (!isset($input['input']) || !is_string($input['input'])) { throw new RuntimeException('Invalid input text'); } + $inputText = $input['input']; $maxTokens = null; diff --git a/apps/theming/lib/Command/UpdateConfig.php b/apps/theming/lib/Command/UpdateConfig.php index 3a3f3f0c76763..4e0d3f1a6edb6 100644 --- a/apps/theming/lib/Command/UpdateConfig.php +++ b/apps/theming/lib/Command/UpdateConfig.php @@ -66,10 +66,12 @@ protected function execute(InputInterface $input, OutputInterface $output): int $value = $this->config->getAppValue('theming', $key, ''); $output->writeln('- ' . $key . ': ' . $value . ''); } + foreach (ImageManager::SUPPORTED_IMAGE_KEYS as $key) { $value = $this->config->getAppValue('theming', $key . 'Mime', ''); $output->writeln('- ' . $key . ': ' . $value . ''); } + return 0; } @@ -81,6 +83,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int if ($input->getOption('reset')) { $defaultValue = $this->themingDefaults->undo($key); $output->writeln('Reset ' . $key . ' to ' . $defaultValue . ''); + return 0; } @@ -91,6 +94,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int } else { $output->writeln('' . $key . ' is currently not set'); } + return 0; } @@ -104,10 +108,12 @@ protected function execute(InputInterface $input, OutputInterface $output): int $output->writeln('The image file needs to be provided as an absolute path: ' . $value . '.'); return 1; } + if (!file_exists($value)) { $output->writeln('File could not be found: ' . $value . '.'); return 1; } + $value = $this->imageManager->updateImage($key, $value); $key = $key . 'Mime'; } diff --git a/apps/theming/lib/Controller/IconController.php b/apps/theming/lib/Controller/IconController.php index bdd5a43ddc3c6..a9632ba6d99ca 100644 --- a/apps/theming/lib/Controller/IconController.php +++ b/apps/theming/lib/Controller/IconController.php @@ -40,7 +40,7 @@ public function __construct( IconBuilder $iconBuilder, ImageManager $imageManager, FileAccessHelper $fileAccessHelper, - IAppManager $appManager + IAppManager $appManager, ) { parent::__construct($appName, $request); @@ -78,10 +78,13 @@ public function getThemedIcon(string $app, string $image): Response { if ($icon === false || $icon === '') { return new NotFoundResponse(); } + $iconFileName = $this->imageManager->setCachedImage('icon-' . $app . '-' . $color . str_replace('/', '_', $image), $icon); } + $response = new FileDisplayResponse($iconFileName, Http::STATUS_OK, ['Content-Type' => 'image/svg+xml']); $response->cacheFor(86400, false, true); + return $response; } @@ -109,6 +112,7 @@ public function getFavicon(string $app = 'core'): Response { $response = new FileDisplayResponse($iconFile, Http::STATUS_OK, ['Content-Type' => 'image/x-icon']); } catch (NotFoundException $e) { } + if ($iconFile === null && $this->imageManager->shouldReplaceIcons()) { $color = $this->themingDefaults->getColorPrimary(); try { @@ -118,15 +122,20 @@ public function getFavicon(string $app = 'core'): Response { if ($icon === false || $icon === '') { return new NotFoundResponse(); } + $iconFile = $this->imageManager->setCachedImage('favIcon-' . $app . $color, $icon); } + $response = new FileDisplayResponse($iconFile, Http::STATUS_OK, ['Content-Type' => 'image/x-icon']); } + if ($response === null) { $fallbackLogo = \OC::$SERVERROOT . '/core/img/favicon.png'; $response = new DataDisplayResponse($this->fileAccessHelper->file_get_contents($fallbackLogo), Http::STATUS_OK, ['Content-Type' => 'image/x-icon']); } + $response->cacheFor(86400); + return $response; } @@ -153,6 +162,7 @@ public function getTouchIcon(string $app = 'core'): Response { $response = new FileDisplayResponse($iconFile, Http::STATUS_OK, ['Content-Type' => 'image/x-icon']); } catch (NotFoundException $e) { } + if ($this->imageManager->shouldReplaceIcons()) { $color = $this->themingDefaults->getColorPrimary(); try { @@ -162,15 +172,20 @@ public function getTouchIcon(string $app = 'core'): Response { if ($icon === false || $icon === '') { return new NotFoundResponse(); } + $iconFile = $this->imageManager->setCachedImage('touchIcon-' . $app . $color, $icon); } + $response = new FileDisplayResponse($iconFile, Http::STATUS_OK, ['Content-Type' => 'image/png']); } + if ($response === null) { $fallbackLogo = \OC::$SERVERROOT . '/core/img/favicon-touch.png'; $response = new DataDisplayResponse($this->fileAccessHelper->file_get_contents($fallbackLogo), Http::STATUS_OK, ['Content-Type' => 'image/png']); } + $response->cacheFor(86400); + return $response; } } diff --git a/apps/theming/lib/Controller/ThemingController.php b/apps/theming/lib/Controller/ThemingController.php index cda149cd48fa3..7901b5985db4e 100644 --- a/apps/theming/lib/Controller/ThemingController.php +++ b/apps/theming/lib/Controller/ThemingController.php @@ -74,35 +74,43 @@ public function updateStylesheet($setting, $value) { if (strlen($value) > 250) { $error = $this->l10n->t('The given name is too long'); } + break; case 'url': if (strlen($value) > 500) { $error = $this->l10n->t('The given web address is too long'); } + if (!$this->isValidUrl($value)) { $error = $this->l10n->t('The given web address is not a valid URL'); } + break; case 'imprintUrl': if (strlen($value) > 500) { $error = $this->l10n->t('The given legal notice address is too long'); } + if (!$this->isValidUrl($value)) { $error = $this->l10n->t('The given legal notice address is not a valid URL'); } + break; case 'privacyUrl': if (strlen($value) > 500) { $error = $this->l10n->t('The given privacy policy address is too long'); } + if (!$this->isValidUrl($value)) { $error = $this->l10n->t('The given privacy policy address is not a valid URL'); } + break; case 'slogan': if (strlen($value) > 500) { $error = $this->l10n->t('The given slogan is too long'); } + break; case 'primary_color': if (!preg_match('/^\#([0-9a-f]{3}|[0-9a-f]{6})$/i', $value)) { @@ -111,6 +119,7 @@ public function updateStylesheet($setting, $value) { $this->appConfig->setAppValueString('primary_color', $value); $saved = true; } + break; case 'background_color': if (!preg_match('/^\#([0-9a-f]{3}|[0-9a-f]{6})$/i', $value)) { @@ -119,6 +128,7 @@ public function updateStylesheet($setting, $value) { $this->appConfig->setAppValueString('background_color', $value); $saved = true; } + break; case 'disable-user-theming': if (!in_array($value, ['yes', 'true', 'no', 'false'])) { @@ -127,8 +137,10 @@ public function updateStylesheet($setting, $value) { $this->appConfig->setAppValueBool('disable-user-theming', $value === 'yes' || $value === 'true'); $saved = true; } + break; } + if ($error !== null) { return new DataResponse([ 'data' => [ @@ -170,10 +182,12 @@ public function updateAppMenu($setting, $value) { } else { $error = $this->l10n->t('Invalid type for setting "defaultApp" given'); } + break; default: $error = $this->l10n->t('Invalid setting key'); } + if ($error !== null) { return new DataResponse([ 'data' => [ @@ -217,6 +231,7 @@ public function uploadImage(): DataResponse { Http::STATUS_BAD_REQUEST ); } + $image = $this->request->getUploadedFile('image'); $error = null; $phpFileUploadErrors = [ @@ -232,6 +247,7 @@ public function uploadImage(): DataResponse { if (empty($image)) { $error = $this->l10n->t('No file uploaded'); } + if (!empty($image) && array_key_exists('error', $image) && $image['error'] !== UPLOAD_ERR_OK) { $error = $phpFileUploadErrors[$image['error']]; } @@ -357,6 +373,7 @@ public function getImage(string $key, bool $useSvg = true) { } else { $response->addHeader('Content-Type', $this->config->getAppValue($this->appName, $key . 'Mime', '')); } + return $response; } @@ -389,7 +406,9 @@ public function getThemeStylesheet(string $themeId, bool $plain = false, bool $w $variables = ''; foreach ($theme->getCSSVariables() as $variable => $value) { $variables .= "$variable:$value; "; - }; + } + +; // If plain is set, the browser decides of the css priority if ($plain) { @@ -405,6 +424,7 @@ public function getThemeStylesheet(string $themeId, bool $plain = false, bool $w try { $response = new DataDisplayResponse($css, Http::STATUS_OK, ['Content-Type' => 'text/css']); $response->cacheFor(86400); + return $response; } catch (NotFoundException $e) { return new NotFoundResponse(); @@ -435,6 +455,7 @@ public function getManifest(string $app): JSONResponse { if (!$this->appManager->isEnabledForUser($app)) { $response = new JSONResponse([], Http::STATUS_NOT_FOUND); $response->throttle(['action' => 'manifest', 'app' => $app]); + return $response; } @@ -446,8 +467,10 @@ public function getManifest(string $app): JSONResponse { } else { $startUrl = $this->urlGenerator->getBaseUrl() . '/apps/' . $app . '/'; } + $description = $info['summary'] ?? ''; } + /** * @var string $description * @var string $shortName @@ -478,6 +501,7 @@ public function getManifest(string $app): JSONResponse { ]; $response = new JSONResponse($responseJS); $response->cacheFor(3600); + return $response; } } diff --git a/apps/theming/lib/Controller/UserThemeController.php b/apps/theming/lib/Controller/UserThemeController.php index bef0f38f598c0..442e6793e85ed 100644 --- a/apps/theming/lib/Controller/UserThemeController.php +++ b/apps/theming/lib/Controller/UserThemeController.php @@ -76,6 +76,7 @@ public function enableTheme(string $themeId): DataResponse { // Enable selected theme $this->themesService->enableTheme($theme); + return new DataResponse(); } @@ -95,6 +96,7 @@ public function disableTheme(string $themeId): DataResponse { // Enable selected theme $this->themesService->disableTheme($theme); + return new DataResponse(); } @@ -141,8 +143,10 @@ public function getBackground(): Http\Response { if ($file !== null) { $response = new FileDisplayResponse($file, Http::STATUS_OK, ['Content-Type' => $file->getMimeType()]); $response->cacheFor(24 * 60 * 60, false, true); + return $response; } + return new NotFoundResponse(); } @@ -157,6 +161,7 @@ public function getBackground(): Http\Response { public function deleteBackground(): JSONResponse { $currentVersion = (int)$this->config->getUserValue($this->userId, Application::APP_ID, 'userCacheBuster', '0'); $this->backgroundService->deleteBackgroundImage(); + return new JSONResponse([ 'backgroundImage' => null, 'backgroundColor' => $this->themingDefaults->getColorBackground(), diff --git a/apps/theming/lib/IconBuilder.php b/apps/theming/lib/IconBuilder.php index e7e83a0b4c04b..3dfcf3cfc2841 100644 --- a/apps/theming/lib/IconBuilder.php +++ b/apps/theming/lib/IconBuilder.php @@ -27,7 +27,7 @@ class IconBuilder { public function __construct( ThemingDefaults $themingDefaults, Util $util, - ImageManager $imageManager + ImageManager $imageManager, ) { $this->themingDefaults = $themingDefaults; $this->util = $util; @@ -42,6 +42,7 @@ public function getFavicon($app) { if (!$this->imageManager->shouldReplaceIcons()) { return false; } + try { $favicon = new Imagick(); $favicon->setFormat('ico'); @@ -49,6 +50,7 @@ public function getFavicon($app) { if ($icon === false) { return false; } + $icon->setImageFormat('png32'); $clone = clone $icon; @@ -71,6 +73,7 @@ public function getFavicon($app) { $favicon->destroy(); $icon->destroy(); $clone->destroy(); + return $data; } catch (\ImagickException $e) { return false; @@ -87,9 +90,11 @@ public function getTouchIcon($app) { if ($icon === false) { return false; } + $icon->setImageFormat('png32'); $data = $icon->getImageBlob(); $icon->destroy(); + return $data; } catch (\ImagickException $e) { return false; @@ -109,6 +114,7 @@ public function renderAppIcon($app, $size) { if ($appIcon === false) { return false; } + if ($appIcon instanceof ISimpleFile) { $appIconContent = $appIcon->getContent(); $mime = $appIcon->getMimeType(); @@ -127,15 +133,16 @@ public function renderAppIcon($app, $size) { $cornerRadius = 0.2 * $size; $background = '' . '' . - '' . + '' . ''; // resize svg magic as this seems broken in Imagemagick if ($mime === 'image/svg+xml' || substr($appIconContent, 0, 4) === 'setBackgroundColor(new ImagickPixel('transparent')); $tmp->setResolution(72, 72); @@ -167,6 +174,7 @@ public function renderAppIcon($app, $size) { $appIconFile->setBackgroundColor(new ImagickPixel('transparent')); $appIconFile->readImageBlob($appIconContent); } + // offset for icon positioning $padding = 0.15; $border_w = (int)($appIconFile->getImageWidth() * $padding); @@ -190,9 +198,11 @@ public function renderAppIcon($app, $size) { } else { $filter = Imagick::FILTER_LANCZOS; } + $finalIconFile->resizeImage($size, $size, $filter, 1, false); $appIconFile->destroy(); + return $finalIconFile; } @@ -206,10 +216,12 @@ public function colorSvg($app, $image) { if ($imageFile === false || $imageFile === '') { return false; } + $svg = file_get_contents($imageFile); if ($svg !== false && $svg !== '') { $color = $this->util->elementColor($this->themingDefaults->getColorPrimary()); $svg = $this->util->colorizeSvg($svg, $color); + return $svg; } else { return false; diff --git a/apps/theming/lib/ImageManager.php b/apps/theming/lib/ImageManager.php index 4afdedf51896b..9d37d3079cc38 100644 --- a/apps/theming/lib/ImageManager.php +++ b/apps/theming/lib/ImageManager.php @@ -61,9 +61,11 @@ public function getImageUrl(string $key): string { if ($key === 'backgroundDark') { $image = BackgroundService::SHIPPED_BACKGROUNDS[$image]['dark_variant'] ?? $image; } + return $this->urlGenerator->linkTo(Application::APP_ID, "img/background/$image"); } } + return ''; } @@ -98,6 +100,7 @@ public function getImage(string $key, bool $useSvg = true): ISimpleFile { $finalIconFile->setImageFormat('png32'); $pngFile = $folder->newFile($key . '.png'); $pngFile->putContent($finalIconFile->getImageBlob()); + return $pngFile; } catch (\ImagickException $e) { $this->logger->info('The image was requested to be no SVG file, but converting it to PNG failed: ' . $e->getMessage()); @@ -127,6 +130,7 @@ public function getCustomImages(): array { 'url' => $this->getImageUrl($key), ]; } + return $images; } @@ -144,6 +148,7 @@ public function getCacheFolder(): ISimpleFolder { $folder = $this->getRootFolder()->newFolder($cacheBusterValue); $this->cleanup(); } + return $folder; } @@ -176,7 +181,9 @@ public function setCachedImage(string $filename, string $data): ISimpleFile { } else { $file = $currentFolder->newFile($filename); } + $file->putContent($data); + return $file; } @@ -188,6 +195,7 @@ public function delete(string $key): void { } catch (NotFoundException $e) { } catch (NotPermittedException $e) { } + try { $file = $this->getRootFolder()->getFolder('images')->getFile($key . '.png'); $file->delete(); @@ -253,6 +261,7 @@ public function updateImage(string $key, string $tmpFile): string { throw new \Exception('Could not recompress background image as PNG'); } } + $tmpFile = $newTmpFile; imagedestroy($outputImage); } catch (\Exception $e) { @@ -304,15 +313,18 @@ private function shouldOptimizeBackgroundImage(string $mimeType, int $contentSiz if (str_contains($mimeType, 'image/svg')) { return false; } + // GIF does not benefit from converting if (str_contains($mimeType, 'image/gif')) { return false; } + // WebP also does not benefit from converting // We could possibly try to convert to progressive image, but normally webP images are quite small if (str_contains($mimeType, 'image/webp')) { return false; } + // As a rule of thumb background images should be max. 150-300 KiB, small images do not benefit from converting return $contentSize > 150000; } @@ -367,13 +379,16 @@ public function shouldReplaceIcons() { if ($value = $cache->get('shouldReplaceIcons')) { return (bool)$value; } + $value = false; if (extension_loaded('imagick')) { if (count(\Imagick::queryFormats('SVG')) >= 1) { $value = true; } } + $cache->set('shouldReplaceIcons', $value); + return $value; } diff --git a/apps/theming/lib/Jobs/MigrateBackgroundImages.php b/apps/theming/lib/Jobs/MigrateBackgroundImages.php index aff13fc2910a0..53103b3e07153 100644 --- a/apps/theming/lib/Jobs/MigrateBackgroundImages.php +++ b/apps/theming/lib/Jobs/MigrateBackgroundImages.php @@ -42,7 +42,7 @@ public function __construct( IJobList $jobList, IDBConnection $dbc, IAppData $appData, - LoggerInterface $logger + LoggerInterface $logger, ) { parent::__construct($time); $this->appDataFactory = $appDataFactory; @@ -54,7 +54,7 @@ public function __construct( protected function run(mixed $argument): void { if (!is_array($argument) || !isset($argument['stage'])) { - throw new \Exception('Job '.self::class.' called with wrong argument'); + throw new \Exception('Job ' . self::class . ' called with wrong argument'); } switch ($argument['stage']) { @@ -85,6 +85,7 @@ protected function runPreparation(): void { $this->jobList->add(self::class, ['stage' => self::STAGE_PREPARE]); throw $t; } + $this->jobList->add(self::class, ['stage' => self::STAGE_EXECUTE]); } @@ -107,6 +108,7 @@ protected function runMigration(): void { if (!$targetDir->fileExists('background.jpg')) { $targetDir->newFile('background.jpg', $file->getContent()); } + $file->delete(); } catch (NotFoundException|NotPermittedException $e) { } @@ -134,12 +136,14 @@ protected function readUserIdsToProcess(): array { } catch (NotFoundException $e) { $userIds = []; } + if ($userIds === null) { $userIds = []; } } else { $userIds = []; } + return $userIds; } @@ -155,6 +159,7 @@ protected function storeUserIdsToProcess(array $userIds): void { } else { $file = $globalFolder->newFile(self::STATE_FILE_NAME); } + $file->putContent($storableUserIds); } catch (NotFoundException $e) { } catch (NotPermittedException $e) { diff --git a/apps/theming/lib/Jobs/RestoreBackgroundImageColor.php b/apps/theming/lib/Jobs/RestoreBackgroundImageColor.php index 1ec659d0646df..1ceff0ae9d7d7 100644 --- a/apps/theming/lib/Jobs/RestoreBackgroundImageColor.php +++ b/apps/theming/lib/Jobs/RestoreBackgroundImageColor.php @@ -42,7 +42,7 @@ public function __construct( protected function run(mixed $argument): void { if (!is_array($argument) || !isset($argument['stage'])) { - throw new \Exception('Job '.self::class.' called with wrong argument'); + throw new \Exception('Job ' . self::class . ' called with wrong argument'); } switch ($argument['stage']) { @@ -69,7 +69,7 @@ protected function runPreparation(): void { // Get those users, that have a background_image set - not the default, but no background_color. $result = $qb->selectDistinct('a.userid') ->from('preferences', 'a') - ->leftJoin('a', $qb->createFunction('('.$innerSQL->getSQL().')'), 'b', 'a.userid = b.userid') + ->leftJoin('a', $qb->createFunction('(' . $innerSQL->getSQL() . ')'), 'b', 'a.userid = b.userid') ->where($qb2->expr()->eq('a.configkey', $qb->createNamedParameter('background_image'))) ->andWhere($qb2->expr()->neq('a.configvalue', $qb->createNamedParameter(BackgroundService::BACKGROUND_DEFAULT))) ->andWhere($qb2->expr()->isNull('b.userid')) @@ -82,6 +82,7 @@ protected function runPreparation(): void { $this->jobList->add(self::class, ['stage' => self::STAGE_PREPARE]); throw $t; } + $this->jobList->add(self::class, ['stage' => self::STAGE_EXECUTE]); } @@ -119,6 +120,7 @@ protected function runMigration(): void { } else { $this->service->setDefaultBackground($userId); } + // Restore primary if ($primary !== '') { $this->config->setUserValue($userId, Application::APP_ID, 'primary_color', $primary); @@ -148,12 +150,14 @@ protected function readUserIdsToProcess(): array { } catch (NotFoundException $e) { $userIds = []; } + if ($userIds === null) { $userIds = []; } } else { $userIds = []; } + return $userIds; } @@ -169,6 +173,7 @@ protected function storeUserIdsToProcess(array $userIds): void { } else { $file = $globalFolder->newFile(self::STATE_FILE_NAME); } + $file->putContent($storableUserIds); } catch (NotFoundException $e) { } catch (NotPermittedException $e) { diff --git a/apps/theming/lib/Listener/BeforePreferenceListener.php b/apps/theming/lib/Listener/BeforePreferenceListener.php index 048deae50cef8..6ab9058d23e3c 100644 --- a/apps/theming/lib/Listener/BeforePreferenceListener.php +++ b/apps/theming/lib/Listener/BeforePreferenceListener.php @@ -63,6 +63,7 @@ private function handleThemingValues(BeforePreferenceSetEvent|BeforePreferenceDe default: $event->setValid(false); } + return; } @@ -93,6 +94,7 @@ private function handleCoreValues(BeforePreferenceSetEvent|BeforePreferenceDelet return; } } + $event->setValid(true); } } diff --git a/apps/theming/lib/Listener/BeforeTemplateRenderedListener.php b/apps/theming/lib/Listener/BeforeTemplateRenderedListener.php index cb72e46360ec2..1d86b471cdfeb 100644 --- a/apps/theming/lib/Listener/BeforeTemplateRenderedListener.php +++ b/apps/theming/lib/Listener/BeforeTemplateRenderedListener.php @@ -35,7 +35,7 @@ public function __construct( ContainerInterface $container, ThemeInjectionService $themeInjectionService, IUserSession $userSession, - IConfig $config + IConfig $config, ) { $this->initialState = $initialState; $this->container = $container; @@ -57,6 +57,7 @@ public function handle(Event $event): void { $uid = $this->userSession->getUser()->getUID(); return $this->config->getUserValue($uid, Application::APP_ID, 'shortcuts_disabled', 'no') === 'yes'; } + return false; }); } diff --git a/apps/theming/lib/Service/BackgroundService.php b/apps/theming/lib/Service/BackgroundService.php index a0158dcb667e1..81999321d6b4b 100644 --- a/apps/theming/lib/Service/BackgroundService.php +++ b/apps/theming/lib/Service/BackgroundService.php @@ -228,6 +228,7 @@ public function setFileBackground($path): void { if ($this->userId === null) { throw new RuntimeException('No currently logged-in user'); } + $userFolder = $this->rootFolder->getUserFolder($this->userId); /** @var File $file */ @@ -236,6 +237,7 @@ public function setFileBackground($path): void { if ($handle === false) { throw new InvalidArgumentException('Invalid image file'); } + $this->getAppDataFolder()->newFile('background.jpg', $handle); $this->recalculateMeanColor(); @@ -257,6 +259,7 @@ public function recalculateMeanColor(?string $userId = null): void { if ($meanColor !== false) { $this->setColorBackground($meanColor); } + $this->config->setUserValue($userId, Application::APP_ID, 'background_image', self::BACKGROUND_CUSTOM); } @@ -272,9 +275,11 @@ public function setShippedBackground(string $filename, ?string $userId = null): if ($userId === null) { throw new RuntimeException('No currently logged-in user'); } + if (!array_key_exists($filename, self::SHIPPED_BACKGROUNDS)) { throw new InvalidArgumentException('The given file name is invalid'); } + $this->setColorBackground(self::SHIPPED_BACKGROUNDS[$filename]['background_color'], $userId); $this->config->setUserValue($userId, Application::APP_ID, 'background_image', $filename); $this->config->setUserValue($userId, Application::APP_ID, 'primary_color', self::SHIPPED_BACKGROUNDS[$filename]['primary_color']); @@ -289,9 +294,11 @@ public function setColorBackground(string $color, ?string $userId = null): void if ($userId === null) { throw new RuntimeException('No currently logged-in user'); } + if (!preg_match('/^#([0-9a-f]{3}|[0-9a-f]{6})$/i', $color)) { throw new InvalidArgumentException('The given color is invalid'); } + $this->config->setUserValue($userId, Application::APP_ID, 'background_color', $color); $this->config->setUserValue($userId, Application::APP_ID, 'background_image', self::BACKGROUND_COLOR); } @@ -300,6 +307,7 @@ public function deleteBackgroundImage(): void { if ($this->userId === null) { throw new RuntimeException('No currently logged-in user'); } + $this->config->setUserValue($this->userId, Application::APP_ID, 'background_image', self::BACKGROUND_COLOR); } @@ -312,6 +320,7 @@ public function getBackground(): ?ISimpleFile { return null; } } + return null; } @@ -321,7 +330,7 @@ public function getBackground(): ?ISimpleFile { * @param resource|string $path * @return string|null The fallback background color - if any */ - public function setGlobalBackground($path): string|null { + public function setGlobalBackground($path): ?string { $image = new \OCP\Image(); $handle = is_resource($path) ? $path : fopen($path, 'rb'); @@ -332,6 +341,7 @@ public function setGlobalBackground($path): string|null { return $meanColor; } } + return null; } @@ -347,7 +357,7 @@ function toHex(int $channel): string { $hex = dechex($channel); return match (strlen($hex)) { 0 => '00', - 1 => '0'.$hex, + 1 => '0' . $hex, 2 => $hex, default => 'ff', }; @@ -380,14 +390,17 @@ function toHex(int $channel): string { if ($value === false) { continue; } + $reds[] = ($value >> 16) & 0xFF; $greens[] = ($value >> 8) & 0xFF; $blues[] = $value & 0xFF; } } + $meanColor = '#' . toHex((int)(array_sum($reds) / count($reds))); $meanColor .= toHex((int)(array_sum($greens) / count($greens))); $meanColor .= toHex((int)(array_sum($blues) / count($blues))); + return $meanColor; } @@ -408,6 +421,7 @@ private function getAppDataFolder(?string $userId = null): ISimpleFolder { } catch (NotFoundException $e) { $rootFolder = $this->appData->newFolder('users'); } + try { return $rootFolder->getFolder($userId); } catch (NotFoundException $e) { diff --git a/apps/theming/lib/Service/ThemeInjectionService.php b/apps/theming/lib/Service/ThemeInjectionService.php index 3c7bf1ce544cc..ff8cf3385d595 100644 --- a/apps/theming/lib/Service/ThemeInjectionService.php +++ b/apps/theming/lib/Service/ThemeInjectionService.php @@ -62,6 +62,7 @@ public function injectHeaders(): void { if ($theme->getId() === $this->defaultTheme->getId()) { continue; } + $this->addThemeHeaders($theme, false); } @@ -109,6 +110,7 @@ private function addThemeMetaHeaders(array $themes): void { if (!isset($metaHeaders[$meta['name']])) { $metaHeaders[$meta['name']] = []; } + $metaHeaders[$meta['name']][] = $meta['content']; } } diff --git a/apps/theming/lib/Service/ThemesService.php b/apps/theming/lib/Service/ThemesService.php index 49cd72d4255a9..87938bd0be69c 100644 --- a/apps/theming/lib/Service/ThemesService.php +++ b/apps/theming/lib/Service/ThemesService.php @@ -31,7 +31,8 @@ public function __construct( private DarkTheme $darkTheme, HighContrastTheme $highContrastTheme, DarkHighContrastTheme $darkHighContrastTheme, - DyslexiaFont $dyslexiaFont) { + DyslexiaFont $dyslexiaFont, + ) { // Register themes $this->themesProviders = [ @@ -61,6 +62,7 @@ public function getThemes(): array { $defaultTheme = $this->themesProviders[$this->defaultTheme->getId()]; $darkTheme = $this->themesProviders[$this->darkTheme->getId()]; $theme = $this->themesProviders[$enforcedTheme]; + return [ // Leave the default theme as a fallback $defaultTheme->getId() => $defaultTheme, @@ -124,6 +126,7 @@ public function disableTheme(ITheme $theme): array { if (in_array($theme->getId(), $themesIds)) { $enabledThemes = array_diff($themesIds, [$theme->getId()]); $this->setEnabledThemes($enabledThemes); + return $enabledThemes; } @@ -141,8 +144,10 @@ public function isEnabled(ITheme $theme): bool { if ($user instanceof IUser) { // Using keys as it's faster $themes = $this->getEnabledThemes(); + return in_array($theme->getId(), $themes); } + return false; } diff --git a/apps/theming/lib/Themes/CommonThemeTrait.php b/apps/theming/lib/Themes/CommonThemeTrait.php index 74979770b705c..4033755e4b922 100644 --- a/apps/theming/lib/Themes/CommonThemeTrait.php +++ b/apps/theming/lib/Themes/CommonThemeTrait.php @@ -145,6 +145,7 @@ protected function generateUserBackgroundVariables(): array { if ($this->isDarkVariant && isset($shippedBackground['dark_variant'])) { $backgroundImage = $shippedBackground['dark_variant']; } + $variables['--image-background'] = "url('" . $this->urlGenerator->linkTo(Application::APP_ID, "img/background/$backgroundImage") . "')"; } diff --git a/apps/theming/lib/Themes/HighContrastTheme.php b/apps/theming/lib/Themes/HighContrastTheme.php index 3eee51630ba90..47ee915762cad 100644 --- a/apps/theming/lib/Themes/HighContrastTheme.php +++ b/apps/theming/lib/Themes/HighContrastTheme.php @@ -47,6 +47,7 @@ public function getCSSVariables(): array { $colorInfo = '#006DA8'; $primaryVariables = $this->generatePrimaryVariables($colorMainBackground, $colorMainText, true); + return array_merge( $defaultVariables, $primaryVariables, diff --git a/apps/theming/lib/ThemingDefaults.php b/apps/theming/lib/ThemingDefaults.php index 9be830a61fda9..2a6f8cdb4499f 100644 --- a/apps/theming/lib/ThemingDefaults.php +++ b/apps/theming/lib/ThemingDefaults.php @@ -123,9 +123,10 @@ public function getShortFooter() { $footer = '' . $entity . ''; } else { - $footer = '' .$entity . ''; + $footer = '' . $entity . ''; } } + $footer .= ($slogan !== '' ? ' – ' . $slogan : ''); $links = [ @@ -159,6 +160,7 @@ public function getShortFooter() { $divider = ' · '; } } + if ($legalLinks !== '') { $footer .= '
    ' . $legalLinks . ''; } @@ -277,6 +279,7 @@ public function getLogo($useSvg = true): string { } else { $logo = $this->urlGenerator->imagePath('core', 'logo/logo.png'); } + return $logo . '?v=' . $cacheBusterCounter; } @@ -340,10 +343,10 @@ public function getScssVariables() { 'theming-favicon-mime' => "'" . $this->config->getAppValue('theming', 'faviconMime') . "'" ]; - $variables['image-logo'] = "url('".$this->imageManager->getImageUrl('logo')."')"; - $variables['image-logoheader'] = "url('".$this->imageManager->getImageUrl('logoheader')."')"; - $variables['image-favicon'] = "url('".$this->imageManager->getImageUrl('favicon')."')"; - $variables['image-login-background'] = "url('".$this->imageManager->getImageUrl('background')."')"; + $variables['image-logo'] = "url('" . $this->imageManager->getImageUrl('logo') . "')"; + $variables['image-logoheader'] = "url('" . $this->imageManager->getImageUrl('logoheader') . "')"; + $variables['image-favicon'] = "url('" . $this->imageManager->getImageUrl('favicon') . "')"; + $variables['image-login-background'] = "url('" . $this->imageManager->getImageUrl('background') . "')"; $variables['image-login-plain'] = 'false'; if ($this->appConfig->getValueString(Application::APP_ID, 'primary_color', '') !== '') { @@ -362,6 +365,7 @@ public function getScssVariables() { } $cache->set('getScssVariables', $variables); + return $variables; } @@ -377,15 +381,18 @@ public function replaceImagePath($app, $image) { if ($app === '' || $app === 'files_sharing') { $app = 'core'; } + $cacheBusterValue = $this->config->getAppValue('theming', 'cachebuster', '0'); $route = false; if ($image === 'favicon.ico' && ($this->imageManager->shouldReplaceIcons() || $this->getCustomFavicon() !== null)) { $route = $this->urlGenerator->linkToRoute('theming.Icon.getFavicon', ['app' => $app]); } + if (($image === 'favicon-touch.png' || $image === 'favicon-fb.png') && ($this->imageManager->shouldReplaceIcons() || $this->getCustomFavicon() !== null)) { $route = $this->urlGenerator->linkToRoute('theming.Icon.getTouchIcon', ['app' => $app]); } + if ($image === 'manifest.json') { try { $appPath = $this->appManager->getAppPath($app); @@ -394,8 +401,10 @@ public function replaceImagePath($app, $image) { } } catch (AppPathNotFoundException $e) { } + $route = $this->urlGenerator->linkToRoute('theming.Theming.getManifest', ['app' => $app ]); } + if (str_starts_with($image, 'filetypes/') && file_exists(\OC::$SERVERROOT . '/core/img/' . $image)) { $route = $this->urlGenerator->linkToRoute('theming.Icon.getThemedIcon', ['app' => $app, 'image' => $image]); } @@ -478,6 +487,7 @@ public function undo($setting): string { $file = $this->imageManager->getImage('background'); $returnValue = $this->backgroundService->setGlobalBackground($file->read()) ?? ''; } + break; case 'logo': case 'logoheader': diff --git a/apps/theming/lib/Util.php b/apps/theming/lib/Util.php index 809ff53f5e1af..ffac5b7771fd2 100644 --- a/apps/theming/lib/Util.php +++ b/apps/theming/lib/Util.php @@ -78,6 +78,7 @@ public function elementColor($color, ?bool $brightBackground = null, ?string $ba $color = '#' . Color::hslToHex($hsl); $contrast = $this->colorContrast($color, $blurredBackground); } + return $color; } @@ -135,6 +136,7 @@ public function toHSL(int $red, int $green, int $blue): array { public function calculateLuminance(string $color): float { [$red, $green, $blue] = $this->hexToRGB($color); $hsl = $this->toHSL($red, $green, $blue); + return $hsl[2]; } @@ -158,6 +160,7 @@ public function calculateLuma(string $color): float { }, $rgb); [$red, $green, $blue] = $rgb; + return (0.2126 * $red + 0.7152 * $green + 0.0722 * $blue); } @@ -170,6 +173,7 @@ public function calculateLuma(string $color): float { public function colorContrast(string $color1, string $color2): float { $luminance1 = $this->calculateLuma($color1) + 0.05; $luminance2 = $this->calculateLuma($color2) + 0.05; + return max($luminance1, $luminance2) / min($luminance1, $luminance2); } @@ -189,7 +193,8 @@ public function hexToRGB(string $color): array { */ public function generateRadioButton($color) { $radioButtonIcon = '' . - ''; + ''; + return base64_encode($radioButtonIcon); } @@ -206,6 +211,7 @@ public function getAppIcon($app) { if (file_exists($icon)) { return $icon; } + $icon = $appPath . '/img/app.svg'; if (file_exists($icon)) { return $icon; @@ -221,6 +227,7 @@ public function getAppIcon($app) { } catch (NotFoundException $e) { } } + return \OC::$SERVERROOT . '/core/img/logo/logo.svg'; } @@ -249,18 +256,22 @@ public function getAppImage($app, $image) { if (file_exists($icon)) { return $icon; } + $icon = $appPath . '/img/' . $image . '.svg'; if (file_exists($icon)) { return $icon; } + $icon = $appPath . '/img/' . $image . '.png'; if (file_exists($icon)) { return $icon; } + $icon = $appPath . '/img/' . $image . '.gif'; if (file_exists($icon)) { return $icon; } + $icon = $appPath . '/img/' . $image . '.jpg'; if (file_exists($icon)) { return $icon; @@ -291,6 +302,7 @@ public function isAlreadyThemed() { if ($theme !== '') { return true; } + return false; } @@ -311,6 +323,7 @@ public function getCacheBuster(): string { if (!is_null($user)) { $userId = $user->getUID(); } + $serverVersion = \OC_Util::getVersionString(); $themingAppVersion = $this->appManager->getAppVersion('theming'); $userCacheBuster = ''; @@ -318,7 +331,9 @@ public function getCacheBuster(): string { $userCacheBusterValue = (int)$this->config->getUserValue($userId, 'theming', 'userCacheBuster', '0'); $userCacheBuster = $userId . '_' . $userCacheBusterValue; } + $systemCacheBuster = $this->config->getAppValue('theming', 'cachebuster', '0'); + return substr(sha1($serverVersion . $themingAppVersion . $userCacheBuster . $systemCacheBuster), 0, 8); } } diff --git a/apps/theming/tests/Controller/IconControllerTest.php b/apps/theming/tests/Controller/IconControllerTest.php index 18fa7de1cc846..83554af888456 100644 --- a/apps/theming/tests/Controller/IconControllerTest.php +++ b/apps/theming/tests/Controller/IconControllerTest.php @@ -77,6 +77,7 @@ private function iconFileMock($filename, $data) { $icon->expects($this->any())->method('getName')->willReturn('my name'); $icon->expects($this->any())->method('getMTime')->willReturn(42); $icon->method('getName')->willReturn($filename); + return new SimpleFile($icon); } @@ -95,10 +96,12 @@ public function testGetFaviconDefault(): void { if (!extension_loaded('imagick')) { $this->markTestSkipped('Imagemagick is required for dynamic icon generation.'); } + $checkImagick = new \Imagick(); if (count($checkImagick->queryFormats('SVG')) < 1) { $this->markTestSkipped('No SVG provider present.'); } + $file = $this->iconFileMock('filename', 'filecontent'); $this->imageManager->expects($this->once()) ->method('getImage', false) @@ -145,6 +148,7 @@ public function testGetTouchIconDefault(): void { if (!extension_loaded('imagick')) { $this->markTestSkipped('Imagemagick is required for dynamic icon generation.'); } + $checkImagick = new \Imagick(); if (count($checkImagick->queryFormats('SVG')) < 1) { $this->markTestSkipped('No SVG provider present.'); diff --git a/apps/theming/tests/Controller/ThemingControllerTest.php b/apps/theming/tests/Controller/ThemingControllerTest.php index d69ceefa0e551..6ce808253a0a3 100644 --- a/apps/theming/tests/Controller/ThemingControllerTest.php +++ b/apps/theming/tests/Controller/ThemingControllerTest.php @@ -256,7 +256,7 @@ public function testUploadSVGFaviconWithoutImagemagick(): void { ->method('getUploadedFile') ->with('image') ->willReturn([ - 'tmp_name' => __DIR__ . '/../../../../tests/data/testimagelarge.svg', + 'tmp_name' => __DIR__ . '/../../../../tests/data/testimagelarge.svg', 'type' => 'image/svg', 'name' => 'testimagelarge.svg', 'error' => 0, @@ -297,7 +297,7 @@ public function testUpdateLogoInvalidMimeType(): void { ->method('getUploadedFile') ->with('image') ->willReturn([ - 'tmp_name' => __DIR__ . '/../../../../tests/data/lorem.txt', + 'tmp_name' => __DIR__ . '/../../../../tests/data/lorem.txt', 'type' => 'application/pdf', 'name' => 'logo.pdf', 'error' => 0, @@ -443,7 +443,7 @@ public function testUpdateLogoLoginScreenUploadWithInvalidImage(): void { $tmpLogo = \OC::$server->getTempManager()->getTemporaryFolder() . '/logo.svg'; touch($tmpLogo); - file_put_contents($tmpLogo, file_get_contents(__DIR__ . '/../../../../tests/data/data.zip')); + file_put_contents($tmpLogo, file_get_contents(__DIR__ . '/../../../../tests/data/data.zip')); $this->request ->expects($this->once()) ->method('getParam') diff --git a/apps/theming/tests/IconBuilderTest.php b/apps/theming/tests/IconBuilderTest.php index 942bcf399fa98..cc31e09c768e0 100644 --- a/apps/theming/tests/IconBuilderTest.php +++ b/apps/theming/tests/IconBuilderTest.php @@ -49,10 +49,12 @@ private function checkImagick() { if (!extension_loaded('imagick')) { $this->markTestSkipped('Imagemagick is required for dynamic icon generation.'); } + $checkImagick = new \Imagick(); if (count($checkImagick->queryFormats('SVG')) < 1) { $this->markTestSkipped('No SVG provider present.'); } + if (count($checkImagick->queryFormats('PNG')) < 1) { $this->markTestSkipped('No PNG provider present.'); } @@ -84,7 +86,7 @@ public function testRenderAppIcon($app, $color, $file): void { ->with('global/images') ->willThrowException(new NotFoundException()); - $expectedIcon = new \Imagick(realpath(__DIR__). '/data/' . $file); + $expectedIcon = new \Imagick(realpath(__DIR__) . '/data/' . $file); $icon = $this->iconBuilder->renderAppIcon($app, 512); $this->assertEquals(true, $icon->valid()); @@ -113,7 +115,7 @@ public function testGetTouchIcon($app, $color, $file): void { ->with('global/images') ->willThrowException(new NotFoundException()); - $expectedIcon = new \Imagick(realpath(__DIR__). '/data/' . $file); + $expectedIcon = new \Imagick(realpath(__DIR__) . '/data/' . $file); $icon = new \Imagick(); $icon->readImageBlob($this->iconBuilder->getTouchIcon($app)); @@ -146,7 +148,7 @@ public function testGetFavicon($app, $color, $file): void { ->with('global/images') ->willThrowException(new NotFoundException()); - $expectedIcon = new \Imagick(realpath(__DIR__). '/data/' . $file); + $expectedIcon = new \Imagick(realpath(__DIR__) . '/data/' . $file); $actualIcon = $this->iconBuilder->getFavicon($app); $icon = new \Imagick(); diff --git a/apps/theming/tests/ImageManagerTest.php b/apps/theming/tests/ImageManagerTest.php index 54e4e2a70ee43..df654ba4fe67d 100644 --- a/apps/theming/tests/ImageManagerTest.php +++ b/apps/theming/tests/ImageManagerTest.php @@ -69,10 +69,12 @@ private function checkImagick() { if (!extension_loaded('imagick')) { $this->markTestSkipped('Imagemagick is required for dynamic icon generation.'); } + $checkImagick = new \Imagick(); if (empty($checkImagick->queryFormats('SVG'))) { $this->markTestSkipped('No SVG provider present.'); } + if (empty($checkImagick->queryFormats('PNG'))) { $this->markTestSkipped('No PNG provider present.'); } @@ -285,6 +287,7 @@ private function setupCacheFolder() { ->method('getFolder') ->with('0') ->willReturn($folder); + return $folder; } @@ -299,6 +302,7 @@ public function testCleanup(): void { ->method('getName') ->willReturn("$index"); } + $folders[0]->expects($this->once())->method('delete'); $folders[1]->expects($this->once())->method('delete'); $folders[2]->expects($this->never())->method('delete'); diff --git a/apps/theming/tests/ServicesTest.php b/apps/theming/tests/ServicesTest.php index 3223de0a48969..f2dedc60f8524 100644 --- a/apps/theming/tests/ServicesTest.php +++ b/apps/theming/tests/ServicesTest.php @@ -68,6 +68,7 @@ public function testContainerQuery($service, $expected = null): void { if ($expected === null) { $expected = $service; } + $this->assertTrue($this->container->query($service) instanceof $expected); } } diff --git a/apps/theming/tests/Settings/PersonalTest.php b/apps/theming/tests/Settings/PersonalTest.php index 4630ef48c8a8e..a4c49ad387dec 100644 --- a/apps/theming/tests/Settings/PersonalTest.php +++ b/apps/theming/tests/Settings/PersonalTest.php @@ -225,6 +225,7 @@ private function formatThemeForm(string $themeId): array { $this->initThemes(); $theme = $this->themes[$themeId]; + return [ 'id' => $theme->getId(), 'type' => $theme->getType(), diff --git a/apps/theming/tests/Themes/DefaultThemeTest.php b/apps/theming/tests/Themes/DefaultThemeTest.php index db6a5bf1cfcf0..1bb7f6e501990 100644 --- a/apps/theming/tests/Themes/DefaultThemeTest.php +++ b/apps/theming/tests/Themes/DefaultThemeTest.php @@ -146,7 +146,9 @@ public function testThemindDisabledFallbackCss(): void { $variables = ''; foreach ($this->theme->getCSSVariables() as $variable => $value) { $variables .= " $variable: $value;" . PHP_EOL; - }; + } + +; $css = "\n:root {" . PHP_EOL . "$variables}" . PHP_EOL; $fallbackCss = file_get_contents(__DIR__ . '/../../css/default.css'); diff --git a/apps/theming/tests/ThemingDefaultsTest.php b/apps/theming/tests/ThemingDefaultsTest.php index d8f169e7f3de0..443c1d77c41df 100644 --- a/apps/theming/tests/ThemingDefaultsTest.php +++ b/apps/theming/tests/ThemingDefaultsTest.php @@ -844,6 +844,7 @@ public function testReplaceImagePath($app, $image, $result = 'themingRoute?v=123 ->method('getCacheBuster') ->willReturn('1234abcd'); } + $this->assertEquals($result, $this->template->replaceImagePath($app, $image)); } } diff --git a/apps/twofactor_backupcodes/lib/Activity/Provider.php b/apps/twofactor_backupcodes/lib/Activity/Provider.php index 7bf0db04e77b8..8ad5fc8e17706 100644 --- a/apps/twofactor_backupcodes/lib/Activity/Provider.php +++ b/apps/twofactor_backupcodes/lib/Activity/Provider.php @@ -53,10 +53,12 @@ public function parse($language, IEvent $event, ?IEvent $previousEvent = null): } else { $event->setIcon($this->urlGenerator->getAbsoluteURL($this->urlGenerator->imagePath('core', 'actions/password.svg'))); } + break; default: throw new UnknownActivityException(); } + return $event; } } diff --git a/apps/twofactor_backupcodes/lib/BackgroundJob/RememberBackupCodesJob.php b/apps/twofactor_backupcodes/lib/BackgroundJob/RememberBackupCodesJob.php index 2bf182d59fd68..27f9829bc691e 100644 --- a/apps/twofactor_backupcodes/lib/BackgroundJob/RememberBackupCodesJob.php +++ b/apps/twofactor_backupcodes/lib/BackgroundJob/RememberBackupCodesJob.php @@ -53,6 +53,7 @@ protected function run($argument) { if ($user === null) { // We can't run with an invalid user $this->jobList->remove(self::class, $argument); + return; } @@ -68,6 +69,7 @@ protected function run($argument) { if ($state2fa === false || (isset($providers['backup_codes']) && $providers['backup_codes'] === true)) { // Backup codes already generated lets remove this job $this->jobList->remove(self::class, $argument); + return; } @@ -84,8 +86,10 @@ protected function run($argument) { if (!$user->isEnabled()) { // Don't recreate a notification for a user that can not read it $this->jobList->remove(self::class, $argument); + return; } + $notification->setDateTime($date); $this->notificationManager->notify($notification); } diff --git a/apps/twofactor_backupcodes/lib/Controller/SettingsController.php b/apps/twofactor_backupcodes/lib/Controller/SettingsController.php index 7982ce382c4a1..bb1dd9d53f765 100644 --- a/apps/twofactor_backupcodes/lib/Controller/SettingsController.php +++ b/apps/twofactor_backupcodes/lib/Controller/SettingsController.php @@ -44,6 +44,7 @@ public function __construct($appName, IRequest $request, BackupCodeStorage $stor public function createCodes(): JSONResponse { $user = $this->userSession->getUser(); $codes = $this->storage->createCodes($user); + return new JSONResponse([ 'codes' => $codes, 'state' => $this->storage->getBackupCodesState($user), diff --git a/apps/twofactor_backupcodes/lib/Migration/Version1002Date20170607113030.php b/apps/twofactor_backupcodes/lib/Migration/Version1002Date20170607113030.php index c76fc548cd9ed..99da601c53a01 100644 --- a/apps/twofactor_backupcodes/lib/Migration/Version1002Date20170607113030.php +++ b/apps/twofactor_backupcodes/lib/Migration/Version1002Date20170607113030.php @@ -67,6 +67,7 @@ public function preSchemaChange(IOutput $output, \Closure $schemaClosure, array ->setParameter('used', $row['used'], IQueryBuilder::PARAM_INT) ->execute(); } + $output->finishProgress(); } @@ -85,6 +86,7 @@ public function changeSchema(IOutput $output, \Closure $schemaClosure, array $op $schema->dropTable('twofactor_backup_codes'); return $schema; } + return null; } } diff --git a/apps/twofactor_backupcodes/lib/Provider/BackupCodesProvider.php b/apps/twofactor_backupcodes/lib/Provider/BackupCodesProvider.php index dc47cf0b3230f..1536af85be18a 100644 --- a/apps/twofactor_backupcodes/lib/Provider/BackupCodesProvider.php +++ b/apps/twofactor_backupcodes/lib/Provider/BackupCodesProvider.php @@ -132,6 +132,7 @@ public function isActive(IUser $user): bool { return true; } } + return false; } @@ -143,6 +144,7 @@ public function isActive(IUser $user): bool { public function getPersonalSettings(IUser $user): IPersonalProviderSettings { $state = $this->storage->getBackupCodesState($user); $this->initialStateService->provideInitialState($this->appName, 'state', $state); + return new Personal(); } diff --git a/apps/twofactor_backupcodes/lib/Service/BackupCodeStorage.php b/apps/twofactor_backupcodes/lib/Service/BackupCodeStorage.php index cab2033e94e78..7bc77180b9a6c 100644 --- a/apps/twofactor_backupcodes/lib/Service/BackupCodeStorage.php +++ b/apps/twofactor_backupcodes/lib/Service/BackupCodeStorage.php @@ -92,6 +92,7 @@ public function getBackupCodesState(IUser $user): array { $used++; } }); + return [ 'enabled' => $total > 0, 'total' => $total, @@ -111,9 +112,11 @@ public function validateCode(IUser $user, string $code): bool { if ((int)$dbCode->getUsed() === 0 && $this->hasher->verify($code, $dbCode->getCode())) { $dbCode->setUsed(1); $this->mapper->update($dbCode); + return true; } } + return false; } diff --git a/apps/updatenotification/lib/BackgroundJob/UpdateAvailableNotifications.php b/apps/updatenotification/lib/BackgroundJob/UpdateAvailableNotifications.php index dd6497c7e48ce..aa89f4b8b6785 100644 --- a/apps/updatenotification/lib/BackgroundJob/UpdateAvailableNotifications.php +++ b/apps/updatenotification/lib/BackgroundJob/UpdateAvailableNotifications.php @@ -122,6 +122,7 @@ protected function clearErrorNotifications() { } catch (\InvalidArgumentException $e) { return; } + $this->notificationManager->markProcessed($notification); } @@ -200,6 +201,7 @@ protected function getUsersToNotify(): array { } $this->users = array_values(array_unique($this->users)); + return $this->users; } @@ -217,6 +219,7 @@ protected function deleteOutdatedNotifications($app, $version) { } catch (\InvalidArgumentException $e) { return; } + $this->notificationManager->markProcessed($notification); } diff --git a/apps/updatenotification/lib/Command/Check.php b/apps/updatenotification/lib/Command/Check.php index 75e42904515f1..07dfb9f9b0555 100644 --- a/apps/updatenotification/lib/Command/Check.php +++ b/apps/updatenotification/lib/Command/Check.php @@ -52,11 +52,10 @@ protected function execute(InputInterface $input, OutputInterface $output): int // Server $r = $this->updateChecker->getUpdateState(); if (isset($r['updateAvailable']) && $r['updateAvailable']) { - $output->writeln($r['updateVersionString'] . ' is available. Get more information on how to update at '. $r['updateLink'] . '.'); + $output->writeln($r['updateVersionString'] . ' is available. Get more information on how to update at ' . $r['updateLink'] . '.'); $updatesAvailableCount += 1; } - // Apps $apps = $this->appManager->getInstalledApps(); foreach ($apps as $app) { diff --git a/apps/updatenotification/lib/Controller/APIController.php b/apps/updatenotification/lib/Controller/APIController.php index 6a91b90139b00..04235179cd807 100644 --- a/apps/updatenotification/lib/Controller/APIController.php +++ b/apps/updatenotification/lib/Controller/APIController.php @@ -79,6 +79,7 @@ public function getAppList(string $newVersion): DataResponse { } catch (AppPathNotFoundException $e) { return false; } + return !$this->appManager->isShipped($app) && !isset($this->appsShippedInFutureVersion[$app]); }); @@ -138,6 +139,7 @@ protected function getAppDetails(string $appId): array { $app = $this->appManager->getAppInfo($appId, false, $this->language); /** @var ?string $name */ $name = $app['name']; + return [ 'appId' => $appId, 'appName' => $name ?? $appId, diff --git a/apps/updatenotification/lib/Controller/AdminController.php b/apps/updatenotification/lib/Controller/AdminController.php index 6e7f9935d938c..6e490d634b72d 100644 --- a/apps/updatenotification/lib/Controller/AdminController.php +++ b/apps/updatenotification/lib/Controller/AdminController.php @@ -47,6 +47,7 @@ private function isUpdaterEnabled() { public function setChannel(string $channel): DataResponse { Util::setChannel($channel); $this->appConfig->setValueInt('core', 'lastupdatedat', 0); + return new DataResponse(['status' => 'success', 'data' => ['message' => $this->l10n->t('Channel updated')]]); } diff --git a/apps/updatenotification/lib/Controller/ChangelogController.php b/apps/updatenotification/lib/Controller/ChangelogController.php index 709cd7ef9e5e8..d8b5b7aa91fab 100644 --- a/apps/updatenotification/lib/Controller/ChangelogController.php +++ b/apps/updatenotification/lib/Controller/ChangelogController.php @@ -56,6 +56,7 @@ public function showChangelog(string $app, ?string $version = null): TemplateRes ]); \OCP\Util::addScript($this->appName, 'view-changelog-page'); + return new TemplateResponse($this->appName, 'empty'); } } diff --git a/apps/updatenotification/lib/Manager.php b/apps/updatenotification/lib/Manager.php index f2e75eedfa44f..e6efef214f670 100644 --- a/apps/updatenotification/lib/Manager.php +++ b/apps/updatenotification/lib/Manager.php @@ -34,7 +34,7 @@ public function __construct( * @param ?string $languageCode The language in which to query the changelog (defaults to current user language and fallsback to English) * @return string|null Either the changelog entry or null if no changelog is found */ - public function getChangelog(string $appId, string $version, ?string $languageCode = null): string|null { + public function getChangelog(string $appId, string $version, ?string $languageCode = null): ?string { if ($languageCode === null) { $languageCode = $this->l10NFactory->getUserLanguage($this->currentUser); } @@ -46,6 +46,7 @@ public function getChangelog(string $appId, string $version, ?string $languageCo } $changes = $this->retrieveChangelogEntry($path, $version); + return $changes; } @@ -55,7 +56,7 @@ public function getChangelog(string $appId, string $version, ?string $languageCo * @param string $languageCode The language code to search * @return string|null Either the file path or null if not found */ - public function getChangelogFile(string $appId, string $languageCode): string|null { + public function getChangelogFile(string $appId, string $languageCode): ?string { try { $appPath = $this->appManager->getAppPath($appId); $files = ["CHANGELOG.$languageCode.md", 'CHANGELOG.en.md']; @@ -68,6 +69,7 @@ public function getChangelogFile(string $appId, string $languageCode): string|nu } catch (\Throwable $e) { // ignore and return null below } + return null; } @@ -76,7 +78,7 @@ public function getChangelogFile(string $appId, string $languageCode): string|nu * @param string $path The path to the changlog file * @param string $version The version to query (make sure to only pass in "{major}.{minor}(.{patch}" format) */ - protected function retrieveChangelogEntry(string $path, string $version): string|null { + protected function retrieveChangelogEntry(string $path, string $version): ?string { $matches = []; $content = file_get_contents($path); if ($content === false) { @@ -109,6 +111,7 @@ protected function retrieveChangelogEntry(string $path, string $version): string $offsetChangelogEntry = $matches[0][$index][1]; // Length of the changelog entry (offset of next match - own offset) or null if the whole rest should be considered $lengthChangelogEntry = $index < ($result - 1) ? ($matches[0][$index + 1][1] - $offsetChangelogEntry) : null; + return substr($content, $offsetChangelogEntry, $lengthChangelogEntry); } } diff --git a/apps/updatenotification/lib/Settings/Admin.php b/apps/updatenotification/lib/Settings/Admin.php index 6e39a40ad8d8a..c000b3c3cf995 100644 --- a/apps/updatenotification/lib/Settings/Admin.php +++ b/apps/updatenotification/lib/Settings/Admin.php @@ -35,7 +35,7 @@ public function __construct( private IRegistry $subscriptionRegistry, private IUserManager $userManager, private LoggerInterface $logger, - private IInitialState $initialState + private IInitialState $initialState, ) { } @@ -96,6 +96,7 @@ protected function filterChanges(array $changes): array { if (isset($changes['changelogURL'])) { $filtered['changelogURL'] = $changes['changelogURL']; } + if (!isset($changes['whatsNew'])) { return $filtered; } @@ -107,6 +108,7 @@ protected function filterChanges(array $changes): array { $filtered['whatsNew'] = $changes['whatsNew'][$lang]; return $filtered; } + $iterator->next(); } while ($lang !== 'en' && $iterator->valid()); diff --git a/apps/updatenotification/lib/UpdateChecker.php b/apps/updatenotification/lib/UpdateChecker.php index f15974a031828..27a3bab8cfd7b 100644 --- a/apps/updatenotification/lib/UpdateChecker.php +++ b/apps/updatenotification/lib/UpdateChecker.php @@ -41,9 +41,11 @@ public function getUpdateState(): array { if (strpos($data['web'], 'https://') === 0) { $result['updateLink'] = $data['web']; } + if (strpos($data['url'], 'https://') === 0) { $result['downloadLink'] = $data['url']; } + if (strpos($data['changes'], 'https://') === 0) { try { $result['changes'] = $this->changesCheck->check($data['changes'], $data['version']); diff --git a/apps/updatenotification/tests/BackgroundJob/UpdateAvailableNotificationsTest.php b/apps/updatenotification/tests/BackgroundJob/UpdateAvailableNotificationsTest.php index dccccf1a94025..645a174376a31 100644 --- a/apps/updatenotification/tests/BackgroundJob/UpdateAvailableNotificationsTest.php +++ b/apps/updatenotification/tests/BackgroundJob/UpdateAvailableNotificationsTest.php @@ -63,6 +63,7 @@ protected function getJob(array $methods = []) { $this->versionCheck, ); } + { return $this->getMockBuilder(UpdateAvailableNotifications::class) ->setConstructorArgs([ @@ -382,8 +383,10 @@ public function testGetUsersToNotify(array $groups, array $groupUsers, array $ex ->method('getUsers') ->willReturn($this->getUsers($uids)); } + $groupMap[] = [$gid, $group]; } + $this->groupManager->expects($this->exactly(\count($groups))) ->method('get') ->willReturnMap($groupMap); @@ -443,6 +446,7 @@ protected function getUsers(array $userIds): array { ->willReturn($uid); $users[] = $user; } + return $users; } @@ -455,6 +459,7 @@ protected function getGroup(string $gid) { $group->expects($this->any()) ->method('getGID') ->willReturn($gid); + return $group; } } diff --git a/apps/updatenotification/tests/Notification/NotifierTest.php b/apps/updatenotification/tests/Notification/NotifierTest.php index 1e53b8d4aea24..e7db8e1703833 100644 --- a/apps/updatenotification/tests/Notification/NotifierTest.php +++ b/apps/updatenotification/tests/Notification/NotifierTest.php @@ -60,6 +60,7 @@ protected function getNotifier(array $methods = []) { $this->groupManager ); } + { return $this->getMockBuilder(Notifier::class) ->setConstructorArgs([ diff --git a/apps/updatenotification/tests/Settings/AdminTest.php b/apps/updatenotification/tests/Settings/AdminTest.php index b39cb8fe1271b..064c1dde76ac6 100644 --- a/apps/updatenotification/tests/Settings/AdminTest.php +++ b/apps/updatenotification/tests/Settings/AdminTest.php @@ -127,6 +127,7 @@ public function testGetFormWithUpdate(): void { if ($currentChannel === 'git') { $channels[] = 'git'; } + $this->appConfig ->expects($this->once()) ->method('getValueInt') diff --git a/apps/user_ldap/ajax/clearMappings.php b/apps/user_ldap/ajax/clearMappings.php index bd866769cc844..cc74fa47d22d1 100644 --- a/apps/user_ldap/ajax/clearMappings.php +++ b/apps/user_ldap/ajax/clearMappings.php @@ -44,6 +44,7 @@ function (string $uid) use ($dispatcher): void { $l = \OCP\Util::getL10N('user_ldap'); throw new \Exception($l->t('Failed to clear the mappings.')); } + \OC_JSON::success(); } catch (\Exception $e) { \OC_JSON::error(['message' => $e->getMessage()]); diff --git a/apps/user_ldap/ajax/getConfiguration.php b/apps/user_ldap/ajax/getConfiguration.php index 31372e89c20de..a3913c57bfec8 100644 --- a/apps/user_ldap/ajax/getConfiguration.php +++ b/apps/user_ldap/ajax/getConfiguration.php @@ -18,4 +18,5 @@ // hide password $configuration['ldap_agent_password'] = '**PASSWORD SET**'; } + \OC_JSON::success(['configuration' => $configuration]); diff --git a/apps/user_ldap/ajax/getNewServerConfigPrefix.php b/apps/user_ldap/ajax/getNewServerConfigPrefix.php index 8064ce928333e..092e757c08d6d 100644 --- a/apps/user_ldap/ajax/getNewServerConfigPrefix.php +++ b/apps/user_ldap/ajax/getNewServerConfigPrefix.php @@ -15,7 +15,7 @@ sort($serverConnections); $lk = array_pop($serverConnections); $ln = (int)str_replace('s', '', $lk); -$nk = 's'.str_pad($ln + 1, 2, '0', STR_PAD_LEFT); +$nk = 's' . str_pad($ln + 1, 2, '0', STR_PAD_LEFT); $resultData = ['configPrefix' => $nk]; @@ -28,6 +28,7 @@ $newConfig->setConfiguration($configuration->getDefaults()); $resultData['defaults'] = $configuration->getDefaults(); } + $newConfig->saveConfiguration(); \OC_JSON::success($resultData); diff --git a/apps/user_ldap/ajax/testConfiguration.php b/apps/user_ldap/ajax/testConfiguration.php index 93e4813d8b0e5..615d0c7c00af0 100644 --- a/apps/user_ldap/ajax/testConfiguration.php +++ b/apps/user_ldap/ajax/testConfiguration.php @@ -24,6 +24,7 @@ $conf['ldap_configuration_active'] = '1'; $configurationOk = $connection->setConfiguration($conf); } + if ($configurationOk) { //Configuration is okay /* @@ -51,6 +52,7 @@ exit; } } + \OC_JSON::success(['message' => $l->t('Valid configuration, connection established!')]); } else { diff --git a/apps/user_ldap/ajax/wizard.php b/apps/user_ldap/ajax/wizard.php index 5c2cddc767295..93a367f15f27f 100644 --- a/apps/user_ldap/ajax/wizard.php +++ b/apps/user_ldap/ajax/wizard.php @@ -15,11 +15,13 @@ if (!isset($_POST['action'])) { \OC_JSON::error(['message' => $l->t('No action specified')]); } + $action = (string)$_POST['action']; if (!isset($_POST['ldap_serverconfig_chooser'])) { \OC_JSON::error(['message' => $l->t('No configuration specified')]); } + $prefix = (string)$_POST['ldap_serverconfig_chooser']; $ldapWrapper = new \OCA\User_LDAP\LDAP(); @@ -62,6 +64,7 @@ \OC_JSON::error(['message' => $e->getMessage(), 'code' => $e->getCode()]); exit; } + \OC_JSON::error(); exit; break; @@ -78,6 +81,7 @@ \OC_JSON::error(['message' => $e->getMessage()]); exit; } + \OC_JSON::error(); exit; break; @@ -90,18 +94,21 @@ \OC_JSON::error(['message' => $l->t('No data specified')]); exit; } + if (is_array($key)) { \OC_JSON::error(['message' => $l->t('Invalid data specified')]); exit; } + $cfg = [$key => $val]; $setParameters = []; $configuration->setConfiguration($cfg, $setParameters); if (!in_array($key, $setParameters)) { - \OC_JSON::error(['message' => $l->t($key. + \OC_JSON::error(['message' => $l->t($key . ' Could not set configuration %s', $setParameters[0])]); exit; } + $configuration->saveConfiguration(); //clear the cache on save $connection = new \OCA\User_LDAP\Connection($ldapWrapper, $prefix); diff --git a/apps/user_ldap/lib/Access.php b/apps/user_ldap/lib/Access.php index dab8db20c9915..0d928c562dee0 100644 --- a/apps/user_ldap/lib/Access.php +++ b/apps/user_ldap/lib/Access.php @@ -76,6 +76,7 @@ public function getUserMapper(): AbstractMapping { if (is_null($this->userMapper)) { throw new \Exception('UserMapper was not assigned to this Access instance.'); } + return $this->userMapper; } @@ -95,6 +96,7 @@ public function getGroupMapper(): AbstractMapping { if (is_null($this->groupMapper)) { throw new \Exception('GroupMapper was not assigned to this Access instance.'); } + return $this->groupMapper; } @@ -132,8 +134,10 @@ public function readAttributes(string $dn, array $attrs, string $filter = 'objec 'No LDAP Connector assigned, access impossible for readAttribute.', ['app' => 'user_ldap'] ); + return false; } + $cr = $this->connection->getConnectionResource(); $attrs = array_map( fn (string $attr): string => mb_strtolower($attr, 'UTF-8'), @@ -161,6 +165,7 @@ public function readAttributes(string $dn, array $attrs, string $filter = 'objec } $this->logger->debug('Requested attributes {attrs} not found for ' . $dn, ['app' => 'user_ldap', 'attrs' => $attrs]); + return false; } @@ -181,8 +186,10 @@ public function readAttribute(string $dn, string $attr, string $filter = 'object 'No LDAP Connector assigned, access impossible for readAttribute.', ['app' => 'user_ldap'] ); + return false; } + $cr = $this->connection->getConnectionResource(); $attr = mb_strtolower($attr, 'UTF-8'); // the actual read attribute later may contain parameters on a ranged @@ -228,6 +235,7 @@ public function readAttribute(string $dn, string $attr, string $filter = 'object } while ($isRangeRequest); $this->logger->debug('Requested attribute ' . $attr . ' not found for ' . $dn, ['app' => 'user_ldap']); + return false; } @@ -247,18 +255,22 @@ public function executeRead(string $dn, string|array $attribute, string $filter) //do not throw this message on userExists check, irritates $this->logger->debug('readAttribute failed for DN ' . $dn, ['app' => 'user_ldap']); } + //in case an error occurs , e.g. object does not exist return false; } + if ($attribute === '' && ($filter === 'objectclass=*' || $this->invokeLDAPMethod('countEntries', $rr) === 1)) { $this->logger->debug('readAttribute: ' . $dn . ' found', ['app' => 'user_ldap']); return true; } + $er = $this->invokeLDAPMethod('firstEntry', $rr); if (!$this->ldap->isResource($er)) { //did not match the filter, return false return false; } + //LDAP attributes are not case sensitive $result = \OCP\Util::mb_array_change_key_case( $this->invokeLDAPMethod('getAttributes', $er), MB_CASE_LOWER, 'UTF-8'); @@ -290,6 +302,7 @@ public function extractAttributeValuesFromResult($result, $attribute) { } } } + return $values; } @@ -319,6 +332,7 @@ public function extractRangeData(array $result, string $attribute): array { } } } + return []; } @@ -335,6 +349,7 @@ public function setPassword($userDN, $password) { if ((int)$this->connection->turnOnPasswordChange !== 1) { throw new \Exception('LDAP password changes are disabled.'); } + $cr = $this->connection->getConnectionResource(); try { // try PASSWD extended operation first @@ -359,6 +374,7 @@ private function resemblesDN($attr) { // memberOf is an "operational" attribute, without a definition in any RFC 'memberof' ]; + return in_array($attr, $resemblingAttributes); } @@ -389,16 +405,19 @@ public function getDomainDNFromDN($dn) { //not a valid DN return ''; } + $domainParts = []; $dcFound = false; foreach ($allParts as $part) { if (!$dcFound && str_starts_with($part, 'dc=')) { $dcFound = true; } + if ($dcFound) { $domainParts[] = $part; } } + return implode(',', $domainParts); } @@ -524,6 +543,7 @@ public function dn2ocname($fdn, $ldapName = null, $isUser = true, &$newlyMapped if ($record === false) { $this->logger->debug('Cannot read attributes for ' . $fdn . '. Skipping.', ['filter' => $filter]); $intermediates[($isUser ? 'user-' : 'group-') . $fdn] = true; + return false; } } @@ -539,6 +559,7 @@ public function dn2ocname($fdn, $ldapName = null, $isUser = true, &$newlyMapped } else { //If the UUID can't be detected something is foul. $this->logger->debug('Cannot determine UUID for ' . $fdn . '. Skipping.', ['app' => 'user_ldap']); + return false; } @@ -549,10 +570,12 @@ public function dn2ocname($fdn, $ldapName = null, $isUser = true, &$newlyMapped $this->logger->debug('No or empty username (' . $usernameAttribute . ') for ' . $fdn . '.', ['app' => 'user_ldap']); return false; } + $username = $username[0]; } else { $username = $uuid; } + try { $intName = $this->sanitizeUsername($username); } catch (\InvalidArgumentException $e) { @@ -571,10 +594,13 @@ public function dn2ocname($fdn, $ldapName = null, $isUser = true, &$newlyMapped if (!isset($ldapName[0]) || empty($ldapName[0])) { $this->logger->debug('No or empty name for ' . $fdn . ' with filter ' . $filter . '.', ['app' => 'user_ldap']); $intermediates['group-' . $fdn] = true; + return false; } + $ldapName = $ldapName[0]; } + $intName = $this->sanitizeGroupIDCandidate($ldapName); } @@ -610,12 +636,14 @@ public function dn2ocname($fdn, $ldapName = null, $isUser = true, &$newlyMapped ] ); $newlyMapped = true; + return $altName; } } //if everything else did not help.. $this->logger->info('Could not create unique name for ' . $fdn . '.', ['app' => 'user_ldap']); + return false; } @@ -624,7 +652,7 @@ public function mapAndAnnounceIfApplicable( string $fdn, string $name, string $uuid, - bool $isUser + bool $isUser, ): bool { if ($mapper->map($fdn, $name, $uuid)) { if ($isUser) { @@ -636,8 +664,10 @@ public function mapAndAnnounceIfApplicable( } else { $this->cacheGroupExists($name); } + return true; } + return false; } @@ -680,6 +710,7 @@ private function ldap2NextcloudNames(array $ldapObjects, bool $isUsers): array { $nameAttribute = $this->connection->ldapGroupDisplayName; $sndAttribute = null; } + $nextcloudNames = []; foreach ($ldapObjects as $ldapObject) { @@ -695,6 +726,7 @@ private function ldap2NextcloudNames(array $ldapObjects, bool $isUsers): array { if (is_null($nameByLDAP)) { continue; } + $sndName = $ldapObject[$sndAttribute][0] ?? ''; $this->cacheUserDisplayName($ncName, $nameByLDAP, $sndName); } elseif ($nameByLDAP !== null) { @@ -702,6 +734,7 @@ private function ldap2NextcloudNames(array $ldapObjects, bool $isUsers): array { } } } + return $nextcloudNames; } @@ -757,6 +790,7 @@ public function cacheUserDisplayName(string $ocName, string $displayName, string if ($user === null) { return; } + $displayName = $user->composeAndStoreDisplayName($displayName, $displayName2); $cacheKeyTrunk = 'getDisplayName'; $this->connection->writeToCache($cacheKeyTrunk . $ocName, $displayName); @@ -785,8 +819,10 @@ private function _createAltInternalOwnCloudNameForUsers(string $name) { if (!$this->ncUserManager->userExists($altName)) { return $altName; } + $attempts++; } + return false; } @@ -812,6 +848,7 @@ private function _createAltInternalOwnCloudNameForGroups(string $name) { $lastName = array_pop($usedNames); $lastNo = (int)substr($lastName, strrpos($lastName, '_') + 1); } + $altName = $name . '_' . (string)($lastNo + 1); unset($usedNames); @@ -823,9 +860,11 @@ private function _createAltInternalOwnCloudNameForGroups(string $name) { if (!\OC::$server->getGroupManager()->groupExists($altName)) { return $altName; } + $altName = $name . '_' . ($lastNo + $attempts); $attempts++; } + return false; } @@ -849,6 +888,7 @@ private function createAltInternalOwnCloudName(string $name, bool $isUser) { } else { $altName = $this->_createAltInternalOwnCloudNameForGroups($name); } + $this->connection->setConfiguration(['ldapCacheTTL' => $originalTTL]); return $altName; @@ -861,6 +901,7 @@ private function createAltInternalOwnCloudName(string $name, bool $isUser) { public function fetchUsersByLoginName(string $loginName, array $attributes = ['dn']): array { $loginName = $this->escapeFilterPart($loginName); $filter = str_replace('%uid', $loginName, $this->connection->ldapLoginFilter); + return $this->fetchListOfUsers($filter, $attributes); } @@ -874,6 +915,7 @@ public function fetchUsersByLoginName(string $loginName, array $attributes = ['d public function countUsersByLoginName($loginName) { $loginName = $this->escapeFilterPart($loginName); $filter = str_replace('%uid', $loginName, $this->connection->ldapLoginFilter); + return $this->countUsers($filter); } @@ -896,13 +938,17 @@ public function fetchListOfUsers(string $filter, array $attr, ?int $limit = null if ($uid === null) { $uid = $this->dn2ocname($record['dn'][0], null, true, $newlyMapped, $record); } + if (is_string($uid)) { $this->cacheUserExists($uid); } + return ($uid !== false) && ($newlyMapped || $isBackgroundJobModeAjax); }); } + $this->batchApplyUserAttributes($recordsToUpdate); + return $this->fetchList($ldapRecords, $this->manyAttributes($attr)); } @@ -920,10 +966,12 @@ public function batchApplyUserAttributes(array $ldapRecords): void { // displayName is obligatory continue; } + $ocName = $this->dn2ocname($userRecord['dn'][0], null, true); if ($ocName === false) { continue; } + $this->updateUserState($ocName); $user = $this->userManager->get($ocName); if ($user !== null) { @@ -946,6 +994,7 @@ public function fetchListOfGroups(string $filter, array $attr, ?int $limit = nul if (!is_null($listOfGroups)) { return $listOfGroups; } + $groupRecords = $this->searchGroups($filter, $attr, $limit, $offset); $listOfDNs = array_reduce($groupRecords, function ($listOfDNs, $entry) { @@ -960,12 +1009,14 @@ public function fetchListOfGroups(string $filter, array $attr, ?int $limit = nul if ($gid === null) { $gid = $this->dn2ocname($record['dn'][0], null, false, $newlyMapped, $record); } + if (!$newlyMapped && is_string($gid)) { $this->cacheGroupExists($gid); } }); $listOfGroups = $this->fetchList($groupRecords, $this->manyAttributes($attr)); $this->connection->writeToCache($cacheKey, $listOfGroups); + return $listOfGroups; } @@ -976,8 +1027,10 @@ private function fetchList(array $list, bool $manyAttributes): array { $list = array_reduce($list, function ($carry, $item) { $attribute = array_keys($item)[0]; $carry[] = $item[$attribute][0]; + return $carry; }, []); + return array_unique($list, SORT_LOCALE_STRING); } } @@ -990,6 +1043,7 @@ public function searchUsers(string $filter, ?array $attr = null, ?int $limit = n foreach ($this->connection->ldapBaseUsers as $base) { $result = array_merge($result, $this->search($filter, $base, $attr, $limit, $offset)); } + return $result; } @@ -1004,6 +1058,7 @@ public function countUsers(string $filter, array $attr = ['dn'], ?int $limit = n $count = $this->count($filter, [$base], $attr, $limit ?? 0, $offset ?? 0); $result = is_int($count) ? (int)$result + $count : $result; } + return $result; } @@ -1020,6 +1075,7 @@ public function searchGroups(string $filter, ?array $attr = null, ?int $limit = foreach ($this->connection->ldapBaseGroups as $base) { $result = array_merge($result, $this->search($filter, $base, $attr, $limit, $offset)); } + return $result; } @@ -1035,6 +1091,7 @@ public function countGroups(string $filter, array $attr = ['dn'], ?int $limit = $count = $this->count($filter, [$base], $attr, $limit ?? 0, $offset ?? 0); $result = is_int($count) ? (int)$result + $count : $result; } + return $result; } @@ -1050,6 +1107,7 @@ public function countObjects(?int $limit = null, ?int $offset = null) { $count = $this->count('objectclass=*', [$base], ['dn'], $limit ?? 0, $offset ?? 0); $result = is_int($count) ? (int)$result + $count : $result; } + return $result; } @@ -1071,9 +1129,11 @@ private function invokeLDAPMethod(string $command, ...$arguments) { // is a reference throw new \InvalidArgumentException('Invoker does not support controlPagedResultResponse, call LDAP Wrapper directly instead.'); } + if (!method_exists($this->ldap, $command)) { return null; } + array_unshift($arguments, $this->connection->getConnectionResource()); $doMethod = function () use ($command, &$arguments) { return call_user_func_array([$this->ldap, $command], $arguments); @@ -1098,6 +1158,7 @@ private function invokeLDAPMethod(string $command, ...$arguments) { $arguments[0] = $cr; $ret = $doMethod(); } + return $ret; } @@ -1118,7 +1179,7 @@ private function executeSearch( string $base, ?array &$attr, ?int $pageSize, - ?int $offset + ?int $offset, ) { // See if we have a resource, in case not cancel with message $cr = $this->connection->getConnectionResource(); @@ -1158,7 +1219,7 @@ private function processPagedSearchStatus( int $foundItems, int $limit, bool $pagedSearchOK, - bool $skipHandling + bool $skipHandling, ): bool { $cookie = ''; if ($pagedSearchOK) { @@ -1171,6 +1232,7 @@ private function processPagedSearchStatus( if ($skipHandling) { return false; } + // if count is bigger, then the server does not support // paged search. Instead, he did a normal search. We set a // flag here, so the callee knows how to deal with it. @@ -1185,6 +1247,7 @@ private function processPagedSearchStatus( ); } } + /* ++ Fixing RHDS searches with pages with zero results ++ * Return cookie status. If we don't have more pages, with RHDS * cookie is null, with openldap cookie is an empty string and @@ -1213,7 +1276,7 @@ private function count( ?array $attr = null, int $limit = 0, int $offset = 0, - bool $skipHandling = false + bool $skipHandling = false, ) { $this->logger->debug('Count filter: {filter}', [ 'app' => 'user_ldap', @@ -1235,6 +1298,7 @@ private function count( if ($search === false) { return $counter > 0 ? $counter : false; } + [$sr, $pagedSearchOK] = $search; /* ++ Fixing RHDS searches with pages with zero results ++ @@ -1278,7 +1342,7 @@ public function search( ?array $attr = null, ?int $limit = null, ?int $offset = null, - bool $skipHandling = false + bool $skipHandling = false, ): array { $limitPerPage = (int)$this->connection->ldapPagingSize; if (!is_null($limit) && $limit < $limitPerPage && $limit > 0) { @@ -1301,6 +1365,7 @@ public function search( if ($search === false) { return []; } + [$sr, $pagedSearchOK] = $search; if ($skipHandling) { @@ -1308,6 +1373,7 @@ public function search( //thus pass 1 or any other value as $iFoundItems because it is not //used $this->processPagedSearchStatus($sr, 1, $limitPerPage, $pagedSearchOK, $skipHandling); + return []; } @@ -1329,12 +1395,14 @@ public function search( if (!is_array($item)) { continue; } + $item = \OCP\Util::mb_array_change_key_case($item, MB_CASE_LOWER, 'UTF-8'); foreach ($attr as $key) { if (isset($item[$key])) { if (is_array($item[$key]) && isset($item[$key]['count'])) { unset($item[$key]['count']); } + if ($key !== 'dn') { if ($this->resemblesDN($key)) { $selection[$i][$key] = $this->helper->sanitizeDN($item[$key]); @@ -1348,10 +1416,13 @@ public function search( } } } + $i++; } + $findings = $selection; } + //we slice the findings, when //a) paged search unsuccessful, though attempted //b) no paged search, but limit set @@ -1364,6 +1435,7 @@ public function search( ) { $findings = array_slice($findings, $offset, $limit); } + return $findings; } @@ -1411,6 +1483,7 @@ public function sanitizeGroupIDCandidate(string $candidate): string { if (strlen($candidate) > 64) { $candidate = hash('sha256', $candidate, false); } + if ($candidate === '') { throw new \InvalidArgumentException('provided name template for username does not contain any allowed characters'); } @@ -1431,6 +1504,7 @@ public function escapeFilterPart($input, $allowAsterisk = false): string { $asterisk = '*'; $input = mb_substr($input, 1, null, 'UTF-8'); } + return $asterisk . ldap_escape($input, '', LDAP_ESCAPE_FILTER); } @@ -1468,9 +1542,12 @@ private function combineFilter(array $filters, string $operator): string { if ($filter !== '' && $filter[0] !== '(') { $filter = '(' . $filter . ')'; } + $combinedFilter .= $filter; } + $combinedFilter .= ')'; + return $combinedFilter; } @@ -1512,6 +1589,7 @@ private function getAdvancedFilterPartForSearch(string $search, $searchAttribute if (!is_array($searchAttributes) || count($searchAttributes) < 2) { throw new DomainException('searchAttributes must be an array with at least two string'); } + $searchWords = explode(' ', trim($search)); $wordFilters = []; foreach ($searchWords as $word) { @@ -1521,8 +1599,10 @@ private function getAdvancedFilterPartForSearch(string $search, $searchAttribute foreach ($searchAttributes as $attr) { $wordMatchOneAttrFilters[] = $attr . '=' . $word; } + $wordFilters[] = $this->combineFilterWithOr($wordMatchOneAttrFilters); } + return $this->combineFilterWithAnd($wordFilters); } @@ -1552,10 +1632,12 @@ private function getFilterPartForSearch(string $search, $searchAttributes, strin if ($fallbackAttribute === '') { return ''; } + // wildcards don't work with some attributes if ($originalSearch !== '') { $filter[] = $fallbackAttribute . '=' . $originalSearch; } + $filter[] = $fallbackAttribute . '=' . $search; } else { foreach ($searchAttributes as $attribute) { @@ -1563,12 +1645,15 @@ private function getFilterPartForSearch(string $search, $searchAttributes, strin if ($originalSearch !== '') { $filter[] = $attribute . '=' . $originalSearch; } + $filter[] = $attribute . '=' . $search; } } + if (count($filter) === 1) { return '(' . $filter[0] . ')'; } + return $this->combineFilterWithOr($filter); } @@ -1588,6 +1673,7 @@ private function prepareSearchTerm(string $term): string { } elseif ($allowEnum !== 'no') { $result = $term . '*'; } + return $result; } @@ -1607,6 +1693,7 @@ public function areCredentialsValid(string $name, string $password): bool { if ($name === '' || $password === '') { return false; } + $name = $this->helper->DNasBaseParameter($name); $testConnection = clone $this->connection; $credentials = [ @@ -1616,6 +1703,7 @@ public function areCredentialsValid(string $name, string $password): bool { if (!$testConnection->setConfiguration($credentials)) { return false; } + return $testConnection->bind(); } @@ -1639,11 +1727,13 @@ public function getUserDnByUuid($uuid) { if (!isset($result[0]) || !isset($result[0]['dn'])) { continue; } + $dn = $result[0]['dn'][0]; if ($hasFound = $this->detectUuidAttribute($dn, true)) { break; } } + if (!isset($hasFound) || !$hasFound) { throw new \Exception('Cannot determine UUID attribute'); } @@ -1727,9 +1817,11 @@ private function detectUuidAttribute(string $dn, bool $isUser = true, bool $forc ); $this->connection->$uuidAttr = $attribute; $this->connection->writeToCache($uuidAttr, $attribute); + return true; } } + $this->logger->debug('Could not autodetect the UUID attribute', ['app' => 'user_ldap']); return false; @@ -1760,6 +1852,7 @@ public function getUUID(string $dn, bool $isUser = true, ?array $ldapRecord = nu ? $ldapRecord[$this->connection->$uuidAttr] : $this->readAttribute($dn, $this->connection->$uuidAttr); } + if (is_array($uuid) && !empty($uuid[0])) { $uuid = $uuid[0]; } @@ -1780,14 +1873,17 @@ private function convertObjectGUID2Str(string $oguid): string { for ($k = 1; $k <= 4; ++$k) { $hex_guid_to_guid_str .= substr($hex_guid, 8 - 2 * $k, 2); } + $hex_guid_to_guid_str .= '-'; for ($k = 1; $k <= 2; ++$k) { $hex_guid_to_guid_str .= substr($hex_guid, 12 - 2 * $k, 2); } + $hex_guid_to_guid_str .= '-'; for ($k = 1; $k <= 2; ++$k) { $hex_guid_to_guid_str .= substr($hex_guid, 16 - 2 * $k, 2); } + $hex_guid_to_guid_str .= '-' . substr($hex_guid, 16, 4); $hex_guid_to_guid_str .= '-' . substr($hex_guid, 20); @@ -1821,17 +1917,21 @@ public function formatGuid2ForFilterUser(string $guid): string { '({uuid}) probably does not match UUID configuration.', ['app' => 'user_ldap', 'uuid' => $guid] ); + return $guid; } + for ($i = 0; $i < 3; $i++) { $pairs = str_split($blocks[$i], 2); $pairs = array_reverse($pairs); $blocks[$i] = implode('', $pairs); } + for ($i = 0; $i < 5; $i++) { $pairs = str_split($blocks[$i], 2); $blocks[$i] = '\\' . implode('\\', $pairs); } + return implode('', $blocks); } @@ -1855,6 +1955,7 @@ public function getSID($dn) { $this->connection->writeToCache($cacheKey, false); return false; } + $domainObjectSid = $this->convertSID2Str($objectSid[0]); $this->connection->writeToCache($cacheKey, $domainObjectSid); @@ -1914,10 +2015,12 @@ public function isDNPartOfBase(string $dn, array $bases): bool { if (mb_strripos($dn, $base, 0, 'UTF-8') !== (mb_strlen($dn, 'UTF-8') - mb_strlen($base, 'UTF-8'))) { $belongsToBase = false; } + if ($belongsToBase) { break; } } + return $belongsToBase; } @@ -1930,6 +2033,7 @@ private function abandonPagedSearch(): void { if ($this->lastCookie === '') { return; } + $this->getPagedSearchResultState(); $this->lastCookie = ''; } @@ -1963,6 +2067,7 @@ public function hasMoreResults() { public function getPagedSearchResultState() { $result = $this->pagedSearchedSuccessful; $this->pagedSearchedSuccessful = null; + return $result; } @@ -1983,7 +2088,7 @@ private function initPagedSearch( string $base, ?array $attr, int $pageSize, - int $offset + int $offset, ): array { $pagedSearchOK = false; if ($pageSize !== 0) { @@ -2012,17 +2117,21 @@ private function initPagedSearch( $reOffset = $offset - $defaultPageSize; $this->search($filter, $base, $attr, $defaultPageSize, $reOffset, true); } + if (!$this->hasMoreResults()) { // when the cookie is reset with != 0 offset, there are no further // results, so stop. throw new NoMoreResults(); } } + if ($this->lastCookie !== '' && $offset === 0) { //since offset = 0, this is a new search. We abandon other searches that might be ongoing. $this->abandonPagedSearch(); } + $this->logger->debug('Ready for a paged search', ['app' => 'user_ldap']); + return [true, $pageSize, $this->lastCookie]; /* ++ Fixing RHDS searches with pages with zero results ++ * We couldn't get paged searches working with our RHDS for login ($limit = 0), @@ -2038,6 +2147,7 @@ private function initPagedSearch( // in case someone set it to 0 … use 500, otherwise no results will // be returned. $pageSize = (int)$this->connection->ldapPagingSize > 0 ? (int)$this->connection->ldapPagingSize : 500; + return [true, $pageSize, $this->lastCookie]; } @@ -2054,6 +2164,7 @@ private function manyAttributes($attr): bool { if (\is_array($attr)) { return \count($attr) > 1; } + return false; } } diff --git a/apps/user_ldap/lib/AppInfo/Application.php b/apps/user_ldap/lib/AppInfo/Application.php index 400315442bb93..e3f6f56152c49 100644 --- a/apps/user_ldap/lib/AppInfo/Application.php +++ b/apps/user_ldap/lib/AppInfo/Application.php @@ -109,7 +109,7 @@ public function boot(IBootContext $context): void { IGroupManager $groupManager, User_Proxy $userBackend, Group_Proxy $groupBackend, - Helper $helper + Helper $helper, ) { $configPrefixes = $helper->getServerConfigurationPrefixes(true); if (count($configPrefixes) > 0) { diff --git a/apps/user_ldap/lib/Command/CheckGroup.php b/apps/user_ldap/lib/Command/CheckGroup.php index c376d0f890aa5..d1860ed87f11d 100644 --- a/apps/user_ldap/lib/Command/CheckGroup.php +++ b/apps/user_ldap/lib/Command/CheckGroup.php @@ -72,6 +72,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $gid = $groupname; } } + /* Search to trigger mapping for new groups */ $this->backend->getGroups($gid); $exists = $this->backend->groupExistsOnLDAP($gid, true); @@ -85,6 +86,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $this->service->handleCreatedGroups([$gid]); } } + return self::SUCCESS; } @@ -94,30 +96,31 @@ protected function execute(InputInterface $input, OutputInterface $output): int $this->backend->getLDAPAccess($gid)->connection->clearCache(); $this->service->handleRemovedGroups([$gid]); } + return self::SUCCESS; } throw new \Exception('The given group is not a recognized LDAP group.'); } catch (\Exception $e) { - $output->writeln('' . $e->getMessage(). ''); + $output->writeln('' . $e->getMessage() . ''); return self::FAILURE; } } public function onGroupCreatedEvent(GroupCreatedEvent $event, OutputInterface $output): void { - $output->writeln('The group '.$event->getGroup()->getGID().' was added to Nextcloud with '.$event->getGroup()->count().' users'); + $output->writeln('The group ' . $event->getGroup()->getGID() . ' was added to Nextcloud with ' . $event->getGroup()->count() . ' users'); } public function onUserAddedEvent(UserAddedEvent $event, OutputInterface $output): void { $user = $event->getUser(); $group = $event->getGroup(); - $output->writeln('The user '.$user->getUID().' was added to group '.$group->getGID().''); + $output->writeln('The user ' . $user->getUID() . ' was added to group ' . $group->getGID() . ''); } public function onUserRemovedEvent(UserRemovedEvent $event, OutputInterface $output): void { $user = $event->getUser(); $group = $event->getGroup(); - $output->writeln('The user '.$user->getUID().' was removed from group '.$group->getGID().''); + $output->writeln('The user ' . $user->getUID() . ' was removed from group ' . $group->getGID() . ''); } /** @@ -129,7 +132,9 @@ protected function groupWasMapped(string $gid): bool { if ($dn !== false) { return true; } + $name = $this->mapping->getNameByDN($gid); + return $name !== false; } diff --git a/apps/user_ldap/lib/Command/CheckUser.php b/apps/user_ldap/lib/Command/CheckUser.php index ebb416c24a6a7..87feffb71abc0 100644 --- a/apps/user_ldap/lib/Command/CheckUser.php +++ b/apps/user_ldap/lib/Command/CheckUser.php @@ -64,6 +64,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $uid = $username; } } + $wasMapped = $this->userWasMapped($uid); $exists = $this->backend->userExistsOnLDAP($uid, true); if ($exists === true) { @@ -71,6 +72,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int if ($input->getOption('update')) { $this->updateUser($uid, $output); } + return self::SUCCESS; } @@ -79,12 +81,13 @@ protected function execute(InputInterface $input, OutputInterface $output): int $output->writeln('The user does not exists on LDAP anymore.'); $output->writeln('Clean up the user\'s remnants by: ./occ user:delete "' . $uid . '"'); + return self::SUCCESS; } throw new \Exception('The given user is not a recognized LDAP user.'); } catch (\Exception $e) { - $output->writeln('' . $e->getMessage(). ''); + $output->writeln('' . $e->getMessage() . ''); return self::FAILURE; } } @@ -127,9 +130,11 @@ private function updateUser(string $uid, OutputInterface $output): void { if (in_array($attribute, $avatarAttributes)) { $value = '{ImageData}'; } + $output->writeln(' ' . $value); } } + $access->batchApplyUserAttributes($result); } catch (\Exception $e) { $output->writeln('Error while trying to lookup and update attributes from LDAP'); diff --git a/apps/user_ldap/lib/Command/CreateEmptyConfig.php b/apps/user_ldap/lib/Command/CreateEmptyConfig.php index 7c381cf431fb4..4b85aa25151f7 100644 --- a/apps/user_ldap/lib/Command/CreateEmptyConfig.php +++ b/apps/user_ldap/lib/Command/CreateEmptyConfig.php @@ -44,7 +44,9 @@ protected function execute(InputInterface $input, OutputInterface $output): int if (!$input->getOption('only-print-prefix')) { $prose = 'Created new configuration with configID '; } + $output->writeln($prose . "{$configPrefix}"); + return self::SUCCESS; } } diff --git a/apps/user_ldap/lib/Command/DeleteConfig.php b/apps/user_ldap/lib/Command/DeleteConfig.php index 7604e229bedbd..1ada367c7b64d 100644 --- a/apps/user_ldap/lib/Command/DeleteConfig.php +++ b/apps/user_ldap/lib/Command/DeleteConfig.php @@ -43,6 +43,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int } $output->writeln("Deleted configuration with configID '{$configPrefix}'"); + return self::SUCCESS; } } diff --git a/apps/user_ldap/lib/Command/PromoteGroup.php b/apps/user_ldap/lib/Command/PromoteGroup.php index bb5362e470047..6fdc3d60a4e25 100644 --- a/apps/user_ldap/lib/Command/PromoteGroup.php +++ b/apps/user_ldap/lib/Command/PromoteGroup.php @@ -22,7 +22,7 @@ class PromoteGroup extends Command { public function __construct( private IGroupManager $groupManager, - private Group_Proxy $backend + private Group_Proxy $backend, ) { parent::__construct(); } @@ -49,6 +49,7 @@ protected function formatGroupName(IGroup $group): string { if ($group->getGID() !== $group->getDisplayName()) { $idLabel = sprintf(' (Group ID: %s)', $group->getGID()); } + return sprintf('%s%s', $group->getDisplayName(), $idLabel); } @@ -73,6 +74,7 @@ protected function promoteGroup(IGroup $group, InputInterface $input, OutputInte $q = new Question(sprintf('Promote %s to the admin group %s(y|N)? ', $this->formatGroupName($group), $demoteLabel)); $input->setOption('yes', $helper->ask($input, $output, $q) === 'y'); } + if ($input->getOption('yes') === true) { $access->connection->setConfiguration(['ldapAdminGroup' => $group->getGID()]); $access->connection->saveConfiguration(); @@ -105,6 +107,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int } $output->writeln('No matching group found'); + return 1; } diff --git a/apps/user_ldap/lib/Command/ResetGroup.php b/apps/user_ldap/lib/Command/ResetGroup.php index 89d3f31f69dfe..deea15df3e789 100644 --- a/apps/user_ldap/lib/Command/ResetGroup.php +++ b/apps/user_ldap/lib/Command/ResetGroup.php @@ -50,16 +50,19 @@ protected function execute(InputInterface $input, OutputInterface $output): int if (!$group instanceof IGroup) { throw new \Exception('Group not found'); } + $backends = $group->getBackendNames(); if (!in_array('LDAP', $backends)) { throw new \Exception('The given group is not a recognized LDAP group.'); } + if ($input->getOption('yes') === false) { /** @var QuestionHelper $helper */ $helper = $this->getHelper('question'); $q = new Question('Delete all local data of this group (y|N)? '); $input->setOption('yes', $helper->ask($input, $output, $q) === 'y'); } + if ($input->getOption('yes') !== true) { throw new \Exception('Reset cancelled by operator'); } @@ -77,10 +80,14 @@ protected function execute(InputInterface $input, OutputInterface $output): int if (isset($pluginManagerSuppressed)) { $this->pluginManager->setSuppressDeletion($pluginManagerSuppressed); } + $output->writeln('' . $e->getMessage() . ''); + return self::FAILURE; } + $output->writeln('Error while resetting group'); + return self::INVALID; } } diff --git a/apps/user_ldap/lib/Command/ResetUser.php b/apps/user_ldap/lib/Command/ResetUser.php index 58dfbf685198f..a9a2f16481e85 100644 --- a/apps/user_ldap/lib/Command/ResetUser.php +++ b/apps/user_ldap/lib/Command/ResetUser.php @@ -51,16 +51,19 @@ protected function execute(InputInterface $input, OutputInterface $output): int if (!$user instanceof IUser) { throw new \Exception('User not found'); } + $backend = $user->getBackend(); if (!$backend instanceof User_Proxy) { throw new \Exception('The given user is not a recognized LDAP user.'); } + if ($input->getOption('yes') === false) { /** @var QuestionHelper $helper */ $helper = $this->getHelper('question'); $q = new Question('Delete all local data of this user (y|N)? '); $input->setOption('yes', $helper->ask($input, $output, $q) === 'y'); } + if ($input->getOption('yes') !== true) { throw new \Exception('Reset cancelled by operator'); } @@ -75,10 +78,14 @@ protected function execute(InputInterface $input, OutputInterface $output): int if (isset($pluginManagerSuppressed)) { $this->pluginManager->setSuppressDeletion($pluginManagerSuppressed); } + $output->writeln('' . $e->getMessage() . ''); + return self::FAILURE; } + $output->writeln('Error while resetting user'); + return self::INVALID; } } diff --git a/apps/user_ldap/lib/Command/Search.php b/apps/user_ldap/lib/Command/Search.php index 4886e0c763855..86dc1d3df1845 100644 --- a/apps/user_ldap/lib/Command/Search.php +++ b/apps/user_ldap/lib/Command/Search.php @@ -69,12 +69,15 @@ protected function validateOffsetAndLimit(int $offset, int $limit): void { if ($limit < 0) { throw new \InvalidArgumentException('limit must be 0 or greater'); } + if ($offset < 0) { throw new \InvalidArgumentException('offset must be 0 or greater'); } + if ($limit === 0 && $offset !== 0) { throw new \InvalidArgumentException('offset must be 0 if limit is also set to 0'); } + if ($offset > 0 && ($offset % $limit !== 0)) { throw new \InvalidArgumentException('offset must be a multiple of limit'); } @@ -106,9 +109,10 @@ protected function execute(InputInterface $input, OutputInterface $output): int $result = $proxy->$getMethod($input->getArgument('search'), $limit, $offset); foreach ($result as $id => $name) { - $line = $name . ($printID ? ' ('.$id.')' : ''); + $line = $name . ($printID ? ' (' . $id . ')' : ''); $output->writeln($line); } + return self::SUCCESS; } } diff --git a/apps/user_ldap/lib/Command/SetConfig.php b/apps/user_ldap/lib/Command/SetConfig.php index 3a2bc4437e302..4600bcb56651c 100644 --- a/apps/user_ldap/lib/Command/SetConfig.php +++ b/apps/user_ldap/lib/Command/SetConfig.php @@ -53,6 +53,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $input->getArgument('configKey'), $input->getArgument('configValue') ); + return self::SUCCESS; } diff --git a/apps/user_ldap/lib/Command/ShowConfig.php b/apps/user_ldap/lib/Command/ShowConfig.php index fa021192ac4bc..b841711b7ddb2 100644 --- a/apps/user_ldap/lib/Command/ShowConfig.php +++ b/apps/user_ldap/lib/Command/ShowConfig.php @@ -62,6 +62,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int } $this->renderConfigs($configIDs, $input, $output); + return self::SUCCESS; } @@ -90,12 +91,14 @@ protected function renderConfigs( if (is_array($value)) { $value = implode(';', $value); } + if ($key === 'ldapAgentPassword' && !$showPassword) { $rows[] = [$key, '***']; } else { $rows[] = [$key, $value]; } } + $table = new Table($output); $table->setHeaders(['Configuration', $id]); $table->setRows($rows); @@ -110,8 +113,10 @@ protected function renderConfigs( $rows[$key] = $value; } } + $configs[$id] = $rows; } + if (!$renderTable) { $this->writeArrayInOutputFormat($input, $output, $configs); } diff --git a/apps/user_ldap/lib/Command/ShowRemnants.php b/apps/user_ldap/lib/Command/ShowRemnants.php index d255aac136896..b9b928824460e 100644 --- a/apps/user_ldap/lib/Command/ShowRemnants.php +++ b/apps/user_ldap/lib/Command/ShowRemnants.php @@ -36,9 +36,11 @@ protected function formatDate(int $timestamp, string $default, bool $showShortDa if (!($timestamp > 0)) { return $default; } + if ($showShortDate) { return date('Y-m-d', $timestamp); } + return $this->dateFormatter->formatDate($timestamp); } @@ -75,6 +77,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $table->setRows($rows); $table->render(); } + return self::SUCCESS; } } diff --git a/apps/user_ldap/lib/Command/TestConfig.php b/apps/user_ldap/lib/Command/TestConfig.php index 77eaac91d8590..788ba49882c09 100644 --- a/apps/user_ldap/lib/Command/TestConfig.php +++ b/apps/user_ldap/lib/Command/TestConfig.php @@ -81,14 +81,17 @@ protected function testConfig(string $configID): int { ])) { return static::CONF_INVALID; } + if (!$connection->bind()) { return static::BINDFAILURE; } + $access = $this->accessFactory->get($connection); $result = $access->countObjects(1); if (!is_int($result) || ($result <= 0)) { return static::SEARCHFAILURE; } + return static::ESTABLISHED; } } diff --git a/apps/user_ldap/lib/Command/UpdateUUID.php b/apps/user_ldap/lib/Command/UpdateUUID.php index 93dcc37bada70..02d4ba54c5ad3 100644 --- a/apps/user_ldap/lib/Command/UpdateUUID.php +++ b/apps/user_ldap/lib/Command/UpdateUUID.php @@ -109,9 +109,11 @@ protected function execute(InputInterface $input, OutputInterface $output): int foreach ($this->handleUpdates($input) as $_) { $progress->advance(); } + $progress->finish(); $output->writeln(''); $this->printReport($output); + return count($this->reports[UuidUpdateReport::UNMAPPED]) === 0 && count($this->reports[UuidUpdateReport::UNREADABLE]) === 0 && count($this->reports[UuidUpdateReport::UNWRITABLE]) === 0 @@ -133,6 +135,7 @@ protected function printReport(OutputInterface $output): void { foreach ($this->reports[UuidUpdateReport::UPDATED] as $report) { $output->writeln(sprintf(' %s had their old UUID %s updated to %s', $report->id, $report->oldUuid, $report->newUuid)); } + $output->writeln(''); } } @@ -148,6 +151,7 @@ protected function printReport(OutputInterface $output): void { $output->writeln(sprintf(' DN: %s', $report->dn)); } } + $output->writeln(''); } @@ -158,6 +162,7 @@ protected function printReport(OutputInterface $output): void { foreach ($this->reports[UuidUpdateReport::UNKNOWN] as $report) { $output->writeln(sprintf(' %s: %s', $report->isUser ? 'User' : 'Group', $report->id)); } + $output->writeln(PHP_EOL . 'Old users can be removed along with their data per occ user:delete.' . PHP_EOL); } } @@ -195,9 +200,11 @@ protected function handleUpdates(InputInterface $input): \Generator { foreach ($this->handleUpdatesByUserId($input->getOption('userId')) as $_) { yield; } + foreach ($this->handleUpdatesByGroupId($input->getOption('groupId')) as $_) { yield; } + foreach ($this->handleUpdatesByDN($input->getOption('dn')) as $_) { yield; } @@ -229,18 +236,22 @@ protected function handleUpdatesByDN(array $dns): \Generator { $userList[] = ['name' => $id, 'uuid' => $uuid]; continue; } + $uuid = $this->groupMapping->getUUIDByDN($dn); if ($uuid) { $id = $this->groupMapping->getNameByDN($dn); $groupList[] = ['name' => $id, 'uuid' => $uuid]; continue; } + $this->reports[UuidUpdateReport::UNMAPPED][] = new UuidUpdateReport('', $dn, true, UuidUpdateReport::UNMAPPED); yield; } + foreach ($this->handleUpdatesByList($this->userMapping, $userList) as $_) { yield; } + foreach ($this->handleUpdatesByList($this->groupMapping, $groupList) as $_) { yield; } @@ -255,10 +266,12 @@ protected function handleUpdatesByEntryId(array $ids, AbstractMapping $mapping): yield; continue; } + // Since we know it was mapped the UUID is populated $uuid = $mapping->getUUIDByDN($dn); $list[] = ['name' => $id, 'uuid' => $uuid]; } + foreach ($this->handleUpdatesByList($mapping, $list) as $_) { yield; } @@ -302,6 +315,7 @@ protected function handleUpdatesByList(AbstractMapping $mapping, array $list): \ $this->reports[UuidUpdateReport::UNWRITABLE][] = new UuidUpdateReport($row['name'], $dn, $isUser, UuidUpdateReport::UNWRITABLE, $row['uuid'], $uuid); } + $this->logger->info('UUID of {id} was updated from {from} to {to}', [ 'appid' => 'user_ldap', @@ -317,6 +331,7 @@ protected function handleUpdatesByList(AbstractMapping $mapping, array $list): \ } else { $this->reports[UuidUpdateReport::UNKNOWN][] = new UuidUpdateReport($row['name'], '', $isUser, UuidUpdateReport::UNKNOWN); } + yield; // null, for it only advances progress counter } } diff --git a/apps/user_ldap/lib/Configuration.php b/apps/user_ldap/lib/Configuration.php index 9de180cad767c..d3bda55b058a8 100644 --- a/apps/user_ldap/lib/Configuration.php +++ b/apps/user_ldap/lib/Configuration.php @@ -199,6 +199,7 @@ public function __get($name) { if (isset($this->config[$name])) { return $this->config[$name]; } + return null; } @@ -241,8 +242,9 @@ public function setConfiguration(array $config, ?array &$applied = null): void { case 'homeFolderNamingRule': $trimmedVal = trim($val); if ($trimmedVal !== '' && !str_contains($val, 'attr:')) { - $val = 'attr:'.$trimmedVal; + $val = 'attr:' . $trimmedVal; } + break; case 'ldapBase': case 'ldapBaseUsers': @@ -257,11 +259,13 @@ public function setConfiguration(array $config, ?array &$applied = null): void { $setMethod = 'setMultiLine'; break; } + $this->$setMethod($key, $val); if (is_array($applied)) { $applied[] = $inputKey; // storing key as index avoids duplication, and as value for simplicity } + $this->unsavedChanges[$key] = $key; } } @@ -274,6 +278,7 @@ public function readConfiguration(): void { //some are determined continue; } + $dbKey = $cta[$key]; switch ($key) { case 'ldapBase': @@ -329,8 +334,10 @@ public function readConfiguration(): void { $readMethod = 'getValue'; break; } + $this->config[$key] = $this->$readMethod($dbKey); } + $this->configRead = true; } } @@ -360,6 +367,7 @@ public function saveConfiguration(): void { if (is_array($value)) { $value = implode("\n", $value); } + break; //following options are not stored but detected, skip them case 'ldapIgnoreNamingRules': @@ -367,15 +375,19 @@ public function saveConfiguration(): void { case 'ldapUuidGroupAttribute': continue 2; } + if (is_null($value)) { $value = ''; } + $changed = true; $this->saveValue($cta[$key], $value); } + if ($changed) { $this->saveValue('_lastChange', (string)time()); } + $this->unsavedChanges = []; } @@ -449,8 +461,9 @@ protected function getValue(string $varName): string { if (is_null($defaults)) { $defaults = $this->getDefaults(); } + return \OC::$server->getConfig()->getAppValue('user_ldap', - $this->configPrefix.$varName, + $this->configPrefix . $varName, $defaults[$varName]); } @@ -464,6 +477,7 @@ protected function setValue(string $varName, $value): void { if (is_string($value)) { $value = trim($value); } + $this->config[$varName] = $value; } @@ -480,9 +494,10 @@ protected function setRawValue(string $varName, $value): void { protected function saveValue(string $varName, string $value): bool { \OC::$server->getConfig()->setAppValue( 'user_ldap', - $this->configPrefix.$varName, + $this->configPrefix . $varName, $value ); + return true; } @@ -645,6 +660,7 @@ public function getConfigTranslationArray(): array { 'ldap_attr_anniversarydate' => 'ldapAttributeAnniversaryDate', 'ldap_attr_pronouns' => 'ldapAttributePronouns', ]; + return $array; } @@ -655,6 +671,7 @@ public function resolveRule(string $rule): array { if ($rule === 'avatar') { return $this->getAvatarAttributes(); } + throw new \RuntimeException('Invalid rule'); } @@ -665,16 +682,20 @@ public function getAvatarAttributes(): array { if ($value === self::AVATAR_PREFIX_NONE) { return []; } + if (str_starts_with($value, self::AVATAR_PREFIX_DATA_ATTRIBUTE)) { $attribute = trim(substr($value, strlen(self::AVATAR_PREFIX_DATA_ATTRIBUTE))); if ($attribute === '') { return $defaultAttributes; } + return [strtolower($attribute)]; } + if ($value !== self::AVATAR_PREFIX_DEFAULT) { \OCP\Server::get(LoggerInterface::class)->warning('Invalid config value to ldapUserAvatarRule; falling back to default.'); } + return $defaultAttributes; } diff --git a/apps/user_ldap/lib/Connection.php b/apps/user_ldap/lib/Connection.php index b74c964eb7247..f1454e8e0ccc9 100644 --- a/apps/user_ldap/lib/Connection.php +++ b/apps/user_ldap/lib/Connection.php @@ -148,6 +148,7 @@ public function __construct(ILDAPWrapper $ldap, string $configPrefix = '', ?stri if ($memcache->isAvailable()) { $this->cache = $memcache->createDistributed(); } + $helper = new Helper(\OC::$server->getConfig(), \OC::$server->getDatabaseConnection()); $this->doNotValidate = !in_array($this->configPrefix, $helper->getServerConfigurationPrefixes()); @@ -170,6 +171,7 @@ public function __clone() { if (count($this->bindResult) !== 0 && $this->bindResult['result'] === true) { $this->bindResult = []; } + $this->ldapConnectionRes = null; $this->dontDestruct = true; } @@ -195,6 +197,7 @@ public function __set($name, $value) { if ($this->configID !== '' && $this->configID !== null) { $this->configuration->saveConfiguration(); } + $this->validateConfiguration(); } } @@ -234,6 +237,7 @@ public function getConnectionResource(): \LDAP\Connection { if (!$this->ldapConnectionRes) { $this->init(); } + if (is_null($this->ldapConnectionRes)) { $this->logger->error( 'No LDAP Connection to server ' . $this->configuration->ldapHost, @@ -241,6 +245,7 @@ public function getConnectionResource(): \LDAP\Connection { ); throw new ServerNotAvailableException('Connection to LDAP server could not be established'); } + return $this->ldapConnectionRes; } @@ -259,11 +264,12 @@ public function resetConnectionResource(): void { * @param string|null $key */ private function getCacheKey($key): string { - $prefix = 'LDAP-'.$this->configID.'-'.$this->configPrefix.'-'; + $prefix = 'LDAP-' . $this->configID . '-' . $this->configPrefix . '-'; if (is_null($key)) { return $prefix; } - return $prefix.hash('sha256', $key); + + return $prefix . hash('sha256', $key); } /** @@ -274,9 +280,11 @@ public function getFromCache($key) { if (!$this->configured) { $this->readConfiguration(); } + if (is_null($this->cache) || !$this->configuration->ldapCacheTTL) { return null; } + $key = $this->getCacheKey($key); return json_decode(base64_decode($this->cache->get($key) ?? ''), true); @@ -294,11 +302,13 @@ public function writeToCache($key, $value, ?int $ttlOverride = null): void { if (!$this->configured) { $this->readConfiguration(); } + if (is_null($this->cache) || !$this->configuration->ldapCacheTTL || !$this->configuration->ldapConfigurationActive) { return; } + $key = $this->getCacheKey($key); $value = base64_encode(json_encode($value)); $ttl = $ttlOverride ?? $this->configuration->ldapCacheTTL; @@ -333,13 +343,13 @@ public function setConfiguration($config, &$setParameters = null): bool { if (is_null($setParameters)) { $setParameters = []; } + $this->doNotValidate = false; $this->configuration->setConfiguration($config, $setParameters); if (count($setParameters) > 0) { $this->configured = $this->validateConfiguration(); } - return $this->configured; } @@ -370,6 +380,7 @@ public function getConfiguration() { } else { $result[$dbkey] = ''; } + break; case 'ldapBase': case 'ldapBaseUsers': @@ -379,12 +390,15 @@ public function getConfiguration() { if (is_array($config[$configkey])) { $result[$dbkey] = implode("\n", $config[$configkey]); break; - } //else follows default + } + + //else follows default // no break default: $result[$dbkey] = $config[$configkey]; } } + return $result; } @@ -410,7 +424,7 @@ private function doSoftValidation(): void { $this->configuration->$effectiveSetting = 'auto'; $this->configuration->saveConfiguration(); $this->logger->info( - 'Illegal value for the '.$effectiveSetting.', reset to autodetect.', + 'Illegal value for the ' . $effectiveSetting . ', reset to autodetect.', ['app' => 'user_ldap'] ); } @@ -478,9 +492,10 @@ private function doCriticalValidation(): bool { $subj = $key; break; } + $configurationOK = false; $this->logger->warning( - $errorStr.'No '.$subj.' given!', + $errorStr . 'No ' . $subj . ' given!', ['app' => 'user_ldap'] ); } @@ -494,7 +509,7 @@ private function doCriticalValidation(): bool { || ($agent !== '' && $pwd === '') ) { $this->logger->warning( - $errorStr.'either no password is given for the user ' . + $errorStr . 'either no password is given for the user ' . 'agent or a password is given, but not an LDAP agent.', ['app' => 'user_ldap'] ); @@ -507,7 +522,7 @@ private function doCriticalValidation(): bool { if (empty($base) && empty($baseUsers) && empty($baseGroups)) { $this->logger->warning( - $errorStr.'Not a single Base DN given.', + $errorStr . 'Not a single Base DN given.', ['app' => 'user_ldap'] ); $configurationOK = false; @@ -516,7 +531,7 @@ private function doCriticalValidation(): bool { if (mb_strpos((string)$this->configuration->ldapLoginFilter, '%uid', 0, 'UTF-8') === false) { $this->logger->warning( - $errorStr.'login filter does not contain %uid place holder.', + $errorStr . 'login filter does not contain %uid place holder.', ['app' => 'user_ldap'] ); $configurationOK = false; @@ -556,17 +571,21 @@ private function establishConnection(): ?bool { if (!$this->configuration->ldapConfigurationActive) { return null; } + static $phpLDAPinstalled = true; if (!$phpLDAPinstalled) { return false; } + if (!$this->ignoreValidation && !$this->configured) { $this->logger->warning( 'Configuration is invalid, cannot connect', ['app' => 'user_ldap'] ); + return false; } + if (!$this->ldapConnectionRes) { if (!$this->ldap->areLDAPFunctionsAvailable()) { $phpLDAPinstalled = false; @@ -577,6 +596,7 @@ private function establishConnection(): ?bool { return false; } + if ($this->configuration->turnOffCertCheck) { if (putenv('LDAPTLS_REQCERT=never')) { $this->logger->debug( @@ -605,13 +625,16 @@ private function establishConnection(): ?bool { $host = $this->configuration->ldapBackgroundHost ?? ''; $port = $this->configuration->ldapBackgroundPort ?? ''; } + $this->doConnect($host, $port); + return $this->bind(); } catch (ServerNotAvailableException $e) { if (!$hasBackupHost) { throw $e; } } + $this->logger->warning( 'Main LDAP not reachable, connecting to backup', [ @@ -634,6 +657,7 @@ private function establishConnection(): ?bool { return $bindStatus; } + return null; } @@ -681,6 +705,7 @@ public function bind() { if (!$this->configuration->ldapConfigurationActive) { return false; } + $cr = $this->ldapConnectionRes; if (!$this->ldap->isResource($cr)) { $cr = $this->getConnectionResource(); @@ -724,6 +749,7 @@ public function bind() { return false; } + return true; } } diff --git a/apps/user_ldap/lib/Controller/ConfigAPIController.php b/apps/user_ldap/lib/Controller/ConfigAPIController.php index 978aa005559f0..126e3e3c70da8 100644 --- a/apps/user_ldap/lib/Controller/ConfigAPIController.php +++ b/apps/user_ldap/lib/Controller/ConfigAPIController.php @@ -33,7 +33,7 @@ public function __construct( Manager $keyManager, private Helper $ldapHelper, private LoggerInterface $logger, - private ConnectionFactory $connectionFactory + private ConnectionFactory $connectionFactory, ) { parent::__construct( $appName, @@ -64,6 +64,7 @@ public function create() { $this->logger->error($e->getMessage(), ['exception' => $e]); throw new OCSException('An issue occurred when creating the new config.'); } + return new DataResponse(['configID' => $configPrefix]); } @@ -220,6 +221,7 @@ public function show($configID, $showPassword = false) { if (!$showPassword) { $data['ldapAgentPassword'] = '***'; } + foreach ($data as $key => $value) { if (is_array($value)) { $value = implode(';', $value); diff --git a/apps/user_ldap/lib/Controller/RenewPasswordController.php b/apps/user_ldap/lib/Controller/RenewPasswordController.php index 2b76858d12798..3ab14bdb45777 100644 --- a/apps/user_ldap/lib/Controller/RenewPasswordController.php +++ b/apps/user_ldap/lib/Controller/RenewPasswordController.php @@ -72,6 +72,7 @@ public function showRenewPasswordForm($user) { if ($this->config->getUserValue($user, 'user_ldap', 'needsPasswordReset') !== 'true') { return new RedirectResponse($this->urlGenerator->linkToRouteAbsolute('core.login.showLoginForm')); } + $parameters = []; $renewPasswordMessages = $this->session->get('renewPasswordMessages'); $errors = []; @@ -79,6 +80,7 @@ public function showRenewPasswordForm($user) { if (is_array($renewPasswordMessages)) { [$errors, $messages] = $renewPasswordMessages; } + $this->session->remove('renewPasswordMessages'); foreach ($errors as $value) { $parameters[$value] = true; @@ -95,6 +97,7 @@ public function showRenewPasswordForm($user) { $parameters['canResetPassword'] = $userObj->canChangePassword(); } } + $parameters['cancelLink'] = $this->urlGenerator->linkToRouteAbsolute('core.login.showLoginForm'); return new TemplateResponse( @@ -115,12 +118,14 @@ public function tryRenewPassword($user, $oldPassword, $newPassword) { if ($this->config->getUserValue($user, 'user_ldap', 'needsPasswordReset') !== 'true') { return new RedirectResponse($this->urlGenerator->linkToRouteAbsolute('core.login.showLoginForm')); } + $args = !is_null($user) ? ['user' => $user] : []; $loginResult = $this->userManager->checkPassword($user, $oldPassword); if ($loginResult === false) { $this->session->set('renewPasswordMessages', [ ['invalidpassword'], [] ]); + return new RedirectResponse($this->urlGenerator->linkToRoute('user_ldap.renewPassword.showRenewPasswordForm', $args)); } @@ -130,6 +135,7 @@ public function tryRenewPassword($user, $oldPassword, $newPassword) { [], [$this->l10n->t('Please login with the new password')] ]); $this->config->setUserValue($user, 'user_ldap', 'needsPasswordReset', 'false'); + return new RedirectResponse($this->urlGenerator->linkToRoute('core.login.showLoginForm', $args)); } else { $this->session->set('renewPasswordMessages', [ @@ -156,6 +162,7 @@ public function showLoginFormInvalidPassword($user) { $this->session->set('loginMessages', [ ['invalidpassword'], [] ]); + return new RedirectResponse($this->urlGenerator->linkToRoute('core.login.showLoginForm', $args)); } } diff --git a/apps/user_ldap/lib/Db/GroupMembershipMapper.php b/apps/user_ldap/lib/Db/GroupMembershipMapper.php index b3d6c31dda651..04e247ff40772 100644 --- a/apps/user_ldap/lib/Db/GroupMembershipMapper.php +++ b/apps/user_ldap/lib/Db/GroupMembershipMapper.php @@ -32,6 +32,7 @@ public function getKnownGroups(): array { $groups = array_column($result->fetchAll(), 'groupid'); $result->closeCursor(); + return $groups; } diff --git a/apps/user_ldap/lib/GroupPluginManager.php b/apps/user_ldap/lib/GroupPluginManager.php index fca9f37d09225..02ccef20e3b4d 100644 --- a/apps/user_ldap/lib/GroupPluginManager.php +++ b/apps/user_ldap/lib/GroupPluginManager.php @@ -41,7 +41,7 @@ public function register(ILDAPGroupPlugin $plugin) { foreach ($this->which as $action => $v) { if ((bool)($respondToActions & $action)) { $this->which[$action] = $plugin; - \OCP\Server::get(LoggerInterface::class)->debug('Registered action '.$action.' to plugin '.get_class($plugin), ['app' => 'user_ldap']); + \OCP\Server::get(LoggerInterface::class)->debug('Registered action ' . $action . ' to plugin ' . get_class($plugin), ['app' => 'user_ldap']); } } } @@ -67,6 +67,7 @@ public function createGroup($gid) { if ($plugin) { return $plugin->createGroup($gid); } + throw new \Exception('No plugin implements createGroup in this LDAP Backend.'); } @@ -80,6 +81,7 @@ public function canDeleteGroup(): bool { public function setSuppressDeletion(bool $value): bool { $old = $this->suppressDeletion; $this->suppressDeletion = $value; + return $old; } @@ -95,8 +97,10 @@ public function deleteGroup(string $gid): bool { if ($this->suppressDeletion) { return false; } + return $plugin->deleteGroup($gid); } + throw new \Exception('No plugin implements deleteGroup in this LDAP Backend.'); } @@ -115,6 +119,7 @@ public function addToGroup($uid, $gid) { if ($plugin) { return $plugin->addToGroup($uid, $gid); } + throw new \Exception('No plugin implements addToGroup in this LDAP Backend.'); } @@ -133,6 +138,7 @@ public function removeFromGroup($uid, $gid) { if ($plugin) { return $plugin->removeFromGroup($uid, $gid); } + throw new \Exception('No plugin implements removeFromGroup in this LDAP Backend.'); } @@ -149,6 +155,7 @@ public function countUsersInGroup($gid, $search = '') { if ($plugin) { return $plugin->countUsersInGroup($gid, $search); } + throw new \Exception('No plugin implements countUsersInGroup in this LDAP Backend.'); } @@ -164,6 +171,7 @@ public function getGroupDetails($gid) { if ($plugin) { return $plugin->getGroupDetails($gid); } + throw new \Exception('No plugin implements getGroupDetails in this LDAP Backend.'); } } diff --git a/apps/user_ldap/lib/Group_LDAP.php b/apps/user_ldap/lib/Group_LDAP.php index d4d67b546d67a..fecb4deb7c3c8 100644 --- a/apps/user_ldap/lib/Group_LDAP.php +++ b/apps/user_ldap/lib/Group_LDAP.php @@ -46,7 +46,7 @@ public function __construct( Access $access, GroupPluginManager $groupPluginManager, IConfig $config, - IUserManager $ncUserManager + IUserManager $ncUserManager, ) { $this->access = $access; $filter = $this->access->connection->ldapGroupFilter; @@ -77,6 +77,7 @@ public function inGroup($uid, $gid): bool { if (!$this->enabled) { return false; } + $cacheKey = 'inGroup' . $uid . ':' . $gid; $inGroup = $this->access->connection->getFromCache($cacheKey); if (!is_null($inGroup)) { @@ -95,6 +96,7 @@ public function inGroup($uid, $gid): bool { $this->cachedGroupMembers[$gid] = $members; $isInGroup = in_array($userDN, $members, true); $this->access->connection->writeToCache($cacheKey, $isInGroup); + return $isInGroup; } @@ -127,6 +129,7 @@ public function inGroup($uid, $gid): bool { $parts = explode('@', $mid); //making sure we get only the uid $mid = $parts[0]; } + $filter = str_replace('%uid', $mid, $this->access->connection->ldapLoginFilter); $filterParts[] = $filter; $bytes += strlen($filter); @@ -154,6 +157,7 @@ public function inGroup($uid, $gid): bool { foreach ($users as $record) { $dns[$record['dn'][0]] = 1; } + $members = array_keys($dns); break; @@ -213,6 +217,7 @@ public function getDynamicGroupMembers(string $dnGroup): array { ); } } + return $dynamicMembers; } @@ -226,6 +231,7 @@ private function _groupMembers(string $dnGroup, array $seen = [], bool &$recursi $recursive = true; return []; } + $seen[$dnGroup] = true; // used extensively in cron job, caching makes sense for nested groups @@ -262,6 +268,7 @@ private function _groupMembers(string $dnGroup, array $seen = [], bool &$recursi $this->access->connection->ldapMatchingRuleInChainState = Configuration::LDAP_SERVER_FEATURE_AVAILABLE; $this->access->connection->saveConfiguration(); $this->access->connection->writeToCache($cacheKey, $result); + return $result; } // when feature availability is unknown, and the result is empty, continue and test with original approach @@ -319,6 +326,7 @@ private function _getGroupDNsFromMemberOf(string $dn, array &$seen = []): array if (isset($seen[$dn])) { return []; } + $seen[$dn] = true; if (isset($this->cachedNestedGroups[$dn])) { @@ -341,6 +349,7 @@ private function _getGroupDNsFromMemberOf(string $dn, array &$seen = []): array // We do not perform array_unique here at it is done in getUserGroups later $this->cachedNestedGroups[$dn] = $allGroups; + return $this->filterValidGroups($allGroups); } @@ -364,6 +373,7 @@ public function gidNumber2Name(string $gid, string $dn) { 'objectClass=posixGroup', $this->access->connection->ldapGidNumber . '=' . $gid ]); + return $this->getNameOfGroup($filter, $cacheKey) ?? false; } @@ -378,6 +388,7 @@ private function getNameOfGroup(string $filter, string $cacheKey) { $this->access->connection->writeToCache($cacheKey, false); return null; } + $dn = $result[0]['dn'][0]; //and now the group name @@ -399,6 +410,7 @@ private function getEntryGidNumber(string $dn, string $attribute) { if (is_array($value) && !empty($value)) { return $value[0]; } + return false; } @@ -423,6 +435,7 @@ public function getUserGidNumber(string $dn) { $this->access->connection->hasGidNumber = false; } } + return $gidNumber; } @@ -441,6 +454,7 @@ private function prepareFilterForUsersHasGidNumber(string $groupDN, string $sear if ($search !== '') { $filterParts[] = $this->access->getFilterPartForUserSearch($search); } + $filterParts[] = $this->access->connection->ldapGidNumber . '=' . $groupID; return $this->access->combineFilterWithAnd($filterParts); @@ -454,7 +468,7 @@ public function getUsersInGidNumber( string $groupDN, string $search = '', ?int $limit = -1, - ?int $offset = 0 + ?int $offset = 0, ): array { try { $filter = $this->prepareFilterForUsersHasGidNumber($groupDN, $search); @@ -464,6 +478,7 @@ public function getUsersInGidNumber( $limit, $offset ); + return $this->access->nextcloudUserNames($users); } catch (ServerNotAvailableException $e) { throw $e; @@ -512,6 +527,7 @@ public function primaryGroupID2Name(string $gid, string $dn) { $this->access->connection->ldapGroupFilter, 'objectsid=' . $domainObjectSid . '-' . $gid ]); + return $this->getNameOfGroup($filter, $cacheKey) ?? false; } @@ -524,6 +540,7 @@ private function getEntryGroupID(string $dn, string $attribute) { if (is_array($value) && !empty($value)) { return $value[0]; } + return false; } @@ -547,6 +564,7 @@ public function getUserPrimaryGroupIDs(string $dn) { $this->access->connection->hasPrimaryGroups = false; } } + return $primaryGroupID; } @@ -565,6 +583,7 @@ private function prepareFilterForUsersInPrimaryGroup(string $groupDN, string $se if ($search !== '') { $filterParts[] = $this->access->getFilterPartForUserSearch($search); } + $filterParts[] = 'primaryGroupID=' . $groupID; return $this->access->combineFilterWithAnd($filterParts); @@ -578,7 +597,7 @@ public function getUsersInPrimaryGroup( string $groupDN, string $search = '', ?int $limit = -1, - ?int $offset = 0 + ?int $offset = 0, ): array { try { $filter = $this->prepareFilterForUsersInPrimaryGroup($groupDN, $search); @@ -588,6 +607,7 @@ public function getUsersInPrimaryGroup( $limit, $offset ); + return $this->access->nextcloudUserNames($users); } catch (ServerNotAvailableException $e) { throw $e; @@ -603,11 +623,12 @@ public function countUsersInPrimaryGroup( string $groupDN, string $search = '', int $limit = -1, - int $offset = 0 + int $offset = 0, ): int { try { $filter = $this->prepareFilterForUsersInPrimaryGroup($groupDN, $search); $users = $this->access->countUsers($filter, ['dn'], $limit, $offset); + return (int)$users; } catch (ServerNotAvailableException $e) { throw $e; @@ -638,11 +659,13 @@ private function isUserOnLDAP(string $uid): bool { if ($ncUser === null) { return false; } + $backend = $ncUser->getBackend(); if ($backend instanceof User_Proxy) { // ignoring cache as safeguard (and we are behind the group cache check anyway) return $backend->userExistsOnLDAP($uid, true); } + return false; } @@ -666,6 +689,7 @@ public function getUserGroups($uid): array { if (!$this->enabled) { return []; } + $ncUid = $uid; $cacheKey = 'getUserGroups' . $uid; @@ -701,6 +725,7 @@ public function getUserGroups($uid): array { if (!isset($dynamicGroup[$dynamicGroupMemberURL][0])) { continue; } + $pos = strpos($dynamicGroup[$dynamicGroupMemberURL][0], '('); if ($pos !== false) { $memberUrlFilter = substr($dynamicGroup[$dynamicGroupMemberURL][0], $pos); @@ -770,6 +795,7 @@ public function getUserGroups($uid): array { } else { $uid = $result[0]; } + break; default: @@ -788,6 +814,7 @@ public function getUserGroups($uid): array { if ($primaryGroup !== false) { $groups[] = $primaryGroup; } + if ($gidGroupName !== false) { $groups[] = $gidGroupName; } @@ -798,6 +825,7 @@ public function getUserGroups($uid): array { // aware of it yet. $groups = $this->getCachedGroupsForUserId($ncUid); $this->access->connection->writeToCache($cacheKey, $groups); + return $groups; } @@ -818,6 +846,7 @@ private function getGroupsByMember(string $dn, array &$seen = []): array { if (isset($seen[$dn])) { return []; } + $seen[$dn] = true; if (isset($this->cachedGroupsByMember[$dn])) { @@ -853,6 +882,7 @@ private function getGroupsByMember(string $dn, array &$seen = []): array { $visibleGroups = $this->filterValidGroups($allGroups); $this->cachedGroupsByMember[$dn] = $visibleGroups; + return $visibleGroups; } @@ -871,9 +901,11 @@ public function usersInGroup($gid, $search = '', $limit = -1, $offset = 0) { if (!$this->enabled) { return []; } + if (!$this->groupExists($gid)) { return []; } + $search = $this->access->escapeFilterPart($search, true); $cacheKey = 'usersInGroup-' . $gid . '-' . $search . '-' . $limit . '-' . $offset; // check for cache of the exact query @@ -885,11 +917,13 @@ public function usersInGroup($gid, $search = '', $limit = -1, $offset = 0) { if ($limit === -1) { $limit = null; } + // check for cache of the query without limit and offset $groupUsers = $this->access->connection->getFromCache('usersInGroup-' . $gid . '-' . $search); if (!is_null($groupUsers)) { $groupUsers = array_slice($groupUsers, $offset, $limit); $this->access->connection->writeToCache($cacheKey, $groupUsers); + return $groupUsers; } @@ -897,6 +931,7 @@ public function usersInGroup($gid, $search = '', $limit = -1, $offset = 0) { if (!$groupDN) { // group couldn't be found, return empty result-set $this->access->connection->writeToCache($cacheKey, []); + return []; } @@ -906,6 +941,7 @@ public function usersInGroup($gid, $search = '', $limit = -1, $offset = 0) { if (!$members && empty($posixGroupUsers) && empty($primaryUsers)) { //in case users could not be retrieved, return empty result set $this->access->connection->writeToCache($cacheKey, []); + return []; } @@ -932,10 +968,12 @@ public function usersInGroup($gid, $search = '', $limit = -1, $offset = 0) { if (empty($ldap_users)) { break; } + $uid = $this->access->dn2username($ldap_users[0]['dn'][0]); if (!$uid) { break; } + $groupUsers[] = $uid; break; default: @@ -950,6 +988,7 @@ public function usersInGroup($gid, $search = '', $limit = -1, $offset = 0) { if ($userExists === false) { break; } + if ($userExists === null || $search !== '') { if (!$this->access->readAttribute($member, $this->access->connection->ldapUserDisplayName, @@ -960,10 +999,13 @@ public function usersInGroup($gid, $search = '', $limit = -1, $offset = 0) { if ($search === '') { $this->access->connection->writeToCache($cacheKey, false); } + break; } + $this->access->connection->writeToCache($cacheKey, true); } + $groupUsers[] = $uid; break; } @@ -997,6 +1039,7 @@ public function countUsersInGroup($gid, $search = '') { if (!$this->enabled || !$this->groupExists($gid)) { return false; } + $groupUsers = $this->access->connection->getFromCache($cacheKey); if (!is_null($groupUsers)) { return $groupUsers; @@ -1006,6 +1049,7 @@ public function countUsersInGroup($gid, $search = '') { if (!$groupDN) { // group couldn't be found, return empty result set $this->access->connection->writeToCache($cacheKey, false); + return false; } @@ -1014,14 +1058,17 @@ public function countUsersInGroup($gid, $search = '') { if (!$members && $primaryUserCount === 0) { //in case users could not be retrieved, return empty result set $this->access->connection->writeToCache($cacheKey, false); + return false; } if ($search === '') { $groupUsers = count($members) + $primaryUserCount; $this->access->connection->writeToCache($cacheKey, $groupUsers); + return $groupUsers; } + $search = $this->access->escapeFilterPart($search, true); $isMemberUid = ($this->ldapGroupMemberAssocAttr === 'memberuid' || @@ -1043,6 +1090,7 @@ public function countUsersInGroup($gid, $search = '') { $parts = explode('@', $member); $member = $parts[0]; } + //we got uids, need to get their DNs to 'translate' them to user names $filter = $this->access->combineFilterWithAnd([ str_replace('%uid', $member, $this->access->connection->ldapLoginFilter), @@ -1052,6 +1100,7 @@ public function countUsersInGroup($gid, $search = '') { if (count($ldap_users) < 1) { continue; } + $groupUsers[] = $this->access->dn2username($ldap_users[0]); } else { //we need to apply the search filter now @@ -1060,6 +1109,7 @@ public function countUsersInGroup($gid, $search = '') { $this->access->getFilterPartForUserSearch($search))) { continue; } + // dn2username will also check if the users belong to the allowed base if ($ncGroupId = $this->access->dn2username($member)) { $groupUsers[] = $ncGroupId; @@ -1091,6 +1141,7 @@ public function getGroups($search = '', $limit = -1, $offset = 0) { if (!$this->enabled) { return []; } + $search = $this->access->escapeFilterPart($search, true); $cacheKey = 'getGroups-' . $search . '-' . $limit . '-' . $offset; @@ -1105,6 +1156,7 @@ public function getGroups($search = '', $limit = -1, $offset = 0) { if ($limit <= 0) { $limit = null; } + $filter = $this->access->combineFilterWithAnd([ $this->access->connection->ldapGroupFilter, $this->access->getFilterPartForGroupSearch($search) @@ -1116,6 +1168,7 @@ public function getGroups($search = '', $limit = -1, $offset = 0) { $ldap_groups = $this->access->nextcloudGroupNames($ldap_groups); $this->access->connection->writeToCache($cacheKey, $ldap_groups); + return $ldap_groups; } @@ -1164,6 +1217,7 @@ public function groupExistsOnLDAP(string $gid, bool $ignoreCache = false): bool } $this->access->connection->writeToCache($cacheKey, true); + return true; } @@ -1181,15 +1235,18 @@ protected function filterValidGroups(array $listOfGroups): array { if (is_array($item) && !isset($item[$this->access->connection->ldapGroupDisplayName][0])) { continue; } + $name = $item[$this->access->connection->ldapGroupDisplayName][0] ?? null; $gid = $this->access->dn2groupname($dn, $name); if (!$gid) { continue; } + if ($this->groupExists($gid)) { $validGroupDNs[$key] = $item; } } + return $validGroupDNs; } @@ -1242,8 +1299,10 @@ public function createGroup($gid) { $this->access->cacheGroupExists($gid); } } + return $dn != null; } + throw new Exception('Could not create group in LDAP backend.'); } @@ -1260,23 +1319,25 @@ public function deleteGroup(string $gid): bool { $this->access->getGroupMapper()->unmap($gid); $this->access->connection->writeToCache('groupExists' . $gid, false); } + return $ret; } // Getting dn, if false the group is not mapped $dn = $this->access->groupname2dn($gid); if (!$dn) { - throw new Exception('Could not delete unknown group '.$gid.' in LDAP backend.'); + throw new Exception('Could not delete unknown group ' . $gid . ' in LDAP backend.'); } if (!$this->groupExists($gid)) { // The group does not exist in the LDAP, remove the mapping $this->access->getGroupMapper()->unmap($gid); $this->access->connection->writeToCache('groupExists' . $gid, false); + return true; } - throw new Exception('Could not delete existing group '.$gid.' in LDAP backend.'); + throw new Exception('Could not delete existing group ' . $gid . ' in LDAP backend.'); } /** @@ -1293,8 +1354,10 @@ public function addToGroup($uid, $gid) { $this->access->connection->clearCache(); unset($this->cachedGroupMembers[$gid]); } + return $ret; } + throw new Exception('Could not add user to group in LDAP backend.'); } @@ -1312,8 +1375,10 @@ public function removeFromGroup($uid, $gid) { $this->access->connection->clearCache(); unset($this->cachedGroupMembers[$gid]); } + return $ret; } + throw new Exception('Could not remove user from group in LDAP backend.'); } @@ -1328,6 +1393,7 @@ public function getGroupDetails($gid) { if ($this->groupPluginManager->implementsActions(GroupInterface::GROUP_DETAILS)) { return $this->groupPluginManager->getGroupDetails($gid); } + throw new Exception('Could not get group details in LDAP backend.'); } @@ -1369,6 +1435,7 @@ public function getDisplayName(string $gid): string { } $this->access->connection->writeToCache($cacheKey, $displayName); + return $displayName; } @@ -1385,9 +1452,11 @@ public function addRelationshipToCaches(string $uid, ?string $dnUser, string $gi if ($dnUser === false || $dnGroup === false) { return; } + if (isset($this->cachedGroupMembers[$gid])) { $this->cachedGroupMembers[$gid] = array_merge($this->cachedGroupMembers[$gid], [$dnUser]); } + unset($this->cachedGroupsByMember[$dnUser]); unset($this->cachedNestedGroups[$gid]); $cacheKey = 'inGroup' . $uid . ':' . $gid; @@ -1396,10 +1465,12 @@ public function addRelationshipToCaches(string $uid, ?string $dnUser, string $gi if (!is_null($data = $this->access->connection->getFromCache($cacheKeyMembers))) { $this->access->connection->writeToCache($cacheKeyMembers, array_merge($data, [$dnUser])); } + $cacheKey = '_groupMembers' . $dnGroup; if (!is_null($data = $this->access->connection->getFromCache($cacheKey))) { $this->access->connection->writeToCache($cacheKey, array_merge($data, [$dnUser])); } + $cacheKey = 'getUserGroups' . $uid; if (!is_null($data = $this->access->connection->getFromCache($cacheKey))) { $this->access->connection->writeToCache($cacheKey, array_merge($data, [$gid])); @@ -1417,10 +1488,12 @@ public function isAdmin(string $uid): bool { if (!$this->enabled) { return false; } + $ldapAdminGroup = $this->access->connection->ldapAdminGroup; if ($ldapAdminGroup === '') { return false; } + return $this->inGroup($uid, $ldapAdminGroup); } } diff --git a/apps/user_ldap/lib/Group_Proxy.php b/apps/user_ldap/lib/Group_Proxy.php index 1a78a65e61ec2..2b8d03ab0d196 100644 --- a/apps/user_ldap/lib/Group_Proxy.php +++ b/apps/user_ldap/lib/Group_Proxy.php @@ -77,9 +77,11 @@ protected function walkBackends($id, $method, $parameters) { if (!$this->isSingleBackend()) { $this->writeToCache($cacheKey, $configPrefix); } + return $result; } } + return false; } @@ -113,9 +115,11 @@ protected function callOnLastSeenOn($id, $method, $parameters, $passOnWhen) { $this->writeToCache($cacheKey, null); } } + return $result; } } + return false; } @@ -259,6 +263,7 @@ public function getGroupsDetails(array $gids): array { foreach ($gids as $gid) { $groupData[$gid] = $this->handleRequest($gid, 'getGroupDetails', [$gid]); } + return $groupData; } diff --git a/apps/user_ldap/lib/Helper.php b/apps/user_ldap/lib/Helper.php index 9bca3eec26b72..0b0636869aa4c 100644 --- a/apps/user_ldap/lib/Helper.php +++ b/apps/user_ldap/lib/Helper.php @@ -60,6 +60,7 @@ public function getServerConfigurationPrefixes($activeConfigurations = false): a $len = strlen($key) - strlen($referenceConfigkey); $prefixes[] = substr($key, 0, $len); } + asort($prefixes); return $prefixes; @@ -102,6 +103,7 @@ public function getNextServerConfigurationPrefix() { sort($serverConnections); $lastKey = array_pop($serverConnections); $lastNumber = (int)str_replace('s', '', $lastKey); + return 's' . str_pad((string)($lastNumber + 1), 2, '0', STR_PAD_LEFT); } @@ -146,6 +148,7 @@ public function deleteServerConfiguration($prefix) { } $deletedRows = $query->execute(); + return $deletedRows !== 0; } @@ -209,6 +212,7 @@ public function sanitizeDN($dn) { foreach ($dn as $singleDN) { $result[] = $this->sanitizeDN($singleDN); } + return $result; } diff --git a/apps/user_ldap/lib/Jobs/CleanUp.php b/apps/user_ldap/lib/Jobs/CleanUp.php index 22c878ed067a9..1453701644d21 100644 --- a/apps/user_ldap/lib/Jobs/CleanUp.php +++ b/apps/user_ldap/lib/Jobs/CleanUp.php @@ -50,7 +50,7 @@ class CleanUp extends TimedJob { public function __construct( ITimeFactory $timeFactory, User_Proxy $userBackend, - DeletedUsersIndex $dui + DeletedUsersIndex $dui, ) { parent::__construct($timeFactory); $minutes = \OC::$server->getConfig()->getSystemValue( @@ -113,6 +113,7 @@ public function run($argument): void { if (!$this->isCleanUpAllowed()) { return; } + $users = $this->mapping->getList($this->getOffset(), $this->getChunkSize()); $resetOffset = $this->isOffsetResetNecessary(count($users)); $this->checkUsers($users); @@ -197,6 +198,7 @@ public function getChunkSize(): int { if ($this->limit === null) { $this->limit = (int)$this->ocConfig->getAppValue('user_ldap', 'cleanUpJobChunkSize', '50'); } + return $this->limit; } } diff --git a/apps/user_ldap/lib/Jobs/Sync.php b/apps/user_ldap/lib/Jobs/Sync.php index 26888ae96aeb1..6f65afc793ec1 100644 --- a/apps/user_ldap/lib/Jobs/Sync.php +++ b/apps/user_ldap/lib/Jobs/Sync.php @@ -87,6 +87,7 @@ protected function getMinPagingSize(): int { $pagingSize = $this->config->getAppValue('user_ldap', $configKey, $minPagingSize); $minPagingSize = $minPagingSize === null ? $pagingSize : min($minPagingSize, $pagingSize); } + return (int)$minPagingSize; } @@ -121,6 +122,7 @@ public function run($argument) { } else { $this->determineNextCycle($cycleData); } + $this->updateInterval(); } catch (ServerNotAvailableException $e) { $this->determineNextCycle($cycleData); @@ -152,6 +154,7 @@ public function runCycle(array $cycleData): bool { if ((int)$connection->ldapPagingSize === 0) { return false; } + return count($results) >= (int)$connection->ldapPagingSize; } @@ -209,6 +212,7 @@ public function determineNextCycle(?array $cycleData = null): ?array { if ($prefix === null) { return null; } + $cycleData['prefix'] = $prefix; $cycleData['offset'] = 0; $this->setCycle(['prefix' => $prefix, 'offset' => 0]); @@ -227,6 +231,7 @@ public function qualifiesToRun(array $cycleData): bool { if ((time() - $lastChange) > 60 * 30) { return true; } + return false; } @@ -250,6 +255,7 @@ protected function getNextPrefix(?string $lastPrefix): ?string { if ($noOfPrefixes === 0) { return null; } + $i = $lastPrefix === null ? false : array_search($lastPrefix, $prefixes, true); if ($i === false) { $i = -1; @@ -260,6 +266,7 @@ protected function getNextPrefix(?string $lastPrefix): ?string { if (!isset($prefixes[$i])) { $i = 0; } + return $prefixes[$i]; } diff --git a/apps/user_ldap/lib/LDAP.php b/apps/user_ldap/lib/LDAP.php index b57cae72436c2..eba634548b2e5 100644 --- a/apps/user_ldap/lib/LDAP.php +++ b/apps/user_ldap/lib/LDAP.php @@ -50,10 +50,12 @@ public function connect($host, $port) { $host = 'ldap://' . $host; $pos = 4; } + if (strpos($host, ':', $pos + 1) === false && !empty($port)) { //ldap_connect ignores port parameter when URLs are passed $host .= ':' . $port; } + return $this->invokeLDAPMethod('connect', $host); } @@ -78,6 +80,7 @@ public function controlPagedResultResponse($link, $result, &$cookie): bool { if ($errorCode !== 0) { $this->processLDAPError($link, 'ldap_parse_result', $errorCode, $errorMsg); } + if ($this->dataCollector !== null) { $this->dataCollector->stopLastLdapRequest(); } @@ -183,13 +186,16 @@ public function search($link, $baseDN, $filter, $attr, $attrsOnly = 0, $limit = if (str_contains($message, 'Partial search results returned: Sizelimit exceeded')) { return true; } + $oldHandler($no, $message, $file, $line); + return true; }); try { $result = $this->invokeLDAPMethod('search', $link, $baseDN, $filter, $attr, $attrsOnly, $limit, -1, LDAP_DEREF_NEVER, $serverControls); restore_error_handler(); + return $result; } catch (\Exception $e) { restore_error_handler(); @@ -283,11 +289,14 @@ protected function invokeLDAPMethod(string $func, ...$arguments) { if ($this->isResultFalse($func, $result)) { $this->postFunctionCall($func); } + if ($this->dataCollector !== null) { $this->dataCollector->stopLastLdapRequest(); } + return $result; } + return null; } @@ -312,9 +321,11 @@ private function preFunctionCall(string $functionName, array $args): void { if ($this->isResource($item)) { return '(resource)'; } + if (isset($item[0]['value']['cookie']) && $item[0]['value']['cookie'] !== '') { $item[0]['value']['cookie'] = '*opaque cookie*'; } + return $item; }, $this->curArgs); @@ -390,6 +401,7 @@ private function postFunctionCall(string $functionName): void { if ($errorCode === 0) { return; } + $errorMsg = ldap_error($resource); $this->processLDAPError($resource, $functionName, $errorCode, $errorMsg); diff --git a/apps/user_ldap/lib/LDAPProvider.php b/apps/user_ldap/lib/LDAPProvider.php index a6634382fa1f4..b44c2c4311c62 100644 --- a/apps/user_ldap/lib/LDAPProvider.php +++ b/apps/user_ldap/lib/LDAPProvider.php @@ -36,15 +36,16 @@ public function __construct(IServerContainer $serverContainer, Helper $helper, D $userBackendFound = false; $groupBackendFound = false; foreach ($serverContainer->getUserManager()->getBackends() as $backend) { - $this->logger->debug('instance '.get_class($backend).' user backend.', ['app' => 'user_ldap']); + $this->logger->debug('instance ' . get_class($backend) . ' user backend.', ['app' => 'user_ldap']); if ($backend instanceof IUserLDAP) { $this->userBackend = $backend; $userBackendFound = true; break; } } + foreach ($serverContainer->getGroupManager()->getBackends() as $backend) { - $this->logger->debug('instance '.get_class($backend).' group backend.', ['app' => 'user_ldap']); + $this->logger->debug('instance ' . get_class($backend) . ' group backend.', ['app' => 'user_ldap']); if ($backend instanceof IGroupLDAP) { $this->groupBackend = $backend; $groupBackendFound = true; @@ -67,10 +68,12 @@ public function getUserDN($uid) { if (!$this->userBackend->userExists($uid)) { throw new \Exception('User id not found in LDAP'); } + $result = $this->userBackend->getLDAPAccess($uid)->username2dn($uid); if (!$result) { throw new \Exception('Translation to LDAP DN unsuccessful'); } + return $result; } @@ -84,10 +87,12 @@ public function getGroupDN($gid) { if (!$this->groupBackend->groupExists($gid)) { throw new \Exception('Group id not found in LDAP'); } + $result = $this->groupBackend->getLDAPAccess($gid)->groupname2dn($gid); if (!$result) { throw new \Exception('Translation to LDAP DN unsuccessful'); } + return $result; } @@ -103,6 +108,7 @@ public function getUserName($dn) { if (!$result) { throw new \Exception('Translation to internal user name unsuccessful'); } + return $result; } @@ -135,6 +141,7 @@ public function getLDAPConnection($uid) { if (!$this->userBackend->userExists($uid)) { throw new \Exception('User id not found in LDAP'); } + return $this->userBackend->getNewLDAPConnection($uid); } @@ -149,6 +156,7 @@ public function getGroupLDAPConnection($gid) { if (!$this->groupBackend->groupExists($gid)) { throw new \Exception('Group id not found in LDAP'); } + return $this->groupBackend->getNewLDAPConnection($gid); } @@ -162,6 +170,7 @@ public function getLDAPBaseUsers($uid) { if (!$this->userBackend->userExists($uid)) { throw new \Exception('User id not found in LDAP'); } + $access = $this->userBackend->getLDAPAccess($uid); $bases = $access->getConnection()->ldapBaseUsers; $dn = $this->getUserDN($uid); @@ -170,6 +179,7 @@ public function getLDAPBaseUsers($uid) { return $base; } } + // should not occur, because the user does not qualify to use NC in this case $this->logger->info( 'No matching user base found for user {dn}, available: {bases}.', @@ -179,6 +189,7 @@ public function getLDAPBaseUsers($uid) { 'bases' => $bases, ] ); + return array_shift($bases); } @@ -192,7 +203,9 @@ public function getLDAPBaseGroups($uid) { if (!$this->userBackend->userExists($uid)) { throw new \Exception('User id not found in LDAP'); } + $bases = $this->userBackend->getLDAPAccess($uid)->getConnection()->ldapBaseGroups; + return array_shift($bases); } @@ -205,6 +218,7 @@ public function clearCache($uid) { if (!$this->userBackend->userExists($uid)) { throw new \Exception('User id not found in LDAP'); } + $this->userBackend->getLDAPAccess($uid)->getConnection()->clearCache(); } @@ -218,6 +232,7 @@ public function clearGroupCache($gid) { if (!$this->groupBackend->groupExists($gid)) { throw new \Exception('Group id not found in LDAP'); } + $this->groupBackend->getLDAPAccess($gid)->getConnection()->clearCache(); } @@ -257,6 +272,7 @@ public function getLDAPDisplayNameField($uid) { if (!$this->userBackend->userExists($uid)) { throw new \Exception('User id not found in LDAP'); } + return $this->userBackend->getLDAPAccess($uid)->getConnection()->getConfiguration()['ldap_display_name']; } @@ -270,6 +286,7 @@ public function getLDAPEmailField($uid) { if (!$this->userBackend->userExists($uid)) { throw new \Exception('User id not found in LDAP'); } + return $this->userBackend->getLDAPAccess($uid)->getConnection()->getConfiguration()['ldap_email_attr']; } @@ -283,6 +300,7 @@ public function getLDAPGroupMemberAssoc($gid) { if (!$this->groupBackend->groupExists($gid)) { throw new \Exception('Group id not found in LDAP'); } + return $this->groupBackend->getLDAPAccess($gid)->getConnection()->getConfiguration()['ldap_group_member_assoc_attribute']; } @@ -296,6 +314,7 @@ public function getUserAttribute(string $uid, string $attribute): ?string { if (count($values) === 0) { return null; } + return current($values); } @@ -324,6 +343,7 @@ public function getMultiValueUserAttribute(string $uid, string $attribute): arra } $connection->writeToCache($key, $values); + return $values; } } diff --git a/apps/user_ldap/lib/LoginListener.php b/apps/user_ldap/lib/LoginListener.php index f397f4694d27c..0695336acf284 100644 --- a/apps/user_ldap/lib/LoginListener.php +++ b/apps/user_ldap/lib/LoginListener.php @@ -76,6 +76,7 @@ private function updateGroups(IUser $userObject): void { ); continue; } + try { $this->groupMembershipMapper->insert(GroupMembership::fromParams(['groupid' => $groupId,'userid' => $userId])); } catch (Exception $e) { @@ -90,9 +91,11 @@ private function updateGroups(IUser $userObject): void { ] ); } + /* We failed to insert the groupmembership so we do not want to advertise it */ continue; } + $this->groupBackend->addRelationshipToCaches($userId, null, $groupId); $this->dispatcher->dispatchTyped(new UserAddedEvent($groupObject, $userObject)); $this->logger->info( @@ -104,6 +107,7 @@ private function updateGroups(IUser $userObject): void { ] ); } + foreach ($oldGroups as $groupId) { try { $this->groupMembershipMapper->delete($groupMemberships[$groupId]); @@ -119,9 +123,11 @@ private function updateGroups(IUser $userObject): void { ] ); } + /* We failed to delete the groupmembership so we do not want to advertise it */ continue; } + $groupObject = $this->groupManager->get($groupId); if ($groupObject === null) { $this->logger->error( @@ -134,6 +140,7 @@ private function updateGroups(IUser $userObject): void { ); continue; } + $this->dispatcher->dispatchTyped(new UserRemovedEvent($groupObject, $userObject)); $this->logger->info( 'service "updateGroups" - {user} removed from {group}', diff --git a/apps/user_ldap/lib/Mapping/AbstractMapping.php b/apps/user_ldap/lib/Mapping/AbstractMapping.php index 94ac3c985cae2..cf341c8655216 100644 --- a/apps/user_ldap/lib/Mapping/AbstractMapping.php +++ b/apps/user_ldap/lib/Mapping/AbstractMapping.php @@ -74,6 +74,7 @@ protected function getXbyY($fetchCol, $compareCol, $search) { //having SQL injection at all. throw new \Exception('Invalid Column Name'); } + $query = $this->dbc->prepare(' SELECT `' . $fetchCol . '` FROM `' . $this->getTableName() . '` @@ -84,6 +85,7 @@ protected function getXbyY($fetchCol, $compareCol, $search) { $res = $query->execute([$search]); $data = $res->fetchOne(); $res->closeCursor(); + return $data; } catch (Exception $e) { return false; @@ -102,6 +104,7 @@ protected function modify(IPreparedStatement $statement, $parameters) { $result = $statement->execute($parameters); $updated = $result->rowCount() > 0; $result->closeCursor(); + return $updated; } catch (Exception $e) { return false; @@ -120,6 +123,7 @@ public function getDNByName($name) { if ($dn === false && ($dn = $this->getXbyY('ldap_dn', 'owncloud_name', $name)) !== false) { $this->cache[$dn] = $name; } + return $dn; } @@ -186,6 +190,7 @@ public function getNameByDN($fdn) { if (!isset($this->cache[$fdn])) { $this->cache[$fdn] = $this->getXbyY('owncloud_name', 'ldap_dn_hash', $this->getDNHash($fdn)); } + return $this->cache[$fdn]; } @@ -197,6 +202,7 @@ protected function prepareListOfIdsQuery(array $hashList): IQueryBuilder { $qb->select('owncloud_name', 'ldap_dn_hash', 'ldap_dn') ->from($this->getTableName(false)) ->where($qb->expr()->in('ldap_dn_hash', $qb->createNamedParameter($hashList, IQueryBuilder::PARAM_STR_ARRAY))); + return $qb; } @@ -206,6 +212,7 @@ protected function collectResultsFromListOfIdsQuery(IQueryBuilder $qb, array &$r $results[$entry['ldap_dn']] = $entry['owncloud_name']; $this->cache[$entry['ldap_dn']] = $entry['owncloud_name']; } + $stmt->closeCursor(); } @@ -271,10 +278,12 @@ public function getNamesBySearch(string $search, string $prefixMatch = '', strin } catch (Exception $e) { return []; } + $names = []; while ($row = $res->fetch()) { $names[] = $row['owncloud_name']; } + return $names; } @@ -340,6 +349,7 @@ public function map($fdn, $name, $uuid) { 'dn' => $fdn, ] ); + return false; } @@ -355,6 +365,7 @@ public function map($fdn, $name, $uuid) { if ((bool)$result === true) { $this->cache[$fdn] = $name; } + // insertIfNotExist returns values as int return (bool)$result; } catch (\Exception $e) { @@ -419,9 +430,12 @@ public function clearCb(callable $preCallback, callable $postCallback): bool { if ($isUnmapped = $this->unmap($id)) { $postCallback($id); } + $result = $result && $isUnmapped; } + $cursor->closeCursor(); + return $result; } @@ -437,6 +451,7 @@ public function count(): int { $res = $query->execute(); $count = $res->fetchOne(); $res->closeCursor(); + return (int)$count; } @@ -448,6 +463,7 @@ public function countInvalidated(): int { $res = $query->execute(); $count = $res->fetchOne(); $res->closeCursor(); + return (int)$count; } } diff --git a/apps/user_ldap/lib/Mapping/UserMapping.php b/apps/user_ldap/lib/Mapping/UserMapping.php index 0eeaaef12fd0e..6fd1ff3d9ec5b 100644 --- a/apps/user_ldap/lib/Mapping/UserMapping.php +++ b/apps/user_ldap/lib/Mapping/UserMapping.php @@ -41,14 +41,17 @@ public function map($fdn, $name, $uuid): bool { $request = Server::get(IRequest::class); $isProvisioningApi = \preg_match(self::PROV_API_REGEX, $request->getRequestUri()) === 1; } + if ($isProvisioningApi) { // only throw when prov API is being used, since functionality // should not break for end users (e.g. when sharing). // On direct API usage, e.g. on users page, this is desired. throw $e; } + return false; } + return parent::map($fdn, $name, $uuid); } diff --git a/apps/user_ldap/lib/Migration/GroupMappingMigration.php b/apps/user_ldap/lib/Migration/GroupMappingMigration.php index 437bb202bbae0..a9bfce9aeb29c 100644 --- a/apps/user_ldap/lib/Migration/GroupMappingMigration.php +++ b/apps/user_ldap/lib/Migration/GroupMappingMigration.php @@ -47,6 +47,7 @@ protected function copyGroupMappingData(string $sourceTable, string $destination $insert->executeStatement(); } + $result->closeCursor(); } } diff --git a/apps/user_ldap/lib/Migration/UUIDFix.php b/apps/user_ldap/lib/Migration/UUIDFix.php index 309910cdf0cc6..3f08f99346a6f 100644 --- a/apps/user_ldap/lib/Migration/UUIDFix.php +++ b/apps/user_ldap/lib/Migration/UUIDFix.php @@ -23,6 +23,7 @@ public function run($argument) { // record not found, no prob, continue with the next continue; } + if ($uuid !== $record['uuid']) { $this->mapper->setUUIDbyDN($uuid, $record['dn']); } diff --git a/apps/user_ldap/lib/Migration/UUIDFixInsert.php b/apps/user_ldap/lib/Migration/UUIDFixInsert.php index 52c52190654cc..217019b946289 100644 --- a/apps/user_ldap/lib/Migration/UUIDFixInsert.php +++ b/apps/user_ldap/lib/Migration/UUIDFixInsert.php @@ -67,6 +67,7 @@ public function run(IOutput $output) { if (count($records) === 0) { continue; } + try { $this->jobList->add($jobClass, ['records' => $records]); $offset += $batchSize; diff --git a/apps/user_ldap/lib/Migration/Version1010Date20200630192842.php b/apps/user_ldap/lib/Migration/Version1010Date20200630192842.php index 1464e50e359a9..4e714ac67c8a8 100644 --- a/apps/user_ldap/lib/Migration/Version1010Date20200630192842.php +++ b/apps/user_ldap/lib/Migration/Version1010Date20200630192842.php @@ -89,6 +89,7 @@ public function changeSchema(IOutput $output, Closure $schemaClosure, array $opt ]); $table->setPrimaryKey(['owncloudname']); } + return $schema; } } diff --git a/apps/user_ldap/lib/Migration/Version1120Date20210917155206.php b/apps/user_ldap/lib/Migration/Version1120Date20210917155206.php index 9b6ebbfc5de33..b122450ad13a6 100644 --- a/apps/user_ldap/lib/Migration/Version1120Date20210917155206.php +++ b/apps/user_ldap/lib/Migration/Version1120Date20210917155206.php @@ -83,6 +83,7 @@ protected function handleIDs(string $table, bool $emitHooks) { if ($emitHooks) { $this->emitUnassign($row['owncloud_name'], true); } + $update->setParameter('uuid', $row['directory_uuid']); $update->setParameter('newId', $newId); try { @@ -104,6 +105,7 @@ protected function handleIDs(string $table, bool $emitHooks) { ); } } + $result->closeCursor(); } @@ -112,6 +114,7 @@ protected function getSelectQuery(string $table): IQueryBuilder { $qb->select('owncloud_name', 'directory_uuid') ->from($table) ->where($qb->expr()->gt($qb->func()->octetLength('owncloud_name'), $qb->createNamedParameter('64'), IQueryBuilder::PARAM_INT)); + return $qb; } @@ -120,6 +123,7 @@ protected function getUpdateQuery(string $table): IQueryBuilder { $qb->update($table) ->set('owncloud_name', $qb->createParameter('newId')) ->where($qb->expr()->eq('directory_uuid', $qb->createParameter('uuid'))); + return $qb; } diff --git a/apps/user_ldap/lib/Migration/Version1130Date20211102154716.php b/apps/user_ldap/lib/Migration/Version1130Date20211102154716.php index ff8b113d3e943..3a289fb92a482 100644 --- a/apps/user_ldap/lib/Migration/Version1130Date20211102154716.php +++ b/apps/user_ldap/lib/Migration/Version1130Date20211102154716.php @@ -76,6 +76,7 @@ public function changeSchema(IOutput $output, Closure $schemaClosure, array $opt $changeSchema = true; $this->hashColumnAddedToTables[] = $tableName; } + $column = $table->getColumn('ldap_dn'); if ($tableName === 'ldap_user_mapping') { if ($column->getLength() < 4000) { @@ -87,10 +88,12 @@ public function changeSchema(IOutput $output, Closure $schemaClosure, array $opt $table->dropIndex('ldap_dn_users'); $changeSchema = true; } + if (!$table->hasIndex('ldap_user_dn_hashes')) { $table->addUniqueIndex(['ldap_dn_hash'], 'ldap_user_dn_hashes'); $changeSchema = true; } + if (!$table->hasIndex('ldap_user_directory_uuid')) { $table->addUniqueIndex(['directory_uuid'], 'ldap_user_directory_uuid'); $changeSchema = true; @@ -158,6 +161,7 @@ protected function handleDNHashes(string $table): void { ); } } + $result->closeCursor(); } @@ -181,6 +185,7 @@ protected function getUpdateQuery(string $table): IQueryBuilder { $qb->update($table) ->set('ldap_dn_hash', $qb->createParameter('dn_hash')) ->where($qb->expr()->eq('owncloud_name', $qb->createParameter('name'))); + return $qb; } @@ -193,6 +198,7 @@ protected function processDuplicateUUIDs(string $table): void { foreach ($uuids as $uuid) { array_push($idsWithUuidToInvalidate, ...$this->getNextcloudIdsByUuid($table, $uuid)); } + $this->invalidateUuids($table, $idsWithUuidToInvalidate); } @@ -225,6 +231,7 @@ protected function invalidateUuids(string $table, array $idList): void { if ($e->getReason() !== Exception::REASON_CONSTRAINT_VIOLATION) { throw $e; } + $idList[] = $nextcloudId; } } @@ -245,7 +252,9 @@ protected function getNextcloudIdsByUuid(string $table, string $uuid): array { while (($id = $result->fetchOne()) !== false) { $idList[] = $id; } + $result->closeCursor(); + return $idList; } @@ -264,6 +273,7 @@ protected function getDuplicatedUuids(string $table): Generator { while (($uuid = $result->fetchOne()) !== false) { yield $uuid; } + $result->closeCursor(); } } diff --git a/apps/user_ldap/lib/Migration/Version1141Date20220323143801.php b/apps/user_ldap/lib/Migration/Version1141Date20220323143801.php index 86f10a26c3efd..2a1d4bdca2f77 100644 --- a/apps/user_ldap/lib/Migration/Version1141Date20220323143801.php +++ b/apps/user_ldap/lib/Migration/Version1141Date20220323143801.php @@ -41,6 +41,7 @@ public function preSchemaChange(IOutput $output, Closure $schemaClosure, array $ while (($dn = $result->fetchOne()) !== false) { $dnsTooLong[] = $dn; } + $result->closeCursor(); $this->shortenDNs($dnsTooLong, $tableName); } @@ -64,11 +65,13 @@ protected function shortenDNs(array $dns, string $table): void { $qb->setParameter('originalDn', $dn); $qb->executeStatement(); } + $this->dbc->commit(); } catch (\Throwable $t) { $this->dbc->rollBack(); throw $t; } + $page++; } while (count($subset) === $pageSize); } diff --git a/apps/user_ldap/lib/Migration/Version1190Date20230706134108.php b/apps/user_ldap/lib/Migration/Version1190Date20230706134108.php index 85b046ab7c944..f8f7bada25199 100644 --- a/apps/user_ldap/lib/Migration/Version1190Date20230706134108.php +++ b/apps/user_ldap/lib/Migration/Version1190Date20230706134108.php @@ -47,6 +47,7 @@ public function changeSchema(IOutput $output, Closure $schemaClosure, array $opt ]); $table->setPrimaryKey(['id']); $table->addUniqueIndex(['groupid', 'userid'], 'user_ldap_membership_unique'); + return $schema; } else { return null; @@ -86,6 +87,7 @@ protected function copyGroupMembershipData(): void { /* Unserialize failed or data was incorrect in database, ignore */ continue; } + $knownUsers = array_unique($knownUsers); foreach ($knownUsers as $knownUser) { try { @@ -103,6 +105,7 @@ protected function copyGroupMembershipData(): void { } } } + $result->closeCursor(); } } diff --git a/apps/user_ldap/lib/Notification/Notifier.php b/apps/user_ldap/lib/Notification/Notifier.php index 23ad77f75f2ac..91ef34b7feec7 100644 --- a/apps/user_ldap/lib/Notification/Notifier.php +++ b/apps/user_ldap/lib/Notification/Notifier.php @@ -73,6 +73,7 @@ public function prepare(INotification $notification, string $languageCode): INot $days )); } + return $notification; default: diff --git a/apps/user_ldap/lib/PagedResults/TLinkId.php b/apps/user_ldap/lib/PagedResults/TLinkId.php index 46d392995e015..473bbc0e4ff8c 100644 --- a/apps/user_ldap/lib/PagedResults/TLinkId.php +++ b/apps/user_ldap/lib/PagedResults/TLinkId.php @@ -21,6 +21,7 @@ public function getLinkId($link) { return (int)$link[0]; } } + throw new \RuntimeException('No resource provided'); } } diff --git a/apps/user_ldap/lib/Proxy.php b/apps/user_ldap/lib/Proxy.php index 285427bfd4d09..e5e698e5cab2d 100644 --- a/apps/user_ldap/lib/Proxy.php +++ b/apps/user_ldap/lib/Proxy.php @@ -22,7 +22,7 @@ abstract class Proxy { public function __construct( ILDAPWrapper $ldap, - AccessFactory $accessFactory + AccessFactory $accessFactory, ) { $this->ldap = $ldap; $this->accessFactory = $accessFactory; @@ -47,6 +47,7 @@ protected function getAccess(string $configPrefix): Access { if (!isset(self::$accesses[$configPrefix])) { $this->addAccess($configPrefix); } + return self::$accesses[$configPrefix]; } @@ -95,6 +96,7 @@ protected function isSingleBackend(): bool { if ($this->isSingleBackend === null) { $this->isSingleBackend = $this->activeBackends() === 1; } + return $this->isSingleBackend; } @@ -111,9 +113,11 @@ protected function handleRequest($id, $method, $parameters, $passOnWhen = false) if (!$this->isSingleBackend()) { $result = $this->callOnLastSeenOn($id, $method, $parameters, $passOnWhen); } + if (!isset($result) || $result === $passOnWhen) { $result = $this->walkBackends($id, $method, $parameters); } + return $result; } @@ -126,6 +130,7 @@ private function getCacheKey($key) { if ($key === null) { return $prefix; } + return $prefix . hash('sha256', $key); } @@ -155,6 +160,7 @@ public function writeToCache($key, $value) { if ($this->cache === null) { return; } + $key = $this->getCacheKey($key); $value = base64_encode(json_encode($value)); $this->cache->set($key, $value, 2592000); @@ -164,6 +170,7 @@ public function clearCache() { if ($this->cache === null) { return; } + $this->cache->clear($this->getCacheKey(null)); } } diff --git a/apps/user_ldap/lib/Service/UpdateGroupsService.php b/apps/user_ldap/lib/Service/UpdateGroupsService.php index 94f2a7fd4a192..8776431737718 100644 --- a/apps/user_ldap/lib/Service/UpdateGroupsService.php +++ b/apps/user_ldap/lib/Service/UpdateGroupsService.php @@ -45,6 +45,7 @@ public function updateGroups(): void { $this->logger->info( 'service "updateGroups" - groups do not seem to be configured properly, aborting.', ); + return; } @@ -83,6 +84,7 @@ public function handleKnownGroups(array $groups): void { ); continue; } + foreach (array_diff($knownUsers, $actualUsers) as $removedUser) { try { $this->groupMembershipMapper->delete($groupMemberships[$removedUser]); @@ -99,13 +101,16 @@ public function handleKnownGroups(array $groups): void { ] ); } + /* We failed to delete the groupmembership so we do not want to advertise it */ continue; } + $userObject = $this->userManager->get($removedUser); if ($userObject instanceof IUser) { $this->dispatcher->dispatchTyped(new UserRemovedEvent($groupObject, $userObject)); } + $this->logger->info( 'service "updateGroups" - {user} removed from {group}', [ @@ -114,6 +119,7 @@ public function handleKnownGroups(array $groups): void { ] ); } + foreach (array_diff($actualUsers, $knownUsers) as $addedUser) { try { $this->groupMembershipMapper->insert(GroupMembership::fromParams(['groupid' => $group,'userid' => $addedUser])); @@ -130,13 +136,16 @@ public function handleKnownGroups(array $groups): void { ] ); } + /* We failed to insert the groupmembership so we do not want to advertise it */ continue; } + $userObject = $this->userManager->get($addedUser); if ($userObject instanceof IUser) { $this->dispatcher->dispatchTyped(new UserAddedEvent($groupObject, $userObject)); } + $this->logger->info( 'service "updateGroups" - {user} added to {group}', [ @@ -146,6 +155,7 @@ public function handleKnownGroups(array $groups): void { ); } } + $this->logger->debug('service "updateGroups" - FINISHED dealing with known Groups.'); } @@ -176,9 +186,11 @@ public function handleCreatedGroups(array $createdGroups): void { ] ); } + /* We failed to insert the groupmembership so we do not want to advertise it */ continue; } + if ($groupObject instanceof IGroup) { $userObject = $this->userManager->get($user); if ($userObject instanceof IUser) { @@ -187,6 +199,7 @@ public function handleCreatedGroups(array $createdGroups): void { } } } + $this->logger->debug('service "updateGroups" - FINISHED dealing with created Groups.'); } diff --git a/apps/user_ldap/lib/Settings/Admin.php b/apps/user_ldap/lib/Settings/Admin.php index 71532846b9118..7b1062697afb6 100644 --- a/apps/user_ldap/lib/Settings/Admin.php +++ b/apps/user_ldap/lib/Settings/Admin.php @@ -53,9 +53,10 @@ public function getForm() { if (!isset($config)) { $config = new Configuration('', false); } + $defaults = $config->getDefaults(); foreach ($defaults as $key => $default) { - $parameters[$key.'_default'] = $default; + $parameters[$key . '_default'] = $default; } return new TemplateResponse('user_ldap', 'settings', $parameters); diff --git a/apps/user_ldap/lib/SetupChecks/LdapConnection.php b/apps/user_ldap/lib/SetupChecks/LdapConnection.php index a33dd10f063d2..582f4f0ebf4cc 100644 --- a/apps/user_ldap/lib/SetupChecks/LdapConnection.php +++ b/apps/user_ldap/lib/SetupChecks/LdapConnection.php @@ -44,16 +44,19 @@ public function run(): SetupResult { $inactiveConfigurations[] = $configID; continue; } + if (!$connection->bind()) { $bindFailedConfigurations[] = $configID; continue; } + $access = $this->accessFactory->get($connection); $result = $access->countObjects(1); if (!is_int($result) || ($result <= 0)) { $searchFailedConfigurations[] = $configID; } } + $output = ''; if (!empty($bindFailedConfigurations)) { $output .= $this->l10n->n( @@ -61,29 +64,33 @@ public function run(): SetupResult { 'Binding failed for %n LDAP configurations: %s', count($bindFailedConfigurations), [implode(',', $bindFailedConfigurations)] - )."\n"; + ) . "\n"; } + if (!empty($searchFailedConfigurations)) { $output .= $this->l10n->n( 'Searching failed for this LDAP configuration: %s', 'Searching failed for %n LDAP configurations: %s', count($searchFailedConfigurations), [implode(',', $searchFailedConfigurations)] - )."\n"; + ) . "\n"; } + if (!empty($inactiveConfigurations)) { $output .= $this->l10n->n( 'There is an inactive LDAP configuration: %s', 'There are %n inactive LDAP configurations: %s', count($inactiveConfigurations), [implode(',', $inactiveConfigurations)] - )."\n"; + ) . "\n"; } + if (!empty($bindFailedConfigurations) || !empty($searchFailedConfigurations)) { return SetupResult::error($output); } elseif (!empty($inactiveConfigurations)) { return SetupResult::warning($output); } + return SetupResult::success($this->l10n->n( 'Binding and searching works on the configured LDAP connection (%s)', 'Binding and searching works on all of the %n configured LDAP connections (%s)', diff --git a/apps/user_ldap/lib/User/DeletedUsersIndex.php b/apps/user_ldap/lib/User/DeletedUsersIndex.php index 4b5bb26fa96ec..d2b6cec989cdd 100644 --- a/apps/user_ldap/lib/User/DeletedUsersIndex.php +++ b/apps/user_ldap/lib/User/DeletedUsersIndex.php @@ -22,7 +22,7 @@ class DeletedUsersIndex { public function __construct( IConfig $config, UserMapping $mapping, - IManager $shareManager + IManager $shareManager, ) { $this->config = $config; $this->mapping = $mapping; @@ -45,6 +45,7 @@ private function fetchDeletedUsers(): array { $userObjects[] = $userObject; } } + $this->deletedUsers = $userObjects; return $this->deletedUsers; @@ -58,6 +59,7 @@ public function getUsers(): array { if (is_array($this->deletedUsers)) { return $this->deletedUsers; } + return $this->fetchDeletedUsers(); } @@ -68,6 +70,7 @@ public function hasUsers(): bool { if (!is_array($this->deletedUsers)) { $this->fetchDeletedUsers(); } + return is_array($this->deletedUsers) && (count($this->deletedUsers) > 0); } @@ -81,6 +84,7 @@ public function markUser(string $ocName): void { // the user is already marked, do not write to DB again return; } + $this->config->setUserValue($ocName, 'user_ldap', 'isDeleted', '1'); $this->config->setUserValue($ocName, 'user_ldap', 'foundDeleted', (string)time()); $this->deletedUsers = null; diff --git a/apps/user_ldap/lib/User/Manager.php b/apps/user_ldap/lib/User/Manager.php index bf1da54823ccc..ea75bf618eacc 100644 --- a/apps/user_ldap/lib/User/Manager.php +++ b/apps/user_ldap/lib/User/Manager.php @@ -49,7 +49,7 @@ public function __construct( Image $image, IUserManager $userManager, INotificationManager $notificationManager, - IManager $shareManager + IManager $shareManager, ) { $this->ocConfig = $ocConfig; $this->ocFilesystem = $ocFilesystem; @@ -87,6 +87,7 @@ private function createAndCache($dn, $uid) { $this->notificationManager); $this->usersByDN[$dn] = $user; $this->usersByUid[$uid] = $user; + return $user; } @@ -98,6 +99,7 @@ public function invalidate($uid) { if (!isset($this->usersByUid[$uid])) { return; } + $dn = $this->usersByUid[$uid]->getDN(); unset($this->usersByUid[$uid]); unset($this->usersByDN[$dn]); @@ -182,6 +184,7 @@ function ($list, $attribute) { public function isDeletedUser($id) { $isDeleted = $this->ocConfig->getUserValue( $id, 'user_ldap', 'isDeleted', 0); + return (int)$isDeleted === 1; } @@ -209,10 +212,12 @@ protected function createInstancyByUserName($id) { if ($this->isDeletedUser($id)) { return $this->getDeletedUser($id); } + $dn = $this->access->username2dn($id); if ($dn !== false) { return $this->createAndCache($dn, $id); } + return null; } @@ -266,10 +271,12 @@ public function exists($id): bool { if ($this->isDeletedUser($id)) { return true; } + $dn = $this->access->username2dn($id); if ($dn !== false) { return true; } + return false; } } diff --git a/apps/user_ldap/lib/User/OfflineUser.php b/apps/user_ldap/lib/User/OfflineUser.php index 24f11a5a1f9c3..5767a0de75b38 100644 --- a/apps/user_ldap/lib/User/OfflineUser.php +++ b/apps/user_ldap/lib/User/OfflineUser.php @@ -70,7 +70,7 @@ public function __construct( $ocName, IConfig $config, UserMapping $mapping, - IManager $shareManager + IManager $shareManager, ) { $this->ocName = $ocName; $this->config = $config; @@ -120,6 +120,7 @@ public function getUID() { if ($this->uid === null) { $this->fetchDetails(); } + return $this->uid; } @@ -132,6 +133,7 @@ public function getDN() { $dn = $this->mapping->getDNByName($this->ocName); $this->dn = ($dn !== false) ? $dn : ''; } + return $this->dn; } @@ -143,6 +145,7 @@ public function getDisplayName() { if ($this->displayName === null) { $this->fetchDetails(); } + return $this->displayName; } @@ -154,6 +157,7 @@ public function getEmail() { if ($this->email === null) { $this->fetchDetails(); } + return $this->email; } @@ -165,6 +169,7 @@ public function getHomePath() { if ($this->homePath === null) { $this->fetchDetails(); } + return $this->homePath; } @@ -176,6 +181,7 @@ public function getLastLogin() { if ($this->lastLogin === null) { $this->fetchDetails(); } + return (int)$this->lastLogin; } @@ -187,6 +193,7 @@ public function getDetectedOn() { if ($this->foundDeleted === null) { $this->fetchDetails(); } + return (int)$this->foundDeleted; } @@ -194,6 +201,7 @@ public function getExtStorageHome(): string { if ($this->extStorageHome === null) { $this->fetchDetails(); } + return (string)$this->extStorageHome; } @@ -205,6 +213,7 @@ public function getHasActiveShares() { if ($this->hasActiveShares === null) { $this->determineShares(); } + return $this->hasActiveShares; } @@ -240,6 +249,7 @@ protected function determineShares() { ) { continue; } + $shares = $this->shareManager->getSharesBy( $this->ocName, $constantValue, diff --git a/apps/user_ldap/lib/User/User.php b/apps/user_ldap/lib/User/User.php index f2a9ba49f2f81..6a48ba2c48138 100644 --- a/apps/user_ldap/lib/User/User.php +++ b/apps/user_ldap/lib/User/User.php @@ -135,6 +135,7 @@ public function markUser() { // the user is already marked, do not write to DB again return; } + $this->config->setUserValue($this->getUsername(), 'user_ldap', 'isDeleted', '1'); $this->config->setUserValue($this->getUsername(), 'user_ldap', 'foundDeleted', (string)time()); } @@ -153,6 +154,7 @@ public function processAttributes($ldapEntry) { $this->updateQuota(); } } + unset($attr); //displayName @@ -161,10 +163,12 @@ public function processAttributes($ldapEntry) { if (isset($ldapEntry[$attr])) { $displayName = (string)$ldapEntry[$attr][0]; } + $attr = strtolower($this->connection->ldapUserDisplayName2); if (isset($ldapEntry[$attr])) { $displayName2 = (string)$ldapEntry[$attr][0]; } + if ($displayName !== '') { $this->composeAndStoreDisplayName($displayName, $displayName2); $this->access->cacheUserDisplayName( @@ -173,6 +177,7 @@ public function processAttributes($ldapEntry) { $displayName2 ); } + unset($attr); //Email @@ -182,6 +187,7 @@ public function processAttributes($ldapEntry) { if (isset($ldapEntry[$attr])) { $this->updateEmail($ldapEntry[$attr][0]); } + unset($attr); // LDAP Username, needed for s2s sharing @@ -201,11 +207,12 @@ public function processAttributes($ldapEntry) { } //memberOf groups - $cacheKey = 'getMemberOf'.$this->getUsername(); + $cacheKey = 'getMemberOf' . $this->getUsername(); $groups = false; if (isset($ldapEntry['memberof'])) { $groups = $ldapEntry['memberof']; } + $this->connection->writeToCache($cacheKey, $groups); //external storage var @@ -213,11 +220,12 @@ public function processAttributes($ldapEntry) { if (isset($ldapEntry[$attr])) { $this->updateExtStorageHome($ldapEntry[$attr][0]); } + unset($attr); // check for cached profile data $username = $this->getUsername(); // buffer variable, to save resource - $cacheKey = 'getUserProfile-'.$username; + $cacheKey = 'getUserProfile-' . $username; $profileCached = $this->connection->getFromCache($cacheKey); // honoring profile disabled in config.php and check if user profile was refreshed if ($this->config->getSystemValueBool('profile.enabled', true) && @@ -231,6 +239,7 @@ public function processAttributes($ldapEntry) { $profileValues[\OCP\Accounts\IAccountManager::PROPERTY_PHONE] = $ldapEntry[$attr][0] ?? ''; } + //User Profile Field - website $attr = strtolower($this->connection->ldapAttributeWebsite); if (isset($ldapEntry[$attr])) { @@ -246,6 +255,7 @@ public function processAttributes($ldapEntry) { } elseif (!empty($attr)) { // configured, but not defined $profileValues[\OCP\Accounts\IAccountManager::PROPERTY_WEBSITE] = ''; } + //User Profile Field - Address $attr = strtolower($this->connection->ldapAttributeAddress); if (isset($ldapEntry[$attr])) { @@ -260,36 +270,42 @@ public function processAttributes($ldapEntry) { } elseif (!empty($attr)) { // configured, but not defined $profileValues[\OCP\Accounts\IAccountManager::PROPERTY_ADDRESS] = ''; } + //User Profile Field - Twitter $attr = strtolower($this->connection->ldapAttributeTwitter); if (!empty($attr)) { $profileValues[\OCP\Accounts\IAccountManager::PROPERTY_TWITTER] = $ldapEntry[$attr][0] ?? ''; } + //User Profile Field - fediverse $attr = strtolower($this->connection->ldapAttributeFediverse); if (!empty($attr)) { $profileValues[\OCP\Accounts\IAccountManager::PROPERTY_FEDIVERSE] = $ldapEntry[$attr][0] ?? ''; } + //User Profile Field - organisation $attr = strtolower($this->connection->ldapAttributeOrganisation); if (!empty($attr)) { $profileValues[\OCP\Accounts\IAccountManager::PROPERTY_ORGANISATION] = $ldapEntry[$attr][0] ?? ''; } + //User Profile Field - role $attr = strtolower($this->connection->ldapAttributeRole); if (!empty($attr)) { $profileValues[\OCP\Accounts\IAccountManager::PROPERTY_ROLE] = $ldapEntry[$attr][0] ?? ''; } + //User Profile Field - headline $attr = strtolower($this->connection->ldapAttributeHeadline); if (!empty($attr)) { $profileValues[\OCP\Accounts\IAccountManager::PROPERTY_HEADLINE] = $ldapEntry[$attr][0] ?? ''; } + //User Profile Field - biography $attr = strtolower($this->connection->ldapAttributeBiography); if (isset($ldapEntry[$attr])) { @@ -304,6 +320,7 @@ public function processAttributes($ldapEntry) { } elseif (!empty($attr)) { // configured, but not defined $profileValues[\OCP\Accounts\IAccountManager::PROPERTY_BIOGRAPHY] = ''; } + //User Profile Field - birthday $attr = strtolower($this->connection->ldapAttributeBirthDate); if (!empty($attr) && !empty($ldapEntry[$attr][0])) { @@ -320,12 +337,14 @@ public function processAttributes($ldapEntry) { ]); } } + //User Profile Field - pronouns $attr = strtolower($this->connection->ldapAttributePronouns); if (!empty($attr)) { $profileValues[\OCP\Accounts\IAccountManager::PROPERTY_PRONOUNS] = $ldapEntry[$attr][0] ?? ''; } + // check for changed data and cache just for TTL checking $checksum = hash('sha256', json_encode($profileValues)); $this->connection->writeToCache($cacheKey, $checksum // write array to cache. is waste of cache space @@ -338,6 +357,7 @@ public function processAttributes($ldapEntry) { } else { $this->logger->debug('profile data from LDAP unchanged', ['app' => 'user_ldap', 'uid' => $username]); } + unset($attr); } elseif ($profileCached !== null) { // message delayed, to declutter log $this->logger->debug('skipping profile check, while cached data exist', ['app' => 'user_ldap', 'uid' => $username]); @@ -404,13 +424,15 @@ public function getHomePath($valueFromLDAP = null) { && $path[1] === ':' && ($path[2] === '\\' || $path[2] === '/')) ) { $path = $this->config->getSystemValue('datadirectory', - \OC::$SERVERROOT.'/data') . '/' . $path; + \OC::$SERVERROOT . '/data') . '/' . $path; } + //we need it to store it in the DB as well in case a user gets //deleted so we can clean up afterwards $this->config->setUserValue( $this->getUsername(), 'user_ldap', 'homePath', $path ); + return $path; } @@ -423,17 +445,20 @@ public function getHomePath($valueFromLDAP = null) { //false will apply default behaviour as defined and done by OC_User $this->config->setUserValue($this->getUsername(), 'user_ldap', 'homePath', ''); + return false; } public function getMemberOfGroups() { - $cacheKey = 'getMemberOf'.$this->getUsername(); + $cacheKey = 'getMemberOf' . $this->getUsername(); $memberOfGroups = $this->connection->getFromCache($cacheKey); if (!is_null($memberOfGroups)) { return $memberOfGroups; } + $groupDNs = $this->access->readAttribute($this->getDN(), 'memberOf'); $this->connection->writeToCache($cacheKey, $groupDNs); + return $groupDNs; } @@ -493,6 +518,7 @@ public function composeAndStoreDisplayName($displayName, $displayName2 = '') { if ($displayName2 !== '') { $displayName .= ' (' . $displayName2 . ')'; } + $oldName = $this->config->getUserValue($this->uid, 'user_ldap', 'displayName', null); if ($oldName !== $displayName) { $this->store('displayName', $displayName); @@ -503,6 +529,7 @@ public function composeAndStoreDisplayName($displayName, $displayName2 = '') { $user->triggerChange('displayName', $displayName, $oldName); } } + return $displayName; } @@ -525,7 +552,9 @@ private function wasRefreshed($feature) { if (isset($this->refreshedFeatures[$feature])) { return true; } + $this->refreshedFeatures[$feature] = 1; + return false; } @@ -538,6 +567,7 @@ public function updateEmail($valueFromLDAP = null) { if ($this->wasRefreshed('email')) { return; } + $email = (string)$valueFromLDAP; if (is_null($valueFromLDAP)) { $emailAttribute = $this->connection->ldapEmailAttribute; @@ -548,6 +578,7 @@ public function updateEmail($valueFromLDAP = null) { } } } + if ($email !== '') { $user = $this->userManager->get($this->uid); if (!is_null($user)) { @@ -634,12 +665,14 @@ private function updateProfile(array $profileValues): void { if (empty($profileValues)) { return; // okay, nothing to do } + // fetch/prepare user $user = $this->userManager->get($this->uid); if (is_null($user)) { - $this->logger->error('could not get user for uid='.$this->uid.'', ['app' => 'user_ldap']); + $this->logger->error('could not get user for uid=' . $this->uid . '', ['app' => 'user_ldap']); return; } + // prepare AccountManager and Account $accountManager = Server::get(IAccountManager::class); $account = $accountManager->getAccount($user); // get Account @@ -654,22 +687,24 @@ private function updateProfile(array $profileValues): void { $currentValue = $accountProperty->getValue(); $scope = ($accountProperty->getScope() ?: $defaultScopes[$property]); } catch (PropertyDoesNotExistException $e) { // thrown at getProperty - $this->logger->error('property does not exist: '.$property - .' for uid='.$this->uid.'', ['app' => 'user_ldap', 'exception' => $e]); + $this->logger->error('property does not exist: ' . $property + . ' for uid=' . $this->uid . '', ['app' => 'user_ldap', 'exception' => $e]); $currentValue = ''; $scope = $defaultScopes[$property]; } + $verified = IAccountManager::VERIFIED; // trust the LDAP admin knew what he put there if ($currentValue !== $value) { $account->setProperty($property, $value, $scope, $verified); - $this->logger->debug('update user profile: '.$property.'='.$value - .' for uid='.$this->uid.'', ['app' => 'user_ldap']); + $this->logger->debug('update user profile: ' . $property . '=' . $value + . ' for uid=' . $this->uid . '', ['app' => 'user_ldap']); } } + try { $accountManager->updateAccount($account); // may throw InvalidArgumentException } catch (\InvalidArgumentException $e) { - $this->logger->error('invalid data from LDAP: for uid='.$this->uid.'', ['app' => 'user_ldap', 'func' => 'updateProfile' + $this->logger->error('invalid data from LDAP: for uid=' . $this->uid . '', ['app' => 'user_ldap', 'func' => 'updateProfile' , 'exception' => $e]); } } @@ -693,6 +728,7 @@ public function updateAvatar(bool $force = false): bool { if (!$force && $this->wasRefreshed('avatar')) { return false; } + $avatarImage = $this->getAvatarImage(); if ($avatarImage === false) { //not set, nothing left to do; @@ -735,15 +771,14 @@ private function avatarExists(): bool { */ private function setOwnCloudAvatar() { if (!$this->image->valid()) { - $this->logger->error('avatar image data from LDAP invalid for '.$this->dn, ['app' => 'user_ldap']); + $this->logger->error('avatar image data from LDAP invalid for ' . $this->dn, ['app' => 'user_ldap']); return false; } - //make sure it is a square and not bigger than 512x512 $size = min([$this->image->width(), $this->image->height(), 512]); if (!$this->image->centerCrop($size)) { - $this->logger->error('croping image for avatar failed for '.$this->dn, ['app' => 'user_ldap']); + $this->logger->error('croping image for avatar failed for ' . $this->dn, ['app' => 'user_ldap']); return false; } @@ -754,10 +789,12 @@ private function setOwnCloudAvatar() { try { $avatar = $this->avatarManager->getAvatar($this->uid); $avatar->set($this->image); + return true; } catch (\Exception $e) { $this->logger->info('Could not set avatar for ' . $this->dn, ['exception' => $e]); } + return false; } @@ -792,9 +829,11 @@ public function updateExtStorageHome(?string $valueFromLDAP = null):string { } else { $extHomeValues = [$valueFromLDAP]; } + if ($extHomeValues && isset($extHomeValues[0])) { $extHome = $extHomeValues[0]; $this->config->setUserValue($this->getUsername(), 'user_ldap', 'extStorageHome', $extHome); + return $extHome; } else { $this->config->deleteUserValue($this->getUsername(), 'user_ldap', 'extStorageHome'); @@ -812,6 +851,7 @@ public function handlePasswordExpiry($params) { if (empty($ppolicyDN) || ((int)$this->connection->turnOnPasswordChange !== 1)) { return;//password expiry handling disabled } + $uid = $params['uid']; if (isset($uid) && $uid === $this->getUsername()) { //retrieve relevant user attributes @@ -845,21 +885,24 @@ public function handlePasswordExpiry($params) { if (!empty($pwdGraceAuthNLimit) && count($pwdGraceUseTime) < (int)$pwdGraceAuthNLimit[0]) { //at least one more grace login available? $this->config->setUserValue($uid, 'user_ldap', 'needsPasswordReset', 'true'); - header('Location: '.\OC::$server->getURLGenerator()->linkToRouteAbsolute( + header('Location: ' . \OC::$server->getURLGenerator()->linkToRouteAbsolute( 'user_ldap.renewPassword.showRenewPasswordForm', ['user' => $uid])); } else { //no more grace login available - header('Location: '.\OC::$server->getURLGenerator()->linkToRouteAbsolute( + header('Location: ' . \OC::$server->getURLGenerator()->linkToRouteAbsolute( 'user_ldap.renewPassword.showLoginFormInvalidPassword', ['user' => $uid])); } + exit(); } + //handle pwdReset attribute if (!empty($pwdReset) && $pwdReset[0] === 'TRUE') { //user must change his password $this->config->setUserValue($uid, 'user_ldap', 'needsPasswordReset', 'true'); - header('Location: '.\OC::$server->getURLGenerator()->linkToRouteAbsolute( + header('Location: ' . \OC::$server->getURLGenerator()->linkToRouteAbsolute( 'user_ldap.renewPassword.showRenewPasswordForm', ['user' => $uid])); exit(); } + //handle password expiry warning if (!empty($pwdChangedTime)) { if (!empty($pwdMaxAge) @@ -868,7 +911,7 @@ public function handlePasswordExpiry($params) { $pwdExpireWarningInt = (int)$pwdExpireWarning[0]; if ($pwdMaxAgeInt > 0 && $pwdExpireWarningInt > 0) { $pwdChangedTimeDt = \DateTime::createFromFormat('YmdHisZ', $pwdChangedTime[0]); - $pwdChangedTimeDt->add(new \DateInterval('PT'.$pwdMaxAgeInt.'S')); + $pwdChangedTimeDt->add(new \DateInterval('PT' . $pwdMaxAgeInt . 'S')); $currentDateTime = new \DateTime(); $secondsToExpiry = $pwdChangedTimeDt->getTimestamp() - $currentDateTime->getTimestamp(); if ($secondsToExpiry <= $pwdExpireWarningInt) { diff --git a/apps/user_ldap/lib/UserPluginManager.php b/apps/user_ldap/lib/UserPluginManager.php index ddf23ddfe3e51..8d96787b8e2e2 100644 --- a/apps/user_ldap/lib/UserPluginManager.php +++ b/apps/user_ldap/lib/UserPluginManager.php @@ -43,12 +43,13 @@ public function register(ILDAPUserPlugin $plugin) { foreach ($this->which as $action => $v) { if (is_int($action) && (bool)($respondToActions & $action)) { $this->which[$action] = $plugin; - \OCP\Server::get(LoggerInterface::class)->debug('Registered action '.$action.' to plugin '.get_class($plugin), ['app' => 'user_ldap']); + \OCP\Server::get(LoggerInterface::class)->debug('Registered action ' . $action . ' to plugin ' . get_class($plugin), ['app' => 'user_ldap']); } } + if (method_exists($plugin, 'deleteUser')) { $this->which['deleteUser'] = $plugin; - \OCP\Server::get(LoggerInterface::class)->debug('Registered action deleteUser to plugin '.get_class($plugin), ['app' => 'user_ldap']); + \OCP\Server::get(LoggerInterface::class)->debug('Registered action deleteUser to plugin ' . get_class($plugin), ['app' => 'user_ldap']); } } @@ -75,6 +76,7 @@ public function createUser($username, $password) { if ($plugin) { return $plugin->createUser($username, $password); } + throw new \Exception('No plugin implements createUser in this LDAP Backend.'); } @@ -91,6 +93,7 @@ public function setPassword($uid, $password) { if ($plugin) { return $plugin->setPassword($uid, $password); } + throw new \Exception('No plugin implements setPassword in this LDAP Backend.'); } @@ -106,6 +109,7 @@ public function canChangeAvatar($uid) { if ($plugin) { return $plugin->canChangeAvatar($uid); } + throw new \Exception('No plugin implements canChangeAvatar in this LDAP Backend.'); } @@ -121,6 +125,7 @@ public function getHome($uid) { if ($plugin) { return $plugin->getHome($uid); } + throw new \Exception('No plugin implements getHome in this LDAP Backend.'); } @@ -136,6 +141,7 @@ public function getDisplayName($uid) { if ($plugin) { return $plugin->getDisplayName($uid); } + throw new \Exception('No plugin implements getDisplayName in this LDAP Backend.'); } @@ -152,6 +158,7 @@ public function setDisplayName($uid, $displayName) { if ($plugin) { return $plugin->setDisplayName($uid, $displayName); } + throw new \Exception('No plugin implements setDisplayName in this LDAP Backend.'); } @@ -166,6 +173,7 @@ public function countUsers() { if ($plugin) { return $plugin->countUsers(); } + throw new \Exception('No plugin implements countUsers in this LDAP Backend.'); } @@ -187,8 +195,10 @@ public function deleteUser($uid) { if ($this->suppressDeletion) { return false; } + return $plugin->deleteUser($uid); } + throw new \Exception('No plugin implements deleteUser in this LDAP Backend.'); } @@ -199,6 +209,7 @@ public function deleteUser($uid) { public function setSuppressDeletion(bool $value): bool { $old = $this->suppressDeletion; $this->suppressDeletion = $value; + return $old; } } diff --git a/apps/user_ldap/lib/User_LDAP.php b/apps/user_ldap/lib/User_LDAP.php index b1065fcf7a78d..c0723f3f771dd 100644 --- a/apps/user_ldap/lib/User_LDAP.php +++ b/apps/user_ldap/lib/User_LDAP.php @@ -62,10 +62,12 @@ public function canChangeAvatar($uid) { if (!$user instanceof User) { return false; } + $imageData = $user->getAvatarImage(); if ($imageData === false) { return true; } + return !$user->updateAvatar(true); } @@ -92,13 +94,16 @@ public function loginName2UserName($loginName, bool $forceLdapRefetch = false) { // this path is not really possible, however get() is documented // to return User, OfflineUser or null so we are very defensive here. $this->access->connection->writeToCache($cacheKey, false); + return false; } + $username = $user->getUsername(); $this->access->connection->writeToCache($cacheKey, $username); if ($forceLdapRefetch) { $user->processAttributes($ldapRecord); } + return $username; } catch (NotOnLDAP $e) { $this->access->connection->writeToCache($cacheKey, false); @@ -131,6 +136,7 @@ public function getLDAPUserByLoginName($loginName) { throw new NotOnLDAP('No user available for the given login name on ' . $this->access->connection->ldapHost . ':' . $this->access->connection->ldapPort); } + return $users[0]; } @@ -146,6 +152,7 @@ public function checkPassword($uid, $password) { if ($username === false) { return false; } + $dn = $this->access->username2dn($username); $user = $this->access->userManager->get($dn); @@ -155,8 +162,10 @@ public function checkPassword($uid, $password) { '. Maybe the LDAP entry has no set display name attribute?', ['app' => 'user_ldap'] ); + return false; } + if ($user->getUsername() !== false) { //are the credentials OK? if (!$this->access->areCredentialsValid($dn, $password)) { @@ -189,6 +198,7 @@ public function setPassword($uid, $password) { throw new \Exception('LDAP setPassword: Could not get user object for uid ' . $uid . '. Maybe the LDAP entry has no set display name attribute?'); } + if ($user->getUsername() !== false && $this->access->setPassword($user->getDN(), $password)) { $ldapDefaultPPolicyDN = $this->access->connection->ldapDefaultPPolicyDN; $turnOnPasswordChange = $this->access->connection->turnOnPasswordChange; @@ -201,6 +211,7 @@ public function setPassword($uid, $password) { ; $this->notificationManager->markProcessed($notification); } + return true; } @@ -217,7 +228,7 @@ public function setPassword($uid, $password) { */ public function getUsers($search = '', $limit = 10, $offset = 0) { $search = $this->access->escapeFilterPart($search, true); - $cachekey = 'getUsers-'.$search.'-'.$limit.'-'.$offset; + $cachekey = 'getUsers-' . $search . '-' . $limit . '-' . $offset; //check if users are cached, if so return $ldap_users = $this->access->connection->getFromCache($cachekey); @@ -230,6 +241,7 @@ public function getUsers($search = '', $limit = 10, $offset = 0) { if ($limit <= 0) { $limit = null; } + $filter = $this->access->combineFilterWithAnd([ $this->access->connection->ldapUserFilter, $this->access->connection->ldapUserDisplayName . '=*', @@ -237,7 +249,7 @@ public function getUsers($search = '', $limit = 10, $offset = 0) { ]); $this->logger->debug( - 'getUsers: Options: search '.$search.' limit '.$limit.' offset '.$offset.' Filter: '.$filter, + 'getUsers: Options: search ' . $search . ' limit ' . $limit . ' offset ' . $offset . ' Filter: ' . $filter, ['app' => 'user_ldap'] ); //do the search and translate results to Nextcloud names @@ -247,11 +259,12 @@ public function getUsers($search = '', $limit = 10, $offset = 0) { $limit, $offset); $ldap_users = $this->access->nextcloudUserNames($ldap_users); $this->logger->debug( - 'getUsers: '.count($ldap_users). ' Users found', + 'getUsers: ' . count($ldap_users) . ' Users found', ['app' => 'user_ldap'] ); $this->access->connection->writeToCache($cachekey, $ldap_users); + return $ldap_users; } @@ -267,9 +280,11 @@ public function userExistsOnLDAP($user, bool $ignoreCache = false): bool { if (is_string($user)) { $user = $this->access->userManager->get($user); } + if (is_null($user)) { return false; } + $uid = $user instanceof User ? $user->getUsername() : $user->getOCName(); $cacheKey = 'userExistsOnLDAP' . $uid; if (!$ignoreCache) { @@ -288,12 +303,14 @@ public function userExistsOnLDAP($user, bool $ignoreCache = false): bool { $this->access->connection->writeToCache($cacheKey, false); return false; } + $newDn = $this->access->getUserDnByUuid($uuid); //check if renamed user is still valid by reapplying the ldap filter if ($newDn === $dn || !is_array($this->access->readAttribute($newDn, '', $this->access->connection->ldapUserFilter))) { $this->access->connection->writeToCache($cacheKey, false); return false; } + $this->access->getUserMapper()->setDNbyUUID($newDn, $uuid); } catch (ServerNotAvailableException $e) { throw $e; @@ -308,6 +325,7 @@ public function userExistsOnLDAP($user, bool $ignoreCache = false): bool { } $this->access->connection->writeToCache($cacheKey, true); + return true; } @@ -318,22 +336,25 @@ public function userExistsOnLDAP($user, bool $ignoreCache = false): bool { * @throws \Exception when connection could not be established */ public function userExists($uid) { - $userExists = $this->access->connection->getFromCache('userExists'.$uid); + $userExists = $this->access->connection->getFromCache('userExists' . $uid); if (!is_null($userExists)) { return (bool)$userExists; } + $userExists = $this->access->userManager->exists($uid); if (!$userExists) { $this->logger->debug( - 'No DN found for '.$uid.' on '.$this->access->connection->ldapHost, + 'No DN found for ' . $uid . ' on ' . $this->access->connection->ldapHost, ['app' => 'user_ldap'] ); - $this->access->connection->writeToCache('userExists'.$uid, false); + $this->access->connection->writeToCache('userExists' . $uid, false); + return false; } - $this->access->connection->writeToCache('userExists'.$uid, true); + $this->access->connection->writeToCache('userExists' . $uid, true); + return true; } @@ -365,20 +386,24 @@ public function deleteUser($uid) { ['app' => 'user_ldap', 'exception' => $e] ); } + if (!$marked) { $this->logger->notice( - 'User '.$uid . ' is not marked as deleted, not cleaning up.', + 'User ' . $uid . ' is not marked as deleted, not cleaning up.', ['app' => 'user_ldap'] ); + return false; } } + $this->logger->info('Cleaning up after user ' . $uid, ['app' => 'user_ldap']); $this->access->getUserMapper()->unmap($uid); // we don't emit unassign signals here, since it is implicit to delete signals fired from core $this->access->userManager->invalidate($uid); $this->access->connection->clearCache(); + return true; } @@ -400,7 +425,7 @@ public function getHome($uid) { return $this->userPluginManager->getHome($uid); } - $cacheKey = 'getHome'.$uid; + $cacheKey = 'getHome' . $uid; $path = $this->access->connection->getFromCache($cacheKey); if (!is_null($path)) { return $path; @@ -415,6 +440,7 @@ public function getHome($uid) { } $this->access->cacheUserHome($uid, $path); + return $path; } @@ -432,7 +458,7 @@ public function getDisplayName($uid) { return false; } - $cacheKey = 'getDisplayName'.$uid; + $cacheKey = 'getDisplayName' . $uid; if (!is_null($displayName = $this->access->connection->getFromCache($cacheKey))) { return $displayName; } @@ -462,9 +488,11 @@ public function getDisplayName($uid) { $displayName = $user->composeAndStoreDisplayName($displayName, $displayName2); $this->access->connection->writeToCache($cacheKey, $displayName); } + if ($user instanceof OfflineUser) { $displayName = $user->getDisplayName(); } + return $displayName; } @@ -481,8 +509,10 @@ public function setDisplayName($uid, $displayName) { if ($this->userPluginManager->implementsActions(Backend::SET_DISPLAYNAME)) { $this->userPluginManager->setDisplayName($uid, $displayName); $this->access->cacheUserDisplayName($uid, $displayName); + return $displayName; } + return false; } @@ -495,7 +525,7 @@ public function setDisplayName($uid, $displayName) { * @return array an array of all displayNames (value) and the corresponding uids (key) */ public function getDisplayNames($search = '', $limit = null, $offset = null) { - $cacheKey = 'getDisplayNames-'.$search.'-'.$limit.'-'.$offset; + $cacheKey = 'getDisplayNames-' . $search . '-' . $limit . '-' . $offset; if (!is_null($displayNames = $this->access->connection->getFromCache($cacheKey))) { return $displayNames; } @@ -505,7 +535,9 @@ public function getDisplayNames($search = '', $limit = null, $offset = null) { foreach ($users as $user) { $displayNames[$user] = $this->getDisplayName($user); } + $this->access->connection->writeToCache($cacheKey, $displayNames); + return $displayNames; } @@ -546,12 +578,14 @@ public function countUsers() { } $filter = $this->access->getFilterForUserCount(); - $cacheKey = 'countUsers-'.$filter; + $cacheKey = 'countUsers-' . $filter; if (!is_null($entries = $this->access->connection->getFromCache($cacheKey))) { return $entries; } + $entries = $this->access->countUsers($filter); $this->access->connection->writeToCache($cacheKey, $entries); + return $entries; } @@ -622,8 +656,10 @@ public function createUser($username, $password) { throw new \UnexpectedValueException('LDAP Plugin: Method createUser changed to return the user DN instead of boolean.'); } } + return (bool)$dn; } + return false; } diff --git a/apps/user_ldap/lib/User_Proxy.php b/apps/user_ldap/lib/User_Proxy.php index 0cb0e6e6cdeda..d3d991293d2dd 100644 --- a/apps/user_ldap/lib/User_Proxy.php +++ b/apps/user_ldap/lib/User_Proxy.php @@ -89,13 +89,16 @@ protected function walkBackends($id, $method, $parameters) { && method_exists($this->getAccess($configPrefix), $method)) { $instance = $this->getAccess($configPrefix); } + if ($result = call_user_func_array([$instance, $method], $parameters)) { if (!$this->isSingleBackend()) { $this->writeToCache($cacheKey, $configPrefix); } + return $result; } } + return false; } @@ -122,6 +125,7 @@ protected function callOnLastSeenOn($id, $method, $parameters, $passOnWhen) { && method_exists($this->getAccess($prefix), $method)) { $instance = $this->getAccess($prefix); } + $result = call_user_func_array([$instance, $method], $parameters); if ($result === $passOnWhen) { //not found here, reset cache to null if user vanished @@ -134,9 +138,11 @@ protected function callOnLastSeenOn($id, $method, $parameters, $passOnWhen) { $this->writeToCache($cacheKey, null); } } + return $result; } } + return false; } @@ -189,6 +195,7 @@ public function getUsers($search = '', $limit = 10, $offset = 0) { $users = array_merge($users, $backendUsers); } } + return $users; } @@ -204,6 +211,7 @@ public function userExists($uid) { if ($existsLocally) { $existsOnLDAP = $this->userExistsOnLDAP($uid); } + if ($existsLocally && !$existsOnLDAP) { try { $user = $this->getLDAPAccess($uid)->userManager->get($uid); @@ -214,6 +222,7 @@ public function userExists($uid) { // ignore } } + return $existsLocally; } @@ -323,6 +332,7 @@ public function getDisplayNames($search = '', $limit = null, $offset = null) { $users = $users + $backendUsers; } } + return $users; } @@ -373,6 +383,7 @@ public function countUsers() { $users = (int)$users + $backendUsers; } } + return $users; } @@ -386,6 +397,7 @@ public function countMappedUsers(): int { foreach ($this->backends as $backend) { $users += $backend->countMappedUsers(); } + return $users; } @@ -433,6 +445,7 @@ public function getDisabledUserList(?int $limit = null, int $offset = 0, string if ((int)$this->getAccess(array_key_first($this->backends) ?? '')->connection->markRemnantsAsDisabled !== 1) { return []; } + $disabledUsers = $this->deletedUsersIndex->getUsers(); if ($search !== '') { $disabledUsers = array_filter( @@ -444,6 +457,7 @@ public function getDisabledUserList(?int $limit = null, int $offset = 0, string mb_stripos($user->getEmail(), $search) !== false, ); } + return array_map( fn (OfflineUser $user) => $user->getOCName(), array_slice( diff --git a/apps/user_ldap/lib/Wizard.php b/apps/user_ldap/lib/Wizard.php index fd5f12ce0cf4a..99090e060347e 100644 --- a/apps/user_ldap/lib/Wizard.php +++ b/apps/user_ldap/lib/Wizard.php @@ -37,13 +37,14 @@ class Wizard extends LDAPUtility { public function __construct( Configuration $configuration, ILDAPWrapper $ldap, - Access $access + Access $access, ) { parent::__construct($ldap); $this->configuration = $configuration; if (is_null(static::$l)) { static::$l = \OC::$server->get(IL10NFactory::class)->get('user_ldap'); } + $this->access = $access; $this->result = new WizardResult(); $this->logger = \OC::$server->get(LoggerInterface::class); @@ -67,9 +68,11 @@ public function countEntries(string $filter, string $type): int { if (!$this->configuration->usesLdapi()) { $reqs[] = 'ldapPort'; } + if ($type === 'users') { $reqs[] = 'ldapUserFilter'; } + if (!$this->checkRequirements($reqs)) { throw new \Exception('Requirements not met', 400); } @@ -98,6 +101,7 @@ public function countGroups() { if (empty($filter)) { $output = self::$l->n('%n group found', '%n groups found', 0); $this->result->addChange('ldap_group_count', $output); + return $this->result; } @@ -108,6 +112,7 @@ public function countGroups() { if ($e->getCode() === 500) { throw $e; } + return false; } @@ -120,7 +125,9 @@ public function countGroups() { $groupsTotal ); } + $this->result->addChange('ldap_group_count', $output); + return $this->result; } @@ -140,7 +147,9 @@ public function countUsers(): WizardResult { $usersTotal ); } + $this->result->addChange('ldap_user_count', $output); + return $this->result; } @@ -153,6 +162,7 @@ public function countInBaseDN(): WizardResult { // we don't need to provide a filter in this case $total = $this->countEntries('', 'objects'); $this->result->addChange('ldap_test_base', $total); + return $this->result; } @@ -165,6 +175,7 @@ public function countUsersWithAttribute(string $attr, bool $existsCheck = false) if (!$this->configuration->usesLdapi()) { $reqs[] = 'ldapPort'; } + if (!$this->checkRequirements($reqs)) { return false; } @@ -190,6 +201,7 @@ public function detectUserDisplayNameAttribute() { if (!$this->configuration->usesLdapi()) { $reqs[] = 'ldapPort'; } + if (!$this->checkRequirements($reqs)) { return false; } @@ -203,6 +215,7 @@ public function detectUserDisplayNameAttribute() { //no change, but we sent it back to make sure the user interface //is still correct, even if the ajax call was cancelled meanwhile $this->result->addChange('ldap_display_name', $attr); + return $this->result; } } @@ -232,6 +245,7 @@ public function detectEmailAttribute() { if (!$this->configuration->usesLdapi()) { $reqs[] = 'ldapPort'; } + if (!$this->checkRequirements($reqs)) { return false; } @@ -242,6 +256,7 @@ public function detectEmailAttribute() { if ($count > 0) { return false; } + $writeLog = true; } else { $writeLog = false; @@ -262,7 +277,7 @@ public function detectEmailAttribute() { $this->applyFind('ldap_email_attr', $winner); if ($writeLog) { $this->logger->info( - 'The mail attribute has automatically been reset, '. + 'The mail attribute has automatically been reset, ' . 'because the original value did not return any results.', ['app' => 'user_ldap'] ); @@ -281,6 +296,7 @@ public function determineAttributes() { if (!$this->configuration->usesLdapi()) { $reqs[] = 'ldapPort'; } + if (!$this->checkRequirements($reqs)) { return false; } @@ -314,9 +330,11 @@ private function getUserAttributes() { if (!$this->configuration->usesLdapi()) { $reqs[] = 'ldapPort'; } + if (!$this->checkRequirements($reqs)) { return false; } + $cr = $this->getConnection(); if (!$cr) { throw new \Exception('Could not connect to LDAP'); @@ -328,12 +346,14 @@ private function getUserAttributes() { if (!$this->ldap->isResource($rr)) { return false; } + /** @var \LDAP\Result $rr */ $er = $this->ldap->firstEntry($cr, $rr); $attributes = $this->ldap->getAttributes($cr, $er); if ($attributes === false) { return false; } + $pureAttributes = []; for ($i = 0; $i < $attributes['count']; $i++) { $pureAttributes[] = $attributes[$i]; @@ -371,9 +391,11 @@ private function determineGroups(string $dbKey, string $confKey, bool $testMembe if (!$this->configuration->usesLdapi()) { $reqs[] = 'ldapPort'; } + if (!$this->checkRequirements($reqs)) { return false; } + $cr = $this->getConnection(); if (!$cr) { throw new \Exception('Could not connect to LDAP'); @@ -402,8 +424,9 @@ public function fetchGroups(string $dbKey, string $confKey): array { $filterParts = []; foreach ($obclasses as $obclass) { - $filterParts[] = 'objectclass='.$obclass; + $filterParts[] = 'objectclass=' . $obclass; } + //we filter for everything //- that looks like a group and //- has the group display name set @@ -423,9 +446,11 @@ public function fetchGroups(string $dbKey, string $confKey): array { // just in case - no issue known continue; } + $groupNames[] = $item['cn'][0]; $groupEntries[] = $item; } + $offset += $limit; } while ($this->access->hasMoreResults()); @@ -441,6 +466,7 @@ public function fetchGroups(string $dbKey, string $confKey): array { //something is already configured? pre-select it. $this->result->addChange($dbKey, $setFeatures); } + return $groupEntries; } @@ -452,13 +478,16 @@ public function determineGroupMemberAssoc() { if (!$this->configuration->usesLdapi()) { $reqs[] = 'ldapPort'; } + if (!$this->checkRequirements($reqs)) { return false; } + $attribute = $this->detectGroupMemberAssoc(); if ($attribute === false) { return false; } + $this->configuration->setConfiguration(['ldapGroupMemberAssocAttr' => $attribute]); $this->result->addChange('ldap_group_member_assoc_attribute', $attribute); @@ -475,9 +504,11 @@ public function determineGroupObjectClasses() { if (!$this->configuration->usesLdapi()) { $reqs[] = 'ldapPort'; } + if (!$this->checkRequirements($reqs)) { return false; } + $cr = $this->getConnection(); if (!$cr) { throw new \Exception('Could not connect to LDAP'); @@ -503,9 +534,11 @@ public function determineUserObjectClasses() { if (!$this->configuration->usesLdapi()) { $reqs[] = 'ldapPort'; } + if (!$this->checkRequirements($reqs)) { return false; } + $cr = $this->getConnection(); if (!$cr) { throw new \Exception('Could not connect to LDAP'); @@ -534,9 +567,11 @@ public function getGroupFilter() { if (!$this->configuration->usesLdapi()) { $reqs[] = 'ldapPort'; } + if (!$this->checkRequirements($reqs)) { return false; } + //make sure the use display name is set $displayName = $this->configuration->ldapGroupDisplayName; if ($displayName === '') { @@ -544,9 +579,11 @@ public function getGroupFilter() { $this->applyFind('ldap_group_display_name', $d['ldap_group_display_name']); } + $filter = $this->composeLdapFilter(self::LFILTER_GROUP_LIST); $this->applyFind('ldap_group_filter', $filter); + return $this->result; } @@ -559,21 +596,25 @@ public function getUserListFilter() { if (!$this->configuration->usesLdapi()) { $reqs[] = 'ldapPort'; } + if (!$this->checkRequirements($reqs)) { return false; } + //make sure the use display name is set $displayName = $this->configuration->ldapUserDisplayName; if ($displayName === '') { $d = $this->configuration->getDefaults(); $this->applyFind('ldap_display_name', $d['ldap_display_name']); } + $filter = $this->composeLdapFilter(self::LFILTER_USER_LIST); if (!$filter) { throw new \Exception('Cannot create filter'); } $this->applyFind('ldap_userlist_filter', $filter); + return $this->result; } @@ -586,6 +627,7 @@ public function getUserLoginFilter() { if (!$this->configuration->usesLdapi()) { $reqs[] = 'ldapPort'; } + if (!$this->checkRequirements($reqs)) { return false; } @@ -596,6 +638,7 @@ public function getUserLoginFilter() { } $this->applyFind('ldap_login_filter', $filter); + return $this->result; } @@ -608,6 +651,7 @@ public function testLoginName(string $loginName) { if (!$this->configuration->usesLdapi()) { $reqs[] = 'ldapPort'; } + if (!$this->checkRequirements($reqs)) { return false; } @@ -623,9 +667,11 @@ public function testLoginName(string $loginName) { if ($this->ldap->errno($cr) !== 0) { throw new \Exception($this->ldap->error($cr)); } + $filter = str_replace('%uid', $loginName, $this->access->connection->ldapLoginFilter); $this->result->addChange('ldap_test_loginname', $users); $this->result->addChange('ldap_test_effective_filter', $filter); + return $this->result; } @@ -639,6 +685,7 @@ public function guessPortAndTLS() { ])) { return false; } + $this->checkHost(); $portSettings = $this->getPortSettingsToTry(); @@ -647,7 +694,7 @@ public function guessPortAndTLS() { $p = $setting['port']; $t = $setting['tls']; $this->logger->debug( - 'Wiz: trying port '. $p . ', TLS '. $t, + 'Wiz: trying port ' . $p . ', TLS ' . $t, ['app' => 'user_ldap'] ); //connectAndBind may throw Exception, it needs to be caught by the @@ -677,6 +724,7 @@ public function guessPortAndTLS() { ['app' => 'user_ldap'] ); $this->result->addChange('ldap_port', $p); + return $this->result; } } @@ -694,6 +742,7 @@ public function guessBaseDN() { if (!$this->configuration->usesLdapi()) { $reqs[] = 'ldapPort'; } + if (!$this->checkRequirements($reqs)) { return false; } @@ -725,6 +774,7 @@ public function guessBaseDN() { $this->applyFind('ldap_base', $base2); return $this->result; } + array_shift($dparts); } @@ -755,7 +805,7 @@ private function checkHost(): void { //removes Port from Host if (is_array($hostInfo) && isset($hostInfo['port'])) { $port = $hostInfo['port']; - $host = str_replace(':'.$port, '', $host); + $host = str_replace(':' . $port, '', $host); $this->applyFind('ldap_host', $host); $this->applyFind('ldap_port', (string)$port); } @@ -773,15 +823,18 @@ private function detectGroupMemberAssoc() { if (empty($filter)) { return false; } + $cr = $this->getConnection(); if (!$cr) { throw new \Exception('Could not connect to LDAP'); } + $base = $this->configuration->ldapBaseGroups[0] ?: $this->configuration->ldapBase[0]; $rr = $this->ldap->search($cr, $base, $filter, $possibleAttrs, 0, 1000); if (!$this->ldap->isResource($rr)) { return false; } + /** @var \LDAP\Result $rr */ $er = $this->ldap->firstEntry($cr, $rr); while ($this->ldap->isResource($er)) { @@ -794,6 +847,7 @@ private function detectGroupMemberAssoc() { $result[$possibleAttrs[$i]] = $attrs[$possibleAttrs[$i]]['count']; } } + if (!empty($result)) { natsort($result); return key($result); @@ -824,13 +878,16 @@ private function testBaseDN(string $base): bool { $errorNo = $this->ldap->errno($cr); $errorMsg = $this->ldap->error($cr); $this->logger->info( - 'Wiz: Could not search base '.$base.' Error '.$errorNo.': '.$errorMsg, + 'Wiz: Could not search base ' . $base . ' Error ' . $errorNo . ': ' . $errorMsg, ['app' => 'user_ldap'] ); + return false; } + /** @var \LDAP\Result $rr */ $entries = $this->ldap->countEntries($cr, $rr); + return ($entries !== false) && ($entries > 0); } @@ -848,10 +905,12 @@ private function testMemberOf(): bool { if (!$cr) { throw new \Exception('Could not connect to LDAP'); } + $result = $this->access->countUsers('memberOf=*', ['memberOf'], 1); if (is_int($result) && $result > 0) { return true; } + return false; } @@ -874,9 +933,11 @@ private function composeLdapFilter(int $filterType): string { foreach ($objcs as $objc) { $filter .= '(objectclass=' . ldap_escape($objc, '', LDAP_ESCAPE_FILTER) . ')'; } + $filter .= ')'; $parts++; } + //glue group memberships if ($this->configuration->hasMemberOfFilterSupport) { $cns = $this->configuration->ldapUserFilterGroups; @@ -886,12 +947,14 @@ private function composeLdapFilter(int $filterType): string { if (!$cr) { throw new \Exception('Could not connect to LDAP'); } + $base = $this->configuration->ldapBase[0]; foreach ($cns as $cn) { $rr = $this->ldap->search($cr, $base, 'cn=' . ldap_escape($cn, '', LDAP_ESCAPE_FILTER), ['dn', 'primaryGroupToken']); if (!$this->ldap->isResource($rr)) { continue; } + /** @var \LDAP\Result $rr */ $er = $this->ldap->firstEntry($cr, $rr); $attrs = $this->ldap->getAttributes($cr, $er); @@ -899,25 +962,32 @@ private function composeLdapFilter(int $filterType): string { if ($dn === false || $dn === '') { continue; } + $filterPart = '(memberof=' . ldap_escape($dn, '', LDAP_ESCAPE_FILTER) . ')'; if (isset($attrs['primaryGroupToken'])) { $pgt = $attrs['primaryGroupToken'][0]; - $primaryFilterPart = '(primaryGroupID=' . ldap_escape($pgt, '', LDAP_ESCAPE_FILTER) .')'; + $primaryFilterPart = '(primaryGroupID=' . ldap_escape($pgt, '', LDAP_ESCAPE_FILTER) . ')'; $filterPart = '(|' . $filterPart . $primaryFilterPart . ')'; } + $filter .= $filterPart; } + $filter .= ')'; } + $parts++; } + //wrap parts in AND condition if ($parts > 1) { $filter = '(&' . $filter . ')'; } + if ($filter === '') { $filter = '(objectclass=*)'; } + break; case self::LFILTER_GROUP_LIST: @@ -928,9 +998,11 @@ private function composeLdapFilter(int $filterType): string { foreach ($objcs as $objc) { $filter .= '(objectclass=' . ldap_escape($objc, '', LDAP_ESCAPE_FILTER) . ')'; } + $filter .= ')'; $parts++; } + //glue group memberships $cns = $this->configuration->ldapGroupFilterGroups; if (is_array($cns) && count($cns) > 0) { @@ -938,13 +1010,16 @@ private function composeLdapFilter(int $filterType): string { foreach ($cns as $cn) { $filter .= '(cn=' . ldap_escape($cn, '', LDAP_ESCAPE_FILTER) . ')'; } + $filter .= ')'; } + $parts++; //wrap parts in AND condition if ($parts > 1) { $filter = '(&' . $filter . ')'; } + break; case self::LFILTER_LOGIN: @@ -955,6 +1030,7 @@ private function composeLdapFilter(int $filterType): string { if ($userAttributes === false) { throw new \Exception('Failed to get user attributes'); } + $userAttributes = array_change_key_case(array_flip($userAttributes)); $parts = 0; @@ -968,6 +1044,7 @@ private function composeLdapFilter(int $filterType): string { //fallback $attr = 'cn'; } + if ($attr !== '') { $filterUsername = '(' . $attr . $loginpart . ')'; $parts++; @@ -987,6 +1064,7 @@ private function composeLdapFilter(int $filterType): string { foreach ($attrsToFilter as $attribute) { $filterAttributes .= '(' . $attribute . $loginpart . ')'; } + $filterAttributes .= ')'; $parts++; } @@ -995,6 +1073,7 @@ private function composeLdapFilter(int $filterType): string { if ($parts > 1) { $filterLogin = '(|'; } + $filterLogin .= $filterUsername; $filterLogin .= $filterEmail; $filterLogin .= $filterAttributes; @@ -1002,12 +1081,12 @@ private function composeLdapFilter(int $filterType): string { $filterLogin .= ')'; } - $filter = '(&'.$ulf.$filterLogin.')'; + $filter = '(&' . $ulf . $filterLogin . ')'; break; } $this->logger->debug( - 'Wiz: Final filter '.$filter, + 'Wiz: Final filter ' . $filter, ['app' => 'user_ldap'] ); @@ -1028,6 +1107,7 @@ private function connectAndBind(int $port, bool $tls): bool { if (!is_string($host) || !$hostInfo) { throw new \Exception(self::$l->t('Invalid Host')); } + $this->logger->debug( 'Wiz: Attempting to connect', ['app' => 'user_ldap'] @@ -1036,6 +1116,7 @@ private function connectAndBind(int $port, bool $tls): bool { if (!$this->ldap->isResource($cr)) { throw new \Exception(self::$l->t('Invalid Host')); } + /** @var \LDAP\Connection $cr */ //set LDAP options @@ -1069,9 +1150,10 @@ private function connectAndBind(int $port, bool $tls): bool { if ($login === true) { $this->logger->debug( - 'Wiz: Bind successful to Port '. $port . ' TLS ' . (int)$tls, + 'Wiz: Bind successful to Port ' . $port . ' TLS ' . (int)$tls, ['app' => 'user_ldap'] ); + return true; } @@ -1079,6 +1161,7 @@ private function connectAndBind(int $port, bool $tls): bool { //host, port or TLS wrong return false; } + throw new \Exception($error, $errNo); } @@ -1105,6 +1188,7 @@ private function checkRequirements(array $reqs): bool { return false; } } + return true; } @@ -1127,26 +1211,31 @@ public function cumulativeSearchOnAttribute(array $filters, string $attr, int $d || !isset($this->configuration->ldapBase[0])) { return false; } + $base = $this->configuration->ldapBase[0]; $cr = $this->getConnection(); if (!$this->ldap->isResource($cr)) { return false; } + /** @var \LDAP\Connection $cr */ $lastFilter = null; if (isset($filters[count($filters) - 1])) { $lastFilter = $filters[count($filters) - 1]; } + foreach ($filters as $filter) { if ($lastFilter === $filter && count($foundItems) > 0) { //skip when the filter is a wildcard and results were found continue; } + // 20k limit for performance and reason $rr = $this->ldap->search($cr, $base, $filter, [$attr], 0, 20000); if (!$this->ldap->isResource($rr)) { continue; } + /** @var \LDAP\Result $rr */ $entries = $this->ldap->countEntries($cr, $rr); $getEntryFunc = 'firstEntry'; @@ -1155,6 +1244,7 @@ public function cumulativeSearchOnAttribute(array $filters, string $attr, int $d $maxEntries = $entries; $maxF = $filter; } + $dnReadCount = 0; do { $entry = $this->ldap->$getEntryFunc($cr, $rr); @@ -1162,12 +1252,14 @@ public function cumulativeSearchOnAttribute(array $filters, string $attr, int $d if (!$this->ldap->isResource($entry)) { continue 2; } + $rr = $entry; //will be expected by nextEntry next round $attributes = $this->ldap->getAttributes($cr, $entry); $dn = $this->ldap->getDN($cr, $entry); if ($attributes === false || $dn === false || in_array($dn, $dnRead)) { continue; } + $newItems = []; $state = $this->getAttributeValuesFromEntry( $attributes, @@ -1201,10 +1293,12 @@ private function determineFeature(array $objectclasses, string $attr, string $db if (!$cr) { throw new \Exception('Could not connect to LDAP'); } + $p = 'objectclass='; foreach ($objectclasses as $key => $value) { - $objectclasses[$key] = $p.$value; + $objectclasses[$key] = $p . $value; } + $maxEntryObjC = ''; //how deep to dig? @@ -1260,10 +1354,12 @@ private function getAttributeValuesFromEntry(array $result, string $attribute, a if ($key === 'count') { continue; } + if (!in_array($val, $known)) { $known[] = $val; } } + return self::LRESULT_PROCESSED_OK; } else { return self::LRESULT_PROCESSED_SKIP; @@ -1317,6 +1413,7 @@ private function getDefaultLdapPortSettings(): array { ['port' => 7389, 'tls' => false], ['port' => 389, 'tls' => false], ]; + return $settings; } @@ -1339,6 +1436,7 @@ private function getPortSettingsToTry(): array { && stripos($hostInfo['scheme'], 'ldaps') !== false)) { $portSettings[] = ['port' => $port, 'tls' => true]; } + $portSettings[] = ['port' => $port, 'tls' => false]; } elseif ($this->configuration->usesLdapi()) { $portSettings[] = ['port' => 0, 'tls' => false]; diff --git a/apps/user_ldap/lib/WizardResult.php b/apps/user_ldap/lib/WizardResult.php index 09aadd2b781eb..0ccdae4360b54 100644 --- a/apps/user_ldap/lib/WizardResult.php +++ b/apps/user_ldap/lib/WizardResult.php @@ -33,6 +33,7 @@ public function addOptions($key, $values) { if (!is_array($values)) { $values = [$values]; } + $this->options[$key] = $values; } @@ -52,6 +53,7 @@ public function getResultArray() { if (count($this->options) > 0) { $result['options'] = $this->options; } + return $result; } } diff --git a/apps/user_ldap/templates/part.wizard-server.php b/apps/user_ldap/templates/part.wizard-server.php index 94bd59f7b96f0..2fbb2a33bd11d 100644 --- a/apps/user_ldap/templates/part.wizard-server.php +++ b/apps/user_ldap/templates/part.wizard-server.php @@ -15,9 +15,10 @@ foreach ($_['serverConfigurationPrefixes'] as $prefix) { ?> + $sel = ''; ?>>t('%s. Server:', [$i++])); ?>