Statistics
| Branch: | Tag: | Revision:

root / snf-django-lib / snf_django / lib / api / __init__.py @ 726cb37f

History | View | Annotate | Download (7.8 kB)

1 72bf812d Christos Stavrakakis
# Copyright 2012, 2013 GRNET S.A. All rights reserved.
2 72bf812d Christos Stavrakakis
#
3 72bf812d Christos Stavrakakis
# Redistribution and use in source and binary forms, with or
4 72bf812d Christos Stavrakakis
# without modification, are permitted provided that the following
5 72bf812d Christos Stavrakakis
# conditions are met:
6 72bf812d Christos Stavrakakis
#
7 72bf812d Christos Stavrakakis
#   1. Redistributions of source code must retain the above
8 72bf812d Christos Stavrakakis
#      copyright notice, this list of conditions and the following
9 72bf812d Christos Stavrakakis
#      disclaimer.
10 72bf812d Christos Stavrakakis
#
11 72bf812d Christos Stavrakakis
#   2. Redistributions in binary form must reproduce the above
12 72bf812d Christos Stavrakakis
#      copyright notice, this list of conditions and the following
13 72bf812d Christos Stavrakakis
#      disclaimer in the documentation and/or other materials
14 72bf812d Christos Stavrakakis
#      provided with the distribution.
15 72bf812d Christos Stavrakakis
#
16 72bf812d Christos Stavrakakis
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17 72bf812d Christos Stavrakakis
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 72bf812d Christos Stavrakakis
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19 72bf812d Christos Stavrakakis
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20 72bf812d Christos Stavrakakis
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21 72bf812d Christos Stavrakakis
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22 72bf812d Christos Stavrakakis
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23 72bf812d Christos Stavrakakis
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24 72bf812d Christos Stavrakakis
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 72bf812d Christos Stavrakakis
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26 72bf812d Christos Stavrakakis
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 72bf812d Christos Stavrakakis
# POSSIBILITY OF SUCH DAMAGE.
28 72bf812d Christos Stavrakakis
#
29 72bf812d Christos Stavrakakis
# The views and conclusions contained in the software and
30 72bf812d Christos Stavrakakis
# documentation are those of the authors and should not be
31 72bf812d Christos Stavrakakis
# interpreted as representing official policies, either expressed
32 72bf812d Christos Stavrakakis
# or implied, of GRNET S.A.
33 72bf812d Christos Stavrakakis
34 72bf812d Christos Stavrakakis
from functools import wraps
35 72bf812d Christos Stavrakakis
from traceback import format_exc
36 72bf812d Christos Stavrakakis
from time import time
37 72bf812d Christos Stavrakakis
from logging import getLogger
38 72bf812d Christos Stavrakakis
from wsgiref.handlers import format_date_time
39 72bf812d Christos Stavrakakis
40 72bf812d Christos Stavrakakis
from django.http import HttpResponse
41 72bf812d Christos Stavrakakis
from django.utils import cache
42 72bf812d Christos Stavrakakis
from django.utils import simplejson as json
43 72bf812d Christos Stavrakakis
from django.template.loader import render_to_string
44 72bf812d Christos Stavrakakis
45 72bf812d Christos Stavrakakis
from astakosclient import AstakosClient
46 6c1c0738 Ilias Tsitsimpis
from astakosclient.errors import AstakosClientException
47 72bf812d Christos Stavrakakis
from django.conf import settings
48 72bf812d Christos Stavrakakis
from snf_django.lib.api import faults
49 72bf812d Christos Stavrakakis
50 72bf812d Christos Stavrakakis
51 72bf812d Christos Stavrakakis
log = getLogger(__name__)
52 72bf812d Christos Stavrakakis
53 72bf812d Christos Stavrakakis
54 72bf812d Christos Stavrakakis
def get_token(request):
55 72bf812d Christos Stavrakakis
    """Get the Authentication Token of a request."""
56 72bf812d Christos Stavrakakis
    token = request.GET.get("X-Auth-Token", None)
57 72bf812d Christos Stavrakakis
    if not token:
58 72bf812d Christos Stavrakakis
        token = request.META.get("HTTP_X_AUTH_TOKEN", None)
59 72bf812d Christos Stavrakakis
    return token
60 72bf812d Christos Stavrakakis
61 72bf812d Christos Stavrakakis
62 72bf812d Christos Stavrakakis
def api_method(http_method=None, token_required=True, user_required=True,
63 47ef53d5 Christos Stavrakakis
               logger=None, format_allowed=True, astakos_url=None):
64 72bf812d Christos Stavrakakis
    """Decorator function for views that implement an API method."""
65 72bf812d Christos Stavrakakis
    if not logger:
66 72bf812d Christos Stavrakakis
        logger = log
67 72bf812d Christos Stavrakakis
68 72bf812d Christos Stavrakakis
    def decorator(func):
69 72bf812d Christos Stavrakakis
        @wraps(func)
70 72bf812d Christos Stavrakakis
        def wrapper(request, *args, **kwargs):
71 72bf812d Christos Stavrakakis
            try:
72 72bf812d Christos Stavrakakis
                # Get the requested serialization format
73 72bf812d Christos Stavrakakis
                request.serialization = get_serialization(request,
74 72bf812d Christos Stavrakakis
                                                          format_allowed)
75 72bf812d Christos Stavrakakis
76 72bf812d Christos Stavrakakis
                # Check HTTP method
77 72bf812d Christos Stavrakakis
                if http_method and request.method != http_method:
78 72bf812d Christos Stavrakakis
                    raise faults.BadRequest("Method not allowed")
79 72bf812d Christos Stavrakakis
80 72bf812d Christos Stavrakakis
                # Get authentication token
81 72bf812d Christos Stavrakakis
                request.x_auth_token = None
82 72bf812d Christos Stavrakakis
                if token_required or user_required:
83 72bf812d Christos Stavrakakis
                    token = get_token(request)
84 72bf812d Christos Stavrakakis
                    if not token:
85 72bf812d Christos Stavrakakis
                        msg = "Access denied. No authentication token"
86 72bf812d Christos Stavrakakis
                        raise faults.Unauthorized(msg)
87 72bf812d Christos Stavrakakis
                    request.x_auth_token = token
88 72bf812d Christos Stavrakakis
89 72bf812d Christos Stavrakakis
                # Authenticate
90 72bf812d Christos Stavrakakis
                if user_required:
91 72bf812d Christos Stavrakakis
                    assert(token_required), "Can not get user without token"
92 47ef53d5 Christos Stavrakakis
                    astakos = astakos_url or settings.ASTAKOS_URL
93 726cb37f Ilias Tsitsimpis
                    astakos = AstakosClient(astakos,
94 726cb37f Ilias Tsitsimpis
                                            use_pool=True,
95 726cb37f Ilias Tsitsimpis
                                            logger=logger)
96 726cb37f Ilias Tsitsimpis
                    user_info = astakos.get_user_info(token)
97 72bf812d Christos Stavrakakis
                    request.user_uniq = user_info["uuid"]
98 72bf812d Christos Stavrakakis
                    request.user = user_info
99 72bf812d Christos Stavrakakis
100 72bf812d Christos Stavrakakis
                # Get the response object
101 72bf812d Christos Stavrakakis
                response = func(request, *args, **kwargs)
102 72bf812d Christos Stavrakakis
103 72bf812d Christos Stavrakakis
                # Fill in response variables
104 72bf812d Christos Stavrakakis
                update_response_headers(request, response)
105 72bf812d Christos Stavrakakis
                return response
106 72bf812d Christos Stavrakakis
            except faults.Fault, fault:
107 72bf812d Christos Stavrakakis
                if fault.code >= 500:
108 72bf812d Christos Stavrakakis
                    logger.exception("API ERROR")
109 72bf812d Christos Stavrakakis
                return render_fault(request, fault)
110 726cb37f Ilias Tsitsimpis
            except AstakosClientException as err:
111 726cb37f Ilias Tsitsimpis
                fault = faults.Fault(message=err.message,
112 726cb37f Ilias Tsitsimpis
                                     details=err.details,
113 726cb37f Ilias Tsitsimpis
                                     code=err.status)
114 726cb37f Ilias Tsitsimpis
                if fault.code >= 500:
115 726cb37f Ilias Tsitsimpis
                    logger.exception("API ERROR")
116 726cb37f Ilias Tsitsimpis
                return render_fault(request, fault)
117 72bf812d Christos Stavrakakis
            except:
118 72bf812d Christos Stavrakakis
                logger.exception("Unexpected ERROR")
119 72bf812d Christos Stavrakakis
                fault = faults.InternalServerError("Unexpected ERROR")
120 72bf812d Christos Stavrakakis
                return render_fault(request, fault)
121 72bf812d Christos Stavrakakis
        return wrapper
122 72bf812d Christos Stavrakakis
    return decorator
123 72bf812d Christos Stavrakakis
124 72bf812d Christos Stavrakakis
125 72bf812d Christos Stavrakakis
def get_serialization(request, format_allowed=True):
126 72bf812d Christos Stavrakakis
    """Return the serialization format requested.
127 72bf812d Christos Stavrakakis

128 72bf812d Christos Stavrakakis
    Valid formats are 'json' and 'xml' and 'text'
129 72bf812d Christos Stavrakakis
    """
130 72bf812d Christos Stavrakakis
131 72bf812d Christos Stavrakakis
    if not format_allowed:
132 72bf812d Christos Stavrakakis
        return "text"
133 72bf812d Christos Stavrakakis
134 72bf812d Christos Stavrakakis
    # Try to get serialization from 'format' parameter
135 72bf812d Christos Stavrakakis
    _format = request.GET.get("format")
136 72bf812d Christos Stavrakakis
    if _format:
137 72bf812d Christos Stavrakakis
        if _format == "json":
138 72bf812d Christos Stavrakakis
            return "json"
139 72bf812d Christos Stavrakakis
        elif _format == "xml":
140 72bf812d Christos Stavrakakis
            return "xml"
141 72bf812d Christos Stavrakakis
142 72bf812d Christos Stavrakakis
    # Try to get serialization from path
143 72bf812d Christos Stavrakakis
    path = request.path
144 72bf812d Christos Stavrakakis
    if path.endswith(".json"):
145 72bf812d Christos Stavrakakis
        return "json"
146 72bf812d Christos Stavrakakis
    elif path.endswith(".xml"):
147 72bf812d Christos Stavrakakis
        return "xml"
148 72bf812d Christos Stavrakakis
149 72bf812d Christos Stavrakakis
    for item in request.META.get("HTTP_ACCEPT", "").split(","):
150 72bf812d Christos Stavrakakis
        accept, sep, rest = item.strip().partition(";")
151 72bf812d Christos Stavrakakis
        if accept == "application/json":
152 72bf812d Christos Stavrakakis
            return "json"
153 72bf812d Christos Stavrakakis
        elif accept == "applcation/xml":
154 72bf812d Christos Stavrakakis
            return "xml"
155 72bf812d Christos Stavrakakis
156 72bf812d Christos Stavrakakis
    return "json"
157 72bf812d Christos Stavrakakis
158 72bf812d Christos Stavrakakis
159 72bf812d Christos Stavrakakis
def update_response_headers(request, response):
160 b698d39d Christos Stavrakakis
    if not getattr(response, "override_serialization", False):
161 72bf812d Christos Stavrakakis
        serialization = request.serialization
162 72bf812d Christos Stavrakakis
        if serialization == "xml":
163 72bf812d Christos Stavrakakis
            response["Content-Type"] = "application/xml; charset=UTF-8"
164 72bf812d Christos Stavrakakis
        elif serialization == "json":
165 72bf812d Christos Stavrakakis
            response["Content-Type"] = "application/json; charset=UTF-8"
166 72bf812d Christos Stavrakakis
        elif serialization == "text":
167 72bf812d Christos Stavrakakis
            response["Content-Type"] = "text/plain; charset=UTF-8"
168 72bf812d Christos Stavrakakis
        else:
169 72bf812d Christos Stavrakakis
            raise ValueError("Unknown serialization format '%s'" %
170 72bf812d Christos Stavrakakis
                             serialization)
171 72bf812d Christos Stavrakakis
172 47ef53d5 Christos Stavrakakis
    if settings.DEBUG or getattr(settings, "TEST", False):
173 72bf812d Christos Stavrakakis
        response["Date"] = format_date_time(time())
174 72bf812d Christos Stavrakakis
175 72bf812d Christos Stavrakakis
    if not response.has_header("Content-Length"):
176 72bf812d Christos Stavrakakis
        response["Content-Length"] = len(response.content)
177 72bf812d Christos Stavrakakis
178 72bf812d Christos Stavrakakis
    cache.add_never_cache_headers(response)
179 72bf812d Christos Stavrakakis
    # Fix Vary and Cache-Control Headers. Issue: #3448
180 72bf812d Christos Stavrakakis
    cache.patch_vary_headers(response, ('X-Auth-Token',))
181 72bf812d Christos Stavrakakis
    cache.patch_cache_control(response, no_cache=True, no_store=True,
182 72bf812d Christos Stavrakakis
                              must_revalidate=True)
183 72bf812d Christos Stavrakakis
184 72bf812d Christos Stavrakakis
185 72bf812d Christos Stavrakakis
def render_fault(request, fault):
186 72bf812d Christos Stavrakakis
    """Render an API fault to an HTTP response."""
187 72bf812d Christos Stavrakakis
    # If running in debug mode add exception information to fault details
188 47ef53d5 Christos Stavrakakis
    if settings.DEBUG or getattr(settings, "TEST", False):
189 72bf812d Christos Stavrakakis
        fault.details = format_exc()
190 72bf812d Christos Stavrakakis
191 72bf812d Christos Stavrakakis
    try:
192 72bf812d Christos Stavrakakis
        serialization = request.serialization
193 72bf812d Christos Stavrakakis
    except AttributeError:
194 72bf812d Christos Stavrakakis
        request.serialization = "json"
195 72bf812d Christos Stavrakakis
        serialization = "json"
196 72bf812d Christos Stavrakakis
197 72bf812d Christos Stavrakakis
    # Serialize the fault data to xml or json
198 72bf812d Christos Stavrakakis
    if serialization == "xml":
199 72bf812d Christos Stavrakakis
        data = render_to_string("fault.xml", {"fault": fault})
200 72bf812d Christos Stavrakakis
    else:
201 72bf812d Christos Stavrakakis
        d = {fault.name: {"code": fault.code,
202 72bf812d Christos Stavrakakis
                          "message": fault.message,
203 93c6900c Ilias Tsitsimpis
                          "details": fault.details}}
204 72bf812d Christos Stavrakakis
        data = json.dumps(d)
205 72bf812d Christos Stavrakakis
206 72bf812d Christos Stavrakakis
    response = HttpResponse(data, status=fault.code)
207 72bf812d Christos Stavrakakis
    update_response_headers(request, response)
208 72bf812d Christos Stavrakakis
    return response
209 72bf812d Christos Stavrakakis
210 72bf812d Christos Stavrakakis
211 72bf812d Christos Stavrakakis
def not_found(request):
212 72bf812d Christos Stavrakakis
    raise faults.BadRequest('Not found.')
213 72bf812d Christos Stavrakakis
214 72bf812d Christos Stavrakakis
215 72bf812d Christos Stavrakakis
def method_not_allowed(request):
216 72bf812d Christos Stavrakakis
    raise faults.BadRequest('Method not allowed')