View Javadoc

1   /*
2    * Copyright 2004-2006 the Seasar Foundation and the Others.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 
13   * either express or implied. See the License for the specific language
14   * governing permissions and limitations under the License.
15   */
16  package org.seasar.tuigwaa.security;
17  
18  import java.io.UnsupportedEncodingException;
19  import java.util.ArrayList;
20  import java.util.Hashtable;
21  import java.util.List;
22  import java.util.Properties;
23  
24  import javax.naming.NamingEnumeration;
25  import javax.naming.NamingException;
26  import javax.naming.directory.Attribute;
27  import javax.naming.directory.Attributes;
28  import javax.naming.directory.BasicAttribute;
29  import javax.naming.directory.BasicAttributes;
30  import javax.naming.directory.DirContext;
31  import javax.naming.directory.InitialDirContext;
32  import javax.naming.directory.SearchControls;
33  import javax.naming.directory.SearchResult;
34  
35  import org.apache.commons.logging.Log;
36  import org.apache.commons.logging.LogFactory;
37  import org.seasar.tuigwaa.security.auth.TgwRole;
38  import org.seasar.tuigwaa.security.auth.TgwUser;
39  
40  
41  /***
42   * @author someda
43   */
44  public class DirectoryServiceImpl implements DirectoryService {
45  
46  	private Hashtable environment;	
47  	private Properties props;
48  	
49  	private DirContext ctx;
50  		
51  	private Log log = LogFactory.getLog(getClass());
52  	
53  	private Attribute userObjectClass;
54  	private Attribute roleObjectClass;	
55  	
56  	private Attributes userRequiredAttributes;
57  	private Attributes roleRequiredAttributes;	
58  	
59  	private String searchBase;
60  	
61  	private String userPrefix;
62  	private String userSuffix;
63  	private String rolePrefix;
64  	private String roleSuffix;	
65  	private String roleUserAttribute;
66  	
67  	private String userSearchFilter;
68  	private String roleSearchFilter;
69  	
70  	private String passwordAttribute;
71  	private String descriptionAttribute;
72  	
73  	private String adminUsername;
74  	private String adminRolename;
75  	
76  	private boolean prefixOnly;
77  	
78  	private SearchControls controls;	
79  	
80  	public DirectoryServiceImpl(Hashtable environment, Properties props){
81  		
82  		this.environment = environment;
83  		this.props = props;
84  		
85  		try{
86  			initialize();
87  		}catch(NamingException ne){
88  			log.error("startup failed, check config/directory.dicon.");
89  			throw new RuntimeException(ne);
90  		}
91  	}
92  			
93  	public void setRoleObjectClass(Attribute roleObjectClass) {
94  		this.roleObjectClass = roleObjectClass;
95  	}
96  
97  	public void setUserObjectClass(Attribute userObjectClass) {
98  		this.userObjectClass = userObjectClass;
99  	}
100 
101 	public void setRoleRequiredAttributes(Attributes roleRequiredAttributes) {
102 		this.roleRequiredAttributes = roleRequiredAttributes;
103 	}
104 
105 	public void setUserRequiredAttributes(Attributes userRequiredAttributes) {
106 		this.userRequiredAttributes = userRequiredAttributes;
107 	}
108 
109 	public TgwUser getUser(String userdn){
110 		TgwUser user = null;
111 		
112 		try{			
113 			ctx = new InitialDirContext(environment);			
114 			Attributes attrs = ctx.getAttributes(DirectoryUtils.getRDN(userdn,searchBase));
115 			user = createUser(userdn,attrs);
116 //			ctx.close();
117 		}catch(NamingException ne){
118 			log.error(ne.getMessage());
119 //			ne.printStackTrace();
120 		}finally{
121 			DirectoryUtils.closeQuietly(ctx);
122 		}
123 		return user;
124 	}
125 	
126 	public List getUsers() {
127 				
128 		List list = new ArrayList();
129 		
130 		try{			
131 			ctx = new InitialDirContext(environment);		
132 			String userSearchBase = DirectoryUtils.buildSuffix(userSuffix,searchBase,false,false);
133 			NamingEnumeration res = ctx.search(userSearchBase,userSearchFilter,controls);
134 			while(res.hasMore()){				
135 				SearchResult entry = (SearchResult) res.next();				
136 				Attributes attrs = entry.getAttributes();
137 				
138 				if(attrs == null){
139 					log.info("attributes not found for " + entry.getName());
140 					continue;
141 				}		
142 																
143 				TgwUser user = createUser(getFullDN(entry.getName(),true),attrs);				
144 				list.add(user);								
145 			}			
146 //			ctx.close();
147 		}catch(NamingException ne){
148 			log.error("search failed.");
149 			ne.printStackTrace();
150 		}finally{
151 			DirectoryUtils.closeQuietly(ctx);
152 		}
153 		return list;
154 	}
155 
156 	public void addUser(TgwUser user) {
157 		
158 		String dn = DirectoryUtils.getAbsoluteDN(user.getName(),userPrefix,userSuffix,searchBase); 
159 		user.setDn(dn);
160 		
161 		try{
162 			ctx = new InitialDirContext(environment);															
163 			Attributes attrs = createUserAttributes(user);
164 			
165 			if(userRequiredAttributes != null){
166 				NamingEnumeration reqattrs = userRequiredAttributes.getAll();
167 				while(reqattrs.hasMore()){
168 					attrs.put((Attribute)reqattrs.next());
169 				}								
170 			}			
171 			updateRoles(user,user.getRoles(),DirContext.ADD_ATTRIBUTE);
172 
173 			ctx.createSubcontext(DirectoryUtils.getRDN(dn,searchBase),attrs);
174 			log.info("adding " + dn + " completed.");
175 //			ctx.close();
176 		}catch(NamingException ne){			
177 //			log.error(ne.getMessage());
178 			ne.printStackTrace();
179 		}finally{
180 			DirectoryUtils.closeQuietly(ctx);
181 		}
182 	}
183 
184 	public void deleteUser(String userdn){
185 
186 		TgwUser user = getUser(userdn);
187 		
188 		if(user.isAdmin()){ // should throw Exception
189 			log.warn(user.getName() + "couldn't delete user who has admin priviledge.");
190 			return;			
191 		}		
192 				
193 		try{
194 			ctx = new InitialDirContext(environment);			
195 			updateRoles(user,user.getRoles(),DirContext.REMOVE_ATTRIBUTE);
196 			ctx.destroySubcontext(DirectoryUtils.getRDN(userdn,searchBase));
197 			log.info("deleting " + userdn + " completed.");
198 //			ctx.close();			
199 		}catch(NamingException ne){
200 			ne.printStackTrace();
201 		}finally{
202 			DirectoryUtils.closeQuietly(ctx);
203 		}
204 	}
205 	
206 	public void modifyUser(TgwUser user) {
207 		
208 		String userdn = DirectoryUtils.getAbsoluteDN(user.getName(),userPrefix,userSuffix,searchBase); 
209 		
210 		String[] oldroles = null;		
211 		String[] deleteroles = null;
212 		String[] addroles = null;
213 		String[] updateroles = user.getRoles();
214 		
215 		// get current information
216 		TgwUser olduser = getUser(userdn);
217 		oldroles = olduser.getRoles();
218 		
219 		if(updateroles != null && updateroles.length > 0){			
220 			addroles = getExclusiveStringArray(updateroles,oldroles);
221 			deleteroles = getExclusiveStringArray(oldroles,updateroles);			
222 		}else if(oldroles != null && oldroles.length > 0){
223 			deleteroles = new String[oldroles.length];
224 			System.arraycopy(oldroles,0,deleteroles,0,oldroles.length);			
225 		}		
226 		
227 		if(olduser.isAdmin() && deleteroles != null){
228 			for(int i=0;i<deleteroles.length;i++){
229 				if(deleteroles[i].equals(adminRolename)){
230 					deleteroles[i] = null;
231 					log.info("admin role couldn't delete for admin user, ignore it.");
232 					break;
233 				}	
234 			}
235 		}
236 		
237 		try{
238 			ctx = new InitialDirContext(environment);
239 			Attributes attrs = createUserAttributes(user);
240 			ctx.modifyAttributes(DirectoryUtils.getRDN(userdn,searchBase),DirContext.REPLACE_ATTRIBUTE,attrs);			
241 			
242 			updateRoles(olduser,addroles,DirContext.ADD_ATTRIBUTE);
243 			updateRoles(olduser,deleteroles,DirContext.REMOVE_ATTRIBUTE);
244 //			ctx.close();			
245 		}catch(NamingException ne){
246 			ne.printStackTrace();
247 		}finally{
248 			DirectoryUtils.closeQuietly(ctx);
249 		}
250 	}
251 
252 	public TgwRole getRole(String roledn){
253 		TgwRole role = null;		
254 		try{
255 			ctx = new InitialDirContext(environment);			
256 			Attributes attrs = ctx.getAttributes(DirectoryUtils.getRDN(roledn,searchBase));			
257 			role = createRole(roledn,attrs);			
258 //			ctx.close();			
259 		}catch(NamingException ne){
260 			log.error(ne.getMessage());
261 //			ne.printStackTrace();
262 		}finally{
263 			DirectoryUtils.closeQuietly(ctx);
264 		}
265 		return role;		
266 	}
267 
268 	public List getRoles() {
269 		
270 		List list = new ArrayList();
271 		
272 		try{			
273 			ctx = new InitialDirContext(environment);									
274 			NamingEnumeration res = ctx.search(DirectoryUtils.buildSuffix(roleSuffix,searchBase,false,false),roleSearchFilter,controls);
275 			while(res.hasMore()){
276 				SearchResult entry = (SearchResult) res.next();				
277 				Attributes attrs = entry.getAttributes();
278 				
279 				
280 				if(attrs == null){
281 					log.info("attributes not found for " + entry.getName());
282 					continue;
283 				}
284 				
285 				TgwRole role = createRole(getFullDN(entry.getName(),false),attrs);
286 				list.add(role);									
287 			}			
288 //			ctx.close();
289 		}catch(NamingException ne){
290 			ne.printStackTrace();
291 		}finally{
292 			DirectoryUtils.closeQuietly(ctx);
293 		}
294 		return list;		
295 	}
296 	
297 	public void addRole(TgwRole role) {
298 		String dn = DirectoryUtils.getAbsoluteDN(role.getName(),rolePrefix,roleSuffix,searchBase); 
299 								
300 		try{
301 			ctx = new InitialDirContext(environment);									
302 			Attributes attrs = createRoleAttributes(role);
303 						
304 			if(roleRequiredAttributes != null){
305 				NamingEnumeration reqattrs = roleRequiredAttributes.getAll();
306 				while(reqattrs.hasMore()){
307 					attrs.put((Attribute)reqattrs.next());
308 				}								
309 			}			
310 			ctx.createSubcontext(DirectoryUtils.getRDN(dn,searchBase),attrs);
311 			
312 			String adminUserDn = userPrefix + "=" + adminUsername + DirectoryUtils.buildSuffix(userSuffix,searchBase,true,true);
313 			TgwUser admin = new TgwUser(adminUsername);
314 			admin.setDn(adminUserDn);
315 			updateRoles(admin,new String[]{role.getName()},DirContext.ADD_ATTRIBUTE);			
316 			log.info("adding " + dn + " completed.");	
317 //			ctx.close();			
318 		}catch(NamingException ne){
319 			ne.printStackTrace();
320 		}finally{
321 			DirectoryUtils.closeQuietly(ctx);
322 		}
323 	}
324 
325 	public void deleteRole(String roledn) {
326 		
327 		TgwRole role = getRole(roledn);
328 		if(role.isAdmin()){ // should throw Exception ??
329 			log.warn(role.getName() + "couldn't delete role which represents admin priviledge.");			
330 			return; 
331 		}
332 						
333 		try{
334 			ctx = new InitialDirContext(environment);						
335 			ctx.destroySubcontext(DirectoryUtils.getRDN(roledn,searchBase));
336 			log.info("deleting " + roledn + " completed.");		
337 //			ctx.close();			
338 		}catch(NamingException ne){
339 			ne.printStackTrace();
340 		}finally{
341 			DirectoryUtils.closeQuietly(ctx);
342 		}
343 	}	
344 
345 	public void modifyRole(TgwRole role) {
346 		
347 		String dn = DirectoryUtils.getAbsoluteDN(role.getName(),rolePrefix,roleSuffix,searchBase); 
348 		try{
349 			ctx = new InitialDirContext(environment);
350 			Attributes attrs = createRoleAttributes(role);
351 			ctx.modifyAttributes(DirectoryUtils.getRDN(dn,searchBase),DirContext.REPLACE_ATTRIBUTE,attrs);
352 //			ctx.close();			
353 		}catch(NamingException ne){
354 			ne.printStackTrace();
355 		}finally{
356 			DirectoryUtils.closeQuietly(ctx);
357 		}
358 	}
359 	
360 	public String buildUserDN(String username){
361 		return DirectoryUtils.getAbsoluteDN(username,userPrefix,userSuffix,searchBase);
362 	}
363 	
364 	public void initialize() throws NamingException{		
365 
366 		// initialize properties
367 		searchBase = props.getProperty(BASE_DN);		
368 		userPrefix = props.getProperty(USER_PREFIX);
369 		userSuffix = props.getProperty(USER_SUFFIX);
370 		rolePrefix = props.getProperty(ROLE_PREFIX);
371 		roleSuffix = props.getProperty(ROLE_SUFFIX);
372 		
373 		roleUserAttribute = props.getProperty(ROLE_USER_ATTRIBUTE);		
374 		passwordAttribute = props.getProperty(PASSWORD_ATTRIBUTE);
375 		descriptionAttribute = props.getProperty(DESCRIPTION_ATTRIBUTE);
376 		adminUsername = props.getProperty(ADMIN_USERNAME);
377 		adminRolename = props.getProperty(ADMIN_ROLENAME);
378 		
379 		StringBuffer buf = new StringBuffer();
380 		buf.append("(" + userPrefix + "=*)");
381 		userSearchFilter = buf.toString();
382 		
383 		buf = new StringBuffer();
384 		buf.append("(" + rolePrefix + "=*)");
385 		roleSearchFilter = buf.toString();		
386 		
387 		controls = new SearchControls();
388 		controls.setSearchScope(SearchControls.SUBTREE_SCOPE);
389 				
390 		String value = props.getProperty(DirectoryService.EMBEDED);		
391 		if(Boolean.valueOf(value).booleanValue()){
392 			new InitialDirContext(environment);
393 		}
394 		
395 		String p = props.getProperty(DirectoryService.ROLE_USER_PREFIXONLY);
396 		prefixOnly = Boolean.valueOf(p).booleanValue();		
397 	}
398 	
399 	// ----- [Start] private methods -----	
400 	private TgwUser createUser(String dn, Attributes attrs) throws NamingException{
401 		
402 		TgwUser user = new TgwUser();
403 		user.setDn(dn);
404 
405 		Attribute uid = attrs.get(userPrefix);
406 		if(uid != null){
407 			user.setName((String)uid.get());
408 			user.setAdmin(user.getName().equals(adminUsername));
409 		}		
410 		
411 		Attributes member = null;
412 		if(prefixOnly){
413 			member = new BasicAttributes(roleUserAttribute,user.getName());
414 		}else{
415 			member = new BasicAttributes(roleUserAttribute,dn);			
416 		}
417 		
418 		String searchName = DirectoryUtils.buildSuffix(roleSuffix,searchBase,false,false); 
419 		NamingEnumeration nenum = ctx.search(searchName,member);
420 		List roleList = new ArrayList();
421 		while(nenum.hasMore()){
422 			SearchResult result = (SearchResult) nenum.next();
423 			Attributes roleattrs = result.getAttributes();
424 			Attribute roleattr = roleattrs.get(rolePrefix);										
425 			roleList.add((String)roleattr.get());					
426 		}
427 		if(roleList.size() > 0)
428 			user.setRoles((String[]) roleList.toArray(new String[roleList.size()]));				
429 				
430 		Attribute password = attrs.get(passwordAttribute);
431 		if(password != null){
432 			try{
433 				user.setPassword((String)password.get());
434 			}catch(ClassCastException cce){ // network-based access
435 				try{
436 					String p = new String((byte[])password.get(),"UTF8");
437 					user.setPassword(p);
438 				}catch(UnsupportedEncodingException uee){
439 					uee.printStackTrace();
440 				}
441 			}
442 		}
443 		
444 		Attribute description = attrs.get(descriptionAttribute);
445 		if(description != null)
446 			user.setDescription((String)description.get());			
447 		
448 		return user;
449 	}	
450 	
451 	private Attributes createUserAttributes(TgwUser user){
452 		
453 		Attributes attrs = new BasicAttributes();
454 		attrs.put(userObjectClass);
455 		attrs.put(new BasicAttribute(userPrefix,user.getName()));
456 		attrs.put(new BasicAttribute(passwordAttribute,user.getPassword()));
457 		attrs.put(new BasicAttribute("cn",user.getName()));			
458 		attrs.put(new BasicAttribute("sn",user.getName()));
459 		attrs.put(new BasicAttribute(descriptionAttribute,user.getDescription()));
460 		
461 		return attrs;
462 	}
463 	
464 	private TgwRole createRole(String dn, Attributes attrs) throws NamingException{
465 		
466 		TgwRole role = new TgwRole();
467 		role.setDn(dn);
468 		
469 		Attribute cn = attrs.get(rolePrefix);
470 		if(cn != null){
471 			role.setName((String)cn.get());
472 			role.setAdmin(role.getName().equals(adminRolename));
473 		}
474 				
475 		Attribute description = attrs.get(descriptionAttribute);
476 		if(description != null)
477 			role.setDescription((String)description.get());		
478 				
479 		Attribute member = attrs.get(roleUserAttribute);
480 		if(member != null){
481 			int size = member.size();
482 			String[] users = new String[size];			
483 			for(int i=0;i<size;i++){
484 				users[i] = (String)member.get(i);				
485 			}			
486 			role.setUsers(users);
487 		}		
488 		return role;		
489 	}
490 	
491 	private Attributes createRoleAttributes(TgwRole role){
492 		
493 		Attributes attrs = new BasicAttributes();
494 		attrs.put(roleObjectClass);
495 		attrs.put(new BasicAttribute(rolePrefix,role.getName()));
496 		attrs.put(new BasicAttribute(descriptionAttribute,role.getDescription()));
497 		
498 		return attrs;
499 	}
500 	
501 	
502 	private void updateRoles(TgwUser user, String[] roles, int mod_op) throws NamingException{
503 
504 		if(roles != null){
505 			Attributes member = null;			
506 			if(prefixOnly){
507 				member = new BasicAttributes(roleUserAttribute,user.getName());				
508 			}else{
509 				member = new BasicAttributes(roleUserAttribute,user.getDn());
510 			}
511 			
512 			for(int i=0;i<roles.length;i++){
513 				if(roles[i] != null){ // in-case modifying admin user contains admin role, see modifyUser
514 					String roledn = DirectoryUtils.getAbsoluteDN(roles[i],rolePrefix,roleSuffix,searchBase); 
515 					try{
516 						ctx.modifyAttributes(DirectoryUtils.getRDN(roledn,searchBase),mod_op,member);
517 					}catch(NamingException ne){
518 						log.error("update failed for " + roledn);
519 						continue;
520 					}
521 				}
522 			}
523 		}
524 	}	
525 	
526 	private String[] getExclusiveStringArray(String[] src, String[] dest){
527 		
528 		if(src == null) return null;
529 		if(dest == null) return src;		
530 		
531 		List list = new ArrayList();
532 		for(int i=0;i<src.length;i++){
533 			boolean notIncluded = true;
534 			for(int j=0;j<dest.length;j++){
535 				if(src[i].equals(dest[j])){
536 					notIncluded = false;
537 					break;
538 				}
539 			}
540 			if(notIncluded) list.add(src[i]);
541 		}
542 		return (list.size()>0)?(String[]) list.toArray(new String[list.size()]):null;		
543 	}
544 		
545 	
546 	// in-case network-based access, need to append searchBase to RDN
547 	private String getFullDN(String dn, boolean user){
548 		String ret = dn;		
549 		String suffix = null;
550 		
551 		if(user){
552 			suffix = DirectoryUtils.buildSuffix(userSuffix,searchBase,true,true);
553 		}else{
554 			suffix = DirectoryUtils.buildSuffix(roleSuffix,searchBase,true,true);
555 		}
556 		
557 		if(dn.indexOf(suffix) == -1){
558 			ret += suffix;
559 		}		
560 		return ret;	
561 	}
562 }