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).
This commit is contained in:
TomSebty 2022-06-17 18:50:51 +03:00 committed by GitHub
parent 1112105683
commit 1926b862b8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 120 additions and 5 deletions

View File

@ -6,7 +6,7 @@ from flask_login import current_user
from .models import User, ApiKey, Setting, Domain, Setting from .models import User, ApiKey, Setting, Domain, Setting
from .lib.errors import RequestIsNotJSON, NotEnoughPrivileges from .lib.errors import RequestIsNotJSON, NotEnoughPrivileges
from .lib.errors import DomainAccessForbidden from .lib.errors import DomainAccessForbidden, DomainOverrideForbidden
def admin_role_required(f): 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" msg = "User {0} does not have enough privileges to create domain"
current_app.logger.error(msg.format(current_user.username)) current_app.logger.error(msg.format(current_user.username))
raise NotEnoughPrivileges() 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 f(*args, **kwargs)
return decorated_function return decorated_function
@ -269,6 +276,9 @@ def apikey_can_create_domain(f):
Grant access if: Grant access if:
- user is in Operator role or higher, or - user is in Operator role or higher, or
- allow_user_create_domain is on - allow_user_create_domain is on
and
- deny_domain_override is off or
- override_domain is true (from request)
""" """
@wraps(f) @wraps(f)
def decorated_function(*args, **kwargs): 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" msg = "ApiKey #{0} does not have enough privileges to create domain"
current_app.logger.error(msg.format(g.apikey.id)) current_app.logger.error(msg.format(g.apikey.id))
raise NotEnoughPrivileges() 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 f(*args, **kwargs)
return decorated_function return decorated_function

View File

@ -44,6 +44,13 @@ class DomainAccessForbidden(StructuredException):
self.message = message self.message = message
self.name = name 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): class ApiKeyCreateFail(StructuredException):
status_code = 500 status_code = 500

View File

@ -881,3 +881,18 @@ class Domain(db.Model):
DomainUser.user_id == user_id, DomainUser.user_id == user_id,
AccountUser.user_id == user_id AccountUser.user_id == user_id
)).filter(Domain.id == self.id).first() )).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

View File

@ -190,7 +190,8 @@ class Setting(db.Model):
'otp_field_enabled': True, 'otp_field_enabled': True,
'custom_css': '', 'custom_css': '',
'otp_force': False, 'otp_force': False,
'max_history_records': 1000 'max_history_records': 1000,
'deny_domain_override': False
} }
def __init__(self, id=None, name=None, value=None): def __init__(self, id=None, name=None, value=None):

View File

@ -1268,7 +1268,7 @@ def setting_basic():
'allow_user_create_domain', 'allow_user_remove_domain', 'allow_user_view_history', 'bg_domain_updates', 'site_name', 'allow_user_create_domain', 'allow_user_remove_domain', 'allow_user_view_history', 'bg_domain_updates', 'site_name',
'session_timeout', 'warn_session_timeout', 'ttl_options', 'session_timeout', 'warn_session_timeout', 'ttl_options',
'pdns_api_timeout', 'verify_ssl_connections', 'verify_user_email', '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) return render_template('admin_setting_basic.html', settings=settings)

View File

@ -32,7 +32,6 @@ domain_bp = Blueprint('domain',
template_folder='templates', template_folder='templates',
url_prefix='/domain') url_prefix='/domain')
@domain_bp.before_request @domain_bp.before_request
def before_request(): def before_request():
# Check if user is anonymous # Check if user is anonymous
@ -401,6 +400,38 @@ def add():
account_name = Account().get_name_by_id(account_id) account_name = Account().get_name_by_id(account_id)
d = Domain() 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, result = d.add(domain_name=domain_name,
domain_type=domain_type, domain_type=domain_type,
soa_edit_api=soa_edit_api, soa_edit_api=soa_edit_api,
@ -478,14 +509,17 @@ def add():
# Get # Get
else: else:
domain_override_toggle = False
# Admins and Operators can set to any account # Admins and Operators can set to any account
if current_user.role.name in ['Administrator', 'Operator']: if current_user.role.name in ['Administrator', 'Operator']:
accounts = Account.query.order_by(Account.name).all() accounts = Account.query.order_by(Account.name).all()
domain_override_toggle = True
else: else:
accounts = current_user.get_accounts() accounts = current_user.get_accounts()
return render_template('domain_add.html', return render_template('domain_add.html',
templates=templates, templates=templates,
accounts=accounts) accounts=accounts,
domain_override_toggle=domain_override_toggle)

View File

@ -34,6 +34,13 @@
<input type="text" class="form-control" name="domain_name" id="domain_name" <input type="text" class="form-control" name="domain_name" id="domain_name"
placeholder="Enter a valid domain name (required)"> placeholder="Enter a valid domain name (required)">
</div> </div>
{% if domain_override_toggle == True %}
<div class="form-group">
<label>Domain Override Record</label>
<input type="checkbox" id="domain_override" name="domain_override"
class="checkbox">
</div>
{% endif %}
<select name="accountid" class="form-control" style="width:15em;"> <select name="accountid" class="form-control" style="width:15em;">
<option value="0">- No Account -</option> <option value="0">- No Account -</option>
{% for account in accounts %} {% for account in accounts %}
@ -178,3 +185,37 @@
}); });
</script> </script>
{% endblock %} {% endblock %}
{% block modals %}
<script>
{% if domain_override_message %}
$(document.body).ready(function () {
var modal = $("#modal_warning");
var info = "{{ domain_override_message }}";
modal.find('.modal-body p').text(info);
modal.modal('show');
});
{% endif %}
</script>
</div>
<div class="modal fade" id="modal_warning">
<div class="modal-dialog">
<div class="modal-content modal-sm">
<div class="modal-header alert-danger">
<button type="button" class="close" data-dismiss="modal" aria-label="Close" id="button_close_warn_modal">
<span aria-hidden="true">&times;</span>
</button>
<h4 class="modal-title">WARNING</h4>
</div>
<div class="modal-body">
<p></p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-flat btn-primary center-block" data-dismiss="modal" id="button_confirm_warn_modal">
CLOSE</button>
</div>
</div>
<!-- /.modal-content -->
</div>
<!-- /.modal-dialog -->
</div>
{% endblock %}