1
0
mirror of https://github.com/spl0k/supysonic.git synced 2024-11-09 19:52:16 +00:00

Apply changes

This commit is contained in:
vithyze 2023-03-05 12:07:52 +00:00
parent 8e2adf8fc8
commit b2f0c99f79
14 changed files with 322 additions and 39 deletions

View File

@ -33,6 +33,9 @@ log_level = WARNING
; for testing purposes. Default: on ; for testing purposes. Default: on
;mount_api = on ;mount_api = on
; Require API key for authentication. Default: yes
require_api_key = yes
; Enable the administrative web interface. Default: on ; Enable the administrative web interface. Default: on
;mount_webui = on ;mount_webui = on
@ -87,3 +90,21 @@ default_transcode_target = mp3
;mp3 = audio/mpeg ;mp3 = audio/mpeg
;ogg = audio/vorbis ;ogg = audio/vorbis
[ldap]
; Server URL. Default: none
;url = ldapi://%2Frun%2Fslapd%2Fldapi
;url = ldap://127.0.0.1:389
; Bind credentials. Default: none
;bind_dn = cn=username,dc=example,dc=org
;bind_pw = password
; Base DN. Default: none
;base_dn = ou=Users,dc=example,dc=org
; User filter. The variable '{username}' is used for filtering. Default: none
;user_filter = (&(objectClass=inetOrgperson)(uid={username}))
; Mail attribute. Default: mail
;mail_attr = mail

View File

@ -11,6 +11,7 @@ import binascii
import uuid import uuid
from flask import request from flask import request
from flask import Blueprint from flask import Blueprint
from flask import current_app
from peewee import IntegrityError from peewee import IntegrityError
from ..db import ClientPrefs, Folder from ..db import ClientPrefs, Folder
@ -56,10 +57,16 @@ def decode_password(password):
@api.before_request @api.before_request
def authorize(): def authorize():
require_api_key = current_app.config["WEBAPP"]["require_api_key"]
if request.authorization: if request.authorization:
user = UserManager.try_auth( user = UserManager.try_auth_api(
request.authorization.username, request.authorization.password request.authorization.username, request.authorization.password
) )
if user is None and not require_api_key:
user = UserManager.try_auth(
request.authorization.username, request.authorization.password
)
if user is not None: if user is not None:
request.user = user request.user = user
return return
@ -69,7 +76,11 @@ def authorize():
password = request.values["p"] password = request.values["p"]
password = decode_password(password) password = decode_password(password)
user = UserManager.try_auth(username, password) user = UserManager.try_auth_api(username, password)
if user is None and not require_api_key:
user = UserManager.try_auth(
request.authorization.username, request.authorization.password
)
if user is None: if user is None:
raise Unauthorized() raise Unauthorized()

View File

@ -7,6 +7,7 @@
import click import click
import time import time
import uuid
from click.exceptions import ClickException from click.exceptions import ClickException
@ -236,12 +237,12 @@ def user():
def user_list(): def user_list():
"""Lists users.""" """Lists users."""
click.echo("Name\t\tAdmin\tJukebox\tEmail") click.echo("Name\t\tLDAP\tAdmin\tJukebox\tEmail")
click.echo("----\t\t-----\t-------\t-----") click.echo("----\t\t-----\t-----\t-------\t-----")
for u in User.select(): for u in User.select():
click.echo( click.echo(
"{: <16}{}\t{}\t{}".format( "{: <16}{}\t{}\t{}\t{}".format(
u.name, "*" if u.admin else "", "*" if u.jukebox else "", u.mail u.name, "*" if u.ldap else "", "*" if u.admin else "", "*" if u.jukebox else "", u.mail
) )
) )
@ -249,7 +250,7 @@ def user_list():
@user.command("add") @user.command("add")
@click.argument("name") @click.argument("name")
@click.password_option("-p", "--password", help="Specifies the user's password") @click.password_option("-p", "--password", help="Specifies the user's password")
@click.option("-e", "--email", default="", help="Sets the user's email address") @click.option("-e", "--email", default=None, help="Sets the user's email address")
def user_add(name, password, email): def user_add(name, password, email):
"""Adds a new user. """Adds a new user.
@ -262,10 +263,42 @@ def user_add(name, password, email):
raise ClickException(str(e)) from e raise ClickException(str(e)) from e
@user.group("edit")
def user_edit():
"""User edit commands"""
pass
@user_edit.command("email")
@click.argument("name")
@click.option("-e", "--email", prompt=True, default="", help="Sets the user's email address")
def user_edit_email(name, email):
"""Changes an user's email.
NAME is the name (or login) of the user to edit.
"""
user = User.get(name=name)
if user is None:
raise ClickException("No such user")
if user.ldap:
raise ClickException("Unavailable for LDAP users")
if email == "":
email = None
if user.mail != email:
user.mail = email
user.save()
click.echo(f"Updated user '{name}'")
@user.command("delete") @user.command("delete")
@click.argument("name") @click.argument("name")
def user_delete(name): def user_delete(name):
"""Deletes a user. """Deletes an user.
NAME is the name of the user to delete. NAME is the name of the user to delete.
""" """
@ -295,7 +328,7 @@ def _echo_role_change(username, name, value):
help="Grant or revoke jukebox rights", help="Grant or revoke jukebox rights",
) )
def user_roles(name, admin, jukebox): def user_roles(name, admin, jukebox):
"""Enable/disable rights for a user. """Enable/disable rights for an user.
NAME is the login of the user to which grant or revoke rights. NAME is the login of the user to which grant or revoke rights.
""" """
@ -314,27 +347,31 @@ def user_roles(name, admin, jukebox):
user.save() user.save()
@user.command("changepass") @user_edit.command("password")
@click.argument("name") @click.argument("name")
@click.password_option("-p", "--password", help="New password") @click.password_option("-p", "--password", help="New password")
def user_changepass(name, password): def user_changepass(name, password):
"""Changes a user's password. """Changes an user's password.
NAME is the login of the user to which change the password. NAME is the login of the user to which change the password.
""" """
try: user = User.get(name=name)
UserManager.change_password2(name, password) if user is None:
click.echo(f"Successfully changed '{name}' password") raise ClickException(f"User '{name}' does not exist.")
except User.DoesNotExist as e:
raise ClickException(f"User '{name}' does not exist.") from e if user.ldap:
raise ClickException("Unavailable for LDAP users")
UserManager.change_password2(name, password)
click.echo(f"Successfully changed '{name}' password")
@user.command("rename") @user_edit.command("username")
@click.argument("name") @click.argument("name")
@click.argument("newname") @click.argument("newname")
def user_rename(name, newname): def user_rename(name, newname):
"""Renames a user. """Renames an user.
User NAME will then be known as NEWNAME. User NAME will then be known as NEWNAME.
""" """
@ -361,6 +398,62 @@ def user_rename(name, newname):
click.echo(f"User '{name}' renamed to '{newname}'") click.echo(f"User '{name}' renamed to '{newname}'")
@user.group("apikey")
def user_apikey():
"""User API key commands"""
pass
@user_apikey.command("show")
@click.argument("name")
def user_apikey_show(name):
"""Shows the API key of an user.
NAME is the name (or login) of the user to show the API key.
"""
user = User.get(name=name)
if user is None:
raise ClickException(f"User '{name}' does not exist.")
click.echo(f"'{name}' API key: {user.api_key}")
@user_apikey.command("new")
@click.argument("name")
def user_apikey_new(name):
"""Generates a new API key for an user.
NAME is the name (or login) of the user to generate the API key for.
"""
user = User.get(name=name)
if user is None:
raise ClickException(f"User '{name}' does not exist.")
user.api_key = str(uuid.uuid4()).replace("-", "")
user.save()
click.echo(f"Updated '{name}' API key")
@user_apikey.command("delete")
@click.argument("name")
def user_apikey_delete(name):
"""Deletes the API key of an user.
NAME is the name (or login) of the user to delete the API key.
"""
user = User.get(name=name)
if user is None:
raise ClickException(f"User '{name}' does not exist.")
if user.api_key is not None:
user.api_key = None
user.save()
click.echo(f"Deleted '{name}' API key")
def main(): def main():
config = IniConfig.from_common_locations() config = IniConfig.from_common_locations()
init_database(config.BASE["database_uri"]) init_database(config.BASE["database_uri"])

View File

@ -36,6 +36,7 @@ class DefaultConfig:
"log_level": "WARNING", "log_level": "WARNING",
"mount_webui": True, "mount_webui": True,
"mount_api": True, "mount_api": True,
"require_api_key": True,
"index_ignored_prefixes": "El La Le Las Les Los The", "index_ignored_prefixes": "El La Le Las Les Los The",
} }
DAEMON = { DAEMON = {
@ -51,6 +52,14 @@ class DefaultConfig:
LASTFM = {"api_key": None, "secret": None} LASTFM = {"api_key": None, "secret": None}
TRANSCODING = {} TRANSCODING = {}
MIMETYPES = {} MIMETYPES = {}
LDAP = {
"url": None,
"bind_dn": None,
"bind_pw": None,
"base_dn": None,
"user_filter": None,
"mail_attr": "mail"
}
def __init__(self): def __init__(self):
current_config = self current_config = self

View File

@ -428,12 +428,15 @@ class User(_Model):
id = PrimaryKeyField() id = PrimaryKeyField()
name = CharField(64, unique=True) name = CharField(64, unique=True)
mail = CharField(null=True) mail = CharField(null=True)
password = FixedCharField(40) password = FixedCharField(40, null=True)
salt = FixedCharField(6) salt = FixedCharField(6, null=True)
ldap = BooleanField(default=False)
admin = BooleanField(default=False) admin = BooleanField(default=False)
jukebox = BooleanField(default=False) jukebox = BooleanField(default=False)
api_key = CharField(32, null=True)
lastfm_session = FixedCharField(32, null=True) lastfm_session = FixedCharField(32, null=True)
lastfm_status = BooleanField( lastfm_status = BooleanField(
default=True default=True

View File

@ -6,6 +6,7 @@
# Distributed under terms of the GNU AGPLv3 license. # Distributed under terms of the GNU AGPLv3 license.
import logging import logging
import uuid
from flask import flash, redirect, render_template, request, session, url_for from flask import flash, redirect, render_template, request, session, url_for
from flask import current_app from flask import current_app
@ -172,22 +173,37 @@ def change_username_post(uid):
@frontend.route("/user/<uid>/changemail") @frontend.route("/user/<uid>/changemail")
@me_or_uuid @me_or_uuid
def change_mail_form(uid, user): def change_mail_form(uid, user):
return render_template("change_mail.html", user=user) if user.ldap:
flash("Unavailable for LDAP users")
return redirect(url_for("frontend.user_profile", uid=uid))
else:
return render_template("change_mail.html", user=user)
@frontend.route("/user/<uid>/changemail", methods=["POST"]) @frontend.route("/user/<uid>/changemail", methods=["POST"])
@me_or_uuid @me_or_uuid
def change_mail_post(uid, user): def change_mail_post(uid, user):
mail = request.form.get("mail", "") mail = request.form.get("mail", "")
# No validation, lol. if mail == "":
user.mail = mail mail = None
if user.mail == mail:
flash("No changes made")
else:
# No validation, lol.
user.mail = mail
user.save()
flash("Email changed")
return redirect(url_for("frontend.user_profile", uid=uid)) return redirect(url_for("frontend.user_profile", uid=uid))
@frontend.route("/user/<uid>/changepass") @frontend.route("/user/<uid>/changepass")
@me_or_uuid @me_or_uuid
def change_password_form(uid, user): def change_password_form(uid, user):
return render_template("change_pass.html", user=user) if user.ldap:
flash("Unavailable for LDAP users")
return redirect(url_for("frontend.user_profile", uid=uid))
else:
return render_template("change_pass.html", user=user)
@frontend.route("/user/<uid>/changepass", methods=["POST"]) @frontend.route("/user/<uid>/changepass", methods=["POST"])
@ -235,8 +251,8 @@ def add_user_form():
def add_user_post(): def add_user_post():
error = False error = False
args = request.form.copy() args = request.form.copy()
(name, passwd, passwd_confirm) = map( (name, passwd, passwd_confirm, mail) = map(
args.pop, ("user", "passwd", "passwd_confirm"), (None,) * 3 args.pop, ("user", "passwd", "passwd_confirm", "mail"), (None,) * 4
) )
if not name: if not name:
flash("The name is required.") flash("The name is required.")
@ -248,9 +264,12 @@ def add_user_post():
flash("The passwords don't match.") flash("The passwords don't match.")
error = True error = True
if mail == "":
mail = None
if not error: if not error:
try: try:
UserManager.add(name, passwd, **args) UserManager.add(name, passwd, mail=mail, **args)
flash(f"User '{name}' successfully added") flash(f"User '{name}' successfully added")
return redirect(url_for("frontend.user_index")) return redirect(url_for("frontend.user_index"))
except ValueError as e: except ValueError as e:
@ -333,3 +352,24 @@ def logout():
session.clear() session.clear()
flash("Logged out!") flash("Logged out!")
return redirect(url_for("frontend.login")) return redirect(url_for("frontend.login"))
@frontend.route("/user/<uid>/new_api_key")
@me_or_uuid
def new_api_key(uid, user):
user.api_key = str(uuid.uuid4()).replace("-", "")
user.save()
flash("API key updated")
return redirect(url_for("frontend.user_profile", uid=uid))
@frontend.route("/user/<uid>/del_api_key")
@me_or_uuid
def del_api_key(uid, user):
if user.api_key is None:
flash("No changes made")
else:
user.api_key = None
user.save()
flash("API key deleted")
return redirect(url_for("frontend.user_profile", uid=uid))

47
supysonic/ldap.py Normal file
View File

@ -0,0 +1,47 @@
# This file is part of Supysonic.
# Supysonic is a Python implementation of the Subsonic server API.
#
# Copyright (C) 2013-2018 Alban 'spl0k' Féron
#
# Distributed under terms of the GNU AGPLv3 license.
import logging
try:
import ldap3
except ModuleNotFoundError:
ldap3 = None
from flask import current_app
logger = logging.getLogger(__name__)
class Ldap:
@staticmethod
def try_auth(username, password):
config = current_app.config["LDAP"]
if None in config.values():
return
elif not ldap3:
logger.warning("Module 'ldap3' is not installed.")
return
server = ldap3.Server(config["url"], get_info=None)
with ldap3.Connection(server, config["bind_dn"], config["bind_pw"], read_only=True) as conn:
conn.search(
config["base_dn"],
config["user_filter"].format(username=username),
search_scope=ldap3.LEVEL,
attributes=[config["mail_attr"]],
size_limit=1
)
entries = conn.entries
if entries:
try:
with ldap3.Connection(server, entries[0].entry_dn, password, read_only=True):
return {"mail": entries[0][config["mail_attr"]].value}
except ldap3.core.exceptions.LDAPBindError:
return False

View File

@ -12,6 +12,7 @@ import string
import uuid import uuid
from ..db import User from ..db import User
from ..ldap import Ldap
class UserManager: class UserManager:
@ -45,15 +46,42 @@ class UserManager:
user.delete_instance(recursive=True) user.delete_instance(recursive=True)
@staticmethod @staticmethod
def try_auth(name, password): def try_auth_api(name, password):
user = User.get_or_none(name=name) user = User.get_or_none(name=name)
if user is None: if user is None:
return None return None
elif UserManager.__encrypt_password(password, user.salt)[0] != user.password: if user.api_key is None:
return None
elif password != user.api_key:
return None return None
else: else:
return user return user
@staticmethod
def try_auth(name, password):
ldap_user = Ldap.try_auth(name, password)
user = User.get_or_none(name=name)
if ldap_user is None:
if user is None:
return None
elif UserManager.__encrypt_password(password, user.salt)[0] != user.password:
return None
else:
return user
elif ldap_user:
if user is None:
user = User.create(name=name, mail=ldap_user["mail"], ldap=True)
return user
elif not user.ldap:
return None
else:
if user.mail != ldap_user["mail"]:
user.mail = ldap_user["mail"]
return user
else:
return None
@staticmethod @staticmethod
def change_password(uid, old_pass, new_pass): def change_password(uid, old_pass, new_pass):
user = UserManager.get(uid) user = UserManager.get(uid)

View File

@ -53,10 +53,12 @@ CREATE TABLE IF NOT EXISTS user (
id CHAR(32) PRIMARY KEY, id CHAR(32) PRIMARY KEY,
name VARCHAR(64) NOT NULL, name VARCHAR(64) NOT NULL,
mail VARCHAR(256), mail VARCHAR(256),
password CHAR(40) NOT NULL, password CHAR(40),
salt CHAR(6) NOT NULL, salt CHAR(6),
ldap BOOLEAN NOT NULL,
admin BOOLEAN NOT NULL, admin BOOLEAN NOT NULL,
jukebox BOOLEAN NOT NULL, jukebox BOOLEAN NOT NULL,
api_key CHAR(32),
lastfm_session CHAR(32), lastfm_session CHAR(32),
lastfm_status BOOLEAN NOT NULL, lastfm_status BOOLEAN NOT NULL,
last_play_id CHAR(32) REFERENCES track(id), last_play_id CHAR(32) REFERENCES track(id),

View File

@ -53,10 +53,12 @@ CREATE TABLE IF NOT EXISTS "user" (
id UUID PRIMARY KEY, id UUID PRIMARY KEY,
name VARCHAR(64) NOT NULL, name VARCHAR(64) NOT NULL,
mail VARCHAR(256), mail VARCHAR(256),
password CHAR(40) NOT NULL, password CHAR(40),
salt CHAR(6) NOT NULL, salt CHAR(6),
ldap BOOLEAN NOT NULL,
admin BOOLEAN NOT NULL, admin BOOLEAN NOT NULL,
jukebox BOOLEAN NOT NULL, jukebox BOOLEAN NOT NULL,
api_key CHAR(32),
lastfm_session CHAR(32), lastfm_session CHAR(32),
lastfm_status BOOLEAN NOT NULL, lastfm_status BOOLEAN NOT NULL,
last_play_id UUID REFERENCES track, last_play_id UUID REFERENCES track,

View File

@ -55,10 +55,12 @@ CREATE TABLE IF NOT EXISTS user (
id CHAR(36) PRIMARY KEY, id CHAR(36) PRIMARY KEY,
name VARCHAR(64) NOT NULL, name VARCHAR(64) NOT NULL,
mail VARCHAR(256), mail VARCHAR(256),
password CHAR(40) NOT NULL, password CHAR(40),
salt CHAR(6) NOT NULL, salt CHAR(6),
ldap BOOLEAN NOT NULL,
admin BOOLEAN NOT NULL, admin BOOLEAN NOT NULL,
jukebox BOOLEAN NOT NULL, jukebox BOOLEAN NOT NULL,
api_key CHAR(32),
lastfm_session CHAR(32), lastfm_session CHAR(32),
lastfm_status BOOLEAN NOT NULL, lastfm_status BOOLEAN NOT NULL,
last_play_id CHAR(36) REFERENCES track, last_play_id CHAR(36) REFERENCES track,

View File

@ -32,7 +32,7 @@
<label class="sr-only" for="mail">eMail</label> <label class="sr-only" for="mail">eMail</label>
<div class="input-group"> <div class="input-group">
<div class="input-group-addon"><span class="glyphicon glyphicon-envelope"></span></div> <div class="input-group-addon"><span class="glyphicon glyphicon-envelope"></span></div>
<input type="text" class="form-control" id="mail" name="mail" value="{{ request.form.mail or user.mail }}" placeholder="eMail" /> <input type="text" class="form-control" id="mail" name="mail" value="{{ user.mail or "" }}" placeholder="eMail" />
</div> </div>
</div> </div>
<input type="submit" class="btn btn-default" /> <input type="submit" class="btn btn-default" />

View File

@ -25,7 +25,9 @@
{% endblock %} {% endblock %}
{% block body %} {% block body %}
<div class="page-header first-header"> <div class="page-header first-header">
<h2>{{ user.name }}{% if user.admin %} <small><span class="glyphicon <h2>{{ user.name }}{% if user.ldap %} <small><span class="glyphicon
glyphicon-cloud" data-toggle="tooltip" data-placement="right" title="LDAP"></span></small>{% endif %}
{% if user.admin %} <small><span class="glyphicon
glyphicon-certificate" data-toggle="tooltip" data-placement="right" glyphicon-certificate" data-toggle="tooltip" data-placement="right"
title="{% if request.user.id == user.id %}You're an admin!{% else %}The user is an admin!{% endif %}"></span></small>{% endif %}</h2> title="{% if request.user.id == user.id %}You're an admin!{% else %}The user is an admin!{% endif %}"></span></small>{% endif %}</h2>
</div> </div>
@ -37,6 +39,7 @@
<div class="input-group"> <div class="input-group">
<div class="input-group-addon">User eMail</div> <div class="input-group-addon">User eMail</div>
<input type="text" class="form-control" id="email" placeholder="{{ user.mail }}" readonly> <input type="text" class="form-control" id="email" placeholder="{{ user.mail }}" readonly>
{% if not user.ldap %}
<div class="input-group-btn"> <div class="input-group-btn">
{% if request.user.id == user.id %} {% if request.user.id == user.id %}
<a href="{{ url_for('frontend.change_mail_form', uid = 'me') }}" class="btn btn-default">Change eMail</a> <a href="{{ url_for('frontend.change_mail_form', uid = 'me') }}" class="btn btn-default">Change eMail</a>
@ -44,6 +47,24 @@
<a href="{{ url_for('frontend.change_mail_form', uid = user.id) }}" class="btn btn-default">Change eMail</a> <a href="{{ url_for('frontend.change_mail_form', uid = user.id) }}" class="btn btn-default">Change eMail</a>
{% endif %} {% endif %}
</div> </div>
{% endif %}
</div>
</div>
</form>
</div>
<div class="col-md-6">
<form>
<div class="form-group">
<label class="sr-only" for="apikey">API key</label>
<div class="input-group">
<div class="input-group-addon">API key</div>
<input type="text" class="form-control" id="apikey" {% if user.api_key %}value{% else %}placeholder{% endif %}="{{ user.api_key }}" readonly>
<div class="input-group-btn">
<a href="{{ url_for('frontend.new_api_key', uid = 'me') if request.user.id == user.id else url_for('frontend.new_api_key', uid = user.id) }}" class="btn btn-default">
<span class="glyphicon glyphicon-refresh" aria-hidden="true" data-toggle="tooltip" data-placement="top" title="Generate key"></span></a>
<a href="{{ url_for('frontend.del_api_key', uid = 'me') if request.user.id == user.id else url_for('frontend.del_api_key', uid = user.id) }}" class="btn btn-default{% if not user.api_key %} disabled{% endif %}">
<span class="glyphicon glyphicon-remove-circle" aria-hidden="true" data-toggle="tooltip" data-placement="top" title="Delete key"></span></a>
</div>
</div> </div>
</div> </div>
</form> </form>
@ -83,11 +104,15 @@
</div> </div>
</div> </div>
{% if request.user.id == user.id %} {% if request.user.id == user.id %}
{% if not user.ldap %}
<a href="{{ url_for('frontend.change_password_form', uid = 'me') }}" class="btn btn-default">Change password</a></li> <a href="{{ url_for('frontend.change_password_form', uid = 'me') }}" class="btn btn-default">Change password</a></li>
{% endif %}
{% else %} {% else %}
<a href="{{ url_for('frontend.change_username_form', uid = user.id) }}" class="btn btn-default">Change username or admin status</a></li> <a href="{{ url_for('frontend.change_username_form', uid = user.id) }}" class="btn btn-default">Change username or admin status</a></li>
{% if not user.ldap %}
<a href="{{ url_for('frontend.change_password_form', uid = user.id) }}" class="btn btn-default">Change password</a></li> <a href="{{ url_for('frontend.change_password_form', uid = user.id) }}" class="btn btn-default">Change password</a></li>
{% endif %} {% endif %}
{% endif %}
{% if clients.count() %} {% if clients.count() %}
<div class="page-header"> <div class="page-header">
<h2>Clients</h2> <h2>Clients</h2>

View File

@ -18,14 +18,14 @@
</div> </div>
<table class="table table-striped table-hover"> <table class="table table-striped table-hover">
<thead> <thead>
<tr><th>Name</th><th>EMail</th><th>Admin</th><th>Last play date</th><th></th></tr> <tr><th>Name</th><th>EMail</th><th>LDAP</th><th>Admin</th><th>Last play date</th><th></th></tr>
</thead> </thead>
<tbody> <tbody>
{% for user in users %} {% for user in users %}
<tr> <tr>
<td>{% if request.user.id == user.id %}{{ user.name }}{% else %} <td>{% if request.user.id == user.id %}{{ user.name }}{% else %}
<a href="{{ url_for('frontend.user_profile', uid = user.id) }}">{{ user.name }}</a>{% endif %}</td> <a href="{{ url_for('frontend.user_profile', uid = user.id) }}">{{ user.name }}</a>{% endif %}</td>
<td>{{ user.mail }}</td><td>{{ user.admin }}</td><td>{{ user.last_play_date }}</td><td> <td>{{ user.mail }}</td><td>{{ user.ldap }}</td><td>{{ user.admin }}</td><td>{{ user.last_play_date }}</td><td>
{% if request.user.id != user.id %}<button class="btn btn-danger btn-xs" data-href="{{ url_for('frontend.del_user', uid = user.id) }}" data-toggle="modal" data-target="#confirm-delete" aria-label="Delete user"> {% if request.user.id != user.id %}<button class="btn btn-danger btn-xs" data-href="{{ url_for('frontend.del_user', uid = user.id) }}" data-toggle="modal" data-target="#confirm-delete" aria-label="Delete user">
<span class="glyphicon glyphicon-remove-circle" aria-hidden="true" data-toggle="tooltip" data-placement="top" title="Delete user"></span></button>{% endif %}</td></tr> <span class="glyphicon glyphicon-remove-circle" aria-hidden="true" data-toggle="tooltip" data-placement="top" title="Delete user"></span></button>{% endif %}</td></tr>
{% endfor %} {% endfor %}