Skip to content

Commit 94d6d60

Browse files
committed
Fix: iimplementing support to 23.0.x
Signed-off-by: Kleber Rocha <[email protected]>
1 parent 86fe6b5 commit 94d6d60

File tree

10 files changed

+277
-50
lines changed

10 files changed

+277
-50
lines changed

.github/dependabot.yml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
version: 2
2+
updates:
3+
- package-ecosystem: "github-actions"
4+
directory: "/"
5+
schedule:
6+
interval: weekly
7+
day: friday
8+
- package-ecosystem: gradle
9+
directory: "/"
10+
schedule:
11+
interval: weekly
12+
day: friday
13+
open-pull-requests-limit: 99

.github/workflows/pr_workflow.yml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# This workflow will build a Java project with Gradle
2+
# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle
3+
4+
name: Testing For PRs
5+
6+
on: [ pull_request ]
7+
8+
jobs:
9+
test:
10+
runs-on: ubuntu-latest
11+
steps:
12+
- uses: actions/checkout@v3
13+
- name: Set up JDK
14+
uses: actions/setup-java@v3
15+
with:
16+
java-version: 17
17+
distribution: temurin
18+
- name: Build with Gradle
19+
run: ./gradlew assemble check

.github/workflows/release.yml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
2+
name: Create Stable Release
3+
4+
# Controls when the action will run. Workflow runs when manually triggered using the UI
5+
# or API.
6+
on:
7+
workflow_dispatch:
8+
# Inputs the workflow accepts.
9+
inputs:
10+
prerelease:
11+
description: 'The release should be an experimental release'
12+
default: 'NO'
13+
required: true
14+
15+
jobs:
16+
build_and_release:
17+
runs-on: ubuntu-latest
18+
steps:
19+
- uses: actions/checkout@v3
20+
with:
21+
fetch-depth: 0
22+
- name: Set up JDK
23+
uses: actions/setup-java@v3
24+
with:
25+
java-version: 17
26+
distribution: temurin
27+
- name: Release
28+
run: ./gradlew githubRelease

.github/workflows/test_and_build.yml

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# This workflow will build a Java project with Gradle
2+
# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle
3+
4+
name: Test and Build
5+
6+
on:
7+
push:
8+
branches: [ master ]
9+
10+
jobs:
11+
test:
12+
runs-on: ubuntu-latest
13+
steps:
14+
- uses: actions/checkout@v3
15+
- name: Set up JDK
16+
uses: actions/setup-java@v3
17+
with:
18+
java-version: 17
19+
distribution: temurin
20+
- name: Test with Gradle
21+
run: ./gradlew assemble check
22+
previewGithubRelease:
23+
needs: test
24+
runs-on: ubuntu-latest
25+
steps:
26+
- uses: actions/checkout@v3
27+
with:
28+
fetch-depth: 0
29+
- name: Set up JDK
30+
uses: actions/setup-java@v3
31+
with:
32+
java-version: 17
33+
distribution: temurin
34+
- name: Test with Gradle
35+
run: ./gradlew githubRelease

build.gradle

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ apply plugin: 'java'
1818

1919
apply from: 'plugin-common.gradle'
2020

21-
project.ext.pluginVersion = '1.0.3'
21+
project.ext.pluginVersion = '2.0.0'
2222
project.ext.fullVersion = project.distVersion ? "${project.pluginVersion}-${project.distVersion}" : project.pluginVersion
2323

2424
version = project.fullVersion
@@ -27,7 +27,7 @@ group = 'cd.go'
2727
project.ext.pluginDesc = [
2828
id : 'cd.go.authorization.keycloak',
2929
version : project.fullVersion,
30-
goCdVersion: '17.5.0',
30+
goCdVersion: '19.2.0',
3131
name : 'Keycloak oauth authorization plugin',
3232
description: 'Keycloak oauth authorization plugin for GoCD',
3333
vendorName : 'klinux',
@@ -39,18 +39,24 @@ repositories {
3939
mavenLocal()
4040
}
4141

42+
java {
43+
sourceCompatibility = JavaVersion.VERSION_11
44+
targetCompatibility = JavaVersion.VERSION_11
45+
}
46+
4247
dependencies {
43-
compileOnly group: 'cd.go.plugin', name: 'go-plugin-api', version: '17.5.0'
44-
compile group: 'com.google.code.gson', name: 'gson', version: '2.8.1'
45-
compile group: 'com.squareup.okhttp3', name: 'okhttp', version: '3.8.1'
46-
47-
testCompile group: 'cd.go.plugin', name: 'go-plugin-api', version: '17.5.0'
48-
testCompile group: 'junit', name: 'junit', version: '4.12'
49-
testCompile group: 'org.mockito', name: 'mockito-core', version: '2.2.28'
50-
testCompile group: 'org.hamcrest', name: 'hamcrest-library', version: '1.3'
51-
testCompile group: 'org.skyscreamer', name: 'jsonassert', version: '1.4.0'
52-
testCompile group: 'org.jsoup', name: 'jsoup', version: '1.10.2'
53-
testCompile 'com.squareup.okhttp3:mockwebserver:3.8.1'
48+
compileOnly group: 'cd.go.plugin', name: 'go-plugin-api', version: '19.2.0'
49+
implementation group: 'com.google.code.gson', name: 'gson', version: '2.8.1'
50+
implementation group: 'com.squareup.okhttp3', name: 'okhttp', version: '3.8.1'
51+
52+
testImplementation group: 'cd.go.plugin', name: 'go-plugin-api', version: '19.2.0'
53+
testImplementation group: 'org.mockito', name: 'mockito-core', version: '2.2.28'
54+
testImplementation group: 'org.hamcrest', name: 'hamcrest-library', version: '1.3'
55+
testImplementation group: 'org.skyscreamer', name: 'jsonassert', version: '1.4.0'
56+
testImplementation group: 'org.jsoup', name: 'jsoup', version: '1.10.2'
57+
testImplementation 'com.squareup.okhttp3:mockwebserver:3.8.1'
58+
59+
testImplementation group: 'junit', name: 'junit', version: '4.12'
5460
}
5561

5662
processResources {

docs/INSTALL.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ Provide details of the Keycloak server to connect to via an [Authorization Confi
2121
3. Click in **Clients** menu
2222
4. Click **Add** button
2323
5. On the form insert the client name
24-
6. On the next page, set this configs:
24+
6. On the next page, set these configs:
2525
1. In **Access Type** select **Confidential**
2626
2. In **Valid Redirect URIs** insert the URL of GoCD, ex.: **http://localhost:8153**
2727
3. In **Credentials** tab copy value of **Secret**
@@ -32,10 +32,10 @@ Provide details of the Keycloak server to connect to via an [Authorization Confi
3232
2. Select the realm that you want to configure. Ex. **Master**
3333
3. Click in **Groups** menu
3434
1. Click **Add Group** button
35-
2. Insert the name of **Group** and it description
35+
2. Insert the name of **Group** and the description
3636
3. Save the **Group**
3737
4. Select the **user** that you want to configure this role
3838
5. Select **Groups** tab and select the group in **Available Groups**
3939

4040
> Obs.: By default Keycloak do not provide group definition on user session, to get this, edit
41-
>**profile** scope and add groups in **Mappers** tab, thie scope needs to be added as builtin.
41+
>**profile** scope and add groups in **Mappers** tab, the scope needs to be added as builtin.

gocd/docker-compose.yaml

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,24 @@ version: '3'
22

33
services:
44
keycloak:
5-
image: jboss/keycloak:11.0.3
5+
image: quay.io/keycloak/keycloak:17.0.1-legacy
66
environment:
77
ROOT_LOGLEVEL: INFO
88
KEYCLOAK_USER: admin
99
KEYCLOAK_PASSWORD: admin
1010
DB_VENDOR: h2
1111
ports:
1212
- 8080:8080
13+
volumes:
14+
- data:/opt/jboss/keycloak/standalone/data
1315
gocd:
14-
image: gocd/gocd-server:v20.9.0
16+
image: gocd/gocd-server:v22.3.0
1517
volumes:
18+
- data:/godata
1619
- ./plugins:/godata/plugins
1720
ports:
1821
- 8153:8153
1922
- 8154:8154
23+
24+
volumes:
25+
data:

src/main/java/cd/go/authorization/keycloak/KeycloakApiClient.java

Lines changed: 90 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,13 @@
1818

1919
import cd.go.authorization.keycloak.models.KeycloakConfiguration;
2020
import cd.go.authorization.keycloak.models.TokenInfo;
21+
import cd.go.authorization.keycloak.requests.UserAuthenticationRequest;
22+
import com.thoughtworks.go.plugin.api.response.DefaultGoPluginApiResponse;
23+
import com.thoughtworks.go.plugin.api.response.GoPluginApiResponse;
2124
import okhttp3.*;
2225

2326
import java.io.IOException;
27+
import java.util.Base64;
2428
import java.util.Map;
2529
import java.util.UUID;
2630
import java.util.concurrent.TimeUnit;
@@ -112,6 +116,17 @@ public TokenInfo fetchAccessToken(Map<String, String> params) throws Exception {
112116

113117
public KeycloakUser userProfile(TokenInfo tokenInfo) throws Exception {
114118
validateTokenInfo(tokenInfo);
119+
String accessToken = tokenInfo.accessToken();
120+
121+
// Check status of token
122+
LOG.info("[KeycloakApiClient] Token Before: " + tokenInfo.accessToken());
123+
if (!introspectToken(tokenInfo.accessToken())) {
124+
LOG.info("[KeycloakApiClient] Token status: Not Active");
125+
if (fetchRefreshToken(tokenInfo.refreshToken()).responseCode() == 200) {
126+
LOG.info("[KeycloakApiClient] Token After: " + tokenInfo.accessToken());
127+
accessToken = tokenInfo.accessToken();
128+
}
129+
}
115130

116131
LOG.debug("[KeycloakApiClient] Fetching user profile using access token.");
117132
String realm = keycloakConfiguration.keycloakRealm();
@@ -128,7 +143,7 @@ public KeycloakUser userProfile(TokenInfo tokenInfo) throws Exception {
128143

129144
final Request request = new Request.Builder()
130145
.url(userProfileUrl)
131-
.addHeader("Authorization", "Bearer " + tokenInfo.accessToken())
146+
.addHeader("Authorization", "Bearer " + accessToken)
132147
.get()
133148
.build();
134149

@@ -156,4 +171,78 @@ private void validateTokenInfo(TokenInfo tokenInfo) {
156171
throw new RuntimeException("[KeycloakApiClient] TokenInfo must not be null.");
157172
}
158173
}
174+
175+
public Boolean introspectToken(String token) throws Exception {
176+
177+
LOG.debug("[KeycloakApiClient] Fetching status of the access token.");
178+
String realm = keycloakConfiguration.keycloakRealm();
179+
String client = keycloakConfiguration.clientId();
180+
String secret = keycloakConfiguration.clientSecret();
181+
String basicEncode = Base64.getEncoder().encodeToString((client + ":" + secret).getBytes());
182+
183+
final String introspectUrl = HttpUrl.parse(keycloakConfiguration.keycloakEndpoint())
184+
.newBuilder()
185+
.addPathSegments("auth")
186+
.addPathSegments("realms")
187+
.addPathSegments(realm)
188+
.addPathSegments("protocol")
189+
.addPathSegments("openid-connect")
190+
.addPathSegments("token")
191+
.addPathSegments("introspect")
192+
.toString();
193+
194+
final FormBody formBody = new FormBody.Builder()
195+
.add("token", token)
196+
.build();
197+
198+
final Request request = new Request.Builder()
199+
.url(introspectUrl)
200+
.addHeader("Authorization", "Basic " + basicEncode)
201+
.post(formBody)
202+
.build();
203+
204+
KeycloakIntrospectToken getStatus = executeRequest(request, response -> KeycloakIntrospectToken.fromJSON(response.body().string()));
205+
206+
// Check token status and return true if state ok.
207+
if (getStatus.getActive()) {
208+
return true;
209+
}
210+
211+
return false;
212+
}
213+
214+
public GoPluginApiResponse fetchRefreshToken(String refresh_token) throws Exception {
215+
216+
LOG.debug("[KeycloakApiClient] Fetching token from refresh token.");
217+
String realm = keycloakConfiguration.keycloakRealm();
218+
String client = keycloakConfiguration.clientId();
219+
String secret = keycloakConfiguration.clientSecret();
220+
String basicEncode = Base64.getEncoder().encodeToString((client + ":" + secret).getBytes());
221+
222+
final String refreshTokenUrl = HttpUrl.parse(keycloakConfiguration.keycloakEndpoint())
223+
.newBuilder()
224+
.addPathSegments("auth")
225+
.addPathSegments("realms")
226+
.addPathSegments(realm)
227+
.addPathSegments("protocol")
228+
.addPathSegments("openid-connect")
229+
.addPathSegments("token")
230+
.build().toString();
231+
232+
final FormBody formBody = new FormBody.Builder()
233+
.add("grant_type", "refresh_token")
234+
.add("refresh_token", refresh_token)
235+
.build();
236+
237+
final Request request = new Request.Builder()
238+
.url(refreshTokenUrl)
239+
.addHeader("Authorization", "Basic " + basicEncode)
240+
.addHeader("Accept", "application/json")
241+
.post(formBody)
242+
.build();
243+
244+
TokenInfo tokenInfo = executeRequest(request, response -> TokenInfo.fromJSON(response.body().string()));
245+
LOG.info("[KeycloakApiClient] Token Info: " + tokenInfo.toJSON().toString());
246+
return DefaultGoPluginApiResponse.success(tokenInfo.toJSON());
247+
}
159248
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* Copyright 2017 ThoughtWorks, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package cd.go.authorization.keycloak;
18+
19+
import com.google.gson.annotations.Expose;
20+
import com.google.gson.annotations.SerializedName;
21+
22+
import java.math.BigInteger;
23+
import java.util.List;
24+
25+
import static cd.go.authorization.keycloak.utils.Util.GSON;
26+
27+
public class KeycloakIntrospectToken {
28+
@Expose
29+
@SerializedName("exp")
30+
private BigInteger exp;
31+
32+
@Expose
33+
@SerializedName("aud")
34+
private String aud;
35+
36+
@Expose
37+
@SerializedName("active")
38+
private boolean active;
39+
40+
KeycloakIntrospectToken() {
41+
}
42+
43+
public BigInteger getExp() {
44+
return exp;
45+
}
46+
47+
public boolean getActive() {
48+
return active;
49+
}
50+
51+
public String getAudience() {
52+
return aud;
53+
}
54+
55+
public String toJSON() {
56+
return GSON.toJson(this);
57+
}
58+
59+
public static KeycloakIntrospectToken fromJSON(String json) {
60+
return GSON.fromJson(json, KeycloakIntrospectToken.class);
61+
}
62+
}

0 commit comments

Comments
 (0)