From cec51a3d12f1f9db8accce1975d4c1405ecaaf65 Mon Sep 17 00:00:00 2001
From: Julian Rother <julianr@fsmpi.rwth-aachen.de>
Date: Tue, 23 Feb 2021 23:40:37 +0100
Subject: [PATCH] Made all ldap parameters configurable

---
 deps/ldapalchemy        |  2 +-
 uffd/default_config.cfg | 54 +++++++++++++++++++++++++---------------
 uffd/mail/models.py     | 16 ++++++------
 uffd/user/models.py     | 55 +++++++++++++++++++++++++----------------
 4 files changed, 77 insertions(+), 50 deletions(-)

diff --git a/deps/ldapalchemy b/deps/ldapalchemy
index d2c13338..2358086f 160000
--- a/deps/ldapalchemy
+++ b/deps/ldapalchemy
@@ -1 +1 @@
-Subproject commit d2c133381a8e536ee433e5ab305fc24146c0feb9
+Subproject commit 2358086f5b8184fe89bbb69334a5c80e9b20cfbf
diff --git a/uffd/default_config.cfg b/uffd/default_config.cfg
index b4c84145..91d665ff 100644
--- a/uffd/default_config.cfg
+++ b/uffd/default_config.cfg
@@ -1,35 +1,50 @@
-LDAP_BASE_USER="ou=users,dc=example,dc=com"
-LDAP_BASE_GROUPS="ou=groups,dc=example,dc=com"
-LDAP_BASE_MAIL="ou=postfix,dc=example,dc=com"
+LDAP_USER_SEARCH_BASE="ou=users,dc=example,dc=com"
+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_GID=20001
+LDAP_USER_DN_ATTRIBUTE="uid"
+LDAP_USER_UID_ATTRIBUTE="uidNumber"
+LDAP_USER_UID_ALIASES=[]
+LDAP_USER_LOGINNAME_ATTRIBUTE="uid"
+LDAP_USER_LOGINNAME_ALIASES=[]
+LDAP_USER_DISPLAYNAME_ATTRIBUTE="cn"
+LDAP_USER_DISPLAYNAME_ALIASES=["givenName", "displayName"]
+LDAP_USER_MAIL_ATTRIBUTE="mail"
+LDAP_USER_MAIL_ALIASES=[]
+LDAP_USER_DEFAULT_ATTRIBUTES={
+	"sn": " ",
+	"homeDirectory": "/home/{loginname}",
+	"gidNumber": LDAP_USER_GID
+}
 
-LDAP_FILTER_USER=[("objectClass","person")]
-LDAP_FILTER_GROUP=[("objectClass","groupOfUniqueNames")]
-LDAP_FILTER_MAIL=[("objectClass","postfixVirtual")]
+LDAP_GROUP_SEARCH_BASE="ou=groups,dc=example,dc=com"
+LDAP_GROUP_SEARCH_FILTER=[("objectClass","groupOfUniqueNames")]
+LDAP_GROUP_GID_ATTRIBUTE="gidNumber"
+LDAP_GROUP_NAME_ATTRIBUTE="cn"
+LDAP_GROUP_DESCRIPTION_ATTRIBUTE="description"
+LDAP_GROUP_MEMBER_ATTRIBUTE="uniqueMember"
+
+LDAP_MAIL_SEARCH_BASE="ou=postfix,dc=example,dc=com"
+LDAP_MAIL_SEARCH_FILTER=[("objectClass","postfixVirtual")]
+LDAP_MAIL_OBJECTCLASSES=["top", "postfixVirtual"]
+LDAP_MAIL_DN_ATTRIBUTE="uid"
+LDAP_MAIL_UID_ATTRIBUTE="uid"
+LDAP_MAIL_RECEIVERS_ATTRIBUTE="mailacceptinggeneralid"
+LDAP_MAIL_DESTINATIONS_ATTRIBUTE="maildrop"
 
 LDAP_SERVICE_BIND_DN=""
 LDAP_SERVICE_BIND_PASSWORD=""
 LDAP_SERVICE_URL="ldapi:///"
 LDAP_SERVICE_USE_STARTTLS=True
 
-LDAP_USER_OBJECTCLASSES=["top", "inetOrgPerson", "organizationalPerson", "person", "posixAccount"]
-LDAP_USER_ATTRIBUTE_UID="uidNumber"
-LDAP_USER_ATTRIBUTE_DISPLAYNAME="cn"
-LDAP_USER_ATTRIBUTE_MAIL="mail"
-# The User class gets filled by which LDAP attribute and to type (single/list)
-LDAP_USER_ATTRIBUTE_EXTRA={
-#"phone": {"type": "single", "name": "mobile"},
-}
-LDAP_USER_GID=20001
-LDAP_USER_MIN_UID=10000
-LDAP_USER_MAX_UID=18999
-
 SESSION_LIFETIME_SECONDS=3600
 # CSRF protection
 SESSION_COOKIE_SECURE=True
 SESSION_COOKIE_HTTPONLY=True
 SESSION_COOKIE_SAMESITE='Strict'
 
-
 ACL_ADMIN_GROUP="uffd_admin"
 ACL_SELFSERVICE_GROUP="uffd_access"
 
@@ -39,7 +54,6 @@ MAIL_USERNAME='yourId@example.com'
 MAIL_PASSWORD='*****'
 MAIL_USE_STARTTLS=True
 MAIL_FROM_ADDRESS='foo@bar.com'
-MAIL_LDAP_OBJECTCLASSES=["top", "postfixVirtual"]
 
 #MFA_ICON_URL = 'https://example.com/logo.png'
 #MFA_RP_ID = 'example.com' # If unset, hostname from current request is used
diff --git a/uffd/mail/models.py b/uffd/mail/models.py
index 73f998c2..1791170a 100644
--- a/uffd/mail/models.py
+++ b/uffd/mail/models.py
@@ -2,12 +2,12 @@ from uffd.ldap import ldap
 from uffd.lazyconfig import lazyconfig_str, lazyconfig_list
 
 class Mail(ldap.Model):
-	ldap_search_base = lazyconfig_str('LDAP_BASE_MAIL')
-	ldap_filter_params = lazyconfig_list('LDAP_FILTER_MAIL')
-	ldap_object_classes = lazyconfig_list('MAIL_LDAP_OBJECTCLASSES')
-	ldap_dn_attribute = 'uid'
-	ldap_dn_base = lazyconfig_str('LDAP_BASE_MAIL')
+	ldap_search_base = lazyconfig_str('LDAP_MAIL_SEARCH_BASE')
+	ldap_filter_params = lazyconfig_list('LDAP_MAIL_SEARCH_FILTER')
+	ldap_object_classes = lazyconfig_list('LDAP_MAIL_OBJECTCLASSES')
+	ldap_dn_attribute = lazyconfig_str('LDAP_MAIL_DN_ATTRIBUTE')
+	ldap_dn_base = lazyconfig_str('LDAP_MAIL_SEARCH_BASE')
 
-	uid = ldap.Attribute('uid')
-	receivers = ldap.Attribute('mailacceptinggeneralid', multi=True)
-	destinations = ldap.Attribute('maildrop', multi=True)
+	uid = ldap.Attribute(lazyconfig_str('LDAP_MAIL_UID_ATTRIBUTE'))
+	receivers = ldap.Attribute(lazyconfig_str('LDAP_MAIL_RECEIVERS_ATTRIBUTE'), multi=True)
+	destinations = ldap.Attribute(lazyconfig_str('LDAP_MAIL_DESTINATIONS_ATTRIBUTE'), multi=True)
diff --git a/uffd/user/models.py b/uffd/user/models.py
index 7808a184..14435f00 100644
--- a/uffd/user/models.py
+++ b/uffd/user/models.py
@@ -17,29 +17,40 @@ def get_next_uid():
 		raise Exception('No free uid found')
 	return next_uid
 
-class User(ldap.Model):
-	ldap_search_base = lazyconfig_str('LDAP_BASE_USER')
-	ldap_filter_params = lazyconfig_list('LDAP_FILTER_USER')
+class DictView:
+	def __init__(self, obj):
+		self.obj = obj
+
+	def __getitem__(self, key):
+		return getattr(self.obj, key)
+
+class BaseUser(ldap.Model):
+	ldap_search_base = lazyconfig_str('LDAP_USER_SEARCH_BASE')
+	ldap_filter_params = lazyconfig_list('LDAP_USER_SEARCH_FILTER')
 	ldap_object_classes = lazyconfig_list('LDAP_USER_OBJECTCLASSES')
-	ldap_dn_base = lazyconfig_str('LDAP_BASE_USER')
-	ldap_dn_attribute = 'uid'
+	ldap_dn_base = lazyconfig_str('LDAP_USER_SEARCH_BASE')
+	ldap_dn_attribute = lazyconfig_str('LDAP_USER_DN_ATTRIBUTE')
 
-	uid = ldap.Attribute(lazyconfig_str('LDAP_USER_ATTRIBUTE_UID'), default=get_next_uid)
-	loginname = ldap.Attribute('uid')
-	displayname = ldap.Attribute(lazyconfig_str('LDAP_USER_ATTRIBUTE_DISPLAYNAME'), aliases=['givenName', 'displayName'])
-	mail = ldap.Attribute(lazyconfig_str('LDAP_USER_ATTRIBUTE_MAIL'))
+	uid = ldap.Attribute(lazyconfig_str('LDAP_USER_UID_ATTRIBUTE'), default=get_next_uid, aliases=lazyconfig_list('LDAP_USER_UID_ALIASES'))
+	loginname = ldap.Attribute(lazyconfig_str('LDAP_USER_LOGINNAME_ATTRIBUTE'), aliases=lazyconfig_list('LDAP_USER_LOGINNAME_ALIASES'))
+	displayname = ldap.Attribute(lazyconfig_str('LDAP_USER_DISPLAYNAME_ATTRIBUTE'), aliases=lazyconfig_list('LDAP_USER_DISPLAYNAME_ALIASES'))
+	mail = ldap.Attribute(lazyconfig_str('LDAP_USER_MAIL_ATTRIBUTE'), aliases=lazyconfig_list('LDAP_USER_MAIL_ALIASES'))
 	pwhash = ldap.Attribute('userPassword', default=lambda: hashed(HASHED_SALTED_SHA512, secrets.token_hex(128)))
 
 	groups = [] # Shuts up pylint, overwritten by back-reference
 	roles = [] # Shuts up pylint, overwritten by back-reference
 
 	def dummy_attribute_defaults(self):
-		if self.ldap_object.getattr('sn') == []:
-			self.ldap_object.setattr('sn', [' '])
-		if self.ldap_object.getattr('homeDirectory') == []:
-			self.ldap_object.setattr('homeDirectory', ['/home/%s'%self.loginname])
-		if self.ldap_object.getattr('gidNumber') == []:
-			self.ldap_object.setattr('gidNumber', [current_app.config['LDAP_USER_GID']])
+		for name, patterns in current_app.config['LDAP_USER_DEFAULT_ATTRIBUTES'].items():
+			if not isinstance(patterns, list):
+				patterns = [patterns]
+			values = []
+			for pattern in patterns:
+				if isinstance(pattern, str):
+					values.append(pattern.format_map(DictView(self)))
+				else:
+					values.append(pattern)
+			self.ldap_object.setattr(name, values)
 
 	ldap_add_hooks = ldap.Model.ldap_add_hooks + (dummy_attribute_defaults,)
 
@@ -101,13 +112,15 @@ class User(ldap.Model):
 		self.mail = value
 		return True
 
+User = BaseUser
+
 class Group(ldap.Model):
-	ldap_search_base = lazyconfig_str('LDAP_BASE_GROUPS')
-	ldap_filter_params = lazyconfig_list('LDAP_FILTER_GROUP')
+	ldap_search_base = lazyconfig_str('LDAP_GROUP_SEARCH_BASE')
+	ldap_filter_params = lazyconfig_list('LDAP_GROUP_SEARCH_FILTER')
 
-	gid = ldap.Attribute('gidNumber')
-	name = ldap.Attribute('cn')
-	description = ldap.Attribute('description', default='')
-	members = ldap.Relationship('uniqueMember', User, backref='groups')
+	gid = ldap.Attribute(lazyconfig_str('LDAP_GROUP_GID_ATTRIBUTE'))
+	name = ldap.Attribute(lazyconfig_str('LDAP_GROUP_NAME_ATTRIBUTE'))
+	description = ldap.Attribute(lazyconfig_str('LDAP_GROUP_DESCRIPTION_ATTRIBUTE'), default='')
+	members = ldap.Relationship(lazyconfig_str('LDAP_GROUP_MEMBER_ATTRIBUTE'), User, backref='groups')
 
 	roles = [] # Shuts up pylint, overwritten by back-reference
-- 
GitLab