From 78f339ed3e1e7a058912cb9e10a055a9ed7cd7dc Mon Sep 17 00:00:00 2001 From: Alexander Sulfrian Date: Tue, 4 Oct 2016 02:05:21 +0200 Subject: backend/user: Add uidNumber --- accounts/backend/user/__init__.py | 12 +++++++++ accounts/backend/user/dummy.py | 10 ++++++-- accounts/backend/user/ldap.py | 51 ++++++++++++++++++++++++++++++++------- accounts/models.py | 3 ++- manage.py | 4 +-- 5 files changed, 66 insertions(+), 14 deletions(-) diff --git a/accounts/backend/user/__init__.py b/accounts/backend/user/__init__.py index 57ba952..e72302a 100644 --- a/accounts/backend/user/__init__.py +++ b/accounts/backend/user/__init__.py @@ -115,6 +115,7 @@ class Backend(object): if account.password is None: raise ValueError("Password required for register") + account.uidNumber = self._get_next_uidNumber() self._store(account) def update(self, account, as_admin=False): @@ -134,3 +135,14 @@ class Backend(object): Persists an account in the backend. """ raise NotImplementedError() + + def _get_next_uidNumber(self): + """ + Get the next free uid number. + + During registration the backend need to assign a unique + uidNumber for each user. Most backends are unable to + automaticall increment a value. So this method is used by + register() to get the next free uid number in a safe way. + """ + raise NotImplementedError() diff --git a/accounts/backend/user/dummy.py b/accounts/backend/user/dummy.py index 867db2e..2f935d8 100644 --- a/accounts/backend/user/dummy.py +++ b/accounts/backend/user/dummy.py @@ -38,9 +38,10 @@ class DummyBackend(Backend): super(DummyBackend, self).__init__(app) self._storage = [ - Account('test', 'test@accounts.spline.de', password='test'), - Account('test2', 'test2@accounts.spline.de', password='test2'), + Account('test', 'test@accounts.spline.de', password='test', uidNumber=1), + Account('test2', 'test2@accounts.spline.de', password='test2', uidNumber=2), ] + self._next_uidNumber = 3 def auth(self, username, password): """ @@ -96,3 +97,8 @@ class DummyBackend(Backend): raise self.InvalidPasswordError("Invalid password") self._storage = [acc for acc in self._storage if acc.uid != account.uid] + + def _get_next_uidNumber(self): + value = self._next_uidNumber + self._next_uidNumber += 1 + return value diff --git a/accounts/backend/user/ldap.py b/accounts/backend/user/ldap.py index ea1b7fc..0f32530 100644 --- a/accounts/backend/user/ldap.py +++ b/accounts/backend/user/ldap.py @@ -5,7 +5,7 @@ import ldap3 from ldap3.utils.conv import escape_filter_chars from ldap3.utils.dn import escape_attribute_value -from . import Backend, InvalidPasswordError, NoSuchUserError +from . import Backend, InvalidPasswordError, NoSuchUserError, ShouldNotHappen from accounts.models import Account @@ -51,20 +51,23 @@ class LdapBackend(Backend): uid = None mail = None + uidNumber = None services = [] conn.search(user_dn, '(objectClass=*)', - attributes=['objectClass', 'uid', 'mail', 'cn']) + attributes=['objectClass', 'uid', 'mail', 'cn', 'uidNumber']) for entry in conn.entries: - if 'inetOrgPerson' in entry.objectClass.values: + if 'splineAccount' in entry.objectClass.values: uid = entry.uid.value mail = entry.mail.value + uidNumber = entry.uidNumber.value elif 'servicePassword' in entry.objectClass.value: services.append(entry.cn.value) - if uid is None or mail is None: + if uid is None or mail is None or uidNumber is None: raise NoSuchUserError("User not found") - return Account(uid, mail, services, password) + return Account(uid, mail, services, password, + uidNumber=uidNumber) def find(self, filters=None, wildcard=False): """ @@ -73,7 +76,7 @@ class LdapBackend(Backend): if filters is None: filters = dict() - filters['objectClass'] = 'inetOrgPerson' + filters['objectClass'] = 'splineAccount' filter_as_list = ['(%s=%s)' % (attr, _escape(value, wildcard)) for attr, value in filters.items()] filterstr = '(&%s)' % ''.join(filter_as_list) @@ -84,9 +87,10 @@ class LdapBackend(Backend): accounts = [] try: conn.search(base_dn, filterstr, search_scope=ldap3.LEVEL, - attributes=['uid', 'mail']) + attributes=['uid', 'mail', 'uidNumber']) for entry in conn.entries: - accounts.append(Account(entry.uid.value, entry.mail.value)) + accounts.append(Account(entry.uid.value, entry.mail.value, + uidNumber=entry.uidNumber.value)) except ldap3.LDAPException: pass @@ -97,11 +101,12 @@ class LdapBackend(Backend): user_dn = self._format_dn([('uid', account.uid), ('ou', 'users')]) attrs = { - 'objectClass': ['top', 'inetOrgPerson'], + 'objectClass': ['top', 'inetOrgPerson', 'splineAccount'], 'uid': _escape(account.uid), 'sn': 'n/a', 'cn': _escape(account.uid), 'mail': _escape(account.mail), + 'uidNumber': _escape(account.uidNumber), } conn.add(user_dn, attributes=attrs) @@ -195,3 +200,31 @@ class LdapBackend(Backend): del account.new_password_services[service] + def _get_last_uidNumber(self, conn): + uidNumber_dn = self._format_dn([('cn', 'uidMax'), ('ou', 'other')]) + conn.search(uidNumber_dn, '(objectClass=uidNumberMaximum)', + attributes=['uidNumber']) + for entry in conn.entries: + return entry.uidNumber.value + + raise ShouldNotHappen('Last uidNumber not found.') + + def _get_next_uidNumber(self): + conn = self._connect_as_admin() + + uidNumber_dn = self._format_dn([('cn', 'uidMax'), ('ou', 'other')]) + uidNumber = self._get_last_uidNumber(conn) + + # Try to acquire next uidNumber + for i in [0, 1, 2, 3, 4, 5]: + try: + conn.modify(uidNumber_dn, {'uidNumber': [ + (ldap3.MODIFY_DELETE, [uidNumber + i]), + (ldap3.MODIFY_ADD, [uidNumber + i + 1]), + ]) + + if conn.result == ldap3.RESULT_SUCCESS: + return uidNumber + i + 1 + except ldap3.LDAPOperationResult: + pass + raise ShouldNotHappen('Unable to get next uidNumber, try again.') diff --git a/accounts/models.py b/accounts/models.py index 8f1b541..82dab45 100644 --- a/accounts/models.py +++ b/accounts/models.py @@ -12,13 +12,14 @@ class Account(UserMixin): """ _ready = False - def __init__(self, uid, mail, services=None, password=None): + def __init__(self, uid, mail, services=None, password=None, uidNumber=None): self.uid = uid.encode('utf8') if isinstance(uid, unicode) else uid self.services = list() if services is None else services self.password = password.encode('utf8') if isinstance(password, unicode) else password self.new_password_root = None self.new_password_services = {} self.attributes = {} + self.uidNumber = uidNumber self._set_attribute('mail', mail) self._ready = True diff --git a/manage.py b/manage.py index d7c7e54..e4de15f 100755 --- a/manage.py +++ b/manage.py @@ -40,8 +40,8 @@ class ListUsers(Command): yield user def run(self, locked, only_locked): - table = TablePrinter(['Name', 'E-Mail']) - table.output([(user.uid, user.mail) + table = TablePrinter(['Name', 'E-Mail', 'Uid']) + table.output([(user.uid, user.mail, user.uidNumber) for user in self._get_users(locked, only_locked)]) -- cgit v1.2.3-1-g7c22