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