/*
* 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
*
*
* Portions Copyright 2007 Sun Microsystems, Inc.
*/
package org.opends.server.workflowelement.localbackend;
import org.opends.messages.Message;
import org.opends.messages.MessageBuilder;
import static org.opends.server.config.ConfigConstants.*;
import static org.opends.messages.CoreMessages.*;
import static org.opends.server.util.ServerConstants.*;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
import org.opends.server.api.PasswordStorageScheme;
import org.opends.server.api.PasswordValidator;
import org.opends.server.controls.PasswordPolicyErrorType;
import org.opends.server.controls.PasswordPolicyResponseControl;
import org.opends.server.core.AddOperation;
import org.opends.server.core.AddOperationWrapper;
import org.opends.server.core.DirectoryServer;
import org.opends.server.core.PasswordPolicy;
import org.opends.server.protocols.asn1.ASN1OctetString;
import org.opends.server.schema.AuthPasswordSyntax;
import org.opends.server.schema.BooleanSyntax;
import org.opends.server.schema.UserPasswordSyntax;
import org.opends.server.types.Attribute;
import org.opends.server.types.AttributeType;
import org.opends.server.types.AttributeValue;
import org.opends.server.types.ByteString;
import org.opends.server.types.Control;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.Entry;
import org.opends.server.types.ObjectClass;
import org.opends.server.types.ResultCode;
import org.opends.server.types.operation.PostOperationAddOperation;
import org.opends.server.types.operation.PostResponseAddOperation;
import org.opends.server.types.operation.PreOperationAddOperation;
import org.opends.server.util.TimeThread;
/**
* This class defines an operation used to add an entry in a local backend
* of the Directory Server.
*/
public class LocalBackendAddOperation extends AddOperationWrapper
implements PreOperationAddOperation,
PostOperationAddOperation,
PostResponseAddOperation
{
// The entry being added to the server.
private Entry entry;
/**
* Creates a new operation that may be used to add a new entry in a
* local backend of the Directory Server.
*
* @param add The operation to enhance.
*/
public LocalBackendAddOperation(AddOperation add)
{
super(add);
LocalBackendWorkflowElement.attachLocalOperation (add, this);
}
/**
* Retrieves the entry to be added to the server. Note that this will not be
* available to pre-parse plugins or during the conflict resolution portion of
* the synchronization processing.
*
* @return The entry to be added to the server, or null if it is
* not yet available.
*/
public final Entry getEntryToAdd()
{
return entry;
}
/**
* Sets the entry to be added to the server.
*
* @param entry - The entry to be added to the server, or null
* if it is not yet available.
*/
public final void setEntryToAdd(Entry entry){
this.entry = entry;
}
/**
* Performs all password policy processing necessary for the provided add
* operation.
*
* @param passwordPolicy The password policy associated with the entry to be
* added.
* @param userEntry The user entry being added.
*
* @throws DirectoryException If a problem occurs while performing password
* policy processing for the add operation.
*/
public final void handlePasswordPolicy(PasswordPolicy passwordPolicy,
Entry userEntry)
throws DirectoryException
{
// See if a password was specified.
AttributeType passwordAttribute = passwordPolicy.getPasswordAttribute();
List attrList = userEntry.getAttribute(passwordAttribute);
if ((attrList == null) || attrList.isEmpty())
{
// The entry doesn't have a password, so no action is required.
return;
}
else if (attrList.size() > 1)
{
// This must mean there are attribute options, which we won't allow for
// passwords.
Message message = ERR_PWPOLICY_ATTRIBUTE_OPTIONS_NOT_ALLOWED.get(
passwordAttribute.getNameOrOID());
throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message);
}
Attribute passwordAttr = attrList.get(0);
if (passwordAttr.hasOptions())
{
Message message = ERR_PWPOLICY_ATTRIBUTE_OPTIONS_NOT_ALLOWED.get(
passwordAttribute.getNameOrOID());
throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message);
}
LinkedHashSet values = passwordAttr.getValues();
if (values.isEmpty())
{
// This will be treated the same as not having a password.
return;
}
if ((! passwordPolicy.allowMultiplePasswordValues()) && (values.size() > 1))
{
// FIXME -- What if they're pre-encoded and might all be the same?
addPWPolicyControl(PasswordPolicyErrorType.PASSWORD_MOD_NOT_ALLOWED);
Message message = ERR_PWPOLICY_MULTIPLE_PW_VALUES_NOT_ALLOWED.get(
passwordAttribute.getNameOrOID());
throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message);
}
CopyOnWriteArrayList defaultStorageSchemes =
passwordPolicy.getDefaultStorageSchemes();
LinkedHashSet newValues =
new LinkedHashSet(defaultStorageSchemes.size());
for (AttributeValue v : values)
{
ByteString value = v.getValue();
// See if the password is pre-encoded.
if (passwordPolicy.usesAuthPasswordSyntax())
{
if (AuthPasswordSyntax.isEncoded(value))
{
if (passwordPolicy.allowPreEncodedPasswords())
{
newValues.add(v);
continue;
}
else
{
addPWPolicyControl(
PasswordPolicyErrorType.INSUFFICIENT_PASSWORD_QUALITY);
Message message = ERR_PWPOLICY_PREENCODED_NOT_ALLOWED.get(
passwordAttribute.getNameOrOID());
throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
message);
}
}
}
else
{
if (UserPasswordSyntax.isEncoded(value))
{
if (passwordPolicy.allowPreEncodedPasswords())
{
newValues.add(v);
continue;
}
else
{
addPWPolicyControl(
PasswordPolicyErrorType.INSUFFICIENT_PASSWORD_QUALITY);
Message message = ERR_PWPOLICY_PREENCODED_NOT_ALLOWED.get(
passwordAttribute.getNameOrOID());
throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
message);
}
}
}
// See if the password passes validation. We should only do this if
// validation should be performed for administrators.
if (! passwordPolicy.skipValidationForAdministrators())
{
// There are never any current passwords for an add operation.
HashSet currentPasswords = new HashSet(0);
MessageBuilder invalidReason = new MessageBuilder();
for (PasswordValidator> validator :
passwordPolicy.getPasswordValidators().values())
{
if (! validator.passwordIsAcceptable(value, currentPasswords, this,
userEntry, invalidReason))
{
addPWPolicyControl(
PasswordPolicyErrorType.INSUFFICIENT_PASSWORD_QUALITY);
Message message = ERR_PWPOLICY_VALIDATION_FAILED.
get(passwordAttribute.getNameOrOID(),
String.valueOf(invalidReason));
throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
message);
}
}
}
// Encode the password.
if (passwordPolicy.usesAuthPasswordSyntax())
{
for (PasswordStorageScheme s : defaultStorageSchemes)
{
ByteString encodedValue = s.encodeAuthPassword(value);
newValues.add(new AttributeValue(passwordAttribute, encodedValue));
}
}
else
{
for (PasswordStorageScheme s : defaultStorageSchemes)
{
ByteString encodedValue = s.encodePasswordWithScheme(value);
newValues.add(new AttributeValue(passwordAttribute, encodedValue));
}
}
}
// Put the new encoded values in the entry.
passwordAttr.setValues(newValues);
// Set the password changed time attribute.
ByteString timeString =
new ASN1OctetString(TimeThread.getGeneralizedTime());
AttributeType changedTimeType =
DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_CHANGED_TIME_LC);
if (changedTimeType == null)
{
changedTimeType = DirectoryServer.getDefaultAttributeType(
OP_ATTR_PWPOLICY_CHANGED_TIME);
}
LinkedHashSet changedTimeValues =
new LinkedHashSet(1);
changedTimeValues.add(new AttributeValue(changedTimeType, timeString));
ArrayList changedTimeList = new ArrayList(1);
changedTimeList.add(new Attribute(changedTimeType,
OP_ATTR_PWPOLICY_CHANGED_TIME,
changedTimeValues));
userEntry.putAttribute(changedTimeType, changedTimeList);
// If we should force change on add, then set the appropriate flag.
if (passwordPolicy.forceChangeOnAdd())
{
addPWPolicyControl(PasswordPolicyErrorType.CHANGE_AFTER_RESET);
AttributeType resetType =
DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_RESET_REQUIRED_LC);
if (resetType == null)
{
resetType = DirectoryServer.getDefaultAttributeType(
OP_ATTR_PWPOLICY_RESET_REQUIRED);
}
LinkedHashSet resetValues = new
LinkedHashSet(1);
resetValues.add(BooleanSyntax.createBooleanValue(true));
ArrayList resetList = new ArrayList(1);
resetList.add(new Attribute(resetType, OP_ATTR_PWPOLICY_RESET_REQUIRED,
resetValues));
userEntry.putAttribute(resetType, resetList);
}
}
/**
* Adds a password policy response control if the corresponding request
* control was included.
*
* @param errorType The error type to use for the response control.
*/
private void addPWPolicyControl(PasswordPolicyErrorType errorType)
{
for (Control c : getRequestControls())
{
if (c.getOID().equals(OID_PASSWORD_POLICY_CONTROL))
{
addResponseControl(new PasswordPolicyResponseControl(null, 0,
errorType));
}
}
}
/**
* Adds the provided objectClass to the entry, along with its superior classes
* if appropriate.
*
* @param objectClass The objectclass to add to the entry.
*/
public final void addObjectClassChain(ObjectClass objectClass)
{
Map objectClasses = getObjectClasses();
if (objectClasses != null){
if (! objectClasses.containsKey(objectClass))
{
objectClasses.put(objectClass, objectClass.getNameOrOID());
}
ObjectClass superiorClass = objectClass.getSuperiorClass();
if ((superiorClass != null) &&
(! objectClasses.containsKey(superiorClass)))
{
addObjectClassChain(superiorClass);
}
}
}
}