class SchemaTests(samba.tests.TestCase): def setUp(self): super(SchemaTests, self).setUp() self.ldb = SamDB(host, credentials=creds, session_info=system_session(lp), lp=lp, options=ldb_options) self.base_dn = self.ldb.domain_dn() self.schema_dn = self.ldb.get_schema_basedn().get_linearized() def test_generated_schema(self): """Testing we can read the generated schema via LDAP""" res = self.ldb.search("cn=aggregate,"+self.schema_dn, scope=SCOPE_BASE, attrs=["objectClasses", "attributeTypes", "dITContentRules"]) self.assertEquals(len(res), 1) self.assertTrue("dITContentRules" in res[0]) self.assertTrue("objectClasses" in res[0]) self.assertTrue("attributeTypes" in res[0]) def test_generated_schema_is_operational(self): """Testing we don't get the generated schema via LDAP by default""" # Must keep the "*" form res = self.ldb.search("cn=aggregate,"+self.schema_dn, scope=SCOPE_BASE, attrs=["*"]) self.assertEquals(len(res), 1) self.assertFalse("dITContentRules" in res[0]) self.assertFalse("objectClasses" in res[0]) self.assertFalse("attributeTypes" in res[0]) def test_schemaUpdateNow(self): """Testing schemaUpdateNow""" attr_name = "test-Attr" + time.strftime("%s", time.gmtime()) attr_ldap_display_name = attr_name.replace("-", "") ldif = """ dn: CN=%s,%s""" % (attr_name, self.schema_dn) + """ objectClass: top objectClass: attributeSchema adminDescription: """ + attr_name + """ adminDisplayName: """ + attr_name + """ cn: """ + attr_name + """ attributeId: 1.2.840.""" + str(random.randint(1,100000)) + """.1.5.9940 attributeSyntax: 2.5.5.12 omSyntax: 64 instanceType: 4 isSingleValued: TRUE systemOnly: FALSE """ self.ldb.add_ldif(ldif) # We must do a schemaUpdateNow otherwise it's not 100% sure that the schema # will contain the new attribute ldif = """ dn: changetype: modify add: schemaUpdateNow schemaUpdateNow: 1 """ self.ldb.modify_ldif(ldif) # Search for created attribute res = [] res = self.ldb.search("cn=%s,%s" % (attr_name, self.schema_dn), scope=SCOPE_BASE, attrs=["lDAPDisplayName","schemaIDGUID"]) self.assertEquals(len(res), 1) self.assertEquals(res[0]["lDAPDisplayName"][0], attr_ldap_display_name) self.assertTrue("schemaIDGUID" in res[0]) class_name = "test-Class" + time.strftime("%s", time.gmtime()) class_ldap_display_name = class_name.replace("-", "") # First try to create a class with a wrong "defaultObjectCategory" ldif = """ dn: CN=%s,%s""" % (class_name, self.schema_dn) + """ objectClass: top objectClass: classSchema defaultObjectCategory: CN=_ adminDescription: """ + class_name + """ adminDisplayName: """ + class_name + """ cn: """ + class_name + """ governsId: 1.2.840.""" + str(random.randint(1,100000)) + """.1.5.9939 instanceType: 4 objectClassCategory: 1 subClassOf: organizationalPerson systemFlags: 16 rDNAttID: cn systemMustContain: cn systemMustContain: """ + attr_ldap_display_name + """ systemOnly: FALSE """ try: self.ldb.add_ldif(ldif) self.fail() except LdbError, (num, _): self.assertEquals(num, ERR_CONSTRAINT_VIOLATION) ldif = """ dn: CN=%s,%s""" % (class_name, self.schema_dn) + """ objectClass: top objectClass: classSchema adminDescription: """ + class_name + """ adminDisplayName: """ + class_name + """ cn: """ + class_name + """ governsId: 1.2.840.""" + str(random.randint(1,100000)) + """.1.5.9939 instanceType: 4 objectClassCategory: 1 subClassOf: organizationalPerson systemFlags: 16 rDNAttID: cn systemMustContain: cn systemMustContain: """ + attr_ldap_display_name + """ systemOnly: FALSE """ self.ldb.add_ldif(ldif) # Search for created objectclass res = [] res = self.ldb.search("cn=%s,%s" % (class_name, self.schema_dn), scope=SCOPE_BASE, attrs=["lDAPDisplayName", "defaultObjectCategory", "schemaIDGUID", "distinguishedName"]) self.assertEquals(len(res), 1) self.assertEquals(res[0]["lDAPDisplayName"][0], class_ldap_display_name) self.assertEquals(res[0]["defaultObjectCategory"][0], res[0]["distinguishedName"][0]) self.assertTrue("schemaIDGUID" in res[0]) ldif = """ dn: changetype: modify add: schemaUpdateNow schemaUpdateNow: 1 """ self.ldb.modify_ldif(ldif) object_name = "obj" + time.strftime("%s", time.gmtime()) ldif = """ dn: CN=%s,CN=Users,%s"""% (object_name, self.base_dn) + """ objectClass: organizationalPerson objectClass: person objectClass: """ + class_ldap_display_name + """ objectClass: top cn: """ + object_name + """ instanceType: 4 objectCategory: CN=%s,%s"""% (class_name, self.schema_dn) + """ distinguishedName: CN=%s,CN=Users,%s"""% (object_name, self.base_dn) + """ name: """ + object_name + """ """ + attr_ldap_display_name + """: test """ self.ldb.add_ldif(ldif) # Search for created object res = [] res = self.ldb.search("cn=%s,cn=Users,%s" % (object_name, self.base_dn), scope=SCOPE_BASE, attrs=["dn"]) self.assertEquals(len(res), 1) # Delete the object delete_force(self.ldb, "cn=%s,cn=Users,%s" % (object_name, self.base_dn))
class Schema(object): def __init__(self, domain_sid, invocationid=None, schemadn=None, files=None, override_prefixmap=None, additional_prefixmap=None): from samba.provision import setup_path """Load schema for the SamDB from the AD schema files and samba4_schema.ldif :param samdb: Load a schema into a SamDB. :param schemadn: DN of the schema Returns the schema data loaded, to avoid double-parsing when then needing to add it to the db """ self.schemadn = schemadn # We need to have the am_rodc=False just to keep some warnings quiet - # this isn't a real SAM, so it's meaningless. self.ldb = SamDB(global_schema=False, am_rodc=False) if invocationid is not None: self.ldb.set_invocation_id(invocationid) self.schema_data = read_ms_schema( setup_path('ad-schema/MS-AD_Schema_2K8_R2_Attributes.txt'), setup_path('ad-schema/MS-AD_Schema_2K8_R2_Classes.txt')) if files is not None: for file in files: self.schema_data += open(file, 'r').read() self.schema_data = substitute_var(self.schema_data, {"SCHEMADN": schemadn}) check_all_substituted(self.schema_data) self.schema_dn_modify = read_and_sub_file( setup_path("provision_schema_basedn_modify.ldif"), {"SCHEMADN": schemadn}) descr = b64encode(get_schema_descriptor(domain_sid)) self.schema_dn_add = read_and_sub_file( setup_path("provision_schema_basedn.ldif"), {"SCHEMADN": schemadn, "DESCRIPTOR": descr}) if override_prefixmap is not None: self.prefixmap_data = override_prefixmap else: self.prefixmap_data = open(setup_path("prefixMap.txt"), 'r').read() if additional_prefixmap is not None: for map in additional_prefixmap: self.prefixmap_data += "%s\n" % map self.prefixmap_data = b64encode(self.prefixmap_data) # We don't actually add this ldif, just parse it prefixmap_ldif = "dn: %s\nprefixMap:: %s\n\n" % (self.schemadn, self.prefixmap_data) self.set_from_ldif(prefixmap_ldif, self.schema_data, self.schemadn) def set_from_ldif(self, pf, df, dn): dsdb._dsdb_set_schema_from_ldif(self.ldb, pf, df, dn) def write_to_tmp_ldb(self, schemadb_path): self.ldb.connect(url=schemadb_path) self.ldb.transaction_start() try: self.ldb.add_ldif("""dn: @ATTRIBUTES linkID: INTEGER dn: @INDEXLIST @IDXATTR: linkID @IDXATTR: attributeSyntax """) # These bits of LDIF are supplied when the Schema object is created self.ldb.add_ldif(self.schema_dn_add) self.ldb.modify_ldif(self.schema_dn_modify) self.ldb.add_ldif(self.schema_data) except: self.ldb.transaction_cancel() raise else: self.ldb.transaction_commit() # Return a hash with the forward attribute as a key and the back as the # value def linked_attributes(self): return get_linked_attributes(self.schemadn, self.ldb) def dnsyntax_attributes(self): return get_dnsyntax_attributes(self.schemadn, self.ldb) def convert_to_openldap(self, target, mapping): return dsdb._dsdb_convert_schema_to_openldap(self.ldb, target, mapping)
class SchemaTests_msDS_IntId(samba.tests.TestCase): def setUp(self): super(SchemaTests_msDS_IntId, self).setUp() self.ldb = SamDB(host, credentials=creds, session_info=system_session(lp), lp=lp, options=ldb_options) res = self.ldb.search(base="", expression="", scope=SCOPE_BASE, attrs=["schemaNamingContext", "defaultNamingContext", "forestFunctionality"]) self.assertEquals(len(res), 1) self.schema_dn = res[0]["schemaNamingContext"][0] self.base_dn = res[0]["defaultNamingContext"][0] self.forest_level = int(res[0]["forestFunctionality"][0]) def _ldap_schemaUpdateNow(self): ldif = """ dn: changetype: modify add: schemaUpdateNow schemaUpdateNow: 1 """ self.ldb.modify_ldif(ldif) def _make_obj_names(self, prefix): class_name = prefix + time.strftime("%s", time.gmtime()) class_ldap_name = class_name.replace("-", "") class_dn = "CN=%s,%s" % (class_name, self.schema_dn) return (class_name, class_ldap_name, class_dn) def _is_schema_base_object(self, ldb_msg): """Test systemFlags for SYSTEM_FLAG_SCHEMA_BASE_OBJECT (16)""" systemFlags = 0 if "systemFlags" in ldb_msg: systemFlags = int(ldb_msg["systemFlags"][0]) return (systemFlags & 16) != 0 def _make_attr_ldif(self, attr_name, attr_dn): ldif = """ dn: """ + attr_dn + """ objectClass: top objectClass: attributeSchema adminDescription: """ + attr_name + """ adminDisplayName: """ + attr_name + """ cn: """ + attr_name + """ attributeId: 1.2.840.""" + str(random.randint(1,100000)) + """.1.5.9940 attributeSyntax: 2.5.5.12 omSyntax: 64 instanceType: 4 isSingleValued: TRUE systemOnly: FALSE """ return ldif def test_msDS_IntId_on_attr(self): """Testing msDs-IntId creation for Attributes. See MS-ADTS - 3.1.1.Attributes This test should verify that: - Creating attribute with 'msDS-IntId' fails with ERR_UNWILLING_TO_PERFORM - Adding 'msDS-IntId' on existing attribute fails with ERR_CONSTRAINT_VIOLATION - Creating attribute with 'msDS-IntId' set and FLAG_SCHEMA_BASE_OBJECT flag set fails with ERR_UNWILLING_TO_PERFORM - Attributes created with FLAG_SCHEMA_BASE_OBJECT not set have 'msDS-IntId' attribute added internally """ # 1. Create attribute without systemFlags # msDS-IntId should be created if forest functional # level is >= DS_DOMAIN_FUNCTION_2003 # and missing otherwise (attr_name, attr_ldap_name, attr_dn) = self._make_obj_names("msDS-IntId-Attr-1-") ldif = self._make_attr_ldif(attr_name, attr_dn) # try to add msDS-IntId during Attribute creation ldif_fail = ldif + "msDS-IntId: -1993108831\n" try: self.ldb.add_ldif(ldif_fail) self.fail("Adding attribute with preset msDS-IntId should fail") except LdbError, (num, _): self.assertEquals(num, ERR_UNWILLING_TO_PERFORM) # add the new attribute and update schema self.ldb.add_ldif(ldif) self._ldap_schemaUpdateNow() # Search for created attribute res = [] res = self.ldb.search(attr_dn, scope=SCOPE_BASE, attrs=["lDAPDisplayName", "msDS-IntId", "systemFlags"]) self.assertEquals(len(res), 1) self.assertEquals(res[0]["lDAPDisplayName"][0], attr_ldap_name) if self.forest_level >= DS_DOMAIN_FUNCTION_2003: if self._is_schema_base_object(res[0]): self.assertTrue("msDS-IntId" not in res[0]) else: self.assertTrue("msDS-IntId" in res[0]) else: self.assertTrue("msDS-IntId" not in res[0]) msg = Message() msg.dn = Dn(self.ldb, attr_dn) msg["msDS-IntId"] = MessageElement("-1993108831", FLAG_MOD_REPLACE, "msDS-IntId") try: self.ldb.modify(msg) self.fail("Modifying msDS-IntId should return error") except LdbError, (num, _): self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
class SchemaTests(samba.tests.TestCase): def setUp(self): super(SchemaTests, self).setUp() self.ldb = SamDB(host, credentials=creds, session_info=system_session(lp), lp=lp, options=ldb_options) self.base_dn = self.ldb.domain_dn() self.schema_dn = self.ldb.get_schema_basedn().get_linearized() def test_generated_schema(self): """Testing we can read the generated schema via LDAP""" res = self.ldb.search("cn=aggregate,"+self.schema_dn, scope=SCOPE_BASE, attrs=["objectClasses", "attributeTypes", "dITContentRules"]) self.assertEquals(len(res), 1) self.assertTrue("dITContentRules" in res[0]) self.assertTrue("objectClasses" in res[0]) self.assertTrue("attributeTypes" in res[0]) def test_generated_schema_is_operational(self): """Testing we don't get the generated schema via LDAP by default""" # Must keep the "*" form res = self.ldb.search("cn=aggregate,"+self.schema_dn, scope=SCOPE_BASE, attrs=["*"]) self.assertEquals(len(res), 1) self.assertFalse("dITContentRules" in res[0]) self.assertFalse("objectClasses" in res[0]) self.assertFalse("attributeTypes" in res[0]) def test_schemaUpdateNow(self): """Testing schemaUpdateNow""" attr_name = "test-Attr" + time.strftime("%s", time.gmtime()) attr_ldap_display_name = attr_name.replace("-", "") ldif = """ dn: CN=%s,%s""" % (attr_name, self.schema_dn) + """ objectClass: top objectClass: attributeSchema adminDescription: """ + attr_name + """ adminDisplayName: """ + attr_name + """ cn: """ + attr_name + """ attributeId: 1.2.840.""" + str(random.randint(1,100000)) + """.1.5.9940 attributeSyntax: 2.5.5.12 omSyntax: 64 instanceType: 4 isSingleValued: TRUE systemOnly: FALSE """ self.ldb.add_ldif(ldif) # We must do a schemaUpdateNow otherwise it's not 100% sure that the schema # will contain the new attribute ldif = """ dn: changetype: modify add: schemaUpdateNow schemaUpdateNow: 1 """ self.ldb.modify_ldif(ldif) # Search for created attribute res = [] res = self.ldb.search("cn=%s,%s" % (attr_name, self.schema_dn), scope=SCOPE_BASE, attrs=["lDAPDisplayName","schemaIDGUID", "msDS-IntID"]) self.assertEquals(len(res), 1) self.assertEquals(res[0]["lDAPDisplayName"][0], attr_ldap_display_name) self.assertTrue("schemaIDGUID" in res[0]) if "msDS-IntId" in res[0]: msDS_IntId = int(res[0]["msDS-IntId"][0]) if msDS_IntId < 0: msDS_IntId += (1 << 32) else: msDS_IntId = None class_name = "test-Class" + time.strftime("%s", time.gmtime()) class_ldap_display_name = class_name.replace("-", "") # First try to create a class with a wrong "defaultObjectCategory" ldif = """ dn: CN=%s,%s""" % (class_name, self.schema_dn) + """ objectClass: top objectClass: classSchema defaultObjectCategory: CN=_ adminDescription: """ + class_name + """ adminDisplayName: """ + class_name + """ cn: """ + class_name + """ governsId: 1.2.840.""" + str(random.randint(1,100000)) + """.1.5.9939 instanceType: 4 objectClassCategory: 1 subClassOf: organizationalPerson systemFlags: 16 rDNAttID: cn systemMustContain: cn systemMustContain: """ + attr_ldap_display_name + """ systemOnly: FALSE """ try: self.ldb.add_ldif(ldif) self.fail() except LdbError, (num, _): self.assertEquals(num, ERR_CONSTRAINT_VIOLATION) ldif = """ dn: CN=%s,%s""" % (class_name, self.schema_dn) + """ objectClass: top objectClass: classSchema adminDescription: """ + class_name + """ adminDisplayName: """ + class_name + """ cn: """ + class_name + """ governsId: 1.2.840.""" + str(random.randint(1,100000)) + """.1.5.9939 instanceType: 4 objectClassCategory: 1 subClassOf: organizationalPerson systemFlags: 16 rDNAttID: cn systemMustContain: cn systemMustContain: """ + attr_ldap_display_name + """ systemOnly: FALSE """ self.ldb.add_ldif(ldif) # Search for created objectclass res = [] res = self.ldb.search("cn=%s,%s" % (class_name, self.schema_dn), scope=SCOPE_BASE, attrs=["lDAPDisplayName", "defaultObjectCategory", "schemaIDGUID", "distinguishedName"]) self.assertEquals(len(res), 1) self.assertEquals(res[0]["lDAPDisplayName"][0], class_ldap_display_name) self.assertEquals(res[0]["defaultObjectCategory"][0], res[0]["distinguishedName"][0]) self.assertTrue("schemaIDGUID" in res[0]) ldif = """ dn: changetype: modify add: schemaUpdateNow schemaUpdateNow: 1 """ self.ldb.modify_ldif(ldif) object_name = "obj" + time.strftime("%s", time.gmtime()) ldif = """ dn: CN=%s,CN=Users,%s"""% (object_name, self.base_dn) + """ objectClass: organizationalPerson objectClass: person objectClass: """ + class_ldap_display_name + """ objectClass: top cn: """ + object_name + """ instanceType: 4 objectCategory: CN=%s,%s"""% (class_name, self.schema_dn) + """ distinguishedName: CN=%s,CN=Users,%s"""% (object_name, self.base_dn) + """ name: """ + object_name + """ """ + attr_ldap_display_name + """: test """ self.ldb.add_ldif(ldif) # Search for created object obj_res = self.ldb.search("cn=%s,cn=Users,%s" % (object_name, self.base_dn), scope=SCOPE_BASE, attrs=["replPropertyMetaData"]) self.assertEquals(len(obj_res), 1) self.assertTrue("replPropertyMetaData" in obj_res[0]) val = obj_res[0]["replPropertyMetaData"][0] repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob, str(val)) obj = repl.ctr # Windows 2000 functional level won't have this. It is too # hard to work it out from the prefixmap however, so we skip # this test in that case. if msDS_IntId is not None: found = False for o in repl.ctr.array: if o.attid == msDS_IntId: found = True break self.assertTrue(found, "Did not find 0x%08x in replPropertyMetaData" % msDS_IntId) # Delete the object delete_force(self.ldb, "cn=%s,cn=Users,%s" % (object_name, self.base_dn))
class UserTests(samba.tests.TestCase): def add_if_possible(self, *args, **kwargs): """In these tests sometimes things are left in the database deliberately, so we don't worry if we fail to add them a second time.""" try: self.ldb.add(*args, **kwargs) except LdbError: pass def setUp(self): super(UserTests, self).setUp() self.state = GlobalState # the class itself, not an instance self.lp = lp self.ldb = SamDB(host, credentials=creds, session_info=system_session(lp), lp=lp) self.base_dn = self.ldb.domain_dn() self.ou = "OU=pid%s,%s" % (os.getpid(), self.base_dn) self.ou_users = "OU=users,%s" % self.ou self.ou_groups = "OU=groups,%s" % self.ou self.ou_computers = "OU=computers,%s" % self.ou self.state.test_number += 1 random.seed(self.state.test_number) def tearDown(self): super(UserTests, self).tearDown() def test_00_00_do_nothing(self): # this gives us an idea of the overhead pass def test_00_01_do_nothing_relevant(self): # takes around 1 second on i7-4770 j = 0 for i in range(30000000): j += i def test_00_02_do_nothing_sleepily(self): time.sleep(1) def test_00_03_add_ous_and_groups(self): # initialise the database for dn in (self.ou, self.ou_users, self.ou_groups, self.ou_computers): self.ldb.add({ "dn": dn, "objectclass": "organizationalUnit" }) for i in range(N_GROUPS): self.ldb.add({ "dn": "cn=g%d,%s" % (i, self.ou_groups), "objectclass": "group" }) self.state.n_groups = N_GROUPS def _add_users(self, start, end): for i in range(start, end): self.ldb.add({ "dn": "cn=u%d,%s" % (i, self.ou_users), "objectclass": "user" }) def _add_users_ldif(self, start, end): lines = [] for i in range(start, end): lines.append("dn: cn=u%d,%s" % (i, self.ou_users)) lines.append("objectclass: user") lines.append("") self.ldb.add_ldif('\n'.join(lines)) def _test_join(self): tmpdir = tempfile.mkdtemp() if '://' in host: server = host.split('://', 1)[1] else: server = host cmd = cmd_sambatool.subcommands['domain'].subcommands['join'] result = cmd._run("samba-tool domain join", creds.get_realm(), "dc", "-U%s%%%s" % (creds.get_username(), creds.get_password()), '--targetdir=%s' % tmpdir, '--server=%s' % server) shutil.rmtree(tmpdir) def _test_unindexed_search(self): expressions = [ ('(&(objectclass=user)(description=' 'Built-in account for adminstering the computer/domain))'), '(description=Built-in account for adminstering the computer/domain)', '(objectCategory=*)', '(samaccountname=Administrator*)' ] for expression in expressions: t = time.time() for i in range(25): self.ldb.search(self.ou, expression=expression, scope=SCOPE_SUBTREE, attrs=['cn']) print('%d %s took %s' % (i, expression, time.time() - t), file=sys.stderr) def _test_indexed_search(self): expressions = ['(objectclass=group)', '(samaccountname=Administrator)' ] for expression in expressions: t = time.time() for i in range(4000): self.ldb.search(self.ou, expression=expression, scope=SCOPE_SUBTREE, attrs=['cn']) print('%d runs %s took %s' % (i, expression, time.time() - t), file=sys.stderr) def _test_base_search(self): for dn in [self.base_dn, self.ou, self.ou_users, self.ou_groups, self.ou_computers]: for i in range(4000): try: self.ldb.search(dn, scope=SCOPE_BASE, attrs=['cn']) except LdbError as e: (num, msg) = e.args if num != ERR_NO_SUCH_OBJECT: raise def _test_base_search_failing(self): pattern = 'missing%d' + self.ou for i in range(4000): try: self.ldb.search(pattern % i, scope=SCOPE_BASE, attrs=['cn']) except LdbError as (num, msg): if num != ERR_NO_SUCH_OBJECT: raise
class Schema(object): # the schema files (and corresponding object version) that we know about base_schemas = { "2008_R2_old": ("MS-AD_Schema_2K8_R2_Attributes.txt", "MS-AD_Schema_2K8_R2_Classes.txt", 47), "2008_R2": ("Attributes_for_AD_DS__Windows_Server_2008_R2.ldf", "Classes_for_AD_DS__Windows_Server_2008_R2.ldf", 47), "2012": ("AD_DS_Attributes__Windows_Server_2012.ldf", "AD_DS_Classes__Windows_Server_2012.ldf", 56), "2012_R2": ("AD_DS_Attributes__Windows_Server_2012_R2.ldf", "AD_DS_Classes__Windows_Server_2012_R2.ldf", 69), } def __init__(self, domain_sid, invocationid=None, schemadn=None, files=None, override_prefixmap=None, additional_prefixmap=None, base_schema=None): from samba.provision import setup_path """Load schema for the SamDB from the AD schema files and samba4_schema.ldif :param samdb: Load a schema into a SamDB. :param schemadn: DN of the schema Returns the schema data loaded, to avoid double-parsing when then needing to add it to the db """ if base_schema is None: base_schema = Schema.default_base_schema() self.base_schema = base_schema self.schemadn = schemadn # We need to have the am_rodc=False just to keep some warnings quiet - # this isn't a real SAM, so it's meaningless. self.ldb = SamDB(global_schema=False, am_rodc=False) if invocationid is not None: self.ldb.set_invocation_id(invocationid) self.schema_data = read_ms_schema( setup_path('ad-schema/%s' % Schema.base_schemas[base_schema][0]), setup_path('ad-schema/%s' % Schema.base_schemas[base_schema][1])) if files is not None: for file in files: self.schema_data += open(file, 'rb').read() self.schema_data = substitute_var(self.schema_data, {"SCHEMADN": schemadn}) check_all_substituted(self.schema_data) schema_version = str(Schema.get_version(base_schema)) self.schema_dn_modify = read_and_sub_file( setup_path("provision_schema_basedn_modify.ldif"), { "SCHEMADN": schemadn, "OBJVERSION": schema_version }) descr = b64encode(get_schema_descriptor(domain_sid)).decode('utf8') self.schema_dn_add = read_and_sub_file( setup_path("provision_schema_basedn.ldif"), { "SCHEMADN": schemadn, "DESCRIPTOR": descr }) if override_prefixmap is not None: self.prefixmap_data = override_prefixmap else: self.prefixmap_data = open(setup_path("prefixMap.txt"), 'rb').read() if additional_prefixmap is not None: for map in additional_prefixmap: self.prefixmap_data += "%s\n" % map self.prefixmap_data = b64encode(self.prefixmap_data).decode('utf8') # We don't actually add this ldif, just parse it prefixmap_ldif = "dn: %s\nprefixMap:: %s\n\n" % (self.schemadn, self.prefixmap_data) self.set_from_ldif(prefixmap_ldif, self.schema_data, self.schemadn) @staticmethod def default_base_schema(): """Returns the default base schema to use""" return "2008_R2" @staticmethod def get_version(base_schema): """Returns the base schema's object version, e.g. 47 for 2008_R2""" return Schema.base_schemas[base_schema][2] def set_from_ldif(self, pf, df, dn): dsdb._dsdb_set_schema_from_ldif(self.ldb, pf, df, dn) def write_to_tmp_ldb(self, schemadb_path): self.ldb.connect(url=schemadb_path) self.ldb.transaction_start() try: # These are actually ignored, as the schema has been forced # when the ldb object was created, and that overrides this self.ldb.add_ldif("""dn: @ATTRIBUTES linkID: INTEGER dn: @INDEXLIST @IDXATTR: linkID @IDXATTR: attributeSyntax @IDXGUID: objectGUID """) schema_dn_add = self.schema_dn_add \ + "objectGUID: 24e2ca70-b093-4ae8-84c0-2d7ac652a1b8\n" # These bits of LDIF are supplied when the Schema object is created self.ldb.add_ldif(schema_dn_add) self.ldb.modify_ldif(self.schema_dn_modify) self.ldb.add_ldif(self.schema_data) except: self.ldb.transaction_cancel() raise else: self.ldb.transaction_commit() # Return a hash with the forward attribute as a key and the back as the # value def linked_attributes(self): return get_linked_attributes(self.schemadn, self.ldb) def dnsyntax_attributes(self): return get_dnsyntax_attributes(self.schemadn, self.ldb) def convert_to_openldap(self, target, mapping): return dsdb._dsdb_convert_schema_to_openldap(self.ldb, target, mapping)
class Schema(object): # the schema files (and corresponding object version) that we know about base_schemas = { "2008_R2_old" : ("MS-AD_Schema_2K8_R2_Attributes.txt", "MS-AD_Schema_2K8_R2_Classes.txt", 47), "2008_R2" : ("Attributes_for_AD_DS__Windows_Server_2008_R2.ldf", "Classes_for_AD_DS__Windows_Server_2008_R2.ldf", 47), "2012" : ("AD_DS_Attributes__Windows_Server_2012.ldf", "AD_DS_Classes__Windows_Server_2012.ldf", 56), "2012_R2" : ("AD_DS_Attributes__Windows_Server_2012_R2.ldf", "AD_DS_Classes__Windows_Server_2012_R2.ldf", 69), } def __init__(self, domain_sid, invocationid=None, schemadn=None, files=None, override_prefixmap=None, additional_prefixmap=None, base_schema=None): from samba.provision import setup_path """Load schema for the SamDB from the AD schema files and samba4_schema.ldif :param samdb: Load a schema into a SamDB. :param schemadn: DN of the schema Returns the schema data loaded, to avoid double-parsing when then needing to add it to the db """ if base_schema is None: base_schema = Schema.default_base_schema() self.base_schema = base_schema self.schemadn = schemadn # We need to have the am_rodc=False just to keep some warnings quiet - # this isn't a real SAM, so it's meaningless. self.ldb = SamDB(global_schema=False, am_rodc=False) if invocationid is not None: self.ldb.set_invocation_id(invocationid) self.schema_data = read_ms_schema( setup_path('ad-schema/%s' % Schema.base_schemas[base_schema][0]), setup_path('ad-schema/%s' % Schema.base_schemas[base_schema][1])) if files is not None: for file in files: self.schema_data += open(file, 'r').read() self.schema_data = substitute_var(self.schema_data, {"SCHEMADN": schemadn}) check_all_substituted(self.schema_data) schema_version = str(Schema.get_version(base_schema)) self.schema_dn_modify = read_and_sub_file( setup_path("provision_schema_basedn_modify.ldif"), {"SCHEMADN": schemadn, "OBJVERSION" : schema_version}) descr = b64encode(get_schema_descriptor(domain_sid)).decode('utf8') self.schema_dn_add = read_and_sub_file( setup_path("provision_schema_basedn.ldif"), {"SCHEMADN": schemadn, "DESCRIPTOR": descr}) if override_prefixmap is not None: self.prefixmap_data = override_prefixmap else: self.prefixmap_data = open(setup_path("prefixMap.txt"), 'r').read() if additional_prefixmap is not None: for map in additional_prefixmap: self.prefixmap_data += "%s\n" % map self.prefixmap_data = b64encode(self.prefixmap_data).decode('utf8') # We don't actually add this ldif, just parse it prefixmap_ldif = "dn: %s\nprefixMap:: %s\n\n" % (self.schemadn, self.prefixmap_data) self.set_from_ldif(prefixmap_ldif, self.schema_data, self.schemadn) @staticmethod def default_base_schema(): """Returns the default base schema to use""" return "2008_R2" @staticmethod def get_version(base_schema): """Returns the base schema's object version, e.g. 47 for 2008_R2""" return Schema.base_schemas[base_schema][2] def set_from_ldif(self, pf, df, dn): dsdb._dsdb_set_schema_from_ldif(self.ldb, pf, df, dn) def write_to_tmp_ldb(self, schemadb_path): self.ldb.connect(url=schemadb_path) self.ldb.transaction_start() try: # These are actually ignored, as the schema has been forced # when the ldb object was created, and that overrides this self.ldb.add_ldif("""dn: @ATTRIBUTES linkID: INTEGER dn: @INDEXLIST @IDXATTR: linkID @IDXATTR: attributeSyntax @IDXGUID: objectGUID """) schema_dn_add = self.schema_dn_add \ + "objectGUID: 24e2ca70-b093-4ae8-84c0-2d7ac652a1b8\n" # These bits of LDIF are supplied when the Schema object is created self.ldb.add_ldif(schema_dn_add) self.ldb.modify_ldif(self.schema_dn_modify) self.ldb.add_ldif(self.schema_data) except: self.ldb.transaction_cancel() raise else: self.ldb.transaction_commit() # Return a hash with the forward attribute as a key and the back as the # value def linked_attributes(self): return get_linked_attributes(self.schemadn, self.ldb) def dnsyntax_attributes(self): return get_dnsyntax_attributes(self.schemadn, self.ldb) def convert_to_openldap(self, target, mapping): return dsdb._dsdb_convert_schema_to_openldap(self.ldb, target, mapping)
class UserTests(samba.tests.TestCase): def add_if_possible(self, *args, **kwargs): """In these tests sometimes things are left in the database deliberately, so we don't worry if we fail to add them a second time.""" try: self.ldb.add(*args, **kwargs) except LdbError: pass def setUp(self): super(UserTests, self).setUp() self.state = GlobalState # the class itself, not an instance self.lp = lp self.ldb = SamDB(host, credentials=creds, session_info=system_session(lp), lp=lp) self.base_dn = self.ldb.domain_dn() self.ou = "OU=pid%s,%s" % (os.getpid(), self.base_dn) self.ou_users = "OU=users,%s" % self.ou self.ou_groups = "OU=groups,%s" % self.ou self.ou_computers = "OU=computers,%s" % self.ou for dn in (self.ou, self.ou_users, self.ou_groups, self.ou_computers): self.add_if_possible({ "dn": dn, "objectclass": "organizationalUnit" }) def tearDown(self): super(UserTests, self).tearDown() def test_00_00_do_nothing(self): # this gives us an idea of the overhead pass def _prepare_n_groups(self, n): self.state.n_groups = n for i in range(n): self.add_if_possible({ "dn": "cn=g%d,%s" % (i, self.ou_groups), "objectclass": "group" }) def _add_users(self, start, end): for i in range(start, end): self.ldb.add({ "dn": "cn=u%d,%s" % (i, self.ou_users), "objectclass": "user" }) def _add_users_ldif(self, start, end): lines = [] for i in range(start, end): lines.append("dn: cn=u%d,%s" % (i, self.ou_users)) lines.append("objectclass: user") lines.append("") self.ldb.add_ldif('\n'.join(lines)) def _test_unindexed_search(self): expressions = [( '(&(objectclass=user)(description=' 'Built-in account for adminstering the computer/domain))' ), '(description=Built-in account for adminstering the computer/domain)', '(objectCategory=*)', '(samaccountname=Administrator*)'] for expression in expressions: t = time.time() for i in range(50): self.ldb.search(self.ou, expression=expression, scope=SCOPE_SUBTREE, attrs=['cn']) print('%d %s took %s' % (i, expression, time.time() - t), file=sys.stderr) def _test_indexed_search(self): expressions = ['(objectclass=group)', '(samaccountname=Administrator)'] for expression in expressions: t = time.time() for i in range(10000): self.ldb.search(self.ou, expression=expression, scope=SCOPE_SUBTREE, attrs=['cn']) print('%d runs %s took %s' % (i, expression, time.time() - t), file=sys.stderr) def _test_complex_search(self): classes = ['samaccountname', 'objectCategory', 'dn', 'member'] values = ['*', '*t*', 'g*', 'user'] comparators = ['=', '<=', '>='] # '~=' causes error maybe_not = ['!(', ''] joiners = ['&', '|'] # The number of permuations is 18432, which is not huge but # would take hours to search. So we take a sample. all_permutations = list( itertools.product(joiners, classes, classes, values, values, comparators, comparators, maybe_not, maybe_not)) random.seed(1) for (j, c1, c2, v1, v2, o1, o2, n1, n2) in random.sample(all_permutations, 100): expression = ''.join([ '(', j, '(', n1, c1, o1, v1, '))' if n1 else ')', '(', n2, c2, o2, v2, '))' if n2 else ')', ')' ]) print(expression) self.ldb.search(self.ou, expression=expression, scope=SCOPE_SUBTREE, attrs=['cn']) def _test_member_search(self, rounds=10): expressions = [] for d in range(50): expressions.append('(member=cn=u%d,%s)' % (d + 500, self.ou_users)) expressions.append('(member=u%d*)' % (d + 700, )) for i in range(N_GROUPS): expressions.append('(memberOf=cn=g%d,%s)' % (i, self.ou_groups)) expressions.append('(memberOf=cn=g%d*)' % (i, )) expressions.append('(memberOf=cn=*%s*)' % self.ou_groups) for expression in expressions: t = time.time() for i in range(rounds): self.ldb.search(self.ou, expression=expression, scope=SCOPE_SUBTREE, attrs=['cn']) print('%d runs %s took %s' % (i, expression, time.time() - t), file=sys.stderr) def _test_add_many_users(self, n=BATCH_SIZE): s = self.state.next_user_id e = s + n self._add_users(s, e) self.state.next_user_id = e def _test_add_many_users_ldif(self, n=BATCH_SIZE): s = self.state.next_user_id e = s + n self._add_users_ldif(s, e) self.state.next_user_id = e def _link_user_and_group(self, u, g): m = Message() m.dn = Dn(self.ldb, "CN=g%d,%s" % (g, self.ou_groups)) m["member"] = MessageElement("cn=u%d,%s" % (u, self.ou_users), FLAG_MOD_ADD, "member") self.ldb.modify(m) def _test_link_many_users(self, n=BATCH_SIZE): self._prepare_n_groups(N_GROUPS) s = self.state.next_linked_user e = s + n for i in range(s, e): # put everyone in group 0, and one other group g = i % (N_GROUPS - 1) + 1 self._link_user_and_group(i, g) self._link_user_and_group(i, 0) self.state.next_linked_user = e test_00_01_adding_users_1000 = _test_add_many_users test_00_10_complex_search_1k_users = _test_complex_search test_00_11_unindexed_search_1k_users = _test_unindexed_search test_00_12_indexed_search_1k_users = _test_indexed_search test_00_13_member_search_1k_users = _test_member_search test_01_02_adding_users_2000_ldif = _test_add_many_users_ldif test_01_03_adding_users_3000 = _test_add_many_users test_01_10_complex_search_3k_users = _test_complex_search test_01_11_unindexed_search_3k_users = _test_unindexed_search test_01_12_indexed_search_3k_users = _test_indexed_search def test_01_13_member_search_3k_users(self): self._test_member_search(rounds=5) test_02_01_link_users_1000 = _test_link_many_users test_02_02_link_users_2000 = _test_link_many_users test_02_03_link_users_3000 = _test_link_many_users test_03_10_complex_search_linked_users = _test_complex_search test_03_11_unindexed_search_linked_users = _test_unindexed_search test_03_12_indexed_search_linked_users = _test_indexed_search def test_03_13_member_search_linked_users(self): self._test_member_search(rounds=2)
objectClass: nTDSSiteSettings cn: NTDS Site Settings showInAdvancedViewOnly: TRUE name: NTDS Site Settings objectCategory: CN=NTDS-Site-Settings,CN=Schema,CN=Configuration,%(samba4_ldap_base)s dn: CN=Servers,CN=%(branchsite_name)s,CN=Sites,CN=Configuration,%(samba4_ldap_base)s objectClass: serversContainer cn: Servers showInAdvancedViewOnly: TRUE name: Servers systemFlags: 33554432 objectCategory: CN=Servers-Container,CN=Schema,CN=Configuration,%(samba4_ldap_base)s ''' % ldif_dict samdb.add_ldif(site_add_ldif) print("created site %s" % opts.site) if opts.sitelink and not opts.createsitelink: # and add it to the sitelink sitelink_modify_ldif = ''' dn: CN=%(sitelink)s,CN=IP,CN=Inter-Site Transports,CN=Sites,CN=Configuration,%(samba4_ldap_base)s changetype: modify add: siteList siteList: CN=%(branchsite_name)s,CN=Sites,CN=Configuration,%(samba4_ldap_base)s ''' % ldif_dict samdb.modify_ldif(sitelink_modify_ldif) print("added site %s to sitelink %s" % (opts.site, opts.sitelink)) elif opts.site: res = samdb.search("CN=Configuration,%s" % samba4_ldap_base, scope=ldb.SCOPE_SUBTREE, expression="(&(objectClass=site)(cn=%s))" % opts.site)
class UserTests(samba.tests.TestCase): def add_if_possible(self, *args, **kwargs): """In these tests sometimes things are left in the database deliberately, so we don't worry if we fail to add them a second time.""" try: self.ldb.add(*args, **kwargs) except LdbError: pass def setUp(self): super(UserTests, self).setUp() self.state = GlobalState # the class itself, not an instance self.lp = lp self.ldb = SamDB(host, credentials=creds, session_info=system_session(lp), lp=lp) self.base_dn = self.ldb.domain_dn() self.ou = "OU=pid%s,%s" % (os.getpid(), self.base_dn) self.ou_users = "OU=users,%s" % self.ou self.ou_groups = "OU=groups,%s" % self.ou self.ou_computers = "OU=computers,%s" % self.ou self.state.test_number += 1 random.seed(self.state.test_number) def tearDown(self): super(UserTests, self).tearDown() def test_00_00_do_nothing(self): # this gives us an idea of the overhead pass def test_00_01_do_nothing_relevant(self): # takes around 1 second on i7-4770 j = 0 for i in range(30000000): j += i def test_00_02_do_nothing_sleepily(self): time.sleep(1) def test_00_03_add_ous_and_groups(self): # initialise the database for dn in (self.ou, self.ou_users, self.ou_groups, self.ou_computers): self.ldb.add({"dn": dn, "objectclass": "organizationalUnit"}) for i in range(N_GROUPS): self.ldb.add({ "dn": "cn=g%d,%s" % (i, self.ou_groups), "objectclass": "group" }) self.state.n_groups = N_GROUPS def _add_users(self, start, end): for i in range(start, end): self.ldb.add({ "dn": "cn=u%d,%s" % (i, self.ou_users), "objectclass": "user" }) def _add_users_ldif(self, start, end): lines = [] for i in range(start, end): lines.append("dn: cn=u%d,%s" % (i, self.ou_users)) lines.append("objectclass: user") lines.append("") self.ldb.add_ldif('\n'.join(lines)) def _test_join(self): tmpdir = tempfile.mkdtemp() if '://' in host: server = host.split('://', 1)[1] else: server = host cmd = cmd_sambatool.subcommands['domain'].subcommands['join'] result = cmd._run( "samba-tool domain join", creds.get_realm(), "dc", "-U%s%%%s" % (creds.get_username(), creds.get_password()), '--targetdir=%s' % tmpdir, '--server=%s' % server) shutil.rmtree(tmpdir) def _test_unindexed_search(self): expressions = [( '(&(objectclass=user)(description=' 'Built-in account for adminstering the computer/domain))' ), '(description=Built-in account for adminstering the computer/domain)', '(objectCategory=*)', '(samaccountname=Administrator*)'] for expression in expressions: t = time.time() for i in range(25): self.ldb.search(self.ou, expression=expression, scope=SCOPE_SUBTREE, attrs=['cn']) print('%d %s took %s' % (i, expression, time.time() - t), file=sys.stderr) def _test_indexed_search(self): expressions = ['(objectclass=group)', '(samaccountname=Administrator)'] for expression in expressions: t = time.time() for i in range(4000): self.ldb.search(self.ou, expression=expression, scope=SCOPE_SUBTREE, attrs=['cn']) print('%d runs %s took %s' % (i, expression, time.time() - t), file=sys.stderr) def _test_base_search(self): for dn in [ self.base_dn, self.ou, self.ou_users, self.ou_groups, self.ou_computers ]: for i in range(4000): try: self.ldb.search(dn, scope=SCOPE_BASE, attrs=['cn']) except LdbError as e: (num, msg) = e.args if num != ERR_NO_SUCH_OBJECT: raise def _test_base_search_failing(self): pattern = 'missing%d' + self.ou for i in range(4000): try: self.ldb.search(pattern % i, scope=SCOPE_BASE, attrs=['cn']) except LdbError as e: (num, msg) = e if num != ERR_NO_SUCH_OBJECT: raise def search_expression_list(self, expressions, rounds, attrs=['cn'], scope=SCOPE_SUBTREE): for expression in expressions: t = time.time() for i in range(rounds): self.ldb.search(self.ou, expression=expression, scope=SCOPE_SUBTREE, attrs=['cn']) print('%d runs %s took %s' % (i, expression, time.time() - t), file=sys.stderr) def _test_complex_search(self, n=100): classes = ['samaccountname', 'objectCategory', 'dn', 'member'] values = ['*', '*t*', 'g*', 'user'] comparators = ['=', '<=', '>='] # '~=' causes error maybe_not = ['!(', ''] joiners = ['&', '|'] # The number of permuations is 18432, which is not huge but # would take hours to search. So we take a sample. all_permutations = list( itertools.product(joiners, classes, classes, values, values, comparators, comparators, maybe_not, maybe_not)) expressions = [] for (j, c1, c2, v1, v2, o1, o2, n1, n2) in random.sample(all_permutations, n): expression = ''.join([ '(', j, '(', n1, c1, o1, v1, '))' if n1 else ')', '(', n2, c2, o2, v2, '))' if n2 else ')', ')' ]) expressions.append(expression) self.search_expression_list(expressions, 1) def _test_member_search(self, rounds=10): expressions = [] for d in range(20): expressions.append('(member=cn=u%d,%s)' % (d + 500, self.ou_users)) expressions.append('(member=u%d*)' % (d + 700, )) self.search_expression_list(expressions, rounds) def _test_memberof_search(self, rounds=200): expressions = [] for i in range(min(self.state.n_groups, rounds)): expressions.append('(memberOf=cn=g%d,%s)' % (i, self.ou_groups)) expressions.append('(memberOf=cn=g%d*)' % (i, )) expressions.append('(memberOf=cn=*%s*)' % self.ou_groups) self.search_expression_list(expressions, 2) def _test_add_many_users(self, n=BATCH_SIZE): s = self.state.next_user_id e = s + n self._add_users(s, e) self.state.next_user_id = e def _test_add_many_users_ldif(self, n=BATCH_SIZE): s = self.state.next_user_id e = s + n self._add_users_ldif(s, e) self.state.next_user_id = e def _link_user_and_group(self, u, g): link = (u, g) if link in self.state.active_links: return False m = Message() m.dn = Dn(self.ldb, "CN=g%d,%s" % (g, self.ou_groups)) m["member"] = MessageElement("cn=u%d,%s" % (u, self.ou_users), FLAG_MOD_ADD, "member") self.ldb.modify(m) self.state.active_links.add(link) return True def _unlink_user_and_group(self, u, g): link = (u, g) if link not in self.state.active_links: return False user = "******" % (u, self.ou_users) group = "CN=g%d,%s" % (g, self.ou_groups) m = Message() m.dn = Dn(self.ldb, group) m["member"] = MessageElement(user, FLAG_MOD_DELETE, "member") self.ldb.modify(m) self.state.active_links.remove(link) return True def _test_link_many_users(self, n=LINK_BATCH_SIZE): # this links unevenly, putting more users in the first group # and fewer in the last. ng = self.state.n_groups nu = self.state.next_user_id while n: u = random.randrange(nu) g = random.randrange(random.randrange(ng) + 1) if self._link_user_and_group(u, g): n -= 1 def _test_link_many_users_batch(self, n=(LINK_BATCH_SIZE * 10)): # this links unevenly, putting more users in the first group # and fewer in the last. ng = self.state.n_groups nu = self.state.next_user_id messages = [] for g in range(ng): m = Message() m.dn = Dn(self.ldb, "CN=g%d,%s" % (g, self.ou_groups)) messages.append(m) while n: u = random.randrange(nu) g = random.randrange(random.randrange(ng) + 1) link = (u, g) if link in self.state.active_links: continue m = messages[g] m["member%s" % u] = MessageElement( "cn=u%d,%s" % (u, self.ou_users), FLAG_MOD_ADD, "member") self.state.active_links.add(link) n -= 1 for m in messages: try: self.ldb.modify(m) except LdbError as e: print(e) print(m) def _test_remove_some_links(self, n=(LINK_BATCH_SIZE // 2)): victims = random.sample(list(self.state.active_links), n) for x in victims: self._unlink_user_and_group(*x) test_00_11_join_empty_dc = _test_join test_00_12_adding_users_2000 = _test_add_many_users test_00_20_join_unlinked_2k_users = _test_join test_00_21_unindexed_search_2k_users = _test_unindexed_search test_00_22_indexed_search_2k_users = _test_indexed_search test_00_23_complex_search_2k_users = _test_complex_search test_00_24_member_search_2k_users = _test_member_search test_00_25_memberof_search_2k_users = _test_memberof_search test_00_27_base_search_2k_users = _test_base_search test_00_28_base_search_failing_2k_users = _test_base_search_failing test_01_01_link_2k_users = _test_link_many_users test_01_02_link_2k_users_batch = _test_link_many_users_batch test_02_10_join_2k_linked_dc = _test_join test_02_11_unindexed_search_2k_linked_dc = _test_unindexed_search test_02_12_indexed_search_2k_linked_dc = _test_indexed_search test_04_01_remove_some_links_2k = _test_remove_some_links test_05_01_adding_users_after_links_4k_ldif = _test_add_many_users_ldif test_06_04_link_users_4k = _test_link_many_users test_06_05_link_users_4k_batch = _test_link_many_users_batch test_07_01_adding_users_after_links_6k = _test_add_many_users def _test_ldif_well_linked_group(self, link_chance=1.0): g = self.state.n_groups self.state.n_groups += 1 lines = ["dn: CN=g%d,%s" % (g, self.ou_groups), "objectclass: group"] for i in range(self.state.next_user_id): if random.random() <= link_chance: lines.append("member: cn=u%d,%s" % (i, self.ou_users)) self.state.active_links.add((i, g)) lines.append("") self.ldb.add_ldif('\n'.join(lines)) test_09_01_add_fully_linked_group = _test_ldif_well_linked_group def test_09_02_add_exponentially_diminishing_linked_groups(self): linkage = 0.8 while linkage > 0.01: self._test_ldif_well_linked_group(linkage) linkage *= 0.75 test_09_04_link_users_6k = _test_link_many_users test_10_01_unindexed_search_6k_users = _test_unindexed_search test_10_02_indexed_search_6k_users = _test_indexed_search test_10_27_base_search_6k_users = _test_base_search test_10_28_base_search_failing_6k_users = _test_base_search_failing def test_10_03_complex_search_6k_users(self): self._test_complex_search(n=50) def test_10_04_member_search_6k_users(self): self._test_member_search(rounds=1) def test_10_05_memberof_search_6k_users(self): self._test_memberof_search(rounds=5) test_11_02_join_full_dc = _test_join test_12_01_remove_some_links_6k = _test_remove_some_links def _test_delete_many_users(self, n=DELETE_BATCH_SIZE): e = self.state.next_user_id s = max(0, e - n) self.state.next_user_id = s for i in range(s, e): self.ldb.delete("cn=u%d,%s" % (i, self.ou_users)) for x in tuple(self.state.active_links): if s >= x[0] > e: self.state.active_links.remove(x) test_20_01_delete_users_6k = _test_delete_many_users def test_21_01_delete_10_groups(self): for i in range(self.state.n_groups - 10, self.state.n_groups): self.ldb.delete("cn=g%d,%s" % (i, self.ou_groups)) self.state.n_groups -= 10 for x in tuple(self.state.active_links): if x[1] >= self.state.n_groups: self.state.active_links.remove(x) test_21_02_delete_users_5950 = _test_delete_many_users def test_22_01_delete_all_groups(self): for i in range(self.state.n_groups): self.ldb.delete("cn=g%d,%s" % (i, self.ou_groups)) self.state.n_groups = 0 self.state.active_links = set() # XXX assert the state is as we think, using searches def test_23_01_delete_users_5900_after_groups(self): # we do not delete everything because it takes too long n = 4 * DELETE_BATCH_SIZE self._test_delete_many_users(n=n) test_24_02_join_after_partial_cleanup = _test_join
class UserTests(samba.tests.TestCase): def add_if_possible(self, *args, **kwargs): """In these tests sometimes things are left in the database deliberately, so we don't worry if we fail to add them a second time.""" try: self.ldb.add(*args, **kwargs) except LdbError: pass def setUp(self): super(UserTests, self).setUp() self.state = GlobalState # the class itself, not an instance self.lp = lp self.ldb = SamDB(host, credentials=creds, session_info=system_session(lp), lp=lp) self.base_dn = self.ldb.domain_dn() self.ou = "OU=pid%s,%s" % (os.getpid(), self.base_dn) self.ou_users = "OU=users,%s" % self.ou self.ou_groups = "OU=groups,%s" % self.ou self.ou_computers = "OU=computers,%s" % self.ou self.state.test_number += 1 random.seed(self.state.test_number) def tearDown(self): super(UserTests, self).tearDown() def test_00_00_do_nothing(self): # this gives us an idea of the overhead pass def test_00_01_do_nothing_relevant(self): # takes around 1 second on i7-4770 j = 0 for i in range(30000000): j += i def test_00_02_do_nothing_sleepily(self): time.sleep(1) def test_00_03_add_ous_and_groups(self): # initialise the database for dn in (self.ou, self.ou_users, self.ou_groups, self.ou_computers): self.ldb.add({"dn": dn, "objectclass": "organizationalUnit"}) for i in range(N_GROUPS): self.ldb.add({ "dn": "cn=g%d,%s" % (i, self.ou_groups), "objectclass": "group" }) self.state.n_groups = N_GROUPS def _add_users(self, start, end): for i in range(start, end): self.ldb.add({ "dn": "cn=u%d,%s" % (i, self.ou_users), "objectclass": "user" }) def _add_users_ldif(self, start, end): lines = [] for i in range(start, end): lines.append("dn: cn=u%d,%s" % (i, self.ou_users)) lines.append("objectclass: user") lines.append("") self.ldb.add_ldif('\n'.join(lines)) def _test_join(self): tmpdir = tempfile.mkdtemp() if '://' in host: server = host.split('://', 1)[1] else: server = host cmd = cmd_sambatool.subcommands['domain'].subcommands['join'] result = cmd._run( "samba-tool domain join", creds.get_realm(), "dc", "-U%s%%%s" % (creds.get_username(), creds.get_password()), '--targetdir=%s' % tmpdir, '--server=%s' % server) shutil.rmtree(tmpdir) def _test_unindexed_search(self): expressions = [( '(&(objectclass=user)(description=' 'Built-in account for adminstering the computer/domain))' ), '(description=Built-in account for adminstering the computer/domain)', '(objectCategory=*)', '(samaccountname=Administrator*)'] for expression in expressions: t = time.time() for i in range(25): self.ldb.search(self.ou, expression=expression, scope=SCOPE_SUBTREE, attrs=['cn']) print >> sys.stderr, '%d %s took %s' % (i, expression, time.time() - t) def _test_indexed_search(self): expressions = ['(objectclass=group)', '(samaccountname=Administrator)'] for expression in expressions: t = time.time() for i in range(4000): self.ldb.search(self.ou, expression=expression, scope=SCOPE_SUBTREE, attrs=['cn']) print >> sys.stderr, '%d runs %s took %s' % (i, expression, time.time() - t) def _test_base_search(self): for dn in [ self.base_dn, self.ou, self.ou_users, self.ou_groups, self.ou_computers ]: for i in range(4000): try: self.ldb.search(dn, scope=SCOPE_BASE, attrs=['cn']) except LdbError as (num, msg): if num != 32: raise
class UserTests(samba.tests.TestCase): def add_if_possible(self, *args, **kwargs): """In these tests sometimes things are left in the database deliberately, so we don't worry if we fail to add them a second time.""" try: self.ldb.add(*args, **kwargs) except LdbError: pass def setUp(self): super(UserTests, self).setUp() self.state = GlobalState # the class itself, not an instance self.lp = lp self.ldb = SamDB(host, credentials=creds, session_info=system_session(lp), lp=lp) self.base_dn = self.ldb.domain_dn() self.ou = "OU=pid%s,%s" % (os.getpid(), self.base_dn) self.ou_users = "OU=users,%s" % self.ou self.ou_groups = "OU=groups,%s" % self.ou self.ou_computers = "OU=computers,%s" % self.ou for dn in (self.ou, self.ou_users, self.ou_groups, self.ou_computers): self.add_if_possible({ "dn": dn, "objectclass": "organizationalUnit"}) def tearDown(self): super(UserTests, self).tearDown() def test_00_00_do_nothing(self): # this gives us an idea of the overhead pass def _prepare_n_groups(self, n): self.state.n_groups = n for i in range(n): self.add_if_possible({ "dn": "cn=g%d,%s" % (i, self.ou_groups), "objectclass": "group"}) def _add_users(self, start, end): for i in range(start, end): self.ldb.add({ "dn": "cn=u%d,%s" % (i, self.ou_users), "objectclass": "user"}) def _add_users_ldif(self, start, end): lines = [] for i in range(start, end): lines.append("dn: cn=u%d,%s" % (i, self.ou_users)) lines.append("objectclass: user") lines.append("") self.ldb.add_ldif('\n'.join(lines)) def _test_unindexed_search(self): expressions = [ ('(&(objectclass=user)(description=' 'Built-in account for adminstering the computer/domain))'), '(description=Built-in account for adminstering the computer/domain)', '(objectCategory=*)', '(samaccountname=Administrator*)' ] for expression in expressions: t = time.time() for i in range(50): self.ldb.search(self.ou, expression=expression, scope=SCOPE_SUBTREE, attrs=['cn']) print >> sys.stderr, '%d %s took %s' % (i, expression, time.time() - t) def _test_indexed_search(self): expressions = ['(objectclass=group)', '(samaccountname=Administrator)' ] for expression in expressions: t = time.time() for i in range(10000): self.ldb.search(self.ou, expression=expression, scope=SCOPE_SUBTREE, attrs=['cn']) print >> sys.stderr, '%d runs %s took %s' % (i, expression, time.time() - t) def _test_complex_search(self): classes = ['samaccountname', 'objectCategory', 'dn', 'member'] values = ['*', '*t*', 'g*', 'user'] comparators = ['=', '<=', '>='] # '~=' causes error maybe_not = ['!(', ''] joiners = ['&', '|'] # The number of permuations is 18432, which is not huge but # would take hours to search. So we take a sample. all_permutations = list(itertools.product(joiners, classes, classes, values, values, comparators, comparators, maybe_not, maybe_not)) random.seed(1) for (j, c1, c2, v1, v2, o1, o2, n1, n2) in random.sample(all_permutations, 100): expression = ''.join(['(', j, '(', n1, c1, o1, v1, '))' if n1 else ')', '(', n2, c2, o2, v2, '))' if n2 else ')', ')']) print expression self.ldb.search(self.ou, expression=expression, scope=SCOPE_SUBTREE, attrs=['cn']) def _test_member_search(self, rounds=10): expressions = [] for d in range(50): expressions.append('(member=cn=u%d,%s)' % (d + 500, self.ou_users)) expressions.append('(member=u%d*)' % (d + 700,)) for i in range(N_GROUPS): expressions.append('(memberOf=cn=g%d,%s)' % (i, self.ou_groups)) expressions.append('(memberOf=cn=g%d*)' % (i,)) expressions.append('(memberOf=cn=*%s*)' % self.ou_groups) for expression in expressions: t = time.time() for i in range(rounds): self.ldb.search(self.ou, expression=expression, scope=SCOPE_SUBTREE, attrs=['cn']) print >> sys.stderr, '%d runs %s took %s' % (i, expression, time.time() - t) def _test_add_many_users(self, n=BATCH_SIZE): s = self.state.next_user_id e = s + n self._add_users(s, e) self.state.next_user_id = e def _test_add_many_users_ldif(self, n=BATCH_SIZE): s = self.state.next_user_id e = s + n self._add_users_ldif(s, e) self.state.next_user_id = e def _link_user_and_group(self, u, g): m = Message() m.dn = Dn(self.ldb, "CN=g%d,%s" % (g, self.ou_groups)) m["member"] = MessageElement("cn=u%d,%s" % (u, self.ou_users), FLAG_MOD_ADD, "member") self.ldb.modify(m) def _test_link_many_users(self, n=BATCH_SIZE): self._prepare_n_groups(N_GROUPS) s = self.state.next_linked_user e = s + n for i in range(s, e): # put everyone in group 0, and one other group g = i % (N_GROUPS - 1) + 1 self._link_user_and_group(i, g) self._link_user_and_group(i, 0) self.state.next_linked_user = e test_00_01_adding_users_1000 = _test_add_many_users test_00_10_complex_search_1k_users = _test_complex_search test_00_11_unindexed_search_1k_users = _test_unindexed_search test_00_12_indexed_search_1k_users = _test_indexed_search test_00_13_member_search_1k_users = _test_member_search test_01_02_adding_users_2000_ldif = _test_add_many_users_ldif test_01_03_adding_users_3000 = _test_add_many_users test_01_10_complex_search_3k_users = _test_complex_search test_01_11_unindexed_search_3k_users = _test_unindexed_search test_01_12_indexed_search_3k_users = _test_indexed_search def test_01_13_member_search_3k_users(self): self._test_member_search(rounds=5) test_02_01_link_users_1000 = _test_link_many_users test_02_02_link_users_2000 = _test_link_many_users test_02_03_link_users_3000 = _test_link_many_users test_03_10_complex_search_linked_users = _test_complex_search test_03_11_unindexed_search_linked_users = _test_unindexed_search test_03_12_indexed_search_linked_users = _test_indexed_search def test_03_13_member_search_linked_users(self): self._test_member_search(rounds=2)
objectClass: nTDSSiteSettings cn: NTDS Site Settings showInAdvancedViewOnly: TRUE name: NTDS Site Settings objectCategory: CN=NTDS-Site-Settings,CN=Schema,CN=Configuration,%(samba4_ldap_base)s dn: CN=Servers,CN=%(branchsite_name)s,CN=Sites,CN=Configuration,%(samba4_ldap_base)s objectClass: serversContainer cn: Servers showInAdvancedViewOnly: TRUE name: Servers systemFlags: 33554432 objectCategory: CN=Servers-Container,CN=Schema,CN=Configuration,%(samba4_ldap_base)s ''' % ldif_dict samdb.add_ldif(site_add_ldif) print "created site %s" % opts.site if opts.sitelink and not opts.createsitelink: ## and add it to the sitelink sitelink_modify_ldif=''' dn: CN=%(sitelink)s,CN=IP,CN=Inter-Site Transports,CN=Sites,CN=Configuration,%(samba4_ldap_base)s changetype: modify add: siteList siteList: CN=%(branchsite_name)s,CN=Sites,CN=Configuration,%(samba4_ldap_base)s ''' % ldif_dict samdb.modify_ldif(sitelink_modify_ldif) print "added site %s to sitelink %s" % (opts.site, opts.sitelink) elif opts.site: res = samdb.search("CN=Configuration,%s" % samba4_ldap_base, scope=ldb.SCOPE_SUBTREE, expression="(&(objectClass=site)(cn=%s))" % opts.site)