models.py 6.6 KB

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