19
19
*/
20
20
package org .sonar .python .checks ;
21
21
22
+ import java .util .Collection ;
23
+ import java .util .List ;
22
24
import java .util .Optional ;
23
25
import java .util .Set ;
26
+ import java .util .stream .Stream ;
24
27
import javax .annotation .Nullable ;
25
28
import org .sonar .check .Rule ;
26
29
import org .sonar .plugins .python .api .PythonSubscriptionCheck ;
27
30
import org .sonar .plugins .python .api .SubscriptionContext ;
28
31
import org .sonar .plugins .python .api .symbols .Symbol ;
32
+ import org .sonar .plugins .python .api .symbols .Usage ;
33
+ import org .sonar .plugins .python .api .tree .Argument ;
34
+ import org .sonar .plugins .python .api .tree .AssignmentStatement ;
29
35
import org .sonar .plugins .python .api .tree .CallExpression ;
30
36
import org .sonar .plugins .python .api .tree .DictionaryLiteral ;
31
37
import org .sonar .plugins .python .api .tree .Expression ;
38
+ import org .sonar .plugins .python .api .tree .ExpressionList ;
32
39
import org .sonar .plugins .python .api .tree .KeyValuePair ;
33
40
import org .sonar .plugins .python .api .tree .ListLiteral ;
34
41
import org .sonar .plugins .python .api .tree .Name ;
42
+ import org .sonar .plugins .python .api .tree .QualifiedExpression ;
35
43
import org .sonar .plugins .python .api .tree .RegularArgument ;
36
44
import org .sonar .plugins .python .api .tree .StringLiteral ;
45
+ import org .sonar .plugins .python .api .tree .SubscriptionExpression ;
37
46
import org .sonar .plugins .python .api .tree .Tree ;
38
47
import org .sonar .plugins .python .api .tree .Tree .Kind ;
39
48
import org .sonar .plugins .python .api .tree .Tuple ;
@@ -55,6 +64,8 @@ public class JwtVerificationCheck extends PythonSubscriptionCheck {
55
64
"python_jwt.verify_jwt" ,
56
65
"jwt.verify_jwt" );
57
66
67
+ private static final Set <String > ALLOWED_KEYS_ACCESS = Set .of ("jku" , "jwk" , "kid" , "x5u" , "x5c" , "x5t" , "xt#256" );
68
+
58
69
private static final Set <String > WHERE_VERIFY_KWARG_SHOULD_BE_TRUE_FQNS = Set .of (
59
70
"jwt.decode" ,
60
71
"jose.jws.verify" );
@@ -66,8 +77,7 @@ public class JwtVerificationCheck extends PythonSubscriptionCheck {
66
77
"jose.jws.get_unverified_header" ,
67
78
"jose.jws.get_unverified_headers" ,
68
79
"jose.jwt.get_unverified_claims" ,
69
- "jose.jws.get_unverified_claims"
70
- );
80
+ "jose.jws.get_unverified_claims" );
71
81
72
82
private static final String VERIFY_SIGNATURE_KEYWORD = "verify_signature" ;
73
83
@@ -97,7 +107,7 @@ private static void verifyCallExpression(SubscriptionContext ctx) {
97
107
Optional .ofNullable (TreeUtils .firstAncestorOfKind (call , Kind .FILE_INPUT , Kind .FUNCDEF ))
98
108
.filter (scriptOrFunction -> !TreeUtils .hasDescendant (scriptOrFunction , JwtVerificationCheck ::isCallToVerifyJwt ))
99
109
.ifPresent (scriptOrFunction -> ctx .addIssue (call , MESSAGE ));
100
- } else if (UNVERIFIED_FQNS .contains (calleeFqn )) {
110
+ } else if (UNVERIFIED_FQNS .contains (calleeFqn ) && ! accessOnlyAllowedHeaderKeys ( call ) ) {
101
111
Optional .ofNullable (TreeUtils .nthArgumentOrKeyword (0 , "" , call .arguments ()))
102
112
.flatMap (TreeUtils .toOptionalInstanceOfMapper (RegularArgument .class ))
103
113
.map (RegularArgument ::expression )
@@ -175,4 +185,94 @@ private static boolean isCallToVerifyJwt(Tree t) {
175
185
.isPresent ();
176
186
}
177
187
188
+ private static boolean accessOnlyAllowedHeaderKeys (CallExpression call ) {
189
+ Tree assignment = TreeUtils .firstAncestorOfKind (call , Tree .Kind .ASSIGNMENT_STMT );
190
+ Stream <StringLiteral > headerKeysAccessedDirectly = accessToHeaderKeyWithoutAssignment (call );
191
+ if (assignment == null ) {
192
+ return areStringLiteralsPartOfAllowedKeys (headerKeysAccessedDirectly );
193
+ } else {
194
+ List <Expression > lhsExpressions = ((AssignmentStatement ) assignment ).lhsExpressions ().stream ()
195
+ .map (ExpressionList ::expressions )
196
+ .flatMap (Collection ::stream ).toList ();
197
+ if (lhsExpressions .size () == 1 && lhsExpressions .get (0 ).is (Tree .Kind .NAME )) {
198
+ Name name = (Name ) lhsExpressions .get (0 );
199
+ Symbol symbol = name .symbol ();
200
+ if (symbol != null ) {
201
+ Stream <StringLiteral > argumentsOfGet = usagesAccessedByGet (symbol , call );
202
+ Stream <StringLiteral > argumentsOfSubscription = usagesAccessedBySubscription (symbol , call );
203
+ Stream <StringLiteral > headerKeysAccessFromAssignedValues = Stream .concat (argumentsOfGet , argumentsOfSubscription );
204
+ return areStringLiteralsPartOfAllowedKeys (Stream .concat (headerKeysAccessFromAssignedValues , headerKeysAccessedDirectly ));
205
+ }
206
+ }
207
+ }
208
+ return false ;
209
+ }
210
+
211
+ private static boolean areStringLiteralsPartOfAllowedKeys (Stream <StringLiteral > literals ) {
212
+ var literalList = literals .toList ();
213
+ return !literalList .isEmpty () && literalList .stream ().allMatch (str -> ALLOWED_KEYS_ACCESS .contains (str .trimmedQuotesValue ()));
214
+ }
215
+
216
+ private static Stream <StringLiteral > accessToHeaderKeyWithoutAssignment (CallExpression call ) {
217
+ Stream <CallExpression > callExpressionFromGetUnverifiedHeaders = getCallExprWhereDictIsAccessedWithGet (Stream .of (call .parent ()));
218
+ Stream <Argument > argumentsOfCallExpr = getArgumentsFromCallExpr (callExpressionFromGetUnverifiedHeaders );
219
+ Stream <StringLiteral > stringLiteralArgumentsFromGet = getStringLiteralArguments (argumentsOfCallExpr );
220
+ Stream <SubscriptionExpression > subscriptionFromGetUnverifiedHeaders = getSubscriptions (Stream .of (call .parent ()));
221
+ Stream <StringLiteral > stringLiteralArgumentFromSubscription = getSubscriptsStringLiteral (subscriptionFromGetUnverifiedHeaders );
222
+ return Stream .concat (stringLiteralArgumentsFromGet , stringLiteralArgumentFromSubscription );
223
+ }
224
+
225
+ private static Stream <StringLiteral > usagesAccessedByGet (Symbol symbol , CallExpression call ) {
226
+ var usages = getForwardUsages (symbol , call );
227
+ var parentOfUsages = usages .map (Usage ::tree ).map (Tree ::parent );
228
+ var callExpressionsFromUsages = getCallExprWhereDictIsAccessedWithGet (parentOfUsages );
229
+ return getStringLiteralArguments (getArgumentsFromCallExpr (callExpressionsFromUsages ));
230
+ }
231
+
232
+ private static Stream <Argument > getArgumentsFromCallExpr (Stream <CallExpression > callExprs ) {
233
+ return callExprs .map (CallExpression ::arguments ).flatMap (Collection ::stream );
234
+ }
235
+
236
+ private static Stream <Usage > getForwardUsages (Symbol symbol , CallExpression call ) {
237
+ return symbol .usages ().stream ()
238
+ .filter (usage -> usage .tree ().firstToken ().line () > call .callee ().firstToken ().line ());
239
+ }
240
+
241
+ private static Stream <CallExpression > getCallExprWhereDictIsAccessedWithGet (Stream <Tree > parentQualifiedExpr ) {
242
+ return parentQualifiedExpr
243
+ .filter (parent -> parent .is (Tree .Kind .QUALIFIED_EXPR ))
244
+ .flatMap (TreeUtils .toStreamInstanceOfMapper (QualifiedExpression .class ))
245
+ .filter (expr -> "get" .equals (expr .name ().name ()))
246
+ .filter (expr -> expr .parent ().is (Kind .CALL_EXPR ))
247
+ .map (QualifiedExpression ::parent )
248
+ .flatMap (TreeUtils .toStreamInstanceOfMapper (CallExpression .class ));
249
+ }
250
+
251
+ private static Stream <StringLiteral > getStringLiteralArguments (Stream <Argument > arguments ) {
252
+ return arguments .filter (arg -> arg .is (Tree .Kind .REGULAR_ARGUMENT ))
253
+ .flatMap (TreeUtils .toStreamInstanceOfMapper (RegularArgument .class ))
254
+ .map (RegularArgument ::expression )
255
+ .flatMap (TreeUtils .toStreamInstanceOfMapper (StringLiteral .class ));
256
+ }
257
+
258
+ private static Stream <StringLiteral > usagesAccessedBySubscription (Symbol symbol , CallExpression call ) {
259
+ var usages = getForwardUsages (symbol , call );
260
+ var parentFromUsages = usages .map (Usage ::tree ).map (Tree ::parent );
261
+ var subscriptionsFromUsages = getSubscriptions (parentFromUsages );
262
+ return getSubscriptsStringLiteral (subscriptionsFromUsages );
263
+ }
264
+
265
+ private static Stream <SubscriptionExpression > getSubscriptions (Stream <Tree > subscriptions ) {
266
+ return subscriptions
267
+ .filter (subscription -> subscription .is (Tree .Kind .SUBSCRIPTION ))
268
+ .flatMap (TreeUtils .toStreamInstanceOfMapper (SubscriptionExpression .class ));
269
+ }
270
+
271
+ private static Stream <StringLiteral > getSubscriptsStringLiteral (Stream <SubscriptionExpression > subscriptions ) {
272
+ return subscriptions .map (SubscriptionExpression ::subscripts )
273
+ .map (ExpressionList ::expressions )
274
+ .flatMap (Collection ::stream )
275
+ .flatMap (TreeUtils .toStreamInstanceOfMapper (StringLiteral .class ));
276
+ }
277
+
178
278
}
0 commit comments