Skip to content

Commit

Permalink
Merge pull request #13 from Hillelmed/feature/optionalcrubm
Browse files Browse the repository at this point in the history
Feature/optionalcrubm
  • Loading branch information
Hillelmed authored Apr 9, 2024
2 parents 5b3ccfe + b7b9574 commit 012539c
Show file tree
Hide file tree
Showing 18 changed files with 561 additions and 35 deletions.
4 changes: 4 additions & 0 deletions .github/workflows/github-actions.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,12 @@ jobs:
- run: echo "🎉 The job was automatically triggered by a ${{ github.event_name }} event."
- run: echo "🐧 This job is now running on a ${{ runner.os }} server hosted by GitHub!"
- run: echo "🔎 The name of your branch is ${{ github.ref }} and your repository is ${{ github.repository }}."
- run: echo "🔎 The name of your branch is ${{ github.head_ref || github.ref }} and your repository is ${{ github.repository }}."

- name: Checkout sources
uses: actions/checkout@v4
with:
ref: ${{ github.head_ref || github.ref }}

- name: Set up JDK 17
uses: actions/setup-java@v4
Expand Down
21 changes: 18 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@

[![Maven Central](https://maven-badges.herokuapp.com/maven-central/io.github.hillelmed/jenkins-client-java/badge.png)](https://maven-badges.herokuapp.com/maven-central/io.github.hillelmed/jenkins-client-java)
[![Stack Overflow](https://img.shields.io/badge/stack%20overflow-jenkins–rest-4183C4.svg)](https://stackoverflow.com/questions/tagged/jenkins+client+java)

# jenkins-client-java

Java client is built on the top of Http interface and webClient for working with Jenkins REST API Support Java 17 and Java 21.
Expand All @@ -18,7 +17,23 @@ JenkinsClient jenkinsClient = JenkinsClient.create(jenkinsProperties);
SystemInfo systemInfo = client.api().systemApi().systemInfo();
assertTrue(systemInfo.getUrl().equals("http://localhost:8080/"));
```

If Disabling CSRF Protection in your jenkins

``hudson.security.csrf.GlobalCrumbIssuerConfiguration.DISABLE_CSRF_PROTECTION=true``

Config your properties like that:
```
JenkinsProperties jenkinsProperties = JenkinsProperties.builder().url("http://localhost:8080")
.jenkinsAuthentication(JenkinsAuthentication.builder().crumbEnabled(false).authType(AuthenticationType.USERNAME_PASSWORD)
.credentials("admin:password").build()).build();
JenkinsClient jenkinsClient = JenkinsClient.create(jenkinsProperties);
SystemInfo systemInfo = client.api().systemApi().systemInfo();
assertTrue(systemInfo.getUrl().equals("http://localhost:8080/"));
```


## Latest release

Can be found in maven like so:
Expand Down Expand Up @@ -91,7 +106,7 @@ For more details, see
## Understanding Error objects

When something pops server-side `Jenkins` will hand us back a list of [JenkinsError](https://github.com/Hillelmed/jenkins-client-java/blob/main/src/main/java/io/github/hillelmed/jenkins/client/exception/JenkinsError.java) objects. we're throwing an exception at runtime we attach this List of `Error` objects
The throwing object is Jenkins [JenkinsAppException.java](https://github.com/Hillelmed/jenkins-client-java/blob/main/src/main/java/io/github/hillelmed/jenkins/client/exception/BitbucketAppException.java)
The throwing object is Jenkins [JenkinsAppException.java](https://github.com/Hillelmed/jenkins-client-java/blob/main/src/main/java/io/github/hillelmed/jenkins/client/exception/JenkinsAppException.java)
to most [domain](https://github.com/Hillelmed/jenkins-client-java/tree/main/src/main/java/io/github/hillelmed/jenkins/client/domain) objects. Thus, it is up to the user to check the handed back domain object to see if the attached List is empty, and if not, iterate over the `Error` objects to see if it's something
truly warranting an exception. List of `Error` objects itself will always be non-null but in most cases empty (unless something has failed).

Expand Down
4 changes: 4 additions & 0 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
### Version 1.0.1 start from beginning (April 9, 2024)
* ADDED: options for work with jenkins without CRUMB CSRF protections
* ADDED: JobList API to support Tree and Depth. - [Pull Request 311](https://github.com/cdancy/jenkins-rest/pull/311)

### Version 1.0.0 start from beginning (April 1, 2024) With hillelmed
* Update to java 17 and change to use Http interface with webClient with Spring

Expand Down
17 changes: 15 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -106,14 +106,14 @@ javadoc {
}

group = 'io.github.hillelmed'
version = '1.0.0'
version = '1.0.1'
description = 'Java client for working with Jenkins REST API'

publishing {
publications {
maven(MavenPublication) {
group = 'io.github.hillelmed'
version = '1.0.0'
version = '1.0.1'
description = 'Java client for working with Jenkins REST API'

from components.java
Expand Down Expand Up @@ -141,6 +141,19 @@ publishing {
url = 'https://github.com/Hillelmed/jenkins-client-java/'
}
}

}
}
repositories {
maven {
name = "GitHubPackages"
url = "https://maven.pkg.github.com/hillelmed/jenkins-client-java"
credentials {
username = System.getenv("GITHUB_ACTOR")
password = System.getenv("GITHUB_TOKEN")
}
}

}

}
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
group=io.github.hillelmed
version=1.0.0
version=1.0.1

myProjectLicense=MIT 2.0

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public class JenkinsApiClientImpl implements JenkinsApi {
private final Map<Class<?>, Object> singletons;

public JenkinsApiClientImpl(JenkinsProperties jenkinsProperties, WebClient webClient) {
this.httpServiceProxyFactory = buildHttpServiceProxyFactory(jenkinsProperties, webClient, NONE);
this.httpServiceProxyFactory = buildHttpServiceProxyFactory(jenkinsProperties, webClient, VALUES_ONLY);
this.singletons = Collections.synchronizedMap(new HashMap<>());
}

Expand All @@ -30,8 +30,9 @@ private static HttpServiceProxyFactory buildHttpServiceProxyFactory(JenkinsPrope
ExchangeFilterFunction scrubNullFromPathFilter = new ScrubNullFolderParam();
ExchangeFilterFunction jenkinsUserInjectionFilter = new JenkinsUserInjectionFilter(jenkinsProperties.getJenkinsAuthentication());
if (webClient == null) {
DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(jenkinsProperties.getUrl());
factory.setEncodingMode(encodingMode);
JenkinsUriTemplateHandler factory = new JenkinsUriTemplateHandler(jenkinsProperties.getUrl());
// factory.setParsePath(false);
// factory.setEncodingMode(encodingMode);

webClient = WebClient.builder()
.uriBuilderFactory(factory)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ public class JenkinsAuthentication extends ExchangeFilterFunctions {
private final String identity;
private final String credential;
private final String encodedCred;
private final boolean crumbEnabled;


public static class JenkinsAuthenticationBuilder {
Expand All @@ -29,6 +30,7 @@ public static class JenkinsAuthenticationBuilder {
private String credential = identity + ":";
private String encodedCred = Base64.getEncoder().encodeToString(credential.getBytes(StandardCharsets.UTF_8));
private AuthenticationType authType = AuthenticationType.ANONYMOUS;
private boolean crumbEnabled = true;

/**
* Set 'UsernamePassword' credentials.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package io.github.hillelmed.jenkins.client.domain.job;

import com.fasterxml.jackson.annotation.*;
import lombok.*;

import java.util.*;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class JobListTree {

@JsonProperty("_class")
private String clazz;

private String name;

private String fullName;

private List<JobListTree> jobs;

private String color;

private String url;

}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
*/
public class CrumbMissing extends JenkinsAppException {

public CrumbMissing(String responseBody, List<JenkinsError> errors, HttpStatusCode httpStatusCode) {
super(responseBody, errors, httpStatusCode);
public CrumbMissing(String responseBody, List<JenkinsError> errors, HttpStatusCode httpStatusCode, Throwable e) {
super(responseBody, errors, httpStatusCode, e);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,15 @@ public interface JobsApi {
@GetExchange("/api/json")
ResponseEntity<JobList> jobList();

// @Named("jobs:job-info")
// @Fallback(Fallbacks.NullOnNotFoundOr404.class)
// @Consumes(MediaType.APPLICATION_JSON)
@GetExchange(value = "api/json", accept = MediaType.APPLICATION_JSON_VALUE)
ResponseEntity<JobListTree> jobList(@RequestParam(value = "depth", required = false) Integer depth,
@RequestParam(value = "tree",required = false) String tree);


@GetExchange(value = "/job/{folderPath}/api/json", accept = MediaType.APPLICATION_JSON_VALUE)
ResponseEntity<JobListTree> jobList(@PathVariable String folderPath, @RequestParam(value = "depth", required = false) Integer depth,
@RequestParam(value = "tree", required = false) String tree);

@GetExchange("/job/{optionalFolderPath}/job/{name}/api/json")
ResponseEntity<JobInfo> jobInfo(@PathVariable("optionalFolderPath") String optionalFolderPath,
@PathVariable("name") String jobName);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
import lombok.*;
import org.springframework.http.*;
import org.springframework.web.reactive.function.client.*;
import reactor.core.publisher.*;

import java.util.*;

@RequiredArgsConstructor
public class JenkinsAuthenticationFilter implements ExchangeFilterFunction {
Expand All @@ -30,7 +33,8 @@ public reactor.core.publisher.Mono<ClientResponse> filter(ClientRequest request,

//Anon and Password need the crumb and the cookie when POSTing
//https://www.jenkins.io/doc/book/security/csrf-protection/#working-with-scripted-clients
if (request.method().equals(HttpMethod.POST) && (jenkinsAuthentication.getAuthType() == AuthenticationType.USERNAME_PASSWORD
if (jenkinsAuthentication.isCrumbEnabled() && request.method().equals(HttpMethod.POST)
&& (jenkinsAuthentication.getAuthType() == AuthenticationType.USERNAME_PASSWORD
|| jenkinsAuthentication.getAuthType() == AuthenticationType.ANONYMOUS)) {
try {
final Crumb localCrumb = getCrumb();
Expand All @@ -39,7 +43,7 @@ public reactor.core.publisher.Mono<ClientResponse> filter(ClientRequest request,
builder.cookie(crumbCookie.getName(), crumbCookie.getValue());
}
} catch (JenkinsAppException e) {
throw new CrumbMissing(e.getMessage(), e.errors(), e.code());
throw new CrumbMissing(e.getMessage(), e.errors(), e.code(), e);
}
}
return next.exchange(builder.build());
Expand All @@ -52,13 +56,17 @@ private Crumb getCrumb() {
.header(HttpHeaders.AUTHORIZATION,
jenkinsAuthentication.getAuthType().getAuthScheme() + " " + jenkinsAuthentication.getEncodedCred())
.exchangeToMono(clientResponse -> {
clientResponse.cookies()
.forEach((s, responseCookies) -> {
if (responseCookies.get(0).getName().contains(JenkinsConstants.JENKINS_COOKIES_JSESSIONID)) {
crumbCookie = new HttpCookie(s, responseCookies.get(0).getValue());
}
});
return clientResponse.bodyToMono(Crumb.class);
if (clientResponse.statusCode().is2xxSuccessful()) {
clientResponse.cookies()
.forEach((s, responseCookies) -> {
if (responseCookies.get(0).getName().contains(JenkinsConstants.JENKINS_COOKIES_JSESSIONID)) {
crumbCookie = new HttpCookie(s, responseCookies.get(0).getValue());
}
});
return clientResponse.bodyToMono(Crumb.class);
} else {
return reactor.core.publisher.Mono.error(new JenkinsAppException("Failed to retrieve crumb", Collections.emptyList(), clientResponse.statusCode()));
}
}).block();
}
return crumb;
Expand Down
Loading

0 comments on commit 012539c

Please sign in to comment.