diff --git a/keystone/common/utils.py b/keystone/common/utils.py index 1ec6353003..66ae6050b4 100644 --- a/keystone/common/utils.py +++ b/keystone/common/utils.py @@ -22,6 +22,7 @@ import grp import hashlib import os import pwd +import uuid from oslo_config import cfg from oslo_log import log @@ -42,6 +43,24 @@ CONF = cfg.CONF LOG = log.getLogger(__name__) +# NOTE(stevermar): This UUID must stay the same, forever, across +# all of keystone to preserve its value as a URN namespace, which is +# used for ID transformation. +RESOURCE_ID_NAMESPACE = uuid.UUID('4332ecab-770b-4288-a680-b9aca3b1b153') + + +def resource_uuid(value): + """Converts input to valid UUID hex digits.""" + try: + uuid.UUID(value) + return value + except ValueError: + if len(value) <= 64: + return uuid.uuid5(RESOURCE_ID_NAMESPACE, value).hex + raise ValueError(_('Length of transformable resource id > 64, ' + 'which is max allowed characters')) + + def flatten_dict(d, parent_key=''): """Flatten a nested dictionary diff --git a/keystone/notifications.py b/keystone/notifications.py index 8a9e0c708a..29ac3d2736 100644 --- a/keystone/notifications.py +++ b/keystone/notifications.py @@ -31,6 +31,7 @@ from pycadf import eventfactory from pycadf import resource from keystone.i18n import _, _LE +from keystone.common import utils notifier_opts = [ @@ -501,8 +502,12 @@ def _get_request_audit_info(context, user_id=None): {}).get('domain_id') host = pycadf.host.Host(address=remote_addr, agent=http_user_agent) - initiator = resource.Resource(typeURI=taxonomy.ACCOUNT_USER, - id=user_id, host=host) + initiator = resource.Resource(typeURI=taxonomy.ACCOUNT_USER, host=host) + + if user_id: + initiator.user_id = user_id + initiator.id = utils.resource_uuid(user_id) + if project_id: initiator.project_id = project_id if domain_id: diff --git a/keystone/tests/unit/common/test_utils.py b/keystone/tests/unit/common/test_utils.py index 62d6412b51..4d00a78ae8 100644 --- a/keystone/tests/unit/common/test_utils.py +++ b/keystone/tests/unit/common/test_utils.py @@ -1,3 +1,4 @@ +# encoding: utf-8 # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at @@ -36,6 +37,28 @@ class UtilsTestCase(unit.BaseTestCase): super(UtilsTestCase, self).setUp() self.config_fixture = self.useFixture(config_fixture.Config(CONF)) + def test_resource_uuid(self): + uuid_str = '536e28c2017e405e89b25a1ed777b952' + self.assertEqual(uuid_str, common_utils.resource_uuid(uuid_str)) + + # Exact 64 length string. + uuid_str = ('536e28c2017e405e89b25a1ed777b952' + 'f13de678ac714bb1b7d1e9a007c10db5') + resource_id_namespace = common_utils.RESOURCE_ID_NAMESPACE + transformed_id = uuid.uuid5(resource_id_namespace, uuid_str).hex + self.assertEqual(transformed_id, common_utils.resource_uuid(uuid_str)) + + # Non-ASCII character test. + non_ascii_ = 'ß' * 32 + transformed_id = uuid.uuid5(resource_id_namespace, non_ascii_).hex + self.assertEqual(transformed_id, + common_utils.resource_uuid(non_ascii_)) + + # This input is invalid because it's length is more than 64. + invalid_input = 'x' * 65 + self.assertRaises(ValueError, common_utils.resource_uuid, + invalid_input) + def test_hash(self): password = 'right' wrong = 'wrongwrong' # Two wrongs don't make a right