~corey.bryant/python-keystoneclient/1.6.0

« back to all changes in this revision

Viewing changes to .pc/fix-requests-requirements.patch/keystoneclient/client.py

  • Committer: Package Import Robot
  • Author(s): Chuck Short
  • Date: 2013-02-11 12:32:43 UTC
  • Revision ID: package-import@ubuntu.com-20130211123243-vf4o7xig8e4lbijf
Tags: 1:0.2.2-0ubuntu2
debian/patches/fix-requests-requirements.patch: Bump python-requests
to 1.1.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright 2010 Jacob Kaplan-Moss
 
2
# Copyright 2011 OpenStack LLC.
 
3
# Copyright 2011 Piston Cloud Computing, Inc.
 
4
# Copyright 2011 Nebula, Inc.
 
5
 
 
6
# All Rights Reserved.
 
7
"""
 
8
OpenStack Client interface. Handles the REST calls and responses.
 
9
"""
 
10
 
 
11
import copy
 
12
import logging
 
13
import sys
 
14
import urlparse
 
15
 
 
16
import requests
 
17
 
 
18
try:
 
19
    import json
 
20
except ImportError:
 
21
    import simplejson as json
 
22
 
 
23
# Python 2.5 compat fix
 
24
if not hasattr(urlparse, 'parse_qsl'):
 
25
    import cgi
 
26
    urlparse.parse_qsl = cgi.parse_qsl
 
27
 
 
28
 
 
29
from keystoneclient import access
 
30
from keystoneclient import exceptions
 
31
 
 
32
 
 
33
_logger = logging.getLogger(__name__)
 
34
 
 
35
 
 
36
# keyring init
 
37
keyring_available = True
 
38
try:
 
39
    import keyring
 
40
    import pickle
 
41
except ImportError:
 
42
    if (hasattr(sys.stderr, 'isatty') and sys.stderr.isatty()):
 
43
        print >> sys.stderr, 'Failed to load keyring modules.'
 
44
    else:
 
45
        _logger.warning('Failed to load keyring modules.')
 
46
    keyring_available = False
 
47
 
 
48
 
 
49
class HTTPClient(object):
 
50
 
 
51
    USER_AGENT = 'python-keystoneclient'
 
52
 
 
53
    requests_config = {
 
54
        'danger_mode': False,
 
55
    }
 
56
 
 
57
    def __init__(self, username=None, tenant_id=None, tenant_name=None,
 
58
                 password=None, auth_url=None, region_name=None, timeout=None,
 
59
                 endpoint=None, token=None, cacert=None, key=None,
 
60
                 cert=None, insecure=False, original_ip=None, debug=False,
 
61
                 auth_ref=None, use_keyring=False, force_new_token=False,
 
62
                 stale_duration=None):
 
63
        self.version = 'v2.0'
 
64
        # set baseline defaults
 
65
        self.username = None
 
66
        self.tenant_id = None
 
67
        self.tenant_name = None
 
68
        self.auth_url = None
 
69
        self.token = None
 
70
        self.auth_token = None
 
71
        self.management_url = None
 
72
        # if loading from a dictionary passed in via auth_ref,
 
73
        # load values from AccessInfo parsing that dictionary
 
74
        self.auth_ref = access.AccessInfo(**auth_ref) if auth_ref else None
 
75
        if self.auth_ref:
 
76
            self.username = self.auth_ref.username
 
77
            self.tenant_id = self.auth_ref.tenant_id
 
78
            self.tenant_name = self.auth_ref.tenant_name
 
79
            self.auth_url = self.auth_ref.auth_url[0]
 
80
            self.management_url = self.auth_ref.management_url[0]
 
81
            self.auth_token = self.auth_ref.auth_token
 
82
        # allow override of the auth_ref defaults from explicit
 
83
        # values provided to the client
 
84
        if username:
 
85
            self.username = username
 
86
        if tenant_id:
 
87
            self.tenant_id = tenant_id
 
88
        if tenant_name:
 
89
            self.tenant_name = tenant_name
 
90
        if auth_url:
 
91
            self.auth_url = auth_url.rstrip('/')
 
92
        if token:
 
93
            self.auth_token = token
 
94
        if endpoint:
 
95
            self.management_url = endpoint.rstrip('/')
 
96
        self.password = password
 
97
        self.original_ip = original_ip
 
98
        self.region_name = region_name
 
99
        if cacert:
 
100
            self.verify_cert = cacert
 
101
        else:
 
102
            self.verify_cert = True
 
103
        if insecure:
 
104
            self.verify_cert = False
 
105
        self.cert = cert
 
106
        if cert and key:
 
107
            self.cert = (cert, key,)
 
108
        self.domain = ''
 
109
 
 
110
        # logging setup
 
111
        self.debug_log = debug
 
112
        if self.debug_log:
 
113
            ch = logging.StreamHandler()
 
114
            _logger.setLevel(logging.DEBUG)
 
115
            _logger.addHandler(ch)
 
116
            self.requests_config['verbose'] = sys.stderr
 
117
 
 
118
        # keyring setup
 
119
        self.use_keyring = use_keyring and keyring_available
 
120
        self.force_new_token = force_new_token
 
121
        self.stale_duration = stale_duration or access.STALE_TOKEN_DURATION
 
122
        self.stale_duration = int(self.stale_duration)
 
123
 
 
124
    def authenticate(self, username=None, password=None, tenant_name=None,
 
125
                     tenant_id=None, auth_url=None, token=None):
 
126
        """ Authenticate user.
 
127
 
 
128
        Uses the data provided at instantiation to authenticate against
 
129
        the Keystone server. This may use either a username and password
 
130
        or token for authentication. If a tenant name or id was provided
 
131
        then the resulting authenticated client will be scoped to that
 
132
        tenant and contain a service catalog of available endpoints.
 
133
 
 
134
        With the v2.0 API, if a tenant name or ID is not provided, the
 
135
        authenication token returned will be 'unscoped' and limited in
 
136
        capabilities until a fully-scoped token is acquired.
 
137
 
 
138
        If successful, sets the self.auth_ref and self.auth_token with
 
139
        the returned token. If not already set, will also set
 
140
        self.management_url from the details provided in the token.
 
141
 
 
142
        :returns: ``True`` if authentication was successful.
 
143
        :raises: AuthorizationFailure if unable to authenticate or validate
 
144
                 the existing authorization token
 
145
        :raises: ValueError if insufficient parameters are used.
 
146
 
 
147
        If keyring is used, token is retrieved from keyring instead.
 
148
        Authentication will only be necessary if any of the following
 
149
        conditions are met:
 
150
 
 
151
        * keyring is not used
 
152
        * if token is not found in keyring
 
153
        * if token retrieved from keyring is expired or about to
 
154
          expired (as determined by stale_duration)
 
155
        * if force_new_token is true
 
156
 
 
157
        """
 
158
        auth_url = auth_url or self.auth_url
 
159
        username = username or self.username
 
160
        password = password or self.password
 
161
        tenant_name = tenant_name or self.tenant_name
 
162
        tenant_id = tenant_id or self.tenant_id
 
163
        token = token or self.auth_token
 
164
 
 
165
        (keyring_key, auth_ref) = self.get_auth_ref_from_keyring(auth_url,
 
166
                                                                 username,
 
167
                                                                 tenant_name,
 
168
                                                                 tenant_id,
 
169
                                                                 token)
 
170
        new_token_needed = False
 
171
        if auth_ref is None or self.force_new_token:
 
172
            new_token_needed = True
 
173
            raw_token = self.get_raw_token_from_identity_service(auth_url,
 
174
                                                                 username,
 
175
                                                                 password,
 
176
                                                                 tenant_name,
 
177
                                                                 tenant_id,
 
178
                                                                 token)
 
179
            self.auth_ref = access.AccessInfo(**raw_token)
 
180
        else:
 
181
            self.auth_ref = auth_ref
 
182
        self.process_token()
 
183
        if new_token_needed:
 
184
            self.store_auth_ref_into_keyring(keyring_key)
 
185
        return True
 
186
 
 
187
    def _build_keyring_key(self, auth_url, username, tenant_name,
 
188
                           tenant_id, token):
 
189
        """ Create a unique key for keyring.
 
190
 
 
191
        Used to store and retrieve auth_ref from keyring.
 
192
 
 
193
        """
 
194
        keys = [auth_url, username, tenant_name, tenant_id, token]
 
195
        for index, key in enumerate(keys):
 
196
            if key is None:
 
197
                keys[index] = '?'
 
198
        keyring_key = '/'.join(keys)
 
199
        return keyring_key
 
200
 
 
201
    def get_auth_ref_from_keyring(self, auth_url, username, tenant_name,
 
202
                                  tenant_id, token):
 
203
        """ Retrieve auth_ref from keyring.
 
204
 
 
205
        If auth_ref is found in keyring, (keyring_key, auth_ref) is returned.
 
206
        Otherwise, (keyring_key, None) is returned.
 
207
 
 
208
        :returns: (keyring_key, auth_ref) or (keyring_key, None)
 
209
 
 
210
        """
 
211
        keyring_key = None
 
212
        auth_ref = None
 
213
        if self.use_keyring:
 
214
            keyring_key = self._build_keyring_key(auth_url, username,
 
215
                                                  tenant_name, tenant_id,
 
216
                                                  token)
 
217
            try:
 
218
                auth_ref = keyring.get_password("keystoneclient_auth",
 
219
                                                keyring_key)
 
220
                if auth_ref:
 
221
                    auth_ref = pickle.loads(auth_ref)
 
222
                    if auth_ref.will_expire_soon(self.stale_duration):
 
223
                        # token has expired, don't use it
 
224
                        auth_ref = None
 
225
            except Exception as e:
 
226
                auth_ref = None
 
227
                _logger.warning('Unable to retrieve token from keyring %s' % (
 
228
                    e))
 
229
        return (keyring_key, auth_ref)
 
230
 
 
231
    def store_auth_ref_into_keyring(self, keyring_key):
 
232
        """ Store auth_ref into keyring.
 
233
 
 
234
        """
 
235
        if self.use_keyring:
 
236
            try:
 
237
                keyring.set_password("keystoneclient_auth",
 
238
                                     keyring_key,
 
239
                                     pickle.dumps(self.auth_ref))
 
240
            except Exception as e:
 
241
                _logger.warning("Failed to store token into keyring %s" % (e))
 
242
 
 
243
    def process_token(self):
 
244
        """ Extract and process information from the new auth_ref.
 
245
 
 
246
        """
 
247
        raise NotImplementedError
 
248
 
 
249
    def get_raw_token_from_identity_service(self, auth_url, username=None,
 
250
                                            password=None, tenant_name=None,
 
251
                                            tenant_id=None, token=None):
 
252
        """ Authenticate against the Identity API and get a token.
 
253
 
 
254
        Not implemented here because auth protocols should be API
 
255
        version-specific.
 
256
 
 
257
        Expected to authenticate or validate an existing authentication
 
258
        reference already associated with the client. Invoking this call
 
259
        *always* makes a call to the Keystone.
 
260
 
 
261
        :returns: ``raw token``
 
262
 
 
263
        """
 
264
        raise NotImplementedError
 
265
 
 
266
    def _extract_service_catalog(self, url, body):
 
267
        """ Set the client's service catalog from the response data.
 
268
 
 
269
        Not implemented here because data returned may be API
 
270
        version-specific.
 
271
        """
 
272
        raise NotImplementedError
 
273
 
 
274
    def http_log_req(self, args, kwargs):
 
275
        if not self.debug_log:
 
276
            return
 
277
 
 
278
        string_parts = ['curl -i']
 
279
        for element in args:
 
280
            if element in ('GET', 'POST'):
 
281
                string_parts.append(' -X %s' % element)
 
282
            else:
 
283
                string_parts.append(' %s' % element)
 
284
 
 
285
        for element in kwargs['headers']:
 
286
            header = ' -H "%s: %s"' % (element, kwargs['headers'][element])
 
287
            string_parts.append(header)
 
288
 
 
289
        _logger.debug("REQ: %s" % "".join(string_parts))
 
290
        if 'body' in kwargs:
 
291
            _logger.debug("REQ BODY: %s\n" % (kwargs['body']))
 
292
 
 
293
    def http_log_resp(self, resp):
 
294
        if self.debug_log:
 
295
            _logger.debug(
 
296
                "RESP: [%s] %s\nRESP BODY: %s\n",
 
297
                resp.status_code,
 
298
                resp.headers,
 
299
                resp.text)
 
300
 
 
301
    def serialize(self, entity):
 
302
        return json.dumps(entity)
 
303
 
 
304
    def request(self, url, method, **kwargs):
 
305
        """ Send an http request with the specified characteristics.
 
306
 
 
307
        Wrapper around requests.request to handle tasks such as
 
308
        setting headers, JSON encoding/decoding, and error handling.
 
309
        """
 
310
        # Copy the kwargs so we can reuse the original in case of redirects
 
311
        request_kwargs = copy.copy(kwargs)
 
312
        request_kwargs.setdefault('headers', kwargs.get('headers', {}))
 
313
        request_kwargs['headers']['User-Agent'] = self.USER_AGENT
 
314
        if self.original_ip:
 
315
            request_kwargs['headers']['Forwarded'] = "for=%s;by=%s" % (
 
316
                self.original_ip, self.USER_AGENT)
 
317
        if 'body' in kwargs:
 
318
            request_kwargs['headers']['Content-Type'] = 'application/json'
 
319
            request_kwargs['data'] = self.serialize(kwargs['body'])
 
320
            del request_kwargs['body']
 
321
        if self.cert:
 
322
            request_kwargs['cert'] = self.cert
 
323
 
 
324
        self.http_log_req((url, method,), request_kwargs)
 
325
        resp = requests.request(
 
326
            method,
 
327
            url,
 
328
            verify=self.verify_cert,
 
329
            config=self.requests_config,
 
330
            **request_kwargs)
 
331
 
 
332
        self.http_log_resp(resp)
 
333
 
 
334
        if resp.status_code in (400, 401, 403, 404, 408, 409, 413, 500, 501):
 
335
            _logger.debug(
 
336
                "Request returned failure status: %s",
 
337
                resp.status_code)
 
338
            raise exceptions.from_response(resp, resp.text)
 
339
        elif resp.status_code in (301, 302, 305):
 
340
            # Redirected. Reissue the request to the new location.
 
341
            return self.request(resp.headers['location'], method, **kwargs)
 
342
 
 
343
        if resp.text:
 
344
            try:
 
345
                body = json.loads(resp.text)
 
346
            except ValueError:
 
347
                body = None
 
348
                _logger.debug("Could not decode JSON from body: %s"
 
349
                              % resp.text)
 
350
        else:
 
351
            _logger.debug("No body was returned.")
 
352
            body = None
 
353
 
 
354
        return resp, body
 
355
 
 
356
    def _cs_request(self, url, method, **kwargs):
 
357
        """ Makes an authenticated request to keystone endpoint by
 
358
        concatenating self.management_url and url and passing in method and
 
359
        any associated kwargs. """
 
360
 
 
361
        is_management = kwargs.pop('management', True)
 
362
 
 
363
        if is_management and self.management_url is None:
 
364
            raise exceptions.AuthorizationFailure(
 
365
                'Current authorization does not have a known management url')
 
366
 
 
367
        url_to_use = self.auth_url
 
368
        if is_management:
 
369
            url_to_use = self.management_url
 
370
 
 
371
        kwargs.setdefault('headers', {})
 
372
        if self.auth_token:
 
373
            kwargs['headers']['X-Auth-Token'] = self.auth_token
 
374
 
 
375
        resp, body = self.request(url_to_use + url, method,
 
376
                                  **kwargs)
 
377
        return resp, body
 
378
 
 
379
    def get(self, url, **kwargs):
 
380
        return self._cs_request(url, 'GET', **kwargs)
 
381
 
 
382
    def head(self, url, **kwargs):
 
383
        return self._cs_request(url, 'HEAD', **kwargs)
 
384
 
 
385
    def post(self, url, **kwargs):
 
386
        return self._cs_request(url, 'POST', **kwargs)
 
387
 
 
388
    def put(self, url, **kwargs):
 
389
        return self._cs_request(url, 'PUT', **kwargs)
 
390
 
 
391
    def patch(self, url, **kwargs):
 
392
        return self._cs_request(url, 'PATCH', **kwargs)
 
393
 
 
394
    def delete(self, url, **kwargs):
 
395
        return self._cs_request(url, 'DELETE', **kwargs)