From 8ffcd23cab6d57598274165bc84f53ace4dbc63a Mon Sep 17 00:00:00 2001 From: zak39 Date: Wed, 6 May 2026 18:26:45 +0200 Subject: [PATCH 01/10] feat(user): Implement UserBackend for workspace user management --- lib/AppInfo/Application.php | 6 ++ lib/User/Backend/UserBackend.php | 180 +++++++++++++++++++++++++++++++ 2 files changed, 186 insertions(+) create mode 100644 lib/User/Backend/UserBackend.php diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index 89564152e..3f8bd6f64 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -39,6 +39,7 @@ use OCA\Workspace\Middleware\WorkspaceManagerAccessMiddleware; use OCA\Workspace\Service\SpaceService; use OCA\Workspace\Service\UserService; +use OCA\Workspace\User\Backend\UserBackend; use OCP\AppFramework\App; use OCP\AppFramework\Bootstrap\IBootContext; use OCP\AppFramework\Bootstrap\IBootstrap; @@ -47,6 +48,7 @@ use OCP\AppFramework\Utility\IControllerMethodReflector; use OCP\IGroupManager; use OCP\IRequest; +use OCP\IUserManager; class Application extends App implements IBootstrap { public const APP_ID = 'workspace'; @@ -98,6 +100,10 @@ public function register(IRegistrationContext $context): void { $groupBackend = $container->get(GroupBackend::class); $groupManager->addBackend($groupBackend); } + + $userManager = $container->get(IUserManager::class); + $userBackend = $container->get(UserBackend::class); + $userManager->registerBackend($userBackend); } public function boot(IBootContext $context): void { diff --git a/lib/User/Backend/UserBackend.php b/lib/User/Backend/UserBackend.php new file mode 100644 index 000000000..bc4491fe6 --- /dev/null +++ b/lib/User/Backend/UserBackend.php @@ -0,0 +1,180 @@ + [ + // 'displayName' => 'Workspace2', + // ], + // ]; + + private array $_users = []; + + public function __construct( + protected INotificationManager $notificationManager, + protected LoggerInterface $logger, + private SpaceMapper $spaceMapper, + ) { + $this->initUsers(); + } + + private function initUsers(): void { + if ($this->_users !== []) { + return; + } + + $spaces = $this->spaceMapper->findAll(); + foreach ($spaces as $space) { + $this->_users['SPACE-UWS-' . $space->getSpaceId()] = [ + 'displayName' => 'Workspace' . $space->getSpaceId(), + ]; + } + } + + /** + * checks whether the user is allowed to change their avatar in Nextcloud + * + * @param string $uid the Nextcloud user name + * @return boolean either the user can or cannot + * @throws \Exception + */ + public function canChangeAvatar($uid) { + return false; + } + + /** + * Get a list of all users + * + * @param string $search + * @param integer $limit + * @param integer $offset + * @return string[] an array of all uids + */ + public function getUsers($search = '', $limit = 10, $offset = 0) { + return array_keys($this->_users); + } + + /** + * check if a user exists + * @param string $uid the username + * @return boolean + * @throws \Exception when connection could not be established + */ + public function userExists($uid) { + return array_key_exists($uid, $this->_users); + } + + /** + * returns whether a user was deleted in LDAP + * + * @param string $uid The username of the user to delete + * @return bool + */ + public function deleteUser($uid) { + return false; + } + + /** + * get the user's home directory + * + * @param string $uid the username + * @return bool|string + * @throws NoUserException + * @throws \Exception + */ + public function getHome($uid) { + return false; + } + + /** + * get display name of the user + * @param string $uid user ID of the user + * @return string|false display name + */ + public function getDisplayName($uid) { + if (isset($this->_users[$uid]) && isset($this->_users[$uid]['displayName'])) { + return $this->_users[$uid]['displayName']; + } + return false; + } + + /** + * set display name of the user + * @param string $uid user ID of the user + * @param string $displayName new display name of the user + * @return string|false display name + */ + public function setDisplayName($uid, $displayName) { + return false; + } + + /** + * Get a list of all display names + * + * @param string $search + * @param int|null $limit + * @param int|null $offset + * @return array an array of all displayNames (value) and the corresponding uids (key) + */ + public function getDisplayNames($search = '', $limit = null, $offset = null) { + return array_combine( + array_keys($this->_users), + array_column($this->_users, 'displayName') + ); + } + + /** + * Check if backend implements actions + * @param int $actions bitwise-or'ed actions + * @return boolean + * + * Returns the supported actions as int to be + * compared with \OC\User\Backend::CREATE_USER etc. + */ + public function implementsActions($actions) { + return (bool)( + // Backend::CHECK_PASSWORD + // | Backend::GET_HOME + (Backend::GET_DISPLAYNAME + // | (($this->access->connection->ldapUserAvatarRule !== 'none') ? Backend::PROVIDE_AVATAR : 0) + | Backend::COUNT_USERS) + // | (((int)$this->access->connection->turnOnPasswordChange === 1)? Backend::SET_PASSWORD :0) + // | $this->userPluginManager->getImplementedActions()) + & $actions); + } + + /** + * @return bool + */ + public function hasUserListings() { + return true; + } + + /** + * counts the users in LDAP + */ + public function countUsers(int $limit = 0): int|false { + return count($this->_users); + } + + + /** + * Backend name to be shown in user management + * @return string the name of the backend to be shown + */ + public function getBackendName() { + return 'WorkSpace'; + } + +} From b7c1a0be3c37c49de20e711df11b1c106ad8f79d Mon Sep 17 00:00:00 2001 From: zak39 Date: Wed, 13 May 2026 18:11:17 +0200 Subject: [PATCH 02/10] feat(group): Enhance group management for user workspaces --- lib/Group/GroupBackend.php | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/lib/Group/GroupBackend.php b/lib/Group/GroupBackend.php index 9ee1e763a..62483d4e0 100644 --- a/lib/Group/GroupBackend.php +++ b/lib/Group/GroupBackend.php @@ -24,6 +24,7 @@ namespace OCA\Workspace\Group; use OCA\Workspace\Service\Group\ConnectedGroupsService; +use OCA\Workspace\User\Backend\UserBackend; use OCP\Group\Backend\ABackend; use OCP\Group\Backend\ICountUsersBackend; use OCP\Group\Backend\INamedBackend; @@ -82,6 +83,17 @@ public function getUserGroups($uid) { } else { $groupIds = []; } + + if (empty($groupIds)) { + if (str_starts_with($uid, 'SPACE-UWS-')) { + // die; + preg_match('/[0-9].*/', $uid, $matches); + $id = $matches[0]; + return [ "SPACE-U-{$id}", "SPACE-GE-{$id}"]; + } + } + + $this->avoidRecurse_groups = $avoid; if (empty($groupIds)) { return []; @@ -94,6 +106,19 @@ public function getUserGroups($uid) { } } + if (str_starts_with($uid, 'SPACE-UWS-')) { + $userManagerWorkspaces = array_filter($groupIds, fn ($gid) => str_starts_with($gid, 'SPACE-GE-')); + $userWorkspaces = array_filter($groupIds, fn ($gid) => str_starts_with($gid, 'SPACE-U-')); + + foreach ($userManagerWorkspaces as $gid) { + $userGroups[] = $gid; + } + + foreach ($userWorkspaces as $gid) { + $userGroups[] = $gid; + } + } + return $userGroups; } @@ -152,6 +177,16 @@ public function usersInGroup($gid, $search = '', $limit = -1, $offset = 0) { } } $this->avoidRecurse_users = $avoid; + + if ( + str_starts_with($gid, 'SPACE-U-') + || str_starts_with($gid, 'SPACE-GE-') + ) { + preg_match('/[0-9].*/', $gid, $matches); + $id = $matches[0]; + $users[] = "SPACE-UWS-{$id}"; + } + return $users; } From acd09b69a829b902fa9c8eb862fd94e971e480ee Mon Sep 17 00:00:00 2001 From: zak39 Date: Wed, 20 May 2026 11:46:48 +0200 Subject: [PATCH 03/10] draft --- composer.json | 2 +- composer.lock | 128 ++++++++++++------------ lib/User/Backend/UserBackendService.php | 71 +++++++++++++ 3 files changed, 138 insertions(+), 63 deletions(-) create mode 100644 lib/User/Backend/UserBackendService.php diff --git a/composer.json b/composer.json index e479b701a..9b012e3a4 100644 --- a/composer.json +++ b/composer.json @@ -17,7 +17,7 @@ "require-dev": { "phpunit/phpunit": "^9.5", "nextcloud/coding-standard": "^1.1", - "nextcloud/ocp": "^30.0", + "nextcloud/ocp": "> 30.0 < 33.0", "symfony/console": "^6.3", "mockery/mockery": "^1.6", "nextcloud/openapi-extractor": "^1.0" diff --git a/composer.lock b/composer.lock index cea32ca93..cbedaca7a 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "c582bfdee5f081d8f02d004beead9d0b", + "content-hash": "f65c97f4b302e12d0a7e707880b9d078", "packages": [], "packages-dev": [ { @@ -203,16 +203,16 @@ }, { "name": "kubawerlos/php-cs-fixer-custom-fixers", - "version": "v3.36.0", + "version": "v3.37.0", "source": { "type": "git", "url": "https://github.com/kubawerlos/php-cs-fixer-custom-fixers.git", - "reference": "e1f97f6463f0b2a22e0dd320948a04132ff9c501" + "reference": "c31fb2aa359dcb25fb48cc6f600810ad284343be" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/kubawerlos/php-cs-fixer-custom-fixers/zipball/e1f97f6463f0b2a22e0dd320948a04132ff9c501", - "reference": "e1f97f6463f0b2a22e0dd320948a04132ff9c501", + "url": "https://api.github.com/repos/kubawerlos/php-cs-fixer-custom-fixers/zipball/c31fb2aa359dcb25fb48cc6f600810ad284343be", + "reference": "c31fb2aa359dcb25fb48cc6f600810ad284343be", "shasum": "" }, "require": { @@ -243,7 +243,7 @@ "description": "A set of custom fixers for PHP CS Fixer", "support": { "issues": "https://github.com/kubawerlos/php-cs-fixer-custom-fixers/issues", - "source": "https://github.com/kubawerlos/php-cs-fixer-custom-fixers/tree/v3.36.0" + "source": "https://github.com/kubawerlos/php-cs-fixer-custom-fixers/tree/v3.37.0" }, "funding": [ { @@ -251,7 +251,7 @@ "type": "github" } ], - "time": "2026-01-31T07:02:11+00:00" + "time": "2026-04-16T16:49:13+00:00" }, { "name": "mockery/mockery", @@ -443,29 +443,29 @@ }, { "name": "nextcloud/ocp", - "version": "v30.0.9", + "version": "v32.0.8", "source": { "type": "git", "url": "https://github.com/nextcloud-deps/ocp.git", - "reference": "d6000d61d8ae708199bd90e1da0c794712c81030" + "reference": "9eff94dcc966d95c1f1621cad35f0d83160b42eb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nextcloud-deps/ocp/zipball/d6000d61d8ae708199bd90e1da0c794712c81030", - "reference": "d6000d61d8ae708199bd90e1da0c794712c81030", + "url": "https://api.github.com/repos/nextcloud-deps/ocp/zipball/9eff94dcc966d95c1f1621cad35f0d83160b42eb", + "reference": "9eff94dcc966d95c1f1621cad35f0d83160b42eb", "shasum": "" }, "require": { - "php": "~8.0 || ~8.1 || ~8.2 || ~8.3", + "php": "~8.1 || ~8.2 || ~8.3 || ~8.4", "psr/clock": "^1.0", "psr/container": "^2.0.2", "psr/event-dispatcher": "^1.0", - "psr/log": "^2.0.0" + "psr/log": "^3.0.2" }, "type": "library", "extra": { "branch-alias": { - "dev-stable30": "30.0.0-dev" + "dev-stable32": "32.0.0-dev" } }, "notification-url": "https://packagist.org/downloads/", @@ -476,14 +476,18 @@ { "name": "Christoph Wurst", "email": "christoph@winzerhof-wurst.at" + }, + { + "name": "Joas Schilling", + "email": "coding@schilljs.com" } ], - "description": "Composer package containing Nextcloud's public API (classes, interfaces)", + "description": "Composer package containing Nextcloud's public OCP API and the unstable NCU API", "support": { "issues": "https://github.com/nextcloud-deps/ocp/issues", - "source": "https://github.com/nextcloud-deps/ocp/tree/v30.0.9" + "source": "https://github.com/nextcloud-deps/ocp/tree/v32.0.8" }, - "time": "2025-03-31T13:55:31+00:00" + "time": "2026-03-27T01:17:35+00:00" }, { "name": "nextcloud/openapi-extractor", @@ -710,16 +714,16 @@ }, { "name": "php-cs-fixer/shim", - "version": "v3.94.2", + "version": "v3.95.1", "source": { "type": "git", "url": "https://github.com/PHP-CS-Fixer/shim.git", - "reference": "80fd29f44a736136a2f05bae5464816a444b91d1" + "reference": "f81ccf51ca60cc9dd21358ffba0e79ebd2ebb78a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/shim/zipball/80fd29f44a736136a2f05bae5464816a444b91d1", - "reference": "80fd29f44a736136a2f05bae5464816a444b91d1", + "url": "https://api.github.com/repos/PHP-CS-Fixer/shim/zipball/f81ccf51ca60cc9dd21358ffba0e79ebd2ebb78a", + "reference": "f81ccf51ca60cc9dd21358ffba0e79ebd2ebb78a", "shasum": "" }, "require": { @@ -756,9 +760,9 @@ "description": "A tool to automatically fix PHP code style", "support": { "issues": "https://github.com/PHP-CS-Fixer/shim/issues", - "source": "https://github.com/PHP-CS-Fixer/shim/tree/v3.94.2" + "source": "https://github.com/PHP-CS-Fixer/shim/tree/v3.95.1" }, - "time": "2026-02-20T16:14:17+00:00" + "time": "2026-04-12T17:00:34+00:00" }, { "name": "phpstan/phpdoc-parser", @@ -1390,16 +1394,16 @@ }, { "name": "psr/log", - "version": "2.0.0", + "version": "3.0.2", "source": { "type": "git", "url": "https://github.com/php-fig/log.git", - "reference": "ef29f6d262798707a9edd554e2b82517ef3a9376" + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/ef29f6d262798707a9edd554e2b82517ef3a9376", - "reference": "ef29f6d262798707a9edd554e2b82517ef3a9376", + "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", "shasum": "" }, "require": { @@ -1408,7 +1412,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0.x-dev" + "dev-master": "3.x-dev" } }, "autoload": { @@ -1434,9 +1438,9 @@ "psr-3" ], "support": { - "source": "https://github.com/php-fig/log/tree/2.0.0" + "source": "https://github.com/php-fig/log/tree/3.0.2" }, - "time": "2021-07-14T16:41:46+00:00" + "time": "2024-09-11T13:17:53+00:00" }, { "name": "sebastian/cli-parser", @@ -2451,16 +2455,16 @@ }, { "name": "symfony/console", - "version": "v6.4.34", + "version": "v6.4.36", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "7b1f1c37eff5910ddda2831345467e593a5120ad" + "reference": "9f481cfb580db8bcecc9b2d4c63f3e13df022ad5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/7b1f1c37eff5910ddda2831345467e593a5120ad", - "reference": "7b1f1c37eff5910ddda2831345467e593a5120ad", + "url": "https://api.github.com/repos/symfony/console/zipball/9f481cfb580db8bcecc9b2d4c63f3e13df022ad5", + "reference": "9f481cfb580db8bcecc9b2d4c63f3e13df022ad5", "shasum": "" }, "require": { @@ -2525,7 +2529,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v6.4.34" + "source": "https://github.com/symfony/console/tree/v6.4.36" }, "funding": [ { @@ -2545,7 +2549,7 @@ "type": "tidelift" } ], - "time": "2026-02-23T15:42:15+00:00" + "time": "2026-03-27T15:30:51+00:00" }, { "name": "symfony/deprecation-contracts", @@ -2616,16 +2620,16 @@ }, { "name": "symfony/polyfill-ctype", - "version": "v1.33.0", + "version": "v1.36.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638" + "reference": "141046a8f9477948ff284fa65be2095baafb94f2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638", - "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/141046a8f9477948ff284fa65be2095baafb94f2", + "reference": "141046a8f9477948ff284fa65be2095baafb94f2", "shasum": "" }, "require": { @@ -2675,7 +2679,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.33.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.36.0" }, "funding": [ { @@ -2695,20 +2699,20 @@ "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2026-04-10T16:19:22+00:00" }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.33.0", + "version": "v1.36.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70" + "reference": "ad1b7b9092976d6c948b8a187cec9faaea9ec1df" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/380872130d3a5dd3ace2f4010d95125fde5d5c70", - "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/ad1b7b9092976d6c948b8a187cec9faaea9ec1df", + "reference": "ad1b7b9092976d6c948b8a187cec9faaea9ec1df", "shasum": "" }, "require": { @@ -2757,7 +2761,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.33.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.36.0" }, "funding": [ { @@ -2777,11 +2781,11 @@ "type": "tidelift" } ], - "time": "2025-06-27T09:58:17+00:00" + "time": "2026-04-10T16:19:22+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.33.0", + "version": "v1.36.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", @@ -2842,7 +2846,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.33.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.36.0" }, "funding": [ { @@ -2866,16 +2870,16 @@ }, { "name": "symfony/polyfill-mbstring", - "version": "v1.33.0", + "version": "v1.36.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493" + "reference": "6a21eb99c6973357967f6ce3708cd55a6bec6315" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493", - "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6a21eb99c6973357967f6ce3708cd55a6bec6315", + "reference": "6a21eb99c6973357967f6ce3708cd55a6bec6315", "shasum": "" }, "require": { @@ -2927,7 +2931,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.33.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.36.0" }, "funding": [ { @@ -2947,7 +2951,7 @@ "type": "tidelift" } ], - "time": "2024-12-23T08:48:59+00:00" + "time": "2026-04-10T17:25:58+00:00" }, { "name": "symfony/service-contracts", @@ -3038,16 +3042,16 @@ }, { "name": "symfony/string", - "version": "v7.4.6", + "version": "v7.4.8", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "9f209231affa85aa930a5e46e6eb03381424b30b" + "reference": "114ac57257d75df748eda23dd003878080b8e688" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/9f209231affa85aa930a5e46e6eb03381424b30b", - "reference": "9f209231affa85aa930a5e46e6eb03381424b30b", + "url": "https://api.github.com/repos/symfony/string/zipball/114ac57257d75df748eda23dd003878080b8e688", + "reference": "114ac57257d75df748eda23dd003878080b8e688", "shasum": "" }, "require": { @@ -3105,7 +3109,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v7.4.6" + "source": "https://github.com/symfony/string/tree/v7.4.8" }, "funding": [ { @@ -3125,7 +3129,7 @@ "type": "tidelift" } ], - "time": "2026-02-09T09:33:46+00:00" + "time": "2026-03-24T13:12:05+00:00" }, { "name": "theseer/tokenizer", diff --git a/lib/User/Backend/UserBackendService.php b/lib/User/Backend/UserBackendService.php new file mode 100644 index 000000000..c2534a645 --- /dev/null +++ b/lib/User/Backend/UserBackendService.php @@ -0,0 +1,71 @@ +getUID() => [ + 'displayName' => $user->getDisplayName(), + ] + ]; + } + + public function initUsers(): void { + $users = $this->userManager->searchDisplayName('Workspace'); + $users = array_filter($users, fn ($user) => str_starts_with($user->getUID(), 'SPACE-UWS-')); + $this->users = array_map($this->format(...), $users); + } + + public function createUser(int $spaceId): void { + try { + $user = $this->userManager->createUser('SPACE-UWS-' . $spaceId, 'aaa'); + $user->setDisplayName('Workspace' . $spaceId); + } catch (\Exception $e) { + var_dump($e->getMessage()); + } + } + + public function getUsers(): array { + if (empty($this->users)) { + $this->initUsers(); + } + + return $this->users; + } + + public function userExists(string $uid): bool { + $users = $this->getUsers(); + return isset($users[$uid]); + } + + public function getUser(string $uid): ?IUser { + $users = $this->getUsers(); + if (isset($users[$uid])) { + return $users[$uid]; + } + return null; + } + + public function getUserBySpaceId(int $spaceId): array { + $uid = 'SPACE-UWS-' . $spaceId; + $users = $this->getUsers(); + if (isset($users[$uid])) { + return $users[$uid]; + } + return []; + } +} From 0e7868ae4c1b9b53373a243cb74b96d234f458a3 Mon Sep 17 00:00:00 2001 From: Sebastien Marinier Date: Wed, 20 May 2026 19:18:34 +0200 Subject: [PATCH 04/10] feat: user workspace is automatically added to the SPACE-U group, is searchable, and can't be disabled --- lib/Group/GroupBackend.php | 52 ++++++-------- lib/User/Backend/UserBackend.php | 91 +++++++++++++++++++++---- lib/User/Backend/UserBackendService.php | 71 ------------------- 3 files changed, 97 insertions(+), 117 deletions(-) delete mode 100644 lib/User/Backend/UserBackendService.php diff --git a/lib/Group/GroupBackend.php b/lib/Group/GroupBackend.php index 62483d4e0..f06a1af82 100644 --- a/lib/Group/GroupBackend.php +++ b/lib/Group/GroupBackend.php @@ -84,21 +84,22 @@ public function getUserGroups($uid) { $groupIds = []; } - if (empty($groupIds)) { - if (str_starts_with($uid, 'SPACE-UWS-')) { - // die; - preg_match('/[0-9].*/', $uid, $matches); - $id = $matches[0]; - return [ "SPACE-U-{$id}", "SPACE-GE-{$id}"]; + $userGroups = []; + if (str_starts_with($uid, 'SPACE-UWS-')) { + $spaceId = (int)substr($uid, 10); + if ($spaceId !== 0) { + $userGroups[] = "SPACE-U-{$spaceId}"; + /// @TODO is it necessary ? + // $groupIds[] = "SPACE-GE-{$spaceId}"; } } + $this->avoidRecurse_groups = $avoid; if (empty($groupIds)) { - return []; + return $userGroups; } - $userGroups = []; foreach ($groupIds as $gid) { $connectedGids = $this->connectedGroups->getConnectedSpaceToGroupIds($gid); if ($connectedGids !== null && $user->isEnabled()) { @@ -106,19 +107,6 @@ public function getUserGroups($uid) { } } - if (str_starts_with($uid, 'SPACE-UWS-')) { - $userManagerWorkspaces = array_filter($groupIds, fn ($gid) => str_starts_with($gid, 'SPACE-GE-')); - $userWorkspaces = array_filter($groupIds, fn ($gid) => str_starts_with($gid, 'SPACE-U-')); - - foreach ($userManagerWorkspaces as $gid) { - $userGroups[] = $gid; - } - - foreach ($userWorkspaces as $gid) { - $userGroups[] = $gid; - } - } - return $userGroups; } @@ -143,7 +131,7 @@ public function getGroups($search = '', $limit = -1, $offset = 0) { */ public function groupExists($gid) { // @note : need to implement, but this backend doesn't manage existence of connected groups - return $this->connectedGroups->hasConnectedGroups($gid); + return str_starts_with($gid, 'SPACE-U-') || $this->connectedGroups->hasConnectedGroups($gid); } /** @@ -159,12 +147,19 @@ public function usersInGroup($gid, $search = '', $limit = -1, $offset = 0) { return []; } + $users = []; + if (str_starts_with($gid, 'SPACE-U-')) { + $spaceId = (int)substr($gid, 8); + if ($spaceId !== 0) { + $users[] = "SPACE-UWS-{$spaceId}"; + } + } + $groups = $this->connectedGroups->getConnectedGroupsToSpaceGroup($gid); if ($groups === null) { - return []; + return $users; } - $users = []; $avoid = $this->avoidRecurse_users; $this->avoidRecurse_users = true; foreach ($groups as $group) { @@ -178,15 +173,6 @@ public function usersInGroup($gid, $search = '', $limit = -1, $offset = 0) { } $this->avoidRecurse_users = $avoid; - if ( - str_starts_with($gid, 'SPACE-U-') - || str_starts_with($gid, 'SPACE-GE-') - ) { - preg_match('/[0-9].*/', $gid, $matches); - $id = $matches[0]; - $users[] = "SPACE-UWS-{$id}"; - } - return $users; } diff --git a/lib/User/Backend/UserBackend.php b/lib/User/Backend/UserBackend.php index bc4491fe6..04cf7d313 100644 --- a/lib/User/Backend/UserBackend.php +++ b/lib/User/Backend/UserBackend.php @@ -8,38 +8,36 @@ use OCP\IUserBackend; use OCP\Notification\IManager as INotificationManager; use OCP\User\Backend\ILimitAwareCountUsersBackend; +use OCP\User\Backend\IProvideEnabledStateBackend; use OCP\UserInterface; use Psr\Log\LoggerInterface; -class UserBackend implements IUserBackend, UserInterface, ILimitAwareCountUsersBackend { +class UserBackend implements IUserBackend, UserInterface, ILimitAwareCountUsersBackend, IProvideEnabledStateBackend { - // static private $_users = [ - // 'SPACE-UWS-2' => [ - // 'displayName' => 'Workspace2', - // ], - // ]; - - private array $_users = []; + /** @var array */ + private $_users = null; public function __construct( protected INotificationManager $notificationManager, protected LoggerInterface $logger, private SpaceMapper $spaceMapper, ) { - $this->initUsers(); } private function initUsers(): void { - if ($this->_users !== []) { + if ($this->_users !== null) { return; } + $users = []; $spaces = $this->spaceMapper->findAll(); + /** @var Space $space */ foreach ($spaces as $space) { - $this->_users['SPACE-UWS-' . $space->getSpaceId()] = [ - 'displayName' => 'Workspace' . $space->getSpaceId(), + $users['SPACE-UWS-' . $space->getSpaceId()] = [ + 'displayName' => $space->getSpaceName(), ]; } + $this->_users = $users; } /** @@ -62,7 +60,29 @@ public function canChangeAvatar($uid) { * @return string[] an array of all uids */ public function getUsers($search = '', $limit = 10, $offset = 0) { - return array_keys($this->_users); + $this->initUsers(); + $limit = (is_int($limit) && $limit >= 0) ? $limit : null; + if ($limit === null && $offset === 0 && $search === '') { + return array_keys($this->_users); + } + if ($search === '') { + return array_slice(array_keys($this->_users), $offset, $limit); + } + $search = strtolower($search); + $users = []; + $count = 0; + foreach ($this->_users as $uid => $user) { + if (str_contains(strtolower($user['displayName']), $search)) { + if ($count >= $offset) { + $users[] = $uid; + } + $count++; + if ($limit !== null && $count >= $offset + $limit) { + break; + } + } + } + return $users; } /** @@ -72,6 +92,7 @@ public function getUsers($search = '', $limit = 10, $offset = 0) { * @throws \Exception when connection could not be established */ public function userExists($uid) { + $this->initUsers(); return array_key_exists($uid, $this->_users); } @@ -103,6 +124,7 @@ public function getHome($uid) { * @return string|false display name */ public function getDisplayName($uid) { + $this->initUsers(); if (isset($this->_users[$uid]) && isset($this->_users[$uid]['displayName'])) { return $this->_users[$uid]['displayName']; } @@ -128,6 +150,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) { + $this->initUsers(); return array_combine( array_keys($this->_users), array_column($this->_users, 'displayName') @@ -165,6 +188,7 @@ public function hasUserListings() { * counts the users in LDAP */ public function countUsers(int $limit = 0): int|false { + $this->initUsers(); return count($this->_users); } @@ -177,4 +201,45 @@ public function getBackendName() { return 'WorkSpace'; } + /** + * @since 28.0.0 + * + * @param callable():bool $queryDatabaseValue A callable to query the enabled state from database + */ + public function isUserEnabled(string $uid, callable $queryDatabaseValue): bool { + if (str_starts_with($uid, 'SPACE-UWS-')) { + $spaceId = (int)substr($uid, 10); + if ($spaceId !== 0) { + $space = $this->spaceMapper->find($spaceId); + return $space !== null; + } + return false; + } + return true; + } + + /** + * @since 28.0.0 + * + * @param callable():bool $queryDatabaseValue A callable to query the enabled state from database + * @param callable(bool):void $setDatabaseValue A callable to set the enabled state in the database. + */ + public function setUserEnabled(string $uid, bool $enabled, callable $queryDatabaseValue, callable $setDatabaseValue): bool { + if (str_starts_with($uid, 'SPACE-UWS-')) { + return !$enabled; // refuse change + } + return true; + } + + /** + * Get the list of disabled users, to merge with the ones disabled in database + * + * @since 28.0.0 + * @since 30.0.0 $search parameter added + * + * @return string[] + */ + public function getDisabledUserList(?int $limit = null, int $offset = 0, string $search = ''): array { + return []; + } } diff --git a/lib/User/Backend/UserBackendService.php b/lib/User/Backend/UserBackendService.php deleted file mode 100644 index c2534a645..000000000 --- a/lib/User/Backend/UserBackendService.php +++ /dev/null @@ -1,71 +0,0 @@ -getUID() => [ - 'displayName' => $user->getDisplayName(), - ] - ]; - } - - public function initUsers(): void { - $users = $this->userManager->searchDisplayName('Workspace'); - $users = array_filter($users, fn ($user) => str_starts_with($user->getUID(), 'SPACE-UWS-')); - $this->users = array_map($this->format(...), $users); - } - - public function createUser(int $spaceId): void { - try { - $user = $this->userManager->createUser('SPACE-UWS-' . $spaceId, 'aaa'); - $user->setDisplayName('Workspace' . $spaceId); - } catch (\Exception $e) { - var_dump($e->getMessage()); - } - } - - public function getUsers(): array { - if (empty($this->users)) { - $this->initUsers(); - } - - return $this->users; - } - - public function userExists(string $uid): bool { - $users = $this->getUsers(); - return isset($users[$uid]); - } - - public function getUser(string $uid): ?IUser { - $users = $this->getUsers(); - if (isset($users[$uid])) { - return $users[$uid]; - } - return null; - } - - public function getUserBySpaceId(int $spaceId): array { - $uid = 'SPACE-UWS-' . $spaceId; - $users = $this->getUsers(); - if (isset($users[$uid])) { - return $users[$uid]; - } - return []; - } -} From 1194edd87cd072fe62487a9aa8885c20c0a75f8c Mon Sep 17 00:00:00 2001 From: zak39 Date: Wed, 24 Jun 2026 15:57:08 +0200 Subject: [PATCH 05/10] ci: update PHP version to 8.3 in workflow configuration --- .github/workflows/php.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index 08aa20e37..3d5a29c06 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - php-versions: [8.2] + php-versions: [8.3] nextcloud-versions: ['stable32', 'master'] include: - php: 8.2 From e02cbd7f8a0874197073ba677c166b5af1f32e40 Mon Sep 17 00:00:00 2001 From: zak39 Date: Wed, 24 Jun 2026 16:04:39 +0200 Subject: [PATCH 06/10] chore: Clean up whitespace and remove unused imports in GroupBackend and UserBackendService --- lib/Group/GroupBackend.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/Group/GroupBackend.php b/lib/Group/GroupBackend.php index f06a1af82..712f46972 100644 --- a/lib/Group/GroupBackend.php +++ b/lib/Group/GroupBackend.php @@ -24,7 +24,6 @@ namespace OCA\Workspace\Group; use OCA\Workspace\Service\Group\ConnectedGroupsService; -use OCA\Workspace\User\Backend\UserBackend; use OCP\Group\Backend\ABackend; use OCP\Group\Backend\ICountUsersBackend; use OCP\Group\Backend\INamedBackend; @@ -180,7 +179,6 @@ public function getBackendName(): string { return 'WorkspaceGroupBackend'; } - public function countUsersInGroup(string $gid, string $search = ''): int { $users = $this->usersInGroup($gid); From cd339b91dd623f6d1fb0704e28183019aeadc0f3 Mon Sep 17 00:00:00 2001 From: zak39 Date: Wed, 24 Jun 2026 16:59:51 +0200 Subject: [PATCH 07/10] refactor: Integrate Pull Request #1664 fixes by @smarinier --- lib/Group/GroupBackend.php | 14 ++++++++++++-- lib/Service/Group/UserGroup.php | 9 ++++++--- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/lib/Group/GroupBackend.php b/lib/Group/GroupBackend.php index 712f46972..22735d581 100644 --- a/lib/Group/GroupBackend.php +++ b/lib/Group/GroupBackend.php @@ -93,8 +93,6 @@ public function getUserGroups($uid) { } } - - $this->avoidRecurse_groups = $avoid; if (empty($groupIds)) { return $userGroups; @@ -130,6 +128,10 @@ public function getGroups($search = '', $limit = -1, $offset = 0) { */ public function groupExists($gid) { // @note : need to implement, but this backend doesn't manage existence of connected groups + if (str_starts_with($gid, 'SPACE-U-') && !$this->avoidRecurse_groups) { + return true; + } + return str_starts_with($gid, 'SPACE-U-') || $this->connectedGroups->hasConnectedGroups($gid); } @@ -201,4 +203,12 @@ public function countUsersInGroup(string $gid, string $search = ''): int { } return $nbUsers; } + + public function disable() { + $this->avoidRecurse_users = $this->avoidRecurse_groups = true; + } + + public function enable() { + $this->avoidRecurse_users = $this->avoidRecurse_groups = false; + } }; diff --git a/lib/Service/Group/UserGroup.php b/lib/Service/Group/UserGroup.php index 3676b48b6..ab9aaff75 100644 --- a/lib/Service/Group/UserGroup.php +++ b/lib/Service/Group/UserGroup.php @@ -26,17 +26,17 @@ use OCA\Workspace\Db\Space; use OCA\Workspace\Exceptions\CreateGroupException; +use OCA\Workspace\Group\GroupBackend; use OCP\AppFramework\Http; use OCP\AppFramework\Services\IAppConfig; use OCP\IGroup; use OCP\IGroupManager; +use OCP\Server; class UserGroup extends GroupsWorkspace { - private IGroupManager $groupManager; - public function __construct(IGroupManager $groupManager, IAppConfig $appConfig) { + public function __construct(IAppConfig $appConfig, private IGroupManager $groupManager) { parent::__construct($appConfig); - $this->groupManager = $groupManager; } public static function get(int $spaceId): string { @@ -48,7 +48,10 @@ public static function getPrefix(): string { } public function create(Space $space): IGroup { + $groupBackend = Server::get(GroupBackend::class); + $groupBackend->disable(); $group = $this->groupManager->createGroup(self::PREFIX_GID_USERS . $space->getId()); + $groupBackend->enable(); if (is_null($group)) { throw new CreateGroupException('Error to create a Space Manager group.', Http::STATUS_CONFLICT); From faf2f760cd9543cd876cc88184097bba5312f936 Mon Sep 17 00:00:00 2001 From: zak39 Date: Wed, 24 Jun 2026 17:05:15 +0200 Subject: [PATCH 08/10] chore(php): Apply coding style standard composer run cs:fix --- lib/Service/Group/UserGroup.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/Service/Group/UserGroup.php b/lib/Service/Group/UserGroup.php index ab9aaff75..90b0f7668 100644 --- a/lib/Service/Group/UserGroup.php +++ b/lib/Service/Group/UserGroup.php @@ -35,7 +35,10 @@ class UserGroup extends GroupsWorkspace { - public function __construct(IAppConfig $appConfig, private IGroupManager $groupManager) { + public function __construct( + IAppConfig $appConfig, + private IGroupManager $groupManager, + ) { parent::__construct($appConfig); } From 8c083362fe5b94210da260dcb622835b8a6fbd7b Mon Sep 17 00:00:00 2001 From: zak39 Date: Wed, 24 Jun 2026 17:38:02 +0200 Subject: [PATCH 09/10] refactor: Integrate Pull Request #1664 fixes by @smarinier part 2 --- lib/Group/GroupBackend.php | 4 ++-- lib/Service/Group/UserGroup.php | 5 +---- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/lib/Group/GroupBackend.php b/lib/Group/GroupBackend.php index 22735d581..2b92010f8 100644 --- a/lib/Group/GroupBackend.php +++ b/lib/Group/GroupBackend.php @@ -131,8 +131,7 @@ public function groupExists($gid) { if (str_starts_with($gid, 'SPACE-U-') && !$this->avoidRecurse_groups) { return true; } - - return str_starts_with($gid, 'SPACE-U-') || $this->connectedGroups->hasConnectedGroups($gid); + return $this->connectedGroups->hasConnectedGroups($gid); } /** @@ -181,6 +180,7 @@ public function getBackendName(): string { return 'WorkspaceGroupBackend'; } + public function countUsersInGroup(string $gid, string $search = ''): int { $users = $this->usersInGroup($gid); diff --git a/lib/Service/Group/UserGroup.php b/lib/Service/Group/UserGroup.php index 90b0f7668..ab9aaff75 100644 --- a/lib/Service/Group/UserGroup.php +++ b/lib/Service/Group/UserGroup.php @@ -35,10 +35,7 @@ class UserGroup extends GroupsWorkspace { - public function __construct( - IAppConfig $appConfig, - private IGroupManager $groupManager, - ) { + public function __construct(IAppConfig $appConfig, private IGroupManager $groupManager) { parent::__construct($appConfig); } From ff47495404fcf2c9d6f33de833961825ab442dc2 Mon Sep 17 00:00:00 2001 From: zak39 Date: Wed, 24 Jun 2026 17:49:40 +0200 Subject: [PATCH 10/10] chore(php): Apply coding style standard composer run cs:fix --- lib/Service/Group/UserGroup.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/Service/Group/UserGroup.php b/lib/Service/Group/UserGroup.php index ab9aaff75..90b0f7668 100644 --- a/lib/Service/Group/UserGroup.php +++ b/lib/Service/Group/UserGroup.php @@ -35,7 +35,10 @@ class UserGroup extends GroupsWorkspace { - public function __construct(IAppConfig $appConfig, private IGroupManager $groupManager) { + public function __construct( + IAppConfig $appConfig, + private IGroupManager $groupManager, + ) { parent::__construct($appConfig); }