Skip to content

Conversation

@HazAT
Copy link
Member

@HazAT HazAT commented Dec 21, 2016

kapture 2016-12-21 at 16 24 35

@HazAT HazAT self-assigned this Dec 21, 2016
@getsentry-bot
Copy link
Contributor

getsentry-bot commented Dec 21, 2016

1 Warning
⚠️ Changes require @getsentry/security sign-off

Security concerns found

  • src/sentry/api/endpoints/release_file_details.py
  • src/sentry/static/sentry/app/views/releaseArtifacts.jsx

Generated by 🚫 danger

@mitsuhiko
Copy link
Contributor

Looks good to me. Might need a CHANGES entry

@mattrobenolt
Copy link
Contributor

No no, we explicitly cannot do this for security reasons. There are specific reasons why uploads are a one-way.

Copy link
Contributor

@mattrobenolt mattrobenolt left a 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.

@mitsuhiko
Copy link
Contributor

mitsuhiko commented Dec 21, 2016 via email

@mattrobenolt
Copy link
Contributor

Unsure what we wanted to do, but @dcramer and @benvinegar should weigh their thoughts.

I personally think behind superuser check is fine.

@dcramer
Copy link
Member

dcramer commented Dec 21, 2016 via email

@mattrobenolt
Copy link
Contributor

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.

@dcramer
Copy link
Member

dcramer commented Dec 21, 2016

@mattrobenolt then we scope it to the same perms. They already have indirect access via source expansion.

@mitsuhiko
Copy link
Contributor

@dcramer by same perms you mean project:releases?

@dcramer
Copy link
Member

dcramer commented Dec 21, 2016

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.

@mitsuhiko
Copy link
Contributor

I would just say we add an explicit check for projects:releases or projects:write and if one of them passes we permit the download.


def download(self, releasefile):
file = releasefile.file.getfile()
response = HttpResponse(FileWrapper(file), mimetype='application/octet-stream')
Copy link
Contributor

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

Copy link
Contributor

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.

Copy link
Contributor

@mattrobenolt mattrobenolt Dec 22, 2016

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'),
)

Copy link
Contributor

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
Copy link
Contributor

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')) &&
Copy link
Contributor

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.

@mitsuhiko
Copy link
Contributor

Just needs changes and a sign-off from @mattrobenolt i think.

Copy link
Contributor

@mattrobenolt mattrobenolt left a 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)
Copy link
Contributor

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.

@HazAT
Copy link
Member Author

HazAT commented Dec 31, 2016

@mattrobenolt is there anything left to do?

@mattrobenolt
Copy link
Contributor

We need some tests for this. They can be pretty trivial.

@HazAT
Copy link
Member Author

HazAT commented Jan 1, 2017

Also wanted to write a api integration test but this
https://github.com/getsentry/sentry/pull/4704/files#diff-17bacf0f00a6813b38ad91e1b0efa99eR76
raises a raise ValueError('Cannot seek to pos')
Don't know how to have a seekable file for testing...

@mitsuhiko
Copy link
Contributor

cStringIO.StringIO can seek over binary data. Not sure if that helps.

@mattrobenolt
Copy link
Contributor

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(
Copy link
Contributor

@mitsuhiko mitsuhiko Jan 1, 2017

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)

@mattrobenolt
Copy link
Contributor

mattrobenolt commented Jan 1, 2017 via email

@HazAT
Copy link
Member Author

HazAT commented Jan 1, 2017

Thx guys :)

@mitsuhiko
Copy link
Contributor

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
Copy link
Contributor

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.

@HazAT
Copy link
Member Author

HazAT commented Jan 2, 2017

Squash Merge?

@mitsuhiko
Copy link
Contributor

+1

@HazAT HazAT merged commit c516cc8 into master Jan 2, 2017
@HazAT HazAT deleted the feature/download-release-files branch January 2, 2017 10:36
@github-actions github-actions bot locked and limited conversation to collaborators Dec 23, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants