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-2009 Sun Microsystems, Inc. 015 * Portions Copyright 2011-2016 ForgeRock AS. 016 */ 017package org.opends.server.extensions; 018 019import java.util.HashSet; 020import java.util.List; 021import java.util.Set; 022 023import org.forgerock.i18n.slf4j.LocalizedLogger; 024import org.forgerock.opendj.ldap.ByteString; 025import org.forgerock.opendj.ldap.ConditionResult; 026import org.forgerock.opendj.ldap.DN; 027import org.forgerock.opendj.ldap.SearchScope; 028import org.opends.server.admin.std.server.IsMemberOfVirtualAttributeCfg; 029import org.opends.server.api.Group; 030import org.opends.server.api.VirtualAttributeProvider; 031import org.opends.server.core.DirectoryServer; 032import org.opends.server.core.SearchOperation; 033import org.forgerock.opendj.ldap.schema.AttributeType; 034import org.opends.server.types.*; 035 036import static org.opends.server.util.ServerConstants.*; 037 038/** 039 * This class implements a virtual attribute provider that is meant to serve the 040 * isMemberOf operational attribute. This attribute will be used to provide a 041 * list of all groups in which the specified user is a member. 042 */ 043public class IsMemberOfVirtualAttributeProvider 044 extends VirtualAttributeProvider<IsMemberOfVirtualAttributeCfg> 045{ 046 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 047 048 /** 049 * Creates a new instance of this entryDN virtual attribute provider. 050 */ 051 public IsMemberOfVirtualAttributeProvider() 052 { 053 super(); 054 055 // All initialization should be performed in the 056 // initializeVirtualAttributeProvider method. 057 } 058 059 /** {@inheritDoc} */ 060 @Override 061 public boolean isMultiValued() 062 { 063 return true; 064 } 065 066 /** {@inheritDoc} */ 067 @Override 068 public Attribute getValues(Entry entry, VirtualAttributeRule rule) 069 { 070 // FIXME -- This probably isn't the most efficient implementation. 071 AttributeBuilder builder = new AttributeBuilder(rule.getAttributeType()); 072 for (Group<?> g : DirectoryServer.getGroupManager().getGroupInstances()) 073 { 074 try 075 { 076 if (g.isMember(entry)) 077 { 078 builder.add(g.getGroupDN().toString()); 079 } 080 } 081 catch (Exception e) 082 { 083 logger.traceException(e); 084 } 085 } 086 return builder.toAttribute(); 087 } 088 089 /** {@inheritDoc} */ 090 @Override 091 public boolean hasValue(Entry entry, VirtualAttributeRule rule) 092 { 093 // FIXME -- This probably isn't the most efficient implementation. 094 for (Group<?> g : DirectoryServer.getGroupManager().getGroupInstances()) 095 { 096 try 097 { 098 if (g.isMember(entry)) 099 { 100 return true; 101 } 102 } 103 catch (Exception e) 104 { 105 logger.traceException(e); 106 } 107 } 108 109 return false; 110 } 111 112 /** {@inheritDoc} */ 113 @Override 114 public boolean hasValue(Entry entry, VirtualAttributeRule rule, 115 ByteString value) 116 { 117 try 118 { 119 DN groupDN = DN.valueOf(value); 120 Group<?> g = DirectoryServer.getGroupManager().getGroupInstance(groupDN); 121 return g != null && g.isMember(entry); 122 } 123 catch (Exception e) 124 { 125 logger.traceException(e); 126 127 return false; 128 } 129 } 130 131 /** {@inheritDoc} */ 132 @Override 133 public ConditionResult matchesSubstring(Entry entry, 134 VirtualAttributeRule rule, 135 ByteString subInitial, 136 List<ByteString> subAny, 137 ByteString subFinal) 138 { 139 // DNs cannot be used in substring matching. 140 return ConditionResult.UNDEFINED; 141 } 142 143 /** {@inheritDoc} */ 144 @Override 145 public ConditionResult greaterThanOrEqualTo(Entry entry, 146 VirtualAttributeRule rule, 147 ByteString value) 148 { 149 // DNs cannot be used in ordering matching. 150 return ConditionResult.UNDEFINED; 151 } 152 153 /** {@inheritDoc} */ 154 @Override 155 public ConditionResult lessThanOrEqualTo(Entry entry, 156 VirtualAttributeRule rule, 157 ByteString value) 158 { 159 // DNs cannot be used in ordering matching. 160 return ConditionResult.UNDEFINED; 161 } 162 163 /** {@inheritDoc} */ 164 @Override 165 public ConditionResult approximatelyEqualTo(Entry entry, 166 VirtualAttributeRule rule, 167 ByteString value) 168 { 169 // DNs cannot be used in approximate matching. 170 return ConditionResult.UNDEFINED; 171 } 172 173 174 175 /** 176 * {@inheritDoc}. This virtual attribute will support search operations only 177 * if one of the following is true about the search filter: 178 * <UL> 179 * <LI>It is an equality filter targeting the associated attribute 180 * type.</LI> 181 * <LI>It is an AND filter in which at least one of the components is an 182 * equality filter targeting the associated attribute type.</LI> 183 * </UL> 184 * Searching for this virtual attribute cannot be pre-indexed and thus, 185 * it should not be searchable when pre-indexed is required. 186 */ 187 @Override 188 public boolean isSearchable(VirtualAttributeRule rule, 189 SearchOperation searchOperation, 190 boolean isPreIndexed) 191 { 192 return !isPreIndexed && 193 isSearchable(rule.getAttributeType(), searchOperation.getFilter(), 0); 194 } 195 196 197 198 199 /** 200 * Indicates whether the provided search filter is one that may be used with 201 * this virtual attribute provider, optionally operating in a recursive manner 202 * to make the determination. 203 * 204 * @param attributeType The attribute type used to hold the entryDN value. 205 * @param filter The search filter for which to make the 206 * determination. 207 * @param depth The current recursion depth for this processing. 208 * 209 * @return {@code true} if the provided filter may be used with this virtual 210 * attribute provider, or {@code false} if not. 211 */ 212 private boolean isSearchable(AttributeType attributeType, SearchFilter filter, 213 int depth) 214 { 215 switch (filter.getFilterType()) 216 { 217 case AND: 218 if (depth >= MAX_NESTED_FILTER_DEPTH) 219 { 220 return false; 221 } 222 223 for (SearchFilter f : filter.getFilterComponents()) 224 { 225 if (isSearchable(attributeType, f, depth+1)) 226 { 227 return true; 228 } 229 } 230 return false; 231 232 case EQUALITY: 233 return filter.getAttributeType().equals(attributeType); 234 235 default: 236 return false; 237 } 238 } 239 240 /** {@inheritDoc} */ 241 @Override 242 public void processSearch(VirtualAttributeRule rule, 243 SearchOperation searchOperation) 244 { 245 Group<?> group = extractGroup(rule.getAttributeType(), searchOperation.getFilter()); 246 if (group == null) 247 { 248 return; 249 } 250 251 try 252 { 253 // Check for nested groups to see if we need to keep track of returned entries 254 List<DN> nestedGroupsDNs = group.getNestedGroupDNs(); 255 Set<ByteString> returnedDNs = null; 256 if (!nestedGroupsDNs.isEmpty()) 257 { 258 returnedDNs = new HashSet<>(); 259 } 260 if (!returnGroupMembers(searchOperation, group.getMembers(), returnedDNs)) 261 { 262 return; 263 } 264 // Now check members of nested groups 265 for (DN dn : nestedGroupsDNs) 266 { 267 group = DirectoryServer.getGroupManager().getGroupInstance(dn); 268 if (!returnGroupMembers(searchOperation, group.getMembers(), returnedDNs)) 269 { 270 return; 271 } 272 } 273 } 274 catch (DirectoryException de) 275 { 276 searchOperation.setResponseData(de); 277 } 278 } 279 280 /** 281 * 282 * @param searchOperation the search operation being processed. 283 * @param memberList the list of members of the group being processed. 284 * @param returnedDNs a set to store the normalized DNs of entries already returned, 285 * null if there's no need to track for entries. 286 * @return <CODE>true</CODE> if the caller should continue processing the 287 * search request and sending additional entries and references, or 288 * <CODE>false</CODE> if not for some reason (e.g., the size limit 289 * has been reached or the search has been abandoned). 290 * @throws DirectoryException If a problem occurs while attempting to send 291 * the entry to the client and the search should be terminated. 292 */ 293 private boolean returnGroupMembers(SearchOperation searchOperation, 294 MemberList memberList, Set<ByteString> returnedDNs) 295 throws DirectoryException 296 { 297 DN baseDN = searchOperation.getBaseDN(); 298 SearchScope scope = searchOperation.getScope(); 299 SearchFilter filter = searchOperation.getFilter(); 300 while (memberList.hasMoreMembers()) 301 { 302 try 303 { 304 Entry e = memberList.nextMemberEntry(); 305 if (e.matchesBaseAndScope(baseDN, scope) 306 && filter.matchesEntry(e) 307 // The set of returned DNs is only used for detecting set membership 308 // so it's ok to use the irreversible representation of the DN 309 && (returnedDNs == null || returnedDNs.add(e.getName().toNormalizedByteString())) 310 && !searchOperation.returnEntry(e, null)) 311 { 312 return false; 313 } 314 } 315 catch (Exception e) 316 { 317 logger.traceException(e); 318 } 319 } 320 return true; 321 } 322 323 324 325 /** 326 * Extracts the first group DN encountered in the provided filter, operating 327 * recursively as necessary. 328 * 329 * @param attributeType The attribute type holding the entryDN value. 330 * @param filter The search filter to be processed. 331 * 332 * @return The first group encountered in the provided filter, or 333 * {@code null} if there is no match. 334 */ 335 private Group<?> extractGroup(AttributeType attributeType, 336 SearchFilter filter) 337 { 338 switch (filter.getFilterType()) 339 { 340 case AND: 341 for (SearchFilter f : filter.getFilterComponents()) 342 { 343 Group<?> g = extractGroup(attributeType, f); 344 if (g != null) 345 { 346 return g; 347 } 348 } 349 break; 350 351 case EQUALITY: 352 if (filter.getAttributeType().equals(attributeType)) 353 { 354 try 355 { 356 DN dn = DN.valueOf(filter.getAssertionValue()); 357 return DirectoryServer.getGroupManager().getGroupInstance(dn); 358 } 359 catch (Exception e) 360 { 361 logger.traceException(e); 362 } 363 } 364 break; 365 } 366 367 return null; 368 } 369} 370