diff --git a/docs/configuration/README.md b/docs/configuration/README.md index b39e56ea..74a31a98 100644 --- a/docs/configuration/README.md +++ b/docs/configuration/README.md @@ -40,6 +40,8 @@ but this can be overriden by the `scopesOverride` config parameter. |---------------------------------|--------|------------------------------------------------------------------| | wellKnownOpenIDConfigurationUrl | url | Providers' well-known configuration endpoint | | scopesOverride | string | Space separated list of scopes to request (default: request all) | +| loginQueryParameters | string | Ampersand separated separated key=value pairs | +| logoutQueryParameters | string | Ampersand separated separated key=value pairs | When configuring from the interface, the automatic mode will fill in the fields expected in manual mode. This can be useful for prefilling the @@ -52,17 +54,19 @@ The scopes can be configured but default to `openid email`. If the JWKS endpoint is configured, JWS' signatures will be verified (unless disabled). -| field | format | description | -|------------------------|---------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| authorizationServerUrl | url | URL the user is redirected to at login | -| tokenServerUrl | url | URL used by jenkins to request the tokens | -| endSessionUrl | url | URL to logout from provider (used if activated) | -| jwksServerUrl | url | URL of provider's jws certificates (unused if disabled) | -| scopes | string | Space separated list of scopes to request (default: `openid email`) | -| tokenAuthMethod | enum | Method used for authenticating when requesting token(s)
- `client_secret_basic`: for client id/secret as basic authentication user/pass
- `client_secret_post`: for client id/secret sent in post request | -| userInfoServerUrl | url | URL to get user's details | -| useRefreshTokens | boolean | If server supports refresh tokens, make sure to specify any additional scopes required for refresh token support. | -| issuer | string | The expected received ID Token's issuer | +| field | format | description | +|------------------------|----------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| authorizationServerUrl | url | URL the user is redirected to at login | +| tokenServerUrl | url | URL used by jenkins to request the tokens | +| endSessionUrl | url | URL to logout from provider (used if activated) | +| jwksServerUrl | url | URL of provider's jws certificates (unused if disabled) | +| scopes | string | Space separated list of scopes to request (default: `openid email`) | +| tokenAuthMethod | enum | Method used for authenticating when requesting token(s)
- `client_secret_basic`: for client id/secret as basic authentication user/pass
- `client_secret_post`: for client id/secret sent in post request | +| userInfoServerUrl | url | URL to get user's details | +| useRefreshTokens | boolean | If server supports refresh tokens, make sure to specify any additional scopes required for refresh token support. | +| issuer | string | The expected received ID Token's issuer | +| loginQueryParameters | string | Ampersand separated separated key=value pairs | +| logoutQueryParameters | string | Ampersand separated separated key=value pairs | ### Advanced configuration @@ -116,6 +120,8 @@ jenkins: # use only one of wellKnown or manual # Automatic config of endpoint wellKnown: + loginQueryParameters: + logoutQueryParameters: wellKnownOpenIDConfigurationUrl: scopesOverride: # Manual config of endpoint @@ -124,6 +130,8 @@ jenkins: endSessionUrl: issuer: jwksServerUrl: + loginQueryParameters: + logoutQueryParameters: tokenAuthMethod: tokenServerUrl: scopes: diff --git a/src/main/java/org/jenkinsci/plugins/oic/OicSecurityRealm.java b/src/main/java/org/jenkinsci/plugins/oic/OicSecurityRealm.java index 0458709d..2ced8cf4 100644 --- a/src/main/java/org/jenkinsci/plugins/oic/OicSecurityRealm.java +++ b/src/main/java/org/jenkinsci/plugins/oic/OicSecurityRealm.java @@ -69,15 +69,20 @@ import java.text.ParseException; import java.time.Clock; import java.util.ArrayList; +import java.util.Arrays; import java.util.Base64; import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Random; +import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Pattern; +import java.util.stream.Collectors; import javax.annotation.PostConstruct; import javax.servlet.Filter; import javax.servlet.FilterChain; @@ -505,7 +510,7 @@ ProxyAwareResourceRetriever getResourceRetriever() { return proxyAwareResourceRetriever; } - private OidcConfiguration buildOidcConfiguration() { + private OidcConfiguration buildOidcConfiguration(boolean addCustomLoginParams) { // TODO cache this and use the well known if available. OidcConfiguration conf = new CustomOidcConfiguration(this.isDisableSslVerification()); conf.setClientId(clientId); @@ -534,9 +539,46 @@ private OidcConfiguration buildOidcConfiguration() { if (this.isPkceEnabled()) { conf.setPkceMethod(CodeChallengeMethod.S256); } + if (addCustomLoginParams && this.serverConfiguration.getLoginQueryParameters() != null) { + Set forbiddenKeys = Set.of( + OidcConfiguration.SCOPE, + OidcConfiguration.RESPONSE_TYPE, + OidcConfiguration.RESPONSE_MODE, + OidcConfiguration.REDIRECT_URI, + OidcConfiguration.CLIENT_ID, + OidcConfiguration.STATE, + OidcConfiguration.MAX_AGE, + OidcConfiguration.PROMPT, + OidcConfiguration.NONCE, + OidcConfiguration.CODE_CHALLENGE, + OidcConfiguration.CODE_CHALLENGE_METHOD); + Map customParameterMap = + getCustomParametersMap(this.serverConfiguration.getLoginQueryParameters(), forbiddenKeys); + LOGGER.info("Append the following custom parameters to the authorize endpoint: " + customParameterMap); + customParameterMap.forEach(conf::addCustomParam); + } return conf; } + Map getCustomParametersMap(String queryParameters, Set forbiddenKeys) { + return Arrays.stream(queryParameters.split("&")) + .filter(a -> a.contains("=")) + .map(s -> s.split("=")) + .filter(a -> Util.fixEmptyAndTrim(a[0]) != null && !forbiddenKeys.contains(a[0])) + .collect(Collectors.toMap(a -> Util.fixEmptyAndTrim(a[0]), a -> (a.length > 1 ? a[1].trim() : ""))) + .entrySet() + .stream() + .collect(Collectors.toMap(Map.Entry::getKey, entry -> encodeIfUrl(entry.getValue()))); + } + + private String encodeIfUrl(String value) { + if (value.startsWith("https:") || value.startsWith("http:")) { + return URLEncoder.encode(value, StandardCharsets.UTF_8); + } else { + return value; + } + } + // Visible for testing @Restricted(NoExternalUse.class) protected void filterNonFIPS140CompliantAlgorithms(@NonNull OIDCProviderMetadata oidcProviderMetadata) { @@ -670,8 +712,8 @@ private void filterJwsAlgorithms(@NonNull OIDCProviderMetadata oidcProviderMetad } @Restricted(NoExternalUse.class) // exposed for testing only - protected OidcClient buildOidcClient() { - OidcConfiguration oidcConfiguration = buildOidcConfiguration(); + protected OidcClient buildOidcClient(boolean addCustomLoginParams) { + OidcConfiguration oidcConfiguration = buildOidcConfiguration(addCustomLoginParams); OidcClient client = new OidcClient(oidcConfiguration); // add the extra settings for the client... client.setCallbackUrl(buildOAuthRedirectUrl()); @@ -932,7 +974,7 @@ protected String getValidRedirectUrl(String url) { public void doCommenceLogin(@QueryParameter String from, @Header("Referer") final String referer) throws URISyntaxException { - OidcClient client = buildOidcClient(); + OidcClient client = buildOidcClient(true); // add the extra params for the client... final String redirectOnFinish = getValidRedirectUrl(from != null ? from : referer); @@ -1172,7 +1214,7 @@ public String getPostLogOutUrl2(StaplerRequest req, Authentication auth) { @VisibleForTesting Object getStateAttribute(HttpSession session) { // return null; - OidcClient client = buildOidcClient(); + OidcClient client = buildOidcClient(false); WebContext webContext = JEEContextFactory.INSTANCE.newContext(Stapler.getCurrentRequest(), Stapler.getCurrentResponse()); SessionStore sessionStore = JEESessionStoreFactory.INSTANCE.newSessionStore(); @@ -1183,22 +1225,44 @@ Object getStateAttribute(HttpSession session) { } @CheckForNull - private String maybeOpenIdLogoutEndpoint(String idToken, String state, String postLogoutRedirectUrl) { + String maybeOpenIdLogoutEndpoint(String idToken, String state, String postLogoutRedirectUrl) { final URI url = serverConfiguration.toProviderMetadata().getEndSessionEndpointURI(); if (this.logoutFromOpenidProvider && url != null) { - StringBuilder openidLogoutEndpoint = new StringBuilder(url.toString()); - + Map segmentsMap = new HashMap<>(); + Set segmentsSet = new HashSet<>(); if (!Strings.isNullOrEmpty(idToken)) { - openidLogoutEndpoint.append("?id_token_hint=").append(idToken).append("&"); - } else { - openidLogoutEndpoint.append("?"); + segmentsMap.put("id_token_hint", idToken); + } + if (!Strings.isNullOrEmpty(state) && !"null".equals(state)) { + segmentsMap.put("state", state); } - openidLogoutEndpoint.append("state=").append(state); - if (postLogoutRedirectUrl != null) { - openidLogoutEndpoint - .append("&post_logout_redirect_uri=") - .append(URLEncoder.encode(postLogoutRedirectUrl, StandardCharsets.UTF_8)); + segmentsMap.put( + "post_logout_redirect_uri", URLEncoder.encode(postLogoutRedirectUrl, StandardCharsets.UTF_8)); + } + Set forbiddenKeys = Set.of("id_token_hint", "state", "post_logout_redirect_uri"); + if (this.serverConfiguration.getLogoutQueryParameters() != null) { + String logoutQueryParameters = this.serverConfiguration.getLogoutQueryParameters(); + Map 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"