From 340c1c44bad78992ec49746d3776c3cff1a96fbd Mon Sep 17 00:00:00 2001 From: Maxim Epishchev Date: Wed, 3 Feb 2016 14:34:44 +0300 Subject: Limit size "key" field in BitBucket POST API Refactoring method ApiClient::computeAPIKey() to check maximum size of computed key. Now field size contolled by special constant ApiClient::MAX_KEY_SIZE_BB_API. This changes will resolve, in my opinion, issue with continuously rebuilding: method ApiClient::hasBuildStatus() fail and PR's are always trigger new builds. --- .../BitbucketPullRequestsBuilder.java | 17 ++- .../BitbucketRepository.java | 4 + .../bitbucket/ApiClient.java | 32 +++++- src/test/java/BitbucketBuildFilterTest.java | 9 +- src/test/java/BitbucketBuildRepositoryTest.java | 115 ++++++++++++++++++++- 5 files changed, 169 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketPullRequestsBuilder.java b/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketPullRequestsBuilder.java index c5d4159..d337796 100644 --- a/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketPullRequestsBuilder.java +++ b/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketPullRequestsBuilder.java @@ -2,10 +2,15 @@ package bitbucketpullrequestbuilder.bitbucketpullrequestbuilder; import bitbucketpullrequestbuilder.bitbucketpullrequestbuilder.bitbucket.Pullrequest; import hudson.model.AbstractProject; + +import java.io.UnsupportedEncodingException; import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import java.util.Collection; +import java.util.logging.Level; import java.util.logging.Logger; + import org.apache.commons.codec.binary.Hex; /** @@ -54,12 +59,20 @@ public class BitbucketPullRequestsBuilder { return this.project; } + /** + * Return MD5 hashed full project name or full project name, if MD5 hash provider inaccessible + * @return unique project id + */ public String getProjectId() { try { final MessageDigest MD5 = MessageDigest.getInstance("MD5"); return new String(Hex.encodeHex(MD5.digest(this.project.getFullName().getBytes("UTF-8")))); - } catch (Exception exc) { - logger.severe(exc.toString()); + } catch (NoSuchAlgorithmException exc) { + logger.log(Level.WARNING, "Failed to produce hash", exc); + exc.printStackTrace(); + } catch (UnsupportedEncodingException exc) { + logger.log(Level.WARNING, "Failed to produce hash", exc); + exc.printStackTrace(); } return this.project.getFullName(); } diff --git a/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketRepository.java b/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketRepository.java index 23b0607..8439eea 100644 --- a/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketRepository.java +++ b/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketRepository.java @@ -9,18 +9,22 @@ import java.util.logging.Logger; import bitbucketpullrequestbuilder.bitbucketpullrequestbuilder.bitbucket.ApiClient; import bitbucketpullrequestbuilder.bitbucketpullrequestbuilder.bitbucket.BuildState; import bitbucketpullrequestbuilder.bitbucketpullrequestbuilder.bitbucket.Pullrequest; + import java.util.LinkedList; import java.util.logging.Level; import java.util.regex.Matcher; import java.util.regex.Pattern; + import com.cloudbees.plugins.credentials.CredentialsMatchers; import com.cloudbees.plugins.credentials.CredentialsProvider; import com.cloudbees.plugins.credentials.common.StandardUsernamePasswordCredentials; import com.cloudbees.plugins.credentials.common.UsernamePasswordCredentials; + import jenkins.model.Jenkins; import jenkins.scm.api.SCMSource; import jenkins.scm.api.SCMSourceOwner; import jenkins.scm.api.SCMSourceOwners; + import org.apache.commons.lang.StringUtils; import static com.cloudbees.plugins.credentials.CredentialsMatchers.instanceOf; diff --git a/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/bitbucket/ApiClient.java b/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/bitbucket/ApiClient.java index 509dfaf..047bf5a 100644 --- a/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/bitbucket/ApiClient.java +++ b/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/bitbucket/ApiClient.java @@ -16,6 +16,11 @@ import java.util.logging.Logger; import jenkins.model.Jenkins; import hudson.ProxyConfiguration; + +import java.io.UnsupportedEncodingException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import org.apache.commons.codec.binary.Hex; import org.apache.commons.httpclient.methods.PutMethod; import org.apache.commons.httpclient.util.EncodingUtil; @@ -26,13 +31,15 @@ public class ApiClient { private static final Logger logger = Logger.getLogger(ApiClient.class.getName()); private static final String V1_API_BASE_URL = "https://bitbucket.org/api/1.0/repositories/"; private static final String V2_API_BASE_URL = "https://bitbucket.org/api/2.0/repositories/"; - private static final String COMPUTED_KEY_FORMAT = "%s-%s"; + private static final String COMPUTED_KEY_FORMAT = "%s-%s"; private String owner; private String repositoryName; private Credentials credentials; private String key; private String name; private HttpClientFactory factory; + + public static final byte MAX_KEY_SIZE_BB_API = 40; public static class HttpClientFactory { public static final HttpClientFactory INSTANCE = new HttpClientFactory(); @@ -107,8 +114,29 @@ public class ApiClient { return this.name; } + private static MessageDigest SHA1 = null; + + /** + * Retrun + * @param keyExPart + * @return key parameter for call BitBucket API + */ private String computeAPIKey(String keyExPart) { - return String.format(COMPUTED_KEY_FORMAT, this.key, keyExPart); + String computedKey = String.format(COMPUTED_KEY_FORMAT, this.key, keyExPart); + + if (computedKey.length() > MAX_KEY_SIZE_BB_API) { + try { + if (SHA1 == null) SHA1 = MessageDigest.getInstance("SHA1"); + return new String(Hex.encodeHex(SHA1.digest(computedKey.getBytes("UTF-8")))); + } catch(NoSuchAlgorithmException e) { + logger.log(Level.WARNING, "Failed to create hash provider", e); + e.printStackTrace(); + } catch (UnsupportedEncodingException e) { + logger.log(Level.WARNING, "Failed to create hash provider", e); + e.printStackTrace(); + } + } + return (computedKey.length() <= MAX_KEY_SIZE_BB_API) ? computedKey : computedKey.substring(0, MAX_KEY_SIZE_BB_API); } public String buildStatusKey(String bsKey) { diff --git a/src/test/java/BitbucketBuildFilterTest.java b/src/test/java/BitbucketBuildFilterTest.java index 9b84e73..3b35dbc 100644 --- a/src/test/java/BitbucketBuildFilterTest.java +++ b/src/test/java/BitbucketBuildFilterTest.java @@ -5,17 +5,20 @@ import bitbucketpullrequestbuilder.bitbucketpullrequestbuilder.BitbucketPullRequ import bitbucketpullrequestbuilder.bitbucketpullrequestbuilder.BitbucketRepository; import bitbucketpullrequestbuilder.bitbucketpullrequestbuilder.bitbucket.ApiClient; import bitbucketpullrequestbuilder.bitbucketpullrequestbuilder.bitbucket.Pullrequest; + import java.util.Calendar; import java.util.LinkedList; import java.util.List; import java.util.regex.Pattern; + import jenkins.plugins.git.AbstractGitSCMSource; +import org.jvnet.hudson.test.JenkinsRule; +import org.jvnet.hudson.test.WithoutJenkins; + import org.easymock.*; import org.junit.Test; -import static org.junit.Assert.*; import org.junit.Rule; -import org.jvnet.hudson.test.JenkinsRule; -import org.jvnet.hudson.test.WithoutJenkins; +import static org.junit.Assert.*; /** * Tests diff --git a/src/test/java/BitbucketBuildRepositoryTest.java b/src/test/java/BitbucketBuildRepositoryTest.java index bb6de8f..5f1aca2 100644 --- a/src/test/java/BitbucketBuildRepositoryTest.java +++ b/src/test/java/BitbucketBuildRepositoryTest.java @@ -1,25 +1,41 @@ +import antlr.ANTLRException; + import bitbucketpullrequestbuilder.bitbucketpullrequestbuilder.BitbucketBuildTrigger; import bitbucketpullrequestbuilder.bitbucketpullrequestbuilder.BitbucketPullRequestsBuilder; import bitbucketpullrequestbuilder.bitbucketpullrequestbuilder.BitbucketRepository; import bitbucketpullrequestbuilder.bitbucketpullrequestbuilder.bitbucket.ApiClient; + import com.cloudbees.plugins.credentials.CredentialsProvider; import com.cloudbees.plugins.credentials.CredentialsScope; import com.cloudbees.plugins.credentials.CredentialsStore; import com.cloudbees.plugins.credentials.domains.Domain; import com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl; + +import com.google.common.base.Function; +import com.google.common.collect.Collections2; + +import java.io.UnsupportedEncodingException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; +import java.util.Collection; import org.easymock.*; import org.junit.Test; import static org.junit.Assert.*; import org.junit.Rule; +import org.junit.Assert; import org.jvnet.hudson.test.JenkinsRule; + import jenkins.model.Jenkins; +import org.jvnet.hudson.test.WithoutJenkins; + +import org.apache.commons.codec.binary.Hex; import org.apache.commons.httpclient.Credentials; import org.apache.commons.httpclient.HttpClient; import org.apache.commons.httpclient.HttpState; import org.apache.commons.httpclient.UsernamePasswordCredentials; import org.apache.commons.httpclient.auth.AuthScope; -import org.junit.Assert; interface ICredentialsInterceptor { @@ -150,4 +166,101 @@ public class BitbucketBuildRepositoryTest { try { repo.postPullRequestApproval("prId"); } catch(Error e) { assertTrue(e instanceof AssertionError); } } + + class MD5HasherFunction implements Function { + protected final MessageDigest MD5; + public MD5HasherFunction(MessageDigest md5) { this.MD5 = md5; } + public String apply(String f) { + try { return new String(Hex.encodeHex(MD5.digest(f.getBytes("UTF-8")))); } catch(UnsupportedEncodingException e) { } + return null; + } + } + + class SHA1HasherFunction implements Function { + protected final MessageDigest SHA1; + public SHA1HasherFunction(MessageDigest sha1) { this.SHA1 = sha1; } + public String apply(String f) { + try { return new String(Hex.encodeHex(SHA1.digest(f.getBytes("UTF-8")))); } catch(UnsupportedEncodingException e) { } + return null; + } + } + + @Test + @WithoutJenkins + public void repositoryProjectIdTest() throws ANTLRException, NoSuchAlgorithmException, UnsupportedEncodingException { + BitbucketBuildTrigger trigger = new BitbucketBuildTrigger( + "", "@hourly", + "JenkinsCID", + "foo", + "bar", + "", "", + "", true, + "jenkins", "Jenkins", "", + true, + true + ); + + BitbucketPullRequestsBuilder builder = EasyMock.createMock(BitbucketPullRequestsBuilder.class); + EasyMock.expect(builder.getTrigger()).andReturn(trigger).anyTimes(); + + final MessageDigest MD5 = MessageDigest.getInstance("MD5"); + + String[] projectIds = new String[] { + "one", + "Second project", + "Project abstract 1.1", + "Good project, careated at " + (new java.util.Date()).toString(), + }; + + Collection hashedProjectIdsCollection = Collections2.transform(Arrays.asList(projectIds), new MD5HasherFunction(MD5)); + String[] hashedPojectIds = hashedProjectIdsCollection.toArray(new String[hashedProjectIdsCollection.size()]); + + for(String projectId : hashedPojectIds) { + EasyMock.expect(builder.getProjectId()).andReturn(projectId).times(1); + } + EasyMock.replay(builder); + + BitbucketRepository repo = new BitbucketRepository("", builder); + repo.init(); + + for(String projectId : projectIds) { + String hashMD5 = new String(Hex.encodeHex(MD5.digest(projectId.getBytes("UTF-8")))); + String buildStatusKey = repo.getClient().buildStatusKey(builder.getProjectId()); + + assertTrue(buildStatusKey.length() <= ApiClient.MAX_KEY_SIZE_BB_API); + assertEquals(buildStatusKey, "jenkins-" + hashMD5); + } + } + + @Test + @WithoutJenkins + public void triggerLongCIKeyTest() throws ANTLRException, NoSuchAlgorithmException { + BitbucketBuildTrigger trigger = new BitbucketBuildTrigger( + "", "@hourly", + "JenkinsCID", + "foo", + "bar", + "", "", + "", true, + "jenkins-too-long-ci-key", "Jenkins", "", + true, + true + ); + + final MessageDigest MD5 = MessageDigest.getInstance("MD5"); + final MessageDigest SHA1 = MessageDigest.getInstance("SHA1"); + + BitbucketPullRequestsBuilder builder = EasyMock.createMock(BitbucketPullRequestsBuilder.class); + EasyMock.expect(builder.getTrigger()).andReturn(trigger).anyTimes(); + EasyMock.expect(builder.getProjectId()).andReturn((new MD5HasherFunction(MD5)).apply("projectId")).anyTimes(); + EasyMock.replay(builder); + + BitbucketRepository repo = new BitbucketRepository("", builder); + repo.init(); + + String buildStatusKey = repo.getClient().buildStatusKey(builder.getProjectId()); + assertTrue(buildStatusKey.length() <= ApiClient.MAX_KEY_SIZE_BB_API); + assertFalse(buildStatusKey.startsWith("jenkins-")); + assertEquals((new SHA1HasherFunction(SHA1)).apply("jenkins-too-long-ci-key" + "-" + builder.getProjectId()), buildStatusKey); + } } -- cgit v1.2.3