From b4e7c2adbcf17bb3017ffc4aad6d395187bc4e59 Mon Sep 17 00:00:00 2001
From: neil_a_wilson <neil_a_wilson@localhost>
Date: Sun, 29 Jul 2007 20:58:29 +0000
Subject: [PATCH] Add support for a new disconnect client task that can be used to allow an administrator to terminate a client connection if the need arises. The requester must have the disconnect-client privilege. The task entry should contain the ds-task-disconnect object class, which requires the ds-task-disconnect-connection-id attribute type and optionally allows the ds-task-disconnect-notify-client and ds-task-disconnect-message attribute types.
---
opends/resource/schema/02-config.ldif | 15 +
opends/tests/unit-tests-testng/src/server/org/opends/server/tasks/DisconnectClientTaskTestCase.java | 271 +++++++++++++++++++
opends/src/server/org/opends/server/extensions/GetConnectionIDExtendedOperation.java | 139 +++++++++
opends/src/server/org/opends/server/messages/TaskMessages.java | 90 ++++++
opends/src/server/org/opends/server/tasks/DisconnectClientTask.java | 236 ++++++++++++++++
opends/resource/config/config.ldif | 7
opends/src/server/org/opends/server/util/ServerConstants.java | 35 ++
7 files changed, 793 insertions(+), 0 deletions(-)
diff --git a/opends/resource/config/config.ldif b/opends/resource/config/config.ldif
index a62d9ff..5998b95 100644
--- a/opends/resource/config/config.ldif
+++ b/opends/resource/config/config.ldif
@@ -405,6 +405,13 @@
ds-cfg-extended-operation-handler-class: org.opends.server.extensions.CancelExtendedOperation
ds-cfg-extended-operation-handler-enabled: true
+dn: cn=Get Connection ID,cn=Extended Operations,cn=config
+objectClass: top
+objectClass: ds-cfg-extended-operation-handler
+cn: Cancel
+ds-cfg-extended-operation-handler-class: org.opends.server.extensions.GetConnectionIDExtendedOperation
+ds-cfg-extended-operation-handler-enabled: true
+
dn: cn=Password Modify,cn=Extended Operations,cn=config
objectClass: top
objectClass: ds-cfg-extended-operation-handler
diff --git a/opends/resource/schema/02-config.ldif b/opends/resource/schema/02-config.ldif
index 174bc97..e67d9eb 100644
--- a/opends/resource/schema/02-config.ldif
+++ b/opends/resource/schema/02-config.ldif
@@ -1520,6 +1520,16 @@
NAME 'ds-task-import-clear-backend'
SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE
X-ORIGIN 'OpenDS Directory Server' )
+attributeTypes: ( 1.3.6.1.4.1.26027.1.1.453
+ NAME 'ds-task-disconnect-connection-id'
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE
+ X-ORIGIN 'OpenDS Directory Server' )
+attributeTypes: ( 1.3.6.1.4.1.26027.1.1.454 NAME 'ds-task-disconnect-message'
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE
+ X-ORIGIN 'OpenDS Directory Server' )
+attributeTypes: ( 1.3.6.1.4.1.26027.1.1.455
+ NAME 'ds-task-disconnect-notify-client' SYNTAX 1.3.6.1.4.1.1466.115.121.1.7
+ SINGLE-VALUE X-ORIGIN 'OpenDS Directory Server' )
objectClasses: ( 1.3.6.1.4.1.26027.1.2.1
NAME 'ds-cfg-access-control-handler' SUP top STRUCTURAL
MUST ( cn $ ds-cfg-acl-handler-class $ ds-cfg-acl-handler-enabled )
@@ -2138,4 +2148,9 @@
MUST ( ds-cfg-sender-address $ ds-cfg-recipient-address $
ds-cfg-message-subject $ ds-cfg-message-body )
X-ORIGIN 'OpenDS Directory Server' )
+objectClasses: ( 1.3.6.1.4.1.26027.1.2.119
+ NAME 'ds-task-disconnect' SUP ds-task STRUCTURAL
+ MUST ds-task-disconnect-connection-id
+ MAY ( ds-task-disconnect-message $ ds-task-disconnect-notify-client )
+ X-ORIGIN 'OpenDS Directory Server' )
diff --git a/opends/src/server/org/opends/server/extensions/GetConnectionIDExtendedOperation.java b/opends/src/server/org/opends/server/extensions/GetConnectionIDExtendedOperation.java
new file mode 100644
index 0000000..d1de649
--- /dev/null
+++ b/opends/src/server/org/opends/server/extensions/GetConnectionIDExtendedOperation.java
@@ -0,0 +1,139 @@
+/*
+ * 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.extensions;
+
+
+
+import org.opends.server.admin.std.server.ExtendedOperationHandlerCfg;
+import org.opends.server.api.ExtendedOperationHandler;
+import org.opends.server.config.ConfigException;
+import org.opends.server.core.DirectoryServer;
+import org.opends.server.core.ExtendedOperation;
+import org.opends.server.protocols.asn1.ASN1Exception;
+import org.opends.server.protocols.asn1.ASN1Long;
+import org.opends.server.protocols.asn1.ASN1OctetString;
+import org.opends.server.types.InitializationException;
+import org.opends.server.types.ResultCode;
+
+import static org.opends.server.messages.ExtensionsMessages.*;
+import static org.opends.server.messages.MessageHandler.*;
+import static org.opends.server.util.ServerConstants.*;
+
+
+/**
+ * This class implements the "Get Connection ID" extended operation that can be
+ * used to get the connection ID of the associated client connection.
+ */
+public class GetConnectionIDExtendedOperation
+ extends ExtendedOperationHandler<ExtendedOperationHandlerCfg>
+{
+ /**
+ * Create an instance of this "Get Connection ID" extended operation. All
+ * initialization should be performed in the
+ * {@code initializeExtendedOperationHandler} method.
+ */
+ public GetConnectionIDExtendedOperation()
+ {
+ super();
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public void initializeExtendedOperationHandler(
+ ExtendedOperationHandlerCfg config)
+ throws ConfigException, InitializationException
+ {
+ // No special configuration is required.
+
+ DirectoryServer.registerSupportedExtension(OID_GET_CONNECTION_ID_EXTOP,
+ this);
+
+ registerControlsAndFeatures();
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public void finalizeExtendedOperationHandler()
+ {
+ DirectoryServer.deregisterSupportedExtension(OID_WHO_AM_I_REQUEST);
+
+ deregisterControlsAndFeatures();
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public void processExtendedOperation(ExtendedOperation operation)
+ {
+ operation.setResponseOID(OID_GET_CONNECTION_ID_EXTOP);
+ operation.setResponseValue(
+ encodeResponseValue(operation.getConnectionID()));
+ operation.setResultCode(ResultCode.SUCCESS);
+ }
+
+
+
+ /**
+ * Encodes the provided connection ID in an octet string suitable for use as
+ * the value for this extended operation.
+ *
+ * @param connectionID The connection ID to be encoded.
+ *
+ * @return The ASN.1 octet string containing the encoded connection ID.
+ */
+ public static ASN1OctetString encodeResponseValue(long connectionID)
+ {
+ return new ASN1OctetString(new ASN1Long(connectionID).encode());
+ }
+
+
+
+ /**
+ * Decodes the provided ASN.1 octet string to extract the connection ID.
+ *
+ * @param responseValue The response value to be decoded.
+ *
+ * @return The connection ID decoded from the provided response value.
+ *
+ * @throws ASN1Exception If an error occurs while trying to decode the
+ * response value.
+ */
+ public static long decodeResponseValue(ASN1OctetString responseValue)
+ throws ASN1Exception
+ {
+ return ASN1Long.decodeAsLong(responseValue.value()).longValue();
+ }
+}
+
diff --git a/opends/src/server/org/opends/server/messages/TaskMessages.java b/opends/src/server/org/opends/server/messages/TaskMessages.java
index 9828f8c..364ee2d 100644
--- a/opends/src/server/org/opends/server/messages/TaskMessages.java
+++ b/opends/src/server/org/opends/server/messages/TaskMessages.java
@@ -274,6 +274,76 @@
/**
+ * The message ID for the message that will be used if the client does not
+ * have the DISCONNECT_CLIENT privilege. It does not take any arguments.
+ */
+ public static final int MSGID_TASK_DISCONNECT_NO_PRIVILEGE =
+ CATEGORY_MASK_TASK | SEVERITY_MASK_SEVERE_ERROR | 25;
+
+
+
+ /**
+ * The message ID for the message that will be used if the provided connection
+ * ID cannot be decoded. This takes a single argument, which is the invalid
+ * value.
+ */
+ public static final int MSGID_TASK_DISCONNECT_INVALID_CONN_ID =
+ CATEGORY_MASK_TASK | SEVERITY_MASK_SEVERE_ERROR | 26;
+
+
+
+ /**
+ * The message ID for the message that will be used if the task entry does
+ * not specify a target connection ID. This takes a single argument, which is
+ * the name of the attribute type used to specify the target connection ID.
+ */
+ public static final int MSGID_TASK_DISCONNECT_NO_CONN_ID =
+ CATEGORY_MASK_TASK | SEVERITY_MASK_SEVERE_ERROR | 27;
+
+
+
+ /**
+ * The message ID for the message that will be used if the notifyClient
+ * attribute value cannot be decoded. This takes a single argument, which is
+ * the invalid value.
+ */
+ public static final int MSGID_TASK_DISCONNECT_INVALID_NOTIFY_CLIENT =
+ CATEGORY_MASK_TASK | SEVERITY_MASK_SEVERE_ERROR | 28;
+
+
+
+ /**
+ * The message ID for the message that will be used as a generic message that
+ * may be sent to the client if no other value is given. It does not take
+ * any arguments.
+ */
+ public static final int MSGID_TASK_DISCONNECT_GENERIC_MESSAGE =
+ CATEGORY_MASK_TASK | SEVERITY_MASK_INFORMATIONAL | 29;
+
+
+
+ /**
+ * The message ID for the message that will be used if no client connection
+ * can be found with the specified connection ID. It takes a single argument,
+ * which is the target connection ID.
+ */
+ public static final int MSGID_TASK_DISCONNECT_NO_SUCH_CONNECTION =
+ CATEGORY_MASK_TASK | SEVERITY_MASK_SEVERE_ERROR | 30;
+
+
+
+ /**
+ * The message ID for the message that will be used as the message ID for the
+ * disconnectClient method. The associated message will be defined, but it
+ * will not actually be used, since only the message ID is needed. It does
+ * not take any arguments.
+ */
+ public static final int MSGID_TASK_DISCONNECT_MESSAGE =
+ CATEGORY_MASK_TASK | SEVERITY_MASK_INFORMATIONAL | 31;
+
+
+
+ /**
* Associates a set of generic messages with the message IDs defined in this
* class.
*/
@@ -362,6 +432,26 @@
registerMessage(MSGID_TASK_LEAVELOCKDOWN_NOT_LOOPBACK,
"Only root users connected from a loopback address may " +
"cause the server to leave lockdown mode");
+
+
+ registerMessage(MSGID_TASK_DISCONNECT_NO_PRIVILEGE,
+ "You do not have sufficient privileges to terminate " +
+ "client connections");
+ registerMessage(MSGID_TASK_DISCONNECT_INVALID_CONN_ID,
+ "Unable to decode value %s as an integer connection ID");
+ registerMessage(MSGID_TASK_DISCONNECT_NO_CONN_ID,
+ "Attribute %s must be provided to specify the connection " +
+ "ID for the client to disconnect");
+ registerMessage(MSGID_TASK_DISCONNECT_INVALID_NOTIFY_CLIENT,
+ "Unable to decode value %s as an indication of whether " +
+ "to notify the client before disconnecting it. The " +
+ "provided value should be either 'true' or 'false'");
+ registerMessage(MSGID_TASK_DISCONNECT_GENERIC_MESSAGE,
+ "An administrator has terminated this client connection");
+ registerMessage(MSGID_TASK_DISCONNECT_NO_SUCH_CONNECTION,
+ "There is no client connection with connection ID %s");
+ registerMessage(MSGID_TASK_DISCONNECT_MESSAGE,
+ "An administrator has terminated this client connection");
}
}
diff --git a/opends/src/server/org/opends/server/tasks/DisconnectClientTask.java b/opends/src/server/org/opends/server/tasks/DisconnectClientTask.java
new file mode 100644
index 0000000..adaf290
--- /dev/null
+++ b/opends/src/server/org/opends/server/tasks/DisconnectClientTask.java
@@ -0,0 +1,236 @@
+/*
+ * 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.tasks;
+
+
+
+import java.util.List;
+
+import org.opends.server.admin.std.server.ConnectionHandlerCfg;
+import org.opends.server.backends.task.Task;
+import org.opends.server.backends.task.TaskState;
+import org.opends.server.api.ClientConnection;
+import org.opends.server.api.ConnectionHandler;
+import org.opends.server.core.DirectoryServer;
+import org.opends.server.types.Attribute;
+import org.opends.server.types.AttributeType;
+import org.opends.server.types.AttributeValue;
+import org.opends.server.types.DirectoryException;
+import org.opends.server.types.DisconnectReason;
+import org.opends.server.types.Entry;
+import org.opends.server.types.ErrorLogCategory;
+import org.opends.server.types.ErrorLogSeverity;
+import org.opends.server.types.Operation;
+import org.opends.server.types.Privilege;
+import org.opends.server.types.ResultCode;
+
+import static org.opends.server.messages.MessageHandler.*;
+import static org.opends.server.messages.TaskMessages.*;
+import static org.opends.server.util.ServerConstants.*;
+import static org.opends.server.util.StaticUtils.*;
+
+
+
+/**
+ * This class provides an implementation of a Directory Server task that can be
+ * used to terminate a client connection.
+ */
+public class DisconnectClientTask
+ extends Task
+{
+ // Indicates whether to send a notification message to the client.
+ private boolean notifyClient;
+
+ // The connection ID for the client connection to terminate.
+ private long connectionID;
+
+ // The disconnect message to send to the client.
+ private String disconnectMessage;
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void initializeTask()
+ throws DirectoryException
+ {
+ // If the client connection is available, then make sure the client has the
+ // DISCONNECT_CLIENT privilege.
+ Operation operation = getOperation();
+ if (operation != null)
+ {
+ ClientConnection conn = operation.getClientConnection();
+ if (! conn.hasPrivilege(Privilege.DISCONNECT_CLIENT, operation))
+ {
+ int msgID = MSGID_TASK_DISCONNECT_NO_PRIVILEGE;
+ String message = getMessage(msgID);
+ throw new DirectoryException(ResultCode.INSUFFICIENT_ACCESS_RIGHTS,
+ message, msgID);
+ }
+ }
+
+
+ // Get the connection ID for the client connection.
+ Entry taskEntry = getTaskEntry();
+ connectionID = -1L;
+ AttributeType attrType =
+ DirectoryServer.getAttributeType(ATTR_TASK_DISCONNECT_CONN_ID, true);
+ List<Attribute> attrList = taskEntry.getAttribute(attrType);
+ if (attrList != null)
+ {
+connIDLoop:
+ for (Attribute a : attrList)
+ {
+ for (AttributeValue v : a.getValues())
+ {
+ try
+ {
+ connectionID = Long.parseLong(v.getStringValue());
+ break connIDLoop;
+ }
+ catch (Exception e)
+ {
+ int msgID = MSGID_TASK_DISCONNECT_INVALID_CONN_ID;
+ String message = getMessage(msgID, v.getStringValue());
+ throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
+ message, msgID, e);
+ }
+ }
+ }
+ }
+
+ if (connectionID < 0)
+ {
+ int msgID = MSGID_TASK_DISCONNECT_NO_CONN_ID;
+ String message = getMessage(msgID, ATTR_TASK_DISCONNECT_CONN_ID);
+ throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
+ message, msgID);
+ }
+
+
+ // Determine whether to notify the client.
+ notifyClient = false;
+ attrType =
+ DirectoryServer.getAttributeType(ATTR_TASK_DISCONNECT_NOTIFY_CLIENT,
+ true);
+ attrList = taskEntry.getAttribute(attrType);
+ if (attrList != null)
+ {
+notifyClientLoop:
+ for (Attribute a : attrList)
+ {
+ for (AttributeValue v : a.getValues())
+ {
+ String stringValue = toLowerCase(v.getStringValue());
+ if (stringValue.equals("true"))
+ {
+ notifyClient = true;
+ break notifyClientLoop;
+ }
+ else if (stringValue.equals("false"))
+ {
+ break notifyClientLoop;
+ }
+ else
+ {
+ int msgID = MSGID_TASK_DISCONNECT_INVALID_NOTIFY_CLIENT;
+ String message = getMessage(msgID, stringValue);
+ throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
+ message, msgID);
+ }
+ }
+ }
+ }
+
+
+ // Get the disconnect message.
+ disconnectMessage = getMessage(MSGID_TASK_DISCONNECT_GENERIC_MESSAGE);
+ attrType = DirectoryServer.getAttributeType(ATTR_TASK_DISCONNECT_MESSAGE,
+ true);
+ attrList = taskEntry.getAttribute(attrType);
+ if (attrList != null)
+ {
+disconnectMessageLoop:
+ for (Attribute a : attrList)
+ {
+ for (AttributeValue v : a.getValues())
+ {
+ disconnectMessage = v.getStringValue();
+ break disconnectMessageLoop;
+ }
+ }
+ }
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ protected TaskState runTask()
+ {
+ // Get the specified client connection.
+ ClientConnection clientConnection = null;
+ for (ConnectionHandler handler : DirectoryServer.getConnectionHandlers())
+ {
+ ConnectionHandler<? extends ConnectionHandlerCfg> connHandler =
+ (ConnectionHandler<? extends ConnectionHandlerCfg>) handler;
+ for (ClientConnection c : connHandler.getClientConnections())
+ {
+ if (c.getConnectionID() == connectionID)
+ {
+ clientConnection = c;
+ break;
+ }
+ }
+ }
+
+
+ // If there is no such client connection, then return an error. Otherwise,
+ // terminate it.
+ if (clientConnection == null)
+ {
+ int msgID = MSGID_TASK_DISCONNECT_NO_SUCH_CONNECTION;
+ String message = getMessage(msgID, connectionID);
+
+ logError(ErrorLogCategory.TASK, ErrorLogSeverity.SEVERE_ERROR, message,
+ msgID);
+
+ return TaskState.COMPLETED_WITH_ERRORS;
+ }
+ else
+ {
+ clientConnection.disconnect(DisconnectReason.ADMIN_DISCONNECT,
+ notifyClient, disconnectMessage,
+ MSGID_TASK_DISCONNECT_MESSAGE);
+ return TaskState.COMPLETED_SUCCESSFULLY;
+ }
+ }
+}
+
diff --git a/opends/src/server/org/opends/server/util/ServerConstants.java b/opends/src/server/org/opends/server/util/ServerConstants.java
index dd213c4..6a54295 100644
--- a/opends/src/server/org/opends/server/util/ServerConstants.java
+++ b/opends/src/server/org/opends/server/util/ServerConstants.java
@@ -498,6 +498,32 @@
/**
+ * The name of the attribute that is used to specify the connection ID of the
+ * connection to disconnect.
+ */
+ public static final String ATTR_TASK_DISCONNECT_CONN_ID =
+ "ds-task-disconnect-connection-id";
+
+
+
+ /**
+ * The name of the attribute that is used to specify the disconnect message.
+ */
+ public static final String ATTR_TASK_DISCONNECT_MESSAGE =
+ "ds-task-disconnect-message";
+
+
+
+ /**
+ * The name of the attribute that is used to indicate whether to notify the
+ * connection it is about to be terminated.
+ */
+ public static final String ATTR_TASK_DISCONNECT_NOTIFY_CLIENT =
+ "ds-task-disconnect-notify-client";
+
+
+
+ /**
* The name of the attribute that is used to specify the total number of
* connections established since startup, formatted in camel case.
*/
@@ -687,6 +713,15 @@
/**
+ * The OID for the extended operation that can be used to get the client
+ * connection ID. It will be both the request and response OID.
+ */
+ public static final String OID_GET_CONNECTION_ID_EXTOP =
+ "1.3.6.1.4.1.26027.1.6.2";
+
+
+
+ /**
* The request OID for the password modify extended operation.
*/
public static final String OID_PASSWORD_MODIFY_REQUEST =
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/tasks/DisconnectClientTaskTestCase.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/tasks/DisconnectClientTaskTestCase.java
new file mode 100644
index 0000000..1efb90b
--- /dev/null
+++ b/opends/tests/unit-tests-testng/src/server/org/opends/server/tasks/DisconnectClientTaskTestCase.java
@@ -0,0 +1,271 @@
+/*
+ * 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.tasks;
+
+
+
+import java.net.Socket;
+
+import org.testng.annotations.Test;
+import org.testng.annotations.BeforeClass;
+
+import org.opends.server.TestCaseUtils;
+import org.opends.server.backends.task.Task;
+import org.opends.server.backends.task.TaskBackend;
+import org.opends.server.backends.task.TaskState;
+import org.opends.server.core.DirectoryServer;
+import org.opends.server.extensions.GetConnectionIDExtendedOperation;
+import org.opends.server.protocols.asn1.*;
+import org.opends.server.protocols.ldap.*;
+import org.opends.server.types.DN;
+
+import static org.testng.Assert.*;
+
+import static org.opends.server.util.ServerConstants.*;
+
+
+
+/**
+ * Tests the disconnect client task.
+ */
+public class DisconnectClientTaskTestCase
+ extends TasksTestCase
+{
+ /**
+ * Make sure that the Directory Server is running.
+ *
+ * @throws Exception If an unexpected problem occurs.
+ */
+ @BeforeClass()
+ public void startServer()
+ throws Exception
+ {
+ TestCaseUtils.startServer();
+ }
+
+
+
+ /**
+ * Tests the ability of the server to disconnect an arbitrary client
+ * connection with a notice of disconnection.
+ *
+ * @throws Exception If an unexpected problem occurs.
+ */
+ @Test()
+ public void testDisconnectWithNotification()
+ throws Exception
+ {
+ // Establish a connection to the server, bind, and get the connection ID.
+ Socket s = new Socket("127.0.0.1", TestCaseUtils.getServerLdapPort());
+ ASN1Reader r = new ASN1Reader(s);
+ ASN1Writer w = new ASN1Writer(s);
+
+ BindRequestProtocolOp bindRequest =
+ new BindRequestProtocolOp(new ASN1OctetString("cn=Directory Manager"),
+ 3, new ASN1OctetString("password"));
+ LDAPMessage message = new LDAPMessage(1, bindRequest);
+ w.writeElement(message.encode());
+
+ message = LDAPMessage.decode(r.readElement().decodeAsSequence());
+ BindResponseProtocolOp bindResponse = message.getBindResponseProtocolOp();
+ assertEquals(bindResponse.getResultCode(), LDAPResultCode.SUCCESS);
+
+
+ ExtendedRequestProtocolOp extendedRequest =
+ new ExtendedRequestProtocolOp(OID_GET_CONNECTION_ID_EXTOP);
+ message = new LDAPMessage(2, extendedRequest);
+ w.writeElement(message.encode());
+
+ message = LDAPMessage.decode(r.readElement().decodeAsSequence());
+ ExtendedResponseProtocolOp extendedResponse =
+ message.getExtendedResponseProtocolOp();
+ assertEquals(extendedResponse.getResultCode(), LDAPResultCode.SUCCESS);
+ assertEquals(extendedResponse.getOID(), OID_GET_CONNECTION_ID_EXTOP);
+ long connectionID = GetConnectionIDExtendedOperation.decodeResponseValue(
+ extendedResponse.getValue());
+
+
+ // Invoke the disconnect client task.
+ String taskID = "Disconnect Client " + connectionID;
+ String disconnectMessage = "testDisconnectWithNotification";
+ DN taskDN = DN.decode("ds-task-id=" + taskID +
+ ",cn=Scheduled Tasks,cn=Tasks");
+ TestCaseUtils.addEntry(
+ "dn: " + taskDN.toString(),
+ "objectClass: top",
+ "objectClass: ds-task",
+ "objectClass: ds-task-disconnect",
+ "ds-task-id: " + taskID,
+ "ds-task-class-name: org.opends.server.tasks.DisconnectClientTask",
+ "ds-task-disconnect-connection-id: " + connectionID,
+ "ds-task-disconnect-notify-client: true",
+ "ds-task-disconnect-message: " + disconnectMessage);
+
+ Task task = getCompletedTask(taskDN);
+ assertNotNull(task);
+ assertEquals(task.getTaskState(), TaskState.COMPLETED_SUCCESSFULLY);
+
+
+ // Make sure that we get a notice of disconnection on the initial
+ // connection.
+ message = LDAPMessage.decode(r.readElement().decodeAsSequence());
+ extendedResponse = message.getExtendedResponseProtocolOp();
+ assertEquals(extendedResponse.getOID(),
+ LDAPConstants.OID_NOTICE_OF_DISCONNECTION);
+ assertEquals(extendedResponse.getErrorMessage(), disconnectMessage);
+
+ try
+ {
+ s.close();
+ } catch (Exception e) {}
+ }
+
+
+
+ /**
+ * Tests the ability of the server to disconnect an arbitrary client
+ * connection without a notice of disconnection.
+ *
+ * @throws Exception If an unexpected problem occurs.
+ */
+ @Test()
+ public void testDisconnectWithoutNotification()
+ throws Exception
+ {
+ // Establish a connection to the server, bind, and get the connection ID.
+ Socket s = new Socket("127.0.0.1", TestCaseUtils.getServerLdapPort());
+ ASN1Reader r = new ASN1Reader(s);
+ ASN1Writer w = new ASN1Writer(s);
+
+ BindRequestProtocolOp bindRequest =
+ new BindRequestProtocolOp(new ASN1OctetString("cn=Directory Manager"),
+ 3, new ASN1OctetString("password"));
+ LDAPMessage message = new LDAPMessage(1, bindRequest);
+ w.writeElement(message.encode());
+
+ message = LDAPMessage.decode(r.readElement().decodeAsSequence());
+ BindResponseProtocolOp bindResponse = message.getBindResponseProtocolOp();
+ assertEquals(bindResponse.getResultCode(), LDAPResultCode.SUCCESS);
+
+
+ ExtendedRequestProtocolOp extendedRequest =
+ new ExtendedRequestProtocolOp(OID_GET_CONNECTION_ID_EXTOP);
+ message = new LDAPMessage(2, extendedRequest);
+ w.writeElement(message.encode());
+
+ message = LDAPMessage.decode(r.readElement().decodeAsSequence());
+ ExtendedResponseProtocolOp extendedResponse =
+ message.getExtendedResponseProtocolOp();
+ assertEquals(extendedResponse.getResultCode(), LDAPResultCode.SUCCESS);
+ assertEquals(extendedResponse.getOID(), OID_GET_CONNECTION_ID_EXTOP);
+ long connectionID = GetConnectionIDExtendedOperation.decodeResponseValue(
+ extendedResponse.getValue());
+
+
+ // Invoke the disconnect client task.
+ String taskID = "Disconnect Client " + connectionID;
+ DN taskDN = DN.decode("ds-task-id=" + taskID +
+ ",cn=Scheduled Tasks,cn=Tasks");
+ TestCaseUtils.addEntry(
+ "dn: " + taskDN.toString(),
+ "objectClass: top",
+ "objectClass: ds-task",
+ "objectClass: ds-task-disconnect",
+ "ds-task-id: " + taskID,
+ "ds-task-class-name: org.opends.server.tasks.DisconnectClientTask",
+ "ds-task-disconnect-connection-id: " + connectionID,
+ "ds-task-disconnect-notify-client: false");
+
+ Task task = getCompletedTask(taskDN);
+ assertNotNull(task);
+ assertEquals(task.getTaskState(), TaskState.COMPLETED_SUCCESSFULLY);
+
+
+ // Make sure that the client connection has been closed with no notice of
+ // disconnection.
+ assertNull(r.readElement());
+
+ try
+ {
+ s.close();
+ } catch (Exception e) {}
+ }
+
+
+
+ /**
+ * Retrieves the specified task from the server, waiting for it to finish all
+ * the running its going to do before returning.
+ *
+ * @param taskEntryDN The DN of the entry for the task to retrieve.
+ *
+ * @return The requested task entry.
+ *
+ * @throws Exception If an unexpected problem occurs.
+ */
+ private Task getCompletedTask(DN taskEntryDN)
+ throws Exception
+ {
+ TaskBackend taskBackend =
+ (TaskBackend) DirectoryServer.getBackend(DN.decode("cn=tasks"));
+ Task task = taskBackend.getScheduledTask(taskEntryDN);
+ if (task == null)
+ {
+ long stopWaitingTime = System.currentTimeMillis() + 10000L;
+ while ((task == null) && (System.currentTimeMillis() < stopWaitingTime))
+ {
+ Thread.sleep(10);
+ task = taskBackend.getScheduledTask(taskEntryDN);
+ }
+ }
+
+ if (task == null)
+ {
+ throw new AssertionError("There is no such task " +
+ taskEntryDN.toString());
+ }
+
+ if (! TaskState.isDone(task.getTaskState()))
+ {
+ long stopWaitingTime = System.currentTimeMillis() + 20000L;
+ while ((! TaskState.isDone(task.getTaskState())) &&
+ (System.currentTimeMillis() < stopWaitingTime))
+ {
+ Thread.sleep(10);
+ }
+ }
+
+ if (! TaskState.isDone(task.getTaskState()))
+ {
+ throw new AssertionError("Task " + taskEntryDN.toString() +
+ " did not complete in a timely manner.");
+ }
+
+ return task;
+ }
+}
+
--
Gitblit v1.10.0