swift 2.23.3__py3-none-any.whl → 2.35.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- swift/__init__.py +29 -50
- swift/account/auditor.py +21 -118
- swift/account/backend.py +33 -28
- swift/account/reaper.py +37 -28
- swift/account/replicator.py +22 -0
- swift/account/server.py +60 -26
- swift/account/utils.py +28 -11
- swift-2.23.3.data/scripts/swift-account-audit → swift/cli/account_audit.py +23 -13
- swift-2.23.3.data/scripts/swift-config → swift/cli/config.py +2 -2
- swift/cli/container_deleter.py +5 -11
- swift-2.23.3.data/scripts/swift-dispersion-populate → swift/cli/dispersion_populate.py +8 -7
- swift/cli/dispersion_report.py +10 -9
- swift-2.23.3.data/scripts/swift-drive-audit → swift/cli/drive_audit.py +63 -21
- swift/cli/form_signature.py +3 -7
- swift-2.23.3.data/scripts/swift-get-nodes → swift/cli/get_nodes.py +8 -2
- swift/cli/info.py +154 -14
- swift/cli/manage_shard_ranges.py +705 -37
- swift-2.23.3.data/scripts/swift-oldies → swift/cli/oldies.py +25 -14
- swift-2.23.3.data/scripts/swift-orphans → swift/cli/orphans.py +7 -3
- swift/cli/recon.py +196 -67
- swift-2.23.3.data/scripts/swift-recon-cron → swift/cli/recon_cron.py +17 -20
- swift-2.23.3.data/scripts/swift-reconciler-enqueue → swift/cli/reconciler_enqueue.py +2 -3
- swift/cli/relinker.py +807 -126
- swift/cli/reload.py +135 -0
- swift/cli/ringbuilder.py +217 -20
- swift/cli/ringcomposer.py +0 -1
- swift/cli/shard-info.py +4 -3
- swift/common/base_storage_server.py +9 -20
- swift/common/bufferedhttp.py +48 -74
- swift/common/constraints.py +20 -15
- swift/common/container_sync_realms.py +9 -11
- swift/common/daemon.py +25 -8
- swift/common/db.py +195 -128
- swift/common/db_auditor.py +168 -0
- swift/common/db_replicator.py +95 -55
- swift/common/digest.py +141 -0
- swift/common/direct_client.py +144 -33
- swift/common/error_limiter.py +93 -0
- swift/common/exceptions.py +25 -1
- swift/common/header_key_dict.py +2 -9
- swift/common/http_protocol.py +373 -0
- swift/common/internal_client.py +129 -59
- swift/common/linkat.py +3 -4
- swift/common/manager.py +284 -67
- swift/common/memcached.py +390 -145
- swift/common/middleware/__init__.py +4 -0
- swift/common/middleware/account_quotas.py +211 -46
- swift/common/middleware/acl.py +3 -8
- swift/common/middleware/backend_ratelimit.py +230 -0
- swift/common/middleware/bulk.py +22 -34
- swift/common/middleware/catch_errors.py +1 -3
- swift/common/middleware/cname_lookup.py +6 -11
- swift/common/middleware/container_quotas.py +1 -1
- swift/common/middleware/container_sync.py +39 -17
- swift/common/middleware/copy.py +12 -0
- swift/common/middleware/crossdomain.py +22 -9
- swift/common/middleware/crypto/__init__.py +2 -1
- swift/common/middleware/crypto/crypto_utils.py +11 -15
- swift/common/middleware/crypto/decrypter.py +28 -11
- swift/common/middleware/crypto/encrypter.py +12 -17
- swift/common/middleware/crypto/keymaster.py +8 -15
- swift/common/middleware/crypto/kms_keymaster.py +2 -1
- swift/common/middleware/dlo.py +15 -11
- swift/common/middleware/domain_remap.py +5 -4
- swift/common/middleware/etag_quoter.py +128 -0
- swift/common/middleware/formpost.py +73 -70
- swift/common/middleware/gatekeeper.py +8 -1
- swift/common/middleware/keystoneauth.py +33 -3
- swift/common/middleware/list_endpoints.py +4 -4
- swift/common/middleware/listing_formats.py +85 -49
- swift/common/middleware/memcache.py +4 -95
- swift/common/middleware/name_check.py +3 -2
- swift/common/middleware/proxy_logging.py +160 -92
- swift/common/middleware/ratelimit.py +17 -10
- swift/common/middleware/read_only.py +6 -4
- swift/common/middleware/recon.py +59 -22
- swift/common/middleware/s3api/acl_handlers.py +25 -3
- swift/common/middleware/s3api/acl_utils.py +6 -1
- swift/common/middleware/s3api/controllers/__init__.py +6 -0
- swift/common/middleware/s3api/controllers/acl.py +3 -2
- swift/common/middleware/s3api/controllers/bucket.py +242 -137
- swift/common/middleware/s3api/controllers/logging.py +2 -2
- swift/common/middleware/s3api/controllers/multi_delete.py +43 -20
- swift/common/middleware/s3api/controllers/multi_upload.py +219 -133
- swift/common/middleware/s3api/controllers/obj.py +112 -8
- swift/common/middleware/s3api/controllers/object_lock.py +44 -0
- swift/common/middleware/s3api/controllers/s3_acl.py +2 -2
- swift/common/middleware/s3api/controllers/tagging.py +57 -0
- swift/common/middleware/s3api/controllers/versioning.py +36 -7
- swift/common/middleware/s3api/etree.py +22 -9
- swift/common/middleware/s3api/exception.py +0 -4
- swift/common/middleware/s3api/s3api.py +113 -41
- swift/common/middleware/s3api/s3request.py +384 -218
- swift/common/middleware/s3api/s3response.py +126 -23
- swift/common/middleware/s3api/s3token.py +16 -17
- swift/common/middleware/s3api/schema/delete.rng +1 -1
- swift/common/middleware/s3api/subresource.py +7 -10
- swift/common/middleware/s3api/utils.py +27 -10
- swift/common/middleware/slo.py +665 -358
- swift/common/middleware/staticweb.py +64 -37
- swift/common/middleware/symlink.py +51 -18
- swift/common/middleware/tempauth.py +76 -58
- swift/common/middleware/tempurl.py +191 -173
- swift/common/middleware/versioned_writes/__init__.py +51 -0
- swift/common/middleware/{versioned_writes.py → versioned_writes/legacy.py} +27 -26
- swift/common/middleware/versioned_writes/object_versioning.py +1482 -0
- swift/common/middleware/x_profile/exceptions.py +1 -4
- swift/common/middleware/x_profile/html_viewer.py +18 -19
- swift/common/middleware/x_profile/profile_model.py +1 -2
- swift/common/middleware/xprofile.py +10 -10
- swift-2.23.3.data/scripts/swift-container-server → swift/common/recon.py +13 -8
- swift/common/registry.py +147 -0
- swift/common/request_helpers.py +324 -57
- swift/common/ring/builder.py +67 -25
- swift/common/ring/composite_builder.py +1 -1
- swift/common/ring/ring.py +177 -51
- swift/common/ring/utils.py +1 -1
- swift/common/splice.py +10 -6
- swift/common/statsd_client.py +205 -0
- swift/common/storage_policy.py +49 -44
- swift/common/swob.py +86 -102
- swift/common/{utils.py → utils/__init__.py} +2163 -2772
- swift/common/utils/base.py +131 -0
- swift/common/utils/config.py +433 -0
- swift/common/utils/ipaddrs.py +256 -0
- swift/common/utils/libc.py +345 -0
- swift/common/utils/logs.py +859 -0
- swift/common/utils/timestamp.py +412 -0
- swift/common/wsgi.py +553 -535
- swift/container/auditor.py +14 -100
- swift/container/backend.py +490 -231
- swift/container/reconciler.py +126 -37
- swift/container/replicator.py +96 -22
- swift/container/server.py +358 -165
- swift/container/sharder.py +1540 -684
- swift/container/sync.py +94 -88
- swift/container/updater.py +53 -32
- swift/obj/auditor.py +153 -35
- swift/obj/diskfile.py +466 -217
- swift/obj/expirer.py +406 -124
- swift/obj/mem_diskfile.py +7 -4
- swift/obj/mem_server.py +1 -0
- swift/obj/reconstructor.py +523 -262
- swift/obj/replicator.py +249 -188
- swift/obj/server.py +207 -122
- swift/obj/ssync_receiver.py +145 -85
- swift/obj/ssync_sender.py +113 -54
- swift/obj/updater.py +652 -139
- swift/obj/watchers/__init__.py +0 -0
- swift/obj/watchers/dark_data.py +213 -0
- swift/proxy/controllers/account.py +11 -11
- swift/proxy/controllers/base.py +848 -604
- swift/proxy/controllers/container.py +433 -92
- swift/proxy/controllers/info.py +3 -2
- swift/proxy/controllers/obj.py +1000 -489
- swift/proxy/server.py +185 -112
- {swift-2.23.3.dist-info → swift-2.35.0.dist-info}/AUTHORS +58 -11
- {swift-2.23.3.dist-info → swift-2.35.0.dist-info}/METADATA +51 -56
- swift-2.35.0.dist-info/RECORD +201 -0
- {swift-2.23.3.dist-info → swift-2.35.0.dist-info}/WHEEL +1 -1
- {swift-2.23.3.dist-info → swift-2.35.0.dist-info}/entry_points.txt +43 -0
- swift-2.35.0.dist-info/pbr.json +1 -0
- swift/locale/de/LC_MESSAGES/swift.po +0 -1216
- swift/locale/en_GB/LC_MESSAGES/swift.po +0 -1207
- swift/locale/es/LC_MESSAGES/swift.po +0 -1085
- swift/locale/fr/LC_MESSAGES/swift.po +0 -909
- swift/locale/it/LC_MESSAGES/swift.po +0 -894
- swift/locale/ja/LC_MESSAGES/swift.po +0 -965
- swift/locale/ko_KR/LC_MESSAGES/swift.po +0 -964
- swift/locale/pt_BR/LC_MESSAGES/swift.po +0 -881
- swift/locale/ru/LC_MESSAGES/swift.po +0 -891
- swift/locale/tr_TR/LC_MESSAGES/swift.po +0 -832
- swift/locale/zh_CN/LC_MESSAGES/swift.po +0 -833
- swift/locale/zh_TW/LC_MESSAGES/swift.po +0 -838
- swift-2.23.3.data/scripts/swift-account-auditor +0 -23
- swift-2.23.3.data/scripts/swift-account-info +0 -51
- swift-2.23.3.data/scripts/swift-account-reaper +0 -23
- swift-2.23.3.data/scripts/swift-account-replicator +0 -34
- swift-2.23.3.data/scripts/swift-account-server +0 -23
- swift-2.23.3.data/scripts/swift-container-auditor +0 -23
- swift-2.23.3.data/scripts/swift-container-info +0 -55
- swift-2.23.3.data/scripts/swift-container-reconciler +0 -21
- swift-2.23.3.data/scripts/swift-container-replicator +0 -34
- swift-2.23.3.data/scripts/swift-container-sharder +0 -37
- swift-2.23.3.data/scripts/swift-container-sync +0 -23
- swift-2.23.3.data/scripts/swift-container-updater +0 -23
- swift-2.23.3.data/scripts/swift-dispersion-report +0 -24
- swift-2.23.3.data/scripts/swift-form-signature +0 -20
- swift-2.23.3.data/scripts/swift-init +0 -119
- swift-2.23.3.data/scripts/swift-object-auditor +0 -29
- swift-2.23.3.data/scripts/swift-object-expirer +0 -33
- swift-2.23.3.data/scripts/swift-object-info +0 -60
- swift-2.23.3.data/scripts/swift-object-reconstructor +0 -33
- swift-2.23.3.data/scripts/swift-object-relinker +0 -41
- swift-2.23.3.data/scripts/swift-object-replicator +0 -37
- swift-2.23.3.data/scripts/swift-object-server +0 -27
- swift-2.23.3.data/scripts/swift-object-updater +0 -23
- swift-2.23.3.data/scripts/swift-proxy-server +0 -23
- swift-2.23.3.data/scripts/swift-recon +0 -24
- swift-2.23.3.data/scripts/swift-ring-builder +0 -24
- swift-2.23.3.data/scripts/swift-ring-builder-analyzer +0 -22
- swift-2.23.3.data/scripts/swift-ring-composer +0 -22
- swift-2.23.3.dist-info/RECORD +0 -220
- swift-2.23.3.dist-info/pbr.json +0 -1
- {swift-2.23.3.dist-info → swift-2.35.0.dist-info}/LICENSE +0 -0
- {swift-2.23.3.dist-info → swift-2.35.0.dist-info}/top_level.txt +0 -0
@@ -59,7 +59,8 @@ requests for paths not found.
|
|
59
59
|
|
60
60
|
For pseudo paths that have no <index.name>, this middleware can serve HTML file
|
61
61
|
listings if you set the ``X-Container-Meta-Web-Listings: true`` metadata item
|
62
|
-
on the container.
|
62
|
+
on the container. Note that the listing must be authorized; you may want a
|
63
|
+
container ACL like ``X-Container-Read: .r:*,.rlistings``.
|
63
64
|
|
64
65
|
If listings are enabled, the listings can have a custom style sheet by setting
|
65
66
|
the X-Container-Meta-Web-Listings-CSS header. For instance, setting
|
@@ -68,6 +69,17 @@ the .../listing.css style sheet. If you "view source" in your browser on a
|
|
68
69
|
listing page, you will see the well defined document structure that can be
|
69
70
|
styled.
|
70
71
|
|
72
|
+
Additionally, prefix-based :ref:`tempurl` parameters may be used to authorize
|
73
|
+
requests instead of making the whole container publicly readable. This gives
|
74
|
+
clients dynamic discoverability of the objects available within that prefix.
|
75
|
+
|
76
|
+
.. note::
|
77
|
+
|
78
|
+
``temp_url_prefix`` values should typically end with a slash (``/``) when
|
79
|
+
used with StaticWeb. StaticWeb's redirects will not carry over any TempURL
|
80
|
+
parameters, as they likely indicate that the user created an overly-broad
|
81
|
+
TempURL.
|
82
|
+
|
71
83
|
By default, the listings will be rendered with a label of
|
72
84
|
"Listing of /v1/account/container/path". This can be altered by
|
73
85
|
setting a ``X-Container-Meta-Web-Listings-Label: <label>``. For example,
|
@@ -123,19 +135,20 @@ Example usage of this middleware via ``swift``:
|
|
123
135
|
"""
|
124
136
|
|
125
137
|
|
126
|
-
import
|
138
|
+
import html
|
127
139
|
import json
|
128
|
-
import six
|
129
140
|
import time
|
130
141
|
|
131
|
-
from
|
142
|
+
from urllib.parse import urlparse
|
132
143
|
|
133
144
|
from swift.common.utils import human_readable, split_path, config_true_value, \
|
134
|
-
quote,
|
145
|
+
quote, get_logger
|
146
|
+
from swift.common.registry import register_swift_info
|
135
147
|
from swift.common.wsgi import make_env, WSGIContext
|
136
148
|
from swift.common.http import is_success, is_redirection, HTTP_NOT_FOUND
|
137
149
|
from swift.common.swob import Response, HTTPMovedPermanently, HTTPNotFound, \
|
138
|
-
Request, wsgi_quote, wsgi_to_str
|
150
|
+
Request, wsgi_quote, wsgi_to_str, str_to_wsgi
|
151
|
+
from swift.common.middleware.tempurl import get_temp_url_info
|
139
152
|
from swift.proxy.controllers.base import get_container_info
|
140
153
|
|
141
154
|
|
@@ -224,13 +237,13 @@ class _StaticWebContext(WSGIContext):
|
|
224
237
|
self._dir_type = meta.get('web-directory-type', '').strip()
|
225
238
|
return container_info
|
226
239
|
|
227
|
-
def _listing(self, env, start_response, prefix=
|
240
|
+
def _listing(self, env, start_response, prefix=''):
|
228
241
|
"""
|
229
242
|
Sends an HTML object listing to the remote client.
|
230
243
|
|
231
244
|
:param env: The original WSGI environment dict.
|
232
245
|
:param start_response: The original WSGI start_response hook.
|
233
|
-
:param prefix: Any prefix desired for the container listing.
|
246
|
+
:param prefix: Any WSGI-str prefix desired for the container listing.
|
234
247
|
"""
|
235
248
|
label = wsgi_to_str(env['PATH_INFO'])
|
236
249
|
if self._listings_label:
|
@@ -239,11 +252,10 @@ class _StaticWebContext(WSGIContext):
|
|
239
252
|
'/'.join(groups[4:]))
|
240
253
|
|
241
254
|
if not config_true_value(self._listings):
|
242
|
-
body = '<!DOCTYPE
|
243
|
-
'Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">\n' \
|
255
|
+
body = '<!DOCTYPE html>\n' \
|
244
256
|
'<html>\n' \
|
245
257
|
'<head>\n' \
|
246
|
-
'<title>Listing of %s</title>\n' %
|
258
|
+
'<title>Listing of %s</title>\n' % html.escape(label)
|
247
259
|
if self._listings_css:
|
248
260
|
body += ' <link rel="stylesheet" type="text/css" ' \
|
249
261
|
'href="%s" />\n' % self._build_css_path(prefix or '')
|
@@ -281,16 +293,35 @@ class _StaticWebContext(WSGIContext):
|
|
281
293
|
body = b''.join(resp)
|
282
294
|
if body:
|
283
295
|
listing = json.loads(body)
|
284
|
-
if not listing:
|
296
|
+
if prefix and not listing:
|
285
297
|
resp = HTTPNotFound()(env, self._start_response)
|
286
298
|
return self._error_response(resp, env, start_response)
|
287
|
-
|
288
|
-
|
289
|
-
|
299
|
+
|
300
|
+
tempurl_qs = tempurl_prefix = ''
|
301
|
+
if env.get('REMOTE_USER') == '.wsgi.tempurl':
|
302
|
+
sig, expires, tempurl_prefix, _filename, inline, ip_range = \
|
303
|
+
get_temp_url_info(env)
|
304
|
+
if tempurl_prefix is None:
|
305
|
+
tempurl_prefix = ''
|
306
|
+
else:
|
307
|
+
parts = [
|
308
|
+
'temp_url_prefix=%s' % quote(tempurl_prefix),
|
309
|
+
'temp_url_expires=%s' % quote(str(expires)),
|
310
|
+
'temp_url_sig=%s' % sig,
|
311
|
+
]
|
312
|
+
if ip_range:
|
313
|
+
parts.append('temp_url_ip_range=%s' % quote(ip_range))
|
314
|
+
if inline:
|
315
|
+
parts.append('inline')
|
316
|
+
tempurl_qs = '?' + '&'.join(parts)
|
317
|
+
|
318
|
+
headers = {'Content-Type': 'text/html; charset=UTF-8',
|
319
|
+
'X-Backend-Content-Generator': 'staticweb'}
|
320
|
+
body = '<!DOCTYPE html>\n' \
|
290
321
|
'<html>\n' \
|
291
322
|
' <head>\n' \
|
292
323
|
' <title>Listing of %s</title>\n' % \
|
293
|
-
|
324
|
+
html.escape(label)
|
294
325
|
if self._listings_css:
|
295
326
|
body += ' <link rel="stylesheet" type="text/css" ' \
|
296
327
|
'href="%s" />\n' % (self._build_css_path(prefix))
|
@@ -309,46 +340,42 @@ class _StaticWebContext(WSGIContext):
|
|
309
340
|
' <th class="colname">Name</th>\n' \
|
310
341
|
' <th class="colsize">Size</th>\n' \
|
311
342
|
' <th class="coldate">Date</th>\n' \
|
312
|
-
' </tr>\n' %
|
313
|
-
if prefix:
|
343
|
+
' </tr>\n' % html.escape(label)
|
344
|
+
if len(prefix) > len(tempurl_prefix):
|
314
345
|
body += ' <tr id="parent" class="item">\n' \
|
315
|
-
' <td class="colname"><a href="
|
346
|
+
' <td class="colname"><a href="../%s">../</a></td>\n' \
|
316
347
|
' <td class="colsize"> </td>\n' \
|
317
348
|
' <td class="coldate"> </td>\n' \
|
318
|
-
' </tr>\n'
|
349
|
+
' </tr>\n' % tempurl_qs
|
319
350
|
for item in listing:
|
320
351
|
if 'subdir' in item:
|
321
|
-
subdir = item['subdir']
|
322
|
-
item['subdir'].encode('utf-8')
|
352
|
+
subdir = item['subdir']
|
323
353
|
if prefix:
|
324
|
-
subdir = subdir[len(prefix):]
|
354
|
+
subdir = subdir[len(wsgi_to_str(prefix)):]
|
325
355
|
body += ' <tr class="item subdir">\n' \
|
326
356
|
' <td class="colname"><a href="%s">%s</a></td>\n' \
|
327
357
|
' <td class="colsize"> </td>\n' \
|
328
358
|
' <td class="coldate"> </td>\n' \
|
329
359
|
' </tr>\n' % \
|
330
|
-
(quote(subdir),
|
360
|
+
(quote(subdir) + tempurl_qs, html.escape(subdir))
|
331
361
|
for item in listing:
|
332
362
|
if 'name' in item:
|
333
|
-
name = item['name']
|
334
|
-
item['name'].encode('utf-8')
|
363
|
+
name = item['name']
|
335
364
|
if prefix:
|
336
|
-
name = name[len(prefix):]
|
337
|
-
content_type = item['content_type']
|
338
|
-
item['content_type'].encode('utf-8')
|
365
|
+
name = name[len(wsgi_to_str(prefix)):]
|
366
|
+
content_type = item['content_type']
|
339
367
|
bytes = human_readable(item['bytes'])
|
340
368
|
last_modified = (
|
341
|
-
|
342
|
-
item['last_modified'].encode('utf-8')).
|
369
|
+
html.escape(item['last_modified']).
|
343
370
|
split('.')[0].replace('T', ' '))
|
344
371
|
body += ' <tr class="item %s">\n' \
|
345
372
|
' <td class="colname"><a href="%s">%s</a></td>\n' \
|
346
373
|
' <td class="colsize">%s</td>\n' \
|
347
374
|
' <td class="coldate">%s</td>\n' \
|
348
375
|
' </tr>\n' % \
|
349
|
-
(' '.join('type-' +
|
376
|
+
(' '.join('type-' + html.escape(t.lower())
|
350
377
|
for t in content_type.split('/')),
|
351
|
-
quote(name),
|
378
|
+
quote(name) + tempurl_qs, html.escape(name),
|
352
379
|
bytes, last_modified)
|
353
380
|
body += ' </table>\n' \
|
354
381
|
' </body>\n' \
|
@@ -408,7 +435,7 @@ class _StaticWebContext(WSGIContext):
|
|
408
435
|
tmp_env['HTTP_USER_AGENT'] = \
|
409
436
|
'%s StaticWeb' % env.get('HTTP_USER_AGENT')
|
410
437
|
tmp_env['swift.source'] = 'SW'
|
411
|
-
tmp_env['PATH_INFO'] += self._index
|
438
|
+
tmp_env['PATH_INFO'] += str_to_wsgi(self._index)
|
412
439
|
resp = self._app_call(tmp_env)
|
413
440
|
status_int = self._get_status_int()
|
414
441
|
if status_int == HTTP_NOT_FOUND:
|
@@ -465,7 +492,7 @@ class _StaticWebContext(WSGIContext):
|
|
465
492
|
tmp_env['swift.source'] = 'SW'
|
466
493
|
if not tmp_env['PATH_INFO'].endswith('/'):
|
467
494
|
tmp_env['PATH_INFO'] += '/'
|
468
|
-
tmp_env['PATH_INFO'] += self._index
|
495
|
+
tmp_env['PATH_INFO'] += str_to_wsgi(self._index)
|
469
496
|
resp = self._app_call(tmp_env)
|
470
497
|
status_int = self._get_status_int()
|
471
498
|
if is_success(status_int) or is_redirection(status_int):
|
@@ -541,8 +568,8 @@ class StaticWeb(object):
|
|
541
568
|
return self.app(env, start_response)
|
542
569
|
if env['REQUEST_METHOD'] not in ('HEAD', 'GET'):
|
543
570
|
return self.app(env, start_response)
|
544
|
-
if env.get('REMOTE_USER') and \
|
545
|
-
not config_true_value(env.get('HTTP_X_WEB_MODE', 'f')):
|
571
|
+
if env.get('REMOTE_USER') and env['REMOTE_USER'] != '.wsgi.tempurl' \
|
572
|
+
and not config_true_value(env.get('HTTP_X_WEB_MODE', 'f')):
|
546
573
|
return self.app(env, start_response)
|
547
574
|
if not container:
|
548
575
|
return self.app(env, start_response)
|
@@ -199,17 +199,20 @@ configuration steps are required:
|
|
199
199
|
|
200
200
|
import json
|
201
201
|
import os
|
202
|
-
from cgi import parse_header
|
203
202
|
|
204
|
-
from swift.common.utils import get_logger,
|
205
|
-
MD5_OF_EMPTY_STRING, close_if_possible, closing_if_possible
|
203
|
+
from swift.common.utils import get_logger, split_path, \
|
204
|
+
MD5_OF_EMPTY_STRING, close_if_possible, closing_if_possible, \
|
205
|
+
config_true_value, drain_and_close, parse_header
|
206
|
+
from swift.common.registry import register_swift_info
|
206
207
|
from swift.common.constraints import check_account_format
|
207
|
-
from swift.common.wsgi import WSGIContext, make_subrequest
|
208
|
+
from swift.common.wsgi import WSGIContext, make_subrequest, \
|
209
|
+
make_pre_authed_request
|
208
210
|
from swift.common.request_helpers import get_sys_meta_prefix, \
|
209
|
-
check_path_header, get_container_update_override_key
|
211
|
+
check_path_header, get_container_update_override_key, \
|
212
|
+
update_ignore_range_header
|
210
213
|
from swift.common.swob import Request, HTTPBadRequest, HTTPTemporaryRedirect, \
|
211
214
|
HTTPException, HTTPConflict, HTTPPreconditionFailed, wsgi_quote, \
|
212
|
-
wsgi_unquote, status_map
|
215
|
+
wsgi_unquote, status_map, normalize_etag
|
213
216
|
from swift.common.http import is_success, HTTP_NOT_FOUND
|
214
217
|
from swift.common.exceptions import LinkIterError
|
215
218
|
from swift.common.header_key_dict import HeaderKeyDict
|
@@ -227,6 +230,8 @@ TGT_ETAG_SYSMETA_SYMLINK_HDR = \
|
|
227
230
|
get_sys_meta_prefix('object') + 'symlink-target-etag'
|
228
231
|
TGT_BYTES_SYSMETA_SYMLINK_HDR = \
|
229
232
|
get_sys_meta_prefix('object') + 'symlink-target-bytes'
|
233
|
+
SYMLOOP_EXTEND = get_sys_meta_prefix('object') + 'symloop-extend'
|
234
|
+
ALLOW_RESERVED_NAMES = get_sys_meta_prefix('object') + 'allow-reserved-names'
|
230
235
|
|
231
236
|
|
232
237
|
def _validate_and_prep_request_headers(req):
|
@@ -281,9 +286,9 @@ def _validate_and_prep_request_headers(req):
|
|
281
286
|
raise HTTPBadRequest(
|
282
287
|
body='Symlink cannot target itself',
|
283
288
|
request=req, content_type='text/plain')
|
284
|
-
etag = req.headers.get(TGT_ETAG_SYMLINK_HDR, None)
|
289
|
+
etag = normalize_etag(req.headers.get(TGT_ETAG_SYMLINK_HDR, None))
|
285
290
|
if etag and any(c in etag for c in ';"\\'):
|
286
|
-
# See
|
291
|
+
# See utils.parse_header for why the above chars are problematic
|
287
292
|
raise HTTPBadRequest(
|
288
293
|
body='Bad %s format' % TGT_ETAG_SYMLINK_HDR.title(),
|
289
294
|
request=req, content_type='text/plain')
|
@@ -428,6 +433,7 @@ class SymlinkObjectContext(WSGIContext):
|
|
428
433
|
:param req: HTTP GET or HEAD object request
|
429
434
|
:returns: Response Iterator
|
430
435
|
"""
|
436
|
+
update_ignore_range_header(req, TGT_OBJ_SYSMETA_SYMLINK_HDR)
|
431
437
|
try:
|
432
438
|
return self._recursive_get_head(req)
|
433
439
|
except LinkIterError:
|
@@ -437,7 +443,9 @@ class SymlinkObjectContext(WSGIContext):
|
|
437
443
|
content_type='text/plain')
|
438
444
|
|
439
445
|
def _recursive_get_head(self, req, target_etag=None,
|
440
|
-
follow_softlinks=True):
|
446
|
+
follow_softlinks=True, orig_req=None):
|
447
|
+
if not orig_req:
|
448
|
+
orig_req = req
|
441
449
|
resp = self._app_call(req.environ)
|
442
450
|
|
443
451
|
def build_traversal_req(symlink_target):
|
@@ -452,9 +460,20 @@ class SymlinkObjectContext(WSGIContext):
|
|
452
460
|
'/', version, account,
|
453
461
|
symlink_target.lstrip('/'))
|
454
462
|
self._last_target_path = target_path
|
455
|
-
|
456
|
-
|
457
|
-
|
463
|
+
|
464
|
+
subreq_headers = dict(req.headers)
|
465
|
+
if self._response_header_value(ALLOW_RESERVED_NAMES):
|
466
|
+
# this symlink's sysmeta says it can point to reserved names,
|
467
|
+
# we're infering that some piece of middleware had previously
|
468
|
+
# authorized this request because users can't access reserved
|
469
|
+
# names directly
|
470
|
+
subreq_meth = make_pre_authed_request
|
471
|
+
subreq_headers['X-Backend-Allow-Reserved-Names'] = 'true'
|
472
|
+
else:
|
473
|
+
subreq_meth = make_subrequest
|
474
|
+
new_req = subreq_meth(orig_req.environ, path=target_path,
|
475
|
+
method=req.method, headers=subreq_headers,
|
476
|
+
swift_source='SYM')
|
458
477
|
new_req.headers.pop('X-Backend-Storage-Policy-Index', None)
|
459
478
|
return new_req
|
460
479
|
|
@@ -463,7 +482,8 @@ class SymlinkObjectContext(WSGIContext):
|
|
463
482
|
resp_etag = self._response_header_value(
|
464
483
|
TGT_ETAG_SYSMETA_SYMLINK_HDR)
|
465
484
|
if symlink_target and (resp_etag or follow_softlinks):
|
466
|
-
|
485
|
+
# Should be a zero-byte object
|
486
|
+
drain_and_close(resp)
|
467
487
|
found_etag = resp_etag or self._response_header_value('etag')
|
468
488
|
if target_etag and target_etag != found_etag:
|
469
489
|
raise HTTPConflict(
|
@@ -475,11 +495,15 @@ class SymlinkObjectContext(WSGIContext):
|
|
475
495
|
raise LinkIterError()
|
476
496
|
# format: /<account name>/<container name>/<object name>
|
477
497
|
new_req = build_traversal_req(symlink_target)
|
478
|
-
|
479
|
-
|
498
|
+
if not config_true_value(
|
499
|
+
self._response_header_value(SYMLOOP_EXTEND)):
|
500
|
+
self._loop_count += 1
|
501
|
+
return self._recursive_get_head(new_req, target_etag=resp_etag,
|
502
|
+
orig_req=req)
|
480
503
|
else:
|
481
504
|
final_etag = self._response_header_value('etag')
|
482
505
|
if final_etag and target_etag and target_etag != final_etag:
|
506
|
+
# do *not* drain; we don't know how big this is
|
483
507
|
close_if_possible(resp)
|
484
508
|
body = ('Object Etag %r does not match '
|
485
509
|
'X-Symlink-Target-Etag header %r')
|
@@ -504,10 +528,18 @@ class SymlinkObjectContext(WSGIContext):
|
|
504
528
|
|
505
529
|
def _validate_etag_and_update_sysmeta(self, req, symlink_target_path,
|
506
530
|
etag):
|
531
|
+
if req.environ.get('swift.symlink_override'):
|
532
|
+
req.headers[TGT_ETAG_SYSMETA_SYMLINK_HDR] = etag
|
533
|
+
req.headers[TGT_BYTES_SYSMETA_SYMLINK_HDR] = \
|
534
|
+
req.headers[TGT_BYTES_SYMLINK_HDR]
|
535
|
+
return
|
536
|
+
|
507
537
|
# next we'll make sure the E-Tag matches a real object
|
508
538
|
new_req = make_subrequest(
|
509
539
|
req.environ, path=wsgi_quote(symlink_target_path), method='HEAD',
|
510
540
|
swift_source='SYM')
|
541
|
+
if req.allow_reserved_names:
|
542
|
+
new_req.headers['X-Backend-Allow-Reserved-Names'] = 'true'
|
511
543
|
self._last_target_path = symlink_target_path
|
512
544
|
resp = self._recursive_get_head(new_req, target_etag=etag,
|
513
545
|
follow_softlinks=False)
|
@@ -519,9 +551,7 @@ class SymlinkObjectContext(WSGIContext):
|
|
519
551
|
'Content-Type': 'text/plain',
|
520
552
|
'Content-Location': self._last_target_path})
|
521
553
|
if not is_success(self._get_status_int()):
|
522
|
-
|
523
|
-
for chunk in resp:
|
524
|
-
pass
|
554
|
+
drain_and_close(resp)
|
525
555
|
raise status_map[self._get_status_int()](request=req)
|
526
556
|
response_headers = HeaderKeyDict(self._response_headers)
|
527
557
|
# carry forward any etag update params (e.g. "slo_etag"), we'll append
|
@@ -651,6 +681,9 @@ class SymlinkObjectContext(WSGIContext):
|
|
651
681
|
req.environ['swift.leave_relative_location'] = True
|
652
682
|
errmsg = 'The requested POST was applied to a symlink. POST ' +\
|
653
683
|
'directly to the target to apply requested metadata.'
|
684
|
+
for key, value in self._response_headers:
|
685
|
+
if key.lower().startswith('x-object-sysmeta-'):
|
686
|
+
headers[key] = value
|
654
687
|
raise HTTPTemporaryRedirect(
|
655
688
|
body=errmsg, headers=headers)
|
656
689
|
else:
|
@@ -54,12 +54,13 @@ in a line like this::
|
|
54
54
|
|
55
55
|
user64_<account_b64>_<user_b64> = <key> [group] [...] [storage_url]
|
56
56
|
|
57
|
-
There are
|
57
|
+
There are three special groups:
|
58
58
|
|
59
59
|
* ``.reseller_admin`` -- can do anything to any account for this auth
|
60
|
+
* ``.reseller_reader`` -- can GET/HEAD anything in any account for this auth
|
60
61
|
* ``.admin`` -- can do anything within the account
|
61
62
|
|
62
|
-
If
|
63
|
+
If none of these groups are specified, the user can only access
|
63
64
|
containers that have been explicitly allowed for them by a ``.admin`` or
|
64
65
|
``.reseller_admin``.
|
65
66
|
|
@@ -82,16 +83,16 @@ Multiple Reseller Prefix Items
|
|
82
83
|
|
83
84
|
The reseller prefix specifies which parts of the account namespace this
|
84
85
|
middleware is responsible for managing authentication and authorization.
|
85
|
-
By default, the prefix is
|
86
|
-
by
|
86
|
+
By default, the prefix is ``AUTH`` so accounts and tokens are prefixed
|
87
|
+
by ``AUTH_``. When a request's token and/or path start with ``AUTH_``, this
|
87
88
|
middleware knows it is responsible.
|
88
89
|
|
89
90
|
We allow the reseller prefix to be a list. In tempauth, the first item
|
90
91
|
in the list is used as the prefix for tokens and user groups. The
|
91
92
|
other prefixes provide alternate accounts that user's can access. For
|
92
|
-
example if the reseller prefix list is
|
93
|
-
admin access to
|
94
|
-
|
93
|
+
example if the reseller prefix list is ``AUTH, OTHER``, a user with
|
94
|
+
admin access to ``AUTH_account`` also has admin access to
|
95
|
+
``OTHER_account``.
|
95
96
|
|
96
97
|
Required Group
|
97
98
|
^^^^^^^^^^^^^^
|
@@ -112,7 +113,7 @@ derived from the token are appended to the roles derived from
|
|
112
113
|
|
113
114
|
The ``X-Service-Token`` is useful when combined with multiple reseller
|
114
115
|
prefix items. In the following configuration, accounts prefixed
|
115
|
-
``
|
116
|
+
``SERVICE_`` are only accessible if ``X-Auth-Token`` is from the end-user
|
116
117
|
and ``X-Service-Token`` is from the ``glance`` user::
|
117
118
|
|
118
119
|
[filter:tempauth]
|
@@ -124,8 +125,8 @@ and ``X-Service-Token`` is from the ``glance`` user::
|
|
124
125
|
user_maryacct_mary = marypw .admin
|
125
126
|
user_glance_glance = glancepw .service
|
126
127
|
|
127
|
-
The name ``.service`` is an example. Unlike ``.admin
|
128
|
-
``.
|
128
|
+
The name ``.service`` is an example. Unlike ``.admin``, ``.reseller_admin``,
|
129
|
+
``.reseller_reader`` it is not a reserved name.
|
129
130
|
|
130
131
|
Please note that ACLs can be set on service accounts and are matched
|
131
132
|
against the identity validated by ``X-Auth-Token``. As such ACLs can grant
|
@@ -172,7 +173,6 @@ To generate a curl command line from the above::
|
|
172
173
|
'
|
173
174
|
"""
|
174
175
|
|
175
|
-
from __future__ import print_function
|
176
176
|
|
177
177
|
import json
|
178
178
|
from time import time
|
@@ -181,16 +181,19 @@ from uuid import uuid4
|
|
181
181
|
import base64
|
182
182
|
|
183
183
|
from eventlet import Timeout
|
184
|
-
import
|
185
|
-
from swift.common.swob import
|
186
|
-
|
187
|
-
|
184
|
+
from swift.common.memcached import MemcacheConnectionError
|
185
|
+
from swift.common.swob import (
|
186
|
+
Response, Request, wsgi_to_str, str_to_wsgi, wsgi_unquote,
|
187
|
+
HTTPBadRequest, HTTPForbidden, HTTPNotFound,
|
188
|
+
HTTPUnauthorized, HTTPMethodNotAllowed, HTTPServiceUnavailable,
|
189
|
+
)
|
188
190
|
|
189
191
|
from swift.common.request_helpers import get_sys_meta_prefix
|
190
192
|
from swift.common.middleware.acl import (
|
191
193
|
clean_acl, parse_acl, referrer_allowed, acls_from_account_info)
|
192
194
|
from swift.common.utils import cache_from_env, get_logger, \
|
193
|
-
split_path, config_true_value
|
195
|
+
split_path, config_true_value
|
196
|
+
from swift.common.registry import register_swift_info
|
194
197
|
from swift.common.utils import config_read_reseller_options, quote
|
195
198
|
from swift.proxy.controllers.base import get_account_info
|
196
199
|
|
@@ -207,13 +210,14 @@ class TempAuth(object):
|
|
207
210
|
def __init__(self, app, conf):
|
208
211
|
self.app = app
|
209
212
|
self.conf = conf
|
210
|
-
self.logger = get_logger(conf, log_route='tempauth')
|
211
|
-
self.log_headers = config_true_value(conf.get('log_headers', 'f'))
|
212
213
|
self.reseller_prefixes, self.account_rules = \
|
213
214
|
config_read_reseller_options(conf, dict(require_group=''))
|
214
215
|
self.reseller_prefix = self.reseller_prefixes[0]
|
215
|
-
|
216
|
-
self.reseller_prefix if self.reseller_prefix else 'NONE',)
|
216
|
+
statsd_tail_prefix = 'tempauth.%s' % (
|
217
|
+
self.reseller_prefix if self.reseller_prefix else 'NONE',)
|
218
|
+
self.logger = get_logger(conf, log_route='tempauth',
|
219
|
+
statsd_tail_prefix=statsd_tail_prefix)
|
220
|
+
self.log_headers = config_true_value(conf.get('log_headers', 'f'))
|
217
221
|
self.auth_prefix = conf.get('auth_prefix', '/auth/')
|
218
222
|
if not self.auth_prefix or not self.auth_prefix.strip('/'):
|
219
223
|
self.logger.warning('Rewriting invalid auth prefix "%s" to '
|
@@ -231,17 +235,18 @@ class TempAuth(object):
|
|
231
235
|
self.users = {}
|
232
236
|
for conf_key in conf:
|
233
237
|
if conf_key.startswith(('user_', 'user64_')):
|
234
|
-
|
238
|
+
try:
|
239
|
+
account, username = conf_key.split('_', 1)[1].split('_')
|
240
|
+
except ValueError:
|
241
|
+
raise ValueError("key %s was provided in an "
|
242
|
+
"invalid format" % conf_key)
|
235
243
|
if conf_key.startswith('user64_'):
|
236
244
|
# Because trailing equal signs would screw up config file
|
237
245
|
# parsing, we auto-pad with '=' chars.
|
238
246
|
account += '=' * (len(account) % 4)
|
239
|
-
account = base64.b64decode(account)
|
247
|
+
account = base64.b64decode(account).decode('utf8')
|
240
248
|
username += '=' * (len(username) % 4)
|
241
|
-
username = base64.b64decode(username)
|
242
|
-
if not six.PY2:
|
243
|
-
account = account.decode('utf8')
|
244
|
-
username = username.decode('utf8')
|
249
|
+
username = base64.b64decode(username).decode('utf8')
|
245
250
|
values = conf[conf_key].split()
|
246
251
|
if not values:
|
247
252
|
raise ValueError('%s has no key set' % conf_key)
|
@@ -441,8 +446,6 @@ class TempAuth(object):
|
|
441
446
|
expires, groups = cached_auth_data
|
442
447
|
if expires < time():
|
443
448
|
groups = None
|
444
|
-
elif six.PY2:
|
445
|
-
groups = groups.encode('utf8')
|
446
449
|
|
447
450
|
s3_auth_details = env.get('s3api.auth_details') or\
|
448
451
|
env.get('swift3.auth_details')
|
@@ -461,7 +464,7 @@ class TempAuth(object):
|
|
461
464
|
if not s3_auth_details['check_signature'](user['key']):
|
462
465
|
return None
|
463
466
|
env['PATH_INFO'] = env['PATH_INFO'].replace(
|
464
|
-
account_user, account_id, 1)
|
467
|
+
str_to_wsgi(account_user), wsgi_unquote(account_id), 1)
|
465
468
|
groups = self._get_user_groups(account, account_user, account_id)
|
466
469
|
|
467
470
|
return groups
|
@@ -520,7 +523,7 @@ class TempAuth(object):
|
|
520
523
|
if not isinstance(result[key], list):
|
521
524
|
return "Value for key %s must be a list" % json.dumps(key)
|
522
525
|
for grantee in result[key]:
|
523
|
-
if not isinstance(grantee,
|
526
|
+
if not isinstance(grantee, str):
|
524
527
|
return "Elements of %s list must be strings" % json.dumps(
|
525
528
|
key)
|
526
529
|
|
@@ -569,6 +572,14 @@ class TempAuth(object):
|
|
569
572
|
% account_user)
|
570
573
|
return None
|
571
574
|
|
575
|
+
if '.reseller_reader' in user_groups and \
|
576
|
+
account not in self.reseller_prefixes and \
|
577
|
+
not self._dot_account(account) and \
|
578
|
+
req.method in ('GET', 'HEAD'):
|
579
|
+
self.logger.debug("User %s has reseller reader authorizing."
|
580
|
+
% account_user)
|
581
|
+
return None
|
582
|
+
|
572
583
|
if wsgi_to_str(account) in user_groups and \
|
573
584
|
(req.method not in ('DELETE', 'PUT') or container):
|
574
585
|
# The user is admin for the account and is not trying to do an
|
@@ -666,8 +677,6 @@ class TempAuth(object):
|
|
666
677
|
req = Request(env)
|
667
678
|
if self.auth_prefix:
|
668
679
|
req.path_info_pop()
|
669
|
-
req.bytes_transferred = '-'
|
670
|
-
req.client_disconnect = False
|
671
680
|
if 'x-storage-token' in req.headers and \
|
672
681
|
'x-auth-token' not in req.headers:
|
673
682
|
req.headers['x-auth-token'] = req.headers['x-storage-token']
|
@@ -677,7 +686,7 @@ class TempAuth(object):
|
|
677
686
|
self.logger.increment('errors')
|
678
687
|
start_response('500 Server Error',
|
679
688
|
[('Content-Type', 'text/plain')])
|
680
|
-
return ['Internal server error.\n']
|
689
|
+
return [b'Internal server error.\n']
|
681
690
|
|
682
691
|
def handle_request(self, req):
|
683
692
|
"""
|
@@ -707,6 +716,25 @@ class TempAuth(object):
|
|
707
716
|
req.response = handler(req)
|
708
717
|
return req.response
|
709
718
|
|
719
|
+
def _create_new_token(self, memcache_client,
|
720
|
+
account, account_user, account_id):
|
721
|
+
# Generate new token
|
722
|
+
token = '%stk%s' % (self.reseller_prefix, uuid4().hex)
|
723
|
+
expires = time() + self.token_life
|
724
|
+
groups = self._get_user_groups(account, account_user, account_id)
|
725
|
+
# Save token
|
726
|
+
memcache_token_key = '%s/token/%s' % (self.reseller_prefix, token)
|
727
|
+
memcache_client.set(memcache_token_key, (expires, groups),
|
728
|
+
time=float(expires - time()),
|
729
|
+
raise_on_error=True)
|
730
|
+
# Record the token with the user info for future use.
|
731
|
+
memcache_user_key = \
|
732
|
+
'%s/user/%s' % (self.reseller_prefix, account_user)
|
733
|
+
memcache_client.set(memcache_user_key, token,
|
734
|
+
time=float(expires - time()),
|
735
|
+
raise_on_error=True)
|
736
|
+
return token, expires
|
737
|
+
|
710
738
|
def handle_get_token(self, req):
|
711
739
|
"""
|
712
740
|
Handles the various `request for token and service end point(s)` calls.
|
@@ -771,24 +799,23 @@ class TempAuth(object):
|
|
771
799
|
key = req.headers.get('x-storage-pass')
|
772
800
|
else:
|
773
801
|
return HTTPBadRequest(request=req)
|
802
|
+
unauthed_headers = {
|
803
|
+
'Www-Authenticate': 'Swift realm="%s"' % (account or 'unknown'),
|
804
|
+
}
|
774
805
|
if not all((account, user, key)):
|
775
806
|
self.logger.increment('token_denied')
|
776
|
-
|
777
|
-
return HTTPUnauthorized(request=req, headers={'Www-Authenticate':
|
778
|
-
'Swift realm="%s"' %
|
779
|
-
realm})
|
807
|
+
return HTTPUnauthorized(request=req, headers=unauthed_headers)
|
780
808
|
# Authenticate user
|
809
|
+
account = wsgi_to_str(account)
|
810
|
+
user = wsgi_to_str(user)
|
811
|
+
key = wsgi_to_str(key)
|
781
812
|
account_user = account + ':' + user
|
782
813
|
if account_user not in self.users:
|
783
814
|
self.logger.increment('token_denied')
|
784
|
-
|
785
|
-
return HTTPUnauthorized(request=req,
|
786
|
-
headers={'Www-Authenticate': auth})
|
815
|
+
return HTTPUnauthorized(request=req, headers=unauthed_headers)
|
787
816
|
if self.users[account_user]['key'] != key:
|
788
817
|
self.logger.increment('token_denied')
|
789
|
-
|
790
|
-
return HTTPUnauthorized(request=req,
|
791
|
-
headers={'Www-Authenticate': auth})
|
818
|
+
return HTTPUnauthorized(request=req, headers=unauthed_headers)
|
792
819
|
account_id = self.users[account_user]['url'].rsplit('/', 1)[-1]
|
793
820
|
# Get memcache client
|
794
821
|
memcache_client = cache_from_env(req.environ)
|
@@ -804,8 +831,7 @@ class TempAuth(object):
|
|
804
831
|
cached_auth_data = memcache_client.get(memcache_token_key)
|
805
832
|
if cached_auth_data:
|
806
833
|
expires, old_groups = cached_auth_data
|
807
|
-
old_groups = [group.
|
808
|
-
for group in old_groups.split(',')]
|
834
|
+
old_groups = [group for group in old_groups.split(',')]
|
809
835
|
new_groups = self._get_user_groups(account, account_user,
|
810
836
|
account_id)
|
811
837
|
|
@@ -814,19 +840,11 @@ class TempAuth(object):
|
|
814
840
|
token = candidate_token
|
815
841
|
# Create a new token if one didn't exist
|
816
842
|
if not token:
|
817
|
-
|
818
|
-
|
819
|
-
|
820
|
-
|
821
|
-
|
822
|
-
memcache_token_key = '%s/token/%s' % (self.reseller_prefix, token)
|
823
|
-
memcache_client.set(memcache_token_key, (expires, groups),
|
824
|
-
time=float(expires - time()))
|
825
|
-
# Record the token with the user info for future use.
|
826
|
-
memcache_user_key = \
|
827
|
-
'%s/user/%s' % (self.reseller_prefix, account_user)
|
828
|
-
memcache_client.set(memcache_user_key, token,
|
829
|
-
time=float(expires - time()))
|
843
|
+
try:
|
844
|
+
token, expires = self._create_new_token(
|
845
|
+
memcache_client, account, account_user, account_id)
|
846
|
+
except MemcacheConnectionError:
|
847
|
+
return HTTPServiceUnavailable(request=req)
|
830
848
|
resp = Response(request=req, headers={
|
831
849
|
'x-auth-token': token, 'x-storage-token': token,
|
832
850
|
'x-auth-token-expires': str(int(expires - time()))})
|