From 66f2942eea748f83a1a10048244a7905df5e0cf6 Mon Sep 17 00:00:00 2001 From: Francisco Javier Fernandez Gonzalez Date: Fri, 30 Aug 2024 12:22:55 +0200 Subject: [PATCH 01/16] Add test for oic-auth plugin --- pom.xml | 32 +++ .../acceptance/po/OicAuthSecurityRealm.java | 35 +++ .../utils/keycloack/KeycloakUtils.java | 95 ++++++++ src/test/java/plugins/OicAuthPluginTest.java | 227 ++++++++++++++++++ 4 files changed, 389 insertions(+) create mode 100644 src/main/java/org/jenkinsci/test/acceptance/po/OicAuthSecurityRealm.java create mode 100644 src/main/java/org/jenkinsci/test/acceptance/utils/keycloack/KeycloakUtils.java create mode 100644 src/test/java/plugins/OicAuthPluginTest.java diff --git a/pom.xml b/pom.xml index fb90bf63aa..d950387ea8 100644 --- a/pom.xml +++ b/pom.xml @@ -314,6 +314,28 @@ 2.1.3 test + + + com.github.dasniko + testcontainers-keycloak + 3.4.0 + test + + + jakarta.annotation + jakarta.annotation-api + 2.1.1 + test + + + jakarta.xml.bind + jakarta.xml.bind-api + 3.0.1 + @@ -386,6 +408,16 @@ and httpcore 4.4.16 + + + jakarta.ws.rs + jakarta.ws.rs-api + 3.1.0 + diff --git a/src/main/java/org/jenkinsci/test/acceptance/po/OicAuthSecurityRealm.java b/src/main/java/org/jenkinsci/test/acceptance/po/OicAuthSecurityRealm.java new file mode 100644 index 0000000000..d8aa7e3772 --- /dev/null +++ b/src/main/java/org/jenkinsci/test/acceptance/po/OicAuthSecurityRealm.java @@ -0,0 +1,35 @@ +package org.jenkinsci.test.acceptance.po; + +/** + * Security Realm provided by oic-auth plugin + */ +@Describable({"Login with Openid Connect", "Login with Openid Connect"}) +public class OicAuthSecurityRealm extends SecurityRealm { + + public OicAuthSecurityRealm(GlobalSecurityConfig context, String path) { + super(context, path); + } + + public void configureClient(String clientId, String clientSecret) { + control("clientId").set(clientId); + control("clientSecret").set(clientSecret); + } + + public void setAutomaticConfiguration(String wellKnownEndpoint) { + control(by.radioButton("Automatic configuration")).click(); + control("wellKnownOpenIDConfigurationUrl").set(wellKnownEndpoint); + } + + public void logoutFromOpenidProvider(boolean logout) { + Control check = control(by.checkbox("Logout from OpenID Provider")); + if (logout) { + check.check(); + } else { + check.uncheck(); + } + } + + public void setPostLogoutUrl(String postLogoutUrl) { + control("postLogoutRedirectUrl").set(postLogoutUrl); + } +} diff --git a/src/main/java/org/jenkinsci/test/acceptance/utils/keycloack/KeycloakUtils.java b/src/main/java/org/jenkinsci/test/acceptance/utils/keycloack/KeycloakUtils.java new file mode 100644 index 0000000000..1b4f9f6e0a --- /dev/null +++ b/src/main/java/org/jenkinsci/test/acceptance/utils/keycloack/KeycloakUtils.java @@ -0,0 +1,95 @@ +package org.jenkinsci.test.acceptance.utils.keycloack; + +import java.net.URL; + +import org.jenkinsci.test.acceptance.po.CapybaraPortingLayerImpl; +import org.jenkinsci.test.acceptance.utils.ElasticTime; +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.WebElement; +import jakarta.inject.Inject; + +public class KeycloakUtils extends CapybaraPortingLayerImpl { + + @Inject + public WebDriver driver; + @Inject + public ElasticTime time; + + public KeycloakUtils() { + super(null); + } + + public void open(URL url) { + visit(url); + } + + public void login(String user) { + login(user, user); + } + + public void login(String user, String passwd) { + waitFor(by.id("username"), 5); + find(by.id("username")).sendKeys(user); + find(by.id("password")).sendKeys(passwd); + find(by.id("kc-login")).click(); + } + + + public User getUser(String keycloakUrl, String realm) { + driver.get(String.format("%s/realms/%s/account", keycloakUrl, realm)); + + waitFor(by.id("username"), 5); + String username = find(by.id("username")).getDomProperty("value"); + String email = find(by.id("email")).getDomProperty("value"); + String firstName = find(by.id("firstName")).getDomProperty("value"); + String lastName = find(by.id("lastName")).getDomProperty("value"); + + + return new User(null /* id not available in this page*/, username, email, firstName, lastName); + } + + public void logout(User user) { + final String caption = user.getFirstName() + " " + user.getLastName(); + waitFor(by.button(caption), 5); + clickButton(caption); + waitFor(by.button("Sign out")); + clickButton("Sign out"); + } + + public static class User { + + private final String id; + private final String userName; + private final String email; + private final String firstName; + private final String lastName; + + public User(String id, String userName, String email, String firstName, String lastName) { + this.id = id; + this.userName = userName; + this.email = email; + this.firstName = firstName; + this.lastName = lastName; + } + + public String getId() { + return id; + } + + public String getUserName() { + return userName; + } + + public String getEmail() { + return email; + } + + public String getFirstName() { + return firstName; + } + + public String getLastName() { + return lastName; + } + } +} diff --git a/src/test/java/plugins/OicAuthPluginTest.java b/src/test/java/plugins/OicAuthPluginTest.java new file mode 100644 index 0000000000..af910dc8bd --- /dev/null +++ b/src/test/java/plugins/OicAuthPluginTest.java @@ -0,0 +1,227 @@ +package plugins; + +import java.net.URL; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import org.jenkinsci.test.acceptance.junit.AbstractJUnitTest; +import org.jenkinsci.test.acceptance.junit.WithPlugins; +import org.jenkinsci.test.acceptance.po.GlobalSecurityConfig; +import org.jenkinsci.test.acceptance.po.LoggedInAuthorizationStrategy; +import org.jenkinsci.test.acceptance.po.OicAuthSecurityRealm; +import org.jenkinsci.test.acceptance.utils.keycloack.KeycloakUtils; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.keycloak.admin.client.Keycloak; +import org.keycloak.admin.client.resource.GroupResource; +import org.keycloak.admin.client.resource.RealmResource; +import org.keycloak.representations.idm.ClientRepresentation; +import org.keycloak.representations.idm.CredentialRepresentation; +import org.keycloak.representations.idm.GroupRepresentation; +import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.representations.idm.UserRepresentation; +import org.openqa.selenium.NoSuchElementException; +import dasniko.testcontainers.keycloak.KeycloakContainer; +import jakarta.inject.Inject; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThrows; + +@WithPlugins("oic-auth") +public class OicAuthPluginTest extends AbstractJUnitTest { + + private static final String REALM = "test-realm"; + private static final String CLIENT = "jenkins"; + + @Rule + public KeycloakContainer keycloak = new KeycloakContainer(); + + @Inject + public KeycloakUtils keycloakUtils; + + private String userBobKeycloakId; + private String userJohnKeycloakId; + + @Before + public void setUpKeycloak() { + configureOIDCProvider(); + configureRealm(); + } + + private void configureOIDCProvider() { + try (Keycloak keycloakAdmin = keycloak.getKeycloakAdminClient()) { + RealmRepresentation testRealm = new RealmRepresentation(); + testRealm.setRealm(REALM); + testRealm.setId(REALM); + testRealm.setDisplayName(REALM); + testRealm.setEnabled(true); + + keycloakAdmin.realms().create(testRealm); + + // Add groups and subgroups + GroupRepresentation employees = new GroupRepresentation(); + employees.setName("employees"); + + final RealmResource theRealm = keycloakAdmin.realm(REALM); + theRealm.groups().add(employees); + + String groupId = theRealm.groups().groups().get(0).getId(); + + GroupRepresentation devs = new GroupRepresentation(); + devs.setName("devs"); + GroupResource group = theRealm.groups().group(groupId); + group.subGroup(devs); + + GroupRepresentation sales = new GroupRepresentation(); + sales.setName("sales"); + group = theRealm.groups().group(groupId); + group.subGroup(sales); + + // Users + UserRepresentation bob = new UserRepresentation(); + bob.setEmail("bob@acme.org"); + bob.setUsername("bob"); + bob.setFirstName("Bob"); + bob.setLastName("Smith"); + CredentialRepresentation credentials = new CredentialRepresentation(); + credentials.setValue("bob"); + credentials.setTemporary(false); + credentials.setType(CredentialRepresentation.PASSWORD); + bob.setCredentials(List.of(credentials)); + bob.setGroups(Arrays.asList("/employees", "/employees/devs")); + bob.setEmailVerified(true); + bob.setEnabled(true); + theRealm.users().create(bob); + + UserRepresentation john = new UserRepresentation(); + john.setEmail("john@acme.org"); + john.setUsername("john"); + john.setFirstName("John"); + john.setLastName("Smith"); + credentials = new CredentialRepresentation(); + credentials.setValue("john"); + credentials.setTemporary(false); + credentials.setType(CredentialRepresentation.PASSWORD); + john.setCredentials(List.of(credentials)); + john.setGroups(Arrays.asList("/employees", "/employees/sales")); + john.setEmailVerified(true); + john.setEnabled(true); + theRealm.users().create(john); + + // Client + ClientRepresentation jenkinsClient = new ClientRepresentation(); + jenkinsClient.setClientId(CLIENT); + jenkinsClient.setProtocol("openid-connect"); + jenkinsClient.setSecret(CLIENT); + final String jenkinsUrl = jenkins.url.toString(); + jenkinsClient.setRootUrl(jenkinsUrl); + jenkinsClient.setRedirectUris(List.of(String.format("%ssecurityRealm/finishLogin", jenkinsUrl))); + jenkinsClient.setWebOrigins(List.of(jenkinsUrl)); + jenkinsClient.setAttributes(Map.of("post.logout.redirect.uris", String.format("%sOicLogout", jenkinsUrl))); + theRealm.clients().create(jenkinsClient); + + // Assert that the realm is properly created + assertThat("group is created", theRealm.groups().groups().get(0).getName(), is("employees")); + GroupResource g = theRealm.groups().group(groupId); + assertThat("subgroups are created", + g.getSubGroups(0, 2, true).stream().map(GroupRepresentation::getName).collect(Collectors.toList()), + containsInAnyOrder("devs", "sales")); + assertThat("users are created", theRealm.users().list().stream().map(UserRepresentation::getUsername).collect(Collectors.toList()), + containsInAnyOrder("bob", "john")); + userBobKeycloakId = theRealm.users().searchByUsername("bob", true).get(0).getId(); + assertThat("User bob with the correct groups", + theRealm.users().get(userBobKeycloakId).groups().stream().map(GroupRepresentation::getPath).collect(Collectors.toList()), + containsInAnyOrder("/employees", "/employees/devs")); + userJohnKeycloakId = theRealm.users().searchByUsername("john", true).get(0).getId(); + assertThat("User john with the correct groups", + theRealm.users().get(userJohnKeycloakId).groups().stream().map(GroupRepresentation::getPath).collect(Collectors.toList()), + containsInAnyOrder("/employees", "/employees/sales")); + assertThat("client is created", + theRealm.clients().findByClientId(CLIENT).get(0).getProtocol(), is("openid-connect")); + } + } + + private void configureRealm() { + final String keycloakUrl = keycloak.getAuthServerUrl(); + GlobalSecurityConfig sc = new GlobalSecurityConfig(jenkins); + sc.open(); + OicAuthSecurityRealm securityRealm = sc.useRealm(OicAuthSecurityRealm.class); + securityRealm.configureClient(CLIENT, CLIENT); + securityRealm.setAutomaticConfiguration(String.format("%s/realms/%s/.well-known/openid-configuration", keycloakUrl, REALM)); + securityRealm.logoutFromOpenidProvider(true); + securityRealm.setPostLogoutUrl(jenkins.url("OicLogout").toExternalForm()); + sc.useAuthorizationStrategy(LoggedInAuthorizationStrategy.class); + sc.save(); + } + + @Test + public void fromJenkinsToKeycloak() { + final KeycloakUtils.User bob = new KeycloakUtils.User(userBobKeycloakId, "bob", "bob@acme.org", "Bob", "Smith"); + final KeycloakUtils.User john = new KeycloakUtils.User(userJohnKeycloakId, "john", "john@acme.org", "John", "Smith"); + jenkins.open(); + + jenkins.clickLink("log in"); + keycloakUtils.login(bob.getUserName()); + assertLoggedUser(bob); + + jenkins.logout(); + jenkins.open(); + assertLoggedOut(); + + // logout from Jenkins does mean logout from keycloak + jenkins.open(); + + clickLink("log in"); + keycloakUtils.login(john.getUserName()); + assertLoggedUser(john); + } + + @Test + public void fromKeycloakToJenkins() throws Exception { + final KeycloakUtils.User bob = new KeycloakUtils.User(userBobKeycloakId, "bob", "bob@acme.org", "Bob", "Smith"); + final KeycloakUtils.User john = new KeycloakUtils.User(userJohnKeycloakId, "john", "john@acme.org", "John", "Smith"); + final String loginUrl = String.format("%s/realms/%s/account", keycloak.getAuthServerUrl(), REALM); + keycloakUtils.open(new URL(loginUrl)); + + keycloakUtils.login(bob.getUserName()); + jenkins.open(); + jenkins.clickLink("log in"); // won't request a login, but log in directly with user from + + assertLoggedUser(bob); + + keycloakUtils.logout(bob); + jenkins.open(); + jenkins.logout(); // logout from keycloak does not logout from Jenkins (seems not supported in the plugin) + assertLoggedOut(); + + // Once logged out, we can change the user + jenkins.open(); + jenkins.clickLink("log in"); + keycloakUtils.login(john.getUserName()); + assertLoggedUser(john); + } + + private void assertLoggedOut() { + assertNull("User has logged out from Jenkins", jenkins.getCurrentUser().id()); + + assertThrows("User has logged out from keycloak", NoSuchElementException.class, + () -> keycloakUtils.getUser(keycloak.getAuthServerUrl(), REALM)); + } + + private void assertLoggedUser(KeycloakUtils.User expectedUser) { + assertThat("User has logged in Jenkins", jenkins.getCurrentUser().id(), is(expectedUser.getId())); + + KeycloakUtils.User fromKeyCloak = keycloakUtils.getUser(keycloak.getAuthServerUrl(), REALM); + assertThat("User has logged in keycloack", fromKeyCloak.getUserName(), is(expectedUser.getUserName())); + assertThat("User has logged in keycloack", fromKeyCloak.getEmail(), is(expectedUser.getEmail())); + assertThat("User has logged in keycloack", fromKeyCloak.getFirstName(), is(expectedUser.getFirstName())); + assertThat("User has logged in keycloack", fromKeyCloak.getLastName(), is(expectedUser.getLastName())); + } + +} From 984124fcc81a2f5edb512792c8c6452ae3266fd2 Mon Sep 17 00:00:00 2001 From: Francisco Javier Fernandez Gonzalez Date: Mon, 2 Sep 2024 10:43:32 +0200 Subject: [PATCH 02/16] Retrieve and check the roles from the OICD provider --- .../acceptance/po/OicAuthSecurityRealm.java | 9 ++++ src/test/java/plugins/OicAuthPluginTest.java | 41 +++++++++++++++---- 2 files changed, 43 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/jenkinsci/test/acceptance/po/OicAuthSecurityRealm.java b/src/main/java/org/jenkinsci/test/acceptance/po/OicAuthSecurityRealm.java index d8aa7e3772..37cf588703 100644 --- a/src/main/java/org/jenkinsci/test/acceptance/po/OicAuthSecurityRealm.java +++ b/src/main/java/org/jenkinsci/test/acceptance/po/OicAuthSecurityRealm.java @@ -32,4 +32,13 @@ public void logoutFromOpenidProvider(boolean logout) { public void setPostLogoutUrl(String postLogoutUrl) { control("postLogoutRedirectUrl").set(postLogoutUrl); } + + public void setUserFields(String userNameFieldName, String emailFieldName, String fullNameFieldName, String groupsFieldName) { + clickButton("User fields"); + waitFor(by.path("/securityRealm/groupsFieldName")); + control("userNameField").set(userNameFieldName); + control("emailFieldName").set(emailFieldName); + control("fullNameFieldName").set(fullNameFieldName); + control("groupsFieldName").set(groupsFieldName); + } } diff --git a/src/test/java/plugins/OicAuthPluginTest.java b/src/test/java/plugins/OicAuthPluginTest.java index af910dc8bd..5612bb5a0c 100644 --- a/src/test/java/plugins/OicAuthPluginTest.java +++ b/src/test/java/plugins/OicAuthPluginTest.java @@ -4,8 +4,10 @@ import java.util.Arrays; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.stream.Collectors; +import org.apache.commons.lang3.StringUtils; import org.jenkinsci.test.acceptance.junit.AbstractJUnitTest; import org.jenkinsci.test.acceptance.junit.WithPlugins; import org.jenkinsci.test.acceptance.po.GlobalSecurityConfig; @@ -22,6 +24,7 @@ import org.keycloak.representations.idm.CredentialRepresentation; import org.keycloak.representations.idm.GroupRepresentation; import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.representations.idm.RoleRepresentation; import org.keycloak.representations.idm.UserRepresentation; import org.openqa.selenium.NoSuchElementException; import dasniko.testcontainers.keycloak.KeycloakContainer; @@ -29,6 +32,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.in; import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThrows; @@ -49,13 +53,14 @@ public class OicAuthPluginTest extends AbstractJUnitTest { private String userJohnKeycloakId; @Before - public void setUpKeycloak() { + public void setUpKeycloak() throws Exception { configureOIDCProvider(); configureRealm(); } - private void configureOIDCProvider() { + private void configureOIDCProvider() throws Exception { try (Keycloak keycloakAdmin = keycloak.getKeycloakAdminClient()) { + // Create Realm RealmRepresentation testRealm = new RealmRepresentation(); testRealm.setRealm(REALM); testRealm.setId(REALM); @@ -63,6 +68,12 @@ private void configureOIDCProvider() { testRealm.setEnabled(true); keycloakAdmin.realms().create(testRealm); + RoleRepresentation jenkinsRead = new RoleRepresentation(); + jenkinsRead.setName("jenkinsRead"); + keycloakAdmin.realm(REALM).roles().create(jenkinsRead); + RoleRepresentation jenkinsAdmin = new RoleRepresentation(); + jenkinsAdmin.setName("jenkinsAdmin"); + keycloakAdmin.realm(REALM).roles().create(jenkinsAdmin); // Add groups and subgroups GroupRepresentation employees = new GroupRepresentation(); @@ -83,6 +94,12 @@ private void configureOIDCProvider() { group = theRealm.groups().group(groupId); group.subGroup(sales); + List subGroups = theRealm.groups().group(groupId).getSubGroups(0, 2, true); + String devsId = subGroups.stream().filter(g -> g.getName().equals("devs")).findFirst().orElseThrow(() -> new Exception("Something went wrong initialization keycloak")).getId(); + String salesId = subGroups.stream().filter(g -> g.getName().equals("sales")).findFirst().orElseThrow(() -> new Exception("Something went wrong initialization keycloak")).getId(); + theRealm.groups().group(devsId).roles().realmLevel().add(List.of(theRealm.roles().get("jenkinsAdmin").toRepresentation())); + theRealm.groups().group(salesId).roles().realmLevel().add(List.of(theRealm.roles().get("jenkinsRead").toRepresentation())); + // Users UserRepresentation bob = new UserRepresentation(); bob.setEmail("bob@acme.org"); @@ -156,6 +173,7 @@ private void configureRealm() { securityRealm.setAutomaticConfiguration(String.format("%s/realms/%s/.well-known/openid-configuration", keycloakUrl, REALM)); securityRealm.logoutFromOpenidProvider(true); securityRealm.setPostLogoutUrl(jenkins.url("OicLogout").toExternalForm()); + securityRealm.setUserFields(null, null, null, "groups"); sc.useAuthorizationStrategy(LoggedInAuthorizationStrategy.class); sc.save(); } @@ -168,7 +186,7 @@ public void fromJenkinsToKeycloak() { jenkins.clickLink("log in"); keycloakUtils.login(bob.getUserName()); - assertLoggedUser(bob); + assertLoggedUser(bob, "jenkinsAdmin"); jenkins.logout(); jenkins.open(); @@ -179,7 +197,7 @@ public void fromJenkinsToKeycloak() { clickLink("log in"); keycloakUtils.login(john.getUserName()); - assertLoggedUser(john); + assertLoggedUser(john, "jenkinsRead"); } @Test @@ -193,7 +211,7 @@ public void fromKeycloakToJenkins() throws Exception { jenkins.open(); jenkins.clickLink("log in"); // won't request a login, but log in directly with user from - assertLoggedUser(bob); + assertLoggedUser(bob, "jenkinsAdmin"); keycloakUtils.logout(bob); jenkins.open(); @@ -204,7 +222,7 @@ public void fromKeycloakToJenkins() throws Exception { jenkins.open(); jenkins.clickLink("log in"); keycloakUtils.login(john.getUserName()); - assertLoggedUser(john); + assertLoggedUser(john, "jenkinsRead"); } private void assertLoggedOut() { @@ -214,8 +232,17 @@ private void assertLoggedOut() { () -> keycloakUtils.getUser(keycloak.getAuthServerUrl(), REALM)); } - private void assertLoggedUser(KeycloakUtils.User expectedUser) { + private void assertLoggedUser(KeycloakUtils.User expectedUser, String roleToCheck) { assertThat("User has logged in Jenkins", jenkins.getCurrentUser().id(), is(expectedUser.getId())); + jenkins.visit("whoAmI"); + + // TODO if needed for more tests, consider to create a proper WhoAmI PageObject + // for this test we just need the authorities, which are displayed in UI as
  • "role_name"
  • , we can get + // all of "li" tags and check our roles are there + // Note the quotes surrounding the role name + Set allLiTagsInPage = this.driver.findElements(by.tagName("li")).stream().map(webElement -> StringUtils.defaultString(webElement.getText()) + .replace("\"", "")).collect(Collectors.toSet()); + assertThat("User has the expected roles inherited from keycloak", roleToCheck, is(in(allLiTagsInPage))); KeycloakUtils.User fromKeyCloak = keycloakUtils.getUser(keycloak.getAuthServerUrl(), REALM); assertThat("User has logged in keycloack", fromKeyCloak.getUserName(), is(expectedUser.getUserName())); From ac188a0b5877a091d296970169de532f45588ebd Mon Sep 17 00:00:00 2001 From: Francisco Javier Fernandez <31063239+fcojfernandez@users.noreply.github.com> Date: Mon, 2 Sep 2024 16:41:38 +0200 Subject: [PATCH 03/16] Update src/main/java/org/jenkinsci/test/acceptance/po/OicAuthSecurityRealm.java Co-authored-by: James Nord --- .../org/jenkinsci/test/acceptance/po/OicAuthSecurityRealm.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/jenkinsci/test/acceptance/po/OicAuthSecurityRealm.java b/src/main/java/org/jenkinsci/test/acceptance/po/OicAuthSecurityRealm.java index 37cf588703..27d0653c0f 100644 --- a/src/main/java/org/jenkinsci/test/acceptance/po/OicAuthSecurityRealm.java +++ b/src/main/java/org/jenkinsci/test/acceptance/po/OicAuthSecurityRealm.java @@ -3,7 +3,7 @@ /** * Security Realm provided by oic-auth plugin */ -@Describable({"Login with Openid Connect", "Login with Openid Connect"}) +@Describable("Login with Openid Connect") public class OicAuthSecurityRealm extends SecurityRealm { public OicAuthSecurityRealm(GlobalSecurityConfig context, String path) { From 7cfbcf90aeba34f0ccf5dcde38ae5f553dcd27c1 Mon Sep 17 00:00:00 2001 From: Francisco Javier Fernandez Gonzalez Date: Mon, 2 Sep 2024 17:24:56 +0200 Subject: [PATCH 04/16] Apply suggestions --- .../acceptance/po/OicAuthSecurityRealm.java | 2 +- .../utils/keycloack/KeycloakUtils.java | 3 +- src/test/java/plugins/OicAuthPluginTest.java | 38 +++++++++++++------ 3 files changed, 28 insertions(+), 15 deletions(-) diff --git a/src/main/java/org/jenkinsci/test/acceptance/po/OicAuthSecurityRealm.java b/src/main/java/org/jenkinsci/test/acceptance/po/OicAuthSecurityRealm.java index 27d0653c0f..fbee549863 100644 --- a/src/main/java/org/jenkinsci/test/acceptance/po/OicAuthSecurityRealm.java +++ b/src/main/java/org/jenkinsci/test/acceptance/po/OicAuthSecurityRealm.java @@ -20,7 +20,7 @@ public void setAutomaticConfiguration(String wellKnownEndpoint) { control("wellKnownOpenIDConfigurationUrl").set(wellKnownEndpoint); } - public void logoutFromOpenidProvider(boolean logout) { + public void setLogoutFromOpenidProvider(boolean logout) { Control check = control(by.checkbox("Logout from OpenID Provider")); if (logout) { check.check(); diff --git a/src/main/java/org/jenkinsci/test/acceptance/utils/keycloack/KeycloakUtils.java b/src/main/java/org/jenkinsci/test/acceptance/utils/keycloack/KeycloakUtils.java index 1b4f9f6e0a..42fdc863cf 100644 --- a/src/main/java/org/jenkinsci/test/acceptance/utils/keycloack/KeycloakUtils.java +++ b/src/main/java/org/jenkinsci/test/acceptance/utils/keycloack/KeycloakUtils.java @@ -5,7 +5,6 @@ import org.jenkinsci.test.acceptance.po.CapybaraPortingLayerImpl; import org.jenkinsci.test.acceptance.utils.ElasticTime; import org.openqa.selenium.WebDriver; -import org.openqa.selenium.WebElement; import jakarta.inject.Inject; public class KeycloakUtils extends CapybaraPortingLayerImpl { @@ -35,7 +34,7 @@ public void login(String user, String passwd) { } - public User getUser(String keycloakUrl, String realm) { + public User getCurrentUser(String keycloakUrl, String realm) { driver.get(String.format("%s/realms/%s/account", keycloakUrl, realm)); waitFor(by.id("username"), 5); diff --git a/src/test/java/plugins/OicAuthPluginTest.java b/src/test/java/plugins/OicAuthPluginTest.java index 5612bb5a0c..c4dd80dda2 100644 --- a/src/test/java/plugins/OicAuthPluginTest.java +++ b/src/test/java/plugins/OicAuthPluginTest.java @@ -102,7 +102,7 @@ private void configureOIDCProvider() throws Exception { // Users UserRepresentation bob = new UserRepresentation(); - bob.setEmail("bob@acme.org"); + bob.setEmail("bob@jenkins-ath.test"); bob.setUsername("bob"); bob.setFirstName("Bob"); bob.setLastName("Smith"); @@ -117,7 +117,7 @@ private void configureOIDCProvider() throws Exception { theRealm.users().create(bob); UserRepresentation john = new UserRepresentation(); - john.setEmail("john@acme.org"); + john.setEmail("john@jenkins-ath.test"); john.setUsername("john"); john.setFirstName("John"); john.setLastName("Smith"); @@ -171,17 +171,24 @@ private void configureRealm() { OicAuthSecurityRealm securityRealm = sc.useRealm(OicAuthSecurityRealm.class); securityRealm.configureClient(CLIENT, CLIENT); securityRealm.setAutomaticConfiguration(String.format("%s/realms/%s/.well-known/openid-configuration", keycloakUrl, REALM)); - securityRealm.logoutFromOpenidProvider(true); + securityRealm.setLogoutFromOpenidProvider(true); securityRealm.setPostLogoutUrl(jenkins.url("OicLogout").toExternalForm()); securityRealm.setUserFields(null, null, null, "groups"); sc.useAuthorizationStrategy(LoggedInAuthorizationStrategy.class); sc.save(); } + /** + * Test the login and logout in Jenkins and how it is propagated to the OIDC Provider + * - A login in Jenkins will redirect to the provider login page, so the user will be logged in as well in keycloak + * - A logout in Jenkins will apply only to Jenkins or will be propagated to the provider (keycloak here) depending + * on the value of the property Lout from openid provider in the Security Realm configuration. For this test, + * it'll be propagated + */ @Test - public void fromJenkinsToKeycloak() { - final KeycloakUtils.User bob = new KeycloakUtils.User(userBobKeycloakId, "bob", "bob@acme.org", "Bob", "Smith"); - final KeycloakUtils.User john = new KeycloakUtils.User(userJohnKeycloakId, "john", "john@acme.org", "John", "Smith"); + public void loginAndLogoutInJenkinsIsReflectedInKeycloak() { + final KeycloakUtils.User bob = new KeycloakUtils.User(userBobKeycloakId, "bob", "bob@jenkins-ath.test", "Bob", "Smith"); + final KeycloakUtils.User john = new KeycloakUtils.User(userJohnKeycloakId, "john", "john@jenkins-ath.test", "John", "Smith"); jenkins.open(); jenkins.clickLink("log in"); @@ -192,7 +199,8 @@ public void fromJenkinsToKeycloak() { jenkins.open(); assertLoggedOut(); - // logout from Jenkins does mean logout from keycloak + // As the option Logout from Openid Provider is set to true, a logout from Jenkins means a logout from Keycloak + // so the login page will appear again jenkins.open(); clickLink("log in"); @@ -200,10 +208,16 @@ public void fromJenkinsToKeycloak() { assertLoggedUser(john, "jenkinsRead"); } + /** + * Test the login and logout in the OIDC provider and how it is propagated to Jenkins + * - A login in the provider will be detected by the plugin so Jenkins won't prompt the login form + * - A logout in the provider will redirect to the Jenkins logout action (configured in keycloak) so the user + * will log out from Jenkins as well + */ @Test - public void fromKeycloakToJenkins() throws Exception { - final KeycloakUtils.User bob = new KeycloakUtils.User(userBobKeycloakId, "bob", "bob@acme.org", "Bob", "Smith"); - final KeycloakUtils.User john = new KeycloakUtils.User(userJohnKeycloakId, "john", "john@acme.org", "John", "Smith"); + public void loginAndLogoutInKeycloakIsReflectedInJenkins() throws Exception { + final KeycloakUtils.User bob = new KeycloakUtils.User(userBobKeycloakId, "bob", "bob@jenkins-ath.test", "Bob", "Smith"); + final KeycloakUtils.User john = new KeycloakUtils.User(userJohnKeycloakId, "john", "john@jenkins-ath.test", "John", "Smith"); final String loginUrl = String.format("%s/realms/%s/account", keycloak.getAuthServerUrl(), REALM); keycloakUtils.open(new URL(loginUrl)); @@ -229,7 +243,7 @@ private void assertLoggedOut() { assertNull("User has logged out from Jenkins", jenkins.getCurrentUser().id()); assertThrows("User has logged out from keycloak", NoSuchElementException.class, - () -> keycloakUtils.getUser(keycloak.getAuthServerUrl(), REALM)); + () -> keycloakUtils.getCurrentUser(keycloak.getAuthServerUrl(), REALM)); } private void assertLoggedUser(KeycloakUtils.User expectedUser, String roleToCheck) { @@ -244,7 +258,7 @@ private void assertLoggedUser(KeycloakUtils.User expectedUser, String roleToChec .replace("\"", "")).collect(Collectors.toSet()); assertThat("User has the expected roles inherited from keycloak", roleToCheck, is(in(allLiTagsInPage))); - KeycloakUtils.User fromKeyCloak = keycloakUtils.getUser(keycloak.getAuthServerUrl(), REALM); + KeycloakUtils.User fromKeyCloak = keycloakUtils.getCurrentUser(keycloak.getAuthServerUrl(), REALM); assertThat("User has logged in keycloack", fromKeyCloak.getUserName(), is(expectedUser.getUserName())); assertThat("User has logged in keycloack", fromKeyCloak.getEmail(), is(expectedUser.getEmail())); assertThat("User has logged in keycloack", fromKeyCloak.getFirstName(), is(expectedUser.getFirstName())); From 95e5e3172065f542151b0364b243d1c6f3d818d7 Mon Sep 17 00:00:00 2001 From: Francisco Javier Fernandez Gonzalez Date: Mon, 2 Sep 2024 17:47:21 +0200 Subject: [PATCH 05/16] Add the WhoAmI page --- .../jenkinsci/test/acceptance/po/WhoAmI.java | 11 +++++++++++ src/test/java/plugins/OicAuthPluginTest.java | 19 ++++++++----------- 2 files changed, 19 insertions(+), 11 deletions(-) create mode 100644 src/main/java/org/jenkinsci/test/acceptance/po/WhoAmI.java diff --git a/src/main/java/org/jenkinsci/test/acceptance/po/WhoAmI.java b/src/main/java/org/jenkinsci/test/acceptance/po/WhoAmI.java new file mode 100644 index 0000000000..eb89296a63 --- /dev/null +++ b/src/main/java/org/jenkinsci/test/acceptance/po/WhoAmI.java @@ -0,0 +1,11 @@ +package org.jenkinsci.test.acceptance.po; + +/** + * Who Am I page in Jenkins + */ +public class WhoAmI extends ContainerPageObject { + + public WhoAmI(ContainerPageObject parent) { + super(parent, parent.url("whoAmI/")); + } +} diff --git a/src/test/java/plugins/OicAuthPluginTest.java b/src/test/java/plugins/OicAuthPluginTest.java index c4dd80dda2..a08f4849a2 100644 --- a/src/test/java/plugins/OicAuthPluginTest.java +++ b/src/test/java/plugins/OicAuthPluginTest.java @@ -4,16 +4,16 @@ import java.util.Arrays; import java.util.List; import java.util.Map; -import java.util.Set; import java.util.stream.Collectors; -import org.apache.commons.lang3.StringUtils; import org.jenkinsci.test.acceptance.junit.AbstractJUnitTest; import org.jenkinsci.test.acceptance.junit.WithPlugins; import org.jenkinsci.test.acceptance.po.GlobalSecurityConfig; import org.jenkinsci.test.acceptance.po.LoggedInAuthorizationStrategy; import org.jenkinsci.test.acceptance.po.OicAuthSecurityRealm; +import org.jenkinsci.test.acceptance.po.WhoAmI; import org.jenkinsci.test.acceptance.utils.keycloack.KeycloakUtils; +import org.json.JSONObject; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -248,15 +248,12 @@ private void assertLoggedOut() { private void assertLoggedUser(KeycloakUtils.User expectedUser, String roleToCheck) { assertThat("User has logged in Jenkins", jenkins.getCurrentUser().id(), is(expectedUser.getId())); - jenkins.visit("whoAmI"); - - // TODO if needed for more tests, consider to create a proper WhoAmI PageObject - // for this test we just need the authorities, which are displayed in UI as
  • "role_name"
  • , we can get - // all of "li" tags and check our roles are there - // Note the quotes surrounding the role name - Set allLiTagsInPage = this.driver.findElements(by.tagName("li")).stream().map(webElement -> StringUtils.defaultString(webElement.getText()) - .replace("\"", "")).collect(Collectors.toSet()); - assertThat("User has the expected roles inherited from keycloak", roleToCheck, is(in(allLiTagsInPage))); + + WhoAmI whoAmI = new WhoAmI(jenkins); + whoAmI.open(); + JSONObject jsonData = new JSONObject(whoAmI.getJson().toString()); + assertThat("User is the expected one", jsonData.getString("name"), is(expectedUser.getId())); + assertThat("User has the expected roles inherited from keycloak", roleToCheck, is(in(jsonData.getJSONArray("authorities").toList()))); KeycloakUtils.User fromKeyCloak = keycloakUtils.getCurrentUser(keycloak.getAuthServerUrl(), REALM); assertThat("User has logged in keycloack", fromKeyCloak.getUserName(), is(expectedUser.getUserName())); From 067ae95da4dd857dfc0a2f1d70285145eece032f Mon Sep 17 00:00:00 2001 From: James Nord Date: Wed, 4 Sep 2024 18:13:13 +0100 Subject: [PATCH 06/16] Fix permissions on the docker socket TestContainers does not use docker, but talks directly to the docker socket. The permissions on this socket come from the host where it is mapped and the docker groupid may not match what we have in the container. So allow th arg to be passed through at build time and add the ath-user to the docker group so it has the permissions. We retain the legacy suid on the docker binary as we publish the container and there is only a single test so far using this test-containers. (this can be revistied if required). --- Jenkinsfile | 2 +- src/main/resources/ath-container/Dockerfile | 14 +++++++++++--- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 709730c771..5e637637a7 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -122,7 +122,7 @@ for (int i = 0; i < splits.size(); i++) { retryCounts = retryCounts + 1 // increment the retry count before allocating a node in case it fails node(nodeLabel) { checkout scm - def image = skipImageBuild ? docker.image('jenkins/ath') : docker.build('jenkins/ath', '--build-arg uid="$(id -u)" --build-arg gid="$(id -g)" ./src/main/resources/ath-container/') + def image = skipImageBuild ? docker.image('jenkins/ath') : docker.build('jenkins/ath', '--build-arg uid="$(id -u)" --build-arg gid="$(id -g)" --build-arg dockergid="$(getent group docker | cut -d: -f3)" ./src/main/resources/ath-container/') sh 'mkdir -p target/ath-reports && chmod a+rwx target/ath-reports' def cwd = pwd() image.inside("-v /var/run/docker.sock:/var/run/docker.sock -v '${cwd}/target/ath-reports:/reports:rw' --shm-size 2g") { diff --git a/src/main/resources/ath-container/Dockerfile b/src/main/resources/ath-container/Dockerfile index 5720ead197..6f19931f5b 100644 --- a/src/main/resources/ath-container/Dockerfile +++ b/src/main/resources/ath-container/Dockerfile @@ -37,6 +37,13 @@ RUN install -m 0755 -d /etc/apt/keyrings \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* +# Despite the docker SUID hack below, test-containers accesses /var/run/docker.sock directly and so we can not rely on the SUID hack. +# Rather take the docker user group as an arg and make the ath-user a member of that group +# we retain the suid workaround as this method requires a local build of the container +# we need to do this before we install docker so that any files have the correct permission +ARG dockergid=1002 +RUN groupadd docker -g $dockergid + # Docker installation according to https://docs.docker.com/engine/install/ubuntu/ ARG DOCKER_BUILDX_VERSION=0.16.2 ARG DOCKER_VERSION=27.1.2 @@ -84,10 +91,11 @@ EXPOSE 5942 RUN deluser --remove-home ubuntu \ && groupadd ath-user -g $gid \ - && useradd ath-user -l -c 'ATH User' -u $uid -g $gid -m -d /home/ath-user -s /bin/bash + && useradd ath-user -l -c 'ATH User' -u $uid -g $gid -G docker -m -d /home/ath-user -s /bin/bash -# Set SUID and SGID for docker binary so it can communicate with mapped socket its uid:gid we can not control. Alternative -# approach used for this is adding ath-user to the group of /var/run/docker.sock but that require root permission we do not +# Set SUID and SGID for docker binary so it can communicate with mapped socket its uid:gid we can not control. This alternative +# approach is used as adding ath-user to the group of /var/run/docker.sock is a build time option and any published container may +# not match what is needed, and changing this at runtime would require root permission we do not # have in ENTRYPOINT as the container is started as ath-user. RUN chmod ug+s /usr/bin/docker* From 5fbb28872d36cd5e689b6c291e7bf769219bb8fb Mon Sep 17 00:00:00 2001 From: Basil Crow Date: Thu, 5 Sep 2024 11:06:36 -0700 Subject: [PATCH 07/16] Incremental build --- pom.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 255f14902f..e41920c0cf 100644 --- a/pom.xml +++ b/pom.xml @@ -30,7 +30,8 @@ 999999-SNAPSHOT true - 2.475 + + 2.476-rc35309.670fcb_f703b_7 4.24.0 33.3.0-jre 3.8.1 From d3393fb0fb1850f59fb17da825f82c3756c9f91e Mon Sep 17 00:00:00 2001 From: Francisco Javier Fernandez Gonzalez Date: Fri, 6 Sep 2024 09:54:44 +0200 Subject: [PATCH 08/16] Remove unused libraries --- pom.xml | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/pom.xml b/pom.xml index e41920c0cf..11720fb39d 100644 --- a/pom.xml +++ b/pom.xml @@ -318,7 +318,6 @@ com.github.dasniko @@ -326,17 +325,6 @@ 3.4.0 test - - jakarta.annotation - jakarta.annotation-api - 2.1.1 - test - - - jakarta.xml.bind - jakarta.xml.bind-api - 3.0.1 - From 9c7ae5aa00eef95628361199a8f6a87b312af9ca Mon Sep 17 00:00:00 2001 From: Francisco Javier Fernandez Gonzalez Date: Fri, 6 Sep 2024 11:15:40 +0200 Subject: [PATCH 09/16] Add missing dependencies --- pom.xml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/pom.xml b/pom.xml index 11720fb39d..8742112e65 100644 --- a/pom.xml +++ b/pom.xml @@ -407,6 +407,16 @@ and jakarta.ws.rs-api 3.1.0 + + jakarta.annotation + jakarta.annotation-api + 2.1.1 + + + jakarta.xml.bind + jakarta.xml.bind-api + 3.0.1 + From d007fd09fb6f75cdd29f7c4b756e80335cac6e78 Mon Sep 17 00:00:00 2001 From: Basil Crow Date: Fri, 6 Sep 2024 14:54:37 -0700 Subject: [PATCH 10/16] New incremental --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 8d90bbc816..593164c726 100644 --- a/pom.xml +++ b/pom.xml @@ -30,7 +30,7 @@ 999999-SNAPSHOT true - 2.476-rc35309.670fcb_f703b_7 + 2.476-rc35313.0144a_9701da_2 4.24.0 33.3.0-jre From a58d1f9cafcd029c4ead45552c536cbfdc5847e4 Mon Sep 17 00:00:00 2001 From: Basil Crow Date: Mon, 9 Sep 2024 17:56:36 -0700 Subject: [PATCH 11/16] New incremental --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 593164c726..5dddf8f39c 100644 --- a/pom.xml +++ b/pom.xml @@ -30,7 +30,7 @@ 999999-SNAPSHOT true - 2.476-rc35313.0144a_9701da_2 + 2.476-rc35330.1b_8c1984dc3b_ 4.24.0 33.3.0-jre From 49159eb5ea0ffbc97e4e18c6ca7b17f9e229d813 Mon Sep 17 00:00:00 2001 From: James Nord Date: Tue, 10 Sep 2024 17:33:46 +0100 Subject: [PATCH 12/16] update to 2.476 --- pom.xml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 5dddf8f39c..9462d5c135 100644 --- a/pom.xml +++ b/pom.xml @@ -29,8 +29,7 @@ 999999-SNAPSHOT true - - 2.476-rc35330.1b_8c1984dc3b_ + 2.476 4.24.0 33.3.0-jre From da7973e7992070f2f4c56f981305b38371193110 Mon Sep 17 00:00:00 2001 From: James Nord Date: Wed, 11 Sep 2024 14:43:35 +0100 Subject: [PATCH 13/16] document testcontainers requirement --- docs/BROWSER.md | 2 ++ docs/DOCKER.md | 7 +++++++ vars.cmd | 1 + 3 files changed, 10 insertions(+) diff --git a/docs/BROWSER.md b/docs/BROWSER.md index ec543af118..cfb0e0d816 100644 --- a/docs/BROWSER.md +++ b/docs/BROWSER.md @@ -69,6 +69,8 @@ If the host running maven is different to the host running Selenium (e.g. `remot If this is the case you can specify the address to use using: `SELENIUM_PROXY_HOSTNAME=ip.address.of.host mvn install` **Important**: this could exposed the proxy wider beyond your machine and expose other internal services, so this should only be used on private or internal networks to prevent any information leak. +The same issue will also impact any other containers started that the tests that the Browser (rather than Jenkins) needs to access. +For TestContainers you can additionally set `TESTCONTAINERS_HOST_OVERRIDE=ip.address.of.host` ## Avoid focus steal with Xvnc on Linux If you select a real GUI browser, such as Firefox, diff --git a/docs/DOCKER.md b/docs/DOCKER.md index 4e53f8379b..2d5dfbdd88 100644 --- a/docs/DOCKER.md +++ b/docs/DOCKER.md @@ -71,3 +71,10 @@ It's best explained with an example: # Using a custom docker network If you are using a custom network for the container that executes the testing you may instruct the docker-fixtures to use the same one by setting the env variable `DOCKER_FIXTURES_NETWORK`to specify the network you want your fixtures to connect to. + +# Using TestContainers with a remote webdriver + +If you are using a containerized webdriver (or any other remote webdriver) then any containers launched will not be reachable from the remote web browser. +This will be an issue for some tests that require the browser interact with the container (e.g. for authentication). +If this is the case then the `TESTCONTAINERS_HOST_OVERRIDE` should be set to `host.docker.internal` or if the remote browser is non local the IP adddress of your machine. + diff --git a/vars.cmd b/vars.cmd index 501ff32526..c463ffaf03 100644 --- a/vars.cmd +++ b/vars.cmd @@ -23,6 +23,7 @@ IF NOT DEFINED IP ( @echo on set SELENIUM_PROXY_HOSTNAME=%IP% +set TESTCONTAINERS_HOST_OVERRIDE=%IP% set JENKINS_LOCAL_HOSTNAME=%IP% @echo. @echo To start the remote firefox container run the following command: From d9fa1de575fa825f5b9c6fbef08873403c69472a Mon Sep 17 00:00:00 2001 From: James Nord Date: Wed, 11 Sep 2024 17:42:13 +0100 Subject: [PATCH 14/16] update keycloak container vie renovate --- .github/renovate.json | 7 +++++++ src/test/java/plugins/OicAuthPluginTest.java | 4 +++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/.github/renovate.json b/.github/renovate.json index 8c4946738c..980266d991 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -81,7 +81,14 @@ "matchStrings": ["ARG DOCKER_VERSION=(?.*?)\n"], "depNameTemplate": "docker", "datasourceTemplate": "docker" + }, + { + "fileMatch": ["src/test/java/plugins/OicAuthPluginTest.java"], + "matchStrings": [".* KEYCLOAK_IMAGE=\"(?.*?):(?.*?)@(?sha256:.*?)\";\n"], + "depNameTemplate": "{{{repo}}}", + "datasourceTemplate": "docker" } + ], "customDatasources": { "firefox": { diff --git a/src/test/java/plugins/OicAuthPluginTest.java b/src/test/java/plugins/OicAuthPluginTest.java index 9984862d48..11bb61f9a1 100644 --- a/src/test/java/plugins/OicAuthPluginTest.java +++ b/src/test/java/plugins/OicAuthPluginTest.java @@ -42,8 +42,10 @@ public class OicAuthPluginTest extends AbstractJUnitTest { private static final String REALM = "test-realm"; private static final String CLIENT = "jenkins"; + private static final String KEYCLOAK_IMAGE="keycloak/keycloak:25.0.4@sha256:bf788a3b7fd737143f98d4cb514cb9599c896acee01a26b2117a10bd99e23e11"; + @Rule - public KeycloakContainer keycloak = new KeycloakContainer(); + public KeycloakContainer keycloak = new KeycloakContainer(KEYCLOAK_IMAGE); @Inject public KeycloakUtils keycloakUtils; From ed358918f42bce46462dd881f6e1361a74a33401 Mon Sep 17 00:00:00 2001 From: James Nord Date: Wed, 11 Sep 2024 17:46:45 +0100 Subject: [PATCH 15/16] s/TestContainers/Testcontainers Testcontainers is with a lower case c --- docs/BROWSER.md | 2 +- docs/DOCKER.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/BROWSER.md b/docs/BROWSER.md index cfb0e0d816..2fde14429c 100644 --- a/docs/BROWSER.md +++ b/docs/BROWSER.md @@ -70,7 +70,7 @@ If this is the case you can specify the address to use using: `SELENIUM_PROXY_HOSTNAME=ip.address.of.host mvn install` **Important**: this could exposed the proxy wider beyond your machine and expose other internal services, so this should only be used on private or internal networks to prevent any information leak. The same issue will also impact any other containers started that the tests that the Browser (rather than Jenkins) needs to access. -For TestContainers you can additionally set `TESTCONTAINERS_HOST_OVERRIDE=ip.address.of.host` +For [Testcontainers](https://testcontainers.com/) you can additionally set `TESTCONTAINERS_HOST_OVERRIDE=ip.address.of.host` ## Avoid focus steal with Xvnc on Linux If you select a real GUI browser, such as Firefox, diff --git a/docs/DOCKER.md b/docs/DOCKER.md index 2d5dfbdd88..d845f1f546 100644 --- a/docs/DOCKER.md +++ b/docs/DOCKER.md @@ -72,7 +72,7 @@ It's best explained with an example: If you are using a custom network for the container that executes the testing you may instruct the docker-fixtures to use the same one by setting the env variable `DOCKER_FIXTURES_NETWORK`to specify the network you want your fixtures to connect to. -# Using TestContainers with a remote webdriver +# Using Testcontainers with a remote webdriver If you are using a containerized webdriver (or any other remote webdriver) then any containers launched will not be reachable from the remote web browser. This will be an issue for some tests that require the browser interact with the container (e.g. for authentication). From 49d9388586ffeded925f8c26496d39e00072b6af Mon Sep 17 00:00:00 2001 From: James Nord Date: Wed, 11 Sep 2024 18:27:58 +0100 Subject: [PATCH 16/16] keep the spotless overlords happy --- .github/renovate.json | 2 +- src/test/java/plugins/OicAuthPluginTest.java | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/renovate.json b/.github/renovate.json index 980266d991..f35acd8733 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -84,7 +84,7 @@ }, { "fileMatch": ["src/test/java/plugins/OicAuthPluginTest.java"], - "matchStrings": [".* KEYCLOAK_IMAGE=\"(?.*?):(?.*?)@(?sha256:.*?)\";\n"], + "matchStrings": [".* KEYCLOAK_IMAGE =\n\\s*\"(?.*?):(?.*?)@(?sha256:.*?)\";\n"], "depNameTemplate": "{{{repo}}}", "datasourceTemplate": "docker" } diff --git a/src/test/java/plugins/OicAuthPluginTest.java b/src/test/java/plugins/OicAuthPluginTest.java index 11bb61f9a1..f03eb0f648 100644 --- a/src/test/java/plugins/OicAuthPluginTest.java +++ b/src/test/java/plugins/OicAuthPluginTest.java @@ -42,7 +42,8 @@ public class OicAuthPluginTest extends AbstractJUnitTest { private static final String REALM = "test-realm"; private static final String CLIENT = "jenkins"; - private static final String KEYCLOAK_IMAGE="keycloak/keycloak:25.0.4@sha256:bf788a3b7fd737143f98d4cb514cb9599c896acee01a26b2117a10bd99e23e11"; + private static final String KEYCLOAK_IMAGE = + "keycloak/keycloak:25.0.4@sha256:bf788a3b7fd737143f98d4cb514cb9599c896acee01a26b2117a10bd99e23e11"; @Rule public KeycloakContainer keycloak = new KeycloakContainer(KEYCLOAK_IMAGE);