From 4dea91565e9db70a6bfce715b79d3552ddcfd409 Mon Sep 17 00:00:00 2001 From: Ivan Fernandez Calvo Date: Mon, 12 Dec 2022 08:49:30 +0100 Subject: [PATCH] feat: support random generated relayState parameter (#299) --- doc/CONFIGURE.md | 2 ++ .../plugins/saml/OpenSAMLWrapper.java | 8 +++++-- .../saml/SamlAdvancedConfiguration.java | 14 +++++++++++- .../SamlAdvancedConfiguration/config.jelly | 3 +++ src/main/webapp/help/randomRelayState.html | 4 ++++ .../org/jenkinsci/plugins/saml/LiveTest.java | 22 +++++++++++++++++++ 6 files changed, 50 insertions(+), 3 deletions(-) create mode 100644 src/main/webapp/help/randomRelayState.html diff --git a/doc/CONFIGURE.md b/doc/CONFIGURE.md index 467c2cfb..d63f0f9d 100644 --- a/doc/CONFIGURE.md +++ b/doc/CONFIGURE.md @@ -49,6 +49,8 @@ you also could set the sessions on Jenkins to be shorter than those on your IdP. rather than its default. Check with the IdP administrators to find out which authentication contexts are available * **SP Entity ID** - If this field is not empty, it overrides the default Entity ID for this Service Provider. Service Provider Entity IDs are usually a URL, like ***http://jenkins.example.org/securityRealm/finishLogin***. + * **Use cache for configuration files** - The SP metadata is written on every login, enable this setting change the behaviour to use cache, and save the file only if it has changes. + * **Use Random relayState value** - When you enable this option the value of the relayState parameter sent to the IdP is a random generated value. The default value of relayState is `JENKINS_URL/securityRealm/finishLogin` * **Encryption** - If your provider requires encryption or signing, you can specify the keystore details here that should be used. If you do not specify a keystore, the plugin would create one with a key that is valid for a year, this key would be recreated when it expires, by default the key is not exposed in the SP metadata if you do not enable signing. diff --git a/src/main/java/org/jenkinsci/plugins/saml/OpenSAMLWrapper.java b/src/main/java/org/jenkinsci/plugins/saml/OpenSAMLWrapper.java index 273ad5cd..384a5afa 100644 --- a/src/main/java/org/jenkinsci/plugins/saml/OpenSAMLWrapper.java +++ b/src/main/java/org/jenkinsci/plugins/saml/OpenSAMLWrapper.java @@ -17,7 +17,6 @@ package org.jenkinsci.plugins.saml; -import java.io.IOException; import java.util.Arrays; import java.util.logging.Logger; import org.apache.commons.lang.StringUtils; @@ -25,6 +24,7 @@ import org.kohsuke.stapler.StaplerResponse; import org.opensaml.core.config.InitializationException; import org.opensaml.core.config.InitializationService; +import org.pac4j.core.util.generator.RandomValueGenerator; import org.pac4j.jee.context.JEEContext; import org.pac4j.core.context.WebContext; import org.pac4j.core.exception.TechnicalException; @@ -130,7 +130,8 @@ protected SAML2Client createSAML2Client() { // tolerate missing SAML response Destination attribute https://github.com/pac4j/pac4j/pull/1871 config.setResponseDestinationAttributeMandatory(false); - if (samlPluginConfig.getAdvancedConfiguration() != null) { + SamlAdvancedConfiguration advancedConfiguration = samlPluginConfig.getAdvancedConfiguration(); + if (advancedConfiguration != null) { // request forced authentication at the IdP, if selected config.setForceAuth(samlPluginConfig.getForceAuthn()); @@ -158,6 +159,9 @@ protected SAML2Client createSAML2Client() { SAML2Client saml2Client = new SAML2Client(config); saml2Client.setCallbackUrl(samlPluginConfig.getConsumerServiceUrl()); saml2Client.setCallbackUrlResolver(new NoParameterCallbackUrlResolver()); + if(advancedConfiguration != null && advancedConfiguration.getRandomRelayState()){ + saml2Client.setStateGenerator(new RandomValueGenerator()); + } saml2Client.init(); if (LOG.isLoggable(FINE)) { diff --git a/src/main/java/org/jenkinsci/plugins/saml/SamlAdvancedConfiguration.java b/src/main/java/org/jenkinsci/plugins/saml/SamlAdvancedConfiguration.java index c3e150a0..7afa5baf 100644 --- a/src/main/java/org/jenkinsci/plugins/saml/SamlAdvancedConfiguration.java +++ b/src/main/java/org/jenkinsci/plugins/saml/SamlAdvancedConfiguration.java @@ -42,6 +42,8 @@ public class SamlAdvancedConfiguration extends AbstractDescribableImpl + + + diff --git a/src/main/webapp/help/randomRelayState.html b/src/main/webapp/help/randomRelayState.html new file mode 100644 index 00000000..bb0b2e58 --- /dev/null +++ b/src/main/webapp/help/randomRelayState.html @@ -0,0 +1,4 @@ +
+ When you enable this option the value of the relayState parameter sent to the IdP is a random generated value. + The default value of relayState is JENKINS_URL/securityRealm/finishLogin +
\ No newline at end of file diff --git a/src/test/java/org/jenkinsci/plugins/saml/LiveTest.java b/src/test/java/org/jenkinsci/plugins/saml/LiveTest.java index 51f83b34..1d7e4e31 100644 --- a/src/test/java/org/jenkinsci/plugins/saml/LiveTest.java +++ b/src/test/java/org/jenkinsci/plugins/saml/LiveTest.java @@ -84,6 +84,28 @@ public void run(JenkinsRule r) throws Throwable { } } + @Test + public void authenticationRelayStateRandom() throws Throwable { + then(() -> new AuthenticationRelayStateRandom(readIdPMetadataFromURL())); + } + private static class AuthenticationRelayStateRandom implements RealJenkinsRule.Step { + private final String idpMetadata; + AuthenticationRelayStateRandom(String idpMetadata) { + this.idpMetadata = idpMetadata; + } + @Override + public void run(JenkinsRule r) throws Throwable { + IdpMetadataConfiguration idpMetadataConfiguration = new IdpMetadataConfiguration(idpMetadata); + SamlAdvancedConfiguration advancedConfiguration = new SamlAdvancedConfiguration( + false, null, SERVICE_PROVIDER_ID, null); + advancedConfiguration.setRandomRelayState(true); + SamlSecurityRealm realm = configureBasicSettings(idpMetadataConfiguration, advancedConfiguration, SAML2_REDIRECT_BINDING_URI); + r.jenkins.setSecurityRealm(realm); + configureAuthorization(); + makeLoginWithUser1(r); + } + } + @Test public void authenticationOKFromURL() throws Throwable { then(() -> new AuthenticationOKFromURL(createIdPMetadataURL()));