View Javadoc

1   /*
2    * LDAPPersistenceFactory.java
3    *
4    * Created on January 21, 2005, 9:37 AM
5    */
6   
7   package gov.bnl.gums.ldap;
8   
9   import java.util.ArrayList;
10  import java.util.Collections;
11  import java.util.Enumeration;
12  import java.util.LinkedList;
13  import java.util.List;
14  import java.util.MissingResourceException;
15  import java.util.Properties;
16  import java.util.PropertyResourceBundle;
17  import java.util.ResourceBundle;
18  import java.util.StringTokenizer;
19  import javax.naming.Name;
20  import javax.naming.NameNotFoundException;
21  import javax.naming.NamingEnumeration;
22  import javax.naming.NamingException;
23  import javax.naming.directory.Attribute;
24  import javax.naming.directory.Attributes;
25  import javax.naming.directory.BasicAttribute;
26  import javax.naming.directory.BasicAttributes;
27  import javax.naming.directory.DirContext;
28  import javax.naming.directory.InitialDirContext;
29  import javax.naming.directory.ModificationItem;
30  import javax.naming.directory.SearchControls;
31  import javax.naming.directory.SearchResult;
32  import javax.naming.ldap.InitialLdapContext;
33  import org.apache.commons.logging.Log;
34  import org.apache.commons.logging.LogFactory;
35  import gov.bnl.gums.*;
36  
37  /***
38   *
39   * @author carcassi
40   */
41  public class LDAPPersistenceFactory implements PersistenceFactory {
42      
43      private Log log = LogFactory.getLog(LDAPPersistenceFactory.class);
44      private Log adminLog = LogFactory.getLog(GUMS.resourceAdminLog);
45      
46      private String name;
47      private Properties properties;
48      private String defaultGumsOU = "ou=GUMS";
49      private String updateGIDdomains;
50      private List domains;
51      private boolean synchGroups;
52  
53      
54      // *** LDAP connection pool management
55      private List contexts = Collections.synchronizedList(new LinkedList());
56      
57      /*** Create a new LDAP DirContext based on the configuration.
58       * 
59       * @return a new LDAP DirContext
60       */
61      protected DirContext createLDAPContext() {
62          try {
63              return new InitialLdapContext(properties, null);
64          } catch (NamingException e) {
65              log.warn("Couldn't create LDAP connection: " + e.getMessage() + " - parameters: " + properties, e);
66              throw new RuntimeException("Couldn't create LDAP connection: " + e.getMessage());
67          }
68      }
69      
70      /*** Retrieves an LDAP DirContext from the pool, if available and still valid,
71       * or creates a new DirContext if none are found.
72       * 
73       * @return an LDAP DirContext
74       */
75      DirContext retrieveContext() {
76          DirContext context;
77          while (contexts.size() != 0) {
78              context = (DirContext) contexts.remove(0);
79              if (isContextValid(context)) {
80                  log.trace("Using LDAP connection from pool " + context);
81                  return context;
82              }
83          }
84          context = createLDAPContext();
85          log.trace("New LDAP connection created " + context);
86          return context;
87      }
88      
89      private boolean skipReleaseContext = false;
90      
91      /*** Returns the LDAP DirContext to the pool, so that it can be reused.
92       * 
93       * @param context the LDAP context to be returned
94       */
95      void releaseContext(DirContext context) {
96          if (skipReleaseContext) {
97              skipReleaseContext = false;
98              return;
99          }
100         contexts.add(0, context);
101         log.trace("LDAP connection returned to pool " + context);
102     }
103     
104     private boolean isContextValid(DirContext context) {
105         try {
106             context.search(getDefaultGumsOU(), "(map=*)", null);
107             return true;
108         } catch (Exception e) {
109             log.trace("Removing stale LDAP connection from pool " + context, e);
110             adminLog.warn("LDAP connection test failed, discarding connection from pool: " + e.getMessage());
111             return false;
112         }
113     }
114     
115     /*** Returns a Context ready to be used (taken from the pool).
116      * This is the entry point for the pool, and it can be used
117      * by test cases to prepare the LDAP server.
118      * 
119      * @return an LDAP context
120      */
121     public DirContext getLDAPContext() {
122         return retrieveContext();
123     }
124     
125     // *** PersistenceFactory methods
126     
127     public UserGroupDB retrieveUserGroupDB(String name) {
128         log.trace("Creating LDAP UserGroupDB '" + name + "'");
129         return new LDAPUserGroupDB(this, name);
130     }
131 
132     public ManualUserGroupDB retrieveManualUserGroupDB(String name) {
133         log.trace("Creating LDAP ManualUserGroupDB '" + name + "'");
134         return new LDAPUserGroupDB(this, name);
135     }
136 
137     public ManualAccountMapperDB retrieveManualAccountMapperDB(String name) {
138         log.trace("Creating LDAP ManualAccountMapperDB '" + name + "'");
139         return new LDAPMapDB(this, name);
140     }
141 
142     public AccountPoolMapperDB retrieveAccountPoolMapperDB(String name) {
143         StringTokenizer tokens = new StringTokenizer(name, ".");
144         if (!tokens.hasMoreTokens()) {
145             log.trace("Creating LDAP AccountPoolMapperDB '" + name + "' (no GIDs)");
146             return new LDAPMapDB(this, name);
147         }
148         
149         String pool = tokens.nextToken();
150         if (!tokens.hasMoreTokens()) {
151             log.trace("Creating LDAP AccountPoolMapperDB '" + name + "' (no GIDs)");
152             return new LDAPMapDB(this, name);
153         }
154         
155         String group = tokens.nextToken();
156         List secondaryGroups = new ArrayList();
157         while (tokens.hasMoreTokens()) {
158             secondaryGroups.add(tokens.nextToken());
159         }
160         
161         log.trace("Creating LDAP AccountPoolMapperDB '" + name + "' primary group '" + group + "' secondary groups '" + secondaryGroups + "'");
162         return new LDAPMapDB(this, pool, group, secondaryGroups);
163     }
164     
165     // *** LDAP gid assigner
166     LDAPGroupIDAssigner assigner;
167     /*** Returns the gid assigner for the given ldap, with the configured
168      * ldap domains for the factory.
169      * 
170      * @return an LDAPGroupIDAssigner preconfigured and ready to use
171      */
172     LDAPGroupIDAssigner retrieveAssigner() {
173         if (assigner == null) {
174             assigner = new LDAPGroupIDAssigner(this, domains);
175             log.trace("New LDAPGroupsIDAssigner created " + assigner);
176         }
177         return assigner;
178     }
179 
180     // *** Factory configuration (getters and setters)
181     
182     /*** Returns the name of the factory.
183      * 
184      * @return the name of the factory as indicated in the configuration
185      */
186     public String getName() {
187         return name;
188     }
189     
190     /*** Changes the name of the factory.
191      * 
192      * @param name the name of the factory as indicated in the configuration
193      */
194     public void setName(String name) {
195         this.name = name;
196     }
197 
198     /***
199      * Changes the GUMS DN in which the GUMS objects will be placed.
200      * This DN will be relative to the DN as specified in the LDAP url
201      * within the LDAP connection parameters.
202      * @return the GUMS base DN (defaults to "ou=GUMS")
203      */
204     public String getDefaultGumsOU()   {
205 
206         return this.defaultGumsOU;
207     }
208 
209     /***
210      * Changes the GUMS DN in which the GUMS objects will be placed.
211      * This DN will be relative to the DN as specified in the LDAP url
212      * within the LDAP connection parameters.
213      * @param baseDN the GUMS base DN (defaults to "ou=GUMS")
214      */
215     public void setDefaultGumsOU(String defaultGumsOU)   {
216 
217         this.defaultGumsOU = defaultGumsOU;
218     }
219 
220     /***
221      * Changes the list of domains to update when an account of the pool is 
222      * assigned. The values required are a comma separated set of domain DNs
223      * in the LDAP server. The domain DNs must be relative to the baseDN
224      * specified in the LDAP url within the LDAP connection parameters.
225      * <p>
226      * If set to null, no gid update will be performed.
227      * @return domains for updating the GID (i.e. "dc=usatlas")
228      */
229     public String getUpdateGIDdomains() {
230         return this.updateGIDdomains;
231     }
232 
233     /***
234      * Changes the list of domains to update when an account of the pool is 
235      * assigned. The values required are a comma separated set of domain DNs
236      * in the LDAP server. The domain DNs must be relative to the baseDN
237      * specified in the LDAP url within the LDAP connection parameters.
238      * <p>
239      * If set to null, no gid update will be performed.
240      * @param updateGIDdomains domains for updating the GID (i.e. "dc=usatlas")
241      */
242     public void setUpdateGIDdomains(String updateGIDdomains) {
243         assigner = null;
244         this.updateGIDdomains = updateGIDdomains;
245     }
246 
247     /***
248      * This property forces the gid update for account pools at every access.
249      * It's handy for when gids gets out of synch.
250      * @return if true gids are updated every time accounts from the pool are returned.
251      */
252     public boolean isSynchGroups() {
253         return this.synchGroups;
254     }
255 
256     /***
257      * This property forces the gid update for account pools at every access.
258      * It's handy for when gids gets out of synch.
259      * @param synchGroups if true gids are updated every time accounts from the pool are returned.
260      */
261     public void setSynchGroups(boolean synchGroups) {
262         this.synchGroups = synchGroups;
263     }
264 
265     /***
266      * Returns the list of properties to be used to connect to LDAP, that is
267      * to create the JNDI context.
268      * @return a set of JNDI properties
269      */
270     public Properties getProperties() {
271         return this.properties;
272     }
273 
274     /***
275      * Returns the list of properties to be used to connect to LDAP, that is
276      * to create the JNDI context.
277      * @param properties a set of JNDI properties
278      */
279     public void setProperties(Properties properties) {
280         if (properties.getProperty("java.naming.factory.initial") == null) {
281             properties.put("java.naming.factory.initial", "com.sun.jndi.ldap.LdapCtxFactory");
282         }
283         this.properties = properties;
284     }
285 
286     /***
287      * Retrieves the JNDI properties to be used to create the LDAP context
288      * by looking at the ldap.properties file in the classpath. This is mainly
289      * used for unit testing.
290      */
291     public void setConnectionFromLdapProperties() {
292         try {
293             setProperties(readLdapProperties());
294         } catch (MissingResourceException e) {
295             throw new RuntimeException("Couldn't find LDAP configuration file (ldap.properties)", e);
296         }
297     }
298     
299     private Properties readLdapProperties() {
300         log.trace("Retrieving LDAP properties from ldap.properties in the classpath");
301         PropertyResourceBundle prop = (PropertyResourceBundle) ResourceBundle.getBundle("ldap");
302         Properties prop2 = new Properties();
303         Enumeration keys = prop.getKeys();
304         while (keys.hasMoreElements()) {
305             String key = (String) keys.nextElement();
306             prop2.setProperty(key, prop.getString(key));
307         }
308         return prop2;
309     }
310     
311     // *** Maps management    
312     
313     /*** Creates a new "map=mapName" entry in the LDAP GUMS tree.
314      * 
315      * @param mapName the name of the map (i.e. "usatlasSpecialMap")
316      * @param mapDN the map DN (i.e. "map=usatlasSpecialMap")
317      */
318     void createMap(String mapName, String mapDN) {
319         DirContext context = retrieveContext();
320         try {
321             Attributes atts = new BasicAttributes();
322             Attribute oc = new BasicAttribute("objectclass");
323             oc.add("GUMStruct");
324             oc.add("GUMSMap");
325             Attribute map = new BasicAttribute("map", mapName);
326             atts.put(oc);
327             atts.put(map);
328             context.createSubcontext(mapDN , atts);
329             log.trace("Created LDAP map '" + mapName + "' at '" + mapDN + "'");
330         } catch (Exception e) {
331             log.info("LDAPPersistence error - createMap - map '" + mapName + "'", e);
332             throw new RuntimeException("Couldn't create LDAP map '" + mapName + "': " + e.getMessage(), e);
333         } finally {
334             releaseContext(context);
335         }
336     }
337     
338     /*** Deletes the "map=mapName" map in the LDAP GUMS tree. Will completely
339      * delete the map.
340      * 
341      * @param mapName the name of the map (i.e. "usatlasSpecialMap")
342      * @param mapDN the map DN (i.e. "map=usatlasSpecialMap")
343      */
344     void destroyMap(String mapName, String mapDN) {
345         DirContext context = retrieveContext();
346         try {
347             SearchControls ctrls = new SearchControls();
348             ctrls.setSearchScope(SearchControls.SUBTREE_SCOPE);
349             NamingEnumeration result = context.search(mapDN, "(objectclass=*)", ctrls);
350             while (result.hasMore()) {
351                 SearchResult res = (SearchResult) result.next();
352                 if ("".equals(res.getName().trim())) continue;
353                 context.destroySubcontext(res.getName() + "," + mapDN);
354             }
355             context.destroySubcontext(mapDN);
356             log.trace("Destroyed LDAP map '" + mapName + "' at '" + mapDN + "'");
357         } catch (Exception e) {
358             log.info("LDAPPersistence error - destroyMap - map '" + mapName + "'", e);
359             throw new RuntimeException("Couldn't destroy LDAP map '" + mapName + "': " + e.getMessage(), e);
360         } finally {
361             releaseContext(context);
362         }
363     }
364     
365     /*** Adds a userDN -> account mapping entry in the "map=mapName" LDAP map.
366      * 
367      * @param userDN the certificate DN of the user (i.e. "/DC=org/DC=doegrids/OU=People/CN=Gabriele Carcassi 12345")
368      * @param account the account to whith to map the DN (i.e. "carcassi")
369      * @param mapName the name of the map (i.e. "usatlasSpecialMap")
370      * @param mapDN the map DN (i.e. "map=usatlasSpecialMap")
371      */
372     void addMapEntry(String userDN, String account, String mapName, String mapDN) {
373         DirContext context = retrieveContext();
374         try {
375             try {
376                 ModificationItem[] mods = new ModificationItem[1];
377                 mods[0] = new ModificationItem(context.ADD_ATTRIBUTE,
378                     new BasicAttribute("user", userDN));
379                 context.modifyAttributes("account=" + account + "," + mapDN, mods);
380                 log.trace("Added user '" + userDN + "' / account '" + account + "' to map '" + mapName + "' at '" + mapDN + "' (account entry present)");
381             } catch (NameNotFoundException e) {
382                 Attributes atts = new BasicAttributes();
383                 Attribute oc = new BasicAttribute("objectclass");
384                 oc.add("GUMStruct");
385                 oc.add("GUMSAccount");
386                 Attribute userAtt = new BasicAttribute("user", userDN);
387                 Attribute accountAtt = new BasicAttribute("account", account);
388                 atts.put(oc);
389                 atts.put(userAtt);
390                 atts.put(accountAtt);
391                 context.createSubcontext("account=" + account + "," + mapDN , atts);
392                 log.trace("Added user '" + userDN + "' / account '" + account + "' to map '" + mapName + "' at '" + mapDN + "' (account entry created)");
393             }
394         } catch (Exception e) {
395             log.info("LDAPPersistence error - addMapEntry - user '" + userDN + "' / account '" + account + "' to map '" + mapName + "' at '" + mapDN + "'", e);
396             throw new RuntimeException("Couldn't add mapping to LDAP map - user '" + userDN + "' / account '" + account + "' to map '" + mapName + "' at '" + mapDN + "': " + e.getMessage(), e);
397         } finally {
398             releaseContext(context);
399         }
400     }
401     
402     /*** Creates an account in the map "map=mapName", without having a userDN: this is useful
403      * for pools of accounts.
404      * 
405      * @param account the account to whith to map the DN (i.e. "grid0001")
406      * @param mapName the name of the map (i.e. "usatlasSpecialMap")
407      * @param mapDN the map DN (i.e. "map=usatlasSpecialMap")
408      */
409     void createAccountInMap(String account, String mapName, String mapDN) {
410         DirContext context = retrieveContext();
411         try {
412             Attributes atts = new BasicAttributes();
413             Attribute oc = new BasicAttribute("objectclass");
414             oc.add("GUMStruct");
415             oc.add("GUMSAccount");
416             Attribute accountAtt = new BasicAttribute("account", account);
417             atts.put(oc);
418             atts.put(accountAtt);
419             context.createSubcontext("account=" + account + "," + mapDN , atts);
420             log.trace("Added account '" + account + "' to map '" + mapName + "' at '" + mapDN + "'");
421         } catch (Exception e) {
422             log.info("LDAPPersistence error - createAccountInMap - account '" + account + "' to map '" + mapName + "' at '" + mapDN + "'", e);
423             throw new RuntimeException("Couldn't add account to LDAP map - account '" + account + "' to map '" + mapName + "' at '" + mapDN + "': " + e.getMessage(), e);
424         } finally {
425             releaseContext(context);
426         }
427     }
428     
429     /*** Removes a userDN -> acount mapping entry in the "map=mapName LDAP map.
430      * It will only remove the user entry, while leaving the account entry.
431      * 
432      * @param userDN the certificate DN of the user (i.e. "/DC=org/DC=doegrids/OU=People/CN=Gabriele Carcassi 12345")
433      * @param mapName the name of the map (i.e. "usatlasSpecialMap")
434      * @param mapDN the map DN (i.e. "map=usatlasSpecialMap")
435      * @return false if no mapping was removed
436      */
437     boolean removeMapEntry(String userDN, String mapName, String mapDN) {
438         DirContext context = retrieveContext();
439         boolean deleted = false;
440         try {
441             SearchControls ctrls = new SearchControls();
442             ctrls.setSearchScope(SearchControls.SUBTREE_SCOPE);
443             NamingEnumeration result = context.search(mapDN, "(user={0})", new Object[] {userDN}, ctrls);
444             while (result.hasMore()) {
445                 SearchResult res = (SearchResult) result.next();
446                 if ("".equals(res.getName().trim())) continue;
447                 ModificationItem[] mods = new ModificationItem[1];
448                 mods[0] = new ModificationItem(context.REMOVE_ATTRIBUTE,
449                     new BasicAttribute("user", userDN));
450                 context.modifyAttributes(res.getName() + "," + mapDN, mods);
451                 deleted = true;
452                 log.trace("Removed map entry - user '" + userDN + "' to map '" + mapName + "' at '" + mapDN + "'");
453             }
454             return deleted;
455         } catch (Exception e) {
456             log.info("LDAPPersistence error - removeMapEntry - user '" + userDN + "' to map '" + mapName + "' at '" + mapDN + "'", e);
457             throw new RuntimeException("Couldn't remove map entry from LDAP map - user '" + userDN + "' to map '" + mapName + "' at '" + mapDN + "': " + e.getMessage(), e);
458         } finally {
459             releaseContext(context);
460         }
461     }
462 
463     // *** User group management
464     
465     /*** Creates a new "group=groupName" entry in the LDAP GUMS tree.
466      * 
467      * @param groupName the name of the group (i.e. "usatlas")
468      * @param groupDN the group DN (i.e. "group=usatlas")
469      */
470     void createUserGroup(String groupName, String groupDN) {
471         DirContext context = retrieveContext();
472         try {
473             Attributes atts = new BasicAttributes();
474             Attribute oc = new BasicAttribute("objectclass");
475             oc.add("GUMStruct");
476             oc.add("GUMSGroup");
477             Attribute group = new BasicAttribute("group", groupName);
478             atts.put(oc);
479             atts.put(group);
480             context.createSubcontext(groupDN , atts);
481             log.trace("Created user group '" + groupName + "' at '" + groupDN + "'");
482         } catch (Exception e) {
483             log.info("LDAPPersistence error - createUserGroup - group '" + groupName + "'", e);
484             throw new RuntimeException("Couldn't create LDAP group '" + groupName + "': " + e.getMessage(), e);
485         } finally {
486             releaseContext(context);
487         }
488     }
489 
490     /*** Adds a certificate DN to the group "group=groupName".
491      * 
492      * @param userDN the certificate DN of the user (i.e. "/DC=org/DC=doegrids/OU=People/CN=Gabriele Carcassi 12345")
493      * @param groupName the name of the group (i.e. "usatlas")
494      * @param groupDN the group DN (i.e. "group=usatlas")
495      */
496     void addUserGroupEntry(String userDN, String groupName, String groupDN) {
497         DirContext context = retrieveContext();
498         try {
499             ModificationItem[] mods = new ModificationItem[1];
500             mods[0] = new ModificationItem(context.ADD_ATTRIBUTE,
501                 new BasicAttribute("user", userDN));
502             context.modifyAttributes(groupDN, mods);
503             log.trace("Added user '" + userDN + "' to group '" + groupName + "' at '" + groupDN + "'");
504         } catch (Exception e) {
505             log.info("LDAPPersistence error - addUserGroupEntry - user '" + userDN + "' to group '" + groupName + "' at '" + groupDN + "'", e);
506             throw new RuntimeException("Couldn't add user to LDAP group - user '" + userDN + "' to group '" + groupName + "' at '" + groupDN + "': " + e.getMessage(), e);
507         } finally {
508             releaseContext(context);
509         }
510     }
511 
512     /*** Removes a certificate DN to the group "group=groupName".
513      * 
514      * @param userDN the certificate DN of the user (i.e. "/DC=org/DC=doegrids/OU=People/CN=Gabriele Carcassi 12345")
515      * @param groupName the name of the group (i.e. "usatlas")
516      * @param groupDN the group DN (i.e. "group=usatlas")
517      */
518     void removeUserGroupEntry(String userDN, String groupName, String groupDN) {
519         DirContext context = retrieveContext();
520         try {
521             ModificationItem[] mods = new ModificationItem[1];
522             mods[0] = new ModificationItem(context.REMOVE_ATTRIBUTE,
523                 new BasicAttribute("user", userDN));
524             context.modifyAttributes(groupDN, mods);
525             log.trace("Removed user '" + userDN + "' to group '" + groupName + "' at '" + groupDN + "'");
526         } catch (Exception e) {
527             log.info("LDAPPersistence error - removeUserGroupEntry - user '" + userDN + "' to group '" + groupName + "' at '" + groupDN + "'", e);
528             throw new RuntimeException("Couldn't remove user to LDAP group  - user '" + userDN + "' to group '" + groupName + "' at '" + groupDN + "': " + e.getMessage(), e);
529         } finally {
530             releaseContext(context);
531         }
532     }
533     
534     // *** gid management
535     
536     private String findGID(String domain, String groupname) {
537         DirContext context = retrieveContext();
538         try {
539             DirContext subcontext = getDomainContext(context, domain);
540             NamingEnumeration result = subcontext.search("ou=Group", "(cn={0})", new Object[] {groupname}, null);
541             String gid = null;
542             if (result.hasMore()) {
543                 SearchResult item = (SearchResult) result.next();
544                 Attributes atts = item.getAttributes();
545                 Attribute gidAtt = atts.get("gidNumber");
546                 if (gidAtt != null) {
547                     gid = (String) gidAtt.get();
548                 }
549             }
550             log.trace("Found gid '" + gid + "' for group '" + groupname + "' at '" + domain + "'");
551             return gid;
552         } catch (Exception e) {
553             log.info("Couldn't retrieve gid for '" + groupname + "' at '" + domain + "'", e);
554             throw new RuntimeException("Couldn't retrieve gid for '" + groupname + "' at '" + domain + "': " + e.getMessage(), e);
555         } finally {
556             releaseContext(context);
557         }
558     }
559     
560     private void updateGID(String domain, String username, String gid) {
561         DirContext context = retrieveContext();
562         try {
563             DirContext subcontext = getDomainContext(context, domain);
564             ModificationItem[] mods = new ModificationItem[1];
565             mods[0] = new ModificationItem(subcontext.REPLACE_ATTRIBUTE,
566                 new BasicAttribute("gidNumber", gid));
567             
568             subcontext.modifyAttributes("uid="+username+",ou=People", mods);
569             log.trace("Changed primary gid for user '" + username + "' to gid '" + gid + "' at '" + domain + "'");
570         } catch (Exception e) {
571             log.info("Couldn't change gid for user '" + username + "' to gid '" + gid + "' at '" + domain + "'", e);
572             throw new RuntimeException("Couldn't change gid for user '" + username + "' to gid '" + gid + "' at '" + domain + "': " + e.getMessage(), e);
573         } finally {
574             releaseContext(context);
575         }
576     }
577     
578     /*** Changes the primary gid for the given username. It expects the base DN
579      * provided by the LDAP connection url to point to a domain (i.e. "dc-usatlas,dc=bnl,dc=gov")
580      * that will contain a "ou=People" and a "ou=Group" subtree.
581      * 
582      * @param username the username to change the primary group (i.e. "carcassi")
583      * @param groupname the primary group name (i.e. "usatlas")
584      */
585     public void changeGroupID(String username, String groupname) {
586         String gid = findGID(null, groupname);
587         if (gid == null) {
588             throw new RuntimeException("GID for group '" + groupname + "' wasn't found.");
589         }
590         updateGID(null, username, gid);
591     }
592     
593     /*** Changes the primary gid for the given username. It expects the domain to be
594      * relative to the base DN
595      * provided by the LDAP connection url and to point to a domain (i.e. "dc-usatlas,dc=bnl,dc=gov")
596      * that will contain a "ou=People" and a "ou=Group" subtree.
597      *
598      * @param domain the domain DN relative to be base DN
599      * @param username the username to change the primary group (i.e. "carcassi")
600      * @param groupname the primary group name (i.e. "usatlas")
601      */
602     public void changeGroupID(String domain, String username, String groupname) {
603         String gid = findGID(domain, groupname);
604         if (gid == null) {
605             throw new RuntimeException("GID for group '" + groupname + "' wasn't found.");
606         }
607         updateGID(domain, username, gid);
608     }
609     
610     /*** Adds the username to the given secondary group. It expects the base DN
611      * provided by the LDAP connection url to point to a domain (i.e. "dc-usatlas,dc=bnl,dc=gov")
612      * that will contain a "ou=People" and a "ou=Group" subtree.
613      * 
614      * @param username the username to add to the secondary group (i.e. "carcassi")
615      * @param groupname the secondary group name (i.e. "usatlas")
616      */
617     public void addToSecondaryGroup(String username, String groupname) {
618         addToSecondaryGroup(null, username, groupname);
619     }
620     
621     /*** Adds the username to the given secondary group. It expects the domain to be
622      * relative to the base DN
623      * provided by the LDAP connection url and to point to a domain (i.e. "dc-usatlas,dc=bnl,dc=gov")
624      * that will contain a "ou=People" and a "ou=Group" subtree.
625      * 
626      * @param domain the domain DN relative to be base DN
627      * @param username the username to add to the secondary group (i.e. "carcassi")
628      * @param groupname the secondary group name (i.e. "usatlas")
629      */
630     public void addToSecondaryGroup(String domain, String username, String groupname) {
631         DirContext context = retrieveContext();
632         try {
633             DirContext subcontext = getDomainContext(context, domain);
634             SearchControls ctrls = new SearchControls();
635             ctrls.setSearchScope(SearchControls.SUBTREE_SCOPE);
636             NamingEnumeration result = subcontext.search("cn=usatlas, ou=Group", "(memberUid={0})", new Object[] {username}, ctrls);
637             if (result.hasMore()) return;
638             ModificationItem[] mods = new ModificationItem[1];
639             mods[0] = new ModificationItem(subcontext.ADD_ATTRIBUTE,
640                 new BasicAttribute("memberUid", username));
641             
642             subcontext.modifyAttributes("cn="+groupname+",ou=Group", mods);
643             log.trace("Added secondary group to user - user '" + username + "' to group '" + groupname + "' at '" + domain + "'");
644         } catch (Exception e) {
645             log.info("Couldn't add user to secondary group - user '" + username + "' to group '" + groupname + "' at '" + domain + "'", e);
646             throw new RuntimeException("Couldn't add user to secondary group - user '" + username + "' to group '" + groupname + "' at '" + domain + "': " + e.getMessage(), e);
647         } finally {
648             releaseContext(context);
649         }
650     }
651     
652     private DirContext getDomainContext(DirContext context, String domain) throws Exception {
653         if (domain == null) return context;
654         return (DirContext) context.lookup(domain);
655     }
656 
657 }