Skip to content

Commit

Permalink
Add email extension to test framework (keycloak#35096)
Browse files Browse the repository at this point in the history
Closes keycloak#35094

Signed-off-by: stianst <stianst@gmail.com>
  • Loading branch information
stianst authored Nov 20, 2024
1 parent 0d32d03 commit 89556c7
Show file tree
Hide file tree
Showing 15 changed files with 295 additions and 0 deletions.
6 changes: 6 additions & 0 deletions test-framework/bom/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ public ClientConfigBuilder serviceAccount() {
return this;
}

public ClientConfigBuilder directAccessGrants() {
rep.setDirectAccessGrantsEnabled(true);
return this;
}

public ClientRepresentation build() {
return rep;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
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 {

Expand Down Expand Up @@ -43,6 +46,14 @@ public RealmConfigBuilder defaultSignatureAlgorithm(String algorithm) {
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());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ public UserConfigBuilder email(String email) {
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;
Expand Down
51 changes: 51 additions & 0 deletions test-framework/email-server/pom.xml
Original file line number Diff line number Diff line change
@@ -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>
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -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());
}

}
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
Original file line number Diff line number Diff line change
@@ -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 { }
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
org.keycloak.test.framework.mail.GreenMailTestFrameworkExtension
4 changes: 4 additions & 0 deletions test-framework/examples/tests/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
Original file line number Diff line number Diff line change
@@ -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();
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
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;
Expand Down Expand Up @@ -65,6 +66,15 @@ public TokenResponse clientCredentialGrant() throws IOException, GeneralExceptio
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();
Expand Down
1 change: 1 addition & 0 deletions test-framework/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down

0 comments on commit 89556c7

Please sign in to comment.