5050import org .sonar .plugins .python .api .tree .UnpackingExpression ;
5151import org .sonar .plugins .python .api .tree .WithItem ;
5252import org .sonar .plugins .python .api .tree .WithStatement ;
53+ import org .sonar .plugins .python .api .types .v2 .TriBool ;
5354import org .sonar .python .checks .utils .Expressions ;
5455import org .sonar .python .tree .RegularArgumentImpl ;
5556import org .sonar .python .tree .TreeUtils ;
57+ import org .sonar .python .types .v2 .TypeCheckBuilder ;
58+ import org .sonar .python .types .v2 .TypeCheckMap ;
5659
5760import static java .util .Optional .ofNullable ;
5861
@@ -63,13 +66,27 @@ public class VerifiedSslTlsCertificateCheck extends PythonSubscriptionCheck {
6366 private static final String MESSAGE = "Enable server certificate validation on this SSL/TLS connection." ;
6467 private static final String VERIFY_NONE = Fqn .ssl ("VERIFY_NONE" );
6568
69+ private TypeCheckBuilder requestsSessionTypeCheck ;
70+ private TypeCheckMap <Set <String >> requestsVerifyArguments ;
71+
6672 @ Override
6773 public void initialize (Context context ) {
74+ context .registerSyntaxNodeConsumer (Tree .Kind .FILE_INPUT , this ::initTypeChecks );
6875 context .registerSyntaxNodeConsumer (Tree .Kind .WITH_STMT , VerifiedSslTlsCertificateCheck ::verifyAioHttpWithSession );
6976 context .registerSyntaxNodeConsumer (Tree .Kind .CALL_EXPR , VerifiedSslTlsCertificateCheck ::sslSetVerifyCheck );
70- context .registerSyntaxNodeConsumer (Tree .Kind .CALL_EXPR , VerifiedSslTlsCertificateCheck ::requestsCheck );
77+ context .registerSyntaxNodeConsumer (Tree .Kind .CALL_EXPR , this ::requestsCheck );
7178 context .registerSyntaxNodeConsumer (Tree .Kind .REGULAR_ARGUMENT , VerifiedSslTlsCertificateCheck ::standardSslCheckForRegularArgument );
7279 context .registerSyntaxNodeConsumer (Tree .Kind .ASSIGNMENT_STMT , VerifiedSslTlsCertificateCheck ::standardSslCheckForAssignmentStatement );
80+ context .registerSyntaxNodeConsumer (Tree .Kind .ASSIGNMENT_STMT , this ::requestsSessionAssignmentCheck );
81+ }
82+
83+ private void initTypeChecks (SubscriptionContext ctx ) {
84+ requestsSessionTypeCheck = ctx .typeChecker ().typeCheckBuilder ().isInstanceOf (REQUESTS_SESSIONS_FQN );
85+ requestsVerifyArguments = new TypeCheckMap <>();
86+ CALLS_WHERE_TO_ENFORCE_TRUE_ARGUMENT .forEach (fqn -> {
87+ var check = ctx .typeChecker ().typeCheckBuilder ().isTypeWithFqn (fqn );
88+ requestsVerifyArguments .put (check , VERIFY_ARG_NAME );
89+ });
7390 }
7491
7592 private static void verifyAioHttpWithSession (SubscriptionContext ctx ) {
@@ -104,12 +121,14 @@ private static void verifyAioHttpSessionSymbolUsages(SubscriptionContext ctx, Sy
104121 .forEach (sessionCallExpr -> verifyVulnerableMethods (ctx , sessionCallExpr , VERIFY_SSL_ARG_NAMES ));
105122 }
106123
107- /** Fully qualified name of the <code>set_verify</code> used in <code>sslSetVerifyCheck</code>. */
124+ /**
125+ * Fully qualified name of the <code>set_verify</code> used in <code>sslSetVerifyCheck</code>.
126+ */
108127 private static final String SET_VERIFY = Fqn .context ("set_verify" );
109128
110129 /**
111130 * Check for the <code>OpenSSL.SSL.Context.set_verify</code> flag settings.
112- *
131+ * <p>
113132 * Searches for `set_verify` invocations on instances of `OpenSSL.SSL.Context`,
114133 * extracts the flags from the first argument, checks that the combination of flags is secure.
115134 *
@@ -136,7 +155,9 @@ private static void sslSetVerifyCheck(SubscriptionContext subscriptionContext) {
136155 }
137156 }
138157
139- /** Helper methods for generating FQNs frequently used in this check. */
158+ /**
159+ * Helper methods for generating FQNs frequently used in this check.
160+ */
140161 private static class Fqn {
141162 private static String context (@ SuppressWarnings ("SameParameterValue" ) String method ) {
142163 return ssl ("Context." + method );
@@ -186,22 +207,19 @@ private static Optional<IssueReport> checkFlagSettings(Set<QualifiedExpression>
186207 return Optional .empty ();
187208 }
188209
189- /** Message and a token closest to the problematic position. Glorified <code>Pair<A,B></code>. */
190- private static class IssueReport {
191- final String message ;
192- final Token token ;
193-
194- private IssueReport (String message , Token token ) {
195- this .message = message ;
196- this .token = token ;
197- }
210+ /**
211+ * Message and a token closest to the problematic position. Glorified <code>Pair<A,B></code>.
212+ */
213+ private record IssueReport (String message , Token token ) {
198214 }
199215
200216 public static final Set <String > VERIFY_ARG_NAME = Set .of ("verify" );
217+ public static final String VERIFY_FIELD_NAME = "verify" ;
201218 public static final Set <String > VERIFY_SSL_ARG_NAMES = Set .of ("verify_ssl" , "ssl" );
202219 /**
203220 * Set of FQNs of methods in <code>requests</code>-module that have the vulnerable <code>verify</code>-option.
204221 */
222+ public static final String REQUESTS_SESSIONS_FQN = "requests.sessions.Session" ;
205223 private static final Set <String > CALLS_WHERE_TO_ENFORCE_TRUE_ARGUMENT = Set .of (
206224 "requests.api.request" ,
207225 "requests.api.get" ,
@@ -211,6 +229,13 @@ private IssueReport(String message, Token token) {
211229 "requests.api.delete" ,
212230 "requests.api.patch" ,
213231 "requests.api.options" ,
232+ REQUESTS_SESSIONS_FQN + ".request" ,
233+ REQUESTS_SESSIONS_FQN + ".get" ,
234+ REQUESTS_SESSIONS_FQN + ".head" ,
235+ REQUESTS_SESSIONS_FQN + ".post" ,
236+ REQUESTS_SESSIONS_FQN + ".put" ,
237+ REQUESTS_SESSIONS_FQN + ".delete" ,
238+ REQUESTS_SESSIONS_FQN + ".patch" ,
214239 "httpx.request" ,
215240 "httpx.stream" ,
216241 "httpx.get" ,
@@ -223,18 +248,25 @@ private IssueReport(String message, Token token) {
223248 "httpx.Client" ,
224249 "httpx.AsyncClient" );
225250
226- private static void requestsCheck (SubscriptionContext subscriptionContext ) {
227- var callExpr = (CallExpression ) subscriptionContext .syntaxNode ();
228- var isVulnerableMethod = ofNullable (callExpr .calleeSymbol ())
229- .map (Symbol ::fullyQualifiedName )
230- .filter (CALLS_WHERE_TO_ENFORCE_TRUE_ARGUMENT ::contains )
231- .isPresent ();
232-
233- if (isVulnerableMethod ) {
234- verifyVulnerableMethods (subscriptionContext , callExpr , VERIFY_ARG_NAME );
251+ private void requestsSessionAssignmentCheck (SubscriptionContext ctx ) {
252+ var assignment = (AssignmentStatement ) ctx .syntaxNode ();
253+ // SONARPY-2268
254+ if (assignment .lhsExpressions ().get (0 ).expressions ().size () == 1
255+ && assignment .lhsExpressions ().get (0 ).expressions ().get (0 ) instanceof QualifiedExpression qualifiedExpression
256+ && requestsSessionTypeCheck .check (qualifiedExpression .qualifier ().typeV2 ()) == TriBool .TRUE
257+ && VERIFY_FIELD_NAME .equals (qualifiedExpression .name ().name ())
258+ && Expressions .isFalsy (assignment .assignedValue ())
259+ ) {
260+ ctx .addIssue (assignment , MESSAGE );
235261 }
236262 }
237263
264+ private void requestsCheck (SubscriptionContext subscriptionContext ) {
265+ var callExpr = (CallExpression ) subscriptionContext .syntaxNode ();
266+ requestsVerifyArguments .getOptionalForType (callExpr .callee ().typeV2 ())
267+ .ifPresent (args -> verifyVulnerableMethods (subscriptionContext , callExpr , args ));
268+ }
269+
238270 private static void verifyVulnerableMethods (SubscriptionContext ctx , CallExpression callExpr , Set <String > argumentNames ) {
239271 var verifyRhs = searchVerifyAssignment (callExpr , argumentNames )
240272 .or (() -> searchVerifyInKwargs (callExpr , argumentNames ));
@@ -278,7 +310,7 @@ private static Optional<List<Expression>> searchVerifyAssignment(CallExpression
278310 /**
279311 * Attempts to find the <code>rhs</code> in some definition <code>kwargs = { 'verify': rhs }</code>
280312 * of <code>kwargs</code> used in the arguments of the given <code>callExpression</code>.
281- *
313+ * <p>
282314 * Returns list of problematic expressions in the reverse order of importance (the <code>kwargs</code>-argument comes
283315 * first, the setting in the dictionary comes last).
284316 */
@@ -292,13 +324,17 @@ private static Optional<List<Expression>> searchVerifyInKwargs(CallExpression ca
292324 .map (arg -> ((UnpackingExpression ) arg ).expression ())
293325 .filter (Name .class ::isInstance )
294326 .findFirst ()
295- .flatMap (name -> Optional .ofNullable (Expressions .singleAssignedValue ((Name ) name ))
327+ .map (Name .class ::cast )
328+ .flatMap (name -> Optional .ofNullable (Expressions .singleAssignedValue (name ))
296329 .filter (DictionaryLiteral .class ::isInstance )
297- .flatMap (dict -> searchDangerousVerifySettingInDictionary ((DictionaryLiteral ) dict , argumentNames )
298- .map (settingInDict -> Arrays .asList (name , settingInDict ))));
330+ .map (DictionaryLiteral .class ::cast )
331+ .flatMap (dict -> searchDangerousVerifySettingInDictionary (dict , argumentNames ))
332+ .map (settingInDict -> Arrays .asList (name , settingInDict )));
299333 }
300334
301- /** Searches for a dangerous falsy <code>verify: False</code> in a dictionary literal. */
335+ /**
336+ * Searches for a dangerous falsy <code>verify: False</code> in a dictionary literal.
337+ */
302338 private static Optional <Expression > searchDangerousVerifySettingInDictionary (DictionaryLiteral dict , Set <String > argumentNames ) {
303339 return dict .elements ().stream ()
304340 .filter (KeyValuePair .class ::isInstance )
@@ -327,11 +363,15 @@ private static boolean isFalsyCollection(Expression expr) {
327363 return false ;
328364 }
329365
330- /** FQNs of collection constructors that yield a falsy collection if invoked without arguments. */
366+ /**
367+ * FQNs of collection constructors that yield a falsy collection if invoked without arguments.
368+ */
331369 private static final Set <String > NO_ARG_FALSY_COLLECTION_CONSTRUCTORS = new HashSet <>(Arrays .asList (
332370 "set" , "list" , "dict" ));
333371
334- /** Detects expressions like <code>dict()</code> or <code>list()</code>. */
372+ /**
373+ * Detects expressions like <code>dict()</code> or <code>list()</code>.
374+ */
335375 private static boolean isFalsyNoArgCollectionConstruction (CallExpression callExpr , String fqn ) {
336376 return NO_ARG_FALSY_COLLECTION_CONSTRUCTORS .contains (fqn ) && callExpr .arguments ().isEmpty ();
337377 }
@@ -378,7 +418,9 @@ private static void standardSslCheckForRegularArgument(SubscriptionContext subsc
378418 .ifPresent (vulnTok -> subscriptionContext .addIssue (vulnTok .token , MESSAGE ));
379419 }
380420
381- /** Finds the next higher line where a binding usage occurs. */
421+ /**
422+ * Finds the next higher line where a binding usage occurs.
423+ */
382424 private static int findNextAssignmentLine (List <Usage > usages , int firstAssignmentLine ) {
383425 int closestHigher = Integer .MAX_VALUE ;
384426 for (Usage u : usages ) {
@@ -394,7 +436,7 @@ private static int findNextAssignmentLine(List<Usage> usages, int firstAssignmen
394436
395437 /**
396438 * Selects all non-binding usages between first assignment and next assignment.
397- *
439+ * <p>
398440 * We assume that in a vast majority of cases, there will be no complex control flow between the instantiation
399441 * of the context and the modification of the settings, thus selecting and sorting usages by line numbers
400442 * should suffice here.
@@ -419,7 +461,9 @@ private static List<Usage> selectRelevantModifyingUsages(List<Usage> usages, int
419461 "ssl.create_default_context" , false ,
420462 "ssl._create_default_https_context" , false );
421463
422- /** Pair and a mutable cell for combining all updates to <code>verify_mode</code>. */
464+ /**
465+ * Pair and a mutable cell for combining all updates to <code>verify_mode</code>.
466+ */
423467 private static class VulnerabilityAndProblematicToken {
424468 boolean isInvisibleDefaultPreset ;
425469 boolean isVulnerable ;
0 commit comments