Skip to content

Commit dfd75af

Browse files
committed
Merge branch 'ldap_attribute_store_no_pool' of https://github.com/peppelinux/SATOSA into peppelinux-ldap_attribute_store_no_pool
2 parents ebf9f5f + b888722 commit dfd75af

File tree

2 files changed

+88
-47
lines changed

2 files changed

+88
-47
lines changed

example/plugins/microservices/ldap_attribute_store.yaml.example

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,15 @@ config:
66
bind_dn: cn=admin,dc=example,dc=org
77
bind_password: xxxxxxxx
88
search_base: ou=People,dc=example,dc=org
9+
read_only : true
10+
version : 3
11+
12+
# see ldap3 client_strategies
13+
client_strategy : RESTARTABLE
14+
auto_bind : true
15+
pool_size : 10
16+
pool_keepalive : 10
17+
918
search_return_attributes:
1019
# Format is LDAP attribute name : internal attribute name
1120
sn: surname
@@ -20,10 +29,10 @@ config:
2029
pool_keepalive: 10
2130
ordered_identifier_candidates:
2231
# Ordered list of identifiers to use when constructing the
23-
# search filter to find the user record in LDAP directory.
32+
# search filter to find the user record in LDAP directory.
2433
# This example searches in order for eduPersonUniqueId, eduPersonPrincipalName
2534
# combined with SAML persistent NameID, eduPersonPrincipalName
26-
# combined with eduPersonTargetedId, eduPersonPrincipalName,
35+
# combined with eduPersonTargetedId, eduPersonPrincipalName,
2736
# SAML persistent NameID, and eduPersonTargetedId.
2837
- attribute_names: [epuid]
2938
- attribute_names: [eppn, name_id]
@@ -62,4 +71,3 @@ config:
6271
# The microservice may be configured to ignore a particular SP.
6372
https://another.sp.myserver.edu:
6473
ignore: true
65-

src/satosa/micro_services/ldap_attribute_store.py

Lines changed: 77 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
the record and assert them to the receiving SP.
66
"""
77

8-
import satosa.micro_services.base
8+
from satosa.micro_services.base import ResponseMicroService
99
from satosa.logging_util import satosa_logging
1010
from satosa.response import Redirect
1111
from satosa.exception import SATOSAError
@@ -17,15 +17,18 @@
1717

1818
from ldap3.core.exceptions import LDAPException
1919

20+
2021
logger = logging.getLogger(__name__)
2122

23+
2224
class LdapAttributeStoreError(SATOSAError):
2325
"""
2426
LDAP attribute store error
2527
"""
2628
pass
2729

28-
class LdapAttributeStore(satosa.micro_services.base.ResponseMicroService):
30+
31+
class LdapAttributeStore(ResponseMicroService):
2932
"""
3033
Use identifier provided by the backend authentication service
3134
to lookup a person record in LDAP and obtain attributes
@@ -41,11 +44,15 @@ class LdapAttributeStore(satosa.micro_services.base.ResponseMicroService):
4144
'ldap_url' : None,
4245
'on_ldap_search_result_empty' : None,
4346
'ordered_identifier_candidates' : None,
44-
'pool_size' : 10,
45-
'pool_keepalive' : 10,
4647
'search_base' : None,
4748
'search_return_attributes' : None,
48-
'user_id_from_attrs' : []
49+
'user_id_from_attrs' : [],
50+
'read_only' : True,
51+
'version' : 3,
52+
'auto_bind' : False,
53+
'client_strategy' : ldap3.RESTARTABLE,
54+
'pool_size' : 10,
55+
'pool_keepalive' : 10,
4956
}
5057

5158
def __init__(self, config, *args, **kwargs):
@@ -80,7 +87,8 @@ def __init__(self, config, *args, **kwargs):
8087

8188
# Initialize configuration using module defaults then update
8289
# with configuration defaults and then per-SP overrides.
83-
sp_config = copy.deepcopy(LdapAttributeStore.config_defaults)
90+
# sp_config = copy.deepcopy(LdapAttributeStore.config_defaults)
91+
sp_config = copy.deepcopy(self.config_defaults)
8492
if 'default' in self.config:
8593
sp_config.update(self.config['default'])
8694
sp_config.update(config[sp])
@@ -247,27 +255,35 @@ def _ldap_connection_factory(self, config):
247255
if not bind_password:
248256
raise LdapAttributeStoreError("bind_password is not configured")
249257

250-
pool_size = config['pool_size']
251-
pool_keepalive = config['pool_keepalive']
252-
253258
server = ldap3.Server(config['ldap_url'])
254259

255260
satosa_logging(logger, logging.DEBUG, "Creating a new LDAP connection", None)
256261
satosa_logging(logger, logging.DEBUG, "Using LDAP URL {}".format(ldap_url), None)
257262
satosa_logging(logger, logging.DEBUG, "Using bind DN {}".format(bind_dn), None)
263+
264+
pool_size = config['pool_size']
265+
pool_keepalive = config['pool_keepalive']
258266
satosa_logging(logger, logging.DEBUG, "Using pool size {}".format(pool_size), None)
259267
satosa_logging(logger, logging.DEBUG, "Using pool keep alive {}".format(pool_keepalive), None)
260268

269+
auto_bind = config['auto_bind']
270+
client_strategy = config['client_strategy']
271+
read_only = config['read_only']
272+
version = config['version']
273+
261274
try:
262275
connection = ldap3.Connection(
263276
server,
264277
bind_dn,
265278
bind_password,
266-
auto_bind=True,
267-
client_strategy=ldap3.REUSABLE,
279+
auto_bind=auto_bind,
280+
client_strategy=client_strategy,
281+
read_only=read_only,
282+
version=version,
268283
pool_size=pool_size,
269284
pool_keepalive=pool_keepalive
270-
)
285+
)
286+
satosa_logging(logger, logging.DEBUG, "Successfully connected to LDAP server", None)
271287

272288
except LDAPException as e:
273289
msg = "Caught exception when connecting to LDAP server: {}".format(e)
@@ -400,46 +416,63 @@ def process(self, context, data):
400416
# Initialize an empty LDAP record. The first LDAP record found using the ordered
401417
# list of search filter values will be the record used.
402418
record = None
403-
err_msg = None
419+
results = None
420+
exp_msg = None
404421

405-
try:
422+
for filter_val in filter_values:
406423
connection = config['connection']
407-
for filter_val in filter_values:
408-
if record:
409-
break
410-
411-
search_filter = '({0}={1})'.format(config['ldap_identifier_attribute'], filter_val)
412-
satosa_logging(logger, logging.DEBUG, "Constructed search filter {}".format(search_filter), context.state)
413-
414-
satosa_logging(logger, logging.DEBUG, "Querying LDAP server...", context.state)
415-
message_id = connection.search(config['search_base'], search_filter, attributes=config['search_return_attributes'].keys())
416-
responses = connection.get_response(message_id)[0]
417-
satosa_logging(logger, logging.DEBUG, "Done querying LDAP server", context.state)
418-
satosa_logging(logger, logging.DEBUG, "LDAP server returned {} records".format(len(responses)), context.state)
419-
420-
# for now consider only the first record found (if any)
421-
if len(responses) > 0:
422-
if len(responses) > 1:
423-
satosa_logging(logger, logging.WARN, "LDAP server returned {} records using search filter value {}".format(len(responses), filter_val), context.state)
424-
record = responses[0]
425-
break
426-
except LDAPException as e:
427-
err_msg = "Caught LDAP exception: {}".format(e)
428-
except LdapAttributeStoreError as e:
429-
err_msg = "Caught LDAP Attribute Store exception: {}".format(e)
430-
except Exception as e:
431-
err_msg = "Caught unhandled exception: {}".format(e)
432-
433-
if err_msg:
434-
satosa_logging(logger, logging.ERROR, err_msg, context.state)
435-
return super().process(context, data)
424+
search_filter = '({0}={1})'.format(config['ldap_identifier_attribute'], filter_val)
425+
# show ldap filter
426+
satosa_logging(logger, logging.INFO, "LDAP query for {}".format(search_filter), context.state)
427+
satosa_logging(logger, logging.DEBUG, "Constructed search filter {}".format(search_filter), context.state)
428+
429+
try:
430+
# message_id only works in REUSABLE async connection strategy
431+
results = connection.search(config['search_base'], search_filter, attributes=config['search_return_attributes'].keys())
432+
except LDAPException as err:
433+
exp_msg = "Caught LDAP exception: {}".format(err)
434+
except LdapAttributeStoreError as err:
435+
exp_msg = "Caught LDAP Attribute Store exception: {}".format(err)
436+
except Exception as err:
437+
exp_msg = "Caught unhandled exception: {}".format(err)
438+
439+
if exp_msg:
440+
satosa_logging(logger, logging.ERROR, exp_msg, context.state)
441+
return super().process(context, data)
442+
443+
if not results:
444+
satosa_logging(logger, logging.DEBUG, "Querying LDAP server: No results for {}.".format(filter_val), context.state)
445+
continue
446+
447+
if isinstance(results, bool):
448+
responses = connection.entries
449+
else:
450+
responses = connection.get_response(results)[0]
451+
452+
satosa_logging(logger, logging.DEBUG, "Done querying LDAP server", context.state)
453+
satosa_logging(logger, logging.INFO, "LDAP server returned {} records".format(len(responses)), context.state)
454+
455+
# for now consider only the first record found (if any)
456+
if len(responses) > 0:
457+
if len(responses) > 1:
458+
satosa_logging(logger, logging.WARN, "LDAP server returned {} records using search filter value {}".format(len(responses), filter_val), context.state)
459+
record = responses[0]
460+
break
436461

437462
# Before using a found record, if any, to populate attributes
438463
# clear any attributes incoming to this microservice if so configured.
439464
if config['clear_input_attributes']:
440465
satosa_logging(logger, logging.DEBUG, "Clearing values for these input attributes: {}".format(data.attributes), context.state)
441466
data.attributes = {}
442467

468+
# this adapts records with different search and connection strategy (sync without pool), it should be tested with anonimous bind with message_id
469+
if isinstance(results, bool):
470+
drec = dict()
471+
drec['dn'] = record.entry_dn if hasattr(record, 'entry_dn') else ''
472+
drec['attributes'] = record.entry_attributes_as_dict if hasattr(record, 'entry_attributes_as_dict') else {}
473+
record = drec
474+
# ends adaptation
475+
443476
# Use a found record, if any, to populate attributes and input for NameID
444477
if record:
445478
satosa_logging(logger, logging.DEBUG, "Using record with DN {}".format(record["dn"]), context.state)
@@ -466,4 +499,4 @@ def process(self, context, data):
466499
return Redirect(url)
467500

468501
satosa_logging(logger, logging.DEBUG, "Returning data.attributes {}".format(str(data.attributes)), context.state)
469-
return super().process(context, data)
502+
return ResponseMicroService.process(self, context, data)

0 commit comments

Comments
 (0)