use check permissions in /media endpoint
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
j3d1 2023-11-01 04:32:03 +01:00
parent 90d1149c07
commit e819700bb0
7 changed files with 56 additions and 13 deletions

View file

@ -1,4 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Black">
<option name="sdkName" value="Python 3.11 (venv)" />
</component>
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.11 (venv)" project-jdk-type="Python SDK" />
</project>

View file

@ -25,6 +25,10 @@ class KnownIdentity(models.Model):
def is_authenticated(self):
return True
def friends_or_self(self):
return ToolshedUser.objects.filter(public_identity__friends=self) | ToolshedUser.objects.filter(
public_identity=self)
def verify(self, message, signature):
if len(signature) != 128 or type(signature) != str:
raise TypeError('Signature must be 128 characters long and a string')
@ -53,7 +57,8 @@ class ToolshedUserManager(auth.models.BaseUserManager):
try:
with transaction.atomic():
extra_fields['public_identity'] = identity = KnownIdentity.objects.get_or_create(
username=username, domain=domain, public_key=public_key.encode(encoder=HexEncoder).decode('utf-8'))[0]
username=username, domain=domain,
public_key=public_key.encode(encoder=HexEncoder).decode('utf-8'))[0]
try:
with transaction.atomic():
user = super().create(username=username, email=email, password=password, domain=domain,

View file

@ -2,22 +2,27 @@ from django.http import HttpResponse
from django.urls import path
from drf_yasg.utils import swagger_auto_schema
from rest_framework import status
from rest_framework.decorators import api_view
from rest_framework.decorators import api_view, permission_classes, authentication_classes
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from authentication.signature_auth import SignatureAuthentication
from files.models import File
# TODO check file permissions here
@swagger_auto_schema(method='GET', auto_schema=None)
@api_view(['GET'])
def media_urls(request, id, format=None):
@permission_classes([IsAuthenticated])
@authentication_classes([SignatureAuthentication])
def media_urls(request, hash_path):
try:
file = File.objects.get(file=id)
file = File.objects.filter(connected_items__owner__in=request.user.friends_or_self()).distinct().get(
file=hash_path)
return HttpResponse(status=status.HTTP_200_OK,
content_type=file.mime_type,
headers={
'X-Accel-Redirect': f'/redirect_media/{id}',
'X-Accel-Redirect': f'/redirect_media/{hash_path}',
'Access-Control-Allow-Origin': '*',
}) # TODO Expires and Cache-Control
@ -26,5 +31,5 @@ def media_urls(request, id, format=None):
urlpatterns = [
path('<path:id>', media_urls),
path('<path:hash_path>', media_urls),
]

View file

@ -3,6 +3,7 @@ from django.core.files.storage import DefaultStorage
from django.db import IntegrityError, transaction
from django.test import Client
from authentication.tests import SignatureAuthClient, ToolshedTestCase, UserTestMixin
from toolshed.tests import InventoryTestMixin
from nacl.hash import sha256
from nacl.encoding import HexEncoder
import base64
@ -105,11 +106,19 @@ class FilesTestCase(FilesTestMixin, ToolshedTestCase):
self.assertEqual(countdir(DefaultStorage(), ''), 3)
class MediaUrlTestCase(FilesTestMixin, UserTestMixin, ToolshedTestCase):
class MediaUrlTestCase(FilesTestMixin, UserTestMixin, InventoryTestMixin, ToolshedTestCase):
def setUp(self):
super().setUp()
self.prepare_files()
self.prepare_users()
self.prepare_categories()
self.prepare_tags()
self.prepare_properties()
self.prepare_inventory()
self.f['item1'].files.add(self.f['test_file1'])
self.f['item1'].files.add(self.f['test_file2'])
self.f['item2'].files.add(self.f['test_file1'])
def test_file_url(self):
reply = client.get(
@ -126,10 +135,33 @@ class MediaUrlTestCase(FilesTestMixin, UserTestMixin, ToolshedTestCase):
self.assertEqual(reply.headers['X-Accel-Redirect'],
f"/redirect_media/{self.f['hash2'][:2]}/{self.f['hash2'][2:4]}/{self.f['hash2'][4:6]}/{self.f['hash2'][6:]}")
self.assertEqual(reply.headers['Content-Type'], self.f['test_file2'].mime_type)
reply = client.get(
f"/media/{self.f['hash2'][:2]}/{self.f['hash2'][2:4]}/{self.f['hash2'][4:6]}/{self.f['hash2'][6:]}",
self.f['local_user2'])
self.assertEqual(reply.status_code, 200)
self.assertEqual(reply.headers['X-Accel-Redirect'],
f"/redirect_media/{self.f['hash2'][:2]}/{self.f['hash2'][2:4]}/{self.f['hash2'][4:6]}/{self.f['hash2'][6:]}")
self.assertEqual(reply.headers['Content-Type'], self.f['test_file2'].mime_type)
def test_file_url_fail(self):
reply = client.get('/media/{}/'.format('nonexistent'), self.f['local_user1'])
self.assertEqual(reply.status_code, 404)
self.assertTrue('X-Accel-Redirect' not in reply.headers)
def test_file_url_anonymous(self):
reply = anonymous_client.get(
f"/media/{self.f['hash1'][:2]}/{self.f['hash1'][2:4]}/{self.f['hash1'][4:6]}/{self.f['hash1'][6:]}")
self.assertEqual(reply.status_code, 403)
self.assertTrue('X-Accel-Redirect' not in reply.headers)
def test_file_url_wrong_user(self):
reply = client.get(
f"/media/{self.f['hash3'][:2]}/{self.f['hash3'][2:4]}/{self.f['hash3'][4:6]}/{self.f['hash3'][6:]}",
self.f['local_user1'])
self.assertEqual(reply.status_code, 404)
self.assertTrue('X-Accel-Redirect' not in reply.headers)
reply = client.get(
f"/media/{self.f['hash2'][:2]}/{self.f['hash2'][2:4]}/{self.f['hash2'][4:6]}/{self.f['hash2'][6:]}",
self.f['ext_user1'])
self.assertEqual(reply.status_code, 404)
self.assertTrue('X-Accel-Redirect' not in reply.headers)

View file

@ -64,8 +64,7 @@ def combined_info(request, format=None): # /info/
categories = [str(category) for category in Category.objects.all()]
policies = ['private', 'friends', 'internal', 'public']
domains = [domain.name for domain in Domain.objects.filter(open_registration=True)]
return Response(
{'tags': tags, 'properties': properties, 'policies': policies, 'categories': categories, 'domains': domains})
return Response({'tags': tags, 'properties': properties, 'availability_policies': policies, 'categories': categories, 'domains': domains})
urlpatterns = [

View file

@ -52,10 +52,9 @@ class CombinedApiTestCase(UserTestMixin, CategoryTestMixin, TagTestMixin, Proper
def test_combined_api(self):
response = client.get('/api/info/', self.f['local_user1'])
self.assertEqual(response.status_code, 200)
self.assertEqual(response.json()['policies'], ['private', 'friends', 'internal', 'public'])
self.assertEqual(response.json()['availability_policies'], ['private', 'friends', 'internal', 'public'])
self.assertEqual(response.json()['categories'],
['cat1', 'cat2', 'cat3', 'cat1/subcat1', 'cat1/subcat2', 'cat1/subcat1/subcat3'])
self.assertEqual(response.json()['tags'], ['tag1', 'tag2', 'tag3'])
self.assertEqual([p['name'] for p in response.json()['properties']], ['prop1', 'prop2', 'prop3'])
self.assertEqual(response.json()['domains'], ['example.com'])
self.assertEqual(response.json()['policies'], ['private', 'friends', 'internal', 'public'])

View file

@ -1,7 +1,7 @@
from authentication.tests import SignatureAuthClient, UserTestMixin, ToolshedTestCase
from files.tests import FilesTestMixin
from toolshed.models import InventoryItem, Category
from toolshed.tests import InventoryTestMixin, CategoryTestMixin, TagTestMixin, PropertyTestMixin
from toolshed.tests import InventoryTestMixin
client = SignatureAuthClient()