1 /*
2 * JBoss, Home of Professional Open Source
3 * Copyright 2005, JBoss Inc., and individual contributors as indicated
4 * by the @authors tag. See the copyright.txt in the distribution for a
5 * full listing of individual contributors.
6 *
7 * This is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU Lesser General Public License as
9 * published by the Free Software Foundation; either version 2.1 of
10 * the License, or (at your option) any later version.
11 *
12 * This software is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this software; if not, write to the Free
19 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
20 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
21 */
22 package org.jboss.security.auth.spi;
23
24
25 import java.lang.reflect.Constructor;
26 import java.security.Principal;
27 import java.security.acl.Group;
28 import java.util.Enumeration;
29 import java.util.Iterator;
30 import java.util.Map;
31 import java.util.Set;
32
33 import javax.security.auth.Subject;
34 import javax.security.auth.callback.CallbackHandler;
35 import javax.security.auth.login.LoginException;
36 import javax.security.auth.spi.LoginModule;
37
38 import org.jboss.logging.Logger;
39 import org.jboss.security.NestableGroup;
40 import org.jboss.security.SecurityConstants;
41 import org.jboss.security.SimpleGroup;
42 import org.jboss.security.SimplePrincipal;
43
44 /**
45 * This class implements the common functionality required for a JAAS
46 * server side LoginModule and implements the JBossSX standard Subject usage
47 * pattern of storing identities and roles. Subclass this module to create your
48 * own custom LoginModule and override the login(), getRoleSets() and getIdentity()
49 * methods.
50 * <p>
51 * You may also wish to override
52 * <pre>
53 * public void initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState, Map options)
54 * </pre>
55 * In which case the first line of your initialize() method should be:
56 * <pre>
57 * super.initialize(subject, callbackHandler, sharedState, options);
58 * </pre>
59 * <p>
60 * You may also wish to override
61 * <pre>
62 * public boolean login() throws LoginException
63 * </pre>
64 * In which case the last line of your login() method should be
65 * <pre>
66 * return super.login();
67 * </pre>
68 *
69 *@author <a href="edward.kenworthy@crispgroup.co.uk">Edward Kenworthy</a>, 12th Dec 2000
70 *@author Scott.Stark@jboss.org
71 *@version $Revision: 86122 $
72 */
73 public abstract class AbstractServerLoginModule implements LoginModule
74 {
75 protected Subject subject;
76 protected CallbackHandler callbackHandler;
77 @SuppressWarnings("unchecked")
78 protected Map sharedState;
79 @SuppressWarnings("unchecked")
80 protected Map options;
81 protected Logger log;
82 /** Flag indicating if the shared credential should be used */
83 protected boolean useFirstPass;
84 /** Flag indicating if the login phase succeeded. Subclasses that override
85 the login method must set this to true on successful completion of login
86 */
87 protected boolean loginOk;
88 /** An optional custom Principal class implementation */
89 protected String principalClassName;
90 /** the principal to use when a null username and password are seen */
91 protected Principal unauthenticatedIdentity;
92
93 //--- Begin LoginModule interface methods
94 /** Initialize the login module. This stores the subject, callbackHandler
95 * and sharedState and options for the login session. Subclasses should override
96 * if they need to process their own options. A call to super.initialize(...)
97 * must be made in the case of an override.
98 * <p>
99 * @option password-stacking: If this is set to "useFirstPass", the login
100 * identity will be taken from the <code>javax.security.auth.login.name</code>
101 * value of the sharedState map, and the proof of identity from the
102 * <code>javax.security.auth.login.password</code> value of the sharedState
103 * map.
104 * @option principalClass: A Principal implementation that support a ctor
105 * taking a String argument for the princpal name.
106 * @option unauthenticatedIdentity: the name of the principal to asssign
107 * and authenticate when a null username and password are seen.
108 *
109 * @param subject the Subject to update after a successful login.
110 * @param callbackHandler the CallbackHandler that will be used to obtain the
111 * the user identity and credentials.
112 * @param sharedState a Map shared between all configured login module instances
113 * @param options the parameters passed to the login module.
114 */
115 public void initialize(Subject subject, CallbackHandler callbackHandler,
116 Map<String,?> sharedState, Map<String,?> options)
117 {
118 this.subject = subject;
119 this.callbackHandler = callbackHandler;
120 this.sharedState = sharedState;
121 this.options = options;
122 log = Logger.getLogger(getClass());
123 log.trace("initialize");
124
125 //log securityDomain, if set.
126 log.trace("Security domain: " +
127 (String)options.get(SecurityConstants.SECURITY_DOMAIN_OPTION));
128
129 /* Check for password sharing options. Any non-null value for
130 password_stacking sets useFirstPass as this module has no way to
131 validate any shared password.
132 */
133 String passwordStacking = (String) options.get("password-stacking");
134 if( passwordStacking != null && passwordStacking.equalsIgnoreCase("useFirstPass") )
135 useFirstPass = true;
136
137 // Check for a custom Principal implementation
138 principalClassName = (String) options.get("principalClass");
139
140 // Check for unauthenticatedIdentity option.
141 String name = (String) options.get("unauthenticatedIdentity");
142 if( name != null )
143 {
144 try
145 {
146 unauthenticatedIdentity = createIdentity(name);
147 log.trace("Saw unauthenticatedIdentity="+name);
148 }
149 catch(Exception e)
150 {
151 log.warn("Failed to create custom unauthenticatedIdentity", e);
152 }
153 }
154 }
155
156 /** Looks for javax.security.auth.login.name and javax.security.auth.login.password
157 values in the sharedState map if the useFirstPass option was true and returns
158 true if they exist. If they do not or are null this method returns false.
159
160 Note that subclasses that override the login method must set the loginOk
161 ivar to true if the login succeeds in order for the commit phase to
162 populate the Subject. This implementation sets loginOk to true if the
163 login() method returns true, otherwise, it sets loginOk to false.
164 */
165 public boolean login() throws LoginException
166 {
167 log.trace("login");
168 loginOk = false;
169 // If useFirstPass is true, look for the shared password
170 if( useFirstPass == true )
171 {
172 try
173 {
174 Object identity = sharedState.get("javax.security.auth.login.name");
175 Object credential = sharedState.get("javax.security.auth.login.password");
176 if( identity != null && credential != null )
177 {
178 loginOk = true;
179 return true;
180 }
181 // Else, fall through and perform the login
182 }
183 catch(Exception e)
184 { // Dump the exception and continue
185 log.error("login failed", e);
186 }
187 }
188 return false;
189 }
190
191 /** Method to commit the authentication process (phase 2). If the login
192 method completed successfully as indicated by loginOk == true, this
193 method adds the getIdentity() value to the subject getPrincipals() Set.
194 It also adds the members of each Group returned by getRoleSets()
195 to the subject getPrincipals() Set.
196
197 @see javax.security.auth.Subject;
198 @see java.security.acl.Group;
199 @return true always.
200 */
201 public boolean commit() throws LoginException
202 {
203 log.trace("commit, loginOk="+loginOk);
204 if( loginOk == false )
205 return false;
206
207 Set<Principal> principals = subject.getPrincipals();
208 Principal identity = getIdentity();
209 principals.add(identity);
210 Group[] roleSets = getRoleSets();
211 for(int g = 0; g < roleSets.length; g ++)
212 {
213 Group group = roleSets[g];
214 String name = group.getName();
215 Group subjectGroup = createGroup(name, principals);
216 if( subjectGroup instanceof NestableGroup )
217 {
218 /* A NestableGroup only allows Groups to be added to it so we
219 need to add a SimpleGroup to subjectRoles to contain the roles
220 */
221 SimpleGroup tmp = new SimpleGroup("Roles");
222 subjectGroup.addMember(tmp);
223 subjectGroup = tmp;
224 }
225 // Copy the group members to the Subject group
226 Enumeration<? extends Principal> members = group.members();
227 while( members.hasMoreElements() )
228 {
229 Principal role = (Principal) members.nextElement();
230 subjectGroup.addMember(role);
231 }
232 }
233 return true;
234 }
235
236 /** Method to abort the authentication process (phase 2).
237 @return true alaways
238 */
239 public boolean abort() throws LoginException
240 {
241 log.trace("abort");
242 return true;
243 }
244
245 /** Remove the user identity and roles added to the Subject during commit.
246 @return true always.
247 */
248 public boolean logout() throws LoginException
249 {
250 log.trace("logout");
251 // Remove the user identity
252 Principal identity = getIdentity();
253 Set<Principal> principals = subject.getPrincipals();
254 principals.remove(identity);
255 // Remove any added Groups...
256 return true;
257 }
258 //--- End LoginModule interface methods
259
260 // --- Protected methods
261
262 /** Overriden by subclasses to return the Principal that corresponds to
263 the user primary identity.
264 */
265 abstract protected Principal getIdentity();
266 /** Overriden by subclasses to return the Groups that correspond to the
267 to the role sets assigned to the user. Subclasses should create at
268 least a Group named "Roles" that contains the roles assigned to the user.
269 A second common group is "CallerPrincipal" that provides the application
270 identity of the user rather than the security domain identity.
271 @return Group[] containing the sets of roles
272 */
273 abstract protected Group[] getRoleSets() throws LoginException;
274
275 protected boolean getUseFirstPass()
276 {
277 return useFirstPass;
278 }
279 protected Principal getUnauthenticatedIdentity()
280 {
281 return unauthenticatedIdentity;
282 }
283
284 /** Find or create a Group with the given name. Subclasses should use this
285 method to locate the 'Roles' group or create additional types of groups.
286 @return A named Group from the principals set.
287 */
288 protected Group createGroup(String name, Set<Principal> principals)
289 {
290 Group roles = null;
291 Iterator<Principal> iter = principals.iterator();
292 while( iter.hasNext() )
293 {
294 Object next = iter.next();
295 if( (next instanceof Group) == false )
296 continue;
297 Group grp = (Group) next;
298 if( grp.getName().equals(name) )
299 {
300 roles = grp;
301 break;
302 }
303 }
304 // If we did not find a group create one
305 if( roles == null )
306 {
307 roles = new SimpleGroup(name);
308 principals.add(roles);
309 }
310 return roles;
311 }
312
313 /** Utility method to create a Principal for the given username. This
314 * creates an instance of the principalClassName type if this option was
315 * specified using the class constructor matching: ctor(String). If
316 * principalClassName was not specified, a SimplePrincipal is created.
317 *
318 * @param username the name of the principal
319 * @return the principal instance
320 * @throws java.lang.Exception thrown if the custom principal type cannot be created.
321 */
322 @SuppressWarnings("unchecked")
323 protected Principal createIdentity(String username)
324 throws Exception
325 {
326 Principal p = null;
327 if( principalClassName == null )
328 {
329 p = new SimplePrincipal(username);
330 }
331 else
332 {
333 ClassLoader loader = SecurityActions.getContextClassLoader();
334 Class clazz = loader.loadClass(principalClassName);
335 Class[] ctorSig = {String.class};
336 Constructor ctor = clazz.getConstructor(ctorSig);
337 Object[] ctorArgs = {username};
338 p = (Principal) ctor.newInstance(ctorArgs);
339 }
340 return p;
341 }
342 }