vpnmanager/manager/views.py

763 lines
22 KiB
Python

from django.shortcuts import render, get_object_or_404, redirect
from django.http import HttpResponse
from django.contrib.auth.forms import AuthenticationForm, PasswordChangeForm
from django.contrib.auth.forms import AdminPasswordChangeForm
from django.db.models import Q
from django.views.decorators.csrf import csrf_exempt
from django.utils import timezone
from django.core.files import File
from django.db.models.fields.files import FieldFile
from django.contrib.auth.models import User
from django.contrib.auth.decorators import login_required, user_passes_test
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.conf import settings
from django.contrib.auth.forms import PasswordResetForm
from django.urls import reverse
from .models import Device, Organization, Network, Model, Wifi, UserStatus
from .signals import *
from .forms import *
from .device import makewificonfig, heartbeathandler
from .tasks import mkfirmware
from distutils.dir_util import copy_tree
from celery.exceptions import TimeoutError
from subprocess import call as execute
import glob
import sys
import subprocess
import os
import socket
import tempfile
import crypt
import tarfile
from datetime import datetime
import time
import uuid
def is_superuser(user):
return user.is_superuser
def is_staff(user):
return user.is_staff
@login_required
def index(request):
return redirect(reverse("devices"))
@csrf_exempt
def heartbeat(request):
device = get_object_or_404(Device, secret=request.POST.get("secret", ""))
ip = request.POST.get("ip", "")
return HttpResponse(heartbeathandler(device, ip))
@csrf_exempt
def wireless(request):
device = get_object_or_404(Device, secret=request.POST.get("secret", ""))
device.wireless = timezone.now()
device.save()
return HttpResponse(makewificonfig(device))
@csrf_exempt
def hosts(request):
device = get_object_or_404(Device, secret=request.POST.get("secret", ""))
sigRebootDevice(device.serial, None, False)
device.reboot = False
device.save()
return render(request, "manager/hosts", {"device": device})
@csrf_exempt
def update(request):
FWDIR = "/opt/vpnmanager/images/"
device = get_object_or_404(Device, secret=request.POST.get("secret", ""))
try:
process = mkfirmware.apply_async((device, FWDIR), serializer='pickle')
if not process.get(timeout=300):
return HttpResponse(status=503)
except TimeoutError:
return HttpResponse(status=503)
sigUpdateDevice(device.serial, None, False)
device.update = False
device.save()
with open("%s/%s.bin" % (FWDIR, device.id), "rb") as download:
response = HttpResponse(
download.read(),
content_type="application/octet-stream"
)
response['Content-Disposition'] = (
'inline; '
'filename=%s.bin' % device.serial
)
return response
def ping(request, device_id):
if request.user.is_authenticated:
try:
device = Device.objects.get(
id=device_id,
organization__in=request.user.organization_set.all()
)
except Exception:
device = None
ajax = '{\n "status": '
if not device:
status = "-1"
else:
tz = timezone.get_current_timezone()
if device.lasttime:
naive = timezone.make_naive(device.lasttime, tz)
tt = naive.timetuple()
lasttime = str(int(time.mktime(tt)) * 1000)
else:
lasttime = None
if device.lastbeat:
naive = timezone.make_naive(device.lastbeat, tz)
tt = naive.timetuple()
lastbeat = str(int(time.mktime(tt)) * 1000)
else:
lastbeat = None
status = None
try:
if device.curip:
try:
socket.inet_aton(device.curip)
except Exception:
status = -3
else:
if device.lasttime:
diff = timezone.now() - device.lasttime
secs = diff.total_seconds()
if secs < 300:
cmd = "ping -c1 -w1 %s >/dev/null 2>&1"
status = os.system(cmd % device.curip)
if not os.WEXITSTATUS(status):
status = 1
if not status:
if (not device.lasttime) or secs > 120:
diff = timezone.now() - device.lastbeat
secs = diff.total_seconds()
if secs < 60:
status = 2
if not status:
status = 0
except Exception as e:
status = str(e)
status += "-4"
finally:
ajax += str(status)
ajax += ',\n "serial": "%s"' % device.serial
ajax += ',\n "name": "%s"' % (device.name or "")
ajax += ',\n "ip": "%s"' % (device.curip or "")
ajax += ',\n "time": "%s"' % lasttime
ajax += ',\n "lastbeat": "%s"' % lastbeat
ajax += ',\n "reboot": %i' % (1 if device.reboot else 0)
ajax += ',\n "update": %i' % (1 if device.update else 0)
ajax += ',\n "network": {'
ajax += '\n "intip": "%s"' % device.network.intip
ajax += ',\n "extip": "%s"' % device.network.extip
ajax += ',\n "name": "%s"' % (device.network.name or "")
ajax += '\n }'
else:
ajax += "-2"
ajax += "\n}"
return HttpResponse(ajax, content_type="application/json")
@login_required
def devices(request, code=200, exception=None):
return render(
request,
"manager/index.html",
{
"title": "Device Administration",
"exception": exception
},
status=code
)
@login_required
def editdevice(request, device_id):
post = request.method == "POST"
device = get_object_or_404(
Device,
id=device_id,
organization__in=request.user.organization_set.all()
)
subnets = Network.objects.filter(
organization=device.organization
)
wifis = Wifi.objects.filter(
organization=device.organization
)
form = DeviceForm(
request.POST if post else None,
instance=device
)
form.fields["mac"].disabled = True
form.fields["serial"].disabled = True
form.fields["secret"].disabled = True
form.fields["model"].disabled = True
form.fields["password"].disabled = True
if post and form.is_valid():
form.save()
return redirect(reverse("devices"))
return render(
request,
"manager/form.html",
{
"title": "Edit Device",
"form": form
}
)
@user_passes_test(is_superuser)
def makeuser(request):
if request.POST.get("username", ""):
user = User.objects.create_user(
username=request.POST.get("username", ""),
password=str(uuid.uuid4().hex),
first_name=request.POST.get("firstname", ""),
last_name=request.POST.get("lastname", ""),
is_staff=request.POST.get("staff", "0") == "True",
is_superuser=request.POST.get("superuser", "0") == "True",
email=request.POST.get("email", "")
)
user.organization_set.set(set(request.POST.getlist("orga", [])))
form = PasswordResetForm({"email": user.email})
if form.is_valid():
form.save(
request=request,
use_https=True,
email_template_name='registration/add_user.html')
return redirect(reverse("users"))
else:
return render(request, "manager/adduser.html", {"title": "Add User"})
@login_required
def edituser(request, user_id):
if request.user.is_staff or request.user.id == user_id:
orgas = request.user.organization_set.all()
user = User.objects.distinct().get(id=user_id, organization__in=orgas)
if not user:
return redirect(reverse("users"))
if request.POST.get("form", ""):
newfirst = request.POST.get("firstname", "")
newlast = request.POST.get("lastname", "")
if newlast != user.last_name or newfirst != user.first_name:
sigRenameUser(
user.username,
request.user.username,
"%s %s" % (user.first_name, user.last_name),
"%s %s" % (newfirst, newlast)
)
user.first_name = request.POST.get("firstname", "")
user.last_name = request.POST.get("lastname", "")
if request.user.is_staff or request.user.is_superuser:
newstaff = request.POST.get("staff", "0") == "True"
if newstaff != user.is_staff:
sigStaffUser(
user.username,
request.user.username,
newstaff
)
user.is_staff = newstaff
if request.user.is_superuser:
newsuper = request.POST.get("superuser", "0") == "True"
if newsuper != user.is_superuser:
sigSuperUser(
user.username,
request.user.username,
newsuper)
user.is_superuser = newsuper
neworgas = set(request.POST.getlist("orga", []))
oldorgas = set(user.organization_set.all())
if neworgas != oldorgas:
sigOrgaUser(
user.username,
request.user.username,
oldorgas,
neworgas
)
user.organization_set.set(neworgas)
newmail = request.POST.get("email", "")
if newmail != user.email:
sigMailUser(
user.username,
request.user.username,
user.email,
newmail
)
user.email = newmail
user.save()
return redirect(reverse("users"))
return render(
request,
"manager/edituser.html",
{
"title": "Edit User",
"auser": user
}
)
return redirect(
reverse(
"two_factor:login",
kwargs={
'next': request.path
}
)
)
@login_required
def editwifi(request, wifi_id):
userorgas = request.user.organization_set.all()
wifi = get_object_or_404(
Wifi,
id=wifi_id,
organization__in=userorgas
)
form = WifiForm(
request.POST if request.method == "POST" else None,
instance=wifi
)
form.fields["organization"].queryset = userorgas
if request.method == "POST" and form.is_valid():
form.save()
return redirect(reverse("wifi"))
return render(
request,
"manager/form.html",
{
"title": "Edit WiFi",
"form": form
}
)
@user_passes_test(is_superuser)
def getconfig(request, device_id):
FWDIR = "/opt/vpnmanager/images/"
device = get_object_or_404(Device, id=device_id)
files = glob.glob("%s/%s.bin" % (FWDIR, device_id))
if not files:
mtime = os.path.getmtime(files[0])
tz = timezone.get_current_timezone()
if not datetime.fromtimestamp(mtime, tz) > device.model.firmware:
work = mkfirmware.apply_async((device, FWDIR), serializer='pickle')
if not work.get(timeout=300):
return HttpResponse(status=503)
sigUpdateDevice(device.serial, None, False)
device.update = False
device.save()
with open("%s/%s.bin" % (FWDIR, device.id), "rb") as download:
response = HttpResponse(
download.read(),
content_type="application/octet-stream"
)
response['Content-Disposition'] = (
'inline; '
'filename=%s.bin' % device.serial
)
return response
@login_required
def rebootdevice(request, device_id):
device = get_object_or_404(
Device,
id=device_id,
organization__in=request.user.organization_set.all()
)
if not device.reboot:
sigRebootDevice(device.serial, request.user.username, True)
device.reboot = True
device.save()
return redirect(reverse("devices"))
@user_passes_test(is_staff)
def updatedevice(request, device_id):
device = get_object_or_404(
Device,
id=device_id,
organization__in=request.user.organization_set.all()
)
if not device.update:
sigUpdateDevice(device.serial, request.user.username, True)
device.update = True
device.save()
return redirect(reverse("devices"))
@user_passes_test(is_superuser)
def deletedevice(request, device_id):
CADIR = "/etc/openvpn/ca/"
BEFORE = os.getcwd()
device = get_object_or_404(Device, id=device_id)
os.chdir(CADIR)
execute(CADIR + "/revoke " + device.serial, shell=True)
os.system("rm " + CADIR + "/keys/" + device.serial + ".{crt,csr,key}")
os.chdir(BEFORE)
device.delete()
return redirect(reverse("devices"))
@user_passes_test(is_staff)
def deletewifi(request, wifi_id):
wifi = get_object_or_404(
Wifi,
id=wifi_id,
organization__in=request.user.organization_set.all()
)
wifi.delete()
return redirect(reverse("wifi"))
@user_passes_test(is_superuser)
def deleteuser(request, user_id):
user = get_object_or_404(User, id=user_id)
user.delete()
return redirect(reverse("users"))
@user_passes_test(is_superuser)
def deletenetwork(request, network_id):
network = get_object_or_404(Network, id=network_id)
network.delete()
return redirect(reverse("networks"))
@user_passes_test(is_superuser)
def deleteorga(request, orga_id):
orga = get_object_or_404(Organization, id=orga_id)
orga.delete()
return redirect(reverse("organizations"))
@login_required
def makewifi(request):
post = request.method == "POST"
postdata = request.POST if post else None
organization = request.user.userstatus.orga
initial = {"organization": organization} if not post else None
form = WifiForm(postdata, initial=initial)
form.fields["organization"].queryset = request.user.organization_set.all()
if request.method == "POST" and form.is_valid():
form.save()
return redirect(reverse("wifi"))
return render(
request,
"manager/form.html",
{
"title": "Add WiFi",
"form": form
}
)
@user_passes_test(is_superuser)
def makenetwork(request):
if request.method == "POST":
form = NetworkForm(request.POST)
if form.is_valid():
form.save()
return redirect(reverse("networks"))
else:
form = NetworkForm()
return render(
request,
"manager/form.html",
{
"title": "Add Network",
"form": form
}
)
@user_passes_test(is_superuser)
def editnetwork(request, network_id):
net = get_object_or_404(Network, id=network_id)
postdata = request.POST if request.method == "POST" else None
form = NetworkForm(postdata, instance=net)
form.fields["intip"].disabled = True
form.fields["extip"].disabled = True
if request.method == "POST" and form.is_valid():
form.save()
return redirect(reverse("networks"))
return render(
request,
"manager/form.html",
{
"title": "Edit Network",
"form": form
}
)
@login_required
def setactiveorga(request, orga_id):
request.user.userstatus.orga = get_object_or_404(
Organization,
id=orga_id,
users=request.user
)
request.user.userstatus.save()
return HttpResponse("")
@user_passes_test(is_superuser)
def makeorganization(request):
if request.method == "POST":
form = OrgaForm(request.POST)
if form.is_valid():
orga = form.save()
request.user.organization_set.add(orga)
Network.objects.get(intip="No VPN").organization.add(orga)
return redirect(reverse("organizations"))
else:
form = OrgaForm()
return render(
request,
"manager/form.html",
{
"title": "Add Organization",
"form": form
}
)
@user_passes_test(is_superuser)
def editorganization(request, orga_id):
orga = get_object_or_404(Organization, id=orga_id)
if request.method == "POST":
form = OrgaForm(request.POST, instance=orga)
if form.is_valid():
form.save()
return redirect(reverse("organizations"))
else:
form = OrgaForm(instance=orga)
return render(
request,
"manager/form.html",
{
"title": "Change Organization",
"form": form
}
)
@user_passes_test(is_superuser)
def makedevice(request):
CADIR = "/etc/openvpn/ca/"
CONFIGDIR = "/etc/openvpn/client-configs/"
BEFORE = os.getcwd()
device_serial = request.POST.get("serial", "").replace(" ", "_")
device_name = request.POST.get("name", "")
device_organization = request.POST.get("organization", "")
device_model = request.POST.get("model", "")
device_ssid = request.POST.get("ssid", device_serial)
device_key = request.POST.get("key", "")
device_mac = request.POST.get("mac", "")
if not device_serial:
orga = Organization.objects.all()
models = Model.objects.all()
return render(request, "manager/add.html",
{
"title": "Add Device",
"organizations": orga,
"models": models
}
)
if glob.glob(CADIR + "/keys/" + device_serial + "*"):
return HttpResponse("Key already exists.")
os.chdir(CADIR)
if execute(CADIR + "/generate-key " + device_serial, shell=True):
os.chdir(BEFORE)
return HttpResponse("Error generating key.")
if glob.glob(CONFIGDIR + "/files/" + device_serial + "*"):
os.chdir(BEFORE)
return HttpResponse("Configuration file already exists.")
os.chdir(CONFIGDIR)
if execute(CONFIGDIR + "/make_config " + device_serial, shell=True):
os.chdir(BEFORE)
return HttpResponse("Error generating config file.")
os.chdir(BEFORE)
with open(CONFIGDIR + "/files/" + device_serial + ".ovpn") as vpnfile:
vpnconfig = vpnfile.read()
organization = Organization.objects.get(id=device_organization)
device = Device.objects.create(
serial=device_serial,
name=device_name,
ssid=device_ssid,
key=device_key,
model=Model.objects.filter(id=device_model)[0],
network=Network.objects.filter(intip="No VPN")[0],
organization=organization,
vpnconfig=vpnconfig,
mac=device_mac or None,
changed=timezone.now()
)
return redirect(reverse("devices"))
@receiver(post_save, sender=settings.AUTH_USER_MODEL)
def createUserStatus(sender, instance, created, **kwargs):
if created:
UserStatus.objects.create(user=instance)
@receiver(post_save, sender=settings.AUTH_USER_MODEL)
def saveUserStatus(sender, instance, **kwargs):
try:
instance.userstatus.save()
except Exception:
UserStatus.objects.create(user=instance)
def errorhandler(request, code, exception):
if code == 403:
exception.human = ("You have no permission "
"to access the requested page.")
elif code == 400:
exception.human = ("Something seems to be wrong with "
"the request you sent. Please try again.")
elif code == 404:
exception.human = ("Sorry, we were unable to find the page you "
"requested. Please check the address and try again."
)
elif code == 500:
exception.human = ("Sorry, something seems to be wrong on our end. "
"Please try again later or contact support if "
"the problem persists.")
return devices(request, code=code, exception=exception)
def handler400(request, exception):
return errorhandler(request, 400, exception)
def handler403(request, exception):
return errorhandler(request, 403, exception)
def handler404(request, exception):
return errorhandler(
request,
404,
exception
)
def handler500(request):
return errorhandler(
request,
500,
Exception("Please check server logs for additional information.")
)