Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
package com.fortify.cli.generic_action._main.cli.cmd;

import com.fortify.cli.common.cli.cmd.AbstractContainerCommand;
import com.fortify.cli.common.cli.util.ModuleType;
import com.fortify.cli.common.cli.util.ProductModule;
import com.fortify.cli.generic_action.action.cli.cmd.GenericActionAsciidocCommand;
import com.fortify.cli.generic_action.action.cli.cmd.GenericActionGetCommand;
import com.fortify.cli.generic_action.action.cli.cmd.GenericActionHelpCommand;
Expand All @@ -24,6 +26,7 @@

import picocli.CommandLine.Command;

@ProductModule(ModuleType.OTHER)
@Command(
name = "action",
resourceBundle = "com.fortify.cli.generic_action.i18n.GenericActionMessages",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,14 @@
import com.fortify.cli.aviator.ssc.cli.cmd.AviatorSSCCommands;
import com.fortify.cli.aviator.token.cli.cmd.AviatorTokenCommands;
import com.fortify.cli.common.cli.cmd.AbstractContainerCommand;
import com.fortify.cli.common.cli.util.ModuleType;
import com.fortify.cli.common.cli.util.ProductModule;
import com.fortify.cli.common.cli.util.RelatedModules;

import picocli.CommandLine.Command;

@ProductModule(ModuleType.PRODUCT)
@RelatedModules({"ssc"})
@Command(
name = "aviator",
resourceBundle = "com.fortify.cli.aviator.i18n.AviatorMessages",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Copyright 2021-2026 Open Text.
*
* The only warranties for products and services of Open Text
* and its affiliates and licensors ("Open Text") are as may
* be set forth in the express warranty statements accompanying
* such products and services. Nothing herein should be construed
* as constituting an additional warranty. Open Text shall not be
* liable for technical or editorial errors or omissions contained
* herein. The information contained herein is subject to change
* without notice.
*/
package com.fortify.cli.common.cli.util;

/**
* Enum that holds the type of a module as a product module (SSC, FoD, Aviator, SC-SAST, SC-DAST)
* or a non-product/other module (util, tool, license, actions, config, ...)
*
* @author Sangamesh Vijaykumar
*/

public enum ModuleType {
PRODUCT, // SSC, FoD, Aviator, SC-SAST, SC-DAST
OTHER // util, tool, license, actions, config, ...
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* Copyright 2021-2026 Open Text.
*
* The only warranties for products and services of Open Text
* and its affiliates and licensors ("Open Text") are as may
* be set forth in the express warranty statements accompanying
* such products and services. Nothing herein should be construed
* as constituting an additional warranty. Open Text shall not be
* liable for technical or editorial errors or omissions contained
* herein. The information contained herein is subject to change
* without notice.
*/
package com.fortify.cli.common.cli.util;

import static java.lang.annotation.RetentionPolicy.RUNTIME;

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

/**
* Marks a module as a product module (SSC, FoD, Aviator, SC-SAST, SC-DAST)
* or a non-product/other module (util, tool, license, actions, config, ...).
*
* @author Sangamesh Vijaykumar
*/
@Retention(RUNTIME)
@Target(ElementType.TYPE)
public @interface ProductModule {
ModuleType value();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* Copyright 2021-2026 Open Text.
*
* The only warranties for products and services of Open Text
* and its affiliates and licensors ("Open Text") are as may
* be set forth in the express warranty statements accompanying
* such products and services. Nothing herein should be construed
* as constituting an additional warranty. Open Text shall not be
* liable for technical or editorial errors or omissions contained
* herein. The information contained herein is subject to change
* without notice.
*/
package com.fortify.cli.common.cli.util;

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

/**
* Defines which base modules a module is related to.
* Example: @RelatedModules({"ssc","fod"}) on the tool/util module.
*
* @author Sangamesh Vijaykumar
*/

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface RelatedModules {
String[] value(); // base modules this module is related to, e.g. {"ssc","fod"}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,16 @@
package com.fortify.cli.config._main.cli.cmd;

import com.fortify.cli.common.cli.cmd.AbstractContainerCommand;
import com.fortify.cli.common.cli.util.ModuleType;
import com.fortify.cli.common.cli.util.ProductModule;
import com.fortify.cli.config.language.cli.cmd.LanguageCommands;
import com.fortify.cli.config.proxy.cli.cmd.ProxyCommands;
import com.fortify.cli.config.publickey.cli.cmd.PublicKeyCommands;
import com.fortify.cli.config.truststore.cli.cmd.TrustStoreCommands;

import picocli.CommandLine.Command;

@ProductModule(ModuleType.OTHER)
@Command(
name = "config",
aliases = "cfg",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,18 +43,21 @@ public static class FoDAuthOptions {
@Getter private FoDCredentialOptions credentialOptions = new FoDCredentialOptions();
@Option(names="--scopes", defaultValue="api-tenant", split=",")
@Getter private String[] scopes;
@Option(names = {"-t", "--tenant"}, required = false)
@MaskValue(sensitivity = LogSensitivityLevel.low, description = "FOD TENANT")
@Getter private String tenant; // Optional: required only for user credentials
}

public static class FoDCredentialOptions {
@ArgGroup(exclusive = false, multiplicity = "1", order = 1)
@Getter private UserCredentialOptions userCredentialOptions = new UserCredentialOptions();
@Getter private FoDUserCredentialOptions userCredentialOptions = new FoDUserCredentialOptions();
@ArgGroup(exclusive = false, multiplicity = "1", order = 2)
@Getter private FoDClientCredentialOptions clientCredentialOptions = new FoDClientCredentialOptions();
}

public static class FoDUserCredentialOptions extends UserCredentialOptions {
@Option(names = {"-t", "--tenant"}, required = true)
@MaskValue(sensitivity = LogSensitivityLevel.low, description = "FOD TENANT")
@Getter private String tenant;
}

public static class FoDClientCredentialOptions implements IFoDClientCredentials {
@Option(names = {"--client-id"}, required = true)
@MaskValue(sensitivity = LogSensitivityLevel.medium, description = "FOD CLIENT ID")
Expand All @@ -64,7 +67,7 @@ public static class FoDClientCredentialOptions implements IFoDClientCredentials
@Getter private String clientSecret;
}

public UserCredentialOptions getUserCredentialOptions() {
public FoDUserCredentialOptions getUserCredentialOptions() {
return Optional.ofNullable(authOptions)
.map(FoDAuthOptions::getCredentialOptions)
.map(FoDCredentialOptions::getUserCredentialOptions)
Expand All @@ -84,9 +87,10 @@ public final boolean hasUserCredentials() {

public final BasicFoDUserCredentials getUserCredentials() {
var u = getUserCredentialOptions();
var t = Optional.ofNullable(authOptions).map(FoDAuthOptions::getTenant).orElse(null);
if ( u==null || StringUtils.isBlank(t) || StringUtils.isBlank(u.getUser()) || u.getPassword()==null ) {
throw new FcliSimpleException("--tenant, --user and --password must all be specified for user credential authentication");
var t = Optional.ofNullable(u).map(FoDUserCredentialOptions::getTenant).orElse(null);
if (u == null || StringUtils.isBlank(t) || StringUtils.isBlank(u.getUser()) || u.getPassword() == null) {
throw new FcliSimpleException(
"--tenant, --user and --password must all be specified for user credential authentication");
}
return BasicFoDUserCredentials.builder().tenant(t).user(u.getUser()).password(u.getPassword()).build();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
package com.fortify.cli.fod._main.cli.cmd;

import com.fortify.cli.common.cli.cmd.AbstractContainerCommand;
import com.fortify.cli.common.cli.util.ModuleType;
import com.fortify.cli.common.cli.util.ProductModule;
import com.fortify.cli.fod._common.session.cli.cmd.FoDSessionCommands;
import com.fortify.cli.fod.access_control.cli.cmd.FoDAccessControlCommands;
import com.fortify.cli.fod.action.cli.cmd.FoDActionCommands;
Expand All @@ -30,6 +32,7 @@

import picocli.CommandLine.Command;

@ProductModule(ModuleType.PRODUCT)
@Command(
name = "fod",
resourceBundle = "com.fortify.cli.fod.i18n.FoDMessages",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fortify.cli.common.exception.FcliSimpleException;
import com.fortify.cli.common.json.JsonHelper;
import com.fortify.cli.common.json.producer.AbstractObjectNodeProducer.AbstractObjectNodeProducerBuilder;
import com.fortify.cli.common.json.producer.IObjectNodeProducer;
Expand All @@ -49,6 +48,7 @@
import kong.unirest.HttpRequest;
import kong.unirest.UnirestInstance;
import lombok.Getter;
import picocli.CommandLine.ArgGroup;
import picocli.CommandLine.Command;
import picocli.CommandLine.Mixin;
import picocli.CommandLine.Option;
Expand All @@ -57,8 +57,8 @@
public class FoDIssueListCommand extends AbstractFoDOutputCommand implements IServerSideQueryParamGeneratorSupplier {
@Getter @Mixin private OutputHelperMixins.List outputHelper;
@Mixin private FoDDelimiterMixin delimiterMixin; // injected in resolvers
@Mixin private FoDAppResolverMixin.OptionalOption appResolver;
@Mixin private FoDReleaseByQualifiedNameOrIdResolverMixin.OptionalOption releaseResolver;
@ArgGroup(exclusive = true, multiplicity = "1", order = 1)
@Getter private TargetSpecifierArgGroup targetSpecifier = new TargetSpecifierArgGroup();
@Mixin private FoDFiltersParamMixin filterParamMixin;
@Mixin private FoDIssueEmbedMixin embedMixin;
@Mixin private FoDIssueIncludeMixin includeMixin;
Expand All @@ -75,23 +75,34 @@ public class FoDIssueListCommand extends AbstractFoDOutputCommand implements ISe
.add("severityString","severityString")
.add("category","category");

public static class TargetSpecifierArgGroup {
@ArgGroup(exclusive = false, multiplicity = "1", order = 1) @Getter private AppTarget app = new AppTarget();
@ArgGroup(exclusive = false, multiplicity = "1", order = 2) @Getter private ReleaseTarget release = new ReleaseTarget();
}

public static class AppTarget extends FoDAppResolverMixin.AbstractFoDAppResolverMixin {
@Option(names = { "--app" }, required = true, descriptionKey = "fcli.fod.app.app-name-or-id") @Getter private String appNameOrId;
}

public static class ReleaseTarget extends FoDReleaseByQualifiedNameOrIdResolverMixin.AbstractFoDQualifiedReleaseNameOrIdResolverMixin {
@Option(names = { "--release", "--rel" }, required = true, paramLabel = "id|app[:ms]:rel", descriptionKey = "fcli.fod.release.resolver.name-or-id") @Getter private String qualifiedReleaseNameOrId;
}

@Override
protected IObjectNodeProducer getObjectNodeProducer(UnirestInstance unirest) {
boolean releaseSpecified = releaseResolver.getQualifiedReleaseNameOrId() != null;
boolean appSpecified = appResolver.getAppNameOrId() != null;
if ( releaseSpecified && appSpecified ) {
throw new FcliSimpleException("Cannot specify both an application and release");
}
if ( !releaseSpecified && !appSpecified ) {
throw new FcliSimpleException("Either an application or release must be specified");
var appGroup = targetSpecifier.getApp();
var releaseGroup = targetSpecifier.getRelease();

boolean appSpecified = appGroup != null && appGroup.getAppNameOrId() != null;
boolean releaseSpecified = releaseGroup != null && releaseGroup.getQualifiedReleaseNameOrId() != null;

if (releaseSpecified) {
releaseGroup.setDelimiterMixin(delimiterMixin);
}

var result = releaseSpecified
? singleReleaseProducerBuilder(unirest, releaseResolver.getReleaseId(unirest))
: applicationProducerBuilder(unirest, appResolver.getAppId(unirest));
// For consistent output, we should remove releaseId/releaseName when listing across multiple releases,
// but that breaks existing scripts that may rely on those fields, so for now, we only do this in
// applicationProducerBuilder(). TODO: Change in in fcli v4.0.
// return result.recordTransformer(this::removeReleaseProperties).build();
? singleReleaseProducerBuilder(unirest, releaseGroup.getReleaseId(unirest))
: applicationProducerBuilder(unirest, appGroup.getAppId(unirest));
return result.build();
}

Expand Down Expand Up @@ -225,17 +236,15 @@ private JsonNode enrichIssueRecord(UnirestInstance unirest, String releaseName,
}

private boolean isEffectiveFastOutput() {
boolean appSpecified = appResolver.getAppNameOrId() != null;
boolean releaseSpecified = releaseResolver.getQualifiedReleaseNameOrId() != null;
if ( !appSpecified || releaseSpecified ) { return false; }
var appGroup = targetSpecifier.getApp();
var releaseGroup = targetSpecifier.getRelease();

boolean appSpecified = appGroup != null && appGroup.getAppNameOrId() != null;
boolean releaseSpecified = releaseGroup != null && releaseGroup.getQualifiedReleaseNameOrId() != null;
if (!appSpecified || releaseSpecified) { return false; }
boolean fastOutputStyle = outputHelper.getRecordWriterStyle().isFastOutput();
boolean streamingSupported = outputHelper.isStreamingOutputSupported();
boolean recordConsumerConfigured = getRecordConsumer()!=null;
// Effective fast output requires:
// - application specified (multiple releases)
// - fast output style
// - no aggregation (merging requires full set)
// - streaming output or record consumer configured
boolean recordConsumerConfigured = getRecordConsumer() != null;
return fastOutputStyle && !aggregate && (streamingSupported || recordConsumerConfigured);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
import kong.unirest.HttpResponse;
import kong.unirest.UnirestInstance;
import lombok.Getter;
import picocli.CommandLine.ArgGroup;
import picocli.CommandLine.Command;
import picocli.CommandLine.Mixin;
import picocli.CommandLine.Option;
Expand All @@ -47,24 +48,40 @@
@CommandGroup("oss-components")
public final class FoDOssComponentsListCommand extends AbstractFoDJsonNodeOutputCommand {
private static final Logger LOG = LoggerFactory.getLogger(FoDOssComponentsListCommand.class);
@Getter
@Mixin
private OutputHelperMixins.TableWithQuery outputHelper;
@Mixin
private FoDDelimiterMixin delimiterMixin; // Is automatically injected in resolver mixins
@Mixin
private FoDAppResolverMixin.OptionalOption appResolver;
@Mixin
private FoDReleaseByQualifiedNameOrIdResolverMixin.OptionalOption releaseResolver;
@Option(names = "--scan-types", required = true, split = ",", defaultValue = "Debricked")
private FoDOpenSourceScanType[] scanTypes;
@Getter @Mixin private OutputHelperMixins.TableWithQuery outputHelper;
@Mixin private FoDDelimiterMixin delimiterMixin;
@ArgGroup(exclusive = true, multiplicity = "1", order = 1) @Getter private TargetSpecifierArgGroup targetSpecifier = new TargetSpecifierArgGroup();
@Option(names = "--scan-types", required = true, split = ",", defaultValue = "Debricked") private FoDOpenSourceScanType[] scanTypes;

public static class TargetSpecifierArgGroup {
@ArgGroup(exclusive = false, multiplicity = "1", order = 1) @Getter private AppTarget app = new AppTarget();
@ArgGroup(exclusive = false, multiplicity = "1", order = 2) @Getter private ReleaseTarget release = new ReleaseTarget();
}

public static class AppTarget extends FoDAppResolverMixin.AbstractFoDAppResolverMixin {
@Option(names = { "--app" }, required = true, descriptionKey = "fcli.fod.app.app-name-or-id") @Getter private String appNameOrId;
}

public static class ReleaseTarget extends FoDReleaseByQualifiedNameOrIdResolverMixin.AbstractFoDQualifiedReleaseNameOrIdResolverMixin {
@Option(names = { "--release", "--rel" }, required = true, paramLabel = "id|app[:ms]:rel", descriptionKey = "fcli.fod.release.resolver.name-or-id") @Getter private String qualifiedReleaseNameOrId;
}

@Override
public JsonNode getJsonNode(UnirestInstance unirest) {
ArrayNode result = JsonHelper.getObjectMapper().createArrayNode();

var appGroup = targetSpecifier.getApp();
var releaseGroup = targetSpecifier.getRelease();

final String applicationId = (appGroup != null && appGroup.getAppNameOrId() != null)
? appGroup.getAppId(unirest)
: null;
final String releaseId = (releaseGroup != null && releaseGroup.getQualifiedReleaseNameOrId() != null)
? releaseGroup.getReleaseId(unirest)
: null;

Stream.of(scanTypes)
.map(t -> getForOpenSourceScanType(unirest, t, releaseResolver.getReleaseId(unirest),
appResolver.getAppId(unirest), false))
.map(t -> getForOpenSourceScanType(unirest, t, releaseId, applicationId, false))
.forEach(result::addAll);
return result;
}
Expand Down Expand Up @@ -100,7 +117,7 @@ private ArrayNode getForOpenSourceScanType(UnirestInstance unirest, FoDOpenSourc
if (failOnError) {
throw e;
}
LOG.error("Error retrieving OSS components for release " + releaseResolver.getReleaseId(unirest)
LOG.error("Error retrieving OSS components for release " + releaseId
+ " and scan type " + scanType.name() + ": " + e.getMessage());
return JsonHelper.getObjectMapper().createArrayNode();
}
Expand Down
Loading
Loading