From f33096211b4bfd682dd09f9145df325a51295b27 Mon Sep 17 00:00:00 2001 From: Julian Rother <julianr@fsmpi.rwth-aachen.de> Date: Tue, 23 Feb 2021 01:17:57 +0100 Subject: [PATCH] Ported uffd code to new ldap mapper --- ldap3_mapper_new/__init__.py | 3 ++- ldap3_mapper_new/attribute.py | 6 ++--- ldap3_mapper_new/core.py | 26 +++++++++++--------- ldap3_mapper_new/dbutils.py | 14 ++++++----- ldap3_mapper_new/model.py | 13 ++++++---- ldap3_mapper_new/relationship.py | 22 +++++++++++------ tests/test_mail.py | 4 +-- tests/test_mfa.py | 4 +-- tests/test_oauth2.py | 4 +-- tests/test_role.py | 2 +- tests/test_selfservice.py | 22 ++++++++--------- tests/test_user.py | 42 +++++++++++++++++--------------- uffd/ldap.py | 11 ++++----- uffd/mail/models.py | 6 ++--- uffd/mail/views.py | 8 +++--- uffd/mfa/models.py | 2 +- uffd/mfa/views.py | 2 +- uffd/oauth2/models.py | 4 +-- uffd/role/models.py | 6 ++--- uffd/role/views.py | 4 +-- uffd/selfservice/views.py | 8 +++--- uffd/session/views.py | 7 +++--- uffd/user/models.py | 30 +++++++++++------------ uffd/user/views_group.py | 4 +-- uffd/user/views_user.py | 10 ++++---- 25 files changed, 140 insertions(+), 124 deletions(-) diff --git a/ldap3_mapper_new/__init__.py b/ldap3_mapper_new/__init__.py index 4eb8c8f4..3c0730e8 100644 --- a/ldap3_mapper_new/__init__.py +++ b/ldap3_mapper_new/__init__.py @@ -1,8 +1,9 @@ import ldap3 +from .core import LDAPCommitError from . import model, attribute, relationship -__all__ = ['LDAPMapper'] +__all__ = ['LDAPMapper', 'LDAPCommitError'] class LDAPMapper: def __init__(self, server=None, bind_dn=None, bind_password=None): diff --git a/ldap3_mapper_new/attribute.py b/ldap3_mapper_new/attribute.py index 9e984025..bd776313 100644 --- a/ldap3_mapper_new/attribute.py +++ b/ldap3_mapper_new/attribute.py @@ -46,7 +46,7 @@ class Attribute: def add_hook(self, obj): if obj.ldap_object.getattr(self.name) == []: - self.__set__(self.name, self.default() if callable(self.default) else self.default) + self.__set__(obj, self.default() if callable(self.default) else self.default) def __set_name__(self, cls, name): if self.default is not None: @@ -57,10 +57,10 @@ class Attribute: return self if self.multi: return AttributeList(obj.ldap_object, self.name, self.aliases) - return obj.ldap_object.getattr(self.name) + return (obj.ldap_object.getattr(self.name) or [None])[0] def __set__(self, obj, values): if not self.multi: values = [values] - for name in self.aliases: + for name in [self.name] + self.aliases: obj.ldap_object.setattr(name, values) diff --git a/ldap3_mapper_new/core.py b/ldap3_mapper_new/core.py index 11399220..c5fcff11 100644 --- a/ldap3_mapper_new/core.py +++ b/ldap3_mapper_new/core.py @@ -1,5 +1,3 @@ -from copy import deepcopy - from ldap3 import MODIFY_REPLACE, MODIFY_DELETE, MODIFY_ADD, ALL_ATTRIBUTES, ALL_OPERATIONAL_ATTRIBUTES from ldap3.utils.conv import escape_filter_chars @@ -10,9 +8,9 @@ def match_dn(dn, base): return dn.endswith(base) # Probably good enougth for all valid dns def make_cache_key(search_base, filter_params): - res = [search_base] + res = (search_base,) for attr, value in sorted(filter_params): - res.append((attr, value)) + res += ((attr, value),) return res class LDAPCommitError(Exception): @@ -25,7 +23,10 @@ class SessionState: self.references = references or {} # {(attr_name, value): {srcobj, ...}, ...} def copy(self): - return SessionState(deepcopy(self.objects), deepcopy(self.deleted_objects), deepcopy(self.references)) + objects = self.objects.copy() + deleted_objects = self.deleted_objects.copy() + references = {key: objs.copy() for key, objs in self.references.items()} + return SessionState(objects, deleted_objects, references) def ref(self, obj, attr, values): for value in values: @@ -46,18 +47,19 @@ class ObjectState: self.dn = dn def copy(self): - return ObjectState(attributes=deepcopy(self.attributes), dn=self.dn, session=self.session) + attributes = {name: values.copy() for name, values in self.attributes.items()} + return ObjectState(attributes=attributes, dn=self.dn, session=self.session) class AddOperation: def __init__(self, obj, dn, object_classes): self.obj = obj self.dn = dn self.object_classes = object_classes - self.attributes = deepcopy(obj.state.attributes) + self.attributes = {name: values.copy() for name, values in obj.state.attributes.items()} def apply_object(self, obj_state): obj_state.dn = self.dn - obj_state.attributes = deepcopy(self.attributes) + obj_state.attributes = {name: values.copy() for name, values in self.attributes.items()} def apply_session(self, session_state): assert self.dn not in session_state.objects @@ -74,7 +76,7 @@ class DeleteOperation: def __init__(self, obj): self.dn = obj.state.dn self.obj = obj - self.attributes = deepcopy(obj.state.attributes) + self.attributes = {name: values.copy() for name, values in obj.state.attributes.items()} def apply_object(self, obj_state): obj_state.dn = None @@ -94,8 +96,8 @@ class DeleteOperation: class ModifyOperation: def __init__(self, obj, changes): self.obj = obj - self.attributes = deepcopy(obj.state.attributes) - self.changes = deepcopy(changes) + self.attributes = {name: values.copy() for name, values in obj.state.attributes.items()} + self.changes = changes def apply_object(self, obj_state): for attr, changes in self.changes.items(): @@ -181,7 +183,7 @@ class Session: def get(self, dn, filter_params): if dn in self.state.objects: obj = self.state.objects[dn] - return obj if obj.matches(filter_params) else None + return obj if obj.match(filter_params) else None if dn in self.state.deleted_objects: return None conn = self.get_connection() diff --git a/ldap3_mapper_new/dbutils.py b/ldap3_mapper_new/dbutils.py index 21a62321..42fccb96 100644 --- a/ldap3_mapper_new/dbutils.py +++ b/ldap3_mapper_new/dbutils.py @@ -3,10 +3,11 @@ from collections.abc import MutableSet from .model import add_to_session class DBRelationshipSet(MutableSet): - def __init__(self, dbobj, relattr, ldapcls): + def __init__(self, dbobj, relattr, ldapcls, mapcls): self.__dbobj = dbobj self.__relattr = relattr self.__ldapcls = ldapcls + self.__mapcls = mapcls def __get_dns(self): return [mapobj.dn for mapobj in getattr(self.__dbobj, self.__relattr)] @@ -28,10 +29,10 @@ class DBRelationshipSet(MutableSet): def add(self, value): if not isinstance(value, self.__ldapcls): raise TypeError() - if value.ldap_object.session is not None: - add_to_session(value, self.__ldapcls.ldap_mapper.session) + if value.ldap_object.session is None: + add_to_session(value, self.__ldapcls.ldap_mapper.session.ldap_session) if value.ldap_object.dn not in self.__get_dns(): - getattr(self.__dbobj, self.__relattr).append(self.__ldapcls(dn=value.ldap_object.dn)) + getattr(self.__dbobj, self.__relattr).append(self.__mapcls(dn=value.ldap_object.dn)) def discard(self, value): if not isinstance(value, self.__ldapcls): @@ -57,7 +58,7 @@ class DBRelationship: def __get__(self, obj, objtype=None): if obj is None: return self - return DBRelationshipSet(obj, self.relattr, self.ldapcls) + return DBRelationshipSet(obj, self.relattr, self.ldapcls, self.mapcls) def __set__(self, obj, values): tmp = self.__get__(obj) @@ -69,7 +70,7 @@ class DBBackreferenceSet(MutableSet): def __init__(self, ldapobj, dbcls, relattr, mapcls, backattr): self.__ldapobj = ldapobj self.__dbcls = dbcls - self.__relattr, = relattr + self.__relattr = relattr self.__mapcls = mapcls self.__backattr = backattr @@ -94,6 +95,7 @@ class DBBackreferenceSet(MutableSet): def add(self, value): # TODO: add value to db session if necessary + assert self.__ldapobj.ldap_object.session is not None if not isinstance(value, self.__dbcls): raise TypeError() rel = getattr(value, self.__relattr) diff --git a/ldap3_mapper_new/model.py b/ldap3_mapper_new/model.py index 4a0c20d7..0dc9f240 100644 --- a/ldap3_mapper_new/model.py +++ b/ldap3_mapper_new/model.py @@ -68,7 +68,8 @@ class ModelQuery: return make_modelobjs(objs, self.model) def filter_by(self, **kwargs): - filter_params = self.model.ldap_filter_params + list(kwargs.items()) + filter_params = self.model.ldap_filter_params + filter_params += tuple((getattr(self.model, attr).name, value) for attr, value in kwargs.items()) session = self.model.ldap_mapper.session.ldap_session objs = session.filter(self.model.ldap_search_base, filter_params) return make_modelobjs(objs, self.model) @@ -81,12 +82,12 @@ class Model: # Overwritten by mapper ldap_mapper = None query = ModelQueryWrapper() - ldap_add_hooks = tuple() + ldap_add_hooks = () # Overwritten by models ldap_search_base = None - ldap_filter_params = None - ldap_object_classes = None + ldap_filter_params = () + ldap_object_classes = () ldap_dn_base = None ldap_dn_attribute = None @@ -106,7 +107,9 @@ class Model: values = self.ldap_object.getattr(self.ldap_dn_attribute) if not values: return None - return '%s=%s,%s'%(self.ldap_dn_attribute, escape_rdn(values[0]), self.ldap_dn_base) + # escape_rdn can't handle empty strings + rdn = escape_rdn(values[0]) if values[0] else '' + return '%s=%s,%s'%(self.ldap_dn_attribute, rdn, self.ldap_dn_base) def __repr__(self): cls_name = '%s.%s'%(type(self).__module__, type(self).__name__) diff --git a/ldap3_mapper_new/relationship.py b/ldap3_mapper_new/relationship.py index 06986266..d8e256aa 100644 --- a/ldap3_mapper_new/relationship.py +++ b/ldap3_mapper_new/relationship.py @@ -40,11 +40,15 @@ class RelationshipSet(MutableSet): if value.ldap_object.session is None: add_to_session(value, self.__ldap_object.session) assert value.ldap_object.session == self.__ldap_object.session - self.__ldap_object.attradd(self.__name, value.dn) + self.__ldap_object.attr_append(self.__name, value.dn) def discard(self, value): self.__modify_check(value) - self.__ldap_object.attrdel(self.__name, value.dn) + self.__ldap_object.attr_remove(self.__name, value.dn) + + def update(self, values): + for value in values: + self.add(value) class Relationship: def __init__(self, name, destmodel, backref=None): @@ -59,7 +63,7 @@ class Relationship: def __get__(self, obj, objtype=None): if obj is None: return self - return RelationshipSet(obj, self.name, type(obj), self.destmodel) + return RelationshipSet(obj.ldap_object, self.name, type(obj), self.destmodel) def __set__(self, obj, values): tmp = self.__get__(obj) @@ -83,7 +87,7 @@ class BackreferenceSet(MutableSet): def __get(self): if self.__ldap_object.session is None: return set() - filter_params = self.__srcmodel.filter_params + [(self.__name, self.__ldap_object.dn)] + filter_params = self.__srcmodel.ldap_filter_params + ((self.__name, self.__ldap_object.dn),) objs = self.__ldap_object.session.filter(self.__srcmodel.ldap_search_base, filter_params) return set(make_modelobjs(objs, self.__srcmodel)) @@ -105,11 +109,15 @@ class BackreferenceSet(MutableSet): add_to_session(value, self.__ldap_object.session) assert value.ldap_object.session == self.__ldap_object.session if self.__ldap_object.dn not in value.ldap_object.getattr(self.__name): - value.ldap_object.attradd(self.__name, self.__ldap_object.dn) + value.ldap_object.attr_append(self.__name, self.__ldap_object.dn) def discard(self, value): self.__modify_check(value) - value.ldap_object.attrdel(self.__name, self.__ldap_object.dn) + value.ldap_object.attr_remove(self.__name, self.__ldap_object.dn) + + def update(self, values): + for value in values: + self.add(value) class Backreference: def __init__(self, name, srcmodel): @@ -119,7 +127,7 @@ class Backreference: def __get__(self, obj, objtype=None): if obj is None: return self - return BackreferenceSet(obj, self.name, type(obj), self.srcmodel) + return BackreferenceSet(obj.ldap_object, self.name, type(obj), self.srcmodel) def __set__(self, obj, values): tmp = self.__get__(obj) diff --git a/tests/test_mail.py b/tests/test_mail.py index 5d2f35d7..a5b9f88f 100644 --- a/tests/test_mail.py +++ b/tests/test_mail.py @@ -14,7 +14,7 @@ from uffd import create_app, db from utils import dump, UffdTestCase def get_mail(): - return Mail.ldap_get('uid=test,ou=postfix,dc=example,dc=com') + return Mail.query.get('uid=test,ou=postfix,dc=example,dc=com') class TestMailViews(UffdTestCase): def setUp(self): @@ -68,7 +68,7 @@ class TestMailViews(UffdTestCase): 'mail-destinations': 'testuser@mail.example.com\ntestadmin@mail.example.com'}, follow_redirects=True) dump('mail_create', r) self.assertEqual(r.status_code, 200) - m = Mail.ldap_get('uid=test1,ou=postfix,dc=example,dc=com') + m = Mail.query.get('uid=test1,ou=postfix,dc=example,dc=com') self.assertEqual(m.uid, 'test1') self.assertEqual(sorted(m.receivers), ['foo@bar.com', 'test@bar.com']) self.assertEqual(sorted(m.destinations), ['testadmin@mail.example.com', 'testuser@mail.example.com']) diff --git a/tests/test_mfa.py b/tests/test_mfa.py index b5bd2a51..4ea98102 100644 --- a/tests/test_mfa.py +++ b/tests/test_mfa.py @@ -25,10 +25,10 @@ class TestMfaPrimitives(unittest.TestCase): self.assertEqual(_hotp(2**64-1, b'abcde'), '899292') def get_user(): - return User.ldap_get('uid=testuser,ou=users,dc=example,dc=com') + return User.query.get('uid=testuser,ou=users,dc=example,dc=com') def get_admin(): - return User.ldap_get('uid=testadmin,ou=users,dc=example,dc=com') + return User.query.get('uid=testadmin,ou=users,dc=example,dc=com') def get_fido2_test_cred(): try: diff --git a/tests/test_oauth2.py b/tests/test_oauth2.py index e68795ee..1dc7e1c5 100644 --- a/tests/test_oauth2.py +++ b/tests/test_oauth2.py @@ -14,10 +14,10 @@ from uffd import create_app, db, ldap from utils import dump, UffdTestCase def get_user(): - return User.ldap_get('uid=testuser,ou=users,dc=example,dc=com') + return User.query.get('uid=testuser,ou=users,dc=example,dc=com') def get_admin(): - return User.ldap_get('uid=testadmin,ou=users,dc=example,dc=com') + return User.query.get('uid=testadmin,ou=users,dc=example,dc=com') class TestOAuth2Client(UffdTestCase): def setUpApp(self): diff --git a/tests/test_role.py b/tests/test_role.py index 2f4e0601..c6a42bdc 100644 --- a/tests/test_role.py +++ b/tests/test_role.py @@ -48,7 +48,7 @@ class TestRoleViews(UffdTestCase): role = Role(name='base', description='Base role description') db.session.add(role) db.session.commit() - role.groups.add(Group.ldap_get('cn=uffd_admin,ou=groups,dc=example,dc=com')) + role.groups.add(Group.query.get('cn=uffd_admin,ou=groups,dc=example,dc=com')) db.session.commit() self.assertEqual(role.name, 'base') self.assertEqual(role.description, 'Base role description') diff --git a/tests/test_selfservice.py b/tests/test_selfservice.py index f46b4c1e..851f2d7b 100644 --- a/tests/test_selfservice.py +++ b/tests/test_selfservice.py @@ -14,7 +14,7 @@ from uffd import create_app, db, ldap from utils import dump, UffdTestCase def get_ldap_password(): - return User.ldap_get('uid=testuser,ou=users,dc=example,dc=com').pwhash + return User.query.get('uid=testuser,ou=users,dc=example,dc=com').pwhash class TestSelfservice(UffdTestCase): def setUpApp(self): @@ -111,7 +111,7 @@ class TestSelfservice(UffdTestCase): def test_token_mail_wrong_user(self): self.login() user = get_current_user() - admin_user = User.ldap_get('uid=testadmin,ou=users,dc=example,dc=com') + admin_user = User.query.get('uid=testadmin,ou=users,dc=example,dc=com') db.session.add(MailToken(loginname=user.loginname, newmail='newusermail@example.com')) admin_token = MailToken(loginname='testadmin', newmail='newadminmail@example.com') db.session.add(admin_token) @@ -120,7 +120,7 @@ class TestSelfservice(UffdTestCase): dump('token_mail_wrong_user', r) self.assertEqual(r.status_code, 200) _user = get_current_user() - _admin_user = User.ldap_get('uid=testadmin,ou=users,dc=example,dc=com') + _admin_user = User.query.get('uid=testadmin,ou=users,dc=example,dc=com') self.assertEqual(_user.mail, user.mail) self.assertEqual(_admin_user.mail, admin_user.mail) @@ -140,7 +140,7 @@ class TestSelfservice(UffdTestCase): self.assertEqual(len(tokens), 0) def test_forgot_password(self): - user = User.ldap_get('uid=testuser,ou=users,dc=example,dc=com') + user = User.query.get('uid=testuser,ou=users,dc=example,dc=com') r = self.client.get(path=url_for('selfservice.forgot_password')) dump('forgot_password', r) self.assertEqual(r.status_code, 200) @@ -153,7 +153,7 @@ class TestSelfservice(UffdTestCase): self.assertIn(token.token, str(self.app.last_mail.get_content())) def test_forgot_password_wrong_user(self): - user = User.ldap_get('uid=testuser,ou=users,dc=example,dc=com') + user = User.query.get('uid=testuser,ou=users,dc=example,dc=com') r = self.client.get(path=url_for('selfservice.forgot_password')) self.assertEqual(r.status_code, 200) r = self.client.post(path=url_for('selfservice.forgot_password'), @@ -164,7 +164,7 @@ class TestSelfservice(UffdTestCase): self.assertEqual(len(PasswordToken.query.all()), 0) def test_forgot_password_wrong_email(self): - user = User.ldap_get('uid=testuser,ou=users,dc=example,dc=com') + user = User.query.get('uid=testuser,ou=users,dc=example,dc=com') r = self.client.get(path=url_for('selfservice.forgot_password'), follow_redirects=True) self.assertEqual(r.status_code, 200) r = self.client.post(path=url_for('selfservice.forgot_password'), @@ -184,7 +184,7 @@ class TestSelfservice(UffdTestCase): self.assertEqual(len(PasswordToken.query.all()), 0) def test_token_password(self): - user = User.ldap_get('uid=testuser,ou=users,dc=example,dc=com') + user = User.query.get('uid=testuser,ou=users,dc=example,dc=com') oldpw = get_ldap_password() token = PasswordToken(loginname=user.loginname) db.session.add(token) @@ -201,7 +201,7 @@ class TestSelfservice(UffdTestCase): self.assertEqual(len(PasswordToken.query.all()), 0) def test_token_password_emptydb(self): - user = User.ldap_get('uid=testuser,ou=users,dc=example,dc=com') + user = User.query.get('uid=testuser,ou=users,dc=example,dc=com') oldpw = get_ldap_password() r = self.client.get(path=url_for('selfservice.token_password', token='A'*128), follow_redirects=True) dump('token_password_emptydb', r) @@ -215,7 +215,7 @@ class TestSelfservice(UffdTestCase): self.assertEqual(oldpw, get_ldap_password()) def test_token_password_invalid(self): - user = User.ldap_get('uid=testuser,ou=users,dc=example,dc=com') + user = User.query.get('uid=testuser,ou=users,dc=example,dc=com') oldpw = get_ldap_password() token = PasswordToken(loginname=user.loginname) db.session.add(token) @@ -232,7 +232,7 @@ class TestSelfservice(UffdTestCase): self.assertEqual(oldpw, get_ldap_password()) def test_token_password_expired(self): - user = User.ldap_get('uid=testuser,ou=users,dc=example,dc=com') + user = User.query.get('uid=testuser,ou=users,dc=example,dc=com') oldpw = get_ldap_password() token = PasswordToken(loginname=user.loginname, created=(datetime.datetime.now() - datetime.timedelta(days=10))) @@ -250,7 +250,7 @@ class TestSelfservice(UffdTestCase): self.assertEqual(oldpw, get_ldap_password()) def test_token_password_different_passwords(self): - user = User.ldap_get('uid=testuser,ou=users,dc=example,dc=com') + user = User.query.get('uid=testuser,ou=users,dc=example,dc=com') oldpw = get_ldap_password() token = PasswordToken(loginname=user.loginname) db.session.add(token) diff --git a/tests/test_user.py b/tests/test_user.py index 5bc1e313..63e429f7 100644 --- a/tests/test_user.py +++ b/tests/test_user.py @@ -16,13 +16,13 @@ from uffd import create_app, db from utils import dump, UffdTestCase def get_user(): - return User.ldap_get('uid=testuser,ou=users,dc=example,dc=com') + return User.query.get('uid=testuser,ou=users,dc=example,dc=com') def get_user_password(): return get_user().pwhash def get_admin(): - return User.ldap_get('uid=testadmin,ou=users,dc=example,dc=com') + return User.query.get('uid=testadmin,ou=users,dc=example,dc=com') class TestUserModel(UffdTestCase): def test_has_permission(self): @@ -74,19 +74,21 @@ class TestUserViews(UffdTestCase): r = self.client.get(path=url_for('user.show'), follow_redirects=True) dump('user_new', r) self.assertEqual(r.status_code, 200) - self.assertIsNone(User.ldap_get('uid=newuser,ou=users,dc=example,dc=com')) + 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'}, follow_redirects=True) dump('user_new_submit', r) self.assertEqual(r.status_code, 200) - user = User.ldap_get('uid=newuser,ou=users,dc=example,dc=com') + 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.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: check password hash @@ -96,7 +98,7 @@ class TestUserViews(UffdTestCase): 'password': 'newpassword'}, follow_redirects=True) dump('user_new_invalid_loginname', r) self.assertEqual(r.status_code, 200) - self.assertIsNone(User.ldap_get('uid=newuser,ou=users,dc=example,dc=com')) + self.assertIsNone(User.query.get('uid=newuser,ou=users,dc=example,dc=com')) def test_new_empty_loginname(self): r = self.client.post(path=url_for('user.update'), @@ -104,7 +106,7 @@ class TestUserViews(UffdTestCase): 'password': 'newpassword'}, follow_redirects=True) dump('user_new_empty_loginname', r) self.assertEqual(r.status_code, 200) - self.assertIsNone(User.ldap_get('uid=newuser,ou=users,dc=example,dc=com')) + self.assertIsNone(User.query.get('uid=newuser,ou=users,dc=example,dc=com')) def test_new_empty_email(self): r = self.client.post(path=url_for('user.update'), @@ -112,7 +114,7 @@ class TestUserViews(UffdTestCase): 'password': 'newpassword'}, follow_redirects=True) dump('user_new_empty_email', r) self.assertEqual(r.status_code, 200) - self.assertIsNone(User.ldap_get('uid=newuser,ou=users,dc=example,dc=com')) + self.assertIsNone(User.query.get('uid=newuser,ou=users,dc=example,dc=com')) def test_new_invalid_display_name(self): r = self.client.post(path=url_for('user.update'), @@ -120,7 +122,7 @@ class TestUserViews(UffdTestCase): 'password': 'newpassword'}, follow_redirects=True) dump('user_new_invalid_display_name', r) self.assertEqual(r.status_code, 200) - self.assertIsNone(User.ldap_get('uid=newuser,ou=users,dc=example,dc=com')) + self.assertIsNone(User.query.get('uid=newuser,ou=users,dc=example,dc=com')) def test_update(self): user = get_user() @@ -255,59 +257,59 @@ newuser12,newuser12@example.com,{role1.id};{role1.id} r = self.client.post(path=url_for('user.csvimport'), data={'csv': data}, follow_redirects=True) dump('user_csvimport', r) self.assertEqual(r.status_code, 200) - user = User.ldap_get('uid=newuser1,ou=users,dc=example,dc=com') + user = User.query.get('uid=newuser1,ou=users,dc=example,dc=com') roles = sorted([r.name for r in user.roles]) self.assertIsNotNone(user) self.assertEqual(user.loginname, 'newuser1') self.assertEqual(user.displayname, 'newuser1') self.assertEqual(user.mail, 'newuser1@example.com') self.assertEqual(roles, ['base']) - user = User.ldap_get('uid=newuser2,ou=users,dc=example,dc=com') + user = User.query.get('uid=newuser2,ou=users,dc=example,dc=com') roles = sorted([r.name for r in user.roles]) self.assertIsNotNone(user) self.assertEqual(user.loginname, 'newuser2') self.assertEqual(user.displayname, 'newuser2') self.assertEqual(user.mail, 'newuser2@example.com') self.assertEqual(roles, ['base', 'role1']) - user = User.ldap_get('uid=newuser3,ou=users,dc=example,dc=com') + user = User.query.get('uid=newuser3,ou=users,dc=example,dc=com') roles = sorted([r.name for r in user.roles]) self.assertIsNotNone(user) self.assertEqual(user.loginname, 'newuser3') self.assertEqual(user.displayname, 'newuser3') self.assertEqual(user.mail, 'newuser3@example.com') self.assertEqual(roles, ['base', 'role1', 'role2']) - user = User.ldap_get('uid=newuser4,ou=users,dc=example,dc=com') + user = User.query.get('uid=newuser4,ou=users,dc=example,dc=com') roles = sorted([r.name for r in user.roles]) self.assertIsNotNone(user) self.assertEqual(user.loginname, 'newuser4') self.assertEqual(user.displayname, 'newuser4') self.assertEqual(user.mail, 'newuser4@example.com') self.assertEqual(roles, ['base']) - user = User.ldap_get('uid=newuser5,ou=users,dc=example,dc=com') + user = User.query.get('uid=newuser5,ou=users,dc=example,dc=com') roles = sorted([r.name for r in user.roles]) self.assertIsNotNone(user) self.assertEqual(user.loginname, 'newuser5') self.assertEqual(user.displayname, 'newuser5') self.assertEqual(user.mail, 'newuser5@example.com') self.assertEqual(roles, ['base']) - user = User.ldap_get('uid=newuser6,ou=users,dc=example,dc=com') + user = User.query.get('uid=newuser6,ou=users,dc=example,dc=com') roles = sorted([r.name for r in user.roles]) self.assertIsNotNone(user) self.assertEqual(user.loginname, 'newuser6') self.assertEqual(user.displayname, 'newuser6') self.assertEqual(user.mail, 'newuser6@example.com') self.assertEqual(roles, ['base', 'role1', 'role2']) - self.assertIsNone(User.ldap_get('uid=newuser7,ou=users,dc=example,dc=com')) - self.assertIsNone(User.ldap_get('uid=newuser8,ou=users,dc=example,dc=com')) - self.assertIsNone(User.ldap_get('uid=newuser9,ou=users,dc=example,dc=com')) - user = User.ldap_get('uid=newuser10,ou=users,dc=example,dc=com') + self.assertIsNone(User.query.get('uid=newuser7,ou=users,dc=example,dc=com')) + self.assertIsNone(User.query.get('uid=newuser8,ou=users,dc=example,dc=com')) + self.assertIsNone(User.query.get('uid=newuser9,ou=users,dc=example,dc=com')) + user = User.query.get('uid=newuser10,ou=users,dc=example,dc=com') roles = sorted([r.name for r in user.roles]) self.assertIsNotNone(user) self.assertEqual(user.loginname, 'newuser10') self.assertEqual(user.displayname, 'newuser10') self.assertEqual(user.mail, 'newuser10@example.com') self.assertEqual(roles, ['base']) - user = User.ldap_get('uid=newuser11,ou=users,dc=example,dc=com') + user = User.query.get('uid=newuser11,ou=users,dc=example,dc=com') roles = sorted([r.name for r in user.roles]) self.assertIsNotNone(user) self.assertEqual(user.loginname, 'newuser11') @@ -316,7 +318,7 @@ newuser12,newuser12@example.com,{role1.id};{role1.id} # Currently the csv import is not very robust, imho newuser11 should have role1 and role2! #self.assertEqual(roles, ['base', 'role1', 'role2']) self.assertEqual(roles, ['base', 'role2']) - user = User.ldap_get('uid=newuser12,ou=users,dc=example,dc=com') + user = User.query.get('uid=newuser12,ou=users,dc=example,dc=com') roles = sorted([r.name for r in user.roles]) self.assertIsNotNone(user) self.assertEqual(user.loginname, 'newuser12') diff --git a/uffd/ldap.py b/uffd/ldap.py index 2365a81e..0dcd9fdf 100644 --- a/uffd/ldap.py +++ b/uffd/ldap.py @@ -2,20 +2,19 @@ from flask import current_app, request import ldap3 -from ldap3_mapper import LDAP3Mapper, LDAPCommitError # pylint: disable=unused-import -from ldap3_mapper.base import Session +from ldap3_mapper_new import LDAPMapper, LDAPCommitError # pylint: disable=unused-import -class FlaskLDAP3Mapper(LDAP3Mapper): +class FlaskLDAPMapper(LDAPMapper): def __init__(self): super().__init__() @property def session(self): if not hasattr(request, 'ldap_session'): - request.ldap_session = Session() + request.ldap_session = self.Session(self.get_connection) return request.ldap_session - def connect(self): + def get_connection(self): if current_app.config.get('LDAP_SERVICE_MOCK', False): if not current_app.debug: raise Exception('LDAP_SERVICE_MOCK cannot be enabled on production instances') @@ -32,4 +31,4 @@ class FlaskLDAP3Mapper(LDAP3Mapper): return ldap3.Connection(server, current_app.config["LDAP_SERVICE_BIND_DN"], current_app.config["LDAP_SERVICE_BIND_PASSWORD"], auto_bind=True) -ldap = FlaskLDAP3Mapper() +ldap = FlaskLDAPMapper() diff --git a/uffd/mail/models.py b/uffd/mail/models.py index 22a7e0b2..322af9d4 100644 --- a/uffd/mail/models.py +++ b/uffd/mail/models.py @@ -2,11 +2,11 @@ from uffd.ldap import ldap from uffd.lazyconfig import lazyconfig_str, lazyconfig_list class Mail(ldap.Model): - ldap_base = lazyconfig_str('LDAP_BASE_MAIL') + ldap_search_base = lazyconfig_str('LDAP_BASE_MAIL') + ldap_filter_params = (('objectClass', 'postfixVirtual'),) + ldap_object_classes = lazyconfig_list('MAIL_LDAP_OBJECTCLASSES') ldap_dn_attribute = 'uid' ldap_dn_base = lazyconfig_str('LDAP_BASE_MAIL') - ldap_filter = '(objectClass=postfixVirtual)' - ldap_object_classes = lazyconfig_list('MAIL_LDAP_OBJECTCLASSES') uid = ldap.Attribute('uid') receivers = ldap.Attribute('mailacceptinggeneralid', multi=True) diff --git a/uffd/mail/views.py b/uffd/mail/views.py index db401a3a..d7b68782 100644 --- a/uffd/mail/views.py +++ b/uffd/mail/views.py @@ -21,14 +21,14 @@ def mail_acl_check(): @bp.route("/") @register_navbar('Mail', icon='envelope', blueprint=bp, visible=mail_acl_check) def index(): - return render_template('mail_list.html', mails=Mail.ldap_all()) + return render_template('mail_list.html', mails=Mail.query.all()) @bp.route("/<uid>") @bp.route("/new") def show(uid=None): mail = Mail() if uid is not None: - mail = Mail.ldap_filter_by(uid=uid)[0] + mail = Mail.query.filter_by(uid=uid)[0] return render_template('mail.html', mail=mail) @bp.route("/<uid>/update", methods=['POST']) @@ -36,7 +36,7 @@ def show(uid=None): @csrf_protect(blueprint=bp) def update(uid=None): if uid is not None: - mail = Mail.ldap_filter_by(uid=uid)[0] + mail = Mail.query.filter_by(uid=uid)[0] else: mail = Mail(uid=request.form.get('mail-uid')) mail.receivers = request.form.get('mail-receivers', '').splitlines() @@ -49,7 +49,7 @@ def update(uid=None): @bp.route("/<uid>/del") @csrf_protect(blueprint=bp) def delete(uid): - mail = Mail.ldap_filter_by(uid=uid)[0] + mail = Mail.query.filter_by(uid=uid)[0] ldap.session.delete(mail) ldap.session.commit() flash('Deleted mail mapping.') diff --git a/uffd/mfa/models.py b/uffd/mfa/models.py index 4c20d3c8..92d20cfb 100644 --- a/uffd/mfa/models.py +++ b/uffd/mfa/models.py @@ -41,7 +41,7 @@ class MFAMethod(db.Model): @property def user(self): - return User.ldap_get(self.dn) + return User.query.get(self.dn) @user.setter def user(self, new_user): diff --git a/uffd/mfa/views.py b/uffd/mfa/views.py index 50b6ebce..3ce9a9f0 100644 --- a/uffd/mfa/views.py +++ b/uffd/mfa/views.py @@ -46,7 +46,7 @@ def admin_disable(uid): if not get_current_user().is_in_group(current_app.config['ACL_ADMIN_GROUP']): flash('Access denied') return redirect(url_for('index')) - user = User.ldap_filter_by(uid=uid)[0] + user = User.query.filter_by(uid=uid)[0] MFAMethod.query.filter_by(dn=user.dn).delete() db.session.commit() flash('Two-factor authentication was reset') diff --git a/uffd/oauth2/models.py b/uffd/oauth2/models.py index 3afcb43c..2900367c 100644 --- a/uffd/oauth2/models.py +++ b/uffd/oauth2/models.py @@ -39,7 +39,7 @@ class OAuth2Grant(db.Model): @property def user(self): - return User.ldap_get(self.user_dn) + return User.query.get(self.user_dn) @user.setter def user(self, newuser): @@ -78,7 +78,7 @@ class OAuth2Token(db.Model): user_dn = Column(String(128)) @property def user(self): - return User.ldap_get(self.user_dn) + return User.query.get(self.user_dn) @user.setter def user(self, newuser): diff --git a/uffd/role/models.py b/uffd/role/models.py index 44512b55..996b6b3f 100644 --- a/uffd/role/models.py +++ b/uffd/role/models.py @@ -2,7 +2,7 @@ from sqlalchemy import Column, String, Integer, Text, ForeignKey from sqlalchemy.orm import relationship from sqlalchemy.ext.declarative import declared_attr -from ldap3_mapper.db_relation import DB2LDAPRelation +from ldap3_mapper_new.dbutils import DBRelationship from uffd.database import db from uffd.user.models import User, Group @@ -37,10 +37,10 @@ class Role(db.Model): description = Column(Text(), default='') db_members = relationship("RoleUser", backref="role", cascade="all, delete-orphan") - members = DB2LDAPRelation('db_members', RoleUser, User, backattr='role', backref='roles') + members = DBRelationship('db_members', User, RoleUser, backattr='role', backref='roles') db_groups = relationship("RoleGroup", backref="role", cascade="all, delete-orphan") - groups = DB2LDAPRelation('db_groups', RoleGroup, Group, backattr='role', backref='roles') + groups = DBRelationship('db_groups', Group, RoleGroup, backattr='role', backref='roles') def update_member_groups(self): for user in self.members: diff --git a/uffd/role/views.py b/uffd/role/views.py index 7b16b024..e915ce5a 100644 --- a/uffd/role/views.py +++ b/uffd/role/views.py @@ -31,7 +31,7 @@ def show(roleid=False): role = Role() else: role = Role.query.filter_by(id=roleid).one() - return render_template('role.html', role=role, groups=Group.ldap_all()) + return render_template('role.html', role=role, groups=Group.query.all()) @bp.route("/<int:roleid>/update", methods=['POST']) @bp.route("/new", methods=['POST']) @@ -45,7 +45,7 @@ def update(roleid=False): role = Role.query.filter_by(id=roleid).one() role.name = request.values['name'] role.description = request.values['description'] - for group in Group.ldap_all(): + for group in Group.query.all(): if request.values.get('group-{}'.format(group.gid), False): role.groups.add(group) else: diff --git a/uffd/selfservice/views.py b/uffd/selfservice/views.py index c866d5ec..4ba81514 100644 --- a/uffd/selfservice/views.py +++ b/uffd/selfservice/views.py @@ -67,7 +67,7 @@ def forgot_password(): reset_ratelimit.log(loginname+'/'+mail) host_ratelimit.log() flash("We sent a mail to this users mail address if you entered the correct mail and login name combination") - user = (User.ldap_filter_by(loginname=loginname) or [None])[0] + user = (User.query.filter_by(loginname=loginname) or [None])[0] if user and user.mail == mail: send_passwordreset(user) return redirect(url_for('session.login')) @@ -89,7 +89,7 @@ def token_password(token): if not request.values['password1'] == request.values['password2']: flash('Passwords do not match, please try again.') return render_template('set_password.html', token=token) - user = User.ldap_filter_by(loginname=dbtoken.loginname)[0] + user = User.query.filter_by(loginname=dbtoken.loginname)[0] if not user.set_password(request.values['password1']): flash('Password ist not valid, please try again.') return render_template('set_password.html', token=token) @@ -110,7 +110,7 @@ def token_mail(token): db.session.commit() return redirect(url_for('selfservice.index')) - user = User.ldap_filter_by(loginname=dbtoken.loginname)[0] + user = User.query.filter_by(loginname=dbtoken.loginname)[0] user.set_mail(dbtoken.newmail) flash('New mail set') db.session.delete(dbtoken) @@ -129,7 +129,7 @@ def send_mail_verification(loginname, newmail): db.session.add(token) db.session.commit() - user = User.ldap_filter_by(loginname=loginname)[0] + user = User.query.filter_by(loginname=loginname)[0] msg = EmailMessage() msg.set_content(render_template('mailverification.mail.txt', user=user, token=token.token)) diff --git a/uffd/session/views.py b/uffd/session/views.py index cb28ee57..cad80b2d 100644 --- a/uffd/session/views.py +++ b/uffd/session/views.py @@ -18,7 +18,7 @@ login_ratelimit = Ratelimit('login', 1*60, 3) def login_get_user(loginname, password): dn = User(loginname=loginname).dn if current_app.config.get('LDAP_SERVICE_MOCK', False): - conn = ldap.connect() + conn = ldap.get_connection() # Since we reuse the same conn for all calls to `user_conn()` we # simulate the password check by rebinding. Note that ldap3's mocking # implementation just compares the string in the objects's userPassword @@ -38,7 +38,7 @@ def login_get_user(loginname, password): conn.search(conn.user, '(objectClass=person)') if len(conn.entries) != 1: return None - return User.ldap_get(dn) + return User.query.get(dn) @bp.route("/logout") def logout(): @@ -80,9 +80,8 @@ def login(): def get_current_user(): if 'user_dn' not in session: - print(session) return None - return User.ldap_get(session['user_dn']) + return User.query.get(session['user_dn']) def login_valid(): user = get_current_user() diff --git a/uffd/user/models.py b/uffd/user/models.py index e4507027..51dfe827 100644 --- a/uffd/user/models.py +++ b/uffd/user/models.py @@ -9,7 +9,7 @@ from uffd.lazyconfig import lazyconfig_str, lazyconfig_list def get_next_uid(): max_uid = current_app.config['LDAP_USER_MIN_UID'] - for user in User.ldap_all(): + 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 @@ -18,11 +18,11 @@ def get_next_uid(): return next_uid class User(ldap.Model): - ldap_base = lazyconfig_str('LDAP_BASE_USER') - ldap_dn_attribute = 'uid' - ldap_dn_base = lazyconfig_str('LDAP_BASE_USER') - ldap_filter = '(objectClass=person)' + ldap_search_base = lazyconfig_str('LDAP_BASE_USER') + ldap_filter_params = (('objectClass', 'person'),) ldap_object_classes = lazyconfig_list('LDAP_USER_OBJECTCLASSES') + ldap_dn_base = lazyconfig_str('LDAP_BASE_USER') + ldap_dn_attribute = 'uid' uid = ldap.Attribute('uidNumber', default=get_next_uid) loginname = ldap.Attribute('uid') @@ -34,14 +34,14 @@ class User(ldap.Model): roles = [] # Shuts up pylint, overwritten by back-reference def dummy_attribute_defaults(self): - if self.ldap_getattr('sn') == []: - self.ldap_setattr('sn', [' ']) - if self.ldap_getattr('homeDirectory') == []: - self.ldap_setattr('homeDirectory', ['/home/%s'%self.loginname]) - if self.ldap_getattr('gidNumber') == []: - self.ldap_setattr('gidNumber', [current_app.config['LDAP_USER_GID']]) + 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']]) - ldap_pre_create_hooks = ldap.Model.ldap_pre_create_hooks + [dummy_attribute_defaults] + ldap_add_hooks = ldap.Model.ldap_add_hooks + (dummy_attribute_defaults,) # Write-only property def password(self, value): @@ -102,12 +102,12 @@ class User(ldap.Model): return True class Group(ldap.Model): - ldap_base = lazyconfig_str('LDAP_BASE_GROUPS') - ldap_filter = '(objectClass=groupOfUniqueNames)' + ldap_search_base = lazyconfig_str('LDAP_BASE_GROUPS') + ldap_filter_params = (('objectClass', 'groupOfUniqueNames'),) gid = ldap.Attribute('gidNumber') name = ldap.Attribute('cn') description = ldap.Attribute('description', default='') - members = ldap.Relation('uniqueMember', User, backref='groups') + members = ldap.Relationship('uniqueMember', User, backref='groups') roles = [] # Shuts up pylint, overwritten by back-reference diff --git a/uffd/user/views_group.py b/uffd/user/views_group.py index 3fb1b65f..d4e77741 100644 --- a/uffd/user/views_group.py +++ b/uffd/user/views_group.py @@ -19,8 +19,8 @@ def group_acl_check(): @bp.route("/") @register_navbar('Groups', icon='layer-group', blueprint=bp, visible=group_acl_check) def index(): - return render_template('group_list.html', groups=Group.ldap_all()) + return render_template('group_list.html', groups=Group.query.all()) @bp.route("/<int:gid>") def show(gid): - return render_template('group.html', group=Group.ldap_filter_by(gid=gid)[0]) + return render_template('group.html', group=Group.query.filter_by(gid=gid)[0]) diff --git a/uffd/user/views_user.py b/uffd/user/views_user.py index 0d43feab..63f17cf4 100644 --- a/uffd/user/views_user.py +++ b/uffd/user/views_user.py @@ -27,12 +27,12 @@ def user_acl_check(): @bp.route("/") @register_navbar('Users', icon='users', blueprint=bp, visible=user_acl_check) def index(): - return render_template('user_list.html', users=User.ldap_all()) + return render_template('user_list.html', users=User.query.all()) @bp.route("/<int:uid>") @bp.route("/new") def show(uid=None): - user = User() if uid is None else User.ldap_filter_by(uid=uid)[0] + user = User() if uid is None else User.query.filter_by(uid=uid)[0] return render_template('user.html', user=user, roles=Role.query.all()) @bp.route("/<int:uid>/update", methods=['POST']) @@ -45,7 +45,7 @@ def update(uid=None): flash('Login name does not meet requirements') return redirect(url_for('user.show')) else: - user = User.ldap_filter_by(uid=uid)[0] + user = User.query.filter_by(uid=uid)[0] if not user.set_mail(request.form['mail']): flash('Mail is invalid') return redirect(url_for('user.show', uid=uid)) @@ -56,12 +56,12 @@ def update(uid=None): new_password = request.form.get('password') if uid is not None and new_password: user.set_password(new_password) + 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"]: user.roles.add(role) user.update_groups() - ldap.session.add(user) ldap.session.commit() db.session.commit() if uid is None: @@ -74,7 +74,7 @@ def update(uid=None): @bp.route("/<int:uid>/del") @csrf_protect(blueprint=bp) def delete(uid): - user = User.ldap_filter_by(uid=uid)[0] + user = User.query.filter_by(uid=uid)[0] user.roles.clear() ldap.session.delete(user) ldap.session.commit() -- GitLab