adapters: improve log messages (#55544)

- add mellon: prefix to all messages
- log all failures at the warning or error level instead of debug
This commit is contained in:
Benjamin Dauvergne 2021-07-12 19:08:35 +02:00
parent abe0328a22
commit 472ce61844
2 changed files with 75 additions and 49 deletions

View File

@ -83,13 +83,13 @@ class DefaultAdapter(object):
def load_metadata_path(self, idp, i):
path = idp['METADATA_PATH']
if not os.path.exists(path):
logger.warning('metadata path %s does not exist', path)
logger.warning('mellon: metadata path %s does not exist', path)
return
last_update = idp.get('METADATA_PATH_LAST_UPDATE', 0)
try:
mtime = os.stat(path).st_mtime
except OSError as e:
logger.warning('metadata path %s : stat() call failed, %s', path, e)
logger.warning('mellon: metadata path %s : stat() call failed, %s', path, e)
return
if last_update == 0 or mtime >= last_update:
idp['METADATA_PATH_LAST_UPDATE'] = time.time()
@ -97,15 +97,15 @@ class DefaultAdapter(object):
with open(path) as fd:
metadata = fd.read()
except OSError as e:
logger.warning('metadata path %s : open()/read() call failed, %s', path, e)
logger.warning('mellon: metadata path %s : open()/read() call failed, %s', path, e)
return
entity_id = self.load_entity_id(metadata, i)
if not entity_id:
logger.error('invalid metadata file retrieved from %s', path)
logger.error('mellon: invalid metadata file retrieved from %s', path)
return
if 'ENTITY_ID' in idp and idp['ENTITY_ID'] != entity_id:
logger.error(
'metadata path %s : entityID changed %r != %r', path, entity_id, idp['ENTITY_ID']
'mellon: metadata path %s : entityID changed %r != %r', path, entity_id, idp['ENTITY_ID']
)
del idp['ENTITY_ID']
idp['METADATA'] = metadata
@ -178,7 +178,7 @@ class DefaultAdapter(object):
if 'ENTITY_ID' in idp and idp['ENTITY_ID'] != entity_id:
# entityID change is always en error
logger.error(
'metadata url %s : entityID changed %r != %r', url, entity_id, idp['ENTITY_ID']
'mellon: metadata url %s, entityID changed %r != %r', url, entity_id, idp['ENTITY_ID']
)
del idp['ENTITY_ID']
@ -190,12 +190,15 @@ class DefaultAdapter(object):
fd.write(response.text.encode('utf-8'))
except OSError as e:
logger.error(
'metadata url %s : could not write file cache %s, %s', url, file_cache_path, e
'mellon: metadata url %s : could not write file cache %s, %s',
url,
file_cache_path,
e,
)
idp['METADATA_PATH'] = file_cache_path
# prevent reloading of the file cache immediately
idp['METADATA_PATH_LAST_UPDATE'] = time.time() + 1
logger.debug('metadata url %s : update throught HTTP', url)
logger.info('mellon: metadata url %s updated', url)
finally:
# release thread object
idp.pop('METADATA_URL_UPDATE_THREAD', None)
@ -204,7 +207,9 @@ class DefaultAdapter(object):
stale_timeout = 24 * metadata_cache_time
if last_update and (now - idp['METADATA_URL_LAST_UPDATE']) > stale_timeout:
logger.error(
'metadata url %s: not updated since %.1f hours', url, stale_timeout / 3600.0
'mellon: metadata url %s: not updated since %.1f hours',
url,
stale_timeout / 3600.0,
)
# we have cache, update in background
@ -244,14 +249,14 @@ class DefaultAdapter(object):
try:
doc = ET.fromstring(metadata)
except (TypeError, ET.ParseError):
logger.error('METADATA of %d-th idp is invalid', i)
logger.error('mellon: METADATA of %d-th idp is invalid', i)
return None
if doc.tag != '{%s}EntityDescriptor' % lasso.SAML2_METADATA_HREF:
logger.error('METADATA of %d-th idp has no EntityDescriptor root tag', i)
logger.error('mellon: METADATA of %d-th idp has no EntityDescriptor root tag', i)
return None
if 'entityID' not in doc.attrib:
logger.error('METADATA of %d-th idp has no entityID attribute on its root tag', i)
logger.error('mellon: METADATA of %d-th idp has no entityID attribute on its root tag', i)
return None
return doc.attrib['entityID']
@ -277,11 +282,11 @@ class DefaultAdapter(object):
:30
]
except ValueError:
logger.error('invalid username template %r', username_template)
logger.error('mellon: invalid username template %r', username_template)
except (AttributeError, KeyError, IndexError) as e:
logger.error('invalid reference in username template %r: %s', username_template, e)
logger.error('mellon: invalid reference in username template %r: %s', username_template, e)
except Exception:
logger.exception('unknown error when formatting username')
logger.exception('mellon: unknown error when formatting username')
else:
return username
@ -291,7 +296,6 @@ class DefaultAdapter(object):
def finish_create_user(self, idp, saml_attributes, user):
username = self.format_username(idp, saml_attributes)
if not username:
logger.warning('could not build a username, login refused')
raise UserCreationError
user.username = username
user.save()
@ -306,7 +310,7 @@ class DefaultAdapter(object):
name_id = name_id[0]
else:
logger.warning(
'more than one value for attribute %r, cannot federate',
'mellon: more than one value for attribute %r, cannot federate',
transient_federation_attribute,
)
return None
@ -316,7 +320,9 @@ class DefaultAdapter(object):
self.request,
_('A transient NameID was received but TRANSIENT_FEDERATION_ATTRIBUTE is not set.'),
)
logger.warning('transient NameID was received but TRANSIENT_FEDERATION_ATTRIBUTE is not set')
logger.warning(
'mellon: transient NameID was received but TRANSIENT_FEDERATION_ATTRIBUTE is not set'
)
return None
else:
name_id = saml_attributes['name_id_content']
@ -327,7 +333,7 @@ class DefaultAdapter(object):
)
user = saml_identifier.user
user.saml_identifier = saml_identifier
logger.info('looked up user %s with name_id %s from issuer %s', user, name_id, issuer)
logger.info('mellon: looked up user %s with name_id %s from issuer %s', user, name_id, issuer)
return user
except models.UserSAMLIdentifier.DoesNotExist:
pass
@ -337,14 +343,16 @@ class DefaultAdapter(object):
created = False
if not user:
if not utils.get_setting(idp, 'PROVISION'):
logger.debug('provisionning disabled, login refused')
logger.warning('mellon: login refused for %s, PROVISION is disabled', saml_attributes)
return None
created = True
user = self.create_user(User)
nameid_user = self._link_user(idp, saml_attributes, issuer, name_id, user)
if user != nameid_user:
logger.info('looked up user %s with name_id %s from issuer %s', nameid_user, name_id, issuer)
logger.info(
'mellon: looked up user %s with name_id %s from issuer %s', nameid_user, name_id, issuer
)
if created:
user.delete()
return nameid_user
@ -355,7 +363,9 @@ class DefaultAdapter(object):
except UserCreationError:
user.delete()
return None
logger.info('created new user %s with name_id %s from issuer %s', nameid_user, name_id, issuer)
logger.info(
'mellon: created new user %s with name_id %s from issuer %s', nameid_user, name_id, issuer
)
return nameid_user
def lookup_by_attributes(self, idp, saml_attributes):
@ -367,7 +377,8 @@ class DefaultAdapter(object):
def _lookup_by_attributes(self, idp, saml_attributes, lookup_by_attributes):
if not isinstance(lookup_by_attributes, list):
logger.error(
'invalid LOOKUP_BY_ATTRIBUTES configuration %r: it must be a list', lookup_by_attributes
'mellon: invalid LOOKUP_BY_ATTRIBUTES configuration %r: it must be a list',
lookup_by_attributes,
)
return None
@ -375,30 +386,34 @@ class DefaultAdapter(object):
for line in lookup_by_attributes:
if not isinstance(line, dict):
logger.error(
'invalid LOOKUP_BY_ATTRIBUTES configuration %r: it must be a list of dicts', line
'mellon: invalid LOOKUP_BY_ATTRIBUTES configuration %r: it must be a list of dicts', line
)
continue
user_field = line.get('user_field')
if not hasattr(user_field, 'isalpha'):
logger.error('invalid LOOKUP_BY_ATTRIBUTES configuration %r: user_field is missing', line)
logger.error(
'mellon: invalid LOOKUP_BY_ATTRIBUTES configuration %r: user_field is missing', line
)
continue
try:
User._meta.get_field(user_field)
except FieldDoesNotExist:
logger.error(
'invalid LOOKUP_BY_ATTRIBUTES configuration %r, user field %s does not exist',
'mellon: invalid LOOKUP_BY_ATTRIBUTES configuration %r, user field %s does not exist',
line,
user_field,
)
continue
saml_attribute = line.get('saml_attribute')
if not hasattr(saml_attribute, 'isalpha'):
logger.error('invalid LOOKUP_BY_ATTRIBUTES configuration %r: saml_attribute is missing', line)
logger.error(
'mellon: invalid LOOKUP_BY_ATTRIBUTES configuration %r: saml_attribute is missing', line
)
continue
values = saml_attributes.get(saml_attribute)
if not values:
logger.warning(
'looking for user by saml attribute %r and user field %r, skipping because empty',
'mellon: looking for user by saml attribute %r and user field %r, skipping because empty',
saml_attribute,
user_field,
)
@ -412,15 +427,15 @@ class DefaultAdapter(object):
saml_identifiers__isnull=True, **{key: value}
)
if not users_found:
logger.debug(
'looking for users by attribute %r and user field %r with value %r: not found',
logger.warning(
'mellon: looking for users by attribute %r and user field %r with value %r: not found',
saml_attribute,
user_field,
value,
)
continue
logger.info(
'looking for user by attribute %r and user field %r with value %r: found %s',
'mellon: looking for user by attribute %r and user field %r with value %r: found %s',
saml_attribute,
user_field,
value,
@ -429,11 +444,13 @@ class DefaultAdapter(object):
users.update(users_found)
if len(users) == 1:
user = list(users)[0]
logger.info('looking for user by attributes %r: found user %s', lookup_by_attributes, user)
logger.info(
'mellon: looking for user by attributes %r: found user %s', lookup_by_attributes, user
)
return user
elif len(users) > 1:
logger.warning(
'looking for user by attributes %r: too many users found(%d), failing',
'mellon: looking for user by attributes %r: too many users found(%d), failing',
lookup_by_attributes,
len(users),
)
@ -463,9 +480,9 @@ class DefaultAdapter(object):
try:
value = force_text(tpl).format(realm=realm, attributes=saml_attributes, idp=idp)
except ValueError:
logger.warning('invalid attribute mapping template %r', tpl)
logger.warning('mellon: invalid attribute mapping template %r', tpl)
except (AttributeError, KeyError, IndexError, ValueError) as e:
logger.warning('invalid reference in attribute mapping template %r: %s', tpl, e)
logger.warning('mellon: invalid reference in attribute mapping template %r: %s', tpl, e)
else:
model_field = user._meta.get_field(field)
if hasattr(model_field, 'max_length'):
@ -475,7 +492,11 @@ class DefaultAdapter(object):
setattr(user, field, value)
attribute_set = True
logger.info(
'set field %s of user %s to value %r (old value %r)', field, user, value, old_value
'mellon: set field %s of user %s to value %r (old value %r)',
field,
user,
value,
old_value,
)
if attribute_set:
user.save()
@ -499,13 +520,13 @@ class DefaultAdapter(object):
user.is_staff = True
user.is_superuser = True
attribute_set = True
logger.info('flag is_staff and is_superuser added to user %s', user)
logger.info('mellon: flag is_staff and is_superuser added to user %s', user)
break
else:
if user.is_superuser or user.is_staff:
user.is_staff = False
user.is_superuser = False
logger.info('flag is_staff and is_superuser removed from user %s', user)
logger.info('mellon: flag is_staff and is_superuser removed from user %s', user)
attribute_set = True
if attribute_set:
user.save()
@ -528,11 +549,15 @@ class DefaultAdapter(object):
continue
groups.append(group)
for group in Group.objects.filter(pk__in=[g.pk for g in groups]).exclude(user=user):
logger.info('adding group %s (%s) to user %s (%s)', group, group.pk, user, user.pk)
logger.info('mellon: adding group %s (%s) to user %s (%s)', group, group.pk, user, user.pk)
User.groups.through.objects.get_or_create(group=group, user=user)
qs = User.groups.through.objects.exclude(group__pk__in=[g.pk for g in groups]).filter(user=user)
for rel in qs:
logger.info(
'removing group %s (%s) from user %s (%s)', rel.group, rel.group.pk, rel.user, rel.user.pk
'mellon: removing group %s (%s) from user %s (%s)',
rel.group,
rel.group.pk,
rel.user,
rel.user.pk,
)
qs.delete()

View File

@ -252,7 +252,7 @@ def test_lookup_user_by_attributes_bad_setting1(settings, idp, saml_attributes,
adapter = DefaultAdapter()
settings.MELLON_LOOKUP_BY_ATTRIBUTES = 'coin'
assert adapter.lookup_user(idp, saml_attributes) is None
assert caplog.records[-1].message.endswith('it must be a list')
assert 'it must be a list' in caplog.text
def test_lookup_user_by_attributes_bad_setting2(settings, idp, saml_attributes, caplog):
@ -261,7 +261,7 @@ def test_lookup_user_by_attributes_bad_setting2(settings, idp, saml_attributes,
adapter = DefaultAdapter()
settings.MELLON_LOOKUP_BY_ATTRIBUTES = ['coin']
assert adapter.lookup_user(idp, saml_attributes) is None
assert caplog.records[-1].message.endswith('it must be a list of dicts')
assert 'it must be a list of dicts' in caplog.text
def test_lookup_user_by_attributes_bad_setting3(settings, idp, saml_attributes, caplog):
@ -270,7 +270,7 @@ def test_lookup_user_by_attributes_bad_setting3(settings, idp, saml_attributes,
adapter = DefaultAdapter()
settings.MELLON_LOOKUP_BY_ATTRIBUTES = [{}]
assert adapter.lookup_user(idp, saml_attributes) is None
assert caplog.records[-1].message.endswith('user_field is missing')
assert 'user_field is missing' in caplog.text
def test_lookup_user_by_attributes_bad_setting4(settings, idp, saml_attributes, caplog):
@ -279,7 +279,7 @@ def test_lookup_user_by_attributes_bad_setting4(settings, idp, saml_attributes,
adapter = DefaultAdapter()
settings.MELLON_LOOKUP_BY_ATTRIBUTES = [{'user_field': 'username'}]
assert adapter.lookup_user(idp, saml_attributes) is None
assert caplog.records[-1].message.endswith('saml_attribute is missing')
assert 'saml_attribute is missing' in caplog.text
def test_lookup_user_by_attributes_not_found(settings, idp, saml_attributes, caplog):
@ -290,7 +290,7 @@ def test_lookup_user_by_attributes_not_found(settings, idp, saml_attributes, cap
settings.MELLON_LOOKUP_BY_ATTRIBUTES = [{'user_field': 'username', 'saml_attribute': 'saml_at1'}]
saml_attributes['saml_at1'] = ['john.doe']
assert adapter.lookup_user(idp, saml_attributes) is None
assert caplog.records[-2].message.endswith(': not found')
assert ': not found' in caplog.text
def test_lookup_user_by_attributes_too_many1(settings, idp, saml_attributes, john, jane, caplog):
@ -300,7 +300,7 @@ def test_lookup_user_by_attributes_too_many1(settings, idp, saml_attributes, joh
settings.MELLON_LOOKUP_BY_ATTRIBUTES = [{'user_field': 'email', 'saml_attribute': 'saml_at1'}]
saml_attributes['saml_at1'] = ['john.doe@example.com']
assert adapter.lookup_user(idp, saml_attributes) is None
assert 'too many users found(2)' in caplog.records[-1].message
assert 'too many users found(2)' in caplog.text
def test_lookup_user_by_attributes_too_manyi2(settings, idp, saml_attributes, john, jane, caplog):
@ -315,7 +315,7 @@ def test_lookup_user_by_attributes_too_manyi2(settings, idp, saml_attributes, jo
{'user_field': 'username', 'saml_attribute': 'saml_at2'},
]
assert adapter.lookup_user(idp, saml_attributes) is None
assert 'too many users found(2)' in caplog.records[-1].message
assert 'too many users found(2)' in caplog.text
def test_lookup_user_by_attributes_found(settings, idp, saml_attributes, john, jane, caplog):
@ -397,8 +397,9 @@ def test_load_metadata_url(settings, adapter, metadata, httpserver, freezer, cap
new_meta = adapter.load_metadata(idp, 0)
assert new_meta != metadata
assert new_meta == metadata.replace('idp5', 'idp6')
assert 'entityID changed' in caplog.records[-1].message
assert caplog.records[-1].levelname == 'ERROR'
assert any(
'entityID changed' in record.message and record.levelname == 'ERROR' for record in caplog.records
)
# test load from file cache
del idp['METADATA']
del idp['METADATA_PATH']