base.py 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. # -*- coding: utf-8 -*-
  2. #
  3. # django-ldapdb
  4. # Copyright (c) 2009-2011, Bolloré telecom
  5. # Copyright (c) 2013, Jeremy Lainé
  6. # All rights reserved.
  7. #
  8. # See AUTHORS file for a full list of contributors.
  9. #
  10. # Redistribution and use in source and binary forms, with or without
  11. # modification, are permitted provided that the following conditions are met:
  12. #
  13. # 1. Redistributions of source code must retain the above copyright notice,
  14. # this list of conditions and the following disclaimer.
  15. #
  16. # 2. Redistributions in binary form must reproduce the above copyright
  17. # notice, this list of conditions and the following disclaimer in the
  18. # documentation and/or other materials provided with the distribution.
  19. #
  20. # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  21. # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  22. # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  23. # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
  24. # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  25. # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  26. # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  27. # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
  28. # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  29. # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  30. # POSSIBILITY OF SUCH DAMAGE.
  31. #
  32. import ldap
  33. import logging
  34. import django.db.models
  35. from django.db import connections, router
  36. from django.db.models import signals
  37. import ldapdb # noqa
  38. logger = logging.getLogger('ldapdb')
  39. class Model(django.db.models.base.Model):
  40. """
  41. Base class for all LDAP models.
  42. """
  43. dn = django.db.models.fields.CharField(max_length=200)
  44. # meta-data
  45. base_dn = None
  46. search_scope = ldap.SCOPE_SUBTREE
  47. object_classes = ['top']
  48. def __init__(self, *args, **kwargs):
  49. super(Model, self).__init__(*args, **kwargs)
  50. self.saved_pk = self.pk
  51. def build_rdn(self):
  52. """
  53. Build the Relative Distinguished Name for this entry.
  54. """
  55. bits = []
  56. for field in self._meta.fields:
  57. if field.db_column and field.primary_key:
  58. bits.append("%s=%s" % (field.db_column,
  59. getattr(self, field.name)))
  60. if not len(bits):
  61. raise Exception("Could not build Distinguished Name")
  62. return '+'.join(bits)
  63. def build_dn(self):
  64. """
  65. Build the Distinguished Name for this entry.
  66. """
  67. return "%s,%s" % (self.build_rdn(), self.base_dn)
  68. raise Exception("Could not build Distinguished Name")
  69. def delete(self, using=None):
  70. """
  71. Delete this entry.
  72. """
  73. using = using or router.db_for_write(self.__class__, instance=self)
  74. connection = connections[using]
  75. logger.debug("Deleting LDAP entry %s" % self.dn)
  76. connection.delete_s(self.dn)
  77. signals.post_delete.send(sender=self.__class__, instance=self)
  78. def save(self, using=None):
  79. """
  80. Saves the current instance.
  81. """
  82. signals.pre_save.send(sender=self.__class__, instance=self)
  83. using = using or router.db_for_write(self.__class__, instance=self)
  84. connection = connections[using]
  85. if not self.dn:
  86. # create a new entry
  87. record_exists = False
  88. entry = [('objectClass', self.object_classes)]
  89. new_dn = self.build_dn()
  90. for field in self._meta.fields:
  91. if not field.db_column:
  92. continue
  93. value = getattr(self, field.name)
  94. if value:
  95. entry.append((field.db_column,
  96. field.get_db_prep_save(
  97. value, connection=connection)))
  98. logger.debug("Creating new LDAP entry %s" % new_dn)
  99. connection.add_s(new_dn, entry)
  100. # update object
  101. self.dn = new_dn
  102. else:
  103. # update an existing entry
  104. record_exists = True
  105. modlist = []
  106. orig = self.__class__.objects.get(pk=self.saved_pk)
  107. for field in self._meta.fields:
  108. if not field.db_column:
  109. continue
  110. old_value = getattr(orig, field.name, None)
  111. new_value = getattr(self, field.name, None)
  112. if old_value != new_value:
  113. if new_value:
  114. modlist.append(
  115. (ldap.MOD_REPLACE, field.db_column,
  116. field.get_db_prep_save(new_value,
  117. connection=connection)))
  118. elif old_value:
  119. modlist.append((ldap.MOD_DELETE, field.db_column,
  120. None))
  121. if len(modlist):
  122. # handle renaming
  123. new_dn = self.build_dn()
  124. if new_dn != self.dn:
  125. logger.debug("Renaming LDAP entry %s to %s" % (self.dn,
  126. new_dn))
  127. connection.rename_s(self.dn, self.build_rdn())
  128. self.dn = new_dn
  129. logger.debug("Modifying existing LDAP entry %s" % self.dn)
  130. connection.modify_s(self.dn, modlist)
  131. else:
  132. logger.debug("No changes to be saved to LDAP entry %s" %
  133. self.dn)
  134. # done
  135. self.saved_pk = self.pk
  136. signals.post_save.send(sender=self.__class__, instance=self,
  137. created=(not record_exists))
  138. @classmethod
  139. def scoped(base_class, base_dn):
  140. """
  141. Returns a copy of the current class with a different base_dn.
  142. """
  143. class Meta:
  144. proxy = True
  145. import re
  146. suffix = re.sub('[=,]', '_', base_dn)
  147. name = "%s_%s" % (base_class.__name__, str(suffix))
  148. new_class = type(name, (base_class,), {
  149. 'base_dn': base_dn, '__module__': base_class.__module__,
  150. 'Meta': Meta})
  151. return new_class
  152. class Meta:
  153. abstract = True