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.
Files changed (206) hide show
  1. swift/__init__.py +29 -50
  2. swift/account/auditor.py +21 -118
  3. swift/account/backend.py +33 -28
  4. swift/account/reaper.py +37 -28
  5. swift/account/replicator.py +22 -0
  6. swift/account/server.py +60 -26
  7. swift/account/utils.py +28 -11
  8. swift-2.23.3.data/scripts/swift-account-audit → swift/cli/account_audit.py +23 -13
  9. swift-2.23.3.data/scripts/swift-config → swift/cli/config.py +2 -2
  10. swift/cli/container_deleter.py +5 -11
  11. swift-2.23.3.data/scripts/swift-dispersion-populate → swift/cli/dispersion_populate.py +8 -7
  12. swift/cli/dispersion_report.py +10 -9
  13. swift-2.23.3.data/scripts/swift-drive-audit → swift/cli/drive_audit.py +63 -21
  14. swift/cli/form_signature.py +3 -7
  15. swift-2.23.3.data/scripts/swift-get-nodes → swift/cli/get_nodes.py +8 -2
  16. swift/cli/info.py +154 -14
  17. swift/cli/manage_shard_ranges.py +705 -37
  18. swift-2.23.3.data/scripts/swift-oldies → swift/cli/oldies.py +25 -14
  19. swift-2.23.3.data/scripts/swift-orphans → swift/cli/orphans.py +7 -3
  20. swift/cli/recon.py +196 -67
  21. swift-2.23.3.data/scripts/swift-recon-cron → swift/cli/recon_cron.py +17 -20
  22. swift-2.23.3.data/scripts/swift-reconciler-enqueue → swift/cli/reconciler_enqueue.py +2 -3
  23. swift/cli/relinker.py +807 -126
  24. swift/cli/reload.py +135 -0
  25. swift/cli/ringbuilder.py +217 -20
  26. swift/cli/ringcomposer.py +0 -1
  27. swift/cli/shard-info.py +4 -3
  28. swift/common/base_storage_server.py +9 -20
  29. swift/common/bufferedhttp.py +48 -74
  30. swift/common/constraints.py +20 -15
  31. swift/common/container_sync_realms.py +9 -11
  32. swift/common/daemon.py +25 -8
  33. swift/common/db.py +195 -128
  34. swift/common/db_auditor.py +168 -0
  35. swift/common/db_replicator.py +95 -55
  36. swift/common/digest.py +141 -0
  37. swift/common/direct_client.py +144 -33
  38. swift/common/error_limiter.py +93 -0
  39. swift/common/exceptions.py +25 -1
  40. swift/common/header_key_dict.py +2 -9
  41. swift/common/http_protocol.py +373 -0
  42. swift/common/internal_client.py +129 -59
  43. swift/common/linkat.py +3 -4
  44. swift/common/manager.py +284 -67
  45. swift/common/memcached.py +390 -145
  46. swift/common/middleware/__init__.py +4 -0
  47. swift/common/middleware/account_quotas.py +211 -46
  48. swift/common/middleware/acl.py +3 -8
  49. swift/common/middleware/backend_ratelimit.py +230 -0
  50. swift/common/middleware/bulk.py +22 -34
  51. swift/common/middleware/catch_errors.py +1 -3
  52. swift/common/middleware/cname_lookup.py +6 -11
  53. swift/common/middleware/container_quotas.py +1 -1
  54. swift/common/middleware/container_sync.py +39 -17
  55. swift/common/middleware/copy.py +12 -0
  56. swift/common/middleware/crossdomain.py +22 -9
  57. swift/common/middleware/crypto/__init__.py +2 -1
  58. swift/common/middleware/crypto/crypto_utils.py +11 -15
  59. swift/common/middleware/crypto/decrypter.py +28 -11
  60. swift/common/middleware/crypto/encrypter.py +12 -17
  61. swift/common/middleware/crypto/keymaster.py +8 -15
  62. swift/common/middleware/crypto/kms_keymaster.py +2 -1
  63. swift/common/middleware/dlo.py +15 -11
  64. swift/common/middleware/domain_remap.py +5 -4
  65. swift/common/middleware/etag_quoter.py +128 -0
  66. swift/common/middleware/formpost.py +73 -70
  67. swift/common/middleware/gatekeeper.py +8 -1
  68. swift/common/middleware/keystoneauth.py +33 -3
  69. swift/common/middleware/list_endpoints.py +4 -4
  70. swift/common/middleware/listing_formats.py +85 -49
  71. swift/common/middleware/memcache.py +4 -95
  72. swift/common/middleware/name_check.py +3 -2
  73. swift/common/middleware/proxy_logging.py +160 -92
  74. swift/common/middleware/ratelimit.py +17 -10
  75. swift/common/middleware/read_only.py +6 -4
  76. swift/common/middleware/recon.py +59 -22
  77. swift/common/middleware/s3api/acl_handlers.py +25 -3
  78. swift/common/middleware/s3api/acl_utils.py +6 -1
  79. swift/common/middleware/s3api/controllers/__init__.py +6 -0
  80. swift/common/middleware/s3api/controllers/acl.py +3 -2
  81. swift/common/middleware/s3api/controllers/bucket.py +242 -137
  82. swift/common/middleware/s3api/controllers/logging.py +2 -2
  83. swift/common/middleware/s3api/controllers/multi_delete.py +43 -20
  84. swift/common/middleware/s3api/controllers/multi_upload.py +219 -133
  85. swift/common/middleware/s3api/controllers/obj.py +112 -8
  86. swift/common/middleware/s3api/controllers/object_lock.py +44 -0
  87. swift/common/middleware/s3api/controllers/s3_acl.py +2 -2
  88. swift/common/middleware/s3api/controllers/tagging.py +57 -0
  89. swift/common/middleware/s3api/controllers/versioning.py +36 -7
  90. swift/common/middleware/s3api/etree.py +22 -9
  91. swift/common/middleware/s3api/exception.py +0 -4
  92. swift/common/middleware/s3api/s3api.py +113 -41
  93. swift/common/middleware/s3api/s3request.py +384 -218
  94. swift/common/middleware/s3api/s3response.py +126 -23
  95. swift/common/middleware/s3api/s3token.py +16 -17
  96. swift/common/middleware/s3api/schema/delete.rng +1 -1
  97. swift/common/middleware/s3api/subresource.py +7 -10
  98. swift/common/middleware/s3api/utils.py +27 -10
  99. swift/common/middleware/slo.py +665 -358
  100. swift/common/middleware/staticweb.py +64 -37
  101. swift/common/middleware/symlink.py +51 -18
  102. swift/common/middleware/tempauth.py +76 -58
  103. swift/common/middleware/tempurl.py +191 -173
  104. swift/common/middleware/versioned_writes/__init__.py +51 -0
  105. swift/common/middleware/{versioned_writes.py → versioned_writes/legacy.py} +27 -26
  106. swift/common/middleware/versioned_writes/object_versioning.py +1482 -0
  107. swift/common/middleware/x_profile/exceptions.py +1 -4
  108. swift/common/middleware/x_profile/html_viewer.py +18 -19
  109. swift/common/middleware/x_profile/profile_model.py +1 -2
  110. swift/common/middleware/xprofile.py +10 -10
  111. swift-2.23.3.data/scripts/swift-container-server → swift/common/recon.py +13 -8
  112. swift/common/registry.py +147 -0
  113. swift/common/request_helpers.py +324 -57
  114. swift/common/ring/builder.py +67 -25
  115. swift/common/ring/composite_builder.py +1 -1
  116. swift/common/ring/ring.py +177 -51
  117. swift/common/ring/utils.py +1 -1
  118. swift/common/splice.py +10 -6
  119. swift/common/statsd_client.py +205 -0
  120. swift/common/storage_policy.py +49 -44
  121. swift/common/swob.py +86 -102
  122. swift/common/{utils.py → utils/__init__.py} +2163 -2772
  123. swift/common/utils/base.py +131 -0
  124. swift/common/utils/config.py +433 -0
  125. swift/common/utils/ipaddrs.py +256 -0
  126. swift/common/utils/libc.py +345 -0
  127. swift/common/utils/logs.py +859 -0
  128. swift/common/utils/timestamp.py +412 -0
  129. swift/common/wsgi.py +553 -535
  130. swift/container/auditor.py +14 -100
  131. swift/container/backend.py +490 -231
  132. swift/container/reconciler.py +126 -37
  133. swift/container/replicator.py +96 -22
  134. swift/container/server.py +358 -165
  135. swift/container/sharder.py +1540 -684
  136. swift/container/sync.py +94 -88
  137. swift/container/updater.py +53 -32
  138. swift/obj/auditor.py +153 -35
  139. swift/obj/diskfile.py +466 -217
  140. swift/obj/expirer.py +406 -124
  141. swift/obj/mem_diskfile.py +7 -4
  142. swift/obj/mem_server.py +1 -0
  143. swift/obj/reconstructor.py +523 -262
  144. swift/obj/replicator.py +249 -188
  145. swift/obj/server.py +207 -122
  146. swift/obj/ssync_receiver.py +145 -85
  147. swift/obj/ssync_sender.py +113 -54
  148. swift/obj/updater.py +652 -139
  149. swift/obj/watchers/__init__.py +0 -0
  150. swift/obj/watchers/dark_data.py +213 -0
  151. swift/proxy/controllers/account.py +11 -11
  152. swift/proxy/controllers/base.py +848 -604
  153. swift/proxy/controllers/container.py +433 -92
  154. swift/proxy/controllers/info.py +3 -2
  155. swift/proxy/controllers/obj.py +1000 -489
  156. swift/proxy/server.py +185 -112
  157. {swift-2.23.3.dist-info → swift-2.35.0.dist-info}/AUTHORS +58 -11
  158. {swift-2.23.3.dist-info → swift-2.35.0.dist-info}/METADATA +51 -56
  159. swift-2.35.0.dist-info/RECORD +201 -0
  160. {swift-2.23.3.dist-info → swift-2.35.0.dist-info}/WHEEL +1 -1
  161. {swift-2.23.3.dist-info → swift-2.35.0.dist-info}/entry_points.txt +43 -0
  162. swift-2.35.0.dist-info/pbr.json +1 -0
  163. swift/locale/de/LC_MESSAGES/swift.po +0 -1216
  164. swift/locale/en_GB/LC_MESSAGES/swift.po +0 -1207
  165. swift/locale/es/LC_MESSAGES/swift.po +0 -1085
  166. swift/locale/fr/LC_MESSAGES/swift.po +0 -909
  167. swift/locale/it/LC_MESSAGES/swift.po +0 -894
  168. swift/locale/ja/LC_MESSAGES/swift.po +0 -965
  169. swift/locale/ko_KR/LC_MESSAGES/swift.po +0 -964
  170. swift/locale/pt_BR/LC_MESSAGES/swift.po +0 -881
  171. swift/locale/ru/LC_MESSAGES/swift.po +0 -891
  172. swift/locale/tr_TR/LC_MESSAGES/swift.po +0 -832
  173. swift/locale/zh_CN/LC_MESSAGES/swift.po +0 -833
  174. swift/locale/zh_TW/LC_MESSAGES/swift.po +0 -838
  175. swift-2.23.3.data/scripts/swift-account-auditor +0 -23
  176. swift-2.23.3.data/scripts/swift-account-info +0 -51
  177. swift-2.23.3.data/scripts/swift-account-reaper +0 -23
  178. swift-2.23.3.data/scripts/swift-account-replicator +0 -34
  179. swift-2.23.3.data/scripts/swift-account-server +0 -23
  180. swift-2.23.3.data/scripts/swift-container-auditor +0 -23
  181. swift-2.23.3.data/scripts/swift-container-info +0 -55
  182. swift-2.23.3.data/scripts/swift-container-reconciler +0 -21
  183. swift-2.23.3.data/scripts/swift-container-replicator +0 -34
  184. swift-2.23.3.data/scripts/swift-container-sharder +0 -37
  185. swift-2.23.3.data/scripts/swift-container-sync +0 -23
  186. swift-2.23.3.data/scripts/swift-container-updater +0 -23
  187. swift-2.23.3.data/scripts/swift-dispersion-report +0 -24
  188. swift-2.23.3.data/scripts/swift-form-signature +0 -20
  189. swift-2.23.3.data/scripts/swift-init +0 -119
  190. swift-2.23.3.data/scripts/swift-object-auditor +0 -29
  191. swift-2.23.3.data/scripts/swift-object-expirer +0 -33
  192. swift-2.23.3.data/scripts/swift-object-info +0 -60
  193. swift-2.23.3.data/scripts/swift-object-reconstructor +0 -33
  194. swift-2.23.3.data/scripts/swift-object-relinker +0 -41
  195. swift-2.23.3.data/scripts/swift-object-replicator +0 -37
  196. swift-2.23.3.data/scripts/swift-object-server +0 -27
  197. swift-2.23.3.data/scripts/swift-object-updater +0 -23
  198. swift-2.23.3.data/scripts/swift-proxy-server +0 -23
  199. swift-2.23.3.data/scripts/swift-recon +0 -24
  200. swift-2.23.3.data/scripts/swift-ring-builder +0 -24
  201. swift-2.23.3.data/scripts/swift-ring-builder-analyzer +0 -22
  202. swift-2.23.3.data/scripts/swift-ring-composer +0 -22
  203. swift-2.23.3.dist-info/RECORD +0 -220
  204. swift-2.23.3.dist-info/pbr.json +0 -1
  205. {swift-2.23.3.dist-info → swift-2.35.0.dist-info}/LICENSE +0 -0
  206. {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 cgi
138
+ import html
127
139
  import json
128
- import six
129
140
  import time
130
141
 
131
- from six.moves.urllib.parse import urlparse
142
+ from urllib.parse import urlparse
132
143
 
133
144
  from swift.common.utils import human_readable, split_path, config_true_value, \
134
- quote, register_swift_info, get_logger
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=None):
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 HTML PUBLIC "-//W3C//DTD HTML 4.01 ' \
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' % cgi.escape(label)
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
- headers = {'Content-Type': 'text/html; charset=UTF-8'}
288
- body = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 ' \
289
- 'Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">\n' \
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 = '?' + '&amp;'.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
- cgi.escape(label)
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' % cgi.escape(label)
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="../">../</a></td>\n' \
346
+ ' <td class="colname"><a href="../%s">../</a></td>\n' \
316
347
  ' <td class="colsize">&nbsp;</td>\n' \
317
348
  ' <td class="coldate">&nbsp;</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'] if six.PY3 else \
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">&nbsp;</td>\n' \
328
358
  ' <td class="coldate">&nbsp;</td>\n' \
329
359
  ' </tr>\n' % \
330
- (quote(subdir), cgi.escape(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'] if six.PY3 else \
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'] if six.PY3 else \
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
- cgi.escape(item['last_modified'] if six.PY3 else
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-' + cgi.escape(t.lower(), quote=True)
376
+ (' '.join('type-' + html.escape(t.lower())
350
377
  for t in content_type.split('/')),
351
- quote(name), cgi.escape(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, register_swift_info, split_path, \
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 cgi.parse_header for why the above chars are problematic
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
- new_req = make_subrequest(
456
- req.environ, path=target_path, method=req.method,
457
- headers=req.headers, swift_source='SYM')
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
- close_if_possible(resp)
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
- self._loop_count += 1
479
- return self._recursive_get_head(new_req, target_etag=resp_etag)
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
- with closing_if_possible(resp):
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 two special groups:
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 neither of these groups are specified, the user can only access
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 'AUTH' so accounts and tokens are prefixed
86
- by 'AUTH\_'. When a request's token and/or path start with 'AUTH\_', this
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 'AUTH, OTHER', a user with
93
- admin access to 'AUTH_account' also has admin access to
94
- 'OTHER_account'.
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
- ``SERVICE\_`` are only accessible if ``X-Auth-Token`` is from the end-user
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`` and
128
- ``.reseller_admin`` it is not a reserved name.
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 six
185
- from swift.common.swob import Response, Request, wsgi_to_str
186
- from swift.common.swob import HTTPBadRequest, HTTPForbidden, HTTPNotFound, \
187
- HTTPUnauthorized, HTTPMethodNotAllowed
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, register_swift_info
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
- self.logger.set_statsd_prefix('tempauth.%s' % (
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
- account, username = conf_key.split('_', 1)[1].split('_')
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, six.string_types):
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
- realm = account or 'unknown'
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
- auth = 'Swift realm="%s"' % account
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
- auth = 'Swift realm="unknown"'
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.encode('utf8') if six.PY2 else 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
- # Generate new token
818
- token = '%stk%s' % (self.reseller_prefix, uuid4().hex)
819
- expires = time() + self.token_life
820
- groups = self._get_user_groups(account, account_user, account_id)
821
- # Save token
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()))})