-
-
Notifications
You must be signed in to change notification settings - Fork 4.5k
Add download to release files #4704
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
Conversation
Security concerns found
Generated by 🚫 danger |
|
Looks good to me. Might need a CHANGES entry |
|
No no, we explicitly cannot do this for security reasons. There are specific reasons why uploads are a one-way. |
mattrobenolt
left a comment
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 need to surface this ability possibly only for superusers only or in our admin.
|
Can we put this behind a org admin permission / superuser flag or something
like thatat least? It's super useful for support on react native
…On Wed, Dec 21, 2016 at 16:46 Matt Robenolt ***@***.***> wrote:
No no, we explicitly cannot do this for security reasons. There are
specific reasons why uploads are a one-way.
—
You are receiving this because you are on a team that was mentioned.
Reply to this email directly, view it on GitHub
<#4704 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AAAc5EvrLgTy1cf0RICh9ethXInJot7Kks5rKUnrgaJpZM4LTDdy>
.
|
|
Unsure what we wanted to do, but @dcramer and @benvinegar should weigh their thoughts. I personally think behind superuser check is fine. |
|
What's the security concern?
|
We never wanted having access to downloading release artifacts through the UI since this would expose source code to people on the org who maybe didn't have access before. |
|
@mattrobenolt then we scope it to the same perms. They already have indirect access via source expansion. |
|
@dcramer by same perms you mean |
|
Yeah I think so -- will defer to others here, but its my feeling that these aren't highly sensitive, and should be accessible by the same users who have permission to create/delete them. |
|
I would just say we add an explicit check for |
|
|
||
| def download(self, releasefile): | ||
| file = releasefile.file.getfile() | ||
| response = HttpResponse(FileWrapper(file), mimetype='application/octet-stream') |
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 do the same thing that Django does here for static file delivery, since it's the same idea: https://github.com/django/django/blob/1.6.11/django/views/static.py#L67-L68
The reason for this is if it's this CompatibleStreamingHttpResponse, we can actually stream the bytes back to the browser instead of blocking process while it downloads in memory. This streaming behavior is then also handled directly by uWSGI and deferred away from Django.
This happens here: https://github.com/getsentry/sentry/blob/master/src/sentry/wsgi.py#L44
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 also extract the mimetype from file.headers.get('content-type', 'application/octet-stream') since we typically have this information.
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.
To be more explicit, this last line could be:
file = releasefile.file
fp = file.getfile()
CompatibleStreamingHttpResponse(
iter(lambda: fp.read(4096), b''),
content_type=file.headers.get('content-type', 'application/octet-stream'),
)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.
Oh I see. I thought HttpResponse already can handle this on its own but this just works by chance. Clever :P
| raise ResourceDoesNotExist | ||
|
|
||
| if request.GET.get('download') is not None and ( | ||
| request.access.has_scope('project:releases') or |
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 stick with just project:write.
Checking for project:releases is kinda moot at this point anyways since literally any member has project:releases permission. But as of now, project:releases doesn't have any concept of read vs write, so this is effectively opening it up to everybody.
We should limit to project:write and document this behavior. We can always loosen constraint here if we have the need in the future. I'm hesitant on exposing this feature to people who potentially didn't have access to source code before, so I feel project:write is sufficient to start.
| <div className="col-sm-2 col-xs-2 align-right"><FileSize bytes={file.size} /></div> | ||
| <div className="col-sm-1 col-xs-2 align-right"> | ||
| <div className="col-sm-2 col-xs-3 align-right actions"> | ||
| {(access.has('project:releases') || access.has('project:write')) && |
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.
Same here for project:write.
According to @dcramer, which I agree with, we should leave the button here even if they don't have permission, but disable it with a tooltip saying that they don't have permission. That way we're not hiding things in the UI and they can see easily that they don't have permission to perform an action.
|
Just needs changes and a sign-off from @mattrobenolt i think. |
mattrobenolt
left a comment
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.
You need to revert changes to the locale files.
|
|
||
| if request.GET.get('download') is not None and ( | ||
| request.access.has_scope('project:write')): | ||
| return self.download(releasefile) |
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 also probably return a 403 here when requesting a download but don't have have permission.
This reverts commit 8417c16.
|
@mattrobenolt is there anything left to do? |
|
We need some tests for this. They can be pretty trivial. |
|
Also wanted to write a api integration test but this |
|
cStringIO.StringIO can seek over binary data. Not sure if that helps. |
|
Can you commit the test code that fails? Hard to debug without seeing what you're doing. :) |
| organization_id=project.organization_id, | ||
| project=project, | ||
| release=release, | ||
| file=File.objects.create( |
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 you need to create the file before the release file and then use putfile on it:
from io import BytesIO
f = File.objects.create(...)
f.putfile(BytesIO(b'File contents here'))
ReleaseFIle.objects.create(..., file=f)|
Yeah, that's it. :)
|
|
Thx guys :) |
|
Now only needs a CHANGES entry and this is ready to go. |
| }) | ||
|
|
||
| response = self.client.get(url + '?download=1') | ||
| assert response.status_code == 200, response.content |
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.
It'd be worth adding an assertion for the contents of the payload here to match the BytesIO contents to confirm that we've actually downloaded the correct file.
As well as the correct headers. Content-Length, Content-Type, and Content-Disposition. Mostly because in this case, these things are actually significant and we wouldn't want to break them.
Other than this, lgtm.
|
Squash Merge? |
|
+1 |
Uh oh!
There was an error while loading. Please reload this page.