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