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
010     * trunk/opends/resource/legal-notices/OpenDS.LICENSE
011     * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
012     * See the License for the specific language governing permissions
013     * and limitations under the License.
014     *
015     * When distributing Covered Code, include this CDDL HEADER in each
016     * file and include the License file at
017     * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
018     * add the following below this CDDL HEADER, with the fields enclosed
019     * by brackets "[]" replaced with your own identifying information:
020     *      Portions Copyright [yyyy] [name of copyright owner]
021     *
022     * CDDL HEADER END
023     *
024     *
025     *      Copyright 2008 Sun Microsystems, Inc.
026     */
027    package org.opends.server.extensions;
028    
029    
030    
031    import java.util.Collection;
032    import java.util.LinkedHashSet;
033    import java.util.List;
034    
035    import org.opends.server.admin.std.server.IsMemberOfVirtualAttributeCfg;
036    import org.opends.server.api.Group;
037    import org.opends.server.api.VirtualAttributeProvider;
038    import org.opends.server.config.ConfigException;
039    import org.opends.server.core.DirectoryServer;
040    import org.opends.server.core.SearchOperation;
041    import org.opends.server.loggers.debug.DebugTracer;
042    import org.opends.server.types.AttributeType;
043    import org.opends.server.types.AttributeValue;
044    import org.opends.server.types.ByteString;
045    import org.opends.server.types.ConditionResult;
046    import org.opends.server.types.DebugLogLevel;
047    import org.opends.server.types.DirectoryException;
048    import org.opends.server.types.DN;
049    import org.opends.server.types.Entry;
050    import org.opends.server.types.InitializationException;
051    import org.opends.server.types.MemberList;
052    import org.opends.server.types.SearchFilter;
053    import org.opends.server.types.SearchScope;
054    import org.opends.server.types.VirtualAttributeRule;
055    
056    import static org.opends.server.loggers.debug.DebugLogger.*;
057    import static org.opends.server.util.ServerConstants.*;
058    
059    
060    
061    /**
062     * This class implements a virtual attribute provider that is meant to serve the
063     * isMemberOf operational attribute.  This attribute will be used to provide a
064     * list of all groups in which the specified user is a member.
065     */
066    public class IsMemberOfVirtualAttributeProvider
067           extends VirtualAttributeProvider<IsMemberOfVirtualAttributeCfg>
068    {
069      /**
070       * The tracer object for the debug logger.
071       */
072      private static final DebugTracer TRACER = getTracer();
073    
074      /**
075       * Creates a new instance of this entryDN virtual attribute provider.
076       */
077      public IsMemberOfVirtualAttributeProvider()
078      {
079        super();
080    
081        // All initialization should be performed in the
082        // initializeVirtualAttributeProvider method.
083      }
084    
085    
086    
087      /**
088       * {@inheritDoc}
089       */
090      @Override()
091      public void initializeVirtualAttributeProvider(
092                                IsMemberOfVirtualAttributeCfg configuration)
093             throws ConfigException, InitializationException
094      {
095        // No initialization is required.
096      }
097    
098    
099    
100      /**
101       * {@inheritDoc}
102       */
103      @Override()
104      public boolean isMultiValued()
105      {
106        return true;
107      }
108    
109    
110    
111      /**
112       * {@inheritDoc}
113       */
114      @Override()
115      public LinkedHashSet<AttributeValue> getValues(Entry entry,
116                                                     VirtualAttributeRule rule)
117      {
118        // FIXME -- This probably isn't the most efficient implementation.
119        LinkedHashSet<AttributeValue> values = new LinkedHashSet<AttributeValue>();
120        for (Group g : DirectoryServer.getGroupManager().getGroupInstances())
121        {
122          try
123          {
124            if (g.isMember(entry))
125            {
126              values.add(new AttributeValue(rule.getAttributeType(),
127                                            g.getGroupDN().toString()));
128            }
129          }
130          catch (Exception e)
131          {
132            if (debugEnabled())
133            {
134              TRACER.debugCaught(DebugLogLevel.ERROR, e);
135            }
136          }
137        }
138    
139        return values;
140      }
141    
142    
143    
144      /**
145       * {@inheritDoc}
146       */
147      @Override()
148      public boolean hasValue(Entry entry, VirtualAttributeRule rule)
149      {
150        // FIXME -- This probably isn't the most efficient implementation.
151        for (Group g : DirectoryServer.getGroupManager().getGroupInstances())
152        {
153          try
154          {
155            if (g.isMember(entry))
156            {
157              return true;
158            }
159          }
160          catch (Exception e)
161          {
162            if (debugEnabled())
163            {
164              TRACER.debugCaught(DebugLogLevel.ERROR, e);
165            }
166          }
167        }
168    
169        return false;
170      }
171    
172    
173    
174      /**
175       * {@inheritDoc}
176       */
177      @Override()
178      public boolean hasValue(Entry entry, VirtualAttributeRule rule,
179                              AttributeValue value)
180      {
181        try
182        {
183          DN groupDN = DN.decode(value.getValue());
184          Group g = DirectoryServer.getGroupManager().getGroupInstance(groupDN);
185          if (g == null)
186          {
187            return false;
188          }
189          else
190          {
191            return g.isMember(entry);
192          }
193        }
194        catch (Exception e)
195        {
196          if (debugEnabled())
197          {
198            TRACER.debugCaught(DebugLogLevel.ERROR, e);
199          }
200    
201          return false;
202        }
203      }
204    
205    
206    
207      /**
208       * {@inheritDoc}
209       */
210      @Override()
211      public boolean hasAnyValue(Entry entry, VirtualAttributeRule rule,
212                                 Collection<AttributeValue> values)
213      {
214        for (AttributeValue value : values)
215        {
216          if (hasValue(entry, rule, value))
217          {
218            return true;
219          }
220        }
221    
222        return false;
223      }
224    
225    
226    
227      /**
228       * {@inheritDoc}
229       */
230      @Override()
231      public ConditionResult matchesSubstring(Entry entry,
232                                              VirtualAttributeRule rule,
233                                              ByteString subInitial,
234                                              List<ByteString> subAny,
235                                              ByteString subFinal)
236      {
237        // DNs cannot be used in substring matching.
238        return ConditionResult.UNDEFINED;
239      }
240    
241    
242    
243      /**
244       * {@inheritDoc}
245       */
246      @Override()
247      public ConditionResult greaterThanOrEqualTo(Entry entry,
248                                  VirtualAttributeRule rule,
249                                  AttributeValue value)
250      {
251        // DNs cannot be used in ordering matching.
252        return ConditionResult.UNDEFINED;
253      }
254    
255    
256    
257      /**
258       * {@inheritDoc}
259       */
260      @Override()
261      public ConditionResult lessThanOrEqualTo(Entry entry,
262                                  VirtualAttributeRule rule,
263                                  AttributeValue value)
264      {
265        // DNs cannot be used in ordering matching.
266        return ConditionResult.UNDEFINED;
267      }
268    
269    
270    
271      /**
272       * {@inheritDoc}
273       */
274      @Override()
275      public ConditionResult approximatelyEqualTo(Entry entry,
276                                  VirtualAttributeRule rule,
277                                  AttributeValue value)
278      {
279        // DNs cannot be used in approximate matching.
280        return ConditionResult.UNDEFINED;
281      }
282    
283    
284    
285      /**
286       * {@inheritDoc}.  This virtual attribute will support search operations only
287       * if one of the following is true about the search filter:
288       * <UL>
289       *   <LI>It is an equality filter targeting the associated attribute
290       *       type.</LI>
291       *   <LI>It is an AND filter in which at least one of the components is an
292       *       equality filter targeting the associated attribute type.</LI>
293       * </UL>
294       */
295      @Override()
296      public boolean isSearchable(VirtualAttributeRule rule,
297                                  SearchOperation searchOperation)
298      {
299        return isSearchable(rule.getAttributeType(), searchOperation.getFilter(),
300                            0);
301      }
302    
303    
304    
305    
306      /**
307       * Indicates whether the provided search filter is one that may be used with
308       * this virtual attribute provider, optionally operating in a recursive manner
309       * to make the determination.
310       *
311       * @param  attributeType  The attribute type used to hold the entryDN value.
312       * @param  searchFilter   The search filter for which to make the
313       *                        determination.
314       * @param  depth          The current recursion depth for this processing.
315       *
316       * @return  {@code true} if the provided filter may be used with this virtual
317       *          attribute provider, or {@code false} if not.
318       */
319      private boolean isSearchable(AttributeType attributeType, SearchFilter filter,
320                                   int depth)
321      {
322        switch (filter.getFilterType())
323        {
324          case AND:
325            if (depth >= MAX_NESTED_FILTER_DEPTH)
326            {
327              return false;
328            }
329    
330            for (SearchFilter f : filter.getFilterComponents())
331            {
332              if (isSearchable(attributeType, f, depth+1))
333              {
334                return true;
335              }
336            }
337            return false;
338    
339          case EQUALITY:
340            return filter.getAttributeType().equals(attributeType);
341    
342          default:
343            return false;
344        }
345      }
346    
347    
348    
349      /**
350       * {@inheritDoc}
351       */
352      @Override()
353      public void processSearch(VirtualAttributeRule rule,
354                                SearchOperation searchOperation)
355      {
356        SearchFilter filter = searchOperation.getFilter();
357        Group group = extractGroup(rule.getAttributeType(), filter);
358        if (group == null)
359        {
360          return;
361        }
362    
363        DN          baseDN = searchOperation.getBaseDN();
364        SearchScope scope  = searchOperation.getScope();
365        try
366        {
367          MemberList  memberList = group.getMembers();
368          while (memberList.hasMoreMembers())
369          {
370            try
371            {
372              Entry e = memberList.nextMemberEntry();
373              if (e.matchesBaseAndScope(baseDN, scope) &&
374                  filter.matchesEntry(e))
375              {
376                searchOperation.returnEntry(e, null);
377              }
378            }
379            catch (Exception e)
380            {
381              if (debugEnabled())
382              {
383                TRACER.debugCaught(DebugLogLevel.ERROR, e);
384              }
385            }
386          }
387        }
388        catch (DirectoryException de)
389        {
390          searchOperation.setResponseData(de);
391        }
392      }
393    
394    
395    
396      /**
397       * Extracts the first group DN encountered in the provided filter, operating
398       * recursively as necessary.
399       *
400       * @param  attributeType  The attribute type holding the entryDN value.
401       * @param  filter         The search filter to be processed.
402       *
403       * @return  The first group encountered in the provided filter, or
404       *          {@code null} if there is no match.
405       */
406      private Group extractGroup(AttributeType attributeType, SearchFilter filter)
407      {
408        switch (filter.getFilterType())
409        {
410          case AND:
411            for (SearchFilter f : filter.getFilterComponents())
412            {
413              Group g = extractGroup(attributeType, f);
414              if (g != null)
415              {
416                return g;
417              }
418            }
419            break;
420    
421          case EQUALITY:
422            if (filter.getAttributeType().equals(attributeType))
423            {
424              try
425              {
426                DN dn = DN.decode(filter.getAssertionValue().getValue());
427                return DirectoryServer.getGroupManager().getGroupInstance(dn);
428              }
429              catch (Exception e)
430              {
431                if (debugEnabled())
432                {
433                  TRACER.debugCaught(DebugLogLevel.ERROR, e);
434                }
435              }
436            }
437            break;
438        }
439    
440        return null;
441      }
442    }
443