Version for python 3.6

- basic syntax fixes
- bytestring to utf8 conversions
- __init__.py import problem fix
This commit is contained in:
2019-05-11 12:34:44 +02:00
parent 99f33e55f9
commit 6da52b2950
7 changed files with 154 additions and 46 deletions

View File

@@ -1,4 +1,4 @@
#!/usr/bin/python
#!/usr/bin/env python
import logging
from ldapuserdir import LdapUserDir, __version__ as libversion, LdapUserDirError
@@ -7,7 +7,7 @@ import sys
import os
import pwd
from optparse import OptionParser
import ConfigParser
import configparser
import getpass
class MyOptParser(OptionParser):
@@ -17,7 +17,7 @@ class MyOptParser(OptionParser):
return self.epilog.replace('%prog',os.path.basename(sys.argv[0]))
def read_cfg(filename):
cfg = ConfigParser.ConfigParser()
cfg = configparser.ConfigParser()
try:
mylogger.debug('reading config from %s' % filename)
@@ -29,7 +29,7 @@ def read_cfg(filename):
'default_user_dn' : cfg.get('Ldap','default_user_dn'),
'default_user_pw' : cfg.get('Ldap','default_user_pw'),
}
except Exception, err:
except Exception as err:
sys.stderr.write("Error in reading configuration from %s\n" % filename)
sys.stderr.write(str(err)+'\n')
sys.exit(1)
@@ -93,14 +93,15 @@ usage += """
usage_epilog = """
Examples:
List group members
%prog svc-ra_x06sa
%prog 'svc-ra_*'
%prog svc-cluster_merlin5
%prog 'svc-cluster_*'
Get group memberships for user mueller (optionally with a group filter)
%prog -g mueller
%prog -g mueller 'svc-ra*'
%prog -g mueller 'svc-cluster_merlin5'
%prog -g mueller 'svc-cluster_*'
Add/delete users to/from a group (requires access rights!)
Add/delete users to/from a group (requires access rights defined in LDAP service!)
%prog -a svc-ra_x06sa user1 user2 user3
%prog -d svc-ra_x06sa user1 user2
@@ -108,8 +109,9 @@ usage_epilog = """
%prog -u 'mueller*'
List users matching a mail address pattern
%prog -m '*mueller@psi*
%prog -m '*mueller@psi*'
Author: 2013-19 D. Feichtinger <derek.feichtinger@psi.ch>
"""
examplecfg = """# Configuration file example:
@@ -305,7 +307,7 @@ if flag_needprivileges and user_dn == config['default_user_dn']:
logger = mylogger)
try:
user_dn = l_unpriv.systemuser2dn(loginname)
except LdapUserDirError, err:
except LdapUserDirError as err:
if str(err) == 'No such user':
sys.stderr.write('''
Error: Need priviledged user and cannot map your system user "%s"
@@ -315,7 +317,7 @@ to LDAP DN for binding (you may want to use the explicit -D user_dn option)
else:
sys.stderr.write('Uncaught Error: %s' % str(err))
except ldap.LDAPError, e:
except ldap.LDAPError as e:
sys.stderr.write('LDAP error: %s\n' % str(e))
sys.exit(1)
@@ -352,7 +354,7 @@ try:
elif mode == 'userlist':
records = ldapdir.get_users(userfilter, config['user_ou'], mssfu=flag_mssfu)
records = ldapdir.get_users(userfilter, config['user_ou'], mssfu=flag_mssfu)
ldapdir.list_users_etcpwd(records, verbose = flag_verbose)
elif mode == 'maillist':
@@ -372,7 +374,7 @@ try:
+ '\n')
#sys.stdout.write("\n".join(ldapdir.get_groups_for_user(user_to_group))
# + "\n")
except LdapUserDirError, err:
except LdapUserDirError as err:
if str(err) == "No such user":
sys.stderr.write('Error: No such user (%s)\n' % user_to_group)
else:
@@ -393,9 +395,9 @@ try:
group = args.pop(0)
ldapdir.del_groupmembers(group, args)
except ldap.INVALID_CREDENTIALS, e:
except ldap.INVALID_CREDENTIALS as e:
sys.exit(1)
except ldap.LDAPError, e:
except ldap.LDAPError as e:
sys.stderr.write('Unhandled LDAP error: %s\n' % str(e))
sys.exit(1)
# except Exception, err:

View File

@@ -1,6 +1,6 @@
package:
name: ldapuserdir
version: "2.1.5"
version: "2.2.0"
source:
path: ../../
@@ -15,6 +15,7 @@ requirements:
run:
- python
- python-ldap
- configparser
build:
preserve_egg_dir: True

View File

@@ -1,2 +1,2 @@
from ldapuserdir import LdapUserDir, LdapUserDirError
from version import __version__
from ldapuserdir.ldapuserdir import LdapUserDir, LdapUserDirError
from ldapuserdir.version import __version__

View File

@@ -90,7 +90,8 @@ class LdapUserDir(object):
self.logger = logger
self._ldap = ldap.initialize(self.serverurl, trace_level=0,
trace_file=sys.stderr)
trace_file=sys.stderr,
bytes_mode=False)
self.logger.debug('binding to: %s\n' % serverurl)
self.logger.debug('binding as user: %s\n' % user_dn)
@@ -108,6 +109,12 @@ class LdapUserDir(object):
except ldap.LDAPError:
raise
@staticmethod
def ensure_utf8(bstr):
if type(bstr) == bytes:
return bstr.decode('utf-8')
return bstr
@staticmethod
def has_dn_format(name):
"""Returns true if name has the format of a distinguished name
@@ -148,6 +155,11 @@ class LdapUserDir(object):
"""Helper for search_s_reconn.
Wraps ldap.search_ext to use paged results if desired (see self.page_size).
Returns
-------
list of tuples
list of tuples of the form (dn, attributes)
"""
if self.page_size == 0:
# Do not use paged results
@@ -165,18 +177,22 @@ class LdapUserDir(object):
serverctrls=[page_ctrl])
results = []
npages=0
while True:
_, rdata, _, resp_ctrls = self._ldap.result3(msgid)
npages += 1
results.extend(rdata)
# Extract the SimplePagedResultsControl to get the cookie.
page_ctrls = [c for c in resp_ctrls if c.controlType == SimplePagedResultsControl.controlType]
if page_ctrls == [] or page_ctrls[0].cookie == '':
pagecontrols = [c for c in resp_ctrls if c.controlType == SimplePagedResultsControl.controlType]
self.logger.debug('Retrieved page %d of results' % npages)
if not pagecontrols:
raise RuntimeError("The server ignores RFC 2696 control (paged results)")
if not pagecontrols[0].cookie:
# We're done.
break
else:
# Update the cookie to retrieve the next page.
page_ctrl.cookie = page_ctrls[0].cookie
# Update the cookie to retrieve the next page.
page_ctrl.cookie = pagecontrols[0].cookie
msgid = self._ldap.search_ext(base, scope, filterstr, attrlist, attrsonly,
serverctrls=[page_ctrl])
@@ -226,7 +242,7 @@ class LdapUserDir(object):
attempts += 1
repl = self._search_s(base, scope, filterstr, attrlist,
attrsonly)
except Exception, err:
except Exception as err:
ok = False
self.logger.warning("Got ldap error: %s" % (err,))
@@ -236,7 +252,7 @@ class LdapUserDir(object):
# we try to reconnect and rebind
try:
del self._ldap
except Exception, err:
except Exception as err:
self.logger.warning("failed to delete LDAP object: %s"
% (err,))
@@ -246,13 +262,14 @@ class LdapUserDir(object):
try:
self._ldap = ldap.initialize(self.serverurl, trace_level=0,
trace_file=sys.stderr)
trace_file=sys.stderr,
bytes_mode=False)
except ldap.SERVER_DOWN:
self.logger.warning("ldap initialization error" +
", server down (server: %s)" %
self.serverurl
+ ": %s" % (err,))
except Exception, err:
except Exception as err:
self.logger.warning("ldap initialization error" +
" (server: %s)" %
self.serverurl
@@ -263,7 +280,7 @@ class LdapUserDir(object):
except ldap.INVALID_CREDENTIALS:
self.logger.error('Authentication failure for dn:"%s"\n'
% self.user_dn)
except Exception, err:
except Exception as err:
self.logger.warning("ldap binding error" +
" (server: %s)" %
self.serverurl
@@ -342,14 +359,14 @@ class LdapUserDir(object):
if verbose:
for k in fields + ['description', 'mail', 'mobile']:
if k in entry:
sys.stdout.write('[%s:]%s:' % (k, entry[k][0]))
sys.stdout.write('[%s:]%s:' % (k, self.ensure_utf8(entry[k][0])))
else:
sys.stdout.write('[%s:]N.A.:' % k)
sys.stdout.write('\n')
else:
for k in fields:
if k in entry:
sys.stdout.write('%s:' % (entry[k][0],))
sys.stdout.write('%s:' % (self.ensure_utf8(entry[k][0])))
else:
sys.stdout.write('N.A.:')
sys.stdout.write('\n')
@@ -384,8 +401,9 @@ class LdapUserDir(object):
if len(r) == 0:
raise LdapUserDirError("No such user")
self.logger.debug('systemuser2dn: dn = %s' % r[0][0])
return r[0][0]
dn = self.ensure_utf8(r[0][0])
self.logger.debug('systemuser2dn: dn = %s' % dn)
return dn
def get_groups_struct(self, gfilter='*', ou=None, mssfu=False):
"""searches for groups that match filter
@@ -463,7 +481,7 @@ class LdapUserDir(object):
grplist = []
if 'memberOf' in r[0][1]:
grplist = r[0][1]['memberOf']
grplist = [self.ensure_utf8(g) for g in r[0][1]['memberOf']]
if mssfu:
srch = '(msSFU30GidNumber=*)'
@@ -522,7 +540,7 @@ class LdapUserDir(object):
if not returndn:
try:
reslist = [self.dn_to_cn(grp) for grp in reslist]
except RuntimeError, e:
except RuntimeError as e:
self.logger.error(str(e))
if gfilter:
@@ -563,16 +581,16 @@ class LdapUserDir(object):
indent_increment = 3 # amount to indent members
for dn, entry in r:
if returndn:
print("%sgroup: %s" % (' '*indent, dn)),
print("%sgroup: %s" % (' '*indent, dn), end=''),
else:
print("%sgroup: %s" % (' '*indent, entry['cn'][0])),
print("%sgroup: %s" % (' '*indent, self.ensure_utf8(entry['cn'][0])), end='')
if not 'msSFU30GidNumber' in entry:
gid = '---'
else:
gid = entry['msSFU30GidNumber'][0]
print "(%s)" % gid
gid = self.ensure_utf8(entry['msSFU30GidNumber'][0])
print("(%s)" % gid)
if 'member' in entry:
for member in entry['member']:
for member in (self.ensure_utf8(m) for m in entry['member']):
# Check if member is itself a group. This might be PSI-specific
is_group = self._is_group(member)
if recursive and is_group:
@@ -595,11 +613,11 @@ class LdapUserDir(object):
if not 'msSFU30GidNumber' in entry:
gid = '---'
else:
gid = entry['msSFU30GidNumber'][0]
gid = self.ensure_utf8(entry['msSFU30GidNumber'][0])
sys.stdout.write("%s:IGNORE:%s:" % (entry['cn'][0], gid))
sys.stdout.write("%s:IGNORE:%s:" % (self.ensure_utf8(entry['cn'][0]), gid))
if 'member' in entry:
members = [self.dn_to_cn(dn) for dn in entry['member']]
members = [self.dn_to_cn(self.ensure_utf8(dn)) for dn in entry['member']]
if recursive:
members = [m for cn in members for m in self._get_all_members(cn)]
sys.stdout.write(",".join(members) + "\n")
@@ -641,6 +659,7 @@ class LdapUserDir(object):
if "member" in entry:
# not an empty group
for member in entry["member"]:
member = self.ensure_utf8(member)
# Shortcut recursion for known leaves
#if not LdapUserDir._is_group(member):
# yield member

View File

@@ -1 +1 @@
__version__ = "2.1.5"
__version__ = "2.2.0"

View File

@@ -8,7 +8,8 @@ from setuptools import setup
# we get the package version from inside our package, since we then
# can use it also from within the package
#from ldapuserdir.version import __version__
execfile('ldapuserdir/version.py')
# py2.7 way: execfile('ldapuserdir/version.py')
exec(open("ldapuserdir/version.py").read())
setup(
name="ldapuserdir",
@@ -25,5 +26,5 @@ setup(
# following format is (targetdir,[list of files])
data_files=[('etc', ['etc/ldapuserdir-ctl.cfg'])],
install_requires=['python-ldap']
#install_requires=['python-ldap']
)

View File

@@ -384,3 +384,88 @@ KeyError: 'displayName'
I implemented a workaround by filtering out the None elements.
** [2019-05-10 Fri] compatibility with python-3.6
*** RESOLVED simple fixes
CLOSED: [2019-05-11 Sat 09:54]
:LOGBOOK:
- State "RESOLVED" from "BUG" [2019-05-11 Sat 09:54]
- State "BUG" from [2019-05-11 Sat 09:54]
:END:
- Exceptions: use new syntax
#+begin_src python
except SomeException as err
#+end_src
- print statements
*** RESOLVED importer namespace problem
CLOSED: [2019-05-11 Sat 09:55]
:LOGBOOK:
- State "RESOLVED" from "BUG" [2019-05-11 Sat 09:55]
- State "BUG" from [2019-05-11 Sat 09:54]
:END:
- __init__.py only works with changing to relative import
: from ldapuserdir import LdapUserDir, LdapUserDirError
now must be made explicit with
: from ldapuserdir.ldapuserdir import LdapUserDir, LdapUserDirError
*** RESOLVED hangs in LDAP paging call
CLOSED: [2019-05-11 Sat 12:28]
:LOGBOOK:
- State "RESOLVED" from "BUG" [2019-05-11 Sat 12:28]
- State "BUG" from [2019-05-11 Sat 10:05]
:END:
The loop for reading the paged results never reaches the break condition
in ldapuserdir.py:_search_s
#+begin_src python
page_ctrl = SimplePagedResultsControl(criticality=True,
size=self.page_size,
cookie='')
msgid = self._ldap.search_ext(base, scope, filterstr, attrlist,
attrsonly,
serverctrls=[page_ctrl])
results = []
while True:
_, rdata, _, resp_ctrls = self._ldap.result3(msgid)
results.extend(rdata)
self.logger.debug('DEREK: in paging result call: results= %s' % results)
# .... CUT ....
# Extract the SimplePagedResultsControl to get the cookie.
page_ctrls = [c for c in resp_ctrls if c.controlType == SimplePagedResultsControl.controlType]
if page_ctrls == [] or page_ctrls[0].cookie == '':
# We're done.
break
else:
# Update the cookie to retrieve the next page.
page_ctrl.cookie = page_ctrls[0].cookie
#+end_src
The conditions for the break need to be changed.
Good resource: https://medium.com/@alpolishchuk/pagination-of-ldap-search-results-with-python-ldap-845de60b90d2
#+begin_src python
if not page_ctrls:
raise RuntimeError("The server ignores RFC 2696 control (paged results)")
if not page_ctrls[0].cookie:
# We're done.
break
# Update the cookie to retrieve the next page.
page_ctrl.cookie = page_ctrls[0].cookie
#+end_src
*** RESOLVED In python3 the ldap calls return bytestrings
CLOSED: [2019-05-11 Sat 12:28]
:LOGBOOK:
- State "RESOLVED" from "BUG" [2019-05-11 Sat 12:28]
- State "BUG" from [2019-05-11 Sat 12:28]
:END:
: ldapuserdir-ctl --debug -u feichtinger
: b'feichtinger':b'3896':b'3896':b'840':b'Feichtinger Derek Heinrich':b'/bin/bash':b'/afs/psi.ch/user/f/feichtinger':
The (dn, attributes) that are returned by _search_s contain attributes the
values of which all are bytestrings.
python-ldap returns bytestrings and in py3 a standard string is now utf-8.
This leads to all kinds of problems. I define a function
ensure_utf8 ẗo fix the issue.