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
10 import java.security.Principal;
11 import java.security.acl.Group;
12 import java.util.Enumeration;
13 import java.util.Iterator;
14 import java.util.Map;
15 import java.util.Set;
16
17 import javax.security.auth.Subject;
18 import javax.security.auth.callback.CallbackHandler;
19 import javax.security.auth.login.LoginException;
20 import javax.security.auth.spi.LoginModule;
21
22 import org.jboss.logging.Logger;
23 import org.jboss.security.NestableGroup;
24 import org.jboss.security.SimpleGroup;
25
26 /**
27 * This class implements the common functionality required for a JAAS
28 * server side LoginModule and implements the JBossSX standard Subject usage
29 * pattern of storing identities and roles. Subclass this module to create your
30 * own custom LoginModule and override the login(), getRoleSets() and getIdentity()
31 * methods.
32 * <p>
33 * You may also wish to override
34 * <pre>
35 * public void initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState, Map options)
36 * </pre>
37 * In which case the first line of your initialize() method should be:
38 * <pre>
39 * super.initialize(subject, callbackHandler, sharedState, options);
40 * </pre>
41 * <p>
42 * You may also wish to override
43 * <pre>
44 * public boolean login() throws LoginException
45 * </pre>
46 * In which case the last line of your login() method should be
47 * <pre>
48 * return super.login();
49 * </pre>
50 *
51 *@author <a href="edward.kenworthy@crispgroup.co.uk">Edward Kenworthy</a>, 12th Dec 2000
52 *@author Scott.Stark@jboss.org
53 *@version $Revision: 1.6.4.3 $
54 */
55 public abstract class AbstractServerLoginModule implements LoginModule
56 {
57 protected Subject subject;
58 protected CallbackHandler callbackHandler;
59 protected Map sharedState;
60 protected Map options;
61 protected Logger log;
62 /** Flag indicating if the shared credential should be used */
63 protected boolean useFirstPass;
64 /** Flag indicating if the login phase succeeded. Subclasses that override
65 the login method must set this to true on successful completion of login
66 */
67 protected boolean loginOk;
68
69 //--- Begin LoginModule interface methods
70 /**
71 * Initialize the login module. This stores the subject, callbackHandler
72 * and sharedState and options for the login session. Subclasses should override
73 * if they need to process their own options. A call to super.initialize(...)
74 * must be made in the case of an override.
75 * <p>
76 * The options are checked for the <em>password-stacking</em> parameter.
77 * If this is set to "useFirstPass", the login identity will be taken from the
78 * <code>javax.security.auth.login.name</code> value of the sharedState map,
79 * and the proof of identity from the
80 * <code>javax.security.auth.login.password</code> value of the sharedState map.
81 *
82 * @param subject the Subject to update after a successful login.
83 * @param callbackHandler the CallbackHandler that will be used to obtain the
84 * the user identity and credentials.
85 * @param sharedState a Map shared between all configured login module instances
86 * @param options the parameters passed to the login module.
87 */
88 public void initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState, Map options)
89 {
90 this.subject = subject;
91 this.callbackHandler = callbackHandler;
92 this.sharedState = sharedState;
93 this.options = options;
94 log = Logger.getLogger(getClass());
95 log.trace("initialize");
96 /* Check for password sharing options. Any non-null value for
97 password_stacking sets useFirstPass as this module has no way to
98 validate any shared password.
99 */
100 String passwordStacking = (String) options.get("password-stacking");
101 if( passwordStacking != null && passwordStacking.equalsIgnoreCase("useFirstPass") )
102 useFirstPass = true;
103 }
104
105 /** Looks for javax.security.auth.login.name and javax.security.auth.login.password
106 values in the sharedState map if the useFirstPass option was true and returns
107 true if they exist. If they do not or are null this method returns false.
108
109 Note that subclasses that override the login method must set the loginOk
110 ivar to true if the login succeeds in order for the commit phase to
111 populate the Subject. This implementation sets loginOk to true if the
112 login() method returns true, otherwise, it sets loginOk to false.
113 */
114 public boolean login() throws LoginException
115 {
116 log.trace("login");
117 loginOk = false;
118 // If useFirstPass is true, look for the shared password
119 if( useFirstPass == true )
120 {
121 try
122 {
123 Object identity = sharedState.get("javax.security.auth.login.name");
124 Object credential = sharedState.get("javax.security.auth.login.password");
125 if( identity != null && credential != null )
126 {
127 loginOk = true;
128 return true;
129 }
130 // Else, fall through and perform the login
131 }
132 catch(Exception e)
133 { // Dump the exception and continue
134 log.error("login failed", e);
135 }
136 }
137 return false;
138 }
139
140 /** Method to commit the authentication process (phase 2). If the login
141 method completed successfully as indicated by loginOk == true, this
142 method adds the getIdentity() value to the subject getPrincipals() Set.
143 It also adds the members of each Group returned by getRoleSets()
144 to the subject getPrincipals() Set.
145
146 @see javax.security.auth.Subject;
147 @see java.security.acl.Group;
148 @return true always.
149 */
150 public boolean commit() throws LoginException
151 {
152 log.trace("commit, loginOk="+loginOk);
153 if( loginOk == false )
154 return false;
155
156 Set principals = subject.getPrincipals();
157 Principal identity = getIdentity();
158 principals.add(identity);
159 Group[] roleSets = getRoleSets();
160 for(int g = 0; g < roleSets.length; g ++)
161 {
162 Group group = roleSets[g];
163 String name = group.getName();
164 Group subjectGroup = createGroup(name, principals);
165 if( subjectGroup instanceof NestableGroup )
166 {
167 /* A NestableGroup only allows Groups to be added to it so we
168 need to add a SimpleGroup to subjectRoles to contain the roles
169 */
170 SimpleGroup tmp = new SimpleGroup("Roles");
171 subjectGroup.addMember(tmp);
172 subjectGroup = tmp;
173 }
174 // Copy the group members to the Subject group
175 Enumeration members = group.members();
176 while( members.hasMoreElements() )
177 {
178 Principal role = (Principal) members.nextElement();
179 subjectGroup.addMember(role);
180 }
181 }
182 return true;
183 }
184
185 /** Method to abort the authentication process (phase 2).
186 @return true alaways
187 */
188 public boolean abort() throws LoginException
189 {
190 log.trace("abort");
191 return true;
192 }
193
194 /** Remove the user identity and roles added to the Subject during commit.
195 @return true always.
196 */
197 public boolean logout() throws LoginException
198 {
199 log.trace("logout");
200 // Remove the user identity
201 Principal identity = getIdentity();
202 Set principals = subject.getPrincipals();
203 principals.remove(identity);
204 // Remove any added Groups...
205 return true;
206 }
207 //--- End LoginModule interface methods
208
209 // --- Protected methods
210
211 /** Overriden by subclasses to return the Principal that corresponds to
212 the user primary identity.
213 */
214 abstract protected Principal getIdentity();
215 /** Overriden by subclasses to return the Groups that correspond to the
216 to the role sets assigned to the user. Subclasses should create at
217 least a Group named "Roles" that contains the roles assigned to the user.
218 A second common group is "CallerPrincipal" that provides the application
219 identity of the user rather than the security domain identity.
220 @return Group[] containing the sets of roles
221 */
222 abstract protected Group[] getRoleSets() throws LoginException;
223
224 protected boolean getUseFirstPass()
225 {
226 return useFirstPass;
227 }
228
229 /** Find or create a Group with the given name. Subclasses should use this
230 method to locate the 'Roles' group or create additional types of groups.
231 @return A named Group from the principals set.
232 */
233 protected Group createGroup(String name, Set principals)
234 {
235 Group roles = null;
236 Iterator iter = principals.iterator();
237 while( iter.hasNext() )
238 {
239 Object next = iter.next();
240 if( (next instanceof Group) == false )
241 continue;
242 Group grp = (Group) next;
243 if( grp.getName().equals(name) )
244 {
245 roles = grp;
246 break;
247 }
248 }
249 // If we did not find a group create one
250 if( roles == null )
251 {
252 roles = new NestableGroup(name);
253 principals.add(roles);
254 }
255 return roles;
256 }
257 }