Merge pull request #21133 from owncloud/fix-shared-files-of-deleted-users

Fix shared files of deleted users, detect DN change when checking for existence on LDAP
This commit is contained in:
C. Montero Luque 2015-12-15 12:14:58 -05:00
commit a376fec984
4 changed files with 203 additions and 9 deletions

View File

@ -1276,6 +1276,54 @@ class Access extends LDAPUtility implements user\IUserTools {
return $result; return $result;
} }
/**
* reverse lookup of a DN given a known UUID
*
* @param string $uuid
* @return string
* @throws \Exception
*/
public function getUserDnByUuid($uuid) {
$uuidOverride = $this->connection->ldapExpertUUIDUserAttr;
$filter = $this->connection->ldapUserFilter;
$base = $this->connection->ldapBaseUsers;
if($this->connection->ldapUuidUserAttribute === 'auto' && empty($uuidOverride)) {
// Sacrebleu! The UUID attribute is unknown :( We need first an
// existing DN to be able to reliably detect it.
$result = $this->search($filter, $base, ['dn'], 1);
if(!isset($result[0]) || !isset($result[0]['dn'])) {
throw new \Exception('Cannot determine UUID attribute');
}
$dn = $result[0]['dn'][0];
if(!$this->detectUuidAttribute($dn, true)) {
throw new \Exception('Cannot determine UUID attribute');
}
} else {
// The UUID attribute is either known or an override is given.
// By calling this method we ensure that $this->connection->$uuidAttr
// is definitely set
if(!$this->detectUuidAttribute('', true)) {
throw new \Exception('Cannot determine UUID attribute');
}
}
$uuidAttr = $this->connection->ldapUuidUserAttribute;
if($uuidAttr === 'guid' || $uuidAttr === 'objectguid') {
$uuid = $this->formatGuid2ForFilterUser($uuid);
}
$filter = $uuidAttr . '=' . $uuid;
$result = $this->searchUsers($filter, ['dn'], 2);
if(is_array($result) && isset($result[0]) && isset($result[0]['dn']) && count($result) === 1) {
// we put the count into account to make sure that this is
// really unique
return $result[0]['dn'][0];
}
throw new \Exception('Cannot determine UUID attribute');
}
/** /**
* auto-detects the directory's UUID attribute * auto-detects the directory's UUID attribute
* @param string $dn a known DN used to check against * @param string $dn a known DN used to check against
@ -1378,6 +1426,53 @@ class Access extends LDAPUtility implements user\IUserTools {
return strtoupper($hex_guid_to_guid_str); return strtoupper($hex_guid_to_guid_str);
} }
/**
* the first three blocks of the string-converted GUID happen to be in
* reverse order. In order to use it in a filter, this needs to be
* corrected. Furthermore the dashes need to be replaced and \\ preprended
* to every two hax figures.
*
* If an invalid string is passed, it will be returned without change.
*
* @param string $guid
* @return string
*/
public function formatGuid2ForFilterUser($guid) {
if(!is_string($guid)) {
throw new \InvalidArgumentException('String expected');
}
$blocks = explode('-', $guid);
if(count($blocks) !== 5) {
/*
* Why not throw an Exception instead? This method is a utility
* called only when trying to figure out whether a "missing" known
* LDAP user was or was not renamed on the LDAP server. And this
* even on the use case that a reverse lookup is needed (UUID known,
* not DN), i.e. when finding users (search dialog, users page,
* login, ) this will not be fired. This occurs only if shares from
* a users are supposed to be mounted who cannot be found. Throwing
* an exception here would kill the experience for a valid, acting
* user. Instead we write a log message.
*/
\OC::$server->getLogger()->info(
'Passed string does not resemble a valid GUID. Known UUID ' .
'({uuid}) probably does not match UUID configuration.',
[ 'app' => 'user_ldap', 'uuid' => $guid ]
);
return $guid;
}
for($i=0; $i < 3; $i++) {
$pairs = str_split($blocks[$i], 2);
$pairs = array_reverse($pairs);
$blocks[$i] = implode('', $pairs);
}
for($i=0; $i < 5; $i++) {
$pairs = str_split($blocks[$i], 2);
$blocks[$i] = '\\' . implode('\\', $pairs);
}
return implode('', $blocks);
}
/** /**
* gets a SID of the domain of the given dn * gets a SID of the domain of the given dn
* @param string $dn * @param string $dn

View File

@ -158,7 +158,7 @@ abstract class AbstractMapping {
} }
/** /**
* Gets the name based on the provided LDAP DN. * Gets the name based on the provided LDAP UUID.
* @param string $uuid * @param string $uuid
* @return string|false * @return string|false
*/ */
@ -166,6 +166,16 @@ abstract class AbstractMapping {
return $this->getXbyY('owncloud_name', 'directory_uuid', $uuid); return $this->getXbyY('owncloud_name', 'directory_uuid', $uuid);
} }
/**
* Gets the UUID based on the provided LDAP DN
* @param string $dn
* @return false|string
* @throws \Exception
*/
public function getUUIDByDN($dn) {
return $this->getXbyY('directory_uuid', 'ldap_dn', $dn);
}
/** /**
* gets a piece of the mapping list * gets a piece of the mapping list
* @param int $offset * @param int $offset

View File

@ -70,14 +70,26 @@ class Test_User_Ldap_Direct extends \Test\TestCase {
array($lw, null, null)); array($lw, null, null));
$this->configMock = $this->getMock('\OCP\IConfig'); $this->configMock = $this->getMock('\OCP\IConfig');
$um = new \OCA\user_ldap\lib\user\Manager(
$offlineUser = $this->getMockBuilder('\OCA\user_ldap\lib\user\OfflineUser')
->disableOriginalConstructor()
->getMock();
$um = $this->getMockBuilder('\OCA\user_ldap\lib\user\Manager')
->setMethods(['getDeletedUser'])
->setConstructorArgs([
$this->configMock, $this->configMock,
$this->getMock('\OCA\user_ldap\lib\FilesystemHelper'), $this->getMock('\OCA\user_ldap\lib\FilesystemHelper'),
$this->getMock('\OCA\user_ldap\lib\LogWrapper'), $this->getMock('\OCA\user_ldap\lib\LogWrapper'),
$this->getMock('\OCP\IAvatarManager'), $this->getMock('\OCP\IAvatarManager'),
$this->getMock('\OCP\Image'), $this->getMock('\OCP\Image'),
$this->getMock('\OCP\IDBConnection') $this->getMock('\OCP\IDBConnection')
); ])
->getMock();
$um->expects($this->any())
->method('getDeletedUser')
->will($this->returnValue($offlineUser));
$access = $this->getMock('\OCA\user_ldap\lib\Access', $access = $this->getMock('\OCA\user_ldap\lib\Access',
$accMethods, $accMethods,
@ -661,6 +673,44 @@ class Test_User_Ldap_Direct extends \Test\TestCase {
$this->assertFalse($result); $this->assertFalse($result);
} }
/**
* @expectedException \OC\User\NoUserException
*/
public function testGetHomeDeletedUser() {
$access = $this->getAccessMock();
$backend = new UserLDAP($access, $this->getMock('\OCP\IConfig'));
$this->prepareMockForUserExists($access);
$access->connection->expects($this->any())
->method('__get')
->will($this->returnCallback(function($name) {
if($name === 'homeFolderNamingRule') {
return 'attr:testAttribute';
}
return null;
}));
$access->expects($this->any())
->method('readAttribute')
->will($this->returnValue([]));
$userMapper = $this->getMockBuilder('\OCA\User_LDAP\Mapping\UserMapping')
->disableOriginalConstructor()
->getMock();
$access->expects($this->any())
->method('getUserMapper')
->will($this->returnValue($userMapper));
$this->configMock->expects($this->any())
->method('getUserValue')
->will($this->returnValue(true));
//no path at all triggers OC default behaviour
$result = $backend->getHome('newyorker');
$this->assertFalse($result);
}
private function prepareAccessForGetDisplayName(&$access) { private function prepareAccessForGetDisplayName(&$access) {
$access->connection->expects($this->any()) $access->connection->expects($this->any())
->method('__get') ->method('__get')
@ -686,6 +736,14 @@ class Test_User_Ldap_Direct extends \Test\TestCase {
return false; return false;
} }
})); }));
$userMapper = $this->getMockBuilder('\OCA\User_LDAP\Mapping\UserMapping')
->disableOriginalConstructor()
->getMock();
$access->expects($this->any())
->method('getUserMapper')
->will($this->returnValue($userMapper));
} }
public function testGetDisplayName() { public function testGetDisplayName() {

View File

@ -30,6 +30,7 @@
namespace OCA\user_ldap; namespace OCA\user_ldap;
use OC\User\NoUserException;
use OCA\user_ldap\lib\BackendUtility; use OCA\user_ldap\lib\BackendUtility;
use OCA\user_ldap\lib\Access; use OCA\user_ldap\lib\Access;
use OCA\user_ldap\lib\user\OfflineUser; use OCA\user_ldap\lib\user\OfflineUser;
@ -190,15 +191,18 @@ class USER_LDAP extends BackendUtility implements \OCP\IUserBackend, \OCP\UserIn
/** /**
* checks whether a user is still available on LDAP * checks whether a user is still available on LDAP
*
* @param string|\OCA\User_LDAP\lib\user\User $user either the ownCloud user * @param string|\OCA\User_LDAP\lib\user\User $user either the ownCloud user
* name or an instance of that user * name or an instance of that user
* @return bool * @return bool
* @throws \Exception
* @throws \OC\ServerNotAvailableException
*/ */
public function userExistsOnLDAP($user) { public function userExistsOnLDAP($user) {
if(is_string($user)) { if(is_string($user)) {
$user = $this->access->userManager->get($user); $user = $this->access->userManager->get($user);
} }
if(!$user instanceof User) { if(is_null($user)) {
return false; return false;
} }
@ -209,8 +213,23 @@ class USER_LDAP extends BackendUtility implements \OCP\IUserBackend, \OCP\UserIn
if(is_null($lcr)) { if(is_null($lcr)) {
throw new \Exception('No LDAP Connection to server ' . $this->access->connection->ldapHost); throw new \Exception('No LDAP Connection to server ' . $this->access->connection->ldapHost);
} }
try {
$uuid = $this->access->getUserMapper()->getUUIDByDN($dn);
if(!$uuid) {
return false; return false;
} }
$newDn = $this->access->getUserDnByUuid($uuid);
$this->access->getUserMapper()->setDNbyUUID($newDn, $uuid);
return true;
} catch (\Exception $e) {
return false;
}
}
if($user instanceof OfflineUser) {
$user->unmark();
}
return true; return true;
} }
@ -275,8 +294,11 @@ class USER_LDAP extends BackendUtility implements \OCP\IUserBackend, \OCP\UserIn
/** /**
* get the user's home directory * get the user's home directory
*
* @param string $uid the username * @param string $uid the username
* @return string|bool * @return bool|string
* @throws NoUserException
* @throws \Exception
*/ */
public function getHome($uid) { public function getHome($uid) {
if(isset($this->homesToKill[$uid]) && !empty($this->homesToKill[$uid])) { if(isset($this->homesToKill[$uid]) && !empty($this->homesToKill[$uid])) {
@ -295,6 +317,15 @@ class USER_LDAP extends BackendUtility implements \OCP\IUserBackend, \OCP\UserIn
} }
$user = $this->access->userManager->get($uid); $user = $this->access->userManager->get($uid);
if(is_null($user) || ($user instanceof OfflineUser && !$this->userExistsOnLDAP($user->getOCName()))) {
throw new NoUserException($uid . ' is not a valid user anymore');
}
if($user instanceof OfflineUser) {
// apparently this user survived the userExistsOnLDAP check,
// we request the user instance again in order to retrieve a User
// instance instead
$user = $this->access->userManager->get($uid);
}
$path = $user->getHomePath(); $path = $user->getHomePath();
$this->access->cacheUserHome($uid, $path); $this->access->cacheUserHome($uid, $path);