From 7cc100cb01881107fa7402e008f458e2d6ee53ba Mon Sep 17 00:00:00 2001 From: Ryan Doherty Date: Tue, 10 Feb 2026 23:05:44 -0500 Subject: [PATCH 1/4] Change superclass API to enable easy configuring of endpoint access rules --- .../wdk/service/filter/CheckLoginFilter.java | 50 +++++++++++++++---- 1 file changed, 41 insertions(+), 9 deletions(-) diff --git a/Service/src/main/java/org/gusdb/wdk/service/filter/CheckLoginFilter.java b/Service/src/main/java/org/gusdb/wdk/service/filter/CheckLoginFilter.java index 3cebf61f7..4424acc53 100644 --- a/Service/src/main/java/org/gusdb/wdk/service/filter/CheckLoginFilter.java +++ b/Service/src/main/java/org/gusdb/wdk/service/filter/CheckLoginFilter.java @@ -7,6 +7,7 @@ import javax.inject.Provider; import javax.servlet.ServletContext; import javax.servlet.http.HttpServletRequest; +import javax.ws.rs.ForbiddenException; import javax.ws.rs.NotAuthorizedException; import javax.ws.rs.container.ContainerRequestContext; import javax.ws.rs.container.ContainerRequestFilter; @@ -57,6 +58,26 @@ public class CheckLoginFilter implements ContainerRequestFilter, ContainerRespon @Inject protected Provider _grizzlyRequest; + /*************** The following methods control the default behavior for WDK endpoints ************/ + + // override and add paths to this list if no authentication is required AND' + // no guest user should be created for this request + protected boolean isPathToSkip(String path) { + // skip user check for prometheus metrics requests + return SystemService.PROMETHEUS_ENDPOINT_PATH.equals(path); + } + + // override and add paths to this list if valid token is required (no guest will be created) + protected boolean isValidTokenRequired(String path) { + return false; + } + + // authentication is required AND + // if token is absent or expired, create new guest to use for this request + protected boolean isGuestUserAllowed(String path) { + return true; + } + @Override public void filter(ContainerRequestContext requestContext) throws IOException { // skip endpoints which do not require a user; prevents guests from being unnecessarily created @@ -72,16 +93,32 @@ public void filter(ContainerRequestContext requestContext) throws IOException { try { if (rawToken == null) { - // no credentials submitted; automatically create a guest to use on this request - useNewGuest(factory, request, requestContext, requestPath); + // no credentials submitted; check requirements of this path + if (isValidTokenRequired(requestPath)) { + LOG.warn("Did not received bearer token as required for path:" + requestPath); + throw new NotAuthorizedException(Response.status(Status.UNAUTHORIZED).build()); + } + // if allowed, automatically create a guest to use on this request + if (isGuestUserAllowed(requestPath)) { + useNewGuest(factory, request, requestContext, requestPath); + return; + } + // no authentication provided, and guests are disallowed + throw new NotAuthorizedException(Response.status(Status.UNAUTHORIZED).build()); } else { try { // validate submitted token ValidatedToken token = factory.validateBearerToken(rawToken); User user = factory.convertToUser(token); - setRequestAttributes(request, token, user); - LOG.info("Validated successfully. Request will be processed for user " + user.getUserId()); + if (isGuestUserAllowed(requestPath)) { + setRequestAttributes(request, token, user); + LOG.info("Validated successfully. Request will be processed for user " + user.getUserId()); + } + else { + // valid guest token submitted, but guests disallowed for this path + throw new ForbiddenException(); + } } catch (ExpiredTokenException e) { // token is expired; use guest token for now which should inspire them to log back in @@ -131,11 +168,6 @@ private String findRawBearerToken(RequestData request, ContainerRequestContext r return cookie == null ? null : cookie.getValue(); } - protected boolean isPathToSkip(String path) { - // skip user check for prometheus metrics requests - return SystemService.PROMETHEUS_ENDPOINT_PATH.equals(path); - } - @Override public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) throws IOException { From 16a91d7c1980a0e5bf7f08cbdf836fec37465a5b Mon Sep 17 00:00:00 2001 From: Ryan Doherty Date: Wed, 11 Feb 2026 00:18:14 -0500 Subject: [PATCH 2/4] Slight exception handling change --- .../org/gusdb/wdk/service/filter/CheckLoginFilter.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Service/src/main/java/org/gusdb/wdk/service/filter/CheckLoginFilter.java b/Service/src/main/java/org/gusdb/wdk/service/filter/CheckLoginFilter.java index 4424acc53..0dbeabc6d 100644 --- a/Service/src/main/java/org/gusdb/wdk/service/filter/CheckLoginFilter.java +++ b/Service/src/main/java/org/gusdb/wdk/service/filter/CheckLoginFilter.java @@ -58,7 +58,7 @@ public class CheckLoginFilter implements ContainerRequestFilter, ContainerRespon @Inject protected Provider _grizzlyRequest; - /*************** The following methods control the default behavior for WDK endpoints ************/ + /*************** The following three methods control the default behavior for WDK endpoints ************/ // override and add paths to this list if no authentication is required AND' // no guest user should be created for this request @@ -80,7 +80,7 @@ protected boolean isGuestUserAllowed(String path) { @Override public void filter(ContainerRequestContext requestContext) throws IOException { - // skip endpoints which do not require a user; prevents guests from being unnecessarily created + // skip endpoints which do not require auth nor a guest user; prevents guests from being unnecessarily created String requestPath = requestContext.getUriInfo().getPath(); if (isPathToSkip(requestPath)) return; @@ -131,10 +131,10 @@ public void filter(ContainerRequestContext requestContext) throws IOException { } } } - catch (Exception e) { + catch (WdkModelException | RuntimeException e) { // any other exception is fatal, but log first LOG.error("Unable to authenticate with Authorization header " + rawToken, e); - throw e instanceof RuntimeException ? (RuntimeException)e : new WdkRuntimeException(e); + throw e instanceof WdkModelException ? new WdkRuntimeException(e) : (RuntimeException)e; } } From 4cab27e020eced097250dea2b96be0139d740810 Mon Sep 17 00:00:00 2001 From: Ryan Doherty Date: Wed, 11 Feb 2026 07:00:32 -0500 Subject: [PATCH 3/4] Clearer comments on new methods --- .../wdk/service/filter/CheckLoginFilter.java | 39 +++++++++++++++---- 1 file changed, 32 insertions(+), 7 deletions(-) diff --git a/Service/src/main/java/org/gusdb/wdk/service/filter/CheckLoginFilter.java b/Service/src/main/java/org/gusdb/wdk/service/filter/CheckLoginFilter.java index 0dbeabc6d..de93ef054 100644 --- a/Service/src/main/java/org/gusdb/wdk/service/filter/CheckLoginFilter.java +++ b/Service/src/main/java/org/gusdb/wdk/service/filter/CheckLoginFilter.java @@ -60,20 +60,37 @@ public class CheckLoginFilter implements ContainerRequestFilter, ContainerRespon /*************** The following three methods control the default behavior for WDK endpoints ************/ - // override and add paths to this list if no authentication is required AND' - // no guest user should be created for this request + /** + * @param path request URL path + * @return true if no authorization is required and no guest + * user should be created for this request, else false + */ protected boolean isPathToSkip(String path) { // skip user check for prometheus metrics requests return SystemService.PROMETHEUS_ENDPOINT_PATH.equals(path); } - // override and add paths to this list if valid token is required (no guest will be created) + /** + * A return value of true indicates a valid bearer token is required; the token + * may be a guest depending on the value of isGuestUserAllowed(). If false is + * returned, no token is present, and isGuestUserAllowed() returns true, then a + * new guest token will be generated for this request and returned to the user. + * + * @param path request URL path + * @return true if a valid bearer token is required on the request, else false + */ protected boolean isValidTokenRequired(String path) { return false; } - // authentication is required AND - // if token is absent or expired, create new guest to use for this request + /** + * A return value of true indicates a guest user is allowed to access this + * endpoint. If a sent token is absent and isValidTokenRequired() returns false, + * a new guest token will be generated for use on this request. + * + * @param path request URL path + * @return true if guests are allowed to access this endpoint, else false + */ protected boolean isGuestUserAllowed(String path) { return true; } @@ -121,8 +138,16 @@ public void filter(ContainerRequestContext requestContext) throws IOException { } } catch (ExpiredTokenException e) { - // token is expired; use guest token for now which should inspire them to log back in - useNewGuest(factory, request, requestContext, requestPath); + if (isGuestUserAllowed(requestPath)) { + // token is expired, but guest token is allowed to be generated, + // which will hopefully inspire them to log back in + useNewGuest(factory, request, requestContext, requestPath); + } + else { + throw new NotAuthorizedException(Response.status(Status.UNAUTHORIZED) + .entity("Authorization token has expired.").build()); + + } } catch (InvalidTokenException e) { // passed token is invalid; throw 401 From 47daab757f7115cb2469f4ef2c82921e5ac4a3fc Mon Sep 17 00:00:00 2001 From: Ryan Doherty Date: Wed, 11 Feb 2026 13:10:14 -0500 Subject: [PATCH 4/4] Fix logic for guests --- .../wdk/service/filter/CheckLoginFilter.java | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/Service/src/main/java/org/gusdb/wdk/service/filter/CheckLoginFilter.java b/Service/src/main/java/org/gusdb/wdk/service/filter/CheckLoginFilter.java index de93ef054..d7cdf4e37 100644 --- a/Service/src/main/java/org/gusdb/wdk/service/filter/CheckLoginFilter.java +++ b/Service/src/main/java/org/gusdb/wdk/service/filter/CheckLoginFilter.java @@ -118,19 +118,21 @@ public void filter(ContainerRequestContext requestContext) throws IOException { // if allowed, automatically create a guest to use on this request if (isGuestUserAllowed(requestPath)) { useNewGuest(factory, request, requestContext, requestPath); - return; } - // no authentication provided, and guests are disallowed - throw new NotAuthorizedException(Response.status(Status.UNAUTHORIZED).build()); + else { + // no authentication provided, and guests are disallowed + throw new NotAuthorizedException(Response.status(Status.UNAUTHORIZED).build()); + } } else { try { // validate submitted token ValidatedToken token = factory.validateBearerToken(rawToken); User user = factory.convertToUser(token); - if (isGuestUserAllowed(requestPath)) { + if (!user.isGuest() || isGuestUserAllowed(requestPath)) { setRequestAttributes(request, token, user); - LOG.info("Validated successfully. Request will be processed for user " + user.getUserId()); + LOG.info("Validated successfully. Request will be processed for " + + (user.isGuest() ? "guest" : "registered") + " user " + user.getUserId()); } else { // valid guest token submitted, but guests disallowed for this path @@ -139,14 +141,13 @@ public void filter(ContainerRequestContext requestContext) throws IOException { } catch (ExpiredTokenException e) { if (isGuestUserAllowed(requestPath)) { - // token is expired, but guest token is allowed to be generated, + // token is expired, but a new guest token is allowed to be generated, // which will hopefully inspire them to log back in useNewGuest(factory, request, requestContext, requestPath); } else { throw new NotAuthorizedException(Response.status(Status.UNAUTHORIZED) .entity("Authorization token has expired.").build()); - } } catch (InvalidTokenException e) {