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