1
2
3
4
5
6 package gov.nist.secauto.metaschema.cli.commands;
7
8 import gov.nist.secauto.metaschema.cli.processor.CLIProcessor;
9 import gov.nist.secauto.metaschema.cli.processor.CLIProcessor.CallingContext;
10 import gov.nist.secauto.metaschema.cli.processor.ExitCode;
11 import gov.nist.secauto.metaschema.cli.processor.command.AbstractCommandExecutor;
12 import gov.nist.secauto.metaschema.cli.processor.command.AbstractTerminalCommand;
13 import gov.nist.secauto.metaschema.cli.processor.command.CommandExecutionException;
14 import gov.nist.secauto.metaschema.cli.processor.command.ExtraArgument;
15 import gov.nist.secauto.metaschema.cli.util.LoggingValidationHandler;
16 import gov.nist.secauto.metaschema.core.configuration.DefaultConfiguration;
17 import gov.nist.secauto.metaschema.core.configuration.IMutableConfiguration;
18 import gov.nist.secauto.metaschema.core.metapath.MetapathException;
19 import gov.nist.secauto.metaschema.core.model.IModule;
20 import gov.nist.secauto.metaschema.core.model.constraint.IConstraintSet;
21 import gov.nist.secauto.metaschema.core.model.constraint.ValidationFeature;
22 import gov.nist.secauto.metaschema.core.model.validation.AggregateValidationResult;
23 import gov.nist.secauto.metaschema.core.model.validation.IValidationResult;
24 import gov.nist.secauto.metaschema.core.util.IVersionInfo;
25 import gov.nist.secauto.metaschema.core.util.ObjectUtils;
26 import gov.nist.secauto.metaschema.databind.IBindingContext;
27 import gov.nist.secauto.metaschema.databind.IBindingContext.ISchemaValidationProvider;
28 import gov.nist.secauto.metaschema.databind.io.Format;
29 import gov.nist.secauto.metaschema.databind.io.IBoundLoader;
30 import gov.nist.secauto.metaschema.modules.sarif.SarifValidationHandler;
31
32 import org.apache.commons.cli.CommandLine;
33 import org.apache.commons.cli.Option;
34 import org.apache.logging.log4j.LogManager;
35 import org.apache.logging.log4j.Logger;
36
37 import java.io.FileNotFoundException;
38 import java.io.IOException;
39 import java.net.URI;
40 import java.net.UnknownHostException;
41 import java.nio.file.Path;
42 import java.nio.file.Paths;
43 import java.util.Collection;
44 import java.util.List;
45 import java.util.Set;
46
47 import edu.umd.cs.findbugs.annotations.NonNull;
48 import edu.umd.cs.findbugs.annotations.Nullable;
49
50
51
52
53 public abstract class AbstractValidateContentCommand
54 extends AbstractTerminalCommand {
55 private static final Logger LOGGER = LogManager.getLogger(AbstractValidateContentCommand.class);
56 @NonNull
57 private static final String COMMAND = "validate";
58 @NonNull
59 private static final List<ExtraArgument> EXTRA_ARGUMENTS = ObjectUtils.notNull(List.of(
60 ExtraArgument.newInstance("file-or-URI-to-validate", true)));
61
62 @NonNull
63 private static final Option CONSTRAINTS_OPTION = ObjectUtils.notNull(
64 Option.builder("c")
65 .hasArgs()
66 .argName("URL")
67 .desc("additional constraint definitions")
68 .build());
69 @NonNull
70 private static final Option SARIF_OUTPUT_FILE_OPTION = ObjectUtils.notNull(
71 Option.builder("o")
72 .hasArg()
73 .argName("FILE")
74 .desc("write SARIF results to the provided FILE")
75 .numberOfArgs(1)
76 .build());
77 @NonNull
78 private static final Option SARIF_INCLUDE_PASS_OPTION = ObjectUtils.notNull(
79 Option.builder()
80 .longOpt("sarif-include-pass")
81 .desc("include pass results in SARIF")
82 .build());
83 @NonNull
84 private static final Option NO_SCHEMA_VALIDATION_OPTION = ObjectUtils.notNull(
85 Option.builder()
86 .longOpt("disable-schema-validation")
87 .desc("do not perform schema validation")
88 .build());
89 @NonNull
90 private static final Option NO_CONSTRAINT_VALIDATION_OPTION = ObjectUtils.notNull(
91 Option.builder()
92 .longOpt("disable-constraint-validation")
93 .desc("do not perform constraint validation")
94 .build());
95
96 @Override
97 public String getName() {
98 return COMMAND;
99 }
100
101 @SuppressWarnings("null")
102 @Override
103 public Collection<? extends Option> gatherOptions() {
104 return List.of(
105 MetaschemaCommands.AS_FORMAT_OPTION,
106 CONSTRAINTS_OPTION,
107 SARIF_OUTPUT_FILE_OPTION,
108 SARIF_INCLUDE_PASS_OPTION,
109 NO_SCHEMA_VALIDATION_OPTION,
110 NO_CONSTRAINT_VALIDATION_OPTION);
111 }
112
113 @Override
114 public List<ExtraArgument> getExtraArguments() {
115 return EXTRA_ARGUMENTS;
116 }
117
118
119
120
121 protected abstract class AbstractValidationCommandExecutor
122 extends AbstractCommandExecutor {
123
124
125
126
127
128
129
130
131
132 public AbstractValidationCommandExecutor(
133 @NonNull CallingContext callingContext,
134 @NonNull CommandLine commandLine) {
135 super(callingContext, commandLine);
136 }
137
138
139
140
141
142
143
144
145
146
147 @NonNull
148 protected abstract IBindingContext getBindingContext(@NonNull Set<IConstraintSet> constraintSets)
149 throws CommandExecutionException;
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166 @NonNull
167 protected abstract IModule getModule(
168 @NonNull CommandLine commandLine,
169 @NonNull IBindingContext bindingContext)
170 throws CommandExecutionException;
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188 @NonNull
189 protected abstract ISchemaValidationProvider getSchemaValidationProvider(
190 @NonNull IModule module,
191 @NonNull CommandLine commandLine,
192 @NonNull IBindingContext bindingContext);
193
194
195
196
197 @SuppressWarnings("PMD.OnlyOneReturn")
198 @Override
199 public void execute() throws CommandExecutionException {
200 CommandLine cmdLine = getCommandLine();
201 URI currentWorkingDirectory = ObjectUtils.notNull(getCurrentWorkingDirectory().toUri());
202
203 Set<IConstraintSet> constraintSets = MetaschemaCommands.loadConstraintSets(
204 cmdLine,
205 CONSTRAINTS_OPTION,
206 currentWorkingDirectory);
207
208 List<String> extraArgs = cmdLine.getArgList();
209
210 URI source = MetaschemaCommands.handleSource(
211 ObjectUtils.requireNonNull(extraArgs.get(0)),
212 currentWorkingDirectory);
213
214 IBindingContext bindingContext = getBindingContext(constraintSets);
215 IBoundLoader loader = bindingContext.newBoundLoader();
216 Format asFormat = MetaschemaCommands.determineSourceFormat(
217 cmdLine,
218 MetaschemaCommands.AS_FORMAT_OPTION,
219 loader,
220 source);
221
222 IValidationResult validationResult = validate(source, asFormat, cmdLine, bindingContext);
223 handleOutput(source, validationResult, cmdLine, bindingContext);
224
225 if (validationResult == null || validationResult.isPassing()) {
226 if (LOGGER.isInfoEnabled()) {
227 LOGGER.info("The file '{}' is valid.", source);
228 }
229 } else if (LOGGER.isErrorEnabled()) {
230 LOGGER.error("The file '{}' is invalid.", source);
231 }
232
233 if (validationResult != null && !validationResult.isPassing()) {
234 throw new CommandExecutionException(ExitCode.FAIL);
235 }
236 }
237
238 @SuppressWarnings("PMD.CyclomaticComplexity")
239 @Nullable
240 private IValidationResult validate(
241 @NonNull URI source,
242 @NonNull Format asFormat,
243 @NonNull CommandLine commandLine,
244 @NonNull IBindingContext bindingContext) throws CommandExecutionException {
245
246 if (LOGGER.isInfoEnabled()) {
247 LOGGER.info("Validating '{}' as {}.", source, asFormat.name());
248 }
249
250 IValidationResult validationResult = null;
251 try {
252
253 IModule module = getModule(commandLine, bindingContext);
254 if (!commandLine.hasOption(NO_SCHEMA_VALIDATION_OPTION)) {
255
256 validationResult = getSchemaValidationProvider(module, commandLine, bindingContext)
257 .validateWithSchema(source, asFormat, bindingContext);
258 }
259
260 if (!commandLine.hasOption(NO_CONSTRAINT_VALIDATION_OPTION)) {
261 IMutableConfiguration<ValidationFeature<?>> configuration = new DefaultConfiguration<>();
262 if (commandLine.hasOption(SARIF_OUTPUT_FILE_OPTION) && commandLine.hasOption(SARIF_INCLUDE_PASS_OPTION)) {
263 configuration.enableFeature(ValidationFeature.VALIDATE_GENERATE_PASS_FINDINGS);
264 }
265
266
267 bindingContext.registerModule(module);
268 IValidationResult constraintValidationResult = bindingContext.validateWithConstraints(source, configuration);
269 validationResult = validationResult == null
270 ? constraintValidationResult
271 : AggregateValidationResult.aggregate(validationResult, constraintValidationResult);
272 }
273 } catch (FileNotFoundException ex) {
274 throw new CommandExecutionException(
275 ExitCode.IO_ERROR,
276 String.format("Resource not found at '%s'", source),
277 ex);
278 } catch (UnknownHostException ex) {
279 throw new CommandExecutionException(
280 ExitCode.IO_ERROR,
281 String.format("Unknown host for '%s'.", source),
282 ex);
283 } catch (IOException ex) {
284 throw new CommandExecutionException(ExitCode.IO_ERROR, ex.getLocalizedMessage(), ex);
285 } catch (MetapathException ex) {
286 throw new CommandExecutionException(ExitCode.PROCESSING_ERROR, ex.getLocalizedMessage(), ex);
287 }
288 return validationResult;
289 }
290
291 private void handleOutput(
292 @NonNull URI source,
293 @Nullable IValidationResult validationResult,
294 @NonNull CommandLine commandLine,
295 @NonNull IBindingContext bindingContext) throws CommandExecutionException {
296 if (commandLine.hasOption(SARIF_OUTPUT_FILE_OPTION) && LOGGER.isInfoEnabled()) {
297 Path sarifFile = ObjectUtils.notNull(Paths.get(commandLine.getOptionValue(SARIF_OUTPUT_FILE_OPTION)));
298
299 IVersionInfo version
300 = getCallingContext().getCLIProcessor().getVersionInfos().get(CLIProcessor.COMMAND_VERSION);
301
302 try {
303 SarifValidationHandler sarifHandler = new SarifValidationHandler(source, version);
304 if (validationResult != null) {
305 sarifHandler.addFindings(validationResult.getFindings());
306 }
307 sarifHandler.write(sarifFile, bindingContext);
308 } catch (IOException ex) {
309 throw new CommandExecutionException(ExitCode.IO_ERROR, ex.getLocalizedMessage(), ex);
310 }
311 } else if (validationResult != null && !validationResult.getFindings().isEmpty()) {
312 LOGGER.info("Validation identified the following issues:");
313 LoggingValidationHandler.instance().handleResults(validationResult);
314 }
315
316 }
317 }
318 }