5b4a29
From 36660f00bf11f89c632f581d6f82b7383b1aa190 Mon Sep 17 00:00:00 2001
5b4a29
From: Mark Reynolds <mreynolds@redhat.com>
5b4a29
Date: Thu, 26 Jan 2023 08:16:49 -0500
5b4a29
Subject: [PATCH 4/5] Issue 5497 - boolean attributes should be case
5b4a29
 insensitive
5b4a29
5b4a29
Description:  Boolean values are supposed to be case insensitive, but in our
5b4a29
              code it is senstiive even though the code is in the "cis" file.
5b4a29
5b4a29
relates: https://github.com/389ds/389-ds-base/issues/5497
5b4a29
5b4a29
Reviewed by: spichugi(Thanks!)
5b4a29
---
5b4a29
 .../tests/suites/syntax/acceptance_test.py    | 248 ++++++++++++++++++
5b4a29
 ldap/servers/plugins/syntaxes/cis.c           |   4 +-
5b4a29
 2 files changed, 250 insertions(+), 2 deletions(-)
5b4a29
 create mode 100644 dirsrvtests/tests/suites/syntax/acceptance_test.py
5b4a29
5b4a29
diff --git a/dirsrvtests/tests/suites/syntax/acceptance_test.py b/dirsrvtests/tests/suites/syntax/acceptance_test.py
5b4a29
new file mode 100644
5b4a29
index 000000000..807936892
5b4a29
--- /dev/null
5b4a29
+++ b/dirsrvtests/tests/suites/syntax/acceptance_test.py
5b4a29
@@ -0,0 +1,248 @@
5b4a29
+# --- BEGIN COPYRIGHT BLOCK ---
5b4a29
+# Copyright (C) 2023 Red Hat, Inc.
5b4a29
+# All rights reserved.
5b4a29
+#
5b4a29
+# License: GPL (version 3 or any later version).
5b4a29
+# See LICENSE for details.
5b4a29
+# --- END COPYRIGHT BLOCK ---
5b4a29
+
5b4a29
+import ldap
5b4a29
+import pytest
5b4a29
+import os
5b4a29
+from lib389.schema import Schema
5b4a29
+from lib389.config import Config
5b4a29
+from lib389.idm.user import UserAccounts
5b4a29
+from lib389.idm.group import Group, Groups
5b4a29
+from lib389._constants import DEFAULT_SUFFIX
5b4a29
+from lib389.topologies import log, topology_st as topo
5b4a29
+
5b4a29
+pytestmark = pytest.mark.tier0
5b4a29
+
5b4a29
+log = log.getChild(__name__)
5b4a29
+
5b4a29
+
5b4a29
+@pytest.fixture(scope="function")
5b4a29
+def validate_syntax_off(topo, request):
5b4a29
+    config = Config(topo.standalone)
5b4a29
+    config.replace("nsslapd-syntaxcheck", "off")
5b4a29
+
5b4a29
+    def fin():
5b4a29
+        config.replace("nsslapd-syntaxcheck", "on")
5b4a29
+    request.addfinalizer(fin)
5b4a29
+
5b4a29
+
5b4a29
+def test_valid(topo, validate_syntax_off):
5b4a29
+    """Test syntax-validate task with valid entries
5b4a29
+
5b4a29
+    :id: ec402a5b-bfb1-494d-b751-71b0d31a4d83
5b4a29
+    :setup: Standalone instance
5b4a29
+    :steps:
5b4a29
+        1. Set nsslapd-syntaxcheck to off
5b4a29
+        2. Clean error log
5b4a29
+        3. Run syntax validate task
5b4a29
+        4. Assert that there are no errors in the error log
5b4a29
+        5. Set nsslapd-syntaxcheck to on
5b4a29
+    :expectedresults:
5b4a29
+        1. It should succeed
5b4a29
+        2. It should succeed
5b4a29
+        3. It should succeed
5b4a29
+        4. It should succeed
5b4a29
+        5. It should succeed
5b4a29
+    """
5b4a29
+
5b4a29
+    inst = topo.standalone
5b4a29
+
5b4a29
+    log.info('Clean the error log')
5b4a29
+    inst.deleteErrorLogs()
5b4a29
+
5b4a29
+    schema = Schema(inst)
5b4a29
+    log.info('Attempting to add task entry...')
5b4a29
+    validate_task = schema.validate_syntax(DEFAULT_SUFFIX)
5b4a29
+    validate_task.wait()
5b4a29
+    exitcode = validate_task.get_exit_code()
5b4a29
+    assert exitcode == 0
5b4a29
+    error_lines = inst.ds_error_log.match('.*Found 0 invalid entries.*')
5b4a29
+    assert (len(error_lines) == 1)
5b4a29
+    log.info('Found 0 invalid entries - Success')
5b4a29
+
5b4a29
+
5b4a29
+def test_invalid_uidnumber(topo, validate_syntax_off):
5b4a29
+    """Test syntax-validate task with invalid uidNumber attribute value
5b4a29
+
5b4a29
+    :id: 30fdcae6-ffa6-4ec4-8da9-6fb138fc1828
5b4a29
+    :setup: Standalone instance
5b4a29
+    :steps:
5b4a29
+        1. Set nsslapd-syntaxcheck to off
5b4a29
+        2. Clean error log
5b4a29
+        3. Add a user with uidNumber attribute set to an invalid value (string)
5b4a29
+        4. Run syntax validate task
5b4a29
+        5. Assert that there is corresponding error in the error log
5b4a29
+        6. Set nsslapd-syntaxcheck to on
5b4a29
+    :expectedresults:
5b4a29
+        1. It should succeed
5b4a29
+        2. It should succeed
5b4a29
+        3. It should succeed
5b4a29
+        4. It should succeed
5b4a29
+        5. It should succeed
5b4a29
+        6. It should succeed
5b4a29
+    """
5b4a29
+
5b4a29
+    inst = topo.standalone
5b4a29
+
5b4a29
+    log.info('Clean the error log')
5b4a29
+    inst.deleteErrorLogs()
5b4a29
+
5b4a29
+    users = UserAccounts(inst, DEFAULT_SUFFIX)
5b4a29
+    users.create_test_user(uid="invalid_value")
5b4a29
+
5b4a29
+    schema = Schema(inst)
5b4a29
+    log.info('Attempting to add task entry...')
5b4a29
+    validate_task = schema.validate_syntax(DEFAULT_SUFFIX)
5b4a29
+    validate_task.wait()
5b4a29
+    exitcode = validate_task.get_exit_code()
5b4a29
+    assert exitcode == 0
5b4a29
+    error_lines = inst.ds_error_log.match('.*uidNumber: value #0 invalid per syntax.*')
5b4a29
+    assert (len(error_lines) == 1)
5b4a29
+    log.info('Found an invalid entry with wrong uidNumber - Success')
5b4a29
+
5b4a29
+
5b4a29
+def test_invalid_dn_syntax_crash(topo):
5b4a29
+    """Add an entry with an escaped space, restart the server, and try to delete
5b4a29
+    it.  In this case the DN is not correctly parsed and causes cache revert to
5b4a29
+    to dereference a NULL pointer.  So the delete can fail as long as the server
5b4a29
+    does not crash.
5b4a29
+
5b4a29
+    :id: 62d87272-dfb8-4627-9ca1-dbe33082caf8
5b4a29
+    :setup: Standalone Instance
5b4a29
+    :steps:
5b4a29
+        1. Add entry with leading escaped space in the RDN
5b4a29
+        2. Restart the server so the entry is rebuilt from the database
5b4a29
+        3. Delete the entry
5b4a29
+        4. The server should still be running
5b4a29
+    :expectedresults:
5b4a29
+        1. Success
5b4a29
+        2. Success
5b4a29
+        3. Success
5b4a29
+        4. Success
5b4a29
+    """
5b4a29
+
5b4a29
+        # Create group
5b4a29
+    groups = Groups(topo.standalone, DEFAULT_SUFFIX)
5b4a29
+    group = groups.create(properties={'cn': ' test'})
5b4a29
+
5b4a29
+    # Restart the server
5b4a29
+    topo.standalone.restart()
5b4a29
+
5b4a29
+    # Delete group
5b4a29
+    try:
5b4a29
+        group.delete()
5b4a29
+    except ldap.NO_SUCH_OBJECT:
5b4a29
+        # This is okay in this case as we are only concerned about a crash
5b4a29
+        pass
5b4a29
+
5b4a29
+    # Make sure server is still running
5b4a29
+    groups.list()
5b4a29
+
5b4a29
+
5b4a29
+@pytest.mark.parametrize("props, rawdn", [
5b4a29
+                         ({'cn': ' leadingSpace'}, "cn=\\20leadingSpace,ou=Groups,dc=example,dc=com"),
5b4a29
+                         ({'cn': 'trailingSpace '}, "cn=trailingSpace\\20,ou=Groups,dc=example,dc=com")])
5b4a29
+def test_dn_syntax_spaces_delete(topo,  props,  rawdn):
5b4a29
+    """Test that an entry with a space as the first character in the DN can be
5b4a29
+    deleted without error.  We also want to make sure the indexes are properly
5b4a29
+    updated by repeatedly adding and deleting the entry, and that the entry cache
5b4a29
+    is properly maintained.
5b4a29
+
5b4a29
+    :id: b993f37c-c2b0-4312-992c-a9048ff98965
5b4a29
+    :customerscenario: True
5b4a29
+    :parametrized: yes
5b4a29
+    :setup: Standalone Instance
5b4a29
+    :steps:
5b4a29
+        1. Create a group with a DN that has a space as the first/last
5b4a29
+           character.
5b4a29
+        2. Delete group
5b4a29
+        3. Add group
5b4a29
+        4. Modify group
5b4a29
+        5. Restart server and modify entry
5b4a29
+        6. Delete group
5b4a29
+        7. Add group back
5b4a29
+        8. Delete group using specific DN
5b4a29
+    :expectedresults:
5b4a29
+        1. Success
5b4a29
+        2. Success
5b4a29
+        3. Success
5b4a29
+        4. Success
5b4a29
+        5. Success
5b4a29
+        6. Success
5b4a29
+        7. Success
5b4a29
+        8. Success
5b4a29
+    """
5b4a29
+
5b4a29
+    # Create group
5b4a29
+    groups = Groups(topo.standalone, DEFAULT_SUFFIX)
5b4a29
+    group = groups.create(properties=props.copy())
5b4a29
+
5b4a29
+    # Delete group (verifies DN/RDN parsing works and cache is correct)
5b4a29
+    group.delete()
5b4a29
+
5b4a29
+    # Add group again (verifies entryrdn index was properly updated)
5b4a29
+    groups = Groups(topo.standalone, DEFAULT_SUFFIX)
5b4a29
+    group = groups.create(properties=props.copy())
5b4a29
+
5b4a29
+    # Modify the group (verifies dn/rdn parsing is correct)
5b4a29
+    group.replace('description', 'escaped space group')
5b4a29
+
5b4a29
+    # Restart the server.  This will pull the entry from the database and
5b4a29
+    # convert it into a cache entry, which is different than how a client
5b4a29
+    # first adds an entry and is put into the cache before being written to
5b4a29
+    # disk.
5b4a29
+    topo.standalone.restart()
5b4a29
+
5b4a29
+    # Make sure we can modify the entry (verifies cache entry was created
5b4a29
+    # correctly)
5b4a29
+    group.replace('description', 'escaped space group after restart')
5b4a29
+
5b4a29
+    # Make sure it can still be deleted (verifies cache again).
5b4a29
+    group.delete()
5b4a29
+
5b4a29
+    # Add it back so we can delete it using a specific DN (sanity test to verify
5b4a29
+    # another DN/RDN parsing variation).
5b4a29
+    groups = Groups(topo.standalone, DEFAULT_SUFFIX)
5b4a29
+    group = groups.create(properties=props.copy())
5b4a29
+    group = Group(topo.standalone, dn=rawdn)
5b4a29
+    group.delete()
5b4a29
+
5b4a29
+
5b4a29
+def test_boolean_case(topo):
5b4a29
+    """Test that we can a boolean value in any case
5b4a29
+
5b4a29
+       :id: 56777c1d-b058-41e1-abd5-87a6f1512db2
5b4a29
+       :customerscenario: True
5b4a29
+       :setup: Standalone Instance
5b4a29
+       :steps:
5b4a29
+           1. Create test user
5b4a29
+           2. Add boolean attribute value that is lowercase "false"
5b4a29
+       :expectedresults:
5b4a29
+           1. Success
5b4a29
+           2. Success
5b4a29
+    """
5b4a29
+    inst = topo.standalone
5b4a29
+    users  = UserAccounts(inst, DEFAULT_SUFFIX)
5b4a29
+    user = users.create_test_user(uid=1011)
5b4a29
+
5b4a29
+    user.add('objectclass', 'extensibleObject')
5b4a29
+    user.add('pamsecure', 'false')
5b4a29
+    user.replace('pamsecure', 'FALSE')
5b4a29
+    user.replace('pamsecure', 'true')
5b4a29
+    user.replace('pamsecure', 'TRUE')
5b4a29
+
5b4a29
+    # Test some invalid syntax
5b4a29
+    with pytest.raises(ldap.INVALID_SYNTAX):
5b4a29
+        user.replace('pamsecure', 'blah')
5b4a29
+
5b4a29
+
5b4a29
+if __name__ == '__main__':
5b4a29
+    # Run isolated
5b4a29
+    # -s for DEBUG mode
5b4a29
+    CURRENT_FILE = os.path.realpath(__file__)
5b4a29
+    pytest.main("-s %s" % CURRENT_FILE)
5b4a29
diff --git a/ldap/servers/plugins/syntaxes/cis.c b/ldap/servers/plugins/syntaxes/cis.c
5b4a29
index e1242e3f4..c9274f37f 100644
5b4a29
--- a/ldap/servers/plugins/syntaxes/cis.c
5b4a29
+++ b/ldap/servers/plugins/syntaxes/cis.c
5b4a29
@@ -853,12 +853,12 @@ boolean_validate(
5b4a29
      */
5b4a29
     if (val != NULL) {
5b4a29
         if (val->bv_len == 4) {
5b4a29
-            if (strncmp(val->bv_val, "TRUE", 4) != 0) {
5b4a29
+            if (strncasecmp(val->bv_val, "TRUE", 4) != 0) {
5b4a29
                 rc = 1;
5b4a29
                 goto exit;
5b4a29
             }
5b4a29
         } else if (val->bv_len == 5) {
5b4a29
-            if (strncmp(val->bv_val, "FALSE", 5) != 0) {
5b4a29
+            if (strncasecmp(val->bv_val, "FALSE", 5) != 0) {
5b4a29
                 rc = 1;
5b4a29
                 goto exit;
5b4a29
             }
5b4a29
-- 
5b4a29
2.39.1
5b4a29