From 67bdfa3ad6c6eb36da8aca2af5b6b580b0816a9d Mon Sep 17 00:00:00 2001 From: Yannick Schaus Date: Sat, 12 Dec 2020 22:42:56 +0100 Subject: [PATCH] Auth pages i18n (#1913) This implements localized messages for the authorize, change password and create API token pages using a resource bundle. Messages in English & French are included. Signed-off-by: Yannick Schaus --- .../org.openhab.core.io.http.auth/.classpath | 1 + .../pages/authorize.html | 14 ++++---- .../internal/AbstractAuthPageServlet.java | 35 +++++++++++++++++-- .../auth/internal/AuthorizePageServlet.java | 19 +++++----- .../internal/ChangePasswordPageServlet.java | 17 ++++----- .../internal/CreateAPITokenPageServlet.java | 30 ++++++++-------- .../src/main/resources/messages.properties | 26 ++++++++++++++ .../src/main/resources/messages_fr.properties | 26 ++++++++++++++ 8 files changed, 127 insertions(+), 41 deletions(-) create mode 100644 bundles/org.openhab.core.io.http.auth/src/main/resources/messages.properties create mode 100644 bundles/org.openhab.core.io.http.auth/src/main/resources/messages_fr.properties diff --git a/bundles/org.openhab.core.io.http.auth/.classpath b/bundles/org.openhab.core.io.http.auth/.classpath index 01095f5fb..534c918e6 100644 --- a/bundles/org.openhab.core.io.http.auth/.classpath +++ b/bundles/org.openhab.core.io.http.auth/.classpath @@ -6,6 +6,7 @@ + diff --git a/bundles/org.openhab.core.io.http.auth/pages/authorize.html b/bundles/org.openhab.core.io.http.auth/pages/authorize.html index 3c994ac1e..9b24fe84e 100644 --- a/bundles/org.openhab.core.io.http.auth/pages/authorize.html +++ b/bundles/org.openhab.core.io.http.auth/pages/authorize.html @@ -102,29 +102,29 @@ input.submit:hover {
{form_fields}
- +
- +
- +
- +
- +
- +
diff --git a/bundles/org.openhab.core.io.http.auth/src/main/java/org/openhab/core/io/http/auth/internal/AbstractAuthPageServlet.java b/bundles/org.openhab.core.io.http.auth/src/main/java/org/openhab/core/io/http/auth/internal/AbstractAuthPageServlet.java index 0511074be..217ec6962 100644 --- a/bundles/org.openhab.core.io.http.auth/src/main/java/org/openhab/core/io/http/auth/internal/AbstractAuthPageServlet.java +++ b/bundles/org.openhab.core.io.http.auth/src/main/java/org/openhab/core/io/http/auth/internal/AbstractAuthPageServlet.java @@ -21,6 +21,8 @@ import java.time.Duration; import java.time.Instant; import java.util.HashMap; import java.util.Map; +import java.util.ResourceBundle; +import java.util.ResourceBundle.Control; import java.util.UUID; import javax.servlet.http.HttpServlet; @@ -34,6 +36,7 @@ import org.openhab.core.auth.AuthenticationProvider; import org.openhab.core.auth.User; import org.openhab.core.auth.UserRegistry; import org.openhab.core.auth.UsernamePasswordCredentials; +import org.openhab.core.i18n.LocaleProvider; import org.osgi.framework.BundleContext; import org.osgi.service.component.annotations.Reference; import org.osgi.service.http.HttpService; @@ -51,11 +54,14 @@ public abstract class AbstractAuthPageServlet extends HttpServlet { protected static final long serialVersionUID = 5340598701104679840L; + private static final String MESSAGES_BUNDLE_NAME = "messages"; + private final Logger logger = LoggerFactory.getLogger(AbstractAuthPageServlet.class); protected HttpService httpService; protected UserRegistry userRegistry; protected AuthenticationProvider authProvider; + protected LocaleProvider localeProvider; protected @Nullable Instant lastAuthenticationFailure; protected int authenticationFailureCount = 0; @@ -64,10 +70,12 @@ public abstract class AbstractAuthPageServlet extends HttpServlet { protected String pageTemplate; public AbstractAuthPageServlet(BundleContext bundleContext, @Reference HttpService httpService, - @Reference UserRegistry userRegistry, @Reference AuthenticationProvider authProvider) { + @Reference UserRegistry userRegistry, @Reference AuthenticationProvider authProvider, + @Reference LocaleProvider localeProvider) { this.httpService = httpService; this.userRegistry = userRegistry; this.authProvider = authProvider; + this.localeProvider = localeProvider; pageTemplate = ""; URL resource = bundleContext.getBundle().getResource("pages/authorize.html"); @@ -80,6 +88,23 @@ public abstract class AbstractAuthPageServlet extends HttpServlet { } } + protected String getPageTemplate() { + String template = pageTemplate; + for (String[] replace : new String[][] { // + { "{usernamePlaceholder}", "auth.placeholder.username" }, + { "{passwordPlaceholder}", "auth.placeholder.password" }, + { "{newPasswordPlaceholder}", "auth.placeholder.newpassword" }, + { "{repeatPasswordPlaceholder}", "auth.placeholder.repeatpassword" }, + { "{tokenNamePlaceholder}", "auth.placeholder.tokenname" }, + { "{tokenScopePlaceholder}", "auth.placeholder.tokenscope" }, + { "{returnButtonLabel}", "auth.button.return" } // + }) { + template = template.replace(replace[0], getLocalizedMessage(replace[1])); + } + + return template; + } + protected abstract String getPageBody(Map params, String message, boolean hideForm); protected abstract String getFormFields(Map params); @@ -123,7 +148,13 @@ public abstract class AbstractAuthPageServlet extends HttpServlet { authenticationFailureCount += 1; resp.setContentType("text/html;charset=UTF-8"); logger.warn("Authentication failed: {}", message); - resp.getWriter().append(getPageBody(params, "Please try again.", false)); // TODO: i18n + resp.getWriter().append(getPageBody(params, getLocalizedMessage("auth.login.fail"), false)); resp.getWriter().close(); } + + protected String getLocalizedMessage(String messageKey) { + ResourceBundle rb = ResourceBundle.getBundle(MESSAGES_BUNDLE_NAME, localeProvider.getLocale(), + Control.getNoFallbackControl(Control.FORMAT_PROPERTIES)); + return rb.getString(messageKey); + } } diff --git a/bundles/org.openhab.core.io.http.auth/src/main/java/org/openhab/core/io/http/auth/internal/AuthorizePageServlet.java b/bundles/org.openhab.core.io.http.auth/src/main/java/org/openhab/core/io/http/auth/internal/AuthorizePageServlet.java index 64ac54145..b073cf23d 100644 --- a/bundles/org.openhab.core.io.http.auth/src/main/java/org/openhab/core/io/http/auth/internal/AuthorizePageServlet.java +++ b/bundles/org.openhab.core.io.http.auth/src/main/java/org/openhab/core/io/http/auth/internal/AuthorizePageServlet.java @@ -32,6 +32,7 @@ import org.openhab.core.auth.PendingToken; import org.openhab.core.auth.Role; import org.openhab.core.auth.User; import org.openhab.core.auth.UserRegistry; +import org.openhab.core.i18n.LocaleProvider; import org.osgi.framework.BundleContext; import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; @@ -63,8 +64,9 @@ public class AuthorizePageServlet extends AbstractAuthPageServlet { @Activate public AuthorizePageServlet(BundleContext bundleContext, @Reference HttpService httpService, - @Reference UserRegistry userRegistry, @Reference AuthenticationProvider authProvider) { - super(bundleContext, httpService, userRegistry, authProvider); + @Reference UserRegistry userRegistry, @Reference AuthenticationProvider authProvider, + @Reference LocaleProvider localeProvider) { + super(bundleContext, httpService, userRegistry, authProvider, localeProvider); try { httpService.registerServlet("/auth", this, null, null); } catch (NamespaceException | ServletException e) { @@ -86,11 +88,10 @@ public class AuthorizePageServlet extends AbstractAuthPageServlet { throw new IllegalArgumentException("invalid_request"); } - // TODO: i18n if (isSignupMode()) { - message = "Create a first administrator account to continue."; + message = getLocalizedMessage("auth.createaccount.prompt"); } else { - message = String.format("Sign in to grant %s access to %s:", scope, clientId); + message = String.format(getLocalizedMessage("auth.login.prompt"), scope, clientId); } resp.setContentType("text/html;charset=UTF-8"); resp.getWriter().append(getPageBody(params, message, false)); @@ -153,8 +154,8 @@ public class AuthorizePageServlet extends AbstractAuthPageServlet { // first verify the password confirmation and bail out if necessary if (!params.containsKey("password_repeat") || !password.equals(params.get("password_repeat")[0])) { resp.setContentType("text/html;charset=UTF-8"); - // TODO: i18n - resp.getWriter().append(getPageBody(params, "Passwords don't match, please try again.", false)); + resp.getWriter() + .append(getPageBody(params, getLocalizedMessage("auth.password.confirm.fail"), false)); resp.getWriter().close(); return; } @@ -202,9 +203,9 @@ public class AuthorizePageServlet extends AbstractAuthPageServlet { @Override protected String getPageBody(Map params, String message, boolean hideForm) { - String responseBody = pageTemplate.replace("{form_fields}", getFormFields(params)); + String responseBody = getPageTemplate().replace("{form_fields}", getFormFields(params)); String repeatPasswordFieldType = isSignupMode() ? "password" : "hidden"; - String buttonLabel = isSignupMode() ? "Create Account" : "Sign In"; // TODO: i18n + String buttonLabel = getLocalizedMessage(isSignupMode() ? "auth.button.createaccount" : "auth.button.signin"); responseBody = responseBody.replace("{message}", message); responseBody = responseBody.replace("{formAction}", "/auth"); responseBody = responseBody.replace("{formClass}", "show"); diff --git a/bundles/org.openhab.core.io.http.auth/src/main/java/org/openhab/core/io/http/auth/internal/ChangePasswordPageServlet.java b/bundles/org.openhab.core.io.http.auth/src/main/java/org/openhab/core/io/http/auth/internal/ChangePasswordPageServlet.java index fdefbb808..fa0dcfdd7 100644 --- a/bundles/org.openhab.core.io.http.auth/src/main/java/org/openhab/core/io/http/auth/internal/ChangePasswordPageServlet.java +++ b/bundles/org.openhab.core.io.http.auth/src/main/java/org/openhab/core/io/http/auth/internal/ChangePasswordPageServlet.java @@ -25,6 +25,7 @@ import org.openhab.core.auth.AuthenticationProvider; import org.openhab.core.auth.ManagedUser; import org.openhab.core.auth.User; import org.openhab.core.auth.UserRegistry; +import org.openhab.core.i18n.LocaleProvider; import org.osgi.framework.BundleContext; import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; @@ -51,8 +52,9 @@ public class ChangePasswordPageServlet extends AbstractAuthPageServlet { @Activate public ChangePasswordPageServlet(BundleContext bundleContext, @Reference HttpService httpService, - @Reference UserRegistry userRegistry, @Reference AuthenticationProvider authProvider) { - super(bundleContext, httpService, userRegistry, authProvider); + @Reference UserRegistry userRegistry, @Reference AuthenticationProvider authProvider, + @Reference LocaleProvider localeProvider) { + super(bundleContext, httpService, userRegistry, authProvider, localeProvider); try { httpService.registerServlet("/changePassword", this, null, null); } catch (NamespaceException | ServletException e) { @@ -102,8 +104,7 @@ public class ChangePasswordPageServlet extends AbstractAuthPageServlet { if (!params.containsKey("password_repeat") || !newPassword.equals(params.get("password_repeat")[0])) { resp.setContentType("text/html;charset=UTF-8"); - // TODO: i18n - resp.getWriter().append(getPageBody(params, "Passwords don't match, please try again.", false)); + resp.getWriter().append(getPageBody(params, getLocalizedMessage("auth.password.confirm.fail"), false)); resp.getWriter().close(); return; } @@ -117,7 +118,7 @@ public class ChangePasswordPageServlet extends AbstractAuthPageServlet { } resp.setContentType("text/html;charset=UTF-8"); - resp.getWriter().append(getResultPageBody(params, "Password changed.")); // TODO: i18n + resp.getWriter().append(getResultPageBody(params, getLocalizedMessage("auth.changepassword.success"))); resp.getWriter().close(); } catch (AuthenticationException e) { processFailedLogin(resp, params, e.getMessage()); @@ -126,8 +127,8 @@ public class ChangePasswordPageServlet extends AbstractAuthPageServlet { @Override protected String getPageBody(Map params, String message, boolean hideForm) { - String responseBody = pageTemplate.replace("{form_fields}", getFormFields(params)); - String buttonLabel = "Change Password"; // TODO: i18n + String responseBody = getPageTemplate().replace("{form_fields}", getFormFields(params)); + String buttonLabel = getLocalizedMessage("auth.button.changepassword"); responseBody = responseBody.replace("{message}", message); responseBody = responseBody.replace("{formAction}", "/changePassword"); responseBody = responseBody.replace("{formClass}", hideForm ? "hide" : "show"); @@ -141,7 +142,7 @@ public class ChangePasswordPageServlet extends AbstractAuthPageServlet { } protected String getResultPageBody(Map params, String message) { - String responseBody = pageTemplate.replace("{form_fields}", ""); + String responseBody = getPageTemplate().replace("{form_fields}", ""); responseBody = responseBody.replace("{message}", message); responseBody = responseBody.replace("{formAction}", "/changePassword"); responseBody = responseBody.replace("{formClass}", "hide"); diff --git a/bundles/org.openhab.core.io.http.auth/src/main/java/org/openhab/core/io/http/auth/internal/CreateAPITokenPageServlet.java b/bundles/org.openhab.core.io.http.auth/src/main/java/org/openhab/core/io/http/auth/internal/CreateAPITokenPageServlet.java index 2a470ef84..cbb4266b7 100644 --- a/bundles/org.openhab.core.io.http.auth/src/main/java/org/openhab/core/io/http/auth/internal/CreateAPITokenPageServlet.java +++ b/bundles/org.openhab.core.io.http.auth/src/main/java/org/openhab/core/io/http/auth/internal/CreateAPITokenPageServlet.java @@ -25,6 +25,7 @@ import org.openhab.core.auth.AuthenticationProvider; import org.openhab.core.auth.ManagedUser; import org.openhab.core.auth.User; import org.openhab.core.auth.UserRegistry; +import org.openhab.core.i18n.LocaleProvider; import org.osgi.framework.BundleContext; import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; @@ -51,8 +52,9 @@ public class CreateAPITokenPageServlet extends AbstractAuthPageServlet { @Activate public CreateAPITokenPageServlet(BundleContext bundleContext, @Reference HttpService httpService, - @Reference UserRegistry userRegistry, @Reference AuthenticationProvider authProvider) { - super(bundleContext, httpService, userRegistry, authProvider); + @Reference UserRegistry userRegistry, @Reference AuthenticationProvider authProvider, + @Reference LocaleProvider localeProvider) { + super(bundleContext, httpService, userRegistry, authProvider, localeProvider); try { httpService.registerServlet("/createApiToken", this, null, null); } catch (NamespaceException | ServletException e) { @@ -65,9 +67,8 @@ public class CreateAPITokenPageServlet extends AbstractAuthPageServlet { Map params = req.getParameterMap(); try { - String message = "Create a new API token to authorize external services."; + String message = getLocalizedMessage("auth.createapitoken.prompt"); - // TODO: i18n resp.setContentType("text/html;charset=UTF-8"); resp.getWriter().append(getPageBody(params, message, false)); resp.getWriter().close(); @@ -112,18 +113,16 @@ public class CreateAPITokenPageServlet extends AbstractAuthPageServlet { if (((ManagedUser) user).getApiTokens().stream() .anyMatch(apiToken -> apiToken.getName().equals(tokenName))) { resp.setContentType("text/html;charset=UTF-8"); - // TODO: i18n resp.getWriter().append( - getPageBody(params, "A token with the same name already exists, please try again.", false)); + getPageBody(params, getLocalizedMessage("auth.createapitoken.name.unique.fail"), false)); resp.getWriter().close(); return; } if (!tokenName.matches("[a-zA-Z0-9]*")) { resp.setContentType("text/html;charset=UTF-8"); - // TODO: i18n resp.getWriter().append( - getPageBody(params, "Invalid token name, please use alphanumeric characters only.", false)); + getPageBody(params, getLocalizedMessage("auth.createapitoken.name.format.fail"), false)); resp.getWriter().close(); return; } @@ -132,11 +131,12 @@ public class CreateAPITokenPageServlet extends AbstractAuthPageServlet { throw new AuthenticationException("User is not managed"); } - // TODO: i18n - String resultMessage = "New token created:

" + newApiToken + ""; - resultMessage += "

Please copy it now, it will not be shown again."; + String resultMessage = getLocalizedMessage("auth.createapitoken.success") + "

" + + newApiToken + ""; + resultMessage += "

" + getLocalizedMessage("auth.createapitoken.success.footer") + + ""; resp.setContentType("text/html;charset=UTF-8"); - resp.getWriter().append(getResultPageBody(params, resultMessage)); // TODO: i18n + resp.getWriter().append(getResultPageBody(params, resultMessage)); resp.getWriter().close(); } catch (AuthenticationException e) { processFailedLogin(resp, params, e.getMessage()); @@ -145,8 +145,8 @@ public class CreateAPITokenPageServlet extends AbstractAuthPageServlet { @Override protected String getPageBody(Map params, String message, boolean hideForm) { - String responseBody = pageTemplate.replace("{form_fields}", getFormFields(params)); - String buttonLabel = "Create API Token"; // TODO: i18n + String responseBody = getPageTemplate().replace("{form_fields}", getFormFields(params)); + String buttonLabel = getLocalizedMessage("auth.button.createapitoken"); responseBody = responseBody.replace("{message}", message); responseBody = responseBody.replace("{formAction}", "/createApiToken"); responseBody = responseBody.replace("{formClass}", hideForm ? "hide" : "show"); @@ -160,7 +160,7 @@ public class CreateAPITokenPageServlet extends AbstractAuthPageServlet { } protected String getResultPageBody(Map params, String message) { - String responseBody = pageTemplate.replace("{form_fields}", ""); + String responseBody = getPageTemplate().replace("{form_fields}", ""); responseBody = responseBody.replace("{message}", message); responseBody = responseBody.replace("{formAction}", "/createApiToken"); responseBody = responseBody.replace("{formClass}", "hide"); diff --git a/bundles/org.openhab.core.io.http.auth/src/main/resources/messages.properties b/bundles/org.openhab.core.io.http.auth/src/main/resources/messages.properties new file mode 100644 index 000000000..ac450af09 --- /dev/null +++ b/bundles/org.openhab.core.io.http.auth/src/main/resources/messages.properties @@ -0,0 +1,26 @@ +auth.login.prompt = Sign in to grant %s access to %s: +auth.login.fail = Please try again. +auth.createaccount.prompt = Create a first administrator account to continue. + +auth.changepassword.success = Password changed. + +auth.createapitoken.prompt = Create a new API token to authorize external services. +auth.createapitoken.name.unique.fail = A token with the same name already exists, please try again. +auth.createapitoken.name.format.fail = Invalid token name, please use alphanumeric characters only. +auth.createapitoken.success = New token created: +auth.createapitoken.success.footer = Please copy it now, it will not be shown again. + +auth.password.confirm.fail = Passwords don't match, please try again. + +auth.placeholder.username = User Name +auth.placeholder.password = Password +auth.placeholder.newpassword = New Password +auth.placeholder.repeatpassword = Confirm New Password +auth.placeholder.tokenname = Token Name +auth.placeholder.tokenscope = Token Scope (optional) + +auth.button.signin = Sign In +auth.button.createaccount = Create Account +auth.button.changepassword = Change Password +auth.button.createapitoken = Create API Token +auth.button.return = Return Home diff --git a/bundles/org.openhab.core.io.http.auth/src/main/resources/messages_fr.properties b/bundles/org.openhab.core.io.http.auth/src/main/resources/messages_fr.properties new file mode 100644 index 000000000..a0d15bf2d --- /dev/null +++ b/bundles/org.openhab.core.io.http.auth/src/main/resources/messages_fr.properties @@ -0,0 +1,26 @@ +auth.login.prompt = Connectez-vous pour accorder l'accès %s à %s: +auth.login.fail = Veuillez réessayer. +auth.createaccount.prompt = Créez un premier compte administrateur pour continuer. + +auth.changepassword.success = Mot de passe modifié. + +auth.createapitoken.prompt = Créez des jetons d'API pour autoriser des services externes. +auth.createapitoken.name.unique.fail = Un jeton avec le même nom existe déjà, veuillez réessayer. +auth.createapitoken.name.format.fail = Nom de jeton invalide, merci d'utiliser uniquement des caractères alphanumériques. +auth.createapitoken.success = Nouveau jeton créé : +auth.createapitoken.success.footer = Veuillez le copier maintenant, il ne sera plus possible de le voir à nouveau. + +auth.password.confirm.fail = Les mots de passe ne correspondent pas, veuillez réessayer. + +auth.placeholder.username = Utilisateur +auth.placeholder.password = Mot de passe +auth.placeholder.newpassword = Nouveau mot de passe +auth.placeholder.repeatpassword = Confirmer le nouveau mot de passe +auth.placeholder.tokenname = Nom du jeton +auth.placeholder.tokenscope = Portée (scope) du jeton, facultatif + +auth.button.signin = Connexion +auth.button.createaccount = Créer +auth.button.changepassword = Changer le mot de passe +auth.button.createapitoken = Créer le jeton +auth.button.return = Retour