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 java.io.IOException;
29  import java.lang.reflect.Constructor;
30  import java.lang.reflect.InvocationTargetException;
31  import java.util.ArrayList;
32  import java.util.List;
33  
34  import javax.servlet.http.HttpServletRequest;
35  import javax.servlet.http.HttpServletResponse;
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                  SecurityFilterProviderCollection.LOGGER.error("error loading '{}'", providerName);
90                  throw new RuntimeException(e);
91              } catch (final SecurityException | NoSuchMethodException | IllegalArgumentException | InstantiationException
92                      | IllegalAccessException | InvocationTargetException e) {
93                  SecurityFilterProviderCollection.LOGGER.error("error loading '{}': {}", providerName, e.getMessage());
94                  SecurityFilterProviderCollection.LOGGER.trace("", e);
95              }
96          }
97      }
98  
99      /**
100      * Instantiates a new security filter provider collection.
101      *
102      * @param auth
103      *            the auth
104      */
105     public SecurityFilterProviderCollection(final IWindowsAuthProvider auth) {
106         this.providers.add(new NegotiateSecurityFilterProvider(auth));
107         this.providers.add(new BasicSecurityFilterProvider(auth));
108     }
109 
110     /**
111      * Tests whether a specific security package is supported by any of the underlying providers.
112      *
113      * @param securityPackage
114      *            Security package.
115      *
116      * @return True if the security package is supported, false otherwise.
117      */
118     public boolean isSecurityPackageSupported(final String securityPackage) {
119         return this.get(securityPackage) != null;
120     }
121 
122     /**
123      * Gets the.
124      *
125      * @param securityPackage
126      *            the security package
127      *
128      * @return the security filter provider
129      */
130     private SecurityFilterProvider get(final String securityPackage) {
131         for (final SecurityFilterProvider provider : this.providers) {
132             if (provider.isSecurityPackageSupported(securityPackage)) {
133                 return provider;
134             }
135         }
136         return null;
137     }
138 
139     /**
140      * Filter.
141      *
142      * @param request
143      *            Http Request
144      * @param response
145      *            Http Response
146      *
147      * @return Windows Identity or NULL.
148      *
149      * @throws IOException
150      *             on doFilter.
151      */
152     public IWindowsIdentity doFilter(final HttpServletRequest request, final HttpServletResponse response)
153             throws IOException {
154         final AuthorizationHeader authorizationHeader = new AuthorizationHeader(request);
155         final SecurityFilterProvider provider = this.get(authorizationHeader.getSecurityPackage());
156         if (provider == null) {
157             throw new RuntimeException("Unsupported security package: " + authorizationHeader.getSecurityPackage());
158         }
159         try {
160             return provider.doFilter(request, response);
161         } catch (final Win32Exception e) {
162             throw new IOException(e);
163         }
164     }
165 
166     /**
167      * Returns true if authentication still needs to happen despite an existing principal.
168      *
169      * @param request
170      *            Http Request
171      *
172      * @return True if authentication is required.
173      */
174     public boolean isPrincipalException(final HttpServletRequest request) {
175         for (final SecurityFilterProvider provider : this.providers) {
176             if (provider.isPrincipalException(request)) {
177                 return true;
178             }
179         }
180         return false;
181     }
182 
183     /**
184      * Send authorization headers.
185      *
186      * @param response
187      *            Http Response
188      */
189     public void sendUnauthorized(final HttpServletResponse response) {
190         for (final SecurityFilterProvider provider : this.providers) {
191             provider.sendUnauthorized(response);
192         }
193     }
194 
195     /**
196      * Number of providers.
197      *
198      * @return Number of providers.
199      */
200     public int size() {
201         return this.providers.size();
202     }
203 
204     /**
205      * Get a security provider by class name.
206      *
207      * @param name
208      *            Class name.
209      *
210      * @return A security provider instance.
211      *
212      * @throws ClassNotFoundException
213      *             when class not found.
214      */
215     public SecurityFilterProvider getByClassName(final String name) throws ClassNotFoundException {
216         for (final SecurityFilterProvider provider : this.providers) {
217             if (provider.getClass().getName().equals(name)) {
218                 return provider;
219             }
220         }
221         throw new ClassNotFoundException(name);
222     }
223 }