Skip to content
Snippets Groups Projects
Verified Commit 8e73775e authored by nd's avatar nd
Browse files

add users to roles

parent 890d55db
No related branches found
No related tags found
Loading
......@@ -560,13 +560,13 @@ valid-metaclass-classmethod-first-arg=cls
max-args=7
# Maximum number of attributes for a class (see R0902).
max-attributes=7
max-attributes=12
# Maximum number of boolean expressions in an if statement (see R0916).
max-bool-expr=5
# Maximum number of branch for function / method body.
max-branches=12
max-branches=15
# Maximum number of locals for function / method body.
max-locals=15
......
from .views import bp as bp_ui
from .models import Role, RoleGroup, RoleUser
bp = [bp_ui]
......@@ -5,7 +5,7 @@ from sqlalchemy.orm import relationship
from sqlalchemy.ext.declarative import declared_attr
from uffd.database import db
from uffd.user import User, Group
from uffd.user.models import User, Group
class Role(db.Model):
__tablename__ = 'role'
......@@ -19,10 +19,31 @@ class Role(db.Model):
self.name = name
self.description = description
def group_dns(self):
return map(attrgetter('dn'), self.groups)
@classmethod
def get_for_user(cls, user):
return Role.query.join(Role.members, aliased=True).filter_by(dn=user.dn)
def member_dns(self):
return map(attrgetter('dn'), self.members)
return list(map(attrgetter('dn'), self.members))
def add_member(self, member):
newmapping = RoleUser(member.dn, self)
self.members.append(newmapping)
def del_member(self, member):
for i in self.members:
if i.dn == member.dn:
self.members.remove(i)
break
def group_dns(self):
return list(map(attrgetter('dn'), self.groups))
def add_group(self, group):
newmapping = RoleGroup(group.dn, self)
self.groups.append(newmapping)
def del_group(self, group):
for i in self.groups:
if i.dn == group.dn:
self.groups.remove(i)
break
class LdapMapping():
id = Column(Integer(), primary_key=True, autoincrement=True)
......@@ -35,6 +56,10 @@ class LdapMapping():
return Column(ForeignKey('role.id'))
ldapclass = None
def __init__(self, dn='', role=''):
self.dn = dn
self.role = role
def get_ldap(self):
return self.ldapclass.from_ldap_dn(self.dn)
......
......@@ -2,8 +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, RoleGroup
from uffd.user import Group
from uffd.role.models import Role
from uffd.user.models import Group
from uffd.session import get_current_user, login_required, is_valid_session
from uffd.database import db
......@@ -48,17 +48,19 @@ def update(roleid=False):
role.description = request.values['description']
groups = Group.from_ldap_all()
role_group_dns = list(role.group_dns())
role_group_dns = 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)
role.add_group(group)
elif group.dn in role_group_dns:
session.delete(RoleGroup.query.filter_by(role_id=role.id, dn=group.dn).one())
role.del_group(group)
# usergroups = set()
# for role in Role.get_for_user(user).all():
# usergroups.update(role.group_dns())
# user.replace_group_dns(usergroups)
session.commit()
return redirect(url_for('role.index'))
......
from .views_user import bp as bp_user
from .views_group import bp as bp_group
from .models import User, Group
bp = [bp_user, bp_group]
from ldap3 import MODIFY_REPLACE, HASHED_SALTED_SHA512
import secrets
from ldap3 import MODIFY_REPLACE, MODIFY_DELETE, MODIFY_ADD, HASHED_SALTED_SHA512
from ldap3.utils.hashed import hashed
from flask import current_app
from uffd import ldap
class User():
def __init__(self, uid=None, loginname='', displayname='', mail='', groups=None):
def __init__(self, uid=None, loginname='', displayname='', mail='', groups=None, dn=None):
self.uid = uid
self.loginname = loginname
self.displayname = displayname
self.mail = mail
self.newpassword = None
self.dn = dn
self.groups_ldap = groups or []
self.initial_groups_ldap = groups or []
self.groups_changed = False
self._groups = None
self.newpassword = None
@classmethod
def from_ldap(cls, ldapobject):
......@@ -21,7 +27,8 @@ class User():
loginname=ldapobject['uid'].value,
displayname=ldapobject['cn'].value,
mail=ldapobject['mail'].value,
groups=ldap.get_ldap_array_attribute_safe(ldapobject, 'memberOf')
groups=ldap.get_ldap_array_attribute_safe(ldapobject, 'memberOf'),
dn=ldapobject.entry_dn,
)
@classmethod
......@@ -35,11 +42,13 @@ class User():
def to_ldap(self, new=False):
conn = ldap.get_conn()
if new:
self.uid = ldap.get_next_uid()
attributes = {
'uidNumber': ldap.get_next_uid(),
'uidNumber': self.uid,
'gidNumber': current_app.config['LDAP_USER_GID'],
'homeDirectory': '/home/'+self.loginname,
'sn': ' ',
'userPassword': hashed(HASHED_SALTED_SHA512, secrets.token_hex(128)),
# same as for update
'givenName': self.displayname,
'displayName': self.displayname,
......@@ -59,6 +68,17 @@ class User():
attributes['userPassword'] = [(MODIFY_REPLACE, [hashed(HASHED_SALTED_SHA512, self.newpassword)])]
dn = ldap.uid_to_dn(self.uid)
result = conn.modify(dn, attributes)
self.dn = dn
group_conn = ldap.get_conn()
for group in self.initial_groups_ldap:
if not group in self.groups_ldap:
group_conn.modify(group, {'uniqueMember': [(MODIFY_DELETE, [self.dn])]})
for group in self.groups_ldap:
if not group in self.initial_groups_ldap:
group_conn.modify(group, {'uniqueMember': [(MODIFY_ADD, [self.dn])]})
self.groups_changed = False
return result
def get_groups(self):
......@@ -71,6 +91,10 @@ class User():
groups.append(newgroup)
self._groups = groups
return groups
def replace_group_dns(self, values):
self._groups = None
self.groups_ldap = values
self.groups_changed = True
def is_in_group(self, name):
if not name:
......
......@@ -3,7 +3,16 @@
{% block body %}
<form action="{{ url_for("user.update", uid=user.uid) }}" method="POST">
<div class="align-self-center">
<ul class="nav nav-tabs " id="tablist" role="tablist">
<div class="float-sm-right pb-2">
<button type="submit" class="btn btn-primary"><i class="fa fa-save" aria-hidden="true"></i> Save</button>
<a href="{{ url_for("user.index") }}" class="btn btn-secondary">Cancel</a>
{% if user.uid %}
<a href="{{ url_for("user.delete", uid=user.uid) }}" class="btn btn-danger"><i class="fa fa-trash" aria-hidden="true"></i> Delete</a>
{% else %}
<a href="#" class="btn btn-danger disabled"><i class="fa fa-trash" aria-hidden="true"></i> Delete</a>
{% endif %}
</div>
<ul class="nav nav-tabs pt-2 border-0" id="tablist" role="tablist">
<li class="nav-item">
<a class="nav-link active" id="profile-tab" data-toggle="tab" href="#profile" role="tab" aria-controls="profile" aria-selected="true">Profile</a>
</li>
......@@ -14,7 +23,7 @@
<a class="nav-link" id="ldif-tab" data-toggle="tab" href="#ldif" role="tab" aria-controls="ldif" aria-selected="false">LDIF</a>
</li>
</ul>
<div class="tab-content border border-top-0 mb-2 pt-2" id="tabcontent">
<div class="tab-content border mb-2 pt-2" id="tabcontent">
<div class="tab-pane fade show active" id="profile" role="tabpanel" aria-labelledby="roles-tab">
<div class="form-group col">
<label for="user-uid">uid</label>
......@@ -60,6 +69,34 @@
<div class="tab-pane fade" id="roles" role="tabpanel" aria-labelledby="roles-tab">
<div class="form-group col">
<span>Roles:</span>
<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 role in roles %}
<tr id="role-{{ role.id }}">
<td>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="role-{{ role.id }}-checkbox" name="role-{{ role.id }}" value="1" aria-label="enabled" {% if user.dn in role.member_dns() %}checked{% endif %}>
</div>
</td>
<td>
<a href="{{ url_for("role.show", roleid=role.id) }}">
{{ role.name }}
</a>
</td>
<td>
{{ role.description }}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div class="form-group col">
<span>Resulting groups (only updated after save):</span>
......@@ -93,16 +130,6 @@
</div>
</div>
</div>
<div class="form-group col pl-0">
<button type="submit" class="btn btn-primary"><i class="fa fa-save" aria-hidden="true"></i> Save</button>
<a href="{{ url_for("user.index") }}" class="btn btn-secondary">Cancel</a>
{% if user.uid %}
<a href="{{ url_for("user.delete", uid=user.uid) }}" class="btn btn-danger"><i class="fa fa-trash" aria-hidden="true"></i> Delete</a>
{% else %}
<a href="#" class="btn btn-danger disabled"><i class="fa fa-trash" aria-hidden="true"></i> Delete</a>
{% endif %}
</div>
</div>
</form>
{% endblock %}
......@@ -3,7 +3,7 @@
{% block body %}
<div class="row">
<div class="col">
<table class="table table-striped">
<table class="table table-striped table-sm">
<thead>
<tr>
<th scope="col">uid</th>
......
......@@ -5,6 +5,8 @@ from uffd.csrf import csrf_protect
from uffd.selfservice import send_passwordreset
from uffd.ldap import get_conn, escape_filter_chars
from uffd.session import login_required, is_valid_session, get_current_user
from uffd.role.models import Role
from uffd.database import db
from .models import User
......@@ -41,7 +43,7 @@ def show(uid=None):
assert len(conn.entries) == 1
user = User.from_ldap(conn.entries[0])
ldif = conn.entries[0].entry_to_ldif()
return render_template('user.html', user=user, user_ldif=ldif)
return render_template('user.html', user=user, user_ldif=ldif, roles=Role.query.all())
@bp.route("/<int:uid>/update", methods=['POST'])
@bp.route("/new", methods=['POST'])
......@@ -60,23 +62,42 @@ def update(uid=False):
user = User.from_ldap(conn.entries[0])
if not user.set_mail(request.form['mail']):
flash('Mail is invalide.')
return redirect(url_for('.user_show'))
return redirect(url_for('user.show', uid=uid))
new_displayname = request.form['displayname'] if request.form['displayname'] else request.form['loginname']
if not user.set_displayname(new_displayname):
flash('Display name does not meet requirements')
return redirect(url_for('user.show'))
return redirect(url_for('user.show', uid=uid))
new_password = request.form.get('password')
if new_password and not is_newuser:
user.set_password(new_password)
session = db.session
roles = Role.query.all()
for role in roles:
role_member_dns = role.member_dns()
if request.values.get('role-{}'.format(role.id), False):
if user.dn in role_member_dns:
continue
role.add_member(user)
elif user.dn in role_member_dns:
role.del_member(user)
usergroups = set()
for role in Role.get_for_user(user).all():
usergroups.update(role.group_dns())
user.replace_group_dns(usergroups)
if user.to_ldap(new=is_newuser):
if is_newuser:
send_passwordreset(user.loginname)
flash('User created. We sent the user a password reset link by mail')
session.commit()
else:
flash('User updated')
session.commit()
else:
flash('Error updating user: {}'.format(conn.result['message']))
return redirect(url_for('user.index'))
session.rollback()
return redirect(url_for('user.show', uid=user.uid))
@bp.route("/<int:uid>/del")
@csrf_protect(blueprint=bp)
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment