ticket47653_test.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349
  1. # --- BEGIN COPYRIGHT BLOCK ---
  2. # Copyright (C) 2015 Red Hat, Inc.
  3. # All rights reserved.
  4. #
  5. # License: GPL (version 3 or any later version).
  6. # See LICENSE for details.
  7. # --- END COPYRIGHT BLOCK ---
  8. #
  9. import os
  10. import sys
  11. import time
  12. import ldap
  13. import logging
  14. import pytest
  15. from lib389 import DirSrv, Entry, tools
  16. from lib389.tools import DirSrvTools
  17. from lib389._constants import *
  18. from lib389.properties import *
  19. log = logging.getLogger(__name__)
  20. OC_NAME = 'OCticket47653'
  21. MUST = "(postalAddress $ postalCode)"
  22. MAY = "(member $ street)"
  23. OTHER_NAME = 'other_entry'
  24. MAX_OTHERS = 10
  25. BIND_NAME = 'bind_entry'
  26. BIND_DN = 'cn=%s, %s' % (BIND_NAME, SUFFIX)
  27. BIND_PW = 'password'
  28. ENTRY_NAME = 'test_entry'
  29. ENTRY_DN = 'cn=%s, %s' % (ENTRY_NAME, SUFFIX)
  30. ENTRY_OC = "top person %s" % OC_NAME
  31. def _oc_definition(oid_ext, name, must=None, may=None):
  32. oid = "1.2.3.4.5.6.7.8.9.10.%d" % oid_ext
  33. desc = 'To test ticket 47490'
  34. sup = 'person'
  35. if not must:
  36. must = MUST
  37. if not may:
  38. may = MAY
  39. new_oc = "( %s NAME '%s' DESC '%s' SUP %s AUXILIARY MUST %s MAY %s )" % (oid, name, desc, sup, must, may)
  40. return new_oc
  41. class TopologyStandalone(object):
  42. def __init__(self, standalone):
  43. standalone.open()
  44. self.standalone = standalone
  45. @pytest.fixture(scope="module")
  46. def topology(request):
  47. standalone = DirSrv(verbose=False)
  48. # Args for the standalone instance
  49. args_instance[SER_HOST] = HOST_STANDALONE
  50. args_instance[SER_PORT] = PORT_STANDALONE
  51. args_instance[SER_SERVERID_PROP] = SERVERID_STANDALONE
  52. args_standalone = args_instance.copy()
  53. standalone.allocate(args_standalone)
  54. # Get the status of the instance and restart it if it exists
  55. instance_standalone = standalone.exists()
  56. # Remove the instance
  57. if instance_standalone:
  58. standalone.delete()
  59. # Create the instance
  60. standalone.create()
  61. # Used to retrieve configuration information (dbdir, confdir...)
  62. standalone.open()
  63. def fin():
  64. standalone.delete()
  65. request.addfinalizer(fin)
  66. # Here we have standalone instance up and running
  67. return TopologyStandalone(standalone)
  68. def test_ticket47653_init(topology):
  69. """
  70. It adds
  71. - Objectclass with MAY 'member'
  72. - an entry ('bind_entry') with which we bind to test the 'SELFDN' operation
  73. It deletes the anonymous aci
  74. """
  75. topology.standalone.log.info("Add %s that allows 'member' attribute" % OC_NAME)
  76. new_oc = _oc_definition(2, OC_NAME, must=MUST, may=MAY)
  77. topology.standalone.schema.add_schema('objectClasses', new_oc)
  78. # entry used to bind with
  79. topology.standalone.log.info("Add %s" % BIND_DN)
  80. topology.standalone.add_s(Entry((BIND_DN, {
  81. 'objectclass': "top person".split(),
  82. 'sn': BIND_NAME,
  83. 'cn': BIND_NAME,
  84. 'userpassword': BIND_PW})))
  85. # enable acl error logging
  86. mod = [(ldap.MOD_REPLACE, 'nsslapd-errorlog-level', '128')]
  87. topology.standalone.modify_s(DN_CONFIG, mod)
  88. # Remove aci's to start with a clean slate
  89. mod = [(ldap.MOD_DELETE, 'aci', None)]
  90. topology.standalone.modify_s(SUFFIX, mod)
  91. # add dummy entries
  92. for cpt in range(MAX_OTHERS):
  93. name = "%s%d" % (OTHER_NAME, cpt)
  94. topology.standalone.add_s(Entry(("cn=%s,%s" % (name, SUFFIX), {
  95. 'objectclass': "top person".split(),
  96. 'sn': name,
  97. 'cn': name})))
  98. def test_ticket47653_add(topology):
  99. '''
  100. It checks that, bound as bind_entry,
  101. - we can not ADD an entry without the proper SELFDN aci.
  102. - with the proper ACI we can not ADD with 'member' attribute
  103. - with the proper ACI and 'member' it succeeds to ADD
  104. '''
  105. topology.standalone.log.info("\n\n######################### ADD ######################\n")
  106. # bind as bind_entry
  107. topology.standalone.log.info("Bind as %s" % BIND_DN)
  108. topology.standalone.simple_bind_s(BIND_DN, BIND_PW)
  109. # Prepare the entry with multivalued members
  110. entry_with_members = Entry(ENTRY_DN)
  111. entry_with_members.setValues('objectclass', 'top', 'person', 'OCticket47653')
  112. entry_with_members.setValues('sn', ENTRY_NAME)
  113. entry_with_members.setValues('cn', ENTRY_NAME)
  114. entry_with_members.setValues('postalAddress', 'here')
  115. entry_with_members.setValues('postalCode', '1234')
  116. members = []
  117. for cpt in range(MAX_OTHERS):
  118. name = "%s%d" % (OTHER_NAME, cpt)
  119. members.append("cn=%s,%s" % (name, SUFFIX))
  120. members.append(BIND_DN)
  121. entry_with_members.setValues('member', members)
  122. # Prepare the entry with one member
  123. entry_with_member = Entry(ENTRY_DN)
  124. entry_with_member.setValues('objectclass', 'top', 'person', 'OCticket47653')
  125. entry_with_member.setValues('sn', ENTRY_NAME)
  126. entry_with_member.setValues('cn', ENTRY_NAME)
  127. entry_with_member.setValues('postalAddress', 'here')
  128. entry_with_member.setValues('postalCode', '1234')
  129. member = []
  130. member.append(BIND_DN)
  131. entry_with_member.setValues('member', member)
  132. # entry to add WITH member being BIND_DN but WITHOUT the ACI -> ldap.INSUFFICIENT_ACCESS
  133. try:
  134. topology.standalone.log.info("Try to add Add %s (aci is missing): %r" % (ENTRY_DN, entry_with_member))
  135. topology.standalone.add_s(entry_with_member)
  136. except Exception as e:
  137. topology.standalone.log.info("Exception (expected): %s" % type(e).__name__)
  138. assert isinstance(e, ldap.INSUFFICIENT_ACCESS)
  139. # Ok Now add the proper ACI
  140. topology.standalone.log.info("Bind as %s and add the ADD SELFDN aci" % DN_DM)
  141. topology.standalone.simple_bind_s(DN_DM, PASSWORD)
  142. ACI_TARGET = "(target = \"ldap:///cn=*,%s\")" % SUFFIX
  143. ACI_TARGETFILTER = "(targetfilter =\"(objectClass=%s)\")" % OC_NAME
  144. ACI_ALLOW = "(version 3.0; acl \"SelfDN add\"; allow (add)"
  145. ACI_SUBJECT = " userattr = \"member#selfDN\";)"
  146. ACI_BODY = ACI_TARGET + ACI_TARGETFILTER + ACI_ALLOW + ACI_SUBJECT
  147. mod = [(ldap.MOD_ADD, 'aci', ACI_BODY)]
  148. topology.standalone.modify_s(SUFFIX, mod)
  149. # bind as bind_entry
  150. topology.standalone.log.info("Bind as %s" % BIND_DN)
  151. topology.standalone.simple_bind_s(BIND_DN, BIND_PW)
  152. # entry to add WITHOUT member and WITH the ACI -> ldap.INSUFFICIENT_ACCESS
  153. try:
  154. topology.standalone.log.info("Try to add Add %s (member is missing)" % ENTRY_DN)
  155. topology.standalone.add_s(Entry((ENTRY_DN, {
  156. 'objectclass': ENTRY_OC.split(),
  157. 'sn': ENTRY_NAME,
  158. 'cn': ENTRY_NAME,
  159. 'postalAddress': 'here',
  160. 'postalCode': '1234'})))
  161. except Exception as e:
  162. topology.standalone.log.info("Exception (expected): %s" % type(e).__name__)
  163. assert isinstance(e, ldap.INSUFFICIENT_ACCESS)
  164. # entry to add WITH memberS and WITH the ACI -> ldap.INSUFFICIENT_ACCESS
  165. # member should contain only one value
  166. try:
  167. topology.standalone.log.info("Try to add Add %s (with several member values)" % ENTRY_DN)
  168. topology.standalone.add_s(entry_with_members)
  169. except Exception as e:
  170. topology.standalone.log.info("Exception (expected): %s" % type(e).__name__)
  171. assert isinstance(e, ldap.INSUFFICIENT_ACCESS)
  172. topology.standalone.log.info("Try to add Add %s should be successful" % ENTRY_DN)
  173. topology.standalone.add_s(entry_with_member)
  174. def test_ticket47653_search(topology):
  175. '''
  176. It checks that, bound as bind_entry,
  177. - we can not search an entry without the proper SELFDN aci.
  178. - adding the ACI, we can search the entry
  179. '''
  180. topology.standalone.log.info("\n\n######################### SEARCH ######################\n")
  181. # bind as bind_entry
  182. topology.standalone.log.info("Bind as %s" % BIND_DN)
  183. topology.standalone.simple_bind_s(BIND_DN, BIND_PW)
  184. # entry to search WITH member being BIND_DN but WITHOUT the ACI -> no entry returned
  185. topology.standalone.log.info("Try to search %s (aci is missing)" % ENTRY_DN)
  186. ents = topology.standalone.search_s(ENTRY_DN, ldap.SCOPE_BASE, 'objectclass=*')
  187. assert len(ents) == 0
  188. # Ok Now add the proper ACI
  189. topology.standalone.log.info("Bind as %s and add the READ/SEARCH SELFDN aci" % DN_DM)
  190. topology.standalone.simple_bind_s(DN_DM, PASSWORD)
  191. ACI_TARGET = "(target = \"ldap:///cn=*,%s\")" % SUFFIX
  192. ACI_TARGETATTR = "(targetattr = *)"
  193. ACI_TARGETFILTER = "(targetfilter =\"(objectClass=%s)\")" % OC_NAME
  194. ACI_ALLOW = "(version 3.0; acl \"SelfDN search-read\"; allow (read, search, compare)"
  195. ACI_SUBJECT = " userattr = \"member#selfDN\";)"
  196. ACI_BODY = ACI_TARGET + ACI_TARGETATTR + ACI_TARGETFILTER + ACI_ALLOW + ACI_SUBJECT
  197. mod = [(ldap.MOD_ADD, 'aci', ACI_BODY)]
  198. topology.standalone.modify_s(SUFFIX, mod)
  199. # bind as bind_entry
  200. topology.standalone.log.info("Bind as %s" % BIND_DN)
  201. topology.standalone.simple_bind_s(BIND_DN, BIND_PW)
  202. # entry to search with the proper aci
  203. topology.standalone.log.info("Try to search %s should be successful" % ENTRY_DN)
  204. ents = topology.standalone.search_s(ENTRY_DN, ldap.SCOPE_BASE, 'objectclass=*')
  205. assert len(ents) == 1
  206. def test_ticket47653_modify(topology):
  207. '''
  208. It checks that, bound as bind_entry,
  209. - we can not modify an entry without the proper SELFDN aci.
  210. - adding the ACI, we can modify the entry
  211. '''
  212. # bind as bind_entry
  213. topology.standalone.log.info("Bind as %s" % BIND_DN)
  214. topology.standalone.simple_bind_s(BIND_DN, BIND_PW)
  215. topology.standalone.log.info("\n\n######################### MODIFY ######################\n")
  216. # entry to modify WITH member being BIND_DN but WITHOUT the ACI -> ldap.INSUFFICIENT_ACCESS
  217. try:
  218. topology.standalone.log.info("Try to modify %s (aci is missing)" % ENTRY_DN)
  219. mod = [(ldap.MOD_REPLACE, 'postalCode', '9876')]
  220. topology.standalone.modify_s(ENTRY_DN, mod)
  221. except Exception as e:
  222. topology.standalone.log.info("Exception (expected): %s" % type(e).__name__)
  223. assert isinstance(e, ldap.INSUFFICIENT_ACCESS)
  224. # Ok Now add the proper ACI
  225. topology.standalone.log.info("Bind as %s and add the WRITE SELFDN aci" % DN_DM)
  226. topology.standalone.simple_bind_s(DN_DM, PASSWORD)
  227. ACI_TARGET = "(target = \"ldap:///cn=*,%s\")" % SUFFIX
  228. ACI_TARGETATTR = "(targetattr = *)"
  229. ACI_TARGETFILTER = "(targetfilter =\"(objectClass=%s)\")" % OC_NAME
  230. ACI_ALLOW = "(version 3.0; acl \"SelfDN write\"; allow (write)"
  231. ACI_SUBJECT = " userattr = \"member#selfDN\";)"
  232. ACI_BODY = ACI_TARGET + ACI_TARGETATTR + ACI_TARGETFILTER + ACI_ALLOW + ACI_SUBJECT
  233. mod = [(ldap.MOD_ADD, 'aci', ACI_BODY)]
  234. topology.standalone.modify_s(SUFFIX, mod)
  235. # bind as bind_entry
  236. topology.standalone.log.info("Bind as %s" % BIND_DN)
  237. topology.standalone.simple_bind_s(BIND_DN, BIND_PW)
  238. # modify the entry and checks the value
  239. topology.standalone.log.info("Try to modify %s. It should succeeds" % ENTRY_DN)
  240. mod = [(ldap.MOD_REPLACE, 'postalCode', '1928')]
  241. topology.standalone.modify_s(ENTRY_DN, mod)
  242. ents = topology.standalone.search_s(ENTRY_DN, ldap.SCOPE_BASE, 'objectclass=*')
  243. assert len(ents) == 1
  244. assert ents[0].postalCode == '1928'
  245. def test_ticket47653_delete(topology):
  246. '''
  247. It checks that, bound as bind_entry,
  248. - we can not delete an entry without the proper SELFDN aci.
  249. - adding the ACI, we can delete the entry
  250. '''
  251. topology.standalone.log.info("\n\n######################### DELETE ######################\n")
  252. # bind as bind_entry
  253. topology.standalone.log.info("Bind as %s" % BIND_DN)
  254. topology.standalone.simple_bind_s(BIND_DN, BIND_PW)
  255. # entry to delete WITH member being BIND_DN but WITHOUT the ACI -> ldap.INSUFFICIENT_ACCESS
  256. try:
  257. topology.standalone.log.info("Try to delete %s (aci is missing)" % ENTRY_DN)
  258. topology.standalone.delete_s(ENTRY_DN)
  259. except Exception as e:
  260. topology.standalone.log.info("Exception (expected): %s" % type(e).__name__)
  261. assert isinstance(e, ldap.INSUFFICIENT_ACCESS)
  262. # Ok Now add the proper ACI
  263. topology.standalone.log.info("Bind as %s and add the READ/SEARCH SELFDN aci" % DN_DM)
  264. topology.standalone.simple_bind_s(DN_DM, PASSWORD)
  265. ACI_TARGET = "(target = \"ldap:///cn=*,%s\")" % SUFFIX
  266. ACI_TARGETFILTER = "(targetfilter =\"(objectClass=%s)\")" % OC_NAME
  267. ACI_ALLOW = "(version 3.0; acl \"SelfDN delete\"; allow (delete)"
  268. ACI_SUBJECT = " userattr = \"member#selfDN\";)"
  269. ACI_BODY = ACI_TARGET + ACI_TARGETFILTER + ACI_ALLOW + ACI_SUBJECT
  270. mod = [(ldap.MOD_ADD, 'aci', ACI_BODY)]
  271. topology.standalone.modify_s(SUFFIX, mod)
  272. # bind as bind_entry
  273. topology.standalone.log.info("Bind as %s" % BIND_DN)
  274. topology.standalone.simple_bind_s(BIND_DN, BIND_PW)
  275. # entry to search with the proper aci
  276. topology.standalone.log.info("Try to delete %s should be successful" % ENTRY_DN)
  277. topology.standalone.delete_s(ENTRY_DN)
  278. if __name__ == '__main__':
  279. # Run isolated
  280. # -s for DEBUG mode
  281. CURRENT_FILE = os.path.realpath(__file__)
  282. pytest.main("-s %s" % CURRENT_FILE)