From 89556c7b790e720af7bcb2c3efc42ca2a57dccb0 Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Wed, 20 Nov 2024 08:31:23 +0100 Subject: [PATCH] Add email extension to test framework (#35096) Closes #35094 Signed-off-by: stianst --- test-framework/bom/pom.xml | 6 ++ .../framework/realm/ClientConfigBuilder.java | 5 ++ .../framework/realm/RealmConfigBuilder.java | 11 +++ .../framework/realm/UserConfigBuilder.java | 5 ++ test-framework/email-server/pom.xml | 51 +++++++++++++ .../framework/mail/GreenMailSupplier.java | 52 ++++++++++++++ .../mail/GreenMailTestFrameworkExtension.java | 15 ++++ .../test/framework/mail/MailServer.java | 52 ++++++++++++++ .../mail/annotations/InjectMailServer.java | 10 +++ ...loak.test.framework.TestFrameworkExtension | 1 + test-framework/examples/tests/pom.xml | 4 ++ .../org/keycloak/test/examples/EmailTest.java | 71 +++++++++++++++++++ .../DefaultOAuthClientConfiguration.java | 1 + .../framework/oauth/nimbus/OAuthClient.java | 10 +++ test-framework/pom.xml | 1 + 15 files changed, 295 insertions(+) create mode 100755 test-framework/email-server/pom.xml create mode 100644 test-framework/email-server/src/main/java/org/keycloak/test/framework/mail/GreenMailSupplier.java create mode 100644 test-framework/email-server/src/main/java/org/keycloak/test/framework/mail/GreenMailTestFrameworkExtension.java create mode 100644 test-framework/email-server/src/main/java/org/keycloak/test/framework/mail/MailServer.java create mode 100644 test-framework/email-server/src/main/java/org/keycloak/test/framework/mail/annotations/InjectMailServer.java create mode 100644 test-framework/email-server/src/main/resources/META-INF/services/org.keycloak.test.framework.TestFrameworkExtension create mode 100644 test-framework/examples/tests/src/test/java/org/keycloak/test/examples/EmailTest.java diff --git a/test-framework/bom/pom.xml b/test-framework/bom/pom.xml index 5a5576e8c5a..0c7162e3b08 100755 --- a/test-framework/bom/pom.xml +++ b/test-framework/bom/pom.xml @@ -81,6 +81,12 @@ ${project.version} test + + org.keycloak.test + keycloak-test-framework-email-server + ${project.version} + test + diff --git a/test-framework/core/src/main/java/org/keycloak/test/framework/realm/ClientConfigBuilder.java b/test-framework/core/src/main/java/org/keycloak/test/framework/realm/ClientConfigBuilder.java index ad28b807437..2ab1d35401b 100644 --- a/test-framework/core/src/main/java/org/keycloak/test/framework/realm/ClientConfigBuilder.java +++ b/test-framework/core/src/main/java/org/keycloak/test/framework/realm/ClientConfigBuilder.java @@ -40,6 +40,11 @@ public class ClientConfigBuilder { return this; } + public ClientConfigBuilder directAccessGrants() { + rep.setDirectAccessGrantsEnabled(true); + return this; + } + public ClientRepresentation build() { return rep; } diff --git a/test-framework/core/src/main/java/org/keycloak/test/framework/realm/RealmConfigBuilder.java b/test-framework/core/src/main/java/org/keycloak/test/framework/realm/RealmConfigBuilder.java index 4fd3bf8d2e9..68ff6482421 100644 --- a/test-framework/core/src/main/java/org/keycloak/test/framework/realm/RealmConfigBuilder.java +++ b/test-framework/core/src/main/java/org/keycloak/test/framework/realm/RealmConfigBuilder.java @@ -4,6 +4,9 @@ import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.RolesRepresentation; import java.util.Arrays; +import java.util.EventListener; +import java.util.LinkedList; +import java.util.List; public class RealmConfigBuilder { @@ -43,6 +46,14 @@ public class RealmConfigBuilder { return this; } + public RealmConfigBuilder eventsListeners(String... eventListeners) { + if (rep.getEventsListeners() == null) { + rep.setEventsListeners(new LinkedList<>()); + } + rep.getEventsListeners().addAll(List.of(eventListeners)); + return this; + } + public RealmConfigBuilder roles(String... roleNames) { if (rep.getRoles() == null) { rep.setRoles(new RolesRepresentation()); diff --git a/test-framework/core/src/main/java/org/keycloak/test/framework/realm/UserConfigBuilder.java b/test-framework/core/src/main/java/org/keycloak/test/framework/realm/UserConfigBuilder.java index 71055ee1847..4bd714c9a8f 100644 --- a/test-framework/core/src/main/java/org/keycloak/test/framework/realm/UserConfigBuilder.java +++ b/test-framework/core/src/main/java/org/keycloak/test/framework/realm/UserConfigBuilder.java @@ -37,6 +37,11 @@ public class UserConfigBuilder { return this; } + public UserConfigBuilder emailVerified() { + rep.setEmailVerified(true); + return this; + } + public UserConfigBuilder password(String password) { rep.setCredentials(Collections.combine(rep.getCredentials(), Representations.toCredential(CredentialRepresentation.PASSWORD, password))); return this; diff --git a/test-framework/email-server/pom.xml b/test-framework/email-server/pom.xml new file mode 100755 index 00000000000..6e4f981e1e1 --- /dev/null +++ b/test-framework/email-server/pom.xml @@ -0,0 +1,51 @@ + + + + + + keycloak-test-framework-parent + org.keycloak.test + 999.0.0-SNAPSHOT + ../pom.xml + + 4.0.0 + + keycloak-test-framework-email-server + Keycloak Test Framework - Email Server extension + jar + Email server extension for Keycloak Test Framework + + + 2.1.1 + + + + + org.keycloak.test + keycloak-test-framework-core + ${project.version} + + + com.icegreen + greenmail + ${greenmail.version} + compile + + + diff --git a/test-framework/email-server/src/main/java/org/keycloak/test/framework/mail/GreenMailSupplier.java b/test-framework/email-server/src/main/java/org/keycloak/test/framework/mail/GreenMailSupplier.java new file mode 100644 index 00000000000..a651784ff3e --- /dev/null +++ b/test-framework/email-server/src/main/java/org/keycloak/test/framework/mail/GreenMailSupplier.java @@ -0,0 +1,52 @@ +package org.keycloak.test.framework.mail; + +import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.test.framework.mail.annotations.InjectMailServer; +import org.keycloak.test.framework.injection.InstanceContext; +import org.keycloak.test.framework.injection.RequestedInstance; +import org.keycloak.test.framework.injection.Supplier; +import org.keycloak.test.framework.realm.ManagedRealm; + +import java.util.HashMap; +import java.util.Map; + +public class GreenMailSupplier implements Supplier { + + @Override + public Class getAnnotationClass() { + return InjectMailServer.class; + } + + @Override + public Class getValueType() { + return MailServer.class; + } + + @Override + public MailServer getValue(InstanceContext instanceContext) { + ManagedRealm realm = instanceContext.getDependency(ManagedRealm.class); + RealmRepresentation representation = realm.admin().toRepresentation(); + + Map config = new HashMap<>(); + config.put("from", "auto@keycloak.org"); + config.put("host", "localhost"); + config.put("port", "3025"); + + representation.setSmtpServer(config); + realm.admin().update(representation); + + MailServer mailServer = new MailServer(); + mailServer.start(); + return mailServer; + } + + @Override + public void close(InstanceContext instanceContext) { + instanceContext.getValue().stop(); + } + + @Override + public boolean compatible(InstanceContext a, RequestedInstance b) { + return true; + } +} diff --git a/test-framework/email-server/src/main/java/org/keycloak/test/framework/mail/GreenMailTestFrameworkExtension.java b/test-framework/email-server/src/main/java/org/keycloak/test/framework/mail/GreenMailTestFrameworkExtension.java new file mode 100644 index 00000000000..e596b9b838b --- /dev/null +++ b/test-framework/email-server/src/main/java/org/keycloak/test/framework/mail/GreenMailTestFrameworkExtension.java @@ -0,0 +1,15 @@ +package org.keycloak.test.framework.mail; + +import org.keycloak.test.framework.TestFrameworkExtension; +import org.keycloak.test.framework.injection.Supplier; + +import java.util.List; + +public class GreenMailTestFrameworkExtension implements TestFrameworkExtension { + + @Override + public List> suppliers() { + return List.of(new GreenMailSupplier()); + } + +} diff --git a/test-framework/email-server/src/main/java/org/keycloak/test/framework/mail/MailServer.java b/test-framework/email-server/src/main/java/org/keycloak/test/framework/mail/MailServer.java new file mode 100644 index 00000000000..e21f6ee7c8f --- /dev/null +++ b/test-framework/email-server/src/main/java/org/keycloak/test/framework/mail/MailServer.java @@ -0,0 +1,52 @@ +package org.keycloak.test.framework.mail; + +import com.icegreen.greenmail.store.FolderException; +import com.icegreen.greenmail.util.GreenMail; +import com.icegreen.greenmail.util.ServerSetup; +import jakarta.mail.internet.MimeMessage; +import org.keycloak.test.framework.injection.ManagedTestResource; + +public class MailServer extends ManagedTestResource { + + private static final int PORT = 3025; + private static final String HOST = "localhost"; + + private GreenMail greenMail; + + public void start() { + ServerSetup setup = new ServerSetup(PORT, HOST, "smtp"); + + greenMail = new GreenMail(setup); + greenMail.start(); + } + + public void stop() { + greenMail.stop(); + } + + public MimeMessage[] getReceivedMessages() { + return greenMail.getReceivedMessages(); + } + + public MimeMessage getLastReceivedMessage() { + MimeMessage[] receivedMessages = greenMail.getReceivedMessages(); + return receivedMessages != null && receivedMessages.length > 0 ? receivedMessages[receivedMessages.length - 1] : null; + } + + public boolean waitForIncomingEmail(long timeout, int emailCount) { + return greenMail.waitForIncomingEmail(timeout, emailCount); + } + + public boolean waitForIncomingEmail(int emailCount) { + return greenMail.waitForIncomingEmail(emailCount); + } + + @Override + public void runCleanup() { + try { + greenMail.purgeEmailFromAllMailboxes(); + } catch (FolderException e) { + throw new RuntimeException(e); + } + } +} diff --git a/test-framework/email-server/src/main/java/org/keycloak/test/framework/mail/annotations/InjectMailServer.java b/test-framework/email-server/src/main/java/org/keycloak/test/framework/mail/annotations/InjectMailServer.java new file mode 100644 index 00000000000..9e4a734b0ec --- /dev/null +++ b/test-framework/email-server/src/main/java/org/keycloak/test/framework/mail/annotations/InjectMailServer.java @@ -0,0 +1,10 @@ +package org.keycloak.test.framework.mail.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface InjectMailServer { } diff --git a/test-framework/email-server/src/main/resources/META-INF/services/org.keycloak.test.framework.TestFrameworkExtension b/test-framework/email-server/src/main/resources/META-INF/services/org.keycloak.test.framework.TestFrameworkExtension new file mode 100644 index 00000000000..7108670c459 --- /dev/null +++ b/test-framework/email-server/src/main/resources/META-INF/services/org.keycloak.test.framework.TestFrameworkExtension @@ -0,0 +1 @@ +org.keycloak.test.framework.mail.GreenMailTestFrameworkExtension \ No newline at end of file diff --git a/test-framework/examples/tests/pom.xml b/test-framework/examples/tests/pom.xml index ebdacdcb31e..69bf85b57e5 100644 --- a/test-framework/examples/tests/pom.xml +++ b/test-framework/examples/tests/pom.xml @@ -72,6 +72,10 @@ org.keycloak.test keycloak-test-framework-oauth-nimbus-poc + + org.keycloak.test + keycloak-test-framework-email-server + org.keycloak.test keycloak-test-framework-ui diff --git a/test-framework/examples/tests/src/test/java/org/keycloak/test/examples/EmailTest.java b/test-framework/examples/tests/src/test/java/org/keycloak/test/examples/EmailTest.java new file mode 100644 index 00000000000..f2ecdc2db32 --- /dev/null +++ b/test-framework/examples/tests/src/test/java/org/keycloak/test/examples/EmailTest.java @@ -0,0 +1,71 @@ +package org.keycloak.test.examples; + +import com.nimbusds.oauth2.sdk.GeneralException; +import jakarta.mail.MessagingException; +import jakarta.mail.internet.MimeMessage; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.keycloak.events.email.EmailEventListenerProviderFactory; +import org.keycloak.test.framework.annotations.InjectRealm; +import org.keycloak.test.framework.annotations.InjectUser; +import org.keycloak.test.framework.annotations.KeycloakIntegrationTest; +import org.keycloak.test.framework.mail.MailServer; +import org.keycloak.test.framework.mail.annotations.InjectMailServer; +import org.keycloak.test.framework.oauth.nimbus.OAuthClient; +import org.keycloak.test.framework.oauth.nimbus.annotations.InjectOAuthClient; +import org.keycloak.test.framework.realm.ManagedRealm; +import org.keycloak.test.framework.realm.ManagedUser; +import org.keycloak.test.framework.realm.RealmConfig; +import org.keycloak.test.framework.realm.RealmConfigBuilder; +import org.keycloak.test.framework.realm.UserConfig; +import org.keycloak.test.framework.realm.UserConfigBuilder; + +import java.io.IOException; +import java.util.Map; + +@KeycloakIntegrationTest +public class EmailTest { + + @InjectRealm(config = EmailSenderRealmConfig.class) + ManagedRealm realm; + + @InjectUser(config = UserWithEmail.class) + ManagedUser user; + + @InjectMailServer + MailServer mail; + + @InjectOAuthClient + OAuthClient oAuthClient; + + @Test + public void testEmail() throws GeneralException, IOException, MessagingException { + oAuthClient.resourceOwnerCredentialGrant(user.getUsername(), "invalid"); + + Map smtpServer = realm.admin().toRepresentation().getSmtpServer(); + Assertions.assertEquals("auto@keycloak.org", smtpServer.get("from")); + Assertions.assertEquals("localhost", smtpServer.get("host")); + Assertions.assertEquals("3025", smtpServer.get("port")); + + mail.waitForIncomingEmail(1); + MimeMessage lastReceivedMessage = mail.getLastReceivedMessage(); + Assertions.assertEquals("Login error", lastReceivedMessage.getSubject()); + } + + public static class EmailSenderRealmConfig implements RealmConfig { + + @Override + public RealmConfigBuilder configure(RealmConfigBuilder realm) { + return realm.eventsListeners(EmailEventListenerProviderFactory.ID); + } + } + + public static class UserWithEmail implements UserConfig { + + @Override + public UserConfigBuilder configure(UserConfigBuilder user) { + return user.username("test").email("test@local").password("password").emailVerified(); + } + } + +} diff --git a/test-framework/oauth-nimbus-poc/src/main/java/org/keycloak/test/framework/oauth/nimbus/DefaultOAuthClientConfiguration.java b/test-framework/oauth-nimbus-poc/src/main/java/org/keycloak/test/framework/oauth/nimbus/DefaultOAuthClientConfiguration.java index 245cdb835b9..531df143eab 100644 --- a/test-framework/oauth-nimbus-poc/src/main/java/org/keycloak/test/framework/oauth/nimbus/DefaultOAuthClientConfiguration.java +++ b/test-framework/oauth-nimbus-poc/src/main/java/org/keycloak/test/framework/oauth/nimbus/DefaultOAuthClientConfiguration.java @@ -9,6 +9,7 @@ public class DefaultOAuthClientConfiguration implements ClientConfig { public ClientConfigBuilder configure(ClientConfigBuilder client) { return client.clientId("test-oauth-client") .serviceAccount() + .directAccessGrants() .redirectUris("http://127.0.0.1/callback/oauth") .secret("test-secret"); } diff --git a/test-framework/oauth-nimbus-poc/src/main/java/org/keycloak/test/framework/oauth/nimbus/OAuthClient.java b/test-framework/oauth-nimbus-poc/src/main/java/org/keycloak/test/framework/oauth/nimbus/OAuthClient.java index 73c4fd5878a..29585eb104f 100644 --- a/test-framework/oauth-nimbus-poc/src/main/java/org/keycloak/test/framework/oauth/nimbus/OAuthClient.java +++ b/test-framework/oauth-nimbus-poc/src/main/java/org/keycloak/test/framework/oauth/nimbus/OAuthClient.java @@ -6,6 +6,7 @@ import com.nimbusds.oauth2.sdk.AuthorizationGrant; import com.nimbusds.oauth2.sdk.AuthorizationRequest; import com.nimbusds.oauth2.sdk.ClientCredentialsGrant; import com.nimbusds.oauth2.sdk.GeneralException; +import com.nimbusds.oauth2.sdk.ResourceOwnerPasswordCredentialsGrant; import com.nimbusds.oauth2.sdk.ResponseType; import com.nimbusds.oauth2.sdk.TokenIntrospectionRequest; import com.nimbusds.oauth2.sdk.TokenIntrospectionResponse; @@ -65,6 +66,15 @@ public class OAuthClient { return TokenResponse.parse(tokenRequest.toHTTPRequest().send()); } + public TokenResponse resourceOwnerCredentialGrant(String username, String password) throws GeneralException, IOException { + ResourceOwnerPasswordCredentialsGrant credentialsGrant = new ResourceOwnerPasswordCredentialsGrant(username, new Secret(password)); + ClientAuthentication clientAuthentication = getClientAuthentication(); + URI tokenEndpoint = getOIDCProviderMetadata().getTokenEndpointURI(); + + TokenRequest tokenRequest = new TokenRequest(tokenEndpoint, clientAuthentication, credentialsGrant); + return TokenResponse.parse(tokenRequest.toHTTPRequest().send()); + } + public TokenResponse tokenRequest(AuthorizationCode authorizationCode) throws IOException, GeneralException { AuthorizationGrant grant = new AuthorizationCodeGrant(authorizationCode, callbackServer.getRedirectionUri()); ClientAuthentication clientAuthentication = getClientAuthentication(); diff --git a/test-framework/pom.xml b/test-framework/pom.xml index 4c0ef7beb5c..fbff53bfb31 100755 --- a/test-framework/pom.xml +++ b/test-framework/pom.xml @@ -40,6 +40,7 @@ db-mysql db-oracle db-postgres + email-server oauth-nimbus-poc ui examples