From 34646aad9cee63b831432540d446ad2ee453a45d Mon Sep 17 00:00:00 2001
From: lutoff <lutoff@localhost>
Date: Thu, 12 Jul 2007 08:18:55 +0000
Subject: [PATCH] fix for issue #1217 Privilege checks are done in the JmxClientConnection code. Due to JMX design choice (See chapter 13.4.3,page 210 of the JMX Specification, version 1.4 Final Release - http://jcp.org/en/jsr/detail?id=160) JMX_NOTIFY privilege cannot be checked when a remote client adds a Listener. For this reason, we have chosen to allow JMX connection only if the user has the JMX_READ privilege (at least). The JMX_READ privilege is now also check during connection establishment.
---
opendj-sdk/opends/src/server/org/opends/server/protocols/jmx/JmxClientConnection.java | 228 +++++
opendj-sdk/opends/src/server/org/opends/server/protocols/jmx/RmiAuthenticator.java | 20
opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/jmx/JmxPrivilegeTestCase.java | 1692 ++++++++++++++++++++++++++++++++++++++++++++
opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/types/PrivilegeTestCase.java | 32
opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/jmx/JmxConnectTest.java | 127 ++
opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/jmx/postConnectedDisconnectTest.java | 66 +
opendj-sdk/opends/src/server/org/opends/server/messages/ProtocolMessages.java | 69 +
7 files changed, 2,207 insertions(+), 27 deletions(-)
diff --git a/opendj-sdk/opends/src/server/org/opends/server/messages/ProtocolMessages.java b/opendj-sdk/opends/src/server/org/opends/server/messages/ProtocolMessages.java
index 3d93db9..632fdc2 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/messages/ProtocolMessages.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/messages/ProtocolMessages.java
@@ -4678,6 +4678,56 @@
public static final int MSGID_JMX_CONNHANDLER_CANNOT_BIND =
CATEGORY_MASK_PROTOCOL | SEVERITY_MASK_SEVERE_ERROR | 433;
+ /**
+ * The message ID for the message that will be used if an attempt is made to
+ * perform an add operation through JMX but the user doesn't
+ * have the necessary privileges to do so. This does not take any arguments.
+ */
+ public static final int MSGID_JMX_ADD_INSUFFICIENT_PRIVILEGES =
+ CATEGORY_MASK_PROTOCOL | SEVERITY_MASK_MILD_ERROR | 434;
+
+ /**
+ * The message ID for the message that will be used if an attempt is made to
+ * perform a delete operation through JMX but the user doesn't
+ * have the necessary privileges to do so. This does not take any arguments.
+ */
+ public static final int MSGID_JMX_DELETE_INSUFFICIENT_PRIVILEGES =
+ CATEGORY_MASK_PROTOCOL | SEVERITY_MASK_MILD_ERROR | 435;
+
+ /**
+ * The message ID for the message that will be used if an attempt is made to
+ * perform a modify operation through JMX but the user doesn't
+ * have the necessary privileges to do so. This does not take any arguments.
+ */
+ public static final int MSGID_JMX_MODIFY_INSUFFICIENT_PRIVILEGES =
+ CATEGORY_MASK_PROTOCOL | SEVERITY_MASK_MILD_ERROR | 436;
+
+
+ /**
+ * The message ID for the message that will be used if an attempt is made to
+ * perform a modify DN operation through JMX but the user
+ * doesn't have the necessary privileges to do so. This does not take any
+ * arguments.
+ */
+ public static final int MSGID_JMX_MODDN_INSUFFICIENT_PRIVILEGES =
+ CATEGORY_MASK_PROTOCOL | SEVERITY_MASK_MILD_ERROR | 437;
+
+
+ /**
+ * The message ID for the message that will be used if an attempt is made to
+ * perform a search operation in the server configuration but the user doesn't
+ * have the necessary privileges to do so. This does not take any arguments.
+ */
+ public static final int MSGID_JMX_SEARCH_INSUFFICIENT_PRIVILEGES =
+ CATEGORY_MASK_PROTOCOL | SEVERITY_MASK_MILD_ERROR | 438;
+
+ /**
+ * The message ID for the message that will be used if an attempt is made to
+ * perform a search operation in the server configuration but the user doesn't
+ * have the necessary privileges to do so. This does not take any arguments.
+ */
+ public static final int MSGID_JMX_INSUFFICIENT_PRIVILEGES =
+ CATEGORY_MASK_PROTOCOL | SEVERITY_MASK_MILD_ERROR | 439;
/**
@@ -6505,7 +6555,24 @@
ATTR_KEYMANAGER_DN + " attribute in configuration " +
"entry %s, which is used to specify the DN of the key manager " +
"provider to use for accepting SSL/TSL connections: %s");
-
+ registerMessage(MSGID_JMX_ADD_INSUFFICIENT_PRIVILEGES,
+ "You do not have sufficient privileges to perform add " +
+ "operations through JMX");
+ registerMessage(MSGID_JMX_DELETE_INSUFFICIENT_PRIVILEGES,
+ "You do not have sufficient privileges to perform delete " +
+ "operations through JMX");
+ registerMessage(MSGID_JMX_MODIFY_INSUFFICIENT_PRIVILEGES,
+ "You do not have sufficient privileges to perform modify " +
+ "operations through JMX");
+ registerMessage(MSGID_JMX_MODDN_INSUFFICIENT_PRIVILEGES,
+ "You do not have sufficient privileges to perform modify " +
+ "DN operations through JMX");
+ registerMessage(MSGID_JMX_SEARCH_INSUFFICIENT_PRIVILEGES,
+ "You do not have sufficient privileges to perform search " +
+ "operations through JMX");
+ registerMessage(MSGID_JMX_INSUFFICIENT_PRIVILEGES,
+ "You do not have sufficient privileges to establish the " +
+ "connection through JMX. At least JMX_READ privilege is required");
registerMessage(MSGID_PWPOLICYREQ_CONTROL_HAS_VALUE,
"Cannot decode the provided control as a password policy " +
diff --git a/opendj-sdk/opends/src/server/org/opends/server/protocols/jmx/JmxClientConnection.java b/opendj-sdk/opends/src/server/org/opends/server/protocols/jmx/JmxClientConnection.java
index 67c2fd6..d872b3b 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/protocols/jmx/JmxClientConnection.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/protocols/jmx/JmxClientConnection.java
@@ -44,6 +44,8 @@
import org.opends.server.protocols.internal.InternalSearchListener;
import org.opends.server.types.AbstractOperation;
+import org.opends.server.types.Attribute;
+import org.opends.server.types.AttributeType;
import org.opends.server.types.DebugLogLevel;
import org.opends.server.types.AuthenticationInfo;
import org.opends.server.types.CancelRequest;
@@ -54,9 +56,14 @@
import org.opends.server.types.DirectoryException;
import org.opends.server.types.DisconnectReason;
import org.opends.server.types.IntermediateResponse;
+import org.opends.server.types.Modification;
+import org.opends.server.types.ObjectClass;
import org.opends.server.types.Operation;
+import org.opends.server.types.Privilege;
+import org.opends.server.types.RDN;
import org.opends.server.types.RawAttribute;
import org.opends.server.types.RawModification;
+import org.opends.server.types.ResultCode;
import org.opends.server.types.SearchResultEntry;
import org.opends.server.types.SearchResultReference;
import org.opends.server.types.SearchScope;
@@ -64,6 +71,7 @@
import static org.opends.server.loggers.debug.DebugLogger.*;
import org.opends.server.loggers.debug.DebugTracer;
import static org.opends.server.messages.ProtocolMessages.*;
+import static org.opends.server.messages.MessageHandler.getMessage;
/**
@@ -476,10 +484,95 @@
new AddOperationBasis(this, nextOperationID(), nextMessageID(),
new ArrayList<Control>(0), rawEntryDN, rawAttributes);
- addOperation.run();
+ // Check if we have enough privilege
+ if (! hasPrivilege(Privilege.JMX_WRITE, null))
+ {
+ int msgID = MSGID_JMX_ADD_INSUFFICIENT_PRIVILEGES;
+ String message = getMessage(msgID);
+ addOperation.setErrorMessage(new StringBuilder(message));
+ addOperation.setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS) ;
+ }
+ else
+ {
+ addOperation.run();
+ }
return addOperation;
}
+ /**
+ * Processes an internal add operation with the provided
+ * information.
+ *
+ * @param entryDN The entry DN for the add
+ * operation.
+ * @param objectClasses The set of objectclasses for the
+ * add operation.
+ * @param userAttributes The set of user attributes for the
+ * add operation.
+ * @param operationalAttributes The set of operational attributes
+ * for the add operation.
+ *
+ * @return A reference to the add operation that was processed and
+ * contains information about the result of the processing.
+ */
+ public AddOperation processAdd(DN entryDN,
+ Map<ObjectClass,String> objectClasses,
+ Map<AttributeType,List<Attribute>>
+ userAttributes,
+ Map<AttributeType,List<Attribute>>
+ operationalAttributes)
+ {
+ AddOperationBasis addOperation =
+ new AddOperationBasis(this, nextOperationID(),
+ nextMessageID(),
+ new ArrayList<Control>(0), entryDN,
+ objectClasses, userAttributes,
+ operationalAttributes);
+ // Check if we have enough privilege
+ if (! hasPrivilege(Privilege.JMX_WRITE, null))
+ {
+ int msgID = MSGID_JMX_ADD_INSUFFICIENT_PRIVILEGES;
+ String message = getMessage(msgID);
+ addOperation.setErrorMessage(new StringBuilder(message));
+ addOperation.setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS) ;
+ }
+ else
+ {
+ addOperation.run();
+ }
+ return addOperation;
+ }
+
+ /**
+ * Processes an internal delete operation with the provided
+ * information.
+ *
+ * @param entryDN The entry DN for the delete operation.
+ *
+ * @return A reference to the delete operation that was processed
+ * and contains information about the result of the
+ * processing.
+ */
+ public DeleteOperation processDelete(DN entryDN)
+ {
+ DeleteOperationBasis deleteOperation =
+ new DeleteOperationBasis(this, nextOperationID(),
+ nextMessageID(),
+ new ArrayList<Control>(0), entryDN);
+ // Check if we have enough privilege
+ if (! hasPrivilege(Privilege.JMX_WRITE, null))
+ {
+ int msgID = MSGID_JMX_DELETE_INSUFFICIENT_PRIVILEGES;
+ String message = getMessage(msgID);
+ deleteOperation.setErrorMessage(new StringBuilder(message));
+ deleteOperation.setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS) ;
+ }
+ else
+ {
+ deleteOperation.run();
+ }
+ return deleteOperation;
+ }
/**
@@ -501,7 +594,18 @@
new ArrayList<Control>(0), rawEntryDN,
attributeType, assertionValue);
- compareOperation.run();
+ // Check if we have enough privilege
+ if (! hasPrivilege(Privilege.JMX_READ, null))
+ {
+ int msgID = MSGID_JMX_SEARCH_INSUFFICIENT_PRIVILEGES;
+ String message = getMessage(msgID);
+ compareOperation.setErrorMessage(new StringBuilder(message));
+ compareOperation.setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS) ;
+ }
+ else
+ {
+ compareOperation.run();
+ }
return compareOperation;
}
@@ -521,7 +625,18 @@
new DeleteOperationBasis(this, nextOperationID(), nextMessageID(),
new ArrayList<Control>(0), rawEntryDN);
- deleteOperation.run();
+ // Check if we have enough privilege
+ if (! hasPrivilege(Privilege.JMX_WRITE, null))
+ {
+ int msgID = MSGID_JMX_DELETE_INSUFFICIENT_PRIVILEGES;
+ String message = getMessage(msgID);
+ deleteOperation.setErrorMessage(new StringBuilder(message));
+ deleteOperation.setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS) ;
+ }
+ else
+ {
+ deleteOperation.run();
+ }
return deleteOperation;
}
@@ -569,11 +684,54 @@
new ArrayList<Control>(0), rawEntryDN,
rawModifications);
- modifyOperation.run();
+ if (! hasPrivilege(Privilege.JMX_WRITE, null))
+ {
+ int msgID = MSGID_JMX_MODIFY_INSUFFICIENT_PRIVILEGES;
+ String message = getMessage(msgID);
+ modifyOperation.setErrorMessage(new StringBuilder(message));
+ modifyOperation.setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS) ;
+ }
+ else
+ {
+ modifyOperation.run();
+ }
return modifyOperation;
}
+ /**
+ * Processes an internal modify operation with the provided
+ * information.
+ *
+ * @param entryDN The entry DN for this modify operation.
+ * @param modifications The set of modifications for this modify
+ * operation.
+ *
+ * @return A reference to the modify operation that was processed
+ * and contains information about the result of the
+ * processing.
+ */
+ public ModifyOperation processModify(DN entryDN,
+ List<Modification> modifications)
+ {
+ ModifyOperationBasis modifyOperation =
+ new ModifyOperationBasis(this, nextOperationID(),
+ nextMessageID(),
+ new ArrayList<Control>(0), entryDN,
+ modifications);
+ if (! hasPrivilege(Privilege.JMX_WRITE, null))
+ {
+ int msgID = MSGID_JMX_MODIFY_INSUFFICIENT_PRIVILEGES;
+ String message = getMessage(msgID);
+ modifyOperation.setErrorMessage(new StringBuilder(message));
+ modifyOperation.setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS) ;
+ }
+ else
+ {
+ modifyOperation.run();
+ }
+ return modifyOperation;
+ }
/**
* Processes an Jmx modify DN operation with the provided information.
@@ -619,11 +777,59 @@
new ArrayList<Control>(0), rawEntryDN, rawNewRDN,
deleteOldRDN, rawNewSuperior);
- modifyDNOperation.run();
+ if (! hasPrivilege(Privilege.JMX_WRITE, null))
+ {
+ int msgID = MSGID_JMX_MODDN_INSUFFICIENT_PRIVILEGES;
+ String message = getMessage(msgID);
+ modifyDNOperation.setErrorMessage(new StringBuilder(message));
+ modifyDNOperation.setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS) ;
+ }
+ else
+ {
+ modifyDNOperation.run();
+ }
return modifyDNOperation;
}
+ /**
+ * Processes an internal modify DN operation with the provided
+ * information.
+ *
+ * @param entryDN The current DN of the entry to rename.
+ * @param newRDN The new RDN to use for the entry.
+ * @param deleteOldRDN The flag indicating whether the old RDN
+ * value is to be removed from the entry.
+ * @param newSuperior The new superior for the modify DN
+ * operation, or <CODE>null</CODE> if the
+ * entry will remain below the same parent.
+ *
+ * @return A reference to the modify DN operation that was
+ * processed and contains information about the result of
+ * the processing.
+ */
+ public ModifyDNOperation processModifyDN(DN entryDN, RDN newRDN,
+ boolean deleteOldRDN,
+ DN newSuperior)
+ {
+ ModifyDNOperation modifyDNOperation =
+ new ModifyDNOperation(this, nextOperationID(),
+ nextMessageID(),
+ new ArrayList<Control>(0), entryDN,
+ newRDN, deleteOldRDN, newSuperior);
+ if (! hasPrivilege(Privilege.JMX_WRITE, null))
+ {
+ int msgID = MSGID_JMX_MODDN_INSUFFICIENT_PRIVILEGES;
+ String message = getMessage(msgID);
+ modifyDNOperation.setErrorMessage(new StringBuilder(message));
+ modifyDNOperation.setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS) ;
+ }
+ else
+ {
+ modifyDNOperation.run();
+ }
+ return modifyDNOperation;
+ }
/**
* Processes an Jmx search operation with the provided information.
@@ -677,7 +883,17 @@
scope, derefPolicy, sizeLimit, timeLimit,
typesOnly, filter, attributes, null);
- searchOperation.run();
+ if (! hasPrivilege(Privilege.JMX_READ, null))
+ {
+ int msgID = MSGID_JMX_SEARCH_INSUFFICIENT_PRIVILEGES;
+ String message = getMessage(msgID);
+ searchOperation.setErrorMessage(new StringBuilder(message));
+ searchOperation.setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS) ;
+ }
+ else
+ {
+ searchOperation.run();
+ }
return searchOperation;
}
diff --git a/opendj-sdk/opends/src/server/org/opends/server/protocols/jmx/RmiAuthenticator.java b/opendj-sdk/opends/src/server/org/opends/server/protocols/jmx/RmiAuthenticator.java
index 47bb286..1fcd21f 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/protocols/jmx/RmiAuthenticator.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/protocols/jmx/RmiAuthenticator.java
@@ -39,12 +39,17 @@
import org.opends.server.protocols.asn1.ASN1OctetString;
import org.opends.server.protocols.ldap.LDAPResultCode;
import org.opends.server.types.Control;
+import org.opends.server.types.DisconnectReason;
+import org.opends.server.types.Privilege;
import org.opends.server.types.ResultCode;
import org.opends.server.types.DN;
import org.opends.server.types.AuthenticationInfo;
import org.opends.server.types.LDAPException;
import static org.opends.server.loggers.debug.DebugLogger.*;
+import static org.opends.server.messages.MessageHandler.getMessage;
+import static org.opends.server.messages.ProtocolMessages.*;
+
import org.opends.server.loggers.debug.DebugTracer;
import org.opends.server.types.DebugLogLevel;
@@ -183,7 +188,7 @@
{
TRACER.debugCaught(DebugLogLevel.ERROR, e);
}
- SecurityException se = new SecurityException();
+ SecurityException se = new SecurityException(e.getMessage());
se.initCause(e);
throw se;
}
@@ -277,6 +282,19 @@
authInfo = bindOp.getAuthenticationInfo();
jmxClientConnection.setAuthenticationInfo(authInfo);
+
+ // Check JMX_READ privilege.
+ if (! jmxClientConnection.hasPrivilege(Privilege.JMX_READ, null))
+ {
+ int msgID = MSGID_JMX_INSUFFICIENT_PRIVILEGES;
+ String message = getMessage(msgID);
+
+ jmxClientConnection.disconnect(DisconnectReason.CONNECTION_REJECTED,
+ false, msgID);
+
+ SecurityException se = new SecurityException(message);
+ throw se;
+ }
return jmxClientConnection;
}
else
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/jmx/JmxConnectTest.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/jmx/JmxConnectTest.java
index a2c57a4..a9f5229 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/jmx/JmxConnectTest.java
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/jmx/JmxConnectTest.java
@@ -52,6 +52,7 @@
import org.opends.server.admin.std.server.JMXConnectionHandlerCfg;
import org.opends.server.config.JMXMBean;
import org.opends.server.core.AddOperationBasis;
+import org.opends.server.core.DeleteOperation;
import org.opends.server.core.DeleteOperationBasis;
import org.opends.server.core.DirectoryServer;
import org.opends.server.protocols.internal.InternalClientConnection;
@@ -59,6 +60,8 @@
import org.opends.server.types.DN;
import org.opends.server.types.Entry;
import org.opends.server.types.ResultCode;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
@@ -71,6 +74,94 @@
* JMX get and set - configuration change
*/
public class JmxConnectTest extends JmxTestCase {
+
+ /**
+ * Set up the environment for performing the tests in this suite.
+ *
+ * @throws Exception
+ * If the environment could not be set up.
+ */
+ @BeforeClass
+ public void setUp() throws Exception
+ {
+ super.setUp();
+
+ TestCaseUtils.addEntries(
+ "dn: cn=Privileged User,o=test",
+ "objectClass: top",
+ "objectClass: person",
+ "objectClass: organizationalPerson",
+ "objectClass: inetOrgPerson",
+ "cn: Privileged User",
+ "givenName: Privileged",
+ "sn: User",
+ "uid: privileged.user",
+ "userPassword: password",
+ "ds-privilege-name: config-read",
+ "ds-privilege-name: config-write",
+ "ds-privilege-name: password-reset",
+ "ds-privilege-name: update-schema",
+ "ds-privilege-name: ldif-import",
+ "ds-privilege-name: ldif-export",
+ "ds-privilege-name: backend-backup",
+ "ds-privilege-name: backend-restore",
+ "ds-privilege-name: proxied-auth",
+ "ds-privilege-name: bypass-acl",
+ "ds-privilege-name: unindexed-search",
+ "ds-privilege-name: jmx-read",
+ "ds-privilege-name: jmx-write",
+ "ds-pwp-password-policy-dn: cn=Clear UserPassword Policy," +
+ "cn=Password Policies,cn=config",
+ "",
+ "dn: cn=Unprivileged JMX User,o=test",
+ "objectClass: top",
+ "objectClass: person",
+ "objectClass: organizationalPerson",
+ "objectClass: inetOrgPerson",
+ "cn: Privileged User",
+ "givenName: Privileged",
+ "sn: User",
+ "uid: privileged.user",
+ "userPassword: password",
+ "ds-privilege-name: config-read",
+ "ds-privilege-name: config-write",
+ "ds-privilege-name: password-reset",
+ "ds-privilege-name: update-schema",
+ "ds-privilege-name: ldif-import",
+ "ds-privilege-name: ldif-export",
+ "ds-privilege-name: backend-backup",
+ "ds-privilege-name: backend-restore",
+ "ds-privilege-name: proxied-auth",
+ "ds-privilege-name: bypass-acl",
+ "ds-privilege-name: unindexed-search",
+ "ds-pwp-password-policy-dn: cn=Clear UserPassword Policy," +
+ "cn=Password Policies,cn=config");
+ }
+
+
+ /**
+ * Clean up the environment after performing the tests in this suite.
+ *
+ * @throws Exception
+ * If the environment could not be set up.
+ */
+ @AfterClass
+ public void afterClass() throws Exception
+ {
+ InternalClientConnection conn = InternalClientConnection
+ .getRootConnection();
+
+ DeleteOperation deleteOperation = conn.processDelete(DN
+ .decode("cn=Privileged User,o=test"));
+ assertEquals(deleteOperation.getResultCode(), ResultCode.SUCCESS);
+
+ deleteOperation = conn.processDelete(DN
+ .decode("cn=Unprivileged JMX User,o=test"));
+ assertEquals(deleteOperation.getResultCode(), ResultCode.SUCCESS);
+
+ }
+
+
/**
* Build data for the simpleConnect test.
@@ -80,11 +171,12 @@
@DataProvider(name = "simpleConnect")
Object[][] createCredentials() {
return new Object[][] {
- { "cn=directory manager", "password", true },
- { "cn=directory manager", "wrongPassword", false },
+ { "cn=directory manager", "password", false }, // no JMX_READ privilege
+ { "cn=Privileged User,o=test", "password", true },
+ { "cn=Privileged User,o=test", "wrongPassword", false },
{ "cn=wrong user", "password", false },
{ "invalid DN", "password", false },
- { "cn=directory manager", null, false },
+ { "cn=Privileged User,o=test", null, false },
{ null, "password", false }, { null, null, false }, };
}
@@ -105,9 +197,7 @@
connector.close();
}
}
-
-
-
+
/**
* Build some data for the simpleGet test.
*/
@@ -139,7 +229,7 @@
public void simpleGet(String dn, String attributeName, Object value)
throws Exception {
- OpendsJmxConnector connector = connect("cn=directory manager",
+ OpendsJmxConnector connector = connect("cn=Privileged User,o=test",
"password", TestCaseUtils.getServerJmxPort());
MBeanServerConnection jmxc = connector.getMBeanServerConnection();
assertNotNull(jmxc);
@@ -163,7 +253,7 @@
// the admin framework, and the cn=config entry is now governed by it.
@Test(enabled = false)
public void simpleSet() throws Exception {
- OpendsJmxConnector connector = connect("cn=directory manager",
+ OpendsJmxConnector connector = connect("cn=Privileged User,o=test",
"password", TestCaseUtils.getServerJmxPort());
MBeanServerConnection jmxc = connector.getMBeanServerConnection();
assertNotNull(jmxc);
@@ -231,12 +321,12 @@
addOp.run();
Thread.sleep(200);
OpendsJmxConnector newJmxConnector = connect(
- "cn=directory manager", "password", serverJmxPort);
+ "cn=Privileged User,o=test", "password", serverJmxPort);
assertNotNull(newJmxConnector);
newJmxConnector.close();
// Get the "old" connector
- OpendsJmxConnector connector = connect("cn=directory manager",
+ OpendsJmxConnector connector = connect("cn=Privileged User,o=test",
"password", TestCaseUtils.getServerJmxPort());
MBeanServerConnection jmxc = connector.getMBeanServerConnection();
assertNotNull(jmxc);
@@ -245,14 +335,14 @@
toggleEnableJmxConnector(connector, newJmxConnectionJmx.getDN(),
false);
Thread.sleep(100);
- OpendsJmxConnector jmxcDisabled = connect("cn=directory manager",
+ OpendsJmxConnector jmxcDisabled = connect("cn=Privileged User,o=test",
"password", serverJmxPort);
assertNull(jmxcDisabled);
toggleEnableJmxConnector(connector, newJmxConnectionJmx.getDN(),
true);
Thread.sleep(100);
- jmxcDisabled = connect("cn=directory manager", "password",
+ jmxcDisabled = connect("cn=Privileged User,o=test", "password",
serverJmxPort);
assertNotNull(jmxcDisabled);
@@ -279,7 +369,7 @@
final String dn = "cn=JMX Connection Handler,cn=Connection Handlers,cn=config";
final String attribute = "ds-cfg-listen-port";
- OpendsJmxConnector connector = connect("cn=directory manager",
+ OpendsJmxConnector connector = connect("cn=Privileged User,o=test",
"password", TestCaseUtils.getServerJmxPort());
MBeanServerConnection jmxc = connector.getMBeanServerConnection();
assertNotNull(jmxc);
@@ -309,7 +399,7 @@
configureJmx(entry);
// connect the the JMX service using the new port
- connector = connect("cn=directory manager", "password",
+ connector = connect("cn=Privileged User,o=test", "password",
serverJmxPort);
jmxc = connector.getMBeanServerConnection();
assertNotNull(jmxc);
@@ -333,7 +423,7 @@
configureJmx(entry);
// Check that the old port is ok
- connector = connect("cn=directory manager", "password",
+ connector = connect("cn=Privileged User,o=test", "password",
TestCaseUtils.getServerJmxPort());
jmxc = connector.getMBeanServerConnection();
assertNotNull(jmxc);
@@ -368,8 +458,9 @@
configureJmx(entry);
- OpendsJmxConnector jmxc = sslConnect("cn=directory manager",
+ OpendsJmxConnector jmxc = sslConnect("cn=Privileged User,o=test",
"password", initJmxPort);
+ assertNotNull(jmxc,"OpendsJmxConnector shouldn't be null");
MBeanServerConnection mbsc = jmxc.getMBeanServerConnection();
jmxc.close();
@@ -393,7 +484,7 @@
e.printStackTrace();
}
- jmxc = connect("cn=directory manager", "password", initJmxPort);
+ jmxc = connect("cn=Privileged User,o=test", "password", initJmxPort);
jmxc.close();
assertNotNull(jmxc);
}
@@ -430,7 +521,7 @@
* Connect to the JMX service.
*/
private OpendsJmxConnector connect(String user, String password,
- long jmxPort) throws MalformedURLException, IOException {
+ long jmxPort) throws MalformedURLException, IOException{
HashMap<String, Object> env = new HashMap<String, Object>();
// Provide the credentials required by the server to successfully
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/jmx/JmxPrivilegeTestCase.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/jmx/JmxPrivilegeTestCase.java
new file mode 100644
index 0000000..fd0d383
--- /dev/null
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/jmx/JmxPrivilegeTestCase.java
@@ -0,0 +1,1692 @@
+/*
+ * 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.protocols.jmx;
+
+
+
+import static org.opends.server.messages.MessageHandler.getMessage;
+import static org.opends.server.messages.ProtocolMessages.MSGID_JMX_INSUFFICIENT_PRIVILEGES;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.UUID;
+
+import org.opends.server.TestCaseUtils;
+import org.opends.server.admin.ClassLoaderProvider;
+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.controls.ProxiedAuthV1Control;
+import org.opends.server.controls.ProxiedAuthV2Control;
+import org.opends.server.core.AddOperation;
+import org.opends.server.core.AddOperationBasis;
+import org.opends.server.core.CompareOperation;
+import org.opends.server.core.DeleteOperation;
+import org.opends.server.core.DeleteOperationBasis;
+import org.opends.server.core.DirectoryServer;
+import org.opends.server.core.ModifyDNOperation;
+import org.opends.server.core.ModifyOperation;
+import org.opends.server.core.ModifyOperationBasis;
+import org.opends.server.core.SchemaConfigManager;
+import org.opends.server.protocols.asn1.ASN1OctetString;
+import org.opends.server.protocols.ldap.LDAPFilter;
+import org.opends.server.protocols.internal.InternalClientConnection;
+import org.opends.server.protocols.internal.InternalSearchOperation;
+import org.opends.server.types.Attribute;
+import org.opends.server.types.AuthenticationInfo;
+import org.opends.server.types.ByteStringFactory;
+import org.opends.server.types.Control;
+import org.opends.server.types.DN;
+import org.opends.server.types.DereferencePolicy;
+import org.opends.server.types.Entry;
+import org.opends.server.types.Modification;
+import org.opends.server.types.ModificationType;
+import org.opends.server.types.Privilege;
+import org.opends.server.types.RDN;
+import org.opends.server.types.ResultCode;
+import org.opends.server.types.SearchFilter;
+import org.opends.server.types.SearchScope;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+
+
+
+/**
+ * This class provides a set of test cases for the Directory Server JMX
+ * privilege subsystem.
+ */
+public class JmxPrivilegeTestCase
+ extends JmxTestCase
+{
+ // An array of boolean values that indicates whether config read operations
+ // should be successful for users in the corresponding slots of the
+ // connections array.
+ private boolean[] successful;
+
+ // The set of client connections that should be used when performing
+ // operations.
+ private JmxClientConnection[] connections;
+
+
+
+ /**
+ * Make sure that the server is running and that an appropriate set of
+ * structures are in place.
+ *
+ * @throws Exception If an unexpected problem occurs.
+ */
+ @BeforeClass()
+ public void setUp()
+ throws Exception
+ {
+ super.setUp();
+
+
+ TestCaseUtils.initializeTestBackend(true);
+ TestCaseUtils.addEntries(
+ "dn: cn=Unprivileged Root,cn=Root DNs,cn=config",
+ "objectClass: top",
+ "objectClass: person",
+ "objectClass: organizationalPerson",
+ "objectClass: inetOrgPerson",
+ "objectClass: ds-cfg-root-dn",
+ "cn: Unprivileged Root",
+ "givenName: Unprivileged",
+ "sn: Root",
+ "uid: unprivileged.root",
+ "userPassword: password",
+ "ds-privilege-name: config-read",
+ "ds-privilege-name: config-write",
+ "ds-privilege-name: password-reset",
+ "ds-privilege-name: update-schema",
+ "ds-privilege-name: ldif-import",
+ "ds-privilege-name: ldif-export",
+ "ds-privilege-name: backend-backup",
+ "ds-privilege-name: backend-restore",
+ "ds-privilege-name: unindexed-search",
+ "ds-privilege-name: -jmx-read",
+ "ds-privilege-name: -jmx-write",
+ "",
+ "dn: cn=Unprivileged JMX Root,cn=Root DNs,cn=config",
+ "objectClass: top",
+ "objectClass: person",
+ "objectClass: organizationalPerson",
+ "objectClass: inetOrgPerson",
+ "objectClass: ds-cfg-root-dn",
+ "cn: Unprivileged Root",
+ "givenName: Unprivileged",
+ "sn: Root",
+ "uid: unprivileged.root",
+ "userPassword: password",
+ "",
+ "dn: cn=Proxy Root,cn=Root DNs,cn=config",
+ "objectClass: top",
+ "objectClass: person",
+ "objectClass: organizationalPerson",
+ "objectClass: inetOrgPerson",
+ "objectClass: ds-cfg-root-dn",
+ "cn: Proxy Root",
+ "givenName: Proxy",
+ "sn: Root",
+ "uid: proxy.root",
+ "userPassword: password",
+ "ds-privilege-name: proxied-auth",
+ "ds-privilege-name: jmx-read",
+ "ds-privilege-name: jmx-write",
+ "",
+ "",
+ "dn: cn=Privileged User,o=test",
+ "objectClass: top",
+ "objectClass: person",
+ "objectClass: organizationalPerson",
+ "objectClass: inetOrgPerson",
+ "cn: Privileged User",
+ "givenName: Privileged",
+ "sn: User",
+ "uid: privileged.user",
+ "userPassword: password",
+ "ds-privilege-name: config-read",
+ "ds-privilege-name: config-write",
+ "ds-privilege-name: password-reset",
+ "ds-privilege-name: update-schema",
+ "ds-privilege-name: ldif-import",
+ "ds-privilege-name: ldif-export",
+ "ds-privilege-name: backend-backup",
+ "ds-privilege-name: backend-restore",
+ "ds-privilege-name: proxied-auth",
+ "ds-privilege-name: bypass-acl",
+ "ds-privilege-name: unindexed-search",
+ "ds-privilege-name: jmx-read",
+ "ds-privilege-name: jmx-write",
+ "ds-pwp-password-policy-dn: cn=Clear UserPassword Policy," +
+ "cn=Password Policies,cn=config",
+ "",
+ "dn: cn=Unprivileged User,o=test",
+ "objectClass: top",
+ "objectClass: person",
+ "objectClass: organizationalPerson",
+ "objectClass: inetOrgPerson",
+ "cn: Unprivileged User",
+ "givenName: Unprivileged",
+ "sn: User",
+ "uid: unprivileged.user",
+ "ds-privilege-name: bypass-acl",
+ "userPassword: password",
+ "ds-pwp-password-policy-dn: cn=Clear UserPassword Policy," +
+ "cn=Password Policies,cn=config",
+ "",
+ "dn: cn=PWReset Target,o=test",
+ "objectClass: top",
+ "objectClass: person",
+ "objectClass: organizationalPerson",
+ "objectClass: inetOrgPerson",
+ "cn: PWReset Target",
+ "givenName: PWReset",
+ "sn: Target",
+ "uid: pwreset.target",
+ "userPassword: password");
+
+ TestCaseUtils.applyModifications(
+ "dn: o=test",
+ "changetype: modify",
+ "add: aci",
+ "aci: (version 3.0; acl \"Proxy Root\"; allow (proxy) " +
+ "userdn=\"ldap:///cn=Proxy Root,cn=Root DNs,cn=config\";)",
+ "aci: (version 3.0; acl \"Unprivileged Root\"; allow (proxy) " +
+ "userdn=\"ldap:///cn=Unprivileged Root,cn=Root DNs,cn=config\";)",
+ "aci: (version 3.0; acl \"Privileged User\"; allow (proxy) " +
+ "userdn=\"ldap:///cn=Privileged User,o=test\";)",
+ "aci: (targetattr=\"*\")(version 3.0; acl \"PWReset Target\"; " +
+ "allow (all) userdn=\"ldap:///cn=PWReset Target,o=test\";)");
+
+
+ // Build the array of connections we will use to perform the tests.
+ JmxConnectionHandler jmxCtx = getJmxConnectionHandler();
+ ArrayList<JmxClientConnection> connList =
+ new ArrayList<JmxClientConnection>();
+ ArrayList<Boolean> successList = new ArrayList<Boolean>();
+ String userDN ;
+ Entry userEntry ;
+ AuthenticationInfo authInfo;
+
+ connList.add(new JmxClientConnection(jmxCtx,new AuthenticationInfo()));
+ successList.add(false);
+
+ userDN = "cn=Unprivileged Root,cn=Root DNs,cn=config";
+ userEntry = DirectoryServer.getEntry(DN.decode(userDN));
+ authInfo = new AuthenticationInfo(userEntry, true);
+ connList.add(new JmxClientConnection(jmxCtx,authInfo));
+ successList.add(false);
+
+ userDN = "cn=Proxy Root,cn=Root DNs,cn=config";
+ userEntry = DirectoryServer.getEntry(DN.decode(userDN));
+ authInfo = new AuthenticationInfo(userEntry, true);
+ connList.add(new JmxClientConnection(jmxCtx,authInfo));
+ successList.add(true);
+
+ userDN = "cn=Unprivileged User,o=test";
+ userEntry = DirectoryServer.getEntry(DN.decode(userDN));
+ authInfo = new AuthenticationInfo(userEntry, false);
+ connList.add(new JmxClientConnection(jmxCtx,authInfo));
+ successList.add(false);
+
+ userDN = "cn=Privileged User,o=test";
+ userEntry = DirectoryServer.getEntry(DN.decode(userDN));
+ authInfo = new AuthenticationInfo(userEntry, false);
+ connList.add(new JmxClientConnection(jmxCtx,authInfo));
+ successList.add(true);
+
+
+ connections = new JmxClientConnection[connList.size()];
+ successful = new boolean[connections.length];
+ for (int i=0; i < connections.length; i++)
+ {
+ connections[i] = connList.get(i);
+ successful[i] = successList.get(i);
+ }
+
+ TestCaseUtils.addEntries(
+ "dn: dc=unindexed,dc=jeb",
+ "objectClass: top",
+ "objectClass: domain",
+ "",
+ "dn: cn=test1 user,dc=unindexed,dc=jeb",
+ "objectClass: top",
+ "objectClass: person",
+ "objectClass: organizationalPerson",
+ "objectClass: inetOrgPerson",
+ "cn: test1 user",
+ "givenName: user",
+ "sn: test1",
+ "",
+ "dn: cn=test2 user,dc=unindexed,dc=jeb",
+ "objectClass: top",
+ "objectClass: person",
+ "objectClass: organizationalPerson",
+ "objectClass: inetOrgPerson",
+ "cn: test2 user",
+ "givenName: user",
+ "sn: test2"
+ );
+ }
+
+
+
+ /**
+ * Cleans up anything that might be left around after running the tests in
+ * this class.
+ *
+ * @throws Exception If an unexpected problem occurs.
+ */
+ @AfterClass()
+ public void cleanUp()
+ throws Exception
+ {
+ InternalClientConnection conn = InternalClientConnection
+ .getRootConnection();
+
+ DeleteOperation deleteOperation = conn.processDelete(DN
+ .decode("cn=Unprivileged Root,cn=Root DNs,cn=config"));
+ assertEquals(deleteOperation.getResultCode(), ResultCode.SUCCESS);
+
+ deleteOperation = conn.processDelete(DN
+ .decode("cn=Unprivileged JMX Root,cn=Root DNs,cn=config"));
+ assertEquals(deleteOperation.getResultCode(), ResultCode.SUCCESS);
+
+ deleteOperation = conn.processDelete(DN
+ .decode("cn=Proxy Root,cn=Root DNs,cn=config"));
+ assertEquals(deleteOperation.getResultCode(), ResultCode.SUCCESS);
+
+ deleteOperation = conn.processDelete(DN
+ .decode("cn=Privileged User,o=test"));
+ assertEquals(deleteOperation.getResultCode(), ResultCode.SUCCESS);
+
+ deleteOperation = conn.processDelete(DN
+ .decode("cn=UnPrivileged User,o=test"));
+ assertEquals(deleteOperation.getResultCode(), ResultCode.SUCCESS);
+
+ deleteOperation = conn.processDelete(DN
+ .decode("cn=PWReset Target,o=test"));
+ assertEquals(deleteOperation.getResultCode(), ResultCode.SUCCESS);
+
+ deleteOperation = conn.processDelete(DN
+ .decode("cn=test1 user,dc=unindexed,dc=jeb"));
+ assertEquals(deleteOperation.getResultCode(), ResultCode.SUCCESS);
+
+ deleteOperation = conn.processDelete(DN
+ .decode("cn=test2 user,dc=unindexed,dc=jeb"));
+ assertEquals(deleteOperation.getResultCode(), ResultCode.SUCCESS);
+
+ deleteOperation = conn.processDelete(DN
+ .decode("dc=unindexed,dc=jeb"));
+ assertEquals(deleteOperation.getResultCode(), ResultCode.SUCCESS);
+ }
+
+
+
+ /**
+ * Retrieves a set of data that can be used for performing the tests. The
+ * arguments generated for each method will be:
+ * <OL>
+ * <LI>A client connection to use to perform the operation</LI>
+ * <LI>A flag indicating whether or not the operation should succeed</LI>
+ * </OL>
+ *
+ * @return A set of data that can be used for performing the tests.
+ */
+ @DataProvider(name = "testdata")
+ public Object[][] getTestData()
+ {
+ Object[][] returnArray = new Object[connections.length][2];
+ for (int i=0; i < connections.length; i++)
+ {
+ returnArray[i][0] = connections[i];
+ returnArray[i][1] = successful[i];
+ }
+
+ return returnArray;
+ }
+
+
+ /**
+ * Check that simple connection to the JMX service are
+ * accepted only if JMX_READ privilege is set.
+ */
+ @Test(enabled = true)
+ public void simpleConnectJmxPrivilege() throws Exception
+ {
+ OpendsJmxConnector opendsConnector;
+ int jmxPort = TestCaseUtils.getServerJmxPort() ;
+ HashMap<String, Object> env = new HashMap<String, Object>();
+ String user = "cn=Unprivileged JMX Root,cn=Root DNs,cn=config";
+ String password = "password";
+ String[] credentials = new String[] { user, password };
+ env.put("jmx.remote.credentials", credentials);
+ env.put("jmx.remote.x.client.connection.check.period", 0);
+
+ // Try connection withoutJMX_READ privilege
+ // Expected result: failed
+ try
+ {
+ opendsConnector = new OpendsJmxConnector("localhost", jmxPort, env);
+ opendsConnector.connect();
+ opendsConnector.close() ;
+ assertTrue(false, "User \"cn=Unprivileged JMX Root,cn=Root "+
+ "DNs,cn=config\" doesn't have JMX_READ privilege but he's able " +
+ "to connect, which is not the correct behavior");
+ }
+ catch (SecurityException e)
+ {
+ int msgID = MSGID_JMX_INSUFFICIENT_PRIVILEGES;
+ String message = getMessage(msgID);
+ assertEquals(message, e.getMessage());
+ }
+ catch (IOException e)
+ {
+ assertTrue(false, "Unexpected exception - error message: "
+ + e.getMessage());
+ }
+
+ // Add JMX_READ privilege
+ InternalClientConnection rootConnection =
+ InternalClientConnection.getRootConnection();
+ ArrayList<Modification> mods = new ArrayList<Modification>();
+ mods.add(new Modification(ModificationType.ADD,
+ new Attribute("ds-privilege-name", "jmx-read")));
+ ModifyOperation modifyOperation =
+ rootConnection.processModify(DN.decode(user), mods);
+ assertEquals(modifyOperation.getResultCode(), ResultCode.SUCCESS);
+
+ // Try connection withoutJMX_READ privilege
+ // Expected result: success
+ try
+ {
+ opendsConnector = new OpendsJmxConnector("localhost", jmxPort, env);
+ opendsConnector.connect();
+ opendsConnector.close() ;
+ assertTrue(true, "User \"cn=Unprivileged JMX Root,cn=Root "+
+ "DNs,cn=config\" has JMX_READ privilege and he's able " +
+ "to connect, which is the correct behavior.");
+ }
+ catch (SecurityException e)
+ {
+ assertTrue(true, "User \"cn=Unprivileged JMX Root,cn=Root " +
+ "DNs,cn=config\" has JMX_READ privilege and he's NOT able " +
+ "to connect, which is NOT the correct behavior.");
+ }
+ catch (IOException e)
+ {
+ assertTrue(false, "Unexpected exception - error message: "
+ + e.getMessage());
+ }
+
+ // remove JMX_READ privilege
+ mods = new ArrayList<Modification>();
+ mods.add(new Modification(ModificationType.DELETE,
+ new Attribute("ds-privilege-name", "jmx-read")));
+ modifyOperation =
+ rootConnection.processModify(DN.decode(user), mods);
+ assertEquals(modifyOperation.getResultCode(), ResultCode.SUCCESS);
+
+ // Try connection withoutJMX_READ privilege
+ // Expected result: failed
+ try
+ {
+ opendsConnector = new OpendsJmxConnector("localhost", jmxPort, env);
+ opendsConnector.connect();
+ opendsConnector.close() ;
+ assertTrue(false, "User \"cn=Unprivileged JMX Root,cn=Root "+
+ "DNs,cn=config\" doesn't have JMX_READ privilege but he's able " +
+ "to connect, which is not the correct behavior");
+ }
+ catch (SecurityException e)
+ {
+ int msgID = MSGID_JMX_INSUFFICIENT_PRIVILEGES;
+ String message = getMessage(msgID);
+ assertEquals(message, e.getMessage());
+ }
+ catch (IOException e)
+ {
+ assertTrue(false, "Unexpected exception - error message: "
+ + e.getMessage());
+ }
+ }
+
+
+ /**
+ * Tests to ensure that search operations in the server configuration properly
+ * respect the JMX_READ privilege.
+ *
+ * @param conn The client connection to use to perform the search
+ * operation.
+ * @param hasPrivilege Indicates whether the authenticated user is expected
+ * to have the JMX_READ privilege and therefore the
+ * search should succeed.
+ *
+ * @throws Exception If an unexpected problem occurs.
+ */
+ @Test(dataProvider = "testdata")
+ public void testConfigReadSearch(JmxClientConnection conn,
+ boolean hasPrivilege)
+ throws Exception
+ {
+ assertEquals(conn.hasPrivilege(Privilege.JMX_READ, null), hasPrivilege);
+
+ ASN1OctetString dn = new ASN1OctetString(DN.decode("cn=config").toString());
+ LDAPFilter filter = new LDAPFilter(SearchFilter
+ .createFilterFromString("(objectClass=*)"));
+ InternalSearchOperation searchOperation = conn.processSearch(dn,
+ SearchScope.BASE_OBJECT, filter);
+ if (hasPrivilege)
+ {
+ assertEquals(searchOperation.getResultCode(), ResultCode.SUCCESS);
+ }
+ else
+ {
+ assertEquals(searchOperation.getResultCode(),
+ ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
+ }
+ }
+
+
+
+ /**
+ * Tests to ensure that compare operations in the server configuration
+ * properly respect the JMX_READ privilege.
+ *
+ * @param conn The client connection to use to perform the compare
+ * operation.
+ * @param hasPrivilege Indicates whether the authenticated user is expected
+ * to have the JMX_READ privilege and therefore the
+ * compare should succeed.
+ *
+ * @throws Exception If an unexpected problem occurs.
+ */
+ @Test(dataProvider = "testdata")
+ public void testConfigReadCompare(JmxClientConnection conn,
+ boolean hasPrivilege)
+ throws Exception
+ {
+ assertEquals(conn.hasPrivilege(Privilege.JMX_READ, null), hasPrivilege);
+
+ ASN1OctetString asn1 = new ASN1OctetString(DN.decode("cn=config")
+ .toString());
+ ASN1OctetString value = new ASN1OctetString("config");
+ CompareOperation compareOperation =
+ conn.processCompare(asn1,
+ "cn",
+ value);
+ if (hasPrivilege)
+ {
+ assertEquals(compareOperation.getResultCode(), ResultCode.COMPARE_TRUE);
+ }
+ else
+ {
+ assertEquals(compareOperation.getResultCode(),
+ ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
+ }
+ }
+
+
+
+ /**
+ * Tests to ensure that add and delete operations in the server configuration
+ * properly respect the CONFIG_WRITE privilege.
+ *
+ * @param conn The client connection to use to perform the
+ * operations.
+ * @param hasPrivilege Indicates whether the authenticated user is expected
+ * to have the CONFIG_WRITE privilege and therefore the
+ * operations should succeed.
+ *
+ * @throws Exception If an unexpected problem occurs.
+ */
+ @Test(dataProvider = "testdata")
+ public void testConfigWriteAddAndDelete(JmxClientConnection conn,
+ boolean hasPrivilege)
+ throws Exception
+ {
+ assertEquals(conn.hasPrivilege(Privilege.JMX_WRITE, null), hasPrivilege);
+
+ Entry entry = TestCaseUtils.makeEntry(
+ "dn: cn=Test Root,cn=Root DNs,cn=config",
+ "objectClass: top",
+ "objectClass: person",
+ "objectClass: organizationalPerson",
+ "objectClass: inetOrgPerson",
+ "objectClass: ds-cfg-root-dn",
+ "cn: Test Root",
+ "givenName: Test",
+ "sn: Root",
+ "userPassword: password");
+
+ AddOperation addOperation =
+ conn.processAdd(entry.getDN(), entry.getObjectClasses(),
+ entry.getUserAttributes(),
+ entry.getOperationalAttributes());
+ if (hasPrivilege)
+ {
+ assertEquals(addOperation.getResultCode(), ResultCode.SUCCESS);
+
+ DeleteOperation deleteOperation = conn.processDelete(entry.getDN());
+ assertEquals(deleteOperation.getResultCode(), ResultCode.SUCCESS);
+ }
+ else
+ {
+ assertEquals(addOperation.getResultCode(),
+ ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
+
+ DeleteOperation deleteOperation =
+ conn.processDelete(
+ DN.decode("cn=Telex Number,cn=Syntaxes,cn=config"));
+ assertEquals(deleteOperation.getResultCode(),
+ ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
+ }
+ }
+
+
+
+ /**
+ * Tests to ensure that modify operations in the server configuration
+ * properly respect the CONFIG_WRITE privilege.
+ *
+ * @param conn The client connection to use to perform the modify
+ * operation.
+ * @param hasPrivilege Indicates whether the authenticated user is expected
+ * to have the CONFIG_WRITE privilege and therefore the
+ * modify should succeed.
+ *
+ * @throws Exception If an unexpected problem occurs.
+ */
+ @Test(dataProvider = "testdata")
+ public void testConfigWriteModify(JmxClientConnection conn,
+ boolean hasPrivilege)
+ throws Exception
+ {
+ assertEquals(conn.hasPrivilege(Privilege.JMX_WRITE, null), hasPrivilege);
+
+ ArrayList<Modification> mods = new ArrayList<Modification>();
+
+ mods.add(new Modification(ModificationType.REPLACE,
+ new Attribute("ds-cfg-size-limit", "2000")));
+
+ ModifyOperation modifyOperation =
+ conn.processModify(DN.decode("cn=config"), mods);
+ if (hasPrivilege)
+ {
+ assertEquals(modifyOperation.getResultCode(), ResultCode.SUCCESS);
+
+ mods.clear();
+ mods.add(new Modification(ModificationType.REPLACE,
+ new Attribute("ds-cfg-size-limit", "1000")));
+
+ modifyOperation = conn.processModify(DN.decode("cn=config"), mods);
+ assertEquals(modifyOperation.getResultCode(), ResultCode.SUCCESS);
+ }
+ else
+ {
+ assertEquals(modifyOperation.getResultCode(),
+ ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
+ }
+ }
+
+
+
+ /**
+ * Tests to ensure that modify DN operations in the server configuration
+ * properly respect the CONFIG_WRITE privilege.
+ *
+ * @param conn The client connection to use to perform the modify DN
+ * operation.
+ * @param hasPrivilege Indicates whether the authenticated user is expected
+ * to have the CONFIG_WRITE privilege and therefore the
+ * modify DN should succeed (or at least get past the
+ * privilege check, only to fail because we don't
+ * support modify DN in the server configuration).
+ *
+ * @throws Exception If an unexpected problem occurs.
+ */
+ @Test(dataProvider = "testdata")
+ public void testConfigWriteModifyDN(JmxClientConnection conn,
+ boolean hasPrivilege)
+ throws Exception
+ {
+ assertEquals(conn.hasPrivilege(Privilege.JMX_WRITE, null), hasPrivilege);
+
+ ModifyDNOperation modifyDNOperation =
+ conn.processModifyDN(DN.decode("cn=Work Queue,cn=config"),
+ RDN.decode("cn=New RDN for Work Queue"), true,
+ null);
+ if (hasPrivilege)
+ {
+ // We don't support modify DN operations in the server configuration, but
+ // at least we need to make sure we're getting past the privilege check.
+ assertEquals(modifyDNOperation.getResultCode(),
+ ResultCode.UNWILLING_TO_PERFORM);
+ }
+ else
+ {
+ assertEquals(modifyDNOperation.getResultCode(),
+ ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
+ }
+ }
+
+
+ /**
+ * Tests to ensure that attempts to update the schema with a modify operation
+ * will properly respect the UPDATE_SCHEMA privilege.
+ *
+ * @param conn The client connection to use to perform the schema
+ * update.
+ * @param hasPrivilege Indicates whether the authenticated user is expected
+ * to have the UPDATE_SCHEMA privilege and therefore
+ * the schema update should succeed.
+ *
+ * @throws Exception If an unexpected problem occurs.
+ */
+ @Test(dataProvider = "testdata")
+ public void testUpdateSchemaModify(JmxClientConnection conn,
+ boolean hasPrivilege)
+ throws Exception
+ {
+ assertEquals(conn.hasPrivilege(Privilege.JMX_WRITE, null),
+ hasPrivilege);
+
+ String attrDefinition =
+ "( testupdateschemaat-oid NAME 'testUpdateSchemaAT' " +
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE " +
+ "X-ORIGIN 'PrivilegeTestCase' )";
+
+ ArrayList<Modification> mods = new ArrayList<Modification>();
+
+ mods.add(new Modification(ModificationType.ADD,
+ new Attribute("attributetypes", attrDefinition)));
+
+ ModifyOperation modifyOperation =
+ conn.processModify(DN.decode("cn=schema"), mods);
+ if (hasPrivilege)
+ {
+ assertEquals(modifyOperation.getResultCode(), ResultCode.SUCCESS);
+
+ mods.clear();
+ mods.add(new Modification(ModificationType.DELETE,
+ new Attribute("attributetypes", attrDefinition)));
+
+ modifyOperation = conn.processModify(DN.decode("cn=schema"), mods);
+ assertEquals(modifyOperation.getResultCode(), ResultCode.SUCCESS);
+ }
+ else
+ {
+ assertEquals(modifyOperation.getResultCode(),
+ ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
+ }
+ }
+
+
+
+ /**
+ * Tests to ensure that attempts to update the schema with an add schema file
+ * task will properly respect the UPDATE_SCHEMA privilege.
+ *
+ * @param conn The client connection to use to perform the schema
+ * update.
+ * @param hasPrivilege Indicates whether the authenticated user is expected
+ * to have the UPDATE_SCHEMA privilege and therefore
+ * the schema update should succeed.
+ *
+ * @throws Exception If an unexpected problem occurs.
+ */
+ @Test(dataProvider = "testdata")
+ public void testUpdateSchemaAddSchemaFile(JmxClientConnection conn,
+ boolean hasPrivilege)
+ throws Exception
+ {
+ assertEquals(conn.hasPrivilege(Privilege.JMX_WRITE, null),
+ hasPrivilege);
+
+
+ String schemaDirectory = SchemaConfigManager.getSchemaDirectoryPath();
+
+ String identifier;
+ Entry authNEntry = conn.getAuthenticationInfo().getAuthenticationEntry();
+ if (authNEntry == null)
+ {
+ identifier = "null";
+ }
+ else
+ {
+ identifier = authNEntry.getDN().toString();
+ identifier = identifier.replace(',', '-');
+ identifier = identifier.replace(' ', '-');
+ identifier = identifier.replace('=', '-');
+ }
+
+ String[] fileLines =
+ {
+ "dn: cn=schema",
+ "objectClass: top",
+ "objectClass: ldapSubentry",
+ "objectClass: subschema",
+ "attributeTypes: ( " + identifier.toLowerCase() + "-oid " +
+ "NAME '" + identifier + "' )"
+ };
+
+ File validFile = new File(schemaDirectory, "05-" + identifier + ".ldif");
+ BufferedWriter writer = new BufferedWriter(new FileWriter(validFile));
+ for (String line : fileLines)
+ {
+ writer.write(line);
+ writer.newLine();
+ }
+ writer.close();
+
+ Entry taskEntry = TestCaseUtils.makeEntry(
+ "dn: ds-task-id=" + UUID.randomUUID() + ",cn=Scheduled Tasks,cn=Tasks",
+ "objectClass: top",
+ "objectClass: ds-task",
+ "objectClass: ds-task-add-schema-file",
+ "ds-task-class-name: org.opends.server.tasks.AddSchemaFileTask",
+ "ds-task-schema-file-name: 05-" + identifier + ".ldif");
+
+ AddOperation addOperation =
+ conn.processAdd(taskEntry.getDN(), taskEntry.getObjectClasses(),
+ taskEntry.getUserAttributes(),
+ taskEntry.getOperationalAttributes());
+
+ if (hasPrivilege)
+ {
+ assertEquals(addOperation.getResultCode(), ResultCode.SUCCESS);
+
+ Task task = getCompletedTask(taskEntry.getDN());
+ assertNotNull(task);
+ assertTrue(TaskState.isSuccessful(task.getTaskState()));
+ }
+ else
+ {
+ assertEquals(addOperation.getResultCode(),
+ ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
+ }
+ }
+
+
+
+ /**
+ * Tests to ensure that attempts to backup the Directory Server backends
+ * will properly respect the BACKEND_BACKUP privilege.
+ *
+ * @param conn The client connection to use to perform the backup.
+ * @param hasPrivilege Indicates whether the authenticated user is expected
+ * to have the BACKEND_BACKUP privilege and therefore
+ * the backup should succeed.
+ *
+ * @throws Exception If an unexpected problem occurs.
+ */
+ @Test(dataProvider = "testdata", groups = { "slow" })
+ public void testBackupBackend(JmxClientConnection conn,
+ boolean hasPrivilege)
+ throws Exception
+ {
+ // We have to sleep here because the backup ID that gets generated will be
+ // based on a timestamp and we don't want two in the same second.
+ Thread.sleep(1100);
+
+ assertEquals(conn.hasPrivilege(Privilege.JMX_READ, null),
+ hasPrivilege);
+
+ Entry taskEntry = TestCaseUtils.makeEntry(
+ "dn: ds-task-id=" + UUID.randomUUID() + ",cn=Scheduled Tasks,cn=Tasks",
+ "objectclass: top",
+ "objectclass: ds-task",
+ "objectclass: ds-task-backup",
+ "ds-task-class-name: org.opends.server.tasks.BackupTask",
+ "ds-backup-directory-path: bak",
+ "ds-task-backup-all: TRUE");
+
+ AddOperation addOperation =
+ conn.processAdd(taskEntry.getDN(), taskEntry.getObjectClasses(),
+ taskEntry.getUserAttributes(),
+ taskEntry.getOperationalAttributes());
+
+ if (hasPrivilege)
+ {
+ assertEquals(addOperation.getResultCode(), ResultCode.SUCCESS);
+
+ Task task = getCompletedTask(taskEntry.getDN());
+ assertNotNull(task);
+ assertTrue(TaskState.isSuccessful(task.getTaskState()));
+ }
+ else
+ {
+ assertEquals(addOperation.getResultCode(),
+ ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
+ }
+ }
+
+
+
+ /**
+ * Tests to ensure that attempts to restore the Directory Server backends
+ * will properly respect the BACKEND_RESTORE privilege.
+ *
+ * @param conn The client connection to use to perform the restore.
+ * @param hasPrivilege Indicates whether the authenticated user is expected
+ * to have the BACKEND_RESTORE privilege and therefore
+ * the restore should succeed.
+ *
+ * @throws Exception If an unexpected problem occurs.
+ */
+ @Test(enabled = false, dataProvider = "testdata", groups = { "slow" },
+ dependsOnMethods = { "testBackupBackend" })
+ public void testRestoreBackend(JmxClientConnection conn,
+ boolean hasPrivilege)
+ throws Exception
+ {
+ assertEquals(conn.hasPrivilege(Privilege.JMX_WRITE, null),
+ hasPrivilege);
+
+ Entry taskEntry = TestCaseUtils.makeEntry(
+ "dn: ds-task-id=" + UUID.randomUUID() + ",cn=Scheduled Tasks,cn=Tasks",
+ "objectclass: top",
+ "objectclass: ds-task",
+ "objectclass: ds-task-restore",
+ "ds-task-class-name: org.opends.server.tasks.RestoreTask",
+ "ds-backup-directory-path: bak" + File.separator + "userRoot");
+
+ AddOperation addOperation =
+ conn.processAdd(taskEntry.getDN(), taskEntry.getObjectClasses(),
+ taskEntry.getUserAttributes(),
+ taskEntry.getOperationalAttributes());
+
+ if (hasPrivilege)
+ {
+ assertEquals(addOperation.getResultCode(), ResultCode.SUCCESS);
+
+ Task task = getCompletedTask(taskEntry.getDN());
+ assertNotNull(task);
+ assertTrue(TaskState.isSuccessful(task.getTaskState()));
+ }
+ else
+ {
+ assertEquals(addOperation.getResultCode(),
+ ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
+ }
+ }
+
+
+
+ /**
+ * Tests to ensure that attempts to export the contents of a Directory Server
+ * backend will properly respect the LDIF_EXPORT privilege.
+ *
+ * @param conn The client connection to use to perform the export.
+ * @param hasPrivilege Indicates whether the authenticated user is expected
+ * to have the LDIF_EXPORT privilege and therefore
+ * the export should succeed.
+ *
+ * @throws Exception If an unexpected problem occurs.
+ */
+ @Test(dataProvider = "testdata", groups = { "slow" })
+ public void testLDIFExport(JmxClientConnection conn,
+ boolean hasPrivilege)
+ throws Exception
+ {
+ assertEquals(conn.hasPrivilege(Privilege.JMX_READ, null), hasPrivilege);
+
+ File tempFile = File.createTempFile("export-", ".ldif");
+ String tempFilePath = tempFile.getAbsolutePath();
+ tempFile.delete();
+
+ Entry taskEntry = TestCaseUtils.makeEntry(
+ "dn: ds-task-id=" + UUID.randomUUID() + ",cn=Scheduled Tasks,cn=Tasks",
+ "objectclass: top",
+ "objectclass: ds-task",
+ "objectclass: ds-task-export",
+ "ds-task-class-name: org.opends.server.tasks.ExportTask",
+ "ds-task-export-backend-id: userRoot",
+ "ds-task-export-ldif-file: " + tempFilePath);
+
+ AddOperation addOperation =
+ conn.processAdd(taskEntry.getDN(), taskEntry.getObjectClasses(),
+ taskEntry.getUserAttributes(),
+ taskEntry.getOperationalAttributes());
+
+ if (hasPrivilege)
+ {
+ assertEquals(addOperation.getResultCode(), ResultCode.SUCCESS);
+
+ Task task = getCompletedTask(taskEntry.getDN());
+ assertNotNull(task);
+ assertTrue(TaskState.isSuccessful(task.getTaskState()));
+
+ tempFile.delete();
+ }
+ else
+ {
+ assertEquals(addOperation.getResultCode(),
+ ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
+ }
+ }
+
+
+
+ /**
+ * Tests to ensure that attempts to import into a Directory Server backend
+ * will properly respect the LDIF_IMPORT privilege.
+ *
+ * @param conn The client connection to use to perform the import.
+ * @param hasPrivilege Indicates whether the authenticated user is expected
+ * to have the LDIF_IMPORT privilege and therefore
+ * the import should succeed.
+ *
+ * @throws Exception If an unexpected problem occurs.
+ */
+ @Test(dataProvider = "testdata", groups = { "slow" })
+ public void testLDIFImport(JmxClientConnection conn,
+ boolean hasPrivilege)
+ throws Exception
+ {
+ assertEquals(conn.hasPrivilege(Privilege.JMX_WRITE, null), hasPrivilege);
+
+ String path = TestCaseUtils.createTempFile(
+ "dn: dc=example,dc=com",
+ "objectClass: top",
+ "objectClass: domain",
+ "dc: example");
+
+ Entry taskEntry = TestCaseUtils.makeEntry(
+ "dn: ds-task-id=" + UUID.randomUUID() + ",cn=Scheduled Tasks,cn=Tasks",
+ "objectclass: top",
+ "objectclass: ds-task",
+ "objectclass: ds-task-import",
+ "ds-task-class-name: org.opends.server.tasks.ImportTask",
+ "ds-task-import-backend-id: userRoot",
+ "ds-task-import-ldif-file: " + path);
+
+ AddOperation addOperation =
+ conn.processAdd(taskEntry.getDN(), taskEntry.getObjectClasses(),
+ taskEntry.getUserAttributes(),
+ taskEntry.getOperationalAttributes());
+
+ if (hasPrivilege)
+ {
+ assertEquals(addOperation.getResultCode(), ResultCode.SUCCESS);
+
+ Task task = getCompletedTask(taskEntry.getDN());
+ assertNotNull(task);
+ assertTrue(TaskState.isSuccessful(task.getTaskState()));
+ }
+ else
+ {
+ assertEquals(addOperation.getResultCode(),
+ ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
+ }
+ }
+
+ /**
+ * Test to ensure that attempts to rebuild indexes will property respect
+ * the LDIF_IMPORT privilege.
+ *
+ * @param conn The client connection to use to perform the rebuild.
+ * @param hasPrivilege Indicates weather the authenticated user is
+ * expected to have the INDEX_REBUILD privilege
+ * and therefore the rebuild should succeed.
+ * @throws Exception if an unexpected problem occurs.
+ */
+ @Test(dataProvider = "testdata", groups = { "slow" })
+ public void testRebuildIndex(JmxClientConnection conn,
+ boolean hasPrivilege)
+ throws Exception
+ {
+ assertEquals(conn.hasPrivilege(Privilege.JMX_WRITE, null), hasPrivilege);
+
+ Entry taskEntry = TestCaseUtils.makeEntry(
+ "dn: ds-task-id=" + UUID.randomUUID() + ",cn=Scheduled Tasks,cn=Tasks",
+ "objectclass: top",
+ "objectclass: ds-task",
+ "objectclass: ds-task-rebuild",
+ "ds-task-class-name: org.opends.server.tasks.RebuildTask",
+ "ds-task-rebuild-base-dn: dc=example,dc=com",
+ "ds-task-rebuild-index: cn");
+
+ AddOperation addOperation =
+ conn.processAdd(taskEntry.getDN(), taskEntry.getObjectClasses(),
+ taskEntry.getUserAttributes(),
+ taskEntry.getOperationalAttributes());
+
+ if (hasPrivilege)
+ {
+ assertEquals(addOperation.getResultCode(), ResultCode.SUCCESS);
+
+ Task task = getCompletedTask(taskEntry.getDN());
+ assertNotNull(task);
+ assertTrue(TaskState.isSuccessful(task.getTaskState()));
+ }
+ else
+ {
+ assertEquals(addOperation.getResultCode(),
+ ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
+ }
+ }
+
+
+
+ /**
+ * Tests to ensure that the use of the Directory Server will properly respect
+ * the PROXIED_AUTH privilege for add, delete, modify and modify DN requests
+ * that contain the proxied auth v1 control.
+ *
+ * @param conn The client connection to use to perform the
+ * operation.
+ * @param hasPrivilege Indicates whether the authenticated user is expected
+ * to have the PROXIED_AUTH privilege and therefore
+ * the operation should succeed.
+ *
+ * @throws Exception If an unexpected problem occurs.
+ */
+ @Test(dataProvider = "testdata")
+ public void testProxyAuthV1Write(JmxClientConnection conn,
+ boolean hasPrivilege)
+ throws Exception
+ {
+ // We can't trust the value of hasPrivilege because root users don't get
+ // proxy privileges by default. So make the determination based on the
+ // privileges the user actually has.
+ boolean hasProxyPrivilege = conn.hasPrivilege(Privilege.PROXIED_AUTH, null);
+
+ Entry e = TestCaseUtils.makeEntry(
+ "dn: cn=ProxyV1 Test,o=test",
+ "objectClass: top",
+ "objectClass: person",
+ "objectClass: organizationalPerson",
+ "objectClass: inetOrgPerson",
+ "cn: ProxyV1 Test",
+ "givenName: ProxyV1",
+ "sn: Test");
+
+ ArrayList<Control> controls = new ArrayList<Control>(1);
+ controls.add(new ProxiedAuthV1Control(
+ DN.decode("cn=PWReset Target,o=test")));
+
+
+ // Try to add the entry. If this fails with the proxy control, then add it
+ // with a root connection so we can do other things with it.
+ AddOperationBasis addOperation =
+ new AddOperationBasis(conn, conn
+ .nextOperationID(), conn.nextMessageID(), controls, e.getDN(), e
+ .getObjectClasses(), e.getUserAttributes(), e
+ .getOperationalAttributes());
+ addOperation.run();
+
+ if (hasProxyPrivilege)
+ {
+ assertEquals(addOperation.getResultCode(), ResultCode.SUCCESS);
+ }
+ else
+ {
+ assertEquals(addOperation.getResultCode(),
+ ResultCode.AUTHORIZATION_DENIED);
+ TestCaseUtils.addEntry(e);
+ }
+
+
+ // Try to modify the entry to add a description.
+ ArrayList<Modification> mods = new ArrayList<Modification>(1);
+ mods.add(new Modification(ModificationType.REPLACE,
+ new Attribute("description", "foo")));
+
+ ModifyOperationBasis modifyOperation = new ModifyOperationBasis(conn,
+ conn.nextOperationID(), conn.nextMessageID(), controls, e.getDN(),
+ mods);
+ modifyOperation.run();
+
+ if (hasProxyPrivilege)
+ {
+ assertEquals(modifyOperation.getResultCode(), ResultCode.SUCCESS);
+ }
+ else
+ {
+ assertEquals(modifyOperation.getResultCode(),
+ ResultCode.AUTHORIZATION_DENIED);
+ }
+
+
+ // Try to rename the entry.
+ ModifyDNOperation modifyDNOperation =
+ new ModifyDNOperation(conn, conn.nextOperationID(),
+ conn.nextMessageID(), controls, e.getDN(),
+ RDN.decode("cn=Proxy V1 Test"), true, null);
+ modifyDNOperation.run();
+
+ DN newEntryDN;
+ if (hasProxyPrivilege)
+ {
+ assertEquals(modifyDNOperation.getResultCode(), ResultCode.SUCCESS);
+ newEntryDN = modifyDNOperation.getUpdatedEntry().getDN();
+ }
+ else
+ {
+ assertEquals(modifyDNOperation.getResultCode(),
+ ResultCode.AUTHORIZATION_DENIED);
+ newEntryDN = e.getDN();
+ }
+
+
+ // Try to delete the operation. If this fails, then delete it with a root
+ // connection so it gets cleaned up.
+ DeleteOperationBasis deleteOperation =
+ new DeleteOperationBasis(conn,
+ conn.nextOperationID(), conn.nextMessageID(), controls, newEntryDN);
+ deleteOperation.run();
+
+ if (hasProxyPrivilege)
+ {
+ assertEquals(deleteOperation.getResultCode(), ResultCode.SUCCESS);
+ }
+ else
+ {
+ assertEquals(deleteOperation.getResultCode(),
+ ResultCode.AUTHORIZATION_DENIED);
+
+ InternalClientConnection rootConnection =
+ InternalClientConnection.getRootConnection();
+ DeleteOperation delOp = rootConnection.processDelete(newEntryDN);
+ assertEquals(delOp.getResultCode(), ResultCode.SUCCESS);
+ }
+ }
+
+
+
+ /**
+ * Tests to ensure that the use of the Directory Server will properly respect
+ * the PROXIED_AUTH privilege for search and compare requests that contain the
+ * proxied auth v1 control.
+ *
+ * @param conn The client connection to use to perform the
+ * operation.
+ * @param hasPrivilege Indicates whether the authenticated user is expected
+ * to have the PROXIED_AUTH privilege and therefore
+ * the operation should succeed.
+ *
+ * @throws Exception If an unexpected problem occurs.
+ */
+ @Test(dataProvider = "testdata")
+ public void testProxyAuthV1Read(JmxClientConnection conn,
+ boolean hasPrivilege)
+ throws Exception
+ {
+ // We can't trust the value of hasPrivilege because root users don't get
+ // proxy privileges by default. So make the determination based on the
+ // privileges the user actually has.
+ boolean hasProxyPrivilege = conn.hasPrivilege(Privilege.PROXIED_AUTH, null);
+
+ DN targetDN = DN.decode("cn=PWReset Target,o=test");
+ ArrayList<Control> controls = new ArrayList<Control>(1);
+ controls.add(new ProxiedAuthV1Control(targetDN));
+
+
+ // Test a compare operation against the PWReset Target user.
+ CompareOperation compareOperation =
+ new CompareOperation(conn, conn.nextOperationID(),
+ conn.nextMessageID(), controls, targetDN,
+ DirectoryServer.getAttributeType("cn", true),
+ ByteStringFactory.create("PWReset Target"));
+ compareOperation.run();
+
+ if (hasProxyPrivilege)
+ {
+ assertEquals(compareOperation.getResultCode(), ResultCode.COMPARE_TRUE);
+ }
+ else
+ {
+ assertEquals(compareOperation.getResultCode(),
+ ResultCode.AUTHORIZATION_DENIED);
+ }
+
+
+ // Test a search operation against the PWReset Target user.
+ InternalSearchOperation searchOperation =
+ new InternalSearchOperation(conn, conn.nextOperationID(),
+ conn.nextMessageID(), controls, targetDN,
+ SearchScope.BASE_OBJECT,
+ DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false,
+ SearchFilter.createFilterFromString("(objectClass=*)"), null,
+ null);
+ searchOperation.run();
+
+ if (hasProxyPrivilege)
+ {
+ assertEquals(searchOperation.getResultCode(), ResultCode.SUCCESS);
+ }
+ else
+ {
+ assertEquals(searchOperation.getResultCode(),
+ ResultCode.AUTHORIZATION_DENIED);
+ }
+ }
+
+
+
+ /**
+ * Tests to ensure that the use of the Directory Server will properly respect
+ * the PROXIED_AUTH privilege for add, delete, modify and modify DN requests
+ * that contain the proxied auth v2 control.
+ *
+ * @param conn The client connection to use to perform the
+ * operation.
+ * @param hasPrivilege Indicates whether the authenticated user is expected
+ * to have the PROXIED_AUTH privilege and therefore
+ * the operation should succeed.
+ *
+ * @throws Exception If an unexpected problem occurs.
+ */
+ @Test(dataProvider = "testdata")
+ public void testProxyAuthV2Write(JmxClientConnection conn,
+ boolean hasPrivilege)
+ throws Exception
+ {
+ // We can't trust the value of hasPrivilege because root users don't get
+ // proxy privileges by default. So make the determination based on the
+ // privileges the user actually has.
+ boolean hasProxyPrivilege = conn.hasPrivilege(Privilege.PROXIED_AUTH, null);
+
+ Entry e = TestCaseUtils.makeEntry(
+ "dn: cn=ProxyV2 Test,o=test",
+ "objectClass: top",
+ "objectClass: person",
+ "objectClass: organizationalPerson",
+ "objectClass: inetOrgPerson",
+ "cn: ProxyV2 Test",
+ "givenName: ProxyV2",
+ "sn: Test");
+
+ ArrayList<Control> controls = new ArrayList<Control>(1);
+ controls.add(new ProxiedAuthV2Control(
+ new ASN1OctetString("dn:cn=PWReset Target,o=test")));
+
+
+ // Try to add the entry. If this fails with the proxy control, then add it
+ // with a root connection so we can do other things with it.
+ DN authDN = conn.getAuthenticationInfo().getAuthenticationDN();
+ AddOperationBasis addOperation =
+ new AddOperationBasis(conn, conn
+ .nextOperationID(), conn.nextMessageID(), controls, e.getDN(), e
+ .getObjectClasses(), e.getUserAttributes(), e
+ .getOperationalAttributes());
+ addOperation.run();
+
+ if (hasProxyPrivilege)
+ {
+ assertEquals(addOperation.getResultCode(), ResultCode.SUCCESS,
+ "Unexpected add failure for user " + authDN);
+ }
+ else
+ {
+ assertEquals(addOperation.getResultCode(),
+ ResultCode.AUTHORIZATION_DENIED,
+ "Unexpected add success for user " + authDN);
+ TestCaseUtils.addEntry(e);
+ }
+
+
+ // Try to modify the entry to add a description.
+ ArrayList<Modification> mods = new ArrayList<Modification>(1);
+ mods.add(new Modification(ModificationType.REPLACE,
+ new Attribute("description", "foo")));
+
+ ModifyOperationBasis modifyOperation =
+ new ModifyOperationBasis(conn,
+ conn.nextOperationID(), conn.nextMessageID(), controls, e.getDN(),
+ mods);
+ modifyOperation.run();
+
+ if (hasProxyPrivilege)
+ {
+ assertEquals(modifyOperation.getResultCode(), ResultCode.SUCCESS,
+ "Unexpected mod failure for user " + authDN);
+ }
+ else
+ {
+ assertEquals(modifyOperation.getResultCode(),
+ ResultCode.AUTHORIZATION_DENIED,
+ "Unexpected mod success for user " + authDN);
+ }
+
+
+ // Try to rename the entry.
+ ModifyDNOperation modifyDNOperation =
+ new ModifyDNOperation(conn, conn.nextOperationID(),
+ conn.nextMessageID(), controls, e.getDN(),
+ RDN.decode("cn=Proxy V2 Test"), true, null);
+ modifyDNOperation.run();
+
+ DN newEntryDN;
+ if (hasProxyPrivilege)
+ {
+ assertEquals(modifyDNOperation.getResultCode(), ResultCode.SUCCESS,
+ "Unexpected moddn failure for user " + authDN);
+ newEntryDN = modifyDNOperation.getUpdatedEntry().getDN();
+ }
+ else
+ {
+ assertEquals(modifyDNOperation.getResultCode(),
+ ResultCode.AUTHORIZATION_DENIED,
+ "Unexpected moddn success for user " + authDN);
+ newEntryDN = e.getDN();
+ }
+
+
+ // Try to delete the operation. If this fails, then delete it with a root
+ // connection so it gets cleaned up.
+ DeleteOperationBasis deleteOperation =
+ new DeleteOperationBasis(conn,
+ conn.nextOperationID(), conn.nextMessageID(), controls, newEntryDN);
+ deleteOperation.run();
+
+ if (hasProxyPrivilege)
+ {
+ assertEquals(deleteOperation.getResultCode(), ResultCode.SUCCESS,
+ "Unexpected delete failure for user " + authDN);
+ }
+ else
+ {
+ assertEquals(deleteOperation.getResultCode(),
+ ResultCode.AUTHORIZATION_DENIED,
+ "Unexpected delete success for user " + authDN);
+
+ InternalClientConnection rootConnection =
+ InternalClientConnection.getRootConnection();
+ DeleteOperation delOp = rootConnection.processDelete(newEntryDN);
+ assertEquals(delOp.getResultCode(), ResultCode.SUCCESS);
+ }
+ }
+
+
+
+ /**
+ * Tests to ensure that the use of the Directory Server will properly respect
+ * the PROXIED_AUTH privilege for search and compare requests that contain the
+ * proxied auth v2 control.
+ *
+ * @param conn The client connection to use to perform the
+ * operation.
+ * @param hasPrivilege Indicates whether the authenticated user is expected
+ * to have the PROXIED_AUTH privilege and therefore
+ * the operation should succeed.
+ *
+ * @throws Exception If an unexpected problem occurs.
+ */
+ @Test(dataProvider = "testdata")
+ public void testProxyAuthV2Read(JmxClientConnection conn,
+ boolean hasPrivilege)
+ throws Exception
+ {
+ // We can't trust the value of hasPrivilege because root users don't get
+ // proxy privileges by default. So make the determination based on the
+ // privileges the user actually has.
+ boolean hasProxyPrivilege = conn.hasPrivilege(Privilege.PROXIED_AUTH, null);
+
+ DN targetDN = DN.decode("cn=PWReset Target,o=test");
+ ArrayList<Control> controls = new ArrayList<Control>(1);
+ controls.add(new ProxiedAuthV2Control(
+ new ASN1OctetString("dn:" + targetDN.toString())));
+
+
+ // Test a compare operation against the PWReset Target user.
+ CompareOperation compareOperation =
+ new CompareOperation(conn, conn.nextOperationID(),
+ conn.nextMessageID(), controls, targetDN,
+ DirectoryServer.getAttributeType("cn", true),
+ ByteStringFactory.create("PWReset Target"));
+ compareOperation.run();
+
+ if (hasProxyPrivilege)
+ {
+ assertEquals(compareOperation.getResultCode(), ResultCode.COMPARE_TRUE);
+ }
+ else
+ {
+ assertEquals(compareOperation.getResultCode(),
+ ResultCode.AUTHORIZATION_DENIED);
+ }
+
+
+ // Test a search operation against the PWReset Target user.
+ InternalSearchOperation searchOperation =
+ new InternalSearchOperation(conn, conn.nextOperationID(),
+ conn.nextMessageID(), controls, targetDN,
+ SearchScope.BASE_OBJECT,
+ DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false,
+ SearchFilter.createFilterFromString("(objectClass=*)"), null,
+ null);
+ searchOperation.run();
+
+ if (hasProxyPrivilege)
+ {
+ assertEquals(searchOperation.getResultCode(), ResultCode.SUCCESS);
+ }
+ else
+ {
+ assertEquals(searchOperation.getResultCode(),
+ ResultCode.AUTHORIZATION_DENIED);
+ }
+ }
+
+
+ /**
+ * Tests the ability to update the set of privileges for a user on the fly
+ * and have them take effect immediately.
+ *
+ * @throws Exception If an unexpected problem occurs.
+ */
+ @Test()
+ public void testUpdateUserPrivileges()
+ throws Exception
+ {
+ InternalClientConnection rootConnection =
+ InternalClientConnection.getRootConnection();
+
+ TestCaseUtils.addEntry(
+ "dn: cn=Test User,o=test",
+ "objectClass: top",
+ "objectClass: person",
+ "objectClass: organizationalPerson",
+ "objectClass: inetOrgPerson",
+ "cn: Test User",
+ "givenName: Test",
+ "sn: User",
+ "userPassword: password");
+
+
+ Entry testEntry =
+ DirectoryServer.getEntry(DN.decode("cn=Test User,o=test"));
+ AuthenticationInfo authInfo = new AuthenticationInfo(testEntry, false);
+ JmxConnectionHandler jmxCtx = getJmxConnectionHandler();
+ JmxClientConnection testConnection =
+ new JmxClientConnection(jmxCtx,authInfo);
+
+
+ // Make sure the user starts out without any privileges.
+ for (Privilege p : Privilege.values())
+ {
+ assertFalse(testConnection.hasPrivilege(p, null));
+ }
+
+
+ // Modify the user entry to add the JMX_READ privilege and verify that
+ // the client connection reflects that.
+ ArrayList<Modification> mods = new ArrayList<Modification>();
+ mods.add(new Modification(ModificationType.ADD,
+ new Attribute("ds-privilege-name", "jmx-read")));
+ ModifyOperation modifyOperation =
+ rootConnection.processModify(DN.decode("cn=Test User,o=test"), mods);
+ assertEquals(modifyOperation.getResultCode(), ResultCode.SUCCESS);
+ assertTrue(testConnection.hasPrivilege(Privilege.JMX_READ, null));
+
+
+ // Take the privilege away from the user and verify that it is recognized
+ // immediately.
+ mods.clear();
+ mods.add(new Modification(ModificationType.DELETE,
+ new Attribute("ds-privilege-name", "jmx-read")));
+ modifyOperation =
+ rootConnection.processModify(DN.decode("cn=Test User,o=test"), mods);
+ assertEquals(modifyOperation.getResultCode(), ResultCode.SUCCESS);
+ assertFalse(testConnection.hasPrivilege(Privilege.JMX_READ, null));
+
+
+ DeleteOperation deleteOperation =
+ rootConnection.processDelete(DN.decode("cn=Test User,o=test"));
+ assertEquals(deleteOperation.getResultCode(), ResultCode.SUCCESS);
+ }
+
+
+
+ /**
+ * Tests the ability to update the set of root privileges and have them take
+ * effect immediately for new root connections.
+ *
+ * @throws Exception If an unexpected problem occurs.
+ */
+ @Test()
+ public void testUpdateRootPrivileges()
+ throws Exception
+ {
+ // Make sure that a root connection doesn't have the proxied auth
+ // privilege.
+ DN unprivRootDN = DN.decode("cn=Unprivileged Root,cn=Root DNs,cn=config");
+ Entry unprivRootEntry = DirectoryServer.getEntry(unprivRootDN);
+ AuthenticationInfo authInfo = new AuthenticationInfo(unprivRootEntry, true);
+ JmxConnectionHandler jmxCtx = getJmxConnectionHandler();
+ JmxClientConnection unprivRootConn =
+ new JmxClientConnection(jmxCtx,authInfo);
+ assertFalse(unprivRootConn.hasPrivilege(Privilege.PROXIED_AUTH, null));
+
+
+ // Update the set of root privileges to include proxied auth.
+ InternalClientConnection internalRootConn =
+ InternalClientConnection.getRootConnection();
+
+ ArrayList<Modification> mods = new ArrayList<Modification>();
+ mods.add(new Modification(ModificationType.ADD,
+ new Attribute("ds-cfg-default-root-privilege-name",
+ "proxied-auth")));
+ ModifyOperation modifyOperation =
+ internalRootConn.processModify(DN.decode("cn=Root DNs,cn=config"),
+ mods);
+ assertEquals(modifyOperation.getResultCode(), ResultCode.SUCCESS);
+
+
+ // Get a new root connection and verify that it now has proxied auth.
+ unprivRootEntry = DirectoryServer.getEntry(unprivRootDN);
+ authInfo = new AuthenticationInfo(unprivRootEntry, true);
+ unprivRootConn = new JmxClientConnection(jmxCtx,authInfo);
+ assertTrue(unprivRootConn.hasPrivilege(Privilege.PROXIED_AUTH, null));
+
+
+ // Update the set of root privileges to revoke proxied auth.
+ mods.clear();
+ mods.add(new Modification(ModificationType.DELETE,
+ new Attribute("ds-cfg-default-root-privilege-name",
+ "proxied-auth")));
+ modifyOperation =
+ internalRootConn.processModify(DN.decode("cn=Root DNs,cn=config"),
+ mods);
+ assertEquals(modifyOperation.getResultCode(), ResultCode.SUCCESS);
+
+
+ // Get a new root connection and verify that it no longer has proxied auth.
+ unprivRootEntry = DirectoryServer.getEntry(unprivRootDN);
+ authInfo = new AuthenticationInfo(unprivRootEntry, true);
+ unprivRootConn = new JmxClientConnection(jmxCtx,authInfo);
+ assertFalse(unprivRootConn.hasPrivilege(Privilege.PROXIED_AUTH, null));
+ }
+
+
+
+ /**
+ * 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;
+ }
+}
+
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/jmx/postConnectedDisconnectTest.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/jmx/postConnectedDisconnectTest.java
index 04363eb..d01c65a 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/jmx/postConnectedDisconnectTest.java
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/jmx/postConnectedDisconnectTest.java
@@ -30,7 +30,13 @@
import java.util.HashMap;
import org.opends.server.TestCaseUtils;
+import org.opends.server.core.DeleteOperation;
import org.opends.server.plugins.InvocationCounterPlugin;
+import org.opends.server.protocols.internal.InternalClientConnection;
+import org.opends.server.types.DN;
+import org.opends.server.types.ResultCode;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import static org.testng.Assert.*;
@@ -40,6 +46,63 @@
*/
public class postConnectedDisconnectTest extends JmxTestCase
{
+
+ /**
+ * Set up the environment for performing the tests in this suite.
+ *
+ * @throws Exception
+ * If the environment could not be set up.
+ */
+ @BeforeClass
+ public void setUp() throws Exception
+ {
+ super.setUp();
+
+ TestCaseUtils.addEntries(
+ "dn: cn=Privileged User,o=test",
+ "objectClass: top",
+ "objectClass: person",
+ "objectClass: organizationalPerson",
+ "objectClass: inetOrgPerson",
+ "cn: Privileged User",
+ "givenName: Privileged",
+ "sn: User",
+ "uid: privileged.user",
+ "userPassword: password",
+ "ds-privilege-name: config-read",
+ "ds-privilege-name: config-write",
+ "ds-privilege-name: password-reset",
+ "ds-privilege-name: update-schema",
+ "ds-privilege-name: ldif-import",
+ "ds-privilege-name: ldif-export",
+ "ds-privilege-name: backend-backup",
+ "ds-privilege-name: backend-restore",
+ "ds-privilege-name: proxied-auth",
+ "ds-privilege-name: bypass-acl",
+ "ds-privilege-name: unindexed-search",
+ "ds-privilege-name: jmx-read",
+ "ds-privilege-name: jmx-write",
+ "ds-pwp-password-policy-dn: cn=Clear UserPassword Policy," +
+ "cn=Password Policies,cn=config");
+ }
+
+ /**
+ * Clean up the environment after performing the tests in this suite.
+ *
+ * @throws Exception
+ * If the environment could not be set up.
+ */
+ @AfterClass
+ public void afterClass() throws Exception
+ {
+ InternalClientConnection conn = InternalClientConnection
+ .getRootConnection();
+
+ DeleteOperation deleteOperation = conn.processDelete(DN
+ .decode("cn=Privileged User,o=test"));
+ assertEquals(deleteOperation.getResultCode(), ResultCode.SUCCESS);
+ }
+
/**
* Perform a simple connect.
* @throws Exception If something wrong occurs.
@@ -54,7 +117,8 @@
// Create a new client connection
HashMap<String, Object> env = new HashMap<String, Object>();
- String[] credentials = new String[] { "cn=directory manager" , "password"};
+ String[] credentials = new String[] { "cn=Privileged User,o=test",
+ "password" };
env.put("jmx.remote.credentials", credentials);
env.put("jmx.remote.x.client.connection.check.period",0);
OpendsJmxConnector opendsConnector = new OpendsJmxConnector("localhost",
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/types/PrivilegeTestCase.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/types/PrivilegeTestCase.java
index bdd0828..87e4bf6 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/types/PrivilegeTestCase.java
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/types/PrivilegeTestCase.java
@@ -303,6 +303,38 @@
conn.processDelete(
DN.decode("cn=Unprivileged Root,cn=Root DNs,cn=config"));
assertEquals(deleteOperation.getResultCode(), ResultCode.SUCCESS);
+
+ deleteOperation =
+ conn.processDelete(
+ DN.decode("cn=Proxy Root,cn=Root DNs,cn=config"));
+ assertEquals(deleteOperation.getResultCode(), ResultCode.SUCCESS);
+
+ deleteOperation =
+ conn.processDelete(
+ DN.decode("cn=Privileged User,o=test"));
+ assertEquals(deleteOperation.getResultCode(), ResultCode.SUCCESS);
+
+ deleteOperation =
+ conn.processDelete(
+ DN.decode("cn=UnPrivileged User,o=test"));
+ assertEquals(deleteOperation.getResultCode(), ResultCode.SUCCESS);
+
+ deleteOperation =
+ conn.processDelete(
+ DN.decode("cn=PWReset Target,o=test"));
+ assertEquals(deleteOperation.getResultCode(), ResultCode.SUCCESS);
+
+ deleteOperation = conn.processDelete(DN
+ .decode("cn=test1 user,dc=unindexed,dc=jeb"));
+ assertEquals(deleteOperation.getResultCode(), ResultCode.SUCCESS);
+
+ deleteOperation = conn.processDelete(DN
+ .decode("cn=test2 user,dc=unindexed,dc=jeb"));
+ assertEquals(deleteOperation.getResultCode(), ResultCode.SUCCESS);
+
+ deleteOperation = conn.processDelete(DN
+ .decode("dc=unindexed,dc=jeb"));
+ assertEquals(deleteOperation.getResultCode(), ResultCode.SUCCESS);
}
--
Gitblit v1.10.0