001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package org.apache.jetspeed.security.spi.impl.ldap;
018:
019: import java.util.Hashtable;
020:
021: import javax.naming.AuthenticationException;
022: import javax.naming.Context;
023: import javax.naming.InitialContext;
024: import javax.naming.NamingEnumeration;
025: import javax.naming.NamingException;
026: import javax.naming.directory.Attribute;
027: import javax.naming.directory.Attributes;
028: import javax.naming.directory.BasicAttributes;
029: import javax.naming.directory.DirContext;
030: import javax.naming.directory.SearchControls;
031: import javax.naming.directory.SearchResult;
032:
033: import org.apache.commons.lang.StringUtils;
034: import org.apache.commons.logging.Log;
035: import org.apache.commons.logging.LogFactory;
036: import org.apache.jetspeed.i18n.KeyedMessage;
037: import org.apache.jetspeed.security.SecurityException;
038:
039: /**
040: * @see org.apache.jetspeed.security.spi.impl.ldap.LdapUserCredentialDao
041: * @author <a href="mailto:mike.long@dataline.com">Mike Long </a>, <a href="mailto:dlestrat@apache.org">David Le Strat</a>
042: */
043: public class LdapUserCredentialDaoImpl extends AbstractLdapDao
044: implements LdapUserCredentialDao {
045: /** The logger. */
046: private static final Log logger = LogFactory
047: .getLog(LdapUserCredentialDaoImpl.class);
048:
049: /** The password attribute. */
050:
051: /**
052: * <p>
053: * Default constructor.
054: * </p>
055: *
056: * @throws SecurityException A {@link SecurityException}.
057: */
058: public LdapUserCredentialDaoImpl() throws SecurityException {
059: super ();
060: }
061:
062: /**
063: * <p>
064: * Initializes the dao.
065: * </p>
066: *
067: * @param ldapConfig Holds the ldap binding configuration.
068: *
069: * @throws SecurityException A {@link SecurityException}.
070: */
071: public LdapUserCredentialDaoImpl(LdapBindingConfig ldapConfig)
072: throws SecurityException {
073: super (ldapConfig);
074: }
075:
076: /**
077: * <p>
078: * Updates the password for the specified user.
079: * </p>
080: */
081: public void changePassword(final String uid, final String password)
082: throws SecurityException {
083: validateUid(uid);
084: validatePassword(password);
085: logger.debug("changePassword for " + uid + " with " + password);
086: String userDn = lookupByUid(uid);
087: logger.debug("userDn = " + userDn);
088: try {
089: setPassword(userDn, password);
090: } catch (NamingException e) {
091: throw new SecurityException(e);
092: }
093: }
094:
095: /**
096: * <p>
097: * Looks up the user by the UID attribute. If this lookup succeeds, this
098: * method then attempts to authenticate the user using the password,
099: * throwing an AuthenticationException if the password is incorrect or an
100: * OperationNotSupportedException if the password is empty.
101: * </p>
102: *
103: * @param uid The uid.
104: * @param password The password.
105: * @throws SecurityException Throws a {@link SecurityException}.
106: */
107: public boolean authenticate(final String uid, final String password)
108: throws SecurityException {
109: validateUid(uid);
110: validatePassword(password);
111: try {
112: Hashtable env = this .ctx.getEnvironment();
113: //String savedPassword = String.valueOf(getPassword(uid));
114: String oldCredential = (String) env
115: .get(Context.SECURITY_CREDENTIALS);
116: String oldUsername = (String) env
117: .get(Context.SECURITY_PRINCIPAL);
118:
119: String dn = lookupByUid(uid);
120: if (dn == null)
121: throw new SecurityException(new KeyedMessage("User "
122: + uid + " not found"));
123:
124: // Build user dn using lookup value, just appending the user filter after the uid won't work when users
125: // are/can be stored in a subtree (searchScope sub-tree)
126: // The looked up dn though is/should always be correct, just need to append the root context.
127: if (!StringUtils.isEmpty(getRootContext()))
128: dn += "," + getRootContext();
129:
130: env.put(Context.SECURITY_PRINCIPAL, dn);
131: env.put(Context.SECURITY_CREDENTIALS, password);
132: new InitialContext(env);
133: env.put(Context.SECURITY_PRINCIPAL, oldUsername);
134: env.put(Context.SECURITY_CREDENTIALS, oldCredential);
135: return true;
136: } catch (AuthenticationException e) {
137: return false;
138: } catch (NamingException e) {
139: throw new SecurityException(e);
140: }
141: }
142:
143: /**
144: * @see org.apache.jetspeed.security.spi.impl.ldap.LdapUserCredentialDao#getPassword(java.lang.String)
145: */
146: public char[] getPassword(final String uid)
147: throws SecurityException {
148: validateUid(uid);
149: try {
150: SearchControls cons = setSearchControls();
151: NamingEnumeration results = searchByWildcardedUid(uid, cons);
152:
153: return getPassword(results, uid);
154: } catch (NamingException e) {
155: throw new SecurityException(e);
156: }
157: }
158:
159: /**
160: * <p>
161: * Set the user's password.
162: * </p>
163: *
164: * @param userDn The user.
165: * @param password The password.
166: * @throws NamingException Throws a {@link NamingException}.
167: */
168: private void setPassword(final String userDn, final String password)
169: throws NamingException {
170: logger.debug("setPassword userDn = " + userDn);
171: String rdn = getSubcontextName(userDn);
172: //if (!StringUtils.isEmpty(getUserFilterBase()))
173: // rdn+="," + getUserFilterBase();
174: logger.debug("setPassword rdn = " + rdn);
175: Attributes attrs = new BasicAttributes(false);
176:
177: attrs.put(getUserPasswordAttribute(), password);
178: ctx.modifyAttributes(rdn, DirContext.REPLACE_ATTRIBUTE, attrs);
179: }
180:
181: /**
182: * <p>
183: * Get the password.
184: * </p>
185: *
186: * @param results The {@link NamingEnumeration}.
187: * @param uid The uid.
188: * @return The password as an array of char.
189: * @throws NamingException Throws a {@link NamingException}.
190: */
191: private char[] getPassword(final NamingEnumeration results,
192: final String uid) throws NamingException {
193: if (!results.hasMore()) {
194: throw new NamingException(
195: "Could not find any user with uid[" + uid + "]");
196: }
197:
198: Attributes userAttributes = getFirstUser(results);
199:
200: char[] rawPassword = convertRawPassword(getAttribute(
201: getUserPasswordAttribute(), userAttributes));
202: return rawPassword;
203: }
204:
205: /**
206: * <p>
207: * Get the attribute.
208: * </p>
209: *
210: * @param attributeName The attribute name.
211: * @param userAttributes The user {@link Attributes}.
212: * @return The {@link Attribute}
213: * @throws NamingException Throws a {@link NamingException}.
214: */
215: private Attribute getAttribute(String attributeName,
216: Attributes userAttributes) throws NamingException {
217: for (NamingEnumeration ae = userAttributes.getAll(); ae
218: .hasMore();) {
219: Attribute attr = (Attribute) ae.next();
220:
221: if (attr.getID().equalsIgnoreCase(attributeName)) {
222: return attr;
223: }
224: }
225: return null;
226: }
227:
228: /**
229: * <p>
230: * This method converts an ascii password to a char array. It needs to be
231: * improved to do proper unicode conversion.
232: * </p>
233: *
234: * @param attr The {@link Attribute}.
235: */
236: private char[] convertRawPassword(Attribute attr)
237: throws NamingException {
238: char[] charPass = null;
239:
240: if (attr != null) {
241: byte[] rawPass = (byte[]) attr.getAll().next();
242: charPass = new char[rawPass.length];
243:
244: for (int i = 0; i < rawPass.length; i++) {
245: // I know I lose the sign and this is only good for ascii text.
246: charPass[i] = (char) rawPass[i];
247: }
248: } else {
249: charPass = new char[0];
250: }
251: return charPass;
252: }
253:
254: /**
255: * <p>
256: * Gets the first matching user.
257: * </p>
258: *
259: * @param results The results to find the user in.
260: * @return The Attributes.
261: * @throws NamingException Throws a {@link NamingException}.
262: */
263: private Attributes getFirstUser(NamingEnumeration results)
264: throws NamingException {
265: SearchResult result = (SearchResult) results.next();
266: Attributes answer = result.getAttributes();
267:
268: return answer;
269: }
270:
271: protected String getEntryPrefix() {
272: return this .getUserIdAttribute();
273: }
274:
275: protected String getSearchSuffix() {
276: return this .getUserFilter();
277: }
278:
279: protected String getSearchDomain() {
280: return this .getUserFilterBase();
281: }
282:
283: protected String[] getObjectClasses() {
284: return this .getUserObjectClasses();
285: }
286:
287: protected String[] getAttributes() {
288: return this.getUserAttributes();
289: }
290:
291: }
|