b9212898fc4b4da402a91344f07593644064bb09
[escalator.git] / api / escalator / api / middleware / context.py
1 # Copyright 2016 OPNFV Foundation
2 # All Rights Reserved.
3 #
4 #    Licensed under the Apache License, Version 2.0 (the "License"); you may
5 #    not use this file except in compliance with the License. You may obtain
6 #    a copy of the License at
7 #
8 #         http://www.apache.org/licenses/LICENSE-2.0
9 #
10 #    Unless required by applicable law or agreed to in writing, software
11 #    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 #    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 #    License for the specific language governing permissions and limitations
14 #    under the License.
15
16 from oslo_serialization import jsonutils
17 from oslo_config import cfg
18 from oslo_log import log as logging
19 import webob.exc
20
21 from escalator.api import policy
22 from escalator.common import wsgi
23 import escalator.context
24 from escalator import i18n
25
26 _ = i18n._
27
28 context_opts = [
29     cfg.BoolOpt('owner_is_tenant', default=True,
30                 help=_('When true, this option sets the owner of an image '
31                        'to be the tenant. Otherwise, the owner of the '
32                        ' image will be the authenticated user issuing the '
33                        'request.')),
34     cfg.StrOpt('admin_role', default='admin',
35                help=_('Role used to identify an authenticated user as '
36                       'administrator.')),
37     cfg.BoolOpt('allow_anonymous_access', default=False,
38                 help=_('Allow unauthenticated users to access the API with '
39                        'read-only privileges. This only applies when using '
40                        'ContextMiddleware.')),
41 ]
42
43 CONF = cfg.CONF
44 CONF.register_opts(context_opts)
45
46 LOG = logging.getLogger(__name__)
47
48
49 class BaseContextMiddleware(wsgi.Middleware):
50     def process_response(self, resp):
51         try:
52             request_id = resp.request.context.request_id
53         except AttributeError:
54             LOG.warn(_('Unable to retrieve request id from context'))
55         else:
56             resp.headers['x-openstack-request-id'] = 'req-%s' % request_id
57         return resp
58
59
60 class ContextMiddleware(BaseContextMiddleware):
61     def __init__(self, app):
62         self.policy_enforcer = policy.Enforcer()
63         super(ContextMiddleware, self).__init__(app)
64
65     def process_request(self, req):
66         """Convert authentication information into a request context
67
68         Generate a escalator.context.RequestContext object from the available
69         authentication headers and store on the 'context' attribute
70         of the req object.
71
72         :param req: wsgi request object that will be given the context object
73         :raises webob.exc.HTTPUnauthorized: when value of the X-Identity-Status
74                                             header is not 'Confirmed' and
75                                             anonymous access is disallowed
76         """
77         if req.headers.get('X-Identity-Status') == 'Confirmed':
78             req.context = self._get_authenticated_context(req)
79         elif CONF.allow_anonymous_access:
80             req.context = self._get_anonymous_context()
81         else:
82             raise webob.exc.HTTPUnauthorized()
83
84     def _get_anonymous_context(self):
85         kwargs = {
86             'user': None,
87             'tenant': None,
88             'roles': [],
89             'is_admin': False,
90             'read_only': True,
91             'policy_enforcer': self.policy_enforcer,
92         }
93         return escalator.context.RequestContext(**kwargs)
94
95     def _get_authenticated_context(self, req):
96         # NOTE(bcwaldon): X-Roles is a csv string, but we need to parse
97         # it into a list to be useful
98         roles_header = req.headers.get('X-Roles', '')
99         roles = [r.strip().lower() for r in roles_header.split(',')]
100
101         # NOTE(bcwaldon): This header is deprecated in favor of X-Auth-Token
102         deprecated_token = req.headers.get('X-Storage-Token')
103
104         service_catalog = None
105         if req.headers.get('X-Service-Catalog') is not None:
106             try:
107                 catalog_header = req.headers.get('X-Service-Catalog')
108                 service_catalog = jsonutils.loads(catalog_header)
109             except ValueError:
110                 raise webob.exc.HTTPInternalServerError(
111                     _('Invalid service catalog json.'))
112
113         kwargs = {
114             'user': req.headers.get('X-User-Id'),
115             'tenant': req.headers.get('X-Tenant-Id'),
116             'roles': roles,
117             'is_admin': CONF.admin_role.strip().lower() in roles,
118             'auth_token': req.headers.get('X-Auth-Token', deprecated_token),
119             'owner_is_tenant': CONF.owner_is_tenant,
120             'service_catalog': service_catalog,
121             'policy_enforcer': self.policy_enforcer,
122         }
123
124         return escalator.context.RequestContext(**kwargs)
125
126
127 class UnauthenticatedContextMiddleware(BaseContextMiddleware):
128     def process_request(self, req):
129         """Create a context without an authorized user."""
130         kwargs = {
131             'user': None,
132             'tenant': None,
133             'roles': [],
134             'is_admin': True,
135         }
136
137         req.context = escalator.context.RequestContext(**kwargs)