Skip to content

Commit 21c7e46

Browse files
Sebastian Witowskijochenklein
authored andcommitted
MiscUtil: Add CERN LDAP plugin
* Creates ldap_cern plugin that can be used to retrieve information from LDAP at CERN (it's faster than the LDAP module in bibcirculation due to the modified queries and more generic - it returns a list with all users that match the query). The search function uses pagination to avoid exceeding the size limit of the CERN LDAP server. (supersedes inveniosoftware/pull/2588) Signed-off-by: Jochen Klein <j.klein@cern.ch> Reviewed-by: Sebastian Witowski <sebastian.witowski@cern.ch>
1 parent 89a2df5 commit 21c7e46

3 files changed

Lines changed: 216 additions & 0 deletions

File tree

modules/miscutil/lib/Makefile.am

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ pylib_DATA = __init__.py \
3333
dbquery_regression_tests.py \
3434
dataciteutils.py \
3535
dataciteutils_tester.py \
36+
ldap_cern.py \
37+
ldap_cern_unit_tests.py \
3638
logicutils.py \
3739
logicutils_unit_tests.py \
3840
mailutils.py \

modules/miscutil/lib/ldap_cern.py

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
# This file is part of Invenio.
2+
# Copyright (C) 2009, 2010, 2011, 2014, 2015, 2016 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."""
19+
20+
from thread import get_ident
21+
22+
import ldap
23+
24+
from ldap.controls import SimplePagedResultsControl
25+
26+
27+
CFG_CERN_LDAP_URI = "ldap://xldap.cern.ch:389"
28+
CFG_CERN_LDAP_BASE = "OU=Users,OU=Organic Units,DC=cern,DC=ch"
29+
CFG_CERN_LDAP_PAGESIZE = 250
30+
31+
_ldap_connection_pool = {}
32+
33+
34+
class LDAPError(Exception):
35+
36+
"""Base class for exceptions in this module."""
37+
38+
pass
39+
40+
41+
def _cern_ldap_login():
42+
"""Get a connection from _ldap_connection_pool or create a new one."""
43+
try:
44+
connection = _ldap_connection_pool[get_ident()]
45+
except KeyError:
46+
connection = _ldap_connection_pool[get_ident()]
47+
_ldap_connection_pool[get_ident()] = ldap.initialize(CFG_CERN_LDAP_URI)
48+
49+
connection.set_option(ldap.OPT_PROTOCOL_VERSION, 3)
50+
return connection
51+
52+
53+
def _msgid(connection, req_ctrl, query_filter, attr_list=None):
54+
"""Run the search request using search_ext.
55+
56+
:param string query_filter: filter to apply in the LDAP search
57+
:param list attr_list: retrieved LDAP attributes. If None, all attributes
58+
are returned
59+
:return: msgid
60+
"""
61+
try:
62+
return connection.search_ext(
63+
CFG_CERN_LDAP_BASE,
64+
ldap.SCOPE_SUBTREE,
65+
query_filter,
66+
attr_list,
67+
attrsonly=0,
68+
serverctrls=[req_ctrl])
69+
except ldap.SERVER_DOWN as e:
70+
raise LDAPError("Error: Connection to CERN LDAP failed. ({0})"
71+
.format(e))
72+
73+
74+
def _paged_search(connection, query_filter, attr_list=None):
75+
"""Search the CERN LDAP server using pagination.
76+
77+
:param string query_filter: filter to apply in the LDAP search
78+
:param list attr_list: retrieved LDAP attributes. If None, all attributes
79+
are returned
80+
:return: list of tuples (result-type, result-data) or empty list,
81+
where result-data contains the user dictionary
82+
"""
83+
req_ctrl = SimplePagedResultsControl(True, CFG_CERN_LDAP_PAGESIZE, "")
84+
msgid = _msgid(connection, req_ctrl, query_filter, attr_list)
85+
result_pages = 0
86+
results = []
87+
88+
while True:
89+
rtype, rdata, rmsgid, rctrls = connection.result3(msgid)
90+
results.extend(rdata)
91+
result_pages += 1
92+
93+
pctrls = [
94+
c
95+
for c in rctrls
96+
if c.controlType == SimplePagedResultsControl.controlType
97+
]
98+
if pctrls:
99+
if pctrls[0].cookie:
100+
req_ctrl.cookie = pctrls[0].cookie
101+
msgid = _msgid(connection, req_ctrl,
102+
query_filter, attr_list)
103+
else:
104+
break
105+
106+
return results
107+
108+
109+
def get_users_records_data(query_filter, attr_list=None, decode_encoding=None):
110+
"""Get result-data of records.
111+
112+
:param string query_filter: filter to apply in the LDAP search
113+
:param list attr_list: retrieved LDAP attributes. If None, all attributes
114+
are returned
115+
:param string decode_encoding: decode the values of the LDAP records
116+
:return: list of LDAP records, but result-data only
117+
"""
118+
connection = _cern_ldap_login()
119+
records = _paged_search(connection, query_filter, attr_list)
120+
121+
records_data = []
122+
123+
if decode_encoding:
124+
records_data = [
125+
dict(
126+
(k, [v[0].decode(decode_encoding)]) for (k, v) in x.iteritems()
127+
)
128+
for (dummy, x) in records]
129+
else:
130+
records_data = [x for (dummy, x) in records]
131+
132+
return records_data
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
# -*- coding: utf-8 -*-
2+
#
3+
# This file is part of Invenio.
4+
# Copyright (C) 2008, 2009, 2010, 2011, 2013, 2014, 2015 CERN.
5+
#
6+
# Invenio is free software; you can redistribute it and/or
7+
# modify it under the terms of the GNU General Public License as
8+
# published by the Free Software Foundation; either version 2 of the
9+
# License, or (at your option) any later version.
10+
#
11+
# Invenio is distributed in the hope that it will be useful, but
12+
# WITHOUT ANY WARRANTY; without even the implied warranty of
13+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14+
# General Public License for more details.
15+
#
16+
# You should have received a copy of the GNU General Public License
17+
# along with Invenio; if not, write to the Free Software Foundation, Inc.,
18+
# 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
19+
20+
"""Unit tests for the solrutils library."""
21+
22+
from invenio.ldap_cern import (
23+
get_users_info_by_displayName, get_users_info_by_displayName_or_email,
24+
get_users_records_data)
25+
from invenio.testutils import InvenioTestCase
26+
from invenio.testutils import make_test_suite, run_test_suite
27+
28+
29+
class TestLDAPGetUserInfo(InvenioTestCase):
30+
31+
"""Test for retrieving users information from LDAP at CERN."""
32+
33+
def test_no_user(self):
34+
"""Try to get user that doesn't exists."""
35+
username = "John Nonexisting"
36+
expected_info = []
37+
self.assertEqual(
38+
get_users_info_by_displayName(username), expected_info)
39+
self.assertEqual(
40+
get_users_info_by_displayName_or_email(username), expected_info)
41+
42+
def test_single_user(self):
43+
"""Try to get a specific user (requires a user from CERN)."""
44+
username = "Tibor Simko"
45+
expected_results = 1
46+
expected_displayName = "Tibor Simko"
47+
expected_email = "Tibor.Simko@cern.ch"
48+
expected_affiliation = "CERN"
49+
ldap_info = get_users_info_by_displayName(username)
50+
ldap_info2 = get_users_info_by_displayName_or_email(username)
51+
self.assertEqual(ldap_info, ldap_info2)
52+
self.assertEqual(len(ldap_info), expected_results)
53+
self.assertEqual(
54+
ldap_info[0][1].get('displayName', [])[0], expected_displayName)
55+
self.assertEqual(
56+
ldap_info[0][1].get('mail', [])[0], expected_email)
57+
self.assertEqual(
58+
ldap_info[0][1].get(
59+
'cernInstituteName', [])[0], expected_affiliation)
60+
61+
def test_users_records_data(self):
62+
"""Try to get a specific user data (requires a user from CERN)."""
63+
searchfilter = (r"(&(objectClass=*)(employeeType=Primary)"
64+
"(mail=Tibor.Simko@cern.ch))")
65+
attrlist = ["mail", "displayName", "cernInstituteName"]
66+
expected_results = 1
67+
expected_displayName = "Tibor Simko"
68+
expected_email = "Tibor.Simko@cern.ch"
69+
expected_affiliation = "CERN"
70+
records = get_users_records_data(searchfilter, attrlist)
71+
self.assertEqual(len(records), expected_results)
72+
self.assertEqual(
73+
records[0].get("displayName", [])[0], expected_displayName)
74+
self.assertEqual(
75+
records[0].get("mail", [])[0], expected_email)
76+
self.assertEqual(
77+
records[0].get("cernInstituteName", [])[0], expected_affiliation)
78+
79+
TEST_SUITE = make_test_suite(TestLDAPGetUserInfo)
80+
81+
if __name__ == "__main__":
82+
run_test_suite(TEST_SUITE)

0 commit comments

Comments
 (0)