KEYCLOAK-14231 - validate supported locales

This commit is contained in:
Lukas Hanusovsky 2020-09-22 14:29:48 +02:00 committed by Hynek Mlnařík
parent edef93cd49
commit 7f916ad20c
4 changed files with 93 additions and 13 deletions

View file

@ -409,6 +409,7 @@ public class RealmAdminResource {
} }
ReservedCharValidator.validate(rep.getRealm()); ReservedCharValidator.validate(rep.getRealm());
ReservedCharValidator.validateLocales(rep.getSupportedLocales());
try { try {
if (!Constants.GENERATE.equals(rep.getPublicKey()) && (rep.getPrivateKey() != null && rep.getPublicKey() != null)) { if (!Constants.GENERATE.equals(rep.getPublicKey()) && (rep.getPrivateKey() != null && rep.getPublicKey() != null)) {

View file

@ -19,29 +19,45 @@ package org.keycloak.utils;
import javax.ws.rs.BadRequestException; import javax.ws.rs.BadRequestException;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/** /**
* *
* @author Stan Silvert * @author Stan Silvert
* @author Lukas Hanusovsky lhanusov@redhat.com
*/ */
public class ReservedCharValidator { public class ReservedCharValidator {
protected static final Logger logger = Logger.getLogger(ReservedCharValidator.class); protected static final Logger logger = Logger.getLogger(ReservedCharValidator.class);
// https://tools.ietf.org/html/rfc3986#section-2.2 // https://tools.ietf.org/html/rfc3986#section-2.2
private static final String[] RESERVED_CHARS = { ":", "/", "?", "#", "[", "@", "!", "$", private static final Pattern RESERVED_CHARS_PATTERN = Pattern.compile("[:/?#@!$&()*+,;=\\[\\]\\\\]");
"&", "(", ")", "*", "+", ",", ";", "=",
"]", "[", "\\" }; // KEYCLOAK-14231 - Supported Locales: Three new characters were added on top of this RFC: "{", "}", "%"
private static final Pattern RESERVED_CHARS_LOCALES_PATTERN = Pattern.compile("[:/?#@!$&()*+,;=\\[\\]\\\\{}%]");
private ReservedCharValidator() {} private ReservedCharValidator() {}
public static void validate(String str) throws ReservedCharException { public static void validate(String str, Pattern pattern) throws ReservedCharException {
if (str == null) return; if (str == null) return;
for (String c : RESERVED_CHARS) { Matcher matcher = pattern.matcher(str);
if (str.contains(c)) { if (matcher.find()) {
String message = "Character '" + c + "' not allowed."; String message = "Character '" + matcher.group() + "' not allowed.";
ReservedCharException e = new ReservedCharException(message); logger.warn(message);
logger.warn(message, e); throw new ReservedCharException(message);
throw e; }
} }
public static void validate(String str) {
validate(str, RESERVED_CHARS_PATTERN);
}
public static void validateLocales(Iterable<String> strIterable) {
if (strIterable == null) return;
for (String str: strIterable) {
validate(str, RESERVED_CHARS_LOCALES_PATTERN);
} }
} }

View file

@ -18,6 +18,8 @@
package org.keycloak.testsuite.console.page.realm; package org.keycloak.testsuite.console.page.realm;
import org.keycloak.testsuite.console.page.fragment.OnOffSwitch; import org.keycloak.testsuite.console.page.fragment.OnOffSwitch;
import org.openqa.selenium.Keys;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy; import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.ui.Select; import org.openqa.selenium.support.ui.Select;
@ -26,6 +28,7 @@ import static org.keycloak.testsuite.util.UIUtils.clickLink;
/** /**
* *
* @author Filip Kiss * @author Filip Kiss
* @author Lukas Hanusovsky lhanusov@redhat.com
*/ */
public class ThemeSettings extends RealmSettings { public class ThemeSettings extends RealmSettings {
@ -49,6 +52,12 @@ public class ThemeSettings extends RealmSettings {
@FindBy(xpath = ".//div[@class='onoffswitch' and ./input[@id='internationalizationEnabled']]") @FindBy(xpath = ".//div[@class='onoffswitch' and ./input[@id='internationalizationEnabled']]")
private OnOffSwitch internatEnabledSwitch; private OnOffSwitch internatEnabledSwitch;
@FindBy(className = "select2-input")
private WebElement supportedLocalesInput;
@FindBy(id = "defaultLocale")
private Select defaultLocaleSelect;
public void changeLoginTheme(String themeName) { public void changeLoginTheme(String themeName) {
loginThemeSelect.selectByVisibleText(themeName); loginThemeSelect.selectByVisibleText(themeName);
} }
@ -73,6 +82,17 @@ public class ThemeSettings extends RealmSettings {
return internatEnabledSwitch.isOn(); return internatEnabledSwitch.isOn();
} }
public void addSupportedLocale(String supportedLocale) {
supportedLocalesInput.sendKeys(supportedLocale);
supportedLocalesInput.sendKeys(Keys.RETURN);
}
public void deleteSupportedLocale(String supportedLocale) {
supportedLocalesInput.sendKeys(Keys.chord(Keys.CONTROL, supportedLocale, Keys.BACK_SPACE, Keys.BACK_SPACE));
}
public void setDefaultLocale () { defaultLocaleSelect.selectByVisibleText("en"); }
public void saveTheme() { public void saveTheme() {
clickLink(primaryButton); clickLink(primaryButton);
} }

View file

@ -20,6 +20,7 @@ import static org.keycloak.testsuite.util.URLAssert.*;
/** /**
* @author Vaclav Muzikar <vmuzikar@redhat.com> * @author Vaclav Muzikar <vmuzikar@redhat.com>
* @author Lukas Hanusovsky lhanusov@redhat.com
*/ */
@DisableFeature(value = Profile.Feature.ACCOUNT2, skipRestart = true) // TODO remove this (KEYCLOAK-16228) @DisableFeature(value = Profile.Feature.ACCOUNT2, skipRestart = true) // TODO remove this (KEYCLOAK-16228)
public class InternationalizationTest extends AbstractRealmTest { public class InternationalizationTest extends AbstractRealmTest {
@ -95,6 +96,34 @@ public class InternationalizationTest extends AbstractRealmTest {
assertConsoleLocale(LABEL_CS_REALM_SETTINGS); assertConsoleLocale(LABEL_CS_REALM_SETTINGS);
} }
@Test
public void testSupportedLocalesOnReservedChars() {
realmSettingsPage.setAdminRealm(AuthRealm.MASTER);
realmSettingsPage.navigateTo();
loginPage.form().login(adminUser);
tabs().themes();
if (!themeSettingsPage.isInternatEnabled()) {
themeSettingsPage.setInternatEnabled(true);
themeSettingsPage.saveTheme();
}
// This Locales should pass, because they do not contain special chars.
assertSupportedLocale("test", "succeed");
assertSupportedLocale("sausage", "succeed");
// This Locales should raise exception, because the reserved chars are validated.
assertSupportedLocale("%00f%00", "fail");
assertSupportedLocale("test; Path=/", "fail");
assertSupportedLocale("{test}", "fail");
assertSupportedLocale("\\xc0", "fail");
assertSupportedLocale("\\xbc", "fail");
// Clean up session: back to realm Test
realmSettingsPage.setAdminRealm(AuthRealm.TEST);
deleteAllCookiesForMasterRealm();
}
private void assertConsoleLocale(String expected) { private void assertConsoleLocale(String expected) {
assertCurrentUrlEquals(realmSettingsPage); assertCurrentUrlEquals(realmSettingsPage);
assertLocale(".//div[@class='nav-category'][1]/ul/li[1]//a", expected); // Realm Settings assertLocale(".//div[@class='nav-category'][1]/ul/li[1]//a", expected); // Realm Settings
@ -113,4 +142,18 @@ public class InternationalizationTest extends AbstractRealmTest {
private void assertLocale(WebElement element, String expected) { private void assertLocale(WebElement element, String expected) {
assertEquals(expected, getTextFromElement(element)); assertEquals(expected, getTextFromElement(element));
} }
private void assertSupportedLocale(String supportedLocale, String updateStatus) {
themeSettingsPage.addSupportedLocale(supportedLocale);
themeSettingsPage.setDefaultLocale();
themeSettingsPage.saveTheme();
if (updateStatus.equals("succeed")) {
assertAlertSuccess();
} else if (updateStatus.equals("fail")) {
assertAlertDanger();
themeSettingsPage.deleteSupportedLocale(supportedLocale);
} else {
assertTrue(false);
}
}
} }