diff --git a/sync_ldap_groups_to_svn_authz.py b/sync_ldap_groups_to_svn_authz.py index 22bc605..5b14a62 100755 --- a/sync_ldap_groups_to_svn_authz.py +++ b/sync_ldap_groups_to_svn_authz.py @@ -65,6 +65,9 @@ # This is the attribute of the group object that stores the group memberships. #group_member_attribute = "member" +# This is the used LDAP protocol version +#ldap_protocol_version = 3 + # This is the query/filter used to identify user objects. #user_query = "objectClass=user" @@ -115,6 +118,14 @@ def bind(): ldapobject = ldap.initialize(url) + if(ldap_protocol_version == 1): + ldapobject.protocol_version = ldap.VERSION1 + elif(ldap_protocol_version == 2): + ldapobject.protocol_version = ldap.VERSION2 + else: + ldapobject.protocol_version = ldap.VERSION3 + + ldapobject.set_option(ldap.OPT_REFERRALS, 0) #pagiing result only works without referrals enabled ldapobject.bind_s(bind_dn, bind_password) if verbose: @@ -126,13 +137,14 @@ def bind(): return ldapobject # bind() - + def search_for_groups(ldapobject): """This function will search the LDAP directory for group definitions.""" groups = [] result_set = get_ldap_search_resultset(base_dn, group_query, ldapobject) + if (len(result_set) == 0): if not silent: sys.stderr.write("The group_query %s did not return any results.\n" % group_query) @@ -179,17 +191,36 @@ def get_groups(ldapobject): def get_ldap_search_resultset(base_dn, group_query, ldapobject, scope=ldap.SCOPE_SUBTREE): """This function will return a query result set.""" + cookie = '' + page_size = 1000 + criticality = False result_set = [] - result_id = ldapobject.search(base_dn, scope, group_query) - while 1: - result_type, result_data = ldapobject.result(result_id, 0) - if (result_type == ldap.RES_SEARCH_ENTRY): + first_pass = True + pg_ctrl = ldap.controls.SimplePagedResultsControl(criticality, page_size, cookie) + + #run though results at a time, to avoid exceeding ldap size limit + while first_pass or pg_ctrl.cookie: + first_pass = False + try: + msgid = ldapobject.search_ext(base_dn, scope, group_query, serverctrls=[pg_ctrl]) + except: + sys.stderr.write("ERROR: \n" + str(sys.exc_info()[0])) + sys.exit(1) + + while True: + result_data = None + result_type, result_data, msgid, serverctrls = ldapobject.result3(msgid, all=0) + + if serverctrls is not None and len(serverctrls) > 0: + pg_ctrl.cookie = serverctrls[0].cookie + + if (result_type == ldap.RES_SEARCH_ENTRY): result_set.append(result_data) - elif (result_type == ldap.RES_SEARCH_RESULT): - break + elif (result_type == ldap.RES_SEARCH_RESULT): + break - return result_set + return result_set # get_ldap_search_resultset() @@ -229,7 +260,7 @@ def get_members_from_group(group, ldapobject): else: # Check to see if this member is really a group mg = get_ldap_search_resultset(member, group_query, ldapobject) - + if (len(mg) == 1): # The member is a group if followgroups: @@ -287,11 +318,11 @@ def create_group_model(groups, ldapobject): def get_dict_key_from_value(dict, value): """Returns the key of the dictionary entry with the matching value.""" - + for k, v in dict.iteritems(): if (v == value): return k - + return None # get_dict_key_from_value() @@ -303,7 +334,7 @@ def create_group_map(groups): if groups: for group in groups: cn = simplify_name(group[1]['cn'][0]) - + if (not groupmap.has_key(cn)): groupmap[cn] = group[0] else: @@ -311,11 +342,11 @@ def create_group_map(groups): dups[cn] = 1 else: index = dups[cn] - + dups[cn] = (index + 1) - + groupmap[cn + str(dups[cn])] = group[0] - + return groupmap # create_group_map() @@ -339,21 +370,21 @@ def print_group_model(groups, memberships): header = header_start + header_middle + header_end footer = "### End generated content: " + application_name + " ###\n" text_after_content = "" - + file = None filemode = None tmp_fd, tmp_authz_path = tempfile.mkstemp() - + if ((authz_path != None) and (authz_path != "None")): if (os.path.exists(authz_path)): filemode = os.stat(authz_path) file = open(authz_path, 'r') tmpfile = open(tmp_authz_path, 'w') - + # Remove previous generated content inside_content = False before_content = True - + for line in file.readlines(): # read from the existing file if (inside_content): # currently between header and footer if (line.find(footer) > -1): # footer found @@ -368,14 +399,14 @@ def print_group_model(groups, memberships): tmpfile.write(line) # found before the header: write directly else: text_after_content += line # found after the header, write to a temporary variable - + file.close() tmpfile.close() - + if (os.path.exists(tmp_authz_path)): cp = ConfigParser.ConfigParser() cp.read(tmp_authz_path) - + if (not cp.has_section("groups")): tmpfile = open(tmp_authz_path, 'a') tmpfile.write("[groups]\n") @@ -385,32 +416,32 @@ def print_group_model(groups, memberships): tmpfile = open(tmp_authz_path, 'a') tmpfile.write("[groups]\n") tmpfile.close() - + needs_new_line = False - + tmpfile = open(tmp_authz_path, 'r') if (tmpfile.readlines()[-1].strip() != ''): # if the last line is not empty needs_new_line = True # ask to insert a new empty line at the end tmpfile.close() - + tmpfile = open(tmp_authz_path, 'a') - + if (needs_new_line): tmpfile.write("\n") - + tmpfile.write(header + "\n") - + groupmap = create_group_map(groups) if groups: for i in range(len(groups)): if (i != 0): tmpfile.write("\n") - + short_name = simplify_name(get_dict_key_from_value(groupmap, groups[i][0])) - + tmpfile.write(short_name + " = ") - + users = [] for j in range(len(memberships[i])): user = None @@ -422,7 +453,7 @@ def print_group_model(groups, memberships): if not silent: sys.stderr.write("[WARNING]: subgroup not in search scope: %s. This means " % memberships[i][j].replace("GROUP:","") + - "you won't have all members in the SVN group: %s.\n" % + "you won't have all members in the SVN group: %s.\n" % short_name) else: user = memberships[i][j] @@ -431,22 +462,22 @@ def print_group_model(groups, memberships): users.append(user) tmpfile.write(", ".join(users)) - + generate_legend(tmpfile, groups) - + tmpfile.write("\n" + footer) - + tmpfile.write(text_after_content) # write back original content to file - + tmpfile.close() if authz_path: if (os.path.exists(authz_path + ".bak")): os.remove(authz_path + ".bak") - + if (os.path.exists(authz_path)): shutil.move(authz_path, authz_path + ".bak") - + shutil.move(tmp_authz_path, authz_path) os.chmod(authz_path, filemode.st_mode) else: @@ -470,14 +501,14 @@ def generate_legend(output, groups): output.write("########### " + application_name +" (Legend) ##########\n") output.write("###########################################################" + "#####################\n") - + groupmap = create_group_map(groups) - + for group in groups: short_name = simplify_name(get_dict_key_from_value(groupmap, group[0])) - + output.write("### " + short_name + " = " + str(group[0]) + "\n") - + output.write("###########################################################" + "#####################\n") @@ -493,6 +524,7 @@ def load_cli_properties(parser): global group_query global group_dns global group_member_attribute + global ldap_protocol_version global user_query global userid_attribute global followgroups @@ -500,7 +532,7 @@ def load_cli_properties(parser): global keep_names global silent global verbose - + global is_outfile_specified (options, args) = parser.parse_args(args=None, values=None) @@ -512,6 +544,7 @@ def load_cli_properties(parser): group_query = options.group_query group_dns = options.group_dns group_member_attribute = options.group_member_attribute + ldap_protocol_version = options.ldap_protocol_version user_query = options.user_query userid_attribute = options.userid_attribute followgroups = options.followgroups @@ -519,7 +552,7 @@ def load_cli_properties(parser): keep_names = options.keep_names silent = options.silent verbose = options.verbose - + is_outfile_specified = (authz_path != None) and (authz_path != "None") # load_cli_properties() @@ -563,6 +596,10 @@ def create_cli_parser(): "group memberships. " \ "[Example: member] " \ "[Default: %default]") + parser.add_option("-P", "--ldap-protocol-version", + dest="ldap_protocol_version", default="3", + help="The LDAP protocol version used. " \ + "[Default: %default]") parser.add_option("-u", "--user-query", dest="user_query", default="objectClass=user", help="The query/filter used to identify user objects. " \ @@ -617,7 +654,7 @@ def are_properties_set(): except: # one of the variables may not exist (i.e. not defined at the start of the script) return False - + # bind_password is not checked since if not passed, the user will be prompted # authz_path is not checked since it can be 'None' signifying stdout output @@ -650,7 +687,7 @@ def get_unset_properties(): def main(): """This function is the entry point for this script.""" - + parser = None # If all necessary options are not properly set in the current script file @@ -663,7 +700,7 @@ def main(): # if some properties are not set at this point, there is an error if not are_properties_set(): sys.stderr.write("There is not enough information to proceed.\n") - + for prop in get_unset_properties(): sys.stderr.write("'%s' was not passed\n" % prop) @@ -688,9 +725,9 @@ def main(): sys.stderr.write("Could not connect to %s. Error: %s \n" % (url, error_message)) sys.exit(1) - try: + try: if group_dns: - groups = get_groups(ldapobject) + groups = get_groups(ldapobject) else: groups = search_for_groups(ldapobject) except ldap.LDAPError, error_message: @@ -713,4 +750,4 @@ def main(): # main() if __name__ == "__main__": - main() \ No newline at end of file + main()