Merge branch 'master' into add-global-authentication

This commit is contained in:
Daniel Roschka 2015-07-24 12:14:21 +02:00
commit 52ebb1e1b1
14 changed files with 299 additions and 249 deletions

4
.landscape.yml Normal file
View File

@ -0,0 +1,4 @@
doc-warnings: yes
test-warnings: yes
uses:
- django

View File

@ -1,6 +1,7 @@
# Binder #
[![Build Status](https://travis-ci.org/jforman/binder.svg?branch=master)](https://travis-ci.org/jforman/binder)
[![Code Health](https://landscape.io/github/jforman/binder/master/landscape.svg?style=flat)](https://landscape.io/github/jforman/binder/master)
A Django web application for viewing and editing BIND DNS zone records.

View File

@ -4,6 +4,7 @@ from models import BindServer, Key
from django.contrib import admin
from django.forms import ModelForm, ValidationError
class BindServerAdminForm(ModelForm):
def clean_statistics_port(self):
port = self.cleaned_data["statistics_port"]
@ -13,7 +14,6 @@ class BindServerAdminForm(ModelForm):
params={'port': port})
return self.cleaned_data["statistics_port"]
def clean_dns_port(self):
port = self.cleaned_data["dns_port"]
if port < 1 or port > 65535:
@ -31,7 +31,7 @@ class BindServerAdmin(admin.ModelAdmin):
class KeyAdminForm(ModelForm):
def clean_data(self):
try:
keyring = dns.tsigkeyring.from_text({'': self.cleaned_data["data"]})
dns.tsigkeyring.from_text({'': self.cleaned_data["data"]})
except binascii.Error as err:
raise ValidationError("Invalid key data: %(error)s",
params={'error': err})

View File

@ -1,33 +1,35 @@
### Binder Exceptions
# Binder Exceptions
class TransferException(Exception):
"""
Thrown when an AXFR transfer cannot be performed.
"""
"""Thrown when an AXFR transfer cannot be performed."""
pass
class ZoneException(Exception):
"""
Thrown when there is an issue dealing with a
DNS zone.
"""
"""Thrown when there is an issue dealing with a DNS zone."""
pass
class RecordException(Exception):
"""
Thrown when there is an issue dealign with
a Record.
* Adding or deleting.
"""Thrown when there is an issue dealign with a Record.
* Adding or deleting.
"""
pass
class KeyringException(Exception):
"""
Thrown when there is a problem creating the keyring.
* When the length/padding of the TSIG data is incorrect.
"""Thrown when there is a problem creating the keyring.
* When the length/padding of the TSIG data is incorrect.
"""
pass

View File

@ -1,4 +1,4 @@
### Binder Forms
# Binder Forms
# 3rd Party
from django import forms
@ -8,10 +8,11 @@ from django.forms import ValidationError
# App Imports
from models import Key
### Custom Form Fields
class CustomUnicodeListField(forms.CharField):
""" Convert unicode item list to list of strings. """
"""Convert unicode item list to list of strings."""
def clean(self, value):
try:
string_list = [str(cur_rr) for cur_rr in eval(value)]
@ -19,12 +20,17 @@ class CustomUnicodeListField(forms.CharField):
raise ValidationError("Error in converting Unicode list to list of Strings: %r" % value)
return string_list
class CustomStringPeriodSuffix(forms.CharField):
""" Convert unicode to string and make sure period is last character. """
### This seems very unclean. Need a better to way to complete the fqdn
### depending on if it ends in a period.
### TODO(jforman): Add Regex check in here for valid rr data
### http://www.zytrax.com/books/dns/apa/names.html
"""Convert unicode to string and make sure period is last character.
This seems very unclean. Need a better to way to complete the fqdn
depending on if it ends in a period.
TODO(jforman): Add Regex check in here for valid rr data
http://www.zytrax.com/books/dns/apa/names.html
"""
def clean(self, value):
try:
new_string = str(value)
@ -34,42 +40,60 @@ class CustomStringPeriodSuffix(forms.CharField):
raise ValidationError("Unable to stick a period on the end of your input: %r" % value)
return new_string
### Form Models
class FormAddForwardRecord(forms.Form):
""" Form used to add a Forward DNS record. """
"""Form used to add a Forward DNS record."""
dns_server = forms.CharField(max_length=100)
record_name = forms.RegexField(max_length=100, regex="^[a-zA-Z0-9-_]+$", required=False)
record_name = forms.RegexField(max_length=100,
regex="^[a-zA-Z0-9-_]+$",
required=False)
record_type = forms.ChoiceField(choices=settings.RECORD_TYPE_CHOICES)
zone_name = forms.CharField(max_length=100)
record_data = forms.GenericIPAddressField()
ttl = forms.ChoiceField(choices=settings.TTL_CHOICES)
create_reverse = forms.BooleanField(required=False)
key_name = forms.ModelChoiceField(queryset=Key.objects.all(), required=False)
key_name = forms.ModelChoiceField(queryset=Key.objects.all(),
required=False)
class FormAddReverseRecord(forms.Form):
""" Form used to add a Reverse (PTR) DNS record. """
"""Form used to add a Reverse (PTR) DNS record."""
dns_server = forms.CharField(max_length=100)
record_name = forms.IntegerField(min_value=0, max_value=255)
record_type = forms.RegexField(regex=r"^PTR$",error_messages={"invalid" : "The only valid choice here is PTR."})
record_type = forms.RegexField(regex=r"^PTR$",
error_messages={"invalid": "The only valid choice here is PTR."})
zone_name = forms.CharField(max_length=100)
record_data = CustomStringPeriodSuffix(required=True)
ttl = forms.ChoiceField(choices=settings.TTL_CHOICES)
key_name = forms.ModelChoiceField(queryset=Key.objects.all(), required=False)
key_name = forms.ModelChoiceField(queryset=Key.objects.all(),
required=False)
create_reverse = forms.BooleanField(required=False)
class FormAddCnameRecord(forms.Form):
""" Form used to add a CNAME record. """
"""Form used to add a CNAME record."""
dns_server = forms.CharField(max_length=100)
originating_record = forms.CharField(max_length=100)
cname = forms.RegexField(max_length=100, regex="^[a-zA-Z0-9-_]+$")
zone_name = forms.CharField(max_length=256)
ttl = forms.ChoiceField(choices=settings.TTL_CHOICES)
key_name = forms.ModelChoiceField(queryset=Key.objects.all(), required=False)
key_name = forms.ModelChoiceField(queryset=Key.objects.all(),
required=False)
class FormDeleteRecord(forms.Form):
""" Final form to delete DNS record(s). """
"""Final form to delete DNS record(s)."""
dns_server = forms.CharField(max_length=100)
zone_name = forms.CharField(max_length=256)
rr_list = CustomUnicodeListField()
key_name = forms.ModelChoiceField(queryset=Key.objects.all(), required=False)
key_name = forms.ModelChoiceField(queryset=Key.objects.all(),
required=False)

View File

@ -1,10 +1,8 @@
### Binder Helpers
# Binder Helpers
# Standard Imports
import binascii
import re
import socket
import sys
# 3rd Party
import dns.query
@ -14,10 +12,12 @@ import dns.tsigkeyring
import dns.update
# App Imports
from binder import exceptions, models
from binder import models
def add_record(dns_server, zone_name, record_name, record_type, record_data, ttl, key_name, create_reverse=False):
""" Parse passed elements and determine which records to create.
def add_record(dns_server, zone_name, record_name, record_type, record_data,
ttl, key_name, create_reverse=False):
"""Parse passed elements and determine which records to create.
Args:
String dns_server
@ -32,19 +32,21 @@ def add_record(dns_server, zone_name, record_name, record_type, record_data, ttl
Return:
Dict containing {description, output} from record creation
"""
response = []
response.append({ "description" : "Forward Record Creation: %s.%s" % (record_name, zone_name),
"output" : create_update(dns_server,
zone_name,
record_name,
record_type,
record_data,
ttl,
key_name)})
response.append({"description": "Forward Record Creation: %s.%s" %
(record_name, zone_name),
"output": create_update(dns_server,
zone_name,
record_name,
record_type,
record_data,
ttl,
key_name)})
""" If requested, create a reverse PTR record.
Given the forward record created, resolve its underlying IP. Use that to create the reverse record.
"""If requested, create a reverse PTR record.
Given the forward record created, resolve its underlying IP.
Use that to create the reverse record.
reverse_ip_fqdn ex: 5.0.20.10.in-addr.arpa.
reverse_ip: 5
reverse_domain: 0.20.10.in-addr.arpa.
@ -55,20 +57,21 @@ def add_record(dns_server, zone_name, record_name, record_type, record_data, ttl
# for this reverse DNS record parsing.
reverse_ip = re.search(r"([0-9]+).(.*)$", reverse_ip_fqdn).group(1)
reverse_domain = re.search(r"([0-9]+).(.*)$", reverse_ip_fqdn).group(2)
response.append({ "description" : "Reverse Record Creation: %s" % record_data,
"output" : create_update(dns_server,
reverse_domain,
reverse_ip,
"PTR",
"%s.%s." % (record_name, zone_name),
ttl,
key_name)})
response.append({"description": "Reverse Record Creation: %s" % record_data,
"output": create_update(dns_server,
reverse_domain,
reverse_ip,
"PTR",
"%s.%s." % (record_name, zone_name),
ttl,
key_name)})
return response
def add_cname_record(dns_server, zone_name, cname, originating_record, ttl, key_name):
"""Add a Cname record."""
def add_cname_record(dns_server, zone_name, cname, originating_record, ttl,
key_name):
"""Add a CNAME record."""
output = create_update(dns_server,
zone_name,
cname,
@ -77,12 +80,13 @@ def add_cname_record(dns_server, zone_name, cname, originating_record, ttl, key_
ttl,
key_name)
return [{ "description" : "CNAME %s.%s points to %s" % (cname, zone_name, originating_record),
"output" : output}]
return [{"description": "CNAME %s.%s points to %s" %
(cname, zone_name, originating_record),
"output": output}]
def delete_record(dns_server, rr_list, key_name):
"""Delete a list of DNS records passed as strings in rr_items."""
server = models.BindServer.objects.get(hostname=dns_server)
try:
@ -99,18 +103,24 @@ def delete_record(dns_server, rr_list, key_name):
record_list = current_rr.split(".", 1)
record = record_list[0]
domain = record_list[1]
dns_update = dns.update.Update(domain, keyring=keyring, keyalgorithm=algorithm)
dns_update = dns.update.Update(domain,
keyring=keyring,
keyalgorithm=algorithm)
dns_update.delete(record)
output = send_dns_update(dns_update, dns_server, server.dns_port, key_name)
output = send_dns_update(dns_update,
dns_server,
server.dns_port,
key_name)
delete_response.append({ "description" : "Delete Record: %s" % current_rr,
"output" : output })
delete_response.append({"description": "Delete Record: %s" % current_rr,
"output": output})
return delete_response
def create_update(dns_server, zone_name, record_name, record_type, record_data, ttl, key_name):
""" Update/Create DNS record of name and type with passed data and ttl. """
def create_update(dns_server, zone_name, record_name, record_type, record_data,
ttl, key_name):
"""Update/Create DNS record of name and type with passed data and ttl."""
server = models.BindServer.objects.get(hostname=dns_server)
try:
@ -122,14 +132,18 @@ def create_update(dns_server, zone_name, record_name, record_type, record_data,
keyring = transfer_key.create_keyring()
algorithm = transfer_key.algorithm
dns_update = dns.update.Update(zone_name, keyring=keyring, keyalgorithm=algorithm)
dns_update = dns.update.Update(zone_name,
keyring=keyring,
keyalgorithm=algorithm)
dns_update.replace(record_name, ttl, record_type, record_data)
output = send_dns_update(dns_update, dns_server, server.dns_port, key_name)
return output
def ip_info(host_name):
"""Create a dictionary mapping address types to their IP's.
If an error is encountered, key to error is "Error".
"""
info = []
@ -148,8 +162,9 @@ def ip_info(host_name):
return info
def send_dns_update(dns_message, dns_server, port, key_name):
""" Send DNS message to server and return response.
"""Send DNS message to server and return response.
Args:
Update dns_update
@ -159,14 +174,13 @@ def send_dns_update(dns_message, dns_server, port, key_name):
Returns:
String output
"""
try:
output = dns.query.tcp(dns_message, dns_server, port=port)
except dns.tsig.PeerBadKey:
output = ("DNS server %s is not configured for TSIG key: %s." %
(dns_server, key_name))
except dns.tsig.PeerBadSignature:
output = ("DNS server %s did like the TSIG signature we sent. Check key %s "
"for correctness." % (dns_server, key_name))
output = ("DNS server %s did like the TSIG signature we sent. Check "
"key %s for correctness." % (dns_server, key_name))
return output

View File

@ -3,7 +3,6 @@
# Standard Imports
import binascii
import socket
import urllib2
# 3rd Party
from pybindxml import reader as bindreader
@ -22,20 +21,24 @@ TSIG_ALGORITHMS = (('HMAC-MD5.SIG-ALG.REG.INT', 'MD5'),
('hmac-sha384', 'SHA384'),
('hmac-sha512', 'SHA512'))
class Key(models.Model):
""" Store and reference TSIG keys.
"""Store and reference TSIG keys.
TODO: Should/Can we encrypt these DNS keys in the DB?
"""
name = models.CharField(max_length=255,
unique=True,
help_text="A human readable name for the key to store, used for "
"further references to the key.")
help_text="A human readable name for the key to "
"store, used for further references to the key.")
data = models.CharField(max_length=255,
help_text="The private part of the TSIG key.")
algorithm = models.CharField(max_length=255,
choices=TSIG_ALGORITHMS,
help_text="The algorithm which has been used for the key.")
help_text="The algorithm which has been used "
"for the key.")
def __unicode__(self):
return self.name
@ -56,10 +59,12 @@ class Key(models.Model):
class BindServer(models.Model):
""" Store DNS servers and attributes for referencing their
statistics ports. Also reference FK for TSIG transfer keys,
if required.
"""Store DNS servers and attributes for referencing their statistics ports.
Also reference FK for TSIG transfer keys, if required.
"""
hostname = models.CharField(max_length=255,
unique=True,
help_text="Host name or IP address of the BIND server.")
@ -85,7 +90,7 @@ class BindServer(models.Model):
ordering = ["hostname"]
def list_zones(self):
""" List the DNS zones and attributes.
"""List the DNS zones and attributes.
TODO: Parse these XML more intelligently. Grab the view name. Any other data available?
@ -100,7 +105,7 @@ class BindServer(models.Model):
return zone_data
def list_zone_records(self, zone_name):
""" List all records in a specific zone.
"""List all records in a specific zone.
TODO: Print out current_record in the loop and see if we can parse this more programatically,
rather than just splitting on space. What is the difference between class and type?
@ -110,7 +115,6 @@ class BindServer(models.Model):
Returns:
List of Dicts { String rr_name, String rr_ttl, String rr_class, String rr_type, String rr_data }
"""
try:
transfer_key = Key.objects.get(name=self.default_transfer_key)
except Key.DoesNotExist:
@ -154,4 +158,3 @@ class BindServer(models.Model):
record_array.append(rr_dict)
return record_array

View File

@ -2,10 +2,11 @@ from django.core.urlresolvers import reverse
from django.test import TestCase
from django.test.client import Client
from binder import helpers, models
from binder import models
class Integration_Tests(TestCase):
fixtures = [ "binder/fixtures/binder_test.json" ]
fixtures = ["binder/fixtures/binder_test.json"]
def setUp(self):
self.client = Client()
@ -13,13 +14,13 @@ class Integration_Tests(TestCase):
def test_Integration_Add_Record(self):
"""Add forward and reverse record on domain1.local."""
add_dict = { "dns_server" : self.testserver,
"record_name" : "record1",
"record_type" : "A",
"zone_name" : "domain1.local",
"record_data" : "10.254.1.101",
"ttl" : 86400,
"create_reverse" : True}
add_dict = {"dns_server": self.testserver,
"record_name": "record1",
"record_type": "A",
"zone_name": "domain1.local",
"record_data": "10.254.1.101",
"ttl": 86400,
"create_reverse": True}
response = self.client.post(reverse("add_record_result"), add_dict)
self.assertEqual(response.status_code, 200)
# Make sure that we get two responses (fwd/rev) back from the server.
@ -32,10 +33,9 @@ class Integration_Tests(TestCase):
def test_Integration_Delete_Record(self):
"""Delete record1.domain1.local"""
delete_dict = { "dns_server" : self.testserver,
"zone_name" : "domain1.local",
"rr_list" : '[u"record1.domain1.local", u"record2.domain1.local"]',
}
delete_dict = {"dns_server": self.testserver,
"zone_name": "domain1.local",
"rr_list": '[u"record1.domain1.local", u"record2.domain1.local"]'}
response = self.client.post(reverse("delete_record_result"), delete_dict)
self.assertEqual(response.status_code, 200)
self.assertEqual(len(response.context["response"]), 2)
@ -44,16 +44,15 @@ class Integration_Tests(TestCase):
self.assertRegexpMatches(dns_update_output, "opcode UPDATE")
self.assertRegexpMatches(dns_update_output, "rcode NOERROR")
def test_Integration_Add_Cname(self):
""" Add CNAME cnametest1 after adding associated A record record1."""
add_dict = { "dns_server" : self.testserver,
"record_name" : "record1",
"record_type" : "A",
"zone_name" : "domain1.local",
"record_data" : "10.254.1.101",
"ttl" : 86400,
"create_reverse" : False}
"""Add CNAME cnametest1 after adding associated A record record1."""
add_dict = {"dns_server": self.testserver,
"record_name": "record1",
"record_type": "A",
"zone_name": "domain1.local",
"record_data": "10.254.1.101",
"ttl": 86400,
"create_reverse": False}
response = self.client.post(reverse("add_record_result"), add_dict)
self.assertEqual(response.status_code, 200)
# Make sure that we get two responses (fwd/rev) back from the server.
@ -64,19 +63,17 @@ class Integration_Tests(TestCase):
self.assertRegexpMatches(dns_update_output, "opcode UPDATE")
self.assertRegexpMatches(dns_update_output, "rcode NOERROR")
cname_dict = { "dns_server" : self.testserver,
"originating_record" : "record1.domain1.local",
"cname" : "cnametest1",
"zone_name" : "domain1.local",
"ttl" : 86400,
}
cname_dict = {"dns_server": self.testserver,
"originating_record": "record1.domain1.local",
"cname": "cnametest1",
"zone_name": "domain1.local",
"ttl": 86400}
response = self.client.post(reverse("add_cname_result"), cname_dict)
self.assertEqual(response.status_code, 200)
for current_response in response.context["response"]:
dns_update_output = str(current_response["output"])
self.assertRegexpMatches(dns_update_output, "opcode UPDATE")
self.assertRegexpMatches(dns_update_output, "rcode NOERROR")
def test_Integration_ServerZoneList_ConnectionRefused(self):
"""Confirm connection refused on a server zone list."""
@ -94,11 +91,14 @@ class Integration_Tests(TestCase):
def test_Integration_ZoneList_MissingTransferKey(self):
"""Attempt to list a zone's records with missing TSIG key.
domain3.local should be configured to require a TSIG key
for transfers."""
for transfers.
"""
dns_server = models.BindServer.objects.get(hostname="testserver1")
response = self.client.get(reverse("zone_list", args=("testserver1", "domain3.local")))
self.assertEqual(response.status_code, 200)
self.assertEqual(response.context["zone_name"], "domain3.local")
self.assertEqual(response.context["dns_server"], "testserver1")
self.assertRegexpMatches(str(response.context["errors"]), "Unable to perform AXFR to list zone records. Did you forget to specify a default transfer key?")
self.assertRegexpMatches(str(response.context["errors"]),
"Unable to perform AXFR to list zone records. Did you forget to specify a default transfer key?")

View File

@ -2,56 +2,55 @@ from django.test import TestCase
from binder import forms
class Form_Tests(TestCase):
def test_Valid_FormAddRecord(self):
"""Test FormAddRecord with valid data, with/without create_reverse."""
form_data = {"dns_server":"server1",
"record_name":"record1",
"record_type":"A",
"zone_name":"domain.local",
"record_data":"10.254.254.254",
"ttl":3600,
form_data = {"dns_server": "server1",
"record_name": "record1",
"record_type": "A",
"zone_name": "domain.local",
"record_data": "10.254.254.254",
"ttl": 3600,
"key_name": None,
"create_reverse" : False}
"create_reverse": False}
testform_1 = forms.FormAddForwardRecord(form_data)
self.assertTrue(testform_1.is_valid())
form_data = {"dns_server":"server1",
"record_name":"record1",
"record_type":"A",
"zone_name":"domain.local",
"record_data":"10.254.254.254",
"ttl":3600,
"key_name":None,
"create_reverse":True}
form_data = {"dns_server": "server1",
"record_name": "record1",
"record_type": "A",
"zone_name": "domain.local",
"record_data": "10.254.254.254",
"ttl": 3600,
"key_name": None,
"create_reverse": True}
testform_2 = forms.FormAddForwardRecord(form_data)
self.assertTrue(testform_2.is_valid())
form_data = { "dns_server" : "server1",
"record_name" : 41,
"record_type" : "PTR",
"zone_name" : "1.254.10.in-addr.arpa",
"record_data" : "reverse41.domain1.local",
"ttl" : 3600,
"key_name" : None }
form_data = {"dns_server": "server1",
"record_name": 41,
"record_type": "PTR",
"zone_name": "1.254.10.in-addr.arpa",
"record_data": "reverse41.domain1.local",
"ttl": 3600,
"key_name": None}
reverseform_1 = forms.FormAddReverseRecord(form_data)
reverseform_1.is_valid()
self.assertTrue(reverseform_1.is_valid())
def test_MissingData_FormAddRecord(self):
""" Submit FormAddRecord with missing record_data."""
form_data = {"dns_server":"server1",
"record_name":"record1",
"""Submit FormAddRecord with missing record_data."""
form_data = {"dns_server": "server1",
"record_name": "record1",
"record_type": "A",
"zone_name":"domain.local",
"record_data":"",
"zone_name": "domain.local",
"record_data": "",
"ttl": 300,
"key_name":None,
"create_reverse":True}
"key_name": None,
"create_reverse": True}
expected_form_errors = {"record_data": [u"This field is required."]}
testform = forms.FormAddForwardRecord(form_data)
@ -60,15 +59,15 @@ class Form_Tests(TestCase):
self.assertEquals(expected_form_errors, testform.errors)
def test_InvalidValue_FormAddRecord(self):
""" Pass FormAddRecord invalid values, compare error response dicts."""
form_data = {"dns_server":"server1",
"record_name":"record1$$$",
"record_type":123,
"zone_name":"domain.local",
"record_data":"A.B.C.D",
"ttl":"A",
"key_name":None,
"create_reverse":True}
"""Pass FormAddRecord invalid values, compare error response dicts."""
form_data = {"dns_server": "server1",
"record_name": "record1$$$",
"record_type": 123,
"zone_name": "domain.local",
"record_data": "A.B.C.D",
"ttl": "A",
"key_name": None,
"create_reverse": True}
expected_form_errors = {"record_data": [u"Enter a valid IPv4 or IPv6 address."],
"record_name": [u"Enter a valid value."],
@ -81,10 +80,9 @@ class Form_Tests(TestCase):
def test_Validation_FormDeleteRecord(self):
"""Validate good data in the FormDeleteRecord form."""
delete_dict = { "dns_server" : "foo.net",
"zone_name" : "domain1.local",
"rr_list" : '[u"record1.domain1.local", u"record2.domain1.local"]',
}
delete_dict = {"dns_server": "foo.net",
"zone_name": "domain1.local",
"rr_list": '[u"record1.domain1.local", u"record2.domain1.local"]'}
testform_1 = forms.FormDeleteRecord(delete_dict)
testform_1.is_valid
self.assertFalse(testform_1.errors)
@ -95,12 +93,12 @@ class Form_Tests(TestCase):
def test_MissingName_AddCnameForm(self):
"""Attempt to submit a cname add form missing the cname value."""
form_dict = { "dns_server" : "testserver1",
"zone_name" : "domain1.local",
"originating_record" : "record1.domain1.local",
"cname" : "",
"ttl" : 300 }
expected_form_errors = { "cname" : [u"This field is required."] }
form_dict = {"dns_server": "testserver1",
"zone_name": "domain1.local",
"originating_record": "record1.domain1.local",
"cname": "",
"ttl": 300}
expected_form_errors = {"cname": [u"This field is required."]}
testform_1 = forms.FormAddCnameRecord(form_dict)
testform_1.is_valid()
self.assertTrue(testform_1.errors)

View File

@ -3,6 +3,7 @@ from django.db import IntegrityError
from binder import models
class Model_BindServer_Tests(TestCase):
def test_BindServerModel(self):
"""Test that adding a well-formed BindServer works."""
@ -28,7 +29,7 @@ class Model_BindServer_Tests(TestCase):
class Model_Key_Tests(TestCase):
def test_KeyModel(self):
""" Test that adding a well-formed Key works."""
"""Test that adding a well-formed Key works."""
self.assertEqual(models.Key.objects.count(), 0)
key_1 = models.Key(name="testkey1",
data="abc123",
@ -38,4 +39,4 @@ class Model_Key_Tests(TestCase):
def test_NonExistantKey(self):
with self.assertRaisesMessage(models.Key.DoesNotExist, "Key matching query does not exist"):
this_key = models.Key.objects.get(name="does_not_exist")
models.Key.objects.get(name="does_not_exist")

View File

@ -3,11 +3,13 @@ from django.test.client import Client
from django.contrib.auth.models import User
from django.core.urlresolvers import reverse
from binder import models, helpers
from binder import models
class GetTests(TestCase):
""" Unit Tests that exercise HTTP GET. """
"""Unit Tests that exercise HTTP GET."""
def setUp(self):
self.client = Client()
user = User.objects.create_user('testuser',
@ -26,7 +28,7 @@ class GetTests(TestCase):
self.assertEqual(response.status_code, 200)
def test_GetResultRedirects(self):
""" GETing a /result/ URL should always redirect to /. """
"""GETing a /result/ URL should always redirect to /."""
response = self.client.get(reverse("add_record_result"), follow=True)
self.assertRedirects(response, reverse("index"))
self.assertEqual(response.status_code, 200)
@ -38,14 +40,17 @@ class GetTests(TestCase):
self.assertEqual(response.status_code, 200)
def test_GetInvalidServer(self):
""" Get a zone list for a server not in the database."""
"""Get a zone list for a server not in the database."""
server_name = "unconfigured.server.net"
response = self.client.get(reverse("server_zone_list", args=(server_name, )))
response = self.client.get(reverse("server_zone_list",
args=(server_name, )))
self.assertEqual(response.status_code, 404)
class PostTests(TestCase):
""" Unit Tests that exercise HTTP POST. """
"""Unit Tests that exercise HTTP POST."""
def setUp(self):
self.client = Client()
models.BindServer(hostname="testserver.test.net",
@ -58,10 +63,11 @@ class PostTests(TestCase):
password='testpassword')
def test_DeleteRecordInitial_Empty(self):
""" Ensure the initial deletion form works as expected with no RR list. """
response = self.client.post(reverse("delete_record"), { "dns_server" : "testserver.test.net",
"zone_name" : "testzone1.test.net",
"rr_list" : [] })
"""Ensure the initial deletion form works as expected with no RR list."""
response = self.client.post(reverse("delete_record"),
{"dns_server": "testserver.test.net",
"zone_name": "testzone1.test.net",
"rr_list": []})
self.assertContains(response,
'<input type="text" class="form-control hidden" name="zone_name" value="testzone1.test.net"/>',
@ -75,11 +81,11 @@ class PostTests(TestCase):
def test_DeleteRecordInitial(self):
""" Ensure the initial deletion form works as expected with RRs mentioned. """
response = self.client.post(reverse("delete_record"), {"dns_server" : "testserver.test.net",
"zone_name" : "testzone1.test.net",
"rr_list" : ["testrecord1.testzone1.test.net",
"testrecord2.testzone1.test.net"] })
"""Ensure the initial deletion form works as expected with RRs mentioned."""
response = self.client.post(reverse("delete_record"), {"dns_server": "testserver.test.net",
"zone_name": "testzone1.test.net",
"rr_list": ["testrecord1.testzone1.test.net",
"testrecord2.testzone1.test.net"]})
self.assertContains(response,
'<input type="text" class="form-control hidden" name="zone_name" value="testzone1.test.net"/>', html=True)
self.assertContains(response,

View File

@ -1,6 +1,4 @@
from django.conf.urls import patterns, include, url
from django.core.urlresolvers import reverse
from django.contrib import admin
admin.autodiscover()

View File

@ -1,4 +1,4 @@
### Binder VIews
# Binder VIews
# 3rd Party
from django.conf import settings
@ -7,24 +7,26 @@ from django.shortcuts import get_object_or_404, redirect, render
# App Imports
from binder import exceptions, forms, helpers, models
def home_index(request):
""" List the main index page for Binder. """
"""List the main index page for Binder."""
return render(request, "index.html")
def view_server_list(request):
""" List the DNS servers configured in the database. """
"""List the DNS servers configured in the database."""
server_list = models.BindServer.objects.all().order_by("hostname")
server_info = []
for current in server_list:
server_info.append({"host_name" : current, "ip_address" : helpers.ip_info(current.hostname)})
server_info.append({"host_name": current,
"ip_address": helpers.ip_info(current.hostname)})
return render(request, "bcommon/list_servers.html",
{ "server_info" : server_info})
{"server_info": server_info})
def view_server_zones(request, dns_server):
""" Display the list of DNS zones a particular DNS host provides. """
"""Display the list of DNS zones a particular DNS host provides."""
errors = ""
zone_array = {}
@ -36,13 +38,13 @@ def view_server_zones(request, dns_server):
errors = "Unable to list server zones. Error: %s" % err
return render(request, "bcommon/list_server_zones.html",
{ "errors" : errors,
"dns_server" : this_server,
"zone_array" : zone_array})
{"errors": errors,
"dns_server": this_server,
"zone_array": zone_array})
def view_zone_records(request, dns_server, zone_name):
""" Display the list of records for a particular zone. """
"""Display the list of records for a particular zone."""
errors = ""
zone_array = {}
@ -52,32 +54,31 @@ def view_zone_records(request, dns_server, zone_name):
zone_array = this_server.list_zone_records(zone_name)
except exceptions.TransferException, err:
return render(request, "bcommon/list_zone.html",
{ "errors" : err,
"zone_name" : zone_name,
"dns_server" : this_server})
{"errors": err,
"zone_name": zone_name,
"dns_server": this_server})
return render(request, "bcommon/list_zone.html",
{ "zone_array" : zone_array,
"dns_server" : this_server,
"zone_name" : zone_name,
"errors" : errors})
{"zone_array": zone_array,
"dns_server": this_server,
"zone_name": zone_name,
"errors": errors})
def view_add_record(request, dns_server, zone_name):
""" View to provide form to add a DNS record. """
"""View to provide form to add a DNS record."""
this_server = get_object_or_404(models.BindServer, hostname=dns_server)
return render(request, "bcommon/add_record_form.html",
{ "dns_server" : this_server,
"zone_name" : zone_name,
"tsig_keys" : models.Key.objects.all(),
"ttl_choices": settings.TTL_CHOICES,
"record_type_choices": settings.RECORD_TYPE_CHOICES,
})
{"dns_server": this_server,
"zone_name": zone_name,
"tsig_keys": models.Key.objects.all(),
"ttl_choices": settings.TTL_CHOICES,
"record_type_choices": settings.RECORD_TYPE_CHOICES})
def view_add_record_result(request):
""" Process the input given to add a DNS record. """
"""Process the input given to add a DNS record."""
errors = ""
if request.method == "GET":
return redirect("/")
@ -108,36 +109,35 @@ def view_add_record_result(request):
errors = err
return render(request, "bcommon/response_result.html",
{ "errors" : errors,
"response" : response })
{"errors": errors,
"response": response})
dns_server = models.BindServer.objects.get(hostname=request.POST["dns_server"])
return render(request, "bcommon/add_record_form.html",
{ "dns_server" : dns_server,
"zone_name" : request.POST["zone_name"],
"tsig_keys" : models.Key.objects.all(),
"ttl_choices": settings.TTL_CHOICES,
"record_type_choices": settings.RECORD_TYPE_CHOICES,
"form_errors" : form.errors,
"form_data" : request.POST })
{"dns_server": dns_server,
"zone_name": request.POST["zone_name"],
"tsig_keys": models.Key.objects.all(),
"ttl_choices": settings.TTL_CHOICES,
"record_type_choices": settings.RECORD_TYPE_CHOICES,
"form_errors": form.errors,
"form_data": request.POST})
def view_add_cname_record(request, dns_server, zone_name, record_name):
""" Process given input to add a CNAME pointer."""
"""Process given input to add a CNAME pointer."""
this_server = get_object_or_404(models.BindServer, hostname=dns_server)
return render(request, "bcommon/add_cname_record_form.html",
{ "dns_server" : this_server,
"originating_record" : "%s.%s" % (record_name, zone_name),
"zone_name" : zone_name,
"ttl_choices": settings.TTL_CHOICES,
"tsig_keys" : models.Key.objects.all() })
{"dns_server": this_server,
"originating_record": "%s.%s" % (record_name, zone_name),
"zone_name": zone_name,
"ttl_choices": settings.TTL_CHOICES,
"tsig_keys": models.Key.objects.all()})
def view_add_cname_result(request):
""" Process input on the CNAME form and provide a response."""
"""Process input on the CNAME form and provide a response."""
if request.method == "GET":
return redirect("/")
@ -158,24 +158,24 @@ def view_add_cname_result(request):
errors = err
return render(request, "bcommon/response_result.html",
{"response" : add_cname_response,
"errors" : errors })
{"response": add_cname_response,
"errors": errors})
dns_server = models.BindServer.objects.get(hostname=request.POST["dns_server"])
return render(request, "bcommon/add_cname_record_form.html",
{ "dns_server" : dns_server,
"zone_name" : request.POST["zone_name"],
"record_name" : request.POST["cname"],
"originating_record" : request.POST["originating_record"],
"form_data" : request.POST,
"form_errors" : form.errors,
"ttl_choices": settings.TTL_CHOICES,
"tsig_keys" : models.Key.objects.all() })
{"dns_server": dns_server,
"zone_name": request.POST["zone_name"],
"record_name": request.POST["cname"],
"originating_record": request.POST["originating_record"],
"form_data": request.POST,
"form_errors": form.errors,
"ttl_choices": settings.TTL_CHOICES,
"tsig_keys": models.Key.objects.all()})
def view_delete_record(request):
""" Provide the initial form for deleting records. """
"""Provide the initial form for deleting records."""
if request.method == "GET":
return redirect("/")
@ -184,14 +184,14 @@ def view_delete_record(request):
rr_list = request.POST.getlist("rr_list")
return render(request, "bcommon/delete_record_initial.html",
{ "dns_server" : dns_server,
"zone_name" : zone_name,
"rr_list" : rr_list,
"tsig_keys" : models.Key.objects.all() })
{"dns_server": dns_server,
"zone_name": zone_name,
"rr_list": rr_list,
"tsig_keys": models.Key.objects.all()})
def view_delete_result(request):
""" View that deletes records and returns the response. """
"""View that deletes records and returns the response."""
if request.method == "GET":
return redirect("/")
@ -209,4 +209,4 @@ def view_delete_result(request):
clean_form["key_name"])
return render(request, "bcommon/response_result.html",
{ "response" : delete_result })
{"response": delete_result})

View File

@ -8,7 +8,6 @@ https://docs.djangoproject.com/en/1.7/howto/deployment/wsgi/
"""
import os
import sys
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "binder.settings")
from django.core.wsgi import get_wsgi_application