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.SearchScope;
029import org.forgerock.opendj.ldap.DN;
030import org.opends.server.core.ServerContext;
031import org.opends.server.extensions.StaticGroup.CompactDn;
032import org.opends.server.types.DirectoryConfig;
033import org.opends.server.types.DirectoryException;
034import org.opends.server.types.Entry;
035import org.opends.server.types.MemberList;
036import org.opends.server.types.MembershipException;
037import org.opends.server.types.SearchFilter;
038
039/**
040 * This class provides an implementation of the {@code MemberList} class that
041 * may be used in conjunction when static groups when additional criteria is to
042 * be used to select a subset of the group members.
043 */
044public class FilteredStaticGroupMemberList extends MemberList
045{
046  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
047
048  /** The base DN below which all returned members should exist. */
049  private DN baseDN;
050
051  /** The DN of the static group with which this member list is associated. */
052  private DN groupDN;
053
054  /** The entry of the next entry that matches the member list criteria. */
055  private Entry nextMatchingEntry;
056
057  /** The iterator used to traverse the set of member DNs. */
058  private Iterator<CompactDn> memberDNIterator;
059
060  /** The membership exception that should be thrown the next time a member is requested. */
061  private MembershipException nextMembershipException;
062
063  /** The search filter that all returned members should match. */
064  private SearchFilter filter;
065
066  /** The search scope to apply against the base DN for the member subset. */
067  private SearchScope scope;
068
069  private final ServerContext serverContext;
070
071  /**
072   * Creates a new filtered static group member list with the provided
073   * information.
074   *
075   * @param serverContext
076   *            The server context.
077   * @param  groupDN    The DN of the static group with which this member list
078   *                    is associated.
079   * @param  memberDNs  The set of DNs for the users that are members of the
080   *                    associated static group.
081   * @param  baseDN     The base DN below which all returned members should
082   *                    exist.  If this is {@code null}, then all members will
083   *                    be considered to match the base and scope criteria.
084   * @param  scope      The search scope to apply against the base DN when
085   *                    selecting eligible members.
086   * @param  filter     The search filter which all returned members should
087   *                    match.  If this is {@code null}, then all members will
088   *                    be considered eligible.
089   */
090  public FilteredStaticGroupMemberList(ServerContext serverContext, DN groupDN, Set<CompactDn> memberDNs, DN baseDN,
091      SearchScope scope, SearchFilter filter)
092  {
093    ifNull(groupDN, memberDNs);
094
095    this.serverContext = serverContext;
096    this.groupDN   = groupDN;
097    this.memberDNIterator = memberDNs.iterator();
098    this.baseDN = baseDN;
099    this.filter = filter;
100    this.scope = scope != null ? scope : SearchScope.WHOLE_SUBTREE;
101
102    nextMemberInternal();
103  }
104
105  /**
106   * Attempts to find the next member that matches the associated criteria.
107   * When this method returns, if {@code nextMembershipException} is
108   * non-{@code null}, then that exception should be thrown on the next attempt
109   * to retrieve a member.  If {@code nextMatchingEntry} is non-{@code null},
110   * then that entry should be returned on the next attempt to retrieve a
111   * member.  If both are {@code null}, then there are no more members to
112   * return.
113   */
114  private void nextMemberInternal()
115  {
116    while (memberDNIterator.hasNext())
117    {
118      DN nextDN = null;
119      try
120      {
121        nextDN = memberDNIterator.next().toDn(serverContext);
122      }
123      catch (LocalizedIllegalArgumentException e)
124      {
125        logger.traceException(e);
126        nextMembershipException = new MembershipException(ERR_STATICMEMBERS_CANNOT_DECODE_DN.get(nextDN, groupDN,
127            e.getMessageObject()), true, e);
128        return;
129      }
130
131      // Check to see if we can eliminate the entry as a possible match purely
132      // based on base DN and scope.
133      if (baseDN != null)
134      {
135        switch (scope.asEnum())
136        {
137          case BASE_OBJECT:
138            if (! baseDN.equals(nextDN))
139            {
140              continue;
141            }
142            break;
143
144          case SINGLE_LEVEL:
145            if (! baseDN.equals(nextDN.parent()))
146            {
147              continue;
148            }
149            break;
150
151          case SUBORDINATES:
152            if (baseDN.equals(nextDN) || !baseDN.isSuperiorOrEqualTo(nextDN))
153            {
154              continue;
155            }
156            break;
157
158          default:
159            if (!baseDN.isSuperiorOrEqualTo(nextDN))
160            {
161              continue;
162            }
163            break;
164        }
165      }
166
167      // Get the entry for the potential member.  If we can't, then populate
168      // the next membership exception.
169      try
170      {
171        Entry memberEntry = DirectoryConfig.getEntry(nextDN);
172        if (memberEntry == null)
173        {
174          nextMembershipException = new MembershipException(ERR_STATICMEMBERS_NO_SUCH_ENTRY.get(nextDN, groupDN), true);
175          return;
176        }
177
178        if (filter == null)
179        {
180          nextMatchingEntry = memberEntry;
181          return;
182        }
183        else if (filter.matchesEntry(memberEntry))
184        {
185            nextMatchingEntry = memberEntry;
186            return;
187        }
188        else
189        {
190          continue;
191        }
192      }
193      catch (DirectoryException de)
194      {
195        logger.traceException(de);
196
197        LocalizableMessage message = ERR_STATICMEMBERS_CANNOT_GET_ENTRY.
198            get(nextDN, groupDN, de.getMessageObject());
199        nextMembershipException =
200             new MembershipException(message, true, de);
201        return;
202      }
203    }
204
205    // If we've gotten here, then there are no more members.
206    nextMatchingEntry       = null;
207    nextMembershipException = null;
208  }
209
210  @Override
211  public boolean hasMoreMembers()
212  {
213    return memberDNIterator.hasNext()
214        && (nextMatchingEntry != null || nextMembershipException != null);
215  }
216
217  @Override
218  public DN nextMemberDN() throws MembershipException
219  {
220    if (! memberDNIterator.hasNext())
221    {
222      return null;
223    }
224
225    Entry entry = nextMemberEntry();
226    return entry != null ? entry.getName() : null;
227  }
228
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  @Override
251  public void close()
252  {
253    // No implementation is required.
254  }
255}