-
-
Notifications
You must be signed in to change notification settings - Fork 7.9k
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
Fix Uploads world readable #4387
Conversation
Hello @zulip/server-misc members, this pull request references an issue with the "area: uploads" label, so you may want to check it out! |
Automated message from Dropbox CLA bot @adnrs96, it looks like you've already signed the Dropbox CLA. Thanks! |
@timabbott I have been doubtful regarding bumping the provision_version since this adds a new dependency django-sendfile. Also I was not able to figure out the work you mentioned was needed on tests. I believe you have already added the tests for the cases that may arise. If anything is needed, could you please point out, i might not have been able to recognise some case that needs tests to be added. |
Also just noticed we might wanna update the docs for this. Will give it an update. |
Just while investigating #291, realised that in production the nginx would still have been serving uploads as a static entity due to a bug in last commit. The last commit has been updated to check to that issue. |
The first commit didn't pass tests alone, so I squashed the first 2 commits and merged them. Thanks @adnrs96! I think we can't merge the django-sendfile piece, because that library doesn't support the default nginx version of Ubuntu Trusty (1.4.6). See johnsensible/django-sendfile#58 and the things linked from there for context. I think there are 2 options: (1) try to modify django-sendfile to work with either version or (2) ship an upgraded nginx for Trusty for Zulip production systems, which potentially means using https://launchpad.net/~nginx/+archive/ubuntu/stable so that we're not stuck maintaining an nginx release. I would probably start with investigating whether we can fix that django-sendfile limitation, since that feels like a lot more convenient approach if it's doable. Can you look into that @adnrs96? |
The first commit not passing test's was strange. I checked them before pushing (Maybe forgot to test them after editing commits). Anyways thanks for squashing and merging. |
Hey @timabbott,
Thoughts? |
Let's open a PR to django-sendfile implementing this approach. We'll need to be a bit thoughtful in figuring out how to check the nginx version without creating a perf issue (wouldn't want to be calling |
@timabbott I opened up a PR to django-sendfile. You can take a look at PR here. |
Awesome. |
zerver/tests/test_upload.py
Outdated
fp_path_id = re.sub('/user_uploads/', '', uri) | ||
body = "First message ...[zulip.txt](http://localhost:9991/user_uploads/" + fp_path_id + ")" | ||
self.send_message("[email protected]", "test-subscribe", Recipient.STREAM, body, "test") | ||
self.client_post('/accounts/logout/') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think Django's test classes support self.logout()
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@showell I doubt that. I tried using self.logout()
which gave a error. I took a look at docs and no logout is documented there, just login. Am i doing something wrong that resulted in self.logout()
to error out if it was supposed to function correct?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I said self.logout()
, but what Django actually supports isself.client.logout()
:
https://docs.djangoproject.com/en/1.11/topics/testing/tools/#django.test.Client.logout
I think what we should do here is parallel what we did with login
. We have a login
wrapper in ZulipTestCase that essentially calls self.client.login
(with a few other minor details). We should have a similar wrapper for logout
. This way tests that don't really care about the details of our logging-out mechanism don't need to specify the exact URL we use.
@adnrs96 I think it might be reasonable to update this PR to be based on the version of django-sendfile from your PR? |
@timabbott I have updated the PR to fetch Django-sendfile from my fork of Django sendfile. Please take a look. FYI: We ended up using the approach for Django-sendfile where one can specify NGINX version in django settings if required. Requirement is for Nginx <1.5.9. |
(Made and deleted a comment as figured it didn't made sense) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for working on this @adnrs96! I posted a few comments.
Is there a good way for us to add some tests for this feature? E.g. in the test, send SENDFILE_BACKEND
and verify that the right nginx headers come back in the response?
zproject/settings.py
Outdated
@@ -86,6 +86,9 @@ def get_secret(key): | |||
# Import prod_settings after determining the deployment/machine type | |||
if PRODUCTION: | |||
from .prod_settings import * | |||
SENDFILE_BACKEND = 'sendfile.backends.nginx' | |||
SENDFILE_ROOT = os.path.join(LOCAL_UPLOADS_DIR, 'files') | |||
SENDFILE_URL = '/serve_uploads' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should put this in another section of the file, probably DEFAULT_SETTINGS makes the most sense? Then users can override these in their production settings files. See http://zulip.readthedocs.io/en/latest/settings.html
response = FileResponse(open(local_path, 'rb'), | ||
content_type = mimetypes.guess_type(filename)) | ||
return response | ||
return sendfile(request, local_path) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't we be passing something based on NGINX_VERSION here? Or is NGINX_VERSION just checked in the sendfile
code? I guess that makes sense.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NGINX_VERSION is checked in sendfile
. sendfile
automatically picks it from django settings.
@@ -166,21 +166,20 @@ | |||
# directly on the Zulip server. If file storage in Amazon S3 is | |||
# desired, you can configure that as follows: | |||
# | |||
# (1) Set s3_key and s3_secret_key in /etc/zulip/zulip-secrets.conf to | |||
# Set s3_key and s3_secret_key in /etc/zulip/zulip-secrets.conf to | |||
# be the S3 access and secret keys that you want to use, and setting | |||
# the S3_AUTH_UPLOADS_BUCKET and S3_AVATAR_BUCKET to be the S3 buckets | |||
# you've created to store file uploads and user avatars, respectively. | |||
# Then restart Zulip (scripts/restart-zulip). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should document here, in the instructions, that they need to set NGINX_VERSION
if the version installed on their server isn't the same as what nginx version is in front of Zulip.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess line 178 should be fine for that?
zproject/prod_settings_template.py
Outdated
# Setup the version of NGINX which is being used for this installation. | ||
# By default a NGINX version greater that 1.5.9 is assumed but if you are using | ||
# a older version, it is necessary to specify it here. | ||
# NGINX_VERSION = '1.4.6' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While django-sendfile doesn't want to auto-detect, I think we should auto-detect, because which nginx
version is being used is mostly a function of whether it's Trusty or Xenial, and the software should know that. It's really fast to check if the server has nginx
installed.
time nginx -v
nginx version: nginx/1.4.6 (Ubuntu)
real 0m0.009s
user 0m0.009s
sys 0m0.000s
What I'd do is something like this block in zproject/settings.py
:
if settings.PRODUCTION and settings.LOCAL_UPLOADS_DIR:
detected_nginx_version = subprocess.check_output(["nginx", "-v"]) # Needs further string processing to be correct.
else:
detected_nginx_version = None
DEFAULT_SETTINGS["NGINX_VERSION"] = detected_nginx_version
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@timabbott I was worried about the case where nginx server is separate from Django server (maybe dockers is right example) then we might end up checking versions in wrong place where Nginx if installed might be different from actual Nginx serving the files.
What are your thoughts, should we just go ahead with checking versions as this might not a case that Zulip should worry about? (Having different servers for Nginx and Django also increase scope for scalability right?)
(Above example is same as what I initially did in PR for Django-sendfile and they suggested dockers as an example where this might be a problem)
Let me think about the ways of adding tests. |
@timabbott we cannot use same value of backend by setting it in Though upon further investigation it turns out that we can use the fact that importing same module in different files points to same copy of the imported module and reading django-sendfile code provided with the fact that there is a way to clear the cache it generated for backend settings, we can add testing with just minor modification to the approach used in function previously depicted. PS: I have updated this PR with code to test whether correct headers are being returned. It might be ready for a merge now. |
Hold review on this one, looking into what broke. |
I investigated the issue, though i am unclear on calling on the culprit here(although i feel like Django's way of handling stuff might be) but the issue seems to originate due to the reason that Django Http Response class decodes and replace header value using Snippet from sendfile under consideration response['X-Accel-Redirect'] = url.encode('utf-8') Snippet from Django http responce implementation if six.PY3:
if isinstance(value, str):
# Ensure string is valid in given charset
value.encode(charset)
else:
# Convert bytestring using given charset
value = value.decode(charset)
else:
if isinstance(value, str):
# Ensure string is valid in given charset
value.decode(charset)
else:
# Convert unicode string to given charset
value = value.encode(charset) This creates a difference in file names which are not |
ping @timabbott ^^^ |
This commit will be introducing support for using django-sendfile for serving uploads (attachments) when using LocalStorage. For the dev environment the django backend will be used but for the Production nginx will be used to serve files but after django performs authentication checks. Fixes: zulip#320, zulip#291.
I feel like probably the right solution here is going to involve fixing Django. Is it possible to monkey-patch the function in question safely? |
@adnrs96 I tested with a unicode string,
So it will be correct as long as the string is |
@timabbott it is because Django is complying to the HTTP/1.1 spec[1],
The other choice for later, if we still want transferring filenames over http to have the unicode chars intact, could be to put the filename in the body instead (I'd have to survey the existing file-upload libs across the ecosystems of which choice they make, brb). |
Let's open an issue with django-sendfile to see what they think. It's possible the fix here is that django-sendfile should be using the standard HTTP encoding, not UTF-8. |
The issue in question is johnsensible/django-sendfile#45 (it looks like it is better solved with johnsensible/django-sendfile#45 (comment), reusing Django's |
I took a look into the
Until now everything works fine since we have been dealing in python 2 This will depict py3 behaviour:
The requirement to support py3 with Unicode can be achieved only by patching django in someway but that won't be possible unless django stops complying with the spec @rht has mentioned.(Which is unlikely to happen)
|
Heads up @adnrs96, we just merged some commits (latest: e1f5a4e) that conflict with the changes your made in this pull request! You can review this repository's recent commits to see where the conflicts occur. Please rebase your feature branch against the |
Heads up @adnrs96, we just merged some commits that conflict with the changes your made in this pull request! You can review this repository's recent commits to see where the conflicts occur. Please rebase your feature branch against the |
1 similar comment
Heads up @adnrs96, we just merged some commits that conflict with the changes your made in this pull request! You can review this repository's recent commits to see where the conflicts occur. Please rebase your feature branch against the |
ping |
Hey @rht I do have this in my mind and since Tim recently forked the django-sendfile, I believe this will start moving again soon. I am yet to look again at the django-sendfile to isolate a PR for making it work with py3 + Unicode's. As soon as that is achieved this can be updated. |
Heads up @adnrs96, we just merged some commits that conflict with the changes your made in this pull request! You can review this repository's recent commits to see where the conflicts occur. Please rebase your feature branch against the |
@adnrs96 since this PR needs a lot of updating and is pretty cluttered, I'm going to close it -- just open a new PR (and link to this one) when you have this ready for review. |
This PR contains the extension work to the #769. The work left of was
All these have been achieved in the commits within this PR.
This should fix: Uploads world readable #320 and close [WIP] Fix uploads being world readable #769.