Skip to content
Snippets Groups Projects
Commit a1741146 authored by Jörn Künsemöller's avatar Jörn Künsemöller
Browse files

TMSS-1160: allow filtering for usernames and email to limit the amount of info...

TMSS-1160: allow filtering for usernames and email to limit the amount of info we fetch from Keycloak
parent 69e6af9e
No related branches found
No related tags found
1 merge request!825TMSS-1160: allow filtering for usernames and email to limit the amount of info...
...@@ -86,48 +86,47 @@ def get_project_persons(include_projects: tuple = None): ...@@ -86,48 +86,47 @@ def get_project_persons(include_projects: tuple = None):
if not users and role in legacy_role_keys: if not users and role in legacy_role_keys:
users = attributes.get(legacy_role_keys[role], []) users = attributes.get(legacy_role_keys[role], [])
# convert user list (LDAP DNs) to something we can use in TMSS (email) # convert user list (LDAP DNs) to something we can use in TMSS (email).
user_map = get_user_mapping() # guess the likely username from LDAP DN (to query a sensible amount of data, unfortunately we cannot filter for LDAP attributes directly)
# (Note: this is not exact since the Keycloak filter returns partial matches, but that does not harm us.
# Also, there is a theoretical risk that the Keycloak username differs from the LDAP cn, in
# which case we do not request the required detailed info of that user.
# But since get_user_mapping does match on LDAP DN, the matches we get should always be correct.)
usernames = tuple([user.split('cn=')[1].split(',')[0] for user in users if 'cn=' in user])
# then request the email addresses of these users and map their LDAP DN to their email address
user_map = get_user_mapping(include_usernames=usernames)
mapped_users = [user_map[user] for user in users if user in user_map] # email list of referenced users mapped_users = [user_map[user] for user in users if user in user_map] # email list of referenced users
unmappable_users = [user for user in users if user not in user_map] # list of references for which no account was found unmappable_users = [user for user in users if user not in user_map] # list of references for which no account was found
for unmappable_user in unmappable_users: for unmappable_user in unmappable_users:
# Note: Usually Keycloak should return DN references to user accounts. For PI's, someone had the logger.warning("Could not match Keycloak user reference '%s' to a known user." % unmappable_user)
# great idea to allow to specify a freeform string instead, to refer to people who may or may not if not unmappable_user.startswith('cn='):
# have an account. Even if the person has a user account, there is no way to replicate the exact logger.warning("LOFAR allowed to reference a person by a freeform string instead of a user account. '%s' seems to be such a legacy reference. This needs to be fixed in the identity management." % unmappable_user)
# string 'representation' Keycloak returns, since the string may contain typos, or info that is not
# stored in the user accounts (like titles).
# The following unsafe hack tries to determine whether there is a user account that matches the
# name given in the string (ignore titles since they are not part of the user account):
# unmappable_user_fixed = re.sub('Dr\.', '', unmappable_user)
# unmappable_user_fixed = re.sub('Prof\.', '', unmappable_user_fixed)
# unmappable_user_fixed = re.sub('ir\.', '', unmappable_user_fixed)
# unmappable_user_fixed = re.sub('Ir\.', '', unmappable_user_fixed)
# unmappable_user_fixed = re.sub('apl\.', '', unmappable_user_fixed)
# unmappable_user_fixed = re.sub(' +', ' ', unmappable_user_fixed)
#
# if unmappable_user_fixed in user_map:
# mapped_users.append(user_map[unmappable_user_fixed])
# else:
logger.warning("Could not match Keycloak user reference '%s' to a known user." % unmappable_user)
if not unmappable_user.startswith('cn='):
logger.warning("LOFAR allowed to reference a person by a freeform string instead of a user account. '%s' seems to be such a legacy reference. This needs to be fixed in the identity management." % unmappable_user)
project_persons_map.setdefault(project['name'], {})[role] = mapped_users project_persons_map.setdefault(project['name'], {})[role] = mapped_users
return project_persons_map return project_persons_map
@cachetools.func.ttl_cache(ttl=6000) @cachetools.func.ttl_cache(ttl=600)
def get_user_mapping(): def get_user_mapping(include_usernames: tuple = None, include_email: tuple = None):
""" """
returns a mapping of both the string ('Project, Tobitha') or LDAP ('cn=to_project,ou=Users,o=lofartest,c=eu') returns a mapping of both the string ('Project, Tobitha') or LDAP ('cn=to_project,ou=Users,o=lofartest,c=eu')
representations of users that are returned by Keycloak to a reference that we can use to identify a user representations of users that are returned by Keycloak to a reference that we can use to identify a user
in TMSS, i.e. email. in TMSS, i.e. email.
Optionally filter for email address and/or username since querying all users is very expensive.
# todo: consider looking up / creating / returning user objects directly (but that generates a ton of lookups and unnecessary users) # todo: consider looking up / creating / returning user objects directly (but that generates a ton of lookups and unnecessary users)
# todo: we need to review that all used references are actually unique, especially Keycloak's reference by string representation does not look safe! # todo: we need to review that all used references are actually unique, especially Keycloak's reference by string representation does not look safe!
""" """
user_map = {} user_map = {}
with KeycloakAdminAPISession() as ksession: with KeycloakAdminAPISession() as ksession:
users = ksession.get(url='%s/users/?max=99999' % KEYCLOAK_API_BASE_URL) if include_usernames is None and include_email is None:
users = ksession.get(url='%s/users/?max=99999' % KEYCLOAK_API_BASE_URL)
else:
users = []
for username in include_usernames or []:
users += (ksession.get(url='%s/users/?username=%s' % (KEYCLOAK_API_BASE_URL, username)))
for email in include_email or []:
users += (ksession.get(url='%s/users/?email=%s' % (KEYCLOAK_API_BASE_URL, email)))
for user in users: for user in users:
if 'attributes' in user: if 'attributes' in user:
for ldap_dn in user['attributes'].get('LDAP_ENTRY_DN', []): for ldap_dn in user['attributes'].get('LDAP_ENTRY_DN', []):
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment