|
21 | 21 | import java.io.InputStreamReader; |
22 | 22 | import java.util.List; |
23 | 23 | import java.util.Map; |
| 24 | +import java.util.Objects; |
24 | 25 | import java.util.Optional; |
25 | 26 | import java.util.function.Function; |
26 | 27 | import java.util.function.Predicate; |
27 | 28 | import java.util.stream.Collectors; |
28 | 29 | import java.util.stream.Stream; |
| 30 | +import javax.annotation.Nullable; |
29 | 31 | import org.sonar.check.Rule; |
30 | 32 | import org.sonar.plugins.python.api.PythonSubscriptionCheck; |
31 | 33 | import org.sonar.plugins.python.api.SubscriptionContext; |
@@ -64,15 +66,18 @@ private void processCallExpression(SubscriptionContext ctx) { |
64 | 66 | } |
65 | 67 |
|
66 | 68 | private void checkCallArguments(SubscriptionContext ctx, CallExpression call) { |
67 | | - getMethod(call) |
68 | | - .ifPresent(method -> method.indices() |
69 | | - .forEach(argumentIndex -> { |
70 | | - var argumentName = method.args().get(argumentIndex); |
71 | | - var argument = TreeUtils.nthArgumentOrKeyword(argumentIndex, argumentName, call.arguments()); |
72 | | - if (argument != null) { |
73 | | - checkArgument(ctx, argument); |
74 | | - } |
75 | | - })); |
| 69 | + getMethod(call).ifPresent(method -> |
| 70 | + getArgumentsToCheck(call, method) |
| 71 | + .forEach(argument -> checkArgument(ctx, argument)) |
| 72 | + ); |
| 73 | + } |
| 74 | + |
| 75 | + private Stream<RegularArgument> getArgumentsToCheck(CallExpression call, CredentialMethod method) { |
| 76 | + return method.sensitiveArguments().stream() |
| 77 | + .map(sensitiveArgument -> sensitiveArgument.index() != null |
| 78 | + ? TreeUtils.nthArgumentOrKeyword(sensitiveArgument.index(), sensitiveArgument.name(), call.arguments()) |
| 79 | + : TreeUtils.argumentByKeyword(sensitiveArgument.name(), call.arguments())) |
| 80 | + .filter(Objects::nonNull); |
76 | 81 | } |
77 | 82 |
|
78 | 83 | private static void checkArgument(SubscriptionContext ctx, RegularArgument argument) { |
@@ -142,42 +147,41 @@ private Optional<CredentialMethod> getMethod(CallExpression call) { |
142 | 147 | .map(methods::get); |
143 | 148 | } |
144 | 149 |
|
145 | | - public static class CredentialMethod { |
146 | | - private String name; |
147 | | - private List<String> args; |
148 | | - private List<Integer> indices; |
149 | | - |
150 | | - public String name() { |
151 | | - return name; |
152 | | - } |
153 | | - |
154 | | - public List<String> args() { |
155 | | - return args; |
156 | | - } |
| 150 | + public record CredentialMethod( |
| 151 | + String name, |
| 152 | + List<MethodArgument> sensitiveArguments) { |
| 153 | + } |
157 | 154 |
|
158 | | - public List<Integer> indices() { |
159 | | - return indices; |
160 | | - } |
| 155 | + public record MethodArgument( |
| 156 | + String name, |
| 157 | + @Nullable Integer index) { |
161 | 158 | } |
162 | 159 |
|
163 | 160 | private static class CredentialMethodsLoader { |
164 | | - private static final String METHODS_RESOURCE_PATH = "/org/sonar/python/checks/hardcoded_credentials_call_check_meta.json"; |
| 161 | + private static final String CHECKS_DIR = "/org/sonar/python/checks"; |
| 162 | + private static final String GENERATED_METHODS_RESOURCE_PATH = CHECKS_DIR + "/generated_hardcoded_credentials_call_check_meta.json"; |
| 163 | + private static final String MANUAL_METHODS_RESOURCE_PATH = CHECKS_DIR + "/manual_hardcoded_credentials_call_check_meta.json"; |
165 | 164 | private final Gson gson; |
166 | 165 |
|
167 | 166 | private CredentialMethodsLoader() { |
168 | 167 | gson = new Gson(); |
169 | 168 | } |
170 | 169 |
|
171 | 170 | private Map<String, CredentialMethod> load() { |
172 | | - try (var is = HardcodedCredentialsCallCheck.class.getResourceAsStream(METHODS_RESOURCE_PATH)) { |
| 171 | + var generatedCredentialMethods = loadMethodsFromResource(GENERATED_METHODS_RESOURCE_PATH); |
| 172 | + var manualCredentialMethods = loadMethodsFromResource(MANUAL_METHODS_RESOURCE_PATH); |
| 173 | + return Stream.concat(Stream.of(generatedCredentialMethods), Stream.of(manualCredentialMethods)) |
| 174 | + .collect(Collectors.toMap(CredentialMethod::name, Function.identity())); // Will throw an exception if there are duplicates |
| 175 | + } |
| 176 | + |
| 177 | + private CredentialMethod[] loadMethodsFromResource(String resourcePath) { |
| 178 | + try (var is = HardcodedCredentialsCallCheck.class.getResourceAsStream(resourcePath)) { |
173 | 179 | return Optional.ofNullable(is) |
174 | 180 | .map(InputStreamReader::new) |
175 | | - .map(r -> gson.fromJson(r, CredentialMethod[].class)) |
176 | | - .stream() |
177 | | - .flatMap(Stream::of) |
178 | | - .collect(Collectors.toMap(CredentialMethod::name, Function.identity())); |
| 181 | + .map(reader -> gson.fromJson(reader, CredentialMethod[].class)) |
| 182 | + .orElseThrow(() -> new IllegalStateException("Unable to open resource: " + resourcePath)); |
179 | 183 | } catch (IOException e) { |
180 | | - throw new IllegalStateException("Unable to read methods metadata from " + METHODS_RESOURCE_PATH, e); |
| 184 | + throw new IllegalStateException("Unable to read methods metadata from " + resourcePath, e); |
181 | 185 | } |
182 | 186 | } |
183 | 187 | } |
|
0 commit comments