From db2b2acf099c7a648af54e16cf6d97b567191bc9 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Thu, 16 Feb 2023 14:19:14 +0100 Subject: [PATCH] [Fix] Cherry pick changes to AuthenticationService and RestfulStrolchComponent from develop --- .../strolch/rest/RestfulStrolchComponent.java | 53 +++++--- .../rest/endpoint/AuthenticationService.java | 120 ++++++++++-------- 2 files changed, 102 insertions(+), 71 deletions(-) diff --git a/li.strolch.rest/src/main/java/li/strolch/rest/RestfulStrolchComponent.java b/li.strolch.rest/src/main/java/li/strolch/rest/RestfulStrolchComponent.java index 21f5a3e25..a0cb3c7fa 100644 --- a/li.strolch.rest/src/main/java/li/strolch/rest/RestfulStrolchComponent.java +++ b/li.strolch.rest/src/main/java/li/strolch/rest/RestfulStrolchComponent.java @@ -36,16 +36,17 @@ import li.strolch.utils.dbc.DBC; */ public class RestfulStrolchComponent extends StrolchComponent { - private static final String PARAM_CORS_ENABLED = "corsEnabled"; //$NON-NLS-1$ - private static final String PARAM_CORS_ORIGIN = "corsOrigin"; //$NON-NLS-1$ - private static final String PARAM_REST_LOGGING = "restLogging"; //$NON-NLS-1$ - private static final String PARAM_REST_LOGGING_ENTITY = "restLoggingEntity"; //$NON-NLS-1$ - private static final String PARAM_HTTP_CACHE_MODE = "httpCacheMode"; //$NON-NLS-1$ - private static final String PARAM_SECURE_COOKIE = "secureCookie"; //$NON-NLS-1$ - private static final String PARAM_COOKIE_MAX_AGE = "cookieMaxAge"; //$NON-NLS-1$ - private static final String PARAM_DOMAIN = "domain"; //$NON-NLS-1$ - private static final String PARAM_BASIC_AUTH_ENABLED = "basicAuthEnabled"; //$NON-NLS-1$ - private static final String PARAM_HIDE_VERSION_FROM_UNAUTHORIZED_CLIENTS = "hideVersionFromUnauthorizedClients"; //$NON-NLS-1$ + private static final String PARAM_CORS_ENABLED = "corsEnabled"; + private static final String PARAM_CORS_ORIGIN = "corsOrigin"; + private static final String PARAM_REST_LOGGING = "restLogging"; + private static final String PARAM_REST_LOGGING_ENTITY = "restLoggingEntity"; + private static final String PARAM_HTTP_CACHE_MODE = "httpCacheMode"; + private static final String PARAM_SECURE_COOKIE = "secureCookie"; + private static final String PARAM_COOKIE_MAX_AGE = "cookieMaxAge"; + private static final String PARAM_DOMAIN = "domain"; + private static final String PARAM_PATH = "path"; + private static final String PARAM_BASIC_AUTH_ENABLED = "basicAuthEnabled"; + private static final String PARAM_HIDE_VERSION_FROM_UNAUTHORIZED_CLIENTS = "hideVersionFromUnauthorizedClients"; /** * Allowed values: @@ -58,7 +59,7 @@ public class RestfulStrolchComponent extends StrolchComponent { * * @see org.glassfish.jersey.server.ServerProperties#TRACING */ - private static final String PARAM_REST_TRACING = "restTracing"; //$NON-NLS-1$ + private static final String PARAM_REST_TRACING = "restTracing"; /** * Allowed values: @@ -70,7 +71,7 @@ public class RestfulStrolchComponent extends StrolchComponent { * * @see org.glassfish.jersey.server.ServerProperties#TRACING_THRESHOLD */ - private static final String PARAM_REST_TRACING_THRESHOLD = "restTracingThreshold"; //$NON-NLS-1$ + private static final String PARAM_REST_TRACING_THRESHOLD = "restTracingThreshold"; private static RestfulStrolchComponent instance; @@ -82,6 +83,7 @@ public class RestfulStrolchComponent extends StrolchComponent { private boolean restLoggingEntity; private boolean secureCookie; private String domain; + private String path; private int cookieMaxAge; private boolean basicAuthEnabled; private boolean hideVersionFromUnauthorizedClients; @@ -136,6 +138,18 @@ public class RestfulStrolchComponent extends StrolchComponent { return domain; } + public boolean isDomainSet() { + return this.domain != null; + } + + public boolean isPathSet() { + return this.path != null; + } + + public String getPath() { + return this.path; + } + public boolean isBasicAuthEnabled() { return this.basicAuthEnabled; } @@ -150,7 +164,7 @@ public class RestfulStrolchComponent extends StrolchComponent { this.corsEnabled = configuration.getBoolean(PARAM_CORS_ENABLED, Boolean.FALSE); if (this.corsEnabled) { this.corsOrigin = configuration.getString(PARAM_CORS_ORIGIN, null); - logger.info("Enabling CORS for origin: " + this.corsOrigin); //$NON-NLS-1$ + logger.info("Enabling CORS for origin: " + this.corsOrigin); AccessControlResponseFilter.setCorsEnabled(true); AccessControlResponseFilter.setOrigin(this.corsOrigin); } @@ -158,10 +172,10 @@ public class RestfulStrolchComponent extends StrolchComponent { // restful logging and tracing this.restLogging = configuration.getBoolean(PARAM_REST_LOGGING, Boolean.FALSE); this.restLoggingEntity = configuration.getBoolean(PARAM_REST_LOGGING_ENTITY, Boolean.FALSE); - this.restTracing = configuration.getString(PARAM_REST_TRACING, "OFF"); //$NON-NLS-1$ - this.restTracingThreshold = configuration.getString(PARAM_REST_TRACING_THRESHOLD, "TRACE"); //$NON-NLS-1$ + this.restTracing = configuration.getString(PARAM_REST_TRACING, "OFF"); + this.restTracingThreshold = configuration.getString(PARAM_REST_TRACING_THRESHOLD, "TRACE"); - String msg = "Set restLogging={0} with logEntities={1} restTracing={2} with threshold={3}"; //$NON-NLS-1$ + String msg = "Set restLogging={0} with logEntities={1} restTracing={2} with threshold={3}"; logger.info(MessageFormat.format(msg, this.restLogging, this.restLoggingEntity, this.restTracing, this.restTracingThreshold)); @@ -174,6 +188,9 @@ public class RestfulStrolchComponent extends StrolchComponent { this.domain = configuration.getString(PARAM_DOMAIN, ""); if (this.domain.isEmpty()) this.domain = null; + this.path = configuration.getString(PARAM_PATH, ""); + if (this.path.isEmpty()) + this.path = null; this.basicAuthEnabled = configuration.getBoolean(PARAM_BASIC_AUTH_ENABLED, true); this.hideVersionFromUnauthorizedClients = configuration.getBoolean(PARAM_HIDE_VERSION_FROM_UNAUTHORIZED_CLIENTS, false); @@ -186,7 +203,7 @@ public class RestfulStrolchComponent extends StrolchComponent { @Override public void start() throws Exception { - DBC.PRE.assertNull("Instance is already set! This component is a singleton resource!", instance); //$NON-NLS-1$ + DBC.PRE.assertNull("Instance is already set! This component is a singleton resource!", instance); instance = this; super.start(); } @@ -201,7 +218,7 @@ public class RestfulStrolchComponent extends StrolchComponent { * @return the RestfulStrolchComponent */ public static RestfulStrolchComponent getInstance() { - DBC.PRE.assertNotNull("Not yet initialized!", instance); //$NON-NLS-1$ + DBC.PRE.assertNotNull("Not yet initialized!", instance); return instance; } diff --git a/li.strolch.rest/src/main/java/li/strolch/rest/endpoint/AuthenticationService.java b/li.strolch.rest/src/main/java/li/strolch/rest/endpoint/AuthenticationService.java index 3e992ad55..8aece2a3a 100644 --- a/li.strolch.rest/src/main/java/li/strolch/rest/endpoint/AuthenticationService.java +++ b/li.strolch.rest/src/main/java/li/strolch/rest/endpoint/AuthenticationService.java @@ -25,7 +25,9 @@ import javax.ws.rs.core.*; import javax.ws.rs.core.Response.Status; import java.text.MessageFormat; import java.time.LocalDateTime; +import java.time.ZoneId; import java.util.Base64; +import java.util.Date; import java.util.Set; import java.util.concurrent.TimeUnit; @@ -195,8 +197,8 @@ public class AuthenticationService { } catch (Exception e) { logger.error(e.getMessage(), e); String msg = e.getMessage(); - logoutResult - .addProperty("msg", MessageFormat.format("{0}: {1}", e.getClass().getName(), msg)); //$NON-NLS-1$ + logoutResult.addProperty("msg", + MessageFormat.format("{0}: {1}", e.getClass().getName(), msg)); //$NON-NLS-1$ return Response.serverError().entity(logoutResult.toString()).build(); } } @@ -329,27 +331,19 @@ public class AuthenticationService { String username = jsonObject.get("username").getAsString(); String challenge = jsonObject.get("challenge").getAsString(); - StrolchSessionHandler sessionHandler = RestfulStrolchComponent.getInstance().getSessionHandler(); + RestfulStrolchComponent restComponent = RestfulStrolchComponent.getInstance(); + StrolchSessionHandler sessionHandler = restComponent.getSessionHandler(); String source = getRemoteIp(request); Certificate certificate = sessionHandler.validateChallenge(username, challenge, source); + int sessionMaxKeepAliveMinutes = sessionHandler.getSessionMaxKeepAliveMinutes(); + int cookieMaxAge = getCookieMaxAge(certificate, restComponent, sessionMaxKeepAliveMinutes); + LocalDateTime expirationDate = LocalDateTime.now().plusSeconds(cookieMaxAge); - jsonObject = new JsonObject(); - jsonObject.addProperty("authToken", certificate.getAuthToken()); + JsonObject result = new JsonObject(); + String authToken = certificate.getAuthToken(); + result.addProperty("authToken", authToken); - boolean secureCookie = RestfulStrolchComponent.getInstance().isSecureCookie(); - if (secureCookie && !request.getScheme().equals("https")) { - String msg = "Authorization cookie is secure, but connection is not secure! Cookie won't be passed to client!"; - logger.warn(msg); - } - - String domain = RestfulStrolchComponent.getInstance().getDomain(); - String path = "/;SameSite=Strict"; - - NewCookie cookie = new NewCookie(STROLCH_AUTHORIZATION, certificate.getAuthToken(), path, domain, - "Authorization header", (int) TimeUnit.DAYS.toSeconds(1), secureCookie); - - return Response.ok().entity(jsonObject.toString())// - .header(HttpHeaders.AUTHORIZATION, certificate.getAuthToken()).cookie(cookie).build(); + return setCookiesAndReturnResponse(request, restComponent, cookieMaxAge, expirationDate, result, authToken); } catch (PrivilegeException e) { logger.error("Challenge validation failed: " + e.getMessage()); @@ -363,21 +357,15 @@ public class AuthenticationService { private Response getAuthenticationResponse(HttpServletRequest request, Certificate certificate, String source, boolean setCookies) { - StrolchSessionHandler sessionHandler = RestfulStrolchComponent.getInstance().getSessionHandler(); + RestfulStrolchComponent restComponent = RestfulStrolchComponent.getInstance(); + StrolchSessionHandler sessionHandler = restComponent.getSessionHandler(); int sessionMaxKeepAliveMinutes = sessionHandler.getSessionMaxKeepAliveMinutes(); - int cookieMaxAge; - if (certificate.isKeepAlive()) { - cookieMaxAge = (int) TimeUnit.MINUTES.toSeconds(sessionMaxKeepAliveMinutes); - } else { - cookieMaxAge = RestfulStrolchComponent.getInstance().getCookieMaxAge(); - } - + int cookieMaxAge = getCookieMaxAge(certificate, restComponent, sessionMaxKeepAliveMinutes); LocalDateTime expirationDate = LocalDateTime.now().plusSeconds(cookieMaxAge); - String expirationDateS = ISO8601.toString(expirationDate); JsonObject loginResult = new JsonObject(); - PrivilegeHandler privilegeHandler = RestfulStrolchComponent.getInstance().getContainer().getPrivilegeHandler(); + PrivilegeHandler privilegeHandler = restComponent.getContainer().getPrivilegeHandler(); PrivilegeContext privilegeContext = privilegeHandler.validate(certificate, source); loginResult.addProperty("sessionId", certificate.getSessionId()); String authToken = certificate.getAuthToken(); @@ -389,7 +377,7 @@ public class AuthenticationService { loginResult.addProperty("keepAlive", certificate.isKeepAlive()); loginResult.addProperty("keepAliveMinutes", sessionMaxKeepAliveMinutes); loginResult.addProperty("cookieMaxAge", cookieMaxAge); - loginResult.addProperty("authorizationExpiration", expirationDateS); + loginResult.addProperty("authorizationExpiration", ISO8601.toString(expirationDate)); loginResult.addProperty("refreshAllowed", sessionHandler.isRefreshAllowed()); loginResult.addProperty("usage", certificate.getUsage().getValue()); @@ -433,29 +421,55 @@ public class AuthenticationService { } } - boolean secureCookie = RestfulStrolchComponent.getInstance().isSecureCookie(); - if (secureCookie && !request.getScheme().equals("https")) { - logger.error( - "Authorization cookie is secure, but connection is not secure! Cookie won't be passed to client!"); - } - - if (setCookies) { - - String domain = RestfulStrolchComponent.getInstance().getDomain(); - String path = "/;SameSite=Strict"; - - NewCookie authCookie = new NewCookie(STROLCH_AUTHORIZATION, authToken, path, domain, - "Strolch Authorization header", cookieMaxAge, secureCookie); - NewCookie authExpirationCookie = new NewCookie(STROLCH_AUTHORIZATION_EXPIRATION_DATE, expirationDateS, path, - domain, "Strolch Authorization Expiration Date", cookieMaxAge, secureCookie); - - return Response.ok().entity(loginResult.toString()) // - .header(HttpHeaders.AUTHORIZATION, authToken) // - .cookie(authCookie) // - .cookie(authExpirationCookie) // - .build(); - } - + if (setCookies) + return setCookiesAndReturnResponse(request, restComponent, cookieMaxAge, expirationDate, loginResult, + authToken); return Response.ok().entity(loginResult.toString()).header(HttpHeaders.AUTHORIZATION, authToken).build(); } + + private static Response setCookiesAndReturnResponse(HttpServletRequest request, + RestfulStrolchComponent restComponent, int cookieMaxAge, LocalDateTime expirationDate, + JsonObject loginResult, String authToken) { + boolean secureCookie = restComponent.isSecureCookie(); + if (secureCookie && !request.getScheme().equals("https")) { + String msg = "Authorization cookie is secure, but connection is not secure! Cookie won't be passed to client!"; + logger.error(msg); + } + + String expirationDateS = ISO8601.toString(expirationDate); + String domain = restComponent.isDomainSet() ? restComponent.getDomain() : request.getServerName(); + String path = (restComponent.isPathSet() ? restComponent.getPath() : "/") + ";SameSite=Strict"; + + Date expiry = Date.from(expirationDate.atZone(ZoneId.systemDefault()).toInstant()); + boolean httpOnly = false; + int version = 1; + + NewCookie authCookie = getNewCookie(STROLCH_AUTHORIZATION, authToken, path, domain, version, + "Strolch Authorization header", cookieMaxAge, expiry, secureCookie, httpOnly); + NewCookie authExpirationCookie = getNewCookie(STROLCH_AUTHORIZATION_EXPIRATION_DATE, expirationDateS, path, + domain, version, "Strolch Authorization Expiration Date", cookieMaxAge, expiry, secureCookie, httpOnly); + + return Response.ok().entity(loginResult.toString()) // + .header(HttpHeaders.AUTHORIZATION, authToken) // + .cookie(authCookie) // + .cookie(authExpirationCookie) // + .build(); + } + + private static int getCookieMaxAge(Certificate certificate, RestfulStrolchComponent restComponent, + int sessionMaxKeepAliveMinutes) { + int cookieMaxAge; + if (certificate.isKeepAlive()) { + cookieMaxAge = (int) TimeUnit.MINUTES.toSeconds(sessionMaxKeepAliveMinutes); + } else { + cookieMaxAge = restComponent.getCookieMaxAge(); + } + return cookieMaxAge; + } + + private static NewCookie getNewCookie(String strolchAuthorization, String authToken, String path, String domain, + int version, String comment, int cookieMaxAge, Date expiry, boolean secureCookie, boolean httpOnly) { + return new NewCookie(strolchAuthorization, authToken, path, domain, version, comment, cookieMaxAge, expiry, + secureCookie, httpOnly); + } }