summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAlexander Sulfrian <alexander@sulfrian.net>2016-10-04 02:05:21 +0200
committerAlexander Sulfrian <alexander@sulfrian.net>2016-10-04 02:05:21 +0200
commit78f339ed3e1e7a058912cb9e10a055a9ed7cd7dc (patch)
tree01faed76943283c69bb1daadae9e33873f2e2ba0
parentd29d7eebc3bbbbe2bb019ddb32355d62658e5b8d (diff)
downloadweb-78f339ed3e1e7a058912cb9e10a055a9ed7cd7dc.tar.gz
web-78f339ed3e1e7a058912cb9e10a055a9ed7cd7dc.tar.bz2
web-78f339ed3e1e7a058912cb9e10a055a9ed7cd7dc.zip
backend/user: Add uidNumber
-rw-r--r--accounts/backend/user/__init__.py12
-rw-r--r--accounts/backend/user/dummy.py10
-rw-r--r--accounts/backend/user/ldap.py51
-rw-r--r--accounts/models.py3
-rwxr-xr-xmanage.py4
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)])