From 9403d17e98a4dcddb7e9f2559ab67d56da361331 Mon Sep 17 00:00:00 2001
From: Valery Kharseko <vharseko@3a-systems.ru>
Date: Sat, 11 Jul 2026 07:40:10 +0000
Subject: [PATCH] [#719] Fix NullPointerException decoding an ACI bind rule with a missing and/or operand (#720)

---
 opendj-server-legacy/src/main/java/org/opends/server/authorization/dseecompat/BindRule.java |   31 ++++++++++++++++++++++++++++---
 1 files changed, 28 insertions(+), 3 deletions(-)

diff --git a/opendj-server-legacy/src/main/java/org/opends/server/authorization/dseecompat/BindRule.java b/opendj-server-legacy/src/main/java/org/opends/server/authorization/dseecompat/BindRule.java
index 288a28f..96bbef5 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/authorization/dseecompat/BindRule.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/authorization/dseecompat/BindRule.java
@@ -142,7 +142,9 @@
     /**
      * Decode an ACI bind rule string representation.
      * @param input The string representation of the bind rule.
-     * @return A BindRule class representing the bind rule.
+     * @return A BindRule class representing the bind rule, or {@code null} if
+     * {@code input} is null or blank. This null is the recursion base case for
+     * an empty group; callers must reject it rather than dereference it.
      * @throws AciException If the string is an invalid bind rule.
      */
     public static BindRule decode (String input) throws AciException {
@@ -153,8 +155,10 @@
         String bindruleStr = input.trim();
         if (bindruleStr.isEmpty())
         {
-          // A blank bind rule (e.g. "(          )") must be rejected as a
-          // syntax error rather than throwing StringIndexOutOfBoundsException.
+          // A blank bind rule (e.g. "(          )") decodes to null rather than
+          // throwing StringIndexOutOfBoundsException on charAt(0). This null is
+          // the recursion base case for an empty group; the caller rejects it as
+          // a syntax error (see the bindrule_1 == null check below).
           return null;
         }
         char firstChar = bindruleStr.charAt(0);
@@ -203,6 +207,17 @@
               throw new AciException(WARN_ACI_SYNTAX_BIND_RULE_MISSING_CLOSE_PAREN.get(input));
           }
           /*
+           * An empty group such as "()" or "(   )" decodes to a null bind rule.
+           * Such a group is not a valid bind rule on its own, and using it as the
+           * left operand of an "and"/"or" (e.g. "()or userdn=...") would build a
+           * complex rule with a null left side that is later dereferenced during
+           * evaluation. Reject both cases here, before the remaining-chars test,
+           * so the message names the full offending input rather than a fragment.
+           */
+          if (bindrule_1 == null) {
+            throw new AciException(WARN_ACI_SYNTAX_INVALID_BIND_RULE_SYNTAX.get(input));
+          }
+          /*
            * If there are remaining chars => there MUST be an operand (AND / OR)
            * otherwise there is a syntax error
            */
@@ -318,6 +333,16 @@
             boolean negate=determineNegation(ruleExpr);
             remainingBindrule=ruleExpr.toString();
             BindRule bindrule_2 = BindRule.decode(remainingBindrule);
+            /*
+             * The right operand of an "and"/"or" bind rule must exist. It is null
+             * when the boolean operator is not followed by a bind rule, e.g. the
+             * "or" in "userdn=\"ldap:///self\" or" has nothing to its right
+             * (issue #719).
+             */
+            if (bindrule_2 == null) {
+                throw new AciException(
+                    WARN_ACI_SYNTAX_INVALID_BIND_RULE_SYNTAX.get(remainingBindruleStr));
+            }
             bindrule_2.setNegate(negate);
             return new BindRule(bindrule, bindrule_2, operand);
         }

--
Gitblit v1.10.0