Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ability to protect media folder #1476

Open
wants to merge 10 commits into
base: main
Choose a base branch
from

Conversation

bartjkdp
Copy link
Contributor

@bartjkdp bartjkdp commented Nov 4, 2024

Description

This app provides the possibility to protect the media folder. To use this functionality in production, make sure to configure the PROTECTED_FILE_SYSTEM_STORAGE setting.

Checklist

  • Keep the PR, and the amount of commits to a minimum
  • The commit messages are meaningful and descriptive
  • The change/fix is well documented, particularly in hard-to-understand areas of the code / unit tests
  • Are there any breaking changes in the code, if so are they discussed and did the team agreed to these changes
  • Check that the branch is based on main and is up to date with main
  • Check that the PR targets main
  • There are no merge conflicts and no conflicting Django migrations
  • PR was created with the "Allow edits and access to secrets by maintainers" checkbox checked

How has this been tested?

  • Provided unit tests that will prove the change/fix works as intended
  • Tested the change/fix locally and all unit tests still pass
  • Code coverage is at least 85% (the higher the better)
  • No iSort, Flake8 and SPDX issues are present in the code

@@ -35,6 +35,8 @@ RUN set -eux; \
libmagic1 \
libcairo2 \
libpango1.0-0 \
libpcre3 \
libpcre3-dev \
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deze packages zijn nodig om uwsgi met PCRE ondersteuning te bouwen. Dit is nodig voor de X-Sendfile emulation.

from django.core.files.storage import FileSystemStorage
from django.utils.encoding import filepath_to_uri

signer = signing.TimestampSigner()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In order to make this more secure, would it be a good idea to pass a salt to the signer?
By default the salt is set like this: https://github.com/django/django/blob/5b4d949d7ca118e70985ffc53f8191b766591c12/django/core/signing.py#L188C1-L191C10

It doesn't seem like a huge deal considering the secret is also used, so as long as that is unique we should still receive acceptable signatures. But better safe than sorry I suppose.

if url is not None:
url = url.lstrip('/')

signature = signer.sign(url).split(':')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please note that if the same file is requested multiple times within the same second the signature that is produced will be exactly the same. Not sure if that is acceptable?

from django.core.files.storage import FileSystemStorage
from django.utils.encoding import filepath_to_uri

signer = signing.TimestampSigner()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SHA256 is used by default as the hashing algo here. We might want to explicitly specify it to prevent any issues of it changing in the future. That is if we deem SHA256 sufficient for this use case.
One of it's advantages is that it's fast, however that is also a disadvantage as it would also be possible to relatively quickly produce things like rainbow tables.
Considering we might not need lightning fast speeds here, we could perhaps choose an even more secure algorithm here?

@@ -0,0 +1,23 @@
# Protected media
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Considering this is a separate app, maybe it would be worth noting that it needs to be added to the SIGNALS_APPS in app/signals/settings.py?



class ProtectedFileSystemStorage(FileSystemStorage):
def url(self, name):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please use type hints for the arguments and the return type?

signer = signing.TimestampSigner()


def download_file(request, path):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use type hints for the arguments and return type here.

response = self.client.get('/signals/media/test.txt?t=some_time&s=some_signature')
self.assertEqual(response.status_code, 401)
self.assertEqual(response.content, b'Bad signature')

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there no test case for expired signatures?

return HttpResponse('Bad signature', status=401)

if settings.DEBUG:
response = serve(request, path, document_root=settings.MEDIA_ROOT, show_indexes=False)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a little nitpicky, but this function is already using early returns, why not just return here as well instead of having the else block?

@@ -19,6 +19,10 @@
path('signals/', BaseSignalsAPIRootView.as_view()),
path('signals/', include('signals.apps.api.urls')),

# The media folder is routed with X-Sendfile when DEBUG=False and
# with the Django static helper when DEBUG=True
path('signals/media/', include('signals.apps.media.urls')),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens here if the app is not configured to be loaded in the settings?


@override_settings(PROTECTED_FILE_SYSTEM_STORAGE=True)
class DownloadFileTestCase(TestCase):
def setUp(self):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use type hints for the methods here. It should be as easy as adding -> None: to each of them.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

2 participants