Skip to content

Commit

Permalink
Support fullname field in SAMLResponse
Browse files Browse the repository at this point in the history
As discussed in keitaroinc#11 in some cases it is useful to use the "full name"
claim rather than the concatenation of "first" + "last" name. Setting
aside that not all parts of the world use the first + last name format
for people's names, in some cases some customization is applied in the
IdP side to the user display name, eg to show the department the user
belongs to (eg "John Garcia (Operations)").

This change keeps all existing functionality relying on first + last
names but adds support for a new config option to provide the full name
instead. Startup checks and default fallbacks work in the same way as
before.
  • Loading branch information
amercader committed Feb 17, 2021
1 parent 99db983 commit 03cb5eb
Show file tree
Hide file tree
Showing 3 changed files with 32 additions and 10 deletions.
4 changes: 4 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@ Required::
# Corresponding SAML user field for lastname
ckanext.saml2auth.user_lastname = lastname

# Corresponding SAML user field for fullname
# (Optional: Can be used as an alternative to firstname + lastname)
ckanext.saml2auth.user_fullname = fullname

# Corresponding SAML user field for email
ckanext.saml2auth.user_email = email

Expand Down
15 changes: 12 additions & 3 deletions ckanext/saml2auth/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,7 @@ def configure(self, config):
# exception if they're missing.
missing_config = "{0} is not configured. Please amend your .ini file."
config_options = (
'ckanext.saml2auth.user_firstname',
'ckanext.saml2auth.user_lastname',
'ckanext.saml2auth.user_email'
'ckanext.saml2auth.user_email',
)
if not config.get('ckanext.saml2auth.idp_metadata.local_path'):
config_options += ('ckanext.saml2auth.idp_metadata.remote_url',
Expand All @@ -38,6 +36,17 @@ def configure(self, config):
if not config.get(option, None):
raise RuntimeError(missing_config.format(option))

first_and_last_name = all((
config.get('ckanext.saml2auth.user_firstname'),
config.get('ckanext.saml2auth.user_lastname')
))
full_name = config.get('ckanext.saml2auth.user_fullname')

if not first_and_last_name and not full_name:
raise RuntimeError('''
You need to provide both ckanext.saml2auth.user_firstname +
ckanext.saml2auth.user_lastname or ckanext.saml2auth.user_fullname'''.strip())

acs_endpoint = config.get('ckanext.saml2auth.acs_endpoint')
if acs_endpoint and not acs_endpoint.startswith('/'):
raise RuntimeError('ckanext.saml2auth.acs_endpoint should start with a slash ("/")')
Expand Down
23 changes: 16 additions & 7 deletions ckanext/saml2auth/views/saml2auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def _get_requested_authn_contexts():
return requested_authn_contexts.strip().split()


def process_user(email, saml_id, firstname, lastname):
def process_user(email, saml_id, full_name):
""" Check if CKAN-SAML user exists for the current SAML login """

context = {
Expand All @@ -53,9 +53,9 @@ def process_user(email, saml_id, firstname, lastname):
# SAML user name or SAML user email are changed
# in the identity provider
if email != user_dict['email'] \
or u'{0} {1}'.format(firstname, lastname) != user_dict['fullname']:
or full_name != user_dict['fullname']:
user_dict['email'] = email
user_dict['fullname'] = u'{0} {1}'.format(firstname, lastname)
user_dict['fullname'] = full_name
try:
user_dict = logic.get_action(u'user_update')(context, user_dict)
except logic.ValidationError as e:
Expand Down Expand Up @@ -89,7 +89,7 @@ def process_user(email, saml_id, firstname, lastname):

data_dict = {
u'name': _get_random_username_from_email(email),
u'fullname': u'{0} {1}'.format(firstname, lastname),
u'fullname': full_name,
u'email': email,
u'password': h.generate_password(),
u'plugin_extras': {
Expand Down Expand Up @@ -118,6 +118,8 @@ def acs():
config.get(u'ckanext.saml2auth.user_firstname')
saml_user_lastname = \
config.get(u'ckanext.saml2auth.user_lastname')
saml_user_fullname = \
config.get(u'ckanext.saml2auth.user_fullname')
saml_user_email = \
config.get(u'ckanext.saml2auth.user_email')

Expand Down Expand Up @@ -148,10 +150,17 @@ def acs():
# Required user attributes for user creation
email = auth_response.ava[saml_user_email][0]

firstname = auth_response.ava.get(saml_user_firstname, [email.split('@')[0]])[0]
lastname = auth_response.ava.get(saml_user_lastname, [email.split('@')[1]])[0]
if saml_user_firstname and saml_user_lastname:
first_name = auth_response.ava.get(saml_user_firstname, [email.split('@')[0]])[0]
last_name = auth_response.ava.get(saml_user_lastname, [email.split('@')[1]])[0]
full_name = u'{} {}'.format(first_name, last_name)
else:
if saml_user_fullname in auth_response.ava:
full_name = auth_response.ava[saml_user_fullname][0]
else:
full_name = u'{} {}'.format(email.split('@')[0], email.split('@')[1])

g.user = process_user(email, saml_id, firstname, lastname)
g.user = process_user(email, saml_id, full_name)

# Check if the authenticated user email is in given list of emails
# and make that user sysadmin and opposite
Expand Down

0 comments on commit 03cb5eb

Please sign in to comment.