Statistics
| Branch: | Tag: | Revision:

root / snf-django-lib / snf_django / lib / api / __init__.py @ 6c1c0738

History | View | Annotate | Download (7.6 kB)

1
# Copyright 2012, 2013 GRNET S.A. All rights reserved.
2
#
3
# Redistribution and use in source and binary forms, with or
4
# without modification, are permitted provided that the following
5
# conditions are met:
6
#
7
#   1. Redistributions of source code must retain the above
8
#      copyright notice, this list of conditions and the following
9
#      disclaimer.
10
#
11
#   2. Redistributions in binary form must reproduce the above
12
#      copyright notice, this list of conditions and the following
13
#      disclaimer in the documentation and/or other materials
14
#      provided with the distribution.
15
#
16
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
# POSSIBILITY OF SUCH DAMAGE.
28
#
29
# The views and conclusions contained in the software and
30
# documentation are those of the authors and should not be
31
# interpreted as representing official policies, either expressed
32
# or implied, of GRNET S.A.
33

    
34
from functools import wraps
35
from traceback import format_exc
36
from time import time
37
from logging import getLogger
38
from wsgiref.handlers import format_date_time
39

    
40
from django.http import HttpResponse
41
from django.utils import cache
42
from django.utils import simplejson as json
43
from django.template.loader import render_to_string
44

    
45
from astakosclient import AstakosClient
46
from astakosclient.errors import AstakosClientException
47
from django.conf import settings
48
from snf_django.lib.api import faults
49

    
50

    
51
log = getLogger(__name__)
52

    
53

    
54
def get_token(request):
55
    """Get the Authentication Token of a request."""
56
    token = request.GET.get("X-Auth-Token", None)
57
    if not token:
58
        token = request.META.get("HTTP_X_AUTH_TOKEN", None)
59
    return token
60

    
61

    
62
def api_method(http_method=None, token_required=True, user_required=True,
63
               logger=None, format_allowed=True):
64
    """Decorator function for views that implement an API method."""
65
    if not logger:
66
        logger = log
67

    
68
    def decorator(func):
69
        @wraps(func)
70
        def wrapper(request, *args, **kwargs):
71
            try:
72
                # Get the requested serialization format
73
                request.serialization = get_serialization(request,
74
                                                          format_allowed)
75

    
76
                # Check HTTP method
77
                if http_method and request.method != http_method:
78
                    raise faults.BadRequest("Method not allowed")
79

    
80
                # Get authentication token
81
                request.x_auth_token = None
82
                if token_required or user_required:
83
                    token = get_token(request)
84
                    if not token:
85
                        msg = "Access denied. No authentication token"
86
                        raise faults.Unauthorized(msg)
87
                    request.x_auth_token = token
88

    
89
                # Authenticate
90
                if user_required:
91
                    assert(token_required), "Can not get user without token"
92
                    try:
93
                        astakos = AstakosClient(settings.ASTAKOS_URL,
94
                                                use_pool=True,
95
                                                logger=logger)
96
                        user_info = astakos.get_user_info(token)
97
                    except AstakosClientException as err:
98
                        raise faults.Fault(message=err.message,
99
                                           details=err.details,
100
                                           code=err.status)
101
                    request.user_uniq = user_info["uuid"]
102
                    request.user = user_info
103

    
104
                # Get the response object
105
                response = func(request, *args, **kwargs)
106

    
107
                # Fill in response variables
108
                update_response_headers(request, response)
109
                return response
110
            except faults.Fault, fault:
111
                if fault.code >= 500:
112
                    logger.exception("API ERROR")
113
                return render_fault(request, fault)
114
            except:
115
                logger.exception("Unexpected ERROR")
116
                fault = faults.InternalServerError("Unexpected ERROR")
117
                return render_fault(request, fault)
118
        return wrapper
119
    return decorator
120

    
121

    
122
def get_serialization(request, format_allowed=True):
123
    """Return the serialization format requested.
124

125
    Valid formats are 'json' and 'xml' and 'text'
126
    """
127

    
128
    if not format_allowed:
129
        return "text"
130

    
131
    # Try to get serialization from 'format' parameter
132
    _format = request.GET.get("format")
133
    if _format:
134
        if _format == "json":
135
            return "json"
136
        elif _format == "xml":
137
            return "xml"
138

    
139
    # Try to get serialization from path
140
    path = request.path
141
    if path.endswith(".json"):
142
        return "json"
143
    elif path.endswith(".xml"):
144
        return "xml"
145

    
146
    for item in request.META.get("HTTP_ACCEPT", "").split(","):
147
        accept, sep, rest = item.strip().partition(";")
148
        if accept == "application/json":
149
            return "json"
150
        elif accept == "applcation/xml":
151
            return "xml"
152

    
153
    return "json"
154

    
155

    
156
def update_response_headers(request, response):
157
    if not response.has_header("Content-Type"):
158
        serialization = request.serialization
159
        if serialization == "xml":
160
            response["Content-Type"] = "application/xml; charset=UTF-8"
161
        elif serialization == "json":
162
            response["Content-Type"] = "application/json; charset=UTF-8"
163
        elif serialization == "text":
164
            response["Content-Type"] = "text/plain; charset=UTF-8"
165
        else:
166
            raise ValueError("Unknown serialization format '%s'" %
167
                             serialization)
168

    
169
    if settings.DEBUG or settings.TEST:
170
        response["Date"] = format_date_time(time())
171

    
172
    if not response.has_header("Content-Length"):
173
        response["Content-Length"] = len(response.content)
174

    
175
    cache.add_never_cache_headers(response)
176
    # Fix Vary and Cache-Control Headers. Issue: #3448
177
    cache.patch_vary_headers(response, ('X-Auth-Token',))
178
    cache.patch_cache_control(response, no_cache=True, no_store=True,
179
                              must_revalidate=True)
180

    
181

    
182
def render_fault(request, fault):
183
    """Render an API fault to an HTTP response."""
184
    # If running in debug mode add exception information to fault details
185
    if settings.DEBUG or settings.TEST:
186
        fault.details = format_exc()
187

    
188
    try:
189
        serialization = request.serialization
190
    except AttributeError:
191
        request.serialization = "json"
192
        serialization = "json"
193

    
194
    # Serialize the fault data to xml or json
195
    if serialization == "xml":
196
        data = render_to_string("fault.xml", {"fault": fault})
197
    else:
198
        d = {fault.name: {"code": fault.code,
199
                          "message": fault.message,
200
                          "details": fault.details}
201
            }
202
        data = json.dumps(d)
203

    
204
    response = HttpResponse(data, status=fault.code)
205
    update_response_headers(request, response)
206
    return response
207

    
208

    
209
def not_found(request):
210
    raise faults.BadRequest('Not found.')
211

    
212

    
213
def method_not_allowed(request):
214
    raise faults.BadRequest('Method not allowed')