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-2010 Sun Microsystems, Inc.
015 * Portions Copyright 2014-2016 ForgeRock AS.
016 */
017package org.opends.server.extensions;
018
019import java.util.Collections;
020import java.util.HashSet;
021import java.util.Iterator;
022import java.util.LinkedHashSet;
023import java.util.List;
024import java.util.Set;
025import java.util.concurrent.atomic.AtomicReference;
026
027import org.forgerock.i18n.LocalizableMessage;
028import org.forgerock.i18n.LocalizedIllegalArgumentException;
029import org.forgerock.i18n.slf4j.LocalizedLogger;
030import org.forgerock.opendj.config.server.ConfigException;
031import org.forgerock.opendj.ldap.ByteString;
032import org.forgerock.opendj.ldap.DN;
033import org.forgerock.opendj.ldap.SearchScope;
034import org.forgerock.opendj.ldap.schema.AttributeType;
035import org.forgerock.opendj.server.config.server.DynamicGroupImplementationCfg;
036import org.opends.server.api.Group;
037import org.opends.server.core.DirectoryServer;
038import org.opends.server.core.ServerContext;
039import org.opends.server.types.Attribute;
040import org.opends.server.types.DirectoryConfig;
041import org.opends.server.types.DirectoryException;
042import org.opends.server.types.Entry;
043import org.opends.server.types.InitializationException;
044import org.opends.server.types.LDAPURL;
045import org.opends.server.types.MemberList;
046import org.opends.server.types.Modification;
047import org.opends.server.types.SearchFilter;
048
049import static org.forgerock.util.Reject.*;
050import static org.opends.messages.ExtensionMessages.*;
051import static org.opends.server.config.ConfigConstants.*;
052import static org.opends.server.util.ServerConstants.*;
053
054/**
055 * This class provides a dynamic group implementation, in which
056 * membership is determined dynamically based on criteria provided
057 * in the form of one or more LDAP URLs.  All dynamic groups should
058 * contain the groupOfURLs object class, with the memberURL attribute
059 * specifying the membership criteria.
060 */
061public class DynamicGroup
062       extends Group<DynamicGroupImplementationCfg>
063{
064  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
065
066  /** The DN of the entry that holds the definition for this group. */
067  private DN groupEntryDN;
068
069  /** The set of the LDAP URLs that define the membership criteria. */
070  private LinkedHashSet<LDAPURL> memberURLs;
071
072  /** Creates a new, uninitialized dynamic group instance. This is intended for internal use only. */
073  public DynamicGroup()
074  {
075    super();
076
077    // No initialization is required here.
078  }
079
080  /**
081   * Creates a new dynamic group instance with the provided information.
082   *
083   * @param  groupEntryDN  The DN of the entry that holds the definition for
084   *                       this group.  It must not be {@code null}.
085   * @param  memberURLs    The set of LDAP URLs that define the membership
086   *                       criteria for this group.  It must not be
087   *                       {@code null}.
088   */
089  public DynamicGroup(DN groupEntryDN, LinkedHashSet<LDAPURL> memberURLs)
090  {
091    super();
092
093    ifNull(groupEntryDN, memberURLs);
094
095    this.groupEntryDN = groupEntryDN;
096    this.memberURLs   = memberURLs;
097  }
098
099  @Override
100  public void initializeGroupImplementation(
101                   DynamicGroupImplementationCfg configuration)
102         throws ConfigException, InitializationException
103  {
104    // No additional initialization is required.
105  }
106
107  @Override
108  public DynamicGroup newInstance(ServerContext serverContext, Entry groupEntry)
109         throws DirectoryException
110  {
111    ifNull(groupEntry);
112
113    // Get the memberURL attribute from the entry, if there is one, and parse
114    // out the LDAP URLs that it contains.
115    LinkedHashSet<LDAPURL> memberURLs = new LinkedHashSet<>();
116    AttributeType memberURLType = DirectoryServer.getSchema().getAttributeType(ATTR_MEMBER_URL_LC);
117    for (Attribute a : groupEntry.getAttribute(memberURLType))
118    {
119      for (ByteString v : a)
120      {
121        try
122        {
123          memberURLs.add(LDAPURL.decode(v.toString(), true));
124        }
125        catch (LocalizedIllegalArgumentException | DirectoryException e)
126        {
127          logger.traceException(e);
128          logger.error(ERR_DYNAMICGROUP_CANNOT_DECODE_MEMBERURL, v, groupEntry.getName(), e.getMessageObject());
129        }
130      }
131    }
132
133    return new DynamicGroup(groupEntry.getName(), memberURLs);
134  }
135
136  @Override
137  public SearchFilter getGroupDefinitionFilter()
138         throws DirectoryException
139  {
140    // FIXME -- This needs to exclude enhanced groups once we have support for
141    // them.
142    return SearchFilter.createFilterFromString("(" + ATTR_OBJECTCLASS + "=" +
143                                               OC_GROUP_OF_URLS + ")");
144  }
145
146  @Override
147  public boolean isGroupDefinition(Entry entry)
148  {
149    ifNull(entry);
150
151    // FIXME -- This needs to exclude enhanced groups once we have support for them.
152    return entry.hasObjectClass(DirectoryServer.getSchema().getObjectClass(OC_GROUP_OF_URLS_LC));
153  }
154
155  @Override
156  public DN getGroupDN()
157  {
158    return groupEntryDN;
159  }
160
161  @Override
162  public void setGroupDN(DN groupDN)
163  {
164    groupEntryDN = groupDN;
165  }
166
167  /**
168   * Retrieves the set of member URLs for this dynamic group.  The returned set
169   * must not be altered by the caller.
170   *
171   * @return  The set of member URLs for this dynamic group.
172   */
173  public Set<LDAPURL> getMemberURLs()
174  {
175    return memberURLs;
176  }
177
178  @Override
179  public boolean supportsNestedGroups()
180  {
181    // Dynamic groups don't support nesting.
182    return false;
183  }
184
185  @Override
186  public List<DN> getNestedGroupDNs()
187  {
188    // Dynamic groups don't support nesting.
189    return Collections.<DN>emptyList();
190  }
191
192  @Override
193  public void addNestedGroup(DN nestedGroupDN)
194         throws UnsupportedOperationException, DirectoryException
195  {
196    // Dynamic groups don't support nesting.
197    LocalizableMessage message = ERR_DYNAMICGROUP_NESTING_NOT_SUPPORTED.get();
198    throw new UnsupportedOperationException(message.toString());
199  }
200
201  @Override
202  public void removeNestedGroup(DN nestedGroupDN)
203         throws UnsupportedOperationException, DirectoryException
204  {
205    // Dynamic groups don't support nesting.
206    LocalizableMessage message = ERR_DYNAMICGROUP_NESTING_NOT_SUPPORTED.get();
207    throw new UnsupportedOperationException(message.toString());
208  }
209
210  @Override
211  public boolean isMember(DN userDN, AtomicReference<Set<DN>> examinedGroups)
212         throws DirectoryException
213  {
214    Set<DN> groups = getExaminedGroups(examinedGroups);
215    if (! groups.add(getGroupDN()))
216    {
217      return false;
218    }
219
220    Entry entry = DirectoryConfig.getEntry(userDN);
221    return entry != null && isMember(entry);
222  }
223
224  @Override
225  public boolean isMember(Entry userEntry, AtomicReference<Set<DN>> examinedGroups)
226         throws DirectoryException
227  {
228    Set<DN> groups = getExaminedGroups(examinedGroups);
229    if (! groups.add(getGroupDN()))
230    {
231      return false;
232    }
233
234    for (LDAPURL memberURL : memberURLs)
235    {
236      if (memberURL.matchesEntry(userEntry))
237      {
238        return true;
239      }
240    }
241
242    return false;
243  }
244
245  private Set<DN> getExaminedGroups(AtomicReference<Set<DN>> examinedGroups)
246  {
247    Set<DN> groups = examinedGroups.get();
248    if (groups == null)
249    {
250      groups = new HashSet<DN>();
251      examinedGroups.set(groups);
252    }
253    return groups;
254  }
255
256  @Override
257  public MemberList getMembers()
258         throws DirectoryException
259  {
260    return new DynamicGroupMemberList(groupEntryDN, memberURLs);
261  }
262
263  @Override
264  public MemberList getMembers(DN baseDN, SearchScope scope,
265                               SearchFilter filter)
266         throws DirectoryException
267  {
268    if (baseDN == null && filter == null)
269    {
270      return new DynamicGroupMemberList(groupEntryDN, memberURLs);
271    }
272    else
273    {
274      return new DynamicGroupMemberList(groupEntryDN, memberURLs, baseDN, scope,
275                                        filter);
276    }
277  }
278
279  @Override
280  public boolean mayAlterMemberList()
281  {
282    return false;
283  }
284
285  @Override
286  public void updateMembers(List<Modification> modifications)
287         throws UnsupportedOperationException, DirectoryException
288  {
289    // Dynamic groups don't support altering the member list.
290    LocalizableMessage message = ERR_DYNAMICGROUP_ALTERING_MEMBERS_NOT_SUPPORTED.get();
291    throw new UnsupportedOperationException(message.toString());
292  }
293
294  @Override
295  public void addMember(Entry userEntry)
296         throws UnsupportedOperationException, DirectoryException
297  {
298    // Dynamic groups don't support altering the member list.
299    LocalizableMessage message = ERR_DYNAMICGROUP_ALTERING_MEMBERS_NOT_SUPPORTED.get();
300    throw new UnsupportedOperationException(message.toString());
301  }
302
303  @Override
304  public void removeMember(DN userDN)
305         throws UnsupportedOperationException, DirectoryException
306  {
307    // Dynamic groups don't support altering the member list.
308    LocalizableMessage message = ERR_DYNAMICGROUP_ALTERING_MEMBERS_NOT_SUPPORTED.get();
309    throw new UnsupportedOperationException(message.toString());
310  }
311
312  @Override
313  public void toString(StringBuilder buffer)
314  {
315    buffer.append("DynamicGroup(dn=");
316    buffer.append(groupEntryDN);
317    buffer.append(",urls={");
318
319    if (! memberURLs.isEmpty())
320    {
321      Iterator<LDAPURL> iterator = memberURLs.iterator();
322      buffer.append("\"");
323      iterator.next().toString(buffer, false);
324
325      while (iterator.hasNext())
326      {
327        buffer.append("\", ");
328        iterator.next().toString(buffer, false);
329      }
330
331      buffer.append("\"");
332    }
333
334    buffer.append("})");
335  }
336}