models.py 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270
  1. # -*- coding: utf-8 -*-
  2. import ldapdb.models
  3. import pprint
  4. import os
  5. import base64
  6. import hashlib
  7. import unicodedata
  8. import string
  9. import datetime
  10. from django.db import models
  11. from ldapdb.models.fields import CharField, IntegerField, ListField
  12. from django.db.models.signals import post_save, pre_save, post_delete
  13. from django.dispatch import receiver
  14. from south.modelsinspector import add_ignored_fields
  15. from django.core import exceptions
  16. import logging
  17. class Member(models.Model):
  18. MEMBER_TYPE_CHOICES = (
  19. ('personne_physique', 'Personne physique'),
  20. ('personne_morale', 'Personne morale'),
  21. )
  22. MEMBER_STATUS_CHOICES = (
  23. ('adherent', 'Adhérent'),
  24. ('non_adherent', 'Non adhérent'),
  25. ('demande_adhesion', "Demande d'adhésion"),
  26. )
  27. status = models.CharField(max_length=50, choices=MEMBER_STATUS_CHOICES,
  28. default='non_adherent')
  29. type = models.CharField(max_length=20, choices=MEMBER_TYPE_CHOICES,
  30. default='personne_physique')
  31. first_name = models.CharField(max_length=200, verbose_name=u'Prénom')
  32. last_name = models.CharField(max_length=200, verbose_name=u'Nom')
  33. ldap_cn = models.CharField(max_length=200, blank=True,
  34. help_text='Clé avec le LDAP. Laisser vide pour la générer'
  35. 'automatiquement')
  36. organization_name = models.CharField(max_length=200, blank=True,
  37. verbose_name='Nom de l\'organisme',
  38. help_text='Pour une personne morale')
  39. email = models.EmailField(max_length=254, verbose_name=u'Courriel')
  40. home_phone_number = models.CharField(max_length=25, blank=True,
  41. verbose_name=u'Téléphone fixe')
  42. mobile_phone_number = models.CharField(max_length=25, blank=True,
  43. verbose_name=u'Téléphone mobile')
  44. address = models.TextField(verbose_name=u'Adresse')
  45. postal_code = models.CharField(max_length=15,
  46. verbose_name=u'Code postal')
  47. city = models.CharField(max_length=200,
  48. verbose_name=u'Commune')
  49. country = models.CharField(max_length=200,
  50. default='France',
  51. verbose_name=u'Pays')
  52. entry_date = models.DateField(null=False,
  53. blank=False,
  54. default=datetime.date.today,
  55. verbose_name='Date de première adhésion')
  56. resign_date = models.DateField(null=True, blank=True,
  57. verbose_name='Date de départ de l\'association')
  58. def __unicode__(self):
  59. name = self.first_name + ' ' + self.last_name
  60. if (self.organization_name):
  61. name += ' (%s)' % self.organization_name
  62. return name
  63. # Renvoi la date de fin de la dernière cotisation du membre
  64. def end_date_of_membership(self):
  65. try:
  66. return self.membership_fees.order_by('-end_date')[0].end_date
  67. except:
  68. return None
  69. def change_password(self, new_password):
  70. ldap_user = LdapUser.objects.get(pk=self.ldap_cn)
  71. ldap_user.password = new_password
  72. ldap_user.save()
  73. class Meta:
  74. verbose_name = 'membre'
  75. class CryptoKey(models.Model):
  76. KEY_TYPE_CHOICES = (('RSA', 'RSA'), ('GPG', 'GPG'))
  77. type = models.CharField(max_length=3, choices=KEY_TYPE_CHOICES)
  78. key = models.TextField(verbose_name=u'Clé')
  79. member = models.ForeignKey('Member', verbose_name=u'Membre')
  80. def __unicode__(self):
  81. return u'Clé %s de %s' % (self.type, self.member)
  82. class Meta:
  83. verbose_name = 'clé'
  84. class MembershipFee(models.Model):
  85. member = models.ForeignKey('Member', related_name='membership_fees',
  86. verbose_name=u'Membre')
  87. amount = models.IntegerField(null=False, default='20', help_text='en €',
  88. verbose_name=u'Montant')
  89. start_date = models.DateField(
  90. null=False,
  91. blank=False,
  92. default=datetime.date.today,
  93. verbose_name='Date de début de cotisation')
  94. end_date = models.DateField(
  95. null=False,
  96. blank=False,
  97. default=datetime.date.today() + datetime.timedelta(365),
  98. verbose_name='Date de fin de cotisation')
  99. def __unicode__(self):
  100. return (u'%s - %s - %i€' % (self.member, self.start_date,
  101. self.amount))
  102. class Meta:
  103. verbose_name = 'cotisation'
  104. class LdapUser(ldapdb.models.Model):
  105. # TODO: déplacer ligne suivante dans settings.py
  106. base_dn = "ou=users,ou=unix,o=ILLYSE,l=Villeurbanne,st=RHA,c=FR"
  107. object_classes = ['inetOrgPerson', 'organizationalPerson', 'person',
  108. 'top', 'posixAccount']
  109. uid = CharField(db_column='uid', unique=True, max_length=255)
  110. nick_name = CharField(db_column='cn', unique=True, primary_key=True,
  111. max_length=255)
  112. first_name = CharField(db_column='givenName', max_length=255)
  113. last_name = CharField(db_column='sn', max_length=255)
  114. display_name = CharField(db_column='displayName', max_length=255,
  115. blank=True)
  116. password = CharField(db_column='userPassword', max_length=255)
  117. uidNumber = IntegerField(db_column='uidNumber', unique=True)
  118. gidNumber = IntegerField(db_column='gidNumber', default=2000)
  119. homeDirectory = CharField(db_column='homeDirectory', max_length=255,
  120. default='/tmp')
  121. def __unicode__(self):
  122. return self.display_name
  123. class Meta:
  124. managed = False # Indique à South de ne pas gérer le model LdapUser
  125. class LdapGroup(ldapdb.models.Model):
  126. """
  127. Class for representing an LDAP group entry.
  128. """
  129. # LDAP meta-data
  130. base_dn = "ou=groups,ou=unix,o=ILLYSE,l=Villeurbanne,st=RHA,c=FR"
  131. object_classes = ['posixGroup']
  132. # posixGroup attributes
  133. gid = IntegerField(db_column='gidNumber', unique=True)
  134. name = CharField(db_column='cn', max_length=200, primary_key=True)
  135. members = ListField(db_column='memberUid')
  136. def __unicode__(self):
  137. return self.name
  138. class Meta:
  139. managed = False # Indique à South de ne pas gérer le model LdapGroup
  140. #Indique à South de ne pas gérer les models LdapUser et LdapGroup
  141. add_ignored_fields(["^ldapdb\.models\.fields"])
  142. @receiver(pre_save, sender=LdapUser)
  143. def change_password(sender, instance, **kwargs):
  144. """
  145. Lors de la sauvegarde d'un utilisateur Ldap, cette fonction est exécutée
  146. avant la sauvegarde pour chiffrer le mot de passe s'il est définit
  147. et s'il n'est pas déjà chiffré
  148. """
  149. # Si le mot de passe est définit et n'est pas déjà chiffré,
  150. # alors ça le chiffre
  151. if instance.password and not instance.password.startswith('{SSHA}'):
  152. salt = os.urandom(8).encode('hex')
  153. digest = hashlib.sha1(instance.password + salt).digest()
  154. instance.password = '{SSHA}' + base64.b64encode(digest + salt)
  155. @receiver(pre_save, sender=LdapUser)
  156. def define_display_name(sender, instance, **kwargs):
  157. """
  158. Lors de la sauvegarde d'un utilisateur Ldap, le champ display_name est la
  159. concaténation de first_name et last_name
  160. """
  161. if not instance.display_name:
  162. instance.display_name = '%s %s' % (instance.first_name,
  163. instance.last_name)
  164. @receiver(pre_save, sender=Member)
  165. def define_ldap_cn(sender, instance, **kwargs):
  166. """
  167. Lors de la sauvegarde d'un membre. Si le champ ldap_cn n'est pas définit,
  168. le calcul automatiquement en fonction du nom et du prénom
  169. """
  170. if not instance.ldap_cn:
  171. # Première lettre de chaque partie du prénom
  172. first_name_letters = ''.join(
  173. [c[0] for c in instance.first_name.split('-')]
  174. )
  175. # Concaténer avec nom de famille
  176. ldap_cn = ('%s%s' % (first_name_letters, instance.last_name))
  177. # Remplacer ou enlever les caractères non ascii
  178. ldap_cn = unicodedata.normalize('NFD', ldap_cn)\
  179. .encode('ascii', 'ignore')
  180. # Enlever ponctuation et espace
  181. ldap_cn = ldap_cn.translate(None, string.punctuation + ' ')
  182. # En minuscule
  183. ldap_cn = ldap_cn.lower()
  184. instance.ldap_cn = ldap_cn
  185. @receiver(post_save, sender=Member)
  186. def sync_ldap(sender, instance, created, **kwargs):
  187. """
  188. Update LDAP data when a member is saved
  189. """
  190. if not created:
  191. ldap_user = LdapUser.objects.get(pk=instance.ldap_cn)
  192. if created:
  193. max_uidNumber = LdapUser.objects.order_by('-uidNumber')[0].uidNumber
  194. ldap_user = LdapUser()
  195. ldap_user.pk = instance.ldap_cn
  196. ldap_user.uid = instance.ldap_cn
  197. ldap_user.nick_name = instance.ldap_cn
  198. ldap_user.uidNumber = max_uidNumber + 1
  199. ldap_user.last_name = instance.last_name
  200. ldap_user.first_name = instance.first_name
  201. ldap_user.save()
  202. if created:
  203. ldap_group = LdapGroup.objects.get(pk='coin')
  204. ldap_group.members.append(ldap_user.pk)
  205. ldap_group.save()
  206. @receiver(post_delete, sender=Member)
  207. def remove_ldap_user_from_coin_group_when_deleting_member(sender,
  208. instance,
  209. **kwargs):
  210. """
  211. Lorsqu'un membre est supprimé du SI, son utilisateur LDAP correspondant est
  212. sorti du groupe "coin"
  213. """
  214. ldap_group = LdapGroup.objects.get(pk='coin')
  215. if instance.ldap_cn in ldap_group.members:
  216. ldap_group.members.remove(instance.ldap_cn)
  217. ldap_group.save()
  218. #==============================================================================
  219. # @receiver(pre_save, sender = LdapUser)
  220. # def ssha_password(sender, **kwargs):
  221. # if not kwargs['instance'].password.startswith('{SSHA}'):
  222. # salt = os.urandom(8).encode('hex')
  223. # kwargs['instance'].password = '{SSHA}' + base64.b64encode(
  224. # hashlib.sha1(obj.password + salt).digest() + salt)
  225. #==============================================================================