Skip to content

Commit f5ccb46

Browse files
ghislainpiotsonartech
authored andcommitted
SONARPY-2798 Migrate parts of S5344 to TypeInferenceV2
GitOrigin-RevId: 6bcd468d749723f3fb70305887c61a6a87daaad7
1 parent 046031d commit f5ccb46

1 file changed

Lines changed: 25 additions & 11 deletions

File tree

python-checks/src/main/java/org/sonar/python/checks/hotspots/FastHashingOrPlainTextCheck.java

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
import org.sonar.python.checks.utils.Expressions;
4343
import org.sonar.python.tree.TreeUtils;
4444
import org.sonar.python.types.v2.TypeCheckBuilder;
45+
import org.sonar.python.types.v2.TypeCheckMap;
4546

4647
import static org.sonar.python.checks.hotspots.CommonValidationUtils.isEqualTo;
4748
import static org.sonar.python.checks.hotspots.CommonValidationUtils.isLessThan;
@@ -188,15 +189,18 @@ public class FastHashingOrPlainTextCheck extends PythonSubscriptionCheck {
188189
Map.entry("hashlib.pbkdf2_hmac", List.of(HASHLIB_PBKDF2)),
189190
Map.entry("cryptography.hazmat.primitives.kdf.scrypt.Scrypt", List.of(CRYPTOGRAPHY_R, CRYPTOGRAPHY_N, CRYPTOGRAPHY_LENGTH)),
190191
Map.entry("cryptography.hazmat.primitives.kdf.pbkdf2.PBKDF2HMAC", List.of(CRYPTOGRAPHY_PBKDF2)),
191-
Map.entry("passlib.hash.scrypt.using", List.of(PASSLIB_BLOCK_SIZE, PASSLIB_ROUNDS)),
192+
Map.entry("passlib.handlers.scrypt.scrypt.using", List.of(PASSLIB_BLOCK_SIZE, PASSLIB_ROUNDS)),
192193
Map.entry("argon2.PasswordHasher", List.of(new Argon2PasswordHasherValidator(0, 1, 2))),
193194
Map.entry("argon2.Parameters", List.of(new Argon2PasswordHasherValidator(4, 5, 6))),
194195
Map.entry("argon2.low_level.hash_secret", List.of(new Argon2PasswordHasherValidator(2, 3, 4))),
195196
Map.entry("argon2.low_level.hash_secret_raw", List.of(new Argon2PasswordHasherValidator(2, 3, 4))),
196197
Map.entry("passlib.handlers.argon2._Argon2Common.using", List.of(new Argon2PasswordHasherValidator(3, 4, 5))),
197198
Map.entry("bcrypt.gensalt", List.of(BCRYPT_GENSALT)),
198199
Map.entry("bcrypt.kdf", List.of(BCRYPT_KDF)),
199-
Map.entry("flask_bcrypt.generate_password_hash", List.of(FLASK_BCRYPT)),
200+
Map.entry("flask_bcrypt.generate_password_hash", List.of(FLASK_BCRYPT))
201+
);
202+
203+
private static final Map<String, Collection<CallValidator>> CALL_EXPRESSION_VALIDATORS_V1 = Map.ofEntries(
200204
Map.entry("flask_bcrypt.Bcrypt.generate_password_hash", List.of(FLASK_BCRYPT))
201205
);
202206

@@ -210,6 +214,7 @@ public class FastHashingOrPlainTextCheck extends PythonSubscriptionCheck {
210214

211215
private TypeCheckBuilder argon2CheapestProfileTypeChecker = null;
212216
private TypeCheckBuilder flaskConfigTypeChecker = null;
217+
private TypeCheckMap<Collection<CallValidator>> typeCheckMap = new TypeCheckMap<>();
213218
@Override
214219
public void initialize(Context context) {
215220
context.registerSyntaxNodeConsumer(Tree.Kind.FILE_INPUT, this::registerTypeCheckers);
@@ -219,7 +224,7 @@ public void initialize(Context context) {
219224
}
220225
context.registerSyntaxNodeConsumer(Tree.Kind.ASSIGNMENT_STMT, FastHashingOrPlainTextCheck::checkDjangoHasher);
221226
});
222-
context.registerSyntaxNodeConsumer(Tree.Kind.CALL_EXPR, FastHashingOrPlainTextCheck::checkCallExpr);
227+
context.registerSyntaxNodeConsumer(Tree.Kind.CALL_EXPR, this::checkCallExpr);
223228
context.registerSyntaxNodeConsumer(Tree.Kind.NAME, this::checkName);
224229
context.registerSyntaxNodeConsumer(Tree.Kind.ASSIGNMENT_STMT, subscriptionContext -> checkAssignment(subscriptionContext, flaskConfigTypeChecker));
225230
}
@@ -249,6 +254,11 @@ private static void checkDjangoHasher(SubscriptionContext subscriptionContext) {
249254
private void registerTypeCheckers(SubscriptionContext subscriptionContext) {
250255
argon2CheapestProfileTypeChecker = subscriptionContext.typeChecker().typeCheckBuilder().isTypeWithFqn("argon2.profiles.CHEAPEST");
251256
flaskConfigTypeChecker = subscriptionContext.typeChecker().typeCheckBuilder().isInstanceOf("flask.config.Config");
257+
typeCheckMap = new TypeCheckMap<>();
258+
CALL_EXPRESSION_VALIDATORS.forEach((key, value) -> {
259+
var typeCheckBuilder = subscriptionContext.typeChecker().typeCheckBuilder().isTypeWithFqn(key);
260+
typeCheckMap.put(typeCheckBuilder, value);
261+
});
252262
}
253263

254264
private static void checkAssignment(SubscriptionContext subscriptionContext, TypeCheckBuilder flaskConfigTypeChecker) {
@@ -298,18 +308,22 @@ private static boolean isChildOf(AssignmentStatement ancestorAssign, Name name)
298308
return ancestorAssign.lhsExpressions().stream().flatMap(expressionList -> expressionList.children().stream()).anyMatch(tree -> tree == name);
299309
}
300310

301-
private static void checkCallExpr(SubscriptionContext subscriptionContext) {
311+
private void checkCallExpr(SubscriptionContext subscriptionContext) {
302312
CallExpression callExpression = (CallExpression) subscriptionContext.syntaxNode();
303313
var qualifiedName = qualifiedNameOrEmpty(callExpression);
304-
var configs = CALL_EXPRESSION_VALIDATORS.getOrDefault(qualifiedName, List.of());
305-
306-
configs.forEach(config -> config.validate(subscriptionContext, callExpression));
307-
308-
// We can't directly type check against the callee type (or FQN) for the passlib PBKDF2 because both type inference engine resolve passlib.utils.handlers.HasRounds.using
309-
// which will result in false positives
314+
var configsV2 = typeCheckMap.getOptionalForType(callExpression.callee().typeV2()).orElse(List.of());
315+
configsV2.forEach(config -> config.validate(subscriptionContext, callExpression));
316+
// We need to keep some V1 checks because of SONARPY-2268.
317+
// When resolving members, we can get an UnknownType for the callee. This looses all type information and makes typechecking impossible
318+
// We could use the qualified expression mechanism to bypass this limitation, however handling it at the type inference level is better
319+
var configsV1 = CALL_EXPRESSION_VALIDATORS_V1.getOrDefault(qualifiedName, List.of());
320+
configsV1.forEach(config -> config.validate(subscriptionContext, callExpression));
321+
322+
// We can't directly type check against the callee type (or FQN) for the passlib PBKDF2 because both type inference engine resolve passlib.utils.handlers.HasRounds.using or
323+
// passlib.ifc.PasswordHash.using which will result in false positives
310324
if (callExpression.callee().is(Tree.Kind.QUALIFIED_EXPR)) {
311325
var fqn = TreeUtils.fullyQualifiedNameFromQualifiedExpression(((QualifiedExpression) callExpression.callee())).orElse("");
312-
configs = QUALIFIED_EXPR_VALIDATOR.getOrDefault(fqn, List.of());
326+
var configs = QUALIFIED_EXPR_VALIDATOR.getOrDefault(fqn, List.of());
313327
configs.forEach(config -> config.validate(subscriptionContext, callExpression));
314328
}
315329
}

0 commit comments

Comments
 (0)