rewrote some functions to have consistent use of DN and mssfu

Also improved CLI options usage
This commit is contained in:
2013-05-02 18:06:33 +02:00
parent a3e4694971
commit f7756cbfa9
2 changed files with 170 additions and 117 deletions

View File

@@ -57,7 +57,7 @@ flag_needprivileges = False
userfilter = '-'
user_pw = ''
mode = 'list'
mode = 'grouplist'
mylogger = logging.getLogger(os.path.basename(sys.argv[0]))
mylogger.setLevel(logging.DEBUG)
@@ -71,10 +71,13 @@ mylogger.addHandler(ch)
# OPTION PARSING
usage = """%prog [options] groupname [usernames]
Used to inspect or change members of a group in Active Directory
User names can be given as full distinguished names or just as
the short names (in that case they will be extended by the
standard OU extension)
Shows or changes members of a group in Active Directory. Also
can be used to investigate users and their group memberships.
User and group names can be given as full distinguished names or
just as the short system names (the tool will try to figure out
the full names based on the standard OU extensions in the config
and a lookup)
The configuration is read from a configuration file. Default
locations:
@@ -87,8 +90,9 @@ usage_epilog = """
%prog svc_ra_x06sa
%prog 'svc_ra_*'
Get group memberships for user mueller
Get group memberships for user mueller (optionally with a group filter)
%prog -g mueller
%prog -g mueller 'svc_ra_*'
Add/delete users to/from a group (requires access rights!)
%prog -a svc_ra_x06sa user1 user2 user3
@@ -104,14 +108,18 @@ examplecfg = """# Configuration file example:
[Ldap]
# URL for contacting the LDAP server
serverurl = ldaps://d.psi.ch:636
# base ldap path under which all users are found
user_ou = OU=Users,OU=PSI,DC=d,DC=psi,DC=ch
# base ldap path under which groups are found
group_ou = ou=Groups,ou=PSI,dc=d,dc=psi,dc=ch
# minimally privileged Ldap user and password for running normal
# lookup queries
default_user_dn = CN=linux_ldap,OU=Services,OU=IT,DC=d,DC=psi,DC=ch
default_user_pw = DEFaultPassword
# Optional:
# default filter to be used for group searches
default_group_filter = svc-ra*
@@ -122,95 +130,98 @@ parser.add_option('-a',
action = 'store_true',
dest = 'flag_add',
help = 'add group members',
)
)
parser.add_option('-d',
action = 'store_true',
dest = 'flag_del',
help = 'delete group members',
)
parser.add_option('-c',
action = 'store',
dest = 'cfgfile',
help = 'path of a config file',
default = ''
)
parser.add_option('--configfile',
action = 'store_true',
dest = 'flag_examplecfg',
help = 'show an example configuration file',
default= False
)
parser.add_option('-u',
action = 'store',
dest = 'userfilter',
help = 'list all matching ldap users that have defined unix mappings',
)
parser.add_option('--debug',
action = 'store_true',
dest = 'flag_debug',
help = 'debug mode: log messages at debug level',
)
parser.add_option('-D',
action = 'store',
dest = 'user_dn',
help = 'DN or CN of ldap user for binding to the AD server (%s)' % config['default_user_dn'],
default = None
)
parser.add_option('-f',
action = 'store',
dest = 'pwfile',
help = 'path to password file (without this pwd will be prompted for)',
default = ''
)
)
parser.add_option('-g',
action = 'store',
dest = 'user_to_group',
help = 'get group memberships for this user',
default = ''
)
)
parser.add_option('-u',
action = 'store',
dest = 'userfilter',
help = 'list all matching ldap users that have defined unix mappings (always implies -n)'
)
parser.add_option('--dn',
action = 'store_true',
dest = 'flag_showdn',
help = 'show full DNs in the output',
default = False
)
parser.add_option('-c',
action = 'store',
dest = 'cfgfile',
help = 'path of a config file (else tries default locations)',
default = ''
)
parser.add_option('-D',
action = 'store',
dest = 'user_dn',
help = 'DN or CN of ldap user for binding to the AD server (%s)' % config['default_user_dn'],
default = None
)
parser.add_option('-f',
action = 'store',
dest = 'pwfile',
help = 'path to password file (without this pwd will be prompted for)',
default = ''
)
parser.add_option('-v',
action = 'store_true',
dest = 'flag_verbose',
help = 'use more verbose output (with user list only)',
help = 'use more verbose output (for group and user lists)',
default = False
)
)
parser.add_option('--user-ou',
action = 'store',
dest = 'user_ou',
help = 'default OU for users (%s)' % config['user_ou'],
default = None
)
)
parser.add_option('--group-ou',
action = 'store',
dest = 'group_ou',
help = 'default OU for groups (%s)' % config['group_ou'],
default = None
)
)
parser.add_option('-n', '--allow-no-mssfu',
action = 'store_false',
dest = 'flag_mssfu',
help = 'do not restrict to entries with unix (msSFU) mappings',
default = True
)
)
parser.add_option('--debug',
action = 'store_true',
dest = 'flag_debug',
help = 'debug mode: log messages at debug level',
)
parser.add_option('--showconf',
action = 'store_true',
dest = 'flag_examplecfg',
help = 'show an example configuration file',
default= False
)
parser.add_option('-V',
action = 'store_true',
dest = 'flag_version',
help = 'show version information',
default = False
)
)
(options, args) = parser.parse_args()
group = None
usernames = []
if len(args) > 0:
group = args.pop(0)
usernames = args
if options.flag_debug:
ch.setLevel(logging.DEBUG)
flag_verbose = options.flag_verbose
userfilter = options.userfilter
flag_mssfu = options.flag_mssfu
flag_showdn = options.flag_showdn
if options.flag_version:
sys.stdout.write('Library version: ' + libversion + '\n')
@@ -244,9 +255,6 @@ if options.group_ou:
if options.user_ou:
config['user_ou'] = options.user_ou
flag_verbose = options.flag_verbose
userfilter = options.userfilter
if options.flag_del:
mode = 'del'
flag_needprivileges = True
@@ -259,10 +267,6 @@ if options.user_to_group:
mode = "user_to_group"
user_to_group = options.user_to_group
if (mode == 'add' or mode == 'del') and len(usernames) == 0:
sys.stderr.write("Error: Not enough arguments\n")
sys.exit(1)
# this we should actually do with systemuser2dn
if ',' not in user_dn:
user_dn = 'CN=' + user_dn + ',' + config['user_ou']
@@ -313,26 +317,46 @@ try:
user_ou = config['user_ou'],
logger=mylogger)
if mode == 'list':
if mode == 'grouplist':
sfilter = config['default_group_filter']
if group:
sfilter = group
ldapdir.list_groups(sfilter, mssfu=flag_mssfu)
if args:
sfilter = args.pop(0)
ldapdir.list_groups(sfilter, mssfu=flag_mssfu, returndn=flag_showdn,
verbose=flag_verbose)
elif mode == 'userlist':
ldapdir.list_users_etcpwd(userfilter, verbose = flag_verbose)
elif mode == 'user_to_group':
sfilter = None
if args:
sfilter = args.pop(0)
try:
dn = ldapdir.systemuser2dn(user_to_group)
sys.stdout.write("\n".join(ldapdir.get_memberof(dn, mssfu=flag_mssfu)) + '\n')
sys.stdout.write("\n".join(ldapdir.get_groups_for_user(user_to_group,
gfilter=sfilter,
mssfu=flag_mssfu,
returndn=flag_showdn))
+ '\n')
#sys.stdout.write("\n".join(ldapdir.get_groups_for_user(user_to_group))
# + "\n")
except LdapUserDirError, err:
sys.stderr.write('Error: ' + str(err) +'\n')
sys.exit(1)
elif mode == 'add':
ldapdir.add_groupmembers(group, usernames)
if len(args) < 2:
sys.stderr.write("Error: Not enough arguments\n")
sys.exit(1)
group = args.pop(0)
ldapdir.add_groupmembers(group, args)
elif mode == 'del':
ldapdir.del_groupmembers(group, usernames)
if len(args) < 2:
sys.stderr.write("Error: Not enough arguments\n")
sys.exit(1)
group = args.pop(0)
ldapdir.del_groupmembers(group, args)
except ldap.INVALID_CREDENTIALS, e:
sys.exit(1)
except ldap.LDAPError, e:

View File

@@ -103,6 +103,31 @@ class LdapUserDir(object):
if ',' in name:
return True
return False
@staticmethod
def dn_to_cn(dn):
"""
Parameters
----------
dn :
Raises
------
RuntimeError
Returns
-------
str
"""
"""transforms a DN to a CN
"""
reg = re.compile(r'^CN=([^,]+),')
m = reg.match(dn)
if m:
return m.group(1)
else:
raise RuntimeError("failed to convert DN to CN (%s)" % dn)
def search_s_reconn(self, base, scope, filterstr='(objectClass=*)',
attrlist=None, attrsonly=0, recon_attempts = 2):
@@ -146,9 +171,8 @@ class LdapUserDir(object):
attrsonly)
except Exception, err:
ok = False
self.logger.warning("Got ldap error: Reconnecting (try %s). "
% attempts +
"Error was: " + str(err))
self.logger.warning("Got ldap error: %s" % str(err))
if attempts >= recon_attempts:
raise
@@ -159,6 +183,8 @@ class LdapUserDir(object):
self.logger.warning("failed to delete LDAP object: %s"
% str(err))
self.logger.warning("Trying reconnecting to ldap (attempt %s)"
% attempts)
time.sleep(1)
try:
@@ -324,21 +350,20 @@ class LdapUserDir(object):
# print e
return r
def get_memberof(self, dn, gfilter=None, returndn = False,
recursive = True, mssfu = False):
def get_memberof(self, dn, recursive=True, mssfu=False):
"""Get all memberOf attributes for an dn (optionally recursively)
This routine relies on the LDAP directory keeping memberOf attributes
in the individual entries
in the individual entries.
Note that a recursive search may not show all groups, if mssfu
was selected, and a group in the hierarchy tree has no msSFU
settings.
Parameters
----------
dn : str
distinguished name
gfilter : str, optional
filter expression used for the cn part of the results
returndn : bool, optional
If True then return DNs, else CNs
recursive : bool, optional
if True, recurse into hierarchical groups
mssfu : bool, optional
@@ -347,6 +372,7 @@ class LdapUserDir(object):
Returns
-------
list
"""
self.logger.debug('get_memberof for %s' % dn)
@@ -380,13 +406,10 @@ class LdapUserDir(object):
tmplist.extend(self.get_memberof(g))
grplist.extend(tmplist)
if gfilter:
grplist = [i for i in grplist if fnmatch.fnmatch(i,'CN=%s' % gfilter)]
return grplist
def get_groups_for_user(self, user, gfilter='*', ou=None, returndn = False,
def get_groups_for_user(self, user, gfilter = None, returndn = False,
mssfu=False):
"""Get groups for a particular user from LDAP.
@@ -397,8 +420,6 @@ class LdapUserDir(object):
----------
user : str
system username or user DN
gfilter : str, optional
filter expression used for the cn part of the ldap dn
ou : str, optional
The organisational unit to be used in the ldap search
returndn : bool, optional
@@ -415,39 +436,26 @@ class LdapUserDir(object):
RuntimeError
if CN cannot be identified in a resulting group
"""
if ou == None:
group_ou = self.group_ou
if not self.has_dn_format(user):
dnname = self.systemuser2dn(user)
else:
dnname = user
if mssfu:
srch = '(&(objectClass=Group)(msSFU30GidNumber=*)(cn=%s)(member=%s))' % (gfilter, dnname)
srch = '(&(objectClass=Group)(cn=%s)(member=%s))' % (gfilter, dnname)
reslist = self.get_memberof(dnname, recursive=True, mssfu=mssfu)
self.logger.debug('get_groups_for_user: %s' % srch)
r = self.search_s_reconn(group_ou, ldap.SCOPE_SUBTREE, srch)
if not returndn:
try:
reslist = [self.dn_to_cn(grp) for grp in reslist]
except RuntimeError, e:
self.logger.error(str(e))
reslist = []
for dn, entry in r:
reslist.append(dn)
if gfilter:
reslist = fnmatch.filter(reslist, gfilter)
return reslist
if returndn:
return reslist
cnlist = []
reg = re.compile(r'^CN=([^,]+),')
for dn in reslist:
m = reg.match(dn)
if m:
cnlist.append(m.group(1))
else:
raise RuntimeError("Could not match CN in DN (%s)" % dn)
return cnlist
def list_groups(self, filter = '*', ou = None, mssfu=False):
def list_groups(self, filter = '*', ou = None, mssfu=False,
returndn = False, verbose = False):
"""Prints a list of groups from the LDAP directory to stdout
Parameters
@@ -458,18 +466,39 @@ class LdapUserDir(object):
organisational unit to be used in the ldap search
mssfu : bool, optional
Whether to only show users with mssfu mappings
returndn : bool
If true, return full DNs
verbose : bool
If true, print one name per line
"""
if returndn:
verbose = True
r = self.get_groups_struct(filter, ou, mssfu)
if len(r) == 0:
sys.stderr.write("Error: no groups found (filter: %s)\n" % filter)
return
for dn, entry in r:
print entry['cn'][0]
if 'member' in entry:
for cn in entry['member']:
print ' member: ', cn
if verbose:
for dn, entry in r:
if returndn:
print "group: %s" % dn,
else:
print "group: %s" % entry['cn'][0],
print "(%s)" % entry['msSFU30GidNumber'][0]
if 'member' in entry:
for cn in entry['member']:
if returndn:
print ' member: ', cn
else:
print ' member: ', self.dn_to_cn(cn)
else:
for dn, entry in r:
print "%s:IGNORE:%s:" % (entry['cn'][0], entry['msSFU30GidNumber'][0]),
if 'member' in entry:
print ",".join([self.dn_to_cn(dn) for dn in entry['member']])
def _mod_groupmembers(self, ldapmode, dngroup, usernames):
"""modifies (adds/deletes) members of an LDAP group entry