2N/A/*
2N/A * CDDL HEADER START
2N/A *
2N/A * The contents of this file are subject to the terms of the
2N/A * Common Development and Distribution License, Version 1.0 only
2N/A * (the "License"). You may not use this file except in compliance
2N/A * with the License.
2N/A *
2N/A * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
2N/A * or http://forgerock.org/license/CDDLv1.0.html.
2N/A * See the License for the specific language governing permissions
2N/A * and limitations under the License.
2N/A *
2N/A * When distributing Covered Code, include this CDDL HEADER in each
2N/A * file and include the License file at legal-notices/CDDLv1_0.txt.
2N/A * If applicable, add the following below this CDDL HEADER, with the
2N/A * fields enclosed by brackets "[]" replaced with your own identifying
2N/A * information:
2N/A * Portions Copyright [yyyy] [name of copyright owner]
2N/A *
2N/A * CDDL HEADER END
2N/A *
2N/A *
2N/A * Copyright 2008-2009 Sun Microsystems, Inc.
2N/A * Portions Copyright 2013-2015 ForgeRock AS.
2N/A */
2N/Apackage org.opends.server.admin.client.ldap;
2N/A
2N/A
2N/A
2N/Aimport java.util.ArrayList;
2N/Aimport java.util.Collection;
2N/Aimport java.util.Collections;
2N/Aimport java.util.HashSet;
2N/Aimport java.util.LinkedList;
2N/Aimport java.util.List;
2N/Aimport java.util.Set;
2N/Aimport java.util.SortedSet;
2N/Aimport java.util.TreeSet;
2N/A
2N/Aimport javax.naming.NameNotFoundException;
2N/Aimport javax.naming.NamingEnumeration;
2N/Aimport javax.naming.NamingException;
2N/Aimport javax.naming.NoPermissionException;
2N/Aimport javax.naming.OperationNotSupportedException;
2N/Aimport javax.naming.directory.Attribute;
2N/Aimport javax.naming.directory.Attributes;
2N/Aimport javax.naming.ldap.LdapName;
2N/A
2N/Aimport org.forgerock.i18n.LocalizableMessage;
2N/Aimport org.opends.server.admin.AbstractManagedObjectDefinition;
2N/Aimport org.opends.server.admin.AggregationPropertyDefinition;
2N/Aimport org.opends.server.admin.Configuration;
2N/Aimport org.opends.server.admin.ConfigurationClient;
2N/Aimport org.opends.server.admin.PropertyException;
2N/Aimport org.opends.server.admin.DefinitionDecodingException;
2N/Aimport org.opends.server.admin.DefinitionResolver;
2N/Aimport org.opends.server.admin.InstantiableRelationDefinition;
2N/Aimport org.opends.server.admin.LDAPProfile;
2N/Aimport org.opends.server.admin.ManagedObjectDefinition;
2N/Aimport org.opends.server.admin.ManagedObjectNotFoundException;
2N/Aimport org.opends.server.admin.ManagedObjectPath;
2N/Aimport org.opends.server.admin.PropertyDefinition;
2N/Aimport org.opends.server.admin.PropertyDefinitionVisitor;
2N/Aimport org.opends.server.admin.PropertyOption;
2N/Aimport org.opends.server.admin.Reference;
2N/Aimport org.opends.server.admin.RelationDefinition;
2N/Aimport org.opends.server.admin.SetRelationDefinition;
2N/Aimport org.opends.server.admin.DefinitionDecodingException.Reason;
2N/Aimport org.opends.server.admin.client.AuthorizationException;
2N/Aimport org.opends.server.admin.client.CommunicationException;
2N/Aimport org.opends.server.admin.client.ManagedObject;
2N/Aimport org.opends.server.admin.client.ManagedObjectDecodingException;
2N/Aimport org.opends.server.admin.client.OperationRejectedException;
2N/Aimport org.opends.server.admin.client.OperationRejectedException.OperationType;
2N/Aimport org.opends.server.admin.client.spi.Driver;
2N/Aimport org.opends.server.admin.client.spi.PropertySet;
2N/Aimport org.opends.server.admin.std.client.RootCfgClient;
2N/Aimport org.opends.server.admin.std.meta.RootCfgDefn;
2N/A
2N/A
2N/A
2N/A/**
2N/A * The LDAP management context driver implementation.
2N/A */
2N/Afinal class LDAPDriver extends Driver {
2N/A
2N/A /**
2N/A * A visitor which is used to decode property LDAP values.
2N/A */
2N/A private static final class ValueDecoder extends
2N/A PropertyDefinitionVisitor<Object, String> {
2N/A
2N/A /**
2N/A * Decodes the provided property LDAP value.
2N/A *
2N/A * @param
2N/A * The type of the property.
2N/A * @param pd
2N/A * The property definition.
2N/A * @param value
2N/A * The LDAP string representation.
2N/A * @return Returns the decoded LDAP value.
2N/A * @throws PropertyException
2N/A * If the property value could not be decoded because it
2N/A * was invalid.
2N/A */
2N/A public static <PD> PD decode(PropertyDefinition<PD> pd, Object value)
2N/A throws PropertyException {
2N/A String s = String.valueOf(value);
2N/A return pd.castValue(pd.accept(new ValueDecoder(), s));
2N/A }
2N/A
2N/A
2N/A
2N/A /** Prevent instantiation. */
2N/A private ValueDecoder() {
2N/A // No implementation required.
2N/A }
2N/A
2N/A
2N/A
2N/A /** {@inheritDoc} */
2N/A @Override
2N/A public <C extends ConfigurationClient, S extends Configuration>
2N/A Object visitAggregation(AggregationPropertyDefinition<C, S> d, String p) {
2N/A // Aggregations values are stored as full DNs in LDAP, but
2N/A // just their common name is exposed in the admin framework.
2N/A try {
2N/A Reference<C, S> reference = Reference.parseDN(d.getParentPath(), d
2N/A .getRelationDefinition(), p);
2N/A return reference.getName();
2N/A } catch (IllegalArgumentException e) {
2N/A throw PropertyException.illegalPropertyValueException(d, p);
2N/A }
2N/A }
2N/A
2N/A
2N/A
2N/A /** {@inheritDoc} */
2N/A @Override
2N/A public <T> Object visitUnknown(PropertyDefinition<T> d, String p)
2N/A throws PropertyException {
2N/A // By default the property definition's decoder will do.
2N/A return d.decodeValue(p);
2N/A }
2N/A }
2N/A
2N/A
2N/A
2N/A /** The LDAP connection. */
2N/A private final LDAPConnection connection;
2N/A
2N/A /** The LDAP management context. */
2N/A private final LDAPManagementContext context;
2N/A
2N/A /**
2N/A * The LDAP profile which should be used to construct LDAP
2N/A * requests and decode LDAP responses.
2N/A */
2N/A private final LDAPProfile profile;
2N/A
2N/A
2N/A
2N/A /**
2N/A * Creates a new LDAP driver using the specified LDAP connection and
2N/A * profile.
2N/A *
2N/A * @param context
2N/A * The LDAP management context.
2N/A * @param connection
2N/A * The LDAP connection.
2N/A * @param profile
2N/A * The LDAP profile.
2N/A */
public LDAPDriver(LDAPManagementContext context, LDAPConnection connection,
LDAPProfile profile) {
this.context = context;
this.connection = connection;
this.profile = profile;
}
/** {@inheritDoc} */
@Override
public void close() {
connection.unbind();
}
/** {@inheritDoc} */
@Override
public <C extends ConfigurationClient, S extends Configuration>
ManagedObject<? extends C> getManagedObject(
ManagedObjectPath<C, S> path) throws DefinitionDecodingException,
ManagedObjectDecodingException, ManagedObjectNotFoundException,
AuthorizationException, CommunicationException {
if (!managedObjectExists(path)) {
throw new ManagedObjectNotFoundException();
}
try {
// Read the entry associated with the managed object.
LdapName dn = LDAPNameBuilder.create(path, profile);
AbstractManagedObjectDefinition<C, S> d = path
.getManagedObjectDefinition();
ManagedObjectDefinition<? extends C, ? extends S> mod =
getEntryDefinition(d, dn);
ArrayList<String> attrIds = new ArrayList<>();
for (PropertyDefinition<?> pd : mod.getAllPropertyDefinitions()) {
String attrId = profile.getAttributeName(mod, pd);
attrIds.add(attrId);
}
Attributes attributes = connection.readEntry(dn, attrIds);
// Build the managed object's properties.
List<PropertyException> exceptions = new LinkedList<>();
PropertySet newProperties = new PropertySet();
for (PropertyDefinition<?> pd : mod.getAllPropertyDefinitions()) {
String attrID = profile.getAttributeName(mod, pd);
Attribute attribute = attributes.get(attrID);
try {
decodeProperty(newProperties, path, pd, attribute);
} catch (PropertyException e) {
exceptions.add(e);
}
}
// If there were no decoding problems then return the object,
// otherwise throw an operations exception.
ManagedObject<? extends C> mo = createExistingManagedObject(mod, path,
newProperties);
if (exceptions.isEmpty()) {
return mo;
} else {
throw new ManagedObjectDecodingException(mo, exceptions);
}
} catch (NameNotFoundException e) {
throw new ManagedObjectNotFoundException();
} catch (NoPermissionException e) {
throw new AuthorizationException(e);
} catch (NamingException e) {
throw new CommunicationException(e);
}
}
/** {@inheritDoc} */
@SuppressWarnings("unchecked")
@Override
public <C extends ConfigurationClient, S extends Configuration, PD>
SortedSet<PD> getPropertyValues(ManagedObjectPath<C, S> path,
PropertyDefinition<PD> pd) throws IllegalArgumentException,
DefinitionDecodingException, AuthorizationException,
ManagedObjectNotFoundException, CommunicationException,
PropertyException {
// Check that the requested property is from the definition
// associated with the path.
AbstractManagedObjectDefinition<C, S> d = path.getManagedObjectDefinition();
PropertyDefinition<?> tmp = d.getPropertyDefinition(pd.getName());
if (tmp != pd) {
throw new IllegalArgumentException("The property " + pd.getName()
+ " is not associated with a " + d.getName());
}
if (!managedObjectExists(path)) {
throw new ManagedObjectNotFoundException();
}
try {
// Read the entry associated with the managed object.
LdapName dn = LDAPNameBuilder.create(path, profile);
ManagedObjectDefinition<? extends C, ? extends S> mod;
mod = getEntryDefinition(d, dn);
// Make sure we use the correct property definition, the
// provided one might have been overridden in the resolved
// definition.
pd = (PropertyDefinition<PD>) mod.getPropertyDefinition(pd.getName());
String attrID = profile.getAttributeName(mod, pd);
Attributes attributes = connection.readEntry(dn, Collections
.singleton(attrID));
Attribute attribute = attributes.get(attrID);
// Decode the values.
SortedSet<PD> values = new TreeSet<>(pd);
if (attribute != null) {
NamingEnumeration<?> ldapValues = attribute.getAll();
while (ldapValues.hasMore()) {
Object obj = ldapValues.next();
if (obj != null) {
PD value = ValueDecoder.decode(pd, obj);
values.add(value);
}
}
}
// Sanity check the returned values.
if (values.size() > 1 && !pd.hasOption(PropertyOption.MULTI_VALUED)) {
throw PropertyException.propertyIsSingleValuedException(pd);
}
if (values.isEmpty() && pd.hasOption(PropertyOption.MANDATORY)) {
throw PropertyException.propertyIsMandatoryException(pd);
}
if (values.isEmpty()) {
// Use the property's default values.
values.addAll(findDefaultValues(path.asSubType(mod), pd, false));
}
return values;
} catch (NameNotFoundException e) {
throw new ManagedObjectNotFoundException();
} catch (NoPermissionException e) {
throw new AuthorizationException(e);
} catch (NamingException e) {
throw new CommunicationException(e);
}
}
/** {@inheritDoc} */
@Override
public ManagedObject<RootCfgClient> getRootConfigurationManagedObject() {
return new LDAPManagedObject<>(this,
RootCfgDefn.getInstance(), ManagedObjectPath.emptyPath(),
new PropertySet(), true, null);
}
/** {@inheritDoc} */
@Override
public <C extends ConfigurationClient, S extends Configuration>
String[] listManagedObjects(
ManagedObjectPath<?, ?> parent, InstantiableRelationDefinition<C, S> rd,
AbstractManagedObjectDefinition<? extends C, ? extends S> d)
throws IllegalArgumentException, ManagedObjectNotFoundException,
AuthorizationException, CommunicationException {
validateRelationDefinition(parent, rd);
if (!managedObjectExists(parent)) {
throw new ManagedObjectNotFoundException();
}
// Get the search base DN.
LdapName dn = LDAPNameBuilder.create(parent, rd, profile);
// Retrieve only those entries which are sub-types of the
// specified definition.
StringBuilder builder = new StringBuilder();
builder.append("(objectclass=");
builder.append(profile.getObjectClass(d));
builder.append(')');
String filter = builder.toString();
List<String> children = new ArrayList<>();
try {
for (LdapName child : connection.listEntries(dn, filter)) {
children.add(child.getRdn(child.size() - 1).getValue().toString());
}
} catch (NameNotFoundException e) {
// Ignore this - it means that the base entry does not exist
// (which it might not if this managed object has just been
// created.
} catch (NamingException e) {
adaptNamingException(e);
}
return children.toArray(new String[children.size()]);
}
/** {@inheritDoc} */
@Override
public <C extends ConfigurationClient, S extends Configuration>
String[] listManagedObjects(
ManagedObjectPath<?, ?> parent, SetRelationDefinition<C, S> rd,
AbstractManagedObjectDefinition<? extends C, ? extends S> d)
throws IllegalArgumentException, ManagedObjectNotFoundException,
AuthorizationException, CommunicationException {
validateRelationDefinition(parent, rd);
if (!managedObjectExists(parent)) {
throw new ManagedObjectNotFoundException();
}
// Get the search base DN.
LdapName dn = LDAPNameBuilder.create(parent, rd, profile);
// Retrieve only those entries which are sub-types of the
// specified definition.
StringBuilder builder = new StringBuilder();
builder.append("(objectclass=");
builder.append(profile.getObjectClass(d));
builder.append(')');
String filter = builder.toString();
List<String> children = new ArrayList<>();
try {
for (LdapName child : connection.listEntries(dn, filter)) {
children.add(child.getRdn(child.size() - 1).getValue().toString());
}
} catch (NameNotFoundException e) {
// Ignore this - it means that the base entry does not exist
// (which it might not if this managed object has just been
// created.
} catch (NamingException e) {
adaptNamingException(e);
}
return children.toArray(new String[children.size()]);
}
/** {@inheritDoc} */
@Override
public boolean managedObjectExists(ManagedObjectPath<?, ?> path)
throws ManagedObjectNotFoundException, AuthorizationException,
CommunicationException {
if (path.isEmpty()) {
return true;
}
ManagedObjectPath<?, ?> parent = path.parent();
LdapName dn = LDAPNameBuilder.create(parent, profile);
if (!entryExists(dn)) {
throw new ManagedObjectNotFoundException();
}
dn = LDAPNameBuilder.create(path, profile);
return entryExists(dn);
}
/** {@inheritDoc} */
@Override
protected <C extends ConfigurationClient, S extends Configuration>
void deleteManagedObject(
ManagedObjectPath<C, S> path) throws OperationRejectedException,
AuthorizationException, CommunicationException {
// Delete the entry and any subordinate entries.
LdapName dn = LDAPNameBuilder.create(path, profile);
try {
connection.deleteSubtree(dn);
} catch (OperationNotSupportedException e) {
// Unwilling to perform.
AbstractManagedObjectDefinition<?, ?> d =
path.getManagedObjectDefinition();
if (e.getMessage() == null) {
throw new OperationRejectedException(OperationType.DELETE, d
.getUserFriendlyName());
} else {
LocalizableMessage m = LocalizableMessage.raw("%s", e.getMessage());
throw new OperationRejectedException(OperationType.DELETE, d
.getUserFriendlyName(), m);
}
} catch (NamingException e) {
adaptNamingException(e);
}
}
/** {@inheritDoc} */
@Override
protected LDAPManagementContext getManagementContext() {
return context;
}
/**
* Adapts a naming exception to an appropriate admin client
* exception.
*
* @param ne
* The naming exception.
* @throws CommunicationException
* If the naming exception mapped to a communication
* exception.
* @throws AuthorizationException
* If the naming exception mapped to an authorization
* exception.
*/
void adaptNamingException(NamingException ne) throws CommunicationException,
AuthorizationException {
try {
throw ne;
} catch (javax.naming.NoPermissionException e) {
throw new AuthorizationException(e);
} catch (NamingException e) {
// Just treat it as a communication problem.
throw new CommunicationException(e);
}
}
/**
* Determines whether the named LDAP entry exists.
*
* @param dn
* The LDAP entry name.
* @return Returns <code>true</code> if the named LDAP entry
* exists.
* @throws AuthorizationException
* If the server refuses to make the determination because
* the client does not have the correct privileges.
* @throws CommunicationException
* If the client cannot contact the server due to an
* underlying communication problem.
*/
boolean entryExists(LdapName dn) throws CommunicationException,
AuthorizationException {
try {
return connection.entryExists(dn);
} catch (NamingException e) {
adaptNamingException(e);
}
return false;
}
/**
* Gets the LDAP connection used for interacting with the server.
*
* @return Returns the LDAP connection used for interacting with the
* server.
*/
LDAPConnection getLDAPConnection() {
return connection;
}
/**
* Gets the LDAP profile which should be used to construct LDAP
* requests and decode LDAP responses.
*
* @return Returns the LDAP profile which should be used to
* construct LDAP requests and decode LDAP responses.
*/
LDAPProfile getLDAPProfile() {
return profile;
}
/** Create a managed object which already exists on the server. */
private <M extends ConfigurationClient, N extends Configuration>
ManagedObject<M> createExistingManagedObject(
ManagedObjectDefinition<M, N> d,
ManagedObjectPath<? super M, ? super N> p, PropertySet properties) {
RelationDefinition<?, ?> rd = p.getRelationDefinition();
PropertyDefinition<?> pd = null;
if (rd instanceof InstantiableRelationDefinition) {
InstantiableRelationDefinition<?, ?> ird =
(InstantiableRelationDefinition<?, ?>) rd;
pd = ird.getNamingPropertyDefinition();
}
return new LDAPManagedObject<>(this, d, p.asSubType(d), properties, true, pd);
}
/** Create a property using the provided string values. */
private <PD> void decodeProperty(PropertySet newProperties,
ManagedObjectPath<?, ?> p, PropertyDefinition<PD> pd,
Attribute attribute) throws PropertyException,
NamingException {
PropertyException exception = null;
// Get the property's active values.
SortedSet<PD> activeValues = new TreeSet<>(pd);
if (attribute != null) {
NamingEnumeration<?> ldapValues = attribute.getAll();
while (ldapValues.hasMore()) {
Object obj = ldapValues.next();
if (obj != null) {
PD value = ValueDecoder.decode(pd, obj);
activeValues.add(value);
}
}
}
if (activeValues.size() > 1 && !pd.hasOption(PropertyOption.MULTI_VALUED)) {
// This exception takes precedence over previous exceptions.
exception = PropertyException.propertyIsSingleValuedException(pd);
PD value = activeValues.first();
activeValues.clear();
activeValues.add(value);
}
// Get the property's default values.
Collection<PD> defaultValues;
try {
defaultValues = findDefaultValues(p, pd, false);
} catch (PropertyException e) {
defaultValues = Collections.emptySet();
exception = e;
}
newProperties.addProperty(pd, defaultValues, activeValues);
if (activeValues.isEmpty()
&& defaultValues.isEmpty()
&& pd.hasOption(PropertyOption.MANDATORY)
// The active values maybe empty because of a previous exception.
&& exception == null) {
exception = PropertyException.propertyIsMandatoryException(pd);
}
if (exception != null) {
throw exception;
}
}
/** Determine the type of managed object associated with the named entry. */
private <C extends ConfigurationClient, S extends Configuration>
ManagedObjectDefinition<? extends C, ? extends S> getEntryDefinition(
AbstractManagedObjectDefinition<C, S> d, LdapName dn)
throws NamingException, DefinitionDecodingException {
Attributes attributes = connection.readEntry(dn, Collections
.singleton("objectclass"));
Attribute oc = attributes.get("objectclass");
if (oc == null) {
// No object classes.
throw new DefinitionDecodingException(d, Reason.NO_TYPE_INFORMATION);
}
final Set<String> objectClasses = new HashSet<>();
NamingEnumeration<?> values = oc.getAll();
while (values.hasMore()) {
Object value = values.next();
if (value != null) {
objectClasses.add(value.toString().toLowerCase().trim());
}
}
if (objectClasses.isEmpty()) {
// No object classes.
throw new DefinitionDecodingException(d, Reason.NO_TYPE_INFORMATION);
}
// Resolve the appropriate sub-type based on the object classes.
DefinitionResolver resolver = new DefinitionResolver() {
public boolean matches(AbstractManagedObjectDefinition<?, ?> d) {
String objectClass = profile.getObjectClass(d);
return objectClasses.contains(objectClass);
}
};
return d.resolveManagedObjectDefinition(resolver);
}
}