From a7258f0d06ad232efbe67a4972854d010b69dbe4 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Thu, 21 Mar 2024 14:23:49 +0100 Subject: [PATCH] [Major] Refactored basic auth and getRemoteIp() helper --- .../rest/endpoint/AuthenticationResource.java | 2 +- .../filters/AuthenticationRequestFilter.java | 89 +++++++++---------- .../li/strolch/rest/helper/BasicAuth.java | 52 +++++++++++ .../strolch/rest/helper/BasicAuthFailure.java | 28 ++++++ .../li/strolch/rest/helper/RestfulHelper.java | 48 +++++++--- .../li/strolch/websocket/WebSocketFilter.java | 2 +- 6 files changed, 162 insertions(+), 59 deletions(-) create mode 100644 web-rest/src/main/java/li/strolch/rest/helper/BasicAuth.java create mode 100644 web-rest/src/main/java/li/strolch/rest/helper/BasicAuthFailure.java diff --git a/web-rest/src/main/java/li/strolch/rest/endpoint/AuthenticationResource.java b/web-rest/src/main/java/li/strolch/rest/endpoint/AuthenticationResource.java index aebfd6337..4d92e1ee0 100644 --- a/web-rest/src/main/java/li/strolch/rest/endpoint/AuthenticationResource.java +++ b/web-rest/src/main/java/li/strolch/rest/endpoint/AuthenticationResource.java @@ -51,7 +51,7 @@ import java.util.concurrent.TimeUnit; import static li.strolch.rest.StrolchRestfulConstants.STROLCH_AUTHORIZATION; import static li.strolch.rest.StrolchRestfulConstants.STROLCH_AUTHORIZATION_EXPIRATION_DATE; -import static li.strolch.rest.filters.AuthenticationRequestFilter.getRemoteIp; +import static li.strolch.rest.helper.RestfulHelper.getRemoteIp; import static li.strolch.utils.helper.ExceptionHelper.getRootCause; import static li.strolch.utils.helper.ExceptionHelper.hasCause; diff --git a/web-rest/src/main/java/li/strolch/rest/filters/AuthenticationRequestFilter.java b/web-rest/src/main/java/li/strolch/rest/filters/AuthenticationRequestFilter.java index 770937818..e1d1be02b 100644 --- a/web-rest/src/main/java/li/strolch/rest/filters/AuthenticationRequestFilter.java +++ b/web-rest/src/main/java/li/strolch/rest/filters/AuthenticationRequestFilter.java @@ -28,6 +28,7 @@ import li.strolch.privilege.model.Certificate; import li.strolch.privilege.model.Usage; import li.strolch.rest.RestfulStrolchComponent; import li.strolch.rest.StrolchRestfulConstants; +import li.strolch.rest.helper.RestfulHelper; import li.strolch.runtime.sessions.StrolchSessionHandler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -112,9 +113,10 @@ public class AuthenticationRequestFilter implements ContainerRequestFilter { @Override public void filter(ContainerRequestContext requestContext) { - String remoteIp = getRemoteIp(this.request); - logger.info("Remote IP: " + remoteIp + ": " + requestContext.getMethod() + " " + - requestContext.getUriInfo().getRequestUri()); + String remoteIp = RestfulHelper.getRemoteIp(this.request); + logger.info("Remote IP: " + remoteIp + ": " + requestContext.getMethod() + " " + requestContext + .getUriInfo() + .getRequestUri()); try { @@ -126,19 +128,25 @@ public class AuthenticationRequestFilter implements ContainerRequestFilter { } catch (StrolchNotAuthenticatedException e) { logger.error(e.getMessage()); - requestContext.abortWith( - Response.status(Response.Status.UNAUTHORIZED).header(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN) - .entity("User is not authenticated!").build()); + requestContext.abortWith(Response + .status(Response.Status.UNAUTHORIZED) + .header(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN) + .entity("User is not authenticated!") + .build()); } catch (StrolchAccessDeniedException e) { logger.error(e.getMessage()); - requestContext.abortWith( - Response.status(Response.Status.FORBIDDEN).header(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN) - .entity("User is not authorized!").build()); + requestContext.abortWith(Response + .status(Response.Status.UNAUTHORIZED) + .header(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN) + .entity("User is not authorized!") + .build()); } catch (Exception e) { logger.error(e.getMessage()); - requestContext.abortWith( - Response.status(Response.Status.FORBIDDEN).header(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN) - .entity("User cannot access the resource.").build()); + requestContext.abortWith(Response + .status(Response.Status.INTERNAL_SERVER_ERROR) + .header(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN) + .entity("User cannot access the resource.") + .build()); } } @@ -209,9 +217,11 @@ public class AuthenticationRequestFilter implements ContainerRequestFilter { if (isEmpty(sessionId)) { logger.error( "No Authorization header or cookie on request to URL " + requestContext.getUriInfo().getPath()); - requestContext.abortWith( - Response.status(Response.Status.UNAUTHORIZED).header(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN) - .entity("Missing Authorization!").build()); + requestContext.abortWith(Response + .status(Response.Status.UNAUTHORIZED) + .header(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN) + .entity("Missing Authorization!") + .build()); return null; } @@ -223,9 +233,11 @@ public class AuthenticationRequestFilter implements ContainerRequestFilter { if (!getRestful().isBasicAuthEnabled()) { logger.error("Basic Auth is not available for URL " + requestContext.getUriInfo().getPath()); - requestContext.abortWith( - Response.status(Response.Status.FORBIDDEN).header(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN) - .entity("Basic Auth not available").build()); + requestContext.abortWith(Response + .status(Response.Status.FORBIDDEN) + .header(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN) + .entity("Basic Auth not available") + .build()); return null; } @@ -233,9 +245,11 @@ public class AuthenticationRequestFilter implements ContainerRequestFilter { basicAuth = new String(Base64.getDecoder().decode(basicAuth.getBytes()), StandardCharsets.UTF_8); String[] parts = basicAuth.split(":"); if (parts.length != 2) { - requestContext.abortWith( - Response.status(Response.Status.BAD_REQUEST).header(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN) - .entity("Invalid Basic Authorization!").build()); + requestContext.abortWith(Response + .status(Response.Status.BAD_REQUEST) + .header(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN) + .entity("Invalid Basic Authorization!") + .build()); return null; } @@ -256,10 +270,14 @@ public class AuthenticationRequestFilter implements ContainerRequestFilter { Certificate certificate = sessionHandler.validate(sessionId, remoteIp); if (certificate.getUsage() == Usage.SET_PASSWORD) { - if (!requestContext.getUriInfo().getMatchedURIs() + if (!requestContext + .getUriInfo() + .getMatchedURIs() .contains("strolch/privilege/users/" + certificate.getUsername() + "/password")) { - requestContext.abortWith(Response.status(Response.Status.FORBIDDEN) - .header(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN).entity("Can only set password!") + requestContext.abortWith(Response + .status(Response.Status.FORBIDDEN) + .header(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN) + .entity("Can only set password!") .build()); return null; } @@ -269,27 +287,4 @@ public class AuthenticationRequestFilter implements ContainerRequestFilter { requestContext.setProperty(STROLCH_REQUEST_SOURCE, remoteIp); return certificate; } - - public static String getRemoteIp(HttpServletRequest request) { - if (request == null) { - logger.error("HttpServletRequest NOT AVAILABLE! Probably running in TEST!"); - return "(null)"; - } - - String remoteHost = request.getRemoteHost(); - String remoteAddr = request.getRemoteAddr(); - - StringBuilder sb = new StringBuilder(); - if (remoteHost.equals(remoteAddr)) - sb.append(remoteAddr); - else { - sb.append(remoteHost).append(": (").append(remoteAddr).append(")"); - } - - String xForwardedFor = request.getHeader("X-Forwarded-For"); - if (isNotEmpty(xForwardedFor)) - sb.append(" (fwd)=> ").append(xForwardedFor); - - return sb.toString(); - } } diff --git a/web-rest/src/main/java/li/strolch/rest/helper/BasicAuth.java b/web-rest/src/main/java/li/strolch/rest/helper/BasicAuth.java new file mode 100644 index 000000000..458a7ad25 --- /dev/null +++ b/web-rest/src/main/java/li/strolch/rest/helper/BasicAuth.java @@ -0,0 +1,52 @@ +package li.strolch.rest.helper; + +import jakarta.ws.rs.core.Response; +import li.strolch.exception.StrolchAccessDeniedException; +import li.strolch.exception.StrolchNotAuthenticatedException; +import li.strolch.privilege.model.Certificate; +import li.strolch.privilege.model.Usage; +import li.strolch.runtime.sessions.StrolchSessionHandler; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Base64; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static li.strolch.utils.helper.StringHelper.isEmpty; + +public class BasicAuth { + + private static final Logger logger = LoggerFactory.getLogger(BasicAuth.class); + + private StrolchSessionHandler sessionHandler; + + public BasicAuth(StrolchSessionHandler sessionHandler) { + this.sessionHandler = sessionHandler; + } + + public Certificate doBasicAuth(String authorization, String remoteIp) throws BasicAuthFailure { + if (isEmpty(authorization)) + throw new BasicAuthFailure(Response.Status.UNAUTHORIZED, "Missing auth!"); + if (!authorization.startsWith("Basic ")) + throw new BasicAuthFailure(Response.Status.BAD_REQUEST, "Bad request!"); + + try { + String auth = new String(Base64.getDecoder().decode(authorization.substring(6)), UTF_8); + int splitIndex = auth.indexOf(':'); + String username = auth.substring(0, splitIndex); + String password = auth.substring(splitIndex + 1); + + return this.sessionHandler.authenticate(username, password.toCharArray(), remoteIp, Usage.SINGLE, false); + + } catch (StrolchNotAuthenticatedException e) { + logger.error(e.getMessage()); + throw new BasicAuthFailure(Response.Status.UNAUTHORIZED, "Not authenticated!", e); + } catch (StrolchAccessDeniedException e) { + logger.error(e.getMessage()); + throw new BasicAuthFailure(Response.Status.UNAUTHORIZED, "User is not authorized!", e); + } catch (Exception e) { + logger.error(e.getMessage()); + throw new BasicAuthFailure(Response.Status.INTERNAL_SERVER_ERROR, "Internal error", e); + } + } +} diff --git a/web-rest/src/main/java/li/strolch/rest/helper/BasicAuthFailure.java b/web-rest/src/main/java/li/strolch/rest/helper/BasicAuthFailure.java new file mode 100644 index 000000000..c1902e305 --- /dev/null +++ b/web-rest/src/main/java/li/strolch/rest/helper/BasicAuthFailure.java @@ -0,0 +1,28 @@ +package li.strolch.rest.helper; + +import jakarta.ws.rs.core.Response; + +public class BasicAuthFailure extends Exception { + + private final Response.Status status; + private final String reason; + + public BasicAuthFailure(Response.Status status, String reason) { + this.status = status; + this.reason = reason; + } + + public BasicAuthFailure(Response.Status status, String reason, Exception cause) { + super(cause); + this.status = status; + this.reason = reason; + } + + public Response.Status getStatus() { + return status; + } + + public String getReason() { + return reason; + } +} diff --git a/web-rest/src/main/java/li/strolch/rest/helper/RestfulHelper.java b/web-rest/src/main/java/li/strolch/rest/helper/RestfulHelper.java index a4c0de0aa..51ad7d27c 100644 --- a/web-rest/src/main/java/li/strolch/rest/helper/RestfulHelper.java +++ b/web-rest/src/main/java/li/strolch/rest/helper/RestfulHelper.java @@ -15,17 +15,10 @@ */ package li.strolch.rest.helper; -import static java.util.stream.Collectors.toList; - -import jakarta.servlet.http.HttpServletRequest; -import jakarta.ws.rs.core.HttpHeaders; -import java.util.List; -import java.util.Locale; -import java.util.function.Function; -import java.util.stream.Stream; - import com.google.gson.JsonArray; import com.google.gson.JsonObject; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.ws.rs.core.HttpHeaders; import li.strolch.model.StrolchRootElement; import li.strolch.model.visitor.StrolchRootElementVisitor; import li.strolch.privilege.model.Certificate; @@ -36,16 +29,28 @@ import li.strolch.search.SearchResult; import li.strolch.utils.collections.Paging; import li.strolch.utils.dbc.DBC; import li.strolch.utils.helper.StringHelper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; +import java.util.Locale; +import java.util.function.Function; +import java.util.stream.Stream; + +import static java.util.stream.Collectors.toList; +import static li.strolch.utils.helper.StringHelper.isNotEmpty; /** * @author Robert von Burg */ public class RestfulHelper { + private static final Logger logger = LoggerFactory.getLogger(RestfulHelper.class); + public static Locale getLocale(HttpHeaders headers) { if (headers == null || StringHelper.isEmpty(headers.getHeaderString(HttpHeaders.ACCEPT_LANGUAGE))) return null; - return headers.getAcceptableLanguages().get(0); + return headers.getAcceptableLanguages().getFirst(); } public static Certificate getCert(HttpServletRequest request) { @@ -105,4 +110,27 @@ public class RestfulHelper { root.add("data", data); return root; } + + public static String getRemoteIp(HttpServletRequest request) { + if (request == null) { + logger.error("HttpServletRequest NOT AVAILABLE! Probably running in TEST!"); + return "(null)"; + } + + String remoteHost = request.getRemoteHost(); + String remoteAddr = request.getRemoteAddr(); + + StringBuilder sb = new StringBuilder(); + if (remoteHost.equals(remoteAddr)) + sb.append(remoteAddr); + else { + sb.append(remoteHost).append(": (").append(remoteAddr).append(")"); + } + + String xForwardedFor = request.getHeader("X-Forwarded-For"); + if (isNotEmpty(xForwardedFor)) + sb.append(" (fwd)=> ").append(xForwardedFor); + + return sb.toString(); + } } diff --git a/websocket/src/main/java/li/strolch/websocket/WebSocketFilter.java b/websocket/src/main/java/li/strolch/websocket/WebSocketFilter.java index c033c43ef..2834e9c56 100644 --- a/websocket/src/main/java/li/strolch/websocket/WebSocketFilter.java +++ b/websocket/src/main/java/li/strolch/websocket/WebSocketFilter.java @@ -1,6 +1,6 @@ package li.strolch.websocket; -import static li.strolch.rest.filters.AuthenticationRequestFilter.getRemoteIp; +import static li.strolch.rest.helper.RestfulHelper.getRemoteIp; import jakarta.servlet.annotation.WebFilter;