Skip to content
Open
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,7 @@
{
"ruleKey": "S1168",
"hasTruePositives": true,
"falseNegatives": 24,
"falseNegatives": 25,
"falsePositives": 0
},
{
Expand Down Expand Up @@ -2738,7 +2738,7 @@
{
"ruleKey": "S6813",
"hasTruePositives": true,
"falseNegatives": 66,
"falseNegatives": 67,
"falsePositives": 0
},
{
Expand Down Expand Up @@ -3190,5 +3190,11 @@
"hasTruePositives": true,
"falseNegatives": 1,
"falsePositives": 0
},
{
"ruleKey": "S8912",
"hasTruePositives": false,
"falseNegatives": 8,
"falsePositives": 0
}
]
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"ruleKey": "S1168",
"hasTruePositives": true,
"falseNegatives": 24,
"falseNegatives": 25,
"falsePositives": 0
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"ruleKey": "S6813",
"hasTruePositives": true,
"falseNegatives": 66,
"falseNegatives": 67,
"falsePositives": 0
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"ruleKey": "S8912",
"hasTruePositives": false,
"falseNegatives": 8,
"falsePositives": 0
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
package checks;

import io.quarkus.arc.Unremovable;
import io.quarkus.credentials.CredentialsProvider;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.context.Dependent;
import jakarta.enterprise.context.RequestScoped;
import jakarta.enterprise.context.SessionScoped;
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
import java.util.Map;

@ApplicationScoped
class MyCredentialsProvider implements CredentialsProvider { // Noncompliant {{Add the @Unremovable annotation to this CredentialsProvider implementation.}}
// ^^^^^^^^^^^^^^^^^^^^^

@Override
public Map<String, String> getCredentials(String credentialsProviderName) {
return Map.of(USER_PROPERTY_NAME, "user", PASSWORD_PROPERTY_NAME, "password");
}
}

@ApplicationScoped
@Unremovable
class CompliantProvider implements CredentialsProvider {

@Override
public Map<String, String> getCredentials(String credentialsProviderName) {
return Map.of(USER_PROPERTY_NAME, "user", PASSWORD_PROPERTY_NAME, "password");
}
}

@RequestScoped
class RequestScopedProvider implements CredentialsProvider { // Noncompliant
// ^^^^^^^^^^^^^^^^^^^^^

@Override
public Map<String, String> getCredentials(String credentialsProviderName) {
return Map.of(USER_PROPERTY_NAME, "user", PASSWORD_PROPERTY_NAME, "pass");
}
}

@Singleton
class SingletonProvider implements CredentialsProvider { // Noncompliant
// ^^^^^^^^^^^^^^^^^

@Override
public Map<String, String> getCredentials(String credentialsProviderName) {
return Map.of(USER_PROPERTY_NAME, "admin", PASSWORD_PROPERTY_NAME, "secret");
}
}

@Dependent
class DependentProvider implements CredentialsProvider { // Noncompliant
// ^^^^^^^^^^^^^^^^^

@Override
public Map<String, String> getCredentials(String credentialsProviderName) {
return Map.of(USER_PROPERTY_NAME, "user", PASSWORD_PROPERTY_NAME, "pwd");
}
}

@SessionScoped
class SessionScopedProvider implements CredentialsProvider { // Noncompliant
// ^^^^^^^^^^^^^^^^^^^^^

@Override
public Map<String, String> getCredentials(String credentialsProviderName) {
return Map.of(USER_PROPERTY_NAME, "user", PASSWORD_PROPERTY_NAME, "pass");
}
}

class NoScopeProvider implements CredentialsProvider {

@Override
public Map<String, String> getCredentials(String credentialsProviderName) {
return Map.of(USER_PROPERTY_NAME, "user", PASSWORD_PROPERTY_NAME, "password");
}
}

@ApplicationScoped
class SomeOtherService {
public void doSomething() {
}
}

@Singleton
@Unremovable
class CompliantSingleton implements CredentialsProvider {

@Override
public Map<String, String> getCredentials(String credentialsProviderName) {
return Map.of(USER_PROPERTY_NAME, "admin", PASSWORD_PROPERTY_NAME, "secret");
}
}

@ApplicationScoped
class ProviderWithDependencies implements CredentialsProvider { // Noncompliant
// ^^^^^^^^^^^^^^^^^^^^^^^^

@Inject
ConfigService configService;

@Override
public Map<String, String> getCredentials(String credentialsProviderName) {
String username = configService.getUsername();
String password = configService.getPassword();
return Map.of(USER_PROPERTY_NAME, username, PASSWORD_PROPERTY_NAME, password);
}
}

@ApplicationScoped
class MultiInterfaceProvider implements CredentialsProvider, AutoCloseable { // Noncompliant
// ^^^^^^^^^^^^^^^^^^^^^^

@Override
public Map<String, String> getCredentials(String credentialsProviderName) {
return Map.of(USER_PROPERTY_NAME, "user", PASSWORD_PROPERTY_NAME, "password");
}

@Override
public void close() {
}
}

class ConfigService {
String getUsername() {
return "configUser";
}

String getPassword() {
return "configPass";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package checks;

import io.quarkus.credentials.CredentialsProvider;
import jakarta.enterprise.context.ApplicationScoped;
import java.util.Map;

@ApplicationScoped
class MinimalProvider implements CredentialsProvider { // Noncompliant {{Add the @Unremovable annotation to this CredentialsProvider implementation.}}
// ^^^^^^^^^^^^^^^

@Override
public Map<String, String> getCredentials(String credentialsProviderName) {
return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package io.quarkus.arc;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Unremovable {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package io.quarkus.credentials;

import java.util.Map;

public interface CredentialsProvider {
String USER_PROPERTY_NAME = "user";
String PASSWORD_PROPERTY_NAME = "password";

Map<String, String> getCredentials(String credentialsProviderName);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/*
* SonarQube Java
* Copyright (C) SonarSource Sàrl
* mailto:info AT sonarsource DOT com
*
* You can redistribute and/or modify this program under the terms of
* the Sonar Source-Available License Version 1, as published by SonarSource Sàrl.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the Sonar Source-Available License for more details.
*
* You should have received a copy of the Sonar Source-Available License
* along with this program; if not, see https://sonarsource.com/license/ssal/
*/
package org.sonar.java.checks;

import java.util.List;
import org.sonar.check.Rule;
import org.sonar.plugins.java.api.IssuableSubscriptionVisitor;
import org.sonar.plugins.java.api.semantic.SymbolMetadata;
import org.sonar.plugins.java.api.tree.ClassTree;
import org.sonar.plugins.java.api.tree.Tree;

@Rule(key = "S8912")
public class CredentialsProviderUnremovableCheck extends IssuableSubscriptionVisitor {
private static final String CREDENTIALS_PROVIDER_FQN = "io.quarkus.credentials.CredentialsProvider";
private static final String UNREMOVABLE_FQN = "io.quarkus.arc.Unremovable";

private static final List<String> CDI_SCOPE_ANNOTATIONS = List.of(
"jakarta.enterprise.context.ApplicationScoped",
"jakarta.enterprise.context.RequestScoped",
"jakarta.enterprise.context.SessionScoped",
"jakarta.enterprise.context.Dependent",
"jakarta.inject.Singleton",
"javax.enterprise.context.ApplicationScoped",
"javax.enterprise.context.RequestScoped",
"javax.enterprise.context.SessionScoped",
"javax.enterprise.context.Dependent",
"javax.inject.Singleton"
);

@Override
public List<Tree.Kind> nodesToVisit() {
return List.of(Tree.Kind.CLASS);
}

@Override
public void visitNode(Tree tree) {
ClassTree classTree = (ClassTree) tree;

if (classTree.simpleName() == null) {
return;
}

if (!implementsCredentialsProvider(classTree)) {
return;
}

SymbolMetadata metadata = classTree.symbol().metadata();

if (!hasCdiScopeAnnotation(metadata)) {
return;
}

if (metadata.isAnnotatedWith(UNREMOVABLE_FQN)) {
return;
}

reportIssue(
classTree.simpleName(),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Not sure it matters here, but this can be null

"Add the @Unremovable annotation to this CredentialsProvider implementation."
);
}

private static boolean implementsCredentialsProvider(ClassTree classTree) {
return classTree.symbol().type().isSubtypeOf(CREDENTIALS_PROVIDER_FQN);
}

private static boolean hasCdiScopeAnnotation(SymbolMetadata metadata) {
return CDI_SCOPE_ANNOTATIONS.stream()
.anyMatch(metadata::isAnnotatedWith);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* SonarQube Java
* Copyright (C) SonarSource Sàrl
* mailto:info AT sonarsource DOT com
*
* You can redistribute and/or modify this program under the terms of
* the Sonar Source-Available License Version 1, as published by SonarSource Sàrl.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the Sonar Source-Available License for more details.
*
* You should have received a copy of the Sonar Source-Available License
* along with this program; if not, see https://sonarsource.com/license/ssal/
*/
package org.sonar.java.checks;

import org.junit.jupiter.api.Test;
import org.sonar.java.checks.verifier.CheckVerifier;

import static org.sonar.java.checks.verifier.TestUtils.mainCodeSourcesPath;

class CredentialsProviderUnremovableCheckTest {
private static final String SAMPLE_FILE = mainCodeSourcesPath("checks/CredentialsProviderUnremovableCheckSample.java");
private static final String MINIMAL_FILE = mainCodeSourcesPath("checks/CredentialsProviderUnremovableCheckSampleMinimal.java");
private static final CredentialsProviderUnremovableCheck CHECK = new CredentialsProviderUnremovableCheck();

@Test
void testMinimal() {
CheckVerifier.newVerifier()
.onFile(MINIMAL_FILE)
.withCheck(CHECK)
.verifyIssues();
}

@Test
void test() {
CheckVerifier.newVerifier()
.onFile(SAMPLE_FILE)
.withCheck(CHECK)
.verifyIssues();
}

@Test
void testWithoutSemantic() {
CheckVerifier.newVerifier()
.onFile(SAMPLE_FILE)
.withCheck(CHECK)
.withoutSemantic()
.verifyNoIssues();
}
}
Loading
Loading