~ubuntu-branches/debian/sid/keystone/sid

« back to all changes in this revision

Viewing changes to keystone/common/utils.py

  • Committer: Package Import Robot
  • Author(s): Thomas Goirand
  • Date: 2013-05-10 10:22:18 UTC
  • mfrom: (1.2.1) (26.1.4 experimental)
  • Revision ID: package-import@ubuntu.com-20130510102218-7hph1420gz5jsyr7
Tags: 2013.1.1-2
* Uploading to unstable.
* New upstream release:
  - Fixes CVE-2013-2059: Keystone tokens not immediately invalidated when
  user is deleted [OSSA 2013-011] (Closes: #707598).
* Also installs httpd/keystone.py.

Show diffs side-by-side

added added

removed removed

Lines of Context:
18
18
#    License for the specific language governing permissions and limitations
19
19
#    under the License.
20
20
 
21
 
import base64
22
21
import hashlib
23
 
import hmac
24
22
import json
25
23
import os
26
24
import subprocess
27
 
import sys
28
25
import time
29
 
import urllib
30
26
 
31
27
import passlib.hash
32
28
 
33
 
from keystone import config
 
29
from keystone.common import config
34
30
from keystone.common import logging
 
31
from keystone import exception
35
32
 
36
33
 
37
34
CONF = config.CONF
39
36
 
40
37
LOG = logging.getLogger(__name__)
41
38
 
42
 
ISO_TIME_FORMAT = '%Y-%m-%dT%H:%M:%SZ'
43
39
MAX_PASSWORD_LENGTH = 4096
44
40
 
45
41
 
46
 
def import_class(import_str):
47
 
    """Returns a class from a string including module and class."""
48
 
    mod_str, _sep, class_str = import_str.rpartition('.')
49
 
    try:
50
 
        __import__(mod_str)
51
 
        return getattr(sys.modules[mod_str], class_str)
52
 
    except (ImportError, ValueError, AttributeError), exc:
53
 
        LOG.debug('Inner Exception: %s', exc)
54
 
        raise
55
 
 
56
 
 
57
 
def import_object(import_str, *args, **kw):
58
 
    """Returns an object including a module or module and class."""
59
 
    try:
60
 
        __import__(import_str)
61
 
        return sys.modules[import_str]
62
 
    except ImportError:
63
 
        cls = import_class(import_str)
64
 
        return cls(*args, **kw)
65
 
 
66
 
 
67
 
def find_config(config_path):
68
 
    """Find a configuration file using the given hint.
69
 
 
70
 
    :param config_path: Full or relative path to the config.
71
 
    :returns: Full path of the config, if it exists.
72
 
 
73
 
    """
74
 
    possible_locations = [
75
 
        config_path,
76
 
        os.path.join('etc', 'keystone', config_path),
77
 
        os.path.join('etc', config_path),
78
 
        os.path.join(config_path),
79
 
        '/etc/keystone/%s' % config_path,
80
 
    ]
81
 
 
82
 
    for path in possible_locations:
83
 
        if os.path.exists(path):
84
 
            return os.path.abspath(path)
85
 
 
86
 
    raise Exception('Config not found: %s', os.path.abspath(config_path))
87
 
 
88
 
 
89
42
def read_cached_file(filename, cache_info, reload_func=None):
90
43
    """Read from a file if it has been modified.
91
44
 
93
46
    :param reload_func: optional function to be called with data when
94
47
                        file is reloaded due to a modification.
95
48
 
96
 
    :returns: data from file
 
49
    :returns: data from file.
97
50
 
98
51
    """
99
52
    mtime = os.path.getmtime(filename)
114
67
        return super(SmarterEncoder, self).default(obj)
115
68
 
116
69
 
117
 
class Ec2Signer(object):
118
 
    """Hacked up code from boto/connection.py"""
119
 
 
120
 
    def __init__(self, secret_key):
121
 
        secret_key = secret_key.encode()
122
 
        self.hmac = hmac.new(secret_key, digestmod=hashlib.sha1)
123
 
        if hashlib.sha256:
124
 
            self.hmac_256 = hmac.new(secret_key, digestmod=hashlib.sha256)
125
 
 
126
 
    def generate(self, credentials):
127
 
        """Generate auth string according to what SignatureVersion is given."""
128
 
        if credentials['params']['SignatureVersion'] == '0':
129
 
            return self._calc_signature_0(credentials['params'])
130
 
        if credentials['params']['SignatureVersion'] == '1':
131
 
            return self._calc_signature_1(credentials['params'])
132
 
        if credentials['params']['SignatureVersion'] == '2':
133
 
            return self._calc_signature_2(credentials['params'],
134
 
                                          credentials['verb'],
135
 
                                          credentials['host'],
136
 
                                          credentials['path'])
137
 
        raise Exception('Unknown Signature Version: %s' %
138
 
                        credentials['params']['SignatureVersion'])
139
 
 
140
 
    @staticmethod
141
 
    def _get_utf8_value(value):
142
 
        """Get the UTF8-encoded version of a value."""
143
 
        if not isinstance(value, str) and not isinstance(value, unicode):
144
 
            value = str(value)
145
 
        if isinstance(value, unicode):
146
 
            return value.encode('utf-8')
147
 
        else:
148
 
            return value
149
 
 
150
 
    def _calc_signature_0(self, params):
151
 
        """Generate AWS signature version 0 string."""
152
 
        s = params['Action'] + params['Timestamp']
153
 
        self.hmac.update(s)
154
 
        return base64.b64encode(self.hmac.digest())
155
 
 
156
 
    def _calc_signature_1(self, params):
157
 
        """Generate AWS signature version 1 string."""
158
 
        keys = params.keys()
159
 
        keys.sort(cmp=lambda x, y: cmp(x.lower(), y.lower()))
160
 
        for key in keys:
161
 
            self.hmac.update(key)
162
 
            val = self._get_utf8_value(params[key])
163
 
            self.hmac.update(val)
164
 
        return base64.b64encode(self.hmac.digest())
165
 
 
166
 
    def _calc_signature_2(self, params, verb, server_string, path):
167
 
        """Generate AWS signature version 2 string."""
168
 
        LOG.debug('using _calc_signature_2')
169
 
        string_to_sign = '%s\n%s\n%s\n' % (verb, server_string, path)
170
 
        if self.hmac_256:
171
 
            current_hmac = self.hmac_256
172
 
            params['SignatureMethod'] = 'HmacSHA256'
173
 
        else:
174
 
            current_hmac = self.hmac
175
 
            params['SignatureMethod'] = 'HmacSHA1'
176
 
        keys = params.keys()
177
 
        keys.sort()
178
 
        pairs = []
179
 
        for key in keys:
180
 
            val = self._get_utf8_value(params[key])
181
 
            val = urllib.quote(val, safe='-_~')
182
 
            pairs.append(urllib.quote(key, safe='') + '=' + val)
183
 
        qs = '&'.join(pairs)
184
 
        LOG.debug('query string: %s', qs)
185
 
        string_to_sign += qs
186
 
        LOG.debug('string_to_sign: %s', string_to_sign)
187
 
        current_hmac.update(string_to_sign)
188
 
        b64 = base64.b64encode(current_hmac.digest())
189
 
        LOG.debug('len(b64)=%d', len(b64))
190
 
        LOG.debug('base64 encoded digest: %s', b64)
191
 
        return b64
192
 
 
193
 
 
194
70
def trunc_password(password):
195
71
    """Truncate passwords to the MAX_PASSWORD_LENGTH."""
196
 
    if len(password) > MAX_PASSWORD_LENGTH:
197
 
        return password[:MAX_PASSWORD_LENGTH]
198
 
    else:
199
 
        return password
 
72
    try:
 
73
        if len(password) > MAX_PASSWORD_LENGTH:
 
74
            return password[:MAX_PASSWORD_LENGTH]
 
75
        else:
 
76
            return password
 
77
    except TypeError:
 
78
        raise exception.ValidationError(attribute='string', target='password')
 
79
 
 
80
 
 
81
def hash_user_password(user):
 
82
    """Hash a user dict's password without modifying the passed-in dict"""
 
83
    try:
 
84
        password = user['password']
 
85
    except KeyError:
 
86
        return user
 
87
    else:
 
88
        return dict(user, password=hash_password(password))
 
89
 
 
90
 
 
91
def hash_ldap_user_password(user):
 
92
    """Hash a user dict's password without modifying the passed-in dict"""
 
93
    try:
 
94
        password = user['password']
 
95
    except KeyError:
 
96
        return user
 
97
    else:
 
98
        return dict(user, password=ldap_hash_password(password))
200
99
 
201
100
 
202
101
def hash_password(password):
220
119
    if password is None:
221
120
        return False
222
121
    password_utf8 = trunc_password(password).encode('utf-8')
223
 
    h = passlib.hash.ldap_salted_sha1.encrypt(password_utf8)
224
122
    return passlib.hash.ldap_salted_sha1.verify(password_utf8, hashed)
225
123
 
226
124
 
276
174
    return check_output(['git'] + list(args))
277
175
 
278
176
 
279
 
def isotime(dt_obj):
280
 
    """Format datetime object as ISO compliant string.
281
 
 
282
 
    :param dt_obj: datetime.datetime object
283
 
    :returns: string representation of datetime object
284
 
 
285
 
    """
286
 
    return dt_obj.strftime(ISO_TIME_FORMAT)
287
 
 
288
 
 
289
177
def unixtime(dt_obj):
290
178
    """Format datetime object as unix timestamp
291
179
 
318
206
        b = ord(known[i]) if i < k_len else 0
319
207
        result |= a ^ b
320
208
    return (p_len == k_len) & (result == 0)
 
209
 
 
210
 
 
211
def hash_signed_token(signed_text):
 
212
    hash_ = hashlib.md5()
 
213
    hash_.update(signed_text)
 
214
    return hash_.hexdigest()
 
215
 
 
216
 
 
217
def setup_remote_pydev_debug():
 
218
    if CONF.pydev_debug_host and CONF.pydev_debug_port:
 
219
        error_msg = ('Error setting up the debug environment.  Verify that the'
 
220
                     ' option --debug-url has the format <host>:<port> and '
 
221
                     'that a debugger processes is listening on that port.')
 
222
 
 
223
        try:
 
224
            try:
 
225
                from pydevd import pydevd
 
226
            except ImportError:
 
227
                import pydevd
 
228
 
 
229
            pydevd.settrace(CONF.pydev_debug_host,
 
230
                            port=CONF.pydev_debug_port,
 
231
                            stdoutToServer=True,
 
232
                            stderrToServer=True)
 
233
            return True
 
234
        except:
 
235
            LOG.exception(_(error_msg))
 
236
            raise
 
237
 
 
238
 
 
239
class LimitingReader(object):
 
240
    """Reader to limit the size of an incoming request."""
 
241
    def __init__(self, data, limit):
 
242
        """
 
243
        :param data: Underlying data object
 
244
        :param limit: maximum number of bytes the reader should allow
 
245
        """
 
246
        self.data = data
 
247
        self.limit = limit
 
248
        self.bytes_read = 0
 
249
 
 
250
    def __iter__(self):
 
251
        for chunk in self.data:
 
252
            self.bytes_read += len(chunk)
 
253
            if self.bytes_read > self.limit:
 
254
                raise exception.RequestTooLarge()
 
255
            else:
 
256
                yield chunk
 
257
 
 
258
    def read(self, i):
 
259
        result = self.data.read(i)
 
260
        self.bytes_read += len(result)
 
261
        if self.bytes_read > self.limit:
 
262
            raise exception.RequestTooLarge()
 
263
        return result
 
264
 
 
265
    def read(self):
 
266
        result = self.data.read()
 
267
        self.bytes_read += len(result)
 
268
        if self.bytes_read > self.limit:
 
269
            raise exception.RequestTooLarge()
 
270
        return result