/*
* 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/nasutekds/resource/legal-notices/NasuTekDS.LICENSE
* or https://NasuTekDS.dev.java.net/NasuTekDS.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/nasutekds/resource/legal-notices/NasuTekDS.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.
*/
package org.nasutekds.server.extensions;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import org.nasutekds.messages.Message;
import org.nasutekds.server.admin.std.server.GroupImplementationCfg;
import org.nasutekds.server.admin.std.server.StaticGroupImplementationCfg;
import org.nasutekds.server.api.Group;
import org.nasutekds.server.core.ModifyOperationBasis;
import org.nasutekds.server.core.DirectoryServer;
import org.nasutekds.server.config.ConfigException;
import org.nasutekds.server.loggers.ErrorLogger;
import org.nasutekds.server.loggers.debug.DebugTracer;
import org.nasutekds.server.protocols.internal.InternalClientConnection;
import org.nasutekds.server.protocols.ldap.LDAPControl;
import org.nasutekds.server.types.Attribute;
import org.nasutekds.server.types.AttributeType;
import org.nasutekds.server.types.AttributeValue;
import org.nasutekds.server.types.Attributes;
import org.nasutekds.server.types.Control;
import org.nasutekds.server.types.DebugLogLevel;
import org.nasutekds.server.types.DirectoryConfig;
import org.nasutekds.server.types.DirectoryException;
import org.nasutekds.server.types.DN;
import org.nasutekds.server.types.Entry;
import org.nasutekds.server.types.InitializationException;
import org.nasutekds.server.types.MemberList;
import org.nasutekds.server.types.MembershipException;
import org.nasutekds.server.types.Modification;
import org.nasutekds.server.types.ModificationType;
import org.nasutekds.server.types.ObjectClass;
import org.nasutekds.server.types.ResultCode;
import org.nasutekds.server.types.SearchFilter;
import org.nasutekds.server.types.SearchScope;
import static org.nasutekds.messages.ExtensionMessages.*;
import static org.nasutekds.server.loggers.debug.DebugLogger.*;
import static org.nasutekds.server.util.ServerConstants.*;
import static org.nasutekds.server.util.Validator.*;
/**
* This class provides a static group implementation, in which the DNs
* of all members are explicitly listed. There are two variants of
* static groups: one based on the {@code groupOfNames} object class,
* which stores 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<DN> 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<DN> 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 or groupOfUniqueNames entry. If
// neither, then that's a problem.
AttributeType memberAttributeType;
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);
}
memberAttributeType = 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);
}
memberAttributeType = DirectoryConfig.getAttributeType(ATTR_MEMBER, true);
}
else if (groupEntry.hasObjectClass(groupOfUniqueNamesClass))
{
memberAttributeType =
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);
}
LinkedHashSet<DN> memberDNs = new LinkedHashSet<DN>();
List<Attribute> memberAttrList =
groupEntry.getAttribute(memberAttributeType);
if (memberAttrList != null)
{
for (Attribute a : memberAttrList)
{
for (AttributeValue v : a)
{
try
{
DN memberDN = DN.decode(v.getValue());
memberDNs.add(memberDN);
}
catch (DirectoryException de)
{
if (debugEnabled())
{
TRACER.debugCaught(DebugLogLevel.ERROR, de);
}
Message message = ERR_STATICGROUP_CANNOT_DECODE_MEMBER_VALUE_AS_DN.
get(v.getValue().toString(), memberAttributeType.getNameOrOID(),
String.valueOf(groupEntry.getDN()), de.getMessageObject());
ErrorLogger.logError(message);
}
}
}
}
return new StaticGroup(groupEntry.getDN(), memberAttributeType, memberDNs);
}
/**
* {@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<DN> newMemberDNs = new LinkedHashSet<DN>(memberDNs);
newMemberDNs.add(nestedGroupDN);
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<DN> newMemberDNs = new LinkedHashSet<DN>(memberDNs);
newMemberDNs.remove(nestedGroupDN);
memberDNs = newMemberDNs;
}
}
/**
* {@inheritDoc}
*/
@Override()
public boolean isMember(DN userDN, Set<DN> examinedGroups)
throws DirectoryException
{
reloadIfNeeded();
if(memberDNs.contains(userDN))
{
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<DN> newMemberDNs = new LinkedHashSet<DN>();
MemberList memberList=thisGroup.getMembers();
while (memberList.hasMoreMembers()) {
try {
newMemberDNs.add(memberList.nextMemberDN());
} catch (MembershipException ex) {}
}
memberDNs=newMemberDNs;
}
LinkedList<DN> newNestedGroups = new LinkedList<DN>();
for(DN dn : memberDNs)
{
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();
if (memberDNs.contains(userDN))
{
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, conn.nextOperationID(),
conn.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<DN> newMemberDNs =
new LinkedHashSet<DN>(memberDNs.size()+1);
newMemberDNs.addAll(memberDNs);
newMemberDNs.add(userDN);
memberDNs = newMemberDNs;
}
}
/**
* {@inheritDoc}
*/
@Override()
public void removeMember(DN userDN)
throws UnsupportedOperationException, DirectoryException
{
ensureNotNull(userDN);
synchronized (this)
{
if (! memberDNs.contains(userDN))
{
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, conn.nextOperationID(),
conn.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<DN> newMemberDNs = new LinkedHashSet<DN>(memberDNs);
newMemberDNs.remove(userDN);
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(")");
}
}