diff --git a/directory/localtest/slapd.conf.acls b/directory/localtest/slapd.conf.acls index 1fe88660..f405a909 100644 --- a/directory/localtest/slapd.conf.acls +++ b/directory/localtest/slapd.conf.acls @@ -6,6 +6,41 @@ # andrew.findlay@skills-1st.co.uk # 20 June 2011 +# Enforce ACL rules on the content of entries that are about to be added + +add_content_acl on + +# DIT Content Rule for inetOrgPerson (2.16.840.1.113730.3.2.2) +# +# Prevent unwanted objectclasses on user entries +# +# This is a belt-and-braces protection as normally only the registration agent +# and LDAP admins can change the objectClass attribute +# +# We also make both uid and uniqueIdentifier mandatory: +# uid is the username, so it is essential +# uniqueIdentifier is the naming attribute +# +# Note that because this is part of the schema, even the rootDN cannot break the rules + +ditcontentrule ( 2.16.840.1.113730.3.2.2 + NAME 'dcrInetOrgPerson' + DESC 'Control content of inetOrgPerson entries' + AUX ( mozilliansObject $ mozilliansPerson ) + MUST ( uid $ uniqueIdentifier ) + ) + +# We define some attribute sets as objectclasses for convenience. +# Note that these are NOT intended to be used as objectclasses of actual entries. +# The base OID for these is 1.3.6.1.4.1.13769.3000.3 + +objectclass ( 1.3.6.1.4.1.13769.3000.3.1 + NAME 'attrsetSelfModifiable' + DESC 'Attributes that a user can modify in their own entry' + AUXILIARY + MAY ( cn $ sn $ displayname $ mail $ uid $ telephoneNumber $ jpegPhoto $ description ) + ) + # Permit everyone to read the service entries access to dn.exact="" @@ -29,26 +64,54 @@ access to attrs="userPassword" by self =w by * auth -# Anon may read and search just enough to find a DN given a uid +# We allow all authenticated users to view and modify the basic +# informational attributes in their entry +access to attrs="@attrsetSelfModifiable" + by self write + by * break + +# Massive simplification for now: +# treat all authenticated users as Mozillians +# even if they have not been vouched for + +# User may read their own voucher but not modify it +access to attrs="mozilliansVouchedBy" + by self read + by * break + +# Mozillians may add their own DN to another user's voucher attribute +access to attrs="mozilliansVouchedBy" val.regex="^(.*)$" + by dn.exact,expand="${v1}" set="user/mozilliansVouchedBy" add + by * break + +# Mozillians may read/search everyone's voucher attribute +access to attrs="mozilliansVouchedBy" + by set="user/mozilliansVouchedBy" read + by * break + +# Mozillians may read/search everything that is not restricted above +access to * + by set="user/mozilliansVouchedBy" read + by * break + +# Anon may read and search just enough to find a DN given a uid +# non-Mozillian users can do this too access to attrs="entry" by anonymous read + by users read by * break access to attrs="uid" by anonymous search + by users search by * break access to attrs="uniqueIdentifier" by anonymous read + by users search by * break -# Massive simplification for now: -# all authenticated users can see everything else - -access to * - by users read - # Default deny access to * diff --git a/directory/mozillians-data.ldif b/directory/mozillians-data.ldif index a079c914..22cb0719 100644 --- a/directory/mozillians-data.ldif +++ b/directory/mozillians-data.ldif @@ -1450,6 +1450,18 @@ mozilliansVouchedBy: uniqueIdentifier=1,ou=people,dc=mozillians,dc=org sn: Wadensjöö cn: Åke Wadensjöö displayName: Åke Wadensjöö -userPassword: {SSHA}6v9NTMhqMFpUORpqpCynb5E05eme3UrJ +userPassword: secret description: Ne révèle pas le dénouement SVP. $ Se upp för nationella tecken $ Uważaj na znaki narodowe $ احترس من الشخصيات الوطنية uid: Åke + +dn: uniqueIdentifier=7,ou=people,dc=mozillians,dc=org +objectClass: inetOrgPerson +objectClass: mozilliansPerson +uniqueIdentifier: 7 +sn: Applicant +cn: An Applicant +displayName: An Applicant +userPassword: secret +description: An Applicant +uid: 7 + diff --git a/directory/mozillians.schema b/directory/mozillians.schema index ed6defe9..7d4539c7 100644 --- a/directory/mozillians.schema +++ b/directory/mozillians.schema @@ -9,6 +9,7 @@ # # Object classes under: 1.3.6.1.4.1.13769.3000.1 # Attribute types under: 1.3.6.1.4.1.13769.3000.2 +# Attribute sets under: 1.3.6.1.4.1.13769.3000.3 ######################################################################## # Attribute types diff --git a/directory/testsuite/test-ldap-acls.py b/directory/testsuite/test-ldap-acls.py index f0c29ae4..62bfa56d 100644 --- a/directory/testsuite/test-ldap-acls.py +++ b/directory/testsuite/test-ldap-acls.py @@ -224,6 +224,26 @@ class LdapAclTest(unittest.TestCase): ldap_check.unbind() + # Changing own user attributes + def change_user_attributes(self, user, userDN, ldap_conn): + try: + ldap_conn.modify_s( + userDN, + [ + (ldap.MOD_REPLACE,'cn','modified CN'), + (ldap.MOD_REPLACE,'sn','modified SN'), + (ldap.MOD_REPLACE,'displayName','modified displayName'), + (ldap.MOD_REPLACE,'mail',['new@mail.one','new@mail.two']), + (ldap.MOD_REPLACE,'uid','modified UID'), + (ldap.MOD_REPLACE,'telephoneNumber',['+1 234','+5-678-90']), + (ldap.MOD_REPLACE,'description','modified description'), + (ldap.MOD_REPLACE,'jpegPhoto','modified jpegPhoto'), + ] + ) + except ldap.LDAPError: + self.fail( user + " cannot modify their own user atttributes " + str(sys.exc_info()[0]) ) + + ####################################################################################### # Actual tests start here @@ -284,7 +304,7 @@ class LdapAclTest(unittest.TestCase): except ldap.LDAPError: self.fail( "Anon cannot search under "+people_node+" " + str(sys.exc_info()[0]) ) - def test_T0020_applicant_search_person(self): + def test_T6040_applicant_search_person(self): # Applicant trying to find a person entry that is not their own # This should work, but not expose any data apart from the DN try: @@ -305,19 +325,23 @@ class LdapAclTest(unittest.TestCase): self.fail( "Applicant should not be able to read attributes from user entries. Got: " + str(getAttrNames(res[0])) ) - def test_T0030_applicant_search_multi(self): - # Applicant trying to find multiple entries - # The filter matches 3 in this case - # This should limit at 2 entries returned - try: - with self.assertRaises(ldap.SIZELIMIT_EXCEEDED): - res = self.ldap_applicant001.search_s( - people_node, - ldap.SCOPE_SUBTREE, - filterstr='(uid=test00*)' ) - - except ldap.LDAPError: - self.fail( "Applicant cannot search under "+people_node+" " + str(sys.exc_info()[0]) ) +# It is not practical to enforce different limits on Applicants and Mozillians +# with the current implementation because the limits statement in OpenLDAP does not accept +# set specifications +# +# def test_T6050_applicant_search_multi(self): +# # Applicant trying to find multiple entries +# # The filter matches 3 in this case +# # This should limit at 2 entries returned +# try: +# with self.assertRaises(ldap.SIZELIMIT_EXCEEDED): +# res = self.ldap_applicant001.search_s( +# people_node, +# ldap.SCOPE_SUBTREE, +# filterstr='(uid=test00*)' ) +# +# except ldap.LDAPError: +# self.fail( "Applicant cannot search under "+people_node+" " + str(sys.exc_info()[0]) ) def test_T6030_mozillian_search_person(self): # Mozillian trying to find a person entry @@ -394,6 +418,117 @@ class LdapAclTest(unittest.TestCase): with self.assertRaises(ldap.INSUFFICIENT_ACCESS): self.ldap_applicant001.passwd_s(ldap_mozillian012DN, None, 'owned!') + def test_T6010_applicant_change_user_attributes(self): + self.change_user_attributes( + 'Applicant', + ldap_applicant001DN, + self.ldap_applicant001 ) + + def test_T6010_mozillian_change_user_attributes(self): + self.change_user_attributes( + 'Mozillian', + ldap_mozillian011DN, + self.ldap_mozillian011 ) + + def test_T6010_mozillian_delete_uid(self): + # Users should not be able to delete uid as then it will be + # impossible for them to log in again + # The error here is OBJECT_CLASS_VIOLATION because this is enforced + # by a DIT content rule rather than an ACL + with self.assertRaises(ldap.OBJECT_CLASS_VIOLATION): + self.ldap_mozillian011.modify_s( + ldap_mozillian011DN, + [ + (ldap.MOD_DELETE,'uid',None), + ] + ) + + + def test_T6020_mozillian_read_obscure_attrs(self): + # Mozillian reading more obscure attributes in their own entry + try: + res = self.ldap_mozillian011.search_s( + ldap_mozillian011DN, + ldap.SCOPE_BASE, + filterstr='(objectclass=*)', + attrlist=['mozilliansVouchedBy','modifiersName','modifyTimestamp','userPassword'] ) + except ldap.LDAPError: + self.fail( "Mozillian cannot search own entry " + str(sys.exc_info()[0]) ) + + if not getAttrValue(res[0],'modifiersName'): + self.fail( "Mozillian should see their own modifiersName value" ) + if not getAttrValue(res[0],'modifyTimestamp'): + self.fail( "Mozillian should see their own modifyTimestamp value" ) + if not getAttrValue(res[0],'mozilliansVouchedBy'): + self.fail( "Mozillian should see their own mozilliansVouchedBy value" ) + # Should NOT see own password + if getAttrValue(res[0],'userPassword'): + self.fail( "Mozillian should not see their own userPassword value" ) + + + def test_T5010_mozillian_vouch_for_applicant(self): + try: + self.ldap_mozillian011.modify_s( + ldap_applicant001DN, + [ (ldap.MOD_ADD,'mozilliansVouchedBy',ldap_mozillian011DN) ] + ) + except ldap.LDAPError: + self.fail( "Mozillian cannot vouch for applicant " + str(sys.exc_info()[0]) ) + + def test_T5010_mozillian_fake_vouch_for_applicant(self): + # Mozillian should not be able to put someone else's DN into + # an applicant's mozilliansVouchedBy attribute + with self.assertRaises(ldap.INSUFFICIENT_ACCESS): + self.ldap_mozillian011.modify_s( + ldap_applicant001DN, + [ (ldap.MOD_ADD,'mozilliansVouchedBy',ldap_applicant001DN) ] + ) + + def test_T5010_mozillian_unvouch_applicant(self): + # Mozillian should not be able to remove any value from + # an applicant's mozilliansVouchedBy attribute + with self.assertRaises(ldap.INSUFFICIENT_ACCESS): + self.ldap_mozillian011.modify_s( + ldap_applicant001DN, + [ (ldap.MOD_DELETE,'mozilliansVouchedBy',None) ] + ) + + def test_T5020_mozillian_fake_vouch_for_self(self): + # Mozillian should not be able to modify + # their own mozilliansVouchedBy attribute + with self.assertRaises(ldap.INSUFFICIENT_ACCESS): + self.ldap_mozillian011.modify_s( + ldap_mozillian011DN, + [ (ldap.MOD_ADD,'mozilliansVouchedBy',ldap_applicant001DN) ] + ) + + def test_T5020_applicant_fake_vouch_for_self(self): + # Applicant should not be able to modify + # their own mozilliansVouchedBy attribute + with self.assertRaises(ldap.INSUFFICIENT_ACCESS): + self.ldap_applicant001.modify_s( + ldap_applicant001DN, + [ (ldap.MOD_ADD,'mozilliansVouchedBy',ldap_applicant001DN) ] + ) + + def test_T5020_mozillian_fake_unvouch_self(self): + # Mozillian should not be able to modify + # their own mozilliansVouchedBy attribute + with self.assertRaises(ldap.INSUFFICIENT_ACCESS): + self.ldap_mozillian011.modify_s( + ldap_mozillian011DN, + [ (ldap.MOD_DELETE,'mozilliansVouchedBy',None) ] + ) + + def test_T5030_applicant_fake_vouch_for_another(self): + # Applicant should not be able to modify + # another applicant's mozilliansVouchedBy attribute + with self.assertRaises(ldap.INSUFFICIENT_ACCESS): + self.ldap_applicant001.modify_s( + ldap_applicant002DN, + [ (ldap.MOD_ADD,'mozilliansVouchedBy',ldap_applicant001DN) ] + ) + ######################################################################## # Main program