View Javadoc
1   /*
2    * SPDX-License-Identifier: MIT
3    * See LICENSE file for details.
4    *
5    * Copyright 2010-2026 The Waffle Project Contributors: https://github.com/Waffle/waffle/graphs/contributors
6    */
7   package waffle.servlet.spi;
8   
9   import com.sun.jna.platform.win32.Win32Exception;
10  
11  import jakarta.servlet.http.HttpServletRequest;
12  import jakarta.servlet.http.HttpServletResponse;
13  
14  import java.io.IOException;
15  import java.lang.reflect.Constructor;
16  import java.lang.reflect.InvocationTargetException;
17  import java.util.ArrayList;
18  import java.util.List;
19  
20  import org.slf4j.Logger;
21  import org.slf4j.LoggerFactory;
22  
23  import waffle.util.AuthorizationHeader;
24  import waffle.windows.auth.IWindowsAuthProvider;
25  import waffle.windows.auth.IWindowsIdentity;
26  
27  /**
28   * A collection of security filter providers.
29   */
30  public class SecurityFilterProviderCollection {
31  
32      /** The Constant LOGGER. */
33      private static final Logger LOGGER = LoggerFactory.getLogger(SecurityFilterProviderCollection.class);
34  
35      /** The providers. */
36      private final List<SecurityFilterProvider> providers = new ArrayList<>();
37  
38      /**
39       * Instantiates a new security filter provider collection.
40       *
41       * @param providerArray
42       *            the provider array
43       */
44      public SecurityFilterProviderCollection(final SecurityFilterProvider[] providerArray) {
45          for (final SecurityFilterProvider provider : providerArray) {
46              SecurityFilterProviderCollection.LOGGER.info("using '{}'", provider.getClass().getName());
47              this.providers.add(provider);
48          }
49      }
50  
51      /**
52       * Instantiates a new security filter provider collection.
53       *
54       * @param providerNames
55       *            the provider names
56       * @param auth
57       *            the auth
58       */
59      @SuppressWarnings("unchecked")
60      public SecurityFilterProviderCollection(final String[] providerNames, final IWindowsAuthProvider auth) {
61          Class<SecurityFilterProvider> providerClass;
62          Constructor<SecurityFilterProvider> providerConstructor;
63          for (String providerName : providerNames) {
64              providerName = providerName.trim();
65              SecurityFilterProviderCollection.LOGGER.info("loading '{}'", providerName);
66              try {
67                  providerClass = (Class<SecurityFilterProvider>) Class.forName(providerName);
68                  providerConstructor = providerClass.getConstructor(IWindowsAuthProvider.class);
69                  final SecurityFilterProvider provider = providerConstructor.newInstance(auth);
70                  this.providers.add(provider);
71              } catch (final ClassNotFoundException e) {
72                  throw new RuntimeException(e);
73              } catch (final SecurityException | NoSuchMethodException | IllegalArgumentException | InstantiationException
74                      | IllegalAccessException | InvocationTargetException e) {
75                  SecurityFilterProviderCollection.LOGGER.error("error loading '{}': {}", providerName, e.getMessage());
76                  SecurityFilterProviderCollection.LOGGER.trace("", e);
77              }
78          }
79      }
80  
81      /**
82       * Instantiates a new security filter provider collection.
83       *
84       * @param auth
85       *            the auth
86       */
87      public SecurityFilterProviderCollection(final IWindowsAuthProvider auth) {
88          this.providers.add(new NegotiateSecurityFilterProvider(auth));
89          this.providers.add(new BasicSecurityFilterProvider(auth));
90      }
91  
92      /**
93       * Tests whether a specific security package is supported by any of the underlying providers.
94       *
95       * @param securityPackage
96       *            Security package.
97       *
98       * @return True if the security package is supported, false otherwise.
99       */
100     public boolean isSecurityPackageSupported(final String securityPackage) {
101         return this.get(securityPackage) != null;
102     }
103 
104     /**
105      * Gets the.
106      *
107      * @param securityPackage
108      *            the security package
109      *
110      * @return the security filter provider
111      */
112     private SecurityFilterProvider get(final String securityPackage) {
113         for (final SecurityFilterProvider provider : this.providers) {
114             if (provider.isSecurityPackageSupported(securityPackage)) {
115                 return provider;
116             }
117         }
118         return null;
119     }
120 
121     /**
122      * Filter.
123      *
124      * @param request
125      *            Http Request
126      * @param response
127      *            Http Response
128      *
129      * @return Windows Identity or NULL.
130      *
131      * @throws IOException
132      *             on doFilter.
133      */
134     public IWindowsIdentity doFilter(final HttpServletRequest request, final HttpServletResponse response)
135             throws IOException {
136         final AuthorizationHeader authorizationHeader = new AuthorizationHeader(request);
137         final SecurityFilterProvider provider = this.get(authorizationHeader.getSecurityPackage());
138         if (provider == null) {
139             throw new RuntimeException("Unsupported security package: " + authorizationHeader.getSecurityPackage());
140         }
141         try {
142             return provider.doFilter(request, response);
143         } catch (final Win32Exception e) {
144             throw new IOException(e);
145         }
146     }
147 
148     /**
149      * Returns true if authentication still needs to happen despite an existing principal.
150      *
151      * @param request
152      *            Http Request
153      *
154      * @return True if authentication is required.
155      */
156     public boolean isPrincipalException(final HttpServletRequest request) {
157         for (final SecurityFilterProvider provider : this.providers) {
158             if (provider.isPrincipalException(request)) {
159                 return true;
160             }
161         }
162         return false;
163     }
164 
165     /**
166      * Send authorization headers.
167      *
168      * @param response
169      *            Http Response
170      */
171     public void sendUnauthorized(final HttpServletResponse response) {
172         for (final SecurityFilterProvider provider : this.providers) {
173             provider.sendUnauthorized(response);
174         }
175     }
176 
177     /**
178      * Number of providers.
179      *
180      * @return Number of providers.
181      */
182     public int size() {
183         return this.providers.size();
184     }
185 
186     /**
187      * Get a security provider by class name.
188      *
189      * @param name
190      *            Class name.
191      *
192      * @return A security provider instance.
193      *
194      * @throws ClassNotFoundException
195      *             when class not found.
196      */
197     public SecurityFilterProvider getByClassName(final String name) throws ClassNotFoundException {
198         for (final SecurityFilterProvider provider : this.providers) {
199             if (provider.getClass().getName().equals(name)) {
200                 return provider;
201             }
202         }
203         throw new ClassNotFoundException(name);
204     }
205 }