ticket47653_test.py 14 KB

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