/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at * trunk/opends/resource/legal-notices/OpenDS.LICENSE * or https://OpenDS.dev.java.net/OpenDS.LICENSE. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable, * add the following below this CDDL HEADER, with the fields enclosed * by brackets "[]" replaced with your own identifying information: * Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END * * * Copyright 2008-2010 Sun Microsystems, Inc. * Portions Copyright 2011 ForgeRock AS */ package org.opends.server.extensions; import java.util.Collections; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; import org.opends.messages.Message; import org.opends.server.admin.std.server.GroupImplementationCfg; import org.opends.server.admin.std.server.StaticGroupImplementationCfg; import org.opends.server.api.Group; import org.opends.server.core.ModifyOperationBasis; import org.opends.server.core.DirectoryServer; import org.opends.server.config.ConfigException; import org.opends.server.loggers.ErrorLogger; import org.opends.server.loggers.debug.DebugTracer; import org.opends.server.protocols.internal.InternalClientConnection; import org.opends.server.protocols.ldap.LDAPControl; import org.opends.server.types.Attribute; import org.opends.server.types.AttributeType; import org.opends.server.types.AttributeValue; import org.opends.server.types.Attributes; import org.opends.server.types.ByteString; import org.opends.server.types.Control; import org.opends.server.types.DebugLogLevel; import org.opends.server.types.DirectoryConfig; import org.opends.server.types.DirectoryException; import org.opends.server.types.DN; import org.opends.server.types.Entry; import org.opends.server.types.InitializationException; import org.opends.server.types.MemberList; import org.opends.server.types.MembershipException; import org.opends.server.types.Modification; import org.opends.server.types.ModificationType; import org.opends.server.types.ObjectClass; import org.opends.server.types.ResultCode; import org.opends.server.types.SearchFilter; import org.opends.server.types.SearchScope; import static org.opends.messages.ExtensionMessages.*; import static org.opends.server.loggers.debug.DebugLogger.*; import static org.opends.server.protocols.internal.InternalClientConnection.*; import static org.opends.server.util.ServerConstants.*; import static org.opends.server.util.Validator.*; /** * This class provides a static group implementation, in which the DNs * of all members are explicitly listed. There are three variants of * static groups: two based on the {@code groupOfNames} and * {@code groupOfEntries} object classes, which store the member list in * the {@code member} attribute, and one based on the * {@code groupOfUniqueNames} object class, which stores the member list * in the {@code uniqueMember} attribute. */ public class StaticGroup extends Group<StaticGroupImplementationCfg> { /** * The tracer object for the debug logger. */ private static final DebugTracer TRACER = getTracer(); // The attribute type used to hold the membership list for this group. private AttributeType memberAttributeType; // The DN of the entry that holds the definition for this group. private DN groupEntryDN; // The set of the DNs of the members for this group. private LinkedHashSet<ByteString> memberDNs; //The list of nested group DNs for this group. private LinkedList<DN> nestedGroups = new LinkedList<DN>(); //Passed to the group manager to see if the nested group list needs to be //refreshed. private long nestedGroupRefreshToken = DirectoryServer.getGroupManager().refreshToken(); /** * Creates a new, uninitialized static group instance. This is intended for * internal use only. */ public StaticGroup() { super(); // No initialization is required here. } /** * Creates a new static group instance with the provided information. * * @param groupEntryDN The DN of the entry that holds the definition * for this group. * @param memberAttributeType The attribute type used to hold the membership * list for this group. * @param memberDNs The set of the DNs of the members for this * group. */ public StaticGroup(DN groupEntryDN, AttributeType memberAttributeType, LinkedHashSet<ByteString> memberDNs) { super(); ensureNotNull(groupEntryDN, memberAttributeType, memberDNs); this.groupEntryDN = groupEntryDN; this.memberAttributeType = memberAttributeType; this.memberDNs = memberDNs; } /** * {@inheritDoc} */ @Override() public void initializeGroupImplementation( StaticGroupImplementationCfg configuration) throws ConfigException, InitializationException { // No additional initialization is required. } /** * {@inheritDoc} */ @Override() public StaticGroup newInstance(Entry groupEntry) throws DirectoryException { ensureNotNull(groupEntry); // Determine whether it is a groupOfNames, groupOfEntries or // groupOfUniqueNames entry. If not, then that's a problem. AttributeType someMemberAttributeType; ObjectClass groupOfEntriesClass = DirectoryConfig.getObjectClass(OC_GROUP_OF_ENTRIES_LC, true); ObjectClass groupOfNamesClass = DirectoryConfig.getObjectClass(OC_GROUP_OF_NAMES_LC, true); ObjectClass groupOfUniqueNamesClass = DirectoryConfig.getObjectClass(OC_GROUP_OF_UNIQUE_NAMES_LC, true); if (groupEntry.hasObjectClass(groupOfEntriesClass)) { if (groupEntry.hasObjectClass(groupOfNamesClass)) { Message message = ERR_STATICGROUP_INVALID_OC_COMBINATION. get(String.valueOf(groupEntry.getDN()), OC_GROUP_OF_ENTRIES, OC_GROUP_OF_NAMES); throw new DirectoryException(ResultCode.OBJECTCLASS_VIOLATION, message); } else if (groupEntry.hasObjectClass(groupOfUniqueNamesClass)) { Message message = ERR_STATICGROUP_INVALID_OC_COMBINATION. get(String.valueOf(groupEntry.getDN()), OC_GROUP_OF_ENTRIES, OC_GROUP_OF_UNIQUE_NAMES); throw new DirectoryException(ResultCode.OBJECTCLASS_VIOLATION, message); } someMemberAttributeType = DirectoryConfig .getAttributeType(ATTR_MEMBER, true); } else if (groupEntry.hasObjectClass(groupOfNamesClass)) { if (groupEntry.hasObjectClass(groupOfUniqueNamesClass)) { Message message = ERR_STATICGROUP_INVALID_OC_COMBINATION. get(String.valueOf(groupEntry.getDN()), OC_GROUP_OF_NAMES, OC_GROUP_OF_UNIQUE_NAMES); throw new DirectoryException(ResultCode.OBJECTCLASS_VIOLATION, message); } someMemberAttributeType = DirectoryConfig .getAttributeType(ATTR_MEMBER, true); } else if (groupEntry.hasObjectClass(groupOfUniqueNamesClass)) { someMemberAttributeType = DirectoryConfig.getAttributeType(ATTR_UNIQUE_MEMBER_LC, true); } else { Message message = ERR_STATICGROUP_NO_VALID_OC. get(String.valueOf(groupEntry.getDN()), OC_GROUP_OF_NAMES, OC_GROUP_OF_UNIQUE_NAMES); throw new DirectoryException(ResultCode.OBJECTCLASS_VIOLATION, message); } List<Attribute> memberAttrList = groupEntry.getAttribute(someMemberAttributeType); int membersCount = 0; if (memberAttrList != null) { for (Attribute a : memberAttrList) { membersCount += a.size(); } } LinkedHashSet<ByteString> someMemberDNs = new LinkedHashSet<ByteString>(membersCount); if (memberAttrList != null) { for (Attribute a : memberAttrList) { for (AttributeValue v : a) { try { someMemberDNs.add(v.getNormalizedValue()); } catch (DirectoryException de) { if (debugEnabled()) { TRACER.debugCaught(DebugLogLevel.ERROR, de); } Message message = ERR_STATICGROUP_CANNOT_DECODE_MEMBER_VALUE_AS_DN. get(v.getValue().toString(), someMemberAttributeType.getNameOrOID(), String.valueOf(groupEntry.getDN()), de.getMessageObject()); ErrorLogger.logError(message); } } } } return new StaticGroup(groupEntry.getDN(), someMemberAttributeType, someMemberDNs); } /** * {@inheritDoc} */ @Override() public SearchFilter getGroupDefinitionFilter() throws DirectoryException { // FIXME -- This needs to exclude enhanced groups once we have support for // them. String filterString = "(&(|(objectClass=groupOfNames)(objectClass=groupOfUniqueNames)" + "(objectClass=groupOfEntries))" + "(!(objectClass=ds-virtual-static-group)))"; return SearchFilter.createFilterFromString(filterString); } /** * {@inheritDoc} */ @Override() public boolean isGroupDefinition(Entry entry) { ensureNotNull(entry); // FIXME -- This needs to exclude enhanced groups once we have support for //them. ObjectClass virtualStaticGroupClass = DirectoryConfig.getObjectClass(OC_VIRTUAL_STATIC_GROUP, true); if (entry.hasObjectClass(virtualStaticGroupClass)) { return false; } ObjectClass groupOfEntriesClass = DirectoryConfig.getObjectClass(OC_GROUP_OF_ENTRIES_LC, true); ObjectClass groupOfNamesClass = DirectoryConfig.getObjectClass(OC_GROUP_OF_NAMES_LC, true); ObjectClass groupOfUniqueNamesClass = DirectoryConfig.getObjectClass(OC_GROUP_OF_UNIQUE_NAMES_LC, true); if (entry.hasObjectClass(groupOfEntriesClass)) { if (entry.hasObjectClass(groupOfNamesClass) || entry.hasObjectClass(groupOfUniqueNamesClass)) { return false; } return true; } else if (entry.hasObjectClass(groupOfNamesClass)) { if (entry.hasObjectClass(groupOfUniqueNamesClass)) { return false; } return true; } else if (entry.hasObjectClass(groupOfUniqueNamesClass)) { return true; } else { return false; } } /** * {@inheritDoc} */ @Override() public DN getGroupDN() { return groupEntryDN; } /** * {@inheritDoc} */ @Override public void setGroupDN(DN groupDN) { groupEntryDN = groupDN; } /** * {@inheritDoc} */ @Override() public boolean supportsNestedGroups() { return true; } /** * {@inheritDoc} */ @Override() public List<DN> getNestedGroupDNs() { try { reloadIfNeeded(); } catch (DirectoryException ex) { return Collections.<DN>emptyList(); } return nestedGroups; } /** * {@inheritDoc} */ @Override() public void addNestedGroup(DN nestedGroupDN) throws UnsupportedOperationException, DirectoryException { ensureNotNull(nestedGroupDN); synchronized (this) { if (nestedGroups.contains(nestedGroupDN)) { Message msg = ERR_STATICGROUP_ADD_NESTED_GROUP_ALREADY_EXISTS.get( String.valueOf(nestedGroupDN), String.valueOf(groupEntryDN)); throw new DirectoryException( ResultCode.ATTRIBUTE_OR_VALUE_EXISTS, msg); } Attribute attr = Attributes.create(memberAttributeType, nestedGroupDN.toString()); LinkedList<Modification> mods = new LinkedList<Modification>(); mods.add(new Modification(ModificationType.ADD, attr)); LinkedList<Control> requestControls = new LinkedList<Control>(); requestControls.add(new LDAPControl(OID_INTERNAL_GROUP_MEMBERSHIP_UPDATE, false)); InternalClientConnection conn = InternalClientConnection.getRootConnection(); ModifyOperationBasis modifyOperation = new ModifyOperationBasis(conn, InternalClientConnection.nextOperationID(), InternalClientConnection.nextMessageID(), requestControls, groupEntryDN, mods); modifyOperation.run(); if (modifyOperation.getResultCode() != ResultCode.SUCCESS) { Message msg = ERR_STATICGROUP_ADD_MEMBER_UPDATE_FAILED.get( String.valueOf(nestedGroupDN), String.valueOf(groupEntryDN), modifyOperation.getErrorMessage().toString()); throw new DirectoryException(modifyOperation.getResultCode(), msg); } LinkedList<DN> newNestedGroups = new LinkedList<DN>(nestedGroups); newNestedGroups.add(nestedGroupDN); nestedGroups = newNestedGroups; //Add it to the member DN list. LinkedHashSet<ByteString> newMemberDNs = new LinkedHashSet<ByteString>(memberDNs); newMemberDNs.add(ByteString.valueOf(nestedGroupDN.toNormalizedString())); memberDNs = newMemberDNs; } } /** * {@inheritDoc} */ @Override() public void removeNestedGroup(DN nestedGroupDN) throws UnsupportedOperationException, DirectoryException { ensureNotNull(nestedGroupDN); synchronized (this) { if (! nestedGroups.contains(nestedGroupDN)) { throw new DirectoryException( ResultCode.NO_SUCH_ATTRIBUTE, ERR_STATICGROUP_REMOVE_NESTED_GROUP_NO_SUCH_GROUP.get( String.valueOf(nestedGroupDN), String.valueOf(groupEntryDN))); } Attribute attr = Attributes.create(memberAttributeType, nestedGroupDN.toString()); LinkedList<Modification> mods = new LinkedList<Modification>(); mods.add(new Modification(ModificationType.DELETE, attr)); LinkedList<Control> requestControls = new LinkedList<Control>(); requestControls.add(new LDAPControl(OID_INTERNAL_GROUP_MEMBERSHIP_UPDATE, false)); InternalClientConnection conn = InternalClientConnection.getRootConnection(); ModifyOperationBasis modifyOperation = new ModifyOperationBasis(conn, InternalClientConnection.nextOperationID(), InternalClientConnection.nextMessageID(), requestControls, groupEntryDN, mods); modifyOperation.run(); if (modifyOperation.getResultCode() != ResultCode.SUCCESS) { throw new DirectoryException( modifyOperation.getResultCode(), ERR_STATICGROUP_REMOVE_MEMBER_UPDATE_FAILED.get( String.valueOf(nestedGroupDN), String.valueOf(groupEntryDN), modifyOperation.getErrorMessage())); } LinkedList<DN> newNestedGroups = new LinkedList<DN>(nestedGroups); newNestedGroups.remove(nestedGroupDN); nestedGroups = newNestedGroups; //Remove it from the member DN list. LinkedHashSet<ByteString> newMemberDNs = new LinkedHashSet<ByteString>(memberDNs); newMemberDNs.remove( ByteString.valueOf(nestedGroupDN.toNormalizedString())); memberDNs = newMemberDNs; } } /** * {@inheritDoc} */ @Override() public boolean isMember(DN userDN, Set<DN> examinedGroups) throws DirectoryException { reloadIfNeeded(); ByteString userDNString = ByteString.valueOf(userDN.toNormalizedString()); if(memberDNs.contains(userDNString)) { return true; } else if (! examinedGroups.add(getGroupDN())) { return false; } else { for(DN nestedGroupDN : nestedGroups) { Group<? extends GroupImplementationCfg> g = (Group<? extends GroupImplementationCfg>) DirectoryServer.getGroupManager().getGroupInstance(nestedGroupDN); if((g != null) && (g.isMember(userDN, examinedGroups))) { return true; } } } return false; } /** * {@inheritDoc} */ @Override() public boolean isMember(Entry userEntry, Set<DN> examinedGroups) throws DirectoryException { return isMember(userEntry.getDN(), examinedGroups); } /** * Check if the group manager has registered a new group instance or removed a * a group instance that might impact this group's membership list. */ private void reloadIfNeeded() throws DirectoryException { //Check if group instances have changed by passing the group manager //the current token. if(DirectoryServer.getGroupManager(). hasInstancesChanged(nestedGroupRefreshToken)) { synchronized (this) { Group thisGroup = DirectoryServer.getGroupManager().getGroupInstance(groupEntryDN); //Check if the group itself has been removed if(thisGroup == null) { throw new DirectoryException( ResultCode.NO_SUCH_ATTRIBUTE, ERR_STATICGROUP_GROUP_INSTANCE_INVALID.get( String.valueOf(groupEntryDN))); } else if(thisGroup != this) { LinkedHashSet<ByteString> newMemberDNs = new LinkedHashSet<ByteString>(); MemberList memberList=thisGroup.getMembers(); while (memberList.hasMoreMembers()) { try { newMemberDNs.add(ByteString.valueOf( memberList.nextMemberDN().toNormalizedString())); } catch (MembershipException ex) {} } memberDNs=newMemberDNs; } LinkedList<DN> newNestedGroups = new LinkedList<DN>(); for(ByteString dnString : memberDNs) { DN dn = DN.decode(dnString); Group gr=DirectoryServer.getGroupManager().getGroupInstance(dn); if(gr != null) { newNestedGroups.add(gr.getGroupDN()); } } nestedGroupRefreshToken = DirectoryServer.getGroupManager().refreshToken(); nestedGroups=newNestedGroups; } } } /** * {@inheritDoc} */ @Override() public MemberList getMembers() throws DirectoryException { reloadIfNeeded(); return new SimpleStaticGroupMemberList(groupEntryDN, memberDNs); } /** * {@inheritDoc} */ @Override() public MemberList getMembers(DN baseDN, SearchScope scope, SearchFilter filter) throws DirectoryException { reloadIfNeeded(); if ((baseDN == null) && (filter == null)) { return new SimpleStaticGroupMemberList(groupEntryDN, memberDNs); } else { return new FilteredStaticGroupMemberList(groupEntryDN, memberDNs, baseDN, scope, filter); } } /** * {@inheritDoc} */ @Override() public boolean mayAlterMemberList() { return true; } /** * {@inheritDoc} */ @Override() public void addMember(Entry userEntry) throws UnsupportedOperationException, DirectoryException { ensureNotNull(userEntry); synchronized (this) { DN userDN = userEntry.getDN(); ByteString userDNString = ByteString.valueOf(userDN.toNormalizedString()); if (memberDNs.contains(userDNString)) { Message message = ERR_STATICGROUP_ADD_MEMBER_ALREADY_EXISTS.get( String.valueOf(userDN), String.valueOf(groupEntryDN)); throw new DirectoryException(ResultCode.ATTRIBUTE_OR_VALUE_EXISTS, message); } Attribute attr = Attributes.create(memberAttributeType, userDN .toString()); LinkedList<Modification> mods = new LinkedList<Modification>(); mods.add(new Modification(ModificationType.ADD, attr)); LinkedList<Control> requestControls = new LinkedList<Control>(); requestControls.add(new LDAPControl(OID_INTERNAL_GROUP_MEMBERSHIP_UPDATE, false)); InternalClientConnection conn = InternalClientConnection.getRootConnection(); ModifyOperationBasis modifyOperation = new ModifyOperationBasis(conn, nextOperationID(), nextMessageID(), requestControls, groupEntryDN, mods); modifyOperation.run(); if (modifyOperation.getResultCode() != ResultCode.SUCCESS) { Message message = ERR_STATICGROUP_ADD_MEMBER_UPDATE_FAILED. get(String.valueOf(userDN), String.valueOf(groupEntryDN), modifyOperation.getErrorMessage().toString()); throw new DirectoryException(modifyOperation.getResultCode(), message); } LinkedHashSet<ByteString> newMemberDNs = new LinkedHashSet<ByteString>(memberDNs.size()+1); newMemberDNs.addAll(memberDNs); newMemberDNs.add(userDNString); memberDNs = newMemberDNs; } } /** * {@inheritDoc} */ @Override() public void removeMember(DN userDN) throws UnsupportedOperationException, DirectoryException { ensureNotNull(userDN); ByteString userDNString = ByteString.valueOf(userDN.toNormalizedString()); synchronized (this) { if (! memberDNs.contains(userDNString)) { Message message = ERR_STATICGROUP_REMOVE_MEMBER_NO_SUCH_MEMBER.get( String.valueOf(userDN), String.valueOf(groupEntryDN)); throw new DirectoryException(ResultCode.NO_SUCH_ATTRIBUTE, message); } Attribute attr = Attributes.create(memberAttributeType, userDN .toString()); LinkedList<Modification> mods = new LinkedList<Modification>(); mods.add(new Modification(ModificationType.DELETE, attr)); LinkedList<Control> requestControls = new LinkedList<Control>(); requestControls.add(new LDAPControl(OID_INTERNAL_GROUP_MEMBERSHIP_UPDATE, false)); InternalClientConnection conn = InternalClientConnection.getRootConnection(); ModifyOperationBasis modifyOperation = new ModifyOperationBasis(conn, nextOperationID(), nextMessageID(), requestControls, groupEntryDN, mods); modifyOperation.run(); if (modifyOperation.getResultCode() != ResultCode.SUCCESS) { Message message = ERR_STATICGROUP_REMOVE_MEMBER_UPDATE_FAILED. get(String.valueOf(userDN), String.valueOf(groupEntryDN), modifyOperation.getErrorMessage().toString()); throw new DirectoryException(modifyOperation.getResultCode(), message); } LinkedHashSet<ByteString> newMemberDNs = new LinkedHashSet<ByteString>(memberDNs); newMemberDNs.remove(userDNString); memberDNs = newMemberDNs; //If it is in the nested group list remove it. if(nestedGroups.contains(userDN)) { LinkedList<DN> newNestedGroups = new LinkedList<DN>(nestedGroups); newNestedGroups.remove(userDN); nestedGroups = newNestedGroups; } } } /** * {@inheritDoc} */ @Override() public void toString(StringBuilder buffer) { buffer.append("StaticGroup("); buffer.append(groupEntryDN); buffer.append(")"); } }