Add email extension to test framework (#35096)

Closes #35094

Signed-off-by: stianst <stianst@gmail.com>
This commit is contained in:
Stian Thorgersen 2024-11-20 08:31:23 +01:00 committed by GitHub
parent 0d32d03c58
commit 89556c7b79
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 295 additions and 0 deletions

View file

@ -81,6 +81,12 @@
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.keycloak.test</groupId>
<artifactId>keycloak-test-framework-email-server</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>

View file

@ -40,6 +40,11 @@ public class ClientConfigBuilder {
return this;
}
public ClientConfigBuilder directAccessGrants() {
rep.setDirectAccessGrantsEnabled(true);
return this;
}
public ClientRepresentation build() {
return rep;
}

View file

@ -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());

View file

@ -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;

View file

@ -0,0 +1,51 @@
<?xml version="1.0"?>
<!--
~ Copyright 2016 Red Hat, Inc. and/or its affiliates
~ and other contributors as indicated by the @author tags.
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<parent>
<artifactId>keycloak-test-framework-parent</artifactId>
<groupId>org.keycloak.test</groupId>
<version>999.0.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>keycloak-test-framework-email-server</artifactId>
<name>Keycloak Test Framework - Email Server extension</name>
<packaging>jar</packaging>
<description>Email server extension for Keycloak Test Framework</description>
<properties>
<greenmail.version>2.1.1</greenmail.version>
</properties>
<dependencies>
<dependency>
<groupId>org.keycloak.test</groupId>
<artifactId>keycloak-test-framework-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.icegreen</groupId>
<artifactId>greenmail</artifactId>
<version>${greenmail.version}</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>

View file

@ -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<MailServer, InjectMailServer> {
@Override
public Class<InjectMailServer> getAnnotationClass() {
return InjectMailServer.class;
}
@Override
public Class<MailServer> getValueType() {
return MailServer.class;
}
@Override
public MailServer getValue(InstanceContext<MailServer, InjectMailServer> instanceContext) {
ManagedRealm realm = instanceContext.getDependency(ManagedRealm.class);
RealmRepresentation representation = realm.admin().toRepresentation();
Map<String, String> 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<MailServer, InjectMailServer> instanceContext) {
instanceContext.getValue().stop();
}
@Override
public boolean compatible(InstanceContext<MailServer, InjectMailServer> a, RequestedInstance<MailServer, InjectMailServer> b) {
return true;
}
}

View file

@ -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<Supplier<?, ?>> suppliers() {
return List.of(new GreenMailSupplier());
}
}

View file

@ -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);
}
}
}

View file

@ -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 { }

View file

@ -0,0 +1 @@
org.keycloak.test.framework.mail.GreenMailTestFrameworkExtension

View file

@ -72,6 +72,10 @@
<groupId>org.keycloak.test</groupId>
<artifactId>keycloak-test-framework-oauth-nimbus-poc</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak.test</groupId>
<artifactId>keycloak-test-framework-email-server</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak.test</groupId>
<artifactId>keycloak-test-framework-ui</artifactId>

View file

@ -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<String, String> 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();
}
}
}

View file

@ -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");
}

View file

@ -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();

View file

@ -40,6 +40,7 @@
<module>db-mysql</module>
<module>db-oracle</module>
<module>db-postgres</module>
<module>email-server</module>
<module>oauth-nimbus-poc</module>
<module>ui</module>
<module>examples</module>