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) login_manager.init_app(app)
db = SQLAlchemy(app) # database db = SQLAlchemy(app) # database
migrate = Migrate(app, db) # flask-migrate 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'): if app.config.get('SAML_ENABLED') and app.config.get('SAML_ENCRYPT'):
from app.lib import certutil from app.lib import certutil

View file

@ -1,11 +1,13 @@
from functools import wraps 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 Setting
from app.models import Role, Setting
def admin_role_required(f): def admin_role_required(f):
"""
Grant access if user is in Administrator role
"""
@wraps(f) @wraps(f)
def decorated_function(*args, **kwargs): def decorated_function(*args, **kwargs):
if g.user.role.name != 'Administrator': if g.user.role.name != 'Administrator':
@ -14,10 +16,28 @@ def admin_role_required(f):
return decorated_function 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) @wraps(f)
def decorated_function(*args, **kwargs): 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') domain_name = kwargs.get('domain_name')
user_domain = [d.name for d in g.user.get_domain()] 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): def can_configure_dnssec(f):
"""
Grant access if:
- user is in Operator role or higher, or
- dnssec_admins_only is off
"""
@wraps(f) @wraps(f)
def decorated_function(*args, **kwargs): def decorated_function(*args, **kwargs):
if g.user.role.name != 'Administrator' and Setting().get('dnssec_admins_only'): if g.user.role.name not in ['Administrator', 'Operator'] 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 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 f(*args, **kwargs)
return decorated_function return decorated_function

View file

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

View file

@ -1,5 +1,4 @@
import re import re
import sys
import json import json
import requests import requests
import hashlib import hashlib
@ -10,12 +9,10 @@ from urllib.parse import urlparse
from datetime import datetime, timedelta from datetime import datetime, timedelta
from threading import Thread from threading import Thread
from .certutil import * from .certutil import KEY_FILE, CERT_FILE
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.settings import OneLogin_Saml2_Settings
from onelogin.saml2.idp_metadata_parser import OneLogin_Saml2_IdPMetadataParser from onelogin.saml2.idp_metadata_parser import OneLogin_Saml2_IdPMetadataParser
idp_timestamp = datetime(1970, 1, 1) idp_timestamp = datetime(1970, 1, 1)
idp_data = None idp_data = None
@ -227,7 +224,7 @@ def prepare_flask_request(request):
def init_saml_auth(req): def init_saml_auth(req):
own_url = '' own_url = ''
if req['https'] is 'on': if req['https'] == 'on':
own_url = 'https://' own_url = 'https://'
else: else:
own_url = 'http://' own_url = 'http://'
@ -285,3 +282,12 @@ def init_saml_auth(req):
settings['organization']['en-US']['url'] = own_url settings['organization']['en-US']['url'] = own_url
auth = OneLogin_Saml2_Auth(req, settings) auth = OneLogin_Saml2_Auth(req, settings)
return auth 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 os
import re
import ldap import ldap
import ldap.filter import ldap.filter
import time
import base64 import base64
import bcrypt import bcrypt
import itertools import itertools
import traceback import traceback
import pyotp import pyotp
import re
import dns.reversename import dns.reversename
import dns.inet import dns.inet
import dns.name import dns.name
import sys
import logging as logger import logging as logger
from ast import literal_eval from ast import literal_eval
@ -150,7 +149,7 @@ class User(db.Model):
logging.error(e) logging.error(e)
logging.debug('baseDN: {0}'.format(baseDN)) logging.debug('baseDN: {0}'.format(baseDN))
logging.debug(traceback.format_exc()) logging.debug(traceback.format_exc())
raise
def ldap_auth(self, ldap_username, password): def ldap_auth(self, ldap_username, password):
try: try:
@ -165,6 +164,8 @@ class User(db.Model):
""" """
Validate user credential Validate user credential
""" """
role_name = 'User'
if method == 'LOCAL': if method == 'LOCAL':
user_info = User.query.filter(User.username == self.username).first() user_info = User.query.filter(User.username == self.username).first()
@ -179,12 +180,12 @@ class User(db.Model):
return False return False
if method == 'LDAP': if method == 'LDAP':
isadmin = False
LDAP_TYPE = Setting().get('ldap_type') LDAP_TYPE = Setting().get('ldap_type')
LDAP_BASE_DN = Setting().get('ldap_base_dn') LDAP_BASE_DN = Setting().get('ldap_base_dn')
LDAP_FILTER_BASIC = Setting().get('ldap_filter_basic') LDAP_FILTER_BASIC = Setting().get('ldap_filter_basic')
LDAP_FILTER_USERNAME = Setting().get('ldap_filter_username') LDAP_FILTER_USERNAME = Setting().get('ldap_filter_username')
LDAP_ADMIN_GROUP = Setting().get('ldap_admin_group') 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_USER_GROUP = Setting().get('ldap_user_group')
LDAP_GROUP_SECURITY_ENABLED = Setting().get('ldap_sg_enabled') LDAP_GROUP_SECURITY_ENABLED = Setting().get('ldap_sg_enabled')
@ -206,24 +207,30 @@ class User(db.Model):
try: try:
if LDAP_TYPE == 'ldap': if LDAP_TYPE == 'ldap':
if (self.ldap_search(searchFilter, LDAP_ADMIN_GROUP)): 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)) 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)): 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)) logging.info('User {0} is part of the "{1}" group that allows user access to PowerDNS-Admin'.format(self.username, LDAP_USER_GROUP))
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('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 return False
elif LDAP_TYPE == 'ad': elif LDAP_TYPE == 'ad':
user_ldap_groups = [g.decode("utf-8") for g in ldap_result[0][0][1]['memberOf']] 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)) logging.debug('user_ldap_groups: {0}'.format(user_ldap_groups))
if (LDAP_ADMIN_GROUP in 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)) 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): 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)) logging.info('User {0} is part of the "{1}" group that allows user access to PowerDNS-Admin'.format(self.username, LDAP_USER_GROUP))
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('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 return False
else: else:
logging.error('Invalid LDAP type') logging.error('Invalid LDAP type')
@ -261,21 +268,17 @@ class User(db.Model):
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
self.role_id = Role.query.filter_by(name='User').first().id
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
else:
# user will be in Administrator role if part of LDAP Admin group self.role_id = Role.query.filter_by(name=role_name).first().id
if LDAP_GROUP_SECURITY_ENABLED:
if isadmin == True:
self.role_id = Role.query.filter_by(name='Administrator').first().id
self.create_user() self.create_user()
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 role based on group membership (if enabled)
if LDAP_GROUP_SECURITY_ENABLED: if LDAP_GROUP_SECURITY_ENABLED:
self.set_admin(isadmin) self.set_role(role_name)
return True return True
else: else:
@ -430,9 +433,9 @@ class User(db.Model):
User.query.filter(User.username == self.username).delete() User.query.filter(User.username == self.username).delete()
db.session.commit() db.session.commit()
return True return True
except: except Exception as e:
db.session.rollback() 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 return False
def revoke_privilege(self): def revoke_privilege(self):
@ -447,34 +450,21 @@ class User(db.Model):
DomainUser.query.filter(DomainUser.user_id == user_id).delete() DomainUser.query.filter(DomainUser.user_id == user_id).delete()
db.session.commit() db.session.commit()
return True return True
except: except Exception as e:
db.session.rollback() 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
return False return False
def set_admin(self, is_admin): def set_role(self, role_name):
""" role = Role.query.filter(Role.name==role_name).first()
Set role for a user: if role:
is_admin == True => Administrator user = User.query.filter(User.username==self.username).first()
is_admin == False => User user.role_id = role.id
""" db.session.commit()
user_role_name = 'Administrator' if is_admin else 'User' return {'status': True, 'msg': 'Set user role successfully'}
role = Role.query.filter(Role.name==user_role_name).first() else:
return {'status': False, 'msg': 'Role does not exist'}
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
class Account(db.Model): class Account(db.Model):
@ -580,9 +570,9 @@ class Account(db.Model):
db.session.commit() db.session.commit()
return True return True
except: except Exception as e:
db.session.rollback() 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 return False
def get_user(self): def get_user(self):
@ -611,18 +601,18 @@ class Account(db.Model):
for uid in removed_ids: for uid in removed_ids:
AccountUser.query.filter(AccountUser.user_id == uid).filter(AccountUser.account_id==account_id).delete() AccountUser.query.filter(AccountUser.user_id == uid).filter(AccountUser.account_id==account_id).delete()
db.session.commit() db.session.commit()
except: except Exception as e:
db.session.rollback() 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: try:
for uid in added_ids: for uid in added_ids:
au = AccountUser(account_id, uid) au = AccountUser(account_id, uid)
db.session.add(au) db.session.add(au)
db.session.commit() db.session.commit()
except: except Exception as e:
db.session.rollback() 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): def revoke_privileges_by_id(self, user_id):
""" """
@ -643,9 +633,9 @@ class Account(db.Model):
db.session.add(au) db.session.add(au)
db.session.commit() db.session.commit()
return True return True
except: except Exception as e:
db.session.rollback() 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 return False
def remove_user(self, user): 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() AccountUser.query.filter(AccountUser.user_id == user.id).filter(AccountUser.account_id == self.id).delete()
db.session.commit() db.session.commit()
return True return True
except: except Exception as e:
db.session.rollback() 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 return False
@ -707,8 +697,8 @@ class DomainSetting(db.Model):
self.value = value self.value = value
db.session.commit() db.session.commit()
return True return True
except: except Exception as e:
logging.error('Unable to set DomainSetting value') logging.error('Unable to set DomainSetting value. DETAIL: {0}'.format(e))
logging.debug(traceback.format_exc()) logging.debug(traceback.format_exc())
db.session.rollback() db.session.rollback()
return False return False
@ -784,7 +774,8 @@ class Domain(db.Model):
try: try:
domain = Domain.query.filter(Domain.name==name).first() domain = Domain.query.filter(Domain.name==name).first()
return domain.id return domain.id
except: except Exception as e:
logging.error('Domain does not exist. ERROR: {0}'.format(e))
return None return None
def update(self): def update(self):
@ -818,8 +809,8 @@ class Domain(db.Model):
# then remove domain # then remove domain
Domain.query.filter(Domain.name == d).delete() Domain.query.filter(Domain.name == d).delete()
db.session.commit() db.session.commit()
except: except Exception as e:
logging.error('Can not delete domain from DB') logging.error('Can not delete domain from DB. DETAIL: {0}'.format(e))
logging.debug(traceback.format_exc()) logging.debug(traceback.format_exc())
db.session.rollback() db.session.rollback()
@ -911,7 +902,7 @@ class Domain(db.Model):
return {'status': 'ok', 'msg': 'Added domain successfully'} return {'status': 'ok', 'msg': 'Added domain successfully'}
except Exception as e: except Exception as e:
logging.error('Cannot add domain {0}'.format(domain_name)) 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.'} return {'status': 'error', 'msg': 'Cannot add this domain.'}
def update_soa_setting(self, domain_name, soa_edit_api): def update_soa_setting(self, domain_name, soa_edit_api):
@ -980,7 +971,7 @@ class Domain(db.Model):
domain_users.append(tmp.username) domain_users.append(tmp.username)
if 0 != len(domain_users): if 0 != len(domain_users):
self.name = domain_reverse_name 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 with granted privilages'}
return {'status': 'ok', 'msg': 'New reverse lookup domain created without users'} return {'status': 'ok', 'msg': 'New reverse lookup domain created without users'}
return {'status': 'ok', 'msg': 'Reverse lookup domain already exists'} return {'status': 'ok', 'msg': 'Reverse lookup domain already exists'}
@ -1009,12 +1000,12 @@ class Domain(db.Model):
headers = {} headers = {}
headers['X-API-Key'] = self.PDNS_API_KEY headers['X-API-Key'] = self.PDNS_API_KEY
try: 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)) 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:
logging.error('Cannot delete domain {0}'.format(domain_name)) 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'} return {'status': 'error', 'msg': 'Cannot delete domain'}
def get_user(self): def get_user(self):
@ -1027,7 +1018,7 @@ class Domain(db.Model):
user_ids.append(q[0].user_id) user_ids.append(q[0].user_id)
return user_ids return user_ids
def grant_privielges(self, new_user_list): def grant_privileges(self, new_user_list):
""" """
Reconfigure domain_user table Reconfigure domain_user table
""" """
@ -1044,18 +1035,18 @@ class Domain(db.Model):
for uid in removed_ids: for uid in removed_ids:
DomainUser.query.filter(DomainUser.user_id == uid).filter(DomainUser.domain_id==domain_id).delete() DomainUser.query.filter(DomainUser.user_id == uid).filter(DomainUser.domain_id==domain_id).delete()
db.session.commit() db.session.commit()
except: except Exception as e:
db.session.rollback() 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: try:
for uid in added_ids: for uid in added_ids:
du = DomainUser(domain_id, uid) du = DomainUser(domain_id, uid)
db.session.add(du) db.session.add(du)
db.session.commit() db.session.commit()
except: except Exception as e:
db.session.rollback() 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): def update_from_master(self, domain_name):
""" """
@ -1066,9 +1057,10 @@ class Domain(db.Model):
headers = {} headers = {}
headers['X-API-Key'] = self.PDNS_API_KEY headers['X-API-Key'] = self.PDNS_API_KEY
try: 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'} 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'} return {'status': 'error', 'msg': 'There was something wrong, please contact administrator'}
else: else:
return {'status': 'error', 'msg': 'This domain doesnot exist'} 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'} return {'status': 'error', 'msg': 'DNSSEC is not enabled for this domain'}
else: else:
return {'status': 'ok', 'dnssec': jdata} 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'} return {'status': 'error', 'msg': 'There was something wrong, please contact administrator'}
else: else:
return {'status': 'error', 'msg': 'This domain doesnot exist'} return {'status': 'error', 'msg': 'This domain doesnot exist'}
@ -1120,8 +1113,9 @@ class Domain(db.Model):
return {'status': 'ok'} return {'status': 'ok'}
except: except Exception as e:
logging.error(traceback.print_exc()) 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'} return {'status': 'error', 'msg': 'There was something wrong, please contact administrator'}
else: else:
@ -1151,8 +1145,9 @@ class Domain(db.Model):
return {'status': 'ok'} return {'status': 'ok'}
except: except Exception as e:
logging.error(traceback.print_exc()) 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} return {'status': 'error', 'msg': 'There was something wrong, please contact administrator','domain': domain.name, 'id': key_id}
else: else:
@ -1190,10 +1185,9 @@ class Domain(db.Model):
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']}
else: else:
self.update() 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'} return {'status': 'ok', 'msg': 'account changed successfully'}
except Exception as e: except Exception as e:
@ -1202,8 +1196,6 @@ class Domain(db.Model):
logging.error('Cannot change account for domain {0}'.format(domain_name)) logging.error('Cannot change account for domain {0}'.format(domain_name))
return {'status': 'error', 'msg': 'Cannot change account for this domain.'} return {'status': 'error', 'msg': 'Cannot change account for this domain.'}
return {'status': True, 'msg': 'Domain association successful'}
def get_account(self): def get_account(self):
""" """
Get current account associated with this domain Get current account associated with this domain
@ -1273,8 +1265,8 @@ class Record(object):
headers['X-API-Key'] = self.PDNS_API_KEY headers['X-API-Key'] = self.PDNS_API_KEY
try: try:
jdata = utils.fetch_json(urljoin(self.PDNS_STATS_URL, self.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 Exception as e:
logging.error("Cannot fetch domain's record data from remote powerdns api") logging.error("Cannot fetch domain's record data from remote powerdns api. DETAIL: {0}".format(e))
return False return False
if self.NEW_SCHEMA: if self.NEW_SCHEMA:
@ -1573,7 +1565,6 @@ class Record(object):
self.add(domain_reverse_name) self.add(domain_reverse_name)
for r in deleted_records: for r in deleted_records:
if r['type'] in ['A', 'AAAA']: if r['type'] in ['A', 'AAAA']:
r_name = r['name'] + '.'
r_content = r['content'] r_content = r['content']
reverse_host_address = dns.reversename.from_address(r_content).to_text() reverse_host_address = dns.reversename.from_address(r_content).to_text()
domain_reverse_name = d.get_reverse_domain_name(reverse_host_address) 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) 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 Exception as e:
logging.error("Cannot remove record {0}/{1}/{2} from domain {3}".format(self.name, self.type, self.data, domain)) 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'} return {'status': 'error', 'msg': 'There was something wrong, please contact administrator'}
def is_allowed_edit(self): def is_allowed_edit(self):
@ -1683,7 +1674,7 @@ class Record(object):
] ]
} }
try: 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)) 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:
@ -1730,8 +1721,8 @@ class Server(object):
try: 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') 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 Exception as e:
logging.error("Can not get server configuration.") logging.error("Can not get server configuration. DETAIL: {0}".format(e))
logging.debug(traceback.format_exc()) logging.debug(traceback.format_exc())
return [] return []
@ -1745,8 +1736,8 @@ class Server(object):
try: 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') 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 Exception as e:
logging.error("Can not get server statistics.") logging.error("Can not get server statistics. DETAIL: {0}".format(e))
logging.debug(traceback.format_exc()) logging.debug(traceback.format_exc())
return [] return []
@ -1784,13 +1775,13 @@ class History(db.Model):
Remove all history from DB Remove all history from DB
""" """
try: try:
num_rows_deleted = db.session.query(History).delete() db.session.query(History).delete()
db.session.commit() db.session.commit()
logging.info("Removed all history") logging.info("Removed all history")
return True return True
except: except Exception as e:
db.session.rollback() db.session.rollback()
logging.error("Cannot remove history") logging.error("Cannot remove history. DETAIL: {0}".format(e))
logging.debug(traceback.format_exc()) logging.debug(traceback.format_exc())
return False return False
@ -1798,7 +1789,6 @@ 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.Text()) value = db.Column(db.Text())
view = db.Column(db.String(64))
defaults = { defaults = {
'maintenance': False, 'maintenance': False,
@ -1808,9 +1798,10 @@ class Setting(db.Model):
'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, 'record_quick_edit': True,
'pretty_ipv6_ptr': False, 'pretty_ipv6_ptr': False,
'dnssec_admins_only': False, 'dnssec_admins_only': False,
'allow_user_create_domain': False,
'bg_domain_updates': False, 'bg_domain_updates': False,
'site_name': 'PowerDNS-Admin', 'site_name': 'PowerDNS-Admin',
'pdns_api_url': '', 'pdns_api_url': '',
@ -1827,8 +1818,9 @@ class Setting(db.Model):
'ldap_filter_basic': '', 'ldap_filter_basic': '',
'ldap_filter_username': '', 'ldap_filter_username': '',
'ldap_sg_enabled': False, 'ldap_sg_enabled': False,
'ldap_admin_group': False, 'ldap_admin_group': '',
'ldap_user_group': False, 'ldap_operator_group': '',
'ldap_user_group': '',
'github_oauth_enabled': False, 'github_oauth_enabled': False,
'github_oauth_key': '', 'github_oauth_key': '',
'github_oauth_secret': '', 'github_oauth_secret': '',
@ -1873,8 +1865,8 @@ class Setting(db.Model):
maintenance.value = mode maintenance.value = mode
db.session.commit() db.session.commit()
return True return True
except: except Exception as e:
logging.error('Cannot set maintenance to {0}'.format(mode)) logging.error('Cannot set maintenance to {0}. DETAIL: {1}'.format(mode, e))
logging.debug(traceback.format_exec()) logging.debug(traceback.format_exec())
db.session.rollback() db.session.rollback()
return False return False
@ -1894,8 +1886,8 @@ class Setting(db.Model):
current_setting.value = "True" current_setting.value = "True"
db.session.commit() db.session.commit()
return True return True
except: except Exception as e:
logging.error('Cannot toggle setting {0}'.format(setting)) logging.error('Cannot toggle setting {0}. DETAIL: {1}'.format(setting, e))
logging.debug(traceback.format_exec()) logging.debug(traceback.format_exec())
db.session.rollback() db.session.rollback()
return False return False
@ -1913,8 +1905,8 @@ class Setting(db.Model):
current_setting.value = value current_setting.value = value
db.session.commit() db.session.commit()
return True return True
except: except Exception as e:
logging.error('Cannot edit setting {0}'.format(setting)) logging.error('Cannot edit setting {0}. DETAIL: {1}'.format(setting, e))
logging.debug(traceback.format_exec()) logging.debug(traceback.format_exec())
db.session.rollback() db.session.rollback()
return False 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())) 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): def get_forward_records_allow_to_edit(self):
records = literal_eval(self.get('forward_records_allow_edit')) records = self.get('forward_records_allow_edit')
return [r for r in records if records[r]] 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): def get_reverse_records_allow_to_edit(self):
records = literal_eval(self.get('reverse_records_allow_edit')) records = self.get('reverse_records_allow_edit')
return [r for r in records if records[r]] r_records = literal_eval(records) if isinstance(records, str) else records
r_name = [r for r in r_records if r_records[r]]
def get_view(self, view): # Sort alphabetically if python version is smaller than 3.6
r = {} if sys.version_info[0] < 3 or (sys.version_info[0] == 3 and sys.version_info[1] < 6):
settings = Setting.query.filter(Setting.view == view).all() r_name.sort()
for setting in settings: return r_name
d = setting.__dict__
r[d['name']] = d['value']
return r
class DomainTemplate(db.Model): class DomainTemplate(db.Model):

View file

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

View file

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

View file

@ -23,7 +23,7 @@
<h3 class="box-title">History Management</h3> <h3 class="box-title">History Management</h3>
</div> </div>
<div class="box-body clearfix"> <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> Clear History&nbsp;<i class="fa fa-trash"></i>
</button> </button>
</div> </div>

View file

@ -36,7 +36,7 @@
<th>First Name</th> <th>First Name</th>
<th>Last Name</th> <th>Last Name</th>
<th>Email</th> <th>Email</th>
<th>Admin</th> <th>Role</th>
<th>Privileges</th> <th>Privileges</th>
<th>Action</th> <th>Action</th>
</tr> </tr>
@ -49,18 +49,22 @@
<td>{{ user.lastname }}</td> <td>{{ user.lastname }}</td>
<td>{{ user.email }}</td> <td>{{ user.email }}</td>
<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>
<td width="6%"> <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> Revoke&nbsp;<i class="fa fa-lock"></i>
</button> </button>
</td> </td>
<td width="15%"> <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> Edit&nbsp;<i class="fa fa-lock"></i>
</button> </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> Delete&nbsp;<i class="fa fa-trash"></i>
</button> </button>
</td> </td>
@ -93,14 +97,6 @@
"pageLength": 10 "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 // handle revocation of privileges
$(document.body).on('click', '.button_revoke', function() { $(document.body).on('click', '.button_revoke', function() {
var modal = $("#modal_revoke"); var modal = $("#modal_revoke");
@ -129,24 +125,18 @@
}); });
// initialize pretty checkboxes // handle user role changing
$('.admin_toggle').iCheck({ $('.user_role').on('change', function() {
checkboxClass : 'icheckbox_square-blue', var role_name = this.value;
increaseArea : '20%' // optional
});
// handle checkbox toggling
$(document.body).on('ifToggled', '.admin_toggle', function() {
var is_admin = $(this).prop('checked');
var username = $(this).prop('id'); var username = $(this).prop('id');
postdata = { postdata = {
'action' : 'set_admin', 'action' : 'update_user_role',
'data' : { 'data' : {
'username' : username, '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> </script>
{% endblock %} {% 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') }}"> <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> <span class="help-block with-errors"></span>
</div> </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"> <div class="form-group">
<label for="ldap_user_group">User group</label> <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') }}"> <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> <li>
Admin group - Your LDAP admin group. Admin group - Your LDAP admin group.
</li> </li>
<li>
Operator group - Your LDAP operator group.
</li>
<li> <li>
User group - Your LDAP user group. User group - Your LDAP user group.
</li> </li>

View file

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

View file

@ -41,7 +41,7 @@
</div> </div>
<div class="form-group has-feedback"> <div class="form-group has-feedback">
<label class="control-label" for="pdns_api_key">PDNS API KEY</label> <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> <span class="help-block with-errors"></span>
</div> </div>
<div class="form-group has-feedback"> <div class="form-group has-feedback">

View file

@ -108,26 +108,28 @@
<li class="{{ 'active' if active_page == 'dashboard' else '' }}"> <li class="{{ 'active' if active_page == 'dashboard' else '' }}">
<a href="{{ url_for('dashboard') }}"><i class="fa fa-dashboard"></i> Dashboard</a> <a href="{{ url_for('dashboard') }}"><i class="fa fa-dashboard"></i> Dashboard</a>
</li> </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 '' }}"> <li class="{{ 'active' if active_page == 'new_domain' else '' }}">
<a href="{{ url_for('domain_add') }}"><i class="fa fa-plus"></i> New Domain</a> <a href="{{ url_for('domain_add') }}"><i class="fa fa-plus"></i> New Domain</a>
</li> </li>
{% endif %}
{% if current_user.role.name in ['Administrator', 'Operator'] %}
<li class="header">ADMINISTRATION</li> <li class="header">ADMINISTRATION</li>
<li class="{{ 'active' if active_page == 'admin_console' else '' }}"> <li class="{{ 'active' if active_page == 'admin_console' else '' }}">
<a href="{{ url_for('admin') }}"><i class="fa fa-wrench"></i> Admin Console</a> <a href="{{ url_for('admin_pdns') }}"><i class="fa fa-info-circle"></i> PDNS</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>
</li> </li>
<li class="{{ 'active' if active_page == 'admin_history' else '' }}"> <li class="{{ 'active' if active_page == 'admin_history' else '' }}">
<a href="{{ url_for('admin_history') }}"><i class="fa fa-calendar"></i> History</a> <a href="{{ url_for('admin_history') }}"><i class="fa fa-calendar"></i> History</a>
</li> </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' }}"> <li class="{{ 'treeview active' if active_page == 'admin_settings' else 'treeview' }}">
<a href="#"> <a href="#">
<i class="fa fa-cog"></i> Settings <i class="fa fa-cog"></i> Settings
@ -138,8 +140,10 @@
<ul class="treeview-menu" {% if active_page == 'admin_settings' %}style="display: block;"{% endif %}> <ul class="treeview-menu" {% if active_page == 'admin_settings' %}style="display: block;"{% endif %}>
<li><a href="{{ url_for('admin_setting_basic') }}"><i class="fa fa-circle-o"></i></i> Basic</a></li> <li><a href="{{ url_for('admin_setting_basic') }}"><i class="fa fa-circle-o"></i></i> Basic</a></li>
<li><a href="{{ url_for('admin_setting_records') }}"><i class="fa fa-circle-o"></i> Records</a></li> <li><a href="{{ url_for('admin_setting_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_pdns') }}"><i class="fa fa-circle-o"></i> PDNS</a></li>
<li><a href="{{ url_for('admin_setting_authentication') }}"><i class="fa fa-circle-o"></i> Authentication</a></li> <li><a href="{{ url_for('admin_setting_authentication') }}"><i class="fa fa-circle-o"></i> Authentication</a></li>
{% endif %}
</ul> </ul>
</li> </li>
{% endif %} {% endif %}

View file

@ -19,7 +19,7 @@
{% block content %} {% block content %}
<!-- Main content --> <!-- Main content -->
<section class="content"> <section class="content">
{% if current_user.role.name == 'Administrator' %} {% if current_user.role.name in ['Administrator', 'Operator'] %}
<div class="row"> <div class="row">
<div class="col-xs-3"> <div class="col-xs-3">
<div class="box"> <div class="box">
@ -69,7 +69,7 @@
</a> </a>
</div> </div>
<div class="col-lg-6"> <div class="col-lg-6">
<a href="{{ url_for('admin') }}"> <a href="{{ url_for('admin_pdns') }}">
<div class="small-box bg-green"> <div class="small-box bg-green">
<div class="inner"> <div class="inner">
<h3><span style="font-size: 18px">{{ uptime|display_second_to_time }}</span></h3> <h3><span style="font-size: 18px">{{ uptime|display_second_to_time }}</span></h3>
@ -102,17 +102,17 @@
</thead> </thead>
<tbody> <tbody>
{% for history in histories %} {% for history in histories %}
<tr class="odd"> <tr class="odd">
<td>{{ history.created_by }}</td> <td>{{ history.created_by }}</td>
<td>{{ history.msg }}</td> <td>{{ history.msg }}</td>
<td>{{ history.created_on }}</td> <td>{{ history.created_on }}</td>
<td width="6%"> <td width="6%">
<button type="button" class="btn btn-flat btn-primary history-info-button" value='{{ history.detail|replace("[]","None") }}'> <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> Info&nbsp;<i class="fa fa-info"></i>
</button> </button>
</td> </td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
</div> </div>
@ -136,7 +136,7 @@
<th>Serial</th> <th>Serial</th>
<th>Master</th> <th>Master</th>
<th>Account</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> </tr>
</thead> </thead>
<tbody> <tbody>
@ -156,7 +156,7 @@
{% endblock %} {% endblock %}
{% block extrascripts %} {% block extrascripts %}
<script> <script>
PDNS_VERSION = '{{ SETTING.get("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,
@ -182,7 +182,7 @@
"ordering" : true, "ordering" : true,
"columnDefs": [ "columnDefs": [
{ "orderable": false, "targets": [-1] } { "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, "processing" : true,
"serverSide" : true, "serverSide" : true,
@ -236,7 +236,7 @@
modal.modal('show'); 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() { $(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

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

View file

@ -57,13 +57,13 @@
{{ record.type }} {{ record.type }}
</td> </td>
<td> <td>
{{ record.status }} {{ record.status }}
</td> </td>
<td> <td>
{{ record.ttl }} {{ record.ttl }}
</td> </td>
<td> <td>
{{ record.data }} {{ record.data }}
</td> </td>
{% if domain.type != 'Slave' %} {% if domain.type != 'Slave' %}
<td width="6%"> <td width="6%">
@ -104,7 +104,6 @@
{% endblock %} {% endblock %}
{% block extrascripts %} {% block extrascripts %}
<script> <script>
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;
@ -361,7 +360,7 @@
record_data.val(data); record_data.val(data);
modal.modal('hide'); modal.modal('hide');
}) })
modal.modal('show'); modal.modal('show');
} else if (record_type == "SOA") { } else if (record_type == "SOA") {
var modal = $("#modal_custom_record"); var modal = $("#modal_custom_record");
if (record_data.val() == "") { if (record_data.val() == "") {

View file

@ -174,7 +174,7 @@
<h3 class="box-title">Domain Deletion</h3> <h3 class="box-title">Domain Deletion</h3>
</div> </div>
<div class="box-body"> <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 }}"> <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 }} <i class="fa fa-trash"></i>&nbsp;DELETE DOMAIN {{ domain.name }}
</button> </button>

View file

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

View file

@ -115,12 +115,23 @@
"lengthMenu": " _MENU_ records" "lengthMenu": " _MENU_ records"
}, },
"retrieve" : true, "retrieve" : true,
"columnDefs": [{ "columnDefs": [
"targets": [ 7 ], {
"visible": false, type: 'natural',
"searchable": false 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 // handle delete button
@ -132,15 +143,19 @@
var nRow = $(this).parents('tr')[0]; var nRow = $(this).parents('tr')[0];
var info = "Are you sure you want to delete " + record + "?"; var info = "Are you sure you want to delete " + record + "?";
modal.find('.modal-body p').text(info); 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'); 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 // handle edit button and record click
$(document.body).on("click", ".button_edit, .row_record", function(e) { $(document.body).on("click", ".button_edit{% if quick_edit %}, .row_record{% endif %}", function(e) {
e.stopPropagation(); e.stopPropagation();
if ($(this).is('tr')) { if ($(this).is('tr')) {
var nRow = $(this)[0]; var nRow = $(this)[0];
@ -176,7 +191,9 @@
var template = $(this).prop('id'); var template = $(this).prop('id');
var info = "Are you sure you want to apply your changes?"; var info = "Are you sure you want to apply your changes?";
modal.find('.modal-body p').text(info); 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); var data = getTableData(table);
applyChanges(data, '/template/' + template + '/apply', true); applyChanges(data, '/template/' + template + '/apply', true);
modal.modal('hide'); modal.modal('hide');
@ -188,15 +205,19 @@
// handle add record button // handle add record button
$(document.body).on("click", ".button_add_record", function (e) { $(document.body).on("click", ".button_add_record", function (e) {
if (nNew || nEditing) { if (nNew || nEditing) {
// TODO: replace this alert with modal var modal = $("#modal_error");
alert("Previous record not saved. Please save it before adding more record.") modal.find('.modal-body p').text("Previous record not saved. Please save it before adding more record.");
modal.modal('show');
return; 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(); // add new row
var nRow = aiNew.index(); var default_type = records_allow_edit[0]
editRow(table, nRow); 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; nEditing = nRow;
nNew = true; nNew = true;
}); });
@ -229,20 +250,50 @@
$(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();
var record_data = $(this); 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"); var modal = $("#modal_custom_record");
if (record_data.val() == "") { if (record_data.val() == "") {
var form = " <label for=\"mx_priority\">MX Priority</label> \ 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> \ <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 { } else {
var parts = record_data.val().split(" "); var parts = record_data.val().split(" ");
var form = " <label for=\"mx_priority\">MX Priority</label> \ 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> \ <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); modal.find('.modal-body p').html(form);
@ -360,7 +411,7 @@
<p></p> <p></p>
</div> </div>
<div class="modal-footer"> <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> data-dismiss="modal">Close</button>
<button type="button" class="btn btn-flat btn-danger" id="button_delete_confirm">Delete</button> <button type="button" class="btn btn-flat btn-danger" id="button_delete_confirm">Delete</button>
</div> </div>

View file

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

View file

@ -1,5 +1,4 @@
import base64 import base64
import json
import logging as logger import logging as logger
import os import os
import traceback import traceback
@ -10,22 +9,19 @@ from functools import wraps
from io import BytesIO from io import BytesIO
from ast import literal_eval from ast import literal_eval
import jinja2
import qrcode as qrc import qrcode as qrc
import qrcode.image.svg as qrc_svg 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 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 flask_login import login_user, logout_user, current_user, login_required
from werkzeug import secure_filename 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 .models import User, Account, Domain, Record, Role, Server, History, Anonymous, Setting, DomainSetting, DomainTemplate, DomainTemplateRecord
from app import app, login_manager 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.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']: if app.config['SAML_ENABLED']:
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 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_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
app.jinja_env.filters['display_setting_state'] = utils.display_setting_state
@app.context_processor @app.context_processor
@ -68,7 +65,7 @@ def before_request():
# check site maintenance mode # check site maintenance mode
maintenance = Setting().get('maintenance') 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') return render_template('maintenance.html')
@ -284,7 +281,6 @@ def saml_authorized():
@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 ''
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:
@ -454,7 +450,7 @@ def dashboard():
BG_DOMAIN_UPDATE = Setting().get('bg_domain_updates') BG_DOMAIN_UPDATE = Setting().get('bg_domain_updates')
if not BG_DOMAIN_UPDATE: if not BG_DOMAIN_UPDATE:
logging.debug('Update domains in foreground') logging.debug('Update domains in foreground')
d = Domain().update() Domain().update()
else: else:
logging.debug('Update domains in background') logging.debug('Update domains in background')
@ -476,7 +472,7 @@ def dashboard():
@app.route('/dashboard-domains', methods=['GET']) @app.route('/dashboard-domains', methods=['GET'])
@login_required @login_required
def dashboard_domains(): def dashboard_domains():
if current_user.role.name == 'Administrator': if current_user.role.name in ['Administrator', 'Operator']:
domains = Domain.query domains = Domain.query
else: else:
domains = User(id=current_user.id).get_domain_query() domains = User(id=current_user.id).get_domain_query()
@ -508,7 +504,7 @@ def dashboard_domains():
start = "" if search.startswith("^") else "%" start = "" if search.startswith("^") else "%"
end = "" if search.endswith("$") 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) | domains = domains.outerjoin(Account).filter(Domain.name.ilike(start + search.strip("^$") + end) |
Account.name.ilike(start + search.strip("^$") + end) | Account.name.ilike(start + search.strip("^$") + end) |
Account.description.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 # 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 = Setting().get('allow_quick_edit') quick_edit = Setting().get('record_quick_edit')
records_allow_to_edit = Setting().get_records_allow_to_edit() records_allow_to_edit = Setting().get_records_allow_to_edit()
forward_records_allow_to_edit = Setting().get_forward_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() 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'): if StrictVersion(Setting().get('pdns_version')) >= StrictVersion('4.0.0'):
for jr in jrecords: 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']: 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)
@ -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) 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 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']) 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):
@ -603,7 +599,7 @@ def domain(domain_name):
@app.route('/admin/domain/add', methods=['GET', 'POST']) @app.route('/admin/domain/add', methods=['GET', 'POST'])
@login_required @login_required
@admin_role_required @can_create_domain
def domain_add(): def domain_add():
templates = DomainTemplate.query.all() templates = DomainTemplate.query.all()
if request.method == 'POST': if request.method == 'POST':
@ -632,6 +628,11 @@ def domain_add():
if result['status'] == 'ok': 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 = 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() 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': if domain_template != '0':
template = DomainTemplate.query.filter(DomainTemplate.id == domain_template).first() template = DomainTemplate.query.filter(DomainTemplate.id == domain_template).first()
template_records = DomainTemplateRecord.query.filter(DomainTemplateRecord.template_id == domain_template).all() template_records = DomainTemplateRecord.query.filter(DomainTemplateRecord.template_id == domain_template).all()
@ -651,7 +652,7 @@ def domain_add():
else: else:
return render_template('errors/400.html', msg=result['msg']), 400 return render_template('errors/400.html', msg=result['msg']), 400
except: except:
logging.error(traceback.print_exc()) logging.error(traceback.format_exc())
return redirect(url_for('error', code=500)) return redirect(url_for('error', code=500))
else: else:
@ -661,7 +662,7 @@ def domain_add():
@app.route('/admin/domain/<path:domain_name>/delete', methods=['GET']) @app.route('/admin/domain/<path:domain_name>/delete', methods=['GET'])
@login_required @login_required
@admin_role_required @operator_role_required
def domain_delete(domain_name): def domain_delete(domain_name):
d = Domain() d = Domain()
result = d.delete(domain_name) 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']) @app.route('/admin/domain/<path:domain_name>/manage', methods=['GET', 'POST'])
@login_required @login_required
@admin_role_required @operator_role_required
def domain_management(domain_name): def domain_management(domain_name):
if request.method == 'GET': if request.method == 'GET':
domain = Domain.query.filter(Domain.name == domain_name).first() domain = Domain.query.filter(Domain.name == domain_name).first()
@ -697,12 +698,9 @@ def domain_management(domain_name):
# username in right column # username in right column
new_user_list = request.form.getlist('domain_multi_user[]') 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 # 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 = 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() history.add()
@ -712,13 +710,13 @@ def domain_management(domain_name):
@app.route('/admin/domain/<path:domain_name>/change_soa_setting', methods=['POST']) @app.route('/admin/domain/<path:domain_name>/change_soa_setting', methods=['POST'])
@login_required @login_required
@admin_role_required @operator_role_required
def domain_change_soa_edit_api(domain_name): def domain_change_soa_edit_api(domain_name):
domain = Domain.query.filter(Domain.name == domain_name).first() domain = Domain.query.filter(Domain.name == domain_name).first()
if not domain: if not domain:
return redirect(url_for('error', code=404)) return redirect(url_for('error', code=404))
new_setting = request.form.get('soa_edit_api') new_setting = request.form.get('soa_edit_api')
if new_setting == None: if new_setting is None:
return redirect(url_for('error', code=500)) return redirect(url_for('error', code=500))
if new_setting == '0': if new_setting == '0':
return redirect(url_for('domain_management', domain_name=domain_name)) 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']) @app.route('/admin/domain/<path:domain_name>/change_account', methods=['POST'])
@login_required @login_required
@admin_role_required @operator_role_required
def domain_change_account(domain_name): def domain_change_account(domain_name):
domain = Domain.query.filter(Domain.name == domain_name).first() domain = Domain.query.filter(Domain.name == domain_name).first()
if not domain: if not domain:
@ -787,7 +785,7 @@ def record_apply(domain_name):
else: else:
return make_response(jsonify( result ), 400) return make_response(jsonify( result ), 400)
except: except:
logging.error(traceback.print_exc()) logging.error(traceback.format_exc())
return make_response(jsonify( {'status': 'error', 'msg': 'Error when applying new changes'} ), 500) return make_response(jsonify( {'status': 'error', 'msg': 'Error when applying new changes'} ), 500)
@ -810,13 +808,13 @@ def record_update(domain_name):
else: else:
return make_response(jsonify( {'status': 'error', 'msg': result['msg']} ), 500) return make_response(jsonify( {'status': 'error', 'msg': result['msg']} ), 500)
except: except:
logging.error(traceback.print_exc()) logging.error(traceback.format_exc())
return make_response(jsonify( {'status': 'error', 'msg': 'Error when applying new changes'} ), 500) 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']) @app.route('/domain/<path:domain_name>/record/<path:record_name>/type/<path:record_type>/delete', methods=['GET'])
@login_required @login_required
@admin_role_required @operator_role_required
def record_delete(domain_name, record_name, record_type): def record_delete(domain_name, record_name, record_type):
try: try:
r = Record(name=record_name, type=record_type) 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': if result['status'] == 'error':
print(result['msg']) print(result['msg'])
except: except:
logging.error(traceback.print_exc()) logging.error(traceback.format_exc())
return redirect(url_for('error', code=500)), 500 return redirect(url_for('error', code=500)), 500
return redirect(url_for('domain', domain_name=domain_name)) 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) dnssec = domain.get_domain_dnssec(domain_name)
for key in dnssec['dnssec']: 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.' } )) return make_response(jsonify( { 'status': 'ok', 'msg': 'DNSSEC removed.' } ))
@app.route('/domain/<path:domain_name>/managesetting', methods=['GET', 'POST']) @app.route('/domain/<path:domain_name>/managesetting', methods=['GET', 'POST'])
@login_required @login_required
@admin_role_required @operator_role_required
def admin_setdomainsetting(domain_name): def admin_setdomainsetting(domain_name):
if request.method == 'POST': if request.method == 'POST':
# #
@ -907,14 +905,14 @@ def admin_setdomainsetting(domain_name):
else: else:
return make_response(jsonify( { 'status': 'error', 'msg': 'Action not supported.' } ), 400) return make_response(jsonify( { 'status': 'error', 'msg': 'Action not supported.' } ), 400)
except: 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) return make_response(jsonify( { 'status': 'error', 'msg': 'There is something wrong, please contact Administrator.' } ), 400)
@app.route('/templates', methods=['GET', 'POST']) @app.route('/templates', methods=['GET', 'POST'])
@app.route('/templates/list', methods=['GET', 'POST']) @app.route('/templates/list', methods=['GET', 'POST'])
@login_required @login_required
@admin_role_required @operator_role_required
def templates(): def templates():
templates = DomainTemplate.query.all() templates = DomainTemplate.query.all()
return render_template('template.html', templates=templates) return render_template('template.html', templates=templates)
@ -922,7 +920,7 @@ def templates():
@app.route('/template/create', methods=['GET', 'POST']) @app.route('/template/create', methods=['GET', 'POST'])
@login_required @login_required
@admin_role_required @operator_role_required
def create_template(): def create_template():
if request.method == 'GET': if request.method == 'GET':
return render_template('template_add.html') return render_template('template_add.html')
@ -938,6 +936,7 @@ def create_template():
if DomainTemplate.query.filter(DomainTemplate.name == name).first(): if DomainTemplate.query.filter(DomainTemplate.name == name).first():
flash("A template with the name {0} already exists!".format(name), 'error') flash("A template with the name {0} already exists!".format(name), 'error')
return redirect(url_for('create_template')) return redirect(url_for('create_template'))
t = DomainTemplate(name=name, description=description) t = DomainTemplate(name=name, description=description)
result = t.create() result = t.create()
if result['status'] == 'ok': if result['status'] == 'ok':
@ -948,14 +947,13 @@ def create_template():
flash(result['msg'], 'error') flash(result['msg'], 'error')
return redirect(url_for('create_template')) return redirect(url_for('create_template'))
except: except:
logging.error(traceback.print_exc()) logging.error(traceback.format_exc())
return redirect(url_for('error', code=500)) return redirect(url_for('error', code=500))
return redirect(url_for('templates'))
@app.route('/template/createfromzone', methods=['POST']) @app.route('/template/createfromzone', methods=['POST'])
@login_required @login_required
@admin_role_required @operator_role_required
def create_template_from_zone(): def create_template_from_zone():
try: try:
jdata = request.json jdata = request.json
@ -1003,33 +1001,35 @@ def create_template_from_zone():
if result_records['status'] == 'ok': if result_records['status'] == 'ok':
return make_response(jsonify({'status': 'ok', 'msg': result['msg']}), 200) return make_response(jsonify({'status': 'ok', 'msg': result['msg']}), 200)
else: else:
result = t.delete_template() t.delete_template()
return make_response(jsonify({'status': 'error', 'msg': result_records['msg']}), 500) return make_response(jsonify({'status': 'error', 'msg': result_records['msg']}), 500)
else: else:
return make_response(jsonify({'status': 'error', 'msg': result['msg']}), 500) return make_response(jsonify({'status': 'error', 'msg': result['msg']}), 500)
except: except:
logging.error(traceback.print_exc()) logging.error(traceback.format_exc())
return make_response(jsonify({'status': 'error', 'msg': 'Error when applying new changes'}), 500) return make_response(jsonify({'status': 'error', 'msg': 'Error when applying new changes'}), 500)
@app.route('/template/<path:template>/edit', methods=['GET']) @app.route('/template/<path:template>/edit', methods=['GET'])
@login_required @login_required
@admin_role_required @operator_role_required
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() records_allow_to_edit = Setting().get_records_allow_to_edit()
quick_edit = Setting().get('record_quick_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 records_allow_to_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=records_allow_to_edit) return render_template('template_edit.html', template=t.name, records=records, editable_records=records_allow_to_edit, quick_edit=quick_edit)
except: except Exception as e:
logging.error(traceback.print_exc()) 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('error', code=500))
return redirect(url_for('templates')) return redirect(url_for('templates'))
@ -1060,13 +1060,13 @@ def apply_records(template):
else: else:
return make_response(jsonify(result), 400) return make_response(jsonify(result), 400)
except: except:
logging.error(traceback.print_exc()) logging.error(traceback.format_exc())
return make_response(jsonify({'status': 'error', 'msg': 'Error when applying new changes'}), 500) return make_response(jsonify({'status': 'error', 'msg': 'Error when applying new changes'}), 500)
@app.route('/template/<path:template>/delete', methods=['GET']) @app.route('/template/<path:template>/delete', methods=['GET'])
@login_required @login_required
@admin_role_required @operator_role_required
def delete_template(template): def delete_template(template):
try: try:
t = DomainTemplate.query.filter(DomainTemplate.name == template).first() t = DomainTemplate.query.filter(DomainTemplate.name == template).first()
@ -1080,15 +1080,15 @@ def delete_template(template):
flash(result['msg'], 'error') flash(result['msg'], 'error')
return redirect(url_for('templates')) return redirect(url_for('templates'))
except: except:
logging.error(traceback.print_exc()) logging.error(traceback.format_exc())
return redirect(url_for('error', code=500)) return redirect(url_for('error', code=500))
return redirect(url_for('templates')) return redirect(url_for('templates'))
@app.route('/admin', methods=['GET', 'POST']) @app.route('/admin/pdns', methods=['GET'])
@login_required @login_required
@admin_role_required @operator_role_required
def admin(): def admin_pdns():
if not Setting().get('pdns_api_url') or not Setting().get('pdns_api_key') or not Setting().get('pdns_version'): if not Setting().get('pdns_api_url') or not Setting().get('pdns_api_key') or not Setting().get('pdns_version'):
return redirect(url_for('admin_setting_pdns')) 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/<user_username>', methods=['GET', 'POST'])
@app.route('/admin/user/edit', methods=['GET', 'POST']) @app.route('/admin/user/edit', methods=['GET', 'POST'])
@login_required @login_required
@admin_role_required @operator_role_required
def admin_edituser(user_username=None): def admin_edituser(user_username=None):
if request.method == 'GET': if request.method == 'GET':
if not user_username: if not user_username:
@ -1150,11 +1150,12 @@ def admin_edituser(user_username=None):
@app.route('/admin/manageuser', methods=['GET', 'POST']) @app.route('/admin/manageuser', methods=['GET', 'POST'])
@login_required @login_required
@admin_role_required @operator_role_required
def admin_manageuser(): def admin_manageuser():
if request.method == 'GET': if request.method == 'GET':
roles = Role.query.all()
users = User.query.order_by(User.username).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': if request.method == 'POST':
# #
@ -1197,30 +1198,42 @@ def admin_manageuser():
else: else:
return make_response(jsonify( { 'status': 'error', 'msg': 'Cannot revoke user privilege.' } ), 500) 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'] username = data['username']
role_name = data['role_name']
if username == current_user.username: if username == current_user.username:
return make_response(jsonify( { 'status': 'error', 'msg': 'You cannot change you own admin rights.' } ), 400) return make_response(jsonify( { 'status': 'error', 'msg': 'You cannot change you own roles.' } ), 400)
is_admin = data['is_admin']
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) user = User(username=username)
result = user.set_admin(is_admin) result = user.set_role(role_name)
if result: if result['status']:
history = History(msg='Change user role of {0}'.format(username), created_by=current_user.username) history = History(msg='Change user role of {0} to {1}'.format(username, role_name), created_by=current_user.username)
history.add() history.add()
return make_response(jsonify( { 'status': 'ok', 'msg': 'Changed user role successfully.' } ), 200) return make_response(jsonify( { 'status': 'ok', 'msg': 'Changed user role successfully.' } ), 200)
else: 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: else:
return make_response(jsonify( { 'status': 'error', 'msg': 'Action not supported.' } ), 400) return make_response(jsonify( { 'status': 'error', 'msg': 'Action not supported.' } ), 400)
except: 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) 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/<account_name>', methods=['GET', 'POST'])
@app.route('/admin/account/edit', methods=['GET', 'POST']) @app.route('/admin/account/edit', methods=['GET', 'POST'])
@login_required @login_required
@admin_role_required @operator_role_required
def admin_editaccount(account_name=None): def admin_editaccount(account_name=None):
users = User.query.all() users = User.query.all()
@ -1274,7 +1287,7 @@ def admin_editaccount(account_name=None):
@app.route('/admin/manageaccount', methods=['GET', 'POST']) @app.route('/admin/manageaccount', methods=['GET', 'POST'])
@login_required @login_required
@admin_role_required @operator_role_required
def admin_manageaccount(): def admin_manageaccount():
if request.method == 'GET': if request.method == 'GET':
accounts = Account.query.order_by(Account.name).all() accounts = Account.query.order_by(Account.name).all()
@ -1302,21 +1315,23 @@ def admin_manageaccount():
else: else:
return make_response(jsonify( { 'status': 'error', 'msg': 'Action not supported.' } ), 400) return make_response(jsonify( { 'status': 'error', 'msg': 'Action not supported.' } ), 400)
except: 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) return make_response(jsonify( { 'status': 'error', 'msg': 'There is something wrong, please contact Administrator.' } ), 400)
@app.route('/admin/history', methods=['GET', 'POST']) @app.route('/admin/history', methods=['GET', 'POST'])
@login_required @login_required
@admin_role_required @operator_role_required
def admin_history(): def admin_history():
if request.method == 'POST': 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() h = History()
result = h.remove_all() result = h.remove_all()
if result: if result:
history = History(msg='Remove all histories', created_by=current_user.username) history = History(msg='Remove all histories', created_by=current_user.username)
history.add() history.add()
return make_response(jsonify( { 'status': 'ok', 'msg': 'Changed user role successfully.' } ), 200) return make_response(jsonify( { 'status': 'ok', 'msg': 'Changed user role successfully.' } ), 200)
else: else:
return make_response(jsonify( { 'status': 'error', 'msg': 'Can not remove histories.' } ), 500) 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']) @app.route('/admin/setting/basic', methods=['GET'])
@login_required @login_required
@admin_role_required @operator_role_required
def admin_setting_basic(): def admin_setting_basic():
if request.method == 'GET': 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) return render_template('admin_setting_basic.html', settings=settings)
@app.route('/admin/setting/basic/<path:setting>/edit', methods=['POST']) @app.route('/admin/setting/basic/<path:setting>/edit', methods=['POST'])
@login_required @login_required
@admin_role_required @operator_role_required
def admin_setting_basic_edit(setting): def admin_setting_basic_edit(setting):
jdata = request.json jdata = request.json
new_value = jdata['value'] new_value = jdata['value']
@ -1351,7 +1379,7 @@ def admin_setting_basic_edit(setting):
@app.route('/admin/setting/basic/<path:setting>/toggle', methods=['POST']) @app.route('/admin/setting/basic/<path:setting>/toggle', methods=['POST'])
@login_required @login_required
@admin_role_required @operator_role_required
def admin_setting_basic_toggle(setting): def admin_setting_basic_toggle(setting):
result = Setting().toggle(setting) result = Setting().toggle(setting)
if (result): if (result):
@ -1383,11 +1411,14 @@ def admin_setting_pdns():
@app.route('/admin/setting/dns-records', methods=['GET', 'POST']) @app.route('/admin/setting/dns-records', methods=['GET', 'POST'])
@login_required @login_required
@admin_role_required @operator_role_required
def admin_setting_records(): def admin_setting_records():
if request.method == 'GET': if request.method == 'GET':
f_records = literal_eval(Setting().get('forward_records_allow_edit')) _fr = Setting().get('forward_records_allow_edit')
r_records = literal_eval(Setting().get('reverse_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) return render_template('admin_setting_records.html', f_records=f_records, r_records=r_records)
elif request.method == 'POST': elif request.method == 'POST':
fr = {} fr = {}
@ -1438,6 +1469,7 @@ def admin_setting_authentication():
Setting().set('ldap_filter_username', request.form.get('ldap_filter_username')) 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_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_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')) Setting().set('ldap_user_group', request.form.get('ldap_user_group'))
result = {'status': True, 'msg': 'Saved successfully'} result = {'status': True, 'msg': 'Saved successfully'}
elif conf_type == 'google': elif conf_type == 'google':

View file

@ -41,6 +41,11 @@ else
fi fi
echo "===> Update PDNS API connection info" 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='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';" 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 #!/usr/bin/env python3
from app import app, db from app import db
from app.models import Role, Setting, DomainTemplate from app.models import Role, DomainTemplate
admin_role = Role(name='Administrator', description='Administrator') admin_role = Role(name='Administrator', description='Administrator')
user_role = Role(name='User', description='User') 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 #!/usr/bin/env python3
from app import app from app import app
from config import PORT from config import PORT
from config import BIND_ADDRESS
try:
from config import BIND_ADDRESS
except:
BIND_ADDRESS = '127.0.0.1'
if __name__ == '__main__': if __name__ == '__main__':
app.run(debug = True, host=BIND_ADDRESS, port=PORT) app.run(debug = True, host=BIND_ADDRESS, port=PORT)

View file

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