summaryrefslogtreecommitdiffstats
path: root/apps/user_ldap/lib
diff options
context:
space:
mode:
Diffstat (limited to 'apps/user_ldap/lib')
-rw-r--r--apps/user_ldap/lib/Access.php8
-rw-r--r--apps/user_ldap/lib/AppInfo/Application.php6
-rw-r--r--apps/user_ldap/lib/Command/CheckGroup.php181
-rw-r--r--apps/user_ldap/lib/Controller/ConfigAPIController.php102
-rw-r--r--apps/user_ldap/lib/Db/GroupMembership.php48
-rw-r--r--apps/user_ldap/lib/Db/GroupMembershipMapper.php77
-rw-r--r--apps/user_ldap/lib/Group_LDAP.php39
-rw-r--r--apps/user_ldap/lib/Group_Proxy.php48
-rw-r--r--apps/user_ldap/lib/Jobs/UpdateGroups.php237
-rw-r--r--apps/user_ldap/lib/LDAP.php9
-rw-r--r--apps/user_ldap/lib/Migration/Version1190Date20230706134108.php113
-rw-r--r--apps/user_ldap/lib/Migration/Version1190Date20230706134109.php46
-rw-r--r--apps/user_ldap/lib/Proxy.php2
-rw-r--r--apps/user_ldap/lib/Service/UpdateGroupsService.php194
14 files changed, 790 insertions, 320 deletions
diff --git a/apps/user_ldap/lib/Access.php b/apps/user_ldap/lib/Access.php
index 3fbb003ed51..0b115c42764 100644
--- a/apps/user_ldap/lib/Access.php
+++ b/apps/user_ldap/lib/Access.php
@@ -1545,12 +1545,16 @@ class Access extends LDAPUtility {
return '';
}
// wildcards don't work with some attributes
- $filter[] = $fallbackAttribute . '=' . $originalSearch;
+ if ($originalSearch !== '') {
+ $filter[] = $fallbackAttribute . '=' . $originalSearch;
+ }
$filter[] = $fallbackAttribute . '=' . $search;
} else {
foreach ($searchAttributes as $attribute) {
// wildcards don't work with some attributes
- $filter[] = $attribute . '=' . $originalSearch;
+ if ($originalSearch !== '') {
+ $filter[] = $attribute . '=' . $originalSearch;
+ }
$filter[] = $attribute . '=' . $search;
}
}
diff --git a/apps/user_ldap/lib/AppInfo/Application.php b/apps/user_ldap/lib/AppInfo/Application.php
index 757ac141d3d..d6fb062a028 100644
--- a/apps/user_ldap/lib/AppInfo/Application.php
+++ b/apps/user_ldap/lib/AppInfo/Application.php
@@ -59,7 +59,6 @@ use OCP\Notification\IManager as INotificationManager;
use OCP\Share\IManager as IShareManager;
use Psr\Container\ContainerInterface;
use Psr\Log\LoggerInterface;
-use Symfony\Component\EventDispatcher\EventDispatcherInterface;
class Application extends App implements IBootstrap {
public function __construct() {
@@ -120,7 +119,6 @@ class Application extends App implements IBootstrap {
$context->injectFn(function (
INotificationManager $notificationManager,
IAppContainer $appContainer,
- EventDispatcherInterface $legacyDispatcher,
IEventDispatcher $dispatcher,
IGroupManager $groupManager,
User_Proxy $userBackend,
@@ -136,7 +134,7 @@ class Application extends App implements IBootstrap {
$groupManager->addBackend($groupBackend);
$userBackendRegisteredEvent = new UserBackendRegistered($userBackend, $userPluginManager);
- $legacyDispatcher->dispatch('OCA\\User_LDAP\\User\\User::postLDAPBackendAdded', $userBackendRegisteredEvent);
+ $dispatcher->dispatch('OCA\\User_LDAP\\User\\User::postLDAPBackendAdded', $userBackendRegisteredEvent);
$dispatcher->dispatchTyped($userBackendRegisteredEvent);
$groupBackendRegisteredEvent = new GroupBackendRegistered($groupBackend, $groupPluginManager);
$dispatcher->dispatchTyped($groupBackendRegisteredEvent);
@@ -153,7 +151,7 @@ class Application extends App implements IBootstrap {
);
}
- private function registerBackendDependents(IAppContainer $appContainer, EventDispatcherInterface $dispatcher) {
+ private function registerBackendDependents(IAppContainer $appContainer, IEventDispatcher $dispatcher) {
$dispatcher->addListener(
'OCA\\Files_External::loadAdditionalBackends',
function () use ($appContainer) {
diff --git a/apps/user_ldap/lib/Command/CheckGroup.php b/apps/user_ldap/lib/Command/CheckGroup.php
new file mode 100644
index 00000000000..68f96512a9b
--- /dev/null
+++ b/apps/user_ldap/lib/Command/CheckGroup.php
@@ -0,0 +1,181 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright Copyright (c) 2016, ownCloud, Inc.
+ *
+ * @author Arthur Schiwon <blizzz@arthur-schiwon.de>
+ * @author Christoph Wurst <christoph@winzerhof-wurst.at>
+ * @author Côme Chilliet <come.chilliet@nextcloud.com>
+ * @author Joas Schilling <coding@schilljs.com>
+ * @author Morris Jobke <hey@morrisjobke.de>
+ * @author Roeland Jago Douma <roeland@famdouma.nl>
+ *
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace OCA\User_LDAP\Command;
+
+use OCA\User_LDAP\Group_Proxy;
+use OCA\User_LDAP\Helper;
+use OCA\User_LDAP\Mapping\GroupMapping;
+use OCA\User_LDAP\Service\UpdateGroupsService;
+use OCP\EventDispatcher\IEventDispatcher;
+use OCP\Group\Events\GroupCreatedEvent;
+use OCP\Group\Events\UserAddedEvent;
+use OCP\Group\Events\UserRemovedEvent;
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Output\OutputInterface;
+
+class CheckGroup extends Command {
+ public function __construct(
+ private UpdateGroupsService $service,
+ protected Group_Proxy $backend,
+ protected Helper $helper,
+ protected GroupMapping $mapping,
+ protected IEventDispatcher $dispatcher,
+ ) {
+ parent::__construct();
+ }
+
+ protected function configure(): void {
+ $this
+ ->setName('ldap:check-group')
+ ->setDescription('checks whether a group exists on LDAP.')
+ ->addArgument(
+ 'ocName',
+ InputArgument::REQUIRED,
+ 'the group name as used in Nextcloud, or the LDAP DN'
+ )
+ ->addOption(
+ 'force',
+ null,
+ InputOption::VALUE_NONE,
+ 'ignores disabled LDAP configuration'
+ )
+ ->addOption(
+ 'update',
+ null,
+ InputOption::VALUE_NONE,
+ 'syncs values from LDAP'
+ )
+ ;
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output): int {
+ $this->dispatcher->addListener(GroupCreatedEvent::class, fn ($event) => $this->onGroupCreatedEvent($event, $output));
+ $this->dispatcher->addListener(UserAddedEvent::class, fn ($event) => $this->onUserAddedEvent($event, $output));
+ $this->dispatcher->addListener(UserRemovedEvent::class, fn ($event) => $this->onUserRemovedEvent($event, $output));
+ try {
+ $this->assertAllowed($input->getOption('force'));
+ $gid = $input->getArgument('ocName');
+ $wasMapped = $this->groupWasMapped($gid);
+ if ($this->backend->getLDAPAccess($gid)->stringResemblesDN($gid)) {
+ $groupname = $this->backend->dn2GroupName($gid);
+ if ($groupname !== false) {
+ $gid = $groupname;
+ }
+ }
+ /* Search to trigger mapping for new groups */
+ $this->backend->getGroups($gid);
+ $exists = $this->backend->groupExistsOnLDAP($gid, true);
+ if ($exists === true) {
+ $output->writeln('The group is still available on LDAP.');
+ if ($input->getOption('update')) {
+ $this->backend->getLDAPAccess($gid)->connection->clearCache();
+ if ($wasMapped) {
+ $this->service->handleKnownGroups([$gid]);
+ } else {
+ $this->service->handleCreatedGroups([$gid]);
+ }
+ }
+ return 0;
+ } elseif ($wasMapped) {
+ $output->writeln('The group does not exist on LDAP anymore.');
+ if ($input->getOption('update')) {
+ $this->backend->getLDAPAccess($gid)->connection->clearCache();
+ $this->service->handleRemovedGroups([$gid]);
+ }
+ return 0;
+ } else {
+ throw new \Exception('The given group is not a recognized LDAP group.');
+ }
+ } catch (\Exception $e) {
+ $output->writeln('<error>' . $e->getMessage(). '</error>');
+ return 1;
+ }
+ }
+
+ public function onGroupCreatedEvent(GroupCreatedEvent $event, OutputInterface $output): void {
+ $output->writeln('<info>The group '.$event->getGroup()->getGID().' was added to Nextcloud with '.$event->getGroup()->count().' users</info>');
+ }
+
+ public function onUserAddedEvent(UserAddedEvent $event, OutputInterface $output): void {
+ $user = $event->getUser();
+ $group = $event->getGroup();
+ $output->writeln('<info>The user '.$user->getUID().' was added to group '.$group->getGID().'</info>');
+ }
+
+ public function onUserRemovedEvent(UserRemovedEvent $event, OutputInterface $output): void {
+ $user = $event->getUser();
+ $group = $event->getGroup();
+ $output->writeln('<info>The user '.$user->getUID().' was removed from group '.$group->getGID().'</info>');
+ }
+
+ /**
+ * checks whether a group is actually mapped
+ * @param string $gid the groupname as passed to the command
+ */
+ protected function groupWasMapped(string $gid): bool {
+ $dn = $this->mapping->getDNByName($gid);
+ if ($dn !== false) {
+ return true;
+ }
+ $name = $this->mapping->getNameByDN($gid);
+ return $name !== false;
+ }
+
+ /**
+ * checks whether the setup allows reliable checking of LDAP group existence
+ * @throws \Exception
+ */
+ protected function assertAllowed(bool $force): void {
+ if ($this->helper->haveDisabledConfigurations() && !$force) {
+ throw new \Exception('Cannot check group existence, because '
+ . 'disabled LDAP configurations are present.');
+ }
+
+ // we don't check ldapUserCleanupInterval from config.php because this
+ // action is triggered manually, while the setting only controls the
+ // background job.
+ }
+
+ private function updateGroup(string $gid, OutputInterface $output, bool $wasMapped): void {
+ try {
+ if ($wasMapped) {
+ $this->service->handleKnownGroups([$gid]);
+ } else {
+ $this->service->handleCreatedGroups([$gid]);
+ }
+ } catch (\Exception $e) {
+ $output->writeln('<error>Error while trying to lookup and update attributes from LDAP</error>');
+ }
+ }
+}
diff --git a/apps/user_ldap/lib/Controller/ConfigAPIController.php b/apps/user_ldap/lib/Controller/ConfigAPIController.php
index e408d03fcd5..23c35895c94 100644
--- a/apps/user_ldap/lib/Controller/ConfigAPIController.php
+++ b/apps/user_ldap/lib/Controller/ConfigAPIController.php
@@ -29,6 +29,7 @@ use OC\Security\IdentityProof\Manager;
use OCA\User_LDAP\Configuration;
use OCA\User_LDAP\ConnectionFactory;
use OCA\User_LDAP\Helper;
+use OCP\AppFramework\Http;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\OCS\OCSBadRequestException;
use OCP\AppFramework\OCS\OCSException;
@@ -76,42 +77,10 @@ class ConfigAPIController extends OCSController {
}
/**
- * Creates a new (empty) configuration and returns the resulting prefix
- *
- * Example: curl -X POST -H "OCS-APIREQUEST: true" -u $admin:$password \
- * https://nextcloud.server/ocs/v2.php/apps/user_ldap/api/v1/config
- *
- * results in:
- *
- * <?xml version="1.0"?>
- * <ocs>
- * <meta>
- * <status>ok</status>
- * <statuscode>200</statuscode>
- * <message>OK</message>
- * </meta>
- * <data>
- * <configID>s40</configID>
- * </data>
- * </ocs>
- *
- * Failing example: if an exception is thrown (e.g. Database connection lost)
- * the detailed error will be logged. The output will then look like:
- *
- * <?xml version="1.0"?>
- * <ocs>
- * <meta>
- * <status>failure</status>
- * <statuscode>999</statuscode>
- * <message>An issue occurred when creating the new config.</message>
- * </meta>
- * <data/>
- * </ocs>
- *
- * For JSON output provide the format=json parameter
+ * Create a new (empty) configuration and return the resulting prefix
*
* @AuthorizedAdminSetting(settings=OCA\User_LDAP\Settings\Admin)
- * @return DataResponse
+ * @return DataResponse<Http::STATUS_OK, array{configID: string}, array{}>
* @throws OCSException
*/
public function create() {
@@ -128,27 +97,15 @@ class ConfigAPIController extends OCSController {
}
/**
- * Deletes a LDAP configuration, if present.
- *
- * Example:
- * curl -X DELETE -H "OCS-APIREQUEST: true" -u $admin:$password \
- * https://nextcloud.server/ocs/v2.php/apps/user_ldap/api/v1/config/s60
- *
- * <?xml version="1.0"?>
- * <ocs>
- * <meta>
- * <status>ok</status>
- * <statuscode>200</statuscode>
- * <message>OK</message>
- * </meta>
- * <data/>
- * </ocs>
+ * Delete a LDAP configuration
*
* @AuthorizedAdminSetting(settings=OCA\User_LDAP\Settings\Admin)
- * @param string $configID
- * @return DataResponse
- * @throws OCSBadRequestException
+ * @param string $configID ID of the config
+ * @return DataResponse<Http::STATUS_OK, array<empty>, array{}>
* @throws OCSException
+ * @throws OCSNotFoundException Config not found
+ *
+ * 200: Config deleted successfully
*/
public function delete($configID) {
try {
@@ -167,28 +124,17 @@ class ConfigAPIController extends OCSController {
}
/**
- * Modifies a configuration
- *
- * Example:
- * curl -X PUT -d "configData[ldapHost]=ldaps://my.ldap.server&configData[ldapPort]=636" \
- * -H "OCS-APIREQUEST: true" -u $admin:$password \
- * https://nextcloud.server/ocs/v2.php/apps/user_ldap/api/v1/config/s60
- *
- * <?xml version="1.0"?>
- * <ocs>
- * <meta>
- * <status>ok</status>
- * <statuscode>200</statuscode>
- * <message>OK</message>
- * </meta>
- * <data/>
- * </ocs>
+ * Modify a configuration
*
* @AuthorizedAdminSetting(settings=OCA\User_LDAP\Settings\Admin)
- * @param string $configID
- * @param array $configData
- * @return DataResponse
+ * @param string $configID ID of the config
+ * @param array<string, mixed> $configData New config
+ * @return DataResponse<Http::STATUS_OK, array<empty>, array{}>
* @throws OCSException
+ * @throws OCSBadRequestException Modifying config is not possible
+ * @throws OCSNotFoundException Config not found
+ *
+ * 200: Config returned
*/
public function modify($configID, $configData) {
try {
@@ -220,8 +166,9 @@ class ConfigAPIController extends OCSController {
}
/**
- * Retrieves a configuration
+ * Get a configuration
*
+ * Output can look like this:
* <?xml version="1.0"?>
* <ocs>
* <meta>
@@ -285,10 +232,13 @@ class ConfigAPIController extends OCSController {
* </ocs>
*
* @AuthorizedAdminSetting(settings=OCA\User_LDAP\Settings\Admin)
- * @param string $configID
- * @param bool|string $showPassword
- * @return DataResponse
+ * @param string $configID ID of the config
+ * @param bool $showPassword Whether to show the password
+ * @return DataResponse<Http::STATUS_OK, array<string, mixed>, array{}>
* @throws OCSException
+ * @throws OCSNotFoundException Config not found
+ *
+ * 200: Config returned
*/
public function show($configID, $showPassword = false) {
try {
@@ -296,7 +246,7 @@ class ConfigAPIController extends OCSController {
$config = new Configuration($configID);
$data = $config->getConfiguration();
- if (!(int)$showPassword) {
+ if (!$showPassword) {
$data['ldapAgentPassword'] = '***';
}
foreach ($data as $key => $value) {
diff --git a/apps/user_ldap/lib/Db/GroupMembership.php b/apps/user_ldap/lib/Db/GroupMembership.php
new file mode 100644
index 00000000000..25be5296d52
--- /dev/null
+++ b/apps/user_ldap/lib/Db/GroupMembership.php
@@ -0,0 +1,48 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright Copyright (c) 2023 Côme Chilliet <come.chilliet@nextcloud.com>
+ *
+ * @author Côme Chilliet <come.chilliet@nextcloud.com>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OCA\User_LDAP\Db;
+
+use OCP\AppFramework\Db\Entity;
+
+/**
+ * @method void setUserid(string $userid)
+ * @method string getUserid()
+ * @method void setGroupid(string $groupid)
+ * @method string getGroupid()
+ */
+class GroupMembership extends Entity {
+ /** @var string */
+ protected $groupid;
+
+ /** @var string */
+ protected $userid;
+
+ public function __construct() {
+ $this->addType('groupid', 'string');
+ $this->addType('userid', 'string');
+ }
+}
diff --git a/apps/user_ldap/lib/Db/GroupMembershipMapper.php b/apps/user_ldap/lib/Db/GroupMembershipMapper.php
new file mode 100644
index 00000000000..8f6af16b267
--- /dev/null
+++ b/apps/user_ldap/lib/Db/GroupMembershipMapper.php
@@ -0,0 +1,77 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright Copyright (c) 2023 Côme Chilliet <come.chilliet@nextcloud.com>
+ *
+ * @author Côme Chilliet <come.chilliet@nextcloud.com>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OCA\User_LDAP\Db;
+
+use OCP\AppFramework\Db\QBMapper;
+use OCP\DB\QueryBuilder\IQueryBuilder;
+use OCP\IDBConnection;
+
+/**
+ * @template-extends QBMapper<GroupMembership>
+ */
+class GroupMembershipMapper extends QBMapper {
+ public function __construct(IDBConnection $db) {
+ parent::__construct($db, 'ldap_group_membership', GroupMembership::class);
+ }
+
+ /**
+ * @return string[]
+ */
+ public function getKnownGroups(): array {
+ $query = $this->db->getQueryBuilder();
+ $result = $query->selectDistinct('groupid')
+ ->from($this->getTableName())
+ ->executeQuery();
+
+ $groups = array_column($result->fetchAll(), 'groupid');
+ $result->closeCursor();
+ return $groups;
+ }
+
+ /**
+ * @return GroupMembership[]
+ */
+ public function findGroupMemberships(string $groupid): array {
+ $qb = $this->db->getQueryBuilder();
+ $select = $qb->select('*')
+ ->from($this->getTableName())
+ ->where($qb->expr()->eq('groupid', $qb->createNamedParameter($groupid)));
+
+ return $this->findEntities($select);
+ }
+
+ public function deleteGroups(array $removedGroups): void {
+ $query = $this->db->getQueryBuilder();
+ $query->delete($this->getTableName())
+ ->where($query->expr()->in('groupid', $query->createParameter('groupids')));
+
+ foreach (array_chunk($removedGroups, 1000) as $removedGroupsChunk) {
+ $query->setParameter('groupids', $removedGroupsChunk, IQueryBuilder::PARAM_STR_ARRAY);
+ $query->executeStatement();
+ }
+ }
+}
diff --git a/apps/user_ldap/lib/Group_LDAP.php b/apps/user_ldap/lib/Group_LDAP.php
index 84267171d37..b3ff63d3b5c 100644
--- a/apps/user_ldap/lib/Group_LDAP.php
+++ b/apps/user_ldap/lib/Group_LDAP.php
@@ -48,11 +48,12 @@ use Exception;
use OC\ServerNotAvailableException;
use OCP\Cache\CappedMemoryCache;
use OCP\GroupInterface;
+use OCP\Group\Backend\ABackend;
use OCP\Group\Backend\IDeleteGroupBackend;
use OCP\Group\Backend\IGetDisplayNameBackend;
use Psr\Log\LoggerInterface;
-class Group_LDAP extends BackendUtility implements GroupInterface, IGroupLDAP, IGetDisplayNameBackend, IDeleteGroupBackend {
+class Group_LDAP extends ABackend implements GroupInterface, IGroupLDAP, IGetDisplayNameBackend, IDeleteGroupBackend {
protected bool $enabled = false;
/** @var CappedMemoryCache<string[]> $cachedGroupMembers array of users with gid as key */
@@ -63,6 +64,7 @@ class Group_LDAP extends BackendUtility implements GroupInterface, IGroupLDAP, I
protected CappedMemoryCache $cachedNestedGroups;
protected GroupPluginManager $groupPluginManager;
protected LoggerInterface $logger;
+ protected Access $access;
/**
* @var string $ldapGroupMemberAssocAttr contains the LDAP setting (in lower case) with the same name
@@ -70,7 +72,7 @@ class Group_LDAP extends BackendUtility implements GroupInterface, IGroupLDAP, I
protected string $ldapGroupMemberAssocAttr;
public function __construct(Access $access, GroupPluginManager $groupPluginManager) {
- parent::__construct($access);
+ $this->access = $access;
$filter = $this->access->connection->ldapGroupFilter;
$gAssoc = $this->access->connection->ldapGroupMemberAssocAttr;
if (!empty($filter) && !empty($gAssoc)) {
@@ -1105,31 +1107,43 @@ class Group_LDAP extends BackendUtility implements GroupInterface, IGroupLDAP, I
* @throws ServerNotAvailableException
*/
public function groupExists($gid) {
- $groupExists = $this->access->connection->getFromCache('groupExists' . $gid);
- if (!is_null($groupExists)) {
- return (bool)$groupExists;
+ return $this->groupExistsOnLDAP($gid, false);
+ }
+
+ /**
+ * Check if a group exists
+ *
+ * @throws ServerNotAvailableException
+ */
+ public function groupExistsOnLDAP(string $gid, bool $ignoreCache = false): bool {
+ $cacheKey = 'groupExists' . $gid;
+ if (!$ignoreCache) {
+ $groupExists = $this->access->connection->getFromCache($cacheKey);
+ if (!is_null($groupExists)) {
+ return (bool)$groupExists;
+ }
}
//getting dn, if false the group does not exist. If dn, it may be mapped
//only, requires more checking.
$dn = $this->access->groupname2dn($gid);
if (!$dn) {
- $this->access->connection->writeToCache('groupExists' . $gid, false);
+ $this->access->connection->writeToCache($cacheKey, false);
return false;
}
if (!$this->access->isDNPartOfBase($dn, $this->access->connection->ldapBaseGroups)) {
- $this->access->connection->writeToCache('groupExists' . $gid, false);
+ $this->access->connection->writeToCache($cacheKey, false);
return false;
}
//if group really still exists, we will be able to read its objectClass
if (!is_array($this->access->readAttribute($dn, '', $this->access->connection->ldapGroupFilter))) {
- $this->access->connection->writeToCache('groupExists' . $gid, false);
+ $this->access->connection->writeToCache($cacheKey, false);
return false;
}
- $this->access->connection->writeToCache('groupExists' . $gid, true);
+ $this->access->connection->writeToCache($cacheKey, true);
return true;
}
@@ -1336,4 +1350,11 @@ class Group_LDAP extends BackendUtility implements GroupInterface, IGroupLDAP, I
$this->access->connection->writeToCache($cacheKey, $displayName);
return $displayName;
}
+
+ /**
+ * returns the groupname for the given LDAP DN, if available
+ */
+ public function dn2GroupName(string $dn): string|false {
+ return $this->access->dn2groupname($dn);
+ }
}
diff --git a/apps/user_ldap/lib/Group_Proxy.php b/apps/user_ldap/lib/Group_Proxy.php
index 5f8d0562fd9..114902ff9ba 100644
--- a/apps/user_ldap/lib/Group_Proxy.php
+++ b/apps/user_ldap/lib/Group_Proxy.php
@@ -28,11 +28,15 @@
*/
namespace OCA\User_LDAP;
+use OC\ServerNotAvailableException;
+use OCP\Group\Backend\IBatchMethodsBackend;
use OCP\Group\Backend\IDeleteGroupBackend;
use OCP\Group\Backend\IGetDisplayNameBackend;
+use OCP\Group\Backend\IGroupDetailsBackend;
use OCP\Group\Backend\INamedBackend;
+use OCP\GroupInterface;
-class Group_Proxy extends Proxy implements \OCP\GroupInterface, IGroupLDAP, IGetDisplayNameBackend, INamedBackend, IDeleteGroupBackend {
+class Group_Proxy extends Proxy implements \OCP\GroupInterface, IGroupLDAP, IGetDisplayNameBackend, INamedBackend, IDeleteGroupBackend, IBatchMethodsBackend {
private $backends = [];
private ?Group_LDAP $refBackend = null;
private Helper $helper;
@@ -256,6 +260,21 @@ class Group_Proxy extends Proxy implements \OCP\GroupInterface, IGroupLDAP, IGet
}
/**
+ * {@inheritdoc}
+ */
+ public function getGroupsDetails(array $gids): array {
+ if (!($this instanceof IGroupDetailsBackend || $this->implementsActions(GroupInterface::GROUP_DETAILS))) {
+ throw new \Exception("Should not have been called");
+ }
+
+ $groupData = [];
+ foreach ($gids as $gid) {
+ $groupData[$gid] = $this->handleRequest($gid, 'getGroupDetails', [$gid]);
+ }
+ return $groupData;
+ }
+
+ /**
* get a list of all groups
*
* @return string[] with group names
@@ -287,6 +306,33 @@ class Group_Proxy extends Proxy implements \OCP\GroupInterface, IGroupLDAP, IGet
}
/**
+ * Check if a group exists
+ *
+ * @throws ServerNotAvailableException
+ */
+ public function groupExistsOnLDAP(string $gid, bool $ignoreCache = false): bool {
+ return $this->handleRequest($gid, 'groupExistsOnLDAP', [$gid, $ignoreCache]);
+ }
+
+ /**
+ * returns the groupname for the given LDAP DN, if available
+ */
+ public function dn2GroupName(string $dn): string|false {
+ $id = 'DN,' . $dn;
+ return $this->handleRequest($id, 'dn2GroupName', [$dn]);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function groupsExists(array $gids): array {
+ return array_values(array_filter(
+ $gids,
+ fn (string $gid): bool => $this->handleRequest($gid, 'groupExists', [$gid]),
+ ));
+ }
+
+ /**
* Check if backend implements actions
*
* @param int $actions bitwise-or'ed actions
diff --git a/apps/user_ldap/lib/Jobs/UpdateGroups.php b/apps/user_ldap/lib/Jobs/UpdateGroups.php
index 48687494a61..487f36f4f30 100644
--- a/apps/user_ldap/lib/Jobs/UpdateGroups.php
+++ b/apps/user_ldap/lib/Jobs/UpdateGroups.php
@@ -1,10 +1,14 @@
<?php
+
+declare(strict_types=1);
+
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Arthur Schiwon <blizzz@arthur-schiwon.de>
* @author Bart Visscher <bartv@thisnet.nl>
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
+ * @author Côme Chilliet <come.chilliet@nextcloud.com>
* @author Joas Schilling <coding@schilljs.com>
* @author Lukas Reschke <lukas@statuscode.ch>
* @author Morris Jobke <hey@morrisjobke.de>
@@ -30,47 +34,20 @@ namespace OCA\User_LDAP\Jobs;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\BackgroundJob\TimedJob;
-use OCA\User_LDAP\Group_Proxy;
use OCP\DB\Exception;
-use OCP\DB\QueryBuilder\IQueryBuilder;
-use OCP\EventDispatcher\IEventDispatcher;
-use OCP\Group\Events\UserAddedEvent;
-use OCP\Group\Events\UserRemovedEvent;
use OCP\IConfig;
-use OCP\IDBConnection;
-use OCP\IGroupManager;
-use OCP\IUser;
-use OCP\IUserManager;
+use OCA\User_LDAP\Service\UpdateGroupsService;
use Psr\Log\LoggerInterface;
class UpdateGroups extends TimedJob {
- /** @var ?array<string, array{owncloudusers: string, owncloudname: string}> */
- private ?array $groupsFromDB = null;
- private Group_Proxy $groupBackend;
- private IEventDispatcher $dispatcher;
- private IGroupManager $groupManager;
- private IUserManager $userManager;
- private LoggerInterface $logger;
- private IDBConnection $dbc;
-
public function __construct(
- Group_Proxy $groupBackend,
- IEventDispatcher $dispatcher,
- IGroupManager $groupManager,
- IUserManager $userManager,
- LoggerInterface $logger,
- IDBConnection $dbc,
+ private UpdateGroupsService $service,
+ private LoggerInterface $logger,
IConfig $config,
- ITimeFactory $timeFactory
+ ITimeFactory $timeFactory,
) {
parent::__construct($timeFactory);
$this->interval = (int)$config->getAppValue('user_ldap', 'bgjRefreshInterval', '3600');
- $this->groupBackend = $groupBackend;
- $this->dispatcher = $dispatcher;
- $this->groupManager = $groupManager;
- $this->userManager = $userManager;
- $this->logger = $logger;
- $this->dbc = $dbc;
}
/**
@@ -78,201 +55,7 @@ class UpdateGroups extends TimedJob {
* @throws Exception
*/
public function run($argument): void {
- $this->updateGroups();
- }
-
- /**
- * @throws Exception
- */
- public function updateGroups(): void {
- $this->logger->debug(
- 'Run background job "updateGroups"',
- ['app' => 'user_ldap']
- );
-
- /** @var string[] $knownGroups */
- $knownGroups = array_keys($this->getKnownGroups());
- $actualGroups = $this->groupBackend->getGroups();
-
- if (empty($actualGroups) && empty($knownGroups)) {
- $this->logger->info(
- 'bgJ "updateGroups" – groups do not seem to be configured properly, aborting.',
- ['app' => 'user_ldap']
- );
- return;
- }
-
- $this->handleKnownGroups(array_intersect($actualGroups, $knownGroups));
- $this->handleCreatedGroups(array_diff($actualGroups, $knownGroups));
- $this->handleRemovedGroups(array_diff($knownGroups, $actualGroups));
-
- $this->logger->debug(
- 'bgJ "updateGroups" – Finished.',
- ['app' => 'user_ldap']
- );
- }
-
- /**
- * @return array<string, array{owncloudusers: string, owncloudname: string}>
- * @throws Exception
- */
- private function getKnownGroups(): array {
- if (is_array($this->groupsFromDB)) {
- return $this->groupsFromDB;
- }
- $qb = $this->dbc->getQueryBuilder();
- $qb->select(['owncloudname', 'owncloudusers'])
- ->from('ldap_group_members');
-
- $qResult = $qb->executeQuery();
- $result = $qResult->fetchAll();
- $qResult->closeCursor();
-
- $this->groupsFromDB = [];
- foreach ($result as $dataset) {
- $this->groupsFromDB[$dataset['owncloudname']] = $dataset;
- }
-
- return $this->groupsFromDB;
- }
-
- /**
- * @param string[] $groups
- * @throws Exception
- */
- private function handleKnownGroups(array $groups): void {
- $this->logger->debug(
- 'bgJ "updateGroups" – Dealing with known Groups.',
- ['app' => 'user_ldap']
- );
- $qb = $this->dbc->getQueryBuilder();
- $qb->update('ldap_group_members')
- ->set('owncloudusers', $qb->createParameter('members'))
- ->where($qb->expr()->eq('owncloudname', $qb->createParameter('groupId')));
-
- $groupsFromDB = $this->getKnownGroups();
- foreach ($groups as $group) {
- $knownUsers = unserialize($groupsFromDB[$group]['owncloudusers']);
- $actualUsers = $this->groupBackend->usersInGroup($group);
- $hasChanged = false;
-
- $groupObject = $this->groupManager->get($group);
- if ($groupObject === null) {
- /* We are not expecting the group to not be found since it was returned by $this->groupBackend->getGroups() */
- $this->logger->error(
- 'bgJ "updateGroups" – Failed to get group {group} for update',
- [
- 'app' => 'user_ldap',
- 'group' => $group
- ]
- );
- continue;
- }
- foreach (array_diff($knownUsers, $actualUsers) as $removedUser) {
- $userObject = $this->userManager->get($removedUser);
- if ($userObject instanceof IUser) {
- $this->dispatcher->dispatchTyped(new UserRemovedEvent($groupObject, $userObject));
- }
- $this->logger->info(
- 'bgJ "updateGroups" – {user} removed from {group}',
- [
- 'app' => 'user_ldap',
- 'user' => $removedUser,
- 'group' => $group
- ]
- );
- $hasChanged = true;
- }
- foreach (array_diff($actualUsers, $knownUsers) as $addedUser) {
- $userObject = $this->userManager->get($addedUser);
- if ($userObject instanceof IUser) {
- $this->dispatcher->dispatchTyped(new UserAddedEvent($groupObject, $userObject));
- }
- $this->logger->info(
- 'bgJ "updateGroups" – {user} added to {group}',
- [
- 'app' => 'user_ldap',
- 'user' => $addedUser,
- 'group' => $group
- ]
- );
- $hasChanged = true;
- }
- if ($hasChanged) {
- $qb->setParameters([
- 'members' => serialize($actualUsers),
- 'groupId' => $group
- ]);
- $qb->executeStatement();
- }
- }
- $this->logger->debug(
- 'bgJ "updateGroups" – FINISHED dealing with known Groups.',
- ['app' => 'user_ldap']
- );
- }
-
- /**
- * @param string[] $createdGroups
- * @throws Exception
- */
- private function handleCreatedGroups(array $createdGroups): void {
- $this->logger->debug(
- 'bgJ "updateGroups" – dealing with created Groups.',
- ['app' => 'user_ldap']
- );
-
- $query = $this->dbc->getQueryBuilder();
- $query->insert('ldap_group_members')
- ->setValue('owncloudname', $query->createParameter('owncloudname'))
- ->setValue('owncloudusers', $query->createParameter('owncloudusers'));
-
- foreach ($createdGroups as $createdGroup) {
- $this->logger->info(
- 'bgJ "updateGroups" – new group "' . $createdGroup . '" found.',
- ['app' => 'user_ldap']
- );
- $users = serialize($this->groupBackend->usersInGroup($createdGroup));
-
- $query->setParameter('owncloudname', $createdGroup)
- ->setParameter('owncloudusers', $users);
- $query->executeStatement();
- }
- $this->logger->debug(
- 'bgJ "updateGroups" – FINISHED dealing with created Groups.',
- ['app' => 'user_ldap']
- );
- }
-
- /**
- * @param string[] $removedGroups
- * @throws Exception
- */
- private function handleRemovedGroups(array $removedGroups): void {
- $this->logger->debug(
- 'bgJ "updateGroups" – dealing with removed groups.',
- ['app' => 'user_ldap']
- );
-
- $query = $this->dbc->getQueryBuilder();
- $query->delete('ldap_group_members')
- ->where($query->expr()->in('owncloudname', $query->createParameter('owncloudnames')));
-
- foreach (array_chunk($removedGroups, 1000) as $removedGroupsChunk) {
- $this->logger->info(
- 'bgJ "updateGroups" – groups {removedGroups} were removed.',
- [
- 'app' => 'user_ldap',
- 'removedGroups' => $removedGroupsChunk
- ]
- );
- $query->setParameter('owncloudnames', $removedGroupsChunk, IQueryBuilder::PARAM_STR_ARRAY);
- $query->executeStatement();
- }
-
- $this->logger->debug(
- 'bgJ "updateGroups" – FINISHED dealing with removed groups.',
- ['app' => 'user_ldap']
- );
+ $this->logger->debug('Run background job "updateGroups"');
+ $this->service->updateGroups();
}
}
diff --git a/apps/user_ldap/lib/LDAP.php b/apps/user_ldap/lib/LDAP.php
index 6309a0c8f91..edaf08a7816 100644
--- a/apps/user_ldap/lib/LDAP.php
+++ b/apps/user_ldap/lib/LDAP.php
@@ -33,6 +33,7 @@
*/
namespace OCA\User_LDAP;
+use OCP\IConfig;
use OCP\Profiler\IProfiler;
use OC\ServerNotAvailableException;
use OCA\User_LDAP\DataCollector\LdapDataCollector;
@@ -317,6 +318,14 @@ class LDAP implements ILDAPWrapper {
private function preFunctionCall(string $functionName, array $args): void {
$this->curArgs = $args;
+ if(strcasecmp($functionName, 'ldap_bind') === 0) {
+ // The arguments are not key value pairs
+ // \OCA\User_LDAP\LDAP::bind passes 3 arguments, the 3rd being the pw
+ // Remove it via direct array access for now, although a better solution could be found mebbe?
+ // @link https://github.com/nextcloud/server/issues/38461
+ $args[2] = IConfig::SENSITIVE_VALUE;
+ }
+
$this->logger->debug('Calling LDAP function {func} with parameters {args}', [
'app' => 'user_ldap',
'func' => $functionName,
diff --git a/apps/user_ldap/lib/Migration/Version1190Date20230706134108.php b/apps/user_ldap/lib/Migration/Version1190Date20230706134108.php
new file mode 100644
index 00000000000..3f554f56e64
--- /dev/null
+++ b/apps/user_ldap/lib/Migration/Version1190Date20230706134108.php
@@ -0,0 +1,113 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright Copyright (c) 2023 Your name <your@email.com>
+ *
+ * @author Your name <your@email.com>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OCA\User_LDAP\Migration;
+
+use Closure;
+use OCP\DB\ISchemaWrapper;
+use OCP\DB\Types;
+use OCP\IDBConnection;
+use OCP\Migration\IOutput;
+use OCP\Migration\SimpleMigrationStep;
+
+class Version1190Date20230706134108 extends SimpleMigrationStep {
+ public function __construct(
+ private IDBConnection $dbc,
+ ) {
+ }
+
+ public function preSchemaChange(IOutput $output, Closure $schemaClosure, array $options): void {
+ }
+
+ public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper {
+ /** @var ISchemaWrapper $schema */
+ $schema = $schemaClosure();
+
+ if (!$schema->hasTable('ldap_group_membership')) {
+ $table = $schema->createTable('ldap_group_membership');
+ $table->addColumn('id', Types::BIGINT, [
+ 'autoincrement' => true,
+ 'notnull' => true,
+ ]);
+ $table->addColumn('groupid', Types::STRING, [
+ 'notnull' => true,
+ 'length' => 255,
+ 'default' => '',
+ ]);
+ $table->addColumn('userid', Types::STRING, [
+ 'notnull' => true,
+ 'length' => 64,
+ 'default' => '',
+ ]);
+ $table->setPrimaryKey(['id']);
+ $table->addUniqueIndex(['groupid', 'userid'], 'user_ldap_membership_unique');
+ return $schema;
+ } else {
+ return null;
+ }
+ }
+
+ public function postSchemaChange(IOutput $output, Closure $schemaClosure, array $options): void {
+ /** @var ISchemaWrapper $schema */
+ $schema = $schemaClosure();
+
+ if (!$schema->hasTable('ldap_group_members')) {
+ // Old table does not exist
+ return;
+ }
+
+ $output->startProgress();
+ $this->copyGroupMembershipData();
+ $output->finishProgress();
+ }
+
+ protected function copyGroupMembershipData(): void {
+ $insert = $this->dbc->getQueryBuilder();
+ $insert->insert('ldap_group_membership')
+ ->values([
+ 'userid' => $insert->createParameter('userid'),
+ 'groupid' => $insert->createParameter('groupid'),
+ ]);
+
+ $query = $this->dbc->getQueryBuilder();
+ $query->select('*')
+ ->from('ldap_group_members');
+
+ $result = $query->executeQuery();
+ while ($row = $result->fetch()) {
+ $knownUsers = unserialize($row['owncloudusers']);
+ foreach ($knownUsers as $knownUser) {
+ $insert
+ ->setParameter('groupid', $row['owncloudname'])
+ ->setParameter('userid', $knownUser)
+ ;
+
+ $insert->executeStatement();
+ }
+ }
+ $result->closeCursor();
+ }
+}
diff --git a/apps/user_ldap/lib/Migration/Version1190Date20230706134109.php b/apps/user_ldap/lib/Migration/Version1190Date20230706134109.php
new file mode 100644
index 00000000000..bc88dc2beb9
--- /dev/null
+++ b/apps/user_ldap/lib/Migration/Version1190Date20230706134109.php
@@ -0,0 +1,46 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright Copyright (c) 2023 Your name <your@email.com>
+ *
+ * @author Your name <your@email.com>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OCA\User_LDAP\Migration;
+
+use Closure;
+use OCP\DB\ISchemaWrapper;
+use OCP\Migration\IOutput;
+use OCP\Migration\SimpleMigrationStep;
+
+class Version1190Date20230706134109 extends SimpleMigrationStep {
+ public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper {
+ /** @var ISchemaWrapper $schema */
+ $schema = $schemaClosure();
+
+ if ($schema->hasTable('ldap_group_members')) {
+ $schema->dropTable('ldap_group_members');
+ return $schema;
+ }
+
+ return null;
+ }
+}
diff --git a/apps/user_ldap/lib/Proxy.php b/apps/user_ldap/lib/Proxy.php
index 8cdb1b1da6a..e2e33f3007b 100644
--- a/apps/user_ldap/lib/Proxy.php
+++ b/apps/user_ldap/lib/Proxy.php
@@ -130,7 +130,7 @@ abstract class Proxy {
* @param string $method string, the method of the user backend that shall be called
* @param array $parameters an array of parameters to be passed
* @param bool $passOnWhen
- * @return mixed, the result of the specified method
+ * @return mixed the result of the specified method
*/
protected function handleRequest($id, $method, $parameters, $passOnWhen = false) {
if (!$this->isSingleBackend()) {
diff --git a/apps/user_ldap/lib/Service/UpdateGroupsService.php b/apps/user_ldap/lib/Service/UpdateGroupsService.php
new file mode 100644
index 00000000000..faee092a372
--- /dev/null
+++ b/apps/user_ldap/lib/Service/UpdateGroupsService.php
@@ -0,0 +1,194 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright Copyright (c) 2016, ownCloud, Inc.
+ *
+ * @author Arthur Schiwon <blizzz@arthur-schiwon.de>
+ * @author Bart Visscher <bartv@thisnet.nl>
+ * @author Christoph Wurst <christoph@winzerhof-wurst.at>
+ * @author Côme Chilliet <come.chilliet@nextcloud.com>
+ * @author Joas Schilling <coding@schilljs.com>
+ * @author Lukas Reschke <lukas@statuscode.ch>
+ * @author Morris Jobke <hey@morrisjobke.de>
+ * @author Robin Appelman <robin@icewind.nl>
+ * @author Robin McCorkell <robin@mccorkell.me.uk>
+ *
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace OCA\User_LDAP\Service;
+
+use OCA\User_LDAP\Db\GroupMembership;
+use OCA\User_LDAP\Db\GroupMembershipMapper;
+use OCA\User_LDAP\Group_Proxy;
+use OCP\DB\Exception;
+use OCP\EventDispatcher\IEventDispatcher;
+use OCP\Group\Events\UserAddedEvent;
+use OCP\Group\Events\UserRemovedEvent;
+use OCP\IGroup;
+use OCP\IGroupManager;
+use OCP\IUser;
+use OCP\IUserManager;
+use Psr\Log\LoggerInterface;
+
+class UpdateGroupsService {
+ public function __construct(
+ private Group_Proxy $groupBackend,
+ private IEventDispatcher $dispatcher,
+ private IGroupManager $groupManager,
+ private IUserManager $userManager,
+ private LoggerInterface $logger,
+ private GroupMembershipMapper $groupMembershipMapper,
+ ) {
+ }
+
+ /**
+ * @throws Exception
+ */
+ public function updateGroups(): void {
+ $knownGroups = $this->groupMembershipMapper->getKnownGroups();
+ $actualGroups = $this->groupBackend->getGroups();
+
+ if (empty($actualGroups) && empty($knownGroups)) {
+ $this->logger->info(
+ 'service "updateGroups" – groups do not seem to be configured properly, aborting.',
+ );
+ return;
+ }
+
+ $this->handleKnownGroups(array_intersect($actualGroups, $knownGroups));
+ $this->handleCreatedGroups(array_diff($actualGroups, $knownGroups));
+ $this->handleRemovedGroups(array_diff($knownGroups, $actualGroups));
+
+ $this->logger->debug('service "updateGroups" – Finished.');
+ }
+
+ /**
+ * @param string[] $groups
+ * @throws Exception
+ */
+ public function handleKnownGroups(array $groups): void {
+ $this->logger->debug('service "updateGroups" – Dealing with known Groups.');
+
+ foreach ($groups as $group) {
+ $this->logger->debug('service "updateGroups" – Dealing with {group}.', ['group' => $group]);
+ $groupMemberships = $this->groupMembershipMapper->findGroupMemberships($group);
+ $knownUsers = array_map(
+ static fn (GroupMembership $groupMembership): string => $groupMembership->getUserid(),
+ $groupMemberships
+ );
+ $groupMemberships = array_combine($knownUsers, $groupMemberships);
+ $actualUsers = $this->groupBackend->usersInGroup($group);
+
+ $groupObject = $this->groupManager->get($group);
+ if ($groupObject === null) {
+ /* We are not expecting the group to not be found since it was returned by $this->groupBackend->getGroups() */
+ $this->logger->error(
+ 'service "updateGroups" – Failed to get group {group} for update',
+ [
+ 'group' => $group
+ ]
+ );
+ continue;
+ }
+ foreach (array_diff($knownUsers, $actualUsers) as $removedUser) {
+ $this->groupMembershipMapper->delete($groupMemberships[$removedUser]);
+ $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}',
+ [
+ 'user' => $removedUser,
+ 'group' => $group
+ ]
+ );
+ }
+ foreach (array_diff($actualUsers, $knownUsers) as $addedUser) {
+ $this->groupMembershipMapper->insert(GroupMembership::fromParams(['groupid' => $group,'userid' => $addedUser]));
+ $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}',
+ [
+ 'user' => $addedUser,
+ 'group' => $group
+ ]
+ );
+ }
+ }
+ $this->logger->debug('service "updateGroups" – FINISHED dealing with known Groups.');
+ }
+
+ /**
+ * @param string[] $createdGroups
+ * @throws Exception
+ */
+ public function handleCreatedGroups(array $createdGroups): void {
+ $this->logger->debug('service "updateGroups" – dealing with created Groups.');
+
+ foreach ($createdGroups as $createdGroup) {
+ $this->logger->info('service "updateGroups" – new group "' . $createdGroup . '" found.');
+
+ $users = $this->groupBackend->usersInGroup($createdGroup);
+ $groupObject = $this->groupManager->get($createdGroup);
+ foreach ($users as $user) {
+ $this->groupMembershipMapper->insert(GroupMembership::fromParams(['groupid' => $createdGroup,'userid' => $user]));
+ if ($groupObject instanceof IGroup) {
+ $userObject = $this->userManager->get($user);
+ if ($userObject instanceof IUser) {
+ $this->dispatcher->dispatchTyped(new UserAddedEvent($groupObject, $userObject));
+ }
+ }
+ }
+ }
+ $this->logger->debug('service "updateGroups" – FINISHED dealing with created Groups.');
+ }
+
+ /**
+ * @param string[] $removedGroups
+ * @throws Exception
+ */
+ public function handleRemovedGroups(array $removedGroups): void {
+ $this->logger->debug('service "updateGroups" – dealing with removed groups.');
+
+ $this->groupMembershipMapper->deleteGroups($removedGroups);
+ foreach ($removedGroups as $group) {
+ $groupObject = $this->groupManager->get($group);
+ if ($groupObject instanceof IGroup) {
+ $groupMemberships = $this->groupMembershipMapper->findGroupMemberships($group);
+ foreach ($groupMemberships as $groupMembership) {
+ $userObject = $this->userManager->get($groupMembership->getUserid());
+ if ($userObject instanceof IUser) {
+ $this->dispatcher->dispatchTyped(new UserRemovedEvent($groupObject, $userObject));
+ }
+ }
+ }
+ }
+
+ $this->logger->info(
+ 'service "updateGroups" – groups {removedGroups} were removed.',
+ [
+ 'removedGroups' => $removedGroups
+ ]
+ );
+ }
+}