~ubuntu-branches/ubuntu/trusty/python-keystoneclient/trusty-proposed

« back to all changes in this revision

Viewing changes to keystoneclient/client.py

  • Committer: Package Import Robot
  • Author(s): Chuck Short
  • Date: 2013-01-18 07:44:54 UTC
  • mfrom: (1.1.17)
  • Revision ID: package-import@ubuntu.com-20130118074454-g7w5blpynohn1s48
Tags: 1:0.2.2-0ubuntu1
New upstream release.

Show diffs side-by-side

added added

removed removed

Lines of Context:
10
10
 
11
11
import copy
12
12
import logging
 
13
import sys
13
14
import urlparse
14
15
 
15
 
import httplib2
 
16
import requests
16
17
 
17
18
try:
18
19
    import json
32
33
_logger = logging.getLogger(__name__)
33
34
 
34
35
 
35
 
class HTTPClient(httplib2.Http):
 
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):
36
50
 
37
51
    USER_AGENT = 'python-keystoneclient'
38
52
 
 
53
    requests_config = {
 
54
        'danger_mode': False,
 
55
    }
 
56
 
39
57
    def __init__(self, username=None, tenant_id=None, tenant_name=None,
40
58
                 password=None, auth_url=None, region_name=None, timeout=None,
41
59
                 endpoint=None, token=None, cacert=None, key=None,
42
60
                 cert=None, insecure=False, original_ip=None, debug=False,
43
 
                 auth_ref=None):
44
 
        super(HTTPClient, self).__init__(timeout=timeout, ca_certs=cacert)
45
 
        if cert:
46
 
            if key:
47
 
                self.add_certificate(key=key, cert=cert, domain='')
48
 
            else:
49
 
                self.add_certificate(key=cert, cert=cert, domain='')
 
61
                 auth_ref=None, use_keyring=False, force_new_token=False,
 
62
                 stale_duration=None):
50
63
        self.version = 'v2.0'
51
64
        # set baseline defaults
52
65
        self.username = None
83
96
        self.password = password
84
97
        self.original_ip = original_ip
85
98
        self.region_name = region_name
86
 
 
87
 
        # httplib2 overrides
88
 
        self.force_exception_to_status_code = True
89
 
        self.disable_ssl_certificate_validation = insecure
 
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 = ''
90
109
 
91
110
        # logging setup
92
111
        self.debug_log = debug
94
113
            ch = logging.StreamHandler()
95
114
            _logger.setLevel(logging.DEBUG)
96
115
            _logger.addHandler(ch)
97
 
 
98
 
    def authenticate(self):
99
 
        """ Authenticate against the Identity API.
 
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.
100
253
 
101
254
        Not implemented here because auth protocols should be API
102
255
        version-specific.
104
257
        Expected to authenticate or validate an existing authentication
105
258
        reference already associated with the client. Invoking this call
106
259
        *always* makes a call to the Keystone.
 
260
 
 
261
        :returns: ``raw token``
 
262
 
107
263
        """
108
264
        raise NotImplementedError
109
265
 
130
286
            header = ' -H "%s: %s"' % (element, kwargs['headers'][element])
131
287
            string_parts.append(header)
132
288
 
133
 
        _logger.debug("REQ: %s\n" % "".join(string_parts))
 
289
        _logger.debug("REQ: %s" % "".join(string_parts))
134
290
        if 'body' in kwargs:
135
291
            _logger.debug("REQ BODY: %s\n" % (kwargs['body']))
136
292
 
137
 
    def http_log_resp(self, resp, body):
 
293
    def http_log_resp(self, resp):
138
294
        if self.debug_log:
139
 
            _logger.debug("RESP: %s\nRESP BODY: %s\n", resp, body)
 
295
            _logger.debug(
 
296
                "RESP: [%s] %s\nRESP BODY: %s\n",
 
297
                resp.status_code,
 
298
                resp.headers,
 
299
                resp.text)
140
300
 
141
301
    def serialize(self, entity):
142
302
        return json.dumps(entity)
144
304
    def request(self, url, method, **kwargs):
145
305
        """ Send an http request with the specified characteristics.
146
306
 
147
 
        Wrapper around httplib2.Http.request to handle tasks such as
 
307
        Wrapper around requests.request to handle tasks such as
148
308
        setting headers, JSON encoding/decoding, and error handling.
149
309
        """
150
310
        # Copy the kwargs so we can reuse the original in case of redirects
156
316
                self.original_ip, self.USER_AGENT)
157
317
        if 'body' in kwargs:
158
318
            request_kwargs['headers']['Content-Type'] = 'application/json'
159
 
            request_kwargs['body'] = self.serialize(kwargs['body'])
 
319
            request_kwargs['data'] = self.serialize(kwargs['body'])
 
320
            del request_kwargs['body']
 
321
        if self.cert:
 
322
            request_kwargs['cert'] = self.cert
160
323
 
161
324
        self.http_log_req((url, method,), request_kwargs)
162
 
        resp, body = super(HTTPClient, self).request(url,
163
 
                                                     method,
164
 
                                                     **request_kwargs)
165
 
        self.http_log_resp(resp, body)
166
 
 
167
 
        if resp.status in (400, 401, 403, 404, 408, 409, 413, 500, 501):
168
 
            _logger.debug("Request returned failure status: %s", resp.status)
169
 
            raise exceptions.from_response(resp, body)
170
 
        elif resp.status in (301, 302, 305):
 
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):
171
340
            # Redirected. Reissue the request to the new location.
172
 
            return self.request(resp['location'], method, **kwargs)
 
341
            return self.request(resp.headers['location'], method, **kwargs)
173
342
 
174
 
        if body:
 
343
        if resp.text:
175
344
            try:
176
 
                body = json.loads(body)
 
345
                body = json.loads(resp.text)
177
346
            except ValueError:
178
 
                _logger.debug("Could not decode JSON from body: %s" % body)
 
347
                body = None
 
348
                _logger.debug("Could not decode JSON from body: %s"
 
349
                              % resp.text)
179
350
        else:
180
351
            _logger.debug("No body was returned.")
181
352
            body = None
187
358
        concatenating self.management_url and url and passing in method and
188
359
        any associated kwargs. """
189
360
 
190
 
        if self.management_url is None:
 
361
        is_management = kwargs.pop('management', True)
 
362
 
 
363
        if is_management and self.management_url is None:
191
364
            raise exceptions.AuthorizationFailure(
192
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
 
193
371
        kwargs.setdefault('headers', {})
194
372
        if self.auth_token:
195
373
            kwargs['headers']['X-Auth-Token'] = self.auth_token
196
374
 
197
 
        resp, body = self.request(self.management_url + url, method,
 
375
        resp, body = self.request(url_to_use + url, method,
198
376
                                  **kwargs)
199
377
        return resp, body
200
378