Compare commits
4 commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7efdd60643 | |||
| e13b5e97bd | |||
| d6a02cf945 | |||
| 8660d0ce75 |
10 changed files with 202 additions and 5 deletions
50
Dockerfile
Normal file
50
Dockerfile
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
FROM docker.io/python:3.12.10-alpine3.21
|
||||||
|
|
||||||
|
ENV \
|
||||||
|
LANG=C.UTF-8 \
|
||||||
|
LC_ALL=C.UTF-8 \
|
||||||
|
# python:
|
||||||
|
PYTHONFAULTHANDLER=1 \
|
||||||
|
PYTHONUNBUFFERED=1 \
|
||||||
|
PYTHONHASHSEED=random \
|
||||||
|
PYTHONDONTWRITEBYTECODE=1
|
||||||
|
|
||||||
|
RUN apk add --update --no-cache zlib-dev libxml2-dev libxslt-dev libmemcached-dev \
|
||||||
|
build-base gcc make alpine-sdk musl-dev postgresql-dev openldap-dev \
|
||||||
|
py3-distutils-extra py3-distutils-extra-pyc shadow python3-dev linux-headers pcre-dev
|
||||||
|
|
||||||
|
ENV VIRTUAL_ENV=/venv
|
||||||
|
|
||||||
|
ENV DJANGO_CONFIGURATION=production
|
||||||
|
|
||||||
|
ENV \
|
||||||
|
# pip:
|
||||||
|
PIP_NO_CACHE_DIR=off \
|
||||||
|
PIP_DISABLE_PIP_VERSION_CHECK=on \
|
||||||
|
PIP_DEFAULT_TIMEOUT=100
|
||||||
|
|
||||||
|
|
||||||
|
RUN pip install --upgrade pip wheel
|
||||||
|
RUN python3 -m venv $VIRTUAL_ENV
|
||||||
|
|
||||||
|
COPY requirements.txt .
|
||||||
|
RUN . /venv/bin/activate && pip install -r requirements.txt
|
||||||
|
|
||||||
|
RUN mkdir -p /app
|
||||||
|
COPY ./ /app/
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
RUN . /venv/bin/activate && \
|
||||||
|
DJANGO_SECRET_KEY=dummy \
|
||||||
|
DJANGO_STATIC_ROOT=/app/static ./manage.py collectstatic --no-input
|
||||||
|
|
||||||
|
RUN groupadd --gid 1001 app \
|
||||||
|
&& useradd --home-dir /app --shell /bin/bash --gid app --uid 1001 app
|
||||||
|
|
||||||
|
RUN chown -R app:app ./
|
||||||
|
|
||||||
|
USER app
|
||||||
|
|
||||||
|
ENV PATH="/venv/bin:$PATH"
|
||||||
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
from django import forms
|
from django import forms
|
||||||
|
import ipaddress
|
||||||
from .models import validate_ula_prefix, ULA
|
from .models import validate_ula_prefix, ULA
|
||||||
|
from django.core.exceptions import ValidationError
|
||||||
|
import logging
|
||||||
|
|
||||||
class ULAForm(forms.ModelForm):
|
class ULAForm(forms.ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
@ -17,3 +19,67 @@ class ULAGenerateForm(forms.ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = ULA
|
model = ULA
|
||||||
fields = [ 'name', 'organization', 'website' ]
|
fields = [ 'name', 'organization', 'website' ]
|
||||||
|
|
||||||
|
class ULAUpdateForm(forms.ModelForm):
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
self.owner = kwargs.pop('owner', None)
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = ULA
|
||||||
|
fields = [ 'prefix', 'name', 'organization', 'website' ]
|
||||||
|
|
||||||
|
def clean_prefix(self):
|
||||||
|
prefix = self.cleaned_data.get("prefix")
|
||||||
|
if not prefix or not self.owner:
|
||||||
|
return prefix
|
||||||
|
|
||||||
|
net_str = f"{prefix}/48"
|
||||||
|
net = ipaddress.IPv6Network(net_str, strict=False)
|
||||||
|
normalized_prefix = str(net[0])
|
||||||
|
self.cleaned_data['prefix'] = normalized_prefix
|
||||||
|
|
||||||
|
qs = ULA.objects.filter(prefix=normalized_prefix)
|
||||||
|
if self.instance.pk:
|
||||||
|
qs = qs.exclude(pk=self.instance.pk)
|
||||||
|
|
||||||
|
if qs.exists():
|
||||||
|
existing = qs.first()
|
||||||
|
if existing.owner != self.owner:
|
||||||
|
raise ValidationError("This prefix is already registered by another user.")
|
||||||
|
else:
|
||||||
|
self.instance = existing
|
||||||
|
return normalized_prefix
|
||||||
|
|
||||||
|
|
||||||
|
class ULADeleteForm(forms.ModelForm):
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
self.owner = kwargs.pop('owner', None)
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = ULA
|
||||||
|
fields = [ 'prefix' ]
|
||||||
|
|
||||||
|
def clean_prefix(self):
|
||||||
|
prefix = self.cleaned_data['prefix']
|
||||||
|
|
||||||
|
if not prefix or not self.owner:
|
||||||
|
return prefix
|
||||||
|
|
||||||
|
net_str = f"{prefix}/48"
|
||||||
|
net = ipaddress.IPv6Network(net_str, strict=False)
|
||||||
|
normalized_prefix = str(net[0])
|
||||||
|
self.cleaned_data['prefix'] = normalized_prefix
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.instance = ULA.objects.get(prefix=normalized_prefix, owner=self.owner)
|
||||||
|
except ULA.DoesNotExist:
|
||||||
|
raise ValidationError("No ULA with this prefix was found for the current user.")
|
||||||
|
|
||||||
|
return normalized_prefix
|
||||||
|
|
||||||
|
def delete(self):
|
||||||
|
if self.instance:
|
||||||
|
print(f"Deleting: {self.instance}")
|
||||||
|
self.instance.delete()
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,6 @@ def validate_ula_prefix(prefix):
|
||||||
params = {'prefix': net }
|
params = {'prefix': net }
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class User(AbstractUser):
|
class User(AbstractUser):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
@ -41,7 +40,7 @@ class ULA(models.Model):
|
||||||
on_delete=models.CASCADE
|
on_delete=models.CASCADE
|
||||||
)
|
)
|
||||||
|
|
||||||
prefix = models.GenericIPAddressField(protocol='IPv6', unique=True, validators=[validate_ula_prefix])
|
prefix = models.GenericIPAddressField(protocol='IPv6', unique=True)
|
||||||
name = models.CharField(max_length=256)
|
name = models.CharField(max_length=256)
|
||||||
organization = models.CharField(max_length=256)
|
organization = models.CharField(max_length=256)
|
||||||
website = models.URLField()
|
website = models.URLField()
|
||||||
|
|
@ -54,7 +53,6 @@ class ULA(models.Model):
|
||||||
net_str = f"{self.prefix}/48"
|
net_str = f"{self.prefix}/48"
|
||||||
net = ipaddress.IPv6Network(net_str, strict=False)
|
net = ipaddress.IPv6Network(net_str, strict=False)
|
||||||
self.prefix = str(net[0])
|
self.prefix = str(net[0])
|
||||||
|
|
||||||
super().save(*args, **kwargs)
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
|
|
|
||||||
|
|
@ -140,7 +140,7 @@ USE_TZ = True
|
||||||
|
|
||||||
STATIC_URL = '/static/'
|
STATIC_URL = '/static/'
|
||||||
STATICFILES_DIRS = [ ]
|
STATICFILES_DIRS = [ ]
|
||||||
STATIC_ROOT = "/home/app/app/static"
|
STATIC_ROOT = "/app/static"
|
||||||
|
|
||||||
|
|
||||||
AUTHENTICATION_BACKENDS = [
|
AUTHENTICATION_BACKENDS = [
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,14 @@
|
||||||
<a class="nav-link" href="/submit/">Submit existing
|
<a class="nav-link" href="/submit/">Submit existing
|
||||||
prefix</a>
|
prefix</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="/update/">Update existing
|
||||||
|
prefix</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="/delete/">Delete existing
|
||||||
|
prefix</a>
|
||||||
|
</li>
|
||||||
{% if user.is_authenticated %}
|
{% if user.is_authenticated %}
|
||||||
<span class="navbar-text">Logged in as {{ user }}.</span>
|
<span class="navbar-text">Logged in as {{ user }}.</span>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,16 @@
|
||||||
<p>
|
<p>
|
||||||
A new random prefix will be generated for you.
|
A new random prefix will be generated for you.
|
||||||
</p>
|
</p>
|
||||||
|
{% elif method == 'update' %}
|
||||||
|
<h1>Update existing prefix</h1>
|
||||||
|
<p>
|
||||||
|
You update your existing prefix.
|
||||||
|
</p>
|
||||||
|
{% elif method == 'delete' %}
|
||||||
|
<h1>Delete existing prefix</h1>
|
||||||
|
<p>
|
||||||
|
You delete your existing prefix.
|
||||||
|
</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
|
|
|
||||||
|
|
@ -28,4 +28,6 @@ urlpatterns = [
|
||||||
path('login/', views.LoginView.as_view(), name='login'),
|
path('login/', views.LoginView.as_view(), name='login'),
|
||||||
path('logout/', views.logout_view, name='logout'),
|
path('logout/', views.logout_view, name='logout'),
|
||||||
path('admin/', admin.site.urls),
|
path('admin/', admin.site.urls),
|
||||||
|
path('update/', views.UpdateView.as_view(), name='update'),
|
||||||
|
path('delete/', views.DeleteView.as_view(), name='delete'),
|
||||||
] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
|
] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,9 @@ from django.contrib.auth import logout
|
||||||
from django.shortcuts import redirect
|
from django.shortcuts import redirect
|
||||||
|
|
||||||
import ipaddress
|
import ipaddress
|
||||||
|
from django.http import HttpResponseRedirect
|
||||||
|
from django.urls import reverse_lazy
|
||||||
|
from django.contrib.messages import success
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
|
|
@ -73,6 +76,52 @@ class SubmitView(GenerateSubmitView):
|
||||||
class LoginView(auth_views.LoginView):
|
class LoginView(auth_views.LoginView):
|
||||||
template_name = 'ipv6ula/login.html'
|
template_name = 'ipv6ula/login.html'
|
||||||
|
|
||||||
|
class UpdateView(GenerateSubmitView):
|
||||||
|
form_class = ULAUpdateForm
|
||||||
|
gen_method = "update"
|
||||||
|
success_message = "%(the_prefix)s/48 was updated successfully"
|
||||||
|
|
||||||
|
def get_success_message(self, cleaned_data):
|
||||||
|
return self.success_message % {
|
||||||
|
"the_prefix": cleaned_data.get("prefix", "(unknown)")
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_form_kwargs(self):
|
||||||
|
kwargs = super().get_form_kwargs()
|
||||||
|
kwargs['owner'] = self.request.user
|
||||||
|
return kwargs
|
||||||
|
|
||||||
|
def form_valid(self, form):
|
||||||
|
prefix = form.cleaned_data["prefix"]
|
||||||
|
form.instance.owner = self.request.user
|
||||||
|
if not ULA.objects.filter(prefix=prefix, owner=self.request.user).exists():
|
||||||
|
form.add_error('prefix', 'This prefix is not yours')
|
||||||
|
return self.form_invalid(form)
|
||||||
|
return super().form_valid(form)
|
||||||
|
|
||||||
|
class DeleteView(GenerateSubmitView):
|
||||||
|
form_class = ULADeleteForm
|
||||||
|
gen_method = "delete"
|
||||||
|
success_message = "%(the_prefix)s/48 was deleted successfully"
|
||||||
|
success_url = reverse_lazy("index")
|
||||||
|
|
||||||
|
def get_form_kwargs(self):
|
||||||
|
kwargs = super().get_form_kwargs()
|
||||||
|
kwargs['owner'] = self.request.user
|
||||||
|
return kwargs
|
||||||
|
|
||||||
|
def form_valid(self, form):
|
||||||
|
prefix = form.cleaned_data["prefix"]
|
||||||
|
try:
|
||||||
|
instance = ULA.objects.get(prefix=prefix, owner=self.request.user)
|
||||||
|
instance.delete()
|
||||||
|
self.object = instance
|
||||||
|
except ULA.DoesNotExist:
|
||||||
|
form.add_error("prefix", "The prefix does not exist or does not belong to you.")
|
||||||
|
return self.form_invalid(form)
|
||||||
|
success(self.request, self.get_success_message(form.cleaned_data))
|
||||||
|
return HttpResponseRedirect(self.get_success_url())
|
||||||
|
|
||||||
def logout_view(request):
|
def logout_view(request):
|
||||||
logout(request)
|
logout(request)
|
||||||
return redirect("/")
|
return redirect("/")
|
||||||
|
|
|
||||||
|
|
@ -2,3 +2,5 @@ django==3.1.4
|
||||||
django-auth-ldap
|
django-auth-ldap
|
||||||
psycopg2
|
psycopg2
|
||||||
django-crispy-forms
|
django-crispy-forms
|
||||||
|
setuptools
|
||||||
|
uwsgi
|
||||||
|
|
|
||||||
12
uwsgiconfig/app.ini
Normal file
12
uwsgiconfig/app.ini
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
[uwsgi]
|
||||||
|
socket = /app/uwsgi/uwsgi.sock
|
||||||
|
chdir = /app
|
||||||
|
venv = /venv
|
||||||
|
wsgi-file = ipv6ula/wsgi.py
|
||||||
|
processes = 4
|
||||||
|
threads = 2
|
||||||
|
chmod-socket = 666
|
||||||
|
vacuum = true
|
||||||
|
plugins = python3
|
||||||
|
uid = app
|
||||||
|
gid = app
|
||||||
Loading…
Add table
Add a link
Reference in a new issue