Skip to content

Commit 9b13c92

Browse files
SONARPY-1157 S4423 and S6308 should not raise on non-call expression matching the fqn (#1223)
1 parent 72de2ba commit 9b13c92

File tree

6 files changed

+33
-33
lines changed

6 files changed

+33
-33
lines changed

python-checks/src/main/java/org/sonar/python/checks/cdk/CdkPredicate.java

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,11 @@
2323
import java.util.Locale;
2424
import java.util.Optional;
2525
import java.util.function.Predicate;
26-
import org.sonar.plugins.python.api.SubscriptionContext;
27-
import org.sonar.plugins.python.api.tree.CallExpression;
2826
import org.sonar.plugins.python.api.tree.Expression;
2927
import org.sonar.plugins.python.api.tree.Token;
3028
import org.sonar.plugins.python.api.tree.Tree;
3129
import org.sonar.python.tree.TreeUtils;
3230

33-
import static org.sonar.python.checks.cdk.CdkUtils.getArgument;
34-
3531
public class CdkPredicate {
3632

3733
private CdkPredicate() {
@@ -84,24 +80,4 @@ public static Predicate<Expression> startsWith(String expected) {
8480
return expression -> CdkUtils.getString(expression).filter(str -> str.toLowerCase(Locale.ROOT).startsWith(expected)).isPresent();
8581
}
8682

87-
// TODO refactor this overloaded predicate method
88-
// FIXME we should not raise on a FQN which is not a method call when we want to check for a sensitive method call
89-
public static Predicate<Expression> isSensitiveMethod(SubscriptionContext ctx, String methodFqn, String argName, Predicate<Expression> sensitiveValuePredicate) {
90-
return expression -> {
91-
if (!isFqn(methodFqn).test(expression)) {
92-
return false;
93-
}
94-
if (!expression.is(Tree.Kind.CALL_EXPR)) {
95-
return true;
96-
}
97-
98-
Optional<CdkUtils.ExpressionTrace> argTrace = getArgument(ctx, (CallExpression) expression, argName);
99-
if (argTrace.isEmpty()) {
100-
return true;
101-
}
102-
103-
return argTrace.filter(trace -> trace.hasExpression(sensitiveValuePredicate)).isPresent();
104-
};
105-
}
106-
10783
}

python-checks/src/main/java/org/sonar/python/checks/cdk/CdkUtils.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,13 @@ public static Optional<String> getString(Expression expression) {
5858
}
5959
}
6060

61+
public static Optional<CallExpression> getCall(Expression expression, String fqn) {
62+
if (expression.is(Tree.Kind.CALL_EXPR) && CdkPredicate.isFqn(fqn).test(expression)) {
63+
return Optional.of((CallExpression) expression);
64+
}
65+
return Optional.empty();
66+
}
67+
6168
/**
6269
* Resolve a particular argument of a call or get an empty optional if the argument is not set.
6370
*/

python-checks/src/main/java/org/sonar/python/checks/cdk/DisabledESDomainEncryptionCheck.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,8 @@
3535
import org.sonar.plugins.python.api.tree.Tree;
3636

3737
import static org.sonar.python.checks.cdk.CdkPredicate.isFalse;
38-
import static org.sonar.python.checks.cdk.CdkPredicate.isSensitiveMethod;
3938
import static org.sonar.python.checks.cdk.CdkUtils.getArgument;
39+
import static org.sonar.python.checks.cdk.CdkUtils.getCall;
4040

4141
@Rule(key = "S6308")
4242
public class DisabledESDomainEncryptionCheck extends AbstractCdkResourceCheck {
@@ -78,8 +78,14 @@ private static String unencryptedMessage(String engine) {
7878
return String.format(UNENCRYPTED_MESSAGE, engine);
7979
}
8080

81+
/**
82+
* @return Predicate which tests if the expression is the expected object initialization
83+
* and if the expected argument is set to false or missing
84+
*/
8185
private static Predicate<Expression> isSensitiveOptionObj(SubscriptionContext ctx, String argFqnMethod) {
82-
return isSensitiveMethod(ctx, argFqnMethod, ENABLED, isFalse());
86+
return expression -> getCall(expression, argFqnMethod)
87+
.map(call -> getArgument(ctx, call, ENABLED)).stream()
88+
.anyMatch(enabled -> enabled.isEmpty() || enabled.filter(flow -> flow.hasExpression(isFalse())).isPresent());
8389
}
8490

8591
// TODO : refactor below to use new available method 'isDictionaryWithKeyValue' in AbstractCdkResourceCheck

python-checks/src/main/java/org/sonar/python/checks/cdk/WeakSSLProtocolCheckPart.java

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,9 @@
2929
import org.sonar.plugins.python.api.tree.Tree;
3030

3131
import static org.sonar.python.checks.cdk.CdkPredicate.isFqn;
32-
import static org.sonar.python.checks.cdk.CdkPredicate.isSensitiveMethod;
3332
import static org.sonar.python.checks.cdk.CdkPredicate.isString;
33+
import static org.sonar.python.checks.cdk.CdkUtils.getArgument;
34+
import static org.sonar.python.checks.cdk.CdkUtils.getCall;
3435

3536
public class WeakSSLProtocolCheckPart extends AbstractCdkResourceCheck {
3637
private static final String ENFORCE_MESSAGE = "Change this code to enforce TLS 1.2 or above.";
@@ -75,12 +76,22 @@ private static BiConsumer<SubscriptionContext, CallExpression> checkDomain(Predi
7576

7677
private static BiConsumer<SubscriptionContext, CallExpression> checkCfnDomain(String domainOptionName) {
7778
return (ctx, callExpression) -> CdkUtils.getArgument(ctx, callExpression, "domain_endpoint_options").ifPresentOrElse(
78-
argTrace -> argTrace.addIssueIf(isSensitiveMethod(ctx, domainOptionName, TLS_SECURITY_POLICY, isString(SENSITIVE_TLS_SECURITY_POLICY))
79+
argTrace -> argTrace.addIssueIf(isSensitiveOptionObj(ctx, domainOptionName)
7980
.or(isSensitiveDictionaryTls(ctx)), ENFORCE_MESSAGE),
8081
() -> ctx.addIssue(callExpression.callee(), OMITTING_MESSAGE)
8182
);
8283
}
8384

85+
/**
86+
* @return Predicate which tests if the expression is the expected object initialization
87+
* and if the expected argument is set to a sensitive policy or missing
88+
*/
89+
private static Predicate<Expression> isSensitiveOptionObj(SubscriptionContext ctx, String fqn) {
90+
return expression -> getCall(expression, fqn)
91+
.map(call -> getArgument(ctx, call, TLS_SECURITY_POLICY)).stream()
92+
.anyMatch(policy -> policy.isEmpty() || policy.filter(flow -> flow.hasExpression(isString(SENSITIVE_TLS_SECURITY_POLICY))).isPresent());
93+
}
94+
8495
private static Predicate<Expression> isSensitiveDictionaryTls(SubscriptionContext ctx) {
8596
return expression -> Optional.of(expression)
8697
.filter(expr -> expr.is(Tree.Kind.DICTIONARY_LITERAL)).map(DictionaryLiteral.class::cast)

python-checks/src/test/resources/checks/cdk/disabledESDomainEncryptionCheck.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
1717

1818
# Sensitive test cases
1919
aws_os.Domain() # NonCompliant{{Omitting encryption_at_rest causes encryption of data at rest to be disabled for this OpenSearch domain. Make sure it is safe here.}}
20-
aws_os.Domain(encryption_at_rest=aws_os.EncryptionAtRestOptions) # NonCompliant{{Make sure that using unencrypted OpenSearch domains is safe here.}}
21-
aws_os.Domain(encryption_at_rest=aws_os.EncryptionAtRestOptions()) # NonCompliant
20+
aws_os.Domain(encryption_at_rest=aws_os.EncryptionAtRestOptions)
21+
aws_os.Domain(encryption_at_rest=aws_os.EncryptionAtRestOptions()) # NonCompliant {{Make sure that using unencrypted OpenSearch domains is safe here.}}
2222
aws_os.Domain(encryption_at_rest=aws_os.EncryptionAtRestOptions(enabled=False)) # NonCompliant{{Make sure that using unencrypted OpenSearch domains is safe here.}}
2323
aws_os.Domain(encryption_at_rest=encryptionRestOptionMethodBad) # NonCompliant
2424
aws_os.Domain(encryption_at_rest={"enabled": False}) # NonCompliant
@@ -29,7 +29,7 @@ def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
2929
aws_os.Domain(encryption_at_rest=aws_os.EncryptionAtRestOptions(enabled=not_encrypted)) # NonCompliant
3030
aws_os.Domain(encryption_at_rest={"enabled": not_encrypted}) # NonCompliant
3131
aws_es.Domain() # NonCompliant{{Omitting encryption_at_rest causes encryption of data at rest to be disabled for this Elasticsearch domain. Make sure it is safe here.}}
32-
aws_es.Domain(encryption_at_rest=aws_es.EncryptionAtRestOptions) # NonCompliant{{Make sure that using unencrypted Elasticsearch domains is safe here.}}
32+
aws_es.Domain(encryption_at_rest=aws_es.EncryptionAtRestOptions)
3333

3434
# Compliant test cases
3535
aws_os.Domain(encryption_at_rest=aws_os.EncryptionAtRestOptions(enabled=True))
@@ -57,7 +57,7 @@ def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
5757
# Sensitive test cases
5858
aws_os.CfnDomain() # NonCompliant{{Omitting encryption_at_rest_options causes encryption of data at rest to be disabled for this OpenSearch domain. Make sure it is safe here.}}
5959
aws_os.CfnDomain(encryption_at_rest_options=aws_os.CfnDomain.EncryptionAtRestOptionsProperty()) # NonCompliant{{Make sure that using unencrypted OpenSearch domains is safe here.}}
60-
aws_os.CfnDomain(encryption_at_rest_options=aws_os.CfnDomain.EncryptionAtRestOptionsProperty) # NonCompliant
60+
aws_os.CfnDomain(encryption_at_rest_options=aws_os.CfnDomain.EncryptionAtRestOptionsProperty)
6161
aws_os.CfnDomain(encryption_at_rest_options=aws_os.CfnDomain.EncryptionAtRestOptionsProperty(enabled=False)) # NonCompliant{{Make sure that using unencrypted OpenSearch domains is safe here.}}
6262
aws_os.CfnDomain(encryption_at_rest_options=encryptionRestOptionPropertyMethodBad) # NonCompliant
6363
aws_os.CfnDomain(encryption_at_rest_options={"enabled": False}) # NonCompliant

python-checks/src/test/resources/checks/weakSSLProtocol_elastic_and_open_search.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ def __init__(self, **kwargs) -> None:
4747

4848
# Sensitive test case
4949
opensearch.CfnDomain() # Noncompliant
50-
opensearch.CfnDomain(domain_endpoint_options=opensearch.CfnDomain.DomainEndpointOptionsProperty) # Noncompliant
50+
opensearch.CfnDomain(domain_endpoint_options=opensearch.CfnDomain.DomainEndpointOptionsProperty)
5151
opensearch.CfnDomain(domain_endpoint_options=opensearch.CfnDomain.DomainEndpointOptionsProperty()) # Noncompliant
5252
opensearch.CfnDomain(domain_endpoint_options=opensearch.CfnDomain.DomainEndpointOptionsProperty(tls_security_policy="Policy-Min-TLS-1-0-2019-07")) # Noncompliant
5353
opensearch.CfnDomain(domain_endpoint_options=opensearch.CfnDomain.DomainEndpointOptionsProperty(tls_security_policy=str_tls_10)) # Noncompliant

0 commit comments

Comments
 (0)