Skip to content

Commit

Permalink
Auth pages i18n (openhab#1913)
Browse files Browse the repository at this point in the history
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>
GitOrigin-RevId: 67bdfa3
  • Loading branch information
ghys authored and splatch committed Jul 11, 2023
1 parent a706531 commit 4c8195d
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 34 deletions.
Expand Up @@ -21,6 +21,8 @@
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;
Expand All @@ -34,6 +36,7 @@
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;
Expand All @@ -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;

Expand All @@ -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");
Expand All @@ -80,6 +88,23 @@ public AbstractAuthPageServlet(BundleContext bundleContext, @Reference HttpServi
}
}

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);
Expand Down Expand Up @@ -123,7 +148,13 @@ protected void processFailedLogin(HttpServletResponse resp, Map<String, String[]
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);
}
}
Expand Up @@ -32,6 +32,7 @@
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;
Expand Down Expand Up @@ -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) {
Expand All @@ -86,11 +88,10 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws Se
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));
Expand Down Expand Up @@ -153,8 +154,8 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws S
// 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;
}
Expand Down Expand Up @@ -202,9 +203,9 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws S

@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");
Expand Down
Expand Up @@ -25,6 +25,7 @@
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;
Expand All @@ -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) {
Expand Down Expand Up @@ -102,8 +104,7 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws S

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;
}
Expand All @@ -117,7 +118,7 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws S
}

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());
Expand All @@ -126,8 +127,8 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws S

@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");
Expand All @@ -141,7 +142,7 @@ protected String getPageBody(Map<String, String[]> params, String message, boole
}

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");
Expand Down
Expand Up @@ -25,6 +25,7 @@
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;
Expand All @@ -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) {
Expand All @@ -65,9 +67,8 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws Se
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();
Expand Down Expand Up @@ -112,18 +113,16 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws S
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;
}
Expand All @@ -132,11 +131,12 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws S
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());
Expand All @@ -145,8 +145,8 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws S

@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");
Expand All @@ -160,7 +160,7 @@ protected String getPageBody(Map<String, String[]> params, String message, boole
}

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");
Expand Down

0 comments on commit 4c8195d

Please sign in to comment.