[Fix] Cherry pick changes to AuthenticationService and RestfulStrolchComponent from develop

This commit is contained in:
Robert von Burg 2023-02-16 14:19:14 +01:00
parent bc49e95b9f
commit db2b2acf09
Signed by: eitch
GPG Key ID: 75DB9C85C74331F7
2 changed files with 102 additions and 71 deletions

View File

@ -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;
}

View File

@ -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);
}
}