1414
1515from django .conf import settings
1616from django .contrib .auth .models import Permission
17+ from django .core .files .uploadedfile import SimpleUploadedFile
1718from django .test import tag as test_tag
1819from django .test .utils import override_settings
1920from django .urls import reverse
2021from django .utils import timezone
2122from drf_spectacular .drainage import GENERATOR_STATS
2223from drf_spectacular .settings import spectacular_settings
2324from drf_spectacular .validation import validate_schema
25+ from parameterized import parameterized
2426from rest_framework import status
2527from rest_framework .authtoken .models import Token
2628from rest_framework .mixins import (
@@ -368,6 +370,11 @@ class TestType(Enum):
368370
369371class BaseClass :
370372 class RESTEndpointTest (DojoAPITestCase ):
373+ NOT_AUTHORIZED_USER_ID = 3
374+ GLOBAL_READER_USER_ID = 5
375+ GLOBAL_WRITER_USER_ID = 4
376+ GLOBAL_OWNER_USER_ID = 6
377+
371378 def __init__ (self , * args , ** kwargs ):
372379 DojoAPITestCase .__init__ (self , * args , ** kwargs )
373380
@@ -380,19 +387,25 @@ def setUp(self):
380387 self .schema = get_open_api3_json_schema ()
381388
382389 def setUp_not_authorized (self ):
383- testuser = User .objects .get (id = 3 )
390+ testuser = User .objects .get (id = self . NOT_AUTHORIZED_USER_ID )
384391 token = Token .objects .get (user = testuser )
385392 self .client = APIClient ()
386393 self .client .credentials (HTTP_AUTHORIZATION = "Token " + token .key )
387394
388395 def setUp_global_reader (self ):
389- testuser = User .objects .get (id = 5 )
396+ testuser = User .objects .get (id = self .GLOBAL_READER_USER_ID )
397+ token = Token .objects .get (user = testuser )
398+ self .client = APIClient ()
399+ self .client .credentials (HTTP_AUTHORIZATION = "Token " + token .key )
400+
401+ def setUp_global_writer (self ):
402+ testuser = User .objects .get (id = self .GLOBAL_WRITER_USER_ID )
390403 token = Token .objects .get (user = testuser )
391404 self .client = APIClient ()
392405 self .client .credentials (HTTP_AUTHORIZATION = "Token " + token .key )
393406
394407 def setUp_global_owner (self ):
395- testuser = User .objects .get (id = 6 )
408+ testuser = User .objects .get (id = self . GLOBAL_OWNER_USER_ID )
396409 token = Token .objects .get (user = testuser )
397410 self .client = APIClient ()
398411 self .client .credentials (HTTP_AUTHORIZATION = "Token " + token .key )
@@ -1179,6 +1192,42 @@ def __init__(self, *args, **kwargs):
11791192 self .deleted_objects = 23
11801193 BaseClass .RESTEndpointTest .__init__ (self , * args , ** kwargs )
11811194
1195+ @parameterized .expand (
1196+ [
1197+ ("files" , {"title" : "test" , "file" : b"empty" }),
1198+ ("notes" , {"entry" : "string" }),
1199+ ],
1200+ )
1201+ def test_related_objects (self , related_object_path , payload ):
1202+ """
1203+ Tests that BaseRelatedObjectPermission enforces the permissions not associated
1204+ with the base object. For example, even though a request to add a note to an
1205+ engagement is a POST, we do not need engagement add permissions, but rather
1206+ engagement edit permissions since that is what is defined in the
1207+ UserHasEngagementRelatedObjectPermission class
1208+ """
1209+ self .setUp_global_reader ()
1210+ # Get an engagement
1211+ response = self .client .get (self .url , format = "json" )
1212+ self .assertEqual (200 , response .status_code , response .content [:1000 ])
1213+ engagement_id = response .data ["results" ][0 ]["id" ]
1214+ # Attempt to add a related object
1215+ relative_url = f"{ self .url } { engagement_id } /{ related_object_path } /"
1216+ response = self .client .post (relative_url , payload )
1217+ self .assertEqual (403 , response .status_code , response .content [:1000 ])
1218+ # Now switch to a user with edit permissions (but not create)
1219+ self .setUp_global_writer ()
1220+ # Retry adding the related object
1221+ if related_object_path == "files" :
1222+ # Convert bytes to a mock uploaded file
1223+ payload ["file" ] = SimpleUploadedFile (
1224+ name = "test_file.txt" ,
1225+ content = payload ["file" ], # the b"empty"
1226+ content_type = "text/plain" ,
1227+ )
1228+ response = self .client .post (relative_url , payload )
1229+ self .assertEqual (201 , response .status_code , response .content [:1000 ])
1230+
11821231
11831232class RiskAcceptanceTest (BaseClass .BaseClassTest ):
11841233 fixtures = ["dojo_testdata.json" ]
@@ -3440,6 +3489,66 @@ def test_delete(self):
34403489 response = self .client .delete (relative_url )
34413490 self .assertEqual (409 , response .status_code , response .content [:1000 ])
34423491
3492+ def test_list_method_requires_no_authorization (self ):
3493+ """
3494+ Tests the use case of not supplying GET permissions for the BaseDjangoModelPermission
3495+ class used in the UserHasDevelopmentEnvironmentPermission class.
3496+ """
3497+ self .setUp_not_authorized ()
3498+ response = self .client .get (self .url , format = "json" )
3499+ self .assertEqual (200 , response .status_code , response .content [:1000 ])
3500+
3501+ @parameterized .expand (
3502+ [
3503+ (
3504+ "add_development_environment" ,
3505+ "post" ,
3506+ 201 ,
3507+ {
3508+ "name" : "Test_1" ,
3509+ },
3510+ ),
3511+ (
3512+ "change_development_environment" ,
3513+ "put" ,
3514+ 200 ,
3515+ {"name" : "Test_2" },
3516+ ),
3517+ (
3518+ "change_development_environment" ,
3519+ "put" ,
3520+ 200 ,
3521+ {"name" : "Test_3" },
3522+ ),
3523+ (
3524+ "delete_development_environment" ,
3525+ "delete" ,
3526+ 409 , # Deletion is blocked because of existing references, but it is better than 403 for this test
3527+ None ,
3528+ ),
3529+ ],
3530+ )
3531+ def test_user_needs_configuration_permission (self , codename , method , expected_status , payload ):
3532+ """
3533+ Tests that BaseDjangoModelPermission enforces the django configuration permissions
3534+ through the class used in the UserHasDevelopmentEnvironmentPermission class.
3535+ """
3536+ # Ensure we get a 403 first
3537+ self .setUp_not_authorized ()
3538+ response = self .client .post (self .url , payload , format = "json" )
3539+ self .assertEqual (403 , response .status_code , response .content [:1000 ])
3540+ # Now Get the same user as self.client is using, add the permission, and try again
3541+ testuser = User .objects .get (id = self .NOT_AUTHORIZED_USER_ID )
3542+ permission = Permission .objects .get (codename = codename )
3543+ testuser .user_permissions .add (permission )
3544+ if method in {"put" , "patch" , "delete" }:
3545+ current_objects = self .client .get (self .url , format = "json" ).data
3546+ relative_url = self .url + "{}/" .format (current_objects ["results" ][- 1 ]["id" ])
3547+ else :
3548+ relative_url = self .url
3549+ response = getattr (self .client , method )(relative_url , payload , format = "json" )
3550+ self .assertEqual (expected_status , response .status_code , response .content [:1000 ])
3551+
34433552
34443553class TestTypeTest (BaseClass .AuthenticatedViewTest ):
34453554 fixtures = ["dojo_testdata.json" ]
0 commit comments