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"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <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" /> <component name="ProjectRootManager" version="2" project-jdk-name="Python 3.11 (venv)" project-jdk-type="Python SDK" />
</project> </project>

View file

@ -25,6 +25,10 @@ class KnownIdentity(models.Model):
def is_authenticated(self): def is_authenticated(self):
return True 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): def verify(self, message, signature):
if len(signature) != 128 or type(signature) != str: if len(signature) != 128 or type(signature) != str:
raise TypeError('Signature must be 128 characters long and a string') raise TypeError('Signature must be 128 characters long and a string')
@ -53,7 +57,8 @@ class ToolshedUserManager(auth.models.BaseUserManager):
try: try:
with transaction.atomic(): with transaction.atomic():
extra_fields['public_identity'] = identity = KnownIdentity.objects.get_or_create( 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: try:
with transaction.atomic(): with transaction.atomic():
user = super().create(username=username, email=email, password=password, domain=domain, 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 django.urls import path
from drf_yasg.utils import swagger_auto_schema from drf_yasg.utils import swagger_auto_schema
from rest_framework import status 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 rest_framework.response import Response
from authentication.signature_auth import SignatureAuthentication
from files.models import File from files.models import File
# TODO check file permissions here
@swagger_auto_schema(method='GET', auto_schema=None) @swagger_auto_schema(method='GET', auto_schema=None)
@api_view(['GET']) @api_view(['GET'])
def media_urls(request, id, format=None): @permission_classes([IsAuthenticated])
@authentication_classes([SignatureAuthentication])
def media_urls(request, hash_path):
try: 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, return HttpResponse(status=status.HTTP_200_OK,
content_type=file.mime_type, content_type=file.mime_type,
headers={ headers={
'X-Accel-Redirect': f'/redirect_media/{id}', 'X-Accel-Redirect': f'/redirect_media/{hash_path}',
'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Origin': '*',
}) # TODO Expires and Cache-Control }) # TODO Expires and Cache-Control
@ -26,5 +31,5 @@ def media_urls(request, id, format=None):
urlpatterns = [ 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.db import IntegrityError, transaction
from django.test import Client from django.test import Client
from authentication.tests import SignatureAuthClient, ToolshedTestCase, UserTestMixin from authentication.tests import SignatureAuthClient, ToolshedTestCase, UserTestMixin
from toolshed.tests import InventoryTestMixin
from nacl.hash import sha256 from nacl.hash import sha256
from nacl.encoding import HexEncoder from nacl.encoding import HexEncoder
import base64 import base64
@ -105,11 +106,19 @@ class FilesTestCase(FilesTestMixin, ToolshedTestCase):
self.assertEqual(countdir(DefaultStorage(), ''), 3) self.assertEqual(countdir(DefaultStorage(), ''), 3)
class MediaUrlTestCase(FilesTestMixin, UserTestMixin, ToolshedTestCase): class MediaUrlTestCase(FilesTestMixin, UserTestMixin, InventoryTestMixin, ToolshedTestCase):
def setUp(self): def setUp(self):
super().setUp() super().setUp()
self.prepare_files() self.prepare_files()
self.prepare_users() 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): def test_file_url(self):
reply = client.get( reply = client.get(
@ -126,10 +135,33 @@ class MediaUrlTestCase(FilesTestMixin, UserTestMixin, ToolshedTestCase):
self.assertEqual(reply.headers['X-Accel-Redirect'], 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:]}") 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) 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): def test_file_url_fail(self):
reply = client.get('/media/{}/'.format('nonexistent'), self.f['local_user1']) reply = client.get('/media/{}/'.format('nonexistent'), self.f['local_user1'])
self.assertEqual(reply.status_code, 404) self.assertEqual(reply.status_code, 404)
self.assertTrue('X-Accel-Redirect' not in reply.headers) 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()] categories = [str(category) for category in Category.objects.all()]
policies = ['private', 'friends', 'internal', 'public'] policies = ['private', 'friends', 'internal', 'public']
domains = [domain.name for domain in Domain.objects.filter(open_registration=True)] domains = [domain.name for domain in Domain.objects.filter(open_registration=True)]
return Response( return Response({'tags': tags, 'properties': properties, 'availability_policies': policies, 'categories': categories, 'domains': domains})
{'tags': tags, 'properties': properties, 'policies': policies, 'categories': categories, 'domains': domains})
urlpatterns = [ urlpatterns = [

View file

@ -52,10 +52,9 @@ class CombinedApiTestCase(UserTestMixin, CategoryTestMixin, TagTestMixin, Proper
def test_combined_api(self): def test_combined_api(self):
response = client.get('/api/info/', self.f['local_user1']) response = client.get('/api/info/', self.f['local_user1'])
self.assertEqual(response.status_code, 200) 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'], self.assertEqual(response.json()['categories'],
['cat1', 'cat2', 'cat3', 'cat1/subcat1', 'cat1/subcat2', 'cat1/subcat1/subcat3']) ['cat1', 'cat2', 'cat3', 'cat1/subcat1', 'cat1/subcat2', 'cat1/subcat1/subcat3'])
self.assertEqual(response.json()['tags'], ['tag1', 'tag2', 'tag3']) self.assertEqual(response.json()['tags'], ['tag1', 'tag2', 'tag3'])
self.assertEqual([p['name'] for p in response.json()['properties']], ['prop1', 'prop2', 'prop3']) self.assertEqual([p['name'] for p in response.json()['properties']], ['prop1', 'prop2', 'prop3'])
self.assertEqual(response.json()['domains'], ['example.com']) 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 authentication.tests import SignatureAuthClient, UserTestMixin, ToolshedTestCase
from files.tests import FilesTestMixin from files.tests import FilesTestMixin
from toolshed.models import InventoryItem, Category from toolshed.models import InventoryItem, Category
from toolshed.tests import InventoryTestMixin, CategoryTestMixin, TagTestMixin, PropertyTestMixin from toolshed.tests import InventoryTestMixin
client = SignatureAuthClient() client = SignatureAuthClient()