Add firewall and auth on driver API

This commit is contained in:
2022-04-10 18:33:49 +02:00
parent 28d2f5a68e
commit 9abdf55488
8 changed files with 164 additions and 4 deletions

View File

@@ -3,4 +3,27 @@ from solo.admin import SingletonModelAdmin
from driver.models import DriverConfiguration from driver.models import DriverConfiguration
admin.site.register(DriverConfiguration, SingletonModelAdmin)
@admin.register(DriverConfiguration)
class DriverConfigurationAdmin(SingletonModelAdmin):
fieldsets = (
(
"Remote DCC-EX configuration",
{
"fields": (
"remote_host",
"remote_port",
"timeout",
)
},
),
(
"Firewall setting",
{
"fields": (
"network",
"subnet_mask",
)
},
),
)

View File

@@ -0,0 +1,23 @@
# Generated by Django 4.0.3 on 2022-04-10 15:46
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('driver', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='driverconfiguration',
name='network',
field=models.GenericIPAddressField(default='192.168.4.0', protocol='IPv4'),
),
migrations.AddField(
model_name='driverconfiguration',
name='subnet_mask',
field=models.GenericIPAddressField(default='255.255.255.0', protocol='IPv4'),
),
]

View File

@@ -0,0 +1,28 @@
# Generated by Django 4.0.3 on 2022-04-10 16:00
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('driver', '0002_driverconfiguration_network_and_more'),
]
operations = [
migrations.AlterField(
model_name='driverconfiguration',
name='network',
field=models.GenericIPAddressField(blank=True, default='192.168.4.0', null=True, protocol='IPv4'),
),
migrations.AlterField(
model_name='driverconfiguration',
name='remote_host',
field=models.GenericIPAddressField(blank=True, default='192.168.4.1', null=True, protocol='IPv4'),
),
migrations.AlterField(
model_name='driverconfiguration',
name='subnet_mask',
field=models.GenericIPAddressField(blank=True, default='255.255.255.0', null=True, protocol='IPv4'),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 4.0.3 on 2022-04-10 16:01
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('driver', '0003_alter_driverconfiguration_network_and_more'),
]
operations = [
migrations.AlterField(
model_name='driverconfiguration',
name='remote_host',
field=models.GenericIPAddressField(default='192.168.4.1', protocol='IPv4'),
),
]

View File

@@ -1,16 +1,41 @@
from django.db import models from django.db import models
from django.core.exceptions import ValidationError
from ipaddress import IPv4Address, IPv4Network
from solo.models import SingletonModel from solo.models import SingletonModel
class DriverConfiguration(SingletonModel): class DriverConfiguration(SingletonModel):
remote_host = models.GenericIPAddressField( remote_host = models.GenericIPAddressField(
protocol="IPv4", default="192.168.4.1" protocol="IPv4",
default="192.168.4.1"
) )
remote_port = models.SmallIntegerField(default=2560) remote_port = models.SmallIntegerField(default=2560)
timeout = models.SmallIntegerField(default=250) timeout = models.SmallIntegerField(default=250)
network = models.GenericIPAddressField(
protocol="IPv4",
default="192.168.4.0",
blank=True,
null=True
)
subnet_mask = models.GenericIPAddressField(
protocol="IPv4",
default="255.255.255.0",
blank=True,
null=True
)
def __str__(self): def __str__(self):
return "Configuration" return "Configuration"
def clean(self, *args, **kwargs):
if self.network:
try:
IPv4Network(
"{0}/{1}".format(self.network, self.subnet_mask))
except ValueError as e:
raise ValidationError(e)
super().clean(*args, **kwargs)
class Meta: class Meta:
verbose_name = "Configuration" verbose_name = "Configuration"

View File

@@ -1,10 +1,17 @@
from ipaddress import IPv4Address, IPv4Network
from django.http import Http404 from django.http import Http404
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
from rest_framework import status, serializers from rest_framework import status, serializers
from rest_framework.views import APIView from rest_framework.views import APIView
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.permissions import (
IsAuthenticated,
BasePermission,
SAFE_METHODS
)
from dcc.parsers import PlainTextParser from dcc.parsers import PlainTextParser
from driver.models import DriverConfiguration
from driver.connector import Connector from driver.connector import Connector
from driver.serializers import ( from driver.serializers import (
FunctionSerializer, FunctionSerializer,
@@ -27,12 +34,35 @@ def addresschecker(f):
return addresslookup return addresslookup
class Firewall(BasePermission):
def has_permission(self, request, view):
config = DriverConfiguration.get_solo()
if not config.network:
return request.method in SAFE_METHODS
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
if x_forwarded_for:
ip = IPv4Address(x_forwarded_for.split(',')[0])
else:
ip = IPv4Address(request.META.get("REMOTE_ADDR"))
network = IPv4Network("{0}/{1}".format(
config.network,
config.subnet_mask
))
# accept IP configured is settings or localhost
if ip in network or ip in IPv4Network("127.0.0.0/8"):
return request.method in SAFE_METHODS
class Test(APIView): class Test(APIView):
""" """
Send a test <s> command Send a test <s> command
""" """
parser_classes = [PlainTextParser] parser_classes = [PlainTextParser]
permission_classes = [IsAuthenticated | Firewall]
def get(self, request): def get(self, request):
response = Connector().passthrough("<s>") response = Connector().passthrough("<s>")
@@ -47,6 +77,7 @@ class SendCommand(APIView):
""" """
parser_classes = [PlainTextParser] parser_classes = [PlainTextParser]
permission_classes = [IsAuthenticated | Firewall]
def put(self, request): def put(self, request):
data = request.data data = request.data
@@ -70,6 +101,7 @@ class Function(APIView):
""" """
Send "Function" commands to a valid DCC address Send "Function" commands to a valid DCC address
""" """
permission_classes = [IsAuthenticated | Firewall]
def put(self, request, address): def put(self, request, address):
serializer = FunctionSerializer(data=request.data) serializer = FunctionSerializer(data=request.data)
@@ -85,6 +117,7 @@ class Cab(APIView):
""" """
Send "Cab" commands to a valid DCC address Send "Cab" commands to a valid DCC address
""" """
permission_classes = [IsAuthenticated | Firewall]
def put(self, request, address): def put(self, request, address):
serializer = CabSerializer(data=request.data) serializer = CabSerializer(data=request.data)
@@ -99,6 +132,7 @@ class Infra(APIView):
""" """
Send "Infra" commands to a valid DCC address Send "Infra" commands to a valid DCC address
""" """
permission_classes = [IsAuthenticated | Firewall]
def put(self, request): def put(self, request):
serializer = InfraSerializer(data=request.data) serializer = InfraSerializer(data=request.data)
@@ -113,6 +147,7 @@ class Emergency(APIView):
""" """
Send an "Emergency" stop, no matter the HTTP method used Send an "Emergency" stop, no matter the HTTP method used
""" """
permission_classes = [IsAuthenticated | Firewall]
def put(self, request): def put(self, request):
Connector().emergency() Connector().emergency()

View File

@@ -1,3 +1,7 @@
.card > a > img { .card > a > img {
width: 100%; width: 100%;
} }
#footer > p {
display: inline;
}

View File

@@ -5,8 +5,12 @@
<p class="float-end mb-1"> <p class="float-end mb-1">
<a href="#">Back to top</a> <a href="#">Back to top</a>
</p> </p>
<p class="mb-1">&copy; {% now "Y" %} {{ site_conf.footer | markdown | safe }}</p> <div id="footer" class="mb-1">
<p class="mb-0">{{ site_conf.footer_extended | markdown | safe }}</p> <p>&copy; {% now "Y" %}</p> {{ site_conf.footer | markdown | safe }}
</div>
<div id="footer_extended" class="mb-0">
{{ site_conf.footer_extended | markdown | safe }}
</div>
</div> </div>
{% if site_conf.show_version %} {% if site_conf.show_version %}
<div class="container"> <div class="container">