Add option to edit users from the comfort of the UI

Update user management feature to allow editing user details directly in the admin user interface.

Also added an option to reset the two factor authentication data of a user, for when that's needed (lost device, technical issues etc).

(cherry picked from commit 3139616282a18c11463c6ecf78888417b2ac1c35)
This commit is contained in:
Thomas M Steenholdt 2018-08-12 07:40:32 -02:00
parent 85e745731b
commit 0ac33aa3c4
4 changed files with 143 additions and 22 deletions

View file

@ -328,6 +328,36 @@ class User(db.Model):
db.session.commit()
return {'status': True, 'msg': 'Created user successfully'}
def update_local_user(self):
"""
Update local user
"""
# Sanity check - account name
if self.username == "":
return {'status': False, 'msg': 'No user name specified'}
# read user and check that it exists
user = User.query.filter(User.username == self.username).first()
if not user:
return {'status': False, 'msg': 'User does not exist'}
# check if new email exists (only if changed)
if user.email != self.email:
checkuser = User.query.filter(User.email == self.email).first()
if checkuser:
return {'status': False, 'msg': 'New email address is already in use'}
user.firstname = self.firstname
user.lastname = self.lastname
user.email = self.email
# store new password hash (only if changed)
if self.plain_text_password != "":
user.password = self.get_hashed_password(self.plain_text_password).decode("utf-8")
db.session.commit()
return {'status': True, 'msg': 'User updated successfully'}
def update_profile(self, enable_otp=None):
"""
Update user profile

View file

@ -1,17 +1,17 @@
{% extends "base.html" %}
{% block title %}<title>DNS Control Panel - Create User</title>{% endblock %}
{% block title %}<title>DNS Control Panel - Edit User</title>{% endblock %}
{% block dashboard_stat %}
<!-- Content Header (Page header) -->
<section class="content-header">
<h1>
User
<small>Create new</small>
<small>{% if create %}New user{% else %}{{ user.username }}{% endif %}</small>
</h1>
<ol class="breadcrumb">
<li><a href="{{ url_for('dashboard') }}"><i class="fa fa-dashboard"></i>Home</a></li>
<li><a href="{{ url_for('dashboard') }}">Admin</a></li>
<li class="active">Create user</li>
<li class="active">{% if create %}Add{% else %}Edit{% endif %} user</li>
</ol>
</section>
{% endblock %}
@ -22,11 +22,12 @@
<div class="col-md-4">
<div class="box box-primary">
<div class="box-header with-border">
<h3 class="box-title">Create new user</h3>
<h3 class="box-title">{% if create %}Add{% else %}Edit{% endif %} user</h3>
</div>
<!-- /.box-header -->
<!-- form start -->
<form role="form" method="post" action="{{ url_for('admin_createuser') }}">
<form role="form" method="post" action="{% if create %}{{ url_for('admin_edituser') }}{% else %}{{ url_for('admin_edituser', user_username=user.username) }}{% endif %}">
<input type="hidden" name="create" value="{{ create }}">
<div class="box-body">
{% if error %}
<div class="alert alert-danger alert-dismissible">
@ -58,12 +59,12 @@
<div class="form-group has-feedback">
<label class="control-label" for="username">Username</label>
<input type="text" class="form-control" placeholder="Username"
name="username" {% if user %}value={{ user.username }}{% endif %}> <span
name="username" {% if user %}value={{ user.username }}{% endif %} {% if not create %}disabled{% endif %}> <span
class="glyphicon glyphicon-user form-control-feedback"></span>
</div>
<div class="form-group has-feedback {% if blank_password %}has-error{% endif %}">
<label class="control-label" for="username">Password</label>
<input type="password" class="form-control" placeholder="Password"
<input type="password" class="form-control" placeholder="Password {% if create %}(Required){% else %}(Leave blank to keep unchanged){% endif %}"
name="password"> <span
class="glyphicon glyphicon-lock form-control-feedback"></span>
{% if blank_password %}
@ -72,22 +73,81 @@
</div>
</div>
<div class="box-footer">
<button type="submit" class="btn btn-flat btn-primary">Create User</button>
<button type="submit" class="btn btn-flat btn-primary">{% if create %}Create{% else %}Update{% endif %} User</button>
</div>
</form>
</div>
{% if not create %}
<div class="box box-secondary">
<div class="box-header with-border">
<h3 class="box-title">Two Factor Authentication</h3>
</div>
<div class="box-body">
<p>If two factor authentication was configured and is causing problems due to a lost device or technical issue, it can be disabled here.</p>
<p>The user will need to reconfigure two factor authentication, to re-enable it.</p>
<p><strong>Beware: This could compromise security!</strong></p>
</div>
<div class="box-footer">
<button type="button" class="btn btn-flat btn-warning button_otp_disable" id="{{ user.username }}" {% if user.otp_secret == "" %}disabled{% endif %}>Disable Two Factor Authentication</button>
</div>
</div>
{% endif %}
</div>
<div class="col-md-8">
<div class="box box-primary">
<div class="box-header with-border">
<h3 class="box-title">Help with creating a new user</h3>
<h3 class="box-title">Help with {% if create %}creating a new{% else%}updating a{% endif %} user</h3>
</div>
<div class="box-body">
<p>Fill in all the fields to the in the form to the left.</p>
{% if create %}
<p><strong>Newly created users do not have access to any domains.</strong> You will need to grant access to the user once it is created via the domain management buttons on the dashboard.</p>
{% else %}
<p><strong>Password</strong> can be left empty to keep the current password.</p>
<p><strong>Username</strong> cannot be changed.</p>
{% endif %}
</div>
</div>
</div>
</div>
</section>
{% endblock %}
{% endblock %}
{% block extrascripts %}
<script>
// handle disabling two factor authentication
$(document.body).on('click', '.button_otp_disable', function() {
var modal = $("#modal_otp_disable");
var username = $(this).prop('id');
var info = "Are you sure you want to disable two factor authentication for user " + username + "?";
modal.find('.modal-body p').text(info);
modal.find('#button_otp_disable_confirm').click(function() {
var postdata = {'action': 'user_otp_disable', 'data': username}
applyChanges(postdata, $SCRIPT_ROOT + '/admin/manageuser', false, true);
})
modal.modal('show');
});
</script>
{% endblock %}
{% block modals %}
<div class="modal fade modal-warning" id="modal_otp_disable">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal"
aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
<h4 class="modal-title">Confirmation</h4>
</div>
<div class="modal-body">
<p></p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-flat btn-default pull-left"
data-dismiss="modal">Close</button>
<button type="button" class="btn btn-flat btn-danger" id="button_otp_disable_confirm">Disable Two Factor Authentication</button>
</div>
</div>
</div>
</div>
{% endblock %}

View file

@ -20,7 +20,7 @@
<h3 class="box-title">User Management</h3>
</div>
<div class="box-body">
<a href="{{ url_for('admin_createuser') }}">
<a href="{{ url_for('admin_edituser') }}">
<button type="button" class="btn btn-flat btn-primary pull-left button_add_user">
Add User&nbsp;<i class="fa fa-plus"></i>
</button>
@ -36,7 +36,7 @@
<th>Email</th>
<th>Admin</th>
<th>Privileges</th>
<th>Deletion</th>
<th>Action</th>
</tr>
</thead>
<tbody>
@ -54,7 +54,10 @@
Revoke&nbsp;<i class="fa fa-lock"></i>
</button>
</td>
<td width="6%">
<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) }}'">
Edit&nbsp;<i class="fa fa-lock"></i>
</button>
<button type="button" class="btn btn-flat btn-danger button_delete" id="{{ user.username }}" {% if user.username==current_user.username %}disabled{% endif %}>
Delete&nbsp;<i class="fa fa-trash"></i>
</button>

View file

@ -1156,26 +1156,44 @@ def admin():
return render_template('admin.html', domains=domains, users=users, configs=configs, statistics=statistics, uptime=uptime, history_number=history_number)
@app.route('/admin/user/create', methods=['GET', 'POST'])
@app.route('/admin/user/edit/<user_username>', methods=['GET', 'POST'])
@app.route('/admin/user/edit', methods=['GET', 'POST'])
@login_required
@admin_role_required
def admin_createuser():
def admin_edituser(user_username=None):
if request.method == 'GET':
return render_template('admin_createuser.html')
if not user_username:
return render_template('admin_edituser.html', create=1)
if request.method == 'POST':
else:
user = User.query.filter(User.username == user_username).first()
return render_template('admin_edituser.html', user=user, create=0)
elif request.method == 'POST':
fdata = request.form
user = User(username=fdata['username'], plain_text_password=fdata['password'], firstname=fdata['firstname'], lastname=fdata['lastname'], email=fdata['email'])
if not user_username:
user_username = fdata['username']
if fdata['password'] == "":
return render_template('admin_createuser.html', user=user, blank_password=True)
user = User(username=user_username, plain_text_password=fdata['password'], firstname=fdata['firstname'], lastname=fdata['lastname'], email=fdata['email'], reload_info=False)
create = int(fdata['create'])
if create:
if fdata['password'] == "":
return render_template('admin_edituser.html', user=user, create=create, blank_password=True)
result = user.create_local_user()
history = History(msg='Created user {0}'.format(user.username), created_by=current_user.username)
else:
result = user.update_local_user()
history = History(msg='Updated user {0}'.format(user.username), created_by=current_user.username)
result = user.create_local_user();
if result['status']:
history.add()
return redirect(url_for('admin_manageuser'))
return render_template('admin_createuser.html', user=user, error=result['msg'])
return render_template('admin_edituser.html', user=user, create=create, error=result['msg'])
@app.route('/admin/manageuser', methods=['GET', 'POST'])
@ -1195,6 +1213,16 @@ def admin_manageuser():
jdata = request.json
data = jdata['data']
if jdata['action'] == 'user_otp_disable':
user = User(username=data)
result = user.update_profile(enable_otp=False)
if result:
history = History(msg='Two factor authentication disabled for user {0}'.format(data), created_by=current_user.username)
history.add()
return make_response(jsonify( { 'status': 'ok', 'msg': 'Two factor authentication has been disabled for user.' } ), 200)
else:
return make_response(jsonify( { 'status': 'error', 'msg': 'Cannot disable two factor authentication for user.' } ), 500)
if jdata['action'] == 'delete_user':
user = User(username=data)
if user.username == current_user.username: