22
22
import java .util .Arrays ;
23
23
import java .util .HashSet ;
24
24
import java .util .List ;
25
+ import java .util .Objects ;
25
26
import java .util .Optional ;
26
27
import java .util .Set ;
27
28
import org .sonar .check .Rule ;
33
34
import org .sonar .plugins .python .api .tree .CallExpression ;
34
35
import org .sonar .plugins .python .api .tree .Decorator ;
35
36
import org .sonar .plugins .python .api .tree .Expression ;
37
+ import org .sonar .plugins .python .api .tree .FileInput ;
36
38
import org .sonar .plugins .python .api .tree .FunctionDef ;
37
39
import org .sonar .plugins .python .api .tree .ListLiteral ;
38
40
import org .sonar .plugins .python .api .tree .RegularArgument ;
39
41
import org .sonar .plugins .python .api .tree .StringLiteral ;
40
42
import org .sonar .python .semantic .FunctionSymbolImpl ;
41
43
import org .sonar .python .tree .FunctionDefImpl ;
44
+ import org .sonar .python .tree .TreeUtils ;
42
45
43
46
import static org .sonar .plugins .python .api .tree .Tree .Kind .CALL_EXPR ;
47
+ import static org .sonar .plugins .python .api .tree .Tree .Kind .FILE_INPUT ;
44
48
import static org .sonar .plugins .python .api .tree .Tree .Kind .FUNCDEF ;
45
49
import static org .sonar .plugins .python .api .tree .Tree .Kind .LIST_LITERAL ;
46
50
import static org .sonar .plugins .python .api .tree .Tree .Kind .REGULAR_ARGUMENT ;
47
51
import static org .sonar .plugins .python .api .tree .Tree .Kind .STRING_LITERAL ;
52
+ import static org .sonar .python .tree .TreeUtils .argumentByKeyword ;
48
53
import static org .sonar .python .tree .TreeUtils .getSymbolFromTree ;
49
54
50
55
@ Rule (key = "S3752" )
@@ -65,6 +70,8 @@ public void initialize(Context context) {
65
70
FunctionDef functionDef = (FunctionDef ) ctx .syntaxNode ();
66
71
if (isDjangoView (functionDef )) {
67
72
checkDjangoView (functionDef , ctx );
73
+ } else {
74
+ getFlaskViewDecorator (functionDef ).ifPresent (callExpression -> checkFlaskView (callExpression , ctx ));
68
75
}
69
76
});
70
77
}
@@ -121,4 +128,39 @@ private static boolean isDjangoView(FunctionDef functionDef) {
121
128
.filter (FunctionSymbolImpl ::isDjangoView )
122
129
.isPresent ();
123
130
}
131
+
132
+ private static Optional <CallExpression > getFlaskViewDecorator (FunctionDef functionDef ) {
133
+ return functionDef .decorators ().stream ()
134
+ .map (Decorator ::expression )
135
+ .filter (expression -> expression .is (CALL_EXPR ))
136
+ .map (CallExpression .class ::cast )
137
+ .filter (UnsafeHttpMethodsCheck ::isFlaskRouteDecorator )
138
+ .findFirst ();
139
+ }
140
+
141
+ private static boolean isFlaskRouteDecorator (CallExpression callExpression ) {
142
+ Symbol calleeSymbol = callExpression .calleeSymbol ();
143
+ if (calleeSymbol == null ) {
144
+ return false ;
145
+ }
146
+ return calleeSymbol .name ().equals ("route" );
147
+ }
148
+
149
+ private static void checkFlaskView (CallExpression callExpression , SubscriptionContext ctx ) {
150
+ RegularArgument methodsArg = argumentByKeyword ("methods" , callExpression .arguments ());
151
+ if (methodsArg != null && hasBothUnsafeAndSafeHttpMethods (methodsArg ) && isFlaskImported (callExpression )) {
152
+ ctx .addIssue (callExpression , MESSAGE );
153
+ }
154
+ }
155
+
156
+ private static boolean isFlaskImported (CallExpression callExpression ) {
157
+ // When SONARPY-834 will be implemented we can have a cleaner implementation
158
+ // checking decorator fqn to be equal to flask.blueprints.Blueprint.route
159
+ return Optional .ofNullable (TreeUtils .firstAncestorOfKind (callExpression , FILE_INPUT ))
160
+ .filter (fileInput -> ((FileInput ) fileInput ).globalVariables ().stream ()
161
+ .map (Symbol ::fullyQualifiedName )
162
+ .filter (Objects ::nonNull )
163
+ .anyMatch (fqn -> fqn .contains ("flask" )))
164
+ .isPresent ();
165
+ }
124
166
}
0 commit comments