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