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 .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

View File

@ -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

View File

@ -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

View File

@ -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):

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',
'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)

View File

@ -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)

View File

@ -34,6 +34,13 @@
<input type="text" class="form-control" name="domain_name" id="domain_name"
placeholder="Enter a valid domain name (required)">
</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;">
<option value="0">- No Account -</option>
{% for account in accounts %}
@ -178,3 +185,37 @@
});
</script>
{% 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 %}