Skip to content

Commit

Permalink
[Fix JENKINS-32574] Replace job key and secret by jenkins credential …
Browse files Browse the repository at this point in the history
…storage
  • Loading branch information
Antonio Mansilla committed Mar 3, 2016
1 parent d73ec91 commit 125a9ed
Show file tree
Hide file tree
Showing 5 changed files with 138 additions and 56 deletions.
5 changes: 5 additions & 0 deletions pom.xml
Expand Up @@ -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;
Expand All @@ -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() {
Expand All @@ -67,6 +69,10 @@ public boolean getNotifyFinish() {
return this.notifyFinish;
}

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

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

Expand All @@ -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());
Expand All @@ -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());
Expand All @@ -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;
Expand Down Expand Up @@ -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();
Expand All @@ -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) {
Expand Down Expand Up @@ -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";
Expand All @@ -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());
}
Expand Down
Expand Up @@ -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() {
Expand Down
@@ -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.