Merge pull request #55 from taigaio/attachments
Check permissions when accessing attachmentsremotes/origin/enhancement/email-actions
commit
2e6ddbf98f
|
@ -324,3 +324,8 @@ GRAVATAR_DEFAULT_OPTIONS = {
|
||||||
'default': DEFAULT_AVATAR_URL, # default avatar to show if there's no gravatar image
|
'default': DEFAULT_AVATAR_URL, # default avatar to show if there's no gravatar image
|
||||||
'size': DEFAULT_AVATAR_SIZE
|
'size': DEFAULT_AVATAR_SIZE
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
IN_DEVELOPMENT_SERVER = sys.argv[1] == 'runserver'
|
||||||
|
except IndexError:
|
||||||
|
IN_DEVELOPMENT_SERVER = False
|
||||||
|
|
|
@ -20,3 +20,5 @@ SKIP_SOUTH_TESTS = True
|
||||||
SOUTH_TESTS_MIGRATE = False
|
SOUTH_TESTS_MIGRATE = False
|
||||||
|
|
||||||
CELERY_ALWAYS_EAGER = True
|
CELERY_ALWAYS_EAGER = True
|
||||||
|
|
||||||
|
MEDIA_ROOT = "/tmp"
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import django_sites as sites
|
import django_sites as sites
|
||||||
|
from django.core.urlresolvers import reverse as django_reverse
|
||||||
|
|
||||||
URL_TEMPLATE = "{scheme}://{domain}/{path}"
|
URL_TEMPLATE = "{scheme}://{domain}/{path}"
|
||||||
|
|
||||||
|
@ -18,3 +19,8 @@ def get_absolute_url(path):
|
||||||
return path
|
return path
|
||||||
site = sites.get_current()
|
site = sites.get_current()
|
||||||
return build_url(path, scheme=site.scheme, domain=site.domain)
|
return build_url(path, scheme=site.scheme, domain=site.domain)
|
||||||
|
|
||||||
|
|
||||||
|
def reverse(viewname, *args, **kwargs):
|
||||||
|
"""Same behavior as django's reverse but uses django_sites to compute absolute url."""
|
||||||
|
return get_absolute_url(django_reverse(viewname, *args, **kwargs))
|
||||||
|
|
|
@ -13,13 +13,13 @@
|
||||||
#
|
#
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
from os import path
|
||||||
|
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
from taiga.base.utils.urls import reverse
|
||||||
from . import models
|
from . import models
|
||||||
|
|
||||||
from os import path
|
|
||||||
|
|
||||||
|
|
||||||
class AttachmentSerializer(serializers.ModelSerializer):
|
class AttachmentSerializer(serializers.ModelSerializer):
|
||||||
name = serializers.SerializerMethodField("get_name")
|
name = serializers.SerializerMethodField("get_name")
|
||||||
|
@ -39,7 +39,7 @@ class AttachmentSerializer(serializers.ModelSerializer):
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
def get_url(self, obj):
|
def get_url(self, obj):
|
||||||
return obj.attached_file.url if obj and obj.attached_file else ""
|
return reverse("attachment-url", kwargs={"pk": obj.pk})
|
||||||
|
|
||||||
def get_size(self, obj):
|
def get_size(self, obj):
|
||||||
if obj.attached_file:
|
if obj.attached_file:
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
import os
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django import http
|
||||||
|
|
||||||
|
from rest_framework import generics
|
||||||
|
from rest_framework.permissions import IsAuthenticated
|
||||||
|
|
||||||
|
from . import permissions
|
||||||
|
from . import models
|
||||||
|
|
||||||
|
|
||||||
|
def serve_attachment(request, attachment):
|
||||||
|
if settings.IN_DEVELOPMENT_SERVER:
|
||||||
|
return http.HttpResponseRedirect(attachment.url)
|
||||||
|
|
||||||
|
name = attachment.name
|
||||||
|
response = http.HttpResponse()
|
||||||
|
response['X-Accel-Redirect'] = "/{filepath}".format(filepath=name)
|
||||||
|
response['Content-Disposition'] = 'attachment;filename={filename}'.format(
|
||||||
|
filename=os.path.basename(name))
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
class RawAttachmentView(generics.RetrieveAPIView):
|
||||||
|
queryset = models.Attachment.objects.all()
|
||||||
|
permission_classes = (IsAuthenticated, permissions.AttachmentPermission,)
|
||||||
|
|
||||||
|
def retrieve(self, request, *args, **kwargs):
|
||||||
|
self.object = self.get_object()
|
||||||
|
return serve_attachment(request, self.object.attached_file)
|
|
@ -20,11 +20,14 @@ from django.contrib.staticfiles.urls import staticfiles_urlpatterns
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
|
||||||
from .routers import router
|
from .routers import router
|
||||||
|
from .projects.attachments.views import RawAttachmentView
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
admin.autodiscover()
|
admin.autodiscover()
|
||||||
|
|
||||||
urlpatterns = patterns('',
|
urlpatterns = patterns('',
|
||||||
|
url(r'^attachments/(?P<pk>\d+)/$', RawAttachmentView.as_view(), name="attachment-url"),
|
||||||
url(r'^api/v1/', include(router.urls)),
|
url(r'^api/v1/', include(router.urls)),
|
||||||
url(r'^api/v1/api-auth/', include('rest_framework.urls', namespace='rest_framework')),
|
url(r'^api/v1/api-auth/', include('rest_framework.urls', namespace='rest_framework')),
|
||||||
url(r'^admin/', include(admin.site.urls)),
|
url(r'^admin/', include(admin.site.urls)),
|
||||||
|
|
|
@ -266,6 +266,16 @@ class ContentTypeFactory(Factory):
|
||||||
model = factory.LazyAttribute(lambda obj: ContentTypeFactory.FACTORY_FOR._meta.model_name)
|
model = factory.LazyAttribute(lambda obj: ContentTypeFactory.FACTORY_FOR._meta.model_name)
|
||||||
|
|
||||||
|
|
||||||
|
class AttachmentFactory(Factory):
|
||||||
|
FACTORY_FOR = get_model("attachments", "Attachment")
|
||||||
|
|
||||||
|
owner = factory.SubFactory("tests.factories.UserFactory")
|
||||||
|
project = factory.SubFactory("tests.factories.ProjectFactory")
|
||||||
|
content_type = factory.SubFactory("tests.factories.ContentTypeFactory")
|
||||||
|
object_id = factory.Sequence(lambda n: n)
|
||||||
|
attached_file = factory.django.FileField(data=b"File contents")
|
||||||
|
|
||||||
|
|
||||||
def create_issue(**kwargs):
|
def create_issue(**kwargs):
|
||||||
"Create an issue and along with its dependencies."
|
"Create an issue and along with its dependencies."
|
||||||
owner = kwargs.pop("owner", UserFactory())
|
owner = kwargs.pop("owner", UserFactory())
|
||||||
|
|
|
@ -0,0 +1,58 @@
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from django.core.urlresolvers import reverse
|
||||||
|
from django.core.files.base import File
|
||||||
|
|
||||||
|
from .. import factories as f
|
||||||
|
from ..utils import set_settings
|
||||||
|
|
||||||
|
pytestmark = pytest.mark.django_db
|
||||||
|
|
||||||
|
def test_authentication(client):
|
||||||
|
"User can't access an attachment if not authenticated"
|
||||||
|
attachment = f.AttachmentFactory.create()
|
||||||
|
url = reverse("attachment-url", kwargs={"pk": attachment.pk})
|
||||||
|
|
||||||
|
response = client.get(url)
|
||||||
|
|
||||||
|
assert response.status_code == 401
|
||||||
|
|
||||||
|
|
||||||
|
def test_authorization(client):
|
||||||
|
"User can't access an attachment if not authorized"
|
||||||
|
attachment = f.AttachmentFactory.create()
|
||||||
|
user = f.UserFactory.create()
|
||||||
|
|
||||||
|
url = reverse("attachment-url", kwargs={"pk": attachment.pk})
|
||||||
|
|
||||||
|
client.login(user)
|
||||||
|
response = client.get(url)
|
||||||
|
|
||||||
|
assert response.status_code == 403
|
||||||
|
|
||||||
|
|
||||||
|
@set_settings(IN_DEVELOPMENT_SERVER=True)
|
||||||
|
def test_attachment_redirect_in_devserver(client):
|
||||||
|
"When accessing the attachment in devserver redirect to the real attachment url"
|
||||||
|
attachment = f.AttachmentFactory.create()
|
||||||
|
|
||||||
|
url = reverse("attachment-url", kwargs={"pk": attachment.pk})
|
||||||
|
|
||||||
|
client.login(attachment.owner)
|
||||||
|
response = client.get(url)
|
||||||
|
|
||||||
|
assert response.status_code == 302
|
||||||
|
|
||||||
|
|
||||||
|
@set_settings(IN_DEVELOPMENT_SERVER=False)
|
||||||
|
def test_attachment_redirect(client):
|
||||||
|
"When accessing the attachment redirect using X-Accel-Redirect header"
|
||||||
|
attachment = f.AttachmentFactory.create()
|
||||||
|
|
||||||
|
url = reverse("attachment-url", kwargs={"pk": attachment.pk})
|
||||||
|
|
||||||
|
client.login(attachment.owner)
|
||||||
|
response = client.get(url)
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert response.has_header('x-accel-redirect')
|
Loading…
Reference in New Issue