From 8b21d0a4c6eb8b5c3e60a96fc3e9e13b9c2f650f Mon Sep 17 00:00:00 2001 From: Neil Wilson Date: Thu, 30 Nov 2023 10:14:00 -0600 Subject: [PATCH] Improve ldapsearch/ldapmodify --proxyAs handling Updated the ldapsearch and ldapmodify command-line tools to provide better validation for the value of the --proxyAs argument. The tools will now reject attempts to use the argument with a value that doesn't start with either "dn:" or "u:", and they will also reject attempts to use a value that starts with "dn:" but is not followed by a valid LDAP DN. --- docs/release-notes.html | 9 +++ messages/unboundid-ldapsdk-tools.properties | 12 ++++ .../sdk/unboundidds/tools/LDAPModify.java | 24 +++++++ .../sdk/unboundidds/tools/LDAPSearch.java | 48 +++++++++++++- .../unboundidds/tools/LDAPModifyTestCase.java | 61 ++++++++++++++++++ .../unboundidds/tools/LDAPSearchTestCase.java | 63 +++++++++++++++++++ 6 files changed, 215 insertions(+), 2 deletions(-) diff --git a/docs/release-notes.html b/docs/release-notes.html index b6ada73ac..b91b6fe8b 100644 --- a/docs/release-notes.html +++ b/docs/release-notes.html @@ -19,6 +19,15 @@

Version 6.0.11



+
  • + Updated the ldapsearch and ldapmodify command-line tools to provide better + validation for the value of the --proxyAs argument. The tools will now reject + attempts to use the argument with a value that doesn't start with either "dn:" or + "u:", and they will also reject attempts to use a value that starts with "dn:" + but is not followed by a valid LDAP DN. +

    +
  • +
  • Updated the Filter methods for creating substring filters to better support empty components. In LDAP filters, filters are transmitted using a binary encoding, diff --git a/messages/unboundid-ldapsdk-tools.properties b/messages/unboundid-ldapsdk-tools.properties index 63ad5d21c..48e4c3726 100644 --- a/messages/unboundid-ldapsdk-tools.properties +++ b/messages/unboundid-ldapsdk-tools.properties @@ -1533,6 +1533,12 @@ ERR_LDAPMODIFY_ROUTE_TO_BACKEND_SET_INVALID_FORMAT=Value ''{0}'' provided for \ argument ''{1}'' is invalid. The value must contain the ID of the \ entry-balancing request processor followed by a colon and the ID of a \ desired backend set to use for that entry-balancing request processor. +ERR_LDAPMODIFY_PROXY_AS_DN_NOT_DN=The value of the {0} argument is not valid \ + because ''{1}'' cannot be parsed as a valid LDAP DN. +ERR_LDAPMODIFY_PROXY_AS_VALUE_MISSING_PREFIX=The value of the {0} argument is \ + not valid because it does not start with either ''dn:'' (to indicate that \ + the following value is an LDAP DN) or ''u:'' (to indicate that the \ + following value is a username). ERR_LDAPMODIFY_CANNOT_CREATE_CONNECTION_POOL=An error occurred while \ attempting to create a connection pool to communicate with the directory \ server: {0} @@ -2341,6 +2347,12 @@ ERR_LDAPSEARCH_ROUTE_TO_BACKEND_SET_INVALID_FORMAT=Value ''{0}'' provided for \ ERR_LDAPSEARCH_RENAME_ATTRIBUTE_MISMATCH=The --renameAttributeFrom argument \ must be provided the same number of times as the --renameAttributeTo \ argument. +ERR_LDAPSEARCH_PROXY_AS_DN_NOT_DN=The value of the {0} argument is not valid \ + because ''{1}'' cannot be parsed as a valid LDAP DN. +ERR_LDAPSEARCH_PROXY_AS_VALUE_MISSING_PREFIX=The value of the {0} argument is \ + not valid because it does not start with either ''dn:'' (to indicate that \ + the following value is an LDAP DN) or ''u:'' (to indicate that the \ + following value is a username). ERR_LDAPSEARCH_MOVE_SUBTREE_MISMATCH=The --moveSubtreeFrom argument must be \ provided the same number of times as the --moveSubtreeTo argument. ERR_LDAPSEARCH_OUTPUT_FORMAT_NOT_SUPPORTED_WITH_URLS=The ''{0}'' output \ diff --git a/src/com/unboundid/ldap/sdk/unboundidds/tools/LDAPModify.java b/src/com/unboundid/ldap/sdk/unboundidds/tools/LDAPModify.java index 290f57d2a..0d76db68a 100644 --- a/src/com/unboundid/ldap/sdk/unboundidds/tools/LDAPModify.java +++ b/src/com/unboundid/ldap/sdk/unboundidds/tools/LDAPModify.java @@ -1574,6 +1574,30 @@ public void doExtendedNonLDAPArgumentValidation() rpID, bsIDs)); } } + + + // If the --proxyAs argument was provided, then make sure its value is + // properly formatted. + if (proxyAs.isPresent()) + { + final String proxyAsValue = proxyAs.getValue(); + final String lowerProxyAsValue = StaticUtils.toLowerCase(proxyAsValue); + if (lowerProxyAsValue.startsWith("dn:")) + { + final String dnString = proxyAsValue.substring(3); + if (! DN.isValidDN(dnString)) + { + throw new ArgumentException(ERR_LDAPMODIFY_PROXY_AS_DN_NOT_DN.get( + proxyAs.getIdentifierString(), dnString)); + } + } + else if (! lowerProxyAsValue.startsWith("u:")) + { + throw new ArgumentException( + ERR_LDAPMODIFY_PROXY_AS_VALUE_MISSING_PREFIX.get( + proxyAs.getIdentifierString())); + } + } } diff --git a/src/com/unboundid/ldap/sdk/unboundidds/tools/LDAPSearch.java b/src/com/unboundid/ldap/sdk/unboundidds/tools/LDAPSearch.java index 87a0a6f42..eed7dd12d 100644 --- a/src/com/unboundid/ldap/sdk/unboundidds/tools/LDAPSearch.java +++ b/src/com/unboundid/ldap/sdk/unboundidds/tools/LDAPSearch.java @@ -1110,6 +1110,8 @@ public void addNonLDAPArguments(@NotNull final ArgumentParser parser) INFO_PLACEHOLDER_AUTHZID.get(), INFO_LDAPSEARCH_ARG_DESCRIPTION_PROXY_AS.get()); proxyAs.addLongIdentifier("proxy-as", true); + proxyAs.addLongIdentifier("proxyV2As", true); + proxyAs.addLongIdentifier("proxy-v2-as", true); proxyAs.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get()); parser.addArgument(proxyAs); @@ -2199,6 +2201,30 @@ else if (derefStr.equals("find")) } + // If the --proxyAs argument was provided, then make sure its value is + // properly formatted. + if (proxyAs.isPresent()) + { + final String proxyAsValue = proxyAs.getValue(); + final String lowerProxyAsValue = StaticUtils.toLowerCase(proxyAsValue); + if (lowerProxyAsValue.startsWith("dn:")) + { + final String dnString = proxyAsValue.substring(3); + if (! DN.isValidDN(dnString)) + { + throw new ArgumentException(ERR_LDAPSEARCH_PROXY_AS_DN_NOT_DN.get( + proxyAs.getIdentifierString(), dnString)); + } + } + else if (! lowerProxyAsValue.startsWith("u:")) + { + throw new ArgumentException( + ERR_LDAPSEARCH_PROXY_AS_VALUE_MISSING_PREFIX.get( + proxyAs.getIdentifierString())); + } + } + + // See if any entry transformations need to be applied. final ArrayList transformations = new ArrayList<>(5); if (excludeAttribute.isPresent()) @@ -3442,8 +3468,26 @@ else if (valueStr.equals("without-non-deleted-entries")) if (proxyAs.isPresent()) { - controls.add(new ProxiedAuthorizationV2RequestControl( - proxyAs.getValue())); + final String proxyAsValue = proxyAs.getValue(); + final String lowerProxyAsValue = StaticUtils.toLowerCase(proxyAsValue); + if (lowerProxyAsValue.startsWith("dn:")) + { + final String dnString = proxyAsValue.substring(3); + if (! DN.isValidDN(dnString)) + { + throw new LDAPException(ResultCode.PARAM_ERROR, + ERR_LDAPSEARCH_PROXY_AS_DN_NOT_DN.get( + proxyAs.getIdentifierString(), dnString)); + } + } + else if (! lowerProxyAsValue.startsWith("u:")) + { + throw new LDAPException(ResultCode.PARAM_ERROR, + ERR_LDAPSEARCH_PROXY_AS_VALUE_MISSING_PREFIX.get( + proxyAs.getIdentifierString())); + } + + controls.add(new ProxiedAuthorizationV2RequestControl(proxyAsValue)); } if (proxyV1As.isPresent()) diff --git a/tests/unit/src/com/unboundid/ldap/sdk/unboundidds/tools/LDAPModifyTestCase.java b/tests/unit/src/com/unboundid/ldap/sdk/unboundidds/tools/LDAPModifyTestCase.java index 639c31f9e..c1cdb9dbf 100644 --- a/tests/unit/src/com/unboundid/ldap/sdk/unboundidds/tools/LDAPModifyTestCase.java +++ b/tests/unit/src/com/unboundid/ldap/sdk/unboundidds/tools/LDAPModifyTestCase.java @@ -3449,4 +3449,65 @@ public void testInvalidAccessLogFieldControls() "--ldifFile", ldifFile.getAbsolutePath()), ResultCode.PARAM_ERROR); } + + + + /** + * Tests to ensure that the tool rejects attempts to use the --proxyAs + * argument with invalid values. + * + * @throws Exception If an unexpected problem occurs. + */ + @Test() + public void testInvalidProxyAsValues() + throws Exception + { + final InMemoryDirectoryServer ds = getTestDS(true, true); + + final File ldifFile = createTempFile( + "dn: dc=example,dc=com", + "changetype: modify", + "replace: description", + "description: foo"); + + assertEquals( + LDAPModify.main((InputStream) null, null, null, + "--hostname", "localhost", + "--port", String.valueOf(ds.getListenPort()), + "--proxyAs", "missing-prefix", + "--ldifFile", ldifFile.getAbsolutePath()), + ResultCode.PARAM_ERROR); + + assertEquals( + LDAPModify.main((InputStream) null, null, null, + "--hostname", "localhost", + "--port", String.valueOf(ds.getListenPort()), + "--proxyAs", "i:invalid-prefix", + "--ldifFile", ldifFile.getAbsolutePath()), + ResultCode.PARAM_ERROR); + + assertEquals( + LDAPModify.main((InputStream) null, null, null, + "--hostname", "localhost", + "--port", String.valueOf(ds.getListenPort()), + "--proxyAs", "dn:not-a-dn", + "--ldifFile", ldifFile.getAbsolutePath()), + ResultCode.PARAM_ERROR); + + assertEquals( + LDAPModify.main((InputStream) null, null, null, + "--hostname", "localhost", + "--port", String.valueOf(ds.getListenPort()), + "--proxyAs", "dn:uid=test.user,ou=People,dc=example,dc=com", + "--ldifFile", ldifFile.getAbsolutePath()), + ResultCode.SUCCESS); + + assertEquals( + LDAPModify.main((InputStream) null, null, null, + "--hostname", "localhost", + "--port", String.valueOf(ds.getListenPort()), + "--proxyAs", "u:test.user", + "--ldifFile", ldifFile.getAbsolutePath()), + ResultCode.SUCCESS); + } } diff --git a/tests/unit/src/com/unboundid/ldap/sdk/unboundidds/tools/LDAPSearchTestCase.java b/tests/unit/src/com/unboundid/ldap/sdk/unboundidds/tools/LDAPSearchTestCase.java index 9ffedfc82..1270d00c4 100644 --- a/tests/unit/src/com/unboundid/ldap/sdk/unboundidds/tools/LDAPSearchTestCase.java +++ b/tests/unit/src/com/unboundid/ldap/sdk/unboundidds/tools/LDAPSearchTestCase.java @@ -2770,4 +2770,67 @@ public void testInvalidAccessLogFieldControls() "(objectClass=*)"), ResultCode.PARAM_ERROR); } + + + + /** + * Tests to ensure that the tool rejects attempts to use the --proxyAs + * argument with invalid values. + * + * @throws Exception If an unexpected problem occurs. + */ + @Test() + public void testInvalidProxyAsValues() + throws Exception + { + assertEquals( + LDAPSearch.main(NULL_OUTPUT_STREAM, NULL_OUTPUT_STREAM, + "--hostname", "localhost", + "--port", String.valueOf(ds.getListenPort()), + "--baseDN", "", + "--scope", "base", + "--proxyAs", "missing-prefix", + "(objectClass=*)"), + ResultCode.PARAM_ERROR); + + assertEquals( + LDAPSearch.main(NULL_OUTPUT_STREAM, NULL_OUTPUT_STREAM, + "--hostname", "localhost", + "--port", String.valueOf(ds.getListenPort()), + "--baseDN", "", + "--scope", "base", + "--proxyAs", "i:invalid-prefix", + "(objectClass=*)"), + ResultCode.PARAM_ERROR); + + assertEquals( + LDAPSearch.main(NULL_OUTPUT_STREAM, NULL_OUTPUT_STREAM, + "--hostname", "localhost", + "--port", String.valueOf(ds.getListenPort()), + "--baseDN", "", + "--scope", "base", + "--proxyAs", "dn:not-a-dn", + "(objectClass=*)"), + ResultCode.PARAM_ERROR); + + assertEquals( + LDAPSearch.main(NULL_OUTPUT_STREAM, NULL_OUTPUT_STREAM, + "--hostname", "localhost", + "--port", String.valueOf(ds.getListenPort()), + "--baseDN", "", + "--scope", "base", + "--proxyAs", "dn:uid=aaron.adams,ou=People,dc=example,dc=com", + "(objectClass=*)"), + ResultCode.SUCCESS); + + assertEquals( + LDAPSearch.main(NULL_OUTPUT_STREAM, NULL_OUTPUT_STREAM, + "--hostname", "localhost", + "--port", String.valueOf(ds.getListenPort()), + "--baseDN", "", + "--scope", "base", + "--proxyAs", "u:aaron.adams", + "(objectClass=*)"), + ResultCode.SUCCESS); + } }