def _AnalyzeUserMissingInLDAP(self, args): """ For the syncOneUser command: if the admin has entered an expression which didn't map to any LDAP users, figure out what's what. Is it a user who's in the UserDB but no longer in LDAP? If so, that's probably an exited. Args: args: stripped version of the expression the admin entered Returns: dn of user, or None if a user in UserDB couldn't be found Side effects: displays message to the admin if more than one DN matches the expression, and waits for him to choose one. """ # see if it's in UserDB, but deleted from LDAP: comps = self._SplitExpression(args) if not comps: return (attr, val) = comps if not attr: return dns = self.users.LookupAttrVal(attr, val) if not dns: return None print messages.msg(messages.MSG_FOUND_IN_USERDB, str(len(dns))) return self._ChooseFromList(dns)
def do_syncAllUsers(self, rest): args = rest.strip().lower() if args: if args == 'added': actions = ['added'] elif args == 'exited': actions = ['exited'] elif args == 'updated': actions = ['updated'] elif args == 'renamed': actions = ['renamed'] elif args == 'all': actions = self.sync_google.google_operations else: logging.error(messages.msg(messages.ERR_SYNC_USERS_ACTION)) return else: actions = self.sync_google.google_operations # be sure we can connect, before we spawn a bunch of threads that'll try: errs = self.sync_google.TestConnectivity() if errs: logging.error(messages.msg(messages.ERR_CONNECTING_GOOGLE, errs)) return try: for action in actions: stats = self.sync_google.DoAction(action) if stats is not None: self._ShowSyncStats(stats) except utils.ConfigError, e: logging.error(str(e)) return
def do_showUsers(self, rest): args = rest.strip() start = 1 user_count = self.users.UserCount() if not user_count: logging.info(messages.ERR_NO_USERS) return end = self.users.UserCount() if args: nums = self._GetNumArgs(rest, 2) if not nums: logging.error(messages.msg(messages.ERR_SHOW_USERS_ARGS)) return start = nums[0] if len(nums) > 1: end = nums[1] else: end = start user_count = end - start + 1 # don't just spew out 20,000 users without a warning: if user_count > 10: ans = raw_input(messages.msg(messages.ERR_TOO_MANY_USERS, str(user_count))) first = ans[:1].lower() if first != "y": return dns = self.users.UserDNs() print "Display new users %d to %d" % (start, end) for ix in xrange(start-1, end): print "%d: %s" % (ix + 1, dns[ix]) pp.pprint(self.users.LookupDN(dns[ix]))
def do_updateUsers(self, unused_rest): try: if not self.ldap_context.GetUserFilter(): logging.error(messages.ERR_NO_USER_FILTER) return print messages.msg(messages.MSG_ADDING, self.ldap_context.GetUserFilter()) self._ShowGoogleAttributes() last_update_time.beginNewRun() self.errors = False # add in the condition for "> lastUpdate" search_filter = self.ldap_context.ldap_user_filter self.last_update = None if last_update_time.get(): self.last_update = self._TimeFromLDAPTime(last_update_time.get()) logging.debug('last_update time=%s' % str(self.last_update)) attrs = self.users.GetAttributes() directory_type = _GetDirectoryType(attrs) if self.last_update: search_filter = self._AndUpdateTime(search_filter, self.users.GetTimestampAttributeName(), self.last_update, directory_type) try: found_users = self.ldap_context.Search(filter_arg=search_filter, attrlist=attrs) except RuntimeError,e: logging.exception(str(e)) return if not found_users or found_users.UserCount() == 0: print messages.msg(messages.MSG_FIND_USERS_RETURNED, "0") if found_users: # we need to compute the Google attrs now, since # userdb.AnalyzeChangedUsers uses that: self.users.MapGoogleAttrs(found_users) (adds, mods, renames) = self.users.AnalyzeChangedUsers(found_users) # mark new uses as "to be added to Google" for dn in adds: found_users.SetGoogleAction(dn, 'added') for dn in mods: found_users.SetGoogleAction(dn, 'updated') for dn in renames: found_users.SetGoogleAction(dn, 'renamed') self.users.MergeUsers(found_users) if adds: print messages.msg(messages.MSG_NEW_USERS_ADDED, (str(len(adds)), str(self.users.UserCount()))) if mods: print messages.msg(messages.MSG_UPDATED_USERS_MARKED, (str(len(mods)))) if renames: print messages.msg(messages.MSG_RENAMED_USERS_MARKED, (str(len(renames)))) # find exited users & lock their accounts self._FindExitedUsers()
def do_connect(self, unused_rest): try: if self.ldap_context.Connect(): logging.error(messages.msg(messages.ERR_CONNECT_FAILED)) else: logging.info(messages.msg(messages.MSG_CONNECTED, self.ldap_context.ldap_url)) except utils.ConfigError, e: logging.error(str(e))
def do_showLastUpdate(self, unused_rest): self.last_update = last_update_time.get() if not self.last_update: print messages.MSG_SHOW_NO_LAST_UPDATE else: try: self.last_update = float(self.last_update) except ValueError: logging.exception('bad update time: %s', str(self.last_update)) self.last_update = 0 tstr = time.asctime(time.localtime()) print messages.msg(messages.MSG_SHOW_LAST_UPDATE, tstr)
def _FetchOneUser(self, username): """ Fetch info on a single username from Google, with user feedback """ print messages.msg(messages.MSG_LOOKING_UP, username) user_rec = self.sync_google.FetchOneUser(username) if not user_rec: print messages.msg(messages.ERR_USER_NOT_FOUND, username) else: print messages.MSG_GOOGLE_RETURNED #pp.pprint(user_rec) self._PrintGoogleUserRec(user_rec) return user_rec
def do_writeUsers(self, rest): args = rest.strip() if not args: logging.error(messages.MSG_GIVE_A_FILE_NAME) return else: fname = rest.split(" ")[0] print messages.msg(messages.MSG_WRITE_USERS, fname) try: rejected_attrs = self.users.WriteDataFile(fname) except RuntimeError, e: logging.exception(str(e)) return
def SetConfigVar(self, attr, val): """ To be called AFTER initial config, e.g. when the user does something to change a variable. Args: attr: name of variable val: the value """ if not attr in self._config_parms: return messages.msg(messages.ERR_NO_SUCH_ATTR, attr) try: setattr(self, attr, val) except ValueError: return messages.msg(messages.ERR_INVALID_VALUE, attr)
def do_readUsers(self, rest): args = rest.strip() if not args: logging.error(messages.MSG_GIVE_A_FILE_NAME) return else: fname = rest.split(" ")[0] print messages.msg(messages.MSG_READ_USERS, fname) try: self.users.ReadDataFile(fname) except RuntimeError, e: logging.exception(str(e)) return
def do_batch(self, rest): args = rest.strip() if not args: logging.error(messages.msg(messages.ERR_BATCH_ARG_NEEDED)) return fname = args.split(" ")[0] if not os.path.exists(fname): logging.error(messages.msg(messages.ERR_FILE_NOT_FOUND, fname)) return f = open(fname, "r") for line in f.readlines(): print line self.onecmd(line) f.close()
def _ChooseFromList(self, dns): """ Utility to present the user with a list of DNs and ask them to choose one Args: dns: list of DNs Return: dn of the one chosen, or None if none chosen """ count = len(dns) if count == 1: return dns[0] else: if count > MAX_USER_DISPLAY: logging.info(messages.msg(messages.MSG_HERE_ARE_FIRST_N, str(MAX_USER_DISPLAY))) limit = MAX_USER_DISPLAY for i in xrange(limit): print '%d: %s' % (i, dns[i]) ans = raw_input(messages.MSG_WHICH_USER) try: num = int(ans) if num < 0 or num >= limit: return None return dns[num] except ValueError,e: logging.error(str(e)) return None
def __init__(self, ldap_context, users, google, config): """ Constructor. Args: ldap_context: LDAPCtxt object users: Users object google: sync_google object config: utils.Config object """ cmd.Cmd.__init__(self) self.ldap_context = ldap_context self.users = users self.sync_google = google self._config = config self.new_users = None self.last_update = None # stuff needed by the Cmd superclass: self.completekey = None self.cmdqueue = [] self.prompt = "Command: " self.prompt = messages.msg(messages.CMD_PROMPT) # code only for use of the unittest: self._testing_last_update = None
def DoMain(options): """ main module. A client of the optparse module for command-line parsing. Args: options: first return value from parser.parse_args(), where 'parser' is a optparse.OptionParser """ (config, ldap_context, user_database, google_context, log_config) = \ SetupMain(options) cmd = commands.Commands(ldap_context, user_database, google_context, config) cmd.cmdloop() # write out the config parms: save_config = False if not options.config_file: ans = raw_input(messages.msg(messages.MSG_Q_SAVE_CONFIG)) first = ans[:1].lower() if first == messages.CHAR_YES: options.config_file = GetValidFileFromUser() save_config = True else: ans = raw_input(messages.msg(messages.MSG_Q_SAVE_CONFIG_2, options.config_file)) first = ans[:1].lower() if first == messages.CHAR_YES: save_config = True # if the files can't be written (e.g. on Windows it's open in another window) # we want to allow user to rectify the situation. if save_config and options.config_file: logging.info(messages.msg(messages.MSG_WRITE_CONFIG_FILE, options.config_file)) ldap_context.WriteConfig() user_database.WriteConfig() google_context.WriteConfig() log_config.WriteConfig() while True: try: config.WriteConfig(options.config_file) break except IOError, e: logging.error(str(e)) ans = raw_input(messages.MSG_TRY_AGAIN) if ans[:1] != messages.CHAR_YES: break logging.info(messages.MSG_DONE)
def do_attrList(self, rest): args = rest.strip() if args: toks = args.split(" ") if toks[0].lower() == "show": attrs = self.users.GetAttributes() print messages.msg(messages.MSG_MAPPINGS) pp.pprint(attrs) self._ShowGoogleAttributes() return # if here, we require a second argument if len(toks) < 2: logging.error(messages.msg(messages.MSG_USAGE_ATTR_LIST)) return attr = toks[1] if toks[0].lower() == "add": self.users.AddAttribute(attr) elif toks[0].lower() == "remove": if not attr in self.users.GetAttributes(): logging.error(messages.msg(messages.ERR_NO_SUCH_ATTR, attr)) return count = self.users.RemoveAttribute(attr) print messages.msg(messages.MSG_ATTR_REMOVED, (attr, str(count))) else: logging.error(messages.msg(messages.MSG_USAGE_ATTR_LIST))
def do_mapGoogleAttribute(self, rest): line = rest.strip() if not line: print messages.msg(messages.ERR_MAP_ATTR) return toks = line.split(" ") attr = toks[0] if not attr in self.users.GetGoogleMappings(): print messages.msg(messages.ERR_NO_SUCH_ATTR, attr) return if len(toks) > 1: mapping = rest.replace(attr, "", 1) else: mapping = raw_input(messages.MSG_ENTER_EXPRESSION) # test the expression print messages.msg(messages.MSG_TESTING_MAPPING) err = self.users.TestMapping(mapping.strip()) if err: logging.error(messages.msg(messages.ERR_MAPPING_FAILED)) logging.error(err) return self.users.MapAttr(attr, mapping) print messages.MSG_DONE
def SetConfigVar(self, attr, val): """ Overrides: Configurable.SetConfigVar Imposes some rules on which config vars can be set via the 'set' command, mainly for the multi-valued ones, whose syntax would be very difficult for users to get right (so there are special routines provided) """ if not attr in self.config_parms: return messages.msg(messages.ERR_NO_SUCH_ATTR, attr) if attr == "mapping": return messages.ERR_NO_SET_MAPPING elif attr == "attrs": return messages.ERR_NO_SET_ATTRS else: try: setattr(self, attr, val) except ValueError: return messages.msg(messages.ERR_INVALID_VALUE, attr)
def do_markUsers(self, rest): args = rest.strip() if not args: logging.error(messages.msg(messages.ERR_MARK_USERS)) logging.error(messages.msg(messages.HELP_MARK_USERS)) return toks = args.split(' ') if len(toks) < 2 or len(toks) > 3: logging.error(messages.msg(messages.ERR_MARK_USERS)) logging.error(messages.msg(messages.HELP_MARK_USERS)) return first_str = toks[0] second_str = None second = None if len(toks) == 3: second_str = toks[1] action = toks[2] else: action = toks[1] try: s = first_str first = int(first_str) second = first if second_str: s = second_str second = int(second_str) except ValueError: logging.exception('%s\n' % messages.msg(messages.ERR_ENTER_NUMBER, s)) return # parse the action if action != 'added' and action != 'exited' and action != 'update': logging.error(messages.msg(messages.ERR_MARK_USERS_ACTION)) logging.error(messages.msg(messages.HELP_MARK_USERS)) return else: dns = self.users.UserDNs() if first < 0 or first > len(dns): logging.error(messages.msg(messages.ERR_NUMBER_OUT_OF_RANGE, first_str)) return if second: if second < 0 or second > len(dns) or second < first: logging.error(messages.msg(messages.ERR_NUMBER_OUT_OF_RANGE, second_str)) return for ix in xrange(first, second + 1): self.users.SetGoogleAction(dns[ix], action)
def GetValidFileFromUser(): """ Prompt the user for a writeable file name, until he/she either does, or gives up Return: fname: name of the file """ fname = None while not fname: fname = raw_input(messages.msg(messages.MSG_GIVE_A_FILE_NAME)) if not fname: return None try: f = open(fname, 'w') f.close() return fname except IOError: sys.stderr.write('%s\n' % messages.msg(messages.ERR_NOT_VALID_FILE_NAME, fname)) fname = None
def do_summarizeUsers(self, unused_rest): total = 0 added = 0 exited = 0 updated = 0 renamed = 0 for (dn, attrs) in self.users.db.iteritems(): total += 1 if 'meta-Google-action' in attrs: action = attrs['meta-Google-action'] if action == 'added': added += 1 elif action == 'exited': exited += 1 elif action == 'renamed': renamed += 1 elif action == 'updated': updated += 1 print messages.msg(messages.MSG_USER_SUMMARY, (str(total), str(added), str(exited), str(renamed), str(updated)))
def SetConfigVar(self, attr, val): """ Overrides: the superclass method, in order to provide more validation Args attr: name of config variable val: value Raises: ValueError: if the value is not valid, e.g. not a number when a number is required. """ if not attr in self.config_parms: return messages.msg(messages.ERR_NO_SUCH_ATTR, attr) try: if attr == 'ldap_timeout': try: self.ldap_timeout = float(val) except ValueError: return messages.msg(messages.ERR_ENTER_NUMBER, val) else: setattr(self, attr, val) except ValueError: return messages.msg(messages.ERR_INVALID_VALUE, attr)
def convert_wac(filein, fileout, codec='utf8', line_funcs=[]): """Remove xml tags and extra newlines. The resulting file will have sentences separated with a newline. Args: filein (str): the path to the input file fileout (str): the path to the output file codec (str, optional): the codecs used to decode the file. Default 'utf8' """ XML_TAG = re.compile(r'<[^<]+>') filein = codecs.open(filein, 'r', codec) fileout = codecs.open(fileout, 'w', codec) messages.msg("Converting file...") try: last_is_newline = True for line in messages.pbar(filein): line = XML_TAG.sub('', line) if line == '\n': if last_is_newline: pass else: fileout.write(line) last_is_newline = True else: for func in line_funcs: line = func(line) if line: fileout.write(line) last_is_newline = False except Exception as e: filein.close() fileout.close() raise e filein.close() fileout.close() messages.done()
def do_set(self, rest): args = rest.strip() if not len(args): self.help_set() return toks = rest.split(' ') # see if it's a real variable owner = self._config.FindOwner(toks[0]) if owner == None: logging.error(messages.msg(messages.ERR_NO_SUCH_ATTR, toks[0])) return value = ' '.join(toks[1:]) msg = owner.SetConfigVar(toks[0], value) if msg: logging.error(msg)
def _SplitExpression(self, expr): """ For an admin-typed expression, e.g. givenName=joe, split it into two components around the equals sign. Args: expr: as entered by the admin Returns: (None, None) if the expression couldn't be split, or attr: name of the attribute value: value """ ix = expr.find('=') if ix <= 0: logging.error(messages.msg(messages.ERR_CANT_USE_EXPR, expr)) return (None, None) attr = expr[:ix].strip() val = expr[ix + 1:].strip() return (attr, val)
def _ShowSyncStats(self, stats): """ Display the results of a "sync to Google" operation. The assumption is that 'stats' will contain all members of ThreadStats.stat_names but that some will be zero. If either <op>s or <op>_fails is non-zero, then a line concerning <op> will be displayed, op in {'add', 'exit', 'rename', 'update'} Args: stats: return value of all sync_google.Do_<operation>. An instance of sync_google.ThreadStats. """ if stats['adds'] > 0 or stats['add_fails'] > 0: print messages.msg(messages.MSG_ADD_RESULTS, (stats['adds'], stats['add_fails'])) if stats['exits'] > 0 or stats['exit_fails'] > 0: print messages.msg(messages.MSG_EXITED_RESULTS, (stats['exits'], stats['exit_fails'])) if stats['renames'] > 0 or stats['rename_fails'] > 0: print messages.msg(messages.MSG_RENAME_RESULTS, (stats['renames'], stats['rename_fails'])) if stats['updates'] > 0 or stats['update_fails'] > 0: print messages.msg(messages.MSG_UPDATE_RESULTS, (stats['updates'], stats['update_fails']))
def _FindExitedUsers(self): """ Finding "exited" users: if we have a special filter for that, use it. Else do the search without the "> lastUpdate" filter, to find users no longer in the DB. Even if we DO have a ldap_disabled_filter, still check for deleted entries, since you never know what might have happened. """ total_exits = 0 if (self.ldap_context.ldap_disabled_filter and self.users.GetTimestampAttributeName()): attrs = self.users.GetAttributes() directory_type = _GetDirectoryType(attrs) search_filter = self._AndUpdateTime( self.ldap_context.ldap_disabled_filter, self.users.GetTimestampAttributeName(), self.last_update, directory_type) try: logging.debug(messages.msg(messages.MSG_FIND_EXITS, self.ldap_context.ldap_disabled_filter)) userdb_exits = self.ldap_context.Search(filter_arg=search_filter, attrlist=attrs) if not userdb_exits: return logging.debug('userdb_exits=%s' % userdb_exits.UserDNs()) exited_users = userdb_exits.UserDNs() for dn in exited_users: # Note: users previously marked added can be reset to exited # if they match the exit filter. This ensures # added_user_google_action is never called on a locked user that # exists in Google Apps self.users.SetGoogleAction(dn, 'exited') total_exits += 1 except RuntimeError,e: logging.exception(str(e)) return
def help_stop(self): print messages.msg(messages.HELP_STOP)
def help_readUsers(self): print messages.msg(messages.HELP_READ_USERS)
def help_writeUsers(self): print messages.msg(messages.HELP_WRITE_USERS)
def help_batch(self): print messages.msg(messages.HELP_BATCH)
def do_stop(self, unused_rest): print messages.msg(messages.MSG_STOPPING) # don't know where this is documented, but returning something # other than None stops the cmdloop() return -1