forked from Github-Mirrors/canaille
Moderators group. #12
This commit is contained in:
parent
2c14d35621
commit
0b668f50ef
9 changed files with 87 additions and 11 deletions
|
@ -24,7 +24,7 @@ from .forms import (
|
|||
PasswordResetForm,
|
||||
ForgottenPasswordForm,
|
||||
)
|
||||
from .flaskutils import current_user, user_needed, admin_needed
|
||||
from .flaskutils import current_user, user_needed, moderator_needed
|
||||
from .models import User
|
||||
|
||||
|
||||
|
@ -71,14 +71,14 @@ def logout():
|
|||
|
||||
|
||||
@bp.route("/users")
|
||||
@admin_needed()
|
||||
@moderator_needed()
|
||||
def users(user):
|
||||
users = User.filter(objectClass=current_app.config["LDAP"]["USER_CLASS"])
|
||||
return render_template("users.html", users=users, menuitem="users")
|
||||
|
||||
|
||||
@bp.route("/profile", methods=("GET", "POST"))
|
||||
@admin_needed()
|
||||
@moderator_needed()
|
||||
def profile_creation(user):
|
||||
claims = current_app.config["JWT"]["MAPPING"]
|
||||
form = AddProfileForm(request.form or None)
|
||||
|
@ -120,7 +120,7 @@ def profile_creation(user):
|
|||
@bp.route("/profile/<username>", methods=("GET", "POST"))
|
||||
@user_needed()
|
||||
def profile_edition(user, username):
|
||||
user.admin or username == user.uid[0] or abort(403)
|
||||
user.moderator or username == user.uid[0] or abort(403)
|
||||
|
||||
if request.method == "GET" or request.form.get("action") == "edit":
|
||||
return profile_edit(user, username)
|
||||
|
|
|
@ -39,11 +39,18 @@ USER_FILTER = "(|(uid={login})(cn={login}))"
|
|||
# A class to use for creating new users
|
||||
USER_CLASS = "inetOrgPerson"
|
||||
|
||||
# Filter to match admin users. If your server has memberof
|
||||
# you can filter against group membership
|
||||
# Filter to match super admin users. Super admins can manage
|
||||
# OAuth clients, tokens and authorizations. If your LDAP server has
|
||||
# the 'memberof' overlay, you can filter against group membership.
|
||||
# ADMIN_FILTER = "uid=admin"
|
||||
ADMIN_FILTER = "memberof=cn=admins,ou=groups,dc=mydomain,dc=tld"
|
||||
|
||||
# Filter to match super admin users. User admins can edit, create
|
||||
# and delete user accounts. If your LDAP server has the 'memberof'
|
||||
# overlay, you can filter against group membership.
|
||||
# USER_ADMIN_FILTER = "uid=moderator"
|
||||
USER_ADMIN_FILTER = "memberof=cn=moderators,ou=groups,dc=mydomain,dc=tld"
|
||||
|
||||
# The jwt configuration. You can generate a RSA keypair with:
|
||||
# ssh-keygen -t rsa -b 4096 -m PEM -f private.pem
|
||||
# openssl rsa -in private.pem -pubout -outform PEM -out public.pem
|
||||
|
|
|
@ -28,6 +28,20 @@ def user_needed():
|
|||
return wrapper
|
||||
|
||||
|
||||
def moderator_needed():
|
||||
def wrapper(view_function):
|
||||
@wraps(view_function)
|
||||
def decorator(*args, **kwargs):
|
||||
user = current_user()
|
||||
if not user or not user.moderator:
|
||||
abort(403)
|
||||
return view_function(*args, user=user, **kwargs)
|
||||
|
||||
return decorator
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
def admin_needed():
|
||||
def wrapper(view_function):
|
||||
@wraps(view_function)
|
||||
|
|
|
@ -15,6 +15,7 @@ from .ldaputils import LDAPObject
|
|||
class User(LDAPObject):
|
||||
id = "cn"
|
||||
admin = False
|
||||
moderator = False
|
||||
|
||||
@classmethod
|
||||
def get(cls, login=None, dn=None, filter=None, conn=None):
|
||||
|
@ -26,6 +27,7 @@ class User(LDAPObject):
|
|||
user = super().get(dn, filter, conn)
|
||||
|
||||
admin_filter = current_app.config["LDAP"].get("ADMIN_FILTER")
|
||||
moderator_filter = current_app.config["LDAP"].get("USER_ADMIN_FILTER")
|
||||
if (
|
||||
admin_filter
|
||||
and user
|
||||
|
@ -34,6 +36,17 @@ class User(LDAPObject):
|
|||
):
|
||||
|
||||
user.admin = True
|
||||
user.moderator = True
|
||||
|
||||
elif (
|
||||
moderator_filter
|
||||
and user
|
||||
and user.dn
|
||||
and conn.search_s(user.dn, ldap.SCOPE_SUBTREE, moderator_filter)
|
||||
):
|
||||
|
||||
user.moderator = True
|
||||
|
||||
return user
|
||||
|
||||
@classmethod
|
||||
|
|
|
@ -41,7 +41,7 @@
|
|||
<i class="handshake icon"></i>
|
||||
{% trans %}My consents{% endtrans %}
|
||||
</a>
|
||||
{% if user.admin %}
|
||||
{% if user.moderator %}
|
||||
<a class="item {% if menuitem == "users" %}active{% endif %}"
|
||||
href="{{ url_for('canaille.account.users') }}">
|
||||
<i class="users icon"></i>
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% if user.admin and edited_user %}
|
||||
{% if user.moderator and edited_user %}
|
||||
<div class="ui basic modal">
|
||||
<div class="ui icon header">
|
||||
<i class="user minus icon"></i>
|
||||
|
@ -76,7 +76,7 @@
|
|||
<button type="submit" class="ui right floated primary button" name="action" value="edit" id="edit">
|
||||
{{ _("Send") }}
|
||||
</button>
|
||||
{% if user.admin and edited_user %}
|
||||
{% if user.moderator and edited_user %}
|
||||
<button type="submit" class="ui right floated negative button confirm" name="action" value="delete" id="delete">
|
||||
{{ _("Delete the user") }}
|
||||
</button>
|
||||
|
|
|
@ -17,6 +17,11 @@ objectclass: groupOfNames
|
|||
cn: admins
|
||||
member: cn=Jane Doe,ou=users,dc=mydomain,dc=tld
|
||||
|
||||
dn: cn=moderators,ou=groups,dc=mydomain,dc=tld
|
||||
objectclass: groupOfNames
|
||||
cn: moderators
|
||||
member: cn=Jack Doe,ou=users,dc=mydomain,dc=tld
|
||||
|
||||
dn: cn=Jane Doe,ou=users,dc=mydomain,dc=tld
|
||||
objectclass: top
|
||||
objectclass: inetOrgPerson
|
||||
|
@ -30,6 +35,18 @@ userPassword: {SSHA}7zQVLckaEc6cJEsS0ylVipvb2PAR/4tS
|
|||
memberof: cn=admins,ou=groups,dc=mydomain,dc=tld
|
||||
memberof: cn=users,ou=groups,dc=mydomain,dc=tld
|
||||
|
||||
dn: cn=Jack Doe,ou=users,dc=mydomain,dc=tld
|
||||
objectclass: top
|
||||
objectclass: inetOrgPerson
|
||||
cn: Jack Doe
|
||||
gn: Jack
|
||||
sn: Doe
|
||||
uid: moderator
|
||||
mail: moderator@mydomain.tld
|
||||
telephoneNumber: 555-000-002
|
||||
userPassword: {SSHA}+eHyxWqajMHsOWnhONC2vbtfNZzKTkag
|
||||
memberof: cn=moderators,ou=groups,dc=mydomain,dc=tld
|
||||
|
||||
dn: cn=John Doe,ou=users,dc=mydomain,dc=tld
|
||||
objectclass: top
|
||||
objectclass: inetOrgPerson
|
||||
|
|
|
@ -131,6 +131,7 @@ def app(slapd_server, keypair_path):
|
|||
"USER_FILTER": "(|(uid={login})(cn={login}))",
|
||||
"USER_CLASS": "inetOrgPerson",
|
||||
"ADMIN_FILTER": "(|(uid=admin)(sn=admin))",
|
||||
"USER_ADMIN_FILTER": "(|(uid=moderator)(sn=moderator))",
|
||||
},
|
||||
"JWT": {
|
||||
"PUBLIC_KEY": public_key_path,
|
||||
|
@ -254,6 +255,21 @@ def admin(app, slapd_connection):
|
|||
return u
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def moderator(app, slapd_connection):
|
||||
User.ocs_by_name(slapd_connection)
|
||||
u = User(
|
||||
objectClass=["inetOrgPerson"],
|
||||
cn="Jack Doe",
|
||||
sn="Doe",
|
||||
uid="moderator",
|
||||
mail="jack@doe.com",
|
||||
userPassword="{SSHA}+eHyxWqajMHsOWnhONC2vbtfNZzKTkag",
|
||||
)
|
||||
u.save(slapd_connection)
|
||||
return u
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def token(slapd_connection, client, user):
|
||||
Token.ocs_by_name(slapd_connection)
|
||||
|
@ -298,6 +314,13 @@ def logged_admin(admin, testclient):
|
|||
return admin
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def logged_moderator(moderator, testclient):
|
||||
with testclient.session_transaction() as sess:
|
||||
sess["user_dn"] = moderator.dn
|
||||
return moderator
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def cleanups(slapd_connection):
|
||||
yield
|
||||
|
|
|
@ -96,12 +96,14 @@ def test_simple_user_cannot_edit_other(testclient, logged_user):
|
|||
testclient.get("/users", status=403)
|
||||
|
||||
|
||||
def test_admin_bad_request(testclient, logged_admin):
|
||||
def test_admin_bad_request(testclient, logged_moderator):
|
||||
testclient.post("/profile/admin", {"action": "foobar"}, status=400)
|
||||
testclient.get("/profile/foobar", status=404)
|
||||
|
||||
|
||||
def test_user_creation_edition_and_deletion(testclient, slapd_connection, logged_admin):
|
||||
def test_user_creation_edition_and_deletion(
|
||||
testclient, slapd_connection, logged_moderator
|
||||
):
|
||||
# The user does not exist.
|
||||
res = testclient.get("/users", status=200)
|
||||
with testclient.app.app_context():
|
||||
|
|
Loading…
Reference in a new issue