Skip to content

Commit e0ccaa3

Browse files
committed
Split concern between spawner attributes and spawner user attributes in providing dynamic attributes for now
1 parent 7347b01 commit e0ccaa3

File tree

2 files changed

+127
-53
lines changed

2 files changed

+127
-53
lines changed
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
# Example config
2+
from jhubauthenticators import RegexUsernameParser
3+
from ldap_hooks import setup_ldap_entry_hook
4+
from ldap_hooks import LDAP, LDAP_SEARCH_ATTRIBUTE_QUERY, \
5+
SPAWNER_SUBMIT_DATA, INCREMENT_ATTRIBUTE, SPAWNER_USER_ATTRIBUTE
6+
c = get_config()
7+
8+
c.JupyterHub.ip = '0.0.0.0'
9+
c.JupyterHub.hub_ip = '0.0.0.0'
10+
c.JupyterHub.port = 80
11+
12+
# Spawner setup
13+
c.JupyterHub.spawner_class = 'dockerspawner.DockerSpawner'
14+
c.DockerSpawner.image = 'nielsbohr/base-notebook:latest'
15+
c.DockerSpawner.pre_spawn_hook = setup_ldap_entry_hook
16+
17+
# Authenticator setup
18+
c.JupyterHub.authenticator_class = 'jhubauthenticators.HeaderAuthenticator'
19+
c.HeaderAuthenticator.enable_auth_state = True
20+
c.HeaderAuthenticator.allowed_headers = {'auth': 'Remote-User'}
21+
c.HeaderAuthenticator.header_parser_classes = {'auth': RegexUsernameParser}
22+
c.HeaderAuthenticator.user_external_allow_attributes = ['data']
23+
# Email regex
24+
RegexUsernameParser.username_extract_regex = '([a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]' \
25+
'+\.[a-zA-Z0-9-.]+)'
26+
27+
28+
# Define LDAP connection options
29+
LDAP.url = "openldap"
30+
LDAP.user = "cn=admin,dc=migrid,dc=org"
31+
LDAP.password = "dummyldap_password"
32+
LDAP.base_dn = "dc=migrid,dc=org"
33+
34+
# LDAP get dn to submit to the DIT
35+
LDAP.submit_spawner_attribute = 'user.data'
36+
LDAP.submit_spawner_attribute_keys = ('User', 'CERT')
37+
38+
# Prepare LDAP object
39+
LDAP.replace_object_with = {'/': '+'}
40+
41+
# Dynamic attributes and where to find the value
42+
LDAP.dynamic_attributes = {
43+
'emailAddress': SPAWNER_SUBMIT_DATA,
44+
'name': SPAWNER_USER_ATTRIBUTE,
45+
'uidNumber': LDAP_SEARCH_ATTRIBUTE_QUERY
46+
}
47+
48+
LDAP.set_spawner_attributes = {
49+
'environment': {'NB_USER': '{name}',
50+
'NB_UID': '{uidNumber}'},
51+
}
52+
53+
# Attributes used to check whether the ldap data
54+
# of type object_classes already exists
55+
# LDAP.unique_object_attributes = ['emailAddress']
56+
LDAP.search_attribute_queries = [
57+
{'search_base': LDAP.base_dn,
58+
'search_filter': '(objectclass=X-nextUserIdentifier)',
59+
'attributes': ['uidNumber']}
60+
]
61+
62+
modify_dn = 'cn=uidNext' + ',' + LDAP.base_dn
63+
LDAP.search_result_operations = {'uidNumber': {'action': INCREMENT_ATTRIBUTE,
64+
'modify_dn': modify_dn}}
65+
66+
# Submit object settings
67+
LDAP.object_classes = ['X-certsDistinguishedName', 'PosixAccount']
68+
LDAP.object_attributes = {'uid': '{name}',
69+
'uidNumber': '{uidNumber}',
70+
'gidNumber': '100',
71+
'homeDirectory': '/home/{name}'}

ldap_hooks/hooks.py

Lines changed: 56 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,13 @@
1414

1515
SPAWNER_SUBMIT_DATA = '1'
1616
LDAP_SEARCH_ATTRIBUTE_QUERY = '2'
17-
SPAWNER_ATTR = '3'
17+
SPAWNER_ATTRIBUTE = '3'
18+
SPAWNER_USER_ATTRIBUTE = '4'
19+
1820
DYNAMIC_ATTRIBUTE_METHODS = (SPAWNER_SUBMIT_DATA,
1921
LDAP_SEARCH_ATTRIBUTE_QUERY,
20-
SPAWNER_ATTR)
21-
22+
SPAWNER_ATTRIBUTE,
23+
SPAWNER_USER_ATTRIBUTE)
2224
INCREMENT_ATTRIBUTE = '1'
2325
SEARCH_RESULT_OPERATION_ACTIONS = (INCREMENT_ATTRIBUTE,)
2426

@@ -272,13 +274,19 @@ def get_dict_key(input_dict, attr):
272274
return input_dict[attr]
273275

274276

277+
def get_attr(obj, attr):
278+
has_attr = hasattr(obj, attr)
279+
if not has_attr:
280+
return False
281+
return getattr(obj, attr)
282+
283+
275284
def rec_get_attr(obj, attr):
276285
attributes = attr.split('.')
277286
for attr in attributes:
278-
has_attr = hasattr(obj, attr)
279-
if not has_attr:
287+
obj = get_attr(obj, attr)
288+
if not obj:
280289
return False
281-
obj = getattr(obj, attr)
282290
return obj
283291

284292

@@ -360,28 +368,31 @@ def get_interpolated_dynamic_attributes(logger, sources, dynamic_attributes):
360368
and sources[SPAWNER_SUBMIT_DATA]:
361369
val = get_dict_key(sources[SPAWNER_SUBMIT_DATA],
362370
attr_key)
363-
if attr_val == SPAWNER_ATTR:
364-
if SPAWNER_ATTR in sources \
365-
and sources[SPAWNER_ATTR]:
366-
val = rec_get_attr(sources[SPAWNER_ATTR],
367-
attr_key)
371+
if attr_val == SPAWNER_ATTRIBUTE:
372+
if SPAWNER_ATTRIBUTE in sources \
373+
and sources[SPAWNER_ATTRIBUTE]:
374+
val = get_attr(sources[SPAWNER_ATTRIBUTE], attr_key)
375+
if attr_val == SPAWNER_USER_ATTRIBUTE:
376+
if SPAWNER_USER_ATTRIBUTE in sources \
377+
and sources[SPAWNER_USER_ATTRIBUTE]:
378+
val = get_attr(sources[SPAWNER_USER_ATTRIBUTE], attr_key)
368379
if not val:
369-
logger.error("LDAP - Missing {} in {} which is required for {} in"
370-
" get_interpolated_dynamic_attributes".format(
380+
logger.error("LDAP - Missing {} in {} which is required for {} in "
381+
"get_interpolated_dynamic_attributes".format(
371382
attr_val, sources, attr_key))
372383
return False
373384
set_attributes[attr_key] = val
374385
return set_attributes
375386

376387

377388
def update_spawner_attributes(spawner, spawner_attributes):
378-
for spawner_attr, spawner_value in spawner_attributes.items():
379-
if hasattr(spawner, spawner_attr):
380-
attr = getattr(spawner, spawner_attr)
389+
for SPAWNER_ATTRIBUTE, spawner_value in spawner_attributes.items():
390+
if hasattr(spawner, SPAWNER_ATTRIBUTE):
391+
attr = getattr(spawner, SPAWNER_ATTRIBUTE)
381392
if isinstance(attr, dict):
382393
attr.update(spawner_value)
383394
if isinstance(attr, list) or isinstance(attr, str):
384-
setattr(spawner, spawner_attr, spawner_value)
395+
setattr(spawner, SPAWNER_ATTRIBUTE, spawner_value)
385396

386397

387398
@gen.coroutine
@@ -393,7 +404,6 @@ def hello_hook(spawner):
393404
@gen.coroutine
394405
def setup_ldap_entry_hook(spawner):
395406
instance = LDAP()
396-
397407
# TODO, copy entire default config options dynamically
398408
instance.dynamic_attributes = copy.deepcopy(instance.dynamic_attributes)
399409
instance.set_spawner_attributes = copy.deepcopy(
@@ -434,8 +444,7 @@ def setup_ldap_entry_hook(spawner):
434444
tuple):
435445
spawner.log.error("LDAP - submit_spawner_attribute_keys is "
436446
"of incorrect type: {} must be a tuple".format(
437-
type(instance.submit_spawner_attribute_keys
438-
))
447+
type(instance.submit_spawner_attribute_keys))
439448
)
440449
return False
441450

@@ -446,8 +455,7 @@ def setup_ldap_entry_hook(spawner):
446455
spawner.log.error("LDAP - Failed to extract the specified dict "
447456
"tuple string: {} from dict: {}".format(
448457
instance.submit_spawner_attribute_keys,
449-
ldap_data
450-
))
458+
ldap_data))
451459
return False
452460

453461
ldap_data = new_ldap_data
@@ -490,8 +498,7 @@ def setup_ldap_entry_hook(spawner):
490498
if missing:
491499
spawner.log.error("LDAP - only found: {} required "
492500
"supported objectclasses, missing: {}".format(
493-
found, missing
494-
))
501+
found, missing))
495502
return False
496503

497504
# Prepare ldap data
@@ -554,8 +561,7 @@ def setup_ldap_entry_hook(spawner):
554561
attributes=ALL_ATTRIBUTES)
555562
if success:
556563
spawner.log.info("LDAP - {} already exist, response {}".format(
557-
ldap_dict, conn_manager.get_response())
558-
)
564+
ldap_dict, conn_manager.get_response()))
559565

560566
response = conn_manager.get_response()
561567
if len(response) > 1:
@@ -578,22 +584,23 @@ def setup_ldap_entry_hook(spawner):
578584
# Extract attributes from existing object
579585
sources = {LDAP_SEARCH_ATTRIBUTE_QUERY: attributes,
580586
SPAWNER_SUBMIT_DATA: ldap_dict,
581-
SPAWNER_ATTR: spawner}
587+
SPAWNER_ATTRIBUTE: spawner,
588+
SPAWNER_USER_ATTRIBUTE: spawner.user}
582589
spawner.log.debug("LDAP - dynamic_attributes "
583590
"pre interpolated: {}".format(
584-
instance.dynamic_attributes
585-
))
591+
instance.dynamic_attributes))
586592
prepared_dynamic_attributes = get_interpolated_dynamic_attributes(
587-
spawner.log, sources, instance.dynamic_attributes
588-
)
593+
spawner.log, sources, instance.dynamic_attributes)
589594

590595
if instance.dynamic_attributes and not prepared_dynamic_attributes:
591596
spawner.log.error("LDAP - Failed to setup prepared_attributes:"
592597
" {} with attribute_dict: {}".format(
593598
prepared_dynamic_attributes,
594-
attributes
595-
))
599+
attributes))
596600
return False
601+
spawner.log.debug("LDAP - dynamic_attributes "
602+
"post interpolated: {}".format(
603+
prepared_dynamic_attributes))
597604
# Setup set_spawner_attributes
598605
recursive_format(instance.set_spawner_attributes,
599606
prepared_dynamic_attributes)
@@ -659,19 +666,21 @@ def setup_ldap_entry_hook(spawner):
659666

660667
# Prepare required dynamic attributes
661668
sources.update({SPAWNER_SUBMIT_DATA: ldap_dict,
662-
SPAWNER_ATTR: spawner})
663-
spawner.log.debug("LDAP - Sources state before interpolating with "
664-
" dynamic attributes {}".format(sources))
669+
SPAWNER_ATTRIBUTE: spawner,
670+
SPAWNER_USER_ATTRIBUTE: spawner.user})
671+
spawner.log.debug(
672+
"LDAP - Sources state before interpolation "
673+
"with dynamic attributes {}".format(sources))
665674
prepared_dynamic_attributes = get_interpolated_dynamic_attributes(
666-
spawner.log, sources, instance.dynamic_attributes
667-
)
675+
spawner.log, sources, instance.dynamic_attributes)
676+
spawner.log.debug("LDAP - dynamic_attributes "
677+
"post interpolated: {}".format(
678+
prepared_dynamic_attributes))
668679

669680
if instance.dynamic_attributes and not prepared_dynamic_attributes:
670681
spawner.log.error("LDAP - Failed to setup prepared_attributes:"
671682
" {} with attribute_dict: {}".format(
672-
prepared_dynamic_attributes,
673-
ldap_dict
674-
))
683+
prepared_dynamic_attributes, ldap_dict))
675684
return False
676685

677686
# Format dn provided variables
@@ -681,11 +690,9 @@ def setup_ldap_entry_hook(spawner):
681690
prepared_dynamic_attributes)
682691

683692
spawner.log.debug("LDAP - prepared spawner attributes {}".format(
684-
instance.set_spawner_attributes
685-
))
693+
instance.set_spawner_attributes))
686694
spawner.log.debug("LDAP - prepared object attributes {}".format(
687-
instance.object_attributes
688-
))
695+
instance.object_attributes))
689696

690697
# Add DN
691698
spawner.log.info("LDAP - submit object: {}, attributes: {} "
@@ -715,14 +722,12 @@ def setup_ldap_entry_hook(spawner):
715722
search_filter = '(&{}'.format(
716723
''.join(['(objectclass={})'.format(object_class)
717724
for object_class in
718-
instance.object_classes])
719-
)
725+
instance.object_classes]))
720726

721727
search_filter += '{})'.format(
722728
''.join(['({}={})'.format(attr_key, attr_val)
723729
for attr_key, attr_val in
724-
instance.object_attributes.items()])
725-
)
730+
instance.object_attributes.items()]))
726731
spawner.log.debug("LDAP - search_for, "
727732
"search_base {}, search_filter {}".format(
728733
search_base, search_filter))
@@ -731,12 +736,10 @@ def setup_ldap_entry_hook(spawner):
731736
search_filter)
732737
if not success:
733738
spawner.log.error("Failed to find {} at {}".format(
734-
(search_base, search_filter), instance.url)
735-
)
739+
(search_base, search_filter), instance.url))
736740
return False
737741
spawner.log.info("LDAP - found {} in {}".format(
738-
conn_manager.get_response(), instance.url)
739-
)
742+
conn_manager.get_response(), instance.url))
740743

741744
# Pass prepared attributes to spawner attributes
742745
update_spawner_attributes(spawner, instance.set_spawner_attributes)

0 commit comments

Comments
 (0)