Revision 46662ee6

b/snf-pithos-app/pithos/api/public.py
37 37
from snf_django.lib import api
38 38
from snf_django.lib.api import faults
39 39

  
40
from pithos.api.settings import SERVE_API_DOMAIN
40 41
from pithos.api.util import (put_object_headers, update_manifest_meta,
41 42
                             validate_modification_preconditions,
42 43
                             validate_matching_preconditions,
43 44
                             object_data_response, api_method,
44
                             split_container_object_string)
45
                             split_container_object_string, restrict_to_host)
45 46

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

  
49 50

  
50 51
@csrf_exempt
52
@restrict_to_host(SERVE_API_DOMAIN)
51 53
def public_demux(request, v_public):
52 54
    if request.method == 'HEAD':
53 55
        return public_meta(request, v_public)
b/snf-pithos-app/pithos/api/settings.py
189 189
OA2_CLIENT_CREDENTIALS = getattr(settings, 'PITHOS_OA2_CLIENT_CREDENTIALS',
190 190
                                 (None, None))
191 191

  
192
# Set to False to serve only views
192
# Set to False to disable serving object content serving endpoints
193 193
SERVE_API = getattr(settings, 'PITHOS_SERVE_API', True)
194

  
195
# Set domain to restrict requests of pithos object contents serve endpoint or
196
# None for no domain restriction
197
SERVE_API_DOMAIN = getattr(settings, 'PITHOS_SERVE_API_DOMAIN', None)
b/snf-pithos-app/pithos/api/util.py
67 67
                                 RADOS_POOL_MAPS, TRANSLATE_UUIDS,
68 68
                                 PUBLIC_URL_SECURITY, PUBLIC_URL_ALPHABET,
69 69
                                 BASE_HOST, UPDATE_MD5, VIEW_PREFIX,
70
                                 OA2_CLIENT_CREDENTIALS)
70
                                 OA2_CLIENT_CREDENTIALS, SERVE_API_DOMAIN)
71 71

  
72 72
from pithos.api.resources import resources
73 73
from pithos.backends import connect_backend
......
1121 1121
    return decorator
1122 1122

  
1123 1123

  
1124
def restrict_to_host(host=None):
1125
    """
1126
    View decorator which restricts wrapped view to be accessed only under the
1127
    host set. If an invalid host is identified and request HTTP method is one
1128
    of ``GET``, ``HOST``, the decorator will return a redirect response using a
1129
    clone of the request with host replaced to the one the restriction applies
1130
    to.
1131

  
1132
    e.g.
1133
    @restrict_to_host('files.example.com')
1134
    my_restricted_view(request, path):
1135
        return HttpResponse(file(path).read())
1136

  
1137
    A get to ``https://api.example.com/my_restricted_view/file_path/?param=1``
1138
    will return a redirect response with Location header set to
1139
    ``https://files.example.com/my_restricted_view/file_path/?param=1``.
1140

  
1141
    If host is set to ``None`` no restriction will be applied.
1142
    """
1143
    def decorator(func):
1144
        # skip decoration if no host is set
1145
        if not host:
1146
            return func
1147

  
1148
        @wraps(func)
1149
        def wrapper(request, *args, **kwargs):
1150
            request_host = request.get_host()
1151
            if host != request_host:
1152
                proto = 'https' if request.is_secure() else 'http'
1153
                if request.method in ['GET', 'HEAD']:
1154
                    full_path = request.get_full_path()
1155
                    redirect_uri = "%s://%s%s" % (proto, host, full_path)
1156
                    return HttpResponseRedirect(redirect_uri)
1157
                else:
1158
                    raise PermissionDenied
1159
            return func(request, *args, **kwargs)
1160
        return wrapper
1161
    return decorator
1162

  
1163

  
1124 1164
def view_method():
1125 1165
    """Decorator function for views."""
1126 1166

  
1127 1167
    def decorator(func):
1168
        @restrict_to_host(SERVE_API_DOMAIN)
1128 1169
        @wraps(func)
1129 1170
        def wrapper(request, *args, **kwargs):
1130
            if request.method != 'GET':
1131
                return HttpResponseNotAllowed(['GET'])
1171
            if request.method not in ['GET', 'HEAD']:
1172
                return HttpResponseNotAllowed(['GET', 'HEAD'])
1132 1173

  
1133 1174
            try:
1134 1175
                access_token = request.GET.get('access_token')
......
1141 1182
                    try:
1142 1183
                        request.user = astakos.validate_token(
1143 1184
                            access_token, requested_resource)
1144
                    except AstakosClientException:
1185
                    except AstakosClientException, e:
1145 1186
                        return HttpResponseRedirect(request.path)
1146 1187
                    request.user_uniq = request.user["access"]["user"]["id"]
1147 1188

  
......
1171 1212
                                                 urlencode(params)))
1172 1213
                else:
1173 1214
                    # request short-term access code
1174
                    redirect_uri = join_urls(BASE_HOST, request.path)
1215

  
1216
                    # resolve redirect host. If SERVE_API_DOMAIN is set
1217
                    # redirect to that host instead.
1218
                    redirect_host = BASE_HOST
1219
                    if SERVE_API_DOMAIN:
1220
                        redirect_host = SERVE_API_DOMAIN
1221
                        proto = 'https' if request.is_secure() else 'http'
1222
                        redirect_host = '%s://%s' % (proto, redirect_host)
1223

  
1224
                    redirect_uri = '%s%s' % (redirect_host, request.path)
1175 1225
                    data = astakos.get_token('authorization_code',
1176 1226
                                             *OA2_CLIENT_CREDENTIALS,
1177 1227
                                             redirect_uri=redirect_uri,

Also available in: Unified diff