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