xref: /aosp_15_r20/external/jsoup/src/test/java/org/jsoup/integration/servlets/AuthFilter.java (revision 6da8f8c4bc310ad659121b84dd089062417a2ce2)
1 package org.jsoup.integration.servlets;
2 
3 import javax.servlet.Filter;
4 import javax.servlet.FilterChain;
5 import javax.servlet.FilterConfig;
6 import javax.servlet.ServletException;
7 import javax.servlet.ServletRequest;
8 import javax.servlet.ServletResponse;
9 import javax.servlet.http.HttpServletRequest;
10 import javax.servlet.http.HttpServletResponse;
11 import java.io.IOException;
12 import java.nio.charset.StandardCharsets;
13 import java.util.Base64;
14 
15 /**
16  A filter to test basic authenticated requests. If the request header "X-Wants-Authentication" is set, or if
17  alwaysWantsAuth is enabled, the filter is invoked, and requests must send the correct user authentication details.
18  */
19 public class AuthFilter implements Filter {
20     public static final String WantsServerAuthentication = "X-Wants-ServerAuthentication";
21     public static final String ServerUser = "admin";
22     public static final String ServerRealm = "jsoup test server authentication realm";
23     private static volatile String ServerPassword = newServerPassword();
24 
25     public static final String WantsProxyAuthentication = "X-Wants-ProxyAuthentication";
26     public static final String ProxyUser = "foxyproxy";
27     public static final String ProxyRealm = "jsoup test proxy authentication realm";
28     private static volatile String ProxyPassword = newProxyPassword();
29 
30     private final boolean alwaysWantsAuth; // we run a particular port that always wants auth - so the CONNECT tunnels can be authed. (The Java proxy tunnel CONNECT request strips the wants-auth headers)
31     private final boolean forProxy;
32     private final String wantsHeader;
33     private final String authorizationHeader;
34 
35     /**
36      Creates an Authentication Filter with hardcoded credential expectations.
37      * @param alwaysWantsAuth true if this filter should always check for authentication, regardless of the Wants Auth header
38      * @param forProxy true if this wraps a Proxy and should use Proxy-Authenticate headers, credentials etc. False
39      * if wrapping the web server.
40      */
AuthFilter(boolean alwaysWantsAuth, boolean forProxy)41     public AuthFilter(boolean alwaysWantsAuth, boolean forProxy) {
42         this.alwaysWantsAuth = alwaysWantsAuth;
43         this.forProxy = forProxy;
44 
45         wantsHeader = forProxy ? WantsProxyAuthentication : WantsServerAuthentication;
46         authorizationHeader = forProxy ? "Proxy-Authorization" : "Authorization";
47     }
48 
newPassword()49     private static String newPassword() {
50         return "pass-" + Math.random();
51     }
52 
53     // passwords get rotated in tests so that Java's auth cache is invalidated and a new auth callback occurs.
54     // requires tests hitting these are called serially.
newServerPassword()55     public static String newServerPassword() {
56         return ServerPassword = newPassword() + "-server";
57     }
58 
newProxyPassword()59     public static String newProxyPassword() {
60         return ProxyPassword = newPassword() + "-proxy";
61     }
62 
init(FilterConfig filterConfig)63     @Override public void init(FilterConfig filterConfig) throws ServletException {}
64 
65     @Override
doFilter(ServletRequest request, ServletResponse response, FilterChain chain)66     public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
67         HttpServletRequest req = (HttpServletRequest) request;
68         HttpServletResponse res = (HttpServletResponse) response;
69 
70         boolean accessGranted = checkAuth(req);
71         if (accessGranted) {
72             chain.doFilter(request, response);
73             return;
74         }
75 
76         // Wants but failed auth - send appropriate header:
77         if (forProxy) {
78             res.setHeader("Proxy-Authenticate", "Basic realm=\"" + ProxyRealm + "\"");
79             // ^^ Duped in ProxyServlet for CONNECT
80             res.sendError(HttpServletResponse.SC_PROXY_AUTHENTICATION_REQUIRED);
81         } else {
82             res.setHeader("WWW-Authenticate", "Basic realm=\"" + ServerRealm + "\"");
83             res.sendError(HttpServletResponse.SC_UNAUTHORIZED);
84         }
85     }
86 
destroy()87     @Override public void destroy() {}
88 
checkAuth(HttpServletRequest req)89     public boolean checkAuth(HttpServletRequest req) {
90         if (alwaysWantsAuth || req.getHeader(wantsHeader) != null) {
91             String authHeader = req.getHeader(authorizationHeader);
92             if (authHeader != null) {
93                 int space = authHeader.indexOf(' ');
94                 if (space > 0) {
95                     String value = authHeader.substring(space + 1);
96                     String expected = forProxy ?
97                         (ProxyUser + ":" + ProxyPassword) :
98                         (ServerUser + ":" + ServerPassword);
99                     String base64 = Base64.getEncoder().encodeToString(expected.getBytes(StandardCharsets.UTF_8));
100                     return base64.equals(value); // if passed auth
101                 }
102             }
103             return false; // unexpected header value
104         }
105         return true; // auth not required
106     }
107 }
108