manage records_allow_to_edit setting in DB

This commit is contained in:
Khanh Ngo 2018-08-22 08:36:53 +07:00
parent 74a7b5a3b7
commit 9506315a46
7 changed files with 189 additions and 43 deletions

View File

@ -14,6 +14,7 @@ import dns.name
import sys import sys
import logging as logger import logging as logger
from ast import literal_eval
from datetime import datetime from datetime import datetime
from urllib.parse import urljoin from urllib.parse import urljoin
from distutils.util import strtobool from distutils.util import strtobool
@ -1775,7 +1776,7 @@ class History(db.Model):
class Setting(db.Model): class Setting(db.Model):
id = db.Column(db.Integer, primary_key = True) id = db.Column(db.Integer, primary_key = True)
name = db.Column(db.String(64)) name = db.Column(db.String(64))
value = db.Column(db.String(256)) value = db.Column(db.Text())
view = db.Column(db.String(64)) view = db.Column(db.String(64))
defaults = { defaults = {
@ -1821,6 +1822,8 @@ class Setting(db.Model):
'google_token_params': {'scope': 'email profile'}, 'google_token_params': {'scope': 'email profile'},
'google_authorize_url':'https://accounts.google.com/o/oauth2/auth', 'google_authorize_url':'https://accounts.google.com/o/oauth2/auth',
'google_base_url':'https://www.googleapis.com/oauth2/v1/', 'google_base_url':'https://www.googleapis.com/oauth2/v1/',
'forward_records_allow_edit': {'A': True, 'AAAA': True, 'AFSDB': False, 'ALIAS': False, 'CAA': True, 'CERT': False, 'CDNSKEY': False, 'CDS': False, 'CNAME': True, 'DNSKEY': False, 'DNAME': False, 'DS': False, 'HINFO': False, 'KEY': False, 'LOC': True, 'MX': True, 'NAPTR': False, 'NS': True, 'NSEC': False, 'NSEC3': False, 'NSEC3PARAM': False, 'OPENPGPKEY': False, 'PTR': True, 'RP': False, 'RRSIG': False, 'SOA': False, 'SPF': True, 'SSHFP': False, 'SRV': True, 'TKEY': False, 'TSIG': False, 'TLSA': False, 'SMIMEA': False, 'TXT': True, 'URI': False},
'reverse_records_allow_edit': {'A': False, 'AAAA': False, 'AFSDB': False, 'ALIAS': False, 'CAA': False, 'CERT': False, 'CDNSKEY': False, 'CDS': False, 'CNAME': False, 'DNSKEY': False, 'DNAME': False, 'DS': False, 'HINFO': False, 'KEY': False, 'LOC': True, 'MX': False, 'NAPTR': False, 'NS': True, 'NSEC': False, 'NSEC3': False, 'NSEC3PARAM': False, 'OPENPGPKEY': False, 'PTR': True, 'RP': False, 'RRSIG': False, 'SOA': False, 'SPF': False, 'SSHFP': False, 'SRV': False, 'TKEY': False, 'TSIG': False, 'TLSA': False, 'SMIMEA': False, 'TXT': True, 'URI': False},
} }
def __init__(self, id=None, name=None, value=None): def __init__(self, id=None, name=None, value=None):
@ -1905,6 +1908,17 @@ class Setting(db.Model):
else: else:
logging.error('Unknown setting queried: {0}'.format(setting)) logging.error('Unknown setting queried: {0}'.format(setting))
def get_records_allow_to_edit(self):
return list(set(self.get_forward_records_allow_to_edit() + self.get_reverse_records_allow_to_edit()))
def get_forward_records_allow_to_edit(self):
records = literal_eval(self.get('forward_records_allow_edit'))
return [r for r in records if records[r]]
def get_reverse_records_allow_to_edit(self):
records = literal_eval(self.get('reverse_records_allow_edit'))
return [r for r in records if records[r]]
def get_view(self, view): def get_view(self, view):
r = {} r = {}
settings = Setting.query.filter(Setting.view == view).all() settings = Setting.query.filter(Setting.view == view).all()

View File

@ -26,7 +26,6 @@
<!-- /.box-header --> <!-- /.box-header -->
<!-- form start --> <!-- form start -->
<form role="form" method="post" data-toggle="validator"> <form role="form" method="post" data-toggle="validator">
<input type="hidden" name="create" value="{{ create }}">
<div class="box-body"> <div class="box-body">
{% if not SETTING.get('pdns_api_url') or not SETTING.get('pdns_api_key') or not SETTING.get('pdns_version') %} {% if not SETTING.get('pdns_api_url') or not SETTING.get('pdns_api_key') or not SETTING.get('pdns_version') %}
<div class="alert alert-danger alert-dismissible"> <div class="alert alert-danger alert-dismissible">

View File

@ -0,0 +1,78 @@
{% extends "base.html" %}
{% set active_page = "admin_settings" %}
{% block title %}
<title>DNS Records Settings - {{ SITE_NAME }}</title>
{% endblock %} {% block dashboard_stat %}
<!-- Content Header (Page header) -->
<section class="content-header">
<h1>
Settings <small>PowerDNS-Admin settings</small>
</h1>
<ol class="breadcrumb">
<li><a href="{{ url_for('dashboard') }}"><i class="fa fa-dashboard"></i> Home</a></li>
<li><a href="#">Setting</a></li>
<li class="active">Records</li>
</ol>
</section>
{% endblock %}
{% block content %}
<section class="content">
<div class="row">
<div class="col-md-5">
<div class="box box-primary">
<div class="box-header with-border">
<h3 class="box-title">DNS record Settings</h3>
</div>
<!-- /.box-header -->
<!-- form start -->
<form role="form" method="post">
<input type="hidden" name="create" value="{{ create }}">
<div class="box-body">
<table class="table table-bordered">
<tr>
<th style="width: 10px">#</th>
<th style="width: 40px">Record</th>
<th>Forward Zone</th>
<th>Reverse Zone</th>
</tr>
{% for record in f_records %}
<tr>
<td>{{ loop.index }}</td>
<td>{{ record }}</td>
<td>
<input type="checkbox" id="fr_{{ record|lower }}" name="fr_{{ record|lower }}" class="checkbox" {% if f_records[record] %}checked{% endif %}>
</td>
<td>
<input type="checkbox" id="rr_{{ record|lower }}" name="rr_{{ record|lower }}" class="checkbox" {% if r_records[record] %}checked{% endif %}>
</td>
</tr>
{% endfor %}
</table>
</div>
<div class="box-footer">
<button type="submit" class="btn btn-flat btn-primary">Update</button>
</div>
</form>
</div>
</div>
<div class="col-md-7">
<div class="box box-primary">
<div class="box-header with-border">
<h3 class="box-title">Help</h3>
</div>
<div class="box-body">
<p>TBD</p>
</div>
</div>
</div>
</div>
</section>
{% endblock %}
{% block extrascripts %}
<script>
$('.checkbox').iCheck({
checkboxClass : 'icheckbox_square-blue',
increaseArea : '20%'
})
</script>
{% endblock %}

View File

@ -137,6 +137,7 @@
</a> </a>
<ul class="treeview-menu" {% if active_page == 'admin_settings' %}style="display: block;"{% endif %}> <ul class="treeview-menu" {% if active_page == 'admin_settings' %}style="display: block;"{% endif %}>
<li><a href="{{ url_for('admin_setting_basic') }}"><i class="fa fa-circle-o"></i></i> Basic</a></li> <li><a href="{{ url_for('admin_setting_basic') }}"><i class="fa fa-circle-o"></i></i> Basic</a></li>
<li><a href="{{ url_for('admin_setting_records') }}"><i class="fa fa-circle-o"></i> Records</a></li>
<li><a href="{{ url_for('admin_setting_pdns') }}"><i class="fa fa-circle-o"></i> PDNS</a></li> <li><a href="{{ url_for('admin_setting_pdns') }}"><i class="fa fa-circle-o"></i> PDNS</a></li>
<li><a href="{{ url_for('admin_setting_authentication') }}"><i class="fa fa-circle-o"></i> Authentication</a></li> <li><a href="{{ url_for('admin_setting_authentication') }}"><i class="fa fa-circle-o"></i> Authentication</a></li>
</ul> </ul>

View File

@ -8,6 +8,7 @@ from distutils.util import strtobool
from distutils.version import StrictVersion from distutils.version import StrictVersion
from functools import wraps from functools import wraps
from io import BytesIO from io import BytesIO
from ast import literal_eval
import jinja2 import jinja2
import qrcode as qrc import qrcode as qrc
@ -564,29 +565,31 @@ def domain(domain_name):
return redirect(url_for('error', code=500)) return redirect(url_for('error', code=500))
quick_edit = Setting().get('allow_quick_edit') quick_edit = Setting().get('allow_quick_edit')
records_allow_to_edit = Setting().get_records_allow_to_edit()
forward_records_allow_to_edit = Setting().get_forward_records_allow_to_edit()
reverse_records_allow_to_edit = Setting().get_reverse_records_allow_to_edit()
records = [] records = []
#TODO: This should be done in the "model" instead of "view"
if StrictVersion(Setting().get('pdns_version')) >= StrictVersion('4.0.0'): if StrictVersion(Setting().get('pdns_version')) >= StrictVersion('4.0.0'):
for jr in jrecords: for jr in jrecords:
if jr['type'] in app.config['RECORDS_ALLOW_EDIT']: if jr['type'] in Setting().get_records_allow_to_edit():
for subrecord in jr['records']: for subrecord in jr['records']:
record = Record(name=jr['name'], type=jr['type'], status='Disabled' if subrecord['disabled'] else 'Active', ttl=jr['ttl'], data=subrecord['content']) record = Record(name=jr['name'], type=jr['type'], status='Disabled' if subrecord['disabled'] else 'Active', ttl=jr['ttl'], data=subrecord['content'])
records.append(record) records.append(record)
if not re.search('ip6\.arpa|in-addr\.arpa$', domain_name): if not re.search('ip6\.arpa|in-addr\.arpa$', domain_name):
editable_records = app.config['FORWARD_RECORDS_ALLOW_EDIT'] editable_records = forward_records_allow_to_edit
else: else:
editable_records = app.config['REVERSE_RECORDS_ALLOW_EDIT'] editable_records = reverse_records_allow_to_edit
return render_template('domain.html', domain=domain, records=records, editable_records=editable_records, quick_edit=quick_edit) return render_template('domain.html', domain=domain, records=records, editable_records=editable_records, quick_edit=quick_edit)
else: else:
for jr in jrecords: for jr in jrecords:
if jr['type'] in app.config['RECORDS_ALLOW_EDIT']: if jr['type'] in Setting().get_records_allow_to_edit():
record = Record(name=jr['name'], type=jr['type'], status='Disabled' if jr['disabled'] else 'Active', ttl=jr['ttl'], data=jr['content']) record = Record(name=jr['name'], type=jr['type'], status='Disabled' if jr['disabled'] else 'Active', ttl=jr['ttl'], data=jr['content'])
records.append(record) records.append(record)
if not re.search('ip6\.arpa|in-addr\.arpa$', domain_name): if not re.search('ip6\.arpa|in-addr\.arpa$', domain_name):
editable_records = app.config['FORWARD_RECORDS_ALLOW_EDIT'] editable_records = forward_records_allow_to_edit
else: else:
editable_records = app.config['REVERSE_RECORDS_ALLOW_EDIT'] editable_records = reverse_records_allow_to_edit
return render_template('domain.html', domain=domain, records=records, editable_records=editable_records, quick_edit=quick_edit) return render_template('domain.html', domain=domain, records=records, editable_records=editable_records, quick_edit=quick_edit)
@ -980,14 +983,14 @@ def create_template_from_zone():
if StrictVersion(Setting().get('pdns_version')) >= StrictVersion('4.0.0'): if StrictVersion(Setting().get('pdns_version')) >= StrictVersion('4.0.0'):
for jr in jrecords: for jr in jrecords:
if jr['type'] in app.config['RECORDS_ALLOW_EDIT']: if jr['type'] in Setting().get_records_allow_to_edit():
name = '@' if jr['name'] == domain_name else re.sub('\.{}$'.format(domain_name), '', jr['name']) name = '@' if jr['name'] == domain_name else re.sub('\.{}$'.format(domain_name), '', jr['name'])
for subrecord in jr['records']: for subrecord in jr['records']:
record = DomainTemplateRecord(name=name, type=jr['type'], status=True if subrecord['disabled'] else False, ttl=jr['ttl'], data=subrecord['content']) record = DomainTemplateRecord(name=name, type=jr['type'], status=True if subrecord['disabled'] else False, ttl=jr['ttl'], data=subrecord['content'])
records.append(record) records.append(record)
else: else:
for jr in jrecords: for jr in jrecords:
if jr['type'] in app.config['RECORDS_ALLOW_EDIT']: if jr['type'] in Setting().get_records_allow_to_edit():
name = '@' if jr['name'] == domain_name else re.sub('\.{}$'.format(domain_name), '', jr['name']) name = '@' if jr['name'] == domain_name else re.sub('\.{}$'.format(domain_name), '', jr['name'])
record = DomainTemplateRecord(name=name, type=jr['type'], status=True if jr['disabled'] else False, ttl=jr['ttl'], data=jr['content']) record = DomainTemplateRecord(name=name, type=jr['type'], status=True if jr['disabled'] else False, ttl=jr['ttl'], data=jr['content'])
records.append(record) records.append(record)
@ -1013,14 +1016,15 @@ def create_template_from_zone():
def edit_template(template): def edit_template(template):
try: try:
t = DomainTemplate.query.filter(DomainTemplate.name == template).first() t = DomainTemplate.query.filter(DomainTemplate.name == template).first()
records_allow_to_edit = Setting().get_records_allow_to_edit()
if t is not None: if t is not None:
records = [] records = []
for jr in t.records: for jr in t.records:
if jr.type in app.config['RECORDS_ALLOW_EDIT']: if jr.type in records_allow_to_edit:
record = DomainTemplateRecord(name=jr.name, type=jr.type, status='Disabled' if jr.status else 'Active', ttl=jr.ttl, data=jr.data) record = DomainTemplateRecord(name=jr.name, type=jr.type, status='Disabled' if jr.status else 'Active', ttl=jr.ttl, data=jr.data)
records.append(record) records.append(record)
return render_template('template_edit.html', template=t.name, records=records, editable_records=app.config['RECORDS_ALLOW_EDIT']) return render_template('template_edit.html', template=t.name, records=records, editable_records=records_allow_to_edit)
except: except:
logging.error(traceback.print_exc()) logging.error(traceback.print_exc())
return redirect(url_for('error', code=500)) return redirect(url_for('error', code=500))
@ -1374,6 +1378,27 @@ def admin_setting_pdns():
return render_template('admin_setting_pdns.html', pdns_api_url=pdns_api_url, pdns_api_key=pdns_api_key, pdns_version=pdns_version) return render_template('admin_setting_pdns.html', pdns_api_url=pdns_api_url, pdns_api_key=pdns_api_key, pdns_version=pdns_version)
@app.route('/admin/setting/dns-records', methods=['GET', 'POST'])
@login_required
@admin_role_required
def admin_setting_records():
if request.method == 'GET':
f_records = literal_eval(Setting().get('forward_records_allow_edit'))
r_records = literal_eval(Setting().get('reverse_records_allow_edit'))
return render_template('admin_setting_records.html', f_records=f_records, r_records=r_records)
elif request.method == 'POST':
fr = {}
rr = {}
records = Setting().defaults['forward_records_allow_edit']
for r in records:
fr[r] = True if request.form.get('fr_{0}'.format(r.lower())) else False
rr[r] = True if request.form.get('rr_{0}'.format(r.lower())) else False
Setting().set('forward_records_allow_edit', str(fr))
Setting().set('reverse_records_allow_edit', str(rr))
return redirect(url_for('admin_setting_records'))
@app.route('/admin/setting/authentication', methods=['GET', 'POST']) @app.route('/admin/setting/authentication', methods=['GET', 'POST'])
@login_required @login_required
@admin_role_required @admin_role_required

View File

@ -11,29 +11,26 @@ PORT = 9191
TIMEOUT = 10 TIMEOUT = 10
# LOG CONFIG # LOG CONFIG
# - For docker, LOG_FILE=''
LOG_LEVEL = 'DEBUG' LOG_LEVEL = 'DEBUG'
LOG_FILE = 'logfile.log' LOG_FILE = 'logfile.log'
# For Docker, leave empty string
#LOG_FILE = ''
# Upload # UPLOAD DIRECTORY
UPLOAD_DIR = os.path.join(basedir, 'upload') UPLOAD_DIR = os.path.join(basedir, 'upload')
# DATABASE CONFIG # DATABASE CONFIG
#You'll need MySQL-python SQLA_DB_USER = 'pda'
SQLA_DB_USER = 'powerdnsadmin' SQLA_DB_PASSWORD = 'changeme'
SQLA_DB_PASSWORD = 'powerdnsadminpassword' SQLA_DB_HOST = '127.0.0.1'
SQLA_DB_HOST = 'mysqlhostorip' SQLA_DB_NAME = 'pda'
SQLA_DB_NAME = 'powerdnsadmin'
#MySQL
#SQLALCHEMY_DATABASE_URI = 'mysql://'+SQLA_DB_USER+':'\
# +SQLA_DB_PASSWORD+'@'+SQLA_DB_HOST+'/'+SQLA_DB_NAME
#SQLite
SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(basedir, 'pdns.db')
SQLALCHEMY_MIGRATE_REPO = os.path.join(basedir, 'db_repository')
SQLALCHEMY_TRACK_MODIFICATIONS = True SQLALCHEMY_TRACK_MODIFICATIONS = True
# DATBASE - MySQL
SQLALCHEMY_DATABASE_URI = 'mysql://'+SQLA_DB_USER+':'+SQLA_DB_PASSWORD+'@'+SQLA_DB_HOST+'/'+SQLA_DB_NAME
# DATABSE - SQLite
#SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(basedir, 'pdns.db')
# SAML Authnetication # SAML Authnetication
SAML_ENABLED = False SAML_ENABLED = False
SAML_DEBUG = True SAML_DEBUG = True
@ -106,17 +103,3 @@ SAML_LOGOUT = False
#Configure to redirect to a different url then PowerDNS-Admin login after SAML logout #Configure to redirect to a different url then PowerDNS-Admin login after SAML logout
#for example redirect to google.com after successful saml logout #for example redirect to google.com after successful saml logout
#SAML_LOGOUT_URL = 'https://google.com' #SAML_LOGOUT_URL = 'https://google.com'
# RECORDS ALLOWED TO EDIT
RECORDS_ALLOW_EDIT = ['A', 'AAAA', 'CAA', 'CNAME', 'MX', 'PTR', 'SPF', 'SRV', 'TXT', 'LOC', 'NS', 'PTR', 'SOA']
FORWARD_RECORDS_ALLOW_EDIT = ['A', 'AAAA', 'CAA', 'CNAME', 'MX', 'PTR', 'SPF', 'SRV', 'TXT', 'LOC' 'NS']
REVERSE_RECORDS_ALLOW_EDIT = ['SOA', 'TXT', 'LOC', 'NS', 'PTR']
# ALLOW DNSSEC CHANGES FOR ADMINS ONLY
DNSSEC_ADMINS_ONLY = False
# EXPERIMENTAL FEATURES
PRETTY_IPV6_PTR = False
# Domain updates in background, for big installations
BG_DOMAIN_UPDATES = False

View File

@ -0,0 +1,46 @@
"""Change setting.value data type
Revision ID: 1274ed462010
Revises: 59729e468045
Create Date: 2018-08-21 17:12:30.058782
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '1274ed462010'
down_revision = '59729e468045'
branch_labels = None
depends_on = None
def update_data():
setting_table = sa.sql.table('setting',
sa.sql.column('id', sa.Integer),
sa.sql.column('name', sa.String),
sa.sql.column('value', sa.String),
sa.sql.column('view', sa.String)
)
# add more new settings
op.bulk_insert(setting_table,
[
{'id': 42, 'name': 'forward_records_allow_edit', 'value': "{'A': True, 'AAAA': True, 'AFSDB': False, 'ALIAS': False, 'CAA': True, 'CERT': False, 'CDNSKEY': False, 'CDS': False, 'CNAME': True, 'DNSKEY': False, 'DNAME': False, 'DS': False, 'HINFO': False, 'KEY': False, 'LOC': True, 'MX': True, 'NAPTR': False, 'NS': True, 'NSEC': False, 'NSEC3': False, 'NSEC3PARAM': False, 'OPENPGPKEY': False, 'PTR': True, 'RP': False, 'RRSIG': False, 'SOA': False, 'SPF': True, 'SSHFP': False, 'SRV': True, 'TKEY': False, 'TSIG': False, 'TLSA': False, 'SMIMEA': False, 'TXT': True, 'URI': False}", 'view': 'records'},
{'id': 43, 'name': 'reverse_records_allow_edit', 'value': "{'A': False, 'AAAA': False, 'AFSDB': False, 'ALIAS': False, 'CAA': False, 'CERT': False, 'CDNSKEY': False, 'CDS': False, 'CNAME': False, 'DNSKEY': False, 'DNAME': False, 'DS': False, 'HINFO': False, 'KEY': False, 'LOC': True, 'MX': False, 'NAPTR': False, 'NS': True, 'NSEC': False, 'NSEC3': False, 'NSEC3PARAM': False, 'OPENPGPKEY': False, 'PTR': True, 'RP': False, 'RRSIG': False, 'SOA': False, 'SPF': False, 'SSHFP': False, 'SRV': False, 'TKEY': False, 'TSIG': False, 'TLSA': False, 'SMIMEA': False, 'TXT': True, 'URI': False}", 'view': 'records'},
]
)
def upgrade():
# change column data type
op.alter_column('setting', 'value', existing_type=sa.String(256), type_=sa.Text())
# update data for new schema
update_data()
def downgrade():
# delete added records in previous version
op.execute("DELETE FROM setting WHERE id > 41")
# change column data type
op.alter_column('setting', 'value', existing_type=sa.Text(), type_=sa.String(256))