Skip to content

Commit

Permalink
SONARPY-1567 S5659: Add support for PyJWT (#1681)
Browse files Browse the repository at this point in the history
  • Loading branch information
maksim-grebeniuk-sonarsource authored Dec 8, 2023
1 parent b9b9cf3 commit 8ac9992
Show file tree
Hide file tree
Showing 2 changed files with 26 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ public class JwtVerificationCheck extends PythonSubscriptionCheck {
"jose.jws.verify");

private static final Set<String> UNVERIFIED_FQNS = Set.of(
"jwt.get_unverified_header",
"jose.jwt.get_unverified_header",
"jose.jwt.get_unverified_headers",
"jose.jws.get_unverified_header",
Expand All @@ -70,7 +71,7 @@ public class JwtVerificationCheck extends PythonSubscriptionCheck {

private static final String VERIFY_SIGNATURE_KEYWORD = "verify_signature";

public static final String JOSE_JWT_DECODE_FQN = "jose.jwt.decode";
public static final Set<String> VERIFY_SIGNATURE_OPTION_SUPPORTING_FUNCTION_FQNS = Set.of("jose.jwt.decode", "jwt.decode");

@Override
public void initialize(Context context) {
Expand All @@ -79,26 +80,30 @@ public void initialize(Context context) {

private static void verifyCallExpression(SubscriptionContext ctx) {
CallExpression call = (CallExpression) ctx.syntaxNode();

Symbol calleeSymbol = call.calleeSymbol();
if (calleeSymbol == null || calleeSymbol.fullyQualifiedName() == null) {
return;
}
if (WHERE_VERIFY_KWARG_SHOULD_BE_TRUE_FQNS.contains(calleeSymbol.fullyQualifiedName())) {

String calleeFqn = calleeSymbol.fullyQualifiedName();
if (WHERE_VERIFY_KWARG_SHOULD_BE_TRUE_FQNS.contains(calleeFqn)) {
RegularArgument verifyArg = TreeUtils.argumentByKeyword("verify", call.arguments());
if (verifyArg != null && Expressions.isFalsy(verifyArg.expression())) {
ctx.addIssue(verifyArg, MESSAGE);
return;
}
} else if (PROCESS_JWT_FQNS.contains(calleeSymbol.fullyQualifiedName())) {
} else if (PROCESS_JWT_FQNS.contains(calleeFqn)) {
Optional.ofNullable(TreeUtils.firstAncestorOfKind(call, Kind.FILE_INPUT, Kind.FUNCDEF))
.filter(scriptOrFunction -> !TreeUtils.hasDescendant(scriptOrFunction, JwtVerificationCheck::isCallToVerifyJwt))
.ifPresent(scriptOrFunction -> ctx.addIssue(call, MESSAGE));
} else if (UNVERIFIED_FQNS.contains(calleeSymbol.fullyQualifiedName())) {
} else if (UNVERIFIED_FQNS.contains(calleeFqn)) {
Optional.ofNullable(TreeUtils.nthArgumentOrKeyword(0, "", call.arguments()))
.flatMap(TreeUtils.toOptionalInstanceOfMapper(RegularArgument.class))
.map(RegularArgument::expression)
.ifPresent(argument -> ctx.addIssue(argument, MESSAGE));

} else if (JOSE_JWT_DECODE_FQN.equals(calleeSymbol.fullyQualifiedName())) {
}
if (VERIFY_SIGNATURE_OPTION_SUPPORTING_FUNCTION_FQNS.contains(calleeFqn)) {
Optional.ofNullable(TreeUtils.argumentByKeyword("options", call.arguments()))
.map(RegularArgument::expression)
.filter(JwtVerificationCheck::isListOrDictWithSensitiveEntry)
Expand Down
15 changes: 15 additions & 0 deletions python-checks/src/test/resources/checks/jwtVerification.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,18 @@ def python_jwt_process_without_verify(token):
def python_jwt_process_with_verify(token):
python_jwt.process_jwt(token)
python_jwt.verify_jwt(token)

def pyjwt_decode_token_1(token):
return jwt.decode(token, options={"verify_signature":False, "something":"something"}) # Noncompliant

def pyjwt_decode_token_2(token):
return jwt.decode(token, options=[("verify_signature", False), ("something", "something")]) # Noncompliant

def pyjwt_decode_token_secure_1(token):
return jwt.decode(token, algorithms="HS256", options={"verify_signature":True, "something":"something"}) # Compliant

def pyjwt_decode_token_secure_2(token):
return jwt.decode(token, algorithms="HS256") # Compliant

def pyjwt_decode_unverified_header(token):
return jwt.get_unverified_header(token) # Noncompliant

0 comments on commit 8ac9992

Please sign in to comment.