rewrote some functions to have consistent use of DN and mssfu
Also improved CLI options usage
This commit is contained in:
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user