mirror of
https://github.com/danieldemus/openhab-core.git
synced 2025-01-10 13:21:53 +01:00
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 <github@schaus.net>
This commit is contained in:
parent
e909a81f4b
commit
67bdfa3ad6
@ -6,6 +6,7 @@
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="src" path="src/main/resources"/>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
|
@ -102,29 +102,29 @@ input.submit:hover {
|
||||
<form class="{formClass}" method="POST" action="{formAction}">
|
||||
{form_fields}
|
||||
<div>
|
||||
<input class="field" autocomplete="off" type="text" placeholder="User name" name="username" required autofocus />
|
||||
<input class="field" autocomplete="off" type="text" placeholder="{usernamePlaceholder}" name="username" required autofocus />
|
||||
</div>
|
||||
<div>
|
||||
<input class="field" type="password" placeholder="Password" name="password" required />
|
||||
<input class="field" type="password" placeholder="{passwordPlaceholder}" name="password" required />
|
||||
</div>
|
||||
<div>
|
||||
<input class="field" type="{newPasswordFieldType}" placeholder="New Password" name="new_password" />
|
||||
<input class="field" type="{newPasswordFieldType}" placeholder="{newPasswordPlaceholder}" name="new_password" />
|
||||
</div>
|
||||
<div>
|
||||
<input class="field" type="{repeatPasswordFieldType}" placeholder="Confirm New Password" name="password_repeat" />
|
||||
<input class="field" type="{repeatPasswordFieldType}" placeholder="{repeatPasswordPlaceholder}" name="password_repeat" />
|
||||
</div>
|
||||
<div>
|
||||
<input class="field" type="{tokenNameFieldType}" placeholder="Token Name" name="token_name" />
|
||||
<input class="field" type="{tokenNameFieldType}" placeholder="{tokenNamePlaceholder}" name="token_name" />
|
||||
</div>
|
||||
<div>
|
||||
<input class="field" type="{tokenScopeFieldType}" placeholder="Token Scope (optional)" name="token_scope" />
|
||||
<input class="field" type="{tokenScopeFieldType}" placeholder="{tokenScopePlaceholder}" name="token_scope" />
|
||||
</div>
|
||||
<div>
|
||||
<input class="submit" type="Submit" value="{buttonLabel}" />
|
||||
</div>
|
||||
</form>
|
||||
<div class="result{resultClass}">
|
||||
<a class="submit" type="button" href="/">Return Home</a>
|
||||
<a class="submit" type="button" href="/">{returnButtonLabel}</a>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -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<String, String[]> params, String message, boolean hideForm);
|
||||
|
||||
protected abstract String getFormFields(Map<String, String[]> 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);
|
||||
}
|
||||
}
|
||||
|
@ -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 <b>%s</b> access to <b>%s</b>:", 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<String, String[]> 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");
|
||||
|
@ -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<String, String[]> 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<String, String[]> 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");
|
||||
|
@ -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<String, String[]> 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:<br /><br /><code>" + newApiToken + "</code>";
|
||||
resultMessage += "<br /><br /><small>Please copy it now, it will not be shown again.</small>";
|
||||
String resultMessage = getLocalizedMessage("auth.createapitoken.success") + "<br /><br /><code>"
|
||||
+ newApiToken + "</code>";
|
||||
resultMessage += "<br /><br /><small>" + getLocalizedMessage("auth.createapitoken.success.footer")
|
||||
+ "</small>";
|
||||
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<String, String[]> 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<String, String[]> 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");
|
||||
|
@ -0,0 +1,26 @@
|
||||
auth.login.prompt = Sign in to grant <b>%s</b> access to <b>%s</b>:
|
||||
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
|
@ -0,0 +1,26 @@
|
||||
auth.login.prompt = Connectez-vous pour accorder l'accès <b>%s</b> à <b>%s</b>:
|
||||
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
|
Loading…
Reference in New Issue
Block a user