View Javadoc
1   /*
2    * MIT License
3    *
4    * Copyright (c) 2010-2024 The Waffle Project Contributors: https://github.com/Waffle/waffle/graphs/contributors
5    *
6    * Permission is hereby granted, free of charge, to any person obtaining a copy
7    * of this software and associated documentation files (the "Software"), to deal
8    * in the Software without restriction, including without limitation the rights
9    * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10   * copies of the Software, and to permit persons to whom the Software is
11   * furnished to do so, subject to the following conditions:
12   *
13   * The above copyright notice and this permission notice shall be included in all
14   * copies or substantial portions of the Software.
15   *
16   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17   * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18   * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19   * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20   * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21   * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22   * SOFTWARE.
23   */
24  package waffle.servlet.spi;
25  
26  import com.sun.jna.platform.win32.Win32Exception;
27  
28  import jakarta.servlet.http.HttpServletRequest;
29  import jakarta.servlet.http.HttpServletResponse;
30  
31  import java.io.IOException;
32  import java.lang.reflect.Constructor;
33  import java.lang.reflect.InvocationTargetException;
34  import java.util.ArrayList;
35  import java.util.List;
36  
37  import org.slf4j.Logger;
38  import org.slf4j.LoggerFactory;
39  
40  import waffle.util.AuthorizationHeader;
41  import waffle.windows.auth.IWindowsAuthProvider;
42  import waffle.windows.auth.IWindowsIdentity;
43  
44  /**
45   * A collection of security filter providers.
46   */
47  public class SecurityFilterProviderCollection {
48  
49      /** The Constant LOGGER. */
50      private static final Logger LOGGER = LoggerFactory.getLogger(SecurityFilterProviderCollection.class);
51  
52      /** The providers. */
53      private final List<SecurityFilterProvider> providers = new ArrayList<>();
54  
55      /**
56       * Instantiates a new security filter provider collection.
57       *
58       * @param providerArray
59       *            the provider array
60       */
61      public SecurityFilterProviderCollection(final SecurityFilterProvider[] providerArray) {
62          for (final SecurityFilterProvider provider : providerArray) {
63              SecurityFilterProviderCollection.LOGGER.info("using '{}'", provider.getClass().getName());
64              this.providers.add(provider);
65          }
66      }
67  
68      /**
69       * Instantiates a new security filter provider collection.
70       *
71       * @param providerNames
72       *            the provider names
73       * @param auth
74       *            the auth
75       */
76      @SuppressWarnings("unchecked")
77      public SecurityFilterProviderCollection(final String[] providerNames, final IWindowsAuthProvider auth) {
78          Class<SecurityFilterProvider> providerClass;
79          Constructor<SecurityFilterProvider> providerConstructor;
80          for (String providerName : providerNames) {
81              providerName = providerName.trim();
82              SecurityFilterProviderCollection.LOGGER.info("loading '{}'", providerName);
83              try {
84                  providerClass = (Class<SecurityFilterProvider>) Class.forName(providerName);
85                  providerConstructor = providerClass.getConstructor(IWindowsAuthProvider.class);
86                  final SecurityFilterProvider provider = providerConstructor.newInstance(auth);
87                  this.providers.add(provider);
88              } catch (final ClassNotFoundException e) {
89                  throw new RuntimeException(e);
90              } catch (final SecurityException | NoSuchMethodException | IllegalArgumentException | InstantiationException
91                      | IllegalAccessException | InvocationTargetException e) {
92                  SecurityFilterProviderCollection.LOGGER.error("error loading '{}': {}", providerName, e.getMessage());
93                  SecurityFilterProviderCollection.LOGGER.trace("", e);
94              }
95          }
96      }
97  
98      /**
99       * Instantiates a new security filter provider collection.
100      *
101      * @param auth
102      *            the auth
103      */
104     public SecurityFilterProviderCollection(final IWindowsAuthProvider auth) {
105         this.providers.add(new NegotiateSecurityFilterProvider(auth));
106         this.providers.add(new BasicSecurityFilterProvider(auth));
107     }
108 
109     /**
110      * Tests whether a specific security package is supported by any of the underlying providers.
111      *
112      * @param securityPackage
113      *            Security package.
114      *
115      * @return True if the security package is supported, false otherwise.
116      */
117     public boolean isSecurityPackageSupported(final String securityPackage) {
118         return this.get(securityPackage) != null;
119     }
120 
121     /**
122      * Gets the.
123      *
124      * @param securityPackage
125      *            the security package
126      *
127      * @return the security filter provider
128      */
129     private SecurityFilterProvider get(final String securityPackage) {
130         for (final SecurityFilterProvider provider : this.providers) {
131             if (provider.isSecurityPackageSupported(securityPackage)) {
132                 return provider;
133             }
134         }
135         return null;
136     }
137 
138     /**
139      * Filter.
140      *
141      * @param request
142      *            Http Request
143      * @param response
144      *            Http Response
145      *
146      * @return Windows Identity or NULL.
147      *
148      * @throws IOException
149      *             on doFilter.
150      */
151     public IWindowsIdentity doFilter(final HttpServletRequest request, final HttpServletResponse response)
152             throws IOException {
153         final AuthorizationHeader authorizationHeader = new AuthorizationHeader(request);
154         final SecurityFilterProvider provider = this.get(authorizationHeader.getSecurityPackage());
155         if (provider == null) {
156             throw new RuntimeException("Unsupported security package: " + authorizationHeader.getSecurityPackage());
157         }
158         try {
159             return provider.doFilter(request, response);
160         } catch (final Win32Exception e) {
161             throw new IOException(e);
162         }
163     }
164 
165     /**
166      * Returns true if authentication still needs to happen despite an existing principal.
167      *
168      * @param request
169      *            Http Request
170      *
171      * @return True if authentication is required.
172      */
173     public boolean isPrincipalException(final HttpServletRequest request) {
174         for (final SecurityFilterProvider provider : this.providers) {
175             if (provider.isPrincipalException(request)) {
176                 return true;
177             }
178         }
179         return false;
180     }
181 
182     /**
183      * Send authorization headers.
184      *
185      * @param response
186      *            Http Response
187      */
188     public void sendUnauthorized(final HttpServletResponse response) {
189         for (final SecurityFilterProvider provider : this.providers) {
190             provider.sendUnauthorized(response);
191         }
192     }
193 
194     /**
195      * Number of providers.
196      *
197      * @return Number of providers.
198      */
199     public int size() {
200         return this.providers.size();
201     }
202 
203     /**
204      * Get a security provider by class name.
205      *
206      * @param name
207      *            Class name.
208      *
209      * @return A security provider instance.
210      *
211      * @throws ClassNotFoundException
212      *             when class not found.
213      */
214     public SecurityFilterProvider getByClassName(final String name) throws ClassNotFoundException {
215         for (final SecurityFilterProvider provider : this.providers) {
216             if (provider.getClass().getName().equals(name)) {
217                 return provider;
218             }
219         }
220         throw new ClassNotFoundException(name);
221     }
222 }