-
Notifications
You must be signed in to change notification settings - Fork 21
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
SONARKT-569 Add support for WebViews to S4830 #572
base: master
Are you sure you want to change the base?
Conversation
97c6a03
to
5a73038
Compare
5a73038
to
42d89ca
Compare
} | ||
|
||
override fun visitExpression(expression: KtExpression) { | ||
potentialBranching = potentialBranching || |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Educational question: does this cover all branching?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, good question - the answer is: all the ones I could think of :-)
I find unlikely that somebody will find a way to have branching, have proceed
and not have any cancel, in other ways... but I would not underestimate people creativity :-)
Anyway, unless I am missing some common scenarios, the risk for FP seems close to zero to me.
If we are concerned about this heuristic we can always remove it go for the Option 3 we discussed (only simple proceed
in body), but that seems too much on the side of safety, at the expense of reach.
WDYT?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice work! I see we can have a few improvements here.
private val androidSslErrorHandlerProceedFunMatcher = FunMatcher { | ||
definingSupertype = "android.webkit.SslErrorHandler" | ||
withNames("proceed") | ||
} | ||
|
||
private val androidSslErrorHandlerCancelFunMatcher = FunMatcher { | ||
definingSupertype = "android.webkit.SslErrorHandler" | ||
withNames("cancel") | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's move matchers to the file level. Otherwise, we create them every time AndroidWebViewCandidateVisitor
is instantiated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wanted to keep them local to the context where they are relevant. I should have probably created a private companion object, but maybe it's too much here. So I have moved them to the top of the file, as suggested.
val isProceedCall = androidSslErrorHandlerProceedFunMatcher.matches(expression) | ||
val isCancelCall = androidSslErrorHandlerCancelFunMatcher.matches(expression) | ||
foundProceedCall = foundProceedCall || isProceedCall | ||
foundCancelCall = foundCancelCall || isCancelCall |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
here we always try to match, both proceed and cancel and always override the foundProceedCall and foundCancelCall.
It seems to me we can inline
val isProceedCall = androidSslErrorHandlerProceedFunMatcher.matches(expression) | |
val isCancelCall = androidSslErrorHandlerCancelFunMatcher.matches(expression) | |
foundProceedCall = foundProceedCall || isProceedCall | |
foundCancelCall = foundCancelCall || isCancelCall | |
foundProceedCall = foundProceedCall || androidSslErrorHandlerProceedFunMatcher.matches(expression) | |
foundCancelCall = foundCancelCall || androidSslErrorHandlerCancelFunMatcher.matches(expression) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also since these calls can't be matched together, we can refactor to only match one. I'll let you decide how is better to refactor it.
@@ -104,6 +125,43 @@ class ServerCertificateCheck : AbstractCheck() { | |||
} | |||
} | |||
|
|||
private class AndroidWebViewCandidateVisitor : KtVisitorVoid() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's FYI, Looks like for this rule we implemented recursive visit function by ourselves, however, there's a KtTreeVisitorVoid
, which is recursive. No need to refactor this check, just FYI for the future rules.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have changed the code to useKtTreeVisitorVoid
everywhere, so that I could remove the logic to traverse descendants. Except for the call to super
, it was a drop-in replacement.
val javaCryptographyExtensionCandidate = javaCryptographyExtensionFunMatchers.any { it.matches(function) } | ||
&& !function.callsCheckTrusted() | ||
&& !function.throwsCertificateExceptionWithoutCatching() | ||
) { | ||
val androidWebViewCandidate = androidWebViewFunMatchers.any { it.matches(function) } | ||
&& function.callsProceedUnconditionally() | ||
if (javaCryptographyExtensionCandidate || androidWebViewCandidate) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here we match function to both FunMatchers regardless of the result. I'd suggest extracting refactoring the code so we don't match function a second time if we know it was already matched to something else. As an idea it can look like this:
when {
javaCryptographyExtensionFunMatchers.any { it.matches(function) } ->
if (!function.callsCheckTrusted() && !function.throwsCertificateExceptionWithoutCatching())
kotlinFileContext.reportIssue(function.nameIdentifier ?: function,
"Enable server certificate validation on this SSL/TLS connection.")
androidWebViewFunMatchers.any { it.matches(function) } ->
if (function.callsProceedUnconditionally()) {
kotlinFileContext.reportIssue(function.nameIdentifier ?: function,
"Enable server certificate validation on this SSL/TLS connection.")
}
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed, that's unnecessary computation.
I came up with something slightly different to avoid repeating reporting logic twice:
override fun visitNamedFunction(function: KtNamedFunction, kotlinFileContext: KotlinFileContext) {
val match = when {
javaCryptographyExtensionFunMatchers.any { it.matches(function) } ->
!function.callsCheckTrusted() && !function.throwsCertificateExceptionWithoutCatching()
androidWebViewFunMatchers.any { it.matches(function) } ->
function.callsProceedUnconditionally()
else -> false
}
if (match) {
kotlinFileContext.reportIssue(function.nameIdentifier ?: function,
"Enable server certificate validation on this SSL/TLS connection.")
}
}
private fun KtNamedFunction.callsProceedUnconditionally(): Boolean { | ||
val visitor = AndroidWebViewCandidateVisitor() | ||
this.acceptRecursively(visitor) | ||
return visitor.foundProceedCall && !visitor.foundCancelCall && !visitor.potentialBranching |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's more a matter of taste, what do you think about this way:
with(visitor) { foundProceedCall && !foundCancelCall && !potentialBranching }
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, nice! I used it on the other visitor as well, for consistency.
/* | ||
* Returns true only when the function throws a CertificateException without a catch against it. | ||
*/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's maybe keep this Kdoc
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, I removed it because it seemed redundant - it basically says the same thing as the name of the function, without any further detail.
|
SONARKT-569