Skip to content

Commit 826b5f0

Browse files
committed
implement consistent handling of unsupported characters in auto add user backend (autocreate). Adds configuration option to define which ID fields to filter and how. By default no filtering is applied, but if enabled on full name the default is to skip unsupported characters no matter what auth type is used (OpenID 2.0, OpenID Connect or user certificates). One can set filter method to 'hexlify' instead to get a simple reversible hex translation of such unsupported chars. This should basically address migrid-sync issue 35, but we probably want to add more reversible translators later.
git-svn-id: svn+ssh://svn.code.sf.net/p/migrid/code/trunk@6047 b75ad72c-e7d7-11dd-a971-7dbc132099af
1 parent 055004d commit 826b5f0

File tree

5 files changed

+99
-7
lines changed

5 files changed

+99
-7
lines changed

mig/install/MiGserver-template.conf

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,15 @@ auto_add_oid_user = __AUTO_ADD_OID_USER__
1616
auto_add_oidc_user = __AUTO_ADD_OIDC_USER__
1717
# Auto create dedicated MiG resources from valid users
1818
#auto_add_resource = False
19+
# Apply filters to handle illegal characters e.g. in names during auto add
20+
# User ID fields to filter: full_name, organization, ...
21+
# Leave filter fields empty or unset to disable all filters and let input
22+
# validation simply reject user sign up if names contain such characters.
23+
auto_add_filter_fields =
24+
# How to handle each illegal character in the configured filter fields. The
25+
# default is to skip each such character. Other valid options include hexlify
26+
# to encode each such character with the corresponding hex codepoint.
27+
auto_add_filter_method = skip
1928
# Default account expiry unless set. Renew and web login extends by default.
2029
cert_valid_days = __CERT_VALID_DAYS__
2130
oid_valid_days = __OID_VALID_DAYS__

mig/shared/configuration.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,11 @@
4848
try:
4949
from mig.shared.defaults import CSRF_MINIMAL, CSRF_WARN, CSRF_MEDIUM, \
5050
CSRF_FULL, POLICY_NONE, POLICY_WEAK, POLICY_MEDIUM, POLICY_HIGH, \
51-
POLICY_MODERN, POLICY_CUSTOM, freeze_flavors, \
51+
POLICY_MODERN, POLICY_CUSTOM, freeze_flavors, cert_field_order, \
5252
duplicati_protocol_choices, default_css_filename, keyword_any, \
5353
cert_valid_days, oid_valid_days, generic_valid_days, keyword_all, \
5454
keyword_file, keyword_env, DEFAULT_USER_ID_FORMAT, \
55-
valid_user_id_formats, default_twofactor_auth_apps
55+
valid_user_id_formats, valid_filter_methods, default_twofactor_auth_apps
5656
from mig.shared.logger import Logger, SYSLOG_GDP
5757
from mig.shared.html import menu_items, vgrid_items
5858
from mig.shared.fileio import read_file, load_json, write_file
@@ -137,6 +137,8 @@ def fix_missing(config_file, verbose=True):
137137
'auto_add_oid_user': False,
138138
'auto_add_oidc_user': False,
139139
'auto_add_resource': False,
140+
'auto_add_filter_method': '',
141+
'auto_add_filter_fields': '',
140142
'server_fqdn': fqdn,
141143
'support_email': '',
142144
'admin_email': '%s@%s' % (user, fqdn),
@@ -668,6 +670,8 @@ class Configuration:
668670
auto_add_oid_user = False
669671
auto_add_oidc_user = False
670672
auto_add_resource = False
673+
auto_add_filter_method = ''
674+
auto_add_filter_fields = []
671675

672676
# ARC resource configuration (list)
673677
# wired-in shorthands in arcwrapper:
@@ -2521,6 +2525,18 @@ def reload_config(self, verbose, skip_log=False):
25212525
self.auto_add_resource = config.getboolean('GLOBAL',
25222526
'auto_add_resource')
25232527

2528+
# Apply requested automatic filtering of selected auto add user fields
2529+
if config.has_option('GLOBAL', 'auto_add_filter_method'):
2530+
filter_method = config.get('GLOBAL', 'auto_add_filter_method')
2531+
if filter_method not in valid_filter_methods:
2532+
filter_method = ''
2533+
self.auto_add_filter_method = filter_method
2534+
if config.has_option('GLOBAL', 'auto_add_filter_fields'):
2535+
filter_fields = config.get('GLOBAL', 'auto_add_filter_fields')
2536+
valid_filter_fields = [i[0] for i in cert_field_order]
2537+
self.auto_add_filter_fields = [i for i in filter_fields.split()
2538+
if i in valid_filter_fields]
2539+
25242540
# Allow override of account valid days from shared.defaults
25252541
if config.has_option('GLOBAL', 'cert_valid_days'):
25262542
self.cert_valid_days = config.getint('GLOBAL', 'cert_valid_days')

mig/shared/defaults.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,8 @@
110110
('email', 'emailAddress'),
111111
]
112112

113+
valid_filter_methods = ['', 'skip', 'hexlify']
114+
113115
sandbox_names = ['sandbox', 'oneclick', 'ps3live']
114116

115117
email_keyword_list = ['mail', 'email']

mig/shared/functionality/autocreate.py

Lines changed: 67 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939

4040
from __future__ import absolute_import
4141

42+
import base64
4243
import os
4344
import time
4445

@@ -231,6 +232,66 @@ def split_comma_concat(value_list, sep=','):
231232
return result
232233

233234

235+
def lookup_filter_illegal_handler(filter_method):
236+
"""Get the illegal handler function to match filter_method. The output is
237+
directly suitable for the filter_X helpers with illegal_handler argument.
238+
"""
239+
# NOTE: the None value is a special case that means skip illegal values
240+
if filter_method in ('', 'skip'):
241+
return None
242+
elif filter_method == 'hexlify':
243+
def hex_wrap(val):
244+
"""Insert a clearly marked hex representation of val"""
245+
# NOTE: use '.X' as '.x' will typically be capitalized in use anyway
246+
return ".X%s" % base64.b16encode(val)
247+
return hex_wrap
248+
else:
249+
raise ValueError("unsupported filter_method: %r" % filter_method)
250+
251+
252+
def populate_prefilters(configuration, prefilter_map, auth_type):
253+
"""Populate the prefilter map applied to input values before anything else
254+
so that they can be used e.g. to mangle illegal values into compliance.
255+
Particularly useful for making sure we keep file system names to something
256+
we can actually safely handle.
257+
"""
258+
_logger = configuration.logger
259+
_logger.debug("populate prefilters for %s" % auth_type)
260+
# TODO: add better reversible filters like punycode or base64 on whole name
261+
filter_name = configuration.auto_add_filter_method
262+
illegal_handler = lookup_filter_illegal_handler(filter_name)
263+
_logger.debug("populate prefilters found filter illegal char handler %s" %
264+
illegal_handler)
265+
if auth_type == AUTH_OPENID_V2:
266+
if filter_name and 'full_name' in configuration.auto_add_filter_fields:
267+
def _filter_helper(x):
268+
return filter_commonname(x, illegal_handler)
269+
# NOTE: KUIT OpenID 2.0 provides full name as 'fullname'
270+
for name in ('openid.sreg.fullname', 'openid.sreg.full_name'):
271+
prefilter_map[name] = _filter_helper
272+
elif auth_type == AUTH_OPENID_CONNECT:
273+
if configuration.auto_add_filter_method and \
274+
'full_name' in configuration.auto_add_filter_fields:
275+
def _filter_helper(x):
276+
return filter_commonname(x, illegal_handler)
277+
# NOTE: WAYF provides full name as 'name'
278+
for name in ('oidc.claim.fullname', 'oidc.claim.full_name',
279+
'oidc.claim.name'):
280+
prefilter_map[name] = _filter_helper
281+
elif auth_type == AUTH_CERTIFICATE:
282+
if configuration.auto_add_filter_method and \
283+
'full_name' in configuration.auto_add_filter_fields:
284+
def _filter_helper(x):
285+
return filter_commonname(x, illegal_handler)
286+
for name in ('cert_name', ):
287+
prefilter_map[name] = _filter_helper
288+
else:
289+
raise ValueError("unsupported auth_type in populate_prefilters: %r" %
290+
auth_type)
291+
_logger.debug("populate prefilters returns: %s" % prefilter_map)
292+
return prefilter_map
293+
294+
234295
def main(client_id, user_arguments_dict, environ=None):
235296
"""Main function used by front end"""
236297

@@ -257,6 +318,8 @@ def main(client_id, user_arguments_dict, environ=None):
257318
output_objects.append({'object_type': 'error_text', 'text':
258319
'%s sign up not supported' % auth_flavor})
259320
return (output_objects, returnvalues.SYSTEM_ERROR)
321+
# NOTE: simple filters to handle unsupported chars e.g. in full name
322+
populate_prefilters(configuration, prefilter_map, auth_type)
260323
elif identity and auth_type == AUTH_OPENID_V2:
261324
if auth_flavor == AUTH_MIG_OID:
262325
base_url = configuration.migserver_https_mig_oid_url
@@ -267,9 +330,8 @@ def main(client_id, user_arguments_dict, environ=None):
267330
output_objects.append({'object_type': 'error_text', 'text':
268331
'%s sign up not supported' % auth_flavor})
269332
return (output_objects, returnvalues.SYSTEM_ERROR)
270-
for name in ('openid.sreg.cn', 'openid.sreg.fullname',
271-
'openid.sreg.full_name'):
272-
prefilter_map[name] = filter_commonname
333+
# NOTE: simple filters to handle unsupported chars e.g. in full name
334+
populate_prefilters(configuration, prefilter_map, auth_type)
273335
elif identity and auth_type == AUTH_OPENID_CONNECT:
274336
if auth_flavor == AUTH_MIG_OIDC:
275337
base_url = configuration.migserver_https_mig_oidc_url
@@ -286,6 +348,8 @@ def main(client_id, user_arguments_dict, environ=None):
286348
low_key = key.replace('OIDC_CLAIM_', 'oidc.claim.').lower()
287349
if low_key in oidc_keys:
288350
user_arguments_dict[low_key] = [environ[key]]
351+
# NOTE: simple filters to handle unsupported chars e.g. in full name
352+
populate_prefilters(configuration, prefilter_map, auth_type)
289353
else:
290354
logger.error('autocreate without ID rejected for %s' % client_id)
291355
output_objects.append({'object_type': 'error_text',

mig/shared/safeinput.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1371,10 +1371,11 @@ def filter_date(contents):
13711371
return __filter_contents(contents, digits + '-/')
13721372

13731373

1374-
def filter_commonname(contents):
1374+
def filter_commonname(contents, illegal_handler=None):
13751375
"""Filter supplied contents to only contain valid commonname characters"""
13761376

1377-
return __filter_contents(contents, VALID_NAME_CHARACTERS, COMMON_ACCENTED)
1377+
return __filter_contents(contents, VALID_NAME_CHARACTERS, COMMON_ACCENTED,
1378+
illegal_handler=illegal_handler)
13781379

13791380

13801381
def filter_organization(contents):

0 commit comments

Comments
 (0)