Compare commits

...

6 Commits

5 changed files with 165 additions and 5 deletions

View File

@ -2,9 +2,11 @@ import functools
import ldap
import re
import flask
import flask_wtf
import wtforms
import webapp
from webapp import app, context, config, ldaputils
from webapp import app, context, config, ldaputils, email
bp = flask.Blueprint('admin', __name__)
@ -92,6 +94,9 @@ def _get_groups_of(conn, uid):
return groups
def _is_user_protected(conn, uid, groups):
return any(group in config.ldap_protected_groups for group in groups)
@bp.route('/admin/users/<uid>')
@admin_required
def admin_user_view(uid):
@ -100,11 +105,98 @@ def admin_user_view(uid):
profile = _get_profile(conn, uid)
groups = _get_groups_of(conn, uid)
is_protected = any(group in config.ldap_protected_groups for group in groups)
is_protected = _is_user_protected(conn, uid, groups)
return flask.render_template('admin/user.html', uid=uid, profile=_format_profile(profile), groups=groups, is_protected=is_protected)
# TODO: Deduplicate this modification logic with webapp/vcard.py
class AddMifareIDHash(flask_wtf.FlaskForm):
value = wtforms.fields.StringField(label=config.readable_names.get('mifareidhash'))
class DelForm(flask_wtf.FlaskForm):
pass
@bp.route('/admin/users/<uid>/add_mifareidhash')
@admin_required
def admin_user_view_add_mifareidhash(uid):
form = AddMifareIDHash()
return flask.render_template('admin/ops/add_mifareidhash.html', uid=uid, form=form)
@bp.route('/admin/users/<uid>/del_mifareidhash')
@admin_required
def admin_user_view_del_mifareidhash(uid):
form = DelForm()
value = flask.request.args.get('value')
return flask.render_template('admin/ops/del_mifareidhash.html', uid=uid, form=form, value=value)
def _modify_mifareidhash(uid, form, modify_func):
ldaputils.validate_name(uid)
conn = context.get_connection()
groups = _get_groups_of(conn, uid)
is_protected = _is_user_protected(conn, uid, groups)
redirect_url = flask.url_for('admin.admin_user_view', uid=uid)
if is_protected:
flask.flash('Cannot modify protected user', 'danger')
return flask.redirect(redirect_url)
try:
if form.validate_on_submit():
dn = ldaputils.user_dn(uid)
modify_func(conn, dn)
context.refresh_profile(dn)
flask.flash('Added mifareidhash', category='info')
return flask.redirect(redirect_url)
for field, errors in form.errors.items():
for error in errors:
flask.flash("Error in the {} field - {}".format(
getattr(form, field).label.text,
error
), 'danger')
return flask.redirect(redirect_url)
except ldap.LDAPError as e:
print('LDAP error:', e)
flask.flash(f'Could not modify profile due to LDAP error: {e}', 'danger')
return flask.redirect(redirect_url)
@bp.route('/admin/users/<uid>/add_mifareidhash', methods=['POST'])
@admin_required
def admin_user_add_mifareidhash(uid):
form = AddMifareIDHash()
def modify_func(conn, dn):
new_value = form.value.data
email.send_papertrail(
f'Adding mifareIDHash for user {uid}',
f'New mifareIDHash: {new_value}'
)
conn.modify_s(dn, [(ldap.MOD_ADD, 'mifareidhash', new_value.encode('utf-8'))])
return _modify_mifareidhash(uid, form, modify_func)
@bp.route('/admin/users/<uid>/del_mifareidhash', methods=['POST'])
@admin_required
def admin_user_del_mifareidhash(uid):
form = DelForm()
def modify_func(conn, dn):
old_value = flask.request.args.get('value')
email.send_papertrail(
f'Deleting mifareIDHash for user {uid}',
f'Deleted mifareIDHash: {old_value}'
)
conn.modify_s(dn, [(ldap.MOD_DELETE, 'mifareidhash', old_value.encode('utf-8'))])
return _modify_mifareidhash(uid, form, modify_func)
@bp.route('/admin/groups/')
@admin_required
def admin_groups_view():

View File

@ -29,7 +29,7 @@ ldap_admin_password = os.getenv('LDAPWEB_ADMIN_PASSWORD', 'unused')
# Protected LDAP user groups
# These groups (and their members) cannot be modified by admin UI
ldap_protected_groups = (
'staff,zarzad,rewizja,ldap-admin'.split(',') +
'staff,zarzad,ldap-admin'.split(',') +
os.getenv('LDAPWEB_PROTECTED_GROUPS', '').split(',')
)

View File

@ -0,0 +1,22 @@
<div class="modal fade" tabindex="-1" role="dialog">
<form action="/admin/users/{{ uid }}/add_mifareidhash" method="POST" class="form-signin">
<div class="modal-dialog" role="document">
<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">Adding {{ form.value.label.text }}</h4>
</div>
<div class="modal-body">
{{ form.csrf_token }}
{{ form.value(class_="form-control", placeholder=form.value.label.text) }}
{{ form.old_value }}
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
<button type="submit" class="btn btn-primary">Save changes</button>
</div>
</div>
</div>
</form>
</div>

View File

@ -0,0 +1,20 @@
<div class="modal fade" tabindex="-1" role="dialog">
<form action="/admin/users/{{ uid }}/del_mifareidhash?value={{ value | urlencode }}" method="POST" class="form-signin">
<div class="modal-dialog" role="document">
<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">Deleting mifareIDHash</h4>
</div>
<div class="modal-body">
You are about to delete mifareIDHash: {{ value }}.
{{ form.csrf_token }}
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
<button type="submit" class="btn btn-primary">Delete</button>
</div>
</div>
</div>
</form>
</div>

View File

@ -6,8 +6,10 @@
<div style="margin-bottom: 10px">
<a class="btn btn-default" href="/admin/users" role="button">Back</a>
<a class="btn btn-default" href="https://kasownik.hackerspace.pl/admin/member/{{ uid }}" role="button" target="_blank">View user in Kasownik</a>
{% if not is_protected %}
<a class="btn btn-default modalLink" href="/admin/users/{{ uid }}/add_mifareidhash" role="button">Add mifareIDHash</a>
{% endif %}
</div>
<p>
@ -29,12 +31,18 @@
<th scope="col">Attribute</th>
<th scope="col">Attribute</th>
<th scope="col" class="profile-table-value">Value</th>
<th scope="col" class="profile-table-options">Options</th>
</tr>
{% for attr, attr_readable, value in profile %}
<tr>
<td>{{ attr }}</td>
<td>{{ attr_readable if attr_readable else '' }}</td>
<td class="profile-table-value">{{ '(...omitted...)' if attr == 'jpegPhoto' else value }}</td>
<td class="profile-table-options">
{% if not is_protected and attr == 'mifareIDHash' %}
<a class="modalLink" href="/admin/users/{{ uid }}/del_mifareidhash?value={{ value | urlencode }}"><span class="glyphicon glyphicon-minus-sign" aria-hidden="true"></span> Remove</a>
{% endif %}
</td>
</tr>
{% endfor %}
</table>
@ -52,6 +60,10 @@
.profile-table th.profile-table-value {
width: max-content;
}
.profile-table td.profile-table-options,
.profile-table th.profile-table-options {
width: 100px;
}
.profile-avatar-lmao {
width: 120px;
height: 120px;
@ -66,3 +78,17 @@
}
</style>
{% endblock %}
{% block scripts %}
<script>
$(document).ready(function() {
$(".modalLink").click(function(e) {
e.preventDefault();
var url = $(this).attr('href');
$.get(url, function(data) {
$(data).modal('show');
});
});
});
</script>
{% endblock %}