Adding Operator role

This commit is contained in:
Khanh Ngo 2018-08-31 11:57:06 +07:00
parent 5e6806cc0f
commit 3457d9214a
10 changed files with 228 additions and 133 deletions

View File

@ -6,6 +6,9 @@ from app.models import Role, Setting
def admin_role_required(f): def admin_role_required(f):
"""
Grant access if user is in Administrator role
"""
@wraps(f) @wraps(f)
def decorated_function(*args, **kwargs): def decorated_function(*args, **kwargs):
if g.user.role.name != 'Administrator': if g.user.role.name != 'Administrator':
@ -14,10 +17,28 @@ def admin_role_required(f):
return decorated_function return decorated_function
def can_access_domain(f): def operator_role_required(f):
"""
Grant access if user is in Operator role or higher
"""
@wraps(f) @wraps(f)
def decorated_function(*args, **kwargs): def decorated_function(*args, **kwargs):
if g.user.role.name != 'Administrator': if g.user.role.name not in ['Administrator', 'Operator']:
return redirect(url_for('error', code=401))
return f(*args, **kwargs)
return decorated_function
def can_access_domain(f):
"""
Grant access if:
- user is in Operator role or higher, or
- user is in granted Account, or
- user is in granted Domain
"""
@wraps(f)
def decorated_function(*args, **kwargs):
if g.user.role.name not in ['Administrator', 'Operator']:
domain_name = kwargs.get('domain_name') domain_name = kwargs.get('domain_name')
user_domain = [d.name for d in g.user.get_domain()] user_domain = [d.name for d in g.user.get_domain()]
@ -29,9 +50,14 @@ def can_access_domain(f):
def can_configure_dnssec(f): def can_configure_dnssec(f):
"""
Grant access if:
- user is in Operator role or higher, or
- dnssec_admins_only is off
"""
@wraps(f) @wraps(f)
def decorated_function(*args, **kwargs): def decorated_function(*args, **kwargs):
if g.user.role.name != 'Administrator' and Setting().get('dnssec_admins_only'): if g.user.role.name not in ['Administrator', 'Operator'] and Setting().get('dnssec_admins_only'):
return redirect(url_for('error', code=401)) return redirect(url_for('error', code=401))
return f(*args, **kwargs) return f(*args, **kwargs)

View File

@ -150,7 +150,7 @@ class User(db.Model):
logging.error(e) logging.error(e)
logging.debug('baseDN: {0}'.format(baseDN)) logging.debug('baseDN: {0}'.format(baseDN))
logging.debug(traceback.format_exc()) logging.debug(traceback.format_exc())
raise
def ldap_auth(self, ldap_username, password): def ldap_auth(self, ldap_username, password):
try: try:
@ -165,6 +165,8 @@ class User(db.Model):
""" """
Validate user credential Validate user credential
""" """
role_name = 'User'
if method == 'LOCAL': if method == 'LOCAL':
user_info = User.query.filter(User.username == self.username).first() user_info = User.query.filter(User.username == self.username).first()
@ -179,12 +181,12 @@ class User(db.Model):
return False return False
if method == 'LDAP': if method == 'LDAP':
isadmin = False
LDAP_TYPE = Setting().get('ldap_type') LDAP_TYPE = Setting().get('ldap_type')
LDAP_BASE_DN = Setting().get('ldap_base_dn') LDAP_BASE_DN = Setting().get('ldap_base_dn')
LDAP_FILTER_BASIC = Setting().get('ldap_filter_basic') LDAP_FILTER_BASIC = Setting().get('ldap_filter_basic')
LDAP_FILTER_USERNAME = Setting().get('ldap_filter_username') LDAP_FILTER_USERNAME = Setting().get('ldap_filter_username')
LDAP_ADMIN_GROUP = Setting().get('ldap_admin_group') LDAP_ADMIN_GROUP = Setting().get('ldap_admin_group')
LDAP_OPERATOR_GROUP = Setting().get('ldap_operator_group')
LDAP_USER_GROUP = Setting().get('ldap_user_group') LDAP_USER_GROUP = Setting().get('ldap_user_group')
LDAP_GROUP_SECURITY_ENABLED = Setting().get('ldap_sg_enabled') LDAP_GROUP_SECURITY_ENABLED = Setting().get('ldap_sg_enabled')
@ -206,24 +208,30 @@ class User(db.Model):
try: try:
if LDAP_TYPE == 'ldap': if LDAP_TYPE == 'ldap':
if (self.ldap_search(searchFilter, LDAP_ADMIN_GROUP)): if (self.ldap_search(searchFilter, LDAP_ADMIN_GROUP)):
isadmin = True role_name = 'Administrator'
logging.info('User {0} is part of the "{1}" group that allows admin access to PowerDNS-Admin'.format(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))
elif (self.ldap_search(searchFilter, LDAP_OPERATOR_GROUP)):
role_name = 'Operator'
logging.info('User {0} is part of the "{1}" group that allows operator access to PowerDNS-Admin'.format(self.username, LDAP_OPERATOR_GROUP))
elif (self.ldap_search(searchFilter, LDAP_USER_GROUP)): elif (self.ldap_search(searchFilter, 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)) logging.info('User {0} is part of the "{1}" group that allows user access to PowerDNS-Admin'.format(self.username, LDAP_USER_GROUP))
else: else:
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)) logging.error('User {0} is not part of the "{1}", "{2}" or "{3}" groups that allow access to PowerDNS-Admin'.format(self.username, LDAP_ADMIN_GROUP, LDAP_OPERATOR_GROUP, LDAP_USER_GROUP))
return False return False
elif LDAP_TYPE == 'ad': elif LDAP_TYPE == 'ad':
user_ldap_groups = [g.decode("utf-8") for g in ldap_result[0][0][1]['memberOf']] user_ldap_groups = [g.decode("utf-8") for g in ldap_result[0][0][1]['memberOf']]
logging.debug('user_ldap_groups: {0}'.format(user_ldap_groups)) logging.debug('user_ldap_groups: {0}'.format(user_ldap_groups))
if (LDAP_ADMIN_GROUP in user_ldap_groups): if (LDAP_ADMIN_GROUP in user_ldap_groups):
isadmin = True role_name = 'Administrator'
logging.info('User {0} is part of the "{1}" group that allows admin access to PowerDNS-Admin'.format(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))
elif (LDAP_OPERATOR_GROUP in user_ldap_groups):
role_name = 'Operator'
logging.info('User {0} is part of the "{1}" group that allows operator access to PowerDNS-Admin'.format(self.username, LDAP_OPERATOR_GROUP))
elif (LDAP_USER_GROUP in user_ldap_groups): elif (LDAP_USER_GROUP in user_ldap_groups):
logging.info('User {0} is part of the "{1}" group that allows user access to PowerDNS-Admin'.format(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))
else: else:
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)) logging.error('User {0} is not part of the "{1}", "{2}" or "{3}" groups that allow access to PowerDNS-Admin'.format(self.username, LDAP_ADMIN_GROUP, LDAP_OPERATOR_GROUP, LDAP_USER_GROUP))
return False return False
else: else:
logging.error('Invalid LDAP type') logging.error('Invalid LDAP type')
@ -261,21 +269,17 @@ class User(db.Model):
logging.debug(traceback.format_exc()) logging.debug(traceback.format_exc())
# first register user will be in Administrator role # first register user will be in Administrator role
self.role_id = Role.query.filter_by(name='User').first().id
if User.query.count() == 0: if User.query.count() == 0:
self.role_id = Role.query.filter_by(name='Administrator').first().id self.role_id = Role.query.filter_by(name='Administrator').first().id
else:
# user will be in Administrator role if part of LDAP Admin group self.role_id = Role.query.filter_by(name=role_name).first().id
if LDAP_GROUP_SECURITY_ENABLED:
if isadmin == True:
self.role_id = Role.query.filter_by(name='Administrator').first().id
self.create_user() self.create_user()
logging.info('Created user "{0}" in the DB'.format(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) # user already exists in database, set their role based on group membership (if enabled)
if LDAP_GROUP_SECURITY_ENABLED: if LDAP_GROUP_SECURITY_ENABLED:
self.set_admin(isadmin) self.set_role(role_name)
return True return True
else: else:
@ -453,28 +457,15 @@ class User(db.Model):
return False return False
return False return False
def set_admin(self, is_admin): def set_role(self, role_name):
""" role = Role.query.filter(Role.name==role_name).first()
Set role for a user:
is_admin == True => Administrator
is_admin == False => User
"""
user_role_name = 'Administrator' if is_admin else 'User'
role = Role.query.filter(Role.name==user_role_name).first()
try:
if role: if role:
user = User.query.filter(User.username==self.username).first() user = User.query.filter(User.username==self.username).first()
user.role_id = role.id user.role_id = role.id
db.session.commit() db.session.commit()
return True return {'status': True, 'msg': 'Set user role successfully'}
else: else:
return False return {'status': False, 'msg': 'Role does not exist'}
except:
db.session.roleback()
logging.error('Cannot change user role in DB')
logging.debug(traceback.format_exc())
return False
class Account(db.Model): class Account(db.Model):
@ -1825,8 +1816,9 @@ class Setting(db.Model):
'ldap_filter_basic': '', 'ldap_filter_basic': '',
'ldap_filter_username': '', 'ldap_filter_username': '',
'ldap_sg_enabled': False, 'ldap_sg_enabled': False,
'ldap_admin_group': False, 'ldap_admin_group': '',
'ldap_user_group': False, 'ldap_operator_group': '',
'ldap_user_group': '',
'github_oauth_enabled': False, 'github_oauth_enabled': False,
'github_oauth_key': '', 'github_oauth_key': '',
'github_oauth_secret': '', 'github_oauth_secret': '',

View File

@ -22,9 +22,14 @@ function applyChanges(data, url, showResult, refreshPage) {
}, },
error : function(jqXHR, status) { error : function(jqXHR, status) {
// console.log(jqXHR);
// var modal = $("#modal_error");
// modal.find('.modal-body p').text(jqXHR["responseText"]);
// modal.modal('show');
console.log(jqXHR); console.log(jqXHR);
var modal = $("#modal_error"); var modal = $("#modal_error");
modal.find('.modal-body p').text(jqXHR["responseText"]); var responseJson = jQuery.parseJSON(jqXHR.responseText);
modal.find('.modal-body p').text(responseJson['msg']);
modal.modal('show'); modal.modal('show');
} }
}); });

View File

@ -36,7 +36,7 @@
<th>First Name</th> <th>First Name</th>
<th>Last Name</th> <th>Last Name</th>
<th>Email</th> <th>Email</th>
<th>Admin</th> <th>Role</th>
<th>Privileges</th> <th>Privileges</th>
<th>Action</th> <th>Action</th>
</tr> </tr>
@ -49,18 +49,22 @@
<td>{{ user.lastname }}</td> <td>{{ user.lastname }}</td>
<td>{{ user.email }}</td> <td>{{ user.email }}</td>
<td> <td>
<input type="checkbox" id="{{ user.username }}" class="admin_toggle" {% if user.role.name=='Administrator' %}checked{% endif %} {% if user.username==current_user.username %}disabled{% endif %}> <select id="{{ user.username }}" class="user_role" {% if user.username==current_user.username or (current_user.role.name=='Operator' and user.role.name=='Administrator') %}disabled{% endif %}>
{% for role in roles %}
<option value="{{ role.name }}" {% if role.id==user.role.id %}selected{% endif %}>{{ role.name }}</option>
{% endfor %}
</select>
</td> </td>
<td width="6%"> <td width="6%">
<button type="button" class="btn btn-flat btn-warning button_revoke" id="{{ user.username }}"> <button type="button" class="btn btn-flat btn-warning button_revoke" id="{{ user.username }}" {% if current_user.role.name=='Operator' and user.role.name=='Administrator' %}disabled{% endif %}>
Revoke&nbsp;<i class="fa fa-lock"></i> Revoke&nbsp;<i class="fa fa-lock"></i>
</button> </button>
</td> </td>
<td width="15%"> <td width="15%">
<button type="button" class="btn btn-flat btn-success button_edit" onclick="window.location.href='{{ url_for('admin_edituser', user_username=user.username) }}'"> <button type="button" class="btn btn-flat btn-success button_edit" onclick="window.location.href='{{ url_for('admin_edituser', user_username=user.username) }}'" {% if current_user.role.name=='Operator' and user.role.name=='Administrator' %}disabled{% endif %}>
Edit&nbsp;<i class="fa fa-lock"></i> Edit&nbsp;<i class="fa fa-lock"></i>
</button> </button>
<button type="button" class="btn btn-flat btn-danger button_delete" id="{{ user.username }}" {% if user.username==current_user.username %}disabled{% endif %}> <button type="button" class="btn btn-flat btn-danger button_delete" id="{{ user.username }}" {% if user.username==current_user.username or (current_user.role.name=='Operator' and user.role.name=='Administrator') %}disabled{% endif %}>
Delete&nbsp;<i class="fa fa-trash"></i> Delete&nbsp;<i class="fa fa-trash"></i>
</button> </button>
</td> </td>
@ -93,14 +97,6 @@
"pageLength": 10 "pageLength": 10
}); });
// avoid losing icheck box style when database refreshed
$('#tbl_users').on('draw.dt', function () {
$('.admin_toggle').iCheck({
handle: 'checkbox',
checkboxClass: 'icheckbox_square-blue'
});
});
// handle revocation of privileges // handle revocation of privileges
$(document.body).on('click', '.button_revoke', function() { $(document.body).on('click', '.button_revoke', function() {
var modal = $("#modal_revoke"); var modal = $("#modal_revoke");
@ -129,24 +125,18 @@
}); });
// initialize pretty checkboxes // handle user role changing
$('.admin_toggle').iCheck({ $('.user_role').on('change', function() {
checkboxClass : 'icheckbox_square-blue', var role_name = this.value;
increaseArea : '20%' // optional
});
// handle checkbox toggling
$(document.body).on('ifToggled', '.admin_toggle', function() {
var is_admin = $(this).prop('checked');
var username = $(this).prop('id'); var username = $(this).prop('id');
postdata = { postdata = {
'action' : 'set_admin', 'action' : 'update_user_role',
'data' : { 'data' : {
'username' : username, 'username' : username,
'is_admin' : is_admin 'role_name' : role_name
} }
}; };
applyChanges(postdata, $SCRIPT_ROOT + '/admin/manageuser'); applyChanges(postdata, $SCRIPT_ROOT + '/admin/manageuser', showResult=true);
}); });
</script> </script>
{% endblock %} {% endblock %}

View File

@ -133,6 +133,11 @@
<input type="text" class="form-control" name="ldap_admin_group" id="ldap_admin_group" placeholder="e.g. cn=sysops,dc=mydomain,dc=com" data-error="Please input LDAP DN for Admin group" value="{{ SETTING.get('ldap_admin_group') }}"> <input type="text" class="form-control" name="ldap_admin_group" id="ldap_admin_group" placeholder="e.g. cn=sysops,dc=mydomain,dc=com" data-error="Please input LDAP DN for Admin group" value="{{ SETTING.get('ldap_admin_group') }}">
<span class="help-block with-errors"></span> <span class="help-block with-errors"></span>
</div> </div>
<div class="form-group">
<label for="ldap_operator_group">Operator group</label>
<input type="text" class="form-control" name="ldap_operator_group" id="ldap_operator_group" placeholder="e.g. cn=operators,dc=mydomain,dc=com" data-error="Please input LDAP DN for Operator group" value="{{ SETTING.get('ldap_operator_group') }}">
<span class="help-block with-errors"></span>
</div>
<div class="form-group"> <div class="form-group">
<label for="ldap_user_group">User group</label> <label for="ldap_user_group">User group</label>
<input type="text" class="form-control" name="ldap_user_group" id="ldap_user_group" placeholder="e.g. cn=users,dc=mydomain,dc=com" data-error="Please input LDAP DN for User group" value="{{ SETTING.get('ldap_user_group') }}"> <input type="text" class="form-control" name="ldap_user_group" id="ldap_user_group" placeholder="e.g. cn=users,dc=mydomain,dc=com" data-error="Please input LDAP DN for User group" value="{{ SETTING.get('ldap_user_group') }}">
@ -197,6 +202,9 @@
<li> <li>
Admin group - Your LDAP admin group. Admin group - Your LDAP admin group.
</li> </li>
<li>
Operator group - Your LDAP operator group.
</li>
<li> <li>
User group - Your LDAP user group. User group - Your LDAP user group.
</li> </li>

View File

@ -108,25 +108,25 @@
<li class="{{ 'active' if active_page == 'dashboard' else '' }}"> <li class="{{ 'active' if active_page == 'dashboard' else '' }}">
<a href="{{ url_for('dashboard') }}"><i class="fa fa-dashboard"></i> Dashboard</a> <a href="{{ url_for('dashboard') }}"><i class="fa fa-dashboard"></i> Dashboard</a>
</li> </li>
{% if current_user.role.name == 'Administrator' %} {% if current_user.role.name in ['Administrator', 'Operator'] %}
<li class="{{ 'active' if active_page == 'new_domain' else '' }}"> <li class="{{ 'active' if active_page == 'new_domain' else '' }}">
<a href="{{ url_for('domain_add') }}"><i class="fa fa-plus"></i> New Domain</a> <a href="{{ url_for('domain_add') }}"><i class="fa fa-plus"></i> New Domain</a>
</li> </li>
<li class="header">ADMINISTRATION</li> <li class="header">ADMINISTRATION</li>
<li class="{{ 'active' if active_page == 'admin_console' else '' }}"> <li class="{{ 'active' if active_page == 'admin_console' else '' }}">
<a href="{{ url_for('admin') }}"><i class="fa fa-wrench"></i> Admin Console</a> <a href="{{ url_for('admin_pdns') }}"><i class="fa fa-info-circle"></i> PDNS</a>
</li>
<li class="{{ 'active' if active_page == 'admin_history' else '' }}">
<a href="{{ url_for('admin_history') }}"><i class="fa fa-calendar"></i> History</a>
</li> </li>
<li class="{{ 'active' if active_page == 'admin_domain_template' else '' }}"> <li class="{{ 'active' if active_page == 'admin_domain_template' else '' }}">
<a href="{{ url_for('templates') }}"><i class="fa fa-clone"></i> Domain Templates</a> <a href="{{ url_for('templates') }}"><i class="fa fa-clone"></i> Domain Templates</a>
</li> </li>
<li class="{{ 'active' if active_page == 'admin_users' else '' }}">
<a href="{{ url_for('admin_manageuser') }}"><i class="fa fa-users"></i> Users</a>
</li>
<li class="{{ 'active' if active_page == 'admin_accounts' else '' }}"> <li class="{{ 'active' if active_page == 'admin_accounts' else '' }}">
<a href="{{ url_for('admin_manageaccount') }}"><i class="fa fa-industry"></i> Accounts</a> <a href="{{ url_for('admin_manageaccount') }}"><i class="fa fa-industry"></i> Accounts</a>
</li> </li>
<li class="{{ 'active' if active_page == 'admin_history' else '' }}"> <li class="{{ 'active' if active_page == 'admin_users' else '' }}">
<a href="{{ url_for('admin_history') }}"><i class="fa fa-calendar"></i> History</a> <a href="{{ url_for('admin_manageuser') }}"><i class="fa fa-users"></i> Users</a>
</li> </li>
<li class="{{ 'treeview active' if active_page == 'admin_settings' else 'treeview' }}"> <li class="{{ 'treeview active' if active_page == 'admin_settings' else 'treeview' }}">
<a href="#"> <a href="#">
@ -138,8 +138,10 @@
<ul class="treeview-menu" {% if active_page == 'admin_settings' %}style="display: block;"{% endif %}> <ul class="treeview-menu" {% if active_page == 'admin_settings' %}style="display: block;"{% endif %}>
<li><a href="{{ url_for('admin_setting_basic') }}"><i class="fa fa-circle-o"></i></i> Basic</a></li> <li><a href="{{ url_for('admin_setting_basic') }}"><i class="fa fa-circle-o"></i></i> Basic</a></li>
<li><a href="{{ url_for('admin_setting_records') }}"><i class="fa fa-circle-o"></i> Records</a></li> <li><a href="{{ url_for('admin_setting_records') }}"><i class="fa fa-circle-o"></i> Records</a></li>
{% if current_user.role.name == 'Administrator' %}
<li><a href="{{ url_for('admin_setting_pdns') }}"><i class="fa fa-circle-o"></i> PDNS</a></li> <li><a href="{{ url_for('admin_setting_pdns') }}"><i class="fa fa-circle-o"></i> PDNS</a></li>
<li><a href="{{ url_for('admin_setting_authentication') }}"><i class="fa fa-circle-o"></i> Authentication</a></li> <li><a href="{{ url_for('admin_setting_authentication') }}"><i class="fa fa-circle-o"></i> Authentication</a></li>
{% endif %}
</ul> </ul>
</li> </li>
{% endif %} {% endif %}

View File

@ -19,7 +19,7 @@
{% block content %} {% block content %}
<!-- Main content --> <!-- Main content -->
<section class="content"> <section class="content">
{% if current_user.role.name == 'Administrator' %} {% if current_user.role.name in ['Administrator', 'Operator'] %}
<div class="row"> <div class="row">
<div class="col-xs-3"> <div class="col-xs-3">
<div class="box"> <div class="box">
@ -69,7 +69,7 @@
</a> </a>
</div> </div>
<div class="col-lg-6"> <div class="col-lg-6">
<a href="{{ url_for('admin') }}"> <a href="{{ url_for('admin_pdns') }}">
<div class="small-box bg-green"> <div class="small-box bg-green">
<div class="inner"> <div class="inner">
<h3><span style="font-size: 18px">{{ uptime|display_second_to_time }}</span></h3> <h3><span style="font-size: 18px">{{ uptime|display_second_to_time }}</span></h3>
@ -136,7 +136,7 @@
<th>Serial</th> <th>Serial</th>
<th>Master</th> <th>Master</th>
<th>Account</th> <th>Account</th>
<th {% if current_user.role.name !='Administrator' %}width="6%"{% else %}width="25%"{% endif %}>Action</th> <th {% if current_user.role.name not in ['Administrator','Operator'] %}width="6%"{% else %}width="25%"{% endif %}>Action</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -182,7 +182,7 @@
"ordering" : true, "ordering" : true,
"columnDefs": [ "columnDefs": [
{ "orderable": false, "targets": [-1] } { "orderable": false, "targets": [-1] }
{% if current_user.role.name != 'Administrator' %},{ "visible": false, "targets": [-2] }{% endif %} {% if current_user.role.name not in ['Administrator', 'Operator'] %},{ "visible": false, "targets": [-2] }{% endif %}
], ],
"processing" : true, "processing" : true,
"serverSide" : true, "serverSide" : true,
@ -236,7 +236,7 @@
modal.modal('show'); modal.modal('show');
}); });
{% if current_user.role.name == 'Administrator' or not SETTING.get('dnssec_admins_only') %} {% if current_user.role.name in ['Administrator', 'Operator'] or not SETTING.get('dnssec_admins_only') %}
$(document.body).on("click", ".button_dnssec", function() { $(document.body).on("click", ".button_dnssec", function() {
var domain = $(this).prop('id'); var domain = $(this).prop('id');
getdnssec($SCRIPT_ROOT + '/domain/' + domain + '/dnssec', domain); getdnssec($SCRIPT_ROOT + '/domain/' + domain + '/dnssec', domain);

View File

@ -23,13 +23,13 @@
{% endmacro %} {% endmacro %}
{% macro account(domain) %} {% macro account(domain) %}
{% if current_user.role.name =='Administrator' %} {% if current_user.role.name in ['Administrator', 'Operator'] %}
{% if domain.account_description != "" %}{{ domain.account.description }} {% endif %}[{{ domain.account.name }}] {% if domain.account_description != "" %}{{ domain.account.description }} {% endif %}[{{ domain.account.name }}]
{% endif %} {% endif %}
{% endmacro %} {% endmacro %}
{% macro actions(domain) %} {% macro actions(domain) %}
{% if current_user.role.name =='Administrator' %} {% if current_user.role.name in ['Administrator', 'Operator'] %}
<td width="25%"> <td width="25%">
<button type="button" class="btn btn-flat btn-success button_template" id="{{ domain.name }}"> <button type="button" class="btn btn-flat btn-success button_template" id="{{ domain.name }}">
Template&nbsp;<i class="fa fa-clone"></i> Template&nbsp;<i class="fa fa-clone"></i>

View File

@ -22,7 +22,7 @@ from .models import User, Account, Domain, Record, Role, Server, History, Anonym
from app import app, login_manager from app import app, login_manager
from app.lib import utils from app.lib import utils
from app.oauth import github_oauth, google_oauth from app.oauth import github_oauth, google_oauth
from app.decorators import admin_role_required, can_access_domain, can_configure_dnssec from app.decorators import admin_role_required, operator_role_required, can_access_domain, can_configure_dnssec
if app.config['SAML_ENABLED']: if app.config['SAML_ENABLED']:
from onelogin.saml2.auth import OneLogin_Saml2_Auth from onelogin.saml2.auth import OneLogin_Saml2_Auth
@ -68,7 +68,7 @@ def before_request():
# check site maintenance mode # check site maintenance mode
maintenance = Setting().get('maintenance') maintenance = Setting().get('maintenance')
if maintenance and current_user.is_authenticated and current_user.role.name != 'Administrator': if maintenance and current_user.is_authenticated and current_user.role.name not in ['Administrator', 'Operator']:
return render_template('maintenance.html') return render_template('maintenance.html')
@ -476,7 +476,7 @@ def dashboard():
@app.route('/dashboard-domains', methods=['GET']) @app.route('/dashboard-domains', methods=['GET'])
@login_required @login_required
def dashboard_domains(): def dashboard_domains():
if current_user.role.name == 'Administrator': if current_user.role.name in ['Administrator', 'Operator']:
domains = Domain.query domains = Domain.query
else: else:
domains = User(id=current_user.id).get_domain_query() domains = User(id=current_user.id).get_domain_query()
@ -508,7 +508,7 @@ def dashboard_domains():
start = "" if search.startswith("^") else "%" start = "" if search.startswith("^") else "%"
end = "" if search.endswith("$") else "%" end = "" if search.endswith("$") else "%"
if current_user.role.name == 'Administrator': if current_user.role.name in ['Administrator', 'Operator']:
domains = domains.outerjoin(Account).filter(Domain.name.ilike(start + search.strip("^$") + end) | domains = domains.outerjoin(Account).filter(Domain.name.ilike(start + search.strip("^$") + end) |
Account.name.ilike(start + search.strip("^$") + end) | Account.name.ilike(start + search.strip("^$") + end) |
Account.description.ilike(start + search.strip("^$") + end)) Account.description.ilike(start + search.strip("^$") + end))
@ -603,7 +603,7 @@ def domain(domain_name):
@app.route('/admin/domain/add', methods=['GET', 'POST']) @app.route('/admin/domain/add', methods=['GET', 'POST'])
@login_required @login_required
@admin_role_required @operator_role_required
def domain_add(): def domain_add():
templates = DomainTemplate.query.all() templates = DomainTemplate.query.all()
if request.method == 'POST': if request.method == 'POST':
@ -661,7 +661,7 @@ def domain_add():
@app.route('/admin/domain/<path:domain_name>/delete', methods=['GET']) @app.route('/admin/domain/<path:domain_name>/delete', methods=['GET'])
@login_required @login_required
@admin_role_required @operator_role_required
def domain_delete(domain_name): def domain_delete(domain_name):
d = Domain() d = Domain()
result = d.delete(domain_name) result = d.delete(domain_name)
@ -677,7 +677,7 @@ def domain_delete(domain_name):
@app.route('/admin/domain/<path:domain_name>/manage', methods=['GET', 'POST']) @app.route('/admin/domain/<path:domain_name>/manage', methods=['GET', 'POST'])
@login_required @login_required
@admin_role_required @operator_role_required
def domain_management(domain_name): def domain_management(domain_name):
if request.method == 'GET': if request.method == 'GET':
domain = Domain.query.filter(Domain.name == domain_name).first() domain = Domain.query.filter(Domain.name == domain_name).first()
@ -712,7 +712,7 @@ def domain_management(domain_name):
@app.route('/admin/domain/<path:domain_name>/change_soa_setting', methods=['POST']) @app.route('/admin/domain/<path:domain_name>/change_soa_setting', methods=['POST'])
@login_required @login_required
@admin_role_required @operator_role_required
def domain_change_soa_edit_api(domain_name): def domain_change_soa_edit_api(domain_name):
domain = Domain.query.filter(Domain.name == domain_name).first() domain = Domain.query.filter(Domain.name == domain_name).first()
if not domain: if not domain:
@ -738,7 +738,7 @@ def domain_change_soa_edit_api(domain_name):
@app.route('/admin/domain/<path:domain_name>/change_account', methods=['POST']) @app.route('/admin/domain/<path:domain_name>/change_account', methods=['POST'])
@login_required @login_required
@admin_role_required @operator_role_required
def domain_change_account(domain_name): def domain_change_account(domain_name):
domain = Domain.query.filter(Domain.name == domain_name).first() domain = Domain.query.filter(Domain.name == domain_name).first()
if not domain: if not domain:
@ -816,7 +816,7 @@ def record_update(domain_name):
@app.route('/domain/<path:domain_name>/record/<path:record_name>/type/<path:record_type>/delete', methods=['GET']) @app.route('/domain/<path:domain_name>/record/<path:record_name>/type/<path:record_type>/delete', methods=['GET'])
@login_required @login_required
@admin_role_required @operator_role_required
def record_delete(domain_name, record_name, record_type): def record_delete(domain_name, record_name, record_type):
try: try:
r = Record(name=record_name, type=record_type) r = Record(name=record_name, type=record_type)
@ -873,7 +873,7 @@ def domain_dnssec_disable(domain_name):
@app.route('/domain/<path:domain_name>/managesetting', methods=['GET', 'POST']) @app.route('/domain/<path:domain_name>/managesetting', methods=['GET', 'POST'])
@login_required @login_required
@admin_role_required @operator_role_required
def admin_setdomainsetting(domain_name): def admin_setdomainsetting(domain_name):
if request.method == 'POST': if request.method == 'POST':
# #
@ -914,7 +914,7 @@ def admin_setdomainsetting(domain_name):
@app.route('/templates', methods=['GET', 'POST']) @app.route('/templates', methods=['GET', 'POST'])
@app.route('/templates/list', methods=['GET', 'POST']) @app.route('/templates/list', methods=['GET', 'POST'])
@login_required @login_required
@admin_role_required @operator_role_required
def templates(): def templates():
templates = DomainTemplate.query.all() templates = DomainTemplate.query.all()
return render_template('template.html', templates=templates) return render_template('template.html', templates=templates)
@ -922,7 +922,7 @@ def templates():
@app.route('/template/create', methods=['GET', 'POST']) @app.route('/template/create', methods=['GET', 'POST'])
@login_required @login_required
@admin_role_required @operator_role_required
def create_template(): def create_template():
if request.method == 'GET': if request.method == 'GET':
return render_template('template_add.html') return render_template('template_add.html')
@ -955,7 +955,7 @@ def create_template():
@app.route('/template/createfromzone', methods=['POST']) @app.route('/template/createfromzone', methods=['POST'])
@login_required @login_required
@admin_role_required @operator_role_required
def create_template_from_zone(): def create_template_from_zone():
try: try:
jdata = request.json jdata = request.json
@ -1015,7 +1015,7 @@ def create_template_from_zone():
@app.route('/template/<path:template>/edit', methods=['GET']) @app.route('/template/<path:template>/edit', methods=['GET'])
@login_required @login_required
@admin_role_required @operator_role_required
def edit_template(template): def edit_template(template):
try: try:
t = DomainTemplate.query.filter(DomainTemplate.name == template).first() t = DomainTemplate.query.filter(DomainTemplate.name == template).first()
@ -1066,7 +1066,7 @@ def apply_records(template):
@app.route('/template/<path:template>/delete', methods=['GET']) @app.route('/template/<path:template>/delete', methods=['GET'])
@login_required @login_required
@admin_role_required @operator_role_required
def delete_template(template): def delete_template(template):
try: try:
t = DomainTemplate.query.filter(DomainTemplate.name == template).first() t = DomainTemplate.query.filter(DomainTemplate.name == template).first()
@ -1085,10 +1085,10 @@ def delete_template(template):
return redirect(url_for('templates')) return redirect(url_for('templates'))
@app.route('/admin', methods=['GET', 'POST']) @app.route('/admin/pdns', methods=['GET'])
@login_required @login_required
@admin_role_required @operator_role_required
def admin(): def admin_pdns():
if not Setting().get('pdns_api_url') or not Setting().get('pdns_api_key') or not Setting().get('pdns_version'): if not Setting().get('pdns_api_url') or not Setting().get('pdns_api_key') or not Setting().get('pdns_version'):
return redirect(url_for('admin_setting_pdns')) return redirect(url_for('admin_setting_pdns'))
@ -1111,7 +1111,7 @@ def admin():
@app.route('/admin/user/edit/<user_username>', methods=['GET', 'POST']) @app.route('/admin/user/edit/<user_username>', methods=['GET', 'POST'])
@app.route('/admin/user/edit', methods=['GET', 'POST']) @app.route('/admin/user/edit', methods=['GET', 'POST'])
@login_required @login_required
@admin_role_required @operator_role_required
def admin_edituser(user_username=None): def admin_edituser(user_username=None):
if request.method == 'GET': if request.method == 'GET':
if not user_username: if not user_username:
@ -1150,11 +1150,12 @@ def admin_edituser(user_username=None):
@app.route('/admin/manageuser', methods=['GET', 'POST']) @app.route('/admin/manageuser', methods=['GET', 'POST'])
@login_required @login_required
@admin_role_required @operator_role_required
def admin_manageuser(): def admin_manageuser():
if request.method == 'GET': if request.method == 'GET':
roles = Role.query.all()
users = User.query.order_by(User.username).all() users = User.query.order_by(User.username).all()
return render_template('admin_manageuser.html', users=users) return render_template('admin_manageuser.html', users=users, roles=roles)
if request.method == 'POST': if request.method == 'POST':
# #
@ -1197,19 +1198,31 @@ def admin_manageuser():
else: else:
return make_response(jsonify( { 'status': 'error', 'msg': 'Cannot revoke user privilege.' } ), 500) return make_response(jsonify( { 'status': 'error', 'msg': 'Cannot revoke user privilege.' } ), 500)
elif jdata['action'] == 'set_admin': elif jdata['action'] == 'update_user_role':
username = data['username'] username = data['username']
role_name = data['role_name']
if username == current_user.username: if username == current_user.username:
return make_response(jsonify( { 'status': 'error', 'msg': 'You cannot change you own admin rights.' } ), 400) return make_response(jsonify( { 'status': 'error', 'msg': 'You cannot change you own roles.' } ), 400)
is_admin = data['is_admin']
user = User.query.filter(User.username==username).first()
if not user:
return make_response(jsonify( { 'status': 'error', 'msg': 'User does not exist.' } ), 404)
if user.role.name == 'Administrator' and current_user.role.name != 'Administrator':
return make_response(jsonify( { 'status': 'error', 'msg': 'You do not have permission to change Administrator users role.' } ), 400)
if role_name == 'Administrator' and current_user.role.name != 'Administrator':
return make_response(jsonify( { 'status': 'error', 'msg': 'You do not have permission to promote a user to Administrator role.' } ), 400)
user = User(username=username) user = User(username=username)
result = user.set_admin(is_admin) result = user.set_role(role_name)
if result: if result['status']:
history = History(msg='Change user role of {0}'.format(username), created_by=current_user.username) history = History(msg='Change user role of {0} to {1}'.format(username, role_name), created_by=current_user.username)
history.add() history.add()
return make_response(jsonify( { 'status': 'ok', 'msg': 'Changed user role successfully.' } ), 200) return make_response(jsonify( { 'status': 'ok', 'msg': 'Changed user role successfully.' } ), 200)
else: else:
return make_response(jsonify( { 'status': 'error', 'msg': 'Cannot change user role.' } ), 500) return make_response(jsonify( { 'status': 'error', 'msg': 'Cannot change user role. {0}'.format(result['msg']) } ), 500)
else: else:
return make_response(jsonify( { 'status': 'error', 'msg': 'Action not supported.' } ), 400) return make_response(jsonify( { 'status': 'error', 'msg': 'Action not supported.' } ), 400)
except: except:
@ -1220,7 +1233,7 @@ def admin_manageuser():
@app.route('/admin/account/edit/<account_name>', methods=['GET', 'POST']) @app.route('/admin/account/edit/<account_name>', methods=['GET', 'POST'])
@app.route('/admin/account/edit', methods=['GET', 'POST']) @app.route('/admin/account/edit', methods=['GET', 'POST'])
@login_required @login_required
@admin_role_required @operator_role_required
def admin_editaccount(account_name=None): def admin_editaccount(account_name=None):
users = User.query.all() users = User.query.all()
@ -1274,7 +1287,7 @@ def admin_editaccount(account_name=None):
@app.route('/admin/manageaccount', methods=['GET', 'POST']) @app.route('/admin/manageaccount', methods=['GET', 'POST'])
@login_required @login_required
@admin_role_required @operator_role_required
def admin_manageaccount(): def admin_manageaccount():
if request.method == 'GET': if request.method == 'GET':
accounts = Account.query.order_by(Account.name).all() accounts = Account.query.order_by(Account.name).all()
@ -1308,7 +1321,7 @@ def admin_manageaccount():
@app.route('/admin/history', methods=['GET', 'POST']) @app.route('/admin/history', methods=['GET', 'POST'])
@login_required @login_required
@admin_role_required @operator_role_required
def admin_history(): def admin_history():
if request.method == 'POST': if request.method == 'POST':
h = History() h = History()
@ -1328,7 +1341,7 @@ def admin_history():
@app.route('/admin/setting/basic', methods=['GET']) @app.route('/admin/setting/basic', methods=['GET'])
@login_required @login_required
@admin_role_required @operator_role_required
def admin_setting_basic(): def admin_setting_basic():
if request.method == 'GET': if request.method == 'GET':
settings = Setting.query.filter(Setting.view=='basic').all() settings = Setting.query.filter(Setting.view=='basic').all()
@ -1337,7 +1350,7 @@ def admin_setting_basic():
@app.route('/admin/setting/basic/<path:setting>/edit', methods=['POST']) @app.route('/admin/setting/basic/<path:setting>/edit', methods=['POST'])
@login_required @login_required
@admin_role_required @operator_role_required
def admin_setting_basic_edit(setting): def admin_setting_basic_edit(setting):
jdata = request.json jdata = request.json
new_value = jdata['value'] new_value = jdata['value']
@ -1351,7 +1364,7 @@ def admin_setting_basic_edit(setting):
@app.route('/admin/setting/basic/<path:setting>/toggle', methods=['POST']) @app.route('/admin/setting/basic/<path:setting>/toggle', methods=['POST'])
@login_required @login_required
@admin_role_required @operator_role_required
def admin_setting_basic_toggle(setting): def admin_setting_basic_toggle(setting):
result = Setting().toggle(setting) result = Setting().toggle(setting)
if (result): if (result):
@ -1383,7 +1396,7 @@ def admin_setting_pdns():
@app.route('/admin/setting/dns-records', methods=['GET', 'POST']) @app.route('/admin/setting/dns-records', methods=['GET', 'POST'])
@login_required @login_required
@admin_role_required @operator_role_required
def admin_setting_records(): def admin_setting_records():
if request.method == 'GET': if request.method == 'GET':
f_records = literal_eval(Setting().get('forward_records_allow_edit')) f_records = literal_eval(Setting().get('forward_records_allow_edit'))
@ -1438,6 +1451,7 @@ def admin_setting_authentication():
Setting().set('ldap_filter_username', request.form.get('ldap_filter_username')) Setting().set('ldap_filter_username', request.form.get('ldap_filter_username'))
Setting().set('ldap_sg_enabled', True if request.form.get('ldap_sg_enabled')=='ON' else False) Setting().set('ldap_sg_enabled', True if request.form.get('ldap_sg_enabled')=='ON' else False)
Setting().set('ldap_admin_group', request.form.get('ldap_admin_group')) Setting().set('ldap_admin_group', request.form.get('ldap_admin_group'))
Setting().set('ldap_operator_group', request.form.get('ldap_operator_group'))
Setting().set('ldap_user_group', request.form.get('ldap_user_group')) Setting().set('ldap_user_group', request.form.get('ldap_user_group'))
result = {'status': True, 'msg': 'Saved successfully'} result = {'status': True, 'msg': 'Saved successfully'}
elif conf_type == 'google': elif conf_type == 'google':

View File

@ -0,0 +1,58 @@
"""Adding Operator Role
Revision ID: 4a666113c7bb
Revises: 1274ed462010
Create Date: 2018-08-30 13:28:06.836208
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '4a666113c7bb'
down_revision = '1274ed462010'
branch_labels = None
depends_on = None
def update_data():
setting_table = sa.sql.table('setting',
sa.sql.column('id', sa.Integer),
sa.sql.column('name', sa.String),
sa.sql.column('value', sa.String),
sa.sql.column('view', sa.String)
)
# add ldap_operator_group setting
op.bulk_insert(setting_table,
[
{'id': 44, 'name': 'ldap_operator_group', 'value': '', 'view': 'authentication'},
]
)
role_table = sa.sql.table('role',
sa.sql.column('id', sa.Integer),
sa.sql.column('name', sa.String),
sa.sql.column('description', sa.String)
)
# add new role
op.bulk_insert(role_table,
[
{'id': 3, 'name': 'Operator', 'description': 'Operator'}
]
)
def upgrade():
update_data()
def downgrade():
# remove user Operator role
op.execute("UPDATE user SET role_id = 2 WHERE role_id=3")
op.execute("DELETE FROM role WHERE name = 'Operator'")
# delete ldap setting
op.execute("DELETE FROM setting WHERE name = 'ldap_operator_group'")