aboutsummaryrefslogtreecommitdiffstats
path: root/keystone-moon/keystone/common/utils.py
diff options
context:
space:
mode:
Diffstat (limited to 'keystone-moon/keystone/common/utils.py')
-rw-r--r--keystone-moon/keystone/common/utils.py471
1 files changed, 471 insertions, 0 deletions
diff --git a/keystone-moon/keystone/common/utils.py b/keystone-moon/keystone/common/utils.py
new file mode 100644
index 00000000..a4b03ffd
--- /dev/null
+++ b/keystone-moon/keystone/common/utils.py
@@ -0,0 +1,471 @@
+# Copyright 2012 OpenStack Foundation
+# Copyright 2010 United States Government as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+# Copyright 2011 - 2012 Justin Santa Barbara
+# All Rights Reserved.
+#
+# 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
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import calendar
+import collections
+import grp
+import hashlib
+import os
+import pwd
+
+from oslo_config import cfg
+from oslo_log import log
+from oslo_serialization import jsonutils
+from oslo_utils import strutils
+import passlib.hash
+import six
+from six import moves
+
+from keystone import exception
+from keystone.i18n import _, _LE, _LW
+
+
+CONF = cfg.CONF
+
+LOG = log.getLogger(__name__)
+
+
+def flatten_dict(d, parent_key=''):
+ """Flatten a nested dictionary
+
+ Converts a dictionary with nested values to a single level flat
+ dictionary, with dotted notation for each key.
+
+ """
+ items = []
+ for k, v in d.items():
+ new_key = parent_key + '.' + k if parent_key else k
+ if isinstance(v, collections.MutableMapping):
+ items.extend(flatten_dict(v, new_key).items())
+ else:
+ items.append((new_key, v))
+ return dict(items)
+
+
+def read_cached_file(filename, cache_info, reload_func=None):
+ """Read from a file if it has been modified.
+
+ :param cache_info: dictionary to hold opaque cache.
+ :param reload_func: optional function to be called with data when
+ file is reloaded due to a modification.
+
+ :returns: data from file.
+
+ """
+ mtime = os.path.getmtime(filename)
+ if not cache_info or mtime != cache_info.get('mtime'):
+ with open(filename) as fap:
+ cache_info['data'] = fap.read()
+ cache_info['mtime'] = mtime
+ if reload_func:
+ reload_func(cache_info['data'])
+ return cache_info['data']
+
+
+class SmarterEncoder(jsonutils.json.JSONEncoder):
+ """Help for JSON encoding dict-like objects."""
+ def default(self, obj):
+ if not isinstance(obj, dict) and hasattr(obj, 'iteritems'):
+ return dict(obj.iteritems())
+ return super(SmarterEncoder, self).default(obj)
+
+
+class PKIEncoder(SmarterEncoder):
+ """Special encoder to make token JSON a bit shorter."""
+ item_separator = ','
+ key_separator = ':'
+
+
+def verify_length_and_trunc_password(password):
+ """Verify and truncate the provided password to the max_password_length."""
+ max_length = CONF.identity.max_password_length
+ try:
+ if len(password) > max_length:
+ if CONF.strict_password_check:
+ raise exception.PasswordVerificationError(size=max_length)
+ else:
+ LOG.warning(
+ _LW('Truncating user password to '
+ '%d characters.'), max_length)
+ return password[:max_length]
+ else:
+ return password
+ except TypeError:
+ raise exception.ValidationError(attribute='string', target='password')
+
+
+def hash_access_key(access):
+ hash_ = hashlib.sha256()
+ hash_.update(access)
+ return hash_.hexdigest()
+
+
+def hash_user_password(user):
+ """Hash a user dict's password without modifying the passed-in dict."""
+ password = user.get('password')
+ if password is None:
+ return user
+
+ return dict(user, password=hash_password(password))
+
+
+def hash_password(password):
+ """Hash a password. Hard."""
+ password_utf8 = verify_length_and_trunc_password(password).encode('utf-8')
+ return passlib.hash.sha512_crypt.encrypt(
+ password_utf8, rounds=CONF.crypt_strength)
+
+
+def check_password(password, hashed):
+ """Check that a plaintext password matches hashed.
+
+ hashpw returns the salt value concatenated with the actual hash value.
+ It extracts the actual salt if this value is then passed as the salt.
+
+ """
+ if password is None or hashed is None:
+ return False
+ password_utf8 = verify_length_and_trunc_password(password).encode('utf-8')
+ return passlib.hash.sha512_crypt.verify(password_utf8, hashed)
+
+
+def attr_as_boolean(val_attr):
+ """Returns the boolean value, decoded from a string.
+
+ We test explicitly for a value meaning False, which can be one of
+ several formats as specified in oslo strutils.FALSE_STRINGS.
+ All other string values (including an empty string) are treated as
+ meaning True.
+
+ """
+ return strutils.bool_from_string(val_attr, default=True)
+
+
+def get_blob_from_credential(credential):
+ try:
+ blob = jsonutils.loads(credential.blob)
+ except (ValueError, TypeError):
+ raise exception.ValidationError(
+ message=_('Invalid blob in credential'))
+ if not blob or not isinstance(blob, dict):
+ raise exception.ValidationError(attribute='blob',
+ target='credential')
+ return blob
+
+
+def convert_ec2_to_v3_credential(ec2credential):
+ blob = {'access': ec2credential.access,
+ 'secret': ec2credential.secret}
+ return {'id': hash_access_key(ec2credential.access),
+ 'user_id': ec2credential.user_id,
+ 'project_id': ec2credential.tenant_id,
+ 'blob': jsonutils.dumps(blob),
+ 'type': 'ec2',
+ 'extra': jsonutils.dumps({})}
+
+
+def convert_v3_to_ec2_credential(credential):
+ blob = get_blob_from_credential(credential)
+ return {'access': blob.get('access'),
+ 'secret': blob.get('secret'),
+ 'user_id': credential.user_id,
+ 'tenant_id': credential.project_id,
+ }
+
+
+def unixtime(dt_obj):
+ """Format datetime object as unix timestamp
+
+ :param dt_obj: datetime.datetime object
+ :returns: float
+
+ """
+ return calendar.timegm(dt_obj.utctimetuple())
+
+
+def auth_str_equal(provided, known):
+ """Constant-time string comparison.
+
+ :params provided: the first string
+ :params known: the second string
+
+ :return: True if the strings are equal.
+
+ This function takes two strings and compares them. It is intended to be
+ used when doing a comparison for authentication purposes to help guard
+ against timing attacks. When using the function for this purpose, always
+ provide the user-provided password as the first argument. The time this
+ function will take is always a factor of the length of this string.
+ """
+ result = 0
+ p_len = len(provided)
+ k_len = len(known)
+ for i in moves.range(p_len):
+ a = ord(provided[i]) if i < p_len else 0
+ b = ord(known[i]) if i < k_len else 0
+ result |= a ^ b
+ return (p_len == k_len) & (result == 0)
+
+
+def setup_remote_pydev_debug():
+ if CONF.pydev_debug_host and CONF.pydev_debug_port:
+ try:
+ try:
+ from pydev import pydevd
+ except ImportError:
+ import pydevd
+
+ pydevd.settrace(CONF.pydev_debug_host,
+ port=CONF.pydev_debug_port,
+ stdoutToServer=True,
+ stderrToServer=True)
+ return True
+ except Exception:
+ LOG.exception(_LE(
+ 'Error setting up the debug environment. Verify that the '
+ 'option --debug-url has the format <host>:<port> and that a '
+ 'debugger processes is listening on that port.'))
+ raise
+
+
+def get_unix_user(user=None):
+ '''Get the uid and user name.
+
+ This is a convenience utility which accepts a variety of input
+ which might represent a unix user. If successful it returns the uid
+ and name. Valid input is:
+
+ string
+ A string is first considered to be a user name and a lookup is
+ attempted under that name. If no name is found then an attempt
+ is made to convert the string to an integer and perform a
+ lookup as a uid.
+
+ int
+ An integer is interpretted as a uid.
+
+ None
+ None is interpreted to mean use the current process's
+ effective user.
+
+ If the input is a valid type but no user is found a KeyError is
+ raised. If the input is not a valid type a TypeError is raised.
+
+ :param object user: string, int or None specifying the user to
+ lookup.
+
+ :return: tuple of (uid, name)
+ '''
+
+ if isinstance(user, six.string_types):
+ try:
+ user_info = pwd.getpwnam(user)
+ except KeyError:
+ try:
+ i = int(user)
+ except ValueError:
+ raise KeyError("user name '%s' not found" % user)
+ try:
+ user_info = pwd.getpwuid(i)
+ except KeyError:
+ raise KeyError("user id %d not found" % i)
+ elif isinstance(user, int):
+ try:
+ user_info = pwd.getpwuid(user)
+ except KeyError:
+ raise KeyError("user id %d not found" % user)
+ elif user is None:
+ user_info = pwd.getpwuid(os.geteuid())
+ else:
+ raise TypeError('user must be string, int or None; not %s (%r)' %
+ (user.__class__.__name__, user))
+
+ return user_info.pw_uid, user_info.pw_name
+
+
+def get_unix_group(group=None):
+ '''Get the gid and group name.
+
+ This is a convenience utility which accepts a variety of input
+ which might represent a unix group. If successful it returns the gid
+ and name. Valid input is:
+
+ string
+ A string is first considered to be a group name and a lookup is
+ attempted under that name. If no name is found then an attempt
+ is made to convert the string to an integer and perform a
+ lookup as a gid.
+
+ int
+ An integer is interpretted as a gid.
+
+ None
+ None is interpreted to mean use the current process's
+ effective group.
+
+ If the input is a valid type but no group is found a KeyError is
+ raised. If the input is not a valid type a TypeError is raised.
+
+
+ :param object group: string, int or None specifying the group to
+ lookup.
+
+ :return: tuple of (gid, name)
+ '''
+
+ if isinstance(group, six.string_types):
+ try:
+ group_info = grp.getgrnam(group)
+ except KeyError:
+ # Was an int passed as a string?
+ # Try converting to int and lookup by id instead.
+ try:
+ i = int(group)
+ except ValueError:
+ raise KeyError("group name '%s' not found" % group)
+ try:
+ group_info = grp.getgrgid(i)
+ except KeyError:
+ raise KeyError("group id %d not found" % i)
+ elif isinstance(group, int):
+ try:
+ group_info = grp.getgrgid(group)
+ except KeyError:
+ raise KeyError("group id %d not found" % group)
+ elif group is None:
+ group_info = grp.getgrgid(os.getegid())
+ else:
+ raise TypeError('group must be string, int or None; not %s (%r)' %
+ (group.__class__.__name__, group))
+
+ return group_info.gr_gid, group_info.gr_name
+
+
+def set_permissions(path, mode=None, user=None, group=None, log=None):
+ '''Set the ownership and permissions on the pathname.
+
+ Each of the mode, user and group are optional, if None then
+ that aspect is not modified.
+
+ Owner and group may be specified either with a symbolic name
+ or numeric id.
+
+ :param string path: Pathname of directory whose existence is assured.
+ :param object mode: ownership permissions flags (int) i.e. chmod,
+ if None do not set.
+ :param object user: set user, name (string) or uid (integer),
+ if None do not set.
+ :param object group: set group, name (string) or gid (integer)
+ if None do not set.
+ :param logger log: logging.logger object, used to emit log messages,
+ if None no logging is performed.
+ '''
+
+ if user is None:
+ user_uid, user_name = None, None
+ else:
+ user_uid, user_name = get_unix_user(user)
+
+ if group is None:
+ group_gid, group_name = None, None
+ else:
+ group_gid, group_name = get_unix_group(group)
+
+ if log:
+ if mode is None:
+ mode_string = str(mode)
+ else:
+ mode_string = oct(mode)
+ log.debug("set_permissions: "
+ "path='%s' mode=%s user=%s(%s) group=%s(%s)",
+ path, mode_string,
+ user_name, user_uid, group_name, group_gid)
+
+ # Change user and group if specified
+ if user_uid is not None or group_gid is not None:
+ if user_uid is None:
+ user_uid = -1
+ if group_gid is None:
+ group_gid = -1
+ try:
+ os.chown(path, user_uid, group_gid)
+ except OSError as exc:
+ raise EnvironmentError("chown('%s', %s, %s): %s" %
+ (path,
+ user_name, group_name,
+ exc.strerror))
+
+ # Change permission flags
+ if mode is not None:
+ try:
+ os.chmod(path, mode)
+ except OSError as exc:
+ raise EnvironmentError("chmod('%s', %#o): %s" %
+ (path, mode, exc.strerror))
+
+
+def make_dirs(path, mode=None, user=None, group=None, log=None):
+ '''Assure directory exists, set ownership and permissions.
+
+ Assure the directory exists and optionally set its ownership
+ and permissions.
+
+ Each of the mode, user and group are optional, if None then
+ that aspect is not modified.
+
+ Owner and group may be specified either with a symbolic name
+ or numeric id.
+
+ :param string path: Pathname of directory whose existence is assured.
+ :param object mode: ownership permissions flags (int) i.e. chmod,
+ if None do not set.
+ :param object user: set user, name (string) or uid (integer),
+ if None do not set.
+ :param object group: set group, name (string) or gid (integer)
+ if None do not set.
+ :param logger log: logging.logger object, used to emit log messages,
+ if None no logging is performed.
+ '''
+
+ if log:
+ if mode is None:
+ mode_string = str(mode)
+ else:
+ mode_string = oct(mode)
+ log.debug("make_dirs path='%s' mode=%s user=%s group=%s",
+ path, mode_string, user, group)
+
+ if not os.path.exists(path):
+ try:
+ os.makedirs(path)
+ except OSError as exc:
+ raise EnvironmentError("makedirs('%s'): %s" % (path, exc.strerror))
+
+ set_permissions(path, mode, user, group, log)
+
+
+class WhiteListedItemFilter(object):
+
+ def __init__(self, whitelist, data):
+ self._whitelist = set(whitelist or [])
+ self._data = data
+
+ def __getitem__(self, name):
+ if name not in self._whitelist:
+ raise KeyError
+ return self._data[name]