View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.jetspeed.security.spi.impl.ldap;
18  
19  import java.util.Hashtable;
20  
21  import javax.naming.AuthenticationException;
22  import javax.naming.Context;
23  import javax.naming.InitialContext;
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.BasicAttributes;
29  import javax.naming.directory.DirContext;
30  import javax.naming.directory.SearchControls;
31  import javax.naming.directory.SearchResult;
32  
33  import org.apache.commons.lang.StringUtils;
34  import org.apache.commons.logging.Log;
35  import org.apache.commons.logging.LogFactory;
36  import org.apache.jetspeed.i18n.KeyedMessage;
37  import org.apache.jetspeed.security.SecurityException;
38  
39  /***
40   * @see org.apache.jetspeed.security.spi.impl.ldap.LdapUserCredentialDao
41   * @author <a href="mailto:mike.long@dataline.com">Mike Long </a>, <a href="mailto:dlestrat@apache.org">David Le Strat</a>
42   */
43  public class LdapUserCredentialDaoImpl extends AbstractLdapDao implements LdapUserCredentialDao
44  {
45      /*** The logger. */
46      private static final Log logger = LogFactory.getLog(LdapUserCredentialDaoImpl.class);
47  
48      /*** The password attribute. */ 
49      
50      /***
51       * <p>
52       * Default constructor.
53       * </p>
54       *
55       * @throws SecurityException A {@link SecurityException}.
56       */
57      public LdapUserCredentialDaoImpl() throws SecurityException
58      {
59          super();
60      }
61      
62      /***
63       * <p>
64       * Initializes the dao.
65       * </p>
66       * 
67       * @param ldapConfig Holds the ldap binding configuration.
68       * 
69       * @throws SecurityException A {@link SecurityException}.
70       */
71      public LdapUserCredentialDaoImpl(LdapBindingConfig ldapConfig) throws SecurityException
72      {
73          super(ldapConfig);
74      }    
75      
76      /***
77       * <p>
78       * Updates the password for the specified user.
79       * </p>
80       */
81      public void changePassword(final String uid, final String password) throws SecurityException
82      {
83          validateUid(uid);
84          validatePassword(password);
85          logger.debug("changePassword for " + uid + " with " + password);
86          String userDn = lookupByUid(uid);
87          logger.debug("userDn = " + userDn);
88          try
89          {
90              setPassword(userDn, password);
91          }
92          catch (NamingException e)
93          {
94              throw new SecurityException(e);
95          }
96      }
97  
98      /***
99       * <p>
100      * Looks up the user by the UID attribute. If this lookup succeeds, this
101      * method then attempts to authenticate the user using the password,
102      * throwing an AuthenticationException if the password is incorrect or an
103      * OperationNotSupportedException if the password is empty.
104      * </p>
105      * 
106      * @param uid The uid.
107      * @param password The password.
108      * @throws SecurityException Throws a {@link SecurityException}.
109      */	
110     public boolean authenticate(final String uid, final String password) throws SecurityException
111     {
112         validateUid(uid);
113         validatePassword(password);
114         try
115         {
116 			Hashtable env = this.ctx.getEnvironment();
117 			//String savedPassword = String.valueOf(getPassword(uid));
118 			String oldCredential = (String)env.get(Context.SECURITY_CREDENTIALS);
119 			String oldUsername = (String)env.get(Context.SECURITY_PRINCIPAL);
120 						
121 			String dn = lookupByUid(uid);
122             if ( dn == null )
123 				throw new SecurityException(new KeyedMessage("User " + uid + " not found"));
124             
125             // Build user dn using lookup value, just appending the user filter after the uid won't work when users
126             // are/can be stored in a subtree (searchScope sub-tree)
127             // The looked up dn though is/should always be correct, just need to append the root context.
128             if (!StringUtils.isEmpty(getRootContext()))
129                 dn +="," + getRootContext();
130 			
131 			env.put(Context.SECURITY_PRINCIPAL,dn);
132 			env.put(Context.SECURITY_CREDENTIALS,password);
133 			new InitialContext(env);
134 			env.put(Context.SECURITY_PRINCIPAL,oldUsername);
135 			env.put(Context.SECURITY_CREDENTIALS,oldCredential);
136 			return true;
137 		}
138 		catch (AuthenticationException e)
139 		{
140 			return false;
141 		}
142 		catch (NamingException e)
143 		{
144 			throw new SecurityException(e);
145 		}
146     }
147 
148     /***
149      * @see org.apache.jetspeed.security.spi.impl.ldap.LdapUserCredentialDao#getPassword(java.lang.String)
150      */
151     public char[] getPassword(final String uid) throws SecurityException
152     {
153         validateUid(uid);
154         try
155         {
156             SearchControls cons = setSearchControls();
157             NamingEnumeration results = searchByWildcardedUid(uid, cons);
158 
159             return getPassword(results, uid);
160         }
161         catch (NamingException e)
162         {
163             throw new SecurityException(e);
164         }
165     }
166 
167     /***
168      * <p>
169      * Set the user's password.
170      * </p>
171      * 
172      * @param userDn The user.
173      * @param password The password.
174      * @throws NamingException Throws a {@link NamingException}.
175      */
176     private void setPassword(final String userDn, final String password) throws NamingException
177     {
178     	logger.debug("setPassword userDn = " + userDn);
179         String rdn = getSubcontextName(userDn);
180         //if (!StringUtils.isEmpty(getUserFilterBase()))
181         //	rdn+="," + getUserFilterBase();
182         logger.debug("setPassword rdn = " + rdn);
183         Attributes attrs = new BasicAttributes(false);
184 
185         attrs.put(getUserPasswordAttribute(), password);
186         ctx.modifyAttributes(rdn, DirContext.REPLACE_ATTRIBUTE, attrs);
187     }
188 
189     /***
190      * <p>
191      * Get the password.
192      * </p>
193      * 
194      * @param results The {@link NamingEnumeration}.
195      * @param uid The uid.
196      * @return The password as an array of char.
197      * @throws NamingException Throws a {@link NamingException}.
198      */
199     private char[] getPassword(final NamingEnumeration results, final String uid) throws NamingException
200     {
201     	if (!results.hasMore())
202         {
203             throw new NamingException("Could not find any user with uid[" + uid + "]");
204         }
205 
206         Attributes userAttributes = getFirstUser(results);
207 
208         char[] rawPassword = convertRawPassword(getAttribute(getUserPasswordAttribute(), userAttributes));
209         return rawPassword;
210     }
211 
212     /***
213      * <p>
214      * Get the attribute.
215      * </p>
216      * 
217      * @param attributeName The attribute name.
218      * @param userAttributes The user {@link Attributes}.
219      * @return The {@link Attribute}
220      * @throws NamingException Throws a {@link NamingException}.
221      */
222     private Attribute getAttribute(String attributeName, Attributes userAttributes) throws NamingException
223     {
224         for (NamingEnumeration ae = userAttributes.getAll(); ae.hasMore();)
225         {
226             Attribute attr = (Attribute) ae.next();
227 
228             if (attr.getID().equalsIgnoreCase(attributeName))
229             {
230                 return attr;
231             }
232         }
233         return null;
234     }
235 
236     /***
237      * <p>
238      * This method converts an ascii password to a char array. It needs to be
239      * improved to do proper unicode conversion.
240      * </p>
241      * 
242      * @param attr The {@link Attribute}.  
243      */
244     private char[] convertRawPassword(Attribute attr) throws NamingException
245     {
246         char[] charPass = null;
247         
248         if ( attr != null )
249         {
250             byte[] rawPass = (byte[]) attr.getAll().next();
251             charPass = new char[rawPass.length];
252 
253             for (int i = 0; i < rawPass.length; i++)
254             {
255                 // I know I lose the sign and this is only good for ascii text.
256                 charPass[i] = (char) rawPass[i];           
257             }
258         }
259         else
260         {
261             charPass = new char[0];
262         }
263         return charPass;
264     }
265 
266     /***
267      * <p>
268      * Gets the first matching user.
269      * </p>
270      * 
271      * @param results The results to find the user in.
272      * @return The Attributes.
273      * @throws NamingException Throws a {@link NamingException}.
274      */
275     private Attributes getFirstUser(NamingEnumeration results) throws NamingException
276     {
277         SearchResult result = (SearchResult) results.next();
278         Attributes answer = result.getAttributes();
279 
280         return answer;
281     }
282 
283 	protected String getEntryPrefix() {
284 		return this.getUserIdAttribute();
285 	}
286 	
287 	protected String getSearchSuffix() {
288 		return this.getUserFilter();
289 	}
290 
291 	protected String getSearchDomain() {
292 		return this.getUserFilterBase();
293 	}	
294 	
295 	protected String[] getObjectClasses() {
296 		return this.getUserObjectClasses();
297 	}
298 
299 	protected String[] getAttributes() {
300 		return this.getUserAttributes();
301 	}
302 	
303 }