Skip to content

Commit

Permalink
Merge pull request #7 from KvalitetsIT/keycloak-24.0.3
Browse files Browse the repository at this point in the history
Update tests to support Keycloak 24.
  • Loading branch information
JonasPed authored Apr 25, 2024
2 parents 943d7bf + 2cc0eee commit 85d86c4
Show file tree
Hide file tree
Showing 5 changed files with 171 additions and 27 deletions.
13 changes: 13 additions & 0 deletions compose/realms/attribute-realm.json
Original file line number Diff line number Diff line change
Expand Up @@ -719,6 +719,19 @@
"friendly.name": "organisation",
"attribute.name": "organisation"
}
},
{
"id": "8b0a3f60-1898-48f2-aef4-96bf5154ca8e",
"name": "email",
"protocol": "saml",
"protocolMapper": "saml-user-attribute-mapper",
"consentRequired": false,
"config": {
"attribute.nameformat": "Basic",
"user.attribute": "email",
"friendly.name": "email",
"attribute.name": "email"
}
}
],
"defaultClientScopes": [
Expand Down
72 changes: 72 additions & 0 deletions compose/realms/test-realm.json
Original file line number Diff line number Diff line change
Expand Up @@ -603,6 +603,57 @@
"defaultClientScopes": [],
"optionalClientScopes": []
},
{
"id": "74cea116-df9e-453f-b236-916534c5d5a0",
"clientId": "oauth2-proxy",
"name": "oauth2-proxy",
"description": "",
"rootUrl": "",
"adminUrl": "",
"baseUrl": "",
"surrogateAuthRequired": false,
"enabled": true,
"alwaysDisplayInConsole": false,
"clientAuthenticatorType": "client-secret",
"secret": "4AaKzgtdzIlsUyIQQKDYy6V08aozOpvE",
"redirectUris": [
"*"
],
"webOrigins": [],
"notBefore": 0,
"bearerOnly": false,
"consentRequired": false,
"standardFlowEnabled": true,
"implicitFlowEnabled": false,
"directAccessGrantsEnabled": true,
"serviceAccountsEnabled": false,
"publicClient": false,
"frontchannelLogout": true,
"protocol": "openid-connect",
"attributes": {
"oidc.ciba.grant.enabled": "false",
"client.secret.creation.time": "1713962611",
"backchannel.logout.session.required": "true",
"post.logout.redirect.uris": "*",
"oauth2.device.authorization.grant.enabled": "false",
"backchannel.logout.revoke.offline.tokens": "false"
},
"authenticationFlowBindingOverrides": {},
"fullScopeAllowed": true,
"nodeReRegistrationTimeout": -1,
"defaultClientScopes": [
"web-origins",
"roles",
"profile",
"email"
],
"optionalClientScopes": [
"address",
"phone",
"offline_access",
"microprofile-jwt"
]
},
{
"id": "9ee3a29e-b0b6-4160-9f26-16ba7cf497ca",
"clientId": "realm-management",
Expand Down Expand Up @@ -1255,6 +1306,19 @@
"attribute.friendly.name": "organisation",
"attribute.name": "organisation"
}
},
{
"id": "433e2d47-2dc6-4ac2-834f-402df83e8216",
"name": "email",
"identityProviderAlias": "saml",
"identityProviderMapper": "saml-user-attribute-idp-mapper",
"config": {
"syncMode": "INHERIT",
"user.attribute": "email",
"attribute.friendly.name": "email",
"attribute.name.format": "ATTRIBUTE_FORMAT_BASIC",
"attribute.name": "email"
}
}
],
"components": {
Expand Down Expand Up @@ -1675,6 +1739,7 @@
"userSetupAllowed": false
},
{
"authenticatorConfig": "saml-redirect",
"authenticator": "identity-provider-redirector",
"authenticatorFlow": false,
"requirement": "ALTERNATIVE",
Expand Down Expand Up @@ -2019,6 +2084,13 @@
"config": {
"update.profile.on.first.login": "missing"
}
},
{
"id": "2985f2df-b366-40e2-8c60-38c4215fb59b",
"alias": "saml-redirect",
"config": {
"defaultProvider": "saml"
}
}
],
"requiredActions": [
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@

<properties>
<java.version>17</java.version>
<keycloak.version>22.0.2</keycloak.version>
<keycloak.version>24.0.3</keycloak.version>
<testcontainers.version>1.18.3</testcontainers.version>
<jacoco.version>0.8.10</jacoco.version>
</properties>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@
import org.testcontainers.images.builder.ImageFromDockerfile;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.file.Paths;
import java.time.Duration;
import java.util.*;
import java.util.function.Consumer;
Expand All @@ -40,30 +43,33 @@ public class AbstractIntegrationTest {
private static final RestTemplate restTemplate = new RestTemplate();

private static String accessToken;
private static int proxyPort;
private static String proxyHost;

@BeforeClass
public static void setupTestEnvironment() throws JSONException {
public static void setupTestEnvironment() throws JSONException, IOException {
Network n = Network.newNetwork();

// Start Keycloak service
GenericContainer<?> keycloakContainer = getKeycloakContainer(n);
var version = calculateKeycloakVersion();
GenericContainer<?> keycloakContainer = getKeycloakContainer(n, version);
keycloakContainer.start();

setupProxyContainer(n);

logContainerOutput(keycloakContainer, keycloakLogger);
keycloakPort = keycloakContainer.getMappedPort(8080);
keycloakHost = keycloakContainer.getHost();
accessToken = getAccessToken();
}

private static GenericContainer<?> getKeycloakContainer(Network n) {
logger.info("Starting keycloak container version 20.0");
Consumer<CreateContainerCmd> cmd = e -> e.withHostConfig(new HostConfig().withPortBindings(new PortBinding(Ports.Binding.bindPort(8080), new ExposedPort(8080))));
private static GenericContainer<?> getKeycloakContainer(Network n, String tag) {
logger.info("Starting keycloak container version {}", tag);
Consumer<CreateContainerCmd> cmd = e -> e.withPortBindings(new PortBinding(Ports.Binding.bindPort(8080), new ExposedPort(8080)));

var image = new ImageFromDockerfile()
.withFileFromFile("keycloak-auth-multi-value.jar", new File("../service/target/service.jar"))
.withDockerfileFromBuilder(builder ->
builder.from("quay.io/keycloak/keycloak:22.0")
builder.from("quay.io/keycloak/keycloak:" + tag)
.copy("keycloak-auth-multi-value.jar", "/opt/keycloak/providers/keycloak-auth-multi-value.jar")
.build());

Expand All @@ -77,11 +83,69 @@ private static GenericContainer<?> getKeycloakContainer(Network n) {
.withEnv("required_action_choose_attribute_attribute_name", "organisation")

.withNetwork(n)
.withNetworkAliases("keycloak")
.withExposedPorts(8080)
.withCreateContainerCmdModifier(cmd)
.waitingFor(Wait.forHttp("/auth").withStartupTimeout(Duration.ofMinutes(3)));
}

private static String calculateKeycloakVersion() throws IOException {
var properties = new Properties();
var path = Paths.get(".").toAbsolutePath().normalize().toString();
if(path.endsWith("qa")) {
properties.load(new FileInputStream("../target/classes/git.properties"));
}
else {
properties.load(new FileInputStream("target/classes/git.properties"));
}
var gitBranch = properties.getProperty("git.branch");

if(gitBranch.startsWith("keycloak-")) {
return gitBranch.substring(9);
}

return "latest";
}

private static void setupProxyContainer(Network n) {
GenericContainer<?> nginx = new GenericContainer<>("nginx")
.withExposedPorts(80)
.waitingFor(Wait.forHttp("/"))
.withNetworkAliases("nginx")
.withNetwork(n);
nginx.start();

GenericContainer<?> oauthProxy = new GenericContainer<>("quay.io/oauth2-proxy/oauth2-proxy:v7.6.0")
.withNetwork(n)
.withExposedPorts(4180)
.withCommand(
"--provider=oidc",
"--client-id=oauth2-proxy",
"--client-secret=4AaKzgtdzIlsUyIQQKDYy6V08aozOpvE",
"--http-address=0.0.0.0:4180",
"--insecure-oidc-allow-unverified-email=true",
"--session-store-type=cookie",
"--upstream=http://nginx:80",
"--skip-provider-button=true",
"--skip-auth-preflight",
"--errors-to-info-log",
"--cookie-name=test",
"--skip-oidc-discovery",
"--redeem-url=http://keycloak:8080/auth/realms/Test/protocol/openid-connect/token",
"--login-url=http://localhost:8080/auth/realms/Test/protocol/openid-connect/auth",
"--oidc-jwks-url=http://keycloak:8080/auth/realms/Test/protocol/openid-connect/certs",
"--skip-oidc-discovery=true",
"--cookie-secure=false",

"--oidc-issuer-url=http://localhost:8080/auth/realms/Test",
"--cookie-secret=ThisSecretShouldBeThisLong_12345",
"--email-domain=*")
.waitingFor(Wait.forListeningPort());
oauthProxy.start();
proxyPort = oauthProxy.getMappedPort(4180);
proxyHost = oauthProxy.getContainerIpAddress();
}

private static void logContainerOutput(GenericContainer<?> container, Logger logger) {
logger.info("Attaching logger to container: " + container.getContainerInfo().getName());
Slf4jLogConsumer logConsumer = new Slf4jLogConsumer(logger);
Expand Down Expand Up @@ -111,6 +175,7 @@ public static String addUserAttributeRealm(String userName, List<String> attribu
credential.setTemporary(false);
user.setFirstName("John");
user.setLastName("Doe");
user.setEmail(String.format("%s@example.com", UUID.randomUUID()));
user.getCredentials().add(credential);
user.getAttributes().put("USER_ID", Collections.singletonList(userId));
if (attributeList.size() > 0) {
Expand Down Expand Up @@ -191,4 +256,8 @@ private static String getAccessToken() throws JSONException {
public static String appendToKeycloakHostAndPort(String url) {
return "http://"+ keycloakHost +":"+keycloakPort+url;
}

public static String appendToProxyHostAndPort(String url) {
return "http://" + proxyHost + ":" + proxyPort+url;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public void testRequiredActionNoAttribute() throws IOException, JSONException {
WebClient webdriver = getWebDriver();

//When..Then
HtmlPage afterLogin = doLoginFlow(webdriver, appendToKeycloakHostAndPort("/auth/realms/Test/account"), username, password);
HtmlPage afterLogin = doLoginFlow(webdriver, username, password);
assertFalse(isAttributeInputPage(afterLogin));

// User is created and has no attributes
Expand All @@ -52,7 +52,7 @@ public void testRequiredActionOneAttribute() throws IOException, JSONException {
WebClient webdriver = getWebDriver();

//When..Then
HtmlPage afterLogin = doLoginFlow(webdriver, appendToKeycloakHostAndPort("/auth/realms/Test/account"), username, password);
HtmlPage afterLogin = doLoginFlow(webdriver, username, password);
assertFalse(isAttributeInputPage(afterLogin));

// User is created and has correct attribute
Expand Down Expand Up @@ -83,7 +83,7 @@ public void testRequiredActionManyAttributes() throws IOException, JSONException
WebClient webdriver = getWebDriver();

//When..Then
HtmlPage afterLogin = doLoginFlow(webdriver, appendToKeycloakHostAndPort("/auth/realms/Test/account"), username, password);
HtmlPage afterLogin = doLoginFlow(webdriver, username, password);
assertTrue(isAttributeInputPage(afterLogin));

HtmlPage afterAttributeChoice = setAttribute(afterLogin, "attribute2");
Expand All @@ -100,11 +100,9 @@ public void testRequiredActionManyAttributes() throws IOException, JSONException
assertEquals(1, user.getAttributes().get("organisation").size());
assertEquals("attribute2", user.getAttributes().get("organisation").get(0));

// Logout
doLogoutFlow(webdriver, appendToKeycloakHostAndPort("/auth/realms/Test/account"));

webdriver = getWebDriver();
// Login again and choose new attribute
HtmlPage afterSecondLogin = doLoginFlow(webdriver, appendToKeycloakHostAndPort("/auth/realms/Test/account"), username, password);
HtmlPage afterSecondLogin = doLoginFlow(webdriver, username, password);
assertTrue(isAttributeInputPage(afterSecondLogin));

HtmlPage afterAttributeChoiceSecondLogin = setAttribute(afterSecondLogin, "attribute4");
Expand All @@ -121,20 +119,12 @@ public void testRequiredActionManyAttributes() throws IOException, JSONException
}


public HtmlPage doLoginFlow(WebClient wc, String accountUrl, String username, String password) throws IOException {

// Get the account page
HtmlPage mainPage = wc.getPage(accountUrl);
var loginUrl = mainPage.executeJavaScript("keycloak.createLoginUrl();").getJavaScriptResult();

// Click login
HtmlPage loginPage = wc.getPage(loginUrl.toString());

// Select "saml" IDP
HtmlPage samllogin = loginPage.getElementById("social-saml").click();
public HtmlPage doLoginFlow(WebClient wc, String username, String password) throws IOException {
// Access oauth2 proxy. It redirects to keycloak
HtmlPage mainPage = wc.getPage(appendToProxyHostAndPort("/"));

// Do login
HtmlForm loginForm = samllogin.getForms().get(0);
HtmlForm loginForm = mainPage.getForms().get(0);
loginForm.getInputByName("username").setValueAttribute(username);
loginForm.getInputByName("password").setValueAttribute(password);

Expand Down

0 comments on commit 85d86c4

Please sign in to comment.