From 03562691897ee80d355a38e500a78e2acfee140a Mon Sep 17 00:00:00 2001
From: nd <git@notandy.de>
Date: Sun, 19 Jul 2020 14:04:38 +0200
Subject: [PATCH] adding groups to roles works now

---
 .pylintrc                     |  2 +-
 uffd/role/models.py           | 16 +++++++++++---
 uffd/role/templates/role.html | 41 +++++++++++++++++++++++++++++++++++
 uffd/role/views.py            | 21 +++++++++++++++---
 uffd/user/models.py           | 13 ++++++++++-
 uffd/user/views_group.py      |  7 +-----
 6 files changed, 86 insertions(+), 14 deletions(-)

diff --git a/.pylintrc b/.pylintrc
index 0d8266ca..038648ff 100644
--- a/.pylintrc
+++ b/.pylintrc
@@ -216,7 +216,7 @@ contextmanager-decorators=contextlib.contextmanager
 # List of members which are set dynamically and missed by pylint inference
 # system, and so shouldn't trigger E1101 when accessed. Python regular
 # expressions are accepted.
-generated-members=query
+generated-members=query,UniqueConstraint
 
 # Tells whether missing members accessed in mixin class should be ignored. A
 # mixin class is detected if its name ends with "mixin" (case insensitive).
diff --git a/uffd/role/models.py b/uffd/role/models.py
index 3b4ca85f..596f2e0d 100644
--- a/uffd/role/models.py
+++ b/uffd/role/models.py
@@ -1,25 +1,35 @@
+from operator import attrgetter
+
 from sqlalchemy import Column, String, Integer, Text, ForeignKey
 from sqlalchemy.orm import relationship
 from sqlalchemy.ext.declarative import declared_attr
 
 from uffd.database import db
-from uffd.user.models import User, Group
+from uffd.user import User, Group
 
 class Role(db.Model):
 	__tablename__ = 'role'
 	id = Column(Integer(), primary_key=True, autoincrement=True)
 	name = Column(String(32), unique=True)
 	description = Column(Text())
-	members = relationship("RoleUser", backref="role")
-	groups = relationship("RoleGroup", backref="role")
+	members = relationship("RoleUser", backref="role", cascade="all, delete-orphan")
+	groups = relationship("RoleGroup", backref="role", cascade="all, delete-orphan")
 
 	def __init__(self, name='', description=''):
 		self.name = name
 		self.description = description
 
+	def group_dns(self):
+		return map(attrgetter('dn'), self.groups)
+	def member_dns(self):
+		return map(attrgetter('dn'), self.members)
+
 class LdapMapping():
 	id = Column(Integer(), primary_key=True, autoincrement=True)
 	dn = Column(String(128))
+	__table_args__ = (
+		db.UniqueConstraint('dn', 'role_id'),
+	)
 	@declared_attr
 	def role_id(self):
 		return Column(ForeignKey('role.id'))
diff --git a/uffd/role/templates/role.html b/uffd/role/templates/role.html
index 45672daf..cdddccee 100644
--- a/uffd/role/templates/role.html
+++ b/uffd/role/templates/role.html
@@ -15,6 +15,47 @@
 		<small class="form-text text-muted">
 		</small>
 	</div>
+	<div class="form-group col">
+		<p>
+			Included groups
+		</p>
+		<table class="table table-striped table-sm">
+			<thead>
+				<tr>
+					<th scope="col"></th>
+					<th scope="col">name</th>
+					<th scope="col">description</th>
+				</tr>
+			</thead>
+			<tbody>
+				{% for group in groups|sort(attribute="name") %}
+				<tr id="group-{{ group.gid }}">
+					<td>
+						<div class="form-check">
+							<input class="form-check-input" type="checkbox" id="group-{{ group.gid }}-checkbox" name="group-{{ group.gid }}" value="1" aria-label="enabled" {% if group.dn in role.group_dns() %}checked{% endif %}>
+						</div>
+					</td>
+					<td>
+						<a href="{{ url_for("group.show", gid=group.gid) }}">
+							{{ group.name }}
+						</a>
+					</td>
+					<td>
+						{{ group.description }}
+					</td>
+				</tr>
+				{% endfor %}
+			</tbody>
+		</table>
+	</div>
+	<div class="form-group col">
+		<p>
+			Members
+		</p>
+		{% for member in role.members %}
+			{{ member.dn }}
+		{% endfor %}
+	</div>
 
 	<div class="form-group col">
 		<button type="submit" class="btn btn-primary"><i class="fa fa-save" aria-hidden="true"></i> Save</button>
diff --git a/uffd/role/views.py b/uffd/role/views.py
index 9863d565..737d1c6b 100644
--- a/uffd/role/views.py
+++ b/uffd/role/views.py
@@ -2,7 +2,8 @@ from flask import Blueprint, render_template, request, url_for, redirect, flash,
 
 from uffd.navbar import register_navbar
 from uffd.csrf import csrf_protect
-from uffd.role.models import Role
+from uffd.role.models import Role, RoleGroup
+from uffd.user import Group
 from uffd.session import get_current_user, login_required, is_valid_session
 from uffd.database import db
 
@@ -29,7 +30,8 @@ def show(roleid=False):
 		role = Role()
 	else:
 		role = Role.query.get_or_404(roleid)
-	return render_template('role.html', role=role)
+	groups = Group.from_ldap_all()
+	return render_template('role.html', role=role, groups=groups)
 
 @bp.route("/<int:roleid>/update", methods=['POST'])
 @bp.route("/new", methods=['POST'])
@@ -44,7 +46,20 @@ def update(roleid=False):
 		role = session.query(Role).get_or_404(roleid)
 	role.name = request.values['name']
 	role.description = request.values['description']
-	print(role)
+
+	groups = Group.from_ldap_all()
+	role_group_dns = list(role.group_dns())
+	for group in groups:
+		if request.values.get('group-{}'.format(group.gid), False):
+			if group.dn in role_group_dns:
+				continue
+			newmapping = RoleGroup()
+			newmapping.dn = group.dn
+			newmapping.role = role
+			session.add(newmapping)
+		elif group.dn in role_group_dns:
+			session.delete(RoleGroup.query.filter_by(role_id=role.id, dn=group.dn).one())
+
 	session.commit()
 	return redirect(url_for('role.index'))
 
diff --git a/uffd/user/models.py b/uffd/user/models.py
index fa3dbf66..7da2cca0 100644
--- a/uffd/user/models.py
+++ b/uffd/user/models.py
@@ -106,12 +106,13 @@ class User():
 		return True
 
 class Group():
-	def __init__(self, gid=None, name='', members=None, description=''):
+	def __init__(self, gid=None, name='', members=None, description='', dn=None):
 		self.gid = gid
 		self.name = name
 		self.members_ldap = members
 		self._members = None
 		self.description = description
+		self.dn = dn
 
 	@classmethod
 	def from_ldap(cls, ldapobject):
@@ -120,6 +121,7 @@ class Group():
 				name=ldapobject['cn'].value,
 				members=ldap.get_ldap_array_attribute_safe(ldapobject, 'uniqueMember'),
 				description=ldap.get_ldap_attribute_safe(ldapobject, 'description') or '',
+				dn=ldapobject.entry_dn,
 			)
 
 	@classmethod
@@ -130,6 +132,15 @@ class Group():
 			return None
 		return Group.from_ldap(conn.entries[0])
 
+	@classmethod
+	def from_ldap_all(cls):
+		conn = ldap.get_conn()
+		conn.search(current_app.config["LDAP_BASE_GROUPS"], '(objectclass=groupOfUniqueNames)')
+		groups = []
+		for i in conn.entries:
+			groups.append(Group.from_ldap(i))
+		return groups
+
 	def to_ldap(self, new):
 		pass
 
diff --git a/uffd/user/views_group.py b/uffd/user/views_group.py
index 04351d41..8c1c805e 100644
--- a/uffd/user/views_group.py
+++ b/uffd/user/views_group.py
@@ -20,12 +20,7 @@ def group_acl_check():
 @bp.route("/")
 @register_navbar('Groups', icon='layer-group', blueprint=bp, visible=group_acl_check)
 def index():
-	conn = get_conn()
-	conn.search(current_app.config["LDAP_BASE_GROUPS"], '(objectclass=groupOfUniqueNames)')
-	groups = []
-	for i in conn.entries:
-		groups.append(Group.from_ldap(i))
-	return render_template('group_list.html', groups=groups)
+	return render_template('group_list.html', groups=Group.from_ldap_all())
 
 @bp.route("/<int:gid>")
 def show(gid):
-- 
GitLab