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

« back to all changes in this revision

Viewing changes to keystone/common/wsgi.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:
20
20
 
21
21
"""Utility methods for working with WSGI servers."""
22
22
 
23
 
import json
 
23
import socket
24
24
import sys
25
25
 
26
 
import eventlet
27
26
import eventlet.wsgi
28
 
eventlet.patcher.monkey_patch(all=False, socket=True, time=True)
29
 
import routes
30
27
import routes.middleware
31
 
import webob
 
28
import ssl
32
29
import webob.dec
33
30
import webob.exc
34
31
 
35
 
from keystone import exception
 
32
from keystone.common import config
36
33
from keystone.common import logging
37
34
from keystone.common import utils
38
 
 
39
 
 
 
35
from keystone import exception
 
36
from keystone.openstack.common import importutils
 
37
from keystone.openstack.common import jsonutils
 
38
 
 
39
 
 
40
CONF = config.CONF
40
41
LOG = logging.getLogger(__name__)
41
42
 
 
43
# Environment variable used to pass the request context
 
44
CONTEXT_ENV = 'openstack.context'
 
45
 
 
46
 
 
47
# Environment variable used to pass the request params
 
48
PARAMS_ENV = 'openstack.params'
 
49
 
42
50
 
43
51
class WritableLogger(object):
44
52
    """A thin wrapper that responds to `write` and logs."""
61
69
        self.pool = eventlet.GreenPool(threads)
62
70
        self.socket_info = {}
63
71
        self.greenthread = None
 
72
        self.do_ssl = False
 
73
        self.cert_required = False
64
74
 
65
75
    def start(self, key=None, backlog=128):
66
76
        """Run a WSGI server with the given application."""
67
 
        LOG.debug('Starting %(arg0)s on %(host)s:%(port)s' %
68
 
                      {'arg0': sys.argv[0],
69
 
                       'host': self.host,
70
 
                       'port': self.port})
71
 
        socket = eventlet.listen((self.host, self.port), backlog=backlog)
72
 
        self.greenthread = self.pool.spawn(self._run, self.application, socket)
 
77
        LOG.debug(_('Starting %(arg0)s on %(host)s:%(port)s') %
 
78
                  {'arg0': sys.argv[0],
 
79
                   'host': self.host,
 
80
                   'port': self.port})
 
81
 
 
82
        # TODO(dims): eventlet's green dns/socket module does not actually
 
83
        # support IPv6 in getaddrinfo(). We need to get around this in the
 
84
        # future or monitor upstream for a fix
 
85
        info = socket.getaddrinfo(self.host,
 
86
                                  self.port,
 
87
                                  socket.AF_UNSPEC,
 
88
                                  socket.SOCK_STREAM)[0]
 
89
        _socket = eventlet.listen(info[-1],
 
90
                                  family=info[0],
 
91
                                  backlog=backlog)
73
92
        if key:
74
 
            self.socket_info[key] = socket.getsockname()
 
93
            self.socket_info[key] = _socket.getsockname()
 
94
        # SSL is enabled
 
95
        if self.do_ssl:
 
96
            if self.cert_required:
 
97
                cert_reqs = ssl.CERT_REQUIRED
 
98
            else:
 
99
                cert_reqs = ssl.CERT_NONE
 
100
            sslsocket = eventlet.wrap_ssl(_socket, certfile=self.certfile,
 
101
                                          keyfile=self.keyfile,
 
102
                                          server_side=True,
 
103
                                          cert_reqs=cert_reqs,
 
104
                                          ca_certs=self.ca_certs)
 
105
            _socket = sslsocket
 
106
 
 
107
        self.greenthread = self.pool.spawn(self._run,
 
108
                                           self.application,
 
109
                                           _socket)
 
110
 
 
111
    def set_ssl(self, certfile, keyfile=None, ca_certs=None,
 
112
                cert_required=True):
 
113
        self.certfile = certfile
 
114
        self.keyfile = keyfile
 
115
        self.ca_certs = ca_certs
 
116
        self.cert_required = cert_required
 
117
        self.do_ssl = True
75
118
 
76
119
    def kill(self):
77
120
        if self.greenthread:
87
130
    def _run(self, application, socket):
88
131
        """Start a WSGI server in a new green thread."""
89
132
        log = logging.getLogger('eventlet.wsgi.server')
90
 
        eventlet.wsgi.server(socket, application, custom_pool=self.pool,
91
 
                             log=WritableLogger(log))
 
133
        try:
 
134
            eventlet.wsgi.server(socket, application, custom_pool=self.pool,
 
135
                                 log=WritableLogger(log))
 
136
        except Exception:
 
137
            LOG.exception(_('Server error'))
 
138
            raise
92
139
 
93
140
 
94
141
class Request(webob.Request):
166
213
        arg_dict = req.environ['wsgiorg.routing_args'][1]
167
214
        action = arg_dict.pop('action')
168
215
        del arg_dict['controller']
169
 
        LOG.debug('arg_dict: %s', arg_dict)
 
216
        LOG.debug(_('arg_dict: %s'), arg_dict)
170
217
 
171
218
        # allow middleware up the stack to provide context & params
172
 
        context = req.environ.get('openstack.context', {})
 
219
        context = req.environ.get(CONTEXT_ENV, {})
173
220
        context['query_string'] = dict(req.params.iteritems())
174
 
        params = req.environ.get('openstack.params', {})
 
221
        context['path'] = req.environ['PATH_INFO']
 
222
        params = req.environ.get(PARAMS_ENV, {})
 
223
        if 'REMOTE_USER' in req.environ:
 
224
            context['REMOTE_USER'] = req.environ['REMOTE_USER']
 
225
        elif context.get('REMOTE_USER', None) is not None:
 
226
            del context['REMOTE_USER']
175
227
        params.update(arg_dict)
176
228
 
177
229
        # TODO(termie): do some basic normalization on methods
182
234
 
183
235
        try:
184
236
            result = method(context, **params)
 
237
        except exception.Unauthorized as e:
 
238
            LOG.warning(_("Authorization failed. %s from %s")
 
239
                        % (e, req.environ['REMOTE_ADDR']))
 
240
            return render_exception(e)
185
241
        except exception.Error as e:
186
242
            LOG.warning(e)
187
243
            return render_exception(e)
 
244
        except TypeError as e:
 
245
            logging.exception(e)
 
246
            return render_exception(exception.ValidationError(e))
188
247
        except Exception as e:
189
248
            logging.exception(e)
190
249
            return render_exception(exception.UnexpectedError(exception=e))
197
256
            return result
198
257
        elif isinstance(result, webob.exc.WSGIHTTPException):
199
258
            return result
200
 
        return render_response(body=result)
 
259
 
 
260
        response_code = self._get_response_code(req)
 
261
        return render_response(body=result, status=response_code)
 
262
 
 
263
    def _get_response_code(self, req):
 
264
        req_method = req.environ['REQUEST_METHOD']
 
265
        controller = importutils.import_class('keystone.common.controller')
 
266
        code = None
 
267
        if isinstance(self, controller.V3Controller) and req_method == 'POST':
 
268
            code = (201, 'Created')
 
269
        return code
201
270
 
202
271
    def _normalize_arg(self, arg):
203
272
        return str(arg).replace(':', '_').replace('-', '_')
210
279
        if not context['is_admin']:
211
280
            try:
212
281
                user_token_ref = self.token_api.get_token(
213
 
                        context=context, token_id=context['token_id'])
214
 
            except exception.TokenNotFound:
215
 
                raise exception.Unauthorized()
 
282
                    context=context, token_id=context['token_id'])
 
283
            except exception.TokenNotFound as e:
 
284
                raise exception.Unauthorized(e)
216
285
 
217
286
            creds = user_token_ref['metadata'].copy()
218
287
 
335
404
        """Iterator that prints the contents of a wrapper string."""
336
405
        LOG.debug('%s %s %s', ('*' * 20), 'RESPONSE BODY', ('*' * 20))
337
406
        for part in app_iter:
338
 
            #sys.stdout.write(part)
339
407
            LOG.debug(part)
340
 
            #sys.stdout.flush()
341
408
            yield part
342
 
        print
343
409
 
344
410
 
345
411
class Router(object):
369
435
          mapper.connect(None, '/v1.0/{path_info:.*}', controller=BlogApp())
370
436
 
371
437
        """
 
438
        # if we're only running in debug, bump routes' internal logging up a
 
439
        # notch, as it's very spammy
 
440
        if CONF.debug:
 
441
            logging.getLogger('routes.middleware').setLevel(logging.INFO)
 
442
 
372
443
        self.map = mapper
373
444
        self._router = routes.middleware.RoutesMiddleware(self._dispatch,
374
445
                                                          self.map)
394
465
        """
395
466
        match = req.environ['wsgiorg.routing_args'][1]
396
467
        if not match:
397
 
            return webob.exc.HTTPNotFound()
 
468
            return render_exception(
 
469
                exception.NotFound(_('The resource could not be found.')))
398
470
        app = match['controller']
399
471
        return app
400
472
 
470
542
        return _factory
471
543
 
472
544
 
473
 
def render_response(body=None, status=(200, 'OK'), headers=None):
 
545
def render_response(body=None, status=None, headers=None):
474
546
    """Forms a WSGI response."""
475
 
    resp = webob.Response()
476
 
    resp.status = '%s %s' % status
477
 
    resp.headerlist = headers or [('Content-Type', 'application/json'),
478
 
                                  ('Vary', 'X-Auth-Token')]
479
 
 
480
 
    if body is not None:
481
 
        resp.body = json.dumps(body, cls=utils.SmarterEncoder)
482
 
 
483
 
    return resp
 
547
    headers = headers or []
 
548
    headers.append(('Vary', 'X-Auth-Token'))
 
549
 
 
550
    if body is None:
 
551
        body = ''
 
552
        status = status or (204, 'No Content')
 
553
    else:
 
554
        body = jsonutils.dumps(body, cls=utils.SmarterEncoder)
 
555
        headers.append(('Content-Type', 'application/json'))
 
556
        status = status or (200, 'OK')
 
557
 
 
558
    return webob.Response(body=body,
 
559
                          status='%s %s' % status,
 
560
                          headerlist=headers)
484
561
 
485
562
 
486
563
def render_exception(error):
487
564
    """Forms a WSGI response based on the current error."""
488
 
    return render_response(status=(error.code, error.title), body={
489
 
        'error': {
490
 
            'code': error.code,
491
 
            'title': error.title,
492
 
            'message': str(error),
493
 
        }
494
 
    })
 
565
    body = {'error': {
 
566
        'code': error.code,
 
567
        'title': error.title,
 
568
        'message': str(error)
 
569
    }}
 
570
    if isinstance(error, exception.AuthPluginException):
 
571
        body['error']['identity'] = error.authentication
 
572
    return render_response(status=(error.code, error.title), body=body)