Skip to content

Added bearer token support #159

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

Merged
merged 2 commits into from
Oct 9, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* Copyright (C) 2022 Atlassian
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.jenkinsci.plugins.JiraTestResultReporter;

import com.atlassian.httpclient.api.Request;
import com.atlassian.jira.rest.client.api.AuthenticationHandler;

/**
* Handler for Bearer (Token) authentication
*/
public class BearerAuthenticationHandler implements AuthenticationHandler {

private static final String AUTHORIZATION_HEADER = "Authorization";

private final String token;

Check warning

Code scanning / Jenkins Security Scan

Jenkins: Plaintext password storage Warning

Field should be reviewed whether it stores a password and is serialized to disk: token
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Token here is transferred to jira server. The input therefore comes from the password field which is stored to disk.


public BearerAuthenticationHandler(String token) {
this.token = token;
}

@Override
public void configure(Request.Builder builder) {
builder.setHeader(AUTHORIZATION_HEADER, "Bearer " + token);
}

Check warning on line 38 in src/main/java/org/jenkinsci/plugins/JiraTestResultReporter/BearerAuthenticationHandler.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 31-38 are not covered by tests
}
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,10 @@
*/
public class JiraTestDataPublisher extends TestDataPublisher {

public static final boolean DEBUG = false;
public static final boolean DEBUG = false;

/** Attachments obtained from junit-attachments plugin indexed by className and test method name **/
private Map<String, Map<String, List<String>>> attachments = new HashMap<>();
private Map<String, Map<String, List<String>>> attachments = new HashMap<>();

/**
* Getter for the configured fields
Expand Down Expand Up @@ -110,7 +110,7 @@
public boolean getAutoUnlinkIssue() {
return JobConfigMapping.getInstance().getAutoUnlinkIssue(getJobName());
}

/**
* Getter for list of attachments by test method identified by its classname and name
* @param className
Expand All @@ -122,12 +122,12 @@
return Collections.emptyList();
}
Map<String, List<String>> attachmentsByClassname = this.attachments.get(className);
if (!attachmentsByClassname.containsKey(name)) {
if (!attachmentsByClassname.containsKey(name)) {
return Collections.emptyList();
}
return attachmentsByClassname.get(name);
}

/**
* Getter for the project associated with this publisher
* @return
Expand All @@ -146,12 +146,12 @@
private JobConfigMapping.JobConfigEntry getJobConfig() {
return jobConfig;
}

@CheckForNull
public boolean getAdditionalAttachments() {
return jobConfig.getAdditionalAttachments();
}

@DataBoundSetter
public void setAdditionalAttachments(boolean additionalAttachments) {
JiraUtils.log(String.format("Additional attachments field configured as %s", additionalAttachments));
Expand All @@ -166,7 +166,7 @@
.withConfigs(this.jobConfig.getConfigs())
.build();
}


/**
* Constructor
Expand All @@ -178,8 +178,8 @@
* @param autoUnlinkIssue
* @param overrideResolvedIssues
*/
@DataBoundConstructor
public JiraTestDataPublisher(List<AbstractFields> configs, String projectKey, String issueType,
@DataBoundConstructor
public JiraTestDataPublisher(List<AbstractFields> configs, String projectKey, String issueType,
boolean autoRaiseIssue, boolean autoResolveIssue, boolean autoUnlinkIssue, boolean overrideResolvedIssues) {

long defaultIssueType;
Expand Down Expand Up @@ -222,12 +222,12 @@
* @throws IOException
* @throws InterruptedException
*/
@Override
public TestResultAction.Data contributeTestData(Run<?, ?> run, @Nonnull FilePath workspace, Launcher launcher,
@Override
public TestResultAction.Data contributeTestData(Run<?, ?> run, @Nonnull FilePath workspace, Launcher launcher,
TaskListener listener, TestResult testResult)
throws IOException, InterruptedException {
EnvVars envVars = run.getEnvironment(listener);

EnvVars envVars = run.getEnvironment(listener);
Job job = run.getParent();
Job project;
if (job instanceof MatrixConfiguration) {
Expand Down Expand Up @@ -316,7 +316,7 @@
break;
}
}

if (!transitionExecuted) {
listener.getLogger().println("Could not find transition to resolve issue " + issueKey);
}
Expand Down Expand Up @@ -386,7 +386,7 @@
* Getter for the Descriptor
* @return singleton instance of the Descriptor
*/
@Override
@Override
public JiraTestDataPublisherDescriptor getDescriptor() {
return (JiraTestDataPublisherDescriptor) Jenkins.getInstance().getDescriptorOrDie(getClass());
}
Expand All @@ -400,8 +400,8 @@
}

@Symbol("jiraTestResultReporter")
@Extension
public static class JiraTestDataPublisherDescriptor extends Descriptor<TestDataPublisher> {
@Extension
public static class JiraTestDataPublisherDescriptor extends Descriptor<TestDataPublisher> {
/**
* Constructor
* loads the serialized descriptor from the previous run
Expand Down Expand Up @@ -430,88 +430,101 @@
private transient JiraRestClient restClient;
private transient JiraRestClientExtension restClientExtension;
private transient MetadataCache metadataCache = new MetadataCache();
private URI jiraUri = null;
private String username = null;
private Secret password = null;
private URI jiraUri = null;
private String username = null;
private Secret password = null;
private boolean useBearerAuth = false;
private String defaultSummary;
private String defaultDescription;


public URI getJiraUri() { return jiraUri; }
public String getUsername() { return username; }
public Secret getPassword() { return password; }
public boolean getUseBearerAuth() { return useBearerAuth; }
public String getJiraUrl() { return jiraUri != null ? jiraUri.toString() : null; }
public JiraRestClient getRestClient() { return restClient; }

/**
* Getter for the summary template
* @return
*/
public String getDefaultSummary() {
return defaultSummary != null && !defaultSummary.equals("") ? defaultSummary : DEFAULT_SUMMARY;
}

/**
* Getter for the description template
* @return
*/
public String getDefaultDescription() {
return defaultDescription != null && !defaultDescription.equals("") ? defaultDescription : DEFAULT_DESCRIPTION;
}

/**
* Getter for the statuses map, contains information about status category of each status
* @return
*/
public HashMap<String, FullStatus> getStatusesMap() {
return statuses;
}

/**
* Getter for the cache entry
* @param projectKey
* @param issueType
* @return a metadata cache entry
*/
public MetadataCache.CacheEntry getCacheEntry(String projectKey, String issueType) {
return metadataCache.getCacheEntry(projectKey, issueType);
}

/**
* Method for resolving transient objects after deserialization. Called by the JVM.
* See Java documentation for more details.
* @return this object
*/
public Object readResolve() {
if(jiraUri != null && username != null && password != null) {
AsynchronousJiraRestClientFactory factory = new AsynchronousJiraRestClientFactory();
restClient = factory.createWithBasicHttpAuthentication(jiraUri, username, password.getPlainText());
restClientExtension = new JiraRestClientExtension(jiraUri,
new AsynchronousHttpClientFactory().createClient(jiraUri, new BasicHttpAuthenticationHandler(username, password.getPlainText())));
if(useBearerAuth)
{
BearerAuthenticationHandler handler = new BearerAuthenticationHandler(password.getPlainText());
restClient= factory.create(jiraUri, handler);

restClientExtension = new JiraRestClientExtension(jiraUri,
new AsynchronousHttpClientFactory().createClient(jiraUri, new BearerAuthenticationHandler(password.getPlainText())));
}
else
{
restClient = factory.createWithBasicHttpAuthentication(jiraUri, username, password.getPlainText());
restClientExtension = new JiraRestClientExtension(jiraUri,
new AsynchronousHttpClientFactory().createClient(jiraUri, new BasicHttpAuthenticationHandler(username, password.getPlainText())));
}
tryCreatingStatusToCategoryMap();
}
return this;
}

/**
* Getter for the display name
* @return

Check warning on line 511 in src/main/java/org/jenkinsci/plugins/JiraTestResultReporter/JiraTestDataPublisher.java

View check run for this annotation

ci.jenkins.io / JavaDoc

JavaDoc @return

NORMAL: no description for @return
*/
@Override
public String getDisplayName() {
return "JiraTestResultReporter";
}
@Override
public String getDisplayName() {
return "JiraTestResultReporter";
}

/**
* Method for obtaining the global configurations (global.jelly), when save/apply is clicked
* @param req current request
* @param json form in json format
* @return

Check warning on line 522 in src/main/java/org/jenkinsci/plugins/JiraTestResultReporter/JiraTestDataPublisher.java

View check run for this annotation

ci.jenkins.io / JavaDoc

JavaDoc @return

NORMAL: no description for @return
* @throws FormException

Check warning on line 523 in src/main/java/org/jenkinsci/plugins/JiraTestResultReporter/JiraTestDataPublisher.java

View check run for this annotation

ci.jenkins.io / JavaDoc

JavaDoc @throws

NORMAL: no description for @throws
*/
@Override
public boolean configure(StaplerRequest req, JSONObject json)
throws FormException {
@Override
public boolean configure(StaplerRequest req, JSONObject json)
throws FormException {

try {
jiraUri = new URI(json.getString("jiraUrl"));
Expand All @@ -521,27 +534,40 @@


username = json.getString("username");
password = Secret.fromString(json.getString("password"));
password = Secret.fromString(json.getString("password"));
useBearerAuth = json.getBoolean("useBearerAuth");
defaultSummary = json.getString("summary");
defaultDescription = json.getString("description");

if (json.getString("jiraUrl").equals("")
|| json.getString("username").equals("")
|| json.getString("password").equals("")) {
useBearerAuth=false;
restClient = null;
restClientExtension = null;
save();
return true;
}

AsynchronousJiraRestClientFactory factory = new AsynchronousJiraRestClientFactory();
restClient = factory.createWithBasicHttpAuthentication(jiraUri, username, password.getPlainText());
restClientExtension = new JiraRestClientExtension(jiraUri,
new AsynchronousHttpClientFactory().createClient(jiraUri, new BasicHttpAuthenticationHandler(username, password.getPlainText())));
if(useBearerAuth)
{
BearerAuthenticationHandler handler = new BearerAuthenticationHandler(password.getPlainText());
restClient= factory.create(jiraUri, handler);

restClientExtension = new JiraRestClientExtension(jiraUri,
new AsynchronousHttpClientFactory().createClient(jiraUri, new BearerAuthenticationHandler(password.getPlainText())));
}
else
{
restClient = factory.createWithBasicHttpAuthentication(jiraUri, username, password.getPlainText());
restClientExtension = new JiraRestClientExtension(jiraUri,
new AsynchronousHttpClientFactory().createClient(jiraUri, new BasicHttpAuthenticationHandler(username, password.getPlainText())));
}
tryCreatingStatusToCategoryMap();
save();
save();
return super.configure(req, json);
}
}

/**
* method for creating the status category map, if the Jira server knows about categories
Expand Down Expand Up @@ -580,15 +606,17 @@

/**
* Validation for the global configuration, called when Validate Settings is clicked (global.jelly)
* @param jiraUrl

Check warning on line 609 in src/main/java/org/jenkinsci/plugins/JiraTestResultReporter/JiraTestDataPublisher.java

View check run for this annotation

ci.jenkins.io / JavaDoc

JavaDoc @param

NORMAL: no description for @param
* @param username

Check warning on line 610 in src/main/java/org/jenkinsci/plugins/JiraTestResultReporter/JiraTestDataPublisher.java

View check run for this annotation

ci.jenkins.io / JavaDoc

JavaDoc @param

NORMAL: no description for @param
* @param password

Check warning on line 611 in src/main/java/org/jenkinsci/plugins/JiraTestResultReporter/JiraTestDataPublisher.java

View check run for this annotation

ci.jenkins.io / JavaDoc

JavaDoc @param

NORMAL: no description for @param
* @param useBearerAuth

Check warning on line 612 in src/main/java/org/jenkinsci/plugins/JiraTestResultReporter/JiraTestDataPublisher.java

View check run for this annotation

ci.jenkins.io / JavaDoc

JavaDoc @param

NORMAL: no description for @param
* @return

Check warning on line 613 in src/main/java/org/jenkinsci/plugins/JiraTestResultReporter/JiraTestDataPublisher.java

View check run for this annotation

ci.jenkins.io / JavaDoc

JavaDoc @return

NORMAL: no description for @return
*/
@RequirePOST
public FormValidation doValidateGlobal(@QueryParameter String jiraUrl,
@QueryParameter String username,
@QueryParameter String password
@QueryParameter String password,
@QueryParameter boolean useBearerAuth
) {

Jenkins.get().checkPermission(Jenkins.ADMINISTER);
Expand All @@ -602,7 +630,17 @@
// JIRA does not offer ways to validate username and password, so we try to query some server
// metadata, to see if the configured user is authorized on this server
AsynchronousJiraRestClientFactory factory = new AsynchronousJiraRestClientFactory();
JiraRestClient restClient = factory.createWithBasicHttpAuthentication(uri, username, pass.getPlainText());
JiraRestClient restClient;
if(useBearerAuth)
{
BearerAuthenticationHandler handler = new BearerAuthenticationHandler(pass.getPlainText());
restClient= factory.create(uri, handler);
}
else
{
restClient = factory.createWithBasicHttpAuthentication(uri, username, pass.getPlainText());

Check warning on line 641 in src/main/java/org/jenkinsci/plugins/JiraTestResultReporter/JiraTestDataPublisher.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 444-641 are not covered by tests
}

MetadataRestClient client = restClient.getMetadataClient();
Promise<ServerInfo> serverInfoPromise = client.getServerInfo();
ServerInfo serverInfo = serverInfoPromise.claim();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,13 @@
<f:entry title="Password">
<f:password field="password"/>
</f:entry>

<f:entry title="Use Bearer authentication instead of Basic authentication" field="useBearerAuth" >
<f:checkbox/>
</f:entry>

<f:validateButton title="Validate settings"
method="validateGlobal" with="jiraUrl,username,password" />
method="validateGlobal" with="jiraUrl,username,password,useBearerAuth" />
<f:advanced>
<f:entry title="Default Summary" field="summary">
<f:textbox field="summary" default="${descriptor.defaultSummary}"/>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<div>
Uses password data as input for Bearer token, PAT(personal access token) access to enterprise jira (https://confluence.atlassian.com/enterprise/using-personal-access-tokens-1026032365.html).
Bearer authentication is only supported in Jira Server, for Jira Cloud leave this unchecked
</div>