Show groups and enable group creation

This commit is contained in:
Camille Daniel 2021-07-01 18:21:20 +02:00
parent 3a03f927d1
commit aed6b18aa8
10 changed files with 203 additions and 8 deletions

1
.gitignore vendored
View file

@ -19,3 +19,4 @@ canaille/conf/openid-configuration.json
canaille/conf/*.pem
canaille/conf/*.pub
canaille/conf/*.key
.vscode

View file

@ -11,6 +11,7 @@ import canaille.consents
import canaille.commands.clean
import canaille.oauth
import canaille.account
import canaille.groups
import canaille.well_known
from cryptography.hazmat.primitives import serialization as crypto_serialization
@ -135,6 +136,7 @@ def setup_app(app):
config_oauth(app)
setup_ldap_tree(app)
app.register_blueprint(canaille.account.bp)
app.register_blueprint(canaille.groups.bp, url_prefix="/groups")
app.register_blueprint(canaille.oauth.bp, url_prefix="/oauth")
app.register_blueprint(canaille.commands.clean.bp)
app.register_blueprint(canaille.consents.bp, url_prefix="/consent")

View file

@ -145,3 +145,13 @@ def profile_form(field_names):
render_kw={},
)
return wtforms.form.BaseForm(fields)
class GroupForm(FlaskForm):
name = wtforms.StringField(
_("Name"),
validators=[wtforms.validators.DataRequired()],
render_kw={
"placeholder": _("group"),
},
)

62
canaille/groups.py Normal file
View file

@ -0,0 +1,62 @@
from flask import Blueprint, render_template, redirect, url_for, request, flash, current_app, abort
from flask_babel import gettext as _
from .flaskutils import moderator_needed
from .forms import GroupForm
from .models import Group
bp = Blueprint("groups", __name__)
@bp.route("/")
@moderator_needed()
def groups(user):
groups = Group.filter(objectClass=current_app.config["LDAP"]["GROUP_CLASS"])
return render_template("groups.html", groups=groups, menuitem="groups")
@bp.route("/add", methods=("GET", "POST"))
@moderator_needed()
def create_group(user):
form = GroupForm(request.form or None)
try:
if "name" in form:
del form["name"].render_kw["disabled"]
except KeyError:
pass
if request.form:
if not form.validate():
flash(_("Group creation failed."), "error")
else:
group = Group(objectClass=current_app.config["LDAP"]["GROUP_CLASS"])
group.member = [user.dn]
group.cn = [form.name.data]
group.save()
return redirect(url_for("groups.groups"))
return render_template(
"group.html",
form=form,
edited_group=None,
members=None
)
@bp.route("/<groupname>", methods=("GET", "POST"))
@moderator_needed()
def group(user, groupname):
group = Group.get(groupname) or abort(404)
form = GroupForm(request.form or None, data={"name": group.name})
form["name"].render_kw["disabled"] = "true"
if request.form:
if form.validate():
group.save()
else:
flash(_("Group edition failed."), "error")
return render_template(
"group.html",
form=form,
edited_group=group,
members=group.get_members()
)

View file

@ -154,6 +154,11 @@ class Group(LDAPObject):
attribute = current_app.config["LDAP"].get("GROUP_NAME_ATTRIBUTE")
return [(group[attribute][0], group.dn) for group in groups]
@property
def name(self):
attribute = current_app.config["LDAP"].get("GROUP_NAME_ATTRIBUTE")
return self[attribute][0]
def get_members(self, conn=None):
return [User.get(dn=user_dn, conn=conn) for user_dn in self.member]

View file

@ -47,6 +47,11 @@
<i class="users icon"></i>
{% trans %}Users{% endtrans %}
</a>
<a class="item {% if menuitem == "groups" %}active{% endif %}"
href="{{ url_for('groups.groups') }}">
<i class="users icon"></i>
{% trans %}Groups{% endtrans %}
</a>
{% endif %}
{% if user.admin %}
<div class="ui dropdown item {% if menuitem == "admin" %}active{% endif %}">

View file

@ -0,0 +1,58 @@
{% extends 'base.html' %}
{% import 'fomanticui.j2' as sui %}
{% import 'flask.j2' as flask %}
{% block content %}
{% if edited_group %}
<div class="ui segment">
<h2 class="ui header">{% trans %}Members{% endtrans %}</h2>
<ul>
{% for member in members %}
<div class="ui left icon">
<i class="user icon"></i>
<a href="{{ url_for('account.profile_edition', username=member.uid[0]) }}">{{ member.name }}</a></li>
</div>
{% endfor %}
</ul>
</div>
{% endif %}
<div class="ui clearing segment">
<h2 class="ui center aligned header">
<div class="content">
{% if not edited_group %}
{% trans %}Group creation{% endtrans %}
{% else %}
{% trans %}Group edition{% endtrans %}
{% endif %}
</div>
<div class="sub header">
{% if not edited_group %}
{% trans %}Create a new group{% endtrans %}
{% else %}
{% trans %}Edit informations about a group{% endtrans %}
{% endif %}
</div>
</h2>
{{ flask.messages() }}
<form method="POST"
id="{{ form.__class__.__name__|lower }}"
action="{{ request.url }}"
role="form"
class="ui form"
>
{{ form.hidden_tag() if form.hidden_tag }}
{{ sui.render_field(form.name) }}
<button type="submit" class="ui right floated primary button">
{% if not edited_group %}
{% trans %}Create group{% endtrans %}
{% else %}
{% trans %}Submit{% endtrans %}
{% endif %}
</button>
</form>
</div>
{% endblock %}

View file

@ -0,0 +1,19 @@
{% extends 'base.html' %}
{% block content %}
<div class="ui segment">
<a class="ui primary button" href="{{ url_for('groups.create_group') }}">{% trans %}Add a group{% endtrans %}</a>
</div>
<div class="ui segment">
<h2 class="ui header">{% trans %}Groups{% endtrans %}</h2>
<ul>
{% for group in groups %}
<div class="ui left icon">
<i class="users icon"></i>
<a href="{{ url_for('groups.group', groupname=group.name) }}">{{ group.name }}</a>
</div>
{% endfor %}
</ul>
</div>
{% endblock %}

View file

@ -380,8 +380,3 @@ def bar_group(app, admin, slapd_connection):
yield g
admin._groups = []
g.delete(conn=slapd_connection)
@pytest.fixture
def groups(foo_group, bar_group, slapd_connection):
return (foo_group, bar_group)

View file

@ -9,7 +9,7 @@ def test_no_group(app, slapd_connection):
def test_set_groups(app, slapd_connection, user, foo_group, bar_group):
with app.app_context():
Group.attr_type_by_name(conn=slapd_connection)
a = User.attr_type_by_name(conn=slapd_connection)
User.attr_type_by_name(conn=slapd_connection)
user = User.get(dn=user.dn, conn=slapd_connection)
assert set(Group.available_groups(conn=slapd_connection)) == {
@ -21,14 +21,52 @@ def test_set_groups(app, slapd_connection, user, foo_group, bar_group):
assert user.groups[0].dn == foo_group.dn
user.set_groups([foo_group, bar_group], conn=slapd_connection)
bar_dns = {g.dn for g in bar_group.get_members(conn=slapd_connection)}
assert user.dn in bar_dns
assert user.groups[1].dn == bar_group.dn
user.set_groups([foo_group], conn=slapd_connection)
foo_dns = {g.dn for g in foo_group.get_members(conn=slapd_connection)}
bar_dns = {g.dn for g in bar_group.get_members(conn=slapd_connection)}
assert user.dn in foo_dns
assert user.dn not in bar_dns
def test_group_creation(testclient, slapd_connection, logged_moderator, foo_group):
# The group does not exist
res = testclient.get("/groups", status=200)
with testclient.app.app_context():
assert Group.get("bar", conn=slapd_connection) is None
assert Group.get("foo", conn=slapd_connection) == foo_group
assert "bar" not in res.text
assert "foo" in res.text
# Fill the form for a new group
res = testclient.get("/groups/add", status=200)
res.form["name"] = "bar"
# Group has been created
res = res.form.submit(status=302).follow(status=200)
with testclient.app.app_context():
bar_group = Group.get("bar", conn=slapd_connection)
assert bar_group.name == "bar"
assert [member.dn for member in bar_group.get_members(conn=slapd_connection)] == [logged_moderator.dn] # Group cannot be empty so creator is added in it
assert "bar" in res.text
# Group name can not be edited
res = testclient.get("/groups/bar", status=200)
res.form["name"] = "bar2"
res = res.form.submit(status=200)
with testclient.app.app_context():
bar_group = Group.get("bar", conn=slapd_connection)
assert bar_group.name == "bar"
assert Group.get("bar2", conn=slapd_connection) is None
members = bar_group.get_members(conn=slapd_connection)
for member in members:
assert member.name in res.text
# TODO: Group is deleted