customParameterMap = getCustomParametersMap(logoutQueryParameters, forbiddenKeys);
+ LOGGER.info("Append the following custom parameters to the logout endpoint: " + customParameterMap);
+ segmentsMap.putAll(customParameterMap);
+ segmentsSet.addAll(Arrays.stream(logoutQueryParameters.split("&"))
+ .filter(a -> !a.contains("=") && Util.fixEmptyAndTrim(a) != null && !forbiddenKeys.contains(a))
+ .map(Util::fixEmptyAndTrim)
+ .collect(Collectors.toSet()));
+ }
+
+ StringBuilder openidLogoutEndpoint = new StringBuilder(url.toString());
+ String concatChar = openidLogoutEndpoint.toString().contains("?") ? "&" : "?";
+ if (!segmentsMap.isEmpty()) {
+ String joinedString = segmentsMap.entrySet().stream()
+ .map(entry -> entry.getKey() + "=" + entry.getValue())
+ .collect(Collectors.joining("&"));
+ openidLogoutEndpoint.append(concatChar).append(joinedString);
+ concatChar = "&";
+ }
+ if (!segmentsSet.isEmpty()) {
+ openidLogoutEndpoint.append(concatChar).append(String.join("&", segmentsSet));
}
return openidLogoutEndpoint.toString();
}
@@ -1243,7 +1307,7 @@ private String buildOAuthRedirectUrl() throws NullPointerException {
* @throws ParseException if the JWT (or other response) could not be parsed.
*/
public void doFinishLogin(StaplerRequest request, StaplerResponse response) throws IOException, ParseException {
- OidcClient client = buildOidcClient();
+ OidcClient client = buildOidcClient(false);
WebContext webContext = JEEContextFactory.INSTANCE.newContext(request, response);
SessionStore sessionStore = JEESessionStoreFactory.INSTANCE.newSessionStore();
@@ -1384,7 +1448,7 @@ private boolean refreshExpiredToken(
WebContext webContext = JEEContextFactory.INSTANCE.newContext(httpRequest, httpResponse);
SessionStore sessionStore = JEESessionStoreFactory.INSTANCE.newSessionStore();
- OidcClient client = buildOidcClient();
+ OidcClient client = buildOidcClient(false);
// PAC4J maintains the nonce even though servers should not respond with an id token containing the nonce
// https://openid.net/specs/openid-connect-core-1_0.html#RefreshTokenResponse
// it SHOULD NOT have a nonce Claim, even when the ID Token issued at the time of the original authentication
diff --git a/src/main/java/org/jenkinsci/plugins/oic/OicServerConfiguration.java b/src/main/java/org/jenkinsci/plugins/oic/OicServerConfiguration.java
index 1251fd43..db78e7a1 100644
--- a/src/main/java/org/jenkinsci/plugins/oic/OicServerConfiguration.java
+++ b/src/main/java/org/jenkinsci/plugins/oic/OicServerConfiguration.java
@@ -14,4 +14,8 @@ public abstract class OicServerConfiguration extends AbstractDescribableImpl
+
+
+
+
+
+
+
diff --git a/src/main/resources/org/jenkinsci/plugins/oic/OicServerManualConfiguration/config.properties b/src/main/resources/org/jenkinsci/plugins/oic/OicServerManualConfiguration/config.properties
index 857f29e1..074c291d 100644
--- a/src/main/resources/org/jenkinsci/plugins/oic/OicServerManualConfiguration/config.properties
+++ b/src/main/resources/org/jenkinsci/plugins/oic/OicServerManualConfiguration/config.properties
@@ -3,6 +3,8 @@ Basic=Basic
EndSessionUrl=End session URL for OpenID Provider
Issuer=Issuer
JwksServerUrl=Jwks server url
+LoginQueryParameters=Additional login url query parameters
+LogoutQueryParameters=Additional logout url query parameters
Post=Post
Scopes=Scopes
TokenAuthenticationMethod=Token Authentication Method
diff --git a/src/main/resources/org/jenkinsci/plugins/oic/OicServerManualConfiguration/help-loginQueryParameters.html b/src/main/resources/org/jenkinsci/plugins/oic/OicServerManualConfiguration/help-loginQueryParameters.html
new file mode 100644
index 00000000..0246fba7
--- /dev/null
+++ b/src/main/resources/org/jenkinsci/plugins/oic/OicServerManualConfiguration/help-loginQueryParameters.html
@@ -0,0 +1,3 @@
+
+ When defined, append the string to the authorization server url. Format: key1=value1&key2=value2
+
diff --git a/src/main/resources/org/jenkinsci/plugins/oic/OicServerManualConfiguration/help-logoutQueryParameters.html b/src/main/resources/org/jenkinsci/plugins/oic/OicServerManualConfiguration/help-logoutQueryParameters.html
new file mode 100644
index 00000000..d0d8536d
--- /dev/null
+++ b/src/main/resources/org/jenkinsci/plugins/oic/OicServerManualConfiguration/help-logoutQueryParameters.html
@@ -0,0 +1,3 @@
+
+ When defined, add the key value pairs to the end session url. Format: key1=value1¶m2&key3=value3
+
diff --git a/src/main/resources/org/jenkinsci/plugins/oic/OicServerWellKnownConfiguration/config.jelly b/src/main/resources/org/jenkinsci/plugins/oic/OicServerWellKnownConfiguration/config.jelly
index a3093090..125c061c 100644
--- a/src/main/resources/org/jenkinsci/plugins/oic/OicServerWellKnownConfiguration/config.jelly
+++ b/src/main/resources/org/jenkinsci/plugins/oic/OicServerWellKnownConfiguration/config.jelly
@@ -10,6 +10,11 @@
field="scopesOverride">
+
+
+
+
+
+
-
diff --git a/src/main/resources/org/jenkinsci/plugins/oic/OicServerWellKnownConfiguration/config.properties b/src/main/resources/org/jenkinsci/plugins/oic/OicServerWellKnownConfiguration/config.properties
index 548acc14..98f5233b 100644
--- a/src/main/resources/org/jenkinsci/plugins/oic/OicServerWellKnownConfiguration/config.properties
+++ b/src/main/resources/org/jenkinsci/plugins/oic/OicServerWellKnownConfiguration/config.properties
@@ -1,2 +1,4 @@
OverrideScopes=Override scopes
+LoginQueryParameters=Additional login url query parameters
+LogoutQueryParameters=Additional logout url query parameters
WellknownConfigurationEndpoint=Well-known configuration endpoint
diff --git a/src/main/resources/org/jenkinsci/plugins/oic/OicServerWellKnownConfiguration/help-loginQueryParameters.html b/src/main/resources/org/jenkinsci/plugins/oic/OicServerWellKnownConfiguration/help-loginQueryParameters.html
new file mode 100644
index 00000000..0246fba7
--- /dev/null
+++ b/src/main/resources/org/jenkinsci/plugins/oic/OicServerWellKnownConfiguration/help-loginQueryParameters.html
@@ -0,0 +1,3 @@
+
+ When defined, append the string to the authorization server url. Format: key1=value1&key2=value2
+
diff --git a/src/main/resources/org/jenkinsci/plugins/oic/OicServerWellKnownConfiguration/help-logoutQueryParameters.html b/src/main/resources/org/jenkinsci/plugins/oic/OicServerWellKnownConfiguration/help-logoutQueryParameters.html
new file mode 100644
index 00000000..86bbe239
--- /dev/null
+++ b/src/main/resources/org/jenkinsci/plugins/oic/OicServerWellKnownConfiguration/help-logoutQueryParameters.html
@@ -0,0 +1,3 @@
+
+ When defined, append the string to the end session url. Format: key1=value1¶m2&key3=value3
+
diff --git a/src/test/java/org/jenkinsci/plugins/oic/ConfigurationAsCodeTest.java b/src/test/java/org/jenkinsci/plugins/oic/ConfigurationAsCodeTest.java
index d5de7619..1390e41b 100644
--- a/src/test/java/org/jenkinsci/plugins/oic/ConfigurationAsCodeTest.java
+++ b/src/test/java/org/jenkinsci/plugins/oic/ConfigurationAsCodeTest.java
@@ -68,6 +68,8 @@ public void testConfig() {
assertTrue(oicSecurityRealm.isRootURLFromRequest());
assertEquals("http://localhost/jwks", serverConf.getJwksServerUrl());
assertFalse(oicSecurityRealm.isDisableTokenVerification());
+ assertEquals("key1i=value1i", serverConf.getLoginQueryParameters());
+ assertEquals("key1o=value1o&key2o=value2o", serverConf.getLogoutQueryParameters());
}
@Test
@@ -120,8 +122,10 @@ public void testMinimal() throws Exception {
assertEquals("sub", oicSecurityRealm.getUserNameField());
assertTrue(oicSecurityRealm.isLogoutFromOpenidProvider());
assertFalse(oicSecurityRealm.isRootURLFromRequest());
- assertEquals(null, serverConf.getJwksServerUrl());
+ assertNull(serverConf.getJwksServerUrl());
assertFalse(oicSecurityRealm.isDisableTokenVerification());
+ assertNull(serverConf.getLoginQueryParameters());
+ assertNull(serverConf.getLogoutQueryParameters());
}
@Rule(order = 0)
diff --git a/src/test/java/org/jenkinsci/plugins/oic/OicSecurityRealmTest.java b/src/test/java/org/jenkinsci/plugins/oic/OicSecurityRealmTest.java
index 32d859cc..8f0e01ff 100644
--- a/src/test/java/org/jenkinsci/plugins/oic/OicSecurityRealmTest.java
+++ b/src/test/java/org/jenkinsci/plugins/oic/OicSecurityRealmTest.java
@@ -3,6 +3,8 @@
import com.github.tomakehurst.wiremock.core.WireMockConfiguration;
import com.github.tomakehurst.wiremock.junit.WireMockRule;
import hudson.util.Secret;
+import java.util.Map;
+import java.util.Set;
import org.acegisecurity.AuthenticationManager;
import org.acegisecurity.BadCredentialsException;
import org.acegisecurity.GrantedAuthority;
@@ -142,4 +144,45 @@ public void testShouldCheckEscapeHatchWithHashedPassword() throws Exception {
assertFalse(realm.doCheckEscapeHatch("otherUsername", escapeHatchPassword));
assertFalse(realm.doCheckEscapeHatch(escapeHatchUsername, "wrongPassword"));
}
+
+ @Test
+ public void testGetCustomLoginParameters() throws Exception {
+ TestRealm realm =
+ new TestRealm.Builder(wireMockRule).WithMinimalDefaults().build();
+ Set forbiddenKeys = Set.of("forbidden-key");
+ assertEquals(
+ Map.of("a", "1", "c", "2"),
+ realm.getCustomParametersMap("a=1&b&c= 2 &=no&forbidden-key=test", forbiddenKeys));
+ }
+
+ @Test
+ public void testMaybeOpenIdLogoutEndpointWithNoCustomLogoutQueryParameters() throws Exception {
+ TestRealm realm = new TestRealm.Builder(wireMockRule)
+ .WithMinimalDefaults().WithLogout(true, "https://endpoint").build();
+ assertEquals(
+ "https://endpoint?id_token_hint=my-id-token&post_logout_redirect_uri=https%3A%2F%2Flocalhost",
+ realm.maybeOpenIdLogoutEndpoint("my-id-token", "null", "https://localhost"));
+ assertEquals(
+ "https://endpoint?id_token_hint=my-id-token&post_logout_redirect_uri=https%3A%2F%2Flocalhost",
+ realm.maybeOpenIdLogoutEndpoint("my-id-token", null, "https://localhost"));
+ assertEquals(
+ "https://endpoint?id_token_hint=my-id-token&state=test&post_logout_redirect_uri=https%3A%2F%2Flocalhost",
+ realm.maybeOpenIdLogoutEndpoint("my-id-token", "test", "https://localhost"));
+ }
+
+ @Test
+ public void testMaybeOpenIdLogoutEndpointWithCustomLogoutQueryParameters() throws Exception {
+ TestRealm realm = new TestRealm.Builder(wireMockRule)
+ .WithMinimalDefaults()
+ .WithLogoutQueryParameters(
+ "key1=value1&=drop-me&key2 = with-spaces ¶m-only&id_token_hint=overwrite-test-1&post_logout_redirect_uri=overwrite-test-2&state=overwrite-test-3")
+ .WithLogout(true, "https://endpoint")
+ .build();
+ String result = realm.maybeOpenIdLogoutEndpoint("my-id-token", "test", "https://localhost");
+ assertFalse(result.contains("drop-me"));
+ assertFalse(result.contains("overwrite-test"));
+ assertEquals(
+ "https://endpoint?key1=value1&key2=with-spaces&id_token_hint=my-id-token&state=test&post_logout_redirect_uri=https%3A%2F%2Flocalhost¶m-only",
+ result);
+ }
}
diff --git a/src/test/java/org/jenkinsci/plugins/oic/PluginTest.java b/src/test/java/org/jenkinsci/plugins/oic/PluginTest.java
index 4d2fcb34..d994ca2d 100644
--- a/src/test/java/org/jenkinsci/plugins/oic/PluginTest.java
+++ b/src/test/java/org/jenkinsci/plugins/oic/PluginTest.java
@@ -1000,7 +1000,24 @@ public void testLogoutShouldBeProviderURLWhenProviderLogoutConfigured() throws E
logoutURL[0] = oicsr.getPostLogOutUrl2(Stapler.getCurrentRequest(), Jenkins.ANONYMOUS2);
return null;
});
- assertEquals("http://provider/logout?state=null", logoutURL[0]);
+ assertEquals("http://provider/logout", logoutURL[0]);
+ }
+
+ @Test
+ public void testLogoutShouldBeProviderURLWhenProviderLogoutConfiguredWithAdditionalLogoutQueryParameters()
+ throws Exception {
+ final TestRealm oicsr = new TestRealm.Builder(wireMockRule)
+ .WithLogoutQueryParameters("hello=world&state=test&single&id_token_hint=other&empty=")
+ .WithLogout(Boolean.TRUE, "http://provider/logout")
+ .build();
+ jenkins.setSecurityRealm(oicsr);
+
+ String[] logoutURL = new String[1];
+ jenkinsRule.executeOnServer(() -> {
+ logoutURL[0] = oicsr.getPostLogOutUrl2(Stapler.getCurrentRequest(), Jenkins.ANONYMOUS2);
+ return null;
+ });
+ assertEquals("http://provider/logout?hello=world&empty=&single", logoutURL[0]);
}
@Test
@@ -1018,7 +1035,7 @@ public void testLogoutShouldBeProviderURLWithRedirectWhenProviderLogoutConfigure
return null;
});
assertEquals(
- "http://provider/logout?state=null&post_logout_redirect_uri=http%3A%2F%2Fsee.it%2F%3Fcat%26color%3Dwhite",
+ "http://provider/logout?post_logout_redirect_uri=http%3A%2F%2Fsee.it%2F%3Fcat%26color%3Dwhite",
logoutURL[0]);
}
diff --git a/src/test/java/org/jenkinsci/plugins/oic/TestRealm.java b/src/test/java/org/jenkinsci/plugins/oic/TestRealm.java
index eec1d58e..23a94862 100644
--- a/src/test/java/org/jenkinsci/plugins/oic/TestRealm.java
+++ b/src/test/java/org/jenkinsci/plugins/oic/TestRealm.java
@@ -40,6 +40,8 @@ public static class Builder {
public String fullNameFieldName = FULL_NAME_FIELD;
public String emailFieldName = null;
public String scopes = null;
+ public String loginQueryParameters = null;
+ public String logoutQueryParameters = null;
public String groupsFieldName = null;
public boolean disableSslVerification = false;
public Boolean logoutFromOpenidProvider = false;
@@ -115,6 +117,16 @@ public Builder WithScopes(String scopes) {
return this;
}
+ public Builder WithLoginQueryParameters(String loginQueryParameters) {
+ this.loginQueryParameters = loginQueryParameters;
+ return this;
+ }
+
+ public Builder WithLogoutQueryParameters(String logoutQueryParameters) {
+ this.logoutQueryParameters = logoutQueryParameters;
+ return this;
+ }
+
public Builder WithMinimalDefaults() {
return this.WithEmailFieldName(EMAIL_FIELD).WithGroupsFieldName(GROUPS_FIELD);
}
@@ -159,6 +171,12 @@ public OicServerConfiguration buildServerConfiguration() {
if (scopes != null) {
conf.setScopesOverride(scopes);
}
+ if (loginQueryParameters != null) {
+ conf.setLoginQueryParameters(loginQueryParameters);
+ }
+ if (logoutQueryParameters != null) {
+ conf.setLogoutQueryParameters(logoutQueryParameters);
+ }
return conf;
}
OicServerManualConfiguration conf =
@@ -168,6 +186,12 @@ public OicServerConfiguration buildServerConfiguration() {
if (scopes != null) {
conf.setScopes(scopes);
}
+ if (loginQueryParameters != null) {
+ conf.setLoginQueryParameters(loginQueryParameters);
+ }
+ if (logoutQueryParameters != null) {
+ conf.setLogoutQueryParameters(logoutQueryParameters);
+ }
conf.setJwksServerUrl(jwksServerUrl);
conf.setEndSessionUrl(endSessionEndpoint);
return conf;
@@ -230,6 +254,7 @@ public TestRealm(
.WithUserInfoServerUrl(userInfoServerUrl)
.WithEmailFieldName(emailFieldName)
.WithGroupsFieldName(groupFieldName)
+ .WithLoginQueryParameters("queryLoginParamKey=queryLoginParamValue")
.WithAutomanualconfigure(automanualconfigure));
}
@@ -267,7 +292,7 @@ public void doFinishLogin(StaplerRequest request, StaplerResponse response) thro
// only hack the nonce if the nonce is enabled
WebContext webContext = JEEContextFactory.INSTANCE.newContext(request, response);
SessionStore sessionStore = JEESessionStoreFactory.INSTANCE.newSessionStore();
- OidcClient oidcClient = buildOidcClient();
+ OidcClient oidcClient = buildOidcClient(true);
sessionStore.set(webContext, oidcClient.getNonceSessionAttributeName(), "nonce");
}
super.doFinishLogin(request, response);
diff --git a/src/test/resources/org/jenkinsci/plugins/oic/ConfigurationAsCode.yml b/src/test/resources/org/jenkinsci/plugins/oic/ConfigurationAsCode.yml
index fbd70922..c0da21bf 100644
--- a/src/test/resources/org/jenkinsci/plugins/oic/ConfigurationAsCode.yml
+++ b/src/test/resources/org/jenkinsci/plugins/oic/ConfigurationAsCode.yml
@@ -9,6 +9,8 @@ jenkins:
tokenAuthMethod: client_secret_post
tokenServerUrl: http://localhost/token
scopes: scopes
+ loginQueryParameters: key1i=value1i
+ logoutQueryParameters: key1o=value1o&key2o=value2o
clientId: clientId
clientSecret: clientSecret
disableSslVerification: true
diff --git a/src/test/resources/org/jenkinsci/plugins/oic/ConfigurationAsCodeExport.yml b/src/test/resources/org/jenkinsci/plugins/oic/ConfigurationAsCodeExport.yml
index b8d603a2..4c989d0b 100644
--- a/src/test/resources/org/jenkinsci/plugins/oic/ConfigurationAsCodeExport.yml
+++ b/src/test/resources/org/jenkinsci/plugins/oic/ConfigurationAsCodeExport.yml
@@ -15,6 +15,8 @@ serverConfiguration:
authorizationServerUrl: "http://localhost/authorize"
issuer: "http://localhost/"
jwksServerUrl: "http://localhost/jwks"
+ loginQueryParameters: "key1i=value1i"
+ logoutQueryParameters: "key1o=value1o&key2o=value2o"
scopes: "scopes"
tokenServerUrl: "http://localhost/token"
userNameField: "userNameField"