1 /*
2 * JBoss, the OpenSource WebOS
3 *
4 * Distributable under LGPL license.
5 * See terms of license at gnu.org.
6 */
7 package org.jboss.security.auth.spi;
8
9 import java.security.Principal;
10 import java.util.Map;
11
12 import javax.security.auth.Subject;
13 import javax.security.auth.callback.Callback;
14 import javax.security.auth.callback.CallbackHandler;
15 import javax.security.auth.callback.NameCallback;
16 import javax.security.auth.callback.PasswordCallback;
17 import javax.security.auth.callback.UnsupportedCallbackException;
18 import javax.security.auth.login.FailedLoginException;
19 import javax.security.auth.login.LoginException;
20
21 import org.jboss.security.SimplePrincipal;
22 import org.jboss.security.Util;
23 import org.jboss.security.auth.spi.AbstractServerLoginModule;
24
25
26 /** An abstract subclass of AbstractServerLoginModule that imposes
27 * an identity == String username, credentials == String password view on
28 * the login process.
29 * <p>
30 * Subclasses override the <code>getUsersPassword()</code>
31 * and <code>getRoleSets()</code> methods to return the expected password and roles
32 * for the user.
33 *
34 * @see #getUsername()
35 * @see #getUsersPassword()
36 * @see #getRoleSets()
37
38 @author Scott.Stark@jboss.org
39 @version $Revision: 1.11.4.3 $
40 */
41 public abstract class UsernamePasswordLoginModule extends AbstractServerLoginModule
42 {
43 /** The login identity */
44 private Principal identity;
45 /** The proof of login identity */
46 private char[] credential;
47 /** the principal to use when a null username and password are seen */
48 private Principal unauthenticatedIdentity;
49 /** the message digest algorithm used to hash passwords. If null then
50 plain passwords will be used. */
51 private String hashAlgorithm = null;
52 /** the name of the charset/encoding to use when converting the password
53 String to a byte array. Default is the platform's default encoding.
54 */
55 private String hashCharset = null;
56 /** the string encoding format to use. Defaults to base64. */
57 private String hashEncoding = null;
58 /** A flag indicating if the password comparison should ignore case */
59 private boolean ignorePasswordCase;
60
61 /** Override the superclass method to look for a unauthenticatedIdentity
62 property. This method first invokes the super version.
63 @param options,
64 @option unauthenticatedIdentity: the name of the principal to asssign
65 and authenticate when a null username and password are seen.
66 @option hashAlgorithm: the message digest algorithm used to hash passwords.
67 If null then plain passwords will be used.
68 @option hashCharset: the name of the charset/encoding to use when converting
69 the password String to a byte array. Default is the platform's default
70 encoding.
71 @option hashEncoding: the string encoding format to use. Defaults to base64.
72 @option ignorePasswordCase: A flag indicating if the password comparison
73 should ignore case
74 */
75 public void initialize(Subject subject, CallbackHandler callbackHandler,
76 Map sharedState, Map options)
77 {
78 super.initialize(subject, callbackHandler, sharedState, options);
79 // Check for unauthenticatedIdentity option.
80 String name = (String) options.get("unauthenticatedIdentity");
81 if( name != null )
82 {
83 unauthenticatedIdentity = new SimplePrincipal(name);
84 super.log.trace("Saw unauthenticatedIdentity="+name);
85 }
86
87 // Check to see if password hashing has been enabled.
88 // If an algorithm is set, check for a format and charset.
89 hashAlgorithm = (String) options.get("hashAlgorithm");
90 if( hashAlgorithm != null )
91 {
92 hashEncoding = (String) options.get("hashEncoding");
93 if( hashEncoding == null )
94 hashEncoding = Util.BASE64_ENCODING;
95 hashCharset = (String) options.get("hashCharset");
96 if( log.isTraceEnabled() )
97 {
98 log.trace("Passworg hashing activated: algorithm = " + hashAlgorithm +
99 ", encoding = " + hashEncoding+ (hashCharset == null ? "" : "charset = " + hashCharset));
100 }
101 }
102 String flag = (String) options.get("ignorePasswordCase");
103 ignorePasswordCase = Boolean.valueOf(flag).booleanValue();
104 }
105
106 /** Perform the authentication of the username and password.
107 */
108 public boolean login() throws LoginException
109 {
110 // See if shared credentials exist
111 if( super.login() == true )
112 {
113 // Setup our view of the user
114 Object username = sharedState.get("javax.security.auth.login.name");
115 if( username instanceof Principal )
116 identity = (Principal) username;
117 else
118 {
119 String name = username.toString();
120 identity = new SimplePrincipal(name);
121 }
122 Object password = sharedState.get("javax.security.auth.login.password");
123 if( password instanceof char[] )
124 credential = (char[]) password;
125 else if( password != null )
126 {
127 String tmp = password.toString();
128 credential = tmp.toCharArray();
129 }
130 return true;
131 }
132
133 super.loginOk = false;
134 String[] info = getUsernameAndPassword();
135 String username = info[0];
136 String password = info[1];
137 if( username == null && password == null )
138 {
139 identity = unauthenticatedIdentity;
140 super.log.trace("Authenticating as unauthenticatedIdentity="+identity);
141 }
142
143 if( identity == null )
144 {
145 identity = new SimplePrincipal(username);
146 // Hash the user entered password if password hashing is in use
147 if( hashAlgorithm != null )
148 password = createPasswordHash(username, password);
149 // Validate the password supplied by the subclass
150 String expectedPassword = getUsersPassword();
151 if( validatePassword(password, expectedPassword) == false )
152 {
153 super.log.debug("Bad password for username="+username);
154 throw new FailedLoginException("Password Incorrect/Password Required");
155 }
156 }
157
158 if( getUseFirstPass() == true )
159 { // Add the username and password to the shared state map
160 sharedState.put("javax.security.auth.login.name", username);
161 sharedState.put("javax.security.auth.login.password", credential);
162 }
163 super.loginOk = true;
164 super.log.trace("User '" + identity + "' authenticated, loginOk="+loginOk);
165 return true;
166 }
167
168 protected Principal getIdentity()
169 {
170 return identity;
171 }
172 protected Principal getUnauthenticatedIdentity()
173 {
174 return unauthenticatedIdentity;
175 }
176
177 protected Object getCredentials()
178 {
179 return credential;
180 }
181 protected String getUsername()
182 {
183 String username = null;
184 if( getIdentity() != null )
185 username = getIdentity().getName();
186 return username;
187 }
188
189 /** Called by login() to acquire the username and password strings for
190 authentication. This method does no validation of either.
191 @return String[], [0] = username, [1] = password
192 @exception LoginException thrown if CallbackHandler is not set or fails.
193 */
194 protected String[] getUsernameAndPassword() throws LoginException
195 {
196 String[] info = {null, null};
197 // prompt for a username and password
198 if( callbackHandler == null )
199 {
200 throw new LoginException("Error: no CallbackHandler available " +
201 "to collect authentication information");
202 }
203 NameCallback nc = new NameCallback("User name: ", "guest");
204 PasswordCallback pc = new PasswordCallback("Password: ", false);
205 Callback[] callbacks = {nc, pc};
206 String username = null;
207 String password = null;
208 try
209 {
210 callbackHandler.handle(callbacks);
211 username = nc.getName();
212 char[] tmpPassword = pc.getPassword();
213 if( tmpPassword != null )
214 {
215 credential = new char[tmpPassword.length];
216 System.arraycopy(tmpPassword, 0, credential, 0, tmpPassword.length);
217 pc.clearPassword();
218 password = new String(credential);
219 }
220 }
221 catch(java.io.IOException ioe)
222 {
223 throw new LoginException(ioe.toString());
224 }
225 catch(UnsupportedCallbackException uce)
226 {
227 throw new LoginException("CallbackHandler does not support: " + uce.getCallback());
228 }
229 info[0] = username;
230 info[1] = password;
231 return info;
232 }
233
234 /**
235 * If hashing is enabled, this method is called from <code>login()</code>
236 * prior to password validation.
237 * <p>
238 * Subclasses may override it to provide customized password hashing,
239 * for example by adding user-specific information or salting.
240 * <p>
241 * The default version calculates the hash based on the following options:
242 * <ul>
243 * <li><em>hashAlgorithm</em>: The digest algorithm to use.
244 * <li><em>hashEncoding</em>: The format used to store the hashes (base64 or hex)
245 * <li><em>hashCharset</em>: The encoding used to convert the password to bytes
246 * for hashing.
247 * </ul>
248 * It will return null if the hash fails for any reason, which will in turn
249 * cause <code>validatePassword()</code> to fail.
250 *
251 * @param username ignored in default version
252 * @param password the password string to be hashed
253 */
254 protected String createPasswordHash(String username, String password)
255 {
256 String passwordHash = Util.createPasswordHash(hashAlgorithm, hashEncoding,
257 hashCharset, username, password);
258 return passwordHash;
259 }
260
261 /** A hook that allows subclasses to change the validation of the input
262 password against the expected password. This version checks that
263 neither inputPassword or expectedPassword are null that that
264 inputPassword.equals(expectedPassword) is true;
265 @return true if the inputPassword is valid, false otherwise.
266 */
267 protected boolean validatePassword(String inputPassword, String expectedPassword)
268 {
269 if( inputPassword == null || expectedPassword == null )
270 return false;
271 boolean valid = false;
272 if( ignorePasswordCase == true )
273 valid = inputPassword.equalsIgnoreCase(expectedPassword);
274 else
275 valid = inputPassword.equals(expectedPassword);
276 return valid;
277 }
278
279 /** Get the expected password for the current username available via
280 the getUsername() method. This is called from within the login()
281 method after the CallbackHandler has returned the username and
282 candidate password.
283 @return the valid password String
284 */
285 abstract protected String getUsersPassword() throws LoginException;
286
287 }