models.py 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. # -*- coding: utf-8 -*-
  2. from __future__ import unicode_literals
  3. from django.db import models
  4. from django.core.exceptions import ValidationError
  5. from django.conf import settings
  6. from django.core.urlresolvers import reverse
  7. from netfields import InetAddressField, NetManager
  8. import ldapdb.models
  9. from ldapdb.models.fields import CharField, ListField
  10. from coin.mixins import CoinLdapSyncMixin
  11. from coin.offers.models import OfferSubscription
  12. from coin.configuration.models import Configuration
  13. # from coin.offers.backends import ValidateBackendType
  14. from coin import utils
  15. from coin import validation
  16. class VPNConfiguration(CoinLdapSyncMixin, Configuration):
  17. url_namespace = "vpn"
  18. # backend_name = "openvpn_ldap"
  19. # administrative_subscription = models.OneToOneField(
  20. # 'offers.OfferSubscription',
  21. # related_name=backend_name,
  22. # validators=[ValidateBackendType(backend_name)])
  23. activated = models.BooleanField(default=False, verbose_name='activé')
  24. login = models.CharField(max_length=50, unique=True, blank=True,
  25. verbose_name="identifiant",
  26. help_text="leave empty for automatic generation")
  27. password = models.CharField(max_length=256, verbose_name="mot de passe",
  28. blank=True, null=True)
  29. ipv4_endpoint = InetAddressField(validators=[validation.validate_v4],
  30. verbose_name="IPv4", blank=True, null=True,
  31. help_text="Adresse IPv4 utilisée par "
  32. "défaut sur le VPN")
  33. ipv6_endpoint = InetAddressField(validators=[validation.validate_v6],
  34. verbose_name="IPv6", blank=True, null=True,
  35. help_text="Adresse IPv6 utilisée par "
  36. "défaut sur le VPN")
  37. comment = models.CharField(blank=True, max_length=512,
  38. verbose_name="commentaire")
  39. objects = NetManager()
  40. def get_absolute_url(self):
  41. return reverse('vpn:details', args=[str(self.pk)])
  42. # These two methods are part of the general configuration interface.
  43. def save_subnet(self, subnet, creation):
  44. self.check_endpoints(delete=True)
  45. # We potentially changed the endpoints, so we need to save.
  46. self.full_clean()
  47. self.save()
  48. def delete_subnet(self, subnet):
  49. self.save_subnet(subnet, False)
  50. def get_subnets(self, version):
  51. subnets = self.ip_subnet.all()
  52. return [subnet for subnet in subnets if subnet.inet.version == version]
  53. def sync_to_ldap(self, creation, *args, **kwargs):
  54. if creation:
  55. config = LdapVPNConfig()
  56. else:
  57. config = LdapVPNConfig.objects.get(pk=self.login)
  58. config.login = config.sn = self.login
  59. config.password = self.password
  60. config.active = 'yes' if self.activated else 'no'
  61. config.ipv4_endpoint = utils.str_or_none(self.ipv4_endpoint)
  62. config.ipv6_endpoint = utils.str_or_none(self.ipv6_endpoint)
  63. config.ranges_v4 = [str(s) for s in self.get_subnets(4)]
  64. config.ranges_v6 = [str(s) for s in self.get_subnets(6)]
  65. config.save()
  66. def delete_from_ldap(self):
  67. LdapVPNConfig.objects.get(pk=self.login).delete()
  68. def generate_endpoints(self, v4=True, v6=True):
  69. """Generate IP endpoints in one of the attributed IP subnets. If there is
  70. no available subnet for a given address family, then no endpoint
  71. is generated for this address family. If there already is an
  72. endpoint, do nothing.
  73. Returns True if an endpoint was generated.
  74. TODO: this should be factored for other technologies (DSL, etc)
  75. """
  76. subnets = self.ip_subnet.all()
  77. updated = False
  78. if v4 and self.ipv4_endpoint is None:
  79. subnets_v4 = [s for s in subnets if s.inet.version == 4]
  80. if len(subnets_v4) > 0:
  81. self.ipv4_endpoint = subnets_v4[0].inet.ip
  82. updated = True
  83. if v6 and self.ipv6_endpoint is None:
  84. subnets_v6 = [s for s in subnets if s.inet.version == 6]
  85. if len(subnets_v6) > 0:
  86. # With v6, we choose the second host of the subnet (cafe::1)
  87. gen = subnets_v6[0].inet.iter_hosts()
  88. gen.next()
  89. self.ipv6_endpoint = gen.next()
  90. updated = True
  91. return updated
  92. def check_endpoints(self, delete=False):
  93. """Check that the IP endpoints are included in one of the attributed IP
  94. subnets.
  95. If [delete] is True, then simply delete the faulty endpoints
  96. instead of raising an exception.
  97. """
  98. error = "L'IP {} n'est pas dans un réseau attribué."
  99. subnets = self.ip_subnet.all()
  100. is_faulty = lambda endpoint : endpoint and not any([endpoint in subnet.inet for subnet in subnets])
  101. if is_faulty(self.ipv4_endpoint):
  102. if delete:
  103. self.ipv4_endpoint = None
  104. else:
  105. raise ValidationError(error.format(self.ipv4_endpoint))
  106. if is_faulty(self.ipv6_endpoint):
  107. if delete:
  108. self.ipv6_endpoint = None
  109. else:
  110. raise ValidationError(error.format(self.ipv6_endpoint))
  111. def clean(self):
  112. # Generate VPN login, of the form "login-vpnX". The resulting
  113. # login should not contain any ".", because graphite uses "." as a
  114. # separator.
  115. if not self.login:
  116. username = self.offersubscription.member.username
  117. vpns = VPNConfiguration.objects.filter(offersubscription__member__username=username)
  118. # This is the list of existing VPN logins for this user.
  119. logins = [vpn.login for vpn in vpns]
  120. # 100 VPNs ought to be enough for anybody.
  121. for login in ["{}-vpn{}".format(username, k) for k in range(1, 101)]:
  122. if login not in logins:
  123. self.login = login
  124. break
  125. # We may have failed.
  126. if not self.login:
  127. ValidationError("Impossible de générer un login VPN")
  128. # Hash password if needed
  129. self.password = utils.ldap_hash(self.password)
  130. # If saving for the first time and IP endpoints are not specified,
  131. # generate them automatically.
  132. if self.pk is None:
  133. self.generate_endpoints()
  134. self.check_endpoints()
  135. def __unicode__(self):
  136. return 'VPN ' + self.login
  137. class Meta:
  138. verbose_name = 'VPN'
  139. class LdapVPNConfig(ldapdb.models.Model):
  140. # TODO: déplacer ligne suivante dans settings.py
  141. base_dn = settings.VPN_CONF_BASE_DN # "ou=vpn,o=ILLYSE,l=Villeurbanne,st=RHA,c=FR"
  142. object_classes = [b'person', b'organizationalPerson', b'inetOrgPerson',
  143. b'top', b'radiusprofile']
  144. login = CharField(db_column=b'cn', primary_key=True, max_length=255)
  145. sn = CharField(db_column=b'sn', max_length=255)
  146. password = CharField(db_column=b'userPassword', max_length=255)
  147. active = CharField(db_column=b'dialupAccess', max_length=3)
  148. ipv4_endpoint = CharField(db_column=b'radiusFramedIPAddress', max_length=16)
  149. ipv6_endpoint = CharField(db_column=b'postalAddress', max_length=40)
  150. ranges_v4 = ListField(db_column=b'radiusFramedRoute')
  151. ranges_v6 = ListField(db_column=b'registeredAddress')
  152. def __unicode__(self):
  153. return self.login
  154. class Meta:
  155. managed = False # Indique à South de ne pas gérer le model LdapUser