From 1926b862b8fcf784546e1508308d9dbbeaf72dfb Mon Sep 17 00:00:00 2001 From: TomSebty <61846662+TomSebty@users.noreply.github.com> Date: Fri, 17 Jun 2022 18:50:51 +0300 Subject: [PATCH] feat: Option to forbid the creation of domain if it exists as a record (#1127) When enabled, forbids the creation of a domain if it exists as a record in one of its parent domains (administrators and operators are not limited though). --- powerdnsadmin/decorators.py | 19 +++++++++++- powerdnsadmin/lib/errors.py | 7 +++++ powerdnsadmin/models/domain.py | 15 +++++++++ powerdnsadmin/models/setting.py | 3 +- powerdnsadmin/routes/admin.py | 2 +- powerdnsadmin/routes/domain.py | 38 +++++++++++++++++++++-- powerdnsadmin/templates/domain_add.html | 41 +++++++++++++++++++++++++ 7 files changed, 120 insertions(+), 5 deletions(-) diff --git a/powerdnsadmin/decorators.py b/powerdnsadmin/decorators.py index df1e348..819ee36 100644 --- a/powerdnsadmin/decorators.py +++ b/powerdnsadmin/decorators.py @@ -6,7 +6,7 @@ from flask_login import current_user from .models import User, ApiKey, Setting, Domain, Setting from .lib.errors import RequestIsNotJSON, NotEnoughPrivileges -from .lib.errors import DomainAccessForbidden +from .lib.errors import DomainAccessForbidden, DomainOverrideForbidden def admin_role_required(f): """ @@ -259,6 +259,13 @@ def api_can_create_domain(f): msg = "User {0} does not have enough privileges to create domain" current_app.logger.error(msg.format(current_user.username)) raise NotEnoughPrivileges() + + if Setting().get('deny_domain_override'): + req = request.get_json(force=True) + domain = Domain() + if req['name'] and domain.is_overriding(req['name']): + raise DomainOverrideForbidden() + return f(*args, **kwargs) return decorated_function @@ -269,6 +276,9 @@ def apikey_can_create_domain(f): Grant access if: - user is in Operator role or higher, or - allow_user_create_domain is on + and + - deny_domain_override is off or + - override_domain is true (from request) """ @wraps(f) def decorated_function(*args, **kwargs): @@ -278,6 +288,13 @@ def apikey_can_create_domain(f): msg = "ApiKey #{0} does not have enough privileges to create domain" current_app.logger.error(msg.format(g.apikey.id)) raise NotEnoughPrivileges() + + if Setting().get('deny_domain_override'): + req = request.get_json(force=True) + domain = Domain() + if req['name'] and domain.is_overriding(req['name']): + raise DomainOverrideForbidden() + return f(*args, **kwargs) return decorated_function diff --git a/powerdnsadmin/lib/errors.py b/powerdnsadmin/lib/errors.py index 687f554..f570b4e 100644 --- a/powerdnsadmin/lib/errors.py +++ b/powerdnsadmin/lib/errors.py @@ -44,6 +44,13 @@ class DomainAccessForbidden(StructuredException): self.message = message self.name = name +class DomainOverrideForbidden(StructuredException): + status_code = 409 + + def __init__(self, name=None, message="Domain override of record not allowed"): + StructuredException.__init__(self) + self.message = message + self.name = name class ApiKeyCreateFail(StructuredException): status_code = 500 diff --git a/powerdnsadmin/models/domain.py b/powerdnsadmin/models/domain.py index cfc949d..e4b2776 100644 --- a/powerdnsadmin/models/domain.py +++ b/powerdnsadmin/models/domain.py @@ -881,3 +881,18 @@ class Domain(db.Model): DomainUser.user_id == user_id, AccountUser.user_id == user_id )).filter(Domain.id == self.id).first() + + # Return None if this domain does not exist as record, + # Return the parent domain that hold the record if exist + def is_overriding(self, domain_name): + upper_domain_name = '.'.join(domain_name.split('.')[1:]) + while upper_domain_name != '': + if self.get_id_by_name(upper_domain_name.rstrip('.')) != None: + upper_domain = self.get_domain_info(upper_domain_name) + if 'rrsets' in upper_domain: + for r in upper_domain['rrsets']: + if domain_name.rstrip('.') in r['name'].rstrip('.'): + current_app.logger.error('Domain already exists as a record: {} under domain: {}'.format(r['name'].rstrip('.'), upper_domain_name)) + return upper_domain_name + upper_domain_name = '.'.join(upper_domain_name.split('.')[1:]) + return None \ No newline at end of file diff --git a/powerdnsadmin/models/setting.py b/powerdnsadmin/models/setting.py index 33b8db5..46e6724 100644 --- a/powerdnsadmin/models/setting.py +++ b/powerdnsadmin/models/setting.py @@ -190,7 +190,8 @@ class Setting(db.Model): 'otp_field_enabled': True, 'custom_css': '', 'otp_force': False, - 'max_history_records': 1000 + 'max_history_records': 1000, + 'deny_domain_override': False } def __init__(self, id=None, name=None, value=None): diff --git a/powerdnsadmin/routes/admin.py b/powerdnsadmin/routes/admin.py index ab5dce0..4782ba0 100644 --- a/powerdnsadmin/routes/admin.py +++ b/powerdnsadmin/routes/admin.py @@ -1268,7 +1268,7 @@ def setting_basic(): 'allow_user_create_domain', 'allow_user_remove_domain', 'allow_user_view_history', 'bg_domain_updates', 'site_name', 'session_timeout', 'warn_session_timeout', 'ttl_options', 'pdns_api_timeout', 'verify_ssl_connections', 'verify_user_email', - 'delete_sso_accounts', 'otp_field_enabled', 'custom_css', 'enable_api_rr_history', 'max_history_records', 'otp_force' + 'delete_sso_accounts', 'otp_field_enabled', 'custom_css', 'enable_api_rr_history', 'max_history_records', 'otp_force', 'deny_domain_override' ] return render_template('admin_setting_basic.html', settings=settings) diff --git a/powerdnsadmin/routes/domain.py b/powerdnsadmin/routes/domain.py index f94b2a1..3261264 100644 --- a/powerdnsadmin/routes/domain.py +++ b/powerdnsadmin/routes/domain.py @@ -32,7 +32,6 @@ domain_bp = Blueprint('domain', template_folder='templates', url_prefix='/domain') - @domain_bp.before_request def before_request(): # Check if user is anonymous @@ -401,6 +400,38 @@ def add(): account_name = Account().get_name_by_id(account_id) d = Domain() + + ### Test if a record same as the domain already exists in an upper level domain + if Setting().get('deny_domain_override'): + + upper_domain = None + domain_override = False + domain_override_toggle = False + + if current_user.role.name in ['Administrator', 'Operator']: + domain_override = request.form.get('domain_override') + domain_override_toggle = True + + + # If overriding box is not selected. + # False = Do not allow ovrriding, perform checks + # True = Allow overriding, do not perform checks + if not domain_override: + upper_domain = d.is_overriding(domain_name) + + if upper_domain: + if current_user.role.name in ['Administrator', 'Operator']: + accounts = Account.query.order_by(Account.name).all() + else: + accounts = current_user.get_accounts() + + msg = 'Domain already exists as a record under domain: {}'.format(upper_domain) + + return render_template('domain_add.html', + domain_override_message=msg, + accounts=accounts, + domain_override_toggle=domain_override_toggle) + result = d.add(domain_name=domain_name, domain_type=domain_type, soa_edit_api=soa_edit_api, @@ -478,14 +509,17 @@ def add(): # Get else: + domain_override_toggle = False # Admins and Operators can set to any account if current_user.role.name in ['Administrator', 'Operator']: accounts = Account.query.order_by(Account.name).all() + domain_override_toggle = True else: accounts = current_user.get_accounts() return render_template('domain_add.html', templates=templates, - accounts=accounts) + accounts=accounts, + domain_override_toggle=domain_override_toggle) diff --git a/powerdnsadmin/templates/domain_add.html b/powerdnsadmin/templates/domain_add.html index f6e0054..607730f 100644 --- a/powerdnsadmin/templates/domain_add.html +++ b/powerdnsadmin/templates/domain_add.html @@ -34,6 +34,13 @@ + {% if domain_override_toggle == True %} +