ticket47653_test.py 14 KB

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