2 changed files with 243 additions and 0 deletions
@ -0,0 +1,192 @@
|
||||
# -*- coding: utf-8 -*- |
||||
from __future__ import unicode_literals |
||||
# from django-protected-media |
||||
|
||||
import mimetypes |
||||
import os |
||||
import stat |
||||
from six.moves.urllib.parse import quote |
||||
from django.conf import settings |
||||
from django.http import HttpResponse, Http404, HttpResponseNotModified |
||||
from django.utils.encoding import smart_str |
||||
from django.utils.http import http_date |
||||
from django.views.static import was_modified_since |
||||
|
||||
|
||||
class BaseProtectedMediaServer(object): |
||||
INTERNAL_URL = None |
||||
ROOT = None |
||||
FORCE_DOWNLOAD = True |
||||
|
||||
def get_url(self, relative_path): |
||||
return os.path.join(self.INTERNAL_URL or settings.PROTECTED_MEDIA_INTERNAL_URL, relative_path).encode('utf-8') |
||||
|
||||
def get_full_path(self, relative_path): |
||||
return os.path.join(self.ROOT or settings.PROTECTED_MEDIA_ROOT, relative_path) |
||||
|
||||
def get_mimetype(self, relative_path): |
||||
return mimetypes.guess_type(self.get_full_path(relative_path))[0] or 'application/octet-stream' |
||||
|
||||
def get_force_download(self, overwrite=None): |
||||
if overwrite is not None: |
||||
return overwrite |
||||
else: |
||||
return getattr(settings, 'PROTECTED_MEDIA_FORCE_DOWNLOAD', self.FORCE_DOWNLOAD) |
||||
|
||||
def get_filename(self, relative_path): |
||||
return smart_str(os.path.basename(self.get_full_path(relative_path))) |
||||
|
||||
def add_attachment_header(self, response, relative_path): |
||||
""" |
||||
Add header to force the browser to save the file instead of possibly displaying it. |
||||
""" |
||||
filename = self.get_filename(relative_path) |
||||
response['Content-Disposition'] = "attachment; filename*=UTF=8''{0}".format(quote(filename)) |
||||
return response |
||||
|
||||
def prepare_response(self, request, response, relative_path): |
||||
if self.get_force_download(): |
||||
response = self.add_attachment_header(response, relative_path) |
||||
return response |
||||
|
||||
def serve(self, request, relative_path): |
||||
response = HttpResponse() |
||||
response = self.prepare_response(request, response, relative_path) |
||||
return response |
||||
|
||||
|
||||
|
||||
from django.conf.urls import patterns, url |
||||
from django.conf import settings |
||||
|
||||
def get_urls(self): |
||||
return ( |
||||
) |
||||
url(r'^{0}(?P<path>.*)$'.format(settings.PRIVATE_MEDIA_URL.lstrip('/')), self.serve_protected_path,), |
||||
) |
||||
|
||||
|
||||
def get_urls(self): |
||||
from django.conf.urls import url, include |
||||
# Since this module gets imported in the application's root package, |
||||
# it cannot import models from other applications at the module level, |
||||
# and django.contrib.contenttypes.views imports ContentType. |
||||
from django.contrib.contenttypes import views as contenttype_views |
||||
|
||||
if settings.DEBUG: |
||||
self.check_dependencies() |
||||
|
||||
def wrap(view, cacheable=False): |
||||
def wrapper(*args, **kwargs): |
||||
return self.admin_view(view, cacheable)(*args, **kwargs) |
||||
wrapper.admin_site = self |
||||
return update_wrapper(wrapper, view) |
||||
|
||||
# Admin-site-wide views. |
||||
urlpatterns = [ |
||||
url(r'^$', wrap(self.index), name='index'), |
||||
url(r'^login/$', self.login, name='login'), |
||||
url(r'^logout/$', wrap(self.logout), name='logout'), |
||||
url(r'^password_change/$', wrap(self.password_change, cacheable=True), name='password_change'), |
||||
url(r'^password_change/done/$', wrap(self.password_change_done, cacheable=True), |
||||
name='password_change_done'), |
||||
url(r'^jsi18n/$', wrap(self.i18n_javascript, cacheable=True), name='jsi18n'), |
||||
url(r'^r/(?P<content_type_id>\d+)/(?P<object_id>.+)/$', wrap(contenttype_views.shortcut), |
||||
name='view_on_site'), |
||||
] |
||||
|
||||
# Add in each model's views, and create a list of valid URLS for the |
||||
# app_index |
||||
valid_app_labels = [] |
||||
for model, model_admin in self._registry.items(): |
||||
urlpatterns += [ |
||||
url(r'^%s/%s/' % (model._meta.app_label, model._meta.model_name), include(model_admin.urls)), |
||||
] |
||||
if model._meta.app_label not in valid_app_labels: |
||||
valid_app_labels.append(model._meta.app_label) |
||||
|
||||
# If there were ModelAdmins registered, we should have a list of app |
||||
# labels for which we need to allow access to the app_index view, |
||||
if valid_app_labels: |
||||
regex = r'^(?P<app_label>' + '|'.join(valid_app_labels) + ')/$' |
||||
urlpatterns += [ |
||||
url(regex, wrap(self.app_index), name='app_list'), |
||||
] |
||||
return urlpatterns |
||||
|
||||
@property |
||||
def urls(self): |
||||
return self.get_urls(), 'admin', self.name |
||||
|
||||
|
||||
|
||||
|
||||
def serve_protected_path(self, request, path): |
||||
""" |
||||
Serve private files to users with read permission. |
||||
""" |
||||
if not permissions.has_read_permission(request, path): |
||||
if settings.DEBUG: |
||||
raise PermissionDenied |
||||
else: |
||||
raise Http404('File not found') |
||||
return server.serve(request, relative_path=path) |
||||
|
||||
|
||||
class LocalDevelopmentServer(BaseProtectedMediaServer): |
||||
""" |
||||
Serve static files from the local filesystem through django. |
||||
This is a bad idea for most situations other than testing. |
||||
|
||||
This will only work for files that can be accessed in the local filesystem. |
||||
""" |
||||
|
||||
def serve(self, request, relative_path): |
||||
# the following code is largely borrowed from `django.views.static.serve` |
||||
# and django-filetransfers: filetransfers.backends.default |
||||
|
||||
full_path = self.get_full_path(relative_path) |
||||
|
||||
if not os.path.exists(full_path): |
||||
raise Http404('"{0}" does not exist'.format(full_path)) |
||||
|
||||
# Respect the If-Modified-Since header. |
||||
content_type = self.get_mimetype(relative_path) |
||||
statobj = os.stat(full_path) |
||||
|
||||
if not was_modified_since(request.META.get('HTTP_IF_MODIFIED_SINCE'), |
||||
statobj[stat.ST_MTIME], |
||||
statobj[stat.ST_SIZE]): |
||||
response = HttpResponseNotModified(content_type=content_type) |
||||
else: |
||||
response = HttpResponse(open(full_path, 'rb').read(), content_type=content_type) |
||||
response["Last-Modified"] = http_date(statobj[stat.ST_MTIME]) |
||||
response = self.prepare_response(request, response, relative_path) |
||||
|
||||
return response |
||||
|
||||
|
||||
class ApacheXSendfileServer(BaseProtectedMediaServer): |
||||
def prepare_response(self, request, response, relative_path): |
||||
response = super(ApacheXSendfileServer, self).prepare_response(request, response, relative_path) |
||||
|
||||
# Apache expects a 'X-Sendfile' header an the full filesystem path |
||||
response['X-Sendfile'] = self.get_full_path(request, relative_path) |
||||
|
||||
# From django-filer (https://github.com/stefanfoulis/django-filer/): |
||||
# This is needed for lighttpd, hopefully this will |
||||
# not be needed after this is fixed: |
||||
# http://redmine.lighttpd.net/issues/2076 |
||||
response['Content-Type'] = self.get_mimetype(relative_path) |
||||
return response |
||||
|
||||
|
||||
class NginxXAccelRedirectServer(BaseProtectedMediaServer): |
||||
def prepare_response(self, request, response, relative_path): |
||||
response = super(NginxXAccelRedirectServer, self).prepare_response(request, response, relative_path) |
||||
|
||||
# Nginx expects a 'X-Accel-Redirect' header to a protected url; |
||||
# the actual filesystem path is set in the nginx configuration. |
||||
response['X-Accel-Redirect'] = self.get_url(relative_path) |
||||
response['Content-Type'] = self.get_mimetype(relative_path) |
||||
return response |
@ -0,0 +1,51 @@
|
||||
# -*- coding: utf-8 -*- |
||||
from __future__ import unicode_literals |
||||
|
||||
from importlib import import_module |
||||
from django.conf import settings |
||||
from django.core.exceptions import PermissionDenied |
||||
from django.http import Http404 |
||||
|
||||
from . import servers |
||||
|
||||
|
||||
def get_class(import_path=None): |
||||
""" |
||||
Largely based on django.core.files.storage's get_storage_class |
||||
""" |
||||
from django.core.exceptions import ImproperlyConfigured |
||||
if import_path is None: |
||||
raise ImproperlyConfigured('No class path specified.') |
||||
try: |
||||
dot = import_path.rindex('.') |
||||
except ValueError: |
||||
raise ImproperlyConfigured("%s isn't a module." % import_path) |
||||
module, classname = import_path[:dot], import_path[dot + 1:] |
||||
try: |
||||
mod = import_module(module) |
||||
except ImportError as e: |
||||
raise ImproperlyConfigured('Error importing module %s: "%s"' % (module, e)) |
||||
try: |
||||
return getattr(mod, classname) |
||||
except AttributeError: |
||||
raise ImproperlyConfigured('Module "%s" does not define a "%s" class.' % (module, classname)) |
||||
|
||||
|
||||
server = get_class(settings.PROTECTED_MEDIA_SERVER)(**getattr(settings, 'PROTECTED_MEDIA_SERVER_OPTIONS', {})) |
||||
if hasattr(settings,'PROTECTED_MEDIA_PERMISSIONS'): |
||||
permissions = get_class(settings.PROTECTED_MEDIA_PERMISSIONS)(**getattr(settings, 'PROTECTED_MEDIA_PERMISSIONS_OPTIONS', {})) |
||||
else: |
||||
from .permissions import DefaultProtectedPermissions |
||||
permissions = DefaultProtectedPermissions() |
||||
|
||||
|
||||
def serve_protected_file(request, path): |
||||
""" |
||||
Serve private files to users with read permission. |
||||
""" |
||||
if not permissions.has_read_permission(request, path): |
||||
if settings.DEBUG: |
||||
raise PermissionDenied |
||||
else: |
||||
raise Http404('File not found') |
||||
return server.serve(request, relative_path=path) |
Loading…
Reference in new issue