View Javadoc
1   /**
2    * Waffle (https://github.com/dblock/waffle)
3    *
4    * Copyright (c) 2010 - 2015 Application Security, Inc.
5    *
6    * All rights reserved. This program and the accompanying materials
7    * are made available under the terms of the Eclipse Public License v1.0
8    * which accompanies this distribution, and is available at
9    * http://www.eclipse.org/legal/epl-v10.html
10   *
11   * Contributors:
12   *     Application Security, Inc.
13   */
14  package waffle.jaas;
15  
16  import java.io.IOException;
17  import java.security.Principal;
18  import java.util.ArrayList;
19  import java.util.LinkedHashSet;
20  import java.util.List;
21  import java.util.Locale;
22  import java.util.Map;
23  import java.util.Set;
24  import java.util.Map.Entry;
25  
26  import javax.security.auth.Subject;
27  import javax.security.auth.callback.Callback;
28  import javax.security.auth.callback.CallbackHandler;
29  import javax.security.auth.callback.NameCallback;
30  import javax.security.auth.callback.PasswordCallback;
31  import javax.security.auth.callback.UnsupportedCallbackException;
32  import javax.security.auth.login.LoginException;
33  import javax.security.auth.spi.LoginModule;
34  
35  import org.slf4j.Logger;
36  import org.slf4j.LoggerFactory;
37  
38  import waffle.windows.auth.IWindowsAccount;
39  import waffle.windows.auth.IWindowsAuthProvider;
40  import waffle.windows.auth.IWindowsIdentity;
41  import waffle.windows.auth.PrincipalFormat;
42  import waffle.windows.auth.impl.WindowsAuthProviderImpl;
43  
44  /**
45   * A Java Security login module for Windows authentication.
46   * 
47   * @author dblock[at]dblock[dot]org
48   * @see javax.security.auth.spi.LoginModule
49   */
50  public class WindowsLoginModule implements LoginModule {
51  
52      /** The Constant LOGGER. */
53      private static final Logger  LOGGER          = LoggerFactory.getLogger(WindowsLoginModule.class);
54  
55      /** The username. */
56      private String               username;
57      
58      /** The debug. */
59      private boolean              debug;
60      
61      /** The subject. */
62      private Subject              subject;
63      
64      /** The callback handler. */
65      private CallbackHandler      callbackHandler;
66      
67      /** The auth. */
68      private IWindowsAuthProvider auth            = new WindowsAuthProviderImpl();
69      
70      /** The principals. */
71      private Set<Principal>       principals;
72      
73      /** The principal format. */
74      private PrincipalFormat      principalFormat = PrincipalFormat.FQN;
75      
76      /** The role format. */
77      private PrincipalFormat      roleFormat      = PrincipalFormat.FQN;
78      
79      /** The allow guest login. */
80      private boolean              allowGuestLogin = true;
81  
82      /* (non-Javadoc)
83       * @see javax.security.auth.spi.LoginModule#initialize(javax.security.auth.Subject, javax.security.auth.callback.CallbackHandler, java.util.Map, java.util.Map)
84       */
85      @Override
86      public void initialize(final Subject initSubject, final CallbackHandler initCallbackHandler,
87              final Map<String, ?> initSharedState, final Map<String, ?> initOptions) {
88  
89          this.subject = initSubject;
90          this.callbackHandler = initCallbackHandler;
91  
92          for (final Entry<String, ?> option : initOptions.entrySet()) {
93              if (option.getKey().equalsIgnoreCase("debug")) {
94                  this.debug = Boolean.parseBoolean((String) option.getValue());
95              } else if (option.getKey().equalsIgnoreCase("principalFormat")) {
96                  this.principalFormat = PrincipalFormat
97                          .valueOf(((String) option.getValue()).toUpperCase(Locale.ENGLISH));
98              } else if (option.getKey().equalsIgnoreCase("roleFormat")) {
99                  this.roleFormat = PrincipalFormat.valueOf(((String) option.getValue()).toUpperCase(Locale.ENGLISH));
100             }
101         }
102     }
103 
104     /**
105      * Use Windows SSPI to authenticate a username with a password.
106      *
107      * @return true, if successful
108      * @throws LoginException
109      *             the login exception
110      */
111     @Override
112     public boolean login() throws LoginException {
113         if (this.callbackHandler == null) {
114             throw new LoginException("Missing callback to gather information from the user.");
115         }
116 
117         final NameCallback usernameCallback = new NameCallback("user name: ");
118         final PasswordCallback passwordCallback = new PasswordCallback("password: ", false);
119 
120         final Callback[] callbacks = new Callback[2];
121         callbacks[0] = usernameCallback;
122         callbacks[1] = passwordCallback;
123 
124         final String userName;
125         final String password;
126 
127         try {
128             this.callbackHandler.handle(callbacks);
129             userName = usernameCallback.getName();
130             password = passwordCallback.getPassword() == null ? "" : new String(passwordCallback.getPassword());
131             passwordCallback.clearPassword();
132         } catch (final IOException e) {
133             WindowsLoginModule.LOGGER.trace("{}", e);
134             throw new LoginException(e.toString());
135         } catch (final UnsupportedCallbackException e) {
136             WindowsLoginModule.LOGGER.trace("{}", e);
137             throw new LoginException(
138                     "Callback {} not available to gather authentication information from the user.".replace("{}", e
139                             .getCallback().getClass().getName()));
140         }
141 
142         IWindowsIdentity windowsIdentity;
143         try {
144             windowsIdentity = this.auth.logonUser(userName, password);
145         } catch (final Exception e) {
146             WindowsLoginModule.LOGGER.trace("{}", e);
147             throw new LoginException(e.getMessage());
148         }
149 
150         try {
151             // disable guest login
152             if (!this.allowGuestLogin && windowsIdentity.isGuest()) {
153                 WindowsLoginModule.LOGGER.debug("guest login disabled: {}", windowsIdentity.getFqn());
154                 throw new LoginException("Guest login disabled");
155             }
156 
157             this.principals = new LinkedHashSet<Principal>();
158             this.principals.addAll(WindowsLoginModule.getUserPrincipals(windowsIdentity, this.principalFormat));
159             if (this.roleFormat != PrincipalFormat.NONE) {
160                 for (final IWindowsAccount group : windowsIdentity.getGroups()) {
161                     this.principals.addAll(WindowsLoginModule.getRolePrincipals(group, this.roleFormat));
162                 }
163             }
164 
165             this.username = windowsIdentity.getFqn();
166             WindowsLoginModule.LOGGER.debug("successfully logged in {} ({})", this.username, windowsIdentity.getSidString());
167         } finally {
168             windowsIdentity.dispose();
169         }
170 
171         return true;
172     }
173 
174     /**
175      * Abort a login process.
176      *
177      * @return true, if successful
178      * @throws LoginException
179      *             the login exception
180      */
181     @Override
182     public boolean abort() throws LoginException {
183         return this.logout();
184     }
185 
186     /**
187      * Commit principals to the subject.
188      *
189      * @return true, if successful
190      * @throws LoginException
191      *             the login exception
192      */
193     @Override
194     public boolean commit() throws LoginException {
195         if (this.principals == null) {
196             return false;
197         }
198 
199         if (this.subject.isReadOnly()) {
200             throw new LoginException("Subject cannot be read-only.");
201         }
202 
203         final Set<Principal> principalsSet = this.subject.getPrincipals();
204         principalsSet.addAll(this.principals);
205 
206         WindowsLoginModule.LOGGER.debug("committing {} principals", Integer.valueOf(this.subject.getPrincipals().size()));
207         if (this.debug) {
208             for (final Principal principal : principalsSet) {
209                 WindowsLoginModule.LOGGER.debug(" principal: {}", principal.getName());
210             }
211         }
212 
213         return true;
214     }
215 
216     /**
217      * Logout a user.
218      *
219      * @return true, if successful
220      * @throws LoginException
221      *             the login exception
222      */
223     @Override
224     public boolean logout() throws LoginException {
225         if (this.subject.isReadOnly()) {
226             throw new LoginException("Subject cannot be read-only.");
227         }
228 
229         this.subject.getPrincipals().clear();
230 
231         if (this.username != null) {
232             WindowsLoginModule.LOGGER.debug("logging out {}", this.username);
233         }
234 
235         return true;
236     }
237 
238     /**
239      * True if Debug is enabled.
240      * 
241      * @return True or false.
242      */
243     public boolean isDebug() {
244         return this.debug;
245     }
246 
247     /**
248      * Windows auth provider.
249      * 
250      * @return IWindowsAuthProvider.
251      */
252     public IWindowsAuthProvider getAuth() {
253         return this.auth;
254     }
255 
256     /**
257      * Set Windows auth provider.
258      * 
259      * @param provider
260      *            Class implements IWindowsAuthProvider.
261      */
262     public void setAuth(final IWindowsAuthProvider provider) {
263         this.auth = provider;
264     }
265 
266     /**
267      * Returns a list of user principal objects.
268      * 
269      * @param windowsIdentity
270      *            Windows identity.
271      * @param principalFormat
272      *            Principal format.
273      * @return A list of user principal objects.
274      */
275     private static List<Principal> getUserPrincipals(final IWindowsIdentity windowsIdentity,
276             final PrincipalFormat principalFormat) {
277 
278         final List<Principal> principalsList = new ArrayList<Principal>();
279         switch (principalFormat) {
280             case FQN:
281                 principalsList.add(new UserPrincipal(windowsIdentity.getFqn()));
282                 break;
283             case SID:
284                 principalsList.add(new UserPrincipal(windowsIdentity.getSidString()));
285                 break;
286             case BOTH:
287                 principalsList.add(new UserPrincipal(windowsIdentity.getFqn()));
288                 principalsList.add(new UserPrincipal(windowsIdentity.getSidString()));
289                 break;
290             case NONE:
291                 break;
292             default:
293                 break;
294         }
295         return principalsList;
296     }
297 
298     /**
299      * Returns a list of role principal objects.
300      * 
301      * @param group
302      *            Windows group.
303      * @param principalFormat
304      *            Principal format.
305      * @return List of role principal objects.
306      */
307     private static List<Principal> getRolePrincipals(final IWindowsAccount group, final PrincipalFormat principalFormat) {
308 
309         final List<Principal> principalsList = new ArrayList<Principal>();
310         switch (principalFormat) {
311             case FQN:
312                 principalsList.add(new RolePrincipal(group.getFqn()));
313                 break;
314             case SID:
315                 principalsList.add(new RolePrincipal(group.getSidString()));
316                 break;
317             case BOTH:
318                 principalsList.add(new RolePrincipal(group.getFqn()));
319                 principalsList.add(new RolePrincipal(group.getSidString()));
320                 break;
321             case NONE:
322                 break;
323             default:
324                 break;
325         }
326         return principalsList;
327     }
328 
329     /**
330      * True if Guest login permitted.
331      * 
332      * @return True if Guest login permitted, false otherwise.
333      */
334     public boolean isAllowGuestLogin() {
335         return this.allowGuestLogin;
336     }
337 
338     /**
339      * Set whether Guest login is permitted. Default is true, if the Guest account is enabled, an invalid
340      * username/password results in a Guest login.
341      * 
342      * @param value
343      *            True or false.
344      */
345     public void setAllowGuestLogin(final boolean value) {
346         this.allowGuestLogin = value;
347     }
348 }