Merge pull request #345 from ngoduykhanh/user_role_adjustment

Adding Operator role and Code adjustment
This commit is contained in:
Khanh Ngo 2018-09-04 17:42:55 +07:00 committed by GitHub
commit c8d72f5bba
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
28 changed files with 576 additions and 359 deletions

10
.lgtm.yml Normal file
View file

@ -0,0 +1,10 @@
extraction:
python:
python_setup:
version: 3
index:
exclude:
- .git
- upload
- flask
- node_modules

View file

@ -29,7 +29,7 @@ login_manager = LoginManager()
login_manager.init_app(app)
db = SQLAlchemy(app) # database
migrate = Migrate(app, db) # flask-migrate
oauth = OAuth(app) # oauth
oauth_client = OAuth(app) # oauth
if app.config.get('SAML_ENABLED') and app.config.get('SAML_ENCRYPT'):
from app.lib import certutil

View file

@ -1,11 +1,13 @@
from functools import wraps
from flask import g, request, redirect, url_for
from flask import g, redirect, url_for
from app import app
from app.models import Role, Setting
from app.models import Setting
def admin_role_required(f):
"""
Grant access if user is in Administrator role
"""
@wraps(f)
def decorated_function(*args, **kwargs):
if g.user.role.name != 'Administrator':
@ -14,10 +16,28 @@ def admin_role_required(f):
return decorated_function
def can_access_domain(f):
def operator_role_required(f):
"""
Grant access if user is in Operator role or higher
"""
@wraps(f)
def decorated_function(*args, **kwargs):
if g.user.role.name != 'Administrator':
if g.user.role.name not in ['Administrator', 'Operator']:
return redirect(url_for('error', code=401))
return f(*args, **kwargs)
return decorated_function
def can_access_domain(f):
"""
Grant access if:
- user is in Operator role or higher, or
- user is in granted Account, or
- user is in granted Domain
"""
@wraps(f)
def decorated_function(*args, **kwargs):
if g.user.role.name not in ['Administrator', 'Operator']:
domain_name = kwargs.get('domain_name')
user_domain = [d.name for d in g.user.get_domain()]
@ -29,10 +49,30 @@ def can_access_domain(f):
def can_configure_dnssec(f):
"""
Grant access if:
- user is in Operator role or higher, or
- dnssec_admins_only is off
"""
@wraps(f)
def decorated_function(*args, **kwargs):
if g.user.role.name != 'Administrator' and Setting().get('dnssec_admins_only'):
return redirect(url_for('error', code=401))
if g.user.role.name not in ['Administrator', 'Operator'] and Setting().get('dnssec_admins_only'):
return redirect(url_for('error', code=401))
return f(*args, **kwargs)
return decorated_function
def can_create_domain(f):
"""
Grant access if:
- user is in Operator role or higher, or
- allow_user_create_domain is on
"""
@wraps(f)
def decorated_function(*args, **kwargs):
if g.user.role.name not in ['Administrator', 'Operator'] and not Setting().get('allow_user_create_domain'):
return redirect(url_for('error', code=401))
return f(*args, **kwargs)
return decorated_function

View file

@ -1,4 +1,3 @@
import os
import logging
class logger(object):

View file

@ -1,5 +1,4 @@
import re
import sys
import json
import requests
import hashlib
@ -10,12 +9,10 @@ from urllib.parse import urlparse
from datetime import datetime, timedelta
from threading import Thread
from .certutil import *
from .certutil import KEY_FILE, CERT_FILE
if app.config['SAML_ENABLED']:
from onelogin.saml2.auth import OneLogin_Saml2_Auth
from onelogin.saml2.utils import OneLogin_Saml2_Utils
from onelogin.saml2.settings import OneLogin_Saml2_Settings
from onelogin.saml2.idp_metadata_parser import OneLogin_Saml2_IdPMetadataParser
idp_timestamp = datetime(1970, 1, 1)
idp_data = None
@ -227,7 +224,7 @@ def prepare_flask_request(request):
def init_saml_auth(req):
own_url = ''
if req['https'] is 'on':
if req['https'] == 'on':
own_url = 'https://'
else:
own_url = 'http://'
@ -285,3 +282,12 @@ def init_saml_auth(req):
settings['organization']['en-US']['url'] = own_url
auth = OneLogin_Saml2_Auth(req, settings)
return auth
def display_setting_state(value):
if value == 1:
return "ON"
elif value == 0:
return "OFF"
else:
return "UNKNOWN"

View file

@ -1,17 +1,16 @@
import sys
import os
import re
import ldap
import ldap.filter
import time
import base64
import bcrypt
import itertools
import traceback
import pyotp
import re
import dns.reversename
import dns.inet
import dns.name
import sys
import logging as logger
from ast import literal_eval
@ -150,7 +149,7 @@ class User(db.Model):
logging.error(e)
logging.debug('baseDN: {0}'.format(baseDN))
logging.debug(traceback.format_exc())
raise
def ldap_auth(self, ldap_username, password):
try:
@ -165,6 +164,8 @@ class User(db.Model):
"""
Validate user credential
"""
role_name = 'User'
if method == 'LOCAL':
user_info = User.query.filter(User.username == self.username).first()
@ -179,12 +180,12 @@ class User(db.Model):
return False
if method == 'LDAP':
isadmin = False
LDAP_TYPE = Setting().get('ldap_type')
LDAP_BASE_DN = Setting().get('ldap_base_dn')
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_OPERATOR_GROUP = Setting().get('ldap_operator_group')
LDAP_USER_GROUP = Setting().get('ldap_user_group')
LDAP_GROUP_SECURITY_ENABLED = Setting().get('ldap_sg_enabled')
@ -206,24 +207,30 @@ class User(db.Model):
try:
if LDAP_TYPE == 'ldap':
if (self.ldap_search(searchFilter, LDAP_ADMIN_GROUP)):
isadmin = True
role_name = 'Administrator'
logging.info('User {0} is part of the "{1}" group that allows admin access to PowerDNS-Admin'.format(self.username, LDAP_ADMIN_GROUP))
elif (self.ldap_search(searchFilter, LDAP_OPERATOR_GROUP)):
role_name = 'Operator'
logging.info('User {0} is part of the "{1}" group that allows operator access to PowerDNS-Admin'.format(self.username, LDAP_OPERATOR_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))
logging.error('User {0} is not part of the "{1}", "{2}" or "{3}" groups that allow access to PowerDNS-Admin'.format(self.username, LDAP_ADMIN_GROUP, LDAP_OPERATOR_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
role_name = 'Administrator'
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_OPERATOR_GROUP in user_ldap_groups):
role_name = 'Operator'
logging.info('User {0} is part of the "{1}" group that allows operator access to PowerDNS-Admin'.format(self.username, LDAP_OPERATOR_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))
logging.error('User {0} is not part of the "{1}", "{2}" or "{3}" groups that allow access to PowerDNS-Admin'.format(self.username, LDAP_ADMIN_GROUP, LDAP_OPERATOR_GROUP, LDAP_USER_GROUP))
return False
else:
logging.error('Invalid LDAP type')
@ -261,21 +268,17 @@ class User(db.Model):
logging.debug(traceback.format_exc())
# first register user will be in Administrator role
self.role_id = Role.query.filter_by(name='User').first().id
if User.query.count() == 0:
self.role_id = Role.query.filter_by(name='Administrator').first().id
# user will be in Administrator role if part of LDAP Admin group
if LDAP_GROUP_SECURITY_ENABLED:
if isadmin == True:
self.role_id = Role.query.filter_by(name='Administrator').first().id
else:
self.role_id = Role.query.filter_by(name=role_name).first().id
self.create_user()
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 role based on group membership (if enabled)
if LDAP_GROUP_SECURITY_ENABLED:
self.set_admin(isadmin)
self.set_role(role_name)
return True
else:
@ -430,9 +433,9 @@ class User(db.Model):
User.query.filter(User.username == self.username).delete()
db.session.commit()
return True
except:
except Exception as e:
db.session.rollback()
logging.error('Cannot delete user {0} from DB'.format(self.username))
logging.error('Cannot delete user {0} from DB. DETAIL: {1}'.format(self.username, e))
return False
def revoke_privilege(self):
@ -447,34 +450,21 @@ class User(db.Model):
DomainUser.query.filter(DomainUser.user_id == user_id).delete()
db.session.commit()
return True
except:
except Exception as e:
db.session.rollback()
logging.error('Cannot revoke user {0} privielges'.format(self.username))
logging.error('Cannot revoke user {0} privielges. DETAIL: {1}'.format(self.username, e))
return False
return False
def set_admin(self, is_admin):
"""
Set role for a user:
is_admin == True => Administrator
is_admin == False => User
"""
user_role_name = 'Administrator' if is_admin else 'User'
role = Role.query.filter(Role.name==user_role_name).first()
try:
if role:
user = User.query.filter(User.username==self.username).first()
user.role_id = role.id
db.session.commit()
return True
else:
return False
except:
db.session.roleback()
logging.error('Cannot change user role in DB')
logging.debug(traceback.format_exc())
return False
def set_role(self, role_name):
role = Role.query.filter(Role.name==role_name).first()
if role:
user = User.query.filter(User.username==self.username).first()
user.role_id = role.id
db.session.commit()
return {'status': True, 'msg': 'Set user role successfully'}
else:
return {'status': False, 'msg': 'Role does not exist'}
class Account(db.Model):
@ -580,9 +570,9 @@ class Account(db.Model):
db.session.commit()
return True
except:
except Exception as e:
db.session.rollback()
logging.error('Cannot delete account {0} from DB'.format(self.username))
logging.error('Cannot delete account {0} from DB. DETAIL: {1}'.format(self.username, e))
return False
def get_user(self):
@ -611,18 +601,18 @@ class Account(db.Model):
for uid in removed_ids:
AccountUser.query.filter(AccountUser.user_id == uid).filter(AccountUser.account_id==account_id).delete()
db.session.commit()
except:
except Exception as e:
db.session.rollback()
logging.error('Cannot revoke user privielges on account {0}'.format(self.name))
logging.error('Cannot revoke user privielges on account {0}. DETAIL: {1}'.format(self.name, e))
try:
for uid in added_ids:
au = AccountUser(account_id, uid)
db.session.add(au)
db.session.commit()
except:
except Exception as e:
db.session.rollback()
logging.error('Cannot grant user privileges to account {0}'.format(self.name))
logging.error('Cannot grant user privileges to account {0}. DETAIL: {1}'.format(self.name, e))
def revoke_privileges_by_id(self, user_id):
"""
@ -643,9 +633,9 @@ class Account(db.Model):
db.session.add(au)
db.session.commit()
return True
except:
except Exception as e:
db.session.rollback()
logging.error('Cannot add user privielges on account {0}'.format(self.name))
logging.error('Cannot add user privielges on account {0}. DETAIL: {1}'.format(self.name, e))
return False
def remove_user(self, user):
@ -656,9 +646,9 @@ class Account(db.Model):
AccountUser.query.filter(AccountUser.user_id == user.id).filter(AccountUser.account_id == self.id).delete()
db.session.commit()
return True
except:
except Exception as e:
db.session.rollback()
logging.error('Cannot revoke user privielges on account {0}'.format(self.name))
logging.error('Cannot revoke user privielges on account {0}. DETAIL: {1}'.format(self.name, e))
return False
@ -707,8 +697,8 @@ class DomainSetting(db.Model):
self.value = value
db.session.commit()
return True
except:
logging.error('Unable to set DomainSetting value')
except Exception as e:
logging.error('Unable to set DomainSetting value. DETAIL: {0}'.format(e))
logging.debug(traceback.format_exc())
db.session.rollback()
return False
@ -784,7 +774,8 @@ class Domain(db.Model):
try:
domain = Domain.query.filter(Domain.name==name).first()
return domain.id
except:
except Exception as e:
logging.error('Domain does not exist. ERROR: {0}'.format(e))
return None
def update(self):
@ -818,8 +809,8 @@ class Domain(db.Model):
# then remove domain
Domain.query.filter(Domain.name == d).delete()
db.session.commit()
except:
logging.error('Can not delete domain from DB')
except Exception as e:
logging.error('Can not delete domain from DB. DETAIL: {0}'.format(e))
logging.debug(traceback.format_exc())
db.session.rollback()
@ -911,7 +902,7 @@ class Domain(db.Model):
return {'status': 'ok', 'msg': 'Added domain successfully'}
except Exception as e:
logging.error('Cannot add domain {0}'.format(domain_name))
logging.debug(traceback.print_exc())
logging.debug(traceback.format_exc())
return {'status': 'error', 'msg': 'Cannot add this domain.'}
def update_soa_setting(self, domain_name, soa_edit_api):
@ -980,7 +971,7 @@ class Domain(db.Model):
domain_users.append(tmp.username)
if 0 != len(domain_users):
self.name = domain_reverse_name
self.grant_privielges(domain_users)
self.grant_privileges(domain_users)
return {'status': 'ok', 'msg': 'New reverse lookup domain created with granted privilages'}
return {'status': 'ok', 'msg': 'New reverse lookup domain created without users'}
return {'status': 'ok', 'msg': 'Reverse lookup domain already exists'}
@ -1009,12 +1000,12 @@ class Domain(db.Model):
headers = {}
headers['X-API-Key'] = self.PDNS_API_KEY
try:
jdata = utils.fetch_json(urljoin(self.PDNS_STATS_URL, self.API_EXTENDED_URL + '/servers/localhost/zones/{0}'.format(domain_name)), headers=headers, method='DELETE')
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))
return {'status': 'ok', 'msg': 'Delete domain successfully'}
except Exception as e:
logging.error('Cannot delete domain {0}'.format(domain_name))
logging.debug(traceback.print_exc())
logging.debug(traceback.format_exc())
return {'status': 'error', 'msg': 'Cannot delete domain'}
def get_user(self):
@ -1027,7 +1018,7 @@ class Domain(db.Model):
user_ids.append(q[0].user_id)
return user_ids
def grant_privielges(self, new_user_list):
def grant_privileges(self, new_user_list):
"""
Reconfigure domain_user table
"""
@ -1044,18 +1035,18 @@ class Domain(db.Model):
for uid in removed_ids:
DomainUser.query.filter(DomainUser.user_id == uid).filter(DomainUser.domain_id==domain_id).delete()
db.session.commit()
except:
except Exception as e:
db.session.rollback()
logging.error('Cannot revoke user privielges on domain {0}'.format(self.name))
logging.error('Cannot revoke user privielges on domain {0}. DETAIL: {1}'.format(self.name, e))
try:
for uid in added_ids:
du = DomainUser(domain_id, uid)
db.session.add(du)
db.session.commit()
except:
except Exception as e:
db.session.rollback()
logging.error('Cannot grant user privielges to domain {0}'.format(self.name))
logging.error('Cannot grant user privielges to domain {0}. DETAIL: {1}'.format(self.name, e))
def update_from_master(self, domain_name):
"""
@ -1066,9 +1057,10 @@ class Domain(db.Model):
headers = {}
headers['X-API-Key'] = self.PDNS_API_KEY
try:
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')
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'}
except:
except Exception as e:
logging.error('Cannot update from master. DETAIL: {0}'.format(e))
return {'status': 'error', 'msg': 'There was something wrong, please contact administrator'}
else:
return {'status': 'error', 'msg': 'This domain doesnot exist'}
@ -1087,7 +1079,8 @@ class Domain(db.Model):
return {'status': 'error', 'msg': 'DNSSEC is not enabled for this domain'}
else:
return {'status': 'ok', 'dnssec': jdata}
except:
except Exception as e:
logging.error('Cannot get domain dnssec. DETAIL: {0}'.format(e))
return {'status': 'error', 'msg': 'There was something wrong, please contact administrator'}
else:
return {'status': 'error', 'msg': 'This domain doesnot exist'}
@ -1120,8 +1113,9 @@ class Domain(db.Model):
return {'status': 'ok'}
except:
logging.error(traceback.print_exc())
except Exception as e:
logging.error('Cannot enable dns sec. DETAIL: {}'.format(e))
logging.debug(traceback.format_exc())
return {'status': 'error', 'msg': 'There was something wrong, please contact administrator'}
else:
@ -1151,8 +1145,9 @@ class Domain(db.Model):
return {'status': 'ok'}
except:
logging.error(traceback.print_exc())
except Exception as e:
logging.error('Cannot delete dnssec key. DETAIL: {0}'.format(e))
logging.debug(traceback.format_exc())
return {'status': 'error', 'msg': 'There was something wrong, please contact administrator','domain': domain.name, 'id': key_id}
else:
@ -1190,10 +1185,9 @@ class Domain(db.Model):
if 'error' in jdata.keys():
logging.error(jdata['error'])
return {'status': 'error', 'msg': jdata['error']}
else:
self.update()
logging.info('account changed for domain {0} successfully'.format(domain_name))
logging.info('Account changed for domain {0} successfully'.format(domain_name))
return {'status': 'ok', 'msg': 'account changed successfully'}
except Exception as e:
@ -1202,8 +1196,6 @@ class Domain(db.Model):
logging.error('Cannot change account for domain {0}'.format(domain_name))
return {'status': 'error', 'msg': 'Cannot change account for this domain.'}
return {'status': True, 'msg': 'Domain association successful'}
def get_account(self):
"""
Get current account associated with this domain
@ -1273,8 +1265,8 @@ class Record(object):
headers['X-API-Key'] = self.PDNS_API_KEY
try:
jdata = utils.fetch_json(urljoin(self.PDNS_STATS_URL, self.API_EXTENDED_URL + '/servers/localhost/zones/{0}'.format(domain)), headers=headers)
except:
logging.error("Cannot fetch domain's record data from remote powerdns api")
except Exception as e:
logging.error("Cannot fetch domain's record data from remote powerdns api. DETAIL: {0}".format(e))
return False
if self.NEW_SCHEMA:
@ -1573,7 +1565,6 @@ class Record(object):
self.add(domain_reverse_name)
for r in deleted_records:
if r['type'] in ['A', 'AAAA']:
r_name = r['name'] + '.'
r_content = r['content']
reverse_host_address = dns.reversename.from_address(r_content).to_text()
domain_reverse_name = d.get_reverse_domain_name(reverse_host_address)
@ -1606,8 +1597,8 @@ class Record(object):
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)
return {'status': 'ok', 'msg': 'Record was removed successfully'}
except:
logging.error("Cannot remove record {0}/{1}/{2} from domain {3}".format(self.name, self.type, self.data, domain))
except Exception as e:
logging.error("Cannot remove record {0}/{1}/{2} from domain {3}. DETAIL: {4}".format(self.name, self.type, self.data, domain, e))
return {'status': 'error', 'msg': 'There was something wrong, please contact administrator'}
def is_allowed_edit(self):
@ -1683,7 +1674,7 @@ class Record(object):
]
}
try:
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)
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))
return {'status': 'ok', 'msg': 'Record was updated successfully'}
except Exception as e:
@ -1730,8 +1721,8 @@ class Server(object):
try:
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
except:
logging.error("Can not get server configuration.")
except Exception as e:
logging.error("Can not get server configuration. DETAIL: {0}".format(e))
logging.debug(traceback.format_exc())
return []
@ -1745,8 +1736,8 @@ class Server(object):
try:
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
except:
logging.error("Can not get server statistics.")
except Exception as e:
logging.error("Can not get server statistics. DETAIL: {0}".format(e))
logging.debug(traceback.format_exc())
return []
@ -1784,13 +1775,13 @@ class History(db.Model):
Remove all history from DB
"""
try:
num_rows_deleted = db.session.query(History).delete()
db.session.query(History).delete()
db.session.commit()
logging.info("Removed all history")
return True
except:
except Exception as e:
db.session.rollback()
logging.error("Cannot remove history")
logging.error("Cannot remove history. DETAIL: {0}".format(e))
logging.debug(traceback.format_exc())
return False
@ -1798,7 +1789,6 @@ class Setting(db.Model):
id = db.Column(db.Integer, primary_key = True)
name = db.Column(db.String(64))
value = db.Column(db.Text())
view = db.Column(db.String(64))
defaults = {
'maintenance': False,
@ -1808,9 +1798,10 @@ class Setting(db.Model):
'default_record_table_size': 15,
'default_domain_table_size': 10,
'auto_ptr': False,
'allow_quick_edit': True,
'record_quick_edit': True,
'pretty_ipv6_ptr': False,
'dnssec_admins_only': False,
'allow_user_create_domain': False,
'bg_domain_updates': False,
'site_name': 'PowerDNS-Admin',
'pdns_api_url': '',
@ -1827,8 +1818,9 @@ class Setting(db.Model):
'ldap_filter_basic': '',
'ldap_filter_username': '',
'ldap_sg_enabled': False,
'ldap_admin_group': False,
'ldap_user_group': False,
'ldap_admin_group': '',
'ldap_operator_group': '',
'ldap_user_group': '',
'github_oauth_enabled': False,
'github_oauth_key': '',
'github_oauth_secret': '',
@ -1873,8 +1865,8 @@ class Setting(db.Model):
maintenance.value = mode
db.session.commit()
return True
except:
logging.error('Cannot set maintenance to {0}'.format(mode))
except Exception as e:
logging.error('Cannot set maintenance to {0}. DETAIL: {1}'.format(mode, e))
logging.debug(traceback.format_exec())
db.session.rollback()
return False
@ -1894,8 +1886,8 @@ class Setting(db.Model):
current_setting.value = "True"
db.session.commit()
return True
except:
logging.error('Cannot toggle setting {0}'.format(setting))
except Exception as e:
logging.error('Cannot toggle setting {0}. DETAIL: {1}'.format(setting, e))
logging.debug(traceback.format_exec())
db.session.rollback()
return False
@ -1913,8 +1905,8 @@ class Setting(db.Model):
current_setting.value = value
db.session.commit()
return True
except:
logging.error('Cannot edit setting {0}'.format(setting))
except Exception as e:
logging.error('Cannot edit setting {0}. DETAIL: {1}'.format(setting, e))
logging.debug(traceback.format_exec())
db.session.rollback()
return False
@ -1933,20 +1925,22 @@ class Setting(db.Model):
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]]
records = self.get('forward_records_allow_edit')
f_records = literal_eval(records) if isinstance(records, str) else records
r_name = [r for r in f_records if f_records[r]]
# Sort alphabetically if python version is smaller than 3.6
if sys.version_info[0] < 3 or (sys.version_info[0] == 3 and sys.version_info[1] < 6):
r_name.sort()
return r_name
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
records = self.get('reverse_records_allow_edit')
r_records = literal_eval(records) if isinstance(records, str) else records
r_name = [r for r in r_records if r_records[r]]
# Sort alphabetically if python version is smaller than 3.6
if sys.version_info[0] < 3 or (sys.version_info[0] == 3 and sys.version_info[1] < 6):
r_name.sort()
return r_name
class DomainTemplate(db.Model):

View file

@ -1,8 +1,7 @@
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 import app, oauth_client
from app.models import Setting
# TODO:
@ -13,7 +12,7 @@ def github_oauth():
if not Setting().get('github_oauth_enabled'):
return None
github = oauth.remote_app(
github = oauth_client.remote_app(
'github',
consumer_key = Setting().get('github_oauth_key'),
consumer_secret = Setting().get('github_oauth_secret'),
@ -48,7 +47,7 @@ def google_oauth():
if not Setting().get('google_oauth_enabled'):
return None
google = oauth.remote_app(
google = oauth_client.remote_app(
'google',
consumer_key=Setting().get('google_oauth_client_id'),
consumer_secret=Setting().get('google_oauth_client_secret'),

View file

@ -1,7 +1,6 @@
var dnssecKeyList = []
function applyChanges(data, url, showResult, refreshPage) {
var success = false;
$.ajax({
type : "POST",
url : url,
@ -22,16 +21,20 @@ function applyChanges(data, url, showResult, refreshPage) {
},
error : function(jqXHR, status) {
// console.log(jqXHR);
// var modal = $("#modal_error");
// modal.find('.modal-body p').text(jqXHR["responseText"]);
// modal.modal('show');
console.log(jqXHR);
var modal = $("#modal_error");
modal.find('.modal-body p').text(jqXHR["responseText"]);
var responseJson = jQuery.parseJSON(jqXHR.responseText);
modal.find('.modal-body p').text(responseJson['msg']);
modal.modal('show');
}
});
}
function applyRecordChanges(data, domain) {
var success = false;
$.ajax({
type : "POST",
url : $SCRIPT_ROOT + '/domain/' + domain + '/apply',
@ -62,8 +65,6 @@ function applyRecordChanges(data, domain) {
}
function getTableData(table) {
var rData = []
// reformat - pretty format
var records = []
table.rows().every(function() {
@ -81,16 +82,14 @@ function getTableData(table) {
function saveRow(oTable, nRow) {
var status = 'Disabled';
var jqInputs = $(oTable.row(nRow).node()).find("input");
var jqSelect = $(oTable.row(nRow).node()).find("select");
if (jqSelect[1].value == 'false') {
status = 'Active';
} else {
status = 'Disabled';
}
oTable.cell(nRow,0).data(jqInputs[0].value);
oTable.cell(nRow,1).data(jqSelect[0].value);
oTable.cell(nRow,2).data(status);
@ -109,12 +108,12 @@ function saveRow(oTable, nRow) {
function restoreRow(oTable, nRow) {
var aData = oTable.row(nRow).data();
var jqTds = $('>td', nRow);
oTable.row(nRow).data(aData);
oTable.draw();
}
function editRow(oTable, nRow) {
var isDisabled = 'true';
var aData = oTable.row(nRow).data();
var jqTds = oTable.cells(nRow,'').nodes();
var record_types = "";
@ -134,9 +133,6 @@ function editRow(oTable, nRow) {
if (aData[2] == 'Active'){
isDisabled = 'false';
}
else {
isDisabled = 'true';
}
SelectElement('record_type', aData[1]);
SelectElement('record_status', isDisabled);
@ -167,13 +163,14 @@ function enable_dns_sec(url) {
function getdnssec(url, domain){
$.getJSON(url, function(data) {
var dnssec_footer = '';
var modal = $("#modal_dnssec_info");
if (data['status'] == 'error'){
modal.find('.modal-body p').text(data['msg']);
}
else {
dnssec_msg = '';
var dnssec_msg = '';
var dnssec = data['dnssec'];
if (dnssec.length == 0 && parseFloat(PDNS_VERSION) >= 4.1) {

View file

@ -23,7 +23,7 @@
<h3 class="box-title">History Management</h3>
</div>
<div class="box-body clearfix">
<button type="button" class="btn btn-flat btn-danger pull-right" data-toggle="modal" data-target="#modal_clear_history">
<button type="button" class="btn btn-flat btn-danger pull-right" data-toggle="modal" data-target="#modal_clear_history" {% if current_user.role != 'Administrator' %}disabled{% endif %}>
Clear History&nbsp;<i class="fa fa-trash"></i>
</button>
</div>

View file

@ -36,7 +36,7 @@
<th>First Name</th>
<th>Last Name</th>
<th>Email</th>
<th>Admin</th>
<th>Role</th>
<th>Privileges</th>
<th>Action</th>
</tr>
@ -49,18 +49,22 @@
<td>{{ user.lastname }}</td>
<td>{{ user.email }}</td>
<td>
<input type="checkbox" id="{{ user.username }}" class="admin_toggle" {% if user.role.name=='Administrator' %}checked{% endif %} {% if user.username==current_user.username %}disabled{% endif %}>
<select id="{{ user.username }}" class="user_role" {% if user.username==current_user.username or (current_user.role.name=='Operator' and user.role.name=='Administrator') %}disabled{% endif %}>
{% for role in roles %}
<option value="{{ role.name }}" {% if role.id==user.role.id %}selected{% endif %}>{{ role.name }}</option>
{% endfor %}
</select>
</td>
<td width="6%">
<button type="button" class="btn btn-flat btn-warning button_revoke" id="{{ user.username }}">
<button type="button" class="btn btn-flat btn-warning button_revoke" id="{{ user.username }}" {% if current_user.role.name=='Operator' and user.role.name=='Administrator' %}disabled{% endif %}>
Revoke&nbsp;<i class="fa fa-lock"></i>
</button>
</td>
<td width="15%">
<button type="button" class="btn btn-flat btn-success button_edit" onclick="window.location.href='{{ url_for('admin_edituser', user_username=user.username) }}'">
<button type="button" class="btn btn-flat btn-success button_edit" onclick="window.location.href='{{ url_for('admin_edituser', user_username=user.username) }}'" {% if current_user.role.name=='Operator' and user.role.name=='Administrator' %}disabled{% endif %}>
Edit&nbsp;<i class="fa fa-lock"></i>
</button>
<button type="button" class="btn btn-flat btn-danger button_delete" id="{{ user.username }}" {% if user.username==current_user.username %}disabled{% endif %}>
<button type="button" class="btn btn-flat btn-danger button_delete" id="{{ user.username }}" {% if user.username==current_user.username or (current_user.role.name=='Operator' and user.role.name=='Administrator') %}disabled{% endif %}>
Delete&nbsp;<i class="fa fa-trash"></i>
</button>
</td>
@ -93,14 +97,6 @@
"pageLength": 10
});
// avoid losing icheck box style when database refreshed
$('#tbl_users').on('draw.dt', function () {
$('.admin_toggle').iCheck({
handle: 'checkbox',
checkboxClass: 'icheckbox_square-blue'
});
});
// handle revocation of privileges
$(document.body).on('click', '.button_revoke', function() {
var modal = $("#modal_revoke");
@ -129,24 +125,18 @@
});
// initialize pretty checkboxes
$('.admin_toggle').iCheck({
checkboxClass : 'icheckbox_square-blue',
increaseArea : '20%' // optional
});
// handle checkbox toggling
$(document.body).on('ifToggled', '.admin_toggle', function() {
var is_admin = $(this).prop('checked');
// handle user role changing
$('.user_role').on('change', function() {
var role_name = this.value;
var username = $(this).prop('id');
postdata = {
'action' : 'set_admin',
'action' : 'update_user_role',
'data' : {
'username' : username,
'is_admin' : is_admin
'role_name' : role_name
}
};
applyChanges(postdata, $SCRIPT_ROOT + '/admin/manageuser');
applyChanges(postdata, $SCRIPT_ROOT + '/admin/manageuser', showResult=true);
});
</script>
{% endblock %}

View file

@ -133,6 +133,11 @@
<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_operator_group">Operator group</label>
<input type="text" class="form-control" name="ldap_operator_group" id="ldap_operator_group" placeholder="e.g. cn=operators,dc=mydomain,dc=com" data-error="Please input LDAP DN for Operator group" value="{{ SETTING.get('ldap_operator_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') }}">
@ -197,6 +202,9 @@
<li>
Admin group - Your LDAP admin group.
</li>
<li>
Operator group - Your LDAP operator group.
</li>
<li>
User group - Your LDAP user group.
</li>

View file

@ -34,23 +34,22 @@
<tbody>
{% for setting in settings %}
<tr class="odd ">
<td>{{ setting.name }}</td>
{% if setting.value == "True" or setting.value == "False" %}
<td>{{ setting.value }}</td>
{% else %}
<td><input name="value" id="value" value="{{ setting.value }}"></td>
{% endif %}
<td>{{ setting }}</td>
{% if SETTING.get(setting) in [True, False] %}
<td>{{ SETTING.get(setting)|display_setting_state }}</td>
<td width="6%">
{% 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 }}">
Toggle&nbsp;<i class="fa fa-info"></i>
</button>
{% else %}
<button type="button" class="btn btn-flat btn-warning setting-save-button" id="{{ setting.name }}">
</td>
{% else %}
<td><input name="value" id="value" value="{{ SETTING.get(setting) }}"></td>
<td width="6%">
<button type="button" class="btn btn-flat btn-warning setting-save-button" id="{{ setting }}">
Save&nbsp;<i class="fa fa-info"></i>
</button>
{% endif %}
</td>
{% endif %}
</tr>
{% endfor %}
</tbody>
@ -69,7 +68,7 @@
<script>
// set up history data table
$("#tbl_settings").DataTable({
"paging" : true,
"paging" : false,
"lengthChange" : false,
"searching" : true,
"ordering" : true,

View file

@ -41,7 +41,7 @@
</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 }}">
<input type="password" 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">

View file

@ -108,26 +108,28 @@
<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 SETTING.get('allow_user_create_domain') or current_user.role.name in ['Administrator', 'Operator'] %}
<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>
{% endif %}
{% if current_user.role.name in ['Administrator', 'Operator'] %}
<li class="header">ADMINISTRATION</li>
<li class="{{ 'active' if active_page == 'admin_console' else '' }}">
<a href="{{ url_for('admin') }}"><i class="fa fa-wrench"></i> Admin Console</a>
</li>
<li class="{{ 'active' if active_page == 'admin_domain_template' else '' }}">
<a href="{{ url_for('templates') }}"><i class="fa fa-clone"></i> Domain Templates</a>
</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>
<a href="{{ url_for('admin_pdns') }}"><i class="fa fa-info-circle"></i> PDNS</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="{{ 'active' if active_page == 'admin_domain_template' else '' }}">
<a href="{{ url_for('templates') }}"><i class="fa fa-clone"></i> Domain Templates</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_users' else '' }}">
<a href="{{ url_for('admin_manageuser') }}"><i class="fa fa-users"></i> Users</a>
</li>
<li class="{{ 'treeview active' if active_page == 'admin_settings' else 'treeview' }}">
<a href="#">
<i class="fa fa-cog"></i> Settings
@ -138,8 +140,10 @@
<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>
{% if current_user.role.name == 'Administrator' %}
<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>
{% endif %}
</ul>
</li>
{% endif %}

View file

@ -19,7 +19,7 @@
{% block content %}
<!-- Main content -->
<section class="content">
{% if current_user.role.name == 'Administrator' %}
{% if current_user.role.name in ['Administrator', 'Operator'] %}
<div class="row">
<div class="col-xs-3">
<div class="box">
@ -69,7 +69,7 @@
</a>
</div>
<div class="col-lg-6">
<a href="{{ url_for('admin') }}">
<a href="{{ url_for('admin_pdns') }}">
<div class="small-box bg-green">
<div class="inner">
<h3><span style="font-size: 18px">{{ uptime|display_second_to_time }}</span></h3>
@ -102,17 +102,17 @@
</thead>
<tbody>
{% for history in histories %}
<tr class="odd">
<td>{{ history.created_by }}</td>
<td>{{ history.msg }}</td>
<td>{{ history.created_on }}</td>
<td width="6%">
<button type="button" class="btn btn-flat btn-primary history-info-button" value='{{ history.detail|replace("[]","None") }}'>
Info&nbsp;<i class="fa fa-info"></i>
</button>
</td>
</tr>
{% endfor %}
<tr class="odd">
<td>{{ history.created_by }}</td>
<td>{{ history.msg }}</td>
<td>{{ history.created_on }}</td>
<td width="6%">
<button type="button" class="btn btn-flat btn-primary history-info-button" value='{{ history.detail|replace("[]","None") }}'>
Info&nbsp;<i class="fa fa-info"></i>
</button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
@ -136,7 +136,7 @@
<th>Serial</th>
<th>Master</th>
<th>Account</th>
<th {% if current_user.role.name !='Administrator' %}width="6%"{% else %}width="25%"{% endif %}>Action</th>
<th {% if current_user.role.name not in ['Administrator','Operator'] %}width="6%"{% else %}width="25%"{% endif %}>Action</th>
</tr>
</thead>
<tbody>
@ -156,7 +156,7 @@
{% endblock %}
{% block extrascripts %}
<script>
PDNS_VERSION = '{{ SETTING.get("pdns_version") }}'
PDNS_VERSION = '{{ SETTING.get("pdns_version") }}';
// set up history data table
$("#tbl_history").DataTable({
"paging" : false,
@ -182,7 +182,7 @@
"ordering" : true,
"columnDefs": [
{ "orderable": false, "targets": [-1] }
{% if current_user.role.name != 'Administrator' %},{ "visible": false, "targets": [-2] }{% endif %}
{% if current_user.role.name not in ['Administrator', 'Operator'] %},{ "visible": false, "targets": [-2] }{% endif %}
],
"processing" : true,
"serverSide" : true,
@ -236,7 +236,7 @@
modal.modal('show');
});
{% if current_user.role.name == 'Administrator' or not SETTING.get('dnssec_admins_only') %}
{% if current_user.role.name in ['Administrator', 'Operator'] or not SETTING.get('dnssec_admins_only') %}
$(document.body).on("click", ".button_dnssec", function() {
var domain = $(this).prop('id');
getdnssec($SCRIPT_ROOT + '/domain/' + domain + '/dnssec', domain);

View file

@ -23,13 +23,13 @@
{% endmacro %}
{% macro account(domain) %}
{% if current_user.role.name =='Administrator' %}
{% if current_user.role.name in ['Administrator', 'Operator'] %}
{% if domain.account_description != "" %}{{ domain.account.description }} {% endif %}[{{ domain.account.name }}]
{% endif %}
{% endmacro %}
{% macro actions(domain) %}
{% if current_user.role.name =='Administrator' %}
{% if current_user.role.name in ['Administrator', 'Operator'] %}
<td width="25%">
<button type="button" class="btn btn-flat btn-success button_template" id="{{ domain.name }}">
Template&nbsp;<i class="fa fa-clone"></i>

View file

@ -57,13 +57,13 @@
{{ record.type }}
</td>
<td>
{{ record.status }}
{{ record.status }}
</td>
<td>
{{ record.ttl }}
{{ record.ttl }}
</td>
<td>
{{ record.data }}
{{ record.data }}
</td>
{% if domain.type != 'Slave' %}
<td width="6%">
@ -104,7 +104,6 @@
{% endblock %}
{% block extrascripts %}
<script>
PDNS_VERSION = '{{ SETTING.get("pdns_version") }}'
// superglobals
window.records_allow_edit = {{ editable_records|tojson }};
window.nEditing = null;
@ -361,7 +360,7 @@
record_data.val(data);
modal.modal('hide');
})
modal.modal('show');
modal.modal('show');
} else if (record_type == "SOA") {
var modal = $("#modal_custom_record");
if (record_data.val() == "") {

View file

@ -174,7 +174,7 @@
<h3 class="box-title">Domain Deletion</h3>
</div>
<div class="box-body">
<p>This function is used to remove a domain from PowerDNS-Admin <b>AND</b> PowerDNS. All records and user privileges which associated to this domain will also be removed. This change cannot be reverted.</p>
<p>This function is used to remove a domain from PowerDNS-Admin <b>AND</b> PowerDNS. All records and user privileges associated with this domain will also be removed. This change cannot be reverted.</p>
<button type="button" class="btn btn-flat btn-danger pull-left delete_domain" id="{{ domain.name }}">
<i class="fa fa-trash"></i>&nbsp;DELETE DOMAIN {{ domain.name }}
</button>

View file

@ -20,22 +20,22 @@
<section class="content">
{% with errors = get_flashed_messages(category_filter=["error"]) %} {% if errors %}
<div class="row">
<div class="col-md-12">
<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>
<div class="alert-message block-message error">
<a class="close" href="#">x</a>
<ul>
{%- for msg in errors %}
<li>{{ msg }}</li> {% endfor -%}
</ul>
</div>
</div>
</div>
<div class="col-md-12">
<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>
<div class="alert-message block-message error">
<a class="close" href="#">x</a>
<ul>
{%- for msg in errors %}
<li>{{ msg }}</li> {% endfor -%}
</ul>
</div>
</div>
</div>
</div>
{% endif %} {% endwith %}
<div class="row">
@ -45,11 +45,11 @@
<h3 class="box-title">Templates</h3>
</div>
<div class="box-body">
<a href="{{ url_for('create_template') }}">
<button type="button" class="btn btn-flat btn-primary pull-left">
Create Template&nbsp;<i class="fa fa-plus"></i>
</button>
</a>
<a href="{{ url_for('create_template') }}">
<button type="button" class="btn btn-flat btn-primary pull-left">
Create Template&nbsp;<i class="fa fa-plus"></i>
</button>
</a>
</div>
<div class="box-body">
<table id="tbl_template_list" class="table table-bordered table-striped">
@ -75,15 +75,15 @@
</td>
<td>
<a href="{{ url_for('edit_template', template=template.name) }}">
<button type="button" class="btn btn-flat btn-warning button_edit" id="">
Edit&nbsp;<i class="fa fa-edit"></i>
</button>
</a>
<a href="{{ url_for('delete_template', template=template.name) }}">
<button type="button" class="btn btn-flat btn-danger button_delete" id="">
Delete&nbsp;<i class="fa fa-trash"></i>
</button>
</a>
<button type="button" class="btn btn-flat btn-warning button_edit" id="btn_edit">
Edit&nbsp;<i class="fa fa-edit"></i>
</button>
</a>
<a href="{{ url_for('delete_template', template=template.name) }}">
<button type="button" class="btn btn-flat btn-danger button_delete" id="btn_delete">
Delete&nbsp;<i class="fa fa-trash"></i>
</button>
</a>
</td>
</tr>
{% endfor %}

View file

@ -115,12 +115,23 @@
"lengthMenu": " _MENU_ records"
},
"retrieve" : true,
"columnDefs": [{
"targets": [ 7 ],
"visible": false,
"searchable": false
}]
"columnDefs": [
{
type: 'natural',
targets: [0, 4]
},
{
// hidden column so that we can add new records on top
// regardless of whatever sorting is done
visible: false,
targets: [ 7 ]
},
{
className: "length-break",
targets: [ 4 ]
}
],
"orderFixed": [[7, 'asc']]
});
// handle delete button
@ -132,15 +143,19 @@
var nRow = $(this).parents('tr')[0];
var info = "Are you sure you want to delete " + record + "?";
modal.find('.modal-body p').text(info);
modal.find('#button_delete_confirm').click(function() {
table.row(nRow).remove().draw();
modal.modal('hide');
})
modal.modal('show');
$("#button_delete_confirm").unbind().one('click', function(e) {
table.row(nRow).remove().draw();
modal.modal('hide');
});
$("#button_delete_cancel").unbind().one('click', function(e) {
modal.modal('hide');
});
});
// handle edit button
$(document.body).on("click", ".button_edit, .row_record", function(e) {
// handle edit button and record click
$(document.body).on("click", ".button_edit{% if quick_edit %}, .row_record{% endif %}", function(e) {
e.stopPropagation();
if ($(this).is('tr')) {
var nRow = $(this)[0];
@ -176,7 +191,9 @@
var template = $(this).prop('id');
var info = "Are you sure you want to apply your changes?";
modal.find('.modal-body p').text(info);
modal.find('#button_apply_confirm').click(function() {
// following unbind("click") is to avoid multiple times execution
modal.find('#button_apply_confirm').unbind("click").click(function() {
var data = getTableData(table);
applyChanges(data, '/template/' + template + '/apply', true);
modal.modal('hide');
@ -188,15 +205,19 @@
// handle add record button
$(document.body).on("click", ".button_add_record", function (e) {
if (nNew || nEditing) {
// TODO: replace this alert with modal
alert("Previous record not saved. Please save it before adding more record.")
var modal = $("#modal_error");
modal.find('.modal-body p').text("Previous record not saved. Please save it before adding more record.");
modal.modal('show');
return;
}
var table = $("#tbl_records").DataTable();
// clear search first
$("#tbl_records").DataTable().search('').columns().search('').draw();
var aiNew = table.row.add(['', 'A', 'Active', 3600, '', '', '', '']).draw();
var nRow = aiNew.index();
editRow(table, nRow);
// add new row
var default_type = records_allow_edit[0]
var nRow = jQuery('#tbl_records').dataTable().fnAddData(['', default_type, 'Active', 3600, '', '', '', '0']);
editRow($("#tbl_records").DataTable(), nRow);
document.getElementById("edit-row-focus").focus();
nEditing = nRow;
nNew = true;
});
@ -229,20 +250,50 @@
$(document.body).on("focus", "#current_edit_record_data", function (e) {
var record_type = $(this).parents("tr").find('#record_type').val();
var record_data = $(this);
if (record_type == "MX") {
if (record_type == "CAA") {
var modal = $("#modal_custom_record");
if (record_data.val() == "") {
var form = " <label for=\"caa_flag\">CAA Flag</label> \
<input type=\"text\" class=\"form-control\" name=\"caa_flag\" id=\"caa_flag\" placeholder=\"0\"> \
<label for=\"caa_tag\">CAA Tag</label> \
<input type=\"text\" class=\"form-control\" name=\"caa_tag\" id=\"caa_tag\" placeholder=\"issue\"> \
<label for=\"caa_value\">CAA Value</label> \
<input type=\"text\" class=\"form-control\" name=\"caa_value\" id=\"caa_value\" placeholder=\"eg. letsencrypt.org\"> \
";
} else {
var parts = record_data.val().split(" ");
var form = " <label for=\"caa_flag\">CAA Flag</label> \
<input type=\"text\" class=\"form-control\" name=\"caa_flag\" id=\"caa_flag\" placeholder=\"0\" value=\"" + parts[0] + "\"> \
<label for=\"caa_tag\">CAA Tag</label> \
<input type=\"text\" class=\"form-control\" name=\"caa_tag\" id=\"caa_tag\" placeholder=\"issue\" value=\"" + parts[1] + "\"> \
<label for=\"caa_value\">CAA Value</label> \
<input type=\"text\" class=\"form-control\" name=\"caa_value\" id=\"caa_value\" placeholder=\"eg. letsencrypt.org\" value=\"" + parts[2] + "\"> \
";
}
modal.find('.modal-body p').html(form);
modal.find('#button_save').click(function() {
caa_flag = modal.find('#caa_flag').val();
caa_tag = modal.find('#caa_tag').val();
caa_value = modal.find('#caa_value').val();
data = caa_flag + " " + caa_tag + " " + '"' + caa_value + '"';
record_data.val(data);
modal.modal('hide');
})
modal.modal('show');
} else if (record_type == "MX") {
var modal = $("#modal_custom_record");
if (record_data.val() == "") {
var form = " <label for=\"mx_priority\">MX Priority</label> \
<input type=\"text\" class=\"form-control\" name=\"mx_priority\" id=\"mx_priority\" placeholder=\"10\"> \
<input type=\"text\" class=\"form-control\" name=\"mx_priority\" id=\"mx_priority\" placeholder=\"eg. 10\"> \
<label for=\"mx_server\">MX Server</label> \
<input type=\"text\" class=\"form-control\" name=\"mx_server\" id=\"mx_server\" placeholder=\"postfix.example.com\"> \
<input type=\"text\" class=\"form-control\" name=\"mx_server\" id=\"mx_server\" placeholder=\"eg. postfix.example.com\"> \
";
} else {
var parts = record_data.val().split(" ");
var form = " <label for=\"mx_priority\">MX Priority</label> \
<input type=\"text\" class=\"form-control\" name=\"mx_priority\" id=\"mx_priority\" placeholder=\"10\" value=\"" + parts[0] + "\"> \
<input type=\"text\" class=\"form-control\" name=\"mx_priority\" id=\"mx_priority\" placeholder=\"eg. 10\" value=\"" + parts[0] + "\"> \
<label for=\"mx_server\">MX Server</label> \
<input type=\"text\" class=\"form-control\" name=\"mx_server\" id=\"mx_server\" placeholder=\"postfix.example.com\" value=\"" + parts[1] + "\"> \
<input type=\"text\" class=\"form-control\" name=\"mx_server\" id=\"mx_server\" placeholder=\"eg. postfix.example.com\" value=\"" + parts[1] + "\"> \
";
}
modal.find('.modal-body p').html(form);
@ -360,7 +411,7 @@
<p></p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-flat btn-default pull-left"
<button type="button" class="btn btn-flat btn-default pull-left" id="button_delete_cancel"
data-dismiss="modal">Close</button>
<button type="button" class="btn btn-flat btn-danger" id="button_delete_confirm">Delete</button>
</div>

View file

@ -161,8 +161,7 @@
// handle checkbox toggling
$('.otp_toggle').on('ifToggled', function(event) {
var enable_otp = $(this).prop('checked');
var username = $(this).prop('id');
postdata = {
var postdata = {
'action' : 'enable_otp',
'data' : {
'enable_otp' : enable_otp

View file

@ -1,5 +1,4 @@
import base64
import json
import logging as logger
import os
import traceback
@ -10,22 +9,19 @@ from functools import wraps
from io import BytesIO
from ast import literal_eval
import jinja2
import qrcode as qrc
import qrcode.image.svg as qrc_svg
from flask import g, request, make_response, jsonify, render_template, session, redirect, url_for, send_from_directory, abort, flash
from flask_login import login_user, logout_user, current_user, login_required
from werkzeug import secure_filename
from werkzeug.security import gen_salt
from .models import User, Account, Domain, Record, Role, Server, History, Anonymous, Setting, DomainSetting, DomainTemplate, DomainTemplateRecord
from app import app, login_manager
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, operator_role_required, can_access_domain, can_configure_dnssec, can_create_domain
if app.config['SAML_ENABLED']:
from onelogin.saml2.auth import OneLogin_Saml2_Auth
from onelogin.saml2.utils import OneLogin_Saml2_Utils
google = None
@ -38,6 +34,7 @@ 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_second_to_time'] = utils.display_time
app.jinja_env.filters['email_to_gravatar_url'] = utils.email_to_gravatar_url
app.jinja_env.filters['display_setting_state'] = utils.display_setting_state
@app.context_processor
@ -68,7 +65,7 @@ def before_request():
# check site maintenance mode
maintenance = Setting().get('maintenance')
if maintenance and current_user.is_authenticated and current_user.role.name != 'Administrator':
if maintenance and current_user.is_authenticated and current_user.role.name not in ['Administrator', 'Operator']:
return render_template('maintenance.html')
@ -284,7 +281,6 @@ def saml_authorized():
@app.route('/login', methods=['GET', 'POST'])
@login_manager.unauthorized_handler
def login():
LOGIN_TITLE = app.config['LOGIN_TITLE'] if 'LOGIN_TITLE' in app.config.keys() else ''
SAML_ENABLED = app.config.get('SAML_ENABLED')
if g.user is not None and current_user.is_authenticated:
@ -454,7 +450,7 @@ def dashboard():
BG_DOMAIN_UPDATE = Setting().get('bg_domain_updates')
if not BG_DOMAIN_UPDATE:
logging.debug('Update domains in foreground')
d = Domain().update()
Domain().update()
else:
logging.debug('Update domains in background')
@ -476,7 +472,7 @@ def dashboard():
@app.route('/dashboard-domains', methods=['GET'])
@login_required
def dashboard_domains():
if current_user.role.name == 'Administrator':
if current_user.role.name in ['Administrator', 'Operator']:
domains = Domain.query
else:
domains = User(id=current_user.id).get_domain_query()
@ -508,7 +504,7 @@ def dashboard_domains():
start = "" if search.startswith("^") else "%"
end = "" if search.endswith("$") else "%"
if current_user.role.name == 'Administrator':
if current_user.role.name in ['Administrator', 'Operator']:
domains = domains.outerjoin(Account).filter(Domain.name.ilike(start + search.strip("^$") + end) |
Account.name.ilike(start + search.strip("^$") + end) |
Account.description.ilike(start + search.strip("^$") + end))
@ -572,7 +568,7 @@ def domain(domain_name):
# can not get any record, API server might be down
return redirect(url_for('error', code=500))
quick_edit = Setting().get('allow_quick_edit')
quick_edit = Setting().get('record_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()
@ -580,7 +576,7 @@ def domain(domain_name):
if StrictVersion(Setting().get('pdns_version')) >= StrictVersion('4.0.0'):
for jr in jrecords:
if jr['type'] in Setting().get_records_allow_to_edit():
if jr['type'] in records_allow_to_edit:
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'])
records.append(record)
@ -591,7 +587,7 @@ def domain(domain_name):
return render_template('domain.html', domain=domain, records=records, editable_records=editable_records, quick_edit=quick_edit)
else:
for jr in jrecords:
if jr['type'] in Setting().get_records_allow_to_edit():
if jr['type'] in 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'])
records.append(record)
if not re.search('ip6\.arpa|in-addr\.arpa$', domain_name):
@ -603,7 +599,7 @@ def domain(domain_name):
@app.route('/admin/domain/add', methods=['GET', 'POST'])
@login_required
@admin_role_required
@can_create_domain
def domain_add():
templates = DomainTemplate.query.all()
if request.method == 'POST':
@ -632,6 +628,11 @@ def domain_add():
if result['status'] == 'ok':
history = History(msg='Add domain {0}'.format(domain_name), detail=str({'domain_type': domain_type, 'domain_master_ips': domain_master_ips, 'account_id': account_id}), created_by=current_user.username)
history.add()
# grant user access to the domain
Domain(name=domain_name).grant_privileges([current_user.username])
# apply template if needed
if domain_template != '0':
template = DomainTemplate.query.filter(DomainTemplate.id == domain_template).first()
template_records = DomainTemplateRecord.query.filter(DomainTemplateRecord.template_id == domain_template).all()
@ -651,7 +652,7 @@ def domain_add():
else:
return render_template('errors/400.html', msg=result['msg']), 400
except:
logging.error(traceback.print_exc())
logging.error(traceback.format_exc())
return redirect(url_for('error', code=500))
else:
@ -661,7 +662,7 @@ def domain_add():
@app.route('/admin/domain/<path:domain_name>/delete', methods=['GET'])
@login_required
@admin_role_required
@operator_role_required
def domain_delete(domain_name):
d = Domain()
result = d.delete(domain_name)
@ -677,7 +678,7 @@ def domain_delete(domain_name):
@app.route('/admin/domain/<path:domain_name>/manage', methods=['GET', 'POST'])
@login_required
@admin_role_required
@operator_role_required
def domain_management(domain_name):
if request.method == 'GET':
domain = Domain.query.filter(Domain.name == domain_name).first()
@ -697,12 +698,9 @@ def domain_management(domain_name):
# username in right column
new_user_list = request.form.getlist('domain_multi_user[]')
# get list of user ids to compare
d = Domain(name=domain_name)
domain_user_ids = d.get_user()
# grant/revoke user privielges
d.grant_privielges(new_user_list)
d = Domain(name=domain_name)
d.grant_privileges(new_user_list)
history = History(msg='Change domain {0} access control'.format(domain_name), detail=str({'user_has_access': new_user_list}), created_by=current_user.username)
history.add()
@ -712,13 +710,13 @@ def domain_management(domain_name):
@app.route('/admin/domain/<path:domain_name>/change_soa_setting', methods=['POST'])
@login_required
@admin_role_required
@operator_role_required
def domain_change_soa_edit_api(domain_name):
domain = Domain.query.filter(Domain.name == domain_name).first()
if not domain:
return redirect(url_for('error', code=404))
new_setting = request.form.get('soa_edit_api')
if new_setting == None:
if new_setting is None:
return redirect(url_for('error', code=500))
if new_setting == '0':
return redirect(url_for('domain_management', domain_name=domain_name))
@ -738,7 +736,7 @@ def domain_change_soa_edit_api(domain_name):
@app.route('/admin/domain/<path:domain_name>/change_account', methods=['POST'])
@login_required
@admin_role_required
@operator_role_required
def domain_change_account(domain_name):
domain = Domain.query.filter(Domain.name == domain_name).first()
if not domain:
@ -787,7 +785,7 @@ def record_apply(domain_name):
else:
return make_response(jsonify( result ), 400)
except:
logging.error(traceback.print_exc())
logging.error(traceback.format_exc())
return make_response(jsonify( {'status': 'error', 'msg': 'Error when applying new changes'} ), 500)
@ -810,13 +808,13 @@ def record_update(domain_name):
else:
return make_response(jsonify( {'status': 'error', 'msg': result['msg']} ), 500)
except:
logging.error(traceback.print_exc())
logging.error(traceback.format_exc())
return make_response(jsonify( {'status': 'error', 'msg': 'Error when applying new changes'} ), 500)
@app.route('/domain/<path:domain_name>/record/<path:record_name>/type/<path:record_type>/delete', methods=['GET'])
@login_required
@admin_role_required
@operator_role_required
def record_delete(domain_name, record_name, record_type):
try:
r = Record(name=record_name, type=record_type)
@ -824,7 +822,7 @@ def record_delete(domain_name, record_name, record_type):
if result['status'] == 'error':
print(result['msg'])
except:
logging.error(traceback.print_exc())
logging.error(traceback.format_exc())
return redirect(url_for('error', code=500)), 500
return redirect(url_for('domain', domain_name=domain_name))
@ -866,14 +864,14 @@ def domain_dnssec_disable(domain_name):
dnssec = domain.get_domain_dnssec(domain_name)
for key in dnssec['dnssec']:
response = domain.delete_dnssec_key(domain_name,key['id']);
domain.delete_dnssec_key(domain_name,key['id']);
return make_response(jsonify( { 'status': 'ok', 'msg': 'DNSSEC removed.' } ))
@app.route('/domain/<path:domain_name>/managesetting', methods=['GET', 'POST'])
@login_required
@admin_role_required
@operator_role_required
def admin_setdomainsetting(domain_name):
if request.method == 'POST':
#
@ -907,14 +905,14 @@ def admin_setdomainsetting(domain_name):
else:
return make_response(jsonify( { 'status': 'error', 'msg': 'Action not supported.' } ), 400)
except:
logging.error(traceback.print_exc())
logging.error(traceback.format_exc())
return make_response(jsonify( { 'status': 'error', 'msg': 'There is something wrong, please contact Administrator.' } ), 400)
@app.route('/templates', methods=['GET', 'POST'])
@app.route('/templates/list', methods=['GET', 'POST'])
@login_required
@admin_role_required
@operator_role_required
def templates():
templates = DomainTemplate.query.all()
return render_template('template.html', templates=templates)
@ -922,7 +920,7 @@ def templates():
@app.route('/template/create', methods=['GET', 'POST'])
@login_required
@admin_role_required
@operator_role_required
def create_template():
if request.method == 'GET':
return render_template('template_add.html')
@ -938,6 +936,7 @@ def create_template():
if DomainTemplate.query.filter(DomainTemplate.name == name).first():
flash("A template with the name {0} already exists!".format(name), 'error')
return redirect(url_for('create_template'))
t = DomainTemplate(name=name, description=description)
result = t.create()
if result['status'] == 'ok':
@ -948,14 +947,13 @@ def create_template():
flash(result['msg'], 'error')
return redirect(url_for('create_template'))
except:
logging.error(traceback.print_exc())
logging.error(traceback.format_exc())
return redirect(url_for('error', code=500))
return redirect(url_for('templates'))
@app.route('/template/createfromzone', methods=['POST'])
@login_required
@admin_role_required
@operator_role_required
def create_template_from_zone():
try:
jdata = request.json
@ -1003,33 +1001,35 @@ def create_template_from_zone():
if result_records['status'] == 'ok':
return make_response(jsonify({'status': 'ok', 'msg': result['msg']}), 200)
else:
result = t.delete_template()
t.delete_template()
return make_response(jsonify({'status': 'error', 'msg': result_records['msg']}), 500)
else:
return make_response(jsonify({'status': 'error', 'msg': result['msg']}), 500)
except:
logging.error(traceback.print_exc())
logging.error(traceback.format_exc())
return make_response(jsonify({'status': 'error', 'msg': 'Error when applying new changes'}), 500)
@app.route('/template/<path:template>/edit', methods=['GET'])
@login_required
@admin_role_required
@operator_role_required
def edit_template(template):
try:
t = DomainTemplate.query.filter(DomainTemplate.name == template).first()
records_allow_to_edit = Setting().get_records_allow_to_edit()
quick_edit = Setting().get('record_quick_edit')
if t is not None:
records = []
for jr in t.records:
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)
records.append(record)
record = DomainTemplateRecord(name=jr.name, type=jr.type, status='Disabled' if jr.status else 'Active', ttl=jr.ttl, data=jr.data)
records.append(record)
return render_template('template_edit.html', template=t.name, records=records, editable_records=records_allow_to_edit)
except:
logging.error(traceback.print_exc())
return render_template('template_edit.html', template=t.name, records=records, editable_records=records_allow_to_edit, quick_edit=quick_edit)
except Exception as e:
logging.error('Cannot open domain template page. DETAIL: {0}'.format(e))
logging.debug(traceback.format_exc())
return redirect(url_for('error', code=500))
return redirect(url_for('templates'))
@ -1060,13 +1060,13 @@ def apply_records(template):
else:
return make_response(jsonify(result), 400)
except:
logging.error(traceback.print_exc())
logging.error(traceback.format_exc())
return make_response(jsonify({'status': 'error', 'msg': 'Error when applying new changes'}), 500)
@app.route('/template/<path:template>/delete', methods=['GET'])
@login_required
@admin_role_required
@operator_role_required
def delete_template(template):
try:
t = DomainTemplate.query.filter(DomainTemplate.name == template).first()
@ -1080,15 +1080,15 @@ def delete_template(template):
flash(result['msg'], 'error')
return redirect(url_for('templates'))
except:
logging.error(traceback.print_exc())
logging.error(traceback.format_exc())
return redirect(url_for('error', code=500))
return redirect(url_for('templates'))
@app.route('/admin', methods=['GET', 'POST'])
@app.route('/admin/pdns', methods=['GET'])
@login_required
@admin_role_required
def admin():
@operator_role_required
def admin_pdns():
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'))
@ -1111,7 +1111,7 @@ def admin():
@app.route('/admin/user/edit/<user_username>', methods=['GET', 'POST'])
@app.route('/admin/user/edit', methods=['GET', 'POST'])
@login_required
@admin_role_required
@operator_role_required
def admin_edituser(user_username=None):
if request.method == 'GET':
if not user_username:
@ -1150,11 +1150,12 @@ def admin_edituser(user_username=None):
@app.route('/admin/manageuser', methods=['GET', 'POST'])
@login_required
@admin_role_required
@operator_role_required
def admin_manageuser():
if request.method == 'GET':
roles = Role.query.all()
users = User.query.order_by(User.username).all()
return render_template('admin_manageuser.html', users=users)
return render_template('admin_manageuser.html', users=users, roles=roles)
if request.method == 'POST':
#
@ -1197,30 +1198,42 @@ def admin_manageuser():
else:
return make_response(jsonify( { 'status': 'error', 'msg': 'Cannot revoke user privilege.' } ), 500)
elif jdata['action'] == 'set_admin':
elif jdata['action'] == 'update_user_role':
username = data['username']
role_name = data['role_name']
if username == current_user.username:
return make_response(jsonify( { 'status': 'error', 'msg': 'You cannot change you own admin rights.' } ), 400)
is_admin = data['is_admin']
return make_response(jsonify( { 'status': 'error', 'msg': 'You cannot change you own roles.' } ), 400)
user = User.query.filter(User.username==username).first()
if not user:
return make_response(jsonify( { 'status': 'error', 'msg': 'User does not exist.' } ), 404)
if user.role.name == 'Administrator' and current_user.role.name != 'Administrator':
return make_response(jsonify( { 'status': 'error', 'msg': 'You do not have permission to change Administrator users role.' } ), 400)
if role_name == 'Administrator' and current_user.role.name != 'Administrator':
return make_response(jsonify( { 'status': 'error', 'msg': 'You do not have permission to promote a user to Administrator role.' } ), 400)
user = User(username=username)
result = user.set_admin(is_admin)
if result:
history = History(msg='Change user role of {0}'.format(username), created_by=current_user.username)
result = user.set_role(role_name)
if result['status']:
history = History(msg='Change user role of {0} to {1}'.format(username, role_name), created_by=current_user.username)
history.add()
return make_response(jsonify( { 'status': 'ok', 'msg': 'Changed user role successfully.' } ), 200)
else:
return make_response(jsonify( { 'status': 'error', 'msg': 'Cannot change user role.' } ), 500)
return make_response(jsonify( { 'status': 'error', 'msg': 'Cannot change user role. {0}'.format(result['msg']) } ), 500)
else:
return make_response(jsonify( { 'status': 'error', 'msg': 'Action not supported.' } ), 400)
except:
logging.error(traceback.print_exc())
logging.error(traceback.format_exc())
return make_response(jsonify( { 'status': 'error', 'msg': 'There is something wrong, please contact Administrator.' } ), 400)
@app.route('/admin/account/edit/<account_name>', methods=['GET', 'POST'])
@app.route('/admin/account/edit', methods=['GET', 'POST'])
@login_required
@admin_role_required
@operator_role_required
def admin_editaccount(account_name=None):
users = User.query.all()
@ -1274,7 +1287,7 @@ def admin_editaccount(account_name=None):
@app.route('/admin/manageaccount', methods=['GET', 'POST'])
@login_required
@admin_role_required
@operator_role_required
def admin_manageaccount():
if request.method == 'GET':
accounts = Account.query.order_by(Account.name).all()
@ -1302,21 +1315,23 @@ def admin_manageaccount():
else:
return make_response(jsonify( { 'status': 'error', 'msg': 'Action not supported.' } ), 400)
except:
logging.error(traceback.print_exc())
logging.error(traceback.format_exc())
return make_response(jsonify( { 'status': 'error', 'msg': 'There is something wrong, please contact Administrator.' } ), 400)
@app.route('/admin/history', methods=['GET', 'POST'])
@login_required
@admin_role_required
@operator_role_required
def admin_history():
if request.method == 'POST':
if current_user.role != 'Administrator':
return make_response(jsonify( { 'status': 'error', 'msg': 'You do not have permission to remove history.' } ), 401)
h = History()
result = h.remove_all()
if result:
history = History(msg='Remove all histories', created_by=current_user.username)
history.add()
return make_response(jsonify( { 'status': 'ok', 'msg': 'Changed user role successfully.' } ), 200)
else:
return make_response(jsonify( { 'status': 'error', 'msg': 'Can not remove histories.' } ), 500)
@ -1328,16 +1343,29 @@ def admin_history():
@app.route('/admin/setting/basic', methods=['GET'])
@login_required
@admin_role_required
@operator_role_required
def admin_setting_basic():
if request.method == 'GET':
settings = Setting.query.filter(Setting.view=='basic').all()
settings = ['maintenance',
'fullscreen_layout',
'record_helper',
'login_ldap_first',
'default_record_table_size',
'default_domain_table_size',
'auto_ptr',
'record_quick_edit',
'pretty_ipv6_ptr',
'dnssec_admins_only',
'allow_user_create_domain',
'bg_domain_updates',
'site_name']
return render_template('admin_setting_basic.html', settings=settings)
@app.route('/admin/setting/basic/<path:setting>/edit', methods=['POST'])
@login_required
@admin_role_required
@operator_role_required
def admin_setting_basic_edit(setting):
jdata = request.json
new_value = jdata['value']
@ -1351,7 +1379,7 @@ def admin_setting_basic_edit(setting):
@app.route('/admin/setting/basic/<path:setting>/toggle', methods=['POST'])
@login_required
@admin_role_required
@operator_role_required
def admin_setting_basic_toggle(setting):
result = Setting().toggle(setting)
if (result):
@ -1383,11 +1411,14 @@ def admin_setting_pdns():
@app.route('/admin/setting/dns-records', methods=['GET', 'POST'])
@login_required
@admin_role_required
@operator_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'))
_fr = Setting().get('forward_records_allow_edit')
_rr = Setting().get('reverse_records_allow_edit')
f_records = literal_eval(_fr) if isinstance(_fr, str) else _fr
r_records = literal_eval(_rr) if isinstance(_rr, str) else _rr
return render_template('admin_setting_records.html', f_records=f_records, r_records=r_records)
elif request.method == 'POST':
fr = {}
@ -1438,6 +1469,7 @@ def admin_setting_authentication():
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_operator_group', request.form.get('ldap_operator_group'))
Setting().set('ldap_user_group', request.form.get('ldap_user_group'))
result = {'status': True, 'msg': 'Saved successfully'}
elif conf_type == 'google':

View file

@ -41,6 +41,11 @@ else
fi
echo "===> Update PDNS API connection info"
# initial setting if not available in the DB
mysql -h${PDA_DB_HOST} -u${PDA_DB_USER} -p${PDA_DB_PASSWORD} ${PDA_DB_NAME} -e "INSERT INTO setting (name, value) SELECT * FROM (SELECT 'pdns_api_url', 'http://${PDNS_HOST}:8081') AS tmp WHERE NOT EXISTS (SELECT name FROM setting WHERE name = 'pdns_api_url') LIMIT 1;"
mysql -h${PDA_DB_HOST} -u${PDA_DB_USER} -p${PDA_DB_PASSWORD} ${PDA_DB_NAME} -e "INSERT INTO setting (name, value) SELECT * FROM (SELECT 'pdns_api_key', '${PDNS_API_KEY}') AS tmp WHERE NOT EXISTS (SELECT name FROM setting WHERE name = 'pdns_api_key') LIMIT 1;"
# update pdns api setting if .env is changed.
mysql -h${PDA_DB_HOST} -u${PDA_DB_USER} -p${PDA_DB_PASSWORD} ${PDA_DB_NAME} -e "UPDATE setting SET value='http://${PDNS_HOST}:8081' WHERE name='pdns_api_url';"
mysql -h${PDA_DB_HOST} -u${PDA_DB_USER} -p${PDA_DB_PASSWORD} ${PDA_DB_NAME} -e "UPDATE setting SET value='${PDNS_API_KEY}' WHERE name='pdns_api_key';"

View file

@ -1,7 +1,7 @@
#!/usr/bin/env python3
from app import app, db
from app.models import Role, Setting, DomainTemplate
from app import db
from app.models import Role, DomainTemplate
admin_role = Role(name='Administrator', description='Administrator')
user_role = Role(name='User', description='User')

View file

@ -0,0 +1,30 @@
"""Remove all setting in the DB
Revision ID: 31a4ed468b18
Revises: 4a666113c7bb
Create Date: 2018-08-21 17:12:30.058782
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '31a4ed468b18'
down_revision = '4a666113c7bb'
branch_labels = None
depends_on = None
def upgrade():
# delete all settings from "setting" table.
# PDA should work without initial settings in the DB
# Once user change the settings from UI, they will be
# written to the DB.
op.execute("DELETE FROM setting")
# drop view column since we don't need it
op.drop_column('setting', 'view')
def downgrade():
op.add_column('setting', sa.Column('view', sa.String(length=64), nullable=True))

View file

@ -0,0 +1,61 @@
"""Adding Operator Role
Revision ID: 4a666113c7bb
Revises: 1274ed462010
Create Date: 2018-08-30 13:28:06.836208
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '4a666113c7bb'
down_revision = '1274ed462010'
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 new settings
op.bulk_insert(setting_table,
[
{'id': 44, 'name': 'ldap_operator_group', 'value': '', 'view': 'authentication'},
{'id': 45, 'name': 'allow_user_create_domain', 'value': 'False', 'view': 'basic'},
{'id': 46, 'name': 'record_quick_edit', 'value': 'True', 'view': 'basic'},
]
)
role_table = sa.sql.table('role',
sa.sql.column('id', sa.Integer),
sa.sql.column('name', sa.String),
sa.sql.column('description', sa.String)
)
# add new role
op.bulk_insert(role_table,
[
{'id': 3, 'name': 'Operator', 'description': 'Operator'}
]
)
def upgrade():
update_data()
def downgrade():
# remove user Operator role
op.execute("UPDATE user SET role_id = 2 WHERE role_id=3")
op.execute("DELETE FROM role WHERE name = 'Operator'")
# delete settings
op.execute("DELETE FROM setting WHERE name = 'ldap_operator_group'")
op.execute("DELETE FROM setting WHERE name = 'allow_user_create_domain'")

6
run.py
View file

@ -1,11 +1,7 @@
#!/usr/bin/env python3
from app import app
from config import PORT
try:
from config import BIND_ADDRESS
except:
BIND_ADDRESS = '127.0.0.1'
from config import BIND_ADDRESS
if __name__ == '__main__':
app.run(debug = True, host=BIND_ADDRESS, port=PORT)

View file

@ -10,8 +10,6 @@
##############################################################
### Imports
from app import app
from app.lib import log
from app.models import Domain
from config import BG_DOMAIN_UPDATES