Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ldap storage #500

Closed
wants to merge 17 commits into from
Closed

Add ldap storage #500

wants to merge 17 commits into from

Conversation

paulfariello
Copy link

@paulfariello paulfariello commented Sep 13, 2016

Current implementation is read only.
Not sure if ldap storage is supposed to do vcard conversion.

I haven't added tests nor documentation but will if @pimutils thinks this patch is meaningful.

Other enhancement would be to allow for attributes configuration.


edit by @untitaker: Fix #283

Paul Fariello added 2 commits September 13, 2016 16:17
Current implementation is read only.
Not sure if ldap storage is supposed to do vcard conversion.
@untitaker
Copy link
Member

Cool! This looks reasonable, but:

  • for VCard conversion please use https://github.com/eventable/vobject instead of your own thing, which actually has some bugs with values containing backslashes or newlines. This will also make it possible to parse VCards for uploading.
  • Please specifiy ldap and vobject as dependencies here: https://github.com/pimutils/vdirsyncer/blob/master/setup.py#L69 For example, if I run pip install vdirsyncer[remotestorage], requests-oauthlib will be installed. ldap and vobject should be installed when I run pip install vdirsyncer[ldap].


class LDAPStorage(Storage):

__doc__ = '''
Copy link
Member

Choose a reason for hiding this comment

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

When implementing upload and update, we have to make it very clear which properties are even parsed and which ones are discarded.

Copy link
Member

Choose a reason for hiding this comment

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

Also the __doc__ is only necessary if we need to include variables from other strings.

@untitaker
Copy link
Member

And yes, please add tests for this, this is very meaningful! Do you have an idea how to do this? I.e., is there a simple ldap server we could use?

@untitaker untitaker mentioned this pull request Sep 13, 2016
@paulfariello
Copy link
Author

Thanks for the quick reply.

I'm now using vobject which is clearly cleaner. Thanks for the hint.
I've also added support for title and fax.

I changed the default filter to one that make sense since vobject
clearly states that F and FN are mandatory fields. On some dirty
ldap directory sn or givenName could be missing and I'm not sure how
to
handle it cleanly without filtering it. Maybe just provide the available
family or givenname for N.

About tests, I'm not sure how we could test it against a real ldap
server. I don't know any open ldap server but I can check.

Note that the ldap attributes I'm using are from Active Directory. Any
other ldap directory could also use it but this is not guaranteed. The
most problematic attribute is whenChanged. I've never seen it on other
classic ldap directory and it's used for etag.

Paul Fariello

PGP: 0x672CDD2031AAF49B

@untitaker
Copy link
Member

untitaker commented Sep 13, 2016

It appears that this would be appropriate for testing: https://pypi.python.org/pypi/mockldap

See tests/storage/test_filesystem.py for how to write tests for the LDAP backend. In particular if you subclass StorageTests your storage will already be thoroughly tested, but get_storage_args has to be implemented.

EDIT: However, this would require support of update and upload.

@untitaker
Copy link
Member

Note that the ldap attributes I'm using are from Active Directory. Any other ldap directory could also use it but this is not guaranteed. The most problematic attribute is whenChanged. I've never seen it on other classic ldap directory and it's used for etag.

Yeah I think we should make as much as possible configurable there, and also provide examples for other major LDAP providers.

@untitaker
Copy link
Member

Regarding whenChanged, if it's not there, you'll probably have to download the item and use item.hash or something similar.

super(LDAPStorage, self).__init__(**kwargs)
self.search_base = search_base
self.filter = filter
self.server = ldap3.Server(uri)
Copy link
Member

Choose a reason for hiding this comment

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

I think you could just use ldap.initialize here, no?

Copy link
Member

Choose a reason for hiding this comment

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

Ah, I see, you're using https://github.com/cannatag/ldap3, but I was reading the docs for https://github.com/pyldap/pyldap

Could you switch to the latter? This would make testing easier as mockldap appears to support pyldap only.

@untitaker
Copy link
Member

Am I correct to assume that this storage type does not support multiple addressbooks?


class LDAPStorage(Storage):
'''
:param uri: LDAP URI
Copy link
Member

Choose a reason for hiding this comment

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

Why URI instead of URL?

Copy link
Author

Choose a reason for hiding this comment

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

URI is the right term for this since it can be an URL, a simple ip or a
hostname.

Paul Fariello

PGP: 0x672CDD2031AAF49B

Copy link
Member

Choose a reason for hiding this comment

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

An IP or hostname is not a URI. URIs always have a scheme. https://tools.ietf.org/html/rfc3986#section-1.1.1

@untitaker
Copy link
Member

I added a LDAP testcase that should work if you use pyldap instead of ldap3.

@paulfariello
Copy link
Author

Thanks for the review. I'll fix today.

But first I have two questions:

  • how am I supposed to run the test suites ? I'm running make -e test but got a bunch of errors
  • how did you managed to push a commit on my fork ?

@untitaker
Copy link
Member

You need to run "make install-test" as well. In the documentation there is a page "Contributing" that goes into details.

Regarding how my commit landed on your fork, see https://github.com/blog/2247-improving-collaboration-with-forks

On 14 September 2016 09:23:24 CEST, Paul Fariello [email protected] wrote:

Thanks for the review. I'll fix today.

But first I have two questions:

  • how am I supposed to run the test suites ? I'm running make -e test
    but got a bunch of errors
  • how did you managed to push a commit on my fork ?

You are receiving this because you were mentioned.
Reply to this email directly or view it on GitHub:
#500 (comment)

Sent from my Android device with K-9 Mail. Please excuse my brevity.

@paulfariello
Copy link
Author

I've been trying to switch to pyldap but it doesn't natively support generator for ldap result. It makes it more complicated to cleanly integrate it.
Meanwhile I just discovered that ldap3 has a mocking component. I'll try to use it instead.

@paulfariello
Copy link
Author

Tests with ldap3 mocking are still WIP.

@untitaker
Copy link
Member

Ah, you need to add the new dependencies to test-requirements.txt as well.

@untitaker untitaker force-pushed the master branch 3 times, most recently from 3e375a1 to 6aedd62 Compare September 26, 2016 20:38
@untitaker
Copy link
Member

@paulfariello BTW let me know if I can help with anything or if anything is unclear.

@untitaker
Copy link
Member

@paulfariello Any update on this? Adding tests is really important! Are you using this already for yourself?

@paulfariello
Copy link
Author

Sorry I've been quite busy lately. I'll try to add tests by the end of next week.

Le 8 octobre 2016 14:24:04 GMT+02:00, Markus Unterwaditzer [email protected] a écrit :

@paulfariello Any update on this? Adding tests is really important! Are
you using this already for yourself?

You are receiving this because you were mentioned.
Reply to this email directly or view it on GitHub:
#500 (comment)

Paul Fariello

PGP: 0x672CDD2031AAF49B

@paulfariello
Copy link
Author

I tried to add upload and update method. But I'm facing an issue with VCALENDAR. What should I do with it ? They doesn't really belong to an LDAP directory.

@untitaker
Copy link
Member

Fantastic, please push what you have, I'll do the changes such that VCALENDARs are not used in the tests.

@paulfariello
Copy link
Author

My last push is just a first attempt on update and upload implementation. It push to mocked server.
I also have an issue with start tls on mocked server.

@untitaker
Copy link
Member

Are ldap://-urls SSL by default?

@untitaker
Copy link
Member

I believe I've fixed the issue with ssl, let's just expect that the connection is fully set up if it's passed into the storage. I think ldap doesn't deal with SSL very well when mocked, but in our case SSL is not of any use anyway.

Copy link
Member

@untitaker untitaker left a comment

Choose a reason for hiding this comment

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

Just got around to reviewing all of the LDAP storage. Unfortunately upload and update still need a lot of work.


def upload(self, item):
vcard = vobject.readOne(item.raw)
self.conn.strategy.add_entry('cn={},ou=test,o=lab'.format(vcard.fn),
Copy link
Member

Choose a reason for hiding this comment

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

Upload needs to return the assigned href and etag. It appears that your href is dn, but the etag has to be fetched like in list and get.

Also add_entry returns a boolean that indicates whether the entry has been added, you should check for that.

raise exceptions.NotFoundError(href)

entry = self.conn.entries[0]
etag = str(entry.whenChanged)
Copy link
Member

@untitaker untitaker Oct 22, 2016

Choose a reason for hiding this comment

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

Here you have no fallback from whenChanged to item.hash like you have in list. I suggest a separate helper function (outside of the storage class) for this.

vo = vcard.add('n')
vo.value = vobject.vcard.Name(family=str(entry.sn),
given=str(entry.givenName))
if getattr(entry, 'telephoneNumber', None):
Copy link
Member

Choose a reason for hiding this comment

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

This conversion from entry to item could be its own function (outside of storage class)


def update(self, href, item, etag):
vcard = vobject.readOne(item.raw)
self.conn.strategy.add_entry(href, vcard)
Copy link
Member

Choose a reason for hiding this comment

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

add_entry will do nothing if the item already exists, but if I call update, the item always exists. update is also expected to fail if the passed etag does not match the etag of the item on the server.

I think modify or how it's called is more appropriate than add_entry, but I'm not sure since I couldn't find much docs :(

@untitaker
Copy link
Member

Also please contact me if there are any questions at all, PM, direct email, whatever too.

@untitaker
Copy link
Member

@paulfariello Ping!

@untitaker
Copy link
Member

Closing this due to inactivity, but for reference, this is a good start for a successor PR.

@untitaker untitaker closed this Jun 20, 2017
@untitaker untitaker removed the stalled label Jun 20, 2017
@nblock nblock mentioned this pull request Apr 11, 2023
11 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants