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