-
Notifications
You must be signed in to change notification settings - Fork 527
/
context.py
190 lines (157 loc) · 6.39 KB
/
context.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2011 OpenStack LLC.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from glance.common import cfg
from glance.common import exception
from glance.common import utils
from glance.common import wsgi
from glance.registry.db import api as db_api
class RequestContext(object):
"""
Stores information about the security context under which the user
accesses the system, as well as additional request information.
"""
def __init__(self, auth_tok=None, user=None, tenant=None, roles=None,
is_admin=False, read_only=False, show_deleted=False,
owner_is_tenant=True):
self.auth_tok = auth_tok
self.user = user
self.tenant = tenant
self.roles = roles or []
self.is_admin = is_admin
self.read_only = read_only
self._show_deleted = show_deleted
self.owner_is_tenant = owner_is_tenant
@property
def owner(self):
"""Return the owner to correlate with an image."""
return self.tenant if self.owner_is_tenant else self.user
@property
def show_deleted(self):
"""Admins can see deleted by default"""
if self._show_deleted or self.is_admin:
return True
return False
def is_image_visible(self, image):
"""Return True if the image is visible in this context."""
# Is admin == image visible
if self.is_admin:
return True
# No owner == image visible
if image.owner is None:
return True
# Image is_public == image visible
if image.is_public:
return True
# Perform tests based on whether we have an owner
if self.owner is not None:
if self.owner == image.owner:
return True
# Figure out if this image is shared with that tenant
try:
tmp = db_api.image_member_find(self, image.id, self.owner)
return not tmp['deleted']
except exception.NotFound:
pass
# Private image
return False
def is_image_mutable(self, image):
"""Return True if the image is mutable in this context."""
# Is admin == image mutable
if self.is_admin:
return True
# No owner == image not mutable
if image.owner is None or self.owner is None:
return False
# Image only mutable by its owner
return image.owner == self.owner
def is_image_sharable(self, image, **kwargs):
"""Return True if the image can be shared to others in this context."""
# Only allow sharing if we have an owner
if self.owner is None:
return False
# Is admin == image sharable
if self.is_admin:
return True
# If we own the image, we can share it
if self.owner == image.owner:
return True
# Let's get the membership association
if 'membership' in kwargs:
membership = kwargs['membership']
if membership is None:
# Not shared with us anyway
return False
else:
try:
membership = db_api.image_member_find(self, image.id,
self.owner)
except exception.NotFound:
# Not shared with us anyway
return False
# It's the can_share attribute we're now interested in
return membership.can_share
class ContextMiddleware(wsgi.Middleware):
opts = [
cfg.BoolOpt('owner_is_tenant', default=True),
cfg.StrOpt('admin_role', default='admin'),
]
def __init__(self, app, conf, **local_conf):
self.conf = conf
self.conf.register_opts(self.opts)
super(ContextMiddleware, self).__init__(app)
def make_context(self, *args, **kwargs):
"""
Create a context with the given arguments.
"""
kwargs.setdefault('owner_is_tenant', self.conf.owner_is_tenant)
return RequestContext(*args, **kwargs)
def process_request(self, req):
"""
Extract any authentication information in the request and
construct an appropriate context from it.
A few scenarios exist:
1. If X-Auth-Token is passed in, then consult TENANT and ROLE headers
to determine permissions.
2. An X-Auth-Token was passed in, but the Identity-Status is not
confirmed. For now, just raising a NotAuthenticated exception.
3. X-Auth-Token is omitted. If we were using Keystone, then the
tokenauth middleware would have rejected the request, so we must be
using NoAuth. In that case, assume that is_admin=True.
"""
auth_tok = req.headers.get('X-Auth-Token',
req.headers.get('X-Storage-Token'))
if auth_tok:
if req.headers.get('X-Identity-Status') == 'Confirmed':
# 1. Auth-token is passed, check other headers
user = req.headers.get('X-User-Id')
tenant = req.headers.get('X-Tenant-Id')
roles = [r.strip()
for r in req.headers.get('X-Roles', '').split(',')]
is_admin = self.conf.admin_role in roles
else:
# 2. Indentity-Status not confirmed
# FIXME(sirp): not sure what the correct behavior in this case
# is; just raising NotAuthenticated for now
raise exception.NotAuthenticated()
else:
# 3. Auth-token is ommited, assume NoAuth
user = None
tenant = None
roles = []
is_admin = True
req.context = self.make_context(
auth_tok=auth_tok, user=user, tenant=tenant, roles=roles,
is_admin=is_admin)