Skip to content

Conversation

@andrey-tyukin-sonarsource
Copy link
Contributor

Two differences to the original formulation of the ticket:

  • Presense of CSRFProtect in flask applications is not checked across multiple files; For now, it's limited to a single file
  • Decorators like @csrf_exempt are detected by their literal name, not by their resolved FQN.

The 'defaultSeverity' was missing in the automatically generated S5792.json, I've set it to 'Major' for now. Also, the tags were changed to lowercase in the same JSON file.

@andrey-tyukin-sonarsource
Copy link
Contributor Author

andrey-tyukin-sonarsource commented May 1, 2020

In order to make the mapping of issues in the original test projects to the implementing methods a bit easier, here is the list of triples (original.py / extractedTest.py / implementingMethod):

original: django/mysite/views.py:25:@csrf_exempt # Sensitive
test:       django/djangoExempt.py
src:        decoratorCsrfExemptCheck

original: django/mysite/settings.noncompliant.py:50:] # Sensitive: django.middleware.csrf.CsrfViewMiddleware is missing
test:       django/settings.py
src:        djangoMiddlewareArrayCheck

original: flask/globalsensitive2.py:10:app = Flask(__name__) # Sensitive: CSRFProtect is missing
test:       flask/global.py
src:        improperlyConfiguredFlaskApp

original: flask/globalexemptroutesensitive1.py:21:@csrf.exempt # Sensitive
test:       flask/flaskExempt.py
src:        decoratorCsrfExemptCheck
// Note: the `csrf` variable is renamed there to `csrfProtect`, it shouldn't matter.

original: flask/flaskformsensitive1.py:18:        csrf = False # Sensitive
test:       flask/flaskform1.py
src:        metaCheck

original: flask/flaskformsensitive1.py:29:    form = TestForm(request.form, csrf_enabled=False) # Sensitive
test:       flask/flaskform1.py
src:        formInstantiationCheck

original: flask/globalsensitive1.py:14:app.config['WTF_CSRF_ENABLED'] = False # Sensitive: it overrides whatever configuration
test:       flask/wtfCsrfEnabledFalse.py
src:        flaskWtfCsrfEnabledFalseCheck

original: flask/globalexemptblueprintsensitive1.py:17:csrf.exempt(simple_page) # Sensitive
test:       flask/exemptAsFunction.py
src:        functionCsrfExemptCheck

original: flask/globalsensitive3.py:12:app.config['WTF_CSRF_CHECK_DEFAULT'] = False # Sensitive: it overrides whatever configuration
test:       flask/wtfCsrfEnabledFalse.py
src:        flaskWtfCsrfEnabledFalseCheck

Copy link
Contributor

@guillaume-dequenne guillaume-dequenne left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall, I'd recommend using Tree#is to test the Kind of nodes rather than testing the instance.
The Kind is generally what we base our assumptions on (we sometimes use the instance but it's not the usual way) and is the strongest guarantee we have, as some different kinds might be linked to the same interface. I didn't flag every cast/test instance to avoid the noise, but I believe it might be worth to reconsider their usage nonetheless.

Also, while I wouldn't want to discourage you from using a functional style, I think some parts might be a bit more readable (at least due to consistency with the style used in the rest of the project) if written in a more idiomatic style (cf for instance the snippets I suggest).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You used the Python specific key here. You should be using the top-level RSPEC key instead (S4502). I believe that's why you had missing information in the generated metadata.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed, 4502 worked a bit better, but the tags still had to be converted to lowercase manually (otherwise, PythonRuleRepositoryTest complained about "Flask" and "Django")

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That would mean it's an issue with the rule as defined in Jira: https://jira.sonarsource.com/browse/RSPEC-5792. I guess in those cases you can ping whoever created the RSPEC to notify the problem and update it (otherwise we would have to manually update each time we update metadata)

Comment on lines 198 to 206
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure we need those as I'd prefer going for a test on Tree#is and then a direct cast rather than testing the possibility of a cast (as different Tree.Kind could have the same underlying interface).

Copy link
Contributor

@guillaume-dequenne guillaume-dequenne left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still a few comments/nitpicks, I'm not convinced we need the checkedCast methods and I think they can be replaced by something simpler. But I'd like your opinion @andrea-guarino-sonarsource on that because maybe they have their place in a util class as well.

* If the value has the specified tree-<code>kind</code>, it is cast as an instance of the specified class, and
* returned in a non-empty <code>Optional</code>. Otherwise, returns an empty <code>Optional</code>.
*/
private static <A extends Tree, B extends Tree> Optional<B> checkedCast(Tree.Kind kind, Class<B> c, @Nullable A a) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personally I think we shouldn't need those methods.

Why not use something like .filter(x -> x.is(Kind.Y)).map(Y.class::cast) instead of defining these methods? It feels more idiomatic.
At any rate if we're going to use those methods, I believe they should be in a util class where other checks will be able to benefit from them. What do you think @andrea-guarino-sonarsource ?

Copy link
Contributor Author

@andrey-tyukin-sonarsource andrey-tyukin-sonarsource May 4, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well... Optional.ofNullable(x).filter(x -> x.is(Kind.YK)).map(Y.class::cast) still looked a bit noisy, especially since it occurred over and over again in several places.

If the method definitions themselves are too distracting, I can inline them for now.

I think there is no need extracting them into an Util class, stuff like that is easy to introduce, but hard to get rid of. If anyone else wants to discuss how one might cut down the noise from the checks and casts, maybe we could collect some proposals at some later point.

private static void metaCheck(SubscriptionContext subscriptionContext) {
ClassDef classDef = (ClassDef) subscriptionContext.syntaxNode();
boolean isMetaClass = "Meta".equals(classDef.name().name());
if (!isMetaClass) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor nitpick but I believe you can inline this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

inlined


/** Checks whether the expression is a string literal with value exactly equal to <code>s</code>. */
@SuppressWarnings("SameParameterValue")
private static Predicate<Expression> isStringEqual(String s) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need this method? I believe you could pass the predicate directly to isStringSatisfying, knowing the method is called only once.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

inlined

if (expr.is(Tree.Kind.STRING_LITERAL)) {
return pred.test(((StringLiteral) expr).trimmedQuotesValue());
} else {
return false;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor nitpick but I believe the else is not needed since you return directly from the if anyway.

/** Checks that an expression is a list literal with at least one entry satisfying the predicate. */
private static Predicate<Expression> isListAnyMatch(Predicate<Expression> pred) {
return expr -> Optional.ofNullable(expr)
.filter(ListLiteral.class::isInstance)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you can use .filter(l -> l.is(Tree.Kind.LIST_LITERAL)) instead.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahh, true, missed that one.

/** Looks for <code>'csrf': False</code> and similar settings in a dictionary. */
private static Optional<Expression> searchForBadCsrfSettingInDictionary(DictionaryLiteral dict) {
return dict.elements().stream()
.filter(KeyValuePair.class::isInstance)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here as well I believe we can rely on Kind.

@sonarsource-next
Copy link

Kudos, SonarQube Quality Gate passed!

Bug A 0 Bugs
Vulnerability A 0 Vulnerabilities
Security Hotspot A 0 Security Hotspots
Code Smell A 0 Code Smells

99.5% 99.5% Coverage
0.0% 0.0% Duplication

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@andrea-guarino-sonarsource andrea-guarino-sonarsource merged commit 6681c18 into master May 5, 2020
@andrea-guarino-sonarsource andrea-guarino-sonarsource deleted the SONARPY-668 branch May 5, 2020 08:32
hashicorp-vault-sonar-prod bot pushed a commit that referenced this pull request Dec 9, 2025
GitOrigin-RevId: bd10c810f495da6214dba9cf8281d36cf7262583
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants