Skip to content

Commit

Permalink
SECURITY-3146
Browse files Browse the repository at this point in the history
  • Loading branch information
tylercamp authored and Kevin-CB committed May 10, 2023
1 parent 138932b commit a971a75
Show file tree
Hide file tree
Showing 3 changed files with 116 additions and 24 deletions.
10 changes: 10 additions & 0 deletions pom.xml
Expand Up @@ -152,5 +152,15 @@ limitations under the License
<!-- Minimum version for use in pipeline scripts -->
<version>2.0</version>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>credentials</artifactId>
<version>2.3.11</version>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>plain-credentials</artifactId>
<version>1.7</version>
</dependency>
</dependencies>
</project>
124 changes: 103 additions & 21 deletions src/main/java/org/jenkinsci/plugins/codedx/CodeDxPublisher.java
Expand Up @@ -14,6 +14,11 @@
*/
package org.jenkinsci.plugins.codedx;

import com.cloudbees.plugins.credentials.CredentialsMatchers;
import com.cloudbees.plugins.credentials.CredentialsProvider;
import com.cloudbees.plugins.credentials.common.*;
import com.cloudbees.plugins.credentials.domains.DomainRequirement;
import com.cloudbees.plugins.credentials.domains.URIRequirementBuilder;
import com.codedx.api.client.*;
import com.codedx.api.client.Job;
import com.codedx.api.client.Project;
Expand All @@ -24,24 +29,30 @@
import hudson.Launcher;
import hudson.Extension;
import hudson.model.*;
import hudson.model.queue.Tasks;
import hudson.security.ACL;
import hudson.util.FormValidation;
import hudson.util.ListBoxModel;
import hudson.tasks.BuildStepMonitor;
import hudson.tasks.BuildStepDescriptor;
import hudson.tasks.Publisher;
import hudson.tasks.Recorder;
import hudson.util.Secret;
import jenkins.model.Jenkins;
import jenkins.tasks.SimpleBuildStep;
import net.sf.json.JSONObject;

import org.acegisecurity.Authentication;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.HttpClientBuilder;
import org.jenkinsci.plugins.codedx.model.CodeDxReportStatistics;
import org.jenkinsci.plugins.codedx.model.CodeDxGroupStatistics;
import org.jenkinsci.plugins.codedx.monitor.AnalysisMonitor;
import org.jenkinsci.plugins.codedx.monitor.DirectAnalysisMonitor;
import org.jenkinsci.plugins.codedx.monitor.GitJobAnalysisMonitor;
import org.jenkinsci.plugins.plaincredentials.StringCredentials;
import org.kohsuke.stapler.*;
import org.kohsuke.stapler.verb.POST;

Expand All @@ -66,7 +77,7 @@
public class CodeDxPublisher extends Recorder implements SimpleBuildStep {

private final String url;
private final String key;
private final String keyCredentialId;
private final String projectId;

// Comma separated list of source/binary file Ant GLOB patterns
Expand Down Expand Up @@ -96,20 +107,20 @@ public class CodeDxPublisher extends Recorder implements SimpleBuildStep {

/**
* @param url URL of the Code Dx server
* @param key API key of the Code Dx server
* @param keyCredentialId ID of a Secret Text Credential containing the API key of the Code Dx server
* @param projectId Code Dx project ID
* @param analysisName The name to use for the analysis
*/
@DataBoundConstructor
public CodeDxPublisher(
final String url,
final String key,
final String keyCredentialId,
final String projectId,
final String analysisName
) {
this.projectId = projectId;
this.url = url;
this.key = key;
this.keyCredentialId = keyCredentialId;
this.analysisName = analysisName.trim();

this.sourceAndBinaryFiles = "";
Expand All @@ -122,14 +133,6 @@ public CodeDxPublisher(
this.errorHandlingBehavior = BuildErrorBehavior.MarkFailed;

this.gitFetchConfiguration = null;

setupClient();
}

private void setupClient() {
if (this.client == null) {
this.client = buildClient(url, key, selfSignedCertificateFingerprint);
}
}

public AnalysisResultConfiguration getAnalysisResultConfiguration() {
Expand All @@ -149,8 +152,8 @@ public String getUrl() {
return url;
}

public String getKey() {
return key;
public String getKeyCredentialId() {
return keyCredentialId;
}

public String getSourceAndBinaryFiles() {
Expand Down Expand Up @@ -189,14 +192,12 @@ public void setSelfSignedCertificateFingerprint(String selfSignedCertificateFing
this.selfSignedCertificateFingerprint = selfSignedCertificateFingerprint;

this.client = null;
setupClient();
}

public String getAnalysisName(){ return analysisName; }

private String getLatestAnalysisUrl() {
if (projectId.length() != 0 && !projectId.equals("-1")) {
setupClient();
return client.buildLatestFindingsUrl(Integer.parseInt(projectId));
} else {
return null;
Expand Down Expand Up @@ -278,7 +279,19 @@ public void perform(

Date startingDate = new Date();

setupClient();
StringCredentials apiCredentials = CredentialsProvider.findCredentialById(
keyCredentialId,
StringCredentials.class,
build,
URIRequirementBuilder.fromUri(url).build()
);

if (apiCredentials == null) {
throw new AbortException("Unable to load credential for API Key");
}

String apiKey = apiCredentials.getSecret().getPlainText();
this.client = buildClient(url, apiKey, selfSignedCertificateFingerprint);
final Map<String, InputStream> toSend = new HashMap<String, InputStream>();
final PrintStream buildOutput = listener.getLogger();

Expand Down Expand Up @@ -717,15 +730,66 @@ public FormValidation doCheckProjectId(@QueryParameter final String value)
return FormValidation.ok();
}

public FormValidation doCheckKey(@QueryParameter final String value)
public FormValidation doCheckKey(@QueryParameter final Secret value)
throws IOException, ServletException {

if (value.length() == 0)
if (value.getPlainText().length() == 0)
return FormValidation.error("Please set a Key.");

return FormValidation.ok();
}

public ListBoxModel doFillKeyCredentialIdItems(@QueryParameter String url, @QueryParameter String keyCredentialId, @AncestorInPath Item item) {
if (url == null || url.trim().isEmpty()) {
return new StandardListBoxModel();
}

StandardListBoxModel result = new StandardListBoxModel();
if (item == null) {
if (!Jenkins.get().hasPermission(Jenkins.ADMINISTER)) {
return result.includeCurrentValue(keyCredentialId);
}
} else {
if (!item.hasPermission(Item.EXTENDED_READ)
&& !item.hasPermission(CredentialsProvider.USE_ITEM)) {
return result.includeCurrentValue(keyCredentialId);
}
}
return result
.includeMatchingAs(
ACL.SYSTEM,
item,
StringCredentials.class,
URIRequirementBuilder.fromUri(url).build(),
CredentialsMatchers.always()
)
.includeCurrentValue(keyCredentialId);
}

public FormValidation doCheckKeyCredentialId(@QueryParameter String url, @QueryParameter String keyCredentialId, @AncestorInPath Item item)
throws IOException, ServletException {
if (item == null) {
if (!Jenkins.get().hasPermission(Jenkins.ADMINISTER)) {
return FormValidation.ok();
}
} else {
if (!item.hasPermission(Item.EXTENDED_READ)
&& !item.hasPermission(CredentialsProvider.USE_ITEM)) {
return FormValidation.ok();
}
}
if (StringUtils.isBlank(keyCredentialId)) {
return FormValidation.error("API Key credential cannot be blank");
}
if (keyCredentialId.startsWith("${") && keyCredentialId.endsWith("}")) {
return FormValidation.warning("Cannot validate expression based credentials");
}
if (CredentialsProvider.listCredentials(StringCredentials.class, item, ACL.SYSTEM, URIRequirementBuilder.fromUri(url).build(), CredentialsMatchers.withId(keyCredentialId)).isEmpty()) {
return FormValidation.error("Cannot find currently selected credentials");
}
return FormValidation.ok();
}

@POST
public FormValidation doCheckUrl(@QueryParameter final String value, @QueryParameter final String selfSignedCertificateFingerprint, @AncestorInPath Item item)
throws IOException, ServletException {
Expand Down Expand Up @@ -832,12 +896,30 @@ public FormValidation doCheckToolOutputFiles(@QueryParameter final String value,
}

@POST
public ListBoxModel doFillProjectIdItems(@QueryParameter final String url, @QueryParameter final String selfSignedCertificateFingerprint, @QueryParameter final String key, @AncestorInPath Item item) {
public ListBoxModel doFillProjectIdItems(@QueryParameter final String url, @QueryParameter final String selfSignedCertificateFingerprint, @QueryParameter final String keyCredentialId, @AncestorInPath Item item) {
checkPermissionForRemoteRequests(item);

ListBoxModel listBox = new ListBoxModel();

CodeDxClient client = buildClient(url, key, selfSignedCertificateFingerprint);
if (StringUtils.isBlank(keyCredentialId) || StringUtils.isBlank(url)) {
return listBox;
}

StringCredentials keyCredential = CredentialsMatchers.firstOrNull(
CredentialsProvider.lookupCredentials(
StringCredentials.class,
item,
ACL.SYSTEM,
URIRequirementBuilder.fromUri(url).build()
),
CredentialsMatchers.withId(keyCredentialId)
);

if (keyCredential == null) {
return listBox;
}

CodeDxClient client = buildClient(url, keyCredential.getSecret().getPlainText(), selfSignedCertificateFingerprint);

try {
final List<Project> projects = client.getProjects();
Expand Down
@@ -1,6 +1,6 @@
<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout"
xmlns:t="/lib/hudson" xmlns:f="/lib/form" escapeText="false">
xmlns:t="/lib/hudson" xmlns:f="/lib/form" xmlns:c="/lib/credentials" escapeText="false">
<!--
This jelly script is used for per-project configuration.
Expand All @@ -18,8 +18,8 @@

</f:advanced>

<f:entry title="Server API Key" field="key" help="/plugin/codedx/help-key.html">
<f:textbox/>
<f:entry title="Server API Key" field="keyCredentialId" help="/plugin/codedx/help-key.html">
<c:select />
</f:entry>

<f:entry title="Code Dx Project" field="projectId">
Expand Down

0 comments on commit a971a75

Please sign in to comment.