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