Moderators group. #12

This commit is contained in:
Éloi Rivard 2020-11-02 12:13:03 +01:00
parent 2c14d35621
commit 0b668f50ef
9 changed files with 87 additions and 11 deletions

View file

@ -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)

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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>

View file

@ -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>

View file

@ -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

View file

@ -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

View file

@ -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():