From 193a2eff0a730dca7187fff43e8f28e0cc065ca0 Mon Sep 17 00:00:00 2001 From: Julian Rother <julianr@fsmpi.rwth-aachen.de> Date: Sat, 12 Jun 2021 18:59:54 +0200 Subject: [PATCH] Add service users, closes #55 Co-authored-by: psy <psy@darmstadt.ccc.de> --- tests/test_user.py | 32 +++++++++++++++++++++++++++++- uffd/default_config.cfg | 2 ++ uffd/user/models.py | 29 +++++++++++++++++++++------ uffd/user/templates/user.html | 19 +++++++++++++++--- uffd/user/templates/user_list.html | 5 ++++- uffd/user/views_user.py | 13 +++++++++--- 6 files changed, 86 insertions(+), 14 deletions(-) diff --git a/tests/test_user.py b/tests/test_user.py index 8fae44db..baedf5bc 100644 --- a/tests/test_user.py +++ b/tests/test_user.py @@ -87,16 +87,46 @@ class TestUserViews(UffdTestCase): user = User.query.get('uid=newuser,ou=users,dc=example,dc=com') roles = sorted([r.name for r in user.roles]) self.assertIsNotNone(user) + self.assertFalse(user.is_service_user) self.assertEqual(user.loginname, 'newuser') self.assertEqual(user.displayname, 'New User') self.assertEqual(user.mail, 'newuser@example.com') self.assertTrue(user.uid) role1 = Role(name='role1') - print('test_new', role1.db_members, role1.members, user.roles) self.assertEqual(roles, ['base', 'role1']) # TODO: confirm Mail is send, login not yet possible #self.assertTrue(ldap.test_user_bind(user.dn, 'newpassword')) + def test_new_service(self): + db.session.add(Role(name='base')) + role1 = Role(name='role1') + db.session.add(role1) + role2 = Role(name='role2') + db.session.add(role2) + db.session.commit() + role1_id = role1.id + r = self.client.get(path=url_for('user.show'), follow_redirects=True) + dump('user_new_service', r) + self.assertEqual(r.status_code, 200) + self.assertIsNone(User.query.get('uid=newuser,ou=users,dc=example,dc=com')) + r = self.client.post(path=url_for('user.update'), + data={'loginname': 'newuser', 'mail': 'newuser@example.com', 'displayname': 'New User', + f'role-{role1_id}': '1', 'password': 'newpassword', 'serviceaccount': '1'}, follow_redirects=True) + dump('user_new_submit', r) + self.assertEqual(r.status_code, 200) + user = User.query.get('uid=newuser,ou=users,dc=example,dc=com') + roles = sorted([r.name for r in user.roles]) + self.assertIsNotNone(user) + self.assertTrue(user.is_service_user) + self.assertEqual(user.loginname, 'newuser') + self.assertEqual(user.displayname, 'New User') + self.assertEqual(user.mail, 'newuser@example.com') + self.assertTrue(user.uid) + role1 = Role(name='role1') + self.assertEqual(roles, ['role1']) + # TODO: confirm Mail is send, login not yet possible + #self.assertTrue(ldap.test_user_bind(user.dn, 'newpassword')) + def test_new_invalid_loginname(self): r = self.client.post(path=url_for('user.update'), data={'loginname': '!newuser', 'mail': 'newuser@example.com', 'displayname': 'New User', diff --git a/uffd/default_config.cfg b/uffd/default_config.cfg index 55962602..022657fe 100644 --- a/uffd/default_config.cfg +++ b/uffd/default_config.cfg @@ -3,6 +3,8 @@ LDAP_USER_SEARCH_FILTER=[("objectClass", "person")] LDAP_USER_OBJECTCLASSES=["top", "inetOrgPerson", "organizationalPerson", "person", "posixAccount"] LDAP_USER_MIN_UID=10000 LDAP_USER_MAX_UID=18999 +LDAP_USER_SERVICE_MIN_UID=19000 +LDAP_USER_SERVICE_MAX_UID=19999 LDAP_USER_GID=20001 LDAP_USER_DN_ATTRIBUTE="uid" LDAP_USER_UID_ATTRIBUTE="uidNumber" diff --git a/uffd/user/models.py b/uffd/user/models.py index c0ce603e..2749884f 100644 --- a/uffd/user/models.py +++ b/uffd/user/models.py @@ -8,13 +8,18 @@ from ldap3.utils.hashed import hashed, HASHED_SALTED_SHA512 from uffd.ldap import ldap from uffd.lazyconfig import lazyconfig_str, lazyconfig_list -def get_next_uid(): - max_uid = current_app.config['LDAP_USER_MIN_UID'] +def get_next_uid(service=False): + if service: + new_uid_min = current_app.config['LDAP_USER_SERVICE_MIN_UID'] + new_uid_max = current_app.config['LDAP_USER_SERVICE_MAX_UID'] + else: + new_uid_min = current_app.config['LDAP_USER_MIN_UID'] + new_uid_max = current_app.config['LDAP_USER_MAX_UID'] + next_uid = new_uid_min for user in User.query.all(): - if user.uid <= current_app.config['LDAP_USER_MAX_UID']: - max_uid = max(user.uid, max_uid) - next_uid = max_uid + 1 - if next_uid > current_app.config['LDAP_USER_MAX_UID']: + if user.uid <= new_uid_max: + next_uid = max(next_uid, user.uid + 1) + if next_uid > new_uid_max: raise Exception('No free uid found') return next_uid @@ -50,6 +55,18 @@ class BaseUser(ldap.Model): def group_dns(self): return [group.dn for group in self.groups] + @property + def is_service_user(self): + if self.uid is None: + return None + return self.uid >= current_app.config['LDAP_USER_SERVICE_MIN_UID'] and self.uid <= current_app.config['LDAP_USER_SERVICE_MAX_UID'] + + @is_service_user.setter + def is_service_user(self, value): + assert self.uid is None + if value: + self.uid = get_next_uid(service=True) + def add_default_attributes(self): for name, values in current_app.config['LDAP_USER_DEFAULT_ATTRIBUTES'].items(): if self.ldap_object.getattr(name): diff --git a/uffd/user/templates/user.html b/uffd/user/templates/user.html index b2feedf9..d638f7fc 100644 --- a/uffd/user/templates/user.html +++ b/uffd/user/templates/user.html @@ -27,13 +27,26 @@ <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> + <label for="user-uid"> + uid + {% if user.is_service_user %} + <span class="badge badge-secondary">service</span> + {% endif %} + </label> {% if user.uid %} <input type="number" class="form-control" id="user-uid" name="uid" value="{{ user.uid or '' }}" readonly> {% else %} <input type="text" class="form-control" id="user-uid" name="uid" placeholder="will be choosen" readonly> {% endif %} </div> + {% if not user.uid %} + <div class="form-group col"> + <div class="form-check"> + <input class="form-check-input" type="checkbox" id="user-serviceaccount" name="serviceaccount" value="1" aria-label="enabled"> + <label class="form-check-label" for="user-serviceaccount">Service User</label> + </div> + </div> + {% endif %} <div class="form-group col"> <label for="user-loginname">Login Name</label> <input type="text" class="form-control" id="user-loginname" name="loginname" value="{{ user.loginname or '' }}" {% if user.uid %}readonly{% endif %}> @@ -92,8 +105,8 @@ <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 in role.members or role.name in config["ROLES_BASEROLES"] %}checked {% endif %} - {% if role.name in config["ROLES_BASEROLES"] %}disabled {% endif %}> + {% if user in role.members %}checked {% endif %} + {% if role.name in config["ROLES_BASEROLES"] and not user.is_service_user %}disabled {% endif %}> </div> </td> <td> diff --git a/uffd/user/templates/user_list.html b/uffd/user/templates/user_list.html index ddcaab3e..93d7da52 100644 --- a/uffd/user/templates/user_list.html +++ b/uffd/user/templates/user_list.html @@ -30,12 +30,15 @@ <a href="{{ url_for("user.show", uid=user.uid) }}"> {{ user.loginname }} </a> + {% if user.is_service_user %} + <span class="badge badge-secondary">service</span> + {% endif %} </td> <td> {{ user.displayname }} </td> <td> - {% for role in user.roles|sort(attribute="name") if not role.name in config["ROLES_BASEROLES"] %} + {% for role in user.roles|sort(attribute="name") if not role.name in config["ROLES_BASEROLES"] or user.is_service_user %} <a href="{{ url_for("role.show", roleid=role.id) }}">{{ role.name }}</a>{% if not loop.last %}, {% endif %} {% endfor %} </td> diff --git a/uffd/user/views_user.py b/uffd/user/views_user.py index e25f89e7..71f75d8e 100644 --- a/uffd/user/views_user.py +++ b/uffd/user/views_user.py @@ -42,6 +42,8 @@ def update(uid=None): if uid is None: user = User() ignore_blacklist = request.form.get('ignore-loginname-blacklist', False) + if request.form.get('serviceaccount'): + user.is_service_user = True if not user.set_loginname(request.form['loginname'], ignore_blacklist=ignore_blacklist): flash('Login name does not meet requirements') return redirect(url_for('user.show')) @@ -60,14 +62,19 @@ def update(uid=None): ldap.session.add(user) user.roles.clear() for role in Role.query.all(): - if request.values.get('role-{}'.format(role.id), False) or role.name in current_app.config["ROLES_BASEROLES"]: + if request.values.get('role-{}'.format(role.id), False): + user.roles.add(role) + elif not user.is_service_user and role.name in current_app.config["ROLES_BASEROLES"]: user.roles.add(role) user.update_groups() ldap.session.commit() db.session.commit() if uid is None: - send_passwordreset(user, new=True) - flash('User created. We sent the user a password reset link by mail') + if user.is_service_user: + flash('Service user created') + else: + send_passwordreset(user, new=True) + flash('User created. We sent the user a password reset link by mail') else: flash('User updated') return redirect(url_for('user.show', uid=user.uid)) -- GitLab