| /* |
| * Copyright (c) 2003, 2021 Oracle and/or its affiliates. All rights reserved. |
| * |
| * This program and the accompanying materials are made available under the |
| * terms of the Eclipse Public License v. 2.0, which is available at |
| * http://www.eclipse.org/legal/epl-2.0. |
| * |
| * This Source Code may also be made available under the following Secondary |
| * Licenses when the conditions for such availability set forth in the |
| * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, |
| * version 2 with the GNU Classpath Exception, which is available at |
| * https://www.gnu.org/software/classpath/license.html. |
| * |
| * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 |
| */ |
| |
| package com.sun.jndi.ldap.obj; |
| |
| import com.sun.enterprise.security.GroupPrincipal; |
| import com.sun.jndi.ldap.LdapURL; |
| |
| import java.security.Principal; |
| import java.util.ArrayList; |
| import java.util.Enumeration; |
| import java.util.Hashtable; |
| import java.util.NoSuchElementException; |
| import java.util.Set; |
| |
| import javax.naming.CompositeName; |
| import javax.naming.Context; |
| import javax.naming.Name; |
| import javax.naming.NameNotFoundException; |
| import javax.naming.NamingEnumeration; |
| import javax.naming.NamingException; |
| import javax.naming.directory.Attribute; |
| import javax.naming.directory.Attributes; |
| import javax.naming.directory.BasicAttribute; |
| import javax.naming.directory.BasicAttributes; |
| import javax.naming.directory.DirContext; |
| import javax.naming.directory.ModificationItem; |
| import javax.naming.directory.SearchControls; |
| import javax.naming.ldap.LdapContext; |
| import javax.naming.spi.NamingManager; |
| |
| /** |
| * A representation of the LDAP groupOfNames object class. |
| * This is a static group: its members are listed in the group's member |
| * LDAP attribute. |
| * <p> |
| * Note that when a <tt>GroupOfNames</tt> object is created by the application |
| * program then most of its methods throw {@link IllegalStateException} |
| * until the program binds the object in the directory. However, when a |
| * <tt>GroupOfNames</tt> object is returned to the application program then the |
| * object is already bound in the directory and its methods function normally. |
| * <p> |
| * A <tt>GroupOfNames</tt> instance is not synchronized against concurrent |
| * multithreaded access. Multiple threads trying to access and modify a |
| * <tt>GroupOfNames</tt> should lock the object. |
| * <p> |
| * In order to bind a <tt>GroupOfNames</tt> object in the directory, the |
| * following LDAP object class definition (RFC 2256) must be supported in the |
| * directory schema: |
| * <pre> |
| * ( 2.5.6.9 NAME 'groupOfNames' |
| * SUP top |
| * STRUCTURAL |
| * MUST ( member $ |
| * cn ) |
| * MAY ( businessCategory $ |
| * seeAlso $ |
| * owner $ |
| * ou $ |
| * o $ |
| * description ) ) |
| * </pre> |
| * See |
| * {@link javax.naming.directory.DirContext#bind(javax.naming.Name, |
| * java.lang.Object, javax.naming.directory.Attributes) DirContext.bind} |
| * for details on binding an object in the directory. |
| * <p> |
| * The following code sample shows how the class may be used: |
| * <pre> |
| * |
| * // set the java.naming.factory.object property |
| * env.put(Context.OBJECT_FACTORIES, |
| * "com.sun.jndi.ldap.obj.LdapGroupFactory"); |
| * |
| * // set the java.naming.factory.state property |
| * env.put(Context.STATE_FACTORIES, |
| * "com.sun.jndi.ldap.obj.LdapGroupFactory"); |
| * |
| * // create an initial context using the supplied environment properties |
| * DirContext ctx = new InitialDirContext(env); |
| * |
| * // create a set of members |
| * Set members = new HashSet(); |
| * members.add("cn=bill,ou=people"); |
| * members.add("cn=ben,ou=people"); |
| * members.add("cn=sysadmins,ou=groups"); |
| * Group administrators = new GroupOfNames(members); |
| * |
| * // bind the group in the directory |
| * ctx.bind("cn=administrators,ou=groups", administrators); |
| * |
| * // list all of the group's members |
| * listMembers(administrators); |
| * ... |
| * |
| * |
| * // list the members of a group (subgroups are expanded, by default) |
| * void listMembers(Group group) { |
| * for (Enumeration members = group.members(); |
| * members.hasMoreElements(); ) { |
| * Object object = members.nextElement(); |
| * if (object instanceof Group) { |
| * System.out.println("+" + ((Group) object).getName()); |
| * } else if (object instanceof Principal) { |
| * System.out.println(" " + ((Principal) object).getName()); |
| * } |
| * } |
| * } |
| * |
| * </pre> |
| * |
| * @author Vincent Ryan |
| */ |
| public class GroupOfNames implements GroupPrincipal { |
| |
| private static final boolean debug = false; |
| private static final String OBJECT_CLASS = "groupOfNames"; |
| private static final String MEMBER_ATTR_ID = "member"; |
| private static final String MEMBER_FILTER_EXPR = "(member={0})"; |
| private static final String EXPAND_GROUP = "com.sun.jndi.ldap.obj.expandGroup"; |
| private static final Attribute OBJECT_CLASS_ATTR = new BasicAttribute("objectClass", "top"); |
| static { |
| OBJECT_CLASS_ATTR.add(OBJECT_CLASS); |
| } |
| private static final SearchControls BASE_SEARCH_NO_ATTRS = new SearchControls(); |
| static { |
| BASE_SEARCH_NO_ATTRS.setSearchScope(SearchControls.OBJECT_SCOPE); |
| BASE_SEARCH_NO_ATTRS.setReturningAttributes(new String[0]); // no attrs |
| } |
| |
| private boolean objectIsBound; |
| private boolean expandGroup = true; |
| private Attributes attributes = null; |
| private Attribute memberAttr = null; |
| private String memberAttrId; |
| private String memberFilterExpr; |
| private final Object[] filterArgs = new Object[1]; |
| private final ModificationItem[] modification = new ModificationItem[1]; |
| |
| private String groupDN = null; |
| private String bindDN = null; |
| private DirContext rootCtx = null; |
| private DirContext ctx = null; |
| private DirContext bindCtx = null; |
| private Name name = null; |
| private Name bindName = null; |
| private Hashtable env = null; |
| |
| /** |
| * Create an empty group object. |
| * <p> |
| * Note that the newly constructed object does not represent a group in |
| * the directory until it is bound by using |
| * {@link javax.naming.directory.DirContext#bind(javax.naming.Name, |
| * java.lang.Object, javax.naming.directory.Attributes) DirContext.bind}. |
| */ |
| public GroupOfNames() { |
| if (debug) { |
| System.out.println("[debug] constructing an empty group"); |
| } |
| initializeState(OBJECT_CLASS_ATTR, MEMBER_ATTR_ID, MEMBER_FILTER_EXPR, null); |
| } |
| |
| /** |
| * Create a group object with an initial set of members. |
| * <p> |
| * Note that the newly constructed object does not represent a group in |
| * the directory until it is bound by using |
| * {@link javax.naming.directory.DirContext#bind(javax.naming.Name, |
| * java.lang.Object, javax.naming.directory.Attributes) DirContext.bind}. |
| * |
| * @param members The set of initial members. It may be null. |
| * Each element is of class {@link String} or |
| * {@link java.security.Principal}. |
| */ |
| public GroupOfNames(Set members) { |
| if (debug) { |
| System.out.println("[debug] constructing a group"); |
| } |
| initializeState(OBJECT_CLASS_ATTR, MEMBER_ATTR_ID, MEMBER_FILTER_EXPR, members); |
| } |
| |
| /** |
| * Create a group object. |
| * This method is called by {@link GroupOfUniqueNames}. |
| * |
| * @param objectClass The LDAP objectClass attribute. |
| * @param memberAttrId The LDAP attribute ID which identifies the members. |
| * @param memberfilterExpr The filter expression used to find a member. |
| * @param members The set of initial members. It may be null. |
| */ |
| // package private (used by GroupOfUniqueNames) |
| GroupOfNames(Attribute objectClass, String memberAttrId, String memberFilterExpr, Set members) { |
| initializeState(objectClass, memberAttrId, memberFilterExpr, members); |
| } |
| |
| /** |
| * Create a group object from its entry in the directory. |
| * This method is called by {@link GroupOfUniqueNames}. |
| * |
| * @param objectClass The LDAP objectClass attribute. |
| * @param memberAttrId The LDAP attribute ID which identifies the members. |
| * @param memberfilterExpr The filter expression used to find a member. |
| * @param members The set of initial members. It may be null. |
| * @param groupDN The group's distinguished name. |
| * @param name The group's LDAP distinguished name. |
| * @param ctx An LDAP context. |
| * @param name The group's name relative to the context. |
| * @param env The context's environment properties. |
| * @param attributes The group's LDAP attributes. |
| */ |
| // package private (used by GroupOfUniqueNames) |
| GroupOfNames(Attribute objectClass, String memberAttrId, |
| String memberFilterExpr, Set members, String groupDN, |
| DirContext ctx, Name name, Hashtable env, Attributes attributes) { |
| |
| initializeState(objectClass, memberAttrId, memberFilterExpr, members); |
| initializeBoundState(groupDN, ctx, name, env, attributes); |
| } |
| |
| /** |
| * Create a group object from its entry in the directory. |
| * This method is called by {@link LdapGroupFactory}. |
| * |
| * @param groupDN The group's distinguished name. |
| * @param ctx An LDAP context. |
| * @param name The group's name relative to the context. |
| * @param env The context's environment properties. |
| * @param attributes The group's LDAP attributes. |
| * @return Object The new object instance. |
| */ |
| // package private (used by LdapGroupFactory) |
| static Object getObjectInstance(String groupDN, DirContext ctx, Name name, Hashtable env, Attributes attributes) { |
| if (debug) { |
| System.out.println("[debug] creating a group named: " + groupDN); |
| } |
| return new GroupOfNames(OBJECT_CLASS_ATTR, MEMBER_ATTR_ID, |
| MEMBER_FILTER_EXPR, null, groupDN, ctx, name, env, attributes); |
| } |
| |
| /** |
| * Adds a member to the group. |
| * Performs an LDAP modify to add the member. |
| * |
| * @param member The name of the member to be added. |
| * @return boolean true if the addition was successful; false otherwise. |
| * @throws IllegalStateException The exception is thrown if the |
| * group does not represent a group in the directory. |
| */ |
| public boolean addMember(Principal member) { |
| try { |
| return addMember(member.getName()); |
| } catch (NamingException e) { |
| if (debug) { |
| System.out.println("[debug] error adding the member: " + e); |
| } |
| return false; |
| } |
| } |
| |
| /** |
| * Adds a member to the group. |
| * Performs an LDAP modify to add the member. |
| * |
| * @param dn The distinguished name (RFC 2253) of the member to be added. |
| * @return boolean true if the addition was successful; false otherwise. |
| * @throws NamingException The exception is thrown if an error occurs while |
| * performing LDAP modify. |
| * @throws IllegalStateException The exception is thrown if the |
| * group does not represent a group in the directory. |
| */ |
| public boolean addMember(String dn) throws NamingException { |
| if (! isBound()) { |
| throw new IllegalStateException(); |
| } |
| if (debug) { |
| System.out.println("[debug] adding the member: " + dn); |
| } |
| return modifyMember(dn, DirContext.ADD_ATTRIBUTE); |
| } |
| |
| /** |
| * Checks if the supplied name is a member of the group. |
| * Performs LDAP searches to determine membership. |
| * <p> |
| * By default, subgroups are also checked. |
| * As subgroup expansion is potentially an expensive activity the feature |
| * may be disabled by setting the environment property |
| * "com.sun.jndi.ldap.obj.expandGroup" |
| * to the string value "false". |
| * |
| * @param member The name of the member to be checked. |
| * @return boolean true if membership is confirmed; false otherwise. |
| * @throws IllegalStateException The exception is thrown if the |
| * group does not represent a group in the directory. |
| */ |
| @Override |
| public boolean isMember(Principal member) { |
| try { |
| return isMember(member.getName()); |
| } catch (NamingException e) { |
| if (debug) { |
| System.out.println("[debug] error testing for membership: " + e); |
| } |
| return false; |
| } |
| } |
| |
| /** |
| * Checks if the supplied name is a member of the group. |
| * Performs LDAP searches to determine membership. |
| * <p> |
| * By default, subgroups are also checked. |
| * As subgroup expansion is potentially an expensive activity the feature |
| * may be disabled by setting the environment property |
| * "com.sun.jndi.ldap.obj.expandGroup" |
| * to the string value "false". |
| * |
| * @param dn The distinguished name (RFC 2253) of the member to be checked. |
| * @return boolean true if membership is confirmed; false otherwise. |
| * @throws NamingException The exception is thrown if an error occurs while |
| * performing LDAP search. |
| * @throws IllegalStateException The exception is thrown if the |
| * group does not represent a group in the directory. |
| */ |
| public boolean isMember(String dn) throws NamingException { |
| |
| if (! isBound()) { |
| throw new IllegalStateException(); |
| } |
| if (debug) { |
| System.out.println("[debug] checking if \"" + dn + "\" is a member"); |
| } |
| |
| // Check cache |
| if (memberAttr != null && memberAttr.contains(dn)) { |
| return true; |
| } |
| |
| // Check directory group |
| filterArgs[0] = dn; |
| NamingEnumeration results = ctx.search(name, memberFilterExpr, filterArgs, BASE_SEARCH_NO_ATTRS); |
| |
| // Membership is confirmed if any results are returned |
| if (results != null && results.hasMore()) { |
| results.close(); // cleanup |
| return true; |
| } |
| |
| // Check directory subgroups |
| if (expandGroup) { |
| return isSubgroupMember(dn); |
| } |
| |
| return false; |
| } |
| |
| /** |
| * Returns the members of the group. |
| * Performs LDAP searches to retrieve the members. |
| * <p> |
| * By default, subgroups and their members are also included. |
| * As subgroup expansion is potentially an expensive activity the feature |
| * may be disabled by setting the environment property |
| * "com.sun.jndi.ldap.obj.expandGroup" |
| * to the string value "false". When the feature is disabled only the |
| * group's direct members are returned. |
| * |
| * @return Enumeration The list of members of the group. |
| * When only the {@link LdapGroupFactory} object factory is active |
| * then each element in the enumeration is of class |
| * {@link com.sun.enterprise.security.GroupPrincipal} or |
| * {@link java.security.Principal}. However, when additional |
| * object factories are active then the enumeration may contain |
| * elements of a different class. |
| * @throws IllegalStateException The exception is thrown if the |
| * group does not represent a group in the directory. |
| */ |
| @Override |
| public Enumeration members() { |
| if (!isBound()) { |
| throw new IllegalStateException(); |
| } |
| if (debug) { |
| System.out.println("[debug] enumerating the members"); |
| } |
| |
| try { |
| // Retrieve the group's member attribute unless already cached |
| if ((memberAttr != null) |
| || (memberAttr = ctx.getAttributes(name, new String[] {memberAttrId}).get(memberAttrId)) != null) { |
| return new Members(memberAttr.getAll()); |
| } |
| |
| } catch (NamingException e) { |
| if (debug) { |
| System.out.println("[debug] error enumerating the members: " + e); |
| } |
| // ignore |
| } |
| return new Members(); // empty |
| } |
| |
| /** |
| * Removes a member from the group. |
| * Performs an LDAP modify to remove the member. |
| * |
| * @param member The name of the member to be removed. |
| * @return boolean true if the removal was successful; false otherwise. |
| * @throws IllegalStateException The exception is thrown if the |
| * group does not represent a group in the directory. |
| */ |
| public boolean removeMember(Principal member) { |
| try { |
| return removeMember(member.getName()); |
| } catch (NamingException e) { |
| if (debug) { |
| System.out.println("[debug] error removing the member: " + e); |
| } |
| return false; |
| } |
| } |
| |
| /** |
| * Removes a member from the group. |
| * Performs an LDAP modify to remove the member. |
| * |
| * @param dn The distinguished name (RFC 2253) of the member to be removed. |
| * @return boolean true if the removal was successful; false otherwise. |
| * @throws NamingException The exception is thrown if an error occurs while |
| * performing LDAP modify. |
| * @throws IllegalStateException The exception is thrown if the |
| * group does not represent a group in the directory. |
| */ |
| public boolean removeMember(String dn) throws NamingException { |
| if (!isBound()) { |
| throw new IllegalStateException(); |
| } |
| if (debug) { |
| System.out.println("[debug] removing the member: " + dn); |
| } |
| return modifyMember(dn, DirContext.REMOVE_ATTRIBUTE); |
| } |
| |
| /** |
| * Retrieves the distinguished name of the group. |
| * |
| * @return String The distinguished name of the group. |
| * @throws IllegalStateException The exception is thrown if the |
| * group does not represent a group in the directory. |
| */ |
| @Override |
| public String getName() { |
| if (!isBound()) { |
| throw new IllegalStateException(); |
| } |
| return groupDN; |
| } |
| |
| /** |
| * Sets the distinguished name of the group. |
| * This method is called by {@link LdapGroupFactory}. |
| * |
| * @param groupDN The group's distinguished name. |
| * @param ctx An LDAP context. |
| * @param name The group's name relative to the context. |
| */ |
| // package private (used by LdapGroupFactory) |
| void setName(String groupDN, DirContext ctx, Name name) { |
| bindDN = groupDN; |
| bindCtx = ctx; |
| bindName = name; |
| } |
| |
| /** |
| * Creates a string representation of the group. |
| * |
| * @return String A string listing the distinguished name of the group and |
| * the contents of the group's attribute set. See |
| * {@link javax.naming.directory.BasicAttributes#toString()} |
| * for details. The name is omitted if the group is not bound in |
| * the directory and null is returned if no attributes are |
| * available. |
| */ |
| @Override |
| public String toString() { |
| isBound(); // refresh attributes (if necessary) |
| if (groupDN != null) { |
| StringBuffer buffer = new StringBuffer(); |
| buffer.append("{name: ").append(groupDN).append("}"); |
| if (attributes != null) { |
| buffer.append(attributes.toString()); |
| } |
| return buffer.toString(); |
| } else { |
| return attributes == null ? "" : attributes.toString(); |
| } |
| } |
| |
| /** |
| * Retrieves the group's attributes. |
| * This method is called by {@link LdapGroupFactory}. |
| * |
| * @return Attribute The group's attributes. |
| */ |
| // package private (used by LdapGroupFactory) |
| Attributes getAttributes() { |
| return attributes; |
| } |
| |
| /** |
| * Determines whether the supplied LDAP objectClass attribute matches that |
| * of the group. A match occurs if the argument contains the value |
| * "GroupOfNames". |
| * |
| * @param objectClass The non-null objectClass attribute to check against. |
| * @return true if the objectClass attributes match; false otherwise. |
| */ |
| // package private (used by LdapGroupFactory) |
| static boolean matches(Attribute objectClass) { |
| |
| try { |
| for (Enumeration values = objectClass.getAll(); values.hasMoreElements();) { |
| if (OBJECT_CLASS.equalsIgnoreCase((String) values.nextElement())) { |
| return true; |
| } |
| } |
| } catch (NamingException e) { |
| if (debug) { |
| System.out.println("[debug] error matching objectClass: " + e); |
| } |
| // ignore |
| } |
| return false; |
| } |
| |
| /** |
| * Determines whether the group object is bound in the directory. |
| * |
| * A group object is considered to be bound in the directory when |
| * each of the values of its objectClass attribute match those of |
| * an object in the directory having this group's distinguished name. |
| * |
| * @return true if the object is bound; false otherwise. |
| */ |
| private boolean isBound() { |
| if (objectIsBound) { |
| return true; |
| } else if (bindCtx != null && bindName != null && attributes != null) { |
| try { |
| // Retrieve the group's attributes |
| Attributes bindAttrs = bindCtx.getAttributes(bindName); |
| Attribute bindObjectClass = bindAttrs.get("objectClass"); |
| // Check whether the objectClass attributes match |
| if (bindObjectClass != null && bindObjectClass.equals(attributes.get("objectClass"))) { |
| // Set the group's bound state |
| initializeBoundState(bindDN, bindCtx, bindName, env, bindAttrs); |
| return true; |
| } |
| |
| } catch (NameNotFoundException e) { |
| if (debug) { |
| System.out.println("[debug] object is not bound: " + e); |
| } |
| // ignore |
| |
| } catch (NamingException e) { |
| if (debug) { |
| System.out.println("[debug] error checking if bound: " + e); |
| } |
| // ignore |
| } |
| // Reset state to unbound |
| bindDN = null; |
| bindCtx = null; |
| bindName = null; |
| } |
| return false; |
| } |
| |
| /** |
| * Releases the naming context created by this group. |
| * Closes the root naming context if one had been created. |
| * |
| * @throws NamingException The exception is thrown if a problem is |
| * encountered while closing the naming context. |
| */ |
| public void close() throws NamingException { |
| if (rootCtx != null && rootCtx != ctx) { |
| rootCtx.close(); |
| rootCtx = null; |
| } |
| } |
| |
| /** |
| * Initialize the group's state when unbound. |
| */ |
| private void initializeState(Attribute objectClass, String memberAttrId, String memberFilterExpr, Set members) { |
| objectIsBound = false; |
| this.memberAttrId = memberAttrId; |
| this.memberFilterExpr = memberFilterExpr; |
| |
| // initialize the group's attribute set. |
| attributes = new BasicAttributes(true); |
| attributes.put(objectClass); |
| |
| if (members != null && (!members.isEmpty())) { |
| memberAttr = new BasicAttribute(memberAttrId); |
| for (Object object : members) { |
| if (object instanceof Principal) { |
| memberAttr.add(((Principal) object).getName()); |
| } else { |
| memberAttr.add(object); |
| } |
| } |
| attributes.put(memberAttr); |
| } |
| } |
| |
| /** |
| * Initialize the group's state when bound. |
| */ |
| private void initializeBoundState(String groupDN, DirContext ctx, Name name, Hashtable env, Attributes attributes) { |
| objectIsBound = true; |
| this.groupDN = groupDN; |
| this.ctx = ctx; |
| this.name = name; |
| this.env = env; |
| if (env == null && ctx != null) { |
| try { |
| this.env = ctx.getEnvironment(); |
| } catch (NamingException e) { |
| // ignore |
| } |
| } |
| if (env != null) { |
| String expandGroup = (String) env.get(EXPAND_GROUP); |
| if ("false".equalsIgnoreCase(expandGroup)) { |
| this.expandGroup = false; |
| } |
| } |
| if (attributes != null) { |
| this.attributes = attributes; |
| memberAttr = attributes.get(memberAttrId); |
| } |
| } |
| |
| /** |
| * Add or remove a value from the member attribute. |
| */ |
| private boolean modifyMember(String member, int mod_op) throws NamingException { |
| Attribute memberAttr = new BasicAttribute(memberAttrId, member); |
| modification[0] = new ModificationItem(mod_op, memberAttr); |
| ctx.modifyAttributes(name, modification); |
| this.memberAttr = null; // invalidate the cache |
| return true; |
| } |
| |
| /** |
| * Checks if the supplied name is a member of any subgroups. |
| * All the members are retrieved and any subgroups are explored. |
| */ |
| private boolean isSubgroupMember(String dn) throws NamingException { |
| for (NamingEnumeration members = (NamingEnumeration) members(); members.hasMore();) { |
| Object obj = members.next(); |
| if (obj instanceof GroupOfNames && ((GroupOfNames) obj).isMember(dn)) { |
| members.close(); // cleanup |
| return true; |
| } else if (obj instanceof GroupOfURLs && ((GroupOfURLs) obj).isMember(dn)) { |
| members.close(); // cleanup |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Generate environment properties suitable for the root context using |
| * the supplied set of properties. The following properties are modified |
| * if necessary: |
| * <ul> |
| * <li> java.naming.provider.url property: |
| * trim any components after hostname/port. |
| * (identifies the root context). |
| * </ul> |
| * |
| * @param env A set of environment properties. It is cloned only when a |
| * modification is necessary. |
| * @return A set of environment properties suitable for the root context. |
| */ |
| // package private (used by GroupOfURLs) |
| static Hashtable generateRootContextProperties(Hashtable env) { |
| String url = null; |
| |
| if (env != null) { |
| if ((url = (String) env.get(Context.PROVIDER_URL)) != null) { |
| |
| try { |
| // java.net.URI is cleaner but depends on J2SE v 1.4 |
| LdapURL ldapUrl = new LdapURL(url); |
| String dn = ldapUrl.getDN(); |
| // check if a non-empty DN is present |
| if (dn != null && dn.length() > 0) { |
| String host = ldapUrl.getHost(); |
| int port = ldapUrl.getPort(); |
| url = "ldap://" + ((host != null) ? host : "") + ((port != -1) ? (":" + port) : ""); |
| } else { |
| url = null; // reset flag |
| } |
| } catch (NamingException e) { |
| throw new IllegalArgumentException(url); |
| } |
| } |
| // only clone if making mods |
| if (url != null) { |
| env = (Hashtable) env.clone(); |
| env.put(Context.PROVIDER_URL, url); |
| } |
| } |
| |
| return env; |
| } |
| |
| |
| /** |
| * The members of a static group. |
| */ |
| class Members implements NamingEnumeration { |
| |
| private NamingEnumeration memberDNs = null; |
| private final boolean expandSubgroups; |
| private ArrayList subgroups = null; |
| private NamingEnumeration subgroupMembers = null; |
| |
| /** |
| * Empty members object derived from a static group. |
| */ |
| Members() { |
| if (debug) { |
| System.out.println("[debug] constructing an empty GroupOfNames.Members object"); |
| } |
| expandSubgroups = expandGroup; // GroupOfNames.expandGroup |
| } |
| |
| /** |
| * Members object derived from a static group. |
| */ |
| Members(NamingEnumeration memberDNs) { |
| if (debug) { |
| System.out.println("[debug] constructing a GroupOfNames.Members object"); |
| } |
| this.memberDNs = memberDNs; |
| expandSubgroups = expandGroup; // GroupOfNames.expandGroup |
| } |
| |
| /** |
| * Check if the group has more members. |
| * |
| * @return true if the group has another member. |
| */ |
| @Override |
| public boolean hasMoreElements() { |
| try { |
| return hasMore(); |
| } catch (NamingException e) { |
| if (debug) { |
| System.out.println("[debug] error checking for more members: " + |
| e); |
| } |
| return false; |
| } |
| } |
| |
| /** |
| * Check if the group has more members. |
| * |
| * @return true if the group has another member. |
| * @throws NamingException If a problem is encountered while checking |
| * whether the group has any more members. |
| */ |
| @Override |
| public boolean hasMore() throws NamingException { |
| |
| if (memberDNs == null) { |
| return false; // empty |
| } |
| |
| if (memberDNs.hasMore()) { |
| return true; |
| } |
| |
| // Check subgroups |
| if (expandSubgroups && subgroups != null) { |
| if (subgroupMembers == null && (! subgroups.isEmpty())) { |
| // Retrieve the first subgroup's members |
| subgroupMembers = |
| (NamingEnumeration)((GroupPrincipal)subgroups.remove(0)).members(); |
| } |
| if (null != subgroupMembers && subgroupMembers.hasMore()) { |
| return true; |
| } else if (! subgroups.isEmpty()) { |
| // Retrieve the next subgroup's members |
| subgroupMembers = |
| (NamingEnumeration)((GroupPrincipal)subgroups.remove(0)).members(); |
| return subgroupMembers.hasMore(); |
| } |
| } |
| |
| return false; |
| } |
| |
| /** |
| * Retrieve the next member of the group. |
| * Some members may themselves be groups. Such a member is returned as |
| * an object of class {@link com.sun.enterprise.security.GroupPrincipal}. |
| * <p> |
| * Note that in order to determine whether a member is itself a group |
| * this method reads each member's LDAP entry. As this is potentially an |
| * expensive activity the feature may be disabled by setting the |
| * environment property |
| * "com.sun.jndi.ldap.obj.expandGroup" |
| * to the string value "false". When the feature is |
| * disabled then an object of class {@link java.security.Principal} |
| * is returned. By default, the feature is enabled. |
| * |
| * @return The next member of the group. |
| * When only the {@link LdapGroupFactory} object factory is active |
| * then an object of class |
| * {@link java.security.Principal} or |
| * {@link com.sun.enterprise.security.GroupPrincipal} is returned. |
| * However, when additional object factories are active then an |
| * object of a different class may be returned. |
| * @throws NoSuchElementException If no more members exist or if a |
| * {@link javax.naming.NamingException} was encountered while |
| * retrieving the next element. |
| */ |
| @Override |
| public Object nextElement() { |
| try { |
| return next(); |
| } catch (NamingException e) { |
| // Exception.initCause is cleaner but depends on J2SE v 1.4 |
| throw new NoSuchElementException(e.toString()); |
| } |
| } |
| |
| /** |
| * Retrieve the next member of the group. |
| * Some members may themselves be groups. Such a member is returned as |
| * an object of class {@link com.sun.enterprise.security.GroupPrincipal}. |
| * <p> |
| * Note that in order to determine whether a member is itself a group |
| * this method reads each member's LDAP entry. As this is potentially an |
| * expensive activity the feature may be disabled by setting the |
| * environment property |
| * "com.sun.jndi.ldap.obj.expandGroup" |
| * to the string value "false". When the feature is |
| * disabled then an object of class {@link java.security.Principal} |
| * is returned. By default, the feature is enabled. |
| * |
| * @return The next member of the group. |
| * When only the {@link LdapGroupFactory} object factory is active |
| * then an object of class |
| * {@link java.security.Principal} or |
| * {@link com.sun.enterprise.security.GroupPrincipal} is returned. |
| * However, when additional object factories are active then an |
| * object of a different class may be returned. |
| * @throws NamingException If a problem is encountered while retrieving the |
| * next member of the group. |
| * @throws NoSuchElementException If no more members exist. |
| */ |
| @Override |
| public Object next() throws NamingException { |
| |
| if (memberDNs == null) { |
| throw new NoSuchElementException(); // empty |
| } |
| |
| String memberDN = null; |
| |
| try { |
| if (memberDNs.hasMore()) { |
| memberDN = (String)memberDNs.next(); |
| |
| // Skip lookup when expandGroup=false |
| if (! expandGroup) { |
| return new LdapPrincipal(memberDN); |
| } |
| |
| // Create the root context |
| if (rootCtx == null) { |
| rootCtx = getRootContext(); |
| } |
| // Perform the lookup from the root context |
| Object object = rootCtx.lookup(new CompositeName().add(memberDN)); |
| |
| if (object instanceof GroupPrincipal) { |
| if (expandSubgroups) { |
| if (subgroups == null) { |
| subgroups = new ArrayList(); |
| } |
| subgroups.add(object); |
| } |
| // Subgroups are members too |
| return object; |
| |
| } else if (object instanceof DirContext) { |
| ((DirContext)object).close(); // cleanup |
| return new LdapPrincipal(memberDN); |
| |
| } else { |
| return object; // additional object factories are active |
| } |
| } |
| } catch (NameNotFoundException e) { |
| // Cannot find the member's LDAP entry so return an LdapPrincipal |
| return new LdapPrincipal(memberDN); |
| } |
| |
| // Check subgroups |
| if (expandSubgroups && subgroups != null) { |
| if (subgroupMembers == null && (!subgroups.isEmpty())) { |
| // Retrieve the first subgroup's members |
| subgroupMembers = (NamingEnumeration) ((GroupPrincipal) subgroups.remove(0)).members(); |
| } |
| if (null != subgroupMembers && subgroupMembers.hasMore()) { |
| return subgroupMembers.next(); |
| } else if (!subgroups.isEmpty()) { |
| // Retrieve the next subgroup's members |
| subgroupMembers = (NamingEnumeration) ((GroupPrincipal) subgroups.remove(0)).members(); |
| return subgroupMembers.next(); |
| } |
| } |
| |
| throw new NoSuchElementException(); |
| } |
| |
| /** |
| * Closes the enumeration and releases its resources. |
| * |
| * @throws NamingException If a problem is encountered while |
| * closing the enumeration. |
| */ |
| @Override |
| public void close() throws NamingException { |
| if (subgroupMembers != null) { |
| subgroupMembers.close(); // cleanup |
| } |
| } |
| |
| |
| private DirContext getRootContext() throws NamingException { |
| DirContext rootCtx = null; |
| // test for the root of the namespace (the empty name) |
| if (ctx.getNameInNamespace().length() == 0) { |
| rootCtx = ctx; // context is already a root context |
| } else { |
| // Make properties suitable for the root context |
| env = generateRootContextProperties(env); |
| rootCtx = (DirContext) NamingManager.getInitialContext(env); |
| // Propagate any context request controls (to rootCtx) |
| if (ctx instanceof LdapContext) { |
| ((LdapContext) rootCtx).setRequestControls(((LdapContext) ctx).getRequestControls()); |
| } |
| } |
| return rootCtx; |
| } |
| } |
| } |