@@ -1014,6 +1014,93 @@ def test_update_recall_parameters_requires_auth(self, enable_multiuser: Any, cli
10141014 assert r .status_code == status .HTTP_401_UNAUTHORIZED
10151015
10161016
1017+ # ===========================================================================
1018+ # 7a2. Recall parameters image access control
1019+ # ===========================================================================
1020+
1021+
1022+ class TestRecallImageAccess :
1023+ """Tests that recall parameter image references are validated for read access."""
1024+
1025+ def test_recall_controlnet_with_other_users_image_rejected (
1026+ self , client : TestClient , mock_invoker : Invoker , user1_token : str , user2_token : str
1027+ ):
1028+ """User2 must not be able to reference user1's private image in a control layer."""
1029+ user1 = mock_invoker .services .users .get_by_email ("user1@test.com" )
1030+ assert user1 is not None
1031+ _save_image (mock_invoker , "victim-ctrl-img" , user1 .user_id )
1032+
1033+ r = client .post (
1034+ "/api/v1/recall/default" ,
1035+ json = {"control_layers" : [{"model_name" : "some-controlnet" , "image_name" : "victim-ctrl-img" }]},
1036+ headers = _auth (user2_token ),
1037+ )
1038+ assert r .status_code == status .HTTP_403_FORBIDDEN
1039+
1040+ def test_recall_ip_adapter_with_other_users_image_rejected (
1041+ self , client : TestClient , mock_invoker : Invoker , user1_token : str , user2_token : str
1042+ ):
1043+ """User2 must not be able to reference user1's private image in an IP adapter."""
1044+ user1 = mock_invoker .services .users .get_by_email ("user1@test.com" )
1045+ assert user1 is not None
1046+ _save_image (mock_invoker , "victim-ip-img" , user1 .user_id )
1047+
1048+ r = client .post (
1049+ "/api/v1/recall/default" ,
1050+ json = {"ip_adapters" : [{"model_name" : "some-ip-adapter" , "image_name" : "victim-ip-img" }]},
1051+ headers = _auth (user2_token ),
1052+ )
1053+ assert r .status_code == status .HTTP_403_FORBIDDEN
1054+
1055+ def test_recall_own_image_allowed (self , client : TestClient , mock_invoker : Invoker , user1_token : str ):
1056+ """Owner should be able to reference their own image in recall parameters."""
1057+ user1 = mock_invoker .services .users .get_by_email ("user1@test.com" )
1058+ assert user1 is not None
1059+ _save_image (mock_invoker , "own-ctrl-img" , user1 .user_id )
1060+
1061+ r = client .post (
1062+ "/api/v1/recall/default" ,
1063+ json = {"control_layers" : [{"model_name" : "some-controlnet" , "image_name" : "own-ctrl-img" }]},
1064+ headers = _auth (user1_token ),
1065+ )
1066+ # Should not be 403 (may fail downstream for other reasons, e.g. model not found)
1067+ assert r .status_code != status .HTTP_403_FORBIDDEN
1068+
1069+ def test_recall_shared_board_image_allowed (
1070+ self , client : TestClient , mock_invoker : Invoker , user1_token : str , user2_token : str
1071+ ):
1072+ """An image on a shared board should be usable in recall by any user."""
1073+ user1 = mock_invoker .services .users .get_by_email ("user1@test.com" )
1074+ assert user1 is not None
1075+ _save_image (mock_invoker , "shared-recall-img" , user1 .user_id )
1076+
1077+ board_id = _create_board (client , user1_token , "Shared Recall Board" )
1078+ _share_board (client , user1_token , board_id )
1079+ mock_invoker .services .board_image_records .add_image_to_board (board_id = board_id , image_name = "shared-recall-img" )
1080+
1081+ r = client .post (
1082+ "/api/v1/recall/default" ,
1083+ json = {"ip_adapters" : [{"model_name" : "some-ip-adapter" , "image_name" : "shared-recall-img" }]},
1084+ headers = _auth (user2_token ),
1085+ )
1086+ assert r .status_code != status .HTTP_403_FORBIDDEN
1087+
1088+ def test_recall_admin_can_reference_any_image (
1089+ self , client : TestClient , mock_invoker : Invoker , admin_token : str , user1_token : str
1090+ ):
1091+ """Admin should be able to reference any user's image."""
1092+ user1 = mock_invoker .services .users .get_by_email ("user1@test.com" )
1093+ assert user1 is not None
1094+ _save_image (mock_invoker , "admin-recall-img" , user1 .user_id )
1095+
1096+ r = client .post (
1097+ "/api/v1/recall/default" ,
1098+ json = {"control_layers" : [{"model_name" : "some-controlnet" , "image_name" : "admin-recall-img" }]},
1099+ headers = _auth (admin_token ),
1100+ )
1101+ assert r .status_code != status .HTTP_403_FORBIDDEN
1102+
1103+
10171104# ===========================================================================
10181105# 7b. Recall parameters cross-user isolation
10191106# ===========================================================================
0 commit comments