Clover coverage report - Ledge Components - SNAPSHOT
Coverage timestamp: Fri Nov 17 2006 05:13:20 CET
file stats: LOC: 570   Methods: 17
NCLOC: 408   Classes: 1
 
 Source file Conditionals Statements Methods TOTAL
DirectoryUserManager.java 52.4% 71.7% 88.2% 69.3%
coverage coverage
 1    //
 2    //Copyright (c) 2003, Caltha - Gajda, Krzewski, Mach, Potempski Sp.J.
 3    //All rights reserved.
 4    //
 5    //Redistribution and use in source and binary forms, with or without modification,
 6    //are permitted provided that the following conditions are met:
 7    //
 8    //* Redistributions of source code must retain the above copyright notice,
 9    //this list of conditions and the following disclaimer.
 10    //* Redistributions in binary form must reproduce the above copyright notice,
 11    //this list of conditions and the following disclaimer in the documentation
 12    //and/or other materials provided with the distribution.
 13    //* Neither the name of the Caltha - Gajda, Krzewski, Mach, Potempski Sp.J.
 14    //nor the names of its contributors may be used to endorse or promote products
 15    //derived from this software without specific prior written permission.
 16    //
 17    //THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 18    //AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 19    //WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 20    //IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 21    //INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 22    //BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
 23    //OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 24    //WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 25    //ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 26    //POSSIBILITY OF SUCH DAMAGE.
 27    //
 28   
 29    package org.objectledge.authentication;
 30   
 31    import java.security.Principal;
 32    import java.util.ArrayList;
 33    import java.util.HashMap;
 34    import java.util.List;
 35    import java.util.Map;
 36    import java.util.StringTokenizer;
 37   
 38    import javax.naming.Context;
 39    import javax.naming.NameAlreadyBoundException;
 40    import javax.naming.NameNotFoundException;
 41    import javax.naming.NamingEnumeration;
 42    import javax.naming.NamingException;
 43    import javax.naming.directory.Attribute;
 44    import javax.naming.directory.Attributes;
 45    import javax.naming.directory.BasicAttribute;
 46    import javax.naming.directory.BasicAttributes;
 47    import javax.naming.directory.DirContext;
 48    import javax.naming.directory.SearchControls;
 49    import javax.naming.directory.SearchResult;
 50   
 51    import org.jcontainer.dna.Configuration;
 52    import org.jcontainer.dna.Logger;
 53    import org.objectledge.naming.ContextFactory;
 54    import org.objectledge.naming.ContextHelper;
 55   
 56    /**
 57    * The user manager implementation based on ldap.
 58    *
 59    * @author <a href="mailto:pablo@caltha.pl">Pawel Potempski</a>
 60    */
 61    public class DirectoryUserManager extends UserManager
 62    {
 63    /** Default value for login attribute name. */
 64    public static final String LOGIN_ATTRIBUTE_DEFAULT = "uid";
 65   
 66    /** Default password attribute key name. */
 67    public static final String PASSWORD_ATTRIBUTE_DEFAULT = "userPassword";
 68   
 69    /**
 70    * Default value for login person object class (uidObject, simpleSecurityObject).
 71    * Minimalistic set of classes was chosen: uidObject (1.3.6.1.1.3.1),
 72    * RFC2377 and simpleSecurityObject (0.9.2342.19200300.100.4.19), RFC1274. */
 73    public static final String[] OBJECT_CLASS_DEFAULT =
 74    {
 75    "uidObject", "simpleSecurityObject"
 76    };
 77   
 78    /** the logger. */
 79    protected Logger logger;
 80   
 81    /** the login attribute key. */
 82    protected String loginAttribute;
 83   
 84    /** the password attribute key. */
 85    protected String passwordAttribute;
 86   
 87    /** the anonymous name. */
 88    protected String anonymousName;
 89   
 90    /** the superuser name. */
 91    protected String superuserName;
 92   
 93    /** The objectClass of newly created containers. */
 94    protected String[] objectClass;
 95   
 96    /** the directory context. */
 97    protected ContextHelper directory;
 98   
 99    /** The principal by name cache */
 100    private Map<String, String> loginByName;
 101   
 102    /** The name by login cache */
 103    private Map<String, String> nameByLogin;
 104   
 105    /** The default search controls */
 106    private SearchControls defaultSearchControls;
 107   
 108    /** The list of user management participants */
 109    private UserManagementParticipant[] participants;
 110   
 111    /**
 112    * Creates an instance of the user manager.
 113    *
 114    * @param config the configuration.
 115    * @param logger the logger.
 116    * @param namingPolicy the namig policy to be used.
 117    * @param loginVerifier the login verifier.
 118    * @param passwordGenerator the password generator.
 119    * @param passwordDigester the password digester.
 120    * @param factory the factory to get context from.
 121    * @throws NamingException if the context could not be accessed.
 122    */
 123  782 public DirectoryUserManager(
 124    Configuration config,
 125    Logger logger,
 126    NamingPolicy namingPolicy,
 127    LoginVerifier loginVerifier,
 128    PasswordGenerator passwordGenerator,
 129    PasswordDigester passwordDigester,
 130    ContextFactory factory,
 131    UserManagementParticipant[] participants)
 132    throws NamingException
 133    {
 134  782 super(namingPolicy, loginVerifier, passwordGenerator, passwordDigester);
 135  782 this.logger = logger;
 136  782 loginByName = new HashMap<String, String>();
 137  782 nameByLogin = new HashMap<String, String>();
 138  782 defaultSearchControls = new SearchControls();
 139  782 loginAttribute = config.getChild("loginAttribute").
 140    getValue(LOGIN_ATTRIBUTE_DEFAULT);
 141  782 passwordAttribute = config.getChild("passwordAttribute").
 142    getValue(PASSWORD_ATTRIBUTE_DEFAULT);
 143  782 anonymousName = config.getChild("anonymousName").getValue(null);
 144  782 superuserName = config.getChild("superuserName").getValue(null);
 145  782 String contextId = config.getChild("contextId").getValue("people");
 146  782 directory = new ContextHelper(factory, contextId, logger);
 147   
 148  782 String objectClassList = config.getChild("objectClass").getValue("");
 149  782 StringTokenizer st = new StringTokenizer(objectClassList,",");
 150  782 objectClass = new String[st.countTokens()];
 151  782 for(int i = 0; st.hasMoreTokens(); i++)
 152    {
 153  0 objectClass[i] = st.nextToken().trim();
 154    }
 155  782 if(objectClass.length == 0)
 156    {
 157  782 objectClass = OBJECT_CLASS_DEFAULT;
 158    }
 159  782 this.participants = participants;
 160    }
 161   
 162    /**
 163    * {@inheritDoc}
 164    */
 165  138 public boolean userExists(String dn)
 166    {
 167  138 try
 168    {
 169  138 getLoginName(dn);
 170  46 return true;
 171    }
 172    catch(AuthenticationException e)
 173    {
 174  92 return false;
 175    }
 176    }
 177   
 178    /**
 179    * {@inheritDoc}
 180    */
 181  506 public Principal createAccount(String login, String dn, String password)
 182    throws AuthenticationException
 183    {
 184  506 if(!checkLogin(login))
 185    {
 186  46 throw new AuthenticationException("login '"+login+"' reserved");
 187    }
 188  460 DirContext ctx = null;
 189  460 try
 190    {
 191  460 ctx = directory.getBaseDirContext();
 192  460 Attributes attrs = new BasicAttributes(true);
 193  460 BasicAttribute oc = new BasicAttribute("objectClass");
 194  460 for (int i = 0; i < objectClass.length; i++)
 195    {
 196  920 oc.add(objectClass[i]);
 197    }
 198  460 attrs.put(oc);
 199  460 attrs.put(new BasicAttribute(loginAttribute, login));
 200  460 attrs.put(new BasicAttribute(passwordAttribute,
 201    passwordDigester.digestPassword(password)));
 202  460 ctx.createSubcontext(directory.getRelativeName(dn), attrs);
 203  414 nameByLogin.put(login, dn);
 204  414 loginByName.put(dn, login);
 205  414 logger.info("User " + dn + " created");
 206    }
 207    catch (NameAlreadyBoundException e)
 208    {
 209  46 throw new UserAlreadyExistsException("user '" + dn + "' already exists");
 210    }
 211    catch (NamingException e)
 212    {
 213  0 throw new AuthenticationException("account creation failed", e);
 214    }
 215    finally
 216    {
 217  460 closeContext(ctx);
 218    }
 219  414 Principal principal = new DefaultPrincipal(dn);
 220  414 for(UserManagementParticipant p: participants)
 221    {
 222  0 p.createAccount(principal);
 223    }
 224  414 return principal;
 225    }
 226   
 227    /**
 228    * {@inheritDoc}
 229    */
 230  92 public void removeAccount(Principal account) throws AuthenticationException
 231    {
 232  92 String login = getLoginName(account.getName());
 233  46 DirContext ctx = null;
 234  46 try
 235    {
 236  46 ctx = directory.getBaseDirContext();
 237  46 ctx.destroySubcontext(directory.getRelativeName(account.getName()));
 238  46 nameByLogin.remove(login);
 239  46 loginByName.remove(account.getName());
 240  46 logger.info("User " + account.getName() + " deleted");
 241    }
 242    catch(NameNotFoundException e)
 243    {
 244  0 throw new UserUnknownException("user "+account.getName()+" does not exist");
 245    }
 246    catch(NamingException e)
 247    {
 248  0 throw new AuthenticationException("account removal failed", e);
 249    }
 250    finally
 251    {
 252  46 closeContext(ctx);
 253    }
 254  46 for(UserManagementParticipant p: participants)
 255    {
 256  0 if(p.supportsRemoval())
 257    {
 258  0 p.createAccount(account);
 259    }
 260    }
 261    }
 262   
 263    /**
 264    * {@inheritDoc}
 265    */
 266  46 public Principal getUserByName(String dn) throws AuthenticationException
 267    {
 268    //load the caches
 269  46 getLoginName(dn);
 270  46 return new DefaultPrincipal(dn);
 271    }
 272   
 273    /**
 274    * {@inheritDoc}
 275    */
 276  92 public Principal getUserByLogin(String login) throws AuthenticationException
 277    {
 278  92 try
 279    {
 280  92 List list = lookupDNs(loginAttribute, login);
 281  92 if(list.size()==0)
 282    {
 283  46 throw new UserUnknownException("failed to lookup user by login");
 284    }
 285  46 if(list.size()>1)
 286    {
 287  0 StringBuilder message = new StringBuilder();
 288  0 message.append("ambigous login, following dn's shares the same login '");
 289  0 message.append(login);
 290  0 message.append("':\n");
 291  0 for(int i = 0; i < list.size();i++)
 292    {
 293  0 message.append((String)list.get(i));
 294  0 message.append("\n");
 295    }
 296  0 throw new AuthenticationException(message.toString());
 297    }
 298  46 return new DefaultPrincipal((String)list.get(0));
 299    }
 300    catch(NamingException e)
 301    {
 302  0 throw new AuthenticationException("Failed to loolup user by login");
 303    }
 304    }
 305   
 306    /**
 307    * {@inheritDoc}
 308    */
 309  46 public Principal getAnonymousAccount() throws AuthenticationException
 310    {
 311  46 if(anonymousName == null)
 312    {
 313  0 return null;
 314    }
 315  46 return new DefaultPrincipal(anonymousName);
 316    }
 317   
 318    /**
 319    * {@inheritDoc}
 320    */
 321  46 public Principal getSuperuserAccount() throws AuthenticationException
 322    {
 323  46 if(superuserName == null)
 324    {
 325  0 return null;
 326    }
 327  46 return new DefaultPrincipal(superuserName);
 328    }
 329   
 330    /**
 331    * {@inheritDoc}
 332    */
 333  46 public void changeUserPassword(Principal account, String password)
 334    throws AuthenticationException
 335    {
 336  46 DirContext ctx = null;
 337  46 try
 338    {
 339  46 ctx = directory.lookupDirContext(account.getName());
 340  46 if(ctx == null)
 341    {
 342  0 throw new UserUnknownException("user "+account.getName()+" does not exist");
 343    }
 344  46 Attributes attrs = new BasicAttributes(true);
 345  46 attrs.put(new BasicAttribute(passwordAttribute,
 346    passwordDigester.digestPassword(password)));
 347  46 ctx.modifyAttributes("", DirContext.REPLACE_ATTRIBUTE, attrs);
 348  46 logger.info("User " + account.getName() + "'s password changed");
 349    }
 350    catch(NamingException e)
 351    {
 352  0 throw new AuthenticationException("password modification failed", e);
 353    }
 354    finally
 355    {
 356  46 closeContext(ctx);
 357    }
 358    }
 359   
 360    /**
 361    * {@inheritDoc}
 362    */
 363  230 public boolean checkUserPassword(Principal account, String password)
 364    throws AuthenticationException
 365    {
 366  230 String storedPassword = null;
 367  230 DirContext ctx = null;
 368  230 try
 369    {
 370  230 ctx = directory.lookupDirContext(account.getName());
 371  184 if(ctx == null)
 372    {
 373  0 throw new UserUnknownException("user "+account.getName()+" does not exist");
 374    }
 375  184 String[] attrIds = { passwordAttribute };
 376  184 Attribute attr = ctx.getAttributes("", attrIds).get(passwordAttribute);
 377  184 Object obj = attr.get();
 378  184 if(obj instanceof String)
 379    {
 380  184 storedPassword = (String)obj;
 381    }
 382    else
 383    {
 384  0 storedPassword = new String((byte[])obj);
 385    }
 386    }
 387    catch (Exception e)
 388    {
 389  46 throw new AuthenticationException("password retreiveal failed", e);
 390    }
 391    finally
 392    {
 393  230 closeContext(ctx);
 394    }
 395  184 return storedPassword.equals(passwordDigester.digestPassword(password));
 396    }
 397   
 398    /**
 399    * {@inheritDoc}
 400    */
 401  138 public DirContext getPersonalData(Principal account) throws AuthenticationException
 402    {
 403  138 try
 404    {
 405  138 DirContext ctx = directory.lookupDirContext(account.getName());
 406  92 return ctx;
 407    }
 408    catch(NamingException e)
 409    {
 410  46 throw new AuthenticationException("Failed to lookup user personal data" +
 411    " for principal: "+account.getName(), e);
 412    }
 413    }
 414   
 415    /**
 416    * {@inheritDoc}
 417    */
 418  92 public Principal[] lookupAccounts(String attribute, String value) throws NamingException
 419    {
 420  92 List list = lookupDNs(attribute, value);
 421  92 Principal[] principals = new Principal[list.size()];
 422  92 for(int i = 0; i < list.size();i++)
 423    {
 424  46 principals[i] = new DefaultPrincipal((String)list.get(i));
 425    }
 426  92 return principals;
 427    }
 428   
 429    /**
 430    * {@inheritDoc}
 431    */
 432  0 public Principal[] lookupAccounts(String query) throws NamingException
 433    {
 434  0 List list = lookupDNs(query);
 435  0 Principal[] principals = new Principal[list.size()];
 436  0 for(int i = 0; i < list.size();i++)
 437    {
 438  0 principals[i] = new DefaultPrincipal((String)list.get(i));
 439    }
 440  0 return principals;
 441    }
 442   
 443    // private helper methods.
 444   
 445    /**
 446    * Close directory context silently.
 447    *
 448    * @param ctx the context.
 449    */
 450  1104 private void closeContext(Context ctx)
 451    {
 452  1104 try
 453    {
 454  1104 if(ctx != null)
 455    {
 456  920 ctx.close();
 457    }
 458    }
 459    catch (Exception e)
 460    {
 461  0 logger.error("closing context failed", e);
 462    }
 463    }
 464   
 465    /**
 466    * Map full user name to login name.
 467    *
 468    * @param name full user name.
 469    * @return the login name, or <code>null</code> if not found.
 470    */
 471  276 private synchronized String getLoginName(String name)
 472    throws AuthenticationException
 473    {
 474  276 String login = (St