Skip to content

Commit bc8da52

Browse files
committed
MiscUtil: CERN LDAP plugin improvement
* Improves the search function and introduces paged search to avoid exceeding the size limit of the CERN LDAP server. * Adds function which returns the user data given a searchfilter and attribute list. * Fixes kwalitee. Signed-off-by: Jochen Klein <[email protected]>
1 parent c3e2911 commit bc8da52

File tree

1 file changed

+129
-38
lines changed

1 file changed

+129
-38
lines changed

modules/miscutil/lib/ldap_cern.py

Lines changed: 129 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,60 +1,155 @@
1-
## This file is part of Invenio.
2-
## Copyright (C) 2009, 2010, 2011, 2014 CERN.
3-
##
4-
## Invenio is free software; you can redistribute it and/or
5-
## modify it under the terms of the GNU General Public License as
6-
## published by the Free Software Foundation; either version 2 of the
7-
## License, or (at your option) any later version.
8-
##
9-
## Invenio is distributed in the hope that it will be useful, but
10-
## WITHOUT ANY WARRANTY; without even the implied warranty of
11-
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12-
## General Public License for more details.
13-
##
14-
## You should have received a copy of the GNU General Public License
15-
## along with Invenio; if not, write to the Free Software Foundation, Inc.,
16-
## 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
17-
18-
"""Invenio LDAP interface for CERN. """
1+
# This file is part of Invenio.
2+
# Copyright (C) 2009, 2010, 2011, 2014, 2015 CERN.
3+
#
4+
# Invenio is free software; you can redistribute it and/or
5+
# modify it under the terms of the GNU General Public License as
6+
# published by the Free Software Foundation; either version 2 of the
7+
# License, or (at your option) any later version.
8+
#
9+
# Invenio is distributed in the hope that it will be useful, but
10+
# WITHOUT ANY WARRANTY; without even the implied warranty of
11+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12+
# General Public License for more details.
13+
#
14+
# You should have received a copy of the GNU General Public License
15+
# along with Invenio; if not, write to the Free Software Foundation, Inc.,
16+
# 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
17+
18+
"""Invenio LDAP interface for CERN."""
1919

20-
from time import sleep
2120
from thread import get_ident
21+
from time import sleep
2222

2323
import ldap
24+
from ldap.controls import SimplePagedResultsControl
2425
import ldap.filter
2526

2627
CFG_CERN_LDAP_URI = "ldap://xldap.cern.ch:389"
2728
CFG_CERN_LDAP_BASE = "OU=Users,OU=Organic Units,DC=cern,DC=ch"
29+
CFG_CERN_LDAP_PAGESIZE = 250
2830

2931
_ldap_connection_pool = {}
3032

3133

34+
class LDAPError(Exception):
35+
36+
"""Base class for exceptions in this module."""
37+
38+
pass
39+
40+
3241
def _cern_ldap_login():
33-
"""Get a connection from _ldap_connection_pool or create a new one"""
42+
"""Get a connection from _ldap_connection_pool or create a new one."""
3443
try:
3544
connection = _ldap_connection_pool[get_ident()]
3645
except KeyError:
3746
connection = _ldap_connection_pool[get_ident()] = ldap.initialize(CFG_CERN_LDAP_URI)
47+
48+
connection.set_option(ldap.OPT_PROTOCOL_VERSION, 3)
3849
return connection
3950

4051

4152
def _sanitize_input(query):
4253
"""
43-
Take the query, filter it through ldap.filter.escape_filter_chars and
54+
Sanitize input query.
55+
56+
Take the query, filter it through ldap.filter.escape_filter_chars, and
4457
replace the dots with spaces.
4558
"""
4659
query = ldap.filter.escape_filter_chars(query)
4760
query = query.replace(".", " ")
4861
return query
4962

5063

51-
def get_users_info_by_displayName(displayName):
64+
def _msgid(connection, req_ctrl, query_filter, attr_list=None):
65+
"""Run the search request using search_ext.
66+
67+
:param string query_filter: filter to apply in the LDAP search
68+
:param list attr_list: retrieved LDAP attributes. If None, all attributes
69+
are returned
70+
:return: msgid
71+
"""
72+
try:
73+
return connection.search_ext(
74+
CFG_CERN_LDAP_BASE,
75+
ldap.SCOPE_SUBTREE,
76+
query_filter,
77+
attr_list,
78+
attrsonly=0,
79+
serverctrls=[req_ctrl])
80+
except ldap.SERVER_DOWN as e:
81+
raise LDAPError("Error: Connection to CERN LDAP failed. ({0})"
82+
.format(e))
83+
84+
85+
def _paged_search(connection, query_filter, attr_list=None):
86+
"""Search the CERN LDAP server using pagination.
87+
88+
:param string query_filter: filter to apply in the LDAP search
89+
:param list attr_list: retrieved LDAP attributes. If None, all attributes
90+
are returned
91+
:return: list of tuples (result-type, result-data) or empty list,
92+
where result-data contains the user dictionary
5293
"""
94+
req_ctrl = SimplePagedResultsControl(True, CFG_CERN_LDAP_PAGESIZE, "")
95+
msgid = _msgid(connection, req_ctrl, query_filter, attr_list)
96+
result_pages = 0
97+
results = []
98+
99+
while True:
100+
rtype, rdata, rmsgid, rctrls = connection.result3(msgid)
101+
results.extend(rdata)
102+
result_pages += 1
103+
104+
pctrls = [
105+
c
106+
for c in rctrls
107+
if c.controlType == SimplePagedResultsControl.controlType
108+
]
109+
if pctrls:
110+
if pctrls[0].cookie:
111+
req_ctrl.cookie = pctrls[0].cookie
112+
msgid = _msgid(connection, req_ctrl,
113+
query_filter, attr_list)
114+
else:
115+
break
116+
117+
return results
118+
119+
120+
def get_users_records_data(query_filter, attr_list=None, decode_encoding=None):
121+
"""Get result-data of records.
122+
123+
:param string query_filter: filter to apply in the LDAP search
124+
:param list attr_list: retrieved LDAP attributes. If None, all attributes
125+
are returned
126+
:param string decode_encoding: decode the values of the LDAP records
127+
:return: list of LDAP records, but result-data only
128+
"""
129+
connection = _cern_ldap_login()
130+
records = _paged_search(connection, query_filter, attr_list)
131+
132+
records_data = []
133+
134+
if decode_encoding:
135+
records_data = [
136+
dict(
137+
(k, [v[0].decode(decode_encoding)]) for (k, v) in x.iteritems()
138+
)
139+
for (dummy, x) in records]
140+
else:
141+
records_data = [x for (dummy, x) in records]
142+
143+
return records_data
144+
145+
146+
def get_users_info_by_displayName(displayName):
147+
"""Get user information, given the displayName.
148+
53149
Query the CERN LDAP server for information about all users whose name
54150
contains the displayName.
55151
Return a list of user dictionaries (or empty list).
56152
"""
57-
58153
connection = _cern_ldap_login()
59154

60155
# Split displayName and add each part of it to the search query
@@ -64,22 +159,20 @@ def get_users_info_by_displayName(displayName):
64159
query_filter = "& "
65160
for element in query_elements:
66161
query_filter += '(displayName=*%s*) ' % element
67-
# Query will look like that: "(& (displayName=*john*) (displayName=*smith*)"
162+
# Query will look like: "(& (displayName=*john*) (displayName=*smith*)"
68163
# Eliminate the secondary accounts (aliases, etc.)
69164
query_filter = "(& (%s) (| (employeetype=primary) (employeetype=external) (employeetype=ExCern) ) )" % query_filter
70165
else:
71166
return []
72167

73168
try:
74-
results = connection.search_st(CFG_CERN_LDAP_BASE, ldap.SCOPE_SUBTREE,
75-
query_filter, timeout=5)
169+
results = _paged_search(connection, query_filter)
76170
except ldap.LDAPError:
77-
## Mmh.. connection error? Let's reconnect at least once just in case
171+
# Mmh.. connection error? Let's reconnect at least once just in case
78172
sleep(1)
79173
connection = _cern_ldap_login()
80174
try:
81-
results = connection.search_st(CFG_CERN_LDAP_BASE, ldap.SCOPE_SUBTREE,
82-
query_filter, timeout=5)
175+
results = _paged_search(connection, query_filter)
83176
except ldap.LDAPError:
84177
# Another error (maybe the LDAP query size is too big, etc.)
85178
# TODO, if it's needed, here we can return various different
@@ -89,12 +182,12 @@ def get_users_info_by_displayName(displayName):
89182

90183

91184
def get_users_info_by_displayName_or_email(name):
92-
"""
93-
Query the CERN LDAP server for information about all users whose displayName
94-
or email contains the name.
185+
"""Get user information, given displayName or email address.
186+
187+
Query the CERN LDAP server for information about all users whose
188+
displayName or email contains the name.
95189
Return a list of user dictionaries (or empty list).
96190
"""
97-
98191
connection = _cern_ldap_login()
99192

100193
# Split name and add each part of it to the search query
@@ -114,15 +207,13 @@ def get_users_info_by_displayName_or_email(name):
114207
return []
115208

116209
try:
117-
results = connection.search_st(CFG_CERN_LDAP_BASE, ldap.SCOPE_SUBTREE,
118-
query_filter, timeout=5)
210+
results = _paged_search(connection, query_filter)
119211
except ldap.LDAPError:
120-
## Mmh.. connection error? Let's reconnect at least once just in case
212+
# Mmh.. connection error? Let's reconnect at least once just in case
121213
sleep(1)
122214
connection = _cern_ldap_login()
123215
try:
124-
results = connection.search_st(CFG_CERN_LDAP_BASE, ldap.SCOPE_SUBTREE,
125-
query_filter, timeout=5)
216+
results = _paged_search(connection, query_filter)
126217
except ldap.LDAPError:
127218
# Another error (maybe the LDAP query size is too big, etc.)
128219
# TODO, if it's needed, here we can return various different

0 commit comments

Comments
 (0)