Compare commits

..

6 Commits

33 changed files with 2170 additions and 2085 deletions

View File

@ -11,121 +11,121 @@ import dns.resolver
# FIXME: DNS timeouts # FIXME: DNS timeouts
def compareRecords(rrset, expected): def compareRecords(rrset, expected):
result = { result = {
"nameMissing": [], "nameMissing": [],
"rrMissing": [], "rrMissing": [],
"rrExtra": [], "rrExtra": [],
} }
for domain, rrtype, content in expected: for domain, rrtype, content in expected:
for rrrec in rrset: for rrrec in rrset:
if domain == rrrec.name.to_text() and dns.rdatatype.from_text(rrtype) == rrrec.rdtype: if domain == rrrec.name.to_text() and dns.rdatatype.from_text(rrtype) == rrrec.rdtype:
for name in content: for name in content:
if name not in map(lambda _x: _x.to_text(), rrrec.items): if name not in map(lambda _x: _x.to_text(), rrrec.items):
# record missing # record missing
result["rrMissing"].append((domain, rrtype, name)) result["rrMissing"].append((domain, rrtype, name))
for item in rrrec.items: for item in rrrec.items:
if item.to_text() not in content: if item.to_text() not in content:
# superfluous record # superfluous record
result["rrExtra"].append((domain, rrtype, item.to_text())) result["rrExtra"].append((domain, rrtype, item.to_text()))
break break
else: else:
# domain + rr nicht in nameserver # domain + rr nicht in nameserver
result["nameMissing"].append((domain, rrtype)) result["nameMissing"].append((domain, rrtype))
success = not any(len(_x) > 0 for _x in result.values()) success = not any(len(_x) > 0 for _x in result.values())
return success, result return success, result
def dnsQuery(domain, rrType, nameserverIp): def dnsQuery(domain, rrType, nameserverIp):
dname = dns.name.from_text(domain) dname = dns.name.from_text(domain)
req = dns.message.make_query(dname, dns.rdatatype.from_text(rrType)) req = dns.message.make_query(dname, dns.rdatatype.from_text(rrType))
resp = dns.query.udp(req, nameserverIp, timeout=2.0) resp = dns.query.udp(req, nameserverIp, timeout=2.0)
if resp.rcode() != dns.rcode.NXDOMAIN: if resp.rcode() != dns.rcode.NXDOMAIN:
rrset = resp.answer + resp.authority + resp.additional rrset = resp.answer + resp.authority + resp.additional
return True, rrset return True, rrset
else: else:
return False, [] return False, []
def checkDomain(domain, tldNameserver, nameservers): def checkDomain(domain, tldNameserver, nameservers):
result = [] result = []
if nameservers.count() == 0: if nameservers.count() == 0:
return [("err", "Domain %s has no nameservers attached to it, nothing to check" % domain)] return [("err", "Domain %s has no nameservers attached to it, nothing to check" % domain)]
# build record set # build record set
nsRecords = [(domain, "NS", list(ns.name for ns in nameservers))] nsRecords = [(domain, "NS", list(ns.name for ns in nameservers))]
glueRecords = [] glueRecords = []
for ns in nameservers: for ns in nameservers:
if ns.name.endswith("." + domain): if ns.name.endswith("." + domain):
if ns.glueIPv4 or ns.glueIPv6: if ns.glueIPv4 or ns.glueIPv6:
if ns.glueIPv4: if ns.glueIPv4:
glueRecords.append((ns.name, "A", [ns.glueIPv4])) glueRecords.append((ns.name, "A", [ns.glueIPv4]))
if ns.glueIPv6: if ns.glueIPv6:
glueRecords.append((ns.name, "AAAA", [ns.glueIPv6])) glueRecords.append((ns.name, "AAAA", [ns.glueIPv6]))
else: else:
result.append(("err", "Nameserver %s is under domain %s, but has no glue entries." % (ns.name, domain))) result.append(("err", "Nameserver %s is under domain %s, but has no glue entries." % (ns.name, domain)))
# 1. TLD nameserver # 1. TLD nameserver
try: try:
found, rrset = dnsQuery(domain, "ANY", tldNameserver) found, rrset = dnsQuery(domain, "ANY", tldNameserver)
if found: if found:
success, errors = compareRecords(rrset, nsRecords + glueRecords) success, errors = compareRecords(rrset, nsRecords + glueRecords)
if success: if success:
result.append(("succ", "All records present in TLD nameserver")) result.append(("succ", "All records present in TLD nameserver"))
else: else:
result.append(("err", "Record mismatch between TLD nameserver and WHOIS database", errors)) result.append(("err", "Record mismatch between TLD nameserver and WHOIS database", errors))
else: else:
result.append(("err", "Domain %s not found in TLD nameserver" % (domain,))) result.append(("err", "Domain %s not found in TLD nameserver" % (domain,)))
except (dns.exception.Timeout, OSError): except (dns.exception.Timeout, OSError):
result.append(("err", "TLD nameserver is currently not reachable")) result.append(("err", "TLD nameserver is currently not reachable"))
# find other records... # find other records...
# 2. your nameservers # 2. your nameservers
for ns in nameservers: for ns in nameservers:
addr = None addr = None
if ns.glueIPv4: if ns.glueIPv4:
addr = ns.glueIPv4 addr = ns.glueIPv4
elif ns.glueIPv6: elif ns.glueIPv6:
addr = ns.glueIPv6 addr = ns.glueIPv6
else: else:
for rrType in ("A", "AAAA"): for rrType in ("A", "AAAA"):
try: try:
r = dns.resolver.Resolver() r = dns.resolver.Resolver()
r.timeout = 2.0 r.timeout = 2.0
q = r.query(ns.name, rdtype=dns.rdatatype.from_text(rrType)) q = r.query(ns.name, rdtype=dns.rdatatype.from_text(rrType))
addr = q.response.answer[0].items[0].address addr = q.response.answer[0].items[0].address
except (dns.exception.DNSException, OSError): except (dns.exception.DNSException, OSError):
pass pass
if addr: if addr:
err = False err = False
errDict = {"nameMissing": [], "rrMissing": [], "rrExtra": []} errDict = {"nameMissing": [], "rrMissing": [], "rrExtra": []}
try: try:
for rec in (nsRecords + glueRecords): for rec in (nsRecords + glueRecords):
found, rrset = dnsQuery(rec[0], rec[1], addr) found, rrset = dnsQuery(rec[0], rec[1], addr)
#success, errors = compareRecords(rrset, nsRecords + glueRecords) #success, errors = compareRecords(rrset, nsRecords + glueRecords)
success, errors = compareRecords(rrset, [rec]) success, errors = compareRecords(rrset, [rec])
if not success: if not success:
err = True err = True
for k in errors.keys(): for k in errors.keys():
errDict[k].extend(errors[k]) errDict[k].extend(errors[k])
if not err: if not err:
result.append(("succ", "Nameserver %s is configured correctly" % ns.name)) result.append(("succ", "Nameserver %s is configured correctly" % ns.name))
else: else:
result.append(("err", "Nameserver %s (via %s) recordset does not match the database" % (ns.name, addr), errDict)) result.append(("err", "Nameserver %s (via %s) recordset does not match the database" % (ns.name, addr), errDict))
except (dns.exception.DNSException, OSError): except (dns.exception.DNSException, OSError):
result.append(("err", "Nameserver %s is not reachable (via %s)" % (ns.name, addr))) result.append(("err", "Nameserver %s is not reachable (via %s)" % (ns.name, addr)))
else: else:
result.append(("err", "Can't resolv an ip address for nameserver %s" % ns.name)) result.append(("err", "Can't resolv an ip address for nameserver %s" % ns.name))
return result return result

View File

@ -7,13 +7,13 @@ from django.conf.urls import url
from . import views as api_views from . import views as api_views
urlpatterns = [ urlpatterns = [
url(r'asblock/free-as/$', api_views.asblockFreeAS, name='asblock-free-as'), url(r'asblock/free-as/$', api_views.asblockFreeAS, name='asblock-free-as'),
url(r'asblock/free-subnet/$', api_views.freeSubnet, name='inetnum-free-subnet'), url(r'asblock/free-subnet/$', api_views.freeSubnet, name='inetnum-free-subnet'),
url(r'inetnum/get-subnet/$', api_views.getSubnet, name='inetnum-get-subnet'), url(r'inetnum/get-subnet/$', api_views.getSubnet, name='inetnum-get-subnet'),
url(r'domain/check/$', api_views.checkDomain, name='domain-check'), url(r'domain/check/$', api_views.checkDomain, name='domain-check'),
url(r'rzone/check/$', api_views.checkRzone, name='reversezone-check'), url(r'rzone/check/$', api_views.checkRzone, name='reversezone-check'),
url(r'roa/$', api_views.getROA, name='get-roa'), url(r'roa/$', api_views.getROA, name='get-roa'),
] ]

View File

@ -19,219 +19,219 @@ import ipaddress
@login_required @login_required
def asblockFreeAS(request): def asblockFreeAS(request):
ret = { ret = {
"success": False, "success": False,
"errorMsg": None, "errorMsg": None,
"number": -1, "number": -1,
} }
try: try:
blockName = request.GET.get('block', None) blockName = request.GET.get('block', None)
if not blockName: if not blockName:
raise ValidationError("No block given") raise ValidationError("No block given")
try: try:
mnts = request.user.maintainer_set.all() mnts = request.user.maintainer_set.all()
block = ASBlock.objects.filter(Q(mnt_by__in=mnts) | Q(mnt_lower__in=mnts)).distinct().get(handle=blockName) block = ASBlock.objects.filter(Q(mnt_by__in=mnts) | Q(mnt_lower__in=mnts)).distinct().get(handle=blockName)
if block.asblock_set.count() > 0: if block.asblock_set.count() > 0:
raise ValidationError("AS Block already has sub AS Blocks") raise ValidationError("AS Block already has sub AS Blocks")
if block.asnumber_set.count() > 0: if block.asnumber_set.count() > 0:
num = block.asnumber_set.order_by("-number")[0].number + 1 num = block.asnumber_set.order_by("-number")[0].number + 1
if num > block.asEnd: if num > block.asEnd:
num = None num = None
for n in range(block.asBegin, block.asEnd + 1): for n in range(block.asBegin, block.asEnd + 1):
try: try:
ASNumber.objects.get(number=n) ASNumber.objects.get(number=n)
except ASNumber.DoesNotExist: except ASNumber.DoesNotExist:
num = n num = n
break break
if not num: if not num:
raise ValidationError("No free AS Number in block") raise ValidationError("No free AS Number in block")
ret["number"] = num ret["number"] = num
else: else:
ret["number"] = block.asBegin ret["number"] = block.asBegin
except ASBlock.DoesNotExist: except ASBlock.DoesNotExist:
raise ValidationError("Could not get AS Block") raise ValidationError("Could not get AS Block")
ret["success"] = True ret["success"] = True
except ValidationError as e: except ValidationError as e:
ret["errorMsg"] = e.message ret["errorMsg"] = e.message
return JsonResponse(ret) return JsonResponse(ret)
@login_required @login_required
def freeSubnet(request): def freeSubnet(request):
ret = { ret = {
"success": False, "success": False,
"errorMsg": None, "errorMsg": None,
"network": None, "network": None,
} }
try: try:
parentRangeName = request.GET.get('parentRange', None) parentRangeName = request.GET.get('parentRange', None)
if not parentRangeName: if not parentRangeName:
raise ValidationError("No subnet given") raise ValidationError("No subnet given")
parentRange = None parentRange = None
try: try:
mnts = request.user.maintainer_set.all() mnts = request.user.maintainer_set.all()
parentRange = InetNum.objects.filter(Q(mnt_by__in=mnts) | Q(mnt_lower__in=mnts)).distinct().get(handle=parentRangeName) parentRange = InetNum.objects.filter(Q(mnt_by__in=mnts) | Q(mnt_lower__in=mnts)).distinct().get(handle=parentRangeName)
except InetNum.DoesNotExist: except InetNum.DoesNotExist:
raise ValidationError("Parent range does not exist / is not maintained by you") raise ValidationError("Parent range does not exist / is not maintained by you")
prefixLen = 0 prefixLen = 0
try: try:
prefixLen = request.GET.get("prefixLen", None) prefixLen = request.GET.get("prefixLen", None)
if not prefixLen: if not prefixLen:
if parentRange.protocol == InetNum.IPv4: if parentRange.protocol == InetNum.IPv4:
prefixLen = 27 prefixLen = 27
else: else:
prefixLen = 60 prefixLen = 60
prefixLen = int(prefixLen) prefixLen = int(prefixLen)
if prefixLen < 8 or \ if prefixLen < 8 or \
(parentRange.protocol == InetNum.IPv4 and prefixLen > 32) or \ (parentRange.protocol == InetNum.IPv4 and prefixLen > 32) or \
(parentRange.protocol == InetNum.IPv6 and prefixLen > 128): (parentRange.protocol == InetNum.IPv6 and prefixLen > 128):
raise ValidationError("Given prefix length is out of range") raise ValidationError("Given prefix length is out of range")
except ValueError: except ValueError:
raise ValidationError("PrefixLen is not a number") raise ValidationError("PrefixLen is not a number")
usableNet = None usableNet = None
# FIXME: use first biggest usable netblock... # FIXME: use first biggest usable netblock...
#biggestNet = parentRange.inetnum_set.order_by("-address") #biggestNet = parentRange.inetnum_set.order_by("-address")
#if biggestNet #if biggestNet
# candidateNet = # candidateNet =
# and (biggestNet.getNetwork().broadcast_address.: # and (biggestNet.getNetwork().broadcast_address.:
# try using next network in range # try using next network in range
# ordering does not work, as order_by sorts alphabetically # ordering does not work, as order_by sorts alphabetically
# ==> the set is short, we can iterate through all. no sense in unpacking the SQL magic # ==> the set is short, we can iterate through all. no sense in unpacking the SQL magic
#biggestNets = parentRange.inetnum_set.order_by("-address") #biggestNets = parentRange.inetnum_set.order_by("-address")
biggestNet = None biggestNet = None
for ipRange in parentRange.inetnum_set.all(): for ipRange in parentRange.inetnum_set.all():
if not biggestNet or ipRange.getNetwork() > biggestNet: if not biggestNet or ipRange.getNetwork() > biggestNet:
biggestNet = ipRange.getNetwork() biggestNet = ipRange.getNetwork()
if biggestNet: if biggestNet:
candidateNet = ipaddress.ip_network("%s/%s" % (biggestNet.broadcast_address + 1, biggestNet.prefixlen)) candidateNet = ipaddress.ip_network("%s/%s" % (biggestNet.broadcast_address + 1, biggestNet.prefixlen))
print("biggest net", biggestNet, "candidate", candidateNet) print("biggest net", biggestNet, "candidate", candidateNet)
if candidateNet.network_address in parentRange.getNetwork(): if candidateNet.network_address in parentRange.getNetwork():
usableNet = candidateNet usableNet = candidateNet
# check if there are still networks left in range # check if there are still networks left in range
if not usableNet: if not usableNet:
# search for free network # search for free network
nets = list(parentRange.getNetwork().subnets()) nets = list(parentRange.getNetwork().subnets())
for subRange in parentRange.inetnum_set.all(): for subRange in parentRange.inetnum_set.all():
newNet = None newNet = None
for net in nets: for net in nets:
if subRange.getNetwork().network_address in net: if subRange.getNetwork().network_address in net:
newNet = net newNet = net
if not newNet: if not newNet:
# critical error, we want a 500 here # critical error, we want a 500 here
raise ValueError("Subnet not in range") raise ValueError("Subnet not in range")
nets.remove(newNet) nets.remove(newNet)
nets.extend(newNet.address_exclude(subRange.getNetwork())) nets.extend(newNet.address_exclude(subRange.getNetwork()))
nets = sorted(nets) nets = sorted(nets)
for net in nets: for net in nets:
if net.prefixlen <= prefixLen: if net.prefixlen <= prefixLen:
usableNet = net usableNet = net
break break
if not usableNet: if not usableNet:
raise ValidationError("No space left in given range") raise ValidationError("No space left in given range")
ret["network"] = "%s/%s" % (usableNet.network_address, prefixLen) ret["network"] = "%s/%s" % (usableNet.network_address, prefixLen)
ret["success"] = True ret["success"] = True
except ValidationError as e: except ValidationError as e:
ret["errorMsg"] = e.message ret["errorMsg"] = e.message
return JsonResponse(ret) return JsonResponse(ret)
@login_required @login_required
def getSubnet(request): def getSubnet(request):
ret = { ret = {
"success": False, "success": False,
"errorMsg": None, "errorMsg": None,
"network": None, "network": None,
} }
try: try:
netName = request.GET.get('net', None) netName = request.GET.get('net', None)
mnts = request.user.maintainer_set.all() mnts = request.user.maintainer_set.all()
nets = InetNum.objects.filter(Q(mnt_by__in=mnts) | Q(mnt_lower__in=mnts)).distinct() nets = InetNum.objects.filter(Q(mnt_by__in=mnts) | Q(mnt_lower__in=mnts)).distinct()
net = nets.get(handle=netName) net = nets.get(handle=netName)
ret["success"] = True ret["success"] = True
ret["network"] = net.prefix() ret["network"] = net.prefix()
except InetNum.DoesNotExist: except InetNum.DoesNotExist:
ret["errorMsg"] = "Chosen network does not exist" ret["errorMsg"] = "Chosen network does not exist"
return JsonResponse(ret) return JsonResponse(ret)
@login_required @login_required
def checkDomain(request): def checkDomain(request):
ret = { ret = {
"success": False, "success": False,
"errorMsg": None, "errorMsg": None,
"domain": None, "domain": None,
"result": None, "result": None,
} }
try: try:
domainName = Domain.fixName(request.GET.get('domain', '')) domainName = Domain.fixName(request.GET.get('domain', ''))
domain = Domain.objects.get(name=domainName) domain = Domain.objects.get(name=domainName)
#if not domain.canEdit(request.user): #if not domain.canEdit(request.user):
# raise Domain.DoesNotExist() # raise Domain.DoesNotExist()
ret["success"] = True ret["success"] = True
ret["domain"] = domain.name ret["domain"] = domain.name
# FIXME: change this if we ever have more than one... # FIXME: change this if we ever have more than one...
ret["result"] = helperCheckDomain(domain.name, TLD_NAMESERVERS[0], domain.nameservers.all()) ret["result"] = helperCheckDomain(domain.name, TLD_NAMESERVERS[0], domain.nameservers.all())
except Domain.DoesNotExist: except Domain.DoesNotExist:
ret["errorMsg"] = "Domain does not exist" ret["errorMsg"] = "Domain does not exist"
return JsonResponse(ret) return JsonResponse(ret)
@login_required @login_required
def checkRzone(request): def checkRzone(request):
ret = { ret = {
"success": False, "success": False,
"errorMsg": None, "errorMsg": None,
"domain": None, "domain": None,
"result": None, "result": None,
} }
try: try:
rzonePk = int(request.GET.get('domain', '')) rzonePk = int(request.GET.get('domain', ''))
rzone = ReverseZone.objects.get(pk=rzonePk) rzone = ReverseZone.objects.get(pk=rzonePk)
#if not rzone.canEdit(request.user): #if not rzone.canEdit(request.user):
# raise ReverseZone.DoesNotExist() # raise ReverseZone.DoesNotExist()
ret["success"] = True ret["success"] = True
# FIXME: change this if we ever have more than one... # FIXME: change this if we ever have more than one...
ret["result"] = helperCheckDomain(rzone.getZone(), TLD_NAMESERVERS[0], rzone.nameservers.all()) ret["result"] = helperCheckDomain(rzone.getZone(), TLD_NAMESERVERS[0], rzone.nameservers.all())
except (ReverseZone.DoesNotExist, ValueError): except (ReverseZone.DoesNotExist, ValueError):
ret["errorMsg"] = "ReverseZone does not exist" ret["errorMsg"] = "ReverseZone does not exist"
return JsonResponse(ret) return JsonResponse(ret)
def getROA(request): def getROA(request):
roa = {} roa = {}
for asn in ASNumber.objects.all(): for asn in ASNumber.objects.all():
nets = [] nets = []
for net in asn.inetnum_set.all(): for net in asn.inetnum_set.all():
nets.append(net.prefix()) nets.append(net.prefix())
if nets: if nets:
roa[asn.number] = nets roa[asn.number] = nets
return JsonResponse(roa) return JsonResponse(roa)

View File

@ -25,179 +25,181 @@ __VERSION__ = '0.1'
def _parser(): def _parser():
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser()
#prog='foo',
#description='do some awesome foo',
)
parser.add_argument("--pdns-host", default="127.0.0.1", help="PDNS host") parser.add_argument("--pdns-host", default="127.0.0.1", help="PDNS host")
parser.add_argument("--pdns-port", default=8081, help="PDNS port") parser.add_argument("--pdns-port", default=8081, help="PDNS port")
parser.add_argument("-c", "--config", default=None, type=argparse.FileType("r"), help="Path to config file (default path: ./dns-sync.conf, /etc/dns-sync.conf)") parser.add_argument("-c", "--config", default=None, type=argparse.FileType("r"), help="Path to config file (default path: ./dns-sync.conf, /etc/dns-sync.conf)")
parser.add_argument("--api-key", default=None, help="PDNS API key") parser.add_argument("--api-key", default=None, help="PDNS API key")
parser.add_argument("--version", action="version", version="%(prog)s " + __VERSION__) parser.add_argument("--version", action="version", version="%(prog)s " + __VERSION__)
return parser
return parser
def mergeDomains(zoneData, pdnsData): def mergeDomains(zoneData, pdnsData):
rrAdd = [] rrAdd = []
rrData = pdnsData['rrsets'] rrData = pdnsData['rrsets']
for domain, rrType, records in zoneData: for domain, rrType, records in zoneData:
found = False found = False
pdnsDom = list(filter(lambda _x: _x['name'] == domain and _x['type'] == rrType, rrData)) pdnsDom = list(filter(lambda _x: _x['name'] == domain and _x['type'] == rrType, rrData))
if len(pdnsDom) > 0: if len(pdnsDom) > 0:
rrSet = set(_x['content'] for _x in pdnsDom[0]['records']) rrSet = set(_x['content'].lower() for _x in pdnsDom[0]['records'])
if rrSet == set(records): if rrSet == set(record.lower() for record in records):
found = True found = True
if not found: if not found:
# new domain! # new domain!
rrAdd.append({ rrAdd.append({
"name": domain, "name": domain,
"type": rrType, "type": rrType,
"ttl": 60*60, "ttl": 60*60,
"changetype": "REPLACE", "changetype": "REPLACE",
"records": [{"content": record, "disabled": False} for record in records], "records": [{"content": record, "disabled": False} for record in records],
}) })
return rrAdd return rrAdd
def removeOldDomains(zoneData, pdnsData): def removeOldDomains(zoneData, pdnsData):
rrDel = [] rrDel = []
#print("zone data", zoneData) #print("zone data", zoneData)
#print("pdnsData", pdnsData) #print("pdnsData", pdnsData)
for entry in pdnsData['rrsets']: for entry in pdnsData['rrsets']:
# search for name/type in domain dict. if non-existtant mark for deletion # search for name/type in domain dict. if non-existtant mark for deletion
# this could be much more efficient with a dict! name: [rrset...] # this could be much more efficient with a dict! name: [rrset...]
if not any(entry['name'] == _x[0] and entry['type'] == _x[1] for _x in zoneData): if not any(entry['name'] == _x[0] and entry['type'] == _x[1] for _x in zoneData):
rrDel.append({ rrDel.append({
"changetype": "DELETE", "changetype": "DELETE",
"name": entry["name"], "name": entry["name"],
"type": entry["type"], "type": entry["type"],
}) })
return rrDel
return rrDel
def handleNameserver(ns, servers, usedNameservers, domains): def handleNameserver(ns, servers, usedNameservers, domains):
servers.append(ns.name) servers.append(ns.name)
if ns.name not in usedNameservers and (ns.glueIPv4 or ns.glueIPv6): if ns.name not in usedNameservers and (ns.glueIPv4 or ns.glueIPv6):
usedNameservers.append(ns.name) usedNameservers.append(ns.name)
if ns.glueIPv4: if ns.glueIPv4:
domains.append((ns.name, "A", [ns.glueIPv4])) domains.append((ns.name, "A", [ns.glueIPv4]))
if ns.glueIPv6: if ns.glueIPv6:
domains.append((ns.name, "AAAA", [ns.glueIPv6])) domains.append((ns.name, "AAAA", [ns.glueIPv6]))
def getDomainsFromQueryset(qs, zone, glueRecords, usedNameservers, v4reverse=False): def getDomainsFromQueryset(qs, zone, glueRecords, usedNameservers, v4reverse=False):
for domain in qs: for domain in qs:
servers = [] servers = []
for ns in domain.nameservers.all(): for ns in domain.nameservers.all():
servers.append(ns.name) servers.append(ns.name)
if ns.name not in usedNameservers and (ns.glueIPv4 or ns.glueIPv6): if ns.name not in usedNameservers and (ns.glueIPv4 or ns.glueIPv6):
usedNameservers.append(ns.name) usedNameservers.append(ns.name)
if ns.glueIPv4: if ns.glueIPv4:
glueRecords.append((ns.name, "A", [ns.glueIPv4])) glueRecords.append((ns.name, "A", [ns.glueIPv4]))
if ns.glueIPv6: if ns.glueIPv6:
glueRecords.append((ns.name, "AAAA", [ns.glueIPv6])) glueRecords.append((ns.name, "AAAA", [ns.glueIPv6]))
zone.append((domain.getZone(), "NS", servers)) zone.append((domain.getZone(), "NS", servers))
if v4reverse: if domain.ds_records:
# for ipv4 reverse we have to do some extra work in case of classless delegations zone.append((domain.getZone(), "DS", domain.ds_records.split("\n")))
# see RFC2317
net = domain.parentNet.getNetwork() if v4reverse:
if net.prefixlen % 8 != 0: # for ipv4 reverse we have to do some extra work in case of classless delegations
revZone = domain.getZone() # see RFC2317
parts = str(net.network_address).split(".") net = domain.parentNet.getNetwork()
baseZone = ".".join(reversed(parts[:net.prefixlen // 8])) + ".in-addr.arpa." if net.prefixlen % 8 != 0:
startNo = int(parts[net.prefixlen // 8]) revZone = domain.getZone()
lenExp = 8 - (net.prefixlen % 8) parts = str(net.network_address).split(".")
baseZone = ".".join(reversed(parts[:net.prefixlen // 8])) + ".in-addr.arpa."
startNo = int(parts[net.prefixlen // 8])
lenExp = 8 - (net.prefixlen % 8)
for i in range(2 ** lenExp):
no = startNo + i
zone.append(("%d.%s" % (no, baseZone), "CNAME", ["%d.%s" % (no, revZone)]))
for i in range(2 ** lenExp):
no = startNo + i
zone.append(("%d.%s" % (no, baseZone), "CNAME", ["%d.%s" % (no, revZone)]))
def mergeDomainsWithPdns(s, args, zone, zoneData, protectedRecords=[]): def mergeDomainsWithPdns(s, args, zone, zoneData, protectedRecords=[]):
url = "http://%s:%s/api/v1/servers/localhost/zones/%s" % (args.pdns_host, args.pdns_port, zone,) url = "http://%s:%s/api/v1/servers/localhost/zones/%s" % (args.pdns_host, args.pdns_port, zone,)
pdnsData = s.get(url).json() pdnsData = s.get(url).json()
baseProtectedRecords = [ baseProtectedRecords = [
(zone, "NS", []), (zone, "NS", []),
(zone, "SOA", []), (zone, "SOA", []),
] ]
# add dn. (NS + glue Nameservers) # add dn. (NS + glue Nameservers)
newDomains = mergeDomains(zoneData, pdnsData) newDomains = mergeDomains(zoneData, pdnsData)
print("Add/replace", newDomains) print("Add/replace", newDomains)
if len(newDomains) > 0: if len(newDomains) > 0:
r = s.patch(url, data=json.dumps({'rrsets': newDomains})) r = s.patch(url, data=json.dumps({'rrsets': newDomains}))
if r.status_code != 204: if r.status_code != 204:
raise RuntimeError("Could not update records in powerdns, API returned %d" % r.status_code) raise RuntimeError("Could not update records in powerdns, API returned %d" % r.status_code)
delDomains = removeOldDomains(zoneData + protectedRecords + baseProtectedRecords, pdnsData) delDomains = removeOldDomains(zoneData + protectedRecords + baseProtectedRecords, pdnsData)
print("Del", delDomains) print("Del", delDomains)
r = s.patch(url, data=json.dumps({'rrsets': delDomains})) r = s.patch(url, data=json.dumps({'rrsets': delDomains}))
if r.status_code != 204: if r.status_code != 204:
raise RuntimeError("Could not update records in powerdns, API returned %d" % r.status_code) raise RuntimeError("Could not update records in powerdns, API returned %d" % r.status_code)
def main(): def main():
parser = _parser() parser = _parser()
args = parser.parse_args() args = parser.parse_args()
config = configparser.ConfigParser() config = configparser.ConfigParser()
if args.config: if args.config:
config.read_file(args.config) config.read_file(args.config)
else: else:
config.read([os.path.join(os.path.abspath(__file__), "dns-sync.conf"), "/etc/dns-sync.conf"]) config.read([os.path.join(os.path.abspath(__file__), "dns-sync.conf"), "/etc/dns-sync.conf"])
#print(config) #print(config)
#print(config.get("DEFAULT", "api-key")) #print(config.get("DEFAULT", "api-key"))
#print(config.has_section("DEFAULT"), config.has_option("DEFAULT", "api-key")) #print(config.has_section("DEFAULT"), config.has_option("DEFAULT", "api-key"))
if args.api_key: if args.api_key:
config["DEFAULT"]["api-key"] = args.api_key config["DEFAULT"]["api-key"] = args.api_key
if not config.has_option("DEFAULT", "api-key"): if not config.has_option("DEFAULT", "api-key"):
print("Error: Could not find api-key (not present in config under [DEFAULT]; not given via command line)", file=sys.stderr) print("Error: Could not find api-key (not present in config under [DEFAULT]; not given via command line)", file=sys.stderr)
sys.exit(2) sys.exit(2)
s = requests.Session() s = requests.Session()
s.headers = { s.headers = {
'X-API-Key': config.get("DEFAULT", "api-key"), 'X-API-Key': config.get("DEFAULT", "api-key"),
'Accept': 'application/json' 'Accept': 'application/json'
} }
domains = [] domains = []
rzone4 = [] rzone4 = []
rzone6 = [] rzone6 = []
usedNameservers = [] usedNameservers = []
# assenble domain data # assenble domain data
# dn. # dn.
qs = Domain.objects.annotate(nsCount=Count('nameservers')).filter(nsCount__gt=0).order_by("name") qs = Domain.objects.annotate(nsCount=Count('nameservers')).filter(nsCount__gt=0).order_by("name")
getDomainsFromQueryset(qs, domains, domains, usedNameservers) getDomainsFromQueryset(qs, domains, domains, usedNameservers)
# reverse zones # reverse zones
qs = ReverseZone.objects.annotate(nsCount=Count('nameservers')).filter(nsCount__gt=0).order_by("parentNet__address") qs = ReverseZone.objects.annotate(nsCount=Count('nameservers')).filter(nsCount__gt=0).order_by("parentNet__address")
qs4 = filter(lambda _revz: _revz.getNetwork().version == 4, qs) qs4 = filter(lambda _revz: _revz.getNetwork().version == 4, qs)
qs6 = filter(lambda _revz: _revz.getNetwork().version == 6, qs) qs6 = filter(lambda _revz: _revz.getNetwork().version == 6, qs)
getDomainsFromQueryset(qs4, rzone4, domains, usedNameservers, v4reverse=True) getDomainsFromQueryset(qs4, rzone4, domains, usedNameservers, v4reverse=True)
getDomainsFromQueryset(qs6, rzone6, domains, usedNameservers) getDomainsFromQueryset(qs6, rzone6, domains, usedNameservers)
#print("dn.", domains) #print("dn.", domains)
#print("v4", rzone4) #print("v4", rzone4)
#print("v6", rzone6) #print("v6", rzone6)
mergeDomainsWithPdns(s, args, "dn.", domains)
mergeDomainsWithPdns(s, args, "10.in-addr.arpa.", rzone4)
mergeDomainsWithPdns(s, args, "3.2.7.c.3.a.d.f.ip6.arpa.", rzone6)
mergeDomainsWithPdns(s, args, "dn.", domains)
mergeDomainsWithPdns(s, args, "10.in-addr.arpa.", rzone4)
mergeDomainsWithPdns(s, args, "3.2.7.c.3.a.d.f.ip6.arpa.", rzone6)
if __name__ == '__main__': if __name__ == '__main__':
main() main()

View File

@ -24,107 +24,107 @@ from whoisdb.models import ASBlock, ASNumber, Contact, Maintainer, InetNum
__VERSION__ = '0.1' __VERSION__ = '0.1'
def _parser(): def _parser():
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
#prog='foo', #prog='foo',
#description='do some awesome foo', #description='do some awesome foo',
) )
#parser.add_argument("-p", "--port", default=2323, type=int, help="Your port") #parser.add_argument("-p", "--port", default=2323, type=int, help="Your port")
#parser.add_argument("-v", "--verbose", default=False, action="store_true", help="Be more verbose") #parser.add_argument("-v", "--verbose", default=False, action="store_true", help="Be more verbose")
parser.add_argument("-j", "--json", type=argparse.FileType('r'), required=True, help="Path to json file") parser.add_argument("-j", "--json", type=argparse.FileType('r'), required=True, help="Path to json file")
parser.add_argument("--version", action="version", version="%(prog)s " + __VERSION__) parser.add_argument("--version", action="version", version="%(prog)s " + __VERSION__)
return parser return parser
def main(): def main():
parser = _parser() parser = _parser()
args = parser.parse_args() args = parser.parse_args()
defContact = Contact.objects.get(handle="UNKNOWN-DN") defContact = Contact.objects.get(handle="UNKNOWN-DN")
defMnt = Maintainer.objects.get(handle="DARKNET-MNT") defMnt = Maintainer.objects.get(handle="DARKNET-MNT")
blocks = [ blocks = [
ASBlock.objects.get(handle="TRA1-ASB"), ASBlock.objects.get(handle="TRA1-ASB"),
ASBlock.objects.get(handle="UAB1-ASB") ASBlock.objects.get(handle="UAB1-ASB")
] ]
ranges = [ ranges = [
InetNum.objects.get(handle="TRA1-NET"), InetNum.objects.get(handle="TRA1-NET"),
InetNum.objects.get(handle="DEF4-NET"), InetNum.objects.get(handle="DEF4-NET"),
InetNum.objects.get(handle="DEF6-NET"), InetNum.objects.get(handle="DEF6-NET"),
InetNum.objects.get(handle="MISC6-NET"), InetNum.objects.get(handle="MISC6-NET"),
InetNum.objects.get(handle="LTRA1-NET"), InetNum.objects.get(handle="LTRA1-NET"),
InetNum.objects.get(handle="MAIN4-NET"), InetNum.objects.get(handle="MAIN4-NET"),
] ]
data = json.load(args.json) data = json.load(args.json)
print(data.keys()) print(data.keys())
for k, v in data["handles"].items(): for k, v in data["handles"].items():
print(k, v["person"]) print(k, v["person"])
for asn in data["as"]: for asn in data["as"]:
print(asn) print(asn)
if ASNumber.objects.filter(number=asn["number"]).count() > 0: if ASNumber.objects.filter(number=asn["number"]).count() > 0:
continue continue
obj = ASNumber(number=asn["number"]) obj = ASNumber(number=asn["number"])
block = None block = None
for block in blocks: for block in blocks:
if obj.number >= block.asBegin and obj.number <= block.asEnd: if obj.number >= block.asBegin and obj.number <= block.asEnd:
obj.asblock = block obj.asblock = block
break break
else: else:
raise ValueError("AS %d does not fit a block" % asn["number"]) raise ValueError("AS %d does not fit a block" % asn["number"])
if len(asn['admin_c']) >= 1: if len(asn['admin_c']) >= 1:
name = data["handles"][asn["admin_c"][0]]["person"] name = data["handles"][asn["admin_c"][0]]["person"]
obj.name = "Imported AS of %s" % name obj.name = "Imported AS of %s" % name
obj.handle = ASNumber.genGenericHandle(re.sub("[^a-zA-Z0-9 ]", "", name)) obj.handle = ASNumber.genGenericHandle(re.sub("[^a-zA-Z0-9 ]", "", name))
else: else:
obj.name = "Imported AS without admin info" obj.name = "Imported AS without admin info"
obj.handle = ASNumber.genGenericHandle("Unknown Imported AS") obj.handle = ASNumber.genGenericHandle("Unknown Imported AS")
obj.description = "Object has been imported from old DB and has not yet been edited" obj.description = "Object has been imported from old DB and has not yet been edited"
obj.save() obj.save()
obj.mnt_by.add(defMnt) obj.mnt_by.add(defMnt)
obj.admin_c.add(defContact) obj.admin_c.add(defContact)
obj.save() obj.save()
for net in data["network"]: for net in data["network"]:
print(net) print(net)
network = ipaddress.ip_network(net["prefix"]) network = ipaddress.ip_network(net["prefix"])
if InetNum.objects.filter(address=str(network.network_address), netmask=network.prefixlen).count() > 0: if InetNum.objects.filter(address=str(network.network_address), netmask=network.prefixlen).count() > 0:
continue continue
origin = None origin = None
if net["origin"]: if net["origin"]:
origin = ASNumber.objects.get(number=net["origin"]) origin = ASNumber.objects.get(number=net["origin"])
obj = InetNum(address=str(network.network_address), netmask=network.prefixlen) obj = InetNum(address=str(network.network_address), netmask=network.prefixlen)
obj.protocol = InetNum.IPv4 if network.version == 4 else InetNum.IPv6 obj.protocol = InetNum.IPv4 if network.version == 4 else InetNum.IPv6
x = list(filter(lambda _x: _x['number'] == net["origin"], data["as"])) x = list(filter(lambda _x: _x['number'] == net["origin"], data["as"]))
if len(x) > 0 and x[0]["admin_c"]: if len(x) > 0 and x[0]["admin_c"]:
name = data["handles"][x[0]["admin_c"][0]]["person"] name = data["handles"][x[0]["admin_c"][0]]["person"]
obj.name = "Imported Network of %s" % name obj.name = "Imported Network of %s" % name
obj.handle = InetNum.genGenericHandle(re.sub("[^a-zA-Z0-9 ]", "", name)) obj.handle = InetNum.genGenericHandle(re.sub("[^a-zA-Z0-9 ]", "", name))
else: else:
obj.name = "Imported Network without admin info" obj.name = "Imported Network without admin info"
obj.handle = InetNum.genGenericHandle("Unknown Imported Network") obj.handle = InetNum.genGenericHandle("Unknown Imported Network")
obj.description = "Object has been imported from old DB and has not yet been edited" obj.description = "Object has been imported from old DB and has not yet been edited"
for r in ranges: for r in ranges:
if network.network_address in r.getNetwork(): if network.network_address in r.getNetwork():
obj.parent_range = r obj.parent_range = r
break break
else: else:
raise ValueError("%s did not fit in any netblock" % network) raise ValueError("%s did not fit in any netblock" % network)
obj.save() obj.save()
obj.mnt_by.add(defMnt) obj.mnt_by.add(defMnt)
obj.admin_c.add(defContact) obj.admin_c.add(defContact)
if origin: if origin:
obj.origin_as.add(origin) obj.origin_as.add(origin)
obj.save() obj.save()
if __name__ == '__main__': if __name__ == '__main__':
main() main()

View File

@ -12,11 +12,11 @@ import os
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
files = [ files = [
("templates/help/faq.md", "templates/help/faq.html"), ("templates/help/faq.md", "templates/help/faq.html"),
] ]
for srcFile, dstFile in files: for srcFile, dstFile in files:
srcFile = os.path.join(BASE_DIR, srcFile) srcFile = os.path.join(BASE_DIR, srcFile)
dstFile = os.path.join(BASE_DIR, dstFile) dstFile = os.path.join(BASE_DIR, dstFile)
markdown.markdownFromFile(open(srcFile, "rb"), open(dstFile, "wb"), extensions=[TocExtension(baselevel=1, permalink=True)]) markdown.markdownFromFile(open(srcFile, "rb"), open(dstFile, "wb"), extensions=[TocExtension(baselevel=1, permalink=True)])

View File

@ -22,26 +22,26 @@ from whoisdb.models import ASNumber
__VERSION__ = '0.1' __VERSION__ = '0.1'
def _parser(): def _parser():
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
parser.add_argument("--version", action="version", version="%(prog)s " + __VERSION__) parser.add_argument("--version", action="version", version="%(prog)s " + __VERSION__)
return parser return parser
def main(): def main():
parser = _parser() parser = _parser()
parser.parse_args() parser.parse_args()
roa = {} roa = {}
for asn in ASNumber.objects.all(): for asn in ASNumber.objects.all():
nets = [] nets = []
for net in asn.inetnum_set.all(): for net in asn.inetnum_set.all():
nets.append(net.prefix()) nets.append(net.prefix())
if nets: if nets:
roa[asn.number] = nets roa[asn.number] = nets
print(json.dumps(roa)) print(json.dumps(roa))
if __name__ == '__main__': if __name__ == '__main__':
main() main()

View File

@ -24,84 +24,84 @@ log = logging.getLogger("whoisd")
__VERSION__ = '0.1' __VERSION__ = '0.1'
class WhoisHandler(socketserver.BaseRequestHandler): class WhoisHandler(socketserver.BaseRequestHandler):
header = "% This is the DARKNET database query service.\n" \ header = "% This is the DARKNET database query service.\n" \
"% The objects should be in something like RPSL format.\n" \ "% The objects should be in something like RPSL format.\n" \
"%\n" \ "%\n" \
"% The DARKNET database is subject to terms and conditions.\n" \ "% The DARKNET database is subject to terms and conditions.\n" \
"% Mostly these are \"be nice\" and \"don't knowingly break things\".\n" \ "% Mostly these are \"be nice\" and \"don't knowingly break things\".\n" \
"\n" "\n"
def handle(self): def handle(self):
self.request.sendall(self.header.encode()) self.request.sendall(self.header.encode())
line = self.request.recv(1024) line = self.request.recv(1024)
line = line.split(b"\n", 2)[0].strip() line = line.split(b"\n", 2)[0].strip()
print("Request object is %s" % line) print("Request object is %s" % line)
log.info("Request by %s for %s" % (self.request.getpeername()[0], line)) log.info("Request by %s for %s" % (self.request.getpeername()[0], line))
self.request.send(b"\n") self.request.send(b"\n")
objs = findInDatabase(line.decode()) objs = findInDatabase(line.decode())
if len(objs) > 0: if len(objs) > 0:
self.request.sendall(("%% %d result%s\n" % (len(objs), "" if len(objs)==1 else "s")).encode()) self.request.sendall(("%% %d result%s\n" % (len(objs), "" if len(objs)==1 else "s")).encode())
for obj in objs: for obj in objs:
self.sendObject(obj) self.sendObject(obj)
else: else:
self.request.sendall(b"%% NOT FOUND\n\n") self.request.sendall(b"%% NOT FOUND\n\n")
def sendObject(self, obj): def sendObject(self, obj):
result = [ result = [
"", "",
"%% Object %s (%s)" % (obj, obj.getClassName()), "%% Object %s (%s)" % (obj, obj.getClassName()),
"" ""
] ]
for field, value in getWhoisObjectFields(obj, False): for field, value in getWhoisObjectFields(obj, False):
fieldName = field.lower().replace(" ", "-") + ":" fieldName = field.lower().replace(" ", "-") + ":"
if not value: if not value:
value = "" value = ""
if hasattr(value, "through"): if hasattr(value, "through"):
for v in value.all(): for v in value.all():
result.append("%-16s %s" % (fieldName, v)) result.append("%-16s %s" % (fieldName, v))
else: else:
result.append("%-16s %s" % (fieldName, value)) result.append("%-16s %s" % (fieldName, value))
result.extend(["", ""]) result.extend(["", ""])
self.request.sendall("\n".join(result).encode()) self.request.sendall("\n".join(result).encode())
class ThreadedTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer): class ThreadedTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
allow_reuse_address = True allow_reuse_address = True
def _parser(): def _parser():
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
#prog='foo', #prog='foo',
#description='do some awesome foo', #description='do some awesome foo',
) )
parser.add_argument("-p", "--port", default=43, type=int, help="whoisd port") parser.add_argument("-p", "--port", default=43, type=int, help="whoisd port")
#parser.add_argument("-v", "--verbose", default=False, action="store_true", help="Be more verbose") #parser.add_argument("-v", "--verbose", default=False, action="store_true", help="Be more verbose")
parser.add_argument("--version", action="version", version="%(prog)s " + __VERSION__) parser.add_argument("--version", action="version", version="%(prog)s " + __VERSION__)
return parser return parser
def main(): def main():
parser = _parser() parser = _parser()
args = parser.parse_args() args = parser.parse_args()
server = ThreadedTCPServer(('', args.port), WhoisHandler) server = ThreadedTCPServer(('', args.port), WhoisHandler)
while True: while True:
try: try:
server.serve_forever() server.serve_forever()
except select.error as e: except select.error as e:
log.exception(e) log.exception(e)
except KeyboardInterrupt: except KeyboardInterrupt:
log.info("^c hit, quitting.") log.info("^c hit, quitting.")
break break
server.server_close() server.server_close()
if __name__ == '__main__': if __name__ == '__main__':
main() main()

View File

@ -7,6 +7,6 @@ from .models import User
class CustomUserCreationForm(UserCreationForm): class CustomUserCreationForm(UserCreationForm):
class Meta: class Meta:
model = User model = User
fields = ("username",) fields = ("username",)

View File

@ -7,4 +7,4 @@ from django.db import models
from django.contrib.auth.models import AbstractUser from django.contrib.auth.models import AbstractUser
class User(AbstractUser): class User(AbstractUser):
pass pass

View File

@ -8,10 +8,10 @@ from django.contrib.auth import views as auth_views
from . import views as dncore_views from . import views as dncore_views
urlpatterns = [ urlpatterns = [
url(r'^$', dncore_views.dashboard, name='dashboard'), url(r'^$', dncore_views.dashboard, name='dashboard'),
url(r'^login/$', auth_views.login, name='login'), url(r'^login/$', auth_views.login, name='login'),
url(r'^register/$', dncore_views.RegisterUser.as_view(), name='register'), url(r'^register/$', dncore_views.RegisterUser.as_view(), name='register'),
url(r'^logout/$', auth_views.logout, {'next_page': '/'}, name='logout'), url(r'^logout/$', auth_views.logout, {'next_page': '/'}, name='logout'),
url(r'^profile/$', dncore_views.profile, name='profile'), url(r'^profile/$', dncore_views.profile, name='profile'),
] ]

View File

@ -22,54 +22,54 @@ from .forms import CustomUserCreationForm
@login_required @login_required
def profile(request): def profile(request):
pwForm = None pwForm = None
if request.method == "POST": if request.method == "POST":
if request.POST.get("submit", None) == "pwchange": if request.POST.get("submit", None) == "pwchange":
pwForm = PasswordChangeForm(user=request.user, data=request.POST) pwForm = PasswordChangeForm(user=request.user, data=request.POST)
if pwForm.is_valid(): if pwForm.is_valid():
pwForm.save() pwForm.save()
auth_login(request, pwForm.user) auth_login(request, pwForm.user)
messages.success(request, "Password changed") messages.success(request, "Password changed")
return HttpResponseRedirect(reverse("user:profile")) return HttpResponseRedirect(reverse("user:profile"))
if not pwForm: if not pwForm:
pwForm = PasswordChangeForm(user=request.user) pwForm = PasswordChangeForm(user=request.user)
return render(request, "registration/profile.html", {'pwForm': pwForm}) return render(request, "registration/profile.html", {'pwForm': pwForm})
@login_required @login_required
def dashboard(request): def dashboard(request):
mnts = request.user.maintainer_set.all() mnts = request.user.maintainer_set.all()
ownMnts = request.user.maintainer_set.filter(rir=False, lir=False).all().distinct() ownMnts = request.user.maintainer_set.filter(rir=False, lir=False).all().distinct()
# if account only has rir/lir objects, show them # if account only has rir/lir objects, show them
if ownMnts.count() == 0: if ownMnts.count() == 0:
ownMnts = mnts ownMnts = mnts
asns = ASNumber.objects.filter(Q(mnt_by__in=ownMnts) | Q(mnt_lower__in=ownMnts)).distinct() asns = ASNumber.objects.filter(Q(mnt_by__in=ownMnts) | Q(mnt_lower__in=ownMnts)).distinct()
inetnums = InetNum.objects.filter(Q(mnt_by__in=ownMnts) | Q(mnt_lower__in=ownMnts)).distinct() inetnums = InetNum.objects.filter(Q(mnt_by__in=ownMnts) | Q(mnt_lower__in=ownMnts)).distinct()
domains = Domain.objects.filter(mnt_by__in=mnts).distinct() domains = Domain.objects.filter(mnt_by__in=mnts).distinct()
rrequests = Request.objects.filter((Q(provider__in=mnts) | Q(applicant__in=mnts)) & Q(status=Request.STATE_OPEN)).distinct() rrequests = Request.objects.filter((Q(provider__in=mnts) | Q(applicant__in=mnts)) & Q(status=Request.STATE_OPEN)).distinct()
return render(request, "dncore/dashboard.html", {"asns": asns, "inetnums": inetnums, "domains": domains, 'rrequests': rrequests}) return render(request, "dncore/dashboard.html", {"asns": asns, "inetnums": inetnums, "domains": domains, 'rrequests': rrequests})
def index(request): def index(request):
if request.user.is_authenticated(): if request.user.is_authenticated():
return HttpResponseRedirect(reverse("dashboard")) return HttpResponseRedirect(reverse("dashboard"))
return render(request, "index.html", {}) return render(request, "index.html", {})
class RegisterUser(CreateView): class RegisterUser(CreateView):
template_name = "dncore/registration.html" template_name = "dncore/registration.html"
form_class = CustomUserCreationForm form_class = CustomUserCreationForm
success_url = reverse_lazy("user:login") success_url = reverse_lazy("user:login")
def form_valid(self, form): def form_valid(self, form):
ret = super(RegisterUser, self).form_valid(form) ret = super(RegisterUser, self).form_valid(form)
messages.success(self.request, "You successfully registered as user %s and can now log in!" % form.instance.username) messages.success(self.request, "You successfully registered as user %s and can now log in!" % form.instance.username)
return ret return ret

View File

@ -21,7 +21,7 @@ from django.contrib import messages
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
TLD_NAMESERVERS = [ TLD_NAMESERVERS = [
'10.100.100.100', '10.100.100.100',
] ]
DATETIME_FORMAT = "Y-m-d H:i:s" DATETIME_FORMAT = "Y-m-d H:i:s"
@ -42,47 +42,47 @@ ALLOWED_HOSTS = ["*"]
# Application definition # Application definition
INSTALLED_APPS = [ INSTALLED_APPS = [
'django.contrib.admin', 'django.contrib.admin',
'django.contrib.auth', 'django.contrib.auth',
'django.contrib.contenttypes', 'django.contrib.contenttypes',
'django.contrib.sessions', 'django.contrib.sessions',
'django.contrib.messages', 'django.contrib.messages',
'django.contrib.staticfiles', 'django.contrib.staticfiles',
'crispy_forms', 'crispy_forms',
'formtools', 'formtools',
'dncore', 'dncore',
'whoisdb', 'whoisdb',
'rrequests', 'rrequests',
'domains', 'domains',
'api', 'api',
] ]
MIDDLEWARE = [ MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware', 'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware', 'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware', 'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware', 'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware',
] ]
ROOT_URLCONF = 'dnmgmt.urls' ROOT_URLCONF = 'dnmgmt.urls'
TEMPLATES = [ TEMPLATES = [
{ {
'BACKEND': 'django.template.backends.django.DjangoTemplates', 'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, "templates/")], 'DIRS': [os.path.join(BASE_DIR, "templates/")],
'APP_DIRS': True, 'APP_DIRS': True,
'OPTIONS': { 'OPTIONS': {
'context_processors': [ 'context_processors': [
'django.template.context_processors.debug', 'django.template.context_processors.debug',
'django.template.context_processors.request', 'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth', 'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages', 'django.contrib.messages.context_processors.messages',
], ],
}, },
}, },
] ]
WSGI_APPLICATION = 'dnmgmt.wsgi.application' WSGI_APPLICATION = 'dnmgmt.wsgi.application'
@ -92,10 +92,10 @@ WSGI_APPLICATION = 'dnmgmt.wsgi.application'
# https://docs.djangoproject.com/en/1.10/ref/settings/#databases # https://docs.djangoproject.com/en/1.10/ref/settings/#databases
DATABASES = { DATABASES = {
'default': { 'default': {
'ENGINE': 'django.db.backends.sqlite3', 'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
} }
} }
@ -103,18 +103,18 @@ DATABASES = {
# https://docs.djangoproject.com/en/1.10/ref/settings/#auth-password-validators # https://docs.djangoproject.com/en/1.10/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [ AUTH_PASSWORD_VALIDATORS = [
{ {
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
}, },
{ {
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
}, },
{ {
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
}, },
{ {
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
}, },
] ]
@ -139,11 +139,11 @@ USE_TZ = True
STATIC_URL = '/static/' STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, "cstatic") STATIC_ROOT = os.path.join(BASE_DIR, "cstatic")
STATICFILES_DIRS = [ STATICFILES_DIRS = [
os.path.join(BASE_DIR, "static"), os.path.join(BASE_DIR, "static"),
] ]
CRISPY_TEMPLATE_PACK = 'bootstrap3' CRISPY_TEMPLATE_PACK = 'bootstrap3'
MESSAGE_TAGS = { MESSAGE_TAGS = {
messages.ERROR: 'danger', messages.ERROR: 'danger',
} }
AUTH_USER_MODEL = 'dncore.User' AUTH_USER_MODEL = 'dncore.User'

View File

@ -30,15 +30,15 @@ import api.urls
urlpatterns = [ urlpatterns = [
url(r'^$', dncore.views.index, name='index'), url(r'^$', dncore.views.index, name='index'),
url(r'^dashboard/$', dncore.views.dashboard, name='dashboard'), url(r'^dashboard/$', dncore.views.dashboard, name='dashboard'),
url(r'^admin/', admin.site.urls), url(r'^admin/', admin.site.urls),
url(r'^user/', include(dncore.urls, namespace='user')), url(r'^user/', include(dncore.urls, namespace='user')),
url(r'^whoisdb/', include(whoisdb.urls, namespace='whoisdb')), url(r'^whoisdb/', include(whoisdb.urls, namespace='whoisdb')),
url(r'^rrequests/', include(rrequests.urls, namespace='rrequests')), url(r'^rrequests/', include(rrequests.urls, namespace='rrequests')),
url(r'^domains/', include(domains.urls, namespace='domains')), url(r'^domains/', include(domains.urls, namespace='domains')),
url(r'^api/', include(api.urls, namespace='api')), url(r'^api/', include(api.urls, namespace='api')),
url(r'^help/$', TemplateView.as_view(template_name='help/help.html'), name='help'), url(r'^help/$', TemplateView.as_view(template_name='help/help.html'), name='help'),
] ]

View File

@ -15,229 +15,260 @@ import re
import ipaddress import ipaddress
class DomainForm(MntFormMixin, WhoisObjectFormMixin, forms.ModelForm): class DSRecordMixin(object):
class Meta: ds_re = re.compile(r"^(?P<id>\d+)\s+(?P<crypto>\d+)\s+(?P<hashtype>\d+)\s+(?P<hash>[0-9a-fA-F]+)$")
model = Domain HASH_SUPPORTED = (1, 2)
fields = ['name', 'nameservers', 'mnt_by', 'admin_c'] CRYPTO_SUPPORTED = (3, 5, 6, 8, 10, 13, 15)
def __init__(self, *args, **kwargs): def clean_ds_records(self):
super(DomainForm, self).__init__(*args, **kwargs) ds_records = self.cleaned_data['ds_records'].strip()
result = []
mnts = self._user.maintainer_set.all() if not ds_records:
self.fields['nameservers'].queryset = Nameserver.objects.filter(Q(mnt_by__in=mnts)) return ''
instance = getattr(self, "instance", None) for n, rec in enumerate(ds_records.split("\n"), 1):
self._create = not (instance and instance.pk) rec = rec.strip()
m = self.ds_re.match(rec)
if not m:
raise forms.ValidationError("Could not parse records {} - needs to be in format "
"'<id> <crypto> <hashtype> <hash>'".format(n))
if not self._create: if int(m.group('hashtype')) not in self.HASH_SUPPORTED:
self.fields['name'].disabled = True raise forms.ValidationError("Record {} has an invalid hashtype of {}, supported are {}"
self.fields['nameservers'].queryset |= self.instance.nameservers.all() "".format(n, m.group('hashtype'), " ".join(map(str, self.HASH_SUPPORTED))))
if int(m.group('crypto')) not in self.CRYPTO_SUPPORTED:
raise forms.ValidationError("Record {} has unsupported crypto {}, supported are {}"
"".format(n, m.group('crypto'), " ".join(map(str, self.CRYPTO_SUPPORTED))))
self.fields['nameservers'].queryset = self.fields['nameservers'].queryset.distinct() result.append("{id} {crypto} {hashtype} {hash}".format(**m.groupdict()))
def clean_name(self): return "\n".join(result)
name = self.cleaned_data['name'].lower()
if self._create:
if not name.endswith("."):
name += "."
if not name.endswith("dn."):
raise forms.ValidationError("Only .dn domains can be registered at this point")
if name.count(".") > 2: class DomainForm(MntFormMixin, WhoisObjectFormMixin, DSRecordMixin, forms.ModelForm):
raise forms.ValidationError("No subdomains can be registered") class Meta:
model = Domain
fields = ['name', 'nameservers', 'mnt_by', 'admin_c', 'ds_records']
if not re.match("^[a-z0-9.-]+$", name): def __init__(self, *args, **kwargs):
raise forms.ValidationError("Only a-z, 0-9 and - are allowed inside the domain name") super(DomainForm, self).__init__(*args, **kwargs)
try: mnts = self._user.maintainer_set.all()
Domain.objects.get(name=name) self.fields['nameservers'].queryset = Nameserver.objects.filter(Q(mnt_by__in=mnts))
raise forms.ValidationError("Domain already exists")
except Domain.DoesNotExist:
pass
return name instance = getattr(self, "instance", None)
self._create = not (instance and instance.pk)
if not self._create:
self.fields['name'].disabled = True
self.fields['nameservers'].queryset |= self.instance.nameservers.all()
self.fields['nameservers'].queryset = self.fields['nameservers'].queryset.distinct()
def clean_name(self):
name = self.cleaned_data['name'].lower()
if self._create:
if not name.endswith("."):
name += "."
if not name.endswith("dn."):
raise forms.ValidationError("Only .dn domains can be registered at this point")
if name.count(".") > 2:
raise forms.ValidationError("No subdomains can be registered")
if not re.match("^[a-z0-9.-]+$", name):
raise forms.ValidationError("Only a-z, 0-9 and - are allowed inside the domain name")
try:
Domain.objects.get(name=name)
raise forms.ValidationError("Domain already exists")
except Domain.DoesNotExist:
pass
return name
class NameserverForm(MntFormMixin, WhoisObjectFormMixin, forms.ModelForm): class NameserverForm(MntFormMixin, WhoisObjectFormMixin, forms.ModelForm):
class Meta: class Meta:
model = Nameserver model = Nameserver
fields = ['name', 'glueIPv4', 'glueIPv6', 'mnt_by', 'admin_c'] fields = ['name', 'glueIPv4', 'glueIPv6', 'mnt_by', 'admin_c']
help_texts = { help_texts = {
"glueIPv4": "Note: You can only set a glue record if the base domain of this nameserver belongs to you!" "glueIPv4": "Note: You can only set a glue record if the base domain of this nameserver belongs to you!"
} }
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(NameserverForm, self).__init__(*args, **kwargs) super(NameserverForm, self).__init__(*args, **kwargs)
instance = getattr(self, "instance", None) instance = getattr(self, "instance", None)
self._create = not (instance and instance.pk) self._create = not (instance and instance.pk)
def cleanNetwork(self, glue): def cleanNetwork(self, glue):
ip = ipaddress.ip_address(glue) ip = ipaddress.ip_address(glue)
proto = InetNum.IPv4 if ip.version == 4 else InetNum.IPv6 proto = InetNum.IPv4 if ip.version == 4 else InetNum.IPv6
nets = InetNum.objects.filter(parent_range=None, protocol=proto) nets = InetNum.objects.filter(parent_range=None, protocol=proto)
if len(nets) == 0: if len(nets) == 0:
raise forms.ValidationError("No range has been registered for IPv%s in the whois interface" % ip.version) raise forms.ValidationError("No range has been registered for IPv%s in the whois interface" % ip.version)
for net in nets: for net in nets:
if ip in net.getNetwork(): if ip in net.getNetwork():
break break
else: else:
raise forms.ValidationError("Glue record address is not inside DarkNet (subnet %s)" % ", ".join(map(lambda _x: _x.prefix(), nets))) raise forms.ValidationError("Glue record address is not inside DarkNet (subnet %s)" % ", ".join(map(lambda _x: _x.prefix(), nets)))
def clean_glueIPv4(self): def clean_glueIPv4(self):
glue = self.cleaned_data['glueIPv4'] glue = self.cleaned_data['glueIPv4']
if glue: if glue:
self.cleanNetwork(glue) self.cleanNetwork(glue)
return glue return glue
def clean_glueIPv6(self): def clean_glueIPv6(self):
glue = self.cleaned_data['glueIPv6'] glue = self.cleaned_data['glueIPv6']
if glue: if glue:
self.cleanNetwork(glue) self.cleanNetwork(glue)
return glue return glue
def clean_name(self): def clean_name(self):
name = self.cleaned_data['name'].lower().strip() name = self.cleaned_data['name'].lower().strip()
if not name.endswith("."): if not name.endswith("."):
name += "." name += "."
# allow name to stay if it did not change # allow name to stay if it did not change
if not self._create and self.instance.name == name: if not self._create and self.instance.name == name:
return name return name
if name.count(".") <= 2: if name.count(".") <= 2:
raise forms.ValidationError("Nameserver must be inside a domain (e.g. ns1.noot.dn.)") raise forms.ValidationError("Nameserver must be inside a domain (e.g. ns1.noot.dn.)")
mnts = self._user.maintainer_set.all() mnts = self._user.maintainer_set.all()
try: try:
obj = Nameserver.objects.get(name=name, mnt_by__in=mnts) obj = Nameserver.objects.get(name=name, mnt_by__in=mnts)
if self._create or not self._create and obj.pk != self.instance.pk: if self._create or not self._create and obj.pk != self.instance.pk:
raise forms.ValidationError("You already have a nameserver with this name under your control") raise forms.ValidationError("You already have a nameserver with this name under your control")
except Nameserver.DoesNotExist: except Nameserver.DoesNotExist:
pass pass
except Nameserver.MultipleObjectsReturned: except Nameserver.MultipleObjectsReturned:
pass pass
return name return name
def clean(self): def clean(self):
cleaned_data = super(NameserverForm, self).clean() cleaned_data = super(NameserverForm, self).clean()
if not self.errors: if not self.errors:
name = cleaned_data.get("name") name = cleaned_data.get("name")
mntBy = cleaned_data.get("mnt_by") mntBy = cleaned_data.get("mnt_by")
zone = ".".join(name.split(".")[-3:]) zone = ".".join(name.split(".")[-3:])
ipv4 = cleaned_data.get("glueIPv4", None) ipv4 = cleaned_data.get("glueIPv4", None)
ipv6 = cleaned_data.get("glueIPv6", None) ipv6 = cleaned_data.get("glueIPv6", None)
if not ipv4: if not ipv4:
ipv4 = None ipv4 = None
if not ipv6: if not ipv6:
ipv6 = None ipv6 = None
if self._create and (ipv4 or ipv6) or not self._create and not (self.instance.glueIPv4 == ipv4 and self.instance.glueIPv6 == ipv6): if self._create and (ipv4 or ipv6) or not self._create and not (self.instance.glueIPv4 == ipv4 and self.instance.glueIPv6 == ipv6):
mnts = self._user.maintainer_set.all() mnts = self._user.maintainer_set.all()
domains = Domain.objects.filter(mnt_by__in=mnts) domains = Domain.objects.filter(mnt_by__in=mnts)
found = False found = False
for domain in domains: for domain in domains:
if domain.name == zone: if domain.name == zone:
found = True found = True
break break
if not found: if not found:
raise forms.ValidationError("You have glue IPs set, but this domain is not under a domain you control.") raise forms.ValidationError("You have glue IPs set, but this domain is not under a domain you control.")
if ipv4 or ipv6: if ipv4 or ipv6:
try: try:
ns = Nameserver.objects.get(Q(name=name) & (Q(glueIPv4__isnull=False) | Q(glueIPv6__isnull=False))) ns = Nameserver.objects.get(Q(name=name) & (Q(glueIPv4__isnull=False) | Q(glueIPv6__isnull=False)))
if self._create or ns.pk != self.instance.pk: if self._create or ns.pk != self.instance.pk:
nsMnts = ", ".join(n.handle for n in ns.mnt_by.all()) nsMnts = ", ".join(n.handle for n in ns.mnt_by.all())
raise forms.ValidationError("Only one nameserver for this domain can have glue records and one already exists (maintained by %s)" % nsMnts) raise forms.ValidationError("Only one nameserver for this domain can have glue records and one already exists (maintained by %s)" % nsMnts)
except Nameserver.DoesNotExist: except Nameserver.DoesNotExist:
pass pass
failedMnts = set() failedMnts = set()
for ns in Nameserver.objects.filter(name=name, mnt_by__in=mntBy): for ns in Nameserver.objects.filter(name=name, mnt_by__in=mntBy):
if self._create or self.instance.pk != ns.pk: if self._create or self.instance.pk != ns.pk:
for mnt in ns.mnt_by.all(): for mnt in ns.mnt_by.all():
if mnt in mntBy: if mnt in mntBy:
failedMnts.add(mnt.handle) failedMnts.add(mnt.handle)
if len(failedMnts) > 0: if len(failedMnts) > 0:
raise forms.ValidationError("The following maintainer objects already have this nameservers: %s" % ", ".join(failedMnts)) raise forms.ValidationError("The following maintainer objects already have this nameservers: %s" % ", ".join(failedMnts))
return cleaned_data return cleaned_data
class ReverseZoneForm(forms.ModelForm): class ReverseZoneForm(DSRecordMixin, forms.ModelForm):
prefix = forms.CharField(validators=[IP46CIDRValidator]) prefix = forms.CharField(validators=[IP46CIDRValidator])
class Meta: class Meta:
model = ReverseZone model = ReverseZone
fields = ['parentNet', 'nameservers'] fields = ['parentNet', 'nameservers']
help_texts = { help_texts = {
"prefix": "The prefix in CIDR form for which this object is responsible", "prefix": "The prefix in CIDR form for which this object is responsible",
} }
def __init__(self, user, *args, **kwargs): def __init__(self, user, *args, **kwargs):
self._user = user self._user = user
super(ReverseZoneForm, self).__init__(*args, **kwargs) super(ReverseZoneForm, self).__init__(*args, **kwargs)
instance = getattr(self, "instance", None) instance = getattr(self, "instance", None)
self._create = not (instance and instance.pk) self._create = not (instance and instance.pk)
mnts = self._user.maintainer_set.all() mnts = self._user.maintainer_set.all()
self.fields['parentNet'].queryset = InetNum.objects.filter(Q(mnt_by__in=mnts) | Q(mnt_lower__in=mnts)).distinct() self.fields['parentNet'].queryset = InetNum.objects.filter(Q(mnt_by__in=mnts) | Q(mnt_lower__in=mnts)).distinct()
self.fields['nameservers'].queryset = Nameserver.objects.filter(Q(mnt_by__in=mnts)) self.fields['nameservers'].queryset = Nameserver.objects.filter(Q(mnt_by__in=mnts))
if not self._create: if not self._create:
self.fields['prefix'].disabled = True self.fields['prefix'].disabled = True
self.fields['nameservers'].queryset |= self.instance.nameservers.all() self.fields['nameservers'].queryset |= self.instance.nameservers.all()
self.fields['nameservers'].queryset = self.fields['nameservers'].queryset.distinct() self.fields['nameservers'].queryset = self.fields['nameservers'].queryset.distinct()
def clean_prefix(self): def clean_prefix(self):
prefix = self.cleaned_data['prefix'] prefix = self.cleaned_data['prefix']
net = ipaddress.ip_network(prefix) net = ipaddress.ip_network(prefix)
if net.version == 6 and net.prefixlen % 4 != 0: if net.version == 6 and net.prefixlen % 4 != 0:
raise forms.ValidationError("IPv6 reverse zone prefix length has to be a multiple of 4") raise forms.ValidationError("IPv6 reverse zone prefix length has to be a multiple of 4")
return prefix return prefix
def clean(self): def clean(self):
cleaned_data = super(ReverseZoneForm, self).clean() cleaned_data = super(ReverseZoneForm, self).clean()
if not self.errors: if not self.errors:
if self._create: if self._create:
net = ipaddress.ip_network(cleaned_data['prefix']) net = ipaddress.ip_network(cleaned_data['prefix'])
parentNet = cleaned_data['parentNet'].getNetwork() parentNet = cleaned_data['parentNet'].getNetwork()
if net.network_address not in parentNet: if net.network_address not in parentNet:
raise forms.ValidationError("Given prefix %s is not inside of parent netblock %s" % (net, parentNet)) raise forms.ValidationError("Given prefix %s is not inside of parent netblock %s" % (net, parentNet))
# For now just check all the zones... # For now just check all the zones...
#zones = ReverseZone.objects.filter(parentNet=cleaned_data['parentNet']) #zones = ReverseZone.objects.filter(parentNet=cleaned_data['parentNet'])
zones = ReverseZone.objects.all() zones = ReverseZone.objects.all()
for zone in zones: for zone in zones:
if net.network_address in zone.parentNet.getNetwork(): if net.network_address in zone.parentNet.getNetwork():
raise forms.ValidationError("Given prefix already has a reverse zone object associated to it: %s" % zone) raise forms.ValidationError("Given prefix already has a reverse zone object associated to it: %s" % zone)
self.instance.address = str(net.network_address) self.instance.address = str(net.network_address)
self.instance.netmask = net.prefixlen self.instance.netmask = net.prefixlen
return cleaned_data return cleaned_data

View File

@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.5 on 2017-04-03 05:33
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('domains', '0003_auto_20170322_1801'),
]
operations = [
migrations.AlterField(
model_name='domain',
name='mnt_by',
field=models.ManyToManyField(help_text='You can select multiple maintainers here', to='whoisdb.Maintainer'),
),
migrations.AlterField(
model_name='nameserver',
name='mnt_by',
field=models.ManyToManyField(help_text='You can select multiple maintainers here', to='whoisdb.Maintainer'),
),
]

View File

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2019-05-30 21:18
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('domains', '0004_auto_20170403_0533'),
]
operations = [
migrations.AddField(
model_name='domain',
name='ds_records',
field=models.TextField(blank=True),
),
]

View File

@ -13,139 +13,146 @@ import ipaddress
# allow owners of a subnet to create reverse dns record? # allow owners of a subnet to create reverse dns record?
class DSRecordModelMixin(models.Model):
class Meta:
abstract = True
ds_records = models.TextField(blank=True, verbose_name='DS Records',
help_text='DS Records in the format of [id] [crypto-algo] [hash-algo] [hash]')
class Nameserver(MntdObject): class Nameserver(MntdObject):
handleSuffix = "NS" handleSuffix = "NS"
# dns name # dns name
# ip address, if glue # ip address, if glue
# ipv4/ipv6 address? # ipv4/ipv6 address?
handle = None handle = None
name = models.CharField(max_length=256) name = models.CharField(max_length=256)
glueIPv4 = models.GenericIPAddressField(protocol='IPv4', blank=True, null=True) glueIPv4 = models.GenericIPAddressField(protocol='IPv4', blank=True, null=True)
glueIPv6 = models.GenericIPAddressField(protocol='IPv6', blank=True, null=True) glueIPv6 = models.GenericIPAddressField(protocol='IPv6', blank=True, null=True)
admin_c = models.ManyToManyField(Contact) admin_c = models.ManyToManyField(Contact)
def getPK(self): def getPK(self):
return self.pk return self.pk
def get_absolute_url(self): def get_absolute_url(self):
return reverse("domains:nameserver-show", args=(self.pk,)) return reverse("domains:nameserver-show", args=(self.pk,))
def __str__(self): def __str__(self):
return self.name return self.name
def getAppName(self): def getAppName(self):
return "domains" return "domains"
def getNoDeleteReasons(self): def getNoDeleteReasons(self):
# nameservers can always be deleted # nameservers can always be deleted
return [] return []
class Domain(MntdObject): class Domain(DSRecordModelMixin, MntdObject):
handle = None handle = None
handleSuffix = "DOM" handleSuffix = "DOM"
name = models.CharField(max_length=67, unique=True, db_index=True) name = models.CharField(max_length=67, unique=True, db_index=True)
nameservers = models.ManyToManyField(Nameserver, blank=True) nameservers = models.ManyToManyField(Nameserver, blank=True)
admin_c = models.ManyToManyField(Contact) admin_c = models.ManyToManyField(Contact)
def getPK(self): def getPK(self):
return self.name return self.name
def getZone(self): def getZone(self):
return self.name return self.name
def get_absolute_url(self): def get_absolute_url(self):
return reverse("domains:domain-show", args=(self.name,)) return reverse("domains:domain-show", args=(self.name,))
def __str__(self): def __str__(self):
return self.name return self.name
def getAppName(self): def getAppName(self):
return "domains" return "domains"
def getNoDeleteReasons(self): def getNoDeleteReasons(self):
reasons = [] reasons = []
nameservers = Nameserver.objects.filter(name__endswith="." + self.name) nameservers = Nameserver.objects.filter(name__endswith="." + self.name)
for ns in nameservers: for ns in nameservers:
# FIXME: check if the nameserver has a glue record.... and also if it is maintained by us # FIXME: check if the nameserver has a glue record.... and also if it is maintained by us
reasons.append("Nameserver %s depends on this domain" % ns.name) reasons.append("Nameserver %s depends on this domain" % ns.name)
return reasons return reasons
@classmethod @classmethod
def fixName(clazz, name): def fixName(clazz, name):
if not name.endswith("."): if not name.endswith("."):
name += "." name += "."
return name.lower() return name.lower()
class ReverseZone(WhoisObject): class ReverseZone(DSRecordModelMixin, WhoisObject):
handle = None handle = None
parentNet = models.ForeignKey(InetNum) parentNet = models.ForeignKey(InetNum)
address = models.GenericIPAddressField(db_index=True) address = models.GenericIPAddressField(db_index=True)
netmask = models.PositiveIntegerField() netmask = models.PositiveIntegerField()
nameservers = models.ManyToManyField(Nameserver) nameservers = models.ManyToManyField(Nameserver)
def getPK(self): def getPK(self):
return self.pk return self.pk
def prefix(self): def prefix(self):
""" Helper function, mainly used in templates """ """ Helper function, mainly used in templates """
return "%s/%s" % (self.address, self.netmask) return "%s/%s" % (self.address, self.netmask)
def getNetwork(self): def getNetwork(self):
return ipaddress.ip_network(self.prefix()) return ipaddress.ip_network(self.prefix())
def getZone(self): def getZone(self):
net = self.parentNet.getNetwork() net = self.parentNet.getNetwork()
if net.version == 4: if net.version == 4:
# does not work with python3.4 # does not work with python3.4
## for these we delegate the full domain ## for these we delegate the full domain
#if 0 < net.prefixlen < 32 and net.prefixlen % 8 == 0: #if 0 < net.prefixlen < 32 and net.prefixlen % 8 == 0:
# zoneParts = net.reverse_pointer.split(".") # zoneParts = net.reverse_pointer.split(".")
# return ".".join(zoneParts[1:]) # return ".".join(zoneParts[1:])
#else: #else:
# # return RFC2317 compliant zone # # return RFC2317 compliant zone
# return net.reverse_pointer # return net.reverse_pointer
parts = list(reversed(str(net.network_address).split("."))) parts = list(reversed(str(net.network_address).split(".")))
domain = ".".join(parts[4 - net.prefixlen // 8:]) + ".in-addr.arpa." domain = ".".join(parts[4 - net.prefixlen // 8:]) + ".in-addr.arpa."
if net.prefixlen % 8 == 0: if net.prefixlen % 8 == 0:
# clean cut! # clean cut!
return domain return domain
else: else:
# RFC2317 compliant! # RFC2317 compliant!
rfc2317Domain = "%s/%s.%s" % (parts[4 - net.prefixlen // 8 - 1], net.prefixlen, domain) rfc2317Domain = "%s/%s.%s" % (parts[4 - net.prefixlen // 8 - 1], net.prefixlen, domain)
return rfc2317Domain return rfc2317Domain
else: else:
## does not work with python3.4 ## does not work with python3.4
## ipv6 ## ipv6
## thefuck ipaddress lib... _the_ _fuck_ ## thefuck ipaddress lib... _the_ _fuck_
#zoneParts = net.reverse_pointer.split(".")[-2 - net.prefixlen // 4:] #zoneParts = net.reverse_pointer.split(".")[-2 - net.prefixlen // 4:]
#return ".".join(zoneParts) #return ".".join(zoneParts)
parts = list(reversed(net.exploded.split("/")[0].replace(":", "")))[32 - net.prefixlen // 4:] parts = list(reversed(net.exploded.split("/")[0].replace(":", "")))[32 - net.prefixlen // 4:]
return ".".join(parts) + ".ip6.arpa." return ".".join(parts) + ".ip6.arpa."
def get_absolute_url(self): def get_absolute_url(self):
return reverse("domains:reversezone-show", args=(self.pk,)) return reverse("domains:reversezone-show", args=(self.pk,))
def getAppName(self): def getAppName(self):
return "domains" return "domains"
def __str__(self): def __str__(self):
return "%s @ %s" % (self.prefix(), self.parentNet) return "%s @ %s" % (self.prefix(), self.parentNet)
def getNoDeleteReasons(self): def getNoDeleteReasons(self):
return [] return []
def canEdit(self, user): def canEdit(self, user):
if self.parentNet: if self.parentNet:
return self.parentNet.canEdit(user) return self.parentNet.canEdit(user)
else: else:
return False return False

View File

@ -7,23 +7,23 @@ from django.conf.urls import url
from . import views as domains_views from . import views as domains_views
urlpatterns = [ urlpatterns = [
url(r'^$', domains_views.overview, name='overview'), url(r'^$', domains_views.overview, name='overview'),
url(r'domain/create/$', domains_views.DomainCreate.as_view(), name='domain-create'), url(r'domain/create/$', domains_views.DomainCreate.as_view(), name='domain-create'),
url(r'domain/check/(?P<domain>[a-z0-9.-]+)/$', domains_views.DomainCheck.as_view(), name='domain-check'), url(r'domain/check/(?P<domain>[a-z0-9.-]+)/$', domains_views.DomainCheck.as_view(), name='domain-check'),
url(r'domain/show/(?P<domain>[a-z0-9.-]+)/$', domains_views.DomainDetail.as_view(), name='domain-show'), url(r'domain/show/(?P<domain>[a-z0-9.-]+)/$', domains_views.DomainDetail.as_view(), name='domain-show'),
url(r'domain/edit/(?P<domain>[a-z0-9.-]+)/$', domains_views.DomainEdit.as_view(), name='domain-edit'), url(r'domain/edit/(?P<domain>[a-z0-9.-]+)/$', domains_views.DomainEdit.as_view(), name='domain-edit'),
url(r'domain/delete/(?P<domain>[a-z0-9.-]+)/$', domains_views.DomainDelete.as_view(), name='domain-delete'), url(r'domain/delete/(?P<domain>[a-z0-9.-]+)/$', domains_views.DomainDelete.as_view(), name='domain-delete'),
url(r'nameserver/create/$', domains_views.NameserverCreate.as_view(), name='nameserver-create'), url(r'nameserver/create/$', domains_views.NameserverCreate.as_view(), name='nameserver-create'),
url(r'nameserver/show/(?P<pk>[0-9]+)/$', domains_views.NameserverDetail.as_view(), name='nameserver-show'), url(r'nameserver/show/(?P<pk>[0-9]+)/$', domains_views.NameserverDetail.as_view(), name='nameserver-show'),
url(r'nameserver/edit/(?P<pk>[0-9]+)/$', domains_views.NameserverEdit.as_view(), name='nameserver-edit'), url(r'nameserver/edit/(?P<pk>[0-9]+)/$', domains_views.NameserverEdit.as_view(), name='nameserver-edit'),
url(r'nameserver/delete/(?P<pk>[0-9]+)/$', domains_views.NameserverDelete.as_view(), name='nameserver-delete'), url(r'nameserver/delete/(?P<pk>[0-9]+)/$', domains_views.NameserverDelete.as_view(), name='nameserver-delete'),
url(r'reversezone/create/$', domains_views.ReverseZoneCreate.as_view(), name='reversezone-create'), url(r'reversezone/create/$', domains_views.ReverseZoneCreate.as_view(), name='reversezone-create'),
url(r'reversezone/show/(?P<pk>[0-9]+)/$', domains_views.ReverseZoneDetail.as_view(), name='reversezone-show'), url(r'reversezone/show/(?P<pk>[0-9]+)/$', domains_views.ReverseZoneDetail.as_view(), name='reversezone-show'),
url(r'reversezone/check/(?P<pk>[0-9]+)/$', domains_views.ReverseZoneCheck.as_view(), name='reversezone-check'), url(r'reversezone/check/(?P<pk>[0-9]+)/$', domains_views.ReverseZoneCheck.as_view(), name='reversezone-check'),
url(r'reversezone/edit/(?P<pk>[0-9]+)/$', domains_views.ReverseZoneEdit.as_view(), name='reversezone-edit'), url(r'reversezone/edit/(?P<pk>[0-9]+)/$', domains_views.ReverseZoneEdit.as_view(), name='reversezone-edit'),
url(r'reversezone/delete/(?P<pk>[0-9]+)/$', domains_views.ReverseZoneDelete.as_view(), name='reversezone-delete'), url(r'reversezone/delete/(?P<pk>[0-9]+)/$', domains_views.ReverseZoneDelete.as_view(), name='reversezone-delete'),
] ]

View File

@ -17,157 +17,157 @@ from .forms import DomainForm, NameserverForm, ReverseZoneForm
@login_required @login_required
def overview(request): def overview(request):
mnts = request.user.maintainer_set.all() mnts = request.user.maintainer_set.all()
# get all domains and nameservers # get all domains and nameservers
domains = Domain.objects.filter(mnt_by__in=mnts).distinct() domains = Domain.objects.filter(mnt_by__in=mnts).distinct()
nameservers = Nameserver.objects.filter(mnt_by__in=mnts).distinct() nameservers = Nameserver.objects.filter(mnt_by__in=mnts).distinct()
reversezones = ReverseZone.objects.filter(Q(parentNet__mnt_by__in=mnts) | Q(parentNet__mnt_lower__in=mnts)).distinct() reversezones = ReverseZone.objects.filter(Q(parentNet__mnt_by__in=mnts) | Q(parentNet__mnt_lower__in=mnts)).distinct()
return render(request, "domains/overview.html", {"mnts": mnts, "domains": domains, "nameservers": nameservers, 'reversezones': reversezones}) return render(request, "domains/overview.html", {"mnts": mnts, "domains": domains, "nameservers": nameservers, 'reversezones': reversezones})
class DomainCreate(LoginRequiredMixin, CreateView): class DomainCreate(LoginRequiredMixin, CreateView):
template_name = "domains/obj_create.html" template_name = "domains/obj_create.html"
form_class = DomainForm form_class = DomainForm
def get_form_kwargs(self, *args, **kwargs): def get_form_kwargs(self, *args, **kwargs):
kwargs = super(DomainCreate, self).get_form_kwargs(*args, **kwargs) kwargs = super(DomainCreate, self).get_form_kwargs(*args, **kwargs)
kwargs["user"] = self.request.user kwargs["user"] = self.request.user
return kwargs return kwargs
class DomainCheck(LoginRequiredMixin, DetailView): class DomainCheck(LoginRequiredMixin, DetailView):
model = Domain model = Domain
slug_field = "name" slug_field = "name"
slug_url_kwarg = "domain" slug_url_kwarg = "domain"
context_object_name = "domain" context_object_name = "domain"
template_name = "domains/dns_check.html" template_name = "domains/dns_check.html"
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
ctx = super(DomainCheck, self).get_context_data(**kwargs) ctx = super(DomainCheck, self).get_context_data(**kwargs)
ctx["key"] = self.object.name ctx["key"] = self.object.name
ctx["apiUrl"] = reverse("api:domain-check") ctx["apiUrl"] = reverse("api:domain-check")
return ctx return ctx
class DomainDetail(LoginRequiredMixin, DetailView): class DomainDetail(LoginRequiredMixin, DetailView):
model = Domain model = Domain
slug_field = "name" slug_field = "name"
slug_url_kwarg = "domain" slug_url_kwarg = "domain"
context_object_name = "domain" context_object_name = "domain"
template_name = "whoisdb/handle_show.html" template_name = "whoisdb/handle_show.html"
class DomainEdit(MntGenericMixin, LoginRequiredMixin, UpdateView): class DomainEdit(MntGenericMixin, LoginRequiredMixin, UpdateView):
model = Domain model = Domain
form_class = DomainForm form_class = DomainForm
slug_field = "name" slug_field = "name"
slug_url_kwarg = "domain" slug_url_kwarg = "domain"
template_name = "domains/obj_edit.html" template_name = "domains/obj_edit.html"
def get_form_kwargs(self, *args, **kwargs): def get_form_kwargs(self, *args, **kwargs):
kwargs = super(DomainEdit, self).get_form_kwargs(*args, **kwargs) kwargs = super(DomainEdit, self).get_form_kwargs(*args, **kwargs)
kwargs["user"] = self.request.user kwargs["user"] = self.request.user
return kwargs return kwargs
class DomainDelete(MntGenericMixin, LoginRequiredMixin, DeleteCheckView): class DomainDelete(MntGenericMixin, LoginRequiredMixin, DeleteCheckView):
template_name = "domains/obj_delete.html" template_name = "domains/obj_delete.html"
model = Domain model = Domain
slug_field = "name" slug_field = "name"
slug_url_kwarg = "domain" slug_url_kwarg = "domain"
success_url = reverse_lazy("domains:overview") success_url = reverse_lazy("domains:overview")
class NameserverCreate(LoginRequiredMixin, CreateView): class NameserverCreate(LoginRequiredMixin, CreateView):
template_name = "domains/obj_create.html" template_name = "domains/obj_create.html"
form_class = NameserverForm form_class = NameserverForm
def get_form_kwargs(self, *args, **kwargs): def get_form_kwargs(self, *args, **kwargs):
kwargs = super(NameserverCreate, self).get_form_kwargs(*args, **kwargs) kwargs = super(NameserverCreate, self).get_form_kwargs(*args, **kwargs)
kwargs["user"] = self.request.user kwargs["user"] = self.request.user
return kwargs return kwargs
class NameserverDetail(LoginRequiredMixin, DetailView): class NameserverDetail(LoginRequiredMixin, DetailView):
model = Nameserver model = Nameserver
template_name = "whoisdb/handle_show.html" template_name = "whoisdb/handle_show.html"
class NameserverEdit(MntGenericMixin, LoginRequiredMixin, UpdateView): class NameserverEdit(MntGenericMixin, LoginRequiredMixin, UpdateView):
model = Nameserver model = Nameserver
form_class = NameserverForm form_class = NameserverForm
template_name = "domains/obj_edit.html" template_name = "domains/obj_edit.html"
def get_form_kwargs(self, *args, **kwargs): def get_form_kwargs(self, *args, **kwargs):
kwargs = super(NameserverEdit, self).get_form_kwargs(*args, **kwargs) kwargs = super(NameserverEdit, self).get_form_kwargs(*args, **kwargs)
kwargs["user"] = self.request.user kwargs["user"] = self.request.user
return kwargs return kwargs
class NameserverDelete(MntGenericMixin, LoginRequiredMixin, DeleteCheckView): class NameserverDelete(MntGenericMixin, LoginRequiredMixin, DeleteCheckView):
template_name = "domains/obj_delete.html" template_name = "domains/obj_delete.html"
model = Nameserver model = Nameserver
success_url = reverse_lazy("domains:overview") success_url = reverse_lazy("domains:overview")
class ReverseZoneCreate(LoginRequiredMixin, CreateView): class ReverseZoneCreate(LoginRequiredMixin, CreateView):
template_name = "domains/obj_create.html" template_name = "domains/obj_create.html"
form_class = ReverseZoneForm form_class = ReverseZoneForm
def get_form_kwargs(self, *args, **kwargs): def get_form_kwargs(self, *args, **kwargs):
kwargs = super(ReverseZoneCreate, self).get_form_kwargs(*args, **kwargs) kwargs = super(ReverseZoneCreate, self).get_form_kwargs(*args, **kwargs)
kwargs["user"] = self.request.user kwargs["user"] = self.request.user
return kwargs return kwargs
class ReverseZoneCheck(LoginRequiredMixin, DetailView): class ReverseZoneCheck(LoginRequiredMixin, DetailView):
model = ReverseZone model = ReverseZone
template_name = "domains/dns_check.html" template_name = "domains/dns_check.html"
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
ctx = super(ReverseZoneCheck, self).get_context_data(**kwargs) ctx = super(ReverseZoneCheck, self).get_context_data(**kwargs)
ctx["key"] = self.object.id ctx["key"] = self.object.id
ctx["apiUrl"] = reverse("api:reversezone-check") ctx["apiUrl"] = reverse("api:reversezone-check")
return ctx return ctx
class ReverseZoneDetail(LoginRequiredMixin, DetailView): class ReverseZoneDetail(LoginRequiredMixin, DetailView):
model = ReverseZone model = ReverseZone
template_name = "whoisdb/handle_show.html" template_name = "whoisdb/handle_show.html"
class ReverseZoneEdit(MntGenericMixin, LoginRequiredMixin, UpdateView): class ReverseZoneEdit(MntGenericMixin, LoginRequiredMixin, UpdateView):
model = ReverseZone model = ReverseZone
form_class = ReverseZoneForm form_class = ReverseZoneForm
template_name = "domains/obj_edit.html" template_name = "domains/obj_edit.html"
def get_form_kwargs(self, *args, **kwargs): def get_form_kwargs(self, *args, **kwargs):
kwargs = super(ReverseZoneEdit, self).get_form_kwargs(*args, **kwargs) kwargs = super(ReverseZoneEdit, self).get_form_kwargs(*args, **kwargs)
kwargs["user"] = self.request.user kwargs["user"] = self.request.user
if "initial" not in kwargs: if "initial" not in kwargs:
kwargs["initial"] = {} kwargs["initial"] = {}
kwargs["initial"]["prefix"] = self.object.prefix() kwargs["initial"]["prefix"] = self.object.prefix()
return kwargs return kwargs
def get_queryset(self): def get_queryset(self):
mnts = self.request.user.maintainer_set.all() mnts = self.request.user.maintainer_set.all()
return ReverseZone.objects.filter(Q(parentNet__mnt_by__in=mnts) | Q(parentNet__mnt_lower__in=mnts)).distinct() return ReverseZone.objects.filter(Q(parentNet__mnt_by__in=mnts) | Q(parentNet__mnt_lower__in=mnts)).distinct()
class ReverseZoneDelete(LoginRequiredMixin, DeleteCheckView): class ReverseZoneDelete(LoginRequiredMixin, DeleteCheckView):
template_name = "domains/obj_delete.html" template_name = "domains/obj_delete.html"
model = ReverseZone model = ReverseZone
success_url = reverse_lazy("domains:overview") success_url = reverse_lazy("domains:overview")
def get_queryset(self): def get_queryset(self):
mnts = self.request.user.maintainer_set.all() mnts = self.request.user.maintainer_set.all()
return ReverseZone.objects.filter(Q(parentNet__mnt_by__in=mnts) | Q(parentNet__mnt_lower__in=mnts)).distinct() return ReverseZone.objects.filter(Q(parentNet__mnt_by__in=mnts) | Q(parentNet__mnt_lower__in=mnts)).distinct()

View File

@ -9,51 +9,51 @@ from .models import Request
from whoisdb.models import Maintainer from whoisdb.models import Maintainer
class RequestForm(forms.Form): class RequestForm(forms.Form):
RESOURCES = map(lambda _x: (_x, _x), [ RESOURCES = map(lambda _x: (_x, _x), [
"AS Number (16bit)", "IPv4 /27", "IPv4 > /27", "IPv6", "other" "AS Number (16bit)", "IPv4 /27", "IPv4 > /27", "IPv6", "other"
]) ])
applicant = forms.ModelChoiceField(Maintainer.objects.none(), label="Applicant (you)", help_text="Maintainer you want to request resources for") applicant = forms.ModelChoiceField(Maintainer.objects.none(), label="Applicant (you)", help_text="Maintainer you want to request resources for")
provider = forms.ModelChoiceField(Maintainer.objects.none(), label="Provider", help_text="LIR/RIR you want to request resources from") provider = forms.ModelChoiceField(Maintainer.objects.none(), label="Provider", help_text="LIR/RIR you want to request resources from")
subject = forms.CharField(label="Subject") subject = forms.CharField(label="Subject")
resources = forms.CheckboxSelectMultiple(choices=RESOURCES) resources = forms.CheckboxSelectMultiple(choices=RESOURCES)
message = forms.CharField(widget=forms.Textarea, help_text="Describe shortly what resources you need and what you need them for") message = forms.CharField(widget=forms.Textarea, help_text="Describe shortly what resources you need and what you need them for")
def __init__(self, user, *args, **kwargs): def __init__(self, user, *args, **kwargs):
super(RequestForm, self).__init__(*args, **kwargs) super(RequestForm, self).__init__(*args, **kwargs)
self._user = user self._user = user
self.fields['applicant'].queryset = self._user.maintainer_set.all() self.fields['applicant'].queryset = self._user.maintainer_set.all()
self.fields['provider'].queryset = Maintainer.objects.filter(Q(rir=True) | Q(lir=True)) self.fields['provider'].queryset = Maintainer.objects.filter(Q(rir=True) | Q(lir=True))
def clean(self): def clean(self):
cleaned_data = super(RequestForm, self).clean() cleaned_data = super(RequestForm, self).clean()
if not self.errors: if not self.errors:
mnts = self._user.maintainer_set.all() mnts = self._user.maintainer_set.all()
if cleaned_data['applicant'] in mnts and cleaned_data['provider'] in mnts: if cleaned_data['applicant'] in mnts and cleaned_data['provider'] in mnts:
raise forms.ValidationError("You could request resources from yourself, but this would actually not make that much sense.") raise forms.ValidationError("You could request resources from yourself, but this would actually not make that much sense.")
class ResponseForm(forms.Form): class ResponseForm(forms.Form):
new_status = forms.ChoiceField(choices=[("KEEP", "----", )] + list(Request.STATES), initial="KEEP", help_text="Only set this if you want to change the status of this request") new_status = forms.ChoiceField(choices=[("KEEP", "----", )] + list(Request.STATES), initial="KEEP", help_text="Only set this if you want to change the status of this request")
message = forms.CharField(widget=forms.Textarea) message = forms.CharField(widget=forms.Textarea)
def __init__(self, request, user, *args, **kwargs): def __init__(self, request, user, *args, **kwargs):
super(ResponseForm, self).__init__(*args, **kwargs) super(ResponseForm, self).__init__(*args, **kwargs)
self._request = request self._request = request
self._user = user self._user = user
def clean(self): def clean(self):
cleaned_data = super(ResponseForm, self).clean() cleaned_data = super(ResponseForm, self).clean()
if not self.errors: if not self.errors:
if cleaned_data['new_status'] == self._request.status: if cleaned_data['new_status'] == self._request.status:
raise forms.ValidationError("Status changed to same as ticket") raise forms.ValidationError("Status changed to same as ticket")
if self._request.status in (Request.STATE_RESOLVED, Request.STATE_REJECTED) and \ if self._request.status in (Request.STATE_RESOLVED, Request.STATE_REJECTED) and \
cleaned_data['new_status'] not in (Request.STATE_OPEN,): cleaned_data['new_status'] not in (Request.STATE_OPEN,):
raise forms.ValidationError("Please put this ticket in an open state before adding messages") raise forms.ValidationError("Please put this ticket in an open state before adding messages")
class ProviderResponseForm(ResponseForm): class ProviderResponseForm(ResponseForm):
createdResources = forms.CharField(label="Created resources", help_text="If you have created resources for this request, please enter their handles here", required=False) createdResources = forms.CharField(label="Created resources", help_text="If you have created resources for this request, please enter their handles here", required=False)

View File

@ -8,49 +8,49 @@ from django.urls import reverse
from whoisdb.models import Maintainer from whoisdb.models import Maintainer
class Request(models.Model): class Request(models.Model):
STATE_OPEN = "OPEN" STATE_OPEN = "OPEN"
STATE_RESOLVED = "RESOLVED" STATE_RESOLVED = "RESOLVED"
STATE_REJECTED = "REJECTED" STATE_REJECTED = "REJECTED"
STATES = ( STATES = (
(STATE_OPEN, 'Open'), (STATE_OPEN, 'Open'),
(STATE_RESOLVED, 'Resolved'), (STATE_RESOLVED, 'Resolved'),
(STATE_REJECTED, 'Rejected'), (STATE_REJECTED, 'Rejected'),
) )
# request goes to mnt? # request goes to mnt?
subject = models.CharField(max_length=200) subject = models.CharField(max_length=200)
status = models.CharField(max_length=16, choices=STATES) status = models.CharField(max_length=16, choices=STATES)
applicant = models.ForeignKey(Maintainer) applicant = models.ForeignKey(Maintainer)
provider = models.ForeignKey(Maintainer, related_name='requestfrom_set') provider = models.ForeignKey(Maintainer, related_name='requestfrom_set')
requestResources = models.TextField() requestResources = models.TextField()
grantedResources = models.TextField() grantedResources = models.TextField()
created = models.DateTimeField(auto_now_add=True) created = models.DateTimeField(auto_now_add=True)
lastAction = models.DateTimeField(auto_now_add=True) lastAction = models.DateTimeField(auto_now_add=True)
def get_absolute_url(self): def get_absolute_url(self):
return reverse("rrequests:show", args=(self.pk,)) return reverse("rrequests:show", args=(self.pk,))
def __str__(self): def __str__(self):
return "(%s -> %s) [%s] %s" % (self.applicant, self.provider, self.status, self.subject) return "(%s -> %s) [%s] %s" % (self.applicant, self.provider, self.status, self.subject)
def getLastActionBy(self): def getLastActionBy(self):
msgs = self.requestmessage_set.order_by("-created") msgs = self.requestmessage_set.order_by("-created")
if msgs.count() > 0: if msgs.count() > 0:
return msgs[0].creator return msgs[0].creator
else: else:
return None return None
class RequestMessage(models.Model): class RequestMessage(models.Model):
request = models.ForeignKey(Request) request = models.ForeignKey(Request)
statusChanged = models.CharField(max_length=16, choices=Request.STATES, default=None, null=True, blank=True) statusChanged = models.CharField(max_length=16, choices=Request.STATES, default=None, null=True, blank=True)
creator = models.ForeignKey(Maintainer) creator = models.ForeignKey(Maintainer)
message = models.TextField() message = models.TextField()
created = models.DateTimeField(auto_now_add=True) created = models.DateTimeField(auto_now_add=True)

View File

@ -7,8 +7,8 @@ from django.conf.urls import url
from . import views as rrequests_views from . import views as rrequests_views
urlpatterns = [ urlpatterns = [
url(r'^$', rrequests_views.listRequests, name='dashboard'), url(r'^$', rrequests_views.listRequests, name='dashboard'),
url(r'create/$', rrequests_views.RrequestCreate.as_view(), name='create'), url(r'create/$', rrequests_views.RrequestCreate.as_view(), name='create'),
url(r'show/(?P<pk>\d+)/$', rrequests_views.rrequestDetail, name='show'), url(r'show/(?P<pk>\d+)/$', rrequests_views.rrequestDetail, name='show'),
] ]

View File

@ -17,88 +17,88 @@ from .forms import RequestForm, ResponseForm, ProviderResponseForm
@login_required @login_required
def listRequests(request): def listRequests(request):
mnts = request.user.maintainer_set.all() mnts = request.user.maintainer_set.all()
requestedFromMe = Request.objects.filter(applicant__in=mnts) requestedFromMe = Request.objects.filter(applicant__in=mnts)
requestedToMe = Request.objects.filter(provider__in=mnts) requestedToMe = Request.objects.filter(provider__in=mnts)
requests = (requestedFromMe | requestedToMe).order_by("-lastAction") requests = (requestedFromMe | requestedToMe).order_by("-lastAction")
return render(request, "rrequests/list.html", {"mnts": mnts, "requests": requests, "requestedFromMe": requestedFromMe, "requestedToMe": requestedToMe}) return render(request, "rrequests/list.html", {"mnts": mnts, "requests": requests, "requestedFromMe": requestedFromMe, "requestedToMe": requestedToMe})
class RrequestCreate(LoginRequiredMixin, FormView): class RrequestCreate(LoginRequiredMixin, FormView):
template_name = "rrequests/request_create.html" template_name = "rrequests/request_create.html"
form_class = RequestForm form_class = RequestForm
def get_form_kwargs(self, *args, **kwargs): def get_form_kwargs(self, *args, **kwargs):
kwargs = super(RrequestCreate, self).get_form_kwargs(*args, **kwargs) kwargs = super(RrequestCreate, self).get_form_kwargs(*args, **kwargs)
kwargs['user'] = self.request.user kwargs['user'] = self.request.user
return kwargs return kwargs
def form_valid(self, form): def form_valid(self, form):
formData = form.cleaned_data formData = form.cleaned_data
print(formData) print(formData)
request = Request( request = Request(
subject=formData['subject'], subject=formData['subject'],
status=Request.STATE_OPEN, status=Request.STATE_OPEN,
applicant=formData['applicant'], applicant=formData['applicant'],
provider=formData['provider'], provider=formData['provider'],
) )
request.save() request.save()
requestMsg = RequestMessage( requestMsg = RequestMessage(
request=request, request=request,
creator=formData['applicant'], creator=formData['applicant'],
message=formData['message'], message=formData['message'],
) )
requestMsg.save() requestMsg.save()
return HttpResponseRedirect(request.get_absolute_url()) return HttpResponseRedirect(request.get_absolute_url())
@login_required @login_required
def rrequestDetail(request, pk): def rrequestDetail(request, pk):
mnts = request.user.maintainer_set.all() mnts = request.user.maintainer_set.all()
reqObj = get_object_or_404(Request.objects.filter(Q(provider__in=mnts) | Q(applicant__in=mnts)), pk=pk) reqObj = get_object_or_404(Request.objects.filter(Q(provider__in=mnts) | Q(applicant__in=mnts)), pk=pk)
mnts = request.user.maintainer_set.all() mnts = request.user.maintainer_set.all()
formClass = None formClass = None
provider = None provider = None
initialFormData = {} initialFormData = {}
if reqObj.provider in mnts: if reqObj.provider in mnts:
provider = True provider = True
formClass = ProviderResponseForm formClass = ProviderResponseForm
initialFormData["createdResources"] = reqObj.grantedResources initialFormData["createdResources"] = reqObj.grantedResources
else: else:
provider = False provider = False
formClass = ResponseForm formClass = ResponseForm
if request.method == "POST": if request.method == "POST":
form = formClass(request=reqObj, user=request.user, data=request.POST) form = formClass(request=reqObj, user=request.user, data=request.POST)
if form.is_valid(): if form.is_valid():
# create message object # create message object
msg = RequestMessage( msg = RequestMessage(
request=reqObj, request=reqObj,
creator=reqObj.provider if provider else reqObj.applicant, creator=reqObj.provider if provider else reqObj.applicant,
message=form.cleaned_data['message'] message=form.cleaned_data['message']
) )
if form.cleaned_data['new_status'] != "KEEP": if form.cleaned_data['new_status'] != "KEEP":
msg.statusChanged = form.cleaned_data['new_status'] msg.statusChanged = form.cleaned_data['new_status']
reqObj.status = msg.statusChanged reqObj.status = msg.statusChanged
msg.save() msg.save()
if "createdResources" in form.cleaned_data and \ if "createdResources" in form.cleaned_data and \
form.cleaned_data['createdResources'].strip() != "": form.cleaned_data['createdResources'].strip() != "":
reqObj.grantedResources = form.cleaned_data["createdResources"].strip() reqObj.grantedResources = form.cleaned_data["createdResources"].strip()
reqObj.lastAction = timezone.now() reqObj.lastAction = timezone.now()
reqObj.save() reqObj.save()
return HttpResponseRedirect(reverse("rrequests:show", args=(pk,))) return HttpResponseRedirect(reverse("rrequests:show", args=(pk,)))
else: else:
form = formClass(request=reqObj, user=request.user, initial=initialFormData) form = formClass(request=reqObj, user=request.user, initial=initialFormData)
return render(request, "rrequests/request_detail.html", {"request": reqObj, "form": form}) return render(request, "rrequests/request_detail.html", {"request": reqObj, "form": form})

View File

@ -6,33 +6,33 @@ from django import forms
class MultiTextInput(forms.widgets.Input): class MultiTextInput(forms.widgets.Input):
input_type = "text" input_type = "text"
def render(self, name, value, attrs=None): def render(self, name, value, attrs=None):
if value is not None: if value is not None:
selectedOptions = [] selectedOptions = []
for val in value: for val in value:
for k, v in self.choices: for k, v in self.choices:
if val == k: if val == k:
selectedOptions.append(v) selectedOptions.append(v)
value = " ".join(selectedOptions) value = " ".join(selectedOptions)
return super(MultiTextInput, self).render(name, value, attrs) return super(MultiTextInput, self).render(name, value, attrs)
def value_from_datadict(self, data, files, name): def value_from_datadict(self, data, files, name):
values = list(filter(bool, map(lambda _x: _x.strip(), data.get(name).split(" ")))) values = list(filter(bool, map(lambda _x: _x.strip(), data.get(name).split(" "))))
result = [] result = []
for value in values: for value in values:
# FIXME: using value here throws a weird error message at some point # FIXME: using value here throws a weird error message at some point
# could be handled by overriding the messages in ChoiceField # could be handled by overriding the messages in ChoiceField
# or... well, don't know # or... well, don't know
kId = value kId = value
for k, v in self.choices: for k, v in self.choices:
if v.lower() == value.lower(): if v.lower() == value.lower():
kId = str(k) kId = str(k)
break break
result.append(kId) result.append(kId)
return result return result

View File

@ -15,351 +15,351 @@ import ipaddress
class WhoisObjectFormMixin(object): class WhoisObjectFormMixin(object):
def __init__(self, user, *args, **kwargs): def __init__(self, user, *args, **kwargs):
super(WhoisObjectFormMixin, self).__init__(*args, **kwargs) super(WhoisObjectFormMixin, self).__init__(*args, **kwargs)
self._user = user self._user = user
instance = getattr(self, 'instance', None) instance = getattr(self, 'instance', None)
if instance and instance.pk: if instance and instance.pk:
self._create = False self._create = False
#self.fields['handle'].disabled = True #self.fields['handle'].disabled = True
else: else:
self._create = True self._create = True
if 'handle' in self.fields: if 'handle' in self.fields:
self.fields['handle'].help_text = "Handle for this object in uppercase with a suffix of -%s" % instance.handleSuffix self.fields['handle'].help_text = "Handle for this object in uppercase with a suffix of -%s" % instance.handleSuffix
# only show users contacts and already present contacts # only show users contacts and already present contacts
mnts = self._user.maintainer_set.all() mnts = self._user.maintainer_set.all()
if 'admin_c' in self.fields: if 'admin_c' in self.fields:
self.fields['admin_c'].queryset = Contact.getMntQueryset(mnts, self.instance, "admin_c") self.fields['admin_c'].queryset = Contact.getMntQueryset(mnts, self.instance, "admin_c")
def clean_handle(self): def clean_handle(self):
HandleValidatorWithSuffix(self.instance.handleSuffix)(self.cleaned_data['handle']) HandleValidatorWithSuffix(self.instance.handleSuffix)(self.cleaned_data['handle'])
return self.cleaned_data['handle'] return self.cleaned_data['handle']
def clean(self): def clean(self):
cleaned_data = super(WhoisObjectFormMixin, self).clean() cleaned_data = super(WhoisObjectFormMixin, self).clean()
if cleaned_data.get("handle") == "AUTO" and not self.errors: if cleaned_data.get("handle") == "AUTO" and not self.errors:
name = cleaned_data.get("name") name = cleaned_data.get("name")
if name is None: if name is None:
name = self._user.username name = self._user.username
cleaned_data['handle'] = self._meta.model.genGenericHandle(name) cleaned_data['handle'] = self._meta.model.genGenericHandle(name)
# XXX: Find a better position to update last_changed # XXX: Find a better position to update last_changed
self.instance.last_modified = timezone.now() self.instance.last_modified = timezone.now()
return cleaned_data return cleaned_data
class MntFormMixin(object): class MntFormMixin(object):
protectedFields = [] protectedFields = []
def __init__(self, lower=False, *args, **kwargs): def __init__(self, lower=False, *args, **kwargs):
super(MntFormMixin, self).__init__(*args, **kwargs) super(MntFormMixin, self).__init__(*args, **kwargs)
self._editLower = lower self._editLower = lower
if self._editLower: if self._editLower:
for key in self.protectedFields: for key in self.protectedFields:
self.fields[key].disabled = True self.fields[key].disabled = True
instance = getattr(self, "instance", None) instance = getattr(self, "instance", None)
if not hasattr(self, "_create"): if not hasattr(self, "_create"):
self._create = not (instance and instance.pk) self._create = not (instance and instance.pk)
mntQs = orderQueryset(Maintainer.objects.all(), Q(auth=self._user), Q(pk__in=instance.mnt_by.all()) if not self._create else None) mntQs = orderQueryset(Maintainer.objects.all(), Q(auth=self._user), Q(pk__in=instance.mnt_by.all()) if not self._create else None)
self.fields["mnt_by"].queryset = mntQs self.fields["mnt_by"].queryset = mntQs
if "mnt_lower" in self.fields: if "mnt_lower" in self.fields:
self.fields["mnt_lower"].queryset = mntQs self.fields["mnt_lower"].queryset = mntQs
def clean(self): def clean(self):
cleaned_data = super(MntFormMixin, self).clean() cleaned_data = super(MntFormMixin, self).clean()
if not self.errors: if not self.errors:
if self._create: if self._create:
# at least one own mnt on creation # at least one own mnt on creation
mnts = self._user.maintainer_set.all() mnts = self._user.maintainer_set.all()
for mnt in cleaned_data['mnt_by']: for mnt in cleaned_data['mnt_by']:
if mnt in mnts: if mnt in mnts:
break break
else: else:
raise forms.ValidationError("On object creation at least one maintainer needs to be under your control") raise forms.ValidationError("On object creation at least one maintainer needs to be under your control")
if cleaned_data['mnt_by'].count() > 5 or "mnt_lower" in self.fields and cleaned_data['mnt_lower'].count() > 5: if cleaned_data['mnt_by'].count() > 5 or "mnt_lower" in self.fields and cleaned_data['mnt_lower'].count() > 5:
raise forms.ValidationError("Currently only 5 MNTs per object allowed. If you need more ask @ irc/rrequest and state your use case") raise forms.ValidationError("Currently only 5 MNTs per object allowed. If you need more ask @ irc/rrequest and state your use case")
return cleaned_data return cleaned_data
class MntForm(WhoisObjectFormMixin, forms.ModelForm): class MntForm(WhoisObjectFormMixin, forms.ModelForm):
class Meta: class Meta:
model = Maintainer model = Maintainer
#fields = ['handle', 'description', 'admin_c', 'auth'] #fields = ['handle', 'description', 'admin_c', 'auth']
fields = ['handle', 'description', 'admin_c'] fields = ['handle', 'description', 'admin_c']
widgets = {'auth': MultiTextInput()} widgets = {'auth': MultiTextInput()}
help_texts = { help_texts = {
'auth': 'Enter names of users which can edit this object (space separated; ' 'auth': 'Enter names of users which can edit this object (space separated; '
'and yes, validation is somewhat broken (values disappear on error - just reload))' 'and yes, validation is somewhat broken (values disappear on error - just reload))'
} }
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(MntForm, self).__init__(*args, **kwargs) super(MntForm, self).__init__(*args, **kwargs)
print(args, kwargs) print(args, kwargs)
#if self._create: #if self._create:
# self.fields['auth'].text("noot") # self.fields['auth'].text("noot")
class MntInitialForm(MntForm): class MntInitialForm(MntForm):
class Meta: class Meta:
model = Maintainer model = Maintainer
fields = ['handle', 'description'] fields = ['handle', 'description']
class ContactForm(WhoisObjectFormMixin, forms.ModelForm): class ContactForm(WhoisObjectFormMixin, forms.ModelForm):
class Meta: class Meta:
model = Contact model = Contact
fields = ['handle', 'name', 'mnt_by'] fields = ['handle', 'name', 'mnt_by']
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(ContactForm, self).__init__(*args, **kwargs) super(ContactForm, self).__init__(*args, **kwargs)
if "mnt_by" in self.fields: if "mnt_by" in self.fields:
self.fields['mnt_by'].queryset = Maintainer.objects.filter(auth=self._user).distinct() self.fields['mnt_by'].queryset = Maintainer.objects.filter(auth=self._user).distinct()
class ContactInitialForm(ContactForm): class ContactInitialForm(ContactForm):
class Meta: class Meta:
model = Contact model = Contact
fields = ['handle', 'name'] fields = ['handle', 'name']
class InetNumForm(MntFormMixin, WhoisObjectFormMixin, forms.ModelForm): class InetNumForm(MntFormMixin, WhoisObjectFormMixin, forms.ModelForm):
prefix = forms.CharField() prefix = forms.CharField()
protectedFields = ['protocol', 'parent_range', 'mnt_by', 'prefix'] protectedFields = ['protocol', 'parent_range', 'mnt_by', 'prefix']
class Meta: class Meta:
model = InetNum model = InetNum
fields = ['handle', 'protocol', 'parent_range', 'prefix', 'name', 'description', 'origin_as', 'mnt_by', 'mnt_lower', 'admin_c'] fields = ['handle', 'protocol', 'parent_range', 'prefix', 'name', 'description', 'origin_as', 'mnt_by', 'mnt_lower', 'admin_c']
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(InetNumForm, self).__init__(*args, **kwargs) super(InetNumForm, self).__init__(*args, **kwargs)
if self._editLower: if self._editLower:
for key in self.protectedFields: for key in self.protectedFields:
self.fields[key].disabled = True self.fields[key].disabled = True
mnts = self._user.maintainer_set.all() mnts = self._user.maintainer_set.all()
#self.fields['parent_range'].queryset = InetNum.objects.filter(Q(mnt_by__in=mnts) | Q(mnt_lower__in=mnts)) #self.fields['parent_range'].queryset = InetNum.objects.filter(Q(mnt_by__in=mnts) | Q(mnt_lower__in=mnts))
#if not self._create: #if not self._create:
# self.fields['parent_range'].queryset |= InetNum.objects.filter(pk=self.instance.pk) # self.fields['parent_range'].queryset |= InetNum.objects.filter(pk=self.instance.pk)
#self.fields['parent_range'].queryset = self.fields['parent_range'].queryset.distinct() #self.fields['parent_range'].queryset = self.fields['parent_range'].queryset.distinct()
self.fields['parent_range'].queryset = InetNum.getMntQueryset(mnts, self.instance, "parent_range") self.fields['parent_range'].queryset = InetNum.getMntQueryset(mnts, self.instance, "parent_range")
self.fields['origin_as'].queryset = ASNumber.getMntQueryset(mnts, self.instance, "origin_as") self.fields['origin_as'].queryset = ASNumber.getMntQueryset(mnts, self.instance, "origin_as")
def clean_prefix(self): def clean_prefix(self):
# make sure this is a subnet we're getting # make sure this is a subnet we're getting
net = self.cleaned_data['prefix'].lower() net = self.cleaned_data['prefix'].lower()
IP46CIDRValidator(net) IP46CIDRValidator(net)
try: try:
net = ipaddress.ip_network(net) net = ipaddress.ip_network(net)
except ValueError as e: except ValueError as e:
raise forms.ValidationError(str(e)) raise forms.ValidationError(str(e))
return net return net
def clean_parent_range(self): def clean_parent_range(self):
parent_range = self.cleaned_data.get('parent_range', None) parent_range = self.cleaned_data.get('parent_range', None)
# allow parent range to be unset for already present objects # allow parent range to be unset for already present objects
if not parent_range and (self._create or not self._create and self.instance.parent_range): if not parent_range and (self._create or not self._create and self.instance.parent_range):
raise forms.ValidationError("Parent range must be set") raise forms.ValidationError("Parent range must be set")
if not self._create and parent_range: if not self._create and parent_range:
# make sure we don't have circular dependencies # make sure we don't have circular dependencies
obj = parent_range obj = parent_range
while obj.parent_range: while obj.parent_range:
if obj.pk == self.instance.pk: if obj.pk == self.instance.pk:
raise forms.ValidationError("No circular dependencies allowed") raise forms.ValidationError("No circular dependencies allowed")
obj = obj.parent_range obj = obj.parent_range
if parent_range.origin_as.count() > 0: if parent_range.origin_as.count() > 0:
raise forms.ValidationError("Parent range has origin as set") raise forms.ValidationError("Parent range has origin as set")
return parent_range return parent_range
def clean(self): def clean(self):
cleaned_data = super(InetNumForm, self).clean() cleaned_data = super(InetNumForm, self).clean()
if not self._editLower: if not self._editLower:
if not self.errors: if not self.errors:
if not self._create and self.cleaned_data['origin_as']: if not self._create and self.cleaned_data['origin_as']:
if self.instance.inetnum_set.count() > 0: if self.instance.inetnum_set.count() > 0:
ranges = ", ".join(map(str, self.instance.inetnum_set.all())) ranges = ", ".join(map(str, self.instance.inetnum_set.all()))
raise forms.ValidationError("You cannot set an origin as if there are already existing subranges (%s)" % (ranges)) raise forms.ValidationError("You cannot set an origin as if there are already existing subranges (%s)" % (ranges))
prefix = cleaned_data['prefix'] prefix = cleaned_data['prefix']
parent = cleaned_data['parent_range'] parent = cleaned_data['parent_range']
if parent: if parent:
parentNet = parent.getNetwork() parentNet = parent.getNetwork()
if cleaned_data['protocol'] != parent.protocol: if cleaned_data['protocol'] != parent.protocol:
raise forms.ValidationError("Protocol type for prefix must be same as parent network") raise forms.ValidationError("Protocol type for prefix must be same as parent network")
# check if in parent block # check if in parent block
if prefix.network_address not in parentNet or prefix.prefixlen < parentNet.prefixlen: if prefix.network_address not in parentNet or prefix.prefixlen < parentNet.prefixlen:
raise forms.ValidationError("Prefix must be inside parent network range") raise forms.ValidationError("Prefix must be inside parent network range")
# check if parent block has net that overlaps with us # check if parent block has net that overlaps with us
for otherNet in parent.inetnum_set.all(): for otherNet in parent.inetnum_set.all():
if self.instance and self.instance.pk == otherNet.pk: if self.instance and self.instance.pk == otherNet.pk:
continue continue
if otherNet.getNetwork().overlaps(prefix): if otherNet.getNetwork().overlaps(prefix):
raise forms.ValidationError("The given prefix overlaps with network %s" % otherNet.handle) raise forms.ValidationError("The given prefix overlaps with network %s" % otherNet.handle)
# check if subnets to this subnet are (still) in current network # check if subnets to this subnet are (still) in current network
if not self._create: if not self._create:
for subnet in self.instance.inetnum_set.all(): for subnet in self.instance.inetnum_set.all():
if subnet.getNetwork().network_address not in self.instance.getNetwork(): if subnet.getNetwork().network_address not in self.instance.getNetwork():
raise forms.ValidationError("Subnet %s with %s is not in block anymore" % (subnet, subnet.getNetwork())) raise forms.ValidationError("Subnet %s with %s is not in block anymore" % (subnet, subnet.getNetwork()))
self.instance.address = str(prefix.network_address) self.instance.address = str(prefix.network_address)
self.instance.netmask = prefix.prefixlen self.instance.netmask = prefix.prefixlen
return cleaned_data return cleaned_data
class ASBlockForm(MntFormMixin, WhoisObjectFormMixin, forms.ModelForm): class ASBlockForm(MntFormMixin, WhoisObjectFormMixin, forms.ModelForm):
protectedFields = ['parent_block', 'asBegin', 'asEnd', 'mnt_by'] protectedFields = ['parent_block', 'asBegin', 'asEnd', 'mnt_by']
# FIXME: Filter blocks # FIXME: Filter blocks
class Meta: class Meta:
model = ASBlock model = ASBlock
fields = ['handle', 'parent_block', 'asBegin', 'asEnd', 'name', 'description', 'mnt_by', 'mnt_lower', 'admin_c'] fields = ['handle', 'parent_block', 'asBegin', 'asEnd', 'name', 'description', 'mnt_by', 'mnt_lower', 'admin_c']
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(ASBlockForm, self).__init__(*args, **kwargs) super(ASBlockForm, self).__init__(*args, **kwargs)
if not self.instance or self.instance and self.instance and self.instance.parent_block: if not self.instance or self.instance and self.instance and self.instance.parent_block:
self.fields["parent_block"].required = True self.fields["parent_block"].required = True
if self.instance and self.instance.pk: if self.instance and self.instance.pk:
self.fields["parent_block"].disabled = True self.fields["parent_block"].disabled = True
mnts = self._user.maintainer_set.all() mnts = self._user.maintainer_set.all()
self.fields['parent_block'].queryset = ASBlock.getMntQueryset(mnts, self.instance, "parent_block") self.fields['parent_block'].queryset = ASBlock.getMntQueryset(mnts, self.instance, "parent_block")
def clean_parent_block(self): def clean_parent_block(self):
parent_block = self.cleaned_data.get('parent_block', None) parent_block = self.cleaned_data.get('parent_block', None)
# allow parent range to be unset for already present objects # allow parent range to be unset for already present objects
if not parent_block and (self._create or not self._create and self.instance.parent_block): if not parent_block and (self._create or not self._create and self.instance.parent_block):
raise forms.ValidationError("Parent block must be set") raise forms.ValidationError("Parent block must be set")
if not self._create and parent_block: if not self._create and parent_block:
# make sure we don't have circular dependencies # make sure we don't have circular dependencies
obj = parent_block obj = parent_block
while obj.parent_block: while obj.parent_block:
if obj.pk == self.instance.pk: if obj.pk == self.instance.pk:
raise forms.ValidationError("No circular dependencies allowed") raise forms.ValidationError("No circular dependencies allowed")
obj = obj.parent_block obj = obj.parent_block
return parent_block return parent_block
def clean(self): def clean(self):
cleaned_data = super(ASBlockForm, self).clean() cleaned_data = super(ASBlockForm, self).clean()
if not self.errors: if not self.errors:
asBegin = cleaned_data['asBegin'] asBegin = cleaned_data['asBegin']
asEnd = cleaned_data['asEnd'] asEnd = cleaned_data['asEnd']
parent = cleaned_data['parent_block'] parent = cleaned_data['parent_block']
# check if somebody is already using this block # check if somebody is already using this block
# check if in range # check if in range
if asBegin > asEnd: if asBegin > asEnd:
raise forms.ValidationError("AS beginning must be smaller or equal to AS end") raise forms.ValidationError("AS beginning must be smaller or equal to AS end")
if parent: if parent:
if parent.asnumber_set.count() > 0: if parent.asnumber_set.count() > 0:
raise forms.ValidationError("The parent AS block is already references by following AS number objects: %s" % (", ".join(map(lambda _x: _x.handle, parent.asnumber_set.all())),)) raise forms.ValidationError("The parent AS block is already references by following AS number objects: %s" % (", ".join(map(lambda _x: _x.handle, parent.asnumber_set.all())),))
# check if same range # check if same range
if not (asBegin >= parent.asBegin and asEnd <= parent.asEnd): if not (asBegin >= parent.asBegin and asEnd <= parent.asEnd):
raise forms.ValidationError("AS beginning and end must be inside the range of the parent AS block") raise forms.ValidationError("AS beginning and end must be inside the range of the parent AS block")
if parent.asBegin == asBegin and parent.asEnd == asEnd: if parent.asBegin == asBegin and parent.asEnd == asEnd:
raise forms.ValidationError("The range of this block cannot be the same range AS the parent AS block") raise forms.ValidationError("The range of this block cannot be the same range AS the parent AS block")
# check for overlap with other asblocks # check for overlap with other asblocks
for block in parent.asblock_set.all(): for block in parent.asblock_set.all():
if self.instance and self.instance.pk and block.pk == self.instance.pk: if self.instance and self.instance.pk and block.pk == self.instance.pk:
continue continue
if block.asBegin <= asBegin <= block.asEnd or block.asBegin <= asEnd <= block.asEnd or \ if block.asBegin <= asBegin <= block.asEnd or block.asBegin <= asEnd <= block.asEnd or \
asBegin <= block.asBegin <= asEnd or asBegin <= block.asEnd <= asEnd: asBegin <= block.asBegin <= asEnd or asBegin <= block.asEnd <= asEnd:
raise forms.ValidationError("Block overlaps with block %s" % block.handle) raise forms.ValidationError("Block overlaps with block %s" % block.handle)
if not self._create: if not self._create:
# check if subblocks are still in range # check if subblocks are still in range
for subblock in self.instance.asblock_set.all(): for subblock in self.instance.asblock_set.all():
if not (asBegin <= subblock.asBegin <= asEnd and asBegin <= subblock.asEnd <= asEnd): if not (asBegin <= subblock.asBegin <= asEnd and asBegin <= subblock.asEnd <= asEnd):
raise forms.ValidationError("Subblock %s (%s - %s) is not contained in this block anymore" % (subblock, subblock.asBegin, subblock.asEnd)) raise forms.ValidationError("Subblock %s (%s - %s) is not contained in this block anymore" % (subblock, subblock.asBegin, subblock.asEnd))
# check if asnumbers are still in range # check if asnumbers are still in range
for asnumber in self.instance.asnumber_set.all(): for asnumber in self.instance.asnumber_set.all():
if not (asBegin <= asnumber.number <= asEnd): if not (asBegin <= asnumber.number <= asEnd):
raise forms.ValidationError("AS %s (%s) is not contained in this block anymore" % (asnumber, asnumber.number)) raise forms.ValidationError("AS %s (%s) is not contained in this block anymore" % (asnumber, asnumber.number))
return cleaned_data return cleaned_data
class ASNumberForm(MntFormMixin, WhoisObjectFormMixin, forms.ModelForm): class ASNumberForm(MntFormMixin, WhoisObjectFormMixin, forms.ModelForm):
protectedFields = ['asblock', 'number', 'mnt_by'] protectedFields = ['asblock', 'number', 'mnt_by']
class Meta: class Meta:
model = ASNumber model = ASNumber
fields = ['handle', 'asblock', 'number', 'volatile', 'name', 'description', 'mnt_by', 'mnt_lower', 'admin_c'] fields = ['handle', 'asblock', 'number', 'volatile', 'name', 'description', 'mnt_by', 'mnt_lower', 'admin_c']
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(ASNumberForm, self).__init__(*args, **kwargs) super(ASNumberForm, self).__init__(*args, **kwargs)
if not (self.instance and self.instance.pk): if not (self.instance and self.instance.pk):
self.fields["asblock"].required = True self.fields["asblock"].required = True
else: else:
self.fields["asblock"].disabled = True self.fields["asblock"].disabled = True
mnts = self._user.maintainer_set.all() mnts = self._user.maintainer_set.all()
self.fields['asblock'].queryset = ASBlock.getMntQueryset(mnts, self.instance, "asblock") self.fields['asblock'].queryset = ASBlock.getMntQueryset(mnts, self.instance, "asblock")
def clean(self): def clean(self):
cleaned_data = super(ASNumberForm, self).clean() cleaned_data = super(ASNumberForm, self).clean()
if not self.errors: if not self.errors:
number = cleaned_data['number'] number = cleaned_data['number']
block = cleaned_data['asblock'] block = cleaned_data['asblock']
# belongs to asblock? # belongs to asblock?
if number < block.asBegin or number > block.asEnd: if number < block.asBegin or number > block.asEnd:
raise forms.ValidationError("AS number is not inside AS block") raise forms.ValidationError("AS number is not inside AS block")
# does an entry already exist? # does an entry already exist?
try: try:
otherAS = ASNumber.objects.get(number=number) otherAS = ASNumber.objects.get(number=number)
if not (self.instance and self.instance.pk and self.instance.pk == otherAS.pk): if not (self.instance and self.instance.pk and self.instance.pk == otherAS.pk):
raise forms.ValidationError("This AS number is already represented by %s" % otherAS.handle) raise forms.ValidationError("This AS number is already represented by %s" % otherAS.handle)
except ASNumber.DoesNotExist: except ASNumber.DoesNotExist:
pass pass
# has already other asblock? # has already other asblock?
if block.asblock_set.count() > 0: if block.asblock_set.count() > 0:
raise forms.ValidationError("The given AS block is already references by following sub AS blocks: %s" % (", ".join(map(lambda _x: _x.handle, block.asblock_set.all())),)) raise forms.ValidationError("The given AS block is already references by following sub AS blocks: %s" % (", ".join(map(lambda _x: _x.handle, block.asblock_set.all())),))

View File

@ -9,45 +9,45 @@ from django.db.models import Q
class DeleteCheckView(DeleteView): class DeleteCheckView(DeleteView):
""" Check if object actually can be deleted. Provide reasons to template """ Check if object actually can be deleted. Provide reasons to template
if not. if not.
""" """
def delete(self, request, *args, **kwargs): def delete(self, request, *args, **kwargs):
self.object = self.get_object() self.object = self.get_object()
success_url = self.get_success_url() success_url = self.get_success_url()
reasons = self.object.getNoDeleteReasons() reasons = self.object.getNoDeleteReasons()
if reasons: if reasons:
# do not delete, do what get does... # do not delete, do what get does...
return self.get(request, *args, **kwargs) return self.get(request, *args, **kwargs)
else: else:
self.object.delete() self.object.delete()
messages.info(request, "Object %s has been deleted" % str(self.object)) messages.info(request, "Object %s has been deleted" % str(self.object))
return HttpResponseRedirect(success_url) return HttpResponseRedirect(success_url)
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
if 'reasons' not in kwargs: if 'reasons' not in kwargs:
kwargs['reasons'] = self.object.getNoDeleteReasons() kwargs['reasons'] = self.object.getNoDeleteReasons()
return super(DeleteCheckView, self).get_context_data(**kwargs) return super(DeleteCheckView, self).get_context_data(**kwargs)
class MntGenericMixin(object): class MntGenericMixin(object):
def get_queryset(self): def get_queryset(self):
mnts = self.request.user.maintainer_set.all() mnts = self.request.user.maintainer_set.all()
q = Q(mnt_by__in=mnts) q = Q(mnt_by__in=mnts)
if hasattr(self.model, "mnt_lower"): if hasattr(self.model, "mnt_lower"):
q |= Q(mnt_lower__in=mnts) q |= Q(mnt_lower__in=mnts)
return self.model.objects.filter(q).distinct() return self.model.objects.filter(q).distinct()
def get_form_kwargs(self, *args, **kwargs): def get_form_kwargs(self, *args, **kwargs):
kwargs = super(MntGenericMixin, self).get_form_kwargs(*args, **kwargs) kwargs = super(MntGenericMixin, self).get_form_kwargs(*args, **kwargs)
if hasattr(self.model, "mnt_lower"): if hasattr(self.model, "mnt_lower"):
mnts = self.request.user.maintainer_set.all() mnts = self.request.user.maintainer_set.all()
if not any(mnt in self.object.mnt_by.all() for mnt in mnts): if not any(mnt in self.object.mnt_by.all() for mnt in mnts):
# we are in mnt_lower # we are in mnt_lower
kwargs["lower"] = True kwargs["lower"] = True
return kwargs return kwargs

View File

@ -11,161 +11,161 @@ import ipaddress
import re import re
def _addFields(fields, obj, fieldNames): def _addFields(fields, obj, fieldNames):
for fieldName in fieldNames: for fieldName in fieldNames:
fields.append((fieldName.capitalize().replace("_", " "), getattr(obj, fieldName))) fields.append((fieldName.capitalize().replace("_", " "), getattr(obj, fieldName)))
def getWhoisObjectFields(obj, owner): def getWhoisObjectFields(obj, owner):
fields = [] fields = []
if getattr(obj, "handle", None): if getattr(obj, "handle", None):
_addFields(fields, obj, ["handle"]) _addFields(fields, obj, ["handle"])
c = type(obj) c = type(obj)
if c == whoisdb.models.Maintainer: if c == whoisdb.models.Maintainer:
_addFields(fields, obj, ["description", "admin_c"]) _addFields(fields, obj, ["description", "admin_c"])
if owner: if owner:
_addFields(fields, obj, ["auth"]) _addFields(fields, obj, ["auth"])
elif c == whoisdb.models.Contact: elif c == whoisdb.models.Contact:
_addFields(fields, obj, ["name", "mnt_by"]) _addFields(fields, obj, ["name", "mnt_by"])
elif c == whoisdb.models.ASBlock: elif c == whoisdb.models.ASBlock:
_addFields(fields, obj, ["name"]) _addFields(fields, obj, ["name"])
fields.append(("AS Range", "%s - %s" % (obj.asBegin, obj.asEnd))) fields.append(("AS Range", "%s - %s" % (obj.asBegin, obj.asEnd)))
_addFields(fields, obj, ["description", "parent_block", "mnt_by", "mnt_lower", "admin_c"]) _addFields(fields, obj, ["description", "parent_block", "mnt_by", "mnt_lower", "admin_c"])
elif c == whoisdb.models.ASNumber: elif c == whoisdb.models.ASNumber:
_addFields(fields, obj, ["name", "number", "description", "asblock", "volatile", "mnt_by", "mnt_lower", "admin_c"]) _addFields(fields, obj, ["name", "number", "description", "asblock", "volatile", "mnt_by", "mnt_lower", "admin_c"])
elif c == whoisdb.models.InetNum: elif c == whoisdb.models.InetNum:
_addFields(fields, obj, ["name"]) _addFields(fields, obj, ["name"])
fields.append(("Address CIDR", obj.prefix())) fields.append(("Address CIDR", obj.prefix()))
_addFields(fields, obj, ["description", "parent_range", "origin_as", "mnt_by", "mnt_lower", "admin_c"]) _addFields(fields, obj, ["description", "parent_range", "origin_as", "mnt_by", "mnt_lower", "admin_c"])
elif c == domains.models.Domain: elif c == domains.models.Domain:
_addFields(fields, obj, ["name", "nameservers", "mnt_by", "admin_c"]) _addFields(fields, obj, ["name", "nameservers", "mnt_by", "admin_c", "ds_records"])
elif c == domains.models.Nameserver: elif c == domains.models.Nameserver:
_addFields(fields, obj, ["name", "glueIPv4", "glueIPv6", "mnt_by", "admin_c"]) _addFields(fields, obj, ["name", "glueIPv4", "glueIPv6", "mnt_by", "admin_c"])
elif c == domains.models.ReverseZone: elif c == domains.models.ReverseZone:
#_addFields(fields, obj, ["name"]) #_addFields(fields, obj, ["name"])
fields.append(("Address CIDR", obj.prefix())) fields.append(("Address CIDR", obj.prefix()))
_addFields(fields, obj, ["parentNet", "nameservers"]) _addFields(fields, obj, ["parentNet", "nameservers"])
_addFields(fields, obj, ["created", "last_modified"]) _addFields(fields, obj, ["created", "last_modified"])
return fields return fields
def guessWhoisObject(self, handle): def guessWhoisObject(self, handle):
# is it a normal handle? # is it a normal handle?
pass pass
def findInDatabase(rawValue): def findInDatabase(rawValue):
# is this an ip address? # is this an ip address?
rawValue = rawValue.strip().upper() rawValue = rawValue.strip().upper()
value = None value = None
results = [] results = []
# try subnetwork # try subnetwork
try: try:
value = ipaddress.ip_network(rawValue, strict=False) value = ipaddress.ip_network(rawValue, strict=False)
except ValueError: except ValueError:
pass pass
if value: if value:
# ssubnet # ssubnet
obj = whoisdb.models.InetNum.objects.filter(address=str(value.network_address), netmask=value.prefixlen) obj = whoisdb.models.InetNum.objects.filter(address=str(value.network_address), netmask=value.prefixlen)
results.extend(obj) results.extend(obj)
# single ip # single ip
value = None value = None
try: try:
value = ipaddress.ip_address(rawValue) value = ipaddress.ip_address(rawValue)
except ValueError: except ValueError:
pass pass
if value: if value:
# NOTE: this is only for "small subnets", we could increase this... # NOTE: this is only for "small subnets", we could increase this...
baseaddr = None baseaddr = None
if value.version == 4: if value.version == 4:
basenet = ipaddress.ip_network("%s/24" % value, strict=False) basenet = ipaddress.ip_network("%s/24" % value, strict=False)
baseaddr = ".".join(str(basenet).split(".")[0:3]) + "." baseaddr = ".".join(str(basenet).split(".")[0:3]) + "."
else: else:
basenet = ipaddress.ip_network("%s/56" % value.exploded, strict=False) basenet = ipaddress.ip_network("%s/56" % value.exploded, strict=False)
baseaddr = ":".join(str(basenet).split(":")[0:4])[-2] baseaddr = ":".join(str(basenet).split(":")[0:4])[-2]
nets = whoisdb.models.InetNum.objects.filter(address__startswith=baseaddr).order_by("-netmask") nets = whoisdb.models.InetNum.objects.filter(address__startswith=baseaddr).order_by("-netmask")
for net in nets: for net in nets:
if value in net.getNetwork(): if value in net.getNetwork():
results.append(net) results.append(net)
break break
# asnumber? # asnumber?
m = re.match("^(?:AS)?(\d+)$", rawValue) m = re.match("^(?:AS)?(\d+)$", rawValue)
if m: if m:
# asnumber! # asnumber!
num = int(m.group(1)) num = int(m.group(1))
obj = whoisdb.models.ASNumber.objects.filter(number=num) obj = whoisdb.models.ASNumber.objects.filter(number=num)
results.extend(obj) results.extend(obj)
# find a matching block # find a matching block
blocks = whoisdb.models.ASBlock.objects.filter(asBegin__lte=num, asEnd__gte=num).annotate(size=F('asEnd')-F('asBegin')).order_by('size') blocks = whoisdb.models.ASBlock.objects.filter(asBegin__lte=num, asEnd__gte=num).annotate(size=F('asEnd')-F('asBegin')).order_by('size')
if blocks.count() > 0: if blocks.count() > 0:
results.append(blocks[0]) results.append(blocks[0])
# asblocks? smallest asblock containing the range # asblocks? smallest asblock containing the range
# WHEN anotation foo... could also be done in asnumber match # WHEN anotation foo... could also be done in asnumber match
# also look for number - number queries? # also look for number - number queries?
# domain? # domain?
if rawValue.endswith("DN") or rawValue.endswith("DN."): if rawValue.endswith("DN") or rawValue.endswith("DN."):
value = rawValue.lower() value = rawValue.lower()
if not value.endswith("."): if not value.endswith("."):
value += "." value += "."
obj = domains.models.Domain.objects.filter(name=value) obj = domains.models.Domain.objects.filter(name=value)
results.extend(obj) results.extend(obj)
# contact by name? # contact by name?
# handlenames for Maintainer, Contact, InetNum, ASNumber, ASBlock # handlenames for Maintainer, Contact, InetNum, ASNumber, ASBlock
handleObjs = [ handleObjs = [
whoisdb.models.Contact, whoisdb.models.Contact,
whoisdb.models.Maintainer, whoisdb.models.Maintainer,
whoisdb.models.InetNum, whoisdb.models.InetNum,
whoisdb.models.ASBlock, whoisdb.models.ASBlock,
whoisdb.models.ASNumber, whoisdb.models.ASNumber,
] ]
for handleObj in handleObjs: for handleObj in handleObjs:
obj = handleObj.objects.filter(handle=rawValue) obj = handleObj.objects.filter(handle=rawValue)
if not obj and len(rawValue) >= 3: if not obj and len(rawValue) >= 3:
obj = handleObj.objects.filter(handle__startswith=rawValue) obj = handleObj.objects.filter(handle__startswith=rawValue)
results.extend(obj) results.extend(obj)
return results return results
def findHandleFromStr(rawValue): def findHandleFromStr(rawValue):
handleObjs = [ handleObjs = [
whoisdb.models.Contact, whoisdb.models.Contact,
whoisdb.models.Maintainer, whoisdb.models.Maintainer,
whoisdb.models.InetNum, whoisdb.models.InetNum,
whoisdb.models.ASBlock, whoisdb.models.ASBlock,
whoisdb.models.ASNumber, whoisdb.models.ASNumber,
] ]
for handleObj in handleObjs: for handleObj in handleObjs:
try: try:
return handleObj.objects.get(handle=rawValue) return handleObj.objects.get(handle=rawValue)
except handleObj.DoesNotExist: except handleObj.DoesNotExist:
pass pass
return None return None
def orderQueryset(qs, userOwned, objOwned): def orderQueryset(qs, userOwned, objOwned):
# when for # when for
whens = [When(userOwned, then=2)] whens = [When(userOwned, then=2)]
if objOwned: if objOwned:
# add existing # add existing
whens.append(When(objOwned, then=1)) whens.append(When(objOwned, then=1))
qs = qs.annotate(card=Max(Case(*whens, default=0, output_field=IntegerField()))).order_by("-card") qs = qs.annotate(card=Max(Case(*whens, default=0, output_field=IntegerField()))).order_by("-card")
return qs return qs

View File

@ -13,269 +13,269 @@ import ipaddress
class WhoisObject(models.Model): class WhoisObject(models.Model):
class Meta: class Meta:
abstract = True abstract = True
handleSuffix = "" handleSuffix = ""
handle = models.SlugField(max_length=32, unique=True, verbose_name='handle', validators=[HandleValidator()]) handle = models.SlugField(max_length=32, unique=True, verbose_name='handle', validators=[HandleValidator()])
created = models.DateTimeField(auto_now_add=True) created = models.DateTimeField(auto_now_add=True)
last_modified = models.DateTimeField(auto_now_add=True) last_modified = models.DateTimeField(auto_now_add=True)
#def __init__(self, *args, **kwargs): #def __init__(self, *args, **kwargs):
# super(WhoisObject, self).__init__(*args, **kwargs) # super(WhoisObject, self).__init__(*args, **kwargs)
# if getattr(self, "handle"): # if getattr(self, "handle"):
# field = self._meta.get_field("handle") # field = self._meta.get_field("handle")
# if HandleValidatorWithSuffix not in map(type, field.validators): # if HandleValidatorWithSuffix not in map(type, field.validators):
# print(self.handle, "NOOOOT") # print(self.handle, "NOOOOT")
# field.validators.append(HandleValidatorWithSuffix(self.handleSuffix)) # field.validators.append(HandleValidatorWithSuffix(self.handleSuffix))
# else: # else:
# print(self.handle, list(map(type, field.validators))) # print(self.handle, list(map(type, field.validators)))
def getPK(self): def getPK(self):
return self.handle return self.handle
def __str__(self): def __str__(self):
return self.handle return self.handle
def getAppName(self): def getAppName(self):
return "whoisdb" return "whoisdb"
def getClassName(self): def getClassName(self):
return self._meta.object_name return self._meta.object_name
def genHandle(self, main=None): def genHandle(self, main=None):
if not main: if not main:
main = self.name main = self.name
return self.genGenericHandle(main) return self.genGenericHandle(main)
@classmethod @classmethod
def genGenericHandle(clazz, main): def genGenericHandle(clazz, main):
prefix = "" prefix = ""
if " " in main: if " " in main:
parts = main.split(" ") parts = main.split(" ")
prefix = "".join(map(lambda _x: _x[0], parts)) prefix = "".join(map(lambda _x: _x[0], parts))
if len(prefix) < 3 and len(parts[-1]) > 1: if len(prefix) < 3 and len(parts[-1]) > 1:
prefix += parts[-1][1:4 - len(prefix)] prefix += parts[-1][1:4 - len(prefix)]
else: else:
prefix = main[0:3] prefix = main[0:3]
prefix = prefix.upper() prefix = prefix.upper()
i = 1 i = 1
handle = "%s%%d-%s" % (prefix, clazz.handleSuffix) handle = "%s%%d-%s" % (prefix, clazz.handleSuffix)
while True: while True:
try: try:
prefix prefix
clazz.objects.get(handle=handle % i) clazz.objects.get(handle=handle % i)
i += 1 i += 1
except clazz.DoesNotExist: except clazz.DoesNotExist:
break break
return handle % i return handle % i
def getNoDeleteReasons(self): def getNoDeleteReasons(self):
raise NotImplementedError("Delete reason checking is not implemented for this model") raise NotImplementedError("Delete reason checking is not implemented for this model")
def canBeDeleted(self): def canBeDeleted(self):
return not bool(self.getNoDeleteReasons()) return not bool(self.getNoDeleteReasons())
def handleAuto(self, name=None): def handleAuto(self, name=None):
if self.handle == "AUTO": if self.handle == "AUTO":
self.handle = self.genHandle(name) self.handle = self.genHandle(name)
class Maintainer(WhoisObject): class Maintainer(WhoisObject):
handleSuffix = "MNT" handleSuffix = "MNT"
auth = models.ManyToManyField(User) auth = models.ManyToManyField(User)
handle = models.SlugField(max_length=32, unique=True, verbose_name='handle', validators=[HandleValidatorWithSuffix('MNT')], help_text="Must end with -MNT, eg FOO3-MNT") handle = models.SlugField(max_length=32, unique=True, verbose_name='handle', validators=[HandleValidatorWithSuffix('MNT')], help_text="Must end with -MNT, eg FOO3-MNT")
description = models.CharField(max_length=64, blank=True, help_text="Short description what this maintainer is for") description = models.CharField(max_length=64, blank=True, help_text="Short description what this maintainer is for")
admin_c = models.ManyToManyField("Contact", verbose_name="Administrative Contact") admin_c = models.ManyToManyField("Contact", verbose_name="Administrative Contact")
rir = models.BooleanField(default=False) rir = models.BooleanField(default=False)
lir = models.BooleanField(default=False) lir = models.BooleanField(default=False)
# autoInclude = models.BooleanField(default=True) # autoInclude = models.BooleanField(default=True)
def get_absolute_url(self): def get_absolute_url(self):
return reverse("whoisdb:handle-detail", kwargs={"handle": self.handle}) return reverse("whoisdb:handle-detail", kwargs={"handle": self.handle})
def getNoDeleteReasons(self): def getNoDeleteReasons(self):
reasons = [] reasons = []
# FIXME: Tempfix for circular dependency problem # FIXME: Tempfix for circular dependency problem
import domains.models import domains.models
mntables = [Contact, ASBlock, ASNumber, InetNum, domains.models.Domain, domains.models.Nameserver] mntables = [Contact, ASBlock, ASNumber, InetNum, domains.models.Domain, domains.models.Nameserver]
for mntable in mntables: for mntable in mntables:
candidates = mntable.objects.filter(mnt_by=self).annotate(mntCount=models.Count('mnt_by')).filter(mntCount__lte=1) candidates = mntable.objects.filter(mnt_by=self).annotate(mntCount=models.Count('mnt_by')).filter(mntCount__lte=1)
for candidate in candidates: for candidate in candidates:
reasons.append("Object %s would have no maintainers left." % candidate.handle) reasons.append("Object %s would have no maintainers left." % candidate.handle)
return reasons return reasons
def canEdit(self, user): def canEdit(self, user):
return user in self.auth.all() return user in self.auth.all()
class MntdObject(WhoisObject): class MntdObject(WhoisObject):
class Meta: class Meta:
abstract = True abstract = True
mnt_by = models.ManyToManyField(Maintainer, help_text="You can select multiple maintainers here") mnt_by = models.ManyToManyField(Maintainer, help_text="You can select multiple maintainers here")
def canEdit(self, user): def canEdit(self, user):
if not hasattr(user, "maintainer_set"): if not hasattr(user, "maintainer_set"):
return False return False
mnts = user.maintainer_set.all() mnts = user.maintainer_set.all()
objmnts = self.mnt_by.all() objmnts = self.mnt_by.all()
if hasattr(self, "mnt_lower"): if hasattr(self, "mnt_lower"):
objmnts |= self.mnt_lower.all() objmnts |= self.mnt_lower.all()
for objmnt in objmnts: for objmnt in objmnts:
if objmnt in mnts: if objmnt in mnts:
return True return True
return False return False
@classmethod @classmethod
def getMntQueryset(clazz, mnts, instance, attr=None): def getMntQueryset(clazz, mnts, instance, attr=None):
mntQ = Q(mnt_by__in=mnts) mntQ = Q(mnt_by__in=mnts)
if hasattr(clazz, "mnt_lower"): if hasattr(clazz, "mnt_lower"):
mntQ |= Q(mnt_lower__in=mnts) mntQ |= Q(mnt_lower__in=mnts)
qs = clazz.objects.filter(mntQ) qs = clazz.objects.filter(mntQ)
if attr and instance and instance.pk: if attr and instance and instance.pk:
if type(instance._meta.get_field(attr)) == models.ManyToManyField: if type(instance._meta.get_field(attr)) == models.ManyToManyField:
qs |= getattr(instance, attr).all() qs |= getattr(instance, attr).all()
elif getattr(instance, attr) is not None: elif getattr(instance, attr) is not None:
qs |= clazz.objects.filter(pk=getattr(instance, attr).pk) qs |= clazz.objects.filter(pk=getattr(instance, attr).pk)
return qs.distinct() return qs.distinct()
class Contact(MntdObject): class Contact(MntdObject):
handleSuffix = "DN" handleSuffix = "DN"
TYPE_PERSON = 'PERSON' TYPE_PERSON = 'PERSON'
TYPE_ROLE = 'ROLE' TYPE_ROLE = 'ROLE'
TYPE = (('person', TYPE_PERSON), ('role', TYPE_ROLE)) TYPE = (('person', TYPE_PERSON), ('role', TYPE_ROLE))
TYPE = (('person', TYPE_PERSON),) TYPE = (('person', TYPE_PERSON),)
name = models.CharField(max_length=128) name = models.CharField(max_length=128)
type = models.CharField(max_length=10, choices=TYPE, default=TYPE_PERSON) type = models.CharField(max_length=10, choices=TYPE, default=TYPE_PERSON)
def get_absolute_url(self): def get_absolute_url(self):
return reverse("whoisdb:handle-detail", kwargs={"handle": self.handle}) return reverse("whoisdb:handle-detail", kwargs={"handle": self.handle})
def getNoDeleteReasons(self): def getNoDeleteReasons(self):
reasons = [] reasons = []
contactables = [Maintainer, ASBlock, ASNumber, InetNum] contactables = [Maintainer, ASBlock, ASNumber, InetNum]
for contactable in contactables: for contactable in contactables:
candidates = contactable.objects.filter(admin_c=self).annotate(contactCount=models.Count('admin_c')).filter(contactCount__lte=1) candidates = contactable.objects.filter(admin_c=self).annotate(contactCount=models.Count('admin_c')).filter(contactCount__lte=1)
for candidate in candidates: for candidate in candidates:
reasons.append("Object %s would have no contact left." % candidate.handle) reasons.append("Object %s would have no contact left." % candidate.handle)
return reasons return reasons
class ASBlock(MntdObject): class ASBlock(MntdObject):
handleSuffix = "ASB" handleSuffix = "ASB"
parent_block = models.ForeignKey("ASBlock", models.CASCADE, null=True, blank=True, default=None) parent_block = models.ForeignKey("ASBlock", models.CASCADE, null=True, blank=True, default=None)
name = models.CharField(max_length=32) name = models.CharField(max_length=32)
asBegin = models.PositiveIntegerField() asBegin = models.PositiveIntegerField()
asEnd = models.PositiveIntegerField() asEnd = models.PositiveIntegerField()
description = models.CharField(max_length=64, blank=True) description = models.CharField(max_length=64, blank=True)
admin_c = models.ManyToManyField("Contact") admin_c = models.ManyToManyField("Contact")
mnt_lower = models.ManyToManyField(Maintainer, related_name='lower_asblock_set', blank=True) mnt_lower = models.ManyToManyField(Maintainer, related_name='lower_asblock_set', blank=True)
def contains(self, block): def contains(self, block):
return self.asBegin <= block.asBegin <= self.asEnd and self.asBegin <= block.asEnd <= self.asEnd return self.asBegin <= block.asBegin <= self.asEnd and self.asBegin <= block.asEnd <= self.asEnd
def getResource(self): def getResource(self):
return "%s - %s" % (self.asBegin, self.asEnd) return "%s - %s" % (self.asBegin, self.asEnd)
def get_absolute_url(self): def get_absolute_url(self):
return reverse("whoisdb:handle-detail", kwargs={"handle": self.handle}) return reverse("whoisdb:handle-detail", kwargs={"handle": self.handle})
def getNoDeleteReasons(self): def getNoDeleteReasons(self):
reasons = [] reasons = []
if self.asblock_set.count() > 0: if self.asblock_set.count() > 0:
reasons.append("The AS block is referenced by the following other blocks: %s" % (", ".join(map(lambda _x: _x.handle, self.asblock_set.all())))) reasons.append("The AS block is referenced by the following other blocks: %s" % (", ".join(map(lambda _x: _x.handle, self.asblock_set.all()))))
if self.asnumber_set.count() > 0: if self.asnumber_set.count() > 0:
reasons.append("The AS block is referenced by the following as numbers: %s" % (", ".join(map(lambda _x: _x.handle, self.asnumber_set.all())))) reasons.append("The AS block is referenced by the following as numbers: %s" % (", ".join(map(lambda _x: _x.handle, self.asnumber_set.all()))))
return reasons return reasons
class ASNumber(MntdObject): class ASNumber(MntdObject):
handleSuffix = "AS" handleSuffix = "AS"
number = models.PositiveIntegerField(unique=True, db_index=True) number = models.PositiveIntegerField(unique=True, db_index=True)
volatile = models.BooleanField(default=False, help_text="Check if this AS is not going to be online 24/7 (for example on a laptop)") volatile = models.BooleanField(default=False, help_text="Check if this AS is not going to be online 24/7 (for example on a laptop)")
asblock = models.ForeignKey(ASBlock, models.CASCADE) asblock = models.ForeignKey(ASBlock, models.CASCADE)
name = models.CharField(max_length=32) name = models.CharField(max_length=32)
description = models.CharField(max_length=64, blank=True) description = models.CharField(max_length=64, blank=True)
admin_c = models.ManyToManyField("Contact") admin_c = models.ManyToManyField("Contact")
mnt_lower = models.ManyToManyField(Maintainer, related_name='lower_asnumber_set', blank=True) mnt_lower = models.ManyToManyField(Maintainer, related_name='lower_asnumber_set', blank=True)
def get_absolute_url(self): def get_absolute_url(self):
return reverse("whoisdb:handle-detail", kwargs={"handle": self.handle}) return reverse("whoisdb:handle-detail", kwargs={"handle": self.handle})
def getNoDeleteReasons(self): def getNoDeleteReasons(self):
reasons = [] reasons = []
return reasons return reasons
def getResource(self): def getResource(self):
return str(self.number) return str(self.number)
class InetNum(MntdObject): class InetNum(MntdObject):
class Meta: class Meta:
unique_together = ( unique_together = (
("address", "netmask"), ("address", "netmask"),
) )
handleSuffix = "NET" handleSuffix = "NET"
IPv4 = "ipv4" IPv4 = "ipv4"
IPv6 = "ipv6" IPv6 = "ipv6"
PROTO = ((IPv4, 'IPv4'), (IPv6, 'IPv6')) PROTO = ((IPv4, 'IPv4'), (IPv6, 'IPv6'))
protocol = models.CharField(max_length=4, choices=PROTO) protocol = models.CharField(max_length=4, choices=PROTO)
address = models.GenericIPAddressField(db_index=True) address = models.GenericIPAddressField(db_index=True)
netmask = models.PositiveIntegerField() netmask = models.PositiveIntegerField()
parent_range = models.ForeignKey("InetNum", models.CASCADE, null=True, blank=True, default=None) parent_range = models.ForeignKey("InetNum", models.CASCADE, null=True, blank=True, default=None)
name = models.CharField(max_length=64) name = models.CharField(max_length=64)
description = models.CharField(max_length=64, blank=True) description = models.CharField(max_length=64, blank=True)
origin_as = models.ManyToManyField(ASNumber, blank=True) origin_as = models.ManyToManyField(ASNumber, blank=True)
admin_c = models.ManyToManyField("Contact") admin_c = models.ManyToManyField("Contact")
mnt_lower = models.ManyToManyField(Maintainer, related_name='lower_inetnum_set', blank=True) mnt_lower = models.ManyToManyField(Maintainer, related_name='lower_inetnum_set', blank=True)
def getResource(self): def getResource(self):
return self.prefix() return self.prefix()
def prefix(self): def prefix(self):
""" Helper function, mainly used in templates """ """ Helper function, mainly used in templates """
return "%s/%s" % (self.address, self.netmask) return "%s/%s" % (self.address, self.netmask)
def getNetwork(self): def getNetwork(self):
return ipaddress.ip_network(self.prefix()) return ipaddress.ip_network(self.prefix())
def get_absolute_url(self): def get_absolute_url(self):
return reverse("whoisdb:handle-detail", kwargs={"handle": self.handle}) return reverse("whoisdb:handle-detail", kwargs={"handle": self.handle})
def getNoDeleteReasons(self): def getNoDeleteReasons(self):
reasons = [] reasons = []
if self.inetnum_set.all().count() > 0: if self.inetnum_set.all().count() > 0:
reasons.append("The following networks depend on this network: %s" % ", ".join(map(lambda _x: _x.handle, self.inetnum_set.all()))) reasons.append("The following networks depend on this network: %s" % ", ".join(map(lambda _x: _x.handle, self.inetnum_set.all())))
return reasons return reasons

View File

@ -9,44 +9,44 @@ register = template.Library()
@register.filter @register.filter
def linkObject(value): def linkObject(value):
return mark_safe('<a href="%s">%s</a>' % (value.get_absolute_url(), str(value))) return mark_safe('<a href="%s">%s</a>' % (value.get_absolute_url(), str(value)))
@register.filter @register.filter
def tryLinkHandle(handle): def tryLinkHandle(handle):
try: try:
if not handle: if not handle:
raise ValueError() raise ValueError()
HandleValidator()(str(handle)) HandleValidator()(str(handle))
obj = findHandleFromStr(handle) obj = findHandleFromStr(handle)
if obj: if obj:
return linkObject(obj) return linkObject(obj)
except: except:
pass pass
return handle return handle
@register.filter @register.filter
def linkObjects(value): def linkObjects(value):
links = [] links = []
for obj in value: for obj in value:
if hasattr(obj, "get_absolute_url"): if hasattr(obj, "get_absolute_url"):
links.append('<a href="%s">%s</a>' % (obj.get_absolute_url(), str(obj))) links.append('<a href="%s">%s</a>' % (obj.get_absolute_url(), str(obj)))
else: else:
links.append(str(obj)) links.append(str(obj))
return mark_safe(", ".join(links)) return mark_safe(", ".join(links))
@register.filter @register.filter
def getFields(value, user): def getFields(value, user):
owner = value.canEdit(user) owner = value.canEdit(user)
return getWhoisObjectFields(value, owner) return getWhoisObjectFields(value, owner)
@register.filter @register.filter
def userCanEdit(value, user): def userCanEdit(value, user):
if hasattr(value, "canEdit"): if hasattr(value, "canEdit"):
return value.canEdit(user) return value.canEdit(user)
return False return False

View File

@ -7,40 +7,40 @@ from django.conf.urls import url
from . import views as whoisdb_views from . import views as whoisdb_views
urlpatterns = [ urlpatterns = [
url(r'^$', whoisdb_views.dbDashboard, name='dashboard'), url(r'^$', whoisdb_views.dbDashboard, name='dashboard'),
url(r'^search/$', whoisdb_views.searchObject, name='search'), url(r'^search/$', whoisdb_views.searchObject, name='search'),
url(r'^create/$', whoisdb_views.createObjectOverview, name='createObjectOverview'), url(r'^create/$', whoisdb_views.createObjectOverview, name='createObjectOverview'),
url(r'^handle/(?P<handle>[A-Z0-9-]+)/$', whoisdb_views.showHandle, name='showhandle'), url(r'^handle/(?P<handle>[A-Z0-9-]+)/$', whoisdb_views.showHandle, name='showhandle'),
url(r'^handle/(?P<handle>[A-Z0-9-]+)/$', whoisdb_views.showHandle, name='handle-detail'), url(r'^handle/(?P<handle>[A-Z0-9-]+)/$', whoisdb_views.showHandle, name='handle-detail'),
url(r'^mnt/create/$', whoisdb_views.MaintainerCreate.as_view(), name='maintainer-create'), url(r'^mnt/create/$', whoisdb_views.MaintainerCreate.as_view(), name='maintainer-create'),
url(r'^mnt/show/(?P<handle>[A-Z0-9-]+)/$', whoisdb_views.MaintainerDetail.as_view(), name='maintainer-detail'), url(r'^mnt/show/(?P<handle>[A-Z0-9-]+)/$', whoisdb_views.MaintainerDetail.as_view(), name='maintainer-detail'),
url(r'^mnt/edit/(?P<handle>[A-Z0-9-]+)/$', whoisdb_views.MaintainerEdit.as_view(), name='maintainer-edit'), url(r'^mnt/edit/(?P<handle>[A-Z0-9-]+)/$', whoisdb_views.MaintainerEdit.as_view(), name='maintainer-edit'),
url(r'^mnt/delete/(?P<handle>[A-Z0-9-]+)/$', whoisdb_views.MaintainerDelete.as_view(), name='maintainer-delete'), url(r'^mnt/delete/(?P<handle>[A-Z0-9-]+)/$', whoisdb_views.MaintainerDelete.as_view(), name='maintainer-delete'),
url(r'^contact/create/$', whoisdb_views.ContactCreate.as_view(), name='contact-create'), url(r'^contact/create/$', whoisdb_views.ContactCreate.as_view(), name='contact-create'),
url(r'^contact/show/(?P<handle>[A-Z0-9-]+)/$', whoisdb_views.ContactDetail.as_view(), name='contact-detail'), url(r'^contact/show/(?P<handle>[A-Z0-9-]+)/$', whoisdb_views.ContactDetail.as_view(), name='contact-detail'),
url(r'^contact/edit/(?P<handle>[A-Z0-9-]+)/$', whoisdb_views.ContactEdit.as_view(), name='contact-edit'), url(r'^contact/edit/(?P<handle>[A-Z0-9-]+)/$', whoisdb_views.ContactEdit.as_view(), name='contact-edit'),
url(r'^contact/delete/(?P<handle>[A-Z0-9-]+)/$', whoisdb_views.ContactDelete.as_view(), name='contact-delete'), url(r'^contact/delete/(?P<handle>[A-Z0-9-]+)/$', whoisdb_views.ContactDelete.as_view(), name='contact-delete'),
url(r'^inetnum/create/$', whoisdb_views.InetNumCreate.as_view(), name='inetnum-create'), url(r'^inetnum/create/$', whoisdb_views.InetNumCreate.as_view(), name='inetnum-create'),
url(r'^inetnum/show/(?P<handle>[A-Z0-9-]+)/$', whoisdb_views.InetNumDetail.as_view(), name='inetnum-detail'), url(r'^inetnum/show/(?P<handle>[A-Z0-9-]+)/$', whoisdb_views.InetNumDetail.as_view(), name='inetnum-detail'),
url(r'^inetnum/edit/(?P<handle>[A-Z0-9-]+)/$', whoisdb_views.InetNumEdit.as_view(), name='inetnum-edit'), url(r'^inetnum/edit/(?P<handle>[A-Z0-9-]+)/$', whoisdb_views.InetNumEdit.as_view(), name='inetnum-edit'),
url(r'^inetnum/delete/(?P<handle>[A-Z0-9-]+)/$', whoisdb_views.InetNumDelete.as_view(), name='inetnum-delete'), url(r'^inetnum/delete/(?P<handle>[A-Z0-9-]+)/$', whoisdb_views.InetNumDelete.as_view(), name='inetnum-delete'),
url(r'^asblock/create/$', whoisdb_views.ASBlockCreate.as_view(), name='asblock-create'), url(r'^asblock/create/$', whoisdb_views.ASBlockCreate.as_view(), name='asblock-create'),
url(r'^asblock/show/(?P<handle>[A-Z0-9-]+)/$', whoisdb_views.ASBlockDetail.as_view(), name='asblock-detail'), url(r'^asblock/show/(?P<handle>[A-Z0-9-]+)/$', whoisdb_views.ASBlockDetail.as_view(), name='asblock-detail'),
url(r'^asblock/edit/(?P<handle>[A-Z0-9-]+)/$', whoisdb_views.ASBlockEdit.as_view(), name='asblock-edit'), url(r'^asblock/edit/(?P<handle>[A-Z0-9-]+)/$', whoisdb_views.ASBlockEdit.as_view(), name='asblock-edit'),
url(r'^asblock/delete/(?P<handle>[A-Z0-9-]+)/$', whoisdb_views.ASBlockDelete.as_view(), name='asblock-delete'), url(r'^asblock/delete/(?P<handle>[A-Z0-9-]+)/$', whoisdb_views.ASBlockDelete.as_view(), name='asblock-delete'),
url(r'^asnumber/create/$', whoisdb_views.ASNumberCreate.as_view(), name='asnumber-create'), url(r'^asnumber/create/$', whoisdb_views.ASNumberCreate.as_view(), name='asnumber-create'),
url(r'^asnumber/show/(?P<handle>[A-Z0-9-]+)/$', whoisdb_views.ASNumberDetail.as_view(), name='asnumber-detail'), url(r'^asnumber/show/(?P<handle>[A-Z0-9-]+)/$', whoisdb_views.ASNumberDetail.as_view(), name='asnumber-detail'),
url(r'^asnumber/edit/(?P<handle>[A-Z0-9-]+)/$', whoisdb_views.ASNumberEdit.as_view(), name='asnumber-edit'), url(r'^asnumber/edit/(?P<handle>[A-Z0-9-]+)/$', whoisdb_views.ASNumberEdit.as_view(), name='asnumber-edit'),
url(r'^asnumber/delete/(?P<handle>[A-Z0-9-]+)/$', whoisdb_views.ASNumberDelete.as_view(), name='asnumber-delete'), url(r'^asnumber/delete/(?P<handle>[A-Z0-9-]+)/$', whoisdb_views.ASNumberDelete.as_view(), name='asnumber-delete'),
url(r'^assubnetset/create/$', whoisdb_views.ASAndSubnetWizard.as_view(), name='asandsubnet-wizard'), url(r'^assubnetset/create/$', whoisdb_views.ASAndSubnetWizard.as_view(), name='asandsubnet-wizard'),
] ]

View File

@ -14,31 +14,31 @@ import ipaddress
@deconstructible @deconstructible
class HandleValidator(validators.RegexValidator): class HandleValidator(validators.RegexValidator):
regex = r'^(?:[A-Z]+[0-9]*(-[A-Z]+)|AUTO)$' regex = r'^(?:[A-Z]+[0-9]*(-[A-Z]+)|AUTO)$'
message = _( message = _(
'Enter a valid handle (all uppercase)' 'Enter a valid handle (all uppercase)'
) )
flags = re.ASCII if six.PY3 else 0 flags = re.ASCII if six.PY3 else 0
@deconstructible @deconstructible
class HandleValidatorWithSuffix(validators.RegexValidator): class HandleValidatorWithSuffix(validators.RegexValidator):
flags = re.ASCII if six.PY3 else 0 flags = re.ASCII if six.PY3 else 0
def __init__(self, suffix): def __init__(self, suffix):
self.regex = r'^(?:[A-Z]+[0-9]*-%s|AUTO)$' % re.escape(suffix) self.regex = r'^(?:[A-Z]+[0-9]*-%s|AUTO)$' % re.escape(suffix)
self.message = _( self.message = _(
'Enter a valid handle with suffix %s (all uppercase), e.g. FOO3-%s' % (suffix, suffix) 'Enter a valid handle with suffix %s (all uppercase), e.g. FOO3-%s' % (suffix, suffix)
) )
super(HandleValidatorWithSuffix, self).__init__() super(HandleValidatorWithSuffix, self).__init__()
def IP46CIDRValidator(value): def IP46CIDRValidator(value):
if not re.match(r"[0-9a-fA-F:.]+/[0-9]+", value): if not re.match(r"[0-9a-fA-F:.]+/[0-9]+", value):
raise ValidationError("Address needs to be a subnet in the format of ip/prefix") raise ValidationError("Address needs to be a subnet in the format of ip/prefix")
try: try:
ipaddress.ip_network(value) ipaddress.ip_network(value)
except ValueError as e: except ValueError as e:
raise ValidationError(str(e)) raise ValidationError(str(e))

View File

@ -20,349 +20,349 @@ from .helpers import findInDatabase
@login_required @login_required
def createObjectOverview(request): def createObjectOverview(request):
mnts = request.user.maintainer_set.filter().all() mnts = request.user.maintainer_set.filter().all()
netblocks = InetNum.objects.filter(Q(mnt_by__in=mnts) | Q(mnt_lower__in=mnts)).distinct() netblocks = InetNum.objects.filter(Q(mnt_by__in=mnts) | Q(mnt_lower__in=mnts)).distinct()
asblocks = ASBlock.objects.filter(Q(mnt_by__in=mnts) | Q(mnt_lower__in=mnts)).distinct() asblocks = ASBlock.objects.filter(Q(mnt_by__in=mnts) | Q(mnt_lower__in=mnts)).distinct()
return render(request, "whoisdb/create_overview.html", {"netblocks": netblocks, "asblocks": asblocks}) return render(request, "whoisdb/create_overview.html", {"netblocks": netblocks, "asblocks": asblocks})
@login_required @login_required
def dbDashboard(request): def dbDashboard(request):
mnts = request.user.maintainer_set.filter(rir=False, lir=False).all() mnts = request.user.maintainer_set.filter(rir=False, lir=False).all()
if request.GET.get("delegated", None) or mnts.count() == 0: if request.GET.get("delegated", None) or mnts.count() == 0:
# if user wants to see rir/lir objects or only has rir/lir mnts, use all available mnts # if user wants to see rir/lir objects or only has rir/lir mnts, use all available mnts
mnts = request.user.maintainer_set.all() mnts = request.user.maintainer_set.all()
showDelegations = True showDelegations = True
else: else:
showDelegations = False showDelegations = False
hasDelegations = request.user.maintainer_set.filter(Q(rir=True) | Q(lir=True)).count() > 0 hasDelegations = request.user.maintainer_set.filter(Q(rir=True) | Q(lir=True)).count() > 0
contacts = Contact.objects.filter(mnt_by__in=mnts) contacts = Contact.objects.filter(mnt_by__in=mnts)
netblocks = InetNum.objects.filter(Q(mnt_by__in=mnts) | Q(mnt_lower__in=mnts)).distinct() netblocks = InetNum.objects.filter(Q(mnt_by__in=mnts) | Q(mnt_lower__in=mnts)).distinct()
asblocks = ASBlock.objects.filter(Q(mnt_by__in=mnts) | Q(mnt_lower__in=mnts)).distinct() asblocks = ASBlock.objects.filter(Q(mnt_by__in=mnts) | Q(mnt_lower__in=mnts)).distinct()
asnumbers = ASNumber.objects.filter(Q(mnt_by__in=mnts) | Q(mnt_lower__in=mnts)).distinct() asnumbers = ASNumber.objects.filter(Q(mnt_by__in=mnts) | Q(mnt_lower__in=mnts)).distinct()
mntForm = contactForm = None mntForm = contactForm = None
if mnts.count() == 0: if mnts.count() == 0:
mntForm = contactForm = None mntForm = contactForm = None
if request.method == "POST": if request.method == "POST":
mntForm = MntInitialForm(user=request.user, data=request.POST, prefix="mnt") mntForm = MntInitialForm(user=request.user, data=request.POST, prefix="mnt")
contactForm = ContactInitialForm(user=request.user, data=request.POST, prefix="contact") contactForm = ContactInitialForm(user=request.user, data=request.POST, prefix="contact")
if mntForm.is_valid() and contactForm.is_valid(): if mntForm.is_valid() and contactForm.is_valid():
mnt = mntForm.save(commit=False) mnt = mntForm.save(commit=False)
mnt.handleAuto(request.user.username) mnt.handleAuto(request.user.username)
mnt.save() mnt.save()
contact = contactForm.save(commit=False) contact = contactForm.save(commit=False)
contact.handleAuto() contact.handleAuto()
contact.type = Contact.TYPE_PERSON contact.type = Contact.TYPE_PERSON
contact.save() contact.save()
contact.mnt_by.add(mnt.id) contact.mnt_by.add(mnt.id)
contact.save() contact.save()
mnt.auth.add(request.user.id) mnt.auth.add(request.user.id)
mnt.admin_c.add(contact.id) mnt.admin_c.add(contact.id)
mnt.save() mnt.save()
return HttpResponseRedirect(reverse("whoisdb:dashboard")) return HttpResponseRedirect(reverse("whoisdb:dashboard"))
else: else:
mntForm = MntInitialForm(user=request.user, prefix="mnt", initial={'handle': 'AUTO', 'description': 'Primary maintainer of %s' % request.user.username}) mntForm = MntInitialForm(user=request.user, prefix="mnt", initial={'handle': 'AUTO', 'description': 'Primary maintainer of %s' % request.user.username})
contactForm = ContactInitialForm(user=request.user, initial={'handle': 'AUTO', 'name': request.user.username.capitalize()}, prefix='contact') contactForm = ContactInitialForm(user=request.user, initial={'handle': 'AUTO', 'name': request.user.username.capitalize()}, prefix='contact')
return render(request, "whoisdb/overview.html", {"mnts": mnts, "contacts": contacts, "mntForm": mntForm, "contactForm": contactForm, "netblocks": netblocks, "asblocks": asblocks, "asnumbers": asnumbers, 'hasDelegations': hasDelegations, "showDelegations": showDelegations}) return render(request, "whoisdb/overview.html", {"mnts": mnts, "contacts": contacts, "mntForm": mntForm, "contactForm": contactForm, "netblocks": netblocks, "asblocks": asblocks, "asnumbers": asnumbers, 'hasDelegations': hasDelegations, "showDelegations": showDelegations})
def showHandle(request, handle): def showHandle(request, handle):
# a) find handle # a) find handle
models = [Contact, Maintainer, ASBlock, ASNumber, InetNum] models = [Contact, Maintainer, ASBlock, ASNumber, InetNum]
obj = None obj = None
for model in models: for model in models:
if handle.endswith(model.handleSuffix): if handle.endswith(model.handleSuffix):
obj = get_object_or_404(model, handle=handle) obj = get_object_or_404(model, handle=handle)
break break
if not obj: if not obj:
raise Http404("Handle object not found") raise Http404("Handle object not found")
return render(request, "whoisdb/handle_show.html", {"object": obj}) return render(request, "whoisdb/handle_show.html", {"object": obj})
def searchObject(request): def searchObject(request):
results = None results = None
term = request.GET.get("q", None) term = request.GET.get("q", None)
if term: if term:
results = findInDatabase(term) results = findInDatabase(term)
return render(request, "whoisdb/search.html", {"results": results, "term": term}) return render(request, "whoisdb/search.html", {"results": results, "term": term})
class MaintainerCreate(LoginRequiredMixin, CreateView): class MaintainerCreate(LoginRequiredMixin, CreateView):
template_name = "whoisdb/obj_create.html" template_name = "whoisdb/obj_create.html"
form_class = MntForm form_class = MntForm
def get_form_kwargs(self, *args, **kwargs): def get_form_kwargs(self, *args, **kwargs):
kwargs = super(MaintainerCreate, self).get_form_kwargs(*args, **kwargs) kwargs = super(MaintainerCreate, self).get_form_kwargs(*args, **kwargs)
kwargs["user"] = self.request.user kwargs["user"] = self.request.user
kwargs["initial"] = { kwargs["initial"] = {
"handle": "AUTO", "handle": "AUTO",
#"auth": self.request.user.username, #"auth": self.request.user.username,
} }
return kwargs return kwargs
def form_valid(self, form): def form_valid(self, form):
self.object = form.save(commit=False) self.object = form.save(commit=False)
self.object.handleAuto(self.request.user.username) self.object.handleAuto(self.request.user.username)
self.object.save() self.object.save()
self.object.auth.add(self.request.user) self.object.auth.add(self.request.user)
self.object.save() self.object.save()
return super(MaintainerCreate, self).form_valid(form) return super(MaintainerCreate, self).form_valid(form)
class MaintainerEdit(LoginRequiredMixin, UpdateView): class MaintainerEdit(LoginRequiredMixin, UpdateView):
template_name = "whoisdb/obj_edit.html" template_name = "whoisdb/obj_edit.html"
model = Maintainer model = Maintainer
form_class = MntForm form_class = MntForm
slug_field = "handle" slug_field = "handle"
slug_url_kwarg = "handle" slug_url_kwarg = "handle"
def get_form_kwargs(self, *args, **kwargs): def get_form_kwargs(self, *args, **kwargs):
kwargs = super(MaintainerEdit, self).get_form_kwargs(*args, **kwargs) kwargs = super(MaintainerEdit, self).get_form_kwargs(*args, **kwargs)
kwargs["user"] = self.request.user kwargs["user"] = self.request.user
return kwargs return kwargs
def get_queryset(self): def get_queryset(self):
return self.model.objects.filter(auth=self.request.user) return self.model.objects.filter(auth=self.request.user)
class MaintainerDelete(LoginRequiredMixin, DeleteCheckView): class MaintainerDelete(LoginRequiredMixin, DeleteCheckView):
template_name = "whoisdb/obj_delete.html" template_name = "whoisdb/obj_delete.html"
model = Maintainer model = Maintainer
slug_field = "handle" slug_field = "handle"
slug_url_kwarg = "handle" slug_url_kwarg = "handle"
success_url = reverse_lazy("whoisdb:dashboard") success_url = reverse_lazy("whoisdb:dashboard")
def get_queryset(self): def get_queryset(self):
return self.model.objects.filter(auth=self.request.user) return self.model.objects.filter(auth=self.request.user)
class MaintainerDetail(LoginRequiredMixin, DetailView): class MaintainerDetail(LoginRequiredMixin, DetailView):
template_name = "whoisdb/handle_show.html" template_name = "whoisdb/handle_show.html"
model = Maintainer model = Maintainer
slug_field = "handle" slug_field = "handle"
slug_url_kwarg = "handle" slug_url_kwarg = "handle"
#context_object_name = "mnt" #context_object_name = "mnt"
class ContactDetail(DetailView): class ContactDetail(DetailView):
model = Contact model = Contact
slug_field = "handle" slug_field = "handle"
slug_url_kwarg = "handle" slug_url_kwarg = "handle"
context_object_name = "contact" context_object_name = "contact"
class ContactEdit(MntGenericMixin, LoginRequiredMixin, UpdateView): class ContactEdit(MntGenericMixin, LoginRequiredMixin, UpdateView):
template_name = "whoisdb/obj_edit.html" template_name = "whoisdb/obj_edit.html"
model = Contact model = Contact
form_class = ContactForm form_class = ContactForm
slug_field = "handle" slug_field = "handle"
slug_url_kwarg = "handle" slug_url_kwarg = "handle"
def get_form_kwargs(self, *args, **kwargs): def get_form_kwargs(self, *args, **kwargs):
kwargs = super(ContactEdit, self).get_form_kwargs(*args, **kwargs) kwargs = super(ContactEdit, self).get_form_kwargs(*args, **kwargs)
kwargs["user"] = self.request.user kwargs["user"] = self.request.user
return kwargs return kwargs
class ContactCreate(LoginRequiredMixin, CreateView): class ContactCreate(LoginRequiredMixin, CreateView):
template_name = "whoisdb/obj_create.html" template_name = "whoisdb/obj_create.html"
form_class = ContactForm form_class = ContactForm
def get_form_kwargs(self, *args, **kwargs): def get_form_kwargs(self, *args, **kwargs):
kwargs = super(ContactCreate, self).get_form_kwargs(*args, **kwargs) kwargs = super(ContactCreate, self).get_form_kwargs(*args, **kwargs)
kwargs["user"] = self.request.user kwargs["user"] = self.request.user
kwargs["initial"] = { kwargs["initial"] = {
"handle": "AUTO", "handle": "AUTO",
"type": Contact.TYPE_PERSON "type": Contact.TYPE_PERSON
} }
return kwargs return kwargs
class ContactDelete(MntGenericMixin, LoginRequiredMixin, DeleteCheckView): class ContactDelete(MntGenericMixin, LoginRequiredMixin, DeleteCheckView):
template_name = "whoisdb/obj_delete.html" template_name = "whoisdb/obj_delete.html"
model = Contact model = Contact
slug_field = "handle" slug_field = "handle"
slug_url_kwarg = "handle" slug_url_kwarg = "handle"
success_url = reverse_lazy("whoisdb:dashboard") success_url = reverse_lazy("whoisdb:dashboard")
# InetNum # InetNum
class InetNumCreate(LoginRequiredMixin, CreateView): class InetNumCreate(LoginRequiredMixin, CreateView):
template_name = "whoisdb/obj_create.html" template_name = "whoisdb/obj_create.html"
form_class = InetNumForm form_class = InetNumForm
def get_form_kwargs(self, *args, **kwargs): def get_form_kwargs(self, *args, **kwargs):
print("NOOOOOOOOOOOOOOOOOOOOOOOOOOOOT", args, kwargs) print("NOOOOOOOOOOOOOOOOOOOOOOOOOOOOT", args, kwargs)
kwargs = super(InetNumCreate, self).get_form_kwargs(*args, **kwargs) kwargs = super(InetNumCreate, self).get_form_kwargs(*args, **kwargs)
kwargs["user"] = self.request.user kwargs["user"] = self.request.user
kwargs["initial"] = { kwargs["initial"] = {
"handle": "AUTO", "handle": "AUTO",
} }
return kwargs return kwargs
class InetNumDetail(DetailView): class InetNumDetail(DetailView):
model = InetNum model = InetNum
slug_field = "handle" slug_field = "handle"
slug_url_kwarg = "handle" slug_url_kwarg = "handle"
context_object_name = "inetnum" context_object_name = "inetnum"
class InetNumEdit(MntGenericMixin, LoginRequiredMixin, UpdateView): class InetNumEdit(MntGenericMixin, LoginRequiredMixin, UpdateView):
template_name = "whoisdb/obj_edit.html" template_name = "whoisdb/obj_edit.html"
model = InetNum model = InetNum
form_class = InetNumForm form_class = InetNumForm
slug_field = "handle" slug_field = "handle"
slug_url_kwarg = "handle" slug_url_kwarg = "handle"
def get_form_kwargs(self, *args, **kwargs): def get_form_kwargs(self, *args, **kwargs):
kwargs = super(InetNumEdit, self).get_form_kwargs(*args, **kwargs) kwargs = super(InetNumEdit, self).get_form_kwargs(*args, **kwargs)
kwargs["user"] = self.request.user kwargs["user"] = self.request.user
kwargs["initial"] = {'prefix': str(self.object.getNetwork())} kwargs["initial"] = {'prefix': str(self.object.getNetwork())}
return kwargs return kwargs
class InetNumDelete(MntGenericMixin, LoginRequiredMixin, DeleteCheckView): class InetNumDelete(MntGenericMixin, LoginRequiredMixin, DeleteCheckView):
template_name = "whoisdb/obj_delete.html" template_name = "whoisdb/obj_delete.html"
model = InetNum model = InetNum
slug_field = "handle" slug_field = "handle"
slug_url_kwarg = "handle" slug_url_kwarg = "handle"
success_url = reverse_lazy("whoisdb:dashboard") success_url = reverse_lazy("whoisdb:dashboard")
# asblock # asblock
class ASBlockCreate(LoginRequiredMixin, CreateView): class ASBlockCreate(LoginRequiredMixin, CreateView):
template_name = "whoisdb/obj_create.html" template_name = "whoisdb/obj_create.html"
form_class = ASBlockForm form_class = ASBlockForm
def get_form_kwargs(self, *args, **kwargs): def get_form_kwargs(self, *args, **kwargs):
kwargs = super(ASBlockCreate, self).get_form_kwargs(*args, **kwargs) kwargs = super(ASBlockCreate, self).get_form_kwargs(*args, **kwargs)
kwargs["user"] = self.request.user kwargs["user"] = self.request.user
kwargs["initial"] = { kwargs["initial"] = {
"handle": "AUTO", "handle": "AUTO",
} }
return kwargs return kwargs
class ASBlockDetail(DetailView): class ASBlockDetail(DetailView):
model = ASBlock model = ASBlock
slug_field = "handle" slug_field = "handle"
slug_url_kwarg = "handle" slug_url_kwarg = "handle"
context_object_name = "asblock" context_object_name = "asblock"
class ASBlockEdit(MntGenericMixin, LoginRequiredMixin, UpdateView): class ASBlockEdit(MntGenericMixin, LoginRequiredMixin, UpdateView):
template_name = "whoisdb/obj_edit.html" template_name = "whoisdb/obj_edit.html"
model = ASBlock model = ASBlock
form_class = ASBlockForm form_class = ASBlockForm
slug_field = "handle" slug_field = "handle"
slug_url_kwarg = "handle" slug_url_kwarg = "handle"
def get_form_kwargs(self, *args, **kwargs): def get_form_kwargs(self, *args, **kwargs):
kwargs = super(ASBlockEdit, self).get_form_kwargs(*args, **kwargs) kwargs = super(ASBlockEdit, self).get_form_kwargs(*args, **kwargs)
kwargs["user"] = self.request.user kwargs["user"] = self.request.user
#kwargs["initial"] = {'prefix': str(self.object.getNetwork())} #kwargs["initial"] = {'prefix': str(self.object.getNetwork())}
return kwargs return kwargs
class ASBlockDelete(MntGenericMixin, LoginRequiredMixin, DeleteCheckView): class ASBlockDelete(MntGenericMixin, LoginRequiredMixin, DeleteCheckView):
template_name = "whoisdb/obj_delete.html" template_name = "whoisdb/obj_delete.html"
model = ASBlock model = ASBlock
slug_field = "handle" slug_field = "handle"
slug_url_kwarg = "handle" slug_url_kwarg = "handle"
success_url = reverse_lazy("whoisdb:dashboard") success_url = reverse_lazy("whoisdb:dashboard")
# asnumber # asnumber
class ASNumberCreate(LoginRequiredMixin, CreateView): class ASNumberCreate(LoginRequiredMixin, CreateView):
template_name = "whoisdb/obj_create.html" template_name = "whoisdb/obj_create.html"
form_class = ASNumberForm form_class = ASNumberForm
def get_form_kwargs(self, *args, **kwargs): def get_form_kwargs(self, *args, **kwargs):
kwargs = super(ASNumberCreate, self).get_form_kwargs(*args, **kwargs) kwargs = super(ASNumberCreate, self).get_form_kwargs(*args, **kwargs)
kwargs["user"] = self.request.user kwargs["user"] = self.request.user
kwargs["initial"] = { kwargs["initial"] = {
"handle": "AUTO", "handle": "AUTO",
} }
return kwargs return kwargs
class ASNumberDetail(DetailView): class ASNumberDetail(DetailView):
model = ASNumber model = ASNumber
slug_field = "handle" slug_field = "handle"
slug_url_kwarg = "handle" slug_url_kwarg = "handle"
context_object_name = "asnumber" context_object_name = "asnumber"
class ASNumberEdit(MntGenericMixin, LoginRequiredMixin, UpdateView): class ASNumberEdit(MntGenericMixin, LoginRequiredMixin, UpdateView):
template_name = "whoisdb/obj_edit.html" template_name = "whoisdb/obj_edit.html"
model = ASNumber model = ASNumber
form_class = ASNumberForm form_class = ASNumberForm
slug_field = "handle" slug_field = "handle"
slug_url_kwarg = "handle" slug_url_kwarg = "handle"
def get_form_kwargs(self, *args, **kwargs): def get_form_kwargs(self, *args, **kwargs):
kwargs = super(ASNumberEdit, self).get_form_kwargs(*args, **kwargs) kwargs = super(ASNumberEdit, self).get_form_kwargs(*args, **kwargs)
kwargs["user"] = self.request.user kwargs["user"] = self.request.user
return kwargs return kwargs
class ASNumberDelete(MntGenericMixin, LoginRequiredMixin, DeleteCheckView): class ASNumberDelete(MntGenericMixin, LoginRequiredMixin, DeleteCheckView):
template_name = "whoisdb/obj_delete.html" template_name = "whoisdb/obj_delete.html"
model = ASNumber model = ASNumber
slug_field = "handle" slug_field = "handle"
slug_url_kwarg = "handle" slug_url_kwarg = "handle"
success_url = reverse_lazy("whoisdb:dashboard") success_url = reverse_lazy("whoisdb:dashboard")
class ASAndSubnetWizard(LoginRequiredMixin, SessionWizardView): class ASAndSubnetWizard(LoginRequiredMixin, SessionWizardView):
form_list = [ASNumberForm, InetNumForm] form_list = [ASNumberForm, InetNumForm]
template_name = "whoisdb/wizard.html" template_name = "whoisdb/wizard.html"
def get_form_initial(self, step): def get_form_initial(self, step):
return {"handle": "AUTO"} return {"handle": "AUTO"}
def done(self, form_list, **kwargs): def done(self, form_list, **kwargs):
fl = list(form_list) fl = list(form_list)
asNum = fl[0].save() asNum = fl[0].save()
net = fl[1].save() net = fl[1].save()
messages.info(self.request, "The following objects have been created: AS %s %s, Subnet %s %s" % (asNum.handle, asNum.number, net.handle, net.getNetwork())) messages.info(self.request, "The following objects have been created: AS %s %s, Subnet %s %s" % (asNum.handle, asNum.number, net.handle, net.getNetwork()))
return HttpResponseRedirect(reverse("whoisdb:dashboard")) return HttpResponseRedirect(reverse("whoisdb:dashboard"))
def get_form_kwargs(self, *args, **kwargs): def get_form_kwargs(self, *args, **kwargs):
kwargs = super(ASAndSubnetWizard, self).get_form_kwargs(*args, **kwargs) kwargs = super(ASAndSubnetWizard, self).get_form_kwargs(*args, **kwargs)
kwargs["user"] = self.request.user kwargs["user"] = self.request.user
return kwargs return kwargs
def get_context_data(self, *args, **kwargs): def get_context_data(self, *args, **kwargs):
d = super(ASAndSubnetWizard, self).get_context_data(*args, **kwargs) d = super(ASAndSubnetWizard, self).get_context_data(*args, **kwargs)
step = d["wizard"]["steps"].step1 step = d["wizard"]["steps"].step1
if step == 1: if step == 1:
d["message"] = "Create an AS object" d["message"] = "Create an AS object"
elif step == 2: elif step == 2:
d["message"] = "Create a Subnet" d["message"] = "Create a Subnet"
return d return d