Ignore Accept-Language header for email themes

Closes #10233

(cherry picked from commit 84f60bc121bc815711b615723833e19fd29838ac)

Signed-off-by: Giuseppe Graziano <g.graziano94@gmail.com>
Co-authored-by: Giuseppe Graziano <g.graziano94@gmail.com>
This commit is contained in:
Jon Koops 2024-11-22 12:08:45 +01:00 committed by GitHub
parent 63180be938
commit cd8a801a85
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 70 additions and 12 deletions

View File

@ -19,6 +19,7 @@ package org.keycloak.locale;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.provider.Provider;
import org.keycloak.theme.Theme;
import java.util.Locale;
@ -36,4 +37,8 @@ public interface LocaleSelectorProvider extends Provider {
*/
Locale resolveLocale(RealmModel realm, UserModel user);
default Locale resolveLocale(RealmModel realm, UserModel user, Theme.Type themeType) {
return resolveLocale(realm, user);
}
}

View File

@ -21,6 +21,7 @@ import org.keycloak.common.ClientConnection;
import org.keycloak.http.HttpRequest;
import org.keycloak.http.HttpResponse;
import org.keycloak.sessions.AuthenticationSessionModel;
import org.keycloak.theme.Theme;
import org.keycloak.urls.UrlType;
import jakarta.ws.rs.core.HttpHeaders;
@ -81,6 +82,10 @@ public interface KeycloakContext {
Locale resolveLocale(UserModel user);
default Locale resolveLocale(UserModel user, Theme.Type themeType) {
return resolveLocale(user);
}
/**
* Get current AuthenticationSessionModel, can be null out of the AuthenticationSession context.
*

View File

@ -199,7 +199,7 @@ public class FreeMarkerEmailTemplateProvider implements EmailTemplateProvider {
attributes.put("link", link);
attributes.put("linkExpiration", expirationInMinutes);
try {
Locale locale = session.getContext().resolveLocale(user);
Locale locale = session.getContext().resolveLocale(user, getTheme().getType());
attributes.put("linkExpirationFormatter", new LinkExpirationFormatterMethod(getTheme().getMessages(locale), locale));
} catch (IOException e) {
throw new EmailException("Failed to template email", e);
@ -214,7 +214,7 @@ public class FreeMarkerEmailTemplateProvider implements EmailTemplateProvider {
protected EmailTemplate processTemplate(String subjectKey, List<Object> subjectAttributes, String template, Map<String, Object> attributes) throws EmailException {
try {
Theme theme = getTheme();
Locale locale = session.getContext().resolveLocale(user);
Locale locale = session.getContext().resolveLocale(user, theme.getType());
attributes.put("locale", locale);
Properties messages = theme.getEnhancedMessages(realm, locale);

View File

@ -25,6 +25,8 @@ import org.keycloak.models.UserModel;
import org.keycloak.sessions.AuthenticationSessionModel;
import jakarta.ws.rs.core.HttpHeaders;
import org.keycloak.theme.Theme;
import java.util.List;
import java.util.Locale;
import java.util.stream.Collectors;
@ -41,6 +43,11 @@ public class DefaultLocaleSelectorProvider implements LocaleSelectorProvider {
@Override
public Locale resolveLocale(RealmModel realm, UserModel user) {
return resolveLocale(realm, user, null);
}
@Override
public Locale resolveLocale(RealmModel realm, UserModel user, Theme.Type themeType) {
HttpHeaders requestHeaders = session.getContext().getRequestHeaders();
AuthenticationSessionModel session = this.session.getContext().getAuthenticationSession();
@ -48,7 +55,7 @@ public class DefaultLocaleSelectorProvider implements LocaleSelectorProvider {
return Locale.ENGLISH;
}
Locale userLocale = getUserLocale(realm, session, user, requestHeaders);
Locale userLocale = getUserLocale(realm, session, user, requestHeaders, themeType);
if (userLocale != null) {
return userLocale;
}
@ -61,7 +68,7 @@ public class DefaultLocaleSelectorProvider implements LocaleSelectorProvider {
return Locale.ENGLISH;
}
private Locale getUserLocale(RealmModel realm, AuthenticationSessionModel session, UserModel user, HttpHeaders requestHeaders) {
private Locale getUserLocale(RealmModel realm, AuthenticationSessionModel session, UserModel user, HttpHeaders requestHeaders, Theme.Type themeType) {
Locale locale;
locale = getUserSelectedLocale(realm, session);
@ -74,6 +81,10 @@ public class DefaultLocaleSelectorProvider implements LocaleSelectorProvider {
return locale;
}
if(Theme.Type.EMAIL.equals(themeType)) {
return null;
}
locale = getClientSelectedLocale(realm, session);
if (locale != null) {
return locale;

View File

@ -30,6 +30,7 @@ import org.keycloak.models.OrganizationModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.sessions.AuthenticationSessionModel;
import org.keycloak.theme.Theme;
import org.keycloak.urls.UrlType;
import java.net.URI;
@ -144,6 +145,11 @@ public abstract class DefaultKeycloakContext implements KeycloakContext {
return session.getProvider(LocaleSelectorProvider.class).resolveLocale(getRealm(), user);
}
@Override
public Locale resolveLocale(UserModel user, Theme.Type themeType) {
return session.getProvider(LocaleSelectorProvider.class).resolveLocale(getRealm(), user, themeType);
}
@Override
public AuthenticationSessionModel getAuthenticationSession() {
return authenticationSession;

View File

@ -29,14 +29,18 @@ import java.io.IOException;
import java.util.Arrays;
import java.util.Locale;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.jboss.arquillian.graphene.page.Page;
import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
import org.keycloak.admin.client.resource.UserResource;
import org.keycloak.broker.provider.util.SimpleHttp;
import org.keycloak.models.UserModel;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.testsuite.admin.ApiUtil;
import org.keycloak.testsuite.broker.util.SimpleHttpDefault;
import org.keycloak.testsuite.pages.InfoPage;
import org.keycloak.testsuite.pages.LoginPage;
import org.keycloak.testsuite.pages.LoginPasswordResetPage;
@ -127,6 +131,33 @@ public class EmailTest extends AbstractI18NTest {
}
}
@Test
public void updatePasswordFromAdmin() throws MessagingException, IOException {
changeUserLocale(null);
try {
UserResource testUser = ApiUtil.findUserByUsernameId(testRealm(), "login-test");
CloseableHttpClient httpClient = HttpClientBuilder.create().build();
SimpleHttp.Response responseGet = SimpleHttpDefault.doPut(getAuthServerRoot() + "admin/realms/test/users/" + testUser.toRepresentation().getId() + "/execute-actions-email", httpClient)
.auth(adminClient.tokenManager().getAccessTokenString())
.header("Accept-Language", "de")
.json(Arrays.asList(UserModel.RequiredAction.UPDATE_PASSWORD.toString()))
.asResponse();
assertEquals(responseGet.getStatus(), 204);
MimeMessage message = greenMail.getReceivedMessages()[0];
String textBody = MailUtils.getBody(message).getText();
Assert.assertThat(textBody, containsString("Your administrator has just requested"));
} catch (Exception e) {
Assert.fail(e.getMessage());
} finally {
// Revert
changeUserLocale("en");
}
}
private void verifyResetPassword(String expectedSubject, String expectedTextBodyContent, int expectedMsgCount)
throws MessagingException, IOException {
loginPage.open();
@ -152,32 +183,32 @@ public class EmailTest extends AbstractI18NTest {
public void changeLocaleOnInfoPage() throws InterruptedException, IOException {
UserResource testUser = ApiUtil.findUserByUsernameId(testRealm(), "login-test");
testUser.executeActionsEmail(Arrays.asList(UserModel.RequiredAction.UPDATE_PASSWORD.toString()));
if (!greenMail.waitForIncomingEmail(1)) {
Assert.fail("Error when receiving email");
}
String link = MailUtils.getPasswordResetEmailLink(greenMail.getLastReceivedMessage());
// Make sure kc_locale added to link doesn't set locale
link += "&kc_locale=de";
DroneUtils.getCurrentDriver().navigate().to(link);
WaitUtils.waitForPageToLoad();
Assert.assertTrue("Expected to be on InfoPage, but it was on " + DroneUtils.getCurrentDriver().getTitle(), infoPage.isCurrent());
assertThat(infoPage.getLanguageDropdownText(), is(equalTo("English")));
infoPage.openLanguage("Deutsch");
assertThat(DroneUtils.getCurrentDriver().getPageSource(), containsString("Passwort aktualisieren"));
infoPage.clickToContinueDe();
loginPasswordUpdatePage.openLanguage("English");
loginPasswordUpdatePage.changePassword("pass", "pass");
WaitUtils.waitForPageToLoad();
Assert.assertTrue("Expected to be on InfoPage, but it was on " + DroneUtils.getCurrentDriver().getTitle(), infoPage.isCurrent());
assertThat(infoPage.getInfo(), containsString("Your account has been updated."));