Skip to content

Commit

Permalink
Minor improvements to invitation email templates
Browse files Browse the repository at this point in the history
Signed-off-by: Pedro Igor <[email protected]>
  • Loading branch information
pedroigor committed May 14, 2024
1 parent 7daa2a0 commit e899c95
Show file tree
Hide file tree
Showing 8 changed files with 33 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
package org.keycloak.email;

import org.keycloak.events.Event;
import org.keycloak.models.OrganizationModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.provider.Provider;
Expand Down Expand Up @@ -77,7 +78,7 @@ public interface EmailTemplateProvider extends Provider {

void sendVerifyEmail(String link, long expirationInMinutes) throws EmailException;

void sendOrgInviteEmail(String link, long expirationInMinutes) throws EmailException;
void sendOrgInviteEmail(OrganizationModel organization, String link, long expirationInMinutes) throws EmailException;

void sendEmailUpdateConfirmation(String link, long expirationInMinutes, String address) throws EmailException;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
import org.keycloak.forms.login.freemarker.model.UrlBean;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakUriInfo;
import org.keycloak.models.OrganizationModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.sessions.AuthenticationSessionModel;
Expand Down Expand Up @@ -163,10 +164,11 @@ public void sendVerifyEmail(String link, long expirationInMinutes) throws EmailE
}

@Override
public void sendOrgInviteEmail(String link, long expirationInMinutes) throws EmailException {
public void sendOrgInviteEmail(OrganizationModel organization, String link, long expirationInMinutes) throws EmailException {
Map<String, Object> attributes = new HashMap<>(this.attributes);
addLinkInfoIntoAttributes(link, expirationInMinutes, attributes);
send("orgInviteSubject", "org-invite.ftl", attributes);
attributes.put("organization", organization);
send("orgInviteSubject", List.of(organization.getName()), "org-invite.ftl", attributes);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ private Response sendInvitation(UserModel user) {
session.getProvider(EmailTemplateProvider.class)
.setRealm(realm)
.setUser(user)
.sendOrgInviteEmail(link, TimeUnit.SECONDS.toMinutes(tokenExpiration));
.sendOrgInviteEmail(organization, link, TimeUnit.SECONDS.toMinutes(tokenExpiration));
} catch (EmailException e) {
ServicesLogger.LOGGER.failedToSendEmail(e);
throw ErrorResponse.error("Failed to send invite email", Status.INTERNAL_SERVER_ERROR);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ public Response addMember(String id) {
}

@Path("invite-user")
@POST
public Response inviteUser(String email) {
return new OrganizationInvitationResource(session, organization, adminEvent).inviteUser(email);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,14 @@

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.junit.Assert.assertTrue;

import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import jakarta.mail.MessagingException;
import jakarta.mail.internet.MimeMessage;
import jakarta.ws.rs.core.Response;
import org.jboss.arquillian.graphene.page.Page;
Expand All @@ -33,8 +35,6 @@
import org.keycloak.admin.client.resource.OrganizationResource;
import org.keycloak.common.Profile.Feature;
import org.keycloak.common.util.UriUtils;
import org.keycloak.cookie.CookieProvider;
import org.keycloak.cookie.CookieScope;
import org.keycloak.cookie.CookieType;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
Expand All @@ -46,6 +46,7 @@
import org.keycloak.testsuite.pages.RegisterPage;
import org.keycloak.testsuite.util.GreenMailRule;
import org.keycloak.testsuite.util.MailUtils;
import org.keycloak.testsuite.util.MailUtils.EmailBody;
import org.keycloak.testsuite.util.UserBuilder;

@EnableFeature(Feature.ORGANIZATION)
Expand All @@ -71,7 +72,7 @@ public void configureTestRealm(RealmRepresentation testRealm) {
}

@Test
public void testInviteExistingUser() throws IOException {
public void testInviteExistingUser() throws IOException, MessagingException {
UserRepresentation user = UserBuilder.create()
.username("invited")
.email("[email protected]")
Expand All @@ -88,7 +89,9 @@ public void testInviteExistingUser() throws IOException {

MimeMessage message = greenMail.getLastReceivedMessage();
Assert.assertNotNull(message);
String link = MailUtils.getPasswordResetEmailLink(message);
Assert.assertEquals("Invitation to join the " + organizationName + " organization", message.getSubject());
EmailBody body = MailUtils.getBody(message);
String link = MailUtils.getLink(body.getHtml());
driver.navigate().to(link.trim());
// not yet a member
Assert.assertFalse(organization.members().getAll().stream().anyMatch(actual -> user.getId().equals(actual.getId())));
Expand All @@ -100,7 +103,7 @@ public void testInviteExistingUser() throws IOException {
}

@Test
public void testInviteNewUserRegistration() throws IOException {
public void testInviteNewUserRegistration() throws IOException, MessagingException {
UserRepresentation user = UserBuilder.create()
.username("invitedUser")
.email("inviteduser@email")
Expand All @@ -112,7 +115,14 @@ public void testInviteNewUserRegistration() throws IOException {

MimeMessage message = greenMail.getLastReceivedMessage();
Assert.assertNotNull(message);
String link = MailUtils.getPasswordResetEmailLink(message);
Assert.assertEquals("Invitation to join the " + organizationName + " organization", message.getSubject());
EmailBody body = MailUtils.getBody(message);
String link = MailUtils.getLink(body.getHtml());
String text = body.getHtml();
assertTrue(text.contains("<p>You were invited to join the " + organizationName + " organization. Click the link below to join. </p>"));
assertTrue(text.contains("<a href=\"" + link + "\" rel=\"nofollow\">Link to join the organization</a></p>"));
assertTrue(text.contains("Link to join the organization"));
assertTrue(text.contains("<p>If you dont want to join the organization, just ignore this message.</p>"));
String orgToken = UriUtils.parseQueryParameters(link, false).values().stream().map(strings -> strings.get(0)).findFirst().orElse(null);
Assert.assertNotNull(orgToken);
driver.navigate().to(link.trim());
Expand Down Expand Up @@ -144,7 +154,8 @@ public void testEmailDoesNotChangeOnRegistration() throws IOException {

MimeMessage message = greenMail.getLastReceivedMessage();
Assert.assertNotNull(message);
String link = MailUtils.getPasswordResetEmailLink(message);
EmailBody body = MailUtils.getBody(message);
String link = MailUtils.getLink(body.getHtml());
String orgToken = UriUtils.parseQueryParameters(link, false).values().stream().map(strings -> strings.get(0)).findFirst().orElse(null);
Assert.assertNotNull(orgToken);
driver.navigate().to(link.trim());
Expand Down Expand Up @@ -173,7 +184,8 @@ public void testLinkExpired() throws IOException {
setTimeOffset((int) TimeUnit.DAYS.toSeconds(1));
MimeMessage message = greenMail.getLastReceivedMessage();
Assert.assertNotNull(message);
String link = MailUtils.getPasswordResetEmailLink(message);
EmailBody body = MailUtils.getBody(message);
String link = MailUtils.getLink(body.getHtml());
String orgToken = UriUtils.parseQueryParameters(link, false).values().stream().map(strings -> strings.get(0)).findFirst().orElse(null);
Assert.assertNotNull(orgToken);
driver.navigate().to(link.trim());
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<#import "template.ftl" as layout>
<@layout.emailLayout>
${kcSanitize(msg("orgInviteBodyHtml", link, linkExpiration, realmName, linkExpirationFormatter(linkExpiration)))?no_esc}
${kcSanitize(msg("orgInviteBodyHtml", link, linkExpiration, realmName, organization.name, linkExpirationFormatter(linkExpiration)))?no_esc}
</@layout.emailLayout>
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
emailVerificationSubject=Verify email
emailVerificationBody=Someone has created a {2} account with this email address. If this was you, click the link below to verify your email address\n\n{0}\n\nThis link will expire within {3}.\n\nIf you didn''t create this account, just ignore this message.
emailVerificationBodyHtml=<p>Someone has created a {2} account with this email address. If this was you, click the link below to verify your email address</p><p><a href="{0}">Link to e-mail address verification</a></p><p>This link will expire within {3}.</p><p>If you didn''t create this account, just ignore this message.</p>
orgInviteBodyHtml=<p>Someone has invited your account {2} account to join their keycloak organization! Click the link below to join. </p><p><a href="{0}">Link to join the organization</a></p><p>This link will expire within {3}.</p><p>If you don't want to join the organization, just ignore this message.</p>
orgInviteSubject=Invitation to join the {0} organization
orgInviteBody=You were invited to join the "{3}" organization. Click the link below to join.\n\n{0}\n\nThis link will expire within {4}.\n\nIf you don't want to join the organization, just ignore this message.
orgInviteBodyHtml=<p>You were invited to join the {3} organization. Click the link below to join. </p><p><a href="{0}">Link to join the organization</a></p><p>This link will expire within {4}.</p><p>If you don't want to join the organization, just ignore this message.</p>
emailUpdateConfirmationSubject=Verify new email
emailUpdateConfirmationBody=To update your {2} account with email address {1}, click the link below\n\n{0}\n\nThis link will expire within {3}.\n\nIf you don''t want to proceed with this modification, just ignore this message.
emailUpdateConfirmationBodyHtml=<p>To update your {2} account with email address {1}, click the link below</p><p><a href="{0}">{0}</a></p><p>This link will expire within {3}.</p><p>If you don''t want to proceed with this modification, just ignore this message.</p>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
<#ftl output_format="plainText">
${kcSanitize(msg("orgInviteBodyHtml", link, linkExpiration, realmName, linkExpirationFormatter(linkExpiration)))}
${kcSanitize(msg("orgInviteBody", link, linkExpiration, realmName, organization.name, linkExpirationFormatter(linkExpiration)))}

0 comments on commit e899c95

Please sign in to comment.