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