Skip to content
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

JiraAPI Groovy Script conversion #3936

Merged
merged 15 commits into from
Jun 25, 2024
Merged
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
19 changes: 18 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.10</version>
<version>1.5.6</version>
</dependency>
<dependency>
<groupId>org.kocakosm</groupId>
Expand Down Expand Up @@ -219,6 +219,23 @@
<version>5.10.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.25.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.5.6</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;

import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;

Expand Down Expand Up @@ -32,7 +33,7 @@ public static class SecurityContacts {
*/
public static class IssueTracker {
public interface JiraComponentSource {
String getComponentId(String componentName);
String getComponentId(String componentName) throws IOException;
}

private static final Logger LOGGER = Logger.getLogger(IssueTracker.class.getName());
Expand Down Expand Up @@ -65,7 +66,7 @@ public boolean isGitHubIssues() {

@SuppressFBWarnings(value = "NP_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD",
justification = "All calls are guarded by jira null check in isJira()")
private String loadComponentId(JiraComponentSource source) {
private String loadComponentId(JiraComponentSource source) throws IOException {
String jiraComponentId = jira;
if (!jira.matches("[0-9]+")) {
// CreateIssueDetails needs the numeric Jira component ID
Expand All @@ -78,7 +79,7 @@ private String loadComponentId(JiraComponentSource source) {
return jiraComponentId;
}

public String getViewUrl(JiraComponentSource source) {
public String getViewUrl(JiraComponentSource source) throws IOException {
if (isJira()) {
final String id = loadComponentId(source);
if (id != null) {
Expand All @@ -92,7 +93,7 @@ public String getViewUrl(JiraComponentSource source) {
throw new IllegalStateException("Invalid issue tracker: " + github + " / " + jira);
}

public String getReportUrl(JiraComponentSource source) {
public String getReportUrl(JiraComponentSource source) throws IOException {
if (!report) {
return null;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package io.jenkins.infra.repository_permissions_updater;

import java.io.IOException;

public abstract class JiraAPI implements Definition.IssueTracker.JiraComponentSource {

static JiraAPI INSTANCE = null;

public abstract String getComponentId(String componentName) throws IOException;

abstract boolean isUserPresent(String username);

/* Singleton support */
public static synchronized JiraAPI getInstance() {
if (INSTANCE == null) {
INSTANCE = new JiraImpl(System.getProperty("jiraUrl", "https://issues.jenkins.io"));
}
return INSTANCE;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
package io.jenkins.infra.repository_permissions_updater;

import com.google.gson.Gson;
import com.google.gson.JsonArray;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.ProtocolException;
import java.net.URI;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

class JiraImpl extends JiraAPI {
private static final Logger LOGGER = LoggerFactory.getLogger(JiraImpl.class);
private static final Gson gson = new Gson();
private static final Pattern USERNAME_REGEX = Pattern.compile("[a-zA-Z0-9_]+");
private static final String JIRA_USERNAME = System.getenv("JIRA_USERNAME");
private static final String JIRA_PASSWORD = System.getenv("JIRA_PASSWORD");
private static final String JIRA_BASIC_AUTH_VALUE = Base64.getEncoder().encodeToString((JIRA_USERNAME + ":" + JIRA_PASSWORD).getBytes(StandardCharsets.UTF_8));
private static final String JIRA_BASIC_AUTH_HEADER = "Basic %s";

/**
* URL to Jira components API
*/
private final String JIRA_COMPONENTS_URL;
private final String JIRA_USE_URL;
/**
* URL to Jira user API
*/
private final String JIRA_USER_QUERY;
private Map<String, String> componentNamesToIds;
private final Map<String, Boolean> userMapping = new HashMap<>();


JiraImpl(String jiraUrl) {
JIRA_COMPONENTS_URL = jiraUrl + "/rest/api/2/project/JENKINS/components";
JIRA_USE_URL = jiraUrl + "/rest/api/2/user";
JIRA_USER_QUERY = JIRA_USE_URL + "?username=%s";
}

@SuppressFBWarnings({"SE_NO_SERIALVERSIONID", "URLCONNECTION_SSRF_FD"})
private void ensureDataLoaded() throws IOException {
if (componentNamesToIds == null) {
componentNamesToIds = new HashMap<>();

LOGGER.info("Retrieving components from Jira...");
final URL url;
try {
url = URI.create(JIRA_COMPONENTS_URL).toURL();
} catch (final MalformedURLException e) {
throw new IOException("Failed to construct Jira URL", e);
}
final HttpURLConnection conn;
try {
conn = (HttpURLConnection) url.openConnection();
} catch (final IOException e) {
throw new IOException("Failed to open connection for Jira URL", e);
}

try {
conn.setRequestMethod("GET");
} catch (final ProtocolException e) {
throw new IOException("Failed to set request method", e);
}
try {
conn.connect();
} catch (IOException e) {
throw new IOException("Failed to connect to Jira URL", e);
}
try (final BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8))) {
final String text = bufferedReader.lines().collect(Collectors.joining());
final JsonArray jsonElements = gson.fromJson(text, JsonArray.class);
jsonElements.forEach(jsonElement -> {
final var object = jsonElement.getAsJsonObject();
final var id = object.get("id").getAsString();
final var name = object.get("name").getAsString();
LOGGER.trace("Identified Jira component with ID {} and name {}", id, name);
componentNamesToIds.put(name, id);
});
} catch (final IOException e) {
throw new IOException("Failed to parse Jira response", e);
}
}
}
@Override
public String getComponentId(final String componentName) throws IOException {
ensureDataLoaded();
return componentNamesToIds.get(componentName);
}

@Override
boolean isUserPresent(final String username) {
return userMapping.computeIfAbsent(username, this::isUserPresentInternal);
}
@SuppressFBWarnings({"URLCONNECTION_SSRF_FD"})
private boolean isUserPresentInternal(final String username) throws RuntimeException {
if (!USERNAME_REGEX.matcher(username).matches()) {
throw new RuntimeException(String.format("Rejecting user name for Jira lookup: %s", username));
}

LOGGER.info("Checking whether user exists in Jira: {}", username);

final URL url;
try {
url = URI.create(String.format(JIRA_USER_QUERY, username)).toURL();
} catch (final MalformedURLException e) {
throw new RuntimeException("Failed to construct Jira URL", e);
}
final HttpURLConnection conn;
try {
conn = (HttpURLConnection) url.openConnection();
} catch (final IOException e) {
throw new RuntimeException("Failed to open connection for Jira URL", e);
}

try {
conn.setRequestMethod("GET");
} catch (final ProtocolException e) {
throw new RuntimeException("Failed to set request method", e);
}
conn.setRequestProperty("Authorization", String.format(JIRA_BASIC_AUTH_HEADER, JIRA_BASIC_AUTH_VALUE));
try {
conn.connect();
} catch (final IOException e) {
throw new RuntimeException("Failed to connect to Jira URL", e);
}

final int code;
try {
code = conn.getResponseCode();
} catch (final IOException e) {
return false;
}
return code == 200;
}
}
Loading