From 809f5c17c4bbfbebe9d6de969ef34ba9592984c9 Mon Sep 17 00:00:00 2001
From: nd <git@notandy.de>
Date: Sun, 23 Aug 2020 16:48:02 +0200
Subject: [PATCH] added user csv import

---
 uffd/user/templates/user_list.html | 38 ++++++++++++++++-
 uffd/user/views_user.py            | 67 +++++++++++++++++++++++++++---
 2 files changed, 98 insertions(+), 7 deletions(-)

diff --git a/uffd/user/templates/user_list.html b/uffd/user/templates/user_list.html
index 8bd88a19..85da86db 100644
--- a/uffd/user/templates/user_list.html
+++ b/uffd/user/templates/user_list.html
@@ -14,6 +14,9 @@
 							<a type="button" class="btn btn-primary" href="{{ url_for("user.show") }}">
 								<i class="fa fa-plus" aria-hidden="true"></i> New
 							</a>
+							<button type="button" class="btn btn-primary" data-toggle="modal" data-target="#csvimport">
+								<i class="fa fa-file-csv" aria-hidden="true"></i> CSV import
+							</button>
 						</p>
 					</th>
 				</tr>
@@ -44,5 +47,38 @@
 			</tbody>
 		</table>
 	</div>
-</dev>
+</div>
+
+<div class="modal fade" id="csvimport" tabindex="-1" role="dialog" aria-hidden="true">
+	<form action="{{ url_for("user.csvimport") }}" method="POST">
+	<div class="modal-dialog" role="document">
+		<div class="modal-content">
+			<div class="modal-header">
+				<h5 class="modal-title" id="exampleModalLabel">Import a csv formated list of users</h5>
+				<button type="button" class="close" data-dismiss="modal" aria-label="Close">
+					<span aria-hidden="true">&times;</span>
+				</button>
+			</div>
+			<div class="modal-body">
+				<p>
+					The format should be "loginname,mailaddres,groupid1;groupid2;".
+					Neither setting the display name, nor setting roles or passwords is supported (yet).
+					Example:
+				</p>
+				<pre>
+testuser1,foobar@example.com
+testuser5,foobsdfar@example.com
+testuser5,foobadfar@example.com
+testuser2,foobaadsfr@example.com
+				</pre>
+				<textarea rows="10" class="form-control" name="csv"></textarea>
+			</div>
+			<div class="modal-footer">
+				<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
+				<button type="submit" class="btn btn-primary">Import</button>
+			</div>
+		</div>
+	</div>
+	</form>
+</div>
 {% endblock %}
diff --git a/uffd/user/views_user.py b/uffd/user/views_user.py
index c8afb560..755929b6 100644
--- a/uffd/user/views_user.py
+++ b/uffd/user/views_user.py
@@ -1,3 +1,6 @@
+import csv
+import io
+
 from flask import Blueprint, render_template, request, url_for, redirect, flash, current_app
 
 from uffd.navbar import register_navbar
@@ -81,19 +84,19 @@ def update(uid=False):
 			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()
+
+		usergroups = set()
+		for role in Role.get_for_user(user).all():
+			usergroups.update(role.group_dns())
+		user.replace_group_dns(usergroups)
+		session.commit()
 	else:
 		flash('Error updating user: {}'.format(conn.result['message']))
 		session.rollback()
@@ -120,3 +123,55 @@ def delete(uid):
 		flash('Could not delete user: {}'.format(conn.result['message']))
 		session.rollback()
 	return redirect(url_for('user.index'))
+
+@bp.route("/csv", methods=['POST'])
+@csrf_protect(blueprint=bp)
+def csvimport():
+	csvdata = request.values.get('csv')
+	if not csvdata:
+		flash('No data for csv import!')
+		return redirect(url_for('user.index'))
+
+	roles = Role.query.all()
+	usersadded = 0
+	with io.StringIO(initial_value=csvdata) as csvfile:
+		csvreader = csv.reader(csvfile)
+		for row in csvreader:
+			if not len(row) == 3:
+				flash("invalid line, ignored : {}".format(row))
+				continue
+			newuser = User()
+			if not newuser.set_loginname(row[0]) or not newuser.set_displayname(row[0]):
+				flash("invalid login name, skipped : {}".format(row))
+				continue
+			if not newuser.set_mail(row[1]):
+				flash("invalid mail address, skipped : {}".format(row))
+				continue
+			session = db.session
+
+			for role in roles:
+				role_member_dns = role.member_dns()
+				if (str(role.id) in row[2].split(';')) or role.name in current_app.config["ROLES_BASEROLES"]:
+					if newuser.dn in role_member_dns:
+						continue
+					role.add_member(newuser)
+
+			result = newuser.to_ldap(new=True)
+			print(result)
+			if result:
+				send_passwordreset(newuser.loginname)
+
+				usergroups = set()
+				for role in Role.get_for_user(newuser).all():
+					usergroups.update(role.group_dns())
+				newuser.replace_group_dns(usergroups)
+
+				session.commit()
+				usersadded += 1
+			else:
+				flash('Error adding user {}'.format(row[0]))
+				session.rollback()
+				continue
+
+	flash('Added {} new users'.format(usersadded))
+	return redirect(url_for('user.index'))
-- 
GitLab