Merge pull request #339 from ngoduykhanh/app_config_adjustment

Reading app config from DB and authentication adjustment
This commit is contained in:
Khanh Ngo 2018-08-25 14:57:27 +07:00 committed by GitHub
commit 601859c952
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
34 changed files with 1643 additions and 712 deletions

View file

@ -2,11 +2,23 @@ language: python
python: python:
- "3.5.2" - "3.5.2"
before_install: before_install:
- 'travis_retry sudo apt-get update' - sudo apt-key adv --fetch-keys http://dl.yarnpkg.com/debian/pubkey.gpg
- 'travis_retry sudo apt-get install python3-dev libxml2-dev libxmlsec1-dev' - echo "deb http://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
- travis_retry sudo apt-get update
- travis_retry sudo apt-get install python3-dev libxml2-dev libxmlsec1-dev yarn
- mysql -e 'CREATE DATABASE pda';
- mysql -e "GRANT ALL PRIVILEGES ON pda.* to pda@'%' IDENTIFIED BY 'changeme'";
install: install:
- pip install -r requirements.txt - pip install -r requirements.txt
before_script: before_script:
- mv config_template.py config.py - mv config_template.py config.py
- export FLASK_APP=app/__init__.py
- flask db upgrade
- yarn install --pure-lockfile
- flask assets build
script: script:
- sh run_travis.sh - sh run_travis.sh
cache:
yarn: true
services:
- mysql

View file

@ -3,7 +3,8 @@ from flask import Flask, request, session, redirect, url_for
from flask_login import LoginManager from flask_login import LoginManager
from flask_sqlalchemy import SQLAlchemy as SA from flask_sqlalchemy import SQLAlchemy as SA
from flask_migrate import Migrate from flask_migrate import Migrate
from flask_oauthlib.client import OAuth
from sqlalchemy.exc import OperationalError
# subclass SQLAlchemy to enable pool_pre_ping # subclass SQLAlchemy to enable pool_pre_ping
class SQLAlchemy(SA): class SQLAlchemy(SA):
@ -26,89 +27,14 @@ logging = logger('powerdns-admin', app.config['LOG_LEVEL'], app.config['LOG_FILE
login_manager = LoginManager() login_manager = LoginManager()
login_manager.init_app(app) login_manager.init_app(app)
db = SQLAlchemy(app) db = SQLAlchemy(app) # database
migrate = Migrate(app, db) # used for flask-migrate migrate = Migrate(app, db) # flask-migrate
oauth = OAuth(app) # oauth
def enable_github_oauth(GITHUB_ENABLE):
if not GITHUB_ENABLE:
return None, None
from flask_oauthlib.client import OAuth
oauth = OAuth(app)
github = oauth.remote_app(
'github',
consumer_key=app.config['GITHUB_OAUTH_KEY'],
consumer_secret=app.config['GITHUB_OAUTH_SECRET'],
request_token_params={'scope': app.config['GITHUB_OAUTH_SCOPE']},
base_url=app.config['GITHUB_OAUTH_URL'],
request_token_url=None,
access_token_method='POST',
access_token_url=app.config['GITHUB_OAUTH_TOKEN'],
authorize_url=app.config['GITHUB_OAUTH_AUTHORIZE']
)
@app.route('/user/authorized')
def authorized():
session['github_oauthredir'] = url_for('.authorized', _external=True)
resp = github.authorized_response()
if resp is None:
return 'Access denied: reason=%s error=%s' % (
request.args['error'],
request.args['error_description']
)
session['github_token'] = (resp['access_token'], '')
return redirect(url_for('.login'))
@github.tokengetter
def get_github_oauth_token():
return session.get('github_token')
return oauth, github
oauth, github = enable_github_oauth(app.config.get('GITHUB_OAUTH_ENABLE'))
def enable_google_oauth(GOOGLE_ENABLE):
if not GOOGLE_ENABLE:
return None
from flask_oauthlib.client import OAuth
oauth = OAuth(app)
google = oauth.remote_app(
'google',
consumer_key=app.config['GOOGLE_OAUTH_CLIENT_ID'],
consumer_secret=app.config['GOOGLE_OAUTH_CLIENT_SECRET'],
request_token_params=app.config['GOOGLE_TOKEN_PARAMS'],
base_url=app.config['GOOGLE_BASE_URL'],
request_token_url=None,
access_token_method='POST',
access_token_url=app.config['GOOGLE_TOKEN_URL'],
authorize_url=app.config['GOOGLE_AUTHORIZE_URL'],
)
@app.route('/user/authorized')
def authorized():
resp = google.authorized_response()
if resp is None:
return 'Access denied: reason=%s error=%s' % (
request.args['error_reason'],
request.args['error_description']
)
session['google_token'] = (resp['access_token'], '')
return redirect(url_for('.login'))
@google.tokengetter
def get_google_oauth_token():
return session.get('google_token')
return google
google = enable_google_oauth(app.config.get('GOOGLE_OAUTH_ENABLE'))
from app import views, models
if app.config.get('SAML_ENABLED') and app.config.get('SAML_ENCRYPT'): if app.config.get('SAML_ENABLED') and app.config.get('SAML_ENCRYPT'):
from app.lib import certutil from app.lib import certutil
if not certutil.check_certificate(): if not certutil.check_certificate():
certutil.create_self_signed_cert() certutil.create_self_signed_cert()
from app import models
from app import views

View file

@ -28,6 +28,11 @@ js_login = Bundle(
output='generated/login.js' output='generated/login.js'
) )
js_validation = Bundle(
'node_modules/bootstrap-validator/dist/validator.js',
output='generated/validation.js'
)
css_main = Bundle( css_main = Bundle(
'node_modules/bootstrap/dist/css/bootstrap.css', 'node_modules/bootstrap/dist/css/bootstrap.css',
'node_modules/font-awesome/css/font-awesome.css', 'node_modules/font-awesome/css/font-awesome.css',
@ -62,6 +67,7 @@ js_main = Bundle(
assets = Environment() assets = Environment()
assets.register('js_login', js_login) assets.register('js_login', js_login)
assets.register('js_validation', js_validation)
assets.register('css_login', css_login) assets.register('css_login', css_login)
assets.register('js_main', js_main) assets.register('js_main', js_main)
assets.register('css_main', css_main) assets.register('css_main', css_main)

View file

@ -2,7 +2,7 @@ from functools import wraps
from flask import g, request, redirect, url_for from flask import g, request, redirect, url_for
from app import app from app import app
from app.models import Role from app.models import Role, Setting
def admin_role_required(f): def admin_role_required(f):
@ -31,7 +31,7 @@ def can_access_domain(f):
def can_configure_dnssec(f): def can_configure_dnssec(f):
@wraps(f) @wraps(f)
def decorated_function(*args, **kwargs): def decorated_function(*args, **kwargs):
if g.user.role.name != 'Administrator' and app.config['DNSSEC_ADMINS_ONLY']: if g.user.role.name != 'Administrator' and Setting().get('dnssec_admins_only'):
return redirect(url_for('error', code=401)) return redirect(url_for('error', code=401))
return f(*args, **kwargs) return f(*args, **kwargs)

View file

@ -9,9 +9,12 @@ import traceback
import pyotp import pyotp
import re import re
import dns.reversename import dns.reversename
import dns.inet
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
@ -23,38 +26,6 @@ from app.lib import utils
logging = logger.getLogger(__name__) logging = logger.getLogger(__name__)
if 'LDAP_TYPE' in app.config.keys():
LDAP_URI = app.config['LDAP_URI']
LDAP_SEARCH_BASE = app.config['LDAP_SEARCH_BASE']
LDAP_TYPE = app.config['LDAP_TYPE']
LDAP_FILTER = app.config['LDAP_FILTER']
LDAP_USERNAMEFIELD = app.config['LDAP_USERNAMEFIELD']
LDAP_GROUP_SECURITY = app.config.get('LDAP_GROUP_SECURITY')
if LDAP_GROUP_SECURITY == True:
LDAP_ADMIN_GROUP = app.config['LDAP_ADMIN_GROUP']
LDAP_USER_GROUP = app.config['LDAP_USER_GROUP']
else:
LDAP_TYPE = False
if 'PRETTY_IPV6_PTR' in app.config.keys():
import dns.inet
import dns.name
PRETTY_IPV6_PTR = app.config['PRETTY_IPV6_PTR']
else:
PRETTY_IPV6_PTR = False
PDNS_STATS_URL = app.config['PDNS_STATS_URL']
PDNS_API_KEY = app.config['PDNS_API_KEY']
PDNS_VERSION = app.config['PDNS_VERSION']
API_EXTENDED_URL = utils.pdns_api_extended_uri(PDNS_VERSION)
# Flag for pdns v4.x.x
# TODO: Find another way to do this
if StrictVersion(PDNS_VERSION) >= StrictVersion('4.0.0'):
NEW_SCHEMA = True
else:
NEW_SCHEMA = False
class Anonymous(AnonymousUserMixin): class Anonymous(AnonymousUserMixin):
def __init__(self): def __init__(self):
@ -147,7 +118,7 @@ class User(db.Model):
def ldap_init_conn(self): def ldap_init_conn(self):
ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_NEVER) ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_NEVER)
conn = ldap.initialize(LDAP_URI) conn = ldap.initialize(Setting().get('ldap_uri'))
conn.set_option(ldap.OPT_REFERRALS, ldap.OPT_OFF) conn.set_option(ldap.OPT_REFERRALS, ldap.OPT_OFF)
conn.set_option(ldap.OPT_PROTOCOL_VERSION, 3) conn.set_option(ldap.OPT_PROTOCOL_VERSION, 3)
conn.set_option(ldap.OPT_X_TLS,ldap.OPT_X_TLS_DEMAND) conn.set_option(ldap.OPT_X_TLS,ldap.OPT_X_TLS_DEMAND)
@ -162,7 +133,7 @@ class User(db.Model):
try: try:
conn = self.ldap_init_conn() conn = self.ldap_init_conn()
conn.simple_bind_s(app.config['LDAP_ADMIN_USERNAME'], app.config['LDAP_ADMIN_PASSWORD']) conn.simple_bind_s(Setting().get('ldap_admin_username'), Setting().get('ldap_admin_password'))
ldap_result_id = conn.search(baseDN, searchScope, searchFilter, retrieveAttributes) ldap_result_id = conn.search(baseDN, searchScope, searchFilter, retrieveAttributes)
result_set = [] result_set = []
@ -177,6 +148,8 @@ class User(db.Model):
except ldap.LDAPError as e: except ldap.LDAPError as e:
logging.error(e) logging.error(e)
logging.debug('baseDN: {0}'.format(baseDN))
logging.debug(traceback.format_exc())
raise raise
def ldap_auth(self, ldap_username, password): def ldap_auth(self, ldap_username, password):
@ -207,34 +180,53 @@ class User(db.Model):
if method == 'LDAP': if method == 'LDAP':
isadmin = False isadmin = False
if not LDAP_TYPE: LDAP_TYPE = Setting().get('ldap_type')
logging.error('LDAP authentication is disabled') LDAP_BASE_DN = Setting().get('ldap_base_dn')
return False LDAP_FILTER_BASIC = Setting().get('ldap_filter_basic')
LDAP_FILTER_USERNAME = Setting().get('ldap_filter_username')
LDAP_ADMIN_GROUP = Setting().get('ldap_admin_group')
LDAP_USER_GROUP = Setting().get('ldap_user_group')
LDAP_GROUP_SECURITY_ENABLED = Setting().get('ldap_sg_enabled')
if LDAP_TYPE == 'ldap': searchFilter = "(&({0}={1}){2})".format(LDAP_FILTER_USERNAME, self.username, LDAP_FILTER_BASIC)
searchFilter = "(&({0}={1}){2})".format(LDAP_USERNAMEFIELD, self.username, LDAP_FILTER) logging.debug('Ldap searchFilter {0}'.format(searchFilter))
logging.debug('Ldap searchFilter "{0}"'.format(searchFilter))
elif LDAP_TYPE == 'ad': ldap_result = self.ldap_search(searchFilter, LDAP_BASE_DN)
searchFilter = "(&(objectcategory=person)({0}={1}){2})".format(LDAP_USERNAMEFIELD, self.username, LDAP_FILTER) logging.debug('Ldap search result: {0}'.format(ldap_result))
ldap_result = self.ldap_search(searchFilter, LDAP_SEARCH_BASE)
if not ldap_result: if not ldap_result:
logging.warning('LDAP User "{0}" does not exist. Authentication request from {1}'.format(self.username, src_ip)) logging.warning('LDAP User "{0}" does not exist. Authentication request from {1}'.format(self.username, src_ip))
return False return False
else: else:
try: try:
ldap_username = ldap.filter.escape_filter_chars(ldap_result[0][0][0]) ldap_username = ldap.filter.escape_filter_chars(ldap_result[0][0][0])
# check if LDAP_SECURITY_GROUP is enabled # check if LDAP_GROUP_SECURITY_ENABLED is True
# user can be assigned to ADMIN or USER role. # user can be assigned to ADMIN or USER role.
if LDAP_GROUP_SECURITY: if LDAP_GROUP_SECURITY_ENABLED:
try: try:
if (self.ldap_search(searchFilter, LDAP_ADMIN_GROUP)): if LDAP_TYPE == 'ldap':
isadmin = True if (self.ldap_search(searchFilter, LDAP_ADMIN_GROUP)):
logging.info('User {0} is part of the "{1}" group that allows admin access to PowerDNS-Admin'.format(self.username,LDAP_ADMIN_GROUP)) isadmin = True
elif (self.ldap_search(searchFilter, LDAP_USER_GROUP)): logging.info('User {0} is part of the "{1}" group that allows admin access to PowerDNS-Admin'.format(self.username, LDAP_ADMIN_GROUP))
logging.info('User {0} is part of the "{1}" group that allows user access to PowerDNS-Admin'.format(self.username,LDAP_USER_GROUP)) elif (self.ldap_search(searchFilter, LDAP_USER_GROUP)):
logging.info('User {0} is part of the "{1}" group that allows user access to PowerDNS-Admin'.format(self.username, LDAP_USER_GROUP))
else:
logging.error('User {0} is not part of the "{1}" or "{2}" groups that allow access to PowerDNS-Admin'.format(self.username, LDAP_ADMIN_GROUP, LDAP_USER_GROUP))
return False
elif LDAP_TYPE == 'ad':
user_ldap_groups = [g.decode("utf-8") for g in ldap_result[0][0][1]['memberOf']]
logging.debug('user_ldap_groups: {0}'.format(user_ldap_groups))
if (LDAP_ADMIN_GROUP in user_ldap_groups):
isadmin = True
logging.info('User {0} is part of the "{1}" group that allows admin access to PowerDNS-Admin'.format(self.username, LDAP_ADMIN_GROUP))
elif (LDAP_USER_GROUP in user_ldap_groups):
logging.info('User {0} is part of the "{1}" group that allows user access to PowerDNS-Admin'.format(self.username, LDAP_USER_GROUP))
else:
logging.error('User {0} is not part of the "{1}" or "{2}" groups that allow access to PowerDNS-Admin'.format(self.username, LDAP_ADMIN_GROUP, LDAP_USER_GROUP))
return False
else: else:
logging.error('User {0} is not part of the "{1}" or "{2}" groups that allow access to PowerDNS-Admin'.format(self.username,LDAP_ADMIN_GROUP,LDAP_USER_GROUP)) logging.error('Invalid LDAP type')
return False return False
except Exception as e: except Exception as e:
logging.error('LDAP group lookup for user "{0}" has failed. Authentication request from {1}'.format(self.username, src_ip)) logging.error('LDAP group lookup for user "{0}" has failed. Authentication request from {1}'.format(self.username, src_ip))
@ -256,13 +248,16 @@ class User(db.Model):
self.firstname = self.username self.firstname = self.username
self.lastname = '' self.lastname = ''
try: try:
# try to get user's firstname & lastname from LDAP # try to get user's firstname, lastname and email address from LDAP attributes
# this might be changed in the future if LDAP_TYPE == 'ldap':
self.firstname = ldap_result[0][0][1]['givenName'][0].decode("utf-8") self.firstname = ldap_result[0][0][1]['givenName'][0].decode("utf-8")
self.lastname = ldap_result[0][0][1]['sn'][0].decode("utf-8") self.lastname = ldap_result[0][0][1]['sn'][0].decode("utf-8")
self.email = ldap_result[0][0][1]['mail'][0].decode("utf-8") self.email = ldap_result[0][0][1]['mail'][0].decode("utf-8")
elif LDAP_TYPE == 'ad':
self.firstname = ldap_result[0][0][1]['name'][0].decode("utf-8")
self.email = ldap_result[0][0][1]['userPrincipalName'][0].decode("utf-8")
except Exception as e: except Exception as e:
logging.info("Reading ldap data threw an exception {0}".format(e)) logging.warning("Reading ldap data threw an exception {0}".format(e))
logging.debug(traceback.format_exc()) logging.debug(traceback.format_exc())
# first register user will be in Administrator role # first register user will be in Administrator role
@ -271,7 +266,7 @@ class User(db.Model):
self.role_id = Role.query.filter_by(name='Administrator').first().id self.role_id = Role.query.filter_by(name='Administrator').first().id
# user will be in Administrator role if part of LDAP Admin group # user will be in Administrator role if part of LDAP Admin group
if LDAP_GROUP_SECURITY: if LDAP_GROUP_SECURITY_ENABLED:
if isadmin == True: if isadmin == True:
self.role_id = Role.query.filter_by(name='Administrator').first().id self.role_id = Role.query.filter_by(name='Administrator').first().id
@ -279,9 +274,9 @@ class User(db.Model):
logging.info('Created user "{0}" in the DB'.format(self.username)) logging.info('Created user "{0}" in the DB'.format(self.username))
# user already exists in database, set their admin status based on group membership (if enabled) # user already exists in database, set their admin status based on group membership (if enabled)
if LDAP_GROUP_SECURITY: if LDAP_GROUP_SECURITY_ENABLED:
self.set_admin(isadmin) self.set_admin(isadmin)
self.update_profile()
return True return True
else: else:
logging.error('Unsupported authentication method') logging.error('Unsupported authentication method')
@ -319,9 +314,9 @@ class User(db.Model):
if User.query.count() == 0: if User.query.count() == 0:
self.role_id = Role.query.filter_by(name='Administrator').first().id self.role_id = Role.query.filter_by(name='Administrator').first().id
self.password = self.get_hashed_password(self.plain_text_password) self.password = self.get_hashed_password(self.plain_text_password) if self.plain_text_password else '*'
if self.password: if self.password and self.password != '*':
self.password = self.password.decode("utf-8") self.password = self.password.decode("utf-8")
db.session.add(self) db.session.add(self)
@ -741,6 +736,16 @@ class Domain(db.Model):
self.last_check = last_check self.last_check = last_check
self.dnssec = dnssec self.dnssec = dnssec
self.account_id = account_id self.account_id = account_id
# PDNS configs
self.PDNS_STATS_URL = Setting().get('pdns_api_url')
self.PDNS_API_KEY = Setting().get('pdns_api_key')
self.PDNS_VERSION = Setting().get('pdns_version')
self.API_EXTENDED_URL = utils.pdns_api_extended_uri(self.PDNS_VERSION)
if StrictVersion(self.PDNS_VERSION) >= StrictVersion('4.0.0'):
self.NEW_SCHEMA = True
else:
self.NEW_SCHEMA = False
def __repr__(self): def __repr__(self):
return '<Domain {0}>'.format(self.name) return '<Domain {0}>'.format(self.name)
@ -759,8 +764,8 @@ class Domain(db.Model):
Get all domains which has in PowerDNS Get all domains which has in PowerDNS
""" """
headers = {} headers = {}
headers['X-API-Key'] = PDNS_API_KEY headers['X-API-Key'] = self.PDNS_API_KEY
jdata = utils.fetch_json(urljoin(PDNS_STATS_URL, API_EXTENDED_URL + '/servers/localhost/zones/{0}'.format(domain_name)), headers=headers) jdata = utils.fetch_json(urljoin(self.PDNS_STATS_URL, self.API_EXTENDED_URL + '/servers/localhost/zones/{0}'.format(domain_name)), headers=headers)
return jdata return jdata
def get_domains(self): def get_domains(self):
@ -768,8 +773,8 @@ class Domain(db.Model):
Get all domains which has in PowerDNS Get all domains which has in PowerDNS
""" """
headers = {} headers = {}
headers['X-API-Key'] = PDNS_API_KEY headers['X-API-Key'] = self.PDNS_API_KEY
jdata = utils.fetch_json(urljoin(PDNS_STATS_URL, API_EXTENDED_URL + '/servers/localhost/zones'), headers=headers) jdata = utils.fetch_json(urljoin(self.PDNS_STATS_URL, self.API_EXTENDED_URL + '/servers/localhost/zones'), headers=headers)
return jdata return jdata
def get_id_by_name(self, name): def get_id_by_name(self, name):
@ -791,9 +796,9 @@ class Domain(db.Model):
dict_db_domain = dict((x.name,x) for x in db_domain) dict_db_domain = dict((x.name,x) for x in db_domain)
headers = {} headers = {}
headers['X-API-Key'] = PDNS_API_KEY headers['X-API-Key'] = self.PDNS_API_KEY
try: try:
jdata = utils.fetch_json(urljoin(PDNS_STATS_URL, API_EXTENDED_URL + '/servers/localhost/zones'), headers=headers) jdata = utils.fetch_json(urljoin(self.PDNS_STATS_URL, self.API_EXTENDED_URL + '/servers/localhost/zones'), headers=headers)
list_jdomain = [d['name'].rstrip('.') for d in jdata] list_jdomain = [d['name'].rstrip('.') for d in jdata]
try: try:
# domains should remove from db since it doesn't exist in powerdns anymore # domains should remove from db since it doesn't exist in powerdns anymore
@ -874,9 +879,9 @@ class Domain(db.Model):
Add a domain to power dns Add a domain to power dns
""" """
headers = {} headers = {}
headers['X-API-Key'] = PDNS_API_KEY headers['X-API-Key'] = self.PDNS_API_KEY
if NEW_SCHEMA: if self.NEW_SCHEMA:
domain_name = domain_name + '.' domain_name = domain_name + '.'
domain_ns = [ns + '.' for ns in domain_ns] domain_ns = [ns + '.' for ns in domain_ns]
@ -896,7 +901,7 @@ class Domain(db.Model):
} }
try: try:
jdata = utils.fetch_json(urljoin(PDNS_STATS_URL, API_EXTENDED_URL + '/servers/localhost/zones'), headers=headers, method='POST', data=post_data) jdata = utils.fetch_json(urljoin(self.PDNS_STATS_URL, self.API_EXTENDED_URL + '/servers/localhost/zones'), headers=headers, method='POST', data=post_data)
if 'error' in jdata.keys(): if 'error' in jdata.keys():
logging.error(jdata['error']) logging.error(jdata['error'])
return {'status': 'error', 'msg': jdata['error']} return {'status': 'error', 'msg': jdata['error']}
@ -914,7 +919,7 @@ class Domain(db.Model):
if not domain: if not domain:
return {'status': 'error', 'msg': 'Domain doesnt exist.'} return {'status': 'error', 'msg': 'Domain doesnt exist.'}
headers = {} headers = {}
headers['X-API-Key'] = PDNS_API_KEY headers['X-API-Key'] = self.PDNS_API_KEY
if soa_edit_api not in ["DEFAULT", "INCREASE", "EPOCH", "OFF"]: if soa_edit_api not in ["DEFAULT", "INCREASE", "EPOCH", "OFF"]:
soa_edit_api = 'DEFAULT' soa_edit_api = 'DEFAULT'
@ -929,7 +934,7 @@ class Domain(db.Model):
try: try:
jdata = utils.fetch_json( jdata = utils.fetch_json(
urljoin(PDNS_STATS_URL, API_EXTENDED_URL + '/servers/localhost/zones/{0}'.format(domain.name)), headers=headers, urljoin(self.PDNS_STATS_URL, self.API_EXTENDED_URL + '/servers/localhost/zones/{0}'.format(domain.name)), headers=headers,
method='PUT', data=post_data) method='PUT', data=post_data)
if 'error' in jdata.keys(): if 'error' in jdata.keys():
logging.error(jdata['error']) logging.error(jdata['error'])
@ -951,7 +956,7 @@ class Domain(db.Model):
domain_obj = Domain.query.filter(Domain.name == domain_name).first() domain_obj = Domain.query.filter(Domain.name == domain_name).first()
domain_auto_ptr = DomainSetting.query.filter(DomainSetting.domain == domain_obj).filter(DomainSetting.setting == 'auto_ptr').first() domain_auto_ptr = DomainSetting.query.filter(DomainSetting.domain == domain_obj).filter(DomainSetting.setting == 'auto_ptr').first()
domain_auto_ptr = strtobool(domain_auto_ptr.value) if domain_auto_ptr else False domain_auto_ptr = strtobool(domain_auto_ptr.value) if domain_auto_ptr else False
system_auto_ptr = strtobool(Setting().get('auto_ptr')) system_auto_ptr = Setting().get('auto_ptr')
self.name = domain_name self.name = domain_name
domain_id = self.get_id_by_name(domain_reverse_name) domain_id = self.get_id_by_name(domain_reverse_name)
if None == domain_id and \ if None == domain_id and \
@ -1002,9 +1007,9 @@ class Domain(db.Model):
Delete a single domain name from powerdns Delete a single domain name from powerdns
""" """
headers = {} headers = {}
headers['X-API-Key'] = PDNS_API_KEY headers['X-API-Key'] = self.PDNS_API_KEY
try: try:
jdata = utils.fetch_json(urljoin(PDNS_STATS_URL, API_EXTENDED_URL + '/servers/localhost/zones/{0}'.format(domain_name)), headers=headers, method='DELETE') jdata = utils.fetch_json(urljoin(self.PDNS_STATS_URL, self.API_EXTENDED_URL + '/servers/localhost/zones/{0}'.format(domain_name)), headers=headers, method='DELETE')
logging.info('Delete domain {0} successfully'.format(domain_name)) logging.info('Delete domain {0} successfully'.format(domain_name))
return {'status': 'ok', 'msg': 'Delete domain successfully'} return {'status': 'ok', 'msg': 'Delete domain successfully'}
except Exception as e: except Exception as e:
@ -1059,9 +1064,9 @@ class Domain(db.Model):
domain = Domain.query.filter(Domain.name == domain_name).first() domain = Domain.query.filter(Domain.name == domain_name).first()
if domain: if domain:
headers = {} headers = {}
headers['X-API-Key'] = PDNS_API_KEY headers['X-API-Key'] = self.PDNS_API_KEY
try: try:
jdata = utils.fetch_json(urljoin(PDNS_STATS_URL, API_EXTENDED_URL + '/servers/localhost/zones/{0}/axfr-retrieve'.format(domain.name)), headers=headers, method='PUT') jdata = utils.fetch_json(urljoin(self.PDNS_STATS_URL, self.API_EXTENDED_URL + '/servers/localhost/zones/{0}/axfr-retrieve'.format(domain.name)), headers=headers, method='PUT')
return {'status': 'ok', 'msg': 'Update from Master successfully'} return {'status': 'ok', 'msg': 'Update from Master successfully'}
except: except:
return {'status': 'error', 'msg': 'There was something wrong, please contact administrator'} return {'status': 'error', 'msg': 'There was something wrong, please contact administrator'}
@ -1075,9 +1080,9 @@ class Domain(db.Model):
domain = Domain.query.filter(Domain.name == domain_name).first() domain = Domain.query.filter(Domain.name == domain_name).first()
if domain: if domain:
headers = {} headers = {}
headers['X-API-Key'] = PDNS_API_KEY headers['X-API-Key'] = self.PDNS_API_KEY
try: try:
jdata = utils.fetch_json(urljoin(PDNS_STATS_URL, API_EXTENDED_URL + '/servers/localhost/zones/{0}/cryptokeys'.format(domain.name)), headers=headers, method='GET') jdata = utils.fetch_json(urljoin(self.PDNS_STATS_URL, self.API_EXTENDED_URL + '/servers/localhost/zones/{0}/cryptokeys'.format(domain.name)), headers=headers, method='GET')
if 'error' in jdata: if 'error' in jdata:
return {'status': 'error', 'msg': 'DNSSEC is not enabled for this domain'} return {'status': 'error', 'msg': 'DNSSEC is not enabled for this domain'}
else: else:
@ -1094,13 +1099,13 @@ class Domain(db.Model):
domain = Domain.query.filter(Domain.name == domain_name).first() domain = Domain.query.filter(Domain.name == domain_name).first()
if domain: if domain:
headers = {} headers = {}
headers['X-API-Key'] = PDNS_API_KEY headers['X-API-Key'] = self.PDNS_API_KEY
try: try:
# Enable API-RECTIFY for domain, BEFORE activating DNSSEC # Enable API-RECTIFY for domain, BEFORE activating DNSSEC
post_data = { post_data = {
"api_rectify": True "api_rectify": True
} }
jdata = utils.fetch_json(urljoin(PDNS_STATS_URL, API_EXTENDED_URL + '/servers/localhost/zones/{0}'.format(domain.name)), headers=headers, method='PUT', data=post_data) jdata = utils.fetch_json(urljoin(self.PDNS_STATS_URL, self.API_EXTENDED_URL + '/servers/localhost/zones/{0}'.format(domain.name)), headers=headers, method='PUT', data=post_data)
if 'error' in jdata: if 'error' in jdata:
return {'status': 'error', 'msg': 'API-RECTIFY could not be enabled for this domain', 'jdata' : jdata} return {'status': 'error', 'msg': 'API-RECTIFY could not be enabled for this domain', 'jdata' : jdata}
@ -1109,7 +1114,7 @@ class Domain(db.Model):
"keytype": "ksk", "keytype": "ksk",
"active": True "active": True
} }
jdata = utils.fetch_json(urljoin(PDNS_STATS_URL, API_EXTENDED_URL + '/servers/localhost/zones/{0}/cryptokeys'.format(domain.name)), headers=headers, method='POST',data=post_data) jdata = utils.fetch_json(urljoin(self.PDNS_STATS_URL, self.API_EXTENDED_URL + '/servers/localhost/zones/{0}/cryptokeys'.format(domain.name)), headers=headers, method='POST',data=post_data)
if 'error' in jdata: if 'error' in jdata:
return {'status': 'error', 'msg': 'Cannot enable DNSSEC for this domain. Error: {0}'.format(jdata['error']), 'jdata' : jdata} return {'status': 'error', 'msg': 'Cannot enable DNSSEC for this domain. Error: {0}'.format(jdata['error']), 'jdata' : jdata}
@ -1129,10 +1134,10 @@ class Domain(db.Model):
domain = Domain.query.filter(Domain.name == domain_name).first() domain = Domain.query.filter(Domain.name == domain_name).first()
if domain: if domain:
headers = {} headers = {}
headers['X-API-Key'] = PDNS_API_KEY headers['X-API-Key'] = self.PDNS_API_KEY
try: try:
# Deactivate DNSSEC # Deactivate DNSSEC
jdata = utils.fetch_json(urljoin(PDNS_STATS_URL, API_EXTENDED_URL + '/servers/localhost/zones/{0}/cryptokeys/{1}'.format(domain.name, key_id)), headers=headers, method='DELETE') jdata = utils.fetch_json(urljoin(self.PDNS_STATS_URL, self.API_EXTENDED_URL + '/servers/localhost/zones/{0}/cryptokeys/{1}'.format(domain.name, key_id)), headers=headers, method='DELETE')
if jdata != True: if jdata != True:
return {'status': 'error', 'msg': 'Cannot disable DNSSEC for this domain. Error: {0}'.format(jdata['error']), 'jdata' : jdata} return {'status': 'error', 'msg': 'Cannot disable DNSSEC for this domain. Error: {0}'.format(jdata['error']), 'jdata' : jdata}
@ -1140,7 +1145,7 @@ class Domain(db.Model):
post_data = { post_data = {
"api_rectify": False "api_rectify": False
} }
jdata = utils.fetch_json(urljoin(PDNS_STATS_URL, API_EXTENDED_URL + '/servers/localhost/zones/{0}'.format(domain.name)), headers=headers, method='PUT', data=post_data) jdata = utils.fetch_json(urljoin(self.PDNS_STATS_URL, self.API_EXTENDED_URL + '/servers/localhost/zones/{0}'.format(domain.name)), headers=headers, method='PUT', data=post_data)
if 'error' in jdata: if 'error' in jdata:
return {'status': 'error', 'msg': 'API-RECTIFY could not be disabled for this domain', 'jdata' : jdata} return {'status': 'error', 'msg': 'API-RECTIFY could not be disabled for this domain', 'jdata' : jdata}
@ -1169,7 +1174,7 @@ class Domain(db.Model):
return {'status': False, 'msg': 'Domain does not exist'} return {'status': False, 'msg': 'Domain does not exist'}
headers = {} headers = {}
headers['X-API-Key'] = PDNS_API_KEY headers['X-API-Key'] = self.PDNS_API_KEY
account_name = Account().get_name_by_id(account_id) account_name = Account().get_name_by_id(account_id)
@ -1179,7 +1184,7 @@ class Domain(db.Model):
try: try:
jdata = utils.fetch_json( jdata = utils.fetch_json(
urljoin(PDNS_STATS_URL, API_EXTENDED_URL + '/servers/localhost/zones/{0}'.format(domain_name)), headers=headers, urljoin(self.PDNS_STATS_URL, self.API_EXTENDED_URL + '/servers/localhost/zones/{0}'.format(domain_name)), headers=headers,
method='PUT', data=post_data) method='PUT', data=post_data)
if 'error' in jdata.keys(): if 'error' in jdata.keys():
@ -1248,24 +1253,35 @@ class Record(object):
self.status = status self.status = status
self.ttl = ttl self.ttl = ttl
self.data = data self.data = data
# PDNS configs
self.PDNS_STATS_URL = Setting().get('pdns_api_url')
self.PDNS_API_KEY = Setting().get('pdns_api_key')
self.PDNS_VERSION = Setting().get('pdns_version')
self.API_EXTENDED_URL = utils.pdns_api_extended_uri(self.PDNS_VERSION)
self.PRETTY_IPV6_PTR = Setting().get('pretty_ipv6_ptr')
if StrictVersion(self.PDNS_VERSION) >= StrictVersion('4.0.0'):
self.NEW_SCHEMA = True
else:
self.NEW_SCHEMA = False
def get_record_data(self, domain): def get_record_data(self, domain):
""" """
Query domain's DNS records via API Query domain's DNS records via API
""" """
headers = {} headers = {}
headers['X-API-Key'] = PDNS_API_KEY headers['X-API-Key'] = self.PDNS_API_KEY
try: try:
jdata = utils.fetch_json(urljoin(PDNS_STATS_URL, API_EXTENDED_URL + '/servers/localhost/zones/{0}'.format(domain)), headers=headers) jdata = utils.fetch_json(urljoin(self.PDNS_STATS_URL, self.API_EXTENDED_URL + '/servers/localhost/zones/{0}'.format(domain)), headers=headers)
except: except:
logging.error("Cannot fetch domain's record data from remote powerdns api") logging.error("Cannot fetch domain's record data from remote powerdns api")
return False return False
if NEW_SCHEMA: if self.NEW_SCHEMA:
rrsets = jdata['rrsets'] rrsets = jdata['rrsets']
for rrset in rrsets: for rrset in rrsets:
r_name = rrset['name'].rstrip('.') r_name = rrset['name'].rstrip('.')
if PRETTY_IPV6_PTR: # only if activated if self.PRETTY_IPV6_PTR: # only if activated
if rrset['type'] == 'PTR': # only ptr if rrset['type'] == 'PTR': # only ptr
if 'ip6.arpa' in r_name: # only if v6-ptr if 'ip6.arpa' in r_name: # only if v6-ptr
r_name = dns.reversename.to_address(dns.name.from_text(r_name)) r_name = dns.reversename.to_address(dns.name.from_text(r_name))
@ -1292,9 +1308,9 @@ class Record(object):
# continue if the record is ready to be added # continue if the record is ready to be added
headers = {} headers = {}
headers['X-API-Key'] = PDNS_API_KEY headers['X-API-Key'] = self.PDNS_API_KEY
if NEW_SCHEMA: if self.NEW_SCHEMA:
data = {"rrsets": [ data = {"rrsets": [
{ {
"name": self.name.rstrip('.') + '.', "name": self.name.rstrip('.') + '.',
@ -1330,7 +1346,7 @@ class Record(object):
} }
try: try:
jdata = utils.fetch_json(urljoin(PDNS_STATS_URL, API_EXTENDED_URL + '/servers/localhost/zones/{0}'.format(domain)), headers=headers, method='PATCH', data=data) jdata = utils.fetch_json(urljoin(self.PDNS_STATS_URL, self.API_EXTENDED_URL + '/servers/localhost/zones/{0}'.format(domain)), headers=headers, method='PATCH', data=data)
logging.debug(jdata) logging.debug(jdata)
return {'status': 'ok', 'msg': 'Record was added successfully'} return {'status': 'ok', 'msg': 'Record was added successfully'}
except Exception as e: except Exception as e:
@ -1356,7 +1372,7 @@ class Record(object):
list_deleted_records = [x for x in list_current_records if x not in list_new_records] list_deleted_records = [x for x in list_current_records if x not in list_new_records]
# convert back to list of hash # convert back to list of hash
deleted_records = [x for x in current_records if [x['name'],x['type']] in list_deleted_records and (x['type'] in app.config['RECORDS_ALLOW_EDIT'] and x['type'] != 'SOA')] deleted_records = [x for x in current_records if [x['name'],x['type']] in list_deleted_records and (x['type'] in Setting().get_records_allow_to_edit() and x['type'] != 'SOA')]
# return a tuple # return a tuple
return deleted_records, new_records return deleted_records, new_records
@ -1370,8 +1386,8 @@ class Record(object):
for r in post_records: for r in post_records:
r_name = domain if r['record_name'] in ['@', ''] else r['record_name'] + '.' + domain r_name = domain if r['record_name'] in ['@', ''] else r['record_name'] + '.' + domain
r_type = r['record_type'] r_type = r['record_type']
if PRETTY_IPV6_PTR: # only if activated if self.PRETTY_IPV6_PTR: # only if activated
if NEW_SCHEMA: # only if new schema if self.NEW_SCHEMA: # only if new schema
if r_type == 'PTR': # only ptr if r_type == 'PTR': # only ptr
if ':' in r['record_name']: # dirty ipv6 check if ':' in r['record_name']: # dirty ipv6 check
r_name = r['record_name'] r_name = r['record_name']
@ -1389,10 +1405,10 @@ class Record(object):
records = [] records = []
for r in deleted_records: for r in deleted_records:
r_name = r['name'].rstrip('.') + '.' if NEW_SCHEMA else r['name'] r_name = r['name'].rstrip('.') + '.' if self.NEW_SCHEMA else r['name']
r_type = r['type'] r_type = r['type']
if PRETTY_IPV6_PTR: # only if activated if self.PRETTY_IPV6_PTR: # only if activated
if NEW_SCHEMA: # only if new schema if self.NEW_SCHEMA: # only if new schema
if r_type == 'PTR': # only ptr if r_type == 'PTR': # only ptr
if ':' in r['name']: # dirty ipv6 check if ':' in r['name']: # dirty ipv6 check
r_name = dns.reversename.from_address(r['name']).to_text() r_name = dns.reversename.from_address(r['name']).to_text()
@ -1410,10 +1426,10 @@ class Record(object):
records = [] records = []
for r in new_records: for r in new_records:
if NEW_SCHEMA: if self.NEW_SCHEMA:
r_name = r['name'].rstrip('.') + '.' r_name = r['name'].rstrip('.') + '.'
r_type = r['type'] r_type = r['type']
if PRETTY_IPV6_PTR: # only if activated if self.PRETTY_IPV6_PTR: # only if activated
if r_type == 'PTR': # only ptr if r_type == 'PTR': # only ptr
if ':' in r['name']: # dirty ipv6 check if ':' in r['name']: # dirty ipv6 check
r_name = r['name'] r_name = r['name']
@ -1453,12 +1469,12 @@ class Record(object):
final_records = [] final_records = []
records = sorted(records, key = lambda item: (item["name"], item["type"], item["changetype"])) records = sorted(records, key = lambda item: (item["name"], item["type"], item["changetype"]))
for key, group in itertools.groupby(records, lambda item: (item["name"], item["type"], item["changetype"])): for key, group in itertools.groupby(records, lambda item: (item["name"], item["type"], item["changetype"])):
if NEW_SCHEMA: if self.NEW_SCHEMA:
r_name = key[0] r_name = key[0]
r_type = key[1] r_type = key[1]
r_changetype = key[2] r_changetype = key[2]
if PRETTY_IPV6_PTR: # only if activated if self.PRETTY_IPV6_PTR: # only if activated
if r_type == 'PTR': # only ptr if r_type == 'PTR': # only ptr
if ':' in r_name: # dirty ipv6 check if ':' in r_name: # dirty ipv6 check
r_name = dns.reversename.from_address(r_name).to_text() r_name = dns.reversename.from_address(r_name).to_text()
@ -1504,14 +1520,14 @@ class Record(object):
}) })
postdata_for_new = {"rrsets": final_records} postdata_for_new = {"rrsets": final_records}
logging.info(postdata_for_new) logging.debug(postdata_for_new)
logging.info(postdata_for_delete) logging.debug(postdata_for_delete)
logging.info(urljoin(PDNS_STATS_URL, API_EXTENDED_URL + '/servers/localhost/zones/{0}'.format(domain))) logging.info(urljoin(self.PDNS_STATS_URL, self.API_EXTENDED_URL + '/servers/localhost/zones/{0}'.format(domain)))
try: try:
headers = {} headers = {}
headers['X-API-Key'] = PDNS_API_KEY headers['X-API-Key'] = self.PDNS_API_KEY
jdata1 = utils.fetch_json(urljoin(PDNS_STATS_URL, API_EXTENDED_URL + '/servers/localhost/zones/{0}'.format(domain)), headers=headers, method='PATCH', data=postdata_for_delete) jdata1 = utils.fetch_json(urljoin(self.PDNS_STATS_URL, self.API_EXTENDED_URL + '/servers/localhost/zones/{0}'.format(domain)), headers=headers, method='PATCH', data=postdata_for_delete)
jdata2 = utils.fetch_json(urljoin(PDNS_STATS_URL, API_EXTENDED_URL + '/servers/localhost/zones/{0}'.format(domain)), headers=headers, method='PATCH', data=postdata_for_new) jdata2 = utils.fetch_json(urljoin(self.PDNS_STATS_URL, self.API_EXTENDED_URL + '/servers/localhost/zones/{0}'.format(domain)), headers=headers, method='PATCH', data=postdata_for_new)
if 'error' in jdata2.keys(): if 'error' in jdata2.keys():
logging.error('Cannot apply record changes.') logging.error('Cannot apply record changes.')
@ -1523,7 +1539,8 @@ class Record(object):
logging.info('Record was applied successfully.') logging.info('Record was applied successfully.')
return {'status': 'ok', 'msg': 'Record was applied successfully'} return {'status': 'ok', 'msg': 'Record was applied successfully'}
except Exception as e: except Exception as e:
logging.error("Cannot apply record changes to domain {0}. DETAIL: {1}".format(e, domain)) logging.error("Cannot apply record changes to domain {0}. Error: {1}".format(domain, e))
logging.debug(traceback.format_exc())
return {'status': 'error', 'msg': 'There was something wrong, please contact administrator'} return {'status': 'error', 'msg': 'There was something wrong, please contact administrator'}
def auto_ptr(self, domain, new_records, deleted_records): def auto_ptr(self, domain, new_records, deleted_records):
@ -1534,7 +1551,7 @@ class Record(object):
domain_auto_ptr = DomainSetting.query.filter(DomainSetting.domain == domain_obj).filter(DomainSetting.setting == 'auto_ptr').first() domain_auto_ptr = DomainSetting.query.filter(DomainSetting.domain == domain_obj).filter(DomainSetting.setting == 'auto_ptr').first()
domain_auto_ptr = strtobool(domain_auto_ptr.value) if domain_auto_ptr else False domain_auto_ptr = strtobool(domain_auto_ptr.value) if domain_auto_ptr else False
system_auto_ptr = strtobool(Setting().get('auto_ptr')) system_auto_ptr = Setting().get('auto_ptr')
if system_auto_ptr or domain_auto_ptr: if system_auto_ptr or domain_auto_ptr:
try: try:
@ -1572,7 +1589,7 @@ class Record(object):
Delete a record from domain Delete a record from domain
""" """
headers = {} headers = {}
headers['X-API-Key'] = PDNS_API_KEY headers['X-API-Key'] = self.PDNS_API_KEY
data = {"rrsets": [ data = {"rrsets": [
{ {
"name": self.name.rstrip('.') + '.', "name": self.name.rstrip('.') + '.',
@ -1584,7 +1601,7 @@ class Record(object):
] ]
} }
try: try:
jdata = utils.fetch_json(urljoin(PDNS_STATS_URL, API_EXTENDED_URL + '/servers/localhost/zones/{0}'.format(domain)), headers=headers, method='PATCH', data=data) jdata = utils.fetch_json(urljoin(self.PDNS_STATS_URL, self.API_EXTENDED_URL + '/servers/localhost/zones/{0}'.format(domain)), headers=headers, method='PATCH', data=data)
logging.debug(jdata) logging.debug(jdata)
return {'status': 'ok', 'msg': 'Record was removed successfully'} return {'status': 'ok', 'msg': 'Record was removed successfully'}
except: except:
@ -1595,13 +1612,13 @@ class Record(object):
""" """
Check if record is allowed to edit Check if record is allowed to edit
""" """
return self.type in app.config['RECORDS_ALLOW_EDIT'] return self.type in Setting().get_records_allow_to_edit()
def is_allowed_delete(self): def is_allowed_delete(self):
""" """
Check if record is allowed to removed Check if record is allowed to removed
""" """
return (self.type in app.config['RECORDS_ALLOW_EDIT'] and self.type != 'SOA') return (self.type in Setting().get_records_allow_to_edit() and self.type != 'SOA')
def exists(self, domain): def exists(self, domain):
""" """
@ -1626,9 +1643,9 @@ class Record(object):
Update single record Update single record
""" """
headers = {} headers = {}
headers['X-API-Key'] = PDNS_API_KEY headers['X-API-Key'] = self.PDNS_API_KEY
if NEW_SCHEMA: if self.NEW_SCHEMA:
data = {"rrsets": [ data = {"rrsets": [
{ {
"name": self.name + '.', "name": self.name + '.',
@ -1664,7 +1681,7 @@ class Record(object):
] ]
} }
try: try:
jdata = utils.fetch_json(urljoin(PDNS_STATS_URL, API_EXTENDED_URL + '/servers/localhost/zones/{0}'.format(domain)), headers=headers, method='PATCH', data=data) jdata = utils.fetch_json(urljoin(self.PDNS_STATS_URL, self.API_EXTENDED_URL + '/servers/localhost/zones/{0}'.format(domain)), headers=headers, method='PATCH', data=data)
logging.debug("dyndns data: {0}".format(data)) logging.debug("dyndns data: {0}".format(data))
return {'status': 'ok', 'msg': 'Record was updated successfully'} return {'status': 'ok', 'msg': 'Record was updated successfully'}
except Exception as e: except Exception as e:
@ -1673,8 +1690,8 @@ class Record(object):
def update_db_serial(self, domain): def update_db_serial(self, domain):
headers = {} headers = {}
headers['X-API-Key'] = PDNS_API_KEY headers['X-API-Key'] = self.PDNS_API_KEY
jdata = utils.fetch_json(urljoin(PDNS_STATS_URL, API_EXTENDED_URL + '/servers/localhost/zones/{0}'.format(domain)), headers=headers, method='GET') jdata = utils.fetch_json(urljoin(self.PDNS_STATS_URL, self.API_EXTENDED_URL + '/servers/localhost/zones/{0}'.format(domain)), headers=headers, method='GET')
serial = jdata['serial'] serial = jdata['serial']
domain = Domain.query.filter(Domain.name==domain).first() domain = Domain.query.filter(Domain.name==domain).first()
@ -1695,16 +1712,21 @@ class Server(object):
def __init__(self, server_id=None, server_config=None): def __init__(self, server_id=None, server_config=None):
self.server_id = server_id self.server_id = server_id
self.server_config = server_config self.server_config = server_config
# PDNS configs
self.PDNS_STATS_URL = Setting().get('pdns_api_url')
self.PDNS_API_KEY = Setting().get('pdns_api_key')
self.PDNS_VERSION = Setting().get('pdns_version')
self.API_EXTENDED_URL = utils.pdns_api_extended_uri(self.PDNS_VERSION)
def get_config(self): def get_config(self):
""" """
Get server config Get server config
""" """
headers = {} headers = {}
headers['X-API-Key'] = PDNS_API_KEY headers['X-API-Key'] = self.PDNS_API_KEY
try: try:
jdata = utils.fetch_json(urljoin(PDNS_STATS_URL, API_EXTENDED_URL + '/servers/{0}/config'.format(self.server_id)), headers=headers, method='GET') jdata = utils.fetch_json(urljoin(self.PDNS_STATS_URL, self.API_EXTENDED_URL + '/servers/{0}/config'.format(self.server_id)), headers=headers, method='GET')
return jdata return jdata
except: except:
logging.error("Can not get server configuration.") logging.error("Can not get server configuration.")
@ -1716,10 +1738,10 @@ class Server(object):
Get server statistics Get server statistics
""" """
headers = {} headers = {}
headers['X-API-Key'] = PDNS_API_KEY headers['X-API-Key'] = self.PDNS_API_KEY
try: try:
jdata = utils.fetch_json(urljoin(PDNS_STATS_URL, API_EXTENDED_URL + '/servers/{0}/statistics'.format(self.server_id)), headers=headers, method='GET') jdata = utils.fetch_json(urljoin(self.PDNS_STATS_URL, self.API_EXTENDED_URL + '/servers/{0}/statistics'.format(self.server_id)), headers=headers, method='GET')
return jdata return jdata
except: except:
logging.error("Can not get server statistics.") logging.error("Can not get server statistics.")
@ -1773,19 +1795,54 @@ 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))
# default settings (serves as list of known settings too):
# Note: booleans must be strings because of the way they are stored and used
defaults = { defaults = {
'maintenance': 'False', 'maintenance': False,
'fullscreen_layout': 'True', 'fullscreen_layout': True,
'record_helper': 'True', 'record_helper': True,
'login_ldap_first': 'True', 'login_ldap_first': True,
'default_record_table_size': 15, 'default_record_table_size': 15,
'default_domain_table_size': 10, 'default_domain_table_size': 10,
'auto_ptr': 'False', 'auto_ptr': False,
'allow_quick_edit': 'True' 'allow_quick_edit': True,
'pretty_ipv6_ptr': False,
'dnssec_admins_only': False,
'bg_domain_updates': False,
'site_name': 'PowerDNS-Admin',
'pdns_api_url': '',
'pdns_api_key': '',
'pdns_version': '4.1.1',
'local_db_enabled': True,
'signup_enabled': True,
'ldap_enabled': False,
'ldap_type': 'ldap',
'ldap_uri': '',
'ldap_base_dn': '',
'ldap_admin_username': '',
'ldap_admin_password': '',
'ldap_filter_basic': '',
'ldap_filter_username': '',
'ldap_sg_enabled': False,
'ldap_admin_group': False,
'ldap_user_group': False,
'github_oauth_enabled': False,
'github_oauth_key': '',
'github_oauth_secret': '',
'github_oauth_scope': 'email',
'github_oauth_api_url': 'https://api.github.com/user',
'github_oauth_token_url': 'https://github.com/login/oauth/access_token',
'github_oauth_authorize_url': 'https://github.com/login/oauth/authorize',
'google_oauth_enabled': False,
'google_oauth_client_id':'',
'google_oauth_client_secret':'',
'google_token_url': 'https://accounts.google.com/o/oauth2/token',
'google_token_params': {'scope': 'email profile'},
'google_authorize_url':'https://accounts.google.com/o/oauth2/auth',
'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):
@ -1804,7 +1861,7 @@ class Setting(db.Model):
if maintenance is None: if maintenance is None:
value = self.defaults['maintenance'] value = self.defaults['maintenance']
maintenance = Setting(name='maintenance', value=value) maintenance = Setting(name='maintenance', value=str(value))
db.session.add(maintenance) db.session.add(maintenance)
mode = str(mode) mode = str(mode)
@ -1825,7 +1882,7 @@ class Setting(db.Model):
if current_setting is None: if current_setting is None:
value = self.defaults[setting] value = self.defaults[setting]
current_setting = Setting(name=setting, value=value) current_setting = Setting(name=setting, value=str(value))
db.session.add(current_setting) db.session.add(current_setting)
try: try:
@ -1864,12 +1921,31 @@ class Setting(db.Model):
if setting in self.defaults: if setting in self.defaults:
result = self.query.filter(Setting.name == setting).first() result = self.query.filter(Setting.name == setting).first()
if result is not None: if result is not None:
return result.value return strtobool(result.value) if result.value in ['True', 'False'] else result.value
else: else:
return self.defaults[setting] return self.defaults[setting]
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):
r = {}
settings = Setting.query.filter(Setting.view == view).all()
for setting in settings:
d = setting.__dict__
r[d['name']] = d['value']
return r
class DomainTemplate(db.Model): class DomainTemplate(db.Model):
__tablename__ = "domain_template" __tablename__ = "domain_template"

78
app/oauth.py Normal file
View file

@ -0,0 +1,78 @@
from ast import literal_eval
from flask import request, session, redirect, url_for
from flask_oauthlib.client import OAuth
from app import app, oauth
from app.models import Setting
# TODO:
# - Replace Flask-OAuthlib by authlib
# - Fix github/google enabling (Currently need to reload the flask app)
def github_oauth():
if not Setting().get('github_oauth_enabled'):
return None
github = oauth.remote_app(
'github',
consumer_key = Setting().get('github_oauth_key'),
consumer_secret = Setting().get('github_oauth_secret'),
request_token_params = {'scope': Setting().get('github_oauth_scope')},
base_url = Setting().get('github_oauth_api_url'),
request_token_url = None,
access_token_method = 'POST',
access_token_url = Setting().get('github_oauth_token_url'),
authorize_url = Setting().get('github_oauth_authorize_url')
)
@app.route('/github/authorized')
def github_authorized():
session['github_oauthredir'] = url_for('.github_authorized', _external=True)
resp = github.authorized_response()
if resp is None:
return 'Access denied: reason=%s error=%s' % (
request.args['error'],
request.args['error_description']
)
session['github_token'] = (resp['access_token'], '')
return redirect(url_for('.login'))
@github.tokengetter
def get_github_oauth_token():
return session.get('github_token')
return github
def google_oauth():
if not Setting().get('google_oauth_enabled'):
return None
google = oauth.remote_app(
'google',
consumer_key=Setting().get('google_oauth_client_id'),
consumer_secret=Setting().get('google_oauth_client_secret'),
request_token_params=literal_eval(Setting().get('google_token_params')),
base_url=Setting().get('google_base_url'),
request_token_url=None,
access_token_method='POST',
access_token_url=Setting().get('google_token_url'),
authorize_url=Setting().get('google_authorize_url'),
)
@app.route('/google/authorized')
def google_authorized():
resp = google.authorized_response()
if resp is None:
return 'Access denied: reason=%s error=%s' % (
request.args['error_reason'],
request.args['error_description']
)
session['google_token'] = (resp['access_token'], '')
return redirect(url_for('.login'))
@google.tokengetter
def get_google_oauth_token():
return session.get('google_token')
return google

View file

@ -1,5 +1,6 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block title %}<title>DNS Control Panel - Admin Console</title>{% endblock %} {% set active_page = "admin_console" %}
{% block title %}<title>Admin Console - {{ SITE_NAME }}</title>{% endblock %}
{% block dashboard_stat %} {% block dashboard_stat %}
<!-- Content Header (Page header) --> <!-- Content Header (Page header) -->

View file

@ -1,5 +1,6 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block title %}<title>DNS Control Panel - Edit Account</title>{% endblock %} {% set active_page = "admin_accounts" %}
{% block title %}<title>Edit Account - {{ SITE_NAME }}</title>{% endblock %}
{% block dashboard_stat %} {% block dashboard_stat %}
<!-- Content Header (Page header) --> <!-- Content Header (Page header) -->

View file

@ -1,5 +1,6 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block title %}<title>DNS Control Panel - Edit User</title>{% endblock %} {% set active_page = "admin_users" %}
{% block title %}<title>Edit Use - {{ SITE_NAME }}</title>{% endblock %}
{% block dashboard_stat %} {% block dashboard_stat %}
<!-- Content Header (Page header) --> <!-- Content Header (Page header) -->

View file

@ -1,5 +1,7 @@
{% extends "base.html" %} {% block title %} {% extends "base.html" %}
<title>DNS Control Panel - History</title> {% set active_page = "admin_history" %}
{% block title %}
<title>History - {{ SITE_NAME }}</title>
{% endblock %} {% block dashboard_stat %} {% endblock %} {% block dashboard_stat %}
<!-- Content Header (Page header) --> <!-- Content Header (Page header) -->
<section class="content-header"> <section class="content-header">

View file

@ -1,5 +1,7 @@
{% extends "base.html" %} {% block title %} {% extends "base.html" %}
<title>DNS Control Panel - Account Management</title> {% set active_page = "admin_accounts" %}
{% block title %}
<title>Account Management - {{ SITE_NAME }}</title>
{% endblock %} {% block dashboard_stat %} {% endblock %} {% block dashboard_stat %}
<section class="content-header"> <section class="content-header">
<h1> <h1>

View file

@ -1,5 +1,7 @@
{% extends "base.html" %} {% block title %} {% extends "base.html" %}
<title>DNS Control Panel - User Management</title> {% set active_page = "admin_users" %}
{% block title %}
<title>User Management - {{ SITE_NAME }}</title>
{% endblock %} {% block dashboard_stat %} {% endblock %} {% block dashboard_stat %}
<section class="content-header"> <section class="content-header">
<h1> <h1>

View file

@ -0,0 +1,495 @@
{% extends "base.html" %}
{% set active_page = "admin_settings" %}
{% block title %}
<title>Authentication 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">Authentication</li>
</ol>
</section>
{% endblock %}
{% block content %}
<section class="content">
<div class="row">
<div class="col-lg-12">
<div class="box box-primary">
<div class="box-header with-border">
<h3 class="box-title">Authentication Settings</h3>
</div>
<div class="box-body">
{% if result %}
<div class="alert {% if result['status'] %}alert-success{% else %}alert-danger{% endif%} alert-dismissible">
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">&times;</button>
{{ result['msg'] }}
</div>
{% endif %}
<!-- Custom Tabs -->
<div class="nav-tabs-custom" id="tabs">
<ul class="nav nav-tabs">
<li class="active"><a href="#tabs-general" data-toggle="tab">General</a></li>
<li class="active"><a href="#tabs-ldap" data-toggle="tab">LDAP</a></li>
<li><a href="#tabs-google" data-toggle="tab">Google OAuth</a></li>
<li><a href="#tabs-github" data-toggle="tab">Github OAuth</a></li>
</ul>
<div class="tab-content">
<div class="tab-pane active" id="tabs-general">
<form role="form" method="post">
<input type="hidden" value="general" name="config_tab" />
<div class="form-group">
<input type="checkbox" id="local_db_enabled" name="local_db_enabled" class="checkbox" {% if SETTING.get('local_db_enabled') %}checked{% endif %}>
<label for="local_db_enabled">Local DB Authentication</label>
</div>
<div class="form-group">
<input type="checkbox" id="signup_enabled" name="signup_enabled" class="checkbox" {% if SETTING.get('signup_enabled') %}checked{% endif %}>
<label for="signup_enabled">Allow users to sign up</label>
</div>
<div class="form-group">
<button type="submit" class="btn btn-flat btn-primary">Save</button>
</div>
</form>
</div>
<div class="tab-pane active" id="tabs-ldap">
<div class="row">
<div class="col-md-4">
<form role="form" method="post" data-toggle="validator">
<input type="hidden" value="ldap" name="config_tab" />
<fieldset>
<legend>GENERAL</legend>
<div class="form-group">
<input type="checkbox" id="ldap_enabled" name="ldap_enabled" class="checkbox" {% if SETTING.get('ldap_enabled') %}checked{% endif %}>
<label for="ldap_enabled">Enable LDAP Authentication</label>
</div>
<div class="form-group">
<label>Type</label>
<div class="radio">
<label>
<input type="radio" name="ldap_type" id="ldap" value="ldap" {% if SETTING.get('ldap_type')=='ldap' %}checked{% endif %}> OpenLDAP
</label>
&nbsp;&nbsp;&nbsp;
<label>
<input type="radio" name="ldap_type" id="ad" value="ad" {% if SETTING.get('ldap_type')=='ad' %}checked{% endif %}> Active Directory
</label>
</div>
</div>
</fieldset>
<fieldset>
<legend>ADMINISTRATOR INFO</legend>
<div class="form-group">
<label for="ldap_uri">LDAP URI</label>
<input type="text" class="form-control" name="ldap_uri" id="ldap_uri" placeholder="e.g. ldaps://your-ldap-server:636" data-error="Please input LDAP URI" value="{{ SETTING.get('ldap_uri') }}">
</div>
<div class="form-group">
<label for="ldap_base_dn">LDAP Base DN</label>
<input type="text" class="form-control" name="ldap_base_dn" id="ldap_base_dn" placeholder="e.g. dc=mydomain,dc=com" data-error="Please input LDAP Base DN" value="{{ SETTING.get('ldap_base_dn') }}">
<span class="help-block with-errors"></span>
</div>
<div class="form-group">
<label for="ldap_admin_username">LDAP admin username</label>
<input type="text" class="form-control" name="ldap_admin_username" id="ldap_admin_username" placeholder="e.g. cn=admin,dc=mydomain,dc=com" data-error="Please input LDAP admin username" value="{{ SETTING.get('ldap_admin_username') }}">
<span class="help-block with-errors"></span>
</div>
<div class="form-group">
<label for="ldap_admin_password">LDAP admin password</label>
<input type="password" class="form-control" name="ldap_admin_password" id="ldap_admin_password" placeholder="LDAP Admin password" data-error="Please input LDAP admin password" value="{{ SETTING.get('ldap_admin_password') }}">
<span class="help-block with-errors"></span>
</div>
</fieldset>
<fieldset>
<legend>FILTERS</legend>
<div class="form-group">
<label for="ldap_filter_basic">Basic filter</label>
<input type="text" class="form-control" name="ldap_filter_basic" id="ldap_filter_basic" placeholder="e.g. (objectClass=inetorgperson)" data-error="Please input LDAP filter" value="{{ SETTING.get('ldap_filter_basic') }}">
<span class="help-block with-errors"></span>
</div>
<div class="form-group">
<label for="ldap_filter_username">Username field</label>
<input type="text" class="form-control" name="ldap_filter_username" id="ldap_filter_username" placeholder="e.g. uid" data-error="Please input field for username filtering" value="{{ SETTING.get('ldap_filter_username') }}">
<span class="help-block with-errors"></span>
</div>
</fieldset>
<fieldset>
<legend>GROUP SECURITY</legend>
<div class="form-group">
<label>Status</label>
<div class="radio">
<label>
<input type="radio" name="ldap_sg_enabled" id="ldap_sg_off" value="OFF" {% if not SETTING.get('ldap_sg_enabled') %}checked{% endif %}> OFF
</label>
&nbsp;&nbsp;&nbsp;
<label>
<input type="radio" name="ldap_sg_enabled" id="ldap_sg_on" value="ON" {% if SETTING.get('ldap_sg_enabled') %}checked{% endif %}> ON
</label>
</div>
</div>
<div class="form-group">
<label for="ldap_admin_group">Admin group</label>
<input type="text" class="form-control" name="ldap_admin_group" id="ldap_admin_group" placeholder="e.g. cn=sysops,dc=mydomain,dc=com" data-error="Please input LDAP DN for Admin group" value="{{ SETTING.get('ldap_admin_group') }}">
<span class="help-block with-errors"></span>
</div>
<div class="form-group">
<label for="ldap_user_group">User group</label>
<input type="text" class="form-control" name="ldap_user_group" id="ldap_user_group" placeholder="e.g. cn=users,dc=mydomain,dc=com" data-error="Please input LDAP DN for User group" value="{{ SETTING.get('ldap_user_group') }}">
<span class="help-block with-errors"></span>
</div>
</fieldset>
<div class="form-group">
<button type="submit" class="btn btn-flat btn-primary">Save</button>
</div>
</form>
</div>
<div class="col-md-8">
<legend>Help</legend>
<dl class="dl-horizontal">
<dt>Enable LDAP Authentication</dt>
<dd>Turn on / off the LDAP authentication.</dd>
<dt>Type</dt>
<dd>Select your current directory service type.
<ul>
<li>
OpenLDAP - Open source implementation of the Lightweight Directory Access Protocol.
</li>
<li>
Active Directory - Active Directory is a directory service that Microsoft developed for the Windows domain networks.
</li>
</ul>
</dd>
<dt>ADMINISTRATOR INFO</dt>
<dd>Your LDAP connection string and admin credential used by PDA to query user information.
<ul>
<li>
LDAP URI - The fully qualified domain names of your directory servers. (e.g. ldap://127.0.0.1:389)
</li>
<li>
LDAP Base DN - The point from where a PDA will search for users.
</li>
<li>
LDAP admin username - Your LDAP administrator user which has permission to query information in the Base DN above.
</li>
<li>
LDAP admin password - The password of LDAP administrator user.
</li>
</ul>
</dd>
<dt>FILTERS</dt>
<dd>Define how you want to filter your user in LDAP query.
<ul>
<li>
Basic filter - The filter that will be applied to all LDAP query by PDA. (e.g. <i>(objectClass=inetorgperson)</i> for OpenLDAP and <i>(objectClass=organizationalPerson)</i> for Active Directory)
</li>
<li>
Username field - The field PDA will look for user's username. (e.g. <i>uid</i> for OpenLDAP and <i>sAMAccountName</i> or <i>userPrincipalName</i> for Active Directory)
</li>
</ul>
</dd>
<dt>GROUP SECURITY</dt>
<dd>User can be assigned to PDA's User or Admin group by matching following LDAP Group.
<ul>
<li>
Status - Turn on / off group security feature.
</li>
<li>
Admin group - Your LDAP admin group.
</li>
<li>
User group - Your LDAP user group.
</li>
</ul>
</dd>
</dl>
</div>
</div>
</div>
<div class="tab-pane" id="tabs-google">
<div class="row">
<div class="col-md-4">
<form role="form" method="post" data-toggle="validator">
<input type="hidden" value="google" name="config_tab" />
<fieldset>
<legend>GENERAL</legend>
<div class="form-group">
<input type="checkbox" id="google_oauth_enabled" name="google_oauth_enabled" class="checkbox" {% if SETTING.get('google_oauth_enabled') %}checked{% endif %}>
<label for="google_oauth_enabled">Enable Google OAuth</label>
</div>
<div class="form-group">
<label for="google_oauth_client_id">Client ID</label>
<input type="text" class="form-control" name="google_oauth_client_id" id="google_oauth_client_id" placeholder="Google OAuth client ID" data-error="Please input Client ID" value="{{ SETTING.get('google_oauth_client_id') }}">
<span class="help-block with-errors"></span>
</div>
<div class="form-group">
<label for="google_oauth_client_secret">Client secret</label>
<input type="text" class="form-control" name="google_oauth_client_secret" id="google_oauth_client_secret" placeholder="Google OAuth client secret" data-error="Please input Client secret" value="{{ SETTING.get('google_oauth_client_secret') }}">
<span class="help-block with-errors"></span>
</div>
</fieldset>
<fieldset>
<legend>ADVANCE</legend>
<div class="form-group">
<label for="google_token_url">Token URL</label>
<input type="text" class="form-control" name="google_token_url" id="google_token_url" placeholder="e.g. https://accounts.google.com/o/oauth2/token" data-error="Please input token URL" value="{{ SETTING.get('google_token_url') }}">
<span class="help-block with-errors"></span>
</div>
<div class="form-group">
<label for="google_token_params">Token params</label>
<input type="text" class="form-control" name="google_token_params" id="google_token_params" placeholder="e.g. {'scope': 'email profile'}" data-error="Please input token params" value="{{ SETTING.get('google_token_params') }}">
<span class="help-block with-errors"></span>
</div>
<div class="form-group">
<label for="google_authorize_url">Authorize URL</label>
<input type="text" class="form-control" name="google_authorize_url" id="google_authorize_url" placeholder="e.g. https://accounts.google.com/o/oauth2/auth" data-error="Please input Authorize URL" value="{{ SETTING.get('google_authorize_url') }}">
<span class="help-block with-errors"></span>
</div>
<div class="form-group">
<label for="google_base_url">Base URL</label>
<input type="text" class="form-control" name="google_base_url" id="google_base_url" placeholder="e.g. https://www.googleapis.com/oauth2/v1/" data-error="Please input base URL" value="{{ SETTING.get('google_base_url') }}">
<span class="help-block with-errors"></span>
</div>
</fieldset>
<div class="form-group">
<button type="submit" class="btn btn-flat btn-primary">Save</button>
</div>
</form>
</div>
<div class="col-md-8">
<legend>Help</legend>
<p>Fill in all the fields in the left form.</p>
<p>Make sure you add PDA redirection URI (e.g http://localhost:9191/google/authorized) to your Google App Credentials Restriction.</p>
</div>
</div>
</div>
<div class="tab-pane" id="tabs-github">
<div class="row">
<div class="col-md-4">
<form role="form" method="post" data-toggle="validator">
<input type="hidden" value="github" name="config_tab" />
<fieldset>
<legend>GENERAL</legend>
<div class="form-group">
<input type="checkbox" id="github_oauth_enabled" name="github_oauth_enabled" class="checkbox" {% if SETTING.get('github_oauth_enabled') %}checked{% endif %}>
<label for="github_oauth_enabled">Enable Github OAuth</label>
</div>
<div class="form-group">
<label for="github_oauth_key">Client key</label>
<input type="text" class="form-control" name="github_oauth_key" id="github_oauth_key" placeholder="Google OAuth client ID" data-error="Please input Client key" value="{{ SETTING.get('github_oauth_key') }}">
<span class="help-block with-errors"></span>
</div>
<div class="form-group">
<label for="github_oauth_secret">Client secret</label>
<input type="text" class="form-control" name="github_oauth_secret" id="github_oauth_secret" placeholder="Google OAuth client secret" data-error="Please input Client secret" value="{{ SETTING.get('github_oauth_secret') }}">
<span class="help-block with-errors"></span>
</div>
</fieldset>
<fieldset>
<legend>ADVANCE</legend>
<div class="form-group">
<label for="github_oauth_scope">Scope</label>
<input type="text" class="form-control" name="github_oauth_scope" id="github_oauth_scope" placeholder="e.g. email" data-error="Please input scope" value="{{ SETTING.get('github_oauth_scope') }}">
<span class="help-block with-errors"></span>
</div>
<div class="form-group">
<label for="github_oauth_api_url">API URL</label>
<input type="text" class="form-control" name="github_oauth_api_url" id="github_oauth_api_url" placeholder="e.g. https://api.github.com/user" data-error="Please input API URL" value="{{ SETTING.get('github_oauth_api_url') }}">
<span class="help-block with-errors"></span>
</div>
<div class="form-group">
<label for="github_oauth_token_url">Token URL</label>
<input type="text" class="form-control" name="github_oauth_token_url" id="github_oauth_token_url" placeholder="e.g. https://github.com/login/oauth/access_token" data-error="Please input Token URL" value="{{ SETTING.get('github_oauth_token_url') }}">
<span class="help-block with-errors"></span>
</div>
<div class="form-group">
<label for="github_oauth_authorize_url">Authorize URL</label>
<input type="text" class="form-control" name="github_oauth_authorize_url" id="github_oauth_authorize_url" placeholder="e.g. https://github.com/login/oauth/authorize" data-error="Plesae input Authorize URL" value="{{ SETTING.get('github_oauth_authorize_url') }}">
<span class="help-block with-errors"></span>
</div>
</fieldset>
<div class="form-group">
<button type="submit" class="btn btn-flat btn-primary">Save</button>
</div>
</form>
</div>
<div class="col-md-8">
<legend>Help</legend>
<p>Fill in all the fields in the left form.</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
{% endblock %}
{% block extrascripts %}
{% assets "js_validation" -%}
<script type="text/javascript" src="{{ ASSET_URL }}"></script>
{%- endassets %}
<script>
$(function() {
$('#tabs').tabs({
// add url anchor tags
activate: function(event, ui) {
window.location.hash = ui.newPanel.attr('id');
}
});
// re-set active tab (ui)
var activeTabIdx = $('#tabs').tabs('option','active');
$('#tabs li:eq('+activeTabIdx+')').tab('show')
});
// START: General tab js
$('#local_db_enabled').iCheck({
checkboxClass : 'icheckbox_square-blue',
increaseArea : '20%'
})
$('#signup_enabled').iCheck({
checkboxClass : 'icheckbox_square-blue',
increaseArea : '20%'
})
// END: General tab js
// START: LDAP tab js
// update validation requirement when checkbox is togged
$('#ldap_enabled').iCheck({
checkboxClass : 'icheckbox_square-blue',
increaseArea : '20%'
}).on('ifChanged', function(e) {
var is_enabled = e.currentTarget.checked;
if (is_enabled){
$('#ldap_uri').prop('required', true);
$('#ldap_base_dn').prop('required', true);
$('#ldap_admin_username').prop('required', true);
$('#ldap_admin_password').prop('required', true);
$('#ldap_filter_basic').prop('required', true);
$('#ldap_filter_username').prop('required', true);
if ($('#ldap_sg_on').is(":checked")) {
$('#ldap_admin_group').prop('required', true);
$('#ldap_user_group').prop('required', true);
}
} else {
$('#ldap_uri').prop('required', false);
$('#ldap_base_dn').prop('required', false);
$('#ldap_admin_username').prop('required', false);
$('#ldap_admin_password').prop('required', false);
$('#ldap_filter_basic').prop('required', false);
$('#ldap_filter_username').prop('required', false);
if ($('#ldap_sg_on').is(":checked")) {
$('#ldap_admin_group').prop('required', false);
$('#ldap_user_group').prop('required', false);
}
}
});
$("input[name='ldap_sg_enabled']" ).change(function(){
if ($('#ldap_sg_on').is(":checked") && $('#ldap_enabled').is(":checked")) {
$('#ldap_admin_group').prop('required', true);
$('#ldap_user_group').prop('required', true);
} else {
$('#ldap_admin_group').prop('required', false);
$('#ldap_user_group').prop('required', false);
}
});
// init validation reqirement at first time page load
{% if SETTING.get('ldap_enabled') %}
$('#ldap_uri').prop('required', true);
$('#ldap_base_dn').prop('required', true);
$('#ldap_admin_username').prop('required', true);
$('#ldap_admin_password').prop('required', true);
$('#ldap_filter_basic').prop('required', true);
$('#ldap_filter_username').prop('required', true);
if ($('#ldap_sg_on').is(":checked")) {
$('#ldap_admin_group').prop('required', true);
$('#ldap_user_group').prop('required', true);
}
{% endif %}
// END: LDAP tab js
// START: Google tab js
// update validation requirement when checkbox is togged
$('#google_oauth_enabled').iCheck({
checkboxClass : 'icheckbox_square-blue',
increaseArea : '20%'
}).on('ifChanged', function(e) {
var is_enabled = e.currentTarget.checked;
if (is_enabled){
$('#google_oauth_client_id').prop('required', true);
$('#google_oauth_client_secret').prop('required', true);
$('#google_token_url').prop('required', true);
$('#google_token_params').prop('required', true);
$('#google_authorize_url').prop('required', true);
$('#google_base_url').prop('required', true);
} else {
$('#google_oauth_client_id').prop('required', false);
$('#google_oauth_client_secret').prop('required', false);
$('#google_token_url').prop('required', false);
$('#google_token_params').prop('required', false);
$('#google_authorize_url').prop('required', false);
$('#google_base_url').prop('required', false);
}
});
// init validation reqirement at first time page load
{% if SETTING.get('google_oauth_enabled') %}
$('#google_oauth_client_id').prop('required', true);
$('#google_oauth_client_secret').prop('required', true);
$('#google_token_url').prop('required', true);
$('#google_token_params').prop('required', true);
$('#google_authorize_url').prop('required', true);
$('#google_base_url').prop('required', true);
{% endif %}
// END: Google tab js
// START: Github tab js
// update validation requirement when checkbox is togged
$('#github_oauth_enabled').iCheck({
checkboxClass : 'icheckbox_square-blue',
increaseArea : '20%'
}).on('ifChanged', function(e) {
var is_enabled = e.currentTarget.checked;
if (is_enabled){
$('#github_oauth_key').prop('required', true);
$('#github_oauth_secret').prop('required', true);
$('#github_oauth_scope').prop('required', true);
$('#github_oauth_api_url').prop('required', true);
$('#github_oauth_token_url').prop('required', true);
$('#github_oauth_authorize_url').prop('required', true);
} else {
$('#github_oauth_key').prop('required', false);
$('#github_oauth_secret').prop('required', false);
$('#github_oauth_scope').prop('required', false);
$('#github_oauth_api_url').prop('required', false);
$('#github_oauth_token_url').prop('required', false);
$('#github_oauth_authorize_url').prop('required', false);
}
});
// init validation reqirement at first time page load
{% if SETTING.get('google_oauth_enabled') %}
$('#github_oauth_key').prop('required', true);
$('#github_oauth_secret').prop('required', true);
$('#github_oauth_scope').prop('required', true);
$('#github_oauth_api_url').prop('required', true);
$('#github_oauth_token_url').prop('required', true);
$('#github_oauth_authorize_url').prop('required', true);
{% endif %}
// END: Github tab js
</script>
{% endblock %}

View file

@ -1,5 +1,7 @@
{% extends "base.html" %} {% block title %} {% extends "base.html" %}
<title>DNS Control Panel - Settings</title> {% set active_page = "admin_settings" %}
{% block title %}
<title>Basic Settings - {{ SITE_NAME }}</title>
{% endblock %} {% block dashboard_stat %} {% endblock %} {% block dashboard_stat %}
<!-- Content Header (Page header) --> <!-- Content Header (Page header) -->
<section class="content-header"> <section class="content-header">
@ -7,9 +9,9 @@
Settings <small>PowerDNS-Admin settings</small> Settings <small>PowerDNS-Admin settings</small>
</h1> </h1>
<ol class="breadcrumb"> <ol class="breadcrumb">
<li><a href="{{ url_for('dashboard') }}"><i <li><a href="{{ url_for('dashboard') }}"><i class="fa fa-dashboard"></i> Home</a></li>
class="fa fa-dashboard"></i> Home</a></li> <li><a href="#">Setting</a></li>
<li class="active">Settings</li> <li class="active">Basic</li>
</ol> </ol>
</section> </section>
{% endblock %} {% block content %} {% endblock %} {% block content %}
@ -18,7 +20,7 @@
<div class="col-xs-12"> <div class="col-xs-12">
<div class="box"> <div class="box">
<div class="box-header"> <div class="box-header">
<h3 class="box-title">Settings Management</h3> <h3 class="box-title">Basic Settings</h3>
</div> </div>
<div class="box-body"> <div class="box-body">
<table id="tbl_settings" class="table table-bordered table-striped"> <table id="tbl_settings" class="table table-bordered table-striped">
@ -30,21 +32,21 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for setting_name, setting_value in settings.items() %} {% for setting in settings %}
<tr class="odd "> <tr class="odd ">
<td>{{ setting_name }}</td> <td>{{ setting.name }}</td>
{% if setting_value == "True" or setting_value == "False" %} {% if setting.value == "True" or setting.value == "False" %}
<td>{{ setting_value }}</td> <td>{{ setting.value }}</td>
{% else %} {% else %}
<td><input name="value" id="value" value="{{ setting_value }}"></td> <td><input name="value" id="value" value="{{ setting.value }}"></td>
{% endif %} {% endif %}
<td width="6%"> <td width="6%">
{% if setting_value == "True" or setting_value == "False" %} {% if setting.value == "True" or setting.value == "False" %}
<button type="button" class="btn btn-flat btn-warning setting-toggle-button" id="{{ setting_name }}"> <button type="button" class="btn btn-flat btn-warning setting-toggle-button" id="{{ setting.name }}">
Toggle&nbsp;<i class="fa fa-info"></i> Toggle&nbsp;<i class="fa fa-info"></i>
</button> </button>
{% else %} {% else %}
<button type="button" class="btn btn-flat btn-warning setting-save-button" id="{{ setting_name }}"> <button type="button" class="btn btn-flat btn-warning setting-save-button" id="{{ setting.name }}">
Save&nbsp;<i class="fa fa-info"></i> Save&nbsp;<i class="fa fa-info"></i>
</button> </button>
{% endif %} {% endif %}
@ -76,14 +78,14 @@
}); });
$(document.body).on('click', '.setting-toggle-button', function() { $(document.body).on('click', '.setting-toggle-button', function() {
var setting = $(this).prop('id'); var setting = $(this).prop('id');
applyChanges('', $SCRIPT_ROOT + '/admin/setting/' + setting + '/toggle', false, true) applyChanges('', $SCRIPT_ROOT + '/admin/setting/basic/' + setting + '/toggle', false, true)
}); });
$(document.body).on('click', '.setting-save-button', function() { $(document.body).on('click', '.setting-save-button', function() {
var setting = $(this).prop('id'); var setting = $(this).prop('id');
var value = $(this).parents('tr').find('#value')[0].value; var value = $(this).parents('tr').find('#value')[0].value;
var postdata = {'value': value}; var postdata = {'value': value};
applyChanges(postdata, $SCRIPT_ROOT + '/admin/setting/' + setting + '/edit', false, true) applyChanges(postdata, $SCRIPT_ROOT + '/admin/setting/basic/' + setting + '/edit', false, true)
}); });
</script> </script>
{% endblock %} {% endblock %}

View file

@ -0,0 +1,85 @@
{% extends "base.html" %}
{% set active_page = "admin_settings" %}
{% block title %}
<title>PDNS 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">PDNS</li>
</ol>
</section>
{% endblock %}
{% block content %}
<section class="content">
<div class="row">
<div class="col-md-4">
<div class="box box-primary">
<div class="box-header with-border">
<h3 class="box-title">PDNS Settings</h3>
</div>
<!-- /.box-header -->
<!-- form start -->
<form role="form" method="post" data-toggle="validator">
<div class="box-body">
{% 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">
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">&times;</button>
<h4><i class="icon fa fa-ban"></i> Error!</h4>
Please complete your PowerDNS API configuration before continuing
</div>
{% endif %}
<div class="form-group has-feedback">
<label class="control-label" for="pdns_api_url">PDNS API URL</label>
<input type="url" class="form-control" placeholder="PowerDNS API url" name="pdns_api_url" data-error="Please input a valid PowerDNS API URL" required value="{{ pdns_api_url }}">
<span class="help-block with-errors"></span>
</div>
<div class="form-group has-feedback">
<label class="control-label" for="pdns_api_key">PDNS API KEY</label>
<input type="text" class="form-control" placeholder="PowerDNS API key" name="pdns_api_key" data-error="Please input a valid PowerDNS API key" required value="{{ pdns_api_key }}">
<span class="help-block with-errors"></span>
</div>
<div class="form-group has-feedback">
<label class="control-label" for="pdns_version">PDNS VERSION</label>
<input type="text" class="form-control" placeholder="PowerDNS version" name="pdns_version" data-error="Please input PowerDNS version" required value="{{ pdns_version }}">
<span class="help-block with-errors"></span>
</div>
</div>
<div class="box-footer">
<button type="submit" class="btn btn-flat btn-primary">Update</button>
</div>
</form>
</div>
</div>
<div class="col-md-8">
<div class="box box-primary">
<div class="box-header with-border">
<h3 class="box-title">Help</h3>
</div>
<div class="box-body">
<dl class="dl-horizontal">
<p>You must configure the API connection information before PowerDNS-Admiin can query your PowerDNS data. Following fields are required:</p>
<dt>PDNS API URL</dt>
<dd>Your PowerDNS API URL (eg. http://127.0.0.1:8081/).</dd>
<dt>PDNS API KEY</dt>
<dd>Your PowerDNS API key.</dd>
<dt>PDNS VERSION</dt>
<dd>Your PowerDNS version number (eg. 4.1.1).</dd>
</dl>
<p>Find more details at <a href="https://doc.powerdns.com/md/httpapi/README/">https://doc.powerdns.com/md/httpapi/README/</a></p>
</div>
</div>
</div>
</div>
</section>
{% endblock %}
{% block extrascripts %}
{% assets "js_validation" -%}
<script type="text/javascript" src="{{ ASSET_URL }}"></script>
{%- endassets %}
{% endblock %}

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>Select record types you allow user to edit in the forward zone and reverse zone. Take a look at <a href="https://doc.powerdns.com/authoritative/appendices/types.html">PowerDNS docs</a> for full list of supported record types.</p>
</div>
</div>
</div>
</div>
</section>
{% endblock %}
{% block extrascripts %}
<script>
$('.checkbox').iCheck({
checkboxClass : 'icheckbox_square-blue',
increaseArea : '20%'
})
</script>
{% endblock %}

View file

@ -4,7 +4,7 @@
{% block head %} {% block head %}
<meta charset="utf-8"> <meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge">
{% block title %}<title>DNS Control Panel</title>{% endblock %} {% block title %}<title>{{ SITE_NAME }}</title>{% endblock %}
<!-- Get Google Fonts we like --> <!-- Get Google Fonts we like -->
<link rel="stylesheet" href="//fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,600,700,300italic,400italic,600italic"> <link rel="stylesheet" href="//fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,600,700,300italic,400italic,600italic">
<link rel="stylesheet" href="//fonts.googleapis.com/css?family=Roboto+Mono:400,300,700"> <link rel="stylesheet" href="//fonts.googleapis.com/css?family=Roboto+Mono:400,300,700">
@ -21,7 +21,7 @@
<![endif]--> <![endif]-->
{% endblock %} {% endblock %}
</head> </head>
<body class="hold-transition skin-blue sidebar-mini {% if not fullscreen_layout_setting %}layout-boxed{% endif %}"> <body class="hold-transition skin-blue sidebar-mini {% if not SETTING.get('fullscreen_layout') %}layout-boxed{% endif %}">
<div class="wrapper"> <div class="wrapper">
{% block pageheader %} {% block pageheader %}
<header class="main-header"> <header class="main-header">
@ -105,17 +105,45 @@
<!-- sidebar menu: : style can be found in sidebar.less --> <!-- sidebar menu: : style can be found in sidebar.less -->
<ul class="sidebar-menu" data-widget="tree"> <ul class="sidebar-menu" data-widget="tree">
<li class="header">USER ACTIONS</li> <li class="header">USER ACTIONS</li>
<li><a href="{{ url_for('dashboard') }}"><i class="fa fa-dashboard"></i> <span>Dashboard</span></a></li> <li class="{{ 'active' if active_page == 'dashboard' else '' }}">
<a href="{{ url_for('dashboard') }}"><i class="fa fa-dashboard"></i> Dashboard</a>
</li>
{% if current_user.role.name == 'Administrator' %} {% if current_user.role.name == 'Administrator' %}
<li><a href="{{ url_for('domain_add') }}"><i class="fa fa-plus"></i> <span>New Domain</span></a></li> <li class="{{ 'active' if active_page == 'new_domain' else '' }}">
<a href="{{ url_for('domain_add') }}"><i class="fa fa-plus"></i> New Domain</a>
</li>
<li class="header">ADMINISTRATION</li> <li class="header">ADMINISTRATION</li>
<li><a href="{{ url_for('admin') }}"><i class="fa fa-wrench"></i> <span>Admin Console</span></a></li> <li class="{{ 'active' if active_page == 'admin_console' else '' }}">
<li><a href="{{ url_for('templates') }}"><i class="fa fa-clone"></i> <span>Domain Templates</span></a></li> <a href="{{ url_for('admin') }}"><i class="fa fa-wrench"></i> Admin Console</a>
<li><a href="{{ url_for('admin_manageuser') }}"><i class="fa fa-users"></i> <span>Users</span></a></li> </li>
<li><a href="{{ url_for('admin_manageaccount') }}"><i class="fa fa-industry"></i> <span>Accounts</span></a></li> <li class="{{ 'active' if active_page == 'admin_domain_template' else '' }}">
<li><a href="{{ url_for('admin_history') }}"><i class="fa fa-calendar"></i> <span>History</span></a></li> <a href="{{ url_for('templates') }}"><i class="fa fa-clone"></i> Domain Templates</a>
<li><a href="{{ url_for('admin_settings') }}"><i class="fa fa-cog"></i> <span>Settings</span></a></li> </li>
<li class="{{ 'active' if active_page == 'admin_users' else '' }}">
<a href="{{ url_for('admin_manageuser') }}"><i class="fa fa-users"></i> Users</a>
</li>
<li class="{{ 'active' if active_page == 'admin_accounts' else '' }}">
<a href="{{ url_for('admin_manageaccount') }}"><i class="fa fa-industry"></i> Accounts</a>
</li>
<li class="{{ 'active' if active_page == 'admin_history' else '' }}">
<a href="{{ url_for('admin_history') }}"><i class="fa fa-calendar"></i> History</a>
</li>
<li class="{{ 'treeview active' if active_page == 'admin_settings' else 'treeview' }}">
<a href="#">
<i class="fa fa-cog"></i> Settings
<span class="pull-right-container">
<i class="fa fa-angle-left pull-right"></i>
</span>
</a>
<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_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_authentication') }}"><i class="fa fa-circle-o"></i> Authentication</a></li>
</ul>
</li>
{% endif %} {% endif %}
</ul>
{% endif %} {% endif %}
</section> </section>
<!-- /.sidebar --> <!-- /.sidebar -->
@ -146,7 +174,7 @@
</div> </div>
<!-- ./wrapper --> <!-- ./wrapper -->
<script type="text/javascript"> <script type="text/javascript">
$SCRIPT_ROOT = {{ request.script_root|tojson|safe }}; $SCRIPT_ROOT = {{ request.script_root|tojson|safe }};
</script> </script>
{% block scripts %} {% block scripts %}
{% assets "js_main" -%} {% assets "js_main" -%}

View file

@ -1,5 +1,6 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block title %}<title>DNS Control Panel - HOME</title>{% endblock %} {% set active_page = "dashboard" %}
{% block title %}<title>Dashboard - {{ SITE_NAME }}</title>{% endblock %}
{% block dashboard_stat %} {% block dashboard_stat %}
<!-- Content Header (Page header) --> <!-- Content Header (Page header) -->
@ -155,7 +156,7 @@
{% endblock %} {% endblock %}
{% block extrascripts %} {% block extrascripts %}
<script> <script>
PDNS_VERSION = '{{ pdns_version }}' PDNS_VERSION = '{{ SETTING.get("pdns_version") }}'
// set up history data table // set up history data table
$("#tbl_history").DataTable({ $("#tbl_history").DataTable({
"paging" : false, "paging" : false,
@ -188,14 +189,14 @@
"ajax" : "{{ url_for('dashboard_domains') }}", "ajax" : "{{ url_for('dashboard_domains') }}",
"info" : false, "info" : false,
"autoWidth" : false, "autoWidth" : false,
{% if default_domain_table_size_setting in ['10','25','50','100'] %} {% if SETTING.get('default_domain_table_size') in ['10','25','50','100'] %}
"lengthMenu": [ [10, 25, 50, 100, -1], "lengthMenu": [ [10, 25, 50, 100, -1],
[10, 25, 50, 100, "All"]], [10, 25, 50, 100, "All"]],
{% else %} {% else %}
"lengthMenu": [ [10, 25, 50, 100, {{ default_domain_table_size_setting }}, -1], "lengthMenu": [ [10, 25, 50, 100, {{ SETTING.get('default_domain_table_size') }}, -1],
[10, 25, 50, 100, {{ default_domain_table_size_setting }}, "All"]], [10, 25, 50, 100, {{ SETTING.get('default_domain_table_size') }}, "All"]],
{% endif %} {% endif %}
"pageLength": {{ default_domain_table_size_setting }} "pageLength": {{ SETTING.get('default_domain_table_size') }}
}); });
$(document.body).on('click', '.history-info-button', function() { $(document.body).on('click', '.history-info-button', function() {
var modal = $("#modal_history_info"); var modal = $("#modal_history_info");
@ -235,7 +236,7 @@
modal.modal('show'); modal.modal('show');
}); });
{% if current_user.role.name == 'Administrator' or dnssec_adm_only == false %} {% if current_user.role.name == 'Administrator' or not SETTING.get('dnssec_admins_only') %}
$(document.body).on("click", ".button_dnssec", function() { $(document.body).on("click", ".button_dnssec", function() {
var domain = $(this).prop('id'); var domain = $(this).prop('id');
getdnssec($SCRIPT_ROOT + '/domain/' + domain + '/dnssec', domain); getdnssec($SCRIPT_ROOT + '/domain/' + domain + '/dnssec', domain);

View file

@ -1,5 +1,5 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block title %}<title>{{ domain.name }} - DNS Control Panel</title>{% endblock %} {% block title %}<title>{{ domain.name }} - {{ SITE_NAME }}</title>{% endblock %}
{% block dashboard_stat %} {% block dashboard_stat %}
<section class="content-header"> <section class="content-header">
@ -104,7 +104,7 @@
{% endblock %} {% endblock %}
{% block extrascripts %} {% block extrascripts %}
<script> <script>
PDNS_VERSION = '{{ pdns_version }}' PDNS_VERSION = '{{ SETTING.get("pdns_version") }}'
// superglobals // superglobals
window.records_allow_edit = {{ editable_records|tojson }}; window.records_allow_edit = {{ editable_records|tojson }};
window.nEditing = null; window.nEditing = null;
@ -118,14 +118,14 @@
"ordering" : true, "ordering" : true,
"info" : true, "info" : true,
"autoWidth" : false, "autoWidth" : false,
{% if default_record_table_size_setting in ['5','15','20'] %} {% if SETTING.get('default_record_table_size') in ['5','15','20'] %}
"lengthMenu": [ [5, 15, 20, -1], "lengthMenu": [ [5, 15, 20, -1],
[5, 15, 20, "All"]], [5, 15, 20, "All"]],
{% else %} {% else %}
"lengthMenu": [ [5, 15, 20, {{ default_record_table_size_setting }}, -1], "lengthMenu": [ [5, 15, 20, {{ SETTING.get('default_record_table_size') }}, -1],
[5, 15, 20, {{ default_record_table_size_setting }}, "All"]], [5, 15, 20, {{ SETTING.get('default_record_table_size') }}, "All"]],
{% endif %} {% endif %}
"pageLength": {{ default_record_table_size_setting }}, "pageLength": {{ SETTING.get('default_record_table_size') }},
"language": { "language": {
"lengthMenu": " _MENU_ records" "lengthMenu": " _MENU_ records"
}, },
@ -267,7 +267,7 @@
applyChanges({'domain': domain}, $SCRIPT_ROOT + '/domain/' + domain + '/update'); applyChanges({'domain': domain}, $SCRIPT_ROOT + '/domain/' + domain + '/update');
}); });
{% if record_helper_setting %} {% if SETTING.get('record_helper') %}
//handle wacky record types //handle wacky record types
$(document.body).on("focus", "#current_edit_record_data", function (e) { $(document.body).on("focus", "#current_edit_record_data", function (e) {
var record_type = $(this).parents("tr").find('#record_type').val(); var record_type = $(this).parents("tr").find('#record_type').val();

View file

@ -1,5 +1,6 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block title %}<title>DNS Control Panel - Add Domain</title>{% endblock %} {% set active_page = "new_domain" %}
{% block title %}<title>Add Domain - {{ SITE_NAME }}</title>{% endblock %}
{% block dashboard_stat %} {% block dashboard_stat %}
<!-- Content Header (Page header) --> <!-- Content Header (Page header) -->
@ -45,7 +46,7 @@
</label> </label>
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;
<label> <label>
<input type="radio" name="radio_type"id="radio_type_master" value="master"> Master <input type="radio" name="radio_type" id="radio_type_master" value="master"> Master
</label> </label>
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;
<label> <label>

View file

@ -1,5 +1,5 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block title %}<title>DNS Control Panel - Domain Management</title>{% endblock %} {% block title %}<title>Domain Management - {{ SITE_NAME }}</title>{% endblock %}
{% block dashboard_stat %} {% block dashboard_stat %}
{% if status %} {% if status %}
@ -105,9 +105,9 @@
</div> </div>
<div class="box-body"> <div class="box-body">
<p><input type="checkbox" id="{{ domain.name }}" class="auto_ptr_toggle" <p><input type="checkbox" id="{{ domain.name }}" class="auto_ptr_toggle"
{% for setting in domain.settings %}{% if setting.setting=='auto_ptr' and setting.value=='True' %}checked{% endif %}{% endfor %} {% if auto_ptr_setting %}disabled="True"{% endif %}> {% for setting in domain.settings %}{% if setting.setting=='auto_ptr' and setting.value=='True' %}checked{% endif %}{% endfor %} {% if SETTING.get('auto_ptr') %}disabled="True"{% endif %}>
&nbsp;Allow automatic reverse pointer creation on record updates?{% if &nbsp;Allow automatic reverse pointer creation on record updates?{% if
auto_ptr_setting %}</br><code>Auto-ptr is enabled globally on the PDA system!</code>{% endif %}</p> SETTING.get('auto_ptr') %}</br><code>Auto-ptr is enabled globally on the PDA system!</code>{% endif %}</p>
</div> </div>
</div> </div>

View file

@ -3,7 +3,7 @@
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>DNS Control Panel - Log In</title> <title>Log In - {{ SITE_NAME }}</title>
<!-- Tell the browser to be responsive to screen width --> <!-- Tell the browser to be responsive to screen width -->
<meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" name="viewport"> <meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" name="viewport">
{% assets "css_login" -%} {% assets "css_login" -%}
@ -20,7 +20,7 @@
<body class="hold-transition login-page"> <body class="hold-transition login-page">
<div class="login-box"> <div class="login-box">
<div class="login-logo"> <div class="login-logo">
<a href="{{ url_for('index') }}">Sign In {{ login_title }}</a> <a href="{{ url_for('index') }}"><b>PowerDNS</b>-Admin</a>
</div> </div>
<!-- /.login-logo --> <!-- /.login-logo -->
<div class="login-box-body"> <div class="login-box-body">
@ -31,42 +31,34 @@
{{ error }} {{ error }}
</div> </div>
{% endif %} {% endif %}
<form action="" method="post"> <form action="" method="post" data-toggle="validator">
<div class="form-group"> <div class="form-group">
{% if username %} <input type="text" class="form-control" placeholder="Username" name="username" data-error="Please input your username" required {% if username %}value="{{ username }}"{% endif %}>
<input type="text" class="form-control" placeholder="Username" name="username" value="{{ username }}"> <span class="help-block with-errors"></span>
{% else %}
<input type="text" class="form-control" placeholder="Username" name="username">
{% endif %}
<span class="glyphicon glyphicon-user form-control-feedback"></span>
</div> </div>
<div class="form-group"> <div class="form-group">
{% if password %} <input type="password" class="form-control" placeholder="Password" name="password" data-error="Please input your password" required {% if password %}value="{{ password }}"{% endif %}>
<input type="password" class="form-control" placeholder="Password" name="password" value="{{ password }}"> <span class="help-block with-errors"></span>
{% else %}
<input type="password" class="form-control" placeholder="Password" name="password">
{% endif %}
<span class="glyphicon glyphicon-lock form-control-feedback"></span>
</div> </div>
<div class="form-group"> <div class="form-group">
<input type="otptoken" class="form-control" placeholder="OTP Token" name="otptoken"> <input type="otptoken" class="form-control" placeholder="OTP Token" name="otptoken">
</div> </div>
{% if ldap_enabled and basic_enabled %} {% if SETTING.get('ldap_enabled') and SETTING.get('local_db_enabled') %}
<div class="form-group"> <div class="form-group">
<select class="form-control" name="auth_method"> <select class="form-control" name="auth_method">
<option value="LOCAL">LOCAL Authentication</option> <option value="LOCAL">LOCAL Authentication</option>
{% if login_ldap_first_setting %} {% if SETTING.get('login_ldap_first') %}
<option value="LDAP" selected="selected">LDAP Authentication</option> <option value="LDAP" selected="selected">LDAP Authentication</option>
{% else %} {% else %}
<option value="LDAP">LDAP Authentication</option> <option value="LDAP">LDAP Authentication</option>
{% endif %} {% endif %}
</select> </select>
</div> </div>
{% elif ldap_enabled and not basic_enabled %} {% elif SETTING.get('ldap_enabled') and not SETTING.get('local_db_enabled') %}
<div class="form-group"> <div class="form-group">
<input type="hidden" name="auth_method" value="LDAP"> <input type="hidden" name="auth_method" value="LDAP">
</div> </div>
{% elif basic_enabled and not ldap_enabled %} {% elif SETTING.get('local_db_enabled') and not SETTING.get('ldap_enabled') %}
<div class="form-group"> <div class="form-group">
<input type="hidden" name="auth_method" value="LOCAL"> <input type="hidden" name="auth_method" value="LOCAL">
</div> </div>
@ -91,25 +83,33 @@
<!-- /.col --> <!-- /.col -->
</div> </div>
</form> </form>
{% if google_enabled %} {% if SETTING.get('google_oauth_enabled') or SETTING.get('github_oauth_enabled') %}
<a href="{{ url_for('google_login') }}">Google oauth login</a> <div class="social-auth-links text-center">
<p>- OR -</p>
{% if SETTING.get('github_oauth_enabled') %}
<a href="{{ url_for('github_login') }}" class="btn btn-block btn-social btn-github btn-flat"><i class="fa fa-github"></i> Sign in using
Github</a>
{% endif %}
{% if SETTING.get('google_oauth_enabled') %}
<a href="{{ url_for('google_login') }}" class="btn btn-block btn-social btn-google btn-flat"><i class="fa fa-google-plus"></i> Sign in using
Google</a>
{% endif %}
</div>
{% endif %} {% endif %}
{% if saml_enabled %} {% if saml_enabled %}
<br>
<a href="{{ url_for('saml_login') }}">SAML login</a> <a href="{{ url_for('saml_login') }}">SAML login</a>
{% endif %} {% endif %}
{% if github_enabled %}
<br> {% if SETTING.get('signup_enabled') %}
<a href="{{ url_for('github_login') }}">Github oauth login</a>
{% endif %}
{% if signup_enabled %}
<br> <br>
<a href="{{ url_for('register') }}" class="text-center">Create an account </a> <a href="{{ url_for('register') }}" class="text-center">Create an account </a>
{% endif %} {% endif %}
</div> </div>
<!-- /.login-box-body --> <!-- /.login-box-body -->
<div class="login-box-footer"> <div class="login-box-footer">
<center><p>2018 &copy; Khanh Ngo</p></center> <center><p>Powered by <a href="https://github.com/ngoduykhanh/PowerDNS-Admin">PowerDNS-Admin</a></p></center>
</div> </div>
</div> </div>
<!-- /.login-box --> <!-- /.login-box -->
@ -117,6 +117,10 @@
{% assets "js_login" -%} {% assets "js_login" -%}
<script type="text/javascript" src="{{ ASSET_URL }}"></script> <script type="text/javascript" src="{{ ASSET_URL }}"></script>
{%- endassets %} {%- endassets %}
{% assets "js_validation" -%}
<script type="text/javascript" src="{{ ASSET_URL }}"></script>
{%- endassets %}
<script> <script>
$(function () { $(function () {
$('input').iCheck({ $('input').iCheck({

View file

@ -12,7 +12,7 @@
<article> <article>
<h1>We&rsquo;ll be back soon!</h1> <h1>We&rsquo;ll be back soon!</h1>
<div> <div>
<p>Sorry for the inconvenience but we&rsquo;re performing some maintenance at the moment. If you need to you can always <a href="mailto:ngokhanhit@gmail.com">contact us</a>, otherwise we&rsquo;ll be back online shortly!</p> <p>Sorry for the inconvenience but we&rsquo;re performing some maintenance at the moment. Please contact the System Administrator if you need more information</a>, otherwise we&rsquo;ll be back online shortly!</p>
<p>&mdash; Team</p> <p>&mdash; Team</p>
</div> </div>
</article> </article>

View file

@ -1,96 +1,98 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>DNS Control Panel - Register</title> <title>Register - {{ SITE_NAME }}</title>
<!-- Tell the browser to be responsive to screen width --> <!-- Tell the browser to be responsive to screen width -->
<meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" name="viewport"> <meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" name="viewport">
{% assets "css_login" -%} {% assets "css_login" -%}
<link rel="stylesheet" href="{{ ASSET_URL }}"> <link rel="stylesheet" href="{{ ASSET_URL }}">
{%- endassets %} {%- endassets %}
<!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries --> <!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!-- WARNING: Respond.js doesn't work if you view the page via file:// --> <!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
<!--[if lt IE 9]> <!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/html5shiv/3.7.3/html5shiv.min.js"></script> <script src="https://oss.maxcdn.com/html5shiv/3.7.3/html5shiv.min.js"></script>
<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script> <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
<![endif]--> <![endif]-->
</head> </head>
<body class="hold-transition register-page"> <body class="hold-transition register-page">
<div class="register-box"> <div class="register-box">
<div class="register-logo"> <div class="register-logo">
<a href="{{ url_for('index') }}"><b>PowerDNS</b>-Admin</a> <a href="{{ url_for('index') }}"><b>PowerDNS</b>-Admin</a>
</div> </div>
<div class="register-box-body"> <div class="register-box-body">
{% if error %} {% if error %}
<div class="alert alert-danger alert-dismissible"> <div class="alert alert-danger alert-dismissible">
<button type="button" class="close" data-dismiss="alert" <button type="button" class="close" data-dismiss="alert"
aria-hidden="true">&times;</button> aria-hidden="true">&times;</button>
{{ error }} {{ error }}
</div> </div>
{% endif %} {% endif %}
<p class="login-box-msg">Enter your personal details below</p> <p class="login-box-msg">Enter your personal details below</p>
<form action="{{ url_for('login') }}" method="post"> <form action="{{ url_for('login') }}" method="post" data-toggle="validator">
<div class="form-group has-feedback"> <div class="form-group has-feedback">
<input type="text" class="form-control" placeholder="First Name" <input type="text" class="form-control" placeholder="First Name" name="firstname" data-error="Please input your first name" required>
name="firstname"> <span <span class="glyphicon glyphicon-user form-control-feedback"></span>
class="glyphicon glyphicon-user form-control-feedback"></span> <span class="help-block with-errors"></span>
</div> </div>
<div class="form-group has-feedback"> <div class="form-group has-feedback">
<input type="text" class="form-control" placeholder="Last name" <input type="text" class="form-control" placeholder="Last name" name="lastname" data-error="Please input your last name" required>
name="lastname"> <span <span class="glyphicon glyphicon-user form-control-feedback"></span>
class="glyphicon glyphicon-user form-control-feedback"></span> <span class="help-block with-errors"></span>
</div> </div>
<div class="form-group has-feedback"> <div class="form-group has-feedback">
<input type="email" class="form-control" placeholder="Email" <input type="email" class="form-control" placeholder="Email" name="email" data-error="Please input your valid email address"
name="email"> <span pattern="^([\w-]+(?:\.[\w-]+)*)@((?:[\w-]+\.)*\w[\w-]{0,66})\.([a-z]{2,6}(?:\.[a-z]{2})?)$" required>
class="glyphicon glyphicon-envelope form-control-feedback"></span> <span class="glyphicon glyphicon-envelope form-control-feedback"></span>
</div> <span class="help-block with-errors"></span>
<p class="login-box-msg">Enter your account details below</p> </div>
<div class="form-group has-feedback"> <p class="login-box-msg">Enter your account details below</p>
<input type="text" class="form-control" placeholder="Username" <div class="form-group has-feedback">
name="username"> <span <input type="text" class="form-control" placeholder="Username" name="username" data-error="Please input your username" required>
class="glyphicon glyphicon-user form-control-feedback"></span> <span class="glyphicon glyphicon-user form-control-feedback"></span>
</div> <span class="help-block with-errors"></span>
<div class="form-group has-feedback"> </div>
<input type="password" class="form-control" placeholder="Password" <div class="form-group has-feedback">
name="password"> <span <input type="password" class="form-control" placeholder="Password" id="password" name="password" data-error="Please input your password" required>
class="glyphicon glyphicon-lock form-control-feedback"></span> <span class="glyphicon glyphicon-lock form-control-feedback"></span>
</div> </div>
<div class="form-group has-feedback"> <div class="form-group has-feedback">
<input type="password" class="form-control" <input type="password" class="form-control" placeholder="Retype password" name="rpassword" data-match="#password" data-match-error="Password confirmation does not match" required>
placeholder="Retype password" name="rpassword"> <span <span class="glyphicon glyphicon-log-in form-control-feedback"></span>
class="glyphicon glyphicon-log-in form-control-feedback"></span> <span class="help-block with-errors"></span>
</div> </div>
<div class="row"> <div class="row">
<div class="col-xs-4 pull-left"> <div class="col-xs-4 pull-left">
<button type="button" class="btn btn-flat btn-block" <button type="button" class="btn btn-flat btn-block" id="button_back">Back</button>
id="button_back">Back</button> </div>
</div> <div class="col-xs-4 pull-right">
<div class="col-xs-4 pull-right"> <button type="submit" class="btn btn-flat btn-primary btn-block">Register</button>
<button type="submit" class="btn btn-flat btn-primary btn-block">Register</button> </div>
</div> <!-- /.col -->
<!-- /.col --> </div>
</div> </form>
</form> </div>
</div> <!-- /.form-box -->
<!-- /.form-box --> <div class="login-box-footer">
<div class="login-box-footer"> <center><p>Powered by <a href="https://github.com/ngoduykhanh/PowerDNS-Admin">PowerDNS-Admin</a></p></center>
<center><p>2018 &copy; Khanh Ngo</p></center> </div>
</div> </div>
</div> <!-- /.login-box -->
<!-- /.login-box -->
{% assets "js_login" -%}
{% assets "js_login" -%} <script type="text/javascript" src="{{ ASSET_URL }}"></script>
<script type="text/javascript" src="{{ ASSET_URL }}"></script> {%- endassets %}
{%- endassets %} {% assets "js_validation" -%}
<script> <script type="text/javascript" src="{{ ASSET_URL }}"></script>
$(function () { {%- endassets %}
$('#button_back').click(function(){ <script>
window.location.href='{{ url_for('login') }}'; $(function () {
}) $('#button_back').click(function(){
}); window.location.href='{{ url_for('login') }}';
</script> })
</body> });
</html> </script>
</body>
</html>

View file

@ -1,5 +1,6 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block title %}<title>DNS Control Panel - Templates</title>{% endblock %} {% set active_page = "admin_domain_template" %}
{% block title %}<title>Templates - {{ SITE_NAME }}</title>{% endblock %}
{% block dashboard_stat %} {% block dashboard_stat %}
<!-- Content Header (Page header) --> <!-- Content Header (Page header) -->

View file

@ -1,5 +1,6 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block title %}<title>DNS Control Panel - Create Template</title>{% endblock %} {% set active_page = "admin_domain_template" %}
{% block title %}<title>Create Template - {{ SITE_NAME }}</title>{% endblock %}
{% block dashboard_stat %} {% block dashboard_stat %}
<!-- Content Header (Page header) --> <!-- Content Header (Page header) -->

View file

@ -1,5 +1,6 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block title %}<title>DNS Control Panel - Edit Template</title>{% endblock %} {% set active_page = "admin_domain_template" %}
{% block title %}<title>Edit Template - {{ SITE_NAME }}</title>{% endblock %}
{% block dashboard_stat %} {% block dashboard_stat %}
<section class="content-header"> <section class="content-header">
@ -102,14 +103,14 @@
"ordering" : true, "ordering" : true,
"info" : true, "info" : true,
"autoWidth" : false, "autoWidth" : false,
{% if default_record_table_size_setting in ['5','15','20'] %} {% if SETTING.get('default_record_table_size') in ['5','15','20'] %}
"lengthMenu": [ [5, 15, 20, -1], "lengthMenu": [ [5, 15, 20, -1],
[5, 15, 20, "All"]], [5, 15, 20, "All"]],
{% else %} {% else %}
"lengthMenu": [ [5, 15, 20, {{ default_record_table_size_setting }}, -1], "lengthMenu": [ [5, 15, 20, {{ SETTING.get('default_record_table_size') }}, -1],
[5, 15, 20, {{ default_record_table_size_setting }}, "All"]], [5, 15, 20, {{ SETTING.get('default_record_table_size') }}, "All"]],
{% endif %} {% endif %}
"pageLength": {{ default_record_table_size_setting }}, "pageLength": {{ SETTING.get('default_record_table_size') }},
"language": { "language": {
"lengthMenu": " _MENU_ records" "lengthMenu": " _MENU_ records"
}, },
@ -223,7 +224,7 @@
nNew = false; nNew = false;
}); });
{% if record_helper_setting %} {% if SETTING.get('record_helper') %}
//handle wacky record types //handle wacky record types
$(document.body).on("focus", "#current_edit_record_data", function (e) { $(document.body).on("focus", "#current_edit_record_data", function (e) {
var record_type = $(this).parents("tr").find('#record_type').val(); var record_type = $(this).parents("tr").find('#record_type').val();

View file

@ -1,5 +1,5 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block title %}<title>DNS Control Panel - My Profile</title>{% endblock %} {% block title %}<title>My Profile - {{ SITE_NAME }}</title>{% endblock %}
{% block dashboard_stat %} {% block dashboard_stat %}
<!-- Content Header (Page header) --> <!-- Content Header (Page header) -->
<section class="content-header"> <section class="content-header">
@ -19,7 +19,7 @@
<div class="col-lg-12"> <div class="col-lg-12">
<div class="box box-primary"> <div class="box box-primary">
<div class="box-header with-border"> <div class="box-header with-border">
<h3 class="box-title">Edit my profile{% if external_account %} [Disabled - Authenticated externally]{% endif %}</h3> <h3 class="box-title">Edit my profile{% if session['authentication_type'] != 'LOCAL' %} [Disabled - Authenticated externally]{% endif %}</h3>
</div> </div>
<div class="box-body"> <div class="box-body">
<!-- Custom Tabs --> <!-- Custom Tabs -->
@ -29,10 +29,11 @@
Info</a></li> Info</a></li>
<li><a href="#tabs-avatar" data-toggle="tab">Change <li><a href="#tabs-avatar" data-toggle="tab">Change
Avatar</a></li> Avatar</a></li>
{% if not external_account %}<li><a href="#tabs-password" data-toggle="tab">Change {% if session['authentication_type'] == 'LOCAL' %}
Password</a></li> <li><a href="#tabs-password" data-toggle="tab">Change Password</a></li>
<li><a href="#tabs-authentication" data-toggle="tab">Authentication {% endif %}
</a></li> {% if session['authentication_type'] in ['LOCAL', 'LDAP'] %}
<li><a href="#tabs-authentication" data-toggle="tab">Authentication</a></li>
{% endif %} {% endif %}
</ul> </ul>
<div class="tab-content"> <div class="tab-content">
@ -41,18 +42,18 @@
<div class="form-group"> <div class="form-group">
<label for="firstname">First Name</label> <input type="text" <label for="firstname">First Name</label> <input type="text"
class="form-control" name="firstname" id="firstname" class="form-control" name="firstname" id="firstname"
placeholder="{{ current_user.firstname }}" {% if external_account %}disabled{% endif %}> placeholder="{{ current_user.firstname }}" {% if session['authentication_type'] != 'LOCAL' %}disabled{% endif %}>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="lastname">Last Name</label> <input type="text" <label for="lastname">Last Name</label> <input type="text"
class="form-control" name="lastname" id="lastname" class="form-control" name="lastname" id="lastname"
placeholder="{{ current_user.lastname }}" {% if external_account %}disabled{% endif %}> placeholder="{{ current_user.lastname }}" {% if session['authentication_type'] != 'LOCAL' %}disabled{% endif %}>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="email">E-mail</label> <input type="text" <label for="email">E-mail</label> <input type="text"
class="form-control" name="email" id="email" class="form-control" name="email" id="email"
placeholder="{{ current_user.email }}" {% if external_account %}disabled{% endif %}> placeholder="{{ current_user.email }}" {% if session['authentication_type'] != 'LOCAL' %}disabled{% endif %}>
</div>{% if not external_account %} </div>{% if session['authentication_type'] == 'LOCAL' %}
<div class="form-group"> <div class="form-group">
<button type="submit" class="btn btn-flat btn-primary">Submit</button> <button type="submit" class="btn btn-flat btn-primary">Submit</button>
</div>{% endif %} </div>{% endif %}
@ -70,50 +71,50 @@
else %} <img else %} <img
src="{{ current_user.email|email_to_gravatar_url(size=200) }}" src="{{ current_user.email|email_to_gravatar_url(size=200) }}"
alt="" /> {% endif %} alt="" /> {% endif %}
</div>{% if not external_account %} </div>{% if session['authentication_type'] == 'LOCAL' %}
<div> <div>
<label for="file">Select image</label> <input type="file" <label for="file">Select image</label> <input type="file"
id="file" name="file"> id="file" name="file">
</div>{% endif %} </div>{% endif %}
</div>{% if not external_account %} </div>{% if session['authentication_type'] == 'LOCAL' %}
<div> <div>
<span class="label label-danger">NOTE! </span> <span>&nbsp;Only <span class="label label-danger">NOTE! </span> <span>&nbsp;Only
supports <strong>.PNG, .JPG, .JPEG</strong>. The best size supports <strong>.PNG, .JPG, .JPEG</strong>. The best size
to use is <strong>200x200</strong>. to use is <strong>200x200</strong>.
</span> </span>
</div>{% endif %} </div>{% endif %}
</div>{% if not external_account %} </div>{% if session['authentication_type'] == 'LOCAL' %}
<div class="form-group"> <div class="form-group">
<button type="submit" class="btn btn-flat btn-primary">Submit</button> <button type="submit" class="btn btn-flat btn-primary">Submit</button>
</div>{% endif %} </div>{% endif %}
</form> </form>
</div> </div>
{% if not external_account %}<div class="tab-pane" id="tabs-password"> {% if session['authentication_type'] == 'LOCAL' %}
{% if not current_user.password %} Your account password is <div class="tab-pane" id="tabs-password">
managed via LDAP which isn't supported to change here. {% else {% if not current_user.password %}
%} Your account password is managed via LDAP which isn't supported to change here.
{% else %}
<form action="{{ user_profile }}" method="post"> <form action="{{ user_profile }}" method="post">
<div class="form-group"> <div class="form-group">
<label for="password">New Password</label> <input <label for="password">New Password</label> <input
type="password" class="form-control" name="password" type="password" class="form-control" name="password" id="newpassword"/>
id="newpassword" {% if external_account %}disabled{% endif %} />
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="rpassword">Re-type New Password</label> <input <label for="rpassword">Re-type New Password</label> <input
type="password" class="form-control" name="rpassword" type="password" class="form-control" name="rpassword" id="rpassword"/>
id="rpassword" {% if external_account %}disabled{% endif %} />
</div> </div>
<div class="form-group"> <div class="form-group">
<button type="submit" class="btn btn-flat btn-primary" {% if external_account %}disabled{% endif %}>Change <button type="submit" class="btn btn-flat btn-primary">Change Password</button>
password</button>
</div> </div>
</form> </form>
{% endif %} {% endif %}
</div> </div>
{% endif %}
<!-- {% if session['authentication_type'] in ['LOCAL', 'LDAP'] %} -->
<div class="tab-pane" id="tabs-authentication"> <div class="tab-pane" id="tabs-authentication">
<form action="{{ user_profile }}" method="post"> <form action="{{ user_profile }}" method="post">
<div class="form-group"> <div class="form-group">
<input type="checkbox" id="otp_toggle" class="otp_toggle" {% if current_user.otp_secret %}checked{% endif %} {% if external_account %}disabled{% endif %}> <input type="checkbox" id="otp_toggle" class="otp_toggle" {% if current_user.otp_secret %}checked{% endif %}>
<label for="otp_toggle">Enable Two Factor Authentication</label> <label for="otp_toggle">Enable Two Factor Authentication</label>
{% if current_user.otp_secret %} {% if current_user.otp_secret %}
<div id="token_information"> <div id="token_information">
@ -125,7 +126,8 @@
{% endif %} {% endif %}
</div> </div>
</form> </form>
</div>{% endif %} </div>
<!-- {% endif %} -->
</div> </div>
</div> </div>
</div> </div>

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
@ -18,79 +19,58 @@ from werkzeug import secure_filename
from werkzeug.security import gen_salt from werkzeug.security import gen_salt
from .models import User, Account, Domain, Record, Role, Server, History, Anonymous, Setting, DomainSetting, DomainTemplate, DomainTemplateRecord from .models import User, Account, Domain, Record, Role, Server, History, Anonymous, Setting, DomainSetting, DomainTemplate, DomainTemplateRecord
from app import app, login_manager, github, google from app import app, login_manager
from app.lib import utils from app.lib import utils
from app.oauth import github_oauth, google_oauth
from app.decorators import admin_role_required, can_access_domain, can_configure_dnssec from app.decorators import admin_role_required, can_access_domain, can_configure_dnssec
if app.config['SAML_ENABLED']: if app.config['SAML_ENABLED']:
from onelogin.saml2.auth import OneLogin_Saml2_Auth from onelogin.saml2.auth import OneLogin_Saml2_Auth
from onelogin.saml2.utils import OneLogin_Saml2_Utils from onelogin.saml2.utils import OneLogin_Saml2_Utils
google = None
github = None
logging = logger.getLogger(__name__) logging = logger.getLogger(__name__)
# FILTERS # FILTERS
app.jinja_env.filters['display_record_name'] = utils.display_record_name app.jinja_env.filters['display_record_name'] = utils.display_record_name
app.jinja_env.filters['display_master_name'] = utils.display_master_name app.jinja_env.filters['display_master_name'] = utils.display_master_name
app.jinja_env.filters['display_second_to_time'] = utils.display_time app.jinja_env.filters['display_second_to_time'] = utils.display_time
app.jinja_env.filters['email_to_gravatar_url'] = utils.email_to_gravatar_url app.jinja_env.filters['email_to_gravatar_url'] = utils.email_to_gravatar_url
# Flag for pdns v4.x.x
# TODO: Find another way to do this
PDNS_VERSION = app.config['PDNS_VERSION']
if StrictVersion(PDNS_VERSION) >= StrictVersion('4.0.0'):
NEW_SCHEMA = True
else:
NEW_SCHEMA = False
@app.context_processor @app.context_processor
def inject_fullscreen_layout_setting(): def inject_sitename():
setting_value = Setting().get('fullscreen_layout') setting = Setting().get('site_name')
return dict(fullscreen_layout_setting=strtobool(setting_value)) return dict(SITE_NAME=setting)
@app.context_processor @app.context_processor
def inject_record_helper_setting(): def inject_setting():
setting_value = Setting().get('record_helper') setting = Setting()
return dict(record_helper_setting=strtobool(setting_value)) return dict(SETTING=setting)
@app.context_processor @app.before_first_request
def inject_login_ldap_first_setting(): def register_modules():
setting_value = Setting().get('login_ldap_first') global google
return dict(login_ldap_first_setting=strtobool(setting_value)) global github
google = google_oauth()
github = github_oauth()
@app.context_processor
def inject_default_record_table_size_setting():
setting_value = Setting().get('default_record_table_size')
return dict(default_record_table_size_setting=setting_value)
@app.context_processor
def inject_default_domain_table_size_setting():
setting_value = Setting().get('default_domain_table_size')
return dict(default_domain_table_size_setting=setting_value)
@app.context_processor
def inject_auto_ptr_setting():
setting_value = Setting().get('auto_ptr')
return dict(auto_ptr_setting=strtobool(setting_value))
# START USER AUTHENTICATION HANDLER # START USER AUTHENTICATION HANDLER
@app.before_request @app.before_request
def before_request(): def before_request():
# check site maintenance mode first
maintenance = Setting().get('maintenance')
if strtobool(maintenance):
return render_template('maintenance.html')
# check if user is anonymous # check if user is anonymous
g.user = current_user g.user = current_user
login_manager.anonymous_user = Anonymous login_manager.anonymous_user = Anonymous
# check site maintenance mode
maintenance = Setting().get('maintenance')
if maintenance and current_user.is_authenticated and current_user.role.name != 'Administrator':
return render_template('maintenance.html')
@login_manager.user_loader @login_manager.user_loader
def load_user(id): def load_user(id):
@ -164,8 +144,7 @@ def error(code, msg=None):
@app.route('/register', methods=['GET']) @app.route('/register', methods=['GET'])
def register(): def register():
SIGNUP_ENABLED = app.config['SIGNUP_ENABLED'] if Setting().get('signup_enabled'):
if SIGNUP_ENABLED:
return render_template('register.html') return render_template('register.html')
else: else:
return render_template('errors/404.html'), 404 return render_template('errors/404.html'), 404
@ -173,16 +152,21 @@ def register():
@app.route('/google/login') @app.route('/google/login')
def google_login(): def google_login():
if not app.config.get('GOOGLE_OAUTH_ENABLE'): if not Setting().get('google_oauth_enabled') or google is None:
logging.error('Google OAuth is disabled or you have not yet reloaded the pda application after enabling.')
return abort(400) return abort(400)
return google.authorize(callback=url_for('authorized', _external=True)) else:
return google.authorize(callback=url_for('google_authorized', _external=True))
@app.route('/github/login') @app.route('/github/login')
def github_login(): def github_login():
if not app.config.get('GITHUB_OAUTH_ENABLE'): if not Setting().get('github_oauth_enabled') or github is None:
logging.error('Github OAuth is disabled or you have not yet reloaded the pda application after enabling.')
return abort(400) return abort(400)
return github.authorize(callback=url_for('authorized', _external=True)) else:
return github.authorize(callback=url_for('github_authorized', _external=True))
@app.route('/saml/login') @app.route('/saml/login')
def saml_login(): def saml_login():
@ -193,6 +177,7 @@ def saml_login():
redirect_url=OneLogin_Saml2_Utils.get_self_url(req) + url_for('saml_authorized') redirect_url=OneLogin_Saml2_Utils.get_self_url(req) + url_for('saml_authorized')
return redirect(auth.login(return_to=redirect_url)) return redirect(auth.login(return_to=redirect_url))
@app.route('/saml/metadata') @app.route('/saml/metadata')
def saml_metadata(): def saml_metadata():
if not app.config.get('SAML_ENABLED'): if not app.config.get('SAML_ENABLED'):
@ -210,6 +195,7 @@ def saml_metadata():
resp = make_response(errors.join(', '), 500) resp = make_response(errors.join(', '), 500)
return resp return resp
@app.route('/saml/authorized', methods=['GET', 'POST']) @app.route('/saml/authorized', methods=['GET', 'POST'])
def saml_authorized(): def saml_authorized():
errors = [] errors = []
@ -288,21 +274,17 @@ def saml_authorized():
history.add() history.add()
user.plain_text_password = None user.plain_text_password = None
user.update_profile() user.update_profile()
session['external_auth'] = True session['authentication_type'] = 'SAML'
login_user(user, remember=False) login_user(user, remember=False)
return redirect(url_for('index')) return redirect(url_for('index'))
else: else:
return render_template('errors/SAML.html', errors=errors) return render_template('errors/SAML.html', errors=errors)
@app.route('/login', methods=['GET', 'POST']) @app.route('/login', methods=['GET', 'POST'])
@login_manager.unauthorized_handler @login_manager.unauthorized_handler
def login(): def login():
LOGIN_TITLE = app.config['LOGIN_TITLE'] if 'LOGIN_TITLE' in app.config.keys() else '' LOGIN_TITLE = app.config['LOGIN_TITLE'] if 'LOGIN_TITLE' in app.config.keys() else ''
BASIC_ENABLED = app.config['BASIC_ENABLED']
SIGNUP_ENABLED = app.config['SIGNUP_ENABLED']
LDAP_ENABLED = app.config.get('LDAP_ENABLED')
GITHUB_ENABLE = app.config.get('GITHUB_OAUTH_ENABLE')
GOOGLE_ENABLE = app.config.get('GOOGLE_OAUTH_ENABLE')
SAML_ENABLED = app.config.get('SAML_ENABLED') SAML_ENABLED = app.config.get('SAML_ENABLED')
if g.user is not None and current_user.is_authenticated: if g.user is not None and current_user.is_authenticated:
@ -315,7 +297,6 @@ def login():
email = user_data['email'] email = user_data['email']
user = User.query.filter_by(username=email).first() user = User.query.filter_by(username=email).first()
if not user: if not user:
# create user
user = User(username=email, user = User(username=email,
firstname=first_name, firstname=first_name,
lastname=surname, lastname=surname,
@ -329,18 +310,23 @@ def login():
session['user_id'] = user.id session['user_id'] = user.id
login_user(user, remember = False) login_user(user, remember = False)
session['external_auth'] = True session['authentication_type'] = 'OAuth'
return redirect(url_for('index')) return redirect(url_for('index'))
if 'github_token' in session: if 'github_token' in session:
me = github.get('user') me = github.get('user').data
user_info = me.data
user = User.query.filter_by(username=user_info['name']).first() github_username = me['login']
github_name = me['name']
github_email = me['email']
user = User.query.filter_by(username=github_username).first()
if not user: if not user:
# create user user = User(username=github_username,
user = User(username=user_info['name'],
plain_text_password=None, plain_text_password=None,
email=user_info['email']) firstname=github_name,
lastname='',
email=github_email)
result = user.create_local_user() result = user.create_local_user()
if not result['status']: if not result['status']:
@ -348,18 +334,12 @@ def login():
return redirect(url_for('login')) return redirect(url_for('login'))
session['user_id'] = user.id session['user_id'] = user.id
session['external_auth'] = True session['authentication_type'] = 'OAuth'
login_user(user, remember = False) login_user(user, remember = False)
return redirect(url_for('index')) return redirect(url_for('index'))
if request.method == 'GET': if request.method == 'GET':
return render_template('login.html', github_enabled=GITHUB_ENABLE, return render_template('login.html', saml_enabled=SAML_ENABLED)
google_enabled=GOOGLE_ENABLE,
saml_enabled=SAML_ENABLED,
ldap_enabled=LDAP_ENABLED,
login_title=LOGIN_TITLE,
basic_enabled=BASIC_ENABLED,
signup_enabled=SIGNUP_ENABLED)
# process login # process login
username = request.form['username'] username = request.form['username']
@ -373,8 +353,7 @@ def login():
email = request.form.get('email') email = request.form.get('email')
rpassword = request.form.get('rpassword') rpassword = request.form.get('rpassword')
if auth_method != 'LOCAL': session['authentication_type'] = 'LDAP' if auth_method != 'LOCAL' else 'LOCAL'
session['external_auth'] = True
if None in [firstname, lastname, email]: if None in [firstname, lastname, email]:
#login case #login case
@ -387,46 +366,18 @@ def login():
try: try:
auth = user.is_validate(method=auth_method, src_ip=request.remote_addr) auth = user.is_validate(method=auth_method, src_ip=request.remote_addr)
if auth == False: if auth == False:
return render_template('login.html', error='Invalid credentials', return render_template('login.html', saml_enabled=SAML_ENABLED, error='Invalid credentials')
github_enabled=GITHUB_ENABLE,
google_enabled=GOOGLE_ENABLE,
saml_enabled=SAML_ENABLED,
ldap_enabled=LDAP_ENABLED,
login_title=LOGIN_TITLE,
basic_enabled=BASIC_ENABLED,
signup_enabled=SIGNUP_ENABLED)
except Exception as e: except Exception as e:
return render_template('login.html', error=e, return render_template('login.html', saml_enabled=SAML_ENABLED, error=e)
github_enabled=GITHUB_ENABLE,
google_enabled=GOOGLE_ENABLE,
saml_enabled=SAML_ENABLED,
ldap_enabled=LDAP_ENABLED,
login_title=LOGIN_TITLE,
basic_enabled=BASIC_ENABLED,
signup_enabled=SIGNUP_ENABLED)
# check if user enabled OPT authentication # check if user enabled OPT authentication
if user.otp_secret: if user.otp_secret:
if otp_token and otp_token.isdigit(): if otp_token and otp_token.isdigit():
good_token = user.verify_totp(otp_token) good_token = user.verify_totp(otp_token)
if not good_token: if not good_token:
return render_template('login.html', error='Invalid credentials', return render_template('login.html', saml_enabled=SAML_ENABLED, error='Invalid credentials')
github_enabled=GITHUB_ENABLE,
google_enabled=GOOGLE_ENABLE,
saml_enabled=SAML_ENABLED,
ldap_enabled=LDAP_ENABLED,
login_title=LOGIN_TITLE,
basic_enabled=BASIC_ENABLED,
signup_enabled=SIGNUP_ENABLED)
else: else:
return render_template('login.html', error='Token required', return render_template('login.html', saml_enabled=SAML_ENABLED, error='Token required')
github_enabled=GITHUB_ENABLE,
google_enabled=GOOGLE_ENABLE,
saml_enabled=SAML_ENABLED,
ldap_enabled=LDAP_ENABLED,
login_title=LOGIN_TITLE,
basic_enabled=BASIC_ENABLED,
signup_enabled=SIGNUP_ENABLED)
login_user(user, remember = remember_me) login_user(user, remember = remember_me)
return redirect(request.args.get('next') or url_for('index')) return redirect(request.args.get('next') or url_for('index'))
@ -437,35 +388,29 @@ def login():
# registration case # registration case
user = User(username=username, plain_text_password=password, firstname=firstname, lastname=lastname, email=email) user = User(username=username, plain_text_password=password, firstname=firstname, lastname=lastname, email=email)
# TODO: Move this into the JavaScript
# validate password and password confirmation
if password != rpassword: if password != rpassword:
error = "Password confirmation does not match" error = "Password confirmation does not match"
return render_template('register.html', error=error) return render_template('register.html', error=error)
try: try:
result = user.create_local_user() result = user.create_local_user()
if result == True: if result and result['status']:
return render_template('login.html', username=username, password=password, return render_template('login.html', saml_enabled=SAML_ENABLED, username=username, password=password)
github_enabled=GITHUB_ENABLE,
google_enabled=GOOGLE_ENABLE,
saml_enabled=SAML_ENABLED,
ldap_enabled=LDAP_ENABLED,
login_title=LOGIN_TITLE,
basic_enabled=BASIC_ENABLED,
signup_enabled=SIGNUP_ENABLED)
else: else:
return render_template('register.html', error=result['msg']) return render_template('register.html', error=result['msg'])
except Exception as e: except Exception as e:
return render_template('register.html', error=e) return render_template('register.html', error=e)
def clear_session(): def clear_session():
session.pop('user_id', None) session.pop('user_id', None)
session.pop('github_token', None) session.pop('github_token', None)
session.pop('google_token', None) session.pop('google_token', None)
session.pop('authentication_type', None)
session.clear() session.clear()
logout_user() logout_user()
@app.route('/logout') @app.route('/logout')
def logout(): def logout():
if app.config.get('SAML_ENABLED') and 'samlSessionIndex' in session and app.config.get('SAML_LOGOUT'): if app.config.get('SAML_ENABLED') and 'samlSessionIndex' in session and app.config.get('SAML_LOGOUT'):
@ -474,13 +419,14 @@ def logout():
if app.config.get('SAML_LOGOUT_URL'): if app.config.get('SAML_LOGOUT_URL'):
return redirect(auth.logout(name_id_format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", return redirect(auth.logout(name_id_format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress",
return_to = app.config.get('SAML_LOGOUT_URL'), return_to = app.config.get('SAML_LOGOUT_URL'),
session_index = session['samlSessionIndex'], name_id=session['samlNameId'])) session_index = session['samlSessionIndex'], name_id=session['samlNameId']))
return redirect(auth.logout(name_id_format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", return redirect(auth.logout(name_id_format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress",
session_index = session['samlSessionIndex'], session_index = session['samlSessionIndex'],
name_id=session['samlNameId'])) name_id=session['samlNameId']))
clear_session() clear_session()
return redirect(url_for('login')) return redirect(url_for('login'))
@app.route('/saml/sls') @app.route('/saml/sls')
def saml_logout(): def saml_logout():
req = utils.prepare_flask_request(request) req = utils.prepare_flask_request(request)
@ -498,10 +444,15 @@ def saml_logout():
else: else:
return render_template('errors/SAML.html', errors=errors) return render_template('errors/SAML.html', errors=errors)
@app.route('/dashboard', methods=['GET', 'POST']) @app.route('/dashboard', methods=['GET', 'POST'])
@login_required @login_required
def dashboard(): def dashboard():
if not app.config.get('BG_DOMAIN_UPDATES'): if not Setting().get('pdns_api_url') or not Setting().get('pdns_api_key') or not Setting().get('pdns_version'):
return redirect(url_for('admin_setting_pdns'))
BG_DOMAIN_UPDATE = Setting().get('bg_domain_updates')
if not BG_DOMAIN_UPDATE:
logging.debug('Update domains in foreground') logging.debug('Update domains in foreground')
d = Domain().update() d = Domain().update()
else: else:
@ -519,7 +470,7 @@ def dashboard():
else: else:
uptime = 0 uptime = 0
return render_template('dashboard.html', domain_count=domain_count, users=users, history_number=history_number, uptime=uptime, histories=history, dnssec_adm_only=app.config['DNSSEC_ADMINS_ONLY'], pdns_version=app.config['PDNS_VERSION'], show_bg_domain_button=app.config['BG_DOMAIN_UPDATES']) return render_template('dashboard.html', domain_count=domain_count, users=users, history_number=history_number, uptime=uptime, histories=history, show_bg_domain_button=BG_DOMAIN_UPDATE)
@app.route('/dashboard-domains', methods=['GET']) @app.route('/dashboard-domains', methods=['GET'])
@ -621,31 +572,33 @@ def domain(domain_name):
# can not get any record, API server might be down # can not get any record, API server might be down
return redirect(url_for('error', code=500)) return redirect(url_for('error', code=500))
quick_edit = strtobool(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 NEW_SCHEMA: 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['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, pdns_version=app.config['PDNS_VERSION']) return render_template('domain.html', domain=domain, records=records, editable_records=editable_records, quick_edit=quick_edit)
@app.route('/admin/domain/add', methods=['GET', 'POST']) @app.route('/admin/domain/add', methods=['GET', 'POST'])
@ -1036,16 +989,16 @@ def create_template_from_zone():
if zone_info: if zone_info:
jrecords = zone_info['records'] jrecords = zone_info['records']
if NEW_SCHEMA: 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)
@ -1071,14 +1024,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))
@ -1140,6 +1094,9 @@ def delete_template(template):
@login_required @login_required
@admin_role_required @admin_role_required
def admin(): def admin():
if not Setting().get('pdns_api_url') or not Setting().get('pdns_api_key') or not Setting().get('pdns_version'):
return redirect(url_for('admin_setting_pdns'))
domains = Domain.query.all() domains = Domain.query.all()
users = User.query.all() users = User.query.all()
@ -1374,39 +1331,19 @@ def admin_history():
return render_template('admin_history.html', histories=histories) return render_template('admin_history.html', histories=histories)
@app.route('/admin/settings', methods=['GET']) @app.route('/admin/setting/basic', methods=['GET'])
@login_required @login_required
@admin_role_required @admin_role_required
def admin_settings(): def admin_setting_basic():
if request.method == 'GET': if request.method == 'GET':
# start with a copy of the setting defaults (ignore maintenance setting) settings = Setting.query.filter(Setting.view=='basic').all()
settings = Setting.defaults.copy() return render_template('admin_setting_basic.html', settings=settings)
settings.pop('maintenance', None)
# update settings info with any customizations
for s in settings:
value = Setting().get(s)
if value is not None:
settings[s] = value
return render_template('admin_settings.html', settings=settings)
@app.route('/admin/setting/<path:setting>/toggle', methods=['POST']) @app.route('/admin/setting/basic/<path:setting>/edit', methods=['POST'])
@login_required @login_required
@admin_role_required @admin_role_required
def admin_settings_toggle(setting): def admin_setting_basic_edit(setting):
result = Setting().toggle(setting)
if (result):
return make_response(jsonify( { 'status': 'ok', 'msg': 'Toggled setting successfully.' } ), 200)
else:
return make_response(jsonify( { 'status': 'error', 'msg': 'Unable to toggle setting.' } ), 500)
@app.route('/admin/setting/<path:setting>/edit', methods=['POST'])
@login_required
@admin_role_required
def admin_settings_edit(setting):
jdata = request.json jdata = request.json
new_value = jdata['value'] new_value = jdata['value']
result = Setting().set(setting, new_value) result = Setting().set(setting, new_value)
@ -1417,48 +1354,168 @@ def admin_settings_edit(setting):
return make_response(jsonify( { 'status': 'error', 'msg': 'Unable to toggle setting.' } ), 500) return make_response(jsonify( { 'status': 'error', 'msg': 'Unable to toggle setting.' } ), 500)
@app.route('/admin/setting/basic/<path:setting>/toggle', methods=['POST'])
@login_required
@admin_role_required
def admin_setting_basic_toggle(setting):
result = Setting().toggle(setting)
if (result):
return make_response(jsonify( { 'status': 'ok', 'msg': 'Toggled setting successfully.' } ), 200)
else:
return make_response(jsonify( { 'status': 'error', 'msg': 'Unable to toggle setting.' } ), 500)
@app.route('/admin/setting/pdns', methods=['GET', 'POST'])
@login_required
@admin_role_required
def admin_setting_pdns():
if request.method == 'GET':
pdns_api_url = Setting().get('pdns_api_url')
pdns_api_key = Setting().get('pdns_api_key')
pdns_version = Setting().get('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)
elif request.method == 'POST':
pdns_api_url = request.form.get('pdns_api_url')
pdns_api_key = request.form.get('pdns_api_key')
pdns_version = request.form.get('pdns_version')
Setting().set('pdns_api_url', pdns_api_url)
Setting().set('pdns_api_key', pdns_api_key)
Setting().set('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'])
@login_required
@admin_role_required
def admin_setting_authentication():
if request.method == 'GET':
return render_template('admin_setting_authentication.html')
elif request.method == 'POST':
conf_type = request.form.get('config_tab')
result = None
if conf_type == 'general':
local_db_enabled = True if request.form.get('local_db_enabled') else False
signup_enabled = True if request.form.get('signup_enabled', ) else False
if not local_db_enabled and not Setting().get('ldap_enabled'):
result = {'status': False, 'msg': 'Local DB and LDAP Authentication can not be disabled at the same time.'}
else:
Setting().set('local_db_enabled', local_db_enabled)
Setting().set('signup_enabled', signup_enabled)
result = {'status': True, 'msg': 'Saved successfully'}
elif conf_type == 'ldap':
ldap_enabled = True if request.form.get('ldap_enabled') else False
if not ldap_enabled and not Setting().get('local_db_enabled'):
result = {'status': False, 'msg': 'Local DB and LDAP Authentication can not be disabled at the same time.'}
else:
Setting().set('ldap_enabled', ldap_enabled)
Setting().set('ldap_type', request.form.get('ldap_type'))
Setting().set('ldap_uri', request.form.get('ldap_uri'))
Setting().set('ldap_base_dn', request.form.get('ldap_base_dn'))
Setting().set('ldap_admin_username', request.form.get('ldap_admin_username'))
Setting().set('ldap_admin_password', request.form.get('ldap_admin_password'))
Setting().set('ldap_filter_basic', request.form.get('ldap_filter_basic'))
Setting().set('ldap_filter_username', request.form.get('ldap_filter_username'))
Setting().set('ldap_sg_enabled', True if request.form.get('ldap_sg_enabled')=='ON' else False)
Setting().set('ldap_admin_group', request.form.get('ldap_admin_group'))
Setting().set('ldap_user_group', request.form.get('ldap_user_group'))
result = {'status': True, 'msg': 'Saved successfully'}
elif conf_type == 'google':
Setting().set('google_oauth_enabled', True if request.form.get('google_oauth_enabled') else False)
Setting().set('google_oauth_client_id', request.form.get('google_oauth_client_id'))
Setting().set('google_oauth_client_secret', request.form.get('google_oauth_client_secret'))
Setting().set('google_token_url', request.form.get('google_token_url'))
Setting().set('google_token_params', request.form.get('google_token_params'))
Setting().set('google_authorize_url', request.form.get('google_authorize_url'))
Setting().set('google_base_url', request.form.get('google_base_url'))
result = {'status': True, 'msg': 'Saved successfully. Please reload PDA to take effect.'}
elif conf_type == 'github':
Setting().set('github_oauth_enabled', True if request.form.get('github_oauth_enabled') else False)
Setting().set('github_oauth_key', request.form.get('github_oauth_key'))
Setting().set('github_oauth_secret', request.form.get('github_oauth_secret'))
Setting().set('github_oauth_scope', request.form.get('github_oauth_scope'))
Setting().set('github_oauth_api_url', request.form.get('github_oauth_api_url'))
Setting().set('github_oauth_token_url', request.form.get('github_oauth_token_url'))
Setting().set('github_oauth_authorize_url', request.form.get('github_oauth_authorize_url'))
result = {'status': True, 'msg': 'Saved successfully. Please reload PDA to take effect.'}
else:
return abort(400)
return render_template('admin_setting_authentication.html', result=result)
@app.route('/user/profile', methods=['GET', 'POST']) @app.route('/user/profile', methods=['GET', 'POST'])
@login_required @login_required
def user_profile(): def user_profile():
external_account = False if request.method == 'GET':
if 'external_auth' in session: return render_template('user_profile.html')
external_account = session['external_auth']
if request.method == 'GET' or external_account:
return render_template('user_profile.html', external_account=external_account)
if request.method == 'POST': if request.method == 'POST':
# get new profile info if session['authentication_type'] == 'LOCAL':
firstname = request.form['firstname'] if 'firstname' in request.form else '' firstname = request.form['firstname'] if 'firstname' in request.form else ''
lastname = request.form['lastname'] if 'lastname' in request.form else '' lastname = request.form['lastname'] if 'lastname' in request.form else ''
email = request.form['email'] if 'email' in request.form else '' email = request.form['email'] if 'email' in request.form else ''
new_password = request.form['password'] if 'password' in request.form else '' new_password = request.form['password'] if 'password' in request.form else ''
else:
firstname = lastname = email = new_password = ''
logging.warning('Authenticated externally. User {0} information will not allowed to update the profile'.format(current_user.username))
# json data
if request.data: if request.data:
jdata = request.json jdata = request.json
data = jdata['data'] data = jdata['data']
if jdata['action'] == 'enable_otp': if jdata['action'] == 'enable_otp':
enable_otp = data['enable_otp'] if session['authentication_type'] in ['LOCAL', 'LDAP']:
user = User(username=current_user.username) enable_otp = data['enable_otp']
user.update_profile(enable_otp=enable_otp) user = User(username=current_user.username)
return make_response(jsonify( { 'status': 'ok', 'msg': 'Change OTP Authentication successfully. Status: {0}'.format(enable_otp) } ), 200) user.update_profile(enable_otp=enable_otp)
return make_response(jsonify( { 'status': 'ok', 'msg': 'Change OTP Authentication successfully. Status: {0}'.format(enable_otp) } ), 200)
else:
return make_response(jsonify( { 'status': 'error', 'msg': 'User {0} is externally. You are not allowed to update the OTP'.format(current_user.username) } ), 400)
# get new avatar # get new avatar
save_file_name = None save_file_name = None
if 'file' in request.files: if 'file' in request.files:
file = request.files['file'] if session['authentication_type'] in ['LOCAL', 'LDAP']:
if file: file = request.files['file']
filename = secure_filename(file.filename) if file:
file_extension = filename.rsplit('.', 1)[1] filename = secure_filename(file.filename)
file_extension = filename.rsplit('.', 1)[1]
if file_extension.lower() in ['jpg', 'jpeg', 'png']: if file_extension.lower() in ['jpg', 'jpeg', 'png']:
save_file_name = current_user.username + '.' + file_extension save_file_name = current_user.username + '.' + file_extension
file.save(os.path.join(app.config['UPLOAD_DIR'], 'avatar', save_file_name)) file.save(os.path.join(app.config['UPLOAD_DIR'], 'avatar', save_file_name))
else:
logging.error('Authenticated externally. User {0} is not allowed to update the avatar')
abort(400)
# update user profile
user = User(username=current_user.username, plain_text_password=new_password, firstname=firstname, lastname=lastname, email=email, avatar=save_file_name, reload_info=False) user = User(username=current_user.username, plain_text_password=new_password, firstname=firstname, lastname=lastname, email=email, avatar=save_file_name, reload_info=False)
user.update_profile() user.update_profile()
return render_template('user_profile.html', external_account=external_account) return render_template('user_profile.html')
@app.route('/user/avatar/<path:filename>') @app.route('/user/avatar/<path:filename>')

View file

@ -6,84 +6,30 @@ WTF_CSRF_ENABLED = True
SECRET_KEY = 'We are the world' SECRET_KEY = 'We are the world'
BIND_ADDRESS = '127.0.0.1' BIND_ADDRESS = '127.0.0.1'
PORT = 9191 PORT = 9191
LOGIN_TITLE = "PDNS"
# TIMEOUT - for large zones # TIMEOUT - for large zones
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
# LDAP CONFIG # DATBASE - MySQL
LDAP_ENABLED = False SQLALCHEMY_DATABASE_URI = 'mysql://'+SQLA_DB_USER+':'+SQLA_DB_PASSWORD+'@'+SQLA_DB_HOST+'/'+SQLA_DB_NAME
LDAP_TYPE = 'ldap'
LDAP_URI = 'ldaps://your-ldap-server:636'
LDAP_ADMIN_USERNAME = 'cn=admin,dc=mydomain,dc=com'
LDAP_ADMIN_PASSWORD = 'password'
LDAP_SEARCH_BASE = 'dc=mydomain,dc=com'
# Additional options only if LDAP_TYPE=ldap # DATABSE - SQLite
LDAP_USERNAMEFIELD = 'uid' # SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(basedir, 'pdns.db')
LDAP_FILTER = '(objectClass=inetorgperson)'
# enable LDAP_GROUP_SECURITY to allow Admin and User roles based on LDAP groups
LDAP_GROUP_SECURITY = False # True or False
LDAP_ADMIN_GROUP = 'cn=sysops,dc=mydomain,dc=com'
LDAP_USER_GROUP = 'cn=user,dc=mydomain,dc=com'
## AD CONFIG
#LDAP_TYPE = 'ad'
#LDAP_URI = 'ldaps://your-ad-server:636'
#LDAP_USERNAME = 'cn=dnsuser,ou=Users,dc=domain,dc=local'
#LDAP_PASSWORD = 'dnsuser'
#LDAP_SEARCH_BASE = 'dc=domain,dc=local'
## You may prefer 'userPrincipalName' instead
#LDAP_USERNAMEFIELD = 'sAMAccountName'
## AD Group that you would like to have accesss to web app
#LDAP_FILTER = 'memberof=cn=DNS_users,ou=Groups,dc=domain,dc=local'
# Github Oauth
GITHUB_OAUTH_ENABLE = False
GITHUB_OAUTH_KEY = ''
GITHUB_OAUTH_SECRET = ''
GITHUB_OAUTH_SCOPE = 'email'
GITHUB_OAUTH_URL = 'http://127.0.0.1:9191/api/v3/'
GITHUB_OAUTH_TOKEN = 'http://127.0.0.1:9191/oauth/token'
GITHUB_OAUTH_AUTHORIZE = 'http://127.0.0.1:9191/oauth/authorize'
# Google OAuth
GOOGLE_OAUTH_ENABLE = False
GOOGLE_OAUTH_CLIENT_ID = ' '
GOOGLE_OAUTH_CLIENT_SECRET = ' '
GOOGLE_REDIRECT_URI = '/user/authorized'
GOOGLE_TOKEN_URL = 'https://accounts.google.com/o/oauth2/token'
GOOGLE_TOKEN_PARAMS = {
'scope': 'email profile'
}
GOOGLE_AUTHORIZE_URL='https://accounts.google.com/o/oauth2/auth'
GOOGLE_BASE_URL='https://www.googleapis.com/oauth2/v1/'
# SAML Authnetication # SAML Authnetication
SAML_ENABLED = False SAML_ENABLED = False
@ -157,26 +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'
#Default Auth
BASIC_ENABLED = True
SIGNUP_ENABLED = True
# POWERDNS CONFIG
PDNS_STATS_URL = 'http://172.16.214.131:8081/'
PDNS_API_KEY = 'you never know'
PDNS_VERSION = '4.1.1'
# 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))

View file

@ -0,0 +1,92 @@
"""Add view column to setting table
Revision ID: 59729e468045
Revises: 787bdba9e147
Create Date: 2018-08-17 16:17:31.058782
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '59729e468045'
down_revision = '787bdba9e147'
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)
)
# just update previous records which have id <= 7
op.execute(
setting_table.update().where(setting_table.c.id <= 7).values({'view': 'basic'})
)
# add more new settings
op.bulk_insert(setting_table,
[
{'id': 8, 'name': 'pretty_ipv6_ptr', 'value': 'False', 'view': 'basic'},
{'id': 9, 'name': 'dnssec_admins_only', 'value': 'False', 'view': 'basic'},
{'id': 10, 'name': 'bg_domain_updates', 'value': 'False', 'view': 'basic'},
{'id': 11, 'name': 'site_name', 'value': 'PowerDNS-Admin', 'view': 'basic'},
{'id': 12, 'name': 'pdns_api_url', 'value': '', 'view': 'pdns'},
{'id': 13, 'name': 'pdns_api_key', 'value': '', 'view': 'pdns'},
{'id': 14, 'name': 'pdns_version', 'value': '4.1.1', 'view': 'pdns'},
{'id': 15, 'name': 'local_db_enabled', 'value': 'True', 'view': 'authentication'},
{'id': 16, 'name': 'signup_enabled', 'value': 'True', 'view': 'authentication'},
{'id': 17, 'name': 'ldap_enabled', 'value': 'False', 'view': 'authentication'},
{'id': 18, 'name': 'ldap_type', 'value': 'ldap', 'view': 'authentication'},
{'id': 19, 'name': 'ldap_uri', 'value': '', 'view': 'authentication'},
{'id': 20, 'name': 'ldap_base_dn', 'value': '', 'view': 'authentication'},
{'id': 21, 'name': 'ldap_admin_username', 'value': '', 'view': 'authentication'},
{'id': 22, 'name': 'ldap_admin_password', 'value': '', 'view': 'authentication'},
{'id': 23, 'name': 'ldap_filter_basic', 'value': '', 'view': 'authentication'},
{'id': 24, 'name': 'ldap_filter_username', 'value': '', 'view': 'authentication'},
{'id': 25, 'name': 'ldap_sg_enabled', 'value': 'False', 'view': 'authentication'},
{'id': 26, 'name': 'ldap_admin_group', 'value': '', 'view': 'authentication'},
{'id': 27, 'name': 'ldap_user_group', 'value': '', 'view': 'authentication'},
{'id': 28, 'name': 'github_oauth_enabled', 'value': 'False', 'view': 'authentication'},
{'id': 29, 'name': 'github_oauth_key', 'value': '', 'view': 'authentication'},
{'id': 30, 'name': 'github_oauth_secret', 'value': '', 'view': 'authentication'},
{'id': 31, 'name': 'github_oauth_scope', 'value': 'email', 'view': 'authentication'},
{'id': 32, 'name': 'github_oauth_api_url', 'value': 'https://api.github.com/user', 'view': 'authentication'},
{'id': 33, 'name': 'github_oauth_token_url', 'value': 'https://github.com/login/oauth/access_token', 'view': 'authentication'},
{'id': 34, 'name': 'github_oauth_authorize_url', 'value': 'https://github.com/login/oauth/authorize', 'view': 'authentication'},
{'id': 35, 'name': 'google_oauth_enabled', 'value': 'False', 'view': 'authentication'},
{'id': 36, 'name': 'google_oauth_client_id', 'value': '', 'view': 'authentication'},
{'id': 37, 'name': 'google_oauth_client_secret', 'value': '', 'view': 'authentication'},
{'id': 38, 'name': 'google_token_url', 'value': 'https://accounts.google.com/o/oauth2/token', 'view': 'authentication'},
{'id': 39, 'name': 'google_token_params', 'value': "{'scope': 'email profile'}", 'view': 'authentication'},
{'id': 40, 'name': 'google_authorize_url', 'value': 'https://accounts.google.com/o/oauth2/auth', 'view': 'authentication'},
{'id': 41, 'name': 'google_base_url', 'value': 'https://www.googleapis.com/oauth2/v1/', 'view': 'authentication'},
]
)
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('setting', sa.Column('view', sa.String(length=64), nullable=True))
# ### end Alembic commands ###
# update data for new schema
update_data()
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
## NOTE:
## - Drop action does not work on sqlite3
## - This action touchs the `setting` table which loaded in views.py
## during app initlization, so the downgrade function won't work
## unless we temporary remove importing `views` from `app/__init__.py`
op.drop_column('setting', 'view')
# delete added records in previous version
op.execute("DELETE FROM setting WHERE id > 7")
# ### end Alembic commands ###

View file

@ -1,6 +1,7 @@
{ {
"dependencies": { "dependencies": {
"admin-lte": "2.4.3", "admin-lte": "2.4.3",
"bootstrap-validator": "^0.11.9",
"icheck": "^1.0.2", "icheck": "^1.0.2",
"jquery-slimscroll": "^1.3.8", "jquery-slimscroll": "^1.3.8",
"jquery-ui-dist": "^1.12.1", "jquery-ui-dist": "^1.12.1",

View file

@ -132,6 +132,10 @@ bootstrap-timepicker@^0.5.2:
version "0.5.2" version "0.5.2"
resolved "https://registry.yarnpkg.com/bootstrap-timepicker/-/bootstrap-timepicker-0.5.2.tgz#10ed9f2a2f0b8ccaefcde0fcf6a0738b919a3835" resolved "https://registry.yarnpkg.com/bootstrap-timepicker/-/bootstrap-timepicker-0.5.2.tgz#10ed9f2a2f0b8ccaefcde0fcf6a0738b919a3835"
bootstrap-validator@^0.11.9:
version "0.11.9"
resolved "https://registry.yarnpkg.com/bootstrap-validator/-/bootstrap-validator-0.11.9.tgz#fb7058eef53623e78f5aa7967026f98f875a9404"
bootstrap@^3.3.7: bootstrap@^3.3.7:
version "3.3.7" version "3.3.7"
resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-3.3.7.tgz#5a389394549f23330875a3b150656574f8a9eb71" resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-3.3.7.tgz#5a389394549f23330875a3b150656574f8a9eb71"