diff --git a/appinfo/info.xml b/appinfo/info.xml
index 96887956..9f7531c9 100644
--- a/appinfo/info.xml
+++ b/appinfo/info.xml
@@ -42,6 +42,7 @@ More information is available in the [LDAP User and Group Backend documentation]
OCA\User_LDAP\Command\DeleteConfig
OCA\User_LDAP\Command\Search
OCA\User_LDAP\Command\CheckUser
+ OCA\User_LDAP\Command\RemapUser
OCA\User_LDAP\Command\InvalidateCache
diff --git a/lib/Command/RemapUser.php b/lib/Command/RemapUser.php
new file mode 100644
index 00000000..30ec293f
--- /dev/null
+++ b/lib/Command/RemapUser.php
@@ -0,0 +1,155 @@
+
+ *
+ */
+
+namespace OCA\User_LDAP\Command;
+
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Helper\Table;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Output\OutputInterface;
+
+use OCA\User_LDAP\Mapping\UserMapping;
+use OCA\User_LDAP\Helper as LDAPHelper;
+use OCA\User_LDAP\User_Proxy;
+
+class RemapUser extends Command {
+ /** @var \OCA\User_LDAP\User_Proxy */
+ protected $backend;
+
+ /** @var \OCA\User_LDAP\Helper */
+ protected $helper;
+
+ /** @var \OCA\User_LDAP\Mapping\UserMapping */
+ protected $mapping;
+
+ /**
+ * @param User_Proxy $uBackend
+ * @param LDAPHelper $helper
+ * @param UserMapping $mapping
+ */
+ public function __construct(User_Proxy $uBackend, LDAPHelper $helper, UserMapping $mapping) {
+ $this->backend = $uBackend;
+ $this->helper = $helper;
+ $this->mapping = $mapping;
+ parent::__construct();
+ }
+
+ protected function configure() {
+ $this
+ ->setName('ldap:remap-user')
+ ->setDescription('checks whether a user exists on LDAP')
+ ->addArgument(
+ 'ocName',
+ InputArgument::REQUIRED,
+ 'the user name as used in ownCloud'
+ )
+ ->addOption(
+ 'force',
+ null,
+ InputOption::VALUE_NONE,
+ 'ignores disabled LDAP configuration'
+ )
+ ;
+ }
+
+ /**
+ * @param InputInterface $input
+ * @param OutputInterface $output
+ * @return int|void|null
+ */
+ protected function execute(InputInterface $input, OutputInterface $output) {
+ $uid = $input->getArgument('ocName');
+ $this->isAllowed($input->getOption('force'));
+
+ $mappedData = $this->getMappedUUIDAndDN($uid);
+ if ($mappedData['mappedDN'] === false || $mappedData['mappedUUID'] === false) {
+ $output->writeln('User not mapped yet. Try to sync it with the user:sync command');
+ return -1;
+ }
+
+ $output->writeln('Mapped user found in the DB:');
+ $table1 = new Table($output);
+ $table1->setHeaders(['username', 'uuid', 'dn']);
+ $table1->addRow([$uid, $mappedData['mappedUUID'], $mappedData['mappedDN']]);
+ $table1->render();
+
+ $entries = $this->backend->findUsername($uid);
+
+ $output->writeln('');
+ $output->writeln('Candidates found in LDAP:');
+ $table2 = new Table($output);
+ $table2->setHeaders(['username', 'uuid', 'dn']);
+ foreach ($entries as $entry) {
+ $table2->addRow([$entry['owncloud_name'], $entry['directory_uuid'], $entry['dn']]);
+ }
+ $table2->render();
+
+ try {
+ $message = $this->remapUser($uid, $mappedData, $entries);
+ $output->writeln($message);
+ } catch (\UnexpectedValueException $e) {
+ $output->writeln("{$e->getMessage()}");
+ return $e->getCode();
+ }
+ }
+
+ private function getMappedUUIDAndDN($username) {
+ $dn = $this->mapping->getDNByName($username);
+ $uuid = $this->mapping->getUUIDByName($username);
+ return [
+ 'mappedDN' => $dn,
+ 'mappedUUID' => $uuid,
+ ];
+ }
+
+ /**
+ * checks whether the setup allows reliable checking of LDAP user existence
+ * @throws \Exception
+ * @return true
+ */
+ private function isAllowed($force) {
+ if ($this->helper->haveDisabledConfigurations() && !$force) {
+ throw new \Exception('Cannot check user existence, because '
+ . 'disabled LDAP configurations are present.');
+ }
+
+ return true;
+ }
+
+ private function remapUser($uid, $mappedData, $entries) {
+ $entryCount = \count($entries);
+ if ($entryCount > 1) {
+ throw new \UnexpectedValueException('Found too many candidates in LDAP for the target user, remapping isn\'t possible', 1);
+ } elseif ($entryCount < 1) {
+ throw new \UnexpectedValueException('User not found in LDAP. Consider removing the ownCloud\'s account', 2);
+ }
+
+ if ($mappedData['mappedDN'] === $entries[0]['dn'] && $mappedData['mappedUUID'] === $entries[0]['directory_uuid']) {
+ return 'The same user is already mapped. Nothing to do';
+ }
+
+ $result = $this->mapping->replaceUUIDAndDN($uid, $entries[0]['dn'], $entries[0]['directory_uuid']);
+ if ($result === false) {
+ throw new \UnexpectedValueException("Failed to replace mapping data for user {$uid}", 3);
+ }
+ return 'Mapping data replaced';
+ }
+}
diff --git a/lib/Mapping/AbstractMapping.php b/lib/Mapping/AbstractMapping.php
index d9dc8bd1..14f6eb98 100644
--- a/lib/Mapping/AbstractMapping.php
+++ b/lib/Mapping/AbstractMapping.php
@@ -194,6 +194,15 @@ public function getUUIDByDN($dn) {
return $this->getXbyY('directory_uuid', 'ldap_dn', $dn);
}
+ /**
+ * Gets the LDAP UUID based on the provided name.
+ * @param string $name
+ * @return string|false
+ */
+ public function getUUIDByName($name) {
+ return $this->getXbyY('directory_uuid', 'owncloud_name', $name);
+ }
+
/**
* gets a piece of the mapping list
* TODO unused, remove
@@ -253,6 +262,24 @@ public function unmap($name) {
return $this->modify($query, [$name]);
}
+ /**
+ * Replace the dn and the uuid for the owncloud_name
+ * @param string $name the owncloud_name
+ * @param string $dn the new dn for the owncloud_name
+ * @param string $uuid the new directory_uuid for the owncloud_name
+ * @return int|false the number of row updated or false in case of error
+ */
+ public function replaceUUIDAndDN($name, $dn, $uuid) {
+ $queryStr = "UPDATE `{$this->getTableName()}` SET `ldap_dn` = ?, `directory_uuid` = ? WHERE `owncloud_name` = ?";
+ $query = $this->dbc->prepare($queryStr);
+ $result = $query->execute([$dn, $uuid, $name]);
+ if ($result === true) {
+ return $query->rowCount();
+ } else {
+ return false;
+ }
+ }
+
/**
* Truncate's the mapping table
* @return bool
diff --git a/lib/User/Manager.php b/lib/User/Manager.php
index c4cd152d..81f5f20b 100644
--- a/lib/User/Manager.php
+++ b/lib/User/Manager.php
@@ -28,6 +28,7 @@
use OC\Cache\CappedMemoryCache;
use OC\ServerNotAvailableException;
use OCA\User_LDAP\Access;
+use OCA\User_LDAP\Attributes\ConverterHub;
use OCA\User_LDAP\Connection;
use OCA\User_LDAP\Exceptions\DoesNotExistOnLDAPException;
use OCA\User_LDAP\FilesystemHelper;
@@ -540,6 +541,89 @@ public function getUsers($search = '', $limit = 10, $offset = 0) {
return $ownCloudUserNames;
}
+ /**
+ * Connect to the ldap and find all the users whose username is the $uid.
+ * The query will be based on the configured ldapExpertUsernameAttr.
+ * Usually, this method should return only one result, which is for the owncloud
+ * user mapped, but it might return 0 results if the user was deleted in LDAP
+ * or more than one if multiple LDAP users might have the same username. If multiple
+ * results are returned, then there are mapping collisions that must be resolved.
+ * @param string $uid the ownCloud uid to be looked for in the LDAP
+ * @return array a map containing the user info: the dn, the owncloud_name and
+ * the directory_uuid as they would be inserted in the mapping table, as well
+ * as the raw data fetched.
+ */
+ public function findUsersByUsername($uid) {
+ $ldapConfig = $this->getConnection();
+
+ $uuidAttrs = [$ldapConfig->ldapExpertUUIDUserAttr];
+ if ($ldapConfig->ldapExpertUUIDUserAttr === 'auto' || $ldapConfig->ldapExpertUUIDUserAttr === '') {
+ $uuidAttrs = $ldapConfig->uuidAttributes;
+ }
+
+ $usernameAttrs = [$ldapConfig->ldapExpertUsernameAttr];
+ if ($ldapConfig->ldapExpertUsernameAttr === '') {
+ $usernameAttrs = $uuidAttrs;
+ }
+
+ $escapedUid = $this->access->escapeFilterPart($uid);
+ $attrFilters = [];
+ $converterHub = ConverterHub::getDefaultConverterHub();
+ foreach ($usernameAttrs as $attr) {
+ if ($converterHub->hasConverter($attr)) {
+ $attrFilters[] = "{$attr}=" . $converterHub->str2filter($attr, $uid);
+ } else {
+ $attrFilters[] = "{$attr}={$escapedUid}";
+ }
+ }
+ $innerFilter = $this->access->combineFilterWithOr($attrFilters);
+
+ $filter = $this->access->combineFilterWithAnd([
+ $this->getConnection()->ldapUserFilter,
+ $this->getConnection()->ldapUserDisplayName . '=*',
+ $innerFilter,
+ ]);
+
+ $ldap_users = $this->fetchListOfUsers(
+ $filter,
+ $this->getAttributes(),
+ );
+
+ $entries = [];
+ foreach ($ldap_users as $ldapEntry) {
+ $chosenUsername = $this->getValueFromEntry($ldapEntry, $usernameAttrs);
+ $chosenUuid = $this->getValueFromEntry($ldapEntry, $uuidAttrs);
+
+ $entryData = [
+ 'dn' => $ldapEntry['dn'][0],
+ 'owncloud_name' => $chosenUsername,
+ 'directory_uuid' => $chosenUuid,
+ 'rawData' => $ldapEntry,
+ ];
+ $entries[] = $entryData;
+ }
+ return $entries;
+ }
+
+ /**
+ * Get the value of the first attribute of the attrs list found inside the ldapEntry
+ */
+ private function getValueFromEntry($ldapEntry, $attrs) {
+ $chosenValue = null;
+ $converterHub = ConverterHub::getDefaultConverterHub();
+
+ foreach ($attrs as $attr) {
+ if (isset($ldapEntry[$attr][0])) {
+ $chosenValue = $ldapEntry[$attr][0];
+ if ($converterHub->hasConverter($attr)) {
+ $chosenValue = $converterHub->bin2str($attr, $chosenValue);
+ }
+ break;
+ }
+ }
+ return $chosenValue;
+ }
+
// TODO find better places for the delegations to Access
/**
diff --git a/lib/User_LDAP.php b/lib/User_LDAP.php
index b4683378..8c7fb0dd 100644
--- a/lib/User_LDAP.php
+++ b/lib/User_LDAP.php
@@ -407,6 +407,10 @@ public function getAvatar($uid) {
return null;
}
+ public function findUsername($uid) {
+ return $this->userManager->findUsersByUsername($uid);
+ }
+
public function clearConnectionCache() {
$this->userManager->getConnection()->clearCache();
}
diff --git a/lib/User_Proxy.php b/lib/User_Proxy.php
index 8a1d25b3..06b1cdca 100644
--- a/lib/User_Proxy.php
+++ b/lib/User_Proxy.php
@@ -175,6 +175,18 @@ public function getUsers($search = '', $limit = 10, $offset = 0) {
return $users;
}
+ public function findUsername($uid) {
+ // we do it just as the /OC_User implementation: do not play around with limit and offset but ask all backends
+ $users = [];
+ foreach ($this->backends as $backend) {
+ $backendUsers = $backend->findUsername($uid);
+ if (\is_array($backendUsers)) {
+ $users = \array_merge($users, $backendUsers);
+ }
+ }
+ return $users;
+ }
+
/**
* check if a user exists
* @param string $uid the username