Revision 02c0c3fa
b/pithos/api/functions.py | ||
---|---|---|
42 | 42 |
|
43 | 43 |
from pithos.api.faults import (Fault, NotModified, BadRequest, Unauthorized, ItemNotFound, Conflict, |
44 | 44 |
LengthRequired, PreconditionFailed, RangeNotSatisfiable, UnprocessableEntity) |
45 |
from pithos.api.util import (format_meta_key, printable_meta_dict, get_account_meta,
|
|
46 |
put_account_meta, get_container_meta, put_container_meta, get_object_meta, put_object_meta,
|
|
45 |
from pithos.api.util import (format_header_key, printable_header_dict, get_account_headers,
|
|
46 |
put_account_headers, get_container_headers, put_container_headers, get_object_headers, put_object_headers,
|
|
47 | 47 |
update_manifest_meta, update_sharing_meta, validate_modification_preconditions, |
48 | 48 |
validate_matching_preconditions, split_container_object_string, copy_or_move_object, |
49 | 49 |
get_int_parameter, get_content_length, get_content_range, get_sharing, raw_input_socket, |
... | ... | |
129 | 129 |
until = get_int_parameter(request, 'until') |
130 | 130 |
try: |
131 | 131 |
meta = backend.get_account_meta(request.user, v_account, until) |
132 |
groups = backend.get_account_groups(request.user, v_account) |
|
132 | 133 |
except NotAllowedError: |
133 | 134 |
raise Unauthorized('Access denied') |
134 | 135 |
|
135 | 136 |
response = HttpResponse(status=204) |
136 |
put_account_meta(response, meta)
|
|
137 |
put_account_headers(response, meta, groups)
|
|
137 | 138 |
return response |
138 | 139 |
|
139 | 140 |
@api_method('POST') |
... | ... | |
143 | 144 |
# unauthorized (401), |
144 | 145 |
# badRequest (400) |
145 | 146 |
|
146 |
meta = get_account_meta(request)
|
|
147 |
meta, groups = get_account_headers(request)
|
|
147 | 148 |
replace = True |
148 | 149 |
if 'update' in request.GET: |
149 |
replace = False |
|
150 |
replace = False |
|
151 |
if groups: |
|
152 |
try: |
|
153 |
backend.update_account_groups(request.user, v_account, groups, replace) |
|
154 |
except NotAllowedError: |
|
155 |
raise Unauthorized('Access denied') |
|
156 |
except ValueError: |
|
157 |
raise BadRequest('Invalid groups header') |
|
150 | 158 |
try: |
151 | 159 |
backend.update_account_meta(request.user, v_account, meta, replace) |
152 | 160 |
except NotAllowedError: |
... | ... | |
164 | 172 |
until = get_int_parameter(request, 'until') |
165 | 173 |
try: |
166 | 174 |
meta = backend.get_account_meta(request.user, v_account, until) |
175 |
groups = backend.get_account_groups(request.user, v_account) |
|
167 | 176 |
except NotAllowedError: |
168 | 177 |
raise Unauthorized('Access denied') |
169 | 178 |
|
170 | 179 |
validate_modification_preconditions(request, meta) |
171 | 180 |
|
172 | 181 |
response = HttpResponse() |
173 |
put_account_meta(response, meta)
|
|
182 |
put_account_headers(response, meta, groups)
|
|
174 | 183 |
|
175 | 184 |
marker = request.GET.get('marker') |
176 | 185 |
limit = request.GET.get('limit') |
... | ... | |
203 | 212 |
if x[1] is not None: |
204 | 213 |
try: |
205 | 214 |
meta = backend.get_container_meta(request.user, v_account, x[0], until) |
206 |
container_meta.append(printable_meta_dict(meta))
|
|
215 |
container_meta.append(printable_header_dict(meta))
|
|
207 | 216 |
except NotAllowedError: |
208 | 217 |
raise Unauthorized('Access denied') |
209 | 218 |
except NameError: |
... | ... | |
234 | 243 |
raise ItemNotFound('Container does not exist') |
235 | 244 |
|
236 | 245 |
response = HttpResponse(status=204) |
237 |
put_container_meta(response, meta)
|
|
246 |
put_container_headers(response, meta)
|
|
238 | 247 |
return response |
239 | 248 |
|
240 | 249 |
@api_method('PUT') |
... | ... | |
245 | 254 |
# unauthorized (401), |
246 | 255 |
# badRequest (400) |
247 | 256 |
|
248 |
meta = get_container_meta(request)
|
|
257 |
meta = get_container_headers(request)
|
|
249 | 258 |
|
250 | 259 |
try: |
251 | 260 |
backend.put_container(request.user, v_account, v_container) |
... | ... | |
273 | 282 |
# unauthorized (401), |
274 | 283 |
# badRequest (400) |
275 | 284 |
|
276 |
meta = get_container_meta(request)
|
|
285 |
meta = get_container_headers(request)
|
|
277 | 286 |
replace = True |
278 | 287 |
if 'update' in request.GET: |
279 | 288 |
replace = False |
... | ... | |
324 | 333 |
validate_modification_preconditions(request, meta) |
325 | 334 |
|
326 | 335 |
response = HttpResponse() |
327 |
put_container_meta(response, meta)
|
|
336 |
put_container_headers(response, meta)
|
|
328 | 337 |
|
329 | 338 |
path = request.GET.get('path') |
330 | 339 |
prefix = request.GET.get('prefix') |
... | ... | |
357 | 366 |
keys = request.GET.get('meta') |
358 | 367 |
if keys: |
359 | 368 |
keys = keys.split(',') |
360 |
keys = [format_meta_key('X-Object-Meta-' + x.strip()) for x in keys if x.strip() != '']
|
|
369 |
keys = [format_header_key('X-Object-Meta-' + x.strip()) for x in keys if x.strip() != '']
|
|
361 | 370 |
else: |
362 | 371 |
keys = [] |
363 | 372 |
|
... | ... | |
394 | 403 |
except NameError: |
395 | 404 |
pass |
396 | 405 |
update_sharing_meta(permissions, v_account, v_container, x[0], meta) |
397 |
object_meta.append(printable_meta_dict(meta))
|
|
406 |
object_meta.append(printable_header_dict(meta))
|
|
398 | 407 |
if request.serialization == 'xml': |
399 | 408 |
data = render_to_string('objects.xml', {'container': v_container, 'objects': object_meta}) |
400 | 409 |
elif request.serialization == 'json': |
... | ... | |
429 | 438 |
update_sharing_meta(permissions, v_account, v_container, v_object, meta) |
430 | 439 |
|
431 | 440 |
response = HttpResponse(status=200) |
432 |
put_object_meta(response, meta)
|
|
441 |
put_object_headers(response, meta)
|
|
433 | 442 |
return response |
434 | 443 |
|
435 | 444 |
@api_method('GET', format_allowed=True) |
... | ... | |
538 | 547 |
data = json.dumps(d) |
539 | 548 |
|
540 | 549 |
response = HttpResponse(data, status=200) |
541 |
put_object_meta(response, meta)
|
|
550 |
put_object_headers(response, meta)
|
|
542 | 551 |
response['Content-Length'] = len(data) |
543 | 552 |
return response |
544 | 553 |
|
... | ... | |
575 | 584 |
copy_or_move_object(request, v_account, src_container, src_name, v_container, v_object, move=False) |
576 | 585 |
return HttpResponse(status=201) |
577 | 586 |
|
578 |
meta = get_object_meta(request)
|
|
587 |
meta = get_object_headers(request)
|
|
579 | 588 |
permissions = get_sharing(request) |
580 | 589 |
content_length = -1 |
581 | 590 |
if request.META.get('HTTP_TRANSFER_ENCODING') != 'chunked': |
... | ... | |
660 | 669 |
# unauthorized (401), |
661 | 670 |
# badRequest (400) |
662 | 671 |
|
663 |
meta = get_object_meta(request)
|
|
672 |
meta = get_object_headers(request)
|
|
664 | 673 |
permissions = get_sharing(request) |
665 | 674 |
content_type = meta.get('Content-Type') |
666 | 675 |
if content_type: |
b/pithos/api/tests.py | ||
---|---|---|
50 | 50 |
|
51 | 51 |
class AaiClient(Client): |
52 | 52 |
def request(self, **request): |
53 |
request['HTTP_X_AUTH_TOKEN'] = '46e427d657b20defe352804f0eb6f8a2'
|
|
53 |
request['HTTP_X_AUTH_TOKEN'] = '0000'
|
|
54 | 54 |
return super(AaiClient, self).request(**request) |
55 | 55 |
|
56 | 56 |
class BaseTestCase(TestCase): |
b/pithos/api/util.py | ||
---|---|---|
42 | 42 |
from django.utils.http import http_date, parse_etags |
43 | 43 |
|
44 | 44 |
from pithos.api.compat import parse_http_date_safe |
45 |
from pithos.api.faults import (Fault, NotModified, BadRequest, ItemNotFound, LengthRequired, |
|
46 |
PreconditionFailed, RangeNotSatisfiable, ServiceUnavailable) |
|
45 |
from pithos.api.faults import (Fault, NotModified, BadRequest, Unauthorized, ItemNotFound, |
|
46 |
LengthRequired, PreconditionFailed, RangeNotSatisfiable, |
|
47 |
ServiceUnavailable) |
|
47 | 48 |
from pithos.backends import backend |
48 | 49 |
from pithos.backends.base import NotAllowedError |
49 | 50 |
|
... | ... | |
57 | 58 |
logger = logging.getLogger(__name__) |
58 | 59 |
|
59 | 60 |
|
60 |
def printable_meta_dict(d):
|
|
61 |
def printable_header_dict(d):
|
|
61 | 62 |
"""Format a meta dictionary for printing out json/xml. |
62 | 63 |
|
63 | 64 |
Convert all keys to lower case and replace dashes to underscores. |
... | ... | |
69 | 70 |
del(d['modified']) |
70 | 71 |
return dict([(k.lower().replace('-', '_'), v) for k, v in d.iteritems()]) |
71 | 72 |
|
72 |
def format_meta_key(k):
|
|
73 |
def format_header_key(k):
|
|
73 | 74 |
"""Convert underscores to dashes and capitalize intra-dash strings.""" |
74 | 75 |
|
75 | 76 |
return '-'.join([x.capitalize() for x in k.replace('_', '-').split('-')]) |
76 | 77 |
|
77 |
def get_meta_prefix(request, prefix):
|
|
78 |
"""Get all prefix-* request headers in a dict. Reformat keys with format_meta_key()."""
|
|
78 |
def get_header_prefix(request, prefix):
|
|
79 |
"""Get all prefix-* request headers in a dict. Reformat keys with format_header_key()."""
|
|
79 | 80 |
|
80 | 81 |
prefix = 'HTTP_' + prefix.upper().replace('-', '_') |
81 |
return dict([(format_meta_key(k[5:]), v) for k, v in request.META.iteritems() if k.startswith(prefix) and len(k) > len(prefix)]) |
|
82 |
|
|
83 |
def get_account_meta(request): |
|
84 |
"""Get metadata from an account request.""" |
|
85 |
|
|
86 |
meta = get_meta_prefix(request, 'X-Account-Meta-') |
|
87 |
return meta |
|
88 |
|
|
89 |
def put_account_meta(response, meta): |
|
90 |
"""Put metadata in an account response.""" |
|
91 |
|
|
82 |
return dict([(format_header_key(k[5:]), v.replace('_', '')) for k, v in request.META.iteritems() if k.startswith(prefix) and len(k) > len(prefix)]) |
|
83 |
|
|
84 |
def get_account_headers(request): |
|
85 |
meta = get_header_prefix(request, 'X-Account-Meta-') |
|
86 |
groups = {} |
|
87 |
for k, v in get_header_prefix(request, 'X-Account-Group-').iteritems(): |
|
88 |
n = k[16:].lower() |
|
89 |
if '-' in n or '_' in n: |
|
90 |
raise BadRequest('Bad characters in group name') |
|
91 |
groups[n] = v.replace(' ', '').split(',') |
|
92 |
if '' in groups[n]: |
|
93 |
groups[n].remove('') |
|
94 |
return meta, groups |
|
95 |
|
|
96 |
def put_account_headers(response, meta, groups): |
|
92 | 97 |
response['X-Account-Container-Count'] = meta['count'] |
93 | 98 |
response['X-Account-Bytes-Used'] = meta['bytes'] |
94 | 99 |
if 'modified' in meta: |
... | ... | |
97 | 102 |
response[k.encode('utf-8')] = meta[k].encode('utf-8') |
98 | 103 |
if 'until_timestamp' in meta: |
99 | 104 |
response['X-Account-Until-Timestamp'] = http_date(int(meta['until_timestamp'])) |
105 |
for k, v in groups.iteritems(): |
|
106 |
response[format_header_key('X-Account-Group-' + k).encode('utf-8')] = (','.join(v)).encode('utf-8') |
|
100 | 107 |
|
101 |
def get_container_meta(request): |
|
102 |
"""Get metadata from a container request.""" |
|
103 |
|
|
104 |
meta = get_meta_prefix(request, 'X-Container-Meta-') |
|
108 |
def get_container_headers(request): |
|
109 |
meta = get_header_prefix(request, 'X-Container-Meta-') |
|
105 | 110 |
return meta |
106 | 111 |
|
107 |
def put_container_meta(response, meta): |
|
108 |
"""Put metadata in a container response.""" |
|
109 |
|
|
112 |
def put_container_headers(response, meta): |
|
110 | 113 |
response['X-Container-Object-Count'] = meta['count'] |
111 | 114 |
response['X-Container-Bytes-Used'] = meta['bytes'] |
112 | 115 |
response['Last-Modified'] = http_date(int(meta['modified'])) |
... | ... | |
118 | 121 |
if 'until_timestamp' in meta: |
119 | 122 |
response['X-Container-Until-Timestamp'] = http_date(int(meta['until_timestamp'])) |
120 | 123 |
|
121 |
def get_object_meta(request): |
|
122 |
"""Get metadata from an object request.""" |
|
123 |
|
|
124 |
meta = get_meta_prefix(request, 'X-Object-Meta-') |
|
124 |
def get_object_headers(request): |
|
125 |
meta = get_header_prefix(request, 'X-Object-Meta-') |
|
125 | 126 |
if request.META.get('CONTENT_TYPE'): |
126 | 127 |
meta['Content-Type'] = request.META['CONTENT_TYPE'] |
127 | 128 |
if request.META.get('HTTP_CONTENT_ENCODING'): |
... | ... | |
132 | 133 |
meta['X-Object-Manifest'] = request.META['HTTP_X_OBJECT_MANIFEST'] |
133 | 134 |
return meta |
134 | 135 |
|
135 |
def put_object_meta(response, meta, public=False): |
|
136 |
"""Put metadata in an object response.""" |
|
137 |
|
|
136 |
def put_object_headers(response, meta, public=False): |
|
138 | 137 |
response['ETag'] = meta['hash'] |
139 | 138 |
response['Content-Length'] = meta['bytes'] |
140 | 139 |
response['Content-Type'] = meta.get('Content-Type', 'application/octet-stream') |
... | ... | |
237 | 236 |
def copy_or_move_object(request, v_account, src_container, src_name, dest_container, dest_name, move=False): |
238 | 237 |
"""Copy or move an object.""" |
239 | 238 |
|
240 |
meta = get_object_meta(request)
|
|
239 |
meta = get_object_headers(request)
|
|
241 | 240 |
permissions = get_sharing(request) |
242 | 241 |
src_version = request.META.get('HTTP_X_SOURCE_VERSION') |
243 | 242 |
try: |
... | ... | |
370 | 369 |
return ret |
371 | 370 |
for perm in (x for x in permissions.split(';')): |
372 | 371 |
if perm.startswith('read='): |
373 |
ret['read'] = [v.replace(' ','') for v in perm[5:].split(',')] |
|
374 |
ret['read'].remove('') |
|
372 |
ret['read'] = [v.replace(' ','').lower() for v in perm[5:].split(',')] |
|
373 |
if '' in ret['read']: |
|
374 |
ret['read'].remove('') |
|
375 | 375 |
if '*' in ret['read']: |
376 | 376 |
ret['read'] = ['*'] |
377 | 377 |
if len(ret['read']) == 0: |
378 | 378 |
raise BadRequest('Bad X-Object-Sharing header value') |
379 | 379 |
elif perm.startswith('write='): |
380 |
ret['write'] = [v.replace(' ','') for v in perm[6:].split(',')] |
|
381 |
ret['write'].remove('') |
|
380 |
ret['write'] = [v.replace(' ','').lower() for v in perm[6:].split(',')] |
|
381 |
if '' in ret['write']: |
|
382 |
ret['write'].remove('') |
|
382 | 383 |
if '*' in ret['write']: |
383 | 384 |
ret['write'] = ['*'] |
384 | 385 |
if len(ret['write']) == 0: |
... | ... | |
562 | 563 |
boundary = '' |
563 | 564 |
wrapper = ObjectWrapper(ranges, sizes, hashmaps, boundary) |
564 | 565 |
response = HttpResponse(wrapper, status=ret) |
565 |
put_object_meta(response, meta, public)
|
|
566 |
put_object_headers(response, meta, public)
|
|
566 | 567 |
if ret == 206: |
567 | 568 |
if len(ranges) == 1: |
568 | 569 |
offset, length = ranges[0] |
b/pithos/backends/base.py | ||
---|---|---|
42 | 42 |
|
43 | 43 |
Note that the account level is always valid as it is checked from another subsystem. |
44 | 44 |
|
45 |
When not replacing metadata, keys with empty values should be deleted. |
|
45 |
When not replacing metadata/groups/policy, keys with empty values should be deleted.
|
|
46 | 46 |
|
47 | 47 |
The following variables should be available: |
48 | 48 |
'hash_algorithm': Suggested is 'sha256' |
49 | 49 |
'block_size': Suggested is 4MB |
50 | 50 |
""" |
51 | 51 |
|
52 |
def delete_account(self, user, account): |
|
53 |
"""Delete the account with the given name. |
|
54 |
|
|
55 |
Raises: |
|
56 |
NotAllowedError: Operation not permitted |
|
57 |
IndexError: Account is not empty |
|
58 |
""" |
|
59 |
return |
|
60 |
|
|
61 | 52 |
def get_account_meta(self, user, account, until=None): |
62 | 53 |
"""Return a dictionary with the account metadata. |
63 | 54 |
|
... | ... | |
85 | 76 |
""" |
86 | 77 |
return |
87 | 78 |
|
88 |
def list_containers(self, user, account, marker=None, limit=10000, until=None): |
|
89 |
"""Return a list of container (name, version_id) tuples existing under an account. |
|
90 |
|
|
91 |
Parameters: |
|
92 |
'marker': Start list from the next item after 'marker' |
|
93 |
'limit': Number of containers to return |
|
79 |
def get_account_groups(self, user, account): |
|
80 |
"""Return a dictionary with the user groups defined for this account. |
|
94 | 81 |
|
95 | 82 |
Raises: |
96 | 83 |
NotAllowedError: Operation not permitted |
97 | 84 |
""" |
98 |
return []
|
|
85 |
return {}
|
|
99 | 86 |
|
100 |
def put_container(self, user, account, container):
|
|
101 |
"""Create a new container with the given name.
|
|
87 |
def update_account_groups(self, user, account, groups, replace=False):
|
|
88 |
"""Update the groups associated with the account.
|
|
102 | 89 |
|
103 | 90 |
Raises: |
104 | 91 |
NotAllowedError: Operation not permitted |
105 |
NameError: Container already exists
|
|
92 |
ValueError: Invalid data in groups
|
|
106 | 93 |
""" |
107 | 94 |
return |
108 | 95 |
|
109 |
def delete_container(self, user, account, container):
|
|
110 |
"""Delete the container with the given name.
|
|
96 |
def delete_account(self, user, account):
|
|
97 |
"""Delete the account with the given name.
|
|
111 | 98 |
|
112 | 99 |
Raises: |
113 | 100 |
NotAllowedError: Operation not permitted |
114 |
NameError: Container does not exist |
|
115 |
IndexError: Container is not empty |
|
101 |
IndexError: Account is not empty |
|
116 | 102 |
""" |
117 | 103 |
return |
118 | 104 |
|
105 |
def list_containers(self, user, account, marker=None, limit=10000, until=None): |
|
106 |
"""Return a list of container (name, version_id) tuples existing under an account. |
|
107 |
|
|
108 |
Parameters: |
|
109 |
'marker': Start list from the next item after 'marker' |
|
110 |
'limit': Number of containers to return |
|
111 |
|
|
112 |
Raises: |
|
113 |
NotAllowedError: Operation not permitted |
|
114 |
""" |
|
115 |
return [] |
|
116 |
|
|
119 | 117 |
def get_container_meta(self, user, account, container, until=None): |
120 | 118 |
"""Return a dictionary with the container metadata. |
121 | 119 |
|
... | ... | |
145 | 143 |
""" |
146 | 144 |
return |
147 | 145 |
|
146 |
def get_container_policy(self, user, account, container): |
|
147 |
"""Return a dictionary with the container policy. |
|
148 |
|
|
149 |
The keys returned are: |
|
150 |
'quota': The maximum bytes allowed (default is 0 - unlimited) |
|
151 |
'versioning': Can be 'auto', 'manual' or 'none' (default is 'manual') |
|
152 |
|
|
153 |
Raises: |
|
154 |
NotAllowedError: Operation not permitted |
|
155 |
NameError: Container does not exist |
|
156 |
""" |
|
157 |
return {} |
|
158 |
|
|
159 |
def update_container_policy(self, user, account, container, policy, replace=False): |
|
160 |
"""Update the policy associated with the account. |
|
161 |
|
|
162 |
Raises: |
|
163 |
NotAllowedError: Operation not permitted |
|
164 |
NameError: Container does not exist |
|
165 |
ValueError: Invalid policy defined |
|
166 |
""" |
|
167 |
return |
|
168 |
|
|
169 |
def put_container(self, user, account, container, policy=None): |
|
170 |
"""Create a new container with the given name. |
|
171 |
|
|
172 |
Raises: |
|
173 |
NotAllowedError: Operation not permitted |
|
174 |
NameError: Container already exists |
|
175 |
ValueError: Invalid policy defined |
|
176 |
""" |
|
177 |
return |
|
178 |
|
|
179 |
def delete_container(self, user, account, container): |
|
180 |
"""Delete the container with the given name. |
|
181 |
|
|
182 |
Raises: |
|
183 |
NotAllowedError: Operation not permitted |
|
184 |
NameError: Container does not exist |
|
185 |
IndexError: Container is not empty |
|
186 |
""" |
|
187 |
return |
|
188 |
|
|
148 | 189 |
def list_objects(self, user, account, container, prefix='', delimiter=None, marker=None, limit=10000, virtual=True, keys=[], until=None): |
149 | 190 |
"""Return a list of object (name, version_id) tuples existing under a container. |
150 | 191 |
|
... | ... | |
237 | 278 |
""" |
238 | 279 |
return |
239 | 280 |
|
281 |
def get_object_public(self, user, account, container, name): |
|
282 |
"""Return the public URL of the object if applicable. |
|
283 |
|
|
284 |
Raises: |
|
285 |
NotAllowedError: Operation not permitted |
|
286 |
NameError: Container/object does not exist |
|
287 |
""" |
|
288 |
return None |
|
289 |
|
|
290 |
def update_object_public(self, user, account, container, name, public): |
|
291 |
"""Update the public status of the object. |
|
292 |
|
|
293 |
Parameters: |
|
294 |
'public': Boolean value |
|
295 |
|
|
296 |
Raises: |
|
297 |
NotAllowedError: Operation not permitted |
|
298 |
NameError: Container/object does not exist |
|
299 |
""" |
|
300 |
return |
|
301 |
|
|
240 | 302 |
def get_object_hashmap(self, user, account, container, name, version=None): |
241 | 303 |
"""Return the object's size and a list with partial hashes. |
242 | 304 |
|
b/pithos/backends/simple.py | ||
---|---|---|
80 | 80 |
sql = '''create table if not exists hashmaps ( |
81 | 81 |
version_id integer, pos integer, block_id text, primary key (version_id, pos))''' |
82 | 82 |
self.con.execute(sql) |
83 |
sql = '''create table if not exists groups ( |
|
84 |
account text, name text, users text, primary key (account, name))''' |
|
85 |
self.con.execute(sql) |
|
83 | 86 |
sql = '''create table if not exists permissions ( |
84 | 87 |
name text, read text, write text, primary key (name))''' |
85 | 88 |
self.con.execute(sql) |
89 |
sql = '''create table if not exists policy ( |
|
90 |
name text, key text, value text, primary key (name, key))''' |
|
91 |
self.con.execute(sql) |
|
86 | 92 |
self.con.commit() |
87 | 93 |
|
88 |
def delete_account(self, user, account): |
|
89 |
"""Delete the account with the given name.""" |
|
90 |
|
|
91 |
logger.debug("delete_account: %s", account) |
|
92 |
if user != account: |
|
93 |
raise NotAllowedError |
|
94 |
count, bytes, tstamp = self._get_pathstats(account) |
|
95 |
if count > 0: |
|
96 |
raise IndexError('Account is not empty') |
|
97 |
self._del_path(account) # Point of no return. |
|
98 |
|
|
99 | 94 |
def get_account_meta(self, user, account, until=None): |
100 | 95 |
"""Return a dictionary with the account metadata.""" |
101 | 96 |
|
... | ... | |
140 | 135 |
raise NotAllowedError |
141 | 136 |
self._put_metadata(user, account, meta, replace) |
142 | 137 |
|
143 |
def list_containers(self, user, account, marker=None, limit=10000, until=None):
|
|
144 |
"""Return a list of containers existing under an account."""
|
|
138 |
def get_account_groups(self, user, account):
|
|
139 |
"""Return a dictionary with the user groups defined for this account."""
|
|
145 | 140 |
|
146 |
logger.debug("list_containers: %s %s %s %s", account, marker, limit, until)
|
|
141 |
logger.debug("get_account_groups: %s", account)
|
|
147 | 142 |
if user != account: |
148 | 143 |
raise NotAllowedError |
149 |
return self._list_objects(account, '', '/', marker, limit, False, [], until)
|
|
144 |
return self._get_groups(account)
|
|
150 | 145 |
|
151 |
def put_container(self, user, account, container):
|
|
152 |
"""Create a new container with the given name."""
|
|
146 |
def update_account_groups(self, user, account, groups, replace=False):
|
|
147 |
"""Update the groups associated with the account."""
|
|
153 | 148 |
|
154 |
logger.debug("put_container: %s %s", account, container)
|
|
149 |
logger.debug("update_account_groups: %s %s %s", account, groups, replace)
|
|
155 | 150 |
if user != account: |
156 | 151 |
raise NotAllowedError |
157 |
try: |
|
158 |
path, version_id, mtime = self._get_containerinfo(account, container) |
|
159 |
except NameError: |
|
160 |
path = os.path.join(account, container) |
|
161 |
version_id = self._put_version(path, user) |
|
162 |
else: |
|
163 |
raise NameError('Container already exists') |
|
152 |
for k, v in groups.iteritems(): |
|
153 |
if True in [False or ',' in x for x in v]: |
|
154 |
raise ValueError('Bad characters in groups') |
|
155 |
if replace: |
|
156 |
sql = 'delete from groups where account = ?' |
|
157 |
self.con.execute(sql, (account,)) |
|
158 |
for k, v in groups.iteritems(): |
|
159 |
if len(v) == 0: |
|
160 |
if not replace: |
|
161 |
sql = 'delete from groups where account = ? and name = ?' |
|
162 |
self.con.execute(sql, (account, k)) |
|
163 |
else: |
|
164 |
sql = 'insert or replace into groups (account, name, users) values (?, ?, ?)' |
|
165 |
self.con.execute(sql, (account, k, ','.join(v))) |
|
166 |
self.con.commit() |
|
164 | 167 |
|
165 |
def delete_container(self, user, account, container):
|
|
166 |
"""Delete the container with the given name."""
|
|
168 |
def delete_account(self, user, account):
|
|
169 |
"""Delete the account with the given name."""
|
|
167 | 170 |
|
168 |
logger.debug("delete_container: %s %s", account, container)
|
|
171 |
logger.debug("delete_account: %s", account)
|
|
169 | 172 |
if user != account: |
170 | 173 |
raise NotAllowedError |
171 |
path, version_id, mtime = self._get_containerinfo(account, container) |
|
172 |
count, bytes, tstamp = self._get_pathstats(path) |
|
174 |
count, bytes, tstamp = self._get_pathstats(account) |
|
173 | 175 |
if count > 0: |
174 |
raise IndexError('Container is not empty') |
|
175 |
self._del_path(path) # Point of no return. |
|
176 |
self._copy_version(user, account, account, True, True) # New account version. |
|
176 |
raise IndexError('Account is not empty') |
|
177 |
self._del_path(account) # Point of no return. |
|
178 |
|
|
179 |
def list_containers(self, user, account, marker=None, limit=10000, until=None): |
|
180 |
"""Return a list of containers existing under an account.""" |
|
181 |
|
|
182 |
logger.debug("list_containers: %s %s %s %s", account, marker, limit, until) |
|
183 |
if user != account: |
|
184 |
raise NotAllowedError |
|
185 |
return self._list_objects(account, '', '/', marker, limit, False, [], until) |
|
177 | 186 |
|
178 | 187 |
def get_container_meta(self, user, account, container, until=None): |
179 | 188 |
"""Return a dictionary with the container metadata.""" |
... | ... | |
207 | 216 |
path, version_id, mtime = self._get_containerinfo(account, container) |
208 | 217 |
self._put_metadata(user, path, meta, replace) |
209 | 218 |
|
219 |
def get_container_policy(self, user, account, container): |
|
220 |
"""Return a dictionary with the container policy.""" |
|
221 |
|
|
222 |
logger.debug("get_container_policy: %s %s", account, container) |
|
223 |
return {} |
|
224 |
|
|
225 |
def update_container_policy(self, user, account, container, policy, replace=False): |
|
226 |
"""Update the policy associated with the account.""" |
|
227 |
|
|
228 |
logger.debug("update_container_policy: %s %s %s %s", account, container, policy, replace) |
|
229 |
return |
|
230 |
|
|
231 |
def put_container(self, user, account, container, policy=None): |
|
232 |
"""Create a new container with the given name.""" |
|
233 |
|
|
234 |
logger.debug("put_container: %s %s %s", account, container, policy) |
|
235 |
if user != account: |
|
236 |
raise NotAllowedError |
|
237 |
try: |
|
238 |
path, version_id, mtime = self._get_containerinfo(account, container) |
|
239 |
except NameError: |
|
240 |
path = os.path.join(account, container) |
|
241 |
version_id = self._put_version(path, user) |
|
242 |
else: |
|
243 |
raise NameError('Container already exists') |
|
244 |
|
|
245 |
def delete_container(self, user, account, container): |
|
246 |
"""Delete the container with the given name.""" |
|
247 |
|
|
248 |
logger.debug("delete_container: %s %s", account, container) |
|
249 |
if user != account: |
|
250 |
raise NotAllowedError |
|
251 |
path, version_id, mtime = self._get_containerinfo(account, container) |
|
252 |
count, bytes, tstamp = self._get_pathstats(path) |
|
253 |
if count > 0: |
|
254 |
raise IndexError('Container is not empty') |
|
255 |
self._del_path(path) # Point of no return. |
|
256 |
self._copy_version(user, account, account, True, True) # New account version. |
|
257 |
|
|
210 | 258 |
def list_objects(self, user, account, container, prefix='', delimiter=None, marker=None, limit=10000, virtual=True, keys=[], until=None): |
211 | 259 |
"""Return a list of objects existing under a container.""" |
212 | 260 |
|
... | ... | |
273 | 321 |
r, w = self._check_permissions(path, permissions) |
274 | 322 |
self._put_permissions(path, r, w) |
275 | 323 |
|
324 |
def get_object_public(self, user, account, container, name): |
|
325 |
"""Return the public URL of the object if applicable.""" |
|
326 |
|
|
327 |
logger.debug("get_object_public: %s %s %s", account, container, name) |
|
328 |
return None |
|
329 |
|
|
330 |
def update_object_public(self, user, account, container, name, public): |
|
331 |
"""Update the public status of the object.""" |
|
332 |
|
|
333 |
logger.debug("update_object_public: %s %s %s %s", account, container, name, public) |
|
334 |
return |
|
335 |
|
|
276 | 336 |
def get_object_hashmap(self, user, account, container, name, version=None): |
277 | 337 |
"""Return the object's size and a list with partial hashes.""" |
278 | 338 |
|
... | ... | |
524 | 584 |
self.con.execute(sql, (dest_version_id, k, v)) |
525 | 585 |
self.con.commit() |
526 | 586 |
|
587 |
def _get_groups(self, account): |
|
588 |
sql = 'select name, users from groups where account = ?' |
|
589 |
c = self.con.execute(sql, (account,)) |
|
590 |
return dict([(x[0], x[1].split(',')) for x in c.fetchall()]) |
|
591 |
|
|
527 | 592 |
def _is_allowed(self, user, account, container, name, op='read'): |
528 | 593 |
if user == account: |
529 | 594 |
return True |
530 | 595 |
path = os.path.join(account, container, name) |
531 | 596 |
perm_path, perms = self._get_permissions(path) |
597 |
|
|
598 |
# Expand groups. |
|
599 |
for x in ('read', 'write'): |
|
600 |
g_perms = [] |
|
601 |
for y in perms.get(x, []): |
|
602 |
if ':' in y: |
|
603 |
g_account, g_name = y.split(':', 1) |
|
604 |
groups = self._get_groups(g_account) |
|
605 |
if g_name in groups: |
|
606 |
g_perms += groups[g_name] |
|
607 |
else: |
|
608 |
g_perms.append(y) |
|
609 |
perms[x] = g_perms |
|
610 |
|
|
532 | 611 |
if op == 'read' and user in perms.get('read', []): |
533 | 612 |
return True |
534 | 613 |
if user in perms.get('write', []): |
b/pithos/backends/tests.py | ||
---|---|---|
43 | 43 |
def setUp(self): |
44 | 44 |
self.basepath = './test/content' |
45 | 45 |
self.b = SimpleBackend(self.basepath) |
46 |
self.account = 'account1'
|
|
46 |
self.account = 'test'
|
|
47 | 47 |
|
48 | 48 |
def tearDown(self): |
49 | 49 |
containers = [x[0] for x in self.b.list_containers('test', self.account)] |
... | ... | |
82 | 82 |
|
83 | 83 |
def test_get_account_meta(self): |
84 | 84 |
meta = { |
85 |
"name": "account1",
|
|
85 |
"name": "test",
|
|
86 | 86 |
"username": "aaitest@uth.gr", |
87 | 87 |
"email": "aaitest@uth.gr", |
88 | 88 |
"fileroot": "http://hostname/gss/rest/aaitest@uth.gr/files", |
... | ... | |
100 | 100 |
self.assertEquals(unicode(v), d[k]) |
101 | 101 |
|
102 | 102 |
def test_get_non_existing_account_meta(self): |
103 |
meta = self.b.get_account_meta('test', 'account2')
|
|
104 |
self.assertEquals(meta, {'name': 'account2', 'count': 0, 'bytes': 0})
|
|
103 |
meta = self.b.get_account_meta('account1', 'account1')
|
|
104 |
self.assertEquals(meta, {'name': 'account1', 'count': 0, 'bytes': 0})
|
|
105 | 105 |
|
106 | 106 |
def test_update_account_meta(self): |
107 | 107 |
meta = { |
108 |
"name": "account1",
|
|
108 |
"name": "test",
|
|
109 | 109 |
"username": "aaitest@uth.gr", |
110 | 110 |
"email": "aaitest@uth.gr", |
111 | 111 |
"fileroot": "http://hostname/gss/rest/aaitest@uth.gr/files", |
... | ... | |
130 | 130 |
def setUp(self): |
131 | 131 |
self.basepath = './test/content' |
132 | 132 |
self.b = SimpleBackend(self.basepath) |
133 |
self.account = 'account1'
|
|
133 |
self.account = 'test'
|
|
134 | 134 |
|
135 | 135 |
def tearDown(self): |
136 | 136 |
containers = [x[0] for x in self.b.list_containers('test', self.account)] |
... | ... | |
143 | 143 |
self.b.delete_container('test', self.account, container) |
144 | 144 |
|
145 | 145 |
def test_list_non_existing_account_objects(self): |
146 |
self.assertRaises(NameError, self.b.list_objects, 'test', 'account2', 'container1')
|
|
146 |
self.assertRaises(NameError, self.b.list_objects, 'test', 'test', 'container1')
|
|
147 | 147 |
|
148 | 148 |
def test_list_objects(self): |
149 | 149 |
self.b.put_container('test', self.account, 'container1') |
... | ... | |
245 | 245 |
def setUp(self): |
246 | 246 |
self.basepath = './test/content' |
247 | 247 |
self.b = SimpleBackend(self.basepath) |
248 |
self.account = 'account1'
|
|
248 |
self.account = 'test'
|
|
249 | 249 |
|
250 | 250 |
def tearDown(self): |
251 | 251 |
containers = [x[0] for x in self.b.list_containers('test', self.account)] |
... | ... | |
292 | 292 |
self.assertRaises(NameError, |
293 | 293 |
self.b.copy_object, |
294 | 294 |
'test', |
295 |
'account',
|
|
295 |
'test',
|
|
296 | 296 |
src_cname, |
297 | 297 |
src_obj, |
298 | 298 |
dest_cname, |
Also available in: Unified diff