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