mirror of
https://github.com/keycloak/keycloak.git
synced 2025-12-21 06:20:05 -06:00
Ignore Accept-Language header for reset email from admin api
Closes #36986 Signed-off-by: Giuseppe Graziano <g.graziano94@gmail.com>
This commit is contained in:
committed by
Marek Posolda
parent
fe66bb7cdf
commit
8833c0aa5d
@@ -108,6 +108,7 @@ public final class Constants {
|
||||
public static final String SKIP_LINK = "skipLink";
|
||||
public static final String TEMPLATE_ATTR_ACTION_URI = "actionUri";
|
||||
public static final String TEMPLATE_ATTR_REQUIRED_ACTIONS = "requiredActions";
|
||||
public static final String IGNORE_ACCEPT_LANGUAGE_HEADER = "IGNORE_ACCEPT_LANGUAGE_HEADER";
|
||||
|
||||
// Prefix for user attributes used in various "context"data maps
|
||||
public static final String USER_ATTRIBUTES_PREFIX = "user.attributes.";
|
||||
|
||||
@@ -41,4 +41,8 @@ public interface LocaleSelectorProvider extends Provider {
|
||||
return resolveLocale(realm, user);
|
||||
}
|
||||
|
||||
default Locale resolveLocale(RealmModel realm, UserModel user, boolean ignoreAcceptLanguageHeader) {
|
||||
return resolveLocale(realm, user);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -87,6 +87,10 @@ public interface KeycloakContext {
|
||||
return resolveLocale(user);
|
||||
}
|
||||
|
||||
default Locale resolveLocale(UserModel user, boolean ignoreAcceptLanguageHeader) {
|
||||
return resolveLocale(user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current AuthenticationSessionModel, can be null out of the AuthenticationSession context.
|
||||
*
|
||||
|
||||
@@ -36,6 +36,7 @@ import org.keycloak.email.freemarker.beans.ProfileBean;
|
||||
import org.keycloak.events.Event;
|
||||
import org.keycloak.events.EventType;
|
||||
import org.keycloak.forms.login.freemarker.model.UrlBean;
|
||||
import org.keycloak.models.Constants;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakUriInfo;
|
||||
import org.keycloak.models.OrganizationModel;
|
||||
@@ -200,7 +201,7 @@ public class FreeMarkerEmailTemplateProvider implements EmailTemplateProvider {
|
||||
attributes.put("link", link);
|
||||
attributes.put("linkExpiration", expirationInMinutes);
|
||||
try {
|
||||
Locale locale = session.getContext().resolveLocale(user, getTheme().getType());
|
||||
Locale locale = session.getContext().resolveLocale(user, Boolean.parseBoolean(String.valueOf(attributes.get(Constants.IGNORE_ACCEPT_LANGUAGE_HEADER))));
|
||||
attributes.put("linkExpirationFormatter", new LinkExpirationFormatterMethod(getTheme().getMessages(locale), locale));
|
||||
} catch (IOException e) {
|
||||
throw new EmailException("Failed to template email", e);
|
||||
@@ -214,10 +215,10 @@ 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, theme.getType());
|
||||
Locale locale = session.getContext().resolveLocale(user, Boolean.parseBoolean(String.valueOf(attributes.get(Constants.IGNORE_ACCEPT_LANGUAGE_HEADER))));
|
||||
attributes.put("locale", locale);
|
||||
|
||||
Theme theme = getTheme();
|
||||
Properties messages = theme.getEnhancedMessages(realm, locale);
|
||||
|
||||
String currentLanguageTag = locale.getLanguage();
|
||||
|
||||
@@ -43,11 +43,11 @@ public class DefaultLocaleSelectorProvider implements LocaleSelectorProvider {
|
||||
|
||||
@Override
|
||||
public Locale resolveLocale(RealmModel realm, UserModel user) {
|
||||
return resolveLocale(realm, user, null);
|
||||
return resolveLocale(realm, user, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Locale resolveLocale(RealmModel realm, UserModel user, Theme.Type themeType) {
|
||||
public Locale resolveLocale(RealmModel realm, UserModel user, boolean ignoreAcceptLanguageHeader) {
|
||||
HttpHeaders requestHeaders = session.getContext().getRequestHeaders();
|
||||
AuthenticationSessionModel session = this.session.getContext().getAuthenticationSession();
|
||||
|
||||
@@ -55,7 +55,7 @@ public class DefaultLocaleSelectorProvider implements LocaleSelectorProvider {
|
||||
return Locale.ENGLISH;
|
||||
}
|
||||
|
||||
Locale userLocale = getUserLocale(realm, session, user, requestHeaders, themeType);
|
||||
Locale userLocale = getUserLocale(realm, session, user, requestHeaders, ignoreAcceptLanguageHeader);
|
||||
if (userLocale != null) {
|
||||
return userLocale;
|
||||
}
|
||||
@@ -68,7 +68,7 @@ public class DefaultLocaleSelectorProvider implements LocaleSelectorProvider {
|
||||
return Locale.ENGLISH;
|
||||
}
|
||||
|
||||
private Locale getUserLocale(RealmModel realm, AuthenticationSessionModel session, UserModel user, HttpHeaders requestHeaders, Theme.Type themeType) {
|
||||
private Locale getUserLocale(RealmModel realm, AuthenticationSessionModel session, UserModel user, HttpHeaders requestHeaders, boolean ignoreAcceptLanguageHeader) {
|
||||
Locale locale;
|
||||
|
||||
locale = getUserSelectedLocale(realm, session);
|
||||
@@ -81,10 +81,6 @@ public class DefaultLocaleSelectorProvider implements LocaleSelectorProvider {
|
||||
return locale;
|
||||
}
|
||||
|
||||
if(Theme.Type.EMAIL.equals(themeType)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
locale = getClientSelectedLocale(realm, session);
|
||||
if (locale != null) {
|
||||
return locale;
|
||||
@@ -95,7 +91,7 @@ public class DefaultLocaleSelectorProvider implements LocaleSelectorProvider {
|
||||
return locale;
|
||||
}
|
||||
|
||||
locale = getAcceptLanguageHeaderLocale(realm, requestHeaders);
|
||||
locale = getAcceptLanguageHeaderLocale(realm, requestHeaders, ignoreAcceptLanguageHeader);
|
||||
if (locale != null) {
|
||||
return locale;
|
||||
}
|
||||
@@ -151,7 +147,12 @@ public class DefaultLocaleSelectorProvider implements LocaleSelectorProvider {
|
||||
return findLocale(realm, localeCookie);
|
||||
}
|
||||
|
||||
private Locale getAcceptLanguageHeaderLocale(RealmModel realm, HttpHeaders httpHeaders) {
|
||||
private Locale getAcceptLanguageHeaderLocale(RealmModel realm, HttpHeaders httpHeaders, boolean ignoreAcceptLanguageHeader) {
|
||||
|
||||
if (ignoreAcceptLanguageHeader) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (httpHeaders == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -162,8 +162,8 @@ public abstract class DefaultKeycloakContext implements KeycloakContext {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Locale resolveLocale(UserModel user, Theme.Type themeType) {
|
||||
return session.getProvider(LocaleSelectorProvider.class).resolveLocale(getRealm(), user, themeType);
|
||||
public Locale resolveLocale(UserModel user, boolean ignoreAcceptLanguageHeader) {
|
||||
return session.getProvider(LocaleSelectorProvider.class).resolveLocale(getRealm(), user, ignoreAcceptLanguageHeader);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1014,12 +1014,13 @@ public class UserResource {
|
||||
builder.queryParam("key", token.serialize(session, realm, session.getContext().getUri()));
|
||||
|
||||
String link = builder.build(realm.getName()).toString();
|
||||
|
||||
|
||||
this.session.getProvider(EmailTemplateProvider.class)
|
||||
.setAttribute(Constants.TEMPLATE_ATTR_REQUIRED_ACTIONS, token.getRequiredActions())
|
||||
.setRealm(realm)
|
||||
.setUser(user)
|
||||
.sendExecuteActions(link, TimeUnit.SECONDS.toMinutes(result.lifespan));
|
||||
.setAttribute(Constants.TEMPLATE_ATTR_REQUIRED_ACTIONS, token.getRequiredActions())
|
||||
.setAttribute(Constants.IGNORE_ACCEPT_LANGUAGE_HEADER, true)
|
||||
.setRealm(realm)
|
||||
.setUser(user)
|
||||
.sendExecuteActions(link, TimeUnit.SECONDS.toMinutes(result.lifespan));
|
||||
|
||||
//audit.user(user).detail(Details.EMAIL, user.getEmail()).detail(Details.CODE_ID, accessCode.getCodeId()).success();
|
||||
|
||||
|
||||
@@ -47,6 +47,9 @@ public class LoginPasswordResetPage extends LanguageComboboxAwarePage {
|
||||
@FindBy(id = "kc-info-wrapper")
|
||||
private WebElement infoWrapper;
|
||||
|
||||
@FindBy(id = "kc-reset-password-form")
|
||||
private WebElement formResetPassword;
|
||||
|
||||
public void changePassword() {
|
||||
UIUtils.clickLink(submitButton);
|
||||
}
|
||||
@@ -97,4 +100,8 @@ public class LoginPasswordResetPage extends LanguageComboboxAwarePage {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public String getFormUrl() {
|
||||
return formResetPassword.getAttribute("action");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,11 +26,22 @@ import static org.junit.Assert.assertEquals;
|
||||
import jakarta.mail.MessagingException;
|
||||
import jakarta.mail.internet.MimeMessage;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import jakarta.ws.rs.core.HttpHeaders;
|
||||
import org.apache.http.NameValuePair;
|
||||
import org.apache.http.client.entity.UrlEncodedFormEntity;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
import org.apache.http.client.methods.HttpPost;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.impl.client.HttpClientBuilder;
|
||||
import org.apache.http.message.BasicNameValuePair;
|
||||
import org.jboss.arquillian.graphene.page.Page;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Rule;
|
||||
@@ -50,6 +61,7 @@ import org.keycloak.testsuite.util.GreenMailRule;
|
||||
import org.keycloak.testsuite.util.MailUtils;
|
||||
import org.keycloak.testsuite.util.WaitUtils;
|
||||
import org.openqa.selenium.By;
|
||||
import org.openqa.selenium.Cookie;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:gerbermichi@me.com">Michael Gerber</a>
|
||||
@@ -81,10 +93,11 @@ public class EmailTest extends AbstractI18NTest {
|
||||
@Test
|
||||
public void restPasswordEmail() throws MessagingException, IOException {
|
||||
String expectedBodyContent = "Someone just requested to change";
|
||||
sendResetPasswordEmail();
|
||||
verifyResetPassword("Reset password", expectedBodyContent, null, 1);
|
||||
|
||||
changeUserLocale("en");
|
||||
|
||||
sendResetPasswordEmail();
|
||||
verifyResetPassword("Reset password", expectedBodyContent, null, 2);
|
||||
}
|
||||
|
||||
@@ -109,10 +122,12 @@ public class EmailTest extends AbstractI18NTest {
|
||||
getCleanup().addLocalization(Locale.GERMAN.toLanguageTag());
|
||||
|
||||
try {
|
||||
sendResetPasswordEmail();
|
||||
verifyResetPassword(subjectEn, expectedBodyContentEn, "<html lang=\"en\" dir=\"ltr\">", 1);
|
||||
|
||||
changeUserLocale("de");
|
||||
|
||||
sendResetPasswordEmail();
|
||||
verifyResetPassword(subjectDe, expectedBodyContentDe, "<html lang=\"de\" dir=\"ltr\">", 2);
|
||||
} finally {
|
||||
// Revert
|
||||
@@ -124,6 +139,7 @@ public class EmailTest extends AbstractI18NTest {
|
||||
public void restPasswordEmailGerman() throws MessagingException, IOException {
|
||||
changeUserLocale("de");
|
||||
try {
|
||||
sendResetPasswordEmail();
|
||||
verifyResetPassword("Passwort zurücksetzen", "Es wurde eine Änderung", null, 1);
|
||||
} finally {
|
||||
// Revert
|
||||
@@ -158,12 +174,50 @@ public class EmailTest extends AbstractI18NTest {
|
||||
}
|
||||
}
|
||||
|
||||
private void verifyResetPassword(String expectedSubject, String expectedTextBodyContent, String expectedHtmlBodyContent, int expectedMsgCount)
|
||||
throws MessagingException, IOException {
|
||||
@Test
|
||||
public void restPasswordEmailWithAcceptLanguageHeader() throws MessagingException, IOException {
|
||||
changeUserLocale(null);
|
||||
try {
|
||||
|
||||
loginPage.open();
|
||||
loginPage.resetPassword();
|
||||
|
||||
try (CloseableHttpClient client = HttpClientBuilder.create().build()) {
|
||||
|
||||
Set<Cookie> cookies = oauth.getDriver().manage().getCookies();
|
||||
String cookieHeader = cookies.stream()
|
||||
.map(cookie -> cookie.getName() + "=" + cookie.getValue())
|
||||
.collect(Collectors.joining("; "));
|
||||
String resetFormUrl = resetPasswordPage.getFormUrl();
|
||||
|
||||
HttpPost post = new HttpPost(resetFormUrl);
|
||||
post.setHeader(HttpHeaders.COOKIE, cookieHeader);
|
||||
post.addHeader(HttpHeaders.ACCEPT_LANGUAGE, "de");
|
||||
|
||||
List<NameValuePair> parameters = new LinkedList<>();
|
||||
parameters.add(new BasicNameValuePair("username", "login-test"));
|
||||
UrlEncodedFormEntity formEntity = new UrlEncodedFormEntity(parameters, StandardCharsets.UTF_8);
|
||||
post.setEntity(formEntity);
|
||||
|
||||
CloseableHttpResponse response = client.execute(post);
|
||||
assertEquals(200, response.getStatusLine().getStatusCode());
|
||||
}
|
||||
verifyResetPassword("Passwort zurücksetzen", "Es wurde eine Änderung", null, 1);
|
||||
} finally {
|
||||
// Revert
|
||||
changeUserLocale("en");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void sendResetPasswordEmail() {
|
||||
loginPage.open();
|
||||
loginPage.resetPassword();
|
||||
resetPasswordPage.changePassword("login-test");
|
||||
}
|
||||
|
||||
private void verifyResetPassword(String expectedSubject, String expectedTextBodyContent, String expectedHtmlBodyContent, int expectedMsgCount)
|
||||
throws MessagingException, IOException {
|
||||
assertEquals(expectedMsgCount, greenMail.getReceivedMessages().length);
|
||||
|
||||
MimeMessage message = greenMail.getReceivedMessages()[expectedMsgCount - 1];
|
||||
|
||||
Reference in New Issue
Block a user