opends/resource/config/config.ldif
@@ -82,7 +82,6 @@ ds-cfg-global-aci: (targetattr="createTimestamp||creatorsName||modifiersName||modifyTimestamp||entryDN||entryUUID||subschemaSubentry")(version 3.0; acl "User-Visible Operational Attributes"; allow (read,search,compare) userdn="ldap:///anyone";) ds-cfg-global-aci: (target="ldap:///dc=replicationchanges")(targetattr="*")(version 3.0; acl "Replication backend access"; deny (all) userdn="ldap:///anyone";) ds-cfg-global-aci: (target="ldap:///cn=changelog")(targetattr="*")(version 3.0; acl "External changelog access"; deny (all) userdn="ldap:///anyone";) ds-cfg-global-aci: (targetfilter="(|(objectclass=subentry)(objectclass=ldapsubentry))")(version 3.0; acl "Subentry write access"; deny (add,write,delete) userdn="ldap:///anyone";) cn: Access Control Handler ds-cfg-java-class: org.opends.server.authorization.dseecompat.AciHandler ds-cfg-enabled: true @@ -1880,6 +1879,7 @@ ds-cfg-default-root-privilege-name: update-schema ds-cfg-default-root-privilege-name: privilege-change ds-cfg-default-root-privilege-name: unindexed-search ds-cfg-default-root-privilege-name: subentry-write dn: cn=Directory Manager,cn=Root DNs,cn=config objectClass: top opends/resource/schema/00-core.ldif
@@ -402,7 +402,7 @@ attributeTypes: ( 1.3.6.1.4.1.26027.1.1.621 NAME 'inheritFromDNAttribute' EQUALITY objectIdentifierMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.38 SINGLE-VALUE X-ORIGIN 'OpenDS Directory Server' ) attributeTypes: ( 1.3.6.1.4.1.26027.1.1.622 NAME 'inheritFromBaseDN' attributeTypes: ( 1.3.6.1.4.1.26027.1.1.622 NAME 'inheritFromBaseRDN' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 SINGLE-VALUE X-ORIGIN 'OpenDS Directory Server' ) attributeTypes: ( 1.3.6.1.4.1.26027.1.1.623 NAME 'inheritFromRDNType' @@ -689,7 +689,7 @@ 'inheritedFromRDNCollectiveAttributeSubentry' DESC 'Inherited from RDN Collective Attributes Subentry class' SUP inheritedCollectiveAttributeSubentry STRUCTURAL MUST ( inheritFromRDNAttribute $ inheritFromRDNType $ inheritFromBaseDN ) MUST ( inheritFromRDNAttribute $ inheritFromRDNType $ inheritFromBaseRDN ) X-ORIGIN 'OpenDS Directory Server' ) objectClasses: ( 2.16.840.1.113730.3.2.33 NAME 'groupOfURLs' DESC 'Sun-defined objectclass' SUP top STRUCTURAL MUST ( cn ) opends/src/admin/defn/org/opends/server/admin/std/GlobalConfiguration.xml
@@ -670,6 +670,12 @@ that cannot be optimized using server indexes. </adm:synopsis> </adm:value> <adm:value name="subentry-write"> <adm:synopsis> Allows the associated user to perform LDAP subentry write operations. </adm:synopsis> </adm:value> </adm:enumeration> </adm:syntax> <adm:profile name="ldap"> opends/src/admin/defn/org/opends/server/admin/std/RootDNConfiguration.xml
@@ -76,6 +76,7 @@ <adm:value>update-schema</adm:value> <adm:value>privilege-change</adm:value> <adm:value>unindexed-search</adm:value> <adm:value>subentry-write</adm:value> </adm:defined> </adm:default-behavior> <adm:syntax> @@ -210,6 +211,12 @@ that cannot be optimized using server indexes. </adm:synopsis> </adm:value> <adm:value name="subentry-write"> <adm:synopsis> Allows the associated user to perform LDAP subentry write operations. </adm:synopsis> </adm:value> </adm:enumeration> </adm:syntax> <adm:profile name="ldap"> opends/src/admin/messages/GlobalCfgDefn.properties
@@ -35,6 +35,7 @@ property.disabled-privilege.syntax.enumeration.value.server-lockdown.synopsis=Allows the user to place and bring the server of lockdown mode. property.disabled-privilege.syntax.enumeration.value.server-restart.synopsis=Allows the user to request that the server perform an in-core restart. property.disabled-privilege.syntax.enumeration.value.server-shutdown.synopsis=Allows the user to request that the server shut down. property.disabled-privilege.syntax.enumeration.value.subentry-write.synopsis=Allows the associated user to perform LDAP subentry write operations. property.disabled-privilege.syntax.enumeration.value.unindexed-search.synopsis=Allows the user to request that the server process a search that cannot be optimized using server indexes. property.disabled-privilege.syntax.enumeration.value.update-schema.synopsis=Allows the user to make changes to the server schema. property.entry-cache-preload.synopsis=Indicates whether or not to preload the entry cache on startup. opends/src/admin/messages/RootDNCfgDefn.properties
@@ -23,6 +23,7 @@ property.default-root-privilege-name.syntax.enumeration.value.server-lockdown.synopsis=Allows the user to place and bring the server of lockdown mode. property.default-root-privilege-name.syntax.enumeration.value.server-restart.synopsis=Allows the user to request that the server perform an in-core restart. property.default-root-privilege-name.syntax.enumeration.value.server-shutdown.synopsis=Allows the user to request that the server shut down. property.default-root-privilege-name.syntax.enumeration.value.subentry-write.synopsis=Allows the associated user to perform LDAP subentry write operations. property.default-root-privilege-name.syntax.enumeration.value.unindexed-search.synopsis=Allows the user to request that the server process a search that cannot be optimized using server indexes. property.default-root-privilege-name.syntax.enumeration.value.update-schema.synopsis=Allows the user to make changes to the server schema. relation.root-dn-user.user-friendly-name=Root DN User opends/src/ads/org/opends/admin/ads/ADSContext.java
@@ -1695,6 +1695,7 @@ privilege.add("update-schema"); privilege.add("privilege-change"); privilege.add("unindexed-search"); privilege.add("subentry-write"); return privilege; } opends/src/messages/messages/core.properties
@@ -1846,3 +1846,5 @@ operations each %d ms SEVERE_ERR_MAX_OPS_PER_INTERVAL_738=The value "%d" is not a valid value for \ the maximum number of operations per interval (must be positive) MILD_ERR_SUBENTRY_WRITE_INSUFFICIENT_PRIVILEGES_739=This operation involves \ LDAP subentries which you do not have sufficient privileges to administer opends/src/server/org/opends/server/core/CoreConfigManager.java
@@ -326,6 +326,9 @@ case UPDATE_SCHEMA: disabledPrivileges.add(Privilege.UPDATE_SCHEMA); break; case SUBENTRY_WRITE: disabledPrivileges.add(Privilege.SUBENTRY_WRITE); break; } } } opends/src/server/org/opends/server/core/RootPrivilegeChangeListener.java
@@ -188,6 +188,9 @@ case UNINDEXED_SEARCH: privSet.add(Privilege.UNINDEXED_SEARCH); break; case SUBENTRY_WRITE: privSet.add(Privilege.SUBENTRY_WRITE); break; } } opends/src/server/org/opends/server/core/SubentryManager.java
@@ -28,6 +28,7 @@ import org.opends.server.api.ClientConnection; import org.opends.server.api.SubtreeSpecification; import java.util.*; import java.util.concurrent.CopyOnWriteArrayList; @@ -52,6 +53,8 @@ import org.opends.server.types.DN; import org.opends.server.types.DirectoryException; import org.opends.server.types.Entry; import org.opends.server.types.Privilege; import org.opends.server.types.ResultCode; import org.opends.server.types.SearchResultEntry; import org.opends.server.types.SearchScope; import org.opends.server.types.SearchFilter; @@ -944,6 +947,15 @@ if (entry.isSubentry() || entry.isLDAPSubentry()) { ClientConnection conn = addOperation.getClientConnection(); if (!conn.hasPrivilege(Privilege.SUBENTRY_WRITE, conn.getOperationInProgress( addOperation.getMessageID()))) { return PluginResult.PreOperation.stopProcessing( ResultCode.INSUFFICIENT_ACCESS_RIGHTS, ERR_SUBENTRY_WRITE_INSUFFICIENT_PRIVILEGES.get()); } for (SubentryChangeListener changeListener : changeListeners) { @@ -975,12 +987,29 @@ PreOperationDeleteOperation deleteOperation) { Entry entry = deleteOperation.getEntryToDelete(); boolean hasSubentryWritePrivilege = false; lock.readLock().lock(); try { for (SubEntry subEntry : dit2SubEntry.getSubtree(entry.getDN())) { if (!hasSubentryWritePrivilege) { ClientConnection conn = deleteOperation.getClientConnection(); if (!conn.hasPrivilege(Privilege.SUBENTRY_WRITE, conn.getOperationInProgress( deleteOperation.getMessageID()))) { return PluginResult.PreOperation.stopProcessing( ResultCode.INSUFFICIENT_ACCESS_RIGHTS, ERR_SUBENTRY_WRITE_INSUFFICIENT_PRIVILEGES.get()); } else { hasSubentryWritePrivilege = true; } } for (SubentryChangeListener changeListener : changeListeners) { @@ -1023,6 +1052,15 @@ if ((newEntry.isSubentry() || newEntry.isLDAPSubentry()) || (oldEntry.isSubentry() || oldEntry.isLDAPSubentry())) { ClientConnection conn = modifyOperation.getClientConnection(); if (!conn.hasPrivilege(Privilege.SUBENTRY_WRITE, conn.getOperationInProgress( modifyOperation.getMessageID()))) { return PluginResult.PreOperation.stopProcessing( ResultCode.INSUFFICIENT_ACCESS_RIGHTS, ERR_SUBENTRY_WRITE_INSUFFICIENT_PRIVILEGES.get()); } for (SubentryChangeListener changeListener : changeListeners) { @@ -1058,6 +1096,7 @@ Entry newEntry = modifyDNOperation.getUpdatedEntry(); String oldDNString = oldEntry.getDN().toNormalizedString(); String newDNString = newEntry.getDN().toNormalizedString(); boolean hasSubentryWritePrivilege = false; lock.readLock().lock(); try @@ -1066,6 +1105,22 @@ dit2SubEntry.getSubtree(oldEntry.getDN()); for (SubEntry subentry : setToDelete) { if (!hasSubentryWritePrivilege) { ClientConnection conn = modifyDNOperation.getClientConnection(); if (!conn.hasPrivilege(Privilege.SUBENTRY_WRITE, conn.getOperationInProgress( modifyDNOperation.getMessageID()))) { return PluginResult.PreOperation.stopProcessing( ResultCode.INSUFFICIENT_ACCESS_RIGHTS, ERR_SUBENTRY_WRITE_INSUFFICIENT_PRIVILEGES.get()); } else { hasSubentryWritePrivilege = true; } } oldEntry = subentry.getEntry(); try { opends/src/server/org/opends/server/types/Entry.java
@@ -3707,6 +3707,12 @@ { inheritFromDN = DN.decode( value.getNormalizedValue()); // Respect subentry root scope. if (!inheritFromDN.isDescendantOf( subEntry.getDN().getParent())) { inheritFromDN = null; } break; } } opends/src/server/org/opends/server/types/Privilege.java
@@ -226,7 +226,15 @@ * The privilege that provides the ability to perform an unindexed * search in the JE backend. */ UNINDEXED_SEARCH("unindexed-search"); UNINDEXED_SEARCH("unindexed-search"), /** * The privilege that provides the ability to perform write * operations on LDAP subentries. */ SUBENTRY_WRITE("subentry-write"); @@ -287,6 +295,7 @@ PRIV_MAP.put("update-schema", UPDATE_SCHEMA); PRIV_MAP.put("privilege-change", PRIVILEGE_CHANGE); PRIV_MAP.put("unindexed-search", UNINDEXED_SEARCH); PRIV_MAP.put("subentry-write", SUBENTRY_WRITE); PRIV_NAMES.add("bypass-acl"); PRIV_NAMES.add("bypass-lockdown"); @@ -311,6 +320,7 @@ PRIV_NAMES.add("update-schema"); PRIV_NAMES.add("privilege-change"); PRIV_NAMES.add("unindexed-search"); PRIV_NAMES.add("subentry-write"); DEFAULT_ROOT_PRIV_SET.add(BYPASS_ACL); DEFAULT_ROOT_PRIV_SET.add(BYPASS_LOCKDOWN); @@ -330,6 +340,7 @@ DEFAULT_ROOT_PRIV_SET.add(UPDATE_SCHEMA); DEFAULT_ROOT_PRIV_SET.add(PRIVILEGE_CHANGE); DEFAULT_ROOT_PRIV_SET.add(UNINDEXED_SEARCH); DEFAULT_ROOT_PRIV_SET.add(SUBENTRY_WRITE); } opends/src/server/org/opends/server/types/SubEntry.java
@@ -131,11 +131,11 @@ "inheritfromrdntype"; /** * The name of the "inheritFromBaseDN" attribute type, * The name of the "inheritFromBaseRDN" attribute type, * formatted in all lowercase characters. */ public static final String ATTR_INHERIT_COLLECTIVE_FROM_BASE = "inheritfrombasedn"; "inheritfrombaserdn"; /** * The name of the "inheritAttribute" attribute type, @@ -390,6 +390,11 @@ { this.inheritFromBaseDN = DN.decode(value.getNormalizedValue()); // Has to have a parent since subentry itself // cannot be a suffix entry within the server. this.inheritFromBaseDN = getDN().getParent().concat( inheritFromBaseDN); break; } } @@ -442,7 +447,7 @@ * Retrieves the distinguished name for this subentry. * @return The distinguished name for this subentry. */ public DN getDN() public final DN getDN() { return this.entry.getDN(); } @@ -452,7 +457,7 @@ * for this subentry. * @return entry object for this subentry. */ public Entry getEntry() public final Entry getEntry() { return this.entry; } @@ -562,9 +567,9 @@ } /** * Getter to retrieve inheritFromBaseDN DN * Getter to retrieve inheritFromBaseRDN DN * for inherited collective attribute subentry. * @return DN of inheritFromBaseDN or, * @return DN of inheritFromBaseRDN or, * <code>null</code> if there is none. */ public DN getInheritFromBaseDN() opends/tests/unit-tests-testng/src/server/org/opends/server/core/SubentryManagerTestCase.java
@@ -64,8 +64,8 @@ public class SubentryManagerTestCase extends CoreTestCase { private static final String SUFFIX = "dc=example,dc=com"; private static final String BASE = "ou=Test SubEntry Manager," + SUFFIX; private static final String BASE_RDN = "ou=Test SubEntry Manager"; private static final String BASE = BASE_RDN + "," + SUFFIX; private Entry testEntry; private Entry ldapSubentry; @@ -192,7 +192,7 @@ "objectclass: subentry", "objectClass: inheritedCollectiveAttributeSubentry", "objectClass: inheritedFromRDNCollectiveAttributeSubentry", "inheritFromBaseDN: " + BASE, "inheritFromBaseRDN: " + BASE_RDN, "inheritFromRDNAttribute: title", "inheritFromRDNType: cn", "inheritAttribute: telephoneNumber", opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/jmx/JmxPrivilegeTestCase.java
@@ -22,7 +22,7 @@ * CDDL HEADER END * * * Copyright 2008 Sun Microsystems, Inc. * Copyright 2008-2010 Sun Microsystems, Inc. */ package org.opends.server.protocols.jmx; @@ -180,6 +180,7 @@ "ds-privilege-name: unindexed-search", "ds-privilege-name: jmx-read", "ds-privilege-name: jmx-write", "ds-privilege-name: subentry-write", "ds-pwp-password-policy-dn: cn=Clear UserPassword Policy," + "cn=Password Policies,cn=config", "", opends/tests/unit-tests-testng/src/server/org/opends/server/types/PrivilegeTestCase.java
@@ -22,7 +22,7 @@ * CDDL HEADER END * * * Copyright 2007-2009 Sun Microsystems, Inc. * Copyright 2007-2010 Sun Microsystems, Inc. */ package org.opends.server.types; @@ -141,6 +141,7 @@ "ds-privilege-name: -backend-backup", "ds-privilege-name: -backend-restore", "ds-privilege-name: -unindexed-search", "ds-privilege-name: -subentry-write", "", "dn: cn=Proxy Root,cn=Root DNs,cn=config", "objectClass: top", @@ -176,6 +177,7 @@ "ds-privilege-name: proxied-auth", "ds-privilege-name: bypass-acl", "ds-privilege-name: unindexed-search", "ds-privilege-name: subentry-write", "ds-pwp-password-policy-dn: cn=Clear UserPassword Policy," + "cn=Password Policies,cn=config", "", @@ -193,6 +195,15 @@ "ds-pwp-password-policy-dn: cn=Clear UserPassword Policy," + "cn=Password Policies,cn=config", "", "dn: cn=Subentry Target,o=test", "objectClass: top", "objectClass: subentry", "objectClass: collectiveAttributeSubentry", "objectClass: extensibleObject", "cn: Subentry Target", "l;collective: Test", "subtreeSpecification: {}", "", "dn: cn=PWReset Target,o=test", "objectClass: top", "objectClass: person", @@ -633,6 +644,153 @@ /** * Tests to ensure that add and delete operations * properly respect the SUBENTRY_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 SUBENTRY_WRITE privilege and therefore * the operations should succeed. * * @throws Exception If an unexpected problem occurs. */ @Test(dataProvider = "testdata") public void testSubentryWriteAddAndDelete(InternalClientConnection conn, boolean hasPrivilege) throws Exception { assertEquals(conn.hasPrivilege(Privilege.SUBENTRY_WRITE, null), hasPrivilege); Entry entry = TestCaseUtils.makeEntry( "dn: cn=Test Subentry,o=test", "objectClass: top", "objectClass: subentry", "objectClass: collectiveAttributeSubentry", "objectClass: extensibleObject", "cn: Test Subentry", "l;collective: Test", "subtreeSpecification: {}"); 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=Subentry Target,o=test")); assertEquals(deleteOperation.getResultCode(), ResultCode.INSUFFICIENT_ACCESS_RIGHTS); } } /** * Tests to ensure that modify operations properly respect * the SUBENTRY_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 SUBENTRY_WRITE privilege and therefore * the modify should succeed. * * @throws Exception If an unexpected problem occurs. */ @Test(dataProvider = "testdata") public void testSubentryWriteModify(InternalClientConnection conn, boolean hasPrivilege) throws Exception { assertEquals(conn.hasPrivilege(Privilege.SUBENTRY_WRITE, null), hasPrivilege); ArrayList<Modification> mods = new ArrayList<Modification>(); mods.add(new Modification(ModificationType.REPLACE, Attributes.create("subtreeSpecification", "{base \"ou=doesnotexist\"}"))); ModifyOperation modifyOperation = conn.processModify(DN.decode("cn=Subentry Target,o=test"), mods); if (hasPrivilege) { assertEquals(modifyOperation.getResultCode(), ResultCode.SUCCESS); mods.clear(); mods.add(new Modification(ModificationType.REPLACE, Attributes.create("subtreeSpecification", "{}"))); modifyOperation = conn.processModify( DN.decode("cn=Subentry Target,o=test"), mods); assertEquals(modifyOperation.getResultCode(), ResultCode.SUCCESS); } else { assertEquals(modifyOperation.getResultCode(), ResultCode.INSUFFICIENT_ACCESS_RIGHTS); } } /** * Tests to ensure that modify DN operations * properly respect the SUBENTRY_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 SUBENTRY_WRITE privilege and therefore * the modify DN should succeed. * * @throws Exception If an unexpected problem occurs. */ @Test(dataProvider = "testdata") public void testSubentryWriteModifyDN(InternalClientConnection conn, boolean hasPrivilege) throws Exception { assertEquals(conn.hasPrivilege(Privilege.SUBENTRY_WRITE, null), hasPrivilege); ModifyDNOperation modifyDNOperation = conn.processModifyDN(DN.decode("cn=Subentry Target,o=test"), RDN.decode("cn=New Subentry Target"), true, null); if (hasPrivilege) { assertEquals(modifyDNOperation.getResultCode(), ResultCode.SUCCESS); modifyDNOperation = conn.processModifyDN(DN.decode("cn=New Subentry Target,o=test"), RDN.decode("cn=Subentry Target"), true, null); } else { assertEquals(modifyDNOperation.getResultCode(), ResultCode.INSUFFICIENT_ACCESS_RIGHTS); } } /** * Tests to ensure that modify operations which attempt to reset a user's * password properly respect the PASSWORD_RESET privilege. *