Skip to content

Adding token authentication to Bitbucket server (Stash). 2017.1.x #33

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: Indore-2017.1.x
Choose a base branch
from
Open
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
36 changes: 28 additions & 8 deletions CommitStatusPublisher.xml
Original file line number Diff line number Diff line change
Expand Up @@ -47,15 +47,35 @@
Bitbucket Server URL
</description>
</param>
<param name="stashUsername" dslName="userName">
<description>
<param name="stashAuthenticationType" dslName="authType" type="compound">
<description>
Type of authentication
</description>
<option name="personalToken" value="token">
<description>
Authentication using personal token
</description>
<param name="secure:stashToken" dslName="token">
<description>
Personal token to use
</description>
</param>
</option>
<option name="password" value="password">
<description>
Password authentication
</description>
<param name="stashUsername" dslName="userName">
<description>
A username for Bitbucket Server connection
</description>
</param>
<param name="secure:stashPassword" dslName="password">
<description>
</description>
</param>
<param name="secure:stashPassword" dslName="password">
<description>
A password for Bitbucket Server connection
</description>
</description>
</param>
</option>
</param>
</option>
<option name="gerrit" value="gerritStatusPublisher">
Expand Down Expand Up @@ -187,4 +207,4 @@
<option name="CUSTOM" value="custom"/>
</enum>
</types>
</dsl-extension>
</dsl-extension>
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ public class Constants {

public static final String STASH_PUBLISHER_ID = "atlassianStashPublisher";
public static final String STASH_BASE_URL = "stashBaseUrl";
public static final String STASH_AUTH_TYPE = "stashAuthType";
public static final String STASH_TOKEN = "secure:stashToken";
public static final String STASH_USERNAME = "stashUsername";
public static final String STASH_PASSWORD = "secure:stashPassword";

Expand Down Expand Up @@ -90,21 +92,6 @@ public String getSshKey() {
return ServerSshKeyManager.TEAMCITY_SSH_KEY_PROP;
}

@NotNull
public String getStashBaseUrl() {
return STASH_BASE_URL;
}

@NotNull
public String getStashUsername() {
return STASH_USERNAME;
}

@NotNull
public String getStashPassword() {
return STASH_PASSWORD;
}

@NotNull
public String getGerritServer() {
return GERRIT_SERVER;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package jetbrains.buildServer.commitPublisher.stash;

import jetbrains.buildServer.util.StringUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

public enum StashAuthenticationType {
TOKEN_AUTH("token"),
PASSWORD_AUTH("password"),
;

private final String myValue;


StashAuthenticationType(@NotNull final String value) {
myValue = value;
}

@NotNull
public String getValue() {
return myValue;
}

@NotNull
public static StashAuthenticationType parse(@Nullable final String value) {
//migration
if (value == null || StringUtil.isEmptyOrSpaces(value)) return PASSWORD_AUTH;

for (StashAuthenticationType v : values()) {
if (v.getValue().equals(value)) return v;
}

throw new IllegalArgumentException("Failed to parse StashAuthenticationType: " + value);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@
import java.io.IOException;
import java.util.Map;

import static com.google.common.base.Joiner.on;
import static com.google.common.collect.ImmutableMap.of;

class StashPublisher extends HttpBasedCommitStatusPublisher {
public static final String PUBLISH_QUEUED_BUILD_STATUS = "teamcity.stashCommitStatusPublisher.publishQueuedBuildStatus";

Expand Down Expand Up @@ -150,7 +153,8 @@ private String createMessage(@NotNull StashBuildStatus status,

private void vote(@NotNull String commit, @NotNull String data, @NotNull String buildDescription) {
String url = getBaseUrl() + "/rest/build-status/1.0/commits/" + commit;
postAsync(url, getUsername(), getPassword(), data, ContentType.APPLICATION_JSON, null, buildDescription);
postAsync(url, getUsername(), getPassword(), data, ContentType.APPLICATION_JSON,
of("Authorization", on(' ').join("Bearer", getToken())), buildDescription);
}

@Override
Expand Down Expand Up @@ -206,6 +210,10 @@ private String getBaseUrl() {
return HttpHelper.stripTrailingSlash(myParams.get(Constants.STASH_BASE_URL));
}

private String getToken() {
return myParams.get(Constants.STASH_TOKEN);
}

private String getUsername() {
return myParams.get(Constants.STASH_USERNAME);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import jetbrains.buildServer.commitPublisher.*;
import jetbrains.buildServer.commitPublisher.stash.data.StashError;
import jetbrains.buildServer.commitPublisher.stash.data.StashRepoInfo;
import jetbrains.buildServer.commitPublisher.stash.ui.UpdateChangesConstants;
import jetbrains.buildServer.serverSide.*;
import jetbrains.buildServer.serverSide.executors.ExecutorServices;
import jetbrains.buildServer.vcs.VcsRoot;
Expand All @@ -18,10 +19,16 @@
import java.io.IOException;
import java.util.*;

import static com.google.common.base.Joiner.on;
import static com.google.common.collect.ImmutableMap.of;
import static java.lang.String.format;
import static jetbrains.buildServer.commitPublisher.stash.StashPublisher.PUBLISH_QUEUED_BUILD_STATUS;
import static org.apache.http.entity.ContentType.APPLICATION_JSON;

public class StashSettings extends BasePublisherSettings implements CommitStatusPublisherSettings {

private static final UpdateChangesConstants C = new UpdateChangesConstants();

private static final Set<Event> mySupportedEvents = new HashSet<Event>() {{
if (TeamCityProperties.getBoolean(PUBLISH_QUEUED_BUILD_STATUS)) {
add(Event.QUEUED);
Expand Down Expand Up @@ -65,7 +72,7 @@ public CommitStatusPublisher createPublisher(@NotNull SBuildType buildType, @Not

@NotNull
public String describeParameters(@NotNull Map<String, String> params) {
String url = params.get(Constants.STASH_BASE_URL);
String url = params.get(C.getStashBaseUrl());
return super.describeParameters(params) + (url != null ? ": " + WebUtil.escapeXml(url) : "");
}

Expand All @@ -74,8 +81,8 @@ public PropertiesProcessor getParametersProcessor() {
return new PropertiesProcessor() {
public Collection<InvalidProperty> process(Map<String, String> params) {
List<InvalidProperty> errors = new ArrayList<InvalidProperty>();
if (params.get(Constants.STASH_BASE_URL) == null)
errors.add(new InvalidProperty(Constants.STASH_BASE_URL, "Server URL must be specified"));
if (params.get(C.getStashBaseUrl()) == null)
errors.add(new InvalidProperty(C.getStashBaseUrl(), "Server URL must be specified"));
return errors;
}
};
Expand All @@ -97,7 +104,7 @@ public void testConnection(@NotNull BuildTypeIdentity buildTypeOrTemplate, @NotN
Repository repository = GitRepositoryParser.parseRepository(vcsRootUrl);
if (null == repository)
throw new PublisherException("Cannot parse repository URL from VCS root " + root.getName());
String apiUrl = params.get(Constants.STASH_BASE_URL);
String apiUrl = params.get(C.getStashBaseUrl());
if (null == apiUrl || apiUrl.length() == 0)
throw new PublisherException("Missing Bitbucket Server API URL parameter");
String url = apiUrl + "/rest/api/1.0/projects/" + repository.owner() + "/repos/" + repository.repositoryName();
Expand Down Expand Up @@ -127,15 +134,27 @@ public void processResponse(HttpResponse response) throws HttpPublisherException
String pluralS = "";
if (repoInfo.errors.size() > 1)
pluralS = "s";
throw new HttpPublisherException(String.format("Bitbucket Server publisher error%s:%s", pluralS, sb.toString()));
throw new HttpPublisherException(format("Bitbucket Server publisher error%s:%s", pluralS, sb.toString()));
}
}
};

HttpHelper.get(url, params.get(Constants.STASH_USERNAME), params.get(Constants.STASH_PASSWORD),
Collections.singletonMap("Accept", "application/json"), BaseCommitStatusPublisher.DEFAULT_CONNECTION_TIMEOUT, processor);
switch (StashAuthenticationType.parse(params.get(C.getAuthenticationTypeKey()))) {
case PASSWORD_AUTH:
HttpHelper.get(url, params.get(C.getStashUsername()), params.get(C.getStashPassword()), of("Accept", APPLICATION_JSON.getMimeType()),
BaseCommitStatusPublisher.DEFAULT_CONNECTION_TIMEOUT, processor);
break;

case TOKEN_AUTH:
HttpHelper.get(url, "", "", of("Accept", APPLICATION_JSON.getMimeType(), "Authorization", on(' ').join("Bearer", params.get(C.getStashToken()))),
BaseCommitStatusPublisher.DEFAULT_CONNECTION_TIMEOUT, processor);
break;

default:
throw new IllegalArgumentException("Failed to parse authentication type.");
}
} catch (Exception ex) {
throw new PublisherException(String.format("Bitbucket Server publisher has failed to connect to %s/%s repository", repository.owner(), repository.repositoryName()), ex);
throw new PublisherException(format("Bitbucket Server publisher has failed to connect to %s/%s repository", repository.owner(), repository.repositoryName()), ex);
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package jetbrains.buildServer.commitPublisher.stash.ui;

import jetbrains.buildServer.commitPublisher.Constants;
import jetbrains.buildServer.commitPublisher.stash.StashAuthenticationType;

public class UpdateChangesConstants {
public String getStashBaseUrl() { return Constants.STASH_BASE_URL; }
public String getStashUsername() { return Constants.STASH_USERNAME; }
public String getStashPassword() { return Constants.STASH_PASSWORD; }
public String getStashToken() { return Constants.STASH_TOKEN; }
public String getAuthenticationTypeKey() { return Constants.STASH_AUTH_TYPE;}
public String getAuthenticationTypePasswordValue() { return StashAuthenticationType.PASSWORD_AUTH.getValue();}
public String getAuthenticationTypeTokenValue() { return StashAuthenticationType.TOKEN_AUTH.getValue();}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
<%@ taglib prefix="forms" tagdir="/WEB-INF/tags/forms" %>
<%@ taglib prefix="bs" tagdir="/WEB-INF/tags" %>
<%@ taglib prefix="util" uri="/WEB-INF/functions/util" %>
<jsp:useBean id="keys" class="jetbrains.buildServer.commitPublisher.Constants"/>
<jsp:useBean id="keys" class="jetbrains.buildServer.commitPublisher.stash.ui.UpdateChangesConstants"/>

<tr>
<th><label for="${keys.stashBaseUrl}">Bitbucket Server Base URL: <l:star/></label></th>
<td>
Expand All @@ -14,24 +15,41 @@
</td>
</tr>

<tr>
<th><label for="${keys.stashUsername}">Username: <l:star/></label></th>
<td>
<props:textProperty name="${keys.stashUsername}" className="mediumField"/>
</td>
</tr>
<props:selectSectionProperty name="${keys.authenticationTypeKey}" title="Authentication Type">

<props:selectSectionPropertyContent value="${keys.authenticationTypeTokenValue}" caption="Access Token">
<tr>
<th><label for="${keys.stashToken}">Token: <l:star/></label></th>
<td>
<props:passwordProperty name="${keys.stashToken}" className="mediumField"/>
</td>
</tr>
</props:selectSectionPropertyContent>

<props:selectSectionPropertyContent value="${keys.authenticationTypePasswordValue}" caption="Password">
<tr>
<th><label for="${keys.stashUsername}">Username: <l:star/></label></th>
<td>
<props:textProperty name="${keys.stashUsername}" className="mediumField"/>
</td>
</tr>

<tr>
<th><label for="${keys.stashPassword}">Password: <l:star/></label></th>
<td>
<props:passwordProperty name="${keys.stashPassword}" className="mediumField"/>
</td>
</tr>
</props:selectSectionPropertyContent>

</props:selectSectionProperty>

<c:if test="${testConnectionSupported}">
<script>
$j(document).ready(function() {
PublisherFeature.showTestConnection("This ensures that the repository is reachable under the provided credentials.\nIf status publishing still fails, it can be due to insufficient permissions of the corresponding BitBucket Server user.");
});
</script>
</c:if>

<tr>
<th><label for="${keys.stashPassword}">Password: <l:star/></label></th>
<td>
<props:passwordProperty name="${keys.stashPassword}" className="mediumField"/>
<c:if test="${testConnectionSupported}">
<script>
$j(document).ready(function() {
PublisherFeature.showTestConnection("This ensures that the repository is reachable under the provided credentials.\nIf status publishing still fails, it can be due to insufficient permissions of the corresponding BitBucket Server user.");
});
</script>
</c:if>
</td>
</tr>

Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ public void test_buildFinishedSuccessfully_server_url_with_slash() throws Except
@Override
protected Map<String, String> getPublisherParams() {
return new HashMap<String, String>() {{
put(Constants.STASH_TOKEN, "token");
put(Constants.STASH_USERNAME, "user");
put(Constants.STASH_PASSWORD, "pwd");
put(Constants.STASH_BASE_URL, getServerUrl());
Expand Down