[Major] Refactored basic auth and getRemoteIp() helper

This commit is contained in:
Robert von Burg 2024-03-21 14:23:49 +01:00
parent 2aca456729
commit a7258f0d06
Signed by: eitch
GPG Key ID: 75DB9C85C74331F7
6 changed files with 162 additions and 59 deletions

View File

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

View File

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

View File

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

View File

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

View File

@ -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 <eitch@eitchnet.ch>
*/
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();
}
}

View File

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