-
Notifications
You must be signed in to change notification settings - Fork 164
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
Add ldap storage #500
Changes from all commits
413c0f7
69698da
3b0c4a6
55e5ff6
0959a16
df3b6ff
3a08a00
400ba90
9299f11
88cc171
f812edd
51ec702
626dcf0
52eb20e
f4e5f04
3a5a5d6
2ff778b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
# -*- coding: utf-8 -*- | ||
|
||
import ldap3 | ||
import pytest | ||
|
||
from vdirsyncer.storage.ldap import LDAPStorage | ||
|
||
from . import StorageTests | ||
|
||
|
||
class TestLDAPStorage(StorageTests): | ||
storage_class = LDAPStorage | ||
supports_collections = False | ||
|
||
@pytest.fixture(params=['VCARD']) | ||
def item_type(self, request): | ||
return request.param | ||
|
||
@pytest.fixture | ||
def get_storage_args(self): | ||
url = 'ldap://localhost' | ||
server = ldap3.Server('fake') | ||
conn = ldap3.Connection(server, client_strategy=ldap3.MOCK_SYNC) | ||
|
||
conn.strategy.add_entry( | ||
'cn=user0,ou=test,o=lab', | ||
{'userPassword': 'test0000', 'sn': 'user0_sn', 'revision': 0} | ||
) | ||
|
||
def inner(collection='test'): | ||
return {'url': url, '_conn': conn, 'search_base': 'ou=test,o=lab'} | ||
return inner |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
# -*- coding: utf-8 -*- | ||
import logging | ||
import ldap3 | ||
import vobject | ||
|
||
from .base import Item, Storage | ||
from .. import exceptions | ||
|
||
ldap_logger = logging.getLogger(__name__) | ||
|
||
|
||
class LDAPStorage(Storage): | ||
''' | ||
:param url: LDAP URL | ||
:param search_base: search base | ||
:param bind: bind dn | ||
:param password: bind password | ||
:param filter: filter | ||
''' | ||
storage_name = 'ldap' | ||
fileext = '.vcf' | ||
item_mimetype = 'text/vcard' | ||
|
||
def __init__(self, url='ldap://localhost', search_base=None, bind=None, | ||
password=None, | ||
filter='(&(objectCategory=person)(objectClass=user)' | ||
'(sn=*)(givenName=*))', | ||
_conn=None, **kwargs): | ||
super(LDAPStorage, self).__init__(**kwargs) | ||
self.search_base = search_base | ||
self.filter = filter | ||
self.conn = _conn | ||
if self.conn is None: | ||
server = ldap3.Server(url, get_info=ldap3.DSA) | ||
if bind: | ||
self.conn = ldap3.Connection(server, user=bind, | ||
password=password) | ||
else: | ||
self.conn = ldap3.Connection(server) | ||
self.conn.bind() | ||
self.conn.start_tls() | ||
|
||
ldap_logger.debug('Connected to: {}'.format(self.conn)) | ||
|
||
if self.search_base is None: | ||
# Fallback to default root entry | ||
self.search_base = server.info.naming_contexts[0] | ||
|
||
def list(self): | ||
''' | ||
:returns: list of (href, etag) | ||
''' | ||
ldap_logger.debug('Search on {self.search_base} with filter' | ||
'{self.filter}'.format(self=self)) | ||
self.conn.search(self.search_base, self.filter, | ||
attributes=["whenChanged"]) | ||
for entry in self.conn.entries: | ||
ldap_logger.debug('Found {}'.format(entry.entry_get_dn())) | ||
href = entry.entry_get_dn() | ||
if getattr(entry, 'whenChanged'): | ||
etag = str(entry.whenChanged) | ||
else: | ||
item = self.get(href) | ||
etag = item.hash | ||
|
||
yield href, etag | ||
|
||
def get(self, href): | ||
self.conn.search(href, self.filter, | ||
attributes=["whenChanged", "cn", "sn", "givenName", | ||
"displayName", "telephoneNumber", | ||
"mobile", "facsimileTelephoneNumber", | ||
"mail", "title"]) | ||
|
||
if not self.conn.entries[0]: | ||
raise exceptions.NotFoundError(href) | ||
|
||
entry = self.conn.entries[0] | ||
etag = str(entry.whenChanged) | ||
|
||
vcard = vobject.vCard() | ||
vo = vcard.add('fn') | ||
vo.value = str(entry.cn) | ||
vo = vcard.add('n') | ||
vo.value = vobject.vcard.Name(family=str(entry.sn), | ||
given=str(entry.givenName)) | ||
if getattr(entry, 'telephoneNumber', None): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) |
||
vo = vcard.add('tel') | ||
vo.value = str(entry.telephoneNumber) | ||
vo.type_param = 'WORK' | ||
if getattr(entry, 'mobile', None): | ||
vo = vcard.add('tel') | ||
vo.value = str(entry.mobile) | ||
vo.type_param = 'CELL' | ||
if getattr(entry, 'facsimileTelephoneNumber', None): | ||
vo = vcard.add('tel') | ||
vo.value = str(entry.facsimileTelephoneNumber) | ||
vo.type_param = 'FAX' | ||
if getattr(entry, 'mail', None): | ||
vo = vcard.add('email') | ||
vo.value = str(entry.mail) | ||
vo.type_param = 'INTERNET' | ||
if getattr(entry, 'title', None): | ||
vo = vcard.add('title') | ||
vo.value = str(entry.title) | ||
|
||
item = Item(vcard.serialize()) | ||
|
||
return item, etag | ||
|
||
def upload(self, item): | ||
vcard = vobject.readOne(item.raw) | ||
self.conn.strategy.add_entry('cn={},ou=test,o=lab'.format(vcard.fn), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 Also |
||
vcard) | ||
|
||
def update(self, href, item, etag): | ||
vcard = vobject.readOne(item.raw) | ||
self.conn.strategy.add_entry(href, vcard) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I think |
There was a problem hiding this comment.
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
toitem.hash
like you have inlist
. I suggest a separate helper function (outside of the storage class) for this.