From 5f049debe526fd29da836bf366fc967a79bc6551 Mon Sep 17 00:00:00 2001 From: Khanh Ngo Date: Wed, 21 Nov 2018 10:24:33 +0700 Subject: [PATCH] Adding Flask-SeaSurf module for CSRF protection. --- app/__init__.py | 2 ++ app/static/custom/js/custom.js | 6 ++-- app/templates/admin_editaccount.html | 3 +- app/templates/admin_edituser.html | 7 ++-- app/templates/admin_history.html | 6 ++-- app/templates/admin_manageaccount.html | 8 ++--- app/templates/admin_manageuser.html | 15 ++++---- .../admin_setting_authentication.html | 7 +++- app/templates/admin_setting_basic.html | 10 +++--- app/templates/admin_setting_pdns.html | 3 +- app/templates/admin_setting_records.html | 3 +- app/templates/dashboard.html | 12 +++---- app/templates/domain.html | 6 ++-- app/templates/domain_add.html | 1 + app/templates/domain_management.html | 15 ++++---- app/templates/login.html | 1 + app/templates/register.html | 1 + app/templates/template.html | 17 +++++++--- app/templates/template_add.html | 1 + app/templates/template_edit.html | 2 +- app/templates/user_profile.html | 7 +++- app/views.py | 34 ++++++------------- config_template.py | 1 - configs/development.py | 1 - requirements.txt | 1 + 25 files changed, 93 insertions(+), 77 deletions(-) diff --git a/app/__init__.py b/app/__init__.py index 3747067..bca647e 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -5,6 +5,7 @@ from flask_sqlalchemy import SQLAlchemy as SA from flask_migrate import Migrate from authlib.flask.client import OAuth as AuthlibOAuth from sqlalchemy.exc import OperationalError +from flask_seasurf import SeaSurf # subclass SQLAlchemy to enable pool_pre_ping class SQLAlchemy(SA): @@ -18,6 +19,7 @@ from app.assets import assets app = Flask(__name__) app.config.from_object('config') app.wsgi_app = ProxyFix(app.wsgi_app) +csrf = SeaSurf(app) assets.init_app(app) diff --git a/app/static/custom/js/custom.js b/app/static/custom/js/custom.js index d30cebe..85e523d 100644 --- a/app/static/custom/js/custom.js +++ b/app/static/custom/js/custom.js @@ -145,8 +145,8 @@ function SelectElement(elementID, valueToSelect) element.value = valueToSelect; } -function enable_dns_sec(url) { - $.getJSON(url, function(data) { +function enable_dns_sec(url, csrf_token) { + $.post(url, {'_csrf_token': csrf_token}, function(data) { var modal = $("#modal_dnssec_info"); if (data['status'] == 'error'){ @@ -157,7 +157,7 @@ function enable_dns_sec(url) { //location.reload(); window.location.reload(true); } - }) + }, 'json') } function getdnssec(url, domain){ diff --git a/app/templates/admin_editaccount.html b/app/templates/admin_editaccount.html index 7a46a89..409fec4 100644 --- a/app/templates/admin_editaccount.html +++ b/app/templates/admin_editaccount.html @@ -28,6 +28,7 @@
+
{% if error %} @@ -116,4 +117,4 @@ -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/app/templates/admin_edituser.html b/app/templates/admin_edituser.html index d374d22..ec7102e 100644 --- a/app/templates/admin_edituser.html +++ b/app/templates/admin_edituser.html @@ -28,6 +28,7 @@ +
{% if error %} @@ -115,14 +116,14 @@ {% endblock %} {% block extrascripts %} -{% endblock %} +{% endblock %} {% block modals %} diff --git a/app/templates/admin_manageaccount.html b/app/templates/admin_manageaccount.html index 579d463..efe6210 100644 --- a/app/templates/admin_manageaccount.html +++ b/app/templates/admin_manageaccount.html @@ -67,7 +67,7 @@
-{% endblock %} +{% endblock %} {% block extrascripts %} {% endblock %} {% block modals %} diff --git a/app/templates/admin_manageuser.html b/app/templates/admin_manageuser.html index 2c8c3c4..4832afc 100644 --- a/app/templates/admin_manageuser.html +++ b/app/templates/admin_manageuser.html @@ -81,7 +81,7 @@
-{% endblock %} +{% endblock %} {% block extrascripts %} -{% endblock %} +{% endblock %} diff --git a/app/templates/admin_setting_pdns.html b/app/templates/admin_setting_pdns.html index ce44b48..37a1852 100644 --- a/app/templates/admin_setting_pdns.html +++ b/app/templates/admin_setting_pdns.html @@ -26,6 +26,7 @@ +
{% if not SETTING.get('pdns_api_url') or not SETTING.get('pdns_api_key') or not SETTING.get('pdns_version') %}
@@ -82,4 +83,4 @@ {% assets "js_validation" -%} {%- endassets %} -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/app/templates/admin_setting_records.html b/app/templates/admin_setting_records.html index fd1f316..7366e8c 100644 --- a/app/templates/admin_setting_records.html +++ b/app/templates/admin_setting_records.html @@ -26,6 +26,7 @@ +
@@ -75,4 +76,4 @@ increaseArea : '20%' }) -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/app/templates/dashboard.html b/app/templates/dashboard.html index 50c8e00..b27cb08 100644 --- a/app/templates/dashboard.html +++ b/app/templates/dashboard.html @@ -220,9 +220,9 @@ \ \ "; - modal.find('.modal-body p').html(form); - modal.find('#button_save').click(function() { - var data = {}; + modal.find('.modal-body p').html(form); + modal.find('#button_save').click(function() { + var data = {'_csrf_token': '{{ csrf_token() }}'}; data['name'] = modal.find('#template_name').val(); data['description'] = modal.find('#template_description').val(); data['domain'] = modal.find('#domain').val(); @@ -232,7 +232,7 @@ modal.find('#button_close').click(function() { modal.modal('hide'); }) - + modal.modal('show'); }); @@ -244,13 +244,13 @@ $(document.body).on("click", ".button_dnssec_enable", function() { var domain = $(this).prop('id'); - enable_dns_sec($SCRIPT_ROOT + '/domain/' + domain + '/dnssec/enable'); + enable_dns_sec($SCRIPT_ROOT + '/domain/' + domain + '/dnssec/enable', '{{ csrf_token() }}'); }); $(document.body).on("click", ".button_dnssec_disable", function() { var domain = $(this).prop('id'); - enable_dns_sec($SCRIPT_ROOT + '/domain/' + domain + '/dnssec/disable'); + enable_dns_sec($SCRIPT_ROOT + '/domain/' + domain + '/dnssec/disable', '{{ csrf_token() }}'); }); {% endif %} diff --git a/app/templates/domain.html b/app/templates/domain.html index b82808b..a9613a4 100644 --- a/app/templates/domain.html +++ b/app/templates/domain.html @@ -203,13 +203,13 @@ var modal = $("#modal_apply_changes"); var table = $("#tbl_records").DataTable(); var domain = $(this).prop('id'); - var serial = $(".button_apply_changes").val(); + var serial = $(".button_apply_changes").val(); var info = "Are you sure you want to apply your changes?"; modal.find('.modal-body p').text(info); // following unbind("click") is to avoid multiple times execution modal.find('#button_apply_confirm').unbind("click").click(function() { - var data = {'serial': serial, 'record': getTableData(table)}; + var data = {'serial': serial, 'record': getTableData(table), '_csrf_token': '{{ csrf_token() }}'}; applyRecordChanges(data, domain); modal.modal('hide'); }) @@ -263,7 +263,7 @@ //handle update_from_master button $(document.body).on("click", ".button_update_from_master", function (e) { var domain = $(this).prop('id'); - applyChanges({'domain': domain}, $SCRIPT_ROOT + '/domain/' + domain + '/update'); + applyChanges({'domain': domain, '_csrf_token': '{{ csrf_token() }}'}, $SCRIPT_ROOT + '/domain/' + domain + '/update'); }); {% if SETTING.get('record_helper') %} diff --git a/app/templates/domain_add.html b/app/templates/domain_add.html index 39d5f8e..b57f658 100644 --- a/app/templates/domain_add.html +++ b/app/templates/domain_add.html @@ -28,6 +28,7 @@ +
diff --git a/app/templates/domain_management.html b/app/templates/domain_management.html index 4e504d1..c8900c2 100644 --- a/app/templates/domain_management.html +++ b/app/templates/domain_management.html @@ -35,6 +35,7 @@
+

Domain Access Control

@@ -81,6 +82,7 @@
+  Allow on-demand creation of records via DynDNS updates?

-
@@ -152,6 +152,7 @@ New SOA-EDIT-API Setting: +
diff --git a/app/templates/register.html b/app/templates/register.html index 593279c..b0a5eb0 100644 --- a/app/templates/register.html +++ b/app/templates/register.html @@ -32,6 +32,7 @@ {% endif %} +
diff --git a/app/templates/template.html b/app/templates/template.html index 1ba42ca..0739488 100644 --- a/app/templates/template.html +++ b/app/templates/template.html @@ -79,11 +79,9 @@ Edit  - - - + {% endfor %} @@ -111,6 +109,15 @@ "info" : false, "autoWidth" : false }); + // handle delete button + $(document.body).on("click", ".button_delete", function(e) { + var template = $(this).prop('id'); + $.post($SCRIPT_ROOT + '/template/' + template + '/delete', { '_csrf_token': '{{ csrf_token() }}' }, function() { + window.location.href = '{{ url_for('templates') }}'; + }); + + }); + {% endblock %} {% block modals %} diff --git a/app/templates/template_add.html b/app/templates/template_add.html index 35dfda5..aa94d28 100644 --- a/app/templates/template_add.html +++ b/app/templates/template_add.html @@ -50,6 +50,7 @@ if errors %} +
+
+
@@ -95,6 +97,7 @@ Your account password is managed via LDAP which isn't supported to change here. {% else %} +
@@ -113,6 +116,7 @@
+
@@ -165,7 +169,8 @@ 'action' : 'enable_otp', 'data' : { 'enable_otp' : enable_otp - } + }, + '_csrf_token': '{{ csrf_token() }}' }; applyChanges(postdata, $SCRIPT_ROOT + '/user/profile', false, true); }); diff --git a/app/views.py b/app/views.py index 56fe5c6..ff9cb9d 100644 --- a/app/views.py +++ b/app/views.py @@ -18,7 +18,7 @@ from flask_login import login_user, logout_user, current_user, login_required from werkzeug import secure_filename from .models import User, Account, Domain, Record, Role, Server, History, Anonymous, Setting, DomainSetting, DomainTemplate, DomainTemplateRecord -from app import app, login_manager +from app import app, login_manager, csrf from app.lib import utils from app.oauth import github_oauth, google_oauth, oidc_oauth from app.decorators import admin_role_required, operator_role_required, can_access_domain, can_configure_dnssec, can_create_domain @@ -184,7 +184,6 @@ def oidc_login(): else: redirect_uri = url_for('oidc_authorized', _external=True) return oidc.authorize_redirect(redirect_uri) - @app.route('/saml/login') def saml_login(): @@ -215,6 +214,7 @@ def saml_metadata(): @app.route('/saml/authorized', methods=['GET', 'POST']) +@csrf.exempt def saml_authorized(): errors = [] if not app.config.get('SAML_ENABLED'): @@ -710,7 +710,7 @@ def domain_add(): return render_template('domain_add.html', templates=templates, accounts=accounts) -@app.route('/admin/domain//delete', methods=['GET']) +@app.route('/admin/domain//delete', methods=['POST']) @login_required @operator_role_required def domain_delete(domain_name): @@ -864,22 +864,6 @@ 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']) -@login_required -@operator_role_required -def record_delete(domain_name, record_name, record_type): - try: - r = Record(name=record_name, type=record_type) - result = r.delete(domain=domain_name) - if result['status'] == 'error': - print(result['msg']) - except Exception as e: - logging.error('Cannot delete record. Error: {0}'.format(e)) - logging.debug(traceback.format_exc()) - return redirect(url_for('error', code=500)), 500 - return redirect(url_for('domain', domain_name=domain_name)) - - @app.route('/domain//info', methods=['GET']) @login_required @can_access_domain @@ -898,7 +882,7 @@ def domain_dnssec(domain_name): return make_response(jsonify(dnssec), 200) -@app.route('/domain//dnssec/enable', methods=['GET']) +@app.route('/domain//dnssec/enable', methods=['POST']) @login_required @can_access_domain @can_configure_dnssec @@ -908,7 +892,7 @@ def domain_dnssec_enable(domain_name): return make_response(jsonify(dnssec), 200) -@app.route('/domain//dnssec/disable', methods=['GET']) +@app.route('/domain//dnssec/disable', methods=['POST']) @login_required @can_access_domain @can_configure_dnssec @@ -1097,7 +1081,7 @@ def apply_records(template): jdata = request.json records = [] - for j in jdata: + for j in jdata['records']: name = '@' if j['record_name'] in ['@', ''] else j['record_name'] type = j['record_type'] data = j['record_data'] @@ -1121,7 +1105,7 @@ def apply_records(template): return make_response(jsonify({'status': 'error', 'msg': 'Error when applying new changes'}), 500) -@app.route('/template//delete', methods=['GET']) +@app.route('/template//delete', methods=['POST']) @login_required @operator_role_required def delete_template(template): @@ -1418,7 +1402,7 @@ def admin_setting_basic(): 'allow_user_create_domain', 'bg_domain_updates', 'site_name', - 'session_timeout' ] + 'session_timeout' ] return render_template('admin_setting_basic.html', settings=settings) @@ -1638,6 +1622,7 @@ def qrcode(): @app.route('/nic/checkip.html', methods=['GET', 'POST']) +@csrf.exempt 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)) @@ -1645,6 +1630,7 @@ def dyndns_checkip(): @app.route('/nic/update', methods=['GET', 'POST']) @dyndns_login_required +@csrf.exempt def dyndns_update(): # dyndns protocol response codes in use are: # good: update successful diff --git a/config_template.py b/config_template.py index 2c34193..1fce2eb 100644 --- a/config_template.py +++ b/config_template.py @@ -2,7 +2,6 @@ import os basedir = os.path.abspath(os.path.dirname(__file__)) # BASIC APP CONFIG -WTF_CSRF_ENABLED = True SECRET_KEY = 'We are the world' BIND_ADDRESS = '127.0.0.1' PORT = 9191 diff --git a/configs/development.py b/configs/development.py index 48a3884..1caadf3 100644 --- a/configs/development.py +++ b/configs/development.py @@ -2,7 +2,6 @@ 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 = 'logs/log.txt' diff --git a/requirements.txt b/requirements.txt index 1bda3cc..d89786c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -19,3 +19,4 @@ pytz>=2017.3 cssmin==0.2.0 jsmin==2.2.2 Authlib==0.10 +Flask-Seasurf