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.spring;
25  
26  import java.io.IOException;
27  import java.util.Locale;
28  
29  import javax.servlet.FilterChain;
30  import javax.servlet.ServletException;
31  import javax.servlet.ServletRequest;
32  import javax.servlet.ServletResponse;
33  import javax.servlet.http.HttpServletRequest;
34  import javax.servlet.http.HttpServletResponse;
35  
36  import org.slf4j.Logger;
37  import org.slf4j.LoggerFactory;
38  import org.springframework.security.core.Authentication;
39  import org.springframework.security.core.GrantedAuthority;
40  import org.springframework.security.core.context.SecurityContextHolder;
41  import org.springframework.web.filter.GenericFilterBean;
42  
43  import waffle.servlet.AutoDisposableWindowsPrincipal;
44  import waffle.servlet.WindowsPrincipal;
45  import waffle.servlet.spi.SecurityFilterProviderCollection;
46  import waffle.util.AuthorizationHeader;
47  import waffle.windows.auth.IWindowsIdentity;
48  import waffle.windows.auth.IWindowsImpersonationContext;
49  import waffle.windows.auth.PrincipalFormat;
50  
51  /**
52   * A Spring Negotiate security filter.
53   */
54  public class NegotiateSecurityFilter extends GenericFilterBean {
55  
56      /** The Constant LOGGER. */
57      private static final Logger LOGGER = LoggerFactory.getLogger(NegotiateSecurityFilter.class);
58  
59      /** The provider. */
60      private SecurityFilterProviderCollection provider;
61  
62      /** The principal format. */
63      private PrincipalFormat principalFormat = PrincipalFormat.FQN;
64  
65      /** The role format. */
66      private PrincipalFormat roleFormat = PrincipalFormat.FQN;
67  
68      /** The allow guest login. */
69      private boolean allowGuestLogin = true;
70  
71      /** The impersonate. */
72      private boolean impersonate;
73  
74      /** The granted authority factory. */
75      private GrantedAuthorityFactory grantedAuthorityFactory = WindowsAuthenticationToken.DEFAULT_GRANTED_AUTHORITY_FACTORY;
76  
77      /** The default granted authority. */
78      private GrantedAuthority defaultGrantedAuthority = WindowsAuthenticationToken.DEFAULT_GRANTED_AUTHORITY;
79  
80      /**
81       * Instantiates a new negotiate security filter.
82       */
83      public NegotiateSecurityFilter() {
84          super();
85          NegotiateSecurityFilter.LOGGER.debug("[waffle.spring.NegotiateSecurityFilter] loaded");
86      }
87  
88      @Override
89      public void doFilter(final ServletRequest req, final ServletResponse res, final FilterChain chain)
90              throws IOException, ServletException {
91  
92          final HttpServletRequest request = (HttpServletRequest) req;
93          final HttpServletResponse response = (HttpServletResponse) res;
94  
95          NegotiateSecurityFilter.LOGGER.debug("{} {}, contentlength: {}", request.getMethod(), request.getRequestURI(),
96                  Integer.valueOf(request.getContentLength()));
97  
98          final AuthorizationHeader authorizationHeader = new AuthorizationHeader(request);
99  
100         // authenticate user
101         if (!authorizationHeader.isNull()
102                 && this.provider.isSecurityPackageSupported(authorizationHeader.getSecurityPackage())) {
103 
104             // log the user in using the token
105             IWindowsIdentity windowsIdentity;
106 
107             try {
108                 windowsIdentity = this.provider.doFilter(request, response);
109                 if (windowsIdentity == null) {
110                     return;
111                 }
112             } catch (final IOException e) {
113                 NegotiateSecurityFilter.LOGGER.warn("error logging in user: {}", e.getMessage());
114                 NegotiateSecurityFilter.LOGGER.trace("", e);
115                 this.sendUnauthorized(response, true);
116                 return;
117             }
118 
119             IWindowsImpersonationContext ctx = null;
120             try {
121                 if (!this.allowGuestLogin && windowsIdentity.isGuest()) {
122                     NegotiateSecurityFilter.LOGGER.warn("guest login disabled: {}", windowsIdentity.getFqn());
123                     this.sendUnauthorized(response, true);
124                     return;
125                 }
126 
127                 NegotiateSecurityFilter.LOGGER.debug("logged in user: {} ({})", windowsIdentity.getFqn(),
128                         windowsIdentity.getSidString());
129 
130                 final WindowsPrincipal principal = this.impersonate
131                         ? new AutoDisposableWindowsPrincipal(windowsIdentity, this.principalFormat, this.roleFormat)
132                         : new WindowsPrincipal(windowsIdentity, this.principalFormat, this.roleFormat);
133 
134                 NegotiateSecurityFilter.LOGGER.debug("roles: {}", principal.getRolesString());
135 
136                 final Authentication authentication = new WindowsAuthenticationToken(principal,
137                         this.grantedAuthorityFactory, this.defaultGrantedAuthority);
138 
139                 if (!this.setAuthentication(request, response, authentication)) {
140                     return;
141                 }
142 
143                 NegotiateSecurityFilter.LOGGER.info("successfully logged in user: {}", windowsIdentity.getFqn());
144 
145                 if (this.impersonate) {
146                     NegotiateSecurityFilter.LOGGER.debug("impersonating user");
147                     ctx = windowsIdentity.impersonate();
148                 }
149 
150                 chain.doFilter(request, response);
151             } finally {
152                 if (this.impersonate && ctx != null) {
153                     NegotiateSecurityFilter.LOGGER.debug("terminating impersonation");
154                     ctx.revertToSelf();
155                 } else {
156                     windowsIdentity.dispose();
157                 }
158             }
159         } else {
160             chain.doFilter(request, response);
161         }
162     }
163 
164     /**
165      * Invoked when authentication towards ad was succesful to populate securitycontext Override to add service provider
166      * authorization checks.
167      *
168      * @param request
169      *            the request
170      * @param response
171      *            the response
172      * @param authentication
173      *            the authentication
174      *
175      * @return true, if successful
176      */
177     protected boolean setAuthentication(final HttpServletRequest request, final HttpServletResponse response,
178             final Authentication authentication) {
179         SecurityContextHolder.getContext().setAuthentication(authentication);
180         return true;
181     }
182 
183     @Override
184     public void afterPropertiesSet() throws ServletException {
185         super.afterPropertiesSet();
186 
187         if (this.provider == null) {
188             throw new ServletException("Missing NegotiateSecurityFilter.Provider");
189         }
190     }
191 
192     /**
193      * Send a 401 Unauthorized along with protocol authentication headers.
194      *
195      * @param response
196      *            HTTP Response
197      * @param close
198      *            Close connection.
199      */
200     protected void sendUnauthorized(final HttpServletResponse response, final boolean close) {
201         try {
202             this.provider.sendUnauthorized(response);
203             if (close) {
204                 response.setHeader("Connection", "close");
205             } else {
206                 response.setHeader("Connection", "keep-alive");
207             }
208             response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
209             response.flushBuffer();
210         } catch (final IOException e) {
211             throw new RuntimeException(e);
212         }
213     }
214 
215     /**
216      * Gets the principal format.
217      *
218      * @return the principal format
219      */
220     public PrincipalFormat getPrincipalFormat() {
221         return this.principalFormat;
222     }
223 
224     /**
225      * Sets the principal format enum.
226      *
227      * @param value
228      *            the new principal format enum
229      */
230     public void setPrincipalFormatEnum(final PrincipalFormat value) {
231         this.principalFormat = value;
232     }
233 
234     /**
235      * Sets the principal format.
236      *
237      * @param value
238      *            the new principal format
239      */
240     public void setPrincipalFormat(final String value) {
241         this.setPrincipalFormatEnum(PrincipalFormat.valueOf(value.toUpperCase(Locale.ENGLISH)));
242     }
243 
244     /**
245      * Gets the role format.
246      *
247      * @return the role format
248      */
249     public PrincipalFormat getRoleFormat() {
250         return this.roleFormat;
251     }
252 
253     /**
254      * Sets the role format enum.
255      *
256      * @param value
257      *            the new role format enum
258      */
259     public void setRoleFormatEnum(final PrincipalFormat value) {
260         this.roleFormat = value;
261     }
262 
263     /**
264      * Sets the role format.
265      *
266      * @param value
267      *            the new role format
268      */
269     public void setRoleFormat(final String value) {
270         this.setRoleFormatEnum(PrincipalFormat.valueOf(value.toUpperCase(Locale.ENGLISH)));
271     }
272 
273     /**
274      * Checks if is allow guest login.
275      *
276      * @return true, if is allow guest login
277      */
278     public boolean isAllowGuestLogin() {
279         return this.allowGuestLogin;
280     }
281 
282     /**
283      * Sets the allow guest login.
284      *
285      * @param value
286      *            the new allow guest login
287      */
288     public void setAllowGuestLogin(final boolean value) {
289         this.allowGuestLogin = value;
290     }
291 
292     /**
293      * Enable/Disable impersonation.
294      *
295      * @param value
296      *            true to enable impersonation, false otherwise
297      */
298     public void setImpersonate(final boolean value) {
299         this.impersonate = value;
300     }
301 
302     /**
303      * Checks if is impersonate.
304      *
305      * @return true if impersonation is enabled, false otherwise
306      */
307     public boolean isImpersonate() {
308         return this.impersonate;
309     }
310 
311     /**
312      * Gets the provider.
313      *
314      * @return the provider
315      */
316     public SecurityFilterProviderCollection getProvider() {
317         return this.provider;
318     }
319 
320     /**
321      * Sets the provider.
322      *
323      * @param value
324      *            the new provider
325      */
326     public void setProvider(final SecurityFilterProviderCollection value) {
327         this.provider = value;
328     }
329 
330     /**
331      * Gets the granted authority factory.
332      *
333      * @return the granted authority factory
334      */
335     public GrantedAuthorityFactory getGrantedAuthorityFactory() {
336         return this.grantedAuthorityFactory;
337     }
338 
339     /**
340      * Sets the granted authority factory.
341      *
342      * @param value
343      *            the new granted authority factory
344      */
345     public void setGrantedAuthorityFactory(final GrantedAuthorityFactory value) {
346         this.grantedAuthorityFactory = value;
347     }
348 
349     /**
350      * Gets the default granted authority.
351      *
352      * @return the default granted authority
353      */
354     public GrantedAuthority getDefaultGrantedAuthority() {
355         return this.defaultGrantedAuthority;
356     }
357 
358     /**
359      * Sets the default granted authority.
360      *
361      * @param value
362      *            the new default granted authority
363      */
364     public void setDefaultGrantedAuthority(final GrantedAuthority value) {
365         this.defaultGrantedAuthority = value;
366     }
367 }