The branch, master has been updated via 1b68910 s4:ldap.py - add a test for attribute ranges - still very basic via dd25f4b s4:sam.py - enhance users and groups test via 802e3b4 s4:samldb LDB module - enhance the "member"-check trigger via a839422 s4:ldap.py - enhance and activate the "description" attribute test via 4f25eec s4:objectclass_attrs.c - rework to support these special "description" constraints from c251cb6 wafsamba: Fix soname for standalone private libraries.
http://gitweb.samba.org/?p=samba.git;a=shortlog;h=master - Log ----------------------------------------------------------------- commit 1b68910d65eb7becf968fbe5a3c16104ccf19906 Author: Matthias Dieter Wallnöfer <m...@samba.org> Date: Tue Oct 26 19:09:44 2010 +0200 s4:ldap.py - add a test for attribute ranges - still very basic And partially outcommented. Autobuild-User: Matthias Dieter Wallnöfer <m...@samba.org> Autobuild-Date: Tue Oct 26 18:53:12 UTC 2010 on sn-devel-104 commit dd25f4b83b0ccc24512ae9ba7636a587ec43c9ec Author: Matthias Dieter Wallnöfer <m...@samba.org> Date: Tue Oct 26 12:37:21 2010 +0200 s4:sam.py - enhance users and groups test commit 802e3b4e1f2e0fdc7fc11ed7881dade261bfa34a Author: Matthias Dieter Wallnöfer <m...@samba.org> Date: Tue Oct 26 12:08:02 2010 +0200 s4:samldb LDB module - enhance the "member"-check trigger - Also multi-valued "member" attributes are allowed - When you try to delete a member from a group which has it primary group set exactly to this group you get "UNWILLING_TO_PERFORM" commit a839422fc57762ea3366afbe58b018cf11bfffd7 Author: Matthias Dieter Wallnöfer <m...@samba.org> Date: Sun Oct 24 21:25:52 2010 +0200 s4:ldap.py - enhance and activate the "description" attribute test It tests only the "description" attribute in particular since it behaves differently from all others. commit 4f25eec5a5d9e57f0a63591535fe3f581ce23f45 Author: Matthias Dieter Wallnöfer <m...@samba.org> Date: Mon Oct 25 13:56:14 2010 +0200 s4:objectclass_attrs.c - rework to support these special "description" constraints Only the "description" attribute has this special restrictions. ----------------------------------------------------------------------- Summary of changes: source4/dsdb/samdb/ldb_modules/objectclass_attrs.c | 50 +++++-- source4/dsdb/samdb/ldb_modules/samldb.c | 10 +- source4/dsdb/tests/python/ldap.py | 169 +++++++++++++++++--- source4/dsdb/tests/python/sam.py | 65 ++++++++- 4 files changed, 253 insertions(+), 41 deletions(-) Changeset truncated at 500 lines: diff --git a/source4/dsdb/samdb/ldb_modules/objectclass_attrs.c b/source4/dsdb/samdb/ldb_modules/objectclass_attrs.c index b3f7048..5d3f51f 100644 --- a/source4/dsdb/samdb/ldb_modules/objectclass_attrs.c +++ b/source4/dsdb/samdb/ldb_modules/objectclass_attrs.c @@ -139,17 +139,47 @@ static int attr_handler(struct oc_context *ac) } } - /* Multi-valued replace operations are generally denied but - * there do exist exceptions where attributes have the flag - * "FLAG_ATTR_REQ_PARTIAL_SET_MEMBER" set. */ + /* "description" on AD is very special: it's nearly single- + * valued (only on add operations it isn't). */ if ((ac->req->operation == LDB_MODIFY) && - (LDB_FLAG_MOD_TYPE(msg->elements[i].flags) == LDB_FLAG_MOD_REPLACE) && - (msg->elements[i].num_values > 1) && - ((attr->systemFlags & DS_FLAG_ATTR_REQ_PARTIAL_SET_MEMBER) == 0)) { - ldb_asprintf_errstring(ldb, "objectclass_attrs: attribute '%s' on entry '%s' is replaced multi-valued!", - msg->elements[i].name, - ldb_dn_get_linearized(msg->dn)); - return LDB_ERR_ATTRIBUTE_OR_VALUE_EXISTS; + (ldb_attr_cmp(attr->lDAPDisplayName, "description") == 0)) { + /* Multi-valued add or replace operations are always + * denied */ + if ((LDB_FLAG_MOD_TYPE(msg->elements[i].flags) + != LDB_FLAG_MOD_DELETE) && + (msg->elements[i].num_values > 1)) { + ldb_asprintf_errstring(ldb, + "objectclass_attrs: attribute '%s' on entry '%s' is changed using a multi-valued add or replace operation!", + msg->elements[i].name, + ldb_dn_get_linearized(msg->dn)); + return LDB_ERR_ATTRIBUTE_OR_VALUE_EXISTS; + } + + /* Add operations are only allowed if no value exists */ + if (LDB_FLAG_MOD_TYPE(msg->elements[i].flags) + == LDB_FLAG_MOD_ADD) { + const char *attrs[] = { attr->lDAPDisplayName, + NULL }; + struct ldb_result *res; + struct ldb_message_element *el; + + ret = ldb_search(ldb, ac, &res, msg->dn, + LDB_SCOPE_BASE, attrs, NULL); + if (ret != LDB_SUCCESS) { + return ret; + } + + el = ldb_msg_find_element(res->msgs[0], + attr->lDAPDisplayName); + if (el != NULL) { + ldb_asprintf_errstring(ldb, + "objectclass_attrs: attribute '%s' on entry '%s' is changed using an add operation, but there a value already exists!", + msg->elements[i].name, + ldb_dn_get_linearized(msg->dn)); + return LDB_ERR_ATTRIBUTE_OR_VALUE_EXISTS; + } + talloc_free(res); + } } /* Substitute the attribute name to match in case */ diff --git a/source4/dsdb/samdb/ldb_modules/samldb.c b/source4/dsdb/samdb/ldb_modules/samldb.c index 26022b7..924c05e 100644 --- a/source4/dsdb/samdb/ldb_modules/samldb.c +++ b/source4/dsdb/samdb/ldb_modules/samldb.c @@ -1158,7 +1158,12 @@ static int samldb_member_check(struct samldb_ctx *ac) } if (ldb_dn_compare(group_dn, ac->msg->dn) == 0) { - return LDB_ERR_ENTRY_ALREADY_EXISTS; + if (LDB_FLAG_MOD_TYPE(el->flags) + == LDB_FLAG_MOD_DELETE) { + return LDB_ERR_UNWILLING_TO_PERFORM; + } else { + return LDB_ERR_ENTRY_ALREADY_EXISTS; + } } } @@ -1463,8 +1468,7 @@ static int samldb_modify(struct ldb_module *module, struct ldb_request *req) } el = ldb_msg_find_element(ac->msg, "member"); - if (el && el->flags & (LDB_FLAG_MOD_ADD|LDB_FLAG_MOD_REPLACE) - && el->num_values == 1) { + if (el != NULL) { ret = samldb_member_check(ac); if (ret != LDB_SUCCESS) { return ret; diff --git a/source4/dsdb/tests/python/ldap.py b/source4/dsdb/tests/python/ldap.py index a7e718e..d713056 100755 --- a/source4/dsdb/tests/python/ldap.py +++ b/source4/dsdb/tests/python/ldap.py @@ -20,7 +20,7 @@ from ldb import SCOPE_SUBTREE, SCOPE_ONELEVEL, SCOPE_BASE, LdbError from ldb import ERR_NO_SUCH_OBJECT, ERR_ATTRIBUTE_OR_VALUE_EXISTS from ldb import ERR_ENTRY_ALREADY_EXISTS, ERR_UNWILLING_TO_PERFORM from ldb import ERR_NOT_ALLOWED_ON_NON_LEAF, ERR_OTHER, ERR_INVALID_DN_SYNTAX -from ldb import ERR_NO_SUCH_ATTRIBUTE +from ldb import ERR_NO_SUCH_ATTRIBUTE, ERR_INVALID_ATTRIBUTE_SYNTAX from ldb import ERR_OBJECT_CLASS_VIOLATION, ERR_NOT_ALLOWED_ON_RDN from ldb import ERR_NAMING_VIOLATION, ERR_CONSTRAINT_VIOLATION from ldb import ERR_UNDEFINED_ATTRIBUTE_TYPE @@ -591,14 +591,9 @@ class BasicTests(unittest.TestCase): self.delete_force(self.ldb, "cn=ldaptestgroup,cn=users," + self.base_dn) - def test_multi_valued_attributes(self): - """Test multi-valued attributes""" - print "Test multi-valued attributes""" - -# TODO: In this test I added some special tests where I got very unusual -# results back from a real AD. s4 doesn't match them and I've no idea how to -# implement those error cases (maybe there exists a special trigger for -# "description" attributes which handle them) + def test_description_attribute(self): + """Test description attribute""" + print "Test description attribute""" self.ldb.add({ "dn": "cn=ldaptestgroup,cn=users," + self.base_dn, @@ -606,6 +601,13 @@ class BasicTests(unittest.TestCase): "objectclass": "group", "description": "desc1"}) + res = ldb.search("cn=ldaptestgroup,cn=users," + self.base_dn, + scope=SCOPE_BASE, attrs=["description"]) + self.assertTrue(len(res) == 1) + self.assertTrue("description" in res[0]) + self.assertTrue(len(res[0]["description"]) == 1) + self.assertEquals(res[0]["description"][0], "desc1") + self.delete_force(self.ldb, "cn=ldaptestgroup,cn=users," + self.base_dn) self.ldb.add({ @@ -613,6 +615,16 @@ class BasicTests(unittest.TestCase): "objectclass": "group", "description": ["desc1", "desc2"]}) + res = ldb.search("cn=ldaptestgroup,cn=users," + self.base_dn, + scope=SCOPE_BASE, attrs=["description"]) + self.assertTrue(len(res) == 1) + self.assertTrue("description" in res[0]) + self.assertTrue(len(res[0]["description"]) == 2) + self.assertTrue(res[0]["description"][0] == "desc1" or + res[0]["description"][1] == "desc1") + self.assertTrue(res[0]["description"][0] == "desc2" or + res[0]["description"][1] == "desc2") + m = Message() m.dn = Dn(ldb, "cn=ldaptestgroup,cn=users," + self.base_dn) m["description"] = MessageElement(["desc1","desc2"], FLAG_MOD_REPLACE, @@ -625,19 +637,58 @@ class BasicTests(unittest.TestCase): m = Message() m.dn = Dn(ldb, "cn=ldaptestgroup,cn=users," + self.base_dn) + m["description"] = MessageElement(["desc1","desc2"], FLAG_MOD_DELETE, + "description") + ldb.modify(m) + + self.delete_force(self.ldb, "cn=ldaptestgroup,cn=users," + self.base_dn) + + self.ldb.add({ + "dn": "cn=ldaptestgroup,cn=users," + self.base_dn, + "objectclass": "group" }) + + m = Message() + m.dn = Dn(ldb, "cn=ldaptestgroup,cn=users," + self.base_dn) m["description"] = MessageElement("desc1", FLAG_MOD_REPLACE, "description") ldb.modify(m) -# m = Message() -# m.dn = Dn(ldb, "cn=ldaptestgroup,cn=users," + self.base_dn) -# m["description"] = MessageElement("desc3", FLAG_MOD_ADD, -# "description") -# try: -# ldb.modify(m) -# self.fail() -# except LdbError, (num, _): -# self.assertEquals(num, ERR_ATTRIBUTE_OR_VALUE_EXISTS) + res = ldb.search("cn=ldaptestgroup,cn=users," + self.base_dn, + scope=SCOPE_BASE, attrs=["description"]) + self.assertTrue(len(res) == 1) + self.assertTrue("description" in res[0]) + self.assertTrue(len(res[0]["description"]) == 1) + self.assertEquals(res[0]["description"][0], "desc1") + + self.delete_force(self.ldb, "cn=ldaptestgroup,cn=users," + self.base_dn) + + self.ldb.add({ + "dn": "cn=ldaptestgroup,cn=users," + self.base_dn, + "objectclass": "group", + "description": ["desc1", "desc2"]}) + + m = Message() + m.dn = Dn(ldb, "cn=ldaptestgroup,cn=users," + self.base_dn) + m["description"] = MessageElement("desc1", FLAG_MOD_REPLACE, + "description") + ldb.modify(m) + + res = ldb.search("cn=ldaptestgroup,cn=users," + self.base_dn, + scope=SCOPE_BASE, attrs=["description"]) + self.assertTrue(len(res) == 1) + self.assertTrue("description" in res[0]) + self.assertTrue(len(res[0]["description"]) == 1) + self.assertEquals(res[0]["description"][0], "desc1") + + m = Message() + m.dn = Dn(ldb, "cn=ldaptestgroup,cn=users," + self.base_dn) + m["description"] = MessageElement("desc3", FLAG_MOD_ADD, + "description") + try: + ldb.modify(m) + self.fail() + except LdbError, (num, _): + self.assertEquals(num, ERR_ATTRIBUTE_OR_VALUE_EXISTS) m = Message() m.dn = Dn(ldb, "cn=ldaptestgroup,cn=users," + self.base_dn) @@ -654,6 +705,10 @@ class BasicTests(unittest.TestCase): m["description"] = MessageElement("desc1", FLAG_MOD_DELETE, "description") ldb.modify(m) + res = ldb.search("cn=ldaptestgroup,cn=users," + self.base_dn, + scope=SCOPE_BASE, attrs=["description"]) + self.assertTrue(len(res) == 1) + self.assertFalse("description" in res[0]) m = Message() m.dn = Dn(ldb, "cn=ldaptestgroup,cn=users," + self.base_dn) @@ -665,23 +720,85 @@ class BasicTests(unittest.TestCase): except LdbError, (num, _): self.assertEquals(num, ERR_ATTRIBUTE_OR_VALUE_EXISTS) + m = Message() + m.dn = Dn(ldb, "cn=ldaptestgroup,cn=users," + self.base_dn) + m["description"] = MessageElement(["desc3", "desc4"], FLAG_MOD_ADD, + "description") + try: + ldb.modify(m) + self.fail() + except LdbError, (num, _): + self.assertEquals(num, ERR_ATTRIBUTE_OR_VALUE_EXISTS) + + m = Message() + m.dn = Dn(ldb, "cn=ldaptestgroup,cn=users," + self.base_dn) + m["description"] = MessageElement("desc1", FLAG_MOD_ADD, + "description") + ldb.modify(m) + + res = ldb.search("cn=ldaptestgroup,cn=users," + self.base_dn, + scope=SCOPE_BASE, attrs=["description"]) + self.assertTrue(len(res) == 1) + self.assertTrue("description" in res[0]) + self.assertTrue(len(res[0]["description"]) == 1) + self.assertEquals(res[0]["description"][0], "desc1") + + self.delete_force(self.ldb, "cn=ldaptestgroup,cn=users," + self.base_dn) + + def test_attribute_ranges(self): + """Test attribute ranges""" + print "Test attribute ranges""" + + # Too short (min. 1) + try: + ldb.add({ + "dn": "cn=ldaptestuser,cn=users," + self.base_dn, + "objectClass": "person", + "sn": "" }) + self.fail() + except LdbError, (num, _): + self.assertEquals(num, ERR_INVALID_ATTRIBUTE_SYNTAX) + + # Too long (max. 64) +# try: +# ldb.add({ +# "dn": "cn=ldaptestuser,cn=users," + self.base_dn, +# "objectClass": "person", +# "sn": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }) +# self.fail() +# except LdbError, (num, _): +# self.assertEquals(num, ERR_CONSTRAINT_VIOLATION) + + ldb.add({ + "dn": "cn=ldaptestuser,cn=users," + self.base_dn, + "objectClass": "person" }) + + # Too short (min. 1) + m = Message() + m.dn = Dn(ldb, "cn=ldaptestuser,cn=users," + self.base_dn) + m["sn"] = MessageElement("", FLAG_MOD_REPLACE, "sn") + try: + ldb.modify(m) + self.fail() + except LdbError, (num, _): + self.assertEquals(num, ERR_INVALID_ATTRIBUTE_SYNTAX) + + # Too long (max. 64) # m = Message() -# m.dn = Dn(ldb, "cn=ldaptestgroup,cn=users," + self.base_dn) -# m["description"] = MessageElement(["desc3", "desc4"], FLAG_MOD_ADD, -# "description") +# m.dn = Dn(ldb, "cn=ldaptestuser,cn=users," + self.base_dn) +# m["sn"] = MessageElement("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", FLAG_MOD_REPLACE, "sn") # try: # ldb.modify(m) # self.fail() # except LdbError, (num, _): -# self.assertEquals(num, ERR_ATTRIBUTE_OR_VALUE_EXISTS) +# self.assertEquals(num, ERR_CONSTRAINT_VIOLATION) m = Message() - m.dn = Dn(ldb, "cn=ldaptestgroup,cn=users," + self.base_dn) - m["description"] = MessageElement("desc3", FLAG_MOD_ADD, - "description") + m.dn = Dn(ldb, "cn=ldaptestuser,cn=users," + self.base_dn) + m["sn"] = MessageElement("x", FLAG_MOD_REPLACE, "sn") ldb.modify(m) - self.delete_force(self.ldb, "cn=ldaptestgroup,cn=users," + self.base_dn) + self.delete_force(self.ldb, "cn=ldaptestuser,cn=users," + self.base_dn) def test_empty_messages(self): """Test empty messages""" diff --git a/source4/dsdb/tests/python/sam.py b/source4/dsdb/tests/python/sam.py index 1970673..43e57f9 100755 --- a/source4/dsdb/tests/python/sam.py +++ b/source4/dsdb/tests/python/sam.py @@ -96,9 +96,10 @@ class SamTests(unittest.TestCase): print "baseDN: %s\n" % self.base_dn self.delete_force(self.ldb, "cn=ldaptestuser,cn=users," + self.base_dn) + self.delete_force(self.ldb, "cn=ldaptestuser2,cn=users," + self.base_dn) self.delete_force(self.ldb, "cn=ldaptestcomputer,cn=users," + self.base_dn) self.delete_force(self.ldb, "cn=ldaptestgroup,cn=users," + self.base_dn) - self.delete_force(self.ldb, "cn=ldaptestuser2,cn=users," + self.base_dn) + self.delete_force(self.ldb, "cn=ldaptestgroup2,cn=users," + self.base_dn) def test_users_groups(self): """This tests the SAM users and groups behaviour""" @@ -415,17 +416,77 @@ class SamTests(unittest.TestCase): self.assertTrue(len(res1) == 1) self.assertFalse("member" in res1[0]) + # Primary group member + m = Message() + m.dn = Dn(ldb, "cn=ldaptestgroup2,cn=users," + self.base_dn) + m["member"] = MessageElement("cn=ldaptestuser,cn=users," + self.base_dn, + FLAG_MOD_DELETE, "member") + try: + ldb.modify(m) + self.fail() + except LdbError, (num, _): + self.assertEquals(num, ERR_UNWILLING_TO_PERFORM) + # Also this should be denied try: ldb.add({ - "dn": "cn=ldaptestuser1,cn=users," + self.base_dn, + "dn": "cn=ldaptestuser2,cn=users," + self.base_dn, "objectclass": ["user", "person"], "primaryGroupID": "0"}) self.fail() except LdbError, (num, _): self.assertEquals(num, ERR_UNWILLING_TO_PERFORM) + # Recreate user accounts + + self.delete_force(self.ldb, "cn=ldaptestuser,cn=users," + self.base_dn) + + ldb.add({ + "dn": "cn=ldaptestuser,cn=users," + self.base_dn, + "objectclass": ["user", "person"]}) + + ldb.add({ + "dn": "cn=ldaptestuser2,cn=users," + self.base_dn, + "objectclass": ["user", "person"]}) + + m = Message() + m.dn = Dn(ldb, "cn=ldaptestgroup2,cn=users," + self.base_dn) + m["member"] = MessageElement("cn=ldaptestuser,cn=users," + self.base_dn, + FLAG_MOD_ADD, "member") + ldb.modify(m) + + # Invalid member + m = Message() + m.dn = Dn(ldb, "cn=ldaptestgroup2,cn=users," + self.base_dn) + m["member"] = MessageElement("cn=ldaptestuser1,cn=users," + self.base_dn, + FLAG_MOD_REPLACE, "member") + try: + ldb.modify(m) + self.fail() + except LdbError, (num, _): + self.assertEquals(num, ERR_NO_SUCH_OBJECT) + + # Invalid member + m = Message() + m.dn = Dn(ldb, "cn=ldaptestgroup2,cn=users," + self.base_dn) + m["member"] = MessageElement(["cn=ldaptestuser,cn=users," + self.base_dn, + "cn=ldaptestuser1,cn=users," + self.base_dn], + FLAG_MOD_REPLACE, "member") + try: + ldb.modify(m) + self.fail() + except LdbError, (num, _): + self.assertEquals(num, ERR_NO_SUCH_OBJECT) + + m = Message() + m.dn = Dn(ldb, "cn=ldaptestgroup2,cn=users," + self.base_dn) + m["member"] = MessageElement(["cn=ldaptestuser,cn=users," + self.base_dn, + "cn=ldaptestuser2,cn=users," + self.base_dn], + FLAG_MOD_REPLACE, "member") + ldb.modify(m) + self.delete_force(self.ldb, "cn=ldaptestuser,cn=users," + self.base_dn) + self.delete_force(self.ldb, "cn=ldaptestuser2,cn=users," + self.base_dn) self.delete_force(self.ldb, "cn=ldaptestgroup,cn=users," + self.base_dn) self.delete_force(self.ldb, "cn=ldaptestgroup2,cn=users," + self.base_dn) -- Samba Shared Repository