From 8f31953b6de268b0f23ba1e460f9202d23a7fcea Mon Sep 17 00:00:00 2001 From: Znuff Date: Fri, 13 Jan 2017 16:53:11 +0200 Subject: [PATCH 01/42] Fix for #176 Fixes #176. Tested briefly with my data. --- app/static/custom/js/custom.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/static/custom/js/custom.js b/app/static/custom/js/custom.js index 67f8f0f..ddd6d7b 100644 --- a/app/static/custom/js/custom.js +++ b/app/static/custom/js/custom.js @@ -167,7 +167,7 @@ json_library = { return r + (pEnd || ''); }, prettyPrint: function(obj) { - obj = obj.replace(/u'/g, "\'").replace(/'/g, "\"").replace(/(False|None)/g, "\"$1\""); + obj = obj.replace(/"/g, "\\\"").replace(/u'/g, "\'").replace(/'/g, "\"").replace(/(False|None)/g, "\"$1\""); var jsonData = JSON.parse(obj); var jsonLine = /^( *)("[\w]+": )?("[^"]*"|[\w.+-]*)?([,[{])?$/mg; return JSON.stringify(jsonData, null, 3) @@ -175,4 +175,4 @@ json_library = { .replace(//g, '>') .replace(jsonLine, json_library.replacer); } - }; \ No newline at end of file + }; From f3f9e8d73cf382cf3aff5b2d8da75281554a6aeb Mon Sep 17 00:00:00 2001 From: toxicvengeance Date: Tue, 9 May 2017 21:27:35 +0200 Subject: [PATCH 02/42] Updated config_template.py Added CAA and SRV record to standard template --- config_template.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config_template.py b/config_template.py index 288ff47..8995d15 100644 --- a/config_template.py +++ b/config_template.py @@ -75,7 +75,7 @@ PDNS_API_KEY = 'you never know' PDNS_VERSION = '3.4.7' # RECORDS ALLOWED TO EDIT -RECORDS_ALLOW_EDIT = ['A', 'AAAA', 'CNAME', 'SPF', 'PTR', 'MX', 'TXT'] +RECORDS_ALLOW_EDIT = ['A', 'AAAA', 'CAA', 'CNAME', 'MX', 'PTR', 'SPF', 'SRV', 'TXT'] # EXPERIMENTAL FEATURES PRETTY_IPV6_PTR = False From 85694e4e931ebee9a626c9709d7be8b307d79e73 Mon Sep 17 00:00:00 2001 From: Christopher Himmel Date: Wed, 10 May 2017 22:30:06 +0200 Subject: [PATCH 03/42] added caa record helper --- app/templates/domain.html | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/app/templates/domain.html b/app/templates/domain.html index eeb4c27..36d6ae9 100644 --- a/app/templates/domain.html +++ b/app/templates/domain.html @@ -265,7 +265,36 @@ $(document.body).on("focus", "#current_edit_record_data", function (e) { var record_type = $(this).parents("tr").find('#record_type').val(); var record_data = $(this); - if (record_type == "MX") { + if (record_type == "CAA") { + var modal = $("#modal_custom_record"); + if (record_data.val() == "") { + var form = " \ + \ + \ + \ + \ + \ + "; + } else { + var parts = record_data.val().split(" "); + var form = " \ + \ + \ + \ + \ + \ + "; + } + 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'); + }) + } else if (record_type == "MX") { var modal = $("#modal_custom_record"); if (record_data.val() == "") { var form = " \ From 300af228592c9ae38530a1b4cde3486592141059 Mon Sep 17 00:00:00 2001 From: toxicvengeance Date: Wed, 10 May 2017 22:33:44 +0200 Subject: [PATCH 04/42] added caa record helper --- app/templates/domain.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/templates/domain.html b/app/templates/domain.html index 36d6ae9..af560a7 100644 --- a/app/templates/domain.html +++ b/app/templates/domain.html @@ -290,10 +290,11 @@ 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; + data = caa_flag + " " + caa_tag + " " + '"' + caa_value + '"'; record_data.val(data); modal.modal('hide'); }) + modal.modal('show'); } else if (record_type == "MX") { var modal = $("#modal_custom_record"); if (record_data.val() == "") { From c9bfe00e59975fb7d85f7898cc936044e028b545 Mon Sep 17 00:00:00 2001 From: toxicvengeance Date: Wed, 10 May 2017 23:15:01 +0200 Subject: [PATCH 05/42] added example caa values --- app/templates/domain.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/templates/domain.html b/app/templates/domain.html index af560a7..ded785b 100644 --- a/app/templates/domain.html +++ b/app/templates/domain.html @@ -280,9 +280,9 @@ var form = " \ \ \ - \ + \ \ - \ + \ "; } modal.find('.modal-body p').html(form); From 5c5beec2d6993c7174e723e758453b6245498334 Mon Sep 17 00:00:00 2001 From: toxicvengeance Date: Wed, 10 May 2017 23:25:32 +0200 Subject: [PATCH 06/42] added default values --- app/templates/domain.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/templates/domain.html b/app/templates/domain.html index ded785b..af6b30e 100644 --- a/app/templates/domain.html +++ b/app/templates/domain.html @@ -271,9 +271,9 @@ var form = " \ \ \ - \ + \ \ - \ + \ "; } else { var parts = record_data.val().split(" "); From bcb2b06124c639600d6bd672d7e51aadfa4d3a6b Mon Sep 17 00:00:00 2001 From: Paul Hooijenga Date: Fri, 30 Jun 2017 18:18:06 +0200 Subject: [PATCH 07/42] Do filtering and pagination of domains server-side. --- app/templates/dashboard.html | 51 ++++----------------------- app/views.py | 68 +++++++++++++++++++++++++++++++++--- 2 files changed, 69 insertions(+), 50 deletions(-) diff --git a/app/templates/dashboard.html b/app/templates/dashboard.html index dee30fc..f99b2dd 100644 --- a/app/templates/dashboard.html +++ b/app/templates/dashboard.html @@ -144,53 +144,11 @@ Type Serial Master - Action + Action - {% for domain in domains %} - - - {{ domain.name }} - - - {% if domain.dnssec %} - - {% else %} - - {% endif %} - - - {{ domain.type }} - - - {% if domain.serial == 0 %}{{ domain.notified_serial }}{% else %}{{domain.serial}}{% endif %} - - - {% if domain.master == '[]'%}N/A {% else %}{{ domain.master|display_master_name }}{% endif %} - - {% if current_user.role.name !='Administrator' %} - - - - {% else %} - - - - - {% endif %} - - {% endfor %} + @@ -214,13 +172,16 @@ "ordering" : false, "info" : false, "autoWidth" : false - }); + }); // set up domain list $("#tbl_domain_list").DataTable({ "paging" : true, "lengthChange" : true, "searching" : true, "ordering" : true, + "processing" : true, + "serverSide" : true, + "ajax" : "{{ url_for('dashboard_domains') }}", "info" : false, "autoWidth" : false, {% if default_domain_table_size_setting in ['10','25','50','100'] %} diff --git a/app/views.py b/app/views.py index 8cc8761..dfd8c4c 100644 --- a/app/views.py +++ b/app/views.py @@ -271,10 +271,6 @@ def logout(): @login_required def dashboard(): d = Domain().update() - if current_user.role.name == 'Administrator': - domains = Domain.query.all() - else: - domains = User(id=current_user.id).get_domain() # stats for dashboard domain_count = Domain.query.count() @@ -287,7 +283,69 @@ def dashboard(): uptime = filter(lambda uptime: uptime['name'] == 'uptime', statistics)[0]['value'] else: uptime = 0 - return render_template('dashboard.html', domains=domains, domain_count=domain_count, users=users, history_number=history_number, uptime=uptime, histories=history) + return render_template('dashboard.html', domain_count=domain_count, users=users, history_number=history_number, uptime=uptime, histories=history) + + +@app.route('/dashboard-domains', methods=['GET']) +@login_required +def dashboard_domains(): + if current_user.role.name == 'Administrator': + domains = Domain.query + else: + domains = User(id=current_user.id).get_domain() + + template = app.jinja_env.get_template("dashboard_domain.html") + render = template.make_module(vars={"current_user": current_user}) + + columns = [Domain.name, Domain.dnssec, Domain.type, Domain.serial, Domain.master] + # History.created_on.desc() + order_by = [] + for i in range(len(columns)): + column_index = request.args.get("order[%d][column]" % i) + sort_direction = request.args.get("order[%d][dir]" % i) + if column_index is None: + break + if sort_direction != "asc" and sort_direction != "desc": + sort_direction = "asc" + + column = columns[int(column_index)] + order_by.append(getattr(column, sort_direction)()) + + if order_by: + domains = domains.order_by(*order_by) + + total_count = domains.count() + + search = request.args.get("search[value]") + if search: + start = "" if search.startswith("^") else "%" + end = "" if search.endswith("$") else "%" + domains = domains.filter(Domain.name.ilike(start + search.strip("^$") + end)) + + filtered_count = domains.count() + + start = int(request.args.get("start", 0)) + length = min(int(request.args.get("length", 0)), 100) + domains = domains[start:start + length] + + data = [] + for domain in domains: + data.append([ + render.name(domain), + render.dnssec(domain), + render.type(domain), + render.serial(domain), + render.master(domain), + render.actions(domain), + ]) + + response_data = { + "draw": int(request.args.get("draw", 0)), + "recordsTotal": total_count, + "recordsFiltered": filtered_count, + "data": data, + } + return jsonify(response_data) @app.route('/domain/', methods=['GET', 'POST']) From 8cdfab1c7c45b1363dcbfa8e703aa48e5463fde7 Mon Sep 17 00:00:00 2001 From: dkeightley Date: Mon, 3 Jul 2017 15:53:26 +1200 Subject: [PATCH 08/42] Added NS record for forward and reverse domains --- app/views.py | 2 +- config_template.py | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/app/views.py b/app/views.py index 8cc8761..276d99a 100644 --- a/app/views.py +++ b/app/views.py @@ -321,7 +321,7 @@ def domain(domain_name): if not re.search('ip6\.arpa|in-addr\.arpa$', domain_name): editable_records = app.config['RECORDS_ALLOW_EDIT'] else: - editable_records = ['PTR'] + editable_records = app.config['REVERSE_ALLOW_EDIT'] return render_template('domain.html', domain=domain, records=records, editable_records=editable_records) else: return redirect(url_for('error', code=404)) diff --git a/config_template.py b/config_template.py index 288ff47..f863486 100644 --- a/config_template.py +++ b/config_template.py @@ -75,7 +75,10 @@ PDNS_API_KEY = 'you never know' PDNS_VERSION = '3.4.7' # RECORDS ALLOWED TO EDIT -RECORDS_ALLOW_EDIT = ['A', 'AAAA', 'CNAME', 'SPF', 'PTR', 'MX', 'TXT'] +RECORDS_ALLOW_EDIT = ['A', 'AAAA', 'CNAME', 'SPF', 'PTR', 'MX', 'TXT', 'NS'] + +# RECORDS ALLOWED TO EDIT FOR REVERSE DOMAINS +REVERSE_ALLOW_EDIT = ['PTR', 'NS'] # EXPERIMENTAL FEATURES PRETTY_IPV6_PTR = False From 501c5292ab7af825a738901dfabfd2ce1e55924a Mon Sep 17 00:00:00 2001 From: Maysara A Date: Mon, 24 Jul 2017 21:08:25 -0400 Subject: [PATCH 09/42] binding with user credentials instead of preset LDAP user/pass --- app/models.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/models.py b/app/models.py index 9aee967..c9fcfc2 100644 --- a/app/models.py +++ b/app/models.py @@ -148,7 +148,8 @@ class User(db.Model): l.set_option( ldap.OPT_DEBUG_LEVEL, 255 ) l.protocol_version = ldap.VERSION3 - l.simple_bind_s(LDAP_USERNAME, LDAP_PASSWORD) + #l.simple_bind_s(LDAP_USERNAME, LDAP_PASSWORD) + l.simple_bind_s(self.username,self.password) ldap_result_id = l.search(baseDN, searchScope, searchFilter, retrieveAttributes) result_set = [] while 1: From 28c7a195e87301eaf802c51e07ab60c72132a86e Mon Sep 17 00:00:00 2001 From: Maysara Abdulhaq Date: Sun, 3 Sep 2017 14:23:18 -0400 Subject: [PATCH 10/42] add LDAP direct binding and GROUP_SECURITY --- app/models.py | 91 ++++++++++++++++++++++++++++++++-------------- config_template.py | 4 ++ 2 files changed, 68 insertions(+), 27 deletions(-) diff --git a/app/models.py b/app/models.py index c9fcfc2..ca95998 100644 --- a/app/models.py +++ b/app/models.py @@ -1,5 +1,6 @@ import os import ldap +import ldap.filter import time import base64 import bcrypt @@ -22,12 +23,24 @@ logging = logger('MODEL', app.config['LOG_LEVEL'], app.config['LOG_FILE']).confi if 'LDAP_TYPE' in app.config.keys(): LDAP_URI = app.config['LDAP_URI'] - LDAP_USERNAME = app.config['LDAP_USERNAME'] - LDAP_PASSWORD = app.config['LDAP_PASSWORD'] + + if 'LDAP_USERNAME' in app.config.keys() and 'LDAP_PASSWORD' in app.config.keys(): #backward compatability + LDAP_BIND_TYPE = 'search' + if 'LDAP_BIND_TYPE' in app.config.keys(): + LDAP_BIND_TYPE = app.config['LDAP_BIND_TYPE'] + if LDAP_BIND_TYPE == 'search': + LDAP_USERNAME = app.config['LDAP_USERNAME'] + LDAP_PASSWORD = app.config['LDAP_PASSWORD'] + LDAP_SEARCH_BASE = app.config['LDAP_SEARCH_BASE'] LDAP_TYPE = app.config['LDAP_TYPE'] LDAP_FILTER = app.config['LDAP_FILTER'] LDAP_USERNAMEFIELD = app.config['LDAP_USERNAMEFIELD'] + + LDAP_GROUP_SECURITY = app.config['LDAP_GROUP_SECURITY'] + if app.config['LDAP_GROUP_SECURITY'] == True: + LDAP_ADMIN_GROUP = app.config['LDAP_ADMIN_GROUP'] + LDAP_USER_GROUP = app.config['LDAP_USER_GROUP'] else: LDAP_TYPE = False @@ -141,15 +154,18 @@ class User(db.Model): try: ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_NEVER) l = ldap.initialize(LDAP_URI) - l.set_option(ldap.OPT_REFERRALS, 0) + l.set_option(ldap.OPT_REFERRALS, ldap.OPT_OFF) l.set_option(ldap.OPT_PROTOCOL_VERSION, 3) l.set_option(ldap.OPT_X_TLS,ldap.OPT_X_TLS_DEMAND) l.set_option( ldap.OPT_X_TLS_DEMAND, True ) l.set_option( ldap.OPT_DEBUG_LEVEL, 255 ) l.protocol_version = ldap.VERSION3 + if LDAP_BIND_TYPE == "direct": + global LDAP_USERNAME; LDAP_USERNAME = self.username + global LDAP_PASSWORD; LDAP_PASSWORD = self.password - #l.simple_bind_s(LDAP_USERNAME, LDAP_PASSWORD) - l.simple_bind_s(self.username,self.password) + + l.simple_bind_s(LDAP_USERNAME, LDAP_PASSWORD) ldap_result_id = l.search(baseDN, searchScope, searchFilter, retrieveAttributes) result_set = [] while 1: @@ -183,57 +199,78 @@ class User(db.Model): return False if method == 'LDAP': + allowedlogin = False + isadmin = False if not LDAP_TYPE: logging.error('LDAP authentication is disabled') return False - searchFilter = "(&(objectcategory=person)(samaccountname=%s))" % self.username + #searchFilter = "(&(objectcategory=person)(samaccountname=%s))" % self.username if LDAP_TYPE == 'ldap': searchFilter = "(&(%s=%s)%s)" % (LDAP_USERNAMEFIELD, self.username, LDAP_FILTER) logging.info('Ldap searchFilter "%s"' % searchFilter) result = self.ldap_search(searchFilter, LDAP_SEARCH_BASE) if not result: - logging.warning('User "%s" does not exist' % self.username) + logging.warning('LDAP User "%s" does not exist' % self.username) return False - ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_NEVER) - l = ldap.initialize(LDAP_URI) - l.set_option(ldap.OPT_REFERRALS, 0) - l.set_option(ldap.OPT_PROTOCOL_VERSION, 3) - l.set_option(ldap.OPT_X_TLS,ldap.OPT_X_TLS_DEMAND) - l.set_option( ldap.OPT_X_TLS_DEMAND, True ) - l.set_option( ldap.OPT_DEBUG_LEVEL, 255 ) - l.protocol_version = ldap.VERSION3 - try: - ldap_username = result[0][0][0] - l.simple_bind_s(ldap_username, self.password) - logging.info('User "%s" logged in successfully' % self.username) - except Exception: - logging.error('User "%s" input a wrong password' % self.username) + ldap_username = ldap.filter.escape_filter_chars(result[0][0][0]) + if LDAP_GROUP_SECURITY: + try: + if LDAP_TYPE == 'ldap': + ldap_user_dn = ldap.filter.escape_filter_chars(result[0][0][0]) + logging.info(result[0][0][0]) + if (self.ldap_search('(member=%s)' % ldap_user_dn ,LDAP_ADMIN_GROUP)): + allowedlogin = True + isadmin = True + logging.info('User %s is part of the "%s" group that allows admin access to PowerDNS-Admin' % (self.username,LDAP_ADMIN_GROUP)) + if (self.ldap_search('(member=%s)' % ldap_user_dn ,LDAP_USER_GROUP)): + #if (group == LDAP_USER_GROUP): + allowedlogin = True + logging.info('User %s is part of the "%s" group that allows user access to PowerDNS-Admin' % (self.username,LDAP_USER_GROUP)) + if allowedlogin == False: + logging.error('User %s is not part of the "%s" or "%s" groups that allow access to PowerDNS-Admin' % (self.username,LDAP_ADMIN_GROUP,LDAP_USER_GROUP)) + return False + except Exception, e: + logging.error('LDAP group lookup for user "%s" has failed' % e) + return False + logging.info('User "%s" logged in successfully' % self.username) + except Exception, e: + logging.error('User "%s" input a wrong LDAP password' % e) return False # create user if not exist in the db if not User.query.filter(User.username == self.username).first(): + self.firstname = self.username + self.lastname = '' try: # try to get user's firstname & lastname from LDAP # this might be changed in the future - self.firstname = result[0][0][1]['givenName'][0] - self.lastname = result[0][0][1]['sn'][0] - self.email = result[0][0][1]['mail'][0] - except Exception: - self.firstname = self.username - self.lastname = '' + self.firstname = result[0][0][1]['givenName'] + self.lastname = result[0][0][1]['sn'] + self.email = result[0][0][1]['mail'] + except Exception, e: + logging.info("reading ldap data threw an exception %s" % e) # first register user will be in Administrator role self.role_id = Role.query.filter_by(name='User').first().id if User.query.count() == 0: self.role_id = Role.query.filter_by(name='Administrator').first().id + # user will be in Administrator role if part of LDAP Admin group + if LDAP_GROUP_SECURITY: + if isadmin == True: + self.role_id = Role.query.filter_by(name='Administrator').first().id + self.create_user() logging.info('Created user "%s" in the DB' % self.username) + # user already exists in database, set their admin status based on group membership (if enabled) + if LDAP_GROUP_SECURITY: + self.set_admin(isadmin) + self.update_profile() return True logging.error('Unsupported authentication method') diff --git a/config_template.py b/config_template.py index 288ff47..0775840 100644 --- a/config_template.py +++ b/config_template.py @@ -44,6 +44,10 @@ LDAP_SEARCH_BASE = 'ou=System Admins,ou=People,dc=duykhanh,dc=me' # Additional options only if LDAP_TYPE=ldap LDAP_USERNAMEFIELD = 'uid' LDAP_FILTER = '(objectClass=inetorgperson)' +# enable LDAP_GROUP_SECURITY to allow Admin and User roles based on LDAP groups +#LDAP_GROUP_SECURITY = True # True or False +#LDAP_ADMIN_GROUP = 'CN=DnsAdmins,CN=Users,DC=example,DC=me' +#LDAP_USER_GROUP = 'CN=Domain Admins,CN=Users,DC=example,DC=me' ## AD CONFIG #LDAP_TYPE = 'ad' From 18df0ce4c9b9cd72e8419f487f657af727efc781 Mon Sep 17 00:00:00 2001 From: Maysara Abdulhaq Date: Sun, 3 Sep 2017 14:31:50 -0400 Subject: [PATCH 11/42] document LDAP_BIND_TYPE in config_template.py --- config_template.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/config_template.py b/config_template.py index 0775840..02b3ecb 100644 --- a/config_template.py +++ b/config_template.py @@ -38,6 +38,9 @@ SQLALCHEMY_TRACK_MODIFICATIONS = True # LDAP CONFIG LDAP_TYPE = 'ldap' LDAP_URI = 'ldaps://your-ldap-server:636' +# with LDAP_BIND_TYPE you can specify 'direct' or 'search' to use user credentials +# for binding or a predefined LDAP_USERNAME and LDAP_PASSWORD, binding with non-DN only works with AD +LDAP_BIND_TYPE= 'direct' # direct or search LDAP_USERNAME = 'cn=dnsuser,ou=users,ou=services,dc=duykhanh,dc=me' LDAP_PASSWORD = 'dnsuser' LDAP_SEARCH_BASE = 'ou=System Admins,ou=People,dc=duykhanh,dc=me' From a48417ac236d54f926b71ee83a5ae81ed6ea2540 Mon Sep 17 00:00:00 2001 From: Paul Hooijenga Date: Mon, 4 Sep 2017 15:34:01 +0200 Subject: [PATCH 12/42] Add missing template --- app/templates/dashboard_domain.html | 46 +++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 app/templates/dashboard_domain.html diff --git a/app/templates/dashboard_domain.html b/app/templates/dashboard_domain.html new file mode 100644 index 0000000..47fe2bf --- /dev/null +++ b/app/templates/dashboard_domain.html @@ -0,0 +1,46 @@ +{% macro name(domain) %} + {{ domain.name }} +{% endmacro %} + +{% macro dnssec(domain) %} + {% if domain.dnssec %} + + {% else %} + + {% endif %} +{% endmacro %} + +{% macro type(domain) %} + {{ domain.type }} +{% endmacro %} + +{% macro serial(domain) %} + {% if domain.serial == 0 %}{{ domain.notified_serial }}{% else %}{{domain.serial}}{% endif %} +{% endmacro %} + +{% macro master(domain) %} + {% if domain.master == '[]'%}N/A{% else %}{{ domain.master|display_master_name }}{% endif %} +{% endmacro %} + +{% macro actions(domain) %} + {% if current_user.role.name !='Administrator' %} + + + + {% else %} + + + + + {% endif %} +{% endmacro %} From 5d09daf8ebdaf45a1ca0e132cb57842ded425514 Mon Sep 17 00:00:00 2001 From: Paul Hooijenga Date: Fri, 15 Sep 2017 15:14:04 +0200 Subject: [PATCH 13/42] Fix dashboard domain query for non-admin users --- app/models.py | 12 +++++++----- app/views.py | 5 ++++- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/app/models.py b/app/models.py index 9aee967..71406d8 100644 --- a/app/models.py +++ b/app/models.py @@ -303,16 +303,18 @@ class User(db.Model): db.session.rollback() return False + def get_domain_query(self): + return db.session.query(User, DomainUser, Domain) \ + .filter(User.id == self.id) \ + .filter(User.id == DomainUser.user_id) \ + .filter(Domain.id == DomainUser.domain_id) + def get_domain(self): """ Get domains which user has permission to access """ - user_domains = [] - query = db.session.query(User, DomainUser, Domain).filter(User.id==self.id).filter(User.id==DomainUser.user_id).filter(Domain.id==DomainUser.domain_id).all() - for q in query: - user_domains.append(q[2]) - return user_domains + return [q[2] for q in self.get_domain_query()] def delete(self): """ diff --git a/app/views.py b/app/views.py index dfd8c4c..2c47138 100644 --- a/app/views.py +++ b/app/views.py @@ -292,7 +292,7 @@ def dashboard_domains(): if current_user.role.name == 'Administrator': domains = Domain.query else: - domains = User(id=current_user.id).get_domain() + domains = User(id=current_user.id).get_domain_query() template = app.jinja_env.get_template("dashboard_domain.html") render = template.make_module(vars={"current_user": current_user}) @@ -328,6 +328,9 @@ def dashboard_domains(): length = min(int(request.args.get("length", 0)), 100) domains = domains[start:start + length] + if current_user.role.name != 'Administrator': + domains = [d[2] for d in domains] + data = [] for domain in domains: data.append([ From 168f19950decd36445f223497eacfbf86e706730 Mon Sep 17 00:00:00 2001 From: Nils Sandmann Date: Tue, 19 Sep 2017 12:11:09 +0200 Subject: [PATCH 14/42] Corrected SRV record helper not showing Signed-off-by: Nils Sandmann --- app/templates/domain.html | 1 + 1 file changed, 1 insertion(+) diff --git a/app/templates/domain.html b/app/templates/domain.html index eeb4c27..89a2ce2 100644 --- a/app/templates/domain.html +++ b/app/templates/domain.html @@ -324,6 +324,7 @@ record_data.val(data); modal.modal('hide'); }) + modal.modal('show'); } else if (record_type == "SOA") { var modal = $("#modal_custom_record"); if (record_data.val() == "") { From a4b9722d47c8f418479204a8ea7b7c51b8708313 Mon Sep 17 00:00:00 2001 From: patito Date: Fri, 22 Sep 2017 15:28:09 +0100 Subject: [PATCH 15/42] Google OAuth --- app/__init__.py | 41 ++++++++++++++++++++++++++++++++++++++++ app/templates/login.html | 3 +++ app/views.py | 32 ++++++++++++++++++++++++++++++- config_template.py | 12 ++++++++++++ run.py | 2 +- 5 files changed, 88 insertions(+), 2 deletions(-) diff --git a/app/__init__.py b/app/__init__.py index bcb3525..96e36d8 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -11,6 +11,7 @@ login_manager = LoginManager() login_manager.init_app(app) db = SQLAlchemy(app) + def enable_github_oauth(GITHUB_ENABLE): if not GITHUB_ENABLE: return None, None @@ -46,7 +47,47 @@ def enable_github_oauth(GITHUB_ENABLE): return oauth, github + oauth, github = enable_github_oauth(app.config.get('GITHUB_OAUTH_ENABLE')) +def enable_google_oauth(GOOGLE_ENABLE): + if not GOOGLE_ENABLE: + return None + from flask_oauthlib.client import OAuth + oauth = OAuth(app) + + google = oauth.remote_app( + 'google', + consumer_key=app.config['GOOGLE_OAUTH_CLIENT_ID'], + consumer_secret=app.config['GOOGLE_OAUTH_CLIENT_SECRET'], + request_token_params=app.config['GOOGLE_TOKEN_PARAMS'], + base_url=app.config['GOOGLE_BASE_URL'], + request_token_url=None, + access_token_method='POST', + access_token_url=app.config['GOOGLE_TOKEN_URL'], + authorize_url=app.config['GOOGLE_AUTHORIZE_URL'], + ) + + @app.route('/user/authorized') + def authorized(): + resp = google.authorized_response() + if resp is None: + return 'Access denied: reason=%s error=%s' % ( + request.args['error_reason'], + request.args['error_description'] + ) + session['google_token'] = (resp['access_token'], '') + return redirect(url_for('.login')) + + @google.tokengetter + def get_google_oauth_token(): + return session.get('google_token') + + return google + + +google = enable_google_oauth(app.config.get('GOOGLE_OAUTH_ENABLE')) + + from app import views, models diff --git a/app/templates/login.html b/app/templates/login.html index 9527f22..ca32d04 100644 --- a/app/templates/login.html +++ b/app/templates/login.html @@ -98,6 +98,9 @@ + {% if google_enabled %} + Google oauth login + {% endif %} {% if github_enabled %} Github oauth login {% endif %} diff --git a/app/views.py b/app/views.py index 8cc8761..2023d53 100644 --- a/app/views.py +++ b/app/views.py @@ -17,7 +17,7 @@ from werkzeug import secure_filename from werkzeug.security import gen_salt from .models import User, Domain, Record, Server, History, Anonymous, Setting, DomainSetting -from app import app, login_manager, github +from app import app, login_manager, github, google from lib import utils @@ -160,6 +160,14 @@ def register(): else: return render_template('errors/404.html'), 404 + +@app.route('/google/login') +def google_login(): + if not app.config.get('GOOGLE_OAUTH_ENABLE'): + return abort(400) + return google.authorize(callback=url_for('authorized', _external=True)) + + @app.route('/github/login') def github_login(): if not app.config.get('GITHUB_OAUTH_ENABLE'): @@ -175,10 +183,30 @@ def login(): BASIC_ENABLED = app.config['BASIC_ENABLED'] SIGNUP_ENABLED = app.config['SIGNUP_ENABLED'] GITHUB_ENABLE = app.config.get('GITHUB_OAUTH_ENABLE') + GOOGLE_ENABLE = app.config.get('GOOGLE_OAUTH_ENABLE') if g.user is not None and current_user.is_authenticated: return redirect(url_for('dashboard')) + if 'google_token' in session: + user_data = google.get('userinfo').data + first_name = user_data['given_name'] + surname = user_data['family_name'] + email = user_data['email'] + user = User.query.filter_by(username=email).first() + if not user: + # create user + user = User(username=email, + firstname=first_name, + lastname=surname, + plain_text_password=gen_salt(7), + email=email) + user.create_local_user() + + session['user_id'] = user.id + login_user(user, remember = False) + return redirect(url_for('index')) + if 'github_token' in session: me = github.get('user') user_info = me.data @@ -197,6 +225,7 @@ def login(): if request.method == 'GET': return render_template('login.html', github_enabled=GITHUB_ENABLE, + google_enabled=GOOGLE_ENABLE, ldap_enabled=LDAP_ENABLED, login_title=LOGIN_TITLE, basic_enabled=BASIC_ENABLED, signup_enabled=SIGNUP_ENABLED) @@ -263,6 +292,7 @@ def login(): def logout(): session.pop('user_id', None) session.pop('github_token', None) + session.pop('google_token', None) logout_user() return redirect(url_for('login')) diff --git a/config_template.py b/config_template.py index 288ff47..cc4b983 100644 --- a/config_template.py +++ b/config_template.py @@ -65,6 +65,18 @@ GITHUB_OAUTH_URL = 'http://127.0.0.1:5000/api/v3/' GITHUB_OAUTH_TOKEN = 'http://127.0.0.1:5000/oauth/token' GITHUB_OAUTH_AUTHORIZE = 'http://127.0.0.1:5000/oauth/authorize' +# Google OAuth +GOOGLE_OAUTH_ENABLE = False +GOOGLE_OAUTH_CLIENT_ID = ' ' +GOOGLE_OAUTH_CLIENT_SECRET = ' ' +GOOGLE_REDIRECT_URI = '/Callback' +GOOGLE_TOKEN_URL = 'https://accounts.google.com/o/oauth2/token' +GOOGLE_TOKEN_PARAMS = { + 'scope': 'email profile' +} +GOOGLE_AUTHORIZE_URL='https://accounts.google.com/o/oauth2/auth' +GOOGLE_BASE_URL='https://www.googleapis.com/oauth2/v1/' + #Default Auth BASIC_ENABLED = True SIGNUP_ENABLED = True diff --git a/run.py b/run.py index e159643..7fb4ae4 100755 --- a/run.py +++ b/run.py @@ -3,7 +3,7 @@ from app import app from config import PORT try: - from config import BIND_ADDRESS + from config import BIND_ADDRESS except: BIND_ADDRESS = '127.0.0.1' From b8e7eea8b9966fbdd1737bf5b71ed16626dce825 Mon Sep 17 00:00:00 2001 From: patito Date: Thu, 28 Sep 2017 15:05:52 +0100 Subject: [PATCH 16/42] Changed redirect URI --- config_template.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config_template.py b/config_template.py index cc4b983..e6b5313 100644 --- a/config_template.py +++ b/config_template.py @@ -69,7 +69,7 @@ GITHUB_OAUTH_AUTHORIZE = 'http://127.0.0.1:5000/oauth/authorize' GOOGLE_OAUTH_ENABLE = False GOOGLE_OAUTH_CLIENT_ID = ' ' GOOGLE_OAUTH_CLIENT_SECRET = ' ' -GOOGLE_REDIRECT_URI = '/Callback' +GOOGLE_REDIRECT_URI = '/user/authorized' GOOGLE_TOKEN_URL = 'https://accounts.google.com/o/oauth2/token' GOOGLE_TOKEN_PARAMS = { 'scope': 'email profile' From 52a5789c856ac3e9e17833ae3e6bc10b0cb1472b Mon Sep 17 00:00:00 2001 From: Vadim Aleksandrov Date: Mon, 22 Jan 2018 18:22:19 +0300 Subject: [PATCH 17/42] Add first working draft of domain templating functionality --- app/models.py | 84 ++++++- app/templates/base.html | 1 + app/templates/domain_add.html | 9 + app/templates/template.html | 119 +++++++++ app/templates/template_add.html | 104 ++++++++ app/templates/template_edit.html | 415 +++++++++++++++++++++++++++++++ app/views.py | 137 +++++++++- create_db.py | 26 +- 8 files changed, 889 insertions(+), 6 deletions(-) create mode 100644 app/templates/template.html create mode 100644 app/templates/template_add.html create mode 100644 app/templates/template_edit.html diff --git a/app/models.py b/app/models.py index 9aee967..9bf2b58 100644 --- a/app/models.py +++ b/app/models.py @@ -1039,7 +1039,9 @@ class Record(object): }) postdata_for_new = {"rrsets": final_records} - + logging.info(postdata_for_new) + logging.info(postdata_for_delete) + logging.info(urlparse.urljoin(PDNS_STATS_URL, API_EXTENDED_URL + '/servers/localhost/zones/%s' % domain)) try: headers = {} headers['X-API-Key'] = PDNS_API_KEY @@ -1358,3 +1360,83 @@ class Setting(db.Model): logging.debug(traceback.format_exec()) db.session.rollback() return False + + +class DomainTemplate(db.Model): + __tablename__ = "domain_template" + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String(255), index=True, unique=True) + description = db.Column(db.String(255)) + records = db.relationship('DomainTemplateRecord', back_populates='template', cascade="all, delete-orphan") + + def __repr__(self): + return '' % self.name + + def __init__(self, name=None, description=None): + self.id = None + self.name = name + self.description = description + + def replace_records(self, records): + try: + self.records = [] + for record in records: + self.records.append(record) + db.session.commit() + return {'status': 'ok', 'msg': 'Template records have been modified'} + except Exception, e: + logging.error('Cannot create template records' + str(e)) + db.session.rollback() + return {'status': 'error', 'msg': 'Can not create template records'} + + def create(self): + try: + db.session.add(self) + db.session.commit() + return {'status': 'ok', 'msg': 'Template has been created'} + except Exception, e: + logging.error('Can not update domain template table.' + str(e)) + db.session.rollback() + return {'status': 'error', 'msg': 'Can not update domain template table'} + + def delete_template(self): + try: + self.records = [] + db.session.delete(self) + db.session.commit() + return {'status': 'ok', 'msg': 'Template has been deleted'} + except Exception, e: + logging.error('Can not delete domain template.' + str(e)) + db.session.rollback() + return {'status': 'error', 'msg': 'Can not delete domain template'} + + +class DomainTemplateRecord(db.Model): + __tablename__ = "domain_template_record" + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String(255)) + type = db.Column(db.String(64)) + ttl = db.Column(db.Integer) + data = db.Column(db.String(255)) + status = db.Column(db.Boolean) + template_id = db.Column(db.Integer, db.ForeignKey('domain_template.id')) + template = db.relationship('DomainTemplate', back_populates='records') + + def __repr__(self): + return '' % self.id + + def __init__(self, id=None, name=None, type=None, ttl=None, data=None, status=None): + self.id = id + self.name = name + self.type = type + self.ttl = ttl + self.data = data + self.status = status + + def apply(self): + try: + db.session.commit() + except Exception, e: + logging.error('Can not update domain template table.' + str(e)) + db.session.rollback() + return {'status': 'error', 'msg': 'Can not update domain template table'} diff --git a/app/templates/base.html b/app/templates/base.html index 06f239b..81eec24 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -127,6 +127,7 @@
  • New Domain
  • ADMINISTRATION
  • Admin Console
  • +
  • Domain Templates
  • Users
  • History
  • Settings
  • diff --git a/app/templates/domain_add.html b/app/templates/domain_add.html index 04add24..8da216a 100644 --- a/app/templates/domain_add.html +++ b/app/templates/domain_add.html @@ -47,6 +47,15 @@ +
    + + +
    diff --git a/app/templates/template.html b/app/templates/template.html new file mode 100644 index 0000000..ac58c82 --- /dev/null +++ b/app/templates/template.html @@ -0,0 +1,119 @@ +{% extends "base.html" %} +{% block title %}DNS Control Panel - Templates{% endblock %} + +{% block dashboard_stat %} + +
    +

    + Templates + List +

    + +
    +{% endblock %} +{% block content %} + +
    + {% with errors = get_flashed_messages(category_filter=["error"]) %} {% if errors %} +
    +
    +
    + +

    + Error! +

    +
    + x +
      + {%- for msg in errors %} +
    • {{ msg }}
    • {% endfor -%} +
    +
    +
    +
    +
    +{% endif %} {% endwith %} +
    +
    +
    +
    +

    Templates

    +
    + +
    + + + + + + + + + + + + {% for template in templates %} + + + + + + + + {% endfor %} + +
    NameDescriptionNumber of RecordsEditDelete
    + {{ template.name }} + + {{ template.description }} + + {{ template.records|count }} + + + + + + + + +
    +
    + +
    + +
    + +
    + +
    + +{% endblock %} +{% block extrascripts %} + +{% endblock %} +{% block modals %} +{% endblock %} diff --git a/app/templates/template_add.html b/app/templates/template_add.html new file mode 100644 index 0000000..772c204 --- /dev/null +++ b/app/templates/template_add.html @@ -0,0 +1,104 @@ +{% extends "base.html" %} +{% block title %}DNS Control Panel - Create Template{% endblock %} + +{% block dashboard_stat %} + +
    +

    + Template + Create +

    + +
    +{% endblock %} + +{% block content %} +
    +{% with errors = get_flashed_messages(category_filter=["error"]) %} {% +if errors %} +
    +
    +
    + +

    + Error! +

    +
    + x +
      + {%- for msg in errors %} +
    • {{ msg }}
    • {% endfor -%} +
    +
    +
    +
    +
    +{% endif %} {% endwith %} + +
    +
    +
    +
    +

    Create new template

    +
    + + +
    +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    +
    +
    +

    Help with creating a new template

    +
    +
    +
    +
    Template name
    +
    Enter your template name, this is the name of the template that + will be shown to users. The name should not have any spaces but + can have symbols.
    +
    Template description
    +
    Enter your template description, this is to help better + identify the template.
    +
    +
    +
    +
    +
    +
    +{% endblock %} +{% block extrascripts %} + +{% endblock %} diff --git a/app/templates/template_edit.html b/app/templates/template_edit.html new file mode 100644 index 0000000..64f9799 --- /dev/null +++ b/app/templates/template_edit.html @@ -0,0 +1,415 @@ +{% extends "base.html" %} +{% block title %}DNS Control Panel - Edit Template{% endblock %} + +{% block dashboard_stat %} +
    +

    + Edit template {{ template }} +

    + +
    +{% endblock %} + +{% block content %} +
    +
    +
    +
    +
    +

    Manage Template Records for {{ template }}

    +
    +
    + + +
    +
    + + + + + + + + + + + + + + + {% for record in records %} + + + + + + + + + + + + {% endfor %} + +
    NameTypeStatusTTLDataEditDeleteID
    + {{ record.name }} + + {{ record.type }} + + {{ record.status }} + + {{ record.ttl }} + + {{ record.data }} + + + + + + {{ record.id }} +
    +
    +
    +
    +
    +
    +{% endblock %} +{% block extrascripts %} + +{% endblock %} +{% block modals %} + + + +{% endblock %} diff --git a/app/views.py b/app/views.py index 8cc8761..8cab755 100644 --- a/app/views.py +++ b/app/views.py @@ -11,12 +11,12 @@ from io import BytesIO import jinja2 import qrcode as qrc import qrcode.image.svg as qrc_svg -from flask import g, request, make_response, jsonify, render_template, session, redirect, url_for, send_from_directory, abort +from flask import g, request, make_response, jsonify, render_template, session, redirect, url_for, send_from_directory, abort, flash from flask_login import login_user, logout_user, current_user, login_required from werkzeug import secure_filename from werkzeug.security import gen_salt -from .models import User, Domain, Record, Server, History, Anonymous, Setting, DomainSetting +from .models import User, Domain, Record, Server, History, Anonymous, Setting, DomainSetting, DomainTemplate, DomainTemplateRecord from app import app, login_manager, github from lib import utils @@ -331,10 +331,12 @@ def domain(domain_name): @login_required @admin_role_required def domain_add(): + templates = DomainTemplate.query.all() if request.method == 'POST': try: domain_name = request.form.getlist('domain_name')[0] domain_type = request.form.getlist('radio_type')[0] + domain_template = request.form.getlist('domain_template')[0] soa_edit_api = request.form.getlist('radio_type_soa_edit_api')[0] if ' ' in domain_name or not domain_name or not domain_type: @@ -352,12 +354,27 @@ def domain_add(): if result['status'] == 'ok': history = History(msg='Add domain %s' % domain_name, detail=str({'domain_type': domain_type, 'domain_master_ips': domain_master_ips}), created_by=current_user.username) history.add() + if domain_template != '0': + template = DomainTemplate.query.filter(DomainTemplate.id == domain_template).first() + template_records = DomainTemplateRecord.query.filter(DomainTemplateRecord.template_id == domain_template).all() + record_data = [] + for template_record in template_records: + record_row = {'record_data': template_record.data, 'record_name': template_record.name, 'record_status': template_record.status, 'record_ttl': template_record.ttl, 'record_type': template_record.type} + record_data.append(record_row) + r = Record() + result = r.apply(domain_name, record_data) + if result['status'] == 'ok': + history = History(msg='Applying template %s to %s, created records successfully.' % (template.name, domain_name), detail=str(result), created_by=current_user.username) + history.add() + else: + history = History(msg='Applying template %s to %s, FAILED to created records.' % (template.name, domain_name), detail=str(result), created_by=current_user.username) + history.add() return redirect(url_for('dashboard')) else: return render_template('errors/400.html', msg=result['msg']), 400 except: return redirect(url_for('error', code=500)) - return render_template('domain_add.html') + return render_template('domain_add.html', templates=templates) @app.route('/admin/domain//delete', methods=['GET']) @@ -518,6 +535,120 @@ def admin_setdomainsetting(domain_name): return make_response(jsonify( { 'status': 'error', 'msg': 'There is something wrong, please contact Administrator.' } ), 400) +@app.route('/templates', methods=['GET', 'POST']) +@app.route('/templates/list', methods=['GET', 'POST']) +@login_required +@admin_role_required +def templates(): + templates = DomainTemplate.query.all() + return render_template('template.html', templates=templates) + + +@app.route('/template/create', methods=['GET', 'POST']) +@login_required +@admin_role_required +def create_template(): + if request.method == 'GET': + return render_template('template_add.html') + if request.method == 'POST': + try: + name = request.form.getlist('name')[0] + description = request.form.getlist('description')[0] + + if ' ' in name or not name or not type: + flash("Please correct your input", 'error') + return redirect(url_for('create_template')) + + if DomainTemplate.query.filter(DomainTemplate.name == name).first(): + flash("A template with the name %s already exists!" % name, 'error') + return redirect(url_for('create_template')) + t = DomainTemplate(name=name, description=description) + result = t.create() + if result['status'] == 'ok': + history = History(msg='Add domain template %s' % name, detail=str({'name': name, 'description': description}), created_by=current_user.username) + history.add() + return redirect(url_for('templates')) + else: + flash(result['msg'], 'error') + return redirect(url_for('create_template')) + except Exception: + print traceback.format_exc() + return redirect(url_for('error', code=500)) + return redirect(url_for('templates')) + + +@app.route('/template//edit', methods=['GET']) +@login_required +@admin_role_required +def edit_template(template): + try: + t = DomainTemplate.query.filter(DomainTemplate.name == template).first() + if t is not None: + records = [] + for jr in t.records: + if jr.type in app.config['RECORDS_ALLOW_EDIT']: + record = DomainTemplateRecord(name=jr.name, type=jr.type, status='Disabled' if jr.status else 'Active', ttl=jr.ttl, data=jr.data) + records.append(record) + + return render_template('template_edit.html', template=t.name, records=records, editable_records=app.config['RECORDS_ALLOW_EDIT']) + except Exception: + print traceback.format_exc() + return redirect(url_for('error', code=500)) + return redirect(url_for('templates')) + + +@app.route('/template//apply', methods=['POST'], strict_slashes=False) +@login_required +def apply_records(template): + try: + pdata = request.data + jdata = json.loads(pdata) + records = [] + + for j in jdata: + name = '@' if j['record_name'] in ['@', ''] else j['record_name'] + type = j['record_type'] + data = j['record_data'] + disabled = True if j['record_status'] == 'Disabled' else False + ttl = int(j['record_ttl']) if j['record_ttl'] else 3600 + + dtr = DomainTemplateRecord(name=name, type=type, data=data, status=disabled, ttl=ttl) + records.append(dtr) + + t = DomainTemplate.query.filter(DomainTemplate.name == template).first() + result = t.replace_records(records) + if result['status'] == 'ok': + history = History(msg='Apply domain template record changes to domain template %s' % template, detail=str(jdata), created_by=current_user.username) + history.add() + return make_response(jsonify(result), 200) + else: + return make_response(jsonify(result), 400) + except: + print traceback.format_exc() + return make_response(jsonify({'status': 'error', 'msg': 'Error when applying new changes'}), 500) + + +@app.route('/template//delete', methods=['GET']) +@login_required +@admin_role_required +def delete_template(template): + try: + t = DomainTemplate.query.filter(DomainTemplate.name == template).first() + if t is not None: + result = t.delete_template() + if result['status'] == 'ok': + history = History(msg='Deleted domain template %s' % template, detail=str({'name': template}), created_by=current_user.username) + history.add() + return redirect(url_for('templates')) + else: + flash(result['msg'], 'error') + return redirect(url_for('templates')) + except Exception: + print traceback.format_exc() + return redirect(url_for('error', code=500)) + return redirect(url_for('templates')) + + @app.route('/admin', methods=['GET', 'POST']) @login_required @admin_role_required diff --git a/create_db.py b/create_db.py index 41c874d..25899fd 100755 --- a/create_db.py +++ b/create_db.py @@ -4,7 +4,7 @@ from migrate.versioning import api from config import SQLALCHEMY_DATABASE_URI from config import SQLALCHEMY_MIGRATE_REPO from app import db -from app.models import Role, Setting +from app.models import Role, Setting, DomainTemplate import os.path import time import sys @@ -65,6 +65,23 @@ def init_settings(db, setting_names): for setting in settings: db.session.add(setting) + +def init_domain_templates(db, domain_template_names): + + # Get key name of data + name_of_domain_templates = map(lambda r: r.name, domain_template_names) + + # Query to get current data + rows = db.session.query(DomainTemplate).filter(DomainTemplate.name.in_(name_of_domain_templates)).all() + + # Check which data that need to insert + name_of_rows = map(lambda r: r.name, rows) + domain_templates = filter(lambda r: r.name not in name_of_rows, domain_template_names) + + # Insert data + for domain_template in domain_templates: + db.session.add(domain_template) + def init_records(): # Create initial user roles and turn off maintenance mode init_roles(db, [ @@ -80,7 +97,12 @@ def init_records(): Setting('default_domain_table_size', '10'), Setting('auto_ptr','False') ]) - + # TODO: add sample records to sample templates + init_domain_templates(db, [ + DomainTemplate('basic_template_1', 'Basic Template #1'), + DomainTemplate('basic_template_2', 'Basic Template #2'), + DomainTemplate('basic_template_3', 'Basic Template #3') + ]) db_commit = db.session.commit() commit_version_control(db_commit) From 12cfc4dbc10393f9db3ccfb60df197d001f2306b Mon Sep 17 00:00:00 2001 From: Vadim Aleksandrov Date: Tue, 23 Jan 2018 18:23:21 +0300 Subject: [PATCH 18/42] Added the ability to create a template based on the zone records --- app/templates/dashboard.html | 53 ++++++++++++++++++++++++++++ app/templates/template.html | 2 +- app/templates/template_edit.html | 2 +- app/views.py | 60 ++++++++++++++++++++++++++++++++ 4 files changed, 115 insertions(+), 2 deletions(-) diff --git a/app/templates/dashboard.html b/app/templates/dashboard.html index dee30fc..2db64aa 100644 --- a/app/templates/dashboard.html +++ b/app/templates/dashboard.html @@ -181,6 +181,11 @@ {% else %} + {% if current_user.role.name == 'Administrator' %} + + {% endif %} @@ -242,6 +247,31 @@ var domain = $(this).prop('id'); getdnssec($SCRIPT_ROOT + '/domain/' + domain + '/dnssec'); }); + + $(document.body).on("click", ".button_template", function (e) { + var modal = $("#modal_template"); + var domain = $(this).prop('id'); + var form = " \ + \ + \ + \ + \ + "; + modal.find('.modal-body p').html(form); + modal.find('#button_save').click(function() { + var data = {}; + data['name'] = modal.find('#template_name').val(); + data['description'] = modal.find('#template_description').val(); + data['domain'] = modal.find('#domain').val(); + applyChanges(data, $SCRIPT_ROOT + "{{ url_for('create_template_from_zone') }}", true); + modal.modal('hide'); + }) + modal.find('#button_close').click(function() { + modal.modal('hide'); + }) + + modal.modal('show'); + }); {% endblock %} {% block modals %} @@ -291,4 +321,27 @@ + {% endblock %} diff --git a/app/templates/template.html b/app/templates/template.html index ac58c82..baaada0 100644 --- a/app/templates/template.html +++ b/app/templates/template.html @@ -65,7 +65,7 @@ {% for template in templates %} - {{ template.name }} + {{ template.name }} {{ template.description }} diff --git a/app/templates/template_edit.html b/app/templates/template_edit.html index 64f9799..3445eb0 100644 --- a/app/templates/template_edit.html +++ b/app/templates/template_edit.html @@ -265,7 +265,6 @@ \ \ "; - modal.modal('show'); } else { var parts = record_data.val().split(" "); var form = " \ @@ -288,6 +287,7 @@ record_data.val(data); modal.modal('hide'); }) + modal.modal('show'); } else if (record_type == "SOA") { var modal = $("#modal_custom_record"); if (record_data.val() == "") { diff --git a/app/views.py b/app/views.py index 8cab755..8f4cc0a 100644 --- a/app/views.py +++ b/app/views.py @@ -577,6 +577,66 @@ def create_template(): return redirect(url_for('templates')) +@app.route('/template/createfromzone', methods=['POST']) +@login_required +@admin_role_required +def create_template_from_zone(): + try: + pdata = request.data + jdata = json.loads(pdata) + name = jdata['name'] + description = jdata['description'] + domain_name = jdata['domain'] + + if ' ' in name or not name or not type: + return make_response(jsonify({'status': 'error', 'msg': 'Please correct template name'}), 500) + + if DomainTemplate.query.filter(DomainTemplate.name == name).first(): + return make_response(jsonify({'status': 'error', 'msg': 'A template with the name %s already exists!' % name}), 500) + + t = DomainTemplate(name=name, description=description) + result = t.create() + if result['status'] == 'ok': + history = History(msg='Add domain template %s' % name, detail=str({'name': name, 'description': description}), created_by=current_user.username) + history.add() + + records = [] + r = Record() + domain = Domain.query.filter(Domain.name == domain_name).first() + if domain: + # query domain info from PowerDNS API + zone_info = r.get_record_data(domain.name) + if zone_info: + jrecords = zone_info['records'] + + if NEW_SCHEMA: + for jr in jrecords: + name = '@' if jr['name'] == domain_name else jr['name'] + if jr['type'] in app.config['RECORDS_ALLOW_EDIT']: + for subrecord in jr['records']: + + record = DomainTemplateRecord(name=name, type=jr['type'], status=True if subrecord['disabled'] else False, ttl=jr['ttl'], data=subrecord['content']) + records.append(record) + else: + for jr in jrecords: + if jr['type'] in app.config['RECORDS_ALLOW_EDIT']: + record = DomainTemplateRecord(name=name, type=jr['type'], status=True if jr['disabled'] else False, ttl=jr['ttl'], data=jr['content']) + records.append(record) + result_records = t.replace_records(records) + + if result_records['status'] == 'ok': + return make_response(jsonify({'status': 'ok', 'msg': result['msg']}), 200) + else: + result = t.delete_template() + return make_response(jsonify({'status': 'error', 'msg': result_records['msg']}), 500) + + else: + return make_response(jsonify({'status': 'error', 'msg': result['msg']}), 500) + except Exception: + print traceback.format_exc() + return make_response(jsonify({'status': 'error', 'msg': 'Error when applying new changes'}), 500) + + @app.route('/template//edit', methods=['GET']) @login_required @admin_role_required From 0355fe429364cbe47bdb56f9bc89852d846155f1 Mon Sep 17 00:00:00 2001 From: Vadim Aleksandrov Date: Wed, 7 Feb 2018 16:09:06 +0300 Subject: [PATCH 19/42] Join "Edit" and "Delete" button into th on templates page --- app/templates/template.html | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/app/templates/template.html b/app/templates/template.html index baaada0..19c8efc 100644 --- a/app/templates/template.html +++ b/app/templates/template.html @@ -57,8 +57,7 @@ Name Description Number of Records - Edit - Delete + Action @@ -79,8 +78,6 @@ Edit  - - {% else %} {% endif %} - {% if record.is_allowed() %} + {% if record.is_allowed_delete() %} - {% else %} - {% endif %} {% else %} - + - - + + {% endif %} diff --git a/app/views.py b/app/views.py index 8f4cc0a..b59cfee 100644 --- a/app/views.py +++ b/app/views.py @@ -966,7 +966,7 @@ def dyndns_update(): r = Record() r.name = hostname # check if the user requested record exists within this domain - if r.exists(domain.name) and r.is_allowed: + if r.exists(domain.name) and r.is_allowed_edit(): if r.data == myip: # record content did not change, return 'nochg' history = History(msg="DynDNS update: attempted update of %s but record did not change" % hostname, created_by=current_user.username) @@ -981,7 +981,7 @@ def dyndns_update(): return render_template('dyndns.html', response='good'), 200 else: return render_template('dyndns.html', response='911'), 200 - elif r.is_allowed: + elif r.is_allowed_edit(): ondemand_creation = DomainSetting.query.filter(DomainSetting.domain == domain).filter(DomainSetting.setting == 'create_via_dyndns').first() if (ondemand_creation != None) and (strtobool(ondemand_creation.value) == True): record = Record(name=hostname,type='A',data=myip,status=False,ttl=3600) From 6f4cc4280534f0e4c857e6aab5325cb981dcc2a1 Mon Sep 17 00:00:00 2001 From: Vadim Aleksandrov Date: Fri, 9 Feb 2018 15:32:50 +0300 Subject: [PATCH 21/42] Fix issue with LDAP search filter. It is necessary to bracket the expression with additional filter conditions --- app/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models.py b/app/models.py index 9aee967..5908603 100644 --- a/app/models.py +++ b/app/models.py @@ -188,7 +188,7 @@ class User(db.Model): searchFilter = "(&(objectcategory=person)(samaccountname=%s))" % self.username if LDAP_TYPE == 'ldap': - searchFilter = "(&(%s=%s)%s)" % (LDAP_USERNAMEFIELD, self.username, LDAP_FILTER) + searchFilter = "(&(%s=%s)(%s))" % (LDAP_USERNAMEFIELD, self.username, LDAP_FILTER) logging.info('Ldap searchFilter "%s"' % searchFilter) result = self.ldap_search(searchFilter, LDAP_SEARCH_BASE) From b0caf0ca48017371b06ae0667a0177a5d456cfbf Mon Sep 17 00:00:00 2001 From: Vadim Aleksandrov Date: Fri, 9 Feb 2018 15:37:28 +0300 Subject: [PATCH 22/42] Fix issue with inserting into the database fields 'firstname' and 'lastname' containing non-ascii characters that can be retrieved from LDAP --- app/models.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/models.py b/app/models.py index 5908603..39875e7 100644 --- a/app/models.py +++ b/app/models.py @@ -9,6 +9,7 @@ import traceback import pyotp import re import dns.reversename +import sys from datetime import datetime from distutils.util import strtobool @@ -221,6 +222,12 @@ class User(db.Model): self.firstname = result[0][0][1]['givenName'][0] self.lastname = result[0][0][1]['sn'][0] self.email = result[0][0][1]['mail'][0] + + if sys.version_info < (3,): + if isinstance(self.firstname, str): + self.firstname = self.firstname.decode('utf-8') + if isinstance(self.lastname, str): + self.lastname = self.lastname.decode('utf-8') except Exception: self.firstname = self.username self.lastname = '' From 0436d69ea63e6896809ae225cadd95dd866ae56f Mon Sep 17 00:00:00 2001 From: Vadim Aleksandrov Date: Fri, 9 Feb 2018 15:41:19 +0300 Subject: [PATCH 23/42] Adding the ability to use 'LDAP_USERNAMEFIELD' and 'LDAP_FILTER' in case of use with Active Directory for authorization --- app/models.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/app/models.py b/app/models.py index 39875e7..6f7e1af 100644 --- a/app/models.py +++ b/app/models.py @@ -187,11 +187,13 @@ class User(db.Model): logging.error('LDAP authentication is disabled') return False - searchFilter = "(&(objectcategory=person)(samaccountname=%s))" % self.username - if LDAP_TYPE == 'ldap': - searchFilter = "(&(%s=%s)(%s))" % (LDAP_USERNAMEFIELD, self.username, LDAP_FILTER) - logging.info('Ldap searchFilter "%s"' % searchFilter) + if LDAP_TYPE == 'ad': + searchFilter = "(&(objectcategory=person)(%s=%s)(%s))" % (LDAP_USERNAMEFIELD, self.username, LDAP_FILTER) + elif LDAP_TYPE == 'ldap': + searchFilter = "(&(%s=%s)(%s))" % (LDAP_USERNAMEFIELD, self.username, LDAP_FILTER) + + logging.info('Ldap searchFilter "%s"' % searchFilter) result = self.ldap_search(searchFilter, LDAP_SEARCH_BASE) if not result: logging.warning('User "%s" does not exist' % self.username) From b5b3b77acbf404585bbaf75ad41e29cf89e676a2 Mon Sep 17 00:00:00 2001 From: Khanh Ngo Date: Fri, 30 Mar 2018 13:49:35 +0700 Subject: [PATCH 24/42] Adjustment to work with Python3 --- .gitignore | 1 + Dockerfile | 17 +++++++++ app/lib/utils.py | 30 ++++++++-------- app/models.py | 71 +++++++++++++++++++------------------- app/views.py | 78 +++++++++++++++++++++++++----------------- config_template.py | 2 +- configs/development.py | 59 ++++++++++++++++++++++++++++++++ create_db.py | 26 ++++++++------ db_downgrade.py | 2 +- db_migrate.py | 2 +- db_upgrade.py | 2 +- docker-compose.dev.yml | 22 ++++++++++++ docker-compose.yml | 50 --------------------------- requirements.txt | 28 ++++++++------- supervisord.conf | 23 +++++++++++++ 15 files changed, 254 insertions(+), 159 deletions(-) create mode 100644 Dockerfile create mode 100644 configs/development.py create mode 100644 docker-compose.dev.yml delete mode 100644 docker-compose.yml create mode 100644 supervisord.conf diff --git a/.gitignore b/.gitignore index d548024..eb7fa03 100644 --- a/.gitignore +++ b/.gitignore @@ -23,6 +23,7 @@ nosetests.xml flask config.py logfile.log +log.txt db_repository/* upload/avatar/* diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..8ee6b7d --- /dev/null +++ b/Dockerfile @@ -0,0 +1,17 @@ +FROM ubuntu:latest +MAINTAINER Khanh Ngo "ngokhanhit@gmail.com" +ARG ENVIRONMENT=development +ENV ENVIRONMENT=${ENVIRONMENT} + +WORKDIR /powerdns-admin + +RUN apt-get update -y +RUN apt-get install -y python3-pip python3-dev libmysqlclient-dev supervisor +RUN apt-get install -y libsasl2-dev libldap2-dev libssl-dev + +COPY ./requirements.txt /powerdns-admin/requirements.txt +RUN pip3 install -r requirements.txt + +ADD ./supervisord.conf /etc/supervisord.conf +ADD . /powerdns-admin/ +COPY ./configs/${ENVIRONMENT}.py /powerdns-admin/config.py diff --git a/app/lib/utils.py b/app/lib/utils.py index a4a2954..e122043 100644 --- a/app/lib/utils.py +++ b/app/lib/utils.py @@ -2,20 +2,21 @@ import re import sys import json import requests -import urlparse import hashlib from app import app from distutils.version import StrictVersion +from urllib.parse import urlparse if 'TIMEOUT' in app.config.keys(): TIMEOUT = app.config['TIMEOUT'] else: TIMEOUT = 10 + def auth_from_url(url): auth = None - parsed_url = urlparse.urlparse(url).netloc + parsed_url = urlparse(url).netloc if '@' in parsed_url: auth = parsed_url.split('@')[0].split(':') auth = requests.auth.HTTPBasicAuth(auth[0], auth[1]) @@ -55,7 +56,7 @@ def fetch_remote(remote_url, method='GET', data=None, accept=None, params=None, if r.status_code not in (200, 400, 422): r.raise_for_status() except Exception as e: - raise RuntimeError("While fetching " + remote_url + ": " + str(e)), None, sys.exc_info()[2] + raise RuntimeError('Error while fetching {0}'.format(remote_url)) from e return r @@ -72,16 +73,16 @@ def fetch_json(remote_url, method='GET', data=None, params=None, headers=None): try: assert('json' in r.headers['content-type']) except Exception as e: - raise Exception("While fetching " + remote_url + ": " + str(e)), None, sys.exc_info()[2] + raise RuntimeError('Error while fetching {0}'.format(remote_url)) from e # don't use r.json here, as it will read from r.text, which will trigger # content encoding auto-detection in almost all cases, WHICH IS EXTREMELY # SLOOOOOOOOOOOOOOOOOOOOOOW. just don't. data = None try: - data = json.loads(r.content) - except UnicodeDecodeError: - data = json.loads(r.content, 'iso-8859-1') + data = json.loads(r.content.decode('utf-8')) + except Exception as e: + raise RuntimeError('Error while loading JSON data from {0}'.format(remote_url)) from e return data @@ -92,6 +93,7 @@ def display_record_name(data): else: return record_name.replace('.'+domain_name, '') + def display_master_name(data): """ input data: "[u'127.0.0.1', u'8.8.8.8']" @@ -99,6 +101,7 @@ def display_master_name(data): matches = re.findall(r'\'(.+?)\'', data) return ", ".join(matches) + def display_time(amount, units='s', remove_seconds=True): """ Convert timestamp to normal time format @@ -139,6 +142,7 @@ def display_time(amount, units='s', remove_seconds=True): return final_string + def pdns_api_extended_uri(version): """ Check the pdns version @@ -148,14 +152,10 @@ def pdns_api_extended_uri(version): else: return "" -def email_to_gravatar_url(email, size=100): + +def email_to_gravatar_url(email="", size=100): """ AD doesn't necessarily have email """ - - if not email: - email="" - - - hash_string = hashlib.md5(email).hexdigest() - return "https://s.gravatar.com/avatar/%s?s=%s" % (hash_string, size) + hash_string = hashlib.md5(email.encode('utf-8')).hexdigest() + return "https://s.gravatar.com/avatar/{0}?s={1}".format(hash_string, size) diff --git a/app/models.py b/app/models.py index 9aee967..e5d3cb6 100644 --- a/app/models.py +++ b/app/models.py @@ -3,7 +3,6 @@ import ldap import time import base64 import bcrypt -import urlparse import itertools import traceback import pyotp @@ -11,13 +10,14 @@ import re import dns.reversename from datetime import datetime +from urllib.parse import urljoin from distutils.util import strtobool from distutils.version import StrictVersion from flask_login import AnonymousUserMixin from app import app, db -from lib import utils -from lib.log import logger +from app.lib import utils +from app.lib.log import logger logging = logger('MODEL', app.config['LOG_LEVEL'], app.config['LOG_FILE']).config() if 'LDAP_TYPE' in app.config.keys(): @@ -160,7 +160,7 @@ class User(db.Model): result_set.append(result_data) return result_set - except ldap.LDAPError, e: + except ldap.LDAPError as e: logging.error(e) raise @@ -269,6 +269,7 @@ class User(db.Model): self.role_id = Role.query.filter_by(name='User').first().id if User.query.count() == 0: self.role_id = Role.query.filter_by(name='Administrator').first().id + self.password = self.get_hashed_password(self.plain_text_password) db.session.add(self) @@ -451,8 +452,8 @@ class Domain(db.Model): self.settings.append(DomainSetting(setting=setting, value=value)) db.session.commit() return True - except Exception, e: - logging.error('Can not create setting %s for domain %s. %s' % (setting, self.name, str(e))) + except Exception as e: + logging.error('Can not create setting %s for domain %s. %s' % (setting, self.name, e)) return False def get_domains(self): @@ -476,7 +477,7 @@ class Domain(db.Model): """ headers = {} headers['X-API-Key'] = PDNS_API_KEY - jdata = utils.fetch_json(urlparse.urljoin(PDNS_STATS_URL, API_EXTENDED_URL + '/servers/localhost/zones'), headers=headers) + jdata = utils.fetch_json(urljoin(PDNS_STATS_URL, API_EXTENDED_URL + '/servers/localhost/zones'), headers=headers) return jdata def get_id_by_name(self, name): @@ -500,7 +501,7 @@ class Domain(db.Model): headers = {} headers['X-API-Key'] = PDNS_API_KEY try: - jdata = utils.fetch_json(urlparse.urljoin(PDNS_STATS_URL, API_EXTENDED_URL + '/servers/localhost/zones'), headers=headers) + jdata = utils.fetch_json(urljoin(PDNS_STATS_URL, API_EXTENDED_URL + '/servers/localhost/zones'), headers=headers) list_jdomain = [d['name'].rstrip('.') for d in jdata] try: # domains should remove from db since it doesn't exist in powerdns anymore @@ -564,8 +565,8 @@ class Domain(db.Model): except: db.session.rollback() return {'status': 'ok', 'msg': 'Domain table has been updated successfully'} - except Exception, e: - logging.error('Can not update domain table.' + str(e)) + except Exception as e: + logging.error('Can not update domain table. Error: {0}'.format(e)) return {'status': 'error', 'msg': 'Can not update domain table'} def add(self, domain_name, domain_type, soa_edit_api, domain_ns=[], domain_master_ips=[]): @@ -596,17 +597,17 @@ class Domain(db.Model): } try: - jdata = utils.fetch_json(urlparse.urljoin(PDNS_STATS_URL, API_EXTENDED_URL + '/servers/localhost/zones'), headers=headers, method='POST', data=post_data) + jdata = utils.fetch_json(urljoin(PDNS_STATS_URL, API_EXTENDED_URL + '/servers/localhost/zones'), headers=headers, method='POST', data=post_data) if 'error' in jdata.keys(): logging.error(jdata['error']) return {'status': 'error', 'msg': jdata['error']} else: logging.info('Added domain %s successfully' % domain_name) return {'status': 'ok', 'msg': 'Added domain successfully'} - except Exception, e: - print traceback.format_exc() + except Exception as e: + traceback.print_exc() logging.error('Cannot add domain %s' % domain_name) - logging.debug(str(e)) + logging.debug(e) return {'status': 'error', 'msg': 'Cannot add this domain.'} def create_reverse_domain(self, domain_name, domain_reverse_name): @@ -671,13 +672,13 @@ class Domain(db.Model): headers = {} headers['X-API-Key'] = PDNS_API_KEY try: - jdata = utils.fetch_json(urlparse.urljoin(PDNS_STATS_URL, API_EXTENDED_URL + '/servers/localhost/zones/%s' % domain_name), headers=headers, method='DELETE') + jdata = utils.fetch_json(urljoin(PDNS_STATS_URL, API_EXTENDED_URL + '/servers/localhost/zones/%s' % domain_name), headers=headers, method='DELETE') logging.info('Delete domain %s successfully' % domain_name) return {'status': 'ok', 'msg': 'Delete domain successfully'} - except Exception, e: - print traceback.format_exc() + except Exception as e: + traceback.print_exc() logging.error('Cannot delete domain %s' % domain_name) - logging.debug(str(e)) + logging.debug(e) return {'status': 'error', 'msg': 'Cannot delete domain'} def get_user(self): @@ -730,7 +731,7 @@ class Domain(db.Model): headers = {} headers['X-API-Key'] = PDNS_API_KEY try: - jdata = utils.fetch_json(urlparse.urljoin(PDNS_STATS_URL, API_EXTENDED_URL + '/servers/localhost/zones/%s/axfr-retrieve' % domain), headers=headers, method='PUT') + jdata = utils.fetch_json(urljoin(PDNS_STATS_URL, API_EXTENDED_URL + '/servers/localhost/zones/%s/axfr-retrieve' % domain), headers=headers, method='PUT') return {'status': 'ok', 'msg': 'Update from Master successfully'} except: return {'status': 'error', 'msg': 'There was something wrong, please contact administrator'} @@ -746,7 +747,7 @@ class Domain(db.Model): headers = {} headers['X-API-Key'] = PDNS_API_KEY try: - jdata = utils.fetch_json(urlparse.urljoin(PDNS_STATS_URL, API_EXTENDED_URL + '/servers/localhost/zones/%s/cryptokeys' % domain.name), headers=headers, method='GET') + jdata = utils.fetch_json(urljoin(PDNS_STATS_URL, API_EXTENDED_URL + '/servers/localhost/zones/%s/cryptokeys' % domain.name), headers=headers, method='GET') if 'error' in jdata: return {'status': 'error', 'msg': 'DNSSEC is not enabled for this domain'} else: @@ -791,7 +792,7 @@ class Record(object): headers = {} headers['X-API-Key'] = PDNS_API_KEY try: - jdata = utils.fetch_json(urlparse.urljoin(PDNS_STATS_URL, API_EXTENDED_URL + '/servers/localhost/zones/%s' % domain), headers=headers) + jdata = utils.fetch_json(urljoin(PDNS_STATS_URL, API_EXTENDED_URL + '/servers/localhost/zones/%s' % domain), headers=headers) except: logging.error("Cannot fetch domain's record data from remote powerdns api") return False @@ -865,11 +866,11 @@ class Record(object): } try: - jdata = utils.fetch_json(urlparse.urljoin(PDNS_STATS_URL, API_EXTENDED_URL + '/servers/localhost/zones/%s' % domain), headers=headers, method='PATCH', data=data) + jdata = utils.fetch_json(urljoin(PDNS_STATS_URL, API_EXTENDED_URL + '/servers/localhost/zones/%s' % domain), headers=headers, method='PATCH', data=data) logging.debug(jdata) return {'status': 'ok', 'msg': 'Record was added successfully'} - except Exception, e: - logging.error("Cannot add record %s/%s/%s to domain %s. DETAIL: %s" % (self.name, self.type, self.data, domain, str(e))) + except Exception as e: + logging.error("Cannot add record %s/%s/%s to domain %s. DETAIL: %s" % (self.name, self.type, self.data, domain, e)) return {'status': 'error', 'msg': 'There was something wrong, please contact administrator'} @@ -1043,8 +1044,8 @@ class Record(object): try: headers = {} headers['X-API-Key'] = PDNS_API_KEY - jdata1 = utils.fetch_json(urlparse.urljoin(PDNS_STATS_URL, API_EXTENDED_URL + '/servers/localhost/zones/%s' % domain), headers=headers, method='PATCH', data=postdata_for_delete) - jdata2 = utils.fetch_json(urlparse.urljoin(PDNS_STATS_URL, API_EXTENDED_URL + '/servers/localhost/zones/%s' % domain), headers=headers, method='PATCH', data=postdata_for_new) + jdata1 = utils.fetch_json(urljoin(PDNS_STATS_URL, API_EXTENDED_URL + '/servers/localhost/zones/%s' % domain), headers=headers, method='PATCH', data=postdata_for_delete) + jdata2 = utils.fetch_json(urljoin(PDNS_STATS_URL, API_EXTENDED_URL + '/servers/localhost/zones/%s' % domain), headers=headers, method='PATCH', data=postdata_for_new) if 'error' in jdata2.keys(): logging.error('Cannot apply record changes.') @@ -1054,8 +1055,8 @@ class Record(object): self.auto_ptr(domain, new_records, deleted_records) logging.info('Record was applied successfully.') return {'status': 'ok', 'msg': 'Record was applied successfully'} - except Exception, e: - logging.error("Cannot apply record changes to domain %s. DETAIL: %s" % (str(e), domain)) + except Exception as e: + logging.error("Cannot apply record changes to domain %s. DETAIL: %s" % (e, domain)) return {'status': 'error', 'msg': 'There was something wrong, please contact administrator'} def auto_ptr(self, domain, new_records, deleted_records): @@ -1097,7 +1098,7 @@ class Record(object): self.delete(domain_reverse_name) return {'status': 'ok', 'msg': 'Auto-PTR record was updated successfully'} except Exception as e: - logging.error("Cannot update auto-ptr record changes to domain %s. DETAIL: %s" % (str(e), domain)) + logging.error("Cannot update auto-ptr record changes to domain %s. DETAIL: %s" % (e, domain)) return {'status': 'error', 'msg': 'Auto-PTR creation failed. There was something wrong, please contact administrator.'} def delete(self, domain): @@ -1117,7 +1118,7 @@ class Record(object): ] } try: - jdata = utils.fetch_json(urlparse.urljoin(PDNS_STATS_URL, API_EXTENDED_URL + '/servers/localhost/zones/%s' % domain), headers=headers, method='PATCH', data=data) + jdata = utils.fetch_json(urljoin(PDNS_STATS_URL, API_EXTENDED_URL + '/servers/localhost/zones/%s' % domain), headers=headers, method='PATCH', data=data) logging.debug(jdata) return {'status': 'ok', 'msg': 'Record was removed successfully'} except: @@ -1191,11 +1192,11 @@ class Record(object): ] } try: - jdata = utils.fetch_json(urlparse.urljoin(PDNS_STATS_URL, API_EXTENDED_URL + '/servers/localhost/zones/%s' % domain), headers=headers, method='PATCH', data=data) + jdata = utils.fetch_json(urljoin(PDNS_STATS_URL, API_EXTENDED_URL + '/servers/localhost/zones/%s' % domain), headers=headers, method='PATCH', data=data) logging.debug("dyndns data: " % data) return {'status': 'ok', 'msg': 'Record was updated successfully'} - except Exception, e: - logging.error("Cannot add record %s/%s/%s to domain %s. DETAIL: %s" % (self.name, self.type, self.data, domain, str(e))) + except Exception as e: + logging.error("Cannot add record %s/%s/%s to domain %s. DETAIL: %s" % (self.name, self.type, self.data, domain, e)) return {'status': 'error', 'msg': 'There was something wrong, please contact administrator'} @@ -1217,7 +1218,7 @@ class Server(object): headers['X-API-Key'] = PDNS_API_KEY try: - jdata = utils.fetch_json(urlparse.urljoin(PDNS_STATS_URL, API_EXTENDED_URL + '/servers/%s/config' % self.server_id), headers=headers, method='GET') + jdata = utils.fetch_json(urljoin(PDNS_STATS_URL, API_EXTENDED_URL + '/servers/%s/config' % self.server_id), headers=headers, method='GET') return jdata except: logging.error("Can not get server configuration.") @@ -1232,7 +1233,7 @@ class Server(object): headers['X-API-Key'] = PDNS_API_KEY try: - jdata = utils.fetch_json(urlparse.urljoin(PDNS_STATS_URL, API_EXTENDED_URL + '/servers/%s/statistics' % self.server_id), headers=headers, method='GET') + jdata = utils.fetch_json(urljoin(PDNS_STATS_URL, API_EXTENDED_URL + '/servers/%s/statistics' % self.server_id), headers=headers, method='GET') return jdata except: logging.error("Can not get server statistics.") diff --git a/app/views.py b/app/views.py index 8cc8761..46989ee 100644 --- a/app/views.py +++ b/app/views.py @@ -18,7 +18,7 @@ from werkzeug.security import gen_salt from .models import User, Domain, Record, Server, History, Anonymous, Setting, DomainSetting from app import app, login_manager, github -from lib import utils +from app.lib import utils jinja2.filters.FILTERS['display_record_name'] = utils.display_record_name @@ -34,36 +34,43 @@ if StrictVersion(PDNS_VERSION) >= StrictVersion('4.0.0'): else: NEW_SCHEMA = False + @app.context_processor def inject_fullscreen_layout_setting(): fullscreen_layout_setting = Setting.query.filter(Setting.name == 'fullscreen_layout').first() return dict(fullscreen_layout_setting=strtobool(fullscreen_layout_setting.value)) + @app.context_processor def inject_record_helper_setting(): record_helper_setting = Setting.query.filter(Setting.name == 'record_helper').first() return dict(record_helper_setting=strtobool(record_helper_setting.value)) + @app.context_processor def inject_login_ldap_first_setting(): login_ldap_first_setting = Setting.query.filter(Setting.name == 'login_ldap_first').first() return dict(login_ldap_first_setting=strtobool(login_ldap_first_setting.value)) + @app.context_processor def inject_default_record_table_size_setting(): default_record_table_size_setting = Setting.query.filter(Setting.name == 'default_record_table_size').first() return dict(default_record_table_size_setting=default_record_table_size_setting.value) + @app.context_processor def inject_default_domain_table_size_setting(): default_domain_table_size_setting = Setting.query.filter(Setting.name == 'default_domain_table_size').first() return dict(default_domain_table_size_setting=default_domain_table_size_setting.value) + @app.context_processor def inject_auto_ptr_setting(): auto_ptr_setting = Setting.query.filter(Setting.name == 'auto_ptr').first() return dict(auto_ptr_setting=strtobool(auto_ptr_setting.value)) + # START USER AUTHENTICATION HANDLER @app.before_request def before_request(): @@ -92,6 +99,7 @@ def dyndns_login_required(f): return f(*args, **kwargs) return decorated_function + @login_manager.request_loader def login_via_authorization_header(request): auth_header = request.headers.get('Authorization') @@ -100,8 +108,7 @@ def login_via_authorization_header(request): try: auth_header = base64.b64decode(auth_header) username,password = auth_header.split(":") - except TypeError, e: - error = e.message['desc'] if 'desc' in e.message else e + except TypeError as e: return None user = User(username=username, password=password, plain_text_password=password) try: @@ -111,10 +118,9 @@ def login_via_authorization_header(request): else: login_user(user, remember = False) return user - except Exception, e: + except: return None return None - # END USER AUTHENTICATION HANDLER # START CUSTOMIZE DECORATOR @@ -132,18 +138,22 @@ def admin_role_required(f): def http_bad_request(e): return redirect(url_for('error', code=400)) + @app.errorhandler(401) def http_unauthorized(e): return redirect(url_for('error', code=401)) + @app.errorhandler(404) def http_internal_server_error(e): return redirect(url_for('error', code=404)) + @app.errorhandler(500) def http_page_not_found(e): return redirect(url_for('error', code=500)) + @app.route('/error/') def error(code, msg=None): supported_code = ('400', '401', '404', '500') @@ -152,6 +162,7 @@ def error(code, msg=None): else: return render_template('errors/404.html'), 404 + @app.route('/register', methods=['GET']) def register(): SIGNUP_ENABLED = app.config['SIGNUP_ENABLED'] @@ -160,12 +171,14 @@ def register(): else: return render_template('errors/404.html'), 404 + @app.route('/github/login') def github_login(): if not app.config.get('GITHUB_OAUTH_ENABLE'): return abort(400) return github.authorize(callback=url_for('authorized', _external=True)) + @app.route('/login', methods=['GET', 'POST']) @login_manager.unauthorized_handler def login(): @@ -224,9 +237,8 @@ def login(): auth = user.is_validate(method=auth_method) if auth == False: return render_template('login.html', error='Invalid credentials', ldap_enabled=LDAP_ENABLED, login_title=LOGIN_TITLE, basic_enabled=BASIC_ENABLED, signup_enabled=SIGNUP_ENABLED) - except Exception, e: - error = e.message['desc'] if 'desc' in e.message else e - return render_template('login.html', error=error, ldap_enabled=LDAP_ENABLED, login_title=LOGIN_TITLE, basic_enabled=BASIC_ENABLED, signup_enabled=SIGNUP_ENABLED) + except Exception as e: + return render_template('login.html', error=e, ldap_enabled=LDAP_ENABLED, login_title=LOGIN_TITLE, basic_enabled=BASIC_ENABLED, signup_enabled=SIGNUP_ENABLED) # check if user enabled OPT authentication if user.otp_secret: @@ -255,9 +267,9 @@ def login(): return render_template('login.html', username=username, password=password, ldap_enabled=LDAP_ENABLED, login_title=LOGIN_TITLE, basic_enabled=BASIC_ENABLED, signup_enabled=SIGNUP_ENABLED) else: return render_template('register.html', error=result) - except Exception, e: - error = e.message['desc'] if 'desc' in e.message else e - return render_template('register.html', error=error) + except Exception as e: + return render_template('register.html', error=e) + @app.route('/logout') def logout(): @@ -284,7 +296,7 @@ def dashboard(): server = Server(server_id='localhost') statistics = server.get_statistic() if statistics: - uptime = filter(lambda uptime: uptime['name'] == 'uptime', statistics)[0]['value'] + uptime = list([uptime for uptime in statistics if uptime['name'] == 'uptime'])[0]['value'] else: uptime = 0 return render_template('dashboard.html', domains=domains, domain_count=domain_count, users=users, history_number=history_number, uptime=uptime, histories=history) @@ -417,8 +429,7 @@ def record_apply(domain_name): """ #TODO: filter removed records / name modified records. try: - pdata = request.data - jdata = json.loads(pdata) + jdata = request.json r = Record() result = r.apply(domain_name, jdata) @@ -429,7 +440,7 @@ def record_apply(domain_name): else: return make_response(jsonify( result ), 400) except: - print traceback.format_exc() + traceback.print_exc() return make_response(jsonify( {'status': 'error', 'msg': 'Error when applying new changes'} ), 500) @@ -441,8 +452,7 @@ def record_update(domain_name): Pulling the records update from its Master """ try: - pdata = request.data - jdata = json.loads(pdata) + jdata = request.json domain_name = jdata['domain'] d = Domain() @@ -452,7 +462,7 @@ def record_update(domain_name): else: return make_response(jsonify( {'status': 'error', 'msg': result['msg']} ), 500) except: - print traceback.format_exc() + traceback.print_exc() return make_response(jsonify( {'status': 'error', 'msg': 'Error when applying new changes'} ), 500) @@ -464,9 +474,9 @@ def record_delete(domain_name, record_name, record_type): r = Record(name=record_name, type=record_type) result = r.delete(domain=domain_name) if result['status'] == 'error': - print result['msg'] + print(result['msg']) except: - print traceback.format_exc() + traceback.print_exc() return redirect(url_for('error', code=500)), 500 return redirect(url_for('domain', domain_name=domain_name)) @@ -478,6 +488,7 @@ def domain_dnssec(domain_name): dnssec = domain.get_domain_dnssec(domain_name) return make_response(jsonify(dnssec), 200) + @app.route('/domain//managesetting', methods=['GET', 'POST']) @login_required @admin_role_required @@ -488,9 +499,9 @@ def admin_setdomainsetting(domain_name): # {'action': 'set_setting', 'setting': 'default_action, 'value': 'True'} # try: - pdata = request.data - jdata = json.loads(pdata) + jdata = request.json data = jdata['data'] + if jdata['action'] == 'set_setting': new_setting = data['setting'] new_value = str(data['value']) @@ -514,7 +525,7 @@ def admin_setdomainsetting(domain_name): else: return make_response(jsonify( { 'status': 'error', 'msg': 'Action not supported.' } ), 400) except: - print traceback.format_exc() + traceback.print_exc() return make_response(jsonify( { 'status': 'error', 'msg': 'There is something wrong, please contact Administrator.' } ), 400) @@ -531,12 +542,13 @@ def admin(): history_number = History.query.count() if statistics: - uptime = filter(lambda uptime: uptime['name'] == 'uptime', statistics)[0]['value'] + uptime = list([uptime for uptime in statistics if uptime['name'] == 'uptime'])[0]['value'] else: uptime = 0 return render_template('admin.html', domains=domains, users=users, configs=configs, statistics=statistics, uptime=uptime, history_number=history_number) + @app.route('/admin/user/create', methods=['GET', 'POST']) @login_required @admin_role_required @@ -562,6 +574,7 @@ def admin_createuser(): return redirect(url_for('admin_manageuser')) + @app.route('/admin/manageuser', methods=['GET', 'POST']) @login_required @admin_role_required @@ -576,8 +589,7 @@ def admin_manageuser(): # {'action': 'delete_user', 'data': 'username'} # try: - pdata = request.data - jdata = json.loads(pdata) + jdata = request.json data = jdata['data'] if jdata['action'] == 'delete_user': @@ -614,7 +626,7 @@ def admin_manageuser(): else: return make_response(jsonify( { 'status': 'error', 'msg': 'Action not supported.' } ), 400) except: - print traceback.format_exc() + traceback.print_exc() return make_response(jsonify( { 'status': 'error', 'msg': 'There is something wrong, please contact Administrator.' } ), 400) @@ -637,6 +649,7 @@ def admin_history(): histories = History.query.all() return render_template('admin_history.html', histories=histories) + @app.route('/admin/settings', methods=['GET']) @login_required @admin_role_required @@ -645,6 +658,7 @@ def admin_settings(): settings = Setting.query.filter(Setting.name != 'maintenance') return render_template('admin_settings.html', settings=settings) + @app.route('/admin/setting//toggle', methods=['POST']) @login_required @admin_role_required @@ -655,19 +669,21 @@ def admin_settings_toggle(setting): else: return make_response(jsonify( { 'status': 'error', 'msg': 'Unable to toggle setting.' } ), 500) + @app.route('/admin/setting//edit', methods=['POST']) @login_required @admin_role_required def admin_settings_edit(setting): - pdata = request.data - jdata = json.loads(pdata) + jdata = request.json new_value = jdata['value'] result = Setting().set(setting, new_value) + if (result): return make_response(jsonify( { 'status': 'ok', 'msg': 'Toggled setting successfully.' } ), 200) else: return make_response(jsonify( { 'status': 'error', 'msg': 'Unable to toggle setting.' } ), 500) + @app.route('/user/profile', methods=['GET', 'POST']) @login_required def user_profile(): @@ -682,7 +698,7 @@ def user_profile(): # json data if request.data: - jdata = json.loads(request.data) + jdata = request.json data = jdata['data'] if jdata['action'] == 'enable_otp': enable_otp = data['enable_otp'] @@ -702,7 +718,6 @@ def user_profile(): save_file_name = current_user.username + '.' + file_extension file.save(os.path.join(app.config['UPLOAD_DIR'], 'avatar', save_file_name)) - # update user profile user = User(username=current_user.username, plain_text_password=new_password, firstname=firstname, lastname=lastname, email=email, avatar=save_file_name, reload_info=False) user.update_profile() @@ -737,6 +752,7 @@ def dyndns_checkip(): # route covers the default ddclient 'web' setting for the checkip service return render_template('dyndns.html', response=request.environ.get('HTTP_X_REAL_IP', request.remote_addr)) + @app.route('/nic/update', methods=['GET', 'POST']) @dyndns_login_required def dyndns_update(): diff --git a/config_template.py b/config_template.py index 288ff47..5970ec3 100644 --- a/config_template.py +++ b/config_template.py @@ -5,7 +5,7 @@ basedir = os.path.abspath(os.path.dirname(__file__)) WTF_CSRF_ENABLED = True SECRET_KEY = 'We are the world' BIND_ADDRESS = '127.0.0.1' -PORT = 9393 +PORT = 9191 LOGIN_TITLE = "PDNS" # TIMEOUT - for large zones diff --git a/configs/development.py b/configs/development.py new file mode 100644 index 0000000..98f7d10 --- /dev/null +++ b/configs/development.py @@ -0,0 +1,59 @@ +import os +basedir = os.path.abspath(os.path.dirname(__file__)) + +# BASIC APP CONFIG +WTF_CSRF_ENABLED = True +SECRET_KEY = 'changeme' +LOG_LEVEL = 'DEBUG' +LOG_FILE = 'log.txt' + +# TIMEOUT - for large zones +TIMEOUT = 10 + +# UPLOAD DIR +UPLOAD_DIR = os.path.join(basedir, 'upload') + +# DATABASE CONFIG FOR MYSQL +DB_USER = 'powerdnsadmin' +DB_PASSWORD = 'powerdnsadminpassword' +DB_HOST = 'docker.for.mac.localhost' +DB_NAME = 'powerdnsadmin' + +#MySQL +SQLALCHEMY_DATABASE_URI = 'mysql://'+DB_USER+':'+DB_PASSWORD+'@'+DB_HOST+'/'+DB_NAME +SQLALCHEMY_MIGRATE_REPO = os.path.join(basedir, 'db_repository') +SQLALCHEMY_TRACK_MODIFICATIONS = True + +# AUTHENTICATION CONFIG +BASIC_ENABLED = True +SIGNUP_ENABLED = True + +## LDAP CONFIG +# LDAP_TYPE = 'ldap' +# LDAP_URI = 'ldaps://your-ldap-server:636' +# LDAP_USERNAME = 'cn=dnsuser,ou=users,ou=services,dc=duykhanh,dc=me' +# LDAP_PASSWORD = 'dnsuser' +# LDAP_SEARCH_BASE = 'ou=System Admins,ou=People,dc=duykhanh,dc=me' +## Additional options only if LDAP_TYPE=ldap +# LDAP_USERNAMEFIELD = 'uid' +# LDAP_FILTER = '(objectClass=inetorgperson)' + +## GITHUB AUTHENTICATION +# GITHUB_OAUTH_ENABLE = False +# GITHUB_OAUTH_KEY = 'G0j1Q15aRsn36B3aD6nwKLiYbeirrUPU8nDd1wOC' +# GITHUB_OAUTH_SECRET = '0WYrKWePeBDkxlezzhFbDn1PBnCwEa0vCwVFvy6iLtgePlpT7WfUlAa9sZgm' +# GITHUB_OAUTH_SCOPE = 'email' +# GITHUB_OAUTH_URL = 'http://127.0.0.1:5000/api/v3/' +# GITHUB_OAUTH_TOKEN = 'http://127.0.0.1:5000/oauth/token' +# GITHUB_OAUTH_AUTHORIZE = 'http://127.0.0.1:5000/oauth/authorize' + +# POWERDNS CONFIG +PDNS_STATS_URL = 'http://192.168.100.100:8081/' +PDNS_API_KEY = 'changeme' +PDNS_VERSION = '4.1.1' + +# RECORDS ALLOWED TO EDIT +RECORDS_ALLOW_EDIT = ['A', 'AAAA', 'CNAME', 'SPF', 'PTR', 'MX', 'TXT'] + +# EXPERIMENTAL FEATURES +PRETTY_IPV6_PTR = False diff --git a/create_db.py b/create_db.py index 41c874d..2a70f96 100755 --- a/create_db.py +++ b/create_db.py @@ -1,13 +1,16 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 + +import sys +import time +import os.path +import traceback from migrate.versioning import api from config import SQLALCHEMY_DATABASE_URI from config import SQLALCHEMY_MIGRATE_REPO from app import db from app.models import Role, Setting -import os.path -import time -import sys + def start(): wait_time = get_waittime_from_env() @@ -22,13 +25,14 @@ def get_waittime_from_env(): return int(os.environ.get('WAITFOR_DB', 1)) def connect_db(wait_time): - for i in xrange(0, wait_time): + for i in range(0, wait_time): print("INFO: Wait for database server") sys.stdout.flush() try: db.create_all() return True except: + traceback.print_exc() time.sleep(1) return False @@ -36,14 +40,14 @@ def connect_db(wait_time): def init_roles(db, role_names): # Get key name of data - name_of_roles = map(lambda r: r.name, role_names) + name_of_roles = [r.name for r in role_names] # Query to get current data rows = db.session.query(Role).filter(Role.name.in_(name_of_roles)).all() - name_of_rows = map(lambda r: r.name, rows) + name_of_rows = [r.name for r in rows] # Check which data that need to insert - roles = filter(lambda r: r.name not in name_of_rows, role_names) + roles = [r for r in role_names if r.name not in name_of_rows] # Insert data for role in roles: @@ -52,14 +56,14 @@ def init_roles(db, role_names): def init_settings(db, setting_names): # Get key name of data - name_of_settings = map(lambda r: r.name, setting_names) + name_of_settings = [r.name for r in setting_names] # Query to get current data rows = db.session.query(Setting).filter(Setting.name.in_(name_of_settings)).all() # Check which data that need to insert - name_of_rows = map(lambda r: r.name, rows) - settings = filter(lambda r: r.name not in name_of_rows, setting_names) + name_of_rows = [r.name for r in rows] + settings = [r for r in setting_names if r.name not in name_of_rows] # Insert data for setting in settings: diff --git a/db_downgrade.py b/db_downgrade.py index c001e6c..fa5866e 100755 --- a/db_downgrade.py +++ b/db_downgrade.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 from migrate.versioning import api from config import SQLALCHEMY_DATABASE_URI from config import SQLALCHEMY_MIGRATE_REPO diff --git a/db_migrate.py b/db_migrate.py index 6823469..7d0869e 100755 --- a/db_migrate.py +++ b/db_migrate.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 import imp from migrate.versioning import api from app import db diff --git a/db_upgrade.py b/db_upgrade.py index f5ae27b..515b309 100755 --- a/db_upgrade.py +++ b/db_upgrade.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 from migrate.versioning import api from config import SQLALCHEMY_DATABASE_URI from config import SQLALCHEMY_MIGRATE_REPO diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml new file mode 100644 index 0000000..57b1e16 --- /dev/null +++ b/docker-compose.dev.yml @@ -0,0 +1,22 @@ +version: "2.1" + +services: + powerdns-admin: + build: + context: . + dockerfile: Dockerfile + image: powerdns-admin + container_name: powerdns-admin + mem_limit: 256M + memswap_limit: 256M + tty: true + command: /usr/bin/supervisord -c /etc/supervisord.conf + ports: + - "9191:9191" + volumes: + - .:/powerdns-admin/ + - "./configs/development.py:/powerdns-admin/config.py" + logging: + driver: json-file + options: + max-size: 50m diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index c9271b5..0000000 --- a/docker-compose.yml +++ /dev/null @@ -1,50 +0,0 @@ -version: '2' - -services: - - powerdns-authoritative: - image: winggundamth/powerdns-mysql:trusty - hostname: powerdns-authoritative - depends_on: - - powerdns-authoritative-mariadb - links: - - powerdns-authoritative-mariadb:mysqldb - ports: - - 172.17.0.1:53:53/udp - - 8081:8081 - environment: - - PDNS_DB_HOST=mysqldb - - PDNS_DB_USERNAME=root - - PDNS_DB_NAME=powerdns - - PDNS_DB_PASSWORD=PowerDNSPassword - - PDNS_API_KEY=PowerDNSAPIKey - - powerdns-authoritative-mariadb: - image: mariadb:10.1.15 - hostname: powerdns-authoritative-mariadb - environment: - - MYSQL_DATABASE=powerdns - - MYSQL_ROOT_PASSWORD=PowerDNSPassword - - powerdns-admin: - image: winggundamth/powerdns-admin:trusty - hostname: powerdns-admin - depends_on: - - powerdns-admin-mariadb - - powerdns-authoritative - links: - - powerdns-admin-mariadb:mysqldb - - powerdns-authoritative:powerdns-server - volumes: - - ./:/home/web/powerdns-admin - ports: - - 9393:9393 - environment: - - WAITFOR_DB=60 - - powerdns-admin-mariadb: - image: mariadb:10.1.15 - hostname: powerdns-admin-mariadb - environment: - - MYSQL_DATABASE=powerdns-admin - - MYSQL_ROOT_PASSWORD=PowerDNSAdminPassword diff --git a/requirements.txt b/requirements.txt index 46ab6ba..f7215df 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,14 +1,16 @@ -Flask>=0.10 -Flask-WTF>=0.11 -Flask-Login>=0.2.11 -configobj==5.0.5 -bcrypt==3.1.0 -requests==2.7.0 -python-ldap==2.4.21 -Flask-SQLAlchemy==2.1 -SQLAlchemy==1.0.9 +Flask==0.12.2 +Flask-WTF==0.14.2 +Flask-Login==0.4.1 +Flask-OAuthlib==0.9.4 +Flask-SQLAlchemy==2.3.2 +SQLAlchemy==1.2.5 sqlalchemy-migrate==0.10.0 -pyotp==2.2.1 -qrcode==5.3 -Flask-OAuthlib==0.9.3 -dnspython>=1.12.0 +mysqlclient==1.3.12 +configobj==5.0.6 +bcrypt==3.1.4 +requests==2.18.4 +python-ldap==3.0.0 +pyotp==2.2.6 +qrcode==6.0 +dnspython==1.15.0 +gunicorn==19.7.1 diff --git a/supervisord.conf b/supervisord.conf new file mode 100644 index 0000000..ed604cf --- /dev/null +++ b/supervisord.conf @@ -0,0 +1,23 @@ +[unix_http_server] +file=/tmp/supervisor.sock ; (the path to the socket file) +chown=nobody:nogroup ; socket file uid:gid owner + +[supervisord] +logfile=/tmp/supervisord.log ; (main log file;default $CWD/supervisord.log) +logfile_maxbytes=50MB ; (max main logfile bytes b4 rotation;default 50MB) +logfile_backups=10 ; (num of main logfile rotation backups;default 10) +loglevel=info ; (log level;default info; others: debug,warn,trace) +pidfile=/tmp/supervisord.pid ; (supervisord pidfile;default supervisord.pid) +nodaemon=true ; (start in foreground if true;default false) +minfds=1024 ; (min. avail startup file descriptors;default 1024) +minprocs=200 ; (min. avail process descriptors;default 200) + +[program:powerdns-admin] +command=/usr/local/bin/gunicorn -t 120 --workers 4 --bind '0.0.0.0:9191' --log-level info app:app +directory=/powerdns-admin +autostart=true +priority=999 +user=www-data +redirect_stderr=true +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 From becca44a1dede263d1abc6d884660b8aa106869c Mon Sep 17 00:00:00 2001 From: Khanh Ngo Date: Fri, 30 Mar 2018 14:42:54 +0700 Subject: [PATCH 25/42] Add more record to RECORDS_ALLOW_EDIT --- configs/development.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configs/development.py b/configs/development.py index 98f7d10..fd92190 100644 --- a/configs/development.py +++ b/configs/development.py @@ -53,7 +53,7 @@ PDNS_API_KEY = 'changeme' PDNS_VERSION = '4.1.1' # RECORDS ALLOWED TO EDIT -RECORDS_ALLOW_EDIT = ['A', 'AAAA', 'CNAME', 'SPF', 'PTR', 'MX', 'TXT'] +RECORDS_ALLOW_EDIT = ['A', 'AAAA', 'CAA', 'CNAME', 'MX', 'PTR', 'SPF', 'SRV', 'TXT'] # EXPERIMENTAL FEATURES PRETTY_IPV6_PTR = False From 63e7d89df1664fdb587e095be9a0f3afb68f9dd7 Mon Sep 17 00:00:00 2001 From: Khanh Ngo Date: Fri, 30 Mar 2018 15:40:43 +0700 Subject: [PATCH 26/42] Adjustment to be able to show ALL domain in dashboard table --- app/views.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/views.py b/app/views.py index e74d5a3..0b73916 100644 --- a/app/views.py +++ b/app/views.py @@ -338,7 +338,9 @@ def dashboard_domains(): start = int(request.args.get("start", 0)) length = min(int(request.args.get("length", 0)), 100) - domains = domains[start:start + length] + + if length != -1: + domains = domains[start:start + length] if current_user.role.name != 'Administrator': domains = [d[2] for d in domains] From 004ced015df2576f5d6e0015750fbc9106b96de5 Mon Sep 17 00:00:00 2001 From: Khanh Ngo Date: Fri, 30 Mar 2018 16:25:20 +0700 Subject: [PATCH 27/42] Update development config to add REVERSE_ALLOW_EDIT --- configs/development.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/configs/development.py b/configs/development.py index fd92190..61b8b3c 100644 --- a/configs/development.py +++ b/configs/development.py @@ -53,7 +53,10 @@ PDNS_API_KEY = 'changeme' PDNS_VERSION = '4.1.1' # RECORDS ALLOWED TO EDIT -RECORDS_ALLOW_EDIT = ['A', 'AAAA', 'CAA', 'CNAME', 'MX', 'PTR', 'SPF', 'SRV', 'TXT'] +RECORDS_ALLOW_EDIT = ['A', 'AAAA', 'CAA', 'CNAME', 'MX', 'PTR', 'SPF', 'SRV', 'TXT', 'NS'] + +# RECORDS ALLOWED TO EDIT FOR REVERSE DOMAINS +REVERSE_ALLOW_EDIT = ['PTR', 'NS'] # EXPERIMENTAL FEATURES PRETTY_IPV6_PTR = False From 7a9474c3f3bdb21bf98e7918c46ff07198bb6502 Mon Sep 17 00:00:00 2001 From: Khanh Ngo Date: Fri, 30 Mar 2018 16:40:53 +0700 Subject: [PATCH 28/42] Fix cancel button in domain adding page --- app/templates/domain_add.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/templates/domain_add.html b/app/templates/domain_add.html index 04add24..8f74ac9 100644 --- a/app/templates/domain_add.html +++ b/app/templates/domain_add.html @@ -93,7 +93,7 @@ From 51cdba82289c1757d912f18bc51d6f49a5ad222d Mon Sep 17 00:00:00 2001 From: Khanh Ngo Date: Sat, 31 Mar 2018 06:52:14 +0700 Subject: [PATCH 29/42] User path: instead of string: in routes --- app/views.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/app/views.py b/app/views.py index a5596ed..64168b1 100644 --- a/app/views.py +++ b/app/views.py @@ -154,7 +154,7 @@ def http_page_not_found(e): return redirect(url_for('error', code=500)) -@app.route('/error/') +@app.route('/error/') def error(code, msg=None): supported_code = ('400', '401', '404', '500') if code in supported_code: @@ -472,7 +472,7 @@ def domain_add(): return render_template('domain_add.html') -@app.route('/admin/domain//delete', methods=['GET']) +@app.route('/admin/domain//delete', methods=['GET']) @login_required @admin_role_required def domain_delete(domain_name): @@ -488,7 +488,7 @@ def domain_delete(domain_name): return redirect(url_for('dashboard')) -@app.route('/admin/domain//manage', methods=['GET', 'POST']) +@app.route('/admin/domain//manage', methods=['GET', 'POST']) @login_required @admin_role_required def domain_management(domain_name): @@ -521,7 +521,7 @@ def domain_management(domain_name): return redirect(url_for('domain_management', domain_name=domain_name)) -@app.route('/domain//apply', methods=['POST'], strict_slashes=False) +@app.route('/domain//apply', methods=['POST'], strict_slashes=False) @login_required def record_apply(domain_name): """ @@ -544,7 +544,7 @@ def record_apply(domain_name): return make_response(jsonify( {'status': 'error', 'msg': 'Error when applying new changes'} ), 500) -@app.route('/domain//update', methods=['POST'], strict_slashes=False) +@app.route('/domain//update', methods=['POST'], strict_slashes=False) @login_required def record_update(domain_name): """ @@ -566,7 +566,7 @@ def record_update(domain_name): return make_response(jsonify( {'status': 'error', 'msg': 'Error when applying new changes'} ), 500) -@app.route('/domain//record//type//delete', methods=['GET']) +@app.route('/domain//record//type//delete', methods=['GET']) @login_required @admin_role_required def record_delete(domain_name, record_name, record_type): @@ -581,7 +581,7 @@ def record_delete(domain_name, record_name, record_type): return redirect(url_for('domain', domain_name=domain_name)) -@app.route('/domain//dnssec', methods=['GET']) +@app.route('/domain//dnssec', methods=['GET']) @login_required def domain_dnssec(domain_name): domain = Domain() @@ -589,7 +589,7 @@ def domain_dnssec(domain_name): return make_response(jsonify(dnssec), 200) -@app.route('/domain//managesetting', methods=['GET', 'POST']) +@app.route('/domain//managesetting', methods=['GET', 'POST']) @login_required @admin_role_required def admin_setdomainsetting(domain_name): @@ -755,7 +755,7 @@ def admin_settings(): return render_template('admin_settings.html', settings=settings) -@app.route('/admin/setting//toggle', methods=['POST']) +@app.route('/admin/setting//toggle', methods=['POST']) @login_required @admin_role_required def admin_settings_toggle(setting): @@ -766,7 +766,7 @@ def admin_settings_toggle(setting): return make_response(jsonify( { 'status': 'error', 'msg': 'Unable to toggle setting.' } ), 500) -@app.route('/admin/setting//edit', methods=['POST']) +@app.route('/admin/setting//edit', methods=['POST']) @login_required @admin_role_required def admin_settings_edit(setting): @@ -821,7 +821,7 @@ def user_profile(): return render_template('user_profile.html') -@app.route('/user/avatar/') +@app.route('/user/avatar/') def user_avatar(filename): return send_from_directory(os.path.join(app.config['UPLOAD_DIR'], 'avatar'), filename) From ce6c3c21f1f3040a2f04074f97a34205abeea9e8 Mon Sep 17 00:00:00 2001 From: Khanh Ngo Date: Sat, 31 Mar 2018 06:53:57 +0700 Subject: [PATCH 30/42] Show user email address in user management table --- app/templates/admin_manageuser.html | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/templates/admin_manageuser.html b/app/templates/admin_manageuser.html index 6b6ff64..2289bf4 100644 --- a/app/templates/admin_manageuser.html +++ b/app/templates/admin_manageuser.html @@ -33,6 +33,7 @@ Username First Name Last Name + Email Admin Privileges Deletion @@ -44,6 +45,7 @@ {{ user.username }} {{ user.firstname }} {{ user.lastname }} + {{ user.email }} From aa2b29dac39f71eb96d22afa9adb8823f7e5f9e1 Mon Sep 17 00:00:00 2001 From: Khanh Ngo Date: Sat, 31 Mar 2018 07:32:46 +0700 Subject: [PATCH 31/42] Adjustment to give user access to granted domain only --- app/decorators.py | 28 ++++++++++++++++++++++++++++ app/views.py | 14 +++++--------- 2 files changed, 33 insertions(+), 9 deletions(-) create mode 100644 app/decorators.py diff --git a/app/decorators.py b/app/decorators.py new file mode 100644 index 0000000..e13dc8d --- /dev/null +++ b/app/decorators.py @@ -0,0 +1,28 @@ +from functools import wraps +from flask import g, request, redirect, url_for + +from app import app +from app.models import Role + + +def admin_role_required(f): + @wraps(f) + def decorated_function(*args, **kwargs): + if g.user.role.name != 'Administrator': + return redirect(url_for('error', code=401)) + return f(*args, **kwargs) + return decorated_function + + +def can_access_domain(f): + @wraps(f) + def decorated_function(*args, **kwargs): + if g.user.role.name != 'Administrator': + domain_name = kwargs.get('domain_name') + user_domain = [d.name for d in g.user.get_domain()] + + if domain_name not in user_domain: + return redirect(url_for('error', code=401)) + + return f(*args, **kwargs) + return decorated_function diff --git a/app/views.py b/app/views.py index 64168b1..82448e4 100644 --- a/app/views.py +++ b/app/views.py @@ -19,6 +19,7 @@ from werkzeug.security import gen_salt from .models import User, Domain, Record, Server, History, Anonymous, Setting, DomainSetting from app import app, login_manager, github, google from app.lib import utils +from app.decorators import admin_role_required, can_access_domain jinja2.filters.FILTERS['display_record_name'] = utils.display_record_name @@ -123,15 +124,6 @@ def login_via_authorization_header(request): return None # END USER AUTHENTICATION HANDLER -# START CUSTOMIZE DECORATOR -def admin_role_required(f): - @wraps(f) - def decorated_function(*args, **kwargs): - if g.user.role.name != 'Administrator': - return redirect(url_for('error', code=401)) - return f(*args, **kwargs) - return decorated_function -# END CUSTOMIZE DECORATOR # START VIEWS @app.errorhandler(400) @@ -405,6 +397,7 @@ def dashboard_domains(): @app.route('/domain/', methods=['GET', 'POST']) @app.route('/domain', methods=['GET', 'POST']) @login_required +@can_access_domain def domain(domain_name): r = Record() domain = Domain.query.filter(Domain.name == domain_name).first() @@ -523,6 +516,7 @@ def domain_management(domain_name): @app.route('/domain//apply', methods=['POST'], strict_slashes=False) @login_required +@can_access_domain def record_apply(domain_name): """ example jdata: {u'record_ttl': u'1800', u'record_type': u'CNAME', u'record_name': u'test4', u'record_status': u'Active', u'record_data': u'duykhanh.me'} @@ -546,6 +540,7 @@ def record_apply(domain_name): @app.route('/domain//update', methods=['POST'], strict_slashes=False) @login_required +@can_access_domain def record_update(domain_name): """ This route is used for domain work as Slave Zone only @@ -582,6 +577,7 @@ def record_delete(domain_name, record_name, record_type): @app.route('/domain//dnssec', methods=['GET']) +@can_access_domain @login_required def domain_dnssec(domain_name): domain = Domain() From 27074e688ee735dab3509eb91b5f7eab336bd086 Mon Sep 17 00:00:00 2001 From: Khanh Ngo Date: Sun, 1 Apr 2018 07:05:57 +0700 Subject: [PATCH 32/42] Update development config --- config_template.py | 12 ++++----- configs/development.py | 57 +++++++++++++++++++++++++++++------------- 2 files changed, 45 insertions(+), 24 deletions(-) diff --git a/config_template.py b/config_template.py index 04f13db..6078686 100644 --- a/config_template.py +++ b/config_template.py @@ -65,12 +65,12 @@ LDAP_FILTER = '(objectClass=inetorgperson)' # Github Oauth GITHUB_OAUTH_ENABLE = False -GITHUB_OAUTH_KEY = 'G0j1Q15aRsn36B3aD6nwKLiYbeirrUPU8nDd1wOC' -GITHUB_OAUTH_SECRET = '0WYrKWePeBDkxlezzhFbDn1PBnCwEa0vCwVFvy6iLtgePlpT7WfUlAa9sZgm' +GITHUB_OAUTH_KEY = '' +GITHUB_OAUTH_SECRET = '' GITHUB_OAUTH_SCOPE = 'email' -GITHUB_OAUTH_URL = 'http://127.0.0.1:5000/api/v3/' -GITHUB_OAUTH_TOKEN = 'http://127.0.0.1:5000/oauth/token' -GITHUB_OAUTH_AUTHORIZE = 'http://127.0.0.1:5000/oauth/authorize' +GITHUB_OAUTH_URL = 'http://127.0.0.1:9191/api/v3/' +GITHUB_OAUTH_TOKEN = 'http://127.0.0.1:9191/oauth/token' +GITHUB_OAUTH_AUTHORIZE = 'http://127.0.0.1:9191/oauth/authorize' # Google OAuth GOOGLE_OAUTH_ENABLE = False @@ -91,7 +91,7 @@ SIGNUP_ENABLED = True # POWERDNS CONFIG PDNS_STATS_URL = 'http://172.16.214.131:8081/' PDNS_API_KEY = 'you never know' -PDNS_VERSION = '3.4.7' +PDNS_VERSION = '4.1.1' # RECORDS ALLOWED TO EDIT RECORDS_ALLOW_EDIT = ['A', 'AAAA', 'CAA', 'CNAME', 'MX', 'PTR', 'SPF', 'SRV', 'TXT', 'NS'] diff --git a/configs/development.py b/configs/development.py index 2aa5c72..50658b8 100644 --- a/configs/development.py +++ b/configs/development.py @@ -28,29 +28,50 @@ SQLALCHEMY_TRACK_MODIFICATIONS = True BASIC_ENABLED = True SIGNUP_ENABLED = True -## LDAP CONFIG -# LDAP_TYPE = 'ldap' -# LDAP_URI = 'ldaps://your-ldap-server:636' -# LDAP_USERNAME = 'cn=dnsuser,ou=users,ou=services,dc=duykhanh,dc=me' -# LDAP_PASSWORD = 'dnsuser' -# LDAP_SEARCH_BASE = 'ou=System Admins,ou=People,dc=duykhanh,dc=me' -## Additional options only if LDAP_TYPE=ldap -# LDAP_USERNAMEFIELD = 'uid' -# LDAP_FILTER = '(objectClass=inetorgperson)' + +# LDAP CONFIG +LDAP_TYPE = 'ldap' +LDAP_URI = 'ldaps://your-ldap-server:636' +# with LDAP_BIND_TYPE you can specify 'direct' or 'search' to use user credentials +# for binding or a predefined LDAP_USERNAME and LDAP_PASSWORD, binding with non-DN only works with AD +LDAP_BIND_TYPE= 'direct' # direct or search +LDAP_USERNAME = 'cn=dnsuser,ou=users,ou=services,dc=duykhanh,dc=me' +LDAP_PASSWORD = 'dnsuser' +LDAP_SEARCH_BASE = 'ou=System Admins,ou=People,dc=duykhanh,dc=me' +# Additional options only if LDAP_TYPE=ldap +LDAP_USERNAMEFIELD = 'uid' +LDAP_FILTER = '(objectClass=inetorgperson)' +# enable LDAP_GROUP_SECURITY to allow Admin and User roles based on LDAP groups +#LDAP_GROUP_SECURITY = True # True or False +#LDAP_ADMIN_GROUP = 'CN=DnsAdmins,CN=Users,DC=example,DC=me' +#LDAP_USER_GROUP = 'CN=Domain Admins,CN=Users,DC=example,DC=me' + +## AD CONFIG +#LDAP_TYPE = 'ad' +#LDAP_URI = 'ldaps://your-ad-server:636' +#LDAP_USERNAME = 'cn=dnsuser,ou=Users,dc=domain,dc=local' +#LDAP_PASSWORD = 'dnsuser' +#LDAP_SEARCH_BASE = 'dc=domain,dc=local' +## You may prefer 'userPrincipalName' instead +#LDAP_USERNAMEFIELD = 'sAMAccountName' +## AD Group that you would like to have accesss to web app +#LDAP_FILTER = 'memberof=cn=DNS_users,ou=Groups,dc=domain,dc=local' + ## GITHUB AUTHENTICATION -# GITHUB_OAUTH_ENABLE = False -# GITHUB_OAUTH_KEY = 'G0j1Q15aRsn36B3aD6nwKLiYbeirrUPU8nDd1wOC' -# GITHUB_OAUTH_SECRET = '0WYrKWePeBDkxlezzhFbDn1PBnCwEa0vCwVFvy6iLtgePlpT7WfUlAa9sZgm' -# GITHUB_OAUTH_SCOPE = 'email' -# GITHUB_OAUTH_URL = 'http://127.0.0.1:5000/api/v3/' -# GITHUB_OAUTH_TOKEN = 'http://127.0.0.1:5000/oauth/token' -# GITHUB_OAUTH_AUTHORIZE = 'http://127.0.0.1:5000/oauth/authorize' +GITHUB_OAUTH_ENABLE = False +GITHUB_OAUTH_KEY = '' +GITHUB_OAUTH_SECRET = '' +GITHUB_OAUTH_SCOPE = 'email' +GITHUB_OAUTH_URL = 'http://127.0.0.1:9191/api/v3/' +GITHUB_OAUTH_TOKEN = 'http://127.0.0.1:9191/oauth/token' +GITHUB_OAUTH_AUTHORIZE = 'http://127.0.0.1:9191/oauth/authorize' + # GOOGLE AUTHENTICATION GOOGLE_OAUTH_ENABLE = True -GOOGLE_OAUTH_CLIENT_ID = '829241394512-2dc9shen6tv8ouhot68lg9g6gc029cj3.apps.googleusercontent.com' -GOOGLE_OAUTH_CLIENT_SECRET = 'wsa9oYp8kyCoIum44Pj1oICc' +GOOGLE_OAUTH_CLIENT_ID = '' +GOOGLE_OAUTH_CLIENT_SECRET = '' GOOGLE_REDIRECT_URI = '/user/authorized' GOOGLE_TOKEN_URL = 'https://accounts.google.com/o/oauth2/token' GOOGLE_TOKEN_PARAMS = { From 65da9a7a4f2c4d420e149ceb02737a90ff5ee867 Mon Sep 17 00:00:00 2001 From: Khanh Ngo Date: Sun, 1 Apr 2018 07:23:53 +0700 Subject: [PATCH 33/42] Adjustment in LDAP feature to work with python 3 --- app/models.py | 45 ++++++++++++++++++++---------------------- configs/development.py | 2 +- 2 files changed, 22 insertions(+), 25 deletions(-) diff --git a/app/models.py b/app/models.py index aa16753..bfb9351 100644 --- a/app/models.py +++ b/app/models.py @@ -40,8 +40,8 @@ if 'LDAP_TYPE' in app.config.keys(): LDAP_FILTER = app.config['LDAP_FILTER'] LDAP_USERNAMEFIELD = app.config['LDAP_USERNAMEFIELD'] - LDAP_GROUP_SECURITY = app.config['LDAP_GROUP_SECURITY'] - if app.config['LDAP_GROUP_SECURITY'] == True: + LDAP_GROUP_SECURITY = app.config.get('LDAP_GROUP_SECURITY') + if LDAP_GROUP_SECURITY == True: LDAP_ADMIN_GROUP = app.config['LDAP_ADMIN_GROUP'] LDAP_USER_GROUP = app.config['LDAP_USER_GROUP'] else: @@ -193,12 +193,12 @@ class User(db.Model): if user_info: if user_info.password and self.check_password(user_info.password): - logging.info('User "%s" logged in successfully' % self.username) + logging.info('User "{0}" logged in successfully'.format(self.username)) return True - logging.error('User "%s" input a wrong password' % self.username) + logging.error('User "{0}" input a wrong password'.format(self.username)) return False - logging.warning('User "%s" does not exist' % self.username) + logging.warning('User "{0}" does not exist'.format(self.username)) return False if method == 'LDAP': @@ -208,18 +208,15 @@ class User(db.Model): logging.error('LDAP authentication is disabled') return False - #searchFilter = "(&(objectcategory=person)(samaccountname=%s))" % self.username if LDAP_TYPE == 'ldap': - searchFilter = "(&(%s=%s)%s)" % (LDAP_USERNAMEFIELD, self.username, LDAP_FILTER) - logging.info('Ldap searchFilter "%s"' % searchFilter) + searchFilter = "(&({0}={1}){2})".format(LDAP_USERNAMEFIELD, self.username, LDAP_FILTER) + logging.info('Ldap searchFilter "{0}"'.format(searchFilter)) + elif LDAP_TYPE == 'ad': + searchFilter = "(&(objectcategory=person)({0}={1}){2})".format(LDAP_USERNAMEFIELD, self.username, LDAP_FILTER) - elif LDAP_TYPE == 'ldap': - searchFilter = "(&(%s=%s)(%s))" % (LDAP_USERNAMEFIELD, self.username, LDAP_FILTER) - - logging.info('Ldap searchFilter "%s"' % searchFilter) result = self.ldap_search(searchFilter, LDAP_SEARCH_BASE) if not result: - logging.warning('LDAP User "%s" does not exist' % self.username) + logging.warning('LDAP User "{0}" does not exist'.format(self.username)) return False try: @@ -232,20 +229,20 @@ class User(db.Model): if (self.ldap_search('(member=%s)' % ldap_user_dn ,LDAP_ADMIN_GROUP)): allowedlogin = True isadmin = True - logging.info('User %s is part of the "%s" group that allows admin access to PowerDNS-Admin' % (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)) if (self.ldap_search('(member=%s)' % ldap_user_dn ,LDAP_USER_GROUP)): #if (group == LDAP_USER_GROUP): allowedlogin = True - logging.info('User %s is part of the "%s" group that allows user access to PowerDNS-Admin' % (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)) if allowedlogin == False: - logging.error('User %s is not part of the "%s" or "%s" groups that allow access to PowerDNS-Admin' % (self.username,LDAP_ADMIN_GROUP,LDAP_USER_GROUP)) + logging.error('User {0} is not part of the "{1}" or "{2}" groups that allow access to PowerDNS-Admin'.format(self.username,LDAP_ADMIN_GROUP,LDAP_USER_GROUP)) return False - except Exception, e: - logging.error('LDAP group lookup for user "%s" has failed' % e) + except Exception as e: + logging.error('LDAP group lookup for user "{0}" has failed'.format(e)) return False - logging.info('User "%s" logged in successfully' % self.username) - except Exception, e: - logging.error('User "%s" input a wrong LDAP password' % e) + logging.info('User "{0}" logged in successfully'.format(self.username)) + except Exception as e: + logging.error('User "{0}" input a wrong LDAP password'.format(e)) return False # create user if not exist in the db @@ -258,8 +255,8 @@ class User(db.Model): self.firstname = result[0][0][1]['givenName'] self.lastname = result[0][0][1]['sn'] self.email = result[0][0][1]['mail'] - except Exception, e: - logging.info("reading ldap data threw an exception %s" % e) + except Exception as e: + logging.info("reading ldap data threw an exception {0}".format(e)) # first register user will be in Administrator role self.role_id = Role.query.filter_by(name='User').first().id @@ -272,7 +269,7 @@ class User(db.Model): self.role_id = Role.query.filter_by(name='Administrator').first().id self.create_user() - logging.info('Created user "%s" in the DB' % 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) if LDAP_GROUP_SECURITY: diff --git a/configs/development.py b/configs/development.py index 50658b8..afa0760 100644 --- a/configs/development.py +++ b/configs/development.py @@ -69,7 +69,7 @@ GITHUB_OAUTH_AUTHORIZE = 'http://127.0.0.1:9191/oauth/authorize' # GOOGLE AUTHENTICATION -GOOGLE_OAUTH_ENABLE = True +GOOGLE_OAUTH_ENABLE = False GOOGLE_OAUTH_CLIENT_ID = '' GOOGLE_OAUTH_CLIENT_SECRET = '' GOOGLE_REDIRECT_URI = '/user/authorized' From 1c54f008f4352c96144c76115cfe3e93adf0e9f1 Mon Sep 17 00:00:00 2001 From: Khanh Ngo Date: Sun, 1 Apr 2018 07:57:41 +0700 Subject: [PATCH 34/42] Change string to new format --- app/models.py | 86 +++++++++++++++++++++++++-------------------------- app/views.py | 52 +++++++++++++++---------------- 2 files changed, 68 insertions(+), 70 deletions(-) diff --git a/app/models.py b/app/models.py index bfb9351..36dffe9 100644 --- a/app/models.py +++ b/app/models.py @@ -123,10 +123,10 @@ class User(db.Model): return str(self.id) # python 3 def __repr__(self): - return '' % (self.username) + return ''.format(self.username) def get_totp_uri(self): - return 'otpauth://totp/PowerDNS-Admin:%s?secret=%s&issuer=PowerDNS-Admin' % (self.username, self.otp_secret) + return "otpauth://totp/PowerDNS-Admin:{0}?secret={1}&issuer=PowerDNS-Admin".format(self.username, self.otp_secret) def verify_totp(self, token): totp = pyotp.TOTP(self.otp_secret) @@ -226,11 +226,11 @@ class User(db.Model): if LDAP_TYPE == 'ldap': ldap_user_dn = ldap.filter.escape_filter_chars(result[0][0][0]) logging.info(result[0][0][0]) - if (self.ldap_search('(member=%s)' % ldap_user_dn ,LDAP_ADMIN_GROUP)): + if (self.ldap_search('(member={0})'.format(ldap_user_dn) ,LDAP_ADMIN_GROUP)): allowedlogin = True isadmin = True logging.info('User {0} is part of the "{1}" group that allows admin access to PowerDNS-Admin'.format(self.username,LDAP_ADMIN_GROUP)) - if (self.ldap_search('(member=%s)' % ldap_user_dn ,LDAP_USER_GROUP)): + if (self.ldap_search('(member={0})'.format(ldap_user_dn) ,LDAP_USER_GROUP)): #if (group == LDAP_USER_GROUP): allowedlogin = True logging.info('User {0} is part of the "{1}" group that allows user access to PowerDNS-Admin'.format(self.username,LDAP_USER_GROUP)) @@ -372,7 +372,7 @@ class User(db.Model): return True except: db.session.rollback() - logging.error('Cannot delete user %s from DB' % self.username) + logging.error('Cannot delete user {0} from DB'.format(self.username)) return False def revoke_privilege(self): @@ -389,7 +389,7 @@ class User(db.Model): return True except: db.session.rollback() - logging.error('Cannot revoke user %s privielges.' % self.username) + logging.error('Cannot revoke user {0} privielges'.format(self.username)) return False return False @@ -435,7 +435,7 @@ class Role(db.Model): self.description = description def __repr__(self): - return '' % (self.name) + return ''.format(self.name) class DomainSetting(db.Model): __tablename__ = 'domain_setting' @@ -451,7 +451,7 @@ class DomainSetting(db.Model): self.value = value def __repr__(self): - return '' % (setting, self.domain.name) + return ''.format(setting, self.domain.name) def __eq__(self, other): return self.setting == other.setting @@ -489,7 +489,7 @@ class Domain(db.Model): self.dnssec = dnssec def __repr__(self): - return '' % (self.name) + return ''.format(self.name) def add_setting(self, setting, value): try: @@ -497,7 +497,7 @@ class Domain(db.Model): db.session.commit() return True except Exception as e: - logging.error('Can not create setting %s for domain %s. %s' % (setting, self.name, e)) + logging.error('Can not create setting {0} for domain {1}. {2}'.format(setting, self.name, e)) return False def get_domains(self): @@ -646,12 +646,11 @@ class Domain(db.Model): logging.error(jdata['error']) return {'status': 'error', 'msg': jdata['error']} else: - logging.info('Added domain %s successfully' % domain_name) + logging.info('Added domain {0} successfully'.format(domain_name)) return {'status': 'ok', 'msg': 'Added domain successfully'} except Exception as e: - traceback.print_exc() - logging.error('Cannot add domain %s' % domain_name) - logging.debug(e) + logging.error('Cannot add domain {0}'.format(domain_name)) + logging.debug(traceback.print_exc()) return {'status': 'error', 'msg': 'Cannot add this domain.'} def create_reverse_domain(self, domain_name, domain_reverse_name): @@ -674,7 +673,7 @@ class Domain(db.Model): result = self.add(domain_reverse_name, 'Master', 'INCEPTION-INCREMENT', '', '') self.update() if result['status'] == 'ok': - history = History(msg='Add reverse lookup domain %s' % domain_reverse_name, detail=str({'domain_type': 'Master', 'domain_master_ips': ''}), created_by='System') + history = History(msg='Add reverse lookup domain {0}'.format(domain_reverse_name), detail=str({'domain_type': 'Master', 'domain_master_ips': ''}), created_by='System') history.add() else: return {'status': 'error', 'msg': 'Adding reverse lookup domain failed'} @@ -716,13 +715,12 @@ class Domain(db.Model): headers = {} headers['X-API-Key'] = PDNS_API_KEY try: - jdata = utils.fetch_json(urljoin(PDNS_STATS_URL, API_EXTENDED_URL + '/servers/localhost/zones/%s' % domain_name), headers=headers, method='DELETE') - logging.info('Delete domain %s successfully' % domain_name) + jdata = utils.fetch_json(urljoin(PDNS_STATS_URL, API_EXTENDED_URL + '/servers/localhost/zones/{0}'.format(domain_name)), headers=headers, method='DELETE') + logging.info('Delete domain {0} successfully'.format(domain_name)) return {'status': 'ok', 'msg': 'Delete domain successfully'} except Exception as e: - traceback.print_exc() - logging.error('Cannot delete domain %s' % domain_name) - logging.debug(e) + logging.error('Cannot delete domain {0}'.format(domain_name)) + logging.debug(traceback.print_exc()) return {'status': 'error', 'msg': 'Cannot delete domain'} def get_user(self): @@ -754,7 +752,7 @@ class Domain(db.Model): db.session.commit() except: db.session.rollback() - logging.error('Cannot revoke user privielges on domain %s' % self.name) + logging.error('Cannot revoke user privielges on domain {0}'.format(self.name)) try: for uid in added_ids: @@ -763,7 +761,7 @@ class Domain(db.Model): db.session.commit() except: db.session.rollback() - logging.error('Cannot grant user privielges to domain %s' % self.name) + logging.error('Cannot grant user privielges to domain {0}'.format(self.name)) def update_from_master(self, domain_name): @@ -775,7 +773,7 @@ class Domain(db.Model): headers = {} headers['X-API-Key'] = PDNS_API_KEY try: - jdata = utils.fetch_json(urljoin(PDNS_STATS_URL, API_EXTENDED_URL + '/servers/localhost/zones/%s/axfr-retrieve' % domain), headers=headers, method='PUT') + jdata = utils.fetch_json(urljoin(PDNS_STATS_URL, API_EXTENDED_URL + '/servers/localhost/zones/{0}/axfr-retrieve'.format(domain)), headers=headers, method='PUT') return {'status': 'ok', 'msg': 'Update from Master successfully'} except: return {'status': 'error', 'msg': 'There was something wrong, please contact administrator'} @@ -791,7 +789,7 @@ class Domain(db.Model): headers = {} headers['X-API-Key'] = PDNS_API_KEY try: - jdata = utils.fetch_json(urljoin(PDNS_STATS_URL, API_EXTENDED_URL + '/servers/localhost/zones/%s/cryptokeys' % domain.name), headers=headers, method='GET') + jdata = utils.fetch_json(urljoin(PDNS_STATS_URL, API_EXTENDED_URL + '/servers/localhost/zones/{0}/cryptokeys'.format(domain.name)), headers=headers, method='GET') if 'error' in jdata: return {'status': 'error', 'msg': 'DNSSEC is not enabled for this domain'} else: @@ -813,7 +811,7 @@ class DomainUser(db.Model): self.user_id = user_id def __repr__(self): - return '' % (self.domain_id, self.user_id) + return ''.format(self.domain_id, self.user_id) class Record(object): @@ -836,7 +834,7 @@ class Record(object): headers = {} headers['X-API-Key'] = PDNS_API_KEY try: - jdata = utils.fetch_json(urljoin(PDNS_STATS_URL, API_EXTENDED_URL + '/servers/localhost/zones/%s' % domain), headers=headers) + jdata = utils.fetch_json(urljoin(PDNS_STATS_URL, API_EXTENDED_URL + '/servers/localhost/zones/{0}'.format(domain)), headers=headers) except: logging.error("Cannot fetch domain's record data from remote powerdns api") return False @@ -910,11 +908,11 @@ class Record(object): } try: - jdata = utils.fetch_json(urljoin(PDNS_STATS_URL, API_EXTENDED_URL + '/servers/localhost/zones/%s' % domain), headers=headers, method='PATCH', data=data) + jdata = utils.fetch_json(urljoin(PDNS_STATS_URL, API_EXTENDED_URL + '/servers/localhost/zones/{0}'.format(domain)), headers=headers, method='PATCH', data=data) logging.debug(jdata) return {'status': 'ok', 'msg': 'Record was added successfully'} except Exception as e: - logging.error("Cannot add record %s/%s/%s to domain %s. DETAIL: %s" % (self.name, self.type, self.data, domain, e)) + logging.error("Cannot add record {0}/{1}/{2} to domain {3}. DETAIL: {4}".format(self.name, self.type, self.data, domain, e)) return {'status': 'error', 'msg': 'There was something wrong, please contact administrator'} @@ -1144,7 +1142,7 @@ class Record(object): self.delete(domain_reverse_name) return {'status': 'ok', 'msg': 'Auto-PTR record was updated successfully'} except Exception as e: - logging.error("Cannot update auto-ptr record changes to domain %s. DETAIL: %s" % (e, domain)) + logging.error("Cannot update auto-ptr record changes to domain {0}. DETAIL: {1}".format(domain, e)) return {'status': 'error', 'msg': 'Auto-PTR creation failed. There was something wrong, please contact administrator.'} def delete(self, domain): @@ -1164,11 +1162,11 @@ class Record(object): ] } try: - jdata = utils.fetch_json(urljoin(PDNS_STATS_URL, API_EXTENDED_URL + '/servers/localhost/zones/%s' % domain), headers=headers, method='PATCH', data=data) + jdata = utils.fetch_json(urljoin(PDNS_STATS_URL, API_EXTENDED_URL + '/servers/localhost/zones/{0}'.format(domain)), headers=headers, method='PATCH', data=data) logging.debug(jdata) return {'status': 'ok', 'msg': 'Record was removed successfully'} except: - logging.error("Cannot remove record %s/%s/%s from domain %s" % (self.name, self.type, self.data, domain)) + logging.error("Cannot remove record {0}/{1}/{2} from domain {3}".format(self.name, self.type, self.data, domain)) return {'status': 'error', 'msg': 'There was something wrong, please contact administrator'} def is_allowed_edit(self): @@ -1244,11 +1242,11 @@ class Record(object): ] } try: - jdata = utils.fetch_json(urljoin(PDNS_STATS_URL, API_EXTENDED_URL + '/servers/localhost/zones/%s' % domain), headers=headers, method='PATCH', data=data) - logging.debug("dyndns data: " % data) + jdata = utils.fetch_json(urljoin(PDNS_STATS_URL, API_EXTENDED_URL + '/servers/localhost/zones/{0}'.format(domain)), headers=headers, method='PATCH', data=data) + logging.debug("dyndns data: {0}".format(data)) return {'status': 'ok', 'msg': 'Record was updated successfully'} except Exception as e: - logging.error("Cannot add record %s/%s/%s to domain %s. DETAIL: %s" % (self.name, self.type, self.data, domain, e)) + logging.error("Cannot add record {0}/{1}/{2} to domain {3}. DETAIL: {4}".format(self.name, self.type, self.data, domain, e)) return {'status': 'error', 'msg': 'There was something wrong, please contact administrator'} @@ -1270,7 +1268,7 @@ class Server(object): headers['X-API-Key'] = PDNS_API_KEY try: - jdata = utils.fetch_json(urljoin(PDNS_STATS_URL, API_EXTENDED_URL + '/servers/%s/config' % self.server_id), headers=headers, method='GET') + jdata = utils.fetch_json(urljoin(PDNS_STATS_URL, API_EXTENDED_URL + '/servers/{0}/config'.format(self.server_id)), headers=headers, method='GET') return jdata except: logging.error("Can not get server configuration.") @@ -1285,7 +1283,7 @@ class Server(object): headers['X-API-Key'] = PDNS_API_KEY try: - jdata = utils.fetch_json(urljoin(PDNS_STATS_URL, API_EXTENDED_URL + '/servers/%s/statistics' % self.server_id), headers=headers, method='GET') + jdata = utils.fetch_json(urljoin(PDNS_STATS_URL, API_EXTENDED_URL + '/servers/{0}/statistics'.format(self.server_id)), headers=headers, method='GET') return jdata except: logging.error("Can not get server statistics.") @@ -1307,7 +1305,7 @@ class History(db.Model): self.created_by = created_by def __repr__(self): - return '' % (self.msg) + return ''.format(self.msg) def add(self): """ @@ -1369,7 +1367,7 @@ class Setting(db.Model): db.session.commit() return True except: - logging.error('Cannot set maintenance to %s' % mode) + logging.error('Cannot set maintenance to {0}'.format(mode)) logging.debug(traceback.format_exc()) db.session.rollback() return False @@ -1386,10 +1384,10 @@ class Setting(db.Model): db.session.commit() return True else: - logging.error('Setting %s does not exist' % setting) + logging.error('Setting {0} does not exist'.format(setting)) return False except: - logging.error('Cannot toggle setting %s' % setting) + logging.error('Cannot toggle setting {0}'.format(setting)) logging.debug(traceback.format_exec()) db.session.rollback() return False @@ -1404,10 +1402,10 @@ class Setting(db.Model): db.session.commit() return True else: - logging.error('Setting %s does not exist' % setting) + logging.error('Setting {0} does not exist'.format(setting)) return False except: - logging.error('Cannot edit setting %s' % setting) + logging.error('Cannot edit setting {0}'.format(setting)) logging.debug(traceback.format_exec()) db.session.rollback() return False @@ -1421,7 +1419,7 @@ class DomainTemplate(db.Model): records = db.relationship('DomainTemplateRecord', back_populates='template', cascade="all, delete-orphan") def __repr__(self): - return '' % self.name + return ''.format(self.name) def __init__(self, name=None, description=None): self.id = None @@ -1474,7 +1472,7 @@ class DomainTemplateRecord(db.Model): template = db.relationship('DomainTemplate', back_populates='records') def __repr__(self): - return '' % self.id + return ''.format(self.id) def __init__(self, id=None, name=None, type=None, ttl=None, data=None, status=None): self.id = id diff --git a/app/views.py b/app/views.py index fea9d4f..d350089 100644 --- a/app/views.py +++ b/app/views.py @@ -154,7 +154,7 @@ def http_page_not_found(e): def error(code, msg=None): supported_code = ('400', '401', '404', '500') if code in supported_code: - return render_template('errors/%s.html' % code, msg=msg), int(code) + return render_template('errors/{0}.html'.format(code), msg=msg), int(code) else: return render_template('errors/404.html'), 404 @@ -346,8 +346,8 @@ def dashboard_domains(): # History.created_on.desc() order_by = [] for i in range(len(columns)): - column_index = request.args.get("order[%d][column]" % i) - sort_direction = request.args.get("order[%d][dir]" % i) + column_index = request.args.get("order[{0}][column]".format(i)) + sort_direction = request.args.get("order[{0}][dir]".format(i)) if column_index is None: break if sort_direction != "asc" and sort_direction != "desc": @@ -462,7 +462,7 @@ def domain_add(): d = Domain() result = d.add(domain_name=domain_name, domain_type=domain_type, soa_edit_api=soa_edit_api, domain_master_ips=domain_master_ips) if result['status'] == 'ok': - history = History(msg='Add domain %s' % domain_name, detail=str({'domain_type': domain_type, 'domain_master_ips': domain_master_ips}), 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}), created_by=current_user.username) history.add() if domain_template != '0': template = DomainTemplate.query.filter(DomainTemplate.id == domain_template).first() @@ -474,10 +474,10 @@ def domain_add(): r = Record() result = r.apply(domain_name, record_data) if result['status'] == 'ok': - history = History(msg='Applying template %s to %s, created records successfully.' % (template.name, domain_name), detail=str(result), created_by=current_user.username) + history = History(msg='Applying template {0} to {1}, created records successfully.'.format(template.name, domain_name), detail=str(result), created_by=current_user.username) history.add() else: - history = History(msg='Applying template %s to %s, FAILED to created records.' % (template.name, domain_name), detail=str(result), created_by=current_user.username) + history = History(msg='Applying template {0} to {1}, FAILED to created records.'.format(template.name, domain_name), detail=str(result), created_by=current_user.username) history.add() return redirect(url_for('dashboard')) else: @@ -498,7 +498,7 @@ def domain_delete(domain_name): if result['status'] == 'error': return redirect(url_for('error', code=500)) - history = History(msg='Delete domain %s' % domain_name, created_by=current_user.username) + history = History(msg='Delete domain {0}'.format(domain_name), created_by=current_user.username) history.add() return redirect(url_for('dashboard')) @@ -531,7 +531,7 @@ def domain_management(domain_name): # grant/revoke user privielges d.grant_privielges(new_user_list) - history = History(msg='Change domain %s access control' % 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() return redirect(url_for('domain_management', domain_name=domain_name)) @@ -551,7 +551,7 @@ def record_apply(domain_name): r = Record() result = r.apply(domain_name, jdata) if result['status'] == 'ok': - history = History(msg='Apply record changes to domain %s' % domain_name, detail=str(jdata), created_by=current_user.username) + history = History(msg='Apply record changes to domain {0}'.format(domain_name), detail=str(jdata), created_by=current_user.username) history.add() return make_response(jsonify( result ), 200) else: @@ -629,14 +629,14 @@ def admin_setdomainsetting(domain_name): if setting: if setting.set(new_value): - history = History(msg='Setting %s changed value to %s for %s' % (new_setting, new_value, domain.name), created_by=current_user.username) + history = History(msg='Setting {0} changed value to {1} for {2}'.format(new_setting, new_value, domain.name), created_by=current_user.username) history.add() return make_response(jsonify( { 'status': 'ok', 'msg': 'Setting updated.' } )) else: return make_response(jsonify( { 'status': 'error', 'msg': 'Unable to set value of setting.' } )) else: if domain.add_setting(new_setting, new_value): - history = History(msg='New setting %s with value %s for %s has been created' % (new_setting, new_value, domain.name), created_by=current_user.username) + history = History(msg='New setting {0} with value {1} for {2} has been created'.format(new_setting, new_value, domain.name), created_by=current_user.username) history.add() return make_response(jsonify( { 'status': 'ok', 'msg': 'New setting created and updated.' } )) else: @@ -673,12 +673,12 @@ def create_template(): return redirect(url_for('create_template')) if DomainTemplate.query.filter(DomainTemplate.name == name).first(): - flash("A template with the name %s already exists!" % name, 'error') + flash("A template with the name {0} already exists!".format(name), 'error') return redirect(url_for('create_template')) t = DomainTemplate(name=name, description=description) result = t.create() if result['status'] == 'ok': - history = History(msg='Add domain template %s' % name, detail=str({'name': name, 'description': description}), created_by=current_user.username) + history = History(msg='Add domain template {0}'.format(name), detail=str({'name': name, 'description': description}), created_by=current_user.username) history.add() return redirect(url_for('templates')) else: @@ -704,12 +704,12 @@ def create_template_from_zone(): return make_response(jsonify({'status': 'error', 'msg': 'Please correct template name'}), 500) if DomainTemplate.query.filter(DomainTemplate.name == name).first(): - return make_response(jsonify({'status': 'error', 'msg': 'A template with the name %s already exists!' % name}), 500) + return make_response(jsonify({'status': 'error', 'msg': 'A template with the name {0} already exists!'.format(name)}), 500) t = DomainTemplate(name=name, description=description) result = t.create() if result['status'] == 'ok': - history = History(msg='Add domain template %s' % name, detail=str({'name': name, 'description': description}), created_by=current_user.username) + history = History(msg='Add domain template {0}'.format(name), detail=str({'name': name, 'description': description}), created_by=current_user.username) history.add() records = [] @@ -789,7 +789,7 @@ def apply_records(template): t = DomainTemplate.query.filter(DomainTemplate.name == template).first() result = t.replace_records(records) if result['status'] == 'ok': - history = History(msg='Apply domain template record changes to domain template %s' % template, detail=str(jdata), created_by=current_user.username) + history = History(msg='Apply domain template record changes to domain template {0}'.format(template), detail=str(jdata), created_by=current_user.username) history.add() return make_response(jsonify(result), 200) else: @@ -808,7 +808,7 @@ def delete_template(template): if t is not None: result = t.delete_template() if result['status'] == 'ok': - history = History(msg='Deleted domain template %s' % template, detail=str({'name': template}), created_by=current_user.username) + history = History(msg='Deleted domain template {0}'.format(template), detail=str({'name': template}), created_by=current_user.username) history.add() return redirect(url_for('templates')) else: @@ -883,7 +883,7 @@ def admin_manageuser(): user = User(username=data) result = user.delete() if result: - history = History(msg='Delete username %s' % data, created_by=current_user.username) + history = History(msg='Delete username {0}'.format(data), created_by=current_user.username) history.add() return make_response(jsonify( { 'status': 'ok', 'msg': 'User has been removed.' } ), 200) else: @@ -893,7 +893,7 @@ def admin_manageuser(): user = User(username=data) result = user.revoke_privilege() if result: - history = History(msg='Revoke %s user privielges' % data, created_by=current_user.username) + history = History(msg='Revoke {0} user privielges'.format(data), created_by=current_user.username) history.add() return make_response(jsonify( { 'status': 'ok', 'msg': 'Revoked user privielges.' } ), 200) else: @@ -905,7 +905,7 @@ def admin_manageuser(): user = User(username=username) result = user.set_admin(is_admin) if result: - history = History(msg='Change user role of %s' % username, created_by=current_user.username) + history = History(msg='Change user role of {0}'.format(username), created_by=current_user.username) history.add() return make_response(jsonify( { 'status': 'ok', 'msg': 'Changed user role successfully.' } ), 200) else: @@ -991,7 +991,7 @@ def user_profile(): enable_otp = data['enable_otp'] user = User(username=current_user.username) user.update_profile(enable_otp=enable_otp) - return make_response(jsonify( { 'status': 'ok', 'msg': 'Change OTP Authentication successfully. Status: %s' % enable_otp } ), 200) + return make_response(jsonify( { 'status': 'ok', 'msg': 'Change OTP Authentication successfully. Status: {0}'.format(enable_otp) } ), 200) # get new avatar save_file_name = None @@ -1071,7 +1071,7 @@ def dyndns_update(): break if not domain: - history = History(msg="DynDNS update: attempted update of %s but it does not exist for this user" % hostname, created_by=current_user.username) + history = History(msg="DynDNS update: attempted update of {0} but it does not exist for this user".format(hostname), created_by=current_user.username) history.add() return render_template('dyndns.html', response='nohost'), 200 @@ -1081,14 +1081,14 @@ def dyndns_update(): if r.exists(domain.name) and r.is_allowed_edit(): if r.data == myip: # record content did not change, return 'nochg' - history = History(msg="DynDNS update: attempted update of %s but record did not change" % hostname, created_by=current_user.username) + history = History(msg="DynDNS update: attempted update of {0} but record did not change".format(hostname), created_by=current_user.username) history.add() return render_template('dyndns.html', response='nochg'), 200 else: oldip = r.data result = r.update(domain.name, myip) if result['status'] == 'ok': - history = History(msg='DynDNS update: updated record %s in zone %s, it changed from %s to %s' % (hostname,domain.name,oldip,myip), detail=str(result), created_by=current_user.username) + history = History(msg='DynDNS update: updated record {0} in zone {1}, it changed from {2} to {3}'.format(hostname,domain.name,oldip,myip), detail=str(result), created_by=current_user.username) history.add() return render_template('dyndns.html', response='good'), 200 else: @@ -1099,11 +1099,11 @@ def dyndns_update(): record = Record(name=hostname,type='A',data=myip,status=False,ttl=3600) result = record.add(domain.name) if result['status'] == 'ok': - history = History(msg='DynDNS update: created record %s in zone %s, it now represents %s' % (hostname,domain.name,myip), detail=str(result), created_by=current_user.username) + history = History(msg='DynDNS update: created record {0} in zone {1}, it now represents {2}'.format(hostname,domain.name,myip), detail=str(result), created_by=current_user.username) history.add() return render_template('dyndns.html', response='good'), 200 - history = History(msg="DynDNS update: attempted update of %s but it does not exist for this user" % hostname, created_by=current_user.username) + history = History(msg='DynDNS update: attempted update of {0} but it does not exist for this user'.format(hostname), created_by=current_user.username) history.add() return render_template('dyndns.html', response='nohost'), 200 From 5df7fe445f15d48863d2f8880da90cfaa3a939a0 Mon Sep 17 00:00:00 2001 From: Khanh Ngo Date: Sun, 1 Apr 2018 14:32:20 +0700 Subject: [PATCH 35/42] Emphasis on zone name --- app/templates/domain.html | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/app/templates/domain.html b/app/templates/domain.html index 8a67257..6c899d6 100644 --- a/app/templates/domain.html +++ b/app/templates/domain.html @@ -1,10 +1,10 @@ {% extends "base.html" %} -{% block title %}DNS Control Panel - DOMAIN{% endblock %} +{% block title %}{{ domain.name }} - DNS Control Panel{% endblock %} {% block dashboard_stat %}

    - Manage domain {{ domain.name }} + Manage domain: {{ domain.name }}