Skip to content

rework the user ldap logic to match the account table#125

Merged
butonic merged 29 commits into
masterfrom
fix_user_access
Oct 25, 2017
Merged

rework the user ldap logic to match the account table#125
butonic merged 29 commits into
masterfrom
fix_user_access

Conversation

@butonic

@butonic butonic commented Aug 17, 2017

Copy link
Copy Markdown
Contributor

I am sick of our ldap problems. The app is heavily used, yet undertested and quirky at best. It grew over time but its purpose changes with the introduction of the Account table in OC10.

  • getUsers() fetches and caches all necessary ldap attributes in a single paginated ldap search
    • no subsequent readAttribute that aborts the running paged search ...
    • most of the user backend interface calls only work correctly after a getUsers call. That is fine for now, because that is the way the sync works. checkPassword also correctly caches the attributes of the logged in user
    • only caches the UserEntries (which wrap ldap results) in the current process. no longer uses the distributed cache for users ... wasted effort on sync and for user search the account table is used.
  • groups code is unchanged for now.
    • could use some refactoring

requires owncloud/core#28729 to make the user sync correctly update quota and email

  • move some functions from Access to a helper class
  • review commented tests, some are already covered by UserEntryTest, others are better located in the Manager
  • add more tests
  • add Class structure / chain to php doc
  • test with user shibboleth
  • test users as group members if getUsers did not yet fetch the entry ... might there be a problem when groups have > 512 Users (the default size of the capped cache?)

yes, I am ripping this apart and putting it together anew. I don't care about the ui. I need the underlying sync logic to work reliably AND the code to be readable. That means not trying to be smart and use less reflection ...

@butonic butonic changed the title rework the user ldap logic to match the account table 🚧 rework the user ldap logic to match the account table Aug 17, 2017
@butonic butonic self-assigned this Aug 17, 2017
@butonic butonic added this to the development milestone Aug 17, 2017
@butonic

butonic commented Aug 18, 2017

Copy link
Copy Markdown
Contributor Author

fixes #121
obsoletes #119

@butonic

butonic commented Sep 1, 2017

Copy link
Copy Markdown
Contributor Author
  • fix and test GUID binary mapping

@mrow4a

mrow4a commented Sep 2, 2017

Copy link
Copy Markdown
Contributor

To me, it is cleaner version of the code + adjustment to accounts thing. Looks good in general, just difficult to review 👍

@tomneedham

Copy link
Copy Markdown
Contributor

Tested with master:

  • user sync
  • extended search terms
  • multi-value search terms
  • mail attribute sync
  • group sync
  • ui basic setup

Comment thread lib/Access.php
* @return string|false with with the name to use in ownCloud or false if unsuccessful
*/
private function createAltInternalOwnCloudName($name, $isUser) {
public function createAltInternalOwnCloudName($name, $isUser) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

some more context here?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll also need context here.

If this needs to be public I wonder if this function should be here. It would make more sense to move this function to a specialized object.
Although we should move this function to another place, I think it's fine if we can make it private for now.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

separate PR, see lib\User\Manager.php:292:

// FIXME move to a better place, naming related. eg DistinguishedNameUtils

@butonic butonic changed the title 🚧 rework the user ldap logic to match the account table rework the user ldap logic to match the account table Sep 18, 2017

@jvillafanez jvillafanez left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still need to review the big diffs

Comment thread lib/Command/CheckUser.php
$this->isAllowed($input->getOption('force'));
$this->confirmUserIsMapped($uid);
$exists = $this->backend->userExistsOnLDAP($uid);
$exists = $this->backend->userExists($uid);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reason for this change? I think originally this was intended to bypass the cache and check directly against the LDAP server.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The implementation now always talks to LDAP but caches the result per request.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually userExistsOnLdap has been moved to User_LDAP. userExists tries to determine the answer based on the cache in the LDAP UserManager. It will fall back to LDAP just as before.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, so the redis cache has been removed and we cache the information per request... fair enough 👍

Comment thread lib/User/UserEntry.php
$username = $this->getAttributeValue($attr, null);
}
if ($username === null) {
$json = @json_encode($this->ldapEntry, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_NUMERIC_CHECK | JSON_PARTIAL_OUTPUT_ON_ERROR );

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rather have a comment here for the @ since error supression isn't common.

Comment thread lib/User/UserEntry.php
/**
* @param string $ownCloudUID
*/
public function setOwnCloudUID($ownCloudUID) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm worried this is the only "set" method here. Is it expected to be changed? Could it be set during the object creation?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to store an already mapped uid somewhere.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My point is if we can set the value only in the constructor in order to prevent modifications, or we need to modify the value once the object is already constructor.

Comment thread lib/User/UserEntry.php Outdated
}
}
if ($quota === null) {
$quota = 'default';

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this changed recently to not set the "default" quota in order to avoid being overwritten

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fix incoming

Comment thread lib/User/UserEntry.php
}

/**
* @return string

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd comment here what is the expected behavior. I don't expect this method to return the username unless explicitly told.
My expectation is that this method returns null if the displayname can't be fetched, so anyone can fetch the username later if they want. Better to add a comment if this isn't the case

Comment thread lib/User/UserEntry.php Outdated
/**
* returns the home directory of the user if specified by LDAP settings
* @return string|null
* @throws \Exception

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment here when this exception is expected to be thrown.
In addition, we should narrow the exception.

Comment thread lib/User/UserEntry.php
if (isset($this->ldapEntry['memberof'])) {
return $this->ldapEntry['memberof'];
}
return [];

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rather return something different because empty result is also a valid result, it would be difficult to distinguish this case and an error case (or if there isn't such entry)

Comment thread lib/User/UserEntry.php
*/
private function getAttributeValue($attributeName, $default = null, $trim = true) {
$attributeName = strtolower($attributeName); // all ldap keys are lowercase
if (isset($this->ldapEntry[$attributeName][0])) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we check for multivalued attributes? We should have a comment just in case.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no. only search attributes may be multi value. updating phpdoc as well

Comment thread lib/User/UserEntry.php Outdated
* @param $attributeName string
* @param $default string
* @param $trim bool don't trim value, eg for binary data
* @return string|null|mixed

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Specify when we should expect each data type.

Comment thread lib/User_Proxy.php
use OCP\UserInterface;

class User_Proxy extends Proxy implements IUserBackend, UserInterface, IProvidesExtendedSearchBackend {
class User_Proxy extends Proxy implements IUserBackend, UserInterface, IProvidesQuotaBackend, IProvidesExtendedSearchBackend, IProvidesEMailBackend {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm forced to complain about the massive interface implementation here, but no change needed for now because it will require quite huge changes, and not just here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The app used to update core itself, which is not the way it shoud be. This PR just corrects that by removing the related code and implementing the interfaces to get the correct behavior.

Comment thread lib/LDAPUtility.php
@@ -25,6 +26,9 @@
namespace OCA\User_LDAP;

abstract class LDAPUtility {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should review the usage if this class because right now I think it's quite pointless. Probably we can remove it.

@butonic butonic Oct 11, 2017

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

separate PR

Comment thread lib/Access.php
/**
* returns the Connection instance
* @return \OCA\User_LDAP\ILDAPWrapper
*/

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this function used outside of the class? I'm fine to use this function as a shortcut but also private. Making it public would open new dependencies between components which doesn't seem a good idea.

Comment thread lib/Access.php
$hex_guid_to_guid_str .= '-' . substr($hex_guid, 20);

return strtoupper($hex_guid_to_guid_str);
public static function binGUID2str($binGuid) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason to use static methods here? I'd rather move this function to a helper object and inject it in the constructor. This way we have this function outside if this class (I don't think this is needed to be here), and also make the function testeable.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

separate PR

Comment thread lib/Access.php
* @return string
*/
public function convertSID2Str($sid) {
public static function sid2str($sid) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

separate PR

Comment thread lib/Access.php
* The nsuniqueid values are generated based on the entryuuid value by moving the "-" to comply with the format of the ODSEE Nsuniqueid Virtual Attribute attribute.
*
* ## RedHat FreeIPA is defined as utf string
* {@link https://github.com/freeipa/freeipa/blob/master/install/share/uuid.ldif ipaUniqueID schema}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👏
Really greatful for this references. Just this one should be an official redhat link. No need to do this now anyway.

Comment thread lib/User_LDAP.php
*
* @package OCA\User_LDAP
*/
class User_LDAP implements IUserBackend, UserInterface {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if this class should implement these interfaces. It should be oriented to implement them so the User_Proxy class is easier to implement but other than that, the interfaces will be implemented by the User_Proxy.

Comment thread lib/User_LDAP.php Outdated
$user = $this->access->userManager->get($uid);
if(!$user instanceof User) {
$user = $this->userManager->getByOwnCloudUID($uid);
if(!$user instanceof UserEntry) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

According to the PHPDocs, the method returns a UserEntry or null. I'd change this to check against null because otherwise it seems that other type of objects can be returned by that function.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getByOwnCloudUID now always returns a UserEntry

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or throws an exception

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so we can remove that check (or use a try-catch for the exception instead)

Comment thread lib/User_LDAP.php Outdated
* Get a users email address
*
* @param string $uid The username
* @return string|null|false false if user was not found, null if no email is set

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

copy & paste 👀

Comment thread lib/User_LDAP.php Outdated
* FIXME This is an expensive operation and takes roughly half a second to parse the data and create the image. This might be too slow for sync jobs.
*
* @param string $uid The username
* @return IImage|null|false

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to specify each possible cases

Comment thread lib/User_LDAP.php
/**
* Get avatar for a users account for core powered user search
*
* FIXME This is an expensive operation and takes roughly half a second to parse the data and create the image. This might be too slow for sync jobs.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Side question for the future: is it possible to store the raw image data (I think it's base64 encoded) and compare the data we have and the data we receive, and then create the image if the data is different? I'm not sure if it will improve the performance, but it might be worthy to have a try.

@jvillafanez

Copy link
Copy Markdown
Member

Review finished from my part. I think most of them are easy to solve, and the rest might be moved to tech debt if they're too complex to manage in this PR.

@tomneedham

Copy link
Copy Markdown
Contributor

bug here with user sync, suggested arribute is not found when it is present: https://github.com/owncloud/enterprise/issues/2275

@tomneedham

Copy link
Copy Markdown
Contributor

Rebased to include some other fixes and the tests should now run

@tomneedham

Copy link
Copy Markdown
Contributor

screen shot 2017-10-11 at 14 25 56

screen shot 2017-10-11 at 14 24 25

💥 after running a user sync twice

@tomneedham

Copy link
Copy Markdown
Contributor

"0" quota is recorded in the accounts table as NULL => correct or not?

Comment thread lib/User_LDAP.php Outdated
if($userEntry === null) {
try {
$this->userManager->getCachedEntry($uid);
} catch (\Exception $e) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@tomneedham

Copy link
Copy Markdown
Contributor

This mornings commit fixes the sync issue I found.

@tomneedham

Copy link
Copy Markdown
Contributor

Rebased + pushed a test. Will wait for CI. Then build test tarball which we can share round as a beta release 🎉

@tomneedham

Copy link
Copy Markdown
Contributor

Ideas for performance comparisons for release:

  • cold login time
  • user sync + single user sync
  • call for all users

@butonic

butonic commented Oct 25, 2017

Copy link
Copy Markdown
Contributor Author

Merging, next up testing with large installations before release

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants