Revision 65bbcd43

b/snf-cyclades-app/synnefo/plankton/utils.py
35 35
from synnefo.plankton.backend import ImageBackend
36 36
from contextlib import contextmanager
37 37

  
38

  
39 38
def plankton_method(func):
40 39
    """Decorator function for API methods using ImageBackend.
41 40

  
b/snf-pithos-app/pithos/api/functions.py
32 32
# or implied, of GRNET S.A.
33 33

  
34 34
from xml.dom import minidom
35
from urllib import unquote
36 35

  
37
from django.conf import settings
38 36
from django.http import HttpResponse
39 37
from django.template.loader import render_to_string
40 38
from django.utils import simplejson as json
......
44 42

  
45 43
from synnefo.lib.astakos import get_user, get_uuids as _get_uuids
46 44

  
45
from snf_django.lib import api
47 46
from snf_django.lib.api import faults
48 47

  
49 48
from pithos.api.util import (
......
57 56
    get_content_range, socket_read_iterator, SaveToBackendHandler,
58 57
    object_data_response, put_object_block, hashmap_md5, simple_list_response,
59 58
    api_method, is_uuid,
60
    retrieve_uuid, retrieve_displayname, retrieve_uuids, retrieve_displaynames
59
    retrieve_uuid, retrieve_displayname, retrieve_uuids, retrieve_displaynames,
60
    get_pithos_usage
61 61
)
62 62

  
63 63
from pithos.api.settings import (UPDATE_MD5, TRANSLATE_UUIDS,
......
70 70

  
71 71
from pithos.backends.filter import parse_filters
72 72

  
73
import logging
74 73
import hashlib
75 74

  
75
import logging
76 76
logger = logging.getLogger(__name__)
77 77

  
78 78

  
......
102 102
                return authenticate(request)
103 103
        return account_list(request)
104 104
    else:
105
        return method_not_allowed(request)
105
        return api.method_not_allowed(request)
106 106

  
107 107

  
108 108
@csrf_exempt
......
121 121
    elif request.method == 'GET':
122 122
        return container_list(request, v_account)
123 123
    else:
124
        return method_not_allowed(request)
124
        return api.method_not_allowed(request)
125 125

  
126 126

  
127 127
@csrf_exempt
......
144 144
    elif request.method == 'GET':
145 145
        return object_list(request, v_account, v_container)
146 146
    else:
147
        return method_not_allowed(request)
147
        return api.method_not_allowed(request)
148 148

  
149 149

  
150 150
@csrf_exempt
......
174 174
    elif request.method == 'DELETE':
175 175
        return object_delete(request, v_account, v_container, v_object)
176 176
    else:
177
        return method_not_allowed(request)
177
        return api.method_not_allowed(request)
178 178

  
179 179

  
180
@api_method('GET', user_required=False)
180
@api_method('GET', user_required=False, logger=logger)
181 181
def authenticate(request):
182 182
    # Normal Response Codes: 204
183 183
    # Error Response Codes: internalServerError (500),
......
200 200
    return response
201 201

  
202 202

  
203
@api_method('GET', format_allowed=True, request_usage=True)
203
@api_method('GET', format_allowed=True, user_required=True, logger=logger)
204 204
def account_list(request):
205 205
    # Normal Response Codes: 200, 204
206 206
    # Error Response Codes: internalServerError (500),
......
230 230
    for x in accounts:
231 231
        if x == request.user_uniq:
232 232
            continue
233
        usage = get_pithos_usage(request.x_auth_token)
233 234
        try:
234 235
            meta = request.backend.get_account_meta(
235 236
                request.user_uniq, x, 'pithos', include_user_defined=False,
236
                external_quota=request.user_usage)
237
                external_quota=usage)
237 238
            groups = request.backend.get_account_groups(request.user_uniq, x)
238 239
        except NotAllowedError:
239 240
            raise faults.Forbidden('Not allowed')
......
262 263
    return response
263 264

  
264 265

  
265
@api_method('HEAD', request_usage=True)
266
@api_method('HEAD', user_required=True, logger=logger)
266 267
def account_meta(request, v_account):
267 268
    # Normal Response Codes: 204
268 269
    # Error Response Codes: internalServerError (500),
......
270 271
    #                       badRequest (400)
271 272

  
272 273
    until = get_int_parameter(request.GET.get('until'))
274
    usage = get_pithos_usage(request.x_auth_token)
273 275
    try:
274 276
        meta = request.backend.get_account_meta(
275 277
            request.user_uniq, v_account, 'pithos', until,
276
            external_quota=request.user_usage)
278
            external_quota=usage)
277 279
        groups = request.backend.get_account_groups(
278 280
            request.user_uniq, v_account)
279 281

  
......
282 284
                groups[k] = retrieve_displaynames(
283 285
                        getattr(request, 'token', None), groups[k])
284 286
        policy = request.backend.get_account_policy(
285
            request.user_uniq, v_account, external_quota=request.user_usage)
287
            request.user_uniq, v_account, external_quota=usage)
286 288
    except NotAllowedError:
287 289
        raise faults.Forbidden('Not allowed')
288 290

  
......
293 295
    return response
294 296

  
295 297

  
296
@api_method('POST')
298
@api_method('POST', user_required=True, logger=logger)
297 299
def account_update(request, v_account):
298 300
    # Normal Response Codes: 202
299 301
    # Error Response Codes: internalServerError (500),
......
340 342
    return HttpResponse(status=202)
341 343

  
342 344

  
343
@api_method('GET', format_allowed=True, request_usage=True)
345
@api_method('GET', format_allowed=True, user_required=True, logger=logger)
344 346
def container_list(request, v_account):
345 347
    # Normal Response Codes: 200, 204
346 348
    # Error Response Codes: internalServerError (500),
......
349 351
    #                       badRequest (400)
350 352

  
351 353
    until = get_int_parameter(request.GET.get('until'))
354
    usage = get_pithos_usage(request.x_auth_token)
352 355
    try:
353 356
        meta = request.backend.get_account_meta(
354 357
            request.user_uniq, v_account, 'pithos', until,
355
            external_quota=request.user_usage)
358
            external_quota=usage)
356 359
        groups = request.backend.get_account_groups(
357 360
            request.user_uniq, v_account)
358 361
        policy = request.backend.get_account_policy(
359
            request.user_uniq, v_account, external_quota=request.user_usage)
362
            request.user_uniq, v_account, external_quota=usage)
360 363
    except NotAllowedError:
361 364
        raise faults.Forbidden('Not allowed')
362 365

  
......
425 428
    return response
426 429

  
427 430

  
428
@api_method('HEAD')
431
@api_method('HEAD', user_required=True, logger=logger)
429 432
def container_meta(request, v_account, v_container):
430 433
    # Normal Response Codes: 204
431 434
    # Error Response Codes: internalServerError (500),
......
454 457
    return response
455 458

  
456 459

  
457
@api_method('PUT')
460
@api_method('PUT', user_required=True, logger=logger)
458 461
def container_create(request, v_account, v_container):
459 462
    # Normal Response Codes: 201, 202
460 463
    # Error Response Codes: internalServerError (500),
......
498 501
    return HttpResponse(status=ret)
499 502

  
500 503

  
501
@api_method('POST', format_allowed=True)
504
@api_method('POST', format_allowed=True, user_required=True, logger=logger)
502 505
def container_update(request, v_account, v_container):
503 506
    # Normal Response Codes: 202
504 507
    # Error Response Codes: internalServerError (500),
......
549 552
    return response
550 553

  
551 554

  
552
@api_method('DELETE')
555
@api_method('DELETE', user_required=True, logger=logger)
553 556
def container_delete(request, v_account, v_container):
554 557
    # Normal Response Codes: 204
555 558
    # Error Response Codes: internalServerError (500),
......
578 581
    return HttpResponse(status=204)
579 582

  
580 583

  
581
@api_method('GET', format_allowed=True)
584
@api_method('GET', format_allowed=True, user_required=True, logger=logger)
582 585
def object_list(request, v_account, v_container):
583 586
    # Normal Response Codes: 200, 204
584 587
    # Error Response Codes: internalServerError (500),
......
746 749
    return response
747 750

  
748 751

  
749
@api_method('HEAD')
752
@api_method('HEAD', user_required=True, logger=logger)
750 753
def object_meta(request, v_account, v_container, v_object):
751 754
    # Normal Response Codes: 204
752 755
    # Error Response Codes: internalServerError (500),
......
795 798
    return response
796 799

  
797 800

  
798
@api_method('GET', format_allowed=True)
801
@api_method('GET', format_allowed=True, user_required=True, logger=logger)
799 802
def object_read(request, v_account, v_container, v_object):
800 803
    # Normal Response Codes: 200, 206
801 804
    # Error Response Codes: internalServerError (500),
......
937 940
    return object_data_response(request, sizes, hashmaps, meta)
938 941

  
939 942

  
940
@api_method('PUT', format_allowed=True)
943
@api_method('PUT', format_allowed=True, user_required=True, logger=logger)
941 944
def object_write(request, v_account, v_container, v_object):
942 945
    # Normal Response Codes: 201
943 946
    # Error Response Codes: internalServerError (500),
......
1096 1099
    return response
1097 1100

  
1098 1101

  
1099
@api_method('POST')
1102
@api_method('POST', user_required=True, logger=logger)
1100 1103
def object_write_form(request, v_account, v_container, v_object):
1101 1104
    # Normal Response Codes: 201
1102 1105
    # Error Response Codes: internalServerError (500),
......
1129 1132
    return response
1130 1133

  
1131 1134

  
1132
@api_method('COPY', format_allowed=True)
1135
@api_method('COPY', format_allowed=True, user_required=True, logger=logger)
1133 1136
def object_copy(request, v_account, v_container, v_object):
1134 1137
    # Normal Response Codes: 201
1135 1138
    # Error Response Codes: internalServerError (500),
......
1171 1174
    return response
1172 1175

  
1173 1176

  
1174
@api_method('MOVE', format_allowed=True)
1177
@api_method('MOVE', format_allowed=True, user_required=True, logger=logger)
1175 1178
def object_move(request, v_account, v_container, v_object):
1176 1179
    # Normal Response Codes: 201
1177 1180
    # Error Response Codes: internalServerError (500),
......
1212 1215
    return response
1213 1216

  
1214 1217

  
1215
@api_method('POST', format_allowed=True)
1218
@api_method('POST', format_allowed=True, user_required=True, logger=logger)
1216 1219
def object_update(request, v_account, v_container, v_object):
1217 1220
    # Normal Response Codes: 202, 204
1218 1221
    # Error Response Codes: internalServerError (500),
......
1423 1426
    return response
1424 1427

  
1425 1428

  
1426
@api_method('DELETE')
1429
@api_method('DELETE', user_required=True, logger=logger)
1427 1430
def object_delete(request, v_account, v_container, v_object):
1428 1431
    # Normal Response Codes: 204
1429 1432
    # Error Response Codes: internalServerError (500),
......
1446 1449
    except QuotaError, e:
1447 1450
        raise faults.RequestEntityTooLarge('Quota error: %s' % e)
1448 1451
    return HttpResponse(status=204)
1449

  
1450

  
1451
@api_method()
1452
def method_not_allowed(request):
1453
    raise faults.BadRequest('Method not allowed')
b/snf-pithos-app/pithos/api/public.py
1
# Copyright 2011-2012 GRNET S.A. All rights reserved.
1
# Copyright 2011-2013 GRNET S.A. All rights reserved.
2 2
#
3 3
# Redistribution and use in source and binary forms, with or
4 4
# without modification, are permitted provided that the following
......
31 31
# interpreted as representing official policies, either expressed
32 32
# or implied, of GRNET S.A.
33 33

  
34
import logging
35

  
36 34
from django.http import HttpResponse
37 35
from django.views.decorators.csrf import csrf_exempt
38 36

  
39
from synnefo.lib.astakos import get_user
37
from snf_django.lib import api
40 38
from snf_django.lib.api import faults
41 39

  
42 40
from pithos.api.util import (put_object_headers, update_manifest_meta,
......
44 42
                             validate_matching_preconditions,
45 43
                             object_data_response, api_method,
46 44
                             split_container_object_string)
47
from pithos.api.settings import AUTHENTICATION_URL, AUTHENTICATION_USERS
48

  
49 45

  
46
import logging
50 47
logger = logging.getLogger(__name__)
51 48

  
52 49

  
53 50
@csrf_exempt
54 51
def public_demux(request, v_public):
55
    get_user(request, AUTHENTICATION_URL, AUTHENTICATION_USERS)
56 52
    if request.method == 'HEAD':
57 53
        return public_meta(request, v_public)
58 54
    elif request.method == 'GET':
59 55
        return public_read(request, v_public)
60 56
    else:
61
        return method_not_allowed(request)
57
        return api.method_not_allowed(request)
62 58

  
63 59

  
64
@api_method('HEAD', user_required=False)
60
@api_method(http_method="HEAD", user_required=False, logger=logger)
65 61
def public_meta(request, v_public):
66 62
    # Normal Response Codes: 204
67 63
    # Error Response Codes: internalServerError (500),
......
89 85
    return response
90 86

  
91 87

  
92
@api_method('GET', user_required=False)
88
@api_method(http_method="GET", user_required=False, logger=logger)
93 89
def public_read(request, v_public):
94 90
    # Normal Response Codes: 200, 206
95 91
    # Error Response Codes: internalServerError (500),
......
139 135
        try:
140 136
            for x in objects:
141 137
                s, h = request.backend.get_object_hashmap(request.user_uniq,
142
                                                          v_account, src_container, x[0], x[1])
138
                                                          v_account,
139
                                                          src_container,
140
                                                          x[0], x[1])
143 141
                sizes.append(s)
144 142
                hashmaps.append(h)
145 143
        except:
......
161 159
        meta['Content-Disposition'] = 'attachment; filename=%s' % (name,)
162 160

  
163 161
    return object_data_response(request, sizes, hashmaps, meta, True)
164

  
165

  
166
@api_method(user_required=False)
167
def method_not_allowed(request, **v_args):
168
    raise faults.ItemNotFound('Object does not exist')
b/snf-pithos-app/pithos/api/util.py
32 32
# or implied, of GRNET S.A.
33 33

  
34 34
from functools import wraps
35
from time import time
36 35
from traceback import format_exc
37
from wsgiref.handlers import format_date_time
38
from binascii import hexlify, unhexlify
39
from datetime import datetime, tzinfo, timedelta
36
from datetime import datetime
40 37
from urllib import quote, unquote
41 38

  
42 39
from django.conf import settings
......
49 46
from django.core.files.uploadedfile import UploadedFile
50 47

  
51 48
from synnefo.lib.parsedate import parse_http_date_safe, parse_http_date
52
from synnefo.lib.astakos import get_user
53
from snf_django.lib.api import faults
49
from synnefo.lib.astakos import user_for_token
50
from snf_django.lib import api
51
from snf_django.lib.api import faults, utils
54 52

  
55 53
from pithos.api.settings import (BACKEND_DB_MODULE, BACKEND_DB_CONNECTION,
56 54
                                 BACKEND_BLOCK_MODULE, BACKEND_BLOCK_PATH,
......
67 65
                                 RADOS_POOL_MAPS, TRANSLATE_UUIDS,
68 66
                                 PUBLIC_URL_SECURITY,
69 67
                                 PUBLIC_URL_ALPHABET)
70
from pithos.backends import connect_backend
71 68
from pithos.backends.base import (NotAllowedError, QuotaError, ItemNotExists,
72 69
                                  VersionNotExists)
73 70
from synnefo.lib.astakos import (get_user_uuid, get_displayname,
......
82 79
logger = logging.getLogger(__name__)
83 80

  
84 81

  
85
class UTC(tzinfo):
86
    def utcoffset(self, dt):
87
        return timedelta(0)
88

  
89
    def tzname(self, dt):
90
        return 'UTC'
91

  
92
    def dst(self, dt):
93
        return timedelta(0)
94

  
95

  
96 82
def json_encode_decimal(obj):
97 83
    if isinstance(obj, decimal.Decimal):
98 84
        return str(obj)
99 85
    raise TypeError(repr(obj) + " is not JSON serializable")
100 86

  
101 87

  
102
def isoformat(d):
103
    """Return an ISO8601 date string that includes a timezone."""
104

  
105
    return d.replace(tzinfo=UTC()).isoformat()
106

  
107

  
108 88
def rename_meta_key(d, old, new):
109 89
    if old not in d:
110 90
        return
......
120 100
    """
121 101

  
122 102
    if 'last_modified' in d and d['last_modified']:
123
        d['last_modified'] = isoformat(
103
        d['last_modified'] = utils.isoformat(
124 104
            datetime.fromtimestamp(d['last_modified']))
125 105
    return dict([(k.lower().replace('-', '_'), v) for k, v in d.iteritems()])
126 106

  
......
985 965
        public_url_security=PUBLIC_URL_SECURITY,
986 966
        public_url_alphabet=PUBLIC_URL_ALPHABET)
987 967

  
968

  
988 969
def get_backend():
989 970
    backend = _pithos_backend_pool.pool_get()
990 971
    backend.default_policy['quota'] = BACKEND_QUOTA
......
1010 991

  
1011 992

  
1012 993
def update_response_headers(request, response):
1013
    if request.serialization == 'xml':
1014
        response['Content-Type'] = 'application/xml; charset=UTF-8'
1015
    elif request.serialization == 'json':
1016
        response['Content-Type'] = 'application/json; charset=UTF-8'
1017
    elif not response['Content-Type']:
1018
        response['Content-Type'] = 'text/plain; charset=UTF-8'
1019

  
1020 994
    if (not response.has_header('Content-Length') and
1021 995
        not (response.has_header('Content-Type') and
1022 996
             response['Content-Type'].startswith('multipart/byteranges'))):
......
1031 1005
            response[quote(k)] = quote(v, safe='/=,:@; ')
1032 1006

  
1033 1007

  
1034
def render_fault(request, fault):
1035
    if isinstance(fault, faults.InternalServerError) and settings.DEBUG:
1036
        fault.details = format_exc(fault)
1037

  
1038
    request.serialization = 'text'
1039
    data = fault.message + '\n'
1040
    if fault.details:
1041
        data += '\n' + fault.details
1042
    response = HttpResponse(data, status=fault.code)
1043
    update_response_headers(request, response)
1044
    return response
1045

  
1046

  
1047
def request_serialization(request, format_allowed=False):
1048
    """Return the serialization format requested.
1049

  
1050
    Valid formats are 'text' and 'json', 'xml' if 'format_allowed' is True.
1051
    """
1052

  
1053
    if not format_allowed:
1054
        return 'text'
1055

  
1056
    format = request.GET.get('format')
1057
    if format == 'json':
1058
        return 'json'
1059
    elif format == 'xml':
1060
        return 'xml'
1061

  
1062
    for item in request.META.get('HTTP_ACCEPT', '').split(','):
1063
        accept, sep, rest = item.strip().partition(';')
1064
        if accept == 'application/json':
1065
            return 'json'
1066
        elif accept == 'application/xml' or accept == 'text/xml':
1067
            return 'xml'
1068

  
1069
    return 'text'
1070

  
1071
def get_pithos_usage(usage):
1008
def get_pithos_usage(token):
1009
    """Get Pithos Usage from astakos."""
1010
    user_info = user_for_token(token, AUTHENTICATION_URL, AUTHENTICATION_USERS,
1011
                               usage=True)
1012
    usage = user_info.get("usage", [])
1072 1013
    for u in usage:
1073 1014
        if u.get('name') == 'pithos+.diskspace':
1074 1015
            return u
1075 1016

  
1076
def api_method(http_method=None, format_allowed=False, user_required=True,
1077
        request_usage=False):
1078
    """Decorator function for views that implement an API method."""
1079 1017

  
1018
def api_method(http_method=None, user_required=True, logger=None,
1019
               format_allowed=False):
1080 1020
    def decorator(func):
1021
        @api.api_method(http_method=http_method, user_required=user_required,
1022
                          logger=logger, format_allowed=format_allowed)
1081 1023
        @wraps(func)
1082 1024
        def wrapper(request, *args, **kwargs):
1083
            try:
1084
                if http_method and request.method != http_method:
1085
                    raise faults.BadRequest('Method not allowed.')
1086

  
1087
                if user_required:
1088
                    token = None
1089
                    if request.method in ('HEAD', 'GET') and COOKIE_NAME in request.COOKIES:
1090
                        cookie_value = unquote(
1091
                            request.COOKIES.get(COOKIE_NAME, ''))
1092
                        account, sep, token = cookie_value.partition('|')
1093
                    get_user(request,
1094
                             AUTHENTICATION_URL,
1095
                             AUTHENTICATION_USERS,
1096
                             token,
1097
                             request_usage)
1098
                    if  getattr(request, 'user', None) is None:
1099
                        raise faults.Unauthorized('Access denied')
1100
                    assert getattr(request, 'user_uniq', None) != None
1101
                    request.user_usage = get_pithos_usage(request.user.get('usage', []))
1102
                    request.token = request.GET.get('X-Auth-Token', request.META.get('HTTP_X_AUTH_TOKEN', token))
1103

  
1104
                # The args variable may contain up to (account, container, object).
1105
                if len(args) > 1 and len(args[1]) > 256:
1106
                    raise faults.BadRequest('Container name too large.')
1107
                if len(args) > 2 and len(args[2]) > 1024:
1108
                    raise faults.BadRequest('Object name too large.')
1109

  
1110
                # Format and check headers.
1111
                update_request_headers(request)
1025
            # The args variable may contain up to (account, container, object).
1026
            if len(args) > 1 and len(args[1]) > 256:
1027
                raise faults.BadRequest("Container name too large")
1028
            if len(args) > 2 and len(args[2]) > 1024:
1029
                raise faults.BadRequest('Object name too large.')
1112 1030

  
1113
                # Fill in custom request variables.
1114
                request.serialization = request_serialization(
1115
                    request, format_allowed)
1031
            try:
1032
                # Add a PithosBackend as attribute of the request object
1116 1033
                request.backend = get_backend()
1117

  
1034
                # Many API method expect thet X-Auth-Token in request,token
1035
                request.token = request.x_auth_token
1036
                update_request_headers(request)
1118 1037
                response = func(request, *args, **kwargs)
1119 1038
                update_response_headers(request, response)
1120 1039
                return response
1121
            except faults.Fault, fault:
1122
                if fault.code >= 500:
1123
                    logger.exception("API Fault")
1124
                return render_fault(request, fault)
1125
            except BaseException, e:
1126
                logger.exception('Unexpected error: %s' % e)
1127
                fault = faults.InternalServerError('Unexpected error')
1128
                return render_fault(request, fault)
1129 1040
            finally:
1130
                if getattr(request, 'backend', None) is not None:
1041
                # Always close PithosBackend connection
1042
                if getattr(request, "backend", None) is not None:
1131 1043
                    request.backend.close()
1132 1044
        return wrapper
1133 1045
    return decorator

Also available in: Unified diff