Fixed #21219 -- Added a way to set different permission for static files.

Previously, when collecting static files, the files would receive permission
from FILE_UPLOAD_PERMISSIONS. Now, there's an option to give different
permission from uploaded files permission by subclassing any of the static
files storage classes and setting the file_permissions_mode parameter.

Thanks dblack at atlassian.com for the suggestion.
This commit is contained in:
Vajrasky Kok 2013-10-19 20:40:12 +08:00 committed by Tim Graham
parent c052699be3
commit 9eecb91695
7 changed files with 105 additions and 8 deletions

View File

@ -147,7 +147,7 @@ class FileSystemStorage(Storage):
Standard filesystem storage Standard filesystem storage
""" """
def __init__(self, location=None, base_url=None): def __init__(self, location=None, base_url=None, file_permissions_mode=None):
if location is None: if location is None:
location = settings.MEDIA_ROOT location = settings.MEDIA_ROOT
self.base_location = location self.base_location = location
@ -155,6 +155,10 @@ class FileSystemStorage(Storage):
if base_url is None: if base_url is None:
base_url = settings.MEDIA_URL base_url = settings.MEDIA_URL
self.base_url = base_url self.base_url = base_url
self.file_permissions_mode = (
file_permissions_mode if file_permissions_mode is not None
else settings.FILE_UPLOAD_PERMISSIONS
)
def _open(self, name, mode='rb'): def _open(self, name, mode='rb'):
return File(open(self.path(name), mode)) return File(open(self.path(name), mode))
@ -232,8 +236,8 @@ class FileSystemStorage(Storage):
# OK, the file save worked. Break out of the loop. # OK, the file save worked. Break out of the loop.
break break
if settings.FILE_UPLOAD_PERMISSIONS is not None: if self.file_permissions_mode is not None:
os.chmod(full_path, settings.FILE_UPLOAD_PERMISSIONS) os.chmod(full_path, self.file_permissions_mode)
return name return name

View File

@ -32,8 +32,6 @@ following settings:
Management Commands Management Commands
=================== ===================
.. highlight:: console
``django.contrib.staticfiles`` exposes three management commands. ``django.contrib.staticfiles`` exposes three management commands.
collectstatic collectstatic
@ -61,6 +59,30 @@ receives all command line options of :djadmin:`collectstatic`. This is used
by the :class:`~django.contrib.staticfiles.storage.CachedStaticFilesStorage` by the :class:`~django.contrib.staticfiles.storage.CachedStaticFilesStorage`
by default. by default.
By default, collected files receive permissions from
:setting:`FILE_UPLOAD_PERMISSIONS`. If you would like different permissions for
these files, you can subclass either of the :ref:`static files storage
classes <staticfiles-storages>` and specify the ``file_permissions_mode``
parameter. For example::
from django.contrib.staticfiles import storage
class MyStaticFilesStorage(storage.StaticFilesStorage):
def __init__(self, *args, **kwargs):
kwargs['file_permissions_mode'] = 0o640
super(CustomStaticFilesStorage, self).__init__(*args, **kwargs)
Then set the :setting:`STATICFILES_STORAGE` setting to
``'path.to.MyStaticFilesStorage'``.
.. versionadded:: 1.7
The ability to override ``file_permissions_mode`` is new in Django 1.7.
Previously the file permissions always used
:setting:`FILE_UPLOAD_PERMISSIONS`.
.. highlight:: console
Some commonly used options are: Some commonly used options are:
.. django-admin-option:: --noinput .. django-admin-option:: --noinput
@ -174,6 +196,8 @@ Example usage::
django-admin.py runserver --insecure django-admin.py runserver --insecure
.. _staticfiles-storages:
Storages Storages
======== ========

View File

@ -29,13 +29,23 @@ Django provides two convenient ways to access the current storage class:
The FileSystemStorage Class The FileSystemStorage Class
--------------------------- ---------------------------
.. class:: FileSystemStorage .. class:: FileSystemStorage([location=None, base_url=None, file_permissions_mode=None])
The :class:`~django.core.files.storage.FileSystemStorage` class implements The :class:`~django.core.files.storage.FileSystemStorage` class implements
basic file storage on a local filesystem. It inherits from basic file storage on a local filesystem. It inherits from
:class:`~django.core.files.storage.Storage` and provides implementations :class:`~django.core.files.storage.Storage` and provides implementations
for all the public methods thereof. for all the public methods thereof.
.. attribute:: file_permissions_mode
The file system permissions that the file will receive when it is
saved. Defaults to :setting:`FILE_UPLOAD_PERMISSIONS`.
.. versionadded:: 1.7
The ``file_permissions_mode`` attribute was added. Previously files
always received :setting:`FILE_UPLOAD_PERMISSIONS` permissions.
.. note:: .. note::
The ``FileSystemStorage.delete()`` method will not raise The ``FileSystemStorage.delete()`` method will not raise
@ -81,7 +91,6 @@ The Storage Class
available for new content to be written to on the target storage available for new content to be written to on the target storage
system. system.
.. method:: get_valid_name(name) .. method:: get_valid_name(name)
Returns a filename based on the ``name`` parameter that's suitable Returns a filename based on the ``name`` parameter that's suitable

View File

@ -1159,6 +1159,10 @@ dependent behavior. On most platforms, temporary files will have a mode
of ``0600``, and files saved from memory will be saved using the of ``0600``, and files saved from memory will be saved using the
system's standard umask. system's standard umask.
This setting also determines the default permissions for collected static files
when using the :djadmin:`collectstatic` management command. See
:djadmin:`collectstatic` for details on overridding it.
.. warning:: .. warning::
**Always prefix the mode with a 0.** **Always prefix the mode with a 0.**

View File

@ -226,6 +226,15 @@ Minor features
:class:`~django.middleware.http.ConditionalGetMiddleware` to handle :class:`~django.middleware.http.ConditionalGetMiddleware` to handle
conditional ``GET`` requests for sitemaps which set ``lastmod``. conditional ``GET`` requests for sitemaps which set ``lastmod``.
:mod:`django.contrib.staticfiles`
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
* The :ref:`static files storage classes <staticfiles-storages>` may be
subclassed to override the permissions that collected static files receive by
setting the
:attr:`~django.core.files.storage.FileSystemStorage.file_permissions_mode`
parameter. See :djadmin:`collectstatic` for example usage.
:mod:`django.contrib.syndication` :mod:`django.contrib.syndication`
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

View File

@ -442,7 +442,6 @@ class FileStoragePermissions(unittest.TestCase):
self.umask = 0o027 self.umask = 0o027
self.old_umask = os.umask(self.umask) self.old_umask = os.umask(self.umask)
self.storage_dir = tempfile.mkdtemp() self.storage_dir = tempfile.mkdtemp()
self.storage = FileSystemStorage(self.storage_dir)
def tearDown(self): def tearDown(self):
shutil.rmtree(self.storage_dir) shutil.rmtree(self.storage_dir)
@ -450,24 +449,28 @@ class FileStoragePermissions(unittest.TestCase):
@override_settings(FILE_UPLOAD_PERMISSIONS=0o654) @override_settings(FILE_UPLOAD_PERMISSIONS=0o654)
def test_file_upload_permissions(self): def test_file_upload_permissions(self):
self.storage = FileSystemStorage(self.storage_dir)
name = self.storage.save("the_file", ContentFile("data")) name = self.storage.save("the_file", ContentFile("data"))
actual_mode = os.stat(self.storage.path(name))[0] & 0o777 actual_mode = os.stat(self.storage.path(name))[0] & 0o777
self.assertEqual(actual_mode, 0o654) self.assertEqual(actual_mode, 0o654)
@override_settings(FILE_UPLOAD_PERMISSIONS=None) @override_settings(FILE_UPLOAD_PERMISSIONS=None)
def test_file_upload_default_permissions(self): def test_file_upload_default_permissions(self):
self.storage = FileSystemStorage(self.storage_dir)
fname = self.storage.save("some_file", ContentFile("data")) fname = self.storage.save("some_file", ContentFile("data"))
mode = os.stat(self.storage.path(fname))[0] & 0o777 mode = os.stat(self.storage.path(fname))[0] & 0o777
self.assertEqual(mode, 0o666 & ~self.umask) self.assertEqual(mode, 0o666 & ~self.umask)
@override_settings(FILE_UPLOAD_DIRECTORY_PERMISSIONS=0o765) @override_settings(FILE_UPLOAD_DIRECTORY_PERMISSIONS=0o765)
def test_file_upload_directory_permissions(self): def test_file_upload_directory_permissions(self):
self.storage = FileSystemStorage(self.storage_dir)
name = self.storage.save("the_directory/the_file", ContentFile("data")) name = self.storage.save("the_directory/the_file", ContentFile("data"))
dir_mode = os.stat(os.path.dirname(self.storage.path(name)))[0] & 0o777 dir_mode = os.stat(os.path.dirname(self.storage.path(name)))[0] & 0o777
self.assertEqual(dir_mode, 0o765) self.assertEqual(dir_mode, 0o765)
@override_settings(FILE_UPLOAD_DIRECTORY_PERMISSIONS=None) @override_settings(FILE_UPLOAD_DIRECTORY_PERMISSIONS=None)
def test_file_upload_directory_default_permissions(self): def test_file_upload_directory_default_permissions(self):
self.storage = FileSystemStorage(self.storage_dir)
name = self.storage.save("the_directory/the_file", ContentFile("data")) name = self.storage.save("the_directory/the_file", ContentFile("data"))
dir_mode = os.stat(os.path.dirname(self.storage.path(name)))[0] & 0o777 dir_mode = os.stat(os.path.dirname(self.storage.path(name)))[0] & 0o777
self.assertEqual(dir_mode, 0o777 & ~self.umask) self.assertEqual(dir_mode, 0o777 & ~self.umask)

View File

@ -7,6 +7,7 @@ import posixpath
import shutil import shutil
import sys import sys
import tempfile import tempfile
import unittest
from django.template import loader, Context from django.template import loader, Context
from django.conf import settings from django.conf import settings
@ -21,6 +22,7 @@ from django.utils._os import rmtree_errorhandler, upath
from django.utils import six from django.utils import six
from django.contrib.staticfiles import finders, storage from django.contrib.staticfiles import finders, storage
from django.contrib.staticfiles.management.commands import collectstatic
TEST_ROOT = os.path.dirname(upath(__file__)) TEST_ROOT = os.path.dirname(upath(__file__))
TEST_SETTINGS = { TEST_SETTINGS = {
@ -804,3 +806,45 @@ class TestAppStaticStorage(TestCase):
st.path('bar') st.path('bar')
finally: finally:
sys.getfilesystemencoding = old_enc_func sys.getfilesystemencoding = old_enc_func
class CustomStaticFilesStorage(storage.StaticFilesStorage):
"""
Used in TestStaticFilePermissions
"""
def __init__(self, *args, **kwargs):
kwargs['file_permissions_mode'] = 0o640
super(CustomStaticFilesStorage, self).__init__(*args, **kwargs)
@unittest.skipIf(sys.platform.startswith('win'),
"Windows only partially supports chmod.")
class TestStaticFilePermissions(BaseCollectionTestCase, StaticFilesTestCase):
command_params = {'interactive': False,
'post_process': True,
'verbosity': '0',
'ignore_patterns': ['*.ignoreme'],
'use_default_ignore_patterns': True,
'clear': False,
'link': False,
'dry_run': False}
# Don't run collectstatic command in this test class.
def run_collectstatic(self, **kwargs):
pass
@override_settings(FILE_UPLOAD_PERMISSIONS=0o655)
def test_collect_static_files_default_permissions(self):
collectstatic.Command().execute(**self.command_params)
test_file = os.path.join(settings.STATIC_ROOT, "test.txt")
file_mode = os.stat(test_file)[0] & 0o777
self.assertEqual(file_mode, 0o655)
@override_settings(FILE_UPLOAD_PERMISSIONS=0o655,
STATICFILES_STORAGE='staticfiles_tests.tests.CustomStaticFilesStorage')
def test_collect_static_files_subclass_of_static_storage(self):
collectstatic.Command().execute(**self.command_params)
test_file = os.path.join(settings.STATIC_ROOT, "test.txt")
file_mode = os.stat(test_file)[0] & 0o777
self.assertEqual(file_mode, 0o640)