clone = this.clone();
- for (String key : this.getUnknownKeys().keySet()) {
- clone.remove(key);
- }
- return clone;
- }
-
- /**
- * Overriding equals()
- */
- @Override
- public boolean equals(Object o) {
- if (o == this) {
- return true;
- }
- if (o == null || !(o instanceof WellKnownOpenIDConfigurationResponse)) {
- return false;
- }
-
- WellKnownOpenIDConfigurationResponse obj = (WellKnownOpenIDConfigurationResponse) o;
-
- if (!Objects.equal(authorizationEndpoint, obj.getAuthorizationEndpoint())) {
- return false;
- }
- if (!Objects.equal(issuer, obj.getIssuer())) {
- return false;
- }
- if (!Objects.equal(tokenEndpoint, obj.getTokenEndpoint())) {
- return false;
- }
- if (!Objects.equal(userinfoEndpoint, obj.getUserinfoEndpoint())) {
- return false;
- }
- if (!Objects.equal(jwksUri, obj.getJwksUri())) {
- return false;
- }
- if (!Objects.equal(scopesSupported, obj.getScopesSupported())) {
- return false;
- }
- if (!Objects.equal(grantTypesSupported, obj.getGrantTypesSupported())) {
- return false;
- }
- if (!Objects.equal(endSessionEndpoint, obj.getEndSessionEndpoint())) {
- return false;
- }
-
- return true;
- }
-
- /**
- * Overriding hashCode()
- */
- @Override
- public int hashCode() {
- return (authorizationEndpoint
- + issuer
- + tokenAuthMethods
- + tokenEndpoint
- + userinfoEndpoint
- + jwksUri
- + scopesSupported
- + grantTypesSupported
- + endSessionEndpoint)
- .hashCode();
- }
-}
diff --git a/src/main/java/org/jenkinsci/plugins/oic/ssl/AnythingGoesTrustManager.java b/src/main/java/org/jenkinsci/plugins/oic/ssl/AnythingGoesTrustManager.java
new file mode 100644
index 00000000..ee4899b3
--- /dev/null
+++ b/src/main/java/org/jenkinsci/plugins/oic/ssl/AnythingGoesTrustManager.java
@@ -0,0 +1,34 @@
+package org.jenkinsci.plugins.oic.ssl;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import javax.net.ssl.X509TrustManager;
+import jenkins.security.FIPS140;
+
+@SuppressFBWarnings(value = "WEAK_TRUST_MANAGER", justification = "Opt in by user")
+final class AnythingGoesTrustManager implements X509TrustManager {
+
+ static final X509TrustManager INSTANCE = new AnythingGoesTrustManager();
+
+ private AnythingGoesTrustManager() {}
+
+ @Override
+ public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
+ if (FIPS140.useCompliantAlgorithms()) {
+ throw new CertificateException("can not bypass certificate checking in FIPS mode");
+ }
+ }
+
+ @Override
+ public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
+ if (FIPS140.useCompliantAlgorithms()) {
+ throw new CertificateException("can not bypass certificate checking in FIPS mode");
+ }
+ }
+
+ @Override
+ public X509Certificate[] getAcceptedIssuers() {
+ return new X509Certificate[0];
+ }
+}
diff --git a/src/main/java/org/jenkinsci/plugins/oic/ssl/IgnoringHostNameVerifier.java b/src/main/java/org/jenkinsci/plugins/oic/ssl/IgnoringHostNameVerifier.java
new file mode 100644
index 00000000..40e8b8de
--- /dev/null
+++ b/src/main/java/org/jenkinsci/plugins/oic/ssl/IgnoringHostNameVerifier.java
@@ -0,0 +1,25 @@
+package org.jenkinsci.plugins.oic.ssl;
+
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.SSLSession;
+import jenkins.security.FIPS140;
+import org.kohsuke.accmod.Restricted;
+import org.kohsuke.accmod.restrictions.NoExternalUse;
+
+/**
+ * {@link HostnameVerifier} that accepts any presented hostanme including incorrect ones.
+ */
+@Restricted(NoExternalUse.class)
+public final class IgnoringHostNameVerifier implements HostnameVerifier {
+
+ public static final HostnameVerifier INSTANCE = new IgnoringHostNameVerifier();
+
+ private IgnoringHostNameVerifier() {}
+
+ @Override
+ public boolean verify(String hostname, SSLSession session) {
+ // hostnames must be validated in FIPS mode
+ // outside of FIPS mode anything goes
+ return !FIPS140.useCompliantAlgorithms();
+ }
+}
diff --git a/src/main/java/org/jenkinsci/plugins/oic/ssl/TLSUtils.java b/src/main/java/org/jenkinsci/plugins/oic/ssl/TLSUtils.java
new file mode 100644
index 00000000..cebd9b16
--- /dev/null
+++ b/src/main/java/org/jenkinsci/plugins/oic/ssl/TLSUtils.java
@@ -0,0 +1,29 @@
+package org.jenkinsci.plugins.oic.ssl;
+
+import java.security.KeyManagementException;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSocketFactory;
+import javax.net.ssl.TrustManager;
+import jenkins.security.FIPS140;
+import org.kohsuke.accmod.Restricted;
+import org.kohsuke.accmod.restrictions.NoExternalUse;
+
+@Restricted(NoExternalUse.class)
+public class TLSUtils {
+
+ /**
+ * Construct an {@link SSLSocketFactory} that trust all certificates using "TLS".
+ */
+ public static SSLSocketFactory createAnythingGoesSSLSocketFactory()
+ throws KeyManagementException, NoSuchAlgorithmException {
+ if (FIPS140.useCompliantAlgorithms()) {
+ throw new IllegalStateException(
+ "createAnythingGoesSSLSocketFactory is not supported when running in FIPS mode");
+ }
+ SSLContext sslContext = SSLContext.getInstance("TLS");
+ sslContext.init(null, new TrustManager[] {AnythingGoesTrustManager.INSTANCE}, new SecureRandom());
+ return sslContext.getSocketFactory();
+ }
+}
diff --git a/src/main/resources/org/jenkinsci/plugins/oic/Messages.properties b/src/main/resources/org/jenkinsci/plugins/oic/Messages.properties
index ed4ab7e6..da27d25c 100644
--- a/src/main/resources/org/jenkinsci/plugins/oic/Messages.properties
+++ b/src/main/resources/org/jenkinsci/plugins/oic/Messages.properties
@@ -1,6 +1,7 @@
OicLogoutAction.OicLogout = Oic Logout
OicSecurityRealm.DisplayName = Login with Openid Connect
+OicSecurityRealm.CouldNotRefreshToken = Unable to refresh access token
OicSecurityRealm.ClientIdRequired = Client id is required.
OicSecurityRealm.ClientSecretRequired = Client secret is required.
OicSecurityRealm.URLNotAOpenIdEnpoint = URL does not seem to describe OpenID Connect endpoints
@@ -8,7 +9,6 @@ OicSecurityRealm.NotAValidURL = Not a valid url.
OicSecurityRealm.CouldNotRetreiveWellKnownConfig = Could not retrieve well-known config {0,number,integer} {1}
OicSecurityRealm.CouldNotParseResponse = Could not parse response
OicSecurityRealm.ErrorRetreivingWellKnownConfig = Error when retrieving well-known config
-OicSecurityRealm.IssuerRecommended = For security reasons it is strongly recommended to provide an issuer.
OicSecurityRealm.TokenServerURLKeyRequired = Token Server Url Key is required.
OicSecurityRealm.TokenAuthMethodRequired = Token auth method is required.
OicSecurityRealm.UsingDefaultUsername = Using ''sub''.
@@ -16,9 +16,12 @@ OicSecurityRealm.RUSureOpenIdNotInScope = Are you sure you don''t want to includ
OicSecurityRealm.ScopesRequired = Scopes is required.
OicSecurityRealm.EndSessionURLKeyRequired = End Session URL Key is required.
OicSecurityRealm.InvalidFieldName = Invalid field name - must be a valid JMESPath expression.
+OicSecurityRealm.IssuerRequired = Issuer is required.
OicSecurityRealm.NoIdTokenInResponse = No idtoken was provided in response to token request.
OicSecurityRealm.IdTokenParseError = Idtoken could not be parsed.
OicSecurityRealm.UsernameNotFound = No field ''{0}'' was supplied in the UserInfo or the IdToken payload to be used as the username.
+OicSecurityRealm.SSLErrorRetreivingWellKnownConfig = The server presented an invalid or incorrect TLS certificate.
OicSecurityRealm.TokenRequestFailure = Token request failed: {0}"
+OicSecurityRealm.TokenRefreshFailure = Unable to refresh access token
OicServerWellKnownConfiguration.DisplayName = Discovery via well-known endpoint
OicServerManualConfiguration.DisplayName = Manual entry
diff --git a/src/main/resources/org/jenkinsci/plugins/oic/OicSecurityRealm/help-subjectType.html b/src/main/resources/org/jenkinsci/plugins/oic/OicSecurityRealm/help-subjectType.html
new file mode 100644
index 00000000..10c934a2
--- /dev/null
+++ b/src/main/resources/org/jenkinsci/plugins/oic/OicSecurityRealm/help-subjectType.html
@@ -0,0 +1,5 @@
+
+ The Subject Identifier Type to use as part of the flow with the openid connect provider.
+ This must be supported by the IdP.
+ Valid values are public
and pairwise
.
+
diff --git a/src/main/resources/org/jenkinsci/plugins/oic/OicServerManualConfiguration/config.jelly b/src/main/resources/org/jenkinsci/plugins/oic/OicServerManualConfiguration/config.jelly
index 7c7a91b9..88778ae5 100644
--- a/src/main/resources/org/jenkinsci/plugins/oic/OicServerManualConfiguration/config.jelly
+++ b/src/main/resources/org/jenkinsci/plugins/oic/OicServerManualConfiguration/config.jelly
@@ -1,6 +1,11 @@
+
+
+
+
+
@@ -12,9 +17,6 @@
checked="${instance.tokenAuthMethod == null || instance.tokenAuthMethod == 'client_secret_post'}" value="client_secret_post" inline="true" help="${null}"/>
-
-
-
diff --git a/src/main/resources/org/jenkinsci/plugins/oic/OicServerManualConfiguration/help-issuer.html b/src/main/resources/org/jenkinsci/plugins/oic/OicServerManualConfiguration/help-issuer.html
index 7209bfcc..fce0abbd 100644
--- a/src/main/resources/org/jenkinsci/plugins/oic/OicServerManualConfiguration/help-issuer.html
+++ b/src/main/resources/org/jenkinsci/plugins/oic/OicServerManualConfiguration/help-issuer.html
@@ -1,3 +1,3 @@
- Strongly recommended. The received ID Token's issuer must match the specified issuer.
+ Required. The received ID Token's issuer must match the specified issuer.
diff --git a/src/main/resources/org/jenkinsci/plugins/oic/OicServerManualConfiguration/help-issuer_fr.html b/src/main/resources/org/jenkinsci/plugins/oic/OicServerManualConfiguration/help-issuer_fr.html
index 0e98f08e..570c189a 100644
--- a/src/main/resources/org/jenkinsci/plugins/oic/OicServerManualConfiguration/help-issuer_fr.html
+++ b/src/main/resources/org/jenkinsci/plugins/oic/OicServerManualConfiguration/help-issuer_fr.html
@@ -1,3 +1,3 @@
- Fortement recommandé. Le Token ID reçu doit avoir l'émetteur indiqué.
+ Obligatoire. Le Token ID reçu doit avoir l'émetteur indiqué.
diff --git a/src/test/java/org/jenkinsci/plugins/oic/ConfigurationAsCodeTest.java b/src/test/java/org/jenkinsci/plugins/oic/ConfigurationAsCodeTest.java
index 304d9269..d5de7619 100644
--- a/src/test/java/org/jenkinsci/plugins/oic/ConfigurationAsCodeTest.java
+++ b/src/test/java/org/jenkinsci/plugins/oic/ConfigurationAsCodeTest.java
@@ -43,9 +43,11 @@ public void testConfig() {
assertTrue(realm instanceof OicSecurityRealm);
OicSecurityRealm oicSecurityRealm = (OicSecurityRealm) realm;
- assertEquals(
- "http://localhost/authorize",
- oicSecurityRealm.getServerConfiguration().getAuthorizationServerUrl());
+ OicServerManualConfiguration serverConf =
+ (OicServerManualConfiguration) oicSecurityRealm.getServerConfiguration();
+
+ assertEquals("http://localhost/authorize", serverConf.getAuthorizationServerUrl());
+ assertEquals("http://localhost/", serverConf.getIssuer());
assertEquals("clientId", oicSecurityRealm.getClientId());
assertEquals("clientSecret", Secret.toString(oicSecurityRealm.getClientSecret()));
assertTrue(oicSecurityRealm.isDisableSslVerification());
@@ -59,18 +61,12 @@ public void testConfig() {
assertEquals("fullNameFieldName", oicSecurityRealm.getFullNameFieldName());
assertEquals("groupsFieldName", oicSecurityRealm.getGroupsFieldName());
assertTrue(oicSecurityRealm.isLogoutFromOpenidProvider());
- assertEquals("scopes", oicSecurityRealm.getServerConfiguration().getScopes());
- assertEquals(
- "http://localhost/token",
- oicSecurityRealm.getServerConfiguration().getTokenServerUrl());
- assertEquals(
- TokenAuthMethod.client_secret_post,
- oicSecurityRealm.getServerConfiguration().getTokenAuthMethod());
+ assertEquals("scopes", serverConf.getScopes());
+ assertEquals("http://localhost/token", serverConf.getTokenServerUrl());
+ assertEquals(TokenAuthMethod.client_secret_post, serverConf.getTokenAuthMethod());
assertEquals("userNameField", oicSecurityRealm.getUserNameField());
assertTrue(oicSecurityRealm.isRootURLFromRequest());
- assertEquals(
- "http://localhost/jwks",
- oicSecurityRealm.getServerConfiguration().getJwksServerUrl());
+ assertEquals("http://localhost/jwks", serverConf.getJwksServerUrl());
assertFalse(oicSecurityRealm.isDisableTokenVerification());
}
@@ -106,10 +102,11 @@ public void testMinimal() throws Exception {
assertTrue(realm instanceof OicSecurityRealm);
OicSecurityRealm oicSecurityRealm = (OicSecurityRealm) realm;
+ OicServerManualConfiguration serverConf =
+ (OicServerManualConfiguration) oicSecurityRealm.getServerConfiguration();
- assertEquals(
- "http://localhost/authorize",
- oicSecurityRealm.getServerConfiguration().getAuthorizationServerUrl());
+ assertEquals("http://localhost/authorize", serverConf.getAuthorizationServerUrl());
+ assertEquals("http://localhost/", serverConf.getIssuer());
assertEquals("clientId", oicSecurityRealm.getClientId());
assertEquals("clientSecret", Secret.toString(oicSecurityRealm.getClientSecret()));
assertFalse(oicSecurityRealm.isDisableSslVerification());
@@ -117,28 +114,26 @@ public void testMinimal() throws Exception {
assertFalse(oicSecurityRealm.isEscapeHatchEnabled());
assertNull(oicSecurityRealm.getFullNameFieldName());
assertNull(oicSecurityRealm.getGroupsFieldName());
- assertEquals("openid email", oicSecurityRealm.getServerConfiguration().getScopes());
- assertEquals(
- "http://localhost/token",
- oicSecurityRealm.getServerConfiguration().getTokenServerUrl());
- assertEquals(
- TokenAuthMethod.client_secret_post,
- oicSecurityRealm.getServerConfiguration().getTokenAuthMethod());
+ assertEquals("openid email", serverConf.getScopes());
+ assertEquals("http://localhost/token", serverConf.getTokenServerUrl());
+ assertEquals(TokenAuthMethod.client_secret_post, serverConf.getTokenAuthMethod());
assertEquals("sub", oicSecurityRealm.getUserNameField());
assertTrue(oicSecurityRealm.isLogoutFromOpenidProvider());
assertFalse(oicSecurityRealm.isRootURLFromRequest());
- assertEquals(null, oicSecurityRealm.getServerConfiguration().getJwksServerUrl());
+ assertEquals(null, serverConf.getJwksServerUrl());
assertFalse(oicSecurityRealm.isDisableTokenVerification());
}
@Rule(order = 0)
public final WellKnownMockRule wellKnownMockRule = new WellKnownMockRule(
"MOCK_PORT",
- "{\"authorization_endpoint\": \"http://localhost:%1$d/authorize\","
+ "{\"issuer\": \"http://localhost:%1$d/\","
+ + "\"authorization_endpoint\": \"http://localhost:%1$d/authorize\","
+ "\"token_endpoint\":\"http://localhost:%1$d/token\","
+ "\"userinfo_endpoint\":\"http://localhost:%1$d/user\","
+ "\"jwks_uri\":\"http://localhost:%1$d/jwks\","
- + "\"scopes_supported\": null,"
+ + "\"scopes_supported\": [\"openid\",\"email\"],"
+ + "\"subject_types_supported\": [\"public\"],"
+ "\"end_session_endpoint\":\"http://localhost:%1$d/logout\"}");
@Test
@@ -148,36 +143,26 @@ public void testMinimalWellKnown() throws Exception {
assertThat(realm, instanceOf(OicSecurityRealm.class));
OicSecurityRealm oicSecurityRealm = (OicSecurityRealm) realm;
+ assertThat(oicSecurityRealm.getServerConfiguration(), instanceOf(OicServerWellKnownConfiguration.class));
+ OicServerWellKnownConfiguration serverConf =
+ (OicServerWellKnownConfiguration) oicSecurityRealm.getServerConfiguration();
+
String urlBase = String.format("http://localhost:%d", wellKnownMockRule.port());
- assertThat(oicSecurityRealm.getServerConfiguration(), instanceOf(OicServerWellKnownConfiguration.class));
- assertEquals(
- urlBase + "/well.known",
- ((OicServerWellKnownConfiguration) oicSecurityRealm.getServerConfiguration())
- .getWellKnownOpenIDConfigurationUrl());
- assertEquals(
- urlBase + "/authorize",
- oicSecurityRealm.getServerConfiguration().getAuthorizationServerUrl());
- assertEquals(
- urlBase + "/token", oicSecurityRealm.getServerConfiguration().getTokenServerUrl());
- assertEquals(
- urlBase + "/jwks", oicSecurityRealm.getServerConfiguration().getJwksServerUrl());
- assertEquals("clientId", oicSecurityRealm.getClientId());
- assertEquals("clientSecret", Secret.toString(oicSecurityRealm.getClientSecret()));
assertFalse(oicSecurityRealm.isDisableSslVerification());
assertNull(oicSecurityRealm.getEmailFieldName());
assertFalse(oicSecurityRealm.isEscapeHatchEnabled());
assertNull(oicSecurityRealm.getFullNameFieldName());
assertNull(oicSecurityRealm.getGroupsFieldName());
- assertEquals("openid email", oicSecurityRealm.getServerConfiguration().getScopes());
- assertEquals(
- urlBase + "/token", oicSecurityRealm.getServerConfiguration().getTokenServerUrl());
- assertEquals(
- TokenAuthMethod.client_secret_post,
- oicSecurityRealm.getServerConfiguration().getTokenAuthMethod());
+
+ assertEquals("clientId", oicSecurityRealm.getClientId());
+ assertEquals("clientSecret", Secret.toString(oicSecurityRealm.getClientSecret()));
+
assertEquals("sub", oicSecurityRealm.getUserNameField());
assertTrue(oicSecurityRealm.isLogoutFromOpenidProvider());
assertFalse(oicSecurityRealm.isDisableTokenVerification());
+
+ assertEquals(urlBase + "/well.known", serverConf.getWellKnownOpenIDConfigurationUrl());
}
/** Class to setup WireMockRule for well known with stub and setting port in env variable
diff --git a/src/test/java/org/jenkinsci/plugins/oic/FieldTest.java b/src/test/java/org/jenkinsci/plugins/oic/FieldTest.java
index e75e116d..2beb61ee 100644
--- a/src/test/java/org/jenkinsci/plugins/oic/FieldTest.java
+++ b/src/test/java/org/jenkinsci/plugins/oic/FieldTest.java
@@ -2,8 +2,8 @@
import com.github.tomakehurst.wiremock.core.WireMockConfiguration;
import com.github.tomakehurst.wiremock.junit.WireMockRule;
-import com.google.api.client.json.GenericJson;
import java.util.HashMap;
+import java.util.Map;
import org.junit.Rule;
import org.junit.Test;
import org.jvnet.hudson.test.JenkinsRule;
@@ -24,7 +24,7 @@ public void testNestedLookup() throws Exception {
HashMap user = new HashMap<>();
user.put("id", "100");
- GenericJson payload = new GenericJson();
+ Map payload = new HashMap<>();
payload.put("email", "myemail@example.com");
payload.put("user", user);
payload.put("none", null);
@@ -44,7 +44,7 @@ public void testNormalLookupDueToDot() throws Exception {
HashMap user = new HashMap<>();
user.put("id", "100");
- GenericJson payload = new GenericJson();
+ Map payload = new HashMap<>();
payload.put("email", "myemail@example.com");
payload.put("user", user);
payload.put("none", null);
@@ -67,7 +67,7 @@ public void testFieldProcessing() throws Exception {
user.put("name", "john");
user.put("surname", "dow");
- GenericJson payload = new GenericJson();
+ Map payload = new HashMap<>();
payload.put("user", user);
TestRealm realm = new TestRealm(wireMockRule);
@@ -77,7 +77,7 @@ public void testFieldProcessing() throws Exception {
@Test
public void testInvalidFieldName() throws Exception {
- GenericJson payload = new GenericJson();
+ Map payload = new HashMap<>();
payload.put("user", "john");
TestRealm realm = new TestRealm(wireMockRule);
diff --git a/src/test/java/org/jenkinsci/plugins/oic/JenkinsAwareConnectionFactoryTest.java b/src/test/java/org/jenkinsci/plugins/oic/JenkinsAwareConnectionFactoryTest.java
deleted file mode 100644
index cc65805a..00000000
--- a/src/test/java/org/jenkinsci/plugins/oic/JenkinsAwareConnectionFactoryTest.java
+++ /dev/null
@@ -1,36 +0,0 @@
-package org.jenkinsci.plugins.oic;
-
-import hudson.ProxyConfiguration;
-import java.io.IOException;
-import java.net.HttpURLConnection;
-import java.net.URL;
-import jenkins.model.Jenkins;
-import org.junit.Rule;
-import org.junit.Test;
-import org.jvnet.hudson.test.JenkinsRule;
-
-import static org.junit.Assert.assertNotNull;
-
-public class JenkinsAwareConnectionFactoryTest {
-
- private JenkinsAwareConnectionFactory factory = new JenkinsAwareConnectionFactory();
-
- @Rule
- public JenkinsRule jenkinsRule = new JenkinsRule();
-
- @Test
- public void testOpenConnection_WithNullProxy() throws ClassCastException, IOException {
- Jenkins.getInstance().proxy = null;
- URL url = new URL("http://localhost");
- HttpURLConnection conn = factory.openConnection(url);
- assertNotNull(conn);
- }
-
- @Test
- public void testOpenConnection_WithProxy() throws ClassCastException, IOException {
- Jenkins.getInstance().proxy = new ProxyConfiguration("someHost", 8000);
- URL url = new URL("http://localhost");
- HttpURLConnection conn = factory.openConnection(url);
- assertNotNull(conn);
- }
-}
diff --git a/src/test/java/org/jenkinsci/plugins/oic/OicJsonWebTokenVerifierTest.java b/src/test/java/org/jenkinsci/plugins/oic/OicJsonWebTokenVerifierTest.java
deleted file mode 100644
index 442ec45b..00000000
--- a/src/test/java/org/jenkinsci/plugins/oic/OicJsonWebTokenVerifierTest.java
+++ /dev/null
@@ -1,183 +0,0 @@
-/*
- * The MIT License
- *
- * Copyright (c) 2024 JenkinsCI oic-auth-plugin developers
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- */
-package org.jenkinsci.plugins.oic;
-
-import com.github.tomakehurst.wiremock.core.WireMockConfiguration;
-import com.github.tomakehurst.wiremock.junit.WireMockRule;
-import com.google.api.client.auth.openidconnect.IdToken;
-import com.google.api.client.json.JsonFactory;
-import com.google.api.client.json.gson.GsonFactory;
-import com.google.api.client.json.webtoken.JsonWebSignature;
-import com.google.api.client.util.Base64;
-import com.google.api.client.util.Clock;
-import com.google.api.client.util.SecurityUtils;
-import com.google.api.client.util.StringUtils;
-import java.security.KeyPair;
-import java.security.KeyPairGenerator;
-import java.security.NoSuchAlgorithmException;
-import java.security.PrivateKey;
-import java.security.interfaces.RSAPublicKey;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
-import org.junit.Rule;
-import org.junit.Test;
-
-import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
-import static com.github.tomakehurst.wiremock.client.WireMock.get;
-import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-public class OicJsonWebTokenVerifierTest {
-
- @Rule
- public WireMockRule wireMockRule = new WireMockRule(new WireMockConfiguration().dynamicPort(), true);
-
- KeyPair keyPair = createKeyPair();
-
- @Test
- public void testVanillaCaseShouldbeSuccessfulAndVerifySignature() throws Exception {
- wireMockRule.resetAll();
- IdToken idtoken = createIdToken(keyPair.getPrivate(), new HashMap<>());
- OicJsonWebTokenVerifier verifier = new OicJsonWebTokenVerifier(
- "http://localhost:" + wireMockRule.port() + "/jwks", new OicJsonWebTokenVerifier.Builder());
- assertTrue(verifier.isJwksServerUrlAvailable());
-
- wireMockRule.stubFor(get(urlPathEqualTo("/jwks"))
- .willReturn(aResponse()
- .withHeader("Content-Type", "application/json")
- .withBody("{\"keys\":[{" + encodePublicKey(keyPair) + ",\"alg\":\"RS256\""
- + ",\"use\":\"sig\",\"kid\":\"jwks_key_id\""
- + "}]}")));
-
- assertTrue(verifier.verifyIdToken(idtoken));
- assertTrue(verifier.isJwksServerUrlAvailable());
- }
-
- @Test
- public void tesNoJWKSURIShouldBeSuccessfulAndNeverVerifySignature() throws Exception {
- IdToken idtoken = createIdToken(keyPair.getPrivate(), new HashMap<>());
- OicJsonWebTokenVerifier verifier = new OicJsonWebTokenVerifier(null, new OicJsonWebTokenVerifier.Builder());
- assertFalse(verifier.isJwksServerUrlAvailable());
-
- assertTrue(verifier.verifyIdToken(idtoken));
- }
-
- @Test
- public void testCannotGetJWKSURIShouldbeSuccessfulAndDisableSignature() throws Exception {
- wireMockRule.resetAll();
- IdToken idtoken = createIdToken(keyPair.getPrivate(), new HashMap<>());
- OicJsonWebTokenVerifier verifier = new OicJsonWebTokenVerifier(
- "http://localhost:" + wireMockRule.port() + "/jwks", new OicJsonWebTokenVerifier.Builder());
- assertTrue(verifier.isJwksServerUrlAvailable());
-
- wireMockRule.stubFor(get(urlPathEqualTo("/jwks")).willReturn(aResponse().withStatus(404)));
-
- assertTrue(verifier.verifyIdToken(idtoken));
- assertFalse(verifier.isJwksServerUrlAvailable());
- }
-
- @Test
- public void testMissingAlgShouldbeSuccessfulAndDisableSignature() throws Exception {
- wireMockRule.resetAll();
- IdToken idtoken = createIdToken(keyPair.getPrivate(), new HashMap<>());
- OicJsonWebTokenVerifier verifier = new OicJsonWebTokenVerifier(
- "http://localhost:" + wireMockRule.port() + "/jwks", new OicJsonWebTokenVerifier.Builder());
- assertTrue(verifier.isJwksServerUrlAvailable());
-
- wireMockRule.stubFor(get(urlPathEqualTo("/jwks"))
- .willReturn(aResponse()
- .withHeader("Content-Type", "application/json")
- .withBody("{\"keys\":[{" + encodePublicKey(keyPair) + ",\"use\":\"sig\",\"kid\":\"jwks_key_id\""
- + "}]}")));
-
- assertTrue(verifier.verifyIdToken(idtoken));
- assertFalse(verifier.isJwksServerUrlAvailable());
- }
-
- @Test
- public void testUnknownAlgShouldbeSuccessfulAndDisableSignature() throws Exception {
- wireMockRule.resetAll();
- IdToken idtoken = createIdToken(keyPair.getPrivate(), new HashMap<>());
- OicJsonWebTokenVerifier verifier = new OicJsonWebTokenVerifier(
- "http://localhost:" + wireMockRule.port() + "/jwks", new OicJsonWebTokenVerifier.Builder());
- assertTrue(verifier.isJwksServerUrlAvailable());
-
- wireMockRule.stubFor(get(urlPathEqualTo("/jwks"))
- .willReturn(aResponse()
- .withHeader("Content-Type", "application/json")
- .withBody("{\"keys\":[{" + encodePublicKey(keyPair) + ",\"alg\":\"RSA-OAEP\""
- + ",\"use\":\"sig\",\"kid\":\"jwks_key_id\""
- + "}]}")));
-
- assertTrue(verifier.verifyIdToken(idtoken));
- assertFalse(verifier.isJwksServerUrlAvailable());
- }
-
- private static KeyPair createKeyPair() {
- try {
- KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
- keyGen.initialize(2048);
- return keyGen.generateKeyPair();
- } catch (NoSuchAlgorithmException e) {
- /* should not happen */
- }
- return null;
- }
-
- private IdToken createIdToken(PrivateKey privateKey, Map keyValues) throws Exception {
- JsonWebSignature.Header header =
- new JsonWebSignature.Header().setAlgorithm("RS256").setKeyId("jwks_key_id");
- long now = (long) (Clock.SYSTEM.currentTimeMillis() / 1000);
- IdToken.Payload payload = new IdToken.Payload()
- .setExpirationTimeSeconds(now + 60L)
- .setIssuedAtTimeSeconds(now)
- .setIssuer("issuer")
- .setSubject("sub")
- .setAudience(Collections.singletonList("clientId"))
- .setNonce("nonce");
- for (Map.Entry keyValue : keyValues.entrySet()) {
- payload.set(keyValue.getKey(), keyValue.getValue());
- }
-
- JsonFactory jsonFactory = GsonFactory.getDefaultInstance();
- String content = Base64.encodeBase64URLSafeString(jsonFactory.toByteArray(header))
- + "."
- + Base64.encodeBase64URLSafeString(jsonFactory.toByteArray(payload));
- byte[] contentBytes = StringUtils.getBytesUtf8(content);
- byte[] signature =
- SecurityUtils.sign(SecurityUtils.getSha256WithRsaSignatureAlgorithm(), privateKey, contentBytes);
- return new IdToken(header, payload, signature, contentBytes);
- }
-
- /** Generate JWKS entry with public key of keyPair */
- String encodePublicKey(KeyPair keyPair) {
- final RSAPublicKey rsaPKey = (RSAPublicKey) (keyPair.getPublic());
- return "\"n\":\"" + Base64.encodeBase64String(rsaPKey.getModulus().toByteArray())
- + "\",\"e\":\""
- + Base64.encodeBase64String(rsaPKey.getPublicExponent().toByteArray())
- + "\",\"kty\":\"RSA\"";
- }
-}
diff --git a/src/test/java/org/jenkinsci/plugins/oic/OicServerWellKnownConfigurationTest.java b/src/test/java/org/jenkinsci/plugins/oic/OicServerWellKnownConfigurationTest.java
index 0a27fb31..3d1e103c 100644
--- a/src/test/java/org/jenkinsci/plugins/oic/OicServerWellKnownConfigurationTest.java
+++ b/src/test/java/org/jenkinsci/plugins/oic/OicServerWellKnownConfigurationTest.java
@@ -28,7 +28,8 @@ public class OicServerWellKnownConfigurationTest {
public static JenkinsRule jenkinsRule = new JenkinsRule();
@Rule
- public WireMockRule wireMockRule = new WireMockRule(new WireMockConfiguration().dynamicPort(), true);
+ public WireMockRule wireMockRule =
+ new WireMockRule(new WireMockConfiguration().dynamicPort().dynamicHttpsPort(), true);
@Test
public void doCheckWellKnownOpenIDConfigurationUrl() throws IOException {
@@ -43,17 +44,36 @@ public void doCheckWellKnownOpenIDConfigurationUrl() throws IOException {
allOf(hasKind(FormValidation.Kind.ERROR), withMessage("Not a valid url.")));
assertThat(
descriptor.doCheckWellKnownOpenIDConfigurationUrl(
- wireMockRule.url("/.well-known/openid-configuration"), false),
+ "http://localhost:" + wireMockRule.port() + ("/.well-known/openid-configuration"), false),
hasKind(FormValidation.Kind.OK));
+ assertThat(
+ descriptor.doCheckWellKnownOpenIDConfigurationUrl(
+ wireMockRule.url("/.well-known/openid-configuration"), true), // disable TLS
+ hasKind(FormValidation.Kind.OK));
+ // TLS error.
+ assertThat(
+ descriptor.doCheckWellKnownOpenIDConfigurationUrl(
+ wireMockRule.url("/.well-known/openid-configuration"), false),
+ allOf(
+ hasKind(FormValidation.Kind.ERROR),
+ withMessageContaining("The server presented an invalid or incorrect TLS certificate")));
assertThat(
descriptor.doCheckWellKnownOpenIDConfigurationUrl(
jenkinsRule.jenkins.getRootUrl() + "/api/json", false),
allOf(
- hasKind(FormValidation.Kind.WARNING),
- withMessage("URL does not seem to describe OpenID Connect endpoints")));
+ hasKind(FormValidation.Kind.ERROR),
+ withMessageContaining("URL does not seem to describe OpenID Connect endpoints")));
+
assertThat(
descriptor.doCheckWellKnownOpenIDConfigurationUrl(jenkinsRule.jenkins.getRootUrl() + "/api/xml", false),
+ allOf(
+ hasKind(FormValidation.Kind.ERROR),
+ withMessageContaining("URL does not seem to describe OpenID Connect endpoints")));
+
+ assertThat(
+ descriptor.doCheckWellKnownOpenIDConfigurationUrl(
+ jenkinsRule.jenkins.getRootUrl() + "/does/not/exist", false),
allOf(
hasKind(FormValidation.Kind.ERROR),
withMessageContaining("Error when retrieving well-known config")));
@@ -79,6 +99,7 @@ private void configureWireMockWellKnownEndpoint() {
String authUrl = "http://localhost:" + wireMockRule.port() + "/authorization";
String tokenUrl = "http://localhost:" + wireMockRule.port() + "/token";
String userInfoUrl = "http://localhost:" + wireMockRule.port() + "/userinfo";
+ String issuer = "http://localhost:" + wireMockRule.port() + "/";
String jwksUrl = "null";
String endSessionUrl = "null";
@@ -86,10 +107,11 @@ private void configureWireMockWellKnownEndpoint() {
.willReturn(aResponse()
.withHeader("Content-Type", "text/html; charset=utf-8")
.withBody(String.format(
- "{\"authorization_endpoint\": \"%s\", \"token_endpoint\":\"%s\", "
+ "{\"authorization_endpoint\": \"%s\", \"issuer\" :\"%s\", \"token_endpoint\":\"%s\", "
+ "\"userinfo_endpoint\":\"%s\",\"jwks_uri\":\"%s\", \"scopes_supported\": null, "
+ + "\"subject_types_supported\": [ \"public\" ], "
+ "\"end_session_endpoint\":\"%s\"}",
- authUrl, tokenUrl, userInfoUrl, jwksUrl, endSessionUrl))));
+ authUrl, issuer, tokenUrl, userInfoUrl, jwksUrl, endSessionUrl))));
}
private static DescriptorImpl getDescriptor() {
diff --git a/src/test/java/org/jenkinsci/plugins/oic/OicSessionTest.java b/src/test/java/org/jenkinsci/plugins/oic/OicSessionTest.java
deleted file mode 100644
index 21c91ca8..00000000
--- a/src/test/java/org/jenkinsci/plugins/oic/OicSessionTest.java
+++ /dev/null
@@ -1,77 +0,0 @@
-package org.jenkinsci.plugins.oic;
-
-import com.google.api.client.auth.oauth2.AuthorizationCodeFlow;
-import java.io.IOException;
-import java.util.SortedMap;
-import java.util.TreeMap;
-import jenkins.model.Jenkins;
-import org.junit.Rule;
-import org.junit.Test;
-import org.jvnet.hudson.test.JenkinsRule;
-import org.jvnet.hudson.test.WithoutJenkins;
-import org.kohsuke.stapler.HttpResponse;
-import org.kohsuke.stapler.StaplerRequest;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-public class OicSessionTest {
-
- @Rule
- public JenkinsRule jenkinsRule = new JenkinsRule();
-
- private OicSession session;
-
- private static final String from = "fromAddy";
-
- public void init() throws IOException {
- TestRealm realm = new TestRealm.Builder("http://localhost/")
- .WithMinimalDefaults().WithScopes("openid").build();
-
- session = new OicSession(from, buildOAuthRedirectUrl()) {
- @Override
- public HttpResponse onSuccess(String authorizationCode, AuthorizationCodeFlow flow) {
- return null;
- }
- };
- }
-
- private String buildOAuthRedirectUrl() throws NullPointerException {
- String rootUrl = Jenkins.get().getRootUrl();
- if (rootUrl == null) {
- throw new NullPointerException("Jenkins root url should not be null");
- } else {
- return rootUrl + "securityRealm/finishLogin";
- }
- }
-
- @Test
- public void getFrom() throws Exception {
- init();
- assertEquals(from, session.getFrom());
- }
-
- @Test
- public void getState() throws Exception {
- init();
- assertNotEquals("", session.getState());
- }
-
- @Test
- @WithoutJenkins
- public void testFormToQueryParameters() {
- StaplerRequest sr = mock(StaplerRequest.class);
- when(sr.getRequestURL())
- .thenReturn(new StringBuffer("http://domain.invalid/jenkins/securityRealm/finishLogin"));
- SortedMap parametersMap = new TreeMap<>();
- parametersMap.put("param1", new String[] {"p1k1"});
- parametersMap.put("param2", new String[] {"p2k1", "p2k2"});
- when(sr.getParameterMap()).thenReturn(parametersMap);
- String converted = OicSession.convertFormToQueryParameters(sr);
- assertEquals(
- "http://domain.invalid/jenkins/securityRealm/finishLogin?param1=p1k1¶m2=p2k1¶m2=p2k2",
- converted);
- }
-}
diff --git a/src/test/java/org/jenkinsci/plugins/oic/OicTokenResponseTest.java b/src/test/java/org/jenkinsci/plugins/oic/OicTokenResponseTest.java
deleted file mode 100644
index cc8b7bdb..00000000
--- a/src/test/java/org/jenkinsci/plugins/oic/OicTokenResponseTest.java
+++ /dev/null
@@ -1,93 +0,0 @@
-package org.jenkinsci.plugins.oic;
-
-import com.google.api.client.json.gson.GsonFactory;
-import java.io.IOException;
-import org.junit.Test;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-/**
- * We'd like to be more permissive by allowing:
- * - both long literals and Strigns containing to accepted
- */
-public class OicTokenResponseTest {
-
- private static final String JSON_WITH_LONG_AS_STRING = "{\"access_token\":\"2YotnFZFEjr1zCsicMWpAA\","
- + "\"token_type\":\"example\",\"expires_in\":\"3600\","
- + "\"refresh_token\":\"tGzv3JOkF0XG5Qx2TlKWIA\","
- + "\"example_parameter\":\"example_value\"}";
-
- private static final String JSON_WITH_LONG_LITERAL = "{\"access_token\":\"2YotnFZFEjr1zCsicMWpAA\","
- + "\"token_type\":\"example\",\"expires_in\":3600,"
- + "\"refresh_token\":\"tGzv3JOkF0XG5Qx2TlKWIA\","
- + "\"example_parameter\":\"example_value\"}";
-
- private static final String JSON_WITH_ABSENT = "{\"access_token\":\"2YotnFZFEjr1zCsicMWpAA\","
- + "\"token_type\":\"example\","
- + "\"refresh_token\":\"tGzv3JOkF0XG5Qx2TlKWIA\","
- + "\"example_parameter\":\"example_value\"}";
-
- @Test
- public void parseLongLiteral() throws IOException {
- OicTokenResponse response =
- GsonFactory.getDefaultInstance().fromString(JSON_WITH_LONG_LITERAL, OicTokenResponse.class);
- assertEquals("2YotnFZFEjr1zCsicMWpAA", response.getAccessToken());
- assertEquals("example", response.getTokenType());
- assertEquals(3600L, response.getExpiresInSeconds().longValue());
- assertEquals("tGzv3JOkF0XG5Qx2TlKWIA", response.getRefreshToken());
- assertEquals("example_value", response.get("example_parameter"));
- }
-
- @Test
- public void parseStringWithLong() throws IOException {
- OicTokenResponse response =
- GsonFactory.getDefaultInstance().fromString(JSON_WITH_LONG_AS_STRING, OicTokenResponse.class);
- assertEquals("2YotnFZFEjr1zCsicMWpAA", response.getAccessToken());
- assertEquals("example", response.getTokenType());
- assertEquals(3600L, response.getExpiresInSeconds().longValue());
- assertEquals("tGzv3JOkF0XG5Qx2TlKWIA", response.getRefreshToken());
- assertEquals("example_value", response.get("example_parameter"));
- }
-
- @Test
- public void testSetters() throws IOException {
- OicTokenResponse response = new OicTokenResponse();
- assertEquals(response, response.setAccessToken("2YotnFZFEjr1zCsicMWpAA"));
- assertEquals(response, response.setTokenType("example"));
- assertEquals(response, response.setExpiresInSeconds(3600L));
- assertEquals(response, response.setRefreshToken("tGzv3JOkF0XG5Qx2TlKWIA"));
- assertEquals(response, response.set("example_parameter", "example_value"));
- assertEquals(response, response.setScope("myScope"));
- assertEquals("2YotnFZFEjr1zCsicMWpAA", response.getAccessToken());
- assertEquals("example", response.getTokenType());
- assertEquals(3600L, response.getExpiresInSeconds().longValue());
- assertEquals("tGzv3JOkF0XG5Qx2TlKWIA", response.getRefreshToken());
- assertEquals("example_value", response.get("example_parameter"));
- assertEquals("myScope", response.getScope());
-
- OicTokenResponse cloned = response.clone();
- assertEquals(response.getAccessToken(), cloned.getAccessToken());
- assertEquals(response.getTokenType(), cloned.getTokenType());
- assertEquals(
- response.getExpiresInSeconds().longValue(),
- cloned.getExpiresInSeconds().longValue());
- assertEquals(response.getRefreshToken(), cloned.getRefreshToken());
- assertEquals(response.get("example_parameter"), cloned.get("example_parameter"));
- assertEquals(response.getScope(), cloned.getScope());
-
- assertTrue(response.equals(cloned));
- assertTrue(response.hashCode() == cloned.hashCode());
- }
-
- @Test
- public void parseAbsent() throws IOException {
- OicTokenResponse response =
- GsonFactory.getDefaultInstance().fromString(JSON_WITH_ABSENT, OicTokenResponse.class);
- assertEquals("2YotnFZFEjr1zCsicMWpAA", response.getAccessToken());
- assertEquals("example", response.getTokenType());
- assertEquals(null, response.getExpiresInSeconds());
- assertEquals("tGzv3JOkF0XG5Qx2TlKWIA", response.getRefreshToken());
- assertEquals("example_value", response.get("example_parameter"));
- }
-}
diff --git a/src/test/java/org/jenkinsci/plugins/oic/PluginTest.java b/src/test/java/org/jenkinsci/plugins/oic/PluginTest.java
index 94348b6c..8c2a2d00 100644
--- a/src/test/java/org/jenkinsci/plugins/oic/PluginTest.java
+++ b/src/test/java/org/jenkinsci/plugins/oic/PluginTest.java
@@ -1,15 +1,17 @@
package org.jenkinsci.plugins.oic;
+import com.github.tomakehurst.wiremock.common.ConsoleNotifier;
import com.github.tomakehurst.wiremock.core.WireMockConfiguration;
import com.github.tomakehurst.wiremock.junit.WireMockRule;
import com.google.api.client.auth.openidconnect.IdToken;
import com.google.api.client.json.gson.GsonFactory;
import com.google.api.client.json.webtoken.JsonWebSignature;
import com.google.api.client.json.webtoken.JsonWebToken;
-import com.google.api.client.util.Clock;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonNull;
+import com.nimbusds.oauth2.sdk.GrantType;
+import com.nimbusds.oauth2.sdk.Scope;
import edu.umd.cs.findbugs.annotations.CheckForNull;
import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.Nullable;
@@ -28,6 +30,7 @@
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.interfaces.RSAPublicKey;
+import java.time.Clock;
import java.util.Arrays;
import java.util.Base64;
import java.util.Collections;
@@ -47,6 +50,7 @@
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.DisableOnDebug;
import org.jvnet.hudson.test.Issue;
import org.jvnet.hudson.test.JenkinsRule;
import org.jvnet.hudson.test.Url;
@@ -83,9 +87,10 @@
import static org.junit.Assert.assertTrue;
/**
- * goes through a login scenario, the openid provider is mocked and always returns state. We aren't checking
- * if if openid connect or if the openid connect implementation works. Rather we are only
- * checking if the jenkins interaction works and if the plugin code works.
+ * goes through a login scenario, the openid provider is mocked and always
+ * returns state. We aren't checking if if openid connect or if the openid
+ * connect implementation works. Rather we are only checking if the jenkins
+ * interaction works and if the plugin code works.
*/
@Url("https://jenkins.io/blog/2018/01/13/jep-200/")
public class PluginTest {
@@ -98,7 +103,11 @@ public class PluginTest {
List.of(Map.of("id", "id1", "name", "group1"), Map.of("id", "id2", "name", "group2"));
@Rule
- public WireMockRule wireMockRule = new WireMockRule(new WireMockConfiguration().dynamicPort(), true);
+ public WireMockRule wireMockRule = new WireMockRule(
+ new WireMockConfiguration()
+ .dynamicPort()
+ .notifier(new ConsoleNotifier(new DisableOnDebug(null).isDebugging())),
+ true);
@Rule
public JenkinsRule jenkinsRule = new JenkinsRule();
@@ -110,6 +119,9 @@ public class PluginTest {
public void setUp() {
jenkins = jenkinsRule.getInstance();
webClient = jenkinsRule.createWebClient();
+ if (new DisableOnDebug(null).isDebugging()) {
+ webClient.getOptions().setTimeout(0);
+ }
}
@Test
@@ -129,8 +141,7 @@ public void testLoginWithDefaults() throws Exception {
verify(postRequestedFor(urlPathEqualTo("/token")).withRequestBody(notMatching(".*&scope=.*")));
webClient.executeOnServer(() -> {
HttpSession session = Stapler.getCurrentRequest().getSession();
- assertNull(OicSession.getCurrent());
- assertNotNull(OicSecurityRealm.getStateAttribute(session));
+ assertNotNull(((OicSecurityRealm) Jenkins.get().getSecurityRealm()).getStateAttribute(session));
return null;
});
}
@@ -167,13 +178,17 @@ private void assertAnonymous() {
private void mockAuthorizationRedirectsToFinishLogin() {
wireMockRule.stubFor(get(urlPathEqualTo("/authorization"))
.willReturn(aResponse()
+ .withTransformers("response-template")
.withStatus(302)
.withHeader("Content-Type", "text/html; charset=utf-8")
.withHeader(
- "Location", jenkins.getRootUrl() + "securityRealm/finishLogin?state=state&code=code")));
+ "Location",
+ jenkins.getRootUrl()
+ + "securityRealm/finishLogin?state={{request.query.state}}&code=code")));
}
@Test
+ @Ignore("there is no configuration option for this and the spec does not have scopes in a token endpoint")
public void testLoginWithScopesInTokenRequest() throws Exception {
mockAuthorizationRedirectsToFinishLogin();
mockTokenReturnsIdTokenWithGroup();
@@ -198,13 +213,13 @@ public void testLoginWithPkceEnabled() throws Exception {
verify(postRequestedFor(urlPathEqualTo("/token")).withRequestBody(matching(".*&code_verifier=[^&]+.*")));
// check PKCE
- // - get codeChallenge
+ // - get codeChallenge
final String codeChallenge = findAll(getRequestedFor(urlPathEqualTo("/authorization")))
.get(0)
.queryParameter("code_challenge")
.values()
.get(0);
- // - get verifierCode
+ // - get verifierCode
Matcher m = Pattern.compile(".*&code_verifier=([^&]+).*")
.matcher(findAll(postRequestedFor(urlPathEqualTo("/token")))
.get(0)
@@ -212,7 +227,7 @@ public void testLoginWithPkceEnabled() throws Exception {
assertTrue(m.find());
final String codeVerifier = m.group(1);
- // - hash verifierCode
+ // - hash verifierCode
byte[] bytes = codeVerifier.getBytes();
MessageDigest md = MessageDigest.getInstance("SHA-256");
md.update(bytes, 0, bytes.length);
@@ -307,18 +322,25 @@ public void testConfigurationWithAutoConfiguration_withScopeOverride() throws Ex
jenkins.setSecurityRealm(oicsr);
assertEquals(
"All scopes of WellKnown should be used",
- "openid profile scope1 scope2 scope3",
- oicsr.getServerConfiguration().getScopes());
+ new Scope("openid", "profile", "scope1", "scope2", "scope3"),
+ oicsr.getServerConfiguration().toProviderMetadata().getScopes());
OicServerWellKnownConfiguration serverConfig = (OicServerWellKnownConfiguration) oicsr.getServerConfiguration();
serverConfig.setScopesOverride("openid profile scope2 other");
- assertEquals("scopes should be completely overridden", "openid profile scope2 other", serverConfig.getScopes());
+ serverConfig.invalidateProviderMetadata(); // XXX should not be used as it is not a normal code flow, rather the
+ // code should create a new ServerConfig
+ assertEquals(
+ "scopes should be completely overridden",
+ new Scope("openid", "profile", "scope2", "other"),
+ serverConfig.toProviderMetadata().getScopes());
+ serverConfig.invalidateProviderMetadata(); // XXX should not be used as it is not a normal code flow, rather the
+ // code should create a new ServerConfig
serverConfig.setScopesOverride("");
assertEquals(
"All scopes of WellKnown should be used",
- "openid profile scope1 scope2 scope3",
- oicsr.getServerConfiguration().getScopes());
+ new Scope("openid", "profile", "scope1", "scope2", "scope3"),
+ serverConfig.toProviderMetadata().getScopes());
}
@Test
@@ -329,7 +351,10 @@ public void testConfigurationWithAutoConfiguration_withRefreshToken() throws Exc
jenkins.setSecurityRealm(oicsr);
assertTrue(
"Refresh token should be enabled",
- oicsr.getServerConfiguration().isUseRefreshTokens());
+ oicsr.getServerConfiguration()
+ .toProviderMetadata()
+ .getGrantTypes()
+ .contains(GrantType.REFRESH_TOKEN));
}
@Test
@@ -522,7 +547,6 @@ private void expire() throws Exception {
60L,
1L,
60L));
-
return null;
});
}
@@ -599,6 +623,7 @@ public void testLoginWithJWTSignature() throws Exception {
jenkins.setSecurityRealm(new TestRealm.Builder(wireMockRule)
.WithUserInfoServerUrl("http://localhost:" + wireMockRule.port() + "/userinfo")
.WithJwksServerUrl("http://localhost:" + wireMockRule.port() + "/jwks")
+ .WithDisableTokenValidation(false)
.build());
assertAnonymous();
@@ -840,24 +865,38 @@ private void configureWellKnown(
@CheckForNull String endSessionUrl,
@CheckForNull List scopesSupported,
@CheckForNull String... grantTypesSupported) {
+ // scopes_supported may not be null, but is not required to be present.
+ // if present it must minimally be "openid"
+ // Claims with zero elements MUST be omitted from the response.
+
+ Map values = new HashMap<>();
+ values.putAll(Map.of(
+ "authorization_endpoint",
+ "http://localhost:" + wireMockRule.port() + "/authorization",
+ "token_endpoint",
+ "http://localhost:" + wireMockRule.port() + "/token",
+ "userinfo_endpoint",
+ "http://localhost:" + wireMockRule.port() + "/userinfo",
+ "jwks_uri",
+ "http://localhost:" + wireMockRule.port() + "/jwks",
+ "issuer",
+ TestRealm.ISSUER,
+ "subject_types_supported",
+ List.of("public")));
+ if (scopesSupported != null && !scopesSupported.isEmpty()) {
+ values.put("scopes_supported", scopesSupported);
+ }
+ if (endSessionUrl != null) {
+ values.put("end_session_endpoint", endSessionUrl);
+ }
+ if (grantTypesSupported.length != 0) {
+ values.put("grant_types_supported", grantTypesSupported);
+ }
+
wireMockRule.stubFor(get(urlPathEqualTo("/well.known"))
.willReturn(aResponse()
.withHeader("Content-Type", "text/html; charset=utf-8")
- .withBody(toJson(Map.of(
- "authorization_endpoint",
- "http://localhost:" + wireMockRule.port() + "/authorization",
- "token_endpoint",
- "http://localhost:" + wireMockRule.port() + "/token",
- "userinfo_endpoint",
- "http://localhost:" + wireMockRule.port() + "/userinfo",
- "jwks_uri",
- JsonNull.INSTANCE,
- "scopes_supported",
- scopesSupported == null ? JsonNull.INSTANCE : scopesSupported,
- "end_session_endpoint",
- endSessionUrl == null ? JsonNull.INSTANCE : endSessionUrl,
- "grant_types_supported",
- grantTypesSupported)))));
+ .withBody(toJson(values))));
}
@Test
@@ -915,13 +954,13 @@ private KeyPair createKeyPair() throws NoSuchAlgorithmException {
private String createIdToken(PrivateKey privateKey, Map keyValues) throws Exception {
JsonWebSignature.Header header =
new JsonWebSignature.Header().setAlgorithm("RS256").setKeyId("jwks_key_id");
- long now = Clock.SYSTEM.currentTimeMillis() / 1000;
+ long now = Clock.systemUTC().millis() / 1000;
IdToken.Payload payload = new IdToken.Payload()
.setExpirationTimeSeconds(now + 60L)
.setIssuedAtTimeSeconds(now)
- .setIssuer("issuer")
+ .setIssuer(TestRealm.ISSUER)
.setSubject(TEST_USER_USERNAME)
- .setAudience(Collections.singletonList("clientId"))
+ .setAudience(Collections.singletonList(TestRealm.CLIENT_ID))
.setNonce("nonce");
for (Map.Entry keyValue : keyValues.entrySet()) {
payload.set(keyValue.getKey(), keyValue.getValue());
@@ -964,7 +1003,7 @@ public void testLoginWithUnreadableIdTokenShouldBeRefused() throws Exception {
mockTokenReturnsIdToken("This is not an IdToken");
jenkins.setSecurityRealm(new TestRealm(wireMockRule, null, null, null));
assertAnonymous();
- webClient.assertFails(jenkins.getSecurityRealm().getLoginUrl(), 403);
+ webClient.assertFails(jenkins.getSecurityRealm().getLoginUrl(), 500);
}
@Test
@@ -993,38 +1032,12 @@ public void loginWithCheckTokenFailure() throws Exception {
public void loginWithIncorrectIssuerFails() throws Exception {
mockAuthorizationRedirectsToFinishLogin();
mockTokenReturnsIdTokenWithGroup();
- jenkins.setSecurityRealm(
- new TestRealm.Builder(wireMockRule).WithIssuer("another_issuer").build());
- assertAnonymous();
- webClient.setThrowExceptionOnFailingStatusCode(false);
- browseLoginPage();
- assertAnonymous();
- }
-
- @Test
- @Issue("SECURITY-3441")
- public void loginWithoutIssuerSetSucceeds() throws Exception {
- mockAuthorizationRedirectsToFinishLogin();
- mockTokenReturnsIdTokenWithGroup();
- jenkins.setSecurityRealm(
- new TestRealm.Builder(wireMockRule).WithIssuer(null).build());
+ jenkins.setSecurityRealm(new TestRealm.Builder(wireMockRule)
+ .WithIssuer("another_issuer").WithDisableTokenValidation(false).build());
assertAnonymous();
webClient.setThrowExceptionOnFailingStatusCode(false);
browseLoginPage();
- assertTestUser();
- }
-
- @Test
- @Issue("SECURITY-3441")
- public void loginWithEmptyIssuerSetSucceeds() throws Exception {
- mockAuthorizationRedirectsToFinishLogin();
- mockTokenReturnsIdTokenWithGroup();
- jenkins.setSecurityRealm(
- new TestRealm.Builder(wireMockRule).WithIssuer(null).build());
assertAnonymous();
- webClient.setThrowExceptionOnFailingStatusCode(false);
- browseLoginPage();
- assertTestUser();
}
@Test
@@ -1033,7 +1046,9 @@ public void loginWithIncorrectAudienceFails() throws Exception {
mockAuthorizationRedirectsToFinishLogin();
mockTokenReturnsIdTokenWithGroup();
jenkins.setSecurityRealm(new TestRealm.Builder(wireMockRule)
- .WithClient("another_client_id", "client_secret").build());
+ .WithClient("another_client_id", "client_secret")
+ .WithDisableTokenValidation(false)
+ .build());
assertAnonymous();
webClient.setThrowExceptionOnFailingStatusCode(false);
browseLoginPage();
@@ -1151,9 +1166,7 @@ private Authentication getAuthentication() {
private static @NonNull Map setUpKeyValuesNested() {
return Map.of(
"nested",
- Map.of(
- EMAIL_FIELD, TEST_USER_EMAIL_ADDRESS,
- GROUPS_FIELD, TEST_USER_GROUPS),
+ Map.of(EMAIL_FIELD, TEST_USER_EMAIL_ADDRESS, GROUPS_FIELD, TEST_USER_GROUPS),
FULL_NAME_FIELD,
TEST_USER_FULL_NAME);
}
@@ -1231,7 +1244,7 @@ private void mockTokenReturnsIdToken(
@CheckForNull String idToken, @CheckForNull Consumer