001/*
002 * CDDL HEADER START
003 *
004 * The contents of this file are subject to the terms of the
005 * Common Development and Distribution License, Version 1.0 only
006 * (the "License").  You may not use this file except in compliance
007 * with the License.
008 *
009 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
010 * or http://forgerock.org/license/CDDLv1.0.html.
011 * See the License for the specific language governing permissions
012 * and limitations under the License.
013 *
014 * When distributing Covered Code, include this CDDL HEADER in each
015 * file and include the License file at legal-notices/CDDLv1_0.txt.
016 * If applicable, add the following below this CDDL HEADER, with the
017 * fields enclosed by brackets "[]" replaced with your own identifying
018 * information:
019 *      Portions Copyright [yyyy] [name of copyright owner]
020 *
021 * CDDL HEADER END
022 *
023 *
024 *      Copyright 2008 Sun Microsystems, Inc.
025 *      Portions Copyright 2011-2015 ForgeRock AS
026 */
027package org.opends.server.extensions;
028
029import static org.forgerock.util.Reject.*;
030import static org.opends.messages.ExtensionMessages.*;
031
032import java.util.Iterator;
033import java.util.Set;
034
035import org.forgerock.i18n.LocalizableMessage;
036import org.forgerock.i18n.LocalizedIllegalArgumentException;
037import org.forgerock.i18n.slf4j.LocalizedLogger;
038import org.forgerock.opendj.ldap.DN.CompactDn;
039import org.forgerock.opendj.ldap.SearchScope;
040import org.opends.server.types.DN;
041import org.opends.server.types.DirectoryConfig;
042import org.opends.server.types.DirectoryException;
043import org.opends.server.types.Entry;
044import org.opends.server.types.MemberList;
045import org.opends.server.types.MembershipException;
046import org.opends.server.types.SearchFilter;
047
048/**
049 * This class provides an implementation of the {@code MemberList} class that
050 * may be used in conjunction when static groups when additional criteria is to
051 * be used to select a subset of the group members.
052 */
053public class FilteredStaticGroupMemberList extends MemberList
054{
055  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
056
057  /** The base DN below which all returned members should exist. */
058  private DN baseDN;
059
060  /** The DN of the static group with which this member list is associated. */
061  private DN groupDN;
062
063  /** The entry of the next entry that matches the member list criteria. */
064  private Entry nextMatchingEntry;
065
066  /** The iterator used to traverse the set of member DNs. */
067  private Iterator<CompactDn> memberDNIterator;
068
069  /**
070   * The membership exception that should be thrown the next time a member is
071   * requested.
072   */
073  private MembershipException nextMembershipException;
074
075  /** The search filter that all returned members should match. */
076  private SearchFilter filter;
077
078  /** The search scope to apply against the base DN for the member subset. */
079  private SearchScope scope;
080
081  /**
082   * Creates a new filtered static group member list with the provided
083   * information.
084   *
085   * @param  groupDN    The DN of the static group with which this member list
086   *                    is associated.
087   * @param  memberDNs  The set of DNs for the users that are members of the
088   *                    associated static group.
089   * @param  baseDN     The base DN below which all returned members should
090   *                    exist.  If this is {@code null}, then all members will
091   *                    be considered to match the base and scope criteria.
092   * @param  scope      The search scope to apply against the base DN when
093   *                    selecting eligible members.
094   * @param  filter     The search filter which all returned members should
095   *                    match.  If this is {@code null}, then all members will
096   *                    be considered eligible.
097   */
098  public FilteredStaticGroupMemberList(DN groupDN, Set<CompactDn> memberDNs, DN baseDN, SearchScope scope,
099      SearchFilter filter)
100  {
101    ifNull(groupDN, memberDNs);
102
103    this.groupDN   = groupDN;
104    this.memberDNIterator = memberDNs.iterator();
105    this.baseDN = baseDN;
106    this.filter = filter;
107    this.scope = scope != null ? scope : SearchScope.WHOLE_SUBTREE;
108
109    nextMemberInternal();
110  }
111
112  /**
113   * Attempts to find the next member that matches the associated criteria.
114   * When this method returns, if {@code nextMembershipException} is
115   * non-{@code null}, then that exception should be thrown on the next attempt
116   * to retrieve a member.  If {@code nextMatchingEntry} is non-{@code null},
117   * then that entry should be returned on the next attempt to retrieve a
118   * member.  If both are {@code null}, then there are no more members to
119   * return.
120   */
121  private void nextMemberInternal()
122  {
123    while (memberDNIterator.hasNext())
124    {
125      DN nextDN = null;
126      try
127      {
128        nextDN = StaticGroup.fromCompactDn(memberDNIterator.next());
129      }
130      catch (LocalizedIllegalArgumentException e)
131      {
132        logger.traceException(e);
133        nextMembershipException = new MembershipException(ERR_STATICMEMBERS_CANNOT_DECODE_DN.get(nextDN, groupDN,
134            e.getMessageObject()), true, e);
135        return;
136      }
137
138      // Check to see if we can eliminate the entry as a possible match purely
139      // based on base DN and scope.
140      if (baseDN != null)
141      {
142        switch (scope.asEnum())
143        {
144          case BASE_OBJECT:
145            if (! baseDN.equals(nextDN))
146            {
147              continue;
148            }
149            break;
150
151          case SINGLE_LEVEL:
152            if (! baseDN.equals(nextDN.parent()))
153            {
154              continue;
155            }
156            break;
157
158          case SUBORDINATES:
159            if (baseDN.equals(nextDN) || !baseDN.isAncestorOf(nextDN))
160            {
161              continue;
162            }
163            break;
164
165          default:
166            if (!baseDN.isAncestorOf(nextDN))
167            {
168              continue;
169            }
170            break;
171        }
172      }
173
174      // Get the entry for the potential member.  If we can't, then populate
175      // the next membership exception.
176      try
177      {
178        Entry memberEntry = DirectoryConfig.getEntry(nextDN);
179        if (memberEntry == null)
180        {
181          nextMembershipException = new MembershipException(ERR_STATICMEMBERS_NO_SUCH_ENTRY.get(nextDN, groupDN), true);
182          return;
183        }
184
185        if (filter == null)
186        {
187          nextMatchingEntry = memberEntry;
188          return;
189        }
190        else if (filter.matchesEntry(memberEntry))
191        {
192            nextMatchingEntry = memberEntry;
193            return;
194        }
195        else
196        {
197          continue;
198        }
199      }
200      catch (DirectoryException de)
201      {
202        logger.traceException(de);
203
204        LocalizableMessage message = ERR_STATICMEMBERS_CANNOT_GET_ENTRY.
205            get(nextDN, groupDN, de.getMessageObject());
206        nextMembershipException =
207             new MembershipException(message, true, de);
208        return;
209      }
210    }
211
212    // If we've gotten here, then there are no more members.
213    nextMatchingEntry       = null;
214    nextMembershipException = null;
215  }
216
217  /** {@inheritDoc} */
218  @Override
219  public boolean hasMoreMembers()
220  {
221    return memberDNIterator.hasNext()
222        && (nextMatchingEntry != null || nextMembershipException != null);
223  }
224
225  /** {@inheritDoc} */
226  @Override
227  public DN nextMemberDN() throws MembershipException
228  {
229    if (! memberDNIterator.hasNext())
230    {
231      return null;
232    }
233
234    Entry entry = nextMemberEntry();
235    return entry != null ? entry.getName() : null;
236  }
237
238  /** {@inheritDoc} */
239  @Override
240  public Entry nextMemberEntry() throws MembershipException
241  {
242    if (! memberDNIterator.hasNext())
243    {
244      return null;
245    }
246    if (nextMembershipException != null)
247    {
248      MembershipException me = nextMembershipException;
249      nextMembershipException = null;
250      nextMemberInternal();
251      throw me;
252    }
253
254    Entry e = nextMatchingEntry;
255    nextMatchingEntry = null;
256    nextMemberInternal();
257    return e;
258  }
259
260  /** {@inheritDoc} */
261  @Override
262  public void close()
263  {
264    // No implementation is required.
265  }
266}
267