Skip to content
Permalink
Browse files
[Fix JENKINS-32574] Replace job key and secret by jenkins credential …
…storage
  • Loading branch information
amansilla committed Mar 3, 2016
1 parent d73ec91 commit 125a9ed98a418e194e4c0981e5514e6cbeb231c7
Showing 5 changed files with 138 additions and 56 deletions.
@@ -76,6 +76,11 @@
<artifactId>scribe</artifactId>
<version>1.3.3</version>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>credentials</artifactId>
<version>1.22</version>
</dependency>
</dependencies>

</project>
@@ -1,12 +1,20 @@
package org.jenkinsci.plugins.bitbucket;

import com.cloudbees.plugins.credentials.CredentialsProvider;
import com.cloudbees.plugins.credentials.common.StandardUsernameListBoxModel;
import com.cloudbees.plugins.credentials.common.StandardUsernamePasswordCredentials;
import com.cloudbees.plugins.credentials.common.UsernamePasswordCredentials;
import com.cloudbees.plugins.credentials.domains.DomainRequirement;
import com.cloudbees.plugins.credentials.domains.URIRequirementBuilder;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import hudson.Extension;
import hudson.Launcher;
import hudson.model.AbstractBuild;
import hudson.model.AbstractProject;
import hudson.model.BuildListener;
import hudson.model.Item;
import hudson.model.Job;
import hudson.model.Result;
import hudson.plugins.git.GitSCM;
import hudson.plugins.git.util.BuildData;
@@ -17,46 +25,40 @@
import hudson.tasks.Notifier;
import hudson.tasks.Publisher;
import hudson.util.FormValidation;
import hudson.util.ListBoxModel;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import jenkins.model.Jenkins;
import net.sf.json.JSONObject;
import org.apache.commons.codec.digest.DigestUtils;
import org.eclipse.jgit.transport.RemoteConfig;
import org.eclipse.jgit.transport.URIish;
import org.jenkinsci.plugins.bitbucket.api.*;
import org.jenkinsci.plugins.bitbucket.model.BitbucketBuildStatus;
import org.jenkinsci.plugins.bitbucket.model.BitbucketBuildStatusResource;
import org.jenkinsci.plugins.bitbucket.model.BitbucketBuildStatusSerializer;
import org.kohsuke.stapler.AncestorInPath;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.StaplerRequest;
import org.scribe.model.*;

public class BitbucketBuildStatusNotifier extends Notifier {

private static final Logger logger = Logger.getLogger(BitbucketBuildStatusNotifier.class.getName());

private String apiKey;
private String apiSecret;
private boolean notifyStart;
private boolean notifyFinish;
private String credentialsId;

@DataBoundConstructor
public BitbucketBuildStatusNotifier(final String apiKey, final String apiSecret, final boolean notifyStart,
final boolean notifyFinish) {
public BitbucketBuildStatusNotifier(final boolean notifyStart, final boolean notifyFinish,
final String credentialsId) {
super();
this.apiKey = apiKey;
this.apiSecret = apiSecret;
this.notifyStart = notifyStart;
this.notifyFinish = notifyFinish;
}

public String getApiKey() {
return this.apiKey;
}

public String getApiSecret() {
return this.apiSecret;
this.credentialsId = credentialsId;
}

public boolean getNotifyStart() {
@@ -67,6 +69,10 @@ public boolean getNotifyFinish() {
return this.notifyFinish;
}

public String getCredentialsId() {
return this.credentialsId;
}

@Override
public boolean prebuild(AbstractBuild<?, ?> build, BuildListener listener) {

@@ -76,10 +82,7 @@ public boolean prebuild(AbstractBuild<?, ?> build, BuildListener listener) {
logger.info("Bitbucket notify on start");

try {
BitbucketBuildStatusResource buildStatusResource = this.createBuildStatusResourceFromBuild(build);
BitbucketBuildStatus buildStatus = this.createBitbucketBuildStatusFromBuild(build);
this.notifyBuildStatus(buildStatusResource, buildStatus);
listener.getLogger().println("Sending build status " + buildStatus.getState() + " for commit " + buildStatusResource.getCommitId() + " to BitBucket is done!");
this.notifyBuildStatus(build, listener);
} catch (Exception e) {
logger.log(Level.INFO, "Bitbucket notify on start failed: " + e.getMessage(), e);
listener.getLogger().println("Bitbucket notify on start failed: " + e.getMessage());
@@ -100,10 +103,7 @@ public boolean perform(AbstractBuild<?, ?> build, Launcher launcher, BuildListen
logger.info("Bitbucket notify on finish");

try {
BitbucketBuildStatusResource buildStatusResource = this.createBuildStatusResourceFromBuild(build);
BitbucketBuildStatus buildStatus = this.createBitbucketBuildStatusFromBuild(build);
this.notifyBuildStatus(buildStatusResource, buildStatus);
listener.getLogger().println("Sending build status " + buildStatus.getState() + " for commit " + buildStatusResource.getCommitId() + " to BitBucket is done!");
this.notifyBuildStatus(build, listener);
} catch (Exception e) {
logger.log(Level.INFO, "Bitbucket notify on finish failed: " + e.getMessage(), e);
listener.getLogger().println("Bitbucket notify on finish failed: " + e.getMessage());
@@ -115,6 +115,18 @@ public boolean perform(AbstractBuild<?, ?> build, Launcher launcher, BuildListen
return true;
}

public static StandardUsernamePasswordCredentials getCredentials(String credentialsId, Job<?,?> owner) {
if (credentialsId != null) {
for (StandardUsernamePasswordCredentials c : CredentialsProvider.lookupCredentials(StandardUsernamePasswordCredentials.class, owner, null, URIRequirementBuilder.fromUri(BitbucketApi.OAUTH_ENDPOINT).build())) {
if (c.getId().equals(credentialsId)) {
return c;
}
}
}

return null;
}

@Override
public BuildStepMonitor getRequiredMonitorService() {
return BuildStepMonitor.NONE;
@@ -197,9 +209,21 @@ private BitbucketBuildStatusResource createBuildStatusResourceFromBuild(final Ab
return new BitbucketBuildStatusResource(userName, repoName, commitId);
}

private void notifyBuildStatus(final BitbucketBuildStatusResource buildStatusResource, final BitbucketBuildStatus buildStatus) throws Exception {
private void notifyBuildStatus(final AbstractBuild build, final BuildListener listener) throws Exception {

UsernamePasswordCredentials credentials = this.getCredentials(this.getCredentialsId(), build.getProject());
BitbucketBuildStatusResource buildStatusResource = this.createBuildStatusResourceFromBuild(build);
BitbucketBuildStatus buildStatus = this.createBitbucketBuildStatusFromBuild(build);

if (credentials == null) {
Job job = null;
credentials = this.getCredentials(this.getDescriptor().getGlobalCredentialsId(), job);
}
if (credentials == null) {
throw new Exception("Credentials could not be found!");
}

OAuthConfig config = new OAuthConfig(this.apiKey, this.apiSecret);
OAuthConfig config = new OAuthConfig(credentials.getUsername(), credentials.getPassword().getPlainText());
BitbucketApiService apiService = (BitbucketApiService) new BitbucketApi().createService(config);

GsonBuilder gsonBuilder = new GsonBuilder();
@@ -217,6 +241,7 @@ private void notifyBuildStatus(final BitbucketBuildStatusResource buildStatusRes

Response response = request.send();
logger.info("This response was received:" + response.getBody());
listener.getLogger().println("Sending build status " + buildStatus.getState() + " for commit " + buildStatusResource.getCommitId() + " to BitBucket is done!");
}

private BitbucketBuildStatus createBitbucketBuildStatusFromBuild(AbstractBuild build) {
@@ -289,6 +314,16 @@ public String findCurrentCommitId() throws Exception {
@Extension // This indicates to Jenkins that this is an implementation of an extension point.
public static class DescriptorImpl extends BuildStepDescriptor<Publisher> {

private String globalCredentialsId;

public String getGlobalCredentialsId() {
return globalCredentialsId;
}

public void setGlobalCredentialsId(String globalCredentialsId) {
this.globalCredentialsId = globalCredentialsId;
}

@Override
public String getDisplayName() {
return "Bitbucket notify build status";
@@ -303,23 +338,73 @@ public boolean isApplicable(Class<? extends AbstractProject> aClass) {
return true;
}

@Override
public boolean configure(StaplerRequest req, JSONObject formData) throws FormException {
req.bindJSON(this, formData.getJSONObject("bitbucket-build-status-notifier"));
save();

public FormValidation doCheckApiKey(@QueryParameter final String apiKey, @QueryParameter final String apiSecret) throws FormException {
return true;
}

if (apiKey.isEmpty() || apiSecret.isEmpty()) {
return FormValidation.error("Please enter Bitbucket OAuth credentials");
public ListBoxModel doFillCredentialsIdItems(@AncestorInPath Job<?,?> owner) {
if (owner == null || !owner.hasPermission(Item.CONFIGURE)) {
return new ListBoxModel();
}
List<DomainRequirement> apiEndpoint = URIRequirementBuilder.fromUri(BitbucketApi.OAUTH_ENDPOINT).build();

return new StandardUsernameListBoxModel()
.withEmptySelection()
.withAll(CredentialsProvider.lookupCredentials(StandardUsernamePasswordCredentials.class, owner, null, apiEndpoint));
}

public FormValidation doCheckCredentialsId(@QueryParameter final String credentialsId,
@AncestorInPath final Job<?,?> owner) {
String globalCredentialsId = this.getGlobalCredentialsId();

if (credentialsId == null || credentialsId.isEmpty()) {
if (globalCredentialsId == null || globalCredentialsId.isEmpty()) {
return FormValidation.error("Please enter Bitbucket OAuth credentials");
} else {
return this.doCheckGlobalCredentialsId(this.getGlobalCredentialsId());
}
}

UsernamePasswordCredentials credentials = BitbucketBuildStatusNotifier.getCredentials(credentialsId, owner);

return this.checkCredentials(credentials);
}

public ListBoxModel doFillGlobalCredentialsIdItems() {
Job owner = null;
List<DomainRequirement> apiEndpoint = URIRequirementBuilder.fromUri(BitbucketApi.OAUTH_ENDPOINT).build();

return new StandardUsernameListBoxModel()
.withEmptySelection()
.withAll(CredentialsProvider.lookupCredentials(StandardUsernamePasswordCredentials.class, owner, null, apiEndpoint));
}

public FormValidation doCheckGlobalCredentialsId(@QueryParameter final String globalCredentialsId) {
if (globalCredentialsId.isEmpty()) {
return FormValidation.ok();
}

Job owner = null;
UsernamePasswordCredentials credentials = BitbucketBuildStatusNotifier.getCredentials(globalCredentialsId, owner);

return this.checkCredentials(credentials);
}

private FormValidation checkCredentials(UsernamePasswordCredentials credentials) {

try {
OAuthConfig config = new OAuthConfig(apiKey, apiSecret);
OAuthConfig config = new OAuthConfig(credentials.getUsername(), credentials.getPassword().getPlainText());
BitbucketApiService apiService = (BitbucketApiService) new BitbucketApi().createService(config);
Verifier verifier = null;
Token token = apiService.getAccessToken(OAuthConstants.EMPTY_TOKEN, verifier);

if (token.isEmpty()) {
FormValidation.error("Invalid Bitbucket OAuth credentials");
return FormValidation.error("Invalid Bitbucket OAuth credentials");
}

} catch (Exception e) {
return FormValidation.error(e.getClass() + e.getMessage());
}
@@ -9,7 +9,7 @@

public class BitbucketApi extends DefaultApi20 {

private static final String OAUTH_ENDPOINT = "https://bitbucket.org/site/oauth2/";
public static final String OAUTH_ENDPOINT = "https://bitbucket.org/site/oauth2/";

@Override
public String getAccessTokenEndpoint() {
@@ -1,15 +1,14 @@
<?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">
<f:entry title="${%Key}" field="apiKey">
<f:textbox />
</f:entry>
<f:entry title="${%Secret}" field="apiSecret">
<f:textbox />
</f:entry>
<f:entry title="Notify build start" field="notifyStart">
<f:checkbox />
</f:entry>
<f:entry title="Notify build finish" field="notifyFinish">
<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" xmlns:c="/lib/credentials">
<f:entry title="${%Notify build start}" field="notifyStart">
<f:checkbox />
</f:entry>
</f:entry>
<f:entry title="${%Notify build finish}" field="notifyFinish">
<f:checkbox />
</f:entry>
<f:advanced>
<f:entry title="${%Credentials}" field="credentialsId">
<c:select />
</f:entry>
</f:advanced>
</j:jelly>
@@ -1,15 +1,8 @@
<?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">
<!--
This Jelly script is used to produce the global configuration option.
Jenkins uses a set of tag libraries to provide uniformity in forms.
To determine where this tag is defined, first check the namespace URI,
and then look under $JENKINS/views/. For example, <f:section> is defined
in $JENKINS/views/lib/form/section.jelly.
It's also often useful to just check other similar scripts to see what
tags they use. Views are always organized according to its owner class,
so it should be straightforward to find them.
-->
<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" xmlns:c="/lib/credentials">
<f:section title="${%Bitbucket Build Status Notifier Plugin}" name="bitbucket-build-status-notifier">
<f:entry title="${%Global Credentials}" field="globalCredentialsId">
<c:select />
</f:entry>
</f:section>
</j:jelly>

0 comments on commit 125a9ed

Please sign in to comment.