1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.seasar.tuigwaa.security.auth;
17
18 import java.io.IOException;
19 import java.util.ArrayList;
20 import java.util.Hashtable;
21 import java.util.Iterator;
22 import java.util.List;
23 import java.util.Map;
24 import java.util.MissingResourceException;
25 import java.util.ResourceBundle;
26 import java.util.regex.Matcher;
27 import java.util.regex.Pattern;
28
29 import javax.naming.AuthenticationException;
30 import javax.naming.Context;
31 import javax.naming.InitialContext;
32 import javax.naming.NamingEnumeration;
33 import javax.naming.NamingException;
34 import javax.naming.directory.Attribute;
35 import javax.naming.directory.Attributes;
36 import javax.naming.directory.BasicAttributes;
37 import javax.naming.directory.DirContext;
38 import javax.naming.directory.InitialDirContext;
39 import javax.naming.directory.SearchResult;
40 import javax.security.auth.Subject;
41 import javax.security.auth.callback.Callback;
42 import javax.security.auth.callback.CallbackHandler;
43 import javax.security.auth.callback.NameCallback;
44 import javax.security.auth.callback.PasswordCallback;
45 import javax.security.auth.callback.UnsupportedCallbackException;
46 import javax.security.auth.login.FailedLoginException;
47 import javax.security.auth.login.LoginException;
48 import javax.security.auth.spi.LoginModule;
49
50 import org.seasar.tuigwaa.security.DirectoryUtils;
51
52
53 /***
54 * @author someda
55 */
56 public class TgwLoginModule implements LoginModule {
57
58 private Subject subject;
59 private CallbackHandler callbackHandler;
60 private Map sharedState;
61
62 private Hashtable environment = new Hashtable();
63
64 private static final String SYSTEM_PROPERTY_PATH = "tuigwaa";
65 private static ResourceBundle rb;
66 static{
67 try{
68 rb = ResourceBundle.getBundle(SYSTEM_PROPERTY_PATH);
69 }catch(MissingResourceException mre){
70
71 mre.printStackTrace();
72 }
73 }
74
75 private String username;
76 private char[] password;
77
78
79 private boolean tryFirstPass;
80 private static final String TRY_FIRST_PASS = "try_first_pass";
81
82 private boolean useFirstPass;
83 private static final String USE_FIRST_PASS = "use_first_pass";
84
85
86 private String providerUrl = "ldap://localhost:10389/dc=seasar,dc=org";
87 private static final String PROVIDER_URL = "ldap.provider.url";
88
89 private String securityAuthentication = "simple";
90 private static final String SECURITY_AUTHENTICATION = "ldap.security.authentication";
91
92 private String securityPrincipal = "uid=admin,ou=system";
93 private static final String SECURITY_PRINCIPAL = "ldap.security.principal";
94
95 private String securityCredential = "tuigwaa";
96 private static final String SECURITY_CREDENTIAL = "ldap.security.credentials";
97
98
99 private String searchBase = "dc=seasar,dc=org";
100 private static final String SEARCH_BASE = "ldap.search.base";
101
102 private String userSearchBase = "ou=users";
103 private static final String USER_SEARCH_BASE = "ldap.user.search.base";
104
105 private String userSearchPrefix = "uid";
106 private static final String USER_SEARCH_PREFIX = "ldap.user.search.prefix";
107
108 private String userRoleAttribute = "memberOf";
109 private static final String USER_ROLE_ATTRIBUTE = "ldap.user.role.attribute";
110
111
112
113
114 private String roleSearchBase = "ou=roles";
115 private static final String ROLE_SEARCH_BASE = "ldap.role.search.base";
116
117 private String roleSearchPrefix = "cn";
118 private static final String ROLE_SEARCH_PREFIX = "ldap.role.search.prefix";
119
120 private String roleUserAttribute = "member";
121 private static final String ROLE_MEMBER_ATTRIBUTE = "ldap.role.user.attribute";
122
123 private boolean roleUserPrefixOnly = false;
124 private static final String ROLE_USER_PREFIXONLY = "ldap.role.user.prefixonly";
125
126
127 private TgwUser userPrincipal;
128 private List roleList = new ArrayList();
129
130
131 private boolean authenticated = false;
132 private boolean committed = false;
133
134
135 private static final String SHARED_NAME = "javax.security.auth.login.name";
136 private static final String SHARED_PWD = "javax.security.auth.login.password";
137
138 public TgwLoginModule(){
139 }
140
141
142 public boolean abort() throws LoginException {
143
144 if (!authenticated) {
145 return false;
146 } else {
147 if (!committed ){
148 cleanup(true);
149 userPrincipal = null;
150 roleList = new ArrayList();
151 }else{
152 logout();
153 }
154 }
155 return true;
156 }
157
158 public boolean commit() throws LoginException {
159
160 if(authenticated){
161 subject.getPrincipals().add(userPrincipal);
162 for(Iterator i = roleList.iterator();i.hasNext();){
163 TgwRole role = (TgwRole) i.next();
164 subject.getPrincipals().add(role);
165 }
166 }else{
167 return false;
168 }
169 committed = true;
170 return true;
171 }
172
173 public boolean login() throws LoginException {
174
175
176
177 if(useFirstPass){
178 setUsernameAndPassword(true);
179
180 try{
181 authenticate();
182 return true;
183 }catch(LoginException le){
184 cleanup(false);
185 throw le;
186 }
187
188 }else if(tryFirstPass){
189 setUsernameAndPassword(true);
190 try{
191 authenticate();
192 return true;
193 }catch(LoginException le){
194 cleanup(false);
195
196 }
197 }
198
199 setUsernameAndPassword(false);
200
201 try{
202 authenticate();
203 return true;
204 }catch(LoginException le){
205 cleanup(false);
206 throw le;
207 }
208 }
209
210 public boolean logout() throws LoginException {
211
212 subject.getPrincipals().remove(userPrincipal);
213 for(Iterator i=roleList.iterator();i.hasNext();){
214 subject.getPrincipals().remove(i.next());
215 }
216
217 cleanup(true);
218 userPrincipal = null;
219 roleList = new ArrayList();
220
221 return true;
222 }
223
224 public void initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState, Map options) {
225
226
227 this.subject = subject;
228 this.callbackHandler = callbackHandler;
229 this.sharedState = sharedState;
230
231
232 tryFirstPass = "true".equalsIgnoreCase((String)options.get(TRY_FIRST_PASS));
233 useFirstPass = "true".equalsIgnoreCase((String)options.get(USE_FIRST_PASS));
234
235
236 if(rb != null){
237 providerUrl = rb.getString(PROVIDER_URL);
238 securityAuthentication = rb.getString(SECURITY_AUTHENTICATION);
239 securityPrincipal = rb.getString(SECURITY_PRINCIPAL);
240 securityCredential = rb.getString(SECURITY_CREDENTIAL);
241
242
243 searchBase = rb.getString(SEARCH_BASE);
244 userSearchBase = rb.getString(USER_SEARCH_BASE);
245 userSearchPrefix = rb.getString(USER_SEARCH_PREFIX);
246 userRoleAttribute = rb.getString(USER_ROLE_ATTRIBUTE);
247
248 roleSearchBase = rb.getString(ROLE_SEARCH_BASE);
249 roleSearchPrefix = rb.getString(ROLE_SEARCH_PREFIX);
250 roleUserAttribute = rb.getString(ROLE_MEMBER_ATTRIBUTE);
251 String prefixOnly = rb.getString(ROLE_USER_PREFIXONLY);
252 if(prefixOnly != null && !"".equals(prefixOnly)){
253 roleUserPrefixOnly = Boolean.valueOf(prefixOnly).booleanValue();
254 }
255 }
256
257
258 environment.put(Context.INITIAL_CONTEXT_FACTORY,com.sun.jndi.ldap.LdapCtxFactory.class.getName());
259 environment.put(Context.PROVIDER_URL,providerUrl);
260 environment.put(Context.SECURITY_AUTHENTICATION,securityAuthentication);
261 environment.put(Context.SECURITY_PRINCIPAL,securityPrincipal);
262 environment.put(Context.SECURITY_CREDENTIALS,securityCredential);
263 }
264
265
266
267 private void setUsernameAndPassword(boolean shared) throws LoginException{
268
269 if(shared){
270 username = (String) sharedState.get(SHARED_NAME);
271 password = (char[]) sharedState.get(SHARED_PWD);
272 return;
273 }
274
275 if(callbackHandler == null)
276 throw new LoginException("Callback handler needed.");
277
278 Callback[] callbacks = new Callback[2];
279 callbacks[0] = new NameCallback("ldap username: ");
280 callbacks[1] = new PasswordCallback("ldap password: ",false);
281
282 try{
283 callbackHandler.handle(callbacks);
284 username = ((NameCallback) callbacks[0]).getName();
285 password = ((PasswordCallback) callbacks[1]).getPassword();
286
287 }catch(IOException ioe){
288 throw new LoginException("login failed cased by " + ioe.getLocalizedMessage());
289 }catch(UnsupportedCallbackException uce){
290 throw new LoginException("login failed caused by " + uce.getLocalizedMessage());
291 }
292 }
293
294 private void authenticate() throws LoginException{
295
296 DirContext ctx = null;
297 try{
298 if(bindContext()){
299 userPrincipal = new TgwUser(username);
300
301
302 if(!"".equals(userRoleAttribute)){
303 addUserRoleAttribute();
304 }
305
306
307 addRoleUserAttribute();
308
309 if(roleList != null){
310 List tmpList = new ArrayList();
311 for(Iterator i=roleList.iterator();i.hasNext();){
312 TgwRole role = (TgwRole) i.next();
313 tmpList.add(role.getName());
314 }
315 String[] roles = (String[])tmpList.toArray(new String[tmpList.size()]);
316 userPrincipal.setRoles(roles);
317 }
318
319 if(!sharedState.containsKey(SHARED_NAME) && !sharedState.containsKey(SHARED_PWD)){
320 sharedState.put(SHARED_NAME,username);
321 sharedState.put(SHARED_PWD,password);
322 }
323 authenticated = true;
324
325 }else{
326 throw new FailedLoginException("bind failed");
327 }
328 return;
329 }catch(NamingException ne){
330 throw new LoginException("login failed caused by " + ne.getLocalizedMessage());
331 }finally{
332 DirectoryUtils.closeQuietly(ctx);
333 }
334 }
335
336 private Hashtable getCurrentBindEnvironment(){
337
338 Hashtable env = new Hashtable();
339 String bindUserDN = DirectoryUtils.getDN(username,userSearchPrefix,userSearchBase,searchBase,true);
340 env.put(Context.INITIAL_CONTEXT_FACTORY,com.sun.jndi.ldap.LdapCtxFactory.class.getName());
341 env.put(Context.PROVIDER_URL,providerUrl);
342 env.put(Context.SECURITY_AUTHENTICATION,securityAuthentication);
343 env.put(Context.SECURITY_PRINCIPAL,bindUserDN);
344 env.put(Context.SECURITY_CREDENTIALS,new String(password));
345
346 return env;
347 }
348
349 private boolean bindContext() throws NamingException{
350
351 if(username == null || "".equals(username)){
352 return false;
353 }
354
355 Hashtable bindenv = getCurrentBindEnvironment();
356 try{
357 new InitialContext(bindenv);
358 return true;
359 }catch(AuthenticationException ae){
360 return false;
361 }
362 }
363
364 private void addUserRoleAttribute(){
365
366 DirContext ctx = null;
367 Hashtable bindenv = getCurrentBindEnvironment();
368
369 try{
370 ctx = new InitialDirContext(bindenv);
371
372 Attributes attrs = ctx.getAttributes(DirectoryUtils.getDN(username,userSearchPrefix,userSearchBase,searchBase,false));
373 Attribute attr = attrs.get(userRoleAttribute);
374 if(attr != null){
375 NamingEnumeration roles = attr.getAll();
376 while(roles.hasMore()){
377 String rolename = (String)roles.next();
378 Pattern p = Pattern.compile("^" + roleSearchPrefix + "=([0-9a-zA-Z]*)[,0-9a-zA-Z=]?");
379 Matcher m = p.matcher(rolename);
380 if(m.find()){
381 rolename = m.group(1);
382 }
383 TgwRole role = new TgwRole(rolename);
384 roleList.add(role);
385 }
386 }
387 }catch(NamingException ne){
388 ne.printStackTrace();
389 }finally{
390 DirectoryUtils.closeQuietly(ctx);
391 }
392 }
393
394 private void addRoleUserAttribute(){
395
396 DirContext ctx = null;
397 Hashtable bindenv = getCurrentBindEnvironment();
398
399 try{
400 ctx = new InitialDirContext(bindenv);
401
402 String userDn = DirectoryUtils.getDN(username,userSearchPrefix,userSearchBase,searchBase,true);
403
404 Attributes member = null;
405 if(roleUserPrefixOnly){
406 member = new BasicAttributes(roleUserAttribute,username);
407 }else{
408 member = new BasicAttributes(roleUserAttribute,userDn);
409 }
410
411 String roleSuffix = DirectoryUtils.buildSuffix(roleSearchBase,searchBase,false,false);
412 NamingEnumeration nenum = ctx.search(roleSuffix,member);
413
414 while(nenum.hasMore()){
415 SearchResult result = (SearchResult) nenum.next();
416 Attributes attrs =result.getAttributes();
417
418 Attribute cn = attrs.get(roleSearchPrefix);
419 NamingEnumeration values = cn.getAll();
420 if(values.hasMore()){
421 String value = (String)values.next();
422 TgwRole role = new TgwRole(value);
423
424 if(!roleList.contains(role))
425 roleList.add(role);
426 }
427 }
428 }catch(NamingException ne){
429 ne.printStackTrace();
430 }finally{
431 DirectoryUtils.closeQuietly(ctx);
432 }
433 }
434
435 private void cleanup(boolean flagclean){
436
437 if(flagclean){
438 authenticated = false;
439 committed = false;
440 }
441 username = null;
442 password = null;
443
444 sharedState.remove(SHARED_NAME);
445 sharedState.remove(SHARED_PWD);
446 }
447 }