001: /* Copyright 2001 The JA-SIG Collaborative. All rights reserved.
002: * See license distributed with this file and
003: * available online at http://www.uportal.org/license.html
004: */
005:
006: package org.jasig.portal.security.provider;
007:
008: import java.util.Properties;
009:
010: import javax.naming.AuthenticationException;
011: import javax.naming.NamingEnumeration;
012: import javax.naming.NamingException;
013: import javax.naming.directory.Attribute;
014: import javax.naming.directory.Attributes;
015: import javax.naming.directory.DirContext;
016: import javax.naming.directory.SearchControls;
017: import javax.naming.directory.SearchResult;
018:
019: import org.jasig.portal.ldap.LdapServices;
020: import org.jasig.portal.ldap.ILdapServer;
021: import org.jasig.portal.security.IConfigurableSecurityContext;
022: import org.jasig.portal.security.PortalSecurityException;
023: import org.apache.commons.logging.Log;
024: import org.apache.commons.logging.LogFactory;
025:
026: /**
027: * <p>This is an implementation of a SecurityContext that checks a user's
028: * credentials against an LDAP directory. It expects to be able to bind
029: * to the LDAP directory as the user so that it can authenticate the
030: * user.</p>
031: * <p>
032: * By implementing the {@link org.jasig.portal.security.IConfigurableSecurityContext}
033: * interface this context may have properties set on it. The one property
034: * the <code>SimpleLdapSecurityContext</code> looks for is defined by
035: * the String {@link #LDAP_PROPERTIES_CONNECTION_NAME} "connection".
036: * This property allows a specific, named, LDAP connection to be used by
037: * the context. If no "connection" property is specified the default
038: * LDAP connection returned by {@link org.jasig.portal.ldap.LdapServices} is
039: * used.
040: * </p>
041: *
042: * @author Russell Tokuyama (University of Hawaii)
043: * @version $Revision: 35504 $
044: */
045: public class SimpleLdapSecurityContext extends ChainingSecurityContext
046: implements IConfigurableSecurityContext {
047:
048: private static final Log log = LogFactory
049: .getLog(SimpleLdapSecurityContext.class);
050:
051: // Attributes that we're interested in.
052: public static final int ATTR_UID = 0;
053: public static final int ATTR_FIRSTNAME = ATTR_UID + 1;
054: public static final int ATTR_LASTNAME = ATTR_FIRSTNAME + 1;
055: private final int SIMPLE_LDAP_SECURITYAUTHTYPE = 0xFF04;
056: private static final String[] attributes = { "uid", // user ID
057: "givenName", // first name
058: "sn" // last name
059: };
060:
061: public static final String LDAP_PROPERTIES_CONNECTION_NAME = "connection";
062: private Properties ctxProperties;
063:
064: SimpleLdapSecurityContext() {
065: super ();
066: ctxProperties = new Properties();
067: }
068:
069: /**
070: * Sets the properties to use for this security context.
071: *
072: * @see org.jasig.portal.security.IConfigurableSecurityContext#setProperties(java.util.Properties)
073: */
074: public void setProperties(Properties props) {
075: ctxProperties = props;
076: }
077:
078: /**
079: * Returns the type of authentication this class provides.
080: * @return authorization type
081: */
082: public int getAuthType() {
083: /*
084: * What is this for? No one would know what to do with the
085: * value returned. Subclasses might know but our getAuthType()
086: * doesn't return anything easily useful.
087: */
088: return this .SIMPLE_LDAP_SECURITYAUTHTYPE;
089: }
090:
091: /**
092: * Authenticates the user.
093: */
094: public synchronized void authenticate()
095: throws PortalSecurityException {
096: this .isauth = false;
097: ILdapServer ldapConn;
098:
099: String propFile = ctxProperties
100: .getProperty(LDAP_PROPERTIES_CONNECTION_NAME);
101: if (propFile != null && propFile.length() > 0)
102: ldapConn = LdapServices.getLdapServer(propFile);
103: else
104: ldapConn = LdapServices.getDefaultLdapServer();
105:
106: String creds = new String(
107: this .myOpaqueCredentials.credentialstring);
108: if (this .myPrincipal.UID != null
109: && !this .myPrincipal.UID.trim().equals("")
110: && this .myOpaqueCredentials.credentialstring != null
111: && !creds.trim().equals("")) {
112: DirContext conn = null;
113: NamingEnumeration results = null;
114: StringBuffer user = new StringBuffer("(");
115: String first_name = null;
116: String last_name = null;
117:
118: user.append(ldapConn.getUidAttribute()).append("=");
119: user.append(this .myPrincipal.UID).append(")");
120: if (log.isDebugEnabled())
121: log.debug("SimpleLdapSecurityContext: Looking for "
122: + user.toString());
123:
124: try {
125: conn = ldapConn.getConnection();
126:
127: // set up search controls
128: SearchControls searchCtls = new SearchControls();
129: searchCtls.setReturningAttributes(attributes);
130: searchCtls.setSearchScope(SearchControls.SUBTREE_SCOPE);
131:
132: // do lookup
133: if (conn != null) {
134: try {
135: results = conn.search(ldapConn.getBaseDN(),
136: user.toString(), searchCtls);
137: if (results != null) {
138: if (!results.hasMore())
139: log
140: .error("SimpleLdapSecurityContext: user not found , "
141: + this .myPrincipal.UID);
142: while (results != null && results.hasMore()) {
143: SearchResult entry = (SearchResult) results
144: .next();
145: StringBuffer dnBuffer = new StringBuffer();
146: dnBuffer.append(entry.getName())
147: .append(", ");
148: dnBuffer.append(ldapConn.getBaseDN());
149: Attributes attrs = entry
150: .getAttributes();
151: first_name = getAttributeValue(attrs,
152: ATTR_FIRSTNAME);
153: last_name = getAttributeValue(attrs,
154: ATTR_LASTNAME);
155: // re-bind as user
156: conn
157: .removeFromEnvironment(javax.naming.Context.SECURITY_PRINCIPAL);
158: conn
159: .removeFromEnvironment(javax.naming.Context.SECURITY_CREDENTIALS);
160: conn
161: .addToEnvironment(
162: javax.naming.Context.SECURITY_PRINCIPAL,
163: dnBuffer.toString());
164: conn
165: .addToEnvironment(
166: javax.naming.Context.SECURITY_CREDENTIALS,
167: this .myOpaqueCredentials.credentialstring);
168: searchCtls = new SearchControls();
169: searchCtls
170: .setReturningAttributes(new String[0]);
171: searchCtls
172: .setSearchScope(SearchControls.OBJECT_SCOPE);
173:
174: String attrSearch = "("
175: + ldapConn.getUidAttribute()
176: + "=*)";
177: log
178: .debug("SimpleLdapSecurityContext: Looking in "
179: + dnBuffer.toString()
180: + " for " + attrSearch);
181: conn.search(dnBuffer.toString(),
182: attrSearch, searchCtls);
183:
184: this .isauth = true;
185: this .myPrincipal.FullName = first_name
186: + " " + last_name;
187: log
188: .debug("SimpleLdapSecurityContext: User "
189: + this .myPrincipal.UID
190: + " ("
191: + this .myPrincipal.FullName
192: + ") is authenticated");
193:
194: // Since LDAP is case-insensitive with respect to uid, force
195: // user name to lower case for use by the portal
196: this .myPrincipal.UID = this .myPrincipal.UID
197: .toLowerCase();
198: } // while (results != null && results.hasMore())
199: } else {
200: log
201: .error("SimpleLdapSecurityContext: No such user: "
202: + this .myPrincipal.UID);
203: }
204: } catch (AuthenticationException ae) {
205: log
206: .info("SimpleLdapSecurityContext: Password invalid for user: "
207: + this .myPrincipal.UID);
208: } catch (Exception e) {
209: log.error(
210: "SimpleLdapSecurityContext: LDAP Error with user: "
211: + this .myPrincipal.UID + "; ",
212: e);
213: throw new PortalSecurityException(
214: "SimpleLdapSecurityContext: LDAP Error"
215: + e + " with user: "
216: + this .myPrincipal.UID);
217: } finally {
218: ldapConn.releaseConnection(conn);
219: }
220: } else {
221: log.error("LDAP Server Connection unavalable");
222: }
223: } catch (final NamingException ne) {
224: log
225: .error(
226: "Error geting connection to LDAP server.",
227: ne);
228: }
229: } else {
230: log
231: .error("Principal or OpaqueCredentials not initialized prior to authenticate");
232: }
233: // Ok...we are now ready to authenticate all of our subcontexts.
234: super .authenticate();
235: return;
236: }
237:
238: /*--------------------- Helper methods ---------------------*/
239: /**
240: * <p>Return a single value of an attribute from possibly multiple values,
241: * grossly ignoring anything else. If there are no values, then
242: * return an empty string.</p>
243: *
244: * @param attrs LDAP query results
245: * @param attribute LDAP attribute we are interested in
246: * @return a single value of the attribute
247: */
248: private String getAttributeValue(Attributes attrs, int attribute)
249: throws NamingException {
250: NamingEnumeration values = null;
251: String aValue = "";
252: if (!isAttribute(attribute))
253: return aValue;
254: Attribute attrib = attrs.get(attributes[attribute]);
255: if (attrib != null) {
256: for (values = attrib.getAll(); values.hasMoreElements();) {
257: aValue = (String) values.nextElement();
258: break; // take only the first attribute value
259: }
260: }
261: return aValue;
262: }
263:
264: /**
265: * Is this a value attribute that's been requested?
266: *
267: * @param attribute in question
268: */
269: private boolean isAttribute(int attribute) {
270: if (attribute < ATTR_UID || attribute > ATTR_LASTNAME) {
271: return false;
272: }
273: return true;
274: }
275: }
|