Skip to content
Snippets Groups Projects
Commit 7bd585ca authored by sistason's avatar sistason
Browse files

Changes User() to _User and User=_User, to overwrite the User class in an own...

Changes User() to _User and User=_User, to overwrite the User class in an own blueprint Added LDAP_USER_ATTRIBUTE_MAPPING to be able to make the ldap attribute names configurable Added LDAP_USER_FILTER to configure the filter instead of the default (objectClass=person) Added LDAP_GROUP_FILTER to configure the filter instead of the default (objectClass=groupOfUniqueNames)
parent d8fc586c
No related branches found
No related tags found
No related merge requests found
......@@ -5,12 +5,23 @@ LDAP_BASE_MAIL="ou=postfix,dc=example,dc=com"
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_FILTER="(objectClass=person)"
LDAP_USER_GID=20001
LDAP_USER_MIN_UID=10000
LDAP_USER_MAX_UID=18999
LDAP_GROUP_FILTER='(objectClass=groupOfUniqueNames)'
SESSION_LIFETIME_SECONDS=3600
# CSRF protection
SESSION_COOKIE_SECURE=True
......
......@@ -4,7 +4,7 @@ from flask import Blueprint, current_app
from ldap3.utils.conv import escape_filter_chars
from ldap3.core.exceptions import LDAPBindError, LDAPCursorError, LDAPPasswordIsMandatoryError
from ldap3 import Server, Connection, ALL, ALL_ATTRIBUTES, ALL_OPERATIONAL_ATTRIBUTES, MOCK_SYNC
from ldap3 import Server, Connection, ALL, ALL_ATTRIBUTES, ALL_OPERATIONAL_ATTRIBUTES, MOCK_SYNC, AUTO_BIND_TLS_BEFORE_BIND
bp = Blueprint("ldap", __name__)
......@@ -33,7 +33,9 @@ def service_conn():
if current_app.config.get('LDAP_SERVICE_MOCK', False):
return get_mock_conn()
server = Server(current_app.config["LDAP_SERVICE_URL"], get_info=ALL)
return fix_connection(Connection(server, current_app.config["LDAP_SERVICE_BIND_DN"], current_app.config["LDAP_SERVICE_BIND_PASSWORD"], auto_bind=True))
return fix_connection(Connection(server, current_app.config["LDAP_SERVICE_BIND_DN"],
current_app.config["LDAP_SERVICE_BIND_PASSWORD"],
auto_bind=AUTO_BIND_TLS_BEFORE_BIND if current_app.config["LDAP_SERVICE_USE_STARTTLS"] else True))
def user_conn(loginname, password):
if not loginname_is_safe(loginname):
......@@ -53,7 +55,8 @@ def user_conn(loginname, password):
return get_mock_conn()
server = Server(current_app.config["LDAP_SERVICE_URL"], get_info=ALL)
try:
return fix_connection(Connection(server, loginname_to_dn(loginname), password, auto_bind=True))
return fix_connection(Connection(server, loginname_to_dn(loginname), password,
auto_bind=AUTO_BIND_TLS_BEFORE_BIND if current_app.config["LDAP_SERVICE_USE_STARTTLS"] else True))
except (LDAPBindError, LDAPPasswordIsMandatoryError):
return False
......@@ -62,7 +65,8 @@ def get_conn():
def uid_to_dn(uid):
conn = get_conn()
conn.search(current_app.config["LDAP_BASE_USER"], '(&(objectclass=person)(uidNumber={}))'.format(escape_filter_chars(uid)))
conn.search(current_app.config["LDAP_BASE_USER"],
'(&{}(uidNumber={}))'.format(current_app.config["LDAP_USER_FILTER"], escape_filter_chars(uid)))
if not len(conn.entries) == 1:
return None
return conn.entries[0].entry_dn
......@@ -90,7 +94,7 @@ def mailname_is_safe(value):
def get_next_uid():
conn = get_conn()
conn.search(current_app.config["LDAP_BASE_USER"], '(objectclass=person)')
conn.search(current_app.config["LDAP_BASE_USER"], current_app.config["LDAP_USER_FILTER"])
max_uid = current_app.config["LDAP_USER_MIN_UID"]
for i in conn.entries:
# skip out of range entries
......@@ -106,7 +110,7 @@ def get_next_uid():
def get_ldap_attribute_safe(ldapobject, attribute):
try:
result = ldapobject[attribute].value if attribute in ldapobject else None
result = ldapobject[attribute].value if attribute in ldapobject else None
# we have to catch LDAPCursorError here, because ldap3 in older versions has a broken __contains__ function
# see https://github.com/cannatag/ldap3/issues/493
# fixed in version 2.5
......
......@@ -37,7 +37,7 @@ def login():
return render_template('login.html', ref=request.values.get('ref'))
conn = user_conn(username, password)
if conn:
conn.search(conn.user, '(objectClass=person)')
conn.search(conn.user, current_app.config["LDAP_USER_FILTER"])
if not conn or len(conn.entries) != 1:
login_ratelimit.log(username)
host_ratelimit.log()
......
......@@ -6,15 +6,20 @@ from flask import current_app
from uffd import ldap
class User():
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
class BaseUser:
def __init__(self, attributes=None, groups=None, dn=None):
self.uid = None
self.mail = ''
self.loginname = ''
self.displayname = ''
if attributes is not None:
for attribute_name, attribute_value in attributes.items():
setattr(self, attribute_name, attribute_value)
self.dn = dn
self.newpassword = None
self.groups_ldap = groups or []
self.initial_groups_ldap = groups or []
self.groups_changed = False
......@@ -22,19 +27,30 @@ class User():
@classmethod
def from_ldap(cls, ldapobject):
ldap_attributes = {
"loginname": ldap.get_ldap_attribute_safe(ldapobject, "uid"),
"uid": ldap.get_ldap_attribute_safe(ldapobject, current_app.config["LDAP_USER_ATTRIBUTE_UID"]),
"displayname": ldap.get_ldap_attribute_safe(ldapobject, current_app.config["LDAP_USER_ATTRIBUTE_DISPLAYNAME"]),
"mail": ldap.get_ldap_attribute_safe(ldapobject, current_app.config["LDAP_USER_ATTRIBUTE_MAIL"]),
}
for user_attribute, ldap_attribute in current_app.config["LDAP_USER_ATTRIBUTE_EXTRA"].items():
ldap_attribute_name = ldap_attribute.get("name", "")
if ldap_attribute.get("type", "single"):
ldap_attributes[user_attribute] = ldap.get_ldap_attribute_safe(ldapobject, ldap_attribute_name)
else:
ldap_attributes[user_attribute] = ldap.get_ldap_array_attribute_safe(ldapobject, ldap_attribute_name)
return User(
uid=ldapobject['uidNumber'].value,
loginname=ldapobject['uid'].value,
displayname=ldapobject['cn'].value,
mail=ldapobject['mail'].value,
groups=ldap.get_ldap_array_attribute_safe(ldapobject, 'memberOf'),
dn=ldapobject.entry_dn,
attributes=ldap_attributes,
)
@classmethod
def from_ldap_dn(cls, dn):
conn = ldap.get_conn()
conn.search(dn, '(objectClass=person)')
conn.search(dn, current_app.config["LDAP_USER_FILTER"])
if not len(conn.entries) == 1:
return None
return User.from_ldap(conn.entries[0])
......@@ -44,7 +60,9 @@ class User():
if new:
self.uid = ldap.get_next_uid()
attributes = {
'uidNumber': self.uid,
current_app.config["LDAP_USER_ATTRIBUTE_UID"]: self.uid,
current_app.config["LDAP_USER_ATTRIBUTE_DISPLAYNAME"]: self.displayname,
current_app.config["LDAP_USER_ATTRIBUTE_MAIL"]: self.mail,
'gidNumber': current_app.config['LDAP_USER_GID'],
'homeDirectory': '/home/'+self.loginname,
'sn': ' ',
......@@ -52,8 +70,6 @@ class User():
# same as for update
'givenName': self.displayname,
'displayName': self.displayname,
'cn': self.displayname,
'mail': self.mail,
}
dn = ldap.loginname_to_dn(self.loginname)
result = conn.add(dn, current_app.config['LDAP_USER_OBJECTCLASSES'], attributes)
......@@ -61,8 +77,8 @@ class User():
attributes = {
'givenName': [(MODIFY_REPLACE, [self.displayname])],
'displayName': [(MODIFY_REPLACE, [self.displayname])],
'cn': [(MODIFY_REPLACE, [self.displayname])],
'mail': [(MODIFY_REPLACE, [self.mail])],
current_app.config["LDAP_USER_ATTRIBUTE_DISPLAYNAME"]: [(MODIFY_REPLACE, [self.displayname])],
current_app.config["LDAP_USER_ATTRIBUTE_MAIL"]: [(MODIFY_REPLACE, [self.mail])],
}
if self.newpassword:
attributes['userPassword'] = [(MODIFY_REPLACE, [hashed(HASHED_SALTED_SHA512, self.newpassword)])]
......@@ -145,7 +161,11 @@ class User():
self.mail = value
return True
class Group():
User = BaseUser
class Group:
def __init__(self, gid=None, name='', members=None, description='', dn=None):
self.gid = gid
self.name = name
......@@ -167,7 +187,7 @@ class Group():
@classmethod
def from_ldap_dn(cls, dn):
conn = ldap.get_conn()
conn.search(dn, '(objectClass=groupOfUniqueNames)')
conn.search(dn, current_app.config["LDAP_GROUP_FILTER"])
if not len(conn.entries) == 1:
return None
return Group.from_ldap(conn.entries[0])
......@@ -175,7 +195,7 @@ class Group():
@classmethod
def from_ldap_all(cls):
conn = ldap.get_conn()
conn.search(current_app.config["LDAP_BASE_GROUPS"], '(objectclass=groupOfUniqueNames)')
conn.search(current_app.config["LDAP_BASE_GROUPS"], current_app.config["LDAP_GROUP_FILTER"])
groups = []
for i in conn.entries:
groups.append(Group.from_ldap(i))
......
......@@ -25,7 +25,8 @@ def index():
@bp.route("/<int:gid>")
def show(gid):
conn = get_conn()
conn.search(current_app.config["LDAP_BASE_GROUPS"], '(&(objectclass=groupOfUniqueNames)(gidNumber={}))'.format((escape_filter_chars(gid))))
conn.search(current_app.config["LDAP_BASE_GROUPS"],
'(&{}(gidNumber={}))'.format(current_app.config["LDAP_GROUP_FILTER"], escape_filter_chars(gid)))
assert len(conn.entries) == 1
group = Group.from_ldap(conn.entries[0])
return render_template('group.html', group=group)
......@@ -29,7 +29,7 @@ def user_acl_check():
@register_navbar('Users', icon='users', blueprint=bp, visible=user_acl_check)
def index():
conn = get_conn()
conn.search(current_app.config["LDAP_BASE_USER"], '(objectclass=person)')
conn.search(current_app.config["LDAP_BASE_USER"], current_app.config["LDAP_USER_FILTER"])
users = []
for i in conn.entries:
users.append(User.from_ldap(i))
......@@ -43,7 +43,8 @@ def show(uid=None):
ldif = '<none yet>'
else:
conn = get_conn()
conn.search(current_app.config["LDAP_BASE_USER"], '(&(objectclass=person)(uidNumber={}))'.format((escape_filter_chars(uid))))
conn.search(current_app.config["LDAP_BASE_USER"],
'(&{}(uidNumber={}))'.format(current_app.config["LDAP_USER_FILTER"], escape_filter_chars(uid)))
assert len(conn.entries) == 1
user = User.from_ldap(conn.entries[0])
ldif = conn.entries[0].entry_to_ldif()
......@@ -61,7 +62,8 @@ def update(uid=False):
flash('Login name does not meet requirements')
return redirect(url_for('user.show'))
else:
conn.search(current_app.config["LDAP_BASE_USER"], '(&(objectclass=person)(uidNumber={}))'.format((escape_filter_chars(uid))))
conn.search(current_app.config["LDAP_BASE_USER"],
'(&{}(uidNumber={}))'.format(current_app.config["LDAP_USER_FILTER"], escape_filter_chars(uid)))
assert len(conn.entries) == 1
user = User.from_ldap(conn.entries[0])
if not user.set_mail(request.form['mail']):
......@@ -106,7 +108,8 @@ def update(uid=False):
@csrf_protect(blueprint=bp)
def delete(uid):
conn = get_conn()
conn.search(current_app.config["LDAP_BASE_USER"], '(&(objectclass=person)(uidNumber={}))'.format((escape_filter_chars(uid))))
conn.search(current_app.config["LDAP_BASE_USER"],
'(&{}(uidNumber={}))'.format(current_app.config["LDAP_USER_FILTER"], escape_filter_chars(uid)))
assert len(conn.entries) == 1
user = User.from_ldap(conn.entries[0])
......
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