From 2ae2f53756b6f13e2816bb30812740cb7ad97403 Mon Sep 17 00:00:00 2001 From: tbordaz Date: Fri, 5 Nov 2021 09:56:43 +0100 Subject: [PATCH 09/12] Issue 4972 - gecos with IA5 introduces a compatibility issue with previous (#4981) releases where it was DirectoryString Bug description: For years 'gecos' was DirectoryString (UTF8), with #50933 it was restricted to IA5 (ascii) https://github.com/389ds/389-ds-base/commit/0683bcde1b667b6d0ca6e8d1ef605f17c51ea2f7# IA5 definition conforms rfc2307 but is a problem for existing deployments where entries can have 'gecos' attribute value with UTF8. Fix description: Revert the definition to of 'gecos' being Directory String Additional fix to make test_replica_backup_and_restore more robust to CI relates: https://github.com/389ds/389-ds-base/issues/4972 Reviewed by: William Brown, Pierre Rogier, James Chapman (Thanks !) Platforms tested: F34 --- .../tests/suites/schema/schema_test.py | 398 +++++++++++++++++- ldap/schema/10rfc2307compat.ldif | 6 +- 2 files changed, 400 insertions(+), 4 deletions(-) diff --git a/dirsrvtests/tests/suites/schema/schema_test.py b/dirsrvtests/tests/suites/schema/schema_test.py index d590624b6..5d62b8d59 100644 --- a/dirsrvtests/tests/suites/schema/schema_test.py +++ b/dirsrvtests/tests/suites/schema/schema_test.py @@ -18,8 +18,12 @@ import pytest import six from ldap.cidict import cidict from ldap.schema import SubSchema +from lib389.schema import SchemaLegacy from lib389._constants import * -from lib389.topologies import topology_st +from lib389.topologies import topology_st, topology_m2 as topo_m2 +from lib389.idm.user import UserAccounts, UserAccount +from lib389.replica import ReplicationManager +from lib389.utils import ensure_bytes pytestmark = pytest.mark.tier1 @@ -165,6 +169,398 @@ def test_schema_comparewithfiles(topology_st): log.info('test_schema_comparewithfiles: PASSED') +def test_gecos_directoryString(topology_st): + """Check that gecos supports directoryString value + + :id: aee422bb-6299-4124-b5cd-d7393dac19d3 + + :setup: Standalone instance + + :steps: + 1. Add a common user + 2. replace gecos with a direstoryString value + + :expectedresults: + 1. Success + 2. Success + """ + + users = UserAccounts(topology_st.standalone, DEFAULT_SUFFIX) + + user_properties = { + 'uid': 'testuser', + 'cn' : 'testuser', + 'sn' : 'user', + 'uidNumber' : '1000', + 'gidNumber' : '2000', + 'homeDirectory' : '/home/testuser', + } + testuser = users.create(properties=user_properties) + + # Add a gecos UTF value + testuser.replace('gecos', 'Hélène') + +def test_gecos_mixed_definition_topo(topo_m2, request): + """Check that replication is still working if schema contains + definitions that does not conform with a replicated entry + + :id: d5940e71-d18a-4b71-aaf7-b9185361fffe + :setup: Two suppliers replication setup + :steps: + 1. Create a testuser on M1 + 2 Stop M1 and M2 + 3 Change gecos def on M2 to be IA5 + 4 Update testuser with gecos directoryString value + 5 Check replication is still working + :expectedresults: + 1. success + 2. success + 3. success + 4. success + 5. success + + """ + + repl = ReplicationManager(DEFAULT_SUFFIX) + m1 = topo_m2.ms["supplier1"] + m2 = topo_m2.ms["supplier2"] + + + # create a test user + testuser_dn = 'uid={},{}'.format('testuser', DEFAULT_SUFFIX) + testuser = UserAccount(m1, testuser_dn) + try: + testuser.create(properties={ + 'uid': 'testuser', + 'cn': 'testuser', + 'sn': 'testuser', + 'uidNumber' : '1000', + 'gidNumber' : '2000', + 'homeDirectory' : '/home/testuser', + }) + except ldap.ALREADY_EXISTS: + pass + repl.wait_for_replication(m1, m2) + + # Stop suppliers to update the schema + m1.stop() + m2.stop() + + # on M1: gecos is DirectoryString (default) + # on M2: gecos is IA5 + schema_filename = (m2.schemadir + "/99user.ldif") + try: + with open(schema_filename, 'w') as schema_file: + schema_file.write("dn: cn=schema\n") + schema_file.write("attributetypes: ( 1.3.6.1.1.1.1.2 NAME " + + "'gecos' DESC 'The GECOS field; the common name' " + + "EQUALITY caseIgnoreIA5Match " + + "SUBSTR caseIgnoreIA5SubstringsMatch " + + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 " + + "SINGLE-VALUE )\n") + os.chmod(schema_filename, 0o777) + except OSError as e: + log.fatal("Failed to update schema file: " + + "{} Error: {}".format(schema_filename, str(e))) + + # start the instances + m1.start() + m2.start() + + # Check that gecos is IA5 on M2 + schema = SchemaLegacy(m2) + attributetypes = schema.query_attributetype('gecos') + assert attributetypes[0].syntax == "1.3.6.1.4.1.1466.115.121.1.26" + + + # Add a gecos UTF value on M1 + testuser.replace('gecos', 'Hélène') + + # Check replication is still working + testuser.replace('displayName', 'ascii value') + repl.wait_for_replication(m1, m2) + testuser_m2 = UserAccount(m2, testuser_dn) + assert testuser_m2.exists() + assert testuser_m2.get_attr_val_utf8('displayName') == 'ascii value' + + def fin(): + m1.start() + m2.start() + testuser.delete() + repl.wait_for_replication(m1, m2) + + # on M2 restore a default 99user.ldif + m2.stop() + os.remove(m2.schemadir + "/99user.ldif") + schema_filename = (m2.schemadir + "/99user.ldif") + try: + with open(schema_filename, 'w') as schema_file: + schema_file.write("dn: cn=schema\n") + os.chmod(schema_filename, 0o777) + except OSError as e: + log.fatal("Failed to update schema file: " + + "{} Error: {}".format(schema_filename, str(e))) + m2.start() + m1.start() + + request.addfinalizer(fin) + +def test_gecos_directoryString_wins_M1(topo_m2, request): + """Check that if inital syntax are IA5(M2) and DirectoryString(M1) + Then directoryString wins when nsSchemaCSN M1 is the greatest + + :id: ad119fa5-7671-45c8-b2ef-0b28ffb68fdb + :setup: Two suppliers replication setup + :steps: + 1. Create a testuser on M1 + 2 Stop M1 and M2 + 3 Change gecos def on M2 to be IA5 + 4 Start M1 and M2 + 5 Update M1 schema so that M1 has greatest nsSchemaCSN + 6 Update testuser with gecos directoryString value + 7 Check replication is still working + 8 Check gecos is DirectoryString on M1 and M2 + :expectedresults: + 1. success + 2. success + 3. success + 4. success + 5. success + 6. success + 7. success + 8. success + + """ + + repl = ReplicationManager(DEFAULT_SUFFIX) + m1 = topo_m2.ms["supplier1"] + m2 = topo_m2.ms["supplier2"] + + + # create a test user + testuser_dn = 'uid={},{}'.format('testuser', DEFAULT_SUFFIX) + testuser = UserAccount(m1, testuser_dn) + try: + testuser.create(properties={ + 'uid': 'testuser', + 'cn': 'testuser', + 'sn': 'testuser', + 'uidNumber' : '1000', + 'gidNumber' : '2000', + 'homeDirectory' : '/home/testuser', + }) + except ldap.ALREADY_EXISTS: + pass + repl.wait_for_replication(m1, m2) + + # Stop suppliers to update the schema + m1.stop() + m2.stop() + + # on M1: gecos is DirectoryString (default) + # on M2: gecos is IA5 + schema_filename = (m2.schemadir + "/99user.ldif") + try: + with open(schema_filename, 'w') as schema_file: + schema_file.write("dn: cn=schema\n") + schema_file.write("attributetypes: ( 1.3.6.1.1.1.1.2 NAME " + + "'gecos' DESC 'The GECOS field; the common name' " + + "EQUALITY caseIgnoreIA5Match " + + "SUBSTR caseIgnoreIA5SubstringsMatch " + + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 " + + "SINGLE-VALUE )\n") + os.chmod(schema_filename, 0o777) + except OSError as e: + log.fatal("Failed to update schema file: " + + "{} Error: {}".format(schema_filename, str(e))) + + # start the instances + m1.start() + m2.start() + + # Check that gecos is IA5 on M2 + schema = SchemaLegacy(m2) + attributetypes = schema.query_attributetype('gecos') + assert attributetypes[0].syntax == "1.3.6.1.4.1.1466.115.121.1.26" + + + # update M1 schema to increase its nsschemaCSN + new_at = "( dummy-oid NAME 'dummy' DESC 'dummy attribute' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN 'RFC 2307' )" + m1.schema.add_schema('attributetypes', ensure_bytes(new_at)) + + # Add a gecos UTF value on M1 + testuser.replace('gecos', 'Hélène') + + # Check replication is still working + testuser.replace('displayName', 'ascii value') + repl.wait_for_replication(m1, m2) + testuser_m2 = UserAccount(m2, testuser_dn) + assert testuser_m2.exists() + assert testuser_m2.get_attr_val_utf8('displayName') == 'ascii value' + + # Check that gecos is DirectoryString on M1 + schema = SchemaLegacy(m1) + attributetypes = schema.query_attributetype('gecos') + assert attributetypes[0].syntax == "1.3.6.1.4.1.1466.115.121.1.15" + + # Check that gecos is DirectoryString on M2 + schema = SchemaLegacy(m2) + attributetypes = schema.query_attributetype('gecos') + assert attributetypes[0].syntax == "1.3.6.1.4.1.1466.115.121.1.15" + + def fin(): + m1.start() + m2.start() + testuser.delete() + m1.schema.del_schema('attributetypes', ensure_bytes(new_at)) + repl.wait_for_replication(m1, m2) + + # on M2 restore a default 99user.ldif + m2.stop() + os.remove(m2.schemadir + "/99user.ldif") + schema_filename = (m2.schemadir + "/99user.ldif") + try: + with open(schema_filename, 'w') as schema_file: + schema_file.write("dn: cn=schema\n") + os.chmod(schema_filename, 0o777) + except OSError as e: + log.fatal("Failed to update schema file: " + + "{} Error: {}".format(schema_filename, str(e))) + m2.start() + m1.start() + + request.addfinalizer(fin) + +def test_gecos_directoryString_wins_M2(topo_m2, request): + """Check that if inital syntax are IA5(M2) and DirectoryString(M1) + Then directoryString wins when nsSchemaCSN M2 is the greatest + + :id: 2da7f1b1-f86d-4072-a940-ba56d4bc8348 + :setup: Two suppliers replication setup + :steps: + 1. Create a testuser on M1 + 2 Stop M1 and M2 + 3 Change gecos def on M2 to be IA5 + 4 Start M1 and M2 + 5 Update M2 schema so that M2 has greatest nsSchemaCSN + 6 Update testuser on M2 and trigger replication to M1 + 7 Update testuser on M2 with gecos directoryString value + 8 Check replication is still working + 9 Check gecos is DirectoryString on M1 and M2 + :expectedresults: + 1. success + 2. success + 3. success + 4. success + 5. success + 6. success + 7. success + 8. success + 9. success + + """ + + repl = ReplicationManager(DEFAULT_SUFFIX) + m1 = topo_m2.ms["supplier1"] + m2 = topo_m2.ms["supplier2"] + + + # create a test user + testuser_dn = 'uid={},{}'.format('testuser', DEFAULT_SUFFIX) + testuser = UserAccount(m1, testuser_dn) + try: + testuser.create(properties={ + 'uid': 'testuser', + 'cn': 'testuser', + 'sn': 'testuser', + 'uidNumber' : '1000', + 'gidNumber' : '2000', + 'homeDirectory' : '/home/testuser', + }) + except ldap.ALREADY_EXISTS: + pass + testuser.replace('displayName', 'to trigger replication M1-> M2') + repl.wait_for_replication(m1, m2) + + # Stop suppliers to update the schema + m1.stop() + m2.stop() + + # on M1: gecos is DirectoryString (default) + # on M2: gecos is IA5 + schema_filename = (m2.schemadir + "/99user.ldif") + try: + with open(schema_filename, 'w') as schema_file: + schema_file.write("dn: cn=schema\n") + schema_file.write("attributetypes: ( 1.3.6.1.1.1.1.2 NAME " + + "'gecos' DESC 'The GECOS field; the common name' " + + "EQUALITY caseIgnoreIA5Match " + + "SUBSTR caseIgnoreIA5SubstringsMatch " + + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 " + + "SINGLE-VALUE )\n") + os.chmod(schema_filename, 0o777) + except OSError as e: + log.fatal("Failed to update schema file: " + + "{} Error: {}".format(schema_filename, str(e))) + + # start the instances + m1.start() + m2.start() + + # Check that gecos is IA5 on M2 + schema = SchemaLegacy(m2) + attributetypes = schema.query_attributetype('gecos') + assert attributetypes[0].syntax == "1.3.6.1.4.1.1466.115.121.1.26" + + # update M2 schema to increase its nsschemaCSN + new_at = "( dummy-oid NAME 'dummy' DESC 'dummy attribute' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN 'RFC 2307' )" + m2.schema.add_schema('attributetypes', ensure_bytes(new_at)) + + # update just to trigger replication M2->M1 + # and update of M2 schema + testuser_m2 = UserAccount(m2, testuser_dn) + testuser_m2.replace('displayName', 'to trigger replication M2-> M1') + + # Add a gecos UTF value on M1 + testuser.replace('gecos', 'Hélène') + + # Check replication is still working + testuser.replace('displayName', 'ascii value') + repl.wait_for_replication(m1, m2) + assert testuser_m2.exists() + assert testuser_m2.get_attr_val_utf8('displayName') == 'ascii value' + + # Check that gecos is DirectoryString on M1 + schema = SchemaLegacy(m1) + attributetypes = schema.query_attributetype('gecos') + assert attributetypes[0].syntax == "1.3.6.1.4.1.1466.115.121.1.15" + + # Check that gecos is DirectoryString on M2 + schema = SchemaLegacy(m2) + attributetypes = schema.query_attributetype('gecos') + assert attributetypes[0].syntax == "1.3.6.1.4.1.1466.115.121.1.15" + + def fin(): + m1.start() + m2.start() + testuser.delete() + m1.schema.del_schema('attributetypes', ensure_bytes(new_at)) + repl.wait_for_replication(m1, m2) + + # on M2 restore a default 99user.ldif + m2.stop() + os.remove(m2.schemadir + "/99user.ldif") + schema_filename = (m2.schemadir + "/99user.ldif") + try: + with open(schema_filename, 'w') as schema_file: + schema_file.write("dn: cn=schema\n") + os.chmod(schema_filename, 0o777) + except OSError as e: + log.fatal("Failed to update schema file: " + + "{} Error: {}".format(schema_filename, str(e))) + m2.start() + + request.addfinalizer(fin) if __name__ == '__main__': # Run isolated diff --git a/ldap/schema/10rfc2307compat.ldif b/ldap/schema/10rfc2307compat.ldif index 8ba72e1e3..998b8983b 100644 --- a/ldap/schema/10rfc2307compat.ldif +++ b/ldap/schema/10rfc2307compat.ldif @@ -21,9 +21,9 @@ attributeTypes: ( attributeTypes: ( 1.3.6.1.1.1.1.2 NAME 'gecos' DESC 'The GECOS field; the common name' - EQUALITY caseIgnoreIA5Match - SUBSTR caseIgnoreIA5SubstringsMatch - SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE ) attributeTypes: ( -- 2.31.1