From eb07f6e414551125a216f8556ffac77010feb60b Mon Sep 17 00:00:00 2001 From: Nico von Geyso Date: Thu, 20 Sep 2012 13:05:42 +0200 Subject: Updated AccountService API AccountService is now stateless. That means every request needs its own authentication request (bind). Usage examples: * register a new user >> service = AccountService(LDAP_HOST, LDAP_BASE_DN,SERVICES) >> foo = Account('foo','foo@bar.de', password='bar') >> service.register(foo, LDAP_ADMIN_USER, LDAP_ADMIN_PASS) * authenticate a new user >> service = AccountService(LDAP_HOST, LDAP_BASE_DN,SERVICES) >> foo = service.auth('foo', 'bar') * updates an account >> foo.change_mail('a@b.de') >> foo.change_password('bar2') # changes root password >> foo.change_password('bar2', 'gitlab') # changes password for gitlab >> service.update(foo) # save changes in ldap backend # save changes in ldap backend as admin user >> service.update(foo, LDAP_ADMIN_USER, LDAP_ADMIN_USER) * delete an account >> service = AccountService(LDAP_HOST, LDAP_BASE_DN,SERVICES) >> service.delete(Account) >> service.delete('foo') * find accounts >> service = AccountService(LDAP_HOST, LDAP_BASE_DN,SERVICES) >> all_accounts = service.find(LDAP_ADMIN_USER, LDAP_ADMIN_PASS) >> print([x.uid for x in all_accounts]) --- account.py | 208 +++++++++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 146 insertions(+), 62 deletions(-) diff --git a/account.py b/account.py index 46356d5..ef70d9d 100644 --- a/account.py +++ b/account.py @@ -2,26 +2,55 @@ import ldap -LDAP_HOST = 'ldap://localhost' -LDAP_BASE_DN = 'dc=nodomain,dc=local' -LDAP_ADMIN_USER = 'root' -LDAP_ADMIN_PASS = 'root' +LDAP_HOST = 'ldap://localhost:5678' +LDAP_BASE_DN = 'dc=account,dc=spline,dc=inf,dc=fu-berlin,dc=de' +LDAP_ADMIN_USER = 'admin' +LDAP_ADMIN_PASS = 'admin' +SERVICES = ['foren','jabber''gitlab'] class AccountService: """ To simplify account management through ldap this class can be used. + The AccountService class is stateless. It means that every request needs + its own authentication request (bind). + + To test you stuff against our test setup use Port-Forwarding + ssh spline -L 5678:vm-splinux:389 -N + + * register a new user + >> service = AccountService(LDAP_HOST, LDAP_BASE_DN,SERVICES) + >> foo = Account('foo','foo@bar.de', password='bar') + >> service.register(foo, LDAP_ADMIN_USER, LDAP_ADMIN_PASS) + + * authenticate a new user + >> service = AccountService(LDAP_HOST, LDAP_BASE_DN,SERVICES) + >> foo = service.auth('foo', 'bar') + + * updates an account + >> foo.change_mail('a@b.de') + >> foo.change_password('bar2') # changes root password + >> foo.change_password('bar2', 'gitlab') # changes password for gitlab + >> service.update(foo) # save changes in ldap backend + # save changes in ldap backend as admin user + >> service.update(foo, LDAP_ADMIN_USER, LDAP_ADMIN_PASS) + + * delete an account + >> service = AccountService(LDAP_HOST, LDAP_BASE_DN,SERVICES) + >> service.delete(Account) + >> service.delete('foo') + + * find accounts + >> service = AccountService(LDAP_HOST, LDAP_BASE_DN,SERVICES) + >> all_accounts = service.find(LDAP_ADMIN_USER, LDAP_ADMIN_PASS) + >> print([x.uid for x in all_accounts]) + """ - def __init__(self, ldap_host, base_dn, admin_user, admin_pass): + def __init__(self, ldap_host, base_dn, services): self.ldap_host = ldap_host self.base_dn = base_dn - self.admin_cn = 'cn=%s,%s' % (admin_user, self.base_dn) - self.admin_pass = admin_pass - self.binded = False - - self.connection = ldap.initialize(ldap_host) - self.connection.version = ldap.VERSION3 + self.services = services def auth(self, username, password): @@ -29,68 +58,144 @@ class AccountService: Tries to authenticate a user with a given password. If the authentication is successful an Account object will be returned. """ - dn = 'uid=%s,%s' % (username, self.base_dn) + dn = 'uid=%s,ou=users,%s' % (username, self.base_dn) self._bind(dn, password) - dn, data = self.connection.search_s(dn, ldap.SCOPE_BASE)[0] - acc = Account(dn, data['cn'][0], data['sn'][0]) + + dn_user, data_user = self.connection.search_s(dn, ldap.SCOPE_SUBTREE)[0] + uid = data_user['uid'][0] + mail = data_user['mail'][0] + + dn = 'ou=services,%s' % self.base_dn + filterstr = '(uid=%s)' % uid + data_service = self.connection.search_s(dn,ldap.SCOPE_SUBTREE,filterstr) + services = [] + for entry in data_service: + cn = filter(lambda x: x.startswith('cn='), entry[0].split(','))[0] + services.append(cn.split('=')[-1]) + + acc = Account(uid, mail, services, dn_user, password) + self._unbind() + return acc - def register(self, account): + def find(self, admin_user, admin_pass, filterstr = '(objectClass=*)'): + """ + Find accounts with raw ldap filter syntax + """ + self._bind('cn=%s,%s' % (admin_user, self.base_dn), admin_pass) + + dn = 'ou=users,%s' % self.base_dn + data = self.connection.search_s(dn,ldap.SCOPE_SUBTREE,filterstr) + + accounts = [] + for a in data[1:]: + accounts.append(Account(a[1]['uid'],a[1]['mail'])) + + self._unbind() + + return accounts + + + def register(self, account, admin_user, admin_pass): """ Persists an account in the ldap backend """ - self._bind(self.admin_cn, self.admin_pass) - dn = 'uid=%s,%s' % (account.uid, self.base_dn) - self.connection.add_s(dn, account.to_ldif()) + self._bind('cn=%s,%s' % (admin_user, self.base_dn), admin_pass) + + dn = 'uid=%s,ou=users,%s' % (account.uid, self.base_dn) + attr = [ + ('objectClass', ['top','inetOrgPerson']), ('uid',account.uid), + ('sn', ' '), ('cn', ' '), ('mail', account.mail), + ('userPassword', account.password) + ] + self.connection.add_s(dn, attr) account.dn = dn + self._alter_passwords(account) + self._unbind() - def update(self, account): + def update(self, account, admin_user = None, admin_pass = None): """ Updates account informations like passwords or email. """ - self._bind(self.admin_cn, self.admin_pass) + user = 'uid=%s,ou=users' % account.uid + if admin_user: + user = 'cn=%' % user + + password = account.password + if admin_pass: + password = admin_pass + + self._bind('%s,%s' % (user, self.base_dn), password) + attr = [(ldap.MOD_REPLACE, 'mail', account.mail)] - dn = 'uid=%s,%s' % (account.uid, self.base_dn) + dn = 'uid=%s,ou=users,%s' % (account.uid, self.base_dn) self.connection.modify_s(dn, attr) self._alter_passwords(account) + self._unbind() - def delete(self, uid): + def delete(self, account, admin_user = None, admin_pass = None): """ Deletes an account permanently. """ - self._bind(self.admin_cn, self.admin_pass) - dn = 'uid=%s,%s' % (uid, self.base_dn) - self.connection.delete_s(dn) - self._unbind() + try: dn_user = account.dn + except: dn_user = 'uid=%s,ou=users,%s' % (account, self.base_dn) + + user = dn_user + if admin_user: + user = 'cn=%s,%s' % (admin_user, self.base_dn) + + password = account.password + if admin_pass: + password = admin_pass + + self._bind(user, password) + dn = ['uid=%s,cn=%s,ou=services,%s' % (account.uid,s,self.base_dn) for s in account.services] + dn.append(dn_user) + + for x in dn: + self.connection.delete_s(x) + + self._unbind() def _bind(self, dn, password): - if not self.binded: - self.connection.simple_bind_s(dn, password) - self.binded = True + self.connection = ldap.initialize(self.ldap_host) + self.connection.version = ldap.VERSION3 + + self.connection.simple_bind_s(dn, password) def _unbind(self): - if self.binded: - self.connection.unbind_s() - self.binded = False + self.connection.unbind_s() def _alter_passwords(self, account): - if self.binded and 'root' in account.passwords: + if account.new_password_root: attr = [ - (ldap.MOD_REPLACE, 'userPassword', account.passwords['root']) + (ldap.MOD_REPLACE,'userPassword',account.new_password_root) ] - dn = 'uid=%s,%s' % (account.uid, self.base_dn) + dn = 'uid=%s,ou=users,%s' % (account.uid, self.base_dn) self.connection.modify_s(dn, attr) + account.new_password_root = None + + for service, password in account.new_password_services: + attr = [ + (ldap.MOD_REPLACE, 'objectClass', LDAP_OBJECT_CLASS) + (ldap.MOD_REPLACE, 'userPassword', password) + ] + dn = 'uid=%s,ou=services,cn=%s,%s' % (account.uid, service, self.base_dn) + self.connection.modify_s(dn, attr) + + account.new_password_services = [] + class Account: @@ -103,10 +208,9 @@ class Account: self.mail = mail self.services = services self.dn = dn - self.passwords = {} - - if password: - self.change_password(password) + self.password = password + self.new_password_root = None + self.new_password_services = {} def __str__(self): @@ -120,9 +224,9 @@ class Account: password will be changed. """ if not service: - service = 'root' - - self.passwords[service] = new_password + self.new_password_root = new_password + else: + self.new_password_services[service] = new_password def change_email(self, new_mail): @@ -131,23 +235,3 @@ class Account: to make changes permanent. """ self.mail = new_mail - - - def to_ldif(self): - """ - Returns basic account data as a list of tuples (ldif format) - """ - return [ - ('objectClass', ['top','inetOrgPerson']), ('uid',self.uid), - ('sn', self.uid), ('cn', self.uid), ('mail', self.mail) - ] - - -service = AccountService(LDAP_HOST, LDAP_BASE_DN, LDAP_ADMIN_USER, LDAP_ADMIN_PASS) -#print(service.auth('testaccountt6', 'secret')) -#service.delete('testaccountt5') -#a = Account('testaccountt6', 'test@test.de', password='secret') -a = Account('testaccount4', 'mail@mail.de', password='secret') -service.update(a) -#print(service.register(a)) - -- cgit v1.2.3-1-g7c22