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