3030public class AwsExpectedBucketOwnerCheck extends PythonSubscriptionCheck {
3131
3232 private static final String MESSAGE = "Add the 'ExpectedBucketOwner' parameter to verify S3 bucket ownership." ;
33+ private static final String MESSAGE_EXTRA_ARGS = "Add the 'ExpectedBucketOwner' to the 'ExtraArgs' parameter to verify S3 bucket ownership." ;
3334 private static final List <String > S3_CLIENT_FQN_PREFIX = List .of ("botocore.client.BaseClient" , "aiobotocore.client.AioBaseClient" );
3435 private static final List <String > S3_METHODS_REQUIRING_EXPECTED_BUCKET_OWNER = List .of (
3536 "copy_object" ,
@@ -56,8 +57,6 @@ public class AwsExpectedBucketOwnerCheck extends PythonSubscriptionCheck {
5657 "delete_object_tagging" ,
5758 "delete_objects" ,
5859 "delete_public_access_block" ,
59- "download_file" ,
60- "download_fileobj" ,
6160 "get_bucket_accelerate_configuration" ,
6261 "get_bucket_acl" ,
6362 "get_bucket_analytics_configuration" ,
@@ -134,16 +133,25 @@ public class AwsExpectedBucketOwnerCheck extends PythonSubscriptionCheck {
134133 "select_object_content" ,
135134 "update_bucket_metadata_inventory_table_configuration" ,
136135 "update_bucket_metadata_journal_table_configuration" ,
137- "upload_file" ,
138- "upload_fileobj" ,
139136 "upload_part" ,
140137 "upload_part_copy" );
141138
139+ private static final List <String > UPLOAD_DOWNLOAD_FILE_FQN = List .of (
140+ "upload_file" ,
141+ "upload_fileobj" ,
142+ "download_file" ,
143+ "download_fileobj" );
144+
142145 private static final List <String > FQN_TO_CHECK = S3_CLIENT_FQN_PREFIX .stream ()
143146 .flatMap (prefix -> S3_METHODS_REQUIRING_EXPECTED_BUCKET_OWNER .stream ().map (method -> prefix + "." + method ))
144147 .toList ();
145148
149+ private static final List <String > UPLOAD_DOWNLOAD_FQN_TO_CHECK = S3_CLIENT_FQN_PREFIX .stream ()
150+ .flatMap (prefix -> UPLOAD_DOWNLOAD_FILE_FQN .stream ().map (method -> prefix + "." + method ))
151+ .toList ();
152+
146153 private TypeCheckMap <Object > isS3MethodCall ;
154+ private TypeCheckMap <Object > isUploadDownloadFileMethodCall ;
147155
148156 @ Override
149157 public void initialize (Context context ) {
@@ -154,17 +162,28 @@ public void initialize(Context context) {
154162 private void setupTypeChecker (SubscriptionContext ctx ) {
155163 Object object = new Object ();
156164 isS3MethodCall = new TypeCheckMap <>();
165+ isUploadDownloadFileMethodCall = new TypeCheckMap <>();
157166
158167 for (var fqn : FQN_TO_CHECK ) {
159168 var typeChecker = ctx .typeChecker ().typeCheckBuilder ().isTypeWithFqn (fqn );
160169 isS3MethodCall .put (typeChecker , object );
161170 }
171+ for (var fqn : UPLOAD_DOWNLOAD_FQN_TO_CHECK ) {
172+ var typeChecker = ctx .typeChecker ().typeCheckBuilder ().isTypeWithFqn (fqn );
173+ isUploadDownloadFileMethodCall .put (typeChecker , object );
174+ }
162175 }
163176
164177 private void checkCall (SubscriptionContext ctx ) {
165178 CallExpression callExpression = (CallExpression ) ctx .syntaxNode ();
166179
167180 PythonType type = TreeUtils .inferSingleAssignedExpressionType (callExpression .callee ());
181+
182+ if (isUploadDownloadFileMethodCallWithoutExtraArgs (type , callExpression )){
183+ ctx .addIssue (callExpression .callee (), MESSAGE_EXTRA_ARGS );
184+ return ;
185+ }
186+
168187 if (isS3MethodCall .getOptionalForType (type ).isEmpty ()) {
169188 return ;
170189 }
@@ -179,10 +198,21 @@ private void checkCall(SubscriptionContext ctx) {
179198
180199 ctx .addIssue (callExpression .callee (), MESSAGE );
181200 }
201+
202+ private boolean isUploadDownloadFileMethodCallWithoutExtraArgs (PythonType type , CallExpression callExpression ){
203+ return isUploadDownloadFileMethodCall .getOptionalForType (type )
204+ .filter (obj -> !hasExtraArgsParameter (callExpression )).isPresent ();
205+ }
206+
207+ private static boolean hasExtraArgsParameter (CallExpression callExpression ){
208+ return TreeUtils .argumentByKeyword ("ExtraArgs" , callExpression .arguments ()) != null ;
209+ }
210+
182211 private static boolean hasArgsOrKwargsParams (CallExpression callExpression ){
183212 return callExpression .arguments ().stream ().anyMatch (arg -> arg .is (Tree .Kind .UNPACKING_EXPR ));
184213 }
185214
215+
186216 private static boolean hasExpectedBucketOwnerParameter (CallExpression callExpression ) {
187217 return TreeUtils .argumentByKeyword ("ExpectedBucketOwner" , callExpression .arguments ()) != null ;
188218 }
0 commit comments