Skip to content

Commit 5c6a542

Browse files
committed
Updated to that before return to spawner, the set_spawner_attributes are reformatted with updated LDAP attributes that have can extracted from the DIT and now only be available from the pre-submit values, also added the LDAP_FIRST_SEARCH_ATTRIBUTE_QUERY option to get the first attribute value from a list of attributes
1 parent 815f9ee commit 5c6a542

File tree

7 files changed

+241
-58
lines changed

7 files changed

+241
-58
lines changed

example/setup_ldap_entry_hook_config.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from jhubauthenticators import RegexUsernameParser
33
from ldap_hooks import setup_ldap_entry_hook
44
from ldap_hooks import LDAP, LDAP_SEARCH_ATTRIBUTE_QUERY, \
5-
SPAWNER_SUBMIT_DATA, INCREMENT_ATTRIBUTE
5+
SPAWNER_SUBMIT_DATA, INCREMENT_ATTRIBUTE, LDAP_FIRST_SEARCH_ATTRIBUTE_QUERY
66
c = get_config()
77

88
c.JupyterHub.ip = '0.0.0.0'
@@ -40,18 +40,19 @@
4040

4141
# Dynamic attributes and where to find the value
4242
LDAP.dynamic_attributes = {
43+
'uid': LDAP_FIRST_SEARCH_ATTRIBUTE_QUERY,
4344
'emailAddress': SPAWNER_SUBMIT_DATA,
4445
'uidNumber': LDAP_SEARCH_ATTRIBUTE_QUERY
4546
}
4647

4748
LDAP.set_spawner_attributes = {
48-
'environment': {'NB_USER': '{emailAddress}',
49+
'environment': {'NB_USER': '{uid}',
4950
'NB_UID': '{uidNumber}'},
5051
}
5152

5253
# Attributes used to check whether the ldap data
5354
# of type object_classes already exists
54-
# LDAP.unique_object_attributes = ['emailAddress']
55+
LDAP.unique_object_attributes = ['uid']
5556
LDAP.search_attribute_queries = [
5657
{'search_base': LDAP.base_dn,
5758
'search_filter': '(objectclass=X-nextUserIdentifier)',
@@ -64,7 +65,7 @@
6465

6566
# Submit object settings
6667
LDAP.object_classes = ['X-certsDistinguishedName', 'PosixAccount']
67-
LDAP.object_attributes = {'uid': '{emailAddress}',
68+
LDAP.object_attributes = {'uid': '{uid}',
6869
'uidNumber': '{uidNumber}',
6970
'gidNumber': '100',
70-
'homeDirectory': '/home/{emailAddress}'}
71+
'homeDirectory': '/home/{uid}'}

example/setup_ldap_hook_test_config.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@
5555
# LDAP.unique_object_attributes = ['emailAddress']
5656
LDAP.search_attribute_queries = [
5757
{'search_base': LDAP.base_dn,
58-
'search_filter': '(objectclass=X-nextUserIdentifier)',
58+
'search_filter': '(objectclass=x-nextUserIdentifier)',
5959
'attributes': ['uidNumber']}
6060
]
6161

@@ -64,7 +64,7 @@
6464
'modify_dn': modify_dn}}
6565

6666
# Submit object settings
67-
LDAP.object_classes = ['X-certsDistinguishedName', 'PosixAccount']
67+
LDAP.object_classes = ['x-certsDistinguishedName', 'PosixAccount']
6868
LDAP.object_attributes = {'uid': '{name}',
6969
'uidNumber': '{uidNumber}',
7070
'gidNumber': '100',

ldap_hooks/hooks.py

Lines changed: 61 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,13 @@
1616
LDAP_SEARCH_ATTRIBUTE_QUERY = '2'
1717
SPAWNER_ATTRIBUTE = '3'
1818
SPAWNER_USER_ATTRIBUTE = '4'
19+
LDAP_FIRST_SEARCH_ATTRIBUTE_QUERY = '5'
1920

2021
DYNAMIC_ATTRIBUTE_METHODS = (SPAWNER_SUBMIT_DATA,
2122
LDAP_SEARCH_ATTRIBUTE_QUERY,
2223
SPAWNER_ATTRIBUTE,
23-
SPAWNER_USER_ATTRIBUTE)
24+
SPAWNER_USER_ATTRIBUTE,
25+
LDAP_FIRST_SEARCH_ATTRIBUTE_QUERY)
2426
INCREMENT_ATTRIBUTE = '1'
2527
SEARCH_RESULT_OPERATION_ACTIONS = (INCREMENT_ATTRIBUTE,)
2628

@@ -346,10 +348,8 @@ def perform_search_result_operation(logger, conn_manager, base_dn,
346348

347349

348350
def get_interpolated_dynamic_attributes(logger, sources, dynamic_attributes):
349-
set_attributes = {}
351+
return_dict = {}
350352
for attr_key, attr_val in dynamic_attributes.items():
351-
# Check sources for LDAP_SEARCH_ATTRIBUTE_QUERY
352-
# key response with values
353353
val = None
354354
if attr_val not in DYNAMIC_ATTRIBUTE_METHODS:
355355
logger.error("LDAP - Illegal dynamic_attributes value: {}"
@@ -363,6 +363,14 @@ def get_interpolated_dynamic_attributes(logger, sources, dynamic_attributes):
363363
and sources[LDAP_SEARCH_ATTRIBUTE_QUERY]:
364364
val = get_dict_key(sources[LDAP_SEARCH_ATTRIBUTE_QUERY],
365365
attr_key)
366+
if attr_val == LDAP_FIRST_SEARCH_ATTRIBUTE_QUERY:
367+
if LDAP_FIRST_SEARCH_ATTRIBUTE_QUERY in sources \
368+
and sources[LDAP_FIRST_SEARCH_ATTRIBUTE_QUERY]:
369+
val = get_dict_key(sources[LDAP_FIRST_SEARCH_ATTRIBUTE_QUERY],
370+
attr_key)
371+
if isinstance(val, (list, set, tuple)):
372+
val = val[0]
373+
366374
if attr_val == SPAWNER_SUBMIT_DATA:
367375
if SPAWNER_SUBMIT_DATA in sources \
368376
and sources[SPAWNER_SUBMIT_DATA]:
@@ -376,13 +384,11 @@ def get_interpolated_dynamic_attributes(logger, sources, dynamic_attributes):
376384
if SPAWNER_USER_ATTRIBUTE in sources \
377385
and sources[SPAWNER_USER_ATTRIBUTE]:
378386
val = get_attr(sources[SPAWNER_USER_ATTRIBUTE], attr_key)
379-
if not val:
380-
logger.error("LDAP - Missing {} in {} which is required for {} in "
381-
"get_interpolated_dynamic_attributes".format(
382-
attr_val, sources, attr_key))
383-
return False
384-
set_attributes[attr_key] = val
385-
return set_attributes
387+
if val:
388+
return_dict[attr_key] = val
389+
logger.debug("LDAP - prepared interpolated_attributes {}".format(
390+
return_dict))
391+
return return_dict
386392

387393

388394
def update_spawner_attributes(spawner, spawner_attributes):
@@ -583,6 +589,7 @@ def setup_ldap_entry_hook(spawner):
583589
"LDAP - Retrived attributes {}".format(attributes))
584590
# Extract attributes from existing object
585591
sources = {LDAP_SEARCH_ATTRIBUTE_QUERY: attributes,
592+
LDAP_FIRST_SEARCH_ATTRIBUTE_QUERY: attributes,
586593
SPAWNER_SUBMIT_DATA: ldap_dict,
587594
SPAWNER_ATTRIBUTE: spawner,
588595
SPAWNER_USER_ATTRIBUTE: spawner.user}
@@ -660,6 +667,8 @@ def setup_ldap_entry_hook(spawner):
660667
return False
661668
attributes[attr_key] = post_operation_val
662669
sources.update({LDAP_SEARCH_ATTRIBUTE_QUERY:
670+
{attr_key: post_operation_val},
671+
LDAP_FIRST_SEARCH_ATTRIBUTE_QUERY:
663672
{attr_key: post_operation_val}})
664673

665674
ldap_dict.update(attributes)
@@ -671,26 +680,24 @@ def setup_ldap_entry_hook(spawner):
671680
spawner.log.debug(
672681
"LDAP - Sources state before interpolation "
673682
"with dynamic attributes {}".format(sources))
674-
prepared_dynamic_attributes = get_interpolated_dynamic_attributes(
683+
684+
prepared_object_attributes = get_interpolated_dynamic_attributes(
675685
spawner.log, sources, instance.dynamic_attributes)
676-
spawner.log.debug("LDAP - dynamic_attributes "
677-
"post interpolated: {}".format(
678-
prepared_dynamic_attributes))
679-
680-
if instance.dynamic_attributes and not prepared_dynamic_attributes:
681-
spawner.log.error("LDAP - Failed to setup prepared_attributes:"
682-
" {} with attribute_dict: {}".format(
683-
prepared_dynamic_attributes, ldap_dict))
686+
687+
spawner.log.debug("LDAP - prepared_object_attributes:"
688+
" {}".format(prepared_object_attributes))
689+
690+
if instance.dynamic_attributes and not prepared_object_attributes:
691+
spawner.log.error("LDAP - Failed to setup "
692+
"prepared_object_attributes: {} with "
693+
"attribute_dict: {}".format(
694+
prepared_object_attributes, ldap_dict)
695+
)
684696
return False
685697

686698
# Format dn provided variables
687-
recursive_format(instance.set_spawner_attributes,
688-
prepared_dynamic_attributes)
689699
recursive_format(instance.object_attributes,
690-
prepared_dynamic_attributes)
691-
692-
spawner.log.debug("LDAP - prepared spawner attributes {}".format(
693-
instance.set_spawner_attributes))
700+
prepared_object_attributes)
694701
spawner.log.debug("LDAP - prepared object attributes {}".format(
695702
instance.object_attributes))
696703

@@ -733,14 +740,41 @@ def setup_ldap_entry_hook(spawner):
733740
search_base, search_filter))
734741
success = search_for(conn_manager.get_connection(),
735742
search_base,
736-
search_filter)
743+
search_filter,
744+
attributes=ALL_ATTRIBUTES)
737745
if not success:
738746
spawner.log.error("Failed to find {} at {}".format(
739747
(search_base, search_filter), instance.url))
740748
return False
741749
spawner.log.info("LDAP - found {} in {}".format(
742750
conn_manager.get_response(), instance.url))
743751

752+
response = conn_manager.get_response()
753+
if len(response) > 1:
754+
spawner.log.error(
755+
"LDAP - multiple entries: {} were found with:"
756+
" {}".format(response, search_filter))
757+
return False
758+
759+
attributes = conn_manager.get_response_attributes()
760+
# TODO, validate all the attributes are as expected
761+
if not attributes:
762+
spawner.log.error(
763+
"LDAP - No attributes were returned from "
764+
"existing dn: {} with search_filer: {}".format(ldap_data,
765+
search_filter))
766+
return False
767+
768+
sources.update({LDAP_SEARCH_ATTRIBUTE_QUERY: attributes,
769+
LDAP_FIRST_SEARCH_ATTRIBUTE_QUERY: attributes})
770+
prepared_spawner_attributes = get_interpolated_dynamic_attributes(
771+
spawner.log, sources, instance.dynamic_attributes)
772+
773+
recursive_format(instance.set_spawner_attributes,
774+
prepared_spawner_attributes)
775+
spawner.log.debug("LDAP - formatted set_spawner_attributes: "
776+
"{} for the new entry: {}".format(
777+
instance.set_spawner_attributes, attributes))
744778
# Pass prepared attributes to spawner attributes
745779
update_spawner_attributes(spawner, instance.set_spawner_attributes)
746780
return True
Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
"""A simple jupyter config file for testing the authenticator."""
22
from ldap_hooks import setup_ldap_entry_hook
3-
from ldap_hooks import LDAP, LDAP_SEARCH_ATTRIBUTE_QUERY
3+
from ldap_hooks import LDAP, LDAP_SEARCH_ATTRIBUTE_QUERY, \
4+
SPAWNER_USER_ATTRIBUTE, LDAP_FIRST_SEARCH_ATTRIBUTE_QUERY
45
c = get_config()
56

7+
c.JupyterHub.ip = '0.0.0.0'
68
c.JupyterHub.hub_ip = '0.0.0.0'
7-
89
c.JupyterHub.authenticator_class = 'jhubauthenticators.HeaderAuthenticator'
910
c.JupyterHub.spawner_class = 'dockerspawner.DockerSpawner'
11+
1012
c.DockerSpawner.image = 'jupyter/base-notebook:latest'
1113
c.DockerSpawner.network_name = 'jhub_ldap_network'
1214

@@ -27,22 +29,21 @@
2729
LDAP.replace_object_with = {'/': '+'}
2830

2931
LDAP.dynamic_attributes = {
30-
'uidNumber': LDAP_SEARCH_ATTRIBUTE_QUERY
32+
'name': SPAWNER_USER_ATTRIBUTE,
33+
'uid': LDAP_FIRST_SEARCH_ATTRIBUTE_QUERY
3134
}
3235

3336
LDAP.set_spawner_attributes = {
34-
'environment': {'NB_UID': '{uidNumber}'}
37+
'environment': {'NB_USER': '{uid}'}
3538
}
3639

37-
LDAP.search_attribute_queries = [
38-
{'search_base': LDAP.base_dn,
39-
'search_filter': '(objectclass=X-nextUserIdentifier)',
40-
'attributes': ['uidNumber']}
41-
]
42-
43-
# LDAP DIT object definition
44-
LDAP.object_classes = ['Person', 'PosixAccount']
45-
LDAP.object_attributes = {
46-
'uidNumber': '{uidNumber}',
47-
'gidNumber': '100'
48-
}
40+
# Attributes used to check whether the ldap data
41+
# of type object_classes already exists
42+
LDAP.unique_object_attributes = ['CN']
43+
44+
# Submit object settings
45+
LDAP.object_classes = ['InetOrgPerson', 'PosixAccount']
46+
LDAP.object_attributes = {'uid': '{name}',
47+
'uidNumber': '1000 ',
48+
'gidNumber': '100',
49+
'homeDirectory': '/home/{name}'}

tests/configs/jhub/ldap_person_dynamic_attr_hook.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from ldap_hooks import LDAP, SPAWNER_SUBMIT_DATA
44
c = get_config()
55

6+
c.JupyterHub.ip = '0.0.0.0'
67
c.JupyterHub.hub_ip = '0.0.0.0'
78

89
c.JupyterHub.authenticator_class = 'jhubauthenticators.HeaderAuthenticator'

tests/configs/jhub/ldap_person_hook.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from ldap_hooks import LDAP
44
c = get_config()
55

6+
c.JupyterHub.ip = '0.0.0.0'
67
c.JupyterHub.hub_ip = '0.0.0.0'
78

89
c.JupyterHub.authenticator_class = 'jhubauthenticators.HeaderAuthenticator'

0 commit comments

Comments
 (0)