From d190ef00cee75e57fef51487d2f5d58d636072a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Kervella?= Date: Thu, 19 Apr 2018 22:00:49 +0000 Subject: [PATCH 01/42] Std API for user --- api/serializers.py | 907 +++++++++++++++++++--------------- api/urls.py | 90 ++-- api/utils.py | 123 ----- api/views.py | 1181 +++++++++++++++++++++++--------------------- re2o/settings.py | 5 + 5 files changed, 1190 insertions(+), 1116 deletions(-) delete mode 100644 api/utils.py diff --git a/api/serializers.py b/api/serializers.py index f2604068..e274f942 100644 --- a/api/serializers.py +++ b/api/serializers.py @@ -23,7 +23,17 @@ Serializers for the API app """ from rest_framework import serializers -from users.models import Club, Adherent +from users.models import ( + User, + Club, + Adherent, + ServiceUser, + School, + ListRight, + ListShell, + Ban, + Whitelist +) from machines.models import ( Interface, IpType, @@ -40,407 +50,524 @@ from machines.models import ( ) -class ServiceLinkSerializer(serializers.ModelSerializer): - """ Serializer for the ServiceLink objects """ - - name = serializers.CharField(source='service.service_type') +class UserSerializer(serializers.HyperlinkedModelSerializer): + access = serializers.BooleanField(source='has_access') + uid = serializers.IntegerField(source='uid_number') class Meta: - model = Service_link - fields = ('name',) + model = User + fields = ('name', 'pseudo', 'email', 'school', 'shell', 'comment', + 'state', 'registered', 'telephone', 'solde', #'room', + 'access', 'end_access', 'uid', 'class_name', 'api_url') + extra_kwargs = { + 'school': {'view_name': 'api:school-detail'}, + 'shell': {'view_name': 'api:shell-detail'}, + #'room': {'view_name': 'api:room-detail'}, + 'api_url': {'view_name': 'api:user-detail'} + } -class MailingSerializer(serializers.ModelSerializer): - """ Serializer to build Mailing objects """ - - name = serializers.CharField(source='pseudo') +class ClubSerializer(serializers.HyperlinkedModelSerializer): + name = serializers.CharField(source='surname') + access = serializers.BooleanField(source='has_access') + uid = serializers.IntegerField(source='uid_number') class Meta: model = Club - fields = ('name',) + fields = ('name', 'pseudo', 'email', 'school', 'shell', 'comment', + 'state', 'registered', 'telephone', 'solde', #'room', + 'access', 'end_access', 'administrators', 'members', + 'mailing', 'uid', 'api_url') + extra_kwargs = { + 'school': {'view_name': 'api:school-detail'}, + 'shell': {'view_name': 'api:shell-detail'}, + #'room': {'view_name': 'api:room-detail'}, + 'administrators': {'view_name': 'api:adherent-detail'}, + 'members': {'view_name': 'api:adherent-detail'}, + 'api_url': {'view_name': 'api:club-detail'} + } -class MailingMemberSerializer(serializers.ModelSerializer): - """ Serializer fot the Adherent objects (who belong to a - Mailing) """ +class AdherentSerializer(serializers.HyperlinkedModelSerializer): + access = serializers.BooleanField(source='has_access') + uid = serializers.IntegerField(source='uid_number') class Meta: model = Adherent - fields = ('email',) - - -class IpTypeField(serializers.RelatedField): - """ Serializer for an IpType object field """ - - def to_representation(self, value): - return value.type - - def to_internal_value(self, data): - pass - - -class IpListSerializer(serializers.ModelSerializer): - """ Serializer for an Ipv4List obejct using the IpType serialization """ - - ip_type = IpTypeField(read_only=True) - - class Meta: - model = IpList - fields = ('ipv4', 'ip_type') - - -class Ipv6ListSerializer(serializers.ModelSerializer): - """ Serializer for an Ipv6List object """ - - class Meta: - model = Ipv6List - fields = ('ipv6', 'slaac_ip') - - -class InterfaceSerializer(serializers.ModelSerializer): - """ Serializer for an Interface object. Use SerializerMethodField - to get ForeignKey values """ - - ipv4 = IpListSerializer(read_only=True) - # TODO : use serializer.RelatedField to avoid duplicate code - mac_address = serializers.SerializerMethodField('get_macaddress') - domain = serializers.SerializerMethodField('get_dns') - extension = serializers.SerializerMethodField('get_interface_extension') - - class Meta: - model = Interface - fields = ('ipv4', 'mac_address', 'domain', 'extension') - - @staticmethod - def get_dns(obj): - """ The name of the associated DNS object """ - return obj.domain.name - - @staticmethod - def get_interface_extension(obj): - """ The name of the associated Interface object """ - return obj.domain.extension.name - - @staticmethod - def get_macaddress(obj): - """ The string representation of the associated MAC address """ - return str(obj.mac_address) - - -class FullInterfaceSerializer(serializers.ModelSerializer): - """ Serializer for an Interface obejct. Use SerializerMethodField - to get ForeignKey values """ - - ipv4 = IpListSerializer(read_only=True) - ipv6 = Ipv6ListSerializer(read_only=True, many=True) - # TODO : use serializer.RelatedField to avoid duplicate code - mac_address = serializers.SerializerMethodField('get_macaddress') - domain = serializers.SerializerMethodField('get_dns') - extension = serializers.SerializerMethodField('get_interface_extension') - - class Meta: - model = Interface - fields = ('ipv4', 'ipv6', 'mac_address', 'domain', 'extension') - - @staticmethod - def get_dns(obj): - """ The name of the associated DNS object """ - return obj.domain.name - - @staticmethod - def get_interface_extension(obj): - """ The name of the associated Extension object """ - return obj.domain.extension.name - - @staticmethod - def get_macaddress(obj): - """ The string representation of the associated MAC address """ - return str(obj.mac_address) - - -class ExtensionNameField(serializers.RelatedField): - """ Serializer for Extension object field """ - - def to_representation(self, value): - return value.name - - def to_internal_value(self, data): - pass - - -class TypeSerializer(serializers.ModelSerializer): - """ Serializer for an IpType object. Use SerializerMethodField to - get ForeignKey values """ - - extension = ExtensionNameField(read_only=True) - ouverture_ports_tcp_in = serializers\ - .SerializerMethodField('get_port_policy_input_tcp') - ouverture_ports_tcp_out = serializers\ - .SerializerMethodField('get_port_policy_output_tcp') - ouverture_ports_udp_in = serializers\ - .SerializerMethodField('get_port_policy_input_udp') - ouverture_ports_udp_out = serializers\ - .SerializerMethodField('get_port_policy_output_udp') - - class Meta: - model = IpType - fields = ('type', 'extension', 'domaine_ip_start', 'domaine_ip_stop', - 'prefix_v6', - 'ouverture_ports_tcp_in', 'ouverture_ports_tcp_out', - 'ouverture_ports_udp_in', 'ouverture_ports_udp_out',) - - @staticmethod - def get_port_policy(obj, protocole, io): - """ Generic utility function to get the policy for a given - port, protocole and IN or OUT """ - if obj.ouverture_ports is None: - return [] - return map( - str, - obj.ouverture_ports.ouvertureport_set.filter( - protocole=protocole - ).filter(io=io) - ) - - def get_port_policy_input_tcp(self, obj): - """Renvoie la liste des ports ouverts en entrée tcp""" - return self.get_port_policy(obj, OuverturePort.TCP, OuverturePort.IN) - - def get_port_policy_output_tcp(self, obj): - """Renvoie la liste des ports ouverts en sortie tcp""" - return self.get_port_policy(obj, OuverturePort.TCP, OuverturePort.OUT) - - def get_port_policy_input_udp(self, obj): - """Renvoie la liste des ports ouverts en entrée udp""" - return self.get_port_policy(obj, OuverturePort.UDP, OuverturePort.IN) - - def get_port_policy_output_udp(self, obj): - """Renvoie la liste des ports ouverts en sortie udp""" - return self.get_port_policy(obj, OuverturePort.UDP, OuverturePort.OUT) - - -class ExtensionSerializer(serializers.ModelSerializer): - """Serialisation d'une extension : origin_ip et la zone sont - des foreign_key donc evalués en get_...""" - origin = serializers.SerializerMethodField('get_origin_ip') - zone_entry = serializers.SerializerMethodField('get_zone_name') - soa = serializers.SerializerMethodField('get_soa_data') - - class Meta: - model = Extension - fields = ('name', 'origin', 'origin_v6', 'zone_entry', 'soa') - - @staticmethod - def get_origin_ip(obj): - """ The IP of the associated origin for the zone """ - return obj.origin.ipv4 - - @staticmethod - def get_zone_name(obj): - """ The name of the associated zone """ - return str(obj.dns_entry) - - @staticmethod - def get_soa_data(obj): - """ The representation of the associated SOA """ - return {'mail': obj.soa.dns_soa_mail, 'param': obj.soa.dns_soa_param} - - -class MxSerializer(serializers.ModelSerializer): - """Serialisation d'un MX, evaluation du nom, de la zone - et du serveur cible, etant des foreign_key""" - name = serializers.SerializerMethodField('get_entry_name') - zone = serializers.SerializerMethodField('get_zone_name') - mx_entry = serializers.SerializerMethodField('get_mx_name') - - class Meta: - model = Mx - fields = ('zone', 'priority', 'name', 'mx_entry') - - @staticmethod - def get_entry_name(obj): - """ The name of the DNS MX entry """ - return str(obj.name) - - @staticmethod - def get_zone_name(obj): - """ The name of the associated zone of the MX record """ - return obj.zone.name - - @staticmethod - def get_mx_name(obj): - """ The string representation of the entry to add to the DNS """ - return str(obj.dns_entry) - - -class TxtSerializer(serializers.ModelSerializer): - """Serialisation d'un txt : zone cible et l'entrée txt - sont evaluées à part""" - zone = serializers.SerializerMethodField('get_zone_name') - txt_entry = serializers.SerializerMethodField('get_txt_name') - - class Meta: - model = Txt - fields = ('zone', 'txt_entry', 'field1', 'field2') - - @staticmethod - def get_zone_name(obj): - """ The name of the associated zone """ - return str(obj.zone.name) - - @staticmethod - def get_txt_name(obj): - """ The string representation of the entry to add to the DNS """ - return str(obj.dns_entry) - - -class SrvSerializer(serializers.ModelSerializer): - """Serialisation d'un srv : zone cible et l'entrée txt""" - extension = serializers.SerializerMethodField('get_extension_name') - srv_entry = serializers.SerializerMethodField('get_srv_name') - - class Meta: - model = Srv - fields = ( - 'service', - 'protocole', - 'extension', - 'ttl', - 'priority', - 'weight', - 'port', - 'target', - 'srv_entry' - ) - - @staticmethod - def get_extension_name(obj): - """ The name of the associated extension """ - return str(obj.extension.name) - - @staticmethod - def get_srv_name(obj): - """ The string representation of the entry to add to the DNS """ - return str(obj.dns_entry) - - -class NsSerializer(serializers.ModelSerializer): - """Serialisation d'un NS : la zone, l'entrée ns complète et le serveur - ns sont évalués à part""" - zone = serializers.SerializerMethodField('get_zone_name') - ns = serializers.SerializerMethodField('get_domain_name') - ns_entry = serializers.SerializerMethodField('get_text_name') - - class Meta: - model = Ns - fields = ('zone', 'ns', 'ns_entry') - - @staticmethod - def get_zone_name(obj): - """ The name of the associated zone """ - return obj.zone.name - - @staticmethod - def get_domain_name(obj): - """ The name of the associated NS target """ - return str(obj.ns) - - @staticmethod - def get_text_name(obj): - """ The string representation of the entry to add to the DNS """ - return str(obj.dns_entry) - - -class DomainSerializer(serializers.ModelSerializer): - """Serialisation d'un domain, extension, cname sont des foreign_key, - et l'entrée complète, sont évalués à part""" - extension = serializers.SerializerMethodField('get_zone_name') - cname = serializers.SerializerMethodField('get_alias_name') - cname_entry = serializers.SerializerMethodField('get_cname_name') - - class Meta: - model = Domain - fields = ('name', 'extension', 'cname', 'cname_entry') - - @staticmethod - def get_zone_name(obj): - """ The name of the associated zone """ - return obj.extension.name - - @staticmethod - def get_alias_name(obj): - """ The name of the associated alias """ - return str(obj.cname) - - @staticmethod - def get_cname_name(obj): - """ The name of the associated CNAME target """ - return str(obj.dns_entry) - - -class ServicesSerializer(serializers.ModelSerializer): - """Evaluation d'un Service, et serialisation""" - server = serializers.SerializerMethodField('get_server_name') - service = serializers.SerializerMethodField('get_service_name') - need_regen = serializers.SerializerMethodField('get_regen_status') - - class Meta: - model = Service_link - fields = ('server', 'service', 'need_regen') - - @staticmethod - def get_server_name(obj): - """ The name of the associated server """ - return str(obj.server.domain.name) - - @staticmethod - def get_service_name(obj): - """ The name of the service name """ - return str(obj.service) - - @staticmethod - def get_regen_status(obj): - """ The string representation of the regen status """ - return obj.need_regen() - - -class OuverturePortsSerializer(serializers.Serializer): - """Serialisation de l'ouverture des ports""" - ipv4 = serializers.SerializerMethodField() - ipv6 = serializers.SerializerMethodField() - - def create(self, validated_data): - """ Creates a new object based on the un-serialized data. - Used to implement an abstract inherited method """ - pass - - def update(self, instance, validated_data): - """ Updates an object based on the un-serialized data. - Used to implement an abstract inherited method """ - pass - - @staticmethod - def get_ipv4(): - """ The representation of the policy for the IPv4 addresses """ - return { - i.ipv4.ipv4: { - "tcp_in": [j.tcp_ports_in() for j in i.port_lists.all()], - "tcp_out": [j.tcp_ports_out()for j in i.port_lists.all()], - "udp_in": [j.udp_ports_in() for j in i.port_lists.all()], - "udp_out": [j.udp_ports_out() for j in i.port_lists.all()], - } - for i in Interface.objects.all() if i.ipv4 + fields = ('name', 'surname', 'pseudo', 'email', 'school', 'shell', + 'comment', 'state', 'registered', 'telephone', #'room', + 'solde', 'access', 'end_access', 'uid', 'api_url') + extra_kwargs = { + 'school': {'view_name': 'api:school-detail'}, + 'shell': {'view_name': 'api:shell-detail'}, + #'room': {'view_name': 'api:room-detail'}, + 'api_url': {'view_name': 'api:adherent-detail'} } - @staticmethod - def get_ipv6(): - """ The representation of the policy for the IPv6 addresses """ - return { - i.ipv6: { - "tcp_in": [j.tcp_ports_in() for j in i.port_lists.all()], - "tcp_out": [j.tcp_ports_out()for j in i.port_lists.all()], - "udp_in": [j.udp_ports_in() for j in i.port_lists.all()], - "udp_out": [j.udp_ports_out() for j in i.port_lists.all()], - } - for i in Interface.objects.all() if i.ipv6 + +class ServiceUserSerializer(serializers.HyperlinkedModelSerializer): + class Meta: + model = ServiceUser + fields = ('pseudo', 'access_group', 'comment', 'api_url') + extra_kwargs = { + 'api_url': {'view_name': 'api:serviceuser-detail'} } + + +class SchoolSerializer(serializers.HyperlinkedModelSerializer): + class Meta: + model = School + fields = ('name', 'api_url') + extra_kwargs = { + 'api_url': {'view_name': 'api:school-detail'} + } + + +class ListRightSerializer(serializers.HyperlinkedModelSerializer): + class Meta: + model = ListRight + fields = ('unix_name', 'gid', 'critical', 'details', 'api_url') + extra_kwargs = { + 'api_url': {'view_name': 'api:listright-detail'} + } + + +class ShellSerializer(serializers.HyperlinkedModelSerializer): + class Meta: + model = ListShell + fields = ('shell', 'api_url') + extra_kwargs = { + 'api_url': {'view_name': 'api:shell-detail'} + } + + +class BanSerializer(serializers.HyperlinkedModelSerializer): + active = serializers.BooleanField(source='is_active') + + class Meta: + model = Ban + fields = ('user', 'raison', 'date_start', 'date_end', 'state', + 'active', 'api_url') + extra_kwargs = { + 'user': {'view_name': 'api:user-detail'}, + 'api_url': {'view_name': 'api:ban-detail'} + } + + +class WhitelistSerializer(serializers.HyperlinkedModelSerializer): + active = serializers.BooleanField(source='is_active') + + class Meta: + model = Whitelist + fields = ('user', 'raison', 'date_start', 'date_end', 'active', 'api_url') + extra_kwargs = { + 'user': {'view_name': 'api:user-detail'}, + 'api_url': {'view_name': 'api:whitelist-detail'} + } + + + +# class ServiceLinkSerializer(serializers.ModelSerializer): +# """ Serializer for the ServiceLink objects """ +# +# name = serializers.CharField(source='service.service_type') +# +# class Meta: +# model = Service_link +# fields = ('name',) +# +# +# class MailingSerializer(serializers.ModelSerializer): +# """ Serializer to build Mailing objects """ +# +# name = serializers.CharField(source='pseudo') +# +# class Meta: +# model = Club +# fields = ('name',) +# +# +# class MailingMemberSerializer(serializers.ModelSerializer): +# """ Serializer fot the Adherent objects (who belong to a +# Mailing) """ +# +# class Meta: +# model = Adherent +# fields = ('email',) +# +# +# class IpTypeField(serializers.RelatedField): +# """ Serializer for an IpType object field """ +# +# def to_representation(self, value): +# return value.type +# +# def to_internal_value(self, data): +# pass +# +# +# class IpListSerializer(serializers.ModelSerializer): +# """ Serializer for an Ipv4List obejct using the IpType serialization """ +# +# ip_type = IpTypeField(read_only=True) +# +# class Meta: +# model = IpList +# fields = ('ipv4', 'ip_type') +# +# +# class Ipv6ListSerializer(serializers.ModelSerializer): +# """ Serializer for an Ipv6List object """ +# +# class Meta: +# model = Ipv6List +# fields = ('ipv6', 'slaac_ip') +# +# +# class InterfaceSerializer(serializers.ModelSerializer): +# """ Serializer for an Interface object. Use SerializerMethodField +# to get ForeignKey values """ +# +# ipv4 = IpListSerializer(read_only=True) +# # TODO : use serializer.RelatedField to avoid duplicate code +# mac_address = serializers.SerializerMethodField('get_macaddress') +# domain = serializers.SerializerMethodField('get_dns') +# extension = serializers.SerializerMethodField('get_interface_extension') +# +# class Meta: +# model = Interface +# fields = ('ipv4', 'mac_address', 'domain', 'extension') +# +# @staticmethod +# def get_dns(obj): +# """ The name of the associated DNS object """ +# return obj.domain.name +# +# @staticmethod +# def get_interface_extension(obj): +# """ The name of the associated Interface object """ +# return obj.domain.extension.name +# +# @staticmethod +# def get_macaddress(obj): +# """ The string representation of the associated MAC address """ +# return str(obj.mac_address) +# +# +# class FullInterfaceSerializer(serializers.ModelSerializer): +# """ Serializer for an Interface obejct. Use SerializerMethodField +# to get ForeignKey values """ +# +# ipv4 = IpListSerializer(read_only=True) +# ipv6 = Ipv6ListSerializer(read_only=True, many=True) +# # TODO : use serializer.RelatedField to avoid duplicate code +# mac_address = serializers.SerializerMethodField('get_macaddress') +# domain = serializers.SerializerMethodField('get_dns') +# extension = serializers.SerializerMethodField('get_interface_extension') +# +# class Meta: +# model = Interface +# fields = ('ipv4', 'ipv6', 'mac_address', 'domain', 'extension') +# +# @staticmethod +# def get_dns(obj): +# """ The name of the associated DNS object """ +# return obj.domain.name +# +# @staticmethod +# def get_interface_extension(obj): +# """ The name of the associated Extension object """ +# return obj.domain.extension.name +# +# @staticmethod +# def get_macaddress(obj): +# """ The string representation of the associated MAC address """ +# return str(obj.mac_address) +# +# +# class ExtensionNameField(serializers.RelatedField): +# """ Serializer for Extension object field """ +# +# def to_representation(self, value): +# return value.name +# +# def to_internal_value(self, data): +# pass +# +# +# class TypeSerializer(serializers.ModelSerializer): +# """ Serializer for an IpType object. Use SerializerMethodField to +# get ForeignKey values """ +# +# extension = ExtensionNameField(read_only=True) +# ouverture_ports_tcp_in = serializers\ +# .SerializerMethodField('get_port_policy_input_tcp') +# ouverture_ports_tcp_out = serializers\ +# .SerializerMethodField('get_port_policy_output_tcp') +# ouverture_ports_udp_in = serializers\ +# .SerializerMethodField('get_port_policy_input_udp') +# ouverture_ports_udp_out = serializers\ +# .SerializerMethodField('get_port_policy_output_udp') +# +# class Meta: +# model = IpType +# fields = ('type', 'extension', 'domaine_ip_start', 'domaine_ip_stop', +# 'prefix_v6', +# 'ouverture_ports_tcp_in', 'ouverture_ports_tcp_out', +# 'ouverture_ports_udp_in', 'ouverture_ports_udp_out',) +# +# @staticmethod +# def get_port_policy(obj, protocole, io): +# """ Generic utility function to get the policy for a given +# port, protocole and IN or OUT """ +# if obj.ouverture_ports is None: +# return [] +# return map( +# str, +# obj.ouverture_ports.ouvertureport_set.filter( +# protocole=protocole +# ).filter(io=io) +# ) +# +# def get_port_policy_input_tcp(self, obj): +# """Renvoie la liste des ports ouverts en entrée tcp""" +# return self.get_port_policy(obj, OuverturePort.TCP, OuverturePort.IN) +# +# def get_port_policy_output_tcp(self, obj): +# """Renvoie la liste des ports ouverts en sortie tcp""" +# return self.get_port_policy(obj, OuverturePort.TCP, OuverturePort.OUT) +# +# def get_port_policy_input_udp(self, obj): +# """Renvoie la liste des ports ouverts en entrée udp""" +# return self.get_port_policy(obj, OuverturePort.UDP, OuverturePort.IN) +# +# def get_port_policy_output_udp(self, obj): +# """Renvoie la liste des ports ouverts en sortie udp""" +# return self.get_port_policy(obj, OuverturePort.UDP, OuverturePort.OUT) +# +# +# class ExtensionSerializer(serializers.ModelSerializer): +# """Serialisation d'une extension : origin_ip et la zone sont +# des foreign_key donc evalués en get_...""" +# origin = serializers.SerializerMethodField('get_origin_ip') +# zone_entry = serializers.SerializerMethodField('get_zone_name') +# soa = serializers.SerializerMethodField('get_soa_data') +# +# class Meta: +# model = Extension +# fields = ('name', 'origin', 'origin_v6', 'zone_entry', 'soa') +# +# @staticmethod +# def get_origin_ip(obj): +# """ The IP of the associated origin for the zone """ +# return obj.origin.ipv4 +# +# @staticmethod +# def get_zone_name(obj): +# """ The name of the associated zone """ +# return str(obj.dns_entry) +# +# @staticmethod +# def get_soa_data(obj): +# """ The representation of the associated SOA """ +# return {'mail': obj.soa.dns_soa_mail, 'param': obj.soa.dns_soa_param} +# +# +# class MxSerializer(serializers.ModelSerializer): +# """Serialisation d'un MX, evaluation du nom, de la zone +# et du serveur cible, etant des foreign_key""" +# name = serializers.SerializerMethodField('get_entry_name') +# zone = serializers.SerializerMethodField('get_zone_name') +# mx_entry = serializers.SerializerMethodField('get_mx_name') +# +# class Meta: +# model = Mx +# fields = ('zone', 'priority', 'name', 'mx_entry') +# +# @staticmethod +# def get_entry_name(obj): +# """ The name of the DNS MX entry """ +# return str(obj.name) +# +# @staticmethod +# def get_zone_name(obj): +# """ The name of the associated zone of the MX record """ +# return obj.zone.name +# +# @staticmethod +# def get_mx_name(obj): +# """ The string representation of the entry to add to the DNS """ +# return str(obj.dns_entry) +# +# +# class TxtSerializer(serializers.ModelSerializer): +# """Serialisation d'un txt : zone cible et l'entrée txt +# sont evaluées à part""" +# zone = serializers.SerializerMethodField('get_zone_name') +# txt_entry = serializers.SerializerMethodField('get_txt_name') +# +# class Meta: +# model = Txt +# fields = ('zone', 'txt_entry', 'field1', 'field2') +# +# @staticmethod +# def get_zone_name(obj): +# """ The name of the associated zone """ +# return str(obj.zone.name) +# +# @staticmethod +# def get_txt_name(obj): +# """ The string representation of the entry to add to the DNS """ +# return str(obj.dns_entry) +# +# +# class SrvSerializer(serializers.ModelSerializer): +# """Serialisation d'un srv : zone cible et l'entrée txt""" +# extension = serializers.SerializerMethodField('get_extension_name') +# srv_entry = serializers.SerializerMethodField('get_srv_name') +# +# class Meta: +# model = Srv +# fields = ( +# 'service', +# 'protocole', +# 'extension', +# 'ttl', +# 'priority', +# 'weight', +# 'port', +# 'target', +# 'srv_entry' +# ) +# +# @staticmethod +# def get_extension_name(obj): +# """ The name of the associated extension """ +# return str(obj.extension.name) +# +# @staticmethod +# def get_srv_name(obj): +# """ The string representation of the entry to add to the DNS """ +# return str(obj.dns_entry) +# +# +# class NsSerializer(serializers.ModelSerializer): +# """Serialisation d'un NS : la zone, l'entrée ns complète et le serveur +# ns sont évalués à part""" +# zone = serializers.SerializerMethodField('get_zone_name') +# ns = serializers.SerializerMethodField('get_domain_name') +# ns_entry = serializers.SerializerMethodField('get_text_name') +# +# class Meta: +# model = Ns +# fields = ('zone', 'ns', 'ns_entry') +# +# @staticmethod +# def get_zone_name(obj): +# """ The name of the associated zone """ +# return obj.zone.name +# +# @staticmethod +# def get_domain_name(obj): +# """ The name of the associated NS target """ +# return str(obj.ns) +# +# @staticmethod +# def get_text_name(obj): +# """ The string representation of the entry to add to the DNS """ +# return str(obj.dns_entry) +# +# +# class DomainSerializer(serializers.ModelSerializer): +# """Serialisation d'un domain, extension, cname sont des foreign_key, +# et l'entrée complète, sont évalués à part""" +# extension = serializers.SerializerMethodField('get_zone_name') +# cname = serializers.SerializerMethodField('get_alias_name') +# cname_entry = serializers.SerializerMethodField('get_cname_name') +# +# class Meta: +# model = Domain +# fields = ('name', 'extension', 'cname', 'cname_entry') +# +# @staticmethod +# def get_zone_name(obj): +# """ The name of the associated zone """ +# return obj.extension.name +# +# @staticmethod +# def get_alias_name(obj): +# """ The name of the associated alias """ +# return str(obj.cname) +# +# @staticmethod +# def get_cname_name(obj): +# """ The name of the associated CNAME target """ +# return str(obj.dns_entry) +# +# +# class ServicesSerializer(serializers.ModelSerializer): +# """Evaluation d'un Service, et serialisation""" +# server = serializers.SerializerMethodField('get_server_name') +# service = serializers.SerializerMethodField('get_service_name') +# need_regen = serializers.SerializerMethodField('get_regen_status') +# +# class Meta: +# model = Service_link +# fields = ('server', 'service', 'need_regen') +# +# @staticmethod +# def get_server_name(obj): +# """ The name of the associated server """ +# return str(obj.server.domain.name) +# +# @staticmethod +# def get_service_name(obj): +# """ The name of the service name """ +# return str(obj.service) +# +# @staticmethod +# def get_regen_status(obj): +# """ The string representation of the regen status """ +# return obj.need_regen() +# +# +# class OuverturePortsSerializer(serializers.Serializer): +# """Serialisation de l'ouverture des ports""" +# ipv4 = serializers.SerializerMethodField() +# ipv6 = serializers.SerializerMethodField() +# +# def create(self, validated_data): +# """ Creates a new object based on the un-serialized data. +# Used to implement an abstract inherited method """ +# pass +# +# def update(self, instance, validated_data): +# """ Updates an object based on the un-serialized data. +# Used to implement an abstract inherited method """ +# pass +# +# @staticmethod +# def get_ipv4(): +# """ The representation of the policy for the IPv4 addresses """ +# return { +# i.ipv4.ipv4: { +# "tcp_in": [j.tcp_ports_in() for j in i.port_lists.all()], +# "tcp_out": [j.tcp_ports_out()for j in i.port_lists.all()], +# "udp_in": [j.udp_ports_in() for j in i.port_lists.all()], +# "udp_out": [j.udp_ports_out() for j in i.port_lists.all()], +# } +# for i in Interface.objects.all() if i.ipv4 +# } +# +# @staticmethod +# def get_ipv6(): +# """ The representation of the policy for the IPv6 addresses """ +# return { +# i.ipv6: { +# "tcp_in": [j.tcp_ports_in() for j in i.port_lists.all()], +# "tcp_out": [j.tcp_ports_out()for j in i.port_lists.all()], +# "udp_in": [j.udp_ports_in() for j in i.port_lists.all()], +# "udp_out": [j.udp_ports_out() for j in i.port_lists.all()], +# } +# for i in Interface.objects.all() if i.ipv6 +# } diff --git a/api/urls.py b/api/urls.py index 9b77f308..662dd8c4 100644 --- a/api/urls.py +++ b/api/urls.py @@ -24,48 +24,60 @@ Urls de l'api, pointant vers les fonctions de views from __future__ import unicode_literals -from django.conf.urls import url +from django.conf.urls import url, include +from rest_framework.routers import DefaultRouter from . import views +router = DefaultRouter() +router.register(r'users', views.UserViewSet) +router.register(r'clubs', views.ClubViewSet) +router.register(r'adherents', views.AdherentViewSet) +router.register(r'serviceusers', views.ServiceUserViewSet) +router.register(r'schools', views.SchoolViewSet) +router.register(r'listrights', views.ListRightViewSet) +router.register(r'shells', views.ShellViewSet, 'shell') +router.register(r'bans', views.BanViewSet) +router.register(r'whitelists', views.WhitelistViewSet) urlpatterns = [ - # Services - url(r'^services/$', views.services), - url( - r'^services/(?P\w+)/(?P\w+)/regen/$', - views.services_server_service_regen - ), - url(r'^services/(?P\w+)/$', views.services_server), - - # DNS - url(r'^dns/mac-ip-dns/$', views.dns_mac_ip_dns), - url(r'^dns/alias/$', views.dns_alias), - url(r'^dns/corresp/$', views.dns_corresp), - url(r'^dns/mx/$', views.dns_mx), - url(r'^dns/ns/$', views.dns_ns), - url(r'^dns/txt/$', views.dns_txt), - url(r'^dns/srv/$', views.dns_srv), - url(r'^dns/zones/$', views.dns_zones), - - # Unifi controler AP names - url(r'^unifi/ap_names/$', views.accesspoint_ip_dns), - - # Firewall - url(r'^firewall/ouverture_ports/$', views.firewall_ouverture_ports), - - # DHCP - url(r'^dhcp/mac-ip/$', views.dhcp_mac_ip), - - # Mailings - url(r'^mailing/standard/$', views.mailing_standard), - url( - r'^mailing/standard/(?P\w+)/members/$', - views.mailing_standard_ml_members - ), - url(r'^mailing/club/$', views.mailing_club), - url( - r'^mailing/club/(?P\w+)/members/$', - views.mailing_club_ml_members - ), + url(r'^', include(router.urls)), +# # Services +# url(r'^services/$', views.services), +# url( +# r'^services/(?P\w+)/(?P\w+)/regen/$', +# views.services_server_service_regen +# ), +# url(r'^services/(?P\w+)/$', views.services_server), +# +# # DNS +# url(r'^dns/mac-ip-dns/$', views.dns_mac_ip_dns), +# url(r'^dns/alias/$', views.dns_alias), +# url(r'^dns/corresp/$', views.dns_corresp), +# url(r'^dns/mx/$', views.dns_mx), +# url(r'^dns/ns/$', views.dns_ns), +# url(r'^dns/txt/$', views.dns_txt), +# url(r'^dns/srv/$', views.dns_srv), +# url(r'^dns/zones/$', views.dns_zones), +# +# # Unifi controler AP names +# url(r'^unifi/ap_names/$', views.accesspoint_ip_dns), +# +# # Firewall +# url(r'^firewall/ouverture_ports/$', views.firewall_ouverture_ports), +# +# # DHCP +# url(r'^dhcp/mac-ip/$', views.dhcp_mac_ip), +# +# # Mailings +# url(r'^mailing/standard/$', views.mailing_standard), +# url( +# r'^mailing/standard/(?P\w+)/members/$', +# views.mailing_standard_ml_members +# ), +# url(r'^mailing/club/$', views.mailing_club), +# url( +# r'^mailing/club/(?P\w+)/members/$', +# views.mailing_club_ml_members +# ), ] diff --git a/api/utils.py b/api/utils.py deleted file mode 100644 index d65cff44..00000000 --- a/api/utils.py +++ /dev/null @@ -1,123 +0,0 @@ -# Re2o est un logiciel d'administration développé initiallement au rezometz. Il -# se veut agnostique au réseau considéré, de manière à être installable en -# quelques clics. -# -# Copyright © 2018 Maël Kervella -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License along -# with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -"""api.utils. - -Set of various and usefull functions for the API app -""" - -from rest_framework.renderers import JSONRenderer -from django.http import HttpResponse - - -class JSONResponse(HttpResponse): - """A JSON response that can be send as an HTTP response. - Usefull in case of REST API. - """ - - def __init__(self, data, **kwargs): - """Initialisz a JSONResponse object. - - Args: - data: the data to render as JSON (often made of lists, dicts, - strings, boolean and numbers). See `JSONRenderer.render(data)` for - further details. - - Creates: - An HTTPResponse containing the data in JSON format. - """ - - content = JSONRenderer().render(data) - kwargs['content_type'] = 'application/json' - super(JSONResponse, self).__init__(content, **kwargs) - - -class JSONError(JSONResponse): - """A JSON response when the request failed. - """ - - def __init__(self, error_msg, data=None, **kwargs): - """Initialise a JSONError object. - - Args: - error_msg: A message explaining where the error is. - data: An optional field for further data to send along. - - Creates: - A JSONResponse containing a field `status` set to `error` and a - field `reason` containing `error_msg`. If `data` argument has been - given, a field `data` containing it is added to the JSON response. - """ - - response = { - 'status': 'error', - 'reason': error_msg - } - if data is not None: - response['data'] = data - super(JSONError, self).__init__(response, **kwargs) - - -class JSONSuccess(JSONResponse): - """A JSON response when the request suceeded. - """ - - def __init__(self, data=None, **kwargs): - """Initialise a JSONSucess object. - - Args: - error_msg: A message explaining where the error is. - data: An optional field for further data to send along. - - Creates: - A JSONResponse containing a field `status` set to `sucess`. If - `data` argument has been given, a field `data` containing it is - added to the JSON response. - """ - - response = { - 'status': 'success', - } - if data is not None: - response['data'] = data - super(JSONSuccess, self).__init__(response, **kwargs) - - -def accept_method(methods): - """Decorator to set a list of accepted request method. - Check if the method used is accepted. If not, send a NotAllowed response. - """ - - def decorator(view): - """The decorator to use on a specific view - """ - def wrapper(request, *args, **kwargs): - """The wrapper used for a specific request - """ - if request.method in methods: - return view(request, *args, **kwargs) - else: - return JSONError( - 'Invalid request method. Request methods authorize are ' + - str(methods) - ) - return view(request, *args, **kwargs) - return wrapper - return decorator diff --git a/api/views.py b/api/views.py index eda4dd59..00389eb7 100644 --- a/api/views.py +++ b/api/views.py @@ -27,12 +27,25 @@ HTML pages such as the login and index pages for a better integration. from django.contrib.auth.decorators import login_required, permission_required from django.views.decorators.csrf import csrf_exempt +from rest_framework.response import Response +from rest_framework import status, mixins, generics, viewsets + from re2o.utils import ( all_has_access, all_active_assigned_interfaces, filter_active_interfaces ) -from users.models import Club +from users.models import ( + User, + Club, + Adherent, + ServiceUser, + School, + ListRight, + ListShell, + Ban, + Whitelist +) from machines.models import ( Service_link, Service, @@ -49,569 +62,609 @@ from machines.models import ( ) from .serializers import ( - ServicesSerializer, - ServiceLinkSerializer, - FullInterfaceSerializer, - DomainSerializer, - TypeSerializer, - MxSerializer, - NsSerializer, - TxtSerializer, - SrvSerializer, - ExtensionSerializer, - InterfaceSerializer, - MailingMemberSerializer, - MailingSerializer + UserSerializer, + ClubSerializer, + AdherentSerializer, + ServiceUserSerializer, + SchoolSerializer, + ListRightSerializer, + ShellSerializer, + BanSerializer, + WhitelistSerializer ) -from .utils import JSONError, JSONSuccess, accept_method -@csrf_exempt -@login_required -@permission_required('machines.serveur') -@accept_method(['GET']) -def services(_request): - """The list of the different services and servers couples - - Return: - GET: - A JSONSuccess response with a field `data` containing: - * a list of dictionnaries (one for each service-server couple) - containing: - * a field `server`: the server name - * a field `service`: the service name - * a field `need_regen`: does the service need a regeneration ? - """ - - service_link = (Service_link.objects.all() - .select_related('server__domain') - .select_related('service')) - seria = ServicesSerializer(service_link, many=True) - return JSONSuccess(seria.data) - - -@csrf_exempt -@login_required -@permission_required('machines.serveur') -@accept_method(['GET', 'POST']) -def services_server_service_regen(request, server_name, service_name): - """The status of a particular service linked to a particular server. - Mark the service as regenerated if POST used. - - Returns: - GET: - A JSONSucess response with a field `data` containing: - * a field `need_regen`: does the service need a regeneration ? - - POST: - An empty JSONSuccess response. - """ - - query = Service_link.objects.filter( - service__in=Service.objects.filter(service_type=service_name), - server__in=Interface.objects.filter( - domain__in=Domain.objects.filter(name=server_name) - ) - ) - if not query: - return JSONError("This service is not active for this server") - - service = query.first() - if request.method == 'GET': - return JSONSuccess({'need_regen': service.need_regen()}) - else: - service.done_regen() - return JSONSuccess() - - -@csrf_exempt -@login_required -@permission_required('machines.serveur') -@accept_method(['GET']) -def services_server(_request, server_name): - """The list of services attached to a specific server - - Returns: - GET: - A JSONSuccess response with a field `data` containing: - * a list of dictionnaries (one for each service) containing: - * a field `name`: the name of a service - """ - - query = Service_link.objects.filter( - server__in=Interface.objects.filter( - domain__in=Domain.objects.filter(name=server_name) - ) - ) - if not query: - return JSONError("This service is not active for this server") - - services_objects = query.all() - seria = ServiceLinkSerializer(services_objects, many=True) - return JSONSuccess(seria.data) - - -@csrf_exempt -@login_required -@permission_required('machines.serveur') -@accept_method(['GET']) -def dns_mac_ip_dns(_request): - """The list of all active interfaces with all the associated infos - (MAC, IP, IpType, DNS name and associated zone extension) - - Returns: - GET: - A JSON Success response with a field `data` containing: - * a list of dictionnaries (one for each interface) containing: - * a field `ipv4` containing: - * a field `ipv4`: the ip for this interface - * a field `ip_type`: the name of the IpType of this interface - * a field `ipv6` containing `null` if ipv6 is deactivated else: - * a field `ipv6`: the ip for this interface - * a field `ip_type`: the name of the IpType of this interface - * a field `mac_address`: the MAC of this interface - * a field `domain`: the DNS name for this interface - * a field `extension`: the extension for the DNS zone of this - interface - """ - - interfaces = all_active_assigned_interfaces(full=True) - seria = FullInterfaceSerializer(interfaces, many=True) - return JSONSuccess(seria.data) - - -@csrf_exempt -@login_required -@permission_required('machines.serveur') -@accept_method(['GET']) -def dns_alias(_request): - """The list of all the alias used and the DNS info associated - - Returns: - GET: - A JSON Success response with a field `data` containing: - * a list of dictionnaries (one for each alias) containing: - * a field `name`: the alias used - * a field `cname`: the target of the alias (real name of the - interface) - * a field `cname_entry`: the entry to write in the DNS to have - the alias - * a field `extension`: the extension for the DNS zone of this - interface - """ - - alias = (Domain.objects - .filter(interface_parent=None) - .filter( - cname__in=Domain.objects.filter( - interface_parent__in=Interface.objects.exclude(ipv4=None) - ) - ) - .select_related('extension') - .select_related('cname__extension')) - seria = DomainSerializer(alias, many=True) - return JSONSuccess(seria.data) - - -@csrf_exempt -@login_required -@permission_required('machines.serveur') -@accept_method(['GET']) -def accesspoint_ip_dns(_request): - """The list of all active interfaces with all the associated infos - (MAC, IP, IpType, DNS name and associated zone extension) - - Only display access points. Use to gen unifi controler names - - Returns: - GET: - A JSON Success response with a field `data` containing: - * a list of dictionnaries (one for each interface) containing: - * a field `ipv4` containing: - * a field `ipv4`: the ip for this interface - * a field `ip_type`: the name of the IpType of this interface - * a field `ipv6` containing `null` if ipv6 is deactivated else: - * a field `ipv6`: the ip for this interface - * a field `ip_type`: the name of the IpType of this interface - * a field `mac_address`: the MAC of this interface - * a field `domain`: the DNS name for this interface - * a field `extension`: the extension for the DNS zone of this - interface - """ - - interfaces = (all_active_assigned_interfaces(full=True) - .filter(machine__accesspoint__isnull=False)) - seria = FullInterfaceSerializer(interfaces, many=True) - return JSONSuccess(seria.data) - - -@csrf_exempt -@login_required -@permission_required('machines.serveur') -@accept_method(['GET']) -def dns_corresp(_request): - """The list of the IpTypes possible with the infos about each - - Returns: - GET: - A JSON Success response with a field `data` containing: - * a list of dictionnaries (one for each IpType) containing: - * a field `type`: the name of the type - * a field `extension`: the DNS extension associated - * a field `domain_ip_start`: the first ip to use for this type - * a field `domain_ip_stop`: the last ip to use for this type - * a field `prefix_v6`: `null` if IPv6 is deactivated else the - prefix to use - * a field `ouverture_ports_tcp_in`: the policy for TCP IN ports - * a field `ouverture_ports_tcp_out`: the policy for TCP OUT ports - * a field `ouverture_ports_udp_in`: the policy for UDP IN ports - * a field `ouverture_ports_udp_out`: the policy for UDP OUT ports - """ - - ip_type = IpType.objects.all().select_related('extension') - seria = TypeSerializer(ip_type, many=True) - return JSONSuccess(seria.data) - - -@csrf_exempt -@login_required -@permission_required('machines.serveur') -@accept_method(['GET']) -def dns_mx(_request): - """The list of MX record to add to the DNS - - Returns: - GET: - A JSON Success response with a field `data` containing: - * a list of dictionnaries (one for each MX record) containing: - * a field `zone`: the extension for the concerned zone - * a field `priority`: the priority to use - * a field `name`: the name of the target - * a field `mx_entry`: the full entry to add in the DNS for this - MX record - """ - - mx = (Mx.objects.all() - .select_related('zone') - .select_related('name__extension')) - seria = MxSerializer(mx, many=True) - return JSONSuccess(seria.data) - - -@csrf_exempt -@login_required -@permission_required('machines.serveur') -@accept_method(['GET']) -def dns_ns(_request): - """The list of NS record to add to the DNS - - Returns: - GET: - A JSON Success response with a field `data` containing: - * a list of dictionnaries (one for each NS record) containing: - * a field `zone`: the extension for the concerned zone - * a field `ns`: the DNS name for the NS server targeted - * a field `ns_entry`: the full entry to add in the DNS for this - NS record - """ - - ns = (Ns.objects - .exclude( - ns__in=Domain.objects.filter( - interface_parent__in=Interface.objects.filter(ipv4=None) - ) - ) - .select_related('zone') - .select_related('ns__extension')) - seria = NsSerializer(ns, many=True) - return JSONSuccess(seria.data) - - -@csrf_exempt -@login_required -@permission_required('machines.serveur') -@accept_method(['GET']) -def dns_txt(_request): - """The list of TXT record to add to the DNS - - Returns: - GET: - A JSON Success response with a field `data` containing: - * a list of dictionnaries (one for each TXT record) containing: - * a field `zone`: the extension for the concerned zone - * a field `field1`: the first field in the record (target) - * a field `field2`: the second field in the record (value) - * a field `txt_entry`: the full entry to add in the DNS for this - TXT record - """ - - txt = Txt.objects.all().select_related('zone') - seria = TxtSerializer(txt, many=True) - return JSONSuccess(seria.data) - - -@csrf_exempt -@login_required -@permission_required('machines.serveur') -@accept_method(['GET']) -def dns_srv(_request): - """The list of SRV record to add to the DNS - - Returns: - GET: - A JSON Success response with a field `data` containing: - * a list of dictionnaries (one for each SRV record) containing: - * a field `extension`: the extension for the concerned zone - * a field `service`: the name of the service concerned - * a field `protocole`: the name of the protocol to use - * a field `ttl`: the Time To Live to use - * a field `priority`: the priority for this service - * a field `weight`: the weight for same priority entries - * a field `port`: the port targeted - * a field `target`: the interface targeted by this service - * a field `srv_entry`: the full entry to add in the DNS for this - SRV record - """ - - srv = (Srv.objects.all() - .select_related('extension') - .select_related('target__extension')) - seria = SrvSerializer(srv, many=True) - return JSONSuccess(seria.data) - - -@csrf_exempt -@login_required -@permission_required('machines.serveur') -@accept_method(['GET']) -def dns_zones(_request): - """The list of the zones managed - - Returns: - GET: - A JSON Success response with a field `data` containing: - * a list of dictionnaries (one for each zone) containing: - * a field `name`: the extension for the zone - * a field `origin`: the server IPv4 for the orgin of the zone - * a field `origin_v6`: `null` if ipv6 is deactivated else the - server IPv6 for the origin of the zone - * a field `soa` containing: - * a field `mail` containing the mail to contact in case of - problem with the zone - * a field `param` containing the full soa paramters to use - in the DNS for this zone - * a field `zone_entry`: the full entry to add in the DNS for the - origin of the zone - """ - - zones = Extension.objects.all().select_related('origin') - seria = ExtensionSerializer(zones, many=True) - return JSONSuccess(seria.data) - - -@csrf_exempt -@login_required -@permission_required('machines.serveur') -@accept_method(['GET']) -def firewall_ouverture_ports(_request): - """The list of the ports authorized to be openned by the firewall - - Returns: - GET: - A JSONSuccess response with a `data` field containing: - * a field `ipv4` containing: - * a field `tcp_in` containing: - * a list of port number where ipv4 tcp in should be ok - * a field `tcp_out` containing: - * a list of port number where ipv4 tcp ou should be ok - * a field `udp_in` containing: - * a list of port number where ipv4 udp in should be ok - * a field `udp_out` containing: - * a list of port number where ipv4 udp out should be ok - * a field `ipv6` containing: - * a field `tcp_in` containing: - * a list of port number where ipv6 tcp in should be ok - * a field `tcp_out` containing: - * a list of port number where ipv6 tcp ou should be ok - * a field `udp_in` containing: - * a list of port number where ipv6 udp in should be ok - * a field `udp_out` containing: - * a list of port number where ipv6 udp out should be ok - """ - - r = {'ipv4': {}, 'ipv6': {}} - for o in (OuverturePortList.objects.all() - .prefetch_related('ouvertureport_set') - .prefetch_related('interface_set', 'interface_set__ipv4')): - pl = { - "tcp_in": set(map( - str, - o.ouvertureport_set.filter( - protocole=OuverturePort.TCP, - io=OuverturePort.IN - ) - )), - "tcp_out": set(map( - str, - o.ouvertureport_set.filter( - protocole=OuverturePort.TCP, - io=OuverturePort.OUT - ) - )), - "udp_in": set(map( - str, - o.ouvertureport_set.filter( - protocole=OuverturePort.UDP, - io=OuverturePort.IN - ) - )), - "udp_out": set(map( - str, - o.ouvertureport_set.filter( - protocole=OuverturePort.UDP, - io=OuverturePort.OUT - ) - )), - } - for i in filter_active_interfaces(o.interface_set): - if i.may_have_port_open(): - d = r['ipv4'].get(i.ipv4.ipv4, {}) - d["tcp_in"] = (d.get("tcp_in", set()) - .union(pl["tcp_in"])) - d["tcp_out"] = (d.get("tcp_out", set()) - .union(pl["tcp_out"])) - d["udp_in"] = (d.get("udp_in", set()) - .union(pl["udp_in"])) - d["udp_out"] = (d.get("udp_out", set()) - .union(pl["udp_out"])) - r['ipv4'][i.ipv4.ipv4] = d - if i.ipv6(): - for ipv6 in i.ipv6(): - d = r['ipv6'].get(ipv6.ipv6, {}) - d["tcp_in"] = (d.get("tcp_in", set()) - .union(pl["tcp_in"])) - d["tcp_out"] = (d.get("tcp_out", set()) - .union(pl["tcp_out"])) - d["udp_in"] = (d.get("udp_in", set()) - .union(pl["udp_in"])) - d["udp_out"] = (d.get("udp_out", set()) - .union(pl["udp_out"])) - r['ipv6'][ipv6.ipv6] = d - return JSONSuccess(r) - - -@csrf_exempt -@login_required -@permission_required('machines.serveur') -@accept_method(['GET']) -def dhcp_mac_ip(_request): - """The list of all active interfaces with all the associated infos - (MAC, IP, IpType, DNS name and associated zone extension) - - Returns: - GET: - A JSON Success response with a field `data` containing: - * a list of dictionnaries (one for each interface) containing: - * a field `ipv4` containing: - * a field `ipv4`: the ip for this interface - * a field `ip_type`: the name of the IpType of this interface - * a field `mac_address`: the MAC of this interface - * a field `domain`: the DNS name for this interface - * a field `extension`: the extension for the DNS zone of this - interface - """ - - interfaces = all_active_assigned_interfaces() - seria = InterfaceSerializer(interfaces, many=True) - return JSONSuccess(seria.data) - - -@csrf_exempt -@login_required -@permission_required('machines.serveur') -@accept_method(['GET']) -def mailing_standard(_request): - """All the available standard mailings. - - Returns: - GET: - A JSONSucess response with a field `data` containing: - * a list of dictionnaries (one for each mailing) containing: - * a field `name`: the name of a mailing - """ - - return JSONSuccess([ - {'name': 'adherents'} - ]) - - -@csrf_exempt -@login_required -@permission_required('machines.serveur') -@accept_method(['GET']) -def mailing_standard_ml_members(_request, ml_name): - """All the members of a specific standard mailing - - Returns: - GET: - A JSONSucess response with a field `data` containing: - * a list if dictionnaries (one for each member) containing: - * a field `email`: the email of the member - * a field `name`: the name of the member - * a field `surname`: the surname of the member - * a field `pseudo`: the pseudo of the member - """ - - # All with active connextion - if ml_name == 'adherents': - members = all_has_access().values('email').distinct() - # Unknown mailing - else: - return JSONError("This mailing does not exist") - seria = MailingMemberSerializer(members, many=True) - return JSONSuccess(seria.data) - - -@csrf_exempt -@login_required -@permission_required('machines.serveur') -@accept_method(['GET']) -def mailing_club(_request): - """All the available club mailings. - - Returns: - GET: - A JSONSucess response with a field `data` containing: - * a list of dictionnaries (one for each mailing) containing: - * a field `name` indicating the name of a mailing - """ - - clubs = Club.objects.filter(mailing=True).values('pseudo') - seria = MailingSerializer(clubs, many=True) - return JSONSuccess(seria.data) - - -@csrf_exempt -@login_required -@permission_required('machines.serveur') -@accept_method(['GET']) -def mailing_club_ml_members(_request, ml_name): - """All the members of a specific club mailing - - Returns: - GET: - A JSONSucess response with a field `data` containing: - * a list if dictionnaries (one for each member) containing: - * a field `email`: the email of the member - * a field `name`: the name of the member - * a field `surname`: the surname of the member - * a field `pseudo`: the pseudo of the member - """ - - try: - club = Club.objects.get(mailing=True, pseudo=ml_name) - except Club.DoesNotExist: - return JSONError("This mailing does not exist") - members = club.administrators.all().values('email').distinct() - seria = MailingMemberSerializer(members, many=True) - return JSONSuccess(seria.data) +class UserViewSet(viewsets.ReadOnlyModelViewSet): + queryset = User.objects.all() + serializer_class = UserSerializer + + +class ClubViewSet(viewsets.ReadOnlyModelViewSet): + queryset = Club.objects.all() + serializer_class = ClubSerializer + + +class AdherentViewSet(viewsets.ReadOnlyModelViewSet): + queryset = Adherent.objects.all() + serializer_class = AdherentSerializer + + +class ServiceUserViewSet(viewsets.ReadOnlyModelViewSet): + queryset = ServiceUser.objects.all() + serializer_class = ServiceUserSerializer + + +class SchoolViewSet(viewsets.ReadOnlyModelViewSet): + queryset = School.objects.all() + serializer_class = SchoolSerializer + + +class ListRightViewSet(viewsets.ReadOnlyModelViewSet): + queryset = ListRight.objects.all() + serializer_class = ListRightSerializer + + +class ShellViewSet(viewsets.ReadOnlyModelViewSet): + queryset = ListShell.objects.all() + serializer_class = ShellSerializer + + +class BanViewSet(viewsets.ReadOnlyModelViewSet): + queryset = Ban.objects.all() + serializer_class = BanSerializer + + +class WhitelistViewSet(viewsets.ReadOnlyModelViewSet): + queryset = Whitelist.objects.all() + serializer_class = WhitelistSerializer + +# +# @csrf_exempt +# @login_required +# @permission_required('machines.serveur') +# @accept_method(['GET']) +# def services(_request): +# """The list of the different services and servers couples +# +# Return: +# GET: +# A JSONSuccess response with a field `data` containing: +# * a list of dictionnaries (one for each service-server couple) +# containing: +# * a field `server`: the server name +# * a field `service`: the service name +# * a field `need_regen`: does the service need a regeneration ? +# """ +# +# service_link = (Service_link.objects.all() +# .select_related('server__domain') +# .select_related('service')) +# seria = ServicesSerializer(service_link, many=True) +# return JSONSuccess(seria.data) +# +# +# @csrf_exempt +# @login_required +# @permission_required('machines.serveur') +# @accept_method(['GET', 'POST']) +# def services_server_service_regen(request, server_name, service_name): +# """The status of a particular service linked to a particular server. +# Mark the service as regenerated if POST used. +# +# Returns: +# GET: +# A JSONSucess response with a field `data` containing: +# * a field `need_regen`: does the service need a regeneration ? +# +# POST: +# An empty JSONSuccess response. +# """ +# +# query = Service_link.objects.filter( +# service__in=Service.objects.filter(service_type=service_name), +# server__in=Interface.objects.filter( +# domain__in=Domain.objects.filter(name=server_name) +# ) +# ) +# if not query: +# return JSONError("This service is not active for this server") +# +# service = query.first() +# if request.method == 'GET': +# return JSONSuccess({'need_regen': service.need_regen()}) +# else: +# service.done_regen() +# return JSONSuccess() +# +# +# @csrf_exempt +# @login_required +# @permission_required('machines.serveur') +# @accept_method(['GET']) +# def services_server(_request, server_name): +# """The list of services attached to a specific server +# +# Returns: +# GET: +# A JSONSuccess response with a field `data` containing: +# * a list of dictionnaries (one for each service) containing: +# * a field `name`: the name of a service +# """ +# +# query = Service_link.objects.filter( +# server__in=Interface.objects.filter( +# domain__in=Domain.objects.filter(name=server_name) +# ) +# ) +# if not query: +# return JSONError("This service is not active for this server") +# +# services_objects = query.all() +# seria = ServiceLinkSerializer(services_objects, many=True) +# return JSONSuccess(seria.data) +# +# +# @csrf_exempt +# @login_required +# @permission_required('machines.serveur') +# @accept_method(['GET']) +# def dns_mac_ip_dns(_request): +# """The list of all active interfaces with all the associated infos +# (MAC, IP, IpType, DNS name and associated zone extension) +# +# Returns: +# GET: +# A JSON Success response with a field `data` containing: +# * a list of dictionnaries (one for each interface) containing: +# * a field `ipv4` containing: +# * a field `ipv4`: the ip for this interface +# * a field `ip_type`: the name of the IpType of this interface +# * a field `ipv6` containing `null` if ipv6 is deactivated else: +# * a field `ipv6`: the ip for this interface +# * a field `ip_type`: the name of the IpType of this interface +# * a field `mac_address`: the MAC of this interface +# * a field `domain`: the DNS name for this interface +# * a field `extension`: the extension for the DNS zone of this +# interface +# """ +# +# interfaces = all_active_assigned_interfaces(full=True) +# seria = FullInterfaceSerializer(interfaces, many=True) +# return JSONSuccess(seria.data) +# +# +# @csrf_exempt +# @login_required +# @permission_required('machines.serveur') +# @accept_method(['GET']) +# def dns_alias(_request): +# """The list of all the alias used and the DNS info associated +# +# Returns: +# GET: +# A JSON Success response with a field `data` containing: +# * a list of dictionnaries (one for each alias) containing: +# * a field `name`: the alias used +# * a field `cname`: the target of the alias (real name of the +# interface) +# * a field `cname_entry`: the entry to write in the DNS to have +# the alias +# * a field `extension`: the extension for the DNS zone of this +# interface +# """ +# +# alias = (Domain.objects +# .filter(interface_parent=None) +# .filter( +# cname__in=Domain.objects.filter( +# interface_parent__in=Interface.objects.exclude(ipv4=None) +# ) +# ) +# .select_related('extension') +# .select_related('cname__extension')) +# seria = DomainSerializer(alias, many=True) +# return JSONSuccess(seria.data) +# +# +# @csrf_exempt +# @login_required +# @permission_required('machines.serveur') +# @accept_method(['GET']) +# def accesspoint_ip_dns(_request): +# """The list of all active interfaces with all the associated infos +# (MAC, IP, IpType, DNS name and associated zone extension) +# +# Only display access points. Use to gen unifi controler names +# +# Returns: +# GET: +# A JSON Success response with a field `data` containing: +# * a list of dictionnaries (one for each interface) containing: +# * a field `ipv4` containing: +# * a field `ipv4`: the ip for this interface +# * a field `ip_type`: the name of the IpType of this interface +# * a field `ipv6` containing `null` if ipv6 is deactivated else: +# * a field `ipv6`: the ip for this interface +# * a field `ip_type`: the name of the IpType of this interface +# * a field `mac_address`: the MAC of this interface +# * a field `domain`: the DNS name for this interface +# * a field `extension`: the extension for the DNS zone of this +# interface +# """ +# +# interfaces = (all_active_assigned_interfaces(full=True) +# .filter(machine__accesspoint__isnull=False)) +# seria = FullInterfaceSerializer(interfaces, many=True) +# return JSONSuccess(seria.data) +# +# +# @csrf_exempt +# @login_required +# @permission_required('machines.serveur') +# @accept_method(['GET']) +# def dns_corresp(_request): +# """The list of the IpTypes possible with the infos about each +# +# Returns: +# GET: +# A JSON Success response with a field `data` containing: +# * a list of dictionnaries (one for each IpType) containing: +# * a field `type`: the name of the type +# * a field `extension`: the DNS extension associated +# * a field `domain_ip_start`: the first ip to use for this type +# * a field `domain_ip_stop`: the last ip to use for this type +# * a field `prefix_v6`: `null` if IPv6 is deactivated else the +# prefix to use +# * a field `ouverture_ports_tcp_in`: the policy for TCP IN ports +# * a field `ouverture_ports_tcp_out`: the policy for TCP OUT ports +# * a field `ouverture_ports_udp_in`: the policy for UDP IN ports +# * a field `ouverture_ports_udp_out`: the policy for UDP OUT ports +# """ +# +# ip_type = IpType.objects.all().select_related('extension') +# seria = TypeSerializer(ip_type, many=True) +# return JSONSuccess(seria.data) +# +# +# @csrf_exempt +# @login_required +# @permission_required('machines.serveur') +# @accept_method(['GET']) +# def dns_mx(_request): +# """The list of MX record to add to the DNS +# +# Returns: +# GET: +# A JSON Success response with a field `data` containing: +# * a list of dictionnaries (one for each MX record) containing: +# * a field `zone`: the extension for the concerned zone +# * a field `priority`: the priority to use +# * a field `name`: the name of the target +# * a field `mx_entry`: the full entry to add in the DNS for this +# MX record +# """ +# +# mx = (Mx.objects.all() +# .select_related('zone') +# .select_related('name__extension')) +# seria = MxSerializer(mx, many=True) +# return JSONSuccess(seria.data) +# +# +# @csrf_exempt +# @login_required +# @permission_required('machines.serveur') +# @accept_method(['GET']) +# def dns_ns(_request): +# """The list of NS record to add to the DNS +# +# Returns: +# GET: +# A JSON Success response with a field `data` containing: +# * a list of dictionnaries (one for each NS record) containing: +# * a field `zone`: the extension for the concerned zone +# * a field `ns`: the DNS name for the NS server targeted +# * a field `ns_entry`: the full entry to add in the DNS for this +# NS record +# """ +# +# ns = (Ns.objects +# .exclude( +# ns__in=Domain.objects.filter( +# interface_parent__in=Interface.objects.filter(ipv4=None) +# ) +# ) +# .select_related('zone') +# .select_related('ns__extension')) +# seria = NsSerializer(ns, many=True) +# return JSONSuccess(seria.data) +# +# +# @csrf_exempt +# @login_required +# @permission_required('machines.serveur') +# @accept_method(['GET']) +# def dns_txt(_request): +# """The list of TXT record to add to the DNS +# +# Returns: +# GET: +# A JSON Success response with a field `data` containing: +# * a list of dictionnaries (one for each TXT record) containing: +# * a field `zone`: the extension for the concerned zone +# * a field `field1`: the first field in the record (target) +# * a field `field2`: the second field in the record (value) +# * a field `txt_entry`: the full entry to add in the DNS for this +# TXT record +# """ +# +# txt = Txt.objects.all().select_related('zone') +# seria = TxtSerializer(txt, many=True) +# return JSONSuccess(seria.data) +# +# +# @csrf_exempt +# @login_required +# @permission_required('machines.serveur') +# @accept_method(['GET']) +# def dns_srv(_request): +# """The list of SRV record to add to the DNS +# +# Returns: +# GET: +# A JSON Success response with a field `data` containing: +# * a list of dictionnaries (one for each SRV record) containing: +# * a field `extension`: the extension for the concerned zone +# * a field `service`: the name of the service concerned +# * a field `protocole`: the name of the protocol to use +# * a field `ttl`: the Time To Live to use +# * a field `priority`: the priority for this service +# * a field `weight`: the weight for same priority entries +# * a field `port`: the port targeted +# * a field `target`: the interface targeted by this service +# * a field `srv_entry`: the full entry to add in the DNS for this +# SRV record +# """ +# +# srv = (Srv.objects.all() +# .select_related('extension') +# .select_related('target__extension')) +# seria = SrvSerializer(srv, many=True) +# return JSONSuccess(seria.data) +# +# +# @csrf_exempt +# @login_required +# @permission_required('machines.serveur') +# @accept_method(['GET']) +# def dns_zones(_request): +# """The list of the zones managed +# +# Returns: +# GET: +# A JSON Success response with a field `data` containing: +# * a list of dictionnaries (one for each zone) containing: +# * a field `name`: the extension for the zone +# * a field `origin`: the server IPv4 for the orgin of the zone +# * a field `origin_v6`: `null` if ipv6 is deactivated else the +# server IPv6 for the origin of the zone +# * a field `soa` containing: +# * a field `mail` containing the mail to contact in case of +# problem with the zone +# * a field `param` containing the full soa paramters to use +# in the DNS for this zone +# * a field `zone_entry`: the full entry to add in the DNS for the +# origin of the zone +# """ +# +# zones = Extension.objects.all().select_related('origin') +# seria = ExtensionSerializer(zones, many=True) +# return JSONSuccess(seria.data) +# +# +# @csrf_exempt +# @login_required +# @permission_required('machines.serveur') +# @accept_method(['GET']) +# def firewall_ouverture_ports(_request): +# """The list of the ports authorized to be openned by the firewall +# +# Returns: +# GET: +# A JSONSuccess response with a `data` field containing: +# * a field `ipv4` containing: +# * a field `tcp_in` containing: +# * a list of port number where ipv4 tcp in should be ok +# * a field `tcp_out` containing: +# * a list of port number where ipv4 tcp ou should be ok +# * a field `udp_in` containing: +# * a list of port number where ipv4 udp in should be ok +# * a field `udp_out` containing: +# * a list of port number where ipv4 udp out should be ok +# * a field `ipv6` containing: +# * a field `tcp_in` containing: +# * a list of port number where ipv6 tcp in should be ok +# * a field `tcp_out` containing: +# * a list of port number where ipv6 tcp ou should be ok +# * a field `udp_in` containing: +# * a list of port number where ipv6 udp in should be ok +# * a field `udp_out` containing: +# * a list of port number where ipv6 udp out should be ok +# """ +# +# r = {'ipv4': {}, 'ipv6': {}} +# for o in (OuverturePortList.objects.all() +# .prefetch_related('ouvertureport_set') +# .prefetch_related('interface_set', 'interface_set__ipv4')): +# pl = { +# "tcp_in": set(map( +# str, +# o.ouvertureport_set.filter( +# protocole=OuverturePort.TCP, +# io=OuverturePort.IN +# ) +# )), +# "tcp_out": set(map( +# str, +# o.ouvertureport_set.filter( +# protocole=OuverturePort.TCP, +# io=OuverturePort.OUT +# ) +# )), +# "udp_in": set(map( +# str, +# o.ouvertureport_set.filter( +# protocole=OuverturePort.UDP, +# io=OuverturePort.IN +# ) +# )), +# "udp_out": set(map( +# str, +# o.ouvertureport_set.filter( +# protocole=OuverturePort.UDP, +# io=OuverturePort.OUT +# ) +# )), +# } +# for i in filter_active_interfaces(o.interface_set): +# if i.may_have_port_open(): +# d = r['ipv4'].get(i.ipv4.ipv4, {}) +# d["tcp_in"] = (d.get("tcp_in", set()) +# .union(pl["tcp_in"])) +# d["tcp_out"] = (d.get("tcp_out", set()) +# .union(pl["tcp_out"])) +# d["udp_in"] = (d.get("udp_in", set()) +# .union(pl["udp_in"])) +# d["udp_out"] = (d.get("udp_out", set()) +# .union(pl["udp_out"])) +# r['ipv4'][i.ipv4.ipv4] = d +# if i.ipv6(): +# for ipv6 in i.ipv6(): +# d = r['ipv6'].get(ipv6.ipv6, {}) +# d["tcp_in"] = (d.get("tcp_in", set()) +# .union(pl["tcp_in"])) +# d["tcp_out"] = (d.get("tcp_out", set()) +# .union(pl["tcp_out"])) +# d["udp_in"] = (d.get("udp_in", set()) +# .union(pl["udp_in"])) +# d["udp_out"] = (d.get("udp_out", set()) +# .union(pl["udp_out"])) +# r['ipv6'][ipv6.ipv6] = d +# return JSONSuccess(r) +# +# +# @csrf_exempt +# @login_required +# @permission_required('machines.serveur') +# @accept_method(['GET']) +# def dhcp_mac_ip(_request): +# """The list of all active interfaces with all the associated infos +# (MAC, IP, IpType, DNS name and associated zone extension) +# +# Returns: +# GET: +# A JSON Success response with a field `data` containing: +# * a list of dictionnaries (one for each interface) containing: +# * a field `ipv4` containing: +# * a field `ipv4`: the ip for this interface +# * a field `ip_type`: the name of the IpType of this interface +# * a field `mac_address`: the MAC of this interface +# * a field `domain`: the DNS name for this interface +# * a field `extension`: the extension for the DNS zone of this +# interface +# """ +# +# interfaces = all_active_assigned_interfaces() +# seria = InterfaceSerializer(interfaces, many=True) +# return JSONSuccess(seria.data) +# +# +# @csrf_exempt +# @login_required +# @permission_required('machines.serveur') +# @accept_method(['GET']) +# def mailing_standard(_request): +# """All the available standard mailings. +# +# Returns: +# GET: +# A JSONSucess response with a field `data` containing: +# * a list of dictionnaries (one for each mailing) containing: +# * a field `name`: the name of a mailing +# """ +# +# return JSONSuccess([ +# {'name': 'adherents'} +# ]) +# +# +# @csrf_exempt +# @login_required +# @permission_required('machines.serveur') +# @accept_method(['GET']) +# def mailing_standard_ml_members(_request, ml_name): +# """All the members of a specific standard mailing +# +# Returns: +# GET: +# A JSONSucess response with a field `data` containing: +# * a list if dictionnaries (one for each member) containing: +# * a field `email`: the email of the member +# * a field `name`: the name of the member +# * a field `surname`: the surname of the member +# * a field `pseudo`: the pseudo of the member +# """ +# +# # All with active connextion +# if ml_name == 'adherents': +# members = all_has_access().values('email').distinct() +# # Unknown mailing +# else: +# return JSONError("This mailing does not exist") +# seria = MailingMemberSerializer(members, many=True) +# return JSONSuccess(seria.data) +# +# +# @csrf_exempt +# @login_required +# @permission_required('machines.serveur') +# @accept_method(['GET']) +# def mailing_club(_request): +# """All the available club mailings. +# +# Returns: +# GET: +# A JSONSucess response with a field `data` containing: +# * a list of dictionnaries (one for each mailing) containing: +# * a field `name` indicating the name of a mailing +# """ +# +# clubs = Club.objects.filter(mailing=True).values('pseudo') +# seria = MailingSerializer(clubs, many=True) +# return JSONSuccess(seria.data) +# +# +# @csrf_exempt +# @login_required +# @permission_required('machines.serveur') +# @accept_method(['GET']) +# def mailing_club_ml_members(_request, ml_name): +# """All the members of a specific club mailing +# +# Returns: +# GET: +# A JSONSucess response with a field `data` containing: +# * a list if dictionnaries (one for each member) containing: +# * a field `email`: the email of the member +# * a field `name`: the name of the member +# * a field `surname`: the surname of the member +# * a field `pseudo`: the pseudo of the member +# """ +# +# try: +# club = Club.objects.get(mailing=True, pseudo=ml_name) +# except Club.DoesNotExist: +# return JSONError("This mailing does not exist") +# members = club.administrators.all().values('email').distinct() +# seria = MailingMemberSerializer(members, many=True) +# return JSONSuccess(seria.data) diff --git a/re2o/settings.py b/re2o/settings.py index 24226817..c9f9bef5 100644 --- a/re2o/settings.py +++ b/re2o/settings.py @@ -174,3 +174,8 @@ GRAPH_MODELS = { 'all_applications': True, 'group_models': True, } + +#RestFramework config fot API +REST_FRAMEWORK = { + 'URL_FIELD_NAME': 'api_url' +} From 510a0c9b437321f8a0a443bd715f572fc584a10d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Kervella?= Date: Fri, 20 Apr 2018 18:10:45 +0000 Subject: [PATCH 02/42] Isolate API settings from project settings --- api/settings.py | 37 +++++++++++++++++++++++++++++++++++++ re2o/settings.py | 8 +++----- 2 files changed, 40 insertions(+), 5 deletions(-) create mode 100644 api/settings.py diff --git a/api/settings.py b/api/settings.py new file mode 100644 index 00000000..4475a950 --- /dev/null +++ b/api/settings.py @@ -0,0 +1,37 @@ +# -*- mode: python; coding: utf-8 -*- +# Re2o est un logiciel d'administration développé initiallement au rezometz. Il +# se veut agnostique au réseau considéré, de manière à être installable en +# quelques clics. +# +# Copyright © 2017 Gabriel Détraz +# Copyright © 2017 Goulven Kermarec +# Copyright © 2017 Augustin Lemesle +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +"""api.settings +Django settings specific to the API. +""" + +# RestFramework config for API +REST_FRAMEWORK = { + 'URL_FIELD_NAME': 'api_url', + 'DEFAULT_AUTHENTICATION_CLASSES': ( + 'rest_framework.authentication.SessionAuthentication', + ), + 'DEFAULT_PERMISSION_CLASSES': ( + 'rest_framework.permissions.IsAuthenticated', + ) +} diff --git a/re2o/settings.py b/re2o/settings.py index c9f9bef5..ad1c4c9c 100644 --- a/re2o/settings.py +++ b/re2o/settings.py @@ -75,7 +75,6 @@ LOCAL_APPS = ( 're2o', 'preferences', 'logs', - 'api', ) INSTALLED_APPS = ( DJANGO_CONTRIB_APPS + @@ -175,7 +174,6 @@ GRAPH_MODELS = { 'group_models': True, } -#RestFramework config fot API -REST_FRAMEWORK = { - 'URL_FIELD_NAME': 'api_url' -} +# Activate API +if 'api' in INSTALLED_APPS: + from api.settings import * From 6478a0aed9cbb069fc5e701cf8dcb534bf1f9a14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Kervella?= Date: Fri, 20 Apr 2018 20:17:50 +0000 Subject: [PATCH 03/42] Add ACL permission on API --- api/permissions.py | 106 +++++++++++++++++++++++++++++++++++++++++++++ api/settings.py | 2 +- 2 files changed, 107 insertions(+), 1 deletion(-) create mode 100644 api/permissions.py diff --git a/api/permissions.py b/api/permissions.py new file mode 100644 index 00000000..6ede9491 --- /dev/null +++ b/api/permissions.py @@ -0,0 +1,106 @@ +from rest_framework import permissions +from re2o.acl import can_create, can_edit, can_delete, can_view_all + +class DefaultACLPermission(permissions.BasePermission): + """ + Permission subclass in charge of checking the ACL to determine + if a user can access the models + """ + perms_map = { + 'GET': [lambda model: model.can_view_all], + 'OPTIONS': [lambda model: model.can_view_all], + 'HEAD': [lambda model: model.can_view_all], + 'POST': [lambda model: model.can_create], + #'PUT': [], + #'PATCH': [], + #'DELETE': [], + } + perms_obj_map = { + 'GET': [lambda obj: obj.can_view], + 'OPTIONS': [lambda obj: obj.can_view], + 'HEAD': [lambda obj: obj.can_view], + #'POST': [], + 'PUT': [lambda obj: obj.can_edit], + #'PATCH': [], + 'DELETE': [lambda obj: obj.can_delete], + } + + def get_required_permissions(self, method, model): + """ + Given a model and an HTTP method, return the list of acl + functions that the user is required to verify. + """ + if method not in self.perms_map: + raise exceptions.MethodNotAllowed(method) + + return [perm(model) for perm in self.perms_map[method]] + + def get_required_object_permissions(self, method, obj): + """ + Given an object and an HTTP method, return the list of acl + functions that the user is required to verify. + """ + if method not in self.perms_map: + raise exceptions.MethodNotAllowed(method) + + return [perm(obj) for perm in self.perms_map[method]] + + def _queryset(self, view): + """ + Return the queryset associated with view and raise an error + is there is none. + """ + assert hasattr(view, 'get_queryset') \ + or getattr(view, 'queryset', None) is not None, ( + 'Cannot apply {} on a view that does not set ' + '`.queryset` or have a `.get_queryset()` method.' + ).format(self.__class__.__name__) + + if hasattr(view, 'get_queryset'): + queryset = view.get_queryset() + assert queryset is not None, ( + '{}.get_queryset() returned None'.format(view.__class__.__name__) + ) + return queryset + return view.queryset + + def has_permission(self, request, view): + # Workaround to ensure ACLPermissions are not applied + # to the root view when using DefaultRouter. + if getattr(view, '_ignore_model_permissions', False): + return True + + if not request.user or not request.user.is_authenticated: + return False + + queryset = self._queryset(view) + perms = self.get_required_permissions(request.method, queryset.model) + + return all(perm(request.user)[0] for perm in perms) + + def has_object_permission(self, request, view, obj): + # authentication checks have already executed via has_permission + queryset = self._queryset(view) + user = request.user + + perms = self.get_required_object_permissions(request.method, obj) + + if not all(perm(request.user)[0] for perm in perms): + # If the user does not have permissions we need to determine if + # they have read permissions to see 403, or not, and simply see + # a 404 response. + + if request.method in SAFE_METHODS: + # Read permissions already checked and failed, no need + # to make another lookup. + raise Http404 + + read_perms = self.get_required_object_permissions('GET', obj) + if not read_perms(request.user)[0]: + raise Http404 + + # Has read permissions. + return False + + return True + diff --git a/api/settings.py b/api/settings.py index 4475a950..767082ce 100644 --- a/api/settings.py +++ b/api/settings.py @@ -32,6 +32,6 @@ REST_FRAMEWORK = { 'rest_framework.authentication.SessionAuthentication', ), 'DEFAULT_PERMISSION_CLASSES': ( - 'rest_framework.permissions.IsAuthenticated', + 'api.permissions.DefaultACLPermission', ) } From 0c7e944b07b1d228b764b9c3c27c16c2c6bff398 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Kervella?= Date: Fri, 20 Apr 2018 22:09:33 +0000 Subject: [PATCH 04/42] Add permission for API view --- api/initial_perm.py | 17 +++++++++++++++++ api/settings.py | 6 ++++++ api/urls.py | 1 + re2o/urls.py | 7 +++++-- 4 files changed, 29 insertions(+), 2 deletions(-) create mode 100644 api/initial_perm.py diff --git a/api/initial_perm.py b/api/initial_perm.py new file mode 100644 index 00000000..f4482fd9 --- /dev/null +++ b/api/initial_perm.py @@ -0,0 +1,17 @@ +from django.contrib.contenttypes.models import ContentType +from django.contrib.auth.models import Permission +from django.conf import settings + +api_content_type, created = ContentType.objects.get_or_create( + app_label=settings.API_CONTENT_TYPE_APP_LABEL, + model=settings.API_CONTENT_TYPE_MODEL +) +if created: + api_content_type.save() +api_permission, created = Permission.objects.get_or_create( + name=settings.API_PERMISSION_NAME, + content_type=api_content_type, + codename=settings.API_PERMISSION_CODENAME +) +if created: + api_permission.save() diff --git a/api/settings.py b/api/settings.py index 767082ce..8cf152f2 100644 --- a/api/settings.py +++ b/api/settings.py @@ -35,3 +35,9 @@ REST_FRAMEWORK = { 'api.permissions.DefaultACLPermission', ) } + +# API permission settings +API_CONTENT_TYPE_APP_LABEL = 'api' +API_CONTENT_TYPE_MODEL = 'api' +API_PERMISSION_NAME = 'Can use the API' +API_PERMISSION_CODENAME = 'use_api' diff --git a/api/urls.py b/api/urls.py index 662dd8c4..90672b47 100644 --- a/api/urls.py +++ b/api/urls.py @@ -28,6 +28,7 @@ from django.conf.urls import url, include from rest_framework.routers import DefaultRouter from . import views +from . import initial_perm router = DefaultRouter() router.register(r'users', views.UserViewSet) diff --git a/re2o/urls.py b/re2o/urls.py index 47172521..34cb0b15 100644 --- a/re2o/urls.py +++ b/re2o/urls.py @@ -42,6 +42,7 @@ Including another URLconf """ from __future__ import unicode_literals +from django.conf import settings from django.conf.urls import include, url from django.contrib import admin from django.contrib.auth import views as auth_views @@ -70,6 +71,8 @@ urlpatterns = [ r'^preferences/', include('preferences.urls', namespace='preferences') ), - url(r'^api/', include('api.urls', namespace='api')), - ] +if 'api' in settings.INSTALLED_APPS: + urlpatterns += [ + url(r'^api/', include('api.urls', namespace='api')), + ] From 0be63ad58e200e7f760dbffc068038d7cd414ea8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Kervella?= Date: Fri, 20 Apr 2018 23:24:10 +0000 Subject: [PATCH 05/42] Use the use_api permission to access API --- api/acl.py | 45 +++++++++++++++++++++++++++++++++++++++++++++ api/permissions.py | 24 +++++++++++++++--------- 2 files changed, 60 insertions(+), 9 deletions(-) create mode 100644 api/acl.py diff --git a/api/acl.py b/api/acl.py new file mode 100644 index 00000000..da0c9a29 --- /dev/null +++ b/api/acl.py @@ -0,0 +1,45 @@ +# -*- mode: python; coding: utf-8 -*- +# Re2o est un logiciel d'administration développé initiallement au rezometz. Il +# se veut agnostique au réseau considéré, de manière à être installable en +# quelques clics. +# +# Copyright © 2018 Maël Kervella +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +"""api.acl + +Here are defined some functions to check acl on the application. +""" + +from django.conf import settings + + +def can_view(user): + """Check if an user can view the application. + + Args: + user: The user who wants to view the application. + + Returns: + A couple (allowed, msg) where allowed is a boolean which is True if + viewing is granted and msg is a message (can be None). + """ + kwargs = { + 'app_label': settings.API_CONTENT_TYPE_APP_LABEL, + 'codename': settings.API_PERMISSION_CODENAME + } + can = user.has_perm('%(app_label)s.%(codename)s' % kwargs) + return can, None if can else "Vous ne pouvez pas voir cette application." diff --git a/api/permissions.py b/api/permissions.py index 6ede9491..a1d79b21 100644 --- a/api/permissions.py +++ b/api/permissions.py @@ -1,28 +1,34 @@ from rest_framework import permissions from re2o.acl import can_create, can_edit, can_delete, can_view_all +from . import acl + +def can_see_api(_): + return lambda user: acl.can_view(user) + + class DefaultACLPermission(permissions.BasePermission): """ Permission subclass in charge of checking the ACL to determine if a user can access the models """ perms_map = { - 'GET': [lambda model: model.can_view_all], - 'OPTIONS': [lambda model: model.can_view_all], - 'HEAD': [lambda model: model.can_view_all], - 'POST': [lambda model: model.can_create], + 'GET': [can_see_api, lambda model: model.can_view_all], + 'OPTIONS': [can_see_api, lambda model: model.can_view_all], + 'HEAD': [can_see_api, lambda model: model.can_view_all], + 'POST': [can_see_api, lambda model: model.can_create], #'PUT': [], #'PATCH': [], #'DELETE': [], } perms_obj_map = { - 'GET': [lambda obj: obj.can_view], - 'OPTIONS': [lambda obj: obj.can_view], - 'HEAD': [lambda obj: obj.can_view], + 'GET': [can_see_api, lambda obj: obj.can_view], + 'OPTIONS': [can_see_api, lambda obj: obj.can_view], + 'HEAD': [can_see_api, lambda obj: obj.can_view], #'POST': [], - 'PUT': [lambda obj: obj.can_edit], + 'PUT': [can_see_api, lambda obj: obj.can_edit], #'PATCH': [], - 'DELETE': [lambda obj: obj.can_delete], + 'DELETE': [can_see_api, lambda obj: obj.can_delete], } def get_required_permissions(self, method, model): From a5715d69b6858c4414053e49855f3db3ba79c2c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Kervella?= Date: Sat, 21 Apr 2018 16:32:12 +0000 Subject: [PATCH 06/42] Include 'use_api' permission in the api.acl module --- api/acl.py | 22 ++++++++++++++++++++++ api/initial_perm.py | 17 ----------------- api/urls.py | 1 - 3 files changed, 22 insertions(+), 18 deletions(-) delete mode 100644 api/initial_perm.py diff --git a/api/acl.py b/api/acl.py index da0c9a29..0c7faae7 100644 --- a/api/acl.py +++ b/api/acl.py @@ -24,7 +24,29 @@ Here are defined some functions to check acl on the application. """ + from django.conf import settings +from django.contrib.contenttypes.models import ContentType +from django.contrib.auth.models import Permission + + +# Creates the 'use_api' permission if not created +# The 'use_api' is a fake permission in the sense +# it is not associated with an existing model and +# this ensure the permission is created every tun +api_content_type, created = ContentType.objects.get_or_create( + app_label=settings.API_CONTENT_TYPE_APP_LABEL, + model=settings.API_CONTENT_TYPE_MODEL +) +if created: + api_content_type.save() +api_permission, created = Permission.objects.get_or_create( + name=settings.API_PERMISSION_NAME, + content_type=api_content_type, + codename=settings.API_PERMISSION_CODENAME +) +if created: + api_permission.save() def can_view(user): diff --git a/api/initial_perm.py b/api/initial_perm.py deleted file mode 100644 index f4482fd9..00000000 --- a/api/initial_perm.py +++ /dev/null @@ -1,17 +0,0 @@ -from django.contrib.contenttypes.models import ContentType -from django.contrib.auth.models import Permission -from django.conf import settings - -api_content_type, created = ContentType.objects.get_or_create( - app_label=settings.API_CONTENT_TYPE_APP_LABEL, - model=settings.API_CONTENT_TYPE_MODEL -) -if created: - api_content_type.save() -api_permission, created = Permission.objects.get_or_create( - name=settings.API_PERMISSION_NAME, - content_type=api_content_type, - codename=settings.API_PERMISSION_CODENAME -) -if created: - api_permission.save() diff --git a/api/urls.py b/api/urls.py index 90672b47..662dd8c4 100644 --- a/api/urls.py +++ b/api/urls.py @@ -28,7 +28,6 @@ from django.conf.urls import url, include from rest_framework.routers import DefaultRouter from . import views -from . import initial_perm router = DefaultRouter() router.register(r'users', views.UserViewSet) From 6562f32ebf99811cd261a93f06db5f961a6eb9c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Kervella?= Date: Sat, 21 Apr 2018 19:48:33 +0000 Subject: [PATCH 07/42] Add token authentication with expiration of tokens --- api/authentication.py | 25 +++++++++++++++++++ api/settings.py | 7 ++++++ api/urls.py | 1 + api/views.py | 38 +++++++++++++++++++++++------ install_utils/apache2/re2o-tls.conf | 1 + install_utils/apache2/re2o.conf | 1 + re2o/settings.py | 1 + 7 files changed, 66 insertions(+), 8 deletions(-) create mode 100644 api/authentication.py diff --git a/api/authentication.py b/api/authentication.py new file mode 100644 index 00000000..4dc5a6f3 --- /dev/null +++ b/api/authentication.py @@ -0,0 +1,25 @@ +import datetime +from django.conf import settings +from django.utils.translation import ugettext_lazy as _ +from rest_framework.authentication import TokenAuthentication +from rest_framework import exceptions + +class ExpiringTokenAuthentication(TokenAuthentication): + def authenticate_credentials(self, key): + model = self.get_model() + try: + token = model.objects.select_related('user').get(key=key) + except model.DoesNotExist: + raise exceptions.AuthenticationFailed(_('Invalid token.')) + + if not token.user.is_active: + raise exceptions.AuthenticationFailed(_('User inactive or deleted.')) + + token_duration = datetime.timedelta( + seconds=settings.API_TOKEN_DURATION + ) + utc_now = datetime.datetime.now(datetime.timezone.utc) + if token.created < utc_now - token_duration: + raise exceptions.AuthenticationFailed(_('Token has expired')) + + return (token.user, token) diff --git a/api/settings.py b/api/settings.py index 8cf152f2..028ec01d 100644 --- a/api/settings.py +++ b/api/settings.py @@ -29,6 +29,7 @@ Django settings specific to the API. REST_FRAMEWORK = { 'URL_FIELD_NAME': 'api_url', 'DEFAULT_AUTHENTICATION_CLASSES': ( + 'api.authentication.ExpiringTokenAuthentication', 'rest_framework.authentication.SessionAuthentication', ), 'DEFAULT_PERMISSION_CLASSES': ( @@ -41,3 +42,9 @@ API_CONTENT_TYPE_APP_LABEL = 'api' API_CONTENT_TYPE_MODEL = 'api' API_PERMISSION_NAME = 'Can use the API' API_PERMISSION_CODENAME = 'use_api' + +# Activate token authentication +API_APPS = ( + 'rest_framework.authtoken', +) +API_TOKEN_DURATION = 86400 # 24 hours diff --git a/api/urls.py b/api/urls.py index 662dd8c4..3379d083 100644 --- a/api/urls.py +++ b/api/urls.py @@ -42,6 +42,7 @@ router.register(r'whitelists', views.WhitelistViewSet) urlpatterns = [ url(r'^', include(router.urls)), + url(r'^token-auth/', views.ObtainExpiringAuthToken.as_view()) # # Services # url(r'^services/$', views.services), # url( diff --git a/api/views.py b/api/views.py index 00389eb7..f3e2a6c7 100644 --- a/api/views.py +++ b/api/views.py @@ -24,17 +24,15 @@ The views for the API app. They should all return JSON data and not fallback on HTML pages such as the login and index pages for a better integration. """ -from django.contrib.auth.decorators import login_required, permission_required -from django.views.decorators.csrf import csrf_exempt +import datetime +from django.conf import settings + +from rest_framework.authtoken.views import ObtainAuthToken +from rest_framework.authtoken.models import Token from rest_framework.response import Response -from rest_framework import status, mixins, generics, viewsets +from rest_framework import viewsets, status -from re2o.utils import ( - all_has_access, - all_active_assigned_interfaces, - filter_active_interfaces -) from users.models import ( User, Club, @@ -118,6 +116,30 @@ class WhitelistViewSet(viewsets.ReadOnlyModelViewSet): queryset = Whitelist.objects.all() serializer_class = WhitelistSerializer +# Subclass the standard rest_framework.auth_token.views.ObtainAuthToken +# in order to renew the lease of the token and add expiration time +class ObtainExpiringAuthToken(ObtainAuthToken): + def post(self, request, *args, **kwargs): + serializer = self.serializer_class(data=request.data) + serializer.is_valid(raise_exception=True) + user = serializer.validated_data['user'] + token, created = Token.objects.get_or_create(user=user) + + token_duration = datetime.timedelta( + seconds=settings.API_TOKEN_DURATION + ) + utc_now = datetime.datetime.now(datetime.timezone.utc) + if not created and token.created < utc_now - token_duration: + token.delete() + token = Token.objects.create(user=user) + token.created = datetime.datetime.utcnow() + token.save() + + return Response({ + 'token': token.key, + 'expiration_date': token.created + token_duration + }) + # # @csrf_exempt # @login_required diff --git a/install_utils/apache2/re2o-tls.conf b/install_utils/apache2/re2o-tls.conf index 83e2cf13..eb8f2c42 100644 --- a/install_utils/apache2/re2o-tls.conf +++ b/install_utils/apache2/re2o-tls.conf @@ -26,6 +26,7 @@ WSGIScriptAlias / PATH/re2o/wsgi.py WSGIProcessGroup re2o WSGIDaemonProcess re2o processes=2 threads=16 maximum-requests=1000 display-name=re2o + WSGIPassAuthorization On SSLCertificateFile /etc/letsencrypt/live/LE_PATH/fullchain.pem SSLCertificateKeyFile /etc/letsencrypt/live/LE_PATH/privkey.pem diff --git a/install_utils/apache2/re2o.conf b/install_utils/apache2/re2o.conf index 680fb05d..1b4e02b3 100644 --- a/install_utils/apache2/re2o.conf +++ b/install_utils/apache2/re2o.conf @@ -19,5 +19,6 @@ WSGIScriptAlias / PATH/re2o/wsgi.py WSGIProcessGroup re2o WSGIDaemonProcess re2o processes=2 threads=16 maximum-requests=1000 display-name=re2o + WSGIPassAuthorization On diff --git a/re2o/settings.py b/re2o/settings.py index ad1c4c9c..7c119a55 100644 --- a/re2o/settings.py +++ b/re2o/settings.py @@ -177,3 +177,4 @@ GRAPH_MODELS = { # Activate API if 'api' in INSTALLED_APPS: from api.settings import * + INSTALLED_APPS += API_APPS From 98dc4205bef1265e16570e8e008b7653a9eaaa5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Kervella?= Date: Sat, 21 Apr 2018 22:36:10 +0000 Subject: [PATCH 08/42] API support for app cotisations --- api/serializers.py | 76 ++++++++++++++++++++++++++++++++++++++++++++++ api/urls.py | 8 +++++ api/views.py | 53 ++++++++++++++++++++++++++++++++ 3 files changed, 137 insertions(+) diff --git a/api/serializers.py b/api/serializers.py index e274f942..9acacce1 100644 --- a/api/serializers.py +++ b/api/serializers.py @@ -23,6 +23,15 @@ Serializers for the API app """ from rest_framework import serializers + +from cotisations.models import ( + Facture, + Vente, + Article, + Banque, + Paiement, + Cotisation +) from users.models import ( User, Club, @@ -49,6 +58,73 @@ from machines.models import ( Ipv6List ) +# COTISATION APP + +class FactureSerializer(serializers.HyperlinkedModelSerializer): + class Meta: + model = Facture + fields = ('user', 'paiement', 'banque', 'cheque', 'date', 'valid', + 'control', 'prix_total', 'name', 'api_url') + extra_kwargs = { + 'user': {'view_name': 'api:user-detail'}, + 'paiement': {'view_name': 'api:paiement-detail'}, + 'banque': {'view_name': 'api:banque-detail'}, + 'api_url': {'view_name': 'api:facture-detail'} + } + + +class VenteSerializer(serializers.HyperlinkedModelSerializer): + class Meta: + model = Vente + fields = ('facture', 'number', 'name', 'prix', 'duration', + 'type_cotisation', 'prix_total', 'api_url') + extra_kwargs = { + 'facture': {'view_name': 'api:facture-detail'}, + 'api_url': {'view_name': 'api:vente-detail'} + } + + +class ArticleSerializer(serializers.HyperlinkedModelSerializer): + class Meta: + model = Article + fields = ('name', 'prix', 'duration', 'type_user', + 'type_cotisation', 'api_url') + extra_kwargs = { + 'api_url': {'view_name': 'api:article-detail'} + } + + +class BanqueSerializer(serializers.HyperlinkedModelSerializer): + class Meta: + model = Banque + fields = ('name', 'api_url') + extra_kwargs = { + 'api_url': {'view_name': 'api:banque-detail'} + } + + +class PaiementSerializer(serializers.HyperlinkedModelSerializer): + class Meta: + model = Paiement + fields = ('moyen', 'type_paiement', 'api_url') + extra_kwargs = { + 'api_url': {'view_name': 'api:paiement-detail'} + } + + +class CotisationSerializer(serializers.HyperlinkedModelSerializer): + class Meta: + model = Cotisation + fields = ('vente', 'type_cotisation', 'date_start', 'date_end', + 'api_url') + extra_kwargs = { + 'vente': {'view_name': 'api:vente-detail'}, + 'api_url': {'view_name': 'api:cotisation-detail'} + } + + +# USER APP + class UserSerializer(serializers.HyperlinkedModelSerializer): access = serializers.BooleanField(source='has_access') diff --git a/api/urls.py b/api/urls.py index 3379d083..3281d326 100644 --- a/api/urls.py +++ b/api/urls.py @@ -30,6 +30,14 @@ from rest_framework.routers import DefaultRouter from . import views router = DefaultRouter() +# COTISATION APP +router.register(r'factures', views.FactureViewSet) +router.register(r'ventes', views.VenteViewSet) +router.register(r'articles', views.ArticleViewSet) +router.register(r'banques', views.BanqueViewSet) +router.register(r'paiements', views.PaiementViewSet) +router.register(r'cotisations', views.CotisationViewSet) +# USER APP router.register(r'users', views.UserViewSet) router.register(r'clubs', views.ClubViewSet) router.register(r'adherents', views.AdherentViewSet) diff --git a/api/views.py b/api/views.py index f3e2a6c7..29531fb8 100644 --- a/api/views.py +++ b/api/views.py @@ -33,6 +33,15 @@ from rest_framework.authtoken.models import Token from rest_framework.response import Response from rest_framework import viewsets, status + +from cotisations.models import ( + Facture, + Vente, + Article, + Banque, + Paiement, + Cotisation +) from users.models import ( User, Club, @@ -60,6 +69,14 @@ from machines.models import ( ) from .serializers import ( + # COTISATION APP + FactureSerializer, + VenteSerializer, + ArticleSerializer, + BanqueSerializer, + PaiementSerializer, + CotisationSerializer, + # USER APP UserSerializer, ClubSerializer, AdherentSerializer, @@ -72,6 +89,42 @@ from .serializers import ( ) +# COTISATION APP + + +class FactureViewSet(viewsets.ReadOnlyModelViewSet): + queryset = Facture.objects.all() + serializer_class = FactureSerializer + + +class VenteViewSet(viewsets.ReadOnlyModelViewSet): + queryset = Vente.objects.all() + serializer_class = VenteSerializer + + +class ArticleViewSet(viewsets.ReadOnlyModelViewSet): + queryset = Article.objects.all() + serializer_class = ArticleSerializer + + +class BanqueViewSet(viewsets.ReadOnlyModelViewSet): + queryset = Banque.objects.all() + serializer_class = BanqueSerializer + + +class PaiementViewSet(viewsets.ReadOnlyModelViewSet): + queryset = Paiement.objects.all() + serializer_class = PaiementSerializer + + +class CotisationViewSet(viewsets.ReadOnlyModelViewSet): + queryset = Cotisation.objects.all() + serializer_class = CotisationSerializer + + +# USER APP + + class UserViewSet(viewsets.ReadOnlyModelViewSet): queryset = User.objects.all() serializer_class = UserSerializer From e2736e17dff7885549e2d281b6fc2c68f7752993 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Kervella?= Date: Sun, 22 Apr 2018 00:36:06 +0000 Subject: [PATCH 09/42] API support for app machines --- api/serializers.py | 261 ++++++++++++++++++++++++++++++++++++++++++--- api/urls.py | 24 ++++- api/views.py | 141 ++++++++++++++++++++++-- 3 files changed, 400 insertions(+), 26 deletions(-) diff --git a/api/serializers.py b/api/serializers.py index 9acacce1..90d00418 100644 --- a/api/serializers.py +++ b/api/serializers.py @@ -32,6 +32,27 @@ from cotisations.models import ( Paiement, Cotisation ) +from machines.models import ( + Machine, + MachineType, + IpType, + Vlan, + Nas, + SOA, + Extension, + Mx, + Ns, + Txt, + Srv, + Interface, + Ipv6List, + Domain, + IpList, + Service, + Service_link, + OuverturePortList, + OuverturePort +) from users.models import ( User, Club, @@ -43,22 +64,9 @@ from users.models import ( Ban, Whitelist ) -from machines.models import ( - Interface, - IpType, - Extension, - IpList, - Domain, - Txt, - Mx, - Srv, - Service_link, - Ns, - OuverturePort, - Ipv6List -) -# COTISATION APP + +# COTISATIONS APP class FactureSerializer(serializers.HyperlinkedModelSerializer): class Meta: @@ -123,7 +131,228 @@ class CotisationSerializer(serializers.HyperlinkedModelSerializer): } -# USER APP +# MACHINES APP + + +class MachineSerializer(serializers.HyperlinkedModelSerializer): + class Meta: + model = Machine + fields = ('user', 'name', 'active', 'api_url') + extra_kwargs = { + 'user': {'view_name': 'api:user-detail'}, + 'api_url': {'view_name': 'api:machine-detail'} + } + + +class MachineTypeSerializer(serializers.HyperlinkedModelSerializer): + class Meta: + model = MachineType + fields = ('type', 'ip_type', 'api_url') + extra_kwargs = { + 'ip_type': {'view_name': 'api:iptype-detail'}, + 'api_url': {'view_name': 'api:machinetype-detail'} + } + + +class IpTypeSerializer(serializers.HyperlinkedModelSerializer): + class Meta: + model = IpType + fields = ('type', 'extension', 'need_infra', 'domaine_ip_start', + 'domaine_ip_stop', 'prefix_v6', 'vlan', 'ouverture_ports', + 'api_url') + extra_kwargs = { + 'extension': {'view_name': 'api:extension-detail'}, + 'vlan': {'view_name': 'api:vlan-detail'}, + 'ouverture_ports': {'view_name': 'api:ouvertureportlist-detail'}, + 'api_url': {'view_name': 'api:iptype-detail'} + } + + +class VlanSerializer(serializers.HyperlinkedModelSerializer): + class Meta: + model = Vlan + fields = ('vlan_id', 'name', 'comment', 'api_url') + extra_kwargs = { + 'api_url': {'view_name': 'api:vlan-detail'} + } + + +class NasSerializer(serializers.HyperlinkedModelSerializer): + class Meta: + model = Nas + fields = ('name', 'nas_type', 'machine_type', 'port_access_mode', + 'autocapture_mac', 'api_url') + extra_kwargs = { + 'nas_type': {'view_name': 'api:machinetype-detail'}, + 'machine_type': {'view_name': 'api:machinetype-detail'}, + 'api_url': {'view_name': 'api:nas-detail'} + } + + +class SOASerializer(serializers.HyperlinkedModelSerializer): + class Meta: + model = SOA + fields = ('name', 'mail', 'refresh', 'retry', 'expire', 'ttl', + 'api_url') + extra_kwargs = { + 'api_url': {'view_name': 'api:soa-detail'} + } + + +class ExtensionSerializer(serializers.HyperlinkedModelSerializer): + class Meta: + model = Extension + fields = ('name', 'need_infra', 'origin', 'origin_v6', 'soa', + 'api_url') + extra_kwargs = { + 'origin': {'view_name': 'api:iplist-detail'}, + 'soa': {'view_name': 'api:soa-detail'}, + 'api_url': {'view_name': 'api:extension-detail'} + } + + +class MxSerializer(serializers.HyperlinkedModelSerializer): + class Meta: + model = Mx + fields = ('zone', 'priority', 'name', 'api_url') + extra_kwargs = { + 'zone': {'view_name': 'api:extension-detail'}, + 'name': {'view_name': 'api:domain-detail'}, + 'api_url': {'view_name': 'api:mx-detail'} + } + + +class NsSerializer(serializers.HyperlinkedModelSerializer): + class Meta: + model = Ns + fields = ('zone', 'ns', 'api_url') + extra_kwargs = { + 'zone': {'view_name': 'api:extension-detail'}, + 'ns': {'view_name': 'api:domain-detail'}, + 'api_url': {'view_name': 'api:ns-detail'} + } + + +class TxtSerializer(serializers.HyperlinkedModelSerializer): + class Meta: + model = Txt + fields = ('zone', 'field1', 'field2', 'api_url') + extra_kwargs = { + 'zone': {'view_name': 'api:extension-detail'}, + 'api_url': {'view_name': 'api:txt-detail'} + } + + +class SrvSerializer(serializers.HyperlinkedModelSerializer): + class Meta: + model = Srv + fields = ('service', 'protocole', 'extension', 'ttl', 'priority', + 'weight', 'port', 'target', 'api_url') + extra_kwargs = { + 'extension': {'view_name': 'api:extension-detail'}, + 'target': {'view_name': 'api:domain-detail'}, + 'api_url': {'view_name': 'api:mx-detail'} + } + + +class InterfaceSerializer(serializers.HyperlinkedModelSerializer): + active = serializers.BooleanField(source='is_active') + + class Meta: + model = Interface + fields = ('ipv4', 'mac_address', 'machine', 'type', 'details', + 'port_lists', 'active', 'api_url') + extra_kwargs = { + 'ipv4': {'view_name': 'api:iplist-detail'}, + 'machine': {'view_name': 'api:machine-detail'}, + 'type': {'view_name': 'api:machinetype-detail'}, + 'port_lists': {'view_name': 'api:ouvertureportlist-detail'}, + 'api_url': {'view_name': 'api:interface-detail'} + } + + +class Ipv6ListSerializer(serializers.HyperlinkedModelSerializer): + class Meta: + model = Ipv6List + fields = ('ipv6', 'interface', 'slaac_ip', 'date_end', + 'api_url') + extra_kwargs = { + 'interface': {'view_name': 'api:interface-detail'}, + 'api_url': {'view_name': 'api:ipv6list-detail'} + } + + +class DomainSerializer(serializers.HyperlinkedModelSerializer): + class Meta: + model = Domain + fields = ('interface_parent', 'name', 'extension', 'cname', + 'api_url') + extra_kwargs = { + 'interface_parent': {'view_name': 'api:interface-detail'}, + 'extension': {'view_name': 'api:extension-detail'}, + 'cname': {'view_name': 'api:domain-detail'}, + 'api_url': {'view_name': 'api:domain-detail'} + } + + +class IpListSerializer(serializers.HyperlinkedModelSerializer): + class Meta: + model = IpList + fields = ('ipv4', 'ip_type', 'need_infra', 'api_url') + extra_kwargs = { + 'ip_type': {'view_name': 'api:iptype-detail'}, + 'api_url': {'view_name': 'api:iplist-detail'} + } + + +class ServiceSerializer(serializers.HyperlinkedModelSerializer): + class Meta: + model = Service + fields = ('service_type', 'min_time_regen', 'regular_time_regen', + 'servers', 'api_url') + extra_kwargs = { + 'servers': {'view_name': 'api:interface-detail'}, + 'api_url': {'view_name': 'api:service-detail'} + } + + +class ServiceLinkSerializer(serializers.HyperlinkedModelSerializer): + class Meta: + model = Service_link + fields = ('service', 'server', 'last_regen', 'asked_regen', + 'need_regen', 'api_url') + extra_kwargs = { + 'service': {'view_name': 'api:service-detail'}, + 'server': {'view_name': 'api:interface-detail'}, + 'api_url': {'view_name': 'api:servicelink-detail'} + } + + +class OuverturePortListSerializer(serializers.HyperlinkedModelSerializer): + class Meta: + model = OuverturePortList + fields = ('name', 'tcp_ports_in', 'udp_ports_in', 'tcp_ports_out', + 'udp_ports_out', 'api_url') + extra_kwargs = { + 'tcp_ports_in': {'view_name': 'api:ouvertureport-detail'}, + 'udp_ports_in': {'view_name': 'api:ouvertureport-detail'}, + 'tcp_ports_out': {'view_name': 'api:ouvertureport-detail'}, + 'udp_ports_out': {'view_name': 'api:ouvertureport-detail'}, + 'api_url': {'view_name': 'api:ouvertureportlist-detail'} + } + + +class OuverturePortSerializer(serializers.HyperlinkedModelSerializer): + class Meta: + model = OuverturePort + fields = ('begin', 'end', 'port_list', 'protocole', 'io', 'api_url') + extra_kwargs = { + 'port_list': {'view_name': 'api:ouvertureportlist-detail'}, + 'api_url': {'view_name': 'api:ouvertureport-detail'} + } + + +# USERS APP class UserSerializer(serializers.HyperlinkedModelSerializer): diff --git a/api/urls.py b/api/urls.py index 3281d326..a712cacd 100644 --- a/api/urls.py +++ b/api/urls.py @@ -30,14 +30,34 @@ from rest_framework.routers import DefaultRouter from . import views router = DefaultRouter() -# COTISATION APP +# COTISATIONS APP router.register(r'factures', views.FactureViewSet) router.register(r'ventes', views.VenteViewSet) router.register(r'articles', views.ArticleViewSet) router.register(r'banques', views.BanqueViewSet) router.register(r'paiements', views.PaiementViewSet) router.register(r'cotisations', views.CotisationViewSet) -# USER APP +# MACHINES APP +router.register(r'machines', views.MachineViewSet) +router.register(r'machinetypes', views.MachineTypeViewSet) +router.register(r'iptypes', views.IpTypeViewSet) +router.register(r'vlans', views.VlanViewSet) +router.register(r'nas', views.NasViewSet) +router.register(r'soa', views.SOAViewSet) +router.register(r'extensions', views.ExtensionViewSet) +router.register(r'mx', views.MxViewSet) +router.register(r'ns', views.NsViewSet) +router.register(r'txt', views.TxtViewSet) +router.register(r'srv', views.SrvViewSet) +router.register(r'interfaces', views.InterfaceViewSet) +router.register(r'ipv6lists', views.Ipv6ListViewSet) +router.register(r'domains', views.DomainViewSet) +router.register(r'iplists', views.IpListViewSet) +router.register(r'services', views.ServiceViewSet) +router.register(r'servicelinks', views.ServiceLinkViewSet, 'servicelink') +router.register(r'ouvertureportlists', views.OuverturePortListViewSet) +router.register(r'ouvertureports', views.OuverturePortViewSet) +# USERS APP router.register(r'users', views.UserViewSet) router.register(r'clubs', views.ClubViewSet) router.register(r'adherents', views.AdherentViewSet) diff --git a/api/views.py b/api/views.py index 29531fb8..db119ce3 100644 --- a/api/views.py +++ b/api/views.py @@ -54,29 +54,56 @@ from users.models import ( Whitelist ) from machines.models import ( - Service_link, - Service, - Interface, - Domain, + Machine, + MachineType, IpType, + Vlan, + Nas, + SOA, + Extension, Mx, Ns, Txt, Srv, - Extension, + Interface, + Ipv6List, + Domain, + IpList, + Service, + Service_link, OuverturePortList, OuverturePort ) from .serializers import ( - # COTISATION APP + # COTISATIONS APP FactureSerializer, VenteSerializer, ArticleSerializer, BanqueSerializer, PaiementSerializer, CotisationSerializer, - # USER APP + # MACHINES APP + MachineSerializer, + MachineTypeSerializer, + IpTypeSerializer, + VlanSerializer, + NasSerializer, + SOASerializer, + ExtensionSerializer, + MxSerializer, + NsSerializer, + TxtSerializer, + SrvSerializer, + InterfaceSerializer, + Ipv6ListSerializer, + DomainSerializer, + IpListSerializer, + ServiceSerializer, + ServiceLinkSerializer, + OuverturePortListSerializer, + OuverturePortSerializer, + # USERS APP UserSerializer, ClubSerializer, AdherentSerializer, @@ -89,7 +116,7 @@ from .serializers import ( ) -# COTISATION APP +# COTISATIONS APP class FactureViewSet(viewsets.ReadOnlyModelViewSet): @@ -122,6 +149,104 @@ class CotisationViewSet(viewsets.ReadOnlyModelViewSet): serializer_class = CotisationSerializer +# MACHINES APP + + +class MachineViewSet(viewsets.ReadOnlyModelViewSet): + queryset = Machine.objects.all() + serializer_class = MachineSerializer + + +class MachineTypeViewSet(viewsets.ReadOnlyModelViewSet): + queryset = MachineType.objects.all() + serializer_class = MachineTypeSerializer + + +class IpTypeViewSet(viewsets.ReadOnlyModelViewSet): + queryset = IpType.objects.all() + serializer_class = IpTypeSerializer + + +class VlanViewSet(viewsets.ReadOnlyModelViewSet): + queryset = Vlan.objects.all() + serializer_class = VlanSerializer + + +class NasViewSet(viewsets.ReadOnlyModelViewSet): + queryset = Nas.objects.all() + serializer_class = NasSerializer + + +class SOAViewSet(viewsets.ReadOnlyModelViewSet): + queryset = SOA.objects.all() + serializer_class = SOASerializer + + +class ExtensionViewSet(viewsets.ReadOnlyModelViewSet): + queryset = Extension.objects.all() + serializer_class = ExtensionSerializer + + +class MxViewSet(viewsets.ReadOnlyModelViewSet): + queryset = Mx.objects.all() + serializer_class = MxSerializer + + +class NsViewSet(viewsets.ReadOnlyModelViewSet): + queryset = Ns.objects.all() + serializer_class = NsSerializer + + +class TxtViewSet(viewsets.ReadOnlyModelViewSet): + queryset = Txt.objects.all() + serializer_class = TxtSerializer + + +class SrvViewSet(viewsets.ReadOnlyModelViewSet): + queryset = Srv.objects.all() + serializer_class = SrvSerializer + + +class InterfaceViewSet(viewsets.ReadOnlyModelViewSet): + queryset = Interface.objects.all() + serializer_class = InterfaceSerializer + + +class Ipv6ListViewSet(viewsets.ReadOnlyModelViewSet): + queryset = Ipv6List.objects.all() + serializer_class = Ipv6ListSerializer + + +class DomainViewSet(viewsets.ReadOnlyModelViewSet): + queryset = Domain.objects.all() + serializer_class = DomainSerializer + + +class IpListViewSet(viewsets.ReadOnlyModelViewSet): + queryset = IpList.objects.all() + serializer_class = IpListSerializer + + +class ServiceViewSet(viewsets.ReadOnlyModelViewSet): + queryset = Service.objects.all() + serializer_class = ServiceSerializer + + +class ServiceLinkViewSet(viewsets.ReadOnlyModelViewSet): + queryset = Service_link.objects.all() + serializer_class = ServiceLinkSerializer + + +class OuverturePortListViewSet(viewsets.ReadOnlyModelViewSet): + queryset = OuverturePortList.objects.all() + serializer_class = OuverturePortListSerializer + + +class OuverturePortViewSet(viewsets.ReadOnlyModelViewSet): + queryset = OuverturePort.objects.all() + serializer_class = OuverturePortSerializer + + # USER APP From 2ef8930ffed75a5d0c58245023a659f16ee7fb6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Kervella?= Date: Sun, 22 Apr 2018 18:28:01 +0000 Subject: [PATCH 10/42] API support for app preferences --- api/serializers.py | 104 +++++++++++++++++++++++++++++++++++++++++++++ api/urls.py | 9 ++++ api/views.py | 84 +++++++++++++++++++++++++++++++----- 3 files changed, 186 insertions(+), 11 deletions(-) diff --git a/api/serializers.py b/api/serializers.py index 90d00418..888891f0 100644 --- a/api/serializers.py +++ b/api/serializers.py @@ -53,6 +53,17 @@ from machines.models import ( OuverturePortList, OuverturePort ) +from preferences.models import ( + OptionalUser, + OptionalMachine, + OptionalTopologie, + GeneralOption, + AssoOption, + HomeOption, + MailMessageOption +) +# Avoid duplicate names +from preferences.models import Service as ServiceOption from users.models import ( User, Club, @@ -352,6 +363,99 @@ class OuverturePortSerializer(serializers.HyperlinkedModelSerializer): } +# PREFERENCES APP + + +# class OptionalUserSerializer(serializers.HyperlinkedModelSerializer): +# tel_mandatory = serializers.BooleanField(source='is_tel_mandatory') +# +# class Meta: +# model = OptionalUser +# fields = ('tel_mandatory', 'user_solde', 'solde_negatif', 'max_solde', +# 'min_online_payement', 'gpg_fingerprint', +# 'all_can_create_club', 'self_adhesion', 'shell_default', +# 'api_url') +# extra_kwargs = { +# 'shell_default': {'view_name': 'api:shell-detail'}, +# 'api_url': {'view_name': 'api:optionaluser-detail'} +# } +# +# +# class OptionalMachineSerializer(serializers.HyperlinkedModelSerializer): +# class Meta: +# model = OptionalMachine +# fields = ('password_machine', 'max_lambdauser_interfaces', +# 'max_lambdauser_aliases', 'ipv6_mode', 'create_machine', +# 'ipv6', 'api_url') +# extra_kwargs = { +# 'api_url': {'view_name': 'api:optionalmachine-detail'} +# } +# +# +# class OptionalTopologieSerializer(serializers.HyperlinkedModelSerializer): +# class Meta: +# model = OptionalTopologie +# fields = ('radius_general_policy', 'vlan_decision_ok', +# 'vlan_decision_no', 'api_url') +# extra_kwargs = { +# 'vlan_decision_ok': {'view_name': 'api:vlan-detail'}, +# 'vlan_decision_nok': {'view_name': 'api:vlan-detail'}, +# 'api_url': {'view_name': 'api:optionaltopologie-detail'} +# } +# +# +# class GeneralOptionSerializer(serializers.HyperlinkedModelSerializer): +# class Meta: +# model = GeneralOption +# fields = ('general_message', 'search_display_page', +# 'pagination_number', 'pagination_large_number', +# 'req_expire_hrs', 'site_name', 'email_from', 'GTU_sum_up', +# 'GTU', 'api_url') +# extra_kwargs = { +# 'api_url': {'view_name': 'api:generaloption-detail'} +# } +# +# +# class ServiceOptionSerializer(serializers.HyperlinkedModelSerializer): +# class Meta: +# model = ServiceOption +# fields = ('name', 'url', 'description', 'image', 'api_url') +# extra_kwargs = { +# 'api_url': {'view_name': 'api:serviceoption-detail'} +# } +# +# +# class AssoOptionSerializer(serializers.HyperlinkedModelSerializer): +# class Meta: +# model = AssoOption +# fields = ('name', 'siret', 'adresse1', 'adresse2', 'contact', +# 'telephone', 'pseudo', 'utilisateur_asso', 'payement', +# 'payement_id', 'payement_pass', 'description', 'api_url') +# extra_kwargs = { +# 'utilisateur_asso': {'view_name': 'api:user-detail'}, +# 'api_url': {'view_name': 'api:assooption-detail'} +# } +# +# +# class HomeOptionSerializer(serializers.HyperlinkedModelSerializer): +# class Meta: +# model = HomeOption +# fields = ('facebook_url', 'twitter_url', 'twitter_account_name', +# 'api_url') +# extra_kwargs = { +# 'api_url': {'view_name': 'api:homeoption-detail'} +# } +# +# +# class MailMessageOptionSerializer(serializers.HyperlinkedModelSerializer): +# class Meta: +# model = MailMessageOption +# fields = ('welcome_mail_fr', 'welcome_mail_en', 'api_url') +# extra_kwargs = { +# 'api_url': {'view_name': 'api:mailmessageoption-detail'} +# } + + # USERS APP diff --git a/api/urls.py b/api/urls.py index a712cacd..c2ba0c2a 100644 --- a/api/urls.py +++ b/api/urls.py @@ -57,6 +57,15 @@ router.register(r'services', views.ServiceViewSet) router.register(r'servicelinks', views.ServiceLinkViewSet, 'servicelink') router.register(r'ouvertureportlists', views.OuverturePortListViewSet) router.register(r'ouvertureports', views.OuverturePortViewSet) +# PREFERENCES APP +#router.register(r'optionaluser', views.OptionalUserSerializer) +#router.register(r'optionalmachine', views.OptionalMachineSerializer) +#router.register(r'optionaltopologie', views.OptionalTopologieSerializer) +#router.register(r'generaloption', views.GeneralOptionSerializer) +#router.register(r'serviceoption', views.ServiceOptionSerializer) +#router.register(r'assooption', views.AssoOptionSerializer) +#router.register(r'homeoption', views.HomeOptionSerializer) +#router.register(r'mailmessageoption', views.MailMessageOptionSerializer) # USERS APP router.register(r'users', views.UserViewSet) router.register(r'clubs', views.ClubViewSet) diff --git a/api/views.py b/api/views.py index db119ce3..f970745c 100644 --- a/api/views.py +++ b/api/views.py @@ -42,17 +42,6 @@ from cotisations.models import ( Paiement, Cotisation ) -from users.models import ( - User, - Club, - Adherent, - ServiceUser, - School, - ListRight, - ListShell, - Ban, - Whitelist -) from machines.models import ( Machine, MachineType, @@ -74,6 +63,28 @@ from machines.models import ( OuverturePortList, OuverturePort ) +# from preferences.models import ( +# OptionalUser, +# OptionalMachine, +# OptionalTopologie, +# GeneralOption, +# AssoOption, +# HomeOption, +# MailMessageOption +# ) +# # Avoid duplicate names +# from preferences.models import Service as ServiceOption +from users.models import ( + User, + Club, + Adherent, + ServiceUser, + School, + ListRight, + ListShell, + Ban, + Whitelist +) from .serializers import ( # COTISATIONS APP @@ -103,6 +114,15 @@ from .serializers import ( ServiceLinkSerializer, OuverturePortListSerializer, OuverturePortSerializer, + # PREFERENCES APP + # OptionalUserSerializer, + # OptionalMachineSerializer, + # OptionalTopologieSerializer, + # GeneralOptionSerializer, + # ServiceOptionSerializer, + # AssoOptionSerializer, + # HomeOptionSerializer, + # MailMessageOptionSerializer, # USERS APP UserSerializer, ClubSerializer, @@ -247,6 +267,48 @@ class OuverturePortViewSet(viewsets.ReadOnlyModelViewSet): serializer_class = OuverturePortSerializer +# PREFERENCES APP + +# class OptionalUserViewSet(viewsets.ReadOnlyModelViewSet): +# queryset = OptionalUser.objects.all() +# serializer_class = OptionalUserSerializer +# +# +# class OptionalMachineViewSet(viewsets.ReadOnlyModelViewSet): +# queryset = OptionalMachine.objects.all() +# serializer_class = OptionalMachineSerializer +# +# +# class OptionalTopologieViewSet(viewsets.ReadOnlyModelViewSet): +# queryset = OptionalTopologie.objects.all() +# serializer_class = OptionalTopologieSerializer +# +# +# class GeneralOptionViewSet(viewsets.ReadOnlyModelViewSet): +# queryset = GeneralOption.objects.all() +# serializer_class = GeneralOptionSerializer +# +# +# class ServiceOptionViewSet(viewsets.ReadOnlyModelViewSet): +# queryset = ServiceOption.objects.all() +# serializer_class = ServiceOptionSerializer +# +# +# class AssoOptionViewSet(viewsets.ReadOnlyModelViewSet): +# queryset = AssoOption.objects.all() +# serializer_class = AssoOptionSerializer +# +# +# class HomeOptionViewSet(viewsets.ReadOnlyModelViewSet): +# queryset = HomeOption.objects.all() +# serializer_class = HomeOptionSerializer +# +# +# class MailMessageOptionViewSet(viewsets.ReadOnlyModelViewSet): +# queryset = MailMessageOption.objects.all() +# serializer_class = MailMessageOptionSerializer + + # USER APP From 95acdb2ecd987d01e022c589b533b86c3d6057c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Kervella?= Date: Tue, 24 Apr 2018 13:59:18 +0000 Subject: [PATCH 11/42] API support for app topologie --- api/serializers.py | 111 +++++++++++++++++++++++++++++++++++++++++++++ api/urls.py | 10 ++++ api/views.py | 69 ++++++++++++++++++++++++++++ 3 files changed, 190 insertions(+) diff --git a/api/serializers.py b/api/serializers.py index 888891f0..192125a7 100644 --- a/api/serializers.py +++ b/api/serializers.py @@ -64,6 +64,17 @@ from preferences.models import ( ) # Avoid duplicate names from preferences.models import Service as ServiceOption +from topologie.models import ( + Stack, + AccessPoint, + Switch, + ModelSwitch, + ConstructorSwitch, + SwitchBay, + Building, + Room +) +from topologie.models import Port as SwitchPort from users.models import ( User, Club, @@ -456,6 +467,106 @@ class OuverturePortSerializer(serializers.HyperlinkedModelSerializer): # } + +# TOPOLOGIE APP + + +class StackSerializer(serializers.HyperlinkedModelSerializer): + class Meta: + model = Stack + fields = ('name', 'stack_id', 'details', 'member_id_min', + 'member_id_max', 'api_url') + extra_kwargs = { + 'api_url': {'view_name': 'api:stack-detail'} + } + + +class AccessPointSerializer(serializers.HyperlinkedModelSerializer): + class Meta: + model = AccessPoint + fields = ('user', 'name', 'active', 'location', 'api_url') + extra_kwargs = { + 'user': {'view_name': 'api:user-detail'}, + 'api_url': {'view_name': 'api:accesspoint-detail'} + } + + +class SwitchSerializer(serializers.HyperlinkedModelSerializer): + port_amount = serializers.IntegerField(source='number') + class Meta: + model = Switch + fields = ('port_amount', 'stack', 'stack_member_id', 'model', + 'switchbay', 'api_url') + extra_kwargs = { + 'stack': {'view_name': 'api:stack-detail'}, + 'model': {'view_name': 'api:modelswitch-detail'}, + 'switchbay': {'view_name': 'api:switchbay-detail'}, + 'api_url': {'view_name': 'api:switch-detail'} + } + + +class ModelSwitchSerializer(serializers.HyperlinkedModelSerializer): + class Meta: + model = ModelSwitch + fields = ('reference', 'constructor', 'api_url') + extra_kwargs = { + 'constructor': {'view_name': 'api:constructorswitch-detail'}, + 'api_url': {'view_name': 'api:modelswitch-detail'} + } + + +class ConstructorSwitchSerializer(serializers.HyperlinkedModelSerializer): + class Meta: + model = ConstructorSwitch + fields = ('name', 'api_url') + extra_kwargs = { + 'api_url': {'view_name': 'api:constructorswitch-detail'} + } + + +class SwitchBaySerializer(serializers.HyperlinkedModelSerializer): + class Meta: + model = SwitchBay + fields = ('name', 'building', 'info', 'api_url') + extra_kwargs = { + 'building': {'view_name': 'api:building-detail'}, + 'api_url': {'view_name': 'api:switchbay-detail'} + } + + +class BuildingSerializer(serializers.HyperlinkedModelSerializer): + class Meta: + model = Building + fields = ('name', 'api_url') + extra_kwargs = { + 'api_url': {'view_name': 'api:building-detail'} + } + + +class SwitchPortSerializer(serializers.HyperlinkedModelSerializer): + class Meta: + model = SwitchPort + fields = ('switch', 'port', 'room', 'machine_interface', 'related', + 'radius', 'vlan_force', 'details', 'api_url') + extra_kwargs = { + 'switch': {'view_name': 'api:switch-detail'}, + 'room': {'view_name': 'api:room-detail'}, + 'machine_interface': {'view_name': 'api:interface-detail'}, + 'related': {'view_name': 'api:switchport-detail'}, + 'vlan_force': {'view_name': 'api:vlan-detail'}, + 'api_url': {'view_name': 'api:switchport-detail'} + } + + +class RoomSerializer(serializers.HyperlinkedModelSerializer): + class Meta: + model = Room + fields = ('name', 'details', 'api_url') + extra_kwargs = { + 'api_url': {'view_name': 'api:room-detail'} + } + + # USERS APP diff --git a/api/urls.py b/api/urls.py index c2ba0c2a..f01e6a3e 100644 --- a/api/urls.py +++ b/api/urls.py @@ -66,6 +66,16 @@ router.register(r'ouvertureports', views.OuverturePortViewSet) #router.register(r'assooption', views.AssoOptionSerializer) #router.register(r'homeoption', views.HomeOptionSerializer) #router.register(r'mailmessageoption', views.MailMessageOptionSerializer) +# TOPOLOGIE APP +router.register(r'stack', views.StackViewSet) +router.register(r'acesspoint', views.AccessPointViewSet) +router.register(r'switch', views.SwitchViewSet) +router.register(r'modelswitch', views.ModelSwitchViewSet) +router.register(r'constructorswitch', views.ConstructorSwitchViewSet) +router.register(r'switchbay', views.SwitchBayViewSet) +router.register(r'building', views.BuildingViewSet) +router.register(r'switchport', views.SwitchPortViewSet, 'switchport') +router.register(r'room', views.RoomViewSet) # USERS APP router.register(r'users', views.UserViewSet) router.register(r'clubs', views.ClubViewSet) diff --git a/api/views.py b/api/views.py index f970745c..34f8a947 100644 --- a/api/views.py +++ b/api/views.py @@ -74,6 +74,17 @@ from machines.models import ( # ) # # Avoid duplicate names # from preferences.models import Service as ServiceOption +from topologie.models import ( + Stack, + AccessPoint, + Switch, + ModelSwitch, + ConstructorSwitch, + SwitchBay, + Building, + Room +) +from topologie.models import Port as SwitchPort from users.models import ( User, Club, @@ -123,6 +134,16 @@ from .serializers import ( # AssoOptionSerializer, # HomeOptionSerializer, # MailMessageOptionSerializer, + # TOPOLOGIE APP + StackSerializer, + AccessPointSerializer, + SwitchSerializer, + ModelSwitchSerializer, + ConstructorSwitchSerializer, + SwitchBaySerializer, + BuildingSerializer, + SwitchPortSerializer, + RoomSerializer, # USERS APP UserSerializer, ClubSerializer, @@ -309,6 +330,54 @@ class OuverturePortViewSet(viewsets.ReadOnlyModelViewSet): # serializer_class = MailMessageOptionSerializer +# TOPOLOGIE APP + + +class StackViewSet(viewsets.ReadOnlyModelViewSet): + queryset = Stack.objects.all() + serializer_class = StackSerializer + + +class AccessPointViewSet(viewsets.ReadOnlyModelViewSet): + queryset = AccessPoint.objects.all() + serializer_class = AccessPointSerializer + + +class SwitchViewSet(viewsets.ReadOnlyModelViewSet): + queryset = Switch.objects.all() + serializer_class = SwitchSerializer + + +class ModelSwitchViewSet(viewsets.ReadOnlyModelViewSet): + queryset = ModelSwitch.objects.all() + serializer_class = ModelSwitchSerializer + + +class ConstructorSwitchViewSet(viewsets.ReadOnlyModelViewSet): + queryset = ConstructorSwitch.objects.all() + serializer_class = ConstructorSwitchSerializer + + +class SwitchBayViewSet(viewsets.ReadOnlyModelViewSet): + queryset = SwitchBay.objects.all() + serializer_class = SwitchBaySerializer + + +class BuildingViewSet(viewsets.ReadOnlyModelViewSet): + queryset = Building.objects.all() + serializer_class = BuildingSerializer + + +class SwitchPortViewSet(viewsets.ReadOnlyModelViewSet): + queryset = SwitchPort.objects.all() + serializer_class = SwitchPortSerializer + + +class RoomViewSet(viewsets.ReadOnlyModelViewSet): + queryset = Room.objects.all() + serializer_class = RoomSerializer + + # USER APP From 187138f6e3287b8fd8a918363102afa9ccb5750f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Kervella?= Date: Tue, 24 Apr 2018 15:57:55 +0000 Subject: [PATCH 12/42] API cleanup code --- api/serializers.py | 519 ++++++++++++--------------------------------- api/views.py | 331 ++++++++++------------------- 2 files changed, 244 insertions(+), 606 deletions(-) diff --git a/api/serializers.py b/api/serializers.py index 192125a7..eab81940 100644 --- a/api/serializers.py +++ b/api/serializers.py @@ -24,666 +24,419 @@ Serializers for the API app from rest_framework import serializers -from cotisations.models import ( - Facture, - Vente, - Article, - Banque, - Paiement, - Cotisation -) -from machines.models import ( - Machine, - MachineType, - IpType, - Vlan, - Nas, - SOA, - Extension, - Mx, - Ns, - Txt, - Srv, - Interface, - Ipv6List, - Domain, - IpList, - Service, - Service_link, - OuverturePortList, - OuverturePort -) -from preferences.models import ( - OptionalUser, - OptionalMachine, - OptionalTopologie, - GeneralOption, - AssoOption, - HomeOption, - MailMessageOption -) -# Avoid duplicate names -from preferences.models import Service as ServiceOption -from topologie.models import ( - Stack, - AccessPoint, - Switch, - ModelSwitch, - ConstructorSwitch, - SwitchBay, - Building, - Room -) -from topologie.models import Port as SwitchPort -from users.models import ( - User, - Club, - Adherent, - ServiceUser, - School, - ListRight, - ListShell, - Ban, - Whitelist -) +import cotisations.models as cotisations +import machines.models as machines +import preferences.models as preferences +import topologie.models as topologie +import users.models as users + + +API_NAMESPACE = 'api' + + +class NamespacedHRField(serializers.HyperlinkedRelatedField): + """ A HyperlinkedRelatedField subclass to automatically prefix + view names with a namespace """ + def __init__(self, view_name=None, **kwargs): + if view_name is not None: + view_name = '%s:%s' % (API_NAMESPACE, view_name) + super(NamespacedHRField, self).__init__(view_name=view_name, **kwargs) + + +class NamespacedHIField(serializers.HyperlinkedIdentityField): + """ A HyperlinkedIdentityField subclass to automatically prefix + view names with a namespace """ + def __init__(self, view_name=None, **kwargs): + if view_name is not None: + view_name = '%s:%s' % (API_NAMESPACE, view_name) + super(NamespacedHIField, self).__init__(view_name=view_name, **kwargs) + + +class NamespacedHMSerializer(serializers.HyperlinkedModelSerializer): + """ A HyperlinkedModelSerializer subclass to use `NamespacedHRField` as + field and automatically prefix view names with a namespace """ + serializer_related_field = NamespacedHRField + serializer_url_field = NamespacedHIField # COTISATIONS APP -class FactureSerializer(serializers.HyperlinkedModelSerializer): + +class FactureSerializer(NamespacedHMSerializer): class Meta: - model = Facture + model = cotisations.Facture fields = ('user', 'paiement', 'banque', 'cheque', 'date', 'valid', 'control', 'prix_total', 'name', 'api_url') - extra_kwargs = { - 'user': {'view_name': 'api:user-detail'}, - 'paiement': {'view_name': 'api:paiement-detail'}, - 'banque': {'view_name': 'api:banque-detail'}, - 'api_url': {'view_name': 'api:facture-detail'} - } -class VenteSerializer(serializers.HyperlinkedModelSerializer): +class VenteSerializer(NamespacedHMSerializer): class Meta: - model = Vente + model = cotisations.Vente fields = ('facture', 'number', 'name', 'prix', 'duration', 'type_cotisation', 'prix_total', 'api_url') - extra_kwargs = { - 'facture': {'view_name': 'api:facture-detail'}, - 'api_url': {'view_name': 'api:vente-detail'} - } -class ArticleSerializer(serializers.HyperlinkedModelSerializer): +class ArticleSerializer(NamespacedHMSerializer): class Meta: - model = Article + model = cotisations.Article fields = ('name', 'prix', 'duration', 'type_user', 'type_cotisation', 'api_url') - extra_kwargs = { - 'api_url': {'view_name': 'api:article-detail'} - } -class BanqueSerializer(serializers.HyperlinkedModelSerializer): +class BanqueSerializer(NamespacedHMSerializer): class Meta: - model = Banque + model = cotisations.Banque fields = ('name', 'api_url') - extra_kwargs = { - 'api_url': {'view_name': 'api:banque-detail'} - } -class PaiementSerializer(serializers.HyperlinkedModelSerializer): +class PaiementSerializer(NamespacedHMSerializer): class Meta: - model = Paiement + model = cotisations.Paiement fields = ('moyen', 'type_paiement', 'api_url') - extra_kwargs = { - 'api_url': {'view_name': 'api:paiement-detail'} - } -class CotisationSerializer(serializers.HyperlinkedModelSerializer): +class CotisationSerializer(NamespacedHMSerializer): class Meta: - model = Cotisation + model = cotisations.Cotisation fields = ('vente', 'type_cotisation', 'date_start', 'date_end', 'api_url') - extra_kwargs = { - 'vente': {'view_name': 'api:vente-detail'}, - 'api_url': {'view_name': 'api:cotisation-detail'} - } # MACHINES APP -class MachineSerializer(serializers.HyperlinkedModelSerializer): +class MachineSerializer(NamespacedHMSerializer): class Meta: - model = Machine + model = machines.Machine fields = ('user', 'name', 'active', 'api_url') - extra_kwargs = { - 'user': {'view_name': 'api:user-detail'}, - 'api_url': {'view_name': 'api:machine-detail'} - } -class MachineTypeSerializer(serializers.HyperlinkedModelSerializer): +class MachineTypeSerializer(NamespacedHMSerializer): class Meta: - model = MachineType + model = machines.MachineType fields = ('type', 'ip_type', 'api_url') - extra_kwargs = { - 'ip_type': {'view_name': 'api:iptype-detail'}, - 'api_url': {'view_name': 'api:machinetype-detail'} - } -class IpTypeSerializer(serializers.HyperlinkedModelSerializer): +class IpTypeSerializer(NamespacedHMSerializer): class Meta: - model = IpType + model = machines.IpType fields = ('type', 'extension', 'need_infra', 'domaine_ip_start', 'domaine_ip_stop', 'prefix_v6', 'vlan', 'ouverture_ports', 'api_url') - extra_kwargs = { - 'extension': {'view_name': 'api:extension-detail'}, - 'vlan': {'view_name': 'api:vlan-detail'}, - 'ouverture_ports': {'view_name': 'api:ouvertureportlist-detail'}, - 'api_url': {'view_name': 'api:iptype-detail'} - } -class VlanSerializer(serializers.HyperlinkedModelSerializer): +class VlanSerializer(NamespacedHMSerializer): class Meta: - model = Vlan + model = machines.Vlan fields = ('vlan_id', 'name', 'comment', 'api_url') - extra_kwargs = { - 'api_url': {'view_name': 'api:vlan-detail'} - } -class NasSerializer(serializers.HyperlinkedModelSerializer): +class NasSerializer(NamespacedHMSerializer): class Meta: - model = Nas + model = machines.Nas fields = ('name', 'nas_type', 'machine_type', 'port_access_mode', 'autocapture_mac', 'api_url') - extra_kwargs = { - 'nas_type': {'view_name': 'api:machinetype-detail'}, - 'machine_type': {'view_name': 'api:machinetype-detail'}, - 'api_url': {'view_name': 'api:nas-detail'} - } -class SOASerializer(serializers.HyperlinkedModelSerializer): +class SOASerializer(NamespacedHMSerializer): class Meta: - model = SOA + model = machines.SOA fields = ('name', 'mail', 'refresh', 'retry', 'expire', 'ttl', 'api_url') - extra_kwargs = { - 'api_url': {'view_name': 'api:soa-detail'} - } -class ExtensionSerializer(serializers.HyperlinkedModelSerializer): +class ExtensionSerializer(NamespacedHMSerializer): class Meta: - model = Extension + model = machines.Extension fields = ('name', 'need_infra', 'origin', 'origin_v6', 'soa', 'api_url') - extra_kwargs = { - 'origin': {'view_name': 'api:iplist-detail'}, - 'soa': {'view_name': 'api:soa-detail'}, - 'api_url': {'view_name': 'api:extension-detail'} - } -class MxSerializer(serializers.HyperlinkedModelSerializer): +class MxSerializer(NamespacedHMSerializer): class Meta: - model = Mx + model = machines.Mx fields = ('zone', 'priority', 'name', 'api_url') - extra_kwargs = { - 'zone': {'view_name': 'api:extension-detail'}, - 'name': {'view_name': 'api:domain-detail'}, - 'api_url': {'view_name': 'api:mx-detail'} - } -class NsSerializer(serializers.HyperlinkedModelSerializer): +class NsSerializer(NamespacedHMSerializer): class Meta: - model = Ns + model = machines.Ns fields = ('zone', 'ns', 'api_url') - extra_kwargs = { - 'zone': {'view_name': 'api:extension-detail'}, - 'ns': {'view_name': 'api:domain-detail'}, - 'api_url': {'view_name': 'api:ns-detail'} - } -class TxtSerializer(serializers.HyperlinkedModelSerializer): +class TxtSerializer(NamespacedHMSerializer): class Meta: - model = Txt + model = machines.Txt fields = ('zone', 'field1', 'field2', 'api_url') - extra_kwargs = { - 'zone': {'view_name': 'api:extension-detail'}, - 'api_url': {'view_name': 'api:txt-detail'} - } -class SrvSerializer(serializers.HyperlinkedModelSerializer): +class SrvSerializer(NamespacedHMSerializer): class Meta: - model = Srv + model = machines.Srv fields = ('service', 'protocole', 'extension', 'ttl', 'priority', 'weight', 'port', 'target', 'api_url') - extra_kwargs = { - 'extension': {'view_name': 'api:extension-detail'}, - 'target': {'view_name': 'api:domain-detail'}, - 'api_url': {'view_name': 'api:mx-detail'} - } -class InterfaceSerializer(serializers.HyperlinkedModelSerializer): +class InterfaceSerializer(NamespacedHMSerializer): active = serializers.BooleanField(source='is_active') class Meta: - model = Interface + model = machines.Interface fields = ('ipv4', 'mac_address', 'machine', 'type', 'details', 'port_lists', 'active', 'api_url') - extra_kwargs = { - 'ipv4': {'view_name': 'api:iplist-detail'}, - 'machine': {'view_name': 'api:machine-detail'}, - 'type': {'view_name': 'api:machinetype-detail'}, - 'port_lists': {'view_name': 'api:ouvertureportlist-detail'}, - 'api_url': {'view_name': 'api:interface-detail'} - } -class Ipv6ListSerializer(serializers.HyperlinkedModelSerializer): +class Ipv6ListSerializer(NamespacedHMSerializer): class Meta: - model = Ipv6List + model = machines.Ipv6List fields = ('ipv6', 'interface', 'slaac_ip', 'date_end', 'api_url') - extra_kwargs = { - 'interface': {'view_name': 'api:interface-detail'}, - 'api_url': {'view_name': 'api:ipv6list-detail'} - } -class DomainSerializer(serializers.HyperlinkedModelSerializer): +class DomainSerializer(NamespacedHMSerializer): class Meta: - model = Domain + model = machines.Domain fields = ('interface_parent', 'name', 'extension', 'cname', 'api_url') - extra_kwargs = { - 'interface_parent': {'view_name': 'api:interface-detail'}, - 'extension': {'view_name': 'api:extension-detail'}, - 'cname': {'view_name': 'api:domain-detail'}, - 'api_url': {'view_name': 'api:domain-detail'} - } -class IpListSerializer(serializers.HyperlinkedModelSerializer): +class IpListSerializer(NamespacedHMSerializer): class Meta: - model = IpList + model = machines.IpList fields = ('ipv4', 'ip_type', 'need_infra', 'api_url') - extra_kwargs = { - 'ip_type': {'view_name': 'api:iptype-detail'}, - 'api_url': {'view_name': 'api:iplist-detail'} - } -class ServiceSerializer(serializers.HyperlinkedModelSerializer): +class ServiceSerializer(NamespacedHMSerializer): class Meta: - model = Service + model = machines.Service fields = ('service_type', 'min_time_regen', 'regular_time_regen', 'servers', 'api_url') - extra_kwargs = { - 'servers': {'view_name': 'api:interface-detail'}, - 'api_url': {'view_name': 'api:service-detail'} - } -class ServiceLinkSerializer(serializers.HyperlinkedModelSerializer): +class ServiceLinkSerializer(NamespacedHMSerializer): class Meta: - model = Service_link + model = machines.Service_link fields = ('service', 'server', 'last_regen', 'asked_regen', 'need_regen', 'api_url') - extra_kwargs = { - 'service': {'view_name': 'api:service-detail'}, - 'server': {'view_name': 'api:interface-detail'}, - 'api_url': {'view_name': 'api:servicelink-detail'} - } -class OuverturePortListSerializer(serializers.HyperlinkedModelSerializer): +class OuverturePortListSerializer(NamespacedHMSerializer): class Meta: - model = OuverturePortList + model = machines.OuverturePortList fields = ('name', 'tcp_ports_in', 'udp_ports_in', 'tcp_ports_out', 'udp_ports_out', 'api_url') - extra_kwargs = { - 'tcp_ports_in': {'view_name': 'api:ouvertureport-detail'}, - 'udp_ports_in': {'view_name': 'api:ouvertureport-detail'}, - 'tcp_ports_out': {'view_name': 'api:ouvertureport-detail'}, - 'udp_ports_out': {'view_name': 'api:ouvertureport-detail'}, - 'api_url': {'view_name': 'api:ouvertureportlist-detail'} - } -class OuverturePortSerializer(serializers.HyperlinkedModelSerializer): +class OuverturePortSerializer(NamespacedHMSerializer): class Meta: - model = OuverturePort + model = machines.OuverturePort fields = ('begin', 'end', 'port_list', 'protocole', 'io', 'api_url') - extra_kwargs = { - 'port_list': {'view_name': 'api:ouvertureportlist-detail'}, - 'api_url': {'view_name': 'api:ouvertureport-detail'} - } # PREFERENCES APP -# class OptionalUserSerializer(serializers.HyperlinkedModelSerializer): +# class OptionalUserSerializer(NamespacedHMSerializer): # tel_mandatory = serializers.BooleanField(source='is_tel_mandatory') # # class Meta: -# model = OptionalUser +# model = preferences.OptionalUser # fields = ('tel_mandatory', 'user_solde', 'solde_negatif', 'max_solde', # 'min_online_payement', 'gpg_fingerprint', # 'all_can_create_club', 'self_adhesion', 'shell_default', # 'api_url') -# extra_kwargs = { -# 'shell_default': {'view_name': 'api:shell-detail'}, -# 'api_url': {'view_name': 'api:optionaluser-detail'} -# } # # -# class OptionalMachineSerializer(serializers.HyperlinkedModelSerializer): +# class OptionalMachineSerializer(NamespacedHMSerializer): # class Meta: -# model = OptionalMachine +# model = preferences.OptionalMachine # fields = ('password_machine', 'max_lambdauser_interfaces', # 'max_lambdauser_aliases', 'ipv6_mode', 'create_machine', # 'ipv6', 'api_url') -# extra_kwargs = { -# 'api_url': {'view_name': 'api:optionalmachine-detail'} -# } # # -# class OptionalTopologieSerializer(serializers.HyperlinkedModelSerializer): +# class OptionalTopologieSerializer(NamespacedHMSerializer): # class Meta: -# model = OptionalTopologie +# model = preferences.OptionalTopologie # fields = ('radius_general_policy', 'vlan_decision_ok', # 'vlan_decision_no', 'api_url') -# extra_kwargs = { -# 'vlan_decision_ok': {'view_name': 'api:vlan-detail'}, -# 'vlan_decision_nok': {'view_name': 'api:vlan-detail'}, -# 'api_url': {'view_name': 'api:optionaltopologie-detail'} -# } # # -# class GeneralOptionSerializer(serializers.HyperlinkedModelSerializer): +# class GeneralOptionSerializer(NamespacedHMSerializer): # class Meta: -# model = GeneralOption +# model = preferences.GeneralOption # fields = ('general_message', 'search_display_page', # 'pagination_number', 'pagination_large_number', # 'req_expire_hrs', 'site_name', 'email_from', 'GTU_sum_up', # 'GTU', 'api_url') -# extra_kwargs = { -# 'api_url': {'view_name': 'api:generaloption-detail'} -# } # # -# class ServiceOptionSerializer(serializers.HyperlinkedModelSerializer): +# class ServiceOptionSerializer(NamespacedHMSerializer): # class Meta: -# model = ServiceOption +# model = preferences.ServiceOption # fields = ('name', 'url', 'description', 'image', 'api_url') -# extra_kwargs = { -# 'api_url': {'view_name': 'api:serviceoption-detail'} -# } # # -# class AssoOptionSerializer(serializers.HyperlinkedModelSerializer): +# class AssoOptionSerializer(NamespacedHMSerializer): # class Meta: -# model = AssoOption +# model = preferences.AssoOption # fields = ('name', 'siret', 'adresse1', 'adresse2', 'contact', # 'telephone', 'pseudo', 'utilisateur_asso', 'payement', # 'payement_id', 'payement_pass', 'description', 'api_url') -# extra_kwargs = { -# 'utilisateur_asso': {'view_name': 'api:user-detail'}, -# 'api_url': {'view_name': 'api:assooption-detail'} -# } # # -# class HomeOptionSerializer(serializers.HyperlinkedModelSerializer): +# class HomeOptionSerializer(NamespacedHMSerializer): # class Meta: -# model = HomeOption +# model = preferences.HomeOption # fields = ('facebook_url', 'twitter_url', 'twitter_account_name', # 'api_url') -# extra_kwargs = { -# 'api_url': {'view_name': 'api:homeoption-detail'} -# } # # -# class MailMessageOptionSerializer(serializers.HyperlinkedModelSerializer): +# class MailMessageOptionSerializer(NamespacedHMSerializer): # class Meta: -# model = MailMessageOption +# model = preferences.MailMessageOption # fields = ('welcome_mail_fr', 'welcome_mail_en', 'api_url') -# extra_kwargs = { -# 'api_url': {'view_name': 'api:mailmessageoption-detail'} -# } # TOPOLOGIE APP -class StackSerializer(serializers.HyperlinkedModelSerializer): +class StackSerializer(NamespacedHMSerializer): class Meta: - model = Stack + model = topologie.Stack fields = ('name', 'stack_id', 'details', 'member_id_min', 'member_id_max', 'api_url') - extra_kwargs = { - 'api_url': {'view_name': 'api:stack-detail'} - } -class AccessPointSerializer(serializers.HyperlinkedModelSerializer): +class AccessPointSerializer(NamespacedHMSerializer): class Meta: - model = AccessPoint + model = topologie.AccessPoint fields = ('user', 'name', 'active', 'location', 'api_url') - extra_kwargs = { - 'user': {'view_name': 'api:user-detail'}, - 'api_url': {'view_name': 'api:accesspoint-detail'} - } -class SwitchSerializer(serializers.HyperlinkedModelSerializer): +class SwitchSerializer(NamespacedHMSerializer): port_amount = serializers.IntegerField(source='number') class Meta: - model = Switch + model = topologie.Switch fields = ('port_amount', 'stack', 'stack_member_id', 'model', 'switchbay', 'api_url') - extra_kwargs = { - 'stack': {'view_name': 'api:stack-detail'}, - 'model': {'view_name': 'api:modelswitch-detail'}, - 'switchbay': {'view_name': 'api:switchbay-detail'}, - 'api_url': {'view_name': 'api:switch-detail'} - } -class ModelSwitchSerializer(serializers.HyperlinkedModelSerializer): +class ModelSwitchSerializer(NamespacedHMSerializer): class Meta: - model = ModelSwitch + model = topologie.ModelSwitch fields = ('reference', 'constructor', 'api_url') - extra_kwargs = { - 'constructor': {'view_name': 'api:constructorswitch-detail'}, - 'api_url': {'view_name': 'api:modelswitch-detail'} - } -class ConstructorSwitchSerializer(serializers.HyperlinkedModelSerializer): +class ConstructorSwitchSerializer(NamespacedHMSerializer): class Meta: - model = ConstructorSwitch + model = topologie.ConstructorSwitch fields = ('name', 'api_url') - extra_kwargs = { - 'api_url': {'view_name': 'api:constructorswitch-detail'} - } -class SwitchBaySerializer(serializers.HyperlinkedModelSerializer): +class SwitchBaySerializer(NamespacedHMSerializer): class Meta: - model = SwitchBay + model = topologie.SwitchBay fields = ('name', 'building', 'info', 'api_url') - extra_kwargs = { - 'building': {'view_name': 'api:building-detail'}, - 'api_url': {'view_name': 'api:switchbay-detail'} - } -class BuildingSerializer(serializers.HyperlinkedModelSerializer): +class BuildingSerializer(NamespacedHMSerializer): class Meta: - model = Building + model = topologie.Building fields = ('name', 'api_url') - extra_kwargs = { - 'api_url': {'view_name': 'api:building-detail'} - } -class SwitchPortSerializer(serializers.HyperlinkedModelSerializer): +class SwitchPortSerializer(NamespacedHMSerializer): class Meta: - model = SwitchPort + model = topologie.Port fields = ('switch', 'port', 'room', 'machine_interface', 'related', 'radius', 'vlan_force', 'details', 'api_url') - extra_kwargs = { - 'switch': {'view_name': 'api:switch-detail'}, - 'room': {'view_name': 'api:room-detail'}, - 'machine_interface': {'view_name': 'api:interface-detail'}, - 'related': {'view_name': 'api:switchport-detail'}, - 'vlan_force': {'view_name': 'api:vlan-detail'}, - 'api_url': {'view_name': 'api:switchport-detail'} - } -class RoomSerializer(serializers.HyperlinkedModelSerializer): +class RoomSerializer(NamespacedHMSerializer): class Meta: - model = Room + model = topologie.Room fields = ('name', 'details', 'api_url') - extra_kwargs = { - 'api_url': {'view_name': 'api:room-detail'} - } # USERS APP -class UserSerializer(serializers.HyperlinkedModelSerializer): +class UserSerializer(NamespacedHMSerializer): access = serializers.BooleanField(source='has_access') uid = serializers.IntegerField(source='uid_number') class Meta: - model = User + model = users.User fields = ('name', 'pseudo', 'email', 'school', 'shell', 'comment', 'state', 'registered', 'telephone', 'solde', #'room', 'access', 'end_access', 'uid', 'class_name', 'api_url') - extra_kwargs = { - 'school': {'view_name': 'api:school-detail'}, - 'shell': {'view_name': 'api:shell-detail'}, - #'room': {'view_name': 'api:room-detail'}, - 'api_url': {'view_name': 'api:user-detail'} - } -class ClubSerializer(serializers.HyperlinkedModelSerializer): +class ClubSerializer(NamespacedHMSerializer): name = serializers.CharField(source='surname') access = serializers.BooleanField(source='has_access') uid = serializers.IntegerField(source='uid_number') class Meta: - model = Club + model = users.Club fields = ('name', 'pseudo', 'email', 'school', 'shell', 'comment', 'state', 'registered', 'telephone', 'solde', #'room', 'access', 'end_access', 'administrators', 'members', 'mailing', 'uid', 'api_url') - extra_kwargs = { - 'school': {'view_name': 'api:school-detail'}, - 'shell': {'view_name': 'api:shell-detail'}, - #'room': {'view_name': 'api:room-detail'}, - 'administrators': {'view_name': 'api:adherent-detail'}, - 'members': {'view_name': 'api:adherent-detail'}, - 'api_url': {'view_name': 'api:club-detail'} - } -class AdherentSerializer(serializers.HyperlinkedModelSerializer): +class AdherentSerializer(NamespacedHMSerializer): access = serializers.BooleanField(source='has_access') uid = serializers.IntegerField(source='uid_number') class Meta: - model = Adherent + model = users.Adherent fields = ('name', 'surname', 'pseudo', 'email', 'school', 'shell', 'comment', 'state', 'registered', 'telephone', #'room', 'solde', 'access', 'end_access', 'uid', 'api_url') - extra_kwargs = { - 'school': {'view_name': 'api:school-detail'}, - 'shell': {'view_name': 'api:shell-detail'}, - #'room': {'view_name': 'api:room-detail'}, - 'api_url': {'view_name': 'api:adherent-detail'} - } -class ServiceUserSerializer(serializers.HyperlinkedModelSerializer): +class ServiceUserSerializer(NamespacedHMSerializer): class Meta: - model = ServiceUser + model = users.ServiceUser fields = ('pseudo', 'access_group', 'comment', 'api_url') - extra_kwargs = { - 'api_url': {'view_name': 'api:serviceuser-detail'} - } -class SchoolSerializer(serializers.HyperlinkedModelSerializer): +class SchoolSerializer(NamespacedHMSerializer): class Meta: - model = School + model = users.School fields = ('name', 'api_url') - extra_kwargs = { - 'api_url': {'view_name': 'api:school-detail'} - } -class ListRightSerializer(serializers.HyperlinkedModelSerializer): +class ListRightSerializer(NamespacedHMSerializer): class Meta: - model = ListRight + model = users.ListRight fields = ('unix_name', 'gid', 'critical', 'details', 'api_url') - extra_kwargs = { - 'api_url': {'view_name': 'api:listright-detail'} - } -class ShellSerializer(serializers.HyperlinkedModelSerializer): +class ShellSerializer(NamespacedHMSerializer): class Meta: - model = ListShell + model = users.ListShell fields = ('shell', 'api_url') - extra_kwargs = { - 'api_url': {'view_name': 'api:shell-detail'} - } -class BanSerializer(serializers.HyperlinkedModelSerializer): +class BanSerializer(NamespacedHMSerializer): active = serializers.BooleanField(source='is_active') class Meta: - model = Ban + model = users.Ban fields = ('user', 'raison', 'date_start', 'date_end', 'state', 'active', 'api_url') - extra_kwargs = { - 'user': {'view_name': 'api:user-detail'}, - 'api_url': {'view_name': 'api:ban-detail'} - } -class WhitelistSerializer(serializers.HyperlinkedModelSerializer): +class WhitelistSerializer(NamespacedHMSerializer): active = serializers.BooleanField(source='is_active') class Meta: - model = Whitelist + model = users.Whitelist fields = ('user', 'raison', 'date_start', 'date_end', 'active', 'api_url') - extra_kwargs = { - 'user': {'view_name': 'api:user-detail'}, - 'api_url': {'view_name': 'api:whitelist-detail'} - } diff --git a/api/views.py b/api/views.py index 34f8a947..439ede8b 100644 --- a/api/views.py +++ b/api/views.py @@ -33,397 +33,282 @@ from rest_framework.authtoken.models import Token from rest_framework.response import Response from rest_framework import viewsets, status +import cotisations.models as cotisations +import machines.models as machines +import preferences.models as preferences +import topologie.models as topologie +import users.models as users -from cotisations.models import ( - Facture, - Vente, - Article, - Banque, - Paiement, - Cotisation -) -from machines.models import ( - Machine, - MachineType, - IpType, - Vlan, - Nas, - SOA, - Extension, - Mx, - Ns, - Txt, - Srv, - Interface, - Ipv6List, - Domain, - IpList, - Service, - Service_link, - OuverturePortList, - OuverturePort -) -# from preferences.models import ( -# OptionalUser, -# OptionalMachine, -# OptionalTopologie, -# GeneralOption, -# AssoOption, -# HomeOption, -# MailMessageOption -# ) -# # Avoid duplicate names -# from preferences.models import Service as ServiceOption -from topologie.models import ( - Stack, - AccessPoint, - Switch, - ModelSwitch, - ConstructorSwitch, - SwitchBay, - Building, - Room -) -from topologie.models import Port as SwitchPort -from users.models import ( - User, - Club, - Adherent, - ServiceUser, - School, - ListRight, - ListShell, - Ban, - Whitelist -) - -from .serializers import ( - # COTISATIONS APP - FactureSerializer, - VenteSerializer, - ArticleSerializer, - BanqueSerializer, - PaiementSerializer, - CotisationSerializer, - # MACHINES APP - MachineSerializer, - MachineTypeSerializer, - IpTypeSerializer, - VlanSerializer, - NasSerializer, - SOASerializer, - ExtensionSerializer, - MxSerializer, - NsSerializer, - TxtSerializer, - SrvSerializer, - InterfaceSerializer, - Ipv6ListSerializer, - DomainSerializer, - IpListSerializer, - ServiceSerializer, - ServiceLinkSerializer, - OuverturePortListSerializer, - OuverturePortSerializer, - # PREFERENCES APP - # OptionalUserSerializer, - # OptionalMachineSerializer, - # OptionalTopologieSerializer, - # GeneralOptionSerializer, - # ServiceOptionSerializer, - # AssoOptionSerializer, - # HomeOptionSerializer, - # MailMessageOptionSerializer, - # TOPOLOGIE APP - StackSerializer, - AccessPointSerializer, - SwitchSerializer, - ModelSwitchSerializer, - ConstructorSwitchSerializer, - SwitchBaySerializer, - BuildingSerializer, - SwitchPortSerializer, - RoomSerializer, - # USERS APP - UserSerializer, - ClubSerializer, - AdherentSerializer, - ServiceUserSerializer, - SchoolSerializer, - ListRightSerializer, - ShellSerializer, - BanSerializer, - WhitelistSerializer -) +from . import serializers # COTISATIONS APP class FactureViewSet(viewsets.ReadOnlyModelViewSet): - queryset = Facture.objects.all() - serializer_class = FactureSerializer + queryset = cotisations.Facture.objects.all() + serializer_class = serializers.FactureSerializer class VenteViewSet(viewsets.ReadOnlyModelViewSet): - queryset = Vente.objects.all() - serializer_class = VenteSerializer + queryset = cotisations.Vente.objects.all() + serializer_class = serializers.VenteSerializer class ArticleViewSet(viewsets.ReadOnlyModelViewSet): - queryset = Article.objects.all() - serializer_class = ArticleSerializer + queryset = cotisations.Article.objects.all() + serializer_class = serializers.ArticleSerializer class BanqueViewSet(viewsets.ReadOnlyModelViewSet): - queryset = Banque.objects.all() - serializer_class = BanqueSerializer + queryset = cotisations.Banque.objects.all() + serializer_class = serializers.BanqueSerializer class PaiementViewSet(viewsets.ReadOnlyModelViewSet): - queryset = Paiement.objects.all() - serializer_class = PaiementSerializer + queryset = cotisations.Paiement.objects.all() + serializer_class = serializers.PaiementSerializer class CotisationViewSet(viewsets.ReadOnlyModelViewSet): - queryset = Cotisation.objects.all() - serializer_class = CotisationSerializer + queryset = cotisations.Cotisation.objects.all() + serializer_class = serializers.CotisationSerializer # MACHINES APP class MachineViewSet(viewsets.ReadOnlyModelViewSet): - queryset = Machine.objects.all() - serializer_class = MachineSerializer + queryset = machines.Machine.objects.all() + serializer_class = serializers.MachineSerializer class MachineTypeViewSet(viewsets.ReadOnlyModelViewSet): - queryset = MachineType.objects.all() - serializer_class = MachineTypeSerializer + queryset = machines.MachineType.objects.all() + serializer_class = serializers.MachineTypeSerializer class IpTypeViewSet(viewsets.ReadOnlyModelViewSet): - queryset = IpType.objects.all() - serializer_class = IpTypeSerializer + queryset = machines.IpType.objects.all() + serializer_class = serializers.IpTypeSerializer class VlanViewSet(viewsets.ReadOnlyModelViewSet): - queryset = Vlan.objects.all() - serializer_class = VlanSerializer + queryset = machines.Vlan.objects.all() + serializer_class = serializers.VlanSerializer class NasViewSet(viewsets.ReadOnlyModelViewSet): - queryset = Nas.objects.all() - serializer_class = NasSerializer + queryset = machines.Nas.objects.all() + serializer_class = serializers.NasSerializer class SOAViewSet(viewsets.ReadOnlyModelViewSet): - queryset = SOA.objects.all() - serializer_class = SOASerializer + queryset = machines.SOA.objects.all() + serializer_class = serializers.SOASerializer class ExtensionViewSet(viewsets.ReadOnlyModelViewSet): - queryset = Extension.objects.all() - serializer_class = ExtensionSerializer + queryset = machines.Extension.objects.all() + serializer_class = serializers.ExtensionSerializer class MxViewSet(viewsets.ReadOnlyModelViewSet): - queryset = Mx.objects.all() - serializer_class = MxSerializer + queryset = machines.Mx.objects.all() + serializer_class = serializers.MxSerializer class NsViewSet(viewsets.ReadOnlyModelViewSet): - queryset = Ns.objects.all() - serializer_class = NsSerializer + queryset = machines.Ns.objects.all() + serializer_class = serializers.NsSerializer class TxtViewSet(viewsets.ReadOnlyModelViewSet): - queryset = Txt.objects.all() - serializer_class = TxtSerializer + queryset = machines.Txt.objects.all() + serializer_class = serializers.TxtSerializer class SrvViewSet(viewsets.ReadOnlyModelViewSet): - queryset = Srv.objects.all() - serializer_class = SrvSerializer + queryset = machines.Srv.objects.all() + serializer_class = serializers.SrvSerializer class InterfaceViewSet(viewsets.ReadOnlyModelViewSet): - queryset = Interface.objects.all() - serializer_class = InterfaceSerializer + queryset = machines.Interface.objects.all() + serializer_class = serializers.InterfaceSerializer class Ipv6ListViewSet(viewsets.ReadOnlyModelViewSet): - queryset = Ipv6List.objects.all() - serializer_class = Ipv6ListSerializer + queryset = machines.Ipv6List.objects.all() + serializer_class = serializers.Ipv6ListSerializer class DomainViewSet(viewsets.ReadOnlyModelViewSet): - queryset = Domain.objects.all() - serializer_class = DomainSerializer + queryset = machines.Domain.objects.all() + serializer_class = serializers.DomainSerializer class IpListViewSet(viewsets.ReadOnlyModelViewSet): - queryset = IpList.objects.all() - serializer_class = IpListSerializer + queryset = machines.IpList.objects.all() + serializer_class = serializers.IpListSerializer class ServiceViewSet(viewsets.ReadOnlyModelViewSet): - queryset = Service.objects.all() - serializer_class = ServiceSerializer + queryset = machines.Service.objects.all() + serializer_class = serializers.ServiceSerializer class ServiceLinkViewSet(viewsets.ReadOnlyModelViewSet): - queryset = Service_link.objects.all() - serializer_class = ServiceLinkSerializer + queryset = machines.Service_link.objects.all() + serializer_class = serializers.ServiceLinkSerializer class OuverturePortListViewSet(viewsets.ReadOnlyModelViewSet): - queryset = OuverturePortList.objects.all() - serializer_class = OuverturePortListSerializer + queryset = machines.OuverturePortList.objects.all() + serializer_class = serializers.OuverturePortListSerializer class OuverturePortViewSet(viewsets.ReadOnlyModelViewSet): - queryset = OuverturePort.objects.all() - serializer_class = OuverturePortSerializer + queryset = machines.OuverturePort.objects.all() + serializer_class = serializers.OuverturePortSerializer # PREFERENCES APP # class OptionalUserViewSet(viewsets.ReadOnlyModelViewSet): -# queryset = OptionalUser.objects.all() -# serializer_class = OptionalUserSerializer +# queryset = preferences.OptionalUser.objects.all() +# serializer_class = serializers.OptionalUserSerializer # # # class OptionalMachineViewSet(viewsets.ReadOnlyModelViewSet): -# queryset = OptionalMachine.objects.all() -# serializer_class = OptionalMachineSerializer +# queryset = preferences.OptionalMachine.objects.all() +# serializer_class = serializers.OptionalMachineSerializer # # # class OptionalTopologieViewSet(viewsets.ReadOnlyModelViewSet): -# queryset = OptionalTopologie.objects.all() -# serializer_class = OptionalTopologieSerializer +# queryset = preferences.OptionalTopologie.objects.all() +# serializer_class = serializers.OptionalTopologieSerializer # # # class GeneralOptionViewSet(viewsets.ReadOnlyModelViewSet): -# queryset = GeneralOption.objects.all() -# serializer_class = GeneralOptionSerializer +# queryset = preferences.GeneralOption.objects.all() +# serializer_class = serializers.GeneralOptionSerializer # # # class ServiceOptionViewSet(viewsets.ReadOnlyModelViewSet): -# queryset = ServiceOption.objects.all() -# serializer_class = ServiceOptionSerializer +# queryset = preferences.ServiceOption.objects.all() +# serializer_class = serializers.ServiceOptionSerializer # # # class AssoOptionViewSet(viewsets.ReadOnlyModelViewSet): -# queryset = AssoOption.objects.all() -# serializer_class = AssoOptionSerializer +# queryset = preferences.AssoOption.objects.all() +# serializer_class = serializers.AssoOptionSerializer # # # class HomeOptionViewSet(viewsets.ReadOnlyModelViewSet): -# queryset = HomeOption.objects.all() -# serializer_class = HomeOptionSerializer +# queryset = preferences.HomeOption.objects.all() +# serializer_class = serializers.HomeOptionSerializer # # # class MailMessageOptionViewSet(viewsets.ReadOnlyModelViewSet): -# queryset = MailMessageOption.objects.all() -# serializer_class = MailMessageOptionSerializer +# queryset = preferences.MailMessageOption.objects.all() +# serializer_class = serializers.MailMessageOptionSerializer # TOPOLOGIE APP class StackViewSet(viewsets.ReadOnlyModelViewSet): - queryset = Stack.objects.all() - serializer_class = StackSerializer + queryset = topologie.Stack.objects.all() + serializer_class = serializers.StackSerializer class AccessPointViewSet(viewsets.ReadOnlyModelViewSet): - queryset = AccessPoint.objects.all() - serializer_class = AccessPointSerializer + queryset = topologie.AccessPoint.objects.all() + serializer_class = serializers.AccessPointSerializer class SwitchViewSet(viewsets.ReadOnlyModelViewSet): - queryset = Switch.objects.all() - serializer_class = SwitchSerializer + queryset = topologie.Switch.objects.all() + serializer_class = serializers.SwitchSerializer class ModelSwitchViewSet(viewsets.ReadOnlyModelViewSet): - queryset = ModelSwitch.objects.all() - serializer_class = ModelSwitchSerializer + queryset = topologie.ModelSwitch.objects.all() + serializer_class = serializers.ModelSwitchSerializer class ConstructorSwitchViewSet(viewsets.ReadOnlyModelViewSet): - queryset = ConstructorSwitch.objects.all() - serializer_class = ConstructorSwitchSerializer + queryset = topologie.ConstructorSwitch.objects.all() + serializer_class = serializers.ConstructorSwitchSerializer class SwitchBayViewSet(viewsets.ReadOnlyModelViewSet): - queryset = SwitchBay.objects.all() - serializer_class = SwitchBaySerializer + queryset = topologie.SwitchBay.objects.all() + serializer_class = serializers.SwitchBaySerializer class BuildingViewSet(viewsets.ReadOnlyModelViewSet): - queryset = Building.objects.all() - serializer_class = BuildingSerializer + queryset = topologie.Building.objects.all() + serializer_class = serializers.BuildingSerializer class SwitchPortViewSet(viewsets.ReadOnlyModelViewSet): - queryset = SwitchPort.objects.all() - serializer_class = SwitchPortSerializer + queryset = topologie.Port.objects.all() + serializer_class = serializers.SwitchPortSerializer class RoomViewSet(viewsets.ReadOnlyModelViewSet): - queryset = Room.objects.all() - serializer_class = RoomSerializer + queryset = topologie.Room.objects.all() + serializer_class = serializers.RoomSerializer # USER APP class UserViewSet(viewsets.ReadOnlyModelViewSet): - queryset = User.objects.all() - serializer_class = UserSerializer + queryset = users.User.objects.all() + serializer_class = serializers.UserSerializer class ClubViewSet(viewsets.ReadOnlyModelViewSet): - queryset = Club.objects.all() - serializer_class = ClubSerializer + queryset = users.Club.objects.all() + serializer_class = serializers.ClubSerializer class AdherentViewSet(viewsets.ReadOnlyModelViewSet): - queryset = Adherent.objects.all() - serializer_class = AdherentSerializer + queryset = users.Adherent.objects.all() + serializer_class = serializers.AdherentSerializer class ServiceUserViewSet(viewsets.ReadOnlyModelViewSet): - queryset = ServiceUser.objects.all() - serializer_class = ServiceUserSerializer + queryset = users.ServiceUser.objects.all() + serializer_class = serializers.ServiceUserSerializer class SchoolViewSet(viewsets.ReadOnlyModelViewSet): - queryset = School.objects.all() - serializer_class = SchoolSerializer + queryset = users.School.objects.all() + serializer_class = serializers.SchoolSerializer class ListRightViewSet(viewsets.ReadOnlyModelViewSet): - queryset = ListRight.objects.all() - serializer_class = ListRightSerializer + queryset = users.ListRight.objects.all() + serializer_class = serializers.ListRightSerializer class ShellViewSet(viewsets.ReadOnlyModelViewSet): - queryset = ListShell.objects.all() - serializer_class = ShellSerializer + queryset = users.ListShell.objects.all() + serializer_class = serializers.ShellSerializer class BanViewSet(viewsets.ReadOnlyModelViewSet): - queryset = Ban.objects.all() - serializer_class = BanSerializer + queryset = users.Ban.objects.all() + serializer_class = serializers.BanSerializer class WhitelistViewSet(viewsets.ReadOnlyModelViewSet): - queryset = Whitelist.objects.all() - serializer_class = WhitelistSerializer + queryset = users.Whitelist.objects.all() + serializer_class = serializers.WhitelistSerializer # Subclass the standard rest_framework.auth_token.views.ObtainAuthToken # in order to renew the lease of the token and add expiration time From 59c48759f20dd2b2ec4bcf9c857053f0092539a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Kervella?= Date: Tue, 24 Apr 2018 16:22:56 +0000 Subject: [PATCH 13/42] Separation of url according to app --- api/urls.py | 102 ++++++++++++++++++++++++++-------------------------- 1 file changed, 51 insertions(+), 51 deletions(-) diff --git a/api/urls.py b/api/urls.py index f01e6a3e..a6d63af2 100644 --- a/api/urls.py +++ b/api/urls.py @@ -31,61 +31,61 @@ from . import views router = DefaultRouter() # COTISATIONS APP -router.register(r'factures', views.FactureViewSet) -router.register(r'ventes', views.VenteViewSet) -router.register(r'articles', views.ArticleViewSet) -router.register(r'banques', views.BanqueViewSet) -router.register(r'paiements', views.PaiementViewSet) -router.register(r'cotisations', views.CotisationViewSet) +router.register(r'cotisations/factures', views.FactureViewSet) +router.register(r'cotisations/ventes', views.VenteViewSet) +router.register(r'cotisations/articles', views.ArticleViewSet) +router.register(r'cotisations/banques', views.BanqueViewSet) +router.register(r'cotisations/paiements', views.PaiementViewSet) +router.register(r'cotisations/cotisations', views.CotisationViewSet) # MACHINES APP -router.register(r'machines', views.MachineViewSet) -router.register(r'machinetypes', views.MachineTypeViewSet) -router.register(r'iptypes', views.IpTypeViewSet) -router.register(r'vlans', views.VlanViewSet) -router.register(r'nas', views.NasViewSet) -router.register(r'soa', views.SOAViewSet) -router.register(r'extensions', views.ExtensionViewSet) -router.register(r'mx', views.MxViewSet) -router.register(r'ns', views.NsViewSet) -router.register(r'txt', views.TxtViewSet) -router.register(r'srv', views.SrvViewSet) -router.register(r'interfaces', views.InterfaceViewSet) -router.register(r'ipv6lists', views.Ipv6ListViewSet) -router.register(r'domains', views.DomainViewSet) -router.register(r'iplists', views.IpListViewSet) -router.register(r'services', views.ServiceViewSet) -router.register(r'servicelinks', views.ServiceLinkViewSet, 'servicelink') -router.register(r'ouvertureportlists', views.OuverturePortListViewSet) -router.register(r'ouvertureports', views.OuverturePortViewSet) +router.register(r'machines/machines', views.MachineViewSet) +router.register(r'machines/machinetypes', views.MachineTypeViewSet) +router.register(r'machines/iptypes', views.IpTypeViewSet) +router.register(r'machines/vlans', views.VlanViewSet) +router.register(r'machines/nas', views.NasViewSet) +router.register(r'machines/soa', views.SOAViewSet) +router.register(r'machines/extensions', views.ExtensionViewSet) +router.register(r'machines/mx', views.MxViewSet) +router.register(r'machines/ns', views.NsViewSet) +router.register(r'machines/txt', views.TxtViewSet) +router.register(r'machines/srv', views.SrvViewSet) +router.register(r'machines/interfaces', views.InterfaceViewSet) +router.register(r'machines/ipv6lists', views.Ipv6ListViewSet) +router.register(r'machines/domains', views.DomainViewSet) +router.register(r'machines/iplists', views.IpListViewSet) +router.register(r'machines/services', views.ServiceViewSet) +router.register(r'machines/servicelinks', views.ServiceLinkViewSet, base_name='servicelink') +router.register(r'machines/ouvertureportlists', views.OuverturePortListViewSet) +router.register(r'machines/ouvertureports', views.OuverturePortViewSet) # PREFERENCES APP -#router.register(r'optionaluser', views.OptionalUserSerializer) -#router.register(r'optionalmachine', views.OptionalMachineSerializer) -#router.register(r'optionaltopologie', views.OptionalTopologieSerializer) -#router.register(r'generaloption', views.GeneralOptionSerializer) -#router.register(r'serviceoption', views.ServiceOptionSerializer) -#router.register(r'assooption', views.AssoOptionSerializer) -#router.register(r'homeoption', views.HomeOptionSerializer) -#router.register(r'mailmessageoption', views.MailMessageOptionSerializer) +#router.register(r'preferences/optionaluser', views.OptionalUserSerializer) +#router.register(r'preferences/optionalmachine', views.OptionalMachineSerializer) +#router.register(r'preferences/optionaltopologie', views.OptionalTopologieSerializer) +#router.register(r'preferences/generaloption', views.GeneralOptionSerializer) +#router.register(r'preferences/serviceoption', views.ServiceOptionSerializer) +#router.register(r'preferences/assooption', views.AssoOptionSerializer) +#router.register(r'preferences/homeoption', views.HomeOptionSerializer) +#router.register(r'preferences/mailmessageoption', views.MailMessageOptionSerializer) # TOPOLOGIE APP -router.register(r'stack', views.StackViewSet) -router.register(r'acesspoint', views.AccessPointViewSet) -router.register(r'switch', views.SwitchViewSet) -router.register(r'modelswitch', views.ModelSwitchViewSet) -router.register(r'constructorswitch', views.ConstructorSwitchViewSet) -router.register(r'switchbay', views.SwitchBayViewSet) -router.register(r'building', views.BuildingViewSet) -router.register(r'switchport', views.SwitchPortViewSet, 'switchport') -router.register(r'room', views.RoomViewSet) +router.register(r'topologie/stack', views.StackViewSet) +router.register(r'topologie/acesspoint', views.AccessPointViewSet) +router.register(r'topologie/switch', views.SwitchViewSet) +router.register(r'topologie/modelswitch', views.ModelSwitchViewSet) +router.register(r'topologie/constructorswitch', views.ConstructorSwitchViewSet) +router.register(r'topologie/switchbay', views.SwitchBayViewSet) +router.register(r'topologie/building', views.BuildingViewSet) +router.register(r'topologie/switchport', views.SwitchPortViewSet, base_name='switchport') +router.register(r'topologie/room', views.RoomViewSet) # USERS APP -router.register(r'users', views.UserViewSet) -router.register(r'clubs', views.ClubViewSet) -router.register(r'adherents', views.AdherentViewSet) -router.register(r'serviceusers', views.ServiceUserViewSet) -router.register(r'schools', views.SchoolViewSet) -router.register(r'listrights', views.ListRightViewSet) -router.register(r'shells', views.ShellViewSet, 'shell') -router.register(r'bans', views.BanViewSet) -router.register(r'whitelists', views.WhitelistViewSet) +router.register(r'users/users', views.UserViewSet) +router.register(r'users/clubs', views.ClubViewSet) +router.register(r'users/adherents', views.AdherentViewSet) +router.register(r'users/serviceusers', views.ServiceUserViewSet) +router.register(r'users/schools', views.SchoolViewSet) +router.register(r'users/listrights', views.ListRightViewSet) +router.register(r'users/shells', views.ShellViewSet, base_name='shell') +router.register(r'users/bans', views.BanViewSet) +router.register(r'users/whitelists', views.WhitelistViewSet) urlpatterns = [ url(r'^', include(router.urls)), From b1738f189baacd865a1e0e0bb789cf03d81665b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Kervella?= Date: Thu, 26 Apr 2018 18:41:13 +0000 Subject: [PATCH 14/42] Add pagination to API results --- api/settings.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/api/settings.py b/api/settings.py index 028ec01d..6bf48e33 100644 --- a/api/settings.py +++ b/api/settings.py @@ -34,7 +34,9 @@ REST_FRAMEWORK = { ), 'DEFAULT_PERMISSION_CLASSES': ( 'api.permissions.DefaultACLPermission', - ) + ), + 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination', + 'PAGE_SIZE': 100 } # API permission settings From 7daa53663a5a0d694e36b57eaf2582486b1eadd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Kervella?= Date: Thu, 26 Apr 2018 22:44:27 +0000 Subject: [PATCH 15/42] Cleanup API --- api/serializers.py | 439 +++--------------------------------- api/urls.py | 38 ---- api/views.py | 551 --------------------------------------------- 3 files changed, 26 insertions(+), 1002 deletions(-) diff --git a/api/serializers.py b/api/serializers.py index eab81940..fe01bf02 100644 --- a/api/serializers.py +++ b/api/serializers.py @@ -178,6 +178,7 @@ class SrvSerializer(NamespacedHMSerializer): class InterfaceSerializer(NamespacedHMSerializer): + mac_address = serializers.CharField() active = serializers.BooleanField(source='is_active') class Meta: @@ -218,6 +219,9 @@ class ServiceLinkSerializer(NamespacedHMSerializer): model = machines.Service_link fields = ('service', 'server', 'last_regen', 'asked_regen', 'need_regen', 'api_url') + extra_kwargs = { + 'api_url': {'view_name': 'servicelink-detail'} + } class OuverturePortListSerializer(NamespacedHMSerializer): @@ -319,8 +323,8 @@ class SwitchSerializer(NamespacedHMSerializer): port_amount = serializers.IntegerField(source='number') class Meta: model = topologie.Switch - fields = ('port_amount', 'stack', 'stack_member_id', 'model', - 'switchbay', 'api_url') + fields = ('user', 'name', 'active', 'port_amount', 'stack', + 'stack_member_id', 'model', 'switchbay', 'api_url') class ModelSwitchSerializer(NamespacedHMSerializer): @@ -352,6 +356,10 @@ class SwitchPortSerializer(NamespacedHMSerializer): model = topologie.Port fields = ('switch', 'port', 'room', 'machine_interface', 'related', 'radius', 'vlan_force', 'details', 'api_url') + extra_kwargs = { + 'related': {'view_name': 'switchport-detail'}, + 'api_url': {'view_name': 'switchport-detail'} + } class RoomSerializer(NamespacedHMSerializer): @@ -370,8 +378,11 @@ class UserSerializer(NamespacedHMSerializer): class Meta: model = users.User fields = ('name', 'pseudo', 'email', 'school', 'shell', 'comment', - 'state', 'registered', 'telephone', 'solde', #'room', - 'access', 'end_access', 'uid', 'class_name', 'api_url') + 'state', 'registered', 'telephone', 'solde', 'access', + 'end_access', 'uid', 'class_name', 'api_url') + extra_kwargs = { + 'shell': {'view_name': 'shell-detail'} + } class ClubSerializer(NamespacedHMSerializer): @@ -382,9 +393,12 @@ class ClubSerializer(NamespacedHMSerializer): class Meta: model = users.Club fields = ('name', 'pseudo', 'email', 'school', 'shell', 'comment', - 'state', 'registered', 'telephone', 'solde', #'room', + 'state', 'registered', 'telephone', 'solde', 'room', 'access', 'end_access', 'administrators', 'members', 'mailing', 'uid', 'api_url') + extra_kwargs = { + 'shell': {'view_name': 'shell-detail'} + } class AdherentSerializer(NamespacedHMSerializer): @@ -394,8 +408,11 @@ class AdherentSerializer(NamespacedHMSerializer): class Meta: model = users.Adherent fields = ('name', 'surname', 'pseudo', 'email', 'school', 'shell', - 'comment', 'state', 'registered', 'telephone', #'room', + 'comment', 'state', 'registered', 'telephone', 'room', 'solde', 'access', 'end_access', 'uid', 'api_url') + extra_kwargs = { + 'shell': {'view_name': 'shell-detail'} + } class ServiceUserSerializer(NamespacedHMSerializer): @@ -420,6 +437,9 @@ class ShellSerializer(NamespacedHMSerializer): class Meta: model = users.ListShell fields = ('shell', 'api_url') + extra_kwargs = { + 'api_url': {'view_name': 'shell-detail'} + } class BanSerializer(NamespacedHMSerializer): @@ -437,410 +457,3 @@ class WhitelistSerializer(NamespacedHMSerializer): class Meta: model = users.Whitelist fields = ('user', 'raison', 'date_start', 'date_end', 'active', 'api_url') - - - -# class ServiceLinkSerializer(serializers.ModelSerializer): -# """ Serializer for the ServiceLink objects """ -# -# name = serializers.CharField(source='service.service_type') -# -# class Meta: -# model = Service_link -# fields = ('name',) -# -# -# class MailingSerializer(serializers.ModelSerializer): -# """ Serializer to build Mailing objects """ -# -# name = serializers.CharField(source='pseudo') -# -# class Meta: -# model = Club -# fields = ('name',) -# -# -# class MailingMemberSerializer(serializers.ModelSerializer): -# """ Serializer fot the Adherent objects (who belong to a -# Mailing) """ -# -# class Meta: -# model = Adherent -# fields = ('email',) -# -# -# class IpTypeField(serializers.RelatedField): -# """ Serializer for an IpType object field """ -# -# def to_representation(self, value): -# return value.type -# -# def to_internal_value(self, data): -# pass -# -# -# class IpListSerializer(serializers.ModelSerializer): -# """ Serializer for an Ipv4List obejct using the IpType serialization """ -# -# ip_type = IpTypeField(read_only=True) -# -# class Meta: -# model = IpList -# fields = ('ipv4', 'ip_type') -# -# -# class Ipv6ListSerializer(serializers.ModelSerializer): -# """ Serializer for an Ipv6List object """ -# -# class Meta: -# model = Ipv6List -# fields = ('ipv6', 'slaac_ip') -# -# -# class InterfaceSerializer(serializers.ModelSerializer): -# """ Serializer for an Interface object. Use SerializerMethodField -# to get ForeignKey values """ -# -# ipv4 = IpListSerializer(read_only=True) -# # TODO : use serializer.RelatedField to avoid duplicate code -# mac_address = serializers.SerializerMethodField('get_macaddress') -# domain = serializers.SerializerMethodField('get_dns') -# extension = serializers.SerializerMethodField('get_interface_extension') -# -# class Meta: -# model = Interface -# fields = ('ipv4', 'mac_address', 'domain', 'extension') -# -# @staticmethod -# def get_dns(obj): -# """ The name of the associated DNS object """ -# return obj.domain.name -# -# @staticmethod -# def get_interface_extension(obj): -# """ The name of the associated Interface object """ -# return obj.domain.extension.name -# -# @staticmethod -# def get_macaddress(obj): -# """ The string representation of the associated MAC address """ -# return str(obj.mac_address) -# -# -# class FullInterfaceSerializer(serializers.ModelSerializer): -# """ Serializer for an Interface obejct. Use SerializerMethodField -# to get ForeignKey values """ -# -# ipv4 = IpListSerializer(read_only=True) -# ipv6 = Ipv6ListSerializer(read_only=True, many=True) -# # TODO : use serializer.RelatedField to avoid duplicate code -# mac_address = serializers.SerializerMethodField('get_macaddress') -# domain = serializers.SerializerMethodField('get_dns') -# extension = serializers.SerializerMethodField('get_interface_extension') -# -# class Meta: -# model = Interface -# fields = ('ipv4', 'ipv6', 'mac_address', 'domain', 'extension') -# -# @staticmethod -# def get_dns(obj): -# """ The name of the associated DNS object """ -# return obj.domain.name -# -# @staticmethod -# def get_interface_extension(obj): -# """ The name of the associated Extension object """ -# return obj.domain.extension.name -# -# @staticmethod -# def get_macaddress(obj): -# """ The string representation of the associated MAC address """ -# return str(obj.mac_address) -# -# -# class ExtensionNameField(serializers.RelatedField): -# """ Serializer for Extension object field """ -# -# def to_representation(self, value): -# return value.name -# -# def to_internal_value(self, data): -# pass -# -# -# class TypeSerializer(serializers.ModelSerializer): -# """ Serializer for an IpType object. Use SerializerMethodField to -# get ForeignKey values """ -# -# extension = ExtensionNameField(read_only=True) -# ouverture_ports_tcp_in = serializers\ -# .SerializerMethodField('get_port_policy_input_tcp') -# ouverture_ports_tcp_out = serializers\ -# .SerializerMethodField('get_port_policy_output_tcp') -# ouverture_ports_udp_in = serializers\ -# .SerializerMethodField('get_port_policy_input_udp') -# ouverture_ports_udp_out = serializers\ -# .SerializerMethodField('get_port_policy_output_udp') -# -# class Meta: -# model = IpType -# fields = ('type', 'extension', 'domaine_ip_start', 'domaine_ip_stop', -# 'prefix_v6', -# 'ouverture_ports_tcp_in', 'ouverture_ports_tcp_out', -# 'ouverture_ports_udp_in', 'ouverture_ports_udp_out',) -# -# @staticmethod -# def get_port_policy(obj, protocole, io): -# """ Generic utility function to get the policy for a given -# port, protocole and IN or OUT """ -# if obj.ouverture_ports is None: -# return [] -# return map( -# str, -# obj.ouverture_ports.ouvertureport_set.filter( -# protocole=protocole -# ).filter(io=io) -# ) -# -# def get_port_policy_input_tcp(self, obj): -# """Renvoie la liste des ports ouverts en entrée tcp""" -# return self.get_port_policy(obj, OuverturePort.TCP, OuverturePort.IN) -# -# def get_port_policy_output_tcp(self, obj): -# """Renvoie la liste des ports ouverts en sortie tcp""" -# return self.get_port_policy(obj, OuverturePort.TCP, OuverturePort.OUT) -# -# def get_port_policy_input_udp(self, obj): -# """Renvoie la liste des ports ouverts en entrée udp""" -# return self.get_port_policy(obj, OuverturePort.UDP, OuverturePort.IN) -# -# def get_port_policy_output_udp(self, obj): -# """Renvoie la liste des ports ouverts en sortie udp""" -# return self.get_port_policy(obj, OuverturePort.UDP, OuverturePort.OUT) -# -# -# class ExtensionSerializer(serializers.ModelSerializer): -# """Serialisation d'une extension : origin_ip et la zone sont -# des foreign_key donc evalués en get_...""" -# origin = serializers.SerializerMethodField('get_origin_ip') -# zone_entry = serializers.SerializerMethodField('get_zone_name') -# soa = serializers.SerializerMethodField('get_soa_data') -# -# class Meta: -# model = Extension -# fields = ('name', 'origin', 'origin_v6', 'zone_entry', 'soa') -# -# @staticmethod -# def get_origin_ip(obj): -# """ The IP of the associated origin for the zone """ -# return obj.origin.ipv4 -# -# @staticmethod -# def get_zone_name(obj): -# """ The name of the associated zone """ -# return str(obj.dns_entry) -# -# @staticmethod -# def get_soa_data(obj): -# """ The representation of the associated SOA """ -# return {'mail': obj.soa.dns_soa_mail, 'param': obj.soa.dns_soa_param} -# -# -# class MxSerializer(serializers.ModelSerializer): -# """Serialisation d'un MX, evaluation du nom, de la zone -# et du serveur cible, etant des foreign_key""" -# name = serializers.SerializerMethodField('get_entry_name') -# zone = serializers.SerializerMethodField('get_zone_name') -# mx_entry = serializers.SerializerMethodField('get_mx_name') -# -# class Meta: -# model = Mx -# fields = ('zone', 'priority', 'name', 'mx_entry') -# -# @staticmethod -# def get_entry_name(obj): -# """ The name of the DNS MX entry """ -# return str(obj.name) -# -# @staticmethod -# def get_zone_name(obj): -# """ The name of the associated zone of the MX record """ -# return obj.zone.name -# -# @staticmethod -# def get_mx_name(obj): -# """ The string representation of the entry to add to the DNS """ -# return str(obj.dns_entry) -# -# -# class TxtSerializer(serializers.ModelSerializer): -# """Serialisation d'un txt : zone cible et l'entrée txt -# sont evaluées à part""" -# zone = serializers.SerializerMethodField('get_zone_name') -# txt_entry = serializers.SerializerMethodField('get_txt_name') -# -# class Meta: -# model = Txt -# fields = ('zone', 'txt_entry', 'field1', 'field2') -# -# @staticmethod -# def get_zone_name(obj): -# """ The name of the associated zone """ -# return str(obj.zone.name) -# -# @staticmethod -# def get_txt_name(obj): -# """ The string representation of the entry to add to the DNS """ -# return str(obj.dns_entry) -# -# -# class SrvSerializer(serializers.ModelSerializer): -# """Serialisation d'un srv : zone cible et l'entrée txt""" -# extension = serializers.SerializerMethodField('get_extension_name') -# srv_entry = serializers.SerializerMethodField('get_srv_name') -# -# class Meta: -# model = Srv -# fields = ( -# 'service', -# 'protocole', -# 'extension', -# 'ttl', -# 'priority', -# 'weight', -# 'port', -# 'target', -# 'srv_entry' -# ) -# -# @staticmethod -# def get_extension_name(obj): -# """ The name of the associated extension """ -# return str(obj.extension.name) -# -# @staticmethod -# def get_srv_name(obj): -# """ The string representation of the entry to add to the DNS """ -# return str(obj.dns_entry) -# -# -# class NsSerializer(serializers.ModelSerializer): -# """Serialisation d'un NS : la zone, l'entrée ns complète et le serveur -# ns sont évalués à part""" -# zone = serializers.SerializerMethodField('get_zone_name') -# ns = serializers.SerializerMethodField('get_domain_name') -# ns_entry = serializers.SerializerMethodField('get_text_name') -# -# class Meta: -# model = Ns -# fields = ('zone', 'ns', 'ns_entry') -# -# @staticmethod -# def get_zone_name(obj): -# """ The name of the associated zone """ -# return obj.zone.name -# -# @staticmethod -# def get_domain_name(obj): -# """ The name of the associated NS target """ -# return str(obj.ns) -# -# @staticmethod -# def get_text_name(obj): -# """ The string representation of the entry to add to the DNS """ -# return str(obj.dns_entry) -# -# -# class DomainSerializer(serializers.ModelSerializer): -# """Serialisation d'un domain, extension, cname sont des foreign_key, -# et l'entrée complète, sont évalués à part""" -# extension = serializers.SerializerMethodField('get_zone_name') -# cname = serializers.SerializerMethodField('get_alias_name') -# cname_entry = serializers.SerializerMethodField('get_cname_name') -# -# class Meta: -# model = Domain -# fields = ('name', 'extension', 'cname', 'cname_entry') -# -# @staticmethod -# def get_zone_name(obj): -# """ The name of the associated zone """ -# return obj.extension.name -# -# @staticmethod -# def get_alias_name(obj): -# """ The name of the associated alias """ -# return str(obj.cname) -# -# @staticmethod -# def get_cname_name(obj): -# """ The name of the associated CNAME target """ -# return str(obj.dns_entry) -# -# -# class ServicesSerializer(serializers.ModelSerializer): -# """Evaluation d'un Service, et serialisation""" -# server = serializers.SerializerMethodField('get_server_name') -# service = serializers.SerializerMethodField('get_service_name') -# need_regen = serializers.SerializerMethodField('get_regen_status') -# -# class Meta: -# model = Service_link -# fields = ('server', 'service', 'need_regen') -# -# @staticmethod -# def get_server_name(obj): -# """ The name of the associated server """ -# return str(obj.server.domain.name) -# -# @staticmethod -# def get_service_name(obj): -# """ The name of the service name """ -# return str(obj.service) -# -# @staticmethod -# def get_regen_status(obj): -# """ The string representation of the regen status """ -# return obj.need_regen() -# -# -# class OuverturePortsSerializer(serializers.Serializer): -# """Serialisation de l'ouverture des ports""" -# ipv4 = serializers.SerializerMethodField() -# ipv6 = serializers.SerializerMethodField() -# -# def create(self, validated_data): -# """ Creates a new object based on the un-serialized data. -# Used to implement an abstract inherited method """ -# pass -# -# def update(self, instance, validated_data): -# """ Updates an object based on the un-serialized data. -# Used to implement an abstract inherited method """ -# pass -# -# @staticmethod -# def get_ipv4(): -# """ The representation of the policy for the IPv4 addresses """ -# return { -# i.ipv4.ipv4: { -# "tcp_in": [j.tcp_ports_in() for j in i.port_lists.all()], -# "tcp_out": [j.tcp_ports_out()for j in i.port_lists.all()], -# "udp_in": [j.udp_ports_in() for j in i.port_lists.all()], -# "udp_out": [j.udp_ports_out() for j in i.port_lists.all()], -# } -# for i in Interface.objects.all() if i.ipv4 -# } -# -# @staticmethod -# def get_ipv6(): -# """ The representation of the policy for the IPv6 addresses """ -# return { -# i.ipv6: { -# "tcp_in": [j.tcp_ports_in() for j in i.port_lists.all()], -# "tcp_out": [j.tcp_ports_out()for j in i.port_lists.all()], -# "udp_in": [j.udp_ports_in() for j in i.port_lists.all()], -# "udp_out": [j.udp_ports_out() for j in i.port_lists.all()], -# } -# for i in Interface.objects.all() if i.ipv6 -# } diff --git a/api/urls.py b/api/urls.py index a6d63af2..03e5026b 100644 --- a/api/urls.py +++ b/api/urls.py @@ -90,42 +90,4 @@ router.register(r'users/whitelists', views.WhitelistViewSet) urlpatterns = [ url(r'^', include(router.urls)), url(r'^token-auth/', views.ObtainExpiringAuthToken.as_view()) -# # Services -# url(r'^services/$', views.services), -# url( -# r'^services/(?P\w+)/(?P\w+)/regen/$', -# views.services_server_service_regen -# ), -# url(r'^services/(?P\w+)/$', views.services_server), -# -# # DNS -# url(r'^dns/mac-ip-dns/$', views.dns_mac_ip_dns), -# url(r'^dns/alias/$', views.dns_alias), -# url(r'^dns/corresp/$', views.dns_corresp), -# url(r'^dns/mx/$', views.dns_mx), -# url(r'^dns/ns/$', views.dns_ns), -# url(r'^dns/txt/$', views.dns_txt), -# url(r'^dns/srv/$', views.dns_srv), -# url(r'^dns/zones/$', views.dns_zones), -# -# # Unifi controler AP names -# url(r'^unifi/ap_names/$', views.accesspoint_ip_dns), -# -# # Firewall -# url(r'^firewall/ouverture_ports/$', views.firewall_ouverture_ports), -# -# # DHCP -# url(r'^dhcp/mac-ip/$', views.dhcp_mac_ip), -# -# # Mailings -# url(r'^mailing/standard/$', views.mailing_standard), -# url( -# r'^mailing/standard/(?P\w+)/members/$', -# views.mailing_standard_ml_members -# ), -# url(r'^mailing/club/$', views.mailing_club), -# url( -# r'^mailing/club/(?P\w+)/members/$', -# views.mailing_club_ml_members -# ), ] diff --git a/api/views.py b/api/views.py index 439ede8b..c337de50 100644 --- a/api/views.py +++ b/api/views.py @@ -333,554 +333,3 @@ class ObtainExpiringAuthToken(ObtainAuthToken): 'token': token.key, 'expiration_date': token.created + token_duration }) - -# -# @csrf_exempt -# @login_required -# @permission_required('machines.serveur') -# @accept_method(['GET']) -# def services(_request): -# """The list of the different services and servers couples -# -# Return: -# GET: -# A JSONSuccess response with a field `data` containing: -# * a list of dictionnaries (one for each service-server couple) -# containing: -# * a field `server`: the server name -# * a field `service`: the service name -# * a field `need_regen`: does the service need a regeneration ? -# """ -# -# service_link = (Service_link.objects.all() -# .select_related('server__domain') -# .select_related('service')) -# seria = ServicesSerializer(service_link, many=True) -# return JSONSuccess(seria.data) -# -# -# @csrf_exempt -# @login_required -# @permission_required('machines.serveur') -# @accept_method(['GET', 'POST']) -# def services_server_service_regen(request, server_name, service_name): -# """The status of a particular service linked to a particular server. -# Mark the service as regenerated if POST used. -# -# Returns: -# GET: -# A JSONSucess response with a field `data` containing: -# * a field `need_regen`: does the service need a regeneration ? -# -# POST: -# An empty JSONSuccess response. -# """ -# -# query = Service_link.objects.filter( -# service__in=Service.objects.filter(service_type=service_name), -# server__in=Interface.objects.filter( -# domain__in=Domain.objects.filter(name=server_name) -# ) -# ) -# if not query: -# return JSONError("This service is not active for this server") -# -# service = query.first() -# if request.method == 'GET': -# return JSONSuccess({'need_regen': service.need_regen()}) -# else: -# service.done_regen() -# return JSONSuccess() -# -# -# @csrf_exempt -# @login_required -# @permission_required('machines.serveur') -# @accept_method(['GET']) -# def services_server(_request, server_name): -# """The list of services attached to a specific server -# -# Returns: -# GET: -# A JSONSuccess response with a field `data` containing: -# * a list of dictionnaries (one for each service) containing: -# * a field `name`: the name of a service -# """ -# -# query = Service_link.objects.filter( -# server__in=Interface.objects.filter( -# domain__in=Domain.objects.filter(name=server_name) -# ) -# ) -# if not query: -# return JSONError("This service is not active for this server") -# -# services_objects = query.all() -# seria = ServiceLinkSerializer(services_objects, many=True) -# return JSONSuccess(seria.data) -# -# -# @csrf_exempt -# @login_required -# @permission_required('machines.serveur') -# @accept_method(['GET']) -# def dns_mac_ip_dns(_request): -# """The list of all active interfaces with all the associated infos -# (MAC, IP, IpType, DNS name and associated zone extension) -# -# Returns: -# GET: -# A JSON Success response with a field `data` containing: -# * a list of dictionnaries (one for each interface) containing: -# * a field `ipv4` containing: -# * a field `ipv4`: the ip for this interface -# * a field `ip_type`: the name of the IpType of this interface -# * a field `ipv6` containing `null` if ipv6 is deactivated else: -# * a field `ipv6`: the ip for this interface -# * a field `ip_type`: the name of the IpType of this interface -# * a field `mac_address`: the MAC of this interface -# * a field `domain`: the DNS name for this interface -# * a field `extension`: the extension for the DNS zone of this -# interface -# """ -# -# interfaces = all_active_assigned_interfaces(full=True) -# seria = FullInterfaceSerializer(interfaces, many=True) -# return JSONSuccess(seria.data) -# -# -# @csrf_exempt -# @login_required -# @permission_required('machines.serveur') -# @accept_method(['GET']) -# def dns_alias(_request): -# """The list of all the alias used and the DNS info associated -# -# Returns: -# GET: -# A JSON Success response with a field `data` containing: -# * a list of dictionnaries (one for each alias) containing: -# * a field `name`: the alias used -# * a field `cname`: the target of the alias (real name of the -# interface) -# * a field `cname_entry`: the entry to write in the DNS to have -# the alias -# * a field `extension`: the extension for the DNS zone of this -# interface -# """ -# -# alias = (Domain.objects -# .filter(interface_parent=None) -# .filter( -# cname__in=Domain.objects.filter( -# interface_parent__in=Interface.objects.exclude(ipv4=None) -# ) -# ) -# .select_related('extension') -# .select_related('cname__extension')) -# seria = DomainSerializer(alias, many=True) -# return JSONSuccess(seria.data) -# -# -# @csrf_exempt -# @login_required -# @permission_required('machines.serveur') -# @accept_method(['GET']) -# def accesspoint_ip_dns(_request): -# """The list of all active interfaces with all the associated infos -# (MAC, IP, IpType, DNS name and associated zone extension) -# -# Only display access points. Use to gen unifi controler names -# -# Returns: -# GET: -# A JSON Success response with a field `data` containing: -# * a list of dictionnaries (one for each interface) containing: -# * a field `ipv4` containing: -# * a field `ipv4`: the ip for this interface -# * a field `ip_type`: the name of the IpType of this interface -# * a field `ipv6` containing `null` if ipv6 is deactivated else: -# * a field `ipv6`: the ip for this interface -# * a field `ip_type`: the name of the IpType of this interface -# * a field `mac_address`: the MAC of this interface -# * a field `domain`: the DNS name for this interface -# * a field `extension`: the extension for the DNS zone of this -# interface -# """ -# -# interfaces = (all_active_assigned_interfaces(full=True) -# .filter(machine__accesspoint__isnull=False)) -# seria = FullInterfaceSerializer(interfaces, many=True) -# return JSONSuccess(seria.data) -# -# -# @csrf_exempt -# @login_required -# @permission_required('machines.serveur') -# @accept_method(['GET']) -# def dns_corresp(_request): -# """The list of the IpTypes possible with the infos about each -# -# Returns: -# GET: -# A JSON Success response with a field `data` containing: -# * a list of dictionnaries (one for each IpType) containing: -# * a field `type`: the name of the type -# * a field `extension`: the DNS extension associated -# * a field `domain_ip_start`: the first ip to use for this type -# * a field `domain_ip_stop`: the last ip to use for this type -# * a field `prefix_v6`: `null` if IPv6 is deactivated else the -# prefix to use -# * a field `ouverture_ports_tcp_in`: the policy for TCP IN ports -# * a field `ouverture_ports_tcp_out`: the policy for TCP OUT ports -# * a field `ouverture_ports_udp_in`: the policy for UDP IN ports -# * a field `ouverture_ports_udp_out`: the policy for UDP OUT ports -# """ -# -# ip_type = IpType.objects.all().select_related('extension') -# seria = TypeSerializer(ip_type, many=True) -# return JSONSuccess(seria.data) -# -# -# @csrf_exempt -# @login_required -# @permission_required('machines.serveur') -# @accept_method(['GET']) -# def dns_mx(_request): -# """The list of MX record to add to the DNS -# -# Returns: -# GET: -# A JSON Success response with a field `data` containing: -# * a list of dictionnaries (one for each MX record) containing: -# * a field `zone`: the extension for the concerned zone -# * a field `priority`: the priority to use -# * a field `name`: the name of the target -# * a field `mx_entry`: the full entry to add in the DNS for this -# MX record -# """ -# -# mx = (Mx.objects.all() -# .select_related('zone') -# .select_related('name__extension')) -# seria = MxSerializer(mx, many=True) -# return JSONSuccess(seria.data) -# -# -# @csrf_exempt -# @login_required -# @permission_required('machines.serveur') -# @accept_method(['GET']) -# def dns_ns(_request): -# """The list of NS record to add to the DNS -# -# Returns: -# GET: -# A JSON Success response with a field `data` containing: -# * a list of dictionnaries (one for each NS record) containing: -# * a field `zone`: the extension for the concerned zone -# * a field `ns`: the DNS name for the NS server targeted -# * a field `ns_entry`: the full entry to add in the DNS for this -# NS record -# """ -# -# ns = (Ns.objects -# .exclude( -# ns__in=Domain.objects.filter( -# interface_parent__in=Interface.objects.filter(ipv4=None) -# ) -# ) -# .select_related('zone') -# .select_related('ns__extension')) -# seria = NsSerializer(ns, many=True) -# return JSONSuccess(seria.data) -# -# -# @csrf_exempt -# @login_required -# @permission_required('machines.serveur') -# @accept_method(['GET']) -# def dns_txt(_request): -# """The list of TXT record to add to the DNS -# -# Returns: -# GET: -# A JSON Success response with a field `data` containing: -# * a list of dictionnaries (one for each TXT record) containing: -# * a field `zone`: the extension for the concerned zone -# * a field `field1`: the first field in the record (target) -# * a field `field2`: the second field in the record (value) -# * a field `txt_entry`: the full entry to add in the DNS for this -# TXT record -# """ -# -# txt = Txt.objects.all().select_related('zone') -# seria = TxtSerializer(txt, many=True) -# return JSONSuccess(seria.data) -# -# -# @csrf_exempt -# @login_required -# @permission_required('machines.serveur') -# @accept_method(['GET']) -# def dns_srv(_request): -# """The list of SRV record to add to the DNS -# -# Returns: -# GET: -# A JSON Success response with a field `data` containing: -# * a list of dictionnaries (one for each SRV record) containing: -# * a field `extension`: the extension for the concerned zone -# * a field `service`: the name of the service concerned -# * a field `protocole`: the name of the protocol to use -# * a field `ttl`: the Time To Live to use -# * a field `priority`: the priority for this service -# * a field `weight`: the weight for same priority entries -# * a field `port`: the port targeted -# * a field `target`: the interface targeted by this service -# * a field `srv_entry`: the full entry to add in the DNS for this -# SRV record -# """ -# -# srv = (Srv.objects.all() -# .select_related('extension') -# .select_related('target__extension')) -# seria = SrvSerializer(srv, many=True) -# return JSONSuccess(seria.data) -# -# -# @csrf_exempt -# @login_required -# @permission_required('machines.serveur') -# @accept_method(['GET']) -# def dns_zones(_request): -# """The list of the zones managed -# -# Returns: -# GET: -# A JSON Success response with a field `data` containing: -# * a list of dictionnaries (one for each zone) containing: -# * a field `name`: the extension for the zone -# * a field `origin`: the server IPv4 for the orgin of the zone -# * a field `origin_v6`: `null` if ipv6 is deactivated else the -# server IPv6 for the origin of the zone -# * a field `soa` containing: -# * a field `mail` containing the mail to contact in case of -# problem with the zone -# * a field `param` containing the full soa paramters to use -# in the DNS for this zone -# * a field `zone_entry`: the full entry to add in the DNS for the -# origin of the zone -# """ -# -# zones = Extension.objects.all().select_related('origin') -# seria = ExtensionSerializer(zones, many=True) -# return JSONSuccess(seria.data) -# -# -# @csrf_exempt -# @login_required -# @permission_required('machines.serveur') -# @accept_method(['GET']) -# def firewall_ouverture_ports(_request): -# """The list of the ports authorized to be openned by the firewall -# -# Returns: -# GET: -# A JSONSuccess response with a `data` field containing: -# * a field `ipv4` containing: -# * a field `tcp_in` containing: -# * a list of port number where ipv4 tcp in should be ok -# * a field `tcp_out` containing: -# * a list of port number where ipv4 tcp ou should be ok -# * a field `udp_in` containing: -# * a list of port number where ipv4 udp in should be ok -# * a field `udp_out` containing: -# * a list of port number where ipv4 udp out should be ok -# * a field `ipv6` containing: -# * a field `tcp_in` containing: -# * a list of port number where ipv6 tcp in should be ok -# * a field `tcp_out` containing: -# * a list of port number where ipv6 tcp ou should be ok -# * a field `udp_in` containing: -# * a list of port number where ipv6 udp in should be ok -# * a field `udp_out` containing: -# * a list of port number where ipv6 udp out should be ok -# """ -# -# r = {'ipv4': {}, 'ipv6': {}} -# for o in (OuverturePortList.objects.all() -# .prefetch_related('ouvertureport_set') -# .prefetch_related('interface_set', 'interface_set__ipv4')): -# pl = { -# "tcp_in": set(map( -# str, -# o.ouvertureport_set.filter( -# protocole=OuverturePort.TCP, -# io=OuverturePort.IN -# ) -# )), -# "tcp_out": set(map( -# str, -# o.ouvertureport_set.filter( -# protocole=OuverturePort.TCP, -# io=OuverturePort.OUT -# ) -# )), -# "udp_in": set(map( -# str, -# o.ouvertureport_set.filter( -# protocole=OuverturePort.UDP, -# io=OuverturePort.IN -# ) -# )), -# "udp_out": set(map( -# str, -# o.ouvertureport_set.filter( -# protocole=OuverturePort.UDP, -# io=OuverturePort.OUT -# ) -# )), -# } -# for i in filter_active_interfaces(o.interface_set): -# if i.may_have_port_open(): -# d = r['ipv4'].get(i.ipv4.ipv4, {}) -# d["tcp_in"] = (d.get("tcp_in", set()) -# .union(pl["tcp_in"])) -# d["tcp_out"] = (d.get("tcp_out", set()) -# .union(pl["tcp_out"])) -# d["udp_in"] = (d.get("udp_in", set()) -# .union(pl["udp_in"])) -# d["udp_out"] = (d.get("udp_out", set()) -# .union(pl["udp_out"])) -# r['ipv4'][i.ipv4.ipv4] = d -# if i.ipv6(): -# for ipv6 in i.ipv6(): -# d = r['ipv6'].get(ipv6.ipv6, {}) -# d["tcp_in"] = (d.get("tcp_in", set()) -# .union(pl["tcp_in"])) -# d["tcp_out"] = (d.get("tcp_out", set()) -# .union(pl["tcp_out"])) -# d["udp_in"] = (d.get("udp_in", set()) -# .union(pl["udp_in"])) -# d["udp_out"] = (d.get("udp_out", set()) -# .union(pl["udp_out"])) -# r['ipv6'][ipv6.ipv6] = d -# return JSONSuccess(r) -# -# -# @csrf_exempt -# @login_required -# @permission_required('machines.serveur') -# @accept_method(['GET']) -# def dhcp_mac_ip(_request): -# """The list of all active interfaces with all the associated infos -# (MAC, IP, IpType, DNS name and associated zone extension) -# -# Returns: -# GET: -# A JSON Success response with a field `data` containing: -# * a list of dictionnaries (one for each interface) containing: -# * a field `ipv4` containing: -# * a field `ipv4`: the ip for this interface -# * a field `ip_type`: the name of the IpType of this interface -# * a field `mac_address`: the MAC of this interface -# * a field `domain`: the DNS name for this interface -# * a field `extension`: the extension for the DNS zone of this -# interface -# """ -# -# interfaces = all_active_assigned_interfaces() -# seria = InterfaceSerializer(interfaces, many=True) -# return JSONSuccess(seria.data) -# -# -# @csrf_exempt -# @login_required -# @permission_required('machines.serveur') -# @accept_method(['GET']) -# def mailing_standard(_request): -# """All the available standard mailings. -# -# Returns: -# GET: -# A JSONSucess response with a field `data` containing: -# * a list of dictionnaries (one for each mailing) containing: -# * a field `name`: the name of a mailing -# """ -# -# return JSONSuccess([ -# {'name': 'adherents'} -# ]) -# -# -# @csrf_exempt -# @login_required -# @permission_required('machines.serveur') -# @accept_method(['GET']) -# def mailing_standard_ml_members(_request, ml_name): -# """All the members of a specific standard mailing -# -# Returns: -# GET: -# A JSONSucess response with a field `data` containing: -# * a list if dictionnaries (one for each member) containing: -# * a field `email`: the email of the member -# * a field `name`: the name of the member -# * a field `surname`: the surname of the member -# * a field `pseudo`: the pseudo of the member -# """ -# -# # All with active connextion -# if ml_name == 'adherents': -# members = all_has_access().values('email').distinct() -# # Unknown mailing -# else: -# return JSONError("This mailing does not exist") -# seria = MailingMemberSerializer(members, many=True) -# return JSONSuccess(seria.data) -# -# -# @csrf_exempt -# @login_required -# @permission_required('machines.serveur') -# @accept_method(['GET']) -# def mailing_club(_request): -# """All the available club mailings. -# -# Returns: -# GET: -# A JSONSucess response with a field `data` containing: -# * a list of dictionnaries (one for each mailing) containing: -# * a field `name` indicating the name of a mailing -# """ -# -# clubs = Club.objects.filter(mailing=True).values('pseudo') -# seria = MailingSerializer(clubs, many=True) -# return JSONSuccess(seria.data) -# -# -# @csrf_exempt -# @login_required -# @permission_required('machines.serveur') -# @accept_method(['GET']) -# def mailing_club_ml_members(_request, ml_name): -# """All the members of a specific club mailing -# -# Returns: -# GET: -# A JSONSucess response with a field `data` containing: -# * a list if dictionnaries (one for each member) containing: -# * a field `email`: the email of the member -# * a field `name`: the name of the member -# * a field `surname`: the surname of the member -# * a field `pseudo`: the pseudo of the member -# """ -# -# try: -# club = Club.objects.get(mailing=True, pseudo=ml_name) -# except Club.DoesNotExist: -# return JSONError("This mailing does not exist") -# members = club.administrators.all().values('email').distinct() -# seria = MailingMemberSerializer(members, many=True) -# return JSONSuccess(seria.data) From 6b777754e523bfc6bd501476e8f19b4b89b6e4cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Kervella?= Date: Wed, 23 May 2018 15:26:41 +0000 Subject: [PATCH 16/42] API: Add tests --- api/tests.py | 330 ++++++++++++++++++++++++++++++++- re2o/settings_local.example.py | 4 + 2 files changed, 332 insertions(+), 2 deletions(-) diff --git a/api/tests.py b/api/tests.py index bfcda28f..aa4747eb 100644 --- a/api/tests.py +++ b/api/tests.py @@ -23,6 +23,332 @@ The tests for the API module. """ -# from django.test import TestCase +import json +from rest_framework.test import APITestCase +from requests import codes + +#import cotisations.models as cotisations +#import machines.models as machines +#import topologie.models as topologie +import users.models as users + + +class APIEndpointsTestCase(APITestCase): + # URLs that don't require to be authenticated + no_auth_endpoints = [ + '/api/' + ] + # URLs that require to be authenticated and have no special permissions + auth_no_perm_endpoints = [] + # URLs that require to be authenticated and have special permissions + auth_perm_endpoints = [ + '/api/cotisations/articles/', +# '/api/cotisations/articles//', + '/api/cotisations/banques/', +# '/api/cotisations/banques//', + '/api/cotisations/cotisations/', +# '/api/cotisations/cotisations//', + '/api/cotisations/factures/', +# '/api/cotisations/factures//', + '/api/cotisations/paiements/', +# '/api/cotisations/paiements//', + '/api/cotisations/ventes/', +# '/api/cotisations/ventes//', + '/api/machines/domains/', +# '/api/machines/domains//', + '/api/machines/extensions/', +# '/api/machines/extensions//', + '/api/machines/interfaces/', +# '/api/machines/interfaces//', + '/api/machines/iplists/', +# '/api/machines/iplists//', + '/api/machines/iptypes/', +# '/api/machines/iptypes//', + '/api/machines/ipv6lists/', +# '/api/machines/ipv6lists//', + '/api/machines/machines/', +# '/api/machines/machines//', + '/api/machines/machinetypes/', +# '/api/machines/machinetypes//', + '/api/machines/mx/', +# '/api/machines/mx//', + '/api/machines/nas/', +# '/api/machines/nas//', + '/api/machines/ns/', +# '/api/machines/ns//', + '/api/machines/ouvertureportlists/', +# '/api/machines/ouvertureportlists//', + '/api/machines/ouvertureports/', +# '/api/machines/ouvertureports//', + '/api/machines/servicelinks/', +# '/api/machines/servicelinks//', + '/api/machines/services/', +# '/api/machines/services//', + '/api/machines/soa/', +# '/api/machines/soa//', + '/api/machines/srv/', +# '/api/machines/srv//', + '/api/machines/txt/', +# '/api/machines/txt//', + '/api/machines/vlans/', +# '/api/machines/vlans//', + '/api/topologie/acesspoint/', +# '/api/topologie/acesspoint//', + '/api/topologie/building/', +# '/api/topologie/building//', + '/api/topologie/constructorswitch/', +# '/api/topologie/constructorswitch//', + '/api/topologie/modelswitch/', +# '/api/topologie/modelswitch//', + '/api/topologie/room/', +# '/api/topologie/room//', + '/api/topologie/stack/', +# '/api/topologie/stack//', + '/api/topologie/switch/', +# '/api/topologie/switch//', + '/api/topologie/switchbay/', +# '/api/topologie/switchbay//', + '/api/topologie/switchport/', +# '/api/topologie/switchport//', + '/api/users/adherents/', +# '/api/users/adherents//', + '/api/users/bans/', +# '/api/users/bans//', + '/api/users/clubs/', +# '/api/users/clubs//', + '/api/users/listrights/', +# '/api/users/listrights//', + '/api/users/schools/', +# '/api/users/schools//', + '/api/users/serviceusers/', +# '/api/users/serviceusers//', + '/api/users/shells/', +# '/api/users/shells//', + '/api/users/users/', +# '/api/users/users//', + '/api/users/whitelists/', +# '/api/users/whitelists//', + ] + + stduser = None + superuser = None + + @classmethod + def setUpTestData(cls): + # A user with no rights + cls.stduser = users.User.objects.create_user( + "apistduser", + "apistduser", + "apistduser@example.net", + "apistduser" + ) + # A user with all the rights + cls.superuser = users.User.objects.create_superuser( + "apisuperuser", + "apisuperuser", + "apisuperuser@example.net", + "apisuperuser" + ) + + # TODO : + # Create 1 object of every model so there is an exisiting object + # when quering for pk=1 + + @classmethod + def tearDownClass(cls): + cls.stduser.delete() + cls.superuser.delete() + super().tearDownClass() + + def check_responses_code(self, urls, expected_code, formats=[None], + assert_more=None): + """ + Utility function to test if a list of urls answer an expected code + + :param urls: (list) The list of urls to test + :param expected_code: (int) The HTTP return code expected + :param formats: (list) The list of formats to use for the request + (Default: [None]) + :param assert_more: (func) A function to assert more specific data + in the same test. It is evaluated with the responsem object, the + url and the format used. + """ + for url in urls: + for format in formats: + with self.subTest(url=url, format=format): + response = self.client.get(url, format=format) + assert response.status_code == expected_code + if assert_more: + assert_more(response, url, format) + + def test_no_auth_endpoints_with_no_auth(self): + """ + Test that every endpoint that does not require to be authenticated, + returns a Ok (200) response when not authenticated. + """ + urls = [endpoint.replace('', '1') + for endpoint in self.no_auth_endpoints] + self.check_responses_code(urls, codes.ok) + + def test_auth_endpoints_with_no_auth(self): + """ + Test that every endpoint that does require to be authenticated, + returns a Unauthorized (401) response when not authenticated. + """ + urls = [endpoint.replace('', '1') for endpoint in \ + self.auth_no_perm_endpoints + self.auth_perm_endpoints] + self.check_responses_code(urls, codes.unauthorized) + + def test_no_auth_endpoints_with_auth(self): + """ + Test that every endpoint that does not require to be authenticated, + returns a Ok (200) response when authenticated. + """ + self.client.force_authenticate(user=self.stduser) + urls = [endpoint.replace('', '1') + for endpoint in self.no_auth_endpoints] + self.check_responses_code(urls, codes.ok) + + def test_auth_no_perm_endpoints_with_auth_and_no_perm(self): + """ + Test that every endpoint that does require to be authenticated and + no special permissions, returns a Ok (200) response when + authenticated but without permissions. + """ + self.client.force_authenticate(user=self.stduser) + urls = [endpoint.replace('', '1') + for endpoint in self.auth_no_perm_endpoints] + self.check_responses_code(urls, codes.ok) + + def test_auth_perm_endpoints_with_auth_and_no_perm(self): + """ + Test that every endpoint that does require to be authenticated and + special permissions, returns a Forbidden (403) response when + authenticated but without permissions. + """ + self.client.force_authenticate(user=self.stduser) + urls = [endpoint.replace('', '1') + for endpoint in self.auth_perm_endpoints] + self.check_responses_code(urls, codes.forbidden) + + def test_auth_endpoints_with_auth_and_perm(self): + """ + Test that every endpoint that does require to be authenticated, + returns a Ok (200) response when authenticated with all permissions + """ + self.client.force_authenticate(user=self.superuser) + urls = [endpoint.replace('', '1') for endpoint \ + in self.auth_no_perm_endpoints + self.auth_perm_endpoints] + self.check_responses_code(urls, codes.ok) + + def test_endpoints_not_found(self): + """ + Test that every endpoint that uses a primary key parameter, + returns a Not Found (404) response when queried with non-existing + primary key + """ + self.client.force_authenticate(user=self.superuser) + # Select only the URLs with '' and replace it with '42' + urls = [endpoint.replace('', '42') for endpoint in \ + self.no_auth_endpoints + self.auth_no_perm_endpoints + \ + self.auth_perm_endpoints if '' in endpoint] + self.check_responses_code(urls, codes.not_found) + + def test_formats(self): + """ + Test that every endpoint returns a Ok (200) response when using + different formats. Also checks that 'json' format returns a valid json + """ + self.client.force_authenticate(user=self.superuser) + + urls = [endpoint.replace('', '1') for endpoint in \ + self.no_auth_endpoints + self.auth_no_perm_endpoints + \ + self.auth_perm_endpoints] + + def assert_more(response, url, format): + """Assert the response is valid json when format is json""" + if format is 'json': + json.loads(response.content.decode()) + + self.check_responses_code(urls, codes.ok, + formats=[None, 'json', 'api'], + assert_more=assert_more) + +class APIPaginationTestCase(APITestCase): + endpoints = [ + '/api/cotisations/articles/', + '/api/cotisations/banques/', + '/api/cotisations/cotisations/', + '/api/cotisations/factures/', + '/api/cotisations/paiements/', + '/api/cotisations/ventes/', + '/api/machines/domains/', + '/api/machines/extensions/', + '/api/machines/interfaces/', + '/api/machines/iplists/', + '/api/machines/iptypes/', + '/api/machines/ipv6lists/', + '/api/machines/machines/', + '/api/machines/machinetypes/', + '/api/machines/mx/', + '/api/machines/nas/', + '/api/machines/ns/', + '/api/machines/ouvertureportlists/', + '/api/machines/ouvertureports/', + '/api/machines/servicelinks/', + '/api/machines/services/', + '/api/machines/soa/', + '/api/machines/srv/', + '/api/machines/txt/', + '/api/machines/vlans/', + '/api/topologie/acesspoint/', + '/api/topologie/building/', + '/api/topologie/constructorswitch/', + '/api/topologie/modelswitch/', + '/api/topologie/room/', + '/api/topologie/stack/', + '/api/topologie/switch/', + '/api/topologie/switchbay/', + '/api/topologie/switchport/', + '/api/users/adherents/', + '/api/users/bans/', + '/api/users/clubs/', + '/api/users/listrights/', + '/api/users/schools/', + '/api/users/serviceusers/', + '/api/users/shells/', + '/api/users/users/', + '/api/users/whitelists/', + ] + superuser = None + + @classmethod + def setUpTestData(cls): + # A user with all the rights + cls.superuser = users.User.objects.create_superuser( + "apisuperuser", + "apisuperuser", + "apisuperuser@example.net", + "apisuperuser" + ) + + @classmethod + def tearDownClass(cls): + cls.superuser.delete() + super().tearDownClass() + + def test_pagination(self): + """ + Test that every endpoint is using the pagination correctly + """ + self.client.force_authenticate(self.superuser) + for url in self.endpoints: + with self.subTest(url=url): + response = self.client.get(url, format='json') + res_json = json.loads(response.content.decode()) + assert 'count' in res_json.keys() + assert 'next' in res_json.keys() + assert 'previous' in res_json.keys() + assert 'results' in res_json.keys() + assert not len('results') > 100 -# Create your tests here. diff --git a/re2o/settings_local.example.py b/re2o/settings_local.example.py index e15455df..662c1447 100644 --- a/re2o/settings_local.example.py +++ b/re2o/settings_local.example.py @@ -56,6 +56,10 @@ DATABASES = { 'USER': 'db_user_value', 'PASSWORD': DB_PASSWORD, 'HOST': 'db_host_value', + 'TEST': { + 'CHARSET': 'utf8', + 'COLLATION': 'utf8_general_ci' + } }, 'ldap': { # The LDAP 'ENGINE': 'ldapdb.backends.ldap', From 7f6126432bfe4d8684016eccb1d8ae855658f785 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Kervella?= Date: Thu, 24 May 2018 17:56:00 +0000 Subject: [PATCH 17/42] Change token data to expiration --- api/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/views.py b/api/views.py index c337de50..ba4129c2 100644 --- a/api/views.py +++ b/api/views.py @@ -331,5 +331,5 @@ class ObtainExpiringAuthToken(ObtainAuthToken): return Response({ 'token': token.key, - 'expiration_date': token.created + token_duration + 'expiration': token.created + token_duration }) From 37458db314ab6b0bc80a78d9295da291b1cd394c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Kervella?= Date: Thu, 24 May 2018 23:02:39 +0000 Subject: [PATCH 18/42] Add custom pagination for setting page_size --- api/pagination.py | 47 +++++++++++++++++++++++++++++++++++++++++++++++ api/settings.py | 2 +- 2 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 api/pagination.py diff --git a/api/pagination.py b/api/pagination.py new file mode 100644 index 00000000..a6a6adcb --- /dev/null +++ b/api/pagination.py @@ -0,0 +1,47 @@ +from rest_framework import pagination +from django.core import paginator +from django.utils.functional import cached_property + + +class AllowNegativePaginator(paginator.Paginator): + """ + Paginator subclass to allow negative or null `per_page` argument, + meaning to show all items in one page. + """ + def page(self, number): + """ + Bypass the default page creation to render all items if `per_page` + argument is negative or null. + """ + if self.per_page <= 0: + return self._get_page(self.object_list, 1, self) + return super(AllowNegativePaginator, self).page(number) + + @cached_property + def num_pages(self): + """ + Bypass the default number of page to return 1 if `per_page` argument + is negative or null. + """ + if self.per_page <= 0: + return 1 + return super(AllowNegativePaginator, self).num_pages + + +class PageSizedPagination(pagination.PageNumberPagination): + """ + Pagination subclass to all to control the page size + """ + page_size_query_param = 'page_size' + all_pages_strings = ('all',) + django_paginator_class = AllowNegativePaginator + + def get_page_size(self, request): + try: + page_size_str = request.query_params[self.page_size_query_param] + if page_size_str in self.all_pages_strings: + return -1 + except KeyError: + pass + + return super(PageSizedPagination, self).get_page_size(request) diff --git a/api/settings.py b/api/settings.py index 6bf48e33..c1ec4786 100644 --- a/api/settings.py +++ b/api/settings.py @@ -35,7 +35,7 @@ REST_FRAMEWORK = { 'DEFAULT_PERMISSION_CLASSES': ( 'api.permissions.DefaultACLPermission', ), - 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination', + 'DEFAULT_PAGINATION_CLASS': 'api.pagination.PageSizedPagination', 'PAGE_SIZE': 100 } From 1f0a3434dd8b7013dc6deb29e41c11f7b605b30a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Kervella?= Date: Thu, 24 May 2018 23:06:03 +0000 Subject: [PATCH 19/42] Limit to 10000 results per_page --- api/pagination.py | 31 ++----------------------------- 1 file changed, 2 insertions(+), 29 deletions(-) diff --git a/api/pagination.py b/api/pagination.py index a6a6adcb..2fc0aaf7 100644 --- a/api/pagination.py +++ b/api/pagination.py @@ -1,31 +1,4 @@ from rest_framework import pagination -from django.core import paginator -from django.utils.functional import cached_property - - -class AllowNegativePaginator(paginator.Paginator): - """ - Paginator subclass to allow negative or null `per_page` argument, - meaning to show all items in one page. - """ - def page(self, number): - """ - Bypass the default page creation to render all items if `per_page` - argument is negative or null. - """ - if self.per_page <= 0: - return self._get_page(self.object_list, 1, self) - return super(AllowNegativePaginator, self).page(number) - - @cached_property - def num_pages(self): - """ - Bypass the default number of page to return 1 if `per_page` argument - is negative or null. - """ - if self.per_page <= 0: - return 1 - return super(AllowNegativePaginator, self).num_pages class PageSizedPagination(pagination.PageNumberPagination): @@ -34,13 +7,13 @@ class PageSizedPagination(pagination.PageNumberPagination): """ page_size_query_param = 'page_size' all_pages_strings = ('all',) - django_paginator_class = AllowNegativePaginator + max_page_size = 10000 def get_page_size(self, request): try: page_size_str = request.query_params[self.page_size_query_param] if page_size_str in self.all_pages_strings: - return -1 + return self.max_page_size except KeyError: pass From f5267eae6c4c1a6dd635904b7e65f41c2fd1bdef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Kervella?= Date: Thu, 24 May 2018 23:07:23 +0000 Subject: [PATCH 20/42] Add DHCP_hostmacip API view --- api/serializers.py | 13 +++++++++++++ api/urls.py | 1 + api/views.py | 12 +++++++++++- 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/api/serializers.py b/api/serializers.py index fe01bf02..0620d9cc 100644 --- a/api/serializers.py +++ b/api/serializers.py @@ -457,3 +457,16 @@ class WhitelistSerializer(NamespacedHMSerializer): class Meta: model = users.Whitelist fields = ('user', 'raison', 'date_start', 'date_end', 'active', 'api_url') + + +# DHCP + + +class HostMacIpSerializer(serializers.ModelSerializer): + hostname = serializers.CharField(source='domain.name', read_only=True) + extension = serializers.CharField(source='domain.extension.name', read_only=True) + ipv4 = serializers.CharField(source='ipv4.ipv4', read_only=True) + + class Meta: + model = machines.Interface + fields = ('hostname', 'extension', 'mac_address', 'ipv4') diff --git a/api/urls.py b/api/urls.py index 03e5026b..cc73ab2c 100644 --- a/api/urls.py +++ b/api/urls.py @@ -89,5 +89,6 @@ router.register(r'users/whitelists', views.WhitelistViewSet) urlpatterns = [ url(r'^', include(router.urls)), + url(r'^dhcp/hostmacip', views.HostMacIpView.as_view()), url(r'^token-auth/', views.ObtainExpiringAuthToken.as_view()) ] diff --git a/api/views.py b/api/views.py index ba4129c2..79f81d33 100644 --- a/api/views.py +++ b/api/views.py @@ -31,7 +31,7 @@ from django.conf import settings from rest_framework.authtoken.views import ObtainAuthToken from rest_framework.authtoken.models import Token from rest_framework.response import Response -from rest_framework import viewsets, status +from rest_framework import viewsets, status, generics import cotisations.models as cotisations import machines.models as machines @@ -39,6 +39,8 @@ import preferences.models as preferences import topologie.models as topologie import users.models as users +from re2o.utils import all_active_interfaces + from . import serializers @@ -310,6 +312,14 @@ class WhitelistViewSet(viewsets.ReadOnlyModelViewSet): queryset = users.Whitelist.objects.all() serializer_class = serializers.WhitelistSerializer + +# DHCP views + +class HostMacIpView(generics.ListAPIView): + queryset = all_active_interfaces() + serializer_class = serializers.HostMacIpSerializer + + # Subclass the standard rest_framework.auth_token.views.ObtainAuthToken # in order to renew the lease of the token and add expiration time class ObtainExpiringAuthToken(ObtainAuthToken): From 3590e1ed512eb0a36625fd5123ab50c59c30e685 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Kervella?= Date: Fri, 25 May 2018 15:36:07 +0000 Subject: [PATCH 21/42] Fix serialization of MAC --- api/serializers.py | 1 + 1 file changed, 1 insertion(+) diff --git a/api/serializers.py b/api/serializers.py index 0620d9cc..37530bbe 100644 --- a/api/serializers.py +++ b/api/serializers.py @@ -465,6 +465,7 @@ class WhitelistSerializer(NamespacedHMSerializer): class HostMacIpSerializer(serializers.ModelSerializer): hostname = serializers.CharField(source='domain.name', read_only=True) extension = serializers.CharField(source='domain.extension.name', read_only=True) + mac_address = serializers.CharField(read_only=True) ipv4 = serializers.CharField(source='ipv4.ipv4', read_only=True) class Meta: From ed1284c06dc734fdbccc31ca5a18a4b413164662 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Kervella?= Date: Fri, 25 May 2018 20:10:31 +0000 Subject: [PATCH 22/42] Fix missing view_service_link perm --- .../migrations/0082_auto_20180525_2209.py | 19 +++++++++++++++++++ machines/models.py | 5 +++++ 2 files changed, 24 insertions(+) create mode 100644 machines/migrations/0082_auto_20180525_2209.py diff --git a/machines/migrations/0082_auto_20180525_2209.py b/machines/migrations/0082_auto_20180525_2209.py new file mode 100644 index 00000000..1da2370c --- /dev/null +++ b/machines/migrations/0082_auto_20180525_2209.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2018-05-25 20:09 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('machines', '0081_auto_20180521_1413'), + ] + + operations = [ + migrations.AlterModelOptions( + name='service_link', + options={'permissions': (('view_service_link', 'Peut voir un objet service_link'),)}, + ), + ] diff --git a/machines/models.py b/machines/models.py index fe1923bb..7027ca6f 100644 --- a/machines/models.py +++ b/machines/models.py @@ -1388,6 +1388,11 @@ class Service_link(RevMixin, AclMixin, models.Model): last_regen = models.DateTimeField(auto_now_add=True) asked_regen = models.BooleanField(default=False) + class Meta: + permissions = ( + ("view_service_link", "Peut voir un objet service_link"), + ) + def done_regen(self): """ Appellé lorsqu'un serveur a regénéré son service""" self.last_regen = timezone.now() From 4fe1be5d31942f8d8f84afaed9d28fe772312cb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Kervella?= Date: Sat, 26 May 2018 21:08:03 +0000 Subject: [PATCH 23/42] Allow update and quick check of need_regen info --- api/permissions.py | 12 ++++++------ api/serializers.py | 17 +++++++++++++++++ api/urls.py | 3 ++- api/views.py | 22 ++++++++++++++++++++-- machines/models.py | 14 ++++++++++++++ machines/serializers.py | 2 +- 6 files changed, 60 insertions(+), 10 deletions(-) diff --git a/api/permissions.py b/api/permissions.py index a1d79b21..66480af6 100644 --- a/api/permissions.py +++ b/api/permissions.py @@ -1,4 +1,4 @@ -from rest_framework import permissions +from rest_framework import permissions, exceptions from re2o.acl import can_create, can_edit, can_delete, can_view_all from . import acl @@ -17,17 +17,17 @@ class DefaultACLPermission(permissions.BasePermission): 'OPTIONS': [can_see_api, lambda model: model.can_view_all], 'HEAD': [can_see_api, lambda model: model.can_view_all], 'POST': [can_see_api, lambda model: model.can_create], - #'PUT': [], - #'PATCH': [], - #'DELETE': [], + 'PUT': [], # No restrictions, apply to objects + 'PATCH': [], # No restrictions, apply to objects + 'DELETE': [], # No restrictions, apply to objects } perms_obj_map = { 'GET': [can_see_api, lambda obj: obj.can_view], 'OPTIONS': [can_see_api, lambda obj: obj.can_view], 'HEAD': [can_see_api, lambda obj: obj.can_view], - #'POST': [], + 'POST': [], # No restrictions, apply to models 'PUT': [can_see_api, lambda obj: obj.can_edit], - #'PATCH': [], + 'PATCH': [can_see_api, lambda obj: obj.can_edit], 'DELETE': [can_see_api, lambda obj: obj.can_delete], } diff --git a/api/serializers.py b/api/serializers.py index 37530bbe..b41bd694 100644 --- a/api/serializers.py +++ b/api/serializers.py @@ -215,6 +215,8 @@ class ServiceSerializer(NamespacedHMSerializer): class ServiceLinkSerializer(NamespacedHMSerializer): + need_regen = serializers.BooleanField() + class Meta: model = machines.Service_link fields = ('service', 'server', 'last_regen', 'asked_regen', @@ -459,6 +461,21 @@ class WhitelistSerializer(NamespacedHMSerializer): fields = ('user', 'raison', 'date_start', 'date_end', 'active', 'api_url') +# Services + + +class ServiceRegenSerializer(NamespacedHMSerializer): + hostname = serializers.CharField(source='server.domain.name', read_only=True) + service_name = serializers.CharField(source='service.service_type', read_only=True) + + class Meta: + model = machines.Service_link + fields = ('hostname', 'service_name', 'need_regen', 'api_url') + extra_kwargs = { + 'api_url': {'view_name': 'servicelink-detail'} + } + + # DHCP diff --git a/api/urls.py b/api/urls.py index cc73ab2c..fc8ef7ef 100644 --- a/api/urls.py +++ b/api/urls.py @@ -90,5 +90,6 @@ router.register(r'users/whitelists', views.WhitelistViewSet) urlpatterns = [ url(r'^', include(router.urls)), url(r'^dhcp/hostmacip', views.HostMacIpView.as_view()), - url(r'^token-auth/', views.ObtainExpiringAuthToken.as_view()) + url(r'^services/regen', views.ServiceRegenView.as_view()), + url(r'^token-auth', views.ObtainExpiringAuthToken.as_view()) ] diff --git a/api/views.py b/api/views.py index 79f81d33..036a9241 100644 --- a/api/views.py +++ b/api/views.py @@ -31,7 +31,7 @@ from django.conf import settings from rest_framework.authtoken.views import ObtainAuthToken from rest_framework.authtoken.models import Token from rest_framework.response import Response -from rest_framework import viewsets, status, generics +from rest_framework import viewsets, generics import cotisations.models as cotisations import machines.models as machines @@ -160,7 +160,7 @@ class ServiceViewSet(viewsets.ReadOnlyModelViewSet): serializer_class = serializers.ServiceSerializer -class ServiceLinkViewSet(viewsets.ReadOnlyModelViewSet): +class ServiceLinkViewSet(viewsets.ModelViewSet): queryset = machines.Service_link.objects.all() serializer_class = serializers.ServiceLinkSerializer @@ -313,6 +313,24 @@ class WhitelistViewSet(viewsets.ReadOnlyModelViewSet): serializer_class = serializers.WhitelistSerializer +# Services views + + +class ServiceRegenView(generics.ListAPIView): + serializer_class = serializers.ServiceRegenSerializer + + def get_queryset(self): + queryset = machines.Service_link.objects.select_related( + 'server__domain' + ).select_related( + 'service' + ) + if 'hostname' in self.request.GET: + hostname = self.request.GET['hostname'] + queryset = queryset.filter(server__domain__name__iexact=hostname) + return queryset + + # DHCP views class HostMacIpView(generics.ListAPIView): diff --git a/machines/models.py b/machines/models.py index 7027ca6f..82d6e21f 100644 --- a/machines/models.py +++ b/machines/models.py @@ -1399,6 +1399,7 @@ class Service_link(RevMixin, AclMixin, models.Model): self.asked_regen = False self.save() + @property def need_regen(self): """ Décide si le temps minimal écoulé est suffisant pour provoquer une régénération de service""" @@ -1411,6 +1412,19 @@ class Service_link(RevMixin, AclMixin, models.Model): ) < timezone.now() ) + @need_regen.setter + def need_regen(self, value): + """ + Force to set the need_regen value. True means a regen is asked and False + means a regen has been done. + + :param value: (bool) The value to set to + """ + if not value: + self.last_regen = timezone.now() + self.asked_regen = value + self.save() + def __str__(self): return str(self.server) + " " + str(self.service) diff --git a/machines/serializers.py b/machines/serializers.py index 9476e9d0..f3a47c55 100644 --- a/machines/serializers.py +++ b/machines/serializers.py @@ -376,7 +376,7 @@ class ServiceServersSerializer(serializers.ModelSerializer): @staticmethod def get_regen_status(obj): """ The string representation of the regen status """ - return obj.need_regen() + return obj.need_regen class OuverturePortsSerializer(serializers.Serializer): From e6884ab4da2022d2d24cef209be1b1167b8fd376 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Kervella?= Date: Sun, 10 Jun 2018 00:47:25 +0000 Subject: [PATCH 24/42] Add DNS info per zone endpoint --- api/serializers.py | 94 +++++++++++++++++++++++++++++++++++++++++++++- api/urls.py | 1 + api/views.py | 7 ++++ machines/models.py | 9 +++++ 4 files changed, 109 insertions(+), 2 deletions(-) diff --git a/api/serializers.py b/api/serializers.py index b41bd694..44dfc0bc 100644 --- a/api/serializers.py +++ b/api/serializers.py @@ -190,8 +190,7 @@ class InterfaceSerializer(NamespacedHMSerializer): class Ipv6ListSerializer(NamespacedHMSerializer): class Meta: model = machines.Ipv6List - fields = ('ipv6', 'interface', 'slaac_ip', 'date_end', - 'api_url') + fields = ('ipv6', 'interface', 'slaac_ip', 'api_url') class DomainSerializer(NamespacedHMSerializer): @@ -488,3 +487,94 @@ class HostMacIpSerializer(serializers.ModelSerializer): class Meta: model = machines.Interface fields = ('hostname', 'extension', 'mac_address', 'ipv4') + + +# DNS + + +class SOARecordSerializer(SOASerializer): + class Meta: + model = machines.SOA + fields = ('name', 'mail', 'refresh', 'retry', 'expire', 'ttl') + + +class OriginV4RecordSerializer(IpListSerializer): + class Meta(IpListSerializer.Meta): + fields = ('ipv4',) + + +class OriginV6RecordSerializer(Ipv6ListSerializer): + class Meta(Ipv6ListSerializer.Meta): + fields = ('ipv6',) + + +class NSRecordSerializer(NsSerializer): + target = serializers.CharField(source='ns.name', read_only=True) + + class Meta(NsSerializer.Meta): + fields = ('target',) + + +class MXRecordSerializer(MxSerializer): + target = serializers.CharField(source='name.name', read_only=True) + + class Meta(MxSerializer.Meta): + fields = ('target', 'priority') + + +class TXTRecordSerializer(TxtSerializer): + class Meta(TxtSerializer.Meta): + fields = ('field1', 'field2') + + +class SRVRecordSerializer(SrvSerializer): + target = serializers.CharField(source='target.name', read_only=True) + + class Meta(SrvSerializer.Meta): + fields = ('service', 'protocole', 'ttl', 'priority', 'weight', 'port', 'target') + + +class ARecordSerializer(serializers.ModelSerializer): + hostname = serializers.CharField(source='domain.name', read_only=True) + ipv4 = serializers.CharField(source='ipv4.ipv4', read_only=True) + + class Meta: + model = machines.Interface + fields = ('hostname', 'ipv4') + + +class AAAARecordSerializer(serializers.ModelSerializer): + hostname = serializers.CharField(source='domain.name', read_only=True) + ipv6 = serializers.CharField(read_only=True) + + class Meta: + model = machines.Interface + fields = ('hostname', 'ipv6') + + +class CNAMERecordSerializer(serializers.ModelSerializer): + alias = serializers.CharField(source='cname.name', read_only=True) + hostname = serializers.CharField(source='name', read_only=True) + + class Meta: + model = machines.Domain + fields = ('alias', 'hostname') + + +class DNSZonesSerializer(serializers.ModelSerializer): + soa = SOARecordSerializer() + ns_records = NSRecordSerializer(many=True, source='ns_set') + originv4 = OriginV4RecordSerializer(source='origin') + originv6 = OriginV6RecordSerializer(source='origin_v6') + mx_records = MXRecordSerializer(many=True, source='mx_set') + txt_records = TXTRecordSerializer(many=True, source='txt_set') + srv_records = SRVRecordSerializer(many=True, source='srv_set') + a_records = ARecordSerializer(many=True, source='get_associated_a_records') + aaaa_records = AAAARecordSerializer(many=True, source='get_associated_aaaa_records') + cname_records = CNAMERecordSerializer(many=True, source='get_associated_cname_records') + + class Meta: + model = machines.Extension + fields = ('name', 'soa', 'ns_records', 'originv4', 'originv6', + 'mx_records', 'txt_records', 'srv_records', 'a_records', + 'aaaa_records', 'cname_records') diff --git a/api/urls.py b/api/urls.py index fc8ef7ef..07f83afb 100644 --- a/api/urls.py +++ b/api/urls.py @@ -90,6 +90,7 @@ router.register(r'users/whitelists', views.WhitelistViewSet) urlpatterns = [ url(r'^', include(router.urls)), url(r'^dhcp/hostmacip', views.HostMacIpView.as_view()), + url(r'^dns/zones', views.DNSZonesView.as_view()), url(r'^services/regen', views.ServiceRegenView.as_view()), url(r'^token-auth', views.ObtainExpiringAuthToken.as_view()) ] diff --git a/api/views.py b/api/views.py index 036a9241..1530fbb3 100644 --- a/api/views.py +++ b/api/views.py @@ -338,6 +338,13 @@ class HostMacIpView(generics.ListAPIView): serializer_class = serializers.HostMacIpSerializer +# DNS views + +class DNSZonesView(generics.ListAPIView): + queryset = machines.Extension.objects.all() + serializer_class = serializers.DNSZonesSerializer + + # Subclass the standard rest_framework.auth_token.views.ObtainAuthToken # in order to renew the lease of the token and add expiration time class ObtainExpiringAuthToken(ObtainAuthToken): diff --git a/machines/models.py b/machines/models.py index 82d6e21f..817e9d5a 100644 --- a/machines/models.py +++ b/machines/models.py @@ -562,6 +562,15 @@ class Extension(RevMixin, AclMixin, models.Model): entry += "@ IN AAAA " + str(self.origin_v6) return entry + def get_associated_a_records(self): + return Interface.objects.filter(type__ip_type__extension=self).filter(ipv4__isnull=False) + + def get_associated_aaaa_records(self): + return Interface.objects.filter(type__ip_type__extension=self) + + def get_associated_cname_records(self): + return Domain.objects.filter(extension=self).filter(cname__isnull=False) + @staticmethod def can_use_all(user_request, *_args, **_kwargs): """Superdroit qui permet d'utiliser toutes les extensions sans From 197475409c23dd6d3795ecf538bf655f8f7a3d6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Kervella?= Date: Sun, 10 Jun 2018 00:50:57 +0000 Subject: [PATCH 25/42] DNS Zone endpoint is now a viewset --- api/serializers.py | 7 +++++-- api/urls.py | 3 ++- api/views.py | 2 +- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/api/serializers.py b/api/serializers.py index 44dfc0bc..608e077d 100644 --- a/api/serializers.py +++ b/api/serializers.py @@ -561,7 +561,7 @@ class CNAMERecordSerializer(serializers.ModelSerializer): fields = ('alias', 'hostname') -class DNSZonesSerializer(serializers.ModelSerializer): +class DNSZonesSerializer(NamespacedHMSerializer): soa = SOARecordSerializer() ns_records = NSRecordSerializer(many=True, source='ns_set') originv4 = OriginV4RecordSerializer(source='origin') @@ -577,4 +577,7 @@ class DNSZonesSerializer(serializers.ModelSerializer): model = machines.Extension fields = ('name', 'soa', 'ns_records', 'originv4', 'originv6', 'mx_records', 'txt_records', 'srv_records', 'a_records', - 'aaaa_records', 'cname_records') + 'aaaa_records', 'cname_records', 'api_url') + extra_kwargs = { + 'api_url': {'view_name': 'dnszone-detail'} + } diff --git a/api/urls.py b/api/urls.py index 07f83afb..8138facb 100644 --- a/api/urls.py +++ b/api/urls.py @@ -86,11 +86,12 @@ router.register(r'users/listrights', views.ListRightViewSet) router.register(r'users/shells', views.ShellViewSet, base_name='shell') router.register(r'users/bans', views.BanViewSet) router.register(r'users/whitelists', views.WhitelistViewSet) +# DNS +router.register(r'dns/zones', views.DNSZonesViewSet, base_name='dnszone') urlpatterns = [ url(r'^', include(router.urls)), url(r'^dhcp/hostmacip', views.HostMacIpView.as_view()), - url(r'^dns/zones', views.DNSZonesView.as_view()), url(r'^services/regen', views.ServiceRegenView.as_view()), url(r'^token-auth', views.ObtainExpiringAuthToken.as_view()) ] diff --git a/api/views.py b/api/views.py index 1530fbb3..36061deb 100644 --- a/api/views.py +++ b/api/views.py @@ -340,7 +340,7 @@ class HostMacIpView(generics.ListAPIView): # DNS views -class DNSZonesView(generics.ListAPIView): +class DNSZonesViewSet(viewsets.ReadOnlyModelViewSet): queryset = machines.Extension.objects.all() serializer_class = serializers.DNSZonesSerializer From 761fad578a2b7865ad68d78ceda4a58a1afaaf70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Kervella?= Date: Sun, 10 Jun 2018 13:41:31 +0000 Subject: [PATCH 26/42] DNS view is a generic view and service regen view is a viewset --- CHANGELOG.md | 10 ++++++++++ api/serializers.py | 14 +++++--------- api/urls.py | 7 ++++--- api/views.py | 6 +++--- 4 files changed, 22 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b65a4ae..b41b96eb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,3 +43,13 @@ Refactored install_re2o.sh script. install_re2o.sh help ``` * The installation templates (LDIF files and `re2o/settings_locale.example.py`) have been changed to use `example.net` instead of `example.org` (more neutral and generic) + + + +## MR XXX: Cleanup and refactor API + +Activate HTTP Authorization passthrough in by adding the following in /etc/apache2/site-available/re2o.conf (example in install_utils/apache2/re2o.conf): +``` + WSGIPassAuthorization On +``` + diff --git a/api/serializers.py b/api/serializers.py index 608e077d..647f9d26 100644 --- a/api/serializers.py +++ b/api/serializers.py @@ -214,8 +214,6 @@ class ServiceSerializer(NamespacedHMSerializer): class ServiceLinkSerializer(NamespacedHMSerializer): - need_regen = serializers.BooleanField() - class Meta: model = machines.Service_link fields = ('service', 'server', 'last_regen', 'asked_regen', @@ -466,12 +464,13 @@ class WhitelistSerializer(NamespacedHMSerializer): class ServiceRegenSerializer(NamespacedHMSerializer): hostname = serializers.CharField(source='server.domain.name', read_only=True) service_name = serializers.CharField(source='service.service_type', read_only=True) + need_regen = serializers.BooleanField() class Meta: model = machines.Service_link fields = ('hostname', 'service_name', 'need_regen', 'api_url') extra_kwargs = { - 'api_url': {'view_name': 'servicelink-detail'} + 'api_url': {'view_name': 'serviceregen-detail'} } @@ -545,7 +544,7 @@ class ARecordSerializer(serializers.ModelSerializer): class AAAARecordSerializer(serializers.ModelSerializer): hostname = serializers.CharField(source='domain.name', read_only=True) - ipv6 = serializers.CharField(read_only=True) + ipv6 = Ipv6ListSerializer(many=True, read_only=True) class Meta: model = machines.Interface @@ -561,7 +560,7 @@ class CNAMERecordSerializer(serializers.ModelSerializer): fields = ('alias', 'hostname') -class DNSZonesSerializer(NamespacedHMSerializer): +class DNSZonesSerializer(serializers.ModelSerializer): soa = SOARecordSerializer() ns_records = NSRecordSerializer(many=True, source='ns_set') originv4 = OriginV4RecordSerializer(source='origin') @@ -577,7 +576,4 @@ class DNSZonesSerializer(NamespacedHMSerializer): model = machines.Extension fields = ('name', 'soa', 'ns_records', 'originv4', 'originv6', 'mx_records', 'txt_records', 'srv_records', 'a_records', - 'aaaa_records', 'cname_records', 'api_url') - extra_kwargs = { - 'api_url': {'view_name': 'dnszone-detail'} - } + 'aaaa_records', 'cname_records') diff --git a/api/urls.py b/api/urls.py index 8138facb..57ce1e12 100644 --- a/api/urls.py +++ b/api/urls.py @@ -86,12 +86,13 @@ router.register(r'users/listrights', views.ListRightViewSet) router.register(r'users/shells', views.ShellViewSet, base_name='shell') router.register(r'users/bans', views.BanViewSet) router.register(r'users/whitelists', views.WhitelistViewSet) -# DNS -router.register(r'dns/zones', views.DNSZonesViewSet, base_name='dnszone') +# SERVICES REGEN +router.register(r'services/regen', views.ServiceRegenViewSet, base_name='serviceregen') + urlpatterns = [ url(r'^', include(router.urls)), url(r'^dhcp/hostmacip', views.HostMacIpView.as_view()), - url(r'^services/regen', views.ServiceRegenView.as_view()), + url(r'^dns/zones', views.DNSZonesView.as_view()), url(r'^token-auth', views.ObtainExpiringAuthToken.as_view()) ] diff --git a/api/views.py b/api/views.py index 36061deb..b290d578 100644 --- a/api/views.py +++ b/api/views.py @@ -160,7 +160,7 @@ class ServiceViewSet(viewsets.ReadOnlyModelViewSet): serializer_class = serializers.ServiceSerializer -class ServiceLinkViewSet(viewsets.ModelViewSet): +class ServiceLinkViewSet(viewsets.ReadOnlyModelViewSet): queryset = machines.Service_link.objects.all() serializer_class = serializers.ServiceLinkSerializer @@ -316,7 +316,7 @@ class WhitelistViewSet(viewsets.ReadOnlyModelViewSet): # Services views -class ServiceRegenView(generics.ListAPIView): +class ServiceRegenViewSet(viewsets.ModelViewSet): serializer_class = serializers.ServiceRegenSerializer def get_queryset(self): @@ -340,7 +340,7 @@ class HostMacIpView(generics.ListAPIView): # DNS views -class DNSZonesViewSet(viewsets.ReadOnlyModelViewSet): +class DNSZonesView(generics.ListAPIView): queryset = machines.Extension.objects.all() serializer_class = serializers.DNSZonesSerializer From 0356947e4a2f79ec1c3fccf670124b00911ff684 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Kervella?= Date: Sun, 10 Jun 2018 15:12:42 +0000 Subject: [PATCH 27/42] Add endpoints for mailing --- api/serializers.py | 15 +++++++++++++++ api/urls.py | 2 ++ api/views.py | 25 +++++++++++++++++++++++-- 3 files changed, 40 insertions(+), 2 deletions(-) diff --git a/api/serializers.py b/api/serializers.py index 647f9d26..1a1f970e 100644 --- a/api/serializers.py +++ b/api/serializers.py @@ -577,3 +577,18 @@ class DNSZonesSerializer(serializers.ModelSerializer): fields = ('name', 'soa', 'ns_records', 'originv4', 'originv6', 'mx_records', 'txt_records', 'srv_records', 'a_records', 'aaaa_records', 'cname_records') + + +# Mailing + + +class MailingMemberSerializer(UserSerializer): + class Meta(UserSerializer.Meta): + fields = ('name', 'pseudo', 'email') + +class MailingSerializer(ClubSerializer): + members = MailingMemberSerializer(many=True) + admins = MailingMemberSerializer(source='administrators', many=True) + + class Meta(ClubSerializer.Meta): + fields = ('name', 'members', 'admins') diff --git a/api/urls.py b/api/urls.py index 57ce1e12..1d244de8 100644 --- a/api/urls.py +++ b/api/urls.py @@ -94,5 +94,7 @@ urlpatterns = [ url(r'^', include(router.urls)), url(r'^dhcp/hostmacip', views.HostMacIpView.as_view()), url(r'^dns/zones', views.DNSZonesView.as_view()), + url(r'^mailing/standard', views.StandardMailingView.as_view()), + url(r'^mailing/club', views.ClubMailingView.as_view()), url(r'^token-auth', views.ObtainExpiringAuthToken.as_view()) ] diff --git a/api/views.py b/api/views.py index b290d578..fce0bc23 100644 --- a/api/views.py +++ b/api/views.py @@ -31,7 +31,7 @@ from django.conf import settings from rest_framework.authtoken.views import ObtainAuthToken from rest_framework.authtoken.models import Token from rest_framework.response import Response -from rest_framework import viewsets, generics +from rest_framework import viewsets, generics, views import cotisations.models as cotisations import machines.models as machines @@ -39,9 +39,10 @@ import preferences.models as preferences import topologie.models as topologie import users.models as users -from re2o.utils import all_active_interfaces +from re2o.utils import all_active_interfaces, all_has_access from . import serializers +from .pagination import PageSizedPagination # COTISATIONS APP @@ -345,6 +346,26 @@ class DNSZonesView(generics.ListAPIView): serializer_class = serializers.DNSZonesSerializer +# Mailing views + + +class StandardMailingView(views.APIView): + pagination_class = PageSizedPagination + get_queryset = lambda self: all_has_access() + + def get(self, request, format=None): + adherents_data = serializers.MailingMemberSerializer(self.get_queryset(), many=True).data + data = [{'name': 'adherents', 'members': adherents_data}] + paginator = self.pagination_class() + paginator.paginate_queryset(data, request) + return paginator.get_paginated_response(data) + + +class ClubMailingView(generics.ListAPIView): + queryset = users.Club.objects.all() + serializer_class = serializers.MailingSerializer + + # Subclass the standard rest_framework.auth_token.views.ObtainAuthToken # in order to renew the lease of the token and add expiration time class ObtainExpiringAuthToken(ObtainAuthToken): From ca0744a38c29b580c60813ee212e7b688d529ef1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Kervella?= Date: Wed, 13 Jun 2018 22:39:37 +0000 Subject: [PATCH 28/42] Add customizable ACL-based permission --- api/permissions.py | 79 +++++++++++++++++++++++++++++++++++----------- api/settings.py | 2 +- api/views.py | 6 ++-- 3 files changed, 66 insertions(+), 21 deletions(-) diff --git a/api/permissions.py b/api/permissions.py index 66480af6..e04abdaf 100644 --- a/api/permissions.py +++ b/api/permissions.py @@ -3,14 +3,69 @@ from re2o.acl import can_create, can_edit, can_delete, can_view_all from . import acl -def can_see_api(_): +def can_see_api(*_, **__): return lambda user: acl.can_view(user) -class DefaultACLPermission(permissions.BasePermission): +def _get_param_in_view(view, param_name): + assert hasattr(view, 'get_'+param_name) \ + or getattr(view, param_name, None) is not None, ( + 'cannot apply {} on a view that does not set ' + '`.{}` or have a `.get_{}()` method.' + ).format(self.__class__.__name__, param_name, param_name) + + if hasattr(view, 'get_'+param_name): + param = getattr(view, 'get_'+param_name)() + assert param is not None, ( + '{}.get_{}() returned None' + ).format(view.__class__.__name__, param_name) + return param + return getattr(view, param_name) + + +class ACLPermission(permissions.BasePermission): + """ + Permission subclass for views that requires a specific model-based + permission or don't define a queryset + """ + + def get_required_permissions(self, method, view): + """ + Given a list of models and an HTTP method, return the list + of acl functions that the user is required to verify. + """ + perms_map = _get_param_in_view(view, 'perms_map') + + if method not in perms_map: + raise exceptions.MethodNotAllowed(method) + + return [can_see_api()] + list(perms_map[method]) + + def has_permission(self, request, view): + # Workaround to ensure ACLPermissions are not applied + # to the root view when using DefaultRouter. + if getattr(view, '_ignore_model_permissions', False): + return True + + if not request.user or not request.user.is_authenticated: + return False + + perms = self.get_required_permissions(request.method, view) + + return all(perm(request.user)[0] for perm in perms) + + def has_object_permission(self, request, view, obj): + # Should never be called here but documentation + # requires to implement this function + return False + + +class AutodetectACLPermission(permissions.BasePermission): """ Permission subclass in charge of checking the ACL to determine - if a user can access the models + if a user can access the models. Autodetect which ACL are required + based on a queryset. Requires `.queryset` or `.get_queryset()` + to be defined in the view. """ perms_map = { 'GET': [can_see_api, lambda model: model.can_view_all], @@ -46,29 +101,17 @@ class DefaultACLPermission(permissions.BasePermission): Given an object and an HTTP method, return the list of acl functions that the user is required to verify. """ - if method not in self.perms_map: + if method not in self.perms_obj_map: raise exceptions.MethodNotAllowed(method) - return [perm(obj) for perm in self.perms_map[method]] + return [perm(obj) for perm in self.perms_obj_map[method]] def _queryset(self, view): """ Return the queryset associated with view and raise an error is there is none. """ - assert hasattr(view, 'get_queryset') \ - or getattr(view, 'queryset', None) is not None, ( - 'Cannot apply {} on a view that does not set ' - '`.queryset` or have a `.get_queryset()` method.' - ).format(self.__class__.__name__) - - if hasattr(view, 'get_queryset'): - queryset = view.get_queryset() - assert queryset is not None, ( - '{}.get_queryset() returned None'.format(view.__class__.__name__) - ) - return queryset - return view.queryset + return _get_param_in_view(view, 'queryset') def has_permission(self, request, view): # Workaround to ensure ACLPermissions are not applied diff --git a/api/settings.py b/api/settings.py index c1ec4786..cd19594e 100644 --- a/api/settings.py +++ b/api/settings.py @@ -33,7 +33,7 @@ REST_FRAMEWORK = { 'rest_framework.authentication.SessionAuthentication', ), 'DEFAULT_PERMISSION_CLASSES': ( - 'api.permissions.DefaultACLPermission', + 'api.permissions.AutodetectACLPermission', ), 'DEFAULT_PAGINATION_CLASS': 'api.pagination.PageSizedPagination', 'PAGE_SIZE': 100 diff --git a/api/views.py b/api/views.py index fce0bc23..99ee3e44 100644 --- a/api/views.py +++ b/api/views.py @@ -43,6 +43,7 @@ from re2o.utils import all_active_interfaces, all_has_access from . import serializers from .pagination import PageSizedPagination +from .permissions import ACLPermission # COTISATIONS APP @@ -351,10 +352,11 @@ class DNSZonesView(generics.ListAPIView): class StandardMailingView(views.APIView): pagination_class = PageSizedPagination - get_queryset = lambda self: all_has_access() + permission_classes = (ACLPermission, ) + perms_map = {'GET' : [users.User.can_view_all]} def get(self, request, format=None): - adherents_data = serializers.MailingMemberSerializer(self.get_queryset(), many=True).data + adherents_data = serializers.MailingMemberSerializer(all_has_access(), many=True).data data = [{'name': 'adherents', 'members': adherents_data}] paginator = self.pagination_class() paginator.paginate_queryset(data, request) From 3a0dda0009e11030ba5181b4acc9bc00c73a7ae7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Kervella?= Date: Sat, 16 Jun 2018 18:35:08 +0000 Subject: [PATCH 29/42] Add preferences API endpoints --- api/serializers.py | 118 ++++++++++++++++++++++----------------------- api/urls.py | 22 ++++++--- api/views.py | 106 +++++++++++++++++++++++++--------------- 3 files changed, 140 insertions(+), 106 deletions(-) diff --git a/api/serializers.py b/api/serializers.py index 1a1f970e..9f4b89ed 100644 --- a/api/serializers.py +++ b/api/serializers.py @@ -239,66 +239,64 @@ class OuverturePortSerializer(NamespacedHMSerializer): # PREFERENCES APP -# class OptionalUserSerializer(NamespacedHMSerializer): -# tel_mandatory = serializers.BooleanField(source='is_tel_mandatory') -# -# class Meta: -# model = preferences.OptionalUser -# fields = ('tel_mandatory', 'user_solde', 'solde_negatif', 'max_solde', -# 'min_online_payement', 'gpg_fingerprint', -# 'all_can_create_club', 'self_adhesion', 'shell_default', -# 'api_url') -# -# -# class OptionalMachineSerializer(NamespacedHMSerializer): -# class Meta: -# model = preferences.OptionalMachine -# fields = ('password_machine', 'max_lambdauser_interfaces', -# 'max_lambdauser_aliases', 'ipv6_mode', 'create_machine', -# 'ipv6', 'api_url') -# -# -# class OptionalTopologieSerializer(NamespacedHMSerializer): -# class Meta: -# model = preferences.OptionalTopologie -# fields = ('radius_general_policy', 'vlan_decision_ok', -# 'vlan_decision_no', 'api_url') -# -# -# class GeneralOptionSerializer(NamespacedHMSerializer): -# class Meta: -# model = preferences.GeneralOption -# fields = ('general_message', 'search_display_page', -# 'pagination_number', 'pagination_large_number', -# 'req_expire_hrs', 'site_name', 'email_from', 'GTU_sum_up', -# 'GTU', 'api_url') -# -# -# class ServiceOptionSerializer(NamespacedHMSerializer): -# class Meta: -# model = preferences.ServiceOption -# fields = ('name', 'url', 'description', 'image', 'api_url') -# -# -# class AssoOptionSerializer(NamespacedHMSerializer): -# class Meta: -# model = preferences.AssoOption -# fields = ('name', 'siret', 'adresse1', 'adresse2', 'contact', -# 'telephone', 'pseudo', 'utilisateur_asso', 'payement', -# 'payement_id', 'payement_pass', 'description', 'api_url') -# -# -# class HomeOptionSerializer(NamespacedHMSerializer): -# class Meta: -# model = preferences.HomeOption -# fields = ('facebook_url', 'twitter_url', 'twitter_account_name', -# 'api_url') -# -# -# class MailMessageOptionSerializer(NamespacedHMSerializer): -# class Meta: -# model = preferences.MailMessageOption -# fields = ('welcome_mail_fr', 'welcome_mail_en', 'api_url') +class OptionalUserSerializer(NamespacedHMSerializer): + tel_mandatory = serializers.BooleanField(source='is_tel_mandatory') + + class Meta: + model = preferences.OptionalUser + fields = ('tel_mandatory', 'user_solde', 'solde_negatif', 'max_solde', + 'min_online_payment', 'gpg_fingerprint', + 'all_can_create_club', 'self_adhesion', 'shell_default') + + +class OptionalMachineSerializer(NamespacedHMSerializer): + class Meta: + model = preferences.OptionalMachine + fields = ('password_machine', 'max_lambdauser_interfaces', + 'max_lambdauser_aliases', 'ipv6_mode', 'create_machine', + 'ipv6') + + +class OptionalTopologieSerializer(NamespacedHMSerializer): + class Meta: + model = preferences.OptionalTopologie + fields = ('radius_general_policy', 'vlan_decision_ok', + 'vlan_decision_nok') + + +class GeneralOptionSerializer(NamespacedHMSerializer): + class Meta: + model = preferences.GeneralOption + fields = ('general_message', 'search_display_page', + 'pagination_number', 'pagination_large_number', + 'req_expire_hrs', 'site_name', 'email_from', 'GTU_sum_up', + 'GTU') + + +class ServiceSerializer(NamespacedHMSerializer): + class Meta: + model = preferences.Service + fields = ('name', 'url', 'description', 'image', 'api_url') + + +class AssoOptionSerializer(NamespacedHMSerializer): + class Meta: + model = preferences.AssoOption + fields = ('name', 'siret', 'adresse1', 'adresse2', 'contact', + 'telephone', 'pseudo', 'utilisateur_asso', 'payment', + 'payment_id', 'payment_pass', 'description') + + +class HomeOptionSerializer(NamespacedHMSerializer): + class Meta: + model = preferences.HomeOption + fields = ('facebook_url', 'twitter_url', 'twitter_account_name') + + +class MailMessageOptionSerializer(NamespacedHMSerializer): + class Meta: + model = preferences.MailMessageOption + fields = ('welcome_mail_fr', 'welcome_mail_en') diff --git a/api/urls.py b/api/urls.py index 1d244de8..dd3783dc 100644 --- a/api/urls.py +++ b/api/urls.py @@ -58,14 +58,7 @@ router.register(r'machines/servicelinks', views.ServiceLinkViewSet, base_name='s router.register(r'machines/ouvertureportlists', views.OuverturePortListViewSet) router.register(r'machines/ouvertureports', views.OuverturePortViewSet) # PREFERENCES APP -#router.register(r'preferences/optionaluser', views.OptionalUserSerializer) -#router.register(r'preferences/optionalmachine', views.OptionalMachineSerializer) -#router.register(r'preferences/optionaltopologie', views.OptionalTopologieSerializer) -#router.register(r'preferences/generaloption', views.GeneralOptionSerializer) -#router.register(r'preferences/serviceoption', views.ServiceOptionSerializer) -#router.register(r'preferences/assooption', views.AssoOptionSerializer) -#router.register(r'preferences/homeoption', views.HomeOptionSerializer) -#router.register(r'preferences/mailmessageoption', views.MailMessageOptionSerializer) +router.register(r'preferences/service', views.ServiceViewSet), # TOPOLOGIE APP router.register(r'topologie/stack', views.StackViewSet) router.register(r'topologie/acesspoint', views.AccessPointViewSet) @@ -91,10 +84,23 @@ router.register(r'services/regen', views.ServiceRegenViewSet, base_name='service urlpatterns = [ + # VIEWSETS url(r'^', include(router.urls)), + # PREFERENCES APP + url(r'^preferences/optionaluser', views.OptionalUserView.as_view()), + url(r'^preferences/optionalmachine', views.OptionalMachineView.as_view()), + url(r'^preferences/optionaltopologie', views.OptionalTopologieView.as_view()), + url(r'^preferences/generaloption', views.GeneralOptionView.as_view()), + url(r'^preferences/assooption', views.AssoOptionView.as_view()), + url(r'^preferences/homeoption', views.HomeOptionView.as_view()), + url(r'^preferences/mailmessageoption', views.MailMessageOptionView.as_view()), + # DHCP url(r'^dhcp/hostmacip', views.HostMacIpView.as_view()), + # DNS url(r'^dns/zones', views.DNSZonesView.as_view()), + # MAILING url(r'^mailing/standard', views.StandardMailingView.as_view()), url(r'^mailing/club', views.ClubMailingView.as_view()), + # TOKEN-AUTH url(r'^token-auth', views.ObtainExpiringAuthToken.as_view()) ] diff --git a/api/views.py b/api/views.py index 99ee3e44..7d4aaffc 100644 --- a/api/views.py +++ b/api/views.py @@ -178,45 +178,75 @@ class OuverturePortViewSet(viewsets.ReadOnlyModelViewSet): # PREFERENCES APP +# Those views differ a bit because there is only one object +# to display, so we don't bother with the listing part -# class OptionalUserViewSet(viewsets.ReadOnlyModelViewSet): -# queryset = preferences.OptionalUser.objects.all() -# serializer_class = serializers.OptionalUserSerializer -# -# -# class OptionalMachineViewSet(viewsets.ReadOnlyModelViewSet): -# queryset = preferences.OptionalMachine.objects.all() -# serializer_class = serializers.OptionalMachineSerializer -# -# -# class OptionalTopologieViewSet(viewsets.ReadOnlyModelViewSet): -# queryset = preferences.OptionalTopologie.objects.all() -# serializer_class = serializers.OptionalTopologieSerializer -# -# -# class GeneralOptionViewSet(viewsets.ReadOnlyModelViewSet): -# queryset = preferences.GeneralOption.objects.all() -# serializer_class = serializers.GeneralOptionSerializer -# -# -# class ServiceOptionViewSet(viewsets.ReadOnlyModelViewSet): -# queryset = preferences.ServiceOption.objects.all() -# serializer_class = serializers.ServiceOptionSerializer -# -# -# class AssoOptionViewSet(viewsets.ReadOnlyModelViewSet): -# queryset = preferences.AssoOption.objects.all() -# serializer_class = serializers.AssoOptionSerializer -# -# -# class HomeOptionViewSet(viewsets.ReadOnlyModelViewSet): -# queryset = preferences.HomeOption.objects.all() -# serializer_class = serializers.HomeOptionSerializer -# -# -# class MailMessageOptionViewSet(viewsets.ReadOnlyModelViewSet): -# queryset = preferences.MailMessageOption.objects.all() -# serializer_class = serializers.MailMessageOptionSerializer +class OptionalUserView(generics.RetrieveAPIView): + permission_classes = (ACLPermission, ) + perms_map = {'GET' : [preferences.OptionalUser.can_view_all]} + serializer_class = serializers.OptionalUserSerializer + + def get_object(self): + return preferences.OptionalUser.objects.first() + + +class OptionalMachineView(generics.RetrieveAPIView): + permission_classes = (ACLPermission, ) + perms_map = {'GET' : [preferences.OptionalMachine.can_view_all]} + serializer_class = serializers.OptionalMachineSerializer + + def get_object(self): + return preferences.OptionalMachine.objects.first() + + +class OptionalTopologieView(generics.RetrieveAPIView): + permission_classes = (ACLPermission, ) + perms_map = {'GET' : [preferences.OptionalTopologie.can_view_all]} + serializer_class = serializers.OptionalTopologieSerializer + + def get_object(self): + return preferences.OptionalTopologie.objects.first() + + +class GeneralOptionView(generics.RetrieveAPIView): + permission_classes = (ACLPermission, ) + perms_map = {'GET' : [preferences.GeneralOption.can_view_all]} + serializer_class = serializers.GeneralOptionSerializer + + def get_object(self): + return preferences.GeneralOption.objects.first() + + +class ServiceViewSet(viewsets.ReadOnlyModelViewSet): + queryset = preferences.Service.objects.all() + serializer_class = serializers.ServiceSerializer + + +class AssoOptionView(generics.RetrieveAPIView): + permission_classes = (ACLPermission, ) + perms_map = {'GET' : [preferences.AssoOption.can_view_all]} + serializer_class = serializers.AssoOptionSerializer + + def get_object(self): + return preferences.AssoOption.objects.first() + + +class HomeOptionView(generics.RetrieveAPIView): + permission_classes = (ACLPermission, ) + perms_map = {'GET' : [preferences.HomeOption.can_view_all]} + serializer_class = serializers.HomeOptionSerializer + + def get_object(self): + return preferences.HomeOption.objects.first() + + +class MailMessageOptionView(generics.RetrieveAPIView): + permission_classes = (ACLPermission, ) + perms_map = {'GET' : [preferences.MailMessageOption.can_view_all]} + serializer_class = serializers.MailMessageOptionSerializer + + def get_object(self): + return preferences.MailMessageOption.objects.first() # TOPOLOGIE APP From 374dd8da1ecd06239e69d473363277bab0d2401e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Kervella?= Date: Sat, 16 Jun 2018 19:20:13 +0000 Subject: [PATCH 30/42] Add a router that can register views --- api/routers.py | 123 +++++++++++++++++++++++++++++++++++++++++++++++ api/urls.py | 128 ++++++++++++++++++++++++------------------------- 2 files changed, 186 insertions(+), 65 deletions(-) create mode 100644 api/routers.py diff --git a/api/routers.py b/api/routers.py new file mode 100644 index 00000000..cea69690 --- /dev/null +++ b/api/routers.py @@ -0,0 +1,123 @@ +# Re2o est un logiciel d'administration développé initiallement au rezometz. Il +# se veut agnostique au réseau considéré, de manière à être installable en +# quelques clics. +# +# Copyright © 2018 Mael Kervella +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +"""api.routers + +Definition of the custom routers to generate the URLs of the API +""" + +from collections import OrderedDict +from django.conf.urls import url, include +from django.core.urlresolvers import NoReverseMatch +from rest_framework import views +from rest_framework.routers import DefaultRouter +from rest_framework.response import Response +from rest_framework.reverse import reverse +from rest_framework.schemas import SchemaGenerator +from rest_framework.settings import api_settings + +class AllViewsRouter(DefaultRouter): + def __init__(self, *args, **kwargs): + self.view_registry = [] + super(AllViewsRouter, self).__init__(*args, **kwargs) + + def register_viewset(self, *args, **kwargs): + """ + Register a viewset in the router + Alias of `register` for convenience + """ + return self.register(*args, **kwargs) + + def register_view(self, pattern, view, name=None): + """ + Register a view in the router + """ + if name is None: + name = self.get_default_name(pattern) + self.view_registry.append((pattern, view, name)) + + def get_default_name(self, pattern): + return pattern.split('/')[-1] + + def get_api_root_view(self, schema_urls=None): + """ + Return a view to use as the API root. + """ + api_root_dict = OrderedDict() + list_name = self.routes[0].name + for prefix, viewset, basename in self.registry: + api_root_dict[prefix] = list_name.format(basename=basename) + for pattern, view, name in self.view_registry: + api_root_dict[pattern] = name + + view_renderers = list(api_settings.DEFAULT_RENDERER_CLASSES) + schema_media_types = [] + + if schema_urls and self.schema_title: + view_renderers += list(self.schema_renderers) + schema_generator = SchemaGenerator( + title=self.schema_title, + patterns=schema_urls + ) + schema_media_types = [ + renderer.media_type + for renderer in self.schema_renderers + ] + + class APIRoot(views.APIView): + _ignore_model_permissions = True + renderer_classes = view_renderers + + def get(self, request, *args, **kwargs): + if request.accepted_renderer.media_type in schema_media_types: + # Return a schema response. + schema = schema_generator.get_schema(request) + if schema is None: + raise exceptions.PermissionDenied() + return Response(schema) + + # Return a plain {"name": "hyperlink"} response. + ret = OrderedDict() + namespace = request.resolver_match.namespace + for key, url_name in api_root_dict.items(): + if namespace: + url_name = namespace + ':' + url_name + try: + ret[key] = reverse( + url_name, + args=args, + kwargs=kwargs, + request=request, + format=kwargs.get('format', None) + ) + except NoReverseMatch: + # Don't bail out if eg. no list routes exist, only detail routes. + continue + + return Response(ret) + + return APIRoot.as_view() + + def get_urls(self): + urls = super(AllViewsRouter, self).get_urls() + + for pattern, view, name in self.view_registry: + urls.append(url(pattern, view.as_view(), name=name)) + + return urls diff --git a/api/urls.py b/api/urls.py index dd3783dc..edf399c8 100644 --- a/api/urls.py +++ b/api/urls.py @@ -25,82 +25,80 @@ Urls de l'api, pointant vers les fonctions de views from __future__ import unicode_literals from django.conf.urls import url, include -from rest_framework.routers import DefaultRouter +from .routers import AllViewsRouter from . import views -router = DefaultRouter() +router = AllViewsRouter() # COTISATIONS APP -router.register(r'cotisations/factures', views.FactureViewSet) -router.register(r'cotisations/ventes', views.VenteViewSet) -router.register(r'cotisations/articles', views.ArticleViewSet) -router.register(r'cotisations/banques', views.BanqueViewSet) -router.register(r'cotisations/paiements', views.PaiementViewSet) -router.register(r'cotisations/cotisations', views.CotisationViewSet) +router.register_viewset(r'cotisations/factures', views.FactureViewSet) +router.register_viewset(r'cotisations/ventes', views.VenteViewSet) +router.register_viewset(r'cotisations/articles', views.ArticleViewSet) +router.register_viewset(r'cotisations/banques', views.BanqueViewSet) +router.register_viewset(r'cotisations/paiements', views.PaiementViewSet) +router.register_viewset(r'cotisations/cotisations', views.CotisationViewSet) # MACHINES APP -router.register(r'machines/machines', views.MachineViewSet) -router.register(r'machines/machinetypes', views.MachineTypeViewSet) -router.register(r'machines/iptypes', views.IpTypeViewSet) -router.register(r'machines/vlans', views.VlanViewSet) -router.register(r'machines/nas', views.NasViewSet) -router.register(r'machines/soa', views.SOAViewSet) -router.register(r'machines/extensions', views.ExtensionViewSet) -router.register(r'machines/mx', views.MxViewSet) -router.register(r'machines/ns', views.NsViewSet) -router.register(r'machines/txt', views.TxtViewSet) -router.register(r'machines/srv', views.SrvViewSet) -router.register(r'machines/interfaces', views.InterfaceViewSet) -router.register(r'machines/ipv6lists', views.Ipv6ListViewSet) -router.register(r'machines/domains', views.DomainViewSet) -router.register(r'machines/iplists', views.IpListViewSet) -router.register(r'machines/services', views.ServiceViewSet) -router.register(r'machines/servicelinks', views.ServiceLinkViewSet, base_name='servicelink') -router.register(r'machines/ouvertureportlists', views.OuverturePortListViewSet) -router.register(r'machines/ouvertureports', views.OuverturePortViewSet) +router.register_viewset(r'machines/machines', views.MachineViewSet) +router.register_viewset(r'machines/machinetypes', views.MachineTypeViewSet) +router.register_viewset(r'machines/iptypes', views.IpTypeViewSet) +router.register_viewset(r'machines/vlans', views.VlanViewSet) +router.register_viewset(r'machines/nas', views.NasViewSet) +router.register_viewset(r'machines/soa', views.SOAViewSet) +router.register_viewset(r'machines/extensions', views.ExtensionViewSet) +router.register_viewset(r'machines/mx', views.MxViewSet) +router.register_viewset(r'machines/ns', views.NsViewSet) +router.register_viewset(r'machines/txt', views.TxtViewSet) +router.register_viewset(r'machines/srv', views.SrvViewSet) +router.register_viewset(r'machines/interfaces', views.InterfaceViewSet) +router.register_viewset(r'machines/ipv6lists', views.Ipv6ListViewSet) +router.register_viewset(r'machines/domains', views.DomainViewSet) +router.register_viewset(r'machines/iplists', views.IpListViewSet) +router.register_viewset(r'machines/services', views.ServiceViewSet) +router.register_viewset(r'machines/servicelinks', views.ServiceLinkViewSet, base_name='servicelink') +router.register_viewset(r'machines/ouvertureportlists', views.OuverturePortListViewSet) +router.register_viewset(r'machines/ouvertureports', views.OuverturePortViewSet) # PREFERENCES APP -router.register(r'preferences/service', views.ServiceViewSet), +router.register_viewset(r'preferences/service', views.ServiceViewSet), +router.register_view(r'preferences/optionaluser', views.OptionalUserView), +router.register_view(r'preferences/optionalmachine', views.OptionalMachineView), +router.register_view(r'preferences/optionaltopologie', views.OptionalTopologieView), +router.register_view(r'preferences/generaloption', views.GeneralOptionView), +router.register_view(r'preferences/assooption', views.AssoOptionView), +router.register_view(r'preferences/homeoption', views.HomeOptionView), +router.register_view(r'preferences/mailmessageoption', views.MailMessageOptionView), # TOPOLOGIE APP -router.register(r'topologie/stack', views.StackViewSet) -router.register(r'topologie/acesspoint', views.AccessPointViewSet) -router.register(r'topologie/switch', views.SwitchViewSet) -router.register(r'topologie/modelswitch', views.ModelSwitchViewSet) -router.register(r'topologie/constructorswitch', views.ConstructorSwitchViewSet) -router.register(r'topologie/switchbay', views.SwitchBayViewSet) -router.register(r'topologie/building', views.BuildingViewSet) -router.register(r'topologie/switchport', views.SwitchPortViewSet, base_name='switchport') -router.register(r'topologie/room', views.RoomViewSet) +router.register_viewset(r'topologie/stack', views.StackViewSet) +router.register_viewset(r'topologie/acesspoint', views.AccessPointViewSet) +router.register_viewset(r'topologie/switch', views.SwitchViewSet) +router.register_viewset(r'topologie/modelswitch', views.ModelSwitchViewSet) +router.register_viewset(r'topologie/constructorswitch', views.ConstructorSwitchViewSet) +router.register_viewset(r'topologie/switchbay', views.SwitchBayViewSet) +router.register_viewset(r'topologie/building', views.BuildingViewSet) +router.register_viewset(r'topologie/switchport', views.SwitchPortViewSet, base_name='switchport') +router.register_viewset(r'topologie/room', views.RoomViewSet) # USERS APP -router.register(r'users/users', views.UserViewSet) -router.register(r'users/clubs', views.ClubViewSet) -router.register(r'users/adherents', views.AdherentViewSet) -router.register(r'users/serviceusers', views.ServiceUserViewSet) -router.register(r'users/schools', views.SchoolViewSet) -router.register(r'users/listrights', views.ListRightViewSet) -router.register(r'users/shells', views.ShellViewSet, base_name='shell') -router.register(r'users/bans', views.BanViewSet) -router.register(r'users/whitelists', views.WhitelistViewSet) +router.register_viewset(r'users/users', views.UserViewSet) +router.register_viewset(r'users/clubs', views.ClubViewSet) +router.register_viewset(r'users/adherents', views.AdherentViewSet) +router.register_viewset(r'users/serviceusers', views.ServiceUserViewSet) +router.register_viewset(r'users/schools', views.SchoolViewSet) +router.register_viewset(r'users/listrights', views.ListRightViewSet) +router.register_viewset(r'users/shells', views.ShellViewSet, base_name='shell') +router.register_viewset(r'users/bans', views.BanViewSet) +router.register_viewset(r'users/whitelists', views.WhitelistViewSet) # SERVICES REGEN -router.register(r'services/regen', views.ServiceRegenViewSet, base_name='serviceregen') +router.register_viewset(r'services/regen', views.ServiceRegenViewSet, base_name='serviceregen') +# DHCP +router.register_view(r'dhcp/hostmacip', views.HostMacIpView), +# DNS +router.register_view(r'dns/zones', views.DNSZonesView), +# MAILING +router.register_view(r'mailing/standard', views.StandardMailingView), +router.register_view(r'mailing/club', views.ClubMailingView), +# TOKEN-AUTH +router.register_view(r'token-auth', views.ObtainExpiringAuthToken) urlpatterns = [ - # VIEWSETS url(r'^', include(router.urls)), - # PREFERENCES APP - url(r'^preferences/optionaluser', views.OptionalUserView.as_view()), - url(r'^preferences/optionalmachine', views.OptionalMachineView.as_view()), - url(r'^preferences/optionaltopologie', views.OptionalTopologieView.as_view()), - url(r'^preferences/generaloption', views.GeneralOptionView.as_view()), - url(r'^preferences/assooption', views.AssoOptionView.as_view()), - url(r'^preferences/homeoption', views.HomeOptionView.as_view()), - url(r'^preferences/mailmessageoption', views.MailMessageOptionView.as_view()), - # DHCP - url(r'^dhcp/hostmacip', views.HostMacIpView.as_view()), - # DNS - url(r'^dns/zones', views.DNSZonesView.as_view()), - # MAILING - url(r'^mailing/standard', views.StandardMailingView.as_view()), - url(r'^mailing/club', views.ClubMailingView.as_view()), - # TOKEN-AUTH - url(r'^token-auth', views.ObtainExpiringAuthToken.as_view()) ] From ecc5ed0b22d59a554b7ccbdf1dd05275d79d9c80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Kervella?= Date: Sun, 17 Jun 2018 01:06:58 +0000 Subject: [PATCH 31/42] Docstrings, docstrings everywhere --- api/acl.py | 52 ++++++------ api/authentication.py | 39 +++++++-- api/pagination.py | 46 ++++++++++- api/permissions.py | 183 +++++++++++++++++++++++++++++++++++------- api/routers.py | 54 ++++++++++--- api/serializers.py | 179 +++++++++++++++++++++++++++++++++++++---- api/settings.py | 10 +-- api/tests.py | 133 ++++++++++++++++++++---------- api/urls.py | 29 ++++--- api/views.py | 156 +++++++++++++++++++++++++++++++---- 10 files changed, 716 insertions(+), 165 deletions(-) diff --git a/api/acl.py b/api/acl.py index 0c7faae7..8c39aed0 100644 --- a/api/acl.py +++ b/api/acl.py @@ -1,9 +1,8 @@ -# -*- mode: python; coding: utf-8 -*- # Re2o est un logiciel d'administration développé initiallement au rezometz. Il # se veut agnostique au réseau considéré, de manière à être installable en # quelques clics. # -# Copyright © 2018 Maël Kervella +# Copyright © 2018 Maël Kervella # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -19,34 +18,41 @@ # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -"""api.acl +"""Defines the ACL for the whole API. -Here are defined some functions to check acl on the application. +Importing this module, creates the 'can view api' permission if not already +done. """ - from django.conf import settings from django.contrib.contenttypes.models import ContentType from django.contrib.auth.models import Permission +from django.utils.translation import ugettext_lazy as _ -# Creates the 'use_api' permission if not created -# The 'use_api' is a fake permission in the sense -# it is not associated with an existing model and -# this ensure the permission is created every tun -api_content_type, created = ContentType.objects.get_or_create( - app_label=settings.API_CONTENT_TYPE_APP_LABEL, - model=settings.API_CONTENT_TYPE_MODEL -) -if created: - api_content_type.save() -api_permission, created = Permission.objects.get_or_create( - name=settings.API_PERMISSION_NAME, - content_type=api_content_type, - codename=settings.API_PERMISSION_CODENAME -) -if created: - api_permission.save() +def _create_api_permission(): + """Creates the 'use_api' permission if not created. + + The 'use_api' is a fake permission in the sense it is not associated with an + existing model and this ensure the permission is created every time this file + is imported. + """ + api_content_type, created = ContentType.objects.get_or_create( + app_label=settings.API_CONTENT_TYPE_APP_LABEL, + model=settings.API_CONTENT_TYPE_MODEL + ) + if created: + api_content_type.save() + api_permission, created = Permission.objects.get_or_create( + name=settings.API_PERMISSION_NAME, + content_type=api_content_type, + codename=settings.API_PERMISSION_CODENAME + ) + if created: + api_permission.save() + + +_create_api_permission() def can_view(user): @@ -64,4 +70,4 @@ def can_view(user): 'codename': settings.API_PERMISSION_CODENAME } can = user.has_perm('%(app_label)s.%(codename)s' % kwargs) - return can, None if can else "Vous ne pouvez pas voir cette application." + return can, None if can else _("You cannot see this application.") diff --git a/api/authentication.py b/api/authentication.py index 4dc5a6f3..469c51f1 100644 --- a/api/authentication.py +++ b/api/authentication.py @@ -1,20 +1,43 @@ +# Re2o est un logiciel d'administration développé initiallement au rezometz. Il +# se veut agnostique au réseau considéré, de manière à être installable en +# quelques clics. +# +# Copyright © 2018 Maël Kervella +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +"""Defines the authentication classes used in the API to authenticate a user. +""" + import datetime + from django.conf import settings from django.utils.translation import ugettext_lazy as _ from rest_framework.authentication import TokenAuthentication from rest_framework import exceptions class ExpiringTokenAuthentication(TokenAuthentication): + """Authenticate a user if the provided token is valid and not expired. + """ def authenticate_credentials(self, key): - model = self.get_model() - try: - token = model.objects.select_related('user').get(key=key) - except model.DoesNotExist: - raise exceptions.AuthenticationFailed(_('Invalid token.')) - - if not token.user.is_active: - raise exceptions.AuthenticationFailed(_('User inactive or deleted.')) + """See base class. Add the verification the token is not expired. + """ + base = super(ExpiringTokenAuthentication, self) + user, token = base.authenticate_credentials(key) + # Check that the genration time of the token is not too old token_duration = datetime.timedelta( seconds=settings.API_TOKEN_DURATION ) diff --git a/api/pagination.py b/api/pagination.py index 2fc0aaf7..20dcad6e 100644 --- a/api/pagination.py +++ b/api/pagination.py @@ -1,15 +1,57 @@ +# Re2o est un logiciel d'administration développé initiallement au rezometz. Il +# se veut agnostique au réseau considéré, de manière à être installable en +# quelques clics. +# +# Copyright © 2018 Maël Kervella +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +"""Defines the pagination classes used in the API to paginate the results. +""" + from rest_framework import pagination class PageSizedPagination(pagination.PageNumberPagination): - """ - Pagination subclass to all to control the page size + """Provide the possibility to control the page size by using the + 'page_size' parameter. The value 'all' can be used for this parameter + to retrieve all the results in a single page. + + Attributes: + page_size_query_param: The string to look for in the parameters of + a query to get the page_size requested. + all_pages_strings: A set of strings that can be used in the query to + request all results in a single page. + max_page_size: The maximum number of results a page can output no + matter what is requested. """ page_size_query_param = 'page_size' all_pages_strings = ('all',) max_page_size = 10000 def get_page_size(self, request): + """Retrieve the size of the page according to the parameters of the + request. + + Args: + request: the request of the user + + Returns: + A integer between 0 and `max_page_size` that represent the size + of the page to use. + """ try: page_size_str = request.query_params[self.page_size_query_param] if page_size_str in self.all_pages_strings: diff --git a/api/permissions.py b/api/permissions.py index e04abdaf..53f06620 100644 --- a/api/permissions.py +++ b/api/permissions.py @@ -1,13 +1,61 @@ +# Re2o est un logiciel d'administration développé initiallement au rezometz. Il +# se veut agnostique au réseau considéré, de manière à être installable en +# quelques clics. +# +# Copyright © 2018 Maël Kervella +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +"""Defines the permission classes used in the API. +""" + from rest_framework import permissions, exceptions + from re2o.acl import can_create, can_edit, can_delete, can_view_all from . import acl + def can_see_api(*_, **__): + """Check if a user can view the API. + + Returns: + A function that takes a user as an argument and returns + an ACL tuple that assert this user can see the API. + """ return lambda user: acl.can_view(user) def _get_param_in_view(view, param_name): + """Utility function to retrieve an attribute in a view passed in argument. + + Uses the result of `{view}.get_{param_name}()` if existing else uses the + value of `{view}.{param_name}` directly. + + Args: + view: The view where to look into. + param_name: The name of the attribute to look for. + + Returns: + The result of the getter function if found else the value of the + attribute itself. + + Raises: + AssertionError: None of the getter function or the attribute are + defined in the view. + """ assert hasattr(view, 'get_'+param_name) \ or getattr(view, param_name, None) is not None, ( 'cannot apply {} on a view that does not set ' @@ -24,15 +72,30 @@ def _get_param_in_view(view, param_name): class ACLPermission(permissions.BasePermission): - """ - Permission subclass for views that requires a specific model-based - permission or don't define a queryset + """A permission class used to check the ACL to validate the permissions + of a user. + + The view must define a `.get_perms_map()` or a `.perms_map` attribute. + See the wiki for the syntax of this attribute. """ def get_required_permissions(self, method, view): - """ - Given a list of models and an HTTP method, return the list - of acl functions that the user is required to verify. + """Build the list of permissions required for the request to be + accepted. + + Args: + method: The HTTP method name used for the request. + view: The view which is responding to the request. + + Returns: + The list of ACL functions to apply to a user in order to check + if he has the right permissions. + + Raises: + AssertionError: None of `.get_perms_map()` or `.perms_map` are + defined in the view. + rest_framework.exception.MethodNotAllowed: The requested method + is not allowed for this view. """ perms_map = _get_param_in_view(view, 'perms_map') @@ -42,6 +105,22 @@ class ACLPermission(permissions.BasePermission): return [can_see_api()] + list(perms_map[method]) def has_permission(self, request, view): + """Check that the user has the permissions to perform the request. + + Args: + request: The request performed. + view: The view which is responding to the request. + + Returns: + A boolean indicating if the user has the permission to + perform the request. + + Raises: + AssertionError: None of `.get_perms_map()` or `.perms_map` are + defined in the view. + rest_framework.exception.MethodNotAllowed: The requested method + is not allowed for this view. + """ # Workaround to ensure ACLPermissions are not applied # to the root view when using DefaultRouter. if getattr(view, '_ignore_model_permissions', False): @@ -54,19 +133,20 @@ class ACLPermission(permissions.BasePermission): return all(perm(request.user)[0] for perm in perms) - def has_object_permission(self, request, view, obj): - # Should never be called here but documentation - # requires to implement this function - return False - class AutodetectACLPermission(permissions.BasePermission): + """A permission class used to autodetect the ACL needed to validate the + permissions of a user based on the queryset of the view. + + The view must define a `.get_queryset()` or a `.queryset` attribute. + + Attributes: + perms_map: The mapping of each valid HTTP method to the required + model-based ACL permissions. + perms_obj_map: The mapping of each valid HTTP method to the required + object-based ACL permissions. """ - Permission subclass in charge of checking the ACL to determine - if a user can access the models. Autodetect which ACL are required - based on a queryset. Requires `.queryset` or `.get_queryset()` - to be defined in the view. - """ + perms_map = { 'GET': [can_see_api, lambda model: model.can_view_all], 'OPTIONS': [can_see_api, lambda model: model.can_view_all], @@ -87,9 +167,20 @@ class AutodetectACLPermission(permissions.BasePermission): } def get_required_permissions(self, method, model): - """ - Given a model and an HTTP method, return the list of acl - functions that the user is required to verify. + """Build the list of model-based permissions required for the + request to be accepted. + + Args: + method: The HTTP method name used for the request. + view: The view which is responding to the request. + + Returns: + The list of ACL functions to apply to a user in order to check + if he has the right permissions. + + Raises: + rest_framework.exception.MethodNotAllowed: The requested method + is not allowed for this view. """ if method not in self.perms_map: raise exceptions.MethodNotAllowed(method) @@ -97,9 +188,20 @@ class AutodetectACLPermission(permissions.BasePermission): return [perm(model) for perm in self.perms_map[method]] def get_required_object_permissions(self, method, obj): - """ - Given an object and an HTTP method, return the list of acl - functions that the user is required to verify. + """Build the list of object-based permissions required for the + request to be accepted. + + Args: + method: The HTTP method name used for the request. + view: The view which is responding to the request. + + Returns: + The list of ACL functions to apply to a user in order to check + if he has the right permissions. + + Raises: + rest_framework.exception.MethodNotAllowed: The requested method + is not allowed for this view. """ if method not in self.perms_obj_map: raise exceptions.MethodNotAllowed(method) @@ -107,13 +209,26 @@ class AutodetectACLPermission(permissions.BasePermission): return [perm(obj) for perm in self.perms_obj_map[method]] def _queryset(self, view): - """ - Return the queryset associated with view and raise an error - is there is none. - """ return _get_param_in_view(view, 'queryset') def has_permission(self, request, view): + """Check that the user has the model-based permissions to perform + the request. + + Args: + request: The request performed. + view: The view which is responding to the request. + + Returns: + A boolean indicating if the user has the permission to + perform the request. + + Raises: + AssertionError: None of `.get_queryset()` or `.queryset` are + defined in the view. + rest_framework.exception.MethodNotAllowed: The requested method + is not allowed for this view. + """ # Workaround to ensure ACLPermissions are not applied # to the root view when using DefaultRouter. if getattr(view, '_ignore_model_permissions', False): @@ -128,8 +243,22 @@ class AutodetectACLPermission(permissions.BasePermission): return all(perm(request.user)[0] for perm in perms) def has_object_permission(self, request, view, obj): + """Check that the user has the object-based permissions to perform + the request. + + Args: + request: The request performed. + view: The view which is responding to the request. + + Returns: + A boolean indicating if the user has the permission to + perform the request. + + Raises: + rest_framework.exception.MethodNotAllowed: The requested method + is not allowed for this view. + """ # authentication checks have already executed via has_permission - queryset = self._queryset(view) user = request.user perms = self.get_required_object_permissions(request.method, obj) diff --git a/api/routers.py b/api/routers.py index cea69690..fcfb5077 100644 --- a/api/routers.py +++ b/api/routers.py @@ -2,7 +2,7 @@ # se veut agnostique au réseau considéré, de manière à être installable en # quelques clics. # -# Copyright © 2018 Mael Kervella +# Copyright © 2018 Mael Kervella # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -17,12 +17,12 @@ # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -"""api.routers -Definition of the custom routers to generate the URLs of the API +"""Defines the custom routers to generate the URLs of the API. """ from collections import OrderedDict + from django.conf.urls import url, include from django.core.urlresolvers import NoReverseMatch from rest_framework import views @@ -32,32 +32,60 @@ from rest_framework.reverse import reverse from rest_framework.schemas import SchemaGenerator from rest_framework.settings import api_settings + class AllViewsRouter(DefaultRouter): + """A router that can register both viewsets and views and generates + a full API root page with all the generated URLs. + """ + def __init__(self, *args, **kwargs): self.view_registry = [] super(AllViewsRouter, self).__init__(*args, **kwargs) def register_viewset(self, *args, **kwargs): - """ - Register a viewset in the router - Alias of `register` for convenience + """Register a viewset in the router. Alias of `register` for + convenience. + + See `register` in the base class for details. """ return self.register(*args, **kwargs) def register_view(self, pattern, view, name=None): - """ - Register a view in the router + """Register a view in the router. + + Args: + pattern: The URL pattern to use for this view. + view: The class-based view to register. + name: An optional name for the route generated. Defaults is + based on the pattern last section (delimited by '/'). """ if name is None: name = self.get_default_name(pattern) self.view_registry.append((pattern, view, name)) def get_default_name(self, pattern): + """Returns the name to use for the route if none was specified. + + Args: + pattern: The pattern for this route. + + Returns: + The name to use for this route. + """ return pattern.split('/')[-1] def get_api_root_view(self, schema_urls=None): - """ - Return a view to use as the API root. + """Create a class-based view to use as the API root. + + Highly inspired by the base class. See details on the implementation + in the base class. The only difference is that registered view URLs + are added after the registered viewset URLs on this root API page. + + Args: + schema_urls: A schema to use for the URLs. + + Returns: + The view to use to display the root API page. """ api_root_dict = OrderedDict() list_name = self.routes[0].name @@ -115,6 +143,12 @@ class AllViewsRouter(DefaultRouter): return APIRoot.as_view() def get_urls(self): + """Builds the list of URLs to register. + + Returns: + A list of the URLs generated based on the viewsets registered + followed by the URLs generated based on the views registered. + """ urls = super(AllViewsRouter, self).get_urls() for pattern, view, name in self.view_registry: diff --git a/api/serializers.py b/api/serializers.py index 9f4b89ed..a1e73091 100644 --- a/api/serializers.py +++ b/api/serializers.py @@ -2,7 +2,7 @@ # se veut agnostique au réseau considéré, de manière à être installable en # quelques clics. # -# Copyright © 2018 Mael Kervella +# Copyright © 2018 Maël Kervella # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -18,8 +18,7 @@ # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -""" -Serializers for the API app +"""Defines the serializers of the API """ from rest_framework import serializers @@ -31,12 +30,15 @@ import topologie.models as topologie import users.models as users +# The namespace used for the API. It must match the namespace used in the +# urlpatterns to include the API URLs. API_NAMESPACE = 'api' class NamespacedHRField(serializers.HyperlinkedRelatedField): - """ A HyperlinkedRelatedField subclass to automatically prefix - view names with a namespace """ + """A `rest_framework.serializers.HyperlinkedRelatedField` subclass to + automatically prefix view names with the API namespace. + """ def __init__(self, view_name=None, **kwargs): if view_name is not None: view_name = '%s:%s' % (API_NAMESPACE, view_name) @@ -44,8 +46,9 @@ class NamespacedHRField(serializers.HyperlinkedRelatedField): class NamespacedHIField(serializers.HyperlinkedIdentityField): - """ A HyperlinkedIdentityField subclass to automatically prefix - view names with a namespace """ + """A `rest_framework.serializers.HyperlinkedIdentityField` subclass to + automatically prefix view names with teh API namespace. + """ def __init__(self, view_name=None, **kwargs): if view_name is not None: view_name = '%s:%s' % (API_NAMESPACE, view_name) @@ -53,16 +56,19 @@ class NamespacedHIField(serializers.HyperlinkedIdentityField): class NamespacedHMSerializer(serializers.HyperlinkedModelSerializer): - """ A HyperlinkedModelSerializer subclass to use `NamespacedHRField` as - field and automatically prefix view names with a namespace """ + """A `rest_framework.serializers.HyperlinkedModelSerializer` subclass to + automatically prefix view names with the API namespace. + """ serializer_related_field = NamespacedHRField serializer_url_field = NamespacedHIField -# COTISATIONS APP +# COTISATIONS class FactureSerializer(NamespacedHMSerializer): + """Serialize `cotisations.models.Facture` objects. + """ class Meta: model = cotisations.Facture fields = ('user', 'paiement', 'banque', 'cheque', 'date', 'valid', @@ -70,6 +76,8 @@ class FactureSerializer(NamespacedHMSerializer): class VenteSerializer(NamespacedHMSerializer): + """Serialize `cotisations.models.Vente` objects. + """ class Meta: model = cotisations.Vente fields = ('facture', 'number', 'name', 'prix', 'duration', @@ -77,6 +85,8 @@ class VenteSerializer(NamespacedHMSerializer): class ArticleSerializer(NamespacedHMSerializer): + """Serialize `cotisations.models.Article` objects. + """ class Meta: model = cotisations.Article fields = ('name', 'prix', 'duration', 'type_user', @@ -84,40 +94,52 @@ class ArticleSerializer(NamespacedHMSerializer): class BanqueSerializer(NamespacedHMSerializer): + """Serialize `cotisations.models.Banque` objects. + """ class Meta: model = cotisations.Banque fields = ('name', 'api_url') class PaiementSerializer(NamespacedHMSerializer): + """Serialize `cotisations.models.Paiement` objects. + """ class Meta: model = cotisations.Paiement fields = ('moyen', 'type_paiement', 'api_url') class CotisationSerializer(NamespacedHMSerializer): + """Serialize `cotisations.models.Cotisation` objects. + """ class Meta: model = cotisations.Cotisation fields = ('vente', 'type_cotisation', 'date_start', 'date_end', 'api_url') -# MACHINES APP +# MACHINES class MachineSerializer(NamespacedHMSerializer): + """Serialize `machines.models.Machine` objects. + """ class Meta: model = machines.Machine fields = ('user', 'name', 'active', 'api_url') class MachineTypeSerializer(NamespacedHMSerializer): + """Serialize `machines.models.MachineType` objects. + """ class Meta: model = machines.MachineType fields = ('type', 'ip_type', 'api_url') class IpTypeSerializer(NamespacedHMSerializer): + """Serialize `machines.models.IpType` objects. + """ class Meta: model = machines.IpType fields = ('type', 'extension', 'need_infra', 'domaine_ip_start', @@ -126,12 +148,16 @@ class IpTypeSerializer(NamespacedHMSerializer): class VlanSerializer(NamespacedHMSerializer): + """Serialize `machines.models.Vlan` objects. + """ class Meta: model = machines.Vlan fields = ('vlan_id', 'name', 'comment', 'api_url') class NasSerializer(NamespacedHMSerializer): + """Serialize `machines.models.Nas` objects. + """ class Meta: model = machines.Nas fields = ('name', 'nas_type', 'machine_type', 'port_access_mode', @@ -139,6 +165,8 @@ class NasSerializer(NamespacedHMSerializer): class SOASerializer(NamespacedHMSerializer): + """Serialize `machines.models.SOA` objects. + """ class Meta: model = machines.SOA fields = ('name', 'mail', 'refresh', 'retry', 'expire', 'ttl', @@ -146,6 +174,8 @@ class SOASerializer(NamespacedHMSerializer): class ExtensionSerializer(NamespacedHMSerializer): + """Serialize `machines.models.Extension` objects. + """ class Meta: model = machines.Extension fields = ('name', 'need_infra', 'origin', 'origin_v6', 'soa', @@ -153,24 +183,32 @@ class ExtensionSerializer(NamespacedHMSerializer): class MxSerializer(NamespacedHMSerializer): + """Serialize `machines.models.Mx` objects. + """ class Meta: model = machines.Mx fields = ('zone', 'priority', 'name', 'api_url') class NsSerializer(NamespacedHMSerializer): + """Serialize `machines.models.Ns` objects. + """ class Meta: model = machines.Ns fields = ('zone', 'ns', 'api_url') class TxtSerializer(NamespacedHMSerializer): + """Serialize `machines.models.Txt` objects. + """ class Meta: model = machines.Txt fields = ('zone', 'field1', 'field2', 'api_url') class SrvSerializer(NamespacedHMSerializer): + """Serialize `machines.models.Srv` objects. + """ class Meta: model = machines.Srv fields = ('service', 'protocole', 'extension', 'ttl', 'priority', @@ -178,6 +216,8 @@ class SrvSerializer(NamespacedHMSerializer): class InterfaceSerializer(NamespacedHMSerializer): + """Serialize `machines.models.Interface` objects. + """ mac_address = serializers.CharField() active = serializers.BooleanField(source='is_active') @@ -188,12 +228,16 @@ class InterfaceSerializer(NamespacedHMSerializer): class Ipv6ListSerializer(NamespacedHMSerializer): + """Serialize `machines.models.Ipv6List` objects. + """ class Meta: model = machines.Ipv6List fields = ('ipv6', 'interface', 'slaac_ip', 'api_url') class DomainSerializer(NamespacedHMSerializer): + """Serialize `machines.models.Domain` objects. + """ class Meta: model = machines.Domain fields = ('interface_parent', 'name', 'extension', 'cname', @@ -201,12 +245,16 @@ class DomainSerializer(NamespacedHMSerializer): class IpListSerializer(NamespacedHMSerializer): + """Serialize `machines.models.IpList` objects. + """ class Meta: model = machines.IpList fields = ('ipv4', 'ip_type', 'need_infra', 'api_url') class ServiceSerializer(NamespacedHMSerializer): + """Serialize `machines.models.Service` objects. + """ class Meta: model = machines.Service fields = ('service_type', 'min_time_regen', 'regular_time_regen', @@ -214,6 +262,8 @@ class ServiceSerializer(NamespacedHMSerializer): class ServiceLinkSerializer(NamespacedHMSerializer): + """Serialize `machines.models.Service_link` objects. + """ class Meta: model = machines.Service_link fields = ('service', 'server', 'last_regen', 'asked_regen', @@ -224,6 +274,8 @@ class ServiceLinkSerializer(NamespacedHMSerializer): class OuverturePortListSerializer(NamespacedHMSerializer): + """Serialize `machines.models.OuverturePortList` objects. + """ class Meta: model = machines.OuverturePortList fields = ('name', 'tcp_ports_in', 'udp_ports_in', 'tcp_ports_out', @@ -231,15 +283,19 @@ class OuverturePortListSerializer(NamespacedHMSerializer): class OuverturePortSerializer(NamespacedHMSerializer): + """Serialize `machines.models.OuverturePort` objects. + """ class Meta: model = machines.OuverturePort fields = ('begin', 'end', 'port_list', 'protocole', 'io', 'api_url') -# PREFERENCES APP +# PREFERENCES class OptionalUserSerializer(NamespacedHMSerializer): + """Serialize `preferences.models.OptionalUser` objects. + """ tel_mandatory = serializers.BooleanField(source='is_tel_mandatory') class Meta: @@ -250,6 +306,8 @@ class OptionalUserSerializer(NamespacedHMSerializer): class OptionalMachineSerializer(NamespacedHMSerializer): + """Serialize `preferences.models.OptionalMachine` objects. + """ class Meta: model = preferences.OptionalMachine fields = ('password_machine', 'max_lambdauser_interfaces', @@ -258,6 +316,8 @@ class OptionalMachineSerializer(NamespacedHMSerializer): class OptionalTopologieSerializer(NamespacedHMSerializer): + """Serialize `preferences.models.OptionalTopologie` objects. + """ class Meta: model = preferences.OptionalTopologie fields = ('radius_general_policy', 'vlan_decision_ok', @@ -265,6 +325,8 @@ class OptionalTopologieSerializer(NamespacedHMSerializer): class GeneralOptionSerializer(NamespacedHMSerializer): + """Serialize `preferences.models.GeneralOption` objects. + """ class Meta: model = preferences.GeneralOption fields = ('general_message', 'search_display_page', @@ -274,12 +336,16 @@ class GeneralOptionSerializer(NamespacedHMSerializer): class ServiceSerializer(NamespacedHMSerializer): + """Serialize `preferences.models.Service` objects. + """ class Meta: model = preferences.Service fields = ('name', 'url', 'description', 'image', 'api_url') class AssoOptionSerializer(NamespacedHMSerializer): + """Serialize `preferences.models.AssoOption` objects. + """ class Meta: model = preferences.AssoOption fields = ('name', 'siret', 'adresse1', 'adresse2', 'contact', @@ -288,22 +354,28 @@ class AssoOptionSerializer(NamespacedHMSerializer): class HomeOptionSerializer(NamespacedHMSerializer): + """Serialize `preferences.models.HomeOption` objects. + """ class Meta: model = preferences.HomeOption fields = ('facebook_url', 'twitter_url', 'twitter_account_name') class MailMessageOptionSerializer(NamespacedHMSerializer): + """Serialize `preferences.models.MailMessageOption` objects. + """ class Meta: model = preferences.MailMessageOption fields = ('welcome_mail_fr', 'welcome_mail_en') -# TOPOLOGIE APP +# TOPOLOGIE class StackSerializer(NamespacedHMSerializer): + """Serialize `topologie.models.Stack` objects + """ class Meta: model = topologie.Stack fields = ('name', 'stack_id', 'details', 'member_id_min', @@ -311,12 +383,16 @@ class StackSerializer(NamespacedHMSerializer): class AccessPointSerializer(NamespacedHMSerializer): + """Serialize `topologie.models.AccessPoint` objects + """ class Meta: model = topologie.AccessPoint fields = ('user', 'name', 'active', 'location', 'api_url') class SwitchSerializer(NamespacedHMSerializer): + """Serialize `topologie.models.Switch` objects + """ port_amount = serializers.IntegerField(source='number') class Meta: model = topologie.Switch @@ -325,30 +401,40 @@ class SwitchSerializer(NamespacedHMSerializer): class ModelSwitchSerializer(NamespacedHMSerializer): + """Serialize `topologie.models.ModelSwitch` objects + """ class Meta: model = topologie.ModelSwitch fields = ('reference', 'constructor', 'api_url') class ConstructorSwitchSerializer(NamespacedHMSerializer): + """Serialize `topologie.models.ConstructorSwitch` objects + """ class Meta: model = topologie.ConstructorSwitch fields = ('name', 'api_url') class SwitchBaySerializer(NamespacedHMSerializer): + """Serialize `topologie.models.SwitchBay` objects + """ class Meta: model = topologie.SwitchBay fields = ('name', 'building', 'info', 'api_url') class BuildingSerializer(NamespacedHMSerializer): + """Serialize `topologie.models.Building` objects + """ class Meta: model = topologie.Building fields = ('name', 'api_url') class SwitchPortSerializer(NamespacedHMSerializer): + """Serialize `topologie.models.Port` objects + """ class Meta: model = topologie.Port fields = ('switch', 'port', 'room', 'machine_interface', 'related', @@ -360,15 +446,19 @@ class SwitchPortSerializer(NamespacedHMSerializer): class RoomSerializer(NamespacedHMSerializer): + """Serialize `topologie.models.Room` objects + """ class Meta: model = topologie.Room fields = ('name', 'details', 'api_url') -# USERS APP +# USERS class UserSerializer(NamespacedHMSerializer): + """Serialize `users.models.User` objects. + """ access = serializers.BooleanField(source='has_access') uid = serializers.IntegerField(source='uid_number') @@ -383,6 +473,8 @@ class UserSerializer(NamespacedHMSerializer): class ClubSerializer(NamespacedHMSerializer): + """Serialize `users.models.Club` objects. + """ name = serializers.CharField(source='surname') access = serializers.BooleanField(source='has_access') uid = serializers.IntegerField(source='uid_number') @@ -399,6 +491,8 @@ class ClubSerializer(NamespacedHMSerializer): class AdherentSerializer(NamespacedHMSerializer): + """Serialize `users.models.Adherent` objects. + """ access = serializers.BooleanField(source='has_access') uid = serializers.IntegerField(source='uid_number') @@ -413,24 +507,32 @@ class AdherentSerializer(NamespacedHMSerializer): class ServiceUserSerializer(NamespacedHMSerializer): + """Serialize `users.models.ServiceUser` objects. + """ class Meta: model = users.ServiceUser fields = ('pseudo', 'access_group', 'comment', 'api_url') class SchoolSerializer(NamespacedHMSerializer): + """Serialize `users.models.School` objects. + """ class Meta: model = users.School fields = ('name', 'api_url') class ListRightSerializer(NamespacedHMSerializer): + """Serialize `users.models.ListRight` objects. + """ class Meta: model = users.ListRight fields = ('unix_name', 'gid', 'critical', 'details', 'api_url') class ShellSerializer(NamespacedHMSerializer): + """Serialize `users.models.ListShell` objects. + """ class Meta: model = users.ListShell fields = ('shell', 'api_url') @@ -440,6 +542,8 @@ class ShellSerializer(NamespacedHMSerializer): class BanSerializer(NamespacedHMSerializer): + """Serialize `users.models.Ban` objects. + """ active = serializers.BooleanField(source='is_active') class Meta: @@ -449,6 +553,8 @@ class BanSerializer(NamespacedHMSerializer): class WhitelistSerializer(NamespacedHMSerializer): + """Serialize `users.models.Whitelist` objects. + """ active = serializers.BooleanField(source='is_active') class Meta: @@ -456,10 +562,12 @@ class WhitelistSerializer(NamespacedHMSerializer): fields = ('user', 'raison', 'date_start', 'date_end', 'active', 'api_url') -# Services +# SERVICE REGEN class ServiceRegenSerializer(NamespacedHMSerializer): + """Serialize the data about the services to regen. + """ hostname = serializers.CharField(source='server.domain.name', read_only=True) service_name = serializers.CharField(source='service.service_type', read_only=True) need_regen = serializers.BooleanField() @@ -476,6 +584,9 @@ class ServiceRegenSerializer(NamespacedHMSerializer): class HostMacIpSerializer(serializers.ModelSerializer): + """Serialize the data about the hostname-ipv4-mac address association + to build the DHCP lease files. + """ hostname = serializers.CharField(source='domain.name', read_only=True) extension = serializers.CharField(source='domain.extension.name', read_only=True) mac_address = serializers.CharField(read_only=True) @@ -490,22 +601,34 @@ class HostMacIpSerializer(serializers.ModelSerializer): class SOARecordSerializer(SOASerializer): + """Serialize `machines.models.SOA` objects with the data needed to + generate a SOA DNS record. + """ class Meta: model = machines.SOA fields = ('name', 'mail', 'refresh', 'retry', 'expire', 'ttl') class OriginV4RecordSerializer(IpListSerializer): + """Serialize `machines.models.IpList` objects with the data needed to + generate an IPv4 Origin DNS record. + """ class Meta(IpListSerializer.Meta): fields = ('ipv4',) class OriginV6RecordSerializer(Ipv6ListSerializer): + """Serialize `machines.models.Ipv6List` objects with the data needed to + generate an IPv6 Origin DNS record. + """ class Meta(Ipv6ListSerializer.Meta): fields = ('ipv6',) class NSRecordSerializer(NsSerializer): + """Serialize `machines.models.Ns` objects with the data needed to + generate a NS DNS record. + """ target = serializers.CharField(source='ns.name', read_only=True) class Meta(NsSerializer.Meta): @@ -513,6 +636,9 @@ class NSRecordSerializer(NsSerializer): class MXRecordSerializer(MxSerializer): + """Serialize `machines.models.Mx` objects with the data needed to + generate a MX DNS record. + """ target = serializers.CharField(source='name.name', read_only=True) class Meta(MxSerializer.Meta): @@ -520,11 +646,17 @@ class MXRecordSerializer(MxSerializer): class TXTRecordSerializer(TxtSerializer): + """Serialize `machines.models.Txt` objects with the data needed to + generate a TXT DNS record. + """ class Meta(TxtSerializer.Meta): fields = ('field1', 'field2') class SRVRecordSerializer(SrvSerializer): + """Serialize `machines.models.Srv` objects with the data needed to + generate a SRV DNS record. + """ target = serializers.CharField(source='target.name', read_only=True) class Meta(SrvSerializer.Meta): @@ -532,6 +664,9 @@ class SRVRecordSerializer(SrvSerializer): class ARecordSerializer(serializers.ModelSerializer): + """Serialize `machines.models.Interface` objects with the data needed to + generate a A DNS record. + """ hostname = serializers.CharField(source='domain.name', read_only=True) ipv4 = serializers.CharField(source='ipv4.ipv4', read_only=True) @@ -541,6 +676,9 @@ class ARecordSerializer(serializers.ModelSerializer): class AAAARecordSerializer(serializers.ModelSerializer): + """Serialize `machines.models.Interface` objects with the data needed to + generate a AAAA DNS record. + """ hostname = serializers.CharField(source='domain.name', read_only=True) ipv6 = Ipv6ListSerializer(many=True, read_only=True) @@ -550,6 +688,9 @@ class AAAARecordSerializer(serializers.ModelSerializer): class CNAMERecordSerializer(serializers.ModelSerializer): + """Serialize `machines.models.Domain` objects with the data needed to + generate a CNAME DNS record. + """ alias = serializers.CharField(source='cname.name', read_only=True) hostname = serializers.CharField(source='name', read_only=True) @@ -559,6 +700,8 @@ class CNAMERecordSerializer(serializers.ModelSerializer): class DNSZonesSerializer(serializers.ModelSerializer): + """Serialize the data about DNS Zones. + """ soa = SOARecordSerializer() ns_records = NSRecordSerializer(many=True, source='ns_set') originv4 = OriginV4RecordSerializer(source='origin') @@ -577,14 +720,18 @@ class DNSZonesSerializer(serializers.ModelSerializer): 'aaaa_records', 'cname_records') -# Mailing +# MAILING class MailingMemberSerializer(UserSerializer): + """Serialize the data about a mailing member. + """ class Meta(UserSerializer.Meta): fields = ('name', 'pseudo', 'email') class MailingSerializer(ClubSerializer): + """Serialize the data about a mailing. + """ members = MailingMemberSerializer(many=True) admins = MailingMemberSerializer(source='administrators', many=True) diff --git a/api/settings.py b/api/settings.py index cd19594e..f8171638 100644 --- a/api/settings.py +++ b/api/settings.py @@ -1,11 +1,8 @@ -# -*- mode: python; coding: utf-8 -*- # Re2o est un logiciel d'administration développé initiallement au rezometz. Il # se veut agnostique au réseau considéré, de manière à être installable en # quelques clics. # -# Copyright © 2017 Gabriel Détraz -# Copyright © 2017 Goulven Kermarec -# Copyright © 2017 Augustin Lemesle +# Copyright © 2018 Maël Kervella # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -21,8 +18,7 @@ # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -"""api.settings -Django settings specific to the API. +"""Settings specific to the API. """ # RestFramework config for API @@ -49,4 +45,6 @@ API_PERMISSION_CODENAME = 'use_api' API_APPS = ( 'rest_framework.authtoken', ) + +# The expiration time for an authentication token API_TOKEN_DURATION = 86400 # 24 hours diff --git a/api/tests.py b/api/tests.py index aa4747eb..24545864 100644 --- a/api/tests.py +++ b/api/tests.py @@ -2,9 +2,7 @@ # se veut agnostique au réseau considéré, de manière à être installable en # quelques clics. # -# Copyright © 2017 Gabriel Détraz -# Copyright © 2017 Goulven Kermarec -# Copyright © 2017 Augustin Lemesle +# Copyright © 2018 Maël Kervella # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -19,8 +17,7 @@ # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -"""api.tests -The tests for the API module. +"""Defines the test suite for the API """ import json @@ -34,13 +31,25 @@ import users.models as users class APIEndpointsTestCase(APITestCase): - # URLs that don't require to be authenticated + """Test case to test that all endpoints are reachable with respects to + authentication and permission checks. + + Attributes: + no_auth_endpoints: A list of endpoints that should be reachable + without authentication. + auth_no_perm_endpoints: A list of endpoints that should be reachable + when being authenticated but without permissions. + auth_perm_endpoints: A list of endpoints that should be reachable + when being authenticated and having the correct permissions. + stduser: A standard user with no permission used for the tests and + initialized at the beggining of this test case. + superuser: A superuser (with all permissions) used for the tests and + initialized at the beggining of this test case. + """ no_auth_endpoints = [ '/api/' ] - # URLs that require to be authenticated and have no special permissions auth_no_perm_endpoints = [] - # URLs that require to be authenticated and have special permissions auth_perm_endpoints = [ '/api/cotisations/articles/', # '/api/cotisations/articles//', @@ -160,49 +169,62 @@ class APIEndpointsTestCase(APITestCase): cls.superuser.delete() super().tearDownClass() - def check_responses_code(self, urls, expected_code, formats=[None], + def check_responses_code(self, urls, expected_code, formats=None, assert_more=None): - """ - Utility function to test if a list of urls answer an expected code + """Utility function to test if a list of urls answer an expected code. - :param urls: (list) The list of urls to test - :param expected_code: (int) The HTTP return code expected - :param formats: (list) The list of formats to use for the request - (Default: [None]) - :param assert_more: (func) A function to assert more specific data - in the same test. It is evaluated with the responsem object, the - url and the format used. + Args: + urls: The list of urls to test + expected_code: The HTTP return code expected + formats: The list of formats to use for the request. Default is to + only test `None` format. + assert_more: An optional function to assert more specific data in + the same test. The response object, the url and the format + used are passed as arguments. + + Raises: + AssertionError: The response got did not have the expected status + code. + Any exception raised in the evalutation of `assert_more`. """ + if formats is None: + formats = [None] for url in urls: for format in formats: with self.subTest(url=url, format=format): response = self.client.get(url, format=format) assert response.status_code == expected_code - if assert_more: + if assert_more is not None: assert_more(response, url, format) def test_no_auth_endpoints_with_no_auth(self): - """ - Test that every endpoint that does not require to be authenticated, - returns a Ok (200) response when not authenticated. + """Tests that every endpoint that does not require to be + authenticated, returns a Ok (200) response when not authenticated. + + Raises: + AssertionError: An endpoint did not have a 200 status code. """ urls = [endpoint.replace('', '1') for endpoint in self.no_auth_endpoints] self.check_responses_code(urls, codes.ok) def test_auth_endpoints_with_no_auth(self): - """ - Test that every endpoint that does require to be authenticated, + """Tests that every endpoint that does require to be authenticated, returns a Unauthorized (401) response when not authenticated. + + Raises: + AssertionError: An endpoint did not have a 401 status code. """ urls = [endpoint.replace('', '1') for endpoint in \ self.auth_no_perm_endpoints + self.auth_perm_endpoints] self.check_responses_code(urls, codes.unauthorized) def test_no_auth_endpoints_with_auth(self): - """ - Test that every endpoint that does not require to be authenticated, - returns a Ok (200) response when authenticated. + """Tests that every endpoint that does not require to be + authenticated, returns a Ok (200) response when authenticated. + + Raises: + AssertionError: An endpoint did not have a 200 status code. """ self.client.force_authenticate(user=self.stduser) urls = [endpoint.replace('', '1') @@ -210,10 +232,12 @@ class APIEndpointsTestCase(APITestCase): self.check_responses_code(urls, codes.ok) def test_auth_no_perm_endpoints_with_auth_and_no_perm(self): - """ - Test that every endpoint that does require to be authenticated and - no special permissions, returns a Ok (200) response when - authenticated but without permissions. + """Tests that every endpoint that does require to be authenticated and + no special permissions, returns a Ok (200) response when authenticated + but without permissions. + + Raises: + AssertionError: An endpoint did not have a 200 status code. """ self.client.force_authenticate(user=self.stduser) urls = [endpoint.replace('', '1') @@ -221,10 +245,12 @@ class APIEndpointsTestCase(APITestCase): self.check_responses_code(urls, codes.ok) def test_auth_perm_endpoints_with_auth_and_no_perm(self): - """ - Test that every endpoint that does require to be authenticated and + """Tests that every endpoint that does require to be authenticated and special permissions, returns a Forbidden (403) response when authenticated but without permissions. + + Raises: + AssertionError: An endpoint did not have a 403 status code. """ self.client.force_authenticate(user=self.stduser) urls = [endpoint.replace('', '1') @@ -232,9 +258,11 @@ class APIEndpointsTestCase(APITestCase): self.check_responses_code(urls, codes.forbidden) def test_auth_endpoints_with_auth_and_perm(self): - """ - Test that every endpoint that does require to be authenticated, - returns a Ok (200) response when authenticated with all permissions + """Tests that every endpoint that does require to be authenticated, + returns a Ok (200) response when authenticated with all permissions. + + Raises: + AssertionError: An endpoint did not have a 200 status code. """ self.client.force_authenticate(user=self.superuser) urls = [endpoint.replace('', '1') for endpoint \ @@ -242,10 +270,12 @@ class APIEndpointsTestCase(APITestCase): self.check_responses_code(urls, codes.ok) def test_endpoints_not_found(self): - """ - Test that every endpoint that uses a primary key parameter, + """Tests that every endpoint that uses a primary key parameter, returns a Not Found (404) response when queried with non-existing - primary key + primary key. + + Raises: + AssertionError: An endpoint did not have a 404 status code. """ self.client.force_authenticate(user=self.superuser) # Select only the URLs with '' and replace it with '42' @@ -255,9 +285,12 @@ class APIEndpointsTestCase(APITestCase): self.check_responses_code(urls, codes.not_found) def test_formats(self): - """ - Test that every endpoint returns a Ok (200) response when using - different formats. Also checks that 'json' format returns a valid json + """Tests that every endpoint returns a Ok (200) response when using + different formats. Also checks that 'json' format returns a valid + JSON object. + + Raises: + AssertionError: An endpoint did not have a 200 status code. """ self.client.force_authenticate(user=self.superuser) @@ -275,6 +308,14 @@ class APIEndpointsTestCase(APITestCase): assert_more=assert_more) class APIPaginationTestCase(APITestCase): + """Test case to check that the pagination is used on all endpoints that + should use it. + + Attributes: + endpoints: A list of endpoints that should use the pagination. + superuser: A superuser used in the tests to access the endpoints. + """ + endpoints = [ '/api/cotisations/articles/', '/api/cotisations/banques/', @@ -338,8 +379,12 @@ class APIPaginationTestCase(APITestCase): super().tearDownClass() def test_pagination(self): - """ - Test that every endpoint is using the pagination correctly + """Tests that every endpoint is using the pagination correctly. + + Raises: + AssertionError: An endpoint did not have one the following keyword + in the JSOn response: 'count', 'next', 'previous', 'results' + or more that 100 results were returned. """ self.client.force_authenticate(self.superuser) for url in self.endpoints: diff --git a/api/urls.py b/api/urls.py index edf399c8..9353975f 100644 --- a/api/urls.py +++ b/api/urls.py @@ -2,7 +2,7 @@ # se veut agnostique au réseau considéré, de manière à être installable en # quelques clics. # -# Copyright © 2018 Mael Kervella +# Copyright © 2018 Maël Kervella # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -17,27 +17,30 @@ # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -"""api.urls -Urls de l'api, pointant vers les fonctions de views +"""Defines the URLs of the API + +A custom router is used to register all the routes. That allows to register +all the URL patterns from the viewsets as a standard router but, the views +can also be register. That way a complete API root page presenting all URLs +can be generated automatically. """ -from __future__ import unicode_literals - from django.conf.urls import url, include -from .routers import AllViewsRouter from . import views +from .routers import AllViewsRouter + router = AllViewsRouter() -# COTISATIONS APP +# COTISATIONS router.register_viewset(r'cotisations/factures', views.FactureViewSet) router.register_viewset(r'cotisations/ventes', views.VenteViewSet) router.register_viewset(r'cotisations/articles', views.ArticleViewSet) router.register_viewset(r'cotisations/banques', views.BanqueViewSet) router.register_viewset(r'cotisations/paiements', views.PaiementViewSet) router.register_viewset(r'cotisations/cotisations', views.CotisationViewSet) -# MACHINES APP +# MACHINES router.register_viewset(r'machines/machines', views.MachineViewSet) router.register_viewset(r'machines/machinetypes', views.MachineTypeViewSet) router.register_viewset(r'machines/iptypes', views.IpTypeViewSet) @@ -57,7 +60,7 @@ router.register_viewset(r'machines/services', views.ServiceViewSet) router.register_viewset(r'machines/servicelinks', views.ServiceLinkViewSet, base_name='servicelink') router.register_viewset(r'machines/ouvertureportlists', views.OuverturePortListViewSet) router.register_viewset(r'machines/ouvertureports', views.OuverturePortViewSet) -# PREFERENCES APP +# PREFERENCES router.register_viewset(r'preferences/service', views.ServiceViewSet), router.register_view(r'preferences/optionaluser', views.OptionalUserView), router.register_view(r'preferences/optionalmachine', views.OptionalMachineView), @@ -66,7 +69,7 @@ router.register_view(r'preferences/generaloption', views.GeneralOptionView), router.register_view(r'preferences/assooption', views.AssoOptionView), router.register_view(r'preferences/homeoption', views.HomeOptionView), router.register_view(r'preferences/mailmessageoption', views.MailMessageOptionView), -# TOPOLOGIE APP +# TOPOLOGIE router.register_viewset(r'topologie/stack', views.StackViewSet) router.register_viewset(r'topologie/acesspoint', views.AccessPointViewSet) router.register_viewset(r'topologie/switch', views.SwitchViewSet) @@ -76,7 +79,7 @@ router.register_viewset(r'topologie/switchbay', views.SwitchBayViewSet) router.register_viewset(r'topologie/building', views.BuildingViewSet) router.register_viewset(r'topologie/switchport', views.SwitchPortViewSet, base_name='switchport') router.register_viewset(r'topologie/room', views.RoomViewSet) -# USERS APP +# USERS router.register_viewset(r'users/users', views.UserViewSet) router.register_viewset(r'users/clubs', views.ClubViewSet) router.register_viewset(r'users/adherents', views.AdherentViewSet) @@ -86,7 +89,7 @@ router.register_viewset(r'users/listrights', views.ListRightViewSet) router.register_viewset(r'users/shells', views.ShellViewSet, base_name='shell') router.register_viewset(r'users/bans', views.BanViewSet) router.register_viewset(r'users/whitelists', views.WhitelistViewSet) -# SERVICES REGEN +# SERVICE REGEN router.register_viewset(r'services/regen', views.ServiceRegenViewSet, base_name='serviceregen') # DHCP router.register_view(r'dhcp/hostmacip', views.HostMacIpView), @@ -95,7 +98,7 @@ router.register_view(r'dns/zones', views.DNSZonesView), # MAILING router.register_view(r'mailing/standard', views.StandardMailingView), router.register_view(r'mailing/club', views.ClubMailingView), -# TOKEN-AUTH +# TOKEN AUTHENTICATION router.register_view(r'token-auth', views.ObtainExpiringAuthToken) diff --git a/api/views.py b/api/views.py index 7d4aaffc..9ee6ea75 100644 --- a/api/views.py +++ b/api/views.py @@ -18,16 +18,16 @@ # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -"""api.views +"""Defines the views of the API -The views for the API app. They should all return JSON data and not fallback on -HTML pages such as the login and index pages for a better integration. +All views inherits the `rest_framework.views.APIview` to respect the +REST API requirements such as dealing with HTTP status code, format of +the response (JSON or other), the CSRF exempting, ... """ import datetime from django.conf import settings - from rest_framework.authtoken.views import ObtainAuthToken from rest_framework.authtoken.models import Token from rest_framework.response import Response @@ -38,7 +38,6 @@ import machines.models as machines import preferences.models as preferences import topologie.models as topologie import users.models as users - from re2o.utils import all_active_interfaces, all_has_access from . import serializers @@ -46,142 +45,195 @@ from .pagination import PageSizedPagination from .permissions import ACLPermission -# COTISATIONS APP +# COTISATIONS class FactureViewSet(viewsets.ReadOnlyModelViewSet): + """Exposes list and details of `cotisations.models.Facture` objects. + """ queryset = cotisations.Facture.objects.all() serializer_class = serializers.FactureSerializer class VenteViewSet(viewsets.ReadOnlyModelViewSet): + """Exposes list and details of `cotisations.models.Vente` objects. + """ queryset = cotisations.Vente.objects.all() serializer_class = serializers.VenteSerializer class ArticleViewSet(viewsets.ReadOnlyModelViewSet): + """Exposes list and details of `cotisations.models.Article` objects. + """ queryset = cotisations.Article.objects.all() serializer_class = serializers.ArticleSerializer class BanqueViewSet(viewsets.ReadOnlyModelViewSet): + """Exposes list and details of `cotisations.models.Banque` objects. + """ queryset = cotisations.Banque.objects.all() serializer_class = serializers.BanqueSerializer class PaiementViewSet(viewsets.ReadOnlyModelViewSet): + """Exposes list and details of `cotisations.models.Paiement` objects. + """ queryset = cotisations.Paiement.objects.all() serializer_class = serializers.PaiementSerializer class CotisationViewSet(viewsets.ReadOnlyModelViewSet): + """Exposes list and details of `cotisations.models.Cotisation` objects. + """ queryset = cotisations.Cotisation.objects.all() serializer_class = serializers.CotisationSerializer -# MACHINES APP +# MACHINES class MachineViewSet(viewsets.ReadOnlyModelViewSet): + """Exposes list and details of `machines.models.Machine` objects. + """ queryset = machines.Machine.objects.all() serializer_class = serializers.MachineSerializer class MachineTypeViewSet(viewsets.ReadOnlyModelViewSet): + """Exposes list and details of `machines.models.MachineType` objects. + """ queryset = machines.MachineType.objects.all() serializer_class = serializers.MachineTypeSerializer class IpTypeViewSet(viewsets.ReadOnlyModelViewSet): + """Exposes list and details of `machines.models.IpType` objects. + """ queryset = machines.IpType.objects.all() serializer_class = serializers.IpTypeSerializer class VlanViewSet(viewsets.ReadOnlyModelViewSet): + """Exposes list and details of `machines.models.Vlan` objects. + """ queryset = machines.Vlan.objects.all() serializer_class = serializers.VlanSerializer class NasViewSet(viewsets.ReadOnlyModelViewSet): + """Exposes list and details of `machines.models.Nas` objects. + """ queryset = machines.Nas.objects.all() serializer_class = serializers.NasSerializer class SOAViewSet(viewsets.ReadOnlyModelViewSet): + """Exposes list and details of `machines.models.SOA` objects. + """ queryset = machines.SOA.objects.all() serializer_class = serializers.SOASerializer class ExtensionViewSet(viewsets.ReadOnlyModelViewSet): + """Exposes list and details of `machines.models.Extension` objects. + """ queryset = machines.Extension.objects.all() serializer_class = serializers.ExtensionSerializer class MxViewSet(viewsets.ReadOnlyModelViewSet): + """Exposes list and details of `machines.models.Mx` objects. + """ queryset = machines.Mx.objects.all() serializer_class = serializers.MxSerializer class NsViewSet(viewsets.ReadOnlyModelViewSet): + """Exposes list and details of `machines.models.Ns` objects. + """ queryset = machines.Ns.objects.all() serializer_class = serializers.NsSerializer class TxtViewSet(viewsets.ReadOnlyModelViewSet): + """Exposes list and details of `machines.models.Txt` objects. + """ queryset = machines.Txt.objects.all() serializer_class = serializers.TxtSerializer class SrvViewSet(viewsets.ReadOnlyModelViewSet): + """Exposes list and details of `machines.models.Srv` objects. + """ queryset = machines.Srv.objects.all() serializer_class = serializers.SrvSerializer class InterfaceViewSet(viewsets.ReadOnlyModelViewSet): + """Exposes list and details of `machines.models.Interface` objects. + """ queryset = machines.Interface.objects.all() serializer_class = serializers.InterfaceSerializer class Ipv6ListViewSet(viewsets.ReadOnlyModelViewSet): + """Exposes list and details of `machines.models.Ipv6List` objects. + """ queryset = machines.Ipv6List.objects.all() serializer_class = serializers.Ipv6ListSerializer class DomainViewSet(viewsets.ReadOnlyModelViewSet): + """Exposes list and details of `machines.models.Domain` objects. + """ queryset = machines.Domain.objects.all() serializer_class = serializers.DomainSerializer class IpListViewSet(viewsets.ReadOnlyModelViewSet): + """Exposes list and details of `machines.models.IpList` objects. + """ queryset = machines.IpList.objects.all() serializer_class = serializers.IpListSerializer class ServiceViewSet(viewsets.ReadOnlyModelViewSet): + """Exposes list and details of `machines.models.Service` objects. + """ queryset = machines.Service.objects.all() serializer_class = serializers.ServiceSerializer class ServiceLinkViewSet(viewsets.ReadOnlyModelViewSet): + """Exposes list and details of `machines.models.Service_link` objects. + """ queryset = machines.Service_link.objects.all() serializer_class = serializers.ServiceLinkSerializer class OuverturePortListViewSet(viewsets.ReadOnlyModelViewSet): + """Exposes list and details of `machines.models.OuverturePortList` + objects. + """ queryset = machines.OuverturePortList.objects.all() serializer_class = serializers.OuverturePortListSerializer class OuverturePortViewSet(viewsets.ReadOnlyModelViewSet): + """Exposes list and details of `machines.models.OuverturePort` objects. + """ queryset = machines.OuverturePort.objects.all() serializer_class = serializers.OuverturePortSerializer -# PREFERENCES APP +# PREFERENCES # Those views differ a bit because there is only one object # to display, so we don't bother with the listing part class OptionalUserView(generics.RetrieveAPIView): + """Exposes details of `preferences.models.` settings. + """ permission_classes = (ACLPermission, ) perms_map = {'GET' : [preferences.OptionalUser.can_view_all]} serializer_class = serializers.OptionalUserSerializer @@ -191,6 +243,8 @@ class OptionalUserView(generics.RetrieveAPIView): class OptionalMachineView(generics.RetrieveAPIView): + """Exposes details of `preferences.models.OptionalMachine` settings. + """ permission_classes = (ACLPermission, ) perms_map = {'GET' : [preferences.OptionalMachine.can_view_all]} serializer_class = serializers.OptionalMachineSerializer @@ -200,6 +254,8 @@ class OptionalMachineView(generics.RetrieveAPIView): class OptionalTopologieView(generics.RetrieveAPIView): + """Exposes details of `preferences.models.OptionalTopologie` settings. + """ permission_classes = (ACLPermission, ) perms_map = {'GET' : [preferences.OptionalTopologie.can_view_all]} serializer_class = serializers.OptionalTopologieSerializer @@ -209,6 +265,8 @@ class OptionalTopologieView(generics.RetrieveAPIView): class GeneralOptionView(generics.RetrieveAPIView): + """Exposes details of `preferences.models.GeneralOption` settings. + """ permission_classes = (ACLPermission, ) perms_map = {'GET' : [preferences.GeneralOption.can_view_all]} serializer_class = serializers.GeneralOptionSerializer @@ -218,11 +276,15 @@ class GeneralOptionView(generics.RetrieveAPIView): class ServiceViewSet(viewsets.ReadOnlyModelViewSet): + """Exposes list and details of `preferences.models.Service` objects. + """ queryset = preferences.Service.objects.all() serializer_class = serializers.ServiceSerializer class AssoOptionView(generics.RetrieveAPIView): + """Exposes details of `preferences.models.AssoOption` settings. + """ permission_classes = (ACLPermission, ) perms_map = {'GET' : [preferences.AssoOption.can_view_all]} serializer_class = serializers.AssoOptionSerializer @@ -232,6 +294,8 @@ class AssoOptionView(generics.RetrieveAPIView): class HomeOptionView(generics.RetrieveAPIView): + """Exposes details of `preferences.models.HomeOption` settings. + """ permission_classes = (ACLPermission, ) perms_map = {'GET' : [preferences.HomeOption.can_view_all]} serializer_class = serializers.HomeOptionSerializer @@ -241,6 +305,8 @@ class HomeOptionView(generics.RetrieveAPIView): class MailMessageOptionView(generics.RetrieveAPIView): + """Exposes details of `preferences.models.MailMessageOption` settings. + """ permission_classes = (ACLPermission, ) perms_map = {'GET' : [preferences.MailMessageOption.can_view_all]} serializer_class = serializers.MailMessageOptionSerializer @@ -249,106 +315,145 @@ class MailMessageOptionView(generics.RetrieveAPIView): return preferences.MailMessageOption.objects.first() -# TOPOLOGIE APP +# TOPOLOGIE class StackViewSet(viewsets.ReadOnlyModelViewSet): + """Exposes list and details of `topologie.models.Stack` objects. + """ queryset = topologie.Stack.objects.all() serializer_class = serializers.StackSerializer class AccessPointViewSet(viewsets.ReadOnlyModelViewSet): + """Exposes list and details of `topologie.models.AccessPoint` objects. + """ queryset = topologie.AccessPoint.objects.all() serializer_class = serializers.AccessPointSerializer class SwitchViewSet(viewsets.ReadOnlyModelViewSet): + """Exposes list and details of `topologie.models.Switch` objects. + """ queryset = topologie.Switch.objects.all() serializer_class = serializers.SwitchSerializer class ModelSwitchViewSet(viewsets.ReadOnlyModelViewSet): + """Exposes list and details of `topologie.models.ModelSwitch` objects. + """ queryset = topologie.ModelSwitch.objects.all() serializer_class = serializers.ModelSwitchSerializer class ConstructorSwitchViewSet(viewsets.ReadOnlyModelViewSet): + """Exposes list and details of `topologie.models.ConstructorSwitch` + objects. + """ queryset = topologie.ConstructorSwitch.objects.all() serializer_class = serializers.ConstructorSwitchSerializer class SwitchBayViewSet(viewsets.ReadOnlyModelViewSet): + """Exposes list and details of `topologie.models.SwitchBay` objects. + """ queryset = topologie.SwitchBay.objects.all() serializer_class = serializers.SwitchBaySerializer class BuildingViewSet(viewsets.ReadOnlyModelViewSet): + """Exposes list and details of `topologie.models.Building` objects. + """ queryset = topologie.Building.objects.all() serializer_class = serializers.BuildingSerializer class SwitchPortViewSet(viewsets.ReadOnlyModelViewSet): + """Exposes list and details of `topologie.models.Port` objects. + """ queryset = topologie.Port.objects.all() serializer_class = serializers.SwitchPortSerializer class RoomViewSet(viewsets.ReadOnlyModelViewSet): + """Exposes list and details of `topologie.models.Room` objects. + """ queryset = topologie.Room.objects.all() serializer_class = serializers.RoomSerializer -# USER APP +# USER class UserViewSet(viewsets.ReadOnlyModelViewSet): + """Exposes list and details of `users.models.Users` objects. + """ queryset = users.User.objects.all() serializer_class = serializers.UserSerializer class ClubViewSet(viewsets.ReadOnlyModelViewSet): + """Exposes list and details of `users.models.Club` objects. + """ queryset = users.Club.objects.all() serializer_class = serializers.ClubSerializer class AdherentViewSet(viewsets.ReadOnlyModelViewSet): + """Exposes list and details of `users.models.Adherent` objects. + """ queryset = users.Adherent.objects.all() serializer_class = serializers.AdherentSerializer class ServiceUserViewSet(viewsets.ReadOnlyModelViewSet): + """Exposes list and details of `users.models.ServiceUser` objects. + """ queryset = users.ServiceUser.objects.all() serializer_class = serializers.ServiceUserSerializer class SchoolViewSet(viewsets.ReadOnlyModelViewSet): + """Exposes list and details of `users.models.School` objects. + """ queryset = users.School.objects.all() serializer_class = serializers.SchoolSerializer class ListRightViewSet(viewsets.ReadOnlyModelViewSet): + """Exposes list and details of `users.models.ListRight` objects. + """ queryset = users.ListRight.objects.all() serializer_class = serializers.ListRightSerializer class ShellViewSet(viewsets.ReadOnlyModelViewSet): + """Exposes list and details of `users.models.ListShell` objects. + """ queryset = users.ListShell.objects.all() serializer_class = serializers.ShellSerializer class BanViewSet(viewsets.ReadOnlyModelViewSet): + """Exposes list and details of `users.models.Ban` objects. + """ queryset = users.Ban.objects.all() serializer_class = serializers.BanSerializer class WhitelistViewSet(viewsets.ReadOnlyModelViewSet): + """Exposes list and details of `users.models.Whitelist` objects. + """ queryset = users.Whitelist.objects.all() serializer_class = serializers.WhitelistSerializer -# Services views +# SERVICE REGEN class ServiceRegenViewSet(viewsets.ModelViewSet): + """Exposes list and details of the services to regen + """ serializer_class = serializers.ServiceRegenSerializer def get_queryset(self): @@ -363,24 +468,33 @@ class ServiceRegenViewSet(viewsets.ModelViewSet): return queryset -# DHCP views +# DHCP class HostMacIpView(generics.ListAPIView): + """Exposes the associations between hostname, mac address and IPv4 in + order to build the DHCP lease files. + """ queryset = all_active_interfaces() serializer_class = serializers.HostMacIpSerializer -# DNS views +# DNS class DNSZonesView(generics.ListAPIView): + """Exposes the detailed information about each extension (hostnames, + IPs, DNS records, etc.) in order to build the DNS zone files. + """ queryset = machines.Extension.objects.all() serializer_class = serializers.DNSZonesSerializer -# Mailing views +# MAILING class StandardMailingView(views.APIView): + """Exposes list and details of standard mailings (name and members) in + order to building the corresponding mailing lists. + """ pagination_class = PageSizedPagination permission_classes = (ACLPermission, ) perms_map = {'GET' : [users.User.can_view_all]} @@ -394,13 +508,23 @@ class StandardMailingView(views.APIView): class ClubMailingView(generics.ListAPIView): + """Exposes list and details of club mailings (name, members and admins) in + order to build the corresponding mailing lists. + """ queryset = users.Club.objects.all() serializer_class = serializers.MailingSerializer -# Subclass the standard rest_framework.auth_token.views.ObtainAuthToken -# in order to renew the lease of the token and add expiration time +# TOKEN AUTHENTICATION + + class ObtainExpiringAuthToken(ObtainAuthToken): + """Exposes a view to obtain a authentication token. + + This view as the same behavior as the + `rest_framework.auth_token.views.ObtainAuthToken` view except that the + expiration time is send along with the token as an addtional information. + """ def post(self, request, *args, **kwargs): serializer = self.serializer_class(data=request.data) serializer.is_valid(raise_exception=True) From 4c1af06780abd0ba3c1f41c768cec759f6a40cff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Kervella?= Date: Sun, 17 Jun 2018 01:16:19 +0000 Subject: [PATCH 32/42] Edit changelog --- CHANGELOG.md | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b41b96eb..1ff8ead5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,7 +38,7 @@ mkdir -p media/images ## MR 163: Fix install re2o Refactored install_re2o.sh script. -* There are more tools available with it but some fucntion have changed, report to [the dedicated wiki page](for more informations) or run: +* There are more tools available with it but some function have changed, report to [the dedicated wiki page](https://gitlab.federez.net/federez/re2o/wikis/User%20Documentation/Setup%20script)for more informations or run: ``` install_re2o.sh help ``` @@ -46,10 +46,19 @@ install_re2o.sh help -## MR XXX: Cleanup and refactor API +## MR 172: Refactor API -Activate HTTP Authorization passthrough in by adding the following in /etc/apache2/site-available/re2o.conf (example in install_utils/apache2/re2o.conf): +Creates a new (nearly) REST API to expose all models of Re2o. See [the dedicated wiki page](https://gitlab.federez.net/federez/re2o/wikis/API/Raw-Usage) for more details on how to use it. +* Activate HTTP Authorization passthrough in by adding the following in `/etc/apache2/site-available/re2o.conf` (example in `install_utils/apache2/re2o.conf`): ``` WSGIPassAuthorization On ``` +* Activate the API if you want to use it by adding `'api'` to the optional apps in `re2o/settings_local.py`: +``` +OPTIONAL_APPS = ( + ... + 'api', + ... +) +``` From 3a129a5d6e3e86c7d9a0c5b55dd8257aaafabca8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Kervella?= Date: Sun, 17 Jun 2018 02:00:15 +0000 Subject: [PATCH 33/42] Add missing endpoints in tests --- api/tests.py | 20 ++++++++++++++++++++ api/urls.py | 2 +- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/api/tests.py b/api/tests.py index 24545864..89ab7bdd 100644 --- a/api/tests.py +++ b/api/tests.py @@ -101,6 +101,15 @@ class APIEndpointsTestCase(APITestCase): # '/api/machines/txt//', '/api/machines/vlans/', # '/api/machines/vlans//', + '/api/preferences/optionaluser/', + '/api/preferences/optionalmachine/', + '/api/preferences/optionaltopologie/', + '/api/preferences/generaloption/', + '/api/preferences/service/', +# '/api/preferences/service//', + '/api/preferences/assooption/', + '/api/preferences/homeoption/', + '/api/preferences/mailmessageoption/', '/api/topologie/acesspoint/', # '/api/topologie/acesspoint//', '/api/topologie/building/', @@ -137,6 +146,11 @@ class APIEndpointsTestCase(APITestCase): # '/api/users/users//', '/api/users/whitelists/', # '/api/users/whitelists//', + '/api/dns/zones/', + '/api/dhcp/hostmacip/', + '/api/mailing/standard', + '/api/mailing/club', + '/api/services/regen/', ] stduser = None @@ -342,6 +356,7 @@ class APIPaginationTestCase(APITestCase): '/api/machines/srv/', '/api/machines/txt/', '/api/machines/vlans/', + '/api/preferences/service/', '/api/topologie/acesspoint/', '/api/topologie/building/', '/api/topologie/constructorswitch/', @@ -360,6 +375,11 @@ class APIPaginationTestCase(APITestCase): '/api/users/shells/', '/api/users/users/', '/api/users/whitelists/', + '/api/dns/zones/', + '/api/dhcp/hostmacip/', + '/api/mailing/standard', + '/api/mailing/club', + '/api/services/regen/', ] superuser = None diff --git a/api/urls.py b/api/urls.py index 9353975f..c835429f 100644 --- a/api/urls.py +++ b/api/urls.py @@ -61,11 +61,11 @@ router.register_viewset(r'machines/servicelinks', views.ServiceLinkViewSet, base router.register_viewset(r'machines/ouvertureportlists', views.OuverturePortListViewSet) router.register_viewset(r'machines/ouvertureports', views.OuverturePortViewSet) # PREFERENCES -router.register_viewset(r'preferences/service', views.ServiceViewSet), router.register_view(r'preferences/optionaluser', views.OptionalUserView), router.register_view(r'preferences/optionalmachine', views.OptionalMachineView), router.register_view(r'preferences/optionaltopologie', views.OptionalTopologieView), router.register_view(r'preferences/generaloption', views.GeneralOptionView), +router.register_viewset(r'preferences/service', views.ServiceViewSet), router.register_view(r'preferences/assooption', views.AssoOptionView), router.register_view(r'preferences/homeoption', views.HomeOptionView), router.register_view(r'preferences/mailmessageoption', views.MailMessageOptionView), From 1951ab467d6e2892de0cf5543fc1aabfccdd3287 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Kervella?= Date: Wed, 20 Jun 2018 11:44:43 +0000 Subject: [PATCH 34/42] Remove plural from urls --- api/tests.py | 168 +++++++++++++++++++++++++-------------------------- api/urls.py | 56 ++++++++--------- 2 files changed, 112 insertions(+), 112 deletions(-) diff --git a/api/tests.py b/api/tests.py index 89ab7bdd..5c4122bd 100644 --- a/api/tests.py +++ b/api/tests.py @@ -51,56 +51,56 @@ class APIEndpointsTestCase(APITestCase): ] auth_no_perm_endpoints = [] auth_perm_endpoints = [ - '/api/cotisations/articles/', -# '/api/cotisations/articles//', - '/api/cotisations/banques/', -# '/api/cotisations/banques//', - '/api/cotisations/cotisations/', -# '/api/cotisations/cotisations//', - '/api/cotisations/factures/', -# '/api/cotisations/factures//', - '/api/cotisations/paiements/', -# '/api/cotisations/paiements//', - '/api/cotisations/ventes/', -# '/api/cotisations/ventes//', - '/api/machines/domains/', -# '/api/machines/domains//', - '/api/machines/extensions/', -# '/api/machines/extensions//', - '/api/machines/interfaces/', -# '/api/machines/interfaces//', - '/api/machines/iplists/', -# '/api/machines/iplists//', - '/api/machines/iptypes/', -# '/api/machines/iptypes//', - '/api/machines/ipv6lists/', -# '/api/machines/ipv6lists//', - '/api/machines/machines/', -# '/api/machines/machines//', - '/api/machines/machinetypes/', -# '/api/machines/machinetypes//', + '/api/cotisations/article/', +# '/api/cotisations/article//', + '/api/cotisations/banque/', +# '/api/cotisations/banque//', + '/api/cotisations/cotisation/', +# '/api/cotisations/cotisation//', + '/api/cotisations/facture/', +# '/api/cotisations/facture//', + '/api/cotisations/paiement/', +# '/api/cotisations/paiement//', + '/api/cotisations/vente/', +# '/api/cotisations/vente//', + '/api/machines/domain/', +# '/api/machines/domain//', + '/api/machines/extension/', +# '/api/machines/extension//', + '/api/machines/interface/', +# '/api/machines/interface//', + '/api/machines/iplist/', +# '/api/machines/iplist//', + '/api/machines/iptype/', +# '/api/machines/iptype//', + '/api/machines/ipv6list/', +# '/api/machines/ipv6list//', + '/api/machines/machine/', +# '/api/machines/machine//', + '/api/machines/machinetype/', +# '/api/machines/machinetype//', '/api/machines/mx/', # '/api/machines/mx//', '/api/machines/nas/', # '/api/machines/nas//', '/api/machines/ns/', # '/api/machines/ns//', - '/api/machines/ouvertureportlists/', -# '/api/machines/ouvertureportlists//', - '/api/machines/ouvertureports/', -# '/api/machines/ouvertureports//', - '/api/machines/servicelinks/', -# '/api/machines/servicelinks//', - '/api/machines/services/', -# '/api/machines/services//', + '/api/machines/ouvertureportlist/', +# '/api/machines/ouvertureportlist//', + '/api/machines/ouvertureport/', +# '/api/machines/ouvertureport//', + '/api/machines/servicelink/', +# '/api/machines/servicelink//', + '/api/machines/service/', +# '/api/machines/service//', '/api/machines/soa/', # '/api/machines/soa//', '/api/machines/srv/', # '/api/machines/srv//', '/api/machines/txt/', # '/api/machines/txt//', - '/api/machines/vlans/', -# '/api/machines/vlans//', + '/api/machines/vlan/', +# '/api/machines/vlan//', '/api/preferences/optionaluser/', '/api/preferences/optionalmachine/', '/api/preferences/optionaltopologie/', @@ -128,24 +128,24 @@ class APIEndpointsTestCase(APITestCase): # '/api/topologie/switchbay//', '/api/topologie/switchport/', # '/api/topologie/switchport//', - '/api/users/adherents/', -# '/api/users/adherents//', - '/api/users/bans/', -# '/api/users/bans//', - '/api/users/clubs/', -# '/api/users/clubs//', - '/api/users/listrights/', -# '/api/users/listrights//', - '/api/users/schools/', -# '/api/users/schools//', - '/api/users/serviceusers/', -# '/api/users/serviceusers//', - '/api/users/shells/', -# '/api/users/shells//', - '/api/users/users/', -# '/api/users/users//', - '/api/users/whitelists/', -# '/api/users/whitelists//', + '/api/users/adherent/', +# '/api/users/adherent//', + '/api/users/ban/', +# '/api/users/ban//', + '/api/users/club/', +# '/api/users/club//', + '/api/users/listright/', +# '/api/users/listright//', + '/api/users/school/', +# '/api/users/school//', + '/api/users/serviceuser/', +# '/api/users/serviceuser//', + '/api/users/shell/', +# '/api/users/shell//', + '/api/users/user/', +# '/api/users/user//', + '/api/users/whitelist/', +# '/api/users/whitelist//', '/api/dns/zones/', '/api/dhcp/hostmacip/', '/api/mailing/standard', @@ -331,31 +331,31 @@ class APIPaginationTestCase(APITestCase): """ endpoints = [ - '/api/cotisations/articles/', - '/api/cotisations/banques/', - '/api/cotisations/cotisations/', - '/api/cotisations/factures/', - '/api/cotisations/paiements/', - '/api/cotisations/ventes/', - '/api/machines/domains/', - '/api/machines/extensions/', - '/api/machines/interfaces/', - '/api/machines/iplists/', - '/api/machines/iptypes/', - '/api/machines/ipv6lists/', - '/api/machines/machines/', - '/api/machines/machinetypes/', + '/api/cotisations/article/', + '/api/cotisations/banque/', + '/api/cotisations/cotisation/', + '/api/cotisations/facture/', + '/api/cotisations/paiement/', + '/api/cotisations/vente/', + '/api/machines/domain/', + '/api/machines/extension/', + '/api/machines/interface/', + '/api/machines/iplist/', + '/api/machines/iptype/', + '/api/machines/ipv6list/', + '/api/machines/machine/', + '/api/machines/machinetype/', '/api/machines/mx/', '/api/machines/nas/', '/api/machines/ns/', - '/api/machines/ouvertureportlists/', - '/api/machines/ouvertureports/', - '/api/machines/servicelinks/', - '/api/machines/services/', + '/api/machines/ouvertureportlist/', + '/api/machines/ouvertureport/', + '/api/machines/servicelink/', + '/api/machines/service/', '/api/machines/soa/', '/api/machines/srv/', '/api/machines/txt/', - '/api/machines/vlans/', + '/api/machines/vlan/', '/api/preferences/service/', '/api/topologie/acesspoint/', '/api/topologie/building/', @@ -366,15 +366,15 @@ class APIPaginationTestCase(APITestCase): '/api/topologie/switch/', '/api/topologie/switchbay/', '/api/topologie/switchport/', - '/api/users/adherents/', - '/api/users/bans/', - '/api/users/clubs/', - '/api/users/listrights/', - '/api/users/schools/', - '/api/users/serviceusers/', - '/api/users/shells/', - '/api/users/users/', - '/api/users/whitelists/', + '/api/users/adherent/', + '/api/users/ban/', + '/api/users/club/', + '/api/users/listright/', + '/api/users/school/', + '/api/users/serviceuser/', + '/api/users/shell/', + '/api/users/user/', + '/api/users/whitelist/', '/api/dns/zones/', '/api/dhcp/hostmacip/', '/api/mailing/standard', diff --git a/api/urls.py b/api/urls.py index c835429f..9d6b5f5e 100644 --- a/api/urls.py +++ b/api/urls.py @@ -34,32 +34,32 @@ from .routers import AllViewsRouter router = AllViewsRouter() # COTISATIONS -router.register_viewset(r'cotisations/factures', views.FactureViewSet) -router.register_viewset(r'cotisations/ventes', views.VenteViewSet) -router.register_viewset(r'cotisations/articles', views.ArticleViewSet) -router.register_viewset(r'cotisations/banques', views.BanqueViewSet) -router.register_viewset(r'cotisations/paiements', views.PaiementViewSet) -router.register_viewset(r'cotisations/cotisations', views.CotisationViewSet) +router.register_viewset(r'cotisations/facture', views.FactureViewSet) +router.register_viewset(r'cotisations/vente', views.VenteViewSet) +router.register_viewset(r'cotisations/article', views.ArticleViewSet) +router.register_viewset(r'cotisations/banque', views.BanqueViewSet) +router.register_viewset(r'cotisations/paiement', views.PaiementViewSet) +router.register_viewset(r'cotisations/cotisation', views.CotisationViewSet) # MACHINES -router.register_viewset(r'machines/machines', views.MachineViewSet) -router.register_viewset(r'machines/machinetypes', views.MachineTypeViewSet) -router.register_viewset(r'machines/iptypes', views.IpTypeViewSet) -router.register_viewset(r'machines/vlans', views.VlanViewSet) +router.register_viewset(r'machines/machine', views.MachineViewSet) +router.register_viewset(r'machines/machinetype', views.MachineTypeViewSet) +router.register_viewset(r'machines/iptype', views.IpTypeViewSet) +router.register_viewset(r'machines/vlan', views.VlanViewSet) router.register_viewset(r'machines/nas', views.NasViewSet) router.register_viewset(r'machines/soa', views.SOAViewSet) -router.register_viewset(r'machines/extensions', views.ExtensionViewSet) +router.register_viewset(r'machines/extension', views.ExtensionViewSet) router.register_viewset(r'machines/mx', views.MxViewSet) router.register_viewset(r'machines/ns', views.NsViewSet) router.register_viewset(r'machines/txt', views.TxtViewSet) router.register_viewset(r'machines/srv', views.SrvViewSet) -router.register_viewset(r'machines/interfaces', views.InterfaceViewSet) -router.register_viewset(r'machines/ipv6lists', views.Ipv6ListViewSet) -router.register_viewset(r'machines/domains', views.DomainViewSet) -router.register_viewset(r'machines/iplists', views.IpListViewSet) -router.register_viewset(r'machines/services', views.ServiceViewSet) -router.register_viewset(r'machines/servicelinks', views.ServiceLinkViewSet, base_name='servicelink') -router.register_viewset(r'machines/ouvertureportlists', views.OuverturePortListViewSet) -router.register_viewset(r'machines/ouvertureports', views.OuverturePortViewSet) +router.register_viewset(r'machines/interface', views.InterfaceViewSet) +router.register_viewset(r'machines/ipv6list', views.Ipv6ListViewSet) +router.register_viewset(r'machines/domain', views.DomainViewSet) +router.register_viewset(r'machines/iplist', views.IpListViewSet) +router.register_viewset(r'machines/service', views.ServiceViewSet) +router.register_viewset(r'machines/servicelink', views.ServiceLinkViewSet, base_name='servicelink') +router.register_viewset(r'machines/ouvertureportlist', views.OuverturePortListViewSet) +router.register_viewset(r'machines/ouvertureport', views.OuverturePortViewSet) # PREFERENCES router.register_view(r'preferences/optionaluser', views.OptionalUserView), router.register_view(r'preferences/optionalmachine', views.OptionalMachineView), @@ -80,15 +80,15 @@ router.register_viewset(r'topologie/building', views.BuildingViewSet) router.register_viewset(r'topologie/switchport', views.SwitchPortViewSet, base_name='switchport') router.register_viewset(r'topologie/room', views.RoomViewSet) # USERS -router.register_viewset(r'users/users', views.UserViewSet) -router.register_viewset(r'users/clubs', views.ClubViewSet) -router.register_viewset(r'users/adherents', views.AdherentViewSet) -router.register_viewset(r'users/serviceusers', views.ServiceUserViewSet) -router.register_viewset(r'users/schools', views.SchoolViewSet) -router.register_viewset(r'users/listrights', views.ListRightViewSet) -router.register_viewset(r'users/shells', views.ShellViewSet, base_name='shell') -router.register_viewset(r'users/bans', views.BanViewSet) -router.register_viewset(r'users/whitelists', views.WhitelistViewSet) +router.register_viewset(r'users/user', views.UserViewSet) +router.register_viewset(r'users/club', views.ClubViewSet) +router.register_viewset(r'users/adherent', views.AdherentViewSet) +router.register_viewset(r'users/serviceuser', views.ServiceUserViewSet) +router.register_viewset(r'users/school', views.SchoolViewSet) +router.register_viewset(r'users/listright', views.ListRightViewSet) +router.register_viewset(r'users/shell', views.ShellViewSet, base_name='shell') +router.register_viewset(r'users/ban', views.BanViewSet) +router.register_viewset(r'users/whitelist', views.WhitelistViewSet) # SERVICE REGEN router.register_viewset(r'services/regen', views.ServiceRegenViewSet, base_name='serviceregen') # DHCP From 6d5a9dc3140e675dd8659cc47a8f397ccd56e540 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Kervella?= Date: Wed, 20 Jun 2018 12:13:23 +0000 Subject: [PATCH 35/42] Add missing topologie/server endpoint --- api/serializers.py | 8 ++++++++ api/tests.py | 3 +++ api/urls.py | 1 + api/views.py | 7 +++++++ 4 files changed, 19 insertions(+) diff --git a/api/serializers.py b/api/serializers.py index a1e73091..26abc1c3 100644 --- a/api/serializers.py +++ b/api/serializers.py @@ -400,6 +400,14 @@ class SwitchSerializer(NamespacedHMSerializer): 'stack_member_id', 'model', 'switchbay', 'api_url') +class ServerSerializer(NamespacedHMSerializer): + """Serialize `topologie.models.Server` objects + """ + class Meta: + model = topologie.Server + fields = ('user', 'name', 'active', 'api_url') + + class ModelSwitchSerializer(NamespacedHMSerializer): """Serialize `topologie.models.ModelSwitch` objects """ diff --git a/api/tests.py b/api/tests.py index 5c4122bd..3120676a 100644 --- a/api/tests.py +++ b/api/tests.py @@ -120,6 +120,8 @@ class APIEndpointsTestCase(APITestCase): # '/api/topologie/modelswitch//', '/api/topologie/room/', # '/api/topologie/room//', + '/api/topologie/server/', +# '/api/topologie/server//', '/api/topologie/stack/', # '/api/topologie/stack//', '/api/topologie/switch/', @@ -362,6 +364,7 @@ class APIPaginationTestCase(APITestCase): '/api/topologie/constructorswitch/', '/api/topologie/modelswitch/', '/api/topologie/room/', + '/api/topologie/server/', '/api/topologie/stack/', '/api/topologie/switch/', '/api/topologie/switchbay/', diff --git a/api/urls.py b/api/urls.py index 9d6b5f5e..a04d6fbe 100644 --- a/api/urls.py +++ b/api/urls.py @@ -73,6 +73,7 @@ router.register_view(r'preferences/mailmessageoption', views.MailMessageOptionVi router.register_viewset(r'topologie/stack', views.StackViewSet) router.register_viewset(r'topologie/acesspoint', views.AccessPointViewSet) router.register_viewset(r'topologie/switch', views.SwitchViewSet) +router.register_viewset(r'topologie/server', views.ServerViewSet) router.register_viewset(r'topologie/modelswitch', views.ModelSwitchViewSet) router.register_viewset(r'topologie/constructorswitch', views.ConstructorSwitchViewSet) router.register_viewset(r'topologie/switchbay', views.SwitchBayViewSet) diff --git a/api/views.py b/api/views.py index 9ee6ea75..7532aece 100644 --- a/api/views.py +++ b/api/views.py @@ -339,6 +339,13 @@ class SwitchViewSet(viewsets.ReadOnlyModelViewSet): serializer_class = serializers.SwitchSerializer +class ServerViewSet(viewsets.ReadOnlyModelViewSet): + """Exposes list and details of `topologie.models.Server` objects. + """ + queryset = topologie.Server.objects.all() + serializer_class = serializers.ServerSerializer + + class ModelSwitchViewSet(viewsets.ReadOnlyModelViewSet): """Exposes list and details of `topologie.models.ModelSwitch` objects. """ From f41fcc843f81cf44814f815f601f5a7b5e4449fb Mon Sep 17 00:00:00 2001 From: Gabriel Detraz Date: Tue, 19 Jun 2018 15:41:15 +0200 Subject: [PATCH 36/42] Add test LDAP --- test_utils/ldap/schema/radius.schema | 564 +++++++++++++++++++++++ test_utils/ldap/schema/samba.schema | 644 +++++++++++++++++++++++++++ users/tests.py | 119 ++++- 3 files changed, 1325 insertions(+), 2 deletions(-) create mode 100644 test_utils/ldap/schema/radius.schema create mode 100644 test_utils/ldap/schema/samba.schema diff --git a/test_utils/ldap/schema/radius.schema b/test_utils/ldap/schema/radius.schema new file mode 100644 index 00000000..cee7502a --- /dev/null +++ b/test_utils/ldap/schema/radius.schema @@ -0,0 +1,564 @@ +# This is a LDAPv3 schema for RADIUS attributes. +# Tested on OpenLDAP 2.0.7 +# Posted by Javier Fernandez-Sanguino Pena +# LDAP v3 version by Jochen Friedrich +# Updates by Adrian Pavlykevych +############## + +attributetype + ( 1.3.6.1.4.1.3317.4.3.1.1 + NAME 'radiusArapFeatures' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + SINGLE-VALUE + ) + +attributetype + ( 1.3.6.1.4.1.3317.4.3.1.2 + NAME 'radiusArapSecurity' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + SINGLE-VALUE + ) + +attributetype + ( 1.3.6.1.4.1.3317.4.3.1.3 + NAME 'radiusArapZoneAccess' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + SINGLE-VALUE + ) + +attributetype + ( 1.3.6.1.4.1.3317.4.3.1.44 + NAME 'radiusAuthType' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + SINGLE-VALUE + ) + +attributetype + ( 1.3.6.1.4.1.3317.4.3.1.4 + NAME 'radiusCallbackId' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + SINGLE-VALUE + ) + +attributetype + ( 1.3.6.1.4.1.3317.4.3.1.5 + NAME 'radiusCallbackNumber' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + SINGLE-VALUE + ) + +attributetype + ( 1.3.6.1.4.1.3317.4.3.1.6 + NAME 'radiusCalledStationId' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + SINGLE-VALUE + ) + +attributetype + ( 1.3.6.1.4.1.3317.4.3.1.7 + NAME 'radiusCallingStationId' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + SINGLE-VALUE + ) + +attributetype + ( 1.3.6.1.4.1.3317.4.3.1.8 + NAME 'radiusClass' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + ) + +attributetype + ( 1.3.6.1.4.1.3317.4.3.1.45 + NAME 'radiusClientIPAddress' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + SINGLE-VALUE + ) + +attributetype + ( 1.3.6.1.4.1.3317.4.3.1.9 + NAME 'radiusFilterId' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + SINGLE-VALUE + ) + +attributetype + ( 1.3.6.1.4.1.3317.4.3.1.10 + NAME 'radiusFramedAppleTalkLink' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + SINGLE-VALUE + ) + +attributetype + ( 1.3.6.1.4.1.3317.4.3.1.11 + NAME 'radiusFramedAppleTalkNetwork' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + SINGLE-VALUE + ) + +attributetype + ( 1.3.6.1.4.1.3317.4.3.1.12 + NAME 'radiusFramedAppleTalkZone' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + SINGLE-VALUE + ) + +attributetype + ( 1.3.6.1.4.1.3317.4.3.1.13 + NAME 'radiusFramedCompression' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + SINGLE-VALUE + ) + +attributetype + ( 1.3.6.1.4.1.3317.4.3.1.14 + NAME 'radiusFramedIPAddress' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + SINGLE-VALUE + ) + +attributetype + ( 1.3.6.1.4.1.3317.4.3.1.15 + NAME 'radiusFramedIPNetmask' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + SINGLE-VALUE + ) + +attributetype + ( 1.3.6.1.4.1.3317.4.3.1.16 + NAME 'radiusFramedIPXNetwork' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + SINGLE-VALUE + ) + +attributetype + ( 1.3.6.1.4.1.3317.4.3.1.17 + NAME 'radiusFramedMTU' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + SINGLE-VALUE + ) + +attributetype + ( 1.3.6.1.4.1.3317.4.3.1.18 + NAME 'radiusFramedProtocol' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + SINGLE-VALUE + ) + +attributetype + ( 1.3.6.1.4.1.3317.4.3.1.19 + NAME 'radiusFramedRoute' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + ) + +attributetype + ( 1.3.6.1.4.1.3317.4.3.1.20 + NAME 'radiusFramedRouting' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + SINGLE-VALUE + ) + +attributetype + ( 1.3.6.1.4.1.3317.4.3.1.46 + NAME 'radiusGroupName' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + ) + +attributetype + ( 1.3.6.1.4.1.3317.4.3.1.47 + NAME 'radiusHint' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + SINGLE-VALUE + ) + +attributetype + ( 1.3.6.1.4.1.3317.4.3.1.48 + NAME 'radiusHuntgroupName' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + ) + +attributetype + ( 1.3.6.1.4.1.3317.4.3.1.21 + NAME 'radiusIdleTimeout' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + SINGLE-VALUE + ) + +attributetype + ( 1.3.6.1.4.1.3317.4.3.1.22 + NAME 'radiusLoginIPHost' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + SINGLE-VALUE + ) + +attributetype + ( 1.3.6.1.4.1.3317.4.3.1.23 + NAME 'radiusLoginLATGroup' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + SINGLE-VALUE + ) + +attributetype + ( 1.3.6.1.4.1.3317.4.3.1.24 + NAME 'radiusLoginLATNode' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + SINGLE-VALUE + ) + +attributetype + ( 1.3.6.1.4.1.3317.4.3.1.25 + NAME 'radiusLoginLATPort' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + SINGLE-VALUE + ) + +attributetype + ( 1.3.6.1.4.1.3317.4.3.1.26 + NAME 'radiusLoginLATService' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + SINGLE-VALUE + ) + +attributetype + ( 1.3.6.1.4.1.3317.4.3.1.27 + NAME 'radiusLoginService' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + SINGLE-VALUE + ) + +attributetype + ( 1.3.6.1.4.1.3317.4.3.1.28 + NAME 'radiusLoginTCPPort' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + SINGLE-VALUE + ) + +attributetype + ( 1.3.6.1.4.1.3317.4.3.1.29 + NAME 'radiusPasswordRetry' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + SINGLE-VALUE + ) + +attributetype + ( 1.3.6.1.4.1.3317.4.3.1.30 + NAME 'radiusPortLimit' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + SINGLE-VALUE + ) + +attributetype + ( 1.3.6.1.4.1.3317.4.3.1.49 + NAME 'radiusProfileDn' + DESC '' + EQUALITY distinguishedNameMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 + SINGLE-VALUE + ) + +attributetype + ( 1.3.6.1.4.1.3317.4.3.1.31 + NAME 'radiusPrompt' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + SINGLE-VALUE + ) + +attributetype + ( 1.3.6.1.4.1.3317.4.3.1.50 + NAME 'radiusProxyToRealm' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + SINGLE-VALUE + ) + +attributetype + ( 1.3.6.1.4.1.3317.4.3.1.51 + NAME 'radiusReplicateToRealm' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + SINGLE-VALUE + ) + +attributetype + ( 1.3.6.1.4.1.3317.4.3.1.52 + NAME 'radiusRealm' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + SINGLE-VALUE + ) + +attributetype + ( 1.3.6.1.4.1.3317.4.3.1.32 + NAME 'radiusServiceType' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + SINGLE-VALUE + ) + +attributetype + ( 1.3.6.1.4.1.3317.4.3.1.33 + NAME 'radiusSessionTimeout' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + SINGLE-VALUE + ) + +attributetype + ( 1.3.6.1.4.1.3317.4.3.1.34 + NAME 'radiusTerminationAction' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + SINGLE-VALUE + ) + +attributetype + ( 1.3.6.1.4.1.3317.4.3.1.35 + NAME 'radiusTunnelAssignmentId' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + ) + +attributetype + ( 1.3.6.1.4.1.3317.4.3.1.36 + NAME 'radiusTunnelMediumType' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + ) + +attributetype + ( 1.3.6.1.4.1.3317.4.3.1.37 + NAME 'radiusTunnelPassword' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + SINGLE-VALUE + ) + +attributetype + ( 1.3.6.1.4.1.3317.4.3.1.38 + NAME 'radiusTunnelPreference' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + ) + +attributetype + ( 1.3.6.1.4.1.3317.4.3.1.39 + NAME 'radiusTunnelPrivateGroupId' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + ) + +attributetype + ( 1.3.6.1.4.1.3317.4.3.1.40 + NAME 'radiusTunnelServerEndpoint' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + ) + +attributetype + ( 1.3.6.1.4.1.3317.4.3.1.41 + NAME 'radiusTunnelType' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + ) + +attributetype + ( 1.3.6.1.4.1.3317.4.3.1.42 + NAME 'radiusVSA' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + ) + +attributetype + ( 1.3.6.1.4.1.3317.4.3.1.43 + NAME 'radiusTunnelClientEndpoint' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + ) + + +#need to change asn1.id +attributetype + ( 1.3.6.1.4.1.3317.4.3.1.53 + NAME 'radiusSimultaneousUse' + DESC '' + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 + SINGLE-VALUE + ) + +attributetype + ( 1.3.6.1.4.1.3317.4.3.1.54 + NAME 'radiusLoginTime' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + SINGLE-VALUE + ) + +attributetype + ( 1.3.6.1.4.1.3317.4.3.1.55 + NAME 'radiusUserCategory' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + SINGLE-VALUE + ) + +attributetype + ( 1.3.6.1.4.1.3317.4.3.1.56 + NAME 'radiusStripUserName' + DESC '' + SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 + SINGLE-VALUE + ) + +attributetype + ( 1.3.6.1.4.1.3317.4.3.1.57 + NAME 'dialupAccess' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + SINGLE-VALUE + ) + +attributetype + ( 1.3.6.1.4.1.3317.4.3.1.58 + NAME 'radiusExpiration' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + SINGLE-VALUE + ) + +attributetype + ( 1.3.6.1.4.1.3317.4.3.1.59 + NAME 'radiusCheckItem' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + ) + +attributetype + ( 1.3.6.1.4.1.3317.4.3.1.60 + NAME 'radiusReplyItem' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + ) + + +objectclass + ( 1.3.6.1.4.1.3317.4.3.2.1 + NAME 'radiusprofile' + SUP top AUXILIARY + DESC '' + MUST cn + MAY ( radiusArapFeatures $ radiusArapSecurity $ radiusArapZoneAccess $ + radiusAuthType $ radiusCallbackId $ radiusCallbackNumber $ + radiusCalledStationId $ radiusCallingStationId $ radiusClass $ + radiusClientIPAddress $ radiusFilterId $ radiusFramedAppleTalkLink $ + radiusFramedAppleTalkNetwork $ radiusFramedAppleTalkZone $ + radiusFramedCompression $ radiusFramedIPAddress $ + radiusFramedIPNetmask $ radiusFramedIPXNetwork $ + radiusFramedMTU $ radiusFramedProtocol $ + radiusCheckItem $ radiusReplyItem $ + radiusFramedRoute $ radiusFramedRouting $ radiusIdleTimeout $ + radiusGroupName $ radiusHint $ radiusHuntgroupName $ + radiusLoginIPHost $ radiusLoginLATGroup $ radiusLoginLATNode $ + radiusLoginLATPort $ radiusLoginLATService $ radiusLoginService $ + radiusLoginTCPPort $ radiusLoginTime $ radiusPasswordRetry $ + radiusPortLimit $ radiusPrompt $ radiusProxyToRealm $ + radiusRealm $ radiusReplicateToRealm $ radiusServiceType $ + radiusSessionTimeout $ radiusStripUserName $ + radiusTerminationAction $ radiusTunnelClientEndpoint $ radiusProfileDn $ + radiusSimultaneousUse $ radiusTunnelAssignmentId $ + radiusTunnelMediumType $ radiusTunnelPassword $ radiusTunnelPreference $ + radiusTunnelPrivateGroupId $ radiusTunnelServerEndpoint $ + radiusTunnelType $ radiusUserCategory $ radiusVSA $ + radiusExpiration $ dialupAccess ) + ) diff --git a/test_utils/ldap/schema/samba.schema b/test_utils/ldap/schema/samba.schema new file mode 100644 index 00000000..08173119 --- /dev/null +++ b/test_utils/ldap/schema/samba.schema @@ -0,0 +1,644 @@ +## +## schema file for OpenLDAP 2.x +## Schema for storing Samba user accounts and group maps in LDAP +## OIDs are owned by the Samba Team +## +## Prerequisite schemas - uid (cosine.schema) +## - displayName (inetorgperson.schema) +## - gidNumber (nis.schema) +## +## 1.3.6.1.4.1.7165.2.1.x - attributetypes +## 1.3.6.1.4.1.7165.2.2.x - objectclasses +## +## Printer support +## 1.3.6.1.4.1.7165.2.3.1.x - attributetypes +## 1.3.6.1.4.1.7165.2.3.2.x - objectclasses +## +## Samba4 +## 1.3.6.1.4.1.7165.4.1.x - attributetypes +## 1.3.6.1.4.1.7165.4.2.x - objectclasses +## 1.3.6.1.4.1.7165.4.3.x - LDB/LDAP Controls +## 1.3.6.1.4.1.7165.4.4.x - LDB/LDAP Extended Operations +## 1.3.6.1.4.1.7165.4.255.x - mapped OIDs due to conflicts between AD and standards-track +## +## External projects +## 1.3.6.1.4.1.7165.655.x +## 1.3.6.1.4.1.7165.655.1.x - GSS-NTLMSSP +## +## ----- READ THIS WHEN ADDING A NEW ATTRIBUTE OR OBJECT CLASS ------ +## +## Run the 'get_next_oid' bash script in this directory to find the +## next available OID for attribute type and object classes. +## +## $ ./get_next_oid +## attributetype ( 1.3.6.1.4.1.7165.2.1.XX NAME .... +## objectclass ( 1.3.6.1.4.1.7165.2.2.XX NAME .... +## +## Also ensure that new entries adhere to the declaration style +## used throughout this file +## +## ( 1.3.6.1.4.1.7165.2.XX.XX NAME .... +## ^ ^ ^ +## +## The spaces are required for the get_next_oid script (and for +## readability). +## +## ------------------------------------------------------------------ + +# objectIdentifier SambaRoot 1.3.6.1.4.1.7165 +# objectIdentifier Samba3 SambaRoot:2 +# objectIdentifier Samba3Attrib Samba3:1 +# objectIdentifier Samba3ObjectClass Samba3:2 +# objectIdentifier Samba4 SambaRoot:4 + +######################################################################## +## HISTORICAL ## +######################################################################## + +## +## Password hashes +## +#attributetype ( 1.3.6.1.4.1.7165.2.1.1 NAME 'lmPassword' +# DESC 'LanManager Passwd' +# EQUALITY caseIgnoreIA5Match +# SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{32} SINGLE-VALUE ) + +#attributetype ( 1.3.6.1.4.1.7165.2.1.2 NAME 'ntPassword' +# DESC 'NT Passwd' +# EQUALITY caseIgnoreIA5Match +# SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{32} SINGLE-VALUE ) + +## +## Account flags in string format ([UWDX ]) +## +#attributetype ( 1.3.6.1.4.1.7165.2.1.4 NAME 'acctFlags' +# DESC 'Account Flags' +# EQUALITY caseIgnoreIA5Match +# SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{16} SINGLE-VALUE ) + +## +## Password timestamps & policies +## +#attributetype ( 1.3.6.1.4.1.7165.2.1.3 NAME 'pwdLastSet' +# DESC 'NT pwdLastSet' +# EQUALITY integerMatch +# SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) + +#attributetype ( 1.3.6.1.4.1.7165.2.1.5 NAME 'logonTime' +# DESC 'NT logonTime' +# EQUALITY integerMatch +# SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) + +#attributetype ( 1.3.6.1.4.1.7165.2.1.6 NAME 'logoffTime' +# DESC 'NT logoffTime' +# EQUALITY integerMatch +# SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) + +#attributetype ( 1.3.6.1.4.1.7165.2.1.7 NAME 'kickoffTime' +# DESC 'NT kickoffTime' +# EQUALITY integerMatch +# SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) + +#attributetype ( 1.3.6.1.4.1.7165.2.1.8 NAME 'pwdCanChange' +# DESC 'NT pwdCanChange' +# EQUALITY integerMatch +# SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) + +#attributetype ( 1.3.6.1.4.1.7165.2.1.9 NAME 'pwdMustChange' +# DESC 'NT pwdMustChange' +# EQUALITY integerMatch +# SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) + +## +## string settings +## +#attributetype ( 1.3.6.1.4.1.7165.2.1.10 NAME 'homeDrive' +# DESC 'NT homeDrive' +# EQUALITY caseIgnoreIA5Match +# SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{4} SINGLE-VALUE ) + +#attributetype ( 1.3.6.1.4.1.7165.2.1.11 NAME 'scriptPath' +# DESC 'NT scriptPath' +# EQUALITY caseIgnoreIA5Match +# SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{255} SINGLE-VALUE ) + +#attributetype ( 1.3.6.1.4.1.7165.2.1.12 NAME 'profilePath' +# DESC 'NT profilePath' +# EQUALITY caseIgnoreIA5Match +# SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{255} SINGLE-VALUE ) + +#attributetype ( 1.3.6.1.4.1.7165.2.1.13 NAME 'userWorkstations' +# DESC 'userWorkstations' +# EQUALITY caseIgnoreIA5Match +# SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{255} SINGLE-VALUE ) + +#attributetype ( 1.3.6.1.4.1.7165.2.1.17 NAME 'smbHome' +# DESC 'smbHome' +# EQUALITY caseIgnoreIA5Match +# SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{128} ) + +#attributetype ( 1.3.6.1.4.1.7165.2.1.18 NAME 'domain' +# DESC 'Windows NT domain to which the user belongs' +# EQUALITY caseIgnoreIA5Match +# SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{128} ) + +## +## user and group RID +## +#attributetype ( 1.3.6.1.4.1.7165.2.1.14 NAME 'rid' +# DESC 'NT rid' +# EQUALITY integerMatch +# SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) + +#attributetype ( 1.3.6.1.4.1.7165.2.1.15 NAME 'primaryGroupID' +# DESC 'NT Group RID' +# EQUALITY integerMatch +# SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) + +## +## The smbPasswordEntry objectclass has been depreciated in favor of the +## sambaAccount objectclass +## +#objectclass ( 1.3.6.1.4.1.7165.2.2.1 NAME 'smbPasswordEntry' SUP top AUXILIARY +# DESC 'Samba smbpasswd entry' +# MUST ( uid $ uidNumber ) +# MAY ( lmPassword $ ntPassword $ pwdLastSet $ acctFlags )) + +#objectclass ( 1.3.6.1.4.1.7165.2.2.2 NAME 'sambaAccount' SUP top STRUCTURAL +# DESC 'Samba Account' +# MUST ( uid $ rid ) +# MAY ( cn $ lmPassword $ ntPassword $ pwdLastSet $ logonTime $ +# logoffTime $ kickoffTime $ pwdCanChange $ pwdMustChange $ acctFlags $ +# displayName $ smbHome $ homeDrive $ scriptPath $ profilePath $ +# description $ userWorkstations $ primaryGroupID $ domain )) + +#objectclass ( 1.3.6.1.4.1.7165.2.2.3 NAME 'sambaAccount' SUP top AUXILIARY +# DESC 'Samba Auxiliary Account' +# MUST ( uid $ rid ) +# MAY ( cn $ lmPassword $ ntPassword $ pwdLastSet $ logonTime $ +# logoffTime $ kickoffTime $ pwdCanChange $ pwdMustChange $ acctFlags $ +# displayName $ smbHome $ homeDrive $ scriptPath $ profilePath $ +# description $ userWorkstations $ primaryGroupID $ domain )) + +######################################################################## +## END OF HISTORICAL ## +######################################################################## + +####################################################################### +## Attributes used by Samba 3.0 schema ## +####################################################################### + +## +## Password hashes +## +attributetype ( 1.3.6.1.4.1.7165.2.1.24 NAME 'sambaLMPassword' + DESC 'LanManager Password' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{32} SINGLE-VALUE ) + +attributetype ( 1.3.6.1.4.1.7165.2.1.25 NAME 'sambaNTPassword' + DESC 'MD4 hash of the unicode password' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{32} SINGLE-VALUE ) + +## +## Account flags in string format ([UWDX ]) +## +attributetype ( 1.3.6.1.4.1.7165.2.1.26 NAME 'sambaAcctFlags' + DESC 'Account Flags' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{16} SINGLE-VALUE ) + +## +## Password timestamps & policies +## +attributetype ( 1.3.6.1.4.1.7165.2.1.27 NAME 'sambaPwdLastSet' + DESC 'Timestamp of the last password update' + EQUALITY integerMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) + +attributetype ( 1.3.6.1.4.1.7165.2.1.28 NAME 'sambaPwdCanChange' + DESC 'Timestamp of when the user is allowed to update the password' + EQUALITY integerMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) + +attributetype ( 1.3.6.1.4.1.7165.2.1.29 NAME 'sambaPwdMustChange' + DESC 'Timestamp of when the password will expire' + EQUALITY integerMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) + +attributetype ( 1.3.6.1.4.1.7165.2.1.30 NAME 'sambaLogonTime' + DESC 'Timestamp of last logon' + EQUALITY integerMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) + +attributetype ( 1.3.6.1.4.1.7165.2.1.31 NAME 'sambaLogoffTime' + DESC 'Timestamp of last logoff' + EQUALITY integerMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) + +attributetype ( 1.3.6.1.4.1.7165.2.1.32 NAME 'sambaKickoffTime' + DESC 'Timestamp of when the user will be logged off automatically' + EQUALITY integerMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) + +attributetype ( 1.3.6.1.4.1.7165.2.1.48 NAME 'sambaBadPasswordCount' + DESC 'Bad password attempt count' + EQUALITY integerMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) + +attributetype ( 1.3.6.1.4.1.7165.2.1.49 NAME 'sambaBadPasswordTime' + DESC 'Time of the last bad password attempt' + EQUALITY integerMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) + +attributetype ( 1.3.6.1.4.1.7165.2.1.55 NAME 'sambaLogonHours' + DESC 'Logon Hours' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{42} SINGLE-VALUE ) + +## +## string settings +## +attributetype ( 1.3.6.1.4.1.7165.2.1.33 NAME 'sambaHomeDrive' + DESC 'Driver letter of home directory mapping' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{4} SINGLE-VALUE ) + +attributetype ( 1.3.6.1.4.1.7165.2.1.34 NAME 'sambaLogonScript' + DESC 'Logon script path' + EQUALITY caseIgnoreMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{255} SINGLE-VALUE ) + +attributetype ( 1.3.6.1.4.1.7165.2.1.35 NAME 'sambaProfilePath' + DESC 'Roaming profile path' + EQUALITY caseIgnoreMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{255} SINGLE-VALUE ) + +attributetype ( 1.3.6.1.4.1.7165.2.1.36 NAME 'sambaUserWorkstations' + DESC 'List of user workstations the user is allowed to logon to' + EQUALITY caseIgnoreMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{255} SINGLE-VALUE ) + +attributetype ( 1.3.6.1.4.1.7165.2.1.37 NAME 'sambaHomePath' + DESC 'Home directory UNC path' + EQUALITY caseIgnoreMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{128} ) + +attributetype ( 1.3.6.1.4.1.7165.2.1.38 NAME 'sambaDomainName' + DESC 'Windows NT domain to which the user belongs' + EQUALITY caseIgnoreMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{128} ) + +attributetype ( 1.3.6.1.4.1.7165.2.1.47 NAME 'sambaMungedDial' + DESC 'Base64 encoded user parameter string' + EQUALITY caseExactMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{1050} ) + +attributetype ( 1.3.6.1.4.1.7165.2.1.54 NAME 'sambaPasswordHistory' + DESC 'Concatenated MD5 hashes of the salted NT passwords used on this account' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{32} ) + +## +## SID, of any type +## + +attributetype ( 1.3.6.1.4.1.7165.2.1.20 NAME 'sambaSID' + DESC 'Security ID' + EQUALITY caseIgnoreIA5Match + SUBSTR caseExactIA5SubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{64} SINGLE-VALUE ) + +## +## Primary group SID, compatible with ntSid +## + +attributetype ( 1.3.6.1.4.1.7165.2.1.23 NAME 'sambaPrimaryGroupSID' + DESC 'Primary Group Security ID' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{64} SINGLE-VALUE ) + +attributetype ( 1.3.6.1.4.1.7165.2.1.51 NAME 'sambaSIDList' + DESC 'Security ID List' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{64} ) + +## +## group mapping attributes +## +attributetype ( 1.3.6.1.4.1.7165.2.1.19 NAME 'sambaGroupType' + DESC 'NT Group Type' + EQUALITY integerMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) + +## +## Store info on the domain +## + +attributetype ( 1.3.6.1.4.1.7165.2.1.21 NAME 'sambaNextUserRid' + DESC 'Next NT rid to give our for users' + EQUALITY integerMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) + +attributetype ( 1.3.6.1.4.1.7165.2.1.22 NAME 'sambaNextGroupRid' + DESC 'Next NT rid to give out for groups' + EQUALITY integerMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) + +attributetype ( 1.3.6.1.4.1.7165.2.1.39 NAME 'sambaNextRid' + DESC 'Next NT rid to give out for anything' + EQUALITY integerMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) + +attributetype ( 1.3.6.1.4.1.7165.2.1.40 NAME 'sambaAlgorithmicRidBase' + DESC 'Base at which the samba RID generation algorithm should operate' + EQUALITY integerMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) + +attributetype ( 1.3.6.1.4.1.7165.2.1.41 NAME 'sambaShareName' + DESC 'Share Name' + EQUALITY caseIgnoreMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE ) + +attributetype ( 1.3.6.1.4.1.7165.2.1.42 NAME 'sambaOptionName' + DESC 'Option Name' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} ) + +attributetype ( 1.3.6.1.4.1.7165.2.1.43 NAME 'sambaBoolOption' + DESC 'A boolean option' + EQUALITY booleanMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE ) + +attributetype ( 1.3.6.1.4.1.7165.2.1.44 NAME 'sambaIntegerOption' + DESC 'An integer option' + EQUALITY integerMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) + +attributetype ( 1.3.6.1.4.1.7165.2.1.45 NAME 'sambaStringOption' + DESC 'A string option' + EQUALITY caseExactIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE ) + +attributetype ( 1.3.6.1.4.1.7165.2.1.46 NAME 'sambaStringListOption' + DESC 'A string list option' + EQUALITY caseIgnoreMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 ) + + +##attributetype ( 1.3.6.1.4.1.7165.2.1.50 NAME 'sambaPrivName' +## SUP name ) + +##attributetype ( 1.3.6.1.4.1.7165.2.1.52 NAME 'sambaPrivilegeList' +## DESC 'Privileges List' +## EQUALITY caseIgnoreIA5Match +## SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{64} ) + +attributetype ( 1.3.6.1.4.1.7165.2.1.53 NAME 'sambaTrustFlags' + DESC 'Trust Password Flags' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 ) + +# "min password length" +attributetype ( 1.3.6.1.4.1.7165.2.1.58 NAME 'sambaMinPwdLength' + DESC 'Minimal password length (default: 5)' + EQUALITY integerMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) + +# "password history" +attributetype ( 1.3.6.1.4.1.7165.2.1.59 NAME 'sambaPwdHistoryLength' + DESC 'Length of Password History Entries (default: 0 => off)' + EQUALITY integerMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) + +# "user must logon to change password" +attributetype ( 1.3.6.1.4.1.7165.2.1.60 NAME 'sambaLogonToChgPwd' + DESC 'Force Users to logon for password change (default: 0 => off, 2 => on)' + EQUALITY integerMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) + +# "maximum password age" +attributetype ( 1.3.6.1.4.1.7165.2.1.61 NAME 'sambaMaxPwdAge' + DESC 'Maximum password age, in seconds (default: -1 => never expire passwords)' + EQUALITY integerMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) + +# "minimum password age" +attributetype ( 1.3.6.1.4.1.7165.2.1.62 NAME 'sambaMinPwdAge' + DESC 'Minimum password age, in seconds (default: 0 => allow immediate password change)' + EQUALITY integerMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) + +# "lockout duration" +attributetype ( 1.3.6.1.4.1.7165.2.1.63 NAME 'sambaLockoutDuration' + DESC 'Lockout duration in minutes (default: 30, -1 => forever)' + EQUALITY integerMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) + +# "reset count minutes" +attributetype ( 1.3.6.1.4.1.7165.2.1.64 NAME 'sambaLockoutObservationWindow' + DESC 'Reset time after lockout in minutes (default: 30)' + EQUALITY integerMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) + +# "bad lockout attempt" +attributetype ( 1.3.6.1.4.1.7165.2.1.65 NAME 'sambaLockoutThreshold' + DESC 'Lockout users after bad logon attempts (default: 0 => off)' + EQUALITY integerMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) + +# "disconnect time" +attributetype ( 1.3.6.1.4.1.7165.2.1.66 NAME 'sambaForceLogoff' + DESC 'Disconnect Users outside logon hours (default: -1 => off, 0 => on)' + EQUALITY integerMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) + +# "refuse machine password change" +attributetype ( 1.3.6.1.4.1.7165.2.1.67 NAME 'sambaRefuseMachinePwdChange' + DESC 'Allow Machine Password changes (default: 0 => off)' + EQUALITY integerMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) + +# +attributetype ( 1.3.6.1.4.1.7165.2.1.68 NAME 'sambaClearTextPassword' + DESC 'Clear text password (used for trusted domain passwords)' + EQUALITY octetStringMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 ) + +# +attributetype ( 1.3.6.1.4.1.7165.2.1.69 NAME 'sambaPreviousClearTextPassword' + DESC 'Previous clear text password (used for trusted domain passwords)' + EQUALITY octetStringMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 ) + +attributetype ( 1.3.6.1.4.1.7165.2.1.70 NAME 'sambaTrustType' + DESC 'Type of trust' + EQUALITY integerMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) + +attributetype ( 1.3.6.1.4.1.7165.2.1.71 NAME 'sambaTrustAttributes' + DESC 'Trust attributes for a trusted domain' + EQUALITY integerMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) + +attributetype ( 1.3.6.1.4.1.7165.2.1.72 NAME 'sambaTrustDirection' + DESC 'Direction of a trust' + EQUALITY integerMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) + +attributetype ( 1.3.6.1.4.1.7165.2.1.73 NAME 'sambaTrustPartner' + DESC 'Fully qualified name of the domain with which a trust exists' + EQUALITY caseIgnoreMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{128} ) + +attributetype ( 1.3.6.1.4.1.7165.2.1.74 NAME 'sambaFlatName' + DESC 'NetBIOS name of a domain' + EQUALITY caseIgnoreMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{128} ) + +attributetype ( 1.3.6.1.4.1.7165.2.1.75 NAME 'sambaTrustAuthOutgoing' + DESC 'Authentication information for the outgoing portion of a trust' + EQUALITY caseExactMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{1050} ) + +attributetype ( 1.3.6.1.4.1.7165.2.1.76 NAME 'sambaTrustAuthIncoming' + DESC 'Authentication information for the incoming portion of a trust' + EQUALITY caseExactMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{1050} ) + +attributetype ( 1.3.6.1.4.1.7165.2.1.77 NAME 'sambaSecurityIdentifier' + DESC 'SID of a trusted domain' + EQUALITY caseIgnoreIA5Match SUBSTR caseExactIA5SubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{64} SINGLE-VALUE ) + +attributetype ( 1.3.6.1.4.1.7165.2.1.78 NAME 'sambaTrustForestTrustInfo' + DESC 'Forest trust information for a trusted domain object' + EQUALITY caseExactMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{1050} ) + +attributetype ( 1.3.6.1.4.1.7165.2.1.79 NAME 'sambaTrustPosixOffset' + DESC 'POSIX offset of a trust' + EQUALITY integerMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) + +attributetype ( 1.3.6.1.4.1.7165.2.1.80 NAME 'sambaSupportedEncryptionTypes' + DESC 'Supported encryption types of a trust' + EQUALITY integerMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) + +####################################################################### +## objectClasses used by Samba 3.0 schema ## +####################################################################### + +## The X.500 data model (and therefore LDAPv3) says that each entry can +## only have one structural objectclass. OpenLDAP 2.0 does not enforce +## this currently but will in v2.1 + +## +## added new objectclass (and OID) for 3.0 to help us deal with backwards +## compatibility with 2.2 installations (e.g. ldapsam_compat) --jerry +## +objectclass ( 1.3.6.1.4.1.7165.2.2.6 NAME 'sambaSamAccount' SUP top AUXILIARY + DESC 'Samba 3.0 Auxilary SAM Account' + MUST ( uid $ sambaSID ) + MAY ( cn $ sambaLMPassword $ sambaNTPassword $ sambaPwdLastSet $ + sambaLogonTime $ sambaLogoffTime $ sambaKickoffTime $ + sambaPwdCanChange $ sambaPwdMustChange $ sambaAcctFlags $ + displayName $ sambaHomePath $ sambaHomeDrive $ sambaLogonScript $ + sambaProfilePath $ description $ sambaUserWorkstations $ + sambaPrimaryGroupSID $ sambaDomainName $ sambaMungedDial $ + sambaBadPasswordCount $ sambaBadPasswordTime $ + sambaPasswordHistory $ sambaLogonHours)) + +## +## Group mapping info +## +objectclass ( 1.3.6.1.4.1.7165.2.2.4 NAME 'sambaGroupMapping' SUP top AUXILIARY + DESC 'Samba Group Mapping' + MUST ( gidNumber $ sambaSID $ sambaGroupType ) + MAY ( displayName $ description $ sambaSIDList )) + +## +## Trust password for trust relationships (any kind) +## +objectclass ( 1.3.6.1.4.1.7165.2.2.14 NAME 'sambaTrustPassword' SUP top STRUCTURAL + DESC 'Samba Trust Password' + MUST ( sambaDomainName $ sambaNTPassword $ sambaTrustFlags ) + MAY ( sambaSID $ sambaPwdLastSet )) + +## +## Trust password for trusted domains +## (to be stored beneath the trusting sambaDomain object in the DIT) +## +objectclass ( 1.3.6.1.4.1.7165.2.2.15 NAME 'sambaTrustedDomainPassword' SUP top STRUCTURAL + DESC 'Samba Trusted Domain Password' + MUST ( sambaDomainName $ sambaSID $ + sambaClearTextPassword $ sambaPwdLastSet ) + MAY ( sambaPreviousClearTextPassword )) + +## +## Whole-of-domain info +## +objectclass ( 1.3.6.1.4.1.7165.2.2.5 NAME 'sambaDomain' SUP top STRUCTURAL + DESC 'Samba Domain Information' + MUST ( sambaDomainName $ + sambaSID ) + MAY ( sambaNextRid $ sambaNextGroupRid $ sambaNextUserRid $ + sambaAlgorithmicRidBase $ + sambaMinPwdLength $ sambaPwdHistoryLength $ sambaLogonToChgPwd $ + sambaMaxPwdAge $ sambaMinPwdAge $ + sambaLockoutDuration $ sambaLockoutObservationWindow $ sambaLockoutThreshold $ + sambaForceLogoff $ sambaRefuseMachinePwdChange )) + +## +## used for idmap_ldap module +## +objectclass ( 1.3.6.1.4.1.7165.2.2.7 NAME 'sambaUnixIdPool' SUP top AUXILIARY + DESC 'Pool for allocating UNIX uids/gids' + MUST ( uidNumber $ gidNumber ) ) + + +objectclass ( 1.3.6.1.4.1.7165.2.2.8 NAME 'sambaIdmapEntry' SUP top AUXILIARY + DESC 'Mapping from a SID to an ID' + MUST ( sambaSID ) + MAY ( uidNumber $ gidNumber ) ) + +objectclass ( 1.3.6.1.4.1.7165.2.2.9 NAME 'sambaSidEntry' SUP top STRUCTURAL + DESC 'Structural Class for a SID' + MUST ( sambaSID ) ) + +objectclass ( 1.3.6.1.4.1.7165.2.2.10 NAME 'sambaConfig' SUP top AUXILIARY + DESC 'Samba Configuration Section' + MAY ( description ) ) + +objectclass ( 1.3.6.1.4.1.7165.2.2.11 NAME 'sambaShare' SUP top STRUCTURAL + DESC 'Samba Share Section' + MUST ( sambaShareName ) + MAY ( description ) ) + +objectclass ( 1.3.6.1.4.1.7165.2.2.12 NAME 'sambaConfigOption' SUP top STRUCTURAL + DESC 'Samba Configuration Option' + MUST ( sambaOptionName ) + MAY ( sambaBoolOption $ sambaIntegerOption $ sambaStringOption $ + sambaStringListoption $ description ) ) + + +## retired during privilege rewrite +##objectclass ( 1.3.6.1.4.1.7165.2.2.13 NAME 'sambaPrivilege' SUP top AUXILIARY +## DESC 'Samba Privilege' +## MUST ( sambaSID ) +## MAY ( sambaPrivilegeList ) ) + +## +## used for IPA_ldapsam +## +objectclass ( 1.3.6.1.4.1.7165.2.2.16 NAME 'sambaTrustedDomain' SUP top STRUCTURAL + DESC 'Samba Trusted Domain Object' + MUST ( cn ) + MAY ( sambaTrustType $ sambaTrustAttributes $ sambaTrustDirection $ + sambaTrustPartner $ sambaFlatName $ sambaTrustAuthOutgoing $ + sambaTrustAuthIncoming $ sambaSecurityIdentifier $ + sambaTrustForestTrustInfo $ sambaTrustPosixOffset $ + sambaSupportedEncryptionTypes) ) diff --git a/users/tests.py b/users/tests.py index 85a8e9f1..a7214cbb 100644 --- a/users/tests.py +++ b/users/tests.py @@ -23,6 +23,121 @@ The tests for the Users module. """ -# from django.test import TestCase +from django.test import TestCase +from django.conf import settings +from .models import School, ListShell, LdapUserGroup, ListRight + +import volatildap + +# Réglages bidon pour volatildap +LdapUserGroup.base_dn='ou=groups,dc=example,dc=org' + + +groups = ('ou=groups,dc=example,dc=org', { + 'objectClass': ['top', 'organizationalUnit'], 'ou': ['groups']}) +people = ('ou=people,dc=example,dc=org', { + 'objectClass': ['top', 'organizationalUnit'], 'ou': ['groups']}) +contacts = ('ou=contacts,ou=groups,dc=example,dc=org', { + 'objectClass': ['top', 'organizationalUnit'], 'ou': ['groups']}) +foogroup = ('cn=foogroup,ou=groups,dc=example,dc=org', { + 'objectClass': ['posixGroup'], 'memberUid': ['foouser', 'baruser'], + 'gidNumber': ['1000'], 'cn': ['foogroup']}) +bargroup = ('cn=bargroup,ou=groups,dc=example,dc=org', { + 'objectClass': ['posixGroup'], 'memberUid': ['zoouser', 'baruser'], + 'gidNumber': ['1001'], 'cn': ['bargroup']}) +wizgroup = ('cn=wizgroup,ou=groups,dc=example,dc=org', { + 'objectClass': ['posixGroup'], 'memberUid': ['wizuser', 'baruser'], + 'gidNumber': ['1002'], 'cn': ['wizgroup']}) +foouser = ('uid=foouser,ou=people,dc=example,dc=org', { + 'cn': [b'F\xc3\xb4o Us\xc3\xa9r'], + 'objectClass': ['posixAccount', 'shadowAccount', 'inetOrgPerson'], + 'loginShell': ['/bin/bash'], + 'jpegPhoto': [ + b'\xff\xd8\xff\xe0\x00\x10JFIF\x00\x01\x01\x01\x00H\x00H\x00\x00\xff' + b'\xfe\x00\x1cCreated with GIMP on a Mac\xff\xdb\x00C\x00\x05\x03\x04' + b'\x04\x04\x03\x05\x04\x04\x04\x05\x05\x05\x06\x07\x0c\x08\x07\x07\x07' + b'\x07\x0f\x0b\x0b\t\x0c\x11\x0f\x12\x12\x11\x0f\x11\x11\x13\x16\x1c' + b'\x17\x13\x14\x1a\x15\x11\x11\x18!\x18\x1a\x1d\x1d\x1f\x1f\x1f\x13' + b'\x17"$"\x1e$\x1c\x1e\x1f\x1e\xff\xdb\x00C\x01\x05\x05\x05\x07\x06\x07' + b'\x0e\x08\x08\x0e\x1e\x14\x11\x14\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e' + b'\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e' + b'\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e' + b'\x1e\x1e\x1e\x1e\x1e\x1e\x1e\xff\xc0\x00\x11\x08\x00\x08\x00\x08\x03' + b'\x01"\x00\x02\x11\x01\x03\x11\x01\xff\xc4\x00\x15\x00\x01\x01\x00\x00' + b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x08\xff\xc4\x00' + b'\x19\x10\x00\x03\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x01\x02\x06\x11A\xff\xc4\x00\x14\x01\x01\x00\x00\x00\x00\x00' + b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xc4\x00\x14\x11\x01' + b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff' + b'\xda\x00\x0c\x03\x01\x00\x02\x11\x03\x11\x00?\x00\x9d\xf29wU5Q\xd6' + b'\xfd\x00\x01\xff\xd9'], + 'uidNumber': ['2000'], 'gidNumber': ['1000'], 'sn': [b'Us\xc3\xa9r'], + 'homeDirectory': ['/home/foouser'], 'givenName': [b'F\xc3\xb4o'], + 'uid': ['foouser']}) + +class LdapTestCase(TestCase): + directory = {} + + @classmethod + def setUpClass(cls): + super(LdapTestCase, cls).setUpClass() + cls.ldap_server = volatildap.LdapServer( + initial_data=cls.directory, + schemas=['core.schema', 'cosine.schema', 'inetorgperson.schema', 'nis.schema'], + ) + settings.DATABASES['ldap']['USER'] = cls.ldap_server.rootdn + settings.DATABASES['ldap']['PASSWORD'] = cls.ldap_server.rootpw + settings.DATABASES['ldap']['NAME'] = cls.ldap_server.uri + + @classmethod + def tearDownClass(cls): + cls.ldap_server.stop() + super(LdapTestCase, cls).tearDownClass() + + def setUp(self): + super(LdapTestCase, self).setUp() + self.ldap_server.start() + + +class SchoolTestCase(TestCase): + def setUp(self): + School.objects.create(name="ENS Paris-Saclay") + School.objects.create(name="Supelec") + + def test_school_are_created(self): + pass + +class ListShellTestCase(TestCase): + def setUp(self): + ListShell.objects.create(shell="/bin/zsh") + ListShell.objects.create(shell="/bin/bash") + + def test_shell_are_created(self): + pass + +class GroupTestCase(LdapTestCase): + directory = dict([groups, foogroup, bargroup, wizgroup, people, foouser]) + + def test_create_ldapgroup(self): + mygroup = LdapUserGroup() + mygroup.name='re2o' + mygroup.gid=1010 + mygroup.members=['someuser', 'foouser'] + mygroup.save() + + # check ldap group was created + new = LdapUserGroup.objects.get(name='re2o') + self.assertEqual(new.name, 're2o') + self.assertEqual(new.gid, 1010) + self.assertEqual(new.members, ['someuser', 'foouser']) + + def test_create_re2ogroup(self): + ListRight.objects.create(gid='1011', unix_name='admins', details='test') + + #check re2o + lr = ListRight.objects.get(gid=1011) + self.asserEqual(lr.gid, 1011) + self.asserEqual(lr.details, 'test') + + -# Create your tests here. From 3f4dd43fa9a1d87c0f130f862cfa83c82222fd6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Kervella?= Date: Thu, 21 Jun 2018 14:19:08 +0000 Subject: [PATCH 37/42] Fix ldap testing --- CHANGELOG.md | 4 + pip_requirements.txt | 2 + test_utils/__init__.py | 0 test_utils/runner.py | 164 +++++++++++++++++++++++++++++++++++++++++ users/tests.py | 152 ++++++++++++-------------------------- 5 files changed, 218 insertions(+), 104 deletions(-) create mode 100644 test_utils/__init__.py create mode 100644 test_utils/runner.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ff8ead5..7d505130 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,6 +49,10 @@ install_re2o.sh help ## MR 172: Refactor API Creates a new (nearly) REST API to expose all models of Re2o. See [the dedicated wiki page](https://gitlab.federez.net/federez/re2o/wikis/API/Raw-Usage) for more details on how to use it. +* For testing purpose, add `volatildap` package: +``` +pip3 install volatildap +``` * Activate HTTP Authorization passthrough in by adding the following in `/etc/apache2/site-available/re2o.conf` (example in `install_utils/apache2/re2o.conf`): ``` WSGIPassAuthorization On diff --git a/pip_requirements.txt b/pip_requirements.txt index 0960c796..b40fa8c5 100644 --- a/pip_requirements.txt +++ b/pip_requirements.txt @@ -1,3 +1,5 @@ django-bootstrap3 django-ldapdb==0.9.0 django-macaddress +# For testing purpose +volatildap diff --git a/test_utils/__init__.py b/test_utils/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/test_utils/runner.py b/test_utils/runner.py new file mode 100644 index 00000000..b715762f --- /dev/null +++ b/test_utils/runner.py @@ -0,0 +1,164 @@ +# Re2o est un logiciel d'administration développé initiallement au rezometz. Il +# se veut agnostique au réseau considéré, de manière à être installable en +# quelques clics. +# +# Copyright © 2017 Gabriel Détraz +# Copyright © 2017 Goulven Kermarec +# Copyright © 2017 Augustin Lemesle +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +"""Defines the custom runners for Re2o. +""" + +import volatildap +import os.path + +from django.test.runner import DiscoverRunner +from django.conf import settings + +from users.models import LdapUser, LdapUserGroup, LdapServiceUser, LdapServiceUserGroup + +# The path of this file +__here = os.path.dirname(os.path.realpath(__file__)) +# The absolute path where to find the schemas for the LDAP +schema_path = os.path.abspath(os.path.join(__here, 'ldap', 'schema')) +# The absolute path of the "radius.schema" file +radius_schema_path = os.path.join(schema_path, 'radius.schema') +# The absolute path of the "samba.schema" file +samba_schema_path = os.path.join(schema_path, 'samba.schema') + +# The suffix for the LDAP +suffix = 'dc=example,dc=net' +# The admin CN of the LDAP +rootdn = 'cn=admin,'+suffix + +# Defines all ldap_entry mandatory for Re2o under a key-value list format +# that can be used directly by volatildap. For more on how to generate this +# data, see https://gitlab.federez.net/re2o/scripts/blob/master/print_ldap_entries.py +ldapentry_Utilisateurs = ('cn=Utilisateurs,'+suffix, { + 'cn': ['Utilisateurs'], + 'sambaSID': ['500'], + 'uid': ['Users'], + 'objectClass': ['posixGroup', 'top', 'sambaSamAccount', 'radiusprofile'], + 'gidNumber': ['500'], +}) +ldapentry_groups = ('ou=groups,'+suffix, { + 'ou': ['groups'], + 'objectClass': ['organizationalUnit'], + 'description': ["Groupes d'utilisateurs"], +}) +ldapentry_services = ('ou=services,ou=groups,'+suffix, { + 'ou': ['services'], + 'objectClass': ['organizationalUnit'], + 'description': ['Groupes de comptes techniques'], +}) +ldapentry_service_users = ('ou=service-users,'+suffix, { + 'ou': ['service-users'], + 'objectClass': ['organizationalUnit'], + 'description': ["Utilisateurs techniques de l'annuaire"], +}) +ldapentry_freeradius = ('cn=freeradius,ou=service-users,'+suffix, { + 'cn': ['freeradius'], + 'objectClass': ['applicationProcess', 'simpleSecurityObject'], + 'userPassword': ['FILL_IT'], +}) +ldapentry_nssauth = ('cn=nssauth,ou=service-users,'+suffix, { + 'cn': ['nssauth'], + 'objectClass': ['applicationProcess', 'simpleSecurityObject'], + 'userPassword': ['FILL_IT'], +}) +ldapentry_auth = ('cn=auth,ou=services,ou=groups,'+suffix, { + 'cn': ['auth'], + 'objectClass': ['groupOfNames'], + 'member': ['cn=nssauth,ou=service-users,'+suffix], +}) +ldapentry_posix = ('ou=posix,ou=groups,'+suffix, { + 'ou': ['posix'], + 'objectClass': ['organizationalUnit'], + 'description': ['Groupes de comptes POSIX'], +}) +ldapentry_wifi = ('cn=wifi,ou=service-users,'+suffix, { + 'cn': ['wifi'], + 'objectClass': ['applicationProcess', 'simpleSecurityObject'], + 'userPassword': ['FILL_IT'], +}) +ldapentry_usermgmt = ('cn=usermgmt,ou=services,ou=groups,'+suffix, { + 'cn': ['usermgmt'], + 'objectClass': ['groupOfNames'], + 'member': ['cn=wifi,ou=service-users,'+suffix], +}) +ldapentry_replica = ('cn=replica,ou=service-users,'+suffix, { + 'cn': ['replica'], + 'objectClass': ['applicationProcess', 'simpleSecurityObject'], + 'userPassword': ['FILL_IT'], +}) +ldapentry_readonly = ('cn=readonly,ou=services,ou=groups,'+suffix, { + 'cn': ['readonly'], + 'objectClass': ['groupOfNames'], + 'member': ['cn=replica,ou=service-users,'+suffix, 'cn=freeradius,ou=service-users,'+suffix], +}) +ldapbasic = dict([ldapentry_Utilisateurs, ldapentry_groups, + ldapentry_services, ldapentry_service_users, + ldapentry_freeradius, ldapentry_nssauth, ldapentry_auth, + ldapentry_posix, ldapentry_wifi, ldapentry_usermgmt, + ldapentry_replica, ldapentry_readonly]) + + +class DiscoverLdapRunner(DiscoverRunner): + """Discovers all the tests in the project + + This is a simple subclass of the default test runner + `django.test.runner.DiscoverRunner` that creates a test LDAP + right after the test databases are setup and destroys it right + before the test databases are setup. + It also ensure re2o's settings are using this new LDAP. + """ + + # The `volatildap.LdapServer` instance initiated with the minimal + # structure required by Re2o + ldap_server = volatildap.LdapServer( + suffix=suffix, + rootdn=rootdn, + initial_data=ldapbasic, + schemas=['core.schema', 'cosine.schema', 'inetorgperson.schema', + 'nis.schema', radius_schema_path, samba_schema_path] + ) + + def __init__(self, *args, **kwargs): + settings.DATABASES['ldap']['USER'] = self.ldap_server.rootdn + settings.DATABASES['ldap']['PASSWORD'] = self.ldap_server.rootpw + settings.DATABASES['ldap']['NAME'] = self.ldap_server.uri + settings.LDAP['base_user_dn'] = ldapentry_Utilisateurs[0] + settings.LDAP['base_userservice_dn'] = ldapentry_service_users[0] + settings.LDAP['base_usergroup_dn'] = ldapentry_posix[0] + settings.LDAP['base_userservicegroup_dn'] = ldapentry_services[0] + settings.LDAP['user_gid'] = ldapentry_Utilisateurs[1].get('gidNumber', ["500"])[0] + LdapUser.base_dn = settings.LDAP['base_user_dn'] + LdapUserGroup.base_dn = settings.LDAP['base_usergroup_dn'] + LdapServiceUser.base_dn = settings.LDAP['base_userservice_dn'] + LdapServiceUserGroup.base_dn = settings.LDAP['base_userservicegroup_dn'] + super(DiscoverLdapRunner, self).__init__(*args, **kwargs) + + + def setup_databases(self, *args, **kwargs): + ret = super(DiscoverLdapRunner, self).setup_databases(*args, **kwargs) + self.ldap_server.start() + return ret + + def teardown_databases(self, *args, **kwargs): + self.ldap_server.stop() + super(DiscoverLdapRunner, self).teardown_databases(*args, **kwargs) + diff --git a/users/tests.py b/users/tests.py index a7214cbb..6b2bfb41 100644 --- a/users/tests.py +++ b/users/tests.py @@ -23,121 +23,65 @@ The tests for the Users module. """ +import os.path + from django.test import TestCase from django.conf import settings -from .models import School, ListShell, LdapUserGroup, ListRight +from . import models import volatildap -# Réglages bidon pour volatildap -LdapUserGroup.base_dn='ou=groups,dc=example,dc=org' - - -groups = ('ou=groups,dc=example,dc=org', { - 'objectClass': ['top', 'organizationalUnit'], 'ou': ['groups']}) -people = ('ou=people,dc=example,dc=org', { - 'objectClass': ['top', 'organizationalUnit'], 'ou': ['groups']}) -contacts = ('ou=contacts,ou=groups,dc=example,dc=org', { - 'objectClass': ['top', 'organizationalUnit'], 'ou': ['groups']}) -foogroup = ('cn=foogroup,ou=groups,dc=example,dc=org', { - 'objectClass': ['posixGroup'], 'memberUid': ['foouser', 'baruser'], - 'gidNumber': ['1000'], 'cn': ['foogroup']}) -bargroup = ('cn=bargroup,ou=groups,dc=example,dc=org', { - 'objectClass': ['posixGroup'], 'memberUid': ['zoouser', 'baruser'], - 'gidNumber': ['1001'], 'cn': ['bargroup']}) -wizgroup = ('cn=wizgroup,ou=groups,dc=example,dc=org', { - 'objectClass': ['posixGroup'], 'memberUid': ['wizuser', 'baruser'], - 'gidNumber': ['1002'], 'cn': ['wizgroup']}) -foouser = ('uid=foouser,ou=people,dc=example,dc=org', { - 'cn': [b'F\xc3\xb4o Us\xc3\xa9r'], - 'objectClass': ['posixAccount', 'shadowAccount', 'inetOrgPerson'], - 'loginShell': ['/bin/bash'], - 'jpegPhoto': [ - b'\xff\xd8\xff\xe0\x00\x10JFIF\x00\x01\x01\x01\x00H\x00H\x00\x00\xff' - b'\xfe\x00\x1cCreated with GIMP on a Mac\xff\xdb\x00C\x00\x05\x03\x04' - b'\x04\x04\x03\x05\x04\x04\x04\x05\x05\x05\x06\x07\x0c\x08\x07\x07\x07' - b'\x07\x0f\x0b\x0b\t\x0c\x11\x0f\x12\x12\x11\x0f\x11\x11\x13\x16\x1c' - b'\x17\x13\x14\x1a\x15\x11\x11\x18!\x18\x1a\x1d\x1d\x1f\x1f\x1f\x13' - b'\x17"$"\x1e$\x1c\x1e\x1f\x1e\xff\xdb\x00C\x01\x05\x05\x05\x07\x06\x07' - b'\x0e\x08\x08\x0e\x1e\x14\x11\x14\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e' - b'\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e' - b'\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e' - b'\x1e\x1e\x1e\x1e\x1e\x1e\x1e\xff\xc0\x00\x11\x08\x00\x08\x00\x08\x03' - b'\x01"\x00\x02\x11\x01\x03\x11\x01\xff\xc4\x00\x15\x00\x01\x01\x00\x00' - b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x08\xff\xc4\x00' - b'\x19\x10\x00\x03\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' - b'\x00\x00\x01\x02\x06\x11A\xff\xc4\x00\x14\x01\x01\x00\x00\x00\x00\x00' - b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xc4\x00\x14\x11\x01' - b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff' - b'\xda\x00\x0c\x03\x01\x00\x02\x11\x03\x11\x00?\x00\x9d\xf29wU5Q\xd6' - b'\xfd\x00\x01\xff\xd9'], - 'uidNumber': ['2000'], 'gidNumber': ['1000'], 'sn': [b'Us\xc3\xa9r'], - 'homeDirectory': ['/home/foouser'], 'givenName': [b'F\xc3\xb4o'], - 'uid': ['foouser']}) - -class LdapTestCase(TestCase): - directory = {} - - @classmethod - def setUpClass(cls): - super(LdapTestCase, cls).setUpClass() - cls.ldap_server = volatildap.LdapServer( - initial_data=cls.directory, - schemas=['core.schema', 'cosine.schema', 'inetorgperson.schema', 'nis.schema'], - ) - settings.DATABASES['ldap']['USER'] = cls.ldap_server.rootdn - settings.DATABASES['ldap']['PASSWORD'] = cls.ldap_server.rootpw - settings.DATABASES['ldap']['NAME'] = cls.ldap_server.uri - - @classmethod - def tearDownClass(cls): - cls.ldap_server.stop() - super(LdapTestCase, cls).tearDownClass() - - def setUp(self): - super(LdapTestCase, self).setUp() - self.ldap_server.start() - class SchoolTestCase(TestCase): - def setUp(self): - School.objects.create(name="ENS Paris-Saclay") - School.objects.create(name="Supelec") - def test_school_are_created(self): - pass + s = models.School.objects.create(name="My awesome school") + self.assertEqual(s.name, "My awesome school") + class ListShellTestCase(TestCase): - def setUp(self): - ListShell.objects.create(shell="/bin/zsh") - ListShell.objects.create(shell="/bin/bash") - def test_shell_are_created(self): - pass - -class GroupTestCase(LdapTestCase): - directory = dict([groups, foogroup, bargroup, wizgroup, people, foouser]) - - def test_create_ldapgroup(self): - mygroup = LdapUserGroup() - mygroup.name='re2o' - mygroup.gid=1010 - mygroup.members=['someuser', 'foouser'] - mygroup.save() - - # check ldap group was created - new = LdapUserGroup.objects.get(name='re2o') - self.assertEqual(new.name, 're2o') - self.assertEqual(new.gid, 1010) - self.assertEqual(new.members, ['someuser', 'foouser']) - - def test_create_re2ogroup(self): - ListRight.objects.create(gid='1011', unix_name='admins', details='test') - - #check re2o - lr = ListRight.objects.get(gid=1011) - self.asserEqual(lr.gid, 1011) - self.asserEqual(lr.details, 'test') + s = models.ListShell.objects.create(shell="/bin/zsh") + self.assertEqual(s.shell, "/bin/zsh") +class LdapUserTestCase(TestCase): + def test_create_ldap_user(self): + g = models.LdapUser.objects.create( + gid="500", + name="users_test_ldapuser", + uid="users_test_ldapuser", + uidNumber="21001", + sn="users_test_ldapuser", + login_shell="/bin/false", + mail="user@example.net", + given_name="users_test_ldapuser", + home_directory="/home/moamoak", + display_name="users_test_ldapuser", + dialupAccess="False", + sambaSID="21001", + user_password="{SSHA}aBcDeFgHiJkLmNoPqRsTuVwXyZ012345", + sambat_nt_password="0123456789ABCDEF0123456789ABCDEF", + macs=[], + shadowexpire="0" + ) + self.assertEqual(g.name, 'users_test_ldapuser') + + +class LdapUserGroupTestCase(TestCase): + def test_create_ldap_user_group(self): + g = models.LdapUserGroup.objects.create( + gid="501", + members=[], + name="users_test_ldapusergroup" + ) + self.assertEqual(g.name, 'users_test_ldapusergroup') + + +class LdapServiceUserTestCase(TestCase): + def test_create_ldap_service_user(self): + g = models.LdapServiceUser.objects.create( + name="users_test_ldapserviceuser", + user_password="{SSHA}AbCdEfGhIjKlMnOpQrStUvWxYz987654" + ) + self.assertEqual(g.name, 'users_test_ldapserviceuser') From 6c33559f00fde3a7a0c6188e8232453dece5c96a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Kervella?= Date: Fri, 22 Jun 2018 00:48:45 +0000 Subject: [PATCH 38/42] Add tests for details of instances of every models --- api/serializers.py | 20 +- api/tests.py | 517 ++++++++++++++++++++++++++++++++++++------- api/urls.py | 2 +- api/views.py | 4 +- test_utils/runner.py | 2 + 5 files changed, 453 insertions(+), 92 deletions(-) diff --git a/api/serializers.py b/api/serializers.py index 26abc1c3..92f54427 100644 --- a/api/serializers.py +++ b/api/serializers.py @@ -276,6 +276,11 @@ class ServiceLinkSerializer(NamespacedHMSerializer): class OuverturePortListSerializer(NamespacedHMSerializer): """Serialize `machines.models.OuverturePortList` objects. """ + tcp_ports_in = NamespacedHRField(view_name='ouvertureport-detail', many=True, read_only=True) + udp_ports_in = NamespacedHRField(view_name='ouvertureport-detail', many=True, read_only=True) + tcp_ports_out = NamespacedHRField(view_name='ouvertureport-detail', many=True, read_only=True) + udp_ports_out = NamespacedHRField(view_name='ouvertureport-detail', many=True, read_only=True) + class Meta: model = machines.OuverturePortList fields = ('name', 'tcp_ports_in', 'udp_ports_in', 'tcp_ports_out', @@ -335,12 +340,15 @@ class GeneralOptionSerializer(NamespacedHMSerializer): 'GTU') -class ServiceSerializer(NamespacedHMSerializer): +class HomeServiceSerializer(NamespacedHMSerializer): """Serialize `preferences.models.Service` objects. """ class Meta: model = preferences.Service fields = ('name', 'url', 'description', 'image', 'api_url') + extra_kwargs = { + 'api_url': {'view_name': 'homeservice-detail'} + } class AssoOptionSerializer(NamespacedHMSerializer): @@ -625,14 +633,6 @@ class OriginV4RecordSerializer(IpListSerializer): fields = ('ipv4',) -class OriginV6RecordSerializer(Ipv6ListSerializer): - """Serialize `machines.models.Ipv6List` objects with the data needed to - generate an IPv6 Origin DNS record. - """ - class Meta(Ipv6ListSerializer.Meta): - fields = ('ipv6',) - - class NSRecordSerializer(NsSerializer): """Serialize `machines.models.Ns` objects with the data needed to generate a NS DNS record. @@ -713,7 +713,7 @@ class DNSZonesSerializer(serializers.ModelSerializer): soa = SOARecordSerializer() ns_records = NSRecordSerializer(many=True, source='ns_set') originv4 = OriginV4RecordSerializer(source='origin') - originv6 = OriginV6RecordSerializer(source='origin_v6') + originv6 = serializers.CharField(source='origin_v6') mx_records = MXRecordSerializer(many=True, source='mx_set') txt_records = TXTRecordSerializer(many=True, source='txt_set') srv_records = SRVRecordSerializer(many=True, source='srv_set') diff --git a/api/tests.py b/api/tests.py index 3120676a..322a3cfb 100644 --- a/api/tests.py +++ b/api/tests.py @@ -21,12 +21,14 @@ """ import json +import datetime from rest_framework.test import APITestCase from requests import codes -#import cotisations.models as cotisations -#import machines.models as machines -#import topologie.models as topologie +import cotisations.models as cotisations +import machines.models as machines +import preferences.models as preferences +import topologie.models as topologie import users.models as users @@ -52,114 +54,180 @@ class APIEndpointsTestCase(APITestCase): auth_no_perm_endpoints = [] auth_perm_endpoints = [ '/api/cotisations/article/', -# '/api/cotisations/article//', + '/api/cotisations/article/1/', '/api/cotisations/banque/', -# '/api/cotisations/banque//', + '/api/cotisations/banque/1/', '/api/cotisations/cotisation/', -# '/api/cotisations/cotisation//', +# '/api/cotisations/cotisation/1/', '/api/cotisations/facture/', -# '/api/cotisations/facture//', + '/api/cotisations/facture/1/', '/api/cotisations/paiement/', -# '/api/cotisations/paiement//', + '/api/cotisations/paiement/1/', '/api/cotisations/vente/', -# '/api/cotisations/vente//', + '/api/cotisations/vente/1/', '/api/machines/domain/', -# '/api/machines/domain//', + '/api/machines/domain/1/', '/api/machines/extension/', -# '/api/machines/extension//', + '/api/machines/extension/1/', '/api/machines/interface/', -# '/api/machines/interface//', + '/api/machines/interface/1/', '/api/machines/iplist/', -# '/api/machines/iplist//', + '/api/machines/iplist/1/', '/api/machines/iptype/', -# '/api/machines/iptype//', + '/api/machines/iptype/1/', '/api/machines/ipv6list/', -# '/api/machines/ipv6list//', + '/api/machines/ipv6list/1/', '/api/machines/machine/', -# '/api/machines/machine//', + '/api/machines/machine/1/', '/api/machines/machinetype/', -# '/api/machines/machinetype//', + '/api/machines/machinetype/1/', '/api/machines/mx/', -# '/api/machines/mx//', + '/api/machines/mx/1/', '/api/machines/nas/', -# '/api/machines/nas//', + '/api/machines/nas/1/', '/api/machines/ns/', -# '/api/machines/ns//', + '/api/machines/ns/1/', '/api/machines/ouvertureportlist/', -# '/api/machines/ouvertureportlist//', + '/api/machines/ouvertureportlist/1/', '/api/machines/ouvertureport/', -# '/api/machines/ouvertureport//', + '/api/machines/ouvertureport/1/', '/api/machines/servicelink/', -# '/api/machines/servicelink//', + '/api/machines/servicelink/1/', '/api/machines/service/', -# '/api/machines/service//', + '/api/machines/service/1/', '/api/machines/soa/', -# '/api/machines/soa//', + '/api/machines/soa/1/', '/api/machines/srv/', -# '/api/machines/srv//', + '/api/machines/srv/1/', '/api/machines/txt/', -# '/api/machines/txt//', + '/api/machines/txt/1/', '/api/machines/vlan/', -# '/api/machines/vlan//', + '/api/machines/vlan/1/', '/api/preferences/optionaluser/', '/api/preferences/optionalmachine/', '/api/preferences/optionaltopologie/', '/api/preferences/generaloption/', '/api/preferences/service/', -# '/api/preferences/service//', + '/api/preferences/service/1/', '/api/preferences/assooption/', '/api/preferences/homeoption/', '/api/preferences/mailmessageoption/', '/api/topologie/acesspoint/', -# '/api/topologie/acesspoint//', + # 2nd machine to be create (machines_machine_1, topologie_accesspoint_1) + '/api/topologie/acesspoint/2/', '/api/topologie/building/', -# '/api/topologie/building//', + '/api/topologie/building/1/', '/api/topologie/constructorswitch/', -# '/api/topologie/constructorswitch//', + '/api/topologie/constructorswitch/1/', '/api/topologie/modelswitch/', -# '/api/topologie/modelswitch//', + '/api/topologie/modelswitch/1/', '/api/topologie/room/', -# '/api/topologie/room//', + '/api/topologie/room/1/', '/api/topologie/server/', -# '/api/topologie/server//', + # 3rd machine to be create (machines_machine_1, topologie_accesspoint_1, + # topologie_server_1) + '/api/topologie/server/3/', '/api/topologie/stack/', -# '/api/topologie/stack//', + '/api/topologie/stack/1/', '/api/topologie/switch/', -# '/api/topologie/switch//', + # 4th machine to be create (machines_machine_1, topologie_accesspoint_1, + # topologie_server_1, topologie_switch_1) + '/api/topologie/switch/4/', '/api/topologie/switchbay/', -# '/api/topologie/switchbay//', + '/api/topologie/switchbay/1/', '/api/topologie/switchport/', -# '/api/topologie/switchport//', + '/api/topologie/switchport/1/', + '/api/topologie/switchport/2/', + '/api/topologie/switchport/3/', '/api/users/adherent/', -# '/api/users/adherent//', + # 3rd user to be create (stduser, superuser, users_adherent_1) + '/api/users/adherent/3/', '/api/users/ban/', -# '/api/users/ban//', + '/api/users/ban/1/', '/api/users/club/', -# '/api/users/club//', + # 4th user to be create (stduser, superuser, users_adherent_1, + # users_club_1) + '/api/users/club/4/', '/api/users/listright/', -# '/api/users/listright//', +# TODO: Merge !145 +# '/api/users/listright/1/', '/api/users/school/', -# '/api/users/school//', + '/api/users/school/1/', '/api/users/serviceuser/', -# '/api/users/serviceuser//', + '/api/users/serviceuser/1/', '/api/users/shell/', -# '/api/users/shell//', + '/api/users/shell/1/', '/api/users/user/', -# '/api/users/user//', + '/api/users/user/1/', '/api/users/whitelist/', -# '/api/users/whitelist//', + '/api/users/whitelist/1/', '/api/dns/zones/', '/api/dhcp/hostmacip/', '/api/mailing/standard', '/api/mailing/club', '/api/services/regen/', ] + not_found_endpoints = [ + '/api/cotisations/article/4242/', + '/api/cotisations/banque/4242/', + '/api/cotisations/cotisation/4242/', + '/api/cotisations/facture/4242/', + '/api/cotisations/paiement/4242/', + '/api/cotisations/vente/4242/', + '/api/machines/domain/4242/', + '/api/machines/extension/4242/', + '/api/machines/interface/4242/', + '/api/machines/iplist/4242/', + '/api/machines/iptype/4242/', + '/api/machines/ipv6list/4242/', + '/api/machines/machine/4242/', + '/api/machines/machinetype/4242/', + '/api/machines/mx/4242/', + '/api/machines/nas/4242/', + '/api/machines/ns/4242/', + '/api/machines/ouvertureportlist/4242/', + '/api/machines/ouvertureport/4242/', + '/api/machines/servicelink/4242/', + '/api/machines/service/4242/', + '/api/machines/soa/4242/', + '/api/machines/srv/4242/', + '/api/machines/txt/4242/', + '/api/machines/vlan/4242/', + '/api/preferences/service/4242/', + '/api/topologie/acesspoint/4242/', + '/api/topologie/building/4242/', + '/api/topologie/constructorswitch/4242/', + '/api/topologie/modelswitch/4242/', + '/api/topologie/room/4242/', + '/api/topologie/server/4242/', + '/api/topologie/stack/4242/', + '/api/topologie/switch/4242/', + '/api/topologie/switchbay/4242/', + '/api/topologie/switchport/4242/', + '/api/users/adherent/4242/', + '/api/users/ban/4242/', + '/api/users/club/4242/', + '/api/users/listright/4242/', + '/api/users/school/4242/', + '/api/users/serviceuser/4242/', + '/api/users/shell/4242/', + '/api/users/user/4242/', + '/api/users/whitelist/4242/', + ] stduser = None superuser = None @classmethod def setUpTestData(cls): + # Be aware that every object created here is never actually committed + # to the database. TestCase uses rollbacks after each test to cancel all + # modifications and recreates the data defined here before each test. + # For more details, see + # https://docs.djangoproject.com/en/1.10/topics/testing/tools/#testcase + + super(APIEndpointsTestCase, cls).setUpClass() + # A user with no rights cls.stduser = users.User.objects.create_user( "apistduser", @@ -175,15 +243,311 @@ class APIEndpointsTestCase(APITestCase): "apisuperuser" ) - # TODO : - # Create 1 object of every model so there is an exisiting object - # when quering for pk=1 - - @classmethod - def tearDownClass(cls): - cls.stduser.delete() - cls.superuser.delete() - super().tearDownClass() + # Creates 1 instance for each object so the "details" endpoints + # can be tested too. Objects need to be created in the right order. + # Dependencies (relatedFields, ...) are highlighted by a comment at + # the end of the concerned line (# Dep ). + cls.users_school_1 = users.School.objects.create( + name="users_school_1" + ) + cls.users_school_1.save() + cls.users_listshell_1 = users.ListShell.objects.create( + shell="users_listshell_1" + ) + cls.users_adherent_1 = users.Adherent.objects.create( + password="password", + last_login=datetime.datetime.now(datetime.timezone.utc), + surname="users_adherent_1", + pseudo="usersadherent1", + email="users_adherent_1@example.net", + school=cls.users_school_1, # Dep users.School + shell=cls.users_listshell_1, # Dep users.ListShell + comment="users Adherent 1 comment", + pwd_ntlm="", + state=users.User.STATES[0][0], + registered=datetime.datetime.now(datetime.timezone.utc), + telephone="0123456789", + uid_number=21102, + rezo_rez_uid=21102 + ) + cls.users_user_1 = cls.users_adherent_1 + cls.cotisations_article_1 = cotisations.Article.objects.create( + name="cotisations_article_1", + prix=10, + duration=1, + type_user=cotisations.Article.USER_TYPES[0][0], + type_cotisation=cotisations.Article.COTISATION_TYPE[0][0] + ) + cls.cotisations_banque_1 = cotisations.Banque.objects.create( + name="cotisations_banque_1" + ) + cls.cotisations_paiement_1 = cotisations.Paiement.objects.create( + moyen="cotisations_paiement_1", + type_paiement=cotisations.Paiement.PAYMENT_TYPES[0][0] + ) + cls.cotisations_facture_1 = cotisations.Facture.objects.create( + user=cls.users_user_1, # Dep users.User + paiement=cls.cotisations_paiement_1, # Dep cotisations.Paiement + banque=cls.cotisations_banque_1, # Dep cotisations.Banque + cheque="1234567890", + date=datetime.datetime.now(datetime.timezone.utc), + valid=True, + control=False + ) + cls.cotisations_vente_1 = cotisations.Vente.objects.create( + facture=cls.cotisations_facture_1, # Dep cotisations.Facture + number=2, + name="cotisations_vente_1", + prix=10, + duration=1, + type_cotisation=cotisations.Vente.COTISATION_TYPE[0][0] + ) +# Creation of this cotisation seems to revalidate the Vente object which of +# course already exists. +# FIXME +# cls.cotisations_cotisation_1 = cotisations.Cotisation.objects.create( +# vente=cls.cotisations_vente_1, # Dep cotisations.Vente +# type_cotisation=cotisations.Cotisation.COTISATION_TYPE[0][0], +# date_start=datetime.datetime.now(datetime.timezone.utc), +# date_end=datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(days=1) +# ) + cls.machines_machine_1 = machines.Machine.objects.create( + user=cls.users_user_1, # Dep users.User + name="machines_machine_1", + active=True + ) + cls.machines_ouvertureportlist_1 = machines.OuverturePortList.objects.create( + name="machines_ouvertureportlist_1" + ) + cls.machines_soa_1 = machines.SOA.objects.create( + name="machines_soa_1", + mail="postmaster@example.net", + refresh=86400, + retry=7200, + expire=3600000, + ttl=172800 + ) + cls.machines_extension_1 = machines.Extension.objects.create( + name="machines_extension_1", + need_infra=False, + # Do not set origin because of circular dependency + origin_v6="2001:db8:1234::", + soa=cls.machines_soa_1 # Dep machines.SOA + ) + cls.machines_vlan_1 = machines.Vlan.objects.create( + vlan_id=0, + name="machines_vlan_1", + comment="machines Vlan 1" + ) + cls.machines_iptype_1 = machines.IpType.objects.create( + type="machines_iptype_1", + extension=cls.machines_extension_1, # Dep machines.Extension + need_infra=False, + domaine_ip_start="10.0.0.1", + domaine_ip_stop="10.0.0.255", + prefix_v6="2001:db8:1234::", + vlan=cls.machines_vlan_1, # Dep machines.Vlan + ouverture_ports=cls.machines_ouvertureportlist_1 # Dep machines.OuverturePortList + ) + # All IPs in the IpType range are autocreated so we can't create + # new ones and thus we only retrieve it if needed in the test + cls.machines_iplist_1 = machines.IpList.objects.get( + ipv4="10.0.0.1", + ip_type=cls.machines_iptype_1, # Dep machines.IpType + ) + cls.machines_machinetype_1 = machines.MachineType.objects.create( + type="machines_machinetype_1", + ip_type=cls.machines_iptype_1, # Dep machines.IpType + ) + cls.machines_interface_1 = machines.Interface.objects.create( + ipv4=cls.machines_iplist_1, # Dep machines.IpList + mac_address="00:00:00:00:00:00", + machine=cls.machines_machine_1, # Dep machines.Machine + type=cls.machines_machinetype_1, # Dep machines.MachineType + details="machines Interface 1", + #port_lists=[cls.machines_ouvertureportlist_1] # Dep machines.OuverturePortList + ) + cls.machines_domain_1 = machines.Domain.objects.create( + interface_parent=cls.machines_interface_1, # Dep machines.Interface + name="machinesdomain", + extension=cls.machines_extension_1 # Dep machines.Extension + # Do no define cname for circular dependency + ) + cls.machines_mx_1 = machines.Mx.objects.create( + zone=cls.machines_extension_1, # Dep machines.Extension + priority=10, + name=cls.machines_domain_1 # Dep machines.Domain + ) + cls.machines_ns_1 = machines.Ns.objects.create( + zone=cls.machines_extension_1, # Dep machines.Extension + ns=cls.machines_domain_1 # Dep machines.Domain + ) + cls.machines_txt_1 = machines.Txt.objects.create( + zone=cls.machines_extension_1, # Dep machines.Extension + field1="machines_txt_1", + field2="machies Txt 1" + ) + cls.machines_srv_1 = machines.Srv.objects.create( + service="machines_srv_1", + protocole=machines.Srv.TCP, + extension=cls.machines_extension_1, # Dep machines.Extension + ttl=172800, + priority=0, + port=1, + target=cls.machines_domain_1, # Dep machines.Domain + ) + cls.machines_ipv6list_1 = machines.Ipv6List.objects.create( + ipv6="2001:db8:1234::", + interface=cls.machines_interface_1, # Dep machines.Interface + slaac_ip=False + ) + cls.machines_service_1 = machines.Service.objects.create( + service_type="machines_service_1", + min_time_regen=datetime.timedelta(minutes=1), + regular_time_regen=datetime.timedelta(hours=1) + # Do not define service_link because circular dependency + ) + cls.machines_servicelink_1 = machines.Service_link.objects.create( + service=cls.machines_service_1, # Dep machines.Service + server=cls.machines_interface_1, # Dep machines.Interface + last_regen=datetime.datetime.now(datetime.timezone.utc), + asked_regen=False + ) + cls.machines_ouvertureport_1 = machines.OuverturePort.objects.create( + begin=1, + end=2, + port_list=cls.machines_ouvertureportlist_1, # Dep machines.OuverturePortList + protocole=machines.OuverturePort.TCP, + io=machines.OuverturePort.OUT + ) + cls.machines_nas_1 = machines.Nas.objects.create( + name="machines_nas_1", + nas_type=cls.machines_machinetype_1, # Dep machines.MachineType + machine_type=cls.machines_machinetype_1, # Dep machines.MachineType + port_access_mode=machines.Nas.AUTH[0][0], + autocapture_mac=False + ) + cls.preferences_service_1 = preferences.Service.objects.create( + name="preferences_service_1", + url="https://example.net", + description="preferences Service 1", + image="/media/logo/none.png" + ) + cls.topologie_stack_1 = topologie.Stack.objects.create( + name="topologie_stack_1", + stack_id="1", + details="topologie Stack 1", + member_id_min=1, + member_id_max=10 + ) + cls.topologie_accespoint_1 = topologie.AccessPoint.objects.create( + user=cls.users_user_1, # Dep users.User + name="machines_machine_1", + active=True, + location="topologie AccessPoint 1" + ) + cls.topologie_server_1 = topologie.Server.objects.create( + user=cls.users_user_1, # Dep users.User + name="machines_machine_1", + active=True + ) + cls.topologie_building_1 = topologie.Building.objects.create( + name="topologie_building_1" + ) + cls.topologie_switchbay_1 = topologie.SwitchBay.objects.create( + name="topologie_switchbay_1", + building=cls.topologie_building_1, # Dep topologie.Building + info="topologie SwitchBay 1" + ) + cls.topologie_constructorswitch_1 = topologie.ConstructorSwitch.objects.create( + name="topologie_constructorswitch_1" + ) + cls.topologie_modelswitch_1 = topologie.ModelSwitch.objects.create( + reference="topologie_modelswitch_1", + constructor=cls.topologie_constructorswitch_1 # Dep topologie.ConstructorSwitch + ) + cls.topologie_switch_1 = topologie.Switch.objects.create( + user=cls.users_user_1, # Dep users.User + name="machines_machine_1", + active=True, + number=10, + stack=cls.topologie_stack_1, # Dep topologie.Stack + stack_member_id=1, + model=cls.topologie_modelswitch_1, # Dep topologie.ModelSwitch + switchbay=cls.topologie_switchbay_1 # Dep topologie.SwitchBay + ) + cls.topologie_room_1 = topologie.Room.objects.create( + name="topologie_romm_1", + details="topologie Room 1" + ) + cls.topologie_port_1 = topologie.Port.objects.create( + switch=cls.topologie_switch_1, # Dep topologie.Switch + port=1, + room=cls.topologie_room_1, # Dep topologie.Room + radius=topologie.Port.STATES[0][0], + vlan_force=cls.machines_vlan_1, # Dep machines.Vlan + details="topologie_switch_1" + ) + cls.topologie_port_2 = topologie.Port.objects.create( + switch=cls.topologie_switch_1, # Dep topologie.Switch + port=2, + machine_interface=cls.machines_interface_1, # Dep machines.Interface + radius=topologie.Port.STATES[0][0], + vlan_force=cls.machines_vlan_1, # Dep machines.Vlan + details="topologie_switch_1" + ) + cls.topologie_port_3 = topologie.Port.objects.create( + switch=cls.topologie_switch_1, # Dep topologie.Switch + port=3, + room=cls.topologie_room_1, # Dep topologie.Room + radius=topologie.Port.STATES[0][0], + # Do not defines related because circular dependency # Dep machines.Vlan + details="topologie_switch_1" + ) + cls.users_ban_1 = users.Ban.objects.create( + user=cls.users_user_1, # Dep users.User + raison="users Ban 1", + date_start=datetime.datetime.now(datetime.timezone.utc), + date_end=datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(days=1), + state=users.Ban.STATES[0][0] + ) + cls.users_club_1 = users.Club.objects.create( + password="password", + last_login=datetime.datetime.now(datetime.timezone.utc), + surname="users_club_1", + pseudo="usersclub1", + email="users_club_1@example.net", + school=cls.users_school_1, # Dep users.School + shell=cls.users_listshell_1, # Dep users.ListShell + comment="users Club 1 comment", + pwd_ntlm="", + state=users.User.STATES[0][0], + registered=datetime.datetime.now(datetime.timezone.utc), + telephone="0123456789", + uid_number=21103, + rezo_rez_uid=21103 + ) +# Need merge of MR145 to work +# TODO: Merge !145 +# cls.users_listright_1 = users.ListRight.objects.create( +# unix_name="userslistright", +# gid=601, +# critical=False, +# details="userslistright" +# ) + cls.users_serviceuser_1 = users.ServiceUser.objects.create( + password="password", + last_login=datetime.datetime.now(datetime.timezone.utc), + pseudo="usersserviceuser1", + access_group=users.ServiceUser.ACCESS[0][0], + comment="users ServiceUser 1" + ) + cls.users_whitelist_1 = users.Whitelist.objects.create( + user=cls.users_user_1, + raison="users Whitelist 1", + date_start=datetime.datetime.now(datetime.timezone.utc), + date_end=datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(days=1) + ) def check_responses_code(self, urls, expected_code, formats=None, assert_more=None): @@ -220,8 +584,7 @@ class APIEndpointsTestCase(APITestCase): Raises: AssertionError: An endpoint did not have a 200 status code. """ - urls = [endpoint.replace('', '1') - for endpoint in self.no_auth_endpoints] + urls = self.no_auth_endpoints self.check_responses_code(urls, codes.ok) def test_auth_endpoints_with_no_auth(self): @@ -231,8 +594,7 @@ class APIEndpointsTestCase(APITestCase): Raises: AssertionError: An endpoint did not have a 401 status code. """ - urls = [endpoint.replace('', '1') for endpoint in \ - self.auth_no_perm_endpoints + self.auth_perm_endpoints] + urls = self.auth_no_perm_endpoints + self.auth_perm_endpoints self.check_responses_code(urls, codes.unauthorized) def test_no_auth_endpoints_with_auth(self): @@ -243,8 +605,7 @@ class APIEndpointsTestCase(APITestCase): AssertionError: An endpoint did not have a 200 status code. """ self.client.force_authenticate(user=self.stduser) - urls = [endpoint.replace('', '1') - for endpoint in self.no_auth_endpoints] + urls = self.no_auth_endpoints self.check_responses_code(urls, codes.ok) def test_auth_no_perm_endpoints_with_auth_and_no_perm(self): @@ -256,8 +617,7 @@ class APIEndpointsTestCase(APITestCase): AssertionError: An endpoint did not have a 200 status code. """ self.client.force_authenticate(user=self.stduser) - urls = [endpoint.replace('', '1') - for endpoint in self.auth_no_perm_endpoints] + urls = self.auth_no_perm_endpoints self.check_responses_code(urls, codes.ok) def test_auth_perm_endpoints_with_auth_and_no_perm(self): @@ -269,8 +629,7 @@ class APIEndpointsTestCase(APITestCase): AssertionError: An endpoint did not have a 403 status code. """ self.client.force_authenticate(user=self.stduser) - urls = [endpoint.replace('', '1') - for endpoint in self.auth_perm_endpoints] + urls = self.auth_perm_endpoints self.check_responses_code(urls, codes.forbidden) def test_auth_endpoints_with_auth_and_perm(self): @@ -281,8 +640,7 @@ class APIEndpointsTestCase(APITestCase): AssertionError: An endpoint did not have a 200 status code. """ self.client.force_authenticate(user=self.superuser) - urls = [endpoint.replace('', '1') for endpoint \ - in self.auth_no_perm_endpoints + self.auth_perm_endpoints] + urls = self.auth_no_perm_endpoints + self.auth_perm_endpoints self.check_responses_code(urls, codes.ok) def test_endpoints_not_found(self): @@ -295,9 +653,7 @@ class APIEndpointsTestCase(APITestCase): """ self.client.force_authenticate(user=self.superuser) # Select only the URLs with '' and replace it with '42' - urls = [endpoint.replace('', '42') for endpoint in \ - self.no_auth_endpoints + self.auth_no_perm_endpoints + \ - self.auth_perm_endpoints if '' in endpoint] + urls = self.not_found_endpoints self.check_responses_code(urls, codes.not_found) def test_formats(self): @@ -310,9 +666,8 @@ class APIEndpointsTestCase(APITestCase): """ self.client.force_authenticate(user=self.superuser) - urls = [endpoint.replace('', '1') for endpoint in \ - self.no_auth_endpoints + self.auth_no_perm_endpoints + \ - self.auth_perm_endpoints] + urls = self.no_auth_endpoints + self.auth_no_perm_endpoints + \ + self.auth_perm_endpoints def assert_more(response, url, format): """Assert the response is valid json when format is json""" @@ -389,11 +744,15 @@ class APIPaginationTestCase(APITestCase): @classmethod def setUpTestData(cls): # A user with all the rights + # We need to use a different username than for the first + # test case because TestCase is using rollbacks which don't + # trigger the ldap_sync() thus the LDAP still have data about + # the old users. cls.superuser = users.User.objects.create_superuser( - "apisuperuser", - "apisuperuser", - "apisuperuser@example.net", - "apisuperuser" + "apisuperuser2", + "apisuperuser2", + "apisuperuser2@example.net", + "apisuperuser2" ) @classmethod diff --git a/api/urls.py b/api/urls.py index a04d6fbe..942435dd 100644 --- a/api/urls.py +++ b/api/urls.py @@ -65,7 +65,7 @@ router.register_view(r'preferences/optionaluser', views.OptionalUserView), router.register_view(r'preferences/optionalmachine', views.OptionalMachineView), router.register_view(r'preferences/optionaltopologie', views.OptionalTopologieView), router.register_view(r'preferences/generaloption', views.GeneralOptionView), -router.register_viewset(r'preferences/service', views.ServiceViewSet), +router.register_viewset(r'preferences/service', views.HomeServiceViewSet, base_name='homeservice'), router.register_view(r'preferences/assooption', views.AssoOptionView), router.register_view(r'preferences/homeoption', views.HomeOptionView), router.register_view(r'preferences/mailmessageoption', views.MailMessageOptionView), diff --git a/api/views.py b/api/views.py index 7532aece..d5245f2f 100644 --- a/api/views.py +++ b/api/views.py @@ -275,11 +275,11 @@ class GeneralOptionView(generics.RetrieveAPIView): return preferences.GeneralOption.objects.first() -class ServiceViewSet(viewsets.ReadOnlyModelViewSet): +class HomeServiceViewSet(viewsets.ReadOnlyModelViewSet): """Exposes list and details of `preferences.models.Service` objects. """ queryset = preferences.Service.objects.all() - serializer_class = serializers.ServiceSerializer + serializer_class = serializers.HomeServiceSerializer class AssoOptionView(generics.RetrieveAPIView): diff --git a/test_utils/runner.py b/test_utils/runner.py index b715762f..54ddd82f 100644 --- a/test_utils/runner.py +++ b/test_utils/runner.py @@ -155,10 +155,12 @@ class DiscoverLdapRunner(DiscoverRunner): def setup_databases(self, *args, **kwargs): ret = super(DiscoverLdapRunner, self).setup_databases(*args, **kwargs) + print("Creating test LDAP with volatildap...") self.ldap_server.start() return ret def teardown_databases(self, *args, **kwargs): self.ldap_server.stop() + print("Destroying test LDAP...") super(DiscoverLdapRunner, self).teardown_databases(*args, **kwargs) From fb78ec7a7726ca2dfafad5ace7178cc4367b12e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Kervella?= Date: Fri, 22 Jun 2018 01:29:19 +0000 Subject: [PATCH 39/42] Fix testing of cotisations.Cotisation objects --- api/tests.py | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/api/tests.py b/api/tests.py index 322a3cfb..ef05cec2 100644 --- a/api/tests.py +++ b/api/tests.py @@ -58,7 +58,7 @@ class APIEndpointsTestCase(APITestCase): '/api/cotisations/banque/', '/api/cotisations/banque/1/', '/api/cotisations/cotisation/', -# '/api/cotisations/cotisation/1/', + '/api/cotisations/cotisation/1/', '/api/cotisations/facture/', '/api/cotisations/facture/1/', '/api/cotisations/paiement/', @@ -302,15 +302,12 @@ class APIEndpointsTestCase(APITestCase): duration=1, type_cotisation=cotisations.Vente.COTISATION_TYPE[0][0] ) -# Creation of this cotisation seems to revalidate the Vente object which of -# course already exists. -# FIXME -# cls.cotisations_cotisation_1 = cotisations.Cotisation.objects.create( -# vente=cls.cotisations_vente_1, # Dep cotisations.Vente -# type_cotisation=cotisations.Cotisation.COTISATION_TYPE[0][0], -# date_start=datetime.datetime.now(datetime.timezone.utc), -# date_end=datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(days=1) -# ) + # A cotisation is automatically created by the Vente object and + # trying to create another cotisation associated with this vente + # will fail so we simply retrieve it so it can be used in the tests + cls.cotisations_cotisation_1 = cotisations.Cotisation.objects.get( + vente=cls.cotisations_vente_1, # Dep cotisations.Vente + ) cls.machines_machine_1 = machines.Machine.objects.create( user=cls.users_user_1, # Dep users.User name="machines_machine_1", @@ -350,7 +347,7 @@ class APIEndpointsTestCase(APITestCase): ouverture_ports=cls.machines_ouvertureportlist_1 # Dep machines.OuverturePortList ) # All IPs in the IpType range are autocreated so we can't create - # new ones and thus we only retrieve it if needed in the test + # new ones and thus we only retrieve it if needed in the tests cls.machines_iplist_1 = machines.IpList.objects.get( ipv4="10.0.0.1", ip_type=cls.machines_iptype_1, # Dep machines.IpType From 67b492b17d3d867029503fe7b04ce90d71c124ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Kervella?= Date: Sat, 23 Jun 2018 16:02:58 +0000 Subject: [PATCH 40/42] Pip requirements for dev are in a separate file --- pip_dev_requirements.txt | 2 ++ pip_requirements.txt | 2 -- 2 files changed, 2 insertions(+), 2 deletions(-) create mode 100644 pip_dev_requirements.txt diff --git a/pip_dev_requirements.txt b/pip_dev_requirements.txt new file mode 100644 index 00000000..cabe8473 --- /dev/null +++ b/pip_dev_requirements.txt @@ -0,0 +1,2 @@ +-r pip_requirements.txt +volatildap diff --git a/pip_requirements.txt b/pip_requirements.txt index b40fa8c5..0960c796 100644 --- a/pip_requirements.txt +++ b/pip_requirements.txt @@ -1,5 +1,3 @@ django-bootstrap3 django-ldapdb==0.9.0 django-macaddress -# For testing purpose -volatildap From a9cfc9aebcc96fc6d95c0d93d9106e666d01b2a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Kervella?= Date: Sat, 23 Jun 2018 21:19:11 +0000 Subject: [PATCH 41/42] DNS endpoint SQL optimization --- api/serializers.py | 3 ++- api/views.py | 9 ++++++++- machines/models.py | 15 ++++++++++++--- 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/api/serializers.py b/api/serializers.py index 92f54427..48988365 100644 --- a/api/serializers.py +++ b/api/serializers.py @@ -701,10 +701,11 @@ class CNAMERecordSerializer(serializers.ModelSerializer): """ alias = serializers.CharField(source='cname.name', read_only=True) hostname = serializers.CharField(source='name', read_only=True) + extension = serializers.CharField(source='extension.name', read_only=True) class Meta: model = machines.Domain - fields = ('alias', 'hostname') + fields = ('alias', 'hostname', 'extension') class DNSZonesSerializer(serializers.ModelSerializer): diff --git a/api/views.py b/api/views.py index d5245f2f..8fffe606 100644 --- a/api/views.py +++ b/api/views.py @@ -491,7 +491,14 @@ class DNSZonesView(generics.ListAPIView): """Exposes the detailed information about each extension (hostnames, IPs, DNS records, etc.) in order to build the DNS zone files. """ - queryset = machines.Extension.objects.all() + queryset = (machines.Extension.objects + .prefetch_related('soa') + .prefetch_related('ns_set').prefetch_related('ns_set__ns') + .prefetch_related('origin') + .prefetch_related('mx_set').prefetch_related('mx_set__name') + .prefetch_related('txt_set') + .prefetch_related('srv_set').prefetch_related('srv_set__target') + .all()) serializer_class = serializers.DNSZonesSerializer diff --git a/machines/models.py b/machines/models.py index 817e9d5a..50d99aaa 100644 --- a/machines/models.py +++ b/machines/models.py @@ -563,13 +563,22 @@ class Extension(RevMixin, AclMixin, models.Model): return entry def get_associated_a_records(self): - return Interface.objects.filter(type__ip_type__extension=self).filter(ipv4__isnull=False) + return (Interface.objects + .filter(type__ip_type__extension=self) + .filter(ipv4__isnull=False) + .prefetch_related('domain') + .prefetch_related('ipv4')) def get_associated_aaaa_records(self): - return Interface.objects.filter(type__ip_type__extension=self) + return (Interface.objects + .filter(type__ip_type__extension=self) + .prefetch_related('domain')) def get_associated_cname_records(self): - return Domain.objects.filter(extension=self).filter(cname__isnull=False) + return (Domain.objects + .filter(extension=self) + .filter(cname__isnull=False) + .prefetch_related('cname')) @staticmethod def can_use_all(user_request, *_args, **_kwargs): From 654da8cb04928681246a1f6b536ce1551315f314 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Kervella?= Date: Sat, 23 Jun 2018 21:36:01 +0000 Subject: [PATCH 42/42] Use re2o.utils functions for optimization --- machines/models.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/machines/models.py b/machines/models.py index 50d99aaa..fd4999d6 100644 --- a/machines/models.py +++ b/machines/models.py @@ -563,21 +563,22 @@ class Extension(RevMixin, AclMixin, models.Model): return entry def get_associated_a_records(self): - return (Interface.objects + from re2o.utils import all_active_assigned_interfaces + return (all_active_assigned_interfaces() .filter(type__ip_type__extension=self) - .filter(ipv4__isnull=False) - .prefetch_related('domain') - .prefetch_related('ipv4')) + .filter(ipv4__isnull=False)) def get_associated_aaaa_records(self): - return (Interface.objects - .filter(type__ip_type__extension=self) - .prefetch_related('domain')) + from re2o.utils import all_active_interfaces + return (all_active_interfaces(full=True) + .filter(type__ip_type__extension=self)) def get_associated_cname_records(self): + from re2o.utils import all_active_assigned_interfaces return (Domain.objects .filter(extension=self) .filter(cname__isnull=False) + .filter(interface_parent__in=all_active_assigned_interfaces()) .prefetch_related('cname')) @staticmethod