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.apache;
25  
26  import java.io.IOException;
27  import java.security.Principal;
28  import java.util.Arrays;
29  import java.util.LinkedHashSet;
30  import java.util.Locale;
31  import java.util.Set;
32  
33  import javax.servlet.ServletException;
34  import javax.servlet.http.HttpServletResponse;
35  
36  import org.apache.catalina.LifecycleException;
37  import org.apache.catalina.authenticator.AuthenticatorBase;
38  import org.apache.catalina.connector.Request;
39  import org.apache.catalina.realm.GenericPrincipal;
40  import org.slf4j.Logger;
41  
42  import waffle.windows.auth.IWindowsAuthProvider;
43  import waffle.windows.auth.IWindowsIdentity;
44  import waffle.windows.auth.PrincipalFormat;
45  import waffle.windows.auth.impl.WindowsAuthProviderImpl;
46  
47  /**
48   * The Class WaffleAuthenticatorBase.
49   */
50  abstract class WaffleAuthenticatorBase extends AuthenticatorBase {
51  
52      /** The Constant SUPPORTED_PROTOCOLS. */
53      private static final Set<String> SUPPORTED_PROTOCOLS = new LinkedHashSet<>(Arrays.asList("Negotiate", "NTLM"));
54  
55      /** The info. */
56      protected String info;
57  
58      /** The log. */
59      protected Logger log;
60  
61      /** The principal format. */
62      protected PrincipalFormat principalFormat = PrincipalFormat.FQN;
63  
64      /** The role format. */
65      protected PrincipalFormat roleFormat = PrincipalFormat.FQN;
66  
67      /** The allow guest login. */
68      protected boolean allowGuestLogin = true;
69  
70      /** The protocols. */
71      protected Set<String> protocols = WaffleAuthenticatorBase.SUPPORTED_PROTOCOLS;
72  
73      /** The auth continueContextsTimeout configuration. */
74      protected int continueContextsTimeout = WindowsAuthProviderImpl.CONTINUE_CONTEXT_TIMEOUT;
75  
76      /** The auth. */
77      protected IWindowsAuthProvider auth;
78  
79      /**
80       * Gets the continue context time out configuration.
81       *
82       * @return the continue contexts timeout
83       */
84      public int getContinueContextsTimeout() {
85          return this.continueContextsTimeout;
86      }
87  
88      /**
89       * Sets the continue context time out configuration.
90       *
91       * @param continueContextsTimeout
92       *            the new continue contexts timeout
93       */
94      public void setContinueContextsTimeout(final int continueContextsTimeout) {
95          this.continueContextsTimeout = continueContextsTimeout;
96      }
97  
98      /**
99       * Windows authentication provider.
100      *
101      * @return IWindowsAuthProvider.
102      */
103     public IWindowsAuthProvider getAuth() {
104         return this.auth;
105     }
106 
107     /**
108      * Set Windows auth provider.
109      *
110      * @param provider
111      *            Class implements IWindowsAuthProvider.
112      */
113     public void setAuth(final IWindowsAuthProvider provider) {
114         this.auth = provider;
115     }
116 
117     /**
118      * Gets the info.
119      *
120      * @return the info
121      */
122     public String getInfo() {
123         return this.info;
124     }
125 
126     /**
127      * Set the principal format.
128      *
129      * @param format
130      *            Principal format.
131      */
132     public void setPrincipalFormat(final String format) {
133         this.principalFormat = PrincipalFormat.valueOf(format.toUpperCase(Locale.ENGLISH));
134         this.log.debug("principal format: {}", this.principalFormat);
135     }
136 
137     /**
138      * Principal format.
139      *
140      * @return Principal format.
141      */
142     public PrincipalFormat getPrincipalFormat() {
143         return this.principalFormat;
144     }
145 
146     /**
147      * Set the principal format.
148      *
149      * @param format
150      *            Role format.
151      */
152     public void setRoleFormat(final String format) {
153         this.roleFormat = PrincipalFormat.valueOf(format.toUpperCase(Locale.ENGLISH));
154         this.log.debug("role format: {}", this.roleFormat);
155     }
156 
157     /**
158      * Principal format.
159      *
160      * @return Role format.
161      */
162     public PrincipalFormat getRoleFormat() {
163         return this.roleFormat;
164     }
165 
166     /**
167      * True if Guest login permitted.
168      *
169      * @return True if Guest login permitted, false otherwise.
170      */
171     public boolean isAllowGuestLogin() {
172         return this.allowGuestLogin;
173     }
174 
175     /**
176      * Set whether Guest login is permitted. Default is true, if the Guest account is enabled, an invalid
177      * username/password results in a Guest login.
178      *
179      * @param value
180      *            True or false.
181      */
182     public void setAllowGuestLogin(final boolean value) {
183         this.allowGuestLogin = value;
184     }
185 
186     /**
187      * Set the authentication protocols. Default is "Negotiate, NTLM".
188      *
189      * @param value
190      *            Authentication protocols
191      */
192     public void setProtocols(final String value) {
193         this.protocols = new LinkedHashSet<>();
194         final String[] protocolNames = value.split(",", -1);
195         for (String protocolName : protocolNames) {
196             protocolName = protocolName.trim();
197             if (!protocolName.isEmpty()) {
198                 this.log.debug("init protocol: {}", protocolName);
199                 if (WaffleAuthenticatorBase.SUPPORTED_PROTOCOLS.contains(protocolName)) {
200                     this.protocols.add(protocolName);
201                 } else {
202                     this.log.error("unsupported protocol: {}", protocolName);
203                     throw new RuntimeException("Unsupported protocol: " + protocolName);
204                 }
205             }
206         }
207     }
208 
209     /**
210      * Send a 401 Unauthorized along with protocol authentication headers.
211      *
212      * @param response
213      *            HTTP Response
214      */
215     protected void sendUnauthorized(final HttpServletResponse response) {
216         try {
217             for (final String protocol : this.protocols) {
218                 response.addHeader("WWW-Authenticate", protocol);
219             }
220             response.setHeader("Connection", "close");
221             response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
222             response.flushBuffer();
223         } catch (final IOException e) {
224             throw new RuntimeException(e);
225         }
226     }
227 
228     /**
229      * Send an error code.
230      *
231      * @param response
232      *            HTTP Response
233      * @param code
234      *            Error Code
235      */
236     protected void sendError(final HttpServletResponse response, final int code) {
237         try {
238             response.sendError(code);
239         } catch (final IOException e) {
240             throw new RuntimeException(e);
241         }
242     }
243 
244     @Override
245     protected String getAuthMethod() {
246         return null;
247     }
248 
249     @Override
250     protected Principal doLogin(final Request request, final String username, final String password)
251             throws ServletException {
252         this.log.debug("logging in: {}", username);
253         IWindowsIdentity windowsIdentity;
254         try {
255             windowsIdentity = this.auth.logonUser(username, password);
256         } catch (final Exception e) {
257             this.log.error(e.getMessage());
258             this.log.trace("", e);
259             return super.doLogin(request, username, password);
260         }
261         // disable guest login
262         if (!this.allowGuestLogin && windowsIdentity.isGuest()) {
263             this.log.warn("guest login disabled: {}", windowsIdentity.getFqn());
264             return super.doLogin(request, username, password);
265         }
266         try {
267             this.log.debug("successfully logged in {} ({})", username, windowsIdentity.getSidString());
268             final GenericPrincipal genericPrincipal = this.createPrincipal(windowsIdentity);
269             if (this.log.isDebugEnabled()) {
270                 this.log.debug("roles: {}", String.join(", ", genericPrincipal.getRoles()));
271             }
272             return genericPrincipal;
273         } finally {
274             windowsIdentity.dispose();
275         }
276     }
277 
278     /**
279      * This method will create an instance of a IWindowsIdentity based GenericPrincipal. It is used for creating custom
280      * implementation within subclasses.
281      *
282      * @param windowsIdentity
283      *            the windows identity to initialize the principal
284      *
285      * @return the Generic Principal
286      */
287     protected GenericPrincipal createPrincipal(final IWindowsIdentity windowsIdentity) {
288         return new GenericWindowsPrincipal(windowsIdentity, this.principalFormat, this.roleFormat);
289     }
290 
291     /**
292      * Hook to the start and to set up the dependencies.
293      *
294      * @throws LifecycleException
295      *             the lifecycle exception
296      */
297     @Override
298     public synchronized void startInternal() throws LifecycleException {
299         this.log.debug("Creating a windows authentication provider with continueContextsTimeout property set to: {}",
300                 this.continueContextsTimeout);
301         this.auth = new WindowsAuthProviderImpl(this.continueContextsTimeout);
302         super.startInternal();
303     }
304 
305 }